github.com/mmcquillan/packer@v1.1.1-0.20171009221028-c85cf0483a5d/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/hashicorp/packer/packer"
    12  	"github.com/mitchellh/multistep"
    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) InnerStepName() string {
    65  	return typeName(s.step)
    66  }
    67  
    68  func (s abortStep) Run(state multistep.StateBag) multistep.StepAction {
    69  	return s.step.Run(state)
    70  }
    71  
    72  func (s abortStep) Cleanup(state multistep.StateBag) {
    73  	if _, ok := state.GetOk(multistep.StateCancelled); ok {
    74  		s.ui.Error("Interrupted, aborting...")
    75  		os.Exit(1)
    76  	}
    77  	if _, ok := state.GetOk(multistep.StateHalted); ok {
    78  		s.ui.Error(fmt.Sprintf("Step %q failed, aborting...", typeName(s.step)))
    79  		os.Exit(1)
    80  	}
    81  	s.step.Cleanup(state)
    82  }
    83  
    84  type askStep struct {
    85  	step multistep.Step
    86  	ui   packer.Ui
    87  }
    88  
    89  func (s askStep) InnerStepName() string {
    90  	return typeName(s.step)
    91  }
    92  
    93  func (s askStep) Run(state multistep.StateBag) (action multistep.StepAction) {
    94  	for {
    95  		action = s.step.Run(state)
    96  
    97  		if action != multistep.ActionHalt {
    98  			return
    99  		}
   100  
   101  		switch ask(s.ui, typeName(s.step), state) {
   102  		case askCleanup:
   103  			return
   104  		case askAbort:
   105  			os.Exit(1)
   106  		case askRetry:
   107  			continue
   108  		}
   109  	}
   110  }
   111  
   112  func (s askStep) Cleanup(state multistep.StateBag) {
   113  	s.step.Cleanup(state)
   114  }
   115  
   116  type askResponse int
   117  
   118  const (
   119  	askCleanup askResponse = iota
   120  	askAbort
   121  	askRetry
   122  )
   123  
   124  func ask(ui packer.Ui, name string, state multistep.StateBag) askResponse {
   125  	ui.Say(fmt.Sprintf("Step %q failed", name))
   126  
   127  	result := make(chan askResponse)
   128  	go func() {
   129  		result <- askPrompt(ui)
   130  	}()
   131  
   132  	for {
   133  		select {
   134  		case response := <-result:
   135  			return response
   136  		case <-time.After(100 * time.Millisecond):
   137  			if _, ok := state.GetOk(multistep.StateCancelled); ok {
   138  				return askCleanup
   139  			}
   140  		}
   141  	}
   142  }
   143  
   144  func askPrompt(ui packer.Ui) askResponse {
   145  	for {
   146  		line, err := ui.Ask("[c] Clean up and exit, [a] abort without cleanup, or [r] retry step (build may fail even if retry succeeds)?")
   147  		if err != nil {
   148  			log.Printf("Error asking for input: %s", err)
   149  		}
   150  
   151  		input := strings.ToLower(line) + "c"
   152  		switch input[0] {
   153  		case 'c':
   154  			return askCleanup
   155  		case 'a':
   156  			return askAbort
   157  		case 'r':
   158  			return askRetry
   159  		}
   160  		ui.Say(fmt.Sprintf("Incorrect input: %#v", line))
   161  	}
   162  }