Skip to content

Commit ff0bd22

Browse files
vicentepinto98stackit-pipelinerenovate-botGokceGKjoaopalet
authored
Load balancer delete command (#270)
* fix(deps): update module github.com/jedib0t/go-pretty/v6 to v6.5.8 (#257) Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com> * Postgresflex backup command improvements (#259) * change title to created at * add note about current backup schedule in long description * add backup schedule to the instance describe output * Update internal/cmd/postgresflex/backup/update-schedule/update_schedule.go Co-authored-by: João Palet <joao.palet@outlook.com> * update docs --------- Co-authored-by: João Palet <joao.palet@outlook.com> * Update changelog generation implementation (#263) * change recovery date format (#266) * Delete loadbalancer command * Update internal/cmd/load-balancer/delete/delete.go Co-authored-by: Diogo Ferrão <diogo.ferrao@freiheit.com> * Update internal/cmd/load-balancer/delete/delete_test.go Co-authored-by: Diogo Ferrão <diogo.ferrao@freiheit.com> * Changes after review --------- Co-authored-by: stackit-pipeline <142982727+stackit-pipeline@users.noreply.github.com> Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com> Co-authored-by: GokceGK <161626272+GokceGK@users.noreply.github.com> Co-authored-by: João Palet <joao.palet@outlook.com> Co-authored-by: Diogo Ferrão <diogo.ferrao@freiheit.com>
1 parent 46a3123 commit ff0bd22

3 files changed

Lines changed: 327 additions & 0 deletions

File tree

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package delete
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
8+
"github.com/stackitcloud/stackit-cli/internal/pkg/errors"
9+
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
10+
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
11+
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
12+
"github.com/stackitcloud/stackit-cli/internal/pkg/services/load-balancer/client"
13+
"github.com/stackitcloud/stackit-cli/internal/pkg/spinner"
14+
15+
"github.com/spf13/cobra"
16+
"github.com/stackitcloud/stackit-sdk-go/services/loadbalancer"
17+
"github.com/stackitcloud/stackit-sdk-go/services/loadbalancer/wait"
18+
)
19+
20+
const (
21+
loadBalancerNameArg = "LOAD_BALANCER_NAME"
22+
)
23+
24+
type inputModel struct {
25+
*globalflags.GlobalFlagModel
26+
LoadBalancerName string
27+
}
28+
29+
func NewCmd(p *print.Printer) *cobra.Command {
30+
cmd := &cobra.Command{
31+
Use: fmt.Sprintf("delete %s", loadBalancerNameArg),
32+
Short: "Deletes a Load Balancer",
33+
Long: "Deletes a Load Balancer.",
34+
Args: args.SingleArg(loadBalancerNameArg, nil),
35+
Example: examples.Build(
36+
examples.NewExample(
37+
`Deletes a load balancer with name "my-load-balancer"`,
38+
"$ stackit load-balancer delete my-load-balancer"),
39+
),
40+
RunE: func(cmd *cobra.Command, args []string) error {
41+
ctx := context.Background()
42+
model, err := parseInput(p, cmd, args)
43+
if err != nil {
44+
return err
45+
}
46+
// Configure API client
47+
apiClient, err := client.ConfigureClient(p)
48+
if err != nil {
49+
return err
50+
}
51+
52+
if !model.AssumeYes {
53+
prompt := fmt.Sprintf("Are you sure you want to delete load balancer %q? (This cannot be undone)", model.LoadBalancerName)
54+
err = p.PromptForConfirmation(prompt)
55+
if err != nil {
56+
return err
57+
}
58+
}
59+
60+
// Call API
61+
req := buildRequest(ctx, model, apiClient)
62+
_, err = req.Execute()
63+
if err != nil {
64+
return fmt.Errorf("delete load balancer: %w", err)
65+
}
66+
67+
// Wait for async operation, if async mode not enabled
68+
if !model.Async {
69+
s := spinner.New(p)
70+
s.Start("Deleting load balancer")
71+
_, err = wait.DeleteLoadBalancerWaitHandler(ctx, apiClient, model.ProjectId, model.LoadBalancerName).WaitWithContext(ctx)
72+
if err != nil {
73+
return fmt.Errorf("wait for load balancer deletion: %w", err)
74+
}
75+
s.Stop()
76+
}
77+
78+
operationState := "Deleted"
79+
if model.Async {
80+
operationState = "Triggered deletion of"
81+
}
82+
p.Info("%s load balancer %q\n", operationState, model.LoadBalancerName)
83+
return nil
84+
},
85+
}
86+
return cmd
87+
}
88+
89+
func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) {
90+
loadBalancerName := inputArgs[0]
91+
92+
globalFlags := globalflags.Parse(p, cmd)
93+
if globalFlags.ProjectId == "" {
94+
return nil, &errors.ProjectIdError{}
95+
}
96+
97+
model := inputModel{
98+
GlobalFlagModel: globalFlags,
99+
LoadBalancerName: loadBalancerName,
100+
}
101+
102+
if p.IsVerbosityDebug() {
103+
modelStr, err := print.BuildDebugStrFromInputModel(model)
104+
if err != nil {
105+
p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err)
106+
} else {
107+
p.Debug(print.DebugLevel, "parsed input values: %s", modelStr)
108+
}
109+
}
110+
111+
return &model, nil
112+
}
113+
114+
func buildRequest(ctx context.Context, model *inputModel, apiClient *loadbalancer.APIClient) loadbalancer.ApiDeleteLoadBalancerRequest {
115+
req := apiClient.DeleteLoadBalancer(ctx, model.ProjectId, model.LoadBalancerName)
116+
return req
117+
}
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
package delete
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
8+
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
9+
10+
"github.com/google/go-cmp/cmp"
11+
"github.com/google/go-cmp/cmp/cmpopts"
12+
"github.com/google/uuid"
13+
"github.com/stackitcloud/stackit-sdk-go/services/loadbalancer"
14+
)
15+
16+
var projectIdFlag = globalflags.ProjectIdFlag
17+
18+
type testCtxKey struct{}
19+
20+
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
21+
var testClient = &loadbalancer.APIClient{}
22+
var testProjectId = uuid.NewString()
23+
var testLoadBalancerName = "loadBalancer"
24+
25+
func fixtureArgValues(mods ...func(argValues []string)) []string {
26+
argValues := []string{
27+
testLoadBalancerName,
28+
}
29+
for _, mod := range mods {
30+
mod(argValues)
31+
}
32+
return argValues
33+
}
34+
35+
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
36+
flagValues := map[string]string{
37+
projectIdFlag: testProjectId,
38+
}
39+
for _, mod := range mods {
40+
mod(flagValues)
41+
}
42+
return flagValues
43+
}
44+
45+
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
46+
model := &inputModel{
47+
GlobalFlagModel: &globalflags.GlobalFlagModel{
48+
ProjectId: testProjectId,
49+
Verbosity: globalflags.VerbosityDefault,
50+
},
51+
LoadBalancerName: testLoadBalancerName,
52+
}
53+
for _, mod := range mods {
54+
mod(model)
55+
}
56+
return model
57+
}
58+
59+
func fixtureRequest(mods ...func(request *loadbalancer.ApiDeleteLoadBalancerRequest)) loadbalancer.ApiDeleteLoadBalancerRequest {
60+
request := testClient.DeleteLoadBalancer(testCtx, testProjectId, testLoadBalancerName)
61+
for _, mod := range mods {
62+
mod(&request)
63+
}
64+
return request
65+
}
66+
67+
func TestParseInput(t *testing.T) {
68+
tests := []struct {
69+
description string
70+
argValues []string
71+
flagValues map[string]string
72+
isValid bool
73+
expectedModel *inputModel
74+
}{
75+
{
76+
description: "base",
77+
argValues: fixtureArgValues(),
78+
flagValues: fixtureFlagValues(),
79+
isValid: true,
80+
expectedModel: fixtureInputModel(),
81+
},
82+
{
83+
description: "no values",
84+
argValues: []string{},
85+
flagValues: map[string]string{},
86+
isValid: false,
87+
},
88+
{
89+
description: "no arg values",
90+
argValues: []string{},
91+
flagValues: fixtureFlagValues(),
92+
isValid: false,
93+
},
94+
{
95+
description: "no flag values",
96+
argValues: fixtureArgValues(),
97+
flagValues: map[string]string{},
98+
isValid: false,
99+
},
100+
{
101+
description: "project id missing",
102+
argValues: fixtureArgValues(),
103+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
104+
delete(flagValues, projectIdFlag)
105+
}),
106+
isValid: false,
107+
},
108+
{
109+
description: "project id invalid 1",
110+
argValues: fixtureArgValues(),
111+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
112+
flagValues[projectIdFlag] = ""
113+
}),
114+
isValid: false,
115+
},
116+
{
117+
description: "project id invalid 2",
118+
argValues: fixtureArgValues(),
119+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
120+
flagValues[projectIdFlag] = "invalid-uuid"
121+
}),
122+
isValid: false,
123+
},
124+
}
125+
126+
for _, tt := range tests {
127+
t.Run(tt.description, func(t *testing.T) {
128+
p := print.NewPrinter()
129+
cmd := NewCmd(p)
130+
err := globalflags.Configure(cmd.Flags())
131+
if err != nil {
132+
t.Fatalf("configure global flags: %v", err)
133+
}
134+
135+
for flag, value := range tt.flagValues {
136+
err := cmd.Flags().Set(flag, value)
137+
if err != nil {
138+
if !tt.isValid {
139+
return
140+
}
141+
t.Fatalf("setting flag --%s=%s: %v", flag, value, err)
142+
}
143+
}
144+
145+
err = cmd.ValidateArgs(tt.argValues)
146+
if err != nil {
147+
if !tt.isValid {
148+
return
149+
}
150+
t.Fatalf("error validating args: %v", err)
151+
}
152+
153+
err = cmd.ValidateRequiredFlags()
154+
if err != nil {
155+
if !tt.isValid {
156+
return
157+
}
158+
t.Fatalf("error validating flags: %v", err)
159+
}
160+
161+
model, err := parseInput(p, cmd, tt.argValues)
162+
if err != nil {
163+
if !tt.isValid {
164+
return
165+
}
166+
t.Fatalf("error parsing flags: %v", err)
167+
}
168+
169+
if !tt.isValid {
170+
t.Fatalf("did not fail on invalid input")
171+
}
172+
diff := cmp.Diff(model, tt.expectedModel)
173+
if diff != "" {
174+
t.Fatalf("Data does not match: %s", diff)
175+
}
176+
})
177+
}
178+
}
179+
180+
func TestBuildRequest(t *testing.T) {
181+
tests := []struct {
182+
description string
183+
model *inputModel
184+
isValid bool
185+
expectedRequest loadbalancer.ApiDeleteLoadBalancerRequest
186+
}{
187+
{
188+
description: "base",
189+
model: fixtureInputModel(),
190+
isValid: true,
191+
expectedRequest: fixtureRequest(),
192+
},
193+
}
194+
195+
for _, tt := range tests {
196+
t.Run(tt.description, func(t *testing.T) {
197+
request := buildRequest(testCtx, tt.model, testClient)
198+
199+
diff := cmp.Diff(request, tt.expectedRequest,
200+
cmp.AllowUnexported(tt.expectedRequest),
201+
cmpopts.EquateComparable(testCtx),
202+
)
203+
if diff != "" {
204+
t.Fatalf("Data does not match: %s", diff)
205+
}
206+
})
207+
}
208+
}

internal/cmd/load-balancer/load_balancer.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package loadbalancer
22

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

2930
func addSubcommands(cmd *cobra.Command, p *print.Printer) {
3031
cmd.AddCommand(describe.NewCmd(p))
32+
cmd.AddCommand(delete.NewCmd(p))
3133
cmd.AddCommand(list.NewCmd(p))
3234
cmd.AddCommand(quota.NewCmd(p))
3335
cmd.AddCommand(generatepayload.NewCmd(p))

0 commit comments

Comments
 (0)