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 <raliev@mirantis.com>
This commit is contained in:
Ruslan Aliev 2021-02-04 14:27:06 -06:00
parent a527b7f1f5
commit d1c7913ed3
11 changed files with 120 additions and 14 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,6 +2,7 @@ apiVersion: airshipit.org/v1alpha1
kind: Phase
metadata:
name: some_phase
clusterName: some_cluster
config:
executorRef:
apiVersion: airshipit.org/v1alpha1