server(install): update installation status request

The new request includes a `reinstall` option to denote
whether the installation failed for the first time, or
during a reinstall.

ref https://github.com/pterodactyl/panel/issues/1994
This commit is contained in:
Matthew Penner 2022-11-21 14:57:44 -07:00
parent 51cb6dfa42
commit da94f750ad
No known key found for this signature in database
6 changed files with 72 additions and 53 deletions

View File

@ -29,7 +29,7 @@ type Client interface {
SetArchiveStatus(ctx context.Context, uuid string, successful bool) error SetArchiveStatus(ctx context.Context, uuid string, successful bool) error
SetBackupStatus(ctx context.Context, backup string, data BackupRequest) error SetBackupStatus(ctx context.Context, backup string, data BackupRequest) error
SendRestorationStatus(ctx context.Context, backup string, successful bool) error SendRestorationStatus(ctx context.Context, backup string, successful bool) error
SetInstallationStatus(ctx context.Context, uuid string, successful bool) error SetInstallationStatus(ctx context.Context, uuid string, data InstallStatusRequest) error
SetTransferStatus(ctx context.Context, uuid string, successful bool) error SetTransferStatus(ctx context.Context, uuid string, successful bool) error
ValidateSftpCredentials(ctx context.Context, request SftpAuthRequest) (SftpAuthResponse, error) ValidateSftpCredentials(ctx context.Context, request SftpAuthRequest) (SftpAuthResponse, error)
SendActivityLogs(ctx context.Context, activity []models.Activity) error SendActivityLogs(ctx context.Context, activity []models.Activity) error

View File

@ -19,7 +19,7 @@ const (
ProcessStopNativeStop = "stop" ProcessStopNativeStop = "stop"
) )
// GetServers returns all of the servers that are present on the Panel making // GetServers returns all the servers that are present on the Panel making
// parallel API calls to the endpoint if more than one page of servers is // parallel API calls to the endpoint if more than one page of servers is
// returned. // returned.
func (c *client) GetServers(ctx context.Context, limit int) ([]RawServerData, error) { func (c *client) GetServers(ctx context.Context, limit int) ([]RawServerData, error) {
@ -58,7 +58,7 @@ func (c *client) GetServers(ctx context.Context, limit int) ([]RawServerData, er
// //
// This handles Wings exiting during either of these processes which will leave // This handles Wings exiting during either of these processes which will leave
// things in a bad state within the Panel. This API call is executed once Wings // things in a bad state within the Panel. This API call is executed once Wings
// has fully booted all of the servers. // has fully booted all the servers.
func (c *client) ResetServersState(ctx context.Context) error { func (c *client) ResetServersState(ctx context.Context) error {
res, err := c.Post(ctx, "/servers/reset", nil) res, err := c.Post(ctx, "/servers/reset", nil)
if err != nil { if err != nil {
@ -92,8 +92,8 @@ func (c *client) GetInstallationScript(ctx context.Context, uuid string) (Instal
return config, err return config, err
} }
func (c *client) SetInstallationStatus(ctx context.Context, uuid string, successful bool) error { func (c *client) SetInstallationStatus(ctx context.Context, uuid string, data InstallStatusRequest) error {
resp, err := c.Post(ctx, fmt.Sprintf("/servers/%s/install", uuid), d{"successful": successful}) resp, err := c.Post(ctx, fmt.Sprintf("/servers/%s/install", uuid), data)
if err != nil { if err != nil {
return err return err
} }
@ -127,7 +127,7 @@ func (c *client) SetTransferStatus(ctx context.Context, uuid string, successful
// password combination provided is associated with a valid server on the instance // password combination provided is associated with a valid server on the instance
// using the Panel's authentication control mechanisms. This will get itself // using the Panel's authentication control mechanisms. This will get itself
// throttled if too many requests are made, allowing us to completely offload // throttled if too many requests are made, allowing us to completely offload
// all of the authorization security logic to the Panel. // all the authorization security logic to the Panel.
func (c *client) ValidateSftpCredentials(ctx context.Context, request SftpAuthRequest) (SftpAuthResponse, error) { func (c *client) ValidateSftpCredentials(ctx context.Context, request SftpAuthRequest) (SftpAuthResponse, error) {
var auth SftpAuthResponse var auth SftpAuthResponse
res, err := c.Post(ctx, "/sftp/auth", request) res, err := c.Post(ctx, "/sftp/auth", request)

View File

@ -92,8 +92,8 @@ type SftpAuthResponse struct {
} }
type OutputLineMatcher struct { type OutputLineMatcher struct {
// The raw string to match against. This may or may not be prefixed with // raw string to match against. This may or may not be prefixed with
// regex: which indicates we want to match against the regex expression. // `regex:` which indicates we want to match against the regex expression.
raw []byte raw []byte
reg *regexp.Regexp reg *regexp.Regexp
} }
@ -139,9 +139,9 @@ type ProcessStopConfiguration struct {
} }
// ProcessConfiguration defines the process configuration for a given server // ProcessConfiguration defines the process configuration for a given server
// instance. This sets what Wings is looking for to mark a server as done starting // instance. This sets what Wings is looking for to mark a server as done
// what to do when stopping, and what changes to make to the configuration file // starting what to do when stopping, and what changes to make to the
// for a server. // configuration file for a server.
type ProcessConfiguration struct { type ProcessConfiguration struct {
Startup struct { Startup struct {
Done []*OutputLineMatcher `json:"done"` Done []*OutputLineMatcher `json:"done"`
@ -169,3 +169,8 @@ type BackupRequest struct {
Successful bool `json:"successful"` Successful bool `json:"successful"`
Parts []BackupPart `json:"parts"` Parts []BackupPart `json:"parts"`
} }
type InstallStatusRequest struct {
Successful bool `json:"successful"`
Reinstall bool `json:"reinstall"`
}

View File

@ -153,9 +153,15 @@ func postServerSync(c *gin.Context) {
func postServerInstall(c *gin.Context) { func postServerInstall(c *gin.Context) {
s := ExtractServer(c) s := ExtractServer(c)
go func(serv *server.Server) { go func(s *server.Server) {
if err := serv.Install(true); err != nil { s.Log().Info("syncing server state with remote source before executing installation process")
serv.Log().WithField("error", err).Error("failed to execute server installation process") if err := s.Sync(); err != nil {
s.Log().WithField("error", err).Error("failed to sync server state with Panel")
return
}
if err := s.Install(); err != nil {
s.Log().WithField("error", err).Error("failed to execute server installation process")
} }
}(s) }(s)

View File

@ -75,7 +75,7 @@ func postCreateServer(c *gin.Context) {
return return
} }
if err := i.Server().Install(false); err != nil { if err := i.Server().Install(); err != nil {
log.WithFields(log.Fields{"server": i.Server().ID(), "error": err}).Error("failed to run install process for server") log.WithFields(log.Fields{"server": i.Server().ID(), "error": err}).Error("failed to run install process for server")
return return
} }

View File

@ -32,19 +32,17 @@ import (
// //
// Pass true as the first argument in order to execute a server sync before the // Pass true as the first argument in order to execute a server sync before the
// process to ensure the latest information is used. // process to ensure the latest information is used.
func (s *Server) Install(sync bool) error { func (s *Server) Install() error {
if sync { return s.install(false)
s.Log().Info("syncing server state with remote source before executing installation process")
if err := s.Sync(); err != nil {
return errors.WrapIf(err, "install: failed to sync server state with Panel")
}
} }
func (s *Server) install(reinstall bool) error {
var err error var err error
if !s.Config().SkipEggScripts { if !s.Config().SkipEggScripts {
// Send the start event so the Panel can automatically update. We don't send this unless the process // Send the start event so the Panel can automatically update. We don't
// is actually going to run, otherwise all sorts of weird rapid UI behavior happens since there isn't // send this unless the process is actually going to run, otherwise all
// an actual install process being executed. // sorts of weird rapid UI behavior happens since there isn't an actual
// install process being executed.
s.Events().Publish(InstallStartedEvent, "") s.Events().Publish(InstallStartedEvent, "")
err = s.internalInstall() err = s.internalInstall()
@ -53,12 +51,13 @@ func (s *Server) Install(sync bool) error {
} }
s.Log().WithField("was_successful", err == nil).Debug("notifying panel of server install state") s.Log().WithField("was_successful", err == nil).Debug("notifying panel of server install state")
if serr := s.SyncInstallState(err == nil); serr != nil { if serr := s.SyncInstallState(err == nil, reinstall); serr != nil {
l := s.Log().WithField("was_successful", err == nil) l := s.Log().WithField("was_successful", err == nil)
// If the request was successful but there was an error with this request, attach the // If the request was successful but there was an error with this request,
// error to this log entry. Otherwise ignore it in this log since whatever is calling // attach the error to this log entry. Otherwise, ignore it in this log
// this function should handle the error and will end up logging the same one. // since whatever is calling this function should handle the error and
// will end up logging the same one.
if err == nil { if err == nil {
l.WithField("error", err) l.WithField("error", err)
} }
@ -66,19 +65,20 @@ func (s *Server) Install(sync bool) error {
l.Warn("failed to notify panel of server install state") l.Warn("failed to notify panel of server install state")
} }
// Ensure that the server is marked as offline at this point, otherwise you end up // Ensure that the server is marked as offline at this point, otherwise you
// with a blank value which is a bit confusing. // end up with a blank value which is a bit confusing.
s.Environment.SetState(environment.ProcessOfflineState) s.Environment.SetState(environment.ProcessOfflineState)
// Push an event to the websocket so we can auto-refresh the information in the panel once // Push an event to the websocket, so we can auto-refresh the information in
// the install is completed. // the panel once the installation is completed.
s.Events().Publish(InstallCompletedEvent, "") s.Events().Publish(InstallCompletedEvent, "")
return errors.WithStackIf(err) return errors.WithStackIf(err)
} }
// Reinstalls a server's software by utilizing the install script for the server egg. This // Reinstall reinstalls a server's software by utilizing the installation script
// does not touch any existing files for the server, other than what the script modifies. // 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 { func (s *Server) Reinstall() error {
if s.Environment.State() != environment.ProcessOfflineState { if s.Environment.State() != environment.ProcessOfflineState {
s.Log().Debug("waiting for server instance to enter a stopped state") s.Log().Debug("waiting for server instance to enter a stopped state")
@ -87,7 +87,12 @@ func (s *Server) Reinstall() error {
} }
} }
return s.Install(true) s.Log().Info("syncing server state with remote source before executing re-installation process")
if err := s.Sync(); err != nil {
return errors.WrapIf(err, "install: failed to sync server state with Panel")
}
return s.install(true)
} }
// Internal installation function used to simplify reporting back to the Panel. // Internal installation function used to simplify reporting back to the Panel.
@ -116,8 +121,9 @@ type InstallationProcess struct {
client *client.Client client *client.Client
} }
// Generates a new installation process struct that will be used to create containers, // NewInstallationProcess returns a new installation process struct that will be
// and otherwise perform installation commands for a server. // used to create containers and otherwise perform installation commands for a
// server.
func NewInstallationProcess(s *Server, script *remote.InstallationScript) (*InstallationProcess, error) { func NewInstallationProcess(s *Server, script *remote.InstallationScript) (*InstallationProcess, error) {
proc := &InstallationProcess{ proc := &InstallationProcess{
Script: script, Script: script,
@ -133,8 +139,8 @@ func NewInstallationProcess(s *Server, script *remote.InstallationScript) (*Inst
return proc, nil return proc, nil
} }
// Determines if the server is actively running the installation process by checking the status // IsInstalling returns if the server is actively running the installation
// of the installer lock. // process by checking the status of the installer lock.
func (s *Server) IsInstalling() bool { func (s *Server) IsInstalling() bool {
return s.installing.Load() return s.installing.Load()
} }
@ -155,7 +161,7 @@ func (s *Server) SetRestoring(state bool) {
s.restoring.Store(state) s.restoring.Store(state)
} }
// Removes the installer container for the server. // RemoveContainer removes the installation container for the server.
func (ip *InstallationProcess) RemoveContainer() error { func (ip *InstallationProcess) RemoveContainer() error {
err := ip.client.ContainerRemove(ip.Server.Context(), ip.Server.ID()+"_installer", types.ContainerRemoveOptions{ err := ip.client.ContainerRemove(ip.Server.Context(), ip.Server.ID()+"_installer", types.ContainerRemoveOptions{
RemoveVolumes: true, RemoveVolumes: true,
@ -328,14 +334,14 @@ func (ip *InstallationProcess) BeforeExecute() error {
return nil return nil
} }
// Returns the log path for the installation process. // GetLogPath returns the log path for the installation process.
func (ip *InstallationProcess) GetLogPath() string { func (ip *InstallationProcess) GetLogPath() string {
return filepath.Join(config.Get().System.LogDirectory, "/install", ip.Server.ID()+".log") return filepath.Join(config.Get().System.LogDirectory, "/install", ip.Server.ID()+".log")
} }
// Cleans up after the execution of the installation process. This grabs the logs from the // AfterExecute cleans up after the execution of the installation process.
// process to store in the server configuration directory, and then destroys the associated // This grabs the logs from the process to store in the server configuration
// installation container. // directory, and then destroys the associated installation container.
func (ip *InstallationProcess) AfterExecute(containerId string) error { func (ip *InstallationProcess) AfterExecute(containerId string) error {
defer ip.RemoveContainer() defer ip.RemoveContainer()
@ -525,7 +531,7 @@ func (ip *InstallationProcess) StreamOutput(ctx context.Context, id string) erro
return nil return nil
} }
// resourceLimits returns the install container specific resource limits. This // resourceLimits returns resource limits for the installation container. This
// looks at the globally defined install container limits and attempts to use // looks at the globally defined install container limits and attempts to use
// the higher of the two (defined limits & server limits). This allows for servers // the higher of the two (defined limits & server limits). This allows for servers
// with super low limits (e.g. Discord bots with 128Mb of memory) to perform more // with super low limits (e.g. Discord bots with 128Mb of memory) to perform more
@ -537,8 +543,8 @@ func (ip *InstallationProcess) StreamOutput(ctx context.Context, id string) erro
func (ip *InstallationProcess) resourceLimits() container.Resources { func (ip *InstallationProcess) resourceLimits() container.Resources {
limits := config.Get().Docker.InstallerLimits limits := config.Get().Docker.InstallerLimits
// Create a copy of the configuration so we're not accidentally making changes // Create a copy of the configuration, so we're not accidentally making
// to the underlying server build data. // changes to the underlying server build data.
c := *ip.Server.Config() c := *ip.Server.Config()
cfg := c.Build cfg := c.Build
if cfg.MemoryLimit < limits.Memory { if cfg.MemoryLimit < limits.Memory {
@ -562,10 +568,12 @@ func (ip *InstallationProcess) resourceLimits() container.Resources {
return resources return resources
} }
// SyncInstallState makes a HTTP request to the Panel instance notifying it that // SyncInstallState makes an HTTP request to the Panel instance notifying it that
// the server has completed the installation process, and what the state of the // the server has completed the installation process, and what the state of the
// server is. A boolean value of "true" means everything was successful, "false" // server is.
// means something went wrong and the server must be deleted and re-created. func (s *Server) SyncInstallState(successful, reinstall bool) error {
func (s *Server) SyncInstallState(successful bool) error { return s.client.SetInstallationStatus(s.Context(), s.ID(), remote.InstallStatusRequest{
return s.client.SetInstallationStatus(s.Context(), s.ID(), successful) Successful: successful,
Reinstall: reinstall,
})
} }