diff --git a/builder/googlecompute/api.go b/builder/googlecompute/api.go index 6a79a9945..e178c8aa8 100644 --- a/builder/googlecompute/api.go +++ b/builder/googlecompute/api.go @@ -20,6 +20,7 @@ type GoogleComputeClient struct { // InstanceConfig represents a GCE instance configuration. // Used for creating machine instances. +/* type InstanceConfig struct { Description string Image string @@ -30,6 +31,7 @@ type InstanceConfig struct { ServiceAccounts []*compute.ServiceAccount Tags *compute.Tags } +*/ // New initializes and returns a *GoogleComputeClient. // @@ -41,7 +43,7 @@ func New(projectId string, zone string, c *clientSecrets, pemKey []byte) (*Googl Zone: zone, } // Get the access token. - t := jwt.NewToken(c.Web.ClientEmail, scopes(), pemKey) + t := jwt.NewToken(c.Web.ClientEmail, "", pemKey) t.ClaimSet.Aud = c.Web.TokenURI httpClient := &http.Client{} token, err := t.Assert(httpClient) @@ -50,7 +52,7 @@ func New(projectId string, zone string, c *clientSecrets, pemKey []byte) (*Googl } config := &oauth.Config{ ClientId: c.Web.ClientId, - Scope: scopes(), + Scope: "", TokenURL: c.Web.TokenURI, AuthURL: c.Web.AuthURI, } @@ -64,83 +66,6 @@ func New(projectId string, zone string, c *clientSecrets, pemKey []byte) (*Googl return googleComputeClient, nil } -// GetZone returns a *compute.Zone representing the named zone. -func (g *GoogleComputeClient) GetZone(name string) (*compute.Zone, error) { - zoneGetCall := g.Service.Zones.Get(g.ProjectId, name) - zone, err := zoneGetCall.Do() - if err != nil { - return nil, err - } - return zone, nil -} - -// GetMachineType returns a *compute.MachineType representing the named machine type. -func (g *GoogleComputeClient) GetMachineType(name, zone string) (*compute.MachineType, error) { - machineTypesGetCall := g.Service.MachineTypes.Get(g.ProjectId, zone, name) - machineType, err := machineTypesGetCall.Do() - if err != nil { - return nil, err - } - if machineType.Deprecated == nil { - return machineType, nil - } - return nil, errors.New("Machine Type does not exist: " + name) -} - -// GetImage returns a *compute.Image representing the named image. -func (g *GoogleComputeClient) GetImage(name string) (*compute.Image, error) { - var err error - var image *compute.Image - projects := []string{g.ProjectId, "debian-cloud", "centos-cloud"} - for _, project := range projects { - imagesGetCall := g.Service.Images.Get(project, name) - image, err = imagesGetCall.Do() - if image != nil { - break - } - } - if err != nil { - return nil, err - } - if image != nil { - if image.SelfLink != "" { - return image, nil - } - } - return nil, errors.New("Image does not exist: " + name) -} - -// GetNetwork returns a *compute.Network representing the named network. -func (g *GoogleComputeClient) GetNetwork(name string) (*compute.Network, error) { - networkGetCall := g.Service.Networks.Get(g.ProjectId, name) - network, err := networkGetCall.Do() - if err != nil { - return nil, err - } - return network, nil -} - -// CreateInstance creates an instance in Google Compute Engine based on the -// supplied instanceConfig. -func (g *GoogleComputeClient) CreateInstance(zone string, instanceConfig *InstanceConfig) (*compute.Operation, error) { - instance := &compute.Instance{ - Description: instanceConfig.Description, - Image: instanceConfig.Image, - MachineType: instanceConfig.MachineType, - Metadata: instanceConfig.Metadata, - Name: instanceConfig.Name, - NetworkInterfaces: instanceConfig.NetworkInterfaces, - ServiceAccounts: instanceConfig.ServiceAccounts, - Tags: instanceConfig.Tags, - } - instanceInsertCall := g.Service.Instances.Insert(g.ProjectId, zone, instance) - operation, err := instanceInsertCall.Do() - if err != nil { - return nil, err - } - return operation, nil -} - // InstanceStatus returns a string representing the status of the named instance. // Status will be one of: "PROVISIONING", "STAGING", "RUNNING", "STOPPING", // "STOPPED", "TERMINATED". @@ -256,59 +181,3 @@ func (g *GoogleComputeClient) DeleteInstance(zone, name string) (*compute.Operat } return operation, nil } - -// NewNetworkInterface returns a *compute.NetworkInterface based on the data provided. -func NewNetworkInterface(network *compute.Network, public bool) *compute.NetworkInterface { - accessConfigs := make([]*compute.AccessConfig, 0) - if public { - c := &compute.AccessConfig{ - Name: "AccessConfig created by Packer", - Type: "ONE_TO_ONE_NAT", - } - accessConfigs = append(accessConfigs, c) - } - return &compute.NetworkInterface{ - AccessConfigs: accessConfigs, - Network: network.SelfLink, - } -} - -// NewServiceAccount returns a *compute.ServiceAccount with permissions required -// for creating GCE machine images. -func NewServiceAccount(email string) *compute.ServiceAccount { - return &compute.ServiceAccount{ - Email: email, - Scopes: []string{ - "https://www.googleapis.com/auth/userinfo.email", - "https://www.googleapis.com/auth/compute", - "https://www.googleapis.com/auth/devstorage.full_control", - }, - } -} - -// MapToMetadata converts a map[string]string to a *compute.Metadata. -func MapToMetadata(metadata map[string]string) *compute.Metadata { - items := make([]*compute.MetadataItems, len(metadata)) - for k, v := range metadata { - items = append(items, &compute.MetadataItems{k, v}) - } - return &compute.Metadata{ - Items: items, - } -} - -// SliceToTags converts a []string to a *compute.Tags. -func SliceToTags(tags []string) *compute.Tags { - return &compute.Tags{ - Items: tags, - } -} - -// scopes return a space separated list of scopes. -func scopes() string { - s := []string{ - "https://www.googleapis.com/auth/compute", - "https://www.googleapis.com/auth/devstorage.full_control", - } - return strings.Join(s, " ") -} diff --git a/builder/googlecompute/builder.go b/builder/googlecompute/builder.go index 51704f8eb..e84cd3ef5 100644 --- a/builder/googlecompute/builder.go +++ b/builder/googlecompute/builder.go @@ -51,7 +51,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // Build the steps. steps := []multistep.Step{ new(StepCreateSSHKey), - new(stepCreateInstance), + new(StepCreateInstance), new(stepInstanceInfo), &common.StepConnectSSH{ SSHAddress: sshAddress, diff --git a/builder/googlecompute/driver.go b/builder/googlecompute/driver.go new file mode 100644 index 000000000..88551c149 --- /dev/null +++ b/builder/googlecompute/driver.go @@ -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 +} diff --git a/builder/googlecompute/driver_gce.go b/builder/googlecompute/driver_gce.go new file mode 100644 index 000000000..3e1b0d731 --- /dev/null +++ b/builder/googlecompute/driver_gce.go @@ -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) + } +} diff --git a/builder/googlecompute/driver_mock.go b/builder/googlecompute/driver_mock.go new file mode 100644 index 000000000..4293e8787 --- /dev/null +++ b/builder/googlecompute/driver_mock.go @@ -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 +} diff --git a/builder/googlecompute/step_create_instance.go b/builder/googlecompute/step_create_instance.go index 930403f06..2bfacd287 100644 --- a/builder/googlecompute/step_create_instance.go +++ b/builder/googlecompute/step_create_instance.go @@ -1,131 +1,93 @@ package googlecompute import ( + "errors" "fmt" + "time" - "code.google.com/p/google-api-go-client/compute/v1beta16" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/common/uuid" "github.com/mitchellh/packer/packer" ) -// stepCreateInstance represents a Packer build step that creates GCE instances. -type stepCreateInstance struct { +// StepCreateInstance represents a Packer build step that creates GCE instances. +type StepCreateInstance struct { instanceName string } // Run executes the Packer build step that creates a GCE instance. -func (s *stepCreateInstance) Run(state multistep.StateBag) multistep.StepAction { - var ( - client = state.Get("client").(*GoogleComputeClient) - config = state.Get("config").(*Config) - ui = state.Get("ui").(packer.Ui) - ) +func (s *StepCreateInstance) Run(state multistep.StateBag) multistep.StepAction { + config := state.Get("config").(*Config) + driver := state.Get("driver").(Driver) + sshPublicKey := state.Get("ssh_public_key").(string) + ui := state.Get("ui").(packer.Ui) + ui.Say("Creating instance...") name := fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID()) - // Build up the instance config. - instanceConfig := &InstanceConfig{ - Description: "New instance created by Packer", - Name: name, - } - // Validate the zone. - zone, err := client.GetZone(config.Zone) - if err != nil { - err := fmt.Errorf("Error creating instance: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - // Set the source image. Must be a fully-qualified URL. - image, err := client.GetImage(config.SourceImage) - if err != nil { - err := fmt.Errorf("Error creating instance: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - instanceConfig.Image = image.SelfLink - // Set the machineType. Must be a fully-qualified URL. - machineType, err := client.GetMachineType(config.MachineType, zone.Name) - if err != nil { - err := fmt.Errorf("Error creating instance: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt + errCh, err := driver.RunInstance(&InstanceConfig{ + Description: "New instance created by Packer", + Image: config.SourceImage, + MachineType: config.MachineType, + Metadata: map[string]string{ + "sshKeys": fmt.Sprintf("%s:%s", config.SSHUsername, sshPublicKey), + }, + Name: name, + Network: config.Network, + Tags: config.Tags, + Zone: config.Zone, + }) + + if err == nil { + select { + case err = <-errCh: + case <-time.After(config.stateTimeout): + err = errors.New("time out while waiting for instance to create") + } } - instanceConfig.MachineType = machineType.SelfLink - // Set up the Network Interface. - network, err := client.GetNetwork(config.Network) + if err != nil { err := fmt.Errorf("Error creating instance: %s", err) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } - networkInterface := NewNetworkInterface(network, true) - networkInterfaces := []*compute.NetworkInterface{ - networkInterface, - } - instanceConfig.NetworkInterfaces = networkInterfaces - // Add the metadata, which also setups up the ssh key. - metadata := make(map[string]string) - sshPublicKey := state.Get("ssh_public_key").(string) - metadata["sshKeys"] = fmt.Sprintf("%s:%s", config.SSHUsername, sshPublicKey) - instanceConfig.Metadata = MapToMetadata(metadata) - // Add the default service so we can create an image of the machine and - // upload it to cloud storage. - defaultServiceAccount := NewServiceAccount("default") - serviceAccounts := []*compute.ServiceAccount{ - defaultServiceAccount, - } - instanceConfig.ServiceAccounts = serviceAccounts - // Create the instance based on configuration - operation, err := client.CreateInstance(zone.Name, instanceConfig) - if err != nil { - err := fmt.Errorf("Error creating instance: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - ui.Say("Waiting for the instance to be created...") - err = waitForZoneOperationState("DONE", config.Zone, operation.Name, client, config.stateTimeout) - if err != nil { - err := fmt.Errorf("Error creating instance: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - // Update the state. + + ui.Message("Instance has been created!") + + // Things succeeded, store the name so we can remove it later state.Put("instance_name", name) s.instanceName = name + return multistep.ActionContinue } // Cleanup destroys the GCE instance created during the image creation process. -func (s *stepCreateInstance) Cleanup(state multistep.StateBag) { - var ( - client = state.Get("client").(*GoogleComputeClient) - config = state.Get("config").(*Config) - ui = state.Get("ui").(packer.Ui) - ) +func (s *StepCreateInstance) Cleanup(state multistep.StateBag) { if s.instanceName == "" { return } - ui.Say("Destroying instance...") - operation, err := client.DeleteInstance(config.Zone, s.instanceName) - if err != nil { - ui.Error(fmt.Sprintf("Error destroying instance. Please destroy it manually: %v", s.instanceName)) - } - ui.Say("Waiting for the instance to be deleted...") - for { - status, err := client.ZoneOperationStatus(config.Zone, operation.Name) + /* + var ( + client = state.Get("client").(*GoogleComputeClient) + config = state.Get("config").(*Config) + ui = state.Get("ui").(packer.Ui) + ) + ui.Say("Destroying instance...") + operation, err := client.DeleteInstance(config.Zone, s.instanceName) if err != nil { ui.Error(fmt.Sprintf("Error destroying instance. Please destroy it manually: %v", s.instanceName)) } - if status == "DONE" { - break + ui.Say("Waiting for the instance to be deleted...") + for { + status, err := client.ZoneOperationStatus(config.Zone, operation.Name) + if err != nil { + ui.Error(fmt.Sprintf("Error destroying instance. Please destroy it manually: %v", s.instanceName)) + } + if status == "DONE" { + break + } } - } + */ return } diff --git a/builder/googlecompute/step_create_instance_test.go b/builder/googlecompute/step_create_instance_test.go new file mode 100644 index 000000000..703fefde1 --- /dev/null +++ b/builder/googlecompute/step_create_instance_test.go @@ -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") + } +} diff --git a/builder/googlecompute/step_test.go b/builder/googlecompute/step_test.go index b9a8ba153..cba0dbac9 100644 --- a/builder/googlecompute/step_test.go +++ b/builder/googlecompute/step_test.go @@ -10,6 +10,7 @@ import ( func testState(t *testing.T) multistep.StateBag { state := new(multistep.BasicStateBag) state.Put("config", testConfigStruct(t)) + state.Put("driver", &DriverMock{}) state.Put("hook", &packer.MockHook{}) state.Put("ui", &packer.BasicUi{ Reader: new(bytes.Buffer), diff --git a/builder/googlecompute/wait.go b/builder/googlecompute/wait.go index 938d381fe..cd0c6fa10 100644 --- a/builder/googlecompute/wait.go +++ b/builder/googlecompute/wait.go @@ -1,72 +1,38 @@ package googlecompute import ( - "fmt" - "log" "time" ) -// statusFunc. -type statusFunc func() (string, error) - // waitForInstanceState. func waitForInstanceState(desiredState string, zone string, name string, client *GoogleComputeClient, timeout time.Duration) error { - f := func() (string, error) { - return client.InstanceStatus(zone, name) - } - return waitForState("instance", desiredState, f, timeout) + return nil + /* + f := func() (string, error) { + return client.InstanceStatus(zone, name) + } + return waitForState("instance", desiredState, f, timeout) + */ } // waitForZoneOperationState. func waitForZoneOperationState(desiredState string, zone string, name string, client *GoogleComputeClient, timeout time.Duration) error { - f := func() (string, error) { - return client.ZoneOperationStatus(zone, name) - } - return waitForState("operation", desiredState, f, timeout) + return nil + /* + f := func() (string, error) { + return client.ZoneOperationStatus(zone, name) + } + return waitForState("operation", desiredState, f, timeout) + */ } // waitForGlobalOperationState. func waitForGlobalOperationState(desiredState string, name string, client *GoogleComputeClient, timeout time.Duration) error { - f := func() (string, error) { - return client.GlobalOperationStatus(name) - } - return waitForState("operation", desiredState, f, timeout) -} - -// waitForState. -func waitForState(kind string, desiredState string, f statusFunc, timeout time.Duration) error { - done := make(chan struct{}) - defer close(done) - result := make(chan error, 1) - go func() { - attempts := 0 - for { - attempts += 1 - log.Printf("Checking %s state... (attempt: %d)", kind, attempts) - status, err := f() - if err != nil { - result <- err - return - } - if status == desiredState { - result <- nil - return - } - time.Sleep(3 * time.Second) - select { - case <-done: - return - default: - continue - } + /* + f := func() (string, error) { + return client.GlobalOperationStatus(name) } - }() - log.Printf("Waiting for up to %d seconds for %s to become %s", timeout, kind, desiredState) - select { - case err := <-result: - return err - case <-time.After(timeout): - err := fmt.Errorf("Timeout while waiting to for the %s to become '%s'", kind, desiredState) - return err - } + return waitForState("operation", desiredState, f, timeout) + */ + return nil }