Add support for reinstalling a server

This commit is contained in:
Dane Everitt 2020-04-03 13:43:13 -07:00
parent ddca34f9e8
commit 019d028011
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
4 changed files with 72 additions and 1 deletions

15
http.go
View File

@ -479,6 +479,20 @@ func (rt *Router) routeCreateServer(w http.ResponseWriter, r *http.Request, ps h
w.WriteHeader(http.StatusAccepted) w.WriteHeader(http.StatusAccepted)
} }
func (rt *Router) routeServerReinstall(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
s := rt.GetServer(ps.ByName("server"))
defer r.Body.Close()
zap.S().Infow("beginning reinstall process for server", zap.String("server", s.Uuid))
go func(server *server.Server) {
if err := server.Reinstall(); err != nil {
zap.S().Errorw("failed to complete server reinstall process", zap.String("server", server.Uuid), zap.Error(err))
}
}(s)
w.WriteHeader(http.StatusAccepted)
}
func (rt *Router) routeSystemInformation(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { func (rt *Router) routeSystemInformation(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
defer r.Body.Close() defer r.Body.Close()
@ -573,6 +587,7 @@ func (rt *Router) ConfigureRouter() *httprouter.Router {
router.POST("/api/servers/:server/files/delete", rt.AuthenticateRequest(rt.routeServerDeleteFile)) router.POST("/api/servers/:server/files/delete", rt.AuthenticateRequest(rt.routeServerDeleteFile))
router.POST("/api/servers/:server/power", rt.AuthenticateRequest(rt.routeServerPower)) router.POST("/api/servers/:server/power", rt.AuthenticateRequest(rt.routeServerPower))
router.POST("/api/servers/:server/commands", rt.AuthenticateRequest(rt.routeServerSendCommand)) router.POST("/api/servers/:server/commands", rt.AuthenticateRequest(rt.routeServerSendCommand))
router.POST("/api/servers/:server/reinstall", rt.AuthenticateRequest(rt.routeServerReinstall))
router.PATCH("/api/servers/:server", rt.AuthenticateRequest(rt.routeServerUpdate)) router.PATCH("/api/servers/:server", rt.AuthenticateRequest(rt.routeServerUpdate))
router.DELETE("/api/servers/:server", rt.AuthenticateRequest(rt.routeServerDelete)) router.DELETE("/api/servers/:server", rt.AuthenticateRequest(rt.routeServerDelete))

View File

@ -31,6 +31,11 @@ type Environment interface {
// not be returned. // not be returned.
Stop() error Stop() error
// Waits for a server instance to stop gracefully. If the server is still detected
// as running after seconds, an error will be returned, or the server will be terminated
// depending on the value of the second argument.
WaitForStop(seconds int, terminate bool) error
// Determines if the server instance exists. For example, in a docker environment // Determines if the server instance exists. For example, in a docker environment
// this should confirm that the container is created and in a bootable state. In // this should confirm that the container is created and in a bootable state. In
// a basic CLI environment this can probably just return true right away. // a basic CLI environment this can probably just return true right away.

View File

@ -267,6 +267,44 @@ func (d *DockerEnvironment) Stop() error {
return d.Client.ContainerStop(context.Background(), d.Server.Uuid, &t) return d.Client.ContainerStop(context.Background(), d.Server.Uuid, &t)
} }
// Attempts to gracefully stop a server using the defined stop command. If the server
// does not stop after seconds have passed, an error will be returned, or the instance
// will be terminated forcefully depending on the value of the second argument.
func (d *DockerEnvironment) WaitForStop(seconds int, terminate bool) error {
if d.Server.State == ProcessOfflineState {
return nil
}
if err := d.Stop(); err != nil {
return errors.WithStack(err)
}
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(seconds)*time.Second)
defer cancel()
// Block the return of this function until the container as been marked as no
// longer running. If this wait does not end by the time seconds have passed,
// attempt to terminate the container, or return an error.
ok, errChan := d.Client.ContainerWait(ctx, d.Server.Uuid, container.WaitConditionNotRunning)
select {
case <-ctx.Done():
if ctxErr := ctx.Err(); ctxErr != nil {
if terminate {
return d.Terminate(os.Kill)
}
return errors.WithStack(ctxErr)
}
case err := <-errChan:
if err != nil {
return errors.WithStack(err)
}
case <-ok:
}
return nil
}
// Forcefully terminates the container using the signal passed through. // Forcefully terminates the container using the signal passed through.
func (d *DockerEnvironment) Terminate(signal os.Signal) error { func (d *DockerEnvironment) Terminate(signal os.Signal) error {
ctx := context.Background() ctx := context.Background()

View File

@ -36,6 +36,19 @@ func (s *Server) Install() error {
return err return err
} }
// Reinstalls a server's software by utilizing the install script for the server egg. This
// does not touch any existing files for the server, other than what the script modifies.
func (s *Server) Reinstall() error {
if s.State != ProcessOfflineState {
zap.S().Debugw("waiting for server instance to enter a stopped state", zap.String("server", s.Uuid))
if err := s.Environment.WaitForStop(10, true); err != nil {
return err
}
}
return s.Install()
}
// Internal installation function used to simplify reporting back to the Panel. // Internal installation function used to simplify reporting back to the Panel.
func (s *Server) internalInstall() error { func (s *Server) internalInstall() error {
script, rerr, err := api.NewRequester().GetInstallationScript(s.Uuid) script, rerr, err := api.NewRequester().GetInstallationScript(s.Uuid)