Formatting

This commit is contained in:
Dane Everitt 2021-02-01 20:33:35 -08:00
parent 1393937904
commit aa287d21cf
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
3 changed files with 61 additions and 141 deletions

View File

@ -1,15 +1,8 @@
package api package api
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"strconv"
"sync"
"github.com/apex/log"
"github.com/pterodactyl/wings/config"
"golang.org/x/sync/errgroup"
) )
const ( const (
@ -39,91 +32,12 @@ type InstallationScript struct {
Script string `json:"script"` Script string `json:"script"`
} }
type allServerResponse struct {
Data []RawServerData `json:"data"`
Meta Pagination `json:"meta"`
}
type RawServerData struct { type RawServerData struct {
Uuid string `json:"uuid"` Uuid string `json:"uuid"`
Settings json.RawMessage `json:"settings"` Settings json.RawMessage `json:"settings"`
ProcessConfiguration json.RawMessage `json:"process_configuration"` ProcessConfiguration json.RawMessage `json:"process_configuration"`
} }
// Fetches all of the server configurations from the Panel API. This will initially load the
// first 50 servers, and then check the pagination response to determine if more pages should
// be loaded. If so, those requests are spun-up in additional routines and the final resulting
// slice of all servers will be returned.
func (r *Request) GetServers() ([]RawServerData, error) {
resp, err := r.Get("/servers", Q{"per_page": strconv.Itoa(config.Get().RemoteQuery.BootServersPerPage)})
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.HasError() {
return nil, resp.Error()
}
var res allServerResponse
if err := resp.Bind(&res); err != nil {
return nil, err
}
var mu sync.Mutex
ret := res.Data
// Check for pagination, and if it exists we'll need to then make a request to the API
// for each page that would exist and get all of the resulting servers.
if res.Meta.LastPage > 1 {
pp := res.Meta.PerPage
log.WithField("per_page", pp).
WithField("total_pages", res.Meta.LastPage).
Debug("detected multiple pages of server configurations, fetching remaining...")
g, ctx := errgroup.WithContext(context.Background())
for i := res.Meta.CurrentPage + 1; i <= res.Meta.LastPage; i++ {
page := strconv.Itoa(int(i))
g.Go(func() error {
select {
case <-ctx.Done():
return ctx.Err()
default:
{
resp, err := r.Get("/servers", Q{"page": page, "per_page": strconv.Itoa(int(pp))})
if err != nil {
return err
}
defer resp.Body.Close()
if resp.Error() != nil {
return resp.Error()
}
var servers allServerResponse
if err := resp.Bind(&servers); err != nil {
return err
}
mu.Lock()
defer mu.Unlock()
ret = append(ret, servers.Data...)
return nil
}
}
})
}
if err := g.Wait(); err != nil {
return nil, err
}
}
return ret, nil
}
// Fetches the server configuration and returns the struct for it. // Fetches the server configuration and returns the struct for it.
func (r *Request) GetServerConfiguration(uuid string) (ServerConfigurationResponse, error) { func (r *Request) GetServerConfiguration(uuid string) (ServerConfigurationResponse, error) {
var cfg ServerConfigurationResponse var cfg ServerConfigurationResponse

View File

@ -13,16 +13,16 @@ import (
"github.com/pterodactyl/wings/system" "github.com/pterodactyl/wings/system"
) )
// A custom response type that allows for commonly used error handling and response // Response is a custom response type that allows for commonly used error
// parsing from the Panel API. This just embeds the normal HTTP response from Go and // handling and response parsing from the Panel API. This just embeds the normal
// we attach a few helper functions to it. // HTTP response from Go and we attach a few helper functions to it.
type Response struct { type Response struct {
*http.Response *http.Response
} }
// A generic type allowing for easy binding use when making requests to API endpoints // A generic type allowing for easy binding use when making requests to API
// that only expect a singular argument or something that would not benefit from being // endpoints that only expect a singular argument or something that would not
// a typed struct. // benefit from being a typed struct.
// //
// Inspired by gin.H, same concept. // Inspired by gin.H, same concept.
type d map[string]interface{} type d map[string]interface{}
@ -30,9 +30,9 @@ type d map[string]interface{}
// Same concept as d, but a map of strings, used for querying GET requests. // Same concept as d, but a map of strings, used for querying GET requests.
type q map[string]string type q map[string]string
// requestOnce creates a http request and executes it once. // requestOnce creates a http request and executes it once. Prefer request()
// Prefer request() over this method when possible. // over this method when possible. It appends the path to the endpoint of the
// It appends the path to the endpoint of the client and adds the authentication token to the request. // client and adds the authentication token to the request.
func (c *client) requestOnce(ctx context.Context, method, path string, body io.Reader, opts ...func(r *http.Request)) (*Response, error) { func (c *client) requestOnce(ctx context.Context, method, path string, body io.Reader, opts ...func(r *http.Request)) (*Response, error) {
req, err := http.NewRequest(method, c.baseUrl+path, body) req, err := http.NewRequest(method, c.baseUrl+path, body)
if err != nil { if err != nil {
@ -94,9 +94,9 @@ func (c *client) post(ctx context.Context, path string, data interface{}) (*Resp
return c.request(ctx, http.MethodPost, path, bytes.NewBuffer(b)) return c.request(ctx, http.MethodPost, path, bytes.NewBuffer(b))
} }
// Determines if the API call encountered an error. If no request has been made // HasError determines if the API call encountered an error. If no request has
// the response will be false. This function will evaluate to true if the response // been made the response will be false. This function will evaluate to true if
// code is anything 300 or higher. // the response code is anything 300 or higher.
func (r *Response) HasError() bool { func (r *Response) HasError() bool {
if r.Response == nil { if r.Response == nil {
return false return false
@ -123,8 +123,9 @@ func (r *Response) Read() ([]byte, error) {
return b, nil return b, nil
} }
// Binds a given interface with the data returned in the response. This is a shortcut // BindJSON binds a given interface with the data returned in the response. This
// for calling Read and then manually calling json.Unmarshal on the raw bytes. // is a shortcut for calling Read and then manually calling json.Unmarshal on
// the raw bytes.
func (r *Response) BindJSON(v interface{}) error { func (r *Response) BindJSON(v interface{}) error {
b, err := r.Read() b, err := r.Read()
if err != nil { if err != nil {
@ -134,8 +135,8 @@ func (r *Response) BindJSON(v interface{}) error {
return json.Unmarshal(b, &v) return json.Unmarshal(b, &v)
} }
// Returns the first error message from the API call as a string. // Returns the first error message from the API call as a string. The error
// The error message will be formatted similar to the below example: // message will be formatted similar to the below example:
// //
// HttpNotFoundException: The requested resource does not exist. (HTTP/404) // HttpNotFoundException: The requested resource does not exist. (HTTP/404)
func (r *Response) Error() error { func (r *Response) Error() error {

View File

@ -17,64 +17,42 @@ const (
ProcessStopNativeStop = "stop" ProcessStopNativeStop = "stop"
) )
// Holds the server configuration data returned from the Panel. When a server process // ServerConfigurationResponse holds the server configuration data returned from
// is started, Wings communicates with the Panel to fetch the latest build information // the Panel. When a server process is started, Wings communicates with the
// as well as get all of the details needed to parse the given Egg. // Panel to fetch the latest build information as well as get all of the details
// needed to parse the given Egg.
// //
// This means we do not need to hit Wings each time part of the server is updated, and // This means we do not need to hit Wings each time part of the server is
// the Panel serves as the source of truth at all times. This also means if a configuration // updated, and the Panel serves as the source of truth at all times. This also
// is accidentally wiped on Wings we can self-recover without too much hassle, so long // means if a configuration is accidentally wiped on Wings we can self-recover
// as Wings is aware of what servers should exist on it. // without too much hassle, so long as Wings is aware of what servers should
// exist on it.
type ServerConfigurationResponse struct { type ServerConfigurationResponse struct {
Settings json.RawMessage `json:"settings"` Settings json.RawMessage `json:"settings"`
ProcessConfiguration *api.ProcessConfiguration `json:"process_configuration"` ProcessConfiguration *api.ProcessConfiguration `json:"process_configuration"`
} }
// Defines installation script information for a server process. This is used when // InstallationScript defines installation script information for a server
// a server is installed for the first time, and when a server is marked for re-installation. // process. This is used when a server is installed for the first time, and when
// a server is marked for re-installation.
type InstallationScript struct { type InstallationScript struct {
ContainerImage string `json:"container_image"` ContainerImage string `json:"container_image"`
Entrypoint string `json:"entrypoint"` Entrypoint string `json:"entrypoint"`
Script string `json:"script"` Script string `json:"script"`
} }
type allServerResponse struct { // RawServerData is a raw response from the API for a server.
Data []api.RawServerData `json:"data"`
Meta api.Pagination `json:"meta"`
}
type RawServerData struct { type RawServerData struct {
Uuid string `json:"uuid"` Uuid string `json:"uuid"`
Settings json.RawMessage `json:"settings"` Settings json.RawMessage `json:"settings"`
ProcessConfiguration json.RawMessage `json:"process_configuration"` ProcessConfiguration json.RawMessage `json:"process_configuration"`
} }
func (c *client) GetServersPaged(ctx context.Context, page, limit int) ([]api.RawServerData, api.Pagination, error) {
res, err := c.get(ctx, "/servers", q{
"page": strconv.Itoa(page),
"per_page": strconv.Itoa(limit),
})
if err != nil {
return nil, api.Pagination{}, err
}
defer res.Body.Close()
if res.HasError() {
return nil, api.Pagination{}, res.Error()
}
var r allServerResponse
if err := res.BindJSON(&r); err != nil {
return nil, api.Pagination{}, err
}
return r.Data, r.Meta, nil
}
// GetServers returns all of the servers that are present on the Panel making // 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. // 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) ([]api.RawServerData, error) {
servers, meta, err := c.GetServersPaged(ctx, 0, limit) servers, meta, err := c.getServersPaged(ctx, 0, limit)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -85,7 +63,7 @@ func (c *client) GetServers(ctx context.Context, limit int) ([]api.RawServerData
for page := meta.CurrentPage + 1; page <= meta.LastPage; page++ { for page := meta.CurrentPage + 1; page <= meta.LastPage; page++ {
page := page page := page
g.Go(func() error { g.Go(func() error {
ps, _, err := c.GetServersPaged(ctx, int(page), limit) ps, _, err := c.getServersPaged(ctx, int(page), limit)
if err != nil { if err != nil {
return err return err
} }
@ -165,3 +143,30 @@ func (c *client) SetTransferStatus(ctx context.Context, uuid string, successful
defer resp.Body.Close() defer resp.Body.Close()
return resp.Error() return resp.Error()
} }
// 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) {
res, err := c.get(ctx, "/servers", q{
"page": strconv.Itoa(page),
"per_page": strconv.Itoa(limit),
})
if err != nil {
return nil, api.Pagination{}, err
}
defer res.Body.Close()
if res.HasError() {
return nil, api.Pagination{}, res.Error()
}
var r struct {
Data []api.RawServerData `json:"data"`
Meta api.Pagination `json:"meta"`
}
if err := res.BindJSON(&r); err != nil {
return nil, api.Pagination{}, err
}
return r.Data, r.Meta, nil
}