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  }