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
|
// NewPlanCommand creates a command which prints available phases
|
||||||
func NewPlanCommand(cfgFactory config.Factory) *cobra.Command {
|
func NewPlanCommand(cfgFactory config.Factory) *cobra.Command {
|
||||||
p := &phase.PlanCommand{Factory: cfgFactory}
|
p := &phase.ListCommand{Factory: cfgFactory}
|
||||||
|
|
||||||
planCmd := &cobra.Command{
|
planCmd := &cobra.Command{
|
||||||
Use: "plan",
|
Use: "list",
|
||||||
Short: "List phases",
|
Short: "List phases",
|
||||||
Long: cmdLong[1:],
|
Long: cmdLong[1:],
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
|
|
@ -6,7 +6,7 @@ Usage:
|
||||||
|
|
||||||
Available Commands:
|
Available Commands:
|
||||||
help Help about any command
|
help Help about any command
|
||||||
plan List phases
|
list List phases
|
||||||
render Render phase documents from model
|
render Render phase documents from model
|
||||||
run Run phase
|
run Run phase
|
||||||
tree Tree view of kustomize entrypoints of 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.
|
are executed in parallel.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
plan [flags]
|
list [flags]
|
||||||
|
|
||||||
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
|
### SEE ALSO
|
||||||
|
|
||||||
* [airshipctl](airshipctl.md) - A unified entrypoint to various airship components
|
* [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 render](airshipctl_phase_render.md) - Render phase documents from model
|
||||||
* [airshipctl phase run](airshipctl_phase_run.md) - Run phase
|
* [airshipctl phase run](airshipctl_phase_run.md) - Run phase
|
||||||
* [airshipctl phase tree](airshipctl_phase_tree.md) - Tree view of kustomize entrypoints of phase
|
* [airshipctl phase tree](airshipctl_phase_tree.md) - Tree view of kustomize entrypoints of phase
|
||||||
|
|
|
@ -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/config"
|
||||||
"opendev.org/airship/airshipctl/pkg/document"
|
"opendev.org/airship/airshipctl/pkg/document"
|
||||||
"opendev.org/airship/airshipctl/pkg/phase/ifc"
|
"opendev.org/airship/airshipctl/pkg/phase/ifc"
|
||||||
|
"opendev.org/airship/airshipctl/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RunFlags options for phase run command
|
// 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})
|
return phase.Run(ifc.RunOptions{DryRun: c.Options.DryRun, Timeout: c.Options.Timeout, Progress: c.Options.Progress})
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlanCommand plan command
|
// ListCommand phase list command
|
||||||
type PlanCommand struct {
|
type ListCommand struct {
|
||||||
Factory config.Factory
|
Factory config.Factory
|
||||||
Writer io.Writer
|
Writer io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunE runs a phase plan command
|
// RunE runs a phase plan command
|
||||||
func (c *PlanCommand) RunE() error {
|
func (c *ListCommand) RunE() error {
|
||||||
cfg, err := c.Factory()
|
cfg, err := c.Factory()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -81,12 +82,18 @@ func (c *PlanCommand) RunE() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
plan, err := helper.Plan()
|
phases, err := helper.ListPhases()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
// TreeCommand plan command
|
||||||
|
|
|
@ -156,7 +156,7 @@ func TestPlanCommand(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
tt := tt
|
tt := tt
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
command := phase.PlanCommand{
|
command := phase.ListCommand{
|
||||||
Factory: tt.factory,
|
Factory: tt.factory,
|
||||||
}
|
}
|
||||||
err := command.RunE()
|
err := command.RunE()
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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