Skip to content

Commit bc7ae7e

Browse files
authored
Add stackit config profile list/delete commands (#359)
* better error handling, make argument option in set * address PR comments, improve testing for file utils copy file * check for command existance in set and get profile * initial implementation * get email * list command done * finish list, add delete * force profile names to be lowercase * adapt testing for lowercase profile names * generate docs * add testing to delete * update regex to not allow starting with hyphen * generate docs * add testing for new storage methods * address PR comments * address PR comments * remove unused test logic * delete profile removes keyring entries * address PR comments * add test for keyring failure
1 parent bd2f2c5 commit bc7ae7e

12 files changed

Lines changed: 1150 additions & 51 deletions

File tree

docs/stackit_config_profile.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ stackit config profile [flags]
3333

3434
* [stackit config](./stackit_config.md) - Provides functionality for CLI configuration options
3535
* [stackit config profile create](./stackit_config_profile_create.md) - Creates a CLI configuration profile
36+
* [stackit config profile delete](./stackit_config_profile_delete.md) - Delete a CLI configuration profile
37+
* [stackit config profile list](./stackit_config_profile_list.md) - Lists all CLI configuration profiles
3638
* [stackit config profile set](./stackit_config_profile_set.md) - Set a CLI configuration profile
3739
* [stackit config profile unset](./stackit_config_profile_unset.md) - Unset the current active CLI configuration profile
3840

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
## stackit config profile delete
2+
3+
Delete a CLI configuration profile
4+
5+
### Synopsis
6+
7+
Delete a CLI configuration profile.
8+
If the deleted profile is the active profile, the default profile will be set to active.
9+
10+
```
11+
stackit config profile delete PROFILE [flags]
12+
```
13+
14+
### Examples
15+
16+
```
17+
Delete the configuration profile "my-profile"
18+
$ stackit config profile delete my-profile
19+
```
20+
21+
### Options
22+
23+
```
24+
-h, --help Help for "stackit config profile delete"
25+
```
26+
27+
### Options inherited from parent commands
28+
29+
```
30+
-y, --assume-yes If set, skips all confirmation prompts
31+
--async If set, runs the command asynchronously
32+
-o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"]
33+
-p, --project-id string Project ID
34+
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
35+
```
36+
37+
### SEE ALSO
38+
39+
* [stackit config profile](./stackit_config_profile.md) - Manage the CLI configuration profiles
40+
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
## stackit config profile list
2+
3+
Lists all CLI configuration profiles
4+
5+
### Synopsis
6+
7+
Lists all CLI configuration profiles.
8+
9+
```
10+
stackit config profile list [flags]
11+
```
12+
13+
### Examples
14+
15+
```
16+
List the configuration profiles
17+
$ stackit config profile list
18+
19+
List the configuration profiles in a json format
20+
$ stackit config profile list --output-format json
21+
```
22+
23+
### Options
24+
25+
```
26+
-h, --help Help for "stackit config profile list"
27+
```
28+
29+
### Options inherited from parent commands
30+
31+
```
32+
-y, --assume-yes If set, skips all confirmation prompts
33+
--async If set, runs the command asynchronously
34+
-o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"]
35+
-p, --project-id string Project ID
36+
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
37+
```
38+
39+
### SEE ALSO
40+
41+
* [stackit config profile](./stackit_config_profile.md) - Manage the CLI configuration profiles
42+
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package delete
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
7+
"github.com/stackitcloud/stackit-cli/internal/pkg/auth"
8+
"github.com/stackitcloud/stackit-cli/internal/pkg/config"
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/print"
13+
14+
"github.com/spf13/cobra"
15+
)
16+
17+
const (
18+
profileArg = "PROFILE"
19+
)
20+
21+
type inputModel struct {
22+
*globalflags.GlobalFlagModel
23+
Profile string
24+
}
25+
26+
func NewCmd(p *print.Printer) *cobra.Command {
27+
cmd := &cobra.Command{
28+
Use: fmt.Sprintf("delete %s", profileArg),
29+
Short: "Delete a CLI configuration profile",
30+
Long: fmt.Sprintf("%s\n%s",
31+
"Delete a CLI configuration profile.",
32+
"If the deleted profile is the active profile, the default profile will be set to active.",
33+
),
34+
Args: args.SingleArg(profileArg, nil),
35+
Example: examples.Build(
36+
examples.NewExample(
37+
`Delete the configuration profile "my-profile"`,
38+
"$ stackit config profile delete my-profile"),
39+
),
40+
RunE: func(cmd *cobra.Command, args []string) error {
41+
model, err := parseInput(p, cmd, args)
42+
if err != nil {
43+
return err
44+
}
45+
46+
profileExists, err := config.ProfileExists(model.Profile)
47+
if err != nil {
48+
return fmt.Errorf("check if profile exists: %w", err)
49+
}
50+
if !profileExists {
51+
return &errors.DeleteInexistentProfile{Profile: model.Profile}
52+
}
53+
54+
if !model.AssumeYes {
55+
prompt := fmt.Sprintf("Are you sure you want to delete profile %q? (This cannot be undone)", model.Profile)
56+
err = p.PromptForConfirmation(prompt)
57+
if err != nil {
58+
return err
59+
}
60+
}
61+
62+
err = config.DeleteProfile(p, model.Profile)
63+
if err != nil {
64+
return fmt.Errorf("delete profile: %w", err)
65+
}
66+
67+
err = auth.DeleteProfileFromKeyring(model.Profile)
68+
if err != nil {
69+
return fmt.Errorf("delete profile from keyring: %w", err)
70+
}
71+
72+
p.Info("Successfully deleted profile %q\n", model.Profile)
73+
74+
return nil
75+
},
76+
}
77+
return cmd
78+
}
79+
80+
func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) {
81+
profile := inputArgs[0]
82+
83+
err := config.ValidateProfile(profile)
84+
if err != nil {
85+
return nil, err
86+
}
87+
88+
globalFlags := globalflags.Parse(p, cmd)
89+
90+
model := inputModel{
91+
GlobalFlagModel: globalFlags,
92+
Profile: profile,
93+
}
94+
95+
if p.IsVerbosityDebug() {
96+
modelStr, err := print.BuildDebugStrFromInputModel(model)
97+
if err != nil {
98+
p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err)
99+
} else {
100+
p.Debug(print.DebugLevel, "parsed input values: %s", modelStr)
101+
}
102+
}
103+
104+
return &model, nil
105+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package delete
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
7+
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
8+
9+
"github.com/google/go-cmp/cmp"
10+
)
11+
12+
const testProfile = "test-profile"
13+
14+
func fixtureArgValues(mods ...func(argValues []string)) []string {
15+
argValues := []string{
16+
testProfile,
17+
}
18+
for _, mod := range mods {
19+
mod(argValues)
20+
}
21+
return argValues
22+
}
23+
24+
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
25+
model := &inputModel{
26+
GlobalFlagModel: &globalflags.GlobalFlagModel{
27+
Verbosity: globalflags.VerbosityDefault,
28+
},
29+
Profile: testProfile,
30+
}
31+
for _, mod := range mods {
32+
mod(model)
33+
}
34+
return model
35+
}
36+
37+
func TestParseInput(t *testing.T) {
38+
tests := []struct {
39+
description string
40+
argValues []string
41+
flagValues map[string]string
42+
isValid bool
43+
expectedModel *inputModel
44+
}{
45+
{
46+
description: "base",
47+
argValues: fixtureArgValues(),
48+
isValid: true,
49+
expectedModel: fixtureInputModel(),
50+
},
51+
{
52+
description: "no values",
53+
argValues: []string{},
54+
flagValues: map[string]string{},
55+
isValid: false,
56+
},
57+
{
58+
description: "no arg values",
59+
argValues: []string{},
60+
isValid: false,
61+
},
62+
{
63+
description: "some global flag",
64+
argValues: fixtureArgValues(),
65+
flagValues: map[string]string{
66+
globalflags.VerbosityFlag: globalflags.DebugVerbosity,
67+
},
68+
isValid: true,
69+
expectedModel: fixtureInputModel(func(model *inputModel) {
70+
model.GlobalFlagModel.Verbosity = globalflags.DebugVerbosity
71+
}),
72+
},
73+
{
74+
description: "invalid profile",
75+
argValues: []string{"invalid-profile-&"},
76+
isValid: false,
77+
},
78+
}
79+
80+
for _, tt := range tests {
81+
t.Run(tt.description, func(t *testing.T) {
82+
p := print.NewPrinter()
83+
cmd := NewCmd(p)
84+
err := globalflags.Configure(cmd.Flags())
85+
if err != nil {
86+
t.Fatalf("configure global flags: %v", err)
87+
}
88+
89+
for flag, value := range tt.flagValues {
90+
err := cmd.Flags().Set(flag, value)
91+
if err != nil {
92+
if !tt.isValid {
93+
return
94+
}
95+
t.Fatalf("setting flag --%s=%s: %v", flag, value, err)
96+
}
97+
}
98+
99+
err = cmd.ValidateArgs(tt.argValues)
100+
if err != nil {
101+
if !tt.isValid {
102+
return
103+
}
104+
t.Fatalf("error validating args: %v", err)
105+
}
106+
107+
err = cmd.ValidateRequiredFlags()
108+
if err != nil {
109+
if !tt.isValid {
110+
return
111+
}
112+
t.Fatalf("error validating flags: %v", err)
113+
}
114+
115+
model, err := parseInput(p, cmd, tt.argValues)
116+
if err != nil {
117+
if !tt.isValid {
118+
return
119+
}
120+
t.Fatalf("error parsing input: %v", err)
121+
}
122+
123+
if !tt.isValid {
124+
t.Fatalf("did not fail on invalid input")
125+
}
126+
diff := cmp.Diff(model, tt.expectedModel)
127+
if diff != "" {
128+
t.Fatalf("Data does not match: %s", diff)
129+
}
130+
})
131+
}
132+
}

0 commit comments

Comments
 (0)