From 241928a87ad20a36458dd436b0c8a73f6e89e201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diogo=20Ferr=C3=A3o?= Date: Wed, 3 Apr 2024 13:35:10 +0100 Subject: [PATCH 1/8] update create examples to existing flavor --- docs/stackit_postgresflex_instance_create.md | 4 ++-- internal/cmd/postgresflex/instance/create/create.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/stackit_postgresflex_instance_create.md b/docs/stackit_postgresflex_instance_create.md index 0078f9e11..b081370e6 100644 --- a/docs/stackit_postgresflex_instance_create.md +++ b/docs/stackit_postgresflex_instance_create.md @@ -14,13 +14,13 @@ stackit postgresflex instance create [flags] ``` Create a PostgreSQL Flex instance with name "my-instance", ACL 0.0.0.0/0 (open access) and specify flavor by CPU and RAM. Other parameters are set to default values - $ stackit postgresflex instance create --name my-instance --cpu 1 --ram 4 --acl 0.0.0.0/0 + $ stackit postgresflex instance create --name my-instance --cpu 2 --ram 4 --acl 0.0.0.0/0 Create a PostgreSQL Flex instance with name "my-instance", ACL 0.0.0.0/0 (open access) and specify flavor by ID. Other parameters are set to default values $ stackit postgresflex instance create --name my-instance --flavor-id xxx --acl 0.0.0.0/0 Create a PostgreSQL Flex instance with name "my-instance", allow access to a specific range of IP addresses, specify flavor by CPU and RAM and set storage size to 20 GB. Other parameters are set to default values - $ stackit postgresflex instance create --name my-instance --cpu 1 --ram 4 --acl 1.2.3.0/24 --storage-size 20 + $ stackit postgresflex instance create --name my-instance --cpu 2 --ram 4 --acl 1.2.3.0/24 --storage-size 20 ``` ### Options diff --git a/internal/cmd/postgresflex/instance/create/create.go b/internal/cmd/postgresflex/instance/create/create.go index a94a7513c..8e43ae3a8 100644 --- a/internal/cmd/postgresflex/instance/create/create.go +++ b/internal/cmd/postgresflex/instance/create/create.go @@ -64,13 +64,13 @@ func NewCmd() *cobra.Command { Example: examples.Build( examples.NewExample( `Create a PostgreSQL Flex instance with name "my-instance", ACL 0.0.0.0/0 (open access) and specify flavor by CPU and RAM. Other parameters are set to default values`, - `$ stackit postgresflex instance create --name my-instance --cpu 1 --ram 4 --acl 0.0.0.0/0`), + `$ stackit postgresflex instance create --name my-instance --cpu 2 --ram 4 --acl 0.0.0.0/0`), examples.NewExample( `Create a PostgreSQL Flex instance with name "my-instance", ACL 0.0.0.0/0 (open access) and specify flavor by ID. Other parameters are set to default values`, `$ stackit postgresflex instance create --name my-instance --flavor-id xxx --acl 0.0.0.0/0`), examples.NewExample( `Create a PostgreSQL Flex instance with name "my-instance", allow access to a specific range of IP addresses, specify flavor by CPU and RAM and set storage size to 20 GB. Other parameters are set to default values`, - `$ stackit postgresflex instance create --name my-instance --cpu 1 --ram 4 --acl 1.2.3.0/24 --storage-size 20`), + `$ stackit postgresflex instance create --name my-instance --cpu 2 --ram 4 --acl 1.2.3.0/24 --storage-size 20`), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() From 5abdbfced405c6d962ebc3a2901a8b00fafe78af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diogo=20Ferr=C3=A3o?= Date: Wed, 3 Apr 2024 17:34:45 +0100 Subject: [PATCH 2/8] add the --force flag to delete command --- .../postgresflex/instance/delete/delete.go | 76 +++++++++++++++---- .../instance/delete/delete_test.go | 44 ++++++++++- .../pkg/services/postgresflex/utils/utils.go | 8 ++ .../services/postgresflex/utils/utils_test.go | 51 +++++++++++++ 4 files changed, 160 insertions(+), 19 deletions(-) diff --git a/internal/cmd/postgresflex/instance/delete/delete.go b/internal/cmd/postgresflex/instance/delete/delete.go index 2b5cae439..06fbdbbc3 100644 --- a/internal/cmd/postgresflex/instance/delete/delete.go +++ b/internal/cmd/postgresflex/instance/delete/delete.go @@ -8,6 +8,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/confirm" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/services/postgresflex/client" postgresflexUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/postgresflex/utils" @@ -21,23 +22,35 @@ import ( const ( instanceIdArg = "INSTANCE_ID" + + forceDeleteFlag = "force" + + deletedStatus = "Deleted" ) type inputModel struct { *globalflags.GlobalFlagModel - InstanceId string + InstanceId string + ForceDelete bool } func NewCmd() *cobra.Command { cmd := &cobra.Command{ Use: fmt.Sprintf("delete %s", instanceIdArg), Short: "Deletes a PostgreSQL Flex instance", - Long: "Deletes a PostgreSQL Flex instance.", - Args: args.SingleArg(instanceIdArg, utils.ValidateUUID), + Long: fmt.Sprintf("%s\n%s\n%s", + "Deletes a PostgreSQL Flex instance.", + "By default, instances will be kept in a delayed deleted state for 7 days before being permanently deleted.", + "Use the --force flag to force the deletion of a delayed deleted instance.", + ), + Args: args.SingleArg(instanceIdArg, utils.ValidateUUID), Example: examples.Build( examples.NewExample( `Delete a PostgreSQL Flex instance with ID "xxx"`, "$ stackit postgresflex instance delete xxx"), + examples.NewExample( + `Force the deletion of a delayed deleted PostgreSQL Flex instance with ID "xxx"`, + "$ stackit postgresflex instance delete xxx --force"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -65,22 +78,44 @@ func NewCmd() *cobra.Command { } } - // Call API - req := buildRequest(ctx, model, apiClient) - err = req.Execute() + instanceStatus, err := postgresflexUtils.GetInstanceStatus(ctx, apiClient, model.ProjectId, model.InstanceId) if err != nil { - return fmt.Errorf("delete PostgreSQL Flex instance: %w", err) + return fmt.Errorf("get PostgreSQL Flex instance status: %w", err) + } + + if instanceStatus == deletedStatus && !model.ForceDelete { + return fmt.Errorf("instance %q is already deleted, use --force to force the deletion of a delayed deleted instance", instanceLabel) + } + + if instanceStatus != deletedStatus { + // Call API + delReq := buildDeleteRequest(ctx, model, apiClient) + err = delReq.Execute() + if err != nil { + return fmt.Errorf("delete PostgreSQL Flex instance: %w", err) + } + + // Wait for async operation, if async mode not enabled + if !model.Async { + s := spinner.New(cmd) + s.Start("Deleting instance") + _, err = wait.DeleteInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId).WaitWithContext(ctx) + if err != nil { + return fmt.Errorf("wait for PostgreSQL Flex instance deletion: %w", err) + } + s.Stop() + } } - // Wait for async operation, if async mode not enabled - if !model.Async { - s := spinner.New(cmd) - s.Start("Deleting instance") - _, err = wait.DeleteInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId).WaitWithContext(ctx) + if model.ForceDelete { + // Call API + forceDelReq := buildForceDeleteRequest(ctx, model, apiClient) + err = forceDelReq.Execute() if err != nil { - return fmt.Errorf("wait for PostgreSQL Flex instance deletion: %w", err) + return fmt.Errorf("force delete PostgreSQL Flex instance: %w", err) } - s.Stop() + + // TODO: Wait for async operation, if async mode not enabled } operationState := "Deleted" @@ -91,9 +126,14 @@ func NewCmd() *cobra.Command { return nil }, } + configureFlags(cmd) return cmd } +func configureFlags(cmd *cobra.Command) { + cmd.Flags().BoolP(forceDeleteFlag, "f", false, "Force deletion of a delayed deleted instance") +} + func parseInput(cmd *cobra.Command, inputArgs []string) (*inputModel, error) { instanceId := inputArgs[0] @@ -105,10 +145,16 @@ func parseInput(cmd *cobra.Command, inputArgs []string) (*inputModel, error) { return &inputModel{ GlobalFlagModel: globalFlags, InstanceId: instanceId, + ForceDelete: flags.FlagToBoolValue(cmd, forceDeleteFlag), }, nil } -func buildRequest(ctx context.Context, model *inputModel, apiClient *postgresflex.APIClient) postgresflex.ApiDeleteInstanceRequest { +func buildDeleteRequest(ctx context.Context, model *inputModel, apiClient *postgresflex.APIClient) postgresflex.ApiDeleteInstanceRequest { req := apiClient.DeleteInstance(ctx, model.ProjectId, model.InstanceId) return req } + +func buildForceDeleteRequest(ctx context.Context, model *inputModel, apiClient *postgresflex.APIClient) postgresflex.ApiForceDeleteInstanceRequest { + req := apiClient.ForceDeleteInstance(ctx, model.ProjectId, model.InstanceId) + return req +} diff --git a/internal/cmd/postgresflex/instance/delete/delete_test.go b/internal/cmd/postgresflex/instance/delete/delete_test.go index a9b5f8735..fd5abe670 100644 --- a/internal/cmd/postgresflex/instance/delete/delete_test.go +++ b/internal/cmd/postgresflex/instance/delete/delete_test.go @@ -54,7 +54,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { return model } -func fixtureRequest(mods ...func(request *postgresflex.ApiDeleteInstanceRequest)) postgresflex.ApiDeleteInstanceRequest { +func fixtureDeleteRequest(mods ...func(request *postgresflex.ApiDeleteInstanceRequest)) postgresflex.ApiDeleteInstanceRequest { request := testClient.DeleteInstance(testCtx, testProjectId, testInstanceId) for _, mod := range mods { mod(&request) @@ -62,6 +62,14 @@ func fixtureRequest(mods ...func(request *postgresflex.ApiDeleteInstanceRequest) return request } +func fixtureForceDeleteRequest(mods ...func(request *postgresflex.ApiForceDeleteInstanceRequest)) postgresflex.ApiForceDeleteInstanceRequest { + request := testClient.ForceDeleteInstance(testCtx, testProjectId, testInstanceId) + for _, mod := range mods { + mod(&request) + } + return request +} + func TestParseInput(t *testing.T) { tests := []struct { description string @@ -186,7 +194,7 @@ func TestParseInput(t *testing.T) { } } -func TestBuildRequest(t *testing.T) { +func TestBuildDeleteRequest(t *testing.T) { tests := []struct { description string model *inputModel @@ -195,13 +203,41 @@ func TestBuildRequest(t *testing.T) { { description: "base", model: fixtureInputModel(), - expectedRequest: fixtureRequest(), + expectedRequest: fixtureDeleteRequest(), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildDeleteRequest(testCtx, tt.model, testClient) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestBuildForceDeleteRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest postgresflex.ApiForceDeleteInstanceRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureForceDeleteRequest(), }, } for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - request := buildRequest(testCtx, tt.model, testClient) + request := buildForceDeleteRequest(testCtx, tt.model, testClient) diff := cmp.Diff(request, tt.expectedRequest, cmp.AllowUnexported(tt.expectedRequest), diff --git a/internal/pkg/services/postgresflex/utils/utils.go b/internal/pkg/services/postgresflex/utils/utils.go index d5582b2f4..298df752e 100644 --- a/internal/pkg/services/postgresflex/utils/utils.go +++ b/internal/pkg/services/postgresflex/utils/utils.go @@ -149,6 +149,14 @@ func GetInstanceName(ctx context.Context, apiClient PostgresFlexClient, projectI return *resp.Item.Name, nil } +func GetInstanceStatus(ctx context.Context, apiClient PostgresFlexClient, projectId, instanceId string) (string, error) { + resp, err := apiClient.GetInstanceExecute(ctx, projectId, instanceId) + if err != nil { + return "", fmt.Errorf("get PostgreSQL Flex instance: %w", err) + } + return *resp.Item.Status, nil +} + func GetUserName(ctx context.Context, apiClient PostgresFlexClient, projectId, instanceId, userId string) (string, error) { resp, err := apiClient.GetUserExecute(ctx, projectId, instanceId, userId) if err != nil { diff --git a/internal/pkg/services/postgresflex/utils/utils_test.go b/internal/pkg/services/postgresflex/utils/utils_test.go index e5a2c6455..c25d4a1d3 100644 --- a/internal/pkg/services/postgresflex/utils/utils_test.go +++ b/internal/pkg/services/postgresflex/utils/utils_test.go @@ -21,6 +21,7 @@ var ( const ( testInstanceName = "instance" testUserName = "user" + testStatus = "running" ) type postgresFlexClientMocked struct { @@ -469,6 +470,56 @@ func TestGetInstanceName(t *testing.T) { } } +func TestGetInstanceStatus(t *testing.T) { + tests := []struct { + description string + getInstanceFails bool + getInstanceResp *postgresflex.InstanceResponse + isValid bool + expectedOutput string + }{ + { + description: "base", + getInstanceResp: &postgresflex.InstanceResponse{ + Item: &postgresflex.Instance{ + Status: utils.Ptr(testStatus), + }, + }, + isValid: true, + expectedOutput: testStatus, + }, + { + description: "get instance fails", + getInstanceFails: true, + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + client := &postgresFlexClientMocked{ + getInstanceFails: tt.getInstanceFails, + getInstanceResp: tt.getInstanceResp, + } + + output, err := GetInstanceStatus(context.Background(), client, testProjectId, testInstanceId) + + if tt.isValid && err != nil { + t.Errorf("failed on valid input") + } + if !tt.isValid && err == nil { + t.Errorf("did not fail on invalid input") + } + if !tt.isValid { + return + } + if output != tt.expectedOutput { + t.Errorf("expected output to be %s, got %s", tt.expectedOutput, output) + } + }) + } +} + func TestGetUserName(t *testing.T) { tests := []struct { description string From eb6d6874f79c9674d5302bbbe5b050f09883c732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diogo=20Ferr=C3=A3o?= Date: Wed, 3 Apr 2024 17:37:50 +0100 Subject: [PATCH 3/8] generate docs --- docs/stackit_postgresflex_instance_delete.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/stackit_postgresflex_instance_delete.md b/docs/stackit_postgresflex_instance_delete.md index ad64987a5..06736231f 100644 --- a/docs/stackit_postgresflex_instance_delete.md +++ b/docs/stackit_postgresflex_instance_delete.md @@ -5,6 +5,8 @@ Deletes a PostgreSQL Flex instance ### Synopsis Deletes a PostgreSQL Flex instance. +By default, instances will be kept in a delayed deleted state for 7 days before being permanently deleted. +Use the --force flag to force the deletion of a delayed deleted instance. ``` stackit postgresflex instance delete INSTANCE_ID [flags] @@ -15,12 +17,16 @@ stackit postgresflex instance delete INSTANCE_ID [flags] ``` Delete a PostgreSQL Flex instance with ID "xxx" $ stackit postgresflex instance delete xxx + + Force the deletion of a delayed deleted PostgreSQL Flex instance with ID "xxx" + $ stackit postgresflex instance delete xxx --force ``` ### Options ``` - -h, --help Help for "stackit postgresflex instance delete" + -f, --force Force deletion of a delayed deleted instance + -h, --help Help for "stackit postgresflex instance delete" ``` ### Options inherited from parent commands From 510398bced5e699652cd4d4f10a8c2855a540f40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diogo=20Ferr=C3=A3o?= Date: Thu, 4 Apr 2024 09:28:16 +0100 Subject: [PATCH 4/8] add forcedeleteinstance waiter --- go.mod | 2 +- go.sum | 4 ++-- .../cmd/postgresflex/instance/delete/delete.go | 18 +++++++++++++++++- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 0923fc9c2..6ec239ebe 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/stackitcloud/stackit-sdk-go/services/dns v0.8.4 github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v0.11.1 github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.10.1 - github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.11.0 + github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.12.0-testing-waiter github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.7.7 github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.6.0 github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.3.6 diff --git a/go.sum b/go.sum index 83285a8f3..5c2066063 100644 --- a/go.sum +++ b/go.sum @@ -91,8 +91,8 @@ github.com/stackitcloud/stackit-sdk-go/services/objectstorage v0.8.6 h1:+mcoBKs6 github.com/stackitcloud/stackit-sdk-go/services/objectstorage v0.8.6/go.mod h1:W9BML8bqZb2dOZe1K+M+qBBs8/QNirr3jA0xxy9tNRY= github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.10.1 h1:LKic8dXtXKsRst2+wY9dNjjkMyJ05QIDpOJuRmVb410= github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.10.1/go.mod h1:g1o1bmqtTliy9UkFlRV/6bn6GQk+hkvnny3UjMI69S0= -github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.11.0 h1:w3vUPJcPE81nItkkbPs1pxm+QF4c0YIbPyY0dd6qI2w= -github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.11.0/go.mod h1:P0YyvgwIsVKJijdWGVJVOp/ac7PVX99Oj+dr4v1zECc= +github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.12.0-testing-waiter h1:jWHHbU4qt7FuwjHZRiggxdPaYb1XHn7Vio0yB0XkFo8= +github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.12.0-testing-waiter/go.mod h1:P0YyvgwIsVKJijdWGVJVOp/ac7PVX99Oj+dr4v1zECc= github.com/stackitcloud/stackit-sdk-go/services/rabbitmq v0.10.0 h1:Fle394socpyf662g3jMrtZpZaWVgBMBIEFnh4fnGock= github.com/stackitcloud/stackit-sdk-go/services/rabbitmq v0.10.0/go.mod h1:JvqOSrTCiynS0x6Y9OsK54yvdB6AtIWLwXDEjoCkAIg= github.com/stackitcloud/stackit-sdk-go/services/redis v0.10.1 h1:/tRad17HUcGRm448l8XyX6uhnnHVfj3VdUQquIwNq2Q= diff --git a/internal/cmd/postgresflex/instance/delete/delete.go b/internal/cmd/postgresflex/instance/delete/delete.go index 06fbdbbc3..19c77a1ac 100644 --- a/internal/cmd/postgresflex/instance/delete/delete.go +++ b/internal/cmd/postgresflex/instance/delete/delete.go @@ -115,13 +115,29 @@ func NewCmd() *cobra.Command { return fmt.Errorf("force delete PostgreSQL Flex instance: %w", err) } - // TODO: Wait for async operation, if async mode not enabled + // Wait for async operation, if async mode not enabled + if !model.Async { + s := spinner.New(cmd) + s.Start("Forcing deletion of instance") + _, err = wait.ForceDeleteInstanceWaitHandler(ctx, apiClient, model.ProjectId, model.InstanceId).WaitWithContext(ctx) + if err != nil { + return fmt.Errorf("wait for PostgreSQL Flex instance force deletion: %w", err) + } + s.Stop() + } } operationState := "Deleted" + if model.ForceDelete { + operationState = "Forcefully deleted" + } if model.Async { operationState = "Triggered deletion of" + if model.ForceDelete { + operationState = "Triggered forced deletion of" + } } + cmd.Printf("%s instance %q\n", operationState, instanceLabel) return nil }, From 4ecbf4cd2936f1b45e82772f4355e6a6b87bb5c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diogo=20Ferr=C3=A3o?= Date: Thu, 4 Apr 2024 15:07:01 +0100 Subject: [PATCH 5/8] extract deletion logic to function, test it. Address PR comments --- .../postgresflex/instance/delete/delete.go | 43 +++++-- .../instance/delete/delete_test.go | 118 ++++++++++++++++++ 2 files changed, 148 insertions(+), 13 deletions(-) diff --git a/internal/cmd/postgresflex/instance/delete/delete.go b/internal/cmd/postgresflex/instance/delete/delete.go index 19c77a1ac..4228e885a 100644 --- a/internal/cmd/postgresflex/instance/delete/delete.go +++ b/internal/cmd/postgresflex/instance/delete/delete.go @@ -24,8 +24,6 @@ const ( instanceIdArg = "INSTANCE_ID" forceDeleteFlag = "force" - - deletedStatus = "Deleted" ) type inputModel struct { @@ -41,7 +39,7 @@ func NewCmd() *cobra.Command { Long: fmt.Sprintf("%s\n%s\n%s", "Deletes a PostgreSQL Flex instance.", "By default, instances will be kept in a delayed deleted state for 7 days before being permanently deleted.", - "Use the --force flag to force the deletion of a delayed deleted instance.", + "Use the --force flag to force the immediate deletion of a delayed deleted instance.", ), Args: args.SingleArg(instanceIdArg, utils.ValidateUUID), Example: examples.Build( @@ -78,16 +76,12 @@ func NewCmd() *cobra.Command { } } - instanceStatus, err := postgresflexUtils.GetInstanceStatus(ctx, apiClient, model.ProjectId, model.InstanceId) + toDelete, toForceDelete, err := checkIfInstanceIsDeleted(ctx, model, apiClient) if err != nil { - return fmt.Errorf("get PostgreSQL Flex instance status: %w", err) - } - - if instanceStatus == deletedStatus && !model.ForceDelete { - return fmt.Errorf("instance %q is already deleted, use --force to force the deletion of a delayed deleted instance", instanceLabel) + return err } - if instanceStatus != deletedStatus { + if toDelete { // Call API delReq := buildDeleteRequest(ctx, model, apiClient) err = delReq.Execute() @@ -107,7 +101,7 @@ func NewCmd() *cobra.Command { } } - if model.ForceDelete { + if toForceDelete { // Call API forceDelReq := buildForceDeleteRequest(ctx, model, apiClient) err = forceDelReq.Execute() @@ -128,12 +122,12 @@ func NewCmd() *cobra.Command { } operationState := "Deleted" - if model.ForceDelete { + if toForceDelete { operationState = "Forcefully deleted" } if model.Async { operationState = "Triggered deletion of" - if model.ForceDelete { + if toForceDelete { operationState = "Triggered forced deletion of" } } @@ -174,3 +168,26 @@ func buildForceDeleteRequest(ctx context.Context, model *inputModel, apiClient * req := apiClient.ForceDeleteInstance(ctx, model.ProjectId, model.InstanceId) return req } + +type PostgreSQLFlexClient interface { + GetInstanceExecute(ctx context.Context, projectId, instanceId string) (*postgresflex.InstanceResponse, error) + ListVersionsExecute(ctx context.Context, projectId string) (*postgresflex.ListVersionsResponse, error) + GetUserExecute(ctx context.Context, projectId, instanceId, userId string) (*postgresflex.GetUserResponse, error) +} + +func checkIfInstanceIsDeleted(ctx context.Context, model *inputModel, apiClient PostgreSQLFlexClient) (toDelete, toForceDelete bool, err error) { + instanceStatus, err := postgresflexUtils.GetInstanceStatus(ctx, apiClient, model.ProjectId, model.InstanceId) + if err != nil { + return false, false, fmt.Errorf("get PostgreSQL Flex instance status: %w", err) + } + + if instanceStatus == wait.InstanceStateDeleted { + if !model.ForceDelete { + return false, false, fmt.Errorf("instance is already deleted, use --force to force the deletion of a delayed deleted instance") + } + + return false, model.ForceDelete, nil + } + + return true, model.ForceDelete, nil +} diff --git a/internal/cmd/postgresflex/instance/delete/delete_test.go b/internal/cmd/postgresflex/instance/delete/delete_test.go index fd5abe670..dfa422f68 100644 --- a/internal/cmd/postgresflex/instance/delete/delete_test.go +++ b/internal/cmd/postgresflex/instance/delete/delete_test.go @@ -2,14 +2,17 @@ package delete import ( "context" + "fmt" "testing" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" "github.com/stackitcloud/stackit-sdk-go/services/postgresflex" + "github.com/stackitcloud/stackit-sdk-go/services/postgresflex/wait" ) var projectIdFlag = globalflags.ProjectIdFlag @@ -21,6 +24,27 @@ var testClient = &postgresflex.APIClient{} var testProjectId = uuid.NewString() var testInstanceId = uuid.NewString() +type postgresFlexClientMocked struct { + getInstanceFails bool + getInstanceResp *postgresflex.InstanceResponse +} + +func (c *postgresFlexClientMocked) GetInstanceExecute(_ context.Context, _, _ string) (*postgresflex.InstanceResponse, error) { + if c.getInstanceFails { + return nil, fmt.Errorf("get instance failed") + } + return c.getInstanceResp, nil +} + +func (c *postgresFlexClientMocked) ListVersionsExecute(ctx context.Context, projectId string) (*postgresflex.ListVersionsResponse, error) { + // Not used in testing + return nil, nil +} +func (c *postgresFlexClientMocked) GetUserExecute(ctx context.Context, projectId, instanceId, userId string) (*postgresflex.GetUserResponse, error) { + // Not used in testing + return nil, nil +} + func fixtureArgValues(mods ...func(argValues []string)) []string { argValues := []string{ testInstanceId, @@ -249,3 +273,97 @@ func TestBuildForceDeleteRequest(t *testing.T) { }) } } + +func TestCheckIfInstanceIsDeleted(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedToDelete bool + expectedToForceDelete bool + getInstanceResponse *postgresflex.InstanceResponse + getInstanceFails bool + isValid bool + }{ + { + description: "delete instance state Ready", + model: fixtureInputModel(), + expectedToDelete: true, + expectedToForceDelete: false, + getInstanceResponse: &postgresflex.InstanceResponse{ + Item: &postgresflex.Instance{ + Status: utils.Ptr(wait.InstanceStateSuccess), + }, + }, + isValid: true, + }, + { + description: "force delete instance state Ready", + model: fixtureInputModel(func(model *inputModel) { + model.ForceDelete = true + }), + expectedToDelete: true, + expectedToForceDelete: true, + getInstanceResponse: &postgresflex.InstanceResponse{ + Item: &postgresflex.Instance{ + Status: utils.Ptr(wait.InstanceStateSuccess), + }, + }, + isValid: true, + }, + { + description: "force delete instance state Deleted", + model: fixtureInputModel(func(model *inputModel) { + model.ForceDelete = true + }), + expectedToDelete: false, + expectedToForceDelete: true, + getInstanceResponse: &postgresflex.InstanceResponse{ + Item: &postgresflex.Instance{ + Status: utils.Ptr(wait.InstanceStateDeleted), + }, + }, + isValid: true, + }, + { + description: "delete instance state Deleted", + model: fixtureInputModel(), + getInstanceResponse: &postgresflex.InstanceResponse{ + Item: &postgresflex.Instance{ + Status: utils.Ptr(wait.InstanceStateDeleted), + }, + }, + isValid: false, + }, + { + description: "delete instance get instance fails", + model: fixtureInputModel(), + getInstanceFails: true, + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + client := &postgresFlexClientMocked{ + getInstanceResp: tt.getInstanceResponse, + getInstanceFails: tt.getInstanceFails, + } + + toDelete, toForceDelete, err := checkIfInstanceIsDeleted(testCtx, tt.model, client) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error checking if instance is deleted: %v", err) + } + + if toDelete != tt.expectedToDelete { + t.Fatalf("toDelete does not match: got %v, expected %v", toDelete, tt.expectedToDelete) + } + + if toForceDelete != tt.expectedToForceDelete { + t.Fatalf("toForceDelete does not match: got %v, expected %v", toForceDelete, tt.expectedToForceDelete) + } + }) + } +} From 323cd7d907e57b625525474f6baac025f8fd67c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diogo=20Ferr=C3=A3o?= Date: Thu, 4 Apr 2024 15:08:55 +0100 Subject: [PATCH 6/8] fix linting, generate docs --- docs/stackit_postgresflex_instance_delete.md | 2 +- internal/cmd/postgresflex/instance/delete/delete_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/stackit_postgresflex_instance_delete.md b/docs/stackit_postgresflex_instance_delete.md index 06736231f..21dbbe393 100644 --- a/docs/stackit_postgresflex_instance_delete.md +++ b/docs/stackit_postgresflex_instance_delete.md @@ -6,7 +6,7 @@ Deletes a PostgreSQL Flex instance Deletes a PostgreSQL Flex instance. By default, instances will be kept in a delayed deleted state for 7 days before being permanently deleted. -Use the --force flag to force the deletion of a delayed deleted instance. +Use the --force flag to force the immediate deletion of a delayed deleted instance. ``` stackit postgresflex instance delete INSTANCE_ID [flags] diff --git a/internal/cmd/postgresflex/instance/delete/delete_test.go b/internal/cmd/postgresflex/instance/delete/delete_test.go index dfa422f68..e47f0c347 100644 --- a/internal/cmd/postgresflex/instance/delete/delete_test.go +++ b/internal/cmd/postgresflex/instance/delete/delete_test.go @@ -36,11 +36,11 @@ func (c *postgresFlexClientMocked) GetInstanceExecute(_ context.Context, _, _ st return c.getInstanceResp, nil } -func (c *postgresFlexClientMocked) ListVersionsExecute(ctx context.Context, projectId string) (*postgresflex.ListVersionsResponse, error) { +func (c *postgresFlexClientMocked) ListVersionsExecute(_ context.Context, _ string) (*postgresflex.ListVersionsResponse, error) { // Not used in testing return nil, nil } -func (c *postgresFlexClientMocked) GetUserExecute(ctx context.Context, projectId, instanceId, userId string) (*postgresflex.GetUserResponse, error) { +func (c *postgresFlexClientMocked) GetUserExecute(_ context.Context, _, _, _ string) (*postgresflex.GetUserResponse, error) { // Not used in testing return nil, nil } From 0dd6729b57de518acbffa0d825ef2315160d7b62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diogo=20Ferr=C3=A3o?= Date: Thu, 4 Apr 2024 15:23:30 +0100 Subject: [PATCH 7/8] rename util function --- internal/cmd/postgresflex/instance/delete/delete.go | 4 ++-- internal/cmd/postgresflex/instance/delete/delete_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/cmd/postgresflex/instance/delete/delete.go b/internal/cmd/postgresflex/instance/delete/delete.go index 4228e885a..025d1bf0f 100644 --- a/internal/cmd/postgresflex/instance/delete/delete.go +++ b/internal/cmd/postgresflex/instance/delete/delete.go @@ -76,7 +76,7 @@ func NewCmd() *cobra.Command { } } - toDelete, toForceDelete, err := checkIfInstanceIsDeleted(ctx, model, apiClient) + toDelete, toForceDelete, err := getNextOperations(ctx, model, apiClient) if err != nil { return err } @@ -175,7 +175,7 @@ type PostgreSQLFlexClient interface { GetUserExecute(ctx context.Context, projectId, instanceId, userId string) (*postgresflex.GetUserResponse, error) } -func checkIfInstanceIsDeleted(ctx context.Context, model *inputModel, apiClient PostgreSQLFlexClient) (toDelete, toForceDelete bool, err error) { +func getNextOperations(ctx context.Context, model *inputModel, apiClient PostgreSQLFlexClient) (toDelete, toForceDelete bool, err error) { instanceStatus, err := postgresflexUtils.GetInstanceStatus(ctx, apiClient, model.ProjectId, model.InstanceId) if err != nil { return false, false, fmt.Errorf("get PostgreSQL Flex instance status: %w", err) diff --git a/internal/cmd/postgresflex/instance/delete/delete_test.go b/internal/cmd/postgresflex/instance/delete/delete_test.go index e47f0c347..26197f744 100644 --- a/internal/cmd/postgresflex/instance/delete/delete_test.go +++ b/internal/cmd/postgresflex/instance/delete/delete_test.go @@ -349,7 +349,7 @@ func TestCheckIfInstanceIsDeleted(t *testing.T) { getInstanceFails: tt.getInstanceFails, } - toDelete, toForceDelete, err := checkIfInstanceIsDeleted(testCtx, tt.model, client) + toDelete, toForceDelete, err := getNextOperations(testCtx, tt.model, client) if err != nil { if !tt.isValid { return From 8ac2944e6c8fafb1c081c5a3c6e06d27882ac0a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diogo=20Ferr=C3=A3o?= Date: Thu, 4 Apr 2024 16:38:48 +0100 Subject: [PATCH 8/8] update sdk version --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6ec239ebe..ed6dc1ea9 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/stackitcloud/stackit-sdk-go/services/dns v0.8.4 github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v0.11.1 github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.10.1 - github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.12.0-testing-waiter + github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.12.0 github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.7.7 github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.6.0 github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.3.6 diff --git a/go.sum b/go.sum index 5c2066063..549215f25 100644 --- a/go.sum +++ b/go.sum @@ -91,8 +91,8 @@ github.com/stackitcloud/stackit-sdk-go/services/objectstorage v0.8.6 h1:+mcoBKs6 github.com/stackitcloud/stackit-sdk-go/services/objectstorage v0.8.6/go.mod h1:W9BML8bqZb2dOZe1K+M+qBBs8/QNirr3jA0xxy9tNRY= github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.10.1 h1:LKic8dXtXKsRst2+wY9dNjjkMyJ05QIDpOJuRmVb410= github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.10.1/go.mod h1:g1o1bmqtTliy9UkFlRV/6bn6GQk+hkvnny3UjMI69S0= -github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.12.0-testing-waiter h1:jWHHbU4qt7FuwjHZRiggxdPaYb1XHn7Vio0yB0XkFo8= -github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.12.0-testing-waiter/go.mod h1:P0YyvgwIsVKJijdWGVJVOp/ac7PVX99Oj+dr4v1zECc= +github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.12.0 h1:W2WSYUyhKaHQ+BZfmyRw9PKv5q7ihGRyNhNgIlyM+Y8= +github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.12.0/go.mod h1:P0YyvgwIsVKJijdWGVJVOp/ac7PVX99Oj+dr4v1zECc= github.com/stackitcloud/stackit-sdk-go/services/rabbitmq v0.10.0 h1:Fle394socpyf662g3jMrtZpZaWVgBMBIEFnh4fnGock= github.com/stackitcloud/stackit-sdk-go/services/rabbitmq v0.10.0/go.mod h1:JvqOSrTCiynS0x6Y9OsK54yvdB6AtIWLwXDEjoCkAIg= github.com/stackitcloud/stackit-sdk-go/services/redis v0.10.1 h1:/tRad17HUcGRm448l8XyX6uhnnHVfj3VdUQquIwNq2Q=