github.com/crossplane/upjet@v1.3.0/pkg/migration/fork_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 (
     8  	"os"
     9  
    10  	"github.com/crossplane/crossplane-runtime/pkg/logging"
    11  	"github.com/pkg/errors"
    12  	"k8s.io/utils/exec"
    13  )
    14  
    15  const (
    16  	errForkExecutorNotSupported = "step type should be Exec or step's manualExecution should be non-empty"
    17  	errStepFailedFmt            = "failed to execute the step %q"
    18  )
    19  
    20  var _ Executor = &forkExecutor{}
    21  
    22  // forkExecutor executes Exec steps or steps with the `manualExecution` hints
    23  // by forking processes.
    24  type forkExecutor struct {
    25  	executor exec.Interface
    26  	logger   logging.Logger
    27  	cwd      string
    28  }
    29  
    30  // ForkExecutorOption allows you to configure forkExecutor objects.
    31  type ForkExecutorOption func(executor *forkExecutor)
    32  
    33  // WithLogger sets the logger of forkExecutor.
    34  func WithLogger(l logging.Logger) ForkExecutorOption {
    35  	return func(e *forkExecutor) {
    36  		e.logger = l
    37  	}
    38  }
    39  
    40  // WithExecutor sets the executor of ForkExecutor.
    41  func WithExecutor(e exec.Interface) ForkExecutorOption {
    42  	return func(fe *forkExecutor) {
    43  		fe.executor = e
    44  	}
    45  }
    46  
    47  // WithWorkingDir sets the current working directory for the executor.
    48  func WithWorkingDir(dir string) ForkExecutorOption {
    49  	return func(e *forkExecutor) {
    50  		e.cwd = dir
    51  	}
    52  }
    53  
    54  // NewForkExecutor returns a new fork executor using a process forker.
    55  func NewForkExecutor(opts ...ForkExecutorOption) Executor {
    56  	fe := &forkExecutor{
    57  		executor: exec.New(),
    58  		logger:   logging.NewNopLogger(),
    59  	}
    60  	for _, f := range opts {
    61  		f(fe)
    62  	}
    63  	return fe
    64  }
    65  
    66  func (f forkExecutor) Init(_ map[string]any) error {
    67  	return nil
    68  }
    69  
    70  func (f forkExecutor) Step(s Step, ctx map[string]any) error {
    71  	var cmd exec.Cmd
    72  	switch {
    73  	case s.Type == StepTypeExec:
    74  		f.logger.Debug("Command to be executed", "command", s.Exec.Command, "args", s.Exec.Args)
    75  		return errors.Wrapf(f.exec(ctx, f.executor.Command(s.Exec.Command, s.Exec.Args...)), errStepFailedFmt, s.Name)
    76  	// TODO: we had better have separate executors to handle the other types of
    77  	// steps
    78  	case len(s.ManualExecution) != 0:
    79  		for _, c := range s.ManualExecution {
    80  			f.logger.Debug("Command to be executed", "command", "sh", "args", []string{"-c", c})
    81  			cmd = f.executor.Command("sh", "-c", c)
    82  			if err := f.exec(ctx, cmd); err != nil {
    83  				return errors.Wrapf(err, errStepFailedFmt, s.Name)
    84  			}
    85  		}
    86  		return nil
    87  	default:
    88  		return errors.Wrap(NewUnsupportedStepTypeError(s), errForkExecutorNotSupported)
    89  	}
    90  }
    91  
    92  func (f forkExecutor) exec(ctx map[string]any, cmd exec.Cmd) error {
    93  	cmd.SetEnv(os.Environ())
    94  	if f.cwd != "" {
    95  		cmd.SetDir(f.cwd)
    96  	}
    97  	buff, err := cmd.CombinedOutput()
    98  	logMsg := "Successfully executed command"
    99  	if err != nil {
   100  		logMsg = "Command execution failed"
   101  	}
   102  	f.logger.Debug(logMsg, "output", string(buff))
   103  	if ctx != nil {
   104  		ctx[KeyContextDiagnostics] = buff
   105  	}
   106  	return errors.Wrapf(err, "failed to execute command")
   107  }
   108  
   109  func (f forkExecutor) Destroy() error {
   110  	return nil
   111  }