terraform/vendor/github.com/joyent/triton-go/machines.go

620 lines
17 KiB
Go

package triton
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"net/url"
"github.com/hashicorp/errwrap"
)
type MachinesClient struct {
*Client
}
// Machines returns a client used for accessing functions pertaining to
// machine functionality in the Triton API.
func (c *Client) Machines() *MachinesClient {
return &MachinesClient{c}
}
const (
machineCNSTagDisable = "triton.cns.disable"
machineCNSTagReversePTR = "triton.cns.reverse_ptr"
machineCNSTagServices = "triton.cns.services"
)
// MachineCNS is a container for the CNS-specific attributes. In the API these
// values are embedded within a Machine's Tags attribute, however they are
// exposed to the caller as their native types.
type MachineCNS struct {
Disable *bool
ReversePTR *string
Services []string
}
type Machine struct {
ID string `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Brand string `json:"brand"`
State string `json:"state"`
Image string `json:"image"`
Memory int `json:"memory"`
Disk int `json:"disk"`
Metadata map[string]string `json:"metadata"`
Tags map[string]string `json:"tags"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
Docker bool `json:"docker"`
IPs []string `json:"ips"`
Networks []string `json:"networks"`
PrimaryIP string `json:"primaryIp"`
FirewallEnabled bool `json:"firewall_enabled"`
ComputeNode string `json:"compute_node"`
Package string `json:"package"`
DomainNames []string `json:"dns_names"`
CNS MachineCNS
}
// _Machine is a private facade over Machine that handles the necessary API
// overrides from vmapi's machine endpoint(s).
type _Machine struct {
Machine
Tags map[string]interface{} `json:"tags"`
}
type NIC struct {
IP string `json:"ip"`
MAC string `json:"mac"`
Primary bool `json:"primary"`
Netmask string `json:"netmask"`
Gateway string `json:"gateway"`
State string `json:"state"`
Network string `json:"network"`
}
type GetMachineInput struct {
ID string
}
func (gmi *GetMachineInput) Validate() error {
if gmi.ID == "" {
return fmt.Errorf("machine ID can not be empty")
}
return nil
}
func (client *MachinesClient) GetMachine(input *GetMachineInput) (*Machine, error) {
if err := input.Validate(); err != nil {
return nil, errwrap.Wrapf("unable to get machine: {{err}}", err)
}
path := fmt.Sprintf("/%s/machines/%s", client.accountName, input.ID)
response, err := client.executeRequestRaw(http.MethodGet, path, nil)
if response != nil {
defer response.Body.Close()
}
if response.StatusCode == http.StatusNotFound {
return nil, &TritonError{
Code: "ResourceNotFound",
}
}
if err != nil {
return nil, errwrap.Wrapf("Error executing GetMachine request: {{err}}",
client.decodeError(response.StatusCode, response.Body))
}
var result *_Machine
decoder := json.NewDecoder(response.Body)
if err = decoder.Decode(&result); err != nil {
return nil, errwrap.Wrapf("Error decoding GetMachine response: {{err}}", err)
}
native, err := result.toNative()
if err != nil {
return nil, errwrap.Wrapf("unable to convert API response for machines to native type: {{err}}", err)
}
return native, nil
}
func (client *MachinesClient) GetMachines() ([]*Machine, error) {
path := fmt.Sprintf("/%s/machines", client.accountName)
response, err := client.executeRequestRaw(http.MethodGet, path, nil)
if response != nil {
defer response.Body.Close()
}
if response.StatusCode == http.StatusNotFound {
return nil, &TritonError{
Code: "ResourceNotFound",
}
}
if err != nil {
return nil, errwrap.Wrapf("Error executing GetMachines request: {{err}}",
client.decodeError(response.StatusCode, response.Body))
}
var results []*_Machine
decoder := json.NewDecoder(response.Body)
if err = decoder.Decode(&results); err != nil {
return nil, errwrap.Wrapf("Error decoding GetMachines response: {{err}}", err)
}
machines := make([]*Machine, 0, len(results))
for _, machineAPI := range results {
native, err := machineAPI.toNative()
if err != nil {
return nil, errwrap.Wrapf("unable to convert API response for machines to native type: {{err}}", err)
}
machines = append(machines, native)
}
return machines, nil
}
type CreateMachineInput struct {
Name string
Package string
Image string
Networks []string
LocalityStrict bool
LocalityNear []string
LocalityFar []string
Metadata map[string]string
Tags map[string]string
FirewallEnabled bool
CNS MachineCNS
}
func (input *CreateMachineInput) toAPI() map[string]interface{} {
const numExtraParams = 8
result := make(map[string]interface{}, numExtraParams+len(input.Metadata)+len(input.Tags))
result["firewall_enabled"] = input.FirewallEnabled
if input.Name != "" {
result["name"] = input.Name
}
if input.Package != "" {
result["package"] = input.Package
}
if input.Image != "" {
result["image"] = input.Image
}
if len(input.Networks) > 0 {
result["networks"] = input.Networks
}
locality := struct {
Strict bool `json:"strict"`
Near []string `json:"near,omitempty"`
Far []string `json:"far,omitempty"`
}{
Strict: input.LocalityStrict,
Near: input.LocalityNear,
Far: input.LocalityFar,
}
result["locality"] = locality
for key, value := range input.Tags {
result[fmt.Sprintf("tag.%s", key)] = value
}
// Deliberately clobber any user-specified Tags with the attributes from the
// CNS struct.
input.CNS.toTags(result)
for key, value := range input.Metadata {
result[fmt.Sprintf("metadata.%s", key)] = value
}
return result
}
func (client *MachinesClient) CreateMachine(input *CreateMachineInput) (*Machine, error) {
respReader, err := client.executeRequest(http.MethodPost, "/my/machines", input.toAPI())
if respReader != nil {
defer respReader.Close()
}
if err != nil {
return nil, errwrap.Wrapf("Error executing CreateMachine request: {{err}}", err)
}
var result *Machine
decoder := json.NewDecoder(respReader)
if err = decoder.Decode(&result); err != nil {
return nil, errwrap.Wrapf("Error decoding CreateMachine response: {{err}}", err)
}
return result, nil
}
type DeleteMachineInput struct {
ID string
}
func (client *MachinesClient) DeleteMachine(input *DeleteMachineInput) error {
path := fmt.Sprintf("/%s/machines/%s", client.accountName, input.ID)
response, err := client.executeRequestRaw(http.MethodDelete, path, nil)
if response.Body != nil {
defer response.Body.Close()
}
if response.StatusCode == http.StatusNotFound {
return nil
}
if err != nil {
return errwrap.Wrapf("Error executing DeleteMachine request: {{err}}",
client.decodeError(response.StatusCode, response.Body))
}
return nil
}
type DeleteMachineTagsInput struct {
ID string
}
func (client *MachinesClient) DeleteMachineTags(input *DeleteMachineTagsInput) error {
path := fmt.Sprintf("/%s/machines/%s/tags", client.accountName, input.ID)
response, err := client.executeRequestRaw(http.MethodDelete, path, nil)
if response.Body != nil {
defer response.Body.Close()
}
if response.StatusCode == http.StatusNotFound {
return nil
}
if err != nil {
return errwrap.Wrapf("Error executing DeleteMachineTags request: {{err}}",
client.decodeError(response.StatusCode, response.Body))
}
return nil
}
type DeleteMachineTagInput struct {
ID string
Key string
}
func (client *MachinesClient) DeleteMachineTag(input *DeleteMachineTagInput) error {
path := fmt.Sprintf("/%s/machines/%s/tags/%s", client.accountName, input.ID, input.Key)
response, err := client.executeRequestRaw(http.MethodDelete, path, nil)
if response.Body != nil {
defer response.Body.Close()
}
if response.StatusCode == http.StatusNotFound {
return nil
}
if err != nil {
return errwrap.Wrapf("Error executing DeleteMachineTag request: {{err}}",
client.decodeError(response.StatusCode, response.Body))
}
return nil
}
type RenameMachineInput struct {
ID string
Name string
}
func (client *MachinesClient) RenameMachine(input *RenameMachineInput) error {
path := fmt.Sprintf("/%s/machines/%s", client.accountName, input.ID)
params := &url.Values{}
params.Set("action", "rename")
params.Set("name", input.Name)
respReader, err := client.executeRequestURIParams(http.MethodPost, path, nil, params)
if respReader != nil {
defer respReader.Close()
}
if err != nil {
return errwrap.Wrapf("Error executing RenameMachine request: {{err}}", err)
}
return nil
}
type ReplaceMachineTagsInput struct {
ID string
Tags map[string]string
}
func (client *MachinesClient) ReplaceMachineTags(input *ReplaceMachineTagsInput) error {
path := fmt.Sprintf("/%s/machines/%s/tags", client.accountName, input.ID)
respReader, err := client.executeRequest(http.MethodPut, path, input.Tags)
if respReader != nil {
defer respReader.Close()
}
if err != nil {
return errwrap.Wrapf("Error executing ReplaceMachineTags request: {{err}}", err)
}
return nil
}
type AddMachineTagsInput struct {
ID string
Tags map[string]string
}
func (client *MachinesClient) AddMachineTags(input *AddMachineTagsInput) error {
path := fmt.Sprintf("/%s/machines/%s/tags", client.accountName, input.ID)
respReader, err := client.executeRequest(http.MethodPost, path, input.Tags)
if respReader != nil {
defer respReader.Close()
}
if err != nil {
return errwrap.Wrapf("Error executing AddMachineTags request: {{err}}", err)
}
return nil
}
type GetMachineTagInput struct {
ID string
Key string
}
func (client *MachinesClient) GetMachineTag(input *GetMachineTagInput) (string, error) {
path := fmt.Sprintf("/%s/machines/%s/tags/%s", client.accountName, input.ID, input.Key)
respReader, err := client.executeRequest(http.MethodGet, path, nil)
if respReader != nil {
defer respReader.Close()
}
if err != nil {
return "", errwrap.Wrapf("Error executing GetMachineTag request: {{err}}", err)
}
var result string
decoder := json.NewDecoder(respReader)
if err = decoder.Decode(&result); err != nil {
return "", errwrap.Wrapf("Error decoding GetMachineTag response: {{err}}", err)
}
return result, nil
}
type ListMachineTagsInput struct {
ID string
}
func (client *MachinesClient) ListMachineTags(input *ListMachineTagsInput) (map[string]string, error) {
path := fmt.Sprintf("/%s/machines/%s/tags", client.accountName, input.ID)
respReader, err := client.executeRequest(http.MethodGet, path, nil)
if respReader != nil {
defer respReader.Close()
}
if err != nil {
return nil, errwrap.Wrapf("Error executing ListMachineTags request: {{err}}", err)
}
var result map[string]interface{}
decoder := json.NewDecoder(respReader)
if err = decoder.Decode(&result); err != nil {
return nil, errwrap.Wrapf("Error decoding ListMachineTags response: {{err}}", err)
}
_, tags := machineTagsExtractMeta(result)
return tags, nil
}
type UpdateMachineMetadataInput struct {
ID string
Metadata map[string]string
}
func (client *MachinesClient) UpdateMachineMetadata(input *UpdateMachineMetadataInput) (map[string]string, error) {
path := fmt.Sprintf("/%s/machines/%s/tags", client.accountName, input.ID)
respReader, err := client.executeRequest(http.MethodPost, path, input.Metadata)
if respReader != nil {
defer respReader.Close()
}
if err != nil {
return nil, errwrap.Wrapf("Error executing UpdateMachineMetadata request: {{err}}", err)
}
var result map[string]string
decoder := json.NewDecoder(respReader)
if err = decoder.Decode(&result); err != nil {
return nil, errwrap.Wrapf("Error decoding UpdateMachineMetadata response: {{err}}", err)
}
return result, nil
}
type ResizeMachineInput struct {
ID string
Package string
}
func (client *MachinesClient) ResizeMachine(input *ResizeMachineInput) error {
path := fmt.Sprintf("/%s/machines/%s", client.accountName, input.ID)
params := &url.Values{}
params.Set("action", "resize")
params.Set("package", input.Package)
respReader, err := client.executeRequestURIParams(http.MethodPost, path, nil, params)
if respReader != nil {
defer respReader.Close()
}
if err != nil {
return errwrap.Wrapf("Error executing ResizeMachine request: {{err}}", err)
}
return nil
}
type EnableMachineFirewallInput struct {
ID string
}
func (client *MachinesClient) EnableMachineFirewall(input *EnableMachineFirewallInput) error {
path := fmt.Sprintf("/%s/machines/%s", client.accountName, input.ID)
params := &url.Values{}
params.Set("action", "enable_firewall")
respReader, err := client.executeRequestURIParams(http.MethodPost, path, nil, params)
if respReader != nil {
defer respReader.Close()
}
if err != nil {
return errwrap.Wrapf("Error executing EnableMachineFirewall request: {{err}}", err)
}
return nil
}
type DisableMachineFirewallInput struct {
ID string
}
func (client *MachinesClient) DisableMachineFirewall(input *DisableMachineFirewallInput) error {
path := fmt.Sprintf("/%s/machines/%s", client.accountName, input.ID)
params := &url.Values{}
params.Set("action", "disable_firewall")
respReader, err := client.executeRequestURIParams(http.MethodPost, path, nil, params)
if respReader != nil {
defer respReader.Close()
}
if err != nil {
return errwrap.Wrapf("Error executing DisableMachineFirewall request: {{err}}", err)
}
return nil
}
type ListNICsInput struct {
MachineID string
}
func (client *MachinesClient) ListNICs(input *ListNICsInput) ([]*NIC, error) {
respReader, err := client.executeRequest(http.MethodGet, fmt.Sprintf("/my/machines/%s/nics", input.MachineID), nil)
if respReader != nil {
defer respReader.Close()
}
if err != nil {
return nil, errwrap.Wrapf("Error executing ListNICs request: {{err}}", err)
}
var result []*NIC
decoder := json.NewDecoder(respReader)
if err = decoder.Decode(&result); err != nil {
return nil, errwrap.Wrapf("Error decoding ListNICs response: {{err}}", err)
}
return result, nil
}
type AddNICInput struct {
MachineID string `json:"-"`
Network string `json:"network"`
}
func (client *MachinesClient) AddNIC(input *AddNICInput) (*NIC, error) {
path := fmt.Sprintf("/%s/machines/%s/nics", client.accountName, input.MachineID)
respReader, err := client.executeRequest(http.MethodPost, path, input)
if respReader != nil {
defer respReader.Close()
}
if err != nil {
return nil, errwrap.Wrapf("Error executing AddNIC request: {{err}}", err)
}
var result *NIC
decoder := json.NewDecoder(respReader)
if err = decoder.Decode(&result); err != nil {
return nil, errwrap.Wrapf("Error decoding AddNIC response: {{err}}", err)
}
return result, nil
}
type RemoveNICInput struct {
MachineID string
MAC string
}
func (client *MachinesClient) RemoveNIC(input *RemoveNICInput) error {
path := fmt.Sprintf("/%s/machines/%s/nics/%s", client.accountName, input.MachineID, input.MAC)
respReader, err := client.executeRequest(http.MethodDelete, path, nil)
if respReader != nil {
defer respReader.Close()
}
if err != nil {
return errwrap.Wrapf("Error executing RemoveNIC request: {{err}}", err)
}
return nil
}
var reservedMachineCNSTags = map[string]struct{}{
machineCNSTagDisable: {},
machineCNSTagReversePTR: {},
machineCNSTagServices: {},
}
// machineTagsExtractMeta() extracts all of the misc parameters from Tags and
// returns a clean CNS and Tags struct.
func machineTagsExtractMeta(tags map[string]interface{}) (MachineCNS, map[string]string) {
nativeCNS := MachineCNS{}
nativeTags := make(map[string]string, len(tags))
for k, raw := range tags {
if _, found := reservedMachineCNSTags[k]; found {
switch k {
case machineCNSTagDisable:
b := raw.(bool)
nativeCNS.Disable = &b
case machineCNSTagReversePTR:
s := raw.(string)
nativeCNS.ReversePTR = &s
case machineCNSTagServices:
nativeCNS.Services = strings.Split(raw.(string), ",")
default:
// TODO(seanc@): should assert, logic fail
}
} else {
nativeTags[k] = raw.(string)
}
}
return nativeCNS, nativeTags
}
// toNative() exports a given _Machine (API representation) to its native object
// format.
func (api *_Machine) toNative() (*Machine, error) {
m := Machine(api.Machine)
m.CNS, m.Tags = machineTagsExtractMeta(api.Tags)
return &m, nil
}
// toTags() injects its state information into a Tags map suitable for use to
// submit an API call to the vmapi machine endpoint
func (mcns *MachineCNS) toTags(m map[string]interface{}) {
if mcns.Disable != nil {
s := fmt.Sprintf("%t", mcns.Disable)
m[machineCNSTagDisable] = &s
}
if mcns.ReversePTR != nil {
m[machineCNSTagReversePTR] = &mcns.ReversePTR
}
if len(mcns.Services) > 0 {
m[machineCNSTagServices] = strings.Join(mcns.Services, ",")
}
}