Consul is a distributed, highly available, and data center aware solution to connect and configure applications across dynamic, distributed infrastructure.
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

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
}