builder/googlecompute: driver and create instance tests
This commit is contained in:
parent
3657f33a4d
commit
2bd6f1e2d7
|
@ -20,6 +20,7 @@ type GoogleComputeClient struct {
|
|||
|
||||
// InstanceConfig represents a GCE instance configuration.
|
||||
// Used for creating machine instances.
|
||||
/*
|
||||
type InstanceConfig struct {
|
||||
Description string
|
||||
Image string
|
||||
|
@ -30,6 +31,7 @@ type InstanceConfig struct {
|
|||
ServiceAccounts []*compute.ServiceAccount
|
||||
Tags *compute.Tags
|
||||
}
|
||||
*/
|
||||
|
||||
// New initializes and returns a *GoogleComputeClient.
|
||||
//
|
||||
|
@ -41,7 +43,7 @@ func New(projectId string, zone string, c *clientSecrets, pemKey []byte) (*Googl
|
|||
Zone: zone,
|
||||
}
|
||||
// Get the access token.
|
||||
t := jwt.NewToken(c.Web.ClientEmail, scopes(), pemKey)
|
||||
t := jwt.NewToken(c.Web.ClientEmail, "", pemKey)
|
||||
t.ClaimSet.Aud = c.Web.TokenURI
|
||||
httpClient := &http.Client{}
|
||||
token, err := t.Assert(httpClient)
|
||||
|
@ -50,7 +52,7 @@ func New(projectId string, zone string, c *clientSecrets, pemKey []byte) (*Googl
|
|||
}
|
||||
config := &oauth.Config{
|
||||
ClientId: c.Web.ClientId,
|
||||
Scope: scopes(),
|
||||
Scope: "",
|
||||
TokenURL: c.Web.TokenURI,
|
||||
AuthURL: c.Web.AuthURI,
|
||||
}
|
||||
|
@ -64,83 +66,6 @@ func New(projectId string, zone string, c *clientSecrets, pemKey []byte) (*Googl
|
|||
return googleComputeClient, nil
|
||||
}
|
||||
|
||||
// GetZone returns a *compute.Zone representing the named zone.
|
||||
func (g *GoogleComputeClient) GetZone(name string) (*compute.Zone, error) {
|
||||
zoneGetCall := g.Service.Zones.Get(g.ProjectId, name)
|
||||
zone, err := zoneGetCall.Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return zone, nil
|
||||
}
|
||||
|
||||
// GetMachineType returns a *compute.MachineType representing the named machine type.
|
||||
func (g *GoogleComputeClient) GetMachineType(name, zone string) (*compute.MachineType, error) {
|
||||
machineTypesGetCall := g.Service.MachineTypes.Get(g.ProjectId, zone, name)
|
||||
machineType, err := machineTypesGetCall.Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if machineType.Deprecated == nil {
|
||||
return machineType, nil
|
||||
}
|
||||
return nil, errors.New("Machine Type does not exist: " + name)
|
||||
}
|
||||
|
||||
// GetImage returns a *compute.Image representing the named image.
|
||||
func (g *GoogleComputeClient) GetImage(name string) (*compute.Image, error) {
|
||||
var err error
|
||||
var image *compute.Image
|
||||
projects := []string{g.ProjectId, "debian-cloud", "centos-cloud"}
|
||||
for _, project := range projects {
|
||||
imagesGetCall := g.Service.Images.Get(project, name)
|
||||
image, err = imagesGetCall.Do()
|
||||
if image != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if image != nil {
|
||||
if image.SelfLink != "" {
|
||||
return image, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("Image does not exist: " + name)
|
||||
}
|
||||
|
||||
// GetNetwork returns a *compute.Network representing the named network.
|
||||
func (g *GoogleComputeClient) GetNetwork(name string) (*compute.Network, error) {
|
||||
networkGetCall := g.Service.Networks.Get(g.ProjectId, name)
|
||||
network, err := networkGetCall.Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return network, nil
|
||||
}
|
||||
|
||||
// CreateInstance creates an instance in Google Compute Engine based on the
|
||||
// supplied instanceConfig.
|
||||
func (g *GoogleComputeClient) CreateInstance(zone string, instanceConfig *InstanceConfig) (*compute.Operation, error) {
|
||||
instance := &compute.Instance{
|
||||
Description: instanceConfig.Description,
|
||||
Image: instanceConfig.Image,
|
||||
MachineType: instanceConfig.MachineType,
|
||||
Metadata: instanceConfig.Metadata,
|
||||
Name: instanceConfig.Name,
|
||||
NetworkInterfaces: instanceConfig.NetworkInterfaces,
|
||||
ServiceAccounts: instanceConfig.ServiceAccounts,
|
||||
Tags: instanceConfig.Tags,
|
||||
}
|
||||
instanceInsertCall := g.Service.Instances.Insert(g.ProjectId, zone, instance)
|
||||
operation, err := instanceInsertCall.Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return operation, nil
|
||||
}
|
||||
|
||||
// InstanceStatus returns a string representing the status of the named instance.
|
||||
// Status will be one of: "PROVISIONING", "STAGING", "RUNNING", "STOPPING",
|
||||
// "STOPPED", "TERMINATED".
|
||||
|
@ -256,59 +181,3 @@ func (g *GoogleComputeClient) DeleteInstance(zone, name string) (*compute.Operat
|
|||
}
|
||||
return operation, nil
|
||||
}
|
||||
|
||||
// NewNetworkInterface returns a *compute.NetworkInterface based on the data provided.
|
||||
func NewNetworkInterface(network *compute.Network, public bool) *compute.NetworkInterface {
|
||||
accessConfigs := make([]*compute.AccessConfig, 0)
|
||||
if public {
|
||||
c := &compute.AccessConfig{
|
||||
Name: "AccessConfig created by Packer",
|
||||
Type: "ONE_TO_ONE_NAT",
|
||||
}
|
||||
accessConfigs = append(accessConfigs, c)
|
||||
}
|
||||
return &compute.NetworkInterface{
|
||||
AccessConfigs: accessConfigs,
|
||||
Network: network.SelfLink,
|
||||
}
|
||||
}
|
||||
|
||||
// NewServiceAccount returns a *compute.ServiceAccount with permissions required
|
||||
// for creating GCE machine images.
|
||||
func NewServiceAccount(email string) *compute.ServiceAccount {
|
||||
return &compute.ServiceAccount{
|
||||
Email: email,
|
||||
Scopes: []string{
|
||||
"https://www.googleapis.com/auth/userinfo.email",
|
||||
"https://www.googleapis.com/auth/compute",
|
||||
"https://www.googleapis.com/auth/devstorage.full_control",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// MapToMetadata converts a map[string]string to a *compute.Metadata.
|
||||
func MapToMetadata(metadata map[string]string) *compute.Metadata {
|
||||
items := make([]*compute.MetadataItems, len(metadata))
|
||||
for k, v := range metadata {
|
||||
items = append(items, &compute.MetadataItems{k, v})
|
||||
}
|
||||
return &compute.Metadata{
|
||||
Items: items,
|
||||
}
|
||||
}
|
||||
|
||||
// SliceToTags converts a []string to a *compute.Tags.
|
||||
func SliceToTags(tags []string) *compute.Tags {
|
||||
return &compute.Tags{
|
||||
Items: tags,
|
||||
}
|
||||
}
|
||||
|
||||
// scopes return a space separated list of scopes.
|
||||
func scopes() string {
|
||||
s := []string{
|
||||
"https://www.googleapis.com/auth/compute",
|
||||
"https://www.googleapis.com/auth/devstorage.full_control",
|
||||
}
|
||||
return strings.Join(s, " ")
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
// Build the steps.
|
||||
steps := []multistep.Step{
|
||||
new(StepCreateSSHKey),
|
||||
new(stepCreateInstance),
|
||||
new(StepCreateInstance),
|
||||
new(stepInstanceInfo),
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: sshAddress,
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package googlecompute
|
||||
|
||||
// Driver is the interface that has to be implemented to communicate
|
||||
// with GCE. The Driver interface exists mostly to allow a mock implementation
|
||||
// to be used to test the steps.
|
||||
type Driver interface {
|
||||
// RunInstance takes the given config and launches an instance.
|
||||
RunInstance(*InstanceConfig) (<-chan error, error)
|
||||
}
|
||||
|
||||
type InstanceConfig struct {
|
||||
Description string
|
||||
Image string
|
||||
MachineType string
|
||||
Metadata map[string]string
|
||||
Name string
|
||||
Network string
|
||||
Tags []string
|
||||
Zone string
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
package googlecompute
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"code.google.com/p/goauth2/oauth"
|
||||
"code.google.com/p/goauth2/oauth/jwt"
|
||||
"code.google.com/p/google-api-go-client/compute/v1beta16"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
// driverGCE is a Driver implementation that actually talks to GCE.
|
||||
// Create an instance using NewDriverGCE.
|
||||
type driverGCE struct {
|
||||
projectId string
|
||||
service *compute.Service
|
||||
ui packer.Ui
|
||||
}
|
||||
|
||||
const DriverScopes string = "https://www.googleapis.com/auth/compute " +
|
||||
"https://www.googleapis.com/auth/devstorage.full_control"
|
||||
|
||||
func NewDriverGCE(ui packer.Ui, projectId string, c *clientSecrets, key []byte) (Driver, error) {
|
||||
jwtTok := jwt.NewToken(c.Web.ClientEmail, DriverScopes, key)
|
||||
jwtTok.ClaimSet.Aud = c.Web.TokenURI
|
||||
token, err := jwtTok.Assert(new(http.Client))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
transport := &oauth.Transport{
|
||||
Config: &oauth.Config{
|
||||
ClientId: c.Web.ClientId,
|
||||
Scope: DriverScopes,
|
||||
TokenURL: c.Web.TokenURI,
|
||||
AuthURL: c.Web.AuthURI,
|
||||
},
|
||||
Token: token,
|
||||
}
|
||||
|
||||
service, err := compute.New(transport.Client())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &driverGCE{
|
||||
projectId: projectId,
|
||||
service: service,
|
||||
ui: ui,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *driverGCE) RunInstance(c *InstanceConfig) (<-chan error, error) {
|
||||
// Get the zone
|
||||
d.ui.Message(fmt.Sprintf("Loading zone: %s", c.Zone))
|
||||
zone, err := d.service.Zones.Get(d.projectId, c.Zone).Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the image
|
||||
d.ui.Message(fmt.Sprintf("Loading image: %s", c.Image))
|
||||
image, err := d.getImage(c.Image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the machine type
|
||||
d.ui.Message(fmt.Sprintf("Loading machine type: %s", c.MachineType))
|
||||
machineType, err := d.service.MachineTypes.Get(
|
||||
d.projectId, zone.Name, c.MachineType).Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO(mitchellh): deprecation warnings
|
||||
|
||||
// Get the network
|
||||
d.ui.Message(fmt.Sprintf("Loading network: %s", c.Network))
|
||||
network, err := d.service.Networks.Get(d.projectId, c.Network).Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Build up the metadata
|
||||
metadata := make([]*compute.MetadataItems, len(c.Metadata))
|
||||
for k, v := range c.Metadata {
|
||||
metadata = append(metadata, &compute.MetadataItems{
|
||||
Key: k,
|
||||
Value: v,
|
||||
})
|
||||
}
|
||||
|
||||
// Create the instance information
|
||||
instance := compute.Instance{
|
||||
Description: c.Description,
|
||||
Image: image.SelfLink,
|
||||
MachineType: machineType.SelfLink,
|
||||
Metadata: &compute.Metadata{
|
||||
Items: metadata,
|
||||
},
|
||||
Name: c.Name,
|
||||
NetworkInterfaces: []*compute.NetworkInterface{
|
||||
&compute.NetworkInterface{
|
||||
AccessConfigs: []*compute.AccessConfig{
|
||||
&compute.AccessConfig{
|
||||
Name: "AccessConfig created by Packer",
|
||||
Type: "ONE_TO_ONE_NAT",
|
||||
},
|
||||
},
|
||||
Network: network.SelfLink,
|
||||
},
|
||||
},
|
||||
ServiceAccounts: []*compute.ServiceAccount{
|
||||
&compute.ServiceAccount{
|
||||
Email: "default",
|
||||
Scopes: []string{
|
||||
"https://www.googleapis.com/auth/userinfo.email",
|
||||
"https://www.googleapis.com/auth/compute",
|
||||
"https://www.googleapis.com/auth/devstorage.full_control",
|
||||
},
|
||||
},
|
||||
},
|
||||
Tags: &compute.Tags{
|
||||
Items: c.Tags,
|
||||
},
|
||||
}
|
||||
|
||||
d.ui.Message("Requesting instance creation...")
|
||||
op, err := d.service.Instances.Insert(d.projectId, zone.Name, &instance).Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
go waitForState(errCh, "DONE", d.refreshZoneOp(op))
|
||||
return errCh, nil
|
||||
}
|
||||
|
||||
func (d *driverGCE) getImage(name string) (image *compute.Image, err error) {
|
||||
projects := []string{d.projectId, "debian-cloud", "centos-cloud"}
|
||||
for _, project := range projects {
|
||||
image, err = d.service.Images.Get(project, name).Do()
|
||||
if err == nil && image != nil && image.SelfLink != "" {
|
||||
return
|
||||
}
|
||||
image = nil
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
err = fmt.Errorf("Image could not be found: %s", name)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d *driverGCE) refreshZoneOp(op *compute.Operation) stateRefreshFunc {
|
||||
return func() (string, error) {
|
||||
newOp, err := d.service.ZoneOperations.Get(d.projectId, op.Zone, op.Name).Do()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// If the op is done, check for errors
|
||||
err = nil
|
||||
if newOp.Status == "DONE" {
|
||||
if newOp.Error != nil {
|
||||
for _, e := range newOp.Error.Errors {
|
||||
err = packer.MultiErrorAppend(err, fmt.Errorf(e.Message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newOp.Status, err
|
||||
}
|
||||
}
|
||||
|
||||
// stateRefreshFunc is used to refresh the state of a thing and is
|
||||
// used in conjunction with waitForState.
|
||||
type stateRefreshFunc func() (string, error)
|
||||
|
||||
// waitForState will spin in a loop forever waiting for state to
|
||||
// reach a certain target.
|
||||
func waitForState(errCh chan<- error, target string, refresh stateRefreshFunc) {
|
||||
for {
|
||||
state, err := refresh()
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
if state == target {
|
||||
errCh <- nil
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package googlecompute
|
||||
|
||||
// DriverMock is a Driver implementation that is a mocked out so that
|
||||
// it can be used for tests.
|
||||
type DriverMock struct {
|
||||
RunInstanceConfig *InstanceConfig
|
||||
RunInstanceErrCh <-chan error
|
||||
RunInstanceErr error
|
||||
}
|
||||
|
||||
func (d *DriverMock) RunInstance(c *InstanceConfig) (<-chan error, error) {
|
||||
d.RunInstanceConfig = c
|
||||
|
||||
resultCh := d.RunInstanceErrCh
|
||||
if resultCh == nil {
|
||||
ch := make(chan error)
|
||||
close(ch)
|
||||
resultCh = ch
|
||||
}
|
||||
|
||||
return resultCh, d.RunInstanceErr
|
||||
}
|
|
@ -1,131 +1,93 @@
|
|||
package googlecompute
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"code.google.com/p/google-api-go-client/compute/v1beta16"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/common/uuid"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
// stepCreateInstance represents a Packer build step that creates GCE instances.
|
||||
type stepCreateInstance struct {
|
||||
// StepCreateInstance represents a Packer build step that creates GCE instances.
|
||||
type StepCreateInstance struct {
|
||||
instanceName string
|
||||
}
|
||||
|
||||
// Run executes the Packer build step that creates a GCE instance.
|
||||
func (s *stepCreateInstance) Run(state multistep.StateBag) multistep.StepAction {
|
||||
var (
|
||||
client = state.Get("client").(*GoogleComputeClient)
|
||||
config = state.Get("config").(*Config)
|
||||
ui = state.Get("ui").(packer.Ui)
|
||||
)
|
||||
func (s *StepCreateInstance) Run(state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
driver := state.Get("driver").(Driver)
|
||||
sshPublicKey := state.Get("ssh_public_key").(string)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Creating instance...")
|
||||
name := fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
||||
// Build up the instance config.
|
||||
instanceConfig := &InstanceConfig{
|
||||
Description: "New instance created by Packer",
|
||||
Name: name,
|
||||
}
|
||||
// Validate the zone.
|
||||
zone, err := client.GetZone(config.Zone)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating instance: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
// Set the source image. Must be a fully-qualified URL.
|
||||
image, err := client.GetImage(config.SourceImage)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating instance: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
instanceConfig.Image = image.SelfLink
|
||||
// Set the machineType. Must be a fully-qualified URL.
|
||||
machineType, err := client.GetMachineType(config.MachineType, zone.Name)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating instance: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
|
||||
errCh, err := driver.RunInstance(&InstanceConfig{
|
||||
Description: "New instance created by Packer",
|
||||
Image: config.SourceImage,
|
||||
MachineType: config.MachineType,
|
||||
Metadata: map[string]string{
|
||||
"sshKeys": fmt.Sprintf("%s:%s", config.SSHUsername, sshPublicKey),
|
||||
},
|
||||
Name: name,
|
||||
Network: config.Network,
|
||||
Tags: config.Tags,
|
||||
Zone: config.Zone,
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
select {
|
||||
case err = <-errCh:
|
||||
case <-time.After(config.stateTimeout):
|
||||
err = errors.New("time out while waiting for instance to create")
|
||||
}
|
||||
}
|
||||
instanceConfig.MachineType = machineType.SelfLink
|
||||
// Set up the Network Interface.
|
||||
network, err := client.GetNetwork(config.Network)
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating instance: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
networkInterface := NewNetworkInterface(network, true)
|
||||
networkInterfaces := []*compute.NetworkInterface{
|
||||
networkInterface,
|
||||
}
|
||||
instanceConfig.NetworkInterfaces = networkInterfaces
|
||||
// Add the metadata, which also setups up the ssh key.
|
||||
metadata := make(map[string]string)
|
||||
sshPublicKey := state.Get("ssh_public_key").(string)
|
||||
metadata["sshKeys"] = fmt.Sprintf("%s:%s", config.SSHUsername, sshPublicKey)
|
||||
instanceConfig.Metadata = MapToMetadata(metadata)
|
||||
// Add the default service so we can create an image of the machine and
|
||||
// upload it to cloud storage.
|
||||
defaultServiceAccount := NewServiceAccount("default")
|
||||
serviceAccounts := []*compute.ServiceAccount{
|
||||
defaultServiceAccount,
|
||||
}
|
||||
instanceConfig.ServiceAccounts = serviceAccounts
|
||||
// Create the instance based on configuration
|
||||
operation, err := client.CreateInstance(zone.Name, instanceConfig)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating instance: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
ui.Say("Waiting for the instance to be created...")
|
||||
err = waitForZoneOperationState("DONE", config.Zone, operation.Name, client, config.stateTimeout)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating instance: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
// Update the state.
|
||||
|
||||
ui.Message("Instance has been created!")
|
||||
|
||||
// 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) {
|
||||
var (
|
||||
client = state.Get("client").(*GoogleComputeClient)
|
||||
config = state.Get("config").(*Config)
|
||||
ui = state.Get("ui").(packer.Ui)
|
||||
)
|
||||
func (s *StepCreateInstance) Cleanup(state multistep.StateBag) {
|
||||
if s.instanceName == "" {
|
||||
return
|
||||
}
|
||||
ui.Say("Destroying instance...")
|
||||
operation, err := client.DeleteInstance(config.Zone, s.instanceName)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error destroying instance. Please destroy it manually: %v", s.instanceName))
|
||||
}
|
||||
ui.Say("Waiting for the instance to be deleted...")
|
||||
for {
|
||||
status, err := client.ZoneOperationStatus(config.Zone, operation.Name)
|
||||
/*
|
||||
var (
|
||||
client = state.Get("client").(*GoogleComputeClient)
|
||||
config = state.Get("config").(*Config)
|
||||
ui = state.Get("ui").(packer.Ui)
|
||||
)
|
||||
ui.Say("Destroying instance...")
|
||||
operation, err := client.DeleteInstance(config.Zone, s.instanceName)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error destroying instance. Please destroy it manually: %v", s.instanceName))
|
||||
}
|
||||
if status == "DONE" {
|
||||
break
|
||||
ui.Say("Waiting for the instance to be deleted...")
|
||||
for {
|
||||
status, err := client.ZoneOperationStatus(config.Zone, operation.Name)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error destroying instance. Please destroy it manually: %v", s.instanceName))
|
||||
}
|
||||
if status == "DONE" {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
return
|
||||
}
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
package googlecompute
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/mitchellh/multistep"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestStepCreateInstance_impl(t *testing.T) {
|
||||
var _ multistep.Step = new(StepCreateInstance)
|
||||
}
|
||||
|
||||
func TestStepCreateInstance(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepCreateInstance)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
state.Put("ssh_public_key", "key")
|
||||
|
||||
// run the step
|
||||
if action := step.Run(state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// Verify state
|
||||
if _, ok := state.GetOk("instance_name"); !ok {
|
||||
t.Fatal("should have instance name")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateInstance_error(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepCreateInstance)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
state.Put("ssh_public_key", "key")
|
||||
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
driver.RunInstanceErr = errors.New("error")
|
||||
|
||||
// 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("instance_name"); ok {
|
||||
t.Fatal("should NOT have instance name")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateInstance_errorOnChannel(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepCreateInstance)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
errCh <- errors.New("error")
|
||||
|
||||
state.Put("ssh_public_key", "key")
|
||||
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
driver.RunInstanceErrCh = 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("instance_name"); ok {
|
||||
t.Fatal("should NOT have instance name")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateInstance_errorTimeout(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepCreateInstance)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
go func() {
|
||||
<-time.After(10 * time.Millisecond)
|
||||
errCh <- nil
|
||||
}()
|
||||
|
||||
state.Put("ssh_public_key", "key")
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
config.stateTimeout = 1 * time.Microsecond
|
||||
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
driver.RunInstanceErrCh = 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("instance_name"); ok {
|
||||
t.Fatal("should NOT have instance name")
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ import (
|
|||
func testState(t *testing.T) multistep.StateBag {
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", testConfigStruct(t))
|
||||
state.Put("driver", &DriverMock{})
|
||||
state.Put("hook", &packer.MockHook{})
|
||||
state.Put("ui", &packer.BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
|
|
|
@ -1,72 +1,38 @@
|
|||
package googlecompute
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
// statusFunc.
|
||||
type statusFunc func() (string, error)
|
||||
|
||||
// waitForInstanceState.
|
||||
func waitForInstanceState(desiredState string, zone string, name string, client *GoogleComputeClient, timeout time.Duration) error {
|
||||
f := func() (string, error) {
|
||||
return client.InstanceStatus(zone, name)
|
||||
}
|
||||
return waitForState("instance", desiredState, f, timeout)
|
||||
return nil
|
||||
/*
|
||||
f := func() (string, error) {
|
||||
return client.InstanceStatus(zone, name)
|
||||
}
|
||||
return waitForState("instance", desiredState, f, timeout)
|
||||
*/
|
||||
}
|
||||
|
||||
// waitForZoneOperationState.
|
||||
func waitForZoneOperationState(desiredState string, zone string, name string, client *GoogleComputeClient, timeout time.Duration) error {
|
||||
f := func() (string, error) {
|
||||
return client.ZoneOperationStatus(zone, name)
|
||||
}
|
||||
return waitForState("operation", desiredState, f, timeout)
|
||||
return nil
|
||||
/*
|
||||
f := func() (string, error) {
|
||||
return client.ZoneOperationStatus(zone, name)
|
||||
}
|
||||
return waitForState("operation", desiredState, f, timeout)
|
||||
*/
|
||||
}
|
||||
|
||||
// waitForGlobalOperationState.
|
||||
func waitForGlobalOperationState(desiredState string, name string, client *GoogleComputeClient, timeout time.Duration) error {
|
||||
f := func() (string, error) {
|
||||
return client.GlobalOperationStatus(name)
|
||||
}
|
||||
return waitForState("operation", desiredState, f, timeout)
|
||||
}
|
||||
|
||||
// waitForState.
|
||||
func waitForState(kind string, desiredState string, f statusFunc, timeout time.Duration) error {
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
result := make(chan error, 1)
|
||||
go func() {
|
||||
attempts := 0
|
||||
for {
|
||||
attempts += 1
|
||||
log.Printf("Checking %s state... (attempt: %d)", kind, attempts)
|
||||
status, err := f()
|
||||
if err != nil {
|
||||
result <- err
|
||||
return
|
||||
}
|
||||
if status == desiredState {
|
||||
result <- nil
|
||||
return
|
||||
}
|
||||
time.Sleep(3 * time.Second)
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
continue
|
||||
}
|
||||
/*
|
||||
f := func() (string, error) {
|
||||
return client.GlobalOperationStatus(name)
|
||||
}
|
||||
}()
|
||||
log.Printf("Waiting for up to %d seconds for %s to become %s", timeout, kind, desiredState)
|
||||
select {
|
||||
case err := <-result:
|
||||
return err
|
||||
case <-time.After(timeout):
|
||||
err := fmt.Errorf("Timeout while waiting to for the %s to become '%s'", kind, desiredState)
|
||||
return err
|
||||
}
|
||||
return waitForState("operation", desiredState, f, timeout)
|
||||
*/
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue