provider/triton: Move to joyent/triton-go (#13225)

* provider/triton: Move to joyent/triton-go

This commit moves the Triton provider to the new joyent/triton-go
library from gosdc. This has a number of advantages - not least that
requests can be signed using an SSH agent without having to keep
unencrypted key material in memory.

Schema has been maintained for all resources, and several tests have
been added and acceptance tests repaired - in some cases by fixing bugs
in the underlying resources.

After applying this patch, all acceptance tests pass:

```
go generate $(go list ./... | grep -v /terraform/vendor/)
2017/03/30 13:48:33 Generated command/internal_plugin_list.go
TF_ACC=1 go test ./builtin/providers/triton -v  -timeout 120m
=== RUN   TestProvider
--- PASS: TestProvider (0.00s)
=== RUN   TestProvider_impl
--- PASS: TestProvider_impl (0.00s)
=== RUN   TestAccTritonFabric_basic
--- PASS: TestAccTritonFabric_basic (15.11s)
=== RUN   TestAccTritonFirewallRule_basic
--- PASS: TestAccTritonFirewallRule_basic (1.48s)
=== RUN   TestAccTritonFirewallRule_update
--- PASS: TestAccTritonFirewallRule_update (1.55s)
=== RUN   TestAccTritonFirewallRule_enable
--- PASS: TestAccTritonFirewallRule_enable (1.52s)
=== RUN   TestAccTritonKey_basic
--- PASS: TestAccTritonKey_basic (11.76s)
=== RUN   TestAccTritonKey_noKeyName
--- PASS: TestAccTritonKey_noKeyName (11.20s)
=== RUN   TestAccTritonMachine_basic
--- PASS: TestAccTritonMachine_basic (82.19s)
=== RUN   TestAccTritonMachine_dns
--- PASS: TestAccTritonMachine_dns (173.36s)
=== RUN   TestAccTritonMachine_nic
--- PASS: TestAccTritonMachine_nic (167.82s)
=== RUN   TestAccTritonMachine_addNIC
--- PASS: TestAccTritonMachine_addNIC (192.11s)
=== RUN   TestAccTritonMachine_firewall
--- PASS: TestAccTritonMachine_firewall (188.53s)
=== RUN   TestAccTritonMachine_metadata
--- PASS: TestAccTritonMachine_metadata (614.57s)
=== RUN   TestAccTritonVLAN_basic
--- PASS: TestAccTritonVLAN_basic (0.93s)
=== RUN   TestAccTritonVLAN_update
--- PASS: TestAccTritonVLAN_update (1.50s)
PASS
ok  	github.com/hashicorp/terraform/builtin/providers/triton	1463.621s
```

* provider/triton: Update docs for provider config

* deps: Vendor github.com/joyent/triton-go/...

* deps: Remove github.com/joyent/gosdc
This commit is contained in:
James Nugent 2017-03-30 15:25:27 -07:00 committed by Paul Stack
parent 90b73d421a
commit a0568e544f
55 changed files with 3499 additions and 2387 deletions

View File

@ -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, err
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)
}
}
creds := &auth.Credentials{
UserAuthentication: userauth,
SdcKeyId: c.KeyID,
SdcEndpoint: auth.Endpoint{URL: c.URL},
client, err := triton.NewClient(c.URL, c.Account, signer)
if err != nil {
return nil, errwrap.Wrapf("Error Creating Triton Client: {{err}}", err)
}
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{
config := Config{
Account: d.Get("account").(string),
URL: d.Get("url").(string),
KeyMaterial: d.Get("key_material").(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),
}

View File

@ -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.")
}
}

View File

@ -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,19 +104,18 @@ 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{
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),
ProvisionStartIP: d.Get("provision_start_ip").(string),
ProvisionEndIP: d.Get("provision_end_ip").(string),
Gateway: d.Get("gateway").(string),
Resolvers: resolvers,
Routes: routes,
@ -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(),
})
}

View File

@ -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)
}
if fabric == nil {
return fmt.Errorf("Bad: Fabric %q does not exist", rs.Primary.ID)
return fmt.Errorf("Error: Check Fabric Exists: %s", err)
}
if exists {
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"

View File

@ -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, 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{
_, 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(),
})
}

View File

@ -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"
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
}
`

View File

@ -5,13 +5,7 @@ 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 {
@ -20,20 +14,21 @@ func resourceKey() *schema.Resource {
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
}
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(),
})
}

View File

@ -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" {
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)
}

View File

@ -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
resourceMachineMetadataKeys = map[string]string{
// semantics: "schema_name": "metadata_name"
@ -35,45 +33,41 @@ func resourceMachine() *schema.Resource {
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())
err := client.Machines().DeleteMachine(&triton.DeleteMachineInput{
ID: d.Id(),
})
if err != nil {
return err
}
waitForMachineState(client, d.Id(), machineStateStopped, machineStateChangeTimeout)
}
err = client.DeleteMachine(d.Id())
stateConf := &resource.StateChangeConf{
Target: []string{machineStateDeleted},
Refresh: func() (interface{}, string, error) {
getResp, err := client.Machines().GetMachine(&triton.GetMachineInput{
ID: d.Id(),
})
if err != nil {
return err
if triton.IsResourceNotFound(err) {
return nil, "deleted", nil
}
return nil, "", err
}
waitForMachineState(client, d.Id(), machineStateDeleted, machineStateChangeTimeout)
return nil
}
func readMachineState(api *cloudapi.Client, id string) (string, error) {
machine, err := api.GetMachine(id)
if err != nil {
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
return getResp, getResp.State, nil
},
machineStateChangeCheckInterval,
machineStateChangeTimeout,
)
Timeout: machineStateChangeTimeout,
MinTimeout: 3 * time.Second,
}
_, err = stateConf.WaitForState()
if err != nil {
return err
}
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
}

View File

@ -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 {
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
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"]
}
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!"
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
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"]
}
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!"
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)}"
}

View File

@ -5,7 +5,7 @@ import (
"strconv"
"github.com/hashicorp/terraform/helper/schema"
"github.com/joyent/gosdc/cloudapi"
"github.com/joyent/triton-go"
)
func resourceVLAN() *schema.Resource {
@ -15,13 +15,14 @@ func resourceVLAN() *schema.Resource {
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
}

View File

@ -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
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
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)
}
`

View File

@ -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
}

View File

@ -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...)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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 <target a> TO <target b> <action> <protocol> <port>'
}
// 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 <target a> TO <target b> <action> <protocol> <port>'
}
// 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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

216
vendor/github.com/joyent/triton-go/README.md generated vendored Normal file
View File

@ -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

92
vendor/github.com/joyent/triton-go/accounts.go generated vendored Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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, ":")
}

179
vendor/github.com/joyent/triton-go/client.go generated vendored Normal file
View File

@ -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
}

71
vendor/github.com/joyent/triton-go/config.go generated vendored Normal file
View File

@ -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
}

90
vendor/github.com/joyent/triton-go/datacenters.go generated vendored Normal file
View File

@ -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
}

126
vendor/github.com/joyent/triton-go/errors.go generated vendored Normal file
View File

@ -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
}

232
vendor/github.com/joyent/triton-go/fabrics.go generated vendored Normal file
View File

@ -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
}

212
vendor/github.com/joyent/triton-go/firewall.go generated vendored Normal file
View File

@ -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
}

204
vendor/github.com/joyent/triton-go/images.go generated vendored Normal file
View File

@ -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
}

122
vendor/github.com/joyent/triton-go/keys.go generated vendored Normal file
View File

@ -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
}

472
vendor/github.com/joyent/triton-go/machines.go generated vendored Normal file
View File

@ -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
}

77
vendor/github.com/joyent/triton-go/networks.go generated vendored Normal file
View File

@ -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
}

85
vendor/github.com/joyent/triton-go/packages.go generated vendored Normal file
View File

@ -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
}

159
vendor/github.com/joyent/triton-go/roles.go generated vendored Normal file
View File

@ -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
}

63
vendor/github.com/joyent/triton-go/services.go generated vendored Normal file
View File

@ -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
}

18
vendor/vendor.json vendored
View File

@ -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",

View File

@ -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.
@ -17,10 +17,10 @@ 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"
# 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`

View File

@ -7,7 +7,7 @@
</li>
<li<%= sidebar_current("docs-triton-index") %>>
<a href="/docs/providers/triton/index.html">Triton Provider</a>
<a href="/docs/providers/triton/index.html">Joyent Triton Provider</a>
</li>
<li<%= sidebar_current(/^docs-triton-resource/) %>>