From 0994d945233c2192fe6f56c7881654564cb48b25 Mon Sep 17 00:00:00 2001 From: Aaron Lee Date: Thu, 16 Jan 2025 21:25:19 -0800 Subject: [PATCH 1/2] Update doctl to support Spaces Keys API --- commands/command_config.go | 3 + commands/commands_test.go | 4 + commands/displayers/spaces_keys.go | 72 +++++++++ commands/doit.go | 2 + commands/spaces.go | 32 ++++ commands/spaces_keys.go | 219 +++++++++++++++++++++++++ commands/spaces_keys_test.go | 250 +++++++++++++++++++++++++++++ do/mocks/SpacesKeysService.go | 101 ++++++++++++ do/spaces_keys.go | 110 +++++++++++++ scripts/regenmocks.sh | 1 + 10 files changed, 794 insertions(+) create mode 100644 commands/displayers/spaces_keys.go create mode 100644 commands/spaces.go create mode 100644 commands/spaces_keys.go create mode 100644 commands/spaces_keys_test.go create mode 100644 do/mocks/SpacesKeysService.go create mode 100644 do/spaces_keys.go diff --git a/commands/command_config.go b/commands/command_config.go index c0e6bfdc9..aee74c08a 100644 --- a/commands/command_config.go +++ b/commands/command_config.go @@ -21,6 +21,7 @@ import ( "github.com/digitalocean/doctl/commands/displayers" "github.com/digitalocean/doctl/do" "github.com/digitalocean/doctl/internal/apps/builder" + "github.com/spf13/viper" ) @@ -74,6 +75,7 @@ type CmdConfig struct { Monitoring func() do.MonitoringService Serverless func() do.ServerlessService OAuth func() do.OAuthService + SpacesKeys func() do.SpacesKeysService } // NewCmdConfig creates an instance of a CmdConfig. @@ -130,6 +132,7 @@ func NewCmdConfig(ns string, dc doctl.Config, out io.Writer, args []string, init return do.NewServerlessService(godoClient, getServerlessDirectory(), accessToken) } c.OAuth = func() do.OAuthService { return do.NewOAuthService(godoClient) } + c.SpacesKeys = func() do.SpacesKeysService { return do.NewSpacesKeysService(godoClient) } return nil }, diff --git a/commands/commands_test.go b/commands/commands_test.go index 160f65a0c..02a720252 100644 --- a/commands/commands_test.go +++ b/commands/commands_test.go @@ -22,6 +22,7 @@ import ( "github.com/digitalocean/doctl/do" domocks "github.com/digitalocean/doctl/do/mocks" "github.com/digitalocean/doctl/internal/apps/builder" + "github.com/digitalocean/godo" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -254,6 +255,7 @@ type tcMocks struct { appBuilder *builder.MockComponentBuilder appDockerEngineClient *builder.MockDockerEngineClient oauth *domocks.MockOAuthService + spacesKeys *domocks.MockSpacesKeysService } func withTestClient(t *testing.T, tFn testFn) { @@ -303,6 +305,7 @@ func withTestClient(t *testing.T, tFn testFn) { appBuilder: builder.NewMockComponentBuilder(ctrl), appDockerEngineClient: builder.NewMockDockerEngineClient(ctrl), oauth: domocks.NewMockOAuthService(ctrl), + spacesKeys: domocks.NewMockSpacesKeysService(ctrl), } testConfig := doctl.NewTestConfig() @@ -360,6 +363,7 @@ func withTestClient(t *testing.T, tFn testFn) { Monitoring: func() do.MonitoringService { return tm.monitoring }, Serverless: func() do.ServerlessService { return tm.serverless }, OAuth: func() do.OAuthService { return tm.oauth }, + SpacesKeys: func() do.SpacesKeysService { return tm.spacesKeys }, } tFn(config, tm) diff --git a/commands/displayers/spaces_keys.go b/commands/displayers/spaces_keys.go new file mode 100644 index 000000000..98a848ec0 --- /dev/null +++ b/commands/displayers/spaces_keys.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 ( + "io" + + "github.com/digitalocean/doctl/do" +) + +type SpacesKey struct { + SpacesKeys []do.SpacesKey +} + +var _ Displayable = &SpacesKey{} + +// ColMap implements Displayable. +func (s *SpacesKey) ColMap() map[string]string { + return map[string]string{ + "Name": "Name", + "AccessKey": "Access Key", + "SecretKey": "Secret Key", + "Grants": "Grants", + "CreatedAt": "Created At", + } +} + +// Cols implements Displayable. +func (s *SpacesKey) Cols() []string { + return []string{ + "Name", + "AccessKey", + "SecretKey", + "Grants", + "CreatedAt", + } +} + +// JSON implements Displayable. +func (s *SpacesKey) JSON(out io.Writer) error { + return writeJSON(s.SpacesKeys, out) +} + +// KV implements Displayable. +func (s *SpacesKey) KV() []map[string]any { + out := make([]map[string]any, 0, len(s.SpacesKeys)) + + for _, key := range s.SpacesKeys { + m := map[string]any{ + "Name": key.Name, + "AccessKey": key.AccessKey, + "SecretKey": key.SecretKey, + "Grants": key.GrantString(), + "CreatedAt": key.CreatedAt, + } + + out = append(out, m) + } + + return out +} diff --git a/commands/doit.go b/commands/doit.go index f4f42e23f..c1a66b675 100644 --- a/commands/doit.go +++ b/commands/doit.go @@ -22,6 +22,7 @@ import ( "time" "github.com/digitalocean/doctl" + "github.com/fatih/color" "github.com/mattn/go-isatty" "github.com/spf13/cobra" @@ -187,6 +188,7 @@ func addCommands() { DoitCmd.AddCommand(OneClicks()) DoitCmd.AddCommand(Monitoring()) DoitCmd.AddCommand(Serverless()) + DoitCmd.AddCommand(Spaces()) } func computeCmd() *Command { diff --git a/commands/spaces.go b/commands/spaces.go new file mode 100644 index 000000000..aacc8db2c --- /dev/null +++ b/commands/spaces.go @@ -0,0 +1,32 @@ +/* +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 "github.com/spf13/cobra" + +func Spaces() *Command { + cmd := &Command{ + Command: &cobra.Command{ + Use: "spaces", + Aliases: []string{"sp"}, + Short: "Display commands that manage DigitalOcean Spaces.", + Long: "The subcommands of `doctl spaces` allow you to access and manage Spaces.", + GroupID: manageResourcesGroup, + }, + } + + cmd.AddCommand(SpacesKeys()) + + return cmd +} diff --git a/commands/spaces_keys.go b/commands/spaces_keys.go new file mode 100644 index 000000000..3478cabcc --- /dev/null +++ b/commands/spaces_keys.go @@ -0,0 +1,219 @@ +/* +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" +) + +// SpacesKeys creates a new command that groups the subcommands for managing DigitalOcean Spaces Keys. +func SpacesKeys() *Command { + cmd := &Command{ + Command: &cobra.Command{ + Use: "keys", + Aliases: []string{"k"}, + Short: "Display commands that manage DigitalOcean Spaces Keys.", + Long: "The subcommands of `doctl spaces keys` allow you to access and manage Spaces Keys.", + }, + } + + createSpacesKeyDesc := "Create a key for a Space with the provided name." + cmdSpacesKeysCreate := CmdBuilder(cmd, spacesKeysCreate, "create ", "Create a key for a Space.", createSpacesKeyDesc, Writer) + AddStringSliceFlag(cmdSpacesKeysCreate, "grants", "g", []string{}, + `A list of grants to add to the key. The permission should be either 'read', 'readwrite', or 'fullaccess'. +Format: `+"`"+`"bucket=your-bucket;permission=your-permission"`+"`", requiredOpt()) + cmdSpacesKeysCreate.Example = "doctl spaces keys create my-key --grants 'bucket=my-bucket;permission=readwrite'" + + listSpacesKeysDesc := "List all keys for a Space." + cmdSpacesKeysList := CmdBuilder( + cmd, + spacesKeysList, + "list", + "List all keys for a Space.", + listSpacesKeysDesc, + Writer, aliasOpt("ls"), displayerType(&displayers.SpacesKey{}), + ) + cmdSpacesKeysList.Example = "The following command lists all Spaces Keys and uses the `--format` flag to return only the Name and Grants of each key. `doctl spaces keys list --format Name,Grants`" + + deleteSpacesKeyDesc := "Delete a key for a Space." + cmdSpacesKeysDelete := CmdBuilder( + cmd, + spacesKeysDelete, + "delete ", + "Delete a key for a Space.", + deleteSpacesKeyDesc, + Writer, aliasOpt("rm"), + ) + cmdSpacesKeysDelete.Example = "doctl spaces keys delete DOACCESSKEY" + + updateSpacesKeyDesc := "Update a key for a Space." + cmdSpacesKeysUpdate := CmdBuilder(cmd, spacesKeysUpdate, "update ", "Update a key for a Space.", updateSpacesKeyDesc, Writer) + AddStringFlag(cmdSpacesKeysUpdate, "name", "n", "", "The new name for the key.", requiredOpt()) + AddStringSliceFlag(cmdSpacesKeysUpdate, "grants", "g", []string{}, + `A list of grants to set to the key. The permission should be either 'read', 'readwrite', or 'fullaccess'. +Format: `+"`"+`"bucket=your-bucket;permission=your-permission"`+"`", requiredOpt()) + cmdSpacesKeysUpdate.Example = "doctl spaces keys update DOACCESSKEY --name new-key --grants 'bucket=my-bucket;permission=readwrite'" + + return cmd +} + +func spacesKeysCreate(c *CmdConfig) error { + err := ensureOneArg(c) + if err != nil { + return err + } + + grants, err := c.Doit.GetStringSlice(c.NS, "grants") + if err != nil { + return err + } + + parsedGrants, err := parseGrantsFromArg(grants) + if err != nil { + return err + } + + r := &godo.SpacesKeyCreateRequest{ + Name: c.Args[0], + Grants: parsedGrants, + } + + key, err := c.SpacesKeys().Create(r) + if err != nil { + return err + } + + return displaySpacesKeys(c, *key) +} + +func spacesKeysList(c *CmdConfig) error { + keys, err := c.SpacesKeys().List() + if err != nil { + return err + } + + return displaySpacesKeys(c, keys...) +} + +func spacesKeysDelete(c *CmdConfig) error { + if len(c.Args) == 0 { + return doctl.NewMissingArgsErr(c.NS) + } + + err := c.SpacesKeys().Delete(c.Args[0]) + if err != nil { + return err + } + + return nil +} + +func spacesKeysUpdate(c *CmdConfig) error { + if len(c.Args) == 0 { + return doctl.NewMissingArgsErr(c.NS) + } + + grants, err := c.Doit.GetStringSlice(c.NS, "grants") + if err != nil { + return err + } + + parsedGrants, err := parseGrantsFromArg(grants) + if err != nil { + return err + } + + name, err := c.Doit.GetString(c.NS, "name") + if err != nil { + return err + } + + r := &godo.SpacesKeyUpdateRequest{ + Name: name, + Grants: parsedGrants, + } + + key, err := c.SpacesKeys().Update(c.Args[0], r) + if err != nil { + return err + } + + return displaySpacesKeys(c, *key) +} + +func displaySpacesKeys(c *CmdConfig, keys ...do.SpacesKey) error { + item := &displayers.SpacesKey{SpacesKeys: keys} + return c.Display(item) +} + +func parseGrantsFromArg(grants []string) ([]*godo.Grant, error) { + parsedGrants := []*godo.Grant{} + for _, grant := range grants { + parsedGrant, err := parseGrant(grant) + if err != nil { + return nil, err + } + parsedGrants = append(parsedGrants, parsedGrant) + } + return parsedGrants, nil +} + +func parseGrant(grant string) (*godo.Grant, error) { + const ( + argSeparator = ";" + kvSeparator = "=" + ) + trimmedGrant := strings.TrimSuffix(grant, argSeparator) + parsedGrant := &godo.Grant{ + Bucket: "", + Permission: "", + } + for _, arg := range strings.Split(trimmedGrant, argSeparator) { + kv := strings.Split(arg, kvSeparator) + if len(kv) != 2 { + return nil, fmt.Errorf("A Grant must be in the format 'key=value'. Provided: %v", kv) + } + + key := kv[0] + value := kv[1] + + switch key { + case "bucket": + parsedGrant.Bucket = value + case "permission": + // Validate permission + switch value { + case "read": + parsedGrant.Permission = godo.SpacesKeyRead + case "readwrite": + parsedGrant.Permission = godo.SpacesKeyReadWrite + case "fullaccess": + parsedGrant.Permission = godo.SpacesKeyFullAccess + default: + return nil, fmt.Errorf("Unsupported permission %q", value) + } + default: + return nil, fmt.Errorf("Unsupported grant argument %q", key) + } + } + return parsedGrant, nil +} diff --git a/commands/spaces_keys_test.go b/commands/spaces_keys_test.go new file mode 100644 index 000000000..7a2267bc7 --- /dev/null +++ b/commands/spaces_keys_test.go @@ -0,0 +1,250 @@ +/* +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 ( + "testing" + + "github.com/digitalocean/doctl/do" + + "github.com/digitalocean/godo" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var ( + testValidName = "my-key" + testValidBucketName = "my-bucket" + testValidAccessKey = "DOACCESSKEY" + testSpacesKey = do.SpacesKey{ + SpacesKey: &godo.SpacesKey{ + Name: testValidName, + Grants: []*godo.Grant{{Bucket: testValidBucketName, Permission: godo.SpacesKeyReadWrite}}, + }, + } +) + +func TestSpacesKeysCommand(t *testing.T) { + cmd := SpacesKeys() + assert.NotNil(t, cmd) + assertCommandNames(t, cmd, "create", "list", "delete", "update") +} + +func TestRunSpacesKeysCreate(t *testing.T) { + testCases := []struct { + name string + args []string + grants []string + expectErr bool + }{ + { + name: "success", + args: []string{testValidName}, + grants: []string{"bucket=my-bucket;permission=readwrite"}, + expectErr: false, + }, + { + name: "missing key name", + args: []string{}, + grants: []string{"bucket=my-bucket;permission=readwrite"}, + expectErr: true, + }, + { + name: "invalid grant format", + args: []string{testValidName}, + grants: []string{"bucket=my-bucket;permission"}, + expectErr: true, + }, + { + name: "unsupported permission", + args: []string{testValidName}, + grants: []string{"bucket=my-bucket;permission=invalid"}, + expectErr: true, + }, + { + name: "too many arguments", + args: []string{testValidName, "extra-arg"}, + grants: []string{"bucket=my-bucket;permission=readwrite"}, + expectErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + if !tc.expectErr { + req := godo.SpacesKeyCreateRequest{ + Name: testValidName, + Grants: []*godo.Grant{ + {Bucket: testValidBucketName, Permission: godo.SpacesKeyReadWrite}, + }, + } + tm.spacesKeys.EXPECT().Create(&req).Return(&testSpacesKey, nil) + } + + config.Args = tc.args + config.Doit.Set(config.NS, "grants", tc.grants) + + err := spacesKeysCreate(config) + if tc.expectErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + }) + } +} + +func TestRunSpacesKeysList(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + tm.spacesKeys.EXPECT().List().Return([]do.SpacesKey{testSpacesKey}, nil) + + err := spacesKeysList(config) + require.NoError(t, err) + }) +} + +func TestRunSpacesKeysDelete(t *testing.T) { + testCases := []struct { + name string + args []string + expectErr bool + }{ + { + name: "success", + args: []string{testValidAccessKey}, + expectErr: false, + }, + { + name: "missing key id", + args: []string{}, + expectErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + if !tc.expectErr { + tm.spacesKeys.EXPECT().Delete(testValidAccessKey).Return(nil) + } + + config.Args = tc.args + + err := spacesKeysDelete(config) + if tc.expectErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + }) + } +} + +func TestRunSpacesKeysUpdate(t *testing.T) { + testCases := []struct { + name string + args []string + grants []string + expectErr bool + }{ + { + name: "success", + args: []string{testValidAccessKey}, + grants: []string{"bucket=my-bucket;permission=readwrite"}, + expectErr: false, + }, + { + name: "missing key id", + args: []string{}, + grants: []string{"bucket=my-bucket;permission=readwrite"}, + expectErr: true, + }, + { + name: "invalid grant format", + args: []string{testValidAccessKey}, + grants: []string{"bucket=my-bucket;permission"}, + expectErr: true, + }, + { + name: "unsupported permission", + args: []string{testValidAccessKey}, + grants: []string{"bucket=my-bucket;permission=invalid"}, + expectErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + if !tc.expectErr { + req := godo.SpacesKeyUpdateRequest{ + Name: testValidName, + Grants: []*godo.Grant{ + {Bucket: testValidBucketName, Permission: godo.SpacesKeyReadWrite}, + }, + } + tm.spacesKeys.EXPECT().Update(testValidAccessKey, &req).Return(&testSpacesKey, nil) + } + + config.Args = tc.args + config.Doit.Set(config.NS, "grants", tc.grants) + config.Doit.Set(config.NS, "name", testValidName) + + err := spacesKeysUpdate(config) + if tc.expectErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + }) + } +} + +func TestParseGrantsFromArg(t *testing.T) { + grants := []string{"bucket=my-bucket;permission=readwrite"} + parsedGrants, err := parseGrantsFromArg(grants) + require.NoError(t, err) + assert.Equal(t, "my-bucket", parsedGrants[0].Bucket) + assert.Equal(t, godo.SpacesKeyReadWrite, parsedGrants[0].Permission) +} + +func TestParseGrant(t *testing.T) { + grant := "bucket=my-bucket;permission=readwrite" + parsedGrant, err := parseGrant(grant) + require.NoError(t, err) + assert.Equal(t, "my-bucket", parsedGrant.Bucket) + assert.Equal(t, godo.SpacesKeyReadWrite, parsedGrant.Permission) +} + +func TestParseGrantInvalidFormat(t *testing.T) { + grant := "bucket=my-bucket;permission" + _, err := parseGrant(grant) + assert.Error(t, err) +} + +func TestParseGrantUnsupportedPermission(t *testing.T) { + grant := "bucket=my-bucket;permission=invalid" + _, err := parseGrant(grant) + assert.Error(t, err) +} + +func TestParseGrantUnsupportedArgument(t *testing.T) { + grant := "unsupported=my-bucket;permission=readwrite" + _, err := parseGrant(grant) + assert.Error(t, err) +} diff --git a/do/mocks/SpacesKeysService.go b/do/mocks/SpacesKeysService.go new file mode 100644 index 000000000..f9278d4a7 --- /dev/null +++ b/do/mocks/SpacesKeysService.go @@ -0,0 +1,101 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: spaces_keys.go +// +// Generated by this command: +// +// mockgen -source spaces_keys.go -package=mocks SpacesKeysService +// + +// Package mocks is a generated GoMock package. +package mocks + +import ( + reflect "reflect" + + do "github.com/digitalocean/doctl/do" + godo "github.com/digitalocean/godo" + gomock "go.uber.org/mock/gomock" +) + +// MockSpacesKeysService is a mock of SpacesKeysService interface. +type MockSpacesKeysService struct { + ctrl *gomock.Controller + recorder *MockSpacesKeysServiceMockRecorder + isgomock struct{} +} + +// MockSpacesKeysServiceMockRecorder is the mock recorder for MockSpacesKeysService. +type MockSpacesKeysServiceMockRecorder struct { + mock *MockSpacesKeysService +} + +// NewMockSpacesKeysService creates a new mock instance. +func NewMockSpacesKeysService(ctrl *gomock.Controller) *MockSpacesKeysService { + mock := &MockSpacesKeysService{ctrl: ctrl} + mock.recorder = &MockSpacesKeysServiceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSpacesKeysService) EXPECT() *MockSpacesKeysServiceMockRecorder { + return m.recorder +} + +// Create mocks base method. +func (m *MockSpacesKeysService) Create(arg0 *godo.SpacesKeyCreateRequest) (*do.SpacesKey, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create", arg0) + ret0, _ := ret[0].(*do.SpacesKey) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Create indicates an expected call of Create. +func (mr *MockSpacesKeysServiceMockRecorder) Create(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockSpacesKeysService)(nil).Create), arg0) +} + +// Delete mocks base method. +func (m *MockSpacesKeysService) Delete(arg0 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Delete", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete. +func (mr *MockSpacesKeysServiceMockRecorder) Delete(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockSpacesKeysService)(nil).Delete), arg0) +} + +// List mocks base method. +func (m *MockSpacesKeysService) List() ([]do.SpacesKey, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "List") + ret0, _ := ret[0].([]do.SpacesKey) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// List indicates an expected call of List. +func (mr *MockSpacesKeysServiceMockRecorder) List() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockSpacesKeysService)(nil).List)) +} + +// Update mocks base method. +func (m *MockSpacesKeysService) Update(arg0 string, arg1 *godo.SpacesKeyUpdateRequest) (*do.SpacesKey, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Update", arg0, arg1) + ret0, _ := ret[0].(*do.SpacesKey) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Update indicates an expected call of Update. +func (mr *MockSpacesKeysServiceMockRecorder) Update(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockSpacesKeysService)(nil).Update), arg0, arg1) +} diff --git a/do/spaces_keys.go b/do/spaces_keys.go new file mode 100644 index 000000000..ad2b6a11a --- /dev/null +++ b/do/spaces_keys.go @@ -0,0 +1,110 @@ +/* +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" + "fmt" + "strings" + + "github.com/digitalocean/godo" +) + +// SpacesKey wraps a godo SpacesKey. +type SpacesKey struct { + *godo.SpacesKey +} + +// GrantString returns a string representation of the grants. +func (s *SpacesKey) GrantString() string { + var grants []string + for _, grant := range s.Grants { + grants = append(grants, fmt.Sprintf("bucket=%s;permission=%s", grant.Bucket, grant.Permission)) + } + return strings.Join(grants, ",") +} + +// SpacesKeysService is an interface for interfacing with the Spaces Keys +type SpacesKeysService interface { + Create(*godo.SpacesKeyCreateRequest) (*SpacesKey, error) + Delete(string) error + Update(string, *godo.SpacesKeyUpdateRequest) (*SpacesKey, error) + List() ([]SpacesKey, error) +} + +type spacesKeysService struct { + client *godo.Client + ctx context.Context +} + +// NewSpacesKeysService returns a new instance of SpacesKeysService. +func NewSpacesKeysService(client *godo.Client) SpacesKeysService { + return &spacesKeysService{client, context.Background()} +} + +// Create creates a new Spaces key. +func (s *spacesKeysService) Create(cr *godo.SpacesKeyCreateRequest) (*SpacesKey, error) { + key, _, err := s.client.SpacesKeys.Create(s.ctx, cr) + if err != nil { + return nil, err + } + + return &SpacesKey{key}, nil +} + +// Delete deletes a Spaces key. +func (s *spacesKeysService) Delete(accessKey string) error { + _, err := s.client.SpacesKeys.Delete(s.ctx, accessKey) + return err +} + +// List returns all Spaces keys. +func (s *spacesKeysService) List() ([]SpacesKey, error) { + f := func(opt *godo.ListOptions) ([]any, *godo.Response, error) { + list, resp, err := s.client.SpacesKeys.List(s.ctx, opt) + if err != nil { + return nil, nil, err + } + + si := make([]any, len(list)) + for i := range list { + si[i] = list[i] + } + + return si, resp, err + } + + keys, err := PaginateResp(f) + if err != nil { + return nil, err + } + + list := make([]SpacesKey, len(keys)) + for i := range keys { + k := keys[i].(*godo.SpacesKey) + list[i] = SpacesKey{k} + } + + return list, nil +} + +// Update updates a Spaces key. +func (s *spacesKeysService) Update(accessKey string, ur *godo.SpacesKeyUpdateRequest) (*SpacesKey, error) { + key, _, err := s.client.SpacesKeys.Update(s.ctx, accessKey, ur) + if err != nil { + return nil, err + } + + return &SpacesKey{key}, nil +} diff --git a/scripts/regenmocks.sh b/scripts/regenmocks.sh index 817711a22..fbb5326d6 100755 --- a/scripts/regenmocks.sh +++ b/scripts/regenmocks.sh @@ -46,3 +46,4 @@ mockgen -source monitoring.go -package=mocks MonitoringService > mocks/Monitorin mockgen -source reserved_ip_actions.go -package=mocks ReservedIPActionsService > mocks/ReservedIPActionsService.go mockgen -source reserved_ips.go -package=mocks ReservedIPsService > mocks/ReservedIPsService.go mockgen -source serverless.go -package=mocks ServerlessService > mocks/ServerlessService.go +mockgen -source spaces_keys.go -package=mocks SpacesKeysService > mocks/SpacesKeysService.go From 56952d878c9ea136d9e9bbe01f55e8322f08ba22 Mon Sep 17 00:00:00 2001 From: Aaron Lee Date: Wed, 22 Jan 2025 11:27:44 -0800 Subject: [PATCH 2/2] Update commands/spaces.go Co-authored-by: Andrew Starr-Bochicchio --- commands/spaces.go | 1 + 1 file changed, 1 insertion(+) diff --git a/commands/spaces.go b/commands/spaces.go index aacc8db2c..904114e39 100644 --- a/commands/spaces.go +++ b/commands/spaces.go @@ -20,6 +20,7 @@ func Spaces() *Command { Command: &cobra.Command{ Use: "spaces", Aliases: []string{"sp"}, + Hidden: true, Short: "Display commands that manage DigitalOcean Spaces.", Long: "The subcommands of `doctl spaces` allow you to access and manage Spaces.", GroupID: manageResourcesGroup,