Introduce generic table printer
* Table printer is based on cli-utils approach * Rename 'phase plan' command to 'phase list' and print all Phase documents from model instead of just printing PhasePlan object Relates-To: #358 Change-Id: If3c5e2463e32f6794af4c82c12955a45583fce80
This commit is contained in:
parent
8efb786a6a
commit
c36a8ea022
@ -31,10 +31,10 @@ are executed in parallel.
|
||||
|
||||
// NewPlanCommand creates a command which prints available phases
|
||||
func NewPlanCommand(cfgFactory config.Factory) *cobra.Command {
|
||||
p := &phase.PlanCommand{Factory: cfgFactory}
|
||||
p := &phase.ListCommand{Factory: cfgFactory}
|
||||
|
||||
planCmd := &cobra.Command{
|
||||
Use: "plan",
|
||||
Use: "list",
|
||||
Short: "List phases",
|
||||
Long: cmdLong[1:],
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
@ -6,7 +6,7 @@ Usage:
|
||||
|
||||
Available Commands:
|
||||
help Help about any command
|
||||
plan List phases
|
||||
list List phases
|
||||
render Render phase documents from model
|
||||
run Run phase
|
||||
tree Tree view of kustomize entrypoints of phase
|
||||
|
@ -3,7 +3,7 @@ Phases within a group are executed sequentially. Multiple phase groups
|
||||
are executed in parallel.
|
||||
|
||||
Usage:
|
||||
plan [flags]
|
||||
list [flags]
|
||||
|
||||
Flags:
|
||||
-h, --help help for plan
|
||||
-h, --help help for list
|
||||
|
@ -24,7 +24,7 @@ such as getting list and applying specific one.
|
||||
### SEE ALSO
|
||||
|
||||
* [airshipctl](airshipctl.md) - A unified entrypoint to various airship components
|
||||
* [airshipctl phase plan](airshipctl_phase_plan.md) - List phases
|
||||
* [airshipctl phase list](airshipctl_phase_list.md) - List phases
|
||||
* [airshipctl phase render](airshipctl_phase_render.md) - Render phase documents from model
|
||||
* [airshipctl phase run](airshipctl_phase_run.md) - Run phase
|
||||
* [airshipctl phase tree](airshipctl_phase_tree.md) - Tree view of kustomize entrypoints of phase
|
||||
|
32
docs/source/cli/airshipctl_phase_list.md
Normal file
32
docs/source/cli/airshipctl_phase_list.md
Normal file
@ -0,0 +1,32 @@
|
||||
## airshipctl phase list
|
||||
|
||||
List phases
|
||||
|
||||
### Synopsis
|
||||
|
||||
List life-cycle phases which were defined in document model by group.
|
||||
Phases within a group are executed sequentially. Multiple phase groups
|
||||
are executed in parallel.
|
||||
|
||||
|
||||
```
|
||||
airshipctl phase list [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for list
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--airshipconf string Path to file for airshipctl configuration. (default "$HOME/.airship/config")
|
||||
--debug enable verbose output
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [airshipctl phase](airshipctl_phase.md) - Manage phases
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
"opendev.org/airship/airshipctl/pkg/config"
|
||||
"opendev.org/airship/airshipctl/pkg/document"
|
||||
"opendev.org/airship/airshipctl/pkg/phase/ifc"
|
||||
"opendev.org/airship/airshipctl/pkg/util"
|
||||
)
|
||||
|
||||
// RunFlags options for phase run command
|
||||
@ -63,14 +64,14 @@ func (c *RunCommand) RunE() error {
|
||||
return phase.Run(ifc.RunOptions{DryRun: c.Options.DryRun, Timeout: c.Options.Timeout, Progress: c.Options.Progress})
|
||||
}
|
||||
|
||||
// PlanCommand plan command
|
||||
type PlanCommand struct {
|
||||
// ListCommand phase list command
|
||||
type ListCommand struct {
|
||||
Factory config.Factory
|
||||
Writer io.Writer
|
||||
}
|
||||
|
||||
// RunE runs a phase plan command
|
||||
func (c *PlanCommand) RunE() error {
|
||||
func (c *ListCommand) RunE() error {
|
||||
cfg, err := c.Factory()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -81,12 +82,18 @@ func (c *PlanCommand) RunE() error {
|
||||
return err
|
||||
}
|
||||
|
||||
plan, err := helper.Plan()
|
||||
phases, err := helper.ListPhases()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return PrintPlan(plan, c.Writer)
|
||||
rt, err := util.NewResourceTable(phases, util.DefaultStatusFunction())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
util.DefaultTablePrinter(c.Writer, nil).PrintTable(rt, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TreeCommand plan command
|
||||
|
@ -156,7 +156,7 @@ func TestPlanCommand(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
command := phase.PlanCommand{
|
||||
command := phase.ListCommand{
|
||||
Factory: tt.factory,
|
||||
}
|
||||
err := command.RunE()
|
||||
|
152
pkg/util/tableprinter.go
Normal file
152
pkg/util/tableprinter.go
Normal file
@ -0,0 +1,152 @@
|
||||
/*
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
pe "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
|
||||
"sigs.k8s.io/cli-utils/pkg/object"
|
||||
"sigs.k8s.io/cli-utils/pkg/print/table"
|
||||
)
|
||||
|
||||
// Printable is an object which has Group, Version, Kind, Name and Namespace
|
||||
// fields and appropriate methods to retrieve them. All K8s api objects
|
||||
// implement methods above
|
||||
type Printable interface {
|
||||
metav1.Object
|
||||
runtime.Object
|
||||
}
|
||||
|
||||
var _ table.ResourceStates = &ResourceTable{}
|
||||
|
||||
// ResourceTable provides information about the resources that should be printed
|
||||
type ResourceTable struct {
|
||||
resources []Printable
|
||||
statusFunc PrintStatusFunction
|
||||
}
|
||||
|
||||
// NewResourceTable creates resource status table
|
||||
func NewResourceTable(object interface{}, statusFunc PrintStatusFunction) (*ResourceTable, error) {
|
||||
var resources []Printable
|
||||
value := reflect.ValueOf(object)
|
||||
switch value.Kind() {
|
||||
case reflect.Slice:
|
||||
resources = make([]Printable, value.Len())
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
printable, ok := value.Index(i).Interface().(Printable)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("resource %#v is not printable", value.Index(i).Interface())
|
||||
}
|
||||
resources[i] = printable
|
||||
}
|
||||
default:
|
||||
res, ok := value.Interface().(Printable)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("resource %#v is not printable", value.Interface())
|
||||
}
|
||||
resources = []Printable{res}
|
||||
}
|
||||
|
||||
return &ResourceTable{
|
||||
resources: resources,
|
||||
statusFunc: statusFunc,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Resources list of table rows
|
||||
func (rt *ResourceTable) Resources() []table.Resource {
|
||||
result := make([]table.Resource, len(rt.resources))
|
||||
for i, obj := range rt.resources {
|
||||
result[i] = &resource{Printable: obj, statusFunc: rt.statusFunc}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
//Error returns error for resource table
|
||||
func (rt *ResourceTable) Error() error {
|
||||
return fmt.Errorf("error table printing")
|
||||
}
|
||||
|
||||
var _ table.Resource = &resource{}
|
||||
|
||||
type resource struct {
|
||||
Printable
|
||||
statusFunc PrintStatusFunction
|
||||
}
|
||||
|
||||
// Identifier opf the resource
|
||||
func (r *resource) Identifier() object.ObjMetadata {
|
||||
return object.ObjMetadata{
|
||||
Namespace: r.GetNamespace(),
|
||||
Name: r.GetName(),
|
||||
GroupKind: r.GetObjectKind().GroupVersionKind().GroupKind(),
|
||||
}
|
||||
}
|
||||
|
||||
// ResourceStatus returns resource status object
|
||||
func (r *resource) ResourceStatus() *PrintResourceStatus {
|
||||
return r.statusFunc(r.Printable)
|
||||
}
|
||||
|
||||
// SubResources list of subresources
|
||||
func (r *resource) SubResources() []table.Resource {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DefaultTablePrinter returns basic table printer with 2 columns: Namespace
|
||||
// and Name/Kind
|
||||
func DefaultTablePrinter(out, errOut io.Writer) *table.BaseTablePrinter {
|
||||
cols := []table.ColumnDefinition{
|
||||
table.MustColumn("namespace"),
|
||||
table.MustColumn("resource"),
|
||||
}
|
||||
return &table.BaseTablePrinter{
|
||||
IOStreams: genericclioptions.IOStreams{
|
||||
Out: out,
|
||||
ErrOut: errOut,
|
||||
},
|
||||
Columns: cols,
|
||||
}
|
||||
}
|
||||
|
||||
// PrintResourceStatus alias for ResourceStatus
|
||||
type PrintResourceStatus = pe.ResourceStatus
|
||||
|
||||
// PrintStatusFunction alias for status function
|
||||
type PrintStatusFunction = func(Printable) *PrintResourceStatus
|
||||
|
||||
// DefaultStatusFunction for resource status
|
||||
func DefaultStatusFunction() PrintStatusFunction {
|
||||
return func(obj Printable) *PrintResourceStatus {
|
||||
unsContent, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
|
||||
identifier := object.ObjMetadata{
|
||||
Namespace: obj.GetNamespace(),
|
||||
Name: obj.GetName(),
|
||||
GroupKind: obj.GetObjectKind().GroupVersionKind().GroupKind(),
|
||||
}
|
||||
return &PrintResourceStatus{
|
||||
Identifier: identifier,
|
||||
Resource: &unstructured.Unstructured{Object: unsContent},
|
||||
Error: err,
|
||||
}
|
||||
}
|
||||
}
|
108
pkg/util/tableprinter_test.go
Normal file
108
pkg/util/tableprinter_test.go
Normal file
@ -0,0 +1,108 @@
|
||||
/*
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package util_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
airapiv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1"
|
||||
"opendev.org/airship/airshipctl/pkg/util"
|
||||
)
|
||||
|
||||
func TestPrintTableForList(t *testing.T) {
|
||||
resources := []*airapiv1.Phase{
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Phase",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "p1",
|
||||
},
|
||||
},
|
||||
}
|
||||
expectedOut := [][]byte{
|
||||
[]byte("NAMESPACE RESOURCE "),
|
||||
[]byte(" Phase/p1 "),
|
||||
{},
|
||||
}
|
||||
|
||||
rt, err := util.NewResourceTable(resources, func(util.Printable) *util.PrintResourceStatus { return nil })
|
||||
require.NoError(t, err)
|
||||
buf := &bytes.Buffer{}
|
||||
util.DefaultTablePrinter(buf, nil).PrintTable(rt, 0)
|
||||
out, err := ioutil.ReadAll(buf)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedOut, bytes.Split(out, []byte("\n")))
|
||||
}
|
||||
|
||||
func TestDefaultStatusFunction(t *testing.T) {
|
||||
f := util.DefaultStatusFunction()
|
||||
expectedObj := map[string]interface{}{
|
||||
"kind": "Phase",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "p1",
|
||||
"creationTimestamp": nil,
|
||||
},
|
||||
"config": map[string]interface{}{
|
||||
"documentEntryPoint": "",
|
||||
"executorRef": nil,
|
||||
},
|
||||
}
|
||||
printable := &airapiv1.Phase{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Phase",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "p1",
|
||||
},
|
||||
}
|
||||
rs := f(printable)
|
||||
assert.Equal(t, expectedObj, rs.Resource.Object)
|
||||
}
|
||||
|
||||
func TestNonPrintable(t *testing.T) {
|
||||
_, err := util.NewResourceTable("non Printable string", util.DefaultStatusFunction())
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestPrintTableForSingleResource(t *testing.T) {
|
||||
resource := &airapiv1.Phase{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Phase",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "p1",
|
||||
},
|
||||
}
|
||||
expectedOut := [][]byte{
|
||||
[]byte("NAMESPACE RESOURCE "),
|
||||
[]byte(" Phase/p1 "),
|
||||
{},
|
||||
}
|
||||
rt, err := util.NewResourceTable(resource, func(util.Printable) *util.PrintResourceStatus { return nil })
|
||||
require.NoError(t, err)
|
||||
buf := &bytes.Buffer{}
|
||||
util.DefaultTablePrinter(buf, nil).PrintTable(rt, 0)
|
||||
out, err := ioutil.ReadAll(buf)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedOut, bytes.Split(out, []byte("\n")))
|
||||
}
|
Loading…
Reference in New Issue
Block a user