Skip to content

Commit

Permalink
builder/googlecompute: StepRegisterImage
Browse files Browse the repository at this point in the history
  • Loading branch information
mitchellh committed Dec 14, 2013
1 parent 587f057 commit 33a84c0
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 151 deletions.
128 changes: 0 additions & 128 deletions builder/googlecompute/api.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
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"
)

Expand All @@ -18,118 +12,6 @@ type GoogleComputeClient struct {
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, "", 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: "",
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
}

// 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
}

// 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)
Expand All @@ -139,13 +21,3 @@ func (g *GoogleComputeClient) DeleteImage(name string) (*compute.Operation, erro
}
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
}
4 changes: 1 addition & 3 deletions builder/googlecompute/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
new(StepUpdateGsutil),
new(StepCreateImage),
new(StepUploadImage),
new(StepRegisterImage),
}
/*
new(stepRegisterImage),
}*/

// Run the steps.
if b.config.PackerDebug {
Expand Down
3 changes: 3 additions & 0 deletions builder/googlecompute/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ package googlecompute
// with GCE. The Driver interface exists mostly to allow a mock implementation
// to be used to test the steps.
type Driver interface {
// CreateImage creates an image with the given URL in Google Storage.
CreateImage(name, description, url string) <-chan error

// DeleteInstance deletes the given instance.
DeleteInstance(zone, name string) (<-chan error, error)

Expand Down
43 changes: 43 additions & 0 deletions builder/googlecompute/driver_gce.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,28 @@ func NewDriverGCE(ui packer.Ui, projectId string, c *clientSecrets, key []byte)
}, nil
}

func (d *driverGCE) CreateImage(name, description, url string) <-chan error {
image := &compute.Image{
Description: description,
Name: name,
RawDisk: &compute.ImageRawDisk{
ContainerType: "TAR",
Source: url,
},
SourceType: "RAW",
}

errCh := make(chan error, 1)
op, err := d.service.Images.Insert(d.projectId, image).Do()
if err != nil {
errCh <- err
} else {
go waitForState(errCh, "DONE", d.refreshGlobalOp(op))
}

return errCh
}

func (d *driverGCE) DeleteInstance(zone, name string) (<-chan error, error) {
op, err := d.service.Instances.Delete(d.projectId, zone, name).Do()
if err != nil {
Expand Down Expand Up @@ -210,6 +232,27 @@ func (d *driverGCE) refreshInstanceState(zone, name string) stateRefreshFunc {
}
}

func (d *driverGCE) refreshGlobalOp(op *compute.Operation) stateRefreshFunc {
return func() (string, error) {
newOp, err := d.service.GlobalOperations.Get(d.projectId, 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
}
}

func (d *driverGCE) refreshZoneOp(zone string, op *compute.Operation) stateRefreshFunc {
return func() (string, error) {
newOp, err := d.service.ZoneOperations.Get(d.projectId, zone, op.Name).Do()
Expand Down
20 changes: 20 additions & 0 deletions builder/googlecompute/driver_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ package googlecompute
// DriverMock is a Driver implementation that is a mocked out so that
// it can be used for tests.
type DriverMock struct {
CreateImageName string
CreateImageDesc string
CreateImageURL string
CreateImageErrCh <-chan error

DeleteInstanceZone string
DeleteInstanceName string
DeleteInstanceErrCh <-chan error
Expand All @@ -23,6 +28,21 @@ type DriverMock struct {
WaitForInstanceErrCh <-chan error
}

func (d *DriverMock) CreateImage(name, description, url string) <-chan error {
d.CreateImageName = name
d.CreateImageDesc = description
d.CreateImageURL = url

resultCh := d.CreateImageErrCh
if resultCh == nil {
ch := make(chan error)
close(ch)
resultCh = ch
}

return resultCh
}

func (d *DriverMock) DeleteInstance(zone, name string) (<-chan error, error) {
d.DeleteInstanceZone = zone
d.DeleteInstanceName = name
Expand Down
44 changes: 24 additions & 20 deletions builder/googlecompute/step_register_image.go
Original file line number Diff line number Diff line change
@@ -1,42 +1,46 @@
package googlecompute

import (
"errors"
"fmt"
"time"

"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)

// stepRegisterImage represents a Packer build step that registers GCE machine images.
type stepRegisterImage int
// 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
func (s *StepRegisterImage) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)

var err error
imageURL := fmt.Sprintf(
"https://storage.cloud.google.com/%s/%s.tar.gz",
config.BucketName, config.ImageName)

ui.Say("Registering image...")
errCh := driver.CreateImage(config.ImageName, config.ImageDescription, imageURL)
select {
case err = <-errCh:
case <-time.After(config.stateTimeout):
err = errors.New("time out while waiting for image to register")
}
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)
err := fmt.Errorf("Error waiting for image: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}

state.Put("image_name", config.ImageName)
return multistep.ActionContinue
}

// Cleanup.
func (s *stepRegisterImage) Cleanup(state multistep.StateBag) {}
func (s *StepRegisterImage) Cleanup(state multistep.StateBag) {}
Loading

0 comments on commit 33a84c0

Please sign in to comment.