diff --git a/vendor/github.com/joyent/triton-go/client.go b/vendor/github.com/joyent/triton-go/client.go index 2b840bba5..e34c6b1fe 100644 --- a/vendor/github.com/joyent/triton-go/client.go +++ b/vendor/github.com/joyent/triton-go/client.go @@ -10,7 +10,6 @@ import ( "net/http" "net/url" "os" - "strings" "time" "github.com/hashicorp/errwrap" @@ -22,7 +21,7 @@ import ( type Client struct { client *retryablehttp.Client authorizer []authentication.Signer - endpoint string + apiURL url.URL accountName string } @@ -36,6 +35,15 @@ func NewClient(endpoint string, accountName string, signers ...authentication.Si defaultRetryWaitMax := 5 * time.Minute defaultRetryMax := 32 + apiURL, err := url.Parse(endpoint) + if err != nil { + return nil, errwrap.Wrapf("invalid endpoint: {{err}}", err) + } + + if accountName == "" { + return nil, fmt.Errorf("account name can not be empty") + } + httpClient := &http.Client{ Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, @@ -62,7 +70,7 @@ func NewClient(endpoint string, accountName string, signers ...authentication.Si return &Client{ client: retryableClient, authorizer: signers, - endpoint: strings.TrimSuffix(endpoint, "/"), + apiURL: *apiURL, accountName: accountName, }, nil } @@ -71,10 +79,6 @@ func doNotFollowRedirects(*http.Request, []*http.Request) error { return http.ErrUseLastResponse } -func (c *Client) formatURL(path string) string { - return fmt.Sprintf("%s%s", c.endpoint, path) -} - func (c *Client) executeRequestURIParams(method, path string, body interface{}, query *url.Values) (io.ReadCloser, error) { var requestBody io.ReadSeeker if body != nil { @@ -85,7 +89,13 @@ func (c *Client) executeRequestURIParams(method, path string, body interface{}, requestBody = bytes.NewReader(marshaled) } - req, err := retryablehttp.NewRequest(method, c.formatURL(path), requestBody) + endpoint := c.apiURL + endpoint.Path = path + if query != nil { + endpoint.RawQuery = query.Encode() + } + + req, err := retryablehttp.NewRequest(method, endpoint.String(), requestBody) if err != nil { return nil, errwrap.Wrapf("Error constructing HTTP request: {{err}}", err) } @@ -106,10 +116,6 @@ func (c *Client) executeRequestURIParams(method, path string, body interface{}, req.Header.Set("Content-Type", "application/json") } - if query != nil { - req.URL.RawQuery = query.Encode() - } - resp, err := c.client.Do(req) if err != nil { return nil, errwrap.Wrapf("Error executing HTTP request: {{err}}", err) @@ -149,7 +155,10 @@ func (c *Client) executeRequestRaw(method, path string, body interface{}) (*http requestBody = bytes.NewReader(marshaled) } - req, err := retryablehttp.NewRequest(method, c.formatURL(path), requestBody) + endpoint := c.apiURL + endpoint.Path = path + + req, err := retryablehttp.NewRequest(method, endpoint.String(), requestBody) if err != nil { return nil, errwrap.Wrapf("Error constructing HTTP request: {{err}}", err) } diff --git a/vendor/github.com/joyent/triton-go/machines.go b/vendor/github.com/joyent/triton-go/machines.go index 8f2de9736..0fae69b4a 100644 --- a/vendor/github.com/joyent/triton-go/machines.go +++ b/vendor/github.com/joyent/triton-go/machines.go @@ -4,10 +4,12 @@ import ( "encoding/json" "fmt" "net/http" + "strings" "time" - "github.com/hashicorp/errwrap" "net/url" + + "github.com/hashicorp/errwrap" ) type MachinesClient struct { @@ -20,6 +22,21 @@ 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"` @@ -41,6 +58,14 @@ type Machine struct { 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 { @@ -57,7 +82,19 @@ 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 { @@ -73,13 +110,51 @@ func (client *MachinesClient) GetMachine(input *GetMachineInput) (*Machine, erro client.decodeError(response.StatusCode, response.Body)) } - var result *Machine + 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) } - return result, nil + 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 { @@ -93,23 +168,31 @@ type CreateMachineInput struct { Metadata map[string]string Tags map[string]string FirewallEnabled bool + CNS MachineCNS } -func transformCreateMachineInput(input *CreateMachineInput) map[string]interface{} { - result := make(map[string]interface{}, 8+len(input.Metadata)+len(input.Tags)) +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"` @@ -123,6 +206,11 @@ func transformCreateMachineInput(input *CreateMachineInput) map[string]interface 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 } @@ -131,7 +219,7 @@ func transformCreateMachineInput(input *CreateMachineInput) map[string]interface } func (client *MachinesClient) CreateMachine(input *CreateMachineInput) (*Machine, error) { - respReader, err := client.executeRequest(http.MethodPost, "/my/machines", transformCreateMachineInput(input)) + respReader, err := client.executeRequest(http.MethodPost, "/my/machines", input.toAPI()) if respReader != nil { defer respReader.Close() } @@ -309,13 +397,14 @@ func (client *MachinesClient) ListMachineTags(input *ListMachineTagsInput) (map[ return nil, errwrap.Wrapf("Error executing ListMachineTags request: {{err}}", err) } - var result map[string]string + 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) } - return result, nil + _, tags := machineTagsExtractMeta(result) + return tags, nil } type UpdateMachineMetadataInput struct { @@ -470,3 +559,61 @@ func (client *MachinesClient) RemoveNIC(input *RemoveNICInput) error { 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, ",") + } +} diff --git a/vendor/vendor.json b/vendor/vendor.json index ed7976359..b7e96ab25 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -2294,10 +2294,10 @@ "revisionTime": "2016-06-16T18:50:15Z" }, { - "checksumSHA1": "fue8Al8kqw/Q6VFPsNzoky7NIgo=", + "checksumSHA1": "2HimxaJVVp2QDVQ0570L71Zd5s4=", "path": "github.com/joyent/triton-go", - "revision": "66b31a94af28a65e902423879a2820ea34b773fb", - "revisionTime": "2017-03-31T18:12:29Z" + "revision": "5db9e2b6a4c1f7ffd2a7e7aa625f42dba956608c", + "revisionTime": "2017-04-12T23:23:58Z" }, { "checksumSHA1": "QzUqkCSn/ZHyIK346xb9V6EBw9U=",