From 23c947acf049b734c03ff3e24530f0310475b1a5 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Mon, 24 Nov 2014 08:36:14 -0800 Subject: [PATCH] Create GCE image from persistent disk instead of from a tarball. The new flow: 1) Provision the instance 2) Tear down the instance, but keep the boot disk 3) Create an image from the disk 4) Tear down the disk The step to update gcloud is no longer needed, since gceimagebundle isn't used anymore. Fixes #1507 and addresses https://github.com/mitchellh/packer/issues/1447#issuecomment-61610235. --- builder/googlecompute/builder.go | 4 +- builder/googlecompute/config.go | 13 ++- builder/googlecompute/config_test.go | 10 +- builder/googlecompute/driver.go | 10 +- builder/googlecompute/driver_gce.go | 24 +++-- builder/googlecompute/driver_mock.go | 27 ++++- builder/googlecompute/step_create_image.go | 36 +++---- .../googlecompute/step_create_image_test.go | 78 +++++--------- builder/googlecompute/step_create_instance.go | 16 +-- builder/googlecompute/step_register_image.go | 46 -------- .../googlecompute/step_register_image_test.go | 100 ------------------ .../googlecompute/step_teardown_instance.go | 80 ++++++++++++++ .../step_teardown_instance_test.go | 41 +++++++ builder/googlecompute/step_update_gcloud.go | 52 --------- .../googlecompute/step_update_gcloud_test.go | 85 --------------- builder/googlecompute/step_upload_image.go | 45 -------- .../googlecompute/step_upload_image_test.go | 88 --------------- 17 files changed, 235 insertions(+), 520 deletions(-) delete mode 100644 builder/googlecompute/step_register_image.go delete mode 100644 builder/googlecompute/step_register_image_test.go create mode 100644 builder/googlecompute/step_teardown_instance.go create mode 100644 builder/googlecompute/step_teardown_instance_test.go delete mode 100644 builder/googlecompute/step_update_gcloud.go delete mode 100644 builder/googlecompute/step_update_gcloud_test.go delete mode 100644 builder/googlecompute/step_upload_image.go delete mode 100644 builder/googlecompute/step_upload_image_test.go diff --git a/builder/googlecompute/builder.go b/builder/googlecompute/builder.go index 4b0b32ca5..9c25490cb 100644 --- a/builder/googlecompute/builder.go +++ b/builder/googlecompute/builder.go @@ -65,10 +65,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe SSHWaitTimeout: 5 * time.Minute, }, new(common.StepProvision), - new(StepUpdateGcloud), + new(StepTeardownInstance), new(StepCreateImage), - new(StepUploadImage), - new(StepRegisterImage), } // Run the steps. diff --git a/builder/googlecompute/config.go b/builder/googlecompute/config.go index 5c3639402..27619c9bf 100644 --- a/builder/googlecompute/config.go +++ b/builder/googlecompute/config.go @@ -16,10 +16,11 @@ import ( type Config struct { common.PackerConfig `mapstructure:",squash"` - AccountFile string `mapstructure:"account_file"` - ProjectId string `mapstructure:"project_id"` + AccountFile string `mapstructure:"account_file"` + ProjectId string `mapstructure:"project_id"` BucketName string `mapstructure:"bucket_name"` + DiskName string `mapstructure:"disk_name"` DiskSizeGb int64 `mapstructure:"disk_size"` ImageName string `mapstructure:"image_name"` ImageDescription string `mapstructure:"image_description"` @@ -37,7 +38,6 @@ type Config struct { Zone string `mapstructure:"zone"` account accountFile - instanceName string privateKeyBytes []byte sshTimeout time.Duration stateTimeout time.Duration @@ -81,6 +81,10 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { c.InstanceName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID()) } + if c.DiskName == "" { + c.DiskName = c.InstanceName + } + if c.MachineType == "" { c.MachineType = "n1-standard-1" } @@ -103,9 +107,10 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { // Process Templates templates := map[string]*string{ - "account_file": &c.AccountFile, + "account_file": &c.AccountFile, "bucket_name": &c.BucketName, + "disk_name": &c.DiskName, "image_name": &c.ImageName, "image_description": &c.ImageDescription, "instance_name": &c.InstanceName, diff --git a/builder/googlecompute/config_test.go b/builder/googlecompute/config_test.go index acc898ce7..77dcca4c2 100644 --- a/builder/googlecompute/config_test.go +++ b/builder/googlecompute/config_test.go @@ -7,11 +7,11 @@ import ( func testConfig(t *testing.T) map[string]interface{} { return map[string]interface{}{ - "account_file": testAccountFile(t), - "bucket_name": "foo", - "project_id": "hashicorp", - "source_image": "foo", - "zone": "us-east-1a", + "account_file": testAccountFile(t), + "bucket_name": "foo", + "project_id": "hashicorp", + "source_image": "foo", + "zone": "us-east-1a", } } diff --git a/builder/googlecompute/driver.go b/builder/googlecompute/driver.go index da1b52088..046bfa3e2 100644 --- a/builder/googlecompute/driver.go +++ b/builder/googlecompute/driver.go @@ -4,15 +4,19 @@ package googlecompute // with GCE. The Driver interface exists mostly to allow a mock implementation // to be used to test the steps. type Driver interface { - // CreateImage creates an image with the given URL in Google Storage. - CreateImage(name, description, url string) <-chan error + // CreateImage creates an image from the given disk in Google Compute + // Engine. + CreateImage(name, description, zone, disk string) <-chan error // DeleteImage deletes the image with the given name. DeleteImage(name string) <-chan error - // DeleteInstance deletes the given instance. + // DeleteInstance deletes the given instance, keeping the boot disk. DeleteInstance(zone, name string) (<-chan error, error) + // DeleteDisk deletes the disk with the given name. + DeleteDisk(zone, name string) (<-chan error, error) + // GetNatIP gets the NAT IP address for the instance. GetNatIP(zone, name string) (string, error) diff --git a/builder/googlecompute/driver_gce.go b/builder/googlecompute/driver_gce.go index 44befad15..e48b50dc1 100644 --- a/builder/googlecompute/driver_gce.go +++ b/builder/googlecompute/driver_gce.go @@ -23,7 +23,7 @@ type driverGCE struct { var DriverScopes = []string{"https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/devstorage.full_control"} func NewDriverGCE(ui packer.Ui, p string, a *accountFile) (Driver, error) { - var f *oauth2.Flow + var f *oauth2.Options var err error // Auth with AccountFile first if provided @@ -60,15 +60,12 @@ func NewDriverGCE(ui packer.Ui, p string, a *accountFile) (Driver, error) { }, nil } -func (d *driverGCE) CreateImage(name, description, url string) <-chan error { +func (d *driverGCE) CreateImage(name, description, zone, disk string) <-chan error { image := &compute.Image{ Description: description, Name: name, - RawDisk: &compute.ImageRawDisk{ - ContainerType: "TAR", - Source: url, - }, - SourceType: "RAW", + SourceDisk: fmt.Sprintf("%s%s/zones/%s/disks/%s", d.service.BasePath, d.projectId, zone, disk), + SourceType: "RAW", } errCh := make(chan error, 1) @@ -105,6 +102,17 @@ func (d *driverGCE) DeleteInstance(zone, name string) (<-chan error, error) { return errCh, nil } +func (d *driverGCE) DeleteDisk(zone, name string) (<-chan error, error) { + op, err := d.service.Disks.Delete(d.projectId, zone, name).Do() + if err != nil { + return nil, err + } + + errCh := make(chan error, 1) + go waitForState(errCh, "DONE", d.refreshZoneOp(zone, op)) + return errCh, nil +} + func (d *driverGCE) GetNatIP(zone, name string) (string, error) { instance, err := d.service.Instances.Get(d.projectId, zone, name).Do() if err != nil { @@ -175,7 +183,7 @@ func (d *driverGCE) RunInstance(c *InstanceConfig) (<-chan error, error) { Mode: "READ_WRITE", Kind: "compute#attachedDisk", Boot: true, - AutoDelete: true, + AutoDelete: false, InitializeParams: &compute.AttachedDiskInitializeParams{ SourceImage: image.SelfLink, DiskSizeGb: c.DiskSizeGb, diff --git a/builder/googlecompute/driver_mock.go b/builder/googlecompute/driver_mock.go index e66727b80..70b310e39 100644 --- a/builder/googlecompute/driver_mock.go +++ b/builder/googlecompute/driver_mock.go @@ -5,7 +5,8 @@ package googlecompute type DriverMock struct { CreateImageName string CreateImageDesc string - CreateImageURL string + CreateImageZone string + CreateImageDisk string CreateImageErrCh <-chan error DeleteImageName string @@ -16,6 +17,11 @@ type DriverMock struct { DeleteInstanceErrCh <-chan error DeleteInstanceErr error + DeleteDiskZone string + DeleteDiskName string + DeleteDiskErrCh <-chan error + DeleteDiskErr error + GetNatIPZone string GetNatIPName string GetNatIPResult string @@ -31,10 +37,11 @@ type DriverMock struct { WaitForInstanceErrCh <-chan error } -func (d *DriverMock) CreateImage(name, description, url string) <-chan error { +func (d *DriverMock) CreateImage(name, description, zone, disk string) <-chan error { d.CreateImageName = name d.CreateImageDesc = description - d.CreateImageURL = url + d.CreateImageZone = zone + d.CreateImageDisk = disk resultCh := d.CreateImageErrCh if resultCh == nil { @@ -73,6 +80,20 @@ func (d *DriverMock) DeleteInstance(zone, name string) (<-chan error, error) { return resultCh, d.DeleteInstanceErr } +func (d *DriverMock) DeleteDisk(zone, name string) (<-chan error, error) { + d.DeleteDiskZone = zone + d.DeleteDiskName = name + + resultCh := d.DeleteDiskErrCh + if resultCh == nil { + ch := make(chan error) + close(ch) + resultCh = ch + } + + return resultCh, d.DeleteDiskErr +} + func (d *DriverMock) GetNatIP(zone, name string) (string, error) { d.GetNatIPZone = zone d.GetNatIPName = name diff --git a/builder/googlecompute/step_create_image.go b/builder/googlecompute/step_create_image.go index ef8026a99..47838bc6c 100644 --- a/builder/googlecompute/step_create_image.go +++ b/builder/googlecompute/step_create_image.go @@ -1,8 +1,9 @@ package googlecompute import ( + "errors" "fmt" - "path/filepath" + "time" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" @@ -14,39 +15,32 @@ type StepCreateImage int // Run executes the Packer build step that creates a GCE machine image. // -// Currently the only way to create a GCE image is to run the gcimagebundle -// command on the running GCE instance. +// The image is created from the persistent disk used by the instance. The +// instance must be deleted and the disk retained before doing this step. func (s *StepCreateImage) Run(state multistep.StateBag) multistep.StepAction { config := state.Get("config").(*Config) - comm := state.Get("communicator").(packer.Communicator) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) - sudoPrefix := "" - if config.SSHUsername != "root" { - sudoPrefix = "sudo " - } - - imageFilename := fmt.Sprintf("%s.tar.gz", config.ImageName) - imageBundleCmd := "/usr/bin/gcimagebundle -d /dev/sda -o /tmp/" - ui.Say("Creating image...") - cmd := new(packer.RemoteCmd) - cmd.Command = fmt.Sprintf("%s%s --output_file_name %s --fssize %d", - sudoPrefix, imageBundleCmd, imageFilename, config.DiskSizeGb*1024*1024*1024) - err := cmd.StartWithUi(comm, ui) - if err == nil && cmd.ExitStatus != 0 { - err = fmt.Errorf( - "gcimagebundle exited with non-zero exit status: %d", cmd.ExitStatus) + errCh := driver.CreateImage(config.ImageName, config.ImageDescription, config.Zone, config.DiskName) + var err error + select { + case err = <-errCh: + case <-time.After(config.stateTimeout): + err = errors.New("time out while waiting for image to register") } + if err != nil { - err := fmt.Errorf("Error creating image: %s", err) + err := fmt.Errorf("Error waiting for image: %s", err) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } - state.Put("image_file_name", filepath.Join("/tmp", imageFilename)) + state.Put("image_name", config.ImageName) return multistep.ActionContinue } +// Cleanup. func (s *StepCreateImage) Cleanup(state multistep.StateBag) {} diff --git a/builder/googlecompute/step_create_image_test.go b/builder/googlecompute/step_create_image_test.go index 0e9b1455d..c9043a39a 100644 --- a/builder/googlecompute/step_create_image_test.go +++ b/builder/googlecompute/step_create_image_test.go @@ -1,11 +1,10 @@ package googlecompute import ( - "strings" + "errors" "testing" "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" ) func TestStepCreateImage_impl(t *testing.T) { @@ -17,38 +16,49 @@ func TestStepCreateImage(t *testing.T) { step := new(StepCreateImage) defer step.Cleanup(state) - comm := new(packer.MockCommunicator) - state.Put("communicator", comm) + config := state.Get("config").(*Config) + driver := state.Get("driver").(*DriverMock) // run the step if action := step.Run(state); action != multistep.ActionContinue { t.Fatalf("bad action: %#v", action) } - // Verify - if !comm.StartCalled { - t.Fatal("start should be called") + // Verify state + if driver.CreateImageName != config.ImageName { + t.Fatalf("bad: %#v", driver.CreateImageName) } - if strings.HasPrefix(comm.StartCmd.Command, "sudo") { - t.Fatal("should not sudo") + if driver.CreateImageDesc != config.ImageDescription { + t.Fatalf("bad: %#v", driver.CreateImageDesc) } - if !strings.Contains(comm.StartCmd.Command, "gcimagebundle") { - t.Fatalf("bad command: %#v", comm.StartCmd.Command) + if driver.CreateImageZone != config.Zone { + t.Fatalf("bad: %#v", driver.CreateImageZone) + } + if driver.CreateImageDisk != config.DiskName { + t.Fatalf("bad: %#v", driver.CreateImageDisk) } - if _, ok := state.GetOk("image_file_name"); !ok { - t.Fatal("should have image") + nameRaw, ok := state.GetOk("image_name") + if !ok { + t.Fatal("should have name") + } + if name, ok := nameRaw.(string); !ok { + t.Fatal("name is not a string") + } else if name != config.ImageName { + t.Fatalf("bad name: %s", name) } } -func TestStepCreateImage_badExitStatus(t *testing.T) { +func TestStepCreateImage_errorOnChannel(t *testing.T) { state := testState(t) step := new(StepCreateImage) defer step.Cleanup(state) - comm := new(packer.MockCommunicator) - comm.StartExitStatus = 12 - state.Put("communicator", comm) + errCh := make(chan error, 1) + errCh <- errors.New("error") + + driver := state.Get("driver").(*DriverMock) + driver.CreateImageErrCh = errCh // run the step if action := step.Run(state); action != multistep.ActionHalt { @@ -58,39 +68,7 @@ func TestStepCreateImage_badExitStatus(t *testing.T) { if _, ok := state.GetOk("error"); !ok { t.Fatal("should have error") } - if _, ok := state.GetOk("image_file_name"); ok { + if _, ok := state.GetOk("image_name"); ok { t.Fatal("should NOT have image") } } - -func TestStepCreateImage_nonRoot(t *testing.T) { - state := testState(t) - step := new(StepCreateImage) - defer step.Cleanup(state) - - comm := new(packer.MockCommunicator) - state.Put("communicator", comm) - - config := state.Get("config").(*Config) - config.SSHUsername = "bob" - - // run the step - if action := step.Run(state); action != multistep.ActionContinue { - t.Fatalf("bad action: %#v", action) - } - - // Verify - if !comm.StartCalled { - t.Fatal("start should be called") - } - if !strings.HasPrefix(comm.StartCmd.Command, "sudo") { - t.Fatal("should sudo") - } - if !strings.Contains(comm.StartCmd.Command, "gcimagebundle") { - t.Fatalf("bad command: %#v", comm.StartCmd.Command) - } - - if _, ok := state.GetOk("image_file_name"); !ok { - t.Fatal("should have image") - } -} diff --git a/builder/googlecompute/step_create_instance.go b/builder/googlecompute/step_create_instance.go index 11ca84edc..e572c441d 100644 --- a/builder/googlecompute/step_create_instance.go +++ b/builder/googlecompute/step_create_instance.go @@ -12,8 +12,6 @@ import ( // StepCreateInstance represents a Packer build step that creates GCE instances. type StepCreateInstance struct { Debug bool - - instanceName string } func (config *Config) getImage() Image { @@ -91,14 +89,18 @@ func (s *StepCreateInstance) Run(state multistep.StateBag) multistep.StepAction // Things succeeded, store the name so we can remove it later state.Put("instance_name", name) - s.instanceName = name return multistep.ActionContinue } // Cleanup destroys the GCE instance created during the image creation process. func (s *StepCreateInstance) Cleanup(state multistep.StateBag) { - if s.instanceName == "" { + nameRaw, ok := state.GetOk("instance_name") + if !ok { + return + } + name := nameRaw.(string) + if name == "" { return } @@ -107,7 +109,7 @@ func (s *StepCreateInstance) Cleanup(state multistep.StateBag) { ui := state.Get("ui").(packer.Ui) ui.Say("Deleting instance...") - errCh, err := driver.DeleteInstance(config.Zone, s.instanceName) + errCh, err := driver.DeleteInstance(config.Zone, name) if err == nil { select { case err = <-errCh: @@ -120,9 +122,9 @@ func (s *StepCreateInstance) Cleanup(state multistep.StateBag) { ui.Error(fmt.Sprintf( "Error deleting instance. Please delete it manually.\n\n"+ "Name: %s\n"+ - "Error: %s", s.instanceName, err)) + "Error: %s", name, err)) } - s.instanceName = "" + state.Put("instance_name", "") return } diff --git a/builder/googlecompute/step_register_image.go b/builder/googlecompute/step_register_image.go deleted file mode 100644 index 84b2a5894..000000000 --- a/builder/googlecompute/step_register_image.go +++ /dev/null @@ -1,46 +0,0 @@ -package googlecompute - -import ( - "errors" - "fmt" - "time" - - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" -) - -// StepRegisterImage represents a Packer build step that registers GCE machine images. -type StepRegisterImage int - -// Run executes the Packer build step that registers a GCE machine image. -func (s *StepRegisterImage) Run(state multistep.StateBag) multistep.StepAction { - config := state.Get("config").(*Config) - driver := state.Get("driver").(Driver) - ui := state.Get("ui").(packer.Ui) - - var err error - imageURL := fmt.Sprintf( - "https://storage.cloud.google.com/%s/%s.tar.gz", - config.BucketName, config.ImageName) - - ui.Say("Registering image...") - errCh := driver.CreateImage(config.ImageName, config.ImageDescription, imageURL) - select { - case err = <-errCh: - case <-time.After(config.stateTimeout): - err = errors.New("time out while waiting for image to register") - } - - if err != nil { - err := fmt.Errorf("Error waiting for image: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - state.Put("image_name", config.ImageName) - return multistep.ActionContinue -} - -// Cleanup. -func (s *StepRegisterImage) Cleanup(state multistep.StateBag) {} diff --git a/builder/googlecompute/step_register_image_test.go b/builder/googlecompute/step_register_image_test.go deleted file mode 100644 index 5faa89b2c..000000000 --- a/builder/googlecompute/step_register_image_test.go +++ /dev/null @@ -1,100 +0,0 @@ -package googlecompute - -import ( - "errors" - "github.com/mitchellh/multistep" - "testing" - "time" -) - -func TestStepRegisterImage_impl(t *testing.T) { - var _ multistep.Step = new(StepRegisterImage) -} - -func TestStepRegisterImage(t *testing.T) { - state := testState(t) - step := new(StepRegisterImage) - defer step.Cleanup(state) - - config := state.Get("config").(*Config) - driver := state.Get("driver").(*DriverMock) - - // run the step - if action := step.Run(state); action != multistep.ActionContinue { - t.Fatalf("bad action: %#v", action) - } - - // Verify state - if driver.CreateImageName != config.ImageName { - t.Fatalf("bad: %#v", driver.CreateImageName) - } - if driver.CreateImageDesc != config.ImageDescription { - t.Fatalf("bad: %#v", driver.CreateImageDesc) - } - - nameRaw, ok := state.GetOk("image_name") - if !ok { - t.Fatal("should have name") - } - if name, ok := nameRaw.(string); !ok { - t.Fatal("name is not a string") - } else if name != config.ImageName { - t.Fatalf("bad name: %s", name) - } -} - -func TestStepRegisterImage_waitError(t *testing.T) { - state := testState(t) - step := new(StepRegisterImage) - defer step.Cleanup(state) - - errCh := make(chan error, 1) - errCh <- errors.New("error") - - driver := state.Get("driver").(*DriverMock) - driver.CreateImageErrCh = errCh - - // run the step - if action := step.Run(state); action != multistep.ActionHalt { - t.Fatalf("bad action: %#v", action) - } - - // Verify state - if _, ok := state.GetOk("error"); !ok { - t.Fatal("should have error") - } - if _, ok := state.GetOk("image_name"); ok { - t.Fatal("should NOT have image_name") - } -} - -func TestStepRegisterImage_errorTimeout(t *testing.T) { - state := testState(t) - step := new(StepRegisterImage) - defer step.Cleanup(state) - - errCh := make(chan error, 1) - go func() { - <-time.After(10 * time.Millisecond) - errCh <- nil - }() - - config := state.Get("config").(*Config) - config.stateTimeout = 1 * time.Microsecond - - driver := state.Get("driver").(*DriverMock) - driver.CreateImageErrCh = errCh - - // run the step - if action := step.Run(state); action != multistep.ActionHalt { - t.Fatalf("bad action: %#v", action) - } - - // Verify state - if _, ok := state.GetOk("error"); !ok { - t.Fatal("should have error") - } - if _, ok := state.GetOk("image_name"); ok { - t.Fatal("should NOT have image name") - } -} diff --git a/builder/googlecompute/step_teardown_instance.go b/builder/googlecompute/step_teardown_instance.go new file mode 100644 index 000000000..b623d24fd --- /dev/null +++ b/builder/googlecompute/step_teardown_instance.go @@ -0,0 +1,80 @@ +package googlecompute + +import ( + "errors" + "fmt" + "time" + + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +// StepTeardownInstance represents a Packer build step that tears down GCE +// instances. +type StepTeardownInstance struct { + Debug bool +} + +// Run executes the Packer build step that tears down a GCE instance. +func (s *StepTeardownInstance) Run(state multistep.StateBag) multistep.StepAction { + config := state.Get("config").(*Config) + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + + name := config.InstanceName + if name == "" { + return multistep.ActionHalt + } + + ui.Say("Deleting instance...") + errCh, err := driver.DeleteInstance(config.Zone, name) + if err == nil { + select { + case err = <-errCh: + case <-time.After(config.stateTimeout): + err = errors.New("time out while waiting for instance to delete") + } + } + + if err != nil { + ui.Error(fmt.Sprintf( + "Error deleting instance. Please delete it manually.\n\n"+ + "Name: %s\n"+ + "Error: %s", name, err)) + return multistep.ActionHalt + } + + ui.Message("Instance has been deleted!") + state.Put("instance_name", "") + + return multistep.ActionContinue +} + +// Deleting the instance does not remove the boot disk. This cleanup removes +// the disk. +func (s *StepTeardownInstance) Cleanup(state multistep.StateBag) { + config := state.Get("config").(*Config) + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + + ui.Say("Deleting disk...") + errCh, err := driver.DeleteDisk(config.Zone, config.DiskName) + if err == nil { + select { + case err = <-errCh: + case <-time.After(config.stateTimeout): + err = errors.New("time out while waiting for disk to delete") + } + } + + if err != nil { + ui.Error(fmt.Sprintf( + "Error deleting disk. Please delete it manually.\n\n"+ + "Name: %s\n"+ + "Error: %s", config.InstanceName, err)) + } + + ui.Message("Disk has been deleted!") + + return +} diff --git a/builder/googlecompute/step_teardown_instance_test.go b/builder/googlecompute/step_teardown_instance_test.go new file mode 100644 index 000000000..f007d5179 --- /dev/null +++ b/builder/googlecompute/step_teardown_instance_test.go @@ -0,0 +1,41 @@ +package googlecompute + +import ( + "github.com/mitchellh/multistep" + "testing" +) + +func TestStepTeardownInstance_impl(t *testing.T) { + var _ multistep.Step = new(StepTeardownInstance) +} + +func TestStepTeardownInstance(t *testing.T) { + state := testState(t) + step := new(StepTeardownInstance) + defer step.Cleanup(state) + + config := state.Get("config").(*Config) + driver := state.Get("driver").(*DriverMock) + + // run the step + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + + if driver.DeleteInstanceName != config.InstanceName { + t.Fatal("should've deleted instance") + } + if driver.DeleteInstanceZone != config.Zone { + t.Fatal("bad zone: %#v", driver.DeleteInstanceZone) + } + + // cleanup + step.Cleanup(state) + + if driver.DeleteDiskName != config.InstanceName { + t.Fatal("should've deleted disk") + } + if driver.DeleteDiskZone != config.Zone { + t.Fatal("bad zone: %#v", driver.DeleteDiskZone) + } +} diff --git a/builder/googlecompute/step_update_gcloud.go b/builder/googlecompute/step_update_gcloud.go deleted file mode 100644 index 3bfc6f2a7..000000000 --- a/builder/googlecompute/step_update_gcloud.go +++ /dev/null @@ -1,52 +0,0 @@ -package googlecompute - -import ( - "fmt" - - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" -) - -// StepUpdateGcloud represents a Packer build step that updates the gsutil -// utility to the latest version available. -type StepUpdateGcloud int - -// Run executes the Packer build step that updates the gsutil utility to the -// latest version available. -// -// This step is required to prevent the image creation process from hanging; -// the image creation process utilizes the gcimagebundle cli tool which will -// prompt to update gsutil if a newer version is available. -func (s *StepUpdateGcloud) Run(state multistep.StateBag) multistep.StepAction { - comm := state.Get("communicator").(packer.Communicator) - config := state.Get("config").(*Config) - ui := state.Get("ui").(packer.Ui) - - sudoPrefix := "" - - if config.SSHUsername != "root" { - sudoPrefix = "sudo " - } - - gsutilUpdateCmd := "/usr/local/bin/gcloud -q components update" - cmd := new(packer.RemoteCmd) - cmd.Command = fmt.Sprintf("%s%s", sudoPrefix, gsutilUpdateCmd) - - ui.Say("Updating gcloud components...") - err := cmd.StartWithUi(comm, ui) - if err == nil && cmd.ExitStatus != 0 { - err = fmt.Errorf( - "gcloud components update exited with non-zero exit status: %d", cmd.ExitStatus) - } - if err != nil { - err := fmt.Errorf("Error updating gcloud components: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - return multistep.ActionContinue -} - -// Cleanup. -func (s *StepUpdateGcloud) Cleanup(state multistep.StateBag) {} diff --git a/builder/googlecompute/step_update_gcloud_test.go b/builder/googlecompute/step_update_gcloud_test.go deleted file mode 100644 index b01406f10..000000000 --- a/builder/googlecompute/step_update_gcloud_test.go +++ /dev/null @@ -1,85 +0,0 @@ -package googlecompute - -import ( - "strings" - "testing" - - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" -) - -func TestStepUpdateGcloud_impl(t *testing.T) { - var _ multistep.Step = new(StepUpdateGcloud) -} - -func TestStepUpdateGcloud(t *testing.T) { - state := testState(t) - step := new(StepUpdateGcloud) - defer step.Cleanup(state) - - comm := new(packer.MockCommunicator) - state.Put("communicator", comm) - - // run the step - if action := step.Run(state); action != multistep.ActionContinue { - t.Fatalf("bad action: %#v", action) - } - - // Verify - if !comm.StartCalled { - t.Fatal("start should be called") - } - if strings.HasPrefix(comm.StartCmd.Command, "sudo") { - t.Fatal("should not sudo") - } - if !strings.Contains(comm.StartCmd.Command, "gcloud -q components update") { - t.Fatalf("bad command: %#v", comm.StartCmd.Command) - } -} - -func TestStepUpdateGcloud_badExitStatus(t *testing.T) { - state := testState(t) - step := new(StepUpdateGcloud) - defer step.Cleanup(state) - - comm := new(packer.MockCommunicator) - comm.StartExitStatus = 12 - state.Put("communicator", comm) - - // run the step - if action := step.Run(state); action != multistep.ActionHalt { - t.Fatalf("bad action: %#v", action) - } - - if _, ok := state.GetOk("error"); !ok { - t.Fatal("should have error") - } -} - -func TestStepUpdateGcloud_nonRoot(t *testing.T) { - state := testState(t) - step := new(StepUpdateGcloud) - defer step.Cleanup(state) - - comm := new(packer.MockCommunicator) - state.Put("communicator", comm) - - config := state.Get("config").(*Config) - config.SSHUsername = "bob" - - // run the step - if action := step.Run(state); action != multistep.ActionContinue { - t.Fatalf("bad action: %#v", action) - } - - // Verify - if !comm.StartCalled { - t.Fatal("start should be called") - } - if !strings.HasPrefix(comm.StartCmd.Command, "sudo") { - t.Fatal("should sudo") - } - if !strings.Contains(comm.StartCmd.Command, "gcloud -q components update") { - t.Fatalf("bad command: %#v", comm.StartCmd.Command) - } -} diff --git a/builder/googlecompute/step_upload_image.go b/builder/googlecompute/step_upload_image.go deleted file mode 100644 index 45505f20b..000000000 --- a/builder/googlecompute/step_upload_image.go +++ /dev/null @@ -1,45 +0,0 @@ -package googlecompute - -import ( - "fmt" - - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" -) - -// StepUploadImage represents a Packer build step that uploads GCE machine images. -type StepUploadImage int - -// Run executes the Packer build step that uploads a GCE machine image. -func (s *StepUploadImage) Run(state multistep.StateBag) multistep.StepAction { - comm := state.Get("communicator").(packer.Communicator) - config := state.Get("config").(*Config) - imageFilename := state.Get("image_file_name").(string) - ui := state.Get("ui").(packer.Ui) - - sudoPrefix := "" - if config.SSHUsername != "root" { - sudoPrefix = "sudo " - } - - ui.Say("Uploading image...") - cmd := new(packer.RemoteCmd) - cmd.Command = fmt.Sprintf("%s/usr/local/bin/gsutil cp %s gs://%s", - sudoPrefix, imageFilename, config.BucketName) - err := cmd.StartWithUi(comm, ui) - if err == nil && cmd.ExitStatus != 0 { - err = fmt.Errorf( - "gsutil exited with non-zero exit status: %d", cmd.ExitStatus) - } - if err != nil { - err := fmt.Errorf("Error uploading image: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - return multistep.ActionContinue -} - -// Cleanup. -func (s *StepUploadImage) Cleanup(state multistep.StateBag) {} diff --git a/builder/googlecompute/step_upload_image_test.go b/builder/googlecompute/step_upload_image_test.go deleted file mode 100644 index 54be445fc..000000000 --- a/builder/googlecompute/step_upload_image_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package googlecompute - -import ( - "strings" - "testing" - - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" -) - -func TestStepUploadImage_impl(t *testing.T) { - var _ multistep.Step = new(StepUploadImage) -} - -func TestStepUploadImage(t *testing.T) { - state := testState(t) - step := new(StepUploadImage) - defer step.Cleanup(state) - - comm := new(packer.MockCommunicator) - state.Put("communicator", comm) - state.Put("image_file_name", "foo") - - // run the step - if action := step.Run(state); action != multistep.ActionContinue { - t.Fatalf("bad action: %#v", action) - } - - // Verify - if !comm.StartCalled { - t.Fatal("start should be called") - } - if strings.HasPrefix(comm.StartCmd.Command, "sudo") { - t.Fatal("should not sudo") - } - if !strings.Contains(comm.StartCmd.Command, "gsutil cp") { - t.Fatalf("bad command: %#v", comm.StartCmd.Command) - } -} - -func TestStepUploadImage_badExitStatus(t *testing.T) { - state := testState(t) - step := new(StepUploadImage) - defer step.Cleanup(state) - - comm := new(packer.MockCommunicator) - comm.StartExitStatus = 12 - state.Put("communicator", comm) - state.Put("image_file_name", "foo") - - // run the step - if action := step.Run(state); action != multistep.ActionHalt { - t.Fatalf("bad action: %#v", action) - } - - if _, ok := state.GetOk("error"); !ok { - t.Fatal("should have error") - } -} - -func TestStepUploadImage_nonRoot(t *testing.T) { - state := testState(t) - step := new(StepUploadImage) - defer step.Cleanup(state) - - comm := new(packer.MockCommunicator) - state.Put("communicator", comm) - state.Put("image_file_name", "foo") - - config := state.Get("config").(*Config) - config.SSHUsername = "bob" - - // run the step - if action := step.Run(state); action != multistep.ActionContinue { - t.Fatalf("bad action: %#v", action) - } - - // Verify - if !comm.StartCalled { - t.Fatal("start should be called") - } - if !strings.HasPrefix(comm.StartCmd.Command, "sudo") { - t.Fatal("should sudo") - } - if !strings.Contains(comm.StartCmd.Command, "gsutil cp") { - t.Fatalf("bad command: %#v", comm.StartCmd.Command) - } -}