diff --git a/builtin/providers/triton/provider.go b/builtin/providers/triton/provider.go index 216ed1c08..31e3659e9 100644 --- a/builtin/providers/triton/provider.go +++ b/builtin/providers/triton/provider.go @@ -1,41 +1,43 @@ package triton import ( - "fmt" - "log" - "os" + "crypto/md5" + "encoding/base64" + "errors" + "sort" + "time" + "github.com/hashicorp/errwrap" "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" - "github.com/joyent/gocommon/client" - "github.com/joyent/gosdc/cloudapi" - "github.com/joyent/gosign/auth" + "github.com/joyent/triton-go" + "github.com/joyent/triton-go/authentication" ) // Provider returns a terraform.ResourceProvider. func Provider() terraform.ResourceProvider { return &schema.Provider{ Schema: map[string]*schema.Schema{ - "account": &schema.Schema{ + "account": { Type: schema.TypeString, Required: true, DefaultFunc: schema.EnvDefaultFunc("SDC_ACCOUNT", ""), }, - "url": &schema.Schema{ + "url": { Type: schema.TypeString, Required: true, DefaultFunc: schema.EnvDefaultFunc("SDC_URL", "https://us-west-1.api.joyentcloud.com"), }, - "key_material": &schema.Schema{ + "key_material": { Type: schema.TypeString, - Required: true, + Optional: true, DefaultFunc: schema.EnvDefaultFunc("SDC_KEY_MATERIAL", ""), }, - "key_id": &schema.Schema{ + "key_id": { Type: schema.TypeString, Required: true, DefaultFunc: schema.EnvDefaultFunc("SDC_KEY_ID", ""), @@ -53,70 +55,113 @@ func Provider() terraform.ResourceProvider { } } -type SDCConfig struct { +type Config struct { Account string KeyMaterial string KeyID string URL string } -func (c SDCConfig) validate() error { +func (c Config) validate() error { var err *multierror.Error if c.URL == "" { - err = multierror.Append(err, fmt.Errorf("URL must be configured for the Triton provider")) - } - if c.KeyMaterial == "" { - err = multierror.Append(err, fmt.Errorf("Key Material must be configured for the Triton provider")) + err = multierror.Append(err, errors.New("URL must be configured for the Triton provider")) } if c.KeyID == "" { - err = multierror.Append(err, fmt.Errorf("Key ID must be configured for the Triton provider")) + err = multierror.Append(err, errors.New("Key ID must be configured for the Triton provider")) } if c.Account == "" { - err = multierror.Append(err, fmt.Errorf("Account must be configured for the Triton provider")) + err = multierror.Append(err, errors.New("Account must be configured for the Triton provider")) } return err.ErrorOrNil() } -func (c SDCConfig) getSDCClient() (*cloudapi.Client, error) { - userauth, err := auth.NewAuth(c.Account, c.KeyMaterial, "rsa-sha256") +func (c Config) getTritonClient() (*triton.Client, error) { + var signer authentication.Signer + var err error + if c.KeyMaterial == "" { + signer, err = authentication.NewSSHAgentSigner(c.KeyID, c.Account) + if err != nil { + return nil, errwrap.Wrapf("Error Creating SSH Agent Signer: {{err}}", err) + } + } else { + signer, err = authentication.NewPrivateKeySigner(c.KeyID, []byte(c.KeyMaterial), c.Account) + if err != nil { + return nil, errwrap.Wrapf("Error Creating SSH Private Key Signer: {{err}}", err) + } + } + + client, err := triton.NewClient(c.URL, c.Account, signer) if err != nil { - return nil, err + return nil, errwrap.Wrapf("Error Creating Triton Client: {{err}}", err) } - creds := &auth.Credentials{ - UserAuthentication: userauth, - SdcKeyId: c.KeyID, - SdcEndpoint: auth.Endpoint{URL: c.URL}, - } - - client := cloudapi.New(client.NewClient( - c.URL, - cloudapi.DefaultAPIVersion, - creds, - log.New(os.Stderr, "", log.LstdFlags), - )) - return client, nil } func providerConfigure(d *schema.ResourceData) (interface{}, error) { - config := SDCConfig{ - Account: d.Get("account").(string), - URL: d.Get("url").(string), - KeyMaterial: d.Get("key_material").(string), - KeyID: d.Get("key_id").(string), + config := Config{ + Account: d.Get("account").(string), + URL: d.Get("url").(string), + KeyID: d.Get("key_id").(string), + } + + if keyMaterial, ok := d.GetOk("key_material"); ok { + config.KeyMaterial = keyMaterial.(string) } if err := config.validate(); err != nil { return nil, err } - client, err := config.getSDCClient() + client, err := config.getTritonClient() if err != nil { return nil, err } return client, nil } + +func resourceExists(resource interface{}, err error) (bool, error) { + if err != nil { + if triton.IsResourceNotFound(err) { + return false, nil + } + + return false, err + } + + return resource != nil, nil +} + +func stableMapHash(input map[string]string) string { + keys := make([]string, 0, len(input)) + for k := range input { + keys = append(keys, k) + } + sort.Strings(keys) + + hash := md5.New() + for _, key := range keys { + hash.Write([]byte(key)) + hash.Write([]byte(input[key])) + } + + return base64.StdEncoding.EncodeToString(hash.Sum([]byte{})) +} + +var fastResourceTimeout = &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(1 * time.Minute), + Read: schema.DefaultTimeout(30 * time.Second), + Update: schema.DefaultTimeout(1 * time.Minute), + Delete: schema.DefaultTimeout(1 * time.Minute), +} + +var slowResourceTimeout = &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Read: schema.DefaultTimeout(30 * time.Second), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), +} diff --git a/builtin/providers/triton/provider_test.go b/builtin/providers/triton/provider_test.go index 2aa283ec3..11b962aca 100644 --- a/builtin/providers/triton/provider_test.go +++ b/builtin/providers/triton/provider_test.go @@ -32,13 +32,13 @@ func testAccPreCheck(t *testing.T) { sdcURL := os.Getenv("SDC_URL") account := os.Getenv("SDC_ACCOUNT") keyID := os.Getenv("SDC_KEY_ID") - keyMaterial := os.Getenv("SDC_KEY_MATERIAL") if sdcURL == "" { sdcURL = "https://us-west-1.api.joyentcloud.com" } - if sdcURL == "" || account == "" || keyID == "" || keyMaterial == "" { - t.Fatal("SDC_ACCOUNT, SDC_KEY_ID and SDC_KEY_MATERIAL must be set for acceptance tests") + if sdcURL == "" || account == "" || keyID == "" { + t.Fatal("SDC_ACCOUNT and SDC_KEY_ID must be set for acceptance tests. To test with the SSH" + + " private key signer, SDC_KEY_MATERIAL must also be set.") } } diff --git a/builtin/providers/triton/resource_fabric.go b/builtin/providers/triton/resource_fabric.go index be85a4381..5e672b3e5 100644 --- a/builtin/providers/triton/resource_fabric.go +++ b/builtin/providers/triton/resource_fabric.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/hashicorp/terraform/helper/schema" - "github.com/joyent/gosdc/cloudapi" + "github.com/joyent/triton-go" ) func resourceFabric() *schema.Resource { @@ -16,74 +16,74 @@ func resourceFabric() *schema.Resource { Schema: map[string]*schema.Schema{ "name": { - Description: "network name", + Description: "Network name", Required: true, ForceNew: true, Type: schema.TypeString, }, "public": { - Description: "whether or not this is an RFC1918 network", + Description: "Whether or not this is an RFC1918 network", Computed: true, Type: schema.TypeBool, }, "fabric": { - Description: "whether or not this network is on a fabric", + Description: "Whether or not this network is on a fabric", Computed: true, Type: schema.TypeBool, }, "description": { - Description: "optional description of network", + Description: "Description of network", Optional: true, ForceNew: true, Type: schema.TypeString, }, "subnet": { - Description: "CIDR formatted string describing network", + Description: "CIDR formatted string describing network address space", Required: true, ForceNew: true, Type: schema.TypeString, }, "provision_start_ip": { - Description: "first IP on the network that can be assigned", + Description: "First IP on the network that can be assigned", Required: true, ForceNew: true, Type: schema.TypeString, }, "provision_end_ip": { - Description: "last assignable IP on the network", + Description: "Last assignable IP on the network", Required: true, ForceNew: true, Type: schema.TypeString, }, "gateway": { - Description: "optional gateway IP", + Description: "Gateway IP", Optional: true, ForceNew: true, Type: schema.TypeString, }, "resolvers": { - Description: "array of IP addresses for resolvers", + Description: "List of IP addresses for DNS resolvers", Optional: true, Computed: true, Type: schema.TypeList, Elem: &schema.Schema{Type: schema.TypeString}, }, "routes": { - Description: "map of CIDR block to Gateway IP address", + Description: "Map of CIDR block to Gateway IP address", Computed: true, Optional: true, ForceNew: true, Type: schema.TypeMap, }, "internet_nat": { - Description: "if a NAT zone is provisioned at Gateway IP address", + Description: "Whether or not a NAT zone is provisioned at the Gateway IP address", Computed: true, Optional: true, ForceNew: true, Type: schema.TypeBool, }, "vlan_id": { - Description: "VLAN network is on", + Description: "VLAN on which the network exists", Required: true, ForceNew: true, Type: schema.TypeInt, @@ -93,7 +93,7 @@ func resourceFabric() *schema.Resource { } func resourceFabricCreate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*cloudapi.Client) + client := meta.(*triton.Client) var resolvers []string for _, resolver := range d.Get("resolvers").([]interface{}) { @@ -104,24 +104,23 @@ func resourceFabricCreate(d *schema.ResourceData, meta interface{}) error { for cidr, v := range d.Get("routes").(map[string]interface{}) { ip, ok := v.(string) if !ok { - return fmt.Errorf(`cannot use "%v" as an IP address`, v) + return fmt.Errorf(`Cannot use "%v" as an IP address`, v) } routes[cidr] = ip } - fabric, err := client.CreateFabricNetwork( - int16(d.Get("vlan_id").(int)), - cloudapi.CreateFabricNetworkOpts{ - Name: d.Get("name").(string), - Description: d.Get("description").(string), - Subnet: d.Get("subnet").(string), - ProvisionStartIp: d.Get("provision_start_ip").(string), - ProvisionEndIp: d.Get("provision_end_ip").(string), - Gateway: d.Get("gateway").(string), - Resolvers: resolvers, - Routes: routes, - InternetNAT: d.Get("internet_nat").(bool), - }, + fabric, err := client.Fabrics().CreateFabricNetwork(&triton.CreateFabricNetworkInput{ + FabricVLANID: d.Get("vlan_id").(int), + Name: d.Get("name").(string), + Description: d.Get("description").(string), + Subnet: d.Get("subnet").(string), + ProvisionStartIP: d.Get("provision_start_ip").(string), + ProvisionEndIP: d.Get("provision_end_ip").(string), + Gateway: d.Get("gateway").(string), + Resolvers: resolvers, + Routes: routes, + InternetNAT: d.Get("internet_nat").(bool), + }, ) if err != nil { return err @@ -129,26 +128,25 @@ func resourceFabricCreate(d *schema.ResourceData, meta interface{}) error { d.SetId(fabric.Id) - err = resourceFabricRead(d, meta) - if err != nil { - return err - } - - return nil + return resourceFabricRead(d, meta) } func resourceFabricExists(d *schema.ResourceData, meta interface{}) (bool, error) { - client := meta.(*cloudapi.Client) + client := meta.(*triton.Client) - fabric, err := client.GetFabricNetwork(int16(d.Get("vlan_id").(int)), d.Id()) - - return fabric != nil && err == nil, err + return resourceExists(client.Fabrics().GetFabricNetwork(&triton.GetFabricNetworkInput{ + FabricVLANID: d.Get("vlan_id").(int), + NetworkID: d.Id(), + })) } func resourceFabricRead(d *schema.ResourceData, meta interface{}) error { - client := meta.(*cloudapi.Client) + client := meta.(*triton.Client) - fabric, err := client.GetFabricNetwork(int16(d.Get("vlan_id").(int)), d.Id()) + fabric, err := client.Fabrics().GetFabricNetwork(&triton.GetFabricNetworkInput{ + FabricVLANID: d.Get("vlan_id").(int), + NetworkID: d.Id(), + }) if err != nil { return err } @@ -156,23 +154,25 @@ func resourceFabricRead(d *schema.ResourceData, meta interface{}) error { d.SetId(fabric.Id) d.Set("name", fabric.Name) d.Set("public", fabric.Public) - d.Set("public", fabric.Public) d.Set("fabric", fabric.Fabric) d.Set("description", fabric.Description) d.Set("subnet", fabric.Subnet) - d.Set("provision_start_ip", fabric.ProvisionStartIp) - d.Set("provision_end_ip", fabric.ProvisionEndIp) + d.Set("provision_start_ip", fabric.ProvisioningStartIP) + d.Set("provision_end_ip", fabric.ProvisioningEndIP) d.Set("gateway", fabric.Gateway) d.Set("resolvers", fabric.Resolvers) d.Set("routes", fabric.Routes) d.Set("internet_nat", fabric.InternetNAT) - d.Set("vlan_id", fabric.VLANId) + d.Set("vlan_id", d.Get("vlan_id").(int)) return nil } func resourceFabricDelete(d *schema.ResourceData, meta interface{}) error { - client := meta.(*cloudapi.Client) + client := meta.(*triton.Client) - return client.DeleteFabricNetwork(int16(d.Get("vlan_id").(int)), d.Id()) + return client.Fabrics().DeleteFabricNetwork(&triton.DeleteFabricNetworkInput{ + FabricVLANID: d.Get("vlan_id").(int), + NetworkID: d.Id(), + }) } diff --git a/builtin/providers/triton/resource_fabric_test.go b/builtin/providers/triton/resource_fabric_test.go index a6b1d8847..ec91eac00 100644 --- a/builtin/providers/triton/resource_fabric_test.go +++ b/builtin/providers/triton/resource_fabric_test.go @@ -9,19 +9,19 @@ import ( "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" - "github.com/joyent/gosdc/cloudapi" + "github.com/joyent/triton-go" ) func TestAccTritonFabric_basic(t *testing.T) { fabricName := fmt.Sprintf("acctest-%d", acctest.RandInt()) - config := fmt.Sprintf(testAccTritonFabric_basic, fabricName) + config := fmt.Sprintf(testAccTritonFabric_basic, acctest.RandIntRange(3, 2049), fabricName, fabricName) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testCheckTritonFabricDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: config, Check: resource.ComposeTestCheckFunc( testCheckTritonFabricExists("triton_fabric.test"), @@ -37,62 +37,75 @@ func TestAccTritonFabric_basic(t *testing.T) { func testCheckTritonFabricExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { - // Ensure we have enough information in state to look up in API rs, ok := s.RootModule().Resources[name] if !ok { return fmt.Errorf("Not found: %s", name) } - conn := testAccProvider.Meta().(*cloudapi.Client) + conn := testAccProvider.Meta().(*triton.Client) - id, err := strconv.ParseInt(rs.Primary.Attributes["vlan_id"], 10, 16) + vlanID, err := strconv.Atoi(rs.Primary.Attributes["vlan_id"]) if err != nil { return err } - fabric, err := conn.GetFabricNetwork(int16(id), rs.Primary.ID) + exists, err := resourceExists(conn.Fabrics().GetFabricNetwork(&triton.GetFabricNetworkInput{ + FabricVLANID: vlanID, + NetworkID: rs.Primary.ID, + })) if err != nil { - return fmt.Errorf("Bad: Check Fabric Exists: %s", err) + return fmt.Errorf("Error: Check Fabric Exists: %s", err) } - if fabric == nil { - return fmt.Errorf("Bad: Fabric %q does not exist", rs.Primary.ID) + if exists { + return nil } - return nil + return fmt.Errorf("Error: Fabric %q (VLAN %d) Does Not Exist", rs.Primary.ID, vlanID) } } func testCheckTritonFabricDestroy(s *terraform.State) error { - conn := testAccProvider.Meta().(*cloudapi.Client) + conn := testAccProvider.Meta().(*triton.Client) for _, rs := range s.RootModule().Resources { if rs.Type != "triton_fabric" { continue } - id, err := strconv.ParseInt(rs.Primary.Attributes["vlan_id"], 10, 16) + vlanID, err := strconv.Atoi(rs.Primary.Attributes["vlan_id"]) if err != nil { return err } - fabric, err := conn.GetFabricNetwork(int16(id), rs.Primary.ID) + exists, err := resourceExists(conn.Fabrics().GetFabricNetwork(&triton.GetFabricNetworkInput{ + FabricVLANID: vlanID, + NetworkID: rs.Primary.ID, + })) if err != nil { return nil } - if fabric != nil { - return fmt.Errorf("Bad: Fabric %q still exists", rs.Primary.ID) + if exists { + return fmt.Errorf("Error: Fabric %q (VLAN %d) Still Exists", rs.Primary.ID, vlanID) } + + return nil } return nil } var testAccTritonFabric_basic = ` +resource "triton_vlan" "test" { + vlan_id = "%d" + name = "%s" + description = "testAccTritonFabric_basic" +} + resource "triton_fabric" "test" { name = "%s" description = "test network" - vlan_id = 2 # every DC seems to have a vlan 2 available + vlan_id = "${triton_vlan.test.id}" subnet = "10.0.0.0/22" gateway = "10.0.0.1" diff --git a/builtin/providers/triton/resource_firewall_rule.go b/builtin/providers/triton/resource_firewall_rule.go index af43dbaff..afa3f012c 100644 --- a/builtin/providers/triton/resource_firewall_rule.go +++ b/builtin/providers/triton/resource_firewall_rule.go @@ -2,8 +2,7 @@ package triton import ( "github.com/hashicorp/terraform/helper/schema" - "github.com/joyent/gocommon/errors" - "github.com/joyent/gosdc/cloudapi" + "github.com/joyent/triton-go" ) func resourceFirewallRule() *schema.Resource { @@ -14,7 +13,7 @@ func resourceFirewallRule() *schema.Resource { Update: resourceFirewallRuleUpdate, Delete: resourceFirewallRuleDelete, Importer: &schema.ResourceImporter{ - State: resourceFirewallRuleImporter, + State: schema.ImportStatePassthrough, }, Schema: map[string]*schema.Schema{ @@ -29,67 +28,73 @@ func resourceFirewallRule() *schema.Resource { Optional: true, Default: false, }, + "description": { + Description: "Human-readable description of the rule", + Type: schema.TypeString, + Optional: true, + }, + "global": { + Description: "Indicates whether or not the rule is global", + Type: schema.TypeBool, + Computed: true, + }, }, } } func resourceFirewallRuleCreate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*cloudapi.Client) + client := meta.(*triton.Client) - rule, err := client.CreateFirewallRule(cloudapi.CreateFwRuleOpts{ - Rule: d.Get("rule").(string), - Enabled: d.Get("enabled").(bool), + rule, err := client.Firewall().CreateFirewallRule(&triton.CreateFirewallRuleInput{ + Rule: d.Get("rule").(string), + Enabled: d.Get("enabled").(bool), + Description: d.Get("description").(string), }) if err != nil { return err } - d.SetId(rule.Id) + d.SetId(rule.ID) - err = resourceFirewallRuleRead(d, meta) - if err != nil { - return err - } - - return nil + return resourceFirewallRuleRead(d, meta) } func resourceFirewallRuleExists(d *schema.ResourceData, meta interface{}) (bool, error) { - client := meta.(*cloudapi.Client) + client := meta.(*triton.Client) - rule, err := client.GetFirewallRule(d.Id()) - if errors.IsResourceNotFound(err) { - return false, nil - } - - return rule != nil && err == nil, err + return resourceExists(client.Firewall().GetFirewallRule(&triton.GetFirewallRuleInput{ + ID: d.Id(), + })) } func resourceFirewallRuleRead(d *schema.ResourceData, meta interface{}) error { - client := meta.(*cloudapi.Client) + client := meta.(*triton.Client) - rule, err := client.GetFirewallRule(d.Id()) + rule, err := client.Firewall().GetFirewallRule(&triton.GetFirewallRuleInput{ + ID: d.Id(), + }) if err != nil { return err } - d.SetId(rule.Id) + d.SetId(rule.ID) d.Set("rule", rule.Rule) d.Set("enabled", rule.Enabled) + d.Set("global", rule.Global) + d.Set("description", rule.Description) return nil } func resourceFirewallRuleUpdate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*cloudapi.Client) + client := meta.(*triton.Client) - _, err := client.UpdateFirewallRule( - d.Id(), - cloudapi.CreateFwRuleOpts{ - Rule: d.Get("rule").(string), - Enabled: d.Get("enabled").(bool), - }, - ) + _, err := client.Firewall().UpdateFirewallRule(&triton.UpdateFirewallRuleInput{ + ID: d.Id(), + Rule: d.Get("rule").(string), + Enabled: d.Get("enabled").(bool), + Description: d.Get("description").(string), + }) if err != nil { return err } @@ -98,15 +103,9 @@ func resourceFirewallRuleUpdate(d *schema.ResourceData, meta interface{}) error } func resourceFirewallRuleDelete(d *schema.ResourceData, meta interface{}) error { - client := meta.(*cloudapi.Client) + client := meta.(*triton.Client) - if err := client.DeleteFirewallRule(d.Id()); err != nil { - return err - } - - return nil -} - -func resourceFirewallRuleImporter(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - return []*schema.ResourceData{d}, nil + return client.Firewall().DeleteFirewallRule(&triton.DeleteFirewallRuleInput{ + ID: d.Id(), + }) } diff --git a/builtin/providers/triton/resource_firewall_rule_test.go b/builtin/providers/triton/resource_firewall_rule_test.go index 2c3c8cdde..199c46330 100644 --- a/builtin/providers/triton/resource_firewall_rule_test.go +++ b/builtin/providers/triton/resource_firewall_rule_test.go @@ -6,7 +6,7 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" - "github.com/joyent/gosdc/cloudapi" + "github.com/joyent/triton-go" ) func TestAccTritonFirewallRule_basic(t *testing.T) { @@ -17,7 +17,7 @@ func TestAccTritonFirewallRule_basic(t *testing.T) { Providers: testAccProviders, CheckDestroy: testCheckTritonFirewallRuleDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: config, Check: resource.ComposeTestCheckFunc( testCheckTritonFirewallRuleExists("triton_firewall_rule.test"), @@ -36,20 +36,20 @@ func TestAccTritonFirewallRule_update(t *testing.T) { Providers: testAccProviders, CheckDestroy: testCheckTritonFirewallRuleDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: preConfig, Check: resource.ComposeTestCheckFunc( testCheckTritonFirewallRuleExists("triton_firewall_rule.test"), - resource.TestCheckResourceAttr("triton_firewall_rule.test", "rule", "FROM any TO tag www ALLOW tcp PORT 80"), + resource.TestCheckResourceAttr("triton_firewall_rule.test", "rule", "FROM any TO tag \"www\" ALLOW tcp PORT 80"), resource.TestCheckResourceAttr("triton_firewall_rule.test", "enabled", "false"), ), }, - resource.TestStep{ + { Config: postConfig, Check: resource.ComposeTestCheckFunc( testCheckTritonFirewallRuleExists("triton_firewall_rule.test"), - resource.TestCheckResourceAttr("triton_firewall_rule.test", "rule", "FROM any TO tag www BLOCK tcp PORT 80"), + resource.TestCheckResourceAttr("triton_firewall_rule.test", "rule", "FROM any TO tag \"www\" BLOCK tcp PORT 80"), resource.TestCheckResourceAttr("triton_firewall_rule.test", "enabled", "true"), ), }, @@ -66,20 +66,20 @@ func TestAccTritonFirewallRule_enable(t *testing.T) { Providers: testAccProviders, CheckDestroy: testCheckTritonFirewallRuleDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: preConfig, Check: resource.ComposeTestCheckFunc( testCheckTritonFirewallRuleExists("triton_firewall_rule.test"), - resource.TestCheckResourceAttr("triton_firewall_rule.test", "rule", "FROM any TO tag www ALLOW tcp PORT 80"), + resource.TestCheckResourceAttr("triton_firewall_rule.test", "rule", "FROM any TO tag \"www\" ALLOW tcp PORT 80"), resource.TestCheckResourceAttr("triton_firewall_rule.test", "enabled", "false"), ), }, - resource.TestStep{ + { Config: postConfig, Check: resource.ComposeTestCheckFunc( testCheckTritonFirewallRuleExists("triton_firewall_rule.test"), - resource.TestCheckResourceAttr("triton_firewall_rule.test", "rule", "FROM any TO tag www ALLOW tcp PORT 80"), + resource.TestCheckResourceAttr("triton_firewall_rule.test", "rule", "FROM any TO tag \"www\" ALLOW tcp PORT 80"), resource.TestCheckResourceAttr("triton_firewall_rule.test", "enabled", "true"), ), }, @@ -94,15 +94,19 @@ func testCheckTritonFirewallRuleExists(name string) resource.TestCheckFunc { if !ok { return fmt.Errorf("Not found: %s", name) } - conn := testAccProvider.Meta().(*cloudapi.Client) + conn := testAccProvider.Meta().(*triton.Client) - rule, err := conn.GetFirewallRule(rs.Primary.ID) - if err != nil { + resp, err := conn.Firewall().GetFirewallRule(&triton.GetFirewallRuleInput{ + ID: rs.Primary.ID, + }) + if err != nil && triton.IsResourceNotFound(err) { return fmt.Errorf("Bad: Check Firewall Rule Exists: %s", err) + } else if err != nil { + return err } - if rule == nil { - return fmt.Errorf("Bad: Firewall rule %q does not exist", rs.Primary.ID) + if resp == nil { + return fmt.Errorf("Bad: Firewall Rule %q does not exist", rs.Primary.ID) } return nil @@ -110,20 +114,24 @@ func testCheckTritonFirewallRuleExists(name string) resource.TestCheckFunc { } func testCheckTritonFirewallRuleDestroy(s *terraform.State) error { - conn := testAccProvider.Meta().(*cloudapi.Client) + conn := testAccProvider.Meta().(*triton.Client) for _, rs := range s.RootModule().Resources { if rs.Type != "triton_firewall_rule" { continue } - resp, err := conn.GetFirewallRule(rs.Primary.ID) - if err != nil { + resp, err := conn.Firewall().GetFirewallRule(&triton.GetFirewallRuleInput{ + ID: rs.Primary.ID, + }) + if triton.IsResourceNotFound(err) { return nil + } else if err != nil { + return err } if resp != nil { - return fmt.Errorf("Bad: Firewall rule %q still exists", rs.Primary.ID) + return fmt.Errorf("Bad: Firewall Rule %q still exists", rs.Primary.ID) } } @@ -132,21 +140,21 @@ func testCheckTritonFirewallRuleDestroy(s *terraform.State) error { var testAccTritonFirewallRule_basic = ` resource "triton_firewall_rule" "test" { - rule = "FROM any TO tag www ALLOW tcp PORT 80" - enabled = false + rule = "FROM any TO tag \"www\" ALLOW tcp PORT 80" + enabled = false } ` var testAccTritonFirewallRule_update = ` resource "triton_firewall_rule" "test" { - rule = "FROM any TO tag www BLOCK tcp PORT 80" + rule = "FROM any TO tag \"www\" BLOCK tcp PORT 80" enabled = true } ` var testAccTritonFirewallRule_enable = ` resource "triton_firewall_rule" "test" { - rule = "FROM any TO tag www ALLOW tcp PORT 80" + rule = "FROM any TO tag \"www\" ALLOW tcp PORT 80" enabled = true } ` diff --git a/builtin/providers/triton/resource_key.go b/builtin/providers/triton/resource_key.go index aa730a48d..31b03de1c 100644 --- a/builtin/providers/triton/resource_key.go +++ b/builtin/providers/triton/resource_key.go @@ -5,35 +5,30 @@ import ( "strings" "github.com/hashicorp/terraform/helper/schema" - "github.com/joyent/gosdc/cloudapi" -) - -var ( - // ErrNoKeyComment will be returned when the key name cannot be generated from - // the key comment and is not otherwise specified. - ErrNoKeyComment = errors.New("no key comment found to use as a name (and none specified)") + "github.com/joyent/triton-go" ) func resourceKey() *schema.Resource { return &schema.Resource{ - Create: resourceKeyCreate, - Exists: resourceKeyExists, - Read: resourceKeyRead, - Delete: resourceKeyDelete, + Create: resourceKeyCreate, + Exists: resourceKeyExists, + Read: resourceKeyRead, + Delete: resourceKeyDelete, + Timeouts: fastResourceTimeout, Importer: &schema.ResourceImporter{ - State: resourceKeyImporter, + State: schema.ImportStatePassthrough, }, Schema: map[string]*schema.Schema{ - "name": &schema.Schema{ - Description: "name of this key (will be generated from the key comment, if not set and comment present)", + "name": { + Description: "Name of the key (generated from the key comment if not set)", Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "key": &schema.Schema{ - Description: "content of public key from disk", + "key": { + Description: "Content of public key from disk in OpenSSH format", Type: schema.TypeString, Required: true, ForceNew: true, @@ -43,18 +38,18 @@ func resourceKey() *schema.Resource { } func resourceKeyCreate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*cloudapi.Client) + client := meta.(*triton.Client) - if d.Get("name").(string) == "" { + if keyName := d.Get("name").(string); keyName == "" { parts := strings.SplitN(d.Get("key").(string), " ", 3) if len(parts) == 3 { d.Set("name", parts[2]) } else { - return ErrNoKeyComment + return errors.New("No key name specified, and key material has no comment") } } - _, err := client.CreateKey(cloudapi.CreateKeyOpts{ + _, err := client.Keys().CreateKey(&triton.CreateKeyInput{ Name: d.Get("name").(string), Key: d.Get("key").(string), }) @@ -64,35 +59,28 @@ func resourceKeyCreate(d *schema.ResourceData, meta interface{}) error { d.SetId(d.Get("name").(string)) - err = resourceKeyRead(d, meta) - if err != nil { - return err - } - - return nil + return resourceKeyRead(d, meta) } func resourceKeyExists(d *schema.ResourceData, meta interface{}) (bool, error) { - client := meta.(*cloudapi.Client) + client := meta.(*triton.Client) - keys, err := client.ListKeys() + _, err := client.Keys().GetKey(&triton.GetKeyInput{ + KeyName: d.Id(), + }) if err != nil { return false, err } - for _, key := range keys { - if key.Name == d.Id() { - return true, nil - } - } - - return false, nil + return true, nil } func resourceKeyRead(d *schema.ResourceData, meta interface{}) error { - client := meta.(*cloudapi.Client) + client := meta.(*triton.Client) - key, err := client.GetKey(d.Id()) + key, err := client.Keys().GetKey(&triton.GetKeyInput{ + KeyName: d.Id(), + }) if err != nil { return err } @@ -104,15 +92,9 @@ func resourceKeyRead(d *schema.ResourceData, meta interface{}) error { } func resourceKeyDelete(d *schema.ResourceData, meta interface{}) error { - client := meta.(*cloudapi.Client) + client := meta.(*triton.Client) - if err := client.DeleteKey(d.Get("name").(string)); err != nil { - return err - } - - return nil -} - -func resourceKeyImporter(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - return []*schema.ResourceData{d}, nil + return client.Keys().DeleteKey(&triton.DeleteKeyInput{ + KeyName: d.Id(), + }) } diff --git a/builtin/providers/triton/resource_key_test.go b/builtin/providers/triton/resource_key_test.go index 1d1bf79a2..224999016 100644 --- a/builtin/providers/triton/resource_key_test.go +++ b/builtin/providers/triton/resource_key_test.go @@ -8,22 +8,57 @@ import ( "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" - "github.com/joyent/gosdc/cloudapi" + "github.com/joyent/triton-go" ) func TestAccTritonKey_basic(t *testing.T) { keyName := fmt.Sprintf("acctest-%d", acctest.RandInt()) - config := fmt.Sprintf(testAccTritonKey_basic, keyName, testAccTritonKey_basicMaterial) + publicKeyMaterial, _, err := acctest.RandSSHKeyPair("TestAccTritonKey_basic@terraform") + if err != nil { + t.Fatalf("Cannot generate test SSH key pair: %s", err) + } + config := testAccTritonKey_basic(keyName, publicKeyMaterial) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testCheckTritonKeyDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: config, Check: resource.ComposeTestCheckFunc( testCheckTritonKeyExists("triton_key.test"), + resource.TestCheckResourceAttr("triton_key.test", "name", keyName), + resource.TestCheckResourceAttr("triton_key.test", "key", publicKeyMaterial), + func(*terraform.State) error { + time.Sleep(10 * time.Second) + return nil + }, + ), + }, + }, + }) +} + +func TestAccTritonKey_noKeyName(t *testing.T) { + keyComment := fmt.Sprintf("acctest_%d@terraform", acctest.RandInt()) + keyMaterial, _, err := acctest.RandSSHKeyPair(keyComment) + if err != nil { + t.Fatalf("Cannot generate test SSH key pair: %s", err) + } + config := testAccTritonKey_noKeyName(keyMaterial) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckTritonKeyDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckTritonKeyExists("triton_key.test"), + resource.TestCheckResourceAttr("triton_key.test", "name", keyComment), + resource.TestCheckResourceAttr("triton_key.test", "key", keyMaterial), func(*terraform.State) error { time.Sleep(10 * time.Second) return nil @@ -41,14 +76,16 @@ func testCheckTritonKeyExists(name string) resource.TestCheckFunc { if !ok { return fmt.Errorf("Not found: %s", name) } - conn := testAccProvider.Meta().(*cloudapi.Client) + conn := testAccProvider.Meta().(*triton.Client) - rule, err := conn.GetKey(rs.Primary.ID) + key, err := conn.Keys().GetKey(&triton.GetKeyInput{ + KeyName: rs.Primary.ID, + }) if err != nil { return fmt.Errorf("Bad: Check Key Exists: %s", err) } - if rule == nil { + if key == nil { return fmt.Errorf("Bad: Key %q does not exist", rs.Primary.ID) } @@ -57,7 +94,7 @@ func testCheckTritonKeyExists(name string) resource.TestCheckFunc { } func testCheckTritonKeyDestroy(s *terraform.State) error { - conn := testAccProvider.Meta().(*cloudapi.Client) + conn := testAccProvider.Meta().(*triton.Client) return resource.Retry(1*time.Minute, func() *resource.RetryError { for _, rs := range s.RootModule().Resources { @@ -65,12 +102,14 @@ func testCheckTritonKeyDestroy(s *terraform.State) error { continue } - resp, err := conn.GetKey(rs.Primary.ID) + key, err := conn.Keys().GetKey(&triton.GetKeyInput{ + KeyName: rs.Primary.ID, + }) if err != nil { return nil } - if resp != nil { + if key != nil { return resource.RetryableError(fmt.Errorf("Bad: Key %q still exists", rs.Primary.ID)) } } @@ -79,11 +118,17 @@ func testCheckTritonKeyDestroy(s *terraform.State) error { }) } -var testAccTritonKey_basic = ` -resource "triton_key" "test" { - name = "%s" - key = "%s" +var testAccTritonKey_basic = func(keyName string, keyMaterial string) string { + return fmt.Sprintf(`resource "triton_key" "test" { + name = "%s" + key = "%s" + } + `, keyName, keyMaterial) } -` -const testAccTritonKey_basicMaterial = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDL18KJIe8N7FxcgOMtabo10qZEDyYUSlOpsh/EYrugQCQHMKuNytog1lhFNZNk4LGNAz5L8/btG9+/axY/PfundbjR3SXt0hupAGQIVHuygWTr7foj5iGhckrEM+r3eMCXqoCnIFLhDZLDcq/zN2MxNbqDKcWSYmc8ul9dZWuiQpKOL+0nNXjhYA8Ewu+07kVAtsZD0WfvnAUjxmYb3rB15eBWk7gLxHrOPfZpeDSvOOX2bmzikpLn+L5NKrJsLrzO6hU/rpxD4OTHLULcsnIts3lYH8hShU8uY5ry94PBzdix++se3pUGvNSe967fKlHw3Ymh9nE/LJDQnzTNyFMj James@jn-mpb13` +var testAccTritonKey_noKeyName = func(keyMaterial string) string { + return fmt.Sprintf(`resource "triton_key" "test" { + key = "%s" + } + `, keyMaterial) +} diff --git a/builtin/providers/triton/resource_machine.go b/builtin/providers/triton/resource_machine.go index 2523dd5d0..7eb66591c 100644 --- a/builtin/providers/triton/resource_machine.go +++ b/builtin/providers/triton/resource_machine.go @@ -2,22 +2,20 @@ package triton import ( "fmt" - "reflect" "regexp" "time" "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" - "github.com/joyent/gosdc/cloudapi" + "github.com/joyent/triton-go" ) var ( machineStateRunning = "running" - machineStateStopped = "stopped" machineStateDeleted = "deleted" - machineStateChangeTimeout = 10 * time.Minute - machineStateChangeCheckInterval = 10 * time.Second + machineStateChangeTimeout = 10 * time.Minute resourceMachineMetadataKeys = map[string]string{ // semantics: "schema_name": "metadata_name" @@ -30,50 +28,46 @@ var ( func resourceMachine() *schema.Resource { return &schema.Resource{ - Create: resourceMachineCreate, - Exists: resourceMachineExists, - Read: resourceMachineRead, - Update: resourceMachineUpdate, - Delete: resourceMachineDelete, + Create: resourceMachineCreate, + Exists: resourceMachineExists, + Read: resourceMachineRead, + Update: resourceMachineUpdate, + Delete: resourceMachineDelete, + Timeouts: slowResourceTimeout, Importer: &schema.ResourceImporter{ - State: resourceMachineImporter, + State: schema.ImportStatePassthrough, }, Schema: map[string]*schema.Schema{ "name": { - Description: "friendly name", + Description: "Friendly name for machine", Type: schema.TypeString, Optional: true, Computed: true, ValidateFunc: resourceMachineValidateName, }, "type": { - Description: "machine type (smartmachine or virtualmachine)", - Type: schema.TypeString, - Computed: true, - }, - "state": { - Description: "current state of the machine", + Description: "Machine type (smartmachine or virtualmachine)", Type: schema.TypeString, Computed: true, }, "dataset": { - Description: "dataset URN the machine was provisioned with", + Description: "Dataset URN with which the machine was provisioned", Type: schema.TypeString, Computed: true, }, "memory": { - Description: "amount of memory the machine has (in Mb)", + Description: "Amount of memory allocated to the machine (in Mb)", Type: schema.TypeInt, Computed: true, }, "disk": { - Description: "amount of disk the machine has (in Gb)", + Description: "Amount of disk allocated to the machine (in Gb)", Type: schema.TypeInt, Computed: true, }, "ips": { - Description: "IP addresses the machine has", + Description: "IP addresses assigned to the machine", Type: schema.TypeList, Computed: true, Elem: &schema.Schema{ @@ -81,39 +75,38 @@ func resourceMachine() *schema.Resource { }, }, "tags": { - Description: "machine tags", + Description: "Machine tags", Type: schema.TypeMap, Optional: true, }, "created": { - Description: "when the machine was created", + Description: "When the machine was created", Type: schema.TypeString, Computed: true, }, "updated": { - Description: "when the machine was update", + Description: "When the machine was updated", Type: schema.TypeString, Computed: true, }, "package": { - Description: "name of the package to use on provisioning", + Description: "The package for use for provisioning", Type: schema.TypeString, Required: true, }, "image": { - Description: "image UUID", + Description: "UUID of the image", Type: schema.TypeString, Required: true, ForceNew: true, - // TODO: validate that the UUID is valid }, "primaryip": { - Description: "the primary (public) IP address for the machine", + Description: "Primary (public) IP address for the machine", Type: schema.TypeString, Computed: true, }, "nic": { - Description: "network interface", + Description: "Network interface", Type: schema.TypeSet, Computed: true, Optional: true, @@ -148,27 +141,27 @@ func resourceMachine() *schema.Resource { Computed: true, Type: schema.TypeString, }, - "state": { - Description: "describes the state of the NIC (e.g. provisioning, running, or stopped)", - Computed: true, + "network": { + Description: "ID of the network to which the NIC is attached", + Required: true, Type: schema.TypeString, }, - "network": { - Description: "Network ID this NIC is attached to", - Required: true, + "state": { + Description: "Provisioning state of the NIC", + Computed: true, Type: schema.TypeString, }, }, }, }, "firewall_enabled": { - Description: "enable firewall for this machine", + Description: "Whether to enable the firewall for this machine", Type: schema.TypeBool, Optional: true, Default: false, }, "domain_names": { - Description: "list of domain names from Triton's CNS", + Description: "List of domain names from Triton CNS", Type: schema.TypeList, Computed: true, Elem: &schema.Schema{ @@ -178,25 +171,25 @@ func resourceMachine() *schema.Resource { // computed resources from metadata "root_authorized_keys": { - Description: "authorized keys for the root user on this machine", + Description: "Authorized keys for the root user on this machine", Type: schema.TypeString, Optional: true, Computed: true, }, "user_script": { - Description: "user script to run on boot (every boot on SmartMachines)", + Description: "User script to run on boot (every boot on SmartMachines)", Type: schema.TypeString, Optional: true, Computed: true, }, "user_data": { - Description: "copied to machine on boot", + Description: "Data copied to machine on boot", Type: schema.TypeString, Optional: true, Computed: true, }, "administrator_pw": { - Description: "administrator's initial password (Windows only)", + Description: "Administrator's initial password (Windows only)", Type: schema.TypeString, Optional: true, Computed: true, @@ -204,7 +197,7 @@ func resourceMachine() *schema.Resource { // deprecated fields "networks": { - Description: "desired network IDs", + Description: "Desired network IDs", Type: schema.TypeList, Optional: true, Computed: true, @@ -218,7 +211,7 @@ func resourceMachine() *schema.Resource { } func resourceMachineCreate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*cloudapi.Client) + client := meta.(*triton.Client) var networks []string for _, network := range d.Get("networks").([]interface{}) { @@ -242,7 +235,7 @@ func resourceMachineCreate(d *schema.ResourceData, meta interface{}) error { tags[k] = v.(string) } - machine, err := client.CreateMachine(cloudapi.CreateMachineOpts{ + machine, err := client.Machines().CreateMachine(&triton.CreateMachineInput{ Name: d.Get("name").(string), Package: d.Get("package").(string), Image: d.Get("image").(string), @@ -255,47 +248,64 @@ func resourceMachineCreate(d *schema.ResourceData, meta interface{}) error { return err } - err = waitForMachineState(client, machine.Id, machineStateRunning, machineStateChangeTimeout) + d.SetId(machine.ID) + stateConf := &resource.StateChangeConf{ + Target: []string{fmt.Sprintf(machineStateRunning)}, + Refresh: func() (interface{}, string, error) { + getResp, err := client.Machines().GetMachine(&triton.GetMachineInput{ + ID: d.Id(), + }) + if err != nil { + return nil, "", err + } + + return getResp, getResp.State, nil + }, + Timeout: machineStateChangeTimeout, + MinTimeout: 3 * time.Second, + } + _, err = stateConf.WaitForState() + if err != nil { + return err + } if err != nil { return err } // refresh state after it provisions - d.SetId(machine.Id) - err = resourceMachineRead(d, meta) - if err != nil { - return err - } - - return nil + return resourceMachineRead(d, meta) } func resourceMachineExists(d *schema.ResourceData, meta interface{}) (bool, error) { - client := meta.(*cloudapi.Client) + client := meta.(*triton.Client) - machine, err := client.GetMachine(d.Id()) - - return machine != nil && err == nil, err + return resourceExists(client.Machines().GetMachine(&triton.GetMachineInput{ + ID: d.Id(), + })) } func resourceMachineRead(d *schema.ResourceData, meta interface{}) error { - client := meta.(*cloudapi.Client) + client := meta.(*triton.Client) - machine, err := client.GetMachine(d.Id()) + machine, err := client.Machines().GetMachine(&triton.GetMachineInput{ + ID: d.Id(), + }) if err != nil { return err } - nics, err := client.ListNICs(d.Id()) + nics, err := client.Machines().ListNICs(&triton.ListNICsInput{ + MachineID: d.Id(), + }) if err != nil { return err } - d.SetId(machine.Id) d.Set("name", machine.Name) d.Set("type", machine.Type) d.Set("state", machine.State) - d.Set("dataset", machine.Dataset) + d.Set("dataset", machine.Image) + d.Set("image", machine.Image) d.Set("memory", machine.Memory) d.Set("disk", machine.Disk) d.Set("ips", machine.IPs) @@ -340,23 +350,40 @@ func resourceMachineRead(d *schema.ResourceData, meta interface{}) error { } func resourceMachineUpdate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*cloudapi.Client) + client := meta.(*triton.Client) d.Partial(true) if d.HasChange("name") { - if err := client.RenameMachine(d.Id(), d.Get("name").(string)); err != nil { + oldNameInterface, newNameInterface := d.GetChange("name") + oldName := oldNameInterface.(string) + newName := newNameInterface.(string) + + err := client.Machines().RenameMachine(&triton.RenameMachineInput{ + ID: d.Id(), + Name: newName, + }) + if err != nil { return err } - err := waitFor( - func() (bool, error) { - machine, err := client.GetMachine(d.Id()) - return machine.Name == d.Get("name").(string), err + stateConf := &resource.StateChangeConf{ + Pending: []string{oldName}, + Target: []string{newName}, + Refresh: func() (interface{}, string, error) { + getResp, err := client.Machines().GetMachine(&triton.GetMachineInput{ + ID: d.Id(), + }) + if err != nil { + return nil, "", err + } + + return getResp, getResp.Name, nil }, - machineStateChangeCheckInterval, - 1*time.Minute, - ) + Timeout: machineStateChangeTimeout, + MinTimeout: 3 * time.Second, + } + _, err = stateConf.WaitForState() if err != nil { return err } @@ -372,22 +399,36 @@ func resourceMachineUpdate(d *schema.ResourceData, meta interface{}) error { var err error if len(tags) == 0 { - err = client.DeleteMachineTags(d.Id()) + err = client.Machines().DeleteMachineTags(&triton.DeleteMachineTagsInput{ + ID: d.Id(), + }) } else { - _, err = client.ReplaceMachineTags(d.Id(), tags) + err = client.Machines().ReplaceMachineTags(&triton.ReplaceMachineTagsInput{ + ID: d.Id(), + Tags: tags, + }) } if err != nil { return err } - err = waitFor( - func() (bool, error) { - machine, err := client.GetMachine(d.Id()) - return reflect.DeepEqual(machine.Tags, tags), err + expectedTagsMD5 := stableMapHash(tags) + stateConf := &resource.StateChangeConf{ + Target: []string{expectedTagsMD5}, + Refresh: func() (interface{}, string, error) { + getResp, err := client.Machines().GetMachine(&triton.GetMachineInput{ + ID: d.Id(), + }) + if err != nil { + return nil, "", err + } + + return getResp, stableMapHash(getResp.Tags), nil }, - machineStateChangeCheckInterval, - 1*time.Minute, - ) + Timeout: machineStateChangeTimeout, + MinTimeout: 3 * time.Second, + } + _, err = stateConf.WaitForState() if err != nil { return err } @@ -396,18 +437,32 @@ func resourceMachineUpdate(d *schema.ResourceData, meta interface{}) error { } if d.HasChange("package") { - if err := client.ResizeMachine(d.Id(), d.Get("package").(string)); err != nil { + newPackage := d.Get("package").(string) + + err := client.Machines().ResizeMachine(&triton.ResizeMachineInput{ + ID: d.Id(), + Package: newPackage, + }) + if err != nil { return err } - err := waitFor( - func() (bool, error) { - machine, err := client.GetMachine(d.Id()) - return machine.Package == d.Get("package").(string) && machine.State == machineStateRunning, err + stateConf := &resource.StateChangeConf{ + Target: []string{fmt.Sprintf("%s@%s", newPackage, "running")}, + Refresh: func() (interface{}, string, error) { + getResp, err := client.Machines().GetMachine(&triton.GetMachineInput{ + ID: d.Id(), + }) + if err != nil { + return nil, "", err + } + + return getResp, fmt.Sprintf("%s@%s", getResp.Package, getResp.State), nil }, - machineStateChangeCheckInterval, - machineStateChangeTimeout, - ) + Timeout: machineStateChangeTimeout, + MinTimeout: 3 * time.Second, + } + _, err = stateConf.WaitForState() if err != nil { return err } @@ -416,25 +471,38 @@ func resourceMachineUpdate(d *schema.ResourceData, meta interface{}) error { } if d.HasChange("firewall_enabled") { + enable := d.Get("firewall_enabled").(bool) + var err error - if d.Get("firewall_enabled").(bool) { - err = client.EnableFirewallMachine(d.Id()) + if enable { + err = client.Machines().EnableMachineFirewall(&triton.EnableMachineFirewallInput{ + ID: d.Id(), + }) } else { - err = client.DisableFirewallMachine(d.Id()) + err = client.Machines().DisableMachineFirewall(&triton.DisableMachineFirewallInput{ + ID: d.Id(), + }) } if err != nil { return err } - err = waitFor( - func() (bool, error) { - machine, err := client.GetMachine(d.Id()) - return machine.FirewallEnabled == d.Get("firewall_enabled").(bool), err - }, - machineStateChangeCheckInterval, - machineStateChangeTimeout, - ) + stateConf := &resource.StateChangeConf{ + Target: []string{fmt.Sprintf("%t", enable)}, + Refresh: func() (interface{}, string, error) { + getResp, err := client.Machines().GetMachine(&triton.GetMachineInput{ + ID: d.Id(), + }) + if err != nil { + return nil, "", err + } + return getResp, fmt.Sprintf("%t", getResp.FirewallEnabled), nil + }, + Timeout: machineStateChangeTimeout, + MinTimeout: 3 * time.Second, + } + _, err = stateConf.WaitForState() if err != nil { return err } @@ -452,24 +520,24 @@ func resourceMachineUpdate(d *schema.ResourceData, meta interface{}) error { } oldNICs := o.(*schema.Set) - newNICs := o.(*schema.Set) + newNICs := n.(*schema.Set) - // add new NICs that are not in old NICs for _, nicI := range newNICs.Difference(oldNICs).List() { nic := nicI.(map[string]interface{}) - fmt.Printf("adding %+v\n", nic) - _, err := client.AddNIC(d.Id(), nic["network"].(string)) - if err != nil { + if _, err := client.Machines().AddNIC(&triton.AddNICInput{ + MachineID: d.Id(), + Network: nic["network"].(string), + }); err != nil { return err } } - // remove old NICs that are not in new NICs for _, nicI := range oldNICs.Difference(newNICs).List() { nic := nicI.(map[string]interface{}) - fmt.Printf("removing %+v\n", nic) - err := client.RemoveNIC(d.Id(), nic["mac"].(string)) - if err != nil { + if err := client.Machines().RemoveNIC(&triton.RemoveNICInput{ + MachineID: d.Id(), + MAC: nic["mac"].(string), + }); err != nil { return err } } @@ -477,7 +545,6 @@ func resourceMachineUpdate(d *schema.ResourceData, meta interface{}) error { d.SetPartial("nic") } - // metadata stuff metadata := map[string]string{} for schemaName, metadataKey := range resourceMachineMetadataKeys { if d.HasChange(schemaName) { @@ -485,24 +552,35 @@ func resourceMachineUpdate(d *schema.ResourceData, meta interface{}) error { } } if len(metadata) > 0 { - _, err := client.UpdateMachineMetadata(d.Id(), metadata) - if err != nil { + if _, err := client.Machines().UpdateMachineMetadata(&triton.UpdateMachineMetadataInput{ + ID: d.Id(), + Metadata: metadata, + }); err != nil { return err } - err = waitFor( - func() (bool, error) { - machine, err := client.GetMachine(d.Id()) + stateConf := &resource.StateChangeConf{ + Target: []string{"converged"}, + Refresh: func() (interface{}, string, error) { + getResp, err := client.Machines().GetMachine(&triton.GetMachineInput{ + ID: d.Id(), + }) + if err != nil { + return nil, "", err + } + for k, v := range metadata { - if provider_v, ok := machine.Metadata[k]; !ok || v != provider_v { - return false, err + if upstream, ok := getResp.Metadata[k]; !ok || v != upstream { + return getResp, "converging", nil } } - return true, err + + return getResp, "converged", nil }, - machineStateChangeCheckInterval, - 1*time.Minute, - ) + Timeout: machineStateChangeTimeout, + MinTimeout: 3 * time.Second, + } + _, err := stateConf.WaitForState() if err != nil { return err } @@ -516,57 +594,43 @@ func resourceMachineUpdate(d *schema.ResourceData, meta interface{}) error { d.Partial(false) - err := resourceMachineRead(d, meta) - if err != nil { - return err - } - - return nil + return resourceMachineRead(d, meta) } func resourceMachineDelete(d *schema.ResourceData, meta interface{}) error { - client := meta.(*cloudapi.Client) + client := meta.(*triton.Client) - state, err := readMachineState(client, d.Id()) - if state != machineStateStopped { - err = client.StopMachine(d.Id()) - if err != nil { - return err - } - - waitForMachineState(client, d.Id(), machineStateStopped, machineStateChangeTimeout) - } - - err = client.DeleteMachine(d.Id()) + err := client.Machines().DeleteMachine(&triton.DeleteMachineInput{ + ID: d.Id(), + }) if err != nil { return err } - waitForMachineState(client, d.Id(), machineStateDeleted, machineStateChangeTimeout) - return nil -} + stateConf := &resource.StateChangeConf{ + Target: []string{machineStateDeleted}, + Refresh: func() (interface{}, string, error) { + getResp, err := client.Machines().GetMachine(&triton.GetMachineInput{ + ID: d.Id(), + }) + if err != nil { + if triton.IsResourceNotFound(err) { + return nil, "deleted", nil + } + return nil, "", err + } -func readMachineState(api *cloudapi.Client, id string) (string, error) { - machine, err := api.GetMachine(id) + return getResp, getResp.State, nil + }, + Timeout: machineStateChangeTimeout, + MinTimeout: 3 * time.Second, + } + _, err = stateConf.WaitForState() if err != nil { - return "", err + return err } - return machine.State, nil -} - -// waitForMachineState waits for a machine to be in the desired state (waiting -// some seconds between each poll). If it doesn't reach the state within the -// duration specified in `timeout`, it returns ErrMachineStateTimeout. -func waitForMachineState(api *cloudapi.Client, id, state string, timeout time.Duration) error { - return waitFor( - func() (bool, error) { - currentState, err := readMachineState(api, id) - return currentState == state, err - }, - machineStateChangeCheckInterval, - machineStateChangeTimeout, - ) + return nil } func resourceMachineValidateName(value interface{}, name string) (warnings []string, errors []error) { @@ -580,7 +644,3 @@ func resourceMachineValidateName(value interface{}, name string) (warnings []str return warnings, errors } - -func resourceMachineImporter(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - return []*schema.ResourceData{d}, nil -} diff --git a/builtin/providers/triton/resource_machine_test.go b/builtin/providers/triton/resource_machine_test.go index f6f43bb77..92152fcdc 100644 --- a/builtin/providers/triton/resource_machine_test.go +++ b/builtin/providers/triton/resource_machine_test.go @@ -2,14 +2,16 @@ package triton import ( "fmt" + "log" "regexp" "testing" "time" + "github.com/davecgh/go-spew/spew" "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" - "github.com/joyent/gosdc/cloudapi" + "github.com/joyent/triton-go" ) func TestAccTritonMachine_basic(t *testing.T) { @@ -21,7 +23,7 @@ func TestAccTritonMachine_basic(t *testing.T) { Providers: testAccProviders, CheckDestroy: testCheckTritonMachineDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: config, Check: resource.ComposeTestCheckFunc( testCheckTritonMachineExists("triton_machine.test"), @@ -44,20 +46,16 @@ func TestAccTritonMachine_dns(t *testing.T) { Providers: testAccProviders, CheckDestroy: testCheckTritonMachineDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: dns_output, Check: resource.ComposeTestCheckFunc( testCheckTritonMachineExists("triton_machine.test"), - func(*terraform.State) error { + func(state *terraform.State) error { time.Sleep(10 * time.Second) + log.Printf("[DEBUG] %s", spew.Sdump(state)) return nil }, - ), - }, - resource.TestStep{ - Config: dns_output, - Check: resource.TestMatchOutput( - "domain_names", regexp.MustCompile(".*acctest-.*"), + resource.TestMatchOutput("domain_names", regexp.MustCompile(".*acctest-.*")), ), }, }, @@ -66,14 +64,14 @@ func TestAccTritonMachine_dns(t *testing.T) { func TestAccTritonMachine_nic(t *testing.T) { machineName := fmt.Sprintf("acctest-%d", acctest.RandInt()) - config := fmt.Sprintf(testAccTritonMachine_withnic, machineName, machineName) + config := testAccTritonMachine_singleNIC(machineName, acctest.RandIntRange(1024, 2048)) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testCheckTritonMachineDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: config, Check: resource.ComposeTestCheckFunc( testCheckTritonMachineExists("triton_machine.test"), @@ -88,32 +86,33 @@ func TestAccTritonMachine_nic(t *testing.T) { }) } -func TestAccTritonMachine_addnic(t *testing.T) { +func TestAccTritonMachine_addNIC(t *testing.T) { machineName := fmt.Sprintf("acctest-%d", acctest.RandInt()) - without := fmt.Sprintf(testAccTritonMachine_withoutnic, machineName, machineName) - with := fmt.Sprintf(testAccTritonMachine_withnic, machineName, machineName) + vlanNumber := acctest.RandIntRange(1024, 2048) + + singleNICConfig := testAccTritonMachine_singleNIC(machineName, vlanNumber) + dualNICConfig := testAccTritonMachine_dualNIC(machineName, vlanNumber) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testCheckTritonMachineDestroy, Steps: []resource.TestStep{ - resource.TestStep{ - Config: without, + { + Config: singleNICConfig, Check: resource.ComposeTestCheckFunc( testCheckTritonMachineExists("triton_machine.test"), func(*terraform.State) error { time.Sleep(10 * time.Second) return nil }, - testCheckTritonMachineHasNoFabric("triton_machine.test", "triton_fabric.test"), ), }, - resource.TestStep{ - Config: with, + { + Config: dualNICConfig, Check: resource.ComposeTestCheckFunc( testCheckTritonMachineExists("triton_machine.test"), - testCheckTritonMachineHasFabric("triton_machine.test", "triton_fabric.test"), + testCheckTritonMachineHasFabric("triton_machine.test", "triton_fabric.test_add"), ), }, }, @@ -127,14 +126,16 @@ func testCheckTritonMachineExists(name string) resource.TestCheckFunc { if !ok { return fmt.Errorf("Not found: %s", name) } - conn := testAccProvider.Meta().(*cloudapi.Client) + conn := testAccProvider.Meta().(*triton.Client) - rule, err := conn.GetMachine(rs.Primary.ID) + machine, err := conn.Machines().GetMachine(&triton.GetMachineInput{ + ID: rs.Primary.ID, + }) if err != nil { return fmt.Errorf("Bad: Check Machine Exists: %s", err) } - if rule == nil { + if machine == nil { return fmt.Errorf("Bad: Machine %q does not exist", rs.Primary.ID) } @@ -154,9 +155,11 @@ func testCheckTritonMachineHasFabric(name, fabricName string) resource.TestCheck if !ok { return fmt.Errorf("Not found: %s", fabricName) } - conn := testAccProvider.Meta().(*cloudapi.Client) + conn := testAccProvider.Meta().(*triton.Client) - nics, err := conn.ListNICs(machine.Primary.ID) + nics, err := conn.Machines().ListNICs(&triton.ListNICsInput{ + MachineID: machine.Primary.ID, + }) if err != nil { return fmt.Errorf("Bad: Check NICs Exist: %s", err) } @@ -171,49 +174,25 @@ func testCheckTritonMachineHasFabric(name, fabricName string) resource.TestCheck } } -func testCheckTritonMachineHasNoFabric(name, fabricName string) resource.TestCheckFunc { - return func(s *terraform.State) error { - // Ensure we have enough information in state to look up in API - machine, ok := s.RootModule().Resources[name] - if !ok { - return fmt.Errorf("Not found: %s", name) - } - - network, ok := s.RootModule().Resources[fabricName] - if !ok { - return fmt.Errorf("Not found: %s", fabricName) - } - conn := testAccProvider.Meta().(*cloudapi.Client) - - nics, err := conn.ListNICs(machine.Primary.ID) - if err != nil { - return fmt.Errorf("Bad: Check NICs Exist: %s", err) - } - - for _, nic := range nics { - if nic.Network == network.Primary.ID { - return fmt.Errorf("Bad: Machine %q has Fabric %q", machine.Primary.ID, network.Primary.ID) - } - } - - return nil - } -} - func testCheckTritonMachineDestroy(s *terraform.State) error { - conn := testAccProvider.Meta().(*cloudapi.Client) + conn := testAccProvider.Meta().(*triton.Client) for _, rs := range s.RootModule().Resources { if rs.Type != "triton_machine" { continue } - resp, err := conn.GetMachine(rs.Primary.ID) + resp, err := conn.Machines().GetMachine(&triton.GetMachineInput{ + ID: rs.Primary.ID, + }) if err != nil { - return nil + if triton.IsResourceNotFound(err) { + return nil + } + return err } - if resp != nil { + if resp != nil && resp.State != machineStateDeleted { return fmt.Errorf("Bad: Machine %q still exists", rs.Primary.ID) } } @@ -231,7 +210,7 @@ func TestAccTritonMachine_firewall(t *testing.T) { Providers: testAccProviders, CheckDestroy: testCheckTritonMachineDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: enabled_config, Check: resource.ComposeTestCheckFunc( testCheckTritonMachineExists("triton_machine.test"), @@ -239,7 +218,7 @@ func TestAccTritonMachine_firewall(t *testing.T) { "triton_machine.test", "firewall_enabled", "true"), ), }, - resource.TestStep{ + { Config: disabled_config, Check: resource.ComposeTestCheckFunc( testCheckTritonMachineExists("triton_machine.test"), @@ -247,7 +226,7 @@ func TestAccTritonMachine_firewall(t *testing.T) { "triton_machine.test", "firewall_enabled", "false"), ), }, - resource.TestStep{ + { Config: enabled_config, Check: resource.ComposeTestCheckFunc( testCheckTritonMachineExists("triton_machine.test"), @@ -271,13 +250,13 @@ func TestAccTritonMachine_metadata(t *testing.T) { Providers: testAccProviders, CheckDestroy: testCheckTritonMachineDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: basic, Check: resource.ComposeTestCheckFunc( testCheckTritonMachineExists("triton_machine.test"), ), }, - resource.TestStep{ + { Config: add_metadata, Check: resource.ComposeTestCheckFunc( testCheckTritonMachineExists("triton_machine.test"), @@ -285,7 +264,7 @@ func TestAccTritonMachine_metadata(t *testing.T) { "triton_machine.test", "user_data", "hello"), ), }, - resource.TestStep{ + { Config: add_metadata_2, Check: resource.ComposeTestCheckFunc( testCheckTritonMachineExists("triton_machine.test"), @@ -294,7 +273,7 @@ func TestAccTritonMachine_metadata(t *testing.T) { "tags.triton.cns.services", "test-cns-service"), ), }, - resource.TestStep{ + { Config: add_metadata_3, Check: resource.ComposeTestCheckFunc( testCheckTritonMachineExists("triton_machine.test"), @@ -311,7 +290,7 @@ var testAccTritonMachine_basic = ` resource "triton_machine" "test" { name = "%s" package = "g4-general-4G" - image = "c20b4b7c-e1a6-11e5-9a4d-ef590901732e" + image = "fb5fe970-e6e4-11e6-9820-4b51be190db9" tags = { test = "hello!" @@ -332,7 +311,7 @@ var testAccTritonMachine_firewall_1 = ` resource "triton_machine" "test" { name = "%s" package = "g4-general-4G" - image = "c20b4b7c-e1a6-11e5-9a4d-ef590901732e" + image = "fb5fe970-e6e4-11e6-9820-4b51be190db9" firewall_enabled = 1 } @@ -361,7 +340,7 @@ variable "tags" { resource "triton_machine" "test" { name = "%s" package = "g4-highcpu-128M" - image = "c20b4b7c-e1a6-11e5-9a4d-ef590901732e" + image = "fb5fe970-e6e4-11e6-9820-4b51be190db9" user_data = "hello" @@ -372,7 +351,7 @@ var testAccTritonMachine_metadata_3 = ` resource "triton_machine" "test" { name = "%s" package = "g4-highcpu-128M" - image = "c20b4b7c-e1a6-11e5-9a4d-ef590901732e" + image = "fb5fe970-e6e4-11e6-9820-4b51be190db9" user_data = "hello" @@ -382,57 +361,91 @@ resource "triton_machine" "test" { } } ` -var testAccTritonMachine_withnic = ` +var testAccTritonMachine_singleNIC = func(name string, vlanNumber int) string { + return fmt.Sprintf(`resource "triton_vlan" "test" { + vlan_id = %d + name = "%s-vlan" + description = "test vlan" +} + resource "triton_fabric" "test" { - name = "%s-network" - description = "test network" - vlan_id = 2 # every DC seems to have a vlan 2 available + name = "%s-network" + description = "test network" + vlan_id = "${triton_vlan.test.vlan_id}" - subnet = "10.0.0.0/22" - gateway = "10.0.0.1" - provision_start_ip = "10.0.0.5" - provision_end_ip = "10.0.3.250" + subnet = "10.10.0.0/24" + gateway = "10.10.0.1" + provision_start_ip = "10.10.0.10" + provision_end_ip = "10.10.0.250" - resolvers = ["8.8.8.8", "8.8.4.4"] + resolvers = ["8.8.8.8", "8.8.4.4"] } resource "triton_machine" "test" { - name = "%s" - package = "g4-general-4G" - image = "842e6fa6-6e9b-11e5-8402-1b490459e334" + name = "%s-instance" + package = "g4-highcpu-128M" + image = "fb5fe970-e6e4-11e6-9820-4b51be190db9" - tags = { - test = "hello!" + tags = { + test = "Test" } - nic { network = "${triton_fabric.test.id}" } + nic { + network = "${triton_fabric.test.id}" + } +}`, vlanNumber, name, name, name) +} + +var testAccTritonMachine_dualNIC = func(name string, vlanNumber int) string { + return fmt.Sprintf(`resource "triton_vlan" "test" { + vlan_id = %d + name = "%s-vlan" + description = "test vlan" } -` -var testAccTritonMachine_withoutnic = ` resource "triton_fabric" "test" { - name = "%s-network" - description = "test network" - vlan_id = 2 # every DC seems to have a vlan 2 available + name = "%s-network" + description = "test network" + vlan_id = "${triton_vlan.test.vlan_id}" - subnet = "10.0.0.0/22" - gateway = "10.0.0.1" - provision_start_ip = "10.0.0.5" - provision_end_ip = "10.0.3.250" + subnet = "10.10.0.0/24" + gateway = "10.10.0.1" + provision_start_ip = "10.10.0.10" + provision_end_ip = "10.10.0.250" - resolvers = ["8.8.8.8", "8.8.4.4"] + resolvers = ["8.8.8.8", "8.8.4.4"] +} + +resource "triton_fabric" "test_add" { + name = "%s-network-2" + description = "test network 2" + vlan_id = "${triton_vlan.test.vlan_id}" + + subnet = "172.23.0.0/24" + gateway = "172.23.0.1" + provision_start_ip = "172.23.0.10" + provision_end_ip = "172.23.0.250" + + resolvers = ["8.8.8.8", "8.8.4.4"] } resource "triton_machine" "test" { - name = "%s" - package = "g4-general-4G" - image = "842e6fa6-6e9b-11e5-8402-1b490459e334" + name = "%s-instance" + package = "g4-highcpu-128M" + image = "fb5fe970-e6e4-11e6-9820-4b51be190db9" - tags = { - test = "hello!" + tags = { + test = "Test" } + + nic { + network = "${triton_fabric.test.id}" + } + nic { + network = "${triton_fabric.test_add.id}" + } +}`, vlanNumber, name, name, name, name) } -` var testAccTritonMachine_dns = ` provider "triton" { @@ -441,8 +454,9 @@ provider "triton" { resource "triton_machine" "test" { name = "%s" package = "g4-highcpu-128M" - image = "e1faace4-e19b-11e5-928b-83849e2fd94a" + image = "fb5fe970-e6e4-11e6-9820-4b51be190db9" } + output "domain_names" { value = "${join(", ", triton_machine.test.domain_names)}" } diff --git a/builtin/providers/triton/resource_vlan.go b/builtin/providers/triton/resource_vlan.go index 34eb5d67c..6858d3415 100644 --- a/builtin/providers/triton/resource_vlan.go +++ b/builtin/providers/triton/resource_vlan.go @@ -5,23 +5,24 @@ import ( "strconv" "github.com/hashicorp/terraform/helper/schema" - "github.com/joyent/gosdc/cloudapi" + "github.com/joyent/triton-go" ) func resourceVLAN() *schema.Resource { return &schema.Resource{ - Create: resourceVLANCreate, - Exists: resourceVLANExists, - Read: resourceVLANRead, - Update: resourceVLANUpdate, - Delete: resourceVLANDelete, + Create: resourceVLANCreate, + Exists: resourceVLANExists, + Read: resourceVLANRead, + Update: resourceVLANUpdate, + Delete: resourceVLANDelete, + Timeouts: fastResourceTimeout, Importer: &schema.ResourceImporter{ - State: resourceVLANImporter, + State: schema.ImportStatePassthrough, }, Schema: map[string]*schema.Schema{ "vlan_id": { - Description: "number between 0-4095 indicating VLAN ID", + Description: "Number between 0-4095 indicating VLAN ID", Required: true, ForceNew: true, Type: schema.TypeInt, @@ -39,7 +40,7 @@ func resourceVLAN() *schema.Resource { Type: schema.TypeString, }, "description": { - Description: "Optional description of the VLAN", + Description: "Description of the VLAN", Optional: true, Type: schema.TypeString, }, @@ -48,10 +49,10 @@ func resourceVLAN() *schema.Resource { } func resourceVLANCreate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*cloudapi.Client) + client := meta.(*triton.Client) - vlan, err := client.CreateFabricVLAN(cloudapi.FabricVLAN{ - Id: int16(d.Get("vlan_id").(int)), + vlan, err := client.Fabrics().CreateFabricVLAN(&triton.CreateFabricVLANInput{ + ID: d.Get("vlan_id").(int), Name: d.Get("name").(string), Description: d.Get("description").(string), }) @@ -59,33 +60,39 @@ func resourceVLANCreate(d *schema.ResourceData, meta interface{}) error { return err } - d.SetId(resourceVLANIDString(vlan.Id)) + d.SetId(strconv.Itoa(vlan.ID)) return resourceVLANRead(d, meta) } func resourceVLANExists(d *schema.ResourceData, meta interface{}) (bool, error) { - client := meta.(*cloudapi.Client) + client := meta.(*triton.Client) - id, err := resourceVLANIDInt16(d.Id()) + id, err := resourceVLANIDInt(d.Id()) if err != nil { return false, err } - vlan, err := client.GetFabricVLAN(id) - - return vlan != nil && err == nil, err + return resourceExists(client.Fabrics().GetFabricVLAN(&triton.GetFabricVLANInput{ + ID: id, + })) } func resourceVLANRead(d *schema.ResourceData, meta interface{}) error { - client := meta.(*cloudapi.Client) + client := meta.(*triton.Client) - vlan, err := client.GetFabricVLAN(int16(d.Get("vlan_id").(int))) + id, err := resourceVLANIDInt(d.Id()) if err != nil { return err } - d.SetId(resourceVLANIDString(vlan.Id)) - d.Set("vlan_id", vlan.Id) + vlan, err := client.Fabrics().GetFabricVLAN(&triton.GetFabricVLANInput{ + ID: id, + }) + if err != nil { + return err + } + + d.Set("vlan_id", vlan.ID) d.Set("name", vlan.Name) d.Set("description", vlan.Description) @@ -93,10 +100,10 @@ func resourceVLANRead(d *schema.ResourceData, meta interface{}) error { } func resourceVLANUpdate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*cloudapi.Client) + client := meta.(*triton.Client) - vlan, err := client.UpdateFabricVLAN(cloudapi.FabricVLAN{ - Id: int16(d.Get("vlan_id").(int)), + vlan, err := client.Fabrics().UpdateFabricVLAN(&triton.UpdateFabricVLANInput{ + ID: d.Get("vlan_id").(int), Name: d.Get("name").(string), Description: d.Get("description").(string), }) @@ -104,36 +111,28 @@ func resourceVLANUpdate(d *schema.ResourceData, meta interface{}) error { return err } - d.SetId(resourceVLANIDString(vlan.Id)) + d.SetId(strconv.Itoa(vlan.ID)) return resourceVLANRead(d, meta) } func resourceVLANDelete(d *schema.ResourceData, meta interface{}) error { - client := meta.(*cloudapi.Client) + client := meta.(*triton.Client) - id, err := resourceVLANIDInt16(d.Id()) + id, err := resourceVLANIDInt(d.Id()) if err != nil { return err } - return client.DeleteFabricVLAN(id) + return client.Fabrics().DeleteFabricVLAN(&triton.DeleteFabricVLANInput{ + ID: id, + }) } -// convenience conversion functions - -func resourceVLANIDString(id int16) string { - return strconv.Itoa(int(id)) -} - -func resourceVLANIDInt16(id string) (int16, error) { - result, err := strconv.ParseInt(id, 10, 16) +func resourceVLANIDInt(id string) (int, error) { + result, err := strconv.ParseInt(id, 10, 32) if err != nil { - return 0, err + return -1, err } - return int16(result), nil -} - -func resourceVLANImporter(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - return []*schema.ResourceData{d}, nil + return int(result), nil } diff --git a/builtin/providers/triton/resource_vlan_test.go b/builtin/providers/triton/resource_vlan_test.go index 91333aed6..4b734d0d6 100644 --- a/builtin/providers/triton/resource_vlan_test.go +++ b/builtin/providers/triton/resource_vlan_test.go @@ -2,22 +2,24 @@ package triton import ( "fmt" + "strconv" "testing" + "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" - "github.com/joyent/gosdc/cloudapi" + "github.com/joyent/triton-go" ) func TestAccTritonVLAN_basic(t *testing.T) { - config := testAccTritonVLAN_basic + config := testAccTritonVLAN_basic(acctest.RandIntRange(3, 2048)) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testCheckTritonVLANDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: config, Check: resource.ComposeTestCheckFunc( testCheckTritonVLANExists("triton_vlan.test"), @@ -28,27 +30,30 @@ func TestAccTritonVLAN_basic(t *testing.T) { } func TestAccTritonVLAN_update(t *testing.T) { - preConfig := testAccTritonVLAN_basic - postConfig := testAccTritonVLAN_update + vlanNumber := acctest.RandIntRange(3, 2048) + preConfig := testAccTritonVLAN_basic(vlanNumber) + postConfig := testAccTritonVLAN_update(vlanNumber) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testCheckTritonVLANDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: preConfig, Check: resource.ComposeTestCheckFunc( testCheckTritonVLANExists("triton_vlan.test"), + resource.TestCheckResourceAttr("triton_vlan.test", "vlan_id", strconv.Itoa(vlanNumber)), resource.TestCheckResourceAttr("triton_vlan.test", "name", "test-vlan"), resource.TestCheckResourceAttr("triton_vlan.test", "description", "test vlan"), ), }, - resource.TestStep{ + { Config: postConfig, Check: resource.ComposeTestCheckFunc( testCheckTritonVLANExists("triton_vlan.test"), + resource.TestCheckResourceAttr("triton_vlan.test", "vlan_id", strconv.Itoa(vlanNumber)), resource.TestCheckResourceAttr("triton_vlan.test", "name", "test-vlan-2"), resource.TestCheckResourceAttr("triton_vlan.test", "description", "test vlan 2"), ), @@ -64,19 +69,23 @@ func testCheckTritonVLANExists(name string) resource.TestCheckFunc { if !ok { return fmt.Errorf("Not found: %s", name) } - conn := testAccProvider.Meta().(*cloudapi.Client) + conn := testAccProvider.Meta().(*triton.Client) - id, err := resourceVLANIDInt16(rs.Primary.ID) + id, err := resourceVLANIDInt(rs.Primary.ID) if err != nil { return err } - rule, err := conn.GetFabricVLAN(id) - if err != nil { + resp, err := conn.Fabrics().GetFabricVLAN(&triton.GetFabricVLANInput{ + ID: id, + }) + if err != nil && triton.IsResourceNotFound(err) { return fmt.Errorf("Bad: Check VLAN Exists: %s", err) + } else if err != nil { + return err } - if rule == nil { + if resp == nil { return fmt.Errorf("Bad: VLAN %q does not exist", rs.Primary.ID) } @@ -85,21 +94,25 @@ func testCheckTritonVLANExists(name string) resource.TestCheckFunc { } func testCheckTritonVLANDestroy(s *terraform.State) error { - conn := testAccProvider.Meta().(*cloudapi.Client) + conn := testAccProvider.Meta().(*triton.Client) for _, rs := range s.RootModule().Resources { if rs.Type != "triton_vlan" { continue } - id, err := resourceVLANIDInt16(rs.Primary.ID) + id, err := resourceVLANIDInt(rs.Primary.ID) if err != nil { return err } - resp, err := conn.GetFabricVLAN(id) - if err != nil { + resp, err := conn.Fabrics().GetFabricVLAN(&triton.GetFabricVLANInput{ + ID: id, + }) + if triton.IsResourceNotFound(err) { return nil + } else if err != nil { + return err } if resp != nil { @@ -110,18 +123,18 @@ func testCheckTritonVLANDestroy(s *terraform.State) error { return nil } -var testAccTritonVLAN_basic = ` -resource "triton_vlan" "test" { - vlan_id = 1024 - name = "test-vlan" - description = "test vlan" +var testAccTritonVLAN_basic = func(vlanID int) string { + return fmt.Sprintf(`resource "triton_vlan" "test" { + vlan_id = %d + name = "test-vlan" + description = "test vlan" + }`, vlanID) } -` -var testAccTritonVLAN_update = ` -resource "triton_vlan" "test" { - vlan_id = 1024 - name = "test-vlan-2" - description = "test vlan 2" +var testAccTritonVLAN_update = func(vlanID int) string { + return fmt.Sprintf(`resource "triton_vlan" "test" { + vlan_id = %d + name = "test-vlan-2" + description = "test vlan 2" + }`, vlanID) } -` diff --git a/builtin/providers/triton/utils.go b/builtin/providers/triton/utils.go deleted file mode 100644 index ef8ae23cb..000000000 --- a/builtin/providers/triton/utils.go +++ /dev/null @@ -1,30 +0,0 @@ -package triton - -import ( - "errors" - "time" -) - -var ( - // ErrTimeout is returned when waiting for state change - ErrTimeout = errors.New("timed out while waiting for resource change") -) - -func waitFor(f func() (bool, error), every, timeout time.Duration) error { - start := time.Now() - - for time.Since(start) <= timeout { - stop, err := f() - if err != nil { - return err - } - - if stop { - return nil - } - - time.Sleep(every) - } - - return ErrTimeout -} diff --git a/vendor/github.com/joyent/gosdc/cloudapi/cloudapi.go b/vendor/github.com/joyent/gosdc/cloudapi/cloudapi.go deleted file mode 100644 index 2f7c406ac..000000000 --- a/vendor/github.com/joyent/gosdc/cloudapi/cloudapi.go +++ /dev/null @@ -1,127 +0,0 @@ -/* -Package cloudapi interacts with the Cloud API (http://apidocs.joyent.com/cloudapi/). - -Licensed under the Mozilla Public License version 2.0 - -Copyright (c) Joyent Inc. -*/ -package cloudapi - -import ( - "net/http" - "net/url" - "path" - - "github.com/joyent/gocommon/client" - jh "github.com/joyent/gocommon/http" -) - -const ( - // DefaultAPIVersion defines the default version of the Cloud API to use - DefaultAPIVersion = "~7.3" - - // CloudAPI URL parts - apiKeys = "keys" - apiPackages = "packages" - apiImages = "images" - apiDatacenters = "datacenters" - apiMachines = "machines" - apiMetadata = "metadata" - apiSnapshots = "snapshots" - apiTags = "tags" - apiAnalytics = "analytics" - apiInstrumentations = "instrumentations" - apiInstrumentationsValue = "value" - apiInstrumentationsRaw = "raw" - apiInstrumentationsHeatmap = "heatmap" - apiInstrumentationsImage = "image" - apiInstrumentationsDetails = "details" - apiUsage = "usage" - apiAudit = "audit" - apiFirewallRules = "fwrules" - apiFirewallRulesEnable = "enable" - apiFirewallRulesDisable = "disable" - apiNetworks = "networks" - apiFabricVLANs = "fabrics/default/vlans" - apiFabricNetworks = "networks" - apiNICs = "nics" - apiServices = "services" - - // CloudAPI actions - actionExport = "export" - actionStop = "stop" - actionStart = "start" - actionReboot = "reboot" - actionResize = "resize" - actionRename = "rename" - actionEnableFw = "enable_firewall" - actionDisableFw = "disable_firewall" -) - -// Client provides a means to access the Joyent CloudAPI -type Client struct { - client client.Client -} - -// New creates a new Client. -func New(client client.Client) *Client { - return &Client{client} -} - -// Filter represents a filter that can be applied to an API request. -type Filter struct { - v url.Values -} - -// NewFilter creates a new Filter. -func NewFilter() *Filter { - return &Filter{make(url.Values)} -} - -// Set a value for the specified filter. -func (f *Filter) Set(filter, value string) { - f.v.Set(filter, value) -} - -// Add a value for the specified filter. -func (f *Filter) Add(filter, value string) { - f.v.Add(filter, value) -} - -// request represents an API request -type request struct { - method string - url string - filter *Filter - reqValue interface{} - reqHeader http.Header - resp interface{} - respHeader *http.Header - expectedStatus int -} - -// Helper method to send an API request -func (c *Client) sendRequest(req request) (*jh.ResponseData, error) { - request := jh.RequestData{ - ReqValue: req.reqValue, - ReqHeaders: req.reqHeader, - } - if req.filter != nil { - request.Params = &req.filter.v - } - if req.expectedStatus == 0 { - req.expectedStatus = http.StatusOK - } - respData := jh.ResponseData{ - RespValue: req.resp, - RespHeaders: req.respHeader, - ExpectedStatus: []int{req.expectedStatus}, - } - err := c.client.SendRequest(req.method, req.url, "", &request, &respData) - return &respData, err -} - -// Helper method to create the API URL -func makeURL(parts ...string) string { - return path.Join(parts...) -} diff --git a/vendor/github.com/joyent/gosdc/cloudapi/datacenters.go b/vendor/github.com/joyent/gosdc/cloudapi/datacenters.go deleted file mode 100644 index e2bddf954..000000000 --- a/vendor/github.com/joyent/gosdc/cloudapi/datacenters.go +++ /dev/null @@ -1,41 +0,0 @@ -package cloudapi - -import ( - "net/http" - - "github.com/joyent/gocommon/client" - "github.com/joyent/gocommon/errors" -) - -// ListDatacenters provides a list of all datacenters this cloud is aware of. -// See API docs: http://apidocs.joyent.com/cloudapi/#ListDatacenters -func (c *Client) ListDatacenters() (map[string]interface{}, error) { - var resp map[string]interface{} - req := request{ - method: client.GET, - url: apiDatacenters, - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get list of datcenters") - } - return resp, nil -} - -// GetDatacenter gets an individual datacenter by name. Returns an HTTP redirect -// to your client, the datacenter URL is in the Location header. -// See API docs: http://apidocs.joyent.com/cloudapi/#GetDatacenter -func (c *Client) GetDatacenter(datacenterName string) (string, error) { - var respHeader http.Header - req := request{ - method: client.GET, - url: makeURL(apiDatacenters, datacenterName), - respHeader: &respHeader, - expectedStatus: http.StatusFound, - } - respData, err := c.sendRequest(req) - if err != nil { - return "", errors.Newf(err, "failed to get datacenter with name: %s", datacenterName) - } - return respData.RespHeaders.Get("Location"), nil -} diff --git a/vendor/github.com/joyent/gosdc/cloudapi/fabrics.go b/vendor/github.com/joyent/gosdc/cloudapi/fabrics.go deleted file mode 100644 index cc36a7b3d..000000000 --- a/vendor/github.com/joyent/gosdc/cloudapi/fabrics.go +++ /dev/null @@ -1,182 +0,0 @@ -package cloudapi - -import ( - "net/http" - "strconv" - - "github.com/joyent/gocommon/client" - "github.com/joyent/gocommon/errors" -) - -type FabricVLAN struct { - Id int16 `json:"vlan_id"` // Number between 0-4095 indicating VLAN Id - Name string `json:"name"` // Unique name to identify VLAN - Description string `json:"description,omitempty"` // Optional description of the VLAN -} - -type FabricNetwork struct { - Id string `json:"id"` // Unique identifier for network - Name string `json:"name"` // Network name - Public bool `json:"public"` // Whether or not this is an RFC1918 network - Fabric bool `json:"fabric"` // Whether this network is on a fabric - Description string `json:"description"` // Optional description of network - Subnet string `json:"subnet"` // CIDR formatted string describing network - ProvisionStartIp string `json:"provision_start_ip"` // First IP on the network that can be assigned - ProvisionEndIp string `json:"provision_end_ip"` // Last assignable IP on the network - Gateway string `json:"gateway"` // Optional Gateway IP - Resolvers []string `json:"resolvers,omitempty"` // Array of IP addresses for resolvers - Routes map[string]string `json:"routes,omitempty"` // Map of CIDR block to Gateway IP Address - InternetNAT bool `json:"internet_nat"` // If a NAT zone is provisioned at Gateway IP Address - VLANId int16 `json:"vlan_id"` // VLAN network is on -} - -type CreateFabricNetworkOpts struct { - Name string `json:"name"` // Network name - Description string `json:"description,omitempty"` // Optional description of network - Subnet string `json:"subnet"` // CIDR formatted string describing network - ProvisionStartIp string `json:"provision_start_ip"` // First IP on the network that can be assigned - ProvisionEndIp string `json:"provision_end_ip"` // Last assignable IP on the network - Gateway string `json:"gateway,omitempty"` // Optional Gateway IP - Resolvers []string `json:"resolvers,omitempty"` // Array of IP addresses for resolvers - Routes map[string]string `json:"routes,omitempty"` // Map of CIDR block to Gateway IP Address - InternetNAT bool `json:"internet_nat"` // If a NAT zone is provisioned at Gateway IP Address -} - -// ListFabricVLANs lists VLANs -// See API docs: https://apidocs.joyent.com/cloudapi/#ListFabricVLANs -func (c *Client) ListFabricVLANs() ([]FabricVLAN, error) { - var resp []FabricVLAN - req := request{ - method: client.GET, - url: apiFabricVLANs, - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get list of fabric VLANs") - } - return resp, nil -} - -// GetFabricLAN retrieves a single VLAN by ID -// See API docs: https://apidocs.joyent.com/cloudapi/#GetFabricVLAN -func (c *Client) GetFabricVLAN(vlanID int16) (*FabricVLAN, error) { - var resp FabricVLAN - req := request{ - method: client.GET, - url: makeURL(apiFabricVLANs, strconv.Itoa(int(vlanID))), - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get fabric VLAN with id %d", vlanID) - } - return &resp, nil -} - -// CreateFabricVLAN creates a new VLAN with the specified options -// See API docs: https://apidocs.joyent.com/cloudapi/#CreateFabricVLAN -func (c *Client) CreateFabricVLAN(vlan FabricVLAN) (*FabricVLAN, error) { - var resp FabricVLAN - req := request{ - method: client.POST, - url: apiFabricVLANs, - reqValue: vlan, - resp: &resp, - expectedStatus: http.StatusCreated, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to create fabric VLAN: %d - %s", vlan.Id, vlan.Name) - } - return &resp, nil -} - -// UpdateFabricVLAN updates a given VLAN with new fields -// See API docs: https://apidocs.joyent.com/cloudapi/#UpdateFabricVLAN -func (c *Client) UpdateFabricVLAN(vlan FabricVLAN) (*FabricVLAN, error) { - var resp FabricVLAN - req := request{ - method: client.PUT, - url: makeURL(apiFabricVLANs, strconv.Itoa(int(vlan.Id))), - reqValue: vlan, - resp: &resp, - expectedStatus: http.StatusAccepted, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to update fabric VLAN with id %d to %s - %s", vlan.Id, vlan.Name, vlan.Description) - } - return &resp, nil -} - -// DeleteFabricVLAN delets a given VLAN as specified by ID -// See API docs: https://apidocs.joyent.com/cloudapi/#DeleteFabricVLAN -func (c *Client) DeleteFabricVLAN(vlanID int16) error { - req := request{ - method: client.DELETE, - url: makeURL(apiFabricVLANs, strconv.Itoa(int(vlanID))), - expectedStatus: http.StatusNoContent, - } - if _, err := c.sendRequest(req); err != nil { - return errors.Newf(err, "failed to delete fabric VLAN with id %d", vlanID) - } - return nil -} - -// ListFabricNetworks lists the networks inside the given VLAN -// See API docs: https://apidocs.joyent.com/cloudapi/#ListFabricNetworks -func (c *Client) ListFabricNetworks(vlanID int16) ([]FabricNetwork, error) { - var resp []FabricNetwork - req := request{ - method: client.GET, - url: makeURL(apiFabricVLANs, strconv.Itoa(int(vlanID)), apiFabricNetworks), - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get list of networks on fabric %d", vlanID) - } - return resp, nil -} - -// GetFabricNetwork gets a single network by VLAN and Network IDs -// See API docs: https://apidocs.joyent.com/cloudapi/#GetFabricNetwork -func (c *Client) GetFabricNetwork(vlanID int16, networkID string) (*FabricNetwork, error) { - var resp FabricNetwork - req := request{ - method: client.GET, - url: makeURL(apiFabricVLANs, strconv.Itoa(int(vlanID)), apiFabricNetworks, networkID), - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get fabric network %s on vlan %d", networkID, vlanID) - } - return &resp, nil -} - -// CreateFabricNetwork creates a new fabric network -// See API docs: https://apidocs.joyent.com/cloudapi/#CreateFabricNetwork -func (c *Client) CreateFabricNetwork(vlanID int16, opts CreateFabricNetworkOpts) (*FabricNetwork, error) { - var resp FabricNetwork - req := request{ - method: client.POST, - url: makeURL(apiFabricVLANs, strconv.Itoa(int(vlanID)), apiFabricNetworks), - reqValue: opts, - resp: &resp, - expectedStatus: http.StatusCreated, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to create fabric network %s on vlan %d", opts.Name, vlanID) - } - return &resp, nil -} - -// DeleteFabricNetwork deletes an existing fabric network -// See API docs: https://apidocs.joyent.com/cloudapi/#DeleteFabricNetwork -func (c *Client) DeleteFabricNetwork(vlanID int16, networkID string) error { - req := request{ - method: client.DELETE, - url: makeURL(apiFabricVLANs, strconv.Itoa(int(vlanID)), apiFabricNetworks, networkID), - expectedStatus: http.StatusNoContent, - } - if _, err := c.sendRequest(req); err != nil { - return errors.Newf(err, "failed to delete fabric network %s on vlan %d", networkID, vlanID) - } - return nil -} diff --git a/vendor/github.com/joyent/gosdc/cloudapi/firewalls.go b/vendor/github.com/joyent/gosdc/cloudapi/firewalls.go deleted file mode 100644 index a7763a668..000000000 --- a/vendor/github.com/joyent/gosdc/cloudapi/firewalls.go +++ /dev/null @@ -1,144 +0,0 @@ -package cloudapi - -import ( - "net/http" - - "github.com/joyent/gocommon/client" - "github.com/joyent/gocommon/errors" -) - -// FirewallRule represent a firewall rule that can be specifed for a machine. -type FirewallRule struct { - Id string // Unique identifier for the rule - Enabled bool // Whether the rule is enabled or not - Rule string // Firewall rule in the form 'FROM TO ' -} - -// CreateFwRuleOpts represent the option that can be specified -// when creating a new firewall rule. -type CreateFwRuleOpts struct { - Enabled bool `json:"enabled"` // Whether to enable the rule or not - Rule string `json:"rule"` // Firewall rule in the form 'FROM TO ' -} - -// ListFirewallRules lists all the firewall rules on record for a specified account. -// See API docs: http://apidocs.joyent.com/cloudapi/#ListFirewallRules -func (c *Client) ListFirewallRules() ([]FirewallRule, error) { - var resp []FirewallRule - req := request{ - method: client.GET, - url: apiFirewallRules, - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get list of firewall rules") - } - return resp, nil -} - -// GetFirewallRule returns the specified firewall rule. -// See API docs: http://apidocs.joyent.com/cloudapi/#GetFirewallRule -func (c *Client) GetFirewallRule(fwRuleID string) (*FirewallRule, error) { - var resp FirewallRule - req := request{ - method: client.GET, - url: makeURL(apiFirewallRules, fwRuleID), - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get firewall rule with id %s", fwRuleID) - } - return &resp, nil -} - -// CreateFirewallRule creates the firewall rule with the specified options. -// See API docs: http://apidocs.joyent.com/cloudapi/#CreateFirewallRule -func (c *Client) CreateFirewallRule(opts CreateFwRuleOpts) (*FirewallRule, error) { - var resp FirewallRule - req := request{ - method: client.POST, - url: apiFirewallRules, - reqValue: opts, - resp: &resp, - expectedStatus: http.StatusCreated, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to create firewall rule: %s", opts.Rule) - } - return &resp, nil -} - -// UpdateFirewallRule updates the specified firewall rule. -// See API docs: http://apidocs.joyent.com/cloudapi/#UpdateFirewallRule -func (c *Client) UpdateFirewallRule(fwRuleID string, opts CreateFwRuleOpts) (*FirewallRule, error) { - var resp FirewallRule - req := request{ - method: client.POST, - url: makeURL(apiFirewallRules, fwRuleID), - reqValue: opts, - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to update firewall rule with id %s to %s", fwRuleID, opts.Rule) - } - return &resp, nil -} - -// EnableFirewallRule enables the given firewall rule record if it is disabled. -// See API docs: http://apidocs.joyent.com/cloudapi/#EnableFirewallRule -func (c *Client) EnableFirewallRule(fwRuleID string) (*FirewallRule, error) { - var resp FirewallRule - req := request{ - method: client.POST, - url: makeURL(apiFirewallRules, fwRuleID, apiFirewallRulesEnable), - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to enable firewall rule with id %s", fwRuleID) - } - return &resp, nil -} - -// DisableFirewallRule disables the given firewall rule record if it is enabled. -// See API docs: http://apidocs.joyent.com/cloudapi/#DisableFirewallRule -func (c *Client) DisableFirewallRule(fwRuleID string) (*FirewallRule, error) { - var resp FirewallRule - req := request{ - method: client.POST, - url: makeURL(apiFirewallRules, fwRuleID, apiFirewallRulesDisable), - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to disable firewall rule with id %s", fwRuleID) - } - return &resp, nil -} - -// DeleteFirewallRule removes the given firewall rule record from all the required account machines. -// See API docs: http://apidocs.joyent.com/cloudapi/#DeleteFirewallRule -func (c *Client) DeleteFirewallRule(fwRuleID string) error { - req := request{ - method: client.DELETE, - url: makeURL(apiFirewallRules, fwRuleID), - expectedStatus: http.StatusNoContent, - } - if _, err := c.sendRequest(req); err != nil { - return errors.Newf(err, "failed to delete firewall rule with id %s", fwRuleID) - } - return nil -} - -// ListFirewallRuleMachines return the list of machines affected by the given firewall rule. -// See API docs: http://apidocs.joyent.com/cloudapi/#ListFirewallRuleMachines -func (c *Client) ListFirewallRuleMachines(fwRuleID string) ([]Machine, error) { - var resp []Machine - req := request{ - method: client.GET, - url: makeURL(apiFirewallRules, fwRuleID, apiMachines), - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get list of machines affected by firewall rule wit id %s", fwRuleID) - } - return resp, nil -} diff --git a/vendor/github.com/joyent/gosdc/cloudapi/images.go b/vendor/github.com/joyent/gosdc/cloudapi/images.go deleted file mode 100644 index c7f9a2fe3..000000000 --- a/vendor/github.com/joyent/gosdc/cloudapi/images.go +++ /dev/null @@ -1,133 +0,0 @@ -package cloudapi - -import ( - "fmt" - "net/http" - - "github.com/joyent/gocommon/client" - "github.com/joyent/gocommon/errors" -) - -// Image represent the software packages that will be available on newly provisioned machines -type Image struct { - Id string // Unique identifier for the image - Name string // Image friendly name - OS string // Underlying operating system - Version string // Image version - Type string // Image type, one of 'smartmachine' or 'virtualmachine' - Description string // Image description - Requirements map[string]interface{} // Minimum requirements for provisioning a machine with this image, e.g. 'password' indicates that a password must be provided - Homepage string // URL for a web page including detailed information for this image (new in API version 7.0) - PublishedAt string `json:"published_at"` // Time this image has been made publicly available (new in API version 7.0) - Public bool // Indicates if the image is publicly available (new in API version 7.1) - State string // Current image state. One of 'active', 'unactivated', 'disabled', 'creating', 'failed' (new in API version 7.1) - Tags map[string]string // A map of key/value pairs that allows clients to categorize images by any given criteria (new in API version 7.1) - EULA string // URL of the End User License Agreement (EULA) for the image (new in API version 7.1) - ACL []string // An array of account UUIDs given access to a private image. The field is only relevant to private images (new in API version 7.1) - Owner string // The UUID of the user owning the image -} - -// ExportImageOpts represent the option that can be specified -// when exporting an image. -type ExportImageOpts struct { - MantaPath string `json:"manta_path"` // The Manta path prefix to use when exporting the image -} - -// MantaLocation represent the properties that allow a user -// to retrieve the image file and manifest from Manta -type MantaLocation struct { - MantaURL string `json:"manta_url"` // Manta datacenter URL - ImagePath string `json:"image_path"` // Path to the image - ManifestPath string `json:"manifest_path"` // Path to the image manifest -} - -// CreateImageFromMachineOpts represent the option that can be specified -// when creating a new image from an existing machine. -type CreateImageFromMachineOpts struct { - Machine string `json:"machine"` // The machine UUID from which the image is to be created - Name string `json:"name"` // Image name - Version string `json:"version"` // Image version - Description string `json:"description,omitempty"` // Image description - Homepage string `json:"homepage,omitempty"` // URL for a web page including detailed information for this image - EULA string `json:"eula,omitempty"` // URL of the End User License Agreement (EULA) for the image - ACL []string `json:"acl,omitempty"` // An array of account UUIDs given access to a private image. The field is only relevant to private images - Tags map[string]string `json:"tags,omitempty"` // A map of key/value pairs that allows clients to categorize images by any given criteria -} - -// ListImages provides a list of images available in the datacenter. -// See API docs: http://apidocs.joyent.com/cloudapi/#ListImages -func (c *Client) ListImages(filter *Filter) ([]Image, error) { - var resp []Image - req := request{ - method: client.GET, - url: apiImages, - filter: filter, - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get list of images") - } - return resp, nil -} - -// GetImage returns the image specified by imageId. -// See API docs: http://apidocs.joyent.com/cloudapi/#GetImage -func (c *Client) GetImage(imageID string) (*Image, error) { - var resp Image - req := request{ - method: client.GET, - url: makeURL(apiImages, imageID), - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get image with id: %s", imageID) - } - return &resp, nil -} - -// DeleteImage (Beta) Delete the image specified by imageId. Must be image owner to do so. -// See API docs: http://apidocs.joyent.com/cloudapi/#DeleteImage -func (c *Client) DeleteImage(imageID string) error { - req := request{ - method: client.DELETE, - url: makeURL(apiImages, imageID), - expectedStatus: http.StatusNoContent, - } - if _, err := c.sendRequest(req); err != nil { - return errors.Newf(err, "failed to delete image with id: %s", imageID) - } - return nil -} - -// ExportImage (Beta) Exports an image to the specified Manta path. -// See API docs: http://apidocs.joyent.com/cloudapi/#ListImages -func (c *Client) ExportImage(imageID string, opts ExportImageOpts) (*MantaLocation, error) { - var resp MantaLocation - req := request{ - method: client.POST, - url: fmt.Sprintf("%s/%s?action=%s", apiImages, imageID, actionExport), - reqValue: opts, - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to export image %s to %s", imageID, opts.MantaPath) - } - return &resp, nil -} - -// CreateImageFromMachine (Beta) Create a new custom image from a machine. -// See API docs: http://apidocs.joyent.com/cloudapi/#ListImages -func (c *Client) CreateImageFromMachine(opts CreateImageFromMachineOpts) (*Image, error) { - var resp Image - req := request{ - method: client.POST, - url: apiImages, - reqValue: opts, - resp: &resp, - expectedStatus: http.StatusCreated, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to create image from machine %s", opts.Machine) - } - return &resp, nil -} diff --git a/vendor/github.com/joyent/gosdc/cloudapi/instrumentations.go b/vendor/github.com/joyent/gosdc/cloudapi/instrumentations.go deleted file mode 100644 index 1dcd32777..000000000 --- a/vendor/github.com/joyent/gosdc/cloudapi/instrumentations.go +++ /dev/null @@ -1,216 +0,0 @@ -package cloudapi - -import ( - "net/http" - - "github.com/joyent/gocommon/client" - "github.com/joyent/gocommon/errors" -) - -// Analytics represents the available analytics -type Analytics struct { - Modules map[string]interface{} // Namespace to organize metrics - Fields map[string]interface{} // Fields represent metadata by which data points can be filtered or decomposed - Types map[string]interface{} // Types are used with both metrics and fields for two purposes: to hint to clients at how to best label values, and to distinguish between numeric and discrete quantities. - Metrics map[string]interface{} // Metrics describe quantities which can be measured by the system - Transformations map[string]interface{} // Transformations are post-processing functions that can be applied to data when it's retrieved. -} - -// Instrumentation specify which metric to collect, how frequently to aggregate data (e.g., every second, every hour, etc.) -// how much data to keep (e.g., 10 minutes' worth, 6 months' worth, etc.) and other configuration options -type Instrumentation struct { - Module string `json:"module"` - Stat string `json:"stat"` - Predicate string `json:"predicate"` - Decomposition []string `json:"decomposition"` - ValueDimension int `json:"value-dimenstion"` - ValueArity string `json:"value-arity"` - RetentionTime int `json:"retention-time"` - Granularity int `json:"granularitiy"` - IdleMax int `json:"idle-max"` - Transformations []string `json:"transformations"` - PersistData bool `json:"persist-data"` - Crtime int `json:"crtime"` - ValueScope string `json:"value-scope"` - Id string `json:"id"` - Uris []Uri `json:"uris"` -} - -// Uri represents a Universal Resource Identifier -type Uri struct { - Uri string // Resource identifier - Name string // URI name -} - -// InstrumentationValue represents the data associated to an instrumentation for a point in time -type InstrumentationValue struct { - Value interface{} - Transformations map[string]interface{} - StartTime int - Duration int -} - -// HeatmapOpts represent the option that can be specified -// when retrieving an instrumentation.'s heatmap -type HeatmapOpts struct { - Height int `json:"height"` // Height of the image in pixels - Width int `json:"width"` // Width of the image in pixels - Ymin int `json:"ymin"` // Y-Axis value for the bottom of the image (default: 0) - Ymax int `json:"ymax"` // Y-Axis value for the top of the image (default: auto) - Nbuckets int `json:"nbuckets"` // Number of buckets in the vertical dimension - Selected []string `json:"selected"` // Array of field values to highlight, isolate or exclude - Isolate bool `json:"isolate"` // If true, only draw selected values - Exclude bool `json:"exclude"` // If true, don't draw selected values at all - Hues []string `json:"hues"` // Array of colors for highlighting selected field values - DecomposeAll bool `json:"decompose_all"` // Highlight all field values - X int `json:"x"` - Y int `json:"y"` -} - -// Heatmap represents an instrumentation's heatmap -type Heatmap struct { - BucketTime int `json:"bucket_time"` // Time corresponding to the bucket (Unix seconds) - BucketYmin int `json:"bucket_ymin"` // Minimum y-axis value for the bucket - BucketYmax int `json:"bucket_ymax"` // Maximum y-axis value for the bucket - Present map[string]interface{} `json:"present"` // If the instrumentation defines a discrete decomposition, this property's value is an object whose keys are values of that field and whose values are the number of data points in that bucket for that key - Total int `json:"total"` // The total number of data points in the bucket -} - -// CreateInstrumentationOpts represent the option that can be specified -// when creating a new instrumentation. -type CreateInstrumentationOpts struct { - Clone int `json:"clone"` // An existing instrumentation ID to be cloned - Module string `json:"module"` // Analytics module - Stat string `json:"stat"` // Analytics stat - Predicate string `json:"predicate"` // Instrumentation predicate, must be JSON string - Decomposition string `json:"decomposition"` - Granularity int `json:"granularity"` // Number of seconds between data points (default is 1) - RetentionTime int `json:"retention-time"` // How long to keep this instrumentation data for - PersistData bool `json:"persist-data"` // Whether or not to store this for historical analysis - IdleMax int `json:"idle-max"` // Number of seconds after which if the instrumentation or its data has not been accessed via the API the service may delete the instrumentation and its data -} - -// DescribeAnalytics retrieves the "schema" for instrumentations that can be created. -// See API docs: http://apidocs.joyent.com/cloudapi/#DescribeAnalytics -func (c *Client) DescribeAnalytics() (*Analytics, error) { - var resp Analytics - req := request{ - method: client.GET, - url: apiAnalytics, - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get analytics") - } - return &resp, nil -} - -// ListInstrumentations retrieves all currently created instrumentations. -// See API docs: http://apidocs.joyent.com/cloudapi/#ListInstrumentations -func (c *Client) ListInstrumentations() ([]Instrumentation, error) { - var resp []Instrumentation - req := request{ - method: client.GET, - url: makeURL(apiAnalytics, apiInstrumentations), - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get instrumentations") - } - return resp, nil -} - -// GetInstrumentation retrieves the configuration for the specified instrumentation. -// See API docs: http://apidocs.joyent.com/cloudapi/#GetInstrumentation -func (c *Client) GetInstrumentation(instrumentationID string) (*Instrumentation, error) { - var resp Instrumentation - req := request{ - method: client.GET, - url: makeURL(apiAnalytics, apiInstrumentations, instrumentationID), - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get instrumentation with id %s", instrumentationID) - } - return &resp, nil -} - -// GetInstrumentationValue retrieves the data associated to an instrumentation -// for a point in time. -// See API docs: http://apidocs.joyent.com/cloudapi/#GetInstrumentationValue -func (c *Client) GetInstrumentationValue(instrumentationID string) (*InstrumentationValue, error) { - var resp InstrumentationValue - req := request{ - method: client.GET, - url: makeURL(apiAnalytics, apiInstrumentations, instrumentationID, apiInstrumentationsValue, apiInstrumentationsRaw), - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get value for instrumentation with id %s", instrumentationID) - } - return &resp, nil -} - -// GetInstrumentationHeatmap retrieves the specified instrumentation's heatmap. -// See API docs: http://apidocs.joyent.com/cloudapi/#GetInstrumentationHeatmap -func (c *Client) GetInstrumentationHeatmap(instrumentationID string) (*Heatmap, error) { - var resp Heatmap - req := request{ - method: client.GET, - url: makeURL(apiAnalytics, apiInstrumentations, instrumentationID, apiInstrumentationsValue, apiInstrumentationsHeatmap, apiInstrumentationsImage), - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get heatmap image for instrumentation with id %s", instrumentationID) - } - return &resp, nil -} - -// GetInstrumentationHeatmapDetails allows you to retrieve the bucket details -// for a heatmap. -// See API docs: http://apidocs.joyent.com/cloudapi/#GetInstrumentationHeatmapDetails -func (c *Client) GetInstrumentationHeatmapDetails(instrumentationID string) (*Heatmap, error) { - var resp Heatmap - req := request{ - method: client.GET, - url: makeURL(apiAnalytics, apiInstrumentations, instrumentationID, apiInstrumentationsValue, apiInstrumentationsHeatmap, apiInstrumentationsDetails), - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get heatmap details for instrumentation with id %s", instrumentationID) - } - return &resp, nil -} - -// CreateInstrumentation Creates an instrumentation. You can clone an existing -// instrumentation by passing in the parameter clone, which should be a numeric id -// of an existing instrumentation. -// See API docs: http://apidocs.joyent.com/cloudapi/#CreateInstrumentation -func (c *Client) CreateInstrumentation(opts CreateInstrumentationOpts) (*Instrumentation, error) { - var resp Instrumentation - req := request{ - method: client.POST, - url: makeURL(apiAnalytics, apiInstrumentations), - reqValue: opts, - resp: &resp, - expectedStatus: http.StatusCreated, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to create instrumentation") - } - return &resp, nil -} - -// DeleteInstrumentation destroys an instrumentation. -// See API docs: http://apidocs.joyent.com/cloudapi/#DeleteInstrumentation -func (c *Client) DeleteInstrumentation(instrumentationID string) error { - req := request{ - method: client.DELETE, - url: makeURL(apiAnalytics, apiInstrumentations, instrumentationID), - expectedStatus: http.StatusNoContent, - } - if _, err := c.sendRequest(req); err != nil { - return errors.Newf(err, "failed to delete instrumentation with id %s", instrumentationID) - } - return nil -} diff --git a/vendor/github.com/joyent/gosdc/cloudapi/keys.go b/vendor/github.com/joyent/gosdc/cloudapi/keys.go deleted file mode 100644 index fd9fd91b3..000000000 --- a/vendor/github.com/joyent/gosdc/cloudapi/keys.go +++ /dev/null @@ -1,90 +0,0 @@ -package cloudapi - -import ( - "net/http" - - "github.com/joyent/gocommon/client" - "github.com/joyent/gocommon/errors" -) - -// Key represent a public key -type Key struct { - Name string // Name for the key - Fingerprint string // Key Fingerprint - Key string // OpenSSH formatted public key -} - -/*func (k Key) Equals(other Key) bool { - if k.Name == other.Name && k.Fingerprint == other.Fingerprint && k.Key == other.Key { - return true - } - return false -}*/ - -// CreateKeyOpts represent the option that can be specified -// when creating a new key. -type CreateKeyOpts struct { - Name string `json:"name"` // Name for the key, optional - Key string `json:"key"` // OpenSSH formatted public key -} - -// ListKeys returns a list of public keys registered with a specific account. -// See API docs: http://apidocs.joyent.com/cloudapi/#ListKeys -func (c *Client) ListKeys() ([]Key, error) { - var resp []Key - req := request{ - method: client.GET, - url: apiKeys, - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get list of keys") - } - return resp, nil -} - -// GetKey returns the key identified by keyName. -// See API docs: http://apidocs.joyent.com/cloudapi/#GetKey -func (c *Client) GetKey(keyName string) (*Key, error) { - var resp Key - req := request{ - method: client.GET, - url: makeURL(apiKeys, keyName), - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get key with name: %s", keyName) - } - return &resp, nil -} - -// CreateKey creates a new key with the specified options. -// See API docs: http://apidocs.joyent.com/cloudapi/#CreateKey -func (c *Client) CreateKey(opts CreateKeyOpts) (*Key, error) { - var resp Key - req := request{ - method: client.POST, - url: apiKeys, - reqValue: opts, - resp: &resp, - expectedStatus: http.StatusCreated, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to create key with name: %s", opts.Name) - } - return &resp, nil -} - -// DeleteKey deletes the key identified by keyName. -// See API docs: http://apidocs.joyent.com/cloudapi/#DeleteKey -func (c *Client) DeleteKey(keyName string) error { - req := request{ - method: client.DELETE, - url: makeURL(apiKeys, keyName), - expectedStatus: http.StatusNoContent, - } - if _, err := c.sendRequest(req); err != nil { - return errors.Newf(err, "failed to delete key with name: %s", keyName) - } - return nil -} diff --git a/vendor/github.com/joyent/gosdc/cloudapi/machine_firewall.go b/vendor/github.com/joyent/gosdc/cloudapi/machine_firewall.go deleted file mode 100644 index 60471e72e..000000000 --- a/vendor/github.com/joyent/gosdc/cloudapi/machine_firewall.go +++ /dev/null @@ -1,52 +0,0 @@ -package cloudapi - -import ( - "fmt" - "net/http" - - "github.com/joyent/gocommon/client" - "github.com/joyent/gocommon/errors" -) - -// ListMachineFirewallRules lists all the firewall rules for the specified machine. -// See API docs: http://apidocs.joyent.com/cloudapi/#ListMachineFirewallRules -func (c *Client) ListMachineFirewallRules(machineID string) ([]FirewallRule, error) { - var resp []FirewallRule - req := request{ - method: client.GET, - url: makeURL(apiMachines, machineID, apiFirewallRules), - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get list of firewall rules for machine with id %s", machineID) - } - return resp, nil -} - -// EnableFirewallMachine enables the firewall for the specified machine. -// See API docs: http://apidocs.joyent.com/cloudapi/#EnableMachineFirewall -func (c *Client) EnableFirewallMachine(machineID string) error { - req := request{ - method: client.POST, - url: fmt.Sprintf("%s/%s?action=%s", apiMachines, machineID, actionEnableFw), - expectedStatus: http.StatusAccepted, - } - if _, err := c.sendRequest(req); err != nil { - return errors.Newf(err, "failed to enable firewall on machine with id: %s", machineID) - } - return nil -} - -// DisableFirewallMachine disables the firewall for the specified machine. -// See API docs: http://apidocs.joyent.com/cloudapi/#DisableMachineFirewall -func (c *Client) DisableFirewallMachine(machineID string) error { - req := request{ - method: client.POST, - url: fmt.Sprintf("%s/%s?action=%s", apiMachines, machineID, actionDisableFw), - expectedStatus: http.StatusAccepted, - } - if _, err := c.sendRequest(req); err != nil { - return errors.Newf(err, "failed to disable firewall on machine with id: %s", machineID) - } - return nil -} diff --git a/vendor/github.com/joyent/gosdc/cloudapi/machine_metadata.go b/vendor/github.com/joyent/gosdc/cloudapi/machine_metadata.go deleted file mode 100644 index ca8d83ca9..000000000 --- a/vendor/github.com/joyent/gosdc/cloudapi/machine_metadata.go +++ /dev/null @@ -1,70 +0,0 @@ -package cloudapi - -import ( - "net/http" - - "github.com/joyent/gocommon/client" - "github.com/joyent/gocommon/errors" -) - -// UpdateMachineMetadata updates the metadata for a given machine. -// Any metadata keys passed in here are created if they do not exist, and -// overwritten if they do. -// See API docs: http://apidocs.joyent.com/cloudapi/#UpdateMachineMetadata -func (c *Client) UpdateMachineMetadata(machineID string, metadata map[string]string) (map[string]interface{}, error) { - var resp map[string]interface{} - req := request{ - method: client.POST, - url: makeURL(apiMachines, machineID, apiMetadata), - reqValue: metadata, - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to update metadata for machine with id %s", machineID) - } - return resp, nil -} - -// GetMachineMetadata returns the complete set of metadata associated with the -// specified machine. -// See API docs: http://apidocs.joyent.com/cloudapi/#GetMachineMetadata -func (c *Client) GetMachineMetadata(machineID string) (map[string]interface{}, error) { - var resp map[string]interface{} - req := request{ - method: client.GET, - url: makeURL(apiMachines, machineID, apiMetadata), - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get list of metadata for machine with id %s", machineID) - } - return resp, nil -} - -// DeleteMachineMetadata deletes a single metadata key from the specified machine. -// See API docs: http://apidocs.joyent.com/cloudapi/#DeleteMachineMetadata -func (c *Client) DeleteMachineMetadata(machineID, metadataKey string) error { - req := request{ - method: client.DELETE, - url: makeURL(apiMachines, machineID, apiMetadata, metadataKey), - expectedStatus: http.StatusNoContent, - } - if _, err := c.sendRequest(req); err != nil { - return errors.Newf(err, "failed to delete metadata with key %s for machine with id %s", metadataKey, machineID) - } - return nil -} - -// DeleteAllMachineMetadata deletes all metadata keys from the specified machine. -// See API docs: http://apidocs.joyent.com/cloudapi/#DeleteAllMachineMetadata -func (c *Client) DeleteAllMachineMetadata(machineID string) error { - req := request{ - method: client.DELETE, - url: makeURL(apiMachines, machineID, apiMetadata), - expectedStatus: http.StatusNoContent, - } - if _, err := c.sendRequest(req); err != nil { - return errors.Newf(err, "failed to delete metadata for machine with id %s", machineID) - } - return nil -} diff --git a/vendor/github.com/joyent/gosdc/cloudapi/machine_nics.go b/vendor/github.com/joyent/gosdc/cloudapi/machine_nics.go deleted file mode 100644 index 4a137e4d9..000000000 --- a/vendor/github.com/joyent/gosdc/cloudapi/machine_nics.go +++ /dev/null @@ -1,95 +0,0 @@ -package cloudapi - -import ( - "net/http" - - "github.com/joyent/gocommon/client" - "github.com/joyent/gocommon/errors" -) - -// NICState represents the state of a NIC -type NICState string - -var ( - NICStateProvisioning NICState = "provisioning" - NICStateRunning NICState = "running" - NICStateStopped NICState = "stopped" -) - -// NIC represents a NIC on a machine -type NIC struct { - IP string `json:"ip"` // NIC's IPv4 Address - MAC string `json:"mac"` // NIC's MAC address - Primary bool `json:"primary"` // Whether this is the machine's primary NIC - Netmask string `json:"netmask"` // IPv4 netmask - Gateway string `json:"gateway"` // IPv4 gateway - State NICState `json:"state"` // Describes the state of the NIC (e.g. provisioning, running, or stopped) - Network string `json:"network"` // Network ID this NIC is attached to -} - -type addNICOptions struct { - Network string `json:"network"` // UUID of network this NIC should attach to -} - -// ListNICs lists all the NICs on a machine belonging to a given account -// See API docs: https://apidocs.joyent.com/cloudapi/#ListNics -func (c *Client) ListNICs(machineID string) ([]NIC, error) { - var resp []NIC - req := request{ - method: client.GET, - url: makeURL(apiMachines, machineID, apiNICs), - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to list NICs") - } - return resp, nil -} - -// GetNIC gets a specific NIC on a machine belonging to a given account -// See API docs: https://apidocs.joyent.com/cloudapi/#GetNic -func (c *Client) GetNIC(machineID, MAC string) (*NIC, error) { - resp := new(NIC) - req := request{ - method: client.GET, - url: makeURL(apiMachines, machineID, apiNICs, MAC), - resp: resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get NIC with MAC: %s", MAC) - } - return resp, nil -} - -// AddNIC creates a new NIC on a machine belonging to a given account. -// *WARNING*: this causes the machine to reboot while adding the NIC. -// See API docs: https://apidocs.joyent.com/cloudapi/#AddNic -func (c *Client) AddNIC(machineID, networkID string) (*NIC, error) { - resp := new(NIC) - req := request{ - method: client.POST, - url: makeURL(apiMachines, machineID, apiNICs), - reqValue: addNICOptions{networkID}, - resp: resp, - expectedStatus: http.StatusCreated, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to add NIC to machine %s on network: %s", machineID, networkID) - } - return resp, nil -} - -// RemoveNIC removes a NIC on a machine belonging to a given account. -// *WARNING*: this causes the machine to reboot while removing the NIC. -// See API docs: https://apidocs.joyent.com/cloudapi/#RemoveNic -func (c *Client) RemoveNIC(machineID, MAC string) error { - req := request{ - method: client.DELETE, - url: makeURL(apiMachines, machineID, apiNICs, MAC), - expectedStatus: http.StatusNoContent, - } - if _, err := c.sendRequest(req); err != nil { - return errors.Newf(err, "failed to remove NIC: %s", MAC) - } - return nil -} diff --git a/vendor/github.com/joyent/gosdc/cloudapi/machine_snapshots.go b/vendor/github.com/joyent/gosdc/cloudapi/machine_snapshots.go deleted file mode 100644 index 0497a0fe5..000000000 --- a/vendor/github.com/joyent/gosdc/cloudapi/machine_snapshots.go +++ /dev/null @@ -1,96 +0,0 @@ -package cloudapi - -import ( - "net/http" - - "github.com/joyent/gocommon/client" - "github.com/joyent/gocommon/errors" -) - -// Snapshot represent a point in time state of a machine. -type Snapshot struct { - Name string // Snapshot name - State string // Snapshot state -} - -// SnapshotOpts represent the option that can be specified -// when creating a new machine snapshot. -type SnapshotOpts struct { - Name string `json:"name"` // Snapshot name -} - -// CreateMachineSnapshot creates a new snapshot for the machine with the options specified. -// See API docs: http://apidocs.joyent.com/cloudapi/#CreateMachineSnapshot -func (c *Client) CreateMachineSnapshot(machineID string, opts SnapshotOpts) (*Snapshot, error) { - var resp Snapshot - req := request{ - method: client.POST, - url: makeURL(apiMachines, machineID, apiSnapshots), - reqValue: opts, - resp: &resp, - expectedStatus: http.StatusCreated, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to create snapshot %s from machine with id %s", opts.Name, machineID) - } - return &resp, nil -} - -// StartMachineFromSnapshot starts the machine from the specified snapshot. -// Machine must be in 'stopped' state. -// See API docs: http://apidocs.joyent.com/cloudapi/#StartMachineFromSnapshot -func (c *Client) StartMachineFromSnapshot(machineID, snapshotName string) error { - req := request{ - method: client.POST, - url: makeURL(apiMachines, machineID, apiSnapshots, snapshotName), - expectedStatus: http.StatusAccepted, - } - if _, err := c.sendRequest(req); err != nil { - return errors.Newf(err, "failed to start machine with id %s from snapshot %s", machineID, snapshotName) - } - return nil -} - -// ListMachineSnapshots lists all snapshots for the specified machine. -// See API docs: http://apidocs.joyent.com/cloudapi/#ListMachineSnapshots -func (c *Client) ListMachineSnapshots(machineID string) ([]Snapshot, error) { - var resp []Snapshot - req := request{ - method: client.GET, - url: makeURL(apiMachines, machineID, apiSnapshots), - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get list of snapshots for machine with id %s", machineID) - } - return resp, nil -} - -// GetMachineSnapshot returns the state of the specified snapshot. -// See API docs: http://apidocs.joyent.com/cloudapi/#GetMachineSnapshot -func (c *Client) GetMachineSnapshot(machineID, snapshotName string) (*Snapshot, error) { - var resp Snapshot - req := request{ - method: client.GET, - url: makeURL(apiMachines, machineID, apiSnapshots, snapshotName), - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get snapshot %s for machine with id %s", snapshotName, machineID) - } - return &resp, nil -} - -// DeleteMachineSnapshot deletes the specified snapshot. -// See API docs: http://apidocs.joyent.com/cloudapi/#DeleteMachineSnapshot -func (c *Client) DeleteMachineSnapshot(machineID, snapshotName string) error { - req := request{ - method: client.DELETE, - url: makeURL(apiMachines, machineID, apiSnapshots, snapshotName), - expectedStatus: http.StatusNoContent, - } - if _, err := c.sendRequest(req); err != nil { - return errors.Newf(err, "failed to delete snapshot %s for machine with id %s", snapshotName, machineID) - } - return nil -} diff --git a/vendor/github.com/joyent/gosdc/cloudapi/machine_tags.go b/vendor/github.com/joyent/gosdc/cloudapi/machine_tags.go deleted file mode 100644 index 9a5242bdb..000000000 --- a/vendor/github.com/joyent/gosdc/cloudapi/machine_tags.go +++ /dev/null @@ -1,103 +0,0 @@ -package cloudapi - -import ( - "net/http" - - "github.com/joyent/gocommon/client" - "github.com/joyent/gocommon/errors" -) - -// AddMachineTags adds additional tags to the specified machine. -// This API lets you append new tags, not overwrite existing tags. -// See API docs: http://apidocs.joyent.com/cloudapi/#AddMachineTags -func (c *Client) AddMachineTags(machineID string, tags map[string]string) (map[string]string, error) { - var resp map[string]string - req := request{ - method: client.POST, - url: makeURL(apiMachines, machineID, apiTags), - reqValue: tags, - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to add tags for machine with id %s", machineID) - } - return resp, nil -} - -// ReplaceMachineTags replaces existing tags for the specified machine. -// This API lets you overwrite existing tags, not append to existing tags. -// See API docs: http://apidocs.joyent.com/cloudapi/#ReplaceMachineTags -func (c *Client) ReplaceMachineTags(machineID string, tags map[string]string) (map[string]string, error) { - var resp map[string]string - req := request{ - method: client.PUT, - url: makeURL(apiMachines, machineID, apiTags), - reqValue: tags, - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to replace tags for machine with id %s", machineID) - } - return resp, nil -} - -// ListMachineTags returns the complete set of tags associated with the specified machine. -// See API docs: http://apidocs.joyent.com/cloudapi/#ListMachineTags -func (c *Client) ListMachineTags(machineID string) (map[string]string, error) { - var resp map[string]string - req := request{ - method: client.GET, - url: makeURL(apiMachines, machineID, apiTags), - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get list of tags for machine with id %s", machineID) - } - return resp, nil -} - -// GetMachineTag returns the value for a single tag on the specified machine. -// See API docs: http://apidocs.joyent.com/cloudapi/#GetMachineTag -func (c *Client) GetMachineTag(machineID, tagKey string) (string, error) { - var resp []byte - requestHeaders := make(http.Header) - requestHeaders.Set("Accept", "text/plain") - req := request{ - method: client.GET, - url: makeURL(apiMachines, machineID, apiTags, tagKey), - resp: &resp, - reqHeader: requestHeaders, - } - if _, err := c.sendRequest(req); err != nil { - return "", errors.Newf(err, "failed to get tag %s for machine with id %s", tagKey, machineID) - } - return string(resp), nil -} - -// DeleteMachineTag deletes a single tag from the specified machine. -// See API docs: http://apidocs.joyent.com/cloudapi/#DeleteMachineTag -func (c *Client) DeleteMachineTag(machineID, tagKey string) error { - req := request{ - method: client.DELETE, - url: makeURL(apiMachines, machineID, apiTags, tagKey), - expectedStatus: http.StatusNoContent, - } - if _, err := c.sendRequest(req); err != nil { - return errors.Newf(err, "failed to delete tag with key %s for machine with id %s", tagKey, machineID) - } - return nil -} - -// DeleteMachineTags deletes all tags from the specified machine. -// See API docs: http://apidocs.joyent.com/cloudapi/#DeleteMachineTags -func (c *Client) DeleteMachineTags(machineID string) error { - req := request{ - method: client.DELETE, - url: makeURL(apiMachines, machineID, apiTags), - expectedStatus: http.StatusNoContent, - } - if _, err := c.sendRequest(req); err != nil { - return errors.Newf(err, "failed to delete tags for machine with id %s", machineID) - } - return nil -} diff --git a/vendor/github.com/joyent/gosdc/cloudapi/machines.go b/vendor/github.com/joyent/gosdc/cloudapi/machines.go deleted file mode 100644 index 32a3ccf65..000000000 --- a/vendor/github.com/joyent/gosdc/cloudapi/machines.go +++ /dev/null @@ -1,307 +0,0 @@ -package cloudapi - -import ( - "encoding/json" - "fmt" - "net/http" - - "strings" - - "github.com/joyent/gocommon/client" - "github.com/joyent/gocommon/errors" -) - -// Machine represent a provisioned virtual machines -type Machine struct { - Id string // Unique identifier for the image - Name string // Machine friendly name - Type string // Machine type, one of 'smartmachine' or 'virtualmachine' - State string // Current state of the machine - Dataset string // The dataset URN the machine was provisioned with. For new images/datasets this value will be the dataset id, i.e, same value than the image attribute - Memory int // The amount of memory the machine has (in Mb) - Disk int // The amount of disk the machine has (in Gb) - IPs []string // The IP addresses the machine has - Metadata map[string]string // Map of the machine metadata, e.g. authorized-keys - Tags map[string]string // Map of the machine tags - Created string // When the machine was created - Updated string // When the machine was updated - Package string // The name of the package used to create the machine - Image string // The image id the machine was provisioned with - PrimaryIP string // The primary (public) IP address for the machine - Networks []string // The network IDs for the machine - FirewallEnabled bool `json:"firewall_enabled"` // whether or not the firewall is enabled - DomainNames []string `json:"dns_names"` // The domain names of this machine -} - -// Equals compares two machines. Ignores state and timestamps. -func (m Machine) Equals(other Machine) bool { - if m.Id == other.Id && m.Name == other.Name && m.Type == other.Type && m.Dataset == other.Dataset && - m.Memory == other.Memory && m.Disk == other.Disk && m.Package == other.Package && m.Image == other.Image && - m.compareIPs(other) && m.compareMetadata(other) { - return true - } - return false -} - -// Helper method to compare two machines IPs -func (m Machine) compareIPs(other Machine) bool { - if len(m.IPs) != len(other.IPs) { - return false - } - for i, v := range m.IPs { - if v != other.IPs[i] { - return false - } - } - return true -} - -// Helper method to compare two machines metadata -func (m Machine) compareMetadata(other Machine) bool { - if len(m.Metadata) != len(other.Metadata) { - return false - } - for k, v := range m.Metadata { - if v != other.Metadata[k] { - return false - } - } - return true -} - -// CreateMachineOpts represent the option that can be specified -// when creating a new machine. -type CreateMachineOpts struct { - Name string `json:"name"` // Machine friendly name, default is a randomly generated name - Package string `json:"package"` // Name of the package to use on provisioning - Image string `json:"image"` // The image UUID - Networks []string `json:"networks"` // Desired networks IDs - Metadata map[string]string `json:"-"` // An arbitrary set of metadata key/value pairs can be set at provision time - Tags map[string]string `json:"-"` // An arbitrary set of tags can be set at provision time - FirewallEnabled bool `json:"firewall_enabled"` // Completely enable or disable firewall for this machine (new in API version 7.0) -} - -// AuditAction represents an action/event accomplished by a machine. -type AuditAction struct { - Action string // Action name - Parameters map[string]interface{} // Original set of parameters sent when the action was requested - Time string // When the action finished - Success string // Either 'yes' or 'no', depending on the action successfulness - Caller Caller // Account requesting the action -} - -// Caller represents an account requesting an action. -type Caller struct { - Type string // Authentication type for the action request. One of 'basic', 'operator', 'signature' or 'token' - User string // When the authentication type is 'basic', this member will be present and include user login - IP string // The IP addresses this from which the action was requested. Not present if type is 'operator' - KeyId string // When authentication type is either 'signature' or 'token', SSH key identifier -} - -// appendJSON marshals the given attribute value and appends it as an encoded value to the given json data. -// The newly encode (attr, value) is inserted just before the closing "}" in the json data. -func appendJSON(data []byte, attr string, value interface{}) ([]byte, error) { - newData, err := json.Marshal(&value) - if err != nil { - return nil, err - } - strData := string(data) - result := fmt.Sprintf(`%s, "%s":%s}`, strData[:len(strData)-1], attr, string(newData)) - return []byte(result), nil -} - -type jsonOpts CreateMachineOpts - -// MarshalJSON turns the given CreateMachineOpts into JSON -func (opts CreateMachineOpts) MarshalJSON() ([]byte, error) { - jo := jsonOpts(opts) - data, err := json.Marshal(&jo) - if err != nil { - return nil, err - } - for k, v := range opts.Tags { - if !strings.HasPrefix(k, "tag.") { - k = "tag." + k - } - data, err = appendJSON(data, k, v) - if err != nil { - return nil, err - } - } - for k, v := range opts.Metadata { - if !strings.HasPrefix(k, "metadata.") { - k = "metadata." + k - } - data, err = appendJSON(data, k, v) - if err != nil { - return nil, err - } - } - return data, nil -} - -// ListMachines lists all machines on record for an account. -// You can paginate this API by passing in offset, and limit -// See API docs: http://apidocs.joyent.com/cloudapi/#ListMachines -func (c *Client) ListMachines(filter *Filter) ([]Machine, error) { - var resp []Machine - req := request{ - method: client.GET, - url: apiMachines, - filter: filter, - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get list of machines") - } - return resp, nil -} - -// CountMachines returns the number of machines on record for an account. -// See API docs: http://apidocs.joyent.com/cloudapi/#ListMachines -func (c *Client) CountMachines() (int, error) { - var resp int - req := request{ - method: client.HEAD, - url: apiMachines, - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return -1, errors.Newf(err, "failed to get count of machines") - } - return resp, nil -} - -// GetMachine returns the machine specified by machineId. -// See API docs: http://apidocs.joyent.com/cloudapi/#GetMachine -func (c *Client) GetMachine(machineID string) (*Machine, error) { - var resp Machine - req := request{ - method: client.GET, - url: makeURL(apiMachines, machineID), - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get machine with id: %s", machineID) - } - return &resp, nil -} - -// CreateMachine creates a new machine with the options specified. -// See API docs: http://apidocs.joyent.com/cloudapi/#CreateMachine -func (c *Client) CreateMachine(opts CreateMachineOpts) (*Machine, error) { - var resp Machine - req := request{ - method: client.POST, - url: apiMachines, - reqValue: opts, - resp: &resp, - expectedStatus: http.StatusCreated, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to create machine with name: %s", opts.Name) - } - return &resp, nil -} - -// StopMachine stops a running machine. -// See API docs: http://apidocs.joyent.com/cloudapi/#StopMachine -func (c *Client) StopMachine(machineID string) error { - req := request{ - method: client.POST, - url: fmt.Sprintf("%s/%s?action=%s", apiMachines, machineID, actionStop), - expectedStatus: http.StatusAccepted, - } - if _, err := c.sendRequest(req); err != nil { - return errors.Newf(err, "failed to stop machine with id: %s", machineID) - } - return nil -} - -// StartMachine starts a stopped machine. -// See API docs: http://apidocs.joyent.com/cloudapi/#StartMachine -func (c *Client) StartMachine(machineID string) error { - req := request{ - method: client.POST, - url: fmt.Sprintf("%s/%s?action=%s", apiMachines, machineID, actionStart), - expectedStatus: http.StatusAccepted, - } - if _, err := c.sendRequest(req); err != nil { - return errors.Newf(err, "failed to start machine with id: %s", machineID) - } - return nil -} - -// RebootMachine reboots (stop followed by a start) a machine. -// See API docs: http://apidocs.joyent.com/cloudapi/#RebootMachine -func (c *Client) RebootMachine(machineID string) error { - req := request{ - method: client.POST, - url: fmt.Sprintf("%s/%s?action=%s", apiMachines, machineID, actionReboot), - expectedStatus: http.StatusAccepted, - } - if _, err := c.sendRequest(req); err != nil { - return errors.Newf(err, "failed to reboot machine with id: %s", machineID) - } - return nil -} - -// ResizeMachine allows you to resize a SmartMachine. Virtual machines can also -// be resized, but only resizing virtual machines to a higher capacity package -// is supported. -// See API docs: http://apidocs.joyent.com/cloudapi/#ResizeMachine -func (c *Client) ResizeMachine(machineID, packageName string) error { - req := request{ - method: client.POST, - url: fmt.Sprintf("%s/%s?action=%s&package=%s", apiMachines, machineID, actionResize, packageName), - expectedStatus: http.StatusAccepted, - } - if _, err := c.sendRequest(req); err != nil { - return errors.Newf(err, "failed to resize machine with id: %s", machineID) - } - return nil -} - -// RenameMachine renames an existing machine. -// See API docs: http://apidocs.joyent.com/cloudapi/#RenameMachine -func (c *Client) RenameMachine(machineID, machineName string) error { - req := request{ - method: client.POST, - url: fmt.Sprintf("%s/%s?action=%s&name=%s", apiMachines, machineID, actionRename, machineName), - expectedStatus: http.StatusAccepted, - } - if _, err := c.sendRequest(req); err != nil { - return errors.Newf(err, "failed to rename machine with id: %s", machineID) - } - return nil -} - -// DeleteMachine allows you to completely destroy a machine. Machine must be in the 'stopped' state. -// See API docs: http://apidocs.joyent.com/cloudapi/#DeleteMachine -func (c *Client) DeleteMachine(machineID string) error { - req := request{ - method: client.DELETE, - url: makeURL(apiMachines, machineID), - expectedStatus: http.StatusNoContent, - } - if _, err := c.sendRequest(req); err != nil { - return errors.Newf(err, "failed to delete machine with id %s", machineID) - } - return nil -} - -// MachineAudit provides a list of machine's accomplished actions, (sorted from -// latest to older one). -// See API docs: http://apidocs.joyent.com/cloudapi/#MachineAudit -func (c *Client) MachineAudit(machineID string) ([]AuditAction, error) { - var resp []AuditAction - req := request{ - method: client.GET, - url: makeURL(apiMachines, machineID, apiAudit), - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get actions for machine with id %s", machineID) - } - return resp, nil -} diff --git a/vendor/github.com/joyent/gosdc/cloudapi/networks.go b/vendor/github.com/joyent/gosdc/cloudapi/networks.go deleted file mode 100644 index 18d828999..000000000 --- a/vendor/github.com/joyent/gosdc/cloudapi/networks.go +++ /dev/null @@ -1,44 +0,0 @@ -package cloudapi - -import ( - "github.com/joyent/gocommon/client" - "github.com/joyent/gocommon/errors" -) - -// Network represents a network available to a given account -type Network struct { - Id string // Unique identifier for the network - Name string // Network name - Public bool // Whether this a public or private (rfc1918) network - Description string // Optional description for this network, when name is not enough -} - -// ListNetworks lists all the networks which can be used by the given account. -// See API docs: http://apidocs.joyent.com/cloudapi/#ListNetworks -func (c *Client) ListNetworks() ([]Network, error) { - var resp []Network - req := request{ - method: client.GET, - url: apiNetworks, - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get list of networks") - } - return resp, nil -} - -// GetNetwork retrieves an individual network record. -// See API docs: http://apidocs.joyent.com/cloudapi/#GetNetwork -func (c *Client) GetNetwork(networkID string) (*Network, error) { - var resp Network - req := request{ - method: client.GET, - url: makeURL(apiNetworks, networkID), - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get network with id %s", networkID) - } - return &resp, nil -} diff --git a/vendor/github.com/joyent/gosdc/cloudapi/packages.go b/vendor/github.com/joyent/gosdc/cloudapi/packages.go deleted file mode 100644 index 9b3399916..000000000 --- a/vendor/github.com/joyent/gosdc/cloudapi/packages.go +++ /dev/null @@ -1,53 +0,0 @@ -package cloudapi - -import ( - "github.com/joyent/gocommon/client" - "github.com/joyent/gocommon/errors" -) - -// Package represents a named collections of resources that are used to describe the 'sizes' -// of either a smart machine or a virtual machine. -type Package struct { - Name string // Name for the package - Memory int // Memory available (in Mb) - Disk int // Disk space available (in Gb) - Swap int // Swap memory available (in Mb) - VCPUs int // Number of VCPUs for the package - Default bool // Indicates whether this is the default package in the datacenter - Id string // Unique identifier for the package - Version string // Version for the package - Group string // Group this package belongs to - Description string // Human friendly description for the package -} - -// ListPackages provides a list of packages available in the datacenter. -// See API docs: http://apidocs.joyent.com/cloudapi/#ListPackages -func (c *Client) ListPackages(filter *Filter) ([]Package, error) { - var resp []Package - req := request{ - method: client.GET, - url: apiPackages, - filter: filter, - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get list of packages") - } - return resp, nil -} - -// GetPackage returns the package specified by packageName. NOTE: packageName can -// specify either the package name or package ID. -// See API docs: http://apidocs.joyent.com/cloudapi/#GetPackage -func (c *Client) GetPackage(packageName string) (*Package, error) { - var resp Package - req := request{ - method: client.GET, - url: makeURL(apiPackages, packageName), - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get package with name: %s", packageName) - } - return &resp, nil -} diff --git a/vendor/github.com/joyent/gosdc/cloudapi/services.go b/vendor/github.com/joyent/gosdc/cloudapi/services.go deleted file mode 100644 index 634b69ff3..000000000 --- a/vendor/github.com/joyent/gosdc/cloudapi/services.go +++ /dev/null @@ -1,20 +0,0 @@ -package cloudapi - -import ( - "github.com/joyent/gocommon/client" - "github.com/joyent/gocommon/errors" -) - -// list available services -func (c *Client) ListServices() (map[string]string, error) { - var resp map[string]string - req := request{ - method: client.GET, - url: apiServices, - resp: &resp, - } - if _, err := c.sendRequest(req); err != nil { - return nil, errors.Newf(err, "failed to get list of services") - } - return resp, nil -} diff --git a/vendor/github.com/joyent/gosdc/LICENSE b/vendor/github.com/joyent/triton-go/LICENSE similarity index 100% rename from vendor/github.com/joyent/gosdc/LICENSE rename to vendor/github.com/joyent/triton-go/LICENSE diff --git a/vendor/github.com/joyent/triton-go/README.md b/vendor/github.com/joyent/triton-go/README.md new file mode 100644 index 000000000..6546ba373 --- /dev/null +++ b/vendor/github.com/joyent/triton-go/README.md @@ -0,0 +1,216 @@ +# triton-go + +`go-triton` is an idiomatic library exposing a client SDK for Go applications using the Joyent Triton API. + +## 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]. + +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) +} +``` + +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: + +```go +client, err := triton.NewClient("https://us-sw-1.api.joyent.com/", "AccountName", sshKeySigner) +if err != nil { + log.Fatalf("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. + +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: + +``` +client := state.Client().Keys() + +key, err := client.CreateKey(&CreateKeyInput{ + Name: "tempKey", + Key: "ssh-rsa .....", +}) +if err != nil { + panic(err) +} + +// Key contains the return value. +``` + +## Error Handling + +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 +- `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. + +### Example Run + +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" +=== RUN TestAccKey_Create +--- PASS: TestAccKey_Create (12.46s) +=== RUN TestAccKey_Get +--- PASS: TestAccKey_Get (4.30s) +=== RUN TestAccKey_Delete +--- PASS: TestAccKey_Delete (15.08s) +PASS +ok github.com/jen20/triton-go 31.861s +``` + +[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 +[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 new file mode 100644 index 000000000..8049d4e7e --- /dev/null +++ b/vendor/github.com/joyent/triton-go/accounts.go @@ -0,0 +1,92 @@ +package triton + +import ( + "encoding/json" + "net/http" + "time" + + "github.com/hashicorp/errwrap" + "fmt" +) + +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(input *GetAccountInput) (*Account, error) { + respReader, err := client.executeRequest(http.MethodGet, "/my", 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(input *UpdateAccountInput) (*Account, error) { + respReader, err := client.executeRequest(http.MethodPost, fmt.Sprintf("/%s", client.accountName), 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/ecdsa_signature.go b/vendor/github.com/joyent/triton-go/authentication/ecdsa_signature.go new file mode 100644 index 000000000..8aaba97a5 --- /dev/null +++ b/vendor/github.com/joyent/triton-go/authentication/ecdsa_signature.go @@ -0,0 +1,66 @@ +package authentication + +import ( + "encoding/asn1" + "encoding/base64" + "fmt" + "math/big" + + "github.com/hashicorp/errwrap" + "golang.org/x/crypto/ssh" +) + +type ecdsaSignature struct { + hashAlgorithm string + R *big.Int + S *big.Int +} + +func (s *ecdsaSignature) SignatureType() string { + return fmt.Sprintf("ecdsa-%s", s.hashAlgorithm) +} + +func (s *ecdsaSignature) String() string { + toEncode := struct { + R *big.Int + S *big.Int + }{ + R: s.R, + S: s.S, + } + + signatureBytes, err := asn1.Marshal(toEncode) + if err != nil { + panic(fmt.Sprintf("Error marshaling signature: %s", err)) + } + + return base64.StdEncoding.EncodeToString(signatureBytes) +} + +func newECDSASignature(signatureBlob []byte) (*ecdsaSignature, error) { + var ecSig struct { + R *big.Int + S *big.Int + } + + if err := ssh.Unmarshal(signatureBlob, &ecSig); err != nil { + return nil, errwrap.Wrapf("Error unmarshaling signature: {{err}}", err) + } + + rValue := ecSig.R.Bytes() + var hashAlgorithm string + switch len(rValue) { + case 31, 32: + hashAlgorithm = "sha256" + case 65, 66: + hashAlgorithm = "sha512" + default: + return nil, fmt.Errorf("Unsupported key length: %d", len(rValue)) + } + + return &ecdsaSignature{ + hashAlgorithm: hashAlgorithm, + R: ecSig.R, + S: ecSig.S, + }, 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 new file mode 100644 index 000000000..aadb6ee5f --- /dev/null +++ b/vendor/github.com/joyent/triton-go/authentication/private_key_signer.go @@ -0,0 +1,73 @@ +package authentication + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "fmt" + "github.com/hashicorp/errwrap" + "golang.org/x/crypto/ssh" + "strings" +) + +type PrivateKeySigner struct { + formattedKeyFingerprint string + keyFingerprint string + accountName string + hashFunc crypto.Hash + + privateKey *rsa.PrivateKey +} + +func NewPrivateKeySigner(keyFingerprint string, privateKeyMaterial []byte, accountName string) (*PrivateKeySigner, error) { + keyFingerprintMD5 := strings.Replace(keyFingerprint, ":", "", -1) + + block, _ := pem.Decode(privateKeyMaterial) + if block == nil { + return nil, fmt.Errorf("Error PEM-decoding private key material: nil block received") + } + + rsakey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, errwrap.Wrapf("Error parsing private key: %s", err) + } + + sshPublicKey, err := ssh.NewPublicKey(rsakey.Public()) + if err != nil { + return nil, errwrap.Wrapf("Error parsing SSH key from private key: %s", err) + } + + matchKeyFingerprint := formatPublicKeyFingerprint(sshPublicKey, false) + displayKeyFingerprint := formatPublicKeyFingerprint(sshPublicKey, true) + if matchKeyFingerprint != keyFingerprintMD5 { + return nil, fmt.Errorf("Private key file does not match public key fingerprint") + } + + return &PrivateKeySigner{ + formattedKeyFingerprint: displayKeyFingerprint, + keyFingerprint: keyFingerprint, + accountName: accountName, + + hashFunc: crypto.SHA1, + privateKey: rsakey, + }, nil +} + +func (s *PrivateKeySigner) Sign(dateHeader string) (string, error) { + const headerName = "date" + + hash := s.hashFunc.New() + hash.Write([]byte(fmt.Sprintf("%s: %s", headerName, dateHeader))) + 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 fmt.Sprintf(authorizationHeaderFormat, s.formattedKeyFingerprint, "rsa-sha1", headerName, signedBase64), nil +} diff --git a/vendor/github.com/joyent/triton-go/authentication/rsa_signature.go b/vendor/github.com/joyent/triton-go/authentication/rsa_signature.go new file mode 100644 index 000000000..8d513f6c4 --- /dev/null +++ b/vendor/github.com/joyent/triton-go/authentication/rsa_signature.go @@ -0,0 +1,25 @@ +package authentication + +import ( + "encoding/base64" +) + +type rsaSignature struct { + hashAlgorithm string + signature []byte +} + +func (s *rsaSignature) SignatureType() string { + return s.hashAlgorithm +} + +func (s *rsaSignature) String() string { + return base64.StdEncoding.EncodeToString(s.signature) +} + +func newRSASignature(signatureBlob []byte) (*rsaSignature, error) { + return &rsaSignature{ + hashAlgorithm: "rsa-sha1", + signature: signatureBlob, + }, nil +} diff --git a/vendor/github.com/joyent/triton-go/authentication/signature.go b/vendor/github.com/joyent/triton-go/authentication/signature.go new file mode 100644 index 000000000..e6a52df30 --- /dev/null +++ b/vendor/github.com/joyent/triton-go/authentication/signature.go @@ -0,0 +1,27 @@ +package authentication + +import ( + "regexp" + "fmt" +) + +type httpAuthSignature interface { + SignatureType() string + String() string +} + +func keyFormatToKeyType(keyFormat string) (string, error) { + if keyFormat == "ssh-rsa" { + return "rsa", nil + } + + if keyFormat == "ssh-ed25519" { + return "ed25519", nil + } + + if regexp.MustCompile("^ecdsa-sha2-*").Match([]byte(keyFormat)) { + return "ecdsa", nil + } + + return "", fmt.Errorf("Unknown key format: %s", keyFormat) +} diff --git a/vendor/github.com/joyent/triton-go/authentication/signer.go b/vendor/github.com/joyent/triton-go/authentication/signer.go new file mode 100644 index 000000000..dfc89ad44 --- /dev/null +++ b/vendor/github.com/joyent/triton-go/authentication/signer.go @@ -0,0 +1,7 @@ +package authentication + +const authorizationHeaderFormat = `Signature keyId="%s",algorithm="%s",headers="%s",signature="%s"` + +type Signer interface { + Sign(dateHeader 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 new file mode 100644 index 000000000..028743159 --- /dev/null +++ b/vendor/github.com/joyent/triton-go/authentication/ssh_agent_signer.go @@ -0,0 +1,104 @@ +package authentication + +import ( + "crypto/md5" + "errors" + "fmt" + "net" + "os" + "strings" + + "github.com/hashicorp/errwrap" + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/agent" +) + +type SSHAgentSigner struct { + formattedKeyFingerprint string + keyFingerprint string + accountName string + keyIdentifier string + + agent agent.Agent + key ssh.PublicKey +} + +func NewSSHAgentSigner(keyFingerprint, accountName string) (*SSHAgentSigner, error) { + sshAgentAddress := os.Getenv("SSH_AUTH_SOCK") + if sshAgentAddress == "" { + return nil, errors.New("SSH_AUTH_SOCK is not set") + } + + conn, err := net.Dial("unix", sshAgentAddress) + if err != nil { + return nil, errwrap.Wrapf("Error dialing SSH agent: {{err}}", err) + } + + ag := agent.NewClient(conn) + + keys, err := ag.List() + if err != nil { + return nil, errwrap.Wrapf("Error listing keys in SSH Agent: %s", err) + } + + keyFingerprintMD5 := strings.Replace(keyFingerprint, ":", "", -1) + + var matchingKey ssh.PublicKey + for _, key := range keys { + h := md5.New() + h.Write(key.Marshal()) + fp := fmt.Sprintf("%x", h.Sum(nil)) + + if fp == keyFingerprintMD5 { + matchingKey = key + } + } + + if matchingKey == nil { + return nil, fmt.Errorf("No key in the SSH Agent matches fingerprint: %s", keyFingerprint) + } + + formattedKeyFingerprint := formatPublicKeyFingerprint(matchingKey, true) + + return &SSHAgentSigner{ + formattedKeyFingerprint: formattedKeyFingerprint, + keyFingerprint: keyFingerprint, + accountName: accountName, + agent: ag, + key: matchingKey, + keyIdentifier: fmt.Sprintf("/%s/keys/%s", accountName, formattedKeyFingerprint), + }, nil +} + +func (s *SSHAgentSigner) Sign(dateHeader string) (string, error) { + const headerName = "date" + + signature, err := s.agent.Sign(s.key, []byte(fmt.Sprintf("%s: %s", headerName, dateHeader))) + if err != nil { + return "", errwrap.Wrapf("Error signing date header: {{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 fmt.Sprintf(authorizationHeaderFormat, s.keyIdentifier, + authSignature.SignatureType(), headerName, authSignature.String()), nil +} diff --git a/vendor/github.com/joyent/triton-go/authentication/util.go b/vendor/github.com/joyent/triton-go/authentication/util.go new file mode 100644 index 000000000..7c298b68c --- /dev/null +++ b/vendor/github.com/joyent/triton-go/authentication/util.go @@ -0,0 +1,29 @@ +package authentication + +import ( + "crypto/md5" + "fmt" + "strings" + + "golang.org/x/crypto/ssh" +) + +// formatPublicKeyFingerprint produces the MD5 fingerprint of the given SSH +// public key. If display is true, the fingerprint is formatted with colons +// between each byte, as per the output of OpenSSL. +func formatPublicKeyFingerprint(key ssh.PublicKey, display bool) string { + publicKeyFingerprint := md5.New() + publicKeyFingerprint.Write(key.Marshal()) + publicKeyFingerprintString := fmt.Sprintf("%x", publicKeyFingerprint.Sum(nil)) + + if !display { + return publicKeyFingerprintString + } + + formatted := "" + for i := 0; i < len(publicKeyFingerprintString); i = i + 2 { + formatted = fmt.Sprintf("%s%s:", formatted, publicKeyFingerprintString[i:i+2]) + } + + return strings.TrimSuffix(formatted, ":") +} diff --git a/vendor/github.com/joyent/triton-go/client.go b/vendor/github.com/joyent/triton-go/client.go new file mode 100644 index 000000000..2b840bba5 --- /dev/null +++ b/vendor/github.com/joyent/triton-go/client.go @@ -0,0 +1,179 @@ +package triton + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "log" + "net" + "net/http" + "net/url" + "os" + "strings" + "time" + + "github.com/hashicorp/errwrap" + "github.com/hashicorp/go-retryablehttp" + "github.com/joyent/triton-go/authentication" +) + +// Client represents a connection to the Triton API. +type Client struct { + client *retryablehttp.Client + authorizer []authentication.Signer + endpoint string + 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) { + defaultRetryWaitMin := 1 * time.Second + defaultRetryWaitMax := 5 * time.Minute + defaultRetryMax := 32 + + httpClient := &http.Client{ + Transport: &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, + }, + CheckRedirect: doNotFollowRedirects, + } + + retryableClient := &retryablehttp.Client{ + HTTPClient: httpClient, + Logger: log.New(os.Stderr, "", log.LstdFlags), + RetryWaitMin: defaultRetryWaitMin, + RetryWaitMax: defaultRetryWaitMax, + RetryMax: defaultRetryMax, + CheckRetry: retryablehttp.DefaultRetryPolicy, + } + + return &Client{ + client: retryableClient, + authorizer: signers, + endpoint: strings.TrimSuffix(endpoint, "/"), + accountName: accountName, + }, nil +} + +func doNotFollowRedirects(*http.Request, []*http.Request) error { + return http.ErrUseLastResponse +} + +func (c *Client) formatURL(path string) string { + return fmt.Sprintf("%s%s", c.endpoint, path) +} + +func (c *Client) executeRequestURIParams(method, path string, body interface{}, query *url.Values) (io.ReadCloser, error) { + var requestBody io.ReadSeeker + if body != nil { + marshaled, err := json.MarshalIndent(body, "", " ") + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(marshaled) + } + + req, err := retryablehttp.NewRequest(method, c.formatURL(path), 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") + } + + if query != nil { + req.URL.RawQuery = query.Encode() + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, errwrap.Wrapf("Error executing HTTP request: {{err}}", err) + } + + 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 { + tritonError := &TritonError{ + StatusCode: statusCode, + } + + errorDecoder := json.NewDecoder(body) + if err := errorDecoder.Decode(tritonError); err != nil { + return errwrap.Wrapf("Error decoding error response: {{err}}", err) + } + + return tritonError +} + +func (c *Client) executeRequest(method, path string, body interface{}) (io.ReadCloser, error) { + return c.executeRequestURIParams(method, path, body, nil) +} + +func (c *Client) executeRequestRaw(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) + } + + req, err := retryablehttp.NewRequest(method, c.formatURL(path), 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) + 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/config.go b/vendor/github.com/joyent/triton-go/config.go new file mode 100644 index 000000000..83d27790a --- /dev/null +++ b/vendor/github.com/joyent/triton-go/config.go @@ -0,0 +1,71 @@ +package triton + +import ( + "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(input *GetConfigInput) (*Config, error) { + respReader, err := client.executeRequest(http.MethodGet, fmt.Sprintf("/%s/config", client.accountName), 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. +// TODO(jen20) Work out a safe way to test this (after networks c implemented) +func (client *ConfigClient) UpdateConfig(input *UpdateConfigInput) (*Config, error) { + respReader, err := client.executeRequest(http.MethodPut, fmt.Sprintf("/%s/config", client.accountName), 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 new file mode 100644 index 000000000..c90cc954a --- /dev/null +++ b/vendor/github.com/joyent/triton-go/datacenters.go @@ -0,0 +1,90 @@ +package triton + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "sort" + + "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(*ListDataCentersInput) ([]*DataCenter, error) { + respReader, err := client.executeRequest(http.MethodGet, "/my/datacenters", 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(input *GetDataCenterInput) (*DataCenter, error) { + resp, err := client.executeRequestRaw(http.MethodGet, fmt.Sprintf("/my/datacenters/%s", input.Name), 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/errors.go b/vendor/github.com/joyent/triton-go/errors.go new file mode 100644 index 000000000..76d4a6254 --- /dev/null +++ b/vendor/github.com/joyent/triton-go/errors.go @@ -0,0 +1,126 @@ +package triton + +import ( + "fmt" + + "github.com/hashicorp/errwrap" +) + +// 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 +// code BadRequest +func IsBadRequest(err error) bool { + return isSpecificError(err, "BadRequest") +} + +// IsInternalError tests whether err wraps a TritonError with +// code InternalError +func IsInternalError(err error) bool { + return isSpecificError(err, "InternalError") +} + +// IsInUseError tests whether err wraps a TritonError with +// code InUseError +func IsInUseError(err error) bool { + return isSpecificError(err, "InUseError") +} + +// IsInvalidArgument tests whether err wraps a TritonError with +// code InvalidArgument +func IsInvalidArgument(err error) bool { + return isSpecificError(err, "InvalidArgument") +} + +// IsInvalidCredentials tests whether err wraps a TritonError with +// code InvalidCredentials +func IsInvalidCredentials(err error) bool { + return isSpecificError(err, "InvalidCredentials") +} + +// IsInvalidHeader tests whether err wraps a TritonError with +// code InvalidHeader +func IsInvalidHeader(err error) bool { + return isSpecificError(err, "InvalidHeader") +} + +// IsInvalidVersion tests whether err wraps a TritonError with +// code InvalidVersion +func IsInvalidVersion(err error) bool { + return isSpecificError(err, "InvalidVersion") +} + +// IsMissingParameter tests whether err wraps a TritonError with +// code MissingParameter +func IsMissingParameter(err error) bool { + return isSpecificError(err, "MissingParameter") +} + +// IsNotAuthorized tests whether err wraps a TritonError with +// code NotAuthorized +func IsNotAuthorized(err error) bool { + return isSpecificError(err, "NotAuthorized") +} + +// IsRequestThrottled tests whether err wraps a TritonError with +// code RequestThrottled +func IsRequestThrottled(err error) bool { + return isSpecificError(err, "RequestThrottled") +} + +// IsRequestTooLarge tests whether err wraps a TritonError with +// code RequestTooLarge +func IsRequestTooLarge(err error) bool { + return isSpecificError(err, "RequestTooLarge") +} + +// IsRequestMoved tests whether err wraps a TritonError with +// code RequestMoved +func IsRequestMoved(err error) bool { + return isSpecificError(err, "RequestMoved") +} + +// IsResourceNotFound tests whether err wraps a TritonError with +// code ResourceNotFound +func IsResourceNotFound(err error) bool { + return isSpecificError(err, "ResourceNotFound") +} + +// IsUnknownError tests whether err wraps a 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. +func isSpecificError(err error, errorCode string) bool { + if err == nil { + return false + } + + tritonErrorInterface := errwrap.GetType(err.(error), &TritonError{}) + if tritonErrorInterface == nil { + return false + } + + tritonErr := tritonErrorInterface.(*TritonError) + if tritonErr.Code == errorCode { + return true + } + + return false +} diff --git a/vendor/github.com/joyent/triton-go/fabrics.go b/vendor/github.com/joyent/triton-go/fabrics.go new file mode 100644 index 000000000..5404d10bb --- /dev/null +++ b/vendor/github.com/joyent/triton-go/fabrics.go @@ -0,0 +1,232 @@ +package triton + +import ( + "encoding/json" + "fmt" + "net/http" + + "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(*ListFabricVLANsInput) ([]*FabricVLAN, error) { + respReader, err := client.executeRequest(http.MethodGet, "/my/fabrics/default/vlans", 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(input *CreateFabricVLANInput) (*FabricVLAN, error) { + path := fmt.Sprintf("/%s/fabrics/default/vlans", client.accountName) + respReader, err := client.executeRequest(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(input *UpdateFabricVLANInput) (*FabricVLAN, error) { + path := fmt.Sprintf("/%s/fabrics/default/vlans/%d", client.accountName, input.ID) + respReader, err := client.executeRequest(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(input *GetFabricVLANInput) (*FabricVLAN, error) { + path := fmt.Sprintf("/%s/fabrics/default/vlans/%d", client.accountName, input.ID) + respReader, err := client.executeRequest(http.MethodGet, path, nil) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing 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(input *DeleteFabricVLANInput) error { + path := fmt.Sprintf("/%s/fabrics/default/vlans/%d", client.accountName, input.ID) + respReader, err := client.executeRequest(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(input *ListFabricNetworksInput) ([]*Network, error) { + path := fmt.Sprintf("/%s/fabrics/default/vlans/%d/networks", client.accountName, input.FabricVLANID) + respReader, err := client.executeRequest(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(input *CreateFabricNetworkInput) (*Network, error) { + path := fmt.Sprintf("/%s/fabrics/default/vlans/%d/networks", client.accountName, input.FabricVLANID) + respReader, err := client.executeRequest(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(input *GetFabricNetworkInput) (*Network, error) { + path := fmt.Sprintf("/%s/fabrics/default/vlans/%d/networks/%s", client.accountName, input.FabricVLANID, input.NetworkID) + respReader, err := client.executeRequest(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(input *DeleteFabricNetworkInput) error { + path := fmt.Sprintf("/%s/fabrics/default/vlans/%d/networks/%s", client.accountName, input.FabricVLANID, input.NetworkID) + respReader, err := client.executeRequest(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 new file mode 100644 index 000000000..c91d012d9 --- /dev/null +++ b/vendor/github.com/joyent/triton-go/firewall.go @@ -0,0 +1,212 @@ +package triton + +import ( + "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(*ListFirewallRulesInput) ([]*FirewallRule, error) { + respReader, err := client.executeRequest(http.MethodGet, "/my/fwrules", 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(input *GetFirewallRuleInput) (*FirewallRule, error) { + path := fmt.Sprintf("/%s/fwrules/%s", client.accountName, input.ID) + respReader, err := client.executeRequest(http.MethodGet, path, nil) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing 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(input *CreateFirewallRuleInput) (*FirewallRule, error) { + respReader, err := client.executeRequest(http.MethodPost, fmt.Sprintf("/%s/fwrules", client.accountName), 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(input *UpdateFirewallRuleInput) (*FirewallRule, error) { + respReader, err := client.executeRequest(http.MethodPost, fmt.Sprintf("/%s/fwrules/%s", client.accountName, input.ID), 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(input *EnableFirewallRuleInput) (*FirewallRule, error) { + respReader, err := client.executeRequest(http.MethodPost, fmt.Sprintf("/%s/fwrules/%s/enable", client.accountName, input.ID), 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(input *DisableFirewallRuleInput) (*FirewallRule, error) { + respReader, err := client.executeRequest(http.MethodPost, fmt.Sprintf("/%s/fwrules/%s/disable", client.accountName, input.ID), 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(input *DeleteFirewallRuleInput) error { + path := fmt.Sprintf("/%s/fwrules/%s", client.accountName, input.ID) + respReader, err := client.executeRequest(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(input *ListMachineFirewallRulesInput) ([]*FirewallRule, error) { + respReader, err := client.executeRequest(http.MethodGet, fmt.Sprintf("/my/machines/%s/firewallrules", input.MachineID), 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/images.go b/vendor/github.com/joyent/triton-go/images.go new file mode 100644 index 000000000..3e0aa8a75 --- /dev/null +++ b/vendor/github.com/joyent/triton-go/images.go @@ -0,0 +1,204 @@ +package triton + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "time" + + "github.com/hashicorp/errwrap" +) + +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} +} + +type ImageFile struct { + Compression string `json:"compression"` + SHA1 string `json:"sha1"` + Size int64 `json:"size"` +} + +type Image struct { + ID string `json:"id"` + Name string `json:"name"` + OS string `json:"os"` + Description string `json:"description"` + Version string `json:"version"` + Type string `json:"type"` + Requirements map[string]interface{} `json:"requirements"` + Homepage string `json:"homepage"` + Files []*ImageFile `json:"files"` + PublishedAt time.Time `json:"published_at"` + Owner string `json:"owner"` + Public bool `json:"public"` + State string `json:"state"` + Tags map[string]string `json:"tags"` + EULA string `json:"eula"` + ACL []string `json:"acl"` + Error TritonError `json:"error"` +} + +type ListImagesInput struct{} + +func (client *ImagesClient) ListImages(*ListImagesInput) ([]*Image, error) { + respReader, err := client.executeRequest(http.MethodGet, "/my/images", nil) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing ListImages 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 result, nil +} + +type GetImageInput struct { + ImageID string +} + +func (client *ImagesClient) GetImage(input *GetImageInput) (*Image, error) { + path := fmt.Sprintf("/%s/images/%s", client.accountName, input.ImageID) + respReader, err := client.executeRequest(http.MethodGet, path, nil) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing GetImage 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 result, nil +} + +type DeleteImageInput struct { + ImageID string +} + +func (client *ImagesClient) DeleteImage(input *DeleteImageInput) error { + path := fmt.Sprintf("/%s/images/%s", client.accountName, input.ImageID) + respReader, err := client.executeRequest(http.MethodDelete, path, nil) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return errwrap.Wrapf("Error executing DeleteKey request: {{err}}", err) + } + + return nil +} + +type ExportImageInput struct { + ImageID string + MantaPath string +} + +type MantaLocation struct { + MantaURL string `json:"manta_url"` + ImagePath string `json:"image_path"` + ManifestPath string `json:"manifest_path"` +} + +func (client *ImagesClient) ExportImage(input *ExportImageInput) (*MantaLocation, error) { + path := fmt.Sprintf("/%s/images/%s", client.accountName, input.ImageID) + query := &url.Values{} + query.Set("action", "export") + query.Set("manta_path", input.MantaPath) + + respReader, err := client.executeRequestURIParams(http.MethodGet, path, nil, query) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing GetImage 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 result, nil +} + +type CreateImageFromMachineInput struct { + MachineID string `json:"machine"` + Name string `json:"name"` + Version string `json:"version,omitempty"` + Description string `json:"description,omitempty"` + HomePage string `json:"homepage,omitempty"` + EULA string `json:"eula,omitempty"` + ACL []string `json:"acl,omitempty"` + tags map[string]string `json:"tags,omitempty"` +} + +func (client *ImagesClient) CreateImageFromMachine(input *CreateImageFromMachineInput) (*Image, error) { + path := fmt.Sprintf("/%s/images", client.accountName) + respReader, err := client.executeRequest(http.MethodPost, path, input) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing CreateImageFromMachine 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 result, nil +} + +type UpdateImageInput struct { + ImageID string `json:"-"` + Name string `json:"name"` + Version string `json:"version,omitempty"` + Description string `json:"description,omitempty"` + HomePage string `json:"homepage,omitempty"` + EULA string `json:"eula,omitempty"` + ACL []string `json:"acl,omitempty"` + tags map[string]string `json:"tags,omitempty"` +} + +func (client *ImagesClient) UpdateImage(input *UpdateImageInput) (*Image, error) { + path := fmt.Sprintf("/%s/images/%s", client.accountName, input.ImageID) + query := &url.Values{} + query.Set("action", "update") + + respReader, err := client.executeRequestURIParams(http.MethodPost, path, input, query) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing UpdateImage 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 result, nil +} diff --git a/vendor/github.com/joyent/triton-go/keys.go b/vendor/github.com/joyent/triton-go/keys.go new file mode 100644 index 000000000..a4f394a21 --- /dev/null +++ b/vendor/github.com/joyent/triton-go/keys.go @@ -0,0 +1,122 @@ +package triton + +import ( + "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(*ListKeysInput) ([]*Key, error) { + respReader, err := client.executeRequest(http.MethodGet, "/my/keys", 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(input *GetKeyInput) (*Key, error) { + path := fmt.Sprintf("/%s/keys/%s", client.accountName, input.KeyName) + respReader, err := client.executeRequest(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(input *DeleteKeyInput) error { + path := fmt.Sprintf("/%s/keys/%s", client.accountName, input.KeyName) + respReader, err := client.executeRequest(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(input *CreateKeyInput) (*Key, error) { + respReader, err := client.executeRequest(http.MethodPost, fmt.Sprintf("/%s/keys", client.accountName), 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 new file mode 100644 index 000000000..8f2de9736 --- /dev/null +++ b/vendor/github.com/joyent/triton-go/machines.go @@ -0,0 +1,472 @@ +package triton + +import ( + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/hashicorp/errwrap" + "net/url" +) + +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} +} + +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"` +} + +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 (client *MachinesClient) GetMachine(input *GetMachineInput) (*Machine, error) { + path := fmt.Sprintf("/%s/machines/%s", client.accountName, input.ID) + response, err := client.executeRequestRaw(http.MethodGet, path, nil) + if response != nil { + defer response.Body.Close() + } + if response.StatusCode == http.StatusNotFound { + return nil, &TritonError{ + Code: "ResourceNotFound", + } + } + if err != nil { + return nil, errwrap.Wrapf("Error executing GetMachine request: {{err}}", + client.decodeError(response.StatusCode, response.Body)) + } + + var result *Machine + decoder := json.NewDecoder(response.Body) + if err = decoder.Decode(&result); err != nil { + return nil, errwrap.Wrapf("Error decoding GetMachine response: {{err}}", err) + } + + return result, 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 +} + +func transformCreateMachineInput(input *CreateMachineInput) map[string]interface{} { + result := make(map[string]interface{}, 8+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 + } + for key, value := range input.Metadata { + result[fmt.Sprintf("metadata.%s", key)] = value + } + + return result +} + +func (client *MachinesClient) CreateMachine(input *CreateMachineInput) (*Machine, error) { + respReader, err := client.executeRequest(http.MethodPost, "/my/machines", transformCreateMachineInput(input)) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing CreateMachine request: {{err}}", err) + } + + var result *Machine + decoder := json.NewDecoder(respReader) + if err = decoder.Decode(&result); err != nil { + return nil, errwrap.Wrapf("Error decoding CreateMachine response: {{err}}", err) + } + + return result, nil +} + +type DeleteMachineInput struct { + ID string +} + +func (client *MachinesClient) DeleteMachine(input *DeleteMachineInput) error { + path := fmt.Sprintf("/%s/machines/%s", client.accountName, input.ID) + response, err := client.executeRequestRaw(http.MethodDelete, path, nil) + if response.Body != nil { + defer response.Body.Close() + } + if response.StatusCode == http.StatusNotFound { + return nil + } + if err != nil { + return errwrap.Wrapf("Error executing DeleteMachine request: {{err}}", + client.decodeError(response.StatusCode, response.Body)) + } + + return nil +} + +type DeleteMachineTagsInput struct { + ID string +} + +func (client *MachinesClient) DeleteMachineTags(input *DeleteMachineTagsInput) error { + path := fmt.Sprintf("/%s/machines/%s/tags", client.accountName, input.ID) + response, err := client.executeRequestRaw(http.MethodDelete, path, nil) + if response.Body != nil { + defer response.Body.Close() + } + if response.StatusCode == http.StatusNotFound { + return nil + } + if err != nil { + return errwrap.Wrapf("Error executing DeleteMachineTags request: {{err}}", + client.decodeError(response.StatusCode, response.Body)) + } + + return nil +} + +type DeleteMachineTagInput struct { + ID string + Key string +} + +func (client *MachinesClient) DeleteMachineTag(input *DeleteMachineTagInput) error { + path := fmt.Sprintf("/%s/machines/%s/tags/%s", client.accountName, input.ID, input.Key) + response, err := client.executeRequestRaw(http.MethodDelete, path, nil) + if response.Body != nil { + defer response.Body.Close() + } + if response.StatusCode == http.StatusNotFound { + return nil + } + if err != nil { + return errwrap.Wrapf("Error executing DeleteMachineTag request: {{err}}", + client.decodeError(response.StatusCode, response.Body)) + } + + return nil +} + +type RenameMachineInput struct { + ID string + Name string +} + +func (client *MachinesClient) RenameMachine(input *RenameMachineInput) error { + path := fmt.Sprintf("/%s/machines/%s", client.accountName, input.ID) + + params := &url.Values{} + params.Set("action", "rename") + params.Set("name", input.Name) + + respReader, err := client.executeRequestURIParams(http.MethodPost, path, nil, params) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return errwrap.Wrapf("Error executing RenameMachine request: {{err}}", err) + } + + return nil +} + +type ReplaceMachineTagsInput struct { + ID string + Tags map[string]string +} + +func (client *MachinesClient) ReplaceMachineTags(input *ReplaceMachineTagsInput) error { + path := fmt.Sprintf("/%s/machines/%s/tags", client.accountName, input.ID) + respReader, err := client.executeRequest(http.MethodPut, path, input.Tags) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return errwrap.Wrapf("Error executing ReplaceMachineTags request: {{err}}", err) + } + + return nil +} + +type AddMachineTagsInput struct { + ID string + Tags map[string]string +} + +func (client *MachinesClient) AddMachineTags(input *AddMachineTagsInput) error { + path := fmt.Sprintf("/%s/machines/%s/tags", client.accountName, input.ID) + respReader, err := client.executeRequest(http.MethodPost, path, input.Tags) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return errwrap.Wrapf("Error executing AddMachineTags request: {{err}}", err) + } + + return nil +} + +type GetMachineTagInput struct { + ID string + Key string +} + +func (client *MachinesClient) GetMachineTag(input *GetMachineTagInput) (string, error) { + path := fmt.Sprintf("/%s/machines/%s/tags/%s", client.accountName, input.ID, input.Key) + respReader, err := client.executeRequest(http.MethodGet, path, nil) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return "", errwrap.Wrapf("Error executing GetMachineTag request: {{err}}", err) + } + + var result string + decoder := json.NewDecoder(respReader) + if err = decoder.Decode(&result); err != nil { + return "", errwrap.Wrapf("Error decoding GetMachineTag response: {{err}}", err) + } + + return result, nil +} + +type ListMachineTagsInput struct { + ID string +} + +func (client *MachinesClient) ListMachineTags(input *ListMachineTagsInput) (map[string]string, error) { + path := fmt.Sprintf("/%s/machines/%s/tags", client.accountName, input.ID) + respReader, err := client.executeRequest(http.MethodGet, path, nil) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing ListMachineTags request: {{err}}", err) + } + + var result map[string]string + decoder := json.NewDecoder(respReader) + if err = decoder.Decode(&result); err != nil { + return nil, errwrap.Wrapf("Error decoding ListMachineTags response: {{err}}", err) + } + + return result, nil +} + +type UpdateMachineMetadataInput struct { + ID string + Metadata map[string]string +} + +func (client *MachinesClient) UpdateMachineMetadata(input *UpdateMachineMetadataInput) (map[string]string, error) { + path := fmt.Sprintf("/%s/machines/%s/tags", client.accountName, input.ID) + respReader, err := client.executeRequest(http.MethodPost, path, input.Metadata) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing UpdateMachineMetadata request: {{err}}", err) + } + + var result map[string]string + decoder := json.NewDecoder(respReader) + if err = decoder.Decode(&result); err != nil { + return nil, errwrap.Wrapf("Error decoding UpdateMachineMetadata response: {{err}}", err) + } + + return result, nil +} + +type ResizeMachineInput struct { + ID string + Package string +} + +func (client *MachinesClient) ResizeMachine(input *ResizeMachineInput) error { + path := fmt.Sprintf("/%s/machines/%s", client.accountName, input.ID) + + params := &url.Values{} + params.Set("action", "resize") + params.Set("package", input.Package) + + respReader, err := client.executeRequestURIParams(http.MethodPost, path, nil, params) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return errwrap.Wrapf("Error executing ResizeMachine request: {{err}}", err) + } + + return nil +} + +type EnableMachineFirewallInput struct { + ID string +} + +func (client *MachinesClient) EnableMachineFirewall(input *EnableMachineFirewallInput) error { + path := fmt.Sprintf("/%s/machines/%s", client.accountName, input.ID) + + params := &url.Values{} + params.Set("action", "enable_firewall") + + respReader, err := client.executeRequestURIParams(http.MethodPost, path, nil, params) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return errwrap.Wrapf("Error executing EnableMachineFirewall request: {{err}}", err) + } + + return nil +} + +type DisableMachineFirewallInput struct { + ID string +} + +func (client *MachinesClient) DisableMachineFirewall(input *DisableMachineFirewallInput) error { + path := fmt.Sprintf("/%s/machines/%s", client.accountName, input.ID) + + params := &url.Values{} + params.Set("action", "disable_firewall") + + respReader, err := client.executeRequestURIParams(http.MethodPost, path, nil, params) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return errwrap.Wrapf("Error executing DisableMachineFirewall request: {{err}}", err) + } + + return nil +} + +type ListNICsInput struct { + MachineID string +} + +func (client *MachinesClient) ListNICs(input *ListNICsInput) ([]*NIC, error) { + respReader, err := client.executeRequest(http.MethodGet, fmt.Sprintf("/my/machines/%s/nics", input.MachineID), nil) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing ListNICs request: {{err}}", err) + } + + var result []*NIC + decoder := json.NewDecoder(respReader) + if err = decoder.Decode(&result); err != nil { + return nil, errwrap.Wrapf("Error decoding ListNICs response: {{err}}", err) + } + + return result, nil +} + +type AddNICInput struct { + MachineID string `json:"-"` + Network string `json:"network"` +} + +func (client *MachinesClient) AddNIC(input *AddNICInput) (*NIC, error) { + path := fmt.Sprintf("/%s/machines/%s/nics", client.accountName, input.MachineID) + respReader, err := client.executeRequest(http.MethodPost, path, input) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing AddNIC request: {{err}}", err) + } + + var result *NIC + decoder := json.NewDecoder(respReader) + if err = decoder.Decode(&result); err != nil { + return nil, errwrap.Wrapf("Error decoding AddNIC response: {{err}}", err) + } + + return result, nil +} + +type RemoveNICInput struct { + MachineID string + MAC string +} + +func (client *MachinesClient) RemoveNIC(input *RemoveNICInput) error { + path := fmt.Sprintf("/%s/machines/%s/nics/%s", client.accountName, input.MachineID, input.MAC) + respReader, err := client.executeRequest(http.MethodDelete, path, nil) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return errwrap.Wrapf("Error executing RemoveNIC request: {{err}}", err) + } + + return nil +} diff --git a/vendor/github.com/joyent/triton-go/networks.go b/vendor/github.com/joyent/triton-go/networks.go new file mode 100644 index 000000000..cb1ec1700 --- /dev/null +++ b/vendor/github.com/joyent/triton-go/networks.go @@ -0,0 +1,77 @@ +package triton + +import ( + "encoding/json" + "net/http" + + "fmt" + "github.com/hashicorp/errwrap" +) + +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"` + Public bool `json:"public"` + Fabric bool `json:"fabric"` + Description string `json:"description"` + Subnet string `json:"subnet"` + ProvisioningStartIP string `json:"provision_start_ip"` + ProvisioningEndIP string `json:"provision_end_ip"` + Gateway string `json:"gateway"` + Resolvers []string `json:"resolvers"` + Routes map[string]string `json:"routes"` + InternetNAT bool `json:"internet_nat"` +} + +type ListNetworksInput struct{} + +func (client *NetworksClient) ListNetworks(*ListNetworksInput) ([]*Network, error) { + respReader, err := client.executeRequest(http.MethodGet, "/my/networks", nil) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing ListNetworks request: {{err}}", err) + } + + var result []*Network + decoder := json.NewDecoder(respReader) + if err = decoder.Decode(&result); err != nil { + return nil, errwrap.Wrapf("Error decoding ListNetworks response: {{err}}", err) + } + + return result, nil +} + +type GetNetworkInput struct { + ID string +} + +func (client *NetworksClient) GetNetwork(input *GetNetworkInput) (*Network, error) { + path := fmt.Sprintf("/%s/networks/%s", client.accountName, input.ID) + respReader, err := client.executeRequest(http.MethodGet, path, nil) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing GetNetwork request: {{err}}", err) + } + + var result *Network + decoder := json.NewDecoder(respReader) + if err = decoder.Decode(&result); err != nil { + return nil, errwrap.Wrapf("Error decoding GetNetwork response: {{err}}", err) + } + + return result, nil +} diff --git a/vendor/github.com/joyent/triton-go/packages.go b/vendor/github.com/joyent/triton-go/packages.go new file mode 100644 index 000000000..2e525acfe --- /dev/null +++ b/vendor/github.com/joyent/triton-go/packages.go @@ -0,0 +1,85 @@ +package triton + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/hashicorp/errwrap" +) + +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} +} + +type Package struct { + ID string `json:"id"` + Name string `json:"name"` + Memory int64 `json:"memory"` + Disk int64 `json:"disk"` + Swap int64 `json:"swap"` + LWPs int64 `json:"lwps"` + VCPUs int64 `json:"vcpus"` + Version string `json:"version"` + Group string `json:"group"` + Description string `json:"description"` + Default bool `json:"default"` +} + +type ListPackagesInput struct { + Name string `json:"name"` + Memory int64 `json:"memory"` + Disk int64 `json:"disk"` + Swap int64 `json:"swap"` + LWPs int64 `json:"lwps"` + VCPUs int64 `json:"vcpus"` + Version string `json:"version"` + Group string `json:"group"` +} + +func (client *PackagesClient) ListPackages(input *ListPackagesInput) ([]*Package, error) { + respReader, err := client.executeRequest(http.MethodGet, fmt.Sprintf("/%s/packages", client.accountName), input) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing ListPackages 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 result, nil +} + +type GetPackageInput struct { + ID string +} + +func (client *PackagesClient) GetPackage(input *GetPackageInput) (*Package, error) { + path := fmt.Sprintf("/%s/packages/%s", client.accountName, input.ID) + respReader, err := client.executeRequest(http.MethodGet, path, nil) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing GetPackage 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 result, nil +} diff --git a/vendor/github.com/joyent/triton-go/roles.go b/vendor/github.com/joyent/triton-go/roles.go new file mode 100644 index 000000000..81bbc44e7 --- /dev/null +++ b/vendor/github.com/joyent/triton-go/roles.go @@ -0,0 +1,159 @@ +package triton + +import ( + "fmt" + "github.com/hashicorp/errwrap" + "net/http" + "encoding/json" +) + +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(*ListRolesInput) ([]*Role, error) { + respReader, err := client.executeRequest(http.MethodGet, fmt.Sprintf("/%s/roles", client.accountName), 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(input *GetRoleInput) (*Role, error) { + path := fmt.Sprintf("/%s/roles/%s", client.accountName, input.RoleID) + respReader, err := client.executeRequest(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(input *CreateRoleInput) (*Role, error) { + respReader, err := client.executeRequest(http.MethodPost, fmt.Sprintf("/%s/roles", client.accountName), 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(input *UpdateRoleInput) (*Role, error) { + respReader, err := client.executeRequest(http.MethodPost, fmt.Sprintf("/%s/roles/%s", client.accountName, input.RoleID), 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(input *DeleteRoleInput) error { + path := fmt.Sprintf("/%s/roles/%s", client.accountName, input.RoleID) + respReader, err := client.executeRequest(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/services.go b/vendor/github.com/joyent/triton-go/services.go new file mode 100644 index 000000000..8b2d53191 --- /dev/null +++ b/vendor/github.com/joyent/triton-go/services.go @@ -0,0 +1,63 @@ +package triton + +import ( + "encoding/json" + "fmt" + "net/http" + "sort" + + "github.com/hashicorp/errwrap" +) + +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} +} + +type Service struct { + Name string + Endpoint string +} + +type ListServicesInput struct{} + +func (client *ServicesClient) ListServices(*ListServicesInput) ([]*Service, error) { + respReader, err := client.executeRequest(http.MethodGet, fmt.Sprintf("/%s/services", client.accountName), nil) + if respReader != nil { + defer respReader.Close() + } + if err != nil { + return nil, errwrap.Wrapf("Error executing ListServices 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) + } + + keys := make([]string, len(intermediate)) + i := 0 + for k := range intermediate { + keys[i] = k + i++ + } + sort.Strings(keys) + + result := make([]*Service, len(intermediate)) + i = 0 + for _, key := range keys { + result[i] = &Service{ + Name: key, + Endpoint: intermediate[key], + } + i++ + } + + return result, nil +} diff --git a/vendor/vendor.json b/vendor/vendor.json index ee9f48dda..e8993495c 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -2287,18 +2287,24 @@ "revision": "ece4f0cbe61f600794bbcff71d8f9ee86909b2dc", "revisionTime": "2016-09-13T20:25:01Z" }, - { - "checksumSHA1": "PDzjpRNeytdYU39/PByzwCMvKQ8=", - "path": "github.com/joyent/gosdc/cloudapi", - "revision": "042c6e9de2b48a646d310e70cc0050c83fe18200", - "revisionTime": "2016-04-26T05:09:12Z" - }, { "checksumSHA1": "N0NRIcJF7aj1wd56DA1N9GpYq/4=", "path": "github.com/joyent/gosign/auth", "revision": "8978c75ffefb3f63a977ad9cbfce40caeb40177e", "revisionTime": "2016-06-16T18:50:15Z" }, + { + "checksumSHA1": "fue8Al8kqw/Q6VFPsNzoky7NIgo=", + "path": "github.com/joyent/triton-go", + "revision": "ed036af6d128e3c1ef76e92218810d3b298d1407", + "revisionTime": "2017-03-30T22:02:44Z" + }, + { + "checksumSHA1": "7sIV9LK625xVO9WsV1gaLQgfBeY=", + "path": "github.com/joyent/triton-go/authentication", + "revision": "ed036af6d128e3c1ef76e92218810d3b298d1407", + "revisionTime": "2017-03-30T22:02:44Z" + }, { "checksumSHA1": "YhQcOsGx8r2S/jkJ0Qt4cZ5BLCU=", "comment": "v0.3.0-33-g53d1c0a", diff --git a/website/source/docs/providers/triton/index.html.markdown b/website/source/docs/providers/triton/index.html.markdown index 4954f1291..1660ad718 100644 --- a/website/source/docs/providers/triton/index.html.markdown +++ b/website/source/docs/providers/triton/index.html.markdown @@ -1,12 +1,12 @@ --- layout: "triton" -page_title: "Provider: Triton" +page_title: "Provider: Joyent Triton" sidebar_current: "docs-triton-index" description: |- Used to provision infrastructure in Joyent's Triton public or on-premise clouds. --- -# Triton Provider +# Joyent Triton Provider The Triton provider is used to interact with resources in Joyent's Triton cloud. It is compatible with both public- and on-premise installations of Triton. The provider needs to be configured with the proper credentials before it can be used. @@ -16,11 +16,11 @@ Use the navigation to the left to read about the available resources. ``` provider "triton" { - account = "AccountName" - key_material = "${file("~/.ssh/id_rsa")}" - key_id = "25:d4:a9:fe:ef:e6:c0:bf:b4:4b:4b:d4:a8:8f:01:0f" + account = "AccountName" + key_id = "25:d4:a9:fe:ef:e6:c0:bf:b4:4b:4b:d4:a8:8f:01:0f" - # Set the URL to specify the specific Triton Data Center: + # If using a private installation of Triton, specify the URL, otherwise + # set the URL according to the region you wish to provision. url = "https://us-west-1.api.joyentcloud.com" } ``` @@ -30,6 +30,6 @@ provider "triton" { The following arguments are supported in the `provider` block: * `account` - (Required) This is the name of the Triton account. It can also be provided via the `SDC_ACCOUNT` environment variable. -* `key_material` - (Required) This is the private key of an SSH key associated with the Triton account to be used. +* `key_material` - (Optional) This is the private key of an SSH key associated with the Triton account to be used. If this is not set, the private key corresponding to the fingerprint in `key_id` must be available via an SSH Agent. * `key_id` - (Required) This is the fingerprint of the public key matching the key specified in `key_path`. It can be obtained via the command `ssh-keygen -l -E md5 -f /path/to/key` * `url` - (Optional) This is the URL to the Triton API endpoint. It is required if using a private installation of Triton. The default is to use the Joyent public cloud us-west-1 endpoint. Valid public cloud endpoints include: `us-east-1`, `us-east-2`, `us-east-3`, `us-sw-1`, `us-west-1`, `eu-ams-1` diff --git a/website/source/layouts/triton.erb b/website/source/layouts/triton.erb index 3e048b3ea..75e256869 100644 --- a/website/source/layouts/triton.erb +++ b/website/source/layouts/triton.erb @@ -7,7 +7,7 @@ > - Triton Provider + Joyent Triton Provider >