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,
|
||||
},
|
||||
new(common.StepProvision),
|
||||
new(StepUpdateGcloud),
|
||||
new(StepTeardownInstance),
|
||||
new(StepCreateImage),
|
||||
new(StepUploadImage),
|
||||
new(StepRegisterImage),
|
||||
}
|
||||
|
||||
// Run the steps.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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