mirror of https://github.com/hashicorp/consul
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
454 lines
13 KiB
454 lines
13 KiB
package linodego |
|
|
|
import ( |
|
"context" |
|
"encoding/json" |
|
"fmt" |
|
"net" |
|
"time" |
|
) |
|
|
|
/* |
|
* https://developers.linode.com/v4/reference/endpoints/linode/instances |
|
*/ |
|
|
|
// InstanceStatus constants start with Instance and include Linode API Instance Status values |
|
type InstanceStatus string |
|
|
|
// InstanceStatus constants reflect the current status of an Instance |
|
const ( |
|
InstanceBooting InstanceStatus = "booting" |
|
InstanceRunning InstanceStatus = "running" |
|
InstanceOffline InstanceStatus = "offline" |
|
InstanceShuttingDown InstanceStatus = "shutting_down" |
|
InstanceRebooting InstanceStatus = "rebooting" |
|
InstanceProvisioning InstanceStatus = "provisioning" |
|
InstanceDeleting InstanceStatus = "deleting" |
|
InstanceMigrating InstanceStatus = "migrating" |
|
InstanceRebuilding InstanceStatus = "rebuilding" |
|
InstanceCloning InstanceStatus = "cloning" |
|
InstanceRestoring InstanceStatus = "restoring" |
|
InstanceResizing InstanceStatus = "resizing" |
|
) |
|
|
|
// Instance represents a linode object |
|
type Instance struct { |
|
CreatedStr string `json:"created"` |
|
UpdatedStr string `json:"updated"` |
|
|
|
ID int `json:"id"` |
|
Created *time.Time `json:"-"` |
|
Updated *time.Time `json:"-"` |
|
Region string `json:"region"` |
|
Alerts *InstanceAlert `json:"alerts"` |
|
Backups *InstanceBackup `json:"backups"` |
|
Image string `json:"image"` |
|
Group string `json:"group"` |
|
IPv4 []*net.IP `json:"ipv4"` |
|
IPv6 string `json:"ipv6"` |
|
Label string `json:"label"` |
|
Type string `json:"type"` |
|
Status InstanceStatus `json:"status"` |
|
Hypervisor string `json:"hypervisor"` |
|
Specs *InstanceSpec `json:"specs"` |
|
WatchdogEnabled bool `json:"watchdog_enabled"` |
|
Tags []string `json:"tags"` |
|
} |
|
|
|
// InstanceSpec represents a linode spec |
|
type InstanceSpec struct { |
|
Disk int `json:"disk"` |
|
Memory int `json:"memory"` |
|
VCPUs int `json:"vcpus"` |
|
Transfer int `json:"transfer"` |
|
} |
|
|
|
// InstanceAlert represents a metric alert |
|
type InstanceAlert struct { |
|
CPU int `json:"cpu"` |
|
IO int `json:"io"` |
|
NetworkIn int `json:"network_in"` |
|
NetworkOut int `json:"network_out"` |
|
TransferQuota int `json:"transfer_quota"` |
|
} |
|
|
|
// InstanceBackup represents backup settings for an instance |
|
type InstanceBackup struct { |
|
Enabled bool `json:"enabled"` |
|
Schedule struct { |
|
Day string `json:"day,omitempty"` |
|
Window string `json:"window,omitempty"` |
|
} |
|
} |
|
|
|
// InstanceCreateOptions require only Region and Type |
|
type InstanceCreateOptions struct { |
|
Region string `json:"region"` |
|
Type string `json:"type"` |
|
Label string `json:"label,omitempty"` |
|
Group string `json:"group,omitempty"` |
|
RootPass string `json:"root_pass,omitempty"` |
|
AuthorizedKeys []string `json:"authorized_keys,omitempty"` |
|
AuthorizedUsers []string `json:"authorized_users,omitempty"` |
|
StackScriptID int `json:"stackscript_id,omitempty"` |
|
StackScriptData map[string]string `json:"stackscript_data,omitempty"` |
|
BackupID int `json:"backup_id,omitempty"` |
|
Image string `json:"image,omitempty"` |
|
BackupsEnabled bool `json:"backups_enabled,omitempty"` |
|
PrivateIP bool `json:"private_ip,omitempty"` |
|
Tags []string `json:"tags,omitempty"` |
|
|
|
// Creation fields that need to be set explicitly false, "", or 0 use pointers |
|
SwapSize *int `json:"swap_size,omitempty"` |
|
Booted *bool `json:"booted,omitempty"` |
|
} |
|
|
|
// InstanceUpdateOptions is an options struct used when Updating an Instance |
|
type InstanceUpdateOptions struct { |
|
Label string `json:"label,omitempty"` |
|
Group string `json:"group,omitempty"` |
|
Backups *InstanceBackup `json:"backups,omitempty"` |
|
Alerts *InstanceAlert `json:"alerts,omitempty"` |
|
WatchdogEnabled *bool `json:"watchdog_enabled,omitempty"` |
|
Tags *[]string `json:"tags,omitempty"` |
|
} |
|
|
|
// GetUpdateOptions converts an Instance to InstanceUpdateOptions for use in UpdateInstance |
|
func (l *Instance) GetUpdateOptions() InstanceUpdateOptions { |
|
return InstanceUpdateOptions{ |
|
Label: l.Label, |
|
Group: l.Group, |
|
Backups: l.Backups, |
|
Alerts: l.Alerts, |
|
WatchdogEnabled: &l.WatchdogEnabled, |
|
Tags: &l.Tags, |
|
} |
|
} |
|
|
|
// InstanceCloneOptions is an options struct sent when Cloning an Instance |
|
type InstanceCloneOptions struct { |
|
Region string `json:"region,omitempty"` |
|
Type string `json:"type,omitempty"` |
|
|
|
// LinodeID is an optional existing instance to use as the target of the clone |
|
LinodeID int `json:"linode_id,omitempty"` |
|
Label string `json:"label,omitempty"` |
|
Group string `json:"group,omitempty"` |
|
BackupsEnabled bool `json:"backups_enabled"` |
|
Disks []int `json:"disks,omitempty"` |
|
Configs []int `json:"configs,omitempty"` |
|
} |
|
|
|
func (l *Instance) fixDates() *Instance { |
|
l.Created, _ = parseDates(l.CreatedStr) |
|
l.Updated, _ = parseDates(l.UpdatedStr) |
|
return l |
|
} |
|
|
|
// InstancesPagedResponse represents a linode API response for listing |
|
type InstancesPagedResponse struct { |
|
*PageOptions |
|
Data []Instance `json:"data"` |
|
} |
|
|
|
// endpoint gets the endpoint URL for Instance |
|
func (InstancesPagedResponse) endpoint(c *Client) string { |
|
endpoint, err := c.Instances.Endpoint() |
|
if err != nil { |
|
panic(err) |
|
} |
|
return endpoint |
|
} |
|
|
|
// appendData appends Instances when processing paginated Instance responses |
|
func (resp *InstancesPagedResponse) appendData(r *InstancesPagedResponse) { |
|
resp.Data = append(resp.Data, r.Data...) |
|
} |
|
|
|
// ListInstances lists linode instances |
|
func (c *Client) ListInstances(ctx context.Context, opts *ListOptions) ([]Instance, error) { |
|
response := InstancesPagedResponse{} |
|
err := c.listHelper(ctx, &response, opts) |
|
for i := range response.Data { |
|
response.Data[i].fixDates() |
|
} |
|
if err != nil { |
|
return nil, err |
|
} |
|
return response.Data, nil |
|
} |
|
|
|
// GetInstance gets the instance with the provided ID |
|
func (c *Client) GetInstance(ctx context.Context, linodeID int) (*Instance, error) { |
|
e, err := c.Instances.Endpoint() |
|
if err != nil { |
|
return nil, err |
|
} |
|
e = fmt.Sprintf("%s/%d", e, linodeID) |
|
r, err := coupleAPIErrors(c.R(ctx). |
|
SetResult(Instance{}). |
|
Get(e)) |
|
if err != nil { |
|
return nil, err |
|
} |
|
return r.Result().(*Instance).fixDates(), nil |
|
} |
|
|
|
// CreateInstance creates a Linode instance |
|
func (c *Client) CreateInstance(ctx context.Context, instance InstanceCreateOptions) (*Instance, error) { |
|
var body string |
|
e, err := c.Instances.Endpoint() |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
req := c.R(ctx).SetResult(&Instance{}) |
|
|
|
if bodyData, err := json.Marshal(instance); err == nil { |
|
body = string(bodyData) |
|
} else { |
|
return nil, NewError(err) |
|
} |
|
|
|
r, err := coupleAPIErrors(req. |
|
SetBody(body). |
|
Post(e)) |
|
|
|
if err != nil { |
|
return nil, err |
|
} |
|
return r.Result().(*Instance).fixDates(), nil |
|
} |
|
|
|
// UpdateInstance creates a Linode instance |
|
func (c *Client) UpdateInstance(ctx context.Context, id int, instance InstanceUpdateOptions) (*Instance, error) { |
|
var body string |
|
e, err := c.Instances.Endpoint() |
|
if err != nil { |
|
return nil, err |
|
} |
|
e = fmt.Sprintf("%s/%d", e, id) |
|
|
|
req := c.R(ctx).SetResult(&Instance{}) |
|
|
|
if bodyData, err := json.Marshal(instance); err == nil { |
|
body = string(bodyData) |
|
} else { |
|
return nil, NewError(err) |
|
} |
|
|
|
r, err := coupleAPIErrors(req. |
|
SetBody(body). |
|
Put(e)) |
|
|
|
if err != nil { |
|
return nil, err |
|
} |
|
return r.Result().(*Instance).fixDates(), nil |
|
} |
|
|
|
// RenameInstance renames an Instance |
|
func (c *Client) RenameInstance(ctx context.Context, linodeID int, label string) (*Instance, error) { |
|
return c.UpdateInstance(ctx, linodeID, InstanceUpdateOptions{Label: label}) |
|
} |
|
|
|
// DeleteInstance deletes a Linode instance |
|
func (c *Client) DeleteInstance(ctx context.Context, id int) error { |
|
e, err := c.Instances.Endpoint() |
|
if err != nil { |
|
return err |
|
} |
|
e = fmt.Sprintf("%s/%d", e, id) |
|
|
|
_, err = coupleAPIErrors(c.R(ctx).Delete(e)) |
|
return err |
|
} |
|
|
|
// BootInstance will boot a Linode instance |
|
// A configID of 0 will cause Linode to choose the last/best config |
|
func (c *Client) BootInstance(ctx context.Context, id int, configID int) error { |
|
bodyStr := "" |
|
|
|
if configID != 0 { |
|
bodyMap := map[string]int{"config_id": configID} |
|
bodyJSON, err := json.Marshal(bodyMap) |
|
if err != nil { |
|
return NewError(err) |
|
} |
|
bodyStr = string(bodyJSON) |
|
} |
|
|
|
e, err := c.Instances.Endpoint() |
|
if err != nil { |
|
return err |
|
} |
|
|
|
e = fmt.Sprintf("%s/%d/boot", e, id) |
|
_, err = coupleAPIErrors(c.R(ctx). |
|
SetBody(bodyStr). |
|
Post(e)) |
|
|
|
return err |
|
} |
|
|
|
// CloneInstance clone an existing Instances Disks and Configuration profiles to another Linode Instance |
|
func (c *Client) CloneInstance(ctx context.Context, id int, options InstanceCloneOptions) (*Instance, error) { |
|
var body string |
|
e, err := c.Instances.Endpoint() |
|
if err != nil { |
|
return nil, err |
|
} |
|
e = fmt.Sprintf("%s/%d/clone", e, id) |
|
|
|
req := c.R(ctx).SetResult(&Instance{}) |
|
|
|
if bodyData, err := json.Marshal(options); err == nil { |
|
body = string(bodyData) |
|
} else { |
|
return nil, NewError(err) |
|
} |
|
|
|
r, err := coupleAPIErrors(req. |
|
SetBody(body). |
|
Post(e)) |
|
|
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
return r.Result().(*Instance).fixDates(), nil |
|
} |
|
|
|
// RebootInstance reboots a Linode instance |
|
// A configID of 0 will cause Linode to choose the last/best config |
|
func (c *Client) RebootInstance(ctx context.Context, id int, configID int) error { |
|
bodyStr := "{}" |
|
|
|
if configID != 0 { |
|
bodyMap := map[string]int{"config_id": configID} |
|
bodyJSON, err := json.Marshal(bodyMap) |
|
if err != nil { |
|
return NewError(err) |
|
} |
|
bodyStr = string(bodyJSON) |
|
} |
|
|
|
e, err := c.Instances.Endpoint() |
|
if err != nil { |
|
return err |
|
} |
|
|
|
e = fmt.Sprintf("%s/%d/reboot", e, id) |
|
|
|
_, err = coupleAPIErrors(c.R(ctx). |
|
SetBody(bodyStr). |
|
Post(e)) |
|
|
|
return err |
|
} |
|
|
|
// RebuildInstanceOptions is a struct representing the options to send to the rebuild linode endpoint |
|
type RebuildInstanceOptions struct { |
|
Image string `json:"image"` |
|
RootPass string `json:"root_pass"` |
|
AuthorizedKeys []string `json:"authorized_keys"` |
|
AuthorizedUsers []string `json:"authorized_users"` |
|
StackscriptID int `json:"stackscript_id"` |
|
StackscriptData map[string]string `json:"stackscript_data"` |
|
Booted bool `json:"booted"` |
|
} |
|
|
|
// RebuildInstance Deletes all Disks and Configs on this Linode, |
|
// then deploys a new Image to this Linode with the given attributes. |
|
func (c *Client) RebuildInstance(ctx context.Context, id int, opts RebuildInstanceOptions) (*Instance, error) { |
|
o, err := json.Marshal(opts) |
|
if err != nil { |
|
return nil, NewError(err) |
|
} |
|
b := string(o) |
|
e, err := c.Instances.Endpoint() |
|
if err != nil { |
|
return nil, err |
|
} |
|
e = fmt.Sprintf("%s/%d/rebuild", e, id) |
|
r, err := coupleAPIErrors(c.R(ctx). |
|
SetBody(b). |
|
SetResult(&Instance{}). |
|
Post(e)) |
|
if err != nil { |
|
return nil, err |
|
} |
|
return r.Result().(*Instance).fixDates(), nil |
|
} |
|
|
|
// RescueInstanceOptions fields are those accepted by RescueInstance |
|
type RescueInstanceOptions struct { |
|
Devices InstanceConfigDeviceMap `json:"devices"` |
|
} |
|
|
|
// RescueInstance reboots an instance into a safe environment for performing many system recovery and disk management tasks. |
|
// Rescue Mode is based on the Finnix recovery distribution, a self-contained and bootable Linux distribution. |
|
// You can also use Rescue Mode for tasks other than disaster recovery, such as formatting disks to use different filesystems, |
|
// copying data between disks, and downloading files from a disk via SSH and SFTP. |
|
func (c *Client) RescueInstance(ctx context.Context, id int, opts RescueInstanceOptions) error { |
|
o, err := json.Marshal(opts) |
|
if err != nil { |
|
return NewError(err) |
|
} |
|
b := string(o) |
|
e, err := c.Instances.Endpoint() |
|
if err != nil { |
|
return err |
|
} |
|
e = fmt.Sprintf("%s/%d/rescue", e, id) |
|
|
|
_, err = coupleAPIErrors(c.R(ctx). |
|
SetBody(b). |
|
Post(e)) |
|
|
|
return err |
|
} |
|
|
|
// ResizeInstance resizes an instance to new Linode type |
|
func (c *Client) ResizeInstance(ctx context.Context, id int, linodeType string) error { |
|
body := fmt.Sprintf("{\"type\":\"%s\"}", linodeType) |
|
|
|
e, err := c.Instances.Endpoint() |
|
if err != nil { |
|
return err |
|
} |
|
e = fmt.Sprintf("%s/%d/resize", e, id) |
|
|
|
_, err = coupleAPIErrors(c.R(ctx). |
|
SetBody(body). |
|
Post(e)) |
|
|
|
return err |
|
} |
|
|
|
// ShutdownInstance - Shutdown an instance |
|
func (c *Client) ShutdownInstance(ctx context.Context, id int) error { |
|
return c.simpleInstanceAction(ctx, "shutdown", id) |
|
} |
|
|
|
// MutateInstance Upgrades a Linode to its next generation. |
|
func (c *Client) MutateInstance(ctx context.Context, id int) error { |
|
return c.simpleInstanceAction(ctx, "mutate", id) |
|
} |
|
|
|
// MigrateInstance - Migrate an instance |
|
func (c *Client) MigrateInstance(ctx context.Context, id int) error { |
|
return c.simpleInstanceAction(ctx, "migrate", id) |
|
} |
|
|
|
// simpleInstanceAction is a helper for Instance actions that take no parameters |
|
// and return empty responses `{}` unless they return a standard error |
|
func (c *Client) simpleInstanceAction(ctx context.Context, action string, id int) error { |
|
e, err := c.Instances.Endpoint() |
|
if err != nil { |
|
return err |
|
} |
|
e = fmt.Sprintf("%s/%d/%s", e, id, action) |
|
_, err = coupleAPIErrors(c.R(ctx).Post(e)) |
|
return err |
|
}
|
|
|