Implement plan list subcommand
Change-Id: Ibcd7dbf6dc8cd9d0b018c148017244526651d8ba Closes: #385
This commit is contained in:
parent
c36a8ea022
commit
069e4069ce
@ -18,7 +18,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/config"
|
||||
"opendev.org/airship/airshipctl/pkg/errors"
|
||||
"opendev.org/airship/airshipctl/pkg/phase"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -29,12 +29,15 @@ List life-cycle plans which were defined in document model.
|
||||
|
||||
// NewListCommand creates a command which prints available phase plans
|
||||
func NewListCommand(cfgFactory config.Factory) *cobra.Command {
|
||||
planCmd := &phase.PlanListCommand{Factory: cfgFactory}
|
||||
|
||||
listCmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List plans",
|
||||
Long: listLong[1:],
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return errors.ErrNotImplemented{What: "airshipctl plan list"}
|
||||
planCmd.Writer = cmd.OutOrStdout()
|
||||
return planCmd.RunE()
|
||||
},
|
||||
}
|
||||
return listCmd
|
||||
|
@ -2,6 +2,7 @@ apiVersion: airshipit.org/v1alpha1
|
||||
kind: PhasePlan
|
||||
metadata:
|
||||
name: phasePlan
|
||||
description: "Default phase plan"
|
||||
phaseGroups:
|
||||
- name: group1
|
||||
phases:
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
type PhasePlan struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
PhaseGroups []PhaseGroup `json:"phaseGroups,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -15,12 +15,17 @@
|
||||
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/config"
|
||||
"opendev.org/airship/airshipctl/pkg/document"
|
||||
"opendev.org/airship/airshipctl/pkg/phase/ifc"
|
||||
@ -146,3 +151,54 @@ func (c *TreeCommand) RunE() error {
|
||||
t.PrintTree("")
|
||||
return nil
|
||||
}
|
||||
|
||||
// PlanListCommand phase list command
|
||||
type PlanListCommand struct {
|
||||
Factory config.Factory
|
||||
Writer io.Writer
|
||||
}
|
||||
|
||||
// RunE runs a phase plan command
|
||||
func (c *PlanListCommand) RunE() error {
|
||||
cfg, err := c.Factory()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
helper, err := NewHelper(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
phases, err := helper.ListPlans()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rt, err := util.NewResourceTable(phases, util.DefaultStatusFunction())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printer := util.DefaultTablePrinter(c.Writer, nil)
|
||||
descriptionCol := table.ColumnDef{
|
||||
ColumnName: "description",
|
||||
ColumnHeader: "DESCRIPTION",
|
||||
ColumnWidth: 40,
|
||||
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
|
||||
}
|
||||
return fmt.Fprint(w, plan.Description)
|
||||
},
|
||||
}
|
||||
printer.Columns = append(printer.Columns, descriptionCol)
|
||||
printer.PrintTable(rt, 0)
|
||||
return nil
|
||||
}
|
||||
|
@ -15,7 +15,9 @@
|
||||
package phase_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -101,7 +103,7 @@ func TestRunCommand(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlanCommand(t *testing.T) {
|
||||
func TestListCommand(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
errContains string
|
||||
@ -236,3 +238,58 @@ func TestTreeCommand(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlanListCommand(t *testing.T) {
|
||||
testErr := fmt.Errorf(testFactoryErr)
|
||||
testCases := []struct {
|
||||
name string
|
||||
factory config.Factory
|
||||
expectedOut [][]byte
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "Error config factory",
|
||||
factory: func() (*config.Config, error) {
|
||||
return nil, testErr
|
||||
},
|
||||
expectedErr: testFactoryErr,
|
||||
expectedOut: [][]byte{{}},
|
||||
},
|
||||
{
|
||||
name: "List phases",
|
||||
factory: func() (*config.Config, error) {
|
||||
conf := config.NewConfig()
|
||||
manifest := conf.Manifests[config.AirshipDefaultManifest]
|
||||
manifest.TargetPath = "testdata"
|
||||
manifest.MetadataPath = "metadata.yaml"
|
||||
manifest.Repositories[config.DefaultTestPhaseRepo].URLString = ""
|
||||
return conf, nil
|
||||
},
|
||||
expectedOut: [][]byte{
|
||||
[]byte("NAMESPACE RESOURCE DESCRIPTION "),
|
||||
[]byte(" PhasePlan/phasePlan Default phase plan "),
|
||||
{},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tt := tc
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
buf := &bytes.Buffer{}
|
||||
cmd := phase.PlanListCommand{
|
||||
Factory: tt.factory,
|
||||
Writer: buf,
|
||||
}
|
||||
err := cmd.RunE()
|
||||
if tt.expectedErr != "" {
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), tt.expectedErr)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
out, err := ioutil.ReadAll(buf)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedOut, bytes.Split(out, []byte("\n")))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -15,8 +15,6 @@
|
||||
package phase
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@ -144,6 +142,35 @@ func (helper *Helper) ListPhases() ([]*v1alpha1.Phase, error) {
|
||||
return phases, nil
|
||||
}
|
||||
|
||||
// ListPlans returns all phases associated with manifest
|
||||
func (helper *Helper) ListPlans() ([]*v1alpha1.PhasePlan, error) {
|
||||
bundle, err := document.NewBundleByPath(helper.phaseBundleRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
plan := &v1alpha1.PhasePlan{}
|
||||
selector, err := document.NewSelector().ByObject(plan, v1alpha1.Scheme)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
docs, err := bundle.Select(selector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
plans := make([]*v1alpha1.PhasePlan, len(docs))
|
||||
for i, doc := range docs {
|
||||
p := &v1alpha1.PhasePlan{}
|
||||
if err = doc.ToAPIObject(p, v1alpha1.Scheme); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
plans[i] = p
|
||||
}
|
||||
return plans, nil
|
||||
}
|
||||
|
||||
// ClusterMapAPIobj associated with the the manifest
|
||||
func (helper *Helper) ClusterMapAPIobj() (*v1alpha1.ClusterMap, error) {
|
||||
bundle, err := document.NewBundleByPath(helper.phaseBundleRoot)
|
||||
@ -236,27 +263,3 @@ func (helper *Helper) PhaseEntryPointBasePath() string {
|
||||
func (helper *Helper) WorkDir() (string, error) {
|
||||
return filepath.Join(util.UserHomeDir(), config.AirshipConfigDir), nil
|
||||
}
|
||||
|
||||
// PrintPlan prints plan
|
||||
// TODO make this more readable in the future, and move to client
|
||||
func PrintPlan(plan *v1alpha1.PhasePlan, w io.Writer) error {
|
||||
result := make(map[string][]string)
|
||||
for _, phaseGroup := range plan.PhaseGroups {
|
||||
phases := make([]string, len(phaseGroup.Phases))
|
||||
for i, phase := range phaseGroup.Phases {
|
||||
phases[i] = phase.Name
|
||||
}
|
||||
result[phaseGroup.Name] = phases
|
||||
}
|
||||
|
||||
tw := util.NewTabWriter(w)
|
||||
defer tw.Flush()
|
||||
fmt.Fprintf(tw, "GROUP\tPHASE\n")
|
||||
for group, phaseList := range result {
|
||||
fmt.Fprintf(tw, "%s\t\n", group)
|
||||
for _, phase := range phaseList {
|
||||
fmt.Fprintf(tw, "\t%s\n", phase)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -15,7 +15,6 @@
|
||||
package phase_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
@ -236,6 +235,57 @@ func TestHelperListPhases(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelperListPlans(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
errContains string
|
||||
expectedLen int
|
||||
config func(t *testing.T) *config.Config
|
||||
}{
|
||||
{
|
||||
name: "Success plan list",
|
||||
expectedLen: 1,
|
||||
config: testConfig,
|
||||
},
|
||||
{
|
||||
name: "Error bundle path doesn't exist",
|
||||
config: func(t *testing.T) *config.Config {
|
||||
conf := testConfig(t)
|
||||
conf.Manifests["dummy_manifest"].MetadataPath = brokenMetaPath
|
||||
return conf
|
||||
},
|
||||
errContains: "no such file or directory",
|
||||
},
|
||||
{
|
||||
name: "Success 0 plans",
|
||||
config: func(t *testing.T) *config.Config {
|
||||
conf := testConfig(t)
|
||||
conf.Manifests["dummy_manifest"].MetadataPath = noPlanMetaPath
|
||||
return conf
|
||||
},
|
||||
expectedLen: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
tt := test
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
helper, err := phase.NewHelper(tt.config(t))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, helper)
|
||||
|
||||
actualList, actualErr := helper.ListPlans()
|
||||
if tt.errContains != "" {
|
||||
require.Error(t, actualErr)
|
||||
assert.Contains(t, actualErr.Error(), tt.errContains)
|
||||
} else {
|
||||
require.NoError(t, actualErr)
|
||||
assert.Len(t, actualList, tt.expectedLen)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelperClusterMapAPI(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
@ -401,24 +451,6 @@ func TestHelperExecutorDoc(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelperPrintPlan(t *testing.T) {
|
||||
helper, err := phase.NewHelper(testConfig(t))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, helper)
|
||||
plan, err := helper.Plan()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, plan)
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
err = phase.PrintPlan(plan, buf)
|
||||
require.NoError(t, err)
|
||||
// easy check to make sure printed plan contains all phases in plan
|
||||
assert.Contains(t, buf.String(), "remotedirect")
|
||||
assert.Contains(t, buf.String(), "isogen")
|
||||
assert.Contains(t, buf.String(), "initinfra")
|
||||
assert.Contains(t, buf.String(), "some_phase")
|
||||
assert.Contains(t, buf.String(), "capi_init")
|
||||
}
|
||||
|
||||
func TestHelperTargetPath(t *testing.T) {
|
||||
helper, err := phase.NewHelper(testConfig(t))
|
||||
require.NoError(t, err)
|
||||
|
@ -29,6 +29,7 @@ type Helper interface {
|
||||
Phase(phaseID ID) (*v1alpha1.Phase, error)
|
||||
Plan() (*v1alpha1.PhasePlan, error)
|
||||
ListPhases() ([]*v1alpha1.Phase, error)
|
||||
ListPlans() ([]*v1alpha1.PhasePlan, error)
|
||||
ClusterMapAPIobj() (*v1alpha1.ClusterMap, error)
|
||||
ClusterMap() (clustermap.ClusterMap, error)
|
||||
ExecutorDoc(phaseID ID) (document.Document, error)
|
||||
|
3
pkg/phase/testdata/phases/kustomization.yaml
vendored
3
pkg/phase/testdata/phases/kustomization.yaml
vendored
@ -1,4 +1,5 @@
|
||||
resources:
|
||||
- phases.yaml
|
||||
- executors.yaml
|
||||
- cluster-map.yaml
|
||||
- cluster-map.yaml
|
||||
- phaseplan.yaml
|
9
pkg/phase/testdata/phases/phaseplan.yaml
vendored
Normal file
9
pkg/phase/testdata/phases/phaseplan.yaml
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: PhasePlan
|
||||
metadata:
|
||||
name: phasePlan
|
||||
description: "Default phase plan"
|
||||
phaseGroups:
|
||||
- name: group1
|
||||
phases:
|
||||
- name: phase
|
Loading…
Reference in New Issue
Block a user