diff --git a/builder/triton/access_config.go b/builder/triton/access_config.go index 0f4d29e12..2df0ed150 100644 --- a/builder/triton/access_config.go +++ b/builder/triton/access_config.go @@ -6,10 +6,13 @@ import ( "io/ioutil" "os" + "github.com/hashicorp/errwrap" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/template/interpolate" - "github.com/joyent/triton-go" + tgo "github.com/joyent/triton-go" "github.com/joyent/triton-go/authentication" + "github.com/joyent/triton-go/compute" + "github.com/joyent/triton-go/network" ) // AccessConfig is for common configuration related to Triton access @@ -106,8 +109,39 @@ func (c *AccessConfig) createPrivateKeySigner() (authentication.Signer, error) { return signer, nil } -func (c *AccessConfig) CreateTritonClient() (*triton.Client, error) { - return triton.NewClient(c.Endpoint, c.Account, c.signer) +func (c *AccessConfig) CreateTritonClient() (*Client, error) { + + config := &tgo.ClientConfig{ + AccountName: c.Account, + TritonURL: c.Endpoint, + Signers: []authentication.Signer{c.signer}, + } + + return &Client{ + config: config, + }, nil +} + +type Client struct { + config *tgo.ClientConfig +} + +func (c *Client) Compute() (*compute.ComputeClient, error) { + computeClient, err := compute.NewClient(c.config) + if err != nil { + return nil, errwrap.Wrapf("Error Creating Triton Compute Client: {{err}}", err) + } + + return computeClient, nil +} + +func (c *Client) Network() (*network.NetworkClient, error) { + networkClient, err := network.NewClient(c.config) + if err != nil { + return nil, errwrap.Wrapf("Error Creating Triton Network Client: {{err}}", err) + } + + return networkClient, nil } func (c *AccessConfig) Comm() communicator.Config { diff --git a/builder/triton/driver_triton.go b/builder/triton/driver_triton.go index cfb3b0e72..a6bc5c153 100644 --- a/builder/triton/driver_triton.go +++ b/builder/triton/driver_triton.go @@ -7,11 +7,12 @@ import ( "time" "github.com/hashicorp/packer/packer" - "github.com/joyent/triton-go" + "github.com/joyent/triton-go/client" + "github.com/joyent/triton-go/compute" ) type driverTriton struct { - client *triton.Client + client *Client ui packer.Ui } @@ -28,7 +29,8 @@ func NewDriverTriton(ui packer.Ui, config Config) (Driver, error) { } func (d *driverTriton) CreateImageFromMachine(machineId string, config Config) (string, error) { - image, err := d.client.Images().CreateImageFromMachine(context.Background(), &triton.CreateImageFromMachineInput{ + computeClient, _ := d.client.Compute() + image, err := computeClient.Images().CreateFromMachine(context.Background(), &compute.CreateImageFromMachineInput{ MachineID: machineId, Name: config.ImageName, Version: config.ImageVersion, @@ -46,7 +48,8 @@ func (d *driverTriton) CreateImageFromMachine(machineId string, config Config) ( } func (d *driverTriton) CreateMachine(config Config) (string, error) { - input := &triton.CreateMachineInput{ + computeClient, _ := d.client.Compute() + input := &compute.CreateInstanceInput{ Package: config.MachinePackage, Image: config.MachineImage, Metadata: config.MachineMetadata, @@ -66,7 +69,7 @@ func (d *driverTriton) CreateMachine(config Config) (string, error) { input.Networks = config.MachineNetworks } - machine, err := d.client.Machines().CreateMachine(context.Background(), input) + machine, err := computeClient.Instances().Create(context.Background(), input) if err != nil { return "", err } @@ -75,19 +78,22 @@ func (d *driverTriton) CreateMachine(config Config) (string, error) { } func (d *driverTriton) DeleteImage(imageId string) error { - return d.client.Images().DeleteImage(context.Background(), &triton.DeleteImageInput{ + computeClient, _ := d.client.Compute() + return computeClient.Images().Delete(context.Background(), &compute.DeleteImageInput{ ImageID: imageId, }) } func (d *driverTriton) DeleteMachine(machineId string) error { - return d.client.Machines().DeleteMachine(context.Background(), &triton.DeleteMachineInput{ + computeClient, _ := d.client.Compute() + return computeClient.Instances().Delete(context.Background(), &compute.DeleteInstanceInput{ ID: machineId, }) } func (d *driverTriton) GetMachineIP(machineId string) (string, error) { - machine, err := d.client.Machines().GetMachine(context.Background(), &triton.GetMachineInput{ + computeClient, _ := d.client.Compute() + machine, err := computeClient.Instances().Get(context.Background(), &compute.GetInstanceInput{ ID: machineId, }) if err != nil { @@ -98,8 +104,9 @@ func (d *driverTriton) GetMachineIP(machineId string) (string, error) { } func (d *driverTriton) StopMachine(machineId string) error { - return d.client.Machines().StopMachine(context.Background(), &triton.StopMachineInput{ - MachineID: machineId, + computeClient, _ := d.client.Compute() + return computeClient.Instances().Stop(context.Background(), &compute.StopInstanceInput{ + InstanceID: machineId, }) } @@ -111,7 +118,8 @@ func (d *driverTriton) StopMachine(machineId string) error { func (d *driverTriton) WaitForMachineState(machineId string, state string, timeout time.Duration) error { return waitFor( func() (bool, error) { - machine, err := d.client.Machines().GetMachine(context.Background(), &triton.GetMachineInput{ + computeClient, _ := d.client.Compute() + machine, err := computeClient.Instances().Get(context.Background(), &compute.GetInstanceInput{ ID: machineId, }) if machine == nil { @@ -130,14 +138,15 @@ func (d *driverTriton) WaitForMachineState(machineId string, state string, timeo func (d *driverTriton) WaitForMachineDeletion(machineId string, timeout time.Duration) error { return waitFor( func() (bool, error) { - _, err := d.client.Machines().GetMachine(context.Background(), &triton.GetMachineInput{ + computeClient, _ := d.client.Compute() + _, err := computeClient.Instances().Get(context.Background(), &compute.GetInstanceInput{ ID: machineId, }) if err != nil { // Return true only when we receive a 410 (Gone) response. A 404 // indicates that the machine is being deleted whereas a 410 indicates // that this process has completed. - if triErr, ok := err.(*triton.TritonError); ok && triErr.StatusCode == http.StatusGone { + if triErr, ok := err.(*client.TritonError); ok && triErr.StatusCode == http.StatusGone { return true, nil } } @@ -152,7 +161,8 @@ func (d *driverTriton) WaitForMachineDeletion(machineId string, timeout time.Dur func (d *driverTriton) WaitForImageCreation(imageId string, timeout time.Duration) error { return waitFor( func() (bool, error) { - image, err := d.client.Images().GetImage(context.Background(), &triton.GetImageInput{ + computeClient, _ := d.client.Compute() + image, err := computeClient.Images().Get(context.Background(), &compute.GetImageInput{ ImageID: imageId, }) if image == nil { diff --git a/vendor/github.com/joyent/triton-go/README.md b/vendor/github.com/joyent/triton-go/README.md index 6546ba373..1089c72da 100644 --- a/vendor/github.com/joyent/triton-go/README.md +++ b/vendor/github.com/joyent/triton-go/README.md @@ -1,193 +1,91 @@ # triton-go -`go-triton` is an idiomatic library exposing a client SDK for Go applications using the Joyent Triton API. +`triton-go` is an idiomatic library exposing a client SDK for Go applications +using Joyent's Triton Compute and Storage (Manta) APIs. ## Usage -Triton uses [HTTP Signature][4] to sign the Date header in each HTTP request made to the Triton API. Currently, requests can be signed using either a private key file loaded from disk (using an [`authentication.PrivateKeySigner`][5]), or using a key stored with the local SSH Agent (using an [`SSHAgentSigner`][6]. +Triton uses [HTTP Signature][4] to sign the Date header in each HTTP request +made to the Triton API. Currently, requests can be signed using either a private +key file loaded from disk (using an [`authentication.PrivateKeySigner`][5]), or +using a key stored with the local SSH Agent (using an [`SSHAgentSigner`][6]. -To construct a Signer, use the `New*` range of methods in the `authentication` package. In the case of `authentication.NewSSHAgentSigner`, the parameters are the fingerprint of the key with which to sign, and the account name (normally stored in the `SDC_ACCOUNT` environment variable). For example: +To construct a Signer, use the `New*` range of methods in the `authentication` +package. In the case of `authentication.NewSSHAgentSigner`, the parameters are +the fingerprint of the key with which to sign, and the account name (normally +stored in the `SDC_ACCOUNT` environment variable). For example: ``` const fingerprint := "a4:c6:f3:75:80:27:e0:03:a9:98:79:ef:c5:0a:06:11" sshKeySigner, err := authentication.NewSSHAgentSigner(fingerprint, "AccountName") if err != nil { - log.Fatalf("NewSSHAgentSigner: %s", err) + log.Fatalf("NewSSHAgentSigner: %s", err) } ``` -An appropriate key fingerprint can be generated using `ssh-keygen`: +An appropriate key fingerprint can be generated using `ssh-keygen`. ``` ssh-keygen -Emd5 -lf ~/.ssh/id_rsa.pub | cut -d " " -f 2 | sed 's/MD5://' ``` -To construct a Client, use the `NewClient` function, passing in the endpoint, account name and constructed signer: +Each top level package, `account`, `compute`, `identity`, `network`, all have +their own seperate client. In order to initialize a package client, simply pass +the global `triton.ClientConfig` struct into the client's constructor function. ```go -client, err := triton.NewClient("https://us-sw-1.api.joyent.com/", "AccountName", sshKeySigner) -if err != nil { - log.Fatalf("NewClient: %s", err) -} + config := &triton.ClientConfig{ + TritonURL: os.Getenv("SDC_URL"), + MantaURL: os.Getenv("MANTA_URL"), + AccountName: accountName, + Signers: []authentication.Signer{sshKeySigner}, + } + + c, err := compute.NewClient(config) + if err != nil { + log.Fatalf("compute.NewClient: %s", err) + } ``` -Having constructed a `triton.Client`, use the methods available to access functionality by functional grouping. For example, for access to operations on SSH keys, use the `Keys()` method to obtain a client which has access to the `CreateKey`, `ListKeys` and `DeleteKey` operations. For access to operations on Machines, use the `Machines()` method to obtain a client which has access to the `RenameMachine`, `GetMachineMetadata`, `GetMachineTag`, and other operations. +Constructing `compute.Client` returns an interface which exposes `compute` API +resources. The same goes for all other packages. Reference their unique +documentation for more information. -Operation methods take their formal parameters via a struct named `OperationInput` - for example when creating an SSH key, the `CreateKeyInput` struct is used with the `func CreateKey(*CreateKeyInput) (*Key, error)` method. This allows specification of named parameters: +The same `triton.ClientConfig` will initialize the Manta `storage` client as +well... +```go + c, err := storage.NewClient(config) + if err != nil { + log.Fatalf("storage.NewClient: %s", err) + } ``` -client := state.Client().Keys() -key, err := client.CreateKey(&CreateKeyInput{ - Name: "tempKey", - Key: "ssh-rsa .....", -}) -if err != nil { - panic(err) -} +## Error Handling -// Key contains the return value. -``` +If an error is returned by the HTTP API, the `error` returned from the function +will contain an instance of `compute.TritonError` in the chain. Error wrapping +is performed using the [errwrap][7] library from HashiCorp. -## Error Handling +## Acceptance Tests + +Acceptance Tests run directly against the Triton API, so you will need either a +local installation of Triton or an account with Joyent's Public Cloud offering +in order to run them. The tests create real resources (and thus cost real +money)! -If an error is returned by the HTTP API, the `error` returned from the function will contain an instance of `triton.TritonError` in the chain. Error wrapping is performed using the [errwrap][7] library from HashiCorp. - -## Completeness - -The following list is updated as new functionality is added. The complete list of operations is taken from the [CloudAPI documentation](https://apidocs.joyent.com/cloudapi). - -- Accounts - - [x] GetAccount - - [x] UpdateAccount -- Keys - - [x] ListKeys - - [x] GetKey - - [x] CreateKey - - [x] DeleteKey -- Users - - [ ] ListUsers - - [ ] GetUser - - [ ] CreateUser - - [ ] UpdateUser - - [ ] ChangeUserPassword - - [ ] DeleteUser -- Roles - - [x] ListRoles - - [x] GetRole - - [x] CreateRole - - [x] UpdateRole - - [x] DeleteRole -- Role Tags - - [ ] SetRoleTags -- Policies - - [ ] ListPolicies - - [ ] GetPolicy - - [ ] CreatePolicy - - [ ] UpdatePolicy - - [ ] DeletePolicy -- User SSH Keys - - [x] ListUserKeys - - [x] GetUserKey - - [x] CreateUserKey - - [x] DeleteUserKey -- Config - - [x] GetConfig - - [x] UpdateConfig -- Datacenters - - [x] ListDatacenters - - [x] GetDatacenter -- Services - - [x] ListServices -- Images - - [x] ListImages - - [x] GetImage - - [x] DeleteImage - - [x] ExportImage - - [x] CreateImageFromMachine - - [x] UpdateImage -- Packages - - [x] ListPackages - - [x] GetPackage -- Instances - - [ ] ListMachines - - [x] GetMachine - - [x] CreateMachine - - [ ] StopMachine - - [ ] StartMachine - - [ ] RebootMachine - - [x] ResizeMachine - - [x] RenameMachine - - [x] EnableMachineFirewall - - [x] DisableMachineFirewall - - [ ] CreateMachineSnapshot - - [ ] StartMachineFromSnapshot - - [ ] ListMachineSnapshots - - [ ] GetMachineSnapshot - - [ ] DeleteMachineSnapshot - - [x] UpdateMachineMetadata - - [ ] ListMachineMetadata - - [ ] GetMachineMetadata - - [ ] DeleteMachineMetadata - - [ ] DeleteAllMachineMetadata - - [x] AddMachineTags - - [x] ReplaceMachineTags - - [x] ListMachineTags - - [x] GetMachineTag - - [x] DeleteMachineTag - - [x] DeleteMachineTags - - [x] DeleteMachine - - [ ] MachineAudit -- Analytics - - [ ] DescribeAnalytics - - [ ] ListInstrumentations - - [ ] GetInstrumentation - - [ ] GetInstrumentationValue - - [ ] GetInstrumentationHeatmap - - [ ] GetInstrumentationHeatmapDetails - - [ ] CreateInstrumentation - - [ ] DeleteInstrumentation -- Firewall Rules - - [x] ListFirewallRules - - [x] GetFirewallRule - - [x] CreateFirewallRule - - [x] UpdateFirewallRule - - [x] EnableFirewallRule - - [x] DisableFirewallRule - - [x] DeleteFirewallRule - - [ ] ListMachineFirewallRules - - [x] ListFirewallRuleMachines -- Fabrics - - [x] ListFabricVLANs - - [x] CreateFabricVLAN - - [x] GetFabricVLAN - - [x] UpdateFabricVLAN - - [x] DeleteFabricVLAN - - [x] ListFabricNetworks - - [x] CreateFabricNetwork - - [x] GetFabricNetwork - - [x] DeleteFabricNetwork -- Networks - - [x] ListNetworks - - [x] GetNetwork -- Nics - - [ ] ListNics - - [ ] GetNic - - [x] AddNic - - [x] RemoveNic - -## Running Acceptance Tests - -Acceptance Tests run directly against the Triton API, so you will need either a local installation or Triton or an account with Joyent in order to run them. The tests create real resources (and thus cost real money!) - -In order to run acceptance tests, the following environment variables must be set: - -- `TRITON_TEST` - must be set to any value in order to indicate desire to create resources +In order to run acceptance tests, the following environment variables must be +set: + +- `TRITON_TEST` - must be set to any value in order to indicate desire to create + resources - `SDC_URL` - the base endpoint for the Triton API - `SDC_ACCOUNT` - the account name for the Triton API - `SDC_KEY_ID` - the fingerprint of the SSH key identifying the key -Additionally, you may set `SDC_KEY_MATERIAL` to the contents of an unencrypted private key. If this is set, the PrivateKeySigner (see above) will be used - if not the SSHAgentSigner will be used. +Additionally, you may set `SDC_KEY_MATERIAL` to the contents of an unencrypted +private key. If this is set, the PrivateKeySigner (see above) will be used - if +not the SSHAgentSigner will be used. ### Example Run @@ -195,11 +93,11 @@ The verbose output has been removed for brevity here. ``` $ HTTP_PROXY=http://localhost:8888 \ - TRITON_TEST=1 \ - SDC_URL=https://us-sw-1.api.joyent.com \ - SDC_ACCOUNT=AccountName \ - SDC_KEY_ID=a4:c6:f3:75:80:27:e0:03:a9:98:79:ef:c5:0a:06:11 \ - go test -v -run "TestAccKey" + TRITON_TEST=1 \ + SDC_URL=https://us-sw-1.api.joyent.com \ + SDC_ACCOUNT=AccountName \ + SDC_KEY_ID=a4:c6:f3:75:80:27:e0:03:a9:98:79:ef:c5:0a:06:11 \ + go test -v -run "TestAccKey" === RUN TestAccKey_Create --- PASS: TestAccKey_Create (12.46s) === RUN TestAccKey_Get @@ -207,10 +105,111 @@ $ HTTP_PROXY=http://localhost:8888 \ === RUN TestAccKey_Delete --- PASS: TestAccKey_Delete (15.08s) PASS -ok github.com/jen20/triton-go 31.861s +ok github.com/joyent/triton-go 31.861s +``` + +## Example API + +There's an `examples/` directory available with sample code setup for many of +the APIs within this library. Most of these can be run using `go run` and +referencing your SSH key file use by your active `triton` CLI profile. + +```sh +$ eval "$(triton env us-sw-1)" +$ SDC_KEY_FILE=~/.ssh/triton-id_rsa go run examples/compute/instances.go +``` + +The following is a complete example of how to initialize the `compute` package +client and list all instances under an account. More detailed usage of this +library follows. + +```go + + +package main + +import ( + "context" + "fmt" + "io/ioutil" + "log" + "os" + "time" + + triton "github.com/joyent/triton-go" + "github.com/joyent/triton-go/authentication" + "github.com/joyent/triton-go/compute" +) + +func main() { + keyID := os.Getenv("SDC_KEY_ID") + accountName := os.Getenv("SDC_ACCOUNT") + keyMaterial := os.Getenv("SDC_KEY_MATERIAL") + + var signer authentication.Signer + var err error + + if keyMaterial == "" { + signer, err = authentication.NewSSHAgentSigner(keyID, accountName) + if err != nil { + log.Fatalf("Error Creating SSH Agent Signer: {{err}}", err) + } + } else { + var keyBytes []byte + if _, err = os.Stat(keyMaterial); err == nil { + keyBytes, err = ioutil.ReadFile(keyMaterial) + if err != nil { + log.Fatalf("Error reading key material from %s: %s", + keyMaterial, err) + } + block, _ := pem.Decode(keyBytes) + if block == nil { + log.Fatalf( + "Failed to read key material '%s': no key found", keyMaterial) + } + + if block.Headers["Proc-Type"] == "4,ENCRYPTED" { + log.Fatalf( + "Failed to read key '%s': password protected keys are\n"+ + "not currently supported. Please decrypt the key prior to use.", keyMaterial) + } + + } else { + keyBytes = []byte(keyMaterial) + } + + signer, err = authentication.NewPrivateKeySigner(keyID, []byte(keyMaterial), accountName) + if err != nil { + log.Fatalf("Error Creating SSH Private Key Signer: {{err}}", err) + } + } + + config := &triton.ClientConfig{ + TritonURL: os.Getenv("SDC_URL"), + AccountName: accountName, + Signers: []authentication.Signer{signer}, + } + + c, err := compute.NewClient(config) + if err != nil { + log.Fatalf("compute.NewClient: %s", err) + } + + listInput := &compute.ListInstancesInput{} + instances, err := c.Instances().List(context.Background(), listInput) + if err != nil { + log.Fatalf("compute.Instances.List: %v", err) + } + numInstances := 0 + for _, instance := range instances { + numInstances++ + fmt.Println(fmt.Sprintf("-- Instance: %v", instance.Name)) + } +} + ``` -[4]: https://github.com/joyent/node-http-signature/blob/master/http_signing.md -[5]: https://godoc.org/github.com/joyent/go-triton/authentication -[6]: https://godoc.org/github.com/joyent/go-triton/authentication +[4]: https://github.com/joyent/node-http-signature/blob/master/http_signing.md +[5]: https://godoc.org/github.com/joyent/triton-go/authentication +[6]: https://godoc.org/github.com/joyent/triton-go/authentication [7]: https://github.com/hashicorp/go-errwrap diff --git a/vendor/github.com/joyent/triton-go/accounts.go b/vendor/github.com/joyent/triton-go/accounts.go deleted file mode 100644 index 88e7bbf12..000000000 --- a/vendor/github.com/joyent/triton-go/accounts.go +++ /dev/null @@ -1,95 +0,0 @@ -package triton - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/hashicorp/errwrap" -) - -type AccountsClient struct { - *Client -} - -// Accounts returns a c used for accessing functions pertaining -// to Account functionality in the Triton API. -func (c *Client) Accounts() *AccountsClient { - return &AccountsClient{c} -} - -type Account struct { - ID string `json:"id"` - Login string `json:"login"` - Email string `json:"email"` - CompanyName string `json:"companyName"` - FirstName string `json:"firstName"` - LastName string `json:"lastName"` - Address string `json:"address"` - PostalCode string `json:"postalCode"` - City string `json:"city"` - State string `json:"state"` - Country string `json:"country"` - Phone string `json:"phone"` - Created time.Time `json:"created"` - Updated time.Time `json:"updated"` - TritonCNSEnabled bool `json:"triton_cns_enabled"` -} - -type GetAccountInput struct{} - -func (client *AccountsClient) GetAccount(ctx context.Context, input *GetAccountInput) (*Account, error) { - path := fmt.Sprintf("/%s", client.accountName) - respReader, err := client.executeRequest(ctx, http.MethodGet, path, nil) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return nil, errwrap.Wrapf("Error executing GetAccount request: {{err}}", err) - } - - var result *Account - decoder := json.NewDecoder(respReader) - if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding GetAccount response: {{err}}", err) - } - - return result, nil -} - -type UpdateAccountInput struct { - Email string `json:"email,omitempty"` - CompanyName string `json:"companyName,omitempty"` - FirstName string `json:"firstName,omitempty"` - LastName string `json:"lastName,omitempty"` - Address string `json:"address,omitempty"` - PostalCode string `json:"postalCode,omitempty"` - City string `json:"city,omitempty"` - State string `json:"state,omitempty"` - Country string `json:"country,omitempty"` - Phone string `json:"phone,omitempty"` - TritonCNSEnabled bool `json:"triton_cns_enabled,omitempty"` -} - -// UpdateAccount updates your account details with the given parameters. -// TODO(jen20) Work out a safe way to test this -func (client *AccountsClient) UpdateAccount(ctx context.Context, input *UpdateAccountInput) (*Account, error) { - path := fmt.Sprintf("/%s", client.accountName) - respReader, err := client.executeRequest(ctx, http.MethodPost, path, input) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return nil, errwrap.Wrapf("Error executing UpdateAccount request: {{err}}", err) - } - - var result *Account - decoder := json.NewDecoder(respReader) - if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding UpdateAccount response: {{err}}", err) - } - - return result, nil -} diff --git a/vendor/github.com/joyent/triton-go/authentication/private_key_signer.go b/vendor/github.com/joyent/triton-go/authentication/private_key_signer.go index 20dc6bfed..43bc286f0 100644 --- a/vendor/github.com/joyent/triton-go/authentication/private_key_signer.go +++ b/vendor/github.com/joyent/triton-go/authentication/private_key_signer.go @@ -18,6 +18,7 @@ import ( type PrivateKeySigner struct { formattedKeyFingerprint string keyFingerprint string + algorithm string accountName string hashFunc crypto.Hash @@ -48,14 +49,22 @@ func NewPrivateKeySigner(keyFingerprint string, privateKeyMaterial []byte, accou return nil, errors.New("Private key file does not match public key fingerprint") } - return &PrivateKeySigner{ + signer := &PrivateKeySigner{ formattedKeyFingerprint: displayKeyFingerprint, keyFingerprint: keyFingerprint, accountName: accountName, hashFunc: crypto.SHA1, privateKey: rsakey, - }, nil + } + + _, algorithm, err := signer.SignRaw("HelloWorld") + if err != nil { + return nil, fmt.Errorf("Cannot sign using ssh agent: %s", err) + } + signer.algorithm = algorithm + + return signer, nil } func (s *PrivateKeySigner) Sign(dateHeader string) (string, error) { @@ -74,3 +83,24 @@ func (s *PrivateKeySigner) Sign(dateHeader string) (string, error) { keyID := fmt.Sprintf("/%s/keys/%s", s.accountName, s.formattedKeyFingerprint) return fmt.Sprintf(authorizationHeaderFormat, keyID, "rsa-sha1", headerName, signedBase64), nil } + +func (s *PrivateKeySigner) SignRaw(toSign string) (string, string, error) { + hash := s.hashFunc.New() + hash.Write([]byte(toSign)) + digest := hash.Sum(nil) + + signed, err := rsa.SignPKCS1v15(rand.Reader, s.privateKey, s.hashFunc, digest) + if err != nil { + return "", "", errwrap.Wrapf("Error signing date header: {{err}}", err) + } + signedBase64 := base64.StdEncoding.EncodeToString(signed) + return signedBase64, "rsa-sha1", nil +} + +func (s *PrivateKeySigner) KeyFingerprint() string { + return s.formattedKeyFingerprint +} + +func (s *PrivateKeySigner) DefaultAlgorithm() string { + return s.algorithm +} diff --git a/vendor/github.com/joyent/triton-go/authentication/signer.go b/vendor/github.com/joyent/triton-go/authentication/signer.go index dfc89ad44..6e3d31dd7 100644 --- a/vendor/github.com/joyent/triton-go/authentication/signer.go +++ b/vendor/github.com/joyent/triton-go/authentication/signer.go @@ -3,5 +3,8 @@ package authentication const authorizationHeaderFormat = `Signature keyId="%s",algorithm="%s",headers="%s",signature="%s"` type Signer interface { + DefaultAlgorithm() string + KeyFingerprint() string Sign(dateHeader string) (string, error) + SignRaw(toSign string) (string, string, error) } diff --git a/vendor/github.com/joyent/triton-go/authentication/ssh_agent_signer.go b/vendor/github.com/joyent/triton-go/authentication/ssh_agent_signer.go index 028743159..ea84c5070 100644 --- a/vendor/github.com/joyent/triton-go/authentication/ssh_agent_signer.go +++ b/vendor/github.com/joyent/triton-go/authentication/ssh_agent_signer.go @@ -2,6 +2,8 @@ package authentication import ( "crypto/md5" + "crypto/sha256" + "encoding/base64" "errors" "fmt" "net" @@ -16,6 +18,7 @@ import ( type SSHAgentSigner struct { formattedKeyFingerprint string keyFingerprint string + algorithm string accountName string keyIdentifier string @@ -41,15 +44,21 @@ func NewSSHAgentSigner(keyFingerprint, accountName string) (*SSHAgentSigner, err return nil, errwrap.Wrapf("Error listing keys in SSH Agent: %s", err) } - keyFingerprintMD5 := strings.Replace(keyFingerprint, ":", "", -1) + keyFingerprintStripped := strings.TrimPrefix(keyFingerprint, "MD5:") + keyFingerprintStripped = strings.TrimPrefix(keyFingerprintStripped, "SHA256:") + keyFingerprintStripped = strings.Replace(keyFingerprintStripped, ":", "", -1) var matchingKey ssh.PublicKey for _, key := range keys { - h := md5.New() - h.Write(key.Marshal()) - fp := fmt.Sprintf("%x", h.Sum(nil)) + keyMD5 := md5.New() + keyMD5.Write(key.Marshal()) + finalizedMD5 := fmt.Sprintf("%x", keyMD5.Sum(nil)) - if fp == keyFingerprintMD5 { + keySHA256 := sha256.New() + keySHA256.Write(key.Marshal()) + finalizedSHA256 := base64.RawStdEncoding.EncodeToString(keySHA256.Sum(nil)) + + if keyFingerprintStripped == finalizedMD5 || keyFingerprintStripped == finalizedSHA256 { matchingKey = key } } @@ -60,14 +69,22 @@ func NewSSHAgentSigner(keyFingerprint, accountName string) (*SSHAgentSigner, err formattedKeyFingerprint := formatPublicKeyFingerprint(matchingKey, true) - return &SSHAgentSigner{ + signer := &SSHAgentSigner{ formattedKeyFingerprint: formattedKeyFingerprint, keyFingerprint: keyFingerprint, accountName: accountName, agent: ag, key: matchingKey, keyIdentifier: fmt.Sprintf("/%s/keys/%s", accountName, formattedKeyFingerprint), - }, nil + } + + _, algorithm, err := signer.SignRaw("HelloWorld") + if err != nil { + return nil, fmt.Errorf("Cannot sign using ssh agent: %s", err) + } + signer.algorithm = algorithm + + return signer, nil } func (s *SSHAgentSigner) Sign(dateHeader string) (string, error) { @@ -102,3 +119,41 @@ func (s *SSHAgentSigner) Sign(dateHeader string) (string, error) { return fmt.Sprintf(authorizationHeaderFormat, s.keyIdentifier, authSignature.SignatureType(), headerName, authSignature.String()), nil } + +func (s *SSHAgentSigner) SignRaw(toSign string) (string, string, error) { + signature, err := s.agent.Sign(s.key, []byte(toSign)) + if err != nil { + return "", "", errwrap.Wrapf("Error signing string: {{err}}", err) + } + + keyFormat, err := keyFormatToKeyType(signature.Format) + if err != nil { + return "", "", errwrap.Wrapf("Error reading signature: {{err}}", err) + } + + var authSignature httpAuthSignature + switch keyFormat { + case "rsa": + authSignature, err = newRSASignature(signature.Blob) + if err != nil { + return "", "", errwrap.Wrapf("Error reading signature: {{err}}", err) + } + case "ecdsa": + authSignature, err = newECDSASignature(signature.Blob) + if err != nil { + return "", "", errwrap.Wrapf("Error reading signature: {{err}}", err) + } + default: + return "", "", fmt.Errorf("Unsupported algorithm from SSH agent: %s", signature.Format) + } + + return authSignature.String(), authSignature.SignatureType(), nil +} + +func (s *SSHAgentSigner) KeyFingerprint() string { + return s.formattedKeyFingerprint +} + +func (s *SSHAgentSigner) DefaultAlgorithm() string { + return s.algorithm +} diff --git a/vendor/github.com/joyent/triton-go/client.go b/vendor/github.com/joyent/triton-go/client.go deleted file mode 100644 index c0ecc5fd0..000000000 --- a/vendor/github.com/joyent/triton-go/client.go +++ /dev/null @@ -1,195 +0,0 @@ -package triton - -import ( - "bytes" - "context" - "crypto/tls" - "encoding/json" - "errors" - "io" - "net" - "net/http" - "net/url" - "time" - - "github.com/hashicorp/errwrap" - "github.com/joyent/triton-go/authentication" -) - -const nilContext = "nil context" - -// Client represents a connection to the Triton API. -type Client struct { - client *http.Client - authorizer []authentication.Signer - apiURL url.URL - accountName string -} - -// NewClient is used to construct a Client in order to make API -// requests to the Triton API. -// -// At least one signer must be provided - example signers include -// authentication.PrivateKeySigner and authentication.SSHAgentSigner. -func NewClient(endpoint string, accountName string, signers ...authentication.Signer) (*Client, error) { - apiURL, err := url.Parse(endpoint) - if err != nil { - return nil, errwrap.Wrapf("invalid endpoint: {{err}}", err) - } - - if accountName == "" { - return nil, errors.New("account name can not be empty") - } - - httpClient := &http.Client{ - Transport: httpTransport(false), - CheckRedirect: doNotFollowRedirects, - } - - return &Client{ - client: httpClient, - authorizer: signers, - apiURL: *apiURL, - accountName: accountName, - }, nil -} - -// InsecureSkipTLSVerify turns off TLS verification for the client connection. This -// allows connection to an endpoint with a certificate which was signed by a non- -// trusted CA, such as self-signed certificates. This can be useful when connecting -// to temporary Triton installations such as Triton Cloud-On-A-Laptop. -func (c *Client) InsecureSkipTLSVerify() { - if c.client == nil { - return - } - - c.client.Transport = httpTransport(true) -} - -func httpTransport(insecureSkipTLSVerify bool) *http.Transport { - return &http.Transport{ - Proxy: http.ProxyFromEnvironment, - Dial: (&net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, - }).Dial, - TLSHandshakeTimeout: 10 * time.Second, - DisableKeepAlives: true, - MaxIdleConnsPerHost: -1, - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: insecureSkipTLSVerify, - }, - } -} - -func doNotFollowRedirects(*http.Request, []*http.Request) error { - return http.ErrUseLastResponse -} - -func (c *Client) executeRequestURIParams(ctx context.Context, method, path string, body interface{}, query *url.Values) (io.ReadCloser, error) { - var requestBody io.ReadSeeker - if body != nil { - marshaled, err := json.MarshalIndent(body, "", " ") - if err != nil { - return nil, err - } - requestBody = bytes.NewReader(marshaled) - } - - endpoint := c.apiURL - endpoint.Path = path - if query != nil { - endpoint.RawQuery = query.Encode() - } - - req, err := http.NewRequest(method, endpoint.String(), requestBody) - if err != nil { - return nil, errwrap.Wrapf("Error constructing HTTP request: {{err}}", err) - } - - dateHeader := time.Now().UTC().Format(time.RFC1123) - req.Header.Set("date", dateHeader) - - authHeader, err := c.authorizer[0].Sign(dateHeader) - if err != nil { - return nil, errwrap.Wrapf("Error signing HTTP request: {{err}}", err) - } - req.Header.Set("Authorization", authHeader) - req.Header.Set("Accept", "application/json") - req.Header.Set("Accept-Version", "8") - req.Header.Set("User-Agent", "triton-go Client API") - - if body != nil { - req.Header.Set("Content-Type", "application/json") - } - - resp, err := c.client.Do(req.WithContext(ctx)) - if err != nil { - return nil, errwrap.Wrapf("Error executing HTTP request: {{err}}", err) - } - - if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices { - return resp.Body, nil - } - - return nil, c.decodeError(resp.StatusCode, resp.Body) -} - -func (c *Client) decodeError(statusCode int, body io.Reader) error { - err := &TritonError{ - StatusCode: statusCode, - } - - errorDecoder := json.NewDecoder(body) - if err := errorDecoder.Decode(err); err != nil { - return errwrap.Wrapf("Error decoding error response: {{err}}", err) - } - - return err -} - -func (c *Client) executeRequest(ctx context.Context, method, path string, body interface{}) (io.ReadCloser, error) { - return c.executeRequestURIParams(ctx, method, path, body, nil) -} - -func (c *Client) executeRequestRaw(ctx context.Context, method, path string, body interface{}) (*http.Response, error) { - var requestBody io.ReadSeeker - if body != nil { - marshaled, err := json.MarshalIndent(body, "", " ") - if err != nil { - return nil, err - } - requestBody = bytes.NewReader(marshaled) - } - - endpoint := c.apiURL - endpoint.Path = path - - req, err := http.NewRequest(method, endpoint.String(), requestBody) - if err != nil { - return nil, errwrap.Wrapf("Error constructing HTTP request: {{err}}", err) - } - - dateHeader := time.Now().UTC().Format(time.RFC1123) - req.Header.Set("date", dateHeader) - - authHeader, err := c.authorizer[0].Sign(dateHeader) - if err != nil { - return nil, errwrap.Wrapf("Error signing HTTP request: {{err}}", err) - } - req.Header.Set("Authorization", authHeader) - req.Header.Set("Accept", "application/json") - req.Header.Set("Accept-Version", "8") - req.Header.Set("User-Agent", "triton-go c API") - - if body != nil { - req.Header.Set("Content-Type", "application/json") - } - - resp, err := c.client.Do(req.WithContext(ctx)) - if err != nil { - return nil, errwrap.Wrapf("Error executing HTTP request: {{err}}", err) - } - - return resp, nil -} diff --git a/vendor/github.com/joyent/triton-go/client/client.go b/vendor/github.com/joyent/triton-go/client/client.go new file mode 100644 index 000000000..b01f86baf --- /dev/null +++ b/vendor/github.com/joyent/triton-go/client/client.go @@ -0,0 +1,397 @@ +package client + +import ( + "bytes" + "context" + "crypto/tls" + "encoding/json" + "errors" + "io" + "net" + "net/http" + "net/url" + "os" + "time" + + "github.com/hashicorp/errwrap" + "github.com/joyent/triton-go/authentication" +) + +const nilContext = "nil context" + +var MissingKeyIdError = errors.New("Default SSH agent authentication requires SDC_KEY_ID") + +// Client represents a connection to the Triton Compute or Object Storage APIs. +type Client struct { + HTTPClient *http.Client + Authorizers []authentication.Signer + TritonURL url.URL + MantaURL url.URL + AccountName string + Endpoint string +} + +// New is used to construct a Client in order to make API +// requests to the Triton API. +// +// At least one signer must be provided - example signers include +// authentication.PrivateKeySigner and authentication.SSHAgentSigner. +func New(tritonURL string, mantaURL string, accountName string, signers ...authentication.Signer) (*Client, error) { + cloudURL, err := url.Parse(tritonURL) + if err != nil { + return nil, errwrap.Wrapf("invalid endpoint URL: {{err}}", err) + } + + storageURL, err := url.Parse(mantaURL) + if err != nil { + return nil, errwrap.Wrapf("invalid manta URL: {{err}}", err) + } + + if accountName == "" { + return nil, errors.New("account name can not be empty") + } + + httpClient := &http.Client{ + Transport: httpTransport(false), + CheckRedirect: doNotFollowRedirects, + } + + newClient := &Client{ + HTTPClient: httpClient, + Authorizers: signers, + TritonURL: *cloudURL, + MantaURL: *storageURL, + AccountName: accountName, + // TODO(justinwr): Deprecated? + // Endpoint: tritonURL, + } + + var authorizers []authentication.Signer + for _, key := range signers { + if key != nil { + authorizers = append(authorizers, key) + } + } + + // Default to constructing an SSHAgentSigner if there are no other signers + // passed into NewClient and there's an SDC_KEY_ID value available in the + // user environ. + if len(authorizers) == 0 { + keyID := os.Getenv("SDC_KEY_ID") + if len(keyID) != 0 { + keySigner, err := authentication.NewSSHAgentSigner(keyID, accountName) + if err != nil { + return nil, errwrap.Wrapf("Problem initializing NewSSHAgentSigner: {{err}}", err) + } + newClient.Authorizers = append(authorizers, keySigner) + } else { + return nil, MissingKeyIdError + } + } + + return newClient, nil +} + +// InsecureSkipTLSVerify turns off TLS verification for the client connection. This +// allows connection to an endpoint with a certificate which was signed by a non- +// trusted CA, such as self-signed certificates. This can be useful when connecting +// to temporary Triton installations such as Triton Cloud-On-A-Laptop. +func (c *Client) InsecureSkipTLSVerify() { + if c.HTTPClient == nil { + return + } + + c.HTTPClient.Transport = httpTransport(true) +} + +func httpTransport(insecureSkipTLSVerify bool) *http.Transport { + return &http.Transport{ + Proxy: http.ProxyFromEnvironment, + Dial: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }).Dial, + TLSHandshakeTimeout: 10 * time.Second, + DisableKeepAlives: true, + MaxIdleConnsPerHost: -1, + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: insecureSkipTLSVerify, + }, + } +} + +func doNotFollowRedirects(*http.Request, []*http.Request) error { + return http.ErrUseLastResponse +} + +// TODO(justinwr): Deprecated? +// func (c *Client) FormatURL(path string) string { +// return fmt.Sprintf("%s%s", c.Endpoint, path) +// } + +func (c *Client) DecodeError(statusCode int, body io.Reader) error { + err := &TritonError{ + StatusCode: statusCode, + } + + errorDecoder := json.NewDecoder(body) + if err := errorDecoder.Decode(err); err != nil { + return errwrap.Wrapf("Error decoding error response: {{err}}", err) + } + + return err +} + +// ----------------------------------------------------------------------------- + +type RequestInput struct { + Method string + Path string + Query *url.Values + Headers *http.Header + Body interface{} +} + +func (c *Client) ExecuteRequestURIParams(ctx context.Context, inputs RequestInput) (io.ReadCloser, error) { + method := inputs.Method + path := inputs.Path + body := inputs.Body + query := inputs.Query + + var requestBody io.ReadSeeker + if body != nil { + marshaled, err := json.MarshalIndent(body, "", " ") + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(marshaled) + } + + endpoint := c.TritonURL + endpoint.Path = path + if query != nil { + endpoint.RawQuery = query.Encode() + } + + req, err := http.NewRequest(method, endpoint.String(), requestBody) + if err != nil { + return nil, errwrap.Wrapf("Error constructing HTTP request: {{err}}", err) + } + + dateHeader := time.Now().UTC().Format(time.RFC1123) + req.Header.Set("date", dateHeader) + + // NewClient ensures there's always an authorizer (unless this is called + // outside that constructor). + authHeader, err := c.Authorizers[0].Sign(dateHeader) + if err != nil { + return nil, errwrap.Wrapf("Error signing HTTP request: {{err}}", err) + } + req.Header.Set("Authorization", authHeader) + req.Header.Set("Accept", "application/json") + req.Header.Set("Accept-Version", "8") + req.Header.Set("User-Agent", "triton-go Client API") + + if body != nil { + req.Header.Set("Content-Type", "application/json") + } + + resp, err := c.HTTPClient.Do(req.WithContext(ctx)) + if err != nil { + return nil, errwrap.Wrapf("Error executing HTTP request: {{err}}", err) + } + + if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices { + return resp.Body, nil + } + + return nil, c.DecodeError(resp.StatusCode, resp.Body) +} + +func (c *Client) ExecuteRequest(ctx context.Context, inputs RequestInput) (io.ReadCloser, error) { + return c.ExecuteRequestURIParams(ctx, inputs) +} + +func (c *Client) ExecuteRequestRaw(ctx context.Context, inputs RequestInput) (*http.Response, error) { + method := inputs.Method + path := inputs.Path + body := inputs.Body + + var requestBody io.ReadSeeker + if body != nil { + marshaled, err := json.MarshalIndent(body, "", " ") + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(marshaled) + } + + endpoint := c.TritonURL + endpoint.Path = path + + req, err := http.NewRequest(method, endpoint.String(), requestBody) + if err != nil { + return nil, errwrap.Wrapf("Error constructing HTTP request: {{err}}", err) + } + + dateHeader := time.Now().UTC().Format(time.RFC1123) + req.Header.Set("date", dateHeader) + + // NewClient ensures there's always an authorizer (unless this is called + // outside that constructor). + authHeader, err := c.Authorizers[0].Sign(dateHeader) + if err != nil { + return nil, errwrap.Wrapf("Error signing HTTP request: {{err}}", err) + } + req.Header.Set("Authorization", authHeader) + req.Header.Set("Accept", "application/json") + req.Header.Set("Accept-Version", "8") + req.Header.Set("User-Agent", "triton-go c API") + + if body != nil { + req.Header.Set("Content-Type", "application/json") + } + + resp, err := c.HTTPClient.Do(req.WithContext(ctx)) + if err != nil { + return nil, errwrap.Wrapf("Error executing HTTP request: {{err}}", err) + } + + return resp, nil +} + +func (c *Client) ExecuteRequestStorage(ctx context.Context, inputs RequestInput) (io.ReadCloser, http.Header, error) { + method := inputs.Method + path := inputs.Path + query := inputs.Query + headers := inputs.Headers + body := inputs.Body + + endpoint := c.MantaURL + endpoint.Path = path + + var requestBody io.ReadSeeker + if body != nil { + marshaled, err := json.MarshalIndent(body, "", " ") + if err != nil { + return nil, nil, err + } + requestBody = bytes.NewReader(marshaled) + } + + req, err := http.NewRequest(method, endpoint.String(), requestBody) + if err != nil { + return nil, nil, errwrap.Wrapf("Error constructing HTTP request: {{err}}", err) + } + + if body != nil && (headers == nil || headers.Get("Content-Type") == "") { + req.Header.Set("Content-Type", "application/json") + } + if headers != nil { + for key, values := range *headers { + for _, value := range values { + req.Header.Set(key, value) + } + } + } + + dateHeader := time.Now().UTC().Format(time.RFC1123) + req.Header.Set("date", dateHeader) + + authHeader, err := c.Authorizers[0].Sign(dateHeader) + if err != nil { + return nil, nil, errwrap.Wrapf("Error signing HTTP request: {{err}}", err) + } + req.Header.Set("Authorization", authHeader) + req.Header.Set("Accept", "*/*") + req.Header.Set("User-Agent", "manta-go client API") + + if query != nil { + req.URL.RawQuery = query.Encode() + } + + resp, err := c.HTTPClient.Do(req.WithContext(ctx)) + if err != nil { + return nil, nil, errwrap.Wrapf("Error executing HTTP request: {{err}}", err) + } + + if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices { + return resp.Body, resp.Header, nil + } + + mantaError := &MantaError{ + StatusCode: resp.StatusCode, + } + + errorDecoder := json.NewDecoder(resp.Body) + if err := errorDecoder.Decode(mantaError); err != nil { + return nil, nil, errwrap.Wrapf("Error decoding error response: {{err}}", err) + } + return nil, nil, mantaError +} + +type RequestNoEncodeInput struct { + Method string + Path string + Query *url.Values + Headers *http.Header + Body io.ReadSeeker +} + +func (c *Client) ExecuteRequestNoEncode(ctx context.Context, inputs RequestNoEncodeInput) (io.ReadCloser, http.Header, error) { + method := inputs.Method + path := inputs.Path + query := inputs.Query + headers := inputs.Headers + body := inputs.Body + + endpoint := c.MantaURL + endpoint.Path = path + + req, err := http.NewRequest(method, endpoint.String(), body) + if err != nil { + return nil, nil, errwrap.Wrapf("Error constructing HTTP request: {{err}}", err) + } + + if headers != nil { + for key, values := range *headers { + for _, value := range values { + req.Header.Set(key, value) + } + } + } + + dateHeader := time.Now().UTC().Format(time.RFC1123) + req.Header.Set("date", dateHeader) + + authHeader, err := c.Authorizers[0].Sign(dateHeader) + if err != nil { + return nil, nil, errwrap.Wrapf("Error signing HTTP request: {{err}}", err) + } + req.Header.Set("Authorization", authHeader) + req.Header.Set("Accept", "*/*") + req.Header.Set("User-Agent", "manta-go client API") + + if query != nil { + req.URL.RawQuery = query.Encode() + } + + resp, err := c.HTTPClient.Do(req.WithContext(ctx)) + if err != nil { + return nil, nil, errwrap.Wrapf("Error executing HTTP request: {{err}}", err) + } + + if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices { + return resp.Body, resp.Header, nil + } + + mantaError := &MantaError{ + StatusCode: resp.StatusCode, + } + + errorDecoder := json.NewDecoder(resp.Body) + if err := errorDecoder.Decode(mantaError); err != nil { + return nil, nil, errwrap.Wrapf("Error decoding error response: {{err}}", err) + } + return nil, nil, mantaError +} diff --git a/vendor/github.com/joyent/triton-go/client/errors.go b/vendor/github.com/joyent/triton-go/client/errors.go new file mode 100644 index 000000000..1fc64a095 --- /dev/null +++ b/vendor/github.com/joyent/triton-go/client/errors.go @@ -0,0 +1,190 @@ +package client + +import ( + "fmt" + + "github.com/hashicorp/errwrap" +) + +// ClientError represents an error code and message along with the status code +// of the HTTP request which resulted in the error message. +type ClientError struct { + StatusCode int + Code string + Message string +} + +// Error implements interface Error on the TritonError type. +func (e ClientError) Error() string { + return fmt.Sprintf("%s: %s", e.Code, e.Message) +} + +// MantaError represents an error code and message along with +// the status code of the HTTP request which resulted in the error +// message. Error codes used by the Manta API are listed at +// https://apidocs.joyent.com/manta/api.html#errors +type MantaError struct { + StatusCode int + Code string `json:"code"` + Message string `json:"message"` +} + +// Error implements interface Error on the MantaError type. +func (e MantaError) Error() string { + return fmt.Sprintf("%s: %s", e.Code, e.Message) +} + +// TritonError represents an error code and message along with +// the status code of the HTTP request which resulted in the error +// message. Error codes used by the Triton API are listed at +// https://apidocs.joyent.com/cloudapi/#cloudapi-http-responses +type TritonError struct { + StatusCode int + Code string `json:"code"` + Message string `json:"message"` +} + +// Error implements interface Error on the TritonError type. +func (e TritonError) Error() string { + return fmt.Sprintf("%s: %s", e.Code, e.Message) +} + +func IsAuthSchemeError(err error) bool { + return isSpecificError(err, "AuthScheme") +} +func IsAuthorizationError(err error) bool { + return isSpecificError(err, "Authorization") +} +func IsBadRequestError(err error) bool { + return isSpecificError(err, "BadRequest") +} +func IsChecksumError(err error) bool { + return isSpecificError(err, "Checksum") +} +func IsConcurrentRequestError(err error) bool { + return isSpecificError(err, "ConcurrentRequest") +} +func IsContentLengthError(err error) bool { + return isSpecificError(err, "ContentLength") +} +func IsContentMD5MismatchError(err error) bool { + return isSpecificError(err, "ContentMD5Mismatch") +} +func IsEntityExistsError(err error) bool { + return isSpecificError(err, "EntityExists") +} +func IsInvalidArgumentError(err error) bool { + return isSpecificError(err, "InvalidArgument") +} +func IsInvalidAuthTokenError(err error) bool { + return isSpecificError(err, "InvalidAuthToken") +} +func IsInvalidCredentialsError(err error) bool { + return isSpecificError(err, "InvalidCredentials") +} +func IsInvalidDurabilityLevelError(err error) bool { + return isSpecificError(err, "InvalidDurabilityLevel") +} +func IsInvalidKeyIdError(err error) bool { + return isSpecificError(err, "InvalidKeyId") +} +func IsInvalidJobError(err error) bool { + return isSpecificError(err, "InvalidJob") +} +func IsInvalidLinkError(err error) bool { + return isSpecificError(err, "InvalidLink") +} +func IsInvalidLimitError(err error) bool { + return isSpecificError(err, "InvalidLimit") +} +func IsInvalidSignatureError(err error) bool { + return isSpecificError(err, "InvalidSignature") +} +func IsInvalidUpdateError(err error) bool { + return isSpecificError(err, "InvalidUpdate") +} +func IsDirectoryDoesNotExistError(err error) bool { + return isSpecificError(err, "DirectoryDoesNotExist") +} +func IsDirectoryExistsError(err error) bool { + return isSpecificError(err, "DirectoryExists") +} +func IsDirectoryNotEmptyError(err error) bool { + return isSpecificError(err, "DirectoryNotEmpty") +} +func IsDirectoryOperationError(err error) bool { + return isSpecificError(err, "DirectoryOperation") +} +func IsInternalError(err error) bool { + return isSpecificError(err, "Internal") +} +func IsJobNotFoundError(err error) bool { + return isSpecificError(err, "JobNotFound") +} +func IsJobStateError(err error) bool { + return isSpecificError(err, "JobState") +} +func IsKeyDoesNotExistError(err error) bool { + return isSpecificError(err, "KeyDoesNotExist") +} +func IsNotAcceptableError(err error) bool { + return isSpecificError(err, "NotAcceptable") +} +func IsNotEnoughSpaceError(err error) bool { + return isSpecificError(err, "NotEnoughSpace") +} +func IsLinkNotFoundError(err error) bool { + return isSpecificError(err, "LinkNotFound") +} +func IsLinkNotObjectError(err error) bool { + return isSpecificError(err, "LinkNotObject") +} +func IsLinkRequiredError(err error) bool { + return isSpecificError(err, "LinkRequired") +} +func IsParentNotDirectoryError(err error) bool { + return isSpecificError(err, "ParentNotDirectory") +} +func IsPreconditionFailedError(err error) bool { + return isSpecificError(err, "PreconditionFailed") +} +func IsPreSignedRequestError(err error) bool { + return isSpecificError(err, "PreSignedRequest") +} +func IsRequestEntityTooLargeError(err error) bool { + return isSpecificError(err, "RequestEntityTooLarge") +} +func IsResourceNotFoundError(err error) bool { + return isSpecificError(err, "ResourceNotFound") +} +func IsRootDirectoryError(err error) bool { + return isSpecificError(err, "RootDirectory") +} +func IsServiceUnavailableError(err error) bool { + return isSpecificError(err, "ServiceUnavailable") +} +func IsSSLRequiredError(err error) bool { + return isSpecificError(err, "SSLRequired") +} +func IsUploadTimeoutError(err error) bool { + return isSpecificError(err, "UploadTimeout") +} +func IsUserDoesNotExistError(err error) bool { + return isSpecificError(err, "UserDoesNotExist") +} + +// isSpecificError checks whether the error represented by err wraps +// an underlying MantaError with code errorCode. +func isSpecificError(err error, errorCode string) bool { + tritonErrorInterface := errwrap.GetType(err.(error), &MantaError{}) + if tritonErrorInterface == nil { + return false + } + + tritonErr := tritonErrorInterface.(*MantaError) + if tritonErr.Code == errorCode { + return true + } + + return false +} diff --git a/vendor/github.com/joyent/triton-go/compute/client.go b/vendor/github.com/joyent/triton-go/compute/client.go new file mode 100644 index 000000000..8ce726cb1 --- /dev/null +++ b/vendor/github.com/joyent/triton-go/compute/client.go @@ -0,0 +1,57 @@ +package compute + +import ( + triton "github.com/joyent/triton-go" + "github.com/joyent/triton-go/client" +) + +type ComputeClient struct { + Client *client.Client +} + +func newComputeClient(client *client.Client) *ComputeClient { + return &ComputeClient{ + Client: client, + } +} + +// NewClient returns a new client for working with Compute endpoints and +// resources within CloudAPI +func NewClient(config *triton.ClientConfig) (*ComputeClient, error) { + // TODO: Utilize config interface within the function itself + client, err := client.New(config.TritonURL, config.MantaURL, config.AccountName, config.Signers...) + if err != nil { + return nil, err + } + return newComputeClient(client), nil +} + +// Datacenters returns a Compute client used for accessing functions pertaining +// to DataCenter functionality in the Triton API. +func (c *ComputeClient) Datacenters() *DataCentersClient { + return &DataCentersClient{c.Client} +} + +// Images returns a Compute client used for accessing functions pertaining to +// Images functionality in the Triton API. +func (c *ComputeClient) Images() *ImagesClient { + return &ImagesClient{c.Client} +} + +// Machines returns a Compute client used for accessing functions pertaining to +// machine functionality in the Triton API. +func (c *ComputeClient) Instances() *InstancesClient { + return &InstancesClient{c.Client} +} + +// Packages returns a Compute client used for accessing functions pertaining to +// Packages functionality in the Triton API. +func (c *ComputeClient) Packages() *PackagesClient { + return &PackagesClient{c.Client} +} + +// Services returns a Compute client used for accessing functions pertaining to +// Services functionality in the Triton API. +func (c *ComputeClient) Services() *ServicesClient { + return &ServicesClient{c.Client} +} diff --git a/vendor/github.com/joyent/triton-go/compute/datacenters.go b/vendor/github.com/joyent/triton-go/compute/datacenters.go new file mode 100644 index 000000000..7acaf20a1 --- /dev/null +++ b/vendor/github.com/joyent/triton-go/compute/datacenters.go @@ -0,0 +1,97 @@ +package compute + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "sort" + + "context" + + "github.com/hashicorp/errwrap" + "github.com/joyent/triton-go/client" +) + +type DataCentersClient struct { + client *client.Client +} + +type DataCenter struct { + Name string `json:"name"` + URL string `json:"url"` +} + +type ListDataCentersInput struct{} + +func (c *DataCentersClient) List(ctx context.Context, _ *ListDataCentersInput) ([]*DataCenter, error) { + path := fmt.Sprintf("/%s/datacenters", c.client.AccountName) + reqInputs := client.RequestInput{ + Method: http.MethodGet, + Path: path, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing List request: {{err}}", err) + } + + var intermediate map[string]string + decoder := json.NewDecoder(respReader) + if err = decoder.Decode(&intermediate); err != nil { + return nil, errwrap.Wrapf("Error decoding List response: {{err}}", err) + } + + keys := make([]string, len(intermediate)) + i := 0 + for k := range intermediate { + keys[i] = k + i++ + } + sort.Strings(keys) + + result := make([]*DataCenter, len(intermediate)) + i = 0 + for _, key := range keys { + result[i] = &DataCenter{ + Name: key, + URL: intermediate[key], + } + i++ + } + + return result, nil +} + +type GetDataCenterInput struct { + Name string +} + +func (c *DataCentersClient) Get(ctx context.Context, input *GetDataCenterInput) (*DataCenter, error) { + path := fmt.Sprintf("/%s/datacenters/%s", c.client.AccountName, input.Name) + reqInputs := client.RequestInput{ + Method: http.MethodGet, + Path: path, + } + resp, err := c.client.ExecuteRequestRaw(ctx, reqInputs) + if err != nil { + return nil, errwrap.Wrapf("Error executing Get request: {{err}}", err) + } + + if resp.StatusCode != http.StatusFound { + return nil, fmt.Errorf("Error executing Get request: expected status code 302, got %s", + resp.StatusCode) + } + + location := resp.Header.Get("Location") + if location == "" { + return nil, errors.New("Error decoding Get response: no Location header") + } + + return &DataCenter{ + Name: input.Name, + URL: location, + }, nil +} diff --git a/vendor/github.com/joyent/triton-go/errors.go b/vendor/github.com/joyent/triton-go/compute/errors.go similarity index 54% rename from vendor/github.com/joyent/triton-go/errors.go rename to vendor/github.com/joyent/triton-go/compute/errors.go index 76d4a6254..ae2a4bf5c 100644 --- a/vendor/github.com/joyent/triton-go/errors.go +++ b/vendor/github.com/joyent/triton-go/compute/errors.go @@ -1,123 +1,112 @@ -package triton +package compute import ( - "fmt" - "github.com/hashicorp/errwrap" + "github.com/joyent/triton-go/client" ) -// TritonError represents an error code and message along with -// the status code of the HTTP request which resulted in the error -// message. Error codes used by the Triton API are listed at -// https://apidocs.joyent.com/cloudapi/#cloudapi-http-responses -type TritonError struct { - StatusCode int - Code string `json:"code"` - Message string `json:"message"` -} - -// Error implements interface Error on the TritonError type. -func (e TritonError) Error() string { - return fmt.Sprintf("%s: %s", e.Code, e.Message) -} - -// IsBadRequest tests whether err wraps a TritonError with +// IsBadRequest tests whether err wraps a client.TritonError with // code BadRequest func IsBadRequest(err error) bool { return isSpecificError(err, "BadRequest") } -// IsInternalError tests whether err wraps a TritonError with +// IsInternalError tests whether err wraps a client.TritonError with // code InternalError func IsInternalError(err error) bool { return isSpecificError(err, "InternalError") } -// IsInUseError tests whether err wraps a TritonError with +// IsInUseError tests whether err wraps a client.TritonError with // code InUseError func IsInUseError(err error) bool { return isSpecificError(err, "InUseError") } -// IsInvalidArgument tests whether err wraps a TritonError with +// IsInvalidArgument tests whether err wraps a client.TritonError with // code InvalidArgument func IsInvalidArgument(err error) bool { return isSpecificError(err, "InvalidArgument") } -// IsInvalidCredentials tests whether err wraps a TritonError with +// IsInvalidCredentials tests whether err wraps a client.TritonError with // code InvalidCredentials func IsInvalidCredentials(err error) bool { return isSpecificError(err, "InvalidCredentials") } -// IsInvalidHeader tests whether err wraps a TritonError with +// IsInvalidHeader tests whether err wraps a client.TritonError with // code InvalidHeader func IsInvalidHeader(err error) bool { return isSpecificError(err, "InvalidHeader") } -// IsInvalidVersion tests whether err wraps a TritonError with +// IsInvalidVersion tests whether err wraps a client.TritonError with // code InvalidVersion func IsInvalidVersion(err error) bool { return isSpecificError(err, "InvalidVersion") } -// IsMissingParameter tests whether err wraps a TritonError with +// IsMissingParameter tests whether err wraps a client.TritonError with // code MissingParameter func IsMissingParameter(err error) bool { return isSpecificError(err, "MissingParameter") } -// IsNotAuthorized tests whether err wraps a TritonError with +// IsNotAuthorized tests whether err wraps a client.TritonError with // code NotAuthorized func IsNotAuthorized(err error) bool { return isSpecificError(err, "NotAuthorized") } -// IsRequestThrottled tests whether err wraps a TritonError with +// IsRequestThrottled tests whether err wraps a client.TritonError with // code RequestThrottled func IsRequestThrottled(err error) bool { return isSpecificError(err, "RequestThrottled") } -// IsRequestTooLarge tests whether err wraps a TritonError with +// IsRequestTooLarge tests whether err wraps a client.TritonError with // code RequestTooLarge func IsRequestTooLarge(err error) bool { return isSpecificError(err, "RequestTooLarge") } -// IsRequestMoved tests whether err wraps a TritonError with +// IsRequestMoved tests whether err wraps a client.TritonError with // code RequestMoved func IsRequestMoved(err error) bool { return isSpecificError(err, "RequestMoved") } -// IsResourceNotFound tests whether err wraps a TritonError with +// IsResourceFound tests whether err wraps a client.TritonError with code ResourceFound +func IsResourceFound(err error) bool { + return isSpecificError(err, "ResourceFound") +} + +// IsResourceNotFound tests whether err wraps a client.TritonError with // code ResourceNotFound func IsResourceNotFound(err error) bool { return isSpecificError(err, "ResourceNotFound") } -// IsUnknownError tests whether err wraps a TritonError with +// IsUnknownError tests whether err wraps a client.TritonError with // code UnknownError func IsUnknownError(err error) bool { return isSpecificError(err, "UnknownError") } // isSpecificError checks whether the error represented by err wraps -// an underlying TritonError with code errorCode. +// an underlying client.TritonError with code errorCode. func isSpecificError(err error, errorCode string) bool { if err == nil { return false } - tritonErrorInterface := errwrap.GetType(err.(error), &TritonError{}) + tritonErrorInterface := errwrap.GetType(err.(error), &client.TritonError{}) if tritonErrorInterface == nil { return false } - tritonErr := tritonErrorInterface.(*TritonError) + tritonErr := tritonErrorInterface.(*client.TritonError) if tritonErr.Code == errorCode { return true } diff --git a/vendor/github.com/joyent/triton-go/images.go b/vendor/github.com/joyent/triton-go/compute/images.go similarity index 51% rename from vendor/github.com/joyent/triton-go/images.go rename to vendor/github.com/joyent/triton-go/compute/images.go index f6ab1fce9..b60f05e53 100644 --- a/vendor/github.com/joyent/triton-go/images.go +++ b/vendor/github.com/joyent/triton-go/compute/images.go @@ -1,4 +1,4 @@ -package triton +package compute import ( "context" @@ -9,16 +9,11 @@ import ( "time" "github.com/hashicorp/errwrap" + "github.com/joyent/triton-go/client" ) type ImagesClient struct { - *Client -} - -// Images returns a c used for accessing functions pertaining to -// Images functionality in the Triton API. -func (c *Client) Images() *ImagesClient { - return &ImagesClient{c} + client *client.Client } type ImageFile struct { @@ -44,25 +39,62 @@ type Image struct { Tags map[string]string `json:"tags"` EULA string `json:"eula"` ACL []string `json:"acl"` - Error TritonError `json:"error"` + Error client.TritonError `json:"error"` } -type ListImagesInput struct{} +type ListImagesInput struct { + Name string + OS string + Version string + Public bool + State string + Owner string + Type string +} -func (client *ImagesClient) ListImages(ctx context.Context, _ *ListImagesInput) ([]*Image, error) { - path := fmt.Sprintf("/%s/images", client.accountName) - respReader, err := client.executeRequest(ctx, http.MethodGet, path, nil) +func (c *ImagesClient) List(ctx context.Context, input *ListImagesInput) ([]*Image, error) { + path := fmt.Sprintf("/%s/images", c.client.AccountName) + + query := &url.Values{} + if input.Name != "" { + query.Set("name", input.Name) + } + if input.OS != "" { + query.Set("os", input.OS) + } + if input.Version != "" { + query.Set("version", input.Version) + } + if input.Public { + query.Set("public", "true") + } + if input.State != "" { + query.Set("state", input.State) + } + if input.Owner != "" { + query.Set("owner", input.Owner) + } + if input.Type != "" { + query.Set("type", input.Type) + } + + reqInputs := client.RequestInput{ + Method: http.MethodGet, + Path: path, + Query: query, + } + respReader, err := c.client.ExecuteRequestURIParams(ctx, reqInputs) if respReader != nil { defer respReader.Close() } if err != nil { - return nil, errwrap.Wrapf("Error executing ListImages request: {{err}}", err) + return nil, errwrap.Wrapf("Error executing List request: {{err}}", err) } var result []*Image decoder := json.NewDecoder(respReader) if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding ListImages response: {{err}}", err) + return nil, errwrap.Wrapf("Error decoding List response: {{err}}", err) } return result, nil @@ -72,20 +104,24 @@ type GetImageInput struct { ImageID string } -func (client *ImagesClient) GetImage(ctx context.Context, input *GetImageInput) (*Image, error) { - path := fmt.Sprintf("/%s/images/%s", client.accountName, input.ImageID) - respReader, err := client.executeRequest(ctx, http.MethodGet, path, nil) +func (c *ImagesClient) Get(ctx context.Context, input *GetImageInput) (*Image, error) { + path := fmt.Sprintf("/%s/images/%s", c.client.AccountName, input.ImageID) + reqInputs := client.RequestInput{ + Method: http.MethodGet, + Path: path, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) if respReader != nil { defer respReader.Close() } if err != nil { - return nil, errwrap.Wrapf("Error executing GetImage request: {{err}}", err) + return nil, errwrap.Wrapf("Error executing Get request: {{err}}", err) } var result *Image decoder := json.NewDecoder(respReader) if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding GetImage response: {{err}}", err) + return nil, errwrap.Wrapf("Error decoding Get response: {{err}}", err) } return result, nil @@ -95,14 +131,18 @@ type DeleteImageInput struct { ImageID string } -func (client *ImagesClient) DeleteImage(ctx context.Context, input *DeleteImageInput) error { - path := fmt.Sprintf("/%s/images/%s", client.accountName, input.ImageID) - respReader, err := client.executeRequest(ctx, http.MethodDelete, path, nil) +func (c *ImagesClient) Delete(ctx context.Context, input *DeleteImageInput) error { + path := fmt.Sprintf("/%s/images/%s", c.client.AccountName, input.ImageID) + reqInputs := client.RequestInput{ + Method: http.MethodDelete, + Path: path, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) if respReader != nil { defer respReader.Close() } if err != nil { - return errwrap.Wrapf("Error executing DeleteKey request: {{err}}", err) + return errwrap.Wrapf("Error executing Delete request: {{err}}", err) } return nil @@ -119,24 +159,29 @@ type MantaLocation struct { ManifestPath string `json:"manifest_path"` } -func (client *ImagesClient) ExportImage(ctx context.Context, input *ExportImageInput) (*MantaLocation, error) { - path := fmt.Sprintf("/%s/images/%s", client.accountName, input.ImageID) +func (c *ImagesClient) Export(ctx context.Context, input *ExportImageInput) (*MantaLocation, error) { + path := fmt.Sprintf("/%s/images/%s", c.client.AccountName, input.ImageID) query := &url.Values{} query.Set("action", "export") query.Set("manta_path", input.MantaPath) - respReader, err := client.executeRequestURIParams(ctx, http.MethodGet, path, nil, query) + reqInputs := client.RequestInput{ + Method: http.MethodGet, + Path: path, + Query: query, + } + respReader, err := c.client.ExecuteRequestURIParams(ctx, reqInputs) if respReader != nil { defer respReader.Close() } if err != nil { - return nil, errwrap.Wrapf("Error executing GetImage request: {{err}}", err) + return nil, errwrap.Wrapf("Error executing Get request: {{err}}", err) } var result *MantaLocation decoder := json.NewDecoder(respReader) if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding GetImage response: {{err}}", err) + return nil, errwrap.Wrapf("Error decoding Get response: {{err}}", err) } return result, nil @@ -153,20 +198,25 @@ type CreateImageFromMachineInput struct { Tags map[string]string `json:"tags,omitempty"` } -func (client *ImagesClient) CreateImageFromMachine(ctx context.Context, input *CreateImageFromMachineInput) (*Image, error) { - path := fmt.Sprintf("/%s/images", client.accountName) - respReader, err := client.executeRequest(ctx, http.MethodPost, path, input) +func (c *ImagesClient) CreateFromMachine(ctx context.Context, input *CreateImageFromMachineInput) (*Image, error) { + path := fmt.Sprintf("/%s/images", c.client.AccountName) + reqInputs := client.RequestInput{ + Method: http.MethodPost, + Path: path, + Body: input, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) if respReader != nil { defer respReader.Close() } if err != nil { - return nil, errwrap.Wrapf("Error executing CreateImageFromMachine request: {{err}}", err) + return nil, errwrap.Wrapf("Error executing CreateFromMachine request: {{err}}", err) } var result *Image decoder := json.NewDecoder(respReader) if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding CreateImageFromMachine response: {{err}}", err) + return nil, errwrap.Wrapf("Error decoding CreateFromMachine response: {{err}}", err) } return result, nil @@ -183,23 +233,29 @@ type UpdateImageInput struct { Tags map[string]string `json:"tags,omitempty"` } -func (client *ImagesClient) UpdateImage(ctx context.Context, input *UpdateImageInput) (*Image, error) { - path := fmt.Sprintf("/%s/images/%s", client.accountName, input.ImageID) +func (c *ImagesClient) Update(ctx context.Context, input *UpdateImageInput) (*Image, error) { + path := fmt.Sprintf("/%s/images/%s", c.client.AccountName, input.ImageID) query := &url.Values{} query.Set("action", "update") - respReader, err := client.executeRequestURIParams(ctx, http.MethodPost, path, input, query) + reqInputs := client.RequestInput{ + Method: http.MethodPost, + Path: path, + Query: query, + Body: input, + } + respReader, err := c.client.ExecuteRequestURIParams(ctx, reqInputs) if respReader != nil { defer respReader.Close() } if err != nil { - return nil, errwrap.Wrapf("Error executing UpdateImage request: {{err}}", err) + return nil, errwrap.Wrapf("Error executing Update request: {{err}}", err) } var result *Image decoder := json.NewDecoder(respReader) if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding UpdateImage response: {{err}}", err) + return nil, errwrap.Wrapf("Error decoding Update response: {{err}}", err) } return result, nil diff --git a/vendor/github.com/joyent/triton-go/compute/instances.go b/vendor/github.com/joyent/triton-go/compute/instances.go new file mode 100644 index 000000000..337e6a482 --- /dev/null +++ b/vendor/github.com/joyent/triton-go/compute/instances.go @@ -0,0 +1,1020 @@ +package compute + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "strings" + "time" + + "github.com/hashicorp/errwrap" + "github.com/joyent/triton-go/client" +) + +type InstancesClient struct { + client *client.Client +} + +const ( + CNSTagDisable = "triton.cns.disable" + CNSTagReversePTR = "triton.cns.reverse_ptr" + CNSTagServices = "triton.cns.services" +) + +// InstanceCNS is a container for the CNS-specific attributes. In the API these +// values are embedded within a Instance's Tags attribute, however they are +// exposed to the caller as their native types. +type InstanceCNS struct { + Disable bool + ReversePTR string + Services []string +} + +type Instance 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]interface{} `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 InstanceCNS +} + +// _Instance is a private facade over Instance that handles the necessary API +// overrides from VMAPI's machine endpoint(s). +type _Instance struct { + Instance + 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 GetInstanceInput struct { + ID string +} + +func (gmi *GetInstanceInput) Validate() error { + if gmi.ID == "" { + return fmt.Errorf("machine ID can not be empty") + } + + return nil +} + +func (c *InstancesClient) Get(ctx context.Context, input *GetInstanceInput) (*Instance, error) { + if err := input.Validate(); err != nil { + return nil, errwrap.Wrapf("unable to get machine: {{err}}", err) + } + + path := fmt.Sprintf("/%s/machines/%s", c.client.AccountName, input.ID) + reqInputs := client.RequestInput{ + Method: http.MethodGet, + Path: path, + } + response, err := c.client.ExecuteRequestRaw(ctx, reqInputs) + if response != nil { + defer response.Body.Close() + } + if response == nil || response.StatusCode == http.StatusNotFound || response.StatusCode == http.StatusGone { + return nil, &client.TritonError{ + StatusCode: response.StatusCode, + Code: "ResourceNotFound", + } + } + if err != nil { + return nil, errwrap.Wrapf("Error executing Get request: {{err}}", + c.client.DecodeError(response.StatusCode, response.Body)) + } + + var result *_Instance + decoder := json.NewDecoder(response.Body) + if err = decoder.Decode(&result); err != nil { + return nil, errwrap.Wrapf("Error decoding Get response: {{err}}", err) + } + + native, err := result.toNative() + if err != nil { + return nil, errwrap.Wrapf("unable to convert API response for instances to native type: {{err}}", err) + } + + return native, nil +} + +type ListInstancesInput struct { + Brand string + Alias string + Name string + Image string + State string + Memory uint16 + Limit uint16 + Offset uint16 + Tags []string // query by arbitrary tags prefixed with "tag." + Tombstone bool + Docker bool + Credentials bool +} + +func (c *InstancesClient) List(ctx context.Context, input *ListInstancesInput) ([]*Instance, error) { + path := fmt.Sprintf("/%s/machines", c.client.AccountName) + + query := &url.Values{} + if input.Brand != "" { + query.Set("brand", input.Brand) + } + if input.Name != "" { + query.Set("name", input.Name) + } + if input.Image != "" { + query.Set("image", input.Image) + } + if input.State != "" { + query.Set("state", input.State) + } + if input.Memory >= 1 && input.Memory <= 1000 { + query.Set("memory", fmt.Sprintf("%d", input.Memory)) + } + if input.Limit >= 1 { + query.Set("limit", fmt.Sprintf("%d", input.Limit)) + } + if input.Offset >= 0 { + query.Set("offset", fmt.Sprintf("%d", input.Offset)) + } + if input.Tombstone { + query.Set("tombstone", "true") + } + if input.Docker { + query.Set("docker", "true") + } + if input.Credentials { + query.Set("credentials", "true") + } + + reqInputs := client.RequestInput{ + Method: http.MethodGet, + Path: path, + Query: query, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) + if err != nil { + return nil, errwrap.Wrapf("Error executing List request: {{err}}", err) + } + + var results []*_Instance + decoder := json.NewDecoder(respReader) + if err = decoder.Decode(&results); err != nil { + return nil, errwrap.Wrapf("Error decoding List response: {{err}}", err) + } + + machines := make([]*Instance, 0, len(results)) + for _, machineAPI := range results { + native, err := machineAPI.toNative() + if err != nil { + return nil, errwrap.Wrapf("unable to convert API response for instances to native type: {{err}}", err) + } + machines = append(machines, native) + } + + return machines, nil +} + +type CreateInstanceInput struct { + Name string + Package string + Image string + Networks []string + Affinity []string + LocalityStrict bool + LocalityNear []string + LocalityFar []string + Metadata map[string]string + Tags map[string]string + FirewallEnabled bool + CNS InstanceCNS +} + +func (input *CreateInstanceInput) toAPI() (map[string]interface{}, error) { + 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 + } + + // validate that affinity and locality are not included together + hasAffinity := len(input.Affinity) > 0 + hasLocality := len(input.LocalityNear) > 0 || len(input.LocalityFar) > 0 + if hasAffinity && hasLocality { + return nil, fmt.Errorf("Cannot include both Affinity and Locality") + } + + // affinity takes precendence over locality regardless + if len(input.Affinity) > 0 { + result["affinity"] = input.Affinity + } else { + 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 + } + + // NOTE(justinwr): CNSTagServices needs to be a tag if available. No other + // CNS tags will be handled at this time. + input.CNS.toTags(result) + if val, found := result[CNSTagServices]; found { + result["tag."+CNSTagServices] = val + delete(result, CNSTagServices) + } + + for key, value := range input.Metadata { + result[fmt.Sprintf("metadata.%s", key)] = value + } + + return result, nil +} + +func (c *InstancesClient) Create(ctx context.Context, input *CreateInstanceInput) (*Instance, error) { + path := fmt.Sprintf("/%s/machines", c.client.AccountName) + body, err := input.toAPI() + if err != nil { + return nil, errwrap.Wrapf("Error preparing Create request: {{err}}", err) + } + + reqInputs := client.RequestInput{ + Method: http.MethodPost, + Path: path, + Body: body, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing Create request: {{err}}", err) + } + + var result *Instance + decoder := json.NewDecoder(respReader) + if err = decoder.Decode(&result); err != nil { + return nil, errwrap.Wrapf("Error decoding Create response: {{err}}", err) + } + + return result, nil +} + +type DeleteInstanceInput struct { + ID string +} + +func (c *InstancesClient) Delete(ctx context.Context, input *DeleteInstanceInput) error { + path := fmt.Sprintf("/%s/machines/%s", c.client.AccountName, input.ID) + reqInputs := client.RequestInput{ + Method: http.MethodDelete, + Path: path, + } + response, err := c.client.ExecuteRequestRaw(ctx, reqInputs) + if response == nil { + return fmt.Errorf("Delete request has empty response") + } + if response.Body != nil { + defer response.Body.Close() + } + if response.StatusCode == http.StatusNotFound || response.StatusCode == http.StatusGone { + return nil + } + if err != nil { + return errwrap.Wrapf("Error executing Delete request: {{err}}", + c.client.DecodeError(response.StatusCode, response.Body)) + } + + return nil +} + +type DeleteTagsInput struct { + ID string +} + +func (c *InstancesClient) DeleteTags(ctx context.Context, input *DeleteTagsInput) error { + path := fmt.Sprintf("/%s/machines/%s/tags", c.client.AccountName, input.ID) + reqInputs := client.RequestInput{ + Method: http.MethodDelete, + Path: path, + } + response, err := c.client.ExecuteRequestRaw(ctx, reqInputs) + if response == nil { + return fmt.Errorf("DeleteTags request has empty response") + } + if response.Body != nil { + defer response.Body.Close() + } + if response.StatusCode == http.StatusNotFound { + return nil + } + if err != nil { + return errwrap.Wrapf("Error executing DeleteTags request: {{err}}", + c.client.DecodeError(response.StatusCode, response.Body)) + } + + return nil +} + +type DeleteTagInput struct { + ID string + Key string +} + +func (c *InstancesClient) DeleteTag(ctx context.Context, input *DeleteTagInput) error { + path := fmt.Sprintf("/%s/machines/%s/tags/%s", c.client.AccountName, input.ID, input.Key) + reqInputs := client.RequestInput{ + Method: http.MethodDelete, + Path: path, + } + response, err := c.client.ExecuteRequestRaw(ctx, reqInputs) + if response == nil { + return fmt.Errorf("DeleteTag request has empty response") + } + if response.Body != nil { + defer response.Body.Close() + } + if response.StatusCode == http.StatusNotFound { + return nil + } + if err != nil { + return errwrap.Wrapf("Error executing DeleteTag request: {{err}}", + c.client.DecodeError(response.StatusCode, response.Body)) + } + + return nil +} + +type RenameInstanceInput struct { + ID string + Name string +} + +func (c *InstancesClient) Rename(ctx context.Context, input *RenameInstanceInput) error { + path := fmt.Sprintf("/%s/machines/%s", c.client.AccountName, input.ID) + + params := &url.Values{} + params.Set("action", "rename") + params.Set("name", input.Name) + + reqInputs := client.RequestInput{ + Method: http.MethodPost, + Path: path, + Query: params, + } + respReader, err := c.client.ExecuteRequestURIParams(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return errwrap.Wrapf("Error executing Rename request: {{err}}", err) + } + + return nil +} + +type ReplaceTagsInput struct { + ID string + Tags map[string]string + CNS InstanceCNS +} + +// toAPI is used to join Tags and CNS tags into the same JSON object before +// sending an API request to the API gateway. +func (input ReplaceTagsInput) toAPI() map[string]interface{} { + result := map[string]interface{}{} + for key, value := range input.Tags { + result[key] = value + } + input.CNS.toTags(result) + return result +} + +func (c *InstancesClient) ReplaceTags(ctx context.Context, input *ReplaceTagsInput) error { + path := fmt.Sprintf("/%s/machines/%s/tags", c.client.AccountName, input.ID) + reqInputs := client.RequestInput{ + Method: http.MethodPut, + Path: path, + Body: input.toAPI(), + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return errwrap.Wrapf("Error executing ReplaceTags request: {{err}}", err) + } + + return nil +} + +type AddTagsInput struct { + ID string + Tags map[string]string +} + +func (c *InstancesClient) AddTags(ctx context.Context, input *AddTagsInput) error { + path := fmt.Sprintf("/%s/machines/%s/tags", c.client.AccountName, input.ID) + reqInputs := client.RequestInput{ + Method: http.MethodPost, + Path: path, + Body: input.Tags, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return errwrap.Wrapf("Error executing AddTags request: {{err}}", err) + } + + return nil +} + +type GetTagInput struct { + ID string + Key string +} + +func (c *InstancesClient) GetTag(ctx context.Context, input *GetTagInput) (string, error) { + path := fmt.Sprintf("/%s/machines/%s/tags/%s", c.client.AccountName, input.ID, input.Key) + reqInputs := client.RequestInput{ + Method: http.MethodGet, + Path: path, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return "", errwrap.Wrapf("Error executing GetTag request: {{err}}", err) + } + + var result string + decoder := json.NewDecoder(respReader) + if err = decoder.Decode(&result); err != nil { + return "", errwrap.Wrapf("Error decoding GetTag response: {{err}}", err) + } + + return result, nil +} + +type ListTagsInput struct { + ID string +} + +func (c *InstancesClient) ListTags(ctx context.Context, input *ListTagsInput) (map[string]interface{}, error) { + path := fmt.Sprintf("/%s/machines/%s/tags", c.client.AccountName, input.ID) + reqInputs := client.RequestInput{ + Method: http.MethodGet, + Path: path, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing ListTags 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 ListTags response: {{err}}", err) + } + + _, tags := tagsExtractMeta(result) + return tags, nil +} + +type GetMetadataInput struct { + ID string + Key string +} + +// GetMetadata returns a single metadata entry associated with an instance. +func (c *InstancesClient) GetMetadata(ctx context.Context, input *GetMetadataInput) (string, error) { + if input.Key == "" { + return "", fmt.Errorf("Missing metadata Key from input: %s", input.Key) + } + + path := fmt.Sprintf("/%s/machines/%s/metadata/%s", c.client.AccountName, input.ID, input.Key) + reqInputs := client.RequestInput{ + Method: http.MethodGet, + Path: path, + } + response, err := c.client.ExecuteRequestRaw(ctx, reqInputs) + if response != nil { + defer response.Body.Close() + } + if response.StatusCode == http.StatusNotFound || response.StatusCode == http.StatusGone { + return "", &client.TritonError{ + StatusCode: response.StatusCode, + Code: "ResourceNotFound", + } + } + if err != nil { + return "", errwrap.Wrapf("Error executing Get request: {{err}}", + c.client.DecodeError(response.StatusCode, response.Body)) + } + + body, err := ioutil.ReadAll(response.Body) + if err != nil { + return "", errwrap.Wrapf("Error unwrapping request body: {{err}}", + c.client.DecodeError(response.StatusCode, response.Body)) + } + + return fmt.Sprintf("%s", body), nil +} + +type ListMetadataInput struct { + ID string + Credentials bool +} + +func (c *InstancesClient) ListMetadata(ctx context.Context, input *ListMetadataInput) (map[string]string, error) { + path := fmt.Sprintf("/%s/machines/%s/metadata", c.client.AccountName, input.ID) + + query := &url.Values{} + if input.Credentials { + query.Set("credentials", "true") + } + + reqInputs := client.RequestInput{ + Method: http.MethodGet, + Path: path, + Query: query, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing ListMetadata 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 ListMetadata response: {{err}}", err) + } + + return result, nil +} + +type UpdateMetadataInput struct { + ID string + Metadata map[string]string +} + +func (c *InstancesClient) UpdateMetadata(ctx context.Context, input *UpdateMetadataInput) (map[string]string, error) { + path := fmt.Sprintf("/%s/machines/%s/metadata", c.client.AccountName, input.ID) + reqInputs := client.RequestInput{ + Method: http.MethodPost, + Path: path, + Body: input.Metadata, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing UpdateMetadata 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 UpdateMetadata response: {{err}}", err) + } + + return result, nil +} + +type DeleteMetadataInput struct { + ID string + Key string +} + +// DeleteMetadata deletes a single metadata key from an instance +func (c *InstancesClient) DeleteMetadata(ctx context.Context, input *DeleteMetadataInput) error { + if input.Key == "" { + return fmt.Errorf("Missing metadata Key from input: %s", input.Key) + } + + path := fmt.Sprintf("/%s/machines/%s/metadata/%s", c.client.AccountName, input.ID, input.Key) + reqInputs := client.RequestInput{ + Method: http.MethodDelete, + Path: path, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return errwrap.Wrapf("Error executing DeleteMetadata request: {{err}}", err) + } + + return nil +} + +type DeleteAllMetadataInput struct { + ID string +} + +// DeleteAllMetadata deletes all metadata keys from this instance +func (c *InstancesClient) DeleteAllMetadata(ctx context.Context, input *DeleteAllMetadataInput) error { + path := fmt.Sprintf("/%s/machines/%s/metadata", c.client.AccountName, input.ID) + reqInputs := client.RequestInput{ + Method: http.MethodDelete, + Path: path, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return errwrap.Wrapf("Error executing DeleteAllMetadata request: {{err}}", err) + } + + return nil +} + +type ResizeInstanceInput struct { + ID string + Package string +} + +func (c *InstancesClient) Resize(ctx context.Context, input *ResizeInstanceInput) error { + path := fmt.Sprintf("/%s/machines/%s", c.client.AccountName, input.ID) + + params := &url.Values{} + params.Set("action", "resize") + params.Set("package", input.Package) + + reqInputs := client.RequestInput{ + Method: http.MethodPost, + Path: path, + Query: params, + } + respReader, err := c.client.ExecuteRequestURIParams(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return errwrap.Wrapf("Error executing Resize request: {{err}}", err) + } + + return nil +} + +type EnableFirewallInput struct { + ID string +} + +func (c *InstancesClient) EnableFirewall(ctx context.Context, input *EnableFirewallInput) error { + path := fmt.Sprintf("/%s/machines/%s", c.client.AccountName, input.ID) + + params := &url.Values{} + params.Set("action", "enable_firewall") + + reqInputs := client.RequestInput{ + Method: http.MethodPost, + Path: path, + Query: params, + } + respReader, err := c.client.ExecuteRequestURIParams(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return errwrap.Wrapf("Error executing EnableFirewall request: {{err}}", err) + } + + return nil +} + +type DisableFirewallInput struct { + ID string +} + +func (c *InstancesClient) DisableFirewall(ctx context.Context, input *DisableFirewallInput) error { + path := fmt.Sprintf("/%s/machines/%s", c.client.AccountName, input.ID) + + params := &url.Values{} + params.Set("action", "disable_firewall") + + reqInputs := client.RequestInput{ + Method: http.MethodPost, + Path: path, + Query: params, + } + respReader, err := c.client.ExecuteRequestURIParams(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return errwrap.Wrapf("Error executing DisableFirewall request: {{err}}", err) + } + + return nil +} + +type ListNICsInput struct { + InstanceID string +} + +func (c *InstancesClient) ListNICs(ctx context.Context, input *ListNICsInput) ([]*NIC, error) { + path := fmt.Sprintf("/%s/machines/%s/nics", c.client.AccountName, input.InstanceID) + reqInputs := client.RequestInput{ + Method: http.MethodGet, + Path: path, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) + 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 GetNICInput struct { + InstanceID string + MAC string +} + +func (c *InstancesClient) GetNIC(ctx context.Context, input *GetNICInput) (*NIC, error) { + mac := strings.Replace(input.MAC, ":", "", -1) + path := fmt.Sprintf("/%s/machines/%s/nics/%s", c.client.AccountName, input.InstanceID, mac) + reqInputs := client.RequestInput{ + Method: http.MethodGet, + Path: path, + } + response, err := c.client.ExecuteRequestRaw(ctx, reqInputs) + if response != nil { + defer response.Body.Close() + } + switch response.StatusCode { + case http.StatusNotFound: + return nil, &client.TritonError{ + StatusCode: response.StatusCode, + Code: "ResourceNotFound", + } + } + if err != nil { + return nil, errwrap.Wrapf("Error executing GetNIC request: {{err}}", err) + } + + var result *NIC + decoder := json.NewDecoder(response.Body) + if err = decoder.Decode(&result); err != nil { + return nil, errwrap.Wrapf("Error decoding ListNICs response: {{err}}", err) + } + + return result, nil +} + +type AddNICInput struct { + InstanceID string `json:"-"` + Network string `json:"network"` +} + +// AddNIC asynchronously adds a NIC to a given instance. If a NIC for a given +// network already exists, a ResourceFound error will be returned. The status +// of the addition of a NIC can be polled by calling GetNIC()'s and testing NIC +// until its state is set to "running". Only one NIC per network may exist. +// Warning: this operation causes the instance to restart. +func (c *InstancesClient) AddNIC(ctx context.Context, input *AddNICInput) (*NIC, error) { + path := fmt.Sprintf("/%s/machines/%s/nics", c.client.AccountName, input.InstanceID) + reqInputs := client.RequestInput{ + Method: http.MethodPost, + Path: path, + Body: input, + } + response, err := c.client.ExecuteRequestRaw(ctx, reqInputs) + if response != nil { + defer response.Body.Close() + } + switch response.StatusCode { + case http.StatusFound: + return nil, &client.TritonError{ + StatusCode: response.StatusCode, + Code: "ResourceFound", + Message: response.Header.Get("Location"), + } + } + if err != nil { + return nil, errwrap.Wrapf("Error executing AddNIC request: {{err}}", err) + } + + var result *NIC + decoder := json.NewDecoder(response.Body) + if err = decoder.Decode(&result); err != nil { + return nil, errwrap.Wrapf("Error decoding AddNIC response: {{err}}", err) + } + + return result, nil +} + +type RemoveNICInput struct { + InstanceID string + MAC string +} + +// RemoveNIC removes a given NIC from a machine asynchronously. The status of +// the removal can be polled via GetNIC(). When GetNIC() returns a 404, the NIC +// has been removed from the instance. Warning: this operation causes the +// machine to restart. +func (c *InstancesClient) RemoveNIC(ctx context.Context, input *RemoveNICInput) error { + mac := strings.Replace(input.MAC, ":", "", -1) + path := fmt.Sprintf("/%s/machines/%s/nics/%s", c.client.AccountName, input.InstanceID, mac) + reqInputs := client.RequestInput{ + Method: http.MethodDelete, + Path: path, + } + response, err := c.client.ExecuteRequestRaw(ctx, reqInputs) + if response != nil { + defer response.Body.Close() + } + switch response.StatusCode { + case http.StatusNotFound: + return &client.TritonError{ + StatusCode: response.StatusCode, + Code: "ResourceNotFound", + } + } + if err != nil { + return errwrap.Wrapf("Error executing RemoveNIC request: {{err}}", err) + } + + return nil +} + +type StopInstanceInput struct { + InstanceID string +} + +func (c *InstancesClient) Stop(ctx context.Context, input *StopInstanceInput) error { + path := fmt.Sprintf("/%s/machines/%s", c.client.AccountName, input.InstanceID) + + params := &url.Values{} + params.Set("action", "stop") + + reqInputs := client.RequestInput{ + Method: http.MethodPost, + Path: path, + Query: params, + } + respReader, err := c.client.ExecuteRequestURIParams(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return errwrap.Wrapf("Error executing Stop request: {{err}}", err) + } + + return nil +} + +type StartInstanceInput struct { + InstanceID string +} + +func (c *InstancesClient) Start(ctx context.Context, input *StartInstanceInput) error { + path := fmt.Sprintf("/%s/machines/%s", c.client.AccountName, input.InstanceID) + + params := &url.Values{} + params.Set("action", "start") + + reqInputs := client.RequestInput{ + Method: http.MethodPost, + Path: path, + Query: params, + } + respReader, err := c.client.ExecuteRequestURIParams(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return errwrap.Wrapf("Error executing Start request: {{err}}", err) + } + + return nil +} + +var reservedInstanceCNSTags = map[string]struct{}{ + CNSTagDisable: {}, + CNSTagReversePTR: {}, + CNSTagServices: {}, +} + +// tagsExtractMeta() extracts all of the misc parameters from Tags and returns a +// clean CNS and Tags struct. +func tagsExtractMeta(tags map[string]interface{}) (InstanceCNS, map[string]interface{}) { + nativeCNS := InstanceCNS{} + nativeTags := make(map[string]interface{}, len(tags)) + for k, raw := range tags { + if _, found := reservedInstanceCNSTags[k]; found { + switch k { + case CNSTagDisable: + b := raw.(bool) + nativeCNS.Disable = b + case CNSTagReversePTR: + s := raw.(string) + nativeCNS.ReversePTR = s + case CNSTagServices: + nativeCNS.Services = strings.Split(raw.(string), ",") + default: + // TODO(seanc@): should assert, logic fail + } + } else { + nativeTags[k] = raw + } + } + + return nativeCNS, nativeTags +} + +// toNative() exports a given _Instance (API representation) to its native object +// format. +func (api *_Instance) toNative() (*Instance, error) { + m := Instance(api.Instance) + m.CNS, m.Tags = tagsExtractMeta(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 (cns *InstanceCNS) toTags(m map[string]interface{}) { + if cns.Disable { + // NOTE(justinwr): The JSON encoder and API require the CNSTagDisable + // attribute to be an actual boolean, not a bool string. + m[CNSTagDisable] = cns.Disable + } + if cns.ReversePTR != "" { + m[CNSTagReversePTR] = cns.ReversePTR + } + if len(cns.Services) > 0 { + m[CNSTagServices] = strings.Join(cns.Services, ",") + } +} diff --git a/vendor/github.com/joyent/triton-go/packages.go b/vendor/github.com/joyent/triton-go/compute/packages.go similarity index 55% rename from vendor/github.com/joyent/triton-go/packages.go rename to vendor/github.com/joyent/triton-go/compute/packages.go index e8a4adbbe..f18407e45 100644 --- a/vendor/github.com/joyent/triton-go/packages.go +++ b/vendor/github.com/joyent/triton-go/compute/packages.go @@ -1,4 +1,4 @@ -package triton +package compute import ( "context" @@ -7,16 +7,11 @@ import ( "net/http" "github.com/hashicorp/errwrap" + "github.com/joyent/triton-go/client" ) type PackagesClient struct { - *Client -} - -// Packages returns a c used for accessing functions pertaining -// to Packages functionality in the Triton API. -func (c *Client) Packages() *PackagesClient { - return &PackagesClient{c} + client *client.Client } type Package struct { @@ -44,20 +39,25 @@ type ListPackagesInput struct { Group string `json:"group"` } -func (client *PackagesClient) ListPackages(ctx context.Context, input *ListPackagesInput) ([]*Package, error) { - path := fmt.Sprintf("/%s/packages", client.accountName) - respReader, err := client.executeRequest(ctx, http.MethodGet, path, input) +func (c *PackagesClient) List(ctx context.Context, input *ListPackagesInput) ([]*Package, error) { + path := fmt.Sprintf("/%s/packages", c.client.AccountName) + reqInputs := client.RequestInput{ + Method: http.MethodGet, + Path: path, + Body: input, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) if respReader != nil { defer respReader.Close() } if err != nil { - return nil, errwrap.Wrapf("Error executing ListPackages request: {{err}}", err) + return nil, errwrap.Wrapf("Error executing List request: {{err}}", err) } var result []*Package decoder := json.NewDecoder(respReader) if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding ListPackages response: {{err}}", err) + return nil, errwrap.Wrapf("Error decoding List response: {{err}}", err) } return result, nil @@ -67,20 +67,24 @@ type GetPackageInput struct { ID string } -func (client *PackagesClient) GetPackage(ctx context.Context, input *GetPackageInput) (*Package, error) { - path := fmt.Sprintf("/%s/packages/%s", client.accountName, input.ID) - respReader, err := client.executeRequest(ctx, http.MethodGet, path, nil) +func (c *PackagesClient) Get(ctx context.Context, input *GetPackageInput) (*Package, error) { + path := fmt.Sprintf("/%s/packages/%s", c.client.AccountName, input.ID) + reqInputs := client.RequestInput{ + Method: http.MethodGet, + Path: path, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) if respReader != nil { defer respReader.Close() } if err != nil { - return nil, errwrap.Wrapf("Error executing GetPackage request: {{err}}", err) + return nil, errwrap.Wrapf("Error executing Get request: {{err}}", err) } var result *Package decoder := json.NewDecoder(respReader) if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding GetPackage response: {{err}}", err) + return nil, errwrap.Wrapf("Error decoding Get response: {{err}}", err) } return result, nil diff --git a/vendor/github.com/joyent/triton-go/services.go b/vendor/github.com/joyent/triton-go/compute/services.go similarity index 55% rename from vendor/github.com/joyent/triton-go/services.go rename to vendor/github.com/joyent/triton-go/compute/services.go index e220b2699..af1b017e0 100644 --- a/vendor/github.com/joyent/triton-go/services.go +++ b/vendor/github.com/joyent/triton-go/compute/services.go @@ -1,4 +1,4 @@ -package triton +package compute import ( "context" @@ -8,16 +8,11 @@ import ( "sort" "github.com/hashicorp/errwrap" + "github.com/joyent/triton-go/client" ) type ServicesClient struct { - *Client -} - -// Services returns a c used for accessing functions pertaining -// to Services functionality in the Triton API. -func (c *Client) Services() *ServicesClient { - return &ServicesClient{c} + client *client.Client } type Service struct { @@ -27,20 +22,24 @@ type Service struct { type ListServicesInput struct{} -func (client *ServicesClient) ListServices(ctx context.Context, _ *ListServicesInput) ([]*Service, error) { - path := fmt.Sprintf("/%s/services", client.accountName) - respReader, err := client.executeRequest(ctx, http.MethodGet, path, nil) +func (c *ServicesClient) List(ctx context.Context, _ *ListServicesInput) ([]*Service, error) { + path := fmt.Sprintf("/%s/services", c.client.AccountName) + reqInputs := client.RequestInput{ + Method: http.MethodGet, + Path: path, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) if respReader != nil { defer respReader.Close() } if err != nil { - return nil, errwrap.Wrapf("Error executing ListServices request: {{err}}", err) + return nil, errwrap.Wrapf("Error executing List request: {{err}}", err) } var intermediate map[string]string decoder := json.NewDecoder(respReader) if err = decoder.Decode(&intermediate); err != nil { - return nil, errwrap.Wrapf("Error decoding ListServices response: {{err}}", err) + return nil, errwrap.Wrapf("Error decoding List response: {{err}}", err) } keys := make([]string, len(intermediate)) diff --git a/vendor/github.com/joyent/triton-go/config.go b/vendor/github.com/joyent/triton-go/config.go deleted file mode 100644 index b4f20e0a8..000000000 --- a/vendor/github.com/joyent/triton-go/config.go +++ /dev/null @@ -1,73 +0,0 @@ -package triton - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - - "github.com/hashicorp/errwrap" -) - -type ConfigClient struct { - *Client -} - -// Config returns a c used for accessing functions pertaining -// to Config functionality in the Triton API. -func (c *Client) Config() *ConfigClient { - return &ConfigClient{c} -} - -// Config represents configuration for your account. -type Config struct { - // DefaultNetwork is the network that docker containers are provisioned on. - DefaultNetwork string `json:"default_network"` -} - -type GetConfigInput struct{} - -// GetConfig outputs configuration for your account. -func (client *ConfigClient) GetConfig(ctx context.Context, input *GetConfigInput) (*Config, error) { - path := fmt.Sprintf("/%s/config", client.accountName) - respReader, err := client.executeRequest(ctx, http.MethodGet, path, nil) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return nil, errwrap.Wrapf("Error executing GetConfig request: {{err}}", err) - } - - var result *Config - decoder := json.NewDecoder(respReader) - if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding GetConfig response: {{err}}", err) - } - - return result, nil -} - -type UpdateConfigInput struct { - // DefaultNetwork is the network that docker containers are provisioned on. - DefaultNetwork string `json:"default_network"` -} - -// UpdateConfig updates configuration values for your account. -func (client *ConfigClient) UpdateConfig(ctx context.Context, input *UpdateConfigInput) (*Config, error) { - path := fmt.Sprintf("/%s/config", client.accountName) - respReader, err := client.executeRequest(ctx, http.MethodPut, path, input) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return nil, errwrap.Wrapf("Error executing UpdateConfig request: {{err}}", err) - } - - var result *Config - decoder := json.NewDecoder(respReader) - if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding UpdateConfig response: {{err}}", err) - } - - return result, nil -} diff --git a/vendor/github.com/joyent/triton-go/datacenters.go b/vendor/github.com/joyent/triton-go/datacenters.go deleted file mode 100644 index 2834f77c2..000000000 --- a/vendor/github.com/joyent/triton-go/datacenters.go +++ /dev/null @@ -1,93 +0,0 @@ -package triton - -import ( - "encoding/json" - "errors" - "fmt" - "net/http" - "sort" - - "context" - "github.com/hashicorp/errwrap" -) - -type DataCentersClient struct { - *Client -} - -// DataCenters returns a c used for accessing functions pertaining -// to Datacenter functionality in the Triton API. -func (c *Client) Datacenters() *DataCentersClient { - return &DataCentersClient{c} -} - -type DataCenter struct { - Name string `json:"name"` - URL string `json:"url"` -} - -type ListDataCentersInput struct{} - -func (client *DataCentersClient) ListDataCenters(ctx context.Context, _ *ListDataCentersInput) ([]*DataCenter, error) { - path := fmt.Sprintf("/%s/datacenters", client.accountName) - respReader, err := client.executeRequest(ctx, http.MethodGet, path, nil) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return nil, errwrap.Wrapf("Error executing ListDatacenters request: {{err}}", err) - } - - var intermediate map[string]string - decoder := json.NewDecoder(respReader) - if err = decoder.Decode(&intermediate); err != nil { - return nil, errwrap.Wrapf("Error decoding ListDatacenters response: {{err}}", err) - } - - keys := make([]string, len(intermediate)) - i := 0 - for k := range intermediate { - keys[i] = k - i++ - } - sort.Strings(keys) - - result := make([]*DataCenter, len(intermediate)) - i = 0 - for _, key := range keys { - result[i] = &DataCenter{ - Name: key, - URL: intermediate[key], - } - i++ - } - - return result, nil -} - -type GetDataCenterInput struct { - Name string -} - -func (client *DataCentersClient) GetDataCenter(ctx context.Context, input *GetDataCenterInput) (*DataCenter, error) { - path := fmt.Sprintf("/%s/datacenters/%s", client.accountName, input.Name) - resp, err := client.executeRequestRaw(ctx, http.MethodGet, path, nil) - if err != nil { - return nil, errwrap.Wrapf("Error executing GetDatacenter request: {{err}}", err) - } - - if resp.StatusCode != http.StatusFound { - return nil, fmt.Errorf("Error executing GetDatacenter request: expected status code 302, got %s", - resp.StatusCode) - } - - location := resp.Header.Get("Location") - if location == "" { - return nil, errors.New("Error decoding GetDatacenter response: no Location header") - } - - return &DataCenter{ - Name: input.Name, - URL: location, - }, nil -} diff --git a/vendor/github.com/joyent/triton-go/fabrics.go b/vendor/github.com/joyent/triton-go/fabrics.go deleted file mode 100644 index 1d32b83ba..000000000 --- a/vendor/github.com/joyent/triton-go/fabrics.go +++ /dev/null @@ -1,234 +0,0 @@ -package triton - -import ( - "encoding/json" - "fmt" - "net/http" - - "context" - "github.com/hashicorp/errwrap" -) - -type FabricsClient struct { - *Client -} - -// Fabrics returns a client used for accessing functions pertaining to -// Fabric functionality in the Triton API. -func (c *Client) Fabrics() *FabricsClient { - return &FabricsClient{c} -} - -type FabricVLAN struct { - Name string `json:"name"` - ID int `json:"vlan_id"` - Description string `json:"description"` -} - -type ListFabricVLANsInput struct{} - -func (client *FabricsClient) ListFabricVLANs(ctx context.Context, _ *ListFabricVLANsInput) ([]*FabricVLAN, error) { - path := fmt.Sprintf("/%s/fabrics/default/vlans", client.accountName) - respReader, err := client.executeRequest(ctx, http.MethodGet, path, nil) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return nil, errwrap.Wrapf("Error executing ListFabricVLANs request: {{err}}", err) - } - - var result []*FabricVLAN - decoder := json.NewDecoder(respReader) - if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding ListFabricVLANs response: {{err}}", err) - } - - return result, nil -} - -type CreateFabricVLANInput struct { - Name string `json:"name"` - ID int `json:"vlan_id"` - Description string `json:"description"` -} - -func (client *FabricsClient) CreateFabricVLAN(ctx context.Context, input *CreateFabricVLANInput) (*FabricVLAN, error) { - path := fmt.Sprintf("/%s/fabrics/default/vlans", client.accountName) - respReader, err := client.executeRequest(ctx, http.MethodPost, path, input) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return nil, errwrap.Wrapf("Error executing CreateFabricVLAN request: {{err}}", err) - } - - var result *FabricVLAN - decoder := json.NewDecoder(respReader) - if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding CreateFabricVLAN response: {{err}}", err) - } - - return result, nil -} - -type UpdateFabricVLANInput struct { - ID int `json:"-"` - Name string `json:"name"` - Description string `json:"description"` -} - -func (client *FabricsClient) UpdateFabricVLAN(ctx context.Context, input *UpdateFabricVLANInput) (*FabricVLAN, error) { - path := fmt.Sprintf("/%s/fabrics/default/vlans/%d", client.accountName, input.ID) - respReader, err := client.executeRequest(ctx, http.MethodPut, path, input) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return nil, errwrap.Wrapf("Error executing UpdateFabricVLAN request: {{err}}", err) - } - - var result *FabricVLAN - decoder := json.NewDecoder(respReader) - if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding UpdateFabricVLAN response: {{err}}", err) - } - - return result, nil -} - -type GetFabricVLANInput struct { - ID int `json:"-"` -} - -func (client *FabricsClient) GetFabricVLAN(ctx context.Context, input *GetFabricVLANInput) (*FabricVLAN, error) { - path := fmt.Sprintf("/%s/fabrics/default/vlans/%d", client.accountName, input.ID) - respReader, err := client.executeRequest(ctx, http.MethodGet, path, nil) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return nil, errwrap.Wrapf("Error executing GetFabricVLAN request: {{err}}", err) - } - - var result *FabricVLAN - decoder := json.NewDecoder(respReader) - if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding GetFabricVLAN response: {{err}}", err) - } - - return result, nil -} - -type DeleteFabricVLANInput struct { - ID int `json:"-"` -} - -func (client *FabricsClient) DeleteFabricVLAN(ctx context.Context, input *DeleteFabricVLANInput) error { - path := fmt.Sprintf("/%s/fabrics/default/vlans/%d", client.accountName, input.ID) - respReader, err := client.executeRequest(ctx, http.MethodDelete, path, nil) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return errwrap.Wrapf("Error executing DeleteFabricVLAN request: {{err}}", err) - } - - return nil -} - -type ListFabricNetworksInput struct { - FabricVLANID int `json:"-"` -} - -func (client *FabricsClient) ListFabricNetworks(ctx context.Context, input *ListFabricNetworksInput) ([]*Network, error) { - path := fmt.Sprintf("/%s/fabrics/default/vlans/%d/networks", client.accountName, input.FabricVLANID) - respReader, err := client.executeRequest(ctx, http.MethodGet, path, nil) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return nil, errwrap.Wrapf("Error executing ListFabricNetworks request: {{err}}", err) - } - - var result []*Network - decoder := json.NewDecoder(respReader) - if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding ListFabricNetworks response: {{err}}", err) - } - - return result, nil -} - -type CreateFabricNetworkInput struct { - FabricVLANID int `json:"-"` - Name string `json:"name"` - Description string `json:"description"` - Subnet string `json:"subnet"` - ProvisionStartIP string `json:"provision_start_ip"` - ProvisionEndIP string `json:"provision_end_ip"` - Gateway string `json:"gateway"` - Resolvers []string `json:"resolvers"` - Routes map[string]string `json:"routes"` - InternetNAT bool `json:"internet_nat"` -} - -func (client *FabricsClient) CreateFabricNetwork(ctx context.Context, input *CreateFabricNetworkInput) (*Network, error) { - path := fmt.Sprintf("/%s/fabrics/default/vlans/%d/networks", client.accountName, input.FabricVLANID) - respReader, err := client.executeRequest(ctx, http.MethodPost, path, input) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return nil, errwrap.Wrapf("Error executing CreateFabricNetwork request: {{err}}", err) - } - - var result *Network - decoder := json.NewDecoder(respReader) - if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding CreateFabricNetwork response: {{err}}", err) - } - - return result, nil -} - -type GetFabricNetworkInput struct { - FabricVLANID int `json:"-"` - NetworkID string `json:"-"` -} - -func (client *FabricsClient) GetFabricNetwork(ctx context.Context, input *GetFabricNetworkInput) (*Network, error) { - path := fmt.Sprintf("/%s/fabrics/default/vlans/%d/networks/%s", client.accountName, input.FabricVLANID, input.NetworkID) - respReader, err := client.executeRequest(ctx, http.MethodGet, path, nil) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return nil, errwrap.Wrapf("Error executing GetFabricNetwork request: {{err}}", err) - } - - var result *Network - decoder := json.NewDecoder(respReader) - if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding GetFabricNetwork response: {{err}}", err) - } - - return result, nil -} - -type DeleteFabricNetworkInput struct { - FabricVLANID int `json:"-"` - NetworkID string `json:"-"` -} - -func (client *FabricsClient) DeleteFabricNetwork(ctx context.Context, input *DeleteFabricNetworkInput) error { - path := fmt.Sprintf("/%s/fabrics/default/vlans/%d/networks/%s", client.accountName, input.FabricVLANID, input.NetworkID) - respReader, err := client.executeRequest(ctx, http.MethodDelete, path, nil) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return errwrap.Wrapf("Error executing DeleteFabricNetwork request: {{err}}", err) - } - - return nil -} diff --git a/vendor/github.com/joyent/triton-go/firewall.go b/vendor/github.com/joyent/triton-go/firewall.go deleted file mode 100644 index a3de9ccf6..000000000 --- a/vendor/github.com/joyent/triton-go/firewall.go +++ /dev/null @@ -1,219 +0,0 @@ -package triton - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - - "github.com/hashicorp/errwrap" -) - -type FirewallClient struct { - *Client -} - -// Firewall returns a client used for accessing functions pertaining to -// firewall functionality in the Triton API. -func (c *Client) Firewall() *FirewallClient { - return &FirewallClient{c} -} - -// FirewallRule represents a firewall rule -type FirewallRule struct { - // ID is a unique identifier for this rule - ID string `json:"id"` - - // Enabled indicates if the rule is enabled - Enabled bool `json:"enabled"` - - // Rule is the firewall rule text - Rule string `json:"rule"` - - // Global indicates if the rule is global. Optional. - Global bool `json:"global"` - - // Description is a human-readable description for the rule. Optional - Description string `json:"description"` -} - -type ListFirewallRulesInput struct{} - -func (client *FirewallClient) ListFirewallRules(ctx context.Context, _ *ListFirewallRulesInput) ([]*FirewallRule, error) { - path := fmt.Sprintf("/%s/fwrules", client.accountName) - respReader, err := client.executeRequest(ctx, http.MethodGet, path, nil) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return nil, errwrap.Wrapf("Error executing ListFirewallRules request: {{err}}", err) - } - - var result []*FirewallRule - decoder := json.NewDecoder(respReader) - if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding ListFirewallRules response: {{err}}", err) - } - - return result, nil -} - -type GetFirewallRuleInput struct { - ID string -} - -func (client *FirewallClient) GetFirewallRule(ctx context.Context, input *GetFirewallRuleInput) (*FirewallRule, error) { - path := fmt.Sprintf("/%s/fwrules/%s", client.accountName, input.ID) - respReader, err := client.executeRequest(ctx, http.MethodGet, path, nil) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return nil, errwrap.Wrapf("Error executing GetFirewallRule request: {{err}}", err) - } - - var result *FirewallRule - decoder := json.NewDecoder(respReader) - if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding GetFirewallRule response: {{err}}", err) - } - - return result, nil -} - -type CreateFirewallRuleInput struct { - Enabled bool `json:"enabled"` - Rule string `json:"rule"` - Description string `json:"description"` -} - -func (client *FirewallClient) CreateFirewallRule(ctx context.Context, input *CreateFirewallRuleInput) (*FirewallRule, error) { - path := fmt.Sprintf("/%s/fwrules", client.accountName) - respReader, err := client.executeRequest(ctx, http.MethodPost, path, input) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return nil, errwrap.Wrapf("Error executing CreateFirewallRule request: {{err}}", err) - } - - var result *FirewallRule - decoder := json.NewDecoder(respReader) - if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding CreateFirewallRule response: {{err}}", err) - } - - return result, nil -} - -type UpdateFirewallRuleInput struct { - ID string `json:"-"` - Enabled bool `json:"enabled"` - Rule string `json:"rule"` - Description string `json:"description"` -} - -func (client *FirewallClient) UpdateFirewallRule(ctx context.Context, input *UpdateFirewallRuleInput) (*FirewallRule, error) { - path := fmt.Sprintf("/%s/fwrules/%s", client.accountName, input.ID) - respReader, err := client.executeRequest(ctx, http.MethodPost, path, input) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return nil, errwrap.Wrapf("Error executing UpdateFirewallRule request: {{err}}", err) - } - - var result *FirewallRule - decoder := json.NewDecoder(respReader) - if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding UpdateFirewallRule response: {{err}}", err) - } - - return result, nil -} - -type EnableFirewallRuleInput struct { - ID string `json:"-"` -} - -func (client *FirewallClient) EnableFirewallRule(ctx context.Context, input *EnableFirewallRuleInput) (*FirewallRule, error) { - path := fmt.Sprintf("/%s/fwrules/%s/enable", client.accountName, input.ID) - respReader, err := client.executeRequest(ctx, http.MethodPost, path, input) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return nil, errwrap.Wrapf("Error executing EnableFirewallRule request: {{err}}", err) - } - - var result *FirewallRule - decoder := json.NewDecoder(respReader) - if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding EnableFirewallRule response: {{err}}", err) - } - - return result, nil -} - -type DisableFirewallRuleInput struct { - ID string `json:"-"` -} - -func (client *FirewallClient) DisableFirewallRule(ctx context.Context, input *DisableFirewallRuleInput) (*FirewallRule, error) { - path := fmt.Sprintf("/%s/fwrules/%s/disable", client.accountName, input.ID) - respReader, err := client.executeRequest(ctx, http.MethodPost, path, input) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return nil, errwrap.Wrapf("Error executing DisableFirewallRule request: {{err}}", err) - } - - var result *FirewallRule - decoder := json.NewDecoder(respReader) - if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding DisableFirewallRule response: {{err}}", err) - } - - return result, nil -} - -type DeleteFirewallRuleInput struct { - ID string -} - -func (client *FirewallClient) DeleteFirewallRule(ctx context.Context, input *DeleteFirewallRuleInput) error { - path := fmt.Sprintf("/%s/fwrules/%s", client.accountName, input.ID) - respReader, err := client.executeRequest(ctx, http.MethodDelete, path, nil) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return errwrap.Wrapf("Error executing DeleteFirewallRule request: {{err}}", err) - } - - return nil -} - -type ListMachineFirewallRulesInput struct { - MachineID string -} - -func (client *FirewallClient) ListMachineFirewallRules(ctx context.Context, input *ListMachineFirewallRulesInput) ([]*FirewallRule, error) { - path := fmt.Sprintf("/%s/machines/%s/firewallrules", client.accountName, input.MachineID) - respReader, err := client.executeRequest(ctx, http.MethodGet, path, nil) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return nil, errwrap.Wrapf("Error executing ListMachineFirewallRules request: {{err}}", err) - } - - var result []*FirewallRule - decoder := json.NewDecoder(respReader) - if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding ListFirewallRules response: {{err}}", err) - } - - return result, nil -} diff --git a/vendor/github.com/joyent/triton-go/keys.go b/vendor/github.com/joyent/triton-go/keys.go deleted file mode 100644 index 001f020ee..000000000 --- a/vendor/github.com/joyent/triton-go/keys.go +++ /dev/null @@ -1,125 +0,0 @@ -package triton - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - - "github.com/hashicorp/errwrap" -) - -type KeysClient struct { - *Client -} - -// Keys returns a c used for accessing functions pertaining to -// SSH key functionality in the Triton API. -func (c *Client) Keys() *KeysClient { - return &KeysClient{c} -} - -// Key represents a public key -type Key struct { - // Name of the key - Name string `json:"name"` - - // Key fingerprint - Fingerprint string `json:"fingerprint"` - - // OpenSSH-formatted public key - Key string `json:"key"` -} - -type ListKeysInput struct{} - -// ListKeys lists all public keys we have on record for the specified -// account. -func (client *KeysClient) ListKeys(ctx context.Context, _ *ListKeysInput) ([]*Key, error) { - path := fmt.Sprintf("/%s/keys", client.accountName) - respReader, err := client.executeRequest(ctx, http.MethodGet, path, nil) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return nil, errwrap.Wrapf("Error executing ListKeys request: {{err}}", err) - } - - var result []*Key - decoder := json.NewDecoder(respReader) - if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding ListKeys response: {{err}}", err) - } - - return result, nil -} - -type GetKeyInput struct { - KeyName string -} - -func (client *KeysClient) GetKey(ctx context.Context, input *GetKeyInput) (*Key, error) { - path := fmt.Sprintf("/%s/keys/%s", client.accountName, input.KeyName) - respReader, err := client.executeRequest(ctx, http.MethodGet, path, nil) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return nil, errwrap.Wrapf("Error executing GetKey request: {{err}}", err) - } - - var result *Key - decoder := json.NewDecoder(respReader) - if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding GetKey response: {{err}}", err) - } - - return result, nil -} - -type DeleteKeyInput struct { - KeyName string -} - -func (client *KeysClient) DeleteKey(ctx context.Context, input *DeleteKeyInput) error { - path := fmt.Sprintf("/%s/keys/%s", client.accountName, input.KeyName) - respReader, err := client.executeRequest(ctx, http.MethodDelete, path, nil) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return errwrap.Wrapf("Error executing DeleteKey request: {{err}}", err) - } - - return nil -} - -// CreateKeyInput represents the option that can be specified -// when creating a new key. -type CreateKeyInput struct { - // Name of the key. Optional. - Name string `json:"name,omitempty"` - - // OpenSSH-formatted public key. - Key string `json:"key"` -} - -// CreateKey uploads a new OpenSSH key to Triton for use in HTTP signing and SSH. -func (client *KeysClient) CreateKey(ctx context.Context, input *CreateKeyInput) (*Key, error) { - path := fmt.Sprintf("/%s/keys", client.accountName) - respReader, err := client.executeRequest(ctx, http.MethodPost, path, input) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return nil, errwrap.Wrapf("Error executing CreateKey request: {{err}}", err) - } - - var result *Key - decoder := json.NewDecoder(respReader) - if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding CreateKey response: {{err}}", err) - } - - return result, nil -} diff --git a/vendor/github.com/joyent/triton-go/machines.go b/vendor/github.com/joyent/triton-go/machines.go deleted file mode 100644 index aba5a984a..000000000 --- a/vendor/github.com/joyent/triton-go/machines.go +++ /dev/null @@ -1,667 +0,0 @@ -package triton - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "net/url" - "strings" - "time" - - "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(ctx context.Context, 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(ctx, http.MethodGet, path, nil) - if response != nil { - defer response.Body.Close() - } - if response.StatusCode == http.StatusNotFound || response.StatusCode == http.StatusGone { - return nil, &TritonError{ - StatusCode: response.StatusCode, - 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 -} - -type ListMachinesInput struct{} - -func (client *MachinesClient) ListMachines(ctx context.Context, _ *ListMachinesInput) ([]*Machine, error) { - path := fmt.Sprintf("/%s/machines", client.accountName) - response, err := client.executeRequestRaw(ctx, http.MethodGet, path, nil) - if response != nil { - defer response.Body.Close() - } - if response.StatusCode == http.StatusNotFound { - return nil, &TritonError{ - StatusCode: response.StatusCode, - Code: "ResourceNotFound", - } - } - if err != nil { - return nil, errwrap.Wrapf("Error executing ListMachines 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 ListMachines 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(ctx context.Context, input *CreateMachineInput) (*Machine, error) { - path := fmt.Sprintf("/%s/machines", client.accountName) - respReader, err := client.executeRequest(ctx, http.MethodPost, path, 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(ctx context.Context, input *DeleteMachineInput) error { - path := fmt.Sprintf("/%s/machines/%s", client.accountName, input.ID) - response, err := client.executeRequestRaw(ctx, http.MethodDelete, path, nil) - if response.Body != nil { - defer response.Body.Close() - } - if response.StatusCode == http.StatusNotFound || response.StatusCode == http.StatusGone { - 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(ctx context.Context, input *DeleteMachineTagsInput) error { - path := fmt.Sprintf("/%s/machines/%s/tags", client.accountName, input.ID) - response, err := client.executeRequestRaw(ctx, 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(ctx context.Context, input *DeleteMachineTagInput) error { - path := fmt.Sprintf("/%s/machines/%s/tags/%s", client.accountName, input.ID, input.Key) - response, err := client.executeRequestRaw(ctx, 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(ctx context.Context, 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(ctx, 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(ctx context.Context, input *ReplaceMachineTagsInput) error { - path := fmt.Sprintf("/%s/machines/%s/tags", client.accountName, input.ID) - respReader, err := client.executeRequest(ctx, 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(ctx context.Context, input *AddMachineTagsInput) error { - path := fmt.Sprintf("/%s/machines/%s/tags", client.accountName, input.ID) - respReader, err := client.executeRequest(ctx, 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(ctx context.Context, input *GetMachineTagInput) (string, error) { - path := fmt.Sprintf("/%s/machines/%s/tags/%s", client.accountName, input.ID, input.Key) - respReader, err := client.executeRequest(ctx, 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(ctx context.Context, input *ListMachineTagsInput) (map[string]string, error) { - path := fmt.Sprintf("/%s/machines/%s/tags", client.accountName, input.ID) - respReader, err := client.executeRequest(ctx, 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(ctx context.Context, input *UpdateMachineMetadataInput) (map[string]string, error) { - path := fmt.Sprintf("/%s/machines/%s/tags", client.accountName, input.ID) - respReader, err := client.executeRequest(ctx, 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(ctx context.Context, 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(ctx, 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(ctx context.Context, 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(ctx, 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(ctx context.Context, 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(ctx, 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(ctx context.Context, input *ListNICsInput) ([]*NIC, error) { - path := fmt.Sprintf("/%s/machines/%s/nics", client.accountName, input.MachineID) - respReader, err := client.executeRequest(ctx, http.MethodGet, path, 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(ctx context.Context, input *AddNICInput) (*NIC, error) { - path := fmt.Sprintf("/%s/machines/%s/nics", client.accountName, input.MachineID) - respReader, err := client.executeRequest(ctx, 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(ctx context.Context, input *RemoveNICInput) error { - path := fmt.Sprintf("/%s/machines/%s/nics/%s", client.accountName, input.MachineID, input.MAC) - respReader, err := client.executeRequest(ctx, http.MethodDelete, path, nil) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return errwrap.Wrapf("Error executing RemoveNIC request: {{err}}", err) - } - - return nil -} - -type StopMachineInput struct { - MachineID string -} - -func (client *MachinesClient) StopMachine(ctx context.Context, input *StopMachineInput) error { - path := fmt.Sprintf("/%s/machines/%s", client.accountName, input.MachineID) - - params := &url.Values{} - params.Set("action", "stop") - - respReader, err := client.executeRequestURIParams(ctx, http.MethodPost, path, nil, params) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return errwrap.Wrapf("Error executing StopMachine request: {{err}}", err) - } - - return nil -} - -type StartMachineInput struct { - MachineID string -} - -func (client *MachinesClient) StartMachine(ctx context.Context, input *StartMachineInput) error { - path := fmt.Sprintf("/%s/machines/%s", client.accountName, input.MachineID) - - params := &url.Values{} - params.Set("action", "start") - - respReader, err := client.executeRequestURIParams(ctx, http.MethodPost, path, nil, params) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return errwrap.Wrapf("Error executing StartMachine 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, ",") - } -} diff --git a/vendor/github.com/joyent/triton-go/network/client.go b/vendor/github.com/joyent/triton-go/network/client.go new file mode 100644 index 000000000..dbc25a84c --- /dev/null +++ b/vendor/github.com/joyent/triton-go/network/client.go @@ -0,0 +1,39 @@ +package network + +import ( + triton "github.com/joyent/triton-go" + "github.com/joyent/triton-go/client" +) + +type NetworkClient struct { + Client *client.Client +} + +func newNetworkClient(client *client.Client) *NetworkClient { + return &NetworkClient{ + Client: client, + } +} + +// NewClient returns a new client for working with Network endpoints and +// resources within CloudAPI +func NewClient(config *triton.ClientConfig) (*NetworkClient, error) { + // TODO: Utilize config interface within the function itself + client, err := client.New(config.TritonURL, config.MantaURL, config.AccountName, config.Signers...) + if err != nil { + return nil, err + } + return newNetworkClient(client), nil +} + +// Fabrics returns a FabricsClient used for accessing functions pertaining to +// Fabric functionality in the Triton API. +func (c *NetworkClient) Fabrics() *FabricsClient { + return &FabricsClient{c.Client} +} + +// Firewall returns a FirewallClient client used for accessing functions +// pertaining to firewall functionality in the Triton API. +func (c *NetworkClient) Firewall() *FirewallClient { + return &FirewallClient{c.Client} +} diff --git a/vendor/github.com/joyent/triton-go/network/fabrics.go b/vendor/github.com/joyent/triton-go/network/fabrics.go new file mode 100644 index 000000000..20f1d2fe6 --- /dev/null +++ b/vendor/github.com/joyent/triton-go/network/fabrics.go @@ -0,0 +1,269 @@ +package network + +import ( + "encoding/json" + "fmt" + "net/http" + + "context" + + "github.com/hashicorp/errwrap" + "github.com/joyent/triton-go/client" +) + +type FabricsClient struct { + client *client.Client +} + +type FabricVLAN struct { + Name string `json:"name"` + ID int `json:"vlan_id"` + Description string `json:"description"` +} + +type ListVLANsInput struct{} + +func (c *FabricsClient) ListVLANs(ctx context.Context, _ *ListVLANsInput) ([]*FabricVLAN, error) { + path := fmt.Sprintf("/%s/fabrics/default/vlans", c.client.AccountName) + reqInputs := client.RequestInput{ + Method: http.MethodGet, + Path: path, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing ListVLANs request: {{err}}", err) + } + + var result []*FabricVLAN + decoder := json.NewDecoder(respReader) + if err = decoder.Decode(&result); err != nil { + return nil, errwrap.Wrapf("Error decoding ListVLANs response: {{err}}", err) + } + + return result, nil +} + +type CreateVLANInput struct { + Name string `json:"name"` + ID int `json:"vlan_id"` + Description string `json:"description,omitempty"` +} + +func (c *FabricsClient) CreateVLAN(ctx context.Context, input *CreateVLANInput) (*FabricVLAN, error) { + path := fmt.Sprintf("/%s/fabrics/default/vlans", c.client.AccountName) + reqInputs := client.RequestInput{ + Method: http.MethodPost, + Path: path, + Body: input, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing CreateVLAN request: {{err}}", err) + } + + var result *FabricVLAN + decoder := json.NewDecoder(respReader) + if err = decoder.Decode(&result); err != nil { + return nil, errwrap.Wrapf("Error decoding CreateVLAN response: {{err}}", err) + } + + return result, nil +} + +type UpdateVLANInput struct { + ID int `json:"-"` + Name string `json:"name"` + Description string `json:"description"` +} + +func (c *FabricsClient) UpdateVLAN(ctx context.Context, input *UpdateVLANInput) (*FabricVLAN, error) { + path := fmt.Sprintf("/%s/fabrics/default/vlans/%d", c.client.AccountName, input.ID) + reqInputs := client.RequestInput{ + Method: http.MethodPut, + Path: path, + Body: input, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing UpdateVLAN request: {{err}}", err) + } + + var result *FabricVLAN + decoder := json.NewDecoder(respReader) + if err = decoder.Decode(&result); err != nil { + return nil, errwrap.Wrapf("Error decoding UpdateVLAN response: {{err}}", err) + } + + return result, nil +} + +type GetVLANInput struct { + ID int `json:"-"` +} + +func (c *FabricsClient) GetVLAN(ctx context.Context, input *GetVLANInput) (*FabricVLAN, error) { + path := fmt.Sprintf("/%s/fabrics/default/vlans/%d", c.client.AccountName, input.ID) + reqInputs := client.RequestInput{ + Method: http.MethodGet, + Path: path, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing GetVLAN request: {{err}}", err) + } + + var result *FabricVLAN + decoder := json.NewDecoder(respReader) + if err = decoder.Decode(&result); err != nil { + return nil, errwrap.Wrapf("Error decoding GetVLAN response: {{err}}", err) + } + + return result, nil +} + +type DeleteVLANInput struct { + ID int `json:"-"` +} + +func (c *FabricsClient) DeleteVLAN(ctx context.Context, input *DeleteVLANInput) error { + path := fmt.Sprintf("/%s/fabrics/default/vlans/%d", c.client.AccountName, input.ID) + reqInputs := client.RequestInput{ + Method: http.MethodDelete, + Path: path, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return errwrap.Wrapf("Error executing DeleteVLAN request: {{err}}", err) + } + + return nil +} + +type ListFabricsInput struct { + FabricVLANID int `json:"-"` +} + +func (c *FabricsClient) List(ctx context.Context, input *ListFabricsInput) ([]*Network, error) { + path := fmt.Sprintf("/%s/fabrics/default/vlans/%d/networks", c.client.AccountName, input.FabricVLANID) + reqInputs := client.RequestInput{ + Method: http.MethodGet, + Path: path, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing ListFabrics request: {{err}}", err) + } + + var result []*Network + decoder := json.NewDecoder(respReader) + if err = decoder.Decode(&result); err != nil { + return nil, errwrap.Wrapf("Error decoding ListFabrics response: {{err}}", err) + } + + return result, nil +} + +type CreateFabricInput struct { + FabricVLANID int `json:"-"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Subnet string `json:"subnet"` + ProvisionStartIP string `json:"provision_start_ip"` + ProvisionEndIP string `json:"provision_end_ip"` + Gateway string `json:"gateway,omitempty"` + Resolvers []string `json:"resolvers,omitempty"` + Routes map[string]string `json:"routes,omitempty"` + InternetNAT bool `json:"internet_nat"` +} + +func (c *FabricsClient) Create(ctx context.Context, input *CreateFabricInput) (*Network, error) { + path := fmt.Sprintf("/%s/fabrics/default/vlans/%d/networks", c.client.AccountName, input.FabricVLANID) + reqInputs := client.RequestInput{ + Method: http.MethodPost, + Path: path, + Body: input, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing CreateFabric request: {{err}}", err) + } + + var result *Network + decoder := json.NewDecoder(respReader) + if err = decoder.Decode(&result); err != nil { + return nil, errwrap.Wrapf("Error decoding CreateFabric response: {{err}}", err) + } + + return result, nil +} + +type GetFabricInput struct { + FabricVLANID int `json:"-"` + NetworkID string `json:"-"` +} + +func (c *FabricsClient) Get(ctx context.Context, input *GetFabricInput) (*Network, error) { + path := fmt.Sprintf("/%s/fabrics/default/vlans/%d/networks/%s", c.client.AccountName, input.FabricVLANID, input.NetworkID) + reqInputs := client.RequestInput{ + Method: http.MethodGet, + Path: path, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing GetFabric request: {{err}}", err) + } + + var result *Network + decoder := json.NewDecoder(respReader) + if err = decoder.Decode(&result); err != nil { + return nil, errwrap.Wrapf("Error decoding GetFabric response: {{err}}", err) + } + + return result, nil +} + +type DeleteFabricInput struct { + FabricVLANID int `json:"-"` + NetworkID string `json:"-"` +} + +func (c *FabricsClient) Delete(ctx context.Context, input *DeleteFabricInput) error { + path := fmt.Sprintf("/%s/fabrics/default/vlans/%d/networks/%s", c.client.AccountName, input.FabricVLANID, input.NetworkID) + reqInputs := client.RequestInput{ + Method: http.MethodDelete, + Path: path, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return errwrap.Wrapf("Error executing DeleteFabric request: {{err}}", err) + } + + return nil +} diff --git a/vendor/github.com/joyent/triton-go/network/firewall.go b/vendor/github.com/joyent/triton-go/network/firewall.go new file mode 100644 index 000000000..60054702a --- /dev/null +++ b/vendor/github.com/joyent/triton-go/network/firewall.go @@ -0,0 +1,250 @@ +package network + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/hashicorp/errwrap" + "github.com/joyent/triton-go/client" +) + +type FirewallClient struct { + client *client.Client +} + +// FirewallRule represents a firewall rule +type FirewallRule struct { + // ID is a unique identifier for this rule + ID string `json:"id"` + + // Enabled indicates if the rule is enabled + Enabled bool `json:"enabled"` + + // Rule is the firewall rule text + Rule string `json:"rule"` + + // Global indicates if the rule is global. Optional. + Global bool `json:"global"` + + // Description is a human-readable description for the rule. Optional + Description string `json:"description"` +} + +type ListRulesInput struct{} + +func (c *FirewallClient) ListRules(ctx context.Context, _ *ListRulesInput) ([]*FirewallRule, error) { + path := fmt.Sprintf("/%s/fwrules", c.client.AccountName) + reqInputs := client.RequestInput{ + Method: http.MethodGet, + Path: path, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing ListRules request: {{err}}", err) + } + + var result []*FirewallRule + decoder := json.NewDecoder(respReader) + if err = decoder.Decode(&result); err != nil { + return nil, errwrap.Wrapf("Error decoding ListRules response: {{err}}", err) + } + + return result, nil +} + +type GetRuleInput struct { + ID string +} + +func (c *FirewallClient) GetRule(ctx context.Context, input *GetRuleInput) (*FirewallRule, error) { + path := fmt.Sprintf("/%s/fwrules/%s", c.client.AccountName, input.ID) + reqInputs := client.RequestInput{ + Method: http.MethodGet, + Path: path, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing GetRule request: {{err}}", err) + } + + var result *FirewallRule + decoder := json.NewDecoder(respReader) + if err = decoder.Decode(&result); err != nil { + return nil, errwrap.Wrapf("Error decoding GetRule response: {{err}}", err) + } + + return result, nil +} + +type CreateRuleInput struct { + Enabled bool `json:"enabled"` + Rule string `json:"rule"` + Description string `json:"description,omitempty"` +} + +func (c *FirewallClient) CreateRule(ctx context.Context, input *CreateRuleInput) (*FirewallRule, error) { + path := fmt.Sprintf("/%s/fwrules", c.client.AccountName) + reqInputs := client.RequestInput{ + Method: http.MethodPost, + Path: path, + Body: input, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing CreateRule request: {{err}}", err) + } + + var result *FirewallRule + decoder := json.NewDecoder(respReader) + if err = decoder.Decode(&result); err != nil { + return nil, errwrap.Wrapf("Error decoding CreateRule response: {{err}}", err) + } + + return result, nil +} + +type UpdateRuleInput struct { + ID string `json:"-"` + Enabled bool `json:"enabled"` + Rule string `json:"rule"` + Description string `json:"description,omitempty"` +} + +func (c *FirewallClient) UpdateRule(ctx context.Context, input *UpdateRuleInput) (*FirewallRule, error) { + path := fmt.Sprintf("/%s/fwrules/%s", c.client.AccountName, input.ID) + reqInputs := client.RequestInput{ + Method: http.MethodPost, + Path: path, + Body: input, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing UpdateRule request: {{err}}", err) + } + + var result *FirewallRule + decoder := json.NewDecoder(respReader) + if err = decoder.Decode(&result); err != nil { + return nil, errwrap.Wrapf("Error decoding UpdateRule response: {{err}}", err) + } + + return result, nil +} + +type EnableRuleInput struct { + ID string `json:"-"` +} + +func (c *FirewallClient) EnableRule(ctx context.Context, input *EnableRuleInput) (*FirewallRule, error) { + path := fmt.Sprintf("/%s/fwrules/%s/enable", c.client.AccountName, input.ID) + reqInputs := client.RequestInput{ + Method: http.MethodPost, + Path: path, + Body: input, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing EnableRule request: {{err}}", err) + } + + var result *FirewallRule + decoder := json.NewDecoder(respReader) + if err = decoder.Decode(&result); err != nil { + return nil, errwrap.Wrapf("Error decoding EnableRule response: {{err}}", err) + } + + return result, nil +} + +type DisableRuleInput struct { + ID string `json:"-"` +} + +func (c *FirewallClient) DisableRule(ctx context.Context, input *DisableRuleInput) (*FirewallRule, error) { + path := fmt.Sprintf("/%s/fwrules/%s/disable", c.client.AccountName, input.ID) + reqInputs := client.RequestInput{ + Method: http.MethodPost, + Path: path, + Body: input, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing DisableRule request: {{err}}", err) + } + + var result *FirewallRule + decoder := json.NewDecoder(respReader) + if err = decoder.Decode(&result); err != nil { + return nil, errwrap.Wrapf("Error decoding DisableRule response: {{err}}", err) + } + + return result, nil +} + +type DeleteRuleInput struct { + ID string +} + +func (c *FirewallClient) DeleteRule(ctx context.Context, input *DeleteRuleInput) error { + path := fmt.Sprintf("/%s/fwrules/%s", c.client.AccountName, input.ID) + reqInputs := client.RequestInput{ + Method: http.MethodDelete, + Path: path, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return errwrap.Wrapf("Error executing DeleteRule request: {{err}}", err) + } + + return nil +} + +type ListMachineRulesInput struct { + MachineID string +} + +func (c *FirewallClient) ListMachineRules(ctx context.Context, input *ListMachineRulesInput) ([]*FirewallRule, error) { + path := fmt.Sprintf("/%s/machines/%s/firewallrules", c.client.AccountName, input.MachineID) + reqInputs := client.RequestInput{ + Method: http.MethodGet, + Path: path, + } + respReader, err := c.client.ExecuteRequest(ctx, reqInputs) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing ListMachineRules request: {{err}}", err) + } + + var result []*FirewallRule + decoder := json.NewDecoder(respReader) + if err = decoder.Decode(&result); err != nil { + return nil, errwrap.Wrapf("Error decoding ListRules response: {{err}}", err) + } + + return result, nil +} diff --git a/vendor/github.com/joyent/triton-go/networks.go b/vendor/github.com/joyent/triton-go/network/network.go similarity index 66% rename from vendor/github.com/joyent/triton-go/networks.go rename to vendor/github.com/joyent/triton-go/network/network.go index 585a65e7c..d853e0402 100644 --- a/vendor/github.com/joyent/triton-go/networks.go +++ b/vendor/github.com/joyent/triton-go/network/network.go @@ -1,24 +1,15 @@ -package triton +package network import ( + "context" "encoding/json" "fmt" "net/http" - "context" "github.com/hashicorp/errwrap" + "github.com/joyent/triton-go/client" ) -type NetworksClient struct { - *Client -} - -// Networks returns a c used for accessing functions pertaining to -// Network functionality in the Triton API. -func (c *Client) Networks() *NetworksClient { - return &NetworksClient{c} -} - type Network struct { Id string `json:"id"` Name string `json:"name"` @@ -34,11 +25,15 @@ type Network struct { InternetNAT bool `json:"internet_nat"` } -type ListNetworksInput struct{} +type ListInput struct{} -func (client *NetworksClient) ListNetworks(ctx context.Context, _ *ListNetworksInput) ([]*Network, error) { - path := fmt.Sprintf("/%s/networks", client.accountName) - respReader, err := client.executeRequest(ctx, http.MethodGet, path, nil) +func (c *NetworkClient) List(ctx context.Context, _ *ListInput) ([]*Network, error) { + path := fmt.Sprintf("/%s/networks", c.Client.AccountName) + reqInputs := client.RequestInput{ + Method: http.MethodGet, + Path: path, + } + respReader, err := c.Client.ExecuteRequest(ctx, reqInputs) if respReader != nil { defer respReader.Close() } @@ -55,13 +50,17 @@ func (client *NetworksClient) ListNetworks(ctx context.Context, _ *ListNetworksI return result, nil } -type GetNetworkInput struct { +type GetInput struct { ID string } -func (client *NetworksClient) GetNetwork(ctx context.Context, input *GetNetworkInput) (*Network, error) { - path := fmt.Sprintf("/%s/networks/%s", client.accountName, input.ID) - respReader, err := client.executeRequest(ctx, http.MethodGet, path, nil) +func (c *NetworkClient) Get(ctx context.Context, input *GetInput) (*Network, error) { + path := fmt.Sprintf("/%s/networks/%s", c.Client.AccountName, input.ID) + reqInputs := client.RequestInput{ + Method: http.MethodGet, + Path: path, + } + respReader, err := c.Client.ExecuteRequest(ctx, reqInputs) if respReader != nil { defer respReader.Close() } diff --git a/vendor/github.com/joyent/triton-go/roles.go b/vendor/github.com/joyent/triton-go/roles.go deleted file mode 100644 index a01fba6a9..000000000 --- a/vendor/github.com/joyent/triton-go/roles.go +++ /dev/null @@ -1,164 +0,0 @@ -package triton - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - - "github.com/hashicorp/errwrap" -) - -type RolesClient struct { - *Client -} - -// Roles returns a c used for accessing functions pertaining -// to Role functionality in the Triton API. -func (c *Client) Roles() *RolesClient { - return &RolesClient{c} -} - -type Role struct { - ID string `json:"id"` - Name string `json:"name"` - Policies []string `json:"policies"` - Members []string `json:"policies"` - DefaultMembers []string `json:"default_members"` -} - -type ListRolesInput struct{} - -func (client *RolesClient) ListRoles(ctx context.Context, _ *ListRolesInput) ([]*Role, error) { - path := fmt.Sprintf("/%s/roles", client.accountName) - respReader, err := client.executeRequest(ctx, http.MethodGet, path, nil) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return nil, errwrap.Wrapf("Error executing ListRoles request: {{err}}", err) - } - - var result []*Role - decoder := json.NewDecoder(respReader) - if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding ListRoles response: {{err}}", err) - } - - return result, nil -} - -type GetRoleInput struct { - RoleID string -} - -func (client *RolesClient) GetRole(ctx context.Context, input *GetRoleInput) (*Role, error) { - path := fmt.Sprintf("/%s/roles/%s", client.accountName, input.RoleID) - respReader, err := client.executeRequest(ctx, http.MethodGet, path, nil) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return nil, errwrap.Wrapf("Error executing GetRole request: {{err}}", err) - } - - var result *Role - decoder := json.NewDecoder(respReader) - if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding GetRole response: {{err}}", err) - } - - return result, nil -} - -// CreateRoleInput represents the options that can be specified -// when creating a new role. -type CreateRoleInput struct { - // Name of the role. Required. - Name string `json:"name"` - - // This account's policies to be given to this role. Optional. - Policies []string `json:"policies,omitempty"` - - // This account's user logins to be added to this role. Optional. - Members []string `json:"members,omitempty"` - - // This account's user logins to be added to this role and have - // it enabled by default. Optional. - DefaultMembers []string `json:"default_members,omitempty"` -} - -func (client *RolesClient) CreateRole(ctx context.Context, input *CreateRoleInput) (*Role, error) { - path := fmt.Sprintf("/%s/roles", client.accountName) - respReader, err := client.executeRequest(ctx, http.MethodPost, path, input) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return nil, errwrap.Wrapf("Error executing CreateRole request: {{err}}", err) - } - - var result *Role - decoder := json.NewDecoder(respReader) - if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding CreateRole response: {{err}}", err) - } - - return result, nil -} - -// UpdateRoleInput represents the options that can be specified -// when updating a role. Anything but ID can be modified. -type UpdateRoleInput struct { - // ID of the role to modify. Required. - RoleID string `json:"id"` - - // Name of the role. Required. - Name string `json:"name"` - - // This account's policies to be given to this role. Optional. - Policies []string `json:"policies,omitempty"` - - // This account's user logins to be added to this role. Optional. - Members []string `json:"members,omitempty"` - - // This account's user logins to be added to this role and have - // it enabled by default. Optional. - DefaultMembers []string `json:"default_members,omitempty"` -} - -func (client *RolesClient) UpdateRole(ctx context.Context, input *UpdateRoleInput) (*Role, error) { - path := fmt.Sprintf("/%s/roles/%s", client.accountName, input.RoleID) - respReader, err := client.executeRequest(ctx, http.MethodPost, path, input) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return nil, errwrap.Wrapf("Error executing UpdateRole request: {{err}}", err) - } - - var result *Role - decoder := json.NewDecoder(respReader) - if err = decoder.Decode(&result); err != nil { - return nil, errwrap.Wrapf("Error decoding UpdateRole response: {{err}}", err) - } - - return result, nil -} - -type DeleteRoleInput struct { - RoleID string -} - -func (client *RolesClient) DeleteRoles(ctx context.Context, input *DeleteRoleInput) error { - path := fmt.Sprintf("/%s/roles/%s", client.accountName, input.RoleID) - respReader, err := client.executeRequest(ctx, http.MethodDelete, path, nil) - if respReader != nil { - defer respReader.Close() - } - if err != nil { - return errwrap.Wrapf("Error executing DeleteRole request: {{err}}", err) - } - - return nil -} diff --git a/vendor/github.com/joyent/triton-go/triton.go b/vendor/github.com/joyent/triton-go/triton.go new file mode 100644 index 000000000..b5bacd255 --- /dev/null +++ b/vendor/github.com/joyent/triton-go/triton.go @@ -0,0 +1,18 @@ +package triton + +import ( + "github.com/joyent/triton-go/authentication" +) + +// Universal package used for defining configuration used across all client +// constructors. + +// ClientConfig is a placeholder/input struct around the behavior of configuring +// a client constructor through the implementation's runtime environment +// (SDC/MANTA env vars). +type ClientConfig struct { + TritonURL string + MantaURL string + AccountName string + Signers []authentication.Signer +} diff --git a/vendor/vendor.json b/vendor/vendor.json index f9b17b648..0a06ac4b1 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -817,16 +817,34 @@ "revision": "c01cf91b011868172fdcd9f41838e80c9d716264" }, { - "checksumSHA1": "o8jaSD36Zq42PMnmUaiB+vq+QNA=", + "checksumSHA1": "EqvUu0Ku0Ec5Tk6yhGNOuOr8yeA=", "path": "github.com/joyent/triton-go", - "revision": "97ccd9f6c0c0652cf87997bcb01955e0329cd37e", - "revisionTime": "2017-05-09T20:29:43Z" + "revision": "5a58ad2cdec95cddd1e0a2e56f559341044b04f0", + "revisionTime": "2017-10-17T16:55:58Z" }, { - "checksumSHA1": "QzUqkCSn/ZHyIK346xb9V6EBw9U=", + "checksumSHA1": "JKf97EAAAZFQ6Wf8qN9X7TWqNBY=", "path": "github.com/joyent/triton-go/authentication", - "revision": "16cef4c2d78ba1d3bf89af75e93ae2dec6e56634", - "revisionTime": "2017-05-04T20:45:05Z" + "revision": "5a58ad2cdec95cddd1e0a2e56f559341044b04f0", + "revisionTime": "2017-10-17T16:55:58Z" + }, + { + "checksumSHA1": "dlO1or0cyVMAmZzyLcBuoy+M0xU=", + "path": "github.com/joyent/triton-go/client", + "revision": "5a58ad2cdec95cddd1e0a2e56f559341044b04f0", + "revisionTime": "2017-10-17T16:55:58Z" + }, + { + "checksumSHA1": "O/y7BfKJFUf3A8TCRMXgo9HSb1w=", + "path": "github.com/joyent/triton-go/compute", + "revision": "5a58ad2cdec95cddd1e0a2e56f559341044b04f0", + "revisionTime": "2017-10-17T16:55:58Z" + }, + { + "checksumSHA1": "gyLtPyKlcumRSkrAH+SsDQo1GnY=", + "path": "github.com/joyent/triton-go/network", + "revision": "5a58ad2cdec95cddd1e0a2e56f559341044b04f0", + "revisionTime": "2017-10-17T16:55:58Z" }, { "checksumSHA1": "gEjGS03N1eysvpQ+FCHTxPcbxXc=",