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  }