Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
cfbd64d
Add setting for custom endpoint
Feb 1, 2024
d310566
Add unset option
Feb 1, 2024
2abc9da
Add postgresflex
Feb 1, 2024
9c4dcf5
Add utils
Feb 1, 2024
00a8357
Add instance create
Feb 1, 2024
029a898
Change storage class to non-default
Feb 1, 2024
46e7c92
Add instance describe
Feb 1, 2024
f7c854a
Add instance list
Feb 1, 2024
003da7e
Fix name
Feb 1, 2024
74689fa
Fix typo
Feb 1, 2024
50001e3
Fix name
Feb 1, 2024
812db82
Add instance update
Feb 1, 2024
c3eda0d
Add instance delete
Feb 1, 2024
4d02d50
Generate docs
Feb 1, 2024
bbfbad8
Fix error message
Feb 2, 2024
312753e
Remove unused const
Feb 2, 2024
82711ae
Go mod tidy
Feb 2, 2024
0dbc7e4
Fix import paths
Feb 2, 2024
1f5265f
Simplify methods' signatures
Feb 5, 2024
6c24482
Simplify methods' signatures
Feb 5, 2024
5e453de
Update dependency
Feb 5, 2024
9355891
Fix error fields
Feb 5, 2024
dc8841f
Refactor long description
Feb 5, 2024
7e0bbbe
Reword command descriptions
Feb 5, 2024
9a1c130
Reword command descriptions
Feb 5, 2024
91a3a62
Fix acls not being shown in describe
Feb 5, 2024
8843470
Fixes to replicas and type flags
Feb 5, 2024
78b6ace
Add nil checks
Feb 6, 2024
88ff764
Add tests
Feb 6, 2024
2005275
Add nil checks
Feb 6, 2024
53acdfd
Add tests
Feb 6, 2024
48e54bd
Generate docs
Feb 6, 2024
4a1ccc4
Move list of available instance types to utils, fix list
Feb 6, 2024
0123978
Fix typo
Feb 6, 2024
44a4d0a
Move list of available instance types to utils
Feb 6, 2024
edd61e8
Reorganize methods
Feb 6, 2024
de44595
PostgreSQL version defaults to latest version available
Feb 6, 2024
fd314d4
Remove unused instance type
Feb 6, 2024
d9a29e0
Reorder fields
Feb 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add instance delete
  • Loading branch information
Henrique Santos committed Feb 5, 2024
commit c3eda0d2232491f952ed387220fccb5244066ee0
114 changes: 114 additions & 0 deletions internal/cmd/postgresflex/instance/delete/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package delete

import (
"context"
"fmt"

"stackit/internal/pkg/args"
"stackit/internal/pkg/confirm"
"stackit/internal/pkg/errors"
"stackit/internal/pkg/examples"
"stackit/internal/pkg/globalflags"
"stackit/internal/pkg/services/postgresflex/client"
postgresflexUtils "stackit/internal/pkg/services/postgresflex/utils"
"stackit/internal/pkg/spinner"
"stackit/internal/pkg/utils"

"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-sdk-go/services/postgresflex"
"github.com/stackitcloud/stackit-sdk-go/services/postgresflex/wait"
)

const (
instanceIdArg = "INSTANCE_ID"
)

type inputModel struct {
*globalflags.GlobalFlagModel
InstanceId string
}

func NewCmd() *cobra.Command {
cmd := &cobra.Command{
Use: fmt.Sprintf("delete %s", instanceIdArg),
Short: "Delete a PostgreSQL Flex instance",
Long: "Delete a PostgreSQL Flex 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"),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(cmd, args)
if err != nil {
return err
}

// Configure API client
apiClient, err := client.ConfigureClient(cmd)
if err != nil {
return err
}

instanceLabel, err := postgresflexUtils.GetInstanceName(ctx, apiClient, model.ProjectId, model.InstanceId)
if err != nil {
instanceLabel = model.InstanceId
}

if !model.AssumeYes {
prompt := fmt.Sprintf("Are you sure you want to delete instance %s? (This cannot be undone)", instanceLabel)
err = confirm.PromptForConfirmation(cmd, prompt)
if err != nil {
return err
}
}

// Call API
req := buildRequest(ctx, model, apiClient)
err = req.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()
}

operationState := "Deleted"
if model.Async {
operationState = "Triggered deletion of"
}
cmd.Printf("%s instance %s\n", operationState, instanceLabel)
return nil
},
}
return cmd
}

func parseInput(cmd *cobra.Command, inputArgs []string) (*inputModel, error) {
instanceId := inputArgs[0]

globalFlags := globalflags.Parse(cmd)
if globalFlags.ProjectId == "" {
return nil, &errors.ProjectIdError{}
}

return &inputModel{
GlobalFlagModel: globalFlags,
InstanceId: instanceId,
}, nil
}

func buildRequest(ctx context.Context, model *inputModel, apiClient *postgresflex.APIClient) postgresflex.ApiDeleteInstanceRequest {
req := apiClient.DeleteInstance(ctx, model.ProjectId, model.InstanceId)
return req
}
215 changes: 215 additions & 0 deletions internal/cmd/postgresflex/instance/delete/delete_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
package delete

import (
"context"
"testing"

"stackit/internal/pkg/globalflags"

"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"
)

var projectIdFlag = globalflags.ProjectIdFlag

type testCtxKey struct{}

var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &postgresflex.APIClient{}
var testProjectId = uuid.NewString()
var testInstanceId = uuid.NewString()

func fixtureArgValues(mods ...func(argValues []string)) []string {
argValues := []string{
testInstanceId,
}
for _, mod := range mods {
mod(argValues)
}
return argValues
}

func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
projectIdFlag: testProjectId,
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}

func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
ProjectId: testProjectId,
},
InstanceId: testInstanceId,
}
for _, mod := range mods {
mod(model)
}
return model
}

func fixtureRequest(mods ...func(request *postgresflex.ApiDeleteInstanceRequest)) postgresflex.ApiDeleteInstanceRequest {
request := testClient.DeleteInstance(testCtx, testProjectId, testInstanceId)
for _, mod := range mods {
mod(&request)
}
return request
}

func TestParseInput(t *testing.T) {
tests := []struct {
description string
argValues []string
flagValues map[string]string
isValid bool
expectedModel *inputModel
}{
{
description: "base",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(),
isValid: true,
expectedModel: fixtureInputModel(),
},
{
description: "no values",
argValues: []string{},
flagValues: map[string]string{},
isValid: false,
},
{
description: "no arg values",
argValues: []string{},
flagValues: fixtureFlagValues(),
isValid: false,
},
{
description: "no flag values",
argValues: fixtureArgValues(),
flagValues: map[string]string{},
isValid: false,
},
{
description: "project id missing",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, projectIdFlag)
}),
isValid: false,
},
{
description: "project id invalid 1",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = ""
}),
isValid: false,
},
{
description: "project id invalid 2",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = "invalid-uuid"
}),
isValid: false,
},
{
description: "instance id invalid 1",
argValues: []string{""},
flagValues: fixtureFlagValues(),
isValid: false,
},
{
description: "instance id invalid 2",
argValues: []string{"invalid-uuid"},
flagValues: fixtureFlagValues(),
isValid: false,
},
}

for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
cmd := NewCmd()
err := globalflags.Configure(cmd.Flags())
if err != nil {
t.Fatalf("configure global flags: %v", err)
}

for flag, value := range tt.flagValues {
err := cmd.Flags().Set(flag, value)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("setting flag --%s=%s: %v", flag, value, err)
}
}

err = cmd.ValidateArgs(tt.argValues)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error validating args: %v", err)
}

err = cmd.ValidateRequiredFlags()
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error validating flags: %v", err)
}

model, err := parseInput(cmd, tt.argValues)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error parsing input: %v", err)
}

if !tt.isValid {
t.Fatalf("did not fail on invalid input")
}
diff := cmp.Diff(model, tt.expectedModel)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}

func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest postgresflex.ApiDeleteInstanceRequest
}{
{
description: "base",
model: fixtureInputModel(),
expectedRequest: fixtureRequest(),
},
}

for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
request := buildRequest(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)
}
})
}
}
2 changes: 2 additions & 0 deletions internal/cmd/postgresflex/instance/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package instance

import (
"stackit/internal/cmd/postgresflex/instance/create"
"stackit/internal/cmd/postgresflex/instance/delete"
"stackit/internal/cmd/postgresflex/instance/describe"
"stackit/internal/cmd/postgresflex/instance/list"
"stackit/internal/cmd/postgresflex/instance/update"
Expand All @@ -28,4 +29,5 @@ func addSubcommands(cmd *cobra.Command) {
cmd.AddCommand(create.NewCmd())
cmd.AddCommand(describe.NewCmd())
cmd.AddCommand(update.NewCmd())
cmd.AddCommand(delete.NewCmd())
}