From 5085f22c564364665cb8c06593899f99f31d822a Mon Sep 17 00:00:00 2001 From: Niharika Bhavaraju Date: Mon, 18 Jan 2021 18:00:39 -0500 Subject: [PATCH] [#394] plan list -o yaml changes Added yaml output flag to show plan list in yaml format Relates-To: #394 Co-authored By: Niharika Bhavaraju Change-Id: I8bed077573eb79783cfc77e4ebf82c819f41d125 --- cmd/plan/list.go | 27 ++++++-- .../plan-list-cmd-with-help.golden | 14 +++- docs/source/cli/airshipctl_plan_list.md | 17 ++++- pkg/phase/command.go | 67 ++++++++----------- pkg/phase/command_test.go | 61 +++++++++++++++-- pkg/phase/printers.go | 35 ++++++++++ pkg/phase/printers_test.go | 58 ++++++++++++++++ 7 files changed, 224 insertions(+), 55 deletions(-) diff --git a/cmd/plan/list.go b/cmd/plan/list.go index d4c07c32e..3d5fa6f30 100644 --- a/cmd/plan/list.go +++ b/cmd/plan/list.go @@ -25,20 +25,35 @@ const ( listLong = ` List life-cycle plans which were defined in document model. ` + listExample = ` +#list plan +airshipctl plan list + +#list plan(yaml output format) +airshipctl plan list -o yaml + +#list plan(table output format) +airshipctl plan list -o table` ) // NewListCommand creates a command which prints available phase plans func NewListCommand(cfgFactory config.Factory) *cobra.Command { - planCmd := &phase.PlanListCommand{Factory: cfgFactory} + p := &phase.PlanListCommand{Factory: cfgFactory} listCmd := &cobra.Command{ - Use: "list", - Short: "List plans", - Long: listLong[1:], + Use: "list", + Short: "List plans", + Long: listLong[1:], + Example: listExample, RunE: func(cmd *cobra.Command, args []string) error { - planCmd.Writer = cmd.OutOrStdout() - return planCmd.RunE() + p.Writer = cmd.OutOrStdout() + return p.RunE() }, } + flags := listCmd.Flags() + flags.StringVarP(&p.Options.FormatType, + "output", "o", "table", "'table' "+ + "and 'yaml' are available "+ + "output formats") return listCmd } diff --git a/cmd/plan/testdata/TestNewListCommandGoldenOutput/plan-list-cmd-with-help.golden b/cmd/plan/testdata/TestNewListCommandGoldenOutput/plan-list-cmd-with-help.golden index 40b587516..3e4e2c903 100644 --- a/cmd/plan/testdata/TestNewListCommandGoldenOutput/plan-list-cmd-with-help.golden +++ b/cmd/plan/testdata/TestNewListCommandGoldenOutput/plan-list-cmd-with-help.golden @@ -3,5 +3,17 @@ List life-cycle plans which were defined in document model. Usage: list [flags] +Examples: + +#list plan +airshipctl plan list + +#list plan(yaml output format) +airshipctl plan list -o yaml + +#list plan(table output format) +airshipctl plan list -o table + Flags: - -h, --help help for list + -h, --help help for list + -o, --output string 'table' and 'yaml' are available output formats (default "table") diff --git a/docs/source/cli/airshipctl_plan_list.md b/docs/source/cli/airshipctl_plan_list.md index 7c455f967..4b071f5cd 100644 --- a/docs/source/cli/airshipctl_plan_list.md +++ b/docs/source/cli/airshipctl_plan_list.md @@ -11,10 +11,25 @@ List life-cycle plans which were defined in document model. airshipctl plan list [flags] ``` +### Examples + +``` + +#list plan +airshipctl plan list + +#list plan(yaml output format) +airshipctl plan list -o yaml + +#list plan(table output format) +airshipctl plan list -o table +``` + ### Options ``` - -h, --help help for list + -h, --help help for list + -o, --output string 'table' and 'yaml' are available output formats (default "table") ``` ### Options inherited from parent commands diff --git a/pkg/phase/command.go b/pkg/phase/command.go index 353aa9423..13b660c43 100644 --- a/pkg/phase/command.go +++ b/pkg/phase/command.go @@ -15,17 +15,12 @@ package phase import ( - "fmt" "io" "os" "path/filepath" "strings" "time" - "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/cli-utils/pkg/print/table" - - "opendev.org/airship/airshipctl/pkg/api/v1alpha1" "opendev.org/airship/airshipctl/pkg/cluster/clustermap" "opendev.org/airship/airshipctl/pkg/config" "opendev.org/airship/airshipctl/pkg/document" @@ -35,6 +30,13 @@ import ( "opendev.org/airship/airshipctl/pkg/util/yaml" ) +const ( + // TableOutputFormat table + TableOutputFormat = "table" + // YamlOutputFormat yaml + YamlOutputFormat = "yaml" +) + // GenericRunFlags generic options for run command type GenericRunFlags struct { DryRun bool @@ -85,7 +87,7 @@ type ListCommand struct { // RunE runs a phase list command func (c *ListCommand) RunE() error { - if c.OutputFormat != "table" && c.OutputFormat != "yaml" { + if c.OutputFormat != TableOutputFormat && c.OutputFormat != YamlOutputFormat { return phaseerrors.ErrInvalidFormat{RequestedFormat: c.OutputFormat} } cfg, err := c.Factory() @@ -103,7 +105,7 @@ func (c *ListCommand) RunE() error { if err != nil { return err } - if c.OutputFormat == "table" { + if c.OutputFormat == TableOutputFormat { return PrintPhaseListTable(c.Writer, phaseList) } return yaml.WriteOut(c.Writer, phaseList) @@ -160,14 +162,23 @@ func (c *TreeCommand) RunE() error { return nil } +// PlanListFlags flags given for plan list command +type PlanListFlags struct { + FormatType string +} + // PlanListCommand phase list command type PlanListCommand struct { + Options PlanListFlags Factory config.Factory Writer io.Writer } // RunE runs a plan list command func (c *PlanListCommand) RunE() error { + if c.Options.FormatType != TableOutputFormat && c.Options.FormatType != YamlOutputFormat { + return phaseerrors.ErrInvalidFormat{RequestedFormat: c.Options.FormatType} + } cfg, err := c.Factory() if err != nil { return err @@ -178,42 +189,18 @@ func (c *PlanListCommand) RunE() error { return err } - phases, err := helper.ListPlans() + phasePlans, err := helper.ListPlans() if err != nil { return err } - - rt, err := util.NewResourceTable(phases, util.DefaultStatusFunction()) - if err != nil { - return err + switch { + case c.Options.FormatType == YamlOutputFormat: + return yaml.WriteOut(c.Writer, phasePlans) + case c.Options.FormatType == TableOutputFormat: + return PrintPlanListTable(c.Writer, phasePlans) + default: + return PrintPlanListTable(c.Writer, phasePlans) } - - printer := util.DefaultTablePrinter(c.Writer, nil) - descriptionCol := table.ColumnDef{ - ColumnName: "description", - ColumnHeader: "DESCRIPTION", - ColumnWidth: 200, - PrintResourceFunc: func(w io.Writer, width int, r table.Resource) (int, error) { - rs := r.ResourceStatus() - if rs == nil { - return 0, nil - } - plan := &v1alpha1.PhasePlan{} - err := runtime.DefaultUnstructuredConverter.FromUnstructured(rs.Resource.Object, plan) - if err != nil { - return 0, err - } - txt := plan.Description - if len(txt) > width { - txt = txt[:width] - } - _, err = fmt.Fprint(w, txt) - return len(txt), err - }, - } - printer.Columns = append(printer.Columns, descriptionCol) - printer.PrintTable(rt, 0) - return nil } // PlanRunFlags options for phase run command @@ -258,7 +245,7 @@ type ClusterListCommand struct { // RunE executes cluster list command func (c *ClusterListCommand) RunE() error { - if c.Format != "table" && c.Format != "name" { + if c.Format != TableOutputFormat && c.Format != "name" { return phaseerrors.ErrInvalidOutputFormat{RequestedFormat: c.Format} } cfg, err := c.Factory() diff --git a/pkg/phase/command_test.go b/pkg/phase/command_test.go index 3507ca698..2aaa58757 100644 --- a/pkg/phase/command_test.go +++ b/pkg/phase/command_test.go @@ -324,12 +324,25 @@ func TestTreeCommand(t *testing.T) { } func TestPlanListCommand(t *testing.T) { + yamlOutput := `--- +- apiVersion: airshipit.org/v1alpha1 + description: Default phase plan + kind: PhasePlan + metadata: + creationTimestamp: null + name: phasePlan + phases: + - name: phase +... +` testErr := fmt.Errorf(testFactoryErr) testCases := []struct { - name string - factory config.Factory - expectedOut [][]byte - expectedErr string + name string + factory config.Factory + expectedOut [][]byte + expectedErr string + Format string + expectedYaml string }{ { name: "Error config factory", @@ -338,13 +351,27 @@ func TestPlanListCommand(t *testing.T) { }, expectedErr: testFactoryErr, expectedOut: [][]byte{{}}, + Format: "table", }, + { + name: "Error new helper", + factory: func() (*config.Config, error) { + return &config.Config{ + CurrentContext: "does not exist", + Contexts: make(map[string]*config.Context), + }, nil + }, + expectedErr: testNewHelperErr, + Format: "table", + expectedOut: [][]byte{{}}, + }, + { name: "List phases", factory: func() (*config.Config, error) { conf := config.NewConfig() manifest := conf.Manifests[config.AirshipDefaultManifest] - manifest.TargetPath = "testdata" + manifest.TargetPath = testTargetPath manifest.MetadataPath = testMetadataPath manifest.Repositories[config.DefaultTestPhaseRepo].URLString = "" return conf, nil @@ -359,6 +386,20 @@ func TestPlanListCommand(t *testing.T) { " "), {}, }, + Format: "table", + }, + { + name: "Valid yaml input format", + factory: func() (*config.Config, error) { + conf := config.NewConfig() + manifest := conf.Manifests[config.AirshipDefaultManifest] + manifest.TargetPath = testTargetPath + manifest.MetadataPath = "metadata.yaml" + manifest.Repositories[config.DefaultTestPhaseRepo].URLString = "" + return conf, nil + }, + Format: "yaml", + expectedYaml: yamlOutput, }, } for _, tc := range testCases { @@ -368,6 +409,7 @@ func TestPlanListCommand(t *testing.T) { cmd := phase.PlanListCommand{ Factory: tt.factory, Writer: buf, + Options: phase.PlanListFlags{FormatType: tt.Format}, } err := cmd.RunE() if tt.expectedErr != "" { @@ -377,8 +419,13 @@ func TestPlanListCommand(t *testing.T) { assert.NoError(t, err) } out, err := ioutil.ReadAll(buf) + fmt.Print(string(out)) require.NoError(t, err) - assert.Equal(t, tt.expectedOut, bytes.Split(out, []byte("\n"))) + if tt.Format == "yaml" { + assert.Equal(t, tt.expectedYaml, string(out)) + } else { + assert.Equal(t, tt.expectedOut, bytes.Split(out, []byte("\n"))) + } }) } } @@ -415,7 +462,7 @@ func TestPlanRunCommand(t *testing.T) { conf.Manifests = map[string]*config.Manifest{ "manifest": { MetadataPath: testMetadataPath, - TargetPath: "testdata", + TargetPath: testTargetPath, PhaseRepositoryName: config.DefaultTestPhaseRepo, Repositories: map[string]*config.Repository{ config.DefaultTestPhaseRepo: { diff --git a/pkg/phase/printers.go b/pkg/phase/printers.go index 3e5352b2a..1f23f7377 100644 --- a/pkg/phase/printers.go +++ b/pkg/phase/printers.go @@ -101,3 +101,38 @@ func phaseFromResource(r table.Resource) (*v1alpha1.Phase, error) { phase := &v1alpha1.Phase{} return phase, runtime.DefaultUnstructuredConverter.FromUnstructured(rs.Resource.Object, phase) } + +//PrintPlanListTable prints plan list table +func PrintPlanListTable(w io.Writer, phasePlans []*v1alpha1.PhasePlan) error { + rt, err := util.NewResourceTable(phasePlans, util.DefaultStatusFunction()) + if err != nil { + return err + } + + printer := util.DefaultTablePrinter(w, nil) + descriptionCol := table.ColumnDef{ + ColumnName: "description", + ColumnHeader: "DESCRIPTION", + ColumnWidth: 200, + PrintResourceFunc: func(w io.Writer, width int, r table.Resource) (int, error) { + rs := r.ResourceStatus() + if rs == nil { + return 0, nil + } + plan := &v1alpha1.PhasePlan{} + err := runtime.DefaultUnstructuredConverter.FromUnstructured(rs.Resource.Object, plan) + if err != nil { + return 0, err + } + txt := plan.Description + if len(txt) > width { + txt = txt[:width] + } + _, err = fmt.Fprintf(w, txt) + return len(txt), err + }, + } + printer.Columns = append(printer.Columns, descriptionCol) + printer.PrintTable(rt, 0) + return nil +} diff --git a/pkg/phase/printers_test.go b/pkg/phase/printers_test.go index 317000af9..06e828a9f 100644 --- a/pkg/phase/printers_test.go +++ b/pkg/phase/printers_test.go @@ -20,6 +20,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -118,3 +119,60 @@ func TestDefaultStatusFunction(t *testing.T) { rs := f(printable) assert.Equal(t, expectedObj, rs.Resource.Object) } + +func TestPrintPlanListTable(t *testing.T) { + plans := []*v1alpha1.PhasePlan{ + { + TypeMeta: metav1.TypeMeta{ + Kind: "PhasePlan", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "p", + }, + Description: "description", + Phases: []v1alpha1.PhaseStep{ + { + Name: "phase", + }, + }, + }, + } + tests := []struct { + name string + plans []*v1alpha1.PhasePlan + }{ + { + name: "Success print plan list", + plans: plans, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := &bytes.Buffer{} + err := PrintPlanListTable(w, tt.plans) + require.NoError(t, err) + }) + } +} + +func TestDefaultStatusFunctionForPhasePlan(t *testing.T) { + f := util.DefaultStatusFunction() + expectedObj := map[string]interface{}{ + "kind": "PhasePlan", + "metadata": map[string]interface{}{ + "name": "p1", + "creationTimestamp": nil, + }, + } + printable := &v1alpha1.PhasePlan{ + TypeMeta: metav1.TypeMeta{ + Kind: "PhasePlan", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "p1", + }, + } + rs := f(printable) + assert.Equal(t, expectedObj, rs.Resource.Object) +}