From 13c733a3db912a45afd6c0c2962c844c1a5fe981 Mon Sep 17 00:00:00 2001 From: "Jason A. Beranek" Date: Thu, 11 Jul 2013 23:43:23 -0500 Subject: [PATCH] Add support for -force flag on builds [GH-119] --- builder/virtualbox/builder.go | 8 +++++++- builder/vmware/builder.go | 8 +++++++- command/build/command.go | 6 +++++- command/build/help.go | 1 + packer/build.go | 20 +++++++++++++++++++ packer/build_test.go | 2 ++ packer/rpc/build.go | 11 ++++++++++ packer/rpc/build_test.go | 9 +++++++++ .../docs/command-line/build.html.markdown | 6 ++++++ 9 files changed, 68 insertions(+), 3 deletions(-) diff --git a/builder/virtualbox/builder.go b/builder/virtualbox/builder.go index e45286a77..a0b23bcad 100644 --- a/builder/virtualbox/builder.go +++ b/builder/virtualbox/builder.go @@ -54,6 +54,7 @@ type config struct { PackerBuildName string `mapstructure:"packer_build_name"` PackerDebug bool `mapstructure:"packer_debug"` + PackerForce bool `mapstructure:"packer_force"` RawBootWait string `mapstructure:"boot_wait"` RawShutdownTimeout string `mapstructure:"shutdown_timeout"` @@ -219,7 +220,12 @@ func (b *Builder) Prepare(raws ...interface{}) error { } if _, err := os.Stat(b.config.OutputDir); err == nil { - errs = append(errs, errors.New("Output directory already exists. It must not exist.")) + if b.config.PackerForce { + log.Printf("Build forced, removing existing output directory: %s", string(b.config.OutputDir)) + os.RemoveAll(b.config.OutputDir) + } else { + errs = append(errs, errors.New("Output directory already exists. It must not exist.")) + } } b.config.BootWait, err = time.ParseDuration(b.config.RawBootWait) diff --git a/builder/vmware/builder.go b/builder/vmware/builder.go index 708f3c988..883fd5484 100644 --- a/builder/vmware/builder.go +++ b/builder/vmware/builder.go @@ -55,6 +55,7 @@ type config struct { PackerBuildName string `mapstructure:"packer_build_name"` PackerDebug bool `mapstructure:"packer_debug"` + PackerForce bool `mapstructure:"packer_force"` RawBootWait string `mapstructure:"boot_wait"` RawShutdownTimeout string `mapstructure:"shutdown_timeout"` @@ -175,7 +176,12 @@ func (b *Builder) Prepare(raws ...interface{}) error { } if _, err := os.Stat(b.config.OutputDir); err == nil { - errs = append(errs, errors.New("Output directory already exists. It must not exist.")) + if b.config.PackerForce { + log.Printf("Build forced, removing existing output directory: %s", string(b.config.OutputDir)) + os.RemoveAll(b.config.OutputDir) + } else { + errs = append(errs, errors.New("Output directory already exists. It must not exist.")) + } } if b.config.SSHUser == "" { diff --git a/command/build/command.go b/command/build/command.go index 3fa9a79dc..39eb590c1 100644 --- a/command/build/command.go +++ b/command/build/command.go @@ -21,12 +21,14 @@ func (Command) Help() string { func (c Command) Run(env packer.Environment, args []string) int { var cfgDebug bool + var cfgForce bool var cfgExcept []string var cfgOnly []string cmdFlags := flag.NewFlagSet("build", flag.ContinueOnError) cmdFlags.Usage = func() { env.Ui().Say(c.Help()) } cmdFlags.BoolVar(&cfgDebug, "debug", false, "debug mode for builds") + cmdFlags.BoolVar(&cfgForce, "force", false, "force a build if artifacts exist") cmdFlags.Var((*stringSliceValue)(&cfgExcept), "except", "build all builds except these") cmdFlags.Var((*stringSliceValue)(&cfgOnly), "only", "only build the given builds by name") if err := cmdFlags.Parse(args); err != nil { @@ -141,11 +143,13 @@ func (c Command) Run(env packer.Environment, args []string) int { env.Ui().Say("") log.Printf("Build debug mode: %v", cfgDebug) + log.Printf("Force build: %v", cfgForce) - // Set the debug mode and prepare all the builds + // Set the debug and force mode and prepare all the builds for _, b := range builds { log.Printf("Preparing build: %s", b.Name()) b.SetDebug(cfgDebug) + b.SetForce(cfgForce) err := b.Prepare() if err != nil { env.Ui().Error(err.Error()) diff --git a/command/build/help.go b/command/build/help.go index 21d8e2920..f94b3fd4f 100644 --- a/command/build/help.go +++ b/command/build/help.go @@ -9,6 +9,7 @@ Usage: packer build [options] TEMPLATE Options: -debug Debug mode enabled for builds + -force Force a build to continue if artifacts exist, deletes existing artifacts -except=foo,bar,baz Build all builds other than these -only=foo,bar,baz Only build the given builds by name ` diff --git a/packer/build.go b/packer/build.go index 3a16b769b..62b96203a 100644 --- a/packer/build.go +++ b/packer/build.go @@ -14,6 +14,10 @@ const BuildNameConfigKey = "packer_build_name" // debugging is enabled. const DebugConfigKey = "packer_debug" +// This is the key in configurations that is set to "true" when Packer +// force build is enabled. +const ForceConfigKey = "packer_force" + // A Build represents a single job within Packer that is responsible for // building some machine image artifact. Builds are meant to be parallelized. type Build interface { @@ -42,6 +46,12 @@ type Build interface { // When SetDebug is set to true, parallelism between builds is // strictly prohibited. SetDebug(bool) + + // SetForce will enable/disable forcing a build when artifacts exist. + // + // When SetForce is set to true, existing artifacts from the build are + // deleted prior to the build. + SetForce(bool) } // A build struct represents a single build job, the result of which should @@ -58,6 +68,7 @@ type coreBuild struct { provisioners []coreBuildProvisioner debug bool + force bool l sync.Mutex prepareCalled bool } @@ -98,6 +109,7 @@ func (b *coreBuild) Prepare() (err error) { packerConfig := map[string]interface{}{ BuildNameConfigKey: b.name, DebugConfigKey: b.debug, + ForceConfigKey: b.force, } // Prepare the builder @@ -265,6 +277,14 @@ func (b *coreBuild) SetDebug(val bool) { b.debug = val } +func (b *coreBuild) SetForce(val bool) { + if b.prepareCalled { + panic("prepare has already been called") + } + + b.force = val +} + // Cancels the build if it is running. func (b *coreBuild) Cancel() { b.builder.Cancel() diff --git a/packer/build_test.go b/packer/build_test.go index d7d3a9bfe..f792391ca 100644 --- a/packer/build_test.go +++ b/packer/build_test.go @@ -42,6 +42,7 @@ func TestBuild_Prepare(t *testing.T) { packerConfig := map[string]interface{}{ BuildNameConfigKey: "test", DebugConfigKey: false, + ForceConfigKey: false, } build := testBuild() @@ -88,6 +89,7 @@ func TestBuild_Prepare_Debug(t *testing.T) { packerConfig := map[string]interface{}{ BuildNameConfigKey: "test", DebugConfigKey: true, + ForceConfigKey: false, } build := testBuild() diff --git a/packer/rpc/build.go b/packer/rpc/build.go index 00c9fc36d..537566bfe 100644 --- a/packer/rpc/build.go +++ b/packer/rpc/build.go @@ -69,6 +69,12 @@ func (b *build) SetDebug(val bool) { } } +func (b *build) SetForce(val bool) { + if err := b.client.Call("Build.SetForce", val, new(interface{})); err != nil { + panic(err) + } +} + func (b *build) Cancel() { if err := b.client.Call("Build.Cancel", new(interface{}), new(interface{})); err != nil { panic(err) @@ -111,6 +117,11 @@ func (b *BuildServer) SetDebug(val *bool, reply *interface{}) error { return nil } +func (b *BuildServer) SetForce(val *bool, reply *interface{}) error { + b.build.SetForce(*val) + return nil +} + func (b *BuildServer) Cancel(args *interface{}, reply *interface{}) error { b.build.Cancel() return nil diff --git a/packer/rpc/build_test.go b/packer/rpc/build_test.go index 0d2bb74df..1d420dbbb 100644 --- a/packer/rpc/build_test.go +++ b/packer/rpc/build_test.go @@ -17,6 +17,7 @@ type testBuild struct { runCache packer.Cache runUi packer.Ui setDebugCalled bool + setForceCalled bool cancelCalled bool errRunResult bool @@ -48,6 +49,10 @@ func (b *testBuild) SetDebug(bool) { b.setDebugCalled = true } +func (b *testBuild) SetForce(bool) { + b.setForceCalled = true +} + func (b *testBuild) Cancel() { b.cancelCalled = true } @@ -104,6 +109,10 @@ func TestBuildRPC(t *testing.T) { bClient.SetDebug(true) assert.True(b.setDebugCalled, "should be called") + // Test SetForce + bClient.SetForce(true) + assert.True(b.setForceCalled, "should be called") + // Test Cancel bClient.Cancel() assert.True(b.cancelCalled, "cancel should be called") diff --git a/website/source/docs/command-line/build.html.markdown b/website/source/docs/command-line/build.html.markdown index 8e8f5832d..09a271af8 100644 --- a/website/source/docs/command-line/build.html.markdown +++ b/website/source/docs/command-line/build.html.markdown @@ -17,6 +17,12 @@ artifacts that are created will be outputted at the end of the build. between each step, waiting for keyboard input before continuing. This will allow the user to inspect state and so on. +* `-force` - Forces a builder to run when artifacts from a previous build prevent + a build from running. The exact behavior of a forced build is left to the builder. + In general, a builder supporting the forced build will remove the artifacts from + the previous build. This will allow the user to repeat a build without having to + manually clean these artifacts beforehand. + * `-except=foo,bar,baz` - Builds all the builds except those with the given comma-separated names. Build names by default are the names of their builders, unless a specific `name` attribute is specified within the configuration.