github.com/HashDataInc/packer@v1.3.2/helper/multistep/debug_runner.go (about) 1 package multistep 2 3 import ( 4 "context" 5 "fmt" 6 "reflect" 7 "sync" 8 ) 9 10 // DebugLocation is the location where the pause is occurring when debugging 11 // a step sequence. "DebugLocationAfterRun" is after the run of the named 12 // step. "DebugLocationBeforeCleanup" is before the cleanup of the named 13 // step. 14 type DebugLocation uint 15 16 const ( 17 DebugLocationAfterRun DebugLocation = iota 18 DebugLocationBeforeCleanup 19 ) 20 21 // StepWrapper is an interface that wrapped steps can implement to expose their 22 // inner step names to the debug runner. 23 type StepWrapper interface { 24 // InnerStepName should return the human readable name of the wrapped step. 25 InnerStepName() string 26 } 27 28 // DebugPauseFn is the type signature for the function that is called 29 // whenever the DebugRunner pauses. It allows the caller time to 30 // inspect the state of the multi-step sequence at a given step. 31 type DebugPauseFn func(DebugLocation, string, StateBag) 32 33 // DebugRunner is a Runner that runs the given set of steps in order, 34 // but pauses between each step until it is told to continue. 35 type DebugRunner struct { 36 // Steps is the steps to run. These will be run in order. 37 Steps []Step 38 39 // PauseFn is the function that is called whenever the debug runner 40 // pauses. The debug runner continues when this function returns. 41 // The function is given the state so that the state can be inspected. 42 PauseFn DebugPauseFn 43 44 l sync.Mutex 45 runner *BasicRunner 46 } 47 48 func (r *DebugRunner) Run(state StateBag) { 49 r.l.Lock() 50 if r.runner != nil { 51 panic("already running") 52 } 53 r.runner = new(BasicRunner) 54 r.l.Unlock() 55 56 pauseFn := r.PauseFn 57 58 // If no PauseFn is specified, use the default 59 if pauseFn == nil { 60 pauseFn = DebugPauseDefault 61 } 62 63 // Rebuild the steps so that we insert the pause step after each 64 steps := make([]Step, len(r.Steps)*2) 65 for i, step := range r.Steps { 66 steps[i*2] = step 67 name := "" 68 if wrapped, ok := step.(StepWrapper); ok { 69 name = wrapped.InnerStepName() 70 } else { 71 name = reflect.Indirect(reflect.ValueOf(step)).Type().Name() 72 } 73 steps[(i*2)+1] = &debugStepPause{ 74 name, 75 pauseFn, 76 } 77 } 78 79 // Then just use a basic runner to run it 80 r.runner.Steps = steps 81 r.runner.Run(state) 82 } 83 84 func (r *DebugRunner) Cancel() { 85 r.l.Lock() 86 defer r.l.Unlock() 87 88 if r.runner != nil { 89 r.runner.Cancel() 90 } 91 } 92 93 // DebugPauseDefault is the default pause function when using the 94 // DebugRunner if no PauseFn is specified. It outputs some information 95 // to stderr about the step and waits for keyboard input on stdin before 96 // continuing. 97 func DebugPauseDefault(loc DebugLocation, name string, state StateBag) { 98 var locationString string 99 switch loc { 100 case DebugLocationAfterRun: 101 locationString = "after run of" 102 case DebugLocationBeforeCleanup: 103 locationString = "before cleanup of" 104 } 105 106 fmt.Printf("Pausing %s step '%s'. Press any key to continue.\n", locationString, name) 107 108 var line string 109 fmt.Scanln(&line) 110 } 111 112 type debugStepPause struct { 113 StepName string 114 PauseFn DebugPauseFn 115 } 116 117 func (s *debugStepPause) Run(_ context.Context, state StateBag) StepAction { 118 s.PauseFn(DebugLocationAfterRun, s.StepName, state) 119 return ActionContinue 120 } 121 122 func (s *debugStepPause) Cleanup(state StateBag) { 123 s.PauseFn(DebugLocationBeforeCleanup, s.StepName, state) 124 }