Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement stackit ske cluster delete #10

Merged
merged 6 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
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/list"

"github.com/spf13/cobra"
Expand All @@ -15,4 +16,5 @@ var Cmd = &cobra.Command{

func init() {
Cmd.AddCommand(list.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 = "cluster-name"
hcsa73 marked this conversation as resolved.
Show resolved Hide resolved
)

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 --cluster-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("read SKE cluster: %w", err)
hcsa73 marked this conversation as resolved.
Show resolved Hide resolved
}

// 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().String(clusterNameFlag, "", "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)
}
})
}
}