forked from GoogleCloudPlatform/magic-modules
/
common_operation.go
168 lines (136 loc) · 4.34 KB
/
common_operation.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
package tpgresource
import (
"fmt"
"log"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
transport_tpg "github.com/hashicorp/terraform-provider-google/google/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