diff --git a/cmd/config/get_context.go b/cmd/config/get_context.go index 40c6b58c5..965159581 100644 --- a/cmd/config/get_context.go +++ b/cmd/config/get_context.go @@ -59,26 +59,12 @@ func NewGetContextCommand(cfgFactory config.Factory) *cobra.Command { if len(args) == 1 { o.Name = args[0] } - if o.Name == "" && !o.CurrentContext { - contexts := airconfig.GetContexts() - if len(contexts) == 0 { - fmt.Fprintln(cmd.OutOrStdout(), "No Contexts found in the configuration.") - } - for _, context := range contexts { - fmt.Fprintln(cmd.OutOrStdout(), context.String()) - } - return nil - } - if o.CurrentContext { - o.Name = airconfig.CurrentContext + if len(airconfig.GetContexts()) == 0 { + fmt.Fprintln(cmd.OutOrStdout(), "No Contexts found in the configuration.") + } else { + return o.Print(airconfig, cmd.OutOrStdout()) } - - context, err := airconfig.GetContext(o.Name) - if err != nil { - return err - } - fmt.Fprintln(cmd.OutOrStdout(), context.String()) return nil }, } @@ -95,4 +81,10 @@ func addGetContextFlags(o *config.ContextOptions, cmd *cobra.Command) { "current", false, "get the current context") + + flags.StringVar( + &o.Format, + "format", + "yaml", + "choose between `yaml` or `table`, default is `yaml`") } diff --git a/cmd/config/get_context_test.go b/cmd/config/get_context_test.go index 7097f40f6..e52dd2a82 100644 --- a/cmd/config/get_context_test.go +++ b/cmd/config/get_context_test.go @@ -94,6 +94,5 @@ func getNamedTestContext(contextName string) *config.Context { newContext := &config.Context{ Manifest: fmt.Sprintf("Manifest_%s", contextName), } - return newContext } diff --git a/cmd/config/testdata/TestGetContextCmdGoldenOutput/get-all-contexts.golden b/cmd/config/testdata/TestGetContextCmdGoldenOutput/get-all-contexts.golden index a0275daf8..b600dfcb5 100644 --- a/cmd/config/testdata/TestGetContextCmdGoldenOutput/get-all-contexts.golden +++ b/cmd/config/testdata/TestGetContextCmdGoldenOutput/get-all-contexts.golden @@ -1,3 +1,4 @@ -managementConfiguration: "" -manifest: Manifest_ContextBar - +contexts: + ContextBar: + managementConfiguration: "" + manifest: Manifest_ContextBar diff --git a/cmd/config/testdata/TestGetContextCmdGoldenOutput/get-context.golden b/cmd/config/testdata/TestGetContextCmdGoldenOutput/get-context.golden index 461c3f74d..7b4384876 100644 --- a/cmd/config/testdata/TestGetContextCmdGoldenOutput/get-context.golden +++ b/cmd/config/testdata/TestGetContextCmdGoldenOutput/get-context.golden @@ -1,3 +1,4 @@ -managementConfiguration: "" -manifest: Manifest_ContextFoo - +contexts: + ContextFoo: + managementConfiguration: "" + manifest: Manifest_ContextFoo diff --git a/cmd/config/testdata/TestGetContextCmdGoldenOutput/get-current-context.golden b/cmd/config/testdata/TestGetContextCmdGoldenOutput/get-current-context.golden index c4603a93a..cfd318eae 100644 --- a/cmd/config/testdata/TestGetContextCmdGoldenOutput/get-current-context.golden +++ b/cmd/config/testdata/TestGetContextCmdGoldenOutput/get-current-context.golden @@ -1,3 +1,4 @@ -managementConfiguration: "" -manifest: Manifest_ContextBaz - +contexts: + ContextBaz: + managementConfiguration: "" + manifest: Manifest_ContextBaz diff --git a/cmd/config/testdata/TestGetContextCmdGoldenOutput/get-multiple-contexts.golden b/cmd/config/testdata/TestGetContextCmdGoldenOutput/get-multiple-contexts.golden index 341a5f36b..112ec0c2c 100644 --- a/cmd/config/testdata/TestGetContextCmdGoldenOutput/get-multiple-contexts.golden +++ b/cmd/config/testdata/TestGetContextCmdGoldenOutput/get-multiple-contexts.golden @@ -18,6 +18,7 @@ airshipctl config get-context exampleContext Flags: - --current get the current context - -h, --help help for get-context + --current get the current context + --format yaml choose between yaml or `table`, default is `yaml` (default "yaml") + -h, --help help for get-context diff --git a/cmd/config/testdata/TestGetContextCmdGoldenOutput/missing.golden b/cmd/config/testdata/TestGetContextCmdGoldenOutput/missing.golden index 6a5c697b5..17f5cdd1c 100644 --- a/cmd/config/testdata/TestGetContextCmdGoldenOutput/missing.golden +++ b/cmd/config/testdata/TestGetContextCmdGoldenOutput/missing.golden @@ -18,6 +18,7 @@ airshipctl config get-context exampleContext Flags: - --current get the current context - -h, --help help for get-context + --current get the current context + --format yaml choose between yaml or `table`, default is `yaml` (default "yaml") + -h, --help help for get-context diff --git a/docs/source/cli/airshipctl_config_get-context.md b/docs/source/cli/airshipctl_config_get-context.md index 504d1520e..b1c9af814 100644 --- a/docs/source/cli/airshipctl_config_get-context.md +++ b/docs/source/cli/airshipctl_config_get-context.md @@ -29,8 +29,9 @@ airshipctl config get-context exampleContext ### Options ``` - --current get the current context - -h, --help help for get-context + --current get the current context + --format yaml choose between yaml or `table`, default is `yaml` (default "yaml") + -h, --help help for get-context ``` ### Options inherited from parent commands diff --git a/docs/source/cli/airshipctl_document_pull.md b/docs/source/cli/airshipctl_document_pull.md index 576708e5c..7b4500f48 100644 --- a/docs/source/cli/airshipctl_document_pull.md +++ b/docs/source/cli/airshipctl_document_pull.md @@ -11,6 +11,7 @@ By default the airship config file is initialized with the repository "https://opendev.org/airship/treasuremap" as a source of manifests and with the manifests target path "$HOME/.airship/default". + ``` airshipctl document pull [flags] ``` diff --git a/pkg/config/errors.go b/pkg/config/errors.go index 42871af66..887c93676 100644 --- a/pkg/config/errors.go +++ b/pkg/config/errors.go @@ -276,3 +276,13 @@ type ErrConfigFileExists struct { func (e ErrConfigFileExists) Error() string { return fmt.Sprintf("could not create default config at %s, file already exists", e.Path) } + +// ErrWrongOutputFormat is returned when unknown output format is defined for printing config +type ErrWrongOutputFormat struct { + Wrong string + Possible []string +} + +func (e ErrWrongOutputFormat) Error() string { + return fmt.Sprintf("wrong output format %s, must be one of %s", e.Wrong, strings.Join(e.Possible, " ")) +} diff --git a/pkg/config/options.go b/pkg/config/options.go index a59ad6860..a1368a960 100644 --- a/pkg/config/options.go +++ b/pkg/config/options.go @@ -15,6 +15,14 @@ limitations under the License. package config import ( + "fmt" + "io" + "sort" + "strings" + + "k8s.io/cli-runtime/pkg/printers" + "sigs.k8s.io/yaml" + "opendev.org/airship/airshipctl/pkg/errors" ) @@ -25,6 +33,7 @@ type ContextOptions struct { Manifest string Current bool ManagementConfiguration string + Format string } // ManifestOptions holds all configurable options for manifest configuration @@ -67,6 +76,76 @@ func (o *ContextOptions) Validate() error { return nil } +// Print prints the config contexts using one of formats `yaml` or `table` to a given output +func (o *ContextOptions) Print(cfg *Config, w io.Writer) error { + if o.CurrentContext { + o.Name = cfg.CurrentContext + } + + switch o.Format { + case "yaml": + type reducedConfig struct { + Contexts map[string]*Context `json:"contexts"` + CurrentContext string `json:"currentContext,omitempty"` + } + contexts := &reducedConfig{ + Contexts: cfg.Contexts, + CurrentContext: cfg.CurrentContext, + } + if o.Name != "" { + c, err := cfg.GetContext(o.Name) + if err != nil { + return err + } + contexts = &reducedConfig{ + Contexts: map[string]*Context{o.Name: c}, + } + } + data, err := yaml.Marshal(contexts) + if err != nil { + return err + } + fmt.Fprintf(w, string(data)) + case "table": + out := printers.GetNewTabWriter(w) + defer out.Flush() + + toPrint := []string{} + if o.Name != "" { + toPrint = append(toPrint, o.Name) + } else { + for name := range cfg.Contexts { + toPrint = append(toPrint, name) + } + } + + columnNames := []string{"CURRENT", "NAME", "MANIFEST", "MANAGEMENTCONFIGURATION"} + _, err := fmt.Fprintf(out, "%s\n", strings.Join(columnNames, "\t")) + if err != nil { + return err + } + + sort.Strings(toPrint) + for _, name := range toPrint { + prefix := " " + if cfg.CurrentContext == name { + prefix = "*" + } + context, err := cfg.GetContext(name) + if err != nil { + return err + } + _, err = fmt.Fprintf(out, "%s\t%s\t%s\t%s\n", prefix, name, context.Manifest, context.ManagementConfiguration) + if err != nil { + return err + } + } + default: + return ErrWrongOutputFormat{Wrong: o.Format, Possible: []string{"yaml", "table"}} + } + return nil +} + // Validate checks for the possible manifest option values and returns // Error when invalid value or incompatible choice of values given func (o *ManifestOptions) Validate() error { diff --git a/pkg/config/options_test.go b/pkg/config/options_test.go index f0417250e..d270aad69 100644 --- a/pkg/config/options_test.go +++ b/pkg/config/options_test.go @@ -15,13 +15,22 @@ limitations under the License. package config_test import ( + "bytes" + "fmt" + "io/ioutil" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "opendev.org/airship/airshipctl/pkg/config" ) +const ( + testFormatError = "wrong output format , must be one of yaml table" + defaultCurrentContext = "a-context" +) + func TestContextOptionsValidate(t *testing.T) { tests := []struct { name string @@ -64,3 +73,128 @@ func TestContextOptionsValidate(t *testing.T) { }) } } +func TestContextOptionsPrint(t *testing.T) { + yamlOutput := `contexts: + a-context: + managementConfiguration: a-manageconf + manifest: a-manifest +currentContext: a-context +` + tests := []struct { + name string + testContextOptions config.ContextOptions + testConfig config.Config + expectedOutput string + expectedErr string + }{ + { + name: "Wrong output format", + testContextOptions: config.ContextOptions{ + Format: "", + }, + testConfig: config.Config{}, + expectedOutput: "", + expectedErr: testFormatError, + }, + { + name: "List contexts in table format", + testContextOptions: config.ContextOptions{ + Name: "", + CurrentContext: false, + Format: "table", + }, + testConfig: config.Config{ + CurrentContext: defaultCurrentContext, + Contexts: map[string]*config.Context{ + "a-context": {Manifest: "a-manifest", ManagementConfiguration: "a-manageconf"}, + "b-context": {Manifest: "b-manifest", ManagementConfiguration: "b-manageconf"}}, + }, + expectedOutput: `CURRENT NAME MANIFEST MANAGEMENTCONFIGURATION +* a-context a-manifest a-manageconf + b-context b-manifest b-manageconf +`, + }, + { + name: "List contexts in table format(Context name is given)", + testContextOptions: config.ContextOptions{ + Name: defaultCurrentContext, + CurrentContext: false, + Format: "table", + }, + testConfig: config.Config{ + CurrentContext: defaultCurrentContext, + Contexts: map[string]*config.Context{ + "a-context": {Manifest: "a-manifest", ManagementConfiguration: "a-manageconf"}, + "b-context": {Manifest: "b-manifest", ManagementConfiguration: "b-manageconf"}}, + }, + expectedOutput: `CURRENT NAME MANIFEST MANAGEMENTCONFIGURATION +* a-context a-manifest a-manageconf +`, + }, + { + name: "List contexts in table format(CurrentContext is true)", + testContextOptions: config.ContextOptions{ + Name: "", + CurrentContext: true, + Format: "table", + }, + testConfig: config.Config{ + CurrentContext: defaultCurrentContext, + Contexts: map[string]*config.Context{ + "a-context": {Manifest: "a-manifest", ManagementConfiguration: "a-manageconf"}, + "b-context": {Manifest: "b-manifest", ManagementConfiguration: "b-manageconf"}}, + }, + expectedOutput: `CURRENT NAME MANIFEST MANAGEMENTCONFIGURATION +* a-context a-manifest a-manageconf +`, + }, + { + name: "List contexts in table format(Wrong Name is given)", + testContextOptions: config.ContextOptions{ + Name: "wrong-context", + CurrentContext: false, + Format: "table", + }, + testConfig: config.Config{ + CurrentContext: defaultCurrentContext, + Contexts: map[string]*config.Context{ + "a-context": {Manifest: "a-manifest", ManagementConfiguration: "a-manageconf"}, + "b-context": {Manifest: "b-manifest", ManagementConfiguration: "b-manageconf"}}, + }, + expectedOutput: `CURRENT NAME MANIFEST MANAGEMENTCONFIGURATION +`, + expectedErr: "context with name 'wrong-context'", + }, + { + name: "List contexts in yaml format", + testContextOptions: config.ContextOptions{ + Name: "", + CurrentContext: false, + Format: "yaml", + }, + testConfig: config.Config{ + CurrentContext: defaultCurrentContext, + Contexts: map[string]*config.Context{ + "a-context": {Manifest: "a-manifest", ManagementConfiguration: "a-manageconf"}}, + }, + expectedOutput: yamlOutput, + }, + } + for _, tc := range tests { + tt := tc + t.Run(tt.name, func(t *testing.T) { + buf := &bytes.Buffer{} + err := tt.testContextOptions.Print(&tt.testConfig, buf) + if tt.expectedErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.expectedErr) + } else { + assert.NoError(t, err) + } + out, err := ioutil.ReadAll(buf) + fmt.Print(string(out)) + require.NoError(t, err) + assert.Equal(t, tt.expectedOutput, string(out)) + }) + } +}