diff --git a/remote/client.go b/remote/client.go index 80cc9e7..7a15ed2 100644 --- a/remote/client.go +++ b/remote/client.go @@ -11,9 +11,9 @@ import ( type Client interface { GetBackupRemoteUploadURLs(ctx context.Context, backup string, size int64) (api.BackupRemoteUploadResponse, error) - GetInstallationScript(ctx context.Context, uuid string) (api.InstallationScript, error) - GetServerConfiguration(ctx context.Context, uuid string) (api.ServerConfigurationResponse, error) - GetServers(context context.Context, perPage int) ([]api.RawServerData, error) + GetInstallationScript(ctx context.Context, uuid string) (InstallationScript, error) + GetServerConfiguration(ctx context.Context, uuid string) (ServerConfigurationResponse, error) + GetServers(context context.Context, perPage int) ([]RawServerData, error) SetArchiveStatus(ctx context.Context, uuid string, successful bool) error SetBackupStatus(ctx context.Context, backup string, data api.BackupRequest) error SetInstallationStatus(ctx context.Context, uuid string, successful bool) error diff --git a/remote/http.go b/remote/http.go index 5090523..6788a55 100644 --- a/remote/http.go +++ b/remote/http.go @@ -13,13 +13,6 @@ import ( "github.com/pterodactyl/wings/system" ) -// Response is a custom response type that allows for commonly used error -// handling and response parsing from the Panel API. This just embeds the normal -// HTTP response from Go and we attach a few helper functions to it. -type Response struct { - *http.Response -} - // A generic type allowing for easy binding use when making requests to API // endpoints that only expect a singular argument or something that would not // benefit from being a typed struct. @@ -30,6 +23,22 @@ type d map[string]interface{} // Same concept as d, but a map of strings, used for querying GET requests. type q map[string]string +// Response is a custom response type that allows for commonly used error +// handling and response parsing from the Panel API. This just embeds the normal +// HTTP response from Go and we attach a few helper functions to it. +type Response struct { + *http.Response +} + +type Pagination struct { + CurrentPage uint `json:"current_page"` + From uint `json:"from"` + LastPage uint `json:"last_page"` + PerPage uint `json:"per_page"` + To uint `json:"to"` + Total uint `json:"total"` +} + // requestOnce creates a http request and executes it once. Prefer request() // over this method when possible. It appends the path to the endpoint of the // client and adds the authentication token to the request. diff --git a/remote/servers.go b/remote/servers.go index feda9cf..e579236 100644 --- a/remote/servers.go +++ b/remote/servers.go @@ -51,7 +51,7 @@ type RawServerData struct { // GetServers returns all of the servers that are present on the Panel making // parallel API calls to the endpoint if more than one page of servers is // returned. -func (c *client) GetServers(ctx context.Context, limit int) ([]api.RawServerData, error) { +func (c *client) GetServers(ctx context.Context, limit int) ([]RawServerData, error) { servers, meta, err := c.getServersPaged(ctx, 0, limit) if err != nil { return nil, err @@ -81,34 +81,34 @@ func (c *client) GetServers(ctx context.Context, limit int) ([]api.RawServerData return servers, nil } -func (c *client) GetServerConfiguration(ctx context.Context, uuid string) (api.ServerConfigurationResponse, error) { +func (c *client) GetServerConfiguration(ctx context.Context, uuid string) (ServerConfigurationResponse, error) { + var config ServerConfigurationResponse res, err := c.get(ctx, fmt.Sprintf("/servers/%s", uuid), nil) if err != nil { - return api.ServerConfigurationResponse{}, err + return config, err } defer res.Body.Close() if res.HasError() { - return api.ServerConfigurationResponse{}, err + return config, err } - config := api.ServerConfigurationResponse{} err = res.BindJSON(&config) return config, err } -func (c *client) GetInstallationScript(ctx context.Context, uuid string) (api.InstallationScript, error) { +func (c *client) GetInstallationScript(ctx context.Context, uuid string) (InstallationScript, error) { res, err := c.get(ctx, fmt.Sprintf("/servers/%s/install", uuid), nil) if err != nil { - return api.InstallationScript{}, err + return InstallationScript{}, err } defer res.Body.Close() if res.HasError() { - return api.InstallationScript{}, err + return InstallationScript{}, err } - config := api.InstallationScript{} + var config InstallationScript err = res.BindJSON(&config) return config, err } @@ -146,7 +146,7 @@ func (c *client) SetTransferStatus(ctx context.Context, uuid string, successful // getServersPaged returns a subset of servers from the Panel API using the // pagination query parameters. -func (c *client) getServersPaged(ctx context.Context, page, limit int) ([]api.RawServerData, api.Pagination, error) { +func (c *client) getServersPaged(ctx context.Context, page, limit int) ([]RawServerData, api.Pagination, error) { res, err := c.get(ctx, "/servers", q{ "page": strconv.Itoa(page), "per_page": strconv.Itoa(limit), @@ -161,7 +161,7 @@ func (c *client) getServersPaged(ctx context.Context, page, limit int) ([]api.Ra } var r struct { - Data []api.RawServerData `json:"data"` + Data []RawServerData `json:"data"` Meta api.Pagination `json:"meta"` } if err := res.BindJSON(&r); err != nil { diff --git a/server/manager.go b/server/manager.go index 0816a18..1b59865 100644 --- a/server/manager.go +++ b/server/manager.go @@ -7,6 +7,7 @@ import ( "io" "io/ioutil" "os" + "path/filepath" "runtime" "sync" "time" @@ -14,13 +15,16 @@ import ( "emperror.dev/errors" "github.com/apex/log" "github.com/gammazero/workerpool" - "github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/config" + "github.com/pterodactyl/wings/environment" + "github.com/pterodactyl/wings/environment/docker" "github.com/pterodactyl/wings/remote" + "github.com/pterodactyl/wings/server/filesystem" ) type Manager struct { mu sync.RWMutex + client remote.Client servers []*Server } @@ -28,8 +32,8 @@ type Manager struct { // the servers that are currently present on the filesystem and set them into // the manager. func NewManager(ctx context.Context, client remote.Client) (*Manager, error) { - m := NewEmptyManager() - if err := m.init(ctx, client); err != nil { + m := NewEmptyManager(client) + if err := m.init(ctx); err != nil { return nil, err } return m, nil @@ -38,15 +42,15 @@ func NewManager(ctx context.Context, client remote.Client) (*Manager, error) { // NewEmptyManager returns a new empty manager collection without actually // loading any of the servers from the disk. This allows the caller to set their // own servers into the collection as needed. -func NewEmptyManager() *Manager { - return &Manager{} +func NewEmptyManager(client remote.Client) *Manager { + return &Manager{client: client} } // initializeFromRemoteSource iterates over a given directory and loads all of // the servers listed before returning them to the calling function. -func (m *Manager) init(ctx context.Context, client remote.Client) error { +func (m *Manager) init(ctx context.Context) error { log.Info("fetching list of servers from API") - servers, err := client.GetServers(ctx, config.Get().RemoteQuery.BootServersPerPage) + servers, err := m.client.GetServers(ctx, config.Get().RemoteQuery.BootServersPerPage) if err != nil { if !remote.IsRequestError(err) { return errors.WithStackIf(err) @@ -65,7 +69,7 @@ func (m *Manager) init(ctx context.Context, client remote.Client) error { // 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{ + d := remote.ServerConfigurationResponse{ Settings: data.Settings, } log.WithField("server", data.Uuid).Info("creating new server object from API response") @@ -73,7 +77,7 @@ func (m *Manager) init(ctx context.Context, client remote.Client) error { log.WithField("server", data.Uuid).WithField("error", err).Error("failed to parse server configuration from API response, skipping...") return } - s, err := FromConfiguration(d) + s, err := m.InitServer(d) if err != nil { log.WithField("server", data.Uuid).WithField("error", err).Error("failed to load server, skipping...") return @@ -201,4 +205,54 @@ func (m *Manager) ReadStates() (map[string]string, error) { } } return out, nil +} + +// InitServer 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 for a server. +func (m *Manager) InitServer(data remote.ServerConfigurationResponse) (*Server, error) { + s, err := New(m.client) + if err != nil { + return nil, errors.WithMessage(err, "loader: failed to instantiate empty server struct") + } + if err := s.UpdateDataStructure(data.Settings); err != nil { + return nil, err + } + + s.Archiver = Archiver{Server: s} + s.fs = filesystem.New(filepath.Join(config.Get().System.Data, s.Id()), s.DiskSpace(), s.Config().Egg.FileDenylist) + + // Right now we only support a Docker based environment, so I'm going to hard code + // this logic in. When we're ready to support other environment we'll need to make + // some modifications here obviously. + settings := environment.Settings{ + Mounts: s.Mounts(), + Allocations: s.cfg.Allocations, + Limits: s.cfg.Build, + } + + envCfg := environment.NewConfiguration(settings, s.GetEnvironmentVariables()) + meta := docker.Metadata{ + Image: s.Config().Container.Image, + } + + if env, err := docker.New(s.Id(), &meta, envCfg); err != nil { + return nil, err + } else { + s.Environment = env + s.StartEventListeners() + s.Throttler().StartTimer(s.Context()) + } + + // Forces the configuration to be synced with the panel. + if err := s.SyncWithConfiguration(data); err != nil { + return nil, err + } + + // If the server's data directory exists, force disk usage calculation. + if _, err := os.Stat(s.Filesystem().Path()); err == nil { + s.Filesystem().HasSpaceAvailable(true) + } + + return s, nil } \ No newline at end of file diff --git a/server/server.go b/server/server.go index 6f2a607..020eb5e 100644 --- a/server/server.go +++ b/server/server.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "os" - "path/filepath" "strings" "sync" @@ -16,6 +15,7 @@ import ( "github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/environment/docker" "github.com/pterodactyl/wings/events" + "github.com/pterodactyl/wings/remote" "github.com/pterodactyl/wings/server/filesystem" "github.com/pterodactyl/wings/system" "golang.org/x/sync/semaphore" @@ -36,7 +36,8 @@ type Server struct { // Maintains the configuration for the server. This is the data that gets returned by the Panel // such as build settings and container images. - cfg Configuration + cfg Configuration + client remote.Client // The crash handler for this server instance. crasher CrashHandler @@ -72,11 +73,12 @@ type Server struct { // Returns a new server instance with a context and all of the default values set on // the instance. -func New() (*Server, error) { +func New(client remote.Client) (*Server, error) { ctx, cancel := context.WithCancel(context.Background()) s := Server{ ctx: ctx, ctxCancel: &cancel, + client: client, installing: system.NewAtomicBool(false), transferring: system.NewAtomicBool(false), } @@ -148,7 +150,7 @@ func (s *Server) Log() *log.Entry { // This also means mass actions can be performed against servers on the Panel and they // will automatically sync with Wings when the server is started. func (s *Server) Sync() error { - cfg, err := api.New().GetServerConfiguration(s.Id()) + cfg, err := s.client.GetServerConfiguration(s.Context(), s.Id()) if err != nil { if !api.IsRequestError(err) { return err @@ -164,7 +166,7 @@ func (s *Server) Sync() error { return s.SyncWithConfiguration(cfg) } -func (s *Server) SyncWithConfiguration(cfg api.ServerConfigurationResponse) error { +func (s *Server) SyncWithConfiguration(cfg remote.ServerConfigurationResponse) error { // Update the data structure and persist it to the disk. if err := s.UpdateDataStructure(cfg.Settings); err != nil { return err @@ -295,61 +297,11 @@ func (s *Server) OnStateChange() { } } -// Determines if the server state is running or not. This is different than the -// environment state, it is simply the tracked state from this daemon instance, and -// not the response from Docker. +// IsRunning determines if the server state is running or not. This is different +// than the environment state, it is simply the tracked state from this daemon +// instance, and not the response from Docker. func (s *Server) IsRunning() bool { st := s.Environment.State() return st == environment.ProcessRunningState || st == environment.ProcessStartingState } - -// FromConfiguration 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 for a server. -func FromConfiguration(data api.ServerConfigurationResponse) (*Server, error) { - s, err := New() - if err != nil { - return nil, errors.WithMessage(err, "loader: failed to instantiate empty server struct") - } - if err := s.UpdateDataStructure(data.Settings); err != nil { - return nil, err - } - - s.Archiver = Archiver{Server: s} - s.fs = filesystem.New(filepath.Join(config.Get().System.Data, s.Id()), s.DiskSpace(), s.Config().Egg.FileDenylist) - - // Right now we only support a Docker based environment, so I'm going to hard code - // this logic in. When we're ready to support other environment we'll need to make - // some modifications here obviously. - settings := environment.Settings{ - Mounts: s.Mounts(), - Allocations: s.cfg.Allocations, - Limits: s.cfg.Build, - } - - envCfg := environment.NewConfiguration(settings, s.GetEnvironmentVariables()) - meta := docker.Metadata{ - Image: s.Config().Container.Image, - } - - if env, err := docker.New(s.Id(), &meta, envCfg); err != nil { - return nil, err - } else { - s.Environment = env - s.StartEventListeners() - s.Throttler().StartTimer(s.Context()) - } - - // Forces the configuration to be synced with the panel. - if err := s.SyncWithConfiguration(data); err != nil { - return nil, err - } - - // If the server's data directory exists, force disk usage calculation. - if _, err := os.Stat(s.Filesystem().Path()); err == nil { - s.Filesystem().HasSpaceAvailable(true) - } - - return s, nil -}