Skip to content

Commit

Permalink
Merge pull request #10 from stackitcloud/hs/ske-cluster-delete
Browse files Browse the repository at this point in the history
Implement `stackit ske cluster delete`
  • Loading branch information
hcsa73 authored Nov 21, 2023
2 parents cb1c77f + 7ebdbd4 commit 9a9ed6c
Show file tree
Hide file tree
Showing 3 changed files with 279 additions and 0 deletions.
2 changes: 2 additions & 0 deletions internal/cmd/ske/cluster/cluster.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cluster

import (
"github.com/stackitcloud/stackit-cli/internal/cmd/ske/cluster/delete"
"github.com/stackitcloud/stackit-cli/internal/cmd/ske/cluster/describe"
"github.com/stackitcloud/stackit-cli/internal/cmd/ske/cluster/list"

Expand All @@ -17,4 +18,5 @@ var Cmd = &cobra.Command{
func init() {
Cmd.AddCommand(list.Cmd)
Cmd.AddCommand(describe.Cmd)
Cmd.AddCommand(delete.Cmd)
}
92 changes: 92 additions & 0 deletions internal/cmd/ske/cluster/delete/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package delete

import (
"context"
"fmt"

"github.com/stackitcloud/stackit-cli/internal/pkg/config"
"github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"

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

const (
projectIdFlag = "project-id"
clusterNameFlag = "name"
)

type flagModel struct {
ProjectId string
ClusterName string
}

var Cmd = &cobra.Command{
Use: "delete",
Short: "Delete a SKE cluster",
Long: "Delete a SKE cluster",
Example: `$ stackit ske cluster delete --project-id xxx --name xxx`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseFlags(cmd)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(cmd)
if err != nil {
return fmt.Errorf("authentication failed, please run \"stackit auth login\" or \"stackit auth activate-service-account\"")
}

// Call API
req := buildRequest(ctx, model, apiClient)
_, err = req.Execute()
if err != nil {
return fmt.Errorf("delete SKE cluster: %w", err)
}

// Wait for async operation
_, err = wait.DeleteClusterWaitHandler(ctx, apiClient, model.ProjectId, model.ClusterName).WaitWithContext(ctx)
if err != nil {
return fmt.Errorf("wait for SKE cluster deletion: %w", err)
}

fmt.Println("Cluster deleted")
return nil
},
}

func init() {
configureFlags(Cmd)
}

func configureFlags(cmd *cobra.Command) {
cmd.Flags().StringP(clusterNameFlag, "n", "", "Cluster name")

err := utils.MarkFlagsRequired(cmd, clusterNameFlag)
cobra.CheckErr(err)
}

func parseFlags(cmd *cobra.Command) (*flagModel, error) {
projectId := viper.GetString(config.ProjectIdKey)
if projectId == "" {
return nil, fmt.Errorf("project ID not set")
}
clusterName := utils.FlagToStringValue(cmd, clusterNameFlag)
if clusterName == "" {
return nil, fmt.Errorf("cluster name can't be empty")
}

return &flagModel{
ProjectId: projectId,
ClusterName: clusterName,
}, nil
}

func buildRequest(ctx context.Context, model *flagModel, apiClient *ske.APIClient) ske.ApiDeleteClusterRequest {
req := apiClient.DeleteCluster(ctx, model.ProjectId, model.ClusterName)
return req
}
185 changes: 185 additions & 0 deletions internal/cmd/ske/cluster/delete/delete_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package delete

import (
"context"
"testing"

"github.com/stackitcloud/stackit-cli/internal/pkg/config"
"github.com/stackitcloud/stackit-cli/internal/pkg/testutils"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-sdk-go/services/ske"
)

type testCtxKey struct{}

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

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

func fixtureFlagModel(mods ...func(model *flagModel)) *flagModel {
model := &flagModel{
ProjectId: testProjectId,
ClusterName: "cluster",
}
for _, mod := range mods {
mod(model)
}
return model
}

func fixtureRequest(mods ...func(request *ske.ApiDeleteClusterRequest)) ske.ApiDeleteClusterRequest {
request := testClient.DeleteCluster(testCtx, testProjectId, "cluster")
for _, mod := range mods {
mod(&request)
}
return request
}

func TestParseFlags(t *testing.T) {
tests := []struct {
description string
flagValues map[string]string
isValid bool
expectedModel *flagModel
}{
{
description: "base",
flagValues: fixtureFlagValues(),
isValid: true,
expectedModel: fixtureFlagModel(),
},
{
description: "no values",
flagValues: map[string]string{},
isValid: false,
},
{
description: "project id missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, projectIdFlag)
}),
isValid: false,
},
{
description: "project id invalid 1",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = ""
}),
isValid: false,
},
{
description: "project id invalid 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = "invalid-uuid"
}),
isValid: false,
},
{
description: "cluster name missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, clusterNameFlag)
}),
isValid: false,
},
{
description: "cluster name invalid",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[clusterNameFlag] = ""
}),
isValid: false,
},
}

for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
cmd := &cobra.Command{}

// Flag defined in root command
err := testutils.ConfigureBindUUIDFlag(cmd, projectIdFlag, config.ProjectIdKey)
if err != nil {
t.Fatalf("configure global flag --%s: %v", projectIdFlag, err)
}

configureFlags(cmd)

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.ValidateRequiredFlags()
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error validating flags: %v", err)
}

model, err := parseFlags(cmd)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error parsing flags: %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 *flagModel
isValid bool
expectedRequest ske.ApiDeleteClusterRequest
}{
{
description: "base",
model: fixtureFlagModel(),
isValid: true,
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)
}
})
}
}

0 comments on commit 9a9ed6c

Please sign in to comment.