From 89bd08e1026447d53d40aba0abf242943a94c3ae Mon Sep 17 00:00:00 2001 From: Ruslan Aliev Date: Wed, 10 Nov 2021 08:24:18 +0000 Subject: [PATCH] Add --resume-flag to plan run subcommand Allows to skip phases in plan execution. Change-Id: Iee0e0da5ae9836f27ec1810ac0d333247a6ddb8f Signed-off-by: Ruslan Aliev Closes: #667 --- cmd/plan/run.go | 5 ++++- .../plan-run-with-help.golden | 1 + docs/source/cli/plan/airshipctl_plan_run.rst | 1 + pkg/phase/client.go | 13 +++++++++++-- pkg/phase/client_test.go | 2 +- pkg/phase/command.go | 3 ++- pkg/phase/command_test.go | 6 ++++-- pkg/phase/ifc/executor.go | 6 ++++++ pkg/phase/ifc/phase.go | 2 +- 9 files changed, 31 insertions(+), 8 deletions(-) diff --git a/cmd/plan/run.go b/cmd/plan/run.go index 21c7c02c0..877dc86a4 100644 --- a/cmd/plan/run.go +++ b/cmd/plan/run.go @@ -39,7 +39,7 @@ Perform a dry run of a plan // NewRunCommand creates a command which execute a particular phase plan func NewRunCommand(cfgFactory config.Factory) *cobra.Command { r := &phase.PlanRunCommand{Factory: cfgFactory} - f := &phase.RunFlags{} + f := &phase.PlanRunFlags{} runCmd := &cobra.Command{ Use: "run PLAN_NAME", @@ -55,6 +55,8 @@ func NewRunCommand(cfgFactory config.Factory) *cobra.Command { r.Options.DryRun = f.DryRun case "wait-timeout": r.Options.Timeout = &f.Timeout + case "resume-from": + r.Options.ResumeFromPhase = f.ResumeFromPhase } } cmd.Flags().Visit(fn) @@ -63,6 +65,7 @@ func NewRunCommand(cfgFactory config.Factory) *cobra.Command { } flags := runCmd.Flags() + flags.StringVar(&f.ResumeFromPhase, "resume-from", "", "skip all phases before the specified one") flags.BoolVar(&f.DryRun, "dry-run", false, "simulate phase execution") flags.DurationVar(&f.Timeout, "wait-timeout", 0, "wait timeout") 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 b9906b492..1314a3f5e 100644 --- a/cmd/plan/testdata/TestNewRunCommandGoldenOutput/plan-run-with-help.golden +++ b/cmd/plan/testdata/TestNewRunCommandGoldenOutput/plan-run-with-help.golden @@ -16,4 +16,5 @@ Perform a dry run of a plan Flags: --dry-run simulate phase execution -h, --help help for run + --resume-from string skip all phases before the specified one --wait-timeout duration wait timeout diff --git a/docs/source/cli/plan/airshipctl_plan_run.rst b/docs/source/cli/plan/airshipctl_plan_run.rst index 8239af19d..311e2d1f1 100644 --- a/docs/source/cli/plan/airshipctl_plan_run.rst +++ b/docs/source/cli/plan/airshipctl_plan_run.rst @@ -37,6 +37,7 @@ Options --dry-run simulate phase execution -h, --help help for run + --resume-from string skip all phases before the specified one --wait-timeout duration wait timeout Options inherited from parent commands diff --git a/pkg/phase/client.go b/pkg/phase/client.go index 0468e9eeb..2299dafc5 100644 --- a/pkg/phase/client.go +++ b/pkg/phase/client.go @@ -270,7 +270,16 @@ func (p *plan) Validate() error { } // Run function executes Run method for each phase -func (p *plan) Run(ro ifc.RunOptions) error { +func (p *plan) Run(ro ifc.PlanRunOptions) error { + if ro.ResumeFromPhase != "" { + for i, phase := range p.apiObj.Phases { + if phase.Name == ro.ResumeFromPhase { + p.apiObj.Phases = p.apiObj.Phases[i:] + break + } + } + } + for _, step := range p.apiObj.Phases { phaseRunner, err := p.phaseClient.PhaseByID(ifc.ID{Name: step.Name}) if err != nil { @@ -278,7 +287,7 @@ func (p *plan) Run(ro ifc.RunOptions) error { } log.Printf("executing phase: %s\n", step.Name) - if err = phaseRunner.Run(ro); err != nil { + if err = phaseRunner.Run(ro.RunOptions); err != nil { return err } } diff --git a/pkg/phase/client_test.go b/pkg/phase/client_test.go index bc953f6aa..8bd966ced 100644 --- a/pkg/phase/client_test.go +++ b/pkg/phase/client_test.go @@ -436,7 +436,7 @@ func TestPlanRun(t *testing.T) { require.NotNil(t, client) p, err := client.PlanByID(tt.planID) require.NoError(t, err) - err = p.Run(ifc.RunOptions{DryRun: true}) + err = p.Run(ifc.PlanRunOptions{RunOptions: ifc.RunOptions{DryRun: true}}) if tt.errContains != "" { require.Error(t, err) assert.Contains(t, err.Error(), tt.errContains) diff --git a/pkg/phase/command.go b/pkg/phase/command.go index 78f471ddc..4f2fa16c7 100644 --- a/pkg/phase/command.go +++ b/pkg/phase/command.go @@ -202,12 +202,13 @@ func (c *PlanListCommand) RunE() error { // PlanRunFlags options for phase run command type PlanRunFlags struct { GenericRunFlags + ResumeFromPhase string } // PlanRunCommand phase run command type PlanRunCommand struct { PlanID ifc.ID - Options ifc.RunOptions + Options ifc.PlanRunOptions Factory config.Factory } diff --git a/pkg/phase/command_test.go b/pkg/phase/command_test.go index df26e912a..d49109aa9 100644 --- a/pkg/phase/command_test.go +++ b/pkg/phase/command_test.go @@ -482,8 +482,10 @@ func TestPlanRunCommand(t *testing.T) { tt := tc t.Run(tt.name, func(t *testing.T) { cmd := phase.PlanRunCommand{ - Options: ifc.RunOptions{ - DryRun: true, + Options: ifc.PlanRunOptions{ + RunOptions: ifc.RunOptions{ + DryRun: true, + }, }, Factory: tt.factory, PlanID: tt.planID, diff --git a/pkg/phase/ifc/executor.go b/pkg/phase/ifc/executor.go index 4de3d2719..e5c75112a 100644 --- a/pkg/phase/ifc/executor.go +++ b/pkg/phase/ifc/executor.go @@ -43,6 +43,12 @@ type RunOptions struct { Timeout *time.Duration } +// PlanRunOptions holds options for plan run method +type PlanRunOptions struct { + RunOptions + ResumeFromPhase string +} + // RenderOptions holds options for render method type RenderOptions struct { FilterSelector document.Selector diff --git a/pkg/phase/ifc/phase.go b/pkg/phase/ifc/phase.go index 827215fdc..2c6bd739f 100644 --- a/pkg/phase/ifc/phase.go +++ b/pkg/phase/ifc/phase.go @@ -40,7 +40,7 @@ type PhaseStatus struct { // Plan provides a way to interact with phase plans type Plan interface { Validate() error - Run(RunOptions) error + Run(PlanRunOptions) error Status(StatusOptions) (PlanStatus, error) }