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 }