Update digitalocean/godo

This commit is contained in:
Gianluca Arbezzano 2017-03-16 23:06:05 +00:00
parent 2d31ddc26b
commit b41ec45823
49 changed files with 5394 additions and 536 deletions

View File

@ -35,8 +35,8 @@
},
{
"name": "github.com/digitalocean/godo",
"version": "v0.9.0",
"revision": "2a0d64a42bb60a95677748a4d5729af6184330b4",
"version": "v1.0.0",
"revision": "84099941ba2381607e1b05ffd4822781af86675e",
"packages": [
"."
]

View File

@ -1,6 +1,7 @@
package provider
import (
"context"
"fmt"
"strconv"
"strings"
@ -16,6 +17,7 @@ import (
type DigitalOceanProvider struct {
client *godo.Client
config map[string]string
ctx context.Context
}
func NewDigitalOceanProvider(c map[string]string) (autoscaler.Provider, error) {
@ -27,6 +29,7 @@ func NewDigitalOceanProvider(c map[string]string) (autoscaler.Provider, error) {
p := DigitalOceanProvider{
client: client,
config: c,
ctx: context.Background(),
}
return p, nil
}
@ -52,7 +55,7 @@ func (p DigitalOceanProvider) Scale(serviceId string, target int, direction bool
Slug: p.config["image"],
},
}
droplet, _, err := p.client.Droplets.Create(createRequest)
droplet, _, err := p.client.Droplets.Create(p.ctx, createRequest)
responseChannel <- response{
err: err,
droplet: droplet,
@ -62,7 +65,7 @@ func (p DigitalOceanProvider) Scale(serviceId string, target int, direction bool
}
} else {
// TODO(gianarb): This can not work forever. We need to have proper pagination
droplets, _, err := p.client.Droplets.List(&godo.ListOptions{
droplets, _, err := p.client.Droplets.List(p.ctx, &godo.ListOptions{
Page: 1,
PerPage: 500,
})
@ -80,7 +83,7 @@ func (p DigitalOceanProvider) Scale(serviceId string, target int, direction bool
if p.isGoodToBeDeleted(single, serviceId) && ii < target {
go func() {
defer wg.Done()
_, err := p.client.Droplets.Delete(single.ID)
_, err := p.client.Droplets.Delete(p.ctx, single.ID)
responseChannel <- response{
err: err,
droplet: &single,
@ -143,7 +146,7 @@ func (p DigitalOceanProvider) Scale(serviceId string, target int, direction bool
func (p DigitalOceanProvider) isGoodToBeDeleted(droplet godo.Droplet, serviceId string) bool {
if droplet.Status == "active" && strings.Contains(strings.ToUpper(droplet.Name), strings.ToUpper(serviceId)) {
// TODO(gianarb): This can not work forever. We need to have proper pagination
actions, _, _ := p.client.Droplets.Actions(droplet.ID, &godo.ListOptions{
actions, _, _ := p.client.Droplets.Actions(p.ctx, droplet.ID, &godo.ListOptions{
Page: 1,
PerPage: 500,
})

1
vendor/github.com/digitalocean/godo/.gitignore generated vendored Executable file
View File

@ -0,0 +1 @@
vendor/

View File

@ -1,6 +1,5 @@
language: go
go:
- 1.3
- 1.4
- 1.7
- tip

View File

@ -1,4 +1,4 @@
Copyright (c) 2014 The godo AUTHORS. All rights reserved.
Copyright (c) 2014-2016 The godo AUTHORS. All rights reserved.
MIT License

View File

@ -65,7 +65,9 @@ createRequest := &godo.DropletCreateRequest{
},
}
newDroplet, _, err := client.Droplets.Create(createRequest)
ctx := context.TODO()
newDroplet, _, err := client.Droplets.Create(ctx, createRequest)
if err != nil {
fmt.Printf("Something bad happened: %s\n\n", err)
@ -78,14 +80,14 @@ if err != nil {
If a list of items is paginated by the API, you must request pages individually. For example, to fetch all Droplets:
```go
func DropletList(client *godo.Client) ([]godo.Droplet, error) {
func DropletList(ctx context.Context, client *godo.Client) ([]godo.Droplet, error) {
// create a list to hold our droplets
list := []godo.Droplet{}
// create options. initially, these will be blank
opt := &godo.ListOptions{}
for {
droplets, resp, err := client.Droplets.List(opt)
droplets, resp, err := client.Droplets.List(ctx, opt)
if err != nil {
return nil, err
}

View File

@ -1,10 +1,12 @@
package godo
import "context"
// AccountService is an interface for interfacing with the Account
// endpoints of the DigitalOcean API
// See: https://developers.digitalocean.com/documentation/v2/#account
type AccountService interface {
Get() (*Account, *Response, error)
Get(context.Context) (*Account, *Response, error)
}
// AccountServiceOp handles communication with the Account related methods of
@ -17,10 +19,13 @@ var _ AccountService = &AccountServiceOp{}
// Account represents a DigitalOcean Account
type Account struct {
DropletLimit int `json:"droplet_limit,omitempty"`
Email string `json:"email,omitempty"`
UUID string `json:"uuid,omitempty"`
EmailVerified bool `json:"email_verified,omitempty"`
DropletLimit int `json:"droplet_limit,omitempty"`
FloatingIPLimit int `json:"floating_ip_limit,omitempty"`
Email string `json:"email,omitempty"`
UUID string `json:"uuid,omitempty"`
EmailVerified bool `json:"email_verified,omitempty"`
Status string `json:"status,omitempty"`
StatusMessage string `json:"status_message,omitempty"`
}
type accountRoot struct {
@ -32,10 +37,11 @@ func (r Account) String() string {
}
// Get DigitalOcean account info
func (s *AccountServiceOp) Get() (*Account, *Response, error) {
func (s *AccountServiceOp) Get(ctx context.Context) (*Account, *Response, error) {
path := "v2/account"
req, err := s.client.NewRequest("GET", path, nil)
req, err := s.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}

View File

@ -17,6 +17,7 @@ func TestAccountGet(t *testing.T) {
response := `
{ "account": {
"droplet_limit": 25,
"floating_ip_limit": 25,
"email": "sammy@digitalocean.com",
"uuid": "b6fr89dbf6d9156cace5f3c78dc9851d957381ef",
"email_verified": true
@ -26,12 +27,12 @@ func TestAccountGet(t *testing.T) {
fmt.Fprint(w, response)
})
acct, _, err := client.Account.Get()
acct, _, err := client.Account.Get(ctx)
if err != nil {
t.Errorf("Account.Get returned error: %v", err)
}
expected := &Account{DropletLimit: 25, Email: "sammy@digitalocean.com",
expected := &Account{DropletLimit: 25, FloatingIPLimit: 25, Email: "sammy@digitalocean.com",
UUID: "b6fr89dbf6d9156cace5f3c78dc9851d957381ef", EmailVerified: true}
if !reflect.DeepEqual(acct, expected) {
t.Errorf("Account.Get returned %+v, expected %+v", acct, expected)
@ -40,14 +41,17 @@ func TestAccountGet(t *testing.T) {
func TestAccountString(t *testing.T) {
acct := &Account{
DropletLimit: 25,
Email: "sammy@digitalocean.com",
UUID: "b6fr89dbf6d9156cace5f3c78dc9851d957381ef",
EmailVerified: true,
DropletLimit: 25,
FloatingIPLimit: 25,
Email: "sammy@digitalocean.com",
UUID: "b6fr89dbf6d9156cace5f3c78dc9851d957381ef",
EmailVerified: true,
Status: "active",
StatusMessage: "message",
}
stringified := acct.String()
expected := `godo.Account{DropletLimit:25, Email:"sammy@digitalocean.com", UUID:"b6fr89dbf6d9156cace5f3c78dc9851d957381ef", EmailVerified:true}`
expected := `godo.Account{DropletLimit:25, FloatingIPLimit:25, Email:"sammy@digitalocean.com", UUID:"b6fr89dbf6d9156cace5f3c78dc9851d957381ef", EmailVerified:true, Status:"active", StatusMessage:"message"}`
if expected != stringified {
t.Errorf("Account.String returned %+v, expected %+v", stringified, expected)
}

View File

@ -1,6 +1,9 @@
package godo
import "fmt"
import (
"context"
"fmt"
)
const (
actionsBasePath = "v2/actions"
@ -15,8 +18,8 @@ const (
// ActionsService handles communction with action related methods of the
// DigitalOcean API: https://developers.digitalocean.com/documentation/v2#actions
type ActionsService interface {
List(*ListOptions) ([]Action, *Response, error)
Get(int) (*Action, *Response, error)
List(context.Context, *ListOptions) ([]Action, *Response, error)
Get(context.Context, int) (*Action, *Response, error)
}
// ActionsServiceOp handles communition with the image action related methods of the
@ -33,7 +36,7 @@ type actionsRoot struct {
}
type actionRoot struct {
Event Action `json:"action"`
Event *Action `json:"action"`
}
// Action represents a DigitalOcean Action
@ -50,14 +53,14 @@ type Action struct {
}
// List all actions
func (s *ActionsServiceOp) List(opt *ListOptions) ([]Action, *Response, error) {
func (s *ActionsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Action, *Response, error) {
path := actionsBasePath
path, err := addOptions(path, opt)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", path, nil)
req, err := s.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
@ -75,13 +78,13 @@ func (s *ActionsServiceOp) List(opt *ListOptions) ([]Action, *Response, error) {
}
// Get an action by ID.
func (s *ActionsServiceOp) Get(id int) (*Action, *Response, error) {
func (s *ActionsServiceOp) Get(ctx context.Context, id int) (*Action, *Response, error) {
if id < 1 {
return nil, nil, NewArgError("id", "cannot be less than 1")
}
path := fmt.Sprintf("%s/%d", actionsBasePath, id)
req, err := s.client.NewRequest("GET", path, nil)
req, err := s.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
@ -92,7 +95,7 @@ func (s *ActionsServiceOp) Get(id int) (*Action, *Response, error) {
return nil, resp, err
}
return &root.Event, resp, err
return root.Event, resp, err
}
func (a Action) String() string {

View File

@ -17,7 +17,7 @@ func TestAction_List(t *testing.T) {
testMethod(t, r, "GET")
})
actions, _, err := client.Actions.List(nil)
actions, _, err := client.Actions.List(ctx, nil)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
@ -37,7 +37,7 @@ func TestAction_ListActionMultiplePages(t *testing.T) {
testMethod(t, r, "GET")
})
_, resp, err := client.Actions.List(nil)
_, resp, err := client.Actions.List(ctx, nil)
if err != nil {
t.Fatal(nil)
}
@ -68,7 +68,7 @@ func TestAction_RetrievePageByNumber(t *testing.T) {
})
opt := &ListOptions{Page: 2}
_, resp, err := client.Actions.List(opt)
_, resp, err := client.Actions.List(ctx, opt)
if err != nil {
t.Fatal(err)
}
@ -85,7 +85,7 @@ func TestAction_Get(t *testing.T) {
testMethod(t, r, "GET")
})
action, _, err := client.Actions.Get(12345)
action, _, err := client.Actions.Get(ctx, 12345)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}

120
vendor/github.com/digitalocean/godo/certificates.go generated vendored Normal file
View File

@ -0,0 +1,120 @@
package godo
import (
"context"
"path"
)
const certificatesBasePath = "/v2/certificates"
// CertificatesService is an interface for managing certificates with the DigitalOcean API.
// See: https://developers.digitalocean.com/documentation/v2/#certificates
type CertificatesService interface {
Get(context.Context, string) (*Certificate, *Response, error)
List(context.Context, *ListOptions) ([]Certificate, *Response, error)
Create(context.Context, *CertificateRequest) (*Certificate, *Response, error)
Delete(context.Context, string) (*Response, error)
}
// Certificate represents a DigitalOcean certificate configuration.
type Certificate struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
NotAfter string `json:"not_after,omitempty"`
SHA1Fingerprint string `json:"sha1_fingerprint,omitempty"`
Created string `json:"created_at,omitempty"`
}
// CertificateRequest represents configuration for a new certificate.
type CertificateRequest struct {
Name string `json:"name,omitempty"`
PrivateKey string `json:"private_key,omitempty"`
LeafCertificate string `json:"leaf_certificate,omitempty"`
CertificateChain string `json:"certificate_chain,omitempty"`
}
type certificateRoot struct {
Certificate *Certificate `json:"certificate"`
}
type certificatesRoot struct {
Certificates []Certificate `json:"certificates"`
Links *Links `json:"links"`
}
// CertificatesServiceOp handles communication with certificates methods of the DigitalOcean API.
type CertificatesServiceOp struct {
client *Client
}
var _ CertificatesService = &CertificatesServiceOp{}
// Get an existing certificate by its identifier.
func (c *CertificatesServiceOp) Get(ctx context.Context, cID string) (*Certificate, *Response, error) {
urlStr := path.Join(certificatesBasePath, cID)
req, err := c.client.NewRequest(ctx, "GET", urlStr, nil)
if err != nil {
return nil, nil, err
}
root := new(certificateRoot)
resp, err := c.client.Do(req, root)
if err != nil {
return nil, resp, err
}
return root.Certificate, resp, nil
}
// List all certificates.
func (c *CertificatesServiceOp) List(ctx context.Context, opt *ListOptions) ([]Certificate, *Response, error) {
urlStr, err := addOptions(certificatesBasePath, opt)
if err != nil {
return nil, nil, err
}
req, err := c.client.NewRequest(ctx, "GET", urlStr, nil)
if err != nil {
return nil, nil, err
}
root := new(certificatesRoot)
resp, err := c.client.Do(req, root)
if err != nil {
return nil, resp, err
}
if l := root.Links; l != nil {
resp.Links = l
}
return root.Certificates, resp, nil
}
// Create a new certificate with provided configuration.
func (c *CertificatesServiceOp) Create(ctx context.Context, cr *CertificateRequest) (*Certificate, *Response, error) {
req, err := c.client.NewRequest(ctx, "POST", certificatesBasePath, cr)
if err != nil {
return nil, nil, err
}
root := new(certificateRoot)
resp, err := c.client.Do(req, root)
if err != nil {
return nil, resp, err
}
return root.Certificate, resp, nil
}
// Delete a certificate by its identifier.
func (c *CertificatesServiceOp) Delete(ctx context.Context, cID string) (*Response, error) {
urlStr := path.Join(certificatesBasePath, cID)
req, err := c.client.NewRequest(ctx, "DELETE", urlStr, nil)
if err != nil {
return nil, err
}
return c.client.Do(req, nil)
}

View File

@ -0,0 +1,171 @@
package godo
import (
"encoding/json"
"fmt"
"net/http"
"path"
"testing"
"github.com/stretchr/testify/assert"
)
var certJSONResponse = `
{
"certificate": {
"id": "892071a0-bb95-49bc-8021-3afd67a210bf",
"name": "web-cert-01",
"not_after": "2017-02-22T00:23:00Z",
"sha1_fingerprint": "dfcc9f57d86bf58e321c2c6c31c7a971be244ac7",
"created_at": "2017-02-08T16:02:37Z"
}
}
`
var certsJSONResponse = `
{
"certificates": [
{
"id": "892071a0-bb95-49bc-8021-3afd67a210bf",
"name": "web-cert-01",
"not_after": "2017-02-22T00:23:00Z",
"sha1_fingerprint": "dfcc9f57d86bf58e321c2c6c31c7a971be244ac7",
"created_at": "2017-02-08T16:02:37Z"
},
{
"id": "992071a0-bb95-49bc-8021-3afd67a210bf",
"name": "web-cert-02",
"not_after": "2017-02-22T00:23:00Z",
"sha1_fingerprint": "cfcc9f57d86bf58e321c2c6c31c7a971be244ac7",
"created_at": "2017-02-08T16:02:37Z"
}
],
"links": {},
"meta": {
"total": 1
}
}
`
func TestCertificates_Get(t *testing.T) {
setup()
defer teardown()
urlStr := "/v2/certificates"
cID := "892071a0-bb95-49bc-8021-3afd67a210bf"
urlStr = path.Join(urlStr, cID)
mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, certJSONResponse)
})
certificate, _, err := client.Certificates.Get(ctx, cID)
if err != nil {
t.Errorf("Certificates.Get returned error: %v", err)
}
expected := &Certificate{
ID: "892071a0-bb95-49bc-8021-3afd67a210bf",
Name: "web-cert-01",
NotAfter: "2017-02-22T00:23:00Z",
SHA1Fingerprint: "dfcc9f57d86bf58e321c2c6c31c7a971be244ac7",
Created: "2017-02-08T16:02:37Z",
}
assert.Equal(t, expected, certificate)
}
func TestCertificates_List(t *testing.T) {
setup()
defer teardown()
urlStr := "/v2/certificates"
mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, certsJSONResponse)
})
certificates, _, err := client.Certificates.List(ctx, nil)
if err != nil {
t.Errorf("Certificates.List returned error: %v", err)
}
expected := []Certificate{
{
ID: "892071a0-bb95-49bc-8021-3afd67a210bf",
Name: "web-cert-01",
NotAfter: "2017-02-22T00:23:00Z",
SHA1Fingerprint: "dfcc9f57d86bf58e321c2c6c31c7a971be244ac7",
Created: "2017-02-08T16:02:37Z",
},
{
ID: "992071a0-bb95-49bc-8021-3afd67a210bf",
Name: "web-cert-02",
NotAfter: "2017-02-22T00:23:00Z",
SHA1Fingerprint: "cfcc9f57d86bf58e321c2c6c31c7a971be244ac7",
Created: "2017-02-08T16:02:37Z",
},
}
assert.Equal(t, expected, certificates)
}
func TestCertificates_Create(t *testing.T) {
setup()
defer teardown()
createRequest := &CertificateRequest{
Name: "web-cert-01",
PrivateKey: "-----BEGIN PRIVATE KEY-----",
LeafCertificate: "-----BEGIN CERTIFICATE-----",
CertificateChain: "-----BEGIN CERTIFICATE-----",
}
urlStr := "/v2/certificates"
mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) {
v := new(CertificateRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatal(err)
}
testMethod(t, r, "POST")
assert.Equal(t, createRequest, v)
fmt.Fprint(w, certJSONResponse)
})
certificate, _, err := client.Certificates.Create(ctx, createRequest)
if err != nil {
t.Errorf("Certificates.Create returned error: %v", err)
}
expected := &Certificate{
ID: "892071a0-bb95-49bc-8021-3afd67a210bf",
Name: "web-cert-01",
NotAfter: "2017-02-22T00:23:00Z",
SHA1Fingerprint: "dfcc9f57d86bf58e321c2c6c31c7a971be244ac7",
Created: "2017-02-08T16:02:37Z",
}
assert.Equal(t, expected, certificate)
}
func TestCertificates_Delete(t *testing.T) {
setup()
defer teardown()
cID := "892071a0-bb95-49bc-8021-3afd67a210bf"
urlStr := "/v2/certificates"
urlStr = path.Join(urlStr, cID)
mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "DELETE")
})
_, err := client.Certificates.Delete(ctx, cID)
if err != nil {
t.Errorf("Certificates.Delete returned error: %v", err)
}
}

View File

@ -1,6 +1,9 @@
package godo
import "fmt"
import (
"context"
"fmt"
)
const domainsBasePath = "v2/domains"
@ -8,16 +11,16 @@ const domainsBasePath = "v2/domains"
// See: https://developers.digitalocean.com/documentation/v2#domains and
// https://developers.digitalocean.com/documentation/v2#domain-records
type DomainsService interface {
List(*ListOptions) ([]Domain, *Response, error)
Get(string) (*Domain, *Response, error)
Create(*DomainCreateRequest) (*Domain, *Response, error)
Delete(string) (*Response, error)
List(context.Context, *ListOptions) ([]Domain, *Response, error)
Get(context.Context, string) (*Domain, *Response, error)
Create(context.Context, *DomainCreateRequest) (*Domain, *Response, error)
Delete(context.Context, string) (*Response, error)
Records(string, *ListOptions) ([]DomainRecord, *Response, error)
Record(string, int) (*DomainRecord, *Response, error)
DeleteRecord(string, int) (*Response, error)
EditRecord(string, int, *DomainRecordEditRequest) (*DomainRecord, *Response, error)
CreateRecord(string, *DomainRecordEditRequest) (*DomainRecord, *Response, error)
Records(context.Context, string, *ListOptions) ([]DomainRecord, *Response, error)
Record(context.Context, string, int) (*DomainRecord, *Response, error)
DeleteRecord(context.Context, string, int) (*Response, error)
EditRecord(context.Context, string, int, *DomainRecordEditRequest) (*DomainRecord, *Response, error)
CreateRecord(context.Context, string, *DomainRecordEditRequest) (*DomainRecord, *Response, error)
}
// DomainsServiceOp handles communication with the domain related methods of the
@ -88,14 +91,14 @@ func (d Domain) String() string {
}
// List all domains.
func (s DomainsServiceOp) List(opt *ListOptions) ([]Domain, *Response, error) {
func (s DomainsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Domain, *Response, error) {
path := domainsBasePath
path, err := addOptions(path, opt)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", path, nil)
req, err := s.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
@ -113,14 +116,14 @@ func (s DomainsServiceOp) List(opt *ListOptions) ([]Domain, *Response, error) {
}
// Get individual domain. It requires a non-empty domain name.
func (s *DomainsServiceOp) Get(name string) (*Domain, *Response, error) {
func (s *DomainsServiceOp) Get(ctx context.Context, name string) (*Domain, *Response, error) {
if len(name) < 1 {
return nil, nil, NewArgError("name", "cannot be an empty string")
}
path := fmt.Sprintf("%s/%s", domainsBasePath, name)
req, err := s.client.NewRequest("GET", path, nil)
req, err := s.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
@ -135,14 +138,14 @@ func (s *DomainsServiceOp) Get(name string) (*Domain, *Response, error) {
}
// Create a new domain
func (s *DomainsServiceOp) Create(createRequest *DomainCreateRequest) (*Domain, *Response, error) {
func (s *DomainsServiceOp) Create(ctx context.Context, createRequest *DomainCreateRequest) (*Domain, *Response, error) {
if createRequest == nil {
return nil, nil, NewArgError("createRequest", "cannot be nil")
}
path := domainsBasePath
req, err := s.client.NewRequest("POST", path, createRequest)
req, err := s.client.NewRequest(ctx, "POST", path, createRequest)
if err != nil {
return nil, nil, err
}
@ -156,14 +159,14 @@ func (s *DomainsServiceOp) Create(createRequest *DomainCreateRequest) (*Domain,
}
// Delete domain
func (s *DomainsServiceOp) Delete(name string) (*Response, error) {
func (s *DomainsServiceOp) Delete(ctx context.Context, name string) (*Response, error) {
if len(name) < 1 {
return nil, NewArgError("name", "cannot be an empty string")
}
path := fmt.Sprintf("%s/%s", domainsBasePath, name)
req, err := s.client.NewRequest("DELETE", path, nil)
req, err := s.client.NewRequest(ctx, "DELETE", path, nil)
if err != nil {
return nil, err
}
@ -184,7 +187,7 @@ func (d DomainRecordEditRequest) String() string {
}
// Records returns a slice of DomainRecords for a domain
func (s *DomainsServiceOp) Records(domain string, opt *ListOptions) ([]DomainRecord, *Response, error) {
func (s *DomainsServiceOp) Records(ctx context.Context, domain string, opt *ListOptions) ([]DomainRecord, *Response, error) {
if len(domain) < 1 {
return nil, nil, NewArgError("domain", "cannot be an empty string")
}
@ -195,7 +198,7 @@ func (s *DomainsServiceOp) Records(domain string, opt *ListOptions) ([]DomainRec
return nil, nil, err
}
req, err := s.client.NewRequest("GET", path, nil)
req, err := s.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
@ -213,7 +216,7 @@ func (s *DomainsServiceOp) Records(domain string, opt *ListOptions) ([]DomainRec
}
// Record returns the record id from a domain
func (s *DomainsServiceOp) Record(domain string, id int) (*DomainRecord, *Response, error) {
func (s *DomainsServiceOp) Record(ctx context.Context, domain string, id int) (*DomainRecord, *Response, error) {
if len(domain) < 1 {
return nil, nil, NewArgError("domain", "cannot be an empty string")
}
@ -224,7 +227,7 @@ func (s *DomainsServiceOp) Record(domain string, id int) (*DomainRecord, *Respon
path := fmt.Sprintf("%s/%s/records/%d", domainsBasePath, domain, id)
req, err := s.client.NewRequest("GET", path, nil)
req, err := s.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
@ -239,7 +242,7 @@ func (s *DomainsServiceOp) Record(domain string, id int) (*DomainRecord, *Respon
}
// DeleteRecord deletes a record from a domain identified by id
func (s *DomainsServiceOp) DeleteRecord(domain string, id int) (*Response, error) {
func (s *DomainsServiceOp) DeleteRecord(ctx context.Context, domain string, id int) (*Response, error) {
if len(domain) < 1 {
return nil, NewArgError("domain", "cannot be an empty string")
}
@ -250,7 +253,7 @@ func (s *DomainsServiceOp) DeleteRecord(domain string, id int) (*Response, error
path := fmt.Sprintf("%s/%s/records/%d", domainsBasePath, domain, id)
req, err := s.client.NewRequest("DELETE", path, nil)
req, err := s.client.NewRequest(ctx, "DELETE", path, nil)
if err != nil {
return nil, err
}
@ -261,7 +264,7 @@ func (s *DomainsServiceOp) DeleteRecord(domain string, id int) (*Response, error
}
// EditRecord edits a record using a DomainRecordEditRequest
func (s *DomainsServiceOp) EditRecord(
func (s *DomainsServiceOp) EditRecord(ctx context.Context,
domain string,
id int,
editRequest *DomainRecordEditRequest,
@ -280,7 +283,7 @@ func (s *DomainsServiceOp) EditRecord(
path := fmt.Sprintf("%s/%s/records/%d", domainsBasePath, domain, id)
req, err := s.client.NewRequest("PUT", path, editRequest)
req, err := s.client.NewRequest(ctx, "PUT", path, editRequest)
if err != nil {
return nil, nil, err
}
@ -295,7 +298,7 @@ func (s *DomainsServiceOp) EditRecord(
}
// CreateRecord creates a record using a DomainRecordEditRequest
func (s *DomainsServiceOp) CreateRecord(
func (s *DomainsServiceOp) CreateRecord(ctx context.Context,
domain string,
createRequest *DomainRecordEditRequest) (*DomainRecord, *Response, error) {
if len(domain) < 1 {
@ -307,7 +310,7 @@ func (s *DomainsServiceOp) CreateRecord(
}
path := fmt.Sprintf("%s/%s/records", domainsBasePath, domain)
req, err := s.client.NewRequest("POST", path, createRequest)
req, err := s.client.NewRequest(ctx, "POST", path, createRequest)
if err != nil {
return nil, nil, err

View File

@ -17,7 +17,7 @@ func TestDomains_ListDomains(t *testing.T) {
fmt.Fprint(w, `{"domains": [{"name":"foo.com"},{"name":"bar.com"}]}`)
})
domains, _, err := client.Domains.List(nil)
domains, _, err := client.Domains.List(ctx, nil)
if err != nil {
t.Errorf("Domains.List returned error: %v", err)
}
@ -37,7 +37,7 @@ func TestDomains_ListDomainsMultiplePages(t *testing.T) {
fmt.Fprint(w, `{"domains": [{"id":1},{"id":2}], "links":{"pages":{"next":"http://example.com/v2/domains/?page=2"}}}`)
})
_, resp, err := client.Domains.List(nil)
_, resp, err := client.Domains.List(ctx, nil)
if err != nil {
t.Fatal(err)
}
@ -68,7 +68,7 @@ func TestDomains_RetrievePageByNumber(t *testing.T) {
})
opt := &ListOptions{Page: 2}
_, resp, err := client.Domains.List(opt)
_, resp, err := client.Domains.List(ctx, opt)
if err != nil {
t.Fatal(err)
}
@ -85,7 +85,7 @@ func TestDomains_GetDomain(t *testing.T) {
fmt.Fprint(w, `{"domain":{"name":"example.com"}}`)
})
domains, _, err := client.Domains.Get("example.com")
domains, _, err := client.Domains.Get(ctx, "example.com")
if err != nil {
t.Errorf("domain.Get returned error: %v", err)
}
@ -120,7 +120,7 @@ func TestDomains_Create(t *testing.T) {
fmt.Fprint(w, `{"domain":{"name":"example.com"}}`)
})
domain, _, err := client.Domains.Create(createRequest)
domain, _, err := client.Domains.Create(ctx, createRequest)
if err != nil {
t.Errorf("Domains.Create returned error: %v", err)
}
@ -139,7 +139,7 @@ func TestDomains_Destroy(t *testing.T) {
testMethod(t, r, "DELETE")
})
_, err := client.Domains.Delete("example.com")
_, err := client.Domains.Delete(ctx, "example.com")
if err != nil {
t.Errorf("Domains.Delete returned error: %v", err)
}
@ -154,7 +154,7 @@ func TestDomains_AllRecordsForDomainName(t *testing.T) {
fmt.Fprint(w, `{"domain_records":[{"id":1},{"id":2}]}`)
})
records, _, err := client.Domains.Records("example.com", nil)
records, _, err := client.Domains.Records(ctx, "example.com", nil)
if err != nil {
t.Errorf("Domains.List returned error: %v", err)
}
@ -179,7 +179,7 @@ func TestDomains_AllRecordsForDomainName_PerPage(t *testing.T) {
})
dro := &ListOptions{PerPage: 2}
records, _, err := client.Domains.Records("example.com", dro)
records, _, err := client.Domains.Records(ctx, "example.com", dro)
if err != nil {
t.Errorf("Domains.List returned error: %v", err)
}
@ -199,7 +199,7 @@ func TestDomains_GetRecordforDomainName(t *testing.T) {
fmt.Fprint(w, `{"domain_record":{"id":1}}`)
})
record, _, err := client.Domains.Record("example.com", 1)
record, _, err := client.Domains.Record(ctx, "example.com", 1)
if err != nil {
t.Errorf("Domains.GetRecord returned error: %v", err)
}
@ -218,7 +218,7 @@ func TestDomains_DeleteRecordForDomainName(t *testing.T) {
testMethod(t, r, "DELETE")
})
_, err := client.Domains.DeleteRecord("example.com", 1)
_, err := client.Domains.DeleteRecord(ctx, "example.com", 1)
if err != nil {
t.Errorf("Domains.RecordDelete returned error: %v", err)
}
@ -254,7 +254,7 @@ func TestDomains_CreateRecordForDomainName(t *testing.T) {
fmt.Fprintf(w, `{"domain_record": {"id":1}}`)
})
record, _, err := client.Domains.CreateRecord("example.com", createRequest)
record, _, err := client.Domains.CreateRecord(ctx, "example.com", createRequest)
if err != nil {
t.Errorf("Domains.CreateRecord returned error: %v", err)
}
@ -293,7 +293,7 @@ func TestDomains_EditRecordForDomainName(t *testing.T) {
fmt.Fprintf(w, `{"id":1}`)
})
record, _, err := client.Domains.EditRecord("example.com", 1, editRequest)
record, _, err := client.Domains.EditRecord(ctx, "example.com", 1, editRequest)
if err != nil {
t.Errorf("Domains.EditRecord returned error: %v", err)
}

View File

@ -1,6 +1,7 @@
package godo
import (
"context"
"fmt"
"net/url"
)
@ -8,32 +9,42 @@ import (
// ActionRequest reprents DigitalOcean Action Request
type ActionRequest map[string]interface{}
// DropletActionsService is an interface for interfacing with the droplet actions
// DropletActionsService is an interface for interfacing with the Droplet actions
// endpoints of the DigitalOcean API
// See: https://developers.digitalocean.com/documentation/v2#droplet-actions
type DropletActionsService interface {
Shutdown(int) (*Action, *Response, error)
PowerOff(int) (*Action, *Response, error)
PowerOn(int) (*Action, *Response, error)
PowerCycle(int) (*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)
DisableBackups(int) (*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)
EnablePrivateNetworking(int) (*Action, *Response, error)
Upgrade(int) (*Action, *Response, error)
Get(int, int) (*Action, *Response, error)
GetByURI(string) (*Action, *Response, error)
Shutdown(context.Context, int) (*Action, *Response, error)
ShutdownByTag(context.Context, string) (*Action, *Response, error)
PowerOff(context.Context, int) (*Action, *Response, error)
PowerOffByTag(context.Context, string) (*Action, *Response, error)
PowerOn(context.Context, int) (*Action, *Response, error)
PowerOnByTag(context.Context, string) (*Action, *Response, error)
PowerCycle(context.Context, int) (*Action, *Response, error)
PowerCycleByTag(context.Context, string) (*Action, *Response, error)
Reboot(context.Context, int) (*Action, *Response, error)
Restore(context.Context, int, int) (*Action, *Response, error)
Resize(context.Context, int, string, bool) (*Action, *Response, error)
Rename(context.Context, int, string) (*Action, *Response, error)
Snapshot(context.Context, int, string) (*Action, *Response, error)
SnapshotByTag(context.Context, string, string) (*Action, *Response, error)
EnableBackups(context.Context, int) (*Action, *Response, error)
EnableBackupsByTag(context.Context, string) (*Action, *Response, error)
DisableBackups(context.Context, int) (*Action, *Response, error)
DisableBackupsByTag(context.Context, string) (*Action, *Response, error)
PasswordReset(context.Context, int) (*Action, *Response, error)
RebuildByImageID(context.Context, int, int) (*Action, *Response, error)
RebuildByImageSlug(context.Context, int, string) (*Action, *Response, error)
ChangeKernel(context.Context, int, int) (*Action, *Response, error)
EnableIPv6(context.Context, int) (*Action, *Response, error)
EnableIPv6ByTag(context.Context, string) (*Action, *Response, error)
EnablePrivateNetworking(context.Context, int) (*Action, *Response, error)
EnablePrivateNetworkingByTag(context.Context, string) (*Action, *Response, error)
Upgrade(context.Context, int) (*Action, *Response, error)
Get(context.Context, int, int) (*Action, *Response, error)
GetByURI(context.Context, string) (*Action, *Response, error)
}
// DropletActionsServiceOp handles communication with the droplet action related
// DropletActionsServiceOp handles communication with the Droplet action related
// methods of the DigitalOcean API.
type DropletActionsServiceOp struct {
client *Client
@ -42,125 +53,189 @@ type DropletActionsServiceOp struct {
var _ DropletActionsService = &DropletActionsServiceOp{}
// Shutdown a Droplet
func (s *DropletActionsServiceOp) Shutdown(id int) (*Action, *Response, error) {
func (s *DropletActionsServiceOp) Shutdown(ctx context.Context, id int) (*Action, *Response, error) {
request := &ActionRequest{"type": "shutdown"}
return s.doAction(id, request)
return s.doAction(ctx, id, request)
}
// ShutdownByTag shuts down Droplets matched by a Tag.
func (s *DropletActionsServiceOp) ShutdownByTag(ctx context.Context, tag string) (*Action, *Response, error) {
request := &ActionRequest{"type": "shutdown"}
return s.doActionByTag(ctx, tag, request)
}
// PowerOff a Droplet
func (s *DropletActionsServiceOp) PowerOff(id int) (*Action, *Response, error) {
func (s *DropletActionsServiceOp) PowerOff(ctx context.Context, id int) (*Action, *Response, error) {
request := &ActionRequest{"type": "power_off"}
return s.doAction(id, request)
return s.doAction(ctx, id, request)
}
// PowerOffByTag powers off Droplets matched by a Tag.
func (s *DropletActionsServiceOp) PowerOffByTag(ctx context.Context, tag string) (*Action, *Response, error) {
request := &ActionRequest{"type": "power_off"}
return s.doActionByTag(ctx, tag, request)
}
// PowerOn a Droplet
func (s *DropletActionsServiceOp) PowerOn(id int) (*Action, *Response, error) {
func (s *DropletActionsServiceOp) PowerOn(ctx context.Context, id int) (*Action, *Response, error) {
request := &ActionRequest{"type": "power_on"}
return s.doAction(id, request)
return s.doAction(ctx, id, request)
}
// PowerOnByTag powers on Droplets matched by a Tag.
func (s *DropletActionsServiceOp) PowerOnByTag(ctx context.Context, tag string) (*Action, *Response, error) {
request := &ActionRequest{"type": "power_on"}
return s.doActionByTag(ctx, tag, request)
}
// PowerCycle a Droplet
func (s *DropletActionsServiceOp) PowerCycle(id int) (*Action, *Response, error) {
func (s *DropletActionsServiceOp) PowerCycle(ctx context.Context, id int) (*Action, *Response, error) {
request := &ActionRequest{"type": "power_cycle"}
return s.doAction(id, request)
return s.doAction(ctx, id, request)
}
// PowerCycleByTag power cycles Droplets matched by a Tag.
func (s *DropletActionsServiceOp) PowerCycleByTag(ctx context.Context, tag string) (*Action, *Response, error) {
request := &ActionRequest{"type": "power_cycle"}
return s.doActionByTag(ctx, tag, request)
}
// Reboot a Droplet
func (s *DropletActionsServiceOp) Reboot(id int) (*Action, *Response, error) {
func (s *DropletActionsServiceOp) Reboot(ctx context.Context, id int) (*Action, *Response, error) {
request := &ActionRequest{"type": "reboot"}
return s.doAction(id, request)
return s.doAction(ctx, id, request)
}
// Restore an image to a Droplet
func (s *DropletActionsServiceOp) Restore(id, imageID int) (*Action, *Response, error) {
func (s *DropletActionsServiceOp) Restore(ctx context.Context, id, imageID int) (*Action, *Response, error) {
requestType := "restore"
request := &ActionRequest{
"type": requestType,
"image": float64(imageID),
}
return s.doAction(id, request)
return s.doAction(ctx, id, request)
}
// Resize a Droplet
func (s *DropletActionsServiceOp) Resize(id int, sizeSlug string, resizeDisk bool) (*Action, *Response, error) {
func (s *DropletActionsServiceOp) Resize(ctx context.Context, id int, sizeSlug string, resizeDisk bool) (*Action, *Response, error) {
requestType := "resize"
request := &ActionRequest{
"type": requestType,
"size": sizeSlug,
"disk": resizeDisk,
}
return s.doAction(id, request)
return s.doAction(ctx, id, request)
}
// Rename a Droplet
func (s *DropletActionsServiceOp) Rename(id int, name string) (*Action, *Response, error) {
func (s *DropletActionsServiceOp) Rename(ctx context.Context, id int, name string) (*Action, *Response, error) {
requestType := "rename"
request := &ActionRequest{
"type": requestType,
"name": name,
}
return s.doAction(id, request)
return s.doAction(ctx, id, request)
}
// Snapshot a Droplet.
func (s *DropletActionsServiceOp) Snapshot(id int, name string) (*Action, *Response, error) {
func (s *DropletActionsServiceOp) Snapshot(ctx context.Context, id int, name string) (*Action, *Response, error) {
requestType := "snapshot"
request := &ActionRequest{
"type": requestType,
"name": name,
}
return s.doAction(id, request)
return s.doAction(ctx, id, request)
}
// DisableBackups disables backups for a droplet.
func (s *DropletActionsServiceOp) DisableBackups(id int) (*Action, *Response, error) {
// SnapshotByTag snapshots Droplets matched by a Tag.
func (s *DropletActionsServiceOp) SnapshotByTag(ctx context.Context, tag string, name string) (*Action, *Response, error) {
requestType := "snapshot"
request := &ActionRequest{
"type": requestType,
"name": name,
}
return s.doActionByTag(ctx, tag, request)
}
// EnableBackups enables backups for a Droplet.
func (s *DropletActionsServiceOp) EnableBackups(ctx context.Context, id int) (*Action, *Response, error) {
request := &ActionRequest{"type": "enable_backups"}
return s.doAction(ctx, id, request)
}
// EnableBackupsByTag enables backups for Droplets matched by a Tag.
func (s *DropletActionsServiceOp) EnableBackupsByTag(ctx context.Context, tag string) (*Action, *Response, error) {
request := &ActionRequest{"type": "enable_backups"}
return s.doActionByTag(ctx, tag, request)
}
// DisableBackups disables backups for a Droplet.
func (s *DropletActionsServiceOp) DisableBackups(ctx context.Context, id int) (*Action, *Response, error) {
request := &ActionRequest{"type": "disable_backups"}
return s.doAction(id, request)
return s.doAction(ctx, id, request)
}
// PasswordReset resets the password for a droplet.
func (s *DropletActionsServiceOp) PasswordReset(id int) (*Action, *Response, error) {
// DisableBackupsByTag disables backups for Droplet matched by a Tag.
func (s *DropletActionsServiceOp) DisableBackupsByTag(ctx context.Context, tag string) (*Action, *Response, error) {
request := &ActionRequest{"type": "disable_backups"}
return s.doActionByTag(ctx, tag, request)
}
// PasswordReset resets the password for a Droplet.
func (s *DropletActionsServiceOp) PasswordReset(ctx context.Context, id int) (*Action, *Response, error) {
request := &ActionRequest{"type": "password_reset"}
return s.doAction(id, request)
return s.doAction(ctx, id, request)
}
// RebuildByImageID rebuilds a droplet droplet from an image with a given id.
func (s *DropletActionsServiceOp) RebuildByImageID(id, imageID int) (*Action, *Response, error) {
// RebuildByImageID rebuilds a Droplet from an image with a given id.
func (s *DropletActionsServiceOp) RebuildByImageID(ctx context.Context, id, imageID int) (*Action, *Response, error) {
request := &ActionRequest{"type": "rebuild", "image": imageID}
return s.doAction(id, request)
return s.doAction(ctx, id, request)
}
// RebuildByImageSlug rebuilds a droplet from an image with a given slug.
func (s *DropletActionsServiceOp) RebuildByImageSlug(id int, slug string) (*Action, *Response, error) {
// RebuildByImageSlug rebuilds a Droplet from an Image matched by a given Slug.
func (s *DropletActionsServiceOp) RebuildByImageSlug(ctx context.Context, id int, slug string) (*Action, *Response, error) {
request := &ActionRequest{"type": "rebuild", "image": slug}
return s.doAction(id, request)
return s.doAction(ctx, id, request)
}
// ChangeKernel changes the kernel for a droplet.
func (s *DropletActionsServiceOp) ChangeKernel(id, kernelID int) (*Action, *Response, error) {
// ChangeKernel changes the kernel for a Droplet.
func (s *DropletActionsServiceOp) ChangeKernel(ctx context.Context, id, kernelID int) (*Action, *Response, error) {
request := &ActionRequest{"type": "change_kernel", "kernel": kernelID}
return s.doAction(id, request)
return s.doAction(ctx, id, request)
}
// EnableIPv6 enables IPv6 for a droplet.
func (s *DropletActionsServiceOp) EnableIPv6(id int) (*Action, *Response, error) {
// EnableIPv6 enables IPv6 for a Droplet.
func (s *DropletActionsServiceOp) EnableIPv6(ctx context.Context, id int) (*Action, *Response, error) {
request := &ActionRequest{"type": "enable_ipv6"}
return s.doAction(id, request)
return s.doAction(ctx, id, request)
}
// EnablePrivateNetworking enables private networking for a droplet.
func (s *DropletActionsServiceOp) EnablePrivateNetworking(id int) (*Action, *Response, error) {
// EnableIPv6ByTag enables IPv6 for Droplets matched by a Tag.
func (s *DropletActionsServiceOp) EnableIPv6ByTag(ctx context.Context, tag string) (*Action, *Response, error) {
request := &ActionRequest{"type": "enable_ipv6"}
return s.doActionByTag(ctx, tag, request)
}
// EnablePrivateNetworking enables private networking for a Droplet.
func (s *DropletActionsServiceOp) EnablePrivateNetworking(ctx context.Context, id int) (*Action, *Response, error) {
request := &ActionRequest{"type": "enable_private_networking"}
return s.doAction(id, request)
return s.doAction(ctx, id, request)
}
// Upgrade a droplet.
func (s *DropletActionsServiceOp) Upgrade(id int) (*Action, *Response, error) {
// EnablePrivateNetworkingByTag enables private networking for Droplets matched by a Tag.
func (s *DropletActionsServiceOp) EnablePrivateNetworkingByTag(ctx context.Context, tag string) (*Action, *Response, error) {
request := &ActionRequest{"type": "enable_private_networking"}
return s.doActionByTag(ctx, tag, request)
}
// Upgrade a Droplet.
func (s *DropletActionsServiceOp) Upgrade(ctx context.Context, id int) (*Action, *Response, error) {
request := &ActionRequest{"type": "upgrade"}
return s.doAction(id, request)
return s.doAction(ctx, id, request)
}
func (s *DropletActionsServiceOp) doAction(id int, request *ActionRequest) (*Action, *Response, error) {
func (s *DropletActionsServiceOp) doAction(ctx context.Context, id int, request *ActionRequest) (*Action, *Response, error) {
if id < 1 {
return nil, nil, NewArgError("id", "cannot be less than 1")
}
@ -171,7 +246,7 @@ func (s *DropletActionsServiceOp) doAction(id int, request *ActionRequest) (*Act
path := dropletActionPath(id)
req, err := s.client.NewRequest("POST", path, request)
req, err := s.client.NewRequest(ctx, "POST", path, request)
if err != nil {
return nil, nil, err
}
@ -182,11 +257,36 @@ func (s *DropletActionsServiceOp) doAction(id int, request *ActionRequest) (*Act
return nil, resp, err
}
return &root.Event, 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) {
func (s *DropletActionsServiceOp) doActionByTag(ctx context.Context, 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(ctx, "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(ctx context.Context, dropletID, actionID int) (*Action, *Response, error) {
if dropletID < 1 {
return nil, nil, NewArgError("dropletID", "cannot be less than 1")
}
@ -196,22 +296,22 @@ func (s *DropletActionsServiceOp) Get(dropletID, actionID int) (*Action, *Respon
}
path := fmt.Sprintf("%s/%d", dropletActionPath(dropletID), actionID)
return s.get(path)
return s.get(ctx, path)
}
// GetByURI gets an action for a particular droplet by id.
func (s *DropletActionsServiceOp) GetByURI(rawurl string) (*Action, *Response, error) {
// GetByURI gets an action for a particular Droplet by id.
func (s *DropletActionsServiceOp) GetByURI(ctx context.Context, rawurl string) (*Action, *Response, error) {
u, err := url.Parse(rawurl)
if err != nil {
return nil, nil, err
}
return s.get(u.Path)
return s.get(ctx, u.Path)
}
func (s *DropletActionsServiceOp) get(path string) (*Action, *Response, error) {
req, err := s.client.NewRequest("GET", path, nil)
func (s *DropletActionsServiceOp) get(ctx context.Context, path string) (*Action, *Response, error) {
req, err := s.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
@ -222,10 +322,14 @@ func (s *DropletActionsServiceOp) get(path string) (*Action, *Response, error) {
return nil, resp, err
}
return &root.Event, resp, err
return root.Event, resp, err
}
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)
}

View File

@ -31,7 +31,7 @@ func TestDropletActions_Shutdown(t *testing.T) {
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
action, _, err := client.DropletActions.Shutdown(1)
action, _, err := client.DropletActions.Shutdown(ctx, 1)
if err != nil {
t.Errorf("DropletActions.Shutdown returned error: %v", err)
}
@ -42,6 +42,44 @@ func TestDropletActions_Shutdown(t *testing.T) {
}
}
func TestDropletActions_ShutdownByTag(t *testing.T) {
setup()
defer teardown()
request := &ActionRequest{
"type": "shutdown",
}
mux.HandleFunc("/v2/droplets/actions", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("tag_name") != "testing-1" {
t.Errorf("DropletActions.ShutdownByTag did not request with a tag parameter")
}
v := new(ActionRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatalf("decode json: %v", err)
}
testMethod(t, r, "POST")
if !reflect.DeepEqual(v, request) {
t.Errorf("Request body = %+v, expected %+v", v, request)
}
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
action, _, err := client.DropletActions.ShutdownByTag(ctx, "testing-1")
if err != nil {
t.Errorf("DropletActions.ShutdownByTag returned error: %v", err)
}
expected := &Action{Status: "in-progress"}
if !reflect.DeepEqual(action, expected) {
t.Errorf("DropletActions.ShutdownByTag returned %+v, expected %+v", action, expected)
}
}
func TestDropletAction_PowerOff(t *testing.T) {
setup()
defer teardown()
@ -65,7 +103,7 @@ func TestDropletAction_PowerOff(t *testing.T) {
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
action, _, err := client.DropletActions.PowerOff(1)
action, _, err := client.DropletActions.PowerOff(ctx, 1)
if err != nil {
t.Errorf("DropletActions.PowerOff returned error: %v", err)
}
@ -76,6 +114,44 @@ func TestDropletAction_PowerOff(t *testing.T) {
}
}
func TestDropletAction_PowerOffByTag(t *testing.T) {
setup()
defer teardown()
request := &ActionRequest{
"type": "power_off",
}
mux.HandleFunc("/v2/droplets/actions", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("tag_name") != "testing-1" {
t.Errorf("DropletActions.PowerOffByTag did not request with a tag parameter")
}
v := new(ActionRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatalf("decode json: %v", err)
}
testMethod(t, r, "POST")
if !reflect.DeepEqual(v, request) {
t.Errorf("Request body = %+v, expected %+v", v, request)
}
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
action, _, err := client.DropletActions.PowerOffByTag(ctx, "testing-1")
if err != nil {
t.Errorf("DropletActions.PowerOffByTag returned error: %v", err)
}
expected := &Action{Status: "in-progress"}
if !reflect.DeepEqual(action, expected) {
t.Errorf("DropletActions.PoweroffByTag returned %+v, expected %+v", action, expected)
}
}
func TestDropletAction_PowerOn(t *testing.T) {
setup()
defer teardown()
@ -99,7 +175,7 @@ func TestDropletAction_PowerOn(t *testing.T) {
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
action, _, err := client.DropletActions.PowerOn(1)
action, _, err := client.DropletActions.PowerOn(ctx, 1)
if err != nil {
t.Errorf("DropletActions.PowerOn returned error: %v", err)
}
@ -110,6 +186,43 @@ func TestDropletAction_PowerOn(t *testing.T) {
}
}
func TestDropletAction_PowerOnByTag(t *testing.T) {
setup()
defer teardown()
request := &ActionRequest{
"type": "power_on",
}
mux.HandleFunc("/v2/droplets/actions", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("tag_name") != "testing-1" {
t.Errorf("DropletActions.PowerOnByTag did not request with a tag parameter")
}
v := new(ActionRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatalf("decode json: %v", err)
}
testMethod(t, r, "POST")
if !reflect.DeepEqual(v, request) {
t.Errorf("Request body = %+v, expected %+v", v, request)
}
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
action, _, err := client.DropletActions.PowerOnByTag(ctx, "testing-1")
if err != nil {
t.Errorf("DropletActions.PowerOnByTag returned error: %v", err)
}
expected := &Action{Status: "in-progress"}
if !reflect.DeepEqual(action, expected) {
t.Errorf("DropletActions.PowerOnByTag returned %+v, expected %+v", action, expected)
}
}
func TestDropletAction_Reboot(t *testing.T) {
setup()
defer teardown()
@ -134,7 +247,7 @@ func TestDropletAction_Reboot(t *testing.T) {
})
action, _, err := client.DropletActions.Reboot(1)
action, _, err := client.DropletActions.Reboot(ctx, 1)
if err != nil {
t.Errorf("DropletActions.Reboot returned error: %v", err)
}
@ -171,7 +284,7 @@ func TestDropletAction_Restore(t *testing.T) {
})
action, _, err := client.DropletActions.Restore(1, 1)
action, _, err := client.DropletActions.Restore(ctx, 1, 1)
if err != nil {
t.Errorf("DropletActions.Restore returned error: %v", err)
}
@ -209,7 +322,7 @@ func TestDropletAction_Resize(t *testing.T) {
})
action, _, err := client.DropletActions.Resize(1, "1024mb", true)
action, _, err := client.DropletActions.Resize(ctx, 1, "1024mb", true)
if err != nil {
t.Errorf("DropletActions.Resize returned error: %v", err)
}
@ -245,7 +358,7 @@ func TestDropletAction_Rename(t *testing.T) {
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
action, _, err := client.DropletActions.Rename(1, "Droplet-Name")
action, _, err := client.DropletActions.Rename(ctx, 1, "Droplet-Name")
if err != nil {
t.Errorf("DropletActions.Rename returned error: %v", err)
}
@ -280,7 +393,7 @@ func TestDropletAction_PowerCycle(t *testing.T) {
})
action, _, err := client.DropletActions.PowerCycle(1)
action, _, err := client.DropletActions.PowerCycle(ctx, 1)
if err != nil {
t.Errorf("DropletActions.PowerCycle returned error: %v", err)
}
@ -291,6 +404,45 @@ func TestDropletAction_PowerCycle(t *testing.T) {
}
}
func TestDropletAction_PowerCycleByTag(t *testing.T) {
setup()
defer teardown()
request := &ActionRequest{
"type": "power_cycle",
}
mux.HandleFunc("/v2/droplets/actions", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("tag_name") != "testing-1" {
t.Errorf("DropletActions.PowerCycleByTag did not request with a tag parameter")
}
v := new(ActionRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatalf("decode json: %v", err)
}
testMethod(t, r, "POST")
if !reflect.DeepEqual(v, request) {
t.Errorf("Request body = %+v, expected %+v", v, request)
}
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
action, _, err := client.DropletActions.PowerCycleByTag(ctx, "testing-1")
if err != nil {
t.Errorf("DropletActions.PowerCycleByTag returned error: %v", err)
}
expected := &Action{Status: "in-progress"}
if !reflect.DeepEqual(action, expected) {
t.Errorf("DropletActions.PowerCycleByTag returned %+v, expected %+v", action, expected)
}
}
func TestDropletAction_Snapshot(t *testing.T) {
setup()
defer teardown()
@ -316,7 +468,7 @@ func TestDropletAction_Snapshot(t *testing.T) {
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
action, _, err := client.DropletActions.Snapshot(1, "Image-Name")
action, _, err := client.DropletActions.Snapshot(ctx, 1, "Image-Name")
if err != nil {
t.Errorf("DropletActions.Snapshot returned error: %v", err)
}
@ -327,6 +479,120 @@ func TestDropletAction_Snapshot(t *testing.T) {
}
}
func TestDropletAction_SnapshotByTag(t *testing.T) {
setup()
defer teardown()
request := &ActionRequest{
"type": "snapshot",
"name": "Image-Name",
}
mux.HandleFunc("/v2/droplets/actions", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("tag_name") != "testing-1" {
t.Errorf("DropletActions.SnapshotByTag did not request with a tag parameter")
}
v := new(ActionRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatalf("decode json: %v", err)
}
testMethod(t, r, "POST")
if !reflect.DeepEqual(v, request) {
t.Errorf("Request body = %+v, expected %+v", v, request)
}
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
action, _, err := client.DropletActions.SnapshotByTag(ctx, "testing-1", "Image-Name")
if err != nil {
t.Errorf("DropletActions.SnapshotByTag returned error: %v", err)
}
expected := &Action{Status: "in-progress"}
if !reflect.DeepEqual(action, expected) {
t.Errorf("DropletActions.SnapshotByTag returned %+v, expected %+v", action, expected)
}
}
func TestDropletAction_EnableBackups(t *testing.T) {
setup()
defer teardown()
request := &ActionRequest{
"type": "enable_backups",
}
mux.HandleFunc("/v2/droplets/1/actions", func(w http.ResponseWriter, r *http.Request) {
v := new(ActionRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatalf("decode json: %v", err)
}
testMethod(t, r, "POST")
if !reflect.DeepEqual(v, request) {
t.Errorf("Request body = %+v, expected %+v", v, request)
}
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
action, _, err := client.DropletActions.EnableBackups(ctx, 1)
if err != nil {
t.Errorf("DropletActions.EnableBackups returned error: %v", err)
}
expected := &Action{Status: "in-progress"}
if !reflect.DeepEqual(action, expected) {
t.Errorf("DropletActions.EnableBackups returned %+v, expected %+v", action, expected)
}
}
func TestDropletAction_EnableBackupsByTag(t *testing.T) {
setup()
defer teardown()
request := &ActionRequest{
"type": "enable_backups",
}
mux.HandleFunc("/v2/droplets/actions", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("tag_name") != "testing-1" {
t.Errorf("DropletActions.EnableBackupByTag did not request with a tag parameter")
}
v := new(ActionRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatalf("decode json: %v", err)
}
testMethod(t, r, "POST")
if !reflect.DeepEqual(v, request) {
t.Errorf("Request body = %+v, expected %+v", v, request)
}
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
action, _, err := client.DropletActions.EnableBackupsByTag(ctx, "testing-1")
if err != nil {
t.Errorf("DropletActions.EnableBackupsByTag returned error: %v", err)
}
expected := &Action{Status: "in-progress"}
if !reflect.DeepEqual(action, expected) {
t.Errorf("DropletActions.EnableBackupsByTag returned %+v, expected %+v", action, expected)
}
}
func TestDropletAction_DisableBackups(t *testing.T) {
setup()
defer teardown()
@ -351,7 +617,7 @@ func TestDropletAction_DisableBackups(t *testing.T) {
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
action, _, err := client.DropletActions.DisableBackups(1)
action, _, err := client.DropletActions.DisableBackups(ctx, 1)
if err != nil {
t.Errorf("DropletActions.DisableBackups returned error: %v", err)
}
@ -362,6 +628,45 @@ func TestDropletAction_DisableBackups(t *testing.T) {
}
}
func TestDropletAction_DisableBackupsByTag(t *testing.T) {
setup()
defer teardown()
request := &ActionRequest{
"type": "disable_backups",
}
mux.HandleFunc("/v2/droplets/actions", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("tag_name") != "testing-1" {
t.Errorf("DropletActions.DisableBackupsByTag did not request with a tag parameter")
}
v := new(ActionRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatalf("decode json: %v", err)
}
testMethod(t, r, "POST")
if !reflect.DeepEqual(v, request) {
t.Errorf("Request body = %+v, expected %+v", v, request)
}
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
action, _, err := client.DropletActions.DisableBackupsByTag(ctx, "testing-1")
if err != nil {
t.Errorf("DropletActions.DisableBackupsByTag returned error: %v", err)
}
expected := &Action{Status: "in-progress"}
if !reflect.DeepEqual(action, expected) {
t.Errorf("DropletActions.DisableBackupsByTag returned %+v, expected %+v", action, expected)
}
}
func TestDropletAction_PasswordReset(t *testing.T) {
setup()
defer teardown()
@ -386,7 +691,7 @@ func TestDropletAction_PasswordReset(t *testing.T) {
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
action, _, err := client.DropletActions.PasswordReset(1)
action, _, err := client.DropletActions.PasswordReset(ctx, 1)
if err != nil {
t.Errorf("DropletActions.PasswordReset returned error: %v", err)
}
@ -422,7 +727,7 @@ func TestDropletAction_RebuildByImageID(t *testing.T) {
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
action, _, err := client.DropletActions.RebuildByImageID(1, 2)
action, _, err := client.DropletActions.RebuildByImageID(ctx, 1, 2)
if err != nil {
t.Errorf("DropletActions.RebuildByImageID returned error: %v", err)
}
@ -458,7 +763,7 @@ func TestDropletAction_RebuildByImageSlug(t *testing.T) {
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
action, _, err := client.DropletActions.RebuildByImageSlug(1, "Image-Name")
action, _, err := client.DropletActions.RebuildByImageSlug(ctx, 1, "Image-Name")
if err != nil {
t.Errorf("DropletActions.RebuildByImageSlug returned error: %v", err)
}
@ -494,7 +799,7 @@ func TestDropletAction_ChangeKernel(t *testing.T) {
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
action, _, err := client.DropletActions.ChangeKernel(1, 2)
action, _, err := client.DropletActions.ChangeKernel(ctx, 1, 2)
if err != nil {
t.Errorf("DropletActions.ChangeKernel returned error: %v", err)
}
@ -529,7 +834,7 @@ func TestDropletAction_EnableIPv6(t *testing.T) {
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
action, _, err := client.DropletActions.EnableIPv6(1)
action, _, err := client.DropletActions.EnableIPv6(ctx, 1)
if err != nil {
t.Errorf("DropletActions.EnableIPv6 returned error: %v", err)
}
@ -540,6 +845,45 @@ func TestDropletAction_EnableIPv6(t *testing.T) {
}
}
func TestDropletAction_EnableIPv6ByTag(t *testing.T) {
setup()
defer teardown()
request := &ActionRequest{
"type": "enable_ipv6",
}
mux.HandleFunc("/v2/droplets/actions", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("tag_name") != "testing-1" {
t.Errorf("DropletActions.EnableIPv6ByTag did not request with a tag parameter")
}
v := new(ActionRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatalf("decode json: %v", err)
}
testMethod(t, r, "POST")
if !reflect.DeepEqual(v, request) {
t.Errorf("Request body = %+v, expected %+v", v, request)
}
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
action, _, err := client.DropletActions.EnableIPv6ByTag(ctx, "testing-1")
if err != nil {
t.Errorf("DropletActions.EnableIPv6ByTag returned error: %v", err)
}
expected := &Action{Status: "in-progress"}
if !reflect.DeepEqual(action, expected) {
t.Errorf("DropletActions.EnableIPv6byTag returned %+v, expected %+v", action, expected)
}
}
func TestDropletAction_EnablePrivateNetworking(t *testing.T) {
setup()
defer teardown()
@ -564,7 +908,7 @@ func TestDropletAction_EnablePrivateNetworking(t *testing.T) {
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
action, _, err := client.DropletActions.EnablePrivateNetworking(1)
action, _, err := client.DropletActions.EnablePrivateNetworking(ctx, 1)
if err != nil {
t.Errorf("DropletActions.EnablePrivateNetworking returned error: %v", err)
}
@ -575,6 +919,45 @@ func TestDropletAction_EnablePrivateNetworking(t *testing.T) {
}
}
func TestDropletAction_EnablePrivateNetworkingByTag(t *testing.T) {
setup()
defer teardown()
request := &ActionRequest{
"type": "enable_private_networking",
}
mux.HandleFunc("/v2/droplets/actions", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("tag_name") != "testing-1" {
t.Errorf("DropletActions.EnablePrivateNetworkingByTag did not request with a tag parameter")
}
v := new(ActionRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatalf("decode json: %v", err)
}
testMethod(t, r, "POST")
if !reflect.DeepEqual(v, request) {
t.Errorf("Request body = %+v, expected %+v", v, request)
}
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
action, _, err := client.DropletActions.EnablePrivateNetworkingByTag(ctx, "testing-1")
if err != nil {
t.Errorf("DropletActions.EnablePrivateNetworkingByTag returned error: %v", err)
}
expected := &Action{Status: "in-progress"}
if !reflect.DeepEqual(action, expected) {
t.Errorf("DropletActions.EnablePrivateNetworkingByTag returned %+v, expected %+v", action, expected)
}
}
func TestDropletAction_Upgrade(t *testing.T) {
setup()
defer teardown()
@ -599,7 +982,7 @@ func TestDropletAction_Upgrade(t *testing.T) {
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
action, _, err := client.DropletActions.Upgrade(1)
action, _, err := client.DropletActions.Upgrade(ctx, 1)
if err != nil {
t.Errorf("DropletActions.Upgrade returned error: %v", err)
}
@ -619,7 +1002,7 @@ func TestDropletActions_Get(t *testing.T) {
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
action, _, err := client.DropletActions.Get(123, 456)
action, _, err := client.DropletActions.Get(ctx, 123, 456)
if err != nil {
t.Errorf("DropletActions.Get returned error: %v", err)
}

View File

@ -1,28 +1,35 @@
package godo
import (
"context"
"encoding/json"
"errors"
"fmt"
)
const dropletBasePath = "v2/droplets"
// DropletsService is an interface for interfacing with the droplet
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)
Get(int) (*Droplet, *Response, error)
Create(*DropletCreateRequest) (*Droplet, *Response, error)
Delete(int) (*Response, error)
Kernels(int, *ListOptions) ([]Kernel, *Response, error)
Snapshots(int, *ListOptions) ([]Image, *Response, error)
Backups(int, *ListOptions) ([]Image, *Response, error)
Actions(int, *ListOptions) ([]Action, *Response, error)
Neighbors(int) ([]Droplet, *Response, error)
List(context.Context, *ListOptions) ([]Droplet, *Response, error)
ListByTag(context.Context, string, *ListOptions) ([]Droplet, *Response, error)
Get(context.Context, int) (*Droplet, *Response, error)
Create(context.Context, *DropletCreateRequest) (*Droplet, *Response, error)
CreateMultiple(context.Context, *DropletMultiCreateRequest) ([]Droplet, *Response, error)
Delete(context.Context, int) (*Response, error)
DeleteByTag(context.Context, string) (*Response, error)
Kernels(context.Context, int, *ListOptions) ([]Kernel, *Response, error)
Snapshots(context.Context, int, *ListOptions) ([]Image, *Response, error)
Backups(context.Context, int, *ListOptions) ([]Image, *Response, error)
Actions(context.Context, int, *ListOptions) ([]Action, *Response, error)
Neighbors(context.Context, int) ([]Droplet, *Response, error)
}
// DropletsServiceOp handles communication with the droplet related methods of the
// DropletsServiceOp handles communication with the Droplet related methods of the
// DigitalOcean API.
type DropletsServiceOp struct {
client *Client
@ -32,22 +39,71 @@ var _ DropletsService = &DropletsServiceOp{}
// Droplet represents a DigitalOcean Droplet
type Droplet struct {
ID int `json:"id,float64,omitempty"`
Name string `json:"name,omitempty"`
Memory int `json:"memory,omitempty"`
Vcpus int `json:"vcpus,omitempty"`
Disk int `json:"disk,omitempty"`
Region *Region `json:"region,omitempty"`
Image *Image `json:"image,omitempty"`
Size *Size `json:"size,omitempty"`
SizeSlug string `json:"size_slug,omitempty"`
BackupIDs []int `json:"backup_ids,omitempty"`
SnapshotIDs []int `json:"snapshot_ids,omitempty"`
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"`
ID int `json:"id,float64,omitempty"`
Name string `json:"name,omitempty"`
Memory int `json:"memory,omitempty"`
Vcpus int `json:"vcpus,omitempty"`
Disk int `json:"disk,omitempty"`
Region *Region `json:"region,omitempty"`
Image *Image `json:"image,omitempty"`
Size *Size `json:"size,omitempty"`
SizeSlug string `json:"size_slug,omitempty"`
BackupIDs []int `json:"backup_ids,omitempty"`
NextBackupWindow *BackupWindow `json:"next_backup_window,omitempty"`
SnapshotIDs []int `json:"snapshot_ids,omitempty"`
Features []string `json:"features,omitempty"`
Locked bool `json:"locked,bool,omitempty"`
Status string `json:"status,omitempty"`
Networks *Networks `json:"networks,omitempty"`
Created string `json:"created_at,omitempty"`
Kernel *Kernel `json:"kernel,omitempty"`
Tags []string `json:"tags,omitempty"`
VolumeIDs []string `json:"volume_ids"`
}
// 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 public IPv6 address for the Droplet.
func (d *Droplet) PublicIPv6() (string, error) {
if d.Networks == nil {
return "", errNoNetworks
}
for _, v6 := range d.Networks.V6 {
if v6.Type == "public" {
return v6.IPAddress, nil
}
}
return "", nil
}
// Kernel object
@ -57,6 +113,12 @@ type Kernel struct {
Version string `json:"version,omitempty"`
}
// BackupWindow object
type BackupWindow struct {
Start *Timestamp `json:"start,omitempty"`
End *Timestamp `json:"end,omitempty"`
}
// Convert Droplet to a string
func (d Droplet) String() string {
return Stringify(d)
@ -78,7 +140,7 @@ type kernelsRoot struct {
Links *Links `json:"links"`
}
type snapshotsRoot struct {
type dropletSnapshotsRoot struct {
Snapshots []Image `json:"snapshots,omitempty"`
Links *Links `json:"links"`
}
@ -104,6 +166,27 @@ func (d DropletCreateImage) MarshalJSON() ([]byte, error) {
return json.Marshal(d.ID)
}
// 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})
}
// DropletCreateSSHKey identifies a SSH Key for the create request. It prefers fingerprint over ID.
type DropletCreateSSHKey struct {
ID int
@ -120,7 +203,7 @@ func (d DropletCreateSSHKey) MarshalJSON() ([]byte, error) {
return json.Marshal(d.ID)
}
// DropletCreateRequest represents a request to create a droplet.
// DropletCreateRequest represents a request to create a Droplet.
type DropletCreateRequest struct {
Name string `json:"name"`
Region string `json:"region"`
@ -130,20 +213,42 @@ type DropletCreateRequest struct {
Backups bool `json:"backups"`
IPv6 bool `json:"ipv6"`
PrivateNetworking bool `json:"private_networking"`
Monitoring bool `json:"monitoring"`
UserData string `json:"user_data,omitempty"`
Volumes []DropletCreateVolume `json:"volumes,omitempty"`
Tags []string `json:"tags"`
}
// DropletMultiCreateRequest is a request to create multiple Droplets.
type DropletMultiCreateRequest struct {
Names []string `json:"names"`
Region string `json:"region"`
Size string `json:"size"`
Image DropletCreateImage `json:"image"`
SSHKeys []DropletCreateSSHKey `json:"ssh_keys"`
Backups bool `json:"backups"`
IPv6 bool `json:"ipv6"`
PrivateNetworking bool `json:"private_networking"`
Monitoring bool `json:"monitoring"`
UserData string `json:"user_data,omitempty"`
Tags []string `json:"tags"`
}
func (d DropletCreateRequest) String() string {
return Stringify(d)
}
// Networks represents the droplet's networks
func (d DropletMultiCreateRequest) String() string {
return Stringify(d)
}
// Networks represents the Droplet's Networks.
type Networks struct {
V4 []NetworkV4 `json:"v4,omitempty"`
V6 []NetworkV6 `json:"v6,omitempty"`
}
// NetworkV4 represents a DigitalOcean IPv4 Network
// NetworkV4 represents a DigitalOcean IPv4 Network.
type NetworkV4 struct {
IPAddress string `json:"ip_address,omitempty"`
Netmask string `json:"netmask,omitempty"`
@ -167,15 +272,9 @@ 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
}
req, err := s.client.NewRequest("GET", path, nil)
// Performs a list request given a path.
func (s *DropletsServiceOp) list(ctx context.Context, path string) ([]Droplet, *Response, error) {
req, err := s.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
@ -192,15 +291,37 @@ func (s *DropletsServiceOp) List(opt *ListOptions) ([]Droplet, *Response, error)
return root.Droplets, resp, err
}
// Get individual droplet
func (s *DropletsServiceOp) Get(dropletID int) (*Droplet, *Response, error) {
// List all Droplets.
func (s *DropletsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Droplet, *Response, error) {
path := dropletBasePath
path, err := addOptions(path, opt)
if err != nil {
return nil, nil, err
}
return s.list(ctx, path)
}
// ListByTag lists all Droplets matched by a Tag.
func (s *DropletsServiceOp) ListByTag(ctx context.Context, 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(ctx, path)
}
// Get individual Droplet.
func (s *DropletsServiceOp) Get(ctx context.Context, dropletID int) (*Droplet, *Response, error) {
if dropletID < 1 {
return nil, nil, NewArgError("dropletID", "cannot be less than 1")
}
path := fmt.Sprintf("%s/%d", dropletBasePath, dropletID)
req, err := s.client.NewRequest("GET", path, nil)
req, err := s.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
@ -214,15 +335,15 @@ func (s *DropletsServiceOp) Get(dropletID int) (*Droplet, *Response, error) {
return root.Droplet, resp, err
}
// Create droplet
func (s *DropletsServiceOp) Create(createRequest *DropletCreateRequest) (*Droplet, *Response, error) {
// Create Droplet
func (s *DropletsServiceOp) Create(ctx context.Context, createRequest *DropletCreateRequest) (*Droplet, *Response, error) {
if createRequest == nil {
return nil, nil, NewArgError("createRequest", "cannot be nil")
}
path := dropletBasePath
req, err := s.client.NewRequest("POST", path, createRequest)
req, err := s.client.NewRequest(ctx, "POST", path, createRequest)
if err != nil {
return nil, nil, err
}
@ -239,15 +360,34 @@ func (s *DropletsServiceOp) Create(createRequest *DropletCreateRequest) (*Drople
return root.Droplet, resp, err
}
// Delete droplet
func (s *DropletsServiceOp) Delete(dropletID int) (*Response, error) {
if dropletID < 1 {
return nil, NewArgError("dropletID", "cannot be less than 1")
// CreateMultiple creates multiple Droplets.
func (s *DropletsServiceOp) CreateMultiple(ctx context.Context, createRequest *DropletMultiCreateRequest) ([]Droplet, *Response, error) {
if createRequest == nil {
return nil, nil, NewArgError("createRequest", "cannot be nil")
}
path := fmt.Sprintf("%s/%d", dropletBasePath, dropletID)
path := dropletBasePath
req, err := s.client.NewRequest("DELETE", path, nil)
req, err := s.client.NewRequest(ctx, "POST", path, createRequest)
if err != nil {
return nil, nil, err
}
root := new(dropletsRoot)
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.Droplets, resp, err
}
// Performs a delete request given a path
func (s *DropletsServiceOp) delete(ctx context.Context, path string) (*Response, error) {
req, err := s.client.NewRequest(ctx, "DELETE", path, nil)
if err != nil {
return nil, err
}
@ -257,8 +397,30 @@ func (s *DropletsServiceOp) Delete(dropletID int) (*Response, error) {
return resp, err
}
// Kernels lists kernels available for a droplet.
func (s *DropletsServiceOp) Kernels(dropletID int, opt *ListOptions) ([]Kernel, *Response, error) {
// Delete Droplet.
func (s *DropletsServiceOp) Delete(ctx context.Context, 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(ctx, path)
}
// DeleteByTag deletes Droplets matched by a Tag.
func (s *DropletsServiceOp) DeleteByTag(ctx context.Context, 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(ctx, path)
}
// Kernels lists kernels available for a Droplet.
func (s *DropletsServiceOp) Kernels(ctx context.Context, dropletID int, opt *ListOptions) ([]Kernel, *Response, error) {
if dropletID < 1 {
return nil, nil, NewArgError("dropletID", "cannot be less than 1")
}
@ -269,7 +431,7 @@ func (s *DropletsServiceOp) Kernels(dropletID int, opt *ListOptions) ([]Kernel,
return nil, nil, err
}
req, err := s.client.NewRequest("GET", path, nil)
req, err := s.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
@ -283,8 +445,8 @@ func (s *DropletsServiceOp) Kernels(dropletID int, opt *ListOptions) ([]Kernel,
return root.Kernels, resp, err
}
// Actions lists the actions for a droplet.
func (s *DropletsServiceOp) Actions(dropletID int, opt *ListOptions) ([]Action, *Response, error) {
// Actions lists the actions for a Droplet.
func (s *DropletsServiceOp) Actions(ctx context.Context, dropletID int, opt *ListOptions) ([]Action, *Response, error) {
if dropletID < 1 {
return nil, nil, NewArgError("dropletID", "cannot be less than 1")
}
@ -295,7 +457,7 @@ func (s *DropletsServiceOp) Actions(dropletID int, opt *ListOptions) ([]Action,
return nil, nil, err
}
req, err := s.client.NewRequest("GET", path, nil)
req, err := s.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
@ -312,8 +474,8 @@ func (s *DropletsServiceOp) Actions(dropletID int, opt *ListOptions) ([]Action,
return root.Actions, resp, err
}
// Backups lists the backups for a droplet.
func (s *DropletsServiceOp) Backups(dropletID int, opt *ListOptions) ([]Image, *Response, error) {
// Backups lists the backups for a Droplet.
func (s *DropletsServiceOp) Backups(ctx context.Context, dropletID int, opt *ListOptions) ([]Image, *Response, error) {
if dropletID < 1 {
return nil, nil, NewArgError("dropletID", "cannot be less than 1")
}
@ -324,7 +486,7 @@ func (s *DropletsServiceOp) Backups(dropletID int, opt *ListOptions) ([]Image, *
return nil, nil, err
}
req, err := s.client.NewRequest("GET", path, nil)
req, err := s.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
@ -341,8 +503,8 @@ func (s *DropletsServiceOp) Backups(dropletID int, opt *ListOptions) ([]Image, *
return root.Backups, resp, err
}
// Snapshots lists the snapshots available for a droplet.
func (s *DropletsServiceOp) Snapshots(dropletID int, opt *ListOptions) ([]Image, *Response, error) {
// Snapshots lists the snapshots available for a Droplet.
func (s *DropletsServiceOp) Snapshots(ctx context.Context, dropletID int, opt *ListOptions) ([]Image, *Response, error) {
if dropletID < 1 {
return nil, nil, NewArgError("dropletID", "cannot be less than 1")
}
@ -353,12 +515,12 @@ func (s *DropletsServiceOp) Snapshots(dropletID int, opt *ListOptions) ([]Image,
return nil, nil, err
}
req, err := s.client.NewRequest("GET", path, nil)
req, err := s.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
root := new(snapshotsRoot)
root := new(dropletSnapshotsRoot)
resp, err := s.client.Do(req, root)
if err != nil {
return nil, resp, err
@ -370,15 +532,15 @@ func (s *DropletsServiceOp) Snapshots(dropletID int, opt *ListOptions) ([]Image,
return root.Snapshots, resp, err
}
// Neighbors lists the neighbors for a droplet.
func (s *DropletsServiceOp) Neighbors(dropletID int) ([]Droplet, *Response, error) {
// Neighbors lists the neighbors for a Droplet.
func (s *DropletsServiceOp) Neighbors(ctx context.Context, dropletID int) ([]Droplet, *Response, error) {
if dropletID < 1 {
return nil, nil, NewArgError("dropletID", "cannot be less than 1")
}
path := fmt.Sprintf("%s/%d/neighbors", dropletBasePath, dropletID)
req, err := s.client.NewRequest("GET", path, nil)
req, err := s.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
@ -392,8 +554,8 @@ func (s *DropletsServiceOp) Neighbors(dropletID int) ([]Droplet, *Response, erro
return root.Droplets, resp, err
}
func (s *DropletsServiceOp) dropletActionStatus(uri string) (string, error) {
action, _, err := s.client.DropletActions.GetByURI(uri)
func (s *DropletsServiceOp) dropletActionStatus(ctx context.Context, uri string) (string, error) {
action, _, err := s.client.DropletActions.GetByURI(ctx, uri)
if err != nil {
return "", err

View File

@ -17,14 +17,38 @@ func TestDroplets_ListDroplets(t *testing.T) {
fmt.Fprint(w, `{"droplets": [{"id":1},{"id":2}]}`)
})
droplets, _, err := client.Droplets.List(nil)
droplets, _, err := client.Droplets.List(ctx, nil)
if err != nil {
t.Errorf("Droplets.List returned error: %v", err)
}
expected := []Droplet{{ID: 1}, {ID: 2}}
if !reflect.DeepEqual(droplets, expected) {
t.Errorf("Droplets.List returned %+v, expected %+v", droplets, expected)
t.Errorf("Droplets.List\n got=%#v\nwant=%#v", droplets, expected)
}
}
func TestDroplets_ListDropletsByTag(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v2/droplets", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("tag_name") != "testing-1" {
t.Errorf("Droplets.ListByTag did not request with a tag parameter")
}
testMethod(t, r, "GET")
fmt.Fprint(w, `{"droplets": [{"id":1},{"id":2}]}`)
})
droplets, _, err := client.Droplets.ListByTag(ctx, "testing-1", nil)
if err != nil {
t.Errorf("Droplets.ListByTag returned error: %v", err)
}
expected := []Droplet{{ID: 1}, {ID: 2}}
if !reflect.DeepEqual(droplets, expected) {
t.Errorf("Droplets.ListByTag returned %+v, expected %+v", droplets, expected)
}
}
@ -53,7 +77,7 @@ func TestDroplets_ListDropletsMultiplePages(t *testing.T) {
fmt.Fprint(w, string(b))
})
_, resp, err := client.Droplets.List(nil)
_, resp, err := client.Droplets.List(ctx, nil)
if err != nil {
t.Fatal(err)
}
@ -84,7 +108,7 @@ func TestDroplets_RetrievePageByNumber(t *testing.T) {
})
opt := &ListOptions{Page: 2}
_, resp, err := client.Droplets.List(opt)
_, resp, err := client.Droplets.List(ctx, opt)
if err != nil {
t.Fatal(err)
}
@ -101,14 +125,14 @@ func TestDroplets_GetDroplet(t *testing.T) {
fmt.Fprint(w, `{"droplet":{"id":12345}}`)
})
droplets, _, err := client.Droplets.Get(12345)
droplets, _, err := client.Droplets.Get(ctx, 12345)
if err != nil {
t.Errorf("Droplet.Get returned error: %v", err)
}
expected := &Droplet{ID: 12345}
if !reflect.DeepEqual(droplets, expected) {
t.Errorf("Droplets.Get returned %+v, expected %+v", droplets, expected)
t.Errorf("Droplets.Get\n got=%#v\nwant=%#v", droplets, expected)
}
}
@ -123,6 +147,12 @@ func TestDroplets_Create(t *testing.T) {
Image: DropletCreateImage{
ID: 1,
},
Volumes: []DropletCreateVolume{
{Name: "hello-im-a-volume"},
{ID: "hello-im-another-volume"},
{Name: "hello-im-still-a-volume", ID: "should be ignored due to Name"},
},
Tags: []string{"one", "two"},
}
mux.HandleFunc("/v2/droplets", func(w http.ResponseWriter, r *http.Request) {
@ -135,6 +165,68 @@ func TestDroplets_Create(t *testing.T) {
"backups": false,
"ipv6": false,
"private_networking": false,
"monitoring": false,
"volumes": []interface{}{
map[string]interface{}{"name": "hello-im-a-volume"},
map[string]interface{}{"id": "hello-im-another-volume"},
map[string]interface{}{"name": "hello-im-still-a-volume"},
},
"tags": []interface{}{"one", "two"},
}
var v map[string]interface{}
err := json.NewDecoder(r.Body).Decode(&v)
if err != nil {
t.Fatalf("decode json: %v", err)
}
if !reflect.DeepEqual(v, expected) {
t.Errorf("Request body\n got=%#v\nwant=%#v", v, expected)
}
fmt.Fprintf(w, `{"droplet":{"id":1}, "links":{"actions": [{"id": 1, "href": "http://example.com", "rel": "create"}]}}`)
})
droplet, resp, err := client.Droplets.Create(ctx, createRequest)
if err != nil {
t.Errorf("Droplets.Create returned error: %v", err)
}
if id := droplet.ID; id != 1 {
t.Errorf("expected id '%d', received '%d'", 1, id)
}
if a := resp.Links.Actions[0]; a.ID != 1 {
t.Errorf("expected action id '%d', received '%d'", 1, a.ID)
}
}
func TestDroplets_CreateMultiple(t *testing.T) {
setup()
defer teardown()
createRequest := &DropletMultiCreateRequest{
Names: []string{"name1", "name2"},
Region: "region",
Size: "size",
Image: DropletCreateImage{
ID: 1,
},
Tags: []string{"one", "two"},
}
mux.HandleFunc("/v2/droplets", func(w http.ResponseWriter, r *http.Request) {
expected := map[string]interface{}{
"names": []interface{}{"name1", "name2"},
"region": "region",
"size": "size",
"image": float64(1),
"ssh_keys": nil,
"backups": false,
"ipv6": false,
"private_networking": false,
"monitoring": false,
"tags": []interface{}{"one", "two"},
}
var v map[string]interface{}
@ -147,15 +239,19 @@ func TestDroplets_Create(t *testing.T) {
t.Errorf("Request body = %#v, expected %#v", v, expected)
}
fmt.Fprintf(w, `{"droplet":{"id":1}, "links":{"actions": [{"id": 1, "href": "http://example.com", "rel": "create"}]}}`)
fmt.Fprintf(w, `{"droplets":[{"id":1},{"id":2}], "links":{"actions": [{"id": 1, "href": "http://example.com", "rel": "multiple_create"}]}}`)
})
droplet, resp, err := client.Droplets.Create(createRequest)
droplets, resp, err := client.Droplets.CreateMultiple(ctx, createRequest)
if err != nil {
t.Errorf("Droplets.Create returned error: %v", err)
t.Errorf("Droplets.CreateMultiple returned error: %v", err)
}
if id := droplet.ID; id != 1 {
if id := droplets[0].ID; id != 1 {
t.Errorf("expected id '%d', received '%d'", 1, id)
}
if id := droplets[1].ID; id != 2 {
t.Errorf("expected id '%d', received '%d'", 1, id)
}
@ -172,7 +268,25 @@ func TestDroplets_Destroy(t *testing.T) {
testMethod(t, r, "DELETE")
})
_, err := client.Droplets.Delete(12345)
_, err := client.Droplets.Delete(ctx, 12345)
if err != nil {
t.Errorf("Droplet.Delete returned error: %v", err)
}
}
func TestDroplets_DestroyByTag(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v2/droplets", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("tag_name") != "testing-1" {
t.Errorf("Droplets.DeleteByTag did not request with a tag parameter")
}
testMethod(t, r, "DELETE")
})
_, err := client.Droplets.DeleteByTag(ctx, "testing-1")
if err != nil {
t.Errorf("Droplet.Delete returned error: %v", err)
}
@ -188,14 +302,14 @@ func TestDroplets_Kernels(t *testing.T) {
})
opt := &ListOptions{Page: 2}
kernels, _, err := client.Droplets.Kernels(12345, opt)
kernels, _, err := client.Droplets.Kernels(ctx, 12345, opt)
if err != nil {
t.Errorf("Droplets.Kernels returned error: %v", err)
}
expected := []Kernel{{ID: 1}, {ID: 2}}
if !reflect.DeepEqual(kernels, expected) {
t.Errorf("Droplets.Kernels returned %+v, expected %+v", kernels, expected)
t.Errorf("Droplets.Kernels\n got=%#v\nwant=%#v", kernels, expected)
}
}
@ -209,14 +323,14 @@ func TestDroplets_Snapshots(t *testing.T) {
})
opt := &ListOptions{Page: 2}
snapshots, _, err := client.Droplets.Snapshots(12345, opt)
snapshots, _, err := client.Droplets.Snapshots(ctx, 12345, opt)
if err != nil {
t.Errorf("Droplets.Snapshots returned error: %v", err)
}
expected := []Image{{ID: 1}, {ID: 2}}
if !reflect.DeepEqual(snapshots, expected) {
t.Errorf("Droplets.Snapshots returned %+v, expected %+v", snapshots, expected)
t.Errorf("Droplets.Snapshots\n got=%#v\nwant=%#v", snapshots, expected)
}
}
@ -230,14 +344,14 @@ func TestDroplets_Backups(t *testing.T) {
})
opt := &ListOptions{Page: 2}
backups, _, err := client.Droplets.Backups(12345, opt)
backups, _, err := client.Droplets.Backups(ctx, 12345, opt)
if err != nil {
t.Errorf("Droplets.Backups returned error: %v", err)
}
expected := []Image{{ID: 1}, {ID: 2}}
if !reflect.DeepEqual(backups, expected) {
t.Errorf("Droplets.Backups returned %+v, expected %+v", backups, expected)
t.Errorf("Droplets.Backups\n got=%#v\nwant=%#v", backups, expected)
}
}
@ -251,14 +365,14 @@ func TestDroplets_Actions(t *testing.T) {
})
opt := &ListOptions{Page: 2}
actions, _, err := client.Droplets.Actions(12345, opt)
actions, _, err := client.Droplets.Actions(ctx, 12345, opt)
if err != nil {
t.Errorf("Droplets.Actions returned error: %v", err)
}
expected := []Action{{ID: 1}, {ID: 2}}
if !reflect.DeepEqual(actions, expected) {
t.Errorf("Droplets.Actions returned %+v, expected %+v", actions, expected)
t.Errorf("Droplets.Actions\n got=%#v\nwant=%#v", actions, expected)
}
}
@ -271,14 +385,14 @@ func TestDroplets_Neighbors(t *testing.T) {
fmt.Fprint(w, `{"droplets": [{"id":1},{"id":2}]}`)
})
neighbors, _, err := client.Droplets.Neighbors(12345)
neighbors, _, err := client.Droplets.Neighbors(ctx, 12345)
if err != nil {
t.Errorf("Droplets.Neighbors returned error: %v", err)
}
expected := []Droplet{{ID: 1}, {ID: 2}}
if !reflect.DeepEqual(neighbors, expected) {
t.Errorf("Droplets.Neighbors returned %+v, expected %+v", neighbors, expected)
t.Errorf("Droplets.Neighbors\n got=%#v\nwant=%#v", neighbors, expected)
}
}
@ -292,7 +406,7 @@ func TestNetworkV4_String(t *testing.T) {
stringified := network.String()
expected := `godo.NetworkV4{IPAddress:"192.168.1.2", Netmask:"255.255.255.0", Gateway:"192.168.1.1", Type:""}`
if expected != stringified {
t.Errorf("NetworkV4.String returned %+v, expected %+v", stringified, expected)
t.Errorf("NetworkV4.String\n got=%#v\nwant=%#v", stringified, expected)
}
}
@ -306,67 +420,49 @@ func TestNetworkV6_String(t *testing.T) {
stringified := network.String()
expected := `godo.NetworkV6{IPAddress:"2604:A880:0800:0010:0000:0000:02DD:4001", Netmask:64, Gateway:"2604:A880:0800:0010:0000:0000:0000:0001", Type:""}`
if expected != stringified {
t.Errorf("NetworkV6.String returned %+v, expected %+v", stringified, expected)
t.Errorf("NetworkV6.String\n got=%#v\nwant=%#v", stringified, expected)
}
}
func TestDroplet_String(t *testing.T) {
func TestDroplets_IPMethods(t *testing.T) {
var d Droplet
region := &Region{
Slug: "region",
Name: "Region",
Sizes: []string{"1", "2"},
Available: true,
ipv6 := "1000:1000:1000:1000:0000:0000:004D:B001"
d.Networks = &Networks{
V4: []NetworkV4{
{IPAddress: "192.168.0.1", Type: "public"},
{IPAddress: "10.0.0.1", Type: "private"},
},
V6: []NetworkV6{
{IPAddress: ipv6, Type: "public"},
},
}
image := &Image{
ID: 1,
Name: "Image",
Type: "snapshot",
Distribution: "Ubuntu",
Slug: "image",
Public: true,
Regions: []string{"one", "two"},
MinDiskSize: 20,
Created: "2013-11-27T09:24:55Z",
ip, err := d.PublicIPv4()
if err != nil {
t.Errorf("unknown error")
}
size := &Size{
Slug: "size",
PriceMonthly: 123,
PriceHourly: 456,
Regions: []string{"1", "2"},
}
network := &NetworkV4{
IPAddress: "192.168.1.2",
Netmask: "255.255.255.0",
Gateway: "192.168.1.1",
}
networks := &Networks{
V4: []NetworkV4{*network},
if got, expected := ip, "192.168.0.1"; got != expected {
t.Errorf("Droplet.PublicIPv4 returned %s; expected %s", got, expected)
}
droplet := &Droplet{
ID: 1,
Name: "droplet",
Memory: 123,
Vcpus: 456,
Disk: 789,
Region: region,
Image: image,
Size: size,
BackupIDs: []int{1},
SnapshotIDs: []int{1},
ActionIDs: []int{1},
Locked: false,
Status: "active",
Networks: networks,
SizeSlug: "1gb",
ip, err = d.PrivateIPv4()
if err != nil {
t.Errorf("unknown error")
}
stringified := droplet.String()
expected := `godo.Droplet{ID:1, Name:"droplet", Memory:123, Vcpus:456, Disk:789, Region:godo.Region{Slug:"region", Name:"Region", Sizes:["1" "2"], Available:true}, Image:godo.Image{ID:1, Name:"Image", Type:"snapshot", Distribution:"Ubuntu", Slug:"image", Public:true, Regions:["one" "two"], MinDiskSize:20, Created:"2013-11-27T09:24:55Z"}, Size:godo.Size{Slug:"size", Memory:0, Vcpus:0, Disk:0, PriceMonthly:123, PriceHourly:456, Regions:["1" "2"], Available:false, Transfer:0}, SizeSlug:"1gb", BackupIDs:[1], SnapshotIDs:[1], Locked:false, Status:"active", Networks:godo.Networks{V4:[godo.NetworkV4{IPAddress:"192.168.1.2", Netmask:"255.255.255.0", Gateway:"192.168.1.1", Type:""}]}, ActionIDs:[1], Created:""}`
if expected != stringified {
t.Errorf("Droplet.String returned %+v, expected %+v", stringified, expected)
if got, expected := ip, "10.0.0.1"; got != expected {
t.Errorf("Droplet.PrivateIPv4 returned %s; expected %s", got, expected)
}
ip, err = d.PublicIPv6()
if err != nil {
t.Errorf("unknown error")
}
if got, expected := ip, ipv6; got != expected {
t.Errorf("Droplet.PublicIPv6 returned %s; expected %s", got, expected)
}
}

134
vendor/github.com/digitalocean/godo/floating_ips.go generated vendored Normal file
View File

@ -0,0 +1,134 @@
package godo
import (
"context"
"fmt"
)
const floatingBasePath = "v2/floating_ips"
// FloatingIPsService is an interface for interfacing with the floating IPs
// endpoints of the Digital Ocean API.
// See: https://developers.digitalocean.com/documentation/v2#floating-ips
type FloatingIPsService interface {
List(context.Context, *ListOptions) ([]FloatingIP, *Response, error)
Get(context.Context, string) (*FloatingIP, *Response, error)
Create(context.Context, *FloatingIPCreateRequest) (*FloatingIP, *Response, error)
Delete(context.Context, string) (*Response, error)
}
// FloatingIPsServiceOp handles communication with the floating IPs related methods of the
// DigitalOcean API.
type FloatingIPsServiceOp struct {
client *Client
}
var _ FloatingIPsService = &FloatingIPsServiceOp{}
// FloatingIP represents a Digital Ocean floating IP.
type FloatingIP struct {
Region *Region `json:"region"`
Droplet *Droplet `json:"droplet"`
IP string `json:"ip"`
}
func (f FloatingIP) String() string {
return Stringify(f)
}
type floatingIPsRoot struct {
FloatingIPs []FloatingIP `json:"floating_ips"`
Links *Links `json:"links"`
}
type floatingIPRoot struct {
FloatingIP *FloatingIP `json:"floating_ip"`
Links *Links `json:"links,omitempty"`
}
// FloatingIPCreateRequest represents a request to create a floating IP.
// If DropletID is not empty, the floating IP will be assigned to the
// droplet.
type FloatingIPCreateRequest struct {
Region string `json:"region"`
DropletID int `json:"droplet_id,omitempty"`
}
// List all floating IPs.
func (f *FloatingIPsServiceOp) List(ctx context.Context, opt *ListOptions) ([]FloatingIP, *Response, error) {
path := floatingBasePath
path, err := addOptions(path, opt)
if err != nil {
return nil, nil, err
}
req, err := f.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
root := new(floatingIPsRoot)
resp, err := f.client.Do(req, root)
if err != nil {
return nil, resp, err
}
if l := root.Links; l != nil {
resp.Links = l
}
return root.FloatingIPs, resp, err
}
// Get an individual floating IP.
func (f *FloatingIPsServiceOp) Get(ctx context.Context, ip string) (*FloatingIP, *Response, error) {
path := fmt.Sprintf("%s/%s", floatingBasePath, ip)
req, err := f.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
root := new(floatingIPRoot)
resp, err := f.client.Do(req, root)
if err != nil {
return nil, resp, err
}
return root.FloatingIP, resp, err
}
// Create a floating IP. If the DropletID field of the request is not empty,
// the floating IP will also be assigned to the droplet.
func (f *FloatingIPsServiceOp) Create(ctx context.Context, createRequest *FloatingIPCreateRequest) (*FloatingIP, *Response, error) {
path := floatingBasePath
req, err := f.client.NewRequest(ctx, "POST", path, createRequest)
if err != nil {
return nil, nil, err
}
root := new(floatingIPRoot)
resp, err := f.client.Do(req, root)
if err != nil {
return nil, resp, err
}
if l := root.Links; l != nil {
resp.Links = l
}
return root.FloatingIP, resp, err
}
// Delete a floating IP.
func (f *FloatingIPsServiceOp) Delete(ctx context.Context, ip string) (*Response, error) {
path := fmt.Sprintf("%s/%s", floatingBasePath, ip)
req, err := f.client.NewRequest(ctx, "DELETE", path, nil)
if err != nil {
return nil, err
}
resp, err := f.client.Do(req, nil)
return resp, err
}

View File

@ -0,0 +1,108 @@
package godo
import (
"context"
"fmt"
)
// FloatingIPActionsService is an interface for interfacing with the
// floating IPs actions endpoints of the Digital Ocean API.
// See: https://developers.digitalocean.com/documentation/v2#floating-ips-action
type FloatingIPActionsService interface {
Assign(ctx context.Context, ip string, dropletID int) (*Action, *Response, error)
Unassign(ctx context.Context, ip string) (*Action, *Response, error)
Get(ctx context.Context, ip string, actionID int) (*Action, *Response, error)
List(ctx context.Context, ip string, opt *ListOptions) ([]Action, *Response, error)
}
// FloatingIPActionsServiceOp handles communication with the floating IPs
// action related methods of the DigitalOcean API.
type FloatingIPActionsServiceOp struct {
client *Client
}
// Assign a floating IP to a droplet.
func (s *FloatingIPActionsServiceOp) Assign(ctx context.Context, ip string, dropletID int) (*Action, *Response, error) {
request := &ActionRequest{
"type": "assign",
"droplet_id": dropletID,
}
return s.doAction(ctx, ip, request)
}
// Unassign a floating IP from the droplet it is currently assigned to.
func (s *FloatingIPActionsServiceOp) Unassign(ctx context.Context, ip string) (*Action, *Response, error) {
request := &ActionRequest{"type": "unassign"}
return s.doAction(ctx, ip, request)
}
// Get an action for a particular floating IP by id.
func (s *FloatingIPActionsServiceOp) Get(ctx context.Context, ip string, actionID int) (*Action, *Response, error) {
path := fmt.Sprintf("%s/%d", floatingIPActionPath(ip), actionID)
return s.get(ctx, path)
}
// List the actions for a particular floating IP.
func (s *FloatingIPActionsServiceOp) List(ctx context.Context, ip string, opt *ListOptions) ([]Action, *Response, error) {
path := floatingIPActionPath(ip)
path, err := addOptions(path, opt)
if err != nil {
return nil, nil, err
}
return s.list(ctx, path)
}
func (s *FloatingIPActionsServiceOp) doAction(ctx context.Context, ip string, request *ActionRequest) (*Action, *Response, error) {
path := floatingIPActionPath(ip)
req, err := s.client.NewRequest(ctx, "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 (s *FloatingIPActionsServiceOp) get(ctx context.Context, path string) (*Action, *Response, error) {
req, err := s.client.NewRequest(ctx, "GET", path, nil)
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 (s *FloatingIPActionsServiceOp) list(ctx context.Context, path string) ([]Action, *Response, error) {
req, err := s.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
root := new(actionsRoot)
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.Actions, resp, err
}
func floatingIPActionPath(ip string) string {
return fmt.Sprintf("%s/%s/actions", floatingBasePath, ip)
}

View File

@ -0,0 +1,167 @@
package godo
import (
"encoding/json"
"fmt"
"net/http"
"reflect"
"testing"
)
func TestFloatingIPsActions_Assign(t *testing.T) {
setup()
defer teardown()
dropletID := 12345
assignRequest := &ActionRequest{
"droplet_id": float64(dropletID), // encoding/json decodes numbers as floats
"type": "assign",
}
mux.HandleFunc("/v2/floating_ips/192.168.0.1/actions", func(w http.ResponseWriter, r *http.Request) {
v := new(ActionRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatalf("decode json: %v", err)
}
testMethod(t, r, "POST")
if !reflect.DeepEqual(v, assignRequest) {
t.Errorf("Request body = %#v, expected %#v", v, assignRequest)
}
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
assign, _, err := client.FloatingIPActions.Assign(ctx, "192.168.0.1", 12345)
if err != nil {
t.Errorf("FloatingIPsActions.Assign returned error: %v", err)
}
expected := &Action{Status: "in-progress"}
if !reflect.DeepEqual(assign, expected) {
t.Errorf("FloatingIPsActions.Assign returned %+v, expected %+v", assign, expected)
}
}
func TestFloatingIPsActions_Unassign(t *testing.T) {
setup()
defer teardown()
unassignRequest := &ActionRequest{
"type": "unassign",
}
mux.HandleFunc("/v2/floating_ips/192.168.0.1/actions", func(w http.ResponseWriter, r *http.Request) {
v := new(ActionRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatalf("decode json: %v", err)
}
testMethod(t, r, "POST")
if !reflect.DeepEqual(v, unassignRequest) {
t.Errorf("Request body = %+v, expected %+v", v, unassignRequest)
}
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
action, _, err := client.FloatingIPActions.Unassign(ctx, "192.168.0.1")
if err != nil {
t.Errorf("FloatingIPsActions.Get returned error: %v", err)
}
expected := &Action{Status: "in-progress"}
if !reflect.DeepEqual(action, expected) {
t.Errorf("FloatingIPsActions.Get returned %+v, expected %+v", action, expected)
}
}
func TestFloatingIPsActions_Get(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v2/floating_ips/192.168.0.1/actions/456", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
action, _, err := client.FloatingIPActions.Get(ctx, "192.168.0.1", 456)
if err != nil {
t.Errorf("FloatingIPsActions.Get returned error: %v", err)
}
expected := &Action{Status: "in-progress"}
if !reflect.DeepEqual(action, expected) {
t.Errorf("FloatingIPsActions.Get returned %+v, expected %+v", action, expected)
}
}
func TestFloatingIPsActions_List(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v2/floating_ips/192.168.0.1/actions", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprintf(w, `{"actions":[{"status":"in-progress"}]}`)
})
actions, _, err := client.FloatingIPActions.List(ctx, "192.168.0.1", nil)
if err != nil {
t.Errorf("FloatingIPsActions.List returned error: %v", err)
}
expected := []Action{{Status: "in-progress"}}
if !reflect.DeepEqual(actions, expected) {
t.Errorf("FloatingIPsActions.List returned %+v, expected %+v", actions, expected)
}
}
func TestFloatingIPsActions_ListMultiplePages(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v2/floating_ips/192.168.0.1/actions", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{"actions":[{"status":"in-progress"}], "links":{"pages":{"next":"http://example.com/v2/floating_ips/192.168.0.1/actions?page=2"}}}`)
})
_, resp, err := client.FloatingIPActions.List(ctx, "192.168.0.1", nil)
if err != nil {
t.Errorf("FloatingIPsActions.List returned error: %v", err)
}
checkCurrentPage(t, resp, 1)
}
func TestFloatingIPsActions_ListPageByNumber(t *testing.T) {
setup()
defer teardown()
jBlob := `
{
"actions":[{"status":"in-progress"}],
"links":{
"pages":{
"next":"http://example.com/v2/regions/?page=3",
"prev":"http://example.com/v2/regions/?page=1",
"last":"http://example.com/v2/regions/?page=3",
"first":"http://example.com/v2/regions/?page=1"
}
}
}`
mux.HandleFunc("/v2/floating_ips/192.168.0.1/actions", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, jBlob)
})
opt := &ListOptions{Page: 2}
_, resp, err := client.FloatingIPActions.List(ctx, "192.168.0.1", opt)
if err != nil {
t.Errorf("FloatingIPsActions.List returned error: %v", err)
}
checkCurrentPage(t, resp, 2)
}

View File

@ -0,0 +1,149 @@
package godo
import (
"encoding/json"
"fmt"
"net/http"
"reflect"
"testing"
)
func TestFloatingIPs_ListFloatingIPs(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v2/floating_ips", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{"floating_ips": [{"region":{"slug":"nyc3"},"droplet":{"id":1},"ip":"192.168.0.1"},{"region":{"slug":"nyc3"},"droplet":{"id":2},"ip":"192.168.0.2"}]}`)
})
floatingIPs, _, err := client.FloatingIPs.List(ctx, nil)
if err != nil {
t.Errorf("FloatingIPs.List returned error: %v", err)
}
expected := []FloatingIP{
{Region: &Region{Slug: "nyc3"}, Droplet: &Droplet{ID: 1}, IP: "192.168.0.1"},
{Region: &Region{Slug: "nyc3"}, Droplet: &Droplet{ID: 2}, IP: "192.168.0.2"},
}
if !reflect.DeepEqual(floatingIPs, expected) {
t.Errorf("FloatingIPs.List returned %+v, expected %+v", floatingIPs, expected)
}
}
func TestFloatingIPs_ListFloatingIPsMultiplePages(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v2/floating_ips", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{"floating_ips": [{"region":{"slug":"nyc3"},"droplet":{"id":1},"ip":"192.168.0.1"},{"region":{"slug":"nyc3"},"droplet":{"id":2},"ip":"192.168.0.2"}], "links":{"pages":{"next":"http://example.com/v2/floating_ips/?page=2"}}}`)
})
_, resp, err := client.FloatingIPs.List(ctx, nil)
if err != nil {
t.Fatal(err)
}
checkCurrentPage(t, resp, 1)
}
func TestFloatingIPs_RetrievePageByNumber(t *testing.T) {
setup()
defer teardown()
jBlob := `
{
"floating_ips": [{"region":{"slug":"nyc3"},"droplet":{"id":1},"ip":"192.168.0.1"},{"region":{"slug":"nyc3"},"droplet":{"id":2},"ip":"192.168.0.2"}],
"links":{
"pages":{
"next":"http://example.com/v2/floating_ips/?page=3",
"prev":"http://example.com/v2/floating_ips/?page=1",
"last":"http://example.com/v2/floating_ips/?page=3",
"first":"http://example.com/v2/floating_ips/?page=1"
}
}
}`
mux.HandleFunc("/v2/floating_ips", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, jBlob)
})
opt := &ListOptions{Page: 2}
_, resp, err := client.FloatingIPs.List(ctx, opt)
if err != nil {
t.Fatal(err)
}
checkCurrentPage(t, resp, 2)
}
func TestFloatingIPs_Get(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v2/floating_ips/192.168.0.1", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{"floating_ip":{"region":{"slug":"nyc3"},"droplet":{"id":1},"ip":"192.168.0.1"}}`)
})
floatingIP, _, err := client.FloatingIPs.Get(ctx, "192.168.0.1")
if err != nil {
t.Errorf("domain.Get returned error: %v", err)
}
expected := &FloatingIP{Region: &Region{Slug: "nyc3"}, Droplet: &Droplet{ID: 1}, IP: "192.168.0.1"}
if !reflect.DeepEqual(floatingIP, expected) {
t.Errorf("FloatingIPs.Get returned %+v, expected %+v", floatingIP, expected)
}
}
func TestFloatingIPs_Create(t *testing.T) {
setup()
defer teardown()
createRequest := &FloatingIPCreateRequest{
Region: "nyc3",
DropletID: 1,
}
mux.HandleFunc("/v2/floating_ips", func(w http.ResponseWriter, r *http.Request) {
v := new(FloatingIPCreateRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatal(err)
}
testMethod(t, r, "POST")
if !reflect.DeepEqual(v, createRequest) {
t.Errorf("Request body = %+v, expected %+v", v, createRequest)
}
fmt.Fprint(w, `{"floating_ip":{"region":{"slug":"nyc3"},"droplet":{"id":1},"ip":"192.168.0.1"}}`)
})
floatingIP, _, err := client.FloatingIPs.Create(ctx, createRequest)
if err != nil {
t.Errorf("FloatingIPs.Create returned error: %v", err)
}
expected := &FloatingIP{Region: &Region{Slug: "nyc3"}, Droplet: &Droplet{ID: 1}, IP: "192.168.0.1"}
if !reflect.DeepEqual(floatingIP, expected) {
t.Errorf("FloatingIPs.Create returned %+v, expected %+v", floatingIP, expected)
}
}
func TestFloatingIPs_Destroy(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v2/floating_ips/192.168.0.1", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "DELETE")
})
_, err := client.FloatingIPs.Delete(ctx, "192.168.0.1")
if err != nil {
t.Errorf("FloatingIPs.Delete returned error: %v", err)
}
}

View File

@ -2,6 +2,7 @@ package godo
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
@ -17,14 +18,14 @@ import (
)
const (
libraryVersion = "0.1.0"
libraryVersion = "1.0.0"
defaultBaseURL = "https://api.digitalocean.com/"
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.
@ -43,16 +44,24 @@ type Client struct {
Rate Rate
// Services used for communicating with the API
Account AccountService
Actions ActionsService
Domains DomainsService
Droplets DropletsService
DropletActions DropletActionsService
Images ImagesService
ImageActions ImageActionsService
Keys KeysService
Regions RegionsService
Sizes SizesService
Account AccountService
Actions ActionsService
Domains DomainsService
Droplets DropletsService
DropletActions DropletActionsService
Images ImagesService
ImageActions ImageActionsService
Keys KeysService
Regions RegionsService
Sizes SizesService
FloatingIPs FloatingIPsService
FloatingIPActions FloatingIPActionsService
Snapshots SnapshotsService
Storage StorageService
StorageActions StorageActionsService
Tags TagsService
LoadBalancers LoadBalancersService
Certificates CertificatesService
// Optional function called after every successful request made to the DO APIs
onRequestCompleted RequestCompletionCallback
@ -91,7 +100,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.
@ -102,7 +114,7 @@ type Rate struct {
// The number of remaining requests the client can make this hour.
Remaining int `json:"remaining"`
// The time at w\hic the current rate limit will reset.
// The time at which the current rate limit will reset.
Reset Timestamp `json:"reset"`
}
@ -147,19 +159,63 @@ func NewClient(httpClient *http.Client) *Client {
c.Domains = &DomainsServiceOp{client: c}
c.Droplets = &DropletsServiceOp{client: c}
c.DropletActions = &DropletActionsServiceOp{client: c}
c.FloatingIPs = &FloatingIPsServiceOp{client: c}
c.FloatingIPActions = &FloatingIPActionsServiceOp{client: c}
c.Images = &ImagesServiceOp{client: c}
c.ImageActions = &ImageActionsServiceOp{client: c}
c.Keys = &KeysServiceOp{client: c}
c.Regions = &RegionsServiceOp{client: c}
c.Snapshots = &SnapshotsServiceOp{client: c}
c.Sizes = &SizesServiceOp{client: c}
c.Storage = &StorageServiceOp{client: c}
c.StorageActions = &StorageActionsServiceOp{client: c}
c.Tags = &TagsServiceOp{client: c}
c.LoadBalancers = &LoadBalancersServiceOp{client: c}
c.Certificates = &CertificatesServiceOp{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.
func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) {
func (c *Client) NewRequest(ctx context.Context, method, urlStr string, body interface{}) (*http.Request, error) {
rel, err := url.Parse(urlStr)
if err != nil {
return nil, err
@ -169,7 +225,7 @@ func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Requ
buf := new(bytes.Buffer)
if body != nil {
err := json.NewEncoder(buf).Encode(body)
err = json.NewEncoder(buf).Encode(body)
if err != nil {
return nil, err
}
@ -180,9 +236,10 @@ func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Requ
return nil, err
}
req = req.WithContext(ctx)
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
}
@ -261,12 +318,12 @@ func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) {
if v != nil {
if w, ok := v.(io.Writer); ok {
_, err := io.Copy(w, resp.Body)
_, err = io.Copy(w, resp.Body)
if err != nil {
return nil, err
}
} else {
err := json.NewDecoder(resp.Body).Decode(v)
err = json.NewDecoder(resp.Body).Decode(v)
if err != nil {
return nil, err
}
@ -276,6 +333,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)
}

View File

@ -1,7 +1,7 @@
package godo
import (
"encoding/json"
"context"
"fmt"
"io/ioutil"
"net/http"
@ -17,6 +17,8 @@ import (
var (
mux *http.ServeMux
ctx = context.TODO()
client *Client
server *httptest.Server
@ -68,17 +70,65 @@ func testURLParseError(t *testing.T, err error) {
}
}
func TestNewClient(t *testing.T) {
c := NewClient(nil)
if c.BaseURL.String() != defaultBaseURL {
t.Errorf("NewClient BaseURL = %v, expected %v", c.BaseURL.String(), defaultBaseURL)
func testClientServices(t *testing.T, c *Client) {
services := []string{
"Account",
"Actions",
"Domains",
"Droplets",
"DropletActions",
"Images",
"ImageActions",
"Keys",
"Regions",
"Sizes",
"FloatingIPs",
"FloatingIPActions",
"Tags",
}
cp := reflect.ValueOf(c)
cv := reflect.Indirect(cp)
for _, s := range services {
if cv.FieldByName(s).IsNil() {
t.Errorf("c.%s shouldn't be nil", s)
}
}
}
func testClientDefaultBaseURL(t *testing.T, c *Client) {
if c.BaseURL == nil || c.BaseURL.String() != defaultBaseURL {
t.Errorf("NewClient BaseURL = %v, expected %v", c.BaseURL, defaultBaseURL)
}
}
func testClientDefaultUserAgent(t *testing.T, c *Client) {
if c.UserAgent != userAgent {
t.Errorf("NewClick UserAgent = %v, expected %v", c.UserAgent, userAgent)
}
}
func testClientDefaults(t *testing.T, c *Client) {
testClientDefaultBaseURL(t, c)
testClientDefaultUserAgent(t, c)
testClientServices(t, c)
}
func TestNewClient(t *testing.T) {
c := NewClient(nil)
testClientDefaults(t, c)
}
func TestNew(t *testing.T) {
c, err := New(nil)
if err != nil {
t.Fatalf("New(): %v", err)
}
testClientDefaults(t, c)
}
func TestNewRequest(t *testing.T) {
c := NewClient(nil)
@ -86,8 +136,8 @@ func TestNewRequest(t *testing.T) {
inBody, outBody := &DropletCreateRequest{Name: "l"},
`{"name":"l","region":"","size":"","image":0,`+
`"ssh_keys":null,"backups":false,"ipv6":false,`+
`"private_networking":false}`+"\n"
req, _ := c.NewRequest("GET", inURL, inBody)
`"private_networking":false,"monitoring":false,"tags":null}`+"\n"
req, _ := c.NewRequest(ctx, "GET", inURL, inBody)
// test relative URL was expanded
if req.URL.String() != outURL {
@ -114,8 +164,8 @@ func TestNewRequest_withUserData(t *testing.T) {
inBody, outBody := &DropletCreateRequest{Name: "l", UserData: "u"},
`{"name":"l","region":"","size":"","image":0,`+
`"ssh_keys":null,"backups":false,"ipv6":false,`+
`"private_networking":false,"user_data":"u"}`+"\n"
req, _ := c.NewRequest("GET", inURL, inBody)
`"private_networking":false,"monitoring":false,"user_data":"u","tags":null}`+"\n"
req, _ := c.NewRequest(ctx, "GET", inURL, inBody)
// test relative URL was expanded
if req.URL.String() != outURL {
@ -135,28 +185,28 @@ func TestNewRequest_withUserData(t *testing.T) {
}
}
func TestNewRequest_invalidJSON(t *testing.T) {
c := NewClient(nil)
type T struct {
A map[int]interface{}
}
_, err := c.NewRequest("GET", "/", &T{})
if err == nil {
t.Error("Expected error to be returned.")
}
if err, ok := err.(*json.UnsupportedTypeError); !ok {
t.Errorf("Expected a JSON error; got %#v.", err)
}
}
func TestNewRequest_badURL(t *testing.T) {
c := NewClient(nil)
_, err := c.NewRequest("GET", ":", nil)
_, err := c.NewRequest(ctx, "GET", ":", nil)
testURLParseError(t, err)
}
func TestNewRequest_withCustomUserAgent(t *testing.T) {
ua := "testing"
c, err := New(nil, SetUserAgent(ua))
if err != nil {
t.Fatalf("New() unexpected error: %v", err)
}
req, _ := c.NewRequest(ctx, "GET", "/foo", nil)
expected := fmt.Sprintf("%s+%s", ua, userAgent)
if got := req.Header.Get("User-Agent"); got != expected {
t.Errorf("New() UserAgent = %s; expected %s", got, expected)
}
}
func TestDo(t *testing.T) {
setup()
defer teardown()
@ -172,7 +222,7 @@ func TestDo(t *testing.T) {
fmt.Fprint(w, `{"A":"a"}`)
})
req, _ := client.NewRequest("GET", "/", nil)
req, _ := client.NewRequest(ctx, "GET", "/", nil)
body := new(foo)
_, err := client.Do(req, body)
if err != nil {
@ -193,7 +243,7 @@ func TestDo_httpError(t *testing.T) {
http.Error(w, "Bad Request", 400)
})
req, _ := client.NewRequest("GET", "/", nil)
req, _ := client.NewRequest(ctx, "GET", "/", nil)
_, err := client.Do(req, nil)
if err == nil {
@ -211,7 +261,7 @@ func TestDo_redirectLoop(t *testing.T) {
http.Redirect(w, r, "/", http.StatusFound)
})
req, _ := client.NewRequest("GET", "/", nil)
req, _ := client.NewRequest(ctx, "GET", "/", nil)
_, err := client.Do(req, nil)
if err == nil {
@ -296,7 +346,7 @@ func TestDo_rateLimit(t *testing.T) {
t.Errorf("Client rate reset not initialized to zero value")
}
req, _ := client.NewRequest("GET", "/", nil)
req, _ := client.NewRequest(ctx, "GET", "/", nil)
_, err := client.Do(req, nil)
if err != nil {
t.Fatalf("Do(): %v", err)
@ -327,7 +377,7 @@ func TestDo_rateLimit_errorResponse(t *testing.T) {
var expected int
req, _ := client.NewRequest("GET", "/", nil)
req, _ := client.NewRequest(ctx, "GET", "/", nil)
_, _ = client.Do(req, nil)
if expected = 60; client.Rate.Limit != expected {
@ -369,7 +419,7 @@ func TestDo_completion_callback(t *testing.T) {
fmt.Fprint(w, `{"A":"a"}`)
})
req, _ := client.NewRequest("GET", "/", nil)
req, _ := client.NewRequest(ctx, "GET", "/", nil)
body := new(foo)
var completedReq *http.Request
var completedResp string
@ -453,3 +503,37 @@ func TestAddOptions(t *testing.T) {
}
}
}
func TestCustomUserAgent(t *testing.T) {
c, err := New(nil, SetUserAgent("testing"))
if err != nil {
t.Fatalf("New() unexpected error: %v", err)
}
expected := fmt.Sprintf("%s+%s", "testing", userAgent)
if got := c.UserAgent; got != expected {
t.Errorf("New() UserAgent = %s; expected %s", got, expected)
}
}
func TestCustomBaseURL(t *testing.T) {
baseURL := "http://localhost/foo"
c, err := New(nil, SetBaseURL(baseURL))
if err != nil {
t.Fatalf("New() unexpected error: %v", err)
}
expected := baseURL
if got := c.BaseURL.String(); got != expected {
t.Errorf("New() BaseURL = %s; expected %s", got, expected)
}
}
func TestCustomBaseURL_badURL(t *testing.T) {
baseURL := ":"
_, err := New(nil, SetBaseURL(baseURL))
testURLParseError(t, err)
}

View File

@ -1,13 +1,17 @@
package godo
import "fmt"
import (
"context"
"fmt"
)
// ImageActionsService is an interface for interfacing with the image actions
// endpoints of the DigitalOcean API
// See: https://developers.digitalocean.com/documentation/v2#image-actions
type ImageActionsService interface {
Get(int, int) (*Action, *Response, error)
Transfer(int, *ActionRequest) (*Action, *Response, error)
Get(context.Context, int, int) (*Action, *Response, error)
Transfer(context.Context, int, *ActionRequest) (*Action, *Response, error)
Convert(context.Context, int) (*Action, *Response, error)
}
// ImageActionsServiceOp handles communition with the image action related methods of the
@ -19,7 +23,7 @@ type ImageActionsServiceOp struct {
var _ ImageActionsService = &ImageActionsServiceOp{}
// Transfer an image
func (i *ImageActionsServiceOp) Transfer(imageID int, transferRequest *ActionRequest) (*Action, *Response, error) {
func (i *ImageActionsServiceOp) Transfer(ctx context.Context, imageID int, transferRequest *ActionRequest) (*Action, *Response, error) {
if imageID < 1 {
return nil, nil, NewArgError("imageID", "cannot be less than 1")
}
@ -30,7 +34,7 @@ func (i *ImageActionsServiceOp) Transfer(imageID int, transferRequest *ActionReq
path := fmt.Sprintf("v2/images/%d/actions", imageID)
req, err := i.client.NewRequest("POST", path, transferRequest)
req, err := i.client.NewRequest(ctx, "POST", path, transferRequest)
if err != nil {
return nil, nil, err
}
@ -41,11 +45,37 @@ func (i *ImageActionsServiceOp) Transfer(imageID int, transferRequest *ActionReq
return nil, resp, err
}
return &root.Event, resp, err
return root.Event, resp, err
}
// Convert an image to a snapshot
func (i *ImageActionsServiceOp) Convert(ctx context.Context, imageID int) (*Action, *Response, error) {
if imageID < 1 {
return nil, nil, NewArgError("imageID", "cannont be less than 1")
}
path := fmt.Sprintf("v2/images/%d/actions", imageID)
convertRequest := &ActionRequest{
"type": "convert",
}
req, err := i.client.NewRequest(ctx, "POST", path, convertRequest)
if err != nil {
return nil, nil, err
}
root := new(actionRoot)
resp, err := i.client.Do(req, root)
if err != nil {
return nil, resp, err
}
return root.Event, resp, err
}
// Get an action for a particular image by id.
func (i *ImageActionsServiceOp) Get(imageID, actionID int) (*Action, *Response, error) {
func (i *ImageActionsServiceOp) Get(ctx context.Context, imageID, actionID int) (*Action, *Response, error) {
if imageID < 1 {
return nil, nil, NewArgError("imageID", "cannot be less than 1")
}
@ -56,7 +86,7 @@ func (i *ImageActionsServiceOp) Get(imageID, actionID int) (*Action, *Response,
path := fmt.Sprintf("v2/images/%d/actions/%d", imageID, actionID)
req, err := i.client.NewRequest("GET", path, nil)
req, err := i.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
@ -67,5 +97,5 @@ func (i *ImageActionsServiceOp) Get(imageID, actionID int) (*Action, *Response,
return nil, resp, err
}
return &root.Event, resp, err
return root.Event, resp, err
}

View File

@ -30,7 +30,42 @@ func TestImageActions_Transfer(t *testing.T) {
})
transfer, _, err := client.ImageActions.Transfer(12345, transferRequest)
transfer, _, err := client.ImageActions.Transfer(ctx, 12345, transferRequest)
if err != nil {
t.Errorf("ImageActions.Transfer returned error: %v", err)
}
expected := &Action{Status: "in-progress"}
if !reflect.DeepEqual(transfer, expected) {
t.Errorf("ImageActions.Transfer returned %+v, expected %+v", transfer, expected)
}
}
func TestImageActions_Convert(t *testing.T) {
setup()
defer teardown()
convertRequest := &ActionRequest{
"type": "convert",
}
mux.HandleFunc("/v2/images/12345/actions", func(w http.ResponseWriter, r *http.Request) {
v := new(ActionRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatalf("decode json: %v", err)
}
testMethod(t, r, "POST")
if !reflect.DeepEqual(v, convertRequest) {
t.Errorf("Request body = %+v, expected %+v", v, convertRequest)
}
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
transfer, _, err := client.ImageActions.Convert(ctx, 12345)
if err != nil {
t.Errorf("ImageActions.Transfer returned error: %v", err)
}
@ -50,7 +85,7 @@ func TestImageActions_Get(t *testing.T) {
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
action, _, err := client.ImageActions.Get(123, 456)
action, _, err := client.ImageActions.Get(ctx, 123, 456)
if err != nil {
t.Errorf("ImageActions.Get returned error: %v", err)
}

View File

@ -1,6 +1,9 @@
package godo
import "fmt"
import (
"context"
"fmt"
)
const imageBasePath = "v2/images"
@ -8,14 +11,14 @@ const imageBasePath = "v2/images"
// endpoints of the DigitalOcean API
// See: https://developers.digitalocean.com/documentation/v2#images
type ImagesService interface {
List(*ListOptions) ([]Image, *Response, error)
ListDistribution(opt *ListOptions) ([]Image, *Response, error)
ListApplication(opt *ListOptions) ([]Image, *Response, error)
ListUser(opt *ListOptions) ([]Image, *Response, error)
GetByID(int) (*Image, *Response, error)
GetBySlug(string) (*Image, *Response, error)
Update(int, *ImageUpdateRequest) (*Image, *Response, error)
Delete(int) (*Response, error)
List(context.Context, *ListOptions) ([]Image, *Response, error)
ListDistribution(ctx context.Context, opt *ListOptions) ([]Image, *Response, error)
ListApplication(ctx context.Context, opt *ListOptions) ([]Image, *Response, error)
ListUser(ctx context.Context, opt *ListOptions) ([]Image, *Response, error)
GetByID(context.Context, int) (*Image, *Response, error)
GetBySlug(context.Context, string) (*Image, *Response, error)
Update(context.Context, int, *ImageUpdateRequest) (*Image, *Response, error)
Delete(context.Context, int) (*Response, error)
}
// ImagesServiceOp handles communication with the image related methods of the
@ -45,7 +48,7 @@ type ImageUpdateRequest struct {
}
type imageRoot struct {
Image Image
Image *Image
}
type imagesRoot struct {
@ -63,48 +66,48 @@ func (i Image) String() string {
}
// List lists all the images available.
func (s *ImagesServiceOp) List(opt *ListOptions) ([]Image, *Response, error) {
return s.list(opt, nil)
func (s *ImagesServiceOp) List(ctx context.Context, opt *ListOptions) ([]Image, *Response, error) {
return s.list(ctx, opt, nil)
}
// ListDistribution lists all the distribution images.
func (s *ImagesServiceOp) ListDistribution(opt *ListOptions) ([]Image, *Response, error) {
func (s *ImagesServiceOp) ListDistribution(ctx context.Context, opt *ListOptions) ([]Image, *Response, error) {
listOpt := listImageOptions{Type: "distribution"}
return s.list(opt, &listOpt)
return s.list(ctx, opt, &listOpt)
}
// ListApplication lists all the application images.
func (s *ImagesServiceOp) ListApplication(opt *ListOptions) ([]Image, *Response, error) {
func (s *ImagesServiceOp) ListApplication(ctx context.Context, opt *ListOptions) ([]Image, *Response, error) {
listOpt := listImageOptions{Type: "application"}
return s.list(opt, &listOpt)
return s.list(ctx, opt, &listOpt)
}
// ListUser lists all the user images.
func (s *ImagesServiceOp) ListUser(opt *ListOptions) ([]Image, *Response, error) {
func (s *ImagesServiceOp) ListUser(ctx context.Context, opt *ListOptions) ([]Image, *Response, error) {
listOpt := listImageOptions{Private: true}
return s.list(opt, &listOpt)
return s.list(ctx, opt, &listOpt)
}
// GetByID retrieves an image by id.
func (s *ImagesServiceOp) GetByID(imageID int) (*Image, *Response, error) {
func (s *ImagesServiceOp) GetByID(ctx context.Context, imageID int) (*Image, *Response, error) {
if imageID < 1 {
return nil, nil, NewArgError("imageID", "cannot be less than 1")
}
return s.get(interface{}(imageID))
return s.get(ctx, interface{}(imageID))
}
// GetBySlug retrieves an image by slug.
func (s *ImagesServiceOp) GetBySlug(slug string) (*Image, *Response, error) {
func (s *ImagesServiceOp) GetBySlug(ctx context.Context, slug string) (*Image, *Response, error) {
if len(slug) < 1 {
return nil, nil, NewArgError("slug", "cannot be blank")
}
return s.get(interface{}(slug))
return s.get(ctx, interface{}(slug))
}
// Update an image name.
func (s *ImagesServiceOp) Update(imageID int, updateRequest *ImageUpdateRequest) (*Image, *Response, error) {
func (s *ImagesServiceOp) Update(ctx context.Context, imageID int, updateRequest *ImageUpdateRequest) (*Image, *Response, error) {
if imageID < 1 {
return nil, nil, NewArgError("imageID", "cannot be less than 1")
}
@ -114,7 +117,7 @@ func (s *ImagesServiceOp) Update(imageID int, updateRequest *ImageUpdateRequest)
}
path := fmt.Sprintf("%s/%d", imageBasePath, imageID)
req, err := s.client.NewRequest("PUT", path, updateRequest)
req, err := s.client.NewRequest(ctx, "PUT", path, updateRequest)
if err != nil {
return nil, nil, err
}
@ -125,18 +128,18 @@ func (s *ImagesServiceOp) Update(imageID int, updateRequest *ImageUpdateRequest)
return nil, resp, err
}
return &root.Image, resp, err
return root.Image, resp, err
}
// Delete an image.
func (s *ImagesServiceOp) Delete(imageID int) (*Response, error) {
func (s *ImagesServiceOp) Delete(ctx context.Context, imageID int) (*Response, error) {
if imageID < 1 {
return nil, NewArgError("imageID", "cannot be less than 1")
}
path := fmt.Sprintf("%s/%d", imageBasePath, imageID)
req, err := s.client.NewRequest("DELETE", path, nil)
req, err := s.client.NewRequest(ctx, "DELETE", path, nil)
if err != nil {
return nil, err
}
@ -147,10 +150,10 @@ func (s *ImagesServiceOp) Delete(imageID int) (*Response, error) {
}
// Helper method for getting an individual image
func (s *ImagesServiceOp) get(ID interface{}) (*Image, *Response, error) {
func (s *ImagesServiceOp) get(ctx context.Context, ID interface{}) (*Image, *Response, error) {
path := fmt.Sprintf("%s/%v", imageBasePath, ID)
req, err := s.client.NewRequest("GET", path, nil)
req, err := s.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
@ -161,11 +164,11 @@ func (s *ImagesServiceOp) get(ID interface{}) (*Image, *Response, error) {
return nil, resp, err
}
return &root.Image, resp, err
return root.Image, resp, err
}
// Helper method for listing images
func (s *ImagesServiceOp) list(opt *ListOptions, listOpt *listImageOptions) ([]Image, *Response, error) {
func (s *ImagesServiceOp) list(ctx context.Context, opt *ListOptions, listOpt *listImageOptions) ([]Image, *Response, error) {
path := imageBasePath
path, err := addOptions(path, opt)
if err != nil {
@ -176,7 +179,7 @@ func (s *ImagesServiceOp) list(opt *ListOptions, listOpt *listImageOptions) ([]I
return nil, nil, err
}
req, err := s.client.NewRequest("GET", path, nil)
req, err := s.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}

View File

@ -17,7 +17,7 @@ func TestImages_List(t *testing.T) {
fmt.Fprint(w, `{"images":[{"id":1},{"id":2}]}`)
})
images, _, err := client.Images.List(nil)
images, _, err := client.Images.List(ctx, nil)
if err != nil {
t.Errorf("Images.List returned error: %v", err)
}
@ -42,7 +42,7 @@ func TestImages_ListDistribution(t *testing.T) {
fmt.Fprint(w, `{"images":[{"id":1},{"id":2}]}`)
})
images, _, err := client.Images.ListDistribution(nil)
images, _, err := client.Images.ListDistribution(ctx, nil)
if err != nil {
t.Errorf("Images.ListDistribution returned error: %v", err)
}
@ -67,7 +67,7 @@ func TestImages_ListApplication(t *testing.T) {
fmt.Fprint(w, `{"images":[{"id":1},{"id":2}]}`)
})
images, _, err := client.Images.ListApplication(nil)
images, _, err := client.Images.ListApplication(ctx, nil)
if err != nil {
t.Errorf("Images.ListApplication returned error: %v", err)
}
@ -93,7 +93,7 @@ func TestImages_ListUser(t *testing.T) {
fmt.Fprint(w, `{"images":[{"id":1},{"id":2}]}`)
})
images, _, err := client.Images.ListUser(nil)
images, _, err := client.Images.ListUser(ctx, nil)
if err != nil {
t.Errorf("Images.ListUser returned error: %v", err)
}
@ -113,7 +113,7 @@ func TestImages_ListImagesMultiplePages(t *testing.T) {
fmt.Fprint(w, `{"images": [{"id":1},{"id":2}], "links":{"pages":{"next":"http://example.com/v2/images/?page=2"}}}`)
})
_, resp, err := client.Images.List(&ListOptions{Page: 2})
_, resp, err := client.Images.List(ctx, &ListOptions{Page: 2})
if err != nil {
t.Fatal(err)
}
@ -143,7 +143,7 @@ func TestImages_RetrievePageByNumber(t *testing.T) {
})
opt := &ListOptions{Page: 2}
_, resp, err := client.Images.List(opt)
_, resp, err := client.Images.List(ctx, opt)
if err != nil {
t.Fatal(err)
}
@ -160,7 +160,7 @@ func TestImages_GetImageByID(t *testing.T) {
fmt.Fprint(w, `{"image":{"id":12345}}`)
})
images, _, err := client.Images.GetByID(12345)
images, _, err := client.Images.GetByID(ctx, 12345)
if err != nil {
t.Errorf("Image.GetByID returned error: %v", err)
}
@ -180,7 +180,7 @@ func TestImages_GetImageBySlug(t *testing.T) {
fmt.Fprint(w, `{"image":{"id":12345}}`)
})
images, _, err := client.Images.GetBySlug("ubuntu")
images, _, err := client.Images.GetBySlug(ctx, "ubuntu")
if err != nil {
t.Errorf("Image.GetBySlug returned error: %v", err)
}
@ -217,7 +217,7 @@ func TestImages_Update(t *testing.T) {
fmt.Fprintf(w, `{"image":{"id":1}}`)
})
image, _, err := client.Images.Update(12345, updateRequest)
image, _, err := client.Images.Update(ctx, 12345, updateRequest)
if err != nil {
t.Errorf("Images.Update returned error: %v", err)
} else {
@ -235,7 +235,7 @@ func TestImages_Destroy(t *testing.T) {
testMethod(t, r, "DELETE")
})
_, err := client.Images.Delete(12345)
_, err := client.Images.Delete(ctx, 12345)
if err != nil {
t.Errorf("Image.Delete returned error: %v", err)
}

View File

@ -1,6 +1,9 @@
package godo
import "fmt"
import (
"context"
"fmt"
)
const keysBasePath = "v2/account/keys"
@ -8,14 +11,14 @@ const keysBasePath = "v2/account/keys"
// endpoints of the DigitalOcean API
// See: https://developers.digitalocean.com/documentation/v2#keys
type KeysService interface {
List(*ListOptions) ([]Key, *Response, error)
GetByID(int) (*Key, *Response, error)
GetByFingerprint(string) (*Key, *Response, error)
Create(*KeyCreateRequest) (*Key, *Response, error)
UpdateByID(int, *KeyUpdateRequest) (*Key, *Response, error)
UpdateByFingerprint(string, *KeyUpdateRequest) (*Key, *Response, error)
DeleteByID(int) (*Response, error)
DeleteByFingerprint(string) (*Response, error)
List(context.Context, *ListOptions) ([]Key, *Response, error)
GetByID(context.Context, int) (*Key, *Response, error)
GetByFingerprint(context.Context, string) (*Key, *Response, error)
Create(context.Context, *KeyCreateRequest) (*Key, *Response, error)
UpdateByID(context.Context, int, *KeyUpdateRequest) (*Key, *Response, error)
UpdateByFingerprint(context.Context, string, *KeyUpdateRequest) (*Key, *Response, error)
DeleteByID(context.Context, int) (*Response, error)
DeleteByFingerprint(context.Context, string) (*Response, error)
}
// KeysServiceOp handles communication with key related method of the
@ -45,7 +48,7 @@ type keysRoot struct {
}
type keyRoot struct {
SSHKey Key `json:"ssh_key"`
SSHKey *Key `json:"ssh_key"`
}
func (s Key) String() string {
@ -59,14 +62,14 @@ type KeyCreateRequest struct {
}
// List all keys
func (s *KeysServiceOp) List(opt *ListOptions) ([]Key, *Response, error) {
func (s *KeysServiceOp) List(ctx context.Context, opt *ListOptions) ([]Key, *Response, error) {
path := keysBasePath
path, err := addOptions(path, opt)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", path, nil)
req, err := s.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
@ -84,8 +87,8 @@ func (s *KeysServiceOp) List(opt *ListOptions) ([]Key, *Response, error) {
}
// Performs a get given a path
func (s *KeysServiceOp) get(path string) (*Key, *Response, error) {
req, err := s.client.NewRequest("GET", path, nil)
func (s *KeysServiceOp) get(ctx context.Context, path string) (*Key, *Response, error) {
req, err := s.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
@ -96,36 +99,36 @@ func (s *KeysServiceOp) get(path string) (*Key, *Response, error) {
return nil, resp, err
}
return &root.SSHKey, resp, err
return root.SSHKey, resp, err
}
// GetByID gets a Key by id
func (s *KeysServiceOp) GetByID(keyID int) (*Key, *Response, error) {
func (s *KeysServiceOp) GetByID(ctx context.Context, keyID int) (*Key, *Response, error) {
if keyID < 1 {
return nil, nil, NewArgError("keyID", "cannot be less than 1")
}
path := fmt.Sprintf("%s/%d", keysBasePath, keyID)
return s.get(path)
return s.get(ctx, path)
}
// GetByFingerprint gets a Key by by fingerprint
func (s *KeysServiceOp) GetByFingerprint(fingerprint string) (*Key, *Response, error) {
func (s *KeysServiceOp) GetByFingerprint(ctx context.Context, fingerprint string) (*Key, *Response, error) {
if len(fingerprint) < 1 {
return nil, nil, NewArgError("fingerprint", "cannot not be empty")
}
path := fmt.Sprintf("%s/%s", keysBasePath, fingerprint)
return s.get(path)
return s.get(ctx, path)
}
// Create a key using a KeyCreateRequest
func (s *KeysServiceOp) Create(createRequest *KeyCreateRequest) (*Key, *Response, error) {
func (s *KeysServiceOp) Create(ctx context.Context, createRequest *KeyCreateRequest) (*Key, *Response, error) {
if createRequest == nil {
return nil, nil, NewArgError("createRequest", "cannot be nil")
}
req, err := s.client.NewRequest("POST", keysBasePath, createRequest)
req, err := s.client.NewRequest(ctx, "POST", keysBasePath, createRequest)
if err != nil {
return nil, nil, err
}
@ -136,11 +139,11 @@ func (s *KeysServiceOp) Create(createRequest *KeyCreateRequest) (*Key, *Response
return nil, resp, err
}
return &root.SSHKey, resp, err
return root.SSHKey, resp, err
}
// UpdateByID updates a key name by ID.
func (s *KeysServiceOp) UpdateByID(keyID int, updateRequest *KeyUpdateRequest) (*Key, *Response, error) {
func (s *KeysServiceOp) UpdateByID(ctx context.Context, keyID int, updateRequest *KeyUpdateRequest) (*Key, *Response, error) {
if keyID < 1 {
return nil, nil, NewArgError("keyID", "cannot be less than 1")
}
@ -150,7 +153,7 @@ func (s *KeysServiceOp) UpdateByID(keyID int, updateRequest *KeyUpdateRequest) (
}
path := fmt.Sprintf("%s/%d", keysBasePath, keyID)
req, err := s.client.NewRequest("PUT", path, updateRequest)
req, err := s.client.NewRequest(ctx, "PUT", path, updateRequest)
if err != nil {
return nil, nil, err
}
@ -161,11 +164,11 @@ func (s *KeysServiceOp) UpdateByID(keyID int, updateRequest *KeyUpdateRequest) (
return nil, resp, err
}
return &root.SSHKey, resp, err
return root.SSHKey, resp, err
}
// UpdateByFingerprint updates a key name by fingerprint.
func (s *KeysServiceOp) UpdateByFingerprint(fingerprint string, updateRequest *KeyUpdateRequest) (*Key, *Response, error) {
func (s *KeysServiceOp) UpdateByFingerprint(ctx context.Context, fingerprint string, updateRequest *KeyUpdateRequest) (*Key, *Response, error) {
if len(fingerprint) < 1 {
return nil, nil, NewArgError("fingerprint", "cannot be empty")
}
@ -175,7 +178,7 @@ func (s *KeysServiceOp) UpdateByFingerprint(fingerprint string, updateRequest *K
}
path := fmt.Sprintf("%s/%s", keysBasePath, fingerprint)
req, err := s.client.NewRequest("PUT", path, updateRequest)
req, err := s.client.NewRequest(ctx, "PUT", path, updateRequest)
if err != nil {
return nil, nil, err
}
@ -186,12 +189,12 @@ func (s *KeysServiceOp) UpdateByFingerprint(fingerprint string, updateRequest *K
return nil, resp, err
}
return &root.SSHKey, resp, err
return root.SSHKey, resp, err
}
// Delete key using a path
func (s *KeysServiceOp) delete(path string) (*Response, error) {
req, err := s.client.NewRequest("DELETE", path, nil)
func (s *KeysServiceOp) delete(ctx context.Context, path string) (*Response, error) {
req, err := s.client.NewRequest(ctx, "DELETE", path, nil)
if err != nil {
return nil, err
}
@ -202,21 +205,21 @@ func (s *KeysServiceOp) delete(path string) (*Response, error) {
}
// DeleteByID deletes a key by its id
func (s *KeysServiceOp) DeleteByID(keyID int) (*Response, error) {
func (s *KeysServiceOp) DeleteByID(ctx context.Context, keyID int) (*Response, error) {
if keyID < 1 {
return nil, NewArgError("keyID", "cannot be less than 1")
}
path := fmt.Sprintf("%s/%d", keysBasePath, keyID)
return s.delete(path)
return s.delete(ctx, path)
}
// DeleteByFingerprint deletes a key by its fingerprint
func (s *KeysServiceOp) DeleteByFingerprint(fingerprint string) (*Response, error) {
func (s *KeysServiceOp) DeleteByFingerprint(ctx context.Context, fingerprint string) (*Response, error) {
if len(fingerprint) < 1 {
return nil, NewArgError("fingerprint", "cannot be empty")
}
path := fmt.Sprintf("%s/%s", keysBasePath, fingerprint)
return s.delete(path)
return s.delete(ctx, path)
}

View File

@ -17,7 +17,7 @@ func TestKeys_List(t *testing.T) {
fmt.Fprint(w, `{"ssh_keys":[{"id":1},{"id":2}]}`)
})
keys, _, err := client.Keys.List(nil)
keys, _, err := client.Keys.List(ctx, nil)
if err != nil {
t.Errorf("Keys.List returned error: %v", err)
}
@ -37,7 +37,7 @@ func TestKeys_ListKeysMultiplePages(t *testing.T) {
fmt.Fprint(w, `{"droplets": [{"id":1},{"id":2}], "links":{"pages":{"next":"http://example.com/v2/account/keys/?page=2"}}}`)
})
_, resp, err := client.Keys.List(nil)
_, resp, err := client.Keys.List(ctx, nil)
if err != nil {
t.Fatal(err)
}
@ -67,7 +67,7 @@ func TestKeys_RetrievePageByNumber(t *testing.T) {
})
opt := &ListOptions{Page: 2}
_, resp, err := client.Keys.List(opt)
_, resp, err := client.Keys.List(ctx, opt)
if err != nil {
t.Fatal(err)
}
@ -83,7 +83,7 @@ func TestKeys_GetByID(t *testing.T) {
fmt.Fprint(w, `{"ssh_key": {"id":12345}}`)
})
keys, _, err := client.Keys.GetByID(12345)
keys, _, err := client.Keys.GetByID(ctx, 12345)
if err != nil {
t.Errorf("Keys.GetByID returned error: %v", err)
}
@ -103,7 +103,7 @@ func TestKeys_GetByFingerprint(t *testing.T) {
fmt.Fprint(w, `{"ssh_key": {"fingerprint":"aa:bb:cc"}}`)
})
keys, _, err := client.Keys.GetByFingerprint("aa:bb:cc")
keys, _, err := client.Keys.GetByFingerprint(ctx, "aa:bb:cc")
if err != nil {
t.Errorf("Keys.GetByFingerprint returned error: %v", err)
}
@ -138,7 +138,7 @@ func TestKeys_Create(t *testing.T) {
fmt.Fprintf(w, `{"ssh_key":{"id":1}}`)
})
key, _, err := client.Keys.Create(createRequest)
key, _, err := client.Keys.Create(ctx, createRequest)
if err != nil {
t.Errorf("Keys.Create returned error: %v", err)
}
@ -175,7 +175,7 @@ func TestKeys_UpdateByID(t *testing.T) {
fmt.Fprintf(w, `{"ssh_key":{"id":1}}`)
})
key, _, err := client.Keys.UpdateByID(12345, updateRequest)
key, _, err := client.Keys.UpdateByID(ctx, 12345, updateRequest)
if err != nil {
t.Errorf("Keys.Update returned error: %v", err)
} else {
@ -211,7 +211,7 @@ func TestKeys_UpdateByFingerprint(t *testing.T) {
fmt.Fprintf(w, `{"ssh_key":{"id":1}}`)
})
key, _, err := client.Keys.UpdateByFingerprint("3b:16:bf:e4:8b:00:8b:b8:59:8c:a9:d3:f0:19:45:fa", updateRequest)
key, _, err := client.Keys.UpdateByFingerprint(ctx, "3b:16:bf:e4:8b:00:8b:b8:59:8c:a9:d3:f0:19:45:fa", updateRequest)
if err != nil {
t.Errorf("Keys.Update returned error: %v", err)
} else {
@ -229,7 +229,7 @@ func TestKeys_DestroyByID(t *testing.T) {
testMethod(t, r, "DELETE")
})
_, err := client.Keys.DeleteByID(12345)
_, err := client.Keys.DeleteByID(ctx, 12345)
if err != nil {
t.Errorf("Keys.Delete returned error: %v", err)
}
@ -243,7 +243,7 @@ func TestKeys_DestroyByFingerprint(t *testing.T) {
testMethod(t, r, "DELETE")
})
_, err := client.Keys.DeleteByFingerprint("aa:bb:cc")
_, err := client.Keys.DeleteByFingerprint(ctx, "aa:bb:cc")
if err != nil {
t.Errorf("Keys.Delete returned error: %v", err)
}

View File

@ -1,6 +1,7 @@
package godo
import (
"context"
"net/url"
"strconv"
)
@ -58,11 +59,7 @@ func (l *Links) IsLastPage() bool {
}
func (p *Pages) isLast() bool {
if p.Last == "" {
return true
}
return false
return p.Last == ""
}
func pageForURL(urlText string) (int, error) {
@ -81,6 +78,6 @@ func pageForURL(urlText string) (int, error) {
}
// Get a link action by id.
func (la *LinkAction) Get(client *Client) (*Action, *Response, error) {
return client.Actions.Get(la.ID)
func (la *LinkAction) Get(ctx context.Context, client *Client) (*Action, *Response, error) {
return client.Actions.Get(ctx, la.ID)
}

275
vendor/github.com/digitalocean/godo/load_balancers.go generated vendored Normal file
View File

@ -0,0 +1,275 @@
package godo
import (
"context"
"fmt"
)
const loadBalancersBasePath = "/v2/load_balancers"
const forwardingRulesPath = "forwarding_rules"
const dropletsPath = "droplets"
// LoadBalancersService is an interface for managing load balancers with the DigitalOcean API.
// See: https://developers.digitalocean.com/documentation/v2#load-balancers
type LoadBalancersService interface {
Get(context.Context, string) (*LoadBalancer, *Response, error)
List(context.Context, *ListOptions) ([]LoadBalancer, *Response, error)
Create(context.Context, *LoadBalancerRequest) (*LoadBalancer, *Response, error)
Update(ctx context.Context, lbID string, lbr *LoadBalancerRequest) (*LoadBalancer, *Response, error)
Delete(ctx context.Context, lbID string) (*Response, error)
AddDroplets(ctx context.Context, lbID string, dropletIDs ...int) (*Response, error)
RemoveDroplets(ctx context.Context, lbID string, dropletIDs ...int) (*Response, error)
AddForwardingRules(ctx context.Context, lbID string, rules ...ForwardingRule) (*Response, error)
RemoveForwardingRules(ctx context.Context, lbID string, rules ...ForwardingRule) (*Response, error)
}
// LoadBalancer represents a DigitalOcean load balancer configuration.
type LoadBalancer struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
IP string `json:"ip,omitempty"`
Algorithm string `json:"algorithm,omitempty"`
Status string `json:"status,omitempty"`
Created string `json:"created_at,omitempty"`
ForwardingRules []ForwardingRule `json:"forwarding_rules,omitempty"`
HealthCheck *HealthCheck `json:"health_check,omitempty"`
StickySessions *StickySessions `json:"sticky_sessions,omitempty"`
Region *Region `json:"region,omitempty"`
DropletIDs []int `json:"droplet_ids,omitempty"`
Tag string `json:"tag,omitempty"`
RedirectHttpToHttps bool `json:"redirect_http_to_https,omitempty"`
}
// String creates a human-readable description of a LoadBalancer.
func (l LoadBalancer) String() string {
return Stringify(l)
}
// ForwardingRule represents load balancer forwarding rules.
type ForwardingRule struct {
EntryProtocol string `json:"entry_protocol,omitempty"`
EntryPort int `json:"entry_port,omitempty"`
TargetProtocol string `json:"target_protocol,omitempty"`
TargetPort int `json:"target_port,omitempty"`
CertificateID string `json:"certificate_id,omitempty"`
TlsPassthrough bool `json:"tls_passthrough,omitempty"`
}
// String creates a human-readable description of a ForwardingRule.
func (f ForwardingRule) String() string {
return Stringify(f)
}
// HealthCheck represents optional load balancer health check rules.
type HealthCheck struct {
Protocol string `json:"protocol,omitempty"`
Port int `json:"port,omitempty"`
Path string `json:"path,omitempty"`
CheckIntervalSeconds int `json:"check_interval_seconds,omitempty"`
ResponseTimeoutSeconds int `json:"response_timeout_seconds,omitempty"`
HealthyThreshold int `json:"healthy_threshold,omitempty"`
UnhealthyThreshold int `json:"unhealthy_threshold,omitempty"`
}
// String creates a human-readable description of a HealthCheck.
func (h HealthCheck) String() string {
return Stringify(h)
}
// StickySessions represents optional load balancer session affinity rules.
type StickySessions struct {
Type string `json:"type,omitempty"`
CookieName string `json:"cookie_name,omitempty"`
CookieTtlSeconds int `json:"cookie_ttl_seconds,omitempty"`
}
// String creates a human-readable description of a StickySessions instance.
func (s StickySessions) String() string {
return Stringify(s)
}
// LoadBalancerRequest represents the configuration to be applied to an existing or a new load balancer.
type LoadBalancerRequest struct {
Name string `json:"name,omitempty"`
Algorithm string `json:"algorithm,omitempty"`
Region string `json:"region,omitempty"`
ForwardingRules []ForwardingRule `json:"forwarding_rules,omitempty"`
HealthCheck *HealthCheck `json:"health_check,omitempty"`
StickySessions *StickySessions `json:"sticky_sessions,omitempty"`
DropletIDs []int `json:"droplet_ids,omitempty"`
Tag string `json:"tag,omitempty"`
RedirectHttpToHttps bool `json:"redirect_http_to_https,omitempty"`
}
// String creates a human-readable description of a LoadBalancerRequest.
func (l LoadBalancerRequest) String() string {
return Stringify(l)
}
type forwardingRulesRequest struct {
Rules []ForwardingRule `json:"forwarding_rules,omitempty"`
}
func (l forwardingRulesRequest) String() string {
return Stringify(l)
}
type dropletIDsRequest struct {
IDs []int `json:"droplet_ids,omitempty"`
}
func (l dropletIDsRequest) String() string {
return Stringify(l)
}
type loadBalancersRoot struct {
LoadBalancers []LoadBalancer `json:"load_balancers"`
Links *Links `json:"links"`
}
type loadBalancerRoot struct {
LoadBalancer *LoadBalancer `json:"load_balancer"`
}
// LoadBalancersServiceOp handles communication with load balancer-related methods of the DigitalOcean API.
type LoadBalancersServiceOp struct {
client *Client
}
var _ LoadBalancersService = &LoadBalancersServiceOp{}
// Get an existing load balancer by its identifier.
func (l *LoadBalancersServiceOp) Get(ctx context.Context, lbID string) (*LoadBalancer, *Response, error) {
path := fmt.Sprintf("%s/%s", loadBalancersBasePath, lbID)
req, err := l.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
root := new(loadBalancerRoot)
resp, err := l.client.Do(req, root)
if err != nil {
return nil, resp, err
}
return root.LoadBalancer, resp, err
}
// List load balancers, with optional pagination.
func (l *LoadBalancersServiceOp) List(ctx context.Context, opt *ListOptions) ([]LoadBalancer, *Response, error) {
path, err := addOptions(loadBalancersBasePath, opt)
if err != nil {
return nil, nil, err
}
req, err := l.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
root := new(loadBalancersRoot)
resp, err := l.client.Do(req, root)
if err != nil {
return nil, resp, err
}
if l := root.Links; l != nil {
resp.Links = l
}
return root.LoadBalancers, resp, err
}
// Create a new load balancer with a given configuration.
func (l *LoadBalancersServiceOp) Create(ctx context.Context, lbr *LoadBalancerRequest) (*LoadBalancer, *Response, error) {
req, err := l.client.NewRequest(ctx, "POST", loadBalancersBasePath, lbr)
if err != nil {
return nil, nil, err
}
root := new(loadBalancerRoot)
resp, err := l.client.Do(req, root)
if err != nil {
return nil, resp, err
}
return root.LoadBalancer, resp, err
}
// Update an existing load balancer with new configuration.
func (l *LoadBalancersServiceOp) Update(ctx context.Context, lbID string, lbr *LoadBalancerRequest) (*LoadBalancer, *Response, error) {
path := fmt.Sprintf("%s/%s", loadBalancersBasePath, lbID)
req, err := l.client.NewRequest(ctx, "PUT", path, lbr)
if err != nil {
return nil, nil, err
}
root := new(loadBalancerRoot)
resp, err := l.client.Do(req, root)
if err != nil {
return nil, resp, err
}
return root.LoadBalancer, resp, err
}
// Delete a load balancer by its identifier.
func (l *LoadBalancersServiceOp) Delete(ctx context.Context, ldID string) (*Response, error) {
path := fmt.Sprintf("%s/%s", loadBalancersBasePath, ldID)
req, err := l.client.NewRequest(ctx, "DELETE", path, nil)
if err != nil {
return nil, err
}
return l.client.Do(req, nil)
}
// AddDroplets adds droplets to a load balancer.
func (l *LoadBalancersServiceOp) AddDroplets(ctx context.Context, lbID string, dropletIDs ...int) (*Response, error) {
path := fmt.Sprintf("%s/%s/%s", loadBalancersBasePath, lbID, dropletsPath)
req, err := l.client.NewRequest(ctx, "POST", path, &dropletIDsRequest{IDs: dropletIDs})
if err != nil {
return nil, err
}
return l.client.Do(req, nil)
}
// RemoveDroplets removes droplets from a load balancer.
func (l *LoadBalancersServiceOp) RemoveDroplets(ctx context.Context, lbID string, dropletIDs ...int) (*Response, error) {
path := fmt.Sprintf("%s/%s/%s", loadBalancersBasePath, lbID, dropletsPath)
req, err := l.client.NewRequest(ctx, "DELETE", path, &dropletIDsRequest{IDs: dropletIDs})
if err != nil {
return nil, err
}
return l.client.Do(req, nil)
}
// AddForwardingRules adds forwarding rules to a load balancer.
func (l *LoadBalancersServiceOp) AddForwardingRules(ctx context.Context, lbID string, rules ...ForwardingRule) (*Response, error) {
path := fmt.Sprintf("%s/%s/%s", loadBalancersBasePath, lbID, forwardingRulesPath)
req, err := l.client.NewRequest(ctx, "POST", path, &forwardingRulesRequest{Rules: rules})
if err != nil {
return nil, err
}
return l.client.Do(req, nil)
}
// RemoveForwardingRules removes forwarding rules from a load balancer.
func (l *LoadBalancersServiceOp) RemoveForwardingRules(ctx context.Context, lbID string, rules ...ForwardingRule) (*Response, error) {
path := fmt.Sprintf("%s/%s/%s", loadBalancersBasePath, lbID, forwardingRulesPath)
req, err := l.client.NewRequest(ctx, "DELETE", path, &forwardingRulesRequest{Rules: rules})
if err != nil {
return nil, err
}
return l.client.Do(req, nil)
}

View File

@ -0,0 +1,796 @@
package godo
import (
"encoding/json"
"fmt"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
)
var lbListJSONResponse = `
{
"load_balancers":[
{
"id":"37e6be88-01ec-4ec7-9bc6-a514d4719057",
"name":"example-lb-01",
"ip":"46.214.185.203",
"algorithm":"round_robin",
"status":"active",
"created_at":"2016-12-15T14:16:36Z",
"forwarding_rules":[
{
"entry_protocol":"https",
"entry_port":443,
"target_protocol":"http",
"target_port":80,
"certificate_id":"a-b-c"
}
],
"health_check":{
"protocol":"http",
"port":80,
"path":"/index.html",
"check_interval_seconds":10,
"response_timeout_seconds":5,
"healthy_threshold":5,
"unhealthy_threshold":3
},
"sticky_sessions":{
"type":"cookies",
"cookie_name":"DO-LB",
"cookie_ttl_seconds":5
},
"region":{
"name":"New York 1",
"slug":"nyc1",
"sizes":[
"512mb",
"1gb",
"2gb",
"4gb",
"8gb",
"16gb"
],
"features":[
"private_networking",
"backups",
"ipv6",
"metadata",
"storage"
],
"available":true
},
"droplet_ids":[
2,
21
]
}
],
"links":{
"pages":{
"last":"http://localhost:3001/v2/load_balancers?page=3&per_page=1",
"next":"http://localhost:3001/v2/load_balancers?page=2&per_page=1"
}
},
"meta":{
"total":3
}
}
`
var lbCreateJSONResponse = `
{
"load_balancer":{
"id":"8268a81c-fcf5-423e-a337-bbfe95817f23",
"name":"example-lb-01",
"ip":"",
"algorithm":"round_robin",
"status":"new",
"created_at":"2016-12-15T14:19:09Z",
"forwarding_rules":[
{
"entry_protocol":"https",
"entry_port":443,
"target_protocol":"http",
"target_port":80,
"certificate_id":"a-b-c"
},
{
"entry_protocol":"https",
"entry_port":444,
"target_protocol":"https",
"target_port":443,
"tls_passthrough":true
}
],
"health_check":{
"protocol":"http",
"port":80,
"path":"/index.html",
"check_interval_seconds":10,
"response_timeout_seconds":5,
"healthy_threshold":5,
"unhealthy_threshold":3
},
"sticky_sessions":{
"type":"cookies",
"cookie_name":"DO-LB",
"cookie_ttl_seconds":5
},
"region":{
"name":"New York 1",
"slug":"nyc1",
"sizes":[
"512mb",
"1gb",
"2gb",
"4gb",
"8gb",
"16gb"
],
"features":[
"private_networking",
"backups",
"ipv6",
"metadata",
"storage"
],
"available":true
},
"droplet_ids":[
2,
21
],
"redirect_http_to_https":true
}
}
`
var lbGetJSONResponse = `
{
"load_balancer":{
"id":"37e6be88-01ec-4ec7-9bc6-a514d4719057",
"name":"example-lb-01",
"ip":"46.214.185.203",
"algorithm":"round_robin",
"status":"active",
"created_at":"2016-12-15T14:16:36Z",
"forwarding_rules":[
{
"entry_protocol":"https",
"entry_port":443,
"target_protocol":"http",
"target_port":80,
"certificate_id":"a-b-c"
}
],
"health_check":{
"protocol":"http",
"port":80,
"path":"/index.html",
"check_interval_seconds":10,
"response_timeout_seconds":5,
"healthy_threshold":5,
"unhealthy_threshold":3
},
"sticky_sessions":{
"type":"cookies",
"cookie_name":"DO-LB",
"cookie_ttl_seconds":5
},
"region":{
"name":"New York 1",
"slug":"nyc1",
"sizes":[
"512mb",
"1gb",
"2gb",
"4gb",
"8gb",
"16gb"
],
"features":[
"private_networking",
"backups",
"ipv6",
"metadata",
"storage"
],
"available":true
},
"droplet_ids":[
2,
21
]
}
}
`
var lbUpdateJSONResponse = `
{
"load_balancer":{
"id":"8268a81c-fcf5-423e-a337-bbfe95817f23",
"name":"example-lb-01",
"ip":"12.34.56.78",
"algorithm":"least_connections",
"status":"active",
"created_at":"2016-12-15T14:19:09Z",
"forwarding_rules":[
{
"entry_protocol":"http",
"entry_port":80,
"target_protocol":"http",
"target_port":80
},
{
"entry_protocol":"https",
"entry_port":443,
"target_protocol":"http",
"target_port":80,
"certificate_id":"a-b-c"
}
],
"health_check":{
"protocol":"tcp",
"port":80,
"path":"",
"check_interval_seconds":10,
"response_timeout_seconds":5,
"healthy_threshold":5,
"unhealthy_threshold":3
},
"sticky_sessions":{
"type":"none"
},
"region":{
"name":"New York 1",
"slug":"nyc1",
"sizes":[
"512mb",
"1gb",
"2gb",
"4gb",
"8gb",
"16gb"
],
"features":[
"private_networking",
"backups",
"ipv6",
"metadata",
"storage"
],
"available":true
},
"droplet_ids":[
2,
21
]
}
}
`
func TestLoadBlanacers_Get(t *testing.T) {
setup()
defer teardown()
path := "/v2/load_balancers"
loadBalancerId := "37e6be88-01ec-4ec7-9bc6-a514d4719057"
path = fmt.Sprintf("%s/%s", path, loadBalancerId)
mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, lbGetJSONResponse)
})
loadBalancer, _, err := client.LoadBalancers.Get(ctx, loadBalancerId)
if err != nil {
t.Errorf("LoadBalancers.Get returned error: %v", err)
}
expected := &LoadBalancer{
ID: "37e6be88-01ec-4ec7-9bc6-a514d4719057",
Name: "example-lb-01",
IP: "46.214.185.203",
Algorithm: "round_robin",
Status: "active",
Created: "2016-12-15T14:16:36Z",
ForwardingRules: []ForwardingRule{
{
EntryProtocol: "https",
EntryPort: 443,
TargetProtocol: "http",
TargetPort: 80,
CertificateID: "a-b-c",
TlsPassthrough: false,
},
},
HealthCheck: &HealthCheck{
Protocol: "http",
Port: 80,
Path: "/index.html",
CheckIntervalSeconds: 10,
ResponseTimeoutSeconds: 5,
HealthyThreshold: 5,
UnhealthyThreshold: 3,
},
StickySessions: &StickySessions{
Type: "cookies",
CookieName: "DO-LB",
CookieTtlSeconds: 5,
},
Region: &Region{
Slug: "nyc1",
Name: "New York 1",
Sizes: []string{"512mb", "1gb", "2gb", "4gb", "8gb", "16gb"},
Available: true,
Features: []string{"private_networking", "backups", "ipv6", "metadata", "storage"},
},
DropletIDs: []int{2, 21},
}
assert.Equal(t, expected, loadBalancer)
}
func TestLoadBlanacers_Create(t *testing.T) {
setup()
defer teardown()
createRequest := &LoadBalancerRequest{
Name: "example-lb-01",
Algorithm: "round_robin",
Region: "nyc1",
ForwardingRules: []ForwardingRule{
{
EntryProtocol: "https",
EntryPort: 443,
TargetProtocol: "http",
TargetPort: 80,
CertificateID: "a-b-c",
},
},
HealthCheck: &HealthCheck{
Protocol: "http",
Port: 80,
Path: "/index.html",
CheckIntervalSeconds: 10,
ResponseTimeoutSeconds: 5,
UnhealthyThreshold: 3,
HealthyThreshold: 5,
},
StickySessions: &StickySessions{
Type: "cookies",
CookieName: "DO-LB",
CookieTtlSeconds: 5,
},
Tag: "my-tag",
DropletIDs: []int{2, 21},
RedirectHttpToHttps: true,
}
path := "/v2/load_balancers"
mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
v := new(LoadBalancerRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatal(err)
}
testMethod(t, r, "POST")
assert.Equal(t, createRequest, v)
fmt.Fprint(w, lbCreateJSONResponse)
})
loadBalancer, _, err := client.LoadBalancers.Create(ctx, createRequest)
if err != nil {
t.Errorf("LoadBalancers.Create returned error: %v", err)
}
expected := &LoadBalancer{
ID: "8268a81c-fcf5-423e-a337-bbfe95817f23",
Name: "example-lb-01",
Algorithm: "round_robin",
Status: "new",
Created: "2016-12-15T14:19:09Z",
ForwardingRules: []ForwardingRule{
{
EntryProtocol: "https",
EntryPort: 443,
TargetProtocol: "http",
TargetPort: 80,
CertificateID: "a-b-c",
TlsPassthrough: false,
},
{
EntryProtocol: "https",
EntryPort: 444,
TargetProtocol: "https",
TargetPort: 443,
CertificateID: "",
TlsPassthrough: true,
},
},
HealthCheck: &HealthCheck{
Protocol: "http",
Port: 80,
Path: "/index.html",
CheckIntervalSeconds: 10,
ResponseTimeoutSeconds: 5,
HealthyThreshold: 5,
UnhealthyThreshold: 3,
},
StickySessions: &StickySessions{
Type: "cookies",
CookieName: "DO-LB",
CookieTtlSeconds: 5,
},
Region: &Region{
Slug: "nyc1",
Name: "New York 1",
Sizes: []string{"512mb", "1gb", "2gb", "4gb", "8gb", "16gb"},
Available: true,
Features: []string{"private_networking", "backups", "ipv6", "metadata", "storage"},
},
DropletIDs: []int{2, 21},
RedirectHttpToHttps: true,
}
assert.Equal(t, expected, loadBalancer)
}
func TestLoadBlanacers_Update(t *testing.T) {
setup()
defer teardown()
updateRequest := &LoadBalancerRequest{
Name: "example-lb-01",
Algorithm: "least_connections",
Region: "nyc1",
ForwardingRules: []ForwardingRule{
{
EntryProtocol: "http",
EntryPort: 80,
TargetProtocol: "http",
TargetPort: 80,
},
{
EntryProtocol: "https",
EntryPort: 443,
TargetProtocol: "http",
TargetPort: 80,
CertificateID: "a-b-c",
},
},
HealthCheck: &HealthCheck{
Protocol: "tcp",
Port: 80,
Path: "",
CheckIntervalSeconds: 10,
ResponseTimeoutSeconds: 5,
UnhealthyThreshold: 3,
HealthyThreshold: 5,
},
StickySessions: &StickySessions{
Type: "none",
},
DropletIDs: []int{2, 21},
}
path := "/v2/load_balancers"
loadBalancerId := "8268a81c-fcf5-423e-a337-bbfe95817f23"
path = fmt.Sprintf("%s/%s", path, loadBalancerId)
mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
v := new(LoadBalancerRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatal(err)
}
testMethod(t, r, "PUT")
assert.Equal(t, updateRequest, v)
fmt.Fprint(w, lbUpdateJSONResponse)
})
loadBalancer, _, err := client.LoadBalancers.Update(ctx, loadBalancerId, updateRequest)
if err != nil {
t.Errorf("LoadBalancers.Update returned error: %v", err)
}
expected := &LoadBalancer{
ID: "8268a81c-fcf5-423e-a337-bbfe95817f23",
Name: "example-lb-01",
IP: "12.34.56.78",
Algorithm: "least_connections",
Status: "active",
Created: "2016-12-15T14:19:09Z",
ForwardingRules: []ForwardingRule{
{
EntryProtocol: "http",
EntryPort: 80,
TargetProtocol: "http",
TargetPort: 80,
},
{
EntryProtocol: "https",
EntryPort: 443,
TargetProtocol: "http",
TargetPort: 80,
CertificateID: "a-b-c",
},
},
HealthCheck: &HealthCheck{
Protocol: "tcp",
Port: 80,
Path: "",
CheckIntervalSeconds: 10,
ResponseTimeoutSeconds: 5,
UnhealthyThreshold: 3,
HealthyThreshold: 5,
},
StickySessions: &StickySessions{
Type: "none",
},
Region: &Region{
Slug: "nyc1",
Name: "New York 1",
Sizes: []string{"512mb", "1gb", "2gb", "4gb", "8gb", "16gb"},
Available: true,
Features: []string{"private_networking", "backups", "ipv6", "metadata", "storage"},
},
DropletIDs: []int{2, 21},
}
assert.Equal(t, expected, loadBalancer)
}
func TestLoadBlanacers_List(t *testing.T) {
setup()
defer teardown()
path := "/v2/load_balancers"
mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, lbListJSONResponse)
})
loadBalancers, _, err := client.LoadBalancers.List(ctx, nil)
if err != nil {
t.Errorf("LoadBalancers.List returned error: %v", err)
}
expected := []LoadBalancer{
{
ID: "37e6be88-01ec-4ec7-9bc6-a514d4719057",
Name: "example-lb-01",
IP: "46.214.185.203",
Algorithm: "round_robin",
Status: "active",
Created: "2016-12-15T14:16:36Z",
ForwardingRules: []ForwardingRule{
{
EntryProtocol: "https",
EntryPort: 443,
TargetProtocol: "http",
TargetPort: 80,
CertificateID: "a-b-c",
},
},
HealthCheck: &HealthCheck{
Protocol: "http",
Port: 80,
Path: "/index.html",
CheckIntervalSeconds: 10,
ResponseTimeoutSeconds: 5,
HealthyThreshold: 5,
UnhealthyThreshold: 3,
},
StickySessions: &StickySessions{
Type: "cookies",
CookieName: "DO-LB",
CookieTtlSeconds: 5,
},
Region: &Region{
Slug: "nyc1",
Name: "New York 1",
Sizes: []string{"512mb", "1gb", "2gb", "4gb", "8gb", "16gb"},
Available: true,
Features: []string{"private_networking", "backups", "ipv6", "metadata", "storage"},
},
DropletIDs: []int{2, 21},
},
}
assert.Equal(t, expected, loadBalancers)
}
func TestLoadBlanacers_List_Pagination(t *testing.T) {
setup()
defer teardown()
path := "/v2/load_balancers"
mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testFormValues(t, r, map[string]string{"page": "2"})
fmt.Fprint(w, lbListJSONResponse)
})
opts := &ListOptions{Page: 2}
_, resp, err := client.LoadBalancers.List(ctx, opts)
if err != nil {
t.Errorf("LoadBalancers.List returned error: %v", err)
}
assert.Equal(t, "http://localhost:3001/v2/load_balancers?page=2&per_page=1", resp.Links.Pages.Next)
assert.Equal(t, "http://localhost:3001/v2/load_balancers?page=3&per_page=1", resp.Links.Pages.Last)
}
func TestLoadBlanacers_Delete(t *testing.T) {
setup()
defer teardown()
lbID := "37e6be88-01ec-4ec7-9bc6-a514d4719057"
path := "/v2/load_balancers"
path = fmt.Sprintf("%s/%s", path, lbID)
mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "DELETE")
})
_, err := client.LoadBalancers.Delete(ctx, lbID)
if err != nil {
t.Errorf("LoadBalancers.Delete returned error: %v", err)
}
}
func TestLoadBlanacers_AddDroplets(t *testing.T) {
setup()
defer teardown()
dropletIdsRequest := &dropletIDsRequest{
IDs: []int{42, 44},
}
lbID := "37e6be88-01ec-4ec7-9bc6-a514d4719057"
path := fmt.Sprintf("/v2/load_balancers/%s/droplets", lbID)
mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
v := new(dropletIDsRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatal(err)
}
testMethod(t, r, "POST")
assert.Equal(t, dropletIdsRequest, v)
fmt.Fprint(w, nil)
})
_, err := client.LoadBalancers.AddDroplets(ctx, lbID, dropletIdsRequest.IDs...)
if err != nil {
t.Errorf("LoadBalancers.AddDroplets returned error: %v", err)
}
}
func TestLoadBlanacers_RemoveDroplets(t *testing.T) {
setup()
defer teardown()
dropletIdsRequest := &dropletIDsRequest{
IDs: []int{2, 21},
}
lbID := "37e6be88-01ec-4ec7-9bc6-a514d4719057"
path := fmt.Sprintf("/v2/load_balancers/%s/droplets", lbID)
mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
v := new(dropletIDsRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatal(err)
}
testMethod(t, r, "DELETE")
assert.Equal(t, dropletIdsRequest, v)
fmt.Fprint(w, nil)
})
_, err := client.LoadBalancers.RemoveDroplets(ctx, lbID, dropletIdsRequest.IDs...)
if err != nil {
t.Errorf("LoadBalancers.RemoveDroplets returned error: %v", err)
}
}
func TestLoadBlanacers_AddForwardingRules(t *testing.T) {
setup()
defer teardown()
frr := &forwardingRulesRequest{
Rules: []ForwardingRule{
{
EntryProtocol: "https",
EntryPort: 444,
TargetProtocol: "http",
TargetPort: 81,
CertificateID: "b2abc00f-d3c4-426c-9f0b-b2f7a3ff7527",
},
{
EntryProtocol: "tcp",
EntryPort: 8080,
TargetProtocol: "tcp",
TargetPort: 8081,
},
},
}
lbID := "37e6be88-01ec-4ec7-9bc6-a514d4719057"
path := fmt.Sprintf("/v2/load_balancers/%s/forwarding_rules", lbID)
mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
v := new(forwardingRulesRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatal(err)
}
testMethod(t, r, "POST")
assert.Equal(t, frr, v)
fmt.Fprint(w, nil)
})
_, err := client.LoadBalancers.AddForwardingRules(ctx, lbID, frr.Rules...)
if err != nil {
t.Errorf("LoadBalancers.AddForwardingRules returned error: %v", err)
}
}
func TestLoadBlanacers_RemoveForwardingRules(t *testing.T) {
setup()
defer teardown()
frr := &forwardingRulesRequest{
Rules: []ForwardingRule{
{
EntryProtocol: "https",
EntryPort: 444,
TargetProtocol: "http",
TargetPort: 81,
},
{
EntryProtocol: "tcp",
EntryPort: 8080,
TargetProtocol: "tcp",
TargetPort: 8081,
},
},
}
lbID := "37e6be88-01ec-4ec7-9bc6-a514d4719057"
path := fmt.Sprintf("/v2/load_balancers/%s/forwarding_rules", lbID)
mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
v := new(forwardingRulesRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatal(err)
}
testMethod(t, r, "DELETE")
assert.Equal(t, frr, v)
fmt.Fprint(w, nil)
})
_, err := client.LoadBalancers.RemoveForwardingRules(ctx, lbID, frr.Rules...)
if err != nil {
t.Errorf("LoadBalancers.RemoveForwardingRules returned error: %v", err)
}
}

View File

@ -1,10 +1,12 @@
package godo
import "context"
// RegionsService is an interface for interfacing with the regions
// endpoints of the DigitalOcean API
// See: https://developers.digitalocean.com/documentation/v2#regions
type RegionsService interface {
List(*ListOptions) ([]Region, *Response, error)
List(context.Context, *ListOptions) ([]Region, *Response, error)
}
// RegionsServiceOp handles communication with the region related methods of the
@ -29,23 +31,19 @@ type regionsRoot struct {
Links *Links `json:"links"`
}
type regionRoot struct {
Region Region
}
func (r Region) String() string {
return Stringify(r)
}
// List all regions
func (s *RegionsServiceOp) List(opt *ListOptions) ([]Region, *Response, error) {
func (s *RegionsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Region, *Response, error) {
path := "v2/regions"
path, err := addOptions(path, opt)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", path, nil)
req, err := s.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}

View File

@ -16,7 +16,7 @@ func TestRegions_List(t *testing.T) {
fmt.Fprint(w, `{"regions":[{"slug":"1"},{"slug":"2"}]}`)
})
regions, _, err := client.Regions.List(nil)
regions, _, err := client.Regions.List(ctx, nil)
if err != nil {
t.Errorf("Regions.List returned error: %v", err)
}
@ -36,7 +36,7 @@ func TestRegions_ListRegionsMultiplePages(t *testing.T) {
fmt.Fprint(w, `{"regions": [{"id":1},{"id":2}], "links":{"pages":{"next":"http://example.com/v2/regions/?page=2"}}}`)
})
_, resp, err := client.Regions.List(nil)
_, resp, err := client.Regions.List(ctx, nil)
if err != nil {
t.Fatal(err)
}
@ -67,7 +67,7 @@ func TestRegions_RetrievePageByNumber(t *testing.T) {
})
opt := &ListOptions{Page: 2}
_, resp, err := client.Regions.List(opt)
_, resp, err := client.Regions.List(ctx, opt)
if err != nil {
t.Fatal(err)
}

View File

@ -1,10 +1,12 @@
package godo
import "context"
// SizesService is an interface for interfacing with the size
// endpoints of the DigitalOcean API
// See: https://developers.digitalocean.com/documentation/v2#sizes
type SizesService interface {
List(*ListOptions) ([]Size, *Response, error)
List(context.Context, *ListOptions) ([]Size, *Response, error)
}
// SizesServiceOp handles communication with the size related methods of the
@ -38,14 +40,14 @@ type sizesRoot struct {
}
// List all images
func (s *SizesServiceOp) List(opt *ListOptions) ([]Size, *Response, error) {
func (s *SizesServiceOp) List(ctx context.Context, opt *ListOptions) ([]Size, *Response, error) {
path := "v2/sizes"
path, err := addOptions(path, opt)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", path, nil)
req, err := s.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}

View File

@ -16,7 +16,7 @@ func TestSizes_List(t *testing.T) {
fmt.Fprint(w, `{"sizes":[{"slug":"1"},{"slug":"2"}]}`)
})
sizes, _, err := client.Sizes.List(nil)
sizes, _, err := client.Sizes.List(ctx, nil)
if err != nil {
t.Errorf("Sizes.List returned error: %v", err)
}
@ -36,7 +36,7 @@ func TestSizes_ListSizesMultiplePages(t *testing.T) {
fmt.Fprint(w, `{"sizes": [{"id":1},{"id":2}], "links":{"pages":{"next":"http://example.com/v2/sizes/?page=2"}}}`)
})
_, resp, err := client.Sizes.List(nil)
_, resp, err := client.Sizes.List(ctx, nil)
if err != nil {
t.Fatal(err)
}
@ -67,7 +67,7 @@ func TestSizes_RetrievePageByNumber(t *testing.T) {
})
opt := &ListOptions{Page: 2}
_, resp, err := client.Sizes.List(opt)
_, resp, err := client.Sizes.List(ctx, opt)
if err != nil {
t.Fatal(err)
}

139
vendor/github.com/digitalocean/godo/snapshots.go generated vendored Normal file
View File

@ -0,0 +1,139 @@
package godo
import (
"context"
"fmt"
)
const snapshotBasePath = "v2/snapshots"
// SnapshotsService is an interface for interfacing with the snapshots
// endpoints of the DigitalOcean API
// See: https://developers.digitalocean.com/documentation/v2#snapshots
type SnapshotsService interface {
List(context.Context, *ListOptions) ([]Snapshot, *Response, error)
ListVolume(context.Context, *ListOptions) ([]Snapshot, *Response, error)
ListDroplet(context.Context, *ListOptions) ([]Snapshot, *Response, error)
Get(context.Context, string) (*Snapshot, *Response, error)
Delete(context.Context, string) (*Response, error)
}
// SnapshotsServiceOp handles communication with the snapshot related methods of the
// DigitalOcean API.
type SnapshotsServiceOp struct {
client *Client
}
var _ SnapshotsService = &SnapshotsServiceOp{}
// Snapshot represents a DigitalOcean Snapshot
type Snapshot struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
ResourceID string `json:"resource_id,omitempty"`
ResourceType string `json:"resource_type,omitempty"`
Regions []string `json:"regions,omitempty"`
MinDiskSize int `json:"min_disk_size,omitempty"`
SizeGigaBytes float64 `json:"size_gigabytes,omitempty"`
Created string `json:"created_at,omitempty"`
}
type snapshotRoot struct {
Snapshot *Snapshot `json:"snapshot"`
}
type snapshotsRoot struct {
Snapshots []Snapshot `json:"snapshots"`
Links *Links `json:"links,omitempty"`
}
type listSnapshotOptions struct {
ResourceType string `url:"resource_type,omitempty"`
}
func (s Snapshot) String() string {
return Stringify(s)
}
// List lists all the snapshots available.
func (s *SnapshotsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Snapshot, *Response, error) {
return s.list(ctx, opt, nil)
}
// ListDroplet lists all the Droplet snapshots.
func (s *SnapshotsServiceOp) ListDroplet(ctx context.Context, opt *ListOptions) ([]Snapshot, *Response, error) {
listOpt := listSnapshotOptions{ResourceType: "droplet"}
return s.list(ctx, opt, &listOpt)
}
// ListVolume lists all the volume snapshots.
func (s *SnapshotsServiceOp) ListVolume(ctx context.Context, opt *ListOptions) ([]Snapshot, *Response, error) {
listOpt := listSnapshotOptions{ResourceType: "volume"}
return s.list(ctx, opt, &listOpt)
}
// Get retrieves an snapshot by id.
func (s *SnapshotsServiceOp) Get(ctx context.Context, snapshotID string) (*Snapshot, *Response, error) {
return s.get(ctx, snapshotID)
}
// Delete an snapshot.
func (s *SnapshotsServiceOp) Delete(ctx context.Context, snapshotID string) (*Response, error) {
path := fmt.Sprintf("%s/%s", snapshotBasePath, snapshotID)
req, err := s.client.NewRequest(ctx, "DELETE", path, nil)
if err != nil {
return nil, err
}
resp, err := s.client.Do(req, nil)
return resp, err
}
// Helper method for getting an individual snapshot
func (s *SnapshotsServiceOp) get(ctx context.Context, ID string) (*Snapshot, *Response, error) {
path := fmt.Sprintf("%s/%s", snapshotBasePath, ID)
req, err := s.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
root := new(snapshotRoot)
resp, err := s.client.Do(req, root)
if err != nil {
return nil, resp, err
}
return root.Snapshot, resp, err
}
// Helper method for listing snapshots
func (s *SnapshotsServiceOp) list(ctx context.Context, opt *ListOptions, listOpt *listSnapshotOptions) ([]Snapshot, *Response, error) {
path := snapshotBasePath
path, err := addOptions(path, opt)
if err != nil {
return nil, nil, err
}
path, err = addOptions(path, listOpt)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
root := new(snapshotsRoot)
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.Snapshots, resp, err
}

186
vendor/github.com/digitalocean/godo/snapshots_test.go generated vendored Normal file
View File

@ -0,0 +1,186 @@
package godo
import (
"context"
"fmt"
"net/http"
"reflect"
"testing"
)
func TestSnapshots_List(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v2/snapshots", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{"snapshots":[{"id":"1"},{"id":"2", "size_gigabytes": 4.84}]}`)
})
ctx := context.Background()
snapshots, _, err := client.Snapshots.List(ctx, nil)
if err != nil {
t.Errorf("Snapshots.List returned error: %v", err)
}
expected := []Snapshot{{ID: "1"}, {ID: "2", SizeGigaBytes: 4.84}}
if !reflect.DeepEqual(snapshots, expected) {
t.Errorf("Snapshots.List returned %+v, expected %+v", snapshots, expected)
}
}
func TestSnapshots_ListVolume(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v2/snapshots", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
expected := "volume"
actual := r.URL.Query().Get("resource_type")
if actual != expected {
t.Errorf("'type' query = %v, expected %v", actual, expected)
}
fmt.Fprint(w, `{"snapshots":[{"id":"1"},{"id":"2"}]}`)
})
ctx := context.Background()
snapshots, _, err := client.Snapshots.ListVolume(ctx, nil)
if err != nil {
t.Errorf("Snapshots.ListVolume returned error: %v", err)
}
expected := []Snapshot{{ID: "1"}, {ID: "2"}}
if !reflect.DeepEqual(snapshots, expected) {
t.Errorf("Snapshots.ListVolume returned %+v, expected %+v", snapshots, expected)
}
}
func TestSnapshots_ListDroplet(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v2/snapshots", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
expected := "droplet"
actual := r.URL.Query().Get("resource_type")
if actual != expected {
t.Errorf("'resource_type' query = %v, expected %v", actual, expected)
}
fmt.Fprint(w, `{"snapshots":[{"id":"1"},{"id":"2", "size_gigabytes": 4.84}]}`)
})
ctx := context.Background()
snapshots, _, err := client.Snapshots.ListDroplet(ctx, nil)
if err != nil {
t.Errorf("Snapshots.ListDroplet returned error: %v", err)
}
expected := []Snapshot{{ID: "1"}, {ID: "2", SizeGigaBytes: 4.84}}
if !reflect.DeepEqual(snapshots, expected) {
t.Errorf("Snapshots.ListDroplet returned %+v, expected %+v", snapshots, expected)
}
}
func TestSnapshots_ListSnapshotsMultiplePages(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v2/snapshots", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{"snapshots": [{"id":"1"},{"id":"2"}], "links":{"pages":{"next":"http://example.com/v2/snapshots/?page=2"}}}`)
})
ctx := context.Background()
_, resp, err := client.Snapshots.List(ctx, &ListOptions{Page: 2})
if err != nil {
t.Fatal(err)
}
checkCurrentPage(t, resp, 1)
}
func TestSnapshots_RetrievePageByNumber(t *testing.T) {
setup()
defer teardown()
jBlob := `
{
"snapshots": [{"id":"1"},{"id":"2"}],
"links":{
"pages":{
"next":"http://example.com/v2/snapshots/?page=3",
"prev":"http://example.com/v2/snapshots/?page=1",
"last":"http://example.com/v2/snapshots/?page=3",
"first":"http://example.com/v2/snapshots/?page=1"
}
}
}`
mux.HandleFunc("/v2/snapshots", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, jBlob)
})
ctx := context.Background()
opt := &ListOptions{Page: 2}
_, resp, err := client.Snapshots.List(ctx, opt)
if err != nil {
t.Fatal(err)
}
checkCurrentPage(t, resp, 2)
}
func TestSnapshots_GetSnapshotByID(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v2/snapshots/12345", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{"snapshot":{"id":"12345"}}`)
})
ctx := context.Background()
snapshots, _, err := client.Snapshots.Get(ctx, "12345")
if err != nil {
t.Errorf("Snapshot.GetByID returned error: %v", err)
}
expected := &Snapshot{ID: "12345"}
if !reflect.DeepEqual(snapshots, expected) {
t.Errorf("Snapshots.GetByID returned %+v, expected %+v", snapshots, expected)
}
}
func TestSnapshots_Destroy(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v2/snapshots/12345", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "DELETE")
})
ctx := context.Background()
_, err := client.Snapshots.Delete(ctx, "12345")
if err != nil {
t.Errorf("Snapshot.Delete returned error: %v", err)
}
}
func TestSnapshot_String(t *testing.T) {
snapshot := &Snapshot{
ID: "1",
Name: "Snapsh176ot",
ResourceID: "0",
ResourceType: "droplet",
Regions: []string{"one"},
MinDiskSize: 20,
SizeGigaBytes: 4.84,
Created: "2013-11-27T09:24:55Z",
}
stringified := snapshot.String()
expected := `godo.Snapshot{ID:"1", Name:"Snapsh176ot", ResourceID:"0", ResourceType:"droplet", Regions:["one"], MinDiskSize:20, SizeGigaBytes:4.84, Created:"2013-11-27T09:24:55Z"}`
if expected != stringified {
t.Errorf("Snapshot.String returned %+v, expected %+v", stringified, expected)
}
}

237
vendor/github.com/digitalocean/godo/storage.go generated vendored Normal file
View File

@ -0,0 +1,237 @@
package godo
import (
"context"
"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(context.Context, *ListVolumeParams) ([]Volume, *Response, error)
GetVolume(context.Context, string) (*Volume, *Response, error)
CreateVolume(context.Context, *VolumeCreateRequest) (*Volume, *Response, error)
DeleteVolume(context.Context, string) (*Response, error)
ListSnapshots(ctx context.Context, volumeID string, opts *ListOptions) ([]Snapshot, *Response, error)
GetSnapshot(context.Context, string) (*Snapshot, *Response, error)
CreateSnapshot(context.Context, *SnapshotCreateRequest) (*Snapshot, *Response, error)
DeleteSnapshot(context.Context, string) (*Response, error)
}
// StorageServiceOp handles communication with the storage volumes related methods of the
// DigitalOcean API.
type StorageServiceOp struct {
client *Client
}
// ListVolumeParams stores the options you can set for a ListVolumeCall
type ListVolumeParams struct {
Region string `json:"region"`
Name string `json:"name"`
ListOptions *ListOptions `json:"list_options,omitempty"`
}
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(ctx context.Context, params *ListVolumeParams) ([]Volume, *Response, error) {
path := storageAllocPath
if params != nil {
if params.Region != "" && params.Name != "" {
path = fmt.Sprintf("%s?name=%s&region=%s", path, params.Name, params.Region)
}
if params.ListOptions != nil {
var err error
path, err = addOptions(path, params.ListOptions)
if err != nil {
return nil, nil, err
}
}
}
req, err := svc.client.NewRequest(ctx, "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(ctx context.Context, createRequest *VolumeCreateRequest) (*Volume, *Response, error) {
path := storageAllocPath
req, err := svc.client.NewRequest(ctx, "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(ctx context.Context, id string) (*Volume, *Response, error) {
path := fmt.Sprintf("%s/%s", storageAllocPath, id)
req, err := svc.client.NewRequest(ctx, "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(ctx context.Context, id string) (*Response, error) {
path := fmt.Sprintf("%s/%s", storageAllocPath, id)
req, err := svc.client.NewRequest(ctx, "DELETE", path, nil)
if err != nil {
return nil, err
}
return svc.client.Do(req, nil)
}
// 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(ctx context.Context, 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(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
root := new(snapshotsRoot)
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(ctx context.Context, createRequest *SnapshotCreateRequest) (*Snapshot, *Response, error) {
path := fmt.Sprintf("%s/%s/snapshots", storageAllocPath, createRequest.VolumeID)
req, err := svc.client.NewRequest(ctx, "POST", path, createRequest)
if err != nil {
return nil, nil, err
}
root := new(snapshotRoot)
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(ctx context.Context, id string) (*Snapshot, *Response, error) {
path := fmt.Sprintf("%s/%s", storageSnapPath, id)
req, err := svc.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
root := new(snapshotRoot)
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(ctx context.Context, id string) (*Response, error) {
path := fmt.Sprintf("%s/%s", storageSnapPath, id)
req, err := svc.client.NewRequest(ctx, "DELETE", path, nil)
if err != nil {
return nil, err
}
return svc.client.Do(req, nil)
}

137
vendor/github.com/digitalocean/godo/storage_actions.go generated vendored Normal file
View File

@ -0,0 +1,137 @@
package godo
import (
"context"
"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(ctx context.Context, volumeID string, dropletID int) (*Action, *Response, error)
Detach(ctx context.Context, volumeID string) (*Action, *Response, error)
DetachByDropletID(ctx context.Context, volumeID string, dropletID int) (*Action, *Response, error)
Get(ctx context.Context, volumeID string, actionID int) (*Action, *Response, error)
List(ctx context.Context, volumeID string, opt *ListOptions) ([]Action, *Response, error)
Resize(ctx context.Context, volumeID string, sizeGigabytes int, regionSlug string) (*Action, *Response, error)
}
// StorageActionsServiceOp handles communication with the storage volumes
// 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(ctx context.Context, volumeID string, dropletID int) (*Action, *Response, error) {
request := &ActionRequest{
"type": "attach",
"droplet_id": dropletID,
}
return s.doAction(ctx, volumeID, request)
}
// Detach a storage volume from a Droplet.
func (s *StorageActionsServiceOp) Detach(ctx context.Context, volumeID string) (*Action, *Response, error) {
request := &ActionRequest{
"type": "detach",
}
return s.doAction(ctx, volumeID, request)
}
// DetachByDropletID a storage volume from a Droplet by Droplet ID.
func (s *StorageActionsServiceOp) DetachByDropletID(ctx context.Context, volumeID string, dropletID int) (*Action, *Response, error) {
request := &ActionRequest{
"type": "detach",
"droplet_id": dropletID,
}
return s.doAction(ctx, volumeID, request)
}
// Get an action for a particular storage volume by id.
func (s *StorageActionsServiceOp) Get(ctx context.Context, volumeID string, actionID int) (*Action, *Response, error) {
path := fmt.Sprintf("%s/%d", storageAllocationActionPath(volumeID), actionID)
return s.get(ctx, path)
}
// List the actions for a particular storage volume.
func (s *StorageActionsServiceOp) List(ctx context.Context, volumeID string, opt *ListOptions) ([]Action, *Response, error) {
path := storageAllocationActionPath(volumeID)
path, err := addOptions(path, opt)
if err != nil {
return nil, nil, err
}
return s.list(ctx, path)
}
// Resize a storage volume.
func (s *StorageActionsServiceOp) Resize(ctx context.Context, volumeID string, sizeGigabytes int, regionSlug string) (*Action, *Response, error) {
request := &ActionRequest{
"type": "resize",
"size_gigabytes": sizeGigabytes,
"region": regionSlug,
}
return s.doAction(ctx, volumeID, request)
}
func (s *StorageActionsServiceOp) doAction(ctx context.Context, volumeID string, request *ActionRequest) (*Action, *Response, error) {
path := storageAllocationActionPath(volumeID)
req, err := s.client.NewRequest(ctx, "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 (s *StorageActionsServiceOp) get(ctx context.Context, path string) (*Action, *Response, error) {
req, err := s.client.NewRequest(ctx, "GET", path, nil)
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 (s *StorageActionsServiceOp) list(ctx context.Context, path string) ([]Action, *Response, error) {
req, err := s.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
root := new(actionsRoot)
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.Actions, resp, err
}
func storageAllocationActionPath(volumeID string) string {
return fmt.Sprintf("%s/%s/actions", storageAllocPath, volumeID)
}

View File

@ -0,0 +1,179 @@
package godo
import (
"encoding/json"
"fmt"
"net/http"
"reflect"
"testing"
)
func TestStoragesActions_Attach(t *testing.T) {
setup()
defer teardown()
const (
volumeID = "98d414c6-295e-4e3a-ac58-eb9456c1e1d1"
dropletID = 12345
)
attachRequest := &ActionRequest{
"type": "attach",
"droplet_id": float64(dropletID), // encoding/json decodes numbers as floats
}
mux.HandleFunc("/v2/volumes/"+volumeID+"/actions", func(w http.ResponseWriter, r *http.Request) {
v := new(ActionRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatalf("decode json: %v", err)
}
testMethod(t, r, "POST")
if !reflect.DeepEqual(v, attachRequest) {
t.Errorf("want=%#v", attachRequest)
t.Errorf("got=%#v", v)
}
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
_, _, err := client.StorageActions.Attach(ctx, volumeID, dropletID)
if err != nil {
t.Errorf("StoragesActions.Attach returned error: %v", err)
}
}
func TestStoragesActions_Detach(t *testing.T) {
setup()
defer teardown()
volumeID := "98d414c6-295e-4e3a-ac58-eb9456c1e1d1"
detachRequest := &ActionRequest{
"type": "detach",
}
mux.HandleFunc("/v2/volumes/"+volumeID+"/actions", func(w http.ResponseWriter, r *http.Request) {
v := new(ActionRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatalf("decode json: %v", err)
}
testMethod(t, r, "POST")
if !reflect.DeepEqual(v, detachRequest) {
t.Errorf("want=%#v", detachRequest)
t.Errorf("got=%#v", v)
}
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
_, _, err := client.StorageActions.Detach(ctx, volumeID)
if err != nil {
t.Errorf("StoragesActions.Detach returned error: %v", err)
}
}
func TestStoragesActions_DetachByDropletID(t *testing.T) {
setup()
defer teardown()
volumeID := "98d414c6-295e-4e3a-ac58-eb9456c1e1d1"
dropletID := 123456
detachByDropletIDRequest := &ActionRequest{
"type": "detach",
"droplet_id": float64(dropletID), // encoding/json decodes numbers as floats
}
mux.HandleFunc("/v2/volumes/"+volumeID+"/actions", func(w http.ResponseWriter, r *http.Request) {
v := new(ActionRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatalf("decode json: %v", err)
}
testMethod(t, r, "POST")
if !reflect.DeepEqual(v, detachByDropletIDRequest) {
t.Errorf("want=%#v", detachByDropletIDRequest)
t.Errorf("got=%#v", v)
}
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
_, _, err := client.StorageActions.DetachByDropletID(ctx, volumeID, dropletID)
if err != nil {
t.Errorf("StoragesActions.DetachByDropletID returned error: %v", err)
}
}
func TestStorageActions_Get(t *testing.T) {
setup()
defer teardown()
volumeID := "98d414c6-295e-4e3a-ac58-eb9456c1e1d1"
mux.HandleFunc("/v2/volumes/"+volumeID+"/actions/456", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
action, _, err := client.StorageActions.Get(ctx, volumeID, 456)
if err != nil {
t.Errorf("StorageActions.Get returned error: %v", err)
}
expected := &Action{Status: "in-progress"}
if !reflect.DeepEqual(action, expected) {
t.Errorf("StorageActions.Get returned %+v, expected %+v", action, expected)
}
}
func TestStorageActions_List(t *testing.T) {
setup()
defer teardown()
volumeID := "98d414c6-295e-4e3a-ac58-eb9456c1e1d1"
mux.HandleFunc("/v2/volumes/"+volumeID+"/actions", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprintf(w, `{"actions":[{"status":"in-progress"}]}`)
})
actions, _, err := client.StorageActions.List(ctx, volumeID, nil)
if err != nil {
t.Errorf("StorageActions.List returned error: %v", err)
}
expected := []Action{{Status: "in-progress"}}
if !reflect.DeepEqual(actions, expected) {
t.Errorf("StorageActions.List returned %+v, expected %+v", actions, expected)
}
}
func TestStoragesActions_Resize(t *testing.T) {
setup()
defer teardown()
volumeID := "98d414c6-295e-4e3a-ac58-eb9456c1e1d1"
resizeRequest := &ActionRequest{
"type": "resize",
"size_gigabytes": float64(500),
"region": "nyc1",
}
mux.HandleFunc("/v2/volumes/"+volumeID+"/actions", func(w http.ResponseWriter, r *http.Request) {
v := new(ActionRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatalf("decode json: %v", err)
}
testMethod(t, r, "POST")
if !reflect.DeepEqual(v, resizeRequest) {
t.Errorf("want=%#v", resizeRequest)
t.Errorf("got=%#v", v)
}
fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})
_, _, err := client.StorageActions.Resize(ctx, volumeID, 500, "nyc1")
if err != nil {
t.Errorf("StoragesActions.Resize returned error: %v", err)
}
}

436
vendor/github.com/digitalocean/godo/storage_test.go generated vendored Normal file
View File

@ -0,0 +1,436 @@
package godo
import (
"encoding/json"
"fmt"
"net/http"
"reflect"
"testing"
"time"
)
func TestStorageVolumes_ListStorageVolumes(t *testing.T) {
setup()
defer teardown()
jBlob := `
{
"volumes": [
{
"user_id": 42,
"region": {"slug": "nyc3"},
"id": "80d414c6-295e-4e3a-ac58-eb9456c1e1d1",
"name": "my volume",
"description": "my description",
"size_gigabytes": 100,
"droplet_ids": [10],
"created_at": "2002-10-02T15:00:00.05Z"
},
{
"user_id": 42,
"region": {"slug": "nyc3"},
"id": "96d414c6-295e-4e3a-ac59-eb9456c1e1d1",
"name": "my other volume",
"description": "my other description",
"size_gigabytes": 100,
"created_at": "2012-10-03T15:00:01.05Z"
}
],
"links": {
"pages": {
"last": "https://api.digitalocean.com/v2/volumes?page=2",
"next": "https://api.digitalocean.com/v2/volumes?page=2"
}
},
"meta": {
"total": 28
}
}`
mux.HandleFunc("/v2/volumes/", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, jBlob)
})
volumes, _, err := client.Storage.ListVolumes(ctx, nil)
if err != nil {
t.Errorf("Storage.ListVolumes returned error: %v", err)
}
expected := []Volume{
{
Region: &Region{Slug: "nyc3"},
ID: "80d414c6-295e-4e3a-ac58-eb9456c1e1d1",
Name: "my volume",
Description: "my description",
SizeGigaBytes: 100,
DropletIDs: []int{10},
CreatedAt: time.Date(2002, 10, 02, 15, 00, 00, 50000000, time.UTC),
},
{
Region: &Region{Slug: "nyc3"},
ID: "96d414c6-295e-4e3a-ac59-eb9456c1e1d1",
Name: "my other volume",
Description: "my other description",
SizeGigaBytes: 100,
CreatedAt: time.Date(2012, 10, 03, 15, 00, 01, 50000000, time.UTC),
},
}
if !reflect.DeepEqual(volumes, expected) {
t.Errorf("Storage.ListVolumes returned %+v, expected %+v", volumes, expected)
}
}
func TestStorageVolumes_Get(t *testing.T) {
setup()
defer teardown()
want := &Volume{
Region: &Region{Slug: "nyc3"},
ID: "80d414c6-295e-4e3a-ac58-eb9456c1e1d1",
Name: "my volume",
Description: "my description",
SizeGigaBytes: 100,
CreatedAt: time.Date(2002, 10, 02, 15, 00, 00, 50000000, time.UTC),
}
jBlob := `{
"volume":{
"region": {"slug":"nyc3"},
"attached_to_droplet": null,
"id": "80d414c6-295e-4e3a-ac58-eb9456c1e1d1",
"name": "my volume",
"description": "my description",
"size_gigabytes": 100,
"created_at": "2002-10-02T15:00:00.05Z"
},
"links": {
"pages": {
"last": "https://api.digitalocean.com/v2/volumes?page=2",
"next": "https://api.digitalocean.com/v2/volumes?page=2"
}
},
"meta": {
"total": 28
}
}`
mux.HandleFunc("/v2/volumes/80d414c6-295e-4e3a-ac58-eb9456c1e1d1", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, jBlob)
})
got, _, err := client.Storage.GetVolume(ctx, "80d414c6-295e-4e3a-ac58-eb9456c1e1d1")
if err != nil {
t.Errorf("Storage.GetVolume returned error: %v", err)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("Storage.GetVolume returned %+v, want %+v", got, want)
}
}
func TestStorageVolumes_ListVolumesByName(t *testing.T) {
setup()
defer teardown()
jBlob :=
`{
"volumes": [
{
"region": {"slug": "nyc3"},
"id": "80d414c6-295e-4e3a-ac58-eb9456c1e1d1",
"name": "myvolume",
"description": "my description",
"size_gigabytes": 100,
"droplet_ids": [10],
"created_at": "2002-10-02T15:00:00.05Z"
}
],
"links": {},
"meta": {
"total": 1
}
}`
expected := []Volume{
{
Region: &Region{Slug: "nyc3"},
ID: "80d414c6-295e-4e3a-ac58-eb9456c1e1d1",
Name: "myvolume",
Description: "my description",
SizeGigaBytes: 100,
DropletIDs: []int{10},
CreatedAt: time.Date(2002, 10, 02, 15, 00, 00, 50000000, time.UTC),
},
}
mux.HandleFunc("/v2/volumes", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("name") != "myvolume" || r.URL.Query().Get("region") != "nyc3" {
t.Errorf("Storage.GetVolumeByName did not request the correct name or region")
}
testMethod(t, r, "GET")
fmt.Fprint(w, jBlob)
})
options := &ListVolumeParams{
Name: "myvolume",
Region: "nyc3",
}
volumes, _, err := client.Storage.ListVolumes(ctx, options)
if err != nil {
t.Errorf("Storage.GetVolumeByName returned error: %v", err)
}
if !reflect.DeepEqual(volumes, expected) {
t.Errorf("Storage.GetVolumeByName returned %+v, expected %+v", volumes, expected)
}
}
func TestStorageVolumes_Create(t *testing.T) {
setup()
defer teardown()
createRequest := &VolumeCreateRequest{
Region: "nyc3",
Name: "my volume",
Description: "my description",
SizeGigaBytes: 100,
}
want := &Volume{
Region: &Region{Slug: "nyc3"},
ID: "80d414c6-295e-4e3a-ac58-eb9456c1e1d1",
Name: "my volume",
Description: "my description",
SizeGigaBytes: 100,
CreatedAt: time.Date(2002, 10, 02, 15, 00, 00, 50000000, time.UTC),
}
jBlob := `{
"volume":{
"region": {"slug":"nyc3"},
"id": "80d414c6-295e-4e3a-ac58-eb9456c1e1d1",
"name": "my volume",
"description": "my description",
"size_gigabytes": 100,
"created_at": "2002-10-02T15:00:00.05Z"
},
"links": {}
}`
mux.HandleFunc("/v2/volumes", func(w http.ResponseWriter, r *http.Request) {
v := new(VolumeCreateRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatal(err)
}
testMethod(t, r, "POST")
if !reflect.DeepEqual(v, createRequest) {
t.Errorf("Request body = %+v, expected %+v", v, createRequest)
}
fmt.Fprint(w, jBlob)
})
got, _, err := client.Storage.CreateVolume(ctx, createRequest)
if err != nil {
t.Errorf("Storage.CreateVolume returned error: %v", err)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("Storage.CreateVolume returned %+v, want %+v", got, want)
}
}
func TestStorageVolumes_Destroy(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v2/volumes/80d414c6-295e-4e3a-ac58-eb9456c1e1d1", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "DELETE")
})
_, err := client.Storage.DeleteVolume(ctx, "80d414c6-295e-4e3a-ac58-eb9456c1e1d1")
if err != nil {
t.Errorf("Storage.DeleteVolume returned error: %v", err)
}
}
func TestStorageSnapshots_ListStorageSnapshots(t *testing.T) {
setup()
defer teardown()
jBlob := `
{
"snapshots": [
{
"regions": ["nyc3"],
"id": "80d414c6-295e-4e3a-ac58-eb9456c1e1d1",
"name": "my snapshot",
"size_gigabytes": 100,
"created_at": "2002-10-02T15:00:00.05Z"
},
{
"regions": ["nyc3"],
"id": "96d414c6-295e-4e3a-ac59-eb9456c1e1d1",
"name": "my other snapshot",
"size_gigabytes": 100,
"created_at": "2012-10-03T15:00:01.05Z"
}
],
"links": {
"pages": {
"last": "https://api.digitalocean.com/v2/volumes?page=2",
"next": "https://api.digitalocean.com/v2/volumes?page=2"
}
},
"meta": {
"total": 28
}
}`
mux.HandleFunc("/v2/volumes/98d414c6-295e-4e3a-ac58-eb9456c1e1d1/snapshots", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, jBlob)
})
volumes, _, err := client.Storage.ListSnapshots(ctx, "98d414c6-295e-4e3a-ac58-eb9456c1e1d1", nil)
if err != nil {
t.Errorf("Storage.ListSnapshots returned error: %v", err)
}
expected := []Snapshot{
{
Regions: []string{"nyc3"},
ID: "80d414c6-295e-4e3a-ac58-eb9456c1e1d1",
Name: "my snapshot",
SizeGigaBytes: 100,
Created: "2002-10-02T15:00:00.05Z",
},
{
Regions: []string{"nyc3"},
ID: "96d414c6-295e-4e3a-ac59-eb9456c1e1d1",
Name: "my other snapshot",
SizeGigaBytes: 100,
Created: "2012-10-03T15:00:01.05Z",
},
}
if !reflect.DeepEqual(volumes, expected) {
t.Errorf("Storage.ListSnapshots returned %+v, expected %+v", volumes, expected)
}
}
func TestStorageSnapshots_Get(t *testing.T) {
setup()
defer teardown()
want := &Snapshot{
Regions: []string{"nyc3"},
ID: "80d414c6-295e-4e3a-ac58-eb9456c1e1d1",
Name: "my snapshot",
SizeGigaBytes: 100,
Created: "2002-10-02T15:00:00.05Z",
}
jBlob := `{
"snapshot":{
"regions": ["nyc3"],
"id": "80d414c6-295e-4e3a-ac58-eb9456c1e1d1",
"name": "my snapshot",
"size_gigabytes": 100,
"created_at": "2002-10-02T15:00:00.05Z"
},
"links": {
"pages": {
"last": "https://api.digitalocean.com/v2/volumes/98d414c6-295e-4e3a-ac58-eb9456c1e1d1/snapshots?page=2",
"next": "https://api.digitalocean.com/v2/volumes/98d414c6-295e-4e3a-ac58-eb9456c1e1d1/snapshots?page=2"
}
},
"meta": {
"total": 28
}
}`
mux.HandleFunc("/v2/snapshots/80d414c6-295e-4e3a-ac58-eb9456c1e1d1", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, jBlob)
})
got, _, err := client.Storage.GetSnapshot(ctx, "80d414c6-295e-4e3a-ac58-eb9456c1e1d1")
if err != nil {
t.Errorf("Storage.GetSnapshot returned error: %v", err)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("Storage.GetSnapshot returned %+v, want %+v", got, want)
}
}
func TestStorageSnapshots_Create(t *testing.T) {
setup()
defer teardown()
createRequest := &SnapshotCreateRequest{
VolumeID: "98d414c6-295e-4e3a-ac58-eb9456c1e1d1",
Name: "my snapshot",
Description: "my description",
}
want := &Snapshot{
Regions: []string{"nyc3"},
ID: "80d414c6-295e-4e3a-ac58-eb9456c1e1d1",
Name: "my snapshot",
SizeGigaBytes: 100,
Created: "2002-10-02T15:00:00.05Z",
}
jBlob := `{
"snapshot":{
"regions": ["nyc3"],
"id": "80d414c6-295e-4e3a-ac58-eb9456c1e1d1",
"name": "my snapshot",
"description": "my description",
"size_gigabytes": 100,
"created_at": "2002-10-02T15:00:00.05Z"
},
"links": {
"pages": {
"last": "https://api.digitalocean.com/v2/volumes/98d414c6-295e-4e3a-ac58-eb9456c1e1d1/snapshots?page=2",
"next": "https://api.digitalocean.com/v2/volumes/98d414c6-295e-4e3a-ac58-eb9456c1e1d1/snapshots?page=2"
}
},
"meta": {
"total": 28
}
}`
mux.HandleFunc("/v2/volumes/98d414c6-295e-4e3a-ac58-eb9456c1e1d1/snapshots", func(w http.ResponseWriter, r *http.Request) {
v := new(SnapshotCreateRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatal(err)
}
testMethod(t, r, "POST")
if !reflect.DeepEqual(v, createRequest) {
t.Errorf("Request body = %+v, expected %+v", v, createRequest)
}
fmt.Fprint(w, jBlob)
})
got, _, err := client.Storage.CreateSnapshot(ctx, createRequest)
if err != nil {
t.Errorf("Storage.CreateSnapshot returned error: %v", err)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("Storage.CreateSnapshot returned %+v, want %+v", got, want)
}
}
func TestStorageSnapshots_Destroy(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v2/snapshots/80d414c6-295e-4e3a-ac58-eb9456c1e1d1", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "DELETE")
})
_, err := client.Storage.DeleteSnapshot(ctx, "80d414c6-295e-4e3a-ac58-eb9456c1e1d1")
if err != nil {
t.Errorf("Storage.DeleteSnapshot returned error: %v", err)
}
}

View File

@ -3,6 +3,7 @@ package godo
import (
"bytes"
"fmt"
"io"
"reflect"
)
@ -17,7 +18,7 @@ func Stringify(message interface{}) string {
}
// stringifyValue was graciously cargoculted from the goprotubuf library
func stringifyValue(w *bytes.Buffer, val reflect.Value) {
func stringifyValue(w io.Writer, val reflect.Value) {
if val.Kind() == reflect.Ptr && val.IsNil() {
_, _ = w.Write([]byte("<nil>"))
return
@ -29,55 +30,63 @@ func stringifyValue(w *bytes.Buffer, val reflect.Value) {
case reflect.String:
fmt.Fprintf(w, `"%s"`, v)
case reflect.Slice:
_, _ = w.Write([]byte{'['})
for i := 0; i < v.Len(); i++ {
if i > 0 {
_, _ = w.Write([]byte{' '})
}
stringifyValue(w, v.Index(i))
}
_, _ = w.Write([]byte{']'})
stringifySlice(w, v)
return
case reflect.Struct:
if v.Type().Name() != "" {
_, _ = w.Write([]byte(v.Type().String()))
}
// special handling of Timestamp values
if v.Type() == timestampType {
fmt.Fprintf(w, "{%s}", v.Interface())
return
}
_, _ = w.Write([]byte{'{'})
var sep bool
for i := 0; i < v.NumField(); i++ {
fv := v.Field(i)
if fv.Kind() == reflect.Ptr && fv.IsNil() {
continue
}
if fv.Kind() == reflect.Slice && fv.IsNil() {
continue
}
if sep {
_, _ = w.Write([]byte(", "))
} else {
sep = true
}
_, _ = w.Write([]byte(v.Type().Field(i).Name))
_, _ = w.Write([]byte{':'})
stringifyValue(w, fv)
}
_, _ = w.Write([]byte{'}'})
stringifyStruct(w, v)
default:
if v.CanInterface() {
fmt.Fprint(w, v.Interface())
}
}
}
func stringifySlice(w io.Writer, v reflect.Value) {
_, _ = w.Write([]byte{'['})
for i := 0; i < v.Len(); i++ {
if i > 0 {
_, _ = w.Write([]byte{' '})
}
stringifyValue(w, v.Index(i))
}
_, _ = w.Write([]byte{']'})
}
func stringifyStruct(w io.Writer, v reflect.Value) {
if v.Type().Name() != "" {
_, _ = w.Write([]byte(v.Type().String()))
}
// special handling of Timestamp values
if v.Type() == timestampType {
fmt.Fprintf(w, "{%s}", v.Interface())
return
}
_, _ = w.Write([]byte{'{'})
var sep bool
for i := 0; i < v.NumField(); i++ {
fv := v.Field(i)
if fv.Kind() == reflect.Ptr && fv.IsNil() {
continue
}
if fv.Kind() == reflect.Slice && fv.IsNil() {
continue
}
if sep {
_, _ = w.Write([]byte(", "))
} else {
sep = true
}
_, _ = w.Write([]byte(v.Type().Field(i).Name))
_, _ = w.Write([]byte{':'})
stringifyValue(w, fv)
}
_, _ = w.Write([]byte{'}'})
}

234
vendor/github.com/digitalocean/godo/tags.go generated vendored Normal file
View File

@ -0,0 +1,234 @@
package godo
import (
"context"
"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(context.Context, *ListOptions) ([]Tag, *Response, error)
Get(context.Context, string) (*Tag, *Response, error)
Create(context.Context, *TagCreateRequest) (*Tag, *Response, error)
Update(context.Context, string, *TagUpdateRequest) (*Response, error)
Delete(context.Context, string) (*Response, error)
TagResources(context.Context, string, *TagResourcesRequest) (*Response, error)
UntagResources(context.Context, 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 holds the string representing our ResourceType of Droplet.
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"`
}
//TagCreateRequest represents the JSON structure of a request of that type.
type TagCreateRequest struct {
Name string `json:"name"`
}
//TagUpdateRequest represents the JSON structure of a request of that type.
type TagUpdateRequest struct {
Name string `json:"name"`
}
// TagResourcesRequest represents the JSON structure of a request of that type.
type TagResourcesRequest struct {
Resources []Resource `json:"resources"`
}
// UntagResourcesRequest represents the JSON structure of a request of that type.
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(ctx context.Context, opt *ListOptions) ([]Tag, *Response, error) {
path := tagsBasePath
path, err := addOptions(path, opt)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest(ctx, "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(ctx context.Context, name string) (*Tag, *Response, error) {
path := fmt.Sprintf("%s/%s", tagsBasePath, name)
req, err := s.client.NewRequest(ctx, "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(ctx context.Context, createRequest *TagCreateRequest) (*Tag, *Response, error) {
if createRequest == nil {
return nil, nil, NewArgError("createRequest", "cannot be nil")
}
req, err := s.client.NewRequest(ctx, "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(ctx context.Context, 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(ctx, "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(ctx context.Context, 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(ctx, "DELETE", path, nil)
if err != nil {
return nil, err
}
resp, err := s.client.Do(req, nil)
return resp, err
}
// TagResources associates resources with a given Tag.
func (s *TagsServiceOp) TagResources(ctx context.Context, 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(ctx, "POST", path, tagRequest)
if err != nil {
return nil, err
}
resp, err := s.client.Do(req, nil)
return resp, err
}
// UntagResources dissociates resources with a given Tag.
func (s *TagsServiceOp) UntagResources(ctx context.Context, 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(ctx, "DELETE", path, untagRequest)
if err != nil {
return nil, err
}
resp, err := s.client.Do(req, nil)
return resp, err
}

384
vendor/github.com/digitalocean/godo/tags_test.go generated vendored Normal file
View File

@ -0,0 +1,384 @@
package godo
import (
"encoding/json"
"fmt"
"net/http"
"reflect"
"testing"
)
var (
listEmptyJSON = `
{
"tags": [
],
"meta": {
"total": 0
}
}
`
listJSON = `
{
"tags": [
{
"name": "testing-1",
"resources": {
"droplets": {
"count": 0,
"last_tagged": null
}
}
},
{
"name": "testing-2",
"resources": {
"droplets": {
"count": 0,
"last_tagged": null
}
}
}
],
"links": {
"pages":{
"next":"http://example.com/v2/tags/?page=3",
"prev":"http://example.com/v2/tags/?page=1",
"last":"http://example.com/v2/tags/?page=3",
"first":"http://example.com/v2/tags/?page=1"
}
},
"meta": {
"total": 2
}
}
`
createJSON = `
{
"tag": {
"name": "testing-1",
"resources": {
"droplets": {
"count": 0,
"last_tagged": null
}
}
}
}
`
getJSON = `
{
"tag": {
"name": "testing-1",
"resources": {
"droplets": {
"count": 1,
"last_tagged": {
"id": 1,
"name": "test.example.com",
"memory": 1024,
"vcpus": 2,
"disk": 20,
"region": {
"slug": "nyc1",
"name": "New York",
"sizes": [
"1024mb",
"512mb"
],
"available": true,
"features": [
"virtio",
"private_networking",
"backups",
"ipv6"
]
},
"image": {
"id": 119192817,
"name": "Ubuntu 13.04",
"distribution": "ubuntu",
"slug": "ubuntu1304",
"public": true,
"regions": [
"nyc1"
],
"created_at": "2014-07-29T14:35:37Z"
},
"size_slug": "1024mb",
"locked": false,
"status": "active",
"networks": {
"v4": [
{
"ip_address": "10.0.0.19",
"netmask": "255.255.0.0",
"gateway": "10.0.0.1",
"type": "private"
},
{
"ip_address": "127.0.0.19",
"netmask": "255.255.255.0",
"gateway": "127.0.0.20",
"type": "public"
}
],
"v6": [
{
"ip_address": "2001::13",
"cidr": 124,
"gateway": "2400:6180:0000:00D0:0000:0000:0009:7000",
"type": "public"
}
]
},
"kernel": {
"id": 485432985,
"name": "DO-recovery-static-fsck",
"version": "3.8.0-25-generic"
},
"created_at": "2014-07-29T14:35:37Z",
"features": [
"ipv6"
],
"backup_ids": [
449676382
],
"snapshot_ids": [
449676383
],
"action_ids": [
],
"tags": [
"tag-1",
"tag-2"
]
}
}
}
}
}
`
)
func TestTags_List(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v2/tags", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, listJSON)
})
tags, _, err := client.Tags.List(ctx, nil)
if err != nil {
t.Errorf("Tags.List returned error: %v", err)
}
expected := []Tag{{Name: "testing-1", Resources: &TaggedResources{Droplets: &TaggedDropletsResources{Count: 0, LastTagged: nil}}},
{Name: "testing-2", Resources: &TaggedResources{Droplets: &TaggedDropletsResources{Count: 0, LastTagged: nil}}}}
if !reflect.DeepEqual(tags, expected) {
t.Errorf("Tags.List returned %+v, expected %+v", tags, expected)
}
}
func TestTags_ListEmpty(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v2/tags", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, listEmptyJSON)
})
tags, _, err := client.Tags.List(ctx, nil)
if err != nil {
t.Errorf("Tags.List returned error: %v", err)
}
expected := []Tag{}
if !reflect.DeepEqual(tags, expected) {
t.Errorf("Tags.List returned %+v, expected %+v", tags, expected)
}
}
func TestTags_ListPaging(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v2/tags", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, listJSON)
})
_, resp, err := client.Tags.List(ctx, nil)
if err != nil {
t.Errorf("Tags.List returned error: %v", err)
}
checkCurrentPage(t, resp, 2)
}
func TestTags_Get(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v2/tags/testing-1", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, getJSON)
})
tag, _, err := client.Tags.Get(ctx, "testing-1")
if err != nil {
t.Errorf("Tags.Get returned error: %v", err)
}
if tag.Name != "testing-1" {
t.Errorf("Tags.Get return an incorrect name, got %+v, expected %+v", tag.Name, "testing-1")
}
if tag.Resources.Droplets.Count != 1 {
t.Errorf("Tags.Get return an incorrect droplet resource count, got %+v, expected %+v", tag.Resources.Droplets.Count, 1)
}
if tag.Resources.Droplets.LastTagged.ID != 1 {
t.Errorf("Tags.Get return an incorrect last tagged droplet %+v, expected %+v", tag.Resources.Droplets.LastTagged.ID, 1)
}
}
func TestTags_Create(t *testing.T) {
setup()
defer teardown()
createRequest := &TagCreateRequest{
Name: "testing-1",
}
mux.HandleFunc("/v2/tags", func(w http.ResponseWriter, r *http.Request) {
v := new(TagCreateRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatalf("decode json: %v", err)
}
testMethod(t, r, "POST")
if !reflect.DeepEqual(v, createRequest) {
t.Errorf("Request body = %+v, expected %+v", v, createRequest)
}
fmt.Fprintf(w, createJSON)
})
tag, _, err := client.Tags.Create(ctx, createRequest)
if err != nil {
t.Errorf("Tags.Create returned error: %v", err)
}
expected := &Tag{Name: "testing-1", Resources: &TaggedResources{Droplets: &TaggedDropletsResources{Count: 0, LastTagged: nil}}}
if !reflect.DeepEqual(tag, expected) {
t.Errorf("Tags.Create returned %+v, expected %+v", tag, expected)
}
}
func TestTags_Update(t *testing.T) {
setup()
defer teardown()
updateRequest := &TagUpdateRequest{
Name: "testing-1",
}
mux.HandleFunc("/v2/tags/old-testing-1", func(w http.ResponseWriter, r *http.Request) {
v := new(TagUpdateRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatalf("decode json: %v", err)
}
testMethod(t, r, "PUT")
if !reflect.DeepEqual(v, updateRequest) {
t.Errorf("Request body = %+v, expected %+v", v, updateRequest)
}
})
_, err := client.Tags.Update(ctx, "old-testing-1", updateRequest)
if err != nil {
t.Errorf("Tags.Update returned error: %v", err)
}
}
func TestTags_Delete(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v2/tags/testing-1", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "DELETE")
})
_, err := client.Tags.Delete(ctx, "testing-1")
if err != nil {
t.Errorf("Tags.Delete returned error: %v", err)
}
}
func TestTags_TagResource(t *testing.T) {
setup()
defer teardown()
tagResourcesRequest := &TagResourcesRequest{
Resources: []Resource{{ID: "1", Type: DropletResourceType}},
}
mux.HandleFunc("/v2/tags/testing-1/resources", func(w http.ResponseWriter, r *http.Request) {
v := new(TagResourcesRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatalf("decode json: %v", err)
}
testMethod(t, r, "POST")
if !reflect.DeepEqual(v, tagResourcesRequest) {
t.Errorf("Request body = %+v, expected %+v", v, tagResourcesRequest)
}
})
_, err := client.Tags.TagResources(ctx, "testing-1", tagResourcesRequest)
if err != nil {
t.Errorf("Tags.TagResources returned error: %v", err)
}
}
func TestTags_UntagResource(t *testing.T) {
setup()
defer teardown()
untagResourcesRequest := &UntagResourcesRequest{
Resources: []Resource{{ID: "1", Type: DropletResourceType}},
}
mux.HandleFunc("/v2/tags/testing-1/resources", func(w http.ResponseWriter, r *http.Request) {
v := new(UntagResourcesRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatalf("decode json: %v", err)
}
testMethod(t, r, "DELETE")
if !reflect.DeepEqual(v, untagResourcesRequest) {
t.Errorf("Request body = %+v, expected %+v", v, untagResourcesRequest)
}
})
_, err := client.Tags.UntagResources(ctx, "testing-1", untagResourcesRequest)
if err != nil {
t.Errorf("Tags.UntagResources returned error: %v", err)
}
}

View File

@ -88,6 +88,9 @@ func TestTimstamp_MarshalReflexivity(t *testing.T) {
}
var got Timestamp
err = json.Unmarshal(data, &got)
if err != nil {
t.Errorf("%s: Unmarshal err=%v", data, err)
}
if !got.Equal(tc.data) {
t.Errorf("%s: %+v != %+v", tc.desc, got, data)
}
@ -169,6 +172,9 @@ func TestWrappedTimestamp_MarshalReflexivity(t *testing.T) {
}
var got WrappedTimestamp
err = json.Unmarshal(bytes, &got)
if err != nil {
t.Errorf("%s: Unmarshal err=%v", bytes, err)
}
if !got.Time.Equal(tc.data.Time) {
t.Errorf("%s: %+v != %+v", tc.desc, got, tc.data)
}

View File

@ -1,6 +1,7 @@
package util
import (
"context"
"fmt"
"time"
@ -15,7 +16,7 @@ const (
)
// WaitForActive waits for a droplet to become active
func WaitForActive(client *godo.Client, monitorURI string) error {
func WaitForActive(ctx context.Context, client *godo.Client, monitorURI string) error {
if len(monitorURI) == 0 {
return fmt.Errorf("create had no monitor uri")
}
@ -23,9 +24,14 @@ func WaitForActive(client *godo.Client, monitorURI string) error {
completed := false
failCount := 0
for !completed {
action, _, err := client.DropletActions.GetByURI(monitorURI)
action, _, err := client.DropletActions.GetByURI(ctx, monitorURI)
if err != nil {
select {
case <-ctx.Done():
return err
default:
}
if failCount <= activeFailure {
failCount++
continue
@ -35,7 +41,11 @@ func WaitForActive(client *godo.Client, monitorURI string) error {
switch action.Status {
case godo.ActionInProgress:
time.Sleep(5 * time.Second)
select {
case <-time.After(5 * time.Second):
case <-ctx.Done():
return err
}
case godo.ActionCompleted:
completed = true
default:

View File

@ -1,6 +1,8 @@
package util
import (
"context"
"golang.org/x/oauth2"
"github.com/digitalocean/godo"
@ -19,7 +21,7 @@ func ExampleWaitForActive() {
uri := "https://api.digitalocean.com/v2/actions/xxxxxxxx"
// block until until the action is complete
err := WaitForActive(client, uri)
err := WaitForActive(context.TODO(), client, uri)
if err != nil {
panic(err)
}