From 7c67228912d6f7ae8bc74ef43dea24642aa5bc81 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Tue, 9 Dec 2014 08:42:33 -0800 Subject: [PATCH] Check if image already exists before doing anything else on GCE. This fixes #1729. --- builder/googlecompute/builder.go | 1 + builder/googlecompute/driver.go | 4 +++ builder/googlecompute/driver_gce.go | 9 ++++- builder/googlecompute/driver_mock.go | 8 +++++ .../step_check_existing_image.go | 33 +++++++++++++++++++ .../step_check_existing_image_test.go | 32 ++++++++++++++++++ 6 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 builder/googlecompute/step_check_existing_image.go create mode 100644 builder/googlecompute/step_check_existing_image_test.go diff --git a/builder/googlecompute/builder.go b/builder/googlecompute/builder.go index 9c25490cb..cfb9c6e56 100644 --- a/builder/googlecompute/builder.go +++ b/builder/googlecompute/builder.go @@ -49,6 +49,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // Build the steps. steps := []multistep.Step{ + new(StepCheckExistingImage), &StepCreateSSHKey{ Debug: b.config.PackerDebug, DebugKeyPath: fmt.Sprintf("gce_%s.pem", b.config.PackerBuildName), diff --git a/builder/googlecompute/driver.go b/builder/googlecompute/driver.go index 046bfa3e2..6f035c562 100644 --- a/builder/googlecompute/driver.go +++ b/builder/googlecompute/driver.go @@ -4,6 +4,10 @@ package googlecompute // with GCE. The Driver interface exists mostly to allow a mock implementation // to be used to test the steps. type Driver interface { + // ImageExists returns true if the specified image exists. If an error + // occurs calling the API, this method returns false. + ImageExists(name string) bool + // CreateImage creates an image from the given disk in Google Compute // Engine. CreateImage(name, description, zone, disk string) <-chan error diff --git a/builder/googlecompute/driver_gce.go b/builder/googlecompute/driver_gce.go index 835add5d9..674019f52 100644 --- a/builder/googlecompute/driver_gce.go +++ b/builder/googlecompute/driver_gce.go @@ -7,9 +7,9 @@ import ( "time" "code.google.com/p/google-api-go-client/compute/v1" + "github.com/mitchellh/packer/packer" "golang.org/x/oauth2" "golang.org/x/oauth2/google" - "github.com/mitchellh/packer/packer" ) // driverGCE is a Driver implementation that actually talks to GCE. @@ -60,6 +60,13 @@ func NewDriverGCE(ui packer.Ui, p string, a *accountFile) (Driver, error) { }, nil } +func (d *driverGCE) ImageExists(name string) bool { + _, err := d.service.Images.Get(d.projectId, name).Do() + // The API may return an error for reasons other than the image not + // existing, but this heuristic is sufficient for now. + return err == nil +} + func (d *driverGCE) CreateImage(name, description, zone, disk string) <-chan error { image := &compute.Image{ Description: description, diff --git a/builder/googlecompute/driver_mock.go b/builder/googlecompute/driver_mock.go index 70b310e39..1a9c03ae9 100644 --- a/builder/googlecompute/driver_mock.go +++ b/builder/googlecompute/driver_mock.go @@ -3,6 +3,9 @@ package googlecompute // DriverMock is a Driver implementation that is a mocked out so that // it can be used for tests. type DriverMock struct { + ImageExistsName string + ImageExistsResult bool + CreateImageName string CreateImageDesc string CreateImageZone string @@ -37,6 +40,11 @@ type DriverMock struct { WaitForInstanceErrCh <-chan error } +func (d *DriverMock) ImageExists(name string) bool { + d.ImageExistsName = name + return d.ImageExistsResult +} + func (d *DriverMock) CreateImage(name, description, zone, disk string) <-chan error { d.CreateImageName = name d.CreateImageDesc = description diff --git a/builder/googlecompute/step_check_existing_image.go b/builder/googlecompute/step_check_existing_image.go new file mode 100644 index 000000000..2a3365739 --- /dev/null +++ b/builder/googlecompute/step_check_existing_image.go @@ -0,0 +1,33 @@ +package googlecompute + +import ( + "fmt" + + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +// StepCheckExistingImage represents a Packer build step that checks if the +// target image already exists, and aborts immediately if so. +type StepCheckExistingImage int + +// Run executes the Packer build step that checks if the image already exists. +func (s *StepCheckExistingImage) Run(state multistep.StateBag) multistep.StepAction { + config := state.Get("config").(*Config) + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + + ui.Say("Checking image does not exist...") + exists := driver.ImageExists(config.ImageName) + if exists { + err := fmt.Errorf("Image %s already exists", config.ImageName) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +// Cleanup. +func (s *StepCheckExistingImage) Cleanup(state multistep.StateBag) {} diff --git a/builder/googlecompute/step_check_existing_image_test.go b/builder/googlecompute/step_check_existing_image_test.go new file mode 100644 index 000000000..a7b336407 --- /dev/null +++ b/builder/googlecompute/step_check_existing_image_test.go @@ -0,0 +1,32 @@ +package googlecompute + +import ( + "github.com/mitchellh/multistep" + "testing" +) + +func TestStepCheckExistingImage_impl(t *testing.T) { + var _ multistep.Step = new(StepCheckExistingImage) +} + +func TestStepCheckExistingImage(t *testing.T) { + state := testState(t) + step := new(StepCheckExistingImage) + defer step.Cleanup(state) + + state.Put("instance_name", "foo") + + config := state.Get("config").(*Config) + driver := state.Get("driver").(*DriverMock) + driver.ImageExistsResult = true + + // run the step + if action := step.Run(state); action != multistep.ActionHalt { + t.Fatalf("bad action: %#v", action) + } + + // Verify state + if driver.ImageExistsName != config.ImageName { + t.Fatalf("bad: %#v", driver.ImageExistsName) + } +}