Skip to content
117 changes: 117 additions & 0 deletions internal/cmd/load-balancer/delete/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package delete

import (
"context"
"fmt"

"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/errors"
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/services/load-balancer/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/spinner"

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

const (
loadBalancerNameArg = "LOAD_BALANCER_NAME"
)

type inputModel struct {
*globalflags.GlobalFlagModel
LoadBalancerName string
}

func NewCmd(p *print.Printer) *cobra.Command {
cmd := &cobra.Command{
Use: fmt.Sprintf("delete %s", loadBalancerNameArg),
Short: "Deletes a Load Balancer",
Long: "Deletes a Load Balancer.",
Args: args.SingleArg(loadBalancerNameArg, nil),
Example: examples.Build(
examples.NewExample(
`Deletes a load balancer with name "my-load-balancer"`,
"$ stackit load-balancer delete my-load-balancer"),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(p, cmd, args)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(p)
if err != nil {
return err
}

if !model.AssumeYes {
prompt := fmt.Sprintf("Are you sure you want to delete load balancer %q? (This cannot be undone)", model.LoadBalancerName)
err = p.PromptForConfirmation(prompt)
if err != nil {
return err
}
}

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

// Wait for async operation, if async mode not enabled
if !model.Async {
s := spinner.New(p)
s.Start("Deleting load balancer")
_, err = wait.DeleteLoadBalancerWaitHandler(ctx, apiClient, model.ProjectId, model.LoadBalancerName).WaitWithContext(ctx)
if err != nil {
return fmt.Errorf("wait for load balancer deletion: %w", err)
}
s.Stop()
}

operationState := "Deleted"
if model.Async {
operationState = "Triggered deletion of"
}
p.Info("%s load balancer %q\n", operationState, model.LoadBalancerName)
return nil
},
}
return cmd
}

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

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

model := inputModel{
GlobalFlagModel: globalFlags,
LoadBalancerName: loadBalancerName,
}

if p.IsVerbosityDebug() {
modelStr, err := print.BuildDebugStrFromInputModel(model)
if err != nil {
p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err)
} else {
p.Debug(print.DebugLevel, "parsed input values: %s", modelStr)
}
}

return &model, nil
}

func buildRequest(ctx context.Context, model *inputModel, apiClient *loadbalancer.APIClient) loadbalancer.ApiDeleteLoadBalancerRequest {
req := apiClient.DeleteLoadBalancer(ctx, model.ProjectId, model.LoadBalancerName)
return req
}
208 changes: 208 additions & 0 deletions internal/cmd/load-balancer/delete/delete_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
package delete

import (
"context"
"testing"

"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"

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

var projectIdFlag = globalflags.ProjectIdFlag

type testCtxKey struct{}

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

func fixtureArgValues(mods ...func(argValues []string)) []string {
argValues := []string{
testLoadBalancerName,
}
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,
Verbosity: globalflags.VerbosityDefault,
},
LoadBalancerName: testLoadBalancerName,
}
for _, mod := range mods {
mod(model)
}
return model
}

func fixtureRequest(mods ...func(request *loadbalancer.ApiDeleteLoadBalancerRequest)) loadbalancer.ApiDeleteLoadBalancerRequest {
request := testClient.DeleteLoadBalancer(testCtx, testProjectId, testLoadBalancerName)
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,
},
}

for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
p := print.NewPrinter()
cmd := NewCmd(p)
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(p, cmd, tt.argValues)
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 *inputModel
isValid bool
expectedRequest loadbalancer.ApiDeleteLoadBalancerRequest
}{
{
description: "base",
model: fixtureInputModel(),
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)
}
})
}
}
2 changes: 2 additions & 0 deletions internal/cmd/load-balancer/load_balancer.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package loadbalancer

import (
"github.com/stackitcloud/stackit-cli/internal/cmd/load-balancer/delete"
"github.com/stackitcloud/stackit-cli/internal/cmd/load-balancer/describe"
generatepayload "github.com/stackitcloud/stackit-cli/internal/cmd/load-balancer/generate-payload"
"github.com/stackitcloud/stackit-cli/internal/cmd/load-balancer/list"
Expand Down Expand Up @@ -28,6 +29,7 @@ func NewCmd(p *print.Printer) *cobra.Command {

func addSubcommands(cmd *cobra.Command, p *print.Printer) {
cmd.AddCommand(describe.NewCmd(p))
cmd.AddCommand(delete.NewCmd(p))
cmd.AddCommand(list.NewCmd(p))
cmd.AddCommand(quota.NewCmd(p))
cmd.AddCommand(generatepayload.NewCmd(p))
Expand Down