Skip to content

Commit cebf8dc

Browse files
authored
Implement stackit config profile commands (#312)
* Implement config profile set * Implement config profile unset * Extend config list to show active profile * Adjustments after review
1 parent 1856fd0 commit cebf8dc

File tree

12 files changed

+379
-20
lines changed

12 files changed

+379
-20
lines changed

internal/cmd/config/config.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55

66
"github.com/stackitcloud/stackit-cli/internal/cmd/config/list"
7+
"github.com/stackitcloud/stackit-cli/internal/cmd/config/profile"
78
"github.com/stackitcloud/stackit-cli/internal/cmd/config/set"
89
"github.com/stackitcloud/stackit-cli/internal/cmd/config/unset"
910
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
@@ -17,12 +18,12 @@ func NewCmd(p *print.Printer) *cobra.Command {
1718
cmd := &cobra.Command{
1819
Use: "config",
1920
Short: "Provides functionality for CLI configuration options",
20-
Long: fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", "Provides functionality for CLI configuration options",
21-
"The configuration is stored in a file in the user's config directory, which is OS dependent.",
22-
"Windows: %APPDATA%\\stackit",
23-
"Linux: $XDG_CONFIG_HOME/stackit",
24-
"macOS: $HOME/Library/Application Support/stackit",
25-
"The configuration file is named `cli-config.json` and is created automatically in your first CLI run.",
21+
Long: fmt.Sprintf("%s\n%s\n\n%s\n%s\n%s",
22+
"Provides functionality for CLI configuration options.",
23+
`You can set and unset different configuration options via the "stackit config set" and "stackit config unset" commands.`,
24+
"Additionally, you can configure the CLI to use different profiles, each with its own configuration.",
25+
`Additional profiles can be configured via the "STACKIT_CLI_PROFILE" environment variable or using the "stackit config profile set PROFILE" and "stackit config profile unset" commands.`,
26+
"The environment variable takes precedence over what is set via the commands.",
2627
),
2728
Args: args.NoArgs,
2829
Run: utils.CmdHelp,
@@ -35,4 +36,5 @@ func addSubcommands(cmd *cobra.Command, p *print.Printer) {
3536
cmd.AddCommand(list.NewCmd(p))
3637
cmd.AddCommand(set.NewCmd(p))
3738
cmd.AddCommand(unset.NewCmd(p))
39+
cmd.AddCommand(profile.NewCmd(p))
3840
}

internal/cmd/config/list/list.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,13 @@ func NewCmd(p *print.Printer) *cobra.Command {
5050
configData := viper.AllSettings()
5151

5252
model := parseInput(p, cmd)
53-
return outputResult(p, model.OutputFormat, configData)
53+
54+
activeProfile, err := config.GetProfile()
55+
if err != nil {
56+
return fmt.Errorf("get profile: %w", err)
57+
}
58+
59+
return outputResult(p, model.OutputFormat, configData, activeProfile)
5460
},
5561
}
5662
return cmd
@@ -64,16 +70,23 @@ func parseInput(p *print.Printer, cmd *cobra.Command) *inputModel {
6470
}
6571
}
6672

67-
func outputResult(p *print.Printer, outputFormat string, configData map[string]any) error {
73+
func outputResult(p *print.Printer, outputFormat string, configData map[string]any, activeProfile string) error {
6874
switch outputFormat {
6975
case print.JSONOutputFormat:
76+
if activeProfile != "" {
77+
configData["active_profile"] = activeProfile
78+
}
7079
details, err := json.MarshalIndent(configData, "", " ")
7180
if err != nil {
7281
return fmt.Errorf("marshal config list: %w", err)
7382
}
7483
p.Outputln(string(details))
7584
return nil
7685
default:
86+
if activeProfile != "" {
87+
p.Outputf("\n ACTIVE PROFILE: %s\n", activeProfile)
88+
}
89+
7790
// Sort the config options by key
7891
configKeys := make([]string, 0, len(configData))
7992
for k := range configData {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package profile
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/stackitcloud/stackit-cli/internal/cmd/config/profile/set"
7+
"github.com/stackitcloud/stackit-cli/internal/cmd/config/profile/unset"
8+
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
9+
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
10+
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
11+
12+
"github.com/spf13/cobra"
13+
)
14+
15+
func NewCmd(p *print.Printer) *cobra.Command {
16+
cmd := &cobra.Command{
17+
Use: "profile",
18+
Short: "Manage the CLI configuration profiles",
19+
Long: fmt.Sprintf("%s\n%s\n%s\n%s",
20+
"Manage the CLI configuration profiles.",
21+
`The profile to be used can be managed via the "STACKIT_CLI_PROFILE" environment variable or using the "stackit config profile set PROFILE" and "stackit config profile unset" commands.`,
22+
"The environment variable takes precedence over what is set via the commands.",
23+
"When no profile is set, the default profile is used.",
24+
),
25+
Args: args.NoArgs,
26+
Run: utils.CmdHelp,
27+
}
28+
addSubcommands(cmd, p)
29+
return cmd
30+
}
31+
32+
func addSubcommands(cmd *cobra.Command, p *print.Printer) {
33+
cmd.AddCommand(set.NewCmd(p))
34+
cmd.AddCommand(unset.NewCmd(p))
35+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package set
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
7+
"github.com/stackitcloud/stackit-cli/internal/pkg/config"
8+
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
9+
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
10+
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
11+
12+
"github.com/spf13/cobra"
13+
)
14+
15+
const (
16+
profileArg = "PROFILE"
17+
)
18+
19+
type inputModel struct {
20+
*globalflags.GlobalFlagModel
21+
Profile string
22+
}
23+
24+
func NewCmd(p *print.Printer) *cobra.Command {
25+
cmd := &cobra.Command{
26+
Use: fmt.Sprintf("set %s", profileArg),
27+
Short: "Set a CLI configuration profile",
28+
Long: fmt.Sprintf("%s\n%s\n%s\n%s",
29+
"Set a CLI configuration profile as the active profile.",
30+
`The profile to be used can be managed via the STACKIT_CLI_PROFILE environment variable or using the "stackit config profile set PROFILE" and "stackit config profile unset" commands.`,
31+
"The environment variable takes precedence over what is set via the commands.",
32+
"When no profile is set, the default profile is used.",
33+
),
34+
Args: args.SingleArg(profileArg, nil),
35+
Example: examples.Build(
36+
examples.NewExample(
37+
`Set the configuration profile "my-profile" as the active profile`,
38+
"$ stackit config profile set 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+
err = config.SetProfile(model.Profile)
47+
if err != nil {
48+
return fmt.Errorf("set profile: %w", err)
49+
}
50+
51+
p.Info("Profile %q set successfully as the active profile\n", model.Profile)
52+
return nil
53+
},
54+
}
55+
return cmd
56+
}
57+
58+
func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) {
59+
profile := inputArgs[0]
60+
61+
err := config.ValidateProfile(profile)
62+
if err != nil {
63+
return nil, err
64+
}
65+
66+
globalFlags := globalflags.Parse(p, cmd)
67+
68+
model := inputModel{
69+
GlobalFlagModel: globalFlags,
70+
Profile: profile,
71+
}
72+
73+
if p.IsVerbosityDebug() {
74+
modelStr, err := print.BuildDebugStrFromInputModel(model)
75+
if err != nil {
76+
p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err)
77+
} else {
78+
p.Debug(print.DebugLevel, "parsed input values: %s", modelStr)
79+
}
80+
}
81+
82+
return &model, nil
83+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package set
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+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package unset
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
7+
"github.com/stackitcloud/stackit-cli/internal/pkg/config"
8+
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
9+
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
10+
11+
"github.com/spf13/cobra"
12+
)
13+
14+
func NewCmd(p *print.Printer) *cobra.Command {
15+
cmd := &cobra.Command{
16+
Use: "unset",
17+
Short: "Unset the current active CLI configuration profile",
18+
Long: fmt.Sprintf("%s\n%s",
19+
"Unset the current active CLI configuration profile.",
20+
"When no profile is set, the default profile will be used.",
21+
),
22+
Args: args.NoArgs,
23+
Example: examples.Build(
24+
examples.NewExample(
25+
`Unset the currently active configuration profile. The default profile will be used.`,
26+
"$ stackit config profile unset"),
27+
),
28+
RunE: func(cmd *cobra.Command, args []string) error {
29+
err := config.UnsetProfile()
30+
if err != nil {
31+
return fmt.Errorf("unset profile: %w", err)
32+
}
33+
34+
p.Info("Profile unset successfully. The default profile will be used.\n")
35+
return nil
36+
},
37+
}
38+
return cmd
39+
}

0 commit comments

Comments
 (0)