Skip to content

Implement stackit config profile commands #312

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions internal/cmd/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"

"github.com/stackitcloud/stackit-cli/internal/cmd/config/list"
"github.com/stackitcloud/stackit-cli/internal/cmd/config/profile"
"github.com/stackitcloud/stackit-cli/internal/cmd/config/set"
"github.com/stackitcloud/stackit-cli/internal/cmd/config/unset"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
Expand All @@ -17,12 +18,12 @@ func NewCmd(p *print.Printer) *cobra.Command {
cmd := &cobra.Command{
Use: "config",
Short: "Provides functionality for CLI configuration options",
Long: fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", "Provides functionality for CLI configuration options",
"The configuration is stored in a file in the user's config directory, which is OS dependent.",
"Windows: %APPDATA%\\stackit",
"Linux: $XDG_CONFIG_HOME/stackit",
"macOS: $HOME/Library/Application Support/stackit",
"The configuration file is named `cli-config.json` and is created automatically in your first CLI run.",
Long: fmt.Sprintf("%s\n%s\n\n%s\n%s\n%s",
"Provides functionality for CLI configuration options.",
`You can set and unset different configuration options via the "stackit config set" and "stackit config unset" commands.`,
"Additionally, you can configure the CLI to use different profiles, each with its own configuration.",
`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.`,
"The environment variable takes precedence over what is set via the commands.",
),
Args: args.NoArgs,
Run: utils.CmdHelp,
Expand All @@ -35,4 +36,5 @@ func addSubcommands(cmd *cobra.Command, p *print.Printer) {
cmd.AddCommand(list.NewCmd(p))
cmd.AddCommand(set.NewCmd(p))
cmd.AddCommand(unset.NewCmd(p))
cmd.AddCommand(profile.NewCmd(p))
}
17 changes: 15 additions & 2 deletions internal/cmd/config/list/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,13 @@ func NewCmd(p *print.Printer) *cobra.Command {
configData := viper.AllSettings()

model := parseInput(p, cmd)
return outputResult(p, model.OutputFormat, configData)

activeProfile, err := config.GetProfile()
if err != nil {
return fmt.Errorf("get profile: %w", err)
}

return outputResult(p, model.OutputFormat, configData, activeProfile)
},
}
return cmd
Expand All @@ -64,16 +70,23 @@ func parseInput(p *print.Printer, cmd *cobra.Command) *inputModel {
}
}

func outputResult(p *print.Printer, outputFormat string, configData map[string]any) error {
func outputResult(p *print.Printer, outputFormat string, configData map[string]any, activeProfile string) error {
switch outputFormat {
case print.JSONOutputFormat:
if activeProfile != "" {
configData["active_profile"] = activeProfile
}
details, err := json.MarshalIndent(configData, "", " ")
if err != nil {
return fmt.Errorf("marshal config list: %w", err)
}
p.Outputln(string(details))
return nil
default:
if activeProfile != "" {
p.Outputf("\n ACTIVE PROFILE: %s\n", activeProfile)
}

// Sort the config options by key
configKeys := make([]string, 0, len(configData))
for k := range configData {
Expand Down
35 changes: 35 additions & 0 deletions internal/cmd/config/profile/profile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package profile

import (
"fmt"

"github.com/stackitcloud/stackit-cli/internal/cmd/config/profile/set"
"github.com/stackitcloud/stackit-cli/internal/cmd/config/profile/unset"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"

"github.com/spf13/cobra"
)

func NewCmd(p *print.Printer) *cobra.Command {
cmd := &cobra.Command{
Use: "profile",
Short: "Manage the CLI configuration profiles",
Long: fmt.Sprintf("%s\n%s\n%s\n%s",
"Manage the CLI configuration profiles.",
`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.`,
"The environment variable takes precedence over what is set via the commands.",
"When no profile is set, the default profile is used.",
),
Args: args.NoArgs,
Run: utils.CmdHelp,
}
addSubcommands(cmd, p)
return cmd
}

func addSubcommands(cmd *cobra.Command, p *print.Printer) {
cmd.AddCommand(set.NewCmd(p))
cmd.AddCommand(unset.NewCmd(p))
}
83 changes: 83 additions & 0 deletions internal/cmd/config/profile/set/set.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package set

import (
"fmt"

"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/config"
"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/spf13/cobra"
)

const (
profileArg = "PROFILE"
)

type inputModel struct {
*globalflags.GlobalFlagModel
Profile string
}

func NewCmd(p *print.Printer) *cobra.Command {
cmd := &cobra.Command{
Use: fmt.Sprintf("set %s", profileArg),
Short: "Set a CLI configuration profile",
Long: fmt.Sprintf("%s\n%s\n%s\n%s",
"Set a CLI configuration profile as the active profile.",
`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.`,
"The environment variable takes precedence over what is set via the commands.",
"When no profile is set, the default profile is used.",
),
Args: args.SingleArg(profileArg, nil),
Example: examples.Build(
examples.NewExample(
`Set the configuration profile "my-profile" as the active profile`,
"$ stackit config profile set my-profile"),
),
RunE: func(cmd *cobra.Command, args []string) error {
model, err := parseInput(p, cmd, args)
if err != nil {
return err
}

err = config.SetProfile(model.Profile)
if err != nil {
return fmt.Errorf("set profile: %w", err)
}

p.Info("Profile %q set successfully as the active profile\n", model.Profile)
return nil
},
}
return cmd
}

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

err := config.ValidateProfile(profile)
if err != nil {
return nil, err
}

globalFlags := globalflags.Parse(p, cmd)

model := inputModel{
GlobalFlagModel: globalFlags,
Profile: profile,
}

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
}
132 changes: 132 additions & 0 deletions internal/cmd/config/profile/set/set_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package set

import (
"testing"

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

"github.com/google/go-cmp/cmp"
)

const testProfile = "test-profile"

func fixtureArgValues(mods ...func(argValues []string)) []string {
argValues := []string{
testProfile,
}
for _, mod := range mods {
mod(argValues)
}
return argValues
}

func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
Verbosity: globalflags.VerbosityDefault,
},
Profile: testProfile,
}
for _, mod := range mods {
mod(model)
}
return model
}

func TestParseInput(t *testing.T) {
tests := []struct {
description string
argValues []string
flagValues map[string]string
isValid bool
expectedModel *inputModel
}{
{
description: "base",
argValues: fixtureArgValues(),
isValid: true,
expectedModel: fixtureInputModel(),
},
{
description: "no values",
argValues: []string{},
flagValues: map[string]string{},
isValid: false,
},
{
description: "no arg values",
argValues: []string{},
isValid: false,
},
{
description: "some global flag",
argValues: fixtureArgValues(),
flagValues: map[string]string{
globalflags.VerbosityFlag: globalflags.DebugVerbosity,
},
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.GlobalFlagModel.Verbosity = globalflags.DebugVerbosity
}),
},
{
description: "invalid profile",
argValues: []string{"invalid-profile-&"},
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 input: %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)
}
})
}
}
39 changes: 39 additions & 0 deletions internal/cmd/config/profile/unset/unset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package unset

import (
"fmt"

"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/config"
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"

"github.com/spf13/cobra"
)

func NewCmd(p *print.Printer) *cobra.Command {
cmd := &cobra.Command{
Use: "unset",
Short: "Unset the current active CLI configuration profile",
Long: fmt.Sprintf("%s\n%s",
"Unset the current active CLI configuration profile.",
"When no profile is set, the default profile will be used.",
),
Args: args.NoArgs,
Example: examples.Build(
examples.NewExample(
`Unset the currently active configuration profile. The default profile will be used.`,
"$ stackit config profile unset"),
),
RunE: func(cmd *cobra.Command, args []string) error {
err := config.UnsetProfile()
if err != nil {
return fmt.Errorf("unset profile: %w", err)
}

p.Info("Profile unset successfully. The default profile will be used.\n")
return nil
},
}
return cmd
}
Loading