github.com/StackPointCloud/packer@v0.10.2-0.20180716202532-b28098e0f79b/common/multistep_runner.go (about) 1 package common 2 3 import ( 4 "context" 5 "fmt" 6 "log" 7 "os" 8 "reflect" 9 "strings" 10 "time" 11 12 "github.com/hashicorp/packer/helper/multistep" 13 "github.com/hashicorp/packer/packer" 14 ) 15 16 func newRunner(steps []multistep.Step, config PackerConfig, ui packer.Ui) (multistep.Runner, multistep.DebugPauseFn) { 17 switch config.PackerOnError { 18 case "", "cleanup": 19 case "abort": 20 for i, step := range steps { 21 steps[i] = abortStep{step, ui} 22 } 23 case "ask": 24 for i, step := range steps { 25 steps[i] = askStep{step, ui} 26 } 27 } 28 29 if config.PackerDebug { 30 pauseFn := MultistepDebugFn(ui) 31 return &multistep.DebugRunner{Steps: steps, PauseFn: pauseFn}, pauseFn 32 } else { 33 return &multistep.BasicRunner{Steps: steps}, nil 34 } 35 } 36 37 // NewRunner returns a multistep.Runner that runs steps augmented with support 38 // for -debug and -on-error command line arguments. 39 func NewRunner(steps []multistep.Step, config PackerConfig, ui packer.Ui) multistep.Runner { 40 runner, _ := newRunner(steps, config, ui) 41 return runner 42 } 43 44 // NewRunnerWithPauseFn returns a multistep.Runner that runs steps augmented 45 // with support for -debug and -on-error command line arguments. With -debug it 46 // puts the multistep.DebugPauseFn that will pause execution between steps into 47 // the state under the key "pauseFn". 48 func NewRunnerWithPauseFn(steps []multistep.Step, config PackerConfig, ui packer.Ui, state multistep.StateBag) multistep.Runner { 49 runner, pauseFn := newRunner(steps, config, ui) 50 if pauseFn != nil { 51 state.Put("pauseFn", pauseFn) 52 } 53 return runner 54 } 55 56 func typeName(i interface{}) string { 57 return reflect.Indirect(reflect.ValueOf(i)).Type().Name() 58 } 59 60 type abortStep struct { 61 step multistep.Step 62 ui packer.Ui 63 } 64 65 func (s abortStep) InnerStepName() string { 66 return typeName(s.step) 67 } 68 69 func (s abortStep) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 70 return s.step.Run(ctx, state) 71 } 72 73 func (s abortStep) Cleanup(state multistep.StateBag) { 74 err, ok := state.GetOk("error") 75 if ok { 76 s.ui.Error(fmt.Sprintf("%s", err)) 77 } 78 if _, ok := state.GetOk(multistep.StateCancelled); ok { 79 s.ui.Error("Interrupted, aborting...") 80 os.Exit(1) 81 } 82 if _, ok := state.GetOk(multistep.StateHalted); ok { 83 s.ui.Error(fmt.Sprintf("Step %q failed, aborting...", typeName(s.step))) 84 os.Exit(1) 85 } 86 s.step.Cleanup(state) 87 } 88 89 type askStep struct { 90 step multistep.Step 91 ui packer.Ui 92 } 93 94 func (s askStep) InnerStepName() string { 95 return typeName(s.step) 96 } 97 98 func (s askStep) Run(ctx context.Context, state multistep.StateBag) (action multistep.StepAction) { 99 for { 100 action = s.step.Run(ctx, state) 101 102 if action != multistep.ActionHalt { 103 return 104 } 105 106 err, ok := state.GetOk("error") 107 if ok { 108 s.ui.Error(fmt.Sprintf("%s", err)) 109 } 110 111 switch ask(s.ui, typeName(s.step), state) { 112 case askCleanup: 113 return 114 case askAbort: 115 os.Exit(1) 116 case askRetry: 117 continue 118 } 119 } 120 } 121 122 func (s askStep) Cleanup(state multistep.StateBag) { 123 s.step.Cleanup(state) 124 } 125 126 type askResponse int 127 128 const ( 129 askCleanup askResponse = iota 130 askAbort 131 askRetry 132 ) 133 134 func ask(ui packer.Ui, name string, state multistep.StateBag) askResponse { 135 ui.Say(fmt.Sprintf("Step %q failed", name)) 136 137 result := make(chan askResponse) 138 go func() { 139 result <- askPrompt(ui) 140 }() 141 142 for { 143 select { 144 case response := <-result: 145 return response 146 case <-time.After(100 * time.Millisecond): 147 if _, ok := state.GetOk(multistep.StateCancelled); ok { 148 return askCleanup 149 } 150 } 151 } 152 } 153 154 func askPrompt(ui packer.Ui) askResponse { 155 for { 156 line, err := ui.Ask("[c] Clean up and exit, [a] abort without cleanup, or [r] retry step (build may fail even if retry succeeds)?") 157 if err != nil { 158 log.Printf("Error asking for input: %s", err) 159 } 160 161 input := strings.ToLower(line) + "c" 162 switch input[0] { 163 case 'c': 164 return askCleanup 165 case 'a': 166 return askAbort 167 case 'r': 168 return askRetry 169 } 170 ui.Say(fmt.Sprintf("Incorrect input: %#v", line)) 171 } 172 }