From 065da77afa370eae7c0c0a80c704052b670cb41c Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Mon, 1 Feb 2021 21:43:04 -0800 Subject: [PATCH] Code cleanup for remote client --- cmd/root.go | 9 ++-- remote/backup.go | 45 ------------------ remote/client.go | 55 ---------------------- remote/client_test.go | 19 -------- remote/http.go | 105 +++++++++++++++++++++++++++++++++--------- remote/http_test.go | 14 ++++++ remote/servers.go | 38 +++++++++++++++ remote/types.go | 21 +++++++++ remote/util.go | 30 ------------ 9 files changed, 161 insertions(+), 175 deletions(-) delete mode 100644 remote/backup.go delete mode 100644 remote/client.go delete mode 100644 remote/client_test.go delete mode 100644 remote/util.go diff --git a/cmd/root.go b/cmd/root.go index c0d47b1..8132e8d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -137,11 +137,12 @@ func rootCmdRun(cmd *cobra.Command, _ []string) { "gid": config.Get().System.User.Gid, }).Info("configured system user successfully") - pclient := remote.CreateClient( + pclient := remote.New( config.Get().PanelLocation, - config.Get().AuthenticationTokenId, - config.Get().AuthenticationToken, - remote.WithTimeout(time.Second*time.Duration(config.Get().RemoteQuery.Timeout)), + remote.WithCredentials(config.Get().AuthenticationTokenId, config.Get().AuthenticationToken), + remote.WithHttpClient(&http.Client{ + Timeout: time.Second * time.Duration(config.Get().RemoteQuery.Timeout), + }), ) manager, err := server.NewManager(cmd.Context(), pclient) diff --git a/remote/backup.go b/remote/backup.go deleted file mode 100644 index bfb36ba..0000000 --- a/remote/backup.go +++ /dev/null @@ -1,45 +0,0 @@ -package remote - -import ( - "context" - "fmt" - "strconv" -) - -func (c *client) GetBackupRemoteUploadURLs(ctx context.Context, backup string, size int64) (BackupRemoteUploadResponse, error) { - var data BackupRemoteUploadResponse - res, err := c.get(ctx, fmt.Sprintf("/backups/%s", backup), q{"size": strconv.FormatInt(size, 10)}) - if err != nil { - return data, err - } - defer res.Body.Close() - - if res.HasError() { - return data, res.Error() - } - - err = res.BindJSON(&data) - return data, err -} - -func (c *client) SetBackupStatus(ctx context.Context, backup string, data BackupRequest) error { - resp, err := c.post(ctx, fmt.Sprintf("/backups/%s", backup), data) - if err != nil { - return err - } - defer resp.Body.Close() - return resp.Error() -} - - -// SendRestorationStatus triggers a request to the Panel to notify it that a -// restoration has been completed and the server should be marked as being -// activated again. -func (c *client) SendRestorationStatus(ctx context.Context, backup string, successful bool) error { - resp, err := c.post(ctx, fmt.Sprintf("/backups/%s/restore", backup), d{"successful": successful}) - if err != nil { - return err - } - defer resp.Body.Close() - return resp.Error() -} \ No newline at end of file diff --git a/remote/client.go b/remote/client.go deleted file mode 100644 index 7d15c4e..0000000 --- a/remote/client.go +++ /dev/null @@ -1,55 +0,0 @@ -package remote - -import ( - "context" - "net/http" - "strings" - "time" -) - -type Client interface { - GetBackupRemoteUploadURLs(ctx context.Context, backup string, size int64) (BackupRemoteUploadResponse, 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 BackupRequest) error - SendRestorationStatus(ctx context.Context, backup string, successful bool) error - SetInstallationStatus(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) -} - -type client struct { - httpClient *http.Client - baseUrl string - tokenId string - token string - retries int -} - -type ClientOption func(c *client) - -func CreateClient(base, tokenId, token string, opts ...ClientOption) Client { - httpClient := &http.Client{ - Timeout: time.Second * 15, - } - base = strings.TrimSuffix(base, "/") - c := &client{ - baseUrl: base + "/api/remote", - tokenId: tokenId, - token: token, - httpClient: httpClient, - retries: 3, - } - for _, o := range opts { - o(c) - } - return c -} - -func WithTimeout(timeout time.Duration) ClientOption { - return func(c *client) { - c.httpClient.Timeout = timeout - } -} diff --git a/remote/client_test.go b/remote/client_test.go deleted file mode 100644 index eefc18d..0000000 --- a/remote/client_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package remote - -import ( - "net/http" - "net/http/httptest" -) - -func createTestClient(h http.HandlerFunc) (*client, *httptest.Server) { - s := httptest.NewServer(h) - c := &client{ - httpClient: s.Client(), - baseUrl: s.URL, - - retries: 1, - tokenId: "testid", - token: "testtoken", - } - return c, s -} diff --git a/remote/http.go b/remote/http.go index 6788a55..24fca52 100644 --- a/remote/http.go +++ b/remote/http.go @@ -9,34 +9,65 @@ import ( "io" "io/ioutil" "net/http" + "strings" + "time" + "github.com/apex/log" "github.com/pterodactyl/wings/system" ) -// 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. -// -// Inspired by gin.H, same concept. -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 Client interface { + GetBackupRemoteUploadURLs(ctx context.Context, backup string, size int64) (BackupRemoteUploadResponse, 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 BackupRequest) error + SendRestorationStatus(ctx context.Context, backup string, successful bool) error + SetInstallationStatus(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) } -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"` +type client struct { + httpClient *http.Client + baseUrl string + tokenId string + token string + retries int +} + +// New returns a new HTTP request client that is used for making authenticated +// requests to the Panel that this instance is running under. +func New(base string, opts ...ClientOption) Client { + c := client{ + baseUrl: strings.TrimSuffix(base, "/") + "/api/remote", + httpClient: &http.Client{ + Timeout: time.Second * 15, + }, + retries: 3, + } + for _, opt := range opts { + opt(&c) + } + return &c +} + +// WithCredentials sets the credentials to use when making request to the remote +// API endpoint. +func WithCredentials(id, token string) ClientOption { + return func(c *client) { + c.tokenId = id + c.token = token + } +} + +// WithHttpClient sets the underlying HTTP client instance to use when making +// requests to the Panel API. +func WithHttpClient(httpClient *http.Client) ClientOption { + return func(c *client) { + c.httpClient = httpClient + } } // requestOnce creates a http request and executes it once. Prefer request() @@ -103,6 +134,13 @@ func (c *client) post(ctx context.Context, path string, data interface{}) (*Resp return c.request(ctx, http.MethodPost, path, bytes.NewBuffer(b)) } +// 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 +} + // HasError determines if the API call encountered an error. If no request has // been made the response will be false. This function will evaluate to true if // the response code is anything 300 or higher. @@ -165,3 +203,26 @@ func (r *Response) Error() error { return e } + +// Logs the request into the debug log with all of the important request bits. +// The authorization key will be cleaned up before being output. +func debugLogRequest(req *http.Request) { + if l, ok := log.Log.(*log.Logger); ok && l.Level != log.DebugLevel { + return + } + headers := make(map[string][]string) + for k, v := range req.Header { + if k != "Authorization" || len(v) == 0 || len(v[0]) == 0 { + headers[k] = v + continue + } + + headers[k] = []string{"(redacted)"} + } + + log.WithFields(log.Fields{ + "method": req.Method, + "endpoint": req.URL.String(), + "headers": headers, + }).Debug("making request to external HTTP endpoint") +} diff --git a/remote/http_test.go b/remote/http_test.go index 5a9b0f1..7ba8685 100644 --- a/remote/http_test.go +++ b/remote/http_test.go @@ -3,11 +3,25 @@ package remote import ( "context" "net/http" + "net/http/httptest" "testing" "github.com/stretchr/testify/assert" ) +func createTestClient(h http.HandlerFunc) (*client, *httptest.Server) { + s := httptest.NewServer(h) + c := &client{ + httpClient: s.Client(), + baseUrl: s.URL, + + retries: 1, + tokenId: "testid", + token: "testtoken", + } + return c, s +} + func TestRequest(t *testing.T) { c, _ := createTestClient(func(rw http.ResponseWriter, r *http.Request) { assert.Equal(t, "application/vnd.pterodactyl.v1+json", r.Header.Get("Accept")) diff --git a/remote/servers.go b/remote/servers.go index 2a3f57c..fa5c3b8 100644 --- a/remote/servers.go +++ b/remote/servers.go @@ -144,6 +144,44 @@ func (c *client) ValidateSftpCredentials(ctx context.Context, request SftpAuthRe return auth, err } +func (c *client) GetBackupRemoteUploadURLs(ctx context.Context, backup string, size int64) (BackupRemoteUploadResponse, error) { + var data BackupRemoteUploadResponse + res, err := c.get(ctx, fmt.Sprintf("/backups/%s", backup), q{"size": strconv.FormatInt(size, 10)}) + if err != nil { + return data, err + } + defer res.Body.Close() + + if res.HasError() { + return data, res.Error() + } + + err = res.BindJSON(&data) + return data, err +} + +func (c *client) SetBackupStatus(ctx context.Context, backup string, data BackupRequest) error { + resp, err := c.post(ctx, fmt.Sprintf("/backups/%s", backup), data) + if err != nil { + return err + } + defer resp.Body.Close() + return resp.Error() +} + + +// SendRestorationStatus triggers a request to the Panel to notify it that a +// restoration has been completed and the server should be marked as being +// activated again. +func (c *client) SendRestorationStatus(ctx context.Context, backup string, successful bool) error { + resp, err := c.post(ctx, fmt.Sprintf("/backups/%s/restore", backup), d{"successful": successful}) + if err != nil { + return err + } + defer resp.Body.Close() + 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) ([]RawServerData, Pagination, error) { diff --git a/remote/types.go b/remote/types.go index 6649ae0..e2d579b 100644 --- a/remote/types.go +++ b/remote/types.go @@ -9,6 +9,27 @@ import ( "github.com/pterodactyl/wings/parser" ) +// 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. +// +// Inspired by gin.H, same concept. +type d map[string]interface{} + +// Same concept as d, but a map of strings, used for querying GET requests. +type q map[string]string + +type ClientOption func(c *client) + +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"` +} + // ServerConfigurationResponse holds the server configuration data returned from // the Panel. When a server process is started, Wings communicates with the // Panel to fetch the latest build information as well as get all of the details diff --git a/remote/util.go b/remote/util.go deleted file mode 100644 index 4cbc989..0000000 --- a/remote/util.go +++ /dev/null @@ -1,30 +0,0 @@ -package remote - -import ( - "net/http" - - "github.com/apex/log" -) - -// Logs the request into the debug log with all of the important request bits. -// The authorization key will be cleaned up before being output. -func debugLogRequest(req *http.Request) { - if l, ok := log.Log.(*log.Logger); ok && l.Level != log.DebugLevel { - return - } - headers := make(map[string][]string) - for k, v := range req.Header { - if k != "Authorization" || len(v) == 0 || len(v[0]) == 0 { - headers[k] = v - continue - } - - headers[k] = []string{"(redacted)"} - } - - log.WithFields(log.Fields{ - "method": req.Method, - "endpoint": req.URL.String(), - "headers": headers, - }).Debug("making request to external HTTP endpoint") -}