[#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 = `
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:],
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
}

View File

@ -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
-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]
```
### 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
-o, --output string 'table' and 'yaml' are available output formats (default "table")
```
### Options inherited from parent commands

View File

@ -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()

View File

@ -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
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)
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: {

View File

@ -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
}

View File

@ -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)
}