2021-01-10 19:52:54 +00:00
|
|
|
package remote
|
2021-01-08 22:43:03 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
2022-07-09 19:47:24 +00:00
|
|
|
"github.com/goccy/go-json"
|
2021-01-08 22:43:03 +00:00
|
|
|
"strconv"
|
|
|
|
"sync"
|
|
|
|
|
2021-02-02 05:28:46 +00:00
|
|
|
"emperror.dev/errors"
|
|
|
|
"github.com/apex/log"
|
2021-01-08 22:43:03 +00:00
|
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
ProcessStopCommand = "command"
|
|
|
|
ProcessStopSignal = "signal"
|
|
|
|
ProcessStopNativeStop = "stop"
|
|
|
|
)
|
|
|
|
|
2021-02-02 04:26:15 +00:00
|
|
|
// GetServers returns all of the servers that are present on the Panel making
|
2021-02-02 04:33:35 +00:00
|
|
|
// parallel API calls to the endpoint if more than one page of servers is
|
|
|
|
// returned.
|
2021-02-02 04:50:23 +00:00
|
|
|
func (c *client) GetServers(ctx context.Context, limit int) ([]RawServerData, error) {
|
2021-02-02 04:33:35 +00:00
|
|
|
servers, meta, err := c.getServersPaged(ctx, 0, limit)
|
2021-01-08 22:43:03 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-02-02 04:26:15 +00:00
|
|
|
var mu sync.Mutex
|
|
|
|
if meta.LastPage > 1 {
|
|
|
|
g, ctx := errgroup.WithContext(ctx)
|
|
|
|
for page := meta.CurrentPage + 1; page <= meta.LastPage; page++ {
|
|
|
|
page := page
|
|
|
|
g.Go(func() error {
|
2021-02-02 04:33:35 +00:00
|
|
|
ps, _, err := c.getServersPaged(ctx, int(page), limit)
|
2021-01-08 22:43:03 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-02-02 04:26:15 +00:00
|
|
|
mu.Lock()
|
2021-01-08 22:43:03 +00:00
|
|
|
servers = append(servers, ps...)
|
2021-02-02 04:26:15 +00:00
|
|
|
mu.Unlock()
|
2021-01-08 22:43:03 +00:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
2021-02-02 04:26:15 +00:00
|
|
|
if err := g.Wait(); err != nil {
|
2021-01-08 22:43:03 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return servers, nil
|
|
|
|
}
|
|
|
|
|
2021-02-24 05:23:49 +00:00
|
|
|
// ResetServersState updates the state of all servers on the node that are
|
|
|
|
// currently marked as "installing" or "restoring from backup" to be marked as
|
|
|
|
// a normal successful install state.
|
|
|
|
//
|
|
|
|
// 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
|
|
|
|
// has fully booted all of the servers.
|
|
|
|
func (c *client) ResetServersState(ctx context.Context) error {
|
2021-05-02 22:16:30 +00:00
|
|
|
res, err := c.Post(ctx, "/servers/reset", nil)
|
2021-02-24 05:23:49 +00:00
|
|
|
if err != nil {
|
2021-05-02 22:41:02 +00:00
|
|
|
return errors.WrapIf(err, "remote: failed to reset server state on Panel")
|
2021-02-24 05:23:49 +00:00
|
|
|
}
|
2021-05-02 22:41:02 +00:00
|
|
|
_ = res.Body.Close()
|
2021-02-24 05:23:49 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-02-02 04:50:23 +00:00
|
|
|
func (c *client) GetServerConfiguration(ctx context.Context, uuid string) (ServerConfigurationResponse, error) {
|
|
|
|
var config ServerConfigurationResponse
|
2021-05-02 22:16:30 +00:00
|
|
|
res, err := c.Get(ctx, fmt.Sprintf("/servers/%s", uuid), nil)
|
2021-01-08 22:43:03 +00:00
|
|
|
if err != nil {
|
2021-02-02 04:50:23 +00:00
|
|
|
return config, err
|
2021-01-08 22:43:03 +00:00
|
|
|
}
|
|
|
|
defer res.Body.Close()
|
|
|
|
|
|
|
|
err = res.BindJSON(&config)
|
|
|
|
return config, err
|
|
|
|
}
|
|
|
|
|
2021-02-02 04:50:23 +00:00
|
|
|
func (c *client) GetInstallationScript(ctx context.Context, uuid string) (InstallationScript, error) {
|
2021-05-02 22:16:30 +00:00
|
|
|
res, err := c.Get(ctx, fmt.Sprintf("/servers/%s/install", uuid), nil)
|
2021-01-08 22:43:03 +00:00
|
|
|
if err != nil {
|
2021-02-02 04:50:23 +00:00
|
|
|
return InstallationScript{}, err
|
2021-01-08 22:43:03 +00:00
|
|
|
}
|
|
|
|
defer res.Body.Close()
|
|
|
|
|
2021-02-02 04:50:23 +00:00
|
|
|
var config InstallationScript
|
2021-01-08 22:43:03 +00:00
|
|
|
err = res.BindJSON(&config)
|
|
|
|
return config, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *client) SetInstallationStatus(ctx context.Context, uuid string, successful bool) error {
|
2021-05-02 22:16:30 +00:00
|
|
|
resp, err := c.Post(ctx, fmt.Sprintf("/servers/%s/install", uuid), d{"successful": successful})
|
2021-01-08 22:43:03 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-05-02 22:41:02 +00:00
|
|
|
_ = resp.Body.Close()
|
|
|
|
return nil
|
2021-01-08 22:43:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *client) SetArchiveStatus(ctx context.Context, uuid string, successful bool) error {
|
2021-05-02 22:16:30 +00:00
|
|
|
resp, err := c.Post(ctx, fmt.Sprintf("/servers/%s/archive", uuid), d{"successful": successful})
|
2021-01-08 22:43:03 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-05-02 22:41:02 +00:00
|
|
|
_ = resp.Body.Close()
|
|
|
|
return nil
|
2021-01-08 22:43:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *client) SetTransferStatus(ctx context.Context, uuid string, successful bool) error {
|
|
|
|
state := "failure"
|
|
|
|
if successful {
|
|
|
|
state = "success"
|
|
|
|
}
|
2021-05-02 22:16:30 +00:00
|
|
|
resp, err := c.Get(ctx, fmt.Sprintf("/servers/%s/transfer/%s", uuid, state), nil)
|
2021-01-08 22:43:03 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-05-02 22:41:02 +00:00
|
|
|
_ = resp.Body.Close()
|
|
|
|
return nil
|
2021-01-08 22:43:03 +00:00
|
|
|
}
|
2021-02-02 04:33:35 +00:00
|
|
|
|
2021-02-02 05:28:46 +00:00
|
|
|
// ValidateSftpCredentials makes a request to determine if the username and
|
|
|
|
// password combination provided is associated with a valid server on the instance
|
|
|
|
// using the Panel's authentication control mechanisms. This will get itself
|
|
|
|
// throttled if too many requests are made, allowing us to completely offload
|
|
|
|
// all of the authorization security logic to the Panel.
|
|
|
|
func (c *client) ValidateSftpCredentials(ctx context.Context, request SftpAuthRequest) (SftpAuthResponse, error) {
|
|
|
|
var auth SftpAuthResponse
|
2021-05-02 22:16:30 +00:00
|
|
|
res, err := c.Post(ctx, "/sftp/auth", request)
|
2021-02-02 05:28:46 +00:00
|
|
|
if err != nil {
|
2021-05-02 22:41:02 +00:00
|
|
|
if err := AsRequestError(err); err != nil && (err.StatusCode() >= 400 && err.StatusCode() < 500) {
|
|
|
|
log.WithFields(log.Fields{"subsystem": "sftp", "username": request.User, "ip": request.IP}).Warn(err.Error())
|
|
|
|
return auth, &SftpInvalidCredentialsError{}
|
|
|
|
}
|
2021-02-02 05:28:46 +00:00
|
|
|
return auth, err
|
|
|
|
}
|
2021-03-04 04:51:49 +00:00
|
|
|
defer res.Body.Close()
|
2021-02-02 05:28:46 +00:00
|
|
|
|
2021-05-02 22:41:02 +00:00
|
|
|
if err := res.BindJSON(&auth); err != nil {
|
|
|
|
return auth, err
|
2021-02-02 05:28:46 +00:00
|
|
|
}
|
2021-05-02 22:41:02 +00:00
|
|
|
return auth, nil
|
2021-02-02 05:28:46 +00:00
|
|
|
}
|
|
|
|
|
2021-02-02 05:43:04 +00:00
|
|
|
func (c *client) GetBackupRemoteUploadURLs(ctx context.Context, backup string, size int64) (BackupRemoteUploadResponse, error) {
|
|
|
|
var data BackupRemoteUploadResponse
|
2021-05-02 22:16:30 +00:00
|
|
|
res, err := c.Get(ctx, fmt.Sprintf("/backups/%s", backup), q{"size": strconv.FormatInt(size, 10)})
|
2021-02-02 05:43:04 +00:00
|
|
|
if err != nil {
|
|
|
|
return data, err
|
|
|
|
}
|
|
|
|
defer res.Body.Close()
|
2021-05-02 22:41:02 +00:00
|
|
|
if err := res.BindJSON(&data); err != nil {
|
|
|
|
return data, err
|
2021-02-02 05:43:04 +00:00
|
|
|
}
|
2021-05-02 22:41:02 +00:00
|
|
|
return data, nil
|
2021-02-02 05:43:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *client) SetBackupStatus(ctx context.Context, backup string, data BackupRequest) error {
|
2021-05-02 22:16:30 +00:00
|
|
|
resp, err := c.Post(ctx, fmt.Sprintf("/backups/%s", backup), data)
|
2021-02-02 05:43:04 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-05-02 22:41:02 +00:00
|
|
|
_ = resp.Body.Close()
|
|
|
|
return nil
|
2021-02-02 05:43:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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 {
|
2021-05-02 22:16:30 +00:00
|
|
|
resp, err := c.Post(ctx, fmt.Sprintf("/backups/%s/restore", backup), d{"successful": successful})
|
2021-02-02 05:43:04 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-05-02 22:41:02 +00:00
|
|
|
_ = resp.Body.Close()
|
|
|
|
return nil
|
2021-02-02 05:43:04 +00:00
|
|
|
}
|
|
|
|
|
2022-07-09 18:38:41 +00:00
|
|
|
// SendActivityLogs sends activity logs back to the Panel for processing.
|
2022-07-09 19:47:24 +00:00
|
|
|
func (c *client) SendActivityLogs(ctx context.Context, activity []json.RawMessage) error {
|
|
|
|
resp, err := c.Post(ctx, "/activity", d{"data": activity})
|
2022-07-09 18:38:41 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.WithStackIf(err)
|
|
|
|
}
|
|
|
|
_ = resp.Body.Close()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-02-02 04:33:35 +00:00
|
|
|
// getServersPaged returns a subset of servers from the Panel API using the
|
|
|
|
// pagination query parameters.
|
2021-02-02 05:32:34 +00:00
|
|
|
func (c *client) getServersPaged(ctx context.Context, page, limit int) ([]RawServerData, Pagination, error) {
|
|
|
|
var r struct {
|
|
|
|
Data []RawServerData `json:"data"`
|
|
|
|
Meta Pagination `json:"meta"`
|
|
|
|
}
|
|
|
|
|
2021-05-02 22:16:30 +00:00
|
|
|
res, err := c.Get(ctx, "/servers", q{
|
2021-02-02 04:33:35 +00:00
|
|
|
"page": strconv.Itoa(page),
|
|
|
|
"per_page": strconv.Itoa(limit),
|
|
|
|
})
|
|
|
|
if err != nil {
|
2021-02-02 05:32:34 +00:00
|
|
|
return nil, r.Meta, err
|
2021-02-02 04:33:35 +00:00
|
|
|
}
|
|
|
|
defer res.Body.Close()
|
|
|
|
if err := res.BindJSON(&r); err != nil {
|
2021-02-02 05:32:34 +00:00
|
|
|
return nil, r.Meta, err
|
2021-02-02 04:33:35 +00:00
|
|
|
}
|
|
|
|
return r.Data, r.Meta, nil
|
2021-05-02 22:41:02 +00:00
|
|
|
}
|