-
Notifications
You must be signed in to change notification settings - Fork 36
/
poller_provisioning_state.go
199 lines (170 loc) · 6.18 KB
/
poller_provisioning_state.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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package resourcemanager
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"github.com/hashicorp/go-azure-helpers/lang/pointer"
"github.com/hashicorp/go-azure-sdk/sdk/client"
"github.com/hashicorp/go-azure-sdk/sdk/client/pollers"
"github.com/hashicorp/go-azure-sdk/sdk/odata"
)
const DefaultPollingInterval = 10 * time.Second
var _ pollers.PollerType = &provisioningStatePoller{}
type provisioningStatePoller struct {
apiVersion string
client *Client
initialRetryDuration time.Duration
originalUri string
resourcePath string
}
func provisioningStatePollerFromResponse(response *client.Response, lroIsSelfReference bool, client *Client, pollingInterval time.Duration) (*provisioningStatePoller, error) {
// if we've gotten to this point then we're polling against a Resource Manager resource/operation of some kind
// we next need to determine if the current URI is a Resource Manager resource, or if we should be polling on the
// resource (e.g. `/my/resource`) rather than an operation on the resource (e.g. `/my/resource/start`)
if response.Request == nil {
return nil, fmt.Errorf("request was nil")
}
if response.Request.URL == nil {
return nil, fmt.Errorf("request url was nil")
}
originalUri := response.Request.URL.RequestURI()
// all Resource Manager operations require the `api-version` querystring
apiVersion := response.Request.URL.Query().Get("api-version")
if apiVersion == "" {
return nil, fmt.Errorf("unable to determine `api-version` from %q", originalUri)
}
resourcePath := originalUri
if !lroIsSelfReference {
// if it's a self-reference (such as API Management's API/API Schema)
path, err := resourceManagerResourcePathFromUri(originalUri)
if err != nil {
return nil, fmt.Errorf("determining Resource Manager Resource Path from %q: %+v", originalUri, err)
}
resourcePath = *path
}
return &provisioningStatePoller{
apiVersion: apiVersion,
client: client,
initialRetryDuration: pollingInterval,
originalUri: originalUri,
resourcePath: resourcePath,
}, nil
}
func (p *provisioningStatePoller) Poll(ctx context.Context) (*pollers.PollResult, error) {
opts := client.RequestOptions{
ContentType: "application/json; charset=utf-8",
ExpectedStatusCodes: []int{
http.StatusOK,
},
HttpMethod: http.MethodGet,
OptionsObject: provisioningStateOptions{
apiVersion: p.apiVersion,
},
Path: p.resourcePath,
}
req, err := p.client.NewRequest(ctx, opts)
if err != nil {
return nil, fmt.Errorf("building request: %+v", err)
}
resp, err := p.client.Execute(ctx, req)
if err != nil {
return nil, fmt.Errorf("executing request: %+v", err)
}
if resp == nil {
return nil, pollers.PollingDroppedConnectionError{}
}
var result provisioningStateResult
if err := resp.Unmarshal(&result); err != nil {
return nil, fmt.Errorf("unmarshaling result: %+v", err)
}
status := ""
if string(result.Status) != "" {
status = string(result.Status)
}
if string(result.Properties.ProvisioningState) != "" {
status = string(result.Properties.ProvisioningState)
}
if status == "" {
// Some Operations support both an LRO and immediate completion, but _don't_ return a provisioningState field
// since we're checking for a 200 OK, if we didn't get a provisioningState field, for the moment we have to
// assume that we're done.
// Examples: `APIManagement` API Versions `2021-08-01` and `2022-08-01` - `Services.GlobalSchemaCreateOrUpdate`.
// Examples: `Automation` API Versions `2020-01-13-preview` - `DscNodeConfiguration.CreateOrUpdate`.
// https://github.com/hashicorp/go-azure-sdk/issues/542
return &pollers.PollResult{
PollInterval: p.initialRetryDuration,
Status: pollers.PollingStatusSucceeded,
}, nil
}
if strings.EqualFold(status, string(statusCanceled)) || strings.EqualFold(status, string(statusCancelled)) {
return nil, pollers.PollingCancelledError{
HttpResponse: resp,
}
}
if strings.EqualFold(status, string(statusFailed)) {
return nil, pollers.PollingFailedError{
HttpResponse: resp,
}
}
if strings.EqualFold(status, string(statusSucceeded)) {
return &pollers.PollResult{
PollInterval: p.initialRetryDuration,
Status: pollers.PollingStatusSucceeded,
}, nil
}
// some API's have unique provisioningStates (e.g. Storage Accounts has `ResolvingDns`)
// if we don't recognise it, treat it as a polling status
return &pollers.PollResult{
PollInterval: p.initialRetryDuration,
Status: pollers.PollingStatusInProgress,
}, nil
}
type provisioningStateResult struct {
Properties struct {
// Some API's (such as Storage) return the Resource Representation from the LRO API, as such we need to check provisioningState
ProvisioningState status `json:"provisioningState"`
} `json:"properties"`
// others return Status, so we check that too
Status status `json:"status"`
}
func resourceManagerResourcePathFromUri(input string) (*string, error) {
parsed, err := url.ParseRequestURI(input)
if err != nil {
return nil, fmt.Errorf("parsing %q: %+v", input, err)
}
segments := strings.Split(strings.TrimPrefix(parsed.Path, "/"), "/")
if len(segments) == 0 {
return nil, fmt.Errorf("polling uri was empty")
}
// Resources within Resource Manager are always a matching list of key-value pairs
// however Operations against a given resource (e.g. `/start`) will be an uneven pair
// as such, if we have matching pairs, this a Resource
if len(segments)%2 != 0 {
segments = segments[0 : len(segments)-1]
parsed.Path = fmt.Sprintf("/%s", strings.Join(segments, "/"))
}
if parsed.Path == "/" {
return nil, fmt.Errorf("expected a Resource Manager URI but got %q", parsed.Path)
}
return pointer.To(parsed.Path), nil
}
var _ client.Options = provisioningStateOptions{}
type provisioningStateOptions struct {
apiVersion string
}
func (p provisioningStateOptions) ToHeaders() *client.Headers {
return &client.Headers{}
}
func (p provisioningStateOptions) ToOData() *odata.Query {
return &odata.Query{}
}
func (p provisioningStateOptions) ToQuery() *client.QueryParams {
q := client.QueryParams{}
q.Append("api-version", p.apiVersion)
return &q
}