builder/googlecompute: Add new googlecompute builder
This commit is contained in:
parent
9307d8a866
commit
58c73727e5
|
@ -0,0 +1,318 @@
|
|||
// Copyright (c) 2013 Kelsey Hightower. All rights reserved.
|
||||
// Use of this source code is governed by the Apache License, Version 2.0
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"code.google.com/p/goauth2/oauth"
|
||||
"code.google.com/p/goauth2/oauth/jwt"
|
||||
"code.google.com/p/google-api-go-client/compute/v1beta16"
|
||||
)
|
||||
|
||||
// GoogleComputeClient represents a GCE client.
|
||||
type GoogleComputeClient struct {
|
||||
ProjectId string
|
||||
Service *compute.Service
|
||||
Zone string
|
||||
clientSecrets *clientSecrets
|
||||
}
|
||||
|
||||
// InstanceConfig represents a GCE instance configuration.
|
||||
// Used for creating machine instances.
|
||||
type InstanceConfig struct {
|
||||
Description string
|
||||
Image string
|
||||
MachineType string
|
||||
Metadata *compute.Metadata
|
||||
Name string
|
||||
NetworkInterfaces []*compute.NetworkInterface
|
||||
ServiceAccounts []*compute.ServiceAccount
|
||||
Tags *compute.Tags
|
||||
}
|
||||
|
||||
// New initializes and returns a *GoogleComputeClient.
|
||||
//
|
||||
// The projectId must be the project name, i.e. myproject, not the project
|
||||
// number.
|
||||
func New(projectId string, zone string, c *clientSecrets, pemKey []byte) (*GoogleComputeClient, error) {
|
||||
googleComputeClient := &GoogleComputeClient{
|
||||
ProjectId: projectId,
|
||||
Zone: zone,
|
||||
}
|
||||
// Get the access token.
|
||||
t := jwt.NewToken(c.Web.ClientEmail, scopes(), pemKey)
|
||||
t.ClaimSet.Aud = c.Web.TokenURI
|
||||
httpClient := &http.Client{}
|
||||
token, err := t.Assert(httpClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config := &oauth.Config{
|
||||
ClientId: c.Web.ClientId,
|
||||
Scope: scopes(),
|
||||
TokenURL: c.Web.TokenURI,
|
||||
AuthURL: c.Web.AuthURI,
|
||||
}
|
||||
transport := &oauth.Transport{Config: config}
|
||||
transport.Token = token
|
||||
s, err := compute.New(transport.Client())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
googleComputeClient.Service = s
|
||||
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".
|
||||
func (g *GoogleComputeClient) InstanceStatus(zone, name string) (string, error) {
|
||||
instanceGetCall := g.Service.Instances.Get(g.ProjectId, zone, name)
|
||||
instance, err := instanceGetCall.Do()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return instance.Status, nil
|
||||
}
|
||||
|
||||
// CreateImage registers a GCE Image with a project.
|
||||
func (g *GoogleComputeClient) CreateImage(name, description, sourceURL string) (*compute.Operation, error) {
|
||||
imageRawDisk := &compute.ImageRawDisk{
|
||||
ContainerType: "TAR",
|
||||
Source: sourceURL,
|
||||
}
|
||||
image := &compute.Image{
|
||||
Description: description,
|
||||
Name: name,
|
||||
RawDisk: imageRawDisk,
|
||||
SourceType: "RAW",
|
||||
}
|
||||
imageInsertCall := g.Service.Images.Insert(g.ProjectId, image)
|
||||
operation, err := imageInsertCall.Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return operation, nil
|
||||
}
|
||||
|
||||
// GetNatIp returns the public IPv4 address for named GCE instance.
|
||||
func (g *GoogleComputeClient) GetNatIP(zone, name string) (string, error) {
|
||||
instanceGetCall := g.Service.Instances.Get(g.ProjectId, zone, name)
|
||||
instance, err := instanceGetCall.Do()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, ni := range instance.NetworkInterfaces {
|
||||
if ni.AccessConfigs == nil {
|
||||
continue
|
||||
}
|
||||
for _, ac := range ni.AccessConfigs {
|
||||
if ac.NatIP != "" {
|
||||
return ac.NatIP, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// ZoneOperationStatus returns the status for the named zone operation.
|
||||
func (g *GoogleComputeClient) ZoneOperationStatus(zone, name string) (string, error) {
|
||||
zoneOperationsGetCall := g.Service.ZoneOperations.Get(g.ProjectId, zone, name)
|
||||
operation, err := zoneOperationsGetCall.Do()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if operation.Status == "DONE" {
|
||||
err = processOperationStatus(operation)
|
||||
if err != nil {
|
||||
return operation.Status, err
|
||||
}
|
||||
}
|
||||
return operation.Status, nil
|
||||
}
|
||||
|
||||
// GlobalOperationStatus returns the status for the named global operation.
|
||||
func (g *GoogleComputeClient) GlobalOperationStatus(name string) (string, error) {
|
||||
globalOperationsGetCall := g.Service.GlobalOperations.Get(g.ProjectId, name)
|
||||
operation, err := globalOperationsGetCall.Do()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if operation.Status == "DONE" {
|
||||
err = processOperationStatus(operation)
|
||||
if err != nil {
|
||||
return operation.Status, err
|
||||
}
|
||||
}
|
||||
return operation.Status, nil
|
||||
}
|
||||
|
||||
// processOperationStatus extracts errors from the specified operation.
|
||||
func processOperationStatus(o *compute.Operation) error {
|
||||
if o.Error != nil {
|
||||
messages := make([]string, len(o.Error.Errors))
|
||||
for _, e := range o.Error.Errors {
|
||||
messages = append(messages, e.Message)
|
||||
}
|
||||
return errors.New(strings.Join(messages, "\n"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteImage deletes the named image. Returns a Global Operation.
|
||||
func (g *GoogleComputeClient) DeleteImage(name string) (*compute.Operation, error) {
|
||||
imagesDeleteCall := g.Service.Images.Delete(g.ProjectId, name)
|
||||
operation, err := imagesDeleteCall.Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return operation, nil
|
||||
}
|
||||
|
||||
// DeleteInstance deletes the named instance. Returns a Zone Operation.
|
||||
func (g *GoogleComputeClient) DeleteInstance(zone, name string) (*compute.Operation, error) {
|
||||
instanceDeleteCall := g.Service.Instances.Delete(g.ProjectId, zone, name)
|
||||
operation, err := instanceDeleteCall.Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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, " ")
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright (c) 2013 Kelsey Hightower. All rights reserved.
|
||||
// Use of this source code is governed by the Apache License, Version 2.0
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
// Artifact represents a GCE image as the result of a Packer build.
|
||||
type Artifact struct {
|
||||
imageName string
|
||||
client *GoogleComputeClient
|
||||
}
|
||||
|
||||
// BuilderId returns the builder Id.
|
||||
func (*Artifact) BuilderId() string {
|
||||
return BuilderId
|
||||
}
|
||||
|
||||
// Destroy destroys the GCE image represented by the artifact.
|
||||
func (a *Artifact) Destroy() error {
|
||||
log.Printf("Destroying image: %s", a.imageName)
|
||||
// Ignore the operation result as we are not waiting until it completes.
|
||||
_, err := a.client.DeleteImage(a.imageName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Files returns the files represented by the artifact.
|
||||
func (*Artifact) Files() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Id returns the GCE image name.
|
||||
func (a *Artifact) Id() string {
|
||||
return a.imageName
|
||||
}
|
||||
|
||||
// String returns the string representation of the artifact.
|
||||
func (a *Artifact) String() string {
|
||||
return fmt.Sprintf("A disk image was created: %v", a.imageName)
|
||||
}
|
|
@ -0,0 +1,241 @@
|
|||
// Copyright (c) 2013 Kelsey Hightower. All rights reserved.
|
||||
// Use of this source code is governed by the Apache License, Version 2.0
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
// The googlecompute package contains a packer.Builder implementation that
|
||||
// builds images for Google Compute Engine.
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
// The unique ID for this builder.
|
||||
const BuilderId = "kelseyhightower.googlecompute"
|
||||
|
||||
// Builder represents a Packer Builder.
|
||||
type Builder struct {
|
||||
config config
|
||||
runner multistep.Runner
|
||||
}
|
||||
|
||||
// config holds the googlecompute builder configuration settings.
|
||||
type config struct {
|
||||
BucketName string `mapstructure:"bucket_name"`
|
||||
ClientSecretsFile string `mapstructure:"client_secrets_file"`
|
||||
ImageName string `mapstructure:"image_name"`
|
||||
ImageDescription string `mapstructure:"image_description"`
|
||||
MachineType string `mapstructure:"machine_type"`
|
||||
Metadata map[string]string `mapstructure:"metadata"`
|
||||
Network string `mapstructure:"network"`
|
||||
Passphrase string `mapstructure:"passphrase"`
|
||||
PrivateKeyFile string `mapstructure:"private_key_file"`
|
||||
ProjectId string `mapstructure:"project_id"`
|
||||
SourceImage string `mapstructure:"source_image"`
|
||||
SSHUsername string `mapstructure:"ssh_username"`
|
||||
SSHPort uint `mapstructure:"ssh_port"`
|
||||
RawSSHTimeout string `mapstructure:"ssh_timeout"`
|
||||
RawStateTimeout string `mapstructure:"state_timeout"`
|
||||
Tags []string `mapstructure:"tags"`
|
||||
Zone string `mapstructure:"zone"`
|
||||
clientSecrets *clientSecrets
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
instanceName string
|
||||
privateKeyBytes []byte
|
||||
sshTimeout time.Duration
|
||||
stateTimeout time.Duration
|
||||
tpl *packer.ConfigTemplate
|
||||
}
|
||||
|
||||
// Prepare processes the build configuration parameters.
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
// Load the packer config.
|
||||
md, err := common.DecodeConfig(&b.config, raws...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.config.tpl, err = packer.NewConfigTemplate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.config.tpl.UserVars = b.config.PackerUserVars
|
||||
|
||||
errs := common.CheckUnusedConfig(md)
|
||||
// Collect errors if any.
|
||||
if err := common.CheckUnusedConfig(md); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Set defaults.
|
||||
if b.config.Network == "" {
|
||||
b.config.Network = "default"
|
||||
}
|
||||
if b.config.ImageDescription == "" {
|
||||
b.config.ImageDescription = "Created by Packer"
|
||||
}
|
||||
if b.config.ImageName == "" {
|
||||
// Default to packer-{{ unix timestamp (utc) }}
|
||||
b.config.ImageName = "packer-{{timestamp}}"
|
||||
}
|
||||
if b.config.MachineType == "" {
|
||||
b.config.MachineType = "n1-standard-1"
|
||||
}
|
||||
if b.config.RawSSHTimeout == "" {
|
||||
b.config.RawSSHTimeout = "5m"
|
||||
}
|
||||
if b.config.RawStateTimeout == "" {
|
||||
b.config.RawStateTimeout = "5m"
|
||||
}
|
||||
if b.config.SSHUsername == "" {
|
||||
b.config.SSHUsername = "root"
|
||||
}
|
||||
if b.config.SSHPort == 0 {
|
||||
b.config.SSHPort = 22
|
||||
}
|
||||
// Process Templates
|
||||
templates := map[string]*string{
|
||||
"bucket_name": &b.config.BucketName,
|
||||
"client_secrets_file": &b.config.ClientSecretsFile,
|
||||
"image_name": &b.config.ImageName,
|
||||
"image_description": &b.config.ImageDescription,
|
||||
"machine_type": &b.config.MachineType,
|
||||
"network": &b.config.Network,
|
||||
"passphrase": &b.config.Passphrase,
|
||||
"private_key_file": &b.config.PrivateKeyFile,
|
||||
"project_id": &b.config.ProjectId,
|
||||
"source_image": &b.config.SourceImage,
|
||||
"ssh_username": &b.config.SSHUsername,
|
||||
"ssh_timeout": &b.config.RawSSHTimeout,
|
||||
"state_timeout": &b.config.RawStateTimeout,
|
||||
"zone": &b.config.Zone,
|
||||
}
|
||||
for n, ptr := range templates {
|
||||
var err error
|
||||
*ptr, err = b.config.tpl.Process(*ptr, nil)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Error processing %s: %s", n, err))
|
||||
}
|
||||
}
|
||||
// Process required parameters.
|
||||
if b.config.BucketName == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("a bucket_name must be specified"))
|
||||
}
|
||||
if b.config.ClientSecretsFile == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("a client_secrets_file must be specified"))
|
||||
}
|
||||
if b.config.PrivateKeyFile == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("a private_key_file must be specified"))
|
||||
}
|
||||
if b.config.ProjectId == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("a project_id must be specified"))
|
||||
}
|
||||
if b.config.SourceImage == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("a source_image must be specified"))
|
||||
}
|
||||
if b.config.Zone == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("a zone must be specified"))
|
||||
}
|
||||
// Process timeout settings.
|
||||
sshTimeout, err := time.ParseDuration(b.config.RawSSHTimeout)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Failed parsing ssh_timeout: %s", err))
|
||||
}
|
||||
b.config.sshTimeout = sshTimeout
|
||||
stateTimeout, err := time.ParseDuration(b.config.RawStateTimeout)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Failed parsing state_timeout: %s", err))
|
||||
}
|
||||
b.config.stateTimeout = stateTimeout
|
||||
// Load the client secrets file.
|
||||
cs, err := loadClientSecrets(b.config.ClientSecretsFile)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Failed parsing client secrets file: %s", err))
|
||||
}
|
||||
b.config.clientSecrets = cs
|
||||
// Load the private key.
|
||||
b.config.privateKeyBytes, err = processPrivateKeyFile(b.config.PrivateKeyFile, b.config.Passphrase)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Failed loading private key file: %s", err))
|
||||
}
|
||||
// Check for any errors.
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return nil, errs
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Run executes a googlecompute Packer build and returns a packer.Artifact
|
||||
// representing a GCE machine image.
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
// Initialize the Google Compute Engine API.
|
||||
client, err := New(b.config.ProjectId, b.config.Zone, b.config.clientSecrets, b.config.privateKeyBytes)
|
||||
if err != nil {
|
||||
log.Println("Failed to create the Google Compute Engine client.")
|
||||
return nil, err
|
||||
}
|
||||
// Set up the state.
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", b.config)
|
||||
state.Put("client", client)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
// Build the steps.
|
||||
steps := []multistep.Step{
|
||||
new(stepCreateSSHKey),
|
||||
new(stepCreateInstance),
|
||||
new(stepInstanceInfo),
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: sshAddress,
|
||||
SSHConfig: sshConfig,
|
||||
SSHWaitTimeout: 5 * time.Minute,
|
||||
},
|
||||
new(common.StepProvision),
|
||||
new(stepUpdateGsutil),
|
||||
new(stepCreateImage),
|
||||
new(stepUploadImage),
|
||||
new(stepRegisterImage),
|
||||
}
|
||||
// Run the steps.
|
||||
if b.config.PackerDebug {
|
||||
b.runner = &multistep.DebugRunner{
|
||||
Steps: steps,
|
||||
PauseFn: common.MultistepDebugFn(ui),
|
||||
}
|
||||
} else {
|
||||
b.runner = &multistep.BasicRunner{Steps: steps}
|
||||
}
|
||||
b.runner.Run(state)
|
||||
// Report any errors.
|
||||
if rawErr, ok := state.GetOk("error"); ok {
|
||||
return nil, rawErr.(error)
|
||||
}
|
||||
if _, ok := state.GetOk("image_name"); !ok {
|
||||
log.Println("Failed to find image_name in state. Bug?")
|
||||
return nil, nil
|
||||
}
|
||||
artifact := &Artifact{
|
||||
imageName: state.Get("image_name").(string),
|
||||
client: client,
|
||||
}
|
||||
return artifact, nil
|
||||
}
|
||||
|
||||
// Cancel.
|
||||
func (b *Builder) Cancel() {}
|
|
@ -0,0 +1,5 @@
|
|||
// Copyright (c) 2013 Kelsey Hightower. All rights reserved.
|
||||
// Use of this source code is governed by the Apache License, Version 2.0
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package googlecompute
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright (c) 2013 Kelsey Hightower. All rights reserved.
|
||||
// Use of this source code is governed by the Apache License, Version 2.0
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// clientSecrets represents the client secrets of a GCE service account.
|
||||
type clientSecrets struct {
|
||||
Web struct {
|
||||
AuthURI string `json:"auth_uri"`
|
||||
ClientEmail string `json:"client_email"`
|
||||
ClientId string `json:"client_id"`
|
||||
TokenURI string `json:"token_uri"`
|
||||
}
|
||||
}
|
||||
|
||||
// loadClientSecrets loads the GCE client secrets file identified by path.
|
||||
func loadClientSecrets(path string) (*clientSecrets, error) {
|
||||
var cs *clientSecrets
|
||||
secretBytes, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal(secretBytes, &cs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cs, nil
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright (c) 2013 Kelsey Hightower. All rights reserved.
|
||||
// Use of this source code is governed by the Apache License, Version 2.0
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// processPrivateKeyFile.
|
||||
func processPrivateKeyFile(privateKeyFile, passphrase string) ([]byte, error) {
|
||||
rawPrivateKeyBytes, err := ioutil.ReadFile(privateKeyFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed loading private key file: %s", err)
|
||||
}
|
||||
PEMBlock, _ := pem.Decode(rawPrivateKeyBytes)
|
||||
if PEMBlock == nil {
|
||||
return nil, fmt.Errorf("%s does not contain a vaild private key", privateKeyFile)
|
||||
}
|
||||
if x509.IsEncryptedPEMBlock(PEMBlock) {
|
||||
if passphrase == "" {
|
||||
return nil, errors.New("a passphrase must be specified when using an encrypted private key")
|
||||
}
|
||||
decryptedPrivateKeyBytes, err := x509.DecryptPEMBlock(PEMBlock, []byte(passphrase))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed decrypting private key: %s", err)
|
||||
}
|
||||
b := &pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: decryptedPrivateKeyBytes,
|
||||
}
|
||||
return pem.EncodeToMemory(b), nil
|
||||
}
|
||||
return rawPrivateKeyBytes, nil
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package googlecompute
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
gossh "code.google.com/p/go.crypto/ssh"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/communicator/ssh"
|
||||
)
|
||||
|
||||
// sshAddress returns the ssh address.
|
||||
func sshAddress(state multistep.StateBag) (string, error) {
|
||||
config := state.Get("config").(config)
|
||||
ipAddress := state.Get("instance_ip").(string)
|
||||
return fmt.Sprintf("%s:%d", ipAddress, config.SSHPort), nil
|
||||
}
|
||||
|
||||
// sshConfig returns the ssh configuration.
|
||||
func sshConfig(state multistep.StateBag) (*gossh.ClientConfig, error) {
|
||||
config := state.Get("config").(config)
|
||||
privateKey := state.Get("ssh_private_key").(string)
|
||||
|
||||
keyring := new(ssh.SimpleKeychain)
|
||||
if err := keyring.AddPEMKey(privateKey); err != nil {
|
||||
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
|
||||
}
|
||||
sshConfig := &gossh.ClientConfig{
|
||||
User: config.SSHUsername,
|
||||
Auth: []gossh.ClientAuth{gossh.ClientAuthKeyring(keyring)},
|
||||
}
|
||||
return sshConfig, nil
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright (c) 2013 Kelsey Hightower. All rights reserved.
|
||||
// Use of this source code is governed by the Apache License, Version 2.0
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
// stepCreateImage represents a Packer build step that creates GCE machine
|
||||
// images.
|
||||
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.
|
||||
func (s *stepCreateImage) Run(state multistep.StateBag) multistep.StepAction {
|
||||
var (
|
||||
config = state.Get("config").(config)
|
||||
comm = state.Get("communicator").(packer.Communicator)
|
||||
sudoPrefix = ""
|
||||
ui = state.Get("ui").(packer.Ui)
|
||||
)
|
||||
ui.Say("Creating image...")
|
||||
if config.SSHUsername != "root" {
|
||||
sudoPrefix = "sudo "
|
||||
}
|
||||
imageFilename := fmt.Sprintf("%s.tar.gz", config.ImageName)
|
||||
imageBundleCmd := "/usr/bin/gcimagebundle -d /dev/sda -o /tmp/"
|
||||
cmd := new(packer.RemoteCmd)
|
||||
cmd.Command = fmt.Sprintf("%s%s --output_file_name %s",
|
||||
sudoPrefix, imageBundleCmd, imageFilename)
|
||||
err := cmd.StartWithUi(comm, ui)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating image: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
state.Put("image_file_name", filepath.Join("/tmp", imageFilename))
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepCreateImage) Cleanup(state multistep.StateBag) {}
|
|
@ -0,0 +1,135 @@
|
|||
// Copyright (c) 2013 Kelsey Hightower. All rights reserved.
|
||||
// Use of this source code is governed by the Apache License, Version 2.0
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"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 {
|
||||
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)
|
||||
)
|
||||
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
|
||||
|
||||
}
|
||||
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.
|
||||
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)
|
||||
)
|
||||
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)
|
||||
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,55 @@
|
|||
// Copyright (c) 2013 Kelsey Hightower. All rights reserved.
|
||||
// Use of this source code is governed by the Apache License, Version 2.0
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
|
||||
"code.google.com/p/go.crypto/ssh"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
// stepCreateSSHKey represents a Packer build step that generates SSH key pairs.
|
||||
type stepCreateSSHKey int
|
||||
|
||||
// Run executes the Packer build step that generates SSH key pairs.
|
||||
func (s *stepCreateSSHKey) Run(state multistep.StateBag) multistep.StepAction {
|
||||
var (
|
||||
ui = state.Get("ui").(packer.Ui)
|
||||
)
|
||||
ui.Say("Creating temporary ssh key for instance...")
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2014)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating temporary ssh key: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
priv_der := x509.MarshalPKCS1PrivateKey(priv)
|
||||
priv_blk := pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Headers: nil,
|
||||
Bytes: priv_der,
|
||||
}
|
||||
pub, err := ssh.NewPublicKey(&priv.PublicKey)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating temporary ssh key: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
state.Put("ssh_private_key", string(pem.EncodeToMemory(&priv_blk)))
|
||||
state.Put("ssh_public_key", string(ssh.MarshalAuthorizedKey(pub)))
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
// Cleanup.
|
||||
// Nothing to clean up. SSH keys are associated with a single GCE instance.
|
||||
func (s *stepCreateSSHKey) Cleanup(state multistep.StateBag) {}
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright (c) 2013 Kelsey Hightower. All rights reserved.
|
||||
// Use of this source code is governed by the Apache License, Version 2.0
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
// stepInstanceInfo represents a Packer build step that gathers GCE instance info.
|
||||
type stepInstanceInfo int
|
||||
|
||||
// Run executes the Packer build step that gathers GCE instance info.
|
||||
func (s *stepInstanceInfo) Run(state multistep.StateBag) multistep.StepAction {
|
||||
var (
|
||||
client = state.Get("client").(*GoogleComputeClient)
|
||||
config = state.Get("config").(config)
|
||||
ui = state.Get("ui").(packer.Ui)
|
||||
)
|
||||
instanceName := state.Get("instance_name").(string)
|
||||
err := waitForInstanceState("RUNNING", config.Zone, instanceName, 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
|
||||
}
|
||||
ip, err := client.GetNatIP(config.Zone, instanceName)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error retrieving instance nat ip address: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
state.Put("instance_ip", ip)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
// Cleanup.
|
||||
func (s *stepInstanceInfo) Cleanup(state multistep.StateBag) {}
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright (c) 2013 Kelsey Hightower. All rights reserved.
|
||||
// Use of this source code is governed by the Apache License, Version 2.0
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"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 {
|
||||
var (
|
||||
client = state.Get("client").(*GoogleComputeClient)
|
||||
config = state.Get("config").(config)
|
||||
ui = state.Get("ui").(packer.Ui)
|
||||
)
|
||||
ui.Say("Adding image to the project...")
|
||||
imageURL := fmt.Sprintf("https://storage.cloud.google.com/%s/%s.tar.gz", config.BucketName, config.ImageName)
|
||||
operation, err := client.CreateImage(config.ImageName, config.ImageDescription, imageURL)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating image: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
ui.Say("Waiting for image to become available...")
|
||||
err = waitForGlobalOperationState("DONE", operation.Name, client, config.stateTimeout)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating 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) {}
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright (c) 2013 Kelsey Hightower. All rights reserved.
|
||||
// Use of this source code is governed by the Apache License, Version 2.0
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
// stepUpdateGsutil represents a Packer build step that updates the gsutil
|
||||
// utility to the latest version available.
|
||||
type stepUpdateGsutil 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 *stepUpdateGsutil) Run(state multistep.StateBag) multistep.StepAction {
|
||||
var (
|
||||
config = state.Get("config").(config)
|
||||
comm = state.Get("communicator").(packer.Communicator)
|
||||
sudoPrefix = ""
|
||||
ui = state.Get("ui").(packer.Ui)
|
||||
)
|
||||
ui.Say("Updating gsutil...")
|
||||
if config.SSHUsername != "root" {
|
||||
sudoPrefix = "sudo "
|
||||
}
|
||||
gsutilUpdateCmd := "/usr/local/bin/gsutil update -n -f"
|
||||
cmd := new(packer.RemoteCmd)
|
||||
cmd.Command = fmt.Sprintf("%s%s", sudoPrefix, gsutilUpdateCmd)
|
||||
err := cmd.StartWithUi(comm, ui)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error updating gsutil: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
// Cleanup.
|
||||
func (s *stepUpdateGsutil) Cleanup(state multistep.StateBag) {}
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright (c) 2013 Kelsey Hightower. All rights reserved.
|
||||
// Use of this source code is governed by the Apache License, Version 2.0
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
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 {
|
||||
var (
|
||||
config = state.Get("config").(config)
|
||||
comm = state.Get("communicator").(packer.Communicator)
|
||||
sudoPrefix = ""
|
||||
ui = state.Get("ui").(packer.Ui)
|
||||
imageFilename = state.Get("image_file_name").(string)
|
||||
)
|
||||
ui.Say("Uploading image...")
|
||||
if config.SSHUsername != "root" {
|
||||
sudoPrefix = "sudo "
|
||||
}
|
||||
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 {
|
||||
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) {}
|
|
@ -0,0 +1,76 @@
|
|||
// Copyright (c) 2013 Kelsey Hightower. All rights reserved.
|
||||
// Use of this source code is governed by the Apache License, Version 2.0
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}()
|
||||
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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) 2013 Kelsey Hightower. All rights reserved.
|
||||
// Use of this source code is governed by the Apache License, Version 2.0
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kelseyhightower/packer-builder-googlecompute/builder/googlecompute"
|
||||
"github.com/mitchellh/packer/packer/plugin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.ServeBuilder(new(googlecompute.Builder))
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package main
|
Loading…
Reference in New Issue