From c3b76ddad560fe210688908370b5468e5eb87927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20V=C3=A4lim=C3=A4ki?= <110451292+villevsv-upcloud@users.noreply.github.com> Date: Wed, 15 Nov 2023 13:08:49 +0200 Subject: [PATCH] feat(objectstorage): `delete`, `list` & `show` commands (#268) --- CHANGELOG.md | 3 + internal/commands/all/all.go | 7 + internal/commands/objectstorage/delete.go | 42 ++++++ .../commands/objectstorage/delete_test.go | 52 +++++++ internal/commands/objectstorage/list.go | 52 +++++++ .../commands/objectstorage/objectstorage.go | 21 +++ internal/commands/objectstorage/show.go | 137 ++++++++++++++++++ internal/format/objectstorage.go | 61 ++++++++ internal/mock/mock.go | 124 ++++++++++++++-- internal/service/service.go | 1 + 10 files changed, 491 insertions(+), 9 deletions(-) create mode 100644 internal/commands/objectstorage/delete.go create mode 100644 internal/commands/objectstorage/delete_test.go create mode 100644 internal/commands/objectstorage/list.go create mode 100644 internal/commands/objectstorage/objectstorage.go create mode 100644 internal/commands/objectstorage/show.go create mode 100644 internal/format/objectstorage.go diff --git a/CHANGELOG.md b/CHANGELOG.md index aa6cad1d1..2d0ebd95c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Add `objectstorage` commands (`delete`, `list`, `show`) for Managed object storage management + ## [3.1.0] - 2023-11-06 ### Added diff --git a/internal/commands/all/all.go b/internal/commands/all/all.go index 741dc4a1c..c47b3609e 100644 --- a/internal/commands/all/all.go +++ b/internal/commands/all/all.go @@ -12,6 +12,7 @@ import ( "github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/kubernetes/nodegroup" "github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/loadbalancer" "github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/network" + "github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/objectstorage" "github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/root" "github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/router" "github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/server" @@ -175,6 +176,12 @@ func BuildCommands(rootCmd *cobra.Command, conf *config.Config) { commands.BuildCommand(servergroup.ModifyCommand(), serverGroupCommand.Cobra(), conf) commands.BuildCommand(servergroup.ShowCommand(), serverGroupCommand.Cobra(), conf) + // Managed object storage operations + objectStorageCommand := commands.BuildCommand(objectstorage.BaseobjectstorageCommand(), rootCmd, conf) + commands.BuildCommand(objectstorage.DeleteCommand(), objectStorageCommand.Cobra(), conf) + commands.BuildCommand(objectstorage.ListCommand(), objectStorageCommand.Cobra(), conf) + commands.BuildCommand(objectstorage.ShowCommand(), objectStorageCommand.Cobra(), conf) + // Misc commands.BuildCommand( &root.VersionCommand{ diff --git a/internal/commands/objectstorage/delete.go b/internal/commands/objectstorage/delete.go new file mode 100644 index 000000000..6c51e22bd --- /dev/null +++ b/internal/commands/objectstorage/delete.go @@ -0,0 +1,42 @@ +package objectstorage + +import ( + "fmt" + + "github.com/UpCloudLtd/upcloud-cli/v3/internal/commands" + "github.com/UpCloudLtd/upcloud-cli/v3/internal/output" + "github.com/UpCloudLtd/upcloud-go-api/v6/upcloud/request" +) + +// DeleteCommand creates the "objectstorage delete" command +func DeleteCommand() commands.Command { + return &deleteCommand{ + BaseCommand: commands.New( + "delete", + "Delete a Managed object storage service", + "upctl objectstorage delete 55199a44-4751-4e27-9394-7c7661910be8", + ), + } +} + +type deleteCommand struct { + *commands.BaseCommand +} + +// Execute implements commands.MultipleArgumentCommand +func (c *deleteCommand) Execute(exec commands.Executor, arg string) (output.Output, error) { + svc := exec.All() + msg := fmt.Sprintf("Deleting object storage service %v", arg) + exec.PushProgressStarted(msg) + + err := svc.DeleteManagedObjectStorage(exec.Context(), &request.DeleteManagedObjectStorageRequest{ + UUID: arg, + }) + if err != nil { + return commands.HandleError(exec, msg, err) + } + + exec.PushProgressSuccess(msg) + + return output.None{}, nil +} diff --git a/internal/commands/objectstorage/delete_test.go b/internal/commands/objectstorage/delete_test.go new file mode 100644 index 000000000..71a5fa77b --- /dev/null +++ b/internal/commands/objectstorage/delete_test.go @@ -0,0 +1,52 @@ +package objectstorage + +import ( + "testing" + + "github.com/UpCloudLtd/upcloud-cli/v3/internal/commands" + "github.com/UpCloudLtd/upcloud-cli/v3/internal/config" + smock "github.com/UpCloudLtd/upcloud-cli/v3/internal/mock" + + "github.com/UpCloudLtd/upcloud-go-api/v6/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/v6/upcloud/request" + "github.com/gemalto/flume" + "github.com/stretchr/testify/assert" +) + +func TestDeleteCommand(t *testing.T) { + targetMethod := "DeleteManagedObjectStorage" + + objectstorage := upcloud.ManagedObjectStorage{ + UUID: "17fbd082-30b0-11eb-adc1-0242ac120003", + } + + for _, test := range []struct { + name string + arg string + error string + req request.DeleteManagedObjectStorageRequest + }{ + { + name: "delete with UUID", + arg: objectstorage.UUID, + req: request.DeleteManagedObjectStorageRequest{UUID: objectstorage.UUID}, + }, + } { + t.Run(test.name, func(t *testing.T) { + mService := smock.Service{} + mService.On(targetMethod, &test.req).Return(nil) + + conf := config.New() + c := commands.BuildCommand(DeleteCommand(), nil, conf) + + _, err := c.(commands.MultipleArgumentCommand).Execute(commands.NewExecutor(conf, &mService, flume.New("test")), test.arg) + + if test.error != "" { + assert.EqualError(t, err, test.error) + } else { + assert.NoError(t, err) + mService.AssertNumberOfCalls(t, targetMethod, 1) + } + }) + } +} diff --git a/internal/commands/objectstorage/list.go b/internal/commands/objectstorage/list.go new file mode 100644 index 000000000..bb5eacc80 --- /dev/null +++ b/internal/commands/objectstorage/list.go @@ -0,0 +1,52 @@ +package objectstorage + +import ( + "github.com/UpCloudLtd/upcloud-cli/v3/internal/commands" + "github.com/UpCloudLtd/upcloud-cli/v3/internal/format" + "github.com/UpCloudLtd/upcloud-cli/v3/internal/output" + "github.com/UpCloudLtd/upcloud-cli/v3/internal/ui" + "github.com/UpCloudLtd/upcloud-go-api/v6/upcloud/request" +) + +// ListCommand creates the "objectstorage list" command +func ListCommand() commands.Command { + return &listCommand{ + BaseCommand: commands.New("list", "List current Managed object storage services", "upctl objectstorage list"), + } +} + +type listCommand struct { + *commands.BaseCommand +} + +// ExecuteWithoutArguments implements commands.NoArgumentCommand +func (c *listCommand) ExecuteWithoutArguments(exec commands.Executor) (output.Output, error) { + svc := exec.All() + objectstorages, err := svc.GetManagedObjectStorages(exec.Context(), &request.GetManagedObjectStoragesRequest{}) + if err != nil { + return nil, err + } + + rows := []output.TableRow{} + for _, objectstorage := range objectstorages { + rows = append(rows, output.TableRow{ + objectstorage.UUID, + objectstorage.Region, + objectstorage.ConfiguredStatus, + objectstorage.OperationalState, + }) + } + + return output.MarshaledWithHumanOutput{ + Value: objectstorages, + Output: output.Table{ + Columns: []output.TableColumn{ + {Key: "uuid", Header: "UUID", Colour: ui.DefaultUUUIDColours}, + {Key: "region", Header: "Region"}, + {Key: "configured_status", Header: "Configured status", Format: format.ObjectStorageConfiguredStatus}, + {Key: "operational_state", Header: "Operational state", Format: format.ObjectStorageOperationalState}, + }, + Rows: rows, + }, + }, nil +} diff --git a/internal/commands/objectstorage/objectstorage.go b/internal/commands/objectstorage/objectstorage.go new file mode 100644 index 000000000..c395cebfb --- /dev/null +++ b/internal/commands/objectstorage/objectstorage.go @@ -0,0 +1,21 @@ +package objectstorage + +import ( + "github.com/UpCloudLtd/upcloud-cli/v3/internal/commands" +) + +// BaseobjectstorageCommand creates the base "objectstorage" command +func BaseobjectstorageCommand() commands.Command { + return &objectstorageCommand{ + commands.New("objectstorage", "Manage Managed object storage services"), + } +} + +type objectstorageCommand struct { + *commands.BaseCommand +} + +// InitCommand implements Command.InitCommand +func (c *objectstorageCommand) InitCommand() { + c.Cobra().Aliases = []string{"object-storage", "objsto"} +} diff --git a/internal/commands/objectstorage/show.go b/internal/commands/objectstorage/show.go new file mode 100644 index 000000000..9524bba23 --- /dev/null +++ b/internal/commands/objectstorage/show.go @@ -0,0 +1,137 @@ +package objectstorage + +import ( + "github.com/UpCloudLtd/upcloud-cli/v3/internal/commands" + "github.com/UpCloudLtd/upcloud-cli/v3/internal/format" + "github.com/UpCloudLtd/upcloud-cli/v3/internal/labels" + "github.com/UpCloudLtd/upcloud-cli/v3/internal/output" + "github.com/UpCloudLtd/upcloud-cli/v3/internal/ui" + "github.com/UpCloudLtd/upcloud-go-api/v6/upcloud/request" +) + +// ShowCommand creates the "objectstorage show" command +func ShowCommand() commands.Command { + return &showCommand{ + BaseCommand: commands.New( + "show", + "Show Managed object storage service details", + "upctl objectstorage show 55199a44-4751-4e27-9394-7c7661910be8", + ), + } +} + +type showCommand struct { + *commands.BaseCommand +} + +// Execute implements commands.MultipleArgumentCommand +func (c *showCommand) Execute(exec commands.Executor, uuid string) (output.Output, error) { + svc := exec.All() + objectStorage, err := svc.GetManagedObjectStorage(exec.Context(), &request.GetManagedObjectStorageRequest{UUID: uuid}) + if err != nil { + return nil, err + } + + endpointRows := make([]output.TableRow, 0) + for _, endpoint := range objectStorage.Endpoints { + endpointRows = append(endpointRows, output.TableRow{ + endpoint.DomainName, + endpoint.Type, + }) + } + + endpointColumns := []output.TableColumn{ + {Key: "domain_name", Header: "Domain"}, + {Key: "type", Header: "Type"}, + } + + networkRows := make([]output.TableRow, 0) + for _, network := range objectStorage.Networks { + networkUUID := "" + if network.UUID != nil { + networkUUID = *network.UUID + } + networkRows = append(networkRows, output.TableRow{ + network.Name, + networkUUID, + network.Type, + network.Family, + }) + } + + networkColumns := []output.TableColumn{ + {Key: "name", Header: "Name"}, + {Key: "uuid", Header: "UUID", Colour: ui.DefaultUUUIDColours, Format: format.PossiblyUnknownString}, + {Key: "type", Header: "Type"}, + {Key: "Family", Header: "Family"}, + } + + userRows := make([]output.TableRow, 0) + for _, user := range objectStorage.Users { + userRows = append(userRows, output.TableRow{ + user.Username, + user.CreatedAt, + user.UpdatedAt, + user.OperationalState, + }) + } + + userColumns := []output.TableColumn{ + {Key: "name", Header: "Username"}, + {Key: "created_at", Header: "Created"}, + {Key: "updated_at", Header: "Updated"}, + {Key: "operational_state", Header: "Updated", Format: format.ObjectStorageUserOperationalState}, + } + + // For JSON and YAML output, passthrough API response + return output.MarshaledWithHumanOutput{ + Value: objectStorage, + Output: output.Combined{ + output.CombinedSection{ + Contents: output.Details{ + Sections: []output.DetailSection{ + { + Title: "Overview:", + Rows: []output.DetailRow{ + {Title: "UUID:", Value: objectStorage.UUID, Colour: ui.DefaultUUUIDColours}, + {Title: "Region:", Value: objectStorage.Region}, + {Title: "Configured status:", Value: objectStorage.ConfiguredStatus, Format: format.ObjectStorageConfiguredStatus}, + {Title: "Operational state:", Value: objectStorage.OperationalState, Format: format.ObjectStorageOperationalState}, + {Title: "Created:", Value: objectStorage.CreatedAt}, + {Title: "Updated:", Value: objectStorage.UpdatedAt}, + }, + }, + }, + }, + }, + labels.GetLabelsSectionWithResourceType(objectStorage.Labels, "managed object storage"), + output.CombinedSection{ + Key: "endpoints", + Title: "Endpoints:", + Contents: output.Table{ + Columns: endpointColumns, + Rows: endpointRows, + EmptyMessage: "No endpoints found for this Managed object storage service.", + }, + }, + output.CombinedSection{ + Key: "networks", + Title: "Networks:", + Contents: output.Table{ + Columns: networkColumns, + Rows: networkRows, + EmptyMessage: "No networks found for this Managed object storage service.", + }, + }, + output.CombinedSection{ + Key: "users", + Title: "Users:", + Contents: output.Table{ + Columns: userColumns, + Rows: userRows, + EmptyMessage: "No users found for this Managed object storage service.", + }, + }, + }, + }, nil +} diff --git a/internal/format/objectstorage.go b/internal/format/objectstorage.go new file mode 100644 index 000000000..fce23e0cb --- /dev/null +++ b/internal/format/objectstorage.go @@ -0,0 +1,61 @@ +package format + +import ( + "github.com/UpCloudLtd/upcloud-go-api/v6/upcloud" + "github.com/jedib0t/go-pretty/v6/text" +) + +// objectStorageOperationalStateColour maps managed object storage operational states to colours +func objectStorageOperationalStateColour(state upcloud.ManagedObjectStorageOperationalState) text.Colors { + switch state { + case upcloud.ManagedObjectStorageOperationalStateRunning: + return text.Colors{text.FgGreen} + case upcloud.ManagedObjectStorageOperationalStateDeleteDNS, + upcloud.ManagedObjectStorageOperationalStateDeleteNetwork, + upcloud.ManagedObjectStorageOperationalStateDeleteUser, + upcloud.ManagedObjectStorageOperationalStatePending, + upcloud.ManagedObjectStorageOperationalStateSetupCheckup, + upcloud.ManagedObjectStorageOperationalStateSetupDNS, + upcloud.ManagedObjectStorageOperationalStateSetupNetwork, + upcloud.ManagedObjectStorageOperationalStateSetupService, + upcloud.ManagedObjectStorageOperationalStateSetupUser: + return text.Colors{text.FgYellow} + default: + return text.Colors{text.FgHiBlack} + } +} + +// ObjectStorageOperationalState implements Format function for managed object storage operational states +func ObjectStorageOperationalState(val interface{}) (text.Colors, string, error) { + return usingColorFunction(objectStorageOperationalStateColour, val) +} + +// objectStorageConfiguredStatusColour maps managed object storage configured statuses to colours +func objectStorageConfiguredStatusColour(state upcloud.ManagedObjectStorageConfiguredStatus) text.Colors { + switch state { + case upcloud.ManagedObjectStorageConfiguredStatusStarted: + return text.Colors{text.FgGreen} + default: + return text.Colors{text.FgHiBlack} + } +} + +// ObjectStorageConfiguredStatus implements Format function for managed object storage configured statuses +func ObjectStorageConfiguredStatus(val interface{}) (text.Colors, string, error) { + return usingColorFunction(objectStorageConfiguredStatusColour, val) +} + +// objectStorageUserOperationalStateColour maps managed object storage user operational states to colours +func objectStorageUserOperationalStateColour(state upcloud.ManagedObjectStorageUserOperationalState) text.Colors { + switch state { + case upcloud.ManagedObjectStorageUserOperationalStateReady: + return text.Colors{text.FgGreen} + default: + return text.Colors{text.FgHiBlack} + } +} + +// ObjectStorageUserOperationalState implements Format function for managed object storage user operational states +func ObjectStorageUserOperationalState(val interface{}) (text.Colors, string, error) { + return usingColorFunction(objectStorageUserOperationalStateColour, val) +} diff --git a/internal/mock/mock.go b/internal/mock/mock.go index 6b6b6ced3..0f0e12bed 100644 --- a/internal/mock/mock.go +++ b/internal/mock/mock.go @@ -54,15 +54,17 @@ func (m *Service) GetPlans(context.Context) (*upcloud.Plans, error) { // make sure Service implements service interfaces var ( - _ service.Server = &Service{} - _ service.Storage = &Service{} - _ service.Firewall = &Service{} - _ service.Network = &Service{} - _ service.IPAddress = &Service{} - _ service.Cloud = &Service{} - _ service.Account = &Service{} - _ service.LoadBalancer = &Service{} - _ service.Kubernetes = &Service{} + _ service.Server = &Service{} + _ service.Storage = &Service{} + _ service.Firewall = &Service{} + _ service.Network = &Service{} + _ service.IPAddress = &Service{} + _ service.Cloud = &Service{} + _ service.Account = &Service{} + _ service.LoadBalancer = &Service{} + _ service.Kubernetes = &Service{} + _ service.ServerGroup = &Service{} + _ service.ManagedObjectStorage = &Service{} ) // GetServerConfigurations implements service.Server.GetServerConfigurations @@ -1050,3 +1052,107 @@ func (m *Service) DeleteServerGroup(ctx context.Context, r *request.DeleteServer } return nil } + +func (m *Service) GetManagedObjectStorageRegions(ctx context.Context, r *request.GetManagedObjectStorageRegionsRequest) ([]upcloud.ManagedObjectStorageRegion, error) { + return nil, nil +} + +func (m *Service) GetManagedObjectStorageRegion(ctx context.Context, r *request.GetManagedObjectStorageRegionRequest) (*upcloud.ManagedObjectStorageRegion, error) { + return nil, nil +} + +func (m *Service) CreateManagedObjectStorage(ctx context.Context, r *request.CreateManagedObjectStorageRequest) (*upcloud.ManagedObjectStorage, error) { + return nil, nil +} + +func (m *Service) GetManagedObjectStorages(ctx context.Context, r *request.GetManagedObjectStoragesRequest) ([]upcloud.ManagedObjectStorage, error) { + return nil, nil +} + +func (m *Service) GetManagedObjectStorage(ctx context.Context, r *request.GetManagedObjectStorageRequest) (*upcloud.ManagedObjectStorage, error) { + return nil, nil +} + +func (m *Service) ReplaceManagedObjectStorage(ctx context.Context, r *request.ReplaceManagedObjectStorageRequest) (*upcloud.ManagedObjectStorage, error) { + return nil, nil +} + +func (m *Service) ModifyManagedObjectStorage(ctx context.Context, r *request.ModifyManagedObjectStorageRequest) (*upcloud.ManagedObjectStorage, error) { + return nil, nil +} + +func (m *Service) DeleteManagedObjectStorage(ctx context.Context, r *request.DeleteManagedObjectStorageRequest) error { + args := m.Called(r) + if args[0] == nil { + return args.Error(0) + } + return nil +} + +func (m *Service) GetManagedObjectStorageMetrics(ctx context.Context, r *request.GetManagedObjectStorageMetricsRequest) (*upcloud.ManagedObjectStorageMetrics, error) { + return nil, nil +} + +func (m *Service) GetManagedObjectStorageBucketMetrics(ctx context.Context, r *request.GetManagedObjectStorageBucketMetricsRequest) ([]upcloud.ManagedObjectStorageBucketMetrics, error) { + return nil, nil +} + +func (m *Service) CreateManagedObjectStorageNetwork(ctx context.Context, r *request.CreateManagedObjectStorageNetworkRequest) (*upcloud.ManagedObjectStorageNetwork, error) { + return nil, nil +} + +func (m *Service) GetManagedObjectStorageNetworks(ctx context.Context, r *request.GetManagedObjectStorageNetworksRequest) ([]upcloud.ManagedObjectStorageNetwork, error) { + return nil, nil +} + +func (m *Service) GetManagedObjectStorageNetwork(ctx context.Context, r *request.GetManagedObjectStorageNetworkRequest) (*upcloud.ManagedObjectStorageNetwork, error) { + return nil, nil +} + +func (m *Service) DeleteManagedObjectStorageNetwork(ctx context.Context, r *request.DeleteManagedObjectStorageNetworkRequest) error { + return nil +} + +func (m *Service) CreateManagedObjectStorageUser(ctx context.Context, r *request.CreateManagedObjectStorageUserRequest) (*upcloud.ManagedObjectStorageUser, error) { + return nil, nil +} + +func (m *Service) GetManagedObjectStorageUsers(ctx context.Context, r *request.GetManagedObjectStorageUsersRequest) ([]upcloud.ManagedObjectStorageUser, error) { + return nil, nil +} + +func (m *Service) GetManagedObjectStorageUser(ctx context.Context, r *request.GetManagedObjectStorageUserRequest) (*upcloud.ManagedObjectStorageUser, error) { + return nil, nil +} + +func (m *Service) DeleteManagedObjectStorageUser(ctx context.Context, r *request.DeleteManagedObjectStorageUserRequest) error { + return nil +} + +func (m *Service) CreateManagedObjectStorageUserAccessKey(ctx context.Context, r *request.CreateManagedObjectStorageUserAccessKeyRequest) (*upcloud.ManagedObjectStorageUserAccessKey, error) { + return nil, nil +} + +func (m *Service) GetManagedObjectStorageUserAccessKeys(ctx context.Context, r *request.GetManagedObjectStorageUserAccessKeysRequest) ([]upcloud.ManagedObjectStorageUserAccessKey, error) { + return nil, nil +} + +func (m *Service) GetManagedObjectStorageUserAccessKey(ctx context.Context, r *request.GetManagedObjectStorageUserAccessKeyRequest) (*upcloud.ManagedObjectStorageUserAccessKey, error) { + return nil, nil +} + +func (m *Service) ModifyManagedObjectStorageUserAccessKey(ctx context.Context, r *request.ModifyManagedObjectStorageUserAccessKeyRequest) (*upcloud.ManagedObjectStorageUserAccessKey, error) { + return nil, nil +} + +func (m *Service) DeleteManagedObjectStorageUserAccessKey(ctx context.Context, r *request.DeleteManagedObjectStorageUserAccessKeyRequest) error { + return nil +} + +func (m *Service) WaitForManagedObjectStorageOperationalState(ctx context.Context, r *request.WaitForManagedObjectStorageOperationalStateRequest) (*upcloud.ManagedObjectStorage, error) { + return nil, nil +} + +func (m *Service) WaitForManagedObjectStorageDeletion(ctx context.Context, r *request.WaitForManagedObjectStorageDeletionRequest) error { + return nil +} diff --git a/internal/service/service.go b/internal/service/service.go index 200581273..13eeb3808 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -17,4 +17,5 @@ type AllServices interface { service.LoadBalancer service.Kubernetes service.ServerGroup + service.ManagedObjectStorage }