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.
|
// InstanceConfig represents a GCE instance configuration.
|
||||||
// Used for creating machine instances.
|
// Used for creating machine instances.
|
||||||
|
/*
|
||||||
type InstanceConfig struct {
|
type InstanceConfig struct {
|
||||||
Description string
|
Description string
|
||||||
Image string
|
Image string
|
||||||
|
@ -30,6 +31,7 @@ type InstanceConfig struct {
|
||||||
ServiceAccounts []*compute.ServiceAccount
|
ServiceAccounts []*compute.ServiceAccount
|
||||||
Tags *compute.Tags
|
Tags *compute.Tags
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// New initializes and returns a *GoogleComputeClient.
|
// New initializes and returns a *GoogleComputeClient.
|
||||||
//
|
//
|
||||||
|
@ -41,7 +43,7 @@ func New(projectId string, zone string, c *clientSecrets, pemKey []byte) (*Googl
|
||||||
Zone: zone,
|
Zone: zone,
|
||||||
}
|
}
|
||||||
// Get the access token.
|
// 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
|
t.ClaimSet.Aud = c.Web.TokenURI
|
||||||
httpClient := &http.Client{}
|
httpClient := &http.Client{}
|
||||||
token, err := t.Assert(httpClient)
|
token, err := t.Assert(httpClient)
|
||||||
|
@ -50,7 +52,7 @@ func New(projectId string, zone string, c *clientSecrets, pemKey []byte) (*Googl
|
||||||
}
|
}
|
||||||
config := &oauth.Config{
|
config := &oauth.Config{
|
||||||
ClientId: c.Web.ClientId,
|
ClientId: c.Web.ClientId,
|
||||||
Scope: scopes(),
|
Scope: "",
|
||||||
TokenURL: c.Web.TokenURI,
|
TokenURL: c.Web.TokenURI,
|
||||||
AuthURL: c.Web.AuthURI,
|
AuthURL: c.Web.AuthURI,
|
||||||
}
|
}
|
||||||
|
@ -64,83 +66,6 @@ func New(projectId string, zone string, c *clientSecrets, pemKey []byte) (*Googl
|
||||||
return googleComputeClient, nil
|
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.
|
// InstanceStatus returns a string representing the status of the named instance.
|
||||||
// Status will be one of: "PROVISIONING", "STAGING", "RUNNING", "STOPPING",
|
// Status will be one of: "PROVISIONING", "STAGING", "RUNNING", "STOPPING",
|
||||||
// "STOPPED", "TERMINATED".
|
// "STOPPED", "TERMINATED".
|
||||||
|
@ -256,59 +181,3 @@ func (g *GoogleComputeClient) DeleteInstance(zone, name string) (*compute.Operat
|
||||||
}
|
}
|
||||||
return operation, nil
|
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.
|
// Build the steps.
|
||||||
steps := []multistep.Step{
|
steps := []multistep.Step{
|
||||||
new(StepCreateSSHKey),
|
new(StepCreateSSHKey),
|
||||||
new(stepCreateInstance),
|
new(StepCreateInstance),
|
||||||
new(stepInstanceInfo),
|
new(stepInstanceInfo),
|
||||||
&common.StepConnectSSH{
|
&common.StepConnectSSH{
|
||||||
SSHAddress: sshAddress,
|
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
|
package googlecompute
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"code.google.com/p/google-api-go-client/compute/v1beta16"
|
|
||||||
"github.com/mitchellh/multistep"
|
"github.com/mitchellh/multistep"
|
||||||
"github.com/mitchellh/packer/common/uuid"
|
"github.com/mitchellh/packer/common/uuid"
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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 {
|
||||||
instanceName string
|
instanceName string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run executes the Packer build step that creates a GCE instance.
|
// Run executes the Packer build step that creates a GCE instance.
|
||||||
func (s *stepCreateInstance) Run(state multistep.StateBag) multistep.StepAction {
|
func (s *StepCreateInstance) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
var (
|
config := state.Get("config").(*Config)
|
||||||
client = state.Get("client").(*GoogleComputeClient)
|
driver := state.Get("driver").(Driver)
|
||||||
config = state.Get("config").(*Config)
|
sshPublicKey := state.Get("ssh_public_key").(string)
|
||||||
ui = state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
)
|
|
||||||
ui.Say("Creating instance...")
|
ui.Say("Creating instance...")
|
||||||
name := fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
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 {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error creating instance: %s", err)
|
err := fmt.Errorf("Error creating instance: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
networkInterface := NewNetworkInterface(network, true)
|
|
||||||
networkInterfaces := []*compute.NetworkInterface{
|
ui.Message("Instance has been created!")
|
||||||
networkInterface,
|
|
||||||
}
|
// Things succeeded, store the name so we can remove it later
|
||||||
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.
|
|
||||||
state.Put("instance_name", name)
|
state.Put("instance_name", name)
|
||||||
s.instanceName = 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) {
|
||||||
var (
|
|
||||||
client = state.Get("client").(*GoogleComputeClient)
|
|
||||||
config = state.Get("config").(*Config)
|
|
||||||
ui = state.Get("ui").(packer.Ui)
|
|
||||||
)
|
|
||||||
if s.instanceName == "" {
|
if s.instanceName == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ui.Say("Destroying instance...")
|
/*
|
||||||
operation, err := client.DeleteInstance(config.Zone, s.instanceName)
|
var (
|
||||||
if err != nil {
|
client = state.Get("client").(*GoogleComputeClient)
|
||||||
ui.Error(fmt.Sprintf("Error destroying instance. Please destroy it manually: %v", s.instanceName))
|
config = state.Get("config").(*Config)
|
||||||
}
|
ui = state.Get("ui").(packer.Ui)
|
||||||
ui.Say("Waiting for the instance to be deleted...")
|
)
|
||||||
for {
|
ui.Say("Destroying instance...")
|
||||||
status, err := client.ZoneOperationStatus(config.Zone, operation.Name)
|
operation, err := client.DeleteInstance(config.Zone, s.instanceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.Error(fmt.Sprintf("Error destroying instance. Please destroy it manually: %v", s.instanceName))
|
ui.Error(fmt.Sprintf("Error destroying instance. Please destroy it manually: %v", s.instanceName))
|
||||||
}
|
}
|
||||||
if status == "DONE" {
|
ui.Say("Waiting for the instance to be deleted...")
|
||||||
break
|
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
|
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 {
|
func testState(t *testing.T) multistep.StateBag {
|
||||||
state := new(multistep.BasicStateBag)
|
state := new(multistep.BasicStateBag)
|
||||||
state.Put("config", testConfigStruct(t))
|
state.Put("config", testConfigStruct(t))
|
||||||
|
state.Put("driver", &DriverMock{})
|
||||||
state.Put("hook", &packer.MockHook{})
|
state.Put("hook", &packer.MockHook{})
|
||||||
state.Put("ui", &packer.BasicUi{
|
state.Put("ui", &packer.BasicUi{
|
||||||
Reader: new(bytes.Buffer),
|
Reader: new(bytes.Buffer),
|
||||||
|
|
|
@ -1,72 +1,38 @@
|
||||||
package googlecompute
|
package googlecompute
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// statusFunc.
|
|
||||||
type statusFunc func() (string, error)
|
|
||||||
|
|
||||||
// waitForInstanceState.
|
// waitForInstanceState.
|
||||||
func waitForInstanceState(desiredState string, zone string, name string, client *GoogleComputeClient, timeout time.Duration) error {
|
func waitForInstanceState(desiredState string, zone string, name string, client *GoogleComputeClient, timeout time.Duration) error {
|
||||||
f := func() (string, error) {
|
return nil
|
||||||
return client.InstanceStatus(zone, name)
|
/*
|
||||||
}
|
f := func() (string, error) {
|
||||||
return waitForState("instance", desiredState, f, timeout)
|
return client.InstanceStatus(zone, name)
|
||||||
|
}
|
||||||
|
return waitForState("instance", desiredState, f, timeout)
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
// waitForZoneOperationState.
|
// waitForZoneOperationState.
|
||||||
func waitForZoneOperationState(desiredState string, zone string, name string, client *GoogleComputeClient, timeout time.Duration) error {
|
func waitForZoneOperationState(desiredState string, zone string, name string, client *GoogleComputeClient, timeout time.Duration) error {
|
||||||
f := func() (string, error) {
|
return nil
|
||||||
return client.ZoneOperationStatus(zone, name)
|
/*
|
||||||
}
|
f := func() (string, error) {
|
||||||
return waitForState("operation", desiredState, f, timeout)
|
return client.ZoneOperationStatus(zone, name)
|
||||||
|
}
|
||||||
|
return waitForState("operation", desiredState, f, timeout)
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
// waitForGlobalOperationState.
|
// waitForGlobalOperationState.
|
||||||
func waitForGlobalOperationState(desiredState string, name string, client *GoogleComputeClient, timeout time.Duration) error {
|
func waitForGlobalOperationState(desiredState string, name string, client *GoogleComputeClient, timeout time.Duration) error {
|
||||||
f := func() (string, error) {
|
/*
|
||||||
return client.GlobalOperationStatus(name)
|
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}()
|
return waitForState("operation", desiredState, f, timeout)
|
||||||
log.Printf("Waiting for up to %d seconds for %s to become %s", timeout, kind, desiredState)
|
*/
|
||||||
select {
|
return nil
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue