From e5abd63eda99118a62d2220aa01b81f99d64e850 Mon Sep 17 00:00:00 2001 From: Dan Vittegleo Date: Tue, 6 Jun 2017 15:48:58 -0700 Subject: [PATCH] Add firewall configuration --- deploy.go | 100 +++ glide.lock | 7 +- handler.go | 18 +- .../github.com/digitalocean/godo/.travis.yml | 1 + .../github.com/digitalocean/godo/CHANGELOG.md | 20 + vendor/github.com/digitalocean/godo/README.md | 2 +- .../github.com/digitalocean/godo/account.go | 4 +- vendor/github.com/digitalocean/godo/action.go | 7 +- .../digitalocean/godo/certificates.go | 11 +- .../digitalocean/godo/context/context.go | 98 ++ .../digitalocean/godo/context/context_go17.go | 39 + .../godo/context/context_pre_go17.go | 41 + .../github.com/digitalocean/godo/domains.go | 23 +- .../digitalocean/godo/domains_test.go | 8 +- .../digitalocean/godo/droplet_actions.go | 51 +- .../digitalocean/godo/droplet_actions_test.go | 37 +- .../github.com/digitalocean/godo/droplets.go | 23 +- .../github.com/digitalocean/godo/firewalls.go | 263 ++++++ .../digitalocean/godo/firewalls_test.go | 839 ++++++++++++++++++ .../digitalocean/godo/floating_ips.go | 11 +- .../digitalocean/godo/floating_ips_actions.go | 9 +- vendor/github.com/digitalocean/godo/godo.go | 12 +- .../github.com/digitalocean/godo/godo_test.go | 15 +- .../digitalocean/godo/image_actions.go | 9 +- vendor/github.com/digitalocean/godo/images.go | 11 +- vendor/github.com/digitalocean/godo/keys.go | 15 +- vendor/github.com/digitalocean/godo/links.go | 3 +- .../digitalocean/godo/load_balancers.go | 21 +- .../github.com/digitalocean/godo/regions.go | 4 +- vendor/github.com/digitalocean/godo/sizes.go | 4 +- .../github.com/digitalocean/godo/snapshots.go | 9 +- .../digitalocean/godo/snapshots_test.go | 3 +- .../github.com/digitalocean/godo/storage.go | 20 +- .../digitalocean/godo/storage_actions.go | 18 +- .../digitalocean/godo/storage_actions_test.go | 30 - .../digitalocean/godo/storage_test.go | 55 ++ vendor/github.com/digitalocean/godo/tags.go | 42 +- .../github.com/digitalocean/godo/tags_test.go | 31 +- .../digitalocean/godo/util/droplet.go | 2 +- .../digitalocean/godo/util/droplet_test.go | 8 +- vpn.go | 2 +- 41 files changed, 1652 insertions(+), 274 deletions(-) create mode 100644 vendor/github.com/digitalocean/godo/CHANGELOG.md create mode 100644 vendor/github.com/digitalocean/godo/context/context.go create mode 100644 vendor/github.com/digitalocean/godo/context/context_go17.go create mode 100644 vendor/github.com/digitalocean/godo/context/context_pre_go17.go create mode 100644 vendor/github.com/digitalocean/godo/firewalls.go create mode 100644 vendor/github.com/digitalocean/godo/firewalls_test.go diff --git a/deploy.go b/deploy.go index 15628fd..f72376c 100644 --- a/deploy.go +++ b/deploy.go @@ -4,6 +4,9 @@ import ( "context" "errors" "fmt" + "log" + "sort" + "strings" "time" "github.com/digitalocean/godo" @@ -44,6 +47,46 @@ type options struct { dropletSize string } +func RemoveAllDroplets(token string) ([]string, error) { + oauthClient := oauth2.NewClient(oauth2.NoContext, oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: token}, + )) + client := godo.NewClient(oauthClient) + + droplets, _, err := client.Droplets.List(context.TODO(), nil) + if err != nil { + return nil, err + } + + // attempt removal of all dosxvpn droplets + removedDroplets := make([]string, 0) + for _, droplet := range droplets { + if strings.Contains(droplet.Name, "dosxvpn") { + _, err := client.Droplets.Delete(context.TODO(), droplet.ID) + if err != nil { + log.Println("Failed to remove droplet", droplet.Name, err) + } + removedDroplets = append(removedDroplets, droplet.Name) + } + } + sort.Strings(removedDroplets) + + // attempt removal of all dosxvpn firewalls + firewalls, _, err := client.Firewalls.List(context.TODO(), nil) + if err == nil { + for _, firewall := range firewalls { + if strings.Contains(firewall.Name, "dosxvpn") { + _, err := client.Firewalls.Delete(context.TODO(), firewall.ID) + if err != nil { + log.Println("Failed to remove firewall", firewall.Name, err) + } + } + } + } + + return removedDroplets, nil +} + func Deploy(accessToken string, opts ...Option) (*Droplet, error) { opt := options{ dropletName: dropletName, @@ -127,5 +170,62 @@ func Deploy(accessToken string, opts ...Option) (*Droplet, error) { return nil, fmt.Errorf("timeout waiting for provisioning of droplet %d", droplet.DropletID) } } + + fwRequest := &godo.FirewallRequest{ + Name: opt.dropletName, + InboundRules: []godo.InboundRule{ + { + Protocol: "tcp", + PortRange: "22", + Sources: &godo.Sources{ + Addresses: []string{"0.0.0.0/0", "::/0"}, + }, + }, + { + Protocol: "udp", + PortRange: "500", + Sources: &godo.Sources{ + Addresses: []string{"0.0.0.0/0", "::/0"}, + }, + }, + { + Protocol: "udp", + PortRange: "4500", + Sources: &godo.Sources{ + Addresses: []string{"0.0.0.0/0", "::/0"}, + }, + }, + }, + OutboundRules: []godo.OutboundRule{ + { + Protocol: "icmp", + Destinations: &godo.Destinations{ + Addresses: []string{"0.0.0.0/0", "::/0"}, + }, + }, + { + Protocol: "tcp", + PortRange: "all", + Destinations: &godo.Destinations{ + Addresses: []string{"0.0.0.0/0", "::/0"}, + }, + }, + { + Protocol: "udp", + PortRange: "all", + Destinations: &godo.Destinations{ + Addresses: []string{"0.0.0.0/0", "::/0"}, + }, + }, + }, + DropletIDs: []int{d.ID}, + } + + // Setup firewall + _, _, err = client.Firewalls.Create(context.TODO(), fwRequest) + if err != nil { + return nil, err + } + return droplet, nil } diff --git a/glide.lock b/glide.lock index cf071c8..6c69410 100644 --- a/glide.lock +++ b/glide.lock @@ -1,8 +1,10 @@ hash: dbfc15942294a55f0adfd0db0aecdceff543969760b44ac349f3bea75c221f2c -updated: 2017-04-05T13:39:26.541621844-07:00 +updated: 2017-06-06T10:59:28.201104696-07:00 imports: - name: github.com/digitalocean/godo - version: 84099941ba2381607e1b05ffd4822781af86675e + version: 4fa9e9d999007b447089056a1c581b5594f0f851 + subpackages: + - context - name: github.com/golang/protobuf version: 2bba0603135d7d7f5cb73b2125beeda19c09f4ef subpackages: @@ -24,6 +26,7 @@ imports: version: ffcf1bedda3b04ebb15a168a59800a73d6dc0f4d subpackages: - context + - context/ctxhttp - name: golang.org/x/oauth2 version: 7fdf09982454086d5570c7db3e11f360194830ca subpackages: diff --git a/handler.go b/handler.go index 26610f3..2dbfb2f 100644 --- a/handler.go +++ b/handler.go @@ -14,7 +14,6 @@ import ( "net/url" "os" "path" - "sort" "strings" "sync" "time" @@ -164,21 +163,10 @@ func (h *handler) uninstall(rw http.ResponseWriter, req *http.Request) { return } - oauthClient := oauth2.NewClient(oauth2.NoContext, oauth2.StaticTokenSource( - &oauth2.Token{AccessToken: token}, - )) - client := godo.NewClient(oauthClient) - - droplets, _, err := client.Droplets.List(context.TODO(), nil) - - removedDroplets := make([]string, 0) - for _, droplet := range droplets { - if strings.Contains(droplet.Name, "dosxvpn") { - client.Droplets.Delete(context.TODO(), droplet.ID) - removedDroplets = append(removedDroplets, droplet.Name) - } + removedDroplets, err := RemoveAllDroplets(token) + if err != nil { + fmt.Fprintf(os.Stderr, "executing template: %s", err.Error()) } - sort.Strings(removedDroplets) tmplData := struct { RemovedDroplets []string diff --git a/vendor/github.com/digitalocean/godo/.travis.yml b/vendor/github.com/digitalocean/godo/.travis.yml index f918f8d..7b27b09 100644 --- a/vendor/github.com/digitalocean/godo/.travis.yml +++ b/vendor/github.com/digitalocean/godo/.travis.yml @@ -1,5 +1,6 @@ language: go go: + - 1.6.3 - 1.7 - tip diff --git a/vendor/github.com/digitalocean/godo/CHANGELOG.md b/vendor/github.com/digitalocean/godo/CHANGELOG.md new file mode 100644 index 0000000..b89def7 --- /dev/null +++ b/vendor/github.com/digitalocean/godo/CHANGELOG.md @@ -0,0 +1,20 @@ +# Change Log + +## [v1.1.0] - 2017-06-06 + +### Added +- #145 Add FirewallsService for managing Firewalls with the DigitalOcean API. - @viola +- #139 Add TTL field to the Domains. - @xmudrii + +### Fixed +- #143 Fix oauth2.NoContext depreciation. - @jbowens +- #141 Fix DropletActions on tagged resources. - @xmudrii + +## [v1.0.0] - 2017-03-10 + +### Added +- #130 Add Convert to ImageActionsService. - @xmudrii +- #126 Add CertificatesService for managing certificates with the DigitalOcean API. - @viola +- #125 Add LoadBalancersService for managing load balancers with the DigitalOcean API. - @viola +- #122 Add GetVolumeByName to StorageService. - @protochron +- #113 Add context.Context to all calls. - @aybabtme diff --git a/vendor/github.com/digitalocean/godo/README.md b/vendor/github.com/digitalocean/godo/README.md index 4d5cdf8..44b2e4d 100644 --- a/vendor/github.com/digitalocean/godo/README.md +++ b/vendor/github.com/digitalocean/godo/README.md @@ -44,7 +44,7 @@ func (t *TokenSource) Token() (*oauth2.Token, error) { tokenSource := &TokenSource{ AccessToken: pat, } -oauthClient := oauth2.NewClient(oauth2.NoContext, tokenSource) +oauthClient := oauth2.NewClient(context.Background(), tokenSource) client := godo.NewClient(oauthClient) ``` diff --git a/vendor/github.com/digitalocean/godo/account.go b/vendor/github.com/digitalocean/godo/account.go index 18eed97..b68ff52 100644 --- a/vendor/github.com/digitalocean/godo/account.go +++ b/vendor/github.com/digitalocean/godo/account.go @@ -1,6 +1,6 @@ package godo -import "context" +import "github.com/digitalocean/godo/context" // AccountService is an interface for interfacing with the Account // endpoints of the DigitalOcean API @@ -47,7 +47,7 @@ func (s *AccountServiceOp) Get(ctx context.Context) (*Account, *Response, error) } root := new(accountRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } diff --git a/vendor/github.com/digitalocean/godo/action.go b/vendor/github.com/digitalocean/godo/action.go index 9baef21..05cd97c 100644 --- a/vendor/github.com/digitalocean/godo/action.go +++ b/vendor/github.com/digitalocean/godo/action.go @@ -1,8 +1,9 @@ package godo import ( - "context" "fmt" + + "github.com/digitalocean/godo/context" ) const ( @@ -66,7 +67,7 @@ func (s *ActionsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Action } root := new(actionsRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -90,7 +91,7 @@ func (s *ActionsServiceOp) Get(ctx context.Context, id int) (*Action, *Response, } root := new(actionRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } diff --git a/vendor/github.com/digitalocean/godo/certificates.go b/vendor/github.com/digitalocean/godo/certificates.go index b48e80e..f7d8c06 100644 --- a/vendor/github.com/digitalocean/godo/certificates.go +++ b/vendor/github.com/digitalocean/godo/certificates.go @@ -1,8 +1,9 @@ package godo import ( - "context" "path" + + "github.com/digitalocean/godo/context" ) const certificatesBasePath = "/v2/certificates" @@ -59,7 +60,7 @@ func (c *CertificatesServiceOp) Get(ctx context.Context, cID string) (*Certifica } root := new(certificateRoot) - resp, err := c.client.Do(req, root) + resp, err := c.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -80,7 +81,7 @@ func (c *CertificatesServiceOp) List(ctx context.Context, opt *ListOptions) ([]C } root := new(certificatesRoot) - resp, err := c.client.Do(req, root) + resp, err := c.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -99,7 +100,7 @@ func (c *CertificatesServiceOp) Create(ctx context.Context, cr *CertificateReque } root := new(certificateRoot) - resp, err := c.client.Do(req, root) + resp, err := c.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -116,5 +117,5 @@ func (c *CertificatesServiceOp) Delete(ctx context.Context, cID string) (*Respon return nil, err } - return c.client.Do(req, nil) + return c.client.Do(ctx, req, nil) } diff --git a/vendor/github.com/digitalocean/godo/context/context.go b/vendor/github.com/digitalocean/godo/context/context.go new file mode 100644 index 0000000..fe49b8d --- /dev/null +++ b/vendor/github.com/digitalocean/godo/context/context.go @@ -0,0 +1,98 @@ +package context + +import "time" + +// A Context carries a deadline, a cancelation signal, and other values across +// API boundaries. +// +// Context's methods may be called by multiple goroutines simultaneously. +type Context interface { + // Deadline returns the time when work done on behalf of this context + // should be canceled. Deadline returns ok==false when no deadline is + // set. Successive calls to Deadline return the same results. + Deadline() (deadline time.Time, ok bool) + + // Done returns a channel that's closed when work done on behalf of this + // context should be canceled. Done may return nil if this context can + // never be canceled. Successive calls to Done return the same value. + // + // WithCancel arranges for Done to be closed when cancel is called; + // WithDeadline arranges for Done to be closed when the deadline + // expires; WithTimeout arranges for Done to be closed when the timeout + // elapses. + // + // Done is provided for use in select statements:s + // + // // Stream generates values with DoSomething and sends them to out + // // until DoSomething returns an error or ctx.Done is closed. + // func Stream(ctx context.Context, out chan<- Value) error { + // for { + // v, err := DoSomething(ctx) + // if err != nil { + // return err + // } + // select { + // case <-ctx.Done(): + // return ctx.Err() + // case out <- v: + // } + // } + // } + // + // See http://blog.golang.org/pipelines for more examples of how to use + // a Done channel for cancelation. + Done() <-chan struct{} + + // Err returns a non-nil error value after Done is closed. Err returns + // Canceled if the context was canceled or DeadlineExceeded if the + // context's deadline passed. No other values for Err are defined. + // After Done is closed, successive calls to Err return the same value. + Err() error + + // Value returns the value associated with this context for key, or nil + // if no value is associated with key. Successive calls to Value with + // the same key returns the same result. + // + // Use context values only for request-scoped data that transits + // processes and API boundaries, not for passing optional parameters to + // functions. + // + // A key identifies a specific value in a Context. Functions that wish + // to store values in Context typically allocate a key in a global + // variable then use that key as the argument to context.WithValue and + // Context.Value. A key can be any type that supports equality; + // packages should define keys as an unexported type to avoid + // collisions. + // + // Packages that define a Context key should provide type-safe accessors + // for the values stores using that key: + // + // // Package user defines a User type that's stored in Contexts. + // package user + // + // import "golang.org/x/net/context" + // + // // User is the type of value stored in the Contexts. + // type User struct {...} + // + // // key is an unexported type for keys defined in this package. + // // This prevents collisions with keys defined in other packages. + // type key int + // + // // userKey is the key for user.User values in Contexts. It is + // // unexported; clients use user.NewContext and user.FromContext + // // instead of using this key directly. + // var userKey key = 0 + // + // // NewContext returns a new Context that carries value u. + // func NewContext(ctx context.Context, u *User) context.Context { + // return context.WithValue(ctx, userKey, u) + // } + // + // // FromContext returns the User value stored in ctx, if any. + // func FromContext(ctx context.Context) (*User, bool) { + // u, ok := ctx.Value(userKey).(*User) + // return u, ok + // } + Value(key interface{}) interface{} +} diff --git a/vendor/github.com/digitalocean/godo/context/context_go17.go b/vendor/github.com/digitalocean/godo/context/context_go17.go new file mode 100644 index 0000000..d5359de --- /dev/null +++ b/vendor/github.com/digitalocean/godo/context/context_go17.go @@ -0,0 +1,39 @@ +// +build go1.7 + +package context + +import ( + "context" + "net/http" +) + +// DoRequest submits an HTTP request. +func DoRequest(ctx Context, req *http.Request) (*http.Response, error) { + return DoRequestWithClient(ctx, http.DefaultClient, req) +} + +// DoRequestWithClient submits an HTTP request using the specified client. +func DoRequestWithClient( + ctx Context, + client *http.Client, + req *http.Request) (*http.Response, error) { + req = req.WithContext(ctx) + return client.Do(req) +} + +// TODO returns a non-nil, empty Context. Code should use context.TODO when +// it's unclear which Context to use or it is not yet available (because the +// surrounding function has not yet been extended to accept a Context +// parameter). TODO is recognized by static analysis tools that determine +// whether Contexts are propagated correctly in a program. +func TODO() Context { + return context.TODO() +} + +// Background returns a non-nil, empty Context. It is never canceled, has no +// values, and has no deadline. It is typically used by the main function, +// initialization, and tests, and as the top-level Context for incoming +// requests. +func Background() Context { + return context.Background() +} diff --git a/vendor/github.com/digitalocean/godo/context/context_pre_go17.go b/vendor/github.com/digitalocean/godo/context/context_pre_go17.go new file mode 100644 index 0000000..e30adb0 --- /dev/null +++ b/vendor/github.com/digitalocean/godo/context/context_pre_go17.go @@ -0,0 +1,41 @@ +// +build !go1.7 + +package context + +import ( + "net/http" + + "golang.org/x/net/context" + "golang.org/x/net/context/ctxhttp" +) + +// DoRequest submits an HTTP request. +func DoRequest(ctx Context, req *http.Request) (*http.Response, error) { + return DoRequestWithClient(ctx, http.DefaultClient, req) +} + +// DoRequestWithClient submits an HTTP request using the specified client. +func DoRequestWithClient( + ctx Context, + client *http.Client, + req *http.Request) (*http.Response, error) { + + return ctxhttp.Do(ctx, client, req) +} + +// TODO returns a non-nil, empty Context. Code should use context.TODO when +// it's unclear which Context to use or it is not yet available (because the +// surrounding function has not yet been extended to accept a Context +// parameter). TODO is recognized by static analysis tools that determine +// whether Contexts are propagated correctly in a program. +func TODO() Context { + return context.TODO() +} + +// Background returns a non-nil, empty Context. It is never canceled, has no +// values, and has no deadline. It is typically used by the main function, +// initialization, and tests, and as the top-level Context for incoming +// requests. +func Background() Context { + return context.Background() +} diff --git a/vendor/github.com/digitalocean/godo/domains.go b/vendor/github.com/digitalocean/godo/domains.go index fdc2a84..542a27b 100644 --- a/vendor/github.com/digitalocean/godo/domains.go +++ b/vendor/github.com/digitalocean/godo/domains.go @@ -1,8 +1,9 @@ package godo import ( - "context" "fmt" + + "github.com/digitalocean/godo/context" ) const domainsBasePath = "v2/domains" @@ -73,6 +74,7 @@ type DomainRecord struct { Data string `json:"data,omitempty"` Priority int `json:"priority,omitempty"` Port int `json:"port,omitempty"` + TTL int `json:"ttl,omitempty"` Weight int `json:"weight,omitempty"` } @@ -83,6 +85,7 @@ type DomainRecordEditRequest struct { Data string `json:"data,omitempty"` Priority int `json:"priority,omitempty"` Port int `json:"port,omitempty"` + TTL int `json:"ttl,omitempty"` Weight int `json:"weight,omitempty"` } @@ -104,7 +107,7 @@ func (s DomainsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Domain, } root := new(domainsRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -129,7 +132,7 @@ func (s *DomainsServiceOp) Get(ctx context.Context, name string) (*Domain, *Resp } root := new(domainRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -151,7 +154,7 @@ func (s *DomainsServiceOp) Create(ctx context.Context, createRequest *DomainCrea } root := new(domainRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -171,7 +174,7 @@ func (s *DomainsServiceOp) Delete(ctx context.Context, name string) (*Response, return nil, err } - resp, err := s.client.Do(req, nil) + resp, err := s.client.Do(ctx, req, nil) return resp, err } @@ -204,7 +207,7 @@ func (s *DomainsServiceOp) Records(ctx context.Context, domain string, opt *List } root := new(domainRecordsRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -233,7 +236,7 @@ func (s *DomainsServiceOp) Record(ctx context.Context, domain string, id int) (* } record := new(domainRecordRoot) - resp, err := s.client.Do(req, record) + resp, err := s.client.Do(ctx, req, record) if err != nil { return nil, resp, err } @@ -258,7 +261,7 @@ func (s *DomainsServiceOp) DeleteRecord(ctx context.Context, domain string, id i return nil, err } - resp, err := s.client.Do(req, nil) + resp, err := s.client.Do(ctx, req, nil) return resp, err } @@ -289,7 +292,7 @@ func (s *DomainsServiceOp) EditRecord(ctx context.Context, } d := new(DomainRecord) - resp, err := s.client.Do(req, d) + resp, err := s.client.Do(ctx, req, d) if err != nil { return nil, resp, err } @@ -317,7 +320,7 @@ func (s *DomainsServiceOp) CreateRecord(ctx context.Context, } d := new(domainRecordRoot) - resp, err := s.client.Do(req, d) + resp, err := s.client.Do(ctx, req, d) if err != nil { return nil, resp, err } diff --git a/vendor/github.com/digitalocean/godo/domains_test.go b/vendor/github.com/digitalocean/godo/domains_test.go index 851b303..77ebd2e 100644 --- a/vendor/github.com/digitalocean/godo/domains_test.go +++ b/vendor/github.com/digitalocean/godo/domains_test.go @@ -234,6 +234,7 @@ func TestDomains_CreateRecordForDomainName(t *testing.T) { Data: "@", Priority: 10, Port: 10, + TTL: 1800, Weight: 10, } @@ -275,6 +276,7 @@ func TestDomains_EditRecordForDomainName(t *testing.T) { Data: "@", Priority: 10, Port: 10, + TTL: 1800, Weight: 10, } @@ -312,11 +314,12 @@ func TestDomainRecord_String(t *testing.T) { Data: "@", Priority: 10, Port: 10, + TTL: 1800, Weight: 10, } stringified := record.String() - expected := `godo.DomainRecord{ID:1, Type:"CNAME", Name:"example", Data:"@", Priority:10, Port:10, Weight:10}` + expected := `godo.DomainRecord{ID:1, Type:"CNAME", Name:"example", Data:"@", Priority:10, Port:10, TTL:1800, Weight:10}` if expected != stringified { t.Errorf("DomainRecord.String returned %+v, expected %+v", stringified, expected) } @@ -329,11 +332,12 @@ func TestDomainRecordEditRequest_String(t *testing.T) { Data: "@", Priority: 10, Port: 10, + TTL: 1800, Weight: 10, } stringified := record.String() - expected := `godo.DomainRecordEditRequest{Type:"CNAME", Name:"example", Data:"@", Priority:10, Port:10, Weight:10}` + expected := `godo.DomainRecordEditRequest{Type:"CNAME", Name:"example", Data:"@", Priority:10, Port:10, TTL:1800, Weight:10}` if expected != stringified { t.Errorf("DomainRecordEditRequest.String returned %+v, expected %+v", stringified, expected) } diff --git a/vendor/github.com/digitalocean/godo/droplet_actions.go b/vendor/github.com/digitalocean/godo/droplet_actions.go index cd8b94e..4902fd6 100644 --- a/vendor/github.com/digitalocean/godo/droplet_actions.go +++ b/vendor/github.com/digitalocean/godo/droplet_actions.go @@ -1,9 +1,10 @@ package godo import ( - "context" "fmt" "net/url" + + "github.com/digitalocean/godo/context" ) // ActionRequest reprents DigitalOcean Action Request @@ -14,31 +15,31 @@ type ActionRequest map[string]interface{} // See: https://developers.digitalocean.com/documentation/v2#droplet-actions type DropletActionsService interface { Shutdown(context.Context, int) (*Action, *Response, error) - ShutdownByTag(context.Context, string) (*Action, *Response, error) + ShutdownByTag(context.Context, string) ([]Action, *Response, error) PowerOff(context.Context, int) (*Action, *Response, error) - PowerOffByTag(context.Context, string) (*Action, *Response, error) + PowerOffByTag(context.Context, string) ([]Action, *Response, error) PowerOn(context.Context, int) (*Action, *Response, error) - PowerOnByTag(context.Context, string) (*Action, *Response, error) + PowerOnByTag(context.Context, string) ([]Action, *Response, error) PowerCycle(context.Context, int) (*Action, *Response, error) - PowerCycleByTag(context.Context, string) (*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) + SnapshotByTag(context.Context, string, string) ([]Action, *Response, error) EnableBackups(context.Context, int) (*Action, *Response, error) - EnableBackupsByTag(context.Context, string) (*Action, *Response, error) + EnableBackupsByTag(context.Context, string) ([]Action, *Response, error) DisableBackups(context.Context, int) (*Action, *Response, error) - DisableBackupsByTag(context.Context, string) (*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) + EnableIPv6ByTag(context.Context, string) ([]Action, *Response, error) EnablePrivateNetworking(context.Context, int) (*Action, *Response, error) - EnablePrivateNetworkingByTag(context.Context, string) (*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) @@ -59,7 +60,7 @@ func (s *DropletActionsServiceOp) Shutdown(ctx context.Context, id int) (*Action } // ShutdownByTag shuts down Droplets matched by a Tag. -func (s *DropletActionsServiceOp) ShutdownByTag(ctx context.Context, tag string) (*Action, *Response, error) { +func (s *DropletActionsServiceOp) ShutdownByTag(ctx context.Context, tag string) ([]Action, *Response, error) { request := &ActionRequest{"type": "shutdown"} return s.doActionByTag(ctx, tag, request) } @@ -71,7 +72,7 @@ func (s *DropletActionsServiceOp) PowerOff(ctx context.Context, id int) (*Action } // PowerOffByTag powers off Droplets matched by a Tag. -func (s *DropletActionsServiceOp) PowerOffByTag(ctx context.Context, tag string) (*Action, *Response, error) { +func (s *DropletActionsServiceOp) PowerOffByTag(ctx context.Context, tag string) ([]Action, *Response, error) { request := &ActionRequest{"type": "power_off"} return s.doActionByTag(ctx, tag, request) } @@ -83,7 +84,7 @@ func (s *DropletActionsServiceOp) PowerOn(ctx context.Context, id int) (*Action, } // PowerOnByTag powers on Droplets matched by a Tag. -func (s *DropletActionsServiceOp) PowerOnByTag(ctx context.Context, tag string) (*Action, *Response, error) { +func (s *DropletActionsServiceOp) PowerOnByTag(ctx context.Context, tag string) ([]Action, *Response, error) { request := &ActionRequest{"type": "power_on"} return s.doActionByTag(ctx, tag, request) } @@ -95,7 +96,7 @@ func (s *DropletActionsServiceOp) PowerCycle(ctx context.Context, id int) (*Acti } // PowerCycleByTag power cycles Droplets matched by a Tag. -func (s *DropletActionsServiceOp) PowerCycleByTag(ctx context.Context, tag string) (*Action, *Response, error) { +func (s *DropletActionsServiceOp) PowerCycleByTag(ctx context.Context, tag string) ([]Action, *Response, error) { request := &ActionRequest{"type": "power_cycle"} return s.doActionByTag(ctx, tag, request) } @@ -148,7 +149,7 @@ func (s *DropletActionsServiceOp) Snapshot(ctx context.Context, id int, name str } // SnapshotByTag snapshots Droplets matched by a Tag. -func (s *DropletActionsServiceOp) SnapshotByTag(ctx context.Context, tag string, name string) (*Action, *Response, error) { +func (s *DropletActionsServiceOp) SnapshotByTag(ctx context.Context, tag string, name string) ([]Action, *Response, error) { requestType := "snapshot" request := &ActionRequest{ "type": requestType, @@ -164,7 +165,7 @@ func (s *DropletActionsServiceOp) EnableBackups(ctx context.Context, id int) (*A } // EnableBackupsByTag enables backups for Droplets matched by a Tag. -func (s *DropletActionsServiceOp) EnableBackupsByTag(ctx context.Context, tag string) (*Action, *Response, error) { +func (s *DropletActionsServiceOp) EnableBackupsByTag(ctx context.Context, tag string) ([]Action, *Response, error) { request := &ActionRequest{"type": "enable_backups"} return s.doActionByTag(ctx, tag, request) } @@ -176,7 +177,7 @@ func (s *DropletActionsServiceOp) DisableBackups(ctx context.Context, id int) (* } // DisableBackupsByTag disables backups for Droplet matched by a Tag. -func (s *DropletActionsServiceOp) DisableBackupsByTag(ctx context.Context, tag string) (*Action, *Response, error) { +func (s *DropletActionsServiceOp) DisableBackupsByTag(ctx context.Context, tag string) ([]Action, *Response, error) { request := &ActionRequest{"type": "disable_backups"} return s.doActionByTag(ctx, tag, request) } @@ -212,7 +213,7 @@ func (s *DropletActionsServiceOp) EnableIPv6(ctx context.Context, id int) (*Acti } // EnableIPv6ByTag enables IPv6 for Droplets matched by a Tag. -func (s *DropletActionsServiceOp) EnableIPv6ByTag(ctx context.Context, tag string) (*Action, *Response, error) { +func (s *DropletActionsServiceOp) EnableIPv6ByTag(ctx context.Context, tag string) ([]Action, *Response, error) { request := &ActionRequest{"type": "enable_ipv6"} return s.doActionByTag(ctx, tag, request) } @@ -224,7 +225,7 @@ func (s *DropletActionsServiceOp) EnablePrivateNetworking(ctx context.Context, i } // EnablePrivateNetworkingByTag enables private networking for Droplets matched by a Tag. -func (s *DropletActionsServiceOp) EnablePrivateNetworkingByTag(ctx context.Context, tag string) (*Action, *Response, error) { +func (s *DropletActionsServiceOp) EnablePrivateNetworkingByTag(ctx context.Context, tag string) ([]Action, *Response, error) { request := &ActionRequest{"type": "enable_private_networking"} return s.doActionByTag(ctx, tag, request) } @@ -252,7 +253,7 @@ func (s *DropletActionsServiceOp) doAction(ctx context.Context, id int, request } root := new(actionRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -260,7 +261,7 @@ func (s *DropletActionsServiceOp) doAction(ctx context.Context, id int, request return root.Event, resp, err } -func (s *DropletActionsServiceOp) doActionByTag(ctx context.Context, tag string, request *ActionRequest) (*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") } @@ -276,13 +277,13 @@ func (s *DropletActionsServiceOp) doActionByTag(ctx context.Context, tag string, return nil, nil, err } - root := new(actionRoot) - resp, err := s.client.Do(req, root) + root := new(actionsRoot) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } - return root.Event, resp, err + return root.Actions, resp, err } // Get an action for a particular Droplet by id. @@ -317,7 +318,7 @@ func (s *DropletActionsServiceOp) get(ctx context.Context, path string) (*Action } root := new(actionRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } diff --git a/vendor/github.com/digitalocean/godo/droplet_actions_test.go b/vendor/github.com/digitalocean/godo/droplet_actions_test.go index d48f260..942f5d5 100644 --- a/vendor/github.com/digitalocean/godo/droplet_actions_test.go +++ b/vendor/github.com/digitalocean/godo/droplet_actions_test.go @@ -66,7 +66,7 @@ func TestDropletActions_ShutdownByTag(t *testing.T) { t.Errorf("Request body = %+v, expected %+v", v, request) } - fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`) + fmt.Fprint(w, `{"actions": [{"status":"in-progress"},{"status":"in-progress"}]}`) }) action, _, err := client.DropletActions.ShutdownByTag(ctx, "testing-1") @@ -74,7 +74,7 @@ func TestDropletActions_ShutdownByTag(t *testing.T) { t.Errorf("DropletActions.ShutdownByTag returned error: %v", err) } - expected := &Action{Status: "in-progress"} + expected := []Action{{Status: "in-progress"}, {Status: "in-progress"}} if !reflect.DeepEqual(action, expected) { t.Errorf("DropletActions.ShutdownByTag returned %+v, expected %+v", action, expected) } @@ -138,7 +138,7 @@ func TestDropletAction_PowerOffByTag(t *testing.T) { t.Errorf("Request body = %+v, expected %+v", v, request) } - fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`) + fmt.Fprint(w, `{"actions": [{"status":"in-progress"},{"status":"in-progress"}]}`) }) action, _, err := client.DropletActions.PowerOffByTag(ctx, "testing-1") @@ -146,7 +146,7 @@ func TestDropletAction_PowerOffByTag(t *testing.T) { t.Errorf("DropletActions.PowerOffByTag returned error: %v", err) } - expected := &Action{Status: "in-progress"} + expected := []Action{{Status: "in-progress"}, {Status: "in-progress"}} if !reflect.DeepEqual(action, expected) { t.Errorf("DropletActions.PoweroffByTag returned %+v, expected %+v", action, expected) } @@ -210,7 +210,7 @@ func TestDropletAction_PowerOnByTag(t *testing.T) { t.Errorf("Request body = %+v, expected %+v", v, request) } - fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`) + fmt.Fprint(w, `{"actions": [{"status":"in-progress"},{"status":"in-progress"}]}`) }) action, _, err := client.DropletActions.PowerOnByTag(ctx, "testing-1") @@ -218,7 +218,7 @@ func TestDropletAction_PowerOnByTag(t *testing.T) { t.Errorf("DropletActions.PowerOnByTag returned error: %v", err) } - expected := &Action{Status: "in-progress"} + expected := []Action{{Status: "in-progress"}, {Status: "in-progress"}} if !reflect.DeepEqual(action, expected) { t.Errorf("DropletActions.PowerOnByTag returned %+v, expected %+v", action, expected) } @@ -428,8 +428,7 @@ func TestDropletAction_PowerCycleByTag(t *testing.T) { t.Errorf("Request body = %+v, expected %+v", v, request) } - fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`) - + fmt.Fprint(w, `{"actions": [{"status":"in-progress"},{"status":"in-progress"}]}`) }) action, _, err := client.DropletActions.PowerCycleByTag(ctx, "testing-1") @@ -437,7 +436,7 @@ func TestDropletAction_PowerCycleByTag(t *testing.T) { t.Errorf("DropletActions.PowerCycleByTag returned error: %v", err) } - expected := &Action{Status: "in-progress"} + expected := []Action{{Status: "in-progress"}, {Status: "in-progress"}} if !reflect.DeepEqual(action, expected) { t.Errorf("DropletActions.PowerCycleByTag returned %+v, expected %+v", action, expected) } @@ -505,7 +504,7 @@ func TestDropletAction_SnapshotByTag(t *testing.T) { t.Errorf("Request body = %+v, expected %+v", v, request) } - fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`) + fmt.Fprint(w, `{"actions": [{"status":"in-progress"},{"status":"in-progress"}]}`) }) action, _, err := client.DropletActions.SnapshotByTag(ctx, "testing-1", "Image-Name") @@ -513,7 +512,7 @@ func TestDropletAction_SnapshotByTag(t *testing.T) { t.Errorf("DropletActions.SnapshotByTag returned error: %v", err) } - expected := &Action{Status: "in-progress"} + expected := []Action{{Status: "in-progress"}, {Status: "in-progress"}} if !reflect.DeepEqual(action, expected) { t.Errorf("DropletActions.SnapshotByTag returned %+v, expected %+v", action, expected) } @@ -579,7 +578,7 @@ func TestDropletAction_EnableBackupsByTag(t *testing.T) { t.Errorf("Request body = %+v, expected %+v", v, request) } - fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`) + fmt.Fprint(w, `{"actions": [{"status":"in-progress"},{"status":"in-progress"}]}`) }) action, _, err := client.DropletActions.EnableBackupsByTag(ctx, "testing-1") @@ -587,7 +586,7 @@ func TestDropletAction_EnableBackupsByTag(t *testing.T) { t.Errorf("DropletActions.EnableBackupsByTag returned error: %v", err) } - expected := &Action{Status: "in-progress"} + expected := []Action{{Status: "in-progress"}, {Status: "in-progress"}} if !reflect.DeepEqual(action, expected) { t.Errorf("DropletActions.EnableBackupsByTag returned %+v, expected %+v", action, expected) } @@ -653,7 +652,7 @@ func TestDropletAction_DisableBackupsByTag(t *testing.T) { t.Errorf("Request body = %+v, expected %+v", v, request) } - fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`) + fmt.Fprint(w, `{"actions": [{"status":"in-progress"},{"status":"in-progress"}]}`) }) action, _, err := client.DropletActions.DisableBackupsByTag(ctx, "testing-1") @@ -661,7 +660,7 @@ func TestDropletAction_DisableBackupsByTag(t *testing.T) { t.Errorf("DropletActions.DisableBackupsByTag returned error: %v", err) } - expected := &Action{Status: "in-progress"} + expected := []Action{{Status: "in-progress"}, {Status: "in-progress"}} if !reflect.DeepEqual(action, expected) { t.Errorf("DropletActions.DisableBackupsByTag returned %+v, expected %+v", action, expected) } @@ -870,7 +869,7 @@ func TestDropletAction_EnableIPv6ByTag(t *testing.T) { t.Errorf("Request body = %+v, expected %+v", v, request) } - fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`) + fmt.Fprint(w, `{"actions": [{"status":"in-progress"},{"status":"in-progress"}]}`) }) action, _, err := client.DropletActions.EnableIPv6ByTag(ctx, "testing-1") @@ -878,7 +877,7 @@ func TestDropletAction_EnableIPv6ByTag(t *testing.T) { t.Errorf("DropletActions.EnableIPv6ByTag returned error: %v", err) } - expected := &Action{Status: "in-progress"} + expected := []Action{{Status: "in-progress"}, {Status: "in-progress"}} if !reflect.DeepEqual(action, expected) { t.Errorf("DropletActions.EnableIPv6byTag returned %+v, expected %+v", action, expected) } @@ -944,7 +943,7 @@ func TestDropletAction_EnablePrivateNetworkingByTag(t *testing.T) { t.Errorf("Request body = %+v, expected %+v", v, request) } - fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`) + fmt.Fprint(w, `{"actions": [{"status":"in-progress"},{"status":"in-progress"}]}`) }) action, _, err := client.DropletActions.EnablePrivateNetworkingByTag(ctx, "testing-1") @@ -952,7 +951,7 @@ func TestDropletAction_EnablePrivateNetworkingByTag(t *testing.T) { t.Errorf("DropletActions.EnablePrivateNetworkingByTag returned error: %v", err) } - expected := &Action{Status: "in-progress"} + expected := []Action{{Status: "in-progress"}, {Status: "in-progress"}} if !reflect.DeepEqual(action, expected) { t.Errorf("DropletActions.EnablePrivateNetworkingByTag returned %+v, expected %+v", action, expected) } diff --git a/vendor/github.com/digitalocean/godo/droplets.go b/vendor/github.com/digitalocean/godo/droplets.go index b145d98..443d5db 100644 --- a/vendor/github.com/digitalocean/godo/droplets.go +++ b/vendor/github.com/digitalocean/godo/droplets.go @@ -1,10 +1,11 @@ package godo import ( - "context" "encoding/json" "errors" "fmt" + + "github.com/digitalocean/godo/context" ) const dropletBasePath = "v2/droplets" @@ -280,7 +281,7 @@ func (s *DropletsServiceOp) list(ctx context.Context, path string) ([]Droplet, * } root := new(dropletsRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -327,7 +328,7 @@ func (s *DropletsServiceOp) Get(ctx context.Context, dropletID int) (*Droplet, * } root := new(dropletRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -349,7 +350,7 @@ func (s *DropletsServiceOp) Create(ctx context.Context, createRequest *DropletCr } root := new(dropletRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -374,7 +375,7 @@ func (s *DropletsServiceOp) CreateMultiple(ctx context.Context, createRequest *D } root := new(dropletsRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -392,7 +393,7 @@ func (s *DropletsServiceOp) delete(ctx context.Context, path string) (*Response, return nil, err } - resp, err := s.client.Do(req, nil) + resp, err := s.client.Do(ctx, req, nil) return resp, err } @@ -437,7 +438,7 @@ func (s *DropletsServiceOp) Kernels(ctx context.Context, dropletID int, opt *Lis } root := new(kernelsRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if l := root.Links; l != nil { resp.Links = l } @@ -463,7 +464,7 @@ func (s *DropletsServiceOp) Actions(ctx context.Context, dropletID int, opt *Lis } root := new(actionsRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -492,7 +493,7 @@ func (s *DropletsServiceOp) Backups(ctx context.Context, dropletID int, opt *Lis } root := new(backupsRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -521,7 +522,7 @@ func (s *DropletsServiceOp) Snapshots(ctx context.Context, dropletID int, opt *L } root := new(dropletSnapshotsRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -546,7 +547,7 @@ func (s *DropletsServiceOp) Neighbors(ctx context.Context, dropletID int) ([]Dro } root := new(dropletsRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } diff --git a/vendor/github.com/digitalocean/godo/firewalls.go b/vendor/github.com/digitalocean/godo/firewalls.go new file mode 100644 index 0000000..661f944 --- /dev/null +++ b/vendor/github.com/digitalocean/godo/firewalls.go @@ -0,0 +1,263 @@ +package godo + +import ( + "path" + "strconv" + + "github.com/digitalocean/godo/context" +) + +const firewallsBasePath = "/v2/firewalls" + +// FirewallsService is an interface for managing Firewalls with the DigitalOcean API. +// See: https://developers.digitalocean.com/documentation/documentation/v2/#firewalls +type FirewallsService interface { + Get(context.Context, string) (*Firewall, *Response, error) + Create(context.Context, *FirewallRequest) (*Firewall, *Response, error) + Update(context.Context, string, *FirewallRequest) (*Firewall, *Response, error) + Delete(context.Context, string) (*Response, error) + List(context.Context, *ListOptions) ([]Firewall, *Response, error) + ListByDroplet(context.Context, int, *ListOptions) ([]Firewall, *Response, error) + AddDroplets(context.Context, string, ...int) (*Response, error) + RemoveDroplets(context.Context, string, ...int) (*Response, error) + AddTags(context.Context, string, ...string) (*Response, error) + RemoveTags(context.Context, string, ...string) (*Response, error) + AddRules(context.Context, string, *FirewallRulesRequest) (*Response, error) + RemoveRules(context.Context, string, *FirewallRulesRequest) (*Response, error) +} + +// FirewallsServiceOp handles communication with Firewalls methods of the DigitalOcean API. +type FirewallsServiceOp struct { + client *Client +} + +// Firewall represents a DigitalOcean Firewall configuration. +type Firewall struct { + ID string `json:"id"` + Name string `json:"name"` + Status string `json:"status"` + InboundRules []InboundRule `json:"inbound_rules"` + OutboundRules []OutboundRule `json:"outbound_rules"` + DropletIDs []int `json:"droplet_ids"` + Tags []string `json:"tags"` + Created string `json:"created_at"` + PendingChanges []PendingChange `json:"pending_changes"` +} + +// String creates a human-readable description of a Firewall. +func (fw Firewall) String() string { + return Stringify(fw) +} + +// FirewallRequest represents the configuration to be applied to an existing or a new Firewall. +type FirewallRequest struct { + Name string `json:"name"` + InboundRules []InboundRule `json:"inbound_rules"` + OutboundRules []OutboundRule `json:"outbound_rules"` + DropletIDs []int `json:"droplet_ids"` + Tags []string `json:"tags"` +} + +// FirewallRulesRequest represents rules configuration to be applied to an existing Firewall. +type FirewallRulesRequest struct { + InboundRules []InboundRule `json:"inbound_rules"` + OutboundRules []OutboundRule `json:"outbound_rules"` +} + +// InboundRule represents a DigitalOcean Firewall inbound rule. +type InboundRule struct { + Protocol string `json:"protocol,omitempty"` + PortRange string `json:"ports,omitempty"` + Sources *Sources `json:"sources"` +} + +// OutboundRule represents a DigitalOcean Firewall outbound rule. +type OutboundRule struct { + Protocol string `json:"protocol,omitempty"` + PortRange string `json:"ports,omitempty"` + Destinations *Destinations `json:"destinations"` +} + +// Sources represents a DigitalOcean Firewall InboundRule sources. +type Sources struct { + Addresses []string `json:"addresses,omitempty"` + Tags []string `json:"tags,omitempty"` + DropletIDs []int `json:"droplet_ids,omitempty"` + LoadBalancerUIDs []string `json:"load_balancer_uids,omitempty"` +} + +// PendingChange represents a DigitalOcean Firewall status details. +type PendingChange struct { + DropletID int `json:"droplet_id,omitempty"` + Removing bool `json:"removing,omitempty"` + Status string `json:"status,omitempty"` +} + +// Destinations represents a DigitalOcean Firewall OutboundRule destinations. +type Destinations struct { + Addresses []string `json:"addresses,omitempty"` + Tags []string `json:"tags,omitempty"` + DropletIDs []int `json:"droplet_ids,omitempty"` + LoadBalancerUIDs []string `json:"load_balancer_uids,omitempty"` +} + +var _ FirewallsService = &FirewallsServiceOp{} + +// Get an existing Firewall by its identifier. +func (fw *FirewallsServiceOp) Get(ctx context.Context, fID string) (*Firewall, *Response, error) { + path := path.Join(firewallsBasePath, fID) + + req, err := fw.client.NewRequest(ctx, "GET", path, nil) + if err != nil { + return nil, nil, err + } + + root := new(firewallRoot) + resp, err := fw.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root.Firewall, resp, err +} + +// Create a new Firewall with a given configuration. +func (fw *FirewallsServiceOp) Create(ctx context.Context, fr *FirewallRequest) (*Firewall, *Response, error) { + req, err := fw.client.NewRequest(ctx, "POST", firewallsBasePath, fr) + if err != nil { + return nil, nil, err + } + + root := new(firewallRoot) + resp, err := fw.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root.Firewall, resp, err +} + +// Update an existing Firewall with new configuration. +func (fw *FirewallsServiceOp) Update(ctx context.Context, fID string, fr *FirewallRequest) (*Firewall, *Response, error) { + path := path.Join(firewallsBasePath, fID) + + req, err := fw.client.NewRequest(ctx, "PUT", path, fr) + if err != nil { + return nil, nil, err + } + + root := new(firewallRoot) + resp, err := fw.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root.Firewall, resp, err +} + +// Delete a Firewall by its identifier. +func (fw *FirewallsServiceOp) Delete(ctx context.Context, fID string) (*Response, error) { + path := path.Join(firewallsBasePath, fID) + return fw.createAndDoReq(ctx, "DELETE", path, nil) +} + +// List Firewalls. +func (fw *FirewallsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Firewall, *Response, error) { + path, err := addOptions(firewallsBasePath, opt) + if err != nil { + return nil, nil, err + } + + return fw.listHelper(ctx, path) +} + +// ListByDroplet Firewalls. +func (fw *FirewallsServiceOp) ListByDroplet(ctx context.Context, dID int, opt *ListOptions) ([]Firewall, *Response, error) { + basePath := path.Join(dropletBasePath, strconv.Itoa(dID), "firewalls") + path, err := addOptions(basePath, opt) + if err != nil { + return nil, nil, err + } + + return fw.listHelper(ctx, path) +} + +// AddDroplets to a Firewall. +func (fw *FirewallsServiceOp) AddDroplets(ctx context.Context, fID string, dropletIDs ...int) (*Response, error) { + path := path.Join(firewallsBasePath, fID, "droplets") + return fw.createAndDoReq(ctx, "POST", path, &dropletsRequest{IDs: dropletIDs}) +} + +// RemoveDroplets from a Firewall. +func (fw *FirewallsServiceOp) RemoveDroplets(ctx context.Context, fID string, dropletIDs ...int) (*Response, error) { + path := path.Join(firewallsBasePath, fID, "droplets") + return fw.createAndDoReq(ctx, "DELETE", path, &dropletsRequest{IDs: dropletIDs}) +} + +// AddTags to a Firewall. +func (fw *FirewallsServiceOp) AddTags(ctx context.Context, fID string, tags ...string) (*Response, error) { + path := path.Join(firewallsBasePath, fID, "tags") + return fw.createAndDoReq(ctx, "POST", path, &tagsRequest{Tags: tags}) +} + +// RemoveTags from a Firewall. +func (fw *FirewallsServiceOp) RemoveTags(ctx context.Context, fID string, tags ...string) (*Response, error) { + path := path.Join(firewallsBasePath, fID, "tags") + return fw.createAndDoReq(ctx, "DELETE", path, &tagsRequest{Tags: tags}) +} + +// AddRules to a Firewall. +func (fw *FirewallsServiceOp) AddRules(ctx context.Context, fID string, rr *FirewallRulesRequest) (*Response, error) { + path := path.Join(firewallsBasePath, fID, "rules") + return fw.createAndDoReq(ctx, "POST", path, rr) +} + +// RemoveRules from a Firewall. +func (fw *FirewallsServiceOp) RemoveRules(ctx context.Context, fID string, rr *FirewallRulesRequest) (*Response, error) { + path := path.Join(firewallsBasePath, fID, "rules") + return fw.createAndDoReq(ctx, "DELETE", path, rr) +} + +type dropletsRequest struct { + IDs []int `json:"droplet_ids"` +} + +type tagsRequest struct { + Tags []string `json:"tags"` +} + +type firewallRoot struct { + Firewall *Firewall `json:"firewall"` +} + +type firewallsRoot struct { + Firewalls []Firewall `json:"firewalls"` + Links *Links `json:"links"` +} + +func (fw *FirewallsServiceOp) createAndDoReq(ctx context.Context, method, path string, v interface{}) (*Response, error) { + req, err := fw.client.NewRequest(ctx, method, path, v) + if err != nil { + return nil, err + } + + return fw.client.Do(ctx, req, nil) +} + +func (fw *FirewallsServiceOp) listHelper(ctx context.Context, path string) ([]Firewall, *Response, error) { + req, err := fw.client.NewRequest(ctx, "GET", path, nil) + if err != nil { + return nil, nil, err + } + + root := new(firewallsRoot) + resp, err := fw.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if l := root.Links; l != nil { + resp.Links = l + } + + return root.Firewalls, resp, err +} diff --git a/vendor/github.com/digitalocean/godo/firewalls_test.go b/vendor/github.com/digitalocean/godo/firewalls_test.go new file mode 100644 index 0000000..cd6d94f --- /dev/null +++ b/vendor/github.com/digitalocean/godo/firewalls_test.go @@ -0,0 +1,839 @@ +package godo + +import ( + "encoding/json" + "fmt" + "net/http" + "path" + "reflect" + "testing" +) + +var ( + firewallCreateJSONBody = ` +{ + "name": "f-i-r-e-w-a-l-l", + "inbound_rules": [ + { + "protocol": "icmp", + "sources": { + "addresses": ["0.0.0.0/0"], + "tags": ["frontend"], + "droplet_ids": [123, 456], + "load_balancer_uids": ["lb-uid"] + } + }, + { + "protocol": "tcp", + "ports": "8000-9000", + "sources": { + "addresses": ["0.0.0.0/0"] + } + } + ], + "outbound_rules": [ + { + "protocol": "icmp", + "destinations": { + "tags": ["frontend"] + } + }, + { + "protocol": "tcp", + "ports": "8000-9000", + "destinations": { + "addresses": ["::/1"] + } + } + ], + "droplet_ids": [123], + "tags": ["frontend"] +} +` + firewallRulesJSONBody = ` +{ + "inbound_rules": [ + { + "protocol": "tcp", + "ports": "22", + "sources": { + "addresses": ["0.0.0.0/0"] + } + } + ], + "outbound_rules": [ + { + "protocol": "tcp", + "ports": "443", + "destinations": { + "addresses": ["0.0.0.0/0"] + } + } + ] +} +` + firewallUpdateJSONBody = ` +{ + "name": "f-i-r-e-w-a-l-l", + "inbound_rules": [ + { + "protocol": "tcp", + "ports": "443", + "sources": { + "addresses": ["10.0.0.0/8"] + } + } + ], + "droplet_ids": [123], + "tags": [] +} +` + firewallUpdateJSONResponse = ` +{ + "firewall": { + "id": "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0", + "name": "f-i-r-e-w-a-l-l", + "inbound_rules": [ + { + "protocol": "tcp", + "ports": "443", + "sources": { + "addresses": ["10.0.0.0/8"] + } + } + ], + "outbound_rules": [], + "created_at": "2017-04-06T13:07:27Z", + "droplet_ids": [ + 123 + ], + "tags": [] + } +} +` + firewallJSONResponse = ` +{ + "firewall": { + "id": "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0", + "name": "f-i-r-e-w-a-l-l", + "status": "waiting", + "inbound_rules": [ + { + "protocol": "icmp", + "ports": "0", + "sources": { + "tags": ["frontend"] + } + }, + { + "protocol": "tcp", + "ports": "8000-9000", + "sources": { + "addresses": ["0.0.0.0/0"] + } + } + ], + "outbound_rules": [ + { + "protocol": "icmp", + "ports": "0" + }, + { + "protocol": "tcp", + "ports": "8000-9000", + "destinations": { + "addresses": ["::/1"] + } + } + ], + "created_at": "2017-04-06T13:07:27Z", + "droplet_ids": [ + 123 + ], + "tags": [ + "frontend" + ], + "pending_changes": [ + { + "droplet_id": 123, + "removing": false, + "status": "waiting" + } + ] + } +} +` + firewallListJSONResponse = ` +{ + "firewalls": [ + { + "id": "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0", + "name": "f-i-r-e-w-a-l-l", + "inbound_rules": [ + { + "protocol": "icmp", + "ports": "0", + "sources": { + "tags": ["frontend"] + } + }, + { + "protocol": "tcp", + "ports": "8000-9000", + "sources": { + "addresses": ["0.0.0.0/0"] + } + } + ], + "outbound_rules": [ + { + "protocol": "icmp", + "ports": "0" + }, + { + "protocol": "tcp", + "ports": "8000-9000", + "destinations": { + "addresses": ["::/1"] + } + } + ], + "created_at": "2017-04-06T13:07:27Z", + "droplet_ids": [ + 123 + ], + "tags": [ + "frontend" + ] + } + ], + "links": {}, + "meta": { + "total": 1 + } +} +` +) + +func TestFirewalls_Get(t *testing.T) { + setup() + defer teardown() + + urlStr := "/v2/firewalls" + fID := "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0" + urlStr = path.Join(urlStr, fID) + + mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, firewallJSONResponse) + }) + + actualFirewall, _, err := client.Firewalls.Get(ctx, fID) + if err != nil { + t.Errorf("Firewalls.Get returned error: %v", err) + } + + expectedFirewall := &Firewall{ + ID: "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0", + Name: "f-i-r-e-w-a-l-l", + Status: "waiting", + InboundRules: []InboundRule{ + { + Protocol: "icmp", + PortRange: "0", + Sources: &Sources{ + Tags: []string{"frontend"}, + }, + }, + { + Protocol: "tcp", + PortRange: "8000-9000", + Sources: &Sources{ + Addresses: []string{"0.0.0.0/0"}, + }, + }, + }, + OutboundRules: []OutboundRule{ + { + Protocol: "icmp", + PortRange: "0", + }, + { + Protocol: "tcp", + PortRange: "8000-9000", + Destinations: &Destinations{ + Addresses: []string{"::/1"}, + }, + }, + }, + Created: "2017-04-06T13:07:27Z", + DropletIDs: []int{123}, + Tags: []string{"frontend"}, + PendingChanges: []PendingChange{ + { + DropletID: 123, + Removing: false, + Status: "waiting", + }, + }, + } + + if !reflect.DeepEqual(actualFirewall, expectedFirewall) { + t.Errorf("Firewalls.Get returned %+v, expected %+v", actualFirewall, expectedFirewall) + } +} + +func TestFirewalls_Create(t *testing.T) { + setup() + defer teardown() + + expectedFirewallRequest := &FirewallRequest{ + Name: "f-i-r-e-w-a-l-l", + InboundRules: []InboundRule{ + { + Protocol: "icmp", + Sources: &Sources{ + Addresses: []string{"0.0.0.0/0"}, + Tags: []string{"frontend"}, + DropletIDs: []int{123, 456}, + LoadBalancerUIDs: []string{"lb-uid"}, + }, + }, + { + Protocol: "tcp", + PortRange: "8000-9000", + Sources: &Sources{ + Addresses: []string{"0.0.0.0/0"}, + }, + }, + }, + OutboundRules: []OutboundRule{ + { + Protocol: "icmp", + Destinations: &Destinations{ + Tags: []string{"frontend"}, + }, + }, + { + Protocol: "tcp", + PortRange: "8000-9000", + Destinations: &Destinations{ + Addresses: []string{"::/1"}, + }, + }, + }, + DropletIDs: []int{123}, + Tags: []string{"frontend"}, + } + + mux.HandleFunc("/v2/firewalls", func(w http.ResponseWriter, r *http.Request) { + v := new(FirewallRequest) + err := json.NewDecoder(r.Body).Decode(v) + if err != nil { + t.Fatal(err) + } + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, expectedFirewallRequest) { + t.Errorf("Request body = %+v, expected %+v", v, expectedFirewallRequest) + } + + var actualFirewallRequest *FirewallRequest + json.Unmarshal([]byte(firewallCreateJSONBody), &actualFirewallRequest) + if !reflect.DeepEqual(actualFirewallRequest, expectedFirewallRequest) { + t.Errorf("Request body = %+v, expected %+v", actualFirewallRequest, expectedFirewallRequest) + } + + fmt.Fprint(w, firewallJSONResponse) + }) + + actualFirewall, _, err := client.Firewalls.Create(ctx, expectedFirewallRequest) + if err != nil { + t.Errorf("Firewalls.Create returned error: %v", err) + } + + expectedFirewall := &Firewall{ + ID: "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0", + Name: "f-i-r-e-w-a-l-l", + Status: "waiting", + InboundRules: []InboundRule{ + { + Protocol: "icmp", + PortRange: "0", + Sources: &Sources{ + Tags: []string{"frontend"}, + }, + }, + { + Protocol: "tcp", + PortRange: "8000-9000", + Sources: &Sources{ + Addresses: []string{"0.0.0.0/0"}, + }, + }, + }, + OutboundRules: []OutboundRule{ + { + Protocol: "icmp", + PortRange: "0", + }, + { + Protocol: "tcp", + PortRange: "8000-9000", + Destinations: &Destinations{ + Addresses: []string{"::/1"}, + }, + }, + }, + Created: "2017-04-06T13:07:27Z", + DropletIDs: []int{123}, + Tags: []string{"frontend"}, + PendingChanges: []PendingChange{ + { + DropletID: 123, + Removing: false, + Status: "waiting", + }, + }, + } + + if !reflect.DeepEqual(actualFirewall, expectedFirewall) { + t.Errorf("Firewalls.Create returned %+v, expected %+v", actualFirewall, expectedFirewall) + } +} + +func TestFirewalls_Update(t *testing.T) { + setup() + defer teardown() + + expectedFirewallRequest := &FirewallRequest{ + Name: "f-i-r-e-w-a-l-l", + InboundRules: []InboundRule{ + { + Protocol: "tcp", + PortRange: "443", + Sources: &Sources{ + Addresses: []string{"10.0.0.0/8"}, + }, + }, + }, + DropletIDs: []int{123}, + Tags: []string{}, + } + + urlStr := "/v2/firewalls" + fID := "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0" + urlStr = path.Join(urlStr, fID) + mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) { + v := new(FirewallRequest) + err := json.NewDecoder(r.Body).Decode(v) + if err != nil { + t.Fatal(err) + } + + testMethod(t, r, "PUT") + if !reflect.DeepEqual(v, expectedFirewallRequest) { + t.Errorf("Request body = %+v, expected %+v", v, expectedFirewallRequest) + } + + var actualFirewallRequest *FirewallRequest + json.Unmarshal([]byte(firewallUpdateJSONBody), &actualFirewallRequest) + if !reflect.DeepEqual(actualFirewallRequest, expectedFirewallRequest) { + t.Errorf("Request body = %+v, expected %+v", actualFirewallRequest, expectedFirewallRequest) + } + + fmt.Fprint(w, firewallUpdateJSONResponse) + }) + + actualFirewall, _, err := client.Firewalls.Update(ctx, fID, expectedFirewallRequest) + if err != nil { + t.Errorf("Firewalls.Update returned error: %v", err) + } + + expectedFirewall := &Firewall{ + ID: "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0", + Name: "f-i-r-e-w-a-l-l", + InboundRules: []InboundRule{ + { + Protocol: "tcp", + PortRange: "443", + Sources: &Sources{ + Addresses: []string{"10.0.0.0/8"}, + }, + }, + }, + OutboundRules: []OutboundRule{}, + Created: "2017-04-06T13:07:27Z", + DropletIDs: []int{123}, + Tags: []string{}, + } + + if !reflect.DeepEqual(actualFirewall, expectedFirewall) { + t.Errorf("Firewalls.Update returned %+v, expected %+v", actualFirewall, expectedFirewall) + } +} + +func TestFirewalls_Delete(t *testing.T) { + setup() + defer teardown() + + urlStr := "/v2/firewalls" + fID := "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0" + urlStr = path.Join(urlStr, fID) + mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Firewalls.Delete(ctx, fID) + + if err != nil { + t.Errorf("Firewalls.Delete returned error: %v", err) + } +} + +func TestFirewalls_List(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/v2/firewalls", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, firewallListJSONResponse) + }) + + actualFirewalls, _, err := client.Firewalls.List(ctx, nil) + + if err != nil { + t.Errorf("Firewalls.List returned error: %v", err) + } + + expectedFirewalls := makeExpectedFirewalls() + if !reflect.DeepEqual(actualFirewalls, expectedFirewalls) { + t.Errorf("Firewalls.List returned %+v, expected %+v", actualFirewalls, expectedFirewalls) + } +} + +func TestFirewalls_ListByDroplet(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/v2/droplets/123/firewalls", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, firewallListJSONResponse) + }) + + actualFirewalls, _, err := client.Firewalls.ListByDroplet(ctx, 123, nil) + + if err != nil { + t.Errorf("Firewalls.List returned error: %v", err) + } + + expectedFirewalls := makeExpectedFirewalls() + if !reflect.DeepEqual(actualFirewalls, expectedFirewalls) { + t.Errorf("Firewalls.List returned %+v, expected %+v", actualFirewalls, expectedFirewalls) + } +} + +func TestFirewalls_AddDroplets(t *testing.T) { + setup() + defer teardown() + + dRequest := &dropletsRequest{ + IDs: []int{123}, + } + + fID := "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0" + urlStr := path.Join("/v2/firewalls", fID, "droplets") + mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) { + v := new(dropletsRequest) + err := json.NewDecoder(r.Body).Decode(v) + if err != nil { + t.Fatal(err) + } + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, dRequest) { + t.Errorf("Request body = %+v, expected %+v", v, dRequest) + } + + expectedJSONBody := `{"droplet_ids": [123]}` + var actualDropletsRequest *dropletsRequest + json.Unmarshal([]byte(expectedJSONBody), &actualDropletsRequest) + if !reflect.DeepEqual(actualDropletsRequest, dRequest) { + t.Errorf("Request body = %+v, expected %+v", actualDropletsRequest, dRequest) + } + + fmt.Fprint(w, nil) + }) + + _, err := client.Firewalls.AddDroplets(ctx, fID, dRequest.IDs...) + + if err != nil { + t.Errorf("Firewalls.AddDroplets returned error: %v", err) + } +} + +func TestFirewalls_RemoveDroplets(t *testing.T) { + setup() + defer teardown() + + dRequest := &dropletsRequest{ + IDs: []int{123, 345}, + } + + fID := "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0" + urlStr := path.Join("/v2/firewalls", fID, "droplets") + mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) { + v := new(dropletsRequest) + err := json.NewDecoder(r.Body).Decode(v) + if err != nil { + t.Fatal(err) + } + + testMethod(t, r, "DELETE") + if !reflect.DeepEqual(v, dRequest) { + t.Errorf("Request body = %+v, expected %+v", v, dRequest) + } + + expectedJSONBody := `{"droplet_ids": [123, 345]}` + var actualDropletsRequest *dropletsRequest + json.Unmarshal([]byte(expectedJSONBody), &actualDropletsRequest) + if !reflect.DeepEqual(actualDropletsRequest, dRequest) { + t.Errorf("Request body = %+v, expected %+v", actualDropletsRequest, dRequest) + } + + fmt.Fprint(w, nil) + }) + + _, err := client.Firewalls.RemoveDroplets(ctx, fID, dRequest.IDs...) + + if err != nil { + t.Errorf("Firewalls.RemoveDroplets returned error: %v", err) + } +} + +func TestFirewalls_AddTags(t *testing.T) { + setup() + defer teardown() + + tRequest := &tagsRequest{ + Tags: []string{"frontend"}, + } + + fID := "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0" + urlStr := path.Join("/v2/firewalls", fID, "tags") + mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) { + v := new(tagsRequest) + err := json.NewDecoder(r.Body).Decode(v) + if err != nil { + t.Fatal(err) + } + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, tRequest) { + t.Errorf("Request body = %+v, expected %+v", v, tRequest) + } + + var actualTagsRequest *tagsRequest + json.Unmarshal([]byte(`{"tags": ["frontend"]}`), &actualTagsRequest) + if !reflect.DeepEqual(actualTagsRequest, tRequest) { + t.Errorf("Request body = %+v, expected %+v", actualTagsRequest, tRequest) + } + + fmt.Fprint(w, nil) + }) + + _, err := client.Firewalls.AddTags(ctx, fID, tRequest.Tags...) + + if err != nil { + t.Errorf("Firewalls.AddTags returned error: %v", err) + } +} + +func TestFirewalls_RemoveTags(t *testing.T) { + setup() + defer teardown() + + tRequest := &tagsRequest{ + Tags: []string{"frontend", "backend"}, + } + + fID := "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0" + urlStr := path.Join("/v2/firewalls", fID, "tags") + mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) { + v := new(tagsRequest) + err := json.NewDecoder(r.Body).Decode(v) + if err != nil { + t.Fatal(err) + } + + testMethod(t, r, "DELETE") + if !reflect.DeepEqual(v, tRequest) { + t.Errorf("Request body = %+v, expected %+v", v, tRequest) + } + + var actualTagsRequest *tagsRequest + json.Unmarshal([]byte(`{"tags": ["frontend", "backend"]}`), &actualTagsRequest) + if !reflect.DeepEqual(actualTagsRequest, tRequest) { + t.Errorf("Request body = %+v, expected %+v", actualTagsRequest, tRequest) + } + + fmt.Fprint(w, nil) + }) + + _, err := client.Firewalls.RemoveTags(ctx, fID, tRequest.Tags...) + + if err != nil { + t.Errorf("Firewalls.RemoveTags returned error: %v", err) + } +} + +func TestFirewalls_AddRules(t *testing.T) { + setup() + defer teardown() + + rr := &FirewallRulesRequest{ + InboundRules: []InboundRule{ + { + Protocol: "tcp", + PortRange: "22", + Sources: &Sources{ + Addresses: []string{"0.0.0.0/0"}, + }, + }, + }, + OutboundRules: []OutboundRule{ + { + Protocol: "tcp", + PortRange: "443", + Destinations: &Destinations{ + Addresses: []string{"0.0.0.0/0"}, + }, + }, + }, + } + + fID := "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0" + urlStr := path.Join("/v2/firewalls", fID, "rules") + mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) { + v := new(FirewallRulesRequest) + err := json.NewDecoder(r.Body).Decode(v) + if err != nil { + t.Fatal(err) + } + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, rr) { + t.Errorf("Request body = %+v, expected %+v", v, rr) + } + + var actualFirewallRulesRequest *FirewallRulesRequest + json.Unmarshal([]byte(firewallRulesJSONBody), &actualFirewallRulesRequest) + if !reflect.DeepEqual(actualFirewallRulesRequest, rr) { + t.Errorf("Request body = %+v, expected %+v", actualFirewallRulesRequest, rr) + } + + fmt.Fprint(w, nil) + }) + + _, err := client.Firewalls.AddRules(ctx, fID, rr) + + if err != nil { + t.Errorf("Firewalls.AddRules returned error: %v", err) + } +} + +func TestFirewalls_RemoveRules(t *testing.T) { + setup() + defer teardown() + + rr := &FirewallRulesRequest{ + InboundRules: []InboundRule{ + { + Protocol: "tcp", + PortRange: "22", + Sources: &Sources{ + Addresses: []string{"0.0.0.0/0"}, + }, + }, + }, + OutboundRules: []OutboundRule{ + { + Protocol: "tcp", + PortRange: "443", + Destinations: &Destinations{ + Addresses: []string{"0.0.0.0/0"}, + }, + }, + }, + } + + fID := "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0" + urlStr := path.Join("/v2/firewalls", fID, "rules") + mux.HandleFunc(urlStr, func(w http.ResponseWriter, r *http.Request) { + v := new(FirewallRulesRequest) + err := json.NewDecoder(r.Body).Decode(v) + if err != nil { + t.Fatal(err) + } + + testMethod(t, r, "DELETE") + if !reflect.DeepEqual(v, rr) { + t.Errorf("Request body = %+v, expected %+v", v, rr) + } + + var actualFirewallRulesRequest *FirewallRulesRequest + json.Unmarshal([]byte(firewallRulesJSONBody), &actualFirewallRulesRequest) + if !reflect.DeepEqual(actualFirewallRulesRequest, rr) { + t.Errorf("Request body = %+v, expected %+v", actualFirewallRulesRequest, rr) + } + + fmt.Fprint(w, nil) + }) + + _, err := client.Firewalls.RemoveRules(ctx, fID, rr) + + if err != nil { + t.Errorf("Firewalls.RemoveRules returned error: %v", err) + } +} + +func makeExpectedFirewalls() []Firewall { + return []Firewall{ + Firewall{ + ID: "fe6b88f2-b42b-4bf7-bbd3-5ae20208f0b0", + Name: "f-i-r-e-w-a-l-l", + InboundRules: []InboundRule{ + { + Protocol: "icmp", + PortRange: "0", + Sources: &Sources{ + Tags: []string{"frontend"}, + }, + }, + { + Protocol: "tcp", + PortRange: "8000-9000", + Sources: &Sources{ + Addresses: []string{"0.0.0.0/0"}, + }, + }, + }, + OutboundRules: []OutboundRule{ + { + Protocol: "icmp", + PortRange: "0", + }, + { + Protocol: "tcp", + PortRange: "8000-9000", + Destinations: &Destinations{ + Addresses: []string{"::/1"}, + }, + }, + }, + DropletIDs: []int{123}, + Tags: []string{"frontend"}, + Created: "2017-04-06T13:07:27Z", + }, + } +} diff --git a/vendor/github.com/digitalocean/godo/floating_ips.go b/vendor/github.com/digitalocean/godo/floating_ips.go index 13e9832..032d7e9 100644 --- a/vendor/github.com/digitalocean/godo/floating_ips.go +++ b/vendor/github.com/digitalocean/godo/floating_ips.go @@ -1,8 +1,9 @@ package godo import ( - "context" "fmt" + + "github.com/digitalocean/godo/context" ) const floatingBasePath = "v2/floating_ips" @@ -68,7 +69,7 @@ func (f *FloatingIPsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Fl } root := new(floatingIPsRoot) - resp, err := f.client.Do(req, root) + resp, err := f.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -89,7 +90,7 @@ func (f *FloatingIPsServiceOp) Get(ctx context.Context, ip string) (*FloatingIP, } root := new(floatingIPRoot) - resp, err := f.client.Do(req, root) + resp, err := f.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -108,7 +109,7 @@ func (f *FloatingIPsServiceOp) Create(ctx context.Context, createRequest *Floati } root := new(floatingIPRoot) - resp, err := f.client.Do(req, root) + resp, err := f.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -128,7 +129,7 @@ func (f *FloatingIPsServiceOp) Delete(ctx context.Context, ip string) (*Response return nil, err } - resp, err := f.client.Do(req, nil) + resp, err := f.client.Do(ctx, req, nil) return resp, err } diff --git a/vendor/github.com/digitalocean/godo/floating_ips_actions.go b/vendor/github.com/digitalocean/godo/floating_ips_actions.go index 5afa051..b0ad216 100644 --- a/vendor/github.com/digitalocean/godo/floating_ips_actions.go +++ b/vendor/github.com/digitalocean/godo/floating_ips_actions.go @@ -1,8 +1,9 @@ package godo import ( - "context" "fmt" + + "github.com/digitalocean/godo/context" ) // FloatingIPActionsService is an interface for interfacing with the @@ -62,7 +63,7 @@ func (s *FloatingIPActionsServiceOp) doAction(ctx context.Context, ip string, re } root := new(actionRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -77,7 +78,7 @@ func (s *FloatingIPActionsServiceOp) get(ctx context.Context, path string) (*Act } root := new(actionRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -92,7 +93,7 @@ func (s *FloatingIPActionsServiceOp) list(ctx context.Context, path string) ([]A } root := new(actionsRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } diff --git a/vendor/github.com/digitalocean/godo/godo.go b/vendor/github.com/digitalocean/godo/godo.go index 0f9d7a9..9dfef2a 100644 --- a/vendor/github.com/digitalocean/godo/godo.go +++ b/vendor/github.com/digitalocean/godo/godo.go @@ -2,7 +2,6 @@ package godo import ( "bytes" - "context" "encoding/json" "fmt" "io" @@ -15,10 +14,12 @@ import ( "github.com/google/go-querystring/query" headerLink "github.com/tent/http-link-go" + + "github.com/digitalocean/godo/context" ) const ( - libraryVersion = "1.0.0" + libraryVersion = "1.1.0" defaultBaseURL = "https://api.digitalocean.com/" userAgent = "godo/" + libraryVersion mediaType = "application/json" @@ -62,6 +63,7 @@ type Client struct { Tags TagsService LoadBalancers LoadBalancersService Certificates CertificatesService + Firewalls FirewallsService // Optional function called after every successful request made to the DO APIs onRequestCompleted RequestCompletionCallback @@ -172,6 +174,7 @@ func NewClient(httpClient *http.Client) *Client { c.Tags = &TagsServiceOp{client: c} c.LoadBalancers = &LoadBalancersServiceOp{client: c} c.Certificates = &CertificatesServiceOp{client: c} + c.Firewalls = &FirewallsServiceOp{client: c} return c } @@ -236,7 +239,6 @@ func (c *Client) NewRequest(ctx context.Context, method, urlStr string, body int return nil, err } - req = req.WithContext(ctx) req.Header.Add("Content-Type", mediaType) req.Header.Add("Accept", mediaType) req.Header.Add("User-Agent", c.UserAgent) @@ -293,8 +295,8 @@ func (r *Response) populateRate() { // Do sends an API request and returns the API response. The API response is JSON decoded and stored in the value // pointed to by v, or returned as an error if an API error has occurred. If v implements the io.Writer interface, // the raw response will be written to v, without attempting to decode it. -func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) { - resp, err := c.client.Do(req) +func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Response, error) { + resp, err := context.DoRequestWithClient(ctx, c.client, req) if err != nil { return nil, err } diff --git a/vendor/github.com/digitalocean/godo/godo_test.go b/vendor/github.com/digitalocean/godo/godo_test.go index aafc51e..4bb264f 100644 --- a/vendor/github.com/digitalocean/godo/godo_test.go +++ b/vendor/github.com/digitalocean/godo/godo_test.go @@ -1,7 +1,6 @@ package godo import ( - "context" "fmt" "io/ioutil" "net/http" @@ -12,6 +11,8 @@ import ( "strings" "testing" "time" + + "github.com/digitalocean/godo/context" ) var ( @@ -224,7 +225,7 @@ func TestDo(t *testing.T) { req, _ := client.NewRequest(ctx, "GET", "/", nil) body := new(foo) - _, err := client.Do(req, body) + _, err := client.Do(context.Background(), req, body) if err != nil { t.Fatalf("Do(): %v", err) } @@ -244,7 +245,7 @@ func TestDo_httpError(t *testing.T) { }) req, _ := client.NewRequest(ctx, "GET", "/", nil) - _, err := client.Do(req, nil) + _, err := client.Do(context.Background(), req, nil) if err == nil { t.Error("Expected HTTP 400 error.") @@ -262,7 +263,7 @@ func TestDo_redirectLoop(t *testing.T) { }) req, _ := client.NewRequest(ctx, "GET", "/", nil) - _, err := client.Do(req, nil) + _, err := client.Do(context.Background(), req, nil) if err == nil { t.Error("Expected error to be returned.") @@ -347,7 +348,7 @@ func TestDo_rateLimit(t *testing.T) { } req, _ := client.NewRequest(ctx, "GET", "/", nil) - _, err := client.Do(req, nil) + _, err := client.Do(context.Background(), req, nil) if err != nil { t.Fatalf("Do(): %v", err) } @@ -378,7 +379,7 @@ func TestDo_rateLimit_errorResponse(t *testing.T) { var expected int req, _ := client.NewRequest(ctx, "GET", "/", nil) - _, _ = client.Do(req, nil) + _, _ = client.Do(context.Background(), req, nil) if expected = 60; client.Rate.Limit != expected { t.Errorf("Client rate limit = %v, expected %v", client.Rate.Limit, expected) @@ -431,7 +432,7 @@ func TestDo_completion_callback(t *testing.T) { } completedResp = string(b) }) - _, err := client.Do(req, body) + _, err := client.Do(context.Background(), req, body) if err != nil { t.Fatalf("Do(): %v", err) } diff --git a/vendor/github.com/digitalocean/godo/image_actions.go b/vendor/github.com/digitalocean/godo/image_actions.go index c4201c0..94ae6c2 100644 --- a/vendor/github.com/digitalocean/godo/image_actions.go +++ b/vendor/github.com/digitalocean/godo/image_actions.go @@ -1,8 +1,9 @@ package godo import ( - "context" "fmt" + + "github.com/digitalocean/godo/context" ) // ImageActionsService is an interface for interfacing with the image actions @@ -40,7 +41,7 @@ func (i *ImageActionsServiceOp) Transfer(ctx context.Context, imageID int, trans } root := new(actionRoot) - resp, err := i.client.Do(req, root) + resp, err := i.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -66,7 +67,7 @@ func (i *ImageActionsServiceOp) Convert(ctx context.Context, imageID int) (*Acti } root := new(actionRoot) - resp, err := i.client.Do(req, root) + resp, err := i.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -92,7 +93,7 @@ func (i *ImageActionsServiceOp) Get(ctx context.Context, imageID, actionID int) } root := new(actionRoot) - resp, err := i.client.Do(req, root) + resp, err := i.client.Do(ctx, req, root) if err != nil { return nil, resp, err } diff --git a/vendor/github.com/digitalocean/godo/images.go b/vendor/github.com/digitalocean/godo/images.go index c808af6..e8ae262 100644 --- a/vendor/github.com/digitalocean/godo/images.go +++ b/vendor/github.com/digitalocean/godo/images.go @@ -1,8 +1,9 @@ package godo import ( - "context" "fmt" + + "github.com/digitalocean/godo/context" ) const imageBasePath = "v2/images" @@ -123,7 +124,7 @@ func (s *ImagesServiceOp) Update(ctx context.Context, imageID int, updateRequest } root := new(imageRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -144,7 +145,7 @@ func (s *ImagesServiceOp) Delete(ctx context.Context, imageID int) (*Response, e return nil, err } - resp, err := s.client.Do(req, nil) + resp, err := s.client.Do(ctx, req, nil) return resp, err } @@ -159,7 +160,7 @@ func (s *ImagesServiceOp) get(ctx context.Context, ID interface{}) (*Image, *Res } root := new(imageRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -185,7 +186,7 @@ func (s *ImagesServiceOp) list(ctx context.Context, opt *ListOptions, listOpt *l } root := new(imagesRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } diff --git a/vendor/github.com/digitalocean/godo/keys.go b/vendor/github.com/digitalocean/godo/keys.go index 7b336c4..d7aaba1 100644 --- a/vendor/github.com/digitalocean/godo/keys.go +++ b/vendor/github.com/digitalocean/godo/keys.go @@ -1,8 +1,9 @@ package godo import ( - "context" "fmt" + + "github.com/digitalocean/godo/context" ) const keysBasePath = "v2/account/keys" @@ -75,7 +76,7 @@ func (s *KeysServiceOp) List(ctx context.Context, opt *ListOptions) ([]Key, *Res } root := new(keysRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -94,7 +95,7 @@ func (s *KeysServiceOp) get(ctx context.Context, path string) (*Key, *Response, } root := new(keyRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -134,7 +135,7 @@ func (s *KeysServiceOp) Create(ctx context.Context, createRequest *KeyCreateRequ } root := new(keyRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -159,7 +160,7 @@ func (s *KeysServiceOp) UpdateByID(ctx context.Context, keyID int, updateRequest } root := new(keyRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -184,7 +185,7 @@ func (s *KeysServiceOp) UpdateByFingerprint(ctx context.Context, fingerprint str } root := new(keyRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -199,7 +200,7 @@ func (s *KeysServiceOp) delete(ctx context.Context, path string) (*Response, err return nil, err } - resp, err := s.client.Do(req, nil) + resp, err := s.client.Do(ctx, req, nil) return resp, err } diff --git a/vendor/github.com/digitalocean/godo/links.go b/vendor/github.com/digitalocean/godo/links.go index 2098441..0c61102 100644 --- a/vendor/github.com/digitalocean/godo/links.go +++ b/vendor/github.com/digitalocean/godo/links.go @@ -1,9 +1,10 @@ package godo import ( - "context" "net/url" "strconv" + + "github.com/digitalocean/godo/context" ) // Links manages links that are returned along with a List diff --git a/vendor/github.com/digitalocean/godo/load_balancers.go b/vendor/github.com/digitalocean/godo/load_balancers.go index 48b9c26..cd225b4 100644 --- a/vendor/github.com/digitalocean/godo/load_balancers.go +++ b/vendor/github.com/digitalocean/godo/load_balancers.go @@ -1,8 +1,9 @@ package godo import ( - "context" "fmt" + + "github.com/digitalocean/godo/context" ) const loadBalancersBasePath = "/v2/load_balancers" @@ -148,7 +149,7 @@ func (l *LoadBalancersServiceOp) Get(ctx context.Context, lbID string) (*LoadBal } root := new(loadBalancerRoot) - resp, err := l.client.Do(req, root) + resp, err := l.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -169,7 +170,7 @@ func (l *LoadBalancersServiceOp) List(ctx context.Context, opt *ListOptions) ([] } root := new(loadBalancersRoot) - resp, err := l.client.Do(req, root) + resp, err := l.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -188,7 +189,7 @@ func (l *LoadBalancersServiceOp) Create(ctx context.Context, lbr *LoadBalancerRe } root := new(loadBalancerRoot) - resp, err := l.client.Do(req, root) + resp, err := l.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -206,7 +207,7 @@ func (l *LoadBalancersServiceOp) Update(ctx context.Context, lbID string, lbr *L } root := new(loadBalancerRoot) - resp, err := l.client.Do(req, root) + resp, err := l.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -223,7 +224,7 @@ func (l *LoadBalancersServiceOp) Delete(ctx context.Context, ldID string) (*Resp return nil, err } - return l.client.Do(req, nil) + return l.client.Do(ctx, req, nil) } // AddDroplets adds droplets to a load balancer. @@ -235,7 +236,7 @@ func (l *LoadBalancersServiceOp) AddDroplets(ctx context.Context, lbID string, d return nil, err } - return l.client.Do(req, nil) + return l.client.Do(ctx, req, nil) } // RemoveDroplets removes droplets from a load balancer. @@ -247,7 +248,7 @@ func (l *LoadBalancersServiceOp) RemoveDroplets(ctx context.Context, lbID string return nil, err } - return l.client.Do(req, nil) + return l.client.Do(ctx, req, nil) } // AddForwardingRules adds forwarding rules to a load balancer. @@ -259,7 +260,7 @@ func (l *LoadBalancersServiceOp) AddForwardingRules(ctx context.Context, lbID st return nil, err } - return l.client.Do(req, nil) + return l.client.Do(ctx, req, nil) } // RemoveForwardingRules removes forwarding rules from a load balancer. @@ -271,5 +272,5 @@ func (l *LoadBalancersServiceOp) RemoveForwardingRules(ctx context.Context, lbID return nil, err } - return l.client.Do(req, nil) + return l.client.Do(ctx, req, nil) } diff --git a/vendor/github.com/digitalocean/godo/regions.go b/vendor/github.com/digitalocean/godo/regions.go index ccfe029..e2a7e6e 100644 --- a/vendor/github.com/digitalocean/godo/regions.go +++ b/vendor/github.com/digitalocean/godo/regions.go @@ -1,6 +1,6 @@ package godo -import "context" +import "github.com/digitalocean/godo/context" // RegionsService is an interface for interfacing with the regions // endpoints of the DigitalOcean API @@ -49,7 +49,7 @@ func (s *RegionsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Region } root := new(regionsRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } diff --git a/vendor/github.com/digitalocean/godo/sizes.go b/vendor/github.com/digitalocean/godo/sizes.go index b695792..9591fd2 100644 --- a/vendor/github.com/digitalocean/godo/sizes.go +++ b/vendor/github.com/digitalocean/godo/sizes.go @@ -1,6 +1,6 @@ package godo -import "context" +import "github.com/digitalocean/godo/context" // SizesService is an interface for interfacing with the size // endpoints of the DigitalOcean API @@ -53,7 +53,7 @@ func (s *SizesServiceOp) List(ctx context.Context, opt *ListOptions) ([]Size, *R } root := new(sizesRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } diff --git a/vendor/github.com/digitalocean/godo/snapshots.go b/vendor/github.com/digitalocean/godo/snapshots.go index 5a9e5c5..aa434bd 100644 --- a/vendor/github.com/digitalocean/godo/snapshots.go +++ b/vendor/github.com/digitalocean/godo/snapshots.go @@ -1,8 +1,9 @@ package godo import ( - "context" "fmt" + + "github.com/digitalocean/godo/context" ) const snapshotBasePath = "v2/snapshots" @@ -86,7 +87,7 @@ func (s *SnapshotsServiceOp) Delete(ctx context.Context, snapshotID string) (*Re return nil, err } - resp, err := s.client.Do(req, nil) + resp, err := s.client.Do(ctx, req, nil) return resp, err } @@ -101,7 +102,7 @@ func (s *SnapshotsServiceOp) get(ctx context.Context, ID string) (*Snapshot, *Re } root := new(snapshotRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -127,7 +128,7 @@ func (s *SnapshotsServiceOp) list(ctx context.Context, opt *ListOptions, listOpt } root := new(snapshotsRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } diff --git a/vendor/github.com/digitalocean/godo/snapshots_test.go b/vendor/github.com/digitalocean/godo/snapshots_test.go index e6b8d72..a40e04a 100644 --- a/vendor/github.com/digitalocean/godo/snapshots_test.go +++ b/vendor/github.com/digitalocean/godo/snapshots_test.go @@ -1,11 +1,12 @@ package godo import ( - "context" "fmt" "net/http" "reflect" "testing" + + "github.com/digitalocean/godo/context" ) func TestSnapshots_List(t *testing.T) { diff --git a/vendor/github.com/digitalocean/godo/storage.go b/vendor/github.com/digitalocean/godo/storage.go index fe5d749..ab9052e 100644 --- a/vendor/github.com/digitalocean/godo/storage.go +++ b/vendor/github.com/digitalocean/godo/storage.go @@ -1,9 +1,10 @@ package godo import ( - "context" "fmt" "time" + + "github.com/digitalocean/godo/context" ) const ( @@ -73,6 +74,7 @@ type VolumeCreateRequest struct { Name string `json:"name"` Description string `json:"description"` SizeGigaBytes int64 `json:"size_gigabytes"` + SnapshotID string `json:"snapshot_id"` } // ListVolumes lists all storage volumes. @@ -98,7 +100,7 @@ func (svc *StorageServiceOp) ListVolumes(ctx context.Context, params *ListVolume } root := new(storageVolumesRoot) - resp, err := svc.client.Do(req, root) + resp, err := svc.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -120,7 +122,7 @@ func (svc *StorageServiceOp) CreateVolume(ctx context.Context, createRequest *Vo } root := new(storageVolumeRoot) - resp, err := svc.client.Do(req, root) + resp, err := svc.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -137,7 +139,7 @@ func (svc *StorageServiceOp) GetVolume(ctx context.Context, id string) (*Volume, } root := new(storageVolumeRoot) - resp, err := svc.client.Do(req, root) + resp, err := svc.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -153,7 +155,7 @@ func (svc *StorageServiceOp) DeleteVolume(ctx context.Context, id string) (*Resp if err != nil { return nil, err } - return svc.client.Do(req, nil) + return svc.client.Do(ctx, req, nil) } // SnapshotCreateRequest represents a request to create a block store @@ -178,7 +180,7 @@ func (svc *StorageServiceOp) ListSnapshots(ctx context.Context, volumeID string, } root := new(snapshotsRoot) - resp, err := svc.client.Do(req, root) + resp, err := svc.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -200,7 +202,7 @@ func (svc *StorageServiceOp) CreateSnapshot(ctx context.Context, createRequest * } root := new(snapshotRoot) - resp, err := svc.client.Do(req, root) + resp, err := svc.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -217,7 +219,7 @@ func (svc *StorageServiceOp) GetSnapshot(ctx context.Context, id string) (*Snaps } root := new(snapshotRoot) - resp, err := svc.client.Do(req, root) + resp, err := svc.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -233,5 +235,5 @@ func (svc *StorageServiceOp) DeleteSnapshot(ctx context.Context, id string) (*Re if err != nil { return nil, err } - return svc.client.Do(req, nil) + return svc.client.Do(ctx, req, nil) } diff --git a/vendor/github.com/digitalocean/godo/storage_actions.go b/vendor/github.com/digitalocean/godo/storage_actions.go index 7988868..1cc0a9d 100644 --- a/vendor/github.com/digitalocean/godo/storage_actions.go +++ b/vendor/github.com/digitalocean/godo/storage_actions.go @@ -1,8 +1,9 @@ package godo import ( - "context" "fmt" + + "github.com/digitalocean/godo/context" ) // StorageActionsService is an interface for interfacing with the @@ -10,7 +11,6 @@ import ( // 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) @@ -38,14 +38,6 @@ func (s *StorageActionsServiceOp) Attach(ctx context.Context, volumeID string, d 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{ @@ -91,7 +83,7 @@ func (s *StorageActionsServiceOp) doAction(ctx context.Context, volumeID string, } root := new(actionRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -106,7 +98,7 @@ func (s *StorageActionsServiceOp) get(ctx context.Context, path string) (*Action } root := new(actionRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -121,7 +113,7 @@ func (s *StorageActionsServiceOp) list(ctx context.Context, path string) ([]Acti } root := new(actionsRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } diff --git a/vendor/github.com/digitalocean/godo/storage_actions_test.go b/vendor/github.com/digitalocean/godo/storage_actions_test.go index 8a06c45..410884a 100644 --- a/vendor/github.com/digitalocean/godo/storage_actions_test.go +++ b/vendor/github.com/digitalocean/godo/storage_actions_test.go @@ -42,36 +42,6 @@ func TestStoragesActions_Attach(t *testing.T) { } } -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() diff --git a/vendor/github.com/digitalocean/godo/storage_test.go b/vendor/github.com/digitalocean/godo/storage_test.go index 350c10b..99d7589 100644 --- a/vendor/github.com/digitalocean/godo/storage_test.go +++ b/vendor/github.com/digitalocean/godo/storage_test.go @@ -239,6 +239,61 @@ func TestStorageVolumes_Create(t *testing.T) { } } +func TestStorageVolumes_CreateFromSnapshot(t *testing.T) { + setup() + defer teardown() + + createRequest := &VolumeCreateRequest{ + Name: "my-volume-from-a-snapshot", + Description: "my description", + SizeGigaBytes: 100, + SnapshotID: "0d165eff-0b4c-11e7-9093-0242ac110207", + } + + want := &Volume{ + Region: &Region{Slug: "nyc3"}, + ID: "80d414c6-295e-4e3a-ac58-eb9456c1e1d1", + Name: "my-volume-from-a-snapshot", + 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-from-a-snapshot", + "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() diff --git a/vendor/github.com/digitalocean/godo/tags.go b/vendor/github.com/digitalocean/godo/tags.go index 709e96c..cbd5c5b 100644 --- a/vendor/github.com/digitalocean/godo/tags.go +++ b/vendor/github.com/digitalocean/godo/tags.go @@ -1,8 +1,9 @@ package godo import ( - "context" "fmt" + + "github.com/digitalocean/godo/context" ) const tagsBasePath = "v2/tags" @@ -14,7 +15,6 @@ 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) @@ -65,11 +65,6 @@ 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"` @@ -104,7 +99,7 @@ func (s *TagsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Tag, *Res } root := new(tagsRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -125,7 +120,7 @@ func (s *TagsServiceOp) Get(ctx context.Context, name string) (*Tag, *Response, } root := new(tagRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -145,7 +140,7 @@ func (s *TagsServiceOp) Create(ctx context.Context, createRequest *TagCreateRequ } root := new(tagRoot) - resp, err := s.client.Do(req, root) + resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } @@ -153,27 +148,6 @@ func (s *TagsServiceOp) Create(ctx context.Context, createRequest *TagCreateRequ 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 == "" { @@ -186,7 +160,7 @@ func (s *TagsServiceOp) Delete(ctx context.Context, name string) (*Response, err return nil, err } - resp, err := s.client.Do(req, nil) + resp, err := s.client.Do(ctx, req, nil) return resp, err } @@ -207,7 +181,7 @@ func (s *TagsServiceOp) TagResources(ctx context.Context, name string, tagReques return nil, err } - resp, err := s.client.Do(req, nil) + resp, err := s.client.Do(ctx, req, nil) return resp, err } @@ -228,7 +202,7 @@ func (s *TagsServiceOp) UntagResources(ctx context.Context, name string, untagRe return nil, err } - resp, err := s.client.Do(req, nil) + resp, err := s.client.Do(ctx, req, nil) return resp, err } diff --git a/vendor/github.com/digitalocean/godo/tags_test.go b/vendor/github.com/digitalocean/godo/tags_test.go index 0ced656..677cb37 100644 --- a/vendor/github.com/digitalocean/godo/tags_test.go +++ b/vendor/github.com/digitalocean/godo/tags_test.go @@ -42,7 +42,7 @@ var ( } ], "links": { - "pages":{ + "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", @@ -282,35 +282,6 @@ func TestTags_Create(t *testing.T) { } } -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() diff --git a/vendor/github.com/digitalocean/godo/util/droplet.go b/vendor/github.com/digitalocean/godo/util/droplet.go index e7c48e2..62f8c0e 100644 --- a/vendor/github.com/digitalocean/godo/util/droplet.go +++ b/vendor/github.com/digitalocean/godo/util/droplet.go @@ -1,11 +1,11 @@ package util import ( - "context" "fmt" "time" "github.com/digitalocean/godo" + "github.com/digitalocean/godo/context" ) const ( diff --git a/vendor/github.com/digitalocean/godo/util/droplet_test.go b/vendor/github.com/digitalocean/godo/util/droplet_test.go index fdbca4e..9dad90a 100644 --- a/vendor/github.com/digitalocean/godo/util/droplet_test.go +++ b/vendor/github.com/digitalocean/godo/util/droplet_test.go @@ -1,11 +1,10 @@ package util import ( - "context" - "golang.org/x/oauth2" "github.com/digitalocean/godo" + "github.com/digitalocean/godo/context" ) func ExampleWaitForActive() { @@ -14,14 +13,15 @@ func ExampleWaitForActive() { token := &oauth2.Token{AccessToken: pat} t := oauth2.StaticTokenSource(token) - oauthClient := oauth2.NewClient(oauth2.NoContext, t) + ctx := context.TODO() + oauthClient := oauth2.NewClient(ctx, t) client := godo.NewClient(oauthClient) // create your droplet and retrieve the create action uri uri := "https://api.digitalocean.com/v2/actions/xxxxxxxx" // block until until the action is complete - err := WaitForActive(context.TODO(), client, uri) + err := WaitForActive(ctx, client, uri) if err != nil { panic(err) } diff --git a/vpn.go b/vpn.go index 26cde0a..d6bfd2c 100644 --- a/vpn.go +++ b/vpn.go @@ -57,7 +57,7 @@ func SetupVPN(vpnDetails string) error { args := []string{"-I", "-F", "/tmp/dosxvpn.mobileconfig"} if err := exec.Command(cmd, args...).Run(); err != nil { fmt.Fprintln(os.Stderr, err) - return err + return nil } exec.Command("open", "/System/Library/CoreServices/Menu Extras/VPN.menu/").Start()