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