diff --git a/CHANGELOG.md b/CHANGELOG.md index 9dda0ed91..7da67a912 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All changes to `doctl` will be documented in this file. +## [1.11.0] - 2018-10-01 + +- #348 Add support for projects API [beta] - @mchitten + ## [1.10.0] - 2018-10-01 - #348 Add support for tagging Images. - @hugocorbucci diff --git a/Dockerfile b/Dockerfile index 9446233de..c920d868d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM alpine:3.5 -ENV DOCTL_VERSION=1.10.0 +ENV DOCTL_VERSION=1.11.0 RUN apk add --no-cache curl diff --git a/README.md b/README.md index eddd592cf..51928cf0d 100644 --- a/README.md +++ b/README.md @@ -58,25 +58,25 @@ For example, with `wget`: ``` cd ~ -wget https://github.com/digitalocean/doctl/releases/download/v1.10.0/doctl-1.10.0-linux-amd64.tar.gz +wget https://github.com/digitalocean/doctl/releases/download/v1.11.0/doctl-1.11.0-linux-amd64.tar.gz ``` Or with `curl`: ``` cd ~ -curl -OL https://github.com/digitalocean/doctl/releases/download/v1.10.0/doctl-1.10.0-linux-amd64.tar.gz +curl -OL https://github.com/digitalocean/doctl/releases/download/v1.11.0/doctl-1.11.0-linux-amd64.tar.gz ``` Extract the binary. On GNU/Linux or OS X systems, you can use `tar`. ``` -tar xf ~/doctl-1.10.0-linux-amd64.tar.gz +tar xf ~/doctl-1.11.0-linux-amd64.tar.gz ``` Or download and extract with this oneliner: ``` -curl -sL https://github.com/digitalocean/doctl/releases/download/v1.10.0/doctl-1.10.0-linux-amd64.tar.gz | tar -xzv +curl -sL https://github.com/digitalocean/doctl/releases/download/v1.11.0/doctl-1.11.0-linux-amd64.tar.gz | tar -xzv ``` On Windows systems, you should be able to double-click the zip archive to extract the `doctl` executable. diff --git a/args.go b/args.go index 8dbb0acc6..271180622 100644 --- a/args.go +++ b/args.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -187,6 +187,19 @@ const ( // ArgOutboundRules is a list of outbound rules for the firewall. ArgOutboundRules = "outbound-rules" + // ArgProjectName is the name of a project. + ArgProjectName = "name" + // ArgProjectDescription is the description of a project. + ArgProjectDescription = "description" + // ArgProjectPurpose is the purpose of a project. + ArgProjectPurpose = "purpose" + // ArgProjectEnvironment is the environment of a project. Should be one of 'Development', 'Staging', 'Production'. + ArgProjectEnvironment = "environment" + // ArgProjectIsDefault is used to change the default project. + ArgProjectIsDefault = "is_default" + // ArgProjectResource is a flag for your resource URNs + ArgProjectResource = "resource" + // ArgForce forces confirmation on actions ArgForce = "force" ) diff --git a/args_short.go b/args_short.go index b0f06b694..3ba5686b6 100644 --- a/args_short.go +++ b/args_short.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/cmd/doctl-gen-doc/main.go b/cmd/doctl-gen-doc/main.go index 6a730b9ac..75eb1a1d6 100644 --- a/cmd/doctl-gen-doc/main.go +++ b/cmd/doctl-gen-doc/main.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/cmd/doctl/main.go b/cmd/doctl/main.go index c602655a6..2cb8f6334 100644 --- a/cmd/doctl/main.go +++ b/cmd/doctl/main.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/cmd/install-doctl/main.go b/cmd/install-doctl/main.go index ce1515163..095b7fa5f 100644 --- a/cmd/install-doctl/main.go +++ b/cmd/install-doctl/main.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/cmd/release-doctl/main.go b/cmd/release-doctl/main.go index 7a9ac0ec8..4148bdbfc 100644 --- a/cmd/release-doctl/main.go +++ b/cmd/release-doctl/main.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/command.go b/command.go index 33e7ff829..142f441eb 100644 --- a/command.go +++ b/command.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/commands/account.go b/commands/account.go index 69c603808..c3ef66eb5 100644 --- a/commands/account.go +++ b/commands/account.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -13,7 +13,10 @@ limitations under the License. package commands -import "github.com/spf13/cobra" +import ( + "github.com/digitalocean/doctl/commands/displayers" + "github.com/spf13/cobra" +) // Account creates the account commands heirarchy. func Account() *Command { @@ -28,10 +31,10 @@ func Account() *Command { } CmdBuilder(cmd, RunAccountGet, "get", "get account", Writer, - aliasOpt("g"), displayerType(&account{}), docCategories("account")) + aliasOpt("g"), displayerType(&displayers.Account{}), docCategories("account")) CmdBuilder(cmd, RunAccountRateLimit, "ratelimit", "get API rate limits", Writer, - aliasOpt("rl"), displayerType(&rateLimit{}), docCategories("account")) + aliasOpt("rl"), displayerType(&displayers.RateLimit{}), docCategories("account")) return cmd } @@ -43,7 +46,7 @@ func RunAccountGet(c *CmdConfig) error { return err } - return c.Display(&account{Account: a}) + return c.Display(&displayers.Account{Account: a}) } // RunAccountRateLimit retrieves API rate limits for the account. @@ -53,5 +56,5 @@ func RunAccountRateLimit(c *CmdConfig) error { return err } - return c.Display(&rateLimit{RateLimit: rl}) + return c.Display(&displayers.RateLimit{RateLimit: rl}) } diff --git a/commands/account_test.go b/commands/account_test.go index 9a797b8f5..0c8527294 100644 --- a/commands/account_test.go +++ b/commands/account_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/commands/actions.go b/commands/actions.go index e29bda48d..199cda158 100644 --- a/commands/actions.go +++ b/commands/actions.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -19,6 +19,7 @@ import ( "time" "github.com/digitalocean/doctl" + "github.com/digitalocean/doctl/commands/displayers" "github.com/digitalocean/doctl/do" "github.com/spf13/cobra" ) @@ -36,10 +37,10 @@ func Actions() *Command { } CmdBuilder(cmd, RunCmdActionGet, "get ", "get action", Writer, - aliasOpt("g"), displayerType(&action{}), docCategories("action")) + aliasOpt("g"), displayerType(&displayers.Action{}), docCategories("action")) cmdActionList := CmdBuilder(cmd, RunCmdActionList, "list", "list actions", Writer, - aliasOpt("ls"), displayerType(&action{}), docCategories("action")) + aliasOpt("ls"), displayerType(&displayers.Action{}), docCategories("action")) AddStringFlag(cmdActionList, doctl.ArgActionResourceType, "", "", "Action resource type") AddStringFlag(cmdActionList, doctl.ArgActionRegion, "", "", "Action region") AddStringFlag(cmdActionList, doctl.ArgActionAfter, "", "", "Action completed after in RFC3339 format") @@ -48,7 +49,7 @@ func Actions() *Command { AddStringFlag(cmdActionList, doctl.ArgActionType, "", "", "Action type") cmdActionWait := CmdBuilder(cmd, RunCmdActionWait, "wait ", "wait for action to complete", Writer, - aliasOpt("w"), displayerType(&action{}), docCategories("action")) + aliasOpt("w"), displayerType(&displayers.Action{}), docCategories("action")) AddIntFlag(cmdActionWait, doctl.ArgPollTime, "", 5, "Re-poll time in seconds") return cmd @@ -68,7 +69,7 @@ func RunCmdActionList(c *CmdConfig) error { sort.Sort(actionsByCompletedAt(actions)) - item := &action{actions: actions} + item := &displayers.Action{Actions: actions} return c.Display(item) } @@ -190,7 +191,7 @@ func RunCmdActionGet(c *CmdConfig) error { return err } - return c.Display(&action{actions: do.Actions{*a}}) + return c.Display(&displayers.Action{Actions: do.Actions{*a}}) } // RunCmdActionWait waits for an action to complete or error. @@ -214,7 +215,7 @@ func RunCmdActionWait(c *CmdConfig) error { return err } - return c.Display(&action{actions: do.Actions{*a}}) + return c.Display(&displayers.Action{Actions: do.Actions{*a}}) } func actionWait(c *CmdConfig, actionID, pollTime int) (*do.Action, error) { diff --git a/commands/actions_test.go b/commands/actions_test.go index ae1d77b7a..74f4640e1 100644 --- a/commands/actions_test.go +++ b/commands/actions_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/commands/auth.go b/commands/auth.go index 9fcbbcf07..ed96fd879 100644 --- a/commands/auth.go +++ b/commands/auth.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/commands/auth_test.go b/commands/auth_test.go index 2f79e5033..b87889e69 100644 --- a/commands/auth_test.go +++ b/commands/auth_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/commands/cdns.go b/commands/cdns.go index 2ddcf7af8..a462051ef 100644 --- a/commands/cdns.go +++ b/commands/cdns.go @@ -18,6 +18,7 @@ import ( "fmt" "github.com/digitalocean/doctl" + "github.com/digitalocean/doctl/commands/displayers" "github.com/digitalocean/doctl/do" "github.com/digitalocean/godo" "github.com/spf13/cobra" @@ -34,10 +35,10 @@ func CDN() *Command { } CmdBuilder(cmd, RunCDNList, "list", "list cdn", Writer, - aliasOpt("ls"), displayerType(&cdn{})) + aliasOpt("ls"), displayerType(&displayers.CDN{})) cmdCDNCreate := CmdBuilder(cmd, RunCDNCreate, "create ", "create a cdn", Writer, - aliasOpt("c"), displayerType(&cdn{})) + aliasOpt("c"), displayerType(&displayers.CDN{})) AddIntFlag(cmdCDNCreate, doctl.ArgCDNTTL, "", 3600, "CDN ttl") cmdRunCDNDelete := CmdBuilder(cmd, RunCDNDelete, "delete ", "delete a cdn", Writer, @@ -45,10 +46,10 @@ func CDN() *Command { AddBoolFlag(cmdRunCDNDelete, doctl.ArgForce, doctl.ArgShortForce, false, "Force cdn delete") CmdBuilder(cmd, RunCDNGet, "get ", "get a cdn", Writer, aliasOpt("g"), - displayerType(&cdn{})) + displayerType(&displayers.CDN{})) cmdCDNUpdate := CmdBuilder(cmd, RunCDNUpdateTTL, "update ", "update a cdn", Writer, - aliasOpt("u"), displayerType(&cdn{})) + aliasOpt("u"), displayerType(&displayers.CDN{})) AddIntFlag(cmdCDNUpdate, doctl.ArgCDNTTL, "", 3600, "cdn ttl") cmdCDNFlushCache := CmdBuilder(cmd, RunCDNFlushCache, "flush ", "flush cdn cache", Writer, @@ -65,7 +66,7 @@ func RunCDNList(c *CmdConfig) error { return err } - return c.Display(&cdn{cdns: cdns}) + return c.Display(&displayers.CDN{CDNs: cdns}) } // RunCDNGet returns an individual CDN. @@ -80,7 +81,7 @@ func RunCDNGet(c *CmdConfig) error { return err } - return c.Display(&cdn{cdns: []do.CDN{*item}}) + return c.Display(&displayers.CDN{CDNs: []do.CDN{*item}}) } // RunCDNCreate creates a cdn. @@ -109,7 +110,7 @@ func RunCDNCreate(c *CmdConfig) error { return err } - return c.Display(&cdn{cdns: []do.CDN{*item}}) + return c.Display(&displayers.CDN{CDNs: []do.CDN{*item}}) } // RunCDNUpdateTTL updates an individual cdn ttl @@ -137,7 +138,7 @@ func RunCDNUpdateTTL(c *CmdConfig) error { return err } - return c.Display(&cdn{cdns: []do.CDN{*item}}) + return c.Display(&displayers.CDN{CDNs: []do.CDN{*item}}) } // RunCDNDelete deletes a cdn. diff --git a/commands/certificates.go b/commands/certificates.go index 6c0a26eee..2c18ca1be 100644 --- a/commands/certificates.go +++ b/commands/certificates.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -18,6 +18,7 @@ import ( "io/ioutil" "github.com/digitalocean/doctl" + "github.com/digitalocean/doctl/commands/displayers" "github.com/digitalocean/doctl/do" "github.com/digitalocean/godo" @@ -65,7 +66,7 @@ func RunCertificateGet(c *CmdConfig) error { return err } - item := &certificate{certificates: do.Certificates{*cer}} + item := &displayers.Certificate{Certificates: do.Certificates{*cer}} return c.Display(item) } @@ -140,7 +141,7 @@ func RunCertificateCreate(c *CmdConfig) error { return err } - item := &certificate{certificates: do.Certificates{*cer}} + item := &displayers.Certificate{Certificates: do.Certificates{*cer}} return c.Display(item) } @@ -152,7 +153,7 @@ func RunCertificateList(c *CmdConfig) error { return err } - item := &certificate{certificates: list} + item := &displayers.Certificate{Certificates: list} return c.Display(item) } diff --git a/commands/command.go b/commands/command.go index 9b14282c3..0eb5840b6 100644 --- a/commands/command.go +++ b/commands/command.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/commands/command_config.go b/commands/command_config.go index befc406be..7edcc2fbb 100644 --- a/commands/command_config.go +++ b/commands/command_config.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -13,6 +13,8 @@ limitations under the License. package commands +import "github.com/digitalocean/doctl/commands/displayers" + // cmdOption allow configuration of a command. type cmdOption func(*Command) @@ -28,7 +30,7 @@ func aliasOpt(aliases ...string) cmdOption { } // displayerType sets the columns for display for a command. -func displayerType(d Displayable) cmdOption { +func displayerType(d displayers.Displayable) cmdOption { return func(c *Command) { c.fmtCols = d.Cols() } diff --git a/commands/commands.go b/commands/commands.go index a974ed242..bf02e5e46 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -12,119 +12,3 @@ limitations under the License. */ package commands - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "strings" - - "github.com/digitalocean/doctl" -) - -// Displayable is a displable entity. These are used for printing results. -type Displayable interface { - Cols() []string - ColMap() map[string]string - KV() []map[string]interface{} - JSON(io.Writer) error -} - -type displayer struct { - ns string - config doctl.Config - item Displayable - out io.Writer -} - -func (d *displayer) Display() error { - output, err := doctl.DoitConfig.GetString(doctl.NSRoot, "output") - if err != nil { - return nil - } - - if output == "" { - output = "text" - } - - switch output { - case "json": - return d.item.JSON(d.out) - case "text": - cols, err := handleColumns(d.ns, d.config) - if err != nil { - return err - } - - return displayText(d.item, d.out, cols) - default: - return fmt.Errorf("unknown output type") - } -} - -func writeJSON(item interface{}, w io.Writer) error { - b, err := json.Marshal(item) - if err != nil { - return err - } - - var out bytes.Buffer - err = json.Indent(&out, b, "", " ") - if err != nil { - return err - } - _, err = out.WriteTo(w) - - return err -} - -func displayText(item Displayable, out io.Writer, includeCols []string) error { - w := newTabWriter(out) - - cols := item.Cols() - if len(includeCols) > 0 && includeCols[0] != "" { - cols = includeCols - } - - if !hc.hideHeader { - headers := []string{} - for _, k := range cols { - col := item.ColMap()[k] - if col == "" { - return fmt.Errorf("unknown column %q", k) - } - - headers = append(headers, col) - } - fmt.Fprintln(w, strings.Join(headers, "\t")) - } - - for _, r := range item.KV() { - values := []interface{}{} - formats := []string{} - - for _, col := range cols { - v := r[col] - - values = append(values, v) - - switch v.(type) { - case string: - formats = append(formats, "%s") - case int: - formats = append(formats, "%d") - case float64: - formats = append(formats, "%f") - case bool: - formats = append(formats, "%v") - default: - formats = append(formats, "%v") - } - } - format := strings.Join(formats, "\t") - fmt.Fprintf(w, format+"\n", values...) - } - - return w.Flush() -} diff --git a/commands/commands_test.go b/commands/commands_test.go index 394e58d4d..a95052e09 100644 --- a/commands/commands_test.go +++ b/commands/commands_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -164,6 +164,7 @@ type tcMocks struct { loadBalancers domocks.LoadBalancersService firewalls domocks.FirewallsService cdns domocks.CDNsService + projects domocks.ProjectsService } func withTestClient(t *testing.T, tFn testFn) { @@ -211,6 +212,7 @@ func withTestClient(t *testing.T, tFn testFn) { LoadBalancers: func() do.LoadBalancersService { return &tm.loadBalancers }, Firewalls: func() do.FirewallsService { return &tm.firewalls }, CDNs: func() do.CDNsService { return &tm.cdns }, + Projects: func() do.ProjectsService { return &tm.projects }, } tFn(config, tm) @@ -235,11 +237,13 @@ func withTestClient(t *testing.T, tFn testFn) { assert.True(t, tm.loadBalancers.AssertExpectations(t)) assert.True(t, tm.firewalls.AssertExpectations(t)) assert.True(t, tm.cdns.AssertExpectations(t)) + assert.True(t, tm.projects.AssertExpectations(t)) } type TestConfig struct { - SSHFn func(user, host, keyPath string, port int, opts ssh.Options) runner.Runner - v *viper.Viper + SSHFn func(user, host, keyPath string, port int, opts ssh.Options) runner.Runner + v *viper.Viper + IsSetMap map[string]bool } var _ doctl.Config = &TestConfig{} @@ -249,7 +253,8 @@ func NewTestConfig() *TestConfig { SSHFn: func(u, h, kp string, p int, opts ssh.Options) runner.Runner { return &doctl.MockRunner{} }, - v: viper.New(), + v: viper.New(), + IsSetMap: make(map[string]bool), } } @@ -268,6 +273,10 @@ func (c *TestConfig) Set(ns, key string, val interface{}) { c.v.Set(nskey, val) } +func (c *TestConfig) IsSet(key string) bool { + return c.IsSetMap[key] +} + func (c *TestConfig) GetString(ns, key string) (string, error) { nskey := fmt.Sprintf("%s-%s", ns, key) return c.v.GetString(nskey), nil diff --git a/commands/completion.go b/commands/completion.go index 5a46652a1..af15359cb 100644 --- a/commands/completion.go +++ b/commands/completion.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -45,7 +45,7 @@ Once installed, you must load bash_completion by adding following line to your .profile or .bashrc/.zshrc: source $(brew --prefix)/etc/bash_completion ` - doctlLicense = `# Copyright 2017 The Doctl Authors All rights reserved. + doctlLicense = `# Copyright 2018 The Doctl Authors All rights reserved. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/commands/displayers/account.go b/commands/displayers/account.go new file mode 100644 index 000000000..d827e1084 --- /dev/null +++ b/commands/displayers/account.go @@ -0,0 +1,55 @@ +/* +Copyright 2018 The Doctl Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package displayers + +import ( + "io" + + "github.com/digitalocean/doctl/do" +) + +type Account struct { + *do.Account +} + +var _ Displayable = &Account{} + +func (a *Account) JSON(out io.Writer) error { + return writeJSON(a.Account, out) +} + +func (a *Account) Cols() []string { + return []string{ + "Email", "DropletLimit", "EmailVerified", "UUID", "Status", + } +} + +func (a *Account) ColMap() map[string]string { + return map[string]string{ + "Email": "Email", "DropletLimit": "Droplet Limit", "EmailVerified": "Email Verified", + "UUID": "UUID", "Status": "Status", + } +} + +func (a *Account) KV() []map[string]interface{} { + out := []map[string]interface{}{} + x := map[string]interface{}{ + "Email": a.Email, "DropletLimit": a.DropletLimit, + "EmailVerified": a.EmailVerified, "UUID": a.UUID, + "Status": a.Status, + } + out = append(out, x) + + return out +} diff --git a/commands/displayers/action.go b/commands/displayers/action.go new file mode 100644 index 000000000..2db5f9bda --- /dev/null +++ b/commands/displayers/action.go @@ -0,0 +1,64 @@ +/* +Copyright 2018 The Doctl Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package displayers + +import ( + "io" + + "github.com/digitalocean/doctl/do" +) + +type Action struct { + Actions do.Actions +} + +var _ Displayable = &Action{} + +func (a *Action) JSON(out io.Writer) error { + return writeJSON(a.Actions, out) +} + +func (a *Action) Cols() []string { + return []string{ + "ID", "Status", "Type", "StartedAt", "CompletedAt", "ResourceID", "ResourceType", "Region", + } +} + +func (a *Action) ColMap() map[string]string { + return map[string]string{ + "ID": "ID", "Status": "Status", "Type": "Type", "StartedAt": "Started At", + "CompletedAt": "Completed At", "ResourceID": "Resource ID", + "ResourceType": "Resource Type", "Region": "Region", + } +} + +func (a *Action) KV() []map[string]interface{} { + out := []map[string]interface{}{} + + for _, x := range a.Actions { + region := "" + if x.Region != nil { + region = x.Region.Slug + } + o := map[string]interface{}{ + "ID": x.ID, "Status": x.Status, "Type": x.Type, + "StartedAt": x.StartedAt, "CompletedAt": x.CompletedAt, + "ResourceID": x.ResourceID, "ResourceType": x.ResourceType, + "Region": region, + } + out = append(out, o) + } + + return out +} diff --git a/commands/displayers/cdn.go b/commands/displayers/cdn.go new file mode 100644 index 000000000..a064a108b --- /dev/null +++ b/commands/displayers/cdn.go @@ -0,0 +1,64 @@ +/* +Copyright 2018 The Doctl Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package displayers + +import ( + "io" + + "github.com/digitalocean/doctl/do" +) + +type CDN struct { + CDNs []do.CDN +} + +var _ Displayable = &CDN{} + +func (c *CDN) JSON(out io.Writer) error { + return writeJSON(c.CDNs, out) +} + +func (c *CDN) Cols() []string { + return []string{ + "ID", "Origin", "Endpoint", "TTL", "CreatedAt", + } +} + +func (c *CDN) ColMap() map[string]string { + return map[string]string{ + "ID": "ID", + "Origin": "Origin", + "Endpoint": "Endpoint", + "TTL": "TTL", + "CreatedAt": "CreatedAt", + } +} + +func (c *CDN) KV() []map[string]interface{} { + out := []map[string]interface{}{} + + for _, cdn := range c.CDNs { + m := map[string]interface{}{ + "ID": cdn.ID, + "Origin": cdn.Origin, + "Endpoint": cdn.Endpoint, + "TTL": cdn.TTL, + "CreatedAt": cdn.CreatedAt, + } + + out = append(out, m) + } + + return out +} diff --git a/commands/displayers/certificate.go b/commands/displayers/certificate.go new file mode 100644 index 000000000..e2c99e886 --- /dev/null +++ b/commands/displayers/certificate.go @@ -0,0 +1,78 @@ +/* +Copyright 2018 The Doctl Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package displayers + +import ( + "fmt" + "io" + "strings" + + "github.com/digitalocean/doctl/do" +) + +type Certificate struct { + Certificates do.Certificates +} + +var _ Displayable = &Certificate{} + +func (c *Certificate) JSON(out io.Writer) error { + return writeJSON(c.Certificates, out) +} + +func (c *Certificate) Cols() []string { + return []string{ + "ID", + "Name", + "DNSNames", + "SHA1Fingerprint", + "NotAfter", + "Created", + "Type", + "State", + } +} + +func (c *Certificate) ColMap() map[string]string { + return map[string]string{ + "ID": "ID", + "Name": "Name", + "DNSNames": "DNS Names", + "SHA1Fingerprint": "SHA-1 Fingerprint", + "NotAfter": "Expiration Date", + "Created": "Created At", + "Type": "Type", + "State": "State", + } +} + +func (c *Certificate) KV() []map[string]interface{} { + out := []map[string]interface{}{} + + for _, c := range c.Certificates { + o := map[string]interface{}{ + "ID": c.ID, + "Name": c.Name, + "DNSNames": fmt.Sprintf(strings.Join(c.DNSNames, ",")), + "SHA1Fingerprint": c.SHA1Fingerprint, + "NotAfter": c.NotAfter, + "Created": c.Created, + "Type": c.Type, + "State": c.State, + } + out = append(out, o) + } + + return out +} diff --git a/commands/displayers/domain.go b/commands/displayers/domain.go new file mode 100644 index 000000000..66f0a4b14 --- /dev/null +++ b/commands/displayers/domain.go @@ -0,0 +1,89 @@ +/* +Copyright 2018 The Doctl Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package displayers + +import ( + "io" + + "github.com/digitalocean/doctl/do" +) + +type Domain struct { + Domains do.Domains +} + +var _ Displayable = &Domain{} + +func (d *Domain) JSON(out io.Writer) error { + return writeJSON(d.Domains, out) +} + +func (d *Domain) Cols() []string { + return []string{"Domain", "TTL"} +} + +func (d *Domain) ColMap() map[string]string { + return map[string]string{ + "Domain": "Domain", "TTL": "TTL", + } +} + +func (d *Domain) KV() []map[string]interface{} { + out := []map[string]interface{}{} + + for _, do := range d.Domains { + o := map[string]interface{}{ + "Domain": do.Name, "TTL": do.TTL, + } + out = append(out, o) + } + + return out +} + +type DomainRecord struct { + DomainRecords do.DomainRecords +} + +func (dr *DomainRecord) JSON(out io.Writer) error { + return writeJSON(dr.DomainRecords, out) +} + +func (dr *DomainRecord) Cols() []string { + return []string{ + "ID", "Type", "Name", "Data", "Priority", "Port", "TTL", "Weight", + } +} + +func (dr *DomainRecord) ColMap() map[string]string { + return map[string]string{ + "ID": "ID", "Type": "Type", "Name": "Name", "Data": "Data", + "Priority": "Priority", "Port": "Port", "TTL": "TTL", "Weight": "Weight", + } +} + +func (dr *DomainRecord) KV() []map[string]interface{} { + out := []map[string]interface{}{} + + for _, d := range dr.DomainRecords { + o := map[string]interface{}{ + "ID": d.ID, "Type": d.Type, "Name": d.Name, + "Data": d.Data, "Priority": d.Priority, + "Port": d.Port, "TTL": d.TTL, "Weight": d.Weight, + } + out = append(out, o) + } + + return out +} diff --git a/commands/displayers/droplet.go b/commands/displayers/droplet.go new file mode 100644 index 000000000..1674211ad --- /dev/null +++ b/commands/displayers/droplet.go @@ -0,0 +1,72 @@ +/* +Copyright 2018 The Doctl Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package displayers + +import ( + "fmt" + "io" + "strings" + + "github.com/digitalocean/doctl/do" +) + +type Droplet struct { + Droplets do.Droplets +} + +var _ Displayable = &Droplet{} + +func (d *Droplet) JSON(out io.Writer) error { + return writeJSON(d.Droplets, out) +} + +func (d *Droplet) Cols() []string { + cols := []string{ + "ID", "Name", "PublicIPv4", "PrivateIPv4", "PublicIPv6", "Memory", "VCPUs", "Disk", "Region", "Image", "Status", "Tags", "Features", "Volumes", + } + return cols +} + +func (d *Droplet) ColMap() map[string]string { + return map[string]string{ + "ID": "ID", "Name": "Name", "PublicIPv4": "Public IPv4", "PrivateIPv4": "Private IPv4", "PublicIPv6": "Public IPv6", + "Memory": "Memory", "VCPUs": "VCPUs", "Disk": "Disk", + "Region": "Region", "Image": "Image", "Status": "Status", + "Tags": "Tags", "Features": "Features", "Volumes": "Volumes", + "SizeSlug": "Size Slug", + } +} + +func (d *Droplet) KV() []map[string]interface{} { + out := []map[string]interface{}{} + for _, d := range d.Droplets { + tags := strings.Join(d.Tags, ",") + image := fmt.Sprintf("%s %s", d.Image.Distribution, d.Image.Name) + ip, _ := d.PublicIPv4() + privIP, _ := d.PrivateIPv4() + ip6, _ := d.PublicIPv6() + features := strings.Join(d.Features, ",") + volumes := strings.Join(d.VolumeIDs, ",") + m := map[string]interface{}{ + "ID": d.ID, "Name": d.Name, "PublicIPv4": ip, "PrivateIPv4": privIP, "PublicIPv6": ip6, + "Memory": d.Memory, "VCPUs": d.Vcpus, "Disk": d.Disk, + "Region": d.Region.Slug, "Image": image, "Status": d.Status, + "Tags": tags, "Features": features, "Volumes": volumes, + "SizeSlug": d.SizeSlug, + } + out = append(out, m) + } + + return out +} diff --git a/commands/displayers/firewall.go b/commands/displayers/firewall.go new file mode 100644 index 000000000..46232ded0 --- /dev/null +++ b/commands/displayers/firewall.go @@ -0,0 +1,148 @@ +/* +Copyright 2018 The Doctl Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package displayers + +import ( + "fmt" + "io" + "strconv" + "strings" + + "github.com/digitalocean/doctl/do" +) + +type Firewall struct { + Firewalls do.Firewalls +} + +var _ Displayable = &Firewall{} + +func (f *Firewall) JSON(out io.Writer) error { + return writeJSON(f.Firewalls, out) +} + +func (f *Firewall) Cols() []string { + return []string{ + "ID", + "Name", + "Status", + "Created", + "InboundRules", + "OutboundRules", + "DropletIDs", + "Tags", + "PendingChanges", + } +} + +func (f *Firewall) ColMap() map[string]string { + return map[string]string{ + "ID": "ID", + "Name": "Name", + "Status": "Status", + "Created": "Created At", + "InboundRules": "Inbound Rules", + "OutboundRules": "Outbound Rules", + "DropletIDs": "Droplet IDs", + "Tags": "Tags", + "PendingChanges": "Pending Changes", + } +} + +func (f *Firewall) KV() []map[string]interface{} { + out := []map[string]interface{}{} + + for _, fw := range f.Firewalls { + irs, ors := firewallRulesPrintHelper(fw) + o := map[string]interface{}{ + "ID": fw.ID, + "Name": fw.Name, + "Status": fw.Status, + "Created": fw.Created, + "InboundRules": irs, + "OutboundRules": ors, + "DropletIDs": dropletListHelper(fw.DropletIDs), + "Tags": strings.Join(fw.Tags, ","), + "PendingChanges": firewallPendingChangesPrintHelper(fw), + } + out = append(out, o) + } + + return out +} + +func firewallRulesPrintHelper(fw do.Firewall) (string, string) { + var irs, ors []string + + for _, ir := range fw.InboundRules { + ss := firewallInAndOutboundRulesPrintHelper(ir.Sources.Addresses, ir.Sources.Tags, ir.Sources.DropletIDs, ir.Sources.LoadBalancerUIDs) + if ir.Protocol == "icmp" { + irs = append(irs, fmt.Sprintf("%v:%v,%v", "protocol", ir.Protocol, ss)) + } else { + irs = append(irs, fmt.Sprintf("%v:%v,%v:%v,%v", "protocol", ir.Protocol, "ports", ir.PortRange, ss)) + } + } + + for _, or := range fw.OutboundRules { + ds := firewallInAndOutboundRulesPrintHelper(or.Destinations.Addresses, or.Destinations.Tags, or.Destinations.DropletIDs, or.Destinations.LoadBalancerUIDs) + if or.Protocol == "icmp" { + ors = append(ors, fmt.Sprintf("%v:%v,%v", "protocol", or.Protocol, ds)) + } else { + ors = append(ors, fmt.Sprintf("%v:%v,%v:%v,%v", "protocol", or.Protocol, "ports", or.PortRange, ds)) + } + } + + return strings.Join(irs, " "), strings.Join(ors, " ") +} + +func firewallInAndOutboundRulesPrintHelper(addresses []string, tags []string, dropletIDs []int, loadBalancerUIDs []string) string { + output := []string{} + resources := map[string][]string{ + "address": addresses, + "tag": tags, + "load_balancer_uid": loadBalancerUIDs, + } + + for k, vs := range resources { + for _, r := range vs { + output = append(output, fmt.Sprintf("%v:%v", k, r)) + } + } + + for _, dID := range dropletIDs { + output = append(output, fmt.Sprintf("%v:%v", "droplet_id", dID)) + } + + return strings.Join(output, ",") +} + +func firewallPendingChangesPrintHelper(fw do.Firewall) string { + output := []string{} + + for _, pc := range fw.PendingChanges { + output = append(output, fmt.Sprintf("%v:%v,%v:%v,%v:%v", "droplet_id", pc.DropletID, "removing", pc.Removing, "status", pc.Status)) + } + + return strings.Join(output, " ") +} + +func dropletListHelper(IDs []int) string { + output := []string{} + + for _, id := range IDs { + output = append(output, strconv.Itoa(id)) + } + + return strings.Join(output, ",") +} diff --git a/commands/displayers/floating_ip.go b/commands/displayers/floating_ip.go new file mode 100644 index 000000000..5d2d7f5d4 --- /dev/null +++ b/commands/displayers/floating_ip.go @@ -0,0 +1,64 @@ +/* +Copyright 2018 The Doctl Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package displayers + +import ( + "fmt" + "io" + + "github.com/digitalocean/doctl/do" +) + +type FloatingIP struct { + FloatingIPs do.FloatingIPs +} + +var _ Displayable = &FloatingIP{} + +func (fi *FloatingIP) JSON(out io.Writer) error { + return writeJSON(fi.FloatingIPs, out) +} + +func (fi *FloatingIP) Cols() []string { + return []string{ + "IP", "Region", "DropletID", "DropletName", + } +} + +func (fi *FloatingIP) ColMap() map[string]string { + return map[string]string{ + "IP": "IP", "Region": "Region", "DropletID": "Droplet ID", "DropletName": "Droplet Name", + } +} + +func (fi *FloatingIP) KV() []map[string]interface{} { + out := []map[string]interface{}{} + + for _, f := range fi.FloatingIPs { + var dropletID, dropletName string + if f.Droplet != nil { + dropletID = fmt.Sprintf("%d", f.Droplet.ID) + dropletName = f.Droplet.Name + } + + o := map[string]interface{}{ + "IP": f.IP, "Region": f.Region.Slug, + "DropletID": dropletID, "DropletName": dropletName, + } + + out = append(out, o) + } + + return out +} diff --git a/commands/header.go b/commands/displayers/header.go similarity index 93% rename from commands/header.go rename to commands/displayers/header.go index bed1a4c35..19691dfb4 100644 --- a/commands/header.go +++ b/commands/displayers/header.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -11,7 +11,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package commands +package displayers import ( "strings" diff --git a/commands/displayers/image.go b/commands/displayers/image.go new file mode 100644 index 000000000..5ad5b966b --- /dev/null +++ b/commands/displayers/image.go @@ -0,0 +1,63 @@ +/* +Copyright 2018 The Doctl Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package displayers + +import ( + "io" + + "github.com/digitalocean/doctl/do" +) + +type Image struct { + Images do.Images +} + +var _ Displayable = &Image{} + +func (gi *Image) JSON(out io.Writer) error { + return writeJSON(gi.Images, out) +} + +func (gi *Image) Cols() []string { + return []string{ + "ID", "Name", "Type", "Distribution", "Slug", "Public", "MinDisk", + } +} + +func (gi *Image) ColMap() map[string]string { + return map[string]string{ + "ID": "ID", "Name": "Name", "Type": "Type", "Distribution": "Distribution", + "Slug": "Slug", "Public": "Public", "MinDisk": "Min Disk", + } +} + +func (gi *Image) KV() []map[string]interface{} { + out := []map[string]interface{}{} + + for _, i := range gi.Images { + publicStatus := false + if i.Public { + publicStatus = true + } + + o := map[string]interface{}{ + "ID": i.ID, "Name": i.Name, "Type": i.Type, "Distribution": i.Distribution, + "Slug": i.Slug, "Public": publicStatus, "MinDisk": i.MinDiskSize, + } + + out = append(out, o) + } + + return out +} diff --git a/commands/displayers/kernel.go b/commands/displayers/kernel.go new file mode 100644 index 000000000..02b9fed0e --- /dev/null +++ b/commands/displayers/kernel.go @@ -0,0 +1,56 @@ +/* +Copyright 2018 The Doctl Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package displayers + +import ( + "io" + + "github.com/digitalocean/doctl/do" +) + +type Kernel struct { + Kernels do.Kernels +} + +var _ Displayable = &Kernel{} + +func (ke *Kernel) JSON(out io.Writer) error { + return writeJSON(ke.Kernels, out) +} + +func (ke *Kernel) Cols() []string { + return []string{ + "ID", "Name", "Version", + } +} + +func (ke *Kernel) ColMap() map[string]string { + return map[string]string{ + "ID": "ID", "Name": "Name", "Version": "Version", + } +} + +func (ke *Kernel) KV() []map[string]interface{} { + out := []map[string]interface{}{} + + for _, k := range ke.Kernels { + o := map[string]interface{}{ + "ID": k.ID, "Name": k.Name, "Version": k.Version, + } + + out = append(out, o) + } + + return out +} diff --git a/commands/displayers/key.go b/commands/displayers/key.go new file mode 100644 index 000000000..fde08c1e5 --- /dev/null +++ b/commands/displayers/key.go @@ -0,0 +1,56 @@ +/* +Copyright 2018 The Doctl Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package displayers + +import ( + "io" + + "github.com/digitalocean/doctl/do" +) + +type Key struct { + Keys do.SSHKeys +} + +var _ Displayable = &Key{} + +func (ke *Key) JSON(out io.Writer) error { + return writeJSON(ke.Keys, out) +} + +func (ke *Key) Cols() []string { + return []string{ + "ID", "Name", "FingerPrint", + } +} + +func (ke *Key) ColMap() map[string]string { + return map[string]string{ + "ID": "ID", "Name": "Name", "FingerPrint": "FingerPrint", + } +} + +func (ke *Key) KV() []map[string]interface{} { + out := []map[string]interface{}{} + + for _, k := range ke.Keys { + o := map[string]interface{}{ + "ID": k.ID, "Name": k.Name, "FingerPrint": k.Fingerprint, + } + + out = append(out, o) + } + + return out +} diff --git a/commands/displayers/load_balancer.go b/commands/displayers/load_balancer.go new file mode 100644 index 000000000..e2b7279de --- /dev/null +++ b/commands/displayers/load_balancer.go @@ -0,0 +1,98 @@ +/* +Copyright 2018 The Doctl Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package displayers + +import ( + "fmt" + "io" + "strings" + + "github.com/digitalocean/doctl/do" +) + +type LoadBalancer struct { + LoadBalancers do.LoadBalancers +} + +var _ Displayable = &LoadBalancer{} + +func (lb *LoadBalancer) JSON(out io.Writer) error { + return writeJSON(lb.LoadBalancers, out) +} + +func (lb *LoadBalancer) Cols() []string { + return []string{ + "ID", + "IP", + "Name", + "Status", + "Created", + "Algorithm", + "Region", + "Tag", + "DropletIDs", + "RedirectHttpToHttps", + "StickySessions", + "HealthCheck", + "ForwardingRules", + } +} + +func (lb *LoadBalancer) ColMap() map[string]string { + return map[string]string{ + "ID": "ID", + "IP": "IP", + "Name": "Name", + "Status": "Status", + "Created": "Created At", + "Algorithm": "Algorithm", + "Region": "Region", + "Tag": "Tag", + "DropletIDs": "Droplet IDs", + "RedirectHttpToHttps": "SSL", + "StickySessions": "Sticky Sessions", + "HealthCheck": "Health Check", + "ForwardingRules": "Forwarding Rules", + } +} + +func (lb *LoadBalancer) KV() []map[string]interface{} { + out := []map[string]interface{}{} + + for _, l := range lb.LoadBalancers { + forwardingRules := []string{} + for _, r := range l.ForwardingRules { + forwardingRules = append(forwardingRules, prettyPrintStruct(r)) + } + + o := map[string]interface{}{ + "ID": l.ID, + "IP": l.IP, + "Name": l.Name, + "Status": l.Status, + "Created": l.Created, + "Algorithm": l.Algorithm, + "Region": l.Region.Slug, + "Tag": l.Tag, + "DropletIDs": fmt.Sprintf(strings.Trim(strings.Replace(fmt.Sprint(l.DropletIDs), " ", ",", -1), "[]")), + "RedirectHttpToHttps": l.RedirectHttpToHttps, + "StickySessions": prettyPrintStruct(l.StickySessions), + "HealthCheck": prettyPrintStruct(l.HealthCheck), + "ForwardingRules": fmt.Sprintf(strings.Join(forwardingRules, " ")), + } + out = append(out, o) + } + + return out +} diff --git a/commands/displayers/output.go b/commands/displayers/output.go new file mode 100644 index 000000000..555421087 --- /dev/null +++ b/commands/displayers/output.go @@ -0,0 +1,170 @@ +/* +Copyright 2018 The Doctl Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package displayers + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "reflect" + "strings" + "text/tabwriter" + + "github.com/digitalocean/doctl" +) + +var ( + hc = &headerControl{} +) + +func newTabWriter(out io.Writer) *tabwriter.Writer { + w := new(tabwriter.Writer) + w.Init(out, 0, 0, 4, ' ', 0) + + return w +} + +type headerControl struct { + hideHeader bool +} + +func (hc *headerControl) HideHeader(hide bool) { + hc.hideHeader = hide +} + +func prettyPrintStruct(obj interface{}) string { + output := []string{} + + defer func() { + if err := recover(); err != nil { + fmt.Printf("Recovered from %v", err) + } + }() + + val := reflect.Indirect(reflect.ValueOf(obj)) + for i := 0; i < val.NumField(); i++ { + k := strings.Split(val.Type().Field(i).Tag.Get("json"), ",")[0] + v := reflect.ValueOf(val.Field(i).Interface()) + output = append(output, fmt.Sprintf("%v:%v", k, v)) + } + + return fmt.Sprintf(strings.Join(output, ",")) +} + +// Displayable is a displable entity. These are used for printing results. +type Displayable interface { + Cols() []string + ColMap() map[string]string + KV() []map[string]interface{} + JSON(io.Writer) error +} + +type Displayer struct { + NS string + Config doctl.Config + Item Displayable + Out io.Writer +} + +func (d *Displayer) Display() error { + output, err := doctl.DoitConfig.GetString(doctl.NSRoot, "output") + if err != nil { + return nil + } + + if output == "" { + output = "text" + } + + switch output { + case "json": + return d.Item.JSON(d.Out) + case "text": + cols, err := handleColumns(d.NS, d.Config) + if err != nil { + return err + } + + return displayText(d.Item, d.Out, cols) + default: + return fmt.Errorf("unknown output type") + } +} + +func writeJSON(item interface{}, w io.Writer) error { + b, err := json.Marshal(item) + if err != nil { + return err + } + + var out bytes.Buffer + err = json.Indent(&out, b, "", " ") + if err != nil { + return err + } + _, err = out.WriteTo(w) + + return err +} + +func displayText(item Displayable, out io.Writer, includeCols []string) error { + w := newTabWriter(out) + + cols := item.Cols() + if len(includeCols) > 0 && includeCols[0] != "" { + cols = includeCols + } + + if !hc.hideHeader { + headers := []string{} + for _, k := range cols { + col := item.ColMap()[k] + if col == "" { + return fmt.Errorf("unknown column %q", k) + } + + headers = append(headers, col) + } + fmt.Fprintln(w, strings.Join(headers, "\t")) + } + + for _, r := range item.KV() { + values := []interface{}{} + formats := []string{} + + for _, col := range cols { + v := r[col] + + values = append(values, v) + + switch v.(type) { + case string: + formats = append(formats, "%s") + case int: + formats = append(formats, "%d") + case float64: + formats = append(formats, "%f") + case bool: + formats = append(formats, "%v") + default: + formats = append(formats, "%v") + } + } + format := strings.Join(formats, "\t") + fmt.Fprintf(w, format+"\n", values...) + } + + return w.Flush() +} diff --git a/commands/displayers/plugin.go b/commands/displayers/plugin.go new file mode 100644 index 000000000..30b086429 --- /dev/null +++ b/commands/displayers/plugin.go @@ -0,0 +1,57 @@ +/* +Copyright 2018 The Doctl Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package displayers + +import "io" + +type PlugDesc struct { + Path string `json:"path"` + Name string `json:"name"` +} + +type Plugin struct { + Plugins []PlugDesc +} + +var _ Displayable = &Plugin{} + +func (p *Plugin) JSON(out io.Writer) error { + return writeJSON(p.Plugins, out) +} + +func (p *Plugin) Cols() []string { + return []string{ + "Name", + } +} + +func (p *Plugin) ColMap() map[string]string { + return map[string]string{ + "Name": "Name", + } +} + +func (p *Plugin) KV() []map[string]interface{} { + out := []map[string]interface{}{} + + for _, plug := range p.Plugins { + o := map[string]interface{}{ + "Name": plug.Name, + } + + out = append(out, o) + } + + return out +} diff --git a/commands/displayers/project.go b/commands/displayers/project.go new file mode 100644 index 000000000..e676611c2 --- /dev/null +++ b/commands/displayers/project.go @@ -0,0 +1,128 @@ +/* +Copyright 2018 The Doctl Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package displayers + +import ( + "io" + + "github.com/digitalocean/doctl/do" +) + +type Project struct { + Projects do.Projects +} + +var _ Displayable = &Project{} + +func (p *Project) JSON(out io.Writer) error { + return writeJSON(p.Projects, out) +} + +func (p *Project) Cols() []string { + return []string{ + "ID", + "OwnerUUID", + "OwnerID", + "Name", + "Description", + "Purpose", + "Environment", + "IsDefault", + "CreatedAt", + "UpdatedAt", + } +} + +func (p *Project) ColMap() map[string]string { + return map[string]string{ + "ID": "ID", + "OwnerUUID": "Owner UUID", + "OwnerID": "Owner ID", + "Name": "Name", + "Description": "Description", + "Purpose": "Purpose", + "Environment": "Environment", + "IsDefault": "Is Default?", + "CreatedAt": "Created At", + "UpdatedAt": "Updated At", + } +} + +func (p *Project) KV() []map[string]interface{} { + out := []map[string]interface{}{} + + for _, pr := range p.Projects { + o := map[string]interface{}{ + "ID": pr.ID, + "OwnerUUID": pr.OwnerUUID, + "OwnerID": pr.OwnerID, + "Name": pr.Name, + "Description": pr.Description, + "Purpose": pr.Purpose, + "Environment": pr.Environment, + "IsDefault": pr.IsDefault, + "CreatedAt": pr.CreatedAt, + "UpdatedAt": pr.UpdatedAt, + } + out = append(out, o) + } + + return out +} + +type ProjectResource struct { + ProjectResources do.ProjectResources +} + +var _ Displayable = &ProjectResource{} + +func (p *ProjectResource) JSON(out io.Writer) error { + return writeJSON(p.ProjectResources, out) +} + +func (p *ProjectResource) Cols() []string { + return []string{ + "URN", + "AssignedAt", + "Status", + } +} + +func (p *ProjectResource) ColMap() map[string]string { + return map[string]string{ + "URN": "URN", + "AssignedAt": "Assigned At", + "Status": "Status", + } +} + +func (p *ProjectResource) KV() []map[string]interface{} { + out := []map[string]interface{}{} + + for _, pr := range p.ProjectResources { + assignedAt := pr.AssignedAt + if assignedAt == "" { + assignedAt = "N/A" + } + + o := map[string]interface{}{ + "URN": pr.URN, + "AssignedAt": assignedAt, + "Status": pr.Status, + } + out = append(out, o) + } + + return out +} diff --git a/commands/displayers/rate_limit.go b/commands/displayers/rate_limit.go new file mode 100644 index 000000000..adeedab95 --- /dev/null +++ b/commands/displayers/rate_limit.go @@ -0,0 +1,52 @@ +/* +Copyright 2018 The Doctl Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package displayers + +import ( + "io" + + "github.com/digitalocean/doctl/do" +) + +type RateLimit struct { + *do.RateLimit +} + +var _ Displayable = &RateLimit{} + +func (rl *RateLimit) JSON(out io.Writer) error { + return writeJSON(rl.Rate, out) +} + +func (rl *RateLimit) Cols() []string { + return []string{ + "Limit", "Remaining", "Reset", + } +} + +func (rl *RateLimit) ColMap() map[string]string { + return map[string]string{ + "Limit": "Limit", "Remaining": "Remaining", "Reset": "Reset", + } +} + +func (rl *RateLimit) KV() []map[string]interface{} { + out := []map[string]interface{}{} + x := map[string]interface{}{ + "Limit": rl.Limit, "Remaining": rl.Remaining, "Reset": rl.Reset, + } + out = append(out, x) + + return out +} diff --git a/commands/displayers/region.go b/commands/displayers/region.go new file mode 100644 index 000000000..2419d9bd0 --- /dev/null +++ b/commands/displayers/region.go @@ -0,0 +1,56 @@ +/* +Copyright 2018 The Doctl Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package displayers + +import ( + "io" + + "github.com/digitalocean/doctl/do" +) + +type Region struct { + Regions do.Regions +} + +var _ Displayable = &Region{} + +func (re *Region) JSON(out io.Writer) error { + return writeJSON(re.Regions, out) +} + +func (re *Region) Cols() []string { + return []string{ + "Slug", "Name", "Available", + } +} + +func (re *Region) ColMap() map[string]string { + return map[string]string{ + "Slug": "Slug", "Name": "Name", "Available": "Available", + } +} + +func (re *Region) KV() []map[string]interface{} { + out := []map[string]interface{}{} + + for _, r := range re.Regions { + o := map[string]interface{}{ + "Slug": r.Slug, "Name": r.Name, "Available": r.Available, + } + + out = append(out, o) + } + + return out +} diff --git a/commands/displayers/size.go b/commands/displayers/size.go new file mode 100644 index 000000000..e483569bb --- /dev/null +++ b/commands/displayers/size.go @@ -0,0 +1,61 @@ +/* +Copyright 2018 The Doctl Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package displayers + +import ( + "fmt" + "io" + + "github.com/digitalocean/doctl/do" +) + +type Size struct { + Sizes do.Sizes +} + +var _ Displayable = &Size{} + +func (si *Size) JSON(out io.Writer) error { + return writeJSON(si.Sizes, out) +} + +func (si *Size) Cols() []string { + return []string{ + "Slug", "Memory", "VCPUs", "Disk", "PriceMonthly", "PriceHourly", + } +} + +func (si *Size) ColMap() map[string]string { + return map[string]string{ + "Slug": "Slug", "Memory": "Memory", "VCPUs": "VCPUs", + "Disk": "Disk", "PriceMonthly": "Price Monthly", + "PriceHourly": "Price Hourly", + } +} + +func (si *Size) KV() []map[string]interface{} { + out := []map[string]interface{}{} + + for _, s := range si.Sizes { + o := map[string]interface{}{ + "Slug": s.Slug, "Memory": s.Memory, "VCPUs": s.Vcpus, + "Disk": s.Disk, "PriceMonthly": fmt.Sprintf("%0.2f", s.PriceMonthly), + "PriceHourly": s.PriceHourly, + } + + out = append(out, o) + } + + return out +} diff --git a/commands/displayers/snapshot.go b/commands/displayers/snapshot.go new file mode 100644 index 000000000..2b25c656a --- /dev/null +++ b/commands/displayers/snapshot.go @@ -0,0 +1,57 @@ +/* +Copyright 2018 The Doctl Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package displayers + +import ( + "io" + "strconv" + + "github.com/digitalocean/doctl/do" +) + +type Snapshot struct { + Snapshots do.Snapshots +} + +var _ Displayable = &Snapshot{} + +func (s *Snapshot) JSON(out io.Writer) error { + return writeJSON(s.Snapshots, out) +} + +func (s *Snapshot) Cols() []string { + return []string{"ID", "Name", "CreatedAt", "Regions", "ResourceId", + "ResourceType", "MinDiskSize", "Size"} +} + +func (s *Snapshot) ColMap() map[string]string { + return map[string]string{ + "ID": "ID", "Name": "Name", "CreatedAt": "Created at", "Regions": "Regions", + "ResourceId": "Resource ID", "ResourceType": "Resource Type", "MinDiskSize": "Min Disk Size", "Size": "Size"} +} + +func (s *Snapshot) KV() []map[string]interface{} { + out := []map[string]interface{}{} + + for _, ss := range s.Snapshots { + o := map[string]interface{}{ + "ID": ss.ID, "Name": ss.Name, "ResourceId": ss.ResourceID, + "ResourceType": ss.ResourceType, "Regions": ss.Regions, "MinDiskSize": ss.MinDiskSize, + "Size": strconv.FormatFloat(ss.SizeGigaBytes, 'f', 2, 64) + " GiB", "CreatedAt": ss.Created, + } + out = append(out, o) + } + + return out +} diff --git a/commands/displayers/tag.go b/commands/displayers/tag.go new file mode 100644 index 000000000..18ef51ff9 --- /dev/null +++ b/commands/displayers/tag.go @@ -0,0 +1,56 @@ +/* +Copyright 2018 The Doctl Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package displayers + +import ( + "io" + + "github.com/digitalocean/doctl/do" +) + +type Tag struct { + Tags do.Tags +} + +var _ Displayable = &Tag{} + +func (t *Tag) JSON(out io.Writer) error { + return writeJSON(t.Tags, out) +} + +func (t *Tag) Cols() []string { + return []string{"Name", "DropletCount"} +} + +func (t *Tag) ColMap() map[string]string { + return map[string]string{ + "Name": "Name", + "DropletCount": "Droplet Count", + } +} + +func (t *Tag) KV() []map[string]interface{} { + out := []map[string]interface{}{} + + for _, x := range t.Tags { + dropletCount := x.Resources.Droplets.Count + o := map[string]interface{}{ + "Name": x.Name, + "DropletCount": dropletCount, + } + out = append(out, o) + } + + return out +} diff --git a/commands/displayers/volume.go b/commands/displayers/volume.go new file mode 100644 index 000000000..1a4517e05 --- /dev/null +++ b/commands/displayers/volume.go @@ -0,0 +1,73 @@ +/* +Copyright 2018 The Doctl Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package displayers + +import ( + "fmt" + "io" + "strconv" + + "github.com/digitalocean/doctl/do" +) + +type Volume struct { + Volumes []do.Volume +} + +var _ Displayable = &Volume{} + +func (a *Volume) JSON(out io.Writer) error { + return writeJSON(a.Volumes, out) + +} + +func (a *Volume) Cols() []string { + return []string{ + "ID", "Name", "Size", "Region", "Filesystem Type", "Filesystem Label", "Droplet IDs", + } +} + +func (a *Volume) ColMap() map[string]string { + return map[string]string{ + "ID": "ID", + "Name": "Name", + "Size": "Size", + "Region": "Region", + "Filesystem Type": "Filesystem Type", + "Filesystem Label": "Filesystem Label", + "Droplet IDs": "Droplet IDs", + } + +} + +func (a *Volume) KV() []map[string]interface{} { + out := []map[string]interface{}{} + for _, volume := range a.Volumes { + m := map[string]interface{}{ + "ID": volume.ID, + "Name": volume.Name, + "Size": strconv.FormatInt(volume.SizeGigaBytes, 10) + " GiB", + "Region": volume.Region.Slug, + "Filesystem Type": volume.FilesystemType, + "Filesystem Label": volume.FilesystemLabel, + } + m["DropletIDs"] = "" + if len(volume.DropletIDs) != 0 { + m["DropletIDs"] = fmt.Sprintf("%v", volume.DropletIDs) + } + out = append(out, m) + + } + return out +} diff --git a/commands/doit.go b/commands/doit.go index 1201ca115..f3967238a 100644 --- a/commands/doit.go +++ b/commands/doit.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -25,6 +25,7 @@ import ( "gopkg.in/yaml.v2" "github.com/digitalocean/doctl" + "github.com/digitalocean/doctl/commands/displayers" "github.com/digitalocean/doctl/do" "github.com/fatih/color" "github.com/spf13/cobra" @@ -173,6 +174,7 @@ func addCommands() { DoitCmd.AddCommand(Auth()) DoitCmd.AddCommand(Completion()) DoitCmd.AddCommand(computeCmd()) + DoitCmd.AddCommand(Projects()) DoitCmd.AddCommand(Version()) } @@ -218,6 +220,7 @@ type flagOpt func(c *Command, name, key string) func requiredOpt() flagOpt { return func(c *Command, name, key string) { c.MarkFlagRequired(key) + key = requiredKey(key) viper.Set(key, true) @@ -226,16 +229,16 @@ func requiredOpt() flagOpt { } } +func requiredKey(key string) string { + return fmt.Sprintf("required.%s", key) +} + func betaOpt() flagOpt { return func(c *Command, name, key string) { c.Flag(name).Hidden = !isBeta() } } -func requiredKey(key string) string { - return fmt.Sprintf("%s.required", key) -} - func isBeta() bool { return viper.GetBool("enable-beta") } @@ -338,6 +341,7 @@ type CmdConfig struct { Certificates func() do.CertificatesService Firewalls func() do.FirewallsService CDNs func() do.CDNsService + Projects func() do.ProjectsService } // NewCmdConfig creates an instance of a CmdConfig. @@ -376,6 +380,7 @@ func NewCmdConfig(ns string, dc doctl.Config, out io.Writer, args []string, init c.LoadBalancers = func() do.LoadBalancersService { return do.NewLoadBalancersService(godoClient) } c.Firewalls = func() do.FirewallsService { return do.NewFirewallsService(godoClient) } c.CDNs = func() do.CDNsService { return do.NewCDNsService(godoClient) } + c.Projects = func() do.ProjectsService { return do.NewProjectsService(godoClient) } return nil }, @@ -427,12 +432,12 @@ func NewCmdConfig(ns string, dc doctl.Config, out io.Writer, args []string, init } // Display displayes the output from a command. -func (c *CmdConfig) Display(d Displayable) error { - dc := &displayer{ - ns: c.NS, - config: c.Doit, - item: d, - out: c.Out, +func (c *CmdConfig) Display(d displayers.Displayable) error { + dc := &displayers.Displayer{ + NS: c.NS, + Config: c.Doit, + Item: d, + Out: c.Out, } return dc.Display() diff --git a/commands/domains.go b/commands/domains.go index 1fc57761b..2dd3590bf 100644 --- a/commands/domains.go +++ b/commands/domains.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -19,6 +19,7 @@ import ( "strconv" "github.com/digitalocean/doctl" + "github.com/digitalocean/doctl/commands/displayers" "github.com/digitalocean/doctl/do" "github.com/digitalocean/godo" "github.com/spf13/cobra" @@ -37,14 +38,14 @@ func Domain() *Command { } cmdDomainCreate := CmdBuilder(cmd, RunDomainCreate, "create ", "create domain", Writer, - aliasOpt("c"), displayerType(&domain{}), docCategories("domain")) + aliasOpt("c"), displayerType(&displayers.Domain{}), docCategories("domain")) AddStringFlag(cmdDomainCreate, doctl.ArgIPAddress, "", "", "IP address", requiredOpt()) CmdBuilder(cmd, RunDomainList, "list", "list domains", Writer, - aliasOpt("ls"), displayerType(&domain{}), docCategories("domain")) + aliasOpt("ls"), displayerType(&displayers.Domain{}), docCategories("domain")) CmdBuilder(cmd, RunDomainGet, "get ", "get domain", Writer, - aliasOpt("g"), displayerType(&domain{}), docCategories("domain")) + aliasOpt("g"), displayerType(&displayers.Domain{}), docCategories("domain")) cmdRunDomainDelete := CmdBuilder(cmd, RunDomainDelete, "delete ", "delete domain", Writer, aliasOpt("g")) AddBoolFlag(cmdRunDomainDelete, doctl.ArgForce, doctl.ArgShortForce, false, "Force domain delete") @@ -59,10 +60,10 @@ func Domain() *Command { cmd.AddCommand(cmdRecord) CmdBuilder(cmdRecord, RunRecordList, "list ", "list records", Writer, - aliasOpt("ls"), displayerType(&domainRecord{}), docCategories("domain")) + aliasOpt("ls"), displayerType(&displayers.DomainRecord{}), docCategories("domain")) cmdRecordCreate := CmdBuilder(cmdRecord, RunRecordCreate, "create ", "create record", Writer, - aliasOpt("c"), displayerType(&domainRecord{}), docCategories("domain")) + aliasOpt("c"), displayerType(&displayers.DomainRecord{}), docCategories("domain")) AddStringFlag(cmdRecordCreate, doctl.ArgRecordType, "", "", "Record type") AddStringFlag(cmdRecordCreate, doctl.ArgRecordName, "", "", "Record name") AddStringFlag(cmdRecordCreate, doctl.ArgRecordData, "", "", "Record data") @@ -78,7 +79,7 @@ func Domain() *Command { AddBoolFlag(cmdRunRecordDelete, doctl.ArgForce, doctl.ArgShortForce, false, "Force record delete") cmdRecordUpdate := CmdBuilder(cmdRecord, RunRecordUpdate, "update ", "update record", Writer, - aliasOpt("u"), displayerType(&domainRecord{}), docCategories("domain")) + aliasOpt("u"), displayerType(&displayers.DomainRecord{}), docCategories("domain")) AddIntFlag(cmdRecordUpdate, doctl.ArgRecordID, "", 0, "Record ID") AddStringFlag(cmdRecordUpdate, doctl.ArgRecordType, "", "", "Record type") AddStringFlag(cmdRecordUpdate, doctl.ArgRecordName, "", "", "Record name") @@ -117,7 +118,7 @@ func RunDomainCreate(c *CmdConfig) error { return err } - return c.Display(&domain{domains: do.Domains{*d}}) + return c.Display(&displayers.Domain{Domains: do.Domains{*d}}) } // RunDomainList runs domain create. @@ -130,7 +131,7 @@ func RunDomainList(c *CmdConfig) error { return err } - item := &domain{domains: domains} + item := &displayers.Domain{Domains: domains} return c.Display(item) } @@ -152,7 +153,7 @@ func RunDomainGet(c *CmdConfig) error { return err } - item := &domain{domains: do.Domains{*d}} + item := &displayers.Domain{Domains: do.Domains{*d}} return c.Display(item) } @@ -202,7 +203,7 @@ func RunRecordList(c *CmdConfig) error { return err } - items := &domainRecord{domainRecords: list} + items := &displayers.DomainRecord{DomainRecords: list} return c.Display(items) } @@ -282,7 +283,7 @@ func RunRecordCreate(c *CmdConfig) error { return err } - item := &domainRecord{domainRecords: do.DomainRecords{*r}} + item := &displayers.DomainRecord{DomainRecords: do.DomainRecords{*r}} return c.Display(item) } @@ -401,6 +402,6 @@ func RunRecordUpdate(c *CmdConfig) error { return err } - item := &domainRecord{domainRecords: do.DomainRecords{*r}} + item := &displayers.DomainRecord{DomainRecords: do.DomainRecords{*r}} return c.Display(item) } diff --git a/commands/domains_test.go b/commands/domains_test.go index 1b1696fe8..9ba0de3d6 100644 --- a/commands/domains_test.go +++ b/commands/domains_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/commands/droplet_actions.go b/commands/droplet_actions.go index 1333f7fbe..ad931c4ed 100644 --- a/commands/droplet_actions.go +++ b/commands/droplet_actions.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,6 +17,7 @@ import ( "strconv" "github.com/digitalocean/doctl" + "github.com/digitalocean/doctl/commands/displayers" "github.com/digitalocean/doctl/do" "github.com/spf13/cobra" ) @@ -44,7 +45,7 @@ func performAction(c *CmdConfig, fn actionFn) error { } - item := &action{actions: do.Actions{*a}} + item := &displayers.Action{Actions: do.Actions{*a}} return c.Display(item) } @@ -60,93 +61,93 @@ func DropletAction() *Command { } cmdDropletActionGet := CmdBuilder(cmd, RunDropletActionGet, "get ", "get droplet action", Writer, - aliasOpt("g"), displayerType(&action{}), docCategories("droplet")) + aliasOpt("g"), displayerType(&displayers.Action{}), docCategories("droplet")) AddIntFlag(cmdDropletActionGet, doctl.ArgActionID, "", 0, "Action ID", requiredOpt()) cmdDropletActionEnableBackups := CmdBuilder(cmd, RunDropletActionEnableBackups, "enable-backups ", "enable backups", Writer, - displayerType(&action{}), docCategories("droplet")) + displayerType(&displayers.Action{}), docCategories("droplet")) AddBoolFlag(cmdDropletActionEnableBackups, doctl.ArgCommandWait, "", false, "Wait for action to complete") cmdDropletActionDisableBackups := CmdBuilder(cmd, RunDropletActionDisableBackups, "disable-backups ", "disable backups", Writer, - displayerType(&action{}), docCategories("droplet")) + displayerType(&displayers.Action{}), docCategories("droplet")) AddBoolFlag(cmdDropletActionDisableBackups, doctl.ArgCommandWait, "", false, "Wait for action to complete") cmdDropletActionReboot := CmdBuilder(cmd, RunDropletActionReboot, "reboot ", "reboot droplet", Writer, - displayerType(&action{}), docCategories("droplet")) + displayerType(&displayers.Action{}), docCategories("droplet")) AddBoolFlag(cmdDropletActionReboot, doctl.ArgCommandWait, "", false, "Wait for action to complete") cmdDropletActionPowerCycle := CmdBuilder(cmd, RunDropletActionPowerCycle, "power-cycle ", "power cycle droplet", Writer, - displayerType(&action{}), docCategories("droplet")) + displayerType(&displayers.Action{}), docCategories("droplet")) AddBoolFlag(cmdDropletActionPowerCycle, doctl.ArgCommandWait, "", false, "Wait for action to complete") cmdDropletActionShutdown := CmdBuilder(cmd, RunDropletActionShutdown, "shutdown ", "shutdown droplet", Writer, - displayerType(&action{}), docCategories("droplet")) + displayerType(&displayers.Action{}), docCategories("droplet")) AddBoolFlag(cmdDropletActionShutdown, doctl.ArgCommandWait, "", false, "Wait for action to complete") cmdDropletActionPowerOff := CmdBuilder(cmd, RunDropletActionPowerOff, "power-off ", "power off droplet", Writer, - displayerType(&action{}), docCategories("droplet")) + displayerType(&displayers.Action{}), docCategories("droplet")) AddBoolFlag(cmdDropletActionPowerOff, doctl.ArgCommandWait, "", false, "Wait for action to complete") cmdDropletActionPowerOn := CmdBuilder(cmd, RunDropletActionPowerOn, "power-on ", "power on droplet", Writer, - displayerType(&action{}), docCategories("droplet")) + displayerType(&displayers.Action{}), docCategories("droplet")) AddBoolFlag(cmdDropletActionPowerOn, doctl.ArgCommandWait, "", false, "Wait for action to complete") cmdDropletActionPasswordReset := CmdBuilder(cmd, RunDropletActionPasswordReset, "password-reset ", "password reset droplet", Writer, - displayerType(&action{}), docCategories("droplet")) + displayerType(&displayers.Action{}), docCategories("droplet")) AddBoolFlag(cmdDropletActionPasswordReset, doctl.ArgCommandWait, "", false, "Wait for action to complete") cmdDropletActionEnableIPv6 := CmdBuilder(cmd, RunDropletActionEnableIPv6, "enable-ipv6 ", "enable ipv6", Writer, - displayerType(&action{}), docCategories("droplet")) + displayerType(&displayers.Action{}), docCategories("droplet")) AddBoolFlag(cmdDropletActionEnableIPv6, doctl.ArgCommandWait, "", false, "Wait for action to complete") cmdDropletActionEnablePrivateNetworking := CmdBuilder(cmd, RunDropletActionEnablePrivateNetworking, "enable-private-networking ", "enable private networking", Writer, - displayerType(&action{}), docCategories("droplet")) + displayerType(&displayers.Action{}), docCategories("droplet")) AddBoolFlag(cmdDropletActionEnablePrivateNetworking, doctl.ArgCommandWait, "", false, "Wait for action to complete") cmdDropletActionRestore := CmdBuilder(cmd, RunDropletActionRestore, "restore ", "restore backup", Writer, - displayerType(&action{}), docCategories("droplet")) + displayerType(&displayers.Action{}), docCategories("droplet")) AddIntFlag(cmdDropletActionRestore, doctl.ArgImageID, "", 0, "Image ID", requiredOpt()) AddBoolFlag(cmdDropletActionRestore, doctl.ArgCommandWait, "", false, "Wait for action to complete") cmdDropletActionResize := CmdBuilder(cmd, RunDropletActionResize, "resize ", "resize droplet", Writer, - displayerType(&action{}), docCategories("droplet")) + displayerType(&displayers.Action{}), docCategories("droplet")) AddBoolFlag(cmdDropletActionResize, doctl.ArgResizeDisk, "", false, "Resize disk") AddStringFlag(cmdDropletActionResize, doctl.ArgSizeSlug, "", "", "New size") AddBoolFlag(cmdDropletActionResize, doctl.ArgCommandWait, "", false, "Wait for action to complete") cmdDropletActionRebuild := CmdBuilder(cmd, RunDropletActionRebuild, "rebuild ", "rebuild droplet", Writer, - displayerType(&action{}), docCategories("droplet")) + displayerType(&displayers.Action{}), docCategories("droplet")) AddStringFlag(cmdDropletActionRebuild, doctl.ArgImage, "", "", "Image ID or Slug", requiredOpt()) AddBoolFlag(cmdDropletActionRebuild, doctl.ArgCommandWait, "", false, "Wait for action to complete") cmdDropletActionRename := CmdBuilder(cmd, RunDropletActionRename, "rename ", "rename droplet", Writer, - displayerType(&action{}), docCategories("droplet")) + displayerType(&displayers.Action{}), docCategories("droplet")) AddStringFlag(cmdDropletActionRename, doctl.ArgDropletName, "", "", "Droplet name", requiredOpt()) AddBoolFlag(cmdDropletActionRename, doctl.ArgCommandWait, "", false, "Wait for action to complete") cmdDropletActionChangeKernel := CmdBuilder(cmd, RunDropletActionChangeKernel, "change-kernel ", "change kernel", Writer, - displayerType(&action{}), docCategories("droplet")) + displayerType(&displayers.Action{}), docCategories("droplet")) AddIntFlag(cmdDropletActionChangeKernel, doctl.ArgKernelID, "", 0, "Kernel ID", requiredOpt()) AddBoolFlag(cmdDropletActionChangeKernel, doctl.ArgCommandWait, "", false, "Wait for action to complete") cmdDropletActionSnapshot := CmdBuilder(cmd, RunDropletActionSnapshot, "snapshot ", "snapshot droplet", Writer, - displayerType(&action{}), docCategories("droplet")) + displayerType(&displayers.Action{}), docCategories("droplet")) AddStringFlag(cmdDropletActionSnapshot, doctl.ArgSnapshotName, "", "", "Snapshot name", requiredOpt()) AddBoolFlag(cmdDropletActionSnapshot, doctl.ArgCommandWait, "", false, "Wait for action to complete") diff --git a/commands/droplet_actions_test.go b/commands/droplet_actions_test.go index 690816ff7..c771c17fb 100644 --- a/commands/droplet_actions_test.go +++ b/commands/droplet_actions_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/commands/droplets.go b/commands/droplets.go index f302dfbf2..3a7fc7682 100644 --- a/commands/droplets.go +++ b/commands/droplets.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -23,6 +23,7 @@ import ( "text/template" "github.com/digitalocean/doctl" + "github.com/digitalocean/doctl/commands/displayers" "github.com/digitalocean/doctl/do" "github.com/digitalocean/godo" "github.com/gobwas/glob" @@ -44,13 +45,13 @@ func Droplet() *Command { } CmdBuilder(cmd, RunDropletActions, "actions ", "droplet actions", Writer, - aliasOpt("a"), displayerType(&action{}), docCategories("droplet")) + aliasOpt("a"), displayerType(&displayers.Action{}), docCategories("droplet")) CmdBuilder(cmd, RunDropletBackups, "backups ", "droplet backups", Writer, - aliasOpt("b"), displayerType(&image{}), docCategories("droplet")) + aliasOpt("b"), displayerType(&displayers.Image{}), docCategories("droplet")) cmdDropletCreate := CmdBuilder(cmd, RunDropletCreate, "create [droplet-name ...]", "create droplet", Writer, - aliasOpt("c"), displayerType(&droplet{}), docCategories("droplet")) + aliasOpt("c"), displayerType(&displayers.Droplet{}), docCategories("droplet")) AddStringSliceFlag(cmdDropletCreate, doctl.ArgSSHKeys, "", []string{}, "SSH Keys or fingerprints") AddStringFlag(cmdDropletCreate, doctl.ArgUserData, "", "", "User data") AddStringFlag(cmdDropletCreate, doctl.ArgUserDataFile, "", "", "User data file") @@ -75,22 +76,22 @@ func Droplet() *Command { AddBoolFlag(cmdRunDropletDelete, doctl.ArgForce, doctl.ArgShortForce, false, "Force droplet delete") cmdRunDropletGet := CmdBuilder(cmd, RunDropletGet, "get ", "get droplet", Writer, - aliasOpt("g"), displayerType(&droplet{}), docCategories("droplet")) + aliasOpt("g"), displayerType(&displayers.Droplet{}), docCategories("droplet")) AddStringFlag(cmdRunDropletGet, doctl.ArgTemplate, "", "", "Go template format. Few sample values:{{.ID}} {{.Name}} {{.Memory}} {{.Region.Name}} {{.Image}} {{.Tags}}") CmdBuilder(cmd, RunDropletKernels, "kernels ", "droplet kernels", Writer, - aliasOpt("k"), displayerType(&kernel{}), docCategories("droplet")) + aliasOpt("k"), displayerType(&displayers.Kernel{}), docCategories("droplet")) cmdRunDropletList := CmdBuilder(cmd, RunDropletList, "list [GLOB]", "list droplets", Writer, - aliasOpt("ls"), displayerType(&droplet{}), docCategories("droplet")) + aliasOpt("ls"), displayerType(&displayers.Droplet{}), docCategories("droplet")) AddStringFlag(cmdRunDropletList, doctl.ArgRegionSlug, "", "", "Droplet region") AddStringFlag(cmdRunDropletList, doctl.ArgTagName, "", "", "Tag name") CmdBuilder(cmd, RunDropletNeighbors, "neighbors ", "droplet neighbors", Writer, - aliasOpt("n"), displayerType(&droplet{}), docCategories("droplet")) + aliasOpt("n"), displayerType(&displayers.Droplet{}), docCategories("droplet")) CmdBuilder(cmd, RunDropletSnapshots, "snapshots ", "snapshots", Writer, - aliasOpt("s"), displayerType(&image{}), docCategories("droplet")) + aliasOpt("s"), displayerType(&displayers.Image{}), docCategories("droplet")) cmdRunDropletTag := CmdBuilder(cmd, RunDropletTag, "tag ", "tag", Writer, docCategories("droplet")) @@ -118,7 +119,7 @@ func RunDropletActions(c *CmdConfig) error { if err != nil { return err } - item := &action{actions: list} + item := &displayers.Action{Actions: list} return c.Display(item) } @@ -137,7 +138,7 @@ func RunDropletBackups(c *CmdConfig) error { return err } - item := &image{images: list} + item := &displayers.Image{Images: list} return c.Display(item) } @@ -298,7 +299,7 @@ func RunDropletCreate(c *CmdConfig) error { wg.Wait() close(errs) - item := &droplet{droplets: createdList} + item := &displayers.Droplet{Droplets: createdList} for err := range errs { if err != nil { @@ -553,7 +554,7 @@ func RunDropletGet(c *CmdConfig) error { return err } - item := &droplet{droplets: do.Droplets{*d}} + item := &displayers.Droplet{Droplets: do.Droplets{*d}} if getTemplate != "" { t := template.New("get template") t, err = t.Parse(getTemplate) @@ -579,7 +580,7 @@ func RunDropletKernels(c *CmdConfig) error { return err } - item := &kernel{kernels: list} + item := &displayers.Kernel{Kernels: list} return c.Display(item) } @@ -643,7 +644,7 @@ func RunDropletList(c *CmdConfig) error { } } - item := &droplet{droplets: matchedList} + item := &displayers.Droplet{Droplets: matchedList} return c.Display(item) } @@ -662,7 +663,7 @@ func RunDropletNeighbors(c *CmdConfig) error { return err } - item := &droplet{droplets: list} + item := &displayers.Droplet{Droplets: list} return c.Display(item) } @@ -680,7 +681,7 @@ func RunDropletSnapshots(c *CmdConfig) error { return err } - item := &image{images: list} + item := &displayers.Image{Images: list} return c.Display(item) } diff --git a/commands/droplets_test.go b/commands/droplets_test.go index d7577b028..4e88daa6d 100644 --- a/commands/droplets_test.go +++ b/commands/droplets_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/commands/error_test.go b/commands/error_test.go index ebfcaa65f..fabf7b5f2 100644 --- a/commands/error_test.go +++ b/commands/error_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/commands/errors.go b/commands/errors.go index 7c14db78d..2bd0dad71 100644 --- a/commands/errors.go +++ b/commands/errors.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/commands/firewalls.go b/commands/firewalls.go index 38c7ff292..6635c4a95 100644 --- a/commands/firewalls.go +++ b/commands/firewalls.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -20,6 +20,7 @@ import ( "strings" "github.com/digitalocean/doctl" + "github.com/digitalocean/doctl/commands/displayers" "github.com/digitalocean/doctl/do" "github.com/digitalocean/godo" @@ -36,25 +37,25 @@ func Firewall() *Command { }, } - CmdBuilder(cmd, RunFirewallGet, "get ", "get firewall", Writer, aliasOpt("g"), displayerType(&firewall{})) + CmdBuilder(cmd, RunFirewallGet, "get ", "get firewall", Writer, aliasOpt("g"), displayerType(&displayers.Firewall{})) - cmdFirewallCreate := CmdBuilder(cmd, RunFirewallCreate, "create", "create firewall", Writer, aliasOpt("c"), displayerType(&firewall{})) + cmdFirewallCreate := CmdBuilder(cmd, RunFirewallCreate, "create", "create firewall", Writer, aliasOpt("c"), displayerType(&displayers.Firewall{})) AddStringFlag(cmdFirewallCreate, doctl.ArgFirewallName, "", "", "firewall name", requiredOpt()) AddStringFlag(cmdFirewallCreate, doctl.ArgInboundRules, "", "", "comma-separated key:value list, example value: protocol:tcp,ports:22,droplet_id:1,droplet_id:2,tag:frontend, use quoted string of space-separated values for multiple rules") AddStringFlag(cmdFirewallCreate, doctl.ArgOutboundRules, "", "", "comma-separated key:value list, example value: protocol:tcp,ports:22,address:0.0.0.0/0, use quoted string of space-separated values for multiple rules") AddStringSliceFlag(cmdFirewallCreate, doctl.ArgDropletIDs, "", []string{}, "comma-separated list of droplet IDs, example value: 123,456") AddStringSliceFlag(cmdFirewallCreate, doctl.ArgTagNames, "", []string{}, "comma-separated list of tag names, example value: frontend,backend") - cmdFirewallUpdate := CmdBuilder(cmd, RunFirewallUpdate, "update ", "update firewall", Writer, aliasOpt("u"), displayerType(&firewall{})) + cmdFirewallUpdate := CmdBuilder(cmd, RunFirewallUpdate, "update ", "update firewall", Writer, aliasOpt("u"), displayerType(&displayers.Firewall{})) AddStringFlag(cmdFirewallUpdate, doctl.ArgFirewallName, "", "", "firewall name", requiredOpt()) AddStringFlag(cmdFirewallUpdate, doctl.ArgInboundRules, "", "", "comma-separated key:value list, example value: protocol:tcp,ports:22,droplet_id:123, use quoted string of space-separated values for multiple rules") AddStringFlag(cmdFirewallUpdate, doctl.ArgOutboundRules, "", "", "comma-separated key:value list, example value: protocol:tcp,ports:22,address:0.0.0.0/0, use quoted string of space-separated values for multiple rules") AddStringSliceFlag(cmdFirewallUpdate, doctl.ArgDropletIDs, "", []string{}, "comma-separated list of droplet IDs, example value: 123,456") AddStringSliceFlag(cmdFirewallUpdate, doctl.ArgTagNames, "", []string{}, "comma-separated list of tag names, example value: frontend,backend") - CmdBuilder(cmd, RunFirewallList, "list", "list firewalls", Writer, aliasOpt("ls"), displayerType(&firewall{})) + CmdBuilder(cmd, RunFirewallList, "list", "list firewalls", Writer, aliasOpt("ls"), displayerType(&displayers.Firewall{})) - CmdBuilder(cmd, RunFirewallListByDroplet, "list-by-droplet ", "list firewalls by droplet ID", Writer, displayerType(&firewall{})) + CmdBuilder(cmd, RunFirewallListByDroplet, "list-by-droplet ", "list firewalls by droplet ID", Writer, displayerType(&displayers.Firewall{})) cmdRunRecordDelete := CmdBuilder(cmd, RunFirewallDelete, "delete [id ...]", "delete firewall", Writer, aliasOpt("d", "rm")) AddBoolFlag(cmdRunRecordDelete, doctl.ArgForce, doctl.ArgShortForce, false, "Force firewall delete") @@ -95,7 +96,7 @@ func RunFirewallGet(c *CmdConfig) error { return err } - item := &firewall{firewalls: do.Firewalls{*f}} + item := &displayers.Firewall{Firewalls: do.Firewalls{*f}} return c.Display(item) } @@ -112,7 +113,7 @@ func RunFirewallCreate(c *CmdConfig) error { return err } - item := &firewall{firewalls: do.Firewalls{*f}} + item := &displayers.Firewall{Firewalls: do.Firewalls{*f}} return c.Display(item) } @@ -134,7 +135,7 @@ func RunFirewallUpdate(c *CmdConfig) error { return err } - item := &firewall{firewalls: do.Firewalls{*f}} + item := &displayers.Firewall{Firewalls: do.Firewalls{*f}} return c.Display(item) } @@ -146,7 +147,7 @@ func RunFirewallList(c *CmdConfig) error { return err } - items := &firewall{firewalls: list} + items := &displayers.Firewall{Firewalls: list} return c.Display(items) } @@ -166,7 +167,7 @@ func RunFirewallListByDroplet(c *CmdConfig) error { return err } - items := &firewall{firewalls: list} + items := &displayers.Firewall{Firewalls: list} return c.Display(items) } diff --git a/commands/floating_ip_actions.go b/commands/floating_ip_actions.go index e4a322921..80e8eb5c2 100644 --- a/commands/floating_ip_actions.go +++ b/commands/floating_ip_actions.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -18,6 +18,7 @@ import ( "strconv" "github.com/digitalocean/doctl" + "github.com/digitalocean/doctl/commands/displayers" "github.com/digitalocean/doctl/do" "github.com/spf13/cobra" ) @@ -35,15 +36,15 @@ func FloatingIPAction() *Command { CmdBuilder(cmd, RunFloatingIPActionsGet, "get ", "get floating-ip action", Writer, - displayerType(&action{}), docCategories("floatingip")) + displayerType(&displayers.Action{}), docCategories("floatingip")) CmdBuilder(cmd, RunFloatingIPActionsAssign, "assign ", "assign a floating IP to a droplet", Writer, - displayerType(&action{}), docCategories("floatingip")) + displayerType(&displayers.Action{}), docCategories("floatingip")) CmdBuilder(cmd, RunFloatingIPActionsUnassign, "unassign ", "unassign a floating IP to a droplet", Writer, - displayerType(&action{}), docCategories("floatingip")) + displayerType(&displayers.Action{}), docCategories("floatingip")) return cmd } @@ -68,7 +69,7 @@ func RunFloatingIPActionsGet(c *CmdConfig) error { return err } - item := &action{actions: do.Actions{*a}} + item := &displayers.Action{Actions: do.Actions{*a}} return c.Display(item) } @@ -92,7 +93,7 @@ func RunFloatingIPActionsAssign(c *CmdConfig) error { checkErr(fmt.Errorf("could not assign IP to droplet: %v", err)) } - item := &action{actions: do.Actions{*a}} + item := &displayers.Action{Actions: do.Actions{*a}} return c.Display(item) } @@ -111,6 +112,6 @@ func RunFloatingIPActionsUnassign(c *CmdConfig) error { checkErr(fmt.Errorf("could not unassign IP to droplet: %v", err)) } - item := &action{actions: do.Actions{*a}} + item := &displayers.Action{Actions: do.Actions{*a}} return c.Display(item) } diff --git a/commands/floating_ip_actions_test.go b/commands/floating_ip_actions_test.go index 26682f7a5..7c437dfe7 100644 --- a/commands/floating_ip_actions_test.go +++ b/commands/floating_ip_actions_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/commands/floating_ips.go b/commands/floating_ips.go index 2fb6623cb..c8e1f5d17 100644 --- a/commands/floating_ips.go +++ b/commands/floating_ips.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -18,6 +18,7 @@ import ( "fmt" "github.com/digitalocean/doctl" + "github.com/digitalocean/doctl/commands/displayers" "github.com/digitalocean/doctl/do" "github.com/digitalocean/godo" "github.com/spf13/cobra" @@ -37,7 +38,7 @@ func FloatingIP() *Command { } cmdFloatingIPCreate := CmdBuilder(cmd, RunFloatingIPCreate, "create", "create a floating IP", Writer, - aliasOpt("c"), displayerType(&floatingIP{}), docCategories("floatingip")) + aliasOpt("c"), displayerType(&displayers.FloatingIP{}), docCategories("floatingip")) AddStringFlag(cmdFloatingIPCreate, doctl.ArgRegionSlug, "", "", fmt.Sprintf("Region where to create the floating IP. (mutually exclusive with %s)", doctl.ArgDropletID)) @@ -46,13 +47,13 @@ func FloatingIP() *Command { doctl.ArgRegionSlug)) CmdBuilder(cmd, RunFloatingIPGet, "get ", "get the details of a floating IP", Writer, - aliasOpt("g"), displayerType(&floatingIP{}), docCategories("floatingip")) + aliasOpt("g"), displayerType(&displayers.FloatingIP{}), docCategories("floatingip")) cmdRunFloatingIPDelete := CmdBuilder(cmd, RunFloatingIPDelete, "delete ", "delete a floating IP address", Writer, aliasOpt("d")) AddBoolFlag(cmdRunFloatingIPDelete, doctl.ArgForce, doctl.ArgShortForce, false, "Force floating IP delete") cmdFloatingIPList := CmdBuilder(cmd, RunFloatingIPList, "list", "list all floating IP addresses", Writer, - aliasOpt("ls"), displayerType(&floatingIP{}), docCategories("floatingip")) + aliasOpt("ls"), displayerType(&displayers.FloatingIP{}), docCategories("floatingip")) AddStringFlag(cmdFloatingIPList, doctl.ArgRegionSlug, "", "", "Floating IP region") return cmd @@ -85,7 +86,7 @@ func RunFloatingIPCreate(c *CmdConfig) error { return err } - item := &floatingIP{floatingIPs: do.FloatingIPs{*ip}} + item := &displayers.FloatingIP{FloatingIPs: do.FloatingIPs{*ip}} return c.Display(item) } @@ -108,7 +109,7 @@ func RunFloatingIPGet(c *CmdConfig) error { return err } - item := &floatingIP{floatingIPs: do.FloatingIPs{*fip}} + item := &displayers.FloatingIP{FloatingIPs: do.FloatingIPs{*fip}} return c.Display(item) } @@ -148,7 +149,7 @@ func RunFloatingIPList(c *CmdConfig) error { return err } - fips := &floatingIP{floatingIPs: do.FloatingIPs{}} + fips := &displayers.FloatingIP{FloatingIPs: do.FloatingIPs{}} for _, fip := range list { var skip bool if region != "" && region != fip.Region.Slug { @@ -156,7 +157,7 @@ func RunFloatingIPList(c *CmdConfig) error { } if !skip { - fips.floatingIPs = append(fips.floatingIPs, fip) + fips.FloatingIPs = append(fips.FloatingIPs, fip) } } diff --git a/commands/floating_ips_test.go b/commands/floating_ips_test.go index c162dd5be..9a56445d7 100644 --- a/commands/floating_ips_test.go +++ b/commands/floating_ips_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/commands/image_actions.go b/commands/image_actions.go index 288bb6c3e..0fd29bd66 100644 --- a/commands/image_actions.go +++ b/commands/image_actions.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -18,6 +18,7 @@ import ( "strconv" "github.com/digitalocean/doctl" + "github.com/digitalocean/doctl/commands/displayers" "github.com/digitalocean/doctl/do" "github.com/digitalocean/godo" "github.com/spf13/cobra" @@ -35,12 +36,12 @@ func ImageAction() *Command { cmdImageActionsGet := CmdBuilder(cmd, RunImageActionsGet, "get ", "get image action", Writer, - displayerType(&action{}), docCategories("image")) + displayerType(&displayers.Action{}), docCategories("image")) AddIntFlag(cmdImageActionsGet, doctl.ArgActionID, "", 0, "action id", requiredOpt()) cmdImageActionsTransfer := CmdBuilder(cmd, RunImageActionsTransfer, "transfer ", "transfer image", Writer, - displayerType(&action{}), docCategories("image")) + displayerType(&displayers.Action{}), docCategories("image")) AddStringFlag(cmdImageActionsTransfer, doctl.ArgRegionSlug, "", "", "region", requiredOpt()) AddBoolFlag(cmdImageActionsTransfer, doctl.ArgCommandWait, "", false, "Wait for action to complete") @@ -70,7 +71,7 @@ func RunImageActionsGet(c *CmdConfig) error { return err } - item := &action{actions: do.Actions{*a}} + item := &displayers.Action{Actions: do.Actions{*a}} return c.Display(item) } @@ -115,6 +116,6 @@ func RunImageActionsTransfer(c *CmdConfig) error { } - item := &action{actions: do.Actions{*a}} + item := &displayers.Action{Actions: do.Actions{*a}} return c.Display(item) } diff --git a/commands/image_actions_test.go b/commands/image_actions_test.go index 1a277f222..12b9f689d 100644 --- a/commands/image_actions_test.go +++ b/commands/image_actions_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/commands/images.go b/commands/images.go index 9b2213a95..580c4094f 100644 --- a/commands/images.go +++ b/commands/images.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -18,6 +18,7 @@ import ( "strconv" "github.com/digitalocean/doctl" + "github.com/digitalocean/doctl/commands/displayers" "github.com/digitalocean/doctl/do" "github.com/digitalocean/godo" "github.com/spf13/cobra" @@ -36,29 +37,29 @@ func Images() *Command { } cmdImagesList := CmdBuilder(cmd, RunImagesList, "list", "list images", Writer, - aliasOpt("ls"), displayerType(&image{}), docCategories("image")) + aliasOpt("ls"), displayerType(&displayers.Image{}), docCategories("image")) AddBoolFlag(cmdImagesList, doctl.ArgImagePublic, "", false, "List public images") cmdImagesListDistribution := CmdBuilder(cmd, RunImagesListDistribution, "list-distribution", "list distribution images", Writer, - displayerType(&image{}), docCategories("image")) + displayerType(&displayers.Image{}), docCategories("image")) AddBoolFlag(cmdImagesListDistribution, doctl.ArgImagePublic, "", true, "List public images") cmdImagesListApplication := CmdBuilder(cmd, RunImagesListApplication, "list-application", "list application images", Writer, - displayerType(&image{}), docCategories("image")) + displayerType(&displayers.Image{}), docCategories("image")) AddBoolFlag(cmdImagesListApplication, doctl.ArgImagePublic, "", true, "List public images") cmdImagesListUser := CmdBuilder(cmd, RunImagesListUser, "list-user", "list user images", Writer, - displayerType(&image{}), docCategories("image")) + displayerType(&displayers.Image{}), docCategories("image")) AddBoolFlag(cmdImagesListUser, doctl.ArgImagePublic, "", false, "List public images") CmdBuilder(cmd, RunImagesGet, "get ", "Get image", Writer, - displayerType(&image{}), docCategories("image")) + displayerType(&displayers.Image{}), docCategories("image")) cmdImagesUpdate := CmdBuilder(cmd, RunImagesUpdate, "update ", "Update image", Writer, - displayerType(&image{}), docCategories("image")) + displayerType(&displayers.Image{}), docCategories("image")) AddStringFlag(cmdImagesUpdate, doctl.ArgImageName, "", "", "Image name", requiredOpt()) cmdRunImagesDelete := CmdBuilder(cmd, RunImagesDelete, "delete ", "Delete image", Writer, @@ -82,7 +83,7 @@ func RunImagesList(c *CmdConfig) error { return err } - item := &image{images: list} + item := &displayers.Image{Images: list} return c.Display(item) } @@ -100,7 +101,7 @@ func RunImagesListDistribution(c *CmdConfig) error { return err } - item := &image{images: list} + item := &displayers.Image{Images: list} return c.Display(item) } @@ -119,7 +120,7 @@ func RunImagesListApplication(c *CmdConfig) error { return err } - item := &image{images: list} + item := &displayers.Image{Images: list} return c.Display(item) } @@ -137,7 +138,7 @@ func RunImagesListUser(c *CmdConfig) error { return err } - item := &image{images: list} + item := &displayers.Image{Images: list} return c.Display(item) } @@ -168,7 +169,7 @@ func RunImagesGet(c *CmdConfig) error { return err } - item := &image{images: do.Images{*i}} + item := &displayers.Image{Images: do.Images{*i}} return c.Display(item) } @@ -196,7 +197,7 @@ func RunImagesUpdate(c *CmdConfig) error { return err } - item := &image{images: do.Images{*i}} + item := &displayers.Image{Images: do.Images{*i}} return c.Display(item) } diff --git a/commands/images_test.go b/commands/images_test.go index 0f93afd26..0af631234 100644 --- a/commands/images_test.go +++ b/commands/images_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/commands/load_balancers.go b/commands/load_balancers.go index 7b5292b60..35c90c822 100644 --- a/commands/load_balancers.go +++ b/commands/load_balancers.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -20,6 +20,7 @@ import ( "strings" "github.com/digitalocean/doctl" + "github.com/digitalocean/doctl/commands/displayers" "github.com/digitalocean/doctl/do" "github.com/digitalocean/godo" @@ -93,7 +94,7 @@ func RunLoadBalancerGet(c *CmdConfig) error { return err } - item := &loadBalancer{loadBalancers: do.LoadBalancers{*lb}} + item := &displayers.LoadBalancer{LoadBalancers: do.LoadBalancers{*lb}} return c.Display(item) } @@ -105,7 +106,7 @@ func RunLoadBalancerList(c *CmdConfig) error { return err } - item := &loadBalancer{loadBalancers: list} + item := &displayers.LoadBalancer{LoadBalancers: list} return c.Display(item) } @@ -122,7 +123,7 @@ func RunLoadBalancerCreate(c *CmdConfig) error { return err } - item := &loadBalancer{loadBalancers: do.LoadBalancers{*lb}} + item := &displayers.LoadBalancer{LoadBalancers: do.LoadBalancers{*lb}} return c.Display(item) } @@ -144,7 +145,7 @@ func RunLoadBalancerUpdate(c *CmdConfig) error { return err } - item := &loadBalancer{loadBalancers: do.LoadBalancers{*lb}} + item := &displayers.LoadBalancer{LoadBalancers: do.LoadBalancers{*lb}} return c.Display(item) } diff --git a/commands/output.go b/commands/output.go deleted file mode 100644 index 7ff87d254..000000000 --- a/commands/output.go +++ /dev/null @@ -1,991 +0,0 @@ -/* -Copyright 2016 The Doctl Authors All rights reserved. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package commands - -import ( - "fmt" - "io" - "reflect" - "strconv" - "strings" - "text/tabwriter" - - "github.com/digitalocean/doctl/do" -) - -var ( - hc = &headerControl{} -) - -func newTabWriter(out io.Writer) *tabwriter.Writer { - w := new(tabwriter.Writer) - w.Init(out, 0, 0, 4, ' ', 0) - - return w -} - -type headerControl struct { - hideHeader bool -} - -func (hc *headerControl) HideHeader(hide bool) { - hc.hideHeader = hide -} - -type rateLimit struct { - *do.RateLimit -} - -var _ Displayable = &rateLimit{} - -func (rl *rateLimit) JSON(out io.Writer) error { - return writeJSON(rl.Rate, out) -} - -func (rl *rateLimit) Cols() []string { - return []string{ - "Limit", "Remaining", "Reset", - } -} - -func (rl *rateLimit) ColMap() map[string]string { - return map[string]string{ - "Limit": "Limit", "Remaining": "Remaining", "Reset": "Reset", - } -} - -func (rl *rateLimit) KV() []map[string]interface{} { - out := []map[string]interface{}{} - x := map[string]interface{}{ - "Limit": rl.Limit, "Remaining": rl.Remaining, "Reset": rl.Reset, - } - out = append(out, x) - - return out -} - -type account struct { - *do.Account -} - -var _ Displayable = &account{} - -func (a *account) JSON(out io.Writer) error { - return writeJSON(a.Account, out) -} - -func (a *account) Cols() []string { - return []string{ - "Email", "DropletLimit", "EmailVerified", "UUID", "Status", - } -} - -func (a *account) ColMap() map[string]string { - return map[string]string{ - "Email": "Email", "DropletLimit": "Droplet Limit", "EmailVerified": "Email Verified", - "UUID": "UUID", "Status": "Status", - } -} - -func (a *account) KV() []map[string]interface{} { - out := []map[string]interface{}{} - x := map[string]interface{}{ - "Email": a.Email, "DropletLimit": a.DropletLimit, - "EmailVerified": a.EmailVerified, "UUID": a.UUID, - "Status": a.Status, - } - out = append(out, x) - - return out -} - -type action struct { - actions do.Actions -} - -var _ Displayable = &action{} - -func (a *action) JSON(out io.Writer) error { - return writeJSON(a.actions, out) -} - -func (a *action) Cols() []string { - return []string{ - "ID", "Status", "Type", "StartedAt", "CompletedAt", "ResourceID", "ResourceType", "Region", - } -} - -func (a *action) ColMap() map[string]string { - return map[string]string{ - "ID": "ID", "Status": "Status", "Type": "Type", "StartedAt": "Started At", - "CompletedAt": "Completed At", "ResourceID": "Resource ID", - "ResourceType": "Resource Type", "Region": "Region", - } -} - -func (a *action) KV() []map[string]interface{} { - out := []map[string]interface{}{} - - for _, x := range a.actions { - region := "" - if x.Region != nil { - region = x.Region.Slug - } - o := map[string]interface{}{ - "ID": x.ID, "Status": x.Status, "Type": x.Type, - "StartedAt": x.StartedAt, "CompletedAt": x.CompletedAt, - "ResourceID": x.ResourceID, "ResourceType": x.ResourceType, - "Region": region, - } - out = append(out, o) - } - - return out -} - -type domain struct { - domains do.Domains -} - -var _ Displayable = &domain{} - -func (d *domain) JSON(out io.Writer) error { - return writeJSON(d.domains, out) -} - -func (d *domain) Cols() []string { - return []string{"Domain", "TTL"} -} - -func (d *domain) ColMap() map[string]string { - return map[string]string{ - "Domain": "Domain", "TTL": "TTL", - } -} - -func (d *domain) KV() []map[string]interface{} { - out := []map[string]interface{}{} - - for _, do := range d.domains { - o := map[string]interface{}{ - "Domain": do.Name, "TTL": do.TTL, - } - out = append(out, o) - } - - return out -} - -type domainRecord struct { - domainRecords do.DomainRecords -} - -func (dr *domainRecord) JSON(out io.Writer) error { - return writeJSON(dr.domainRecords, out) -} - -func (dr *domainRecord) Cols() []string { - return []string{ - "ID", "Type", "Name", "Data", "Priority", "Port", "TTL", "Weight", - } -} - -func (dr *domainRecord) ColMap() map[string]string { - return map[string]string{ - "ID": "ID", "Type": "Type", "Name": "Name", "Data": "Data", - "Priority": "Priority", "Port": "Port", "TTL": "TTL", "Weight": "Weight", - } -} - -func (dr *domainRecord) KV() []map[string]interface{} { - out := []map[string]interface{}{} - - for _, d := range dr.domainRecords { - o := map[string]interface{}{ - "ID": d.ID, "Type": d.Type, "Name": d.Name, - "Data": d.Data, "Priority": d.Priority, - "Port": d.Port, "TTL": d.TTL, "Weight": d.Weight, - } - out = append(out, o) - } - - return out -} - -type droplet struct { - droplets do.Droplets -} - -var _ Displayable = &droplet{} - -func (d *droplet) JSON(out io.Writer) error { - return writeJSON(d.droplets, out) -} - -func (d *droplet) Cols() []string { - cols := []string{ - "ID", "Name", "PublicIPv4", "PrivateIPv4", "PublicIPv6", "Memory", "VCPUs", "Disk", "Region", "Image", "Status", "Tags", "Features", "Volumes", - } - return cols -} - -func (d *droplet) ColMap() map[string]string { - return map[string]string{ - "ID": "ID", "Name": "Name", "PublicIPv4": "Public IPv4", "PrivateIPv4": "Private IPv4", "PublicIPv6": "Public IPv6", - "Memory": "Memory", "VCPUs": "VCPUs", "Disk": "Disk", - "Region": "Region", "Image": "Image", "Status": "Status", - "Tags": "Tags", "Features": "Features", "Volumes": "Volumes", - "SizeSlug": "Size Slug", - } -} - -func (d *droplet) KV() []map[string]interface{} { - out := []map[string]interface{}{} - for _, d := range d.droplets { - tags := strings.Join(d.Tags, ",") - image := fmt.Sprintf("%s %s", d.Image.Distribution, d.Image.Name) - ip, _ := d.PublicIPv4() - privIP, _ := d.PrivateIPv4() - ip6, _ := d.PublicIPv6() - features := strings.Join(d.Features, ",") - volumes := strings.Join(d.VolumeIDs, ",") - m := map[string]interface{}{ - "ID": d.ID, "Name": d.Name, "PublicIPv4": ip, "PrivateIPv4": privIP, "PublicIPv6": ip6, - "Memory": d.Memory, "VCPUs": d.Vcpus, "Disk": d.Disk, - "Region": d.Region.Slug, "Image": image, "Status": d.Status, - "Tags": tags, "Features": features, "Volumes": volumes, - "SizeSlug": d.SizeSlug, - } - out = append(out, m) - } - - return out -} - -type floatingIP struct { - floatingIPs do.FloatingIPs -} - -var _ Displayable = &floatingIP{} - -func (fi *floatingIP) JSON(out io.Writer) error { - return writeJSON(fi.floatingIPs, out) -} - -func (fi *floatingIP) Cols() []string { - return []string{ - "IP", "Region", "DropletID", "DropletName", - } -} - -func (fi *floatingIP) ColMap() map[string]string { - return map[string]string{ - "IP": "IP", "Region": "Region", "DropletID": "Droplet ID", "DropletName": "Droplet Name", - } -} - -func (fi *floatingIP) KV() []map[string]interface{} { - out := []map[string]interface{}{} - - for _, f := range fi.floatingIPs { - var dropletID, dropletName string - if f.Droplet != nil { - dropletID = fmt.Sprintf("%d", f.Droplet.ID) - dropletName = f.Droplet.Name - } - - o := map[string]interface{}{ - "IP": f.IP, "Region": f.Region.Slug, - "DropletID": dropletID, "DropletName": dropletName, - } - - out = append(out, o) - } - - return out -} - -type image struct { - images do.Images -} - -var _ Displayable = &image{} - -func (gi *image) JSON(out io.Writer) error { - return writeJSON(gi.images, out) -} - -func (gi *image) Cols() []string { - return []string{ - "ID", "Name", "Type", "Distribution", "Slug", "Public", "MinDisk", - } -} - -func (gi *image) ColMap() map[string]string { - return map[string]string{ - "ID": "ID", "Name": "Name", "Type": "Type", "Distribution": "Distribution", - "Slug": "Slug", "Public": "Public", "MinDisk": "Min Disk", - } -} - -func (gi *image) KV() []map[string]interface{} { - out := []map[string]interface{}{} - - for _, i := range gi.images { - publicStatus := false - if i.Public { - publicStatus = true - } - - o := map[string]interface{}{ - "ID": i.ID, "Name": i.Name, "Type": i.Type, "Distribution": i.Distribution, - "Slug": i.Slug, "Public": publicStatus, "MinDisk": i.MinDiskSize, - } - - out = append(out, o) - } - - return out -} - -type kernel struct { - kernels do.Kernels -} - -var _ Displayable = &kernel{} - -func (ke *kernel) JSON(out io.Writer) error { - return writeJSON(ke.kernels, out) -} - -func (ke *kernel) Cols() []string { - return []string{ - "ID", "Name", "Version", - } -} - -func (ke *kernel) ColMap() map[string]string { - return map[string]string{ - "ID": "ID", "Name": "Name", "Version": "Version", - } -} - -func (ke *kernel) KV() []map[string]interface{} { - out := []map[string]interface{}{} - - for _, k := range ke.kernels { - o := map[string]interface{}{ - "ID": k.ID, "Name": k.Name, "Version": k.Version, - } - - out = append(out, o) - } - - return out -} - -type key struct { - keys do.SSHKeys -} - -var _ Displayable = &key{} - -func (ke *key) JSON(out io.Writer) error { - return writeJSON(ke.keys, out) -} - -func (ke *key) Cols() []string { - return []string{ - "ID", "Name", "FingerPrint", - } -} - -func (ke *key) ColMap() map[string]string { - return map[string]string{ - "ID": "ID", "Name": "Name", "FingerPrint": "FingerPrint", - } -} - -func (ke *key) KV() []map[string]interface{} { - out := []map[string]interface{}{} - - for _, k := range ke.keys { - o := map[string]interface{}{ - "ID": k.ID, "Name": k.Name, "FingerPrint": k.Fingerprint, - } - - out = append(out, o) - } - - return out -} - -type region struct { - regions do.Regions -} - -var _ Displayable = ®ion{} - -func (re *region) JSON(out io.Writer) error { - return writeJSON(re.regions, out) -} - -func (re *region) Cols() []string { - return []string{ - "Slug", "Name", "Available", - } -} - -func (re *region) ColMap() map[string]string { - return map[string]string{ - "Slug": "Slug", "Name": "Name", "Available": "Available", - } -} - -func (re *region) KV() []map[string]interface{} { - out := []map[string]interface{}{} - - for _, r := range re.regions { - o := map[string]interface{}{ - "Slug": r.Slug, "Name": r.Name, "Available": r.Available, - } - - out = append(out, o) - } - - return out -} - -type size struct { - sizes do.Sizes -} - -var _ Displayable = &size{} - -func (si *size) JSON(out io.Writer) error { - return writeJSON(si.sizes, out) -} - -func (si *size) Cols() []string { - return []string{ - "Slug", "Memory", "VCPUs", "Disk", "PriceMonthly", "PriceHourly", - } -} - -func (si *size) ColMap() map[string]string { - return map[string]string{ - "Slug": "Slug", "Memory": "Memory", "VCPUs": "VCPUs", - "Disk": "Disk", "PriceMonthly": "Price Monthly", - "PriceHourly": "Price Hourly", - } -} - -func (si *size) KV() []map[string]interface{} { - out := []map[string]interface{}{} - - for _, s := range si.sizes { - o := map[string]interface{}{ - "Slug": s.Slug, "Memory": s.Memory, "VCPUs": s.Vcpus, - "Disk": s.Disk, "PriceMonthly": fmt.Sprintf("%0.2f", s.PriceMonthly), - "PriceHourly": s.PriceHourly, - } - - out = append(out, o) - } - - return out -} - -type plugin struct { - plugins []plugDesc -} - -var _ Displayable = &plugin{} - -func (p *plugin) JSON(out io.Writer) error { - return writeJSON(p.plugins, out) -} - -func (p *plugin) Cols() []string { - return []string{ - "Name", - } -} - -func (p *plugin) ColMap() map[string]string { - return map[string]string{ - "Name": "Name", - } -} - -func (p *plugin) KV() []map[string]interface{} { - out := []map[string]interface{}{} - - for _, plug := range p.plugins { - o := map[string]interface{}{ - "Name": plug.Name, - } - - out = append(out, o) - } - - return out -} - -type tag struct { - tags do.Tags -} - -var _ Displayable = &action{} - -func (t *tag) JSON(out io.Writer) error { - return writeJSON(t.tags, out) -} - -func (t *tag) Cols() []string { - return []string{"Name", "DropletCount"} -} - -func (t *tag) ColMap() map[string]string { - return map[string]string{ - "Name": "Name", - "DropletCount": "Droplet Count", - } -} - -func (t *tag) KV() []map[string]interface{} { - out := []map[string]interface{}{} - - for _, x := range t.tags { - dropletCount := x.Resources.Droplets.Count - o := map[string]interface{}{ - "Name": x.Name, - "DropletCount": dropletCount, - } - out = append(out, o) - } - - return out -} - -//CDN Output - -type cdn struct { - cdns []do.CDN -} - -var _ Displayable = &cdn{} - -func (c *cdn) JSON(out io.Writer) error { - return writeJSON(c.cdns, out) -} - -func (c *cdn) Cols() []string { - return []string{ - "ID", "Origin", "Endpoint", "TTL", "CreatedAt", - } -} - -func (c *cdn) ColMap() map[string]string { - return map[string]string{ - "ID": "ID", - "Origin": "Origin", - "Endpoint": "Endpoint", - "TTL": "TTL", - "CreatedAt": "CreatedAt", - } -} - -func (c *cdn) KV() []map[string]interface{} { - out := []map[string]interface{}{} - - for _, cdn := range c.cdns { - m := map[string]interface{}{ - "ID": cdn.ID, - "Origin": cdn.Origin, - "Endpoint": cdn.Endpoint, - "TTL": cdn.TTL, - "CreatedAt": cdn.CreatedAt, - } - - out = append(out, m) - } - - return out -} - -type volume struct { - volumes []do.Volume -} - -var _ Displayable = &volume{} - -func (a *volume) JSON(out io.Writer) error { - return writeJSON(a.volumes, out) - -} - -func (a *volume) Cols() []string { - return []string{ - "ID", "Name", "Size", "Region", "Filesystem Type", "Filesystem Label", "Droplet IDs", - } -} - -func (a *volume) ColMap() map[string]string { - return map[string]string{ - "ID": "ID", - "Name": "Name", - "Size": "Size", - "Region": "Region", - "Filesystem Type": "Filesystem Type", - "Filesystem Label": "Filesystem Label", - "Droplet IDs": "Droplet IDs", - } - -} - -func (a *volume) KV() []map[string]interface{} { - out := []map[string]interface{}{} - for _, volume := range a.volumes { - - m := map[string]interface{}{ - "ID": volume.ID, - "Name": volume.Name, - "Size": strconv.FormatInt(volume.SizeGigaBytes, 10) + " GiB", - "Region": volume.Region.Slug, - "Filesystem Type": volume.FilesystemType, - "Filesystem Label": volume.FilesystemLabel, - } - m["DropletIDs"] = "" - if len(volume.DropletIDs) != 0 { - m["DropletIDs"] = fmt.Sprintf("%v", volume.DropletIDs) - } - out = append(out, m) - - } - return out - -} - -type snapshot struct { - snapshots do.Snapshots -} - -var _ Displayable = &snapshot{} - -func (s *snapshot) JSON(out io.Writer) error { - return writeJSON(s.snapshots, out) -} - -func (s *snapshot) Cols() []string { - return []string{"ID", "Name", "CreatedAt", "Regions", "ResourceId", - "ResourceType", "MinDiskSize", "Size"} -} - -func (s *snapshot) ColMap() map[string]string { - return map[string]string{ - "ID": "ID", "Name": "Name", "CreatedAt": "Created at", "Regions": "Regions", - "ResourceId": "Resource ID", "ResourceType": "Resource Type", "MinDiskSize": "Min Disk Size", "Size": "Size"} -} - -func (s *snapshot) KV() []map[string]interface{} { - out := []map[string]interface{}{} - - for _, ss := range s.snapshots { - o := map[string]interface{}{ - "ID": ss.ID, "Name": ss.Name, "ResourceId": ss.ResourceID, - "ResourceType": ss.ResourceType, "Regions": ss.Regions, "MinDiskSize": ss.MinDiskSize, - "Size": strconv.FormatFloat(ss.SizeGigaBytes, 'f', 2, 64) + " GiB", "CreatedAt": ss.Created, - } - out = append(out, o) - } - - return out -} - -type certificate struct { - certificates do.Certificates -} - -var _ Displayable = &certificate{} - -func (c *certificate) JSON(out io.Writer) error { - return writeJSON(c.certificates, out) -} - -func (c *certificate) Cols() []string { - return []string{ - "ID", - "Name", - "DNSNames", - "SHA1Fingerprint", - "NotAfter", - "Created", - "Type", - "State", - } -} - -func (c *certificate) ColMap() map[string]string { - return map[string]string{ - "ID": "ID", - "Name": "Name", - "DNSNames": "DNS Names", - "SHA1Fingerprint": "SHA-1 Fingerprint", - "NotAfter": "Expiration Date", - "Created": "Created At", - "Type": "Type", - "State": "State", - } -} - -func (c *certificate) KV() []map[string]interface{} { - out := []map[string]interface{}{} - - for _, c := range c.certificates { - o := map[string]interface{}{ - "ID": c.ID, - "Name": c.Name, - "DNSNames": fmt.Sprintf(strings.Join(c.DNSNames, ",")), - "SHA1Fingerprint": c.SHA1Fingerprint, - "NotAfter": c.NotAfter, - "Created": c.Created, - "Type": c.Type, - "State": c.State, - } - out = append(out, o) - } - - return out -} - -type loadBalancer struct { - loadBalancers do.LoadBalancers -} - -var _ Displayable = &loadBalancer{} - -func (lb *loadBalancer) JSON(out io.Writer) error { - return writeJSON(lb.loadBalancers, out) -} - -func (lb *loadBalancer) Cols() []string { - return []string{ - "ID", - "IP", - "Name", - "Status", - "Created", - "Algorithm", - "Region", - "Tag", - "DropletIDs", - "RedirectHttpToHttps", - "StickySessions", - "HealthCheck", - "ForwardingRules", - } -} - -func (lb *loadBalancer) ColMap() map[string]string { - return map[string]string{ - "ID": "ID", - "IP": "IP", - "Name": "Name", - "Status": "Status", - "Created": "Created At", - "Algorithm": "Algorithm", - "Region": "Region", - "Tag": "Tag", - "DropletIDs": "Droplet IDs", - "RedirectHttpToHttps": "SSL", - "StickySessions": "Sticky Sessions", - "HealthCheck": "Health Check", - "ForwardingRules": "Forwarding Rules", - } -} - -func (lb *loadBalancer) KV() []map[string]interface{} { - out := []map[string]interface{}{} - - for _, l := range lb.loadBalancers { - forwardingRules := []string{} - for _, r := range l.ForwardingRules { - forwardingRules = append(forwardingRules, prettyPrintStruct(r)) - } - - o := map[string]interface{}{ - "ID": l.ID, - "IP": l.IP, - "Name": l.Name, - "Status": l.Status, - "Created": l.Created, - "Algorithm": l.Algorithm, - "Region": l.Region.Slug, - "Tag": l.Tag, - "DropletIDs": fmt.Sprintf(strings.Trim(strings.Replace(fmt.Sprint(l.DropletIDs), " ", ",", -1), "[]")), - "RedirectHttpToHttps": l.RedirectHttpToHttps, - "StickySessions": prettyPrintStruct(l.StickySessions), - "HealthCheck": prettyPrintStruct(l.HealthCheck), - "ForwardingRules": fmt.Sprintf(strings.Join(forwardingRules, " ")), - } - out = append(out, o) - } - - return out -} - -type firewall struct { - firewalls do.Firewalls -} - -var _ Displayable = &firewall{} - -func (f *firewall) JSON(out io.Writer) error { - return writeJSON(f.firewalls, out) -} - -func (f *firewall) Cols() []string { - return []string{ - "ID", - "Name", - "Status", - "Created", - "InboundRules", - "OutboundRules", - "DropletIDs", - "Tags", - "PendingChanges", - } -} - -func (f *firewall) ColMap() map[string]string { - return map[string]string{ - "ID": "ID", - "Name": "Name", - "Status": "Status", - "Created": "Created At", - "InboundRules": "Inbound Rules", - "OutboundRules": "Outbound Rules", - "DropletIDs": "Droplet IDs", - "Tags": "Tags", - "PendingChanges": "Pending Changes", - } -} - -func (f *firewall) KV() []map[string]interface{} { - out := []map[string]interface{}{} - - for _, fw := range f.firewalls { - irs, ors := firewallRulesPrintHelper(fw) - o := map[string]interface{}{ - "ID": fw.ID, - "Name": fw.Name, - "Status": fw.Status, - "Created": fw.Created, - "InboundRules": irs, - "OutboundRules": ors, - "DropletIDs": dropletListHelper(fw.DropletIDs), - "Tags": strings.Join(fw.Tags, ","), - "PendingChanges": firewallPendingChangesPrintHelper(fw), - } - out = append(out, o) - } - - return out -} - -func firewallRulesPrintHelper(fw do.Firewall) (string, string) { - var irs, ors []string - - for _, ir := range fw.InboundRules { - ss := firewallInAndOutboundRulesPrintHelper(ir.Sources.Addresses, ir.Sources.Tags, ir.Sources.DropletIDs, ir.Sources.LoadBalancerUIDs) - if ir.Protocol == "icmp" { - irs = append(irs, fmt.Sprintf("%v:%v,%v", "protocol", ir.Protocol, ss)) - } else { - irs = append(irs, fmt.Sprintf("%v:%v,%v:%v,%v", "protocol", ir.Protocol, "ports", ir.PortRange, ss)) - } - } - - for _, or := range fw.OutboundRules { - ds := firewallInAndOutboundRulesPrintHelper(or.Destinations.Addresses, or.Destinations.Tags, or.Destinations.DropletIDs, or.Destinations.LoadBalancerUIDs) - if or.Protocol == "icmp" { - ors = append(ors, fmt.Sprintf("%v:%v,%v", "protocol", or.Protocol, ds)) - } else { - ors = append(ors, fmt.Sprintf("%v:%v,%v:%v,%v", "protocol", or.Protocol, "ports", or.PortRange, ds)) - } - } - - return strings.Join(irs, " "), strings.Join(ors, " ") -} - -func firewallInAndOutboundRulesPrintHelper(addresses []string, tags []string, dropletIDs []int, loadBalancerUIDs []string) string { - output := []string{} - resources := map[string][]string{ - "address": addresses, - "tag": tags, - "load_balancer_uid": loadBalancerUIDs, - } - - for k, vs := range resources { - for _, r := range vs { - output = append(output, fmt.Sprintf("%v:%v", k, r)) - } - } - - for _, dID := range dropletIDs { - output = append(output, fmt.Sprintf("%v:%v", "droplet_id", dID)) - } - - return strings.Join(output, ",") -} - -func firewallPendingChangesPrintHelper(fw do.Firewall) string { - output := []string{} - - for _, pc := range fw.PendingChanges { - output = append(output, fmt.Sprintf("%v:%v,%v:%v,%v:%v", "droplet_id", pc.DropletID, "removing", pc.Removing, "status", pc.Status)) - } - - return strings.Join(output, " ") -} - -func dropletListHelper(IDs []int) string { - output := []string{} - - for _, id := range IDs { - output = append(output, strconv.Itoa(id)) - } - - return strings.Join(output, ",") -} - -func prettyPrintStruct(obj interface{}) string { - output := []string{} - - defer func() { - if err := recover(); err != nil { - fmt.Printf("Recovered from %v", err) - } - }() - - val := reflect.Indirect(reflect.ValueOf(obj)) - for i := 0; i < val.NumField(); i++ { - k := strings.Split(val.Type().Field(i).Tag.Get("json"), ",")[0] - v := reflect.ValueOf(val.Field(i).Interface()) - output = append(output, fmt.Sprintf("%v:%v", k, v)) - } - - return fmt.Sprintf(strings.Join(output, ",")) -} diff --git a/commands/plugin.go b/commands/plugin.go index 8c5332557..7490adc0b 100644 --- a/commands/plugin.go +++ b/commands/plugin.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -19,6 +19,7 @@ import ( "path/filepath" "strings" + "github.com/digitalocean/doctl/commands/displayers" "github.com/digitalocean/doctl/pluginhost" "github.com/spf13/cobra" ) @@ -53,7 +54,7 @@ func RunPluginRun(c *CmdConfig) error { return err } - var selectedPlugin *plugDesc + var selectedPlugin *displayers.PlugDesc for i, p := range plugs { if p.Name == c.Args[0] { selectedPlugin = &plugs[i] @@ -107,20 +108,15 @@ func RunPluginList(c *CmdConfig) error { return err } - item := &plugin{plugins: plugs} + item := &displayers.Plugin{Plugins: plugs} return c.Display(item) } -type plugDesc struct { - Path string `json:"path"` - Name string `json:"name"` -} - -func searchPlugins() ([]plugDesc, error) { +func searchPlugins() ([]displayers.PlugDesc, error) { envPath := os.Getenv("PATH") paths := strings.Split(envPath, string(os.PathListSeparator)) - var plugs []plugDesc + var plugs []displayers.PlugDesc for _, p := range paths { matches, err := filepath.Glob(filepath.Join(p, "doit-provider-*")) @@ -130,7 +126,7 @@ func searchPlugins() ([]plugDesc, error) { for _, pluginPath := range matches { name := pluginName(pluginPath) - plugs = append(plugs, plugDesc{Path: pluginPath, Name: name}) + plugs = append(plugs, displayers.PlugDesc{Path: pluginPath, Name: name}) } } diff --git a/commands/projects.go b/commands/projects.go new file mode 100644 index 000000000..10d44c13b --- /dev/null +++ b/commands/projects.go @@ -0,0 +1,323 @@ +/* +Copyright 2018 The Doctl Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package commands + +import ( + "fmt" + "strings" + + "github.com/digitalocean/doctl" + "github.com/digitalocean/doctl/commands/displayers" + "github.com/digitalocean/doctl/do" + "github.com/digitalocean/godo" + "github.com/spf13/cobra" +) + +func Projects() *Command { + cmd := &Command{ + Command: &cobra.Command{ + Use: "projects", + Short: "[beta] projects commands", + Long: "[beta] projects commands are for creating and managing projects", + }, + } + + CmdBuilder(cmd, RunProjectsList, "list", "list projects", Writer, aliasOpt("ls"), displayerType(&displayers.Project{}), betaCmd()) + CmdBuilder(cmd, RunProjectsGet, "get ", "get a project; use \"default\" as ID to get default project", Writer, aliasOpt("g"), displayerType(&displayers.Project{}), betaCmd()) + + cmdProjectsCreate := CmdBuilder(cmd, RunProjectsCreate, "create", "create project", Writer, aliasOpt("c"), displayerType(&displayers.Project{}), betaCmd()) + AddStringFlag(cmdProjectsCreate, doctl.ArgProjectName, "", "", "project name", requiredOpt()) + AddStringFlag(cmdProjectsCreate, doctl.ArgProjectPurpose, "", "", "project purpose", requiredOpt()) + AddStringFlag(cmdProjectsCreate, doctl.ArgProjectDescription, "", "", "a description of your project") + AddStringFlag(cmdProjectsCreate, doctl.ArgProjectEnvironment, "", "", "the environment in which your project resides. Should be one of 'Development', 'Staging', 'Production'.") + + cmdProjectsUpdate := CmdBuilder(cmd, RunProjectsUpdate, "update ", "update project; use \"default\" as ID to update the default project", Writer, aliasOpt("u"), displayerType(&displayers.Project{}), betaCmd()) + AddStringFlag(cmdProjectsUpdate, doctl.ArgProjectName, "", "", "project name") + AddStringFlag(cmdProjectsUpdate, doctl.ArgProjectPurpose, "", "", "project purpose") + AddStringFlag(cmdProjectsUpdate, doctl.ArgProjectDescription, "", "", "a description of your project") + AddStringFlag(cmdProjectsUpdate, doctl.ArgProjectEnvironment, "", "", "the environment in which your project resides. Should be one of 'Development', 'Staging', 'Production'.") + AddBoolFlag(cmdProjectsUpdate, doctl.ArgProjectIsDefault, "", false, "set the specified project as your default project") + + cmdProjectsDelete := CmdBuilder(cmd, RunProjectsDelete, "delete [ ...]", "delete project", Writer, aliasOpt("d", "rm"), betaCmd()) + AddBoolFlag(cmdProjectsDelete, doctl.ArgForce, doctl.ArgShortForce, false, "Force project delete") + + cmd.AddCommand(ProjectResourcesCmd()) + + return cmd +} + +func ProjectResourcesCmd() *Command { + cmd := &Command{ + Command: &cobra.Command{ + Use: "resources", + Short: "project resources commands", + Long: "project resources commands are for assigning and listing resources in projects", + }, + } + CmdBuilder(cmd, RunProjectResourcesList, "list ", "list project resources", Writer, aliasOpt("ls"), displayerType(&displayers.ProjectResource{}), betaCmd()) + CmdBuilder(cmd, RunProjectResourcesGet, "get ", "get a project resource by its URN", Writer, aliasOpt("g"), betaCmd()) + + cmdProjectResourcesAssign := CmdBuilder(cmd, RunProjectResourcesAssign, "assign --resource= [--resource= ...]", "assign one or more resources to a project project", Writer, aliasOpt("a"), betaCmd()) + AddStringSliceFlag(cmdProjectResourcesAssign, doctl.ArgProjectResource, "", []string{}, "resource URNs denoting resources to assign to the project") + + return cmd +} + +// RunProjectsList lists Projects. +func RunProjectsList(c *CmdConfig) error { + ps := c.Projects() + list, err := ps.List() + if err != nil { + return err + } + + return c.Display(&displayers.Project{Projects: list}) +} + +// RunProjectsGet retrieves an existing Project by its identifier. Use "default" +// as an identifier to retrieve your default project. +func RunProjectsGet(c *CmdConfig) error { + if len(c.Args) != 1 { + return doctl.NewMissingArgsErr(c.NS) + } + id := c.Args[0] + + ps := c.Projects() + p, err := ps.Get(id) + if err != nil { + return err + } + + return c.Display(&displayers.Project{Projects: do.Projects{*p}}) +} + +// RunProjectsCreate creates a new Project with a given configuration. +func RunProjectsCreate(c *CmdConfig) error { + r := new(godo.CreateProjectRequest) + if err := buildProjectsCreateRequestFromArgs(c, r); err != nil { + return err + } + + ps := c.Projects() + p, err := ps.Create(r) + if err != nil { + return err + } + + return c.Display(&displayers.Project{Projects: do.Projects{*p}}) +} + +func RunProjectsUpdate(c *CmdConfig) error { + if len(c.Args) != 1 { + return doctl.NewMissingArgsErr(c.NS) + } + id := c.Args[0] + + r := new(godo.UpdateProjectRequest) + if err := buildProjectsUpdateRequestFromArgs(c, r); err != nil { + return err + } + + ps := c.Projects() + p, err := ps.Update(id, r) + if err != nil { + return err + } + + return c.Display(&displayers.Project{Projects: do.Projects{*p}}) +} + +func RunProjectsDelete(c *CmdConfig) error { + if len(c.Args) < 1 { + return doctl.NewMissingArgsErr(c.NS) + } + + force, err := c.Doit.GetBool(c.NS, doctl.ArgForce) + if err != nil { + return err + } + + ps := c.Projects() + var suffix string + if len(c.Args) != 1 { + suffix = "s" + } + if force || AskForConfirm(fmt.Sprintf("delete %d project%s", len(c.Args), suffix)) == nil { + for _, id := range c.Args { + if err := ps.Delete(id); err != nil { + return err + } + } + + return nil + } + + return fmt.Errorf("operation aborted") +} + +func RunProjectResourcesList(c *CmdConfig) error { + if len(c.Args) != 1 { + return doctl.NewMissingArgsErr(c.NS) + } + id := c.Args[0] + + ps := c.Projects() + list, err := ps.ListResources(id) + if err != nil { + return err + } + + return c.Display(&displayers.ProjectResource{ProjectResources: list}) +} + +func RunProjectResourcesGet(c *CmdConfig) error { + if len(c.Args) != 1 { + return doctl.NewMissingArgsErr(c.NS) + } + urn := c.Args[0] + + parts, isValid := validateURN(urn) + if !isValid { + return doctl.NewInvalidURNErr(urn) + } + + c.Args = []string{parts[2]} + switch parts[1] { + case "droplet": + return RunDropletGet(c) + case "floatingip": + return RunFloatingIPGet(c) + case "loadbalancer": + return RunLoadBalancerGet(c) + case "domain": + return RunDomainGet(c) + case "volume": + return RunVolumeGet(c) + default: + return fmt.Errorf("%q is an invalid resource type, consult the documentation", parts[1]) + } +} + +func RunProjectResourcesAssign(c *CmdConfig) error { + if len(c.Args) != 1 { + return doctl.NewMissingArgsErr(c.NS) + } + projectUUID := c.Args[0] + + urns, err := c.Doit.GetStringSlice(c.NS, doctl.ArgProjectResource) + if err != nil { + return err + } + + ps := c.Projects() + list, err := ps.AssignResources(projectUUID, urns) + if err != nil { + return err + } + + return c.Display(&displayers.ProjectResource{ProjectResources: list}) +} + +func validateURN(urn string) ([]string, bool) { + parts := strings.Split(urn, ":") + if len(parts) != 3 { + return nil, false + } + + if parts[0] != "do" { + return nil, false + } + + if strings.TrimSpace(parts[1]) == "" { + return nil, false + } + + if strings.TrimSpace(parts[2]) == "" { + return nil, false + } + + return parts, true +} + +func buildProjectsCreateRequestFromArgs(c *CmdConfig, r *godo.CreateProjectRequest) error { + name, err := c.Doit.GetString(c.NS, doctl.ArgProjectName) + if err != nil { + return err + } + r.Name = name + + purpose, err := c.Doit.GetString(c.NS, doctl.ArgProjectPurpose) + if err != nil { + return err + } + r.Purpose = purpose + + description, err := c.Doit.GetString(c.NS, doctl.ArgProjectDescription) + if err != nil { + return err + } + r.Description = description + + environment, err := c.Doit.GetString(c.NS, doctl.ArgProjectEnvironment) + if err != nil { + return err + } + r.Environment = environment + + return nil +} + +func buildProjectsUpdateRequestFromArgs(c *CmdConfig, r *godo.UpdateProjectRequest) error { + if c.Doit.IsSet(doctl.ArgProjectName) { + name, err := c.Doit.GetString(c.NS, doctl.ArgProjectName) + if err != nil { + return err + } + r.Name = name + } + + if c.Doit.IsSet(doctl.ArgProjectPurpose) { + purpose, err := c.Doit.GetString(c.NS, doctl.ArgProjectPurpose) + if err != nil { + return err + } + r.Purpose = purpose + } + + if c.Doit.IsSet(doctl.ArgProjectDescription) { + description, err := c.Doit.GetString(c.NS, doctl.ArgProjectDescription) + if err != nil { + return err + } + r.Description = description + } + + if c.Doit.IsSet(doctl.ArgProjectEnvironment) { + environment, err := c.Doit.GetString(c.NS, doctl.ArgProjectEnvironment) + if err != nil { + return err + } + r.Environment = environment + } + + if c.Doit.IsSet(doctl.ArgProjectIsDefault) { + isDefault, err := c.Doit.GetBool(c.NS, doctl.ArgProjectIsDefault) + if err != nil { + return err + } + r.IsDefault = isDefault + } + + return nil +} diff --git a/commands/projects_test.go b/commands/projects_test.go new file mode 100644 index 000000000..27942edf0 --- /dev/null +++ b/commands/projects_test.go @@ -0,0 +1,253 @@ +package commands + +import ( + "testing" + + "github.com/digitalocean/doctl" + "github.com/digitalocean/doctl/do" + "github.com/digitalocean/godo" + "github.com/stretchr/testify/assert" +) + +var ( + testProject = do.Project{ + Project: &godo.Project{ + Name: "my project", + Description: "my project description", + Purpose: "my project purpose", + Environment: "Development", + IsDefault: false, + }, + } + + testProjectList = do.Projects{testProject} + + testProjectResourcesList = do.ProjectResources{ + { + ProjectResource: &godo.ProjectResource{URN: "do:droplet:1234"}, + }, + { + ProjectResource: &godo.ProjectResource{URN: "do:floatingip:1.2.3.4"}, + }, + } + testProjectResourcesListSingle = do.ProjectResources{ + { + ProjectResource: &godo.ProjectResource{URN: "do:droplet:1234"}, + }, + } +) + +func TestProjectsCommand(t *testing.T) { + cmd := Projects() + assert.NotNil(t, cmd) + assertCommandNames(t, cmd, "list", "get", "create", "update", "delete", "resources") +} + +func TestProjectsList(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + tm.projects.On("List").Return(testProjectList, nil) + + err := RunProjectsList(config) + assert.NoError(t, err) + }) +} + +func TestProjectsGet(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + projectUUID := "ab06e011-6dd1-4034-9293-201f71aba299" + tm.projects.On("Get", projectUUID).Return(&testProject, nil) + + config.Args = append(config.Args, projectUUID) + + err := RunProjectsGet(config) + assert.NoError(t, err) + }) +} + +func TestProjectsCreate(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + projectCreateRequest := &godo.CreateProjectRequest{ + Name: "project name", + Description: "project description", + Purpose: "personal use", + Environment: "Staging", + } + tm.projects.On("Create", projectCreateRequest).Return(&testProject, nil) + + config.Doit.Set(config.NS, doctl.ArgProjectName, "project name") + config.Doit.Set(config.NS, doctl.ArgProjectDescription, "project description") + config.Doit.Set(config.NS, doctl.ArgProjectPurpose, "personal use") + config.Doit.Set(config.NS, doctl.ArgProjectEnvironment, "Staging") + + err := RunProjectsCreate(config) + assert.NoError(t, err) + }) +} + +func TestProjectsUpdateAllAttributes(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + projectUUID := "ab06e011-6dd1-4034-9293-201f71aba299" + updateReq := &godo.UpdateProjectRequest{ + Name: "project name", + Description: "project description", + Purpose: "project purpose", + Environment: "Production", + IsDefault: false, + } + tm.projects.On("Update", projectUUID, updateReq).Return(&testProject, nil) + + config.Doit.(*TestConfig).IsSetMap = map[string]bool{ + doctl.ArgProjectName: true, + doctl.ArgProjectDescription: true, + doctl.ArgProjectPurpose: true, + doctl.ArgProjectEnvironment: true, + doctl.ArgProjectIsDefault: true, + } + + config.Args = append(config.Args, projectUUID) + config.Doit.Set(config.NS, doctl.ArgProjectName, "project name") + config.Doit.Set(config.NS, doctl.ArgProjectDescription, "project description") + config.Doit.Set(config.NS, doctl.ArgProjectPurpose, "project purpose") + config.Doit.Set(config.NS, doctl.ArgProjectEnvironment, "Production") + config.Doit.Set(config.NS, doctl.ArgProjectIsDefault, false) + + err := RunProjectsUpdate(config) + assert.NoError(t, err) + }) +} + +func TestProjectsUpdateSomeAttributes(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + projectUUID := "ab06e011-6dd1-4034-9293-201f71aba299" + updateReq := &godo.UpdateProjectRequest{ + Name: "project name", + Description: "project description", + Purpose: nil, + Environment: nil, + IsDefault: nil, + } + tm.projects.On("Update", projectUUID, updateReq).Return(&testProject, nil) + + config.Doit.(*TestConfig).IsSetMap = map[string]bool{ + doctl.ArgProjectName: true, + doctl.ArgProjectDescription: true, + doctl.ArgProjectPurpose: false, + doctl.ArgProjectEnvironment: false, + doctl.ArgProjectIsDefault: false, + } + + config.Args = append(config.Args, projectUUID) + config.Doit.Set(config.NS, doctl.ArgProjectName, "project name") + config.Doit.Set(config.NS, doctl.ArgProjectDescription, "project description") + + err := RunProjectsUpdate(config) + assert.NoError(t, err) + }) +} + +func TestProjectsUpdateOneAttribute(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + projectUUID := "ab06e011-6dd1-4034-9293-201f71aba299" + updateReq := &godo.UpdateProjectRequest{ + Name: "project name", + Description: nil, + Purpose: nil, + Environment: nil, + IsDefault: nil, + } + tm.projects.On("Update", projectUUID, updateReq).Return(&testProject, nil) + + config.Doit.(*TestConfig).IsSetMap = map[string]bool{ + doctl.ArgProjectName: true, + doctl.ArgProjectDescription: false, + doctl.ArgProjectPurpose: false, + doctl.ArgProjectEnvironment: false, + doctl.ArgProjectIsDefault: false, + } + + config.Args = append(config.Args, projectUUID) + config.Doit.Set(config.NS, doctl.ArgProjectName, "project name") + config.Doit.Set(config.NS, doctl.ArgProjectDescription, "project description") + + err := RunProjectsUpdate(config) + assert.NoError(t, err) + }) +} + +func TestProjectsDelete(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + projectUUID := "ab06e011-6dd1-4034-9293-201f71aba299" + tm.projects.On("Delete", projectUUID).Return(nil) + + config.Args = append(config.Args, projectUUID) + config.Doit.Set(config.NS, doctl.ArgForce, true) + + err := RunProjectsDelete(config) + assert.NoError(t, err) + }) +} + +func TestProjectResourcesCommand(t *testing.T) { + cmd := ProjectResourcesCmd() + assert.NotNil(t, cmd) + assertCommandNames(t, cmd, "list", "get", "assign") +} + +func TestProjectResourcesList(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + projectUUID := "ab06e011-6dd1-4034-9293-201f71aba299" + tm.projects.On("ListResources", projectUUID).Return(testProjectResourcesList, nil) + + config.Args = append(config.Args, projectUUID) + err := RunProjectResourcesList(config) + assert.NoError(t, err) + }) +} + +func TestProjectResourcesGetWithValidURN(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + tm.droplets.On("Get", 1234).Return(&testDroplet, nil) + + config.Args = append(config.Args, "do:droplet:1234") + err := RunProjectResourcesGet(config) + assert.NoError(t, err) + }) +} + +func TestProjectResourcesGetWithInvalidURN(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + config.Args = append(config.Args, "fakeurn") + err := RunProjectResourcesGet(config) + assert.Error(t, err) + assert.Contains(t, err.Error(), "URN must be in the format") + }) +} + +func TestProjectResourcesAssignOneResource(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + projectUUID := "ab06e011-6dd1-4034-9293-201f71aba299" + urn := "do:droplet:1234" + tm.projects.On("AssignResources", projectUUID, []string{urn}).Return(testProjectResourcesListSingle, nil) + + config.Args = append(config.Args, projectUUID) + config.Doit.Set(config.NS, doctl.ArgProjectResource, []string{urn}) + + err := RunProjectResourcesAssign(config) + assert.NoError(t, err) + }) +} + +func TestProjectResourcesAssignMultipleResources(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + projectUUID := "ab06e011-6dd1-4034-9293-201f71aba299" + urn := "do:droplet:1234" + otherURN := "do:floatingip:1.2.3.4" + tm.projects.On("AssignResources", projectUUID, []string{urn, otherURN}).Return(testProjectResourcesList, nil) + + config.Args = append(config.Args, projectUUID) + config.Doit.Set(config.NS, doctl.ArgProjectResource, []string{urn, otherURN}) + + err := RunProjectResourcesAssign(config) + assert.NoError(t, err) + }) +} diff --git a/commands/regions.go b/commands/regions.go index 8c60277f5..38472ae51 100644 --- a/commands/regions.go +++ b/commands/regions.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -13,7 +13,10 @@ limitations under the License. package commands -import "github.com/spf13/cobra" +import ( + "github.com/digitalocean/doctl/commands/displayers" + "github.com/spf13/cobra" +) // Region creates the region commands heirarchy. func Region() *Command { @@ -26,7 +29,7 @@ func Region() *Command { } CmdBuilder(cmd, RunRegionList, "list", "list regions", Writer, aliasOpt("ls"), - displayerType(®ion{}), docCategories("compute")) + displayerType(&displayers.Region{}), docCategories("compute")) return cmd } @@ -40,6 +43,6 @@ func RunRegionList(c *CmdConfig) error { return err } - image := ®ion{regions: list} + image := &displayers.Region{Regions: list} return c.Display(image) } diff --git a/commands/regions_test.go b/commands/regions_test.go index e4e4fbf24..c18194deb 100644 --- a/commands/regions_test.go +++ b/commands/regions_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/commands/sizes.go b/commands/sizes.go index 1cce7e3d1..1f359dd25 100644 --- a/commands/sizes.go +++ b/commands/sizes.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -13,7 +13,10 @@ limitations under the License. package commands -import "github.com/spf13/cobra" +import ( + "github.com/digitalocean/doctl/commands/displayers" + "github.com/spf13/cobra" +) // Size creates the size commands heirarchy. func Size() *Command { @@ -26,7 +29,7 @@ func Size() *Command { } CmdBuilder(cmd, RunSizeList, "list", "list sizes", Writer, aliasOpt("ls"), - displayerType(&size{}), docCategories("compute")) + displayerType(&displayers.Size{}), docCategories("compute")) return cmd } @@ -40,6 +43,6 @@ func RunSizeList(c *CmdConfig) error { return err } - item := &size{sizes: list} + item := &displayers.Size{Sizes: list} return c.Display(item) } diff --git a/commands/sizes_test.go b/commands/sizes_test.go index 98f6835d1..cc044523a 100644 --- a/commands/sizes_test.go +++ b/commands/sizes_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/commands/snapshots.go b/commands/snapshots.go index 5e002d378..a2c3f76c5 100644 --- a/commands/snapshots.go +++ b/commands/snapshots.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,6 +17,7 @@ import ( "fmt" "github.com/digitalocean/doctl" + "github.com/digitalocean/doctl/commands/displayers" "github.com/digitalocean/doctl/do" "github.com/gobwas/glob" "github.com/spf13/cobra" @@ -36,15 +37,15 @@ func Snapshot() *Command { } cmdRunSnapshotList := CmdBuilder(cmd, RunSnapshotList, "list [glob]", "list snapshots", Writer, - aliasOpt("ls"), displayerType(&snapshot{}), docCategories("snapshot")) + aliasOpt("ls"), displayerType(&displayers.Snapshot{}), docCategories("snapshot")) AddStringFlag(cmdRunSnapshotList, doctl.ArgResourceType, "", "", "Resource type") AddStringFlag(cmdRunSnapshotList, doctl.ArgRegionSlug, "", "", "Snapshot region") CmdBuilder(cmd, RunSnapshotGet, "get [snapshot-id ...]", "get snapshot", Writer, - aliasOpt("g"), displayerType(&droplet{}), docCategories("snapshot")) + aliasOpt("g"), displayerType(&displayers.Droplet{}), docCategories("snapshot")) cmdRunSnapshotDelete := CmdBuilder(cmd, RunSnapshotDelete, "delete [snapshot-id ...]", "delete snapshot", Writer, - aliasOpt("d"), displayerType(&droplet{}), docCategories("snapshot")) + aliasOpt("d"), displayerType(&displayers.Droplet{}), docCategories("snapshot")) AddBoolFlag(cmdRunSnapshotDelete, doctl.ArgForce, doctl.ArgShortForce, false, "Force snapshot delete") return cmd @@ -127,7 +128,7 @@ func RunSnapshotList(c *CmdConfig) error { } } - item := &snapshot{snapshots: matchedList} + item := &displayers.Snapshot{Snapshots: matchedList} return c.Display(item) } @@ -149,7 +150,7 @@ func RunSnapshotGet(c *CmdConfig) error { } matchedList = append(matchedList, *s) } - item := &snapshot{snapshots: matchedList} + item := &displayers.Snapshot{Snapshots: matchedList} return c.Display(item) } diff --git a/commands/snapshots_test.go b/commands/snapshots_test.go index 8eb60bc59..7553828c6 100644 --- a/commands/snapshots_test.go +++ b/commands/snapshots_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/commands/ssh.go b/commands/ssh.go index cf806c147..df6c4ad14 100644 --- a/commands/ssh.go +++ b/commands/ssh.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/commands/ssh_test.go b/commands/ssh_test.go index 683ef0eeb..4048edb9e 100644 --- a/commands/ssh_test.go +++ b/commands/ssh_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/commands/sshkeys.go b/commands/sshkeys.go index 482c1adac..9678546d1 100644 --- a/commands/sshkeys.go +++ b/commands/sshkeys.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -18,6 +18,7 @@ import ( "io/ioutil" "github.com/digitalocean/doctl" + "github.com/digitalocean/doctl/commands/displayers" "github.com/digitalocean/doctl/do" "github.com/digitalocean/godo" "github.com/spf13/cobra" @@ -38,17 +39,17 @@ func SSHKeys() *Command { } CmdBuilder(cmd, RunKeyList, "list", "list ssh keys", Writer, - aliasOpt("ls"), displayerType(&key{}), docCategories("sshkeys")) + aliasOpt("ls"), displayerType(&displayers.Key{}), docCategories("sshkeys")) CmdBuilder(cmd, RunKeyGet, "get ", "get ssh key", Writer, - aliasOpt("g"), displayerType(&key{}), docCategories("sshkeys")) + aliasOpt("g"), displayerType(&displayers.Key{}), docCategories("sshkeys")) cmdSSHKeysCreate := CmdBuilder(cmd, RunKeyCreate, "create ", "create ssh key", Writer, - aliasOpt("c"), displayerType(&key{}), docCategories("sshkeys")) + aliasOpt("c"), displayerType(&displayers.Key{}), docCategories("sshkeys")) AddStringFlag(cmdSSHKeysCreate, doctl.ArgKeyPublicKey, "", "", "Key contents", requiredOpt()) cmdSSHKeysImport := CmdBuilder(cmd, RunKeyImport, "import ", "import ssh key", Writer, - aliasOpt("i"), displayerType(&key{}), docCategories("sshkeys")) + aliasOpt("i"), displayerType(&displayers.Key{}), docCategories("sshkeys")) AddStringFlag(cmdSSHKeysImport, doctl.ArgKeyPublicKeyFile, "", "", "Public key file", requiredOpt()) cmdRunKeyDelete := CmdBuilder(cmd, RunKeyDelete, "delete ", "delete ssh key", Writer, @@ -56,7 +57,7 @@ func SSHKeys() *Command { AddBoolFlag(cmdRunKeyDelete, doctl.ArgForce, doctl.ArgShortForce, false, "Force ssh key delete") cmdSSHKeysUpdate := CmdBuilder(cmd, RunKeyUpdate, "update ", "update ssh key", Writer, - aliasOpt("u"), displayerType(&key{}), docCategories("sshkeys")) + aliasOpt("u"), displayerType(&displayers.Key{}), docCategories("sshkeys")) AddStringFlag(cmdSSHKeysUpdate, doctl.ArgKeyName, "", "", "Key name", requiredOpt()) return cmd @@ -71,7 +72,7 @@ func RunKeyList(c *CmdConfig) error { return err } - item := &key{keys: list} + item := &displayers.Key{Keys: list} return c.Display(item) } @@ -90,7 +91,7 @@ func RunKeyGet(c *CmdConfig) error { return err } - item := &key{keys: do.SSHKeys{*k}} + item := &displayers.Key{Keys: do.SSHKeys{*k}} return c.Display(item) } @@ -119,7 +120,7 @@ func RunKeyCreate(c *CmdConfig) error { return err } - item := &key{keys: do.SSHKeys{*r}} + item := &displayers.Key{Keys: do.SSHKeys{*r}} return c.Display(item) } @@ -162,7 +163,7 @@ func RunKeyImport(c *CmdConfig) error { return err } - item := &key{keys: do.SSHKeys{*r}} + item := &displayers.Key{Keys: do.SSHKeys{*r}} return c.Display(item) } @@ -212,6 +213,6 @@ func RunKeyUpdate(c *CmdConfig) error { return err } - item := &key{keys: do.SSHKeys{*k}} + item := &displayers.Key{Keys: do.SSHKeys{*k}} return c.Display(item) } diff --git a/commands/sshkeys_test.go b/commands/sshkeys_test.go index fd37e5381..69eecb07d 100644 --- a/commands/sshkeys_test.go +++ b/commands/sshkeys_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/commands/tags.go b/commands/tags.go index 22b5b1a4b..c54c9ac97 100644 --- a/commands/tags.go +++ b/commands/tags.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,6 +17,7 @@ import ( "fmt" "github.com/digitalocean/doctl" + "github.com/digitalocean/doctl/commands/displayers" "github.com/digitalocean/doctl/do" "github.com/digitalocean/godo" "github.com/spf13/cobra" @@ -65,7 +66,7 @@ func RunCmdTagCreate(c *CmdConfig) error { return err } - return c.Display(&tag{tags: do.Tags{*t}}) + return c.Display(&displayers.Tag{Tags: do.Tags{*t}}) } // RunCmdTagGet runs tag get. @@ -81,7 +82,7 @@ func RunCmdTagGet(c *CmdConfig) error { return err } - return c.Display(&tag{tags: do.Tags{*t}}) + return c.Display(&displayers.Tag{Tags: do.Tags{*t}}) } // RunCmdTagList runs tag list. @@ -92,7 +93,7 @@ func RunCmdTagList(c *CmdConfig) error { return err } - return c.Display(&tag{tags: tags}) + return c.Display(&displayers.Tag{Tags: tags}) } // RunCmdTagDelete runs tag delete. diff --git a/commands/tags_test.go b/commands/tags_test.go index 8aef0ef15..d47a6bbc6 100644 --- a/commands/tags_test.go +++ b/commands/tags_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/commands/version.go b/commands/version.go index a6acd23c4..534911bfa 100644 --- a/commands/version.go +++ b/commands/version.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/commands/version_test.go b/commands/version_test.go index 443780aee..e39579631 100644 --- a/commands/version_test.go +++ b/commands/version_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/commands/volume_actions.go b/commands/volume_actions.go index 494084fd0..21e8d8983 100644 --- a/commands/volume_actions.go +++ b/commands/volume_actions.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,6 +17,7 @@ import ( "strconv" "github.com/digitalocean/doctl" + "github.com/digitalocean/doctl/commands/displayers" "github.com/digitalocean/doctl/do" "github.com/spf13/cobra" ) @@ -44,7 +45,7 @@ func performVolumeAction(c *CmdConfig, fn volumeActionFn) error { } - item := &action{actions: do.Actions{*a}} + item := &displayers.Action{Actions: do.Actions{*a}} return c.Display(item) } diff --git a/commands/volume_actions_test.go b/commands/volume_actions_test.go index ec36bbaf3..db40a2e16 100644 --- a/commands/volume_actions_test.go +++ b/commands/volume_actions_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/commands/volumes.go b/commands/volumes.go index 7dd9d594e..98f0c726f 100644 --- a/commands/volumes.go +++ b/commands/volumes.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,6 +17,7 @@ import ( "fmt" "github.com/digitalocean/doctl" + "github.com/digitalocean/doctl/commands/displayers" "github.com/digitalocean/doctl/do" "github.com/digitalocean/godo" "github.com/dustin/go-humanize" @@ -35,11 +36,11 @@ func Volume() *Command { } cmdRunVolumeList := CmdBuilder(cmd, RunVolumeList, "list", "list volume", Writer, - aliasOpt("ls"), displayerType(&volume{})) + aliasOpt("ls"), displayerType(&displayers.Volume{})) AddStringFlag(cmdRunVolumeList, doctl.ArgRegionSlug, "", "", "Volume region") cmdVolumeCreate := CmdBuilder(cmd, RunVolumeCreate, "create ", "create a volume", Writer, - aliasOpt("c"), displayerType(&volume{})) + aliasOpt("c"), displayerType(&displayers.Volume{})) AddStringFlag(cmdVolumeCreate, doctl.ArgVolumeSize, "", "4TiB", "Volume size", requiredOpt()) AddStringFlag(cmdVolumeCreate, doctl.ArgVolumeDesc, "", "", "Volume description") @@ -53,10 +54,10 @@ func Volume() *Command { AddBoolFlag(cmdRunVolumeDelete, doctl.ArgForce, doctl.ArgShortForce, false, "Force volume delete") CmdBuilder(cmd, RunVolumeGet, "get ", "get a volume", Writer, aliasOpt("g"), - displayerType(&volume{})) + displayerType(&displayers.Volume{})) cmdRunVolumeSnapshot := CmdBuilder(cmd, RunVolumeSnapshot, "snapshot ", "create a volume snapshot", Writer, - aliasOpt("s"), displayerType(&volume{})) + aliasOpt("s"), displayerType(&displayers.Volume{})) AddStringFlag(cmdRunVolumeSnapshot, doctl.ArgSnapshotName, "", "", "Snapshot name", requiredOpt()) AddStringFlag(cmdRunVolumeSnapshot, doctl.ArgSnapshotDesc, "", "", "Snapshot description") @@ -115,7 +116,7 @@ func RunVolumeList(c *CmdConfig) error { matchedList = append(matchedList, volume) } } - item := &volume{volumes: matchedList} + item := &displayers.Volume{Volumes: matchedList} return c.Display(item) } @@ -171,7 +172,7 @@ func RunVolumeCreate(c *CmdConfig) error { if err != nil { return err } - item := &volume{volumes: []do.Volume{*d}} + item := &displayers.Volume{Volumes: []do.Volume{*d}} return c.Display(item) } @@ -207,7 +208,7 @@ func RunVolumeGet(c *CmdConfig) error { if err != nil { return err } - item := &volume{volumes: []do.Volume{*d}} + item := &displayers.Volume{Volumes: []do.Volume{*d}} return c.Display(item) } diff --git a/commands/volumes_test.go b/commands/volumes_test.go index 201d92171..9c529b0e7 100644 --- a/commands/volumes_test.go +++ b/commands/volumes_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/commands/xdg.go b/commands/xdg.go index 24d314054..943219941 100644 --- a/commands/xdg.go +++ b/commands/xdg.go @@ -1,4 +1,4 @@ -// Copyright 2016 The Doctl Authors All rights reserved. +// Copyright 2018 The Doctl Authors All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/commands/xdg_windows.go b/commands/xdg_windows.go index ab03c95c9..07c8dd9fa 100644 --- a/commands/xdg_windows.go +++ b/commands/xdg_windows.go @@ -1,4 +1,4 @@ -// Copyright 2016 The Doctl Authors All rights reserved. +// Copyright 2018 The Doctl Authors All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/commands/xdg_windows_test.go b/commands/xdg_windows_test.go index 31678acd7..1b17069d5 100644 --- a/commands/xdg_windows_test.go +++ b/commands/xdg_windows_test.go @@ -1,4 +1,4 @@ -// Copyright 2016 The Doctl Authors All rights reserved. +// Copyright 2018 The Doctl Authors All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/commmand_test.go b/commmand_test.go index 7dbea81fe..c877e5212 100644 --- a/commmand_test.go +++ b/commmand_test.go @@ -1,7 +1,7 @@ // +build !windows /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/do/account.go b/do/account.go index 7d16f2006..d19f2254d 100644 --- a/do/account.go +++ b/do/account.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/do/account_test.go b/do/account_test.go index e7fe1abfe..9b4fbe153 100644 --- a/do/account_test.go +++ b/do/account_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/do/actions.go b/do/actions.go index bb47bdb40..9656513a5 100644 --- a/do/actions.go +++ b/do/actions.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/do/certificates.go b/do/certificates.go index 74429cd7f..dc499b9e5 100644 --- a/do/certificates.go +++ b/do/certificates.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/do/domains.go b/do/domains.go index 4534d7da2..5abab287d 100644 --- a/do/domains.go +++ b/do/domains.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/do/droplet_actions.go b/do/droplet_actions.go index 3f5f7900e..45d6ef1cd 100644 --- a/do/droplet_actions.go +++ b/do/droplet_actions.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/do/droplets.go b/do/droplets.go index c91663bc0..d8e6561cc 100644 --- a/do/droplets.go +++ b/do/droplets.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/do/firewalls.go b/do/firewalls.go index 6cc23cbac..5b75a34f2 100644 --- a/do/firewalls.go +++ b/do/firewalls.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/do/floating_ip_actions.go b/do/floating_ip_actions.go index 70ca56df8..2d602f7f0 100644 --- a/do/floating_ip_actions.go +++ b/do/floating_ip_actions.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/do/floating_ips.go b/do/floating_ips.go index 4265e898c..d77784813 100644 --- a/do/floating_ips.go +++ b/do/floating_ips.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/do/image_actions.go b/do/image_actions.go index fe247fdab..27b52b944 100644 --- a/do/image_actions.go +++ b/do/image_actions.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/do/images.go b/do/images.go index d1ae8818e..f9665f384 100644 --- a/do/images.go +++ b/do/images.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/do/load_balancers.go b/do/load_balancers.go index dbe227e4f..5756dcce9 100644 --- a/do/load_balancers.go +++ b/do/load_balancers.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/do/mocks/ProjectsService.go b/do/mocks/ProjectsService.go new file mode 100644 index 000000000..f6f9165c9 --- /dev/null +++ b/do/mocks/ProjectsService.go @@ -0,0 +1,189 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +// Generated: please do not edit by hand + +package mocks + +import do "github.com/digitalocean/doctl/do" +import godo "github.com/digitalocean/godo" +import mock "github.com/stretchr/testify/mock" + +// ProjectsService is an autogenerated mock type for the ProjectsService type +type ProjectsService struct { + mock.Mock +} + +// AssignResources provides a mock function with given fields: projectUUID, resources +func (_m *ProjectsService) AssignResources(projectUUID string, resources []string) (do.ProjectResources, error) { + ret := _m.Called(projectUUID, resources) + + var r0 do.ProjectResources + if rf, ok := ret.Get(0).(func(string, []string) do.ProjectResources); ok { + r0 = rf(projectUUID, resources) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(do.ProjectResources) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string, []string) error); ok { + r1 = rf(projectUUID, resources) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Create provides a mock function with given fields: _a0 +func (_m *ProjectsService) Create(_a0 *godo.CreateProjectRequest) (*do.Project, error) { + ret := _m.Called(_a0) + + var r0 *do.Project + if rf, ok := ret.Get(0).(func(*godo.CreateProjectRequest) *do.Project); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*do.Project) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(*godo.CreateProjectRequest) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Delete provides a mock function with given fields: projectUUID +func (_m *ProjectsService) Delete(projectUUID string) error { + ret := _m.Called(projectUUID) + + var r0 error + if rf, ok := ret.Get(0).(func(string) error); ok { + r0 = rf(projectUUID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Get provides a mock function with given fields: projectUUID +func (_m *ProjectsService) Get(projectUUID string) (*do.Project, error) { + ret := _m.Called(projectUUID) + + var r0 *do.Project + if rf, ok := ret.Get(0).(func(string) *do.Project); ok { + r0 = rf(projectUUID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*do.Project) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(projectUUID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetDefault provides a mock function with given fields: +func (_m *ProjectsService) GetDefault() (*do.Project, error) { + ret := _m.Called() + + var r0 *do.Project + if rf, ok := ret.Get(0).(func() *do.Project); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*do.Project) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// List provides a mock function with given fields: +func (_m *ProjectsService) List() (do.Projects, error) { + ret := _m.Called() + + var r0 do.Projects + if rf, ok := ret.Get(0).(func() do.Projects); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(do.Projects) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListResources provides a mock function with given fields: projectUUID +func (_m *ProjectsService) ListResources(projectUUID string) (do.ProjectResources, error) { + ret := _m.Called(projectUUID) + + var r0 do.ProjectResources + if rf, ok := ret.Get(0).(func(string) do.ProjectResources); ok { + r0 = rf(projectUUID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(do.ProjectResources) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(projectUUID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Update provides a mock function with given fields: projectUUID, req +func (_m *ProjectsService) Update(projectUUID string, req *godo.UpdateProjectRequest) (*do.Project, error) { + ret := _m.Called(projectUUID, req) + + var r0 *do.Project + if rf, ok := ret.Get(0).(func(string, *godo.UpdateProjectRequest) *do.Project); ok { + r0 = rf(projectUUID, req) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*do.Project) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string, *godo.UpdateProjectRequest) error); ok { + r1 = rf(projectUUID, req) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/do/pagination.go b/do/pagination.go index 355fd9a11..d860d29ed 100644 --- a/do/pagination.go +++ b/do/pagination.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/do/pagination_test.go b/do/pagination_test.go index b957026e2..e267ecb7a 100644 --- a/do/pagination_test.go +++ b/do/pagination_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/do/projects.go b/do/projects.go new file mode 100644 index 000000000..d154ae1df --- /dev/null +++ b/do/projects.go @@ -0,0 +1,200 @@ +/* +Copyright 2018 The Doctl Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package do + +import ( + "context" + "errors" + + "github.com/digitalocean/godo" +) + +// Project wraps a godo Project. +type Project struct { + *godo.Project +} + +// Projects is a slice of Project. +type Projects []Project + +// ProjectResource wraps a godo ProjectResource +type ProjectResource struct { + *godo.ProjectResource +} + +// ProjectResources is a slice of ProjectResource. +type ProjectResources []ProjectResource + +// ProjectsService is the godo ProjectsService interface. +type ProjectsService interface { + List() (Projects, error) + GetDefault() (*Project, error) + Get(projectUUID string) (*Project, error) + Create(*godo.CreateProjectRequest) (*Project, error) + Update(projectUUID string, req *godo.UpdateProjectRequest) (*Project, error) + Delete(projectUUID string) error + + ListResources(projectUUID string) (ProjectResources, error) + AssignResources(projectUUID string, resources []string) (ProjectResources, error) +} + +type projectsService struct { + client *godo.Client + ctx context.Context +} + +var _ ProjectsService = &projectsService{} + +// NewProjectsService builds an instance of ProjectsService. +func NewProjectsService(client *godo.Client) ProjectsService { + return &projectsService{ + client: client, + ctx: context.Background(), + } +} + +// List projects. +func (ps *projectsService) List() (Projects, error) { + listFn := func(opt *godo.ListOptions) ([]interface{}, *godo.Response, error) { + list, resp, err := ps.client.Projects.List(ps.ctx, opt) + if err != nil { + return nil, nil, err + } + + si := make([]interface{}, len(list)) + for i := range list { + si[i] = list[i] + } + + return si, resp, err + } + + return projectsPaginatedListHelper(listFn) +} + +func (ps *projectsService) GetDefault() (*Project, error) { + f, _, err := ps.client.Projects.GetDefault(ps.ctx) + if err != nil { + return nil, err + } + + return &Project{Project: f}, nil +} + +func (ps *projectsService) Get(projectUUID string) (*Project, error) { + f, _, err := ps.client.Projects.Get(ps.ctx, projectUUID) + if err != nil { + return nil, err + } + + return &Project{Project: f}, nil +} + +func (ps *projectsService) Create(cr *godo.CreateProjectRequest) (*Project, error) { + f, _, err := ps.client.Projects.Create(ps.ctx, cr) + if err != nil { + return nil, err + } + + return &Project{Project: f}, nil +} + +func (ps *projectsService) Update(projectUUID string, ur *godo.UpdateProjectRequest) (*Project, error) { + p, _, err := ps.client.Projects.Update(ps.ctx, projectUUID, ur) + if err != nil { + return nil, err + } + + return &Project{Project: p}, nil +} + +func (ps *projectsService) Delete(projectUUID string) error { + _, err := ps.client.Projects.Delete(ps.ctx, projectUUID) + return err +} + +func (ps *projectsService) ListResources(projectUUID string) (ProjectResources, error) { + listFn := func(opt *godo.ListOptions) ([]interface{}, *godo.Response, error) { + list, resp, err := ps.client.Projects.ListResources(ps.ctx, projectUUID, opt) + if err != nil { + return nil, nil, err + } + + si := make([]interface{}, len(list)) + for i := range list { + si[i] = list[i] + } + + return si, resp, err + } + + return projectResourcesPaginatedListHelper(listFn) +} + +func (ps *projectsService) AssignResources(projectUUID string, resources []string) (ProjectResources, error) { + assignableResources := make([]interface{}, len(resources)) + for i, resource := range resources { + assignableResources[i] = resource + } + + assignedResources, _, err := ps.client.Projects.AssignResources(ps.ctx, projectUUID, assignableResources...) + if err != nil { + return nil, err + } + + prs := make(ProjectResources, len(assignedResources)) + for i, resource := range assignedResources { + prs[i] = ProjectResource{&resource} + } + + return prs, err +} + +func projectsPaginatedListHelper(listFn func(opt *godo.ListOptions) ([]interface{}, *godo.Response, error)) (Projects, error) { + si, err := PaginateResp(listFn) + if err != nil { + return nil, err + } + + list := make([]Project, len(si)) + for i := range si { + a, ok := si[i].(godo.Project) + if !ok { + return nil, errors.New("unexpected value in response") + } + + list[i] = Project{Project: &a} + } + + return list, nil +} + +func projectResourcesPaginatedListHelper(listFn func(opt *godo.ListOptions) ([]interface{}, *godo.Response, error)) (ProjectResources, error) { + si, err := PaginateResp(listFn) + if err != nil { + return nil, err + } + + list := make([]ProjectResource, len(si)) + for i := range si { + a, ok := si[i].(godo.ProjectResource) + if !ok { + return nil, errors.New("unexpected value in response") + } + + list[i] = ProjectResource{ProjectResource: &a} + } + + return list, nil +} diff --git a/do/regions.go b/do/regions.go index fc96119ea..dc33ac8e8 100644 --- a/do/regions.go +++ b/do/regions.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/do/sizes.go b/do/sizes.go index 5ed0ce929..801dbd4a1 100644 --- a/do/sizes.go +++ b/do/sizes.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/do/snapshots.go b/do/snapshots.go index ebcbe7a17..72d45383a 100644 --- a/do/snapshots.go +++ b/do/snapshots.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/do/sshkeys.go b/do/sshkeys.go index 25f4ee600..e92f0f112 100644 --- a/do/sshkeys.go +++ b/do/sshkeys.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/do/tags.go b/do/tags.go index 5eb7420e6..d7b7ba30d 100644 --- a/do/tags.go +++ b/do/tags.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/doit.go b/doit.go index 5b3ae6931..886d17a5c 100644 --- a/doit.go +++ b/doit.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -20,6 +20,8 @@ import ( "fmt" "log" "net/http" + "os" + "regexp" "strconv" "strings" @@ -47,7 +49,7 @@ var ( // DoitVersion is doit's version. DoitVersion = Version{ Major: 1, - Minor: 9, + Minor: 11, Patch: 0, Label: "dev", } @@ -156,6 +158,7 @@ type Config interface { GetGodoClient(trace bool, accessToken string) (*godo.Client, error) SSH(user, host, keyPath string, port int, opts ssh.Options) runner.Runner Set(ns, key string, val interface{}) + IsSet(key string) bool GetString(ns, key string) (string, error) GetBool(ns, key string) (bool, error) GetInt(ns, key string) (int, error) @@ -165,6 +168,7 @@ type Config interface { // LiveConfig is an implementation of Config for live values. type LiveConfig struct { godoClient *godo.Client + cliArgs map[string]bool } var _ Config = &LiveConfig{} @@ -233,6 +237,24 @@ func (c *LiveConfig) Set(ns, key string, val interface{}) { viper.Set(nskey, val) } +func (c *LiveConfig) IsSet(key string) bool { + r := regexp.MustCompile("\b*--([a-z-_]+)") + matches := r.FindAllStringSubmatch(strings.Join(os.Args, " "), -1) + if len(matches) == 0 { + return false + } + + if len(c.cliArgs) == 0 { + args := make(map[string]bool) + for _, match := range matches { + args[match[1]] = true + } + c.cliArgs = args + } + + return c.cliArgs[key] +} + // GetString returns a config value as a string. func (c *LiveConfig) GetString(ns, key string) (string, error) { if ns == NSRoot { @@ -241,12 +263,14 @@ func (c *LiveConfig) GetString(ns, key string) (string, error) { nskey := fmt.Sprintf("%s.%s", ns, key) - if _, ok := viper.AllSettings()[fmt.Sprintf("%s.required", nskey)]; ok { - if viper.GetString(nskey) == "" { - return "", NewMissingArgsErr(nskey) - } + isRequired := viper.GetBool(fmt.Sprintf("required.%s", nskey)) + str := viper.GetString(nskey) + + if isRequired && strings.TrimSpace(str) == "" { + return "", NewMissingArgsErr(nskey) } - return viper.GetString(nskey), nil + + return str, nil } // GetBool returns a config value as a bool. @@ -268,13 +292,14 @@ func (c *LiveConfig) GetInt(ns, key string) (int, error) { nskey := fmt.Sprintf("%s.%s", ns, key) - if _, ok := viper.AllSettings()[fmt.Sprintf("%s.required", nskey)]; ok { - if viper.GetInt(nskey) == 0 { - return 0, NewMissingArgsErr(nskey) - } + isRequired := viper.GetBool(fmt.Sprintf("required.%s", nskey)) + val := viper.GetInt(nskey) + + if isRequired && val == 0 { + return 0, NewMissingArgsErr(nskey) } - return viper.GetInt(nskey), nil + return val, nil } // GetStringSlice returns a config value as a string slice. @@ -285,15 +310,14 @@ func (c *LiveConfig) GetStringSlice(ns, key string) ([]string, error) { nskey := fmt.Sprintf("%s.%s", ns, key) - if _, ok := viper.AllSettings()[fmt.Sprintf("%s.required", nskey)]; ok { - if emptyStringSlice(viper.GetStringSlice(nskey)) { - return nil, NewMissingArgsErr(nskey) - } + isRequired := viper.GetBool(fmt.Sprintf("required.%s", nskey)) + val := viper.GetStringSlice(nskey) + if isRequired && emptyStringSlice(val) { + return nil, NewMissingArgsErr(nskey) } out := []string{} for _, item := range viper.GetStringSlice(nskey) { - item = strings.TrimPrefix(item, "[") item = strings.TrimSuffix(item, "]") diff --git a/doit_test.go b/doit_test.go index 2b3b17c4a..92b09cd6d 100644 --- a/doit_test.go +++ b/doit_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/errors.go b/errors.go index 996cd58a2..30f7fd27c 100644 --- a/errors.go +++ b/errors.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -30,3 +30,19 @@ func NewMissingArgsErr(cmd string) *MissingArgsErr { func (e *MissingArgsErr) Error() string { return fmt.Sprintf("(%s) command is missing required arguments", e.Command) } + +// InvalidURNErr is an error returned when their are too few arguments for a command. +type InvalidURNErr struct { + URN string +} + +var _ error = &InvalidURNErr{} + +// NewInvalidURNErr creates a InvalidURNErr instance. +func NewInvalidURNErr(urn string) *InvalidURNErr { + return &InvalidURNErr{URN: urn} +} + +func (e *InvalidURNErr) Error() string { + return fmt.Sprintf("URN must be in the format \"do::\"") +} diff --git a/errors_test.go b/errors_test.go index f6410b568..cde0e6b80 100644 --- a/errors_test.go +++ b/errors_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/install/bintray.go b/install/bintray.go index 13568a110..89fe21216 100644 --- a/install/bintray.go +++ b/install/bintray.go @@ -1,6 +1,6 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/install/download.go b/install/download.go index 9a9e67281..b9c71c20a 100644 --- a/install/download.go +++ b/install/download.go @@ -1,6 +1,6 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/install/download_test.go b/install/download_test.go index 72125777b..015a49142 100644 --- a/install/download_test.go +++ b/install/download_test.go @@ -1,6 +1,6 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/install/upload.go b/install/upload.go index 41668d7d6..47091769c 100644 --- a/install/upload.go +++ b/install/upload.go @@ -1,6 +1,6 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/pkg/runner/runner.go b/pkg/runner/runner.go index ebedcef52..c72aae6a4 100644 --- a/pkg/runner/runner.go +++ b/pkg/runner/runner.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/pkg/ssh/ssh.go b/pkg/ssh/ssh.go index 7c7f7c7fa..ab3c95ec9 100644 --- a/pkg/ssh/ssh.go +++ b/pkg/ssh/ssh.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/pkg/ssh/ssh_external.go b/pkg/ssh/ssh_external.go index ffb4752a5..cca048a14 100644 --- a/pkg/ssh/ssh_external.go +++ b/pkg/ssh/ssh_external.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/pkg/ssh/ssh_internal.go b/pkg/ssh/ssh_internal.go index 941a8ca82..ee1d46120 100644 --- a/pkg/ssh/ssh_internal.go +++ b/pkg/ssh/ssh_internal.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/pluginhost/plugin.go b/pluginhost/plugin.go index 46a52268d..b79c16ae9 100644 --- a/pluginhost/plugin.go +++ b/pluginhost/plugin.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -20,9 +20,9 @@ import ( "net/rpc/jsonrpc" "os" + "github.com/digitalocean/doctl" "github.com/natefinch/pie" "github.com/spf13/viper" - "github.com/digitalocean/doctl" ) // Host is an object consumers can retrieve doit information from. diff --git a/recorder.go b/recorder.go index 297a23309..7d6c69eb6 100644 --- a/recorder.go +++ b/recorder.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 63caa179e..f4bfd1d6d 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: doctl -version: "1.10.0" +version: "1.11.0" summary: A command line tool for DigitalOcean services description: doctl is a command line tool for DigitalOcean servics using the API. confinement: strict diff --git a/util.go b/util.go index 87518ffa9..d8fcc8e4f 100644 --- a/util.go +++ b/util.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/util_test.go b/util_test.go index 5c2ae3d20..0f4dacb7e 100644 --- a/util_test.go +++ b/util_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Doctl Authors All rights reserved. +Copyright 2018 The Doctl Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/vendor/github.com/digitalocean/godo/CHANGELOG.md b/vendor/github.com/digitalocean/godo/CHANGELOG.md index 98774ab65..e01582aff 100644 --- a/vendor/github.com/digitalocean/godo/CHANGELOG.md +++ b/vendor/github.com/digitalocean/godo/CHANGELOG.md @@ -1,8 +1,12 @@ # Change Log +## [v1.6.0] - 2018-10-16 + +- #185 Projects support [beta] - @mchitten + ## [v1.5.0] - 2018-10-01 -- #179 Adding tagging images support - @hugocorbucci +- #181 Adding tagging images support - @hugocorbucci ## [v1.4.2] - 2018-08-30 diff --git a/vendor/github.com/digitalocean/godo/domains.go b/vendor/github.com/digitalocean/godo/domains.go index cbcd46059..de266512e 100644 --- a/vendor/github.com/digitalocean/godo/domains.go +++ b/vendor/github.com/digitalocean/godo/domains.go @@ -97,6 +97,10 @@ func (d Domain) String() string { return Stringify(d) } +func (d Domain) URN() string { + return ToURN("Domain", d.Name) +} + // List all domains. func (s DomainsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Domain, *Response, error) { path := domainsBasePath diff --git a/vendor/github.com/digitalocean/godo/droplets.go b/vendor/github.com/digitalocean/godo/droplets.go index a3650edf4..ab508f1c0 100644 --- a/vendor/github.com/digitalocean/godo/droplets.go +++ b/vendor/github.com/digitalocean/godo/droplets.go @@ -125,6 +125,10 @@ func (d Droplet) String() string { return Stringify(d) } +func (d Droplet) URN() string { + return ToURN("Droplet", d.ID) +} + // DropletRoot represents a Droplet root type dropletRoot struct { Droplet *Droplet `json:"droplet"` diff --git a/vendor/github.com/digitalocean/godo/firewalls.go b/vendor/github.com/digitalocean/godo/firewalls.go index f34774bbe..c28cac03b 100644 --- a/vendor/github.com/digitalocean/godo/firewalls.go +++ b/vendor/github.com/digitalocean/godo/firewalls.go @@ -49,6 +49,10 @@ func (fw Firewall) String() string { return Stringify(fw) } +func (fw Firewall) URN() string { + return ToURN("Firewall", fw.ID) +} + // FirewallRequest represents the configuration to be applied to an existing or a new Firewall. type FirewallRequest struct { Name string `json:"name"` diff --git a/vendor/github.com/digitalocean/godo/floating_ips.go b/vendor/github.com/digitalocean/godo/floating_ips.go index deea3a13f..4545e9037 100644 --- a/vendor/github.com/digitalocean/godo/floating_ips.go +++ b/vendor/github.com/digitalocean/godo/floating_ips.go @@ -37,6 +37,10 @@ func (f FloatingIP) String() string { return Stringify(f) } +func (f FloatingIP) URN() string { + return ToURN("FloatingIP", f.IP) +} + type floatingIPsRoot struct { FloatingIPs []FloatingIP `json:"floating_ips"` Links *Links `json:"links"` diff --git a/vendor/github.com/digitalocean/godo/godo.go b/vendor/github.com/digitalocean/godo/godo.go index 002e6c3ca..e836eddf3 100644 --- a/vendor/github.com/digitalocean/godo/godo.go +++ b/vendor/github.com/digitalocean/godo/godo.go @@ -18,7 +18,7 @@ import ( ) const ( - libraryVersion = "1.5.0" + libraryVersion = "1.6.0" defaultBaseURL = "https://api.digitalocean.com/" userAgent = "godo/" + libraryVersion mediaType = "application/json" @@ -64,6 +64,7 @@ type Client struct { LoadBalancers LoadBalancersService Certificates CertificatesService Firewalls FirewallsService + Projects ProjectsService // Optional function called after every successful request made to the DO APIs onRequestCompleted RequestCompletionCallback @@ -159,23 +160,24 @@ func NewClient(httpClient *http.Client) *Client { c.Account = &AccountServiceOp{client: c} c.Actions = &ActionsServiceOp{client: c} c.CDNs = &CDNServiceOp{client: c} + c.Certificates = &CertificatesServiceOp{client: c} c.Domains = &DomainsServiceOp{client: c} c.Droplets = &DropletsServiceOp{client: c} c.DropletActions = &DropletActionsServiceOp{client: c} + c.Firewalls = &FirewallsServiceOp{client: c} c.FloatingIPs = &FloatingIPsServiceOp{client: c} c.FloatingIPActions = &FloatingIPActionsServiceOp{client: c} c.Images = &ImagesServiceOp{client: c} c.ImageActions = &ImageActionsServiceOp{client: c} c.Keys = &KeysServiceOp{client: c} + c.LoadBalancers = &LoadBalancersServiceOp{client: c} + c.Projects = &ProjectsServiceOp{client: c} c.Regions = &RegionsServiceOp{client: c} - c.Snapshots = &SnapshotsServiceOp{client: c} c.Sizes = &SizesServiceOp{client: c} + c.Snapshots = &SnapshotsServiceOp{client: c} c.Storage = &StorageServiceOp{client: c} c.StorageActions = &StorageActionsServiceOp{client: c} c.Tags = &TagsServiceOp{client: c} - c.LoadBalancers = &LoadBalancersServiceOp{client: c} - c.Certificates = &CertificatesServiceOp{client: c} - c.Firewalls = &FirewallsServiceOp{client: c} return c } diff --git a/vendor/github.com/digitalocean/godo/load_balancers.go b/vendor/github.com/digitalocean/godo/load_balancers.go index de19fe7f5..1472fff0b 100644 --- a/vendor/github.com/digitalocean/godo/load_balancers.go +++ b/vendor/github.com/digitalocean/godo/load_balancers.go @@ -47,6 +47,10 @@ func (l LoadBalancer) String() string { return Stringify(l) } +func (l LoadBalancer) URN() string { + return ToURN("LoadBalancer", l.ID) +} + // AsRequest creates a LoadBalancerRequest that can be submitted to Update with the current values of the LoadBalancer. // Modifying the returned LoadBalancerRequest will not modify the original LoadBalancer. func (l LoadBalancer) AsRequest() *LoadBalancerRequest { diff --git a/vendor/github.com/digitalocean/godo/projects.go b/vendor/github.com/digitalocean/godo/projects.go new file mode 100644 index 000000000..52291a1e0 --- /dev/null +++ b/vendor/github.com/digitalocean/godo/projects.go @@ -0,0 +1,302 @@ +package godo + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "path" +) + +const ( + // DefaultProject is the ID you should use if you are working with your + // default project. + DefaultProject = "default" + + projectsBasePath = "/v2/projects" +) + +// ProjectsService is an interface for creating and managing Projects with the DigitalOcean API. +// See: https://developers.digitalocean.com/documentation/documentation/v2/#projects +type ProjectsService interface { + List(context.Context, *ListOptions) ([]Project, *Response, error) + GetDefault(context.Context) (*Project, *Response, error) + Get(context.Context, string) (*Project, *Response, error) + Create(context.Context, *CreateProjectRequest) (*Project, *Response, error) + Update(context.Context, string, *UpdateProjectRequest) (*Project, *Response, error) + Delete(context.Context, string) (*Response, error) + + ListResources(context.Context, string, *ListOptions) ([]ProjectResource, *Response, error) + AssignResources(context.Context, string, ...interface{}) ([]ProjectResource, *Response, error) +} + +// ProjectsServiceOp handles communication with Projects methods of the DigitalOcean API. +type ProjectsServiceOp struct { + client *Client +} + +// Project represents a DigitalOcean Project configuration. +type Project struct { + ID string `json:"id"` + OwnerUUID string `json:"owner_uuid"` + OwnerID uint64 `json:"owner_id"` + Name string `json:"name"` + Description string `json:"description"` + Purpose string `json:"purpose"` + Environment string `json:"environment"` + IsDefault bool `json:"is_default"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +// String creates a human-readable description of a Project. +func (p Project) String() string { + return Stringify(p) +} + +// CreateProjectRequest represents the request to create a new project. +type CreateProjectRequest struct { + Name string `json:"name"` + Description string `json:"description"` + Purpose string `json:"purpose"` + Environment string `json:"environment"` +} + +// UpdateProjectRequest represents the request to update project information. +// This type expects certain attribute types, but is built this way to allow +// nil values as well. See `updateProjectRequest` for the "real" types. +type UpdateProjectRequest struct { + Name interface{} + Description interface{} + Purpose interface{} + Environment interface{} + IsDefault interface{} +} + +type updateProjectRequest struct { + Name *string `json:"name"` + Description *string `json:"description"` + Purpose *string `json:"purpose"` + Environment *string `json:"environment"` + IsDefault *bool `json:"is_default"` +} + +// MarshalJSON takes an UpdateRequest and converts it to the "typed" request +// which is sent to the projects API. This is a PATCH request, which allows +// partial attributes, so `null` values are OK. +func (upr *UpdateProjectRequest) MarshalJSON() ([]byte, error) { + d := &updateProjectRequest{} + if str, ok := upr.Name.(string); ok { + d.Name = &str + } + if str, ok := upr.Description.(string); ok { + d.Description = &str + } + if str, ok := upr.Purpose.(string); ok { + d.Purpose = &str + } + if str, ok := upr.Environment.(string); ok { + d.Environment = &str + } + if val, ok := upr.IsDefault.(bool); ok { + d.IsDefault = &val + } + + return json.Marshal(d) +} + +type assignResourcesRequest struct { + Resources []string `json:"resources"` +} + +// ProjectResource is the projects API's representation of a resource. +type ProjectResource struct { + URN string `json:"urn"` + AssignedAt string `json:"assigned_at"` + Links *ProjectResourceLinks `json:"links"` + Status string `json:"status,omitempty"` +} + +// ProjetResourceLinks specify the link for more information about the resource. +type ProjectResourceLinks struct { + Self string `json:"self"` +} + +type projectsRoot struct { + Projects []Project `json:"projects"` + Links *Links `json:"links"` +} + +type projectRoot struct { + Project *Project `json:"project"` +} + +type projectResourcesRoot struct { + Resources []ProjectResource `json:"resources"` + Links *Links `json:"links,omitempty"` +} + +var _ ProjectsService = &ProjectsServiceOp{} + +// List Projects. +func (p *ProjectsServiceOp) List(ctx context.Context, opts *ListOptions) ([]Project, *Response, error) { + path, err := addOptions(projectsBasePath, opts) + if err != nil { + return nil, nil, err + } + + req, err := p.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(projectsRoot) + resp, err := p.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if l := root.Links; l != nil { + resp.Links = l + } + + return root.Projects, resp, err +} + +// GetDefault project. +func (p *ProjectsServiceOp) GetDefault(ctx context.Context) (*Project, *Response, error) { + return p.getHelper(ctx, "default") +} + +// Get retrieves a single project by its ID. +func (p *ProjectsServiceOp) Get(ctx context.Context, projectID string) (*Project, *Response, error) { + return p.getHelper(ctx, projectID) +} + +// Create a new project. +func (p *ProjectsServiceOp) Create(ctx context.Context, cr *CreateProjectRequest) (*Project, *Response, error) { + req, err := p.client.NewRequest(ctx, http.MethodPost, projectsBasePath, cr) + if err != nil { + return nil, nil, err + } + + root := new(projectRoot) + resp, err := p.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root.Project, resp, err +} + +// Update an existing project. +func (p *ProjectsServiceOp) Update(ctx context.Context, projectID string, ur *UpdateProjectRequest) (*Project, *Response, error) { + path := path.Join(projectsBasePath, projectID) + req, err := p.client.NewRequest(ctx, http.MethodPatch, path, ur) + if err != nil { + return nil, nil, err + } + + root := new(projectRoot) + resp, err := p.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root.Project, resp, err +} + +// Delete an existing project. You cannot have any resources in a project +// before deleting it. See the API documentation for more details. +func (p *ProjectsServiceOp) Delete(ctx context.Context, projectID string) (*Response, error) { + path := path.Join(projectsBasePath, projectID) + req, err := p.client.NewRequest(ctx, http.MethodDelete, path, nil) + if err != nil { + return nil, err + } + + return p.client.Do(ctx, req, nil) +} + +// ListResources lists all resources in a project. +func (p *ProjectsServiceOp) ListResources(ctx context.Context, projectID string, opts *ListOptions) ([]ProjectResource, *Response, error) { + basePath := path.Join(projectsBasePath, projectID, "resources") + path, err := addOptions(basePath, opts) + if err != nil { + return nil, nil, err + } + + req, err := p.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(projectResourcesRoot) + resp, err := p.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if l := root.Links; l != nil { + resp.Links = l + } + + return root.Resources, resp, err +} + +// AssignResources assigns one or more resources to a project. AssignResources +// accepts resources in two possible formats: + +// 1. The resource type, like `&Droplet{ID: 1}` or `&FloatingIP{IP: "1.2.3.4"}` +// 2. A valid DO URN as a string, like "do:droplet:1234" +// +// There is no unassign. To move a resource to another project, just assign +// it to that other project. +func (p *ProjectsServiceOp) AssignResources(ctx context.Context, projectID string, resources ...interface{}) ([]ProjectResource, *Response, error) { + path := path.Join(projectsBasePath, projectID, "resources") + + ar := &assignResourcesRequest{ + Resources: make([]string, len(resources)), + } + + for i, resource := range resources { + switch resource.(type) { + case ResourceWithURN: + ar.Resources[i] = resource.(ResourceWithURN).URN() + case string: + ar.Resources[i] = resource.(string) + default: + return nil, nil, fmt.Errorf("%T must either be a string or have a valid URN method", resource) + } + } + req, err := p.client.NewRequest(ctx, http.MethodPost, path, ar) + if err != nil { + return nil, nil, err + } + + root := new(projectResourcesRoot) + resp, err := p.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if l := root.Links; l != nil { + resp.Links = l + } + + return root.Resources, resp, err +} + +func (p *ProjectsServiceOp) getHelper(ctx context.Context, projectID string) (*Project, *Response, error) { + path := path.Join(projectsBasePath, projectID) + + req, err := p.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(projectRoot) + resp, err := p.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root.Project, resp, err +} diff --git a/vendor/github.com/digitalocean/godo/projects_test.go b/vendor/github.com/digitalocean/godo/projects_test.go new file mode 100644 index 000000000..aeefd9c92 --- /dev/null +++ b/vendor/github.com/digitalocean/godo/projects_test.go @@ -0,0 +1,609 @@ +package godo + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "reflect" + "strings" + "testing" +) + +func TestProjects_List(t *testing.T) { + setup() + defer teardown() + + projects := []Project{ + { + ID: "project-1", + Name: "project-1", + }, + { + ID: "project-2", + Name: "project-2", + }, + } + + mux.HandleFunc("/v2/projects", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + resp, _ := json.Marshal(projects) + fmt.Fprint(w, fmt.Sprintf(`{"projects":%s}`, string(resp))) + }) + + resp, _, err := client.Projects.List(ctx, nil) + if err != nil { + t.Errorf("Projects.List returned error: %v", err) + } + + if !reflect.DeepEqual(resp, projects) { + t.Errorf("Projects.List returned %+v, expected %+v", resp, projects) + } +} + +func TestProjects_ListWithMultiplePages(t *testing.T) { + setup() + defer teardown() + + mockResp := ` + { + "projects": [ + { + "uuid": "project-1", + "name": "project-1" + }, + { + "uuid": "project-2", + "name": "project-2" + } + ], + "links": { + "pages": { + "next": "http://example.com/v2/projects?page=2" + } + } + }` + + mux.HandleFunc("/v2/projects", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + fmt.Fprint(w, mockResp) + }) + + _, resp, err := client.Projects.List(ctx, nil) + if err != nil { + t.Errorf("Projects.List returned error: %v", err) + } + + checkCurrentPage(t, resp, 1) +} + +func TestProjects_ListWithPageNumber(t *testing.T) { + setup() + defer teardown() + + mockResp := ` + { + "projects": [ + { + "uuid": "project-1", + "name": "project-1" + }, + { + "uuid": "project-2", + "name": "project-2" + } + ], + "links": { + "pages": { + "next": "http://example.com/v2/projects?page=3", + "prev": "http://example.com/v2/projects?page=1", + "last": "http://example.com/v2/projects?page=3", + "first": "http://example.com/v2/projects?page=1" + } + } + }` + + mux.HandleFunc("/v2/projects", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + fmt.Fprint(w, mockResp) + }) + + _, resp, err := client.Projects.List(ctx, &ListOptions{Page: 2}) + if err != nil { + t.Errorf("Projects.List returned error: %v", err) + } + + checkCurrentPage(t, resp, 2) +} + +func TestProjects_GetDefault(t *testing.T) { + setup() + defer teardown() + + project := &Project{ + ID: "project-1", + Name: "project-1", + } + + mux.HandleFunc("/v2/projects/default", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + resp, _ := json.Marshal(project) + fmt.Fprint(w, fmt.Sprintf(`{"project":%s}`, string(resp))) + }) + + resp, _, err := client.Projects.GetDefault(ctx) + if err != nil { + t.Errorf("Projects.GetDefault returned error: %v", err) + } + + if !reflect.DeepEqual(resp, project) { + t.Errorf("Projects.GetDefault returned %+v, expected %+v", resp, project) + } +} + +func TestProjects_GetWithUUID(t *testing.T) { + setup() + defer teardown() + + project := &Project{ + ID: "project-1", + Name: "project-1", + } + + mux.HandleFunc("/v2/projects/project-1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + resp, _ := json.Marshal(project) + fmt.Fprint(w, fmt.Sprintf(`{"project":%s}`, string(resp))) + }) + + resp, _, err := client.Projects.Get(ctx, "project-1") + if err != nil { + t.Errorf("Projects.Get returned error: %v", err) + } + + if !reflect.DeepEqual(resp, project) { + t.Errorf("Projects.Get returned %+v, expected %+v", resp, project) + } +} + +func TestProjects_Create(t *testing.T) { + setup() + defer teardown() + + createRequest := &CreateProjectRequest{ + Name: "my project", + Description: "for my stuff", + Purpose: "Just trying out DigitalOcean", + Environment: "Production", + } + + createResp := &Project{ + ID: "project-id", + Name: createRequest.Name, + Description: createRequest.Description, + Purpose: createRequest.Purpose, + Environment: createRequest.Environment, + } + + mux.HandleFunc("/v2/projects", func(w http.ResponseWriter, r *http.Request) { + v := new(CreateProjectRequest) + err := json.NewDecoder(r.Body).Decode(v) + if err != nil { + t.Fatalf("decode json: %v", err) + } + + testMethod(t, r, http.MethodPost) + if !reflect.DeepEqual(v, createRequest) { + t.Errorf("Request body = %+v, expected %+v", v, createRequest) + } + + resp, _ := json.Marshal(createResp) + fmt.Fprintf(w, fmt.Sprintf(`{"project":%s}`, string(resp))) + }) + + project, _, err := client.Projects.Create(ctx, createRequest) + if err != nil { + t.Errorf("Projects.Create returned error: %v", err) + } + + if !reflect.DeepEqual(project, createResp) { + t.Errorf("Projects.Create returned %+v, expected %+v", project, createResp) + } +} + +func TestProjects_UpdateWithOneAttribute(t *testing.T) { + setup() + defer teardown() + + updateRequest := &UpdateProjectRequest{ + Name: "my-great-project", + } + updateResp := &Project{ + ID: "project-id", + Name: updateRequest.Name.(string), + Description: "some-other-description", + Purpose: "some-other-purpose", + Environment: "some-other-env", + IsDefault: false, + } + + mux.HandleFunc("/v2/projects/project-1", func(w http.ResponseWriter, r *http.Request) { + reqBytes, respErr := ioutil.ReadAll(r.Body) + if respErr != nil { + t.Error("projects mock didn't work") + } + + req := strings.TrimSuffix(string(reqBytes), "\n") + expectedReq := `{"name":"my-great-project","description":null,"purpose":null,"environment":null,"is_default":null}` + if req != expectedReq { + t.Errorf("projects req didn't match up:\n expected %+v\n got %+v\n", expectedReq, req) + } + + resp, _ := json.Marshal(updateResp) + fmt.Fprintf(w, fmt.Sprintf(`{"project":%s}`, string(resp))) + }) + + project, _, err := client.Projects.Update(ctx, "project-1", updateRequest) + if err != nil { + t.Errorf("Projects.Update returned error: %v", err) + } + if !reflect.DeepEqual(project, updateResp) { + t.Errorf("Projects.Update returned %+v, expected %+v", project, updateResp) + } +} + +func TestProjects_UpdateWithAllAttributes(t *testing.T) { + setup() + defer teardown() + + updateRequest := &UpdateProjectRequest{ + Name: "my-great-project", + Description: "some-description", + Purpose: "some-purpose", + Environment: "some-env", + IsDefault: true, + } + updateResp := &Project{ + ID: "project-id", + Name: updateRequest.Name.(string), + Description: updateRequest.Description.(string), + Purpose: updateRequest.Purpose.(string), + Environment: updateRequest.Environment.(string), + IsDefault: updateRequest.IsDefault.(bool), + } + + mux.HandleFunc("/v2/projects/project-1", func(w http.ResponseWriter, r *http.Request) { + reqBytes, respErr := ioutil.ReadAll(r.Body) + if respErr != nil { + t.Error("projects mock didn't work") + } + + req := strings.TrimSuffix(string(reqBytes), "\n") + expectedReq := `{"name":"my-great-project","description":"some-description","purpose":"some-purpose","environment":"some-env","is_default":true}` + if req != expectedReq { + t.Errorf("projects req didn't match up:\n expected %+v\n got %+v\n", expectedReq, req) + } + + resp, _ := json.Marshal(updateResp) + fmt.Fprintf(w, fmt.Sprintf(`{"project":%s}`, string(resp))) + }) + + project, _, err := client.Projects.Update(ctx, "project-1", updateRequest) + if err != nil { + t.Errorf("Projects.Update returned error: %v", err) + } + if !reflect.DeepEqual(project, updateResp) { + t.Errorf("Projects.Update returned %+v, expected %+v", project, updateResp) + } +} + +func TestProjects_Destroy(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/v2/projects/project-1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodDelete) + }) + + _, err := client.Projects.Delete(ctx, "project-1") + if err != nil { + t.Errorf("Projects.Delete returned error: %v", err) + } +} + +func TestProjects_ListResources(t *testing.T) { + setup() + defer teardown() + + resources := []ProjectResource{ + { + URN: "do:droplet:1", + AssignedAt: "2018-09-27 00:00:00", + Links: &ProjectResourceLinks{ + Self: "http://example.com/v2/droplets/1", + }, + }, + { + URN: "do:floatingip:1.2.3.4", + AssignedAt: "2018-09-27 00:00:00", + Links: &ProjectResourceLinks{ + Self: "http://example.com/v2/floating_ips/1.2.3.4", + }, + }, + } + + mux.HandleFunc("/v2/projects/project-1/resources", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + resp, _ := json.Marshal(resources) + fmt.Fprint(w, fmt.Sprintf(`{"resources":%s}`, string(resp))) + }) + + resp, _, err := client.Projects.ListResources(ctx, "project-1", nil) + if err != nil { + t.Errorf("Projects.List returned error: %v", err) + } + + if !reflect.DeepEqual(resp, resources) { + t.Errorf("Projects.ListResources returned %+v, expected %+v", resp, resources) + } +} + +func TestProjects_ListResourcesWithMultiplePages(t *testing.T) { + setup() + defer teardown() + + mockResp := ` + { + "resources": [ + { + "urn": "do:droplet:1", + "assigned_at": "2018-09-27 00:00:00", + "links": { + "self": "http://example.com/v2/droplets/1" + } + }, + { + "urn": "do:floatingip:1.2.3.4", + "assigned_at": "2018-09-27 00:00:00", + "links": { + "self": "http://example.com/v2/floating_ips/1.2.3.4" + } + } + ], + "links": { + "pages": { + "next": "http://example.com/v2/projects/project-1/resources?page=2" + } + } + }` + + mux.HandleFunc("/v2/projects/project-1/resources", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + fmt.Fprint(w, mockResp) + }) + + _, resp, err := client.Projects.ListResources(ctx, "project-1", nil) + if err != nil { + t.Errorf("Projects.ListResources returned error: %v", err) + } + + checkCurrentPage(t, resp, 1) +} + +func TestProjects_ListResourcesWithPageNumber(t *testing.T) { + setup() + defer teardown() + + mockResp := ` + { + "resources": [ + { + "urn": "do:droplet:1", + "assigned_at": "2018-09-27 00:00:00", + "links": { + "self": "http://example.com/v2/droplets/1" + } + }, + { + "urn": "do:floatingip:1.2.3.4", + "assigned_at": "2018-09-27 00:00:00", + "links": { + "self": "http://example.com/v2/floating_ips/1.2.3.4" + } + } + ], + "links": { + "pages": { + "next": "http://example.com/v2/projects/project-1/resources?page=3", + "prev": "http://example.com/v2/projects/project-1/resources?page=1", + "last": "http://example.com/v2/projects/project-1/resources?page=3", + "first": "http://example.com/v2/projects/project-1/resources?page=1" + } + } + }` + + mux.HandleFunc("/v2/projects/project-1/resources", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + fmt.Fprint(w, mockResp) + }) + + _, resp, err := client.Projects.ListResources(ctx, "project-1", &ListOptions{Page: 2}) + if err != nil { + t.Errorf("Projects.ListResources returned error: %v", err) + } + + checkCurrentPage(t, resp, 2) +} + +func TestProjects_AssignFleetResourcesWithTypes(t *testing.T) { + setup() + defer teardown() + + assignableResources := []interface{}{ + &Droplet{ID: 1234}, + &FloatingIP{IP: "1.2.3.4"}, + } + + mockResp := ` + { + "resources": [ + { + "urn": "do:droplet:1234", + "assigned_at": "2018-09-27 00:00:00", + "links": { + "self": "http://example.com/v2/droplets/1" + } + }, + { + "urn": "do:floatingip:1.2.3.4", + "assigned_at": "2018-09-27 00:00:00", + "links": { + "self": "http://example.com/v2/floating_ips/1.2.3.4" + } + } + ] + }` + + mux.HandleFunc("/v2/projects/project-1/resources", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodPost) + reqBytes, respErr := ioutil.ReadAll(r.Body) + if respErr != nil { + t.Error("projects mock didn't work") + } + + req := strings.TrimSuffix(string(reqBytes), "\n") + expectedReq := `{"resources":["do:droplet:1234","do:floatingip:1.2.3.4"]}` + if req != expectedReq { + t.Errorf("projects assign req didn't match up:\n expected %+v\n got %+v\n", expectedReq, req) + } + + fmt.Fprint(w, mockResp) + }) + + _, _, err := client.Projects.AssignResources(ctx, "project-1", assignableResources...) + if err != nil { + t.Errorf("Projects.AssignResources returned error: %v", err) + } +} + +func TestProjects_AssignFleetResourcesWithStrings(t *testing.T) { + setup() + defer teardown() + + assignableResources := []interface{}{ + "do:droplet:1234", + "do:floatingip:1.2.3.4", + } + + mockResp := ` + { + "resources": [ + { + "urn": "do:droplet:1234", + "assigned_at": "2018-09-27 00:00:00", + "links": { + "self": "http://example.com/v2/droplets/1" + } + }, + { + "urn": "do:floatingip:1.2.3.4", + "assigned_at": "2018-09-27 00:00:00", + "links": { + "self": "http://example.com/v2/floating_ips/1.2.3.4" + } + } + ] + }` + + mux.HandleFunc("/v2/projects/project-1/resources", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodPost) + reqBytes, respErr := ioutil.ReadAll(r.Body) + if respErr != nil { + t.Error("projects mock didn't work") + } + + req := strings.TrimSuffix(string(reqBytes), "\n") + expectedReq := `{"resources":["do:droplet:1234","do:floatingip:1.2.3.4"]}` + if req != expectedReq { + t.Errorf("projects assign req didn't match up:\n expected %+v\n got %+v\n", expectedReq, req) + } + + fmt.Fprint(w, mockResp) + }) + + _, _, err := client.Projects.AssignResources(ctx, "project-1", assignableResources...) + if err != nil { + t.Errorf("Projects.AssignResources returned error: %v", err) + } +} + +func TestProjects_AssignFleetResourcesWithStringsAndTypes(t *testing.T) { + setup() + defer teardown() + + assignableResources := []interface{}{ + "do:droplet:1234", + &FloatingIP{IP: "1.2.3.4"}, + } + + mockResp := ` + { + "resources": [ + { + "urn": "do:droplet:1234", + "assigned_at": "2018-09-27 00:00:00", + "links": { + "self": "http://example.com/v2/droplets/1" + } + }, + { + "urn": "do:floatingip:1.2.3.4", + "assigned_at": "2018-09-27 00:00:00", + "links": { + "self": "http://example.com/v2/floating_ips/1.2.3.4" + } + } + ] + }` + + mux.HandleFunc("/v2/projects/project-1/resources", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodPost) + reqBytes, respErr := ioutil.ReadAll(r.Body) + if respErr != nil { + t.Error("projects mock didn't work") + } + + req := strings.TrimSuffix(string(reqBytes), "\n") + expectedReq := `{"resources":["do:droplet:1234","do:floatingip:1.2.3.4"]}` + if req != expectedReq { + t.Errorf("projects assign req didn't match up:\n expected %+v\n got %+v\n", expectedReq, req) + } + + fmt.Fprint(w, mockResp) + }) + + _, _, err := client.Projects.AssignResources(ctx, "project-1", assignableResources...) + if err != nil { + t.Errorf("Projects.AssignResources returned error: %v", err) + } +} + +func TestProjects_AssignFleetResourcesWithTypeWithoutURNReturnsError(t *testing.T) { + setup() + defer teardown() + + type fakeType struct{} + + assignableResources := []interface{}{ + fakeType{}, + } + + _, _, err := client.Projects.AssignResources(ctx, "project-1", assignableResources...) + if err == nil { + t.Errorf("expected Projects.AssignResources to error, but it did not") + } + + if err.Error() != "godo.fakeType must either be a string or have a valid URN method" { + t.Errorf("Projects.AssignResources returned the wrong error: %v", err) + } +} diff --git a/vendor/github.com/digitalocean/godo/storage.go b/vendor/github.com/digitalocean/godo/storage.go index f9c266fab..a79332a79 100644 --- a/vendor/github.com/digitalocean/godo/storage.go +++ b/vendor/github.com/digitalocean/godo/storage.go @@ -59,6 +59,10 @@ func (f Volume) String() string { return Stringify(f) } +func (f Volume) URN() string { + return ToURN("Volume", f.ID) +} + type storageVolumesRoot struct { Volumes []Volume `json:"volumes"` Links *Links `json:"links"` diff --git a/vendor/github.com/digitalocean/godo/strings.go b/vendor/github.com/digitalocean/godo/strings.go index 4a8bfb636..4d5c0ad22 100644 --- a/vendor/github.com/digitalocean/godo/strings.go +++ b/vendor/github.com/digitalocean/godo/strings.go @@ -5,10 +5,20 @@ import ( "fmt" "io" "reflect" + "strings" ) var timestampType = reflect.TypeOf(Timestamp{}) +type ResourceWithURN interface { + URN() string +} + +// ToURN converts the resource type and ID to a valid DO API URN. +func ToURN(resourceType string, id interface{}) string { + return fmt.Sprintf("%s:%s:%v", "do", strings.ToLower(resourceType), id) +} + // Stringify attempts to create a string representation of DigitalOcean types func Stringify(message interface{}) string { var buf bytes.Buffer