github.com/rahart/packer@v0.12.2-0.20161229105310-282bb6ad370f/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  }