435c0d9fc8
This PR switches the Nomad repository from using govendor to Go modules for managing dependencies. Aspects of the Nomad workflow remain pretty much the same. The usual Makefile targets should continue to work as they always did. The API submodule simply defers to the parent Nomad version on the repository, keeping the semantics of API versioning that currently exists.
455 lines
13 KiB
Go
455 lines
13 KiB
Go
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
|
|
}
|