digitalocean tag support (#7500)
* vendor: update godo to support tags * digitalocean: introduce tag resource * website: update for digitalocean_tag resource
This commit is contained in:
parent
d70b9d334b
commit
0c6856f85c
|
@ -23,6 +23,7 @@ func Provider() terraform.ResourceProvider {
|
|||
"digitalocean_floating_ip": resourceDigitalOceanFloatingIp(),
|
||||
"digitalocean_record": resourceDigitalOceanRecord(),
|
||||
"digitalocean_ssh_key": resourceDigitalOceanSSHKey(),
|
||||
"digitalocean_tag": resourceDigitalOceanTag(),
|
||||
},
|
||||
|
||||
ConfigureFunc: providerConfigure,
|
||||
|
|
|
@ -104,6 +104,12 @@ func resourceDigitalOceanDroplet() *schema.Resource {
|
|||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
},
|
||||
|
||||
"tags": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
},
|
||||
|
||||
"user_data": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
|
@ -181,6 +187,12 @@ func resourceDigitalOceanDropletCreate(d *schema.ResourceData, meta interface{})
|
|||
"Error waiting for droplet (%s) to become ready: %s", d.Id(), err)
|
||||
}
|
||||
|
||||
// droplet needs to be active in order to set tags
|
||||
err = setTags(client, d)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error setting tags: %s", err)
|
||||
}
|
||||
|
||||
return resourceDigitalOceanDropletRead(d, meta)
|
||||
}
|
||||
|
||||
|
@ -236,6 +248,8 @@ func resourceDigitalOceanDropletRead(d *schema.ResourceData, meta interface{}) e
|
|||
"host": findIPv4AddrByType(droplet, "public"),
|
||||
})
|
||||
|
||||
d.Set("tags", droplet.Tags)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -379,6 +393,13 @@ func resourceDigitalOceanDropletUpdate(d *schema.ResourceData, meta interface{})
|
|||
}
|
||||
}
|
||||
|
||||
if d.HasChange("tags") {
|
||||
err = setTags(client, d)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error updating tags: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return resourceDigitalOceanDropletRead(d, meta)
|
||||
}
|
||||
|
||||
|
|
|
@ -103,6 +103,40 @@ func TestAccDigitalOceanDroplet_UpdateUserData(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestAccDigitalOceanDroplet_UpdateTags(t *testing.T) {
|
||||
var afterCreate, afterUpdate godo.Droplet
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckDigitalOceanDropletDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccCheckDigitalOceanDropletConfig_basic,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckDigitalOceanDropletExists("digitalocean_droplet.foobar", &afterCreate),
|
||||
testAccCheckDigitalOceanDropletAttributes(&afterCreate),
|
||||
),
|
||||
},
|
||||
|
||||
resource.TestStep{
|
||||
Config: testAccCheckDigitalOceanDropletConfig_tag_update,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckDigitalOceanDropletExists("digitalocean_droplet.foobar", &afterUpdate),
|
||||
resource.TestCheckResourceAttr(
|
||||
"digitalocean_droplet.foobar",
|
||||
"tags.#",
|
||||
"1"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"digitalocean_droplet.foobar",
|
||||
"tags.0",
|
||||
"barbaz"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccDigitalOceanDroplet_PrivateNetworkingIpv6(t *testing.T) {
|
||||
var droplet godo.Droplet
|
||||
|
||||
|
@ -309,6 +343,27 @@ resource "digitalocean_droplet" "foobar" {
|
|||
}
|
||||
`, testAccValidPublicKey)
|
||||
|
||||
var testAccCheckDigitalOceanDropletConfig_tag_update = fmt.Sprintf(`
|
||||
resource "digitalocean_tag" "barbaz" {
|
||||
name = "barbaz"
|
||||
}
|
||||
|
||||
resource "digitalocean_ssh_key" "foobar" {
|
||||
name = "foobar"
|
||||
public_key = "%s"
|
||||
}
|
||||
|
||||
resource "digitalocean_droplet" "foobar" {
|
||||
name = "foo"
|
||||
size = "512mb"
|
||||
image = "centos-5-8-x32"
|
||||
region = "nyc3"
|
||||
user_data = "foobar"
|
||||
ssh_keys = ["${digitalocean_ssh_key.foobar.id}"]
|
||||
tags = ["${digitalocean_tag.barbaz.id}"]
|
||||
}
|
||||
`, testAccValidPublicKey)
|
||||
|
||||
var testAccCheckDigitalOceanDropletConfig_userdata_update = fmt.Sprintf(`
|
||||
resource "digitalocean_ssh_key" "foobar" {
|
||||
name = "foobar"
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
package digitalocean
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/digitalocean/godo"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
func resourceDigitalOceanTag() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceDigitalOceanTagCreate,
|
||||
Read: resourceDigitalOceanTagRead,
|
||||
Update: resourceDigitalOceanTagUpdate,
|
||||
Delete: resourceDigitalOceanTagDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
State: schema.ImportStatePassthrough,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceDigitalOceanTagCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*godo.Client)
|
||||
|
||||
// Build up our creation options
|
||||
opts := &godo.TagCreateRequest{
|
||||
Name: d.Get("name").(string),
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Tag create configuration: %#v", opts)
|
||||
tag, _, err := client.Tags.Create(opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating tag: %s", err)
|
||||
}
|
||||
|
||||
d.SetId(tag.Name)
|
||||
log.Printf("[INFO] Tag: %s", tag.Name)
|
||||
|
||||
return resourceDigitalOceanTagRead(d, meta)
|
||||
}
|
||||
|
||||
func resourceDigitalOceanTagRead(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*godo.Client)
|
||||
|
||||
tag, resp, err := client.Tags.Get(d.Id())
|
||||
if err != nil {
|
||||
// If the tag is somehow already destroyed, mark as
|
||||
// successfully gone
|
||||
if resp != nil && resp.StatusCode == 404 {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("Error retrieving tag: %s", err)
|
||||
}
|
||||
|
||||
d.Set("name", tag.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceDigitalOceanTagUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*godo.Client)
|
||||
|
||||
var newName string
|
||||
if v, ok := d.GetOk("name"); ok {
|
||||
newName = v.(string)
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] tag update name: %#v", newName)
|
||||
opts := &godo.TagUpdateRequest{
|
||||
Name: newName,
|
||||
}
|
||||
|
||||
_, err := client.Tags.Update(d.Id(), opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to update tag: %s", err)
|
||||
}
|
||||
|
||||
d.Set("name", newName)
|
||||
|
||||
return resourceDigitalOceanTagRead(d, meta)
|
||||
}
|
||||
|
||||
func resourceDigitalOceanTagDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*godo.Client)
|
||||
|
||||
log.Printf("[INFO] Deleting tag: %s", d.Id())
|
||||
_, err := client.Tags.Delete(d.Id())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error deleting tag: %s", err)
|
||||
}
|
||||
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
package digitalocean
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/digitalocean/godo"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestAccDigitalOceanTag_Basic(t *testing.T) {
|
||||
var tag godo.Tag
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckDigitalOceanTagDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccCheckDigitalOceanTagConfig_basic,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckDigitalOceanTagExists("digitalocean_tag.foobar", &tag),
|
||||
testAccCheckDigitalOceanTagAttributes(&tag),
|
||||
resource.TestCheckResourceAttr(
|
||||
"digitalocean_tag.foobar", "name", "foobar"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckDigitalOceanTagDestroy(s *terraform.State) error {
|
||||
client := testAccProvider.Meta().(*godo.Client)
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "digitalocean_tag" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Try to find the key
|
||||
_, _, err := client.Tags.Get(rs.Primary.ID)
|
||||
|
||||
if err == nil {
|
||||
return fmt.Errorf("Tag still exists")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckDigitalOceanTagAttributes(tag *godo.Tag) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
|
||||
if tag.Name != "foobar" {
|
||||
return fmt.Errorf("Bad name: %s", tag.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckDigitalOceanTagExists(n string, tag *godo.Tag) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("No Record ID is set")
|
||||
}
|
||||
|
||||
client := testAccProvider.Meta().(*godo.Client)
|
||||
|
||||
// Try to find the tag
|
||||
foundTag, _, err := client.Tags.Get(rs.Primary.ID)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*tag = *foundTag
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var testAccCheckDigitalOceanTagConfig_basic = fmt.Sprintf(`
|
||||
resource "digitalocean_tag" "foobar" {
|
||||
name = "foobar"
|
||||
}`)
|
|
@ -0,0 +1,72 @@
|
|||
package digitalocean
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/digitalocean/godo"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
// setTags is a helper to set the tags for a resource. It expects the
|
||||
// tags field to be named "tags"
|
||||
func setTags(conn *godo.Client, d *schema.ResourceData) error {
|
||||
oraw, nraw := d.GetChange("tags")
|
||||
remove, create := diffTags(tagsFromSchema(oraw), tagsFromSchema(nraw))
|
||||
|
||||
log.Printf("[DEBUG] Removing tags: %#v from %s", remove, d.Id())
|
||||
for _, tag := range remove {
|
||||
_, err := conn.Tags.UntagResources(tag, &godo.UntagResourcesRequest{
|
||||
Resources: []godo.Resource{
|
||||
godo.Resource{
|
||||
ID: d.Id(),
|
||||
Type: godo.DropletResourceType,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Creating tags: %s for %s", create, d.Id())
|
||||
for _, tag := range create {
|
||||
_, err := conn.Tags.TagResources(tag, &godo.TagResourcesRequest{
|
||||
Resources: []godo.Resource{
|
||||
godo.Resource{
|
||||
ID: d.Id(),
|
||||
Type: godo.DropletResourceType,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// tagsFromSchema takes the raw schema tags and returns them as a
|
||||
// properly asserted map[string]string
|
||||
func tagsFromSchema(raw interface{}) map[string]string {
|
||||
result := make(map[string]string)
|
||||
for _, t := range raw.([]interface{}) {
|
||||
result[t.(string)] = t.(string)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// diffTags takes the old and the new tag sets and returns the difference of
|
||||
// both. The remaining tags are those that need to be removed and created
|
||||
func diffTags(oldTags, newTags map[string]string) (map[string]string, map[string]string) {
|
||||
for k := range oldTags {
|
||||
_, ok := newTags[k]
|
||||
if ok {
|
||||
delete(newTags, k)
|
||||
delete(oldTags, k)
|
||||
}
|
||||
}
|
||||
|
||||
return oldTags, newTags
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package digitalocean
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDiffTags(t *testing.T) {
|
||||
cases := []struct {
|
||||
Old, New []interface{}
|
||||
Create, Remove map[string]string
|
||||
}{
|
||||
// Basic add/remove
|
||||
{
|
||||
Old: []interface{}{
|
||||
"foo",
|
||||
},
|
||||
New: []interface{}{
|
||||
"bar",
|
||||
},
|
||||
Create: map[string]string{
|
||||
"bar": "bar",
|
||||
},
|
||||
Remove: map[string]string{
|
||||
"foo": "foo",
|
||||
},
|
||||
},
|
||||
|
||||
// Noop
|
||||
{
|
||||
Old: []interface{}{
|
||||
"foo",
|
||||
},
|
||||
New: []interface{}{
|
||||
"foo",
|
||||
},
|
||||
Create: map[string]string{},
|
||||
Remove: map[string]string{},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
r, c := diffTags(tagsFromSchema(tc.Old), tagsFromSchema(tc.New))
|
||||
if !reflect.DeepEqual(r, tc.Remove) {
|
||||
t.Fatalf("%d: bad remove: %#v", i, r)
|
||||
}
|
||||
if !reflect.DeepEqual(c, tc.Create) {
|
||||
t.Fatalf("%d: bad create: %#v", i, c)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.3
|
||||
- 1.4
|
||||
- tip
|
|
@ -13,22 +13,31 @@ type ActionRequest map[string]interface{}
|
|||
// See: https://developers.digitalocean.com/documentation/v2#droplet-actions
|
||||
type DropletActionsService interface {
|
||||
Shutdown(int) (*Action, *Response, error)
|
||||
ShutdownByTag(string) (*Action, *Response, error)
|
||||
PowerOff(int) (*Action, *Response, error)
|
||||
PowerOffByTag(string) (*Action, *Response, error)
|
||||
PowerOn(int) (*Action, *Response, error)
|
||||
PowerOnByTag(string) (*Action, *Response, error)
|
||||
PowerCycle(int) (*Action, *Response, error)
|
||||
PowerCycleByTag(string) (*Action, *Response, error)
|
||||
Reboot(int) (*Action, *Response, error)
|
||||
Restore(int, int) (*Action, *Response, error)
|
||||
Resize(int, string, bool) (*Action, *Response, error)
|
||||
Rename(int, string) (*Action, *Response, error)
|
||||
Snapshot(int, string) (*Action, *Response, error)
|
||||
SnapshotByTag(string, string) (*Action, *Response, error)
|
||||
EnableBackups(int) (*Action, *Response, error)
|
||||
EnableBackupsByTag(string) (*Action, *Response, error)
|
||||
DisableBackups(int) (*Action, *Response, error)
|
||||
DisableBackupsByTag(string) (*Action, *Response, error)
|
||||
PasswordReset(int) (*Action, *Response, error)
|
||||
RebuildByImageID(int, int) (*Action, *Response, error)
|
||||
RebuildByImageSlug(int, string) (*Action, *Response, error)
|
||||
ChangeKernel(int, int) (*Action, *Response, error)
|
||||
EnableIPv6(int) (*Action, *Response, error)
|
||||
EnableIPv6ByTag(string) (*Action, *Response, error)
|
||||
EnablePrivateNetworking(int) (*Action, *Response, error)
|
||||
EnablePrivateNetworkingByTag(string) (*Action, *Response, error)
|
||||
Upgrade(int) (*Action, *Response, error)
|
||||
Get(int, int) (*Action, *Response, error)
|
||||
GetByURI(string) (*Action, *Response, error)
|
||||
|
@ -48,24 +57,48 @@ func (s *DropletActionsServiceOp) Shutdown(id int) (*Action, *Response, error) {
|
|||
return s.doAction(id, request)
|
||||
}
|
||||
|
||||
// Shutdown Droplets by Tag
|
||||
func (s *DropletActionsServiceOp) ShutdownByTag(tag string) (*Action, *Response, error) {
|
||||
request := &ActionRequest{"type": "shutdown"}
|
||||
return s.doActionByTag(tag, request)
|
||||
}
|
||||
|
||||
// PowerOff a Droplet
|
||||
func (s *DropletActionsServiceOp) PowerOff(id int) (*Action, *Response, error) {
|
||||
request := &ActionRequest{"type": "power_off"}
|
||||
return s.doAction(id, request)
|
||||
}
|
||||
|
||||
// PowerOff a Droplet by Tag
|
||||
func (s *DropletActionsServiceOp) PowerOffByTag(tag string) (*Action, *Response, error) {
|
||||
request := &ActionRequest{"type": "power_off"}
|
||||
return s.doActionByTag(tag, request)
|
||||
}
|
||||
|
||||
// PowerOn a Droplet
|
||||
func (s *DropletActionsServiceOp) PowerOn(id int) (*Action, *Response, error) {
|
||||
request := &ActionRequest{"type": "power_on"}
|
||||
return s.doAction(id, request)
|
||||
}
|
||||
|
||||
// PowerOn a Droplet by Tag
|
||||
func (s *DropletActionsServiceOp) PowerOnByTag(tag string) (*Action, *Response, error) {
|
||||
request := &ActionRequest{"type": "power_on"}
|
||||
return s.doActionByTag(tag, request)
|
||||
}
|
||||
|
||||
// PowerCycle a Droplet
|
||||
func (s *DropletActionsServiceOp) PowerCycle(id int) (*Action, *Response, error) {
|
||||
request := &ActionRequest{"type": "power_cycle"}
|
||||
return s.doAction(id, request)
|
||||
}
|
||||
|
||||
// PowerCycle a Droplet by Tag
|
||||
func (s *DropletActionsServiceOp) PowerCycleByTag(tag string) (*Action, *Response, error) {
|
||||
request := &ActionRequest{"type": "power_cycle"}
|
||||
return s.doActionByTag(tag, request)
|
||||
}
|
||||
|
||||
// Reboot a Droplet
|
||||
func (s *DropletActionsServiceOp) Reboot(id int) (*Action, *Response, error) {
|
||||
request := &ActionRequest{"type": "reboot"}
|
||||
|
@ -113,18 +146,40 @@ func (s *DropletActionsServiceOp) Snapshot(id int, name string) (*Action, *Respo
|
|||
return s.doAction(id, request)
|
||||
}
|
||||
|
||||
// Snapshot a Droplet by Tag
|
||||
func (s *DropletActionsServiceOp) SnapshotByTag(tag string, name string) (*Action, *Response, error) {
|
||||
requestType := "snapshot"
|
||||
request := &ActionRequest{
|
||||
"type": requestType,
|
||||
"name": name,
|
||||
}
|
||||
return s.doActionByTag(tag, request)
|
||||
}
|
||||
|
||||
// EnableBackups enables backups for a droplet.
|
||||
func (s *DropletActionsServiceOp) EnableBackups(id int) (*Action, *Response, error) {
|
||||
request := &ActionRequest{"type": "enable_backups"}
|
||||
return s.doAction(id, request)
|
||||
}
|
||||
|
||||
// EnableBackups enables backups for a droplet by Tag
|
||||
func (s *DropletActionsServiceOp) EnableBackupsByTag(tag string) (*Action, *Response, error) {
|
||||
request := &ActionRequest{"type": "enable_backups"}
|
||||
return s.doActionByTag(tag, request)
|
||||
}
|
||||
|
||||
// DisableBackups disables backups for a droplet.
|
||||
func (s *DropletActionsServiceOp) DisableBackups(id int) (*Action, *Response, error) {
|
||||
request := &ActionRequest{"type": "disable_backups"}
|
||||
return s.doAction(id, request)
|
||||
}
|
||||
|
||||
// DisableBackups disables backups for a droplet by tag
|
||||
func (s *DropletActionsServiceOp) DisableBackupsByTag(tag string) (*Action, *Response, error) {
|
||||
request := &ActionRequest{"type": "disable_backups"}
|
||||
return s.doActionByTag(tag, request)
|
||||
}
|
||||
|
||||
// PasswordReset resets the password for a droplet.
|
||||
func (s *DropletActionsServiceOp) PasswordReset(id int) (*Action, *Response, error) {
|
||||
request := &ActionRequest{"type": "password_reset"}
|
||||
|
@ -155,12 +210,24 @@ func (s *DropletActionsServiceOp) EnableIPv6(id int) (*Action, *Response, error)
|
|||
return s.doAction(id, request)
|
||||
}
|
||||
|
||||
// EnableIPv6 enables IPv6 for a droplet by Tag
|
||||
func (s *DropletActionsServiceOp) EnableIPv6ByTag(tag string) (*Action, *Response, error) {
|
||||
request := &ActionRequest{"type": "enable_ipv6"}
|
||||
return s.doActionByTag(tag, request)
|
||||
}
|
||||
|
||||
// EnablePrivateNetworking enables private networking for a droplet.
|
||||
func (s *DropletActionsServiceOp) EnablePrivateNetworking(id int) (*Action, *Response, error) {
|
||||
request := &ActionRequest{"type": "enable_private_networking"}
|
||||
return s.doAction(id, request)
|
||||
}
|
||||
|
||||
// EnablePrivateNetworking enables private networking for a droplet by Tag
|
||||
func (s *DropletActionsServiceOp) EnablePrivateNetworkingByTag(tag string) (*Action, *Response, error) {
|
||||
request := &ActionRequest{"type": "enable_private_networking"}
|
||||
return s.doActionByTag(tag, request)
|
||||
}
|
||||
|
||||
// Upgrade a droplet.
|
||||
func (s *DropletActionsServiceOp) Upgrade(id int) (*Action, *Response, error) {
|
||||
request := &ActionRequest{"type": "upgrade"}
|
||||
|
@ -192,6 +259,31 @@ func (s *DropletActionsServiceOp) doAction(id int, request *ActionRequest) (*Act
|
|||
return &root.Event, resp, err
|
||||
}
|
||||
|
||||
func (s *DropletActionsServiceOp) doActionByTag(tag string, request *ActionRequest) (*Action, *Response, error) {
|
||||
if tag == "" {
|
||||
return nil, nil, NewArgError("tag", "cannot be empty")
|
||||
}
|
||||
|
||||
if request == nil {
|
||||
return nil, nil, NewArgError("request", "request can't be nil")
|
||||
}
|
||||
|
||||
path := dropletActionPathByTag(tag)
|
||||
|
||||
req, err := s.client.NewRequest("POST", path, request)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(actionRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return &root.Event, resp, err
|
||||
}
|
||||
|
||||
// Get an action for a particular droplet by id.
|
||||
func (s *DropletActionsServiceOp) Get(dropletID, actionID int) (*Action, *Response, error) {
|
||||
if dropletID < 1 {
|
||||
|
@ -236,3 +328,7 @@ func (s *DropletActionsServiceOp) get(path string) (*Action, *Response, error) {
|
|||
func dropletActionPath(dropletID int) string {
|
||||
return fmt.Sprintf("v2/droplets/%d/actions", dropletID)
|
||||
}
|
||||
|
||||
func dropletActionPathByTag(tag string) string {
|
||||
return fmt.Sprintf("v2/droplets/actions?tag_name=%s", tag)
|
||||
}
|
||||
|
|
|
@ -2,20 +2,25 @@ package godo
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const dropletBasePath = "v2/droplets"
|
||||
|
||||
var errNoNetworks = errors.New("no networks have been defined")
|
||||
|
||||
// DropletsService is an interface for interfacing with the droplet
|
||||
// endpoints of the DigitalOcean API
|
||||
// See: https://developers.digitalocean.com/documentation/v2#droplets
|
||||
type DropletsService interface {
|
||||
List(*ListOptions) ([]Droplet, *Response, error)
|
||||
ListByTag(string, *ListOptions) ([]Droplet, *Response, error)
|
||||
Get(int) (*Droplet, *Response, error)
|
||||
Create(*DropletCreateRequest) (*Droplet, *Response, error)
|
||||
CreateMultiple(*DropletMultiCreateRequest) ([]Droplet, *Response, error)
|
||||
Delete(int) (*Response, error)
|
||||
DeleteByTag(string) (*Response, error)
|
||||
Kernels(int, *ListOptions) ([]Kernel, *Response, error)
|
||||
Snapshots(int, *ListOptions) ([]Image, *Response, error)
|
||||
Backups(int, *ListOptions) ([]Image, *Response, error)
|
||||
|
@ -47,9 +52,55 @@ type Droplet struct {
|
|||
Locked bool `json:"locked,bool,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Networks *Networks `json:"networks,omitempty"`
|
||||
ActionIDs []int `json:"action_ids,omitempty"`
|
||||
Created string `json:"created_at,omitempty"`
|
||||
Kernel *Kernel `json:"kernel,omitempty"`
|
||||
Tags []string `json:"tags,ommitempty"`
|
||||
VolumeIDs []string `json:"volumes"`
|
||||
}
|
||||
|
||||
// PublicIPv4 returns the public IPv4 address for the Droplet.
|
||||
func (d *Droplet) PublicIPv4() (string, error) {
|
||||
if d.Networks == nil {
|
||||
return "", errNoNetworks
|
||||
}
|
||||
|
||||
for _, v4 := range d.Networks.V4 {
|
||||
if v4.Type == "public" {
|
||||
return v4.IPAddress, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// PrivateIPv4 returns the private IPv4 address for the Droplet.
|
||||
func (d *Droplet) PrivateIPv4() (string, error) {
|
||||
if d.Networks == nil {
|
||||
return "", errNoNetworks
|
||||
}
|
||||
|
||||
for _, v4 := range d.Networks.V4 {
|
||||
if v4.Type == "private" {
|
||||
return v4.IPAddress, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// PublicIPv6 returns the private IPv6 address for the Droplet.
|
||||
func (d *Droplet) PublicIPv6() (string, error) {
|
||||
if d.Networks == nil {
|
||||
return "", errNoNetworks
|
||||
}
|
||||
|
||||
for _, v4 := range d.Networks.V6 {
|
||||
if v4.Type == "public" {
|
||||
return v4.IPAddress, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Kernel object
|
||||
|
@ -96,6 +147,27 @@ type DropletCreateImage struct {
|
|||
Slug string
|
||||
}
|
||||
|
||||
// DropletCreateVolume identifies a volume to attach for the create request. It
|
||||
// prefers Name over ID,
|
||||
type DropletCreateVolume struct {
|
||||
ID string
|
||||
Name string
|
||||
}
|
||||
|
||||
// MarshalJSON returns an object with either the name or id of the volume. It
|
||||
// returns the id if the name is empty.
|
||||
func (d DropletCreateVolume) MarshalJSON() ([]byte, error) {
|
||||
if d.Name != "" {
|
||||
return json.Marshal(struct {
|
||||
Name string `json:"name"`
|
||||
}{Name: d.Name})
|
||||
}
|
||||
|
||||
return json.Marshal(struct {
|
||||
ID string `json:"id"`
|
||||
}{ID: d.ID})
|
||||
}
|
||||
|
||||
// MarshalJSON returns either the slug or id of the image. It returns the id
|
||||
// if the slug is empty.
|
||||
func (d DropletCreateImage) MarshalJSON() ([]byte, error) {
|
||||
|
@ -133,9 +205,10 @@ type DropletCreateRequest struct {
|
|||
IPv6 bool `json:"ipv6"`
|
||||
PrivateNetworking bool `json:"private_networking"`
|
||||
UserData string `json:"user_data,omitempty"`
|
||||
Volumes []DropletCreateVolume `json:"volumes,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
// DropletMultiCreateRequest is a request to create multiple droplets.
|
||||
type DropletMultiCreateRequest struct {
|
||||
Names []string `json:"names"`
|
||||
Region string `json:"region"`
|
||||
|
@ -186,14 +259,8 @@ func (n NetworkV6) String() string {
|
|||
return Stringify(n)
|
||||
}
|
||||
|
||||
// List all droplets
|
||||
func (s *DropletsServiceOp) List(opt *ListOptions) ([]Droplet, *Response, error) {
|
||||
path := dropletBasePath
|
||||
path, err := addOptions(path, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Performs a list request given a path
|
||||
func (s *DropletsServiceOp) list(path string) ([]Droplet, *Response, error) {
|
||||
req, err := s.client.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
@ -211,6 +278,28 @@ func (s *DropletsServiceOp) List(opt *ListOptions) ([]Droplet, *Response, error)
|
|||
return root.Droplets, resp, err
|
||||
}
|
||||
|
||||
// List all droplets
|
||||
func (s *DropletsServiceOp) List(opt *ListOptions) ([]Droplet, *Response, error) {
|
||||
path := dropletBasePath
|
||||
path, err := addOptions(path, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return s.list(path)
|
||||
}
|
||||
|
||||
// List all droplets by tag
|
||||
func (s *DropletsServiceOp) ListByTag(tag string, opt *ListOptions) ([]Droplet, *Response, error) {
|
||||
path := fmt.Sprintf("%s?tag_name=%s", dropletBasePath, tag)
|
||||
path, err := addOptions(path, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return s.list(path)
|
||||
}
|
||||
|
||||
// Get individual droplet
|
||||
func (s *DropletsServiceOp) Get(dropletID int) (*Droplet, *Response, error) {
|
||||
if dropletID < 1 {
|
||||
|
@ -258,7 +347,7 @@ func (s *DropletsServiceOp) Create(createRequest *DropletCreateRequest) (*Drople
|
|||
return root.Droplet, resp, err
|
||||
}
|
||||
|
||||
// Create multiple droplet
|
||||
// CreateMultiple creates multiple droplets.
|
||||
func (s *DropletsServiceOp) CreateMultiple(createRequest *DropletMultiCreateRequest) ([]Droplet, *Response, error) {
|
||||
if createRequest == nil {
|
||||
return nil, nil, NewArgError("createRequest", "cannot be nil")
|
||||
|
@ -283,14 +372,8 @@ func (s *DropletsServiceOp) CreateMultiple(createRequest *DropletMultiCreateRequ
|
|||
return root.Droplets, resp, err
|
||||
}
|
||||
|
||||
// Delete droplet
|
||||
func (s *DropletsServiceOp) Delete(dropletID int) (*Response, error) {
|
||||
if dropletID < 1 {
|
||||
return nil, NewArgError("dropletID", "cannot be less than 1")
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("%s/%d", dropletBasePath, dropletID)
|
||||
|
||||
// Performs a delete request given a path
|
||||
func (s *DropletsServiceOp) delete(path string) (*Response, error) {
|
||||
req, err := s.client.NewRequest("DELETE", path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -301,6 +384,28 @@ func (s *DropletsServiceOp) Delete(dropletID int) (*Response, error) {
|
|||
return resp, err
|
||||
}
|
||||
|
||||
// Delete droplet
|
||||
func (s *DropletsServiceOp) Delete(dropletID int) (*Response, error) {
|
||||
if dropletID < 1 {
|
||||
return nil, NewArgError("dropletID", "cannot be less than 1")
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("%s/%d", dropletBasePath, dropletID)
|
||||
|
||||
return s.delete(path)
|
||||
}
|
||||
|
||||
// Delete droplets by tag
|
||||
func (s *DropletsServiceOp) DeleteByTag(tag string) (*Response, error) {
|
||||
if tag == "" {
|
||||
return nil, NewArgError("tag", "cannot be empty")
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("%s?tag_name=%s", dropletBasePath, tag)
|
||||
|
||||
return s.delete(path)
|
||||
}
|
||||
|
||||
// Kernels lists kernels available for a droplet.
|
||||
func (s *DropletsServiceOp) Kernels(dropletID int, opt *ListOptions) ([]Kernel, *Response, error) {
|
||||
if dropletID < 1 {
|
||||
|
|
|
@ -22,9 +22,9 @@ const (
|
|||
userAgent = "godo/" + libraryVersion
|
||||
mediaType = "application/json"
|
||||
|
||||
headerRateLimit = "X-RateLimit-Limit"
|
||||
headerRateRemaining = "X-RateLimit-Remaining"
|
||||
headerRateReset = "X-RateLimit-Reset"
|
||||
headerRateLimit = "RateLimit-Limit"
|
||||
headerRateRemaining = "RateLimit-Remaining"
|
||||
headerRateReset = "RateLimit-Reset"
|
||||
)
|
||||
|
||||
// Client manages communication with DigitalOcean V2 API.
|
||||
|
@ -55,6 +55,9 @@ type Client struct {
|
|||
Sizes SizesService
|
||||
FloatingIPs FloatingIPsService
|
||||
FloatingIPActions FloatingIPActionsService
|
||||
Storage StorageService
|
||||
StorageActions StorageActionsService
|
||||
Tags TagsService
|
||||
|
||||
// Optional function called after every successful request made to the DO APIs
|
||||
onRequestCompleted RequestCompletionCallback
|
||||
|
@ -93,7 +96,10 @@ type ErrorResponse struct {
|
|||
Response *http.Response
|
||||
|
||||
// Error message
|
||||
Message string
|
||||
Message string `json:"message"`
|
||||
|
||||
// RequestID returned from the API, useful to contact support.
|
||||
RequestID string `json:"request_id"`
|
||||
}
|
||||
|
||||
// Rate contains the rate limit for the current client.
|
||||
|
@ -156,10 +162,49 @@ func NewClient(httpClient *http.Client) *Client {
|
|||
c.Sizes = &SizesServiceOp{client: c}
|
||||
c.FloatingIPs = &FloatingIPsServiceOp{client: c}
|
||||
c.FloatingIPActions = &FloatingIPActionsServiceOp{client: c}
|
||||
c.Storage = &StorageServiceOp{client: c}
|
||||
c.StorageActions = &StorageActionsServiceOp{client: c}
|
||||
c.Tags = &TagsServiceOp{client: c}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// ClientOpt are options for New.
|
||||
type ClientOpt func(*Client) error
|
||||
|
||||
// New returns a new DIgitalOcean API client instance.
|
||||
func New(httpClient *http.Client, opts ...ClientOpt) (*Client, error) {
|
||||
c := NewClient(httpClient)
|
||||
for _, opt := range opts {
|
||||
if err := opt(c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// SetBaseURL is a client option for setting the base URL.
|
||||
func SetBaseURL(bu string) ClientOpt {
|
||||
return func(c *Client) error {
|
||||
u, err := url.Parse(bu)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.BaseURL = u
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// SetUserAgent is a client option for setting the user agent.
|
||||
func SetUserAgent(ua string) ClientOpt {
|
||||
return func(c *Client) error {
|
||||
c.UserAgent = fmt.Sprintf("%s+%s", ua, c.UserAgent)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NewRequest creates an API request. A relative URL can be provided in urlStr, which will be resolved to the
|
||||
// BaseURL of the Client. Relative URLS should always be specified without a preceding slash. If specified, the
|
||||
// value pointed to by body is JSON encoded and included in as the request body.
|
||||
|
@ -186,7 +231,7 @@ func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Requ
|
|||
|
||||
req.Header.Add("Content-Type", mediaType)
|
||||
req.Header.Add("Accept", mediaType)
|
||||
req.Header.Add("User-Agent", userAgent)
|
||||
req.Header.Add("User-Agent", c.UserAgent)
|
||||
return req, nil
|
||||
}
|
||||
|
||||
|
@ -280,6 +325,10 @@ func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) {
|
|||
return response, err
|
||||
}
|
||||
func (r *ErrorResponse) Error() string {
|
||||
if r.RequestID != "" {
|
||||
return fmt.Sprintf("%v %v: %d (request %q) %v",
|
||||
r.Response.Request.Method, r.Response.Request.URL, r.Response.StatusCode, r.RequestID, r.Message)
|
||||
}
|
||||
return fmt.Sprintf("%v %v: %d %v",
|
||||
r.Response.Request.Method, r.Response.Request.URL, r.Response.StatusCode, r.Message)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,252 @@
|
|||
package godo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
storageBasePath = "v2"
|
||||
storageAllocPath = storageBasePath + "/volumes"
|
||||
storageSnapPath = storageBasePath + "/snapshots"
|
||||
)
|
||||
|
||||
// StorageService is an interface for interfacing with the storage
|
||||
// endpoints of the Digital Ocean API.
|
||||
// See: https://developers.digitalocean.com/documentation/v2#storage
|
||||
type StorageService interface {
|
||||
ListVolumes(*ListOptions) ([]Volume, *Response, error)
|
||||
GetVolume(string) (*Volume, *Response, error)
|
||||
CreateVolume(*VolumeCreateRequest) (*Volume, *Response, error)
|
||||
DeleteVolume(string) (*Response, error)
|
||||
}
|
||||
|
||||
// BetaStorageService is an interface for the storage services that are
|
||||
// not yet stable. The interface is not exposed in the godo.Client and
|
||||
// requires type-asserting the `StorageService` to make it available.
|
||||
//
|
||||
// Note that Beta features will change and compiling against those
|
||||
// symbols (using type-assertion) is prone to breaking your build
|
||||
// if you use our master.
|
||||
type BetaStorageService interface {
|
||||
StorageService
|
||||
|
||||
ListSnapshots(volumeID string, opts *ListOptions) ([]Snapshot, *Response, error)
|
||||
GetSnapshot(string) (*Snapshot, *Response, error)
|
||||
CreateSnapshot(*SnapshotCreateRequest) (*Snapshot, *Response, error)
|
||||
DeleteSnapshot(string) (*Response, error)
|
||||
}
|
||||
|
||||
// StorageServiceOp handles communication with the storage volumes related methods of the
|
||||
// DigitalOcean API.
|
||||
type StorageServiceOp struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
var _ StorageService = &StorageServiceOp{}
|
||||
|
||||
// Volume represents a Digital Ocean block store volume.
|
||||
type Volume struct {
|
||||
ID string `json:"id"`
|
||||
Region *Region `json:"region"`
|
||||
Name string `json:"name"`
|
||||
SizeGigaBytes int64 `json:"size_gigabytes"`
|
||||
Description string `json:"description"`
|
||||
DropletIDs []int `json:"droplet_ids"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
func (f Volume) String() string {
|
||||
return Stringify(f)
|
||||
}
|
||||
|
||||
type storageVolumesRoot struct {
|
||||
Volumes []Volume `json:"volumes"`
|
||||
Links *Links `json:"links"`
|
||||
}
|
||||
|
||||
type storageVolumeRoot struct {
|
||||
Volume *Volume `json:"volume"`
|
||||
Links *Links `json:"links,omitempty"`
|
||||
}
|
||||
|
||||
// VolumeCreateRequest represents a request to create a block store
|
||||
// volume.
|
||||
type VolumeCreateRequest struct {
|
||||
Region string `json:"region"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
SizeGigaBytes int64 `json:"size_gigabytes"`
|
||||
}
|
||||
|
||||
// ListVolumes lists all storage volumes.
|
||||
func (svc *StorageServiceOp) ListVolumes(opt *ListOptions) ([]Volume, *Response, error) {
|
||||
path, err := addOptions(storageAllocPath, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := svc.client.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(storageVolumesRoot)
|
||||
resp, err := svc.client.Do(req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
if l := root.Links; l != nil {
|
||||
resp.Links = l
|
||||
}
|
||||
|
||||
return root.Volumes, resp, nil
|
||||
}
|
||||
|
||||
// CreateVolume creates a storage volume. The name must be unique.
|
||||
func (svc *StorageServiceOp) CreateVolume(createRequest *VolumeCreateRequest) (*Volume, *Response, error) {
|
||||
path := storageAllocPath
|
||||
|
||||
req, err := svc.client.NewRequest("POST", path, createRequest)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(storageVolumeRoot)
|
||||
resp, err := svc.client.Do(req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
return root.Volume, resp, nil
|
||||
}
|
||||
|
||||
// GetVolume retrieves an individual storage volume.
|
||||
func (svc *StorageServiceOp) GetVolume(id string) (*Volume, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", storageAllocPath, id)
|
||||
|
||||
req, err := svc.client.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(storageVolumeRoot)
|
||||
resp, err := svc.client.Do(req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return root.Volume, resp, nil
|
||||
}
|
||||
|
||||
// DeleteVolume deletes a storage volume.
|
||||
func (svc *StorageServiceOp) DeleteVolume(id string) (*Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", storageAllocPath, id)
|
||||
|
||||
req, err := svc.client.NewRequest("DELETE", path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return svc.client.Do(req, nil)
|
||||
}
|
||||
|
||||
// Snapshot represents a Digital Ocean block store snapshot.
|
||||
type Snapshot struct {
|
||||
ID string `json:"id"`
|
||||
VolumeID string `json:"volume_id"`
|
||||
Region *Region `json:"region"`
|
||||
Name string `json:"name"`
|
||||
SizeGigaBytes int64 `json:"size_gigabytes"`
|
||||
Description string `json:"description"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
type storageSnapsRoot struct {
|
||||
Snapshots []Snapshot `json:"snapshots"`
|
||||
Links *Links `json:"links"`
|
||||
}
|
||||
|
||||
type storageSnapRoot struct {
|
||||
Snapshot *Snapshot `json:"snapshot"`
|
||||
Links *Links `json:"links,omitempty"`
|
||||
}
|
||||
|
||||
// SnapshotCreateRequest represents a request to create a block store
|
||||
// volume.
|
||||
type SnapshotCreateRequest struct {
|
||||
VolumeID string `json:"volume_id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// ListSnapshots lists all snapshots related to a storage volume.
|
||||
func (svc *StorageServiceOp) ListSnapshots(volumeID string, opt *ListOptions) ([]Snapshot, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s/snapshots", storageAllocPath, volumeID)
|
||||
path, err := addOptions(path, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := svc.client.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(storageSnapsRoot)
|
||||
resp, err := svc.client.Do(req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
if l := root.Links; l != nil {
|
||||
resp.Links = l
|
||||
}
|
||||
|
||||
return root.Snapshots, resp, nil
|
||||
}
|
||||
|
||||
// CreateSnapshot creates a snapshot of a storage volume.
|
||||
func (svc *StorageServiceOp) CreateSnapshot(createRequest *SnapshotCreateRequest) (*Snapshot, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s/snapshots", storageAllocPath, createRequest.VolumeID)
|
||||
|
||||
req, err := svc.client.NewRequest("POST", path, createRequest)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(storageSnapRoot)
|
||||
resp, err := svc.client.Do(req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
return root.Snapshot, resp, nil
|
||||
}
|
||||
|
||||
// GetSnapshot retrieves an individual snapshot.
|
||||
func (svc *StorageServiceOp) GetSnapshot(id string) (*Snapshot, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", storageSnapPath, id)
|
||||
|
||||
req, err := svc.client.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(storageSnapRoot)
|
||||
resp, err := svc.client.Do(req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return root.Snapshot, resp, nil
|
||||
}
|
||||
|
||||
// DeleteSnapshot deletes a snapshot.
|
||||
func (svc *StorageServiceOp) DeleteSnapshot(id string) (*Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", storageSnapPath, id)
|
||||
|
||||
req, err := svc.client.NewRequest("DELETE", path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return svc.client.Do(req, nil)
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package godo
|
||||
|
||||
import "fmt"
|
||||
|
||||
// StorageActionsService is an interface for interfacing with the
|
||||
// storage actions endpoints of the Digital Ocean API.
|
||||
// See: https://developers.digitalocean.com/documentation/v2#storage-actions
|
||||
type StorageActionsService interface {
|
||||
Attach(volumeID string, dropletID int) (*Action, *Response, error)
|
||||
Detach(volumeID string) (*Action, *Response, error)
|
||||
}
|
||||
|
||||
// StorageActionsServiceOp handles communication with the floating IPs
|
||||
// action related methods of the DigitalOcean API.
|
||||
type StorageActionsServiceOp struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// StorageAttachment represents the attachement of a block storage
|
||||
// volume to a specific droplet under the device name.
|
||||
type StorageAttachment struct {
|
||||
DropletID int `json:"droplet_id"`
|
||||
}
|
||||
|
||||
// Attach a storage volume to a droplet.
|
||||
func (s *StorageActionsServiceOp) Attach(volumeID string, dropletID int) (*Action, *Response, error) {
|
||||
request := &ActionRequest{
|
||||
"type": "attach",
|
||||
"droplet_id": dropletID,
|
||||
}
|
||||
return s.doAction(volumeID, request)
|
||||
}
|
||||
|
||||
// Detach a storage volume from a droplet.
|
||||
func (s *StorageActionsServiceOp) Detach(volumeID string) (*Action, *Response, error) {
|
||||
request := &ActionRequest{
|
||||
"type": "detach",
|
||||
}
|
||||
return s.doAction(volumeID, request)
|
||||
}
|
||||
|
||||
func (s *StorageActionsServiceOp) doAction(volumeID string, request *ActionRequest) (*Action, *Response, error) {
|
||||
path := storageAllocationActionPath(volumeID)
|
||||
|
||||
req, err := s.client.NewRequest("POST", path, request)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(actionRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return &root.Event, resp, err
|
||||
}
|
||||
|
||||
func storageAllocationActionPath(volumeID string) string {
|
||||
return fmt.Sprintf("%s/%s/actions", storageAllocPath, volumeID)
|
||||
}
|
|
@ -0,0 +1,226 @@
|
|||
package godo
|
||||
|
||||
import "fmt"
|
||||
|
||||
const tagsBasePath = "v2/tags"
|
||||
|
||||
// TagsService is an interface for interfacing with the tags
|
||||
// endpoints of the DigitalOcean API
|
||||
// See: https://developers.digitalocean.com/documentation/v2#tags
|
||||
type TagsService interface {
|
||||
List(*ListOptions) ([]Tag, *Response, error)
|
||||
Get(string) (*Tag, *Response, error)
|
||||
Create(*TagCreateRequest) (*Tag, *Response, error)
|
||||
Update(string, *TagUpdateRequest) (*Response, error)
|
||||
Delete(string) (*Response, error)
|
||||
|
||||
TagResources(string, *TagResourcesRequest) (*Response, error)
|
||||
UntagResources(string, *UntagResourcesRequest) (*Response, error)
|
||||
}
|
||||
|
||||
// TagsServiceOp handles communication with tag related method of the
|
||||
// DigitalOcean API.
|
||||
type TagsServiceOp struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
var _ TagsService = &TagsServiceOp{}
|
||||
|
||||
// ResourceType represents a class of resource, currently only droplet are supported
|
||||
type ResourceType string
|
||||
|
||||
const (
|
||||
DropletResourceType ResourceType = "droplet"
|
||||
)
|
||||
|
||||
// Resource represent a single resource for associating/disassociating with tags
|
||||
type Resource struct {
|
||||
ID string `json:"resource_id,omit_empty"`
|
||||
Type ResourceType `json:"resource_type,omit_empty"`
|
||||
}
|
||||
|
||||
// TaggedResources represent the set of resources a tag is attached to
|
||||
type TaggedResources struct {
|
||||
Droplets *TaggedDropletsResources `json:"droplets,omitempty"`
|
||||
}
|
||||
|
||||
// TaggedDropletsResources represent the droplet resources a tag is attached to
|
||||
type TaggedDropletsResources struct {
|
||||
Count int `json:"count,float64,omitempty"`
|
||||
LastTagged *Droplet `json:"last_tagged,omitempty"`
|
||||
}
|
||||
|
||||
// Tag represent DigitalOcean tag
|
||||
type Tag struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Resources *TaggedResources `json:"resources,omitempty"`
|
||||
}
|
||||
|
||||
type TagCreateRequest struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type TagUpdateRequest struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type TagResourcesRequest struct {
|
||||
Resources []Resource `json:"resources"`
|
||||
}
|
||||
|
||||
type UntagResourcesRequest struct {
|
||||
Resources []Resource `json:"resources"`
|
||||
}
|
||||
|
||||
type tagsRoot struct {
|
||||
Tags []Tag `json:"tags"`
|
||||
Links *Links `json:"links"`
|
||||
}
|
||||
|
||||
type tagRoot struct {
|
||||
Tag *Tag `json:"tag"`
|
||||
}
|
||||
|
||||
// List all tags
|
||||
func (s *TagsServiceOp) List(opt *ListOptions) ([]Tag, *Response, error) {
|
||||
path := tagsBasePath
|
||||
path, err := addOptions(path, opt)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(tagsRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
if l := root.Links; l != nil {
|
||||
resp.Links = l
|
||||
}
|
||||
|
||||
return root.Tags, resp, err
|
||||
}
|
||||
|
||||
// Get a single tag
|
||||
func (s *TagsServiceOp) Get(name string) (*Tag, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", tagsBasePath, name)
|
||||
|
||||
req, err := s.client.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(tagRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return root.Tag, resp, err
|
||||
}
|
||||
|
||||
// Create a new tag
|
||||
func (s *TagsServiceOp) Create(createRequest *TagCreateRequest) (*Tag, *Response, error) {
|
||||
if createRequest == nil {
|
||||
return nil, nil, NewArgError("createRequest", "cannot be nil")
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("POST", tagsBasePath, createRequest)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(tagRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return root.Tag, resp, err
|
||||
}
|
||||
|
||||
// Update an exsting tag
|
||||
func (s *TagsServiceOp) Update(name string, updateRequest *TagUpdateRequest) (*Response, error) {
|
||||
if name == "" {
|
||||
return nil, NewArgError("name", "cannot be empty")
|
||||
}
|
||||
|
||||
if updateRequest == nil {
|
||||
return nil, NewArgError("updateRequest", "cannot be nil")
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("%s/%s", tagsBasePath, name)
|
||||
req, err := s.client.NewRequest("PUT", path, updateRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// Delete an existing tag
|
||||
func (s *TagsServiceOp) Delete(name string) (*Response, error) {
|
||||
if name == "" {
|
||||
return nil, NewArgError("name", "cannot be empty")
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("%s/%s", tagsBasePath, name)
|
||||
req, err := s.client.NewRequest("DELETE", path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// Associate resources with a tag
|
||||
func (s *TagsServiceOp) TagResources(name string, tagRequest *TagResourcesRequest) (*Response, error) {
|
||||
if name == "" {
|
||||
return nil, NewArgError("name", "cannot be empty")
|
||||
}
|
||||
|
||||
if tagRequest == nil {
|
||||
return nil, NewArgError("tagRequest", "cannot be nil")
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("%s/%s/resources", tagsBasePath, name)
|
||||
req, err := s.client.NewRequest("POST", path, tagRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// Dissociate resources with a tag
|
||||
func (s *TagsServiceOp) UntagResources(name string, untagRequest *UntagResourcesRequest) (*Response, error) {
|
||||
if name == "" {
|
||||
return nil, NewArgError("name", "cannot be empty")
|
||||
}
|
||||
|
||||
if untagRequest == nil {
|
||||
return nil, NewArgError("tagRequest", "cannot be nil")
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("%s/%s/resources", tagsBasePath, name)
|
||||
req, err := s.client.NewRequest("DELETE", path, untagRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
|
||||
return resp, err
|
||||
}
|
|
@ -625,9 +625,11 @@
|
|||
"revisionTime": "2016-06-17T17:01:58Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "mbMr6wMbQnMrfIwUtej8QcGsx0A=",
|
||||
"comment": "v0.9.0-20-gf75d769",
|
||||
"path": "github.com/digitalocean/godo",
|
||||
"revision": "f75d769b07edce8a73682dcf325b4404f366ab3d"
|
||||
"revision": "e03ac28c3d9b216f7e9ed16bc6aa39e344d56491",
|
||||
"revisionTime": "2016-06-27T19:55:12Z"
|
||||
},
|
||||
{
|
||||
"path": "github.com/dylanmei/iso8601",
|
||||
|
|
|
@ -44,6 +44,8 @@ The following arguments are supported:
|
|||
the format `[12345, 123456]`. To retrieve this info, use a tool such
|
||||
as `curl` with the [DigitalOcean API](https://developers.digitalocean.com/#keys),
|
||||
to retrieve them.
|
||||
* `tags` - (Optional) A list of the tags to label this droplet. A tag resource
|
||||
must exist before it can be associated with a droplet.
|
||||
* `user_data` (Optional) - A string of the desired User Data for the Droplet.
|
||||
User Data is currently only available in regions with metadata
|
||||
listed in their features.
|
||||
|
@ -65,4 +67,4 @@ The following attributes are exported:
|
|||
* `private_networking` - Is private networking enabled
|
||||
* `size` - The instance size
|
||||
* `status` - The status of the droplet
|
||||
|
||||
* `tags` - The tags associated with the droplet
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
layout: "digitalocean"
|
||||
page_title: "DigitalOcean: digitalocean_tag"
|
||||
sidebar_current: "docs-do-resource-tag"
|
||||
description: |-
|
||||
Provides a DigitalOcean Tag resource.
|
||||
---
|
||||
|
||||
# digitalocean\_tag
|
||||
|
||||
Provides a DigitalOcean Tag resource. A Tag is a label that can be applied to a
|
||||
droplet resource in order to better organize or facilitate the lookups and
|
||||
actions on it. Tags created with this resource can be referenced in your droplet
|
||||
configuration via their ID or name.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
# Create a new SSH key
|
||||
resource "digitalocean_tag" "default" {
|
||||
name = "foobar"
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `name` - (Required) The name of the tag
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
The following attributes are exported:
|
||||
|
||||
* `id` - The name of the tag
|
||||
* `name` - The name of the tag
|
Loading…
Reference in New Issue