Skip to content
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

Add command to import config profiles #539

Merged
merged 5 commits into from
Dec 17, 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
1 change: 1 addition & 0 deletions docs/stackit_config_profile.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ stackit config profile [flags]
* [stackit config](./stackit_config.md) - Provides functionality for CLI configuration options
* [stackit config profile create](./stackit_config_profile_create.md) - Creates a CLI configuration profile
* [stackit config profile delete](./stackit_config_profile_delete.md) - Delete a CLI configuration profile
* [stackit config profile import](./stackit_config_profile_import.md) - Imports a CLI configuration profile
* [stackit config profile list](./stackit_config_profile_list.md) - Lists all CLI configuration profiles
* [stackit config profile set](./stackit_config_profile_set.md) - Set a CLI configuration profile
* [stackit config profile unset](./stackit_config_profile_unset.md) - Unset the current active CLI configuration profile
Expand Down
45 changes: 45 additions & 0 deletions docs/stackit_config_profile_import.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
## stackit config profile import

Imports a CLI configuration profile

### Synopsis

Imports a CLI configuration profile.

```
stackit config profile import [flags]
```

### Examples

```
Import a config with name "PROFILE_NAME" from file "./config.json"
$ stackit config profile import --name PROFILE_NAME --config `@./config.json`

Import a config with name "PROFILE_NAME" from file "./config.json" and do not set as active
$ stackit config profile import --name PROFILE_NAME --config `@./config.json` --no-set
```

### Options

```
-c, --config string File where configuration will be imported from
-h, --help Help for "stackit config profile import"
--name string Profile name
--no-set Set the imported profile not as active
```

### Options inherited from parent commands

```
-y, --assume-yes If set, skips all confirmation prompts
--async If set, runs the command asynchronously
-o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"]
-p, --project-id string Project ID
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```

### SEE ALSO

* [stackit config profile](./stackit_config_profile.md) - Manage the CLI configuration profiles

106 changes: 106 additions & 0 deletions internal/cmd/config/profile/import/import.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package importProfile

import (
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/config"
"github.com/stackitcloud/stackit-cli/internal/pkg/errors"
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
"github.com/stackitcloud/stackit-cli/internal/pkg/flags"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
)

const (
nameFlag = "name"
configFlag = "config"
noSetFlag = "no-set"
)

type inputModel struct {
*globalflags.GlobalFlagModel
ProfileName string
Config string
NoSet bool
}

func NewCmd(p *print.Printer) *cobra.Command {
cmd := &cobra.Command{
Use: "import",
Short: "Imports a CLI configuration profile",
Long: "Imports a CLI configuration profile.",
Example: examples.Build(
examples.NewExample(
`Import a config with name "PROFILE_NAME" from file "./config.json"`,
"$ stackit config profile import --name PROFILE_NAME --config `@./config.json`",
),
examples.NewExample(
`Import a config with name "PROFILE_NAME" from file "./config.json" and do not set as active`,
"$ stackit config profile import --name PROFILE_NAME --config `@./config.json` --no-set",
),
),
Args: args.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
model, err := parseInput(p, cmd)
if err != nil {
return err
}

err = config.ImportProfile(p, model.ProfileName, model.Config, !model.NoSet)
if err != nil {
return err
}

p.Info("Successfully imported profile %q\n", model.ProfileName)

return nil
},
}
configureFlags(cmd)
return cmd
}

func configureFlags(cmd *cobra.Command) {
cmd.Flags().String(nameFlag, "", "Profile name")
cmd.Flags().VarP(flags.ReadFromFileFlag(), configFlag, "c", "File where configuration will be imported from")
cmd.Flags().Bool(noSetFlag, false, "Set the imported profile not as active")

cobra.CheckErr(cmd.MarkFlagRequired(nameFlag))
cobra.CheckErr(cmd.MarkFlagRequired(configFlag))
}

func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)

model := &inputModel{
GlobalFlagModel: globalFlags,
ProfileName: flags.FlagToStringValue(p, cmd, nameFlag),
Config: flags.FlagToStringValue(p, cmd, configFlag),
NoSet: flags.FlagToBoolValue(p, cmd, noSetFlag),
}

if model.Config == "" {
return nil, &errors.FlagValidationError{
Flag: configFlag,
Details: "must not be empty",
}
}

if model.ProfileName == "" {
return nil, &errors.FlagValidationError{
Flag: nameFlag,
Details: "must not be empty",
}
}

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

import (
_ "embed"
"strconv"
"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"
const testConfig = "@./template/profile.json"
const testNoSet = false

//go:embed template/profile.json
var testConfigContent string

func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
nameFlag: testProfile,
configFlag: testConfig,
noSetFlag: strconv.FormatBool(testNoSet),
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}

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

func TestParseInput(t *testing.T) {
tests := []struct {
description string
flagValues map[string]string
isValid bool
expectedModel *inputModel
}{
{
description: "base",
flagValues: fixtureFlagValues(),
isValid: true,
expectedModel: fixtureInputModel(),
},
{
description: "no flags",
flagValues: map[string]string{},
isValid: false,
},
{
description: "invalid path",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[configFlag] = "@./template/invalid-file"
}),
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.ValidateRequiredFlags()
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error validating flags: %v", err)
}

model, err := parseInput(p, cmd)
if err != nil {
if !tt.isValid {
t.Fatalf("error parsing input: %v", err)
}
}

if !tt.isValid {
t.Fatalf("did not fail on invalid input")
}
diff := cmp.Diff(tt.expectedModel, model)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
33 changes: 33 additions & 0 deletions internal/cmd/config/profile/import/template/profile.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"allowed_url_domain": "stackit.cloud",
"async": false,
"authorization_custom_endpoint": "",
"dns_custom_endpoint": "",
"iaas_custom_endpoint": "",
"identity_provider_custom_client_id": "",
"identity_provider_custom_well_known_configuration": "",
"load_balancer_custom_endpoint": "",
"logme_custom_endpoint": "",
"mariadb_custom_endpoint": "",
"mongodbflex_custom_endpoint": "",
"object_storage_custom_endpoint": "",
"observability_custom_endpoint": "",
"opensearch_custom_endpoint": "",
"output_format": "",
"postgresflex_custom_endpoint": "",
"project_id": "",
"project_name": "",
"rabbitmq_custom_endpoint": "",
"redis_custom_endpoint": "",
"resource_manager_custom_endpoint": "",
"runcommand_custom_endpoint": "",
"secrets_manager_custom_endpoint": "",
"serverbackup_custom_endpoint": "",
"service_account_custom_endpoint": "",
"service_enablement_custom_endpoint": "",
"session_time_limit": "2h",
"ske_custom_endpoint": "",
"sqlserverflex_custom_endpoint": "",
"token_custom_endpoint": "",
"verbosity": "info"
}
2 changes: 2 additions & 0 deletions internal/cmd/config/profile/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/stackitcloud/stackit-cli/internal/cmd/config/profile/create"
"github.com/stackitcloud/stackit-cli/internal/cmd/config/profile/delete"
importProfile "github.com/stackitcloud/stackit-cli/internal/cmd/config/profile/import"
"github.com/stackitcloud/stackit-cli/internal/cmd/config/profile/list"
"github.com/stackitcloud/stackit-cli/internal/cmd/config/profile/set"
"github.com/stackitcloud/stackit-cli/internal/cmd/config/profile/unset"
Expand Down Expand Up @@ -38,4 +39,5 @@ func addSubcommands(cmd *cobra.Command, p *print.Printer) {
cmd.AddCommand(create.NewCmd(p))
cmd.AddCommand(list.NewCmd(p))
cmd.AddCommand(delete.NewCmd(p))
cmd.AddCommand(importProfile.NewCmd(p))
}
Loading
Loading