From 178b0eff3e171dfdbf8b06b18a8133d531ba367a Mon Sep 17 00:00:00 2001 From: Dmitry Ukov Date: Wed, 9 Dec 2020 17:38:40 +0400 Subject: [PATCH] Implement plan run command Change-Id: Ie627ce670cd2b19d6999dc7c7a7a6dc12b25cace Closes: #395 --- cmd/plan/run.go | 31 +++++++- .../plan-run-with-help.golden | 5 +- docs/source/cli/airshipctl_plan_run.md | 5 +- .../source/providers/cluster_api_openstack.md | 16 ++-- manifests/phases/plan.yaml | 22 +++--- manifests/site/az-test-site/phases/plan.yaml | 16 ++-- .../site/docker-test-site/phases/plan.yaml | 14 ++-- manifests/site/gcp-test-site/phases/plan.yaml | 16 ++-- .../site/openstack-test-site/phases/plan.yaml | 16 ++-- pkg/api/v1alpha1/phaseplan_types.go | 15 +--- pkg/api/v1alpha1/zz_generated.deepcopy.go | 60 +++++---------- pkg/phase/client.go | 40 ++++++++++ pkg/phase/client_test.go | 50 ++++++++++++- pkg/phase/command.go | 45 ++++++++++- pkg/phase/command_test.go | 74 +++++++++++++++++++ pkg/phase/helper.go | 9 ++- pkg/phase/helper_test.go | 40 +++++----- pkg/phase/ifc/helper.go | 2 +- pkg/phase/ifc/phase.go | 7 ++ pkg/phase/testdata/phases/phaseplan.yaml | 6 +- .../testdata/valid_site/phases/phaseplan.yaml | 28 +++++-- 21 files changed, 360 insertions(+), 157 deletions(-) diff --git a/cmd/plan/run.go b/cmd/plan/run.go index 6bec3c759..e8d02cdaf 100644 --- a/cmd/plan/run.go +++ b/cmd/plan/run.go @@ -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,14 +29,37 @@ Run life-cycle phase plan which was defined in document model. // NewRunCommand creates a command which execute a particular phase plan func NewRunCommand(cfgFactory config.Factory) *cobra.Command { - listCmd := &cobra.Command{ + r := &phase.PlanRunCommand{ + Factory: cfgFactory, + Options: phase.PlanRunFlags{}, + } + runCmd := &cobra.Command{ Use: "run PLAN_NAME", Short: "Run plan", Long: runLong[1:], Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - return errors.ErrNotImplemented{What: "airshipctl plan run"} + r.Options.PlanID.Name = args[0] + return r.RunE() }, } - return listCmd + + flags := runCmd.Flags() + flags.BoolVar( + &r.Options.DryRun, + "dry-run", + false, + "simulate phase execution") + flags.DurationVar( + &r.Options.Timeout, + "wait-timeout", + 0, + "wait timeout") + flags.StringVar( + &r.Options.Kubeconfig, + "kubeconfig", + "", + "Path to kubeconfig associated with site being managed") + + return runCmd } diff --git a/cmd/plan/testdata/TestNewRunCommandGoldenOutput/plan-run-with-help.golden b/cmd/plan/testdata/TestNewRunCommandGoldenOutput/plan-run-with-help.golden index 96a31105e..de585572d 100644 --- a/cmd/plan/testdata/TestNewRunCommandGoldenOutput/plan-run-with-help.golden +++ b/cmd/plan/testdata/TestNewRunCommandGoldenOutput/plan-run-with-help.golden @@ -4,4 +4,7 @@ Usage: run PLAN_NAME [flags] Flags: - -h, --help help for run + --dry-run simulate phase execution + -h, --help help for run + --kubeconfig string Path to kubeconfig associated with site being managed + --wait-timeout duration wait timeout diff --git a/docs/source/cli/airshipctl_plan_run.md b/docs/source/cli/airshipctl_plan_run.md index 5d8dee53c..345c6c2b6 100644 --- a/docs/source/cli/airshipctl_plan_run.md +++ b/docs/source/cli/airshipctl_plan_run.md @@ -14,7 +14,10 @@ airshipctl plan run PLAN_NAME [flags] ### Options ``` - -h, --help help for run + --dry-run simulate phase execution + -h, --help help for run + --kubeconfig string Path to kubeconfig associated with site being managed + --wait-timeout duration wait timeout ``` ### Options inherited from parent commands diff --git a/docs/source/providers/cluster_api_openstack.md b/docs/source/providers/cluster_api_openstack.md index f4f07e502..c62c81781 100755 --- a/docs/source/providers/cluster_api_openstack.md +++ b/docs/source/providers/cluster_api_openstack.md @@ -788,15 +788,13 @@ apiVersion: airshipit.org/v1alpha1 kind: PhasePlan metadata: name: phasePlan -phaseGroups: - - name: group1 - phases: - - name: clusterctl-init-ephemeral - - name: controlplane-ephemeral - - name: initinfra-target - - name: clusterctl-init-target - - name: clusterctl-move - - name: workers-target +phases: + - name: clusterctl-init-ephemeral + - name: controlplane-ephemeral + - name: initinfra-target + - name: clusterctl-init-target + - name: clusterctl-move + - name: workers-target ``` ### Cluster Templates diff --git a/manifests/phases/plan.yaml b/manifests/phases/plan.yaml index 26c16b92f..fb531f88f 100644 --- a/manifests/phases/plan.yaml +++ b/manifests/phases/plan.yaml @@ -3,15 +3,13 @@ kind: PhasePlan metadata: name: phasePlan description: "Default phase plan" -phaseGroups: - - name: group1 - phases: - - name: initinfra-ephemeral - - name: initinfra-networking-ephemeral - - name: clusterctl-init-ephemeral - - name: controlplane-ephemeral - - name: initinfra-target - - name: initinfra-networking-target - - name: workers-target - - name: workers-classification - - name: workload-target +phases: + - name: initinfra-ephemeral + - name: initinfra-networking-ephemeral + - name: clusterctl-init-ephemeral + - name: controlplane-ephemeral + - name: initinfra-target + - name: initinfra-networking-target + - name: workers-target + - name: workers-classification + - name: workload-target diff --git a/manifests/site/az-test-site/phases/plan.yaml b/manifests/site/az-test-site/phases/plan.yaml index d10c7d1c9..627df5fbc 100644 --- a/manifests/site/az-test-site/phases/plan.yaml +++ b/manifests/site/az-test-site/phases/plan.yaml @@ -2,15 +2,13 @@ apiVersion: airshipit.org/v1alpha1 kind: PhasePlan metadata: name: phasePlan -phaseGroups: - - name: group1 - phases: - - name: clusterctl-init-ephemeral - - name: controlplane-ephemeral - - name: initinfra-target - - name: clusterctl-init-target - - name: clusterctl-move - - name: workers-target +phases: + - name: clusterctl-init-ephemeral + - name: controlplane-ephemeral + - name: initinfra-target + - name: clusterctl-init-target + - name: clusterctl-move + - name: workers-target --- apiVersion: airshipit.org/v1alpha1 kind: Clusterctl diff --git a/manifests/site/docker-test-site/phases/plan.yaml b/manifests/site/docker-test-site/phases/plan.yaml index ceebf9779..5b8ed2d1d 100644 --- a/manifests/site/docker-test-site/phases/plan.yaml +++ b/manifests/site/docker-test-site/phases/plan.yaml @@ -2,11 +2,9 @@ apiVersion: airshipit.org/v1alpha1 kind: PhasePlan metadata: name: phasePlan -phaseGroups: - - name: group1 - phases: - - name: clusterctl-init-ephemeral - - name: controlplane-ephemeral - - name: clusterctl-init-target - - name: clusterctl-move - - name: workers-target +phases: + - name: clusterctl-init-ephemeral + - name: controlplane-ephemeral + - name: clusterctl-init-target + - name: clusterctl-move + - name: workers-target diff --git a/manifests/site/gcp-test-site/phases/plan.yaml b/manifests/site/gcp-test-site/phases/plan.yaml index 1bd8c40d7..77e7c2440 100644 --- a/manifests/site/gcp-test-site/phases/plan.yaml +++ b/manifests/site/gcp-test-site/phases/plan.yaml @@ -2,12 +2,10 @@ apiVersion: airshipit.org/v1alpha1 kind: PhasePlan metadata: name: phasePlan -phaseGroups: - - name: group1 - phases: - - name: clusterctl-init-ephemeral - - name: controlplane-ephemeral - - name: initinfra-networking-target - - name: clusterctl-init-target - - name: clusterctl-move - - name: workers-target +phases: + - name: clusterctl-init-ephemeral + - name: controlplane-ephemeral + - name: initinfra-networking-target + - name: clusterctl-init-target + - name: clusterctl-move + - name: workers-target diff --git a/manifests/site/openstack-test-site/phases/plan.yaml b/manifests/site/openstack-test-site/phases/plan.yaml index df6ac45c5..1d0583452 100644 --- a/manifests/site/openstack-test-site/phases/plan.yaml +++ b/manifests/site/openstack-test-site/phases/plan.yaml @@ -2,12 +2,10 @@ apiVersion: airshipit.org/v1alpha1 kind: PhasePlan metadata: name: phasePlan -phaseGroups: - - name: group1 - phases: - - name: clusterctl-init-ephemeral - - name: controlplane-ephemeral - - name: initinfra-target - - name: clusterctl-init-target - - name: clusterctl-move - - name: workers-target +phases: + - name: clusterctl-init-ephemeral + - name: controlplane-ephemeral + - name: initinfra-target + - name: clusterctl-init-target + - name: clusterctl-move + - name: workers-target diff --git a/pkg/api/v1alpha1/phaseplan_types.go b/pkg/api/v1alpha1/phaseplan_types.go index 5c7e2543e..577d49dbc 100644 --- a/pkg/api/v1alpha1/phaseplan_types.go +++ b/pkg/api/v1alpha1/phaseplan_types.go @@ -24,18 +24,11 @@ import ( type PhasePlan struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Description string `json:"description,omitempty"` - PhaseGroups []PhaseGroup `json:"phaseGroups,omitempty"` + Description string `json:"description,omitempty"` + Phases []PhaseStep `json:"phases,omitempty"` } -// PhaseGroup represents set of phases (i.e. steps) executed sequentially. -// Phase groups are executed simultaneously -type PhaseGroup struct { - Name string `json:"name,omitempty"` - Phases []PhaseGroupStep `json:"phases,omitempty"` -} - -// PhaseGroupStep represents phase (or step) within phase group -type PhaseGroupStep struct { +// PhaseStep represents phase (or step) within a phase plan +type PhaseStep struct { Name string `json:"name,omitempty"` } diff --git a/pkg/api/v1alpha1/zz_generated.deepcopy.go b/pkg/api/v1alpha1/zz_generated.deepcopy.go index 707ff102d..19ad3bb43 100644 --- a/pkg/api/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/api/v1alpha1/zz_generated.deepcopy.go @@ -506,52 +506,15 @@ func (in *PhaseConfig) DeepCopy() *PhaseConfig { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PhaseGroup) DeepCopyInto(out *PhaseGroup) { - *out = *in - if in.Phases != nil { - in, out := &in.Phases, &out.Phases - *out = make([]PhaseGroupStep, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PhaseGroup. -func (in *PhaseGroup) DeepCopy() *PhaseGroup { - if in == nil { - return nil - } - out := new(PhaseGroup) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PhaseGroupStep) DeepCopyInto(out *PhaseGroupStep) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PhaseGroupStep. -func (in *PhaseGroupStep) DeepCopy() *PhaseGroupStep { - if in == nil { - return nil - } - out := new(PhaseGroupStep) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PhasePlan) DeepCopyInto(out *PhasePlan) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - if in.PhaseGroups != nil { - in, out := &in.PhaseGroups, &out.PhaseGroups - *out = make([]PhaseGroup, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } + if in.Phases != nil { + in, out := &in.Phases, &out.Phases + *out = make([]PhaseStep, len(*in)) + copy(*out, *in) } } @@ -573,6 +536,21 @@ func (in *PhasePlan) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PhaseStep) DeepCopyInto(out *PhaseStep) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PhaseStep. +func (in *PhaseStep) DeepCopy() *PhaseStep { + if in == nil { + return nil + } + out := new(PhaseStep) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Provider) DeepCopyInto(out *Provider) { *out = *in diff --git a/pkg/phase/client.go b/pkg/phase/client.go index 3c24668d2..3bd8af535 100644 --- a/pkg/phase/client.go +++ b/pkg/phase/client.go @@ -177,6 +177,34 @@ func (p *phase) Details() (string, error) { return "", nil } +var _ ifc.Plan = &plan{} + +type plan struct { + helper ifc.Helper + apiObj *v1alpha1.PhasePlan + phaseClient ifc.Client +} + +// Validate makes sure that phase plan is properly configured +// TODO implement this +func (p *plan) Validate() error { return nil } + +// Run function excutes Run method for each phase +func (p *plan) Run(ro ifc.RunOptions) error { + for _, step := range p.apiObj.Phases { + phaseRunner, err := p.phaseClient.PhaseByID(ifc.ID{Name: step.Name}) + if err != nil { + return err + } + + log.Printf("executing phase: %s\n", step) + if err = phaseRunner.Run(ro); err != nil { + return err + } + } + return nil +} + var _ ifc.Client = &client{} type client struct { @@ -245,6 +273,18 @@ func (c *client) PhaseByID(id ifc.ID) (ifc.Phase, error) { return phase, nil } +func (c *client) PlanByID(id ifc.ID) (ifc.Plan, error) { + planObj, err := c.Plan(id) + if err != nil { + return nil, err + } + return &plan{ + apiObj: planObj, + helper: c.Helper, + phaseClient: c, + }, nil +} + func (c *client) PhaseByAPIObj(phaseObj *v1alpha1.Phase) (ifc.Phase, error) { phase := &phase{ apiObj: phaseObj, diff --git a/pkg/phase/client_test.go b/pkg/phase/client_test.go index 698aeec24..5b09ca3ff 100644 --- a/pkg/phase/client_test.go +++ b/pkg/phase/client_test.go @@ -123,7 +123,7 @@ func TestPhaseRun(t *testing.T) { client := phase.NewClient(helper, phase.InjectRegistry(tt.registryFunc)) require.NotNil(t, client) p, err := client.PhaseByID(tt.phaseID) - require.NotNil(t, client) + require.NoError(t, err) err = p.Run(ifc.RunOptions{DryRun: true}) if tt.errContains != "" { require.Error(t, err) @@ -223,7 +223,7 @@ func TestBundleFactoryExecutor(t *testing.T) { require.NoError(t, err) require.NotNil(t, helper) - fakeRegistry := func() map[schema.GroupVersionKind]ifc.ExecutorFactory { + fakeReg := func() map[schema.GroupVersionKind]ifc.ExecutorFactory { validBundleFactory := schema.GroupVersionKind{ Group: "airshipit.org", Version: "v1alpha1", @@ -245,7 +245,7 @@ func TestBundleFactoryExecutor(t *testing.T) { invalidBundleFactory: bundleCheckFunc, } } - c := phase.NewClient(helper, phase.InjectRegistry(fakeRegistry)) + c := phase.NewClient(helper, phase.InjectRegistry(fakeReg)) p, err := c.PhaseByID(ifc.ID{Name: "capi_init"}) require.NoError(t, err) _, err = p.Executor() @@ -257,6 +257,50 @@ func TestBundleFactoryExecutor(t *testing.T) { assert.Equal(t, phase.ErrDocumentEntrypointNotDefined{PhaseName: "no_entry_point"}, err) } +func TestPlanRun(t *testing.T) { + testCases := []struct { + name string + errContains string + planID ifc.ID + configFunc func(t *testing.T) *config.Config + registryFunc phase.ExecutorRegistry + }{ + { + name: "Success fake executor", + configFunc: testConfig, + planID: ifc.ID{Name: "init"}, + registryFunc: fakeRegistry, + }, + { + name: "Error executor doc doesn't exist", + configFunc: testConfig, + planID: ifc.ID{Name: "some_plan"}, + registryFunc: fakeRegistry, + errContains: "found no documents", + }, + } + for _, tc := range testCases { + tt := tc + t.Run(tt.name, func(t *testing.T) { + conf := tt.configFunc(t) + helper, err := phase.NewHelper(conf) + require.NoError(t, err) + require.NotNil(t, helper) + client := phase.NewClient(helper, phase.InjectRegistry(tt.registryFunc)) + require.NotNil(t, client) + p, err := client.PlanByID(tt.planID) + require.NoError(t, err) + err = p.Run(ifc.RunOptions{DryRun: true}) + if tt.errContains != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.errContains) + } else { + require.NoError(t, err) + } + }) + } +} + func fakeExecFactory(config ifc.ExecutorConfig) (ifc.Executor, error) { return fakeExecutor{}, nil } diff --git a/pkg/phase/command.go b/pkg/phase/command.go index 1df54f841..e1e6bc0b7 100644 --- a/pkg/phase/command.go +++ b/pkg/phase/command.go @@ -32,15 +32,20 @@ import ( "opendev.org/airship/airshipctl/pkg/util" ) -// RunFlags options for phase run command -type RunFlags struct { +// GenericRunFlags generic options for run command +type GenericRunFlags struct { DryRun bool Timeout time.Duration - PhaseID ifc.ID Kubeconfig string Progress bool } +// RunFlags options for phase run command +type RunFlags struct { + GenericRunFlags + PhaseID ifc.ID +} + // RunCommand phase run command type RunCommand struct { Options RunFlags @@ -202,3 +207,37 @@ func (c *PlanListCommand) RunE() error { printer.PrintTable(rt, 0) return nil } + +// PlanRunFlags options for phase run command +type PlanRunFlags struct { + GenericRunFlags + PlanID ifc.ID +} + +// PlanRunCommand phase run command +type PlanRunCommand struct { + Options PlanRunFlags + Factory config.Factory +} + +// RunE executes phase plan +func (c *PlanRunCommand) RunE() error { + cfg, err := c.Factory() + if err != nil { + return err + } + + helper, err := NewHelper(cfg) + if err != nil { + return err + } + + kubeconfigOption := InjectKubeconfigPath(c.Options.Kubeconfig) + client := NewClient(helper, kubeconfigOption) + + plan, err := client.PlanByID(c.Options.PlanID) + if err != nil { + return err + } + return plan.Run(ifc.RunOptions{DryRun: c.Options.DryRun, Timeout: c.Options.Timeout, Progress: c.Options.Progress}) +} diff --git a/pkg/phase/command_test.go b/pkg/phase/command_test.go index 541a458c8..ae067fafd 100644 --- a/pkg/phase/command_test.go +++ b/pkg/phase/command_test.go @@ -293,3 +293,77 @@ func TestPlanListCommand(t *testing.T) { }) } } + +func TestPlanRunCommand(t *testing.T) { + testErr := fmt.Errorf(testFactoryErr) + testCases := []struct { + name string + factory config.Factory + expectedErr string + }{ + { + name: "Error config factory", + factory: func() (*config.Config, error) { + return nil, testErr + }, + expectedErr: testFactoryErr, + }, + { + name: "Error new helper", + factory: func() (*config.Config, error) { + return &config.Config{ + CurrentContext: "does not exist", + Contexts: make(map[string]*config.Context), + }, nil + }, + expectedErr: "Missing configuration: Context with name 'does not exist'", + }, + { + name: "Error phase by id", + factory: func() (*config.Config, error) { + conf := config.NewConfig() + conf.Manifests = map[string]*config.Manifest{ + "manifest": { + MetadataPath: "metadata.yaml", + TargetPath: "testdata", + PhaseRepositoryName: config.DefaultTestPhaseRepo, + Repositories: map[string]*config.Repository{ + config.DefaultTestPhaseRepo: { + URLString: "", + }, + }, + }, + } + conf.CurrentContext = "context" + conf.Contexts = map[string]*config.Context{ + "context": { + Manifest: "manifest", + }, + } + return conf, nil + }, + expectedErr: `Error events received on channel, errors are: +[document filtered by selector [Group="airshipit.org", Version="v1alpha1", Kind="KubeConfig"] found no documents]`, + }, + } + for _, tc := range testCases { + tt := tc + t.Run(tt.name, func(t *testing.T) { + cmd := phase.PlanRunCommand{ + Options: phase.PlanRunFlags{ + GenericRunFlags: phase.GenericRunFlags{ + DryRun: true, + }, + }, + Factory: tt.factory, + } + err := cmd.RunE() + if tt.expectedErr != "" { + require.Error(t, err) + assert.Equal(t, tt.expectedErr, err.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/pkg/phase/helper.go b/pkg/phase/helper.go index d6d79ff55..f5b60e900 100644 --- a/pkg/phase/helper.go +++ b/pkg/phase/helper.go @@ -90,13 +90,18 @@ func (helper *Helper) Phase(phaseID ifc.ID) (*v1alpha1.Phase, error) { } // Plan returns plan associated with a manifest -func (helper *Helper) Plan() (*v1alpha1.PhasePlan, error) { +func (helper *Helper) Plan(planID ifc.ID) (*v1alpha1.PhasePlan, error) { bundle, err := document.NewBundleByPath(helper.phaseBundleRoot) if err != nil { return nil, err } - plan := &v1alpha1.PhasePlan{} + plan := &v1alpha1.PhasePlan{ + ObjectMeta: v1.ObjectMeta{ + Name: planID.Name, + Namespace: planID.Namespace, + }, + } selector, err := document.NewSelector().ByObject(plan, v1alpha1.Scheme) if err != nil { return nil, err diff --git a/pkg/phase/helper_test.go b/pkg/phase/helper_test.go index c991213a0..6fc574b45 100644 --- a/pkg/phase/helper_test.go +++ b/pkg/phase/helper_test.go @@ -104,6 +104,7 @@ func TestHelperPhase(t *testing.T) { } func TestHelperPlan(t *testing.T) { + testPlanName := "phasePlan" testCases := []struct { name string errContains string @@ -119,28 +120,23 @@ func TestHelperPlan(t *testing.T) { APIVersion: "airshipit.org/v1alpha1", }, ObjectMeta: metav1.ObjectMeta{ - Name: "phasePlan", + Name: testPlanName, }, - PhaseGroups: []airshipv1.PhaseGroup{ + Phases: []airshipv1.PhaseStep{ { - Name: "group1", - Phases: []airshipv1.PhaseGroupStep{ - { - Name: "isogen", - }, - { - Name: "remotedirect", - }, - { - Name: "initinfra", - }, - { - Name: "some_phase", - }, - { - Name: "capi_init", - }, - }, + Name: "isogen", + }, + { + Name: "remotedirect", + }, + { + Name: "initinfra", + }, + { + Name: "some_phase", + }, + { + Name: "capi_init", }, }, }, @@ -172,7 +168,7 @@ func TestHelperPlan(t *testing.T) { require.NoError(t, err) require.NotNil(t, helper) - actualPlan, actualErr := helper.Plan() + actualPlan, actualErr := helper.Plan(ifc.ID{Name: testPlanName}) if tt.errContains != "" { require.Error(t, actualErr) assert.Contains(t, actualErr.Error(), tt.errContains) @@ -244,7 +240,7 @@ func TestHelperListPlans(t *testing.T) { }{ { name: "Success plan list", - expectedLen: 1, + expectedLen: 3, config: testConfig, }, { diff --git a/pkg/phase/ifc/helper.go b/pkg/phase/ifc/helper.go index f4409a99b..41b658530 100644 --- a/pkg/phase/ifc/helper.go +++ b/pkg/phase/ifc/helper.go @@ -27,7 +27,7 @@ type Helper interface { DocEntryPointPrefix() string WorkDir() (string, error) Phase(phaseID ID) (*v1alpha1.Phase, error) - Plan() (*v1alpha1.PhasePlan, error) + Plan(planID ID) (*v1alpha1.PhasePlan, error) ListPhases() ([]*v1alpha1.Phase, error) ListPlans() ([]*v1alpha1.PhasePlan, error) ClusterMapAPIobj() (*v1alpha1.ClusterMap, error) diff --git a/pkg/phase/ifc/phase.go b/pkg/phase/ifc/phase.go index 4429d0e2a..33372c6b5 100644 --- a/pkg/phase/ifc/phase.go +++ b/pkg/phase/ifc/phase.go @@ -31,6 +31,12 @@ type Phase interface { Render(io.Writer, bool, RenderOptions) error } +// Plan provides a way to interact with phase plans +type Plan interface { + Validate() error + Run(RunOptions) error +} + // ID uniquely identifies the phase type ID struct { Name string @@ -40,6 +46,7 @@ type ID struct { // Client is a phase client that can be used by command line or ui packages type Client interface { PhaseByID(ID) (Phase, error) + PlanByID(ID) (Plan, error) PhaseByAPIObj(*v1alpha1.Phase) (Phase, error) ClusterMap() (clustermap.ClusterMap, error) } diff --git a/pkg/phase/testdata/phases/phaseplan.yaml b/pkg/phase/testdata/phases/phaseplan.yaml index 414efb0d3..c6eba9cc5 100644 --- a/pkg/phase/testdata/phases/phaseplan.yaml +++ b/pkg/phase/testdata/phases/phaseplan.yaml @@ -3,7 +3,5 @@ kind: PhasePlan metadata: name: phasePlan description: "Default phase plan" -phaseGroups: - - name: group1 - phases: - - name: phase \ No newline at end of file +phases: + - name: phase diff --git a/pkg/phase/testdata/valid_site/phases/phaseplan.yaml b/pkg/phase/testdata/valid_site/phases/phaseplan.yaml index 8e07734e9..8d06a7d6c 100644 --- a/pkg/phase/testdata/valid_site/phases/phaseplan.yaml +++ b/pkg/phase/testdata/valid_site/phases/phaseplan.yaml @@ -2,11 +2,23 @@ apiVersion: airshipit.org/v1alpha1 kind: PhasePlan metadata: name: phasePlan -phaseGroups: - - name: group1 - phases: - - name: isogen - - name: remotedirect - - name: initinfra - - name: some_phase - - name: capi_init +phases: + - name: isogen + - name: remotedirect + - name: initinfra + - name: some_phase + - name: capi_init +--- +apiVersion: airshipit.org/v1alpha1 +kind: PhasePlan +metadata: + name: init +phases: + - name: capi_init +--- +apiVersion: airshipit.org/v1alpha1 +kind: PhasePlan +metadata: + name: some_plan +phases: + - name: some_phase