[#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<niha.twinkle@gmail.com>
Change-Id: I8bed077573eb79783cfc77e4ebf82c819f41d125
This commit is contained in:
Niharika Bhavaraju 2021-01-18 18:00:39 -05:00 committed by niharikab
parent 05b36dd40f
commit 5085f22c56
7 changed files with 224 additions and 55 deletions

View File

@ -25,20 +25,35 @@ const (
listLong = ` listLong = `
List life-cycle plans which were defined in document model. 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 // NewListCommand creates a command which prints available phase plans
func NewListCommand(cfgFactory config.Factory) *cobra.Command { func NewListCommand(cfgFactory config.Factory) *cobra.Command {
planCmd := &phase.PlanListCommand{Factory: cfgFactory} p := &phase.PlanListCommand{Factory: cfgFactory}
listCmd := &cobra.Command{ listCmd := &cobra.Command{
Use: "list", Use: "list",
Short: "List plans", Short: "List plans",
Long: listLong[1:], Long: listLong[1:],
Example: listExample,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
planCmd.Writer = cmd.OutOrStdout() p.Writer = cmd.OutOrStdout()
return planCmd.RunE() return p.RunE()
}, },
} }
flags := listCmd.Flags()
flags.StringVarP(&p.Options.FormatType,
"output", "o", "table", "'table' "+
"and 'yaml' are available "+
"output formats")
return listCmd return listCmd
} }

View File

@ -3,5 +3,17 @@ List life-cycle plans which were defined in document model.
Usage: Usage:
list [flags] 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: Flags:
-h, --help help for list -h, --help help for list
-o, --output string 'table' and 'yaml' are available output formats (default "table")

View File

@ -11,10 +11,25 @@ List life-cycle plans which were defined in document model.
airshipctl plan list [flags] 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 ### 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 ### Options inherited from parent commands

View File

@ -15,17 +15,12 @@
package phase package phase
import ( import (
"fmt"
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "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/cluster/clustermap"
"opendev.org/airship/airshipctl/pkg/config" "opendev.org/airship/airshipctl/pkg/config"
"opendev.org/airship/airshipctl/pkg/document" "opendev.org/airship/airshipctl/pkg/document"
@ -35,6 +30,13 @@ import (
"opendev.org/airship/airshipctl/pkg/util/yaml" "opendev.org/airship/airshipctl/pkg/util/yaml"
) )
const (
// TableOutputFormat table
TableOutputFormat = "table"
// YamlOutputFormat yaml
YamlOutputFormat = "yaml"
)
// GenericRunFlags generic options for run command // GenericRunFlags generic options for run command
type GenericRunFlags struct { type GenericRunFlags struct {
DryRun bool DryRun bool
@ -85,7 +87,7 @@ type ListCommand struct {
// RunE runs a phase list command // RunE runs a phase list command
func (c *ListCommand) RunE() error { 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} return phaseerrors.ErrInvalidFormat{RequestedFormat: c.OutputFormat}
} }
cfg, err := c.Factory() cfg, err := c.Factory()
@ -103,7 +105,7 @@ func (c *ListCommand) RunE() error {
if err != nil { if err != nil {
return err return err
} }
if c.OutputFormat == "table" { if c.OutputFormat == TableOutputFormat {
return PrintPhaseListTable(c.Writer, phaseList) return PrintPhaseListTable(c.Writer, phaseList)
} }
return yaml.WriteOut(c.Writer, phaseList) return yaml.WriteOut(c.Writer, phaseList)
@ -160,14 +162,23 @@ func (c *TreeCommand) RunE() error {
return nil return nil
} }
// PlanListFlags flags given for plan list command
type PlanListFlags struct {
FormatType string
}
// PlanListCommand phase list command // PlanListCommand phase list command
type PlanListCommand struct { type PlanListCommand struct {
Options PlanListFlags
Factory config.Factory Factory config.Factory
Writer io.Writer Writer io.Writer
} }
// RunE runs a plan list command // RunE runs a plan list command
func (c *PlanListCommand) RunE() error { 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() cfg, err := c.Factory()
if err != nil { if err != nil {
return err return err
@ -178,42 +189,18 @@ func (c *PlanListCommand) RunE() error {
return err return err
} }
phases, err := helper.ListPlans() phasePlans, err := helper.ListPlans()
if err != nil { if err != nil {
return err return err
} }
switch {
rt, err := util.NewResourceTable(phases, util.DefaultStatusFunction()) case c.Options.FormatType == YamlOutputFormat:
if err != nil { return yaml.WriteOut(c.Writer, phasePlans)
return err 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 // PlanRunFlags options for phase run command
@ -258,7 +245,7 @@ type ClusterListCommand struct {
// RunE executes cluster list command // RunE executes cluster list command
func (c *ClusterListCommand) RunE() error { 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} return phaseerrors.ErrInvalidOutputFormat{RequestedFormat: c.Format}
} }
cfg, err := c.Factory() cfg, err := c.Factory()

View File

@ -324,12 +324,25 @@ func TestTreeCommand(t *testing.T) {
} }
func TestPlanListCommand(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) testErr := fmt.Errorf(testFactoryErr)
testCases := []struct { testCases := []struct {
name string name string
factory config.Factory factory config.Factory
expectedOut [][]byte expectedOut [][]byte
expectedErr string expectedErr string
Format string
expectedYaml string
}{ }{
{ {
name: "Error config factory", name: "Error config factory",
@ -338,13 +351,27 @@ func TestPlanListCommand(t *testing.T) {
}, },
expectedErr: testFactoryErr, expectedErr: testFactoryErr,
expectedOut: [][]byte{{}}, 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", name: "List phases",
factory: func() (*config.Config, error) { factory: func() (*config.Config, error) {
conf := config.NewConfig() conf := config.NewConfig()
manifest := conf.Manifests[config.AirshipDefaultManifest] manifest := conf.Manifests[config.AirshipDefaultManifest]
manifest.TargetPath = "testdata" manifest.TargetPath = testTargetPath
manifest.MetadataPath = testMetadataPath manifest.MetadataPath = testMetadataPath
manifest.Repositories[config.DefaultTestPhaseRepo].URLString = "" manifest.Repositories[config.DefaultTestPhaseRepo].URLString = ""
return conf, nil 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 { for _, tc := range testCases {
@ -368,6 +409,7 @@ func TestPlanListCommand(t *testing.T) {
cmd := phase.PlanListCommand{ cmd := phase.PlanListCommand{
Factory: tt.factory, Factory: tt.factory,
Writer: buf, Writer: buf,
Options: phase.PlanListFlags{FormatType: tt.Format},
} }
err := cmd.RunE() err := cmd.RunE()
if tt.expectedErr != "" { if tt.expectedErr != "" {
@ -377,8 +419,13 @@ func TestPlanListCommand(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
} }
out, err := ioutil.ReadAll(buf) out, err := ioutil.ReadAll(buf)
fmt.Print(string(out))
require.NoError(t, err) 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{ conf.Manifests = map[string]*config.Manifest{
"manifest": { "manifest": {
MetadataPath: testMetadataPath, MetadataPath: testMetadataPath,
TargetPath: "testdata", TargetPath: testTargetPath,
PhaseRepositoryName: config.DefaultTestPhaseRepo, PhaseRepositoryName: config.DefaultTestPhaseRepo,
Repositories: map[string]*config.Repository{ Repositories: map[string]*config.Repository{
config.DefaultTestPhaseRepo: { config.DefaultTestPhaseRepo: {

View File

@ -101,3 +101,38 @@ func phaseFromResource(r table.Resource) (*v1alpha1.Phase, error) {
phase := &v1alpha1.Phase{} phase := &v1alpha1.Phase{}
return phase, runtime.DefaultUnstructuredConverter.FromUnstructured(rs.Resource.Object, 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
}

View File

@ -20,6 +20,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -118,3 +119,60 @@ func TestDefaultStatusFunction(t *testing.T) {
rs := f(printable) rs := f(printable)
assert.Equal(t, expectedObj, rs.Resource.Object) 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)
}