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 driver.CreateImageDesc != config.ImageDescription { + t.Fatalf("bad: %#v", driver.CreateImageDesc) } - if strings.HasPrefix(comm.StartCmd.Command, "sudo") { - t.Fatal("should not sudo") + if driver.CreateImageZone != config.Zone { + t.Fatalf("bad: %#v", driver.CreateImageZone) } - if !strings.Contains(comm.StartCmd.Command, "gcimagebundle") { - t.Fatalf("bad command: %#v", comm.StartCmd.Command) + 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) - } -}