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.
This commit is contained in:
parent
87001dba60
commit
23c947acf0
|
@ -65,10 +65,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
SSHWaitTimeout: 5 * time.Minute,
|
SSHWaitTimeout: 5 * time.Minute,
|
||||||
},
|
},
|
||||||
new(common.StepProvision),
|
new(common.StepProvision),
|
||||||
new(StepUpdateGcloud),
|
new(StepTeardownInstance),
|
||||||
new(StepCreateImage),
|
new(StepCreateImage),
|
||||||
new(StepUploadImage),
|
|
||||||
new(StepRegisterImage),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the steps.
|
// Run the steps.
|
||||||
|
|
|
@ -20,6 +20,7 @@ type Config struct {
|
||||||
ProjectId string `mapstructure:"project_id"`
|
ProjectId string `mapstructure:"project_id"`
|
||||||
|
|
||||||
BucketName string `mapstructure:"bucket_name"`
|
BucketName string `mapstructure:"bucket_name"`
|
||||||
|
DiskName string `mapstructure:"disk_name"`
|
||||||
DiskSizeGb int64 `mapstructure:"disk_size"`
|
DiskSizeGb int64 `mapstructure:"disk_size"`
|
||||||
ImageName string `mapstructure:"image_name"`
|
ImageName string `mapstructure:"image_name"`
|
||||||
ImageDescription string `mapstructure:"image_description"`
|
ImageDescription string `mapstructure:"image_description"`
|
||||||
|
@ -37,7 +38,6 @@ type Config struct {
|
||||||
Zone string `mapstructure:"zone"`
|
Zone string `mapstructure:"zone"`
|
||||||
|
|
||||||
account accountFile
|
account accountFile
|
||||||
instanceName string
|
|
||||||
privateKeyBytes []byte
|
privateKeyBytes []byte
|
||||||
sshTimeout time.Duration
|
sshTimeout time.Duration
|
||||||
stateTimeout time.Duration
|
stateTimeout time.Duration
|
||||||
|
@ -81,6 +81,10 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
||||||
c.InstanceName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
c.InstanceName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.DiskName == "" {
|
||||||
|
c.DiskName = c.InstanceName
|
||||||
|
}
|
||||||
|
|
||||||
if c.MachineType == "" {
|
if c.MachineType == "" {
|
||||||
c.MachineType = "n1-standard-1"
|
c.MachineType = "n1-standard-1"
|
||||||
}
|
}
|
||||||
|
@ -106,6 +110,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
||||||
"account_file": &c.AccountFile,
|
"account_file": &c.AccountFile,
|
||||||
|
|
||||||
"bucket_name": &c.BucketName,
|
"bucket_name": &c.BucketName,
|
||||||
|
"disk_name": &c.DiskName,
|
||||||
"image_name": &c.ImageName,
|
"image_name": &c.ImageName,
|
||||||
"image_description": &c.ImageDescription,
|
"image_description": &c.ImageDescription,
|
||||||
"instance_name": &c.InstanceName,
|
"instance_name": &c.InstanceName,
|
||||||
|
|
|
@ -4,15 +4,19 @@ package googlecompute
|
||||||
// with GCE. The Driver interface exists mostly to allow a mock implementation
|
// with GCE. The Driver interface exists mostly to allow a mock implementation
|
||||||
// to be used to test the steps.
|
// to be used to test the steps.
|
||||||
type Driver interface {
|
type Driver interface {
|
||||||
// CreateImage creates an image with the given URL in Google Storage.
|
// CreateImage creates an image from the given disk in Google Compute
|
||||||
CreateImage(name, description, url string) <-chan error
|
// Engine.
|
||||||
|
CreateImage(name, description, zone, disk string) <-chan error
|
||||||
|
|
||||||
// DeleteImage deletes the image with the given name.
|
// DeleteImage deletes the image with the given name.
|
||||||
DeleteImage(name string) <-chan error
|
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)
|
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 gets the NAT IP address for the instance.
|
||||||
GetNatIP(zone, name string) (string, error)
|
GetNatIP(zone, name string) (string, error)
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ type driverGCE struct {
|
||||||
var DriverScopes = []string{"https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/devstorage.full_control"}
|
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) {
|
func NewDriverGCE(ui packer.Ui, p string, a *accountFile) (Driver, error) {
|
||||||
var f *oauth2.Flow
|
var f *oauth2.Options
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Auth with AccountFile first if provided
|
// Auth with AccountFile first if provided
|
||||||
|
@ -60,14 +60,11 @@ func NewDriverGCE(ui packer.Ui, p string, a *accountFile) (Driver, error) {
|
||||||
}, nil
|
}, 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{
|
image := &compute.Image{
|
||||||
Description: description,
|
Description: description,
|
||||||
Name: name,
|
Name: name,
|
||||||
RawDisk: &compute.ImageRawDisk{
|
SourceDisk: fmt.Sprintf("%s%s/zones/%s/disks/%s", d.service.BasePath, d.projectId, zone, disk),
|
||||||
ContainerType: "TAR",
|
|
||||||
Source: url,
|
|
||||||
},
|
|
||||||
SourceType: "RAW",
|
SourceType: "RAW",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,6 +102,17 @@ func (d *driverGCE) DeleteInstance(zone, name string) (<-chan error, error) {
|
||||||
return errCh, nil
|
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) {
|
func (d *driverGCE) GetNatIP(zone, name string) (string, error) {
|
||||||
instance, err := d.service.Instances.Get(d.projectId, zone, name).Do()
|
instance, err := d.service.Instances.Get(d.projectId, zone, name).Do()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -175,7 +183,7 @@ func (d *driverGCE) RunInstance(c *InstanceConfig) (<-chan error, error) {
|
||||||
Mode: "READ_WRITE",
|
Mode: "READ_WRITE",
|
||||||
Kind: "compute#attachedDisk",
|
Kind: "compute#attachedDisk",
|
||||||
Boot: true,
|
Boot: true,
|
||||||
AutoDelete: true,
|
AutoDelete: false,
|
||||||
InitializeParams: &compute.AttachedDiskInitializeParams{
|
InitializeParams: &compute.AttachedDiskInitializeParams{
|
||||||
SourceImage: image.SelfLink,
|
SourceImage: image.SelfLink,
|
||||||
DiskSizeGb: c.DiskSizeGb,
|
DiskSizeGb: c.DiskSizeGb,
|
||||||
|
|
|
@ -5,7 +5,8 @@ package googlecompute
|
||||||
type DriverMock struct {
|
type DriverMock struct {
|
||||||
CreateImageName string
|
CreateImageName string
|
||||||
CreateImageDesc string
|
CreateImageDesc string
|
||||||
CreateImageURL string
|
CreateImageZone string
|
||||||
|
CreateImageDisk string
|
||||||
CreateImageErrCh <-chan error
|
CreateImageErrCh <-chan error
|
||||||
|
|
||||||
DeleteImageName string
|
DeleteImageName string
|
||||||
|
@ -16,6 +17,11 @@ type DriverMock struct {
|
||||||
DeleteInstanceErrCh <-chan error
|
DeleteInstanceErrCh <-chan error
|
||||||
DeleteInstanceErr error
|
DeleteInstanceErr error
|
||||||
|
|
||||||
|
DeleteDiskZone string
|
||||||
|
DeleteDiskName string
|
||||||
|
DeleteDiskErrCh <-chan error
|
||||||
|
DeleteDiskErr error
|
||||||
|
|
||||||
GetNatIPZone string
|
GetNatIPZone string
|
||||||
GetNatIPName string
|
GetNatIPName string
|
||||||
GetNatIPResult string
|
GetNatIPResult string
|
||||||
|
@ -31,10 +37,11 @@ type DriverMock struct {
|
||||||
WaitForInstanceErrCh <-chan error
|
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.CreateImageName = name
|
||||||
d.CreateImageDesc = description
|
d.CreateImageDesc = description
|
||||||
d.CreateImageURL = url
|
d.CreateImageZone = zone
|
||||||
|
d.CreateImageDisk = disk
|
||||||
|
|
||||||
resultCh := d.CreateImageErrCh
|
resultCh := d.CreateImageErrCh
|
||||||
if resultCh == nil {
|
if resultCh == nil {
|
||||||
|
@ -73,6 +80,20 @@ func (d *DriverMock) DeleteInstance(zone, name string) (<-chan error, error) {
|
||||||
return resultCh, d.DeleteInstanceErr
|
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) {
|
func (d *DriverMock) GetNatIP(zone, name string) (string, error) {
|
||||||
d.GetNatIPZone = zone
|
d.GetNatIPZone = zone
|
||||||
d.GetNatIPName = name
|
d.GetNatIPName = name
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
package googlecompute
|
package googlecompute
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"time"
|
||||||
|
|
||||||
"github.com/mitchellh/multistep"
|
"github.com/mitchellh/multistep"
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
|
@ -14,39 +15,32 @@ type StepCreateImage int
|
||||||
|
|
||||||
// Run executes the Packer build step that creates a GCE machine image.
|
// 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
|
// The image is created from the persistent disk used by the instance. The
|
||||||
// command on the running GCE instance.
|
// instance must be deleted and the disk retained before doing this step.
|
||||||
func (s *StepCreateImage) Run(state multistep.StateBag) multistep.StepAction {
|
func (s *StepCreateImage) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
config := state.Get("config").(*Config)
|
config := state.Get("config").(*Config)
|
||||||
comm := state.Get("communicator").(packer.Communicator)
|
driver := state.Get("driver").(Driver)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
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...")
|
ui.Say("Creating image...")
|
||||||
cmd := new(packer.RemoteCmd)
|
errCh := driver.CreateImage(config.ImageName, config.ImageDescription, config.Zone, config.DiskName)
|
||||||
cmd.Command = fmt.Sprintf("%s%s --output_file_name %s --fssize %d",
|
var err error
|
||||||
sudoPrefix, imageBundleCmd, imageFilename, config.DiskSizeGb*1024*1024*1024)
|
select {
|
||||||
err := cmd.StartWithUi(comm, ui)
|
case err = <-errCh:
|
||||||
if err == nil && cmd.ExitStatus != 0 {
|
case <-time.After(config.stateTimeout):
|
||||||
err = fmt.Errorf(
|
err = errors.New("time out while waiting for image to register")
|
||||||
"gcimagebundle exited with non-zero exit status: %d", cmd.ExitStatus)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error creating image: %s", err)
|
err := fmt.Errorf("Error waiting for image: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
|
||||||
state.Put("image_file_name", filepath.Join("/tmp", imageFilename))
|
state.Put("image_name", config.ImageName)
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cleanup.
|
||||||
func (s *StepCreateImage) Cleanup(state multistep.StateBag) {}
|
func (s *StepCreateImage) Cleanup(state multistep.StateBag) {}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
package googlecompute
|
package googlecompute
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/mitchellh/multistep"
|
"github.com/mitchellh/multistep"
|
||||||
"github.com/mitchellh/packer/packer"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStepCreateImage_impl(t *testing.T) {
|
func TestStepCreateImage_impl(t *testing.T) {
|
||||||
|
@ -17,38 +16,49 @@ func TestStepCreateImage(t *testing.T) {
|
||||||
step := new(StepCreateImage)
|
step := new(StepCreateImage)
|
||||||
defer step.Cleanup(state)
|
defer step.Cleanup(state)
|
||||||
|
|
||||||
comm := new(packer.MockCommunicator)
|
config := state.Get("config").(*Config)
|
||||||
state.Put("communicator", comm)
|
driver := state.Get("driver").(*DriverMock)
|
||||||
|
|
||||||
// run the step
|
// run the step
|
||||||
if action := step.Run(state); action != multistep.ActionContinue {
|
if action := step.Run(state); action != multistep.ActionContinue {
|
||||||
t.Fatalf("bad action: %#v", action)
|
t.Fatalf("bad action: %#v", action)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify
|
// Verify state
|
||||||
if !comm.StartCalled {
|
if driver.CreateImageName != config.ImageName {
|
||||||
t.Fatal("start should be called")
|
t.Fatalf("bad: %#v", driver.CreateImageName)
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(comm.StartCmd.Command, "sudo") {
|
if driver.CreateImageDesc != config.ImageDescription {
|
||||||
t.Fatal("should not sudo")
|
t.Fatalf("bad: %#v", driver.CreateImageDesc)
|
||||||
}
|
}
|
||||||
if !strings.Contains(comm.StartCmd.Command, "gcimagebundle") {
|
if driver.CreateImageZone != config.Zone {
|
||||||
t.Fatalf("bad command: %#v", comm.StartCmd.Command)
|
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 {
|
nameRaw, ok := state.GetOk("image_name")
|
||||||
t.Fatal("should have image")
|
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)
|
state := testState(t)
|
||||||
step := new(StepCreateImage)
|
step := new(StepCreateImage)
|
||||||
defer step.Cleanup(state)
|
defer step.Cleanup(state)
|
||||||
|
|
||||||
comm := new(packer.MockCommunicator)
|
errCh := make(chan error, 1)
|
||||||
comm.StartExitStatus = 12
|
errCh <- errors.New("error")
|
||||||
state.Put("communicator", comm)
|
|
||||||
|
driver := state.Get("driver").(*DriverMock)
|
||||||
|
driver.CreateImageErrCh = errCh
|
||||||
|
|
||||||
// run the step
|
// run the step
|
||||||
if action := step.Run(state); action != multistep.ActionHalt {
|
if action := step.Run(state); action != multistep.ActionHalt {
|
||||||
|
@ -58,39 +68,7 @@ func TestStepCreateImage_badExitStatus(t *testing.T) {
|
||||||
if _, ok := state.GetOk("error"); !ok {
|
if _, ok := state.GetOk("error"); !ok {
|
||||||
t.Fatal("should have error")
|
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")
|
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -12,8 +12,6 @@ import (
|
||||||
// StepCreateInstance represents a Packer build step that creates GCE instances.
|
// StepCreateInstance represents a Packer build step that creates GCE instances.
|
||||||
type StepCreateInstance struct {
|
type StepCreateInstance struct {
|
||||||
Debug bool
|
Debug bool
|
||||||
|
|
||||||
instanceName string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (config *Config) getImage() Image {
|
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
|
// Things succeeded, store the name so we can remove it later
|
||||||
state.Put("instance_name", name)
|
state.Put("instance_name", name)
|
||||||
s.instanceName = name
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup destroys the GCE instance created during the image creation process.
|
// Cleanup destroys the GCE instance created during the image creation process.
|
||||||
func (s *StepCreateInstance) Cleanup(state multistep.StateBag) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +109,7 @@ func (s *StepCreateInstance) Cleanup(state multistep.StateBag) {
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
ui.Say("Deleting instance...")
|
ui.Say("Deleting instance...")
|
||||||
errCh, err := driver.DeleteInstance(config.Zone, s.instanceName)
|
errCh, err := driver.DeleteInstance(config.Zone, name)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
select {
|
select {
|
||||||
case err = <-errCh:
|
case err = <-errCh:
|
||||||
|
@ -120,9 +122,9 @@ func (s *StepCreateInstance) Cleanup(state multistep.StateBag) {
|
||||||
ui.Error(fmt.Sprintf(
|
ui.Error(fmt.Sprintf(
|
||||||
"Error deleting instance. Please delete it manually.\n\n"+
|
"Error deleting instance. Please delete it manually.\n\n"+
|
||||||
"Name: %s\n"+
|
"Name: %s\n"+
|
||||||
"Error: %s", s.instanceName, err))
|
"Error: %s", name, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
s.instanceName = ""
|
state.Put("instance_name", "")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {}
|
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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) {}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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) {}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue