diff --git a/sdk/client/resourcemanager/poller.go b/sdk/client/resourcemanager/poller.go index 70b27ce976c..064d945f873 100644 --- a/sdk/client/resourcemanager/poller.go +++ b/sdk/client/resourcemanager/poller.go @@ -38,7 +38,7 @@ func PollerFromResponse(response *client.Response, client *Client) (poller polle strings.EqualFold(response.Request.Method, "POST") || strings.EqualFold(response.Request.Method, "PUT") if statusCodesToCheckProvisioningState && contentTypeMatchesForProvisioningStateCheck && methodIsApplicable { - provisioningState, provisioningStateErr := provisioningStatePollerFromResponse(response, client, DefaultProvisioningStatePollingInterval) + provisioningState, provisioningStateErr := provisioningStatePollerFromResponse(response, client, DefaultPollingInterval) if provisioningStateErr != nil { err = provisioningStateErr return pollers.Poller{}, fmt.Errorf("building provisioningState poller: %+v", provisioningStateErr) @@ -46,5 +46,17 @@ func PollerFromResponse(response *client.Response, client *Client) (poller polle return pollers.NewPoller(provisioningState, provisioningState.initialRetryDuration, pollers.DefaultNumberOfDroppedConnectionsToAllow), nil } + // finally, if it was a Delete that returned a 200/204 + methodIsDelete := strings.EqualFold(response.Request.Method, "DELETE") + statusCodesToCheckDelete := response.StatusCode == http.StatusOK || response.StatusCode == http.StatusNoContent + if methodIsDelete && statusCodesToCheckDelete { + deletePoller, deletePollerErr := deletePollerFromResponse(response, client, DefaultPollingInterval) + if deletePollerErr != nil { + err = deletePollerErr + return pollers.Poller{}, fmt.Errorf("building delete poller: %+v", deletePollerErr) + } + return pollers.NewPoller(deletePoller, deletePoller.initialRetryDuration, pollers.DefaultNumberOfDroppedConnectionsToAllow), nil + } + return pollers.Poller{}, fmt.Errorf("no applicable pollers were found for the response") } diff --git a/sdk/client/resourcemanager/poller_delete.go b/sdk/client/resourcemanager/poller_delete.go new file mode 100644 index 00000000000..8a866fe312f --- /dev/null +++ b/sdk/client/resourcemanager/poller_delete.go @@ -0,0 +1,120 @@ +package resourcemanager + +import ( + "context" + "fmt" + "net/http" + "time" + + "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" +) + +var _ pollers.PollerType = &deletePoller{} + +type deletePoller struct { + apiVersion string + client *Client + initialRetryDuration time.Duration + originalUri string + resourcePath string +} + +func deletePollerFromResponse(response *client.Response, client *Client, pollingInterval time.Duration) (*deletePoller, 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") + } + originalUri := response.Request.URL.RequestURI() + if response.Request.URL == nil { + return nil, fmt.Errorf("request url was nil") + } + + // 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, err := resourceManagerResourcePathFromUri(originalUri) + if err != nil { + return nil, fmt.Errorf("determining Resource Manager Resource Path from %q: %+v", originalUri, err) + } + + return &deletePoller{ + apiVersion: apiVersion, + client: client, + initialRetryDuration: pollingInterval, + originalUri: originalUri, + resourcePath: *resourcePath, + }, nil +} + +func (p deletePoller) Poll(ctx context.Context) (result *pollers.PollResult, err error) { + opts := client.RequestOptions{ + ContentType: "application/json; charset=utf-8", + ExpectedStatusCodes: []int{ + http.StatusOK, + http.StatusNotFound, + }, + HttpMethod: http.MethodGet, + OptionsObject: deleteOptions{ + 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{} + } + + result = &pollers.PollResult{ + PollInterval: p.initialRetryDuration, + } + + if resp.Response != nil { + switch resp.StatusCode { + case http.StatusNotFound: + result.Status = pollers.PollingStatusSucceeded + return + + case http.StatusOK: + result.Status = pollers.PollingStatusInProgress + return + } + + err = fmt.Errorf("unexpected status code when polling for resource after deletion, expected a 200/204 but got %d", resp.StatusCode) + } + + return +} + +var _ client.Options = deleteOptions{} + +type deleteOptions struct { + apiVersion string +} + +func (p deleteOptions) ToHeaders() *client.Headers { + return &client.Headers{} +} + +func (p deleteOptions) ToOData() *odata.Query { + return &odata.Query{} +} + +func (p deleteOptions) ToQuery() *client.QueryParams { + q := client.QueryParams{} + q.Append("api-version", p.apiVersion) + return &q +} diff --git a/sdk/client/resourcemanager/poller_lro.go b/sdk/client/resourcemanager/poller_lro.go index 5fd837623f3..d738a72711f 100644 --- a/sdk/client/resourcemanager/poller_lro.go +++ b/sdk/client/resourcemanager/poller_lro.go @@ -52,9 +52,6 @@ func longRunningOperationPollerFromResponse(resp *client.Response, client *clien return nil, fmt.Errorf("invalid polling URL %q in response: URL was not absolute", pollingUrl) } poller.pollingUrl = u - if endpoint, err := url.Parse(string(client.BaseUri)); err == nil && u.Host != endpoint.Host { - return nil, fmt.Errorf("unsupported polling URL %q: client endpoint is different", pollingUrl) - } if resp.Request != nil { poller.originalUrl = resp.Request.URL @@ -100,7 +97,9 @@ func (p *longRunningOperationPoller) Poll(ctx context.Context) (result *pollers. // Custom RetryFunc to inspect the operation payload and check the status req.RetryFunc = client.RequestRetryAny(defaultRetryFunctions...) - result = &pollers.PollResult{} + result = &pollers.PollResult{ + PollInterval: p.initialRetryDuration, + } result.HttpResponse, err = req.Execute(ctx) if err != nil { return nil, err @@ -115,6 +114,13 @@ func (p *longRunningOperationPoller) Poll(ctx context.Context) (result *pollers. } result.HttpResponse.Body.Close() + // update the poll interval if a Retry-After header is returned + if s, ok := result.HttpResponse.Header["Retry-After"]; ok { + if sleep, err := strconv.ParseInt(s[0], 10, 64); err == nil { + result.PollInterval = time.Second * time.Duration(sleep) + } + } + // 202's don't necessarily return a body, so there's nothing to deserialize if result.HttpResponse.StatusCode == http.StatusAccepted { result.Status = pollers.PollingStatusInProgress @@ -145,6 +151,10 @@ func (p *longRunningOperationPoller) Poll(ctx context.Context) (result *pollers. statusFailed: pollers.PollingStatusFailed, statusInProgress: pollers.PollingStatusInProgress, statusSucceeded: pollers.PollingStatusSucceeded, + + // whilst the standard set above should be sufficient, some APIs differ from the spec and should be documented below: + // SignalR@2022-02-01 returns `Running` rather than `InProgress` during creation + "Running": pollers.PollingStatusInProgress, } for k, v := range statuses { if strings.EqualFold(string(op.Properties.ProvisioningState), string(k)) { @@ -156,6 +166,10 @@ func (p *longRunningOperationPoller) Poll(ctx context.Context) (result *pollers. return } } + + if result.Status == "" { + err = fmt.Errorf("`result.Status` was nil/empty - `op.Status` was %q / `op.Properties.ProvisioningState` was %q", string(op.Status), string(op.Properties.ProvisioningState)) + } } return diff --git a/sdk/client/resourcemanager/poller_provisioning_state.go b/sdk/client/resourcemanager/poller_provisioning_state.go index 708460cd1c8..7985f942483 100644 --- a/sdk/client/resourcemanager/poller_provisioning_state.go +++ b/sdk/client/resourcemanager/poller_provisioning_state.go @@ -14,7 +14,7 @@ import ( "github.com/hashicorp/go-azure-sdk/sdk/odata" ) -const DefaultProvisioningStatePollingInterval = 2 * time.Second +const DefaultPollingInterval = 10 * time.Second var _ pollers.PollerType = &provisioningStatePoller{}