builder/googlecompute: StepRegisterImage
This commit is contained in:
parent
587f057bf6
commit
33a84c0938
|
@ -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"
|
||||
)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
func (s *StepRegisterImage) Run(state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
driver := state.Get("driver").(Driver)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
var err error
|
||||
imageURL := fmt.Sprintf(
|
||||
"https://storage.cloud.google.com/%s/%s.tar.gz",
|
||||
config.BucketName, config.ImageName)
|
||||
|
||||
ui.Say("Registering image...")
|
||||
errCh := driver.CreateImage(config.ImageName, config.ImageDescription, imageURL)
|
||||
select {
|
||||
case err = <-errCh:
|
||||
case <-time.After(config.stateTimeout):
|
||||
err = errors.New("time out while waiting for image to register")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error 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)
|
||||
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) {}
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
package googlecompute
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/mitchellh/multistep"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestStepRegisterImage_impl(t *testing.T) {
|
||||
var _ multistep.Step = new(StepRegisterImage)
|
||||
}
|
||||
|
||||
func TestStepRegisterImage(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepRegisterImage)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
|
||||
// run the step
|
||||
if action := step.Run(state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// Verify state
|
||||
if driver.CreateImageName != config.ImageName {
|
||||
t.Fatalf("bad: %#v", driver.CreateImageName)
|
||||
}
|
||||
if driver.CreateImageDesc != config.ImageDescription {
|
||||
t.Fatalf("bad: %#v", driver.CreateImageDesc)
|
||||
}
|
||||
|
||||
nameRaw, ok := state.GetOk("image_name")
|
||||
if !ok {
|
||||
t.Fatal("should have name")
|
||||
}
|
||||
if name, ok := nameRaw.(string); !ok {
|
||||
t.Fatal("name is not a string")
|
||||
} else if name != config.ImageName {
|
||||
t.Fatalf("bad name: %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepRegisterImage_waitError(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepRegisterImage)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
errCh <- errors.New("error")
|
||||
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
driver.CreateImageErrCh = errCh
|
||||
|
||||
// run the step
|
||||
if action := step.Run(state); action != multistep.ActionHalt {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// Verify state
|
||||
if _, ok := state.GetOk("error"); !ok {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
if _, ok := state.GetOk("image_name"); ok {
|
||||
t.Fatal("should NOT have image_name")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepRegisterImage_errorTimeout(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepRegisterImage)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
go func() {
|
||||
<-time.After(10 * time.Millisecond)
|
||||
errCh <- nil
|
||||
}()
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
config.stateTimeout = 1 * time.Microsecond
|
||||
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
driver.CreateImageErrCh = errCh
|
||||
|
||||
// run the step
|
||||
if action := step.Run(state); action != multistep.ActionHalt {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// Verify state
|
||||
if _, ok := state.GetOk("error"); !ok {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
if _, ok := state.GetOk("image_name"); ok {
|
||||
t.Fatal("should NOT have image name")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue