Don't abort entire boot process due to one bad server egg; closes pterodactyl/panel#2448

This commit is contained in:
Dane Everitt 2020-10-17 12:06:47 -07:00
parent ad1ed0f24a
commit 947279a07c
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
6 changed files with 34 additions and 26 deletions

View File

@ -34,7 +34,7 @@ type InstallationScript struct {
} }
// GetAllServerConfigurations fetches configurations for all servers assigned to this node. // GetAllServerConfigurations fetches configurations for all servers assigned to this node.
func (r *PanelRequest) GetAllServerConfigurations() (map[string]*ServerConfigurationResponse, *RequestError, error) { func (r *PanelRequest) GetAllServerConfigurations() (map[string]json.RawMessage, *RequestError, error) {
resp, err := r.Get("/servers") resp, err := r.Get("/servers")
if err != nil { if err != nil {
return nil, nil, errors.WithStack(err) return nil, nil, errors.WithStack(err)
@ -48,7 +48,7 @@ func (r *PanelRequest) GetAllServerConfigurations() (map[string]*ServerConfigura
} }
b, _ := r.ReadBody() b, _ := r.ReadBody()
res := map[string]*ServerConfigurationResponse{} res := map[string]json.RawMessage{}
if len(b) == 2 { if len(b) == 2 {
return res, nil, nil return res, nil, nil
} }
@ -61,24 +61,23 @@ func (r *PanelRequest) GetAllServerConfigurations() (map[string]*ServerConfigura
} }
// Fetches the server configuration and returns the struct for it. // Fetches the server configuration and returns the struct for it.
func (r *PanelRequest) GetServerConfiguration(uuid string) (*ServerConfigurationResponse, *RequestError, error) { func (r *PanelRequest) GetServerConfiguration(uuid string) (ServerConfigurationResponse, *RequestError, error) {
res := ServerConfigurationResponse{}
resp, err := r.Get(fmt.Sprintf("/servers/%s", uuid)) resp, err := r.Get(fmt.Sprintf("/servers/%s", uuid))
if err != nil { if err != nil {
return nil, nil, errors.WithStack(err) return res, nil, errors.WithStack(err)
} }
defer resp.Body.Close() defer resp.Body.Close()
r.Response = resp r.Response = resp
if r.HasError() { if r.HasError() {
return nil, r.Error(), nil return res, r.Error(), nil
} }
res := &ServerConfigurationResponse{}
b, _ := r.ReadBody() b, _ := r.ReadBody()
if err := json.Unmarshal(b, &res); err != nil {
if err := json.Unmarshal(b, res); err != nil { return res, nil, errors.WithStack(err)
return nil, nil, errors.WithStack(err)
} }
return res, nil, nil return res, nil, nil

View File

@ -14,7 +14,7 @@ import (
type Metadata struct { type Metadata struct {
Image string Image string
Stop *api.ProcessStopConfiguration Stop api.ProcessStopConfiguration
} }
// Ensure that the Docker environment is always implementing all of the methods // Ensure that the Docker environment is always implementing all of the methods
@ -171,7 +171,7 @@ func (e *Environment) Config() *environment.Configuration {
} }
// Sets the stop configuration for the environment. // Sets the stop configuration for the environment.
func (e *Environment) SetStopConfiguration(c *api.ProcessStopConfiguration) { func (e *Environment) SetStopConfiguration(c api.ProcessStopConfiguration) {
e.mu.Lock() e.mu.Lock()
e.meta.Stop = c e.meta.Stop = c
e.mu.Unlock() e.mu.Unlock()

View File

@ -126,8 +126,8 @@ func (e *Environment) Stop() error {
s := e.meta.Stop s := e.meta.Stop
e.mu.RUnlock() e.mu.RUnlock()
if s == nil || s.Type == api.ProcessStopSignal { if s.Type == "" || s.Type == api.ProcessStopSignal {
if s == nil { if s.Type == "" {
log.WithField("container_id", e.Id).Warn("no stop configuration detected for environment, using termination procedure") log.WithField("container_id", e.Id).Warn("no stop configuration detected for environment, using termination procedure")
} }

View File

@ -33,13 +33,11 @@ func (e *Environment) SendCommand(c string) error {
e.mu.RLock() e.mu.RLock()
defer e.mu.RUnlock() defer e.mu.RUnlock()
if e.meta.Stop != nil { // If the command being processed is the same as the process stop command then we want to mark
// If the command being processed is the same as the process stop command then we want to mark // the server as entering the stopping state otherwise the process will stop and Wings will think
// the server as entering the stopping state otherwise the process will stop and Wings will think // it has crashed and attempt to restart it.
// it has crashed and attempt to restart it. if e.meta.Stop.Type == "command" && c == e.meta.Stop.Value {
if e.meta.Stop.Type == "command" && c == e.meta.Stop.Value { e.Events().Publish(environment.StateChangeEvent, environment.ProcessStoppingState)
e.Events().Publish(environment.StateChangeEvent, environment.ProcessStoppingState)
}
} }
_, err := e.stream.Conn.Write([]byte(c + "\n")) _, err := e.stream.Conn.Write([]byte(c + "\n"))

View File

@ -1,6 +1,7 @@
package server package server
import ( import (
"encoding/json"
"fmt" "fmt"
"github.com/apex/log" "github.com/apex/log"
"github.com/creasty/defaults" "github.com/creasty/defaults"
@ -49,8 +50,18 @@ func LoadDirectory() error {
data := data data := data
pool.Submit(func() { pool.Submit(func() {
// Parse the json.RawMessage into an expected struct value. We do this here so that a single broken
// server does not cause the entire boot process to hang, and allows us to show more useful error
// messaging in the output.
d := api.ServerConfigurationResponse{}
log.WithField("server", uuid).Info("creating new server object from API response") log.WithField("server", uuid).Info("creating new server object from API response")
s, err := FromConfiguration(data) if err := json.Unmarshal(data, &d); err != nil {
log.WithField("server", uuid).WithField("error", err).Error("failed to parse server configuration from API response, skipping...")
return
}
s, err := FromConfiguration(d)
if err != nil { if err != nil {
log.WithField("server", uuid).WithField("error", err).Error("failed to load server, skipping...") log.WithField("server", uuid).WithField("error", err).Error("failed to load server, skipping...")
return return
@ -73,7 +84,7 @@ func LoadDirectory() error {
// Initializes a server using a data byte array. This will be marshaled into the // Initializes a server using a data byte array. This will be marshaled into the
// given struct using a YAML marshaler. This will also configure the given environment // given struct using a YAML marshaler. This will also configure the given environment
// for a server. // for a server.
func FromConfiguration(data *api.ServerConfigurationResponse) (*Server, error) { func FromConfiguration(data api.ServerConfigurationResponse) (*Server, error) {
cfg := Configuration{} cfg := Configuration{}
if err := defaults.Set(&cfg); err != nil { if err := defaults.Set(&cfg); err != nil {
return nil, errors.Wrap(err, "failed to set struct defaults for server configuration") return nil, errors.Wrap(err, "failed to set struct defaults for server configuration")

View File

@ -128,7 +128,7 @@ func (s *Server) Sync() error {
return s.SyncWithConfiguration(cfg) return s.SyncWithConfiguration(cfg)
} }
func (s *Server) SyncWithConfiguration(cfg *api.ServerConfigurationResponse) error { func (s *Server) SyncWithConfiguration(cfg api.ServerConfigurationResponse) error {
// Update the data structure and persist it to the disk. // Update the data structure and persist it to the disk.
if err := s.UpdateDataStructure(cfg.Settings); err != nil { if err := s.UpdateDataStructure(cfg.Settings); err != nil {
return errors.WithStack(err) return errors.WithStack(err)
@ -147,7 +147,7 @@ func (s *Server) SyncWithConfiguration(cfg *api.ServerConfigurationResponse) err
if e, ok := s.Environment.(*docker.Environment); ok { if e, ok := s.Environment.(*docker.Environment); ok {
s.Log().Debug("syncing stop configuration with configured docker environment") s.Log().Debug("syncing stop configuration with configured docker environment")
e.SetImage(s.Config().Container.Image) e.SetImage(s.Config().Container.Image)
e.SetStopConfiguration(&cfg.ProcessConfiguration.Stop) e.SetStopConfiguration(cfg.ProcessConfiguration.Stop)
} }
return nil return nil
@ -178,7 +178,7 @@ func (s *Server) CreateEnvironment() error {
} }
// Gets the process configuration data for the server. // Gets the process configuration data for the server.
func (s *Server) GetProcessConfiguration() (*api.ServerConfigurationResponse, *api.RequestError, error) { func (s *Server) GetProcessConfiguration() (api.ServerConfigurationResponse, *api.RequestError, error) {
return api.NewRequester().GetServerConfiguration(s.Id()) return api.NewRequester().GetServerConfiguration(s.Id())
} }