307 lines
11 KiB
Go
307 lines
11 KiB
Go
package cloudapi
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"strings"
|
|
|
|
"github.com/joyent/gocommon/client"
|
|
"github.com/joyent/gocommon/errors"
|
|
)
|
|
|
|
// Machine represent a provisioned virtual machines
|
|
type Machine struct {
|
|
Id string // Unique identifier for the image
|
|
Name string // Machine friendly name
|
|
Type string // Machine type, one of 'smartmachine' or 'virtualmachine'
|
|
State string // Current state of the machine
|
|
Dataset string // The dataset URN the machine was provisioned with. For new images/datasets this value will be the dataset id, i.e, same value than the image attribute
|
|
Memory int // The amount of memory the machine has (in Mb)
|
|
Disk int // The amount of disk the machine has (in Gb)
|
|
IPs []string // The IP addresses the machine has
|
|
Metadata map[string]string // Map of the machine metadata, e.g. authorized-keys
|
|
Tags map[string]string // Map of the machine tags
|
|
Created string // When the machine was created
|
|
Updated string // When the machine was updated
|
|
Package string // The name of the package used to create the machine
|
|
Image string // The image id the machine was provisioned with
|
|
PrimaryIP string // The primary (public) IP address for the machine
|
|
Networks []string // The network IDs for the machine
|
|
FirewallEnabled bool `json:"firewall_enabled"` // whether or not the firewall is enabled
|
|
}
|
|
|
|
// Equals compares two machines. Ignores state and timestamps.
|
|
func (m Machine) Equals(other Machine) bool {
|
|
if m.Id == other.Id && m.Name == other.Name && m.Type == other.Type && m.Dataset == other.Dataset &&
|
|
m.Memory == other.Memory && m.Disk == other.Disk && m.Package == other.Package && m.Image == other.Image &&
|
|
m.compareIPs(other) && m.compareMetadata(other) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Helper method to compare two machines IPs
|
|
func (m Machine) compareIPs(other Machine) bool {
|
|
if len(m.IPs) != len(other.IPs) {
|
|
return false
|
|
}
|
|
for i, v := range m.IPs {
|
|
if v != other.IPs[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Helper method to compare two machines metadata
|
|
func (m Machine) compareMetadata(other Machine) bool {
|
|
if len(m.Metadata) != len(other.Metadata) {
|
|
return false
|
|
}
|
|
for k, v := range m.Metadata {
|
|
if v != other.Metadata[k] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// CreateMachineOpts represent the option that can be specified
|
|
// when creating a new machine.
|
|
type CreateMachineOpts struct {
|
|
Name string `json:"name"` // Machine friendly name, default is a randomly generated name
|
|
Package string `json:"package"` // Name of the package to use on provisioning
|
|
Image string `json:"image"` // The image UUID
|
|
Networks []string `json:"networks"` // Desired networks IDs
|
|
Metadata map[string]string `json:"-"` // An arbitrary set of metadata key/value pairs can be set at provision time
|
|
Tags map[string]string `json:"-"` // An arbitrary set of tags can be set at provision time
|
|
FirewallEnabled bool `json:"firewall_enabled"` // Completely enable or disable firewall for this machine (new in API version 7.0)
|
|
}
|
|
|
|
// AuditAction represents an action/event accomplished by a machine.
|
|
type AuditAction struct {
|
|
Action string // Action name
|
|
Parameters map[string]interface{} // Original set of parameters sent when the action was requested
|
|
Time string // When the action finished
|
|
Success string // Either 'yes' or 'no', depending on the action successfulness
|
|
Caller Caller // Account requesting the action
|
|
}
|
|
|
|
// Caller represents an account requesting an action.
|
|
type Caller struct {
|
|
Type string // Authentication type for the action request. One of 'basic', 'operator', 'signature' or 'token'
|
|
User string // When the authentication type is 'basic', this member will be present and include user login
|
|
IP string // The IP addresses this from which the action was requested. Not present if type is 'operator'
|
|
KeyId string // When authentication type is either 'signature' or 'token', SSH key identifier
|
|
}
|
|
|
|
// appendJSON marshals the given attribute value and appends it as an encoded value to the given json data.
|
|
// The newly encode (attr, value) is inserted just before the closing "}" in the json data.
|
|
func appendJSON(data []byte, attr string, value interface{}) ([]byte, error) {
|
|
newData, err := json.Marshal(&value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
strData := string(data)
|
|
result := fmt.Sprintf(`%s, "%s":%s}`, strData[:len(strData)-1], attr, string(newData))
|
|
return []byte(result), nil
|
|
}
|
|
|
|
type jsonOpts CreateMachineOpts
|
|
|
|
// MarshalJSON turns the given CreateMachineOpts into JSON
|
|
func (opts CreateMachineOpts) MarshalJSON() ([]byte, error) {
|
|
jo := jsonOpts(opts)
|
|
data, err := json.Marshal(&jo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for k, v := range opts.Tags {
|
|
if !strings.HasPrefix(k, "tag.") {
|
|
k = "tag." + k
|
|
}
|
|
data, err = appendJSON(data, k, v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
for k, v := range opts.Metadata {
|
|
if !strings.HasPrefix(k, "metadata.") {
|
|
k = "metadata." + k
|
|
}
|
|
data, err = appendJSON(data, k, v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return data, nil
|
|
}
|
|
|
|
// ListMachines lists all machines on record for an account.
|
|
// You can paginate this API by passing in offset, and limit
|
|
// See API docs: http://apidocs.joyent.com/cloudapi/#ListMachines
|
|
func (c *Client) ListMachines(filter *Filter) ([]Machine, error) {
|
|
var resp []Machine
|
|
req := request{
|
|
method: client.GET,
|
|
url: apiMachines,
|
|
filter: filter,
|
|
resp: &resp,
|
|
}
|
|
if _, err := c.sendRequest(req); err != nil {
|
|
return nil, errors.Newf(err, "failed to get list of machines")
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
// CountMachines returns the number of machines on record for an account.
|
|
// See API docs: http://apidocs.joyent.com/cloudapi/#ListMachines
|
|
func (c *Client) CountMachines() (int, error) {
|
|
var resp int
|
|
req := request{
|
|
method: client.HEAD,
|
|
url: apiMachines,
|
|
resp: &resp,
|
|
}
|
|
if _, err := c.sendRequest(req); err != nil {
|
|
return -1, errors.Newf(err, "failed to get count of machines")
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
// GetMachine returns the machine specified by machineId.
|
|
// See API docs: http://apidocs.joyent.com/cloudapi/#GetMachine
|
|
func (c *Client) GetMachine(machineID string) (*Machine, error) {
|
|
var resp Machine
|
|
req := request{
|
|
method: client.GET,
|
|
url: makeURL(apiMachines, machineID),
|
|
resp: &resp,
|
|
}
|
|
if _, err := c.sendRequest(req); err != nil {
|
|
return nil, errors.Newf(err, "failed to get machine with id: %s", machineID)
|
|
}
|
|
return &resp, nil
|
|
}
|
|
|
|
// CreateMachine creates a new machine with the options specified.
|
|
// See API docs: http://apidocs.joyent.com/cloudapi/#CreateMachine
|
|
func (c *Client) CreateMachine(opts CreateMachineOpts) (*Machine, error) {
|
|
var resp Machine
|
|
req := request{
|
|
method: client.POST,
|
|
url: apiMachines,
|
|
reqValue: opts,
|
|
resp: &resp,
|
|
expectedStatus: http.StatusCreated,
|
|
}
|
|
if _, err := c.sendRequest(req); err != nil {
|
|
return nil, errors.Newf(err, "failed to create machine with name: %s", opts.Name)
|
|
}
|
|
return &resp, nil
|
|
}
|
|
|
|
// StopMachine stops a running machine.
|
|
// See API docs: http://apidocs.joyent.com/cloudapi/#StopMachine
|
|
func (c *Client) StopMachine(machineID string) error {
|
|
req := request{
|
|
method: client.POST,
|
|
url: fmt.Sprintf("%s/%s?action=%s", apiMachines, machineID, actionStop),
|
|
expectedStatus: http.StatusAccepted,
|
|
}
|
|
if _, err := c.sendRequest(req); err != nil {
|
|
return errors.Newf(err, "failed to stop machine with id: %s", machineID)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// StartMachine starts a stopped machine.
|
|
// See API docs: http://apidocs.joyent.com/cloudapi/#StartMachine
|
|
func (c *Client) StartMachine(machineID string) error {
|
|
req := request{
|
|
method: client.POST,
|
|
url: fmt.Sprintf("%s/%s?action=%s", apiMachines, machineID, actionStart),
|
|
expectedStatus: http.StatusAccepted,
|
|
}
|
|
if _, err := c.sendRequest(req); err != nil {
|
|
return errors.Newf(err, "failed to start machine with id: %s", machineID)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RebootMachine reboots (stop followed by a start) a machine.
|
|
// See API docs: http://apidocs.joyent.com/cloudapi/#RebootMachine
|
|
func (c *Client) RebootMachine(machineID string) error {
|
|
req := request{
|
|
method: client.POST,
|
|
url: fmt.Sprintf("%s/%s?action=%s", apiMachines, machineID, actionReboot),
|
|
expectedStatus: http.StatusAccepted,
|
|
}
|
|
if _, err := c.sendRequest(req); err != nil {
|
|
return errors.Newf(err, "failed to reboot machine with id: %s", machineID)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ResizeMachine allows you to resize a SmartMachine. Virtual machines can also
|
|
// be resized, but only resizing virtual machines to a higher capacity package
|
|
// is supported.
|
|
// See API docs: http://apidocs.joyent.com/cloudapi/#ResizeMachine
|
|
func (c *Client) ResizeMachine(machineID, packageName string) error {
|
|
req := request{
|
|
method: client.POST,
|
|
url: fmt.Sprintf("%s/%s?action=%s&package=%s", apiMachines, machineID, actionResize, packageName),
|
|
expectedStatus: http.StatusAccepted,
|
|
}
|
|
if _, err := c.sendRequest(req); err != nil {
|
|
return errors.Newf(err, "failed to resize machine with id: %s", machineID)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RenameMachine renames an existing machine.
|
|
// See API docs: http://apidocs.joyent.com/cloudapi/#RenameMachine
|
|
func (c *Client) RenameMachine(machineID, machineName string) error {
|
|
req := request{
|
|
method: client.POST,
|
|
url: fmt.Sprintf("%s/%s?action=%s&name=%s", apiMachines, machineID, actionRename, machineName),
|
|
expectedStatus: http.StatusAccepted,
|
|
}
|
|
if _, err := c.sendRequest(req); err != nil {
|
|
return errors.Newf(err, "failed to rename machine with id: %s", machineID)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DeleteMachine allows you to completely destroy a machine. Machine must be in the 'stopped' state.
|
|
// See API docs: http://apidocs.joyent.com/cloudapi/#DeleteMachine
|
|
func (c *Client) DeleteMachine(machineID string) error {
|
|
req := request{
|
|
method: client.DELETE,
|
|
url: makeURL(apiMachines, machineID),
|
|
expectedStatus: http.StatusNoContent,
|
|
}
|
|
if _, err := c.sendRequest(req); err != nil {
|
|
return errors.Newf(err, "failed to delete machine with id %s", machineID)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// MachineAudit provides a list of machine's accomplished actions, (sorted from
|
|
// latest to older one).
|
|
// See API docs: http://apidocs.joyent.com/cloudapi/#MachineAudit
|
|
func (c *Client) MachineAudit(machineID string) ([]AuditAction, error) {
|
|
var resp []AuditAction
|
|
req := request{
|
|
method: client.GET,
|
|
url: makeURL(apiMachines, machineID, apiAudit),
|
|
resp: &resp,
|
|
}
|
|
if _, err := c.sendRequest(req); err != nil {
|
|
return nil, errors.Newf(err, "failed to get actions for machine with id %s", machineID)
|
|
}
|
|
return resp, nil
|
|
}
|