From 91a513ab832f35673720f5f5902e1f6f34841169 Mon Sep 17 00:00:00 2001 From: The Magician Date: Wed, 26 Dec 2018 17:42:37 -0800 Subject: [PATCH] clean up operation code (#2734) --- google/appengine_operation.go | 68 ++------- google/cloudfunctions_operation.go | 69 ++-------- google/common_operation.go | 143 +++++++++++++++++++ google/composer_operation.go | 57 +------- google/compute_operation.go | 137 ++++++++---------- google/container_operation.go | 143 +++++-------------- google/dataproc_cluster_operation.go | 56 +------- google/dataproc_job_operation.go | 153 ++++++++++----------- google/dns_change.go | 15 +- google/redis_operation.go | 57 +------- google/resource_container_cluster.go | 28 ++-- google/resource_container_node_pool.go | 26 ++-- google/resource_dataproc_cluster.go | 6 +- google/resource_google_project_services.go | 4 +- google/resourcemanager_operation.go | 63 ++------- google/service_account_waiter.go | 23 ++-- google/serviceman_operation.go | 59 ++------ google/serviceusage_operation.go | 67 ++------- google/spanner_database_operation.go | 58 +------- google/spanner_instance_operation.go | 59 +------- google/sqladmin_operation.go | 103 ++++++-------- google/utils.go | 11 +- 22 files changed, 487 insertions(+), 918 deletions(-) create mode 100644 google/common_operation.go diff --git a/google/appengine_operation.go b/google/appengine_operation.go index deadc294..48288545 100644 --- a/google/appengine_operation.go +++ b/google/appengine_operation.go @@ -2,12 +2,7 @@ package google import ( "fmt" - "log" "regexp" - "strconv" - "time" - - "github.com/hashicorp/terraform/helper/resource" "google.golang.org/api/appengine/v1" ) @@ -18,73 +13,30 @@ var ( type AppEngineOperationWaiter struct { Service *appengine.APIService - Op *appengine.Operation AppId string + CommonOperationWaiter } -func (w *AppEngineOperationWaiter) RefreshFunc() resource.StateRefreshFunc { - return func() (interface{}, string, error) { - matches := appEngineOperationIdRegexp.FindStringSubmatch(w.Op.Name) - if len(matches) != 2 { - return nil, "", fmt.Errorf("Expected %d results of parsing operation name, got %d from %s", 2, len(matches), w.Op.Name) - } - op, err := w.Service.Apps.Operations.Get(w.AppId, matches[1]).Do() - if err != nil { - return nil, "", err - } - - log.Printf("[DEBUG] Got %v when asking for operation %q", op.Done, w.Op.Name) - return op, strconv.FormatBool(op.Done), nil +func (w *AppEngineOperationWaiter) QueryOp() (interface{}, error) { + matches := appEngineOperationIdRegexp.FindStringSubmatch(w.Op.Name) + if len(matches) != 2 { + return nil, fmt.Errorf("Expected %d results of parsing operation name, got %d from %s", 2, len(matches), w.Op.Name) } -} - -func (w *AppEngineOperationWaiter) Conf() *resource.StateChangeConf { - return &resource.StateChangeConf{ - Pending: []string{"false"}, - Target: []string{"true"}, - Refresh: w.RefreshFunc(), - } -} - -// AppEngineOperationError wraps appengine.Status and implements the -// error interface so it can be returned. -type AppEngineOperationError appengine.Status - -func (e AppEngineOperationError) Error() string { - return e.Message + return w.Service.Apps.Operations.Get(w.AppId, matches[1]).Do() } func appEngineOperationWait(client *appengine.APIService, op *appengine.Operation, appId, activity string) error { return appEngineOperationWaitTime(client, op, appId, activity, 4) } -func appEngineOperationWaitTime(client *appengine.APIService, op *appengine.Operation, appId, activity string, timeoutMin int) error { - if op.Done { - if op.Error != nil { - return AppEngineOperationError(*op.Error) - } - return nil - } - +func appEngineOperationWaitTime(client *appengine.APIService, op *appengine.Operation, appId, activity string, timeoutMinutes int) error { w := &AppEngineOperationWaiter{ Service: client, - Op: op, AppId: appId, } - state := w.Conf() - state.Delay = 10 * time.Second - state.Timeout = time.Duration(timeoutMin) * time.Minute - state.MinTimeout = 2 * time.Second - opRaw, err := state.WaitForState() - if err != nil { - return fmt.Errorf("Error waiting for %s: %s", activity, err) + if err := w.SetOp(op); err != nil { + return err } - - resultOp := opRaw.(*appengine.Operation) - if resultOp.Error != nil { - return AppEngineOperationError(*resultOp.Error) - } - - return nil + return OperationWait(w, activity, timeoutMinutes) } diff --git a/google/cloudfunctions_operation.go b/google/cloudfunctions_operation.go index d13e0e48..71d3d8a8 100644 --- a/google/cloudfunctions_operation.go +++ b/google/cloudfunctions_operation.go @@ -1,77 +1,24 @@ package google import ( - "fmt" - "log" - "time" - - "github.com/hashicorp/terraform/helper/resource" "google.golang.org/api/cloudfunctions/v1" ) type CloudFunctionsOperationWaiter struct { Service *cloudfunctions.Service - Op *cloudfunctions.Operation + CommonOperationWaiter } -func (w *CloudFunctionsOperationWaiter) RefreshFunc() resource.StateRefreshFunc { - return func() (interface{}, string, error) { - op, err := w.Service.Operations.Get(w.Op.Name).Do() - - if err != nil { - return nil, "", err - } - - status := "PENDING" - if op.Done == true { - status = "DONE" - } - - log.Printf("[DEBUG] Got %q when asking for operation %q", status, w.Op.Name) - return op, status, nil - } +func (w *CloudFunctionsOperationWaiter) QueryOp() (interface{}, error) { + return w.Service.Operations.Get(w.Op.Name).Do() } -func (w *CloudFunctionsOperationWaiter) Conf() *resource.StateChangeConf { - return &resource.StateChangeConf{ - Pending: []string{"PENDING"}, - Target: []string{"DONE"}, - Refresh: w.RefreshFunc(), - } -} - -func cloudFunctionsOperationWait(client *cloudfunctions.Service, - op *cloudfunctions.Operation, activity string) error { - return cloudFunctionsOperationWaitTime(client, op, activity, 4) -} - -func cloudFunctionsOperationWaitTime(client *cloudfunctions.Service, op *cloudfunctions.Operation, - activity string, timeoutMin int) error { - if op.Done { - if op.Error != nil { - return fmt.Errorf(op.Error.Message) - } - return nil - } - +func cloudFunctionsOperationWait(service *cloudfunctions.Service, op *cloudfunctions.Operation, activity string) error { w := &CloudFunctionsOperationWaiter{ - Service: client, - Op: op, + Service: service, } - - state := w.Conf() - state.Delay = 10 * time.Second - state.Timeout = time.Duration(timeoutMin) * time.Minute - state.MinTimeout = 2 * time.Second - opRaw, err := state.WaitForState() - if err != nil { - return fmt.Errorf("Error waiting for %s: %s", activity, err) + if err := w.SetOp(op); err != nil { + return err } - - resultOp := opRaw.(*cloudfunctions.Operation) - if resultOp.Error != nil { - return fmt.Errorf(resultOp.Error.Message) - } - - return nil + return OperationWait(w, activity, 4) } diff --git a/google/common_operation.go b/google/common_operation.go new file mode 100644 index 00000000..5e693eac --- /dev/null +++ b/google/common_operation.go @@ -0,0 +1,143 @@ +package google + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform/helper/resource" + cloudresourcemanager "google.golang.org/api/cloudresourcemanager/v1" +) + +type Waiter interface { + // State returns the current status of the operation. + State() string + + // Error returns an error embedded in the operation we're waiting on, or nil + // if the operation has no current error. + Error() error + + // SetOp sets the operation we're waiting on in a Waiter struct so that it + // can be used in other methods. + SetOp(interface{}) error + + // QueryOp sends a request to the server to get the current status of the + // operation. + QueryOp() (interface{}, error) + + // OpName is the name of the operation and is used to log its status. + OpName() string + + // PendingStates contains the values of State() that cause us to continue + // refreshing the operation. + PendingStates() []string + + // TargetStates contain the values of State() that cause us to finish + // refreshing the operation. + TargetStates() []string +} + +type CommonOperationWaiter struct { + Op CommonOperation +} + +func (w *CommonOperationWaiter) State() string { + return fmt.Sprintf("done: %v", w.Op.Done) +} + +func (w *CommonOperationWaiter) Error() error { + if w.Op.Error != nil { + return fmt.Errorf("Error code %v, message: %s", w.Op.Error.Code, w.Op.Error.Message) + } + return nil +} + +func (w *CommonOperationWaiter) SetOp(op interface{}) error { + if err := Convert(op, &w.Op); err != nil { + return err + } + return nil +} + +func (w *CommonOperationWaiter) OpName() string { + return w.Op.Name +} + +func (w *CommonOperationWaiter) PendingStates() []string { + return []string{"done: false"} +} + +func (w *CommonOperationWaiter) TargetStates() []string { + return []string{"done: true"} +} + +func OperationDone(w Waiter) bool { + for _, s := range w.TargetStates() { + if s == w.State() { + return true + } + } + return false +} + +func CommonRefreshFunc(w Waiter) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + // First, read the operation from the server. + op, err := w.QueryOp() + + // If we got a non-retryable error, return it. + if err != nil && !isRetryableError(err) { + return nil, "", err + } + + // Try to set the operation (so we can check it's Error/State), + // and fail if we can't. + if err = w.SetOp(op); err != nil { + return nil, "", err + } + + // Fail if the operation object contains an error. + if err = w.Error(); err != nil { + return nil, "", err + } + log.Printf("[DEBUG] Got %v while polling for operation %s's status", w.State(), w.OpName()) + + return op, w.State(), nil + } +} + +func OperationWait(w Waiter, activity string, timeoutMinutes int) error { + if OperationDone(w) { + if w.Error() != nil { + return w.Error() + } + return nil + } + + c := &resource.StateChangeConf{ + Pending: w.PendingStates(), + Target: w.TargetStates(), + Refresh: CommonRefreshFunc(w), + Timeout: time.Duration(timeoutMinutes) * time.Minute, + MinTimeout: 2 * time.Second, + } + opRaw, err := c.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for %s: %s", activity, err) + } + + err = w.SetOp(opRaw) + if err != nil { + return err + } + if w.Error() != nil { + return w.Error() + } + + return nil +} + +// The cloud resource manager API operation is an example of one of many +// interchangeable API operations. Choose it somewhat arbitrarily to represent +// the "common" operation. +type CommonOperation cloudresourcemanager.Operation diff --git a/google/composer_operation.go b/google/composer_operation.go index 98f5db92..44429809 100644 --- a/google/composer_operation.go +++ b/google/composer_operation.go @@ -1,67 +1,24 @@ package google import ( - "fmt" - "log" - "time" - - "github.com/hashicorp/terraform/helper/resource" composer "google.golang.org/api/composer/v1beta1" ) type ComposerOperationWaiter struct { Service *composer.ProjectsLocationsService - Op *composer.Operation + CommonOperationWaiter } -func (w *ComposerOperationWaiter) RefreshFunc() resource.StateRefreshFunc { - return func() (interface{}, string, error) { - op, err := w.Service.Operations.Get(w.Op.Name).Do() - - if err != nil { - return nil, "", err - } - - log.Printf("[DEBUG] Got %v while polling for operation %s's 'done' status", op.Done, w.Op.Name) - - return op, fmt.Sprint(op.Done), nil - } +func (w *ComposerOperationWaiter) QueryOp() (interface{}, error) { + return w.Service.Operations.Get(w.Op.Name).Do() } -func (w *ComposerOperationWaiter) Conf() *resource.StateChangeConf { - return &resource.StateChangeConf{ - Pending: []string{"false"}, - Target: []string{"true"}, - Refresh: w.RefreshFunc(), - } -} - -func composerOperationWaitTime(service *composer.Service, op *composer.Operation, project, activity string, timeoutMin int) error { - if op.Done { - if op.Error != nil { - return fmt.Errorf("Error code %v, message: %s", op.Error.Code, op.Error.Message) - } - return nil - } - +func composerOperationWaitTime(service *composer.Service, op *composer.Operation, project, activity string, timeoutMinutes int) error { w := &ComposerOperationWaiter{ Service: service.Projects.Locations, - Op: op, } - - state := w.Conf() - state.Delay = 10 * time.Second - state.Timeout = time.Duration(timeoutMin) * time.Minute - state.MinTimeout = 2 * time.Second - opRaw, err := state.WaitForState() - if err != nil { - return fmt.Errorf("Error waiting for %s: %s", activity, err) + if err := w.SetOp(op); err != nil { + return err } - - op = opRaw.(*composer.Operation) - if op.Error != nil { - return fmt.Errorf("Error code %v, message: %s", op.Error.Code, op.Error.Message) - } - - return nil + return OperationWait(w, activity, timeoutMinutes) } diff --git a/google/compute_operation.go b/google/compute_operation.go index 1e2e81d7..69b19746 100644 --- a/google/compute_operation.go +++ b/google/compute_operation.go @@ -2,11 +2,6 @@ package google import ( "bytes" - "fmt" - "log" - "time" - - "github.com/hashicorp/terraform/helper/resource" computeBeta "google.golang.org/api/compute/v0.beta" "google.golang.org/api/compute/v1" @@ -18,35 +13,70 @@ type ComputeOperationWaiter struct { Project string } -func (w *ComputeOperationWaiter) RefreshFunc() resource.StateRefreshFunc { - return func() (interface{}, string, error) { - var op *compute.Operation - var err error - - if w.Op.Zone != "" { - zone := GetResourceNameFromSelfLink(w.Op.Zone) - op, err = w.Service.ZoneOperations.Get(w.Project, zone, w.Op.Name).Do() - } else if w.Op.Region != "" { - region := GetResourceNameFromSelfLink(w.Op.Region) - op, err = w.Service.RegionOperations.Get(w.Project, region, w.Op.Name).Do() - } else { - op, err = w.Service.GlobalOperations.Get(w.Project, w.Op.Name).Do() - } - if err != nil { - return nil, "", err - } - - log.Printf("[DEBUG] Got %q when asking for operation %q", op.Status, w.Op.Name) - return op, op.Status, nil - } +func (w *ComputeOperationWaiter) State() string { + return w.Op.Status } -func (w *ComputeOperationWaiter) Conf() *resource.StateChangeConf { - return &resource.StateChangeConf{ - Pending: []string{"PENDING", "RUNNING"}, - Target: []string{"DONE"}, - Refresh: w.RefreshFunc(), +func (w *ComputeOperationWaiter) Error() error { + if w.Op.Error != nil { + return ComputeOperationError(*w.Op.Error) } + return nil +} + +func (w *ComputeOperationWaiter) SetOp(op interface{}) error { + w.Op = op.(*compute.Operation) + return nil +} + +func (w *ComputeOperationWaiter) QueryOp() (interface{}, error) { + if w.Op.Zone != "" { + zone := GetResourceNameFromSelfLink(w.Op.Zone) + return w.Service.ZoneOperations.Get(w.Project, zone, w.Op.Name).Do() + } else if w.Op.Region != "" { + region := GetResourceNameFromSelfLink(w.Op.Region) + return w.Service.RegionOperations.Get(w.Project, region, w.Op.Name).Do() + } + return w.Service.GlobalOperations.Get(w.Project, w.Op.Name).Do() +} + +func (w *ComputeOperationWaiter) OpName() string { + return w.Op.Name +} + +func (w *ComputeOperationWaiter) PendingStates() []string { + return []string{"PENDING", "RUNNING"} +} + +func (w *ComputeOperationWaiter) TargetStates() []string { + return []string{"DONE"} +} + +func computeOperationWait(client *compute.Service, op *compute.Operation, project, activity string) error { + return computeOperationWaitTime(client, op, project, activity, 4) +} + +func computeOperationWaitTime(client *compute.Service, op *compute.Operation, project, activity string, timeoutMinutes int) error { + w := &ComputeOperationWaiter{ + Service: client, + Op: op, + Project: project, + } + + if err := w.SetOp(op); err != nil { + return err + } + return OperationWait(w, activity, timeoutMinutes) +} + +func computeBetaOperationWaitTime(client *compute.Service, op *computeBeta.Operation, project, activity string, timeoutMin int) error { + opV1 := &compute.Operation{} + err := Convert(op, opV1) + if err != nil { + return err + } + + return computeOperationWaitTime(client, opV1, project, activity, timeoutMin) } // ComputeOperationError wraps compute.OperationError and implements the @@ -61,48 +91,3 @@ func (e ComputeOperationError) Error() string { return buf.String() } - -func computeOperationWait(client *compute.Service, op *compute.Operation, project, activity string) error { - return computeOperationWaitTime(client, op, project, activity, 4) -} - -func computeOperationWaitTime(client *compute.Service, op *compute.Operation, project, activity string, timeoutMin int) error { - if op.Status == "DONE" { - if op.Error != nil { - return ComputeOperationError(*op.Error) - } - return nil - } - - w := &ComputeOperationWaiter{ - Service: client, - Op: op, - Project: project, - } - - state := w.Conf() - state.Delay = 10 * time.Second - state.Timeout = time.Duration(timeoutMin) * time.Minute - state.MinTimeout = 2 * time.Second - opRaw, err := state.WaitForState() - if err != nil { - return fmt.Errorf("Error waiting for %s: %s", activity, err) - } - - resultOp := opRaw.(*compute.Operation) - if resultOp.Error != nil { - return ComputeOperationError(*resultOp.Error) - } - - return nil -} - -func computeBetaOperationWaitTime(client *compute.Service, op *computeBeta.Operation, project, activity string, timeoutMin int) error { - opV1 := &compute.Operation{} - err := Convert(op, opV1) - if err != nil { - return err - } - - return computeOperationWaitTime(client, opV1, project, activity, timeoutMin) -} diff --git a/google/container_operation.go b/google/container_operation.go index e3d2338e..82c2933f 100644 --- a/google/container_operation.go +++ b/google/container_operation.go @@ -2,142 +2,61 @@ package google import ( "fmt" - "log" - "time" - "github.com/hashicorp/terraform/helper/resource" - "google.golang.org/api/container/v1" - containerBeta "google.golang.org/api/container/v1beta1" + "google.golang.org/api/container/v1beta1" ) type ContainerOperationWaiter struct { - Service *container.Service - Op *container.Operation - Project string - Zone string -} - -type ContainerBetaOperationWaiter struct { - Service *containerBeta.Service - Op *containerBeta.Operation + Service *container.Service + Op *container.Operation Project string Location string } -func (w *ContainerOperationWaiter) Conf() *resource.StateChangeConf { - return &resource.StateChangeConf{ - Pending: []string{"PENDING", "RUNNING"}, - Target: []string{"DONE"}, - Refresh: w.RefreshFunc(), - } +func (w *ContainerOperationWaiter) State() string { + return w.Op.Status } -func (w *ContainerBetaOperationWaiter) Conf() *resource.StateChangeConf { - return &resource.StateChangeConf{ - Pending: []string{"PENDING", "RUNNING"}, - Target: []string{"DONE"}, - Refresh: w.RefreshFunc(), +func (w *ContainerOperationWaiter) Error() error { + if w.Op.StatusMessage != "" { + return fmt.Errorf(w.Op.StatusMessage) } + return nil } -func (w *ContainerOperationWaiter) RefreshFunc() resource.StateRefreshFunc { - return func() (interface{}, string, error) { - resp, err := w.Service.Projects.Zones.Operations.Get( - w.Project, w.Zone, w.Op.Name).Do() - - if err != nil { - return nil, "", err - } - - if resp.StatusMessage != "" { - return resp, resp.Status, fmt.Errorf(resp.StatusMessage) - } - - log.Printf("[DEBUG] Progress of operation %q: %q", w.Op.Name, resp.Status) - - return resp, resp.Status, err - } +func (w *ContainerOperationWaiter) SetOp(op interface{}) error { + w.Op = op.(*container.Operation) + return nil } -func (w *ContainerBetaOperationWaiter) RefreshFunc() resource.StateRefreshFunc { - return func() (interface{}, string, error) { - name := fmt.Sprintf("projects/%s/locations/%s/operations/%s", - w.Project, w.Location, w.Op.Name) - resp, err := w.Service.Projects.Locations.Operations.Get(name).Do() - - if err != nil { - return nil, "", err - } - - if resp.StatusMessage != "" { - return resp, resp.Status, fmt.Errorf(resp.StatusMessage) - } - - log.Printf("[DEBUG] Progress of operation %q: %q", w.Op.Name, resp.Status) - - return resp, resp.Status, err - } +func (w *ContainerOperationWaiter) QueryOp() (interface{}, error) { + name := fmt.Sprintf("projects/%s/locations/%s/operations/%s", + w.Project, w.Location, w.Op.Name) + return w.Service.Projects.Locations.Operations.Get(name).Do() } -func containerOperationWait(config *Config, op *container.Operation, project, zone, activity string, timeoutMinutes, minTimeoutSeconds int) error { - if op.Status == "DONE" { - if op.StatusMessage != "" { - return fmt.Errorf(op.StatusMessage) - } - return nil - } +func (w *ContainerOperationWaiter) OpName() string { + return w.Op.Name +} +func (w *ContainerOperationWaiter) PendingStates() []string { + return []string{"PENDING", "RUNNING"} +} + +func (w *ContainerOperationWaiter) TargetStates() []string { + return []string{"DONE"} +} + +func containerOperationWait(config *Config, op *container.Operation, project, location, activity string, timeoutMinutes int) error { w := &ContainerOperationWaiter{ - Service: config.clientContainer, - Op: op, - Project: project, - Zone: zone, - } - - state := w.Conf() - return waitForState(state, activity, timeoutMinutes, minTimeoutSeconds) -} - -func containerBetaOperationWait(config *Config, op *containerBeta.Operation, project, location, activity string, timeoutMinutes, minTimeoutSeconds int) error { - if op.Status == "DONE" { - if op.StatusMessage != "" { - return fmt.Errorf(op.StatusMessage) - } - return nil - } - - w := &ContainerBetaOperationWaiter{ Service: config.clientContainerBeta, Op: op, Project: project, Location: location, } - state := w.Conf() - return waitForState(state, activity, timeoutMinutes, minTimeoutSeconds) -} - -func waitForState(state *resource.StateChangeConf, activity string, timeoutMinutes, minTimeoutSeconds int) error { - state.Timeout = time.Duration(timeoutMinutes) * time.Minute - state.MinTimeout = time.Duration(minTimeoutSeconds) * time.Second - _, err := state.WaitForState() - if err != nil { - return fmt.Errorf("Error waiting for %s: %s", activity, err) - } - return nil -} - -func containerSharedOperationWait(config *Config, op interface{}, project, location, activity string, timeoutMinutes, minTimeoutSeconds int) error { - if op == nil { - panic("Attempted to wait on an Operation that was nil.") - } - - switch op.(type) { - case *container.Operation: - return containerOperationWait(config, op.(*container.Operation), project, location, activity, timeoutMinutes, minTimeoutSeconds) - case *containerBeta.Operation: - return containerBetaOperationWait(config, op.(*containerBeta.Operation), project, location, activity, timeoutMinutes, minTimeoutSeconds) - default: - panic("Attempted to wait on an Operation of unknown type.") + if err := w.SetOp(op); err != nil { + return err } + return OperationWait(w, activity, timeoutMinutes) } diff --git a/google/dataproc_cluster_operation.go b/google/dataproc_cluster_operation.go index cefd2fde..5172a347 100644 --- a/google/dataproc_cluster_operation.go +++ b/google/dataproc_cluster_operation.go @@ -1,66 +1,24 @@ package google import ( - "fmt" - "log" - "time" - - "github.com/hashicorp/terraform/helper/resource" "google.golang.org/api/dataproc/v1" ) type DataprocClusterOperationWaiter struct { Service *dataproc.Service - Op *dataproc.Operation + CommonOperationWaiter } -func (w *DataprocClusterOperationWaiter) Conf() *resource.StateChangeConf { - return &resource.StateChangeConf{ - Pending: []string{"false"}, - Target: []string{"true"}, - Refresh: w.RefreshFunc(), - } +func (w *DataprocClusterOperationWaiter) QueryOp() (interface{}, error) { + return w.Service.Projects.Regions.Operations.Get(w.Op.Name).Do() } -func (w *DataprocClusterOperationWaiter) RefreshFunc() resource.StateRefreshFunc { - return func() (interface{}, string, error) { - op, err := w.Service.Projects.Regions.Operations.Get(w.Op.Name).Do() - - if err != nil { - return nil, "", err - } - - log.Printf("[DEBUG] Got %v while polling for operation %s's 'done' status", op.Done, w.Op.Name) - - return op, fmt.Sprint(op.Done), nil - } -} - -func dataprocClusterOperationWait(config *Config, op *dataproc.Operation, activity string, timeoutMinutes, minTimeoutSeconds int) error { - if op.Done { - if op.Error != nil { - return fmt.Errorf("Error code %v, message: %s", op.Error.Code, op.Error.Message) - } - return nil - } - +func dataprocClusterOperationWait(config *Config, op *dataproc.Operation, activity string, timeoutMinutes int) error { w := &DataprocClusterOperationWaiter{ Service: config.clientDataproc, - Op: op, } - - state := w.Conf() - state.Timeout = time.Duration(timeoutMinutes) * time.Minute - state.MinTimeout = time.Duration(minTimeoutSeconds) * time.Second - opRaw, err := state.WaitForState() - if err != nil { - return fmt.Errorf("Error waiting for %s: %s", activity, err) + if err := w.SetOp(op); err != nil { + return err } - - op = opRaw.(*dataproc.Operation) - if op.Error != nil { - return fmt.Errorf("Error code %v, message: %s", op.Error.Code, op.Error.Message) - } - - return nil + return OperationWait(w, activity, timeoutMinutes) } diff --git a/google/dataproc_job_operation.go b/google/dataproc_job_operation.go index fc374b3d..6e1c56e1 100644 --- a/google/dataproc_job_operation.go +++ b/google/dataproc_job_operation.go @@ -1,14 +1,9 @@ package google import ( - "fmt" - "time" - "net/http" - "github.com/hashicorp/terraform/helper/resource" "google.golang.org/api/dataproc/v1" - "google.golang.org/api/googleapi" ) type DataprocJobOperationWaiter struct { @@ -16,80 +11,46 @@ type DataprocJobOperationWaiter struct { Region string ProjectId string JobId string + Status string } -func (w *DataprocJobOperationWaiter) ConfForDelete() *resource.StateChangeConf { - return &resource.StateChangeConf{ - Pending: []string{"EXISTS"}, - Target: []string{"DELETED"}, - Refresh: w.RefreshFuncForDelete(), - } +func (w *DataprocJobOperationWaiter) State() string { + return w.Status } -func (w *DataprocJobOperationWaiter) Conf() *resource.StateChangeConf { - // For more info on each of the states please see - // https://cloud.google.com/dataproc/docs/reference/rest/v1/projects.regions.jobs#JobStatus - return &resource.StateChangeConf{ - Pending: []string{"PENDING", "CANCEL_PENDING", "CANCEL_STARTED", "SETUP_DONE", "RUNNING"}, - Target: []string{"CANCELLED", "DONE", "ATTEMPT_FAILURE", "ERROR"}, - Refresh: w.RefreshFunc(), - } -} - -func isNotFound(err error) bool { - if err == nil { - return false - } - ae, ok := err.(*googleapi.Error) - return ok && ae.Code == http.StatusNotFound -} - -func (w *DataprocJobOperationWaiter) RefreshFuncForDelete() resource.StateRefreshFunc { - return func() (interface{}, string, error) { - _, err := w.Service.Projects.Regions.Jobs.Get(w.ProjectId, w.Region, w.JobId).Do() - - if err != nil { - if isNotFound(err) { - return "NA", "DELETED", nil - } - return nil, "", err - } - - return "JOB", "EXISTS", err - } -} - -func (w *DataprocJobOperationWaiter) RefreshFunc() resource.StateRefreshFunc { - return func() (interface{}, string, error) { - job, err := w.Service.Projects.Regions.Jobs.Get(w.ProjectId, w.Region, w.JobId).Do() - - if err != nil { - return nil, "", err - } - - return job, job.Status.State, err - } -} - -func dataprocDeleteOperationWait(config *Config, region, projectId, jobId string, activity string, timeoutMinutes, minTimeoutSeconds int) error { - w := &DataprocJobOperationWaiter{ - Service: config.clientDataproc, - Region: region, - ProjectId: projectId, - JobId: jobId, - } - - state := w.ConfForDelete() - state.Timeout = time.Duration(timeoutMinutes) * time.Minute - state.MinTimeout = time.Duration(minTimeoutSeconds) * time.Second - _, err := state.WaitForState() - if err != nil { - return fmt.Errorf("Error waiting for %s: %s", activity, err) - } - +func (w *DataprocJobOperationWaiter) Error() error { + // The "operation" is just the job, which has no special error field that we + // want to expose. return nil } +func (w *DataprocJobOperationWaiter) SetOp(job interface{}) error { + // The "operation" is just the job. Instead of holding onto the whole job + // object, we only care about the state, which gets set in QueryOp, so this + // doesn't have to do anything. + return nil +} + +func (w *DataprocJobOperationWaiter) QueryOp() (interface{}, error) { + job, err := w.Service.Projects.Regions.Jobs.Get(w.ProjectId, w.Region, w.JobId).Do() + if job != nil { + w.Status = job.Status.State + } + return job, err +} + +func (w *DataprocJobOperationWaiter) OpName() string { + return w.JobId +} + +func (w *DataprocJobOperationWaiter) PendingStates() []string { + return []string{"PENDING", "CANCEL_PENDING", "CANCEL_STARTED", "SETUP_DONE", "RUNNING"} +} + +func (w *DataprocJobOperationWaiter) TargetStates() []string { + return []string{"CANCELLED", "DONE", "ATTEMPT_FAILURE", "ERROR"} +} + func dataprocJobOperationWait(config *Config, region, projectId, jobId string, activity string, timeoutMinutes, minTimeoutSeconds int) error { w := &DataprocJobOperationWaiter{ Service: config.clientDataproc, @@ -97,14 +58,42 @@ func dataprocJobOperationWait(config *Config, region, projectId, jobId string, a ProjectId: projectId, JobId: jobId, } - - state := w.Conf() - state.Timeout = time.Duration(timeoutMinutes) * time.Minute - state.MinTimeout = time.Duration(minTimeoutSeconds) * time.Second - _, err := state.WaitForState() - if err != nil { - return fmt.Errorf("Error waiting for operation %s: %s", activity, err) - } - - return nil + return OperationWait(w, activity, timeoutMinutes) +} + +type DataprocDeleteJobOperationWaiter struct { + DataprocJobOperationWaiter +} + +func (w *DataprocDeleteJobOperationWaiter) PendingStates() []string { + return []string{"EXISTS", "ERROR"} +} + +func (w *DataprocDeleteJobOperationWaiter) TargetStates() []string { + return []string{"DELETED"} +} + +func (w *DataprocDeleteJobOperationWaiter) QueryOp() (interface{}, error) { + job, err := w.Service.Projects.Regions.Jobs.Get(w.ProjectId, w.Region, w.JobId).Do() + if err != nil { + if isGoogleApiErrorWithCode(err, http.StatusNotFound) { + w.Status = "DELETED" + return job, nil + } + w.Status = "ERROR" + } + w.Status = "EXISTS" + return job, err +} + +func dataprocDeleteOperationWait(config *Config, region, projectId, jobId string, activity string, timeoutMinutes, minTimeoutSeconds int) error { + w := &DataprocDeleteJobOperationWaiter{ + DataprocJobOperationWaiter{ + Service: config.clientDataproc, + Region: region, + ProjectId: projectId, + JobId: jobId, + }, + } + return OperationWait(w, activity, timeoutMinutes) } diff --git a/google/dns_change.go b/google/dns_change.go index f2f827a3..3d603c47 100644 --- a/google/dns_change.go +++ b/google/dns_change.go @@ -32,14 +32,11 @@ func (w *DnsChangeWaiter) RefreshFunc() resource.StateRefreshFunc { } func (w *DnsChangeWaiter) Conf() *resource.StateChangeConf { - state := &resource.StateChangeConf{ - Pending: []string{"pending"}, - Target: []string{"done"}, - Refresh: w.RefreshFunc(), + return &resource.StateChangeConf{ + Pending: []string{"pending"}, + Target: []string{"done"}, + Refresh: w.RefreshFunc(), + Timeout: 10 * time.Minute, + MinTimeout: 2 * time.Second, } - state.Delay = 10 * time.Second - state.Timeout = 10 * time.Minute - state.MinTimeout = 2 * time.Second - return state - } diff --git a/google/redis_operation.go b/google/redis_operation.go index 951534ac..b907cf5c 100644 --- a/google/redis_operation.go +++ b/google/redis_operation.go @@ -1,67 +1,24 @@ package google import ( - "fmt" - "log" - "time" - - "github.com/hashicorp/terraform/helper/resource" "google.golang.org/api/redis/v1beta1" ) type RedisOperationWaiter struct { Service *redis.ProjectsLocationsService - Op *redis.Operation + CommonOperationWaiter } -func (w *RedisOperationWaiter) RefreshFunc() resource.StateRefreshFunc { - return func() (interface{}, string, error) { - op, err := w.Service.Operations.Get(w.Op.Name).Do() - - if err != nil { - return nil, "", err - } - - log.Printf("[DEBUG] Got %v while polling for operation %s's 'done' status", op.Done, w.Op.Name) - - return op, fmt.Sprint(op.Done), nil - } +func (w *RedisOperationWaiter) QueryOp() (interface{}, error) { + return w.Service.Operations.Get(w.Op.Name).Do() } -func (w *RedisOperationWaiter) Conf() *resource.StateChangeConf { - return &resource.StateChangeConf{ - Pending: []string{"false"}, - Target: []string{"true"}, - Refresh: w.RefreshFunc(), - } -} - -func redisOperationWaitTime(service *redis.Service, op *redis.Operation, project, activity string, timeoutMin int) error { - if op.Done { - if op.Error != nil { - return fmt.Errorf("Error code %v, message: %s", op.Error.Code, op.Error.Message) - } - return nil - } - +func redisOperationWaitTime(service *redis.Service, op *redis.Operation, project, activity string, timeoutMinutes int) error { w := &RedisOperationWaiter{ Service: service.Projects.Locations, - Op: op, } - - state := w.Conf() - state.Delay = 10 * time.Second - state.Timeout = time.Duration(timeoutMin) * time.Minute - state.MinTimeout = 2 * time.Second - opRaw, err := state.WaitForState() - if err != nil { - return fmt.Errorf("Error waiting for %s: %s", activity, err) + if err := w.SetOp(op); err != nil { + return err } - - op = opRaw.(*redis.Operation) - if op.Error != nil { - return fmt.Errorf("Error code %v, message: %s", op.Error.Code, op.Error.Message) - } - - return nil + return OperationWait(w, activity, timeoutMinutes) } diff --git a/google/resource_container_cluster.go b/google/resource_container_cluster.go index 552c4ca9..db10ad24 100644 --- a/google/resource_container_cluster.go +++ b/google/resource_container_cluster.go @@ -715,7 +715,7 @@ func resourceContainerClusterCreate(d *schema.ResourceData, meta interface{}) er defer mutexKV.Unlock(containerClusterMutexKey(project, location, clusterName)) parent := fmt.Sprintf("projects/%s/locations/%s", project, location) - var op interface{} + var op *containerBeta.Operation err = retry(func() error { op, err = config.clientContainerBeta.Projects.Locations.Clusters.Create(parent, req).Do() return err @@ -728,7 +728,7 @@ func resourceContainerClusterCreate(d *schema.ResourceData, meta interface{}) er // Wait until it's created timeoutInMinutes := int(d.Timeout(schema.TimeoutCreate).Minutes()) - waitErr := containerSharedOperationWait(config, op, project, location, "creating GKE cluster", timeoutInMinutes, 3) + waitErr := containerOperationWait(config, op, project, location, "creating GKE cluster", timeoutInMinutes) if waitErr != nil { // The resource didn't actually create d.SetId("") @@ -743,7 +743,7 @@ func resourceContainerClusterCreate(d *schema.ResourceData, meta interface{}) er if err != nil { return errwrap.Wrapf("Error deleting default node pool: {{err}}", err) } - err = containerSharedOperationWait(config, op, project, location, "removing default node pool", timeoutInMinutes, 3) + err = containerOperationWait(config, op, project, location, "removing default node pool", timeoutInMinutes) if err != nil { return errwrap.Wrapf("Error deleting default node pool: {{err}}", err) } @@ -878,7 +878,7 @@ func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) er return err } // Wait until it's updated - return containerSharedOperationWait(config, op, project, location, updateDescription, timeoutInMinutes, 2) + return containerOperationWait(config, op, project, location, updateDescription, timeoutInMinutes) } } @@ -993,7 +993,7 @@ func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) er } // Wait until it's updated - return containerSharedOperationWait(config, op, project, location, "updating GKE cluster maintenance policy", timeoutInMinutes, 2) + return containerOperationWait(config, op, project, location, "updating GKE cluster maintenance policy", timeoutInMinutes) } // Call update serially. @@ -1071,7 +1071,7 @@ func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) er } // Wait until it's updated - err = containerSharedOperationWait(config, op, project, location, "updating GKE legacy ABAC", timeoutInMinutes, 2) + err = containerOperationWait(config, op, project, location, "updating GKE legacy ABAC", timeoutInMinutes) log.Println("[DEBUG] done updating enable_legacy_abac") return err } @@ -1120,7 +1120,7 @@ func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) er } // Wait until it's updated - return containerSharedOperationWait(config, op, project, location, "updating GKE logging service", timeoutInMinutes, 2) + return containerOperationWait(config, op, project, location, "updating GKE logging service", timeoutInMinutes) } // Call update serially. @@ -1148,7 +1148,7 @@ func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) er } // Wait until it's updated - err = containerSharedOperationWait(config, op, project, location, "updating GKE cluster network policy", timeoutInMinutes, 2) + err = containerOperationWait(config, op, project, location, "updating GKE cluster network policy", timeoutInMinutes) log.Println("[DEBUG] done updating network_policy") return err } @@ -1195,7 +1195,7 @@ func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) er } // Wait until it's updated - return containerSharedOperationWait(config, op, project, location, "updating GKE image type", timeoutInMinutes, 2) + return containerOperationWait(config, op, project, location, "updating GKE image type", timeoutInMinutes) } // Call update serially. @@ -1232,7 +1232,7 @@ func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) er } // Wait until it's updated - return containerSharedOperationWait(config, op, project, location, "updating master auth", timeoutInMinutes, 2) + return containerOperationWait(config, op, project, location, "updating master auth", timeoutInMinutes) } // Call update serially. @@ -1257,7 +1257,7 @@ func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) er } // Wait until it's updated - return containerSharedOperationWait(config, op, project, location, "updating GKE resource labels", timeoutInMinutes, 2) + return containerOperationWait(config, op, project, location, "updating GKE resource labels", timeoutInMinutes) } // Call update serially. @@ -1274,7 +1274,7 @@ func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) er if err != nil { return errwrap.Wrapf("Error deleting default node pool: {{err}}", err) } - err = containerSharedOperationWait(config, op, project, location, "removing default node pool", timeoutInMinutes, 3) + err = containerOperationWait(config, op, project, location, "removing default node pool", timeoutInMinutes) if err != nil { return errwrap.Wrapf("Error deleting default node pool: {{err}}", err) } @@ -1312,7 +1312,7 @@ func resourceContainerClusterDelete(d *schema.ResourceData, meta interface{}) er mutexKV.Lock(containerClusterMutexKey(project, location, clusterName)) defer mutexKV.Unlock(containerClusterMutexKey(project, location, clusterName)) - var op interface{} + var op *containerBeta.Operation var count = 0 err = resource.Retry(30*time.Second, func() *resource.RetryError { count++ @@ -1336,7 +1336,7 @@ func resourceContainerClusterDelete(d *schema.ResourceData, meta interface{}) er } // Wait until it's deleted - waitErr := containerSharedOperationWait(config, op, project, location, "deleting GKE cluster", timeoutInMinutes, 3) + waitErr := containerOperationWait(config, op, project, location, "deleting GKE cluster", timeoutInMinutes) if waitErr != nil { return waitErr } diff --git a/google/resource_container_node_pool.go b/google/resource_container_node_pool.go index fbda8c68..296f5f25 100644 --- a/google/resource_container_node_pool.go +++ b/google/resource_container_node_pool.go @@ -251,9 +251,9 @@ func resourceContainerNodePoolCreate(d *schema.ResourceData, meta interface{}) e d.SetId(fmt.Sprintf("%s/%s/%s", nodePoolInfo.location, nodePoolInfo.cluster, nodePool.Name)) - waitErr := containerBetaOperationWait(config, + waitErr := containerOperationWait(config, operation, nodePoolInfo.project, - nodePoolInfo.location, "creating GKE NodePool", int(timeout.Minutes()), 3) + nodePoolInfo.location, "creating GKE NodePool", int(timeout.Minutes())) if waitErr != nil { // The resource didn't actually create @@ -369,7 +369,7 @@ func resourceContainerNodePoolDelete(d *schema.ResourceData, meta interface{}) e } // Wait until it's deleted - waitErr := containerBetaOperationWait(config, op, nodePoolInfo.project, nodePoolInfo.location, "deleting GKE NodePool", timeoutInMinutes, 2) + waitErr := containerOperationWait(config, op, nodePoolInfo.project, nodePoolInfo.location, "deleting GKE NodePool", timeoutInMinutes) if waitErr != nil { return waitErr } @@ -571,10 +571,10 @@ func nodePoolUpdate(d *schema.ResourceData, meta interface{}, nodePoolInfo *Node } // Wait until it's updated - return containerBetaOperationWait(config, op, + return containerOperationWait(config, op, nodePoolInfo.project, nodePoolInfo.location, "updating GKE node pool", - timeoutInMinutes, 2) + timeoutInMinutes) } // Call update serially. @@ -605,10 +605,10 @@ func nodePoolUpdate(d *schema.ResourceData, meta interface{}, nodePoolInfo *Node } // Wait until it's updated - return containerBetaOperationWait(config, op, + return containerOperationWait(config, op, nodePoolInfo.project, nodePoolInfo.location, "updating GKE node pool", - timeoutInMinutes, 2) + timeoutInMinutes) } // Call update serially. @@ -637,10 +637,10 @@ func nodePoolUpdate(d *schema.ResourceData, meta interface{}, nodePoolInfo *Node } // Wait until it's updated - return containerBetaOperationWait(config, op, + return containerOperationWait(config, op, nodePoolInfo.project, nodePoolInfo.location, "updating GKE node pool size", - timeoutInMinutes, 2) + timeoutInMinutes) } // Call update serially. @@ -676,9 +676,9 @@ func nodePoolUpdate(d *schema.ResourceData, meta interface{}, nodePoolInfo *Node } // Wait until it's updated - return containerBetaOperationWait(config, op, + return containerOperationWait(config, op, nodePoolInfo.project, - nodePoolInfo.location, "updating GKE node pool management", timeoutInMinutes, 2) + nodePoolInfo.location, "updating GKE node pool management", timeoutInMinutes) } // Call update serially. @@ -707,9 +707,9 @@ func nodePoolUpdate(d *schema.ResourceData, meta interface{}, nodePoolInfo *Node } // Wait until it's updated - return containerBetaOperationWait(config, op, + return containerOperationWait(config, op, nodePoolInfo.project, - nodePoolInfo.location, "updating GKE node pool version", timeoutInMinutes, 2) + nodePoolInfo.location, "updating GKE node pool version", timeoutInMinutes) } // Call update serially. diff --git a/google/resource_dataproc_cluster.go b/google/resource_dataproc_cluster.go index 6d38c2f7..9f4e7f8e 100644 --- a/google/resource_dataproc_cluster.go +++ b/google/resource_dataproc_cluster.go @@ -456,7 +456,7 @@ func resourceDataprocClusterCreate(d *schema.ResourceData, meta interface{}) err // Wait until it's created timeoutInMinutes := int(d.Timeout(schema.TimeoutCreate).Minutes()) - waitErr := dataprocClusterOperationWait(config, op, "creating Dataproc cluster", timeoutInMinutes, 3) + waitErr := dataprocClusterOperationWait(config, op, "creating Dataproc cluster", timeoutInMinutes) if waitErr != nil { // The resource didn't actually create // Note that we do not remove the ID here - this resource tends to leave @@ -740,7 +740,7 @@ func resourceDataprocClusterUpdate(d *schema.ResourceData, meta interface{}) err } // Wait until it's updated - waitErr := dataprocClusterOperationWait(config, op, "updating Dataproc cluster ", timeoutInMinutes, 2) + waitErr := dataprocClusterOperationWait(config, op, "updating Dataproc cluster ", timeoutInMinutes) if waitErr != nil { return waitErr } @@ -944,7 +944,7 @@ func resourceDataprocClusterDelete(d *schema.ResourceData, meta interface{}) err } // Wait until it's deleted - waitErr := dataprocClusterOperationWait(config, op, "deleting Dataproc cluster", timeoutInMinutes, 3) + waitErr := dataprocClusterOperationWait(config, op, "deleting Dataproc cluster", timeoutInMinutes) if waitErr != nil { return waitErr } diff --git a/google/resource_google_project_services.go b/google/resource_google_project_services.go index 89083580..9029360a 100644 --- a/google/resource_google_project_services.go +++ b/google/resource_google_project_services.go @@ -280,7 +280,7 @@ func enableServices(s []string, pid string, config *Config) error { // Poll for the API to return activity := fmt.Sprintf("apis %q to be enabled for %s", services, pid) - _, waitErr := serviceUsageOperationWait(config, sop, activity) + waitErr := serviceUsageOperationWait(config, sop, activity) if waitErr != nil { return waitErr } @@ -342,7 +342,7 @@ func disableService(s, pid string, config *Config) error { return err } // Wait for the operation to complete - _, waitErr := serviceUsageOperationWait(config, sop, "api to disable") + waitErr := serviceUsageOperationWait(config, sop, "api to disable") if waitErr != nil { return waitErr } diff --git a/google/resourcemanager_operation.go b/google/resourcemanager_operation.go index 239730ed..5df24935 100644 --- a/google/resourcemanager_operation.go +++ b/google/resourcemanager_operation.go @@ -1,76 +1,33 @@ package google import ( - "fmt" - "log" - "time" - - "github.com/hashicorp/terraform/helper/resource" "google.golang.org/api/cloudresourcemanager/v1" resourceManagerV2Beta1 "google.golang.org/api/cloudresourcemanager/v2beta1" ) type ResourceManagerOperationWaiter struct { Service *cloudresourcemanager.Service - Op *cloudresourcemanager.Operation + CommonOperationWaiter } -func (w *ResourceManagerOperationWaiter) RefreshFunc() resource.StateRefreshFunc { - return func() (interface{}, string, error) { - op, err := w.Service.Operations.Get(w.Op.Name).Do() - - if err != nil { - return nil, "", err - } - - log.Printf("[DEBUG] Got %v while polling for operation %s's 'done' status", op.Done, w.Op.Name) - - return op, fmt.Sprint(op.Done), nil - } +func (w *ResourceManagerOperationWaiter) QueryOp() (interface{}, error) { + return w.Service.Operations.Get(w.Op.Name).Do() } -func (w *ResourceManagerOperationWaiter) Conf() *resource.StateChangeConf { - return &resource.StateChangeConf{ - Pending: []string{"false"}, - Target: []string{"true"}, - Refresh: w.RefreshFunc(), +func resourceManagerOperationWaitTime(service *cloudresourcemanager.Service, op *cloudresourcemanager.Operation, activity string, timeoutMin int) error { + w := &ResourceManagerOperationWaiter{ + Service: service, } + if err := w.SetOp(op); err != nil { + return err + } + return OperationWait(w, activity, timeoutMin) } func resourceManagerOperationWait(service *cloudresourcemanager.Service, op *cloudresourcemanager.Operation, activity string) error { return resourceManagerOperationWaitTime(service, op, activity, 4) } -func resourceManagerOperationWaitTime(service *cloudresourcemanager.Service, op *cloudresourcemanager.Operation, activity string, timeoutMin int) error { - if op.Done { - if op.Error != nil { - return fmt.Errorf("Error code %v, message: %s", op.Error.Code, op.Error.Message) - } - return nil - } - - w := &ResourceManagerOperationWaiter{ - Service: service, - Op: op, - } - - state := w.Conf() - state.Delay = 10 * time.Second - state.Timeout = time.Duration(timeoutMin) * time.Minute - state.MinTimeout = 2 * time.Second - opRaw, err := state.WaitForState() - if err != nil { - return fmt.Errorf("Error waiting for %s: %s", activity, err) - } - - op = opRaw.(*cloudresourcemanager.Operation) - if op.Error != nil { - return fmt.Errorf("Error code %v, message: %s", op.Error.Code, op.Error.Message) - } - - return nil -} - func resourceManagerV2Beta1OperationWait(service *cloudresourcemanager.Service, op *resourceManagerV2Beta1.Operation, activity string) error { return resourceManagerV2Beta1OperationWaitTime(service, op, activity, 4) } diff --git a/google/service_account_waiter.go b/google/service_account_waiter.go index 86bf0238..7a03ce18 100644 --- a/google/service_account_waiter.go +++ b/google/service_account_waiter.go @@ -33,26 +33,21 @@ func (w *ServiceAccountKeyWaiter) RefreshFunc() resource.StateRefreshFunc { } } -func (w *ServiceAccountKeyWaiter) Conf() *resource.StateChangeConf { - return &resource.StateChangeConf{ - Pending: []string{"PENDING"}, - Target: []string{"DONE"}, - Refresh: w.RefreshFunc(), - } -} - -func serviceAccountKeyWaitTime(client *iam.ProjectsServiceAccountsKeysService, keyName, publicKeyType, activity string, timeoutMin int) error { +func serviceAccountKeyWaitTime(client *iam.ProjectsServiceAccountsKeysService, keyName, publicKeyType, activity string, timeoutMinutes int) error { w := &ServiceAccountKeyWaiter{ Service: client, PublicKeyType: publicKeyType, KeyName: keyName, } - state := w.Conf() - state.Delay = 10 * time.Second - state.Timeout = time.Duration(timeoutMin) * time.Minute - state.MinTimeout = 2 * time.Second - _, err := state.WaitForState() + c := &resource.StateChangeConf{ + Pending: []string{"PENDING"}, + Target: []string{"DONE"}, + Refresh: w.RefreshFunc(), + Timeout: time.Duration(timeoutMinutes) * time.Minute, + MinTimeout: 2 * time.Second, + } + _, err := c.WaitForState() if err != nil { return fmt.Errorf("Error waiting for %s: %s", activity, err) } diff --git a/google/serviceman_operation.go b/google/serviceman_operation.go index 3e858cfa..3f87ae31 100644 --- a/google/serviceman_operation.go +++ b/google/serviceman_operation.go @@ -1,75 +1,34 @@ package google import ( - "fmt" - "log" - "time" - - "github.com/hashicorp/terraform/helper/resource" "google.golang.org/api/googleapi" "google.golang.org/api/servicemanagement/v1" ) type ServiceManagementOperationWaiter struct { Service *servicemanagement.APIService - Op *servicemanagement.Operation + CommonOperationWaiter } -func (w *ServiceManagementOperationWaiter) RefreshFunc() resource.StateRefreshFunc { - return func() (interface{}, string, error) { - var op *servicemanagement.Operation - var err error - - op, err = w.Service.Operations.Get(w.Op.Name).Do() - - if err != nil { - return nil, "", err - } - - log.Printf("[DEBUG] Got %v while polling for operation %s's 'done' status", op.Done, w.Op.Name) - - return op, fmt.Sprint(op.Done), nil - } -} - -func (w *ServiceManagementOperationWaiter) Conf() *resource.StateChangeConf { - return &resource.StateChangeConf{ - Pending: []string{"false"}, - Target: []string{"true"}, - Refresh: w.RefreshFunc(), - } +func (w *ServiceManagementOperationWaiter) QueryOp() (interface{}, error) { + return w.Service.Operations.Get(w.Op.Name).Do() } func serviceManagementOperationWait(config *Config, op *servicemanagement.Operation, activity string) (googleapi.RawMessage, error) { return serviceManagementOperationWaitTime(config, op, activity, 10) } -func serviceManagementOperationWaitTime(config *Config, op *servicemanagement.Operation, activity string, timeoutMin int) (googleapi.RawMessage, error) { - if op.Done { - if op.Error != nil { - return nil, fmt.Errorf("Error code %v, message: %s", op.Error.Code, op.Error.Message) - } - return op.Response, nil - } - +func serviceManagementOperationWaitTime(config *Config, op *servicemanagement.Operation, activity string, timeoutMinutes int) (googleapi.RawMessage, error) { w := &ServiceManagementOperationWaiter{ Service: config.clientServiceMan, - Op: op, } - state := w.Conf() - state.Delay = 10 * time.Second - state.Timeout = time.Duration(timeoutMin) * time.Minute - state.MinTimeout = 2 * time.Second - opRaw, err := state.WaitForState() - if err != nil { - return nil, fmt.Errorf("Error waiting for %s: %s", activity, err) + if err := w.SetOp(op); err != nil { + return nil, err } - op = opRaw.(*servicemanagement.Operation) - if op.Error != nil { - return nil, fmt.Errorf("Error code %v, message: %s", op.Error.Code, op.Error.Message) + if err := OperationWait(w, activity, timeoutMinutes); err != nil { + return nil, err } - - return op.Response, nil + return w.Op.Response, nil } diff --git a/google/serviceusage_operation.go b/google/serviceusage_operation.go index 65a6b212..bb8e90ab 100644 --- a/google/serviceusage_operation.go +++ b/google/serviceusage_operation.go @@ -1,75 +1,28 @@ package google import ( - "fmt" - "log" - "time" - - "github.com/hashicorp/terraform/helper/resource" - "google.golang.org/api/googleapi" "google.golang.org/api/serviceusage/v1beta1" ) -type serviceUsageOperationWaiter struct { +type ServiceUsageOperationWaiter struct { Service *serviceusage.APIService - Op *serviceusage.Operation + CommonOperationWaiter } -func (w *serviceUsageOperationWaiter) RefreshFunc() resource.StateRefreshFunc { - return func() (interface{}, string, error) { - var op *serviceusage.Operation - var err error - - op, err = w.Service.Operations.Get(w.Op.Name).Do() - - if err != nil { - return nil, "", err - } - - log.Printf("[DEBUG] Got %v while polling for operation %s's 'done' status", op.Done, w.Op.Name) - - return op, fmt.Sprint(op.Done), nil - } +func (w *ServiceUsageOperationWaiter) QueryOp() (interface{}, error) { + return w.Service.Operations.Get(w.Op.Name).Do() } -func (w *serviceUsageOperationWaiter) Conf() *resource.StateChangeConf { - return &resource.StateChangeConf{ - Pending: []string{"false"}, - Target: []string{"true"}, - Refresh: w.RefreshFunc(), - } -} - -func serviceUsageOperationWait(config *Config, op *serviceusage.Operation, activity string) (googleapi.RawMessage, error) { +func serviceUsageOperationWait(config *Config, op *serviceusage.Operation, activity string) error { return serviceUsageOperationWaitTime(config, op, activity, 10) } -func serviceUsageOperationWaitTime(config *Config, op *serviceusage.Operation, activity string, timeoutMin int) (googleapi.RawMessage, error) { - if op.Done { - if op.Error != nil { - return nil, fmt.Errorf("Error code %v, message: %s", op.Error.Code, op.Error.Message) - } - return op.Response, nil - } - - w := &serviceUsageOperationWaiter{ +func serviceUsageOperationWaitTime(config *Config, op *serviceusage.Operation, activity string, timeoutMinutes int) error { + w := &ServiceUsageOperationWaiter{ Service: config.clientServiceUsage, - Op: op, } - - state := w.Conf() - state.Delay = 10 * time.Second - state.Timeout = time.Duration(timeoutMin) * time.Minute - state.MinTimeout = 2 * time.Second - opRaw, err := state.WaitForState() - if err != nil { - return nil, fmt.Errorf("Error waiting for %s: %s", activity, err) + if err := w.SetOp(op); err != nil { + return err } - - op = opRaw.(*serviceusage.Operation) - if op.Error != nil { - return nil, fmt.Errorf("Error code %v, message: %s", op.Error.Code, op.Error.Message) - } - - return op.Response, nil + return OperationWait(w, activity, timeoutMinutes) } diff --git a/google/spanner_database_operation.go b/google/spanner_database_operation.go index 03711820..1be46151 100644 --- a/google/spanner_database_operation.go +++ b/google/spanner_database_operation.go @@ -1,69 +1,25 @@ package google import ( - "fmt" - "log" - "time" - - "github.com/hashicorp/terraform/helper/resource" "google.golang.org/api/spanner/v1" ) type SpannerDatabaseOperationWaiter struct { Service *spanner.Service - Op *spanner.Operation + CommonOperationWaiter } -func (w *SpannerDatabaseOperationWaiter) Conf() *resource.StateChangeConf { - return &resource.StateChangeConf{ - Pending: []string{"false"}, - Target: []string{"true"}, - Refresh: w.RefreshFunc(), - } +func (w *SpannerDatabaseOperationWaiter) QueryOp() (interface{}, error) { + return w.Service.Projects.Instances.Databases.Operations.Get(w.Op.Name).Do() } -func (w *SpannerDatabaseOperationWaiter) RefreshFunc() resource.StateRefreshFunc { - return func() (interface{}, string, error) { - - op, err := w.Service.Projects.Instances.Databases.Operations.Get(w.Op.Name).Do() - - if err != nil { - return nil, "", err - } - - log.Printf("[DEBUG] Got %v while polling for operation %s's 'done' status", op.Done, w.Op.Name) - - return op, fmt.Sprint(op.Done), nil - } -} - -func spannerDatabaseOperationWait(config *Config, op *spanner.Operation, activity string, timeoutMin int) error { - if op.Done { - if op.Error != nil { - return fmt.Errorf("Error code %v, message: %s", op.Error.Code, op.Error.Message) - } - return nil - } - +func spannerDatabaseOperationWait(config *Config, op *spanner.Operation, activity string, timeoutMinutes int) error { w := &SpannerDatabaseOperationWaiter{ Service: config.clientSpanner, - Op: op, } - state := w.Conf() - state.Delay = 10 * time.Second - state.Timeout = time.Duration(timeoutMin) * time.Minute - state.MinTimeout = 2 * time.Second - opRaw, err := state.WaitForState() - if err != nil { - return fmt.Errorf("Error waiting for %s: %s", activity, err) + if err := w.SetOp(op); err != nil { + return err } - - op = opRaw.(*spanner.Operation) - if op.Error != nil { - return fmt.Errorf("Error code %v, message: %s", op.Error.Code, op.Error.Message) - } - - return nil - + return OperationWait(w, activity, timeoutMinutes) } diff --git a/google/spanner_instance_operation.go b/google/spanner_instance_operation.go index 4a83c3aa..3c541950 100644 --- a/google/spanner_instance_operation.go +++ b/google/spanner_instance_operation.go @@ -1,69 +1,24 @@ package google import ( - "fmt" - "log" - "time" - - "github.com/hashicorp/terraform/helper/resource" "google.golang.org/api/spanner/v1" ) type SpannerInstanceOperationWaiter struct { Service *spanner.Service - Op *spanner.Operation + CommonOperationWaiter } -func (w *SpannerInstanceOperationWaiter) Conf() *resource.StateChangeConf { - return &resource.StateChangeConf{ - Pending: []string{"false"}, - Target: []string{"true"}, - Refresh: w.RefreshFunc(), - } +func (w *SpannerInstanceOperationWaiter) QueryOp() (interface{}, error) { + return w.Service.Projects.Instances.Operations.Get(w.Op.Name).Do() } -func (w *SpannerInstanceOperationWaiter) RefreshFunc() resource.StateRefreshFunc { - return func() (interface{}, string, error) { - - op, err := w.Service.Projects.Instances.Operations.Get(w.Op.Name).Do() - - if err != nil { - return nil, "", err - } - - log.Printf("[DEBUG] Got %v while polling for operation %s's 'done' status", op.Done, w.Op.Name) - - return op, fmt.Sprint(op.Done), nil - } -} - -func spannerInstanceOperationWait(config *Config, op *spanner.Operation, activity string, timeoutMin int) error { - if op.Done { - if op.Error != nil { - return fmt.Errorf("Error code %v, message: %s", op.Error.Code, op.Error.Message) - } - return nil - } - +func spannerInstanceOperationWait(config *Config, op *spanner.Operation, activity string, timeoutMinutes int) error { w := &SpannerInstanceOperationWaiter{ Service: config.clientSpanner, - Op: op, } - - state := w.Conf() - state.Delay = 10 * time.Second - state.Timeout = time.Duration(timeoutMin) * time.Minute - state.MinTimeout = 2 * time.Second - opRaw, err := state.WaitForState() - if err != nil { - return fmt.Errorf("Error waiting for %s: %s", activity, err) + if err := w.SetOp(op); err != nil { + return err } - - op = opRaw.(*spanner.Operation) - if op.Error != nil { - return fmt.Errorf("Error code %v, message: %s", op.Error.Code, op.Error.Message) - } - - return nil - + return OperationWait(w, activity, timeoutMinutes) } diff --git a/google/sqladmin_operation.go b/google/sqladmin_operation.go index a35d28d2..635a6eb2 100644 --- a/google/sqladmin_operation.go +++ b/google/sqladmin_operation.go @@ -2,12 +2,7 @@ package google import ( "bytes" - "fmt" - "log" - "time" - "github.com/hashicorp/terraform/helper/resource" - "google.golang.org/api/googleapi" "google.golang.org/api/sqladmin/v1beta4" ) @@ -17,29 +12,52 @@ type SqlAdminOperationWaiter struct { Project string } -func (w *SqlAdminOperationWaiter) RefreshFunc() resource.StateRefreshFunc { - return func() (interface{}, string, error) { - log.Printf("[DEBUG] self_link: %s", w.Op.SelfLink) - op, err := w.Service.Operations.Get(w.Project, w.Op.Name).Do() - - if e, ok := err.(*googleapi.Error); ok && (e.Code == 429 || e.Code == 503) { - return w.Op, "PENDING", nil - } else if err != nil { - return nil, "", err - } - - log.Printf("[DEBUG] Got %q when asking for operation %q", op.Status, w.Op.Name) - - return op, op.Status, nil - } +func (w *SqlAdminOperationWaiter) State() string { + return w.Op.Status } -func (w *SqlAdminOperationWaiter) Conf() *resource.StateChangeConf { - return &resource.StateChangeConf{ - Pending: []string{"PENDING", "RUNNING"}, - Target: []string{"DONE"}, - Refresh: w.RefreshFunc(), +func (w *SqlAdminOperationWaiter) Error() error { + if w.Op.Error != nil { + return SqlAdminOperationError(*w.Op.Error) } + return nil +} + +func (w *SqlAdminOperationWaiter) SetOp(op interface{}) error { + w.Op = op.(*sqladmin.Operation) + return nil +} + +func (w *SqlAdminOperationWaiter) QueryOp() (interface{}, error) { + return w.Service.Operations.Get(w.Project, w.Op.Name).Do() +} + +func (w *SqlAdminOperationWaiter) OpName() string { + return w.Op.Name +} + +func (w *SqlAdminOperationWaiter) PendingStates() []string { + return []string{"PENDING", "RUNNING"} +} + +func (w *SqlAdminOperationWaiter) TargetStates() []string { + return []string{"DONE"} +} + +func sqladminOperationWait(config *Config, op *sqladmin.Operation, project, activity string) error { + return sqladminOperationWaitTime(config, op, project, activity, 10) +} + +func sqladminOperationWaitTime(config *Config, op *sqladmin.Operation, project, activity string, timeoutMinutes int) error { + w := &SqlAdminOperationWaiter{ + Service: config.clientSqlAdmin, + Op: op, + Project: project, + } + if err := w.SetOp(op); err != nil { + return err + } + return OperationWait(w, activity, timeoutMinutes) } // SqlAdminOperationError wraps sqladmin.OperationError and implements the @@ -55,38 +73,3 @@ func (e SqlAdminOperationError) Error() string { return buf.String() } - -func sqladminOperationWait(config *Config, op *sqladmin.Operation, project, activity string) error { - return sqladminOperationWaitTime(config, op, project, activity, 10) -} - -func sqladminOperationWaitTime(config *Config, op *sqladmin.Operation, project, activity string, timeoutMinutes int) error { - if op.Status == "DONE" { - if op.Error != nil { - return SqlAdminOperationError(*op.Error) - } - return nil - } - - w := &SqlAdminOperationWaiter{ - Service: config.clientSqlAdmin, - Op: op, - Project: project, - } - - state := w.Conf() - state.Timeout = time.Duration(timeoutMinutes) * time.Minute - state.MinTimeout = 2 * time.Second - state.Delay = 5 * time.Second - opRaw, err := state.WaitForState() - if err != nil { - return fmt.Errorf("Error waiting for %s (op %s): %s", activity, op.Name, err) - } - - op = opRaw.(*sqladmin.Operation) - if op.Error != nil { - return SqlAdminOperationError(*op.Error) - } - - return nil -} diff --git a/google/utils.go b/google/utils.go index b080b9a8..6778242e 100644 --- a/google/utils.go +++ b/google/utils.go @@ -335,14 +335,21 @@ func retryTimeDuration(retryFunc func() error, duration time.Duration) error { return nil } for _, e := range errwrap.GetAllType(err, &googleapi.Error{}) { - if gerr, ok := e.(*googleapi.Error); ok && (gerr.Code == 429 || gerr.Code == 500 || gerr.Code == 502 || gerr.Code == 503) { - return resource.RetryableError(gerr) + if isRetryableError(e) { + return resource.RetryableError(e) } } return resource.NonRetryableError(err) }) } +func isRetryableError(err error) bool { + if gerr, ok := err.(*googleapi.Error); ok && (gerr.Code == 429 || gerr.Code == 500 || gerr.Code == 502 || gerr.Code == 503) { + return true + } + return false +} + func extractFirstMapConfig(m []interface{}) map[string]interface{} { if len(m) == 0 { return map[string]interface{}{}