-
Notifications
You must be signed in to change notification settings - Fork 39
Onboard command to export config profiles #544
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
Changes from 7 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
deb660c
Add command to export config profiles
6baf1ae
Fix: Linter and tests
ec6c7c1
Fix: Export test
7c8fb37
Fix: Export test
1310042
Fix: CI pipeline - Export test
76113cd
Fix: CI pipeline - Export test
217af01
Refinements in tests and defining filePath
83a10c2
Changed export --file-path behaviour.
caae881
Refactor TestExportProfile
6f603a5
Update export --file-path description and error handling
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| ## stackit config profile export | ||
|
|
||
| Exports a CLI configuration profile | ||
|
|
||
| ### Synopsis | ||
|
|
||
| Exports a CLI configuration profile. | ||
|
|
||
| ``` | ||
| stackit config profile export PROFILE_NAME [flags] | ||
| ``` | ||
|
|
||
| ### Examples | ||
|
|
||
| ``` | ||
| Export a profile with name "PROFILE_NAME" to the current path | ||
| $ stackit config profile export PROFILE_NAME | ||
|
|
||
| Export a profile with name "PROFILE_NAME"" to a specific file path FILE_PATH | ||
| $ stackit config profile export PROFILE_NAME --file-path FILE_PATH | ||
| ``` | ||
|
|
||
| ### Options | ||
|
|
||
| ``` | ||
| --file-path string Path where the config should be saved. E.g. '--file-path ~/config.json', '--file-path ~/' | ||
| -h, --help Help for "stackit config profile export" | ||
| ``` | ||
|
|
||
| ### 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 | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| package export | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "path/filepath" | ||
| "strings" | ||
|
|
||
| "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/flags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
|
|
||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| const ( | ||
| profileNameArg = "PROFILE_NAME" | ||
|
|
||
| filePathFlag = "file-path" | ||
|
|
||
| configFileExtension = "json" | ||
| ) | ||
|
|
||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| ProfileName string | ||
| FilePath string | ||
| } | ||
|
|
||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: fmt.Sprintf("export %s", profileNameArg), | ||
| Short: "Exports a CLI configuration profile", | ||
| Long: "Exports a CLI configuration profile.", | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Export a profile with name "PROFILE_NAME" to a file in your current directory`, | ||
| "$ stackit config profile export PROFILE_NAME", | ||
| ), | ||
| examples.NewExample( | ||
| `Export a profile with name "PROFILE_NAME"" to a specific file path FILE_PATH`, | ||
| "$ stackit config profile export PROFILE_NAME --file-path FILE_PATH", | ||
| ), | ||
| ), | ||
| Args: args.SingleArg(profileNameArg, nil), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| model, err := parseInput(p, cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| err = config.ExportProfile(p, model.ProfileName, model.FilePath) | ||
| if err != nil { | ||
| return fmt.Errorf("could not export profile: %w", err) | ||
| } | ||
|
|
||
| p.Info("Exported profile %q to %q\n", model.ProfileName, model.FilePath) | ||
|
|
||
| return nil | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
|
|
||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().StringP(filePathFlag, "f", "", "If set, writes the payload to the given. If unset, writes the payload to you current directory with the name of the profile. E.g. '--file-path ~/my-config.json', '--file-path ~/'") | ||
| } | ||
|
|
||
| func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { | ||
| profileName := inputArgs[0] | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
|
|
||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| ProfileName: profileName, | ||
| FilePath: flags.FlagToStringValue(p, cmd, filePathFlag), | ||
| } | ||
|
|
||
| // If filePath contains does not contain a file name, then add a default name | ||
| if !strings.HasSuffix(model.FilePath, fmt.Sprintf(".%s", configFileExtension)) { | ||
| exportFileName := fmt.Sprintf("%s.%s", model.ProfileName, configFileExtension) | ||
| model.FilePath = filepath.Join(model.FilePath, exportFileName) | ||
| } | ||
|
joaopalet marked this conversation as resolved.
Outdated
|
||
|
|
||
| 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 | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| package export | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "testing" | ||
|
|
||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
|
|
||
| "github.com/google/go-cmp/cmp" | ||
| ) | ||
|
|
||
| const ( | ||
| testProfileArg = "default" | ||
| testExportPath = "/tmp/stackit-profiles/" + testProfileArg + ".json" | ||
| ) | ||
|
|
||
| func fixtureArgValues(mods ...func(args []string)) []string { | ||
| args := []string{ | ||
| testProfileArg, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(args) | ||
| } | ||
| return args | ||
| } | ||
|
|
||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| filePathFlag: testExportPath, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
|
|
||
| func fixtureInputModel(mods ...func(inputModel *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| ProfileName: testProfileArg, | ||
| FilePath: testExportPath, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
|
|
||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| argsValues []string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| argsValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| argsValues: []string{}, | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no args", | ||
| argsValues: []string{}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no flags", | ||
| argsValues: fixtureArgValues(), | ||
| flagValues: map[string]string{}, | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(func(inputModel *inputModel) { | ||
| inputModel.FilePath = fmt.Sprintf("%s.json", testProfileArg) | ||
| }), | ||
| }, | ||
| } | ||
|
|
||
| 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.argsValues) | ||
| 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.argsValues) | ||
| 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) | ||
| } | ||
| }) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.