blob: eaec2ff734eb37680fa317af57187fe573308423 [file] [log] [blame]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package tpgresource
import (
"fmt"
"log"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport"
cloudresourcemanager "google.golang.org/api/cloudresourcemanager/v1"
)
// Wraps Op.Error in an implementation of built-in Error
type CommonOpError struct {
*cloudresourcemanager.Status
}
func (e *CommonOpError) Error() string {
return fmt.Sprintf("Error code %v, message: %s", e.Code, e.Message)
}
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
// IsRetryable returns whether a given error should be retried.
IsRetryable(error) bool
// 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. It's expected that QueryOp will return exactly one of an
// operation or an error as non-nil, and that requests will be retried by
// specific implementations of the method.
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 {
if w == nil {
return fmt.Sprintf("Operation is nil!")
}
return fmt.Sprintf("done: %v", w.Op.Done)
}
func (w *CommonOperationWaiter) Error() error {
if w != nil && w.Op.Error != nil {
return &CommonOpError{w.Op.Error}
}
return nil
}
func (w *CommonOperationWaiter) IsRetryable(error) bool {
return false
}
func (w *CommonOperationWaiter) SetOp(op interface{}) error {
if err := Convert(op, &w.Op); err != nil {
return err
}
return nil
}
func (w *CommonOperationWaiter) OpName() string {
if w == nil {
return "<nil>"
}
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) {
op, err := w.QueryOp()
if err != nil {
// Retry 404 when getting operation (not resource state)
if transport_tpg.IsRetryableError(err, []transport_tpg.RetryErrorPredicateFunc{transport_tpg.IsNotFoundRetryableError("GET operation")}, nil) {
log.Printf("[DEBUG] Dismissed retryable error on GET operation %q: %s", w.OpName(), err)
return nil, "done: false", nil
}
return nil, "", fmt.Errorf("error while retrieving operation: %s", err)
}
if err = w.SetOp(op); err != nil {
return nil, "", fmt.Errorf("Cannot continue, unable to use operation: %s", err)
}
if err = w.Error(); err != nil {
if w.IsRetryable(err) {
log.Printf("[DEBUG] Retrying operation GET based on retryable err: %s", err)
return nil, w.State(), 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, timeout time.Duration, pollInterval time.Duration) error {
if OperationDone(w) {
return w.Error()
}
c := &resource.StateChangeConf{
Pending: w.PendingStates(),
Target: w.TargetStates(),
Refresh: CommonRefreshFunc(w),
Timeout: timeout,
MinTimeout: 2 * time.Second,
PollInterval: pollInterval,
}
opRaw, err := c.WaitForState()
if err != nil {
return fmt.Errorf("Error waiting for %s: %w", activity, err)
}
err = w.SetOp(opRaw)
if err != nil {
return err
}
return w.Error()
}
// 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