From d1c7913ed37b2504d41feea6edd1d267bbea7eb8 Mon Sep 17 00:00:00 2001 From: Ruslan Aliev Date: Thu, 4 Feb 2021 14:27:06 -0600 Subject: [PATCH] Add cluster name filter for phase list cmd This patch implements ability to filter phase by given cluster name. Change-Id: I4eb95f9f75c57eff4ae8eb41c608c6f6d7fa009c Signed-off-by: Ruslan Aliev --- cmd/phase/{plan.go => list.go} | 23 +++++++- cmd/phase/{plan_test.go => list_test.go} | 2 +- cmd/phase/phase.go | 2 +- .../phase-plan-cmd-with-help.golden | 4 +- docs/source/cli/airshipctl_phase_list.md | 4 +- pkg/phase/command.go | 9 ++- pkg/phase/helper.go | 59 ++++++++++++++++++- pkg/phase/helper_test.go | 22 ++++++- pkg/phase/ifc/helper.go | 2 +- pkg/phase/ifc/phase.go | 6 ++ .../valid_site/phases/some_phase.yaml | 1 + 11 files changed, 120 insertions(+), 14 deletions(-) rename cmd/phase/{plan.go => list.go} (69%) rename cmd/phase/{plan_test.go => list_test.go} (95%) diff --git a/cmd/phase/plan.go b/cmd/phase/list.go similarity index 69% rename from cmd/phase/plan.go rename to cmd/phase/list.go index d33860bd7..bfa50dadd 100644 --- a/cmd/phase/plan.go +++ b/cmd/phase/list.go @@ -29,8 +29,8 @@ are executed in parallel. ` ) -// NewPlanCommand creates a command which prints available phases -func NewPlanCommand(cfgFactory config.Factory) *cobra.Command { +// NewListCommand creates a command which prints available phases +func NewListCommand(cfgFactory config.Factory) *cobra.Command { p := &phase.ListCommand{Factory: cfgFactory} planCmd := &cobra.Command{ @@ -42,5 +42,24 @@ func NewPlanCommand(cfgFactory config.Factory) *cobra.Command { return p.RunE() }, } + addListFlags(p, planCmd) return planCmd } + +// addListFlags adds flags for phase list sub-command +func addListFlags(options *phase.ListCommand, cmd *cobra.Command) { + flags := cmd.Flags() + + flags.StringVarP( + &options.ClusterName, + "cluster-name", + "c", + "", + "filter documents by cluster name") + + flags.StringVar( + &options.PlanID.Name, + "plan", + "", + "Plan name of a plan") +} diff --git a/cmd/phase/plan_test.go b/cmd/phase/list_test.go similarity index 95% rename from cmd/phase/plan_test.go rename to cmd/phase/list_test.go index a0e8aae6f..190e04ea6 100644 --- a/cmd/phase/plan_test.go +++ b/cmd/phase/list_test.go @@ -26,7 +26,7 @@ func TestNewPlanCommand(t *testing.T) { { Name: "phase-plan-cmd-with-help", CmdLine: "--help", - Cmd: phase.NewPlanCommand(nil), + Cmd: phase.NewListCommand(nil), }, } for _, testcase := range tests { diff --git a/cmd/phase/phase.go b/cmd/phase/phase.go index 8ee75db2d..b8155e1ce 100644 --- a/cmd/phase/phase.go +++ b/cmd/phase/phase.go @@ -36,7 +36,7 @@ func NewPhaseCommand(cfgFactory config.Factory) *cobra.Command { } phaseRootCmd.AddCommand(NewRenderCommand(cfgFactory)) - phaseRootCmd.AddCommand(NewPlanCommand(cfgFactory)) + phaseRootCmd.AddCommand(NewListCommand(cfgFactory)) phaseRootCmd.AddCommand(NewRunCommand(cfgFactory)) phaseRootCmd.AddCommand(NewTreeCommand(cfgFactory)) diff --git a/cmd/phase/testdata/TestNewPlanCommandGoldenOutput/phase-plan-cmd-with-help.golden b/cmd/phase/testdata/TestNewPlanCommandGoldenOutput/phase-plan-cmd-with-help.golden index 2c0c133d9..e4c7d300c 100644 --- a/cmd/phase/testdata/TestNewPlanCommandGoldenOutput/phase-plan-cmd-with-help.golden +++ b/cmd/phase/testdata/TestNewPlanCommandGoldenOutput/phase-plan-cmd-with-help.golden @@ -6,4 +6,6 @@ Usage: list [flags] Flags: - -h, --help help for list + -c, --cluster-name string filter documents by cluster name + -h, --help help for list + --plan string Plan name of a plan diff --git a/docs/source/cli/airshipctl_phase_list.md b/docs/source/cli/airshipctl_phase_list.md index ba85107af..3c226e2e0 100644 --- a/docs/source/cli/airshipctl_phase_list.md +++ b/docs/source/cli/airshipctl_phase_list.md @@ -16,7 +16,9 @@ airshipctl phase list [flags] ### Options ``` - -h, --help help for list + -c, --cluster-name string filter documents by cluster name + -h, --help help for list + --plan string Plan name of a plan ``` ### Options inherited from parent commands diff --git a/pkg/phase/command.go b/pkg/phase/command.go index e1e6bc0b7..c79066816 100644 --- a/pkg/phase/command.go +++ b/pkg/phase/command.go @@ -76,8 +76,10 @@ func (c *RunCommand) RunE() error { // ListCommand phase list command type ListCommand struct { - Factory config.Factory - Writer io.Writer + Factory config.Factory + Writer io.Writer + ClusterName string + PlanID ifc.ID } // RunE runs a phase plan command @@ -92,7 +94,8 @@ func (c *ListCommand) RunE() error { return err } - phases, err := helper.ListPhases() + o := ifc.ListPhaseOptions{ClusterName: c.ClusterName, PlanID: c.PlanID} + phases, err := helper.ListPhases(o) if err != nil { return err } diff --git a/pkg/phase/helper.go b/pkg/phase/helper.go index fbe18d434..5f84377ba 100644 --- a/pkg/phase/helper.go +++ b/pkg/phase/helper.go @@ -15,6 +15,7 @@ package phase import ( + "errors" "path/filepath" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -25,6 +26,7 @@ import ( "opendev.org/airship/airshipctl/pkg/document" "opendev.org/airship/airshipctl/pkg/inventory" inventoryifc "opendev.org/airship/airshipctl/pkg/inventory/ifc" + "opendev.org/airship/airshipctl/pkg/log" "opendev.org/airship/airshipctl/pkg/phase/ifc" "opendev.org/airship/airshipctl/pkg/util" ) @@ -124,7 +126,7 @@ func (helper *Helper) Plan(planID ifc.ID) (*v1alpha1.PhasePlan, error) { } // ListPhases returns all phases associated with manifest -func (helper *Helper) ListPhases() ([]*v1alpha1.Phase, error) { +func (helper *Helper) ListPhases(o ifc.ListPhaseOptions) ([]*v1alpha1.Phase, error) { bundle, err := document.NewBundleByPath(helper.phaseBundleRoot) if err != nil { return nil, err @@ -136,12 +138,32 @@ func (helper *Helper) ListPhases() ([]*v1alpha1.Phase, error) { return nil, err } - docs, err := bundle.Select(selector) + bundle, err = bundle.SelectBundle(selector) if err != nil { return nil, err } - phases := []*v1alpha1.Phase{} + if o.ClusterName != "" { + if bundle, err = bundle.SelectByFieldValue("metadata.clusterName", func(v interface{}) bool { + if field, ok := v.(string); ok { + return field == o.ClusterName + } + return false + }); err != nil { + return nil, err + } + } + + var docs []document.Document + if o.PlanID.Name != "" { + if docs, err = helper.getDocsByPhasePlan(o.PlanID, bundle); err != nil { + return nil, err + } + } else if docs, err = bundle.GetAllDocuments(); err != nil { + return nil, err + } + + phases := make([]*v1alpha1.Phase, 0) for _, doc := range docs { p := v1alpha1.DefaultPhase() if err = doc.ToAPIObject(p, v1alpha1.Scheme); err != nil { @@ -152,6 +174,37 @@ func (helper *Helper) ListPhases() ([]*v1alpha1.Phase, error) { return phases, nil } +func (helper *Helper) getDocsByPhasePlan(planID ifc.ID, bundle document.Bundle) ([]document.Document, error) { + docs := make([]document.Document, 0) + plan, filterErr := helper.Plan(planID) + if filterErr != nil { + return nil, filterErr + } + for _, phaseStep := range plan.Phases { + p := &v1alpha1.Phase{ + ObjectMeta: v1.ObjectMeta{ + Name: phaseStep.Name, + }, + } + selector, filterErr := document.NewSelector().ByObject(p, v1alpha1.Scheme) + if filterErr != nil { + return nil, filterErr + } + + doc, filterErr := bundle.SelectOne(selector) + if filterErr != nil { + if errors.As(filterErr, &document.ErrDocNotFound{}) { + log.Debug(filterErr.Error()) + continue + } + return nil, filterErr + } + + docs = append(docs, doc) + } + return docs, nil +} + // ListPlans returns all phases associated with manifest func (helper *Helper) ListPlans() ([]*v1alpha1.PhasePlan, error) { bundle, err := document.NewBundleByPath(helper.phaseBundleRoot) diff --git a/pkg/phase/helper_test.go b/pkg/phase/helper_test.go index 0ff694fd9..438767970 100644 --- a/pkg/phase/helper_test.go +++ b/pkg/phase/helper_test.go @@ -186,6 +186,7 @@ func TestHelperListPhases(t *testing.T) { errContains string phaseLen int config func(t *testing.T) *config.Config + options ifc.ListPhaseOptions }{ { name: "Success phase list", @@ -210,6 +211,25 @@ func TestHelperListPhases(t *testing.T) { }, phaseLen: 0, }, + { + name: "Success with cluster name and phase plan", + config: func(t *testing.T) *config.Config { + conf := testConfig(t) + return conf + }, + options: ifc.ListPhaseOptions{ClusterName: "some_cluster", PlanID: ifc.ID{Name: "phasePlan"}}, + phaseLen: 1, + }, + { + name: "Invalid phase plan name", + config: func(t *testing.T) *config.Config { + conf := testConfig(t) + return conf + }, + options: ifc.ListPhaseOptions{PlanID: ifc.ID{Name: "NonExistentPlan"}}, + phaseLen: 0, + errContains: "found no documents", + }, } for _, test := range testCases { @@ -219,7 +239,7 @@ func TestHelperListPhases(t *testing.T) { require.NoError(t, err) require.NotNil(t, helper) - actualList, actualErr := helper.ListPhases() + actualList, actualErr := helper.ListPhases(tt.options) if tt.errContains != "" { require.Error(t, actualErr) assert.Contains(t, actualErr.Error(), tt.errContains) diff --git a/pkg/phase/ifc/helper.go b/pkg/phase/ifc/helper.go index c76abcbc4..22a5adab5 100644 --- a/pkg/phase/ifc/helper.go +++ b/pkg/phase/ifc/helper.go @@ -29,7 +29,7 @@ type Helper interface { WorkDir() (string, error) Phase(phaseID ID) (*v1alpha1.Phase, error) Plan(planID ID) (*v1alpha1.PhasePlan, error) - ListPhases() ([]*v1alpha1.Phase, error) + ListPhases(o ListPhaseOptions) ([]*v1alpha1.Phase, error) ListPlans() ([]*v1alpha1.PhasePlan, error) ClusterMapAPIobj() (*v1alpha1.ClusterMap, error) ClusterMap() (clustermap.ClusterMap, error) diff --git a/pkg/phase/ifc/phase.go b/pkg/phase/ifc/phase.go index 33372c6b5..5df30c668 100644 --- a/pkg/phase/ifc/phase.go +++ b/pkg/phase/ifc/phase.go @@ -43,6 +43,12 @@ type ID struct { Namespace string } +// ListPhaseOptions is used to filter phases +type ListPhaseOptions struct { + ClusterName string + PlanID ID +} + // Client is a phase client that can be used by command line or ui packages type Client interface { PhaseByID(ID) (Phase, error) diff --git a/pkg/phase/testdata/valid_site/phases/some_phase.yaml b/pkg/phase/testdata/valid_site/phases/some_phase.yaml index 6faa17daf..9d6d30a5c 100644 --- a/pkg/phase/testdata/valid_site/phases/some_phase.yaml +++ b/pkg/phase/testdata/valid_site/phases/some_phase.yaml @@ -2,6 +2,7 @@ apiVersion: airshipit.org/v1alpha1 kind: Phase metadata: name: some_phase + clusterName: some_cluster config: executorRef: apiVersion: airshipit.org/v1alpha1