220 lines
5.1 KiB
Go
220 lines
5.1 KiB
Go
package commonsteps
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"reflect"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/hashicorp/packer/helper/multistep"
|
|
"github.com/hashicorp/packer/packer"
|
|
"github.com/hashicorp/packer/packer-plugin-sdk/common"
|
|
)
|
|
|
|
func newRunner(steps []multistep.Step, config common.PackerConfig, ui packer.Ui) (multistep.Runner, multistep.DebugPauseFn) {
|
|
switch config.PackerOnError {
|
|
case "", "cleanup":
|
|
case "abort":
|
|
for i, step := range steps {
|
|
steps[i] = abortStep{
|
|
step: step,
|
|
cleanupProv: false,
|
|
ui: ui,
|
|
}
|
|
}
|
|
case "ask":
|
|
for i, step := range steps {
|
|
steps[i] = askStep{step, ui}
|
|
}
|
|
case "run-cleanup-provisioner":
|
|
for i, step := range steps {
|
|
steps[i] = abortStep{
|
|
step: step,
|
|
cleanupProv: true,
|
|
ui: ui,
|
|
}
|
|
}
|
|
}
|
|
|
|
if config.PackerDebug {
|
|
pauseFn := MultistepDebugFn(ui)
|
|
return &multistep.DebugRunner{Steps: steps, PauseFn: pauseFn}, pauseFn
|
|
} else {
|
|
return &multistep.BasicRunner{Steps: steps}, nil
|
|
}
|
|
}
|
|
|
|
// NewRunner returns a multistep.Runner that runs steps augmented with support
|
|
// for -debug and -on-error command line arguments.
|
|
func NewRunner(steps []multistep.Step, config common.PackerConfig, ui packer.Ui) multistep.Runner {
|
|
runner, _ := newRunner(steps, config, ui)
|
|
return runner
|
|
}
|
|
|
|
// NewRunnerWithPauseFn returns a multistep.Runner that runs steps augmented
|
|
// with support for -debug and -on-error command line arguments. With -debug it
|
|
// puts the multistep.DebugPauseFn that will pause execution between steps into
|
|
// the state under the key "pauseFn".
|
|
func NewRunnerWithPauseFn(steps []multistep.Step, config common.PackerConfig, ui packer.Ui, state multistep.StateBag) multistep.Runner {
|
|
runner, pauseFn := newRunner(steps, config, ui)
|
|
if pauseFn != nil {
|
|
state.Put("pauseFn", pauseFn)
|
|
}
|
|
return runner
|
|
}
|
|
|
|
func typeName(i interface{}) string {
|
|
return reflect.Indirect(reflect.ValueOf(i)).Type().Name()
|
|
}
|
|
|
|
type abortStep struct {
|
|
step multistep.Step
|
|
cleanupProv bool
|
|
ui packer.Ui
|
|
}
|
|
|
|
func (s abortStep) InnerStepName() string {
|
|
return typeName(s.step)
|
|
}
|
|
|
|
func (s abortStep) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
return s.step.Run(ctx, state)
|
|
}
|
|
|
|
func (s abortStep) Cleanup(state multistep.StateBag) {
|
|
if s.InnerStepName() == typeName(StepProvision{}) && s.cleanupProv {
|
|
s.step.Cleanup(state)
|
|
return
|
|
}
|
|
|
|
shouldCleanup := handleAbortsAndInterupts(state, s.ui, typeName(s.step))
|
|
if !shouldCleanup {
|
|
return
|
|
}
|
|
s.step.Cleanup(state)
|
|
}
|
|
|
|
type askStep struct {
|
|
step multistep.Step
|
|
ui packer.Ui
|
|
}
|
|
|
|
func (s askStep) InnerStepName() string {
|
|
return typeName(s.step)
|
|
}
|
|
|
|
func (s askStep) Run(ctx context.Context, state multistep.StateBag) (action multistep.StepAction) {
|
|
for {
|
|
action = s.step.Run(ctx, state)
|
|
|
|
if action != multistep.ActionHalt {
|
|
return
|
|
}
|
|
|
|
err, ok := state.GetOk("error")
|
|
if ok {
|
|
s.ui.Error(fmt.Sprintf("%s", err))
|
|
}
|
|
|
|
switch ask(s.ui, typeName(s.step), state) {
|
|
case askCleanup:
|
|
return
|
|
case askAbort:
|
|
state.Put("aborted", true)
|
|
return
|
|
case askRetry:
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s askStep) Cleanup(state multistep.StateBag) {
|
|
if _, ok := state.GetOk("aborted"); ok {
|
|
shouldCleanup := handleAbortsAndInterupts(state, s.ui, typeName(s.step))
|
|
if !shouldCleanup {
|
|
return
|
|
}
|
|
}
|
|
s.step.Cleanup(state)
|
|
}
|
|
|
|
type askResponse int
|
|
|
|
const (
|
|
askCleanup askResponse = iota
|
|
askAbort
|
|
askRetry
|
|
)
|
|
|
|
func ask(ui packer.Ui, name string, state multistep.StateBag) askResponse {
|
|
ui.Say(fmt.Sprintf("Step %q failed", name))
|
|
|
|
result := make(chan askResponse)
|
|
go func() {
|
|
result <- askPrompt(ui)
|
|
}()
|
|
|
|
for {
|
|
select {
|
|
case response := <-result:
|
|
return response
|
|
case <-time.After(100 * time.Millisecond):
|
|
if _, ok := state.GetOk(multistep.StateCancelled); ok {
|
|
return askCleanup
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func askPrompt(ui packer.Ui) askResponse {
|
|
for {
|
|
line, err := ui.Ask("[c] Clean up and exit, [a] abort without cleanup, or [r] retry step (build may fail even if retry succeeds)?")
|
|
if err != nil {
|
|
log.Printf("Error asking for input: %s", err)
|
|
}
|
|
|
|
input := strings.ToLower(line) + "c"
|
|
switch input[0] {
|
|
case 'c':
|
|
return askCleanup
|
|
case 'a':
|
|
return askAbort
|
|
case 'r':
|
|
return askRetry
|
|
}
|
|
ui.Say(fmt.Sprintf("Incorrect input: %#v", line))
|
|
}
|
|
}
|
|
|
|
func handleAbortsAndInterupts(state multistep.StateBag, ui packer.Ui, stepName string) bool {
|
|
// if returns false, don't run cleanup. If true, do run cleanup.
|
|
_, alreadyLogged := state.GetOk("abort_step_logged")
|
|
|
|
err, ok := state.GetOk("error")
|
|
if ok && !alreadyLogged {
|
|
ui.Error(fmt.Sprintf("%s", err))
|
|
state.Put("abort_step_logged", true)
|
|
}
|
|
if _, ok := state.GetOk(multistep.StateCancelled); ok {
|
|
if !alreadyLogged {
|
|
ui.Error("Interrupted, aborting...")
|
|
state.Put("abort_step_logged", true)
|
|
} else {
|
|
ui.Error(fmt.Sprintf("aborted: skipping cleanup of step %q", stepName))
|
|
}
|
|
return false
|
|
}
|
|
if _, ok := state.GetOk(multistep.StateHalted); ok {
|
|
if !alreadyLogged {
|
|
ui.Error(fmt.Sprintf("Step %q failed, aborting...", stepName))
|
|
state.Put("abort_step_logged", true)
|
|
} else {
|
|
ui.Error(fmt.Sprintf("aborted: skipping cleanup of step %q", stepName))
|
|
}
|
|
return false
|
|
}
|
|
return true
|
|
}
|