Skip to content

Commit 3623ada

Browse files
authored
Onboard Argus (instance): delete command (#151)
* Onboard Argus Instance: delete command * delete command: add docs
1 parent 39b02c1 commit 3623ada

5 files changed

Lines changed: 370 additions & 0 deletions

File tree

docs/stackit_argus_instance.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,6 @@ stackit argus instance [flags]
2929

3030
* [stackit argus](./stackit_argus.md) - Provides functionality for Argus
3131
* [stackit argus instance create](./stackit_argus_instance_create.md) - Creates an Argus instance
32+
* [stackit argus instance delete](./stackit_argus_instance_delete.md) - Deletes an Argus instance
3233
* [stackit argus instance update](./stackit_argus_instance_update.md) - Updates an Argus instance
3334

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
## stackit argus instance delete
2+
3+
Deletes an Argus instance
4+
5+
### Synopsis
6+
7+
Deletes an Argus instance.
8+
9+
```
10+
stackit argus instance delete INSTANCE_ID [flags]
11+
```
12+
13+
### Examples
14+
15+
```
16+
Delete an Argus instance with ID "xxx"
17+
$ stackit argus instance delete xxx
18+
```
19+
20+
### Options
21+
22+
```
23+
-h, --help Help for "stackit argus instance delete"
24+
```
25+
26+
### Options inherited from parent commands
27+
28+
```
29+
-y, --assume-yes If set, skips all confirmation prompts
30+
--async If set, runs the command asynchronously
31+
-o, --output-format string Output format, one of ["json" "pretty"]
32+
-p, --project-id string Project ID
33+
```
34+
35+
### SEE ALSO
36+
37+
* [stackit argus instance](./stackit_argus_instance.md) - Provides functionality for Argus instances
38+
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
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/confirm"
9+
"github.com/stackitcloud/stackit-cli/internal/pkg/errors"
10+
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
11+
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
12+
"github.com/stackitcloud/stackit-cli/internal/pkg/services/argus/client"
13+
argusUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/argus/utils"
14+
"github.com/stackitcloud/stackit-cli/internal/pkg/spinner"
15+
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
16+
17+
"github.com/spf13/cobra"
18+
"github.com/stackitcloud/stackit-sdk-go/services/argus"
19+
"github.com/stackitcloud/stackit-sdk-go/services/argus/wait"
20+
)
21+
22+
const (
23+
instanceIdArg = "INSTANCE_ID"
24+
)
25+
26+
type inputModel struct {
27+
*globalflags.GlobalFlagModel
28+
InstanceId string
29+
}
30+
31+
func NewCmd() *cobra.Command {
32+
cmd := &cobra.Command{
33+
Use: fmt.Sprintf("delete %s", instanceIdArg),
34+
Short: "Deletes an Argus instance",
35+
Long: "Deletes an Argus instance.",
36+
Args: args.SingleArg(instanceIdArg, utils.ValidateUUID),
37+
Example: examples.Build(
38+
examples.NewExample(
39+
`Delete an Argus instance with ID "xxx"`,
40+
"$ stackit argus instance delete xxx"),
41+
),
42+
RunE: func(cmd *cobra.Command, args []string) error {
43+
ctx := context.Background()
44+
model, err := parseInput(cmd, args)
45+
if err != nil {
46+
return err
47+
}
48+
49+
// Configure API client
50+
apiClient, err := client.ConfigureClient(cmd)
51+
if err != nil {
52+
return err
53+
}
54+
55+
instanceLabel, err := argusUtils.GetInstanceName(ctx, apiClient, model.InstanceId, model.ProjectId)
56+
if err != nil {
57+
instanceLabel = model.InstanceId
58+
}
59+
60+
if !model.AssumeYes {
61+
prompt := fmt.Sprintf("Are you sure you want to delete instance %q? (This cannot be undone)", instanceLabel)
62+
err = confirm.PromptForConfirmation(cmd, prompt)
63+
if err != nil {
64+
return err
65+
}
66+
}
67+
68+
// Call API
69+
req := buildRequest(ctx, model, apiClient)
70+
_, err = req.Execute()
71+
if err != nil {
72+
return fmt.Errorf("delete Argus instance: %w", err)
73+
}
74+
75+
// Wait for async operation, if async mode not enabled
76+
if !model.Async {
77+
s := spinner.New(cmd)
78+
s.Start("Deleting instance")
79+
_, err = wait.DeleteInstanceWaitHandler(ctx, apiClient, model.InstanceId, model.ProjectId).WaitWithContext(ctx)
80+
if err != nil {
81+
return fmt.Errorf("wait for Argus instance deletion: %w", err)
82+
}
83+
s.Stop()
84+
}
85+
86+
operationState := "Deleted"
87+
if model.Async {
88+
operationState = "Triggered deletion of"
89+
}
90+
cmd.Printf("%s instance %q\n", operationState, instanceLabel)
91+
return nil
92+
},
93+
}
94+
return cmd
95+
}
96+
97+
func parseInput(cmd *cobra.Command, inputArgs []string) (*inputModel, error) {
98+
instanceId := inputArgs[0]
99+
100+
globalFlags := globalflags.Parse(cmd)
101+
if globalFlags.ProjectId == "" {
102+
return nil, &errors.ProjectIdError{}
103+
}
104+
105+
return &inputModel{
106+
GlobalFlagModel: globalFlags,
107+
InstanceId: instanceId,
108+
}, nil
109+
}
110+
111+
func buildRequest(ctx context.Context, model *inputModel, apiClient *argus.APIClient) argus.ApiDeleteInstanceRequest {
112+
req := apiClient.DeleteInstance(ctx, model.InstanceId, model.ProjectId)
113+
return req
114+
}
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
package delete
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
8+
9+
"github.com/google/go-cmp/cmp"
10+
"github.com/google/go-cmp/cmp/cmpopts"
11+
"github.com/google/uuid"
12+
"github.com/stackitcloud/stackit-sdk-go/services/argus"
13+
)
14+
15+
var projectIdFlag = globalflags.ProjectIdFlag
16+
17+
type testCtxKey struct{}
18+
19+
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
20+
var testClient = &argus.APIClient{}
21+
var testProjectId = uuid.NewString()
22+
var testInstanceId = uuid.NewString()
23+
24+
func fixtureArgValues(mods ...func(argValues []string)) []string {
25+
argValues := []string{
26+
testInstanceId,
27+
}
28+
for _, mod := range mods {
29+
mod(argValues)
30+
}
31+
return argValues
32+
}
33+
34+
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
35+
flagValues := map[string]string{
36+
projectIdFlag: testProjectId,
37+
}
38+
for _, mod := range mods {
39+
mod(flagValues)
40+
}
41+
return flagValues
42+
}
43+
44+
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
45+
model := &inputModel{
46+
GlobalFlagModel: &globalflags.GlobalFlagModel{
47+
ProjectId: testProjectId,
48+
},
49+
InstanceId: testInstanceId,
50+
}
51+
for _, mod := range mods {
52+
mod(model)
53+
}
54+
return model
55+
}
56+
57+
func fixtureRequest(mods ...func(request *argus.ApiDeleteInstanceRequest)) argus.ApiDeleteInstanceRequest {
58+
request := testClient.DeleteInstance(testCtx, testInstanceId, testProjectId)
59+
for _, mod := range mods {
60+
mod(&request)
61+
}
62+
return request
63+
}
64+
65+
func TestParseInput(t *testing.T) {
66+
tests := []struct {
67+
description string
68+
argValues []string
69+
flagValues map[string]string
70+
isValid bool
71+
expectedModel *inputModel
72+
}{
73+
{
74+
description: "base",
75+
argValues: fixtureArgValues(),
76+
flagValues: fixtureFlagValues(),
77+
isValid: true,
78+
expectedModel: fixtureInputModel(),
79+
},
80+
{
81+
description: "no values",
82+
argValues: []string{},
83+
flagValues: map[string]string{},
84+
isValid: false,
85+
},
86+
{
87+
description: "no arg values",
88+
argValues: []string{},
89+
flagValues: fixtureFlagValues(),
90+
isValid: false,
91+
},
92+
{
93+
description: "no flag values",
94+
argValues: fixtureArgValues(),
95+
flagValues: map[string]string{},
96+
isValid: false,
97+
},
98+
{
99+
description: "project id missing",
100+
argValues: fixtureArgValues(),
101+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
102+
delete(flagValues, projectIdFlag)
103+
}),
104+
isValid: false,
105+
},
106+
{
107+
description: "project id invalid 1",
108+
argValues: fixtureArgValues(),
109+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
110+
flagValues[projectIdFlag] = ""
111+
}),
112+
isValid: false,
113+
},
114+
{
115+
description: "project id invalid 2",
116+
argValues: fixtureArgValues(),
117+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
118+
flagValues[projectIdFlag] = "invalid-uuid"
119+
}),
120+
isValid: false,
121+
},
122+
{
123+
description: "instance id invalid 1",
124+
argValues: []string{""},
125+
flagValues: fixtureFlagValues(),
126+
isValid: false,
127+
},
128+
{
129+
description: "instance id invalid 2",
130+
argValues: []string{"invalid-uuid"},
131+
flagValues: fixtureFlagValues(),
132+
isValid: false,
133+
},
134+
}
135+
136+
for _, tt := range tests {
137+
t.Run(tt.description, func(t *testing.T) {
138+
cmd := NewCmd()
139+
err := globalflags.Configure(cmd.Flags())
140+
if err != nil {
141+
t.Fatalf("configure global flags: %v", err)
142+
}
143+
144+
for flag, value := range tt.flagValues {
145+
err := cmd.Flags().Set(flag, value)
146+
if err != nil {
147+
if !tt.isValid {
148+
return
149+
}
150+
t.Fatalf("setting flag --%s=%s: %v", flag, value, err)
151+
}
152+
}
153+
154+
err = cmd.ValidateArgs(tt.argValues)
155+
if err != nil {
156+
if !tt.isValid {
157+
return
158+
}
159+
t.Fatalf("error validating args: %v", err)
160+
}
161+
162+
err = cmd.ValidateRequiredFlags()
163+
if err != nil {
164+
if !tt.isValid {
165+
return
166+
}
167+
t.Fatalf("error validating flags: %v", err)
168+
}
169+
170+
model, err := parseInput(cmd, tt.argValues)
171+
if err != nil {
172+
if !tt.isValid {
173+
return
174+
}
175+
t.Fatalf("error parsing input: %v", err)
176+
}
177+
178+
if !tt.isValid {
179+
t.Fatalf("did not fail on invalid input")
180+
}
181+
diff := cmp.Diff(model, tt.expectedModel)
182+
if diff != "" {
183+
t.Fatalf("Data does not match: %s", diff)
184+
}
185+
})
186+
}
187+
}
188+
189+
func TestBuildRequest(t *testing.T) {
190+
tests := []struct {
191+
description string
192+
model *inputModel
193+
expectedRequest argus.ApiDeleteInstanceRequest
194+
}{
195+
{
196+
description: "base",
197+
model: fixtureInputModel(),
198+
expectedRequest: fixtureRequest(),
199+
},
200+
}
201+
202+
for _, tt := range tests {
203+
t.Run(tt.description, func(t *testing.T) {
204+
request := buildRequest(testCtx, tt.model, testClient)
205+
206+
diff := cmp.Diff(request, tt.expectedRequest,
207+
cmp.AllowUnexported(tt.expectedRequest),
208+
cmpopts.EquateComparable(testCtx),
209+
)
210+
if diff != "" {
211+
t.Fatalf("Data does not match: %s", diff)
212+
}
213+
})
214+
}
215+
}

internal/cmd/argus/instance/instance.go

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

33
import (
44
"github.com/stackitcloud/stackit-cli/internal/cmd/argus/instance/create"
5+
"github.com/stackitcloud/stackit-cli/internal/cmd/argus/instance/delete"
56
"github.com/stackitcloud/stackit-cli/internal/cmd/argus/instance/update"
67
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
78
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
@@ -24,4 +25,5 @@ func NewCmd() *cobra.Command {
2425
func addSubcommands(cmd *cobra.Command) {
2526
cmd.AddCommand(create.NewCmd())
2627
cmd.AddCommand(update.NewCmd())
28+
cmd.AddCommand(delete.NewCmd())
2729
}

0 commit comments

Comments
 (0)