github.com/crossplane/upjet@v1.3.0/pkg/migration/plan_executor.go (about) 1 // SPDX-FileCopyrightText: 2023 The Crossplane Authors <https://crossplane.io> 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 5 package migration 6 7 import "github.com/pkg/errors" 8 9 const ( 10 // KeyContextDiagnostics is the executor step context key for 11 // storing any extra diagnostics information from 12 // the executor. 13 KeyContextDiagnostics = "diagnostics" 14 ) 15 16 // PlanExecutor drives the execution of a plan's steps and 17 // uses the configured `executors` to execute those steps. 18 type PlanExecutor struct { 19 executors []Executor 20 plan Plan 21 callback ExecutorCallback 22 } 23 24 // Action represents an action to be taken by the PlanExecutor. 25 // An Action is dictated by a ExecutorCallback implementation 26 // to the PlanExecutor for each step. 27 type Action int 28 29 const ( 30 // ActionContinue tells the PlanExecutor to continue with the execution 31 // of a Step. 32 ActionContinue Action = iota 33 // ActionSkip tells the PlanExecutor to skip the execution 34 // of the current Step. 35 ActionSkip 36 // ActionCancel tells the PlanExecutor to stop executing 37 // the Steps of a Plan. 38 ActionCancel 39 // ActionRepeat tells the PlanExecutor to repeat the execution 40 // of the current Step. 41 ActionRepeat 42 ) 43 44 // CallbackResult is the type of a value returned from one of the callback 45 // methods of ExecutorCallback implementations. 46 type CallbackResult struct { 47 Action Action 48 } 49 50 // PlanExecutorOption is a mutator function for setting an option of a 51 // PlanExecutor. 52 type PlanExecutorOption func(executor *PlanExecutor) 53 54 // WithExecutorCallback configures an ExecutorCallback for a PlanExecutor 55 // to be notified as the Plan's Step's are executed. 56 func WithExecutorCallback(cb ExecutorCallback) PlanExecutorOption { 57 return func(pe *PlanExecutor) { 58 pe.callback = cb 59 } 60 } 61 62 // ExecutorCallback is the interface for the callback implementations 63 // to be notified while executing each Step of a migration Plan. 64 type ExecutorCallback interface { 65 // StepToExecute is called just before a migration Plan's Step is executed. 66 // Can be used to cancel the execution of the Plan, or to continue/skip 67 // the Step's execution. 68 StepToExecute(s Step, index int) CallbackResult 69 // StepSucceeded is called after a migration Plan's Step is 70 // successfully executed. 71 // Can be used to cancel the execution of the Plan, or to 72 // continue/skip/repeat the Step's execution. 73 StepSucceeded(s Step, index int, diagnostics any) CallbackResult 74 // StepFailed is called after a migration Plan's Step has 75 // failed to execute. 76 // Can be used to cancel the execution of the Plan, or to 77 // continue/skip/repeat the Step's execution. 78 StepFailed(s Step, index int, diagnostics any, err error) CallbackResult 79 } 80 81 // NewPlanExecutor returns a new plan executor for executing the steps 82 // of a migration plan. 83 func NewPlanExecutor(plan Plan, executors []Executor, opts ...PlanExecutorOption) *PlanExecutor { 84 pe := &PlanExecutor{ 85 executors: executors, 86 plan: plan, 87 } 88 for _, o := range opts { 89 o(pe) 90 } 91 return pe 92 } 93 94 func (pe *PlanExecutor) Execute() error { //nolint:gocyclo // easier to follow this way 95 ctx := make(map[string]any) 96 for i := 0; i < len(pe.plan.Spec.Steps); i++ { 97 var r CallbackResult 98 if pe.callback != nil { 99 r = pe.callback.StepToExecute(pe.plan.Spec.Steps[i], i) 100 switch r.Action { 101 case ActionCancel: 102 return nil 103 case ActionSkip: 104 continue 105 case ActionContinue, ActionRepeat: 106 } 107 } 108 109 err := pe.executors[0].Step(pe.plan.Spec.Steps[i], ctx) 110 diag := ctx[KeyContextDiagnostics] 111 if err != nil { 112 if pe.callback != nil { 113 r = pe.callback.StepFailed(pe.plan.Spec.Steps[i], i, diag, err) 114 } 115 } else if pe.callback != nil { 116 r = pe.callback.StepSucceeded(pe.plan.Spec.Steps[i], i, diag) 117 } 118 119 switch r.Action { 120 case ActionCancel: 121 return errors.Wrapf(err, "failed to execute step %q at index %d", pe.plan.Spec.Steps[i].Name, i) 122 case ActionContinue, ActionSkip: 123 continue 124 case ActionRepeat: 125 i-- 126 } 127 } 128 return nil 129 }