Skip to content

Commit 7e4fd4f

Browse files
authored
Add command to import config profiles (#539)
* Add command to import config profiles
1 parent 8065324 commit 7e4fd4f

File tree

10 files changed

+485
-0
lines changed

10 files changed

+485
-0
lines changed

docs/stackit_config_profile.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ stackit config profile [flags]
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
3636
* [stackit config profile delete](./stackit_config_profile_delete.md) - Delete a CLI configuration profile
37+
* [stackit config profile import](./stackit_config_profile_import.md) - Imports a CLI configuration profile
3738
* [stackit config profile list](./stackit_config_profile_list.md) - Lists all CLI configuration profiles
3839
* [stackit config profile set](./stackit_config_profile_set.md) - Set a CLI configuration profile
3940
* [stackit config profile unset](./stackit_config_profile_unset.md) - Unset the current active CLI configuration profile

docs/stackit_config_profile_import.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
## stackit config profile import
2+
3+
Imports a CLI configuration profile
4+
5+
### Synopsis
6+
7+
Imports a CLI configuration profile.
8+
9+
```
10+
stackit config profile import [flags]
11+
```
12+
13+
### Examples
14+
15+
```
16+
Import a config with name "PROFILE_NAME" from file "./config.json"
17+
$ stackit config profile import --name PROFILE_NAME --config `@./config.json`
18+
19+
Import a config with name "PROFILE_NAME" from file "./config.json" and do not set as active
20+
$ stackit config profile import --name PROFILE_NAME --config `@./config.json` --no-set
21+
```
22+
23+
### Options
24+
25+
```
26+
-c, --config string File where configuration will be imported from
27+
-h, --help Help for "stackit config profile import"
28+
--name string Profile name
29+
--no-set Set the imported profile not as active
30+
```
31+
32+
### Options inherited from parent commands
33+
34+
```
35+
-y, --assume-yes If set, skips all confirmation prompts
36+
--async If set, runs the command asynchronously
37+
-o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"]
38+
-p, --project-id string Project ID
39+
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
40+
```
41+
42+
### SEE ALSO
43+
44+
* [stackit config profile](./stackit_config_profile.md) - Manage the CLI configuration profiles
45+
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package importProfile
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
6+
"github.com/stackitcloud/stackit-cli/internal/pkg/config"
7+
"github.com/stackitcloud/stackit-cli/internal/pkg/errors"
8+
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
9+
"github.com/stackitcloud/stackit-cli/internal/pkg/flags"
10+
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
11+
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
12+
)
13+
14+
const (
15+
nameFlag = "name"
16+
configFlag = "config"
17+
noSetFlag = "no-set"
18+
)
19+
20+
type inputModel struct {
21+
*globalflags.GlobalFlagModel
22+
ProfileName string
23+
Config string
24+
NoSet bool
25+
}
26+
27+
func NewCmd(p *print.Printer) *cobra.Command {
28+
cmd := &cobra.Command{
29+
Use: "import",
30+
Short: "Imports a CLI configuration profile",
31+
Long: "Imports a CLI configuration profile.",
32+
Example: examples.Build(
33+
examples.NewExample(
34+
`Import a config with name "PROFILE_NAME" from file "./config.json"`,
35+
"$ stackit config profile import --name PROFILE_NAME --config `@./config.json`",
36+
),
37+
examples.NewExample(
38+
`Import a config with name "PROFILE_NAME" from file "./config.json" and do not set as active`,
39+
"$ stackit config profile import --name PROFILE_NAME --config `@./config.json` --no-set",
40+
),
41+
),
42+
Args: args.NoArgs,
43+
RunE: func(cmd *cobra.Command, _ []string) error {
44+
model, err := parseInput(p, cmd)
45+
if err != nil {
46+
return err
47+
}
48+
49+
err = config.ImportProfile(p, model.ProfileName, model.Config, !model.NoSet)
50+
if err != nil {
51+
return err
52+
}
53+
54+
p.Info("Successfully imported profile %q\n", model.ProfileName)
55+
56+
return nil
57+
},
58+
}
59+
configureFlags(cmd)
60+
return cmd
61+
}
62+
63+
func configureFlags(cmd *cobra.Command) {
64+
cmd.Flags().String(nameFlag, "", "Profile name")
65+
cmd.Flags().VarP(flags.ReadFromFileFlag(), configFlag, "c", "File where configuration will be imported from")
66+
cmd.Flags().Bool(noSetFlag, false, "Set the imported profile not as active")
67+
68+
cobra.CheckErr(cmd.MarkFlagRequired(nameFlag))
69+
cobra.CheckErr(cmd.MarkFlagRequired(configFlag))
70+
}
71+
72+
func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) {
73+
globalFlags := globalflags.Parse(p, cmd)
74+
75+
model := &inputModel{
76+
GlobalFlagModel: globalFlags,
77+
ProfileName: flags.FlagToStringValue(p, cmd, nameFlag),
78+
Config: flags.FlagToStringValue(p, cmd, configFlag),
79+
NoSet: flags.FlagToBoolValue(p, cmd, noSetFlag),
80+
}
81+
82+
if model.Config == "" {
83+
return nil, &errors.FlagValidationError{
84+
Flag: configFlag,
85+
Details: "must not be empty",
86+
}
87+
}
88+
89+
if model.ProfileName == "" {
90+
return nil, &errors.FlagValidationError{
91+
Flag: nameFlag,
92+
Details: "must not be empty",
93+
}
94+
}
95+
96+
if p.IsVerbosityDebug() {
97+
modelStr, err := print.BuildDebugStrFromInputModel(model)
98+
if err != nil {
99+
p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err)
100+
} else {
101+
p.Debug(print.DebugLevel, "parsed input values: %s", modelStr)
102+
}
103+
}
104+
105+
return model, nil
106+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package importProfile
2+
3+
import (
4+
_ "embed"
5+
"strconv"
6+
"testing"
7+
8+
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
9+
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
10+
11+
"github.com/google/go-cmp/cmp"
12+
)
13+
14+
const testProfile = "test-profile"
15+
const testConfig = "@./template/profile.json"
16+
const testNoSet = false
17+
18+
//go:embed template/profile.json
19+
var testConfigContent string
20+
21+
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
22+
flagValues := map[string]string{
23+
nameFlag: testProfile,
24+
configFlag: testConfig,
25+
noSetFlag: strconv.FormatBool(testNoSet),
26+
}
27+
for _, mod := range mods {
28+
mod(flagValues)
29+
}
30+
return flagValues
31+
}
32+
33+
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
34+
model := &inputModel{
35+
GlobalFlagModel: &globalflags.GlobalFlagModel{
36+
Verbosity: globalflags.VerbosityDefault,
37+
},
38+
ProfileName: testProfile,
39+
Config: testConfigContent,
40+
NoSet: testNoSet,
41+
}
42+
for _, mod := range mods {
43+
mod(model)
44+
}
45+
return model
46+
}
47+
48+
func TestParseInput(t *testing.T) {
49+
tests := []struct {
50+
description string
51+
flagValues map[string]string
52+
isValid bool
53+
expectedModel *inputModel
54+
}{
55+
{
56+
description: "base",
57+
flagValues: fixtureFlagValues(),
58+
isValid: true,
59+
expectedModel: fixtureInputModel(),
60+
},
61+
{
62+
description: "no flags",
63+
flagValues: map[string]string{},
64+
isValid: false,
65+
},
66+
{
67+
description: "invalid path",
68+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
69+
flagValues[configFlag] = "@./template/invalid-file"
70+
}),
71+
isValid: false,
72+
},
73+
}
74+
75+
for _, tt := range tests {
76+
t.Run(tt.description, func(t *testing.T) {
77+
p := print.NewPrinter()
78+
cmd := NewCmd(p)
79+
err := globalflags.Configure(cmd.Flags())
80+
if err != nil {
81+
t.Fatalf("configure global flags: %v", err)
82+
}
83+
84+
for flag, value := range tt.flagValues {
85+
err = cmd.Flags().Set(flag, value)
86+
if err != nil {
87+
if !tt.isValid {
88+
return
89+
}
90+
t.Fatalf("setting flag --%s=%s: %v", flag, value, err)
91+
}
92+
}
93+
94+
err = cmd.ValidateRequiredFlags()
95+
if err != nil {
96+
if !tt.isValid {
97+
return
98+
}
99+
t.Fatalf("error validating flags: %v", err)
100+
}
101+
102+
model, err := parseInput(p, cmd)
103+
if err != nil {
104+
if !tt.isValid {
105+
t.Fatalf("error parsing input: %v", err)
106+
}
107+
}
108+
109+
if !tt.isValid {
110+
t.Fatalf("did not fail on invalid input")
111+
}
112+
diff := cmp.Diff(tt.expectedModel, model)
113+
if diff != "" {
114+
t.Fatalf("Data does not match: %s", diff)
115+
}
116+
})
117+
}
118+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"allowed_url_domain": "stackit.cloud",
3+
"async": false,
4+
"authorization_custom_endpoint": "",
5+
"dns_custom_endpoint": "",
6+
"iaas_custom_endpoint": "",
7+
"identity_provider_custom_client_id": "",
8+
"identity_provider_custom_well_known_configuration": "",
9+
"load_balancer_custom_endpoint": "",
10+
"logme_custom_endpoint": "",
11+
"mariadb_custom_endpoint": "",
12+
"mongodbflex_custom_endpoint": "",
13+
"object_storage_custom_endpoint": "",
14+
"observability_custom_endpoint": "",
15+
"opensearch_custom_endpoint": "",
16+
"output_format": "",
17+
"postgresflex_custom_endpoint": "",
18+
"project_id": "",
19+
"project_name": "",
20+
"rabbitmq_custom_endpoint": "",
21+
"redis_custom_endpoint": "",
22+
"resource_manager_custom_endpoint": "",
23+
"runcommand_custom_endpoint": "",
24+
"secrets_manager_custom_endpoint": "",
25+
"serverbackup_custom_endpoint": "",
26+
"service_account_custom_endpoint": "",
27+
"service_enablement_custom_endpoint": "",
28+
"session_time_limit": "2h",
29+
"ske_custom_endpoint": "",
30+
"sqlserverflex_custom_endpoint": "",
31+
"token_custom_endpoint": "",
32+
"verbosity": "info"
33+
}

internal/cmd/config/profile/profile.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/stackitcloud/stackit-cli/internal/cmd/config/profile/create"
77
"github.com/stackitcloud/stackit-cli/internal/cmd/config/profile/delete"
8+
importProfile "github.com/stackitcloud/stackit-cli/internal/cmd/config/profile/import"
89
"github.com/stackitcloud/stackit-cli/internal/cmd/config/profile/list"
910
"github.com/stackitcloud/stackit-cli/internal/cmd/config/profile/set"
1011
"github.com/stackitcloud/stackit-cli/internal/cmd/config/profile/unset"
@@ -38,4 +39,5 @@ func addSubcommands(cmd *cobra.Command, p *print.Printer) {
3839
cmd.AddCommand(create.NewCmd(p))
3940
cmd.AddCommand(list.NewCmd(p))
4041
cmd.AddCommand(delete.NewCmd(p))
42+
cmd.AddCommand(importProfile.NewCmd(p))
4143
}

0 commit comments

Comments
 (0)