Merge pull request #3885 from orivej/on-error

Add -on-error command line argument to allow preserving artifacts on builder errors
This commit is contained in:
Rickard von Essen 2016-09-20 12:28:14 +02:00 committed by GitHub
commit 13c9db5dd5
25 changed files with 275 additions and 171 deletions

View File

@ -262,15 +262,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
) )
// Run! // Run!
if b.config.PackerDebug { b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner.Run(state) b.runner.Run(state)
// If there was an error, return that // If there was an error, return that

View File

@ -179,15 +179,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
} }
// Run! // Run!
if b.config.PackerDebug { b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner.Run(state) b.runner.Run(state)
// If there was an error, return that // If there was an error, return that

View File

@ -262,15 +262,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
} }
// Run! // Run!
if b.config.PackerDebug { b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner.Run(state) b.runner.Run(state)
// If there was an error, return that // If there was an error, return that

View File

@ -157,7 +157,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
ui.Message(fmt.Sprintf("temp admin password: '%s'", b.config.Password)) ui.Message(fmt.Sprintf("temp admin password: '%s'", b.config.Password))
} }
b.runner = b.createRunner(&steps, ui) b.runner = packerCommon.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(b.stateBag) b.runner.Run(b.stateBag)
// Report any errors. // Report any errors.
@ -198,19 +198,6 @@ func (b *Builder) Cancel() {
} }
} }
func (b *Builder) createRunner(steps *[]multistep.Step, ui packer.Ui) multistep.Runner {
if b.config.PackerDebug {
return &multistep.DebugRunner{
Steps: *steps,
PauseFn: packerCommon.MultistepDebugFn(ui),
}
}
return &multistep.BasicRunner{
Steps: *steps,
}
}
func (b *Builder) getBlobEndpoint(client *AzureClient, resourceGroupName string, storageAccountName string) (string, error) { func (b *Builder) getBlobEndpoint(client *AzureClient, resourceGroupName string, storageAccountName string) (string, error) {
account, err := client.AccountsClient.GetProperties(resourceGroupName, storageAccountName) account, err := client.AccountsClient.GetProperties(resourceGroupName, storageAccountName)
if err != nil { if err != nil {

View File

@ -73,15 +73,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
} }
// Run the steps // Run the steps
if b.config.PackerDebug { b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner.Run(state) b.runner.Run(state)
// If there was an error, return that // If there was an error, return that

View File

@ -78,15 +78,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
state.Put("driver", driver) state.Put("driver", driver)
// Run! // Run!
if b.config.PackerDebug { b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner.Run(state) b.runner.Run(state)
// If there was an error, return that // If there was an error, return that

View File

@ -73,14 +73,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
} }
// Run the steps. // Run the steps.
if b.config.PackerDebug { b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner.Run(state) b.runner.Run(state)
// Report any errors. // Report any errors.

View File

@ -46,15 +46,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
state.Put("ui", ui) state.Put("ui", ui)
// Run! // Run!
if b.config.PackerDebug { b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner.Run(state) b.runner.Run(state)
// If there was an error, return that // If there was an error, return that

View File

@ -116,15 +116,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
} }
// Run! // Run!
if b.config.PackerDebug { b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner.Run(state) b.runner.Run(state)
// If there was an error, return that // If there was an error, return that

View File

@ -215,17 +215,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
state.Put("ui", ui) state.Put("ui", ui)
// Run // Run
if b.config.PackerDebug { b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
pauseFn := common.MultistepDebugFn(ui)
state.Put("pauseFn", pauseFn)
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: pauseFn,
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner.Run(state) b.runner.Run(state)
// If there was an error, return that // If there was an error, return that

View File

@ -108,16 +108,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
} }
// Run the steps. // Run the steps.
if b.config.PackerDebug { b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
pauseFn := common.MultistepDebugFn(ui)
state.Put("pauseFn", pauseFn)
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: pauseFn,
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner.Run(state) b.runner.Run(state)
// Report any errors. // Report any errors.

View File

@ -422,17 +422,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
state.Put("ui", ui) state.Put("ui", ui)
// Run // Run
if b.config.PackerDebug { b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
pauseFn := common.MultistepDebugFn(ui)
state.Put("pauseFn", pauseFn)
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: pauseFn,
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner.Run(state) b.runner.Run(state)
// If there was an error, return that // If there was an error, return that

View File

@ -275,17 +275,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
state.Put("ui", ui) state.Put("ui", ui)
// Run // Run
if b.config.PackerDebug { b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
pauseFn := common.MultistepDebugFn(ui)
state.Put("pauseFn", pauseFn)
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: pauseFn,
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner.Run(state) b.runner.Run(state)
// If there was an error, return that // If there was an error, return that

View File

@ -135,16 +135,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
} }
// Run the steps. // Run the steps.
if b.config.PackerDebug { b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
pauseFn := common.MultistepDebugFn(ui)
state.Put("pauseFn", pauseFn)
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: pauseFn,
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner.Run(state) b.runner.Run(state)
// Report any errors. // Report any errors.

View File

@ -305,17 +305,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
} }
// Run! // Run!
if b.config.PackerDebug { b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
pauseFn := common.MultistepDebugFn(ui)
state.Put("pauseFn", pauseFn)
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: pauseFn,
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner.Run(state) b.runner.Run(state)
// If there was an error, return that // If there was an error, return that

View File

@ -122,16 +122,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
} }
// Run the steps. // Run the steps.
if b.config.PackerDebug { b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
pauseFn := common.MultistepDebugFn(ui)
state.Put("pauseFn", pauseFn)
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: pauseFn,
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner.Run(state) b.runner.Run(state)
// Report any errors. // Report any errors.

View File

@ -10,6 +10,7 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/mitchellh/packer/helper/enumflag"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template" "github.com/mitchellh/packer/template"
) )
@ -20,11 +21,14 @@ type BuildCommand struct {
func (c BuildCommand) Run(args []string) int { func (c BuildCommand) Run(args []string) int {
var cfgColor, cfgDebug, cfgForce, cfgParallel bool var cfgColor, cfgDebug, cfgForce, cfgParallel bool
var cfgOnError string
flags := c.Meta.FlagSet("build", FlagSetBuildFilter|FlagSetVars) flags := c.Meta.FlagSet("build", FlagSetBuildFilter|FlagSetVars)
flags.Usage = func() { c.Ui.Say(c.Help()) } flags.Usage = func() { c.Ui.Say(c.Help()) }
flags.BoolVar(&cfgColor, "color", true, "") flags.BoolVar(&cfgColor, "color", true, "")
flags.BoolVar(&cfgDebug, "debug", false, "") flags.BoolVar(&cfgDebug, "debug", false, "")
flags.BoolVar(&cfgForce, "force", false, "") flags.BoolVar(&cfgForce, "force", false, "")
flagOnError := enumflag.New(&cfgOnError, "cleanup", "abort", "ask")
flags.Var(flagOnError, "on-error", "")
flags.BoolVar(&cfgParallel, "parallel", true, "") flags.BoolVar(&cfgParallel, "parallel", true, "")
if err := flags.Parse(args); err != nil { if err := flags.Parse(args); err != nil {
return 1 return 1
@ -99,12 +103,14 @@ func (c BuildCommand) Run(args []string) int {
log.Printf("Build debug mode: %v", cfgDebug) log.Printf("Build debug mode: %v", cfgDebug)
log.Printf("Force build: %v", cfgForce) log.Printf("Force build: %v", cfgForce)
log.Printf("On error: %v", cfgOnError)
// Set the debug and force mode and prepare all the builds // Set the debug and force mode and prepare all the builds
for _, b := range builds { for _, b := range builds {
log.Printf("Preparing build: %s", b.Name()) log.Printf("Preparing build: %s", b.Name())
b.SetDebug(cfgDebug) b.SetDebug(cfgDebug)
b.SetForce(cfgForce) b.SetForce(cfgForce)
b.SetOnError(cfgOnError)
warnings, err := b.Prepare() warnings, err := b.Prepare()
if err != nil { if err != nil {
@ -284,7 +290,7 @@ Options:
-except=foo,bar,baz Build all builds other than these -except=foo,bar,baz Build all builds other than these
-force Force a build to continue if artifacts exist, deletes existing artifacts -force Force a build to continue if artifacts exist, deletes existing artifacts
-machine-readable Machine-readable output -machine-readable Machine-readable output
-only=foo,bar,baz Only build the given builds by name -on-error=[cleanup|abort|ask] If the build fails do: clean up (default), abort, or ask
-parallel=false Disable parallelization (on by default) -parallel=false Disable parallelization (on by default)
-var 'key=value' Variable for templates, can be used multiple times. -var 'key=value' Variable for templates, can be used multiple times.
-var-file=path JSON file containing user variables. -var-file=path JSON file containing user variables.

154
common/multistep_runner.go Normal file
View File

@ -0,0 +1,154 @@
package common
import (
"fmt"
"log"
"os"
"reflect"
"strings"
"time"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
func newRunner(steps []multistep.Step, config PackerConfig, ui packer.Ui) (multistep.Runner, multistep.DebugPauseFn) {
switch config.PackerOnError {
case "", "cleanup":
case "abort":
for i, step := range steps {
steps[i] = abortStep{step, ui}
}
case "ask":
for i, step := range steps {
steps[i] = askStep{step, 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 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 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
ui packer.Ui
}
func (s abortStep) Run(state multistep.StateBag) multistep.StepAction {
return s.step.Run(state)
}
func (s abortStep) Cleanup(state multistep.StateBag) {
if _, ok := state.GetOk(multistep.StateCancelled); ok {
s.ui.Error("Interrupted, aborting...")
os.Exit(1)
}
if _, ok := state.GetOk(multistep.StateHalted); ok {
s.ui.Error(fmt.Sprintf("Step %q failed, aborting...", typeName(s.step)))
os.Exit(1)
}
s.step.Cleanup(state)
}
type askStep struct {
step multistep.Step
ui packer.Ui
}
func (s askStep) Run(state multistep.StateBag) (action multistep.StepAction) {
for {
action = s.step.Run(state)
if action != multistep.ActionHalt {
return
}
switch ask(s.ui, typeName(s.step), state) {
case askCleanup:
return
case askAbort:
os.Exit(1)
case askRetry:
continue
}
}
}
func (s askStep) Cleanup(state multistep.StateBag) {
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))
}
}

View File

@ -8,5 +8,6 @@ type PackerConfig struct {
PackerBuilderType string `mapstructure:"packer_builder_type"` PackerBuilderType string `mapstructure:"packer_builder_type"`
PackerDebug bool `mapstructure:"packer_debug"` PackerDebug bool `mapstructure:"packer_debug"`
PackerForce bool `mapstructure:"packer_force"` PackerForce bool `mapstructure:"packer_force"`
PackerOnError string `mapstructure:"packer_on_error"`
PackerUserVars map[string]string `mapstructure:"packer_user_variables"` PackerUserVars map[string]string `mapstructure:"packer_user_variables"`
} }

28
helper/enumflag/flag.go Normal file
View File

@ -0,0 +1,28 @@
package enumflag
import "fmt"
type enumFlag struct {
target *string
options []string
}
// New returns a flag.Value implementation for parsing flags with a one-of-a-set value
func New(target *string, options ...string) *enumFlag {
return &enumFlag{target: target, options: options}
}
func (f *enumFlag) String() string {
return *f.target
}
func (f *enumFlag) Set(value string) error {
for _, v := range f.options {
if v == value {
*f.target = value
return nil
}
}
return fmt.Errorf("expected one of %q", f.options)
}

View File

@ -24,6 +24,12 @@ const (
// force build is enabled. // force build is enabled.
ForceConfigKey = "packer_force" ForceConfigKey = "packer_force"
// This key determines what to do when a normal multistep step fails
// - "cleanup" - run cleanup steps
// - "abort" - exit without cleanup
// - "ask" - ask the user
OnErrorConfigKey = "packer_on_error"
// TemplatePathKey is the path to the template that configured this build // TemplatePathKey is the path to the template that configured this build
TemplatePathKey = "packer_template_path" TemplatePathKey = "packer_template_path"
@ -67,6 +73,12 @@ type Build interface {
// When SetForce is set to true, existing artifacts from the build are // When SetForce is set to true, existing artifacts from the build are
// deleted prior to the build. // deleted prior to the build.
SetForce(bool) SetForce(bool)
// SetOnError will determine what to do when a normal multistep step fails
// - "cleanup" - run cleanup steps
// - "abort" - exit without cleanup
// - "ask" - ask the user
SetOnError(string)
} }
// A build struct represents a single build job, the result of which should // A build struct represents a single build job, the result of which should
@ -86,6 +98,7 @@ type coreBuild struct {
debug bool debug bool
force bool force bool
onError string
l sync.Mutex l sync.Mutex
prepareCalled bool prepareCalled bool
} }
@ -129,6 +142,7 @@ func (b *coreBuild) Prepare() (warn []string, err error) {
BuilderTypeConfigKey: b.builderType, BuilderTypeConfigKey: b.builderType,
DebugConfigKey: b.debug, DebugConfigKey: b.debug,
ForceConfigKey: b.force, ForceConfigKey: b.force,
OnErrorConfigKey: b.onError,
TemplatePathKey: b.templatePath, TemplatePathKey: b.templatePath,
UserVariablesConfigKey: b.variables, UserVariablesConfigKey: b.variables,
} }
@ -306,6 +320,14 @@ func (b *coreBuild) SetForce(val bool) {
b.force = val b.force = val
} }
func (b *coreBuild) SetOnError(val string) {
if b.prepareCalled {
panic("prepare has already been called")
}
b.onError = val
}
// Cancels the build if it is running. // Cancels the build if it is running.
func (b *coreBuild) Cancel() { func (b *coreBuild) Cancel() {
b.builder.Cancel() b.builder.Cancel()

View File

@ -23,6 +23,7 @@ func testBuild() *coreBuild {
}, },
}, },
variables: make(map[string]string), variables: make(map[string]string),
onError: "cleanup",
} }
} }
@ -32,6 +33,7 @@ func testDefaultPackerConfig() map[string]interface{} {
BuilderTypeConfigKey: "foo", BuilderTypeConfigKey: "foo",
DebugConfigKey: false, DebugConfigKey: false,
ForceConfigKey: false, ForceConfigKey: false,
OnErrorConfigKey: "cleanup",
TemplatePathKey: "", TemplatePathKey: "",
UserVariablesConfigKey: make(map[string]string), UserVariablesConfigKey: make(map[string]string),
} }

View File

@ -1,8 +1,9 @@
package rpc package rpc
import ( import (
"github.com/mitchellh/packer/packer"
"net/rpc" "net/rpc"
"github.com/mitchellh/packer/packer"
) )
// An implementation of packer.Build where the build is actually executed // An implementation of packer.Build where the build is actually executed
@ -79,6 +80,12 @@ func (b *build) SetForce(val bool) {
} }
} }
func (b *build) SetOnError(val string) {
if err := b.client.Call("Build.SetOnError", val, new(interface{})); err != nil {
panic(err)
}
}
func (b *build) Cancel() { func (b *build) Cancel() {
if err := b.client.Call("Build.Cancel", new(interface{}), new(interface{})); err != nil { if err := b.client.Call("Build.Cancel", new(interface{}), new(interface{})); err != nil {
panic(err) panic(err)
@ -134,6 +141,11 @@ func (b *BuildServer) SetForce(val *bool, reply *interface{}) error {
return nil return nil
} }
func (b *BuildServer) SetOnError(val *string, reply *interface{}) error {
b.build.SetOnError(*val)
return nil
}
func (b *BuildServer) Cancel(args *interface{}, reply *interface{}) error { func (b *BuildServer) Cancel(args *interface{}, reply *interface{}) error {
b.build.Cancel() b.build.Cancel()
return nil return nil

View File

@ -2,9 +2,10 @@ package rpc
import ( import (
"errors" "errors"
"github.com/mitchellh/packer/packer"
"reflect" "reflect"
"testing" "testing"
"github.com/mitchellh/packer/packer"
) )
var testBuildArtifact = &packer.MockArtifact{} var testBuildArtifact = &packer.MockArtifact{}
@ -18,6 +19,7 @@ type testBuild struct {
runUi packer.Ui runUi packer.Ui
setDebugCalled bool setDebugCalled bool
setForceCalled bool setForceCalled bool
setOnErrorCalled bool
cancelCalled bool cancelCalled bool
errRunResult bool errRunResult bool
@ -53,6 +55,10 @@ func (b *testBuild) SetForce(bool) {
b.setForceCalled = true b.setForceCalled = true
} }
func (b *testBuild) SetOnError(string) {
b.setOnErrorCalled = true
}
func (b *testBuild) Cancel() { func (b *testBuild) Cancel() {
b.cancelCalled = true b.cancelCalled = true
} }
@ -116,6 +122,12 @@ func TestBuild(t *testing.T) {
t.Fatal("should be called") t.Fatal("should be called")
} }
// Test SetOnError
bClient.SetOnError("ask")
if !b.setOnErrorCalled {
t.Fatal("should be called")
}
// Test Cancel // Test Cancel
bClient.Cancel() bClient.Cancel()
if !b.cancelCalled { if !b.cancelCalled {

View File

@ -36,7 +36,17 @@ artifacts that are created will be outputted at the end of the build.
remove the artifacts from the previous build. This will allow the user to remove the artifacts from the previous build. This will allow the user to
repeat a build without having to manually clean these artifacts beforehand. repeat a build without having to manually clean these artifacts beforehand.
- `-on-error=cleanup` (default), `-on-error=abort`, `-on-error=ask` - Selects
what to do when the build fails. `cleanup` cleans up after the previous
steps, deleting temporary files and virtual machines. `abort` exits without
any cleanup, which might require the next build to use `-force`. `ask`
presents a prompt and waits for you to decide to clean up, abort, or retry
the failed step.
- `-only=foo,bar,baz` - Only build the builds with the given - `-only=foo,bar,baz` - Only build the builds with the given
comma-separated names. Build names by default are the names of their comma-separated names. Build names by default are the names of their
builders, unless a specific `name` attribute is specified within builders, unless a specific `name` attribute is specified within
the configuration. the configuration.
- `-parallel=false` - Disable parallelization of multiple builders (on by
default).