diff --git a/sdk/client/client.go b/sdk/client/client.go index c112237ce5..68643a8493 100644 --- a/sdk/client/client.go +++ b/sdk/client/client.go @@ -17,7 +17,6 @@ import ( "net" "net/http" "net/url" - "reflect" "regexp" "runtime" "strconv" @@ -30,6 +29,12 @@ import ( "github.com/hashicorp/go-retryablehttp" ) +const ( + // SkipPollingDelayHeader is the HTTP header used to instruct the poller to skip its standard delay wait time. + // This is typically injected via a ResponseMiddleware when running tests in a VCR replay mode. + SkipPollingDelayHeader = "X-Go-Azure-Sdk-Skip-Polling-Delay" +) + // RetryOn404ConsistencyFailureFunc can be used to retry a request when a 404 response is received func RetryOn404ConsistencyFailureFunc(resp *http.Response, _ *odata.OData) (bool, error) { return resp != nil && resp.StatusCode == http.StatusNotFound, nil @@ -349,70 +354,6 @@ func (c *Client) SetAuthorizer(authorizer auth.Authorizer) { c.Authorizer = authorizer } -// IsVcrReplaying returns true if the provided transport appears to be a VCR recorder in replay mode. -func IsVcrReplaying(transport http.RoundTripper) bool { - if transport == nil { - return false - } - - // We use reflection to avoid a hard dependency on the go-vcr package in the SDK. - // We check for "recorder" in the type name and then attempt to call the Mode() method. - t := reflect.TypeOf(transport) - if !strings.Contains(strings.ToLower(t.String()), "recorder") { - return false - } - - v := reflect.ValueOf(transport) - // If it's a pointer, get the underlying value (MethodByName works on both) - modeMethod := v.MethodByName("Mode") - if !modeMethod.IsValid() { - return false - } - - results := modeMethod.Call(nil) - if len(results) != 1 { - return false - } - - // Mode is normally an int (or an alias of int). - // In go-vcr v1/v2, ModeReplay is 1. - // In go-vcr v3/v4, ModeReplayOnly is 1 and ModeReplayWithNewEpisodes is 2. - // Support for RecordOnce (3) mode: skip only if cassette is NOT new. - if results[0].Kind() == reflect.Int { - mode := results[0].Int() - if mode == 1 || mode == 2 { - return true - } - - if mode == 3 { - // use reflection to get the unexported cassette field - v := reflect.ValueOf(transport) - if v.Kind() == reflect.Ptr { - v = v.Elem() - } - if v.Kind() == reflect.Struct { - cassetteField := v.FieldByName("cassette") - if cassetteField.IsValid() { - cassette := cassetteField - if cassette.Kind() == reflect.Ptr && !cassette.IsNil() { - cassette = cassette.Elem() - } - - if cassette.Kind() == reflect.Struct { - isNewField := cassette.FieldByName("IsNew") - if isNewField.IsValid() && isNewField.Kind() == reflect.Bool { - // If the cassette is NOT new, we are replaying - return !isNewField.Bool() - } - } - } - } - } - } - - return false -} - // SetUserAgent configures the user agent to be included in requests func (c *Client) SetUserAgent(userAgent string) { c.UserAgent = userAgent @@ -596,11 +537,6 @@ func (c *Client) Execute(ctx context.Context, req *Request) (*Response, error) { } } - // If we're running with a VCR transport in REPLAY mode, we mark the response so that the poller knows to skip the delay - if IsVcrReplaying(c.Transport) { - resp.Header.Add("X-Go-Azure-SDK-Skip-Polling-Delay", "true") - } - // Extract OData from response, intentionally ignoring any errors as it's not crucial to extract // valid OData at this point (valid json can still error here, such as any non-object literal) resp.OData, _ = odata.FromResponse(resp.Response) diff --git a/sdk/client/client_test.go b/sdk/client/client_test.go index af91981bc5..9f9e823b86 100644 --- a/sdk/client/client_test.go +++ b/sdk/client/client_test.go @@ -573,202 +573,3 @@ func TestClient_CustomTransport(t *testing.T) { t.Errorf("expected transport to be hit 1 time, got %d", hitCount) } } - -func TestClient_VCRHeaderInjected(t *testing.T) { - ctx := context.TODO() - - c := NewClient("http://localhost", "testService", "v1.0") - c.DisableRetries = true - - mockTransport := &recorderRecorder{ - mode: 1, // Replay - roundTripFunc: func(req *http.Request) (*http.Response, error) { - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader([]byte(`{"success": true}`))), - Header: make(http.Header), - Request: req, - }, nil - }, - } - c.Transport = mockTransport - - reqOpts := RequestOptions{ - ContentType: "application/json", - ExpectedStatusCodes: []int{ - http.StatusOK, - }, - HttpMethod: http.MethodGet, - Path: "/test", - } - - req, err := c.NewRequest(ctx, reqOpts) - if err != nil { - t.Fatalf("NewRequest error: %v", err) - } - - resp, err := req.Execute(ctx) - if err != nil { - t.Fatalf("Execute error: %v", err) - } - - if resp.Header.Get("X-Go-Azure-SDK-Skip-Polling-Delay") != "true" { - t.Errorf("expected skip polling delay header to be injected in replay mode, but wasn't") - } -} - -func TestClient_VCRHeaderInjectedInRecordOnceModeReplaying(t *testing.T) { - ctx := context.TODO() - - c := NewClient("http://localhost", "testService", "v1.0") - c.DisableRetries = true - - mockTransport := &recorderRecorder{ - mode: 3, // RecordOnce - cassette: &testCassette{ - IsNew: false, // REPLAYING - }, - roundTripFunc: func(req *http.Request) (*http.Response, error) { - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader([]byte(`{"success": true}`))), - Header: make(http.Header), - Request: req, - }, nil - }, - } - c.Transport = mockTransport - - reqOpts := RequestOptions{ - ContentType: "application/json", - ExpectedStatusCodes: []int{ - http.StatusOK, - }, - HttpMethod: http.MethodGet, - Path: "/test", - } - - req, err := c.NewRequest(ctx, reqOpts) - if err != nil { - t.Fatalf("NewRequest error: %v", err) - } - - resp, err := req.Execute(ctx) - if err != nil { - t.Fatalf("Execute error: %v", err) - } - - if resp.Header.Get("X-Go-Azure-SDK-Skip-Polling-Delay") != "true" { - t.Errorf("expected skip polling delay header to be injected in RecordOnce replaying mode, but wasn't") - } -} - -func TestClient_VCRHeaderNotInjectedInRecordOnceModeRecording(t *testing.T) { - ctx := context.TODO() - - c := NewClient("http://localhost", "testService", "v1.0") - c.DisableRetries = true - - mockTransport := &recorderRecorder{ - mode: 3, // RecordOnce - cassette: &testCassette{ - IsNew: true, // RECORDING - }, - roundTripFunc: func(req *http.Request) (*http.Response, error) { - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader([]byte(`{"success": true}`))), - Header: make(http.Header), - Request: req, - }, nil - }, - } - c.Transport = mockTransport - - reqOpts := RequestOptions{ - ContentType: "application/json", - ExpectedStatusCodes: []int{ - http.StatusOK, - }, - HttpMethod: http.MethodGet, - Path: "/test", - } - - req, err := c.NewRequest(ctx, reqOpts) - if err != nil { - t.Fatalf("NewRequest error: %v", err) - } - - resp, err := req.Execute(ctx) - if err != nil { - t.Fatalf("Execute error: %v", err) - } - - if resp.Header.Get("X-Go-Azure-SDK-Skip-Polling-Delay") == "true" { - t.Errorf("expected skip polling delay header NOT to be injected in RecordOnce recording mode, but it was") - } -} - -func TestClient_VCRHeaderNotInjectedInRecordMode(t *testing.T) { - ctx := context.TODO() - - c := NewClient("http://localhost", "testService", "v1.0") - c.DisableRetries = true - - mockTransport := &recorderRecorder{ - mode: 0, // Record - roundTripFunc: func(req *http.Request) (*http.Response, error) { - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader([]byte(`{"success": true}`))), - Header: make(http.Header), - Request: req, - }, nil - }, - } - c.Transport = mockTransport - - reqOpts := RequestOptions{ - ContentType: "application/json", - ExpectedStatusCodes: []int{ - http.StatusOK, - }, - HttpMethod: http.MethodGet, - Path: "/test", - } - - req, err := c.NewRequest(ctx, reqOpts) - if err != nil { - t.Fatalf("NewRequest error: %v", err) - } - - resp, err := req.Execute(ctx) - if err != nil { - t.Fatalf("Execute error: %v", err) - } - - if resp.Header.Get("X-Go-Azure-SDK-Skip-Polling-Delay") == "true" { - t.Errorf("expected skip polling delay header NOT to be injected in record mode, but it was") - } -} - -type recorderRecorder struct { - roundTripFunc func(*http.Request) (*http.Response, error) - mode int - cassette *testCassette -} - -type testCassette struct { - IsNew bool -} - -func (rt *recorderRecorder) Mode() int { - return rt.mode -} - -func (rt *recorderRecorder) RoundTrip(req *http.Request) (*http.Response, error) { - if rt.roundTripFunc != nil { - return rt.roundTripFunc(req) - } - return nil, fmt.Errorf("roundTripFunc missing from mock") -} diff --git a/sdk/client/dataplane/poller_delete.go b/sdk/client/dataplane/poller_delete.go index a93c739937..aac5f027ce 100644 --- a/sdk/client/dataplane/poller_delete.go +++ b/sdk/client/dataplane/poller_delete.go @@ -24,7 +24,7 @@ type deletePoller struct { resourcePath string } -func deletePollerFromResponse(response *client.Response, client *Client, pollingInterval time.Duration) (*deletePoller, error) { +func deletePollerFromResponse(response *client.Response, c *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`) @@ -47,9 +47,13 @@ func deletePollerFromResponse(response *client.Response, client *Client, polling return nil, fmt.Errorf("determining Resource Manager Resource Path from %q: %+v", originalUri, err) } + if s, ok := response.Header[client.SkipPollingDelayHeader]; ok && s[0] == "true" { + pollingInterval = 0 + } + return &deletePoller{ apiVersion: apiVersion, - client: client, + client: c, initialRetryDuration: pollingInterval, originalUri: originalUri, resourcePath: *resourcePath, @@ -106,13 +110,6 @@ func (p deletePoller) Poll(ctx context.Context) (result *pollers.PollResult, err return } -func (p deletePoller) SkipDelay() bool { - if p.client != nil { - return client.IsVcrReplaying(p.client.Transport) - } - return false -} - var _ client.Options = deleteOptions{} type deleteOptions struct { diff --git a/sdk/client/dataplane/poller_lro.go b/sdk/client/dataplane/poller_lro.go index 7cf8c4a1aa..34c2bc75f5 100644 --- a/sdk/client/dataplane/poller_lro.go +++ b/sdk/client/dataplane/poller_lro.go @@ -41,9 +41,9 @@ func pollingUriForLongRunningOperation(resp *client.Response) string { return pollingUrl } -func longRunningOperationPollerFromResponse(resp *client.Response, client *client.Client) (*longRunningOperationPoller, error) { +func longRunningOperationPollerFromResponse(resp *client.Response, c *client.Client) (*longRunningOperationPoller, error) { poller := longRunningOperationPoller{ - client: client, + client: c, initialRetryDuration: 10 * time.Second, maxDroppedConnections: 3, } @@ -72,6 +72,10 @@ func longRunningOperationPollerFromResponse(resp *client.Response, client *clien } } + if s, ok := resp.Header[client.SkipPollingDelayHeader]; ok && s[0] == "true" { + poller.initialRetryDuration = 0 + } + return &poller, nil } @@ -214,13 +218,6 @@ func (p *longRunningOperationPoller) Poll(ctx context.Context) (result *pollers. return result, nil } -func (p *longRunningOperationPoller) SkipDelay() bool { - if p.client != nil { - return client.IsVcrReplaying(p.client.Transport) - } - return false -} - type operationResult struct { Name *string `json:"name"` // Some APIs (such as CosmosDbPostgreSQLCluster) return a DateTime value that doesn't match RFC3339 diff --git a/sdk/client/dataplane/poller_provisioning_state.go b/sdk/client/dataplane/poller_provisioning_state.go index 096bc52c52..ed578411de 100644 --- a/sdk/client/dataplane/poller_provisioning_state.go +++ b/sdk/client/dataplane/poller_provisioning_state.go @@ -33,7 +33,7 @@ type provisioningStatePoller struct { maxDroppedConnections int } -func provisioningStatePollerFromResponse(response *client.Response, lroIsSelfReference bool, client *Client, pollingInterval time.Duration) (*provisioningStatePoller, error) { +func provisioningStatePollerFromResponse(response *client.Response, lroIsSelfReference bool, c *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`) @@ -61,9 +61,13 @@ func provisioningStatePollerFromResponse(response *client.Response, lroIsSelfRef resourcePath = *path } + if s, ok := response.Header[client.SkipPollingDelayHeader]; ok && s[0] == "true" { + pollingInterval = 0 + } + return &provisioningStatePoller{ apiVersion: apiVersion, - client: client, + client: c, initialRetryDuration: pollingInterval, originalUri: originalUri, resourcePath: resourcePath, @@ -159,13 +163,6 @@ func (p *provisioningStatePoller) Poll(ctx context.Context) (*pollers.PollResult }, nil } -func (p *provisioningStatePoller) SkipDelay() bool { - if p.client != nil { - return client.IsVcrReplaying(p.client.Transport) - } - return false -} - type provisioningStateResult struct { Properties provisioningStateResultProperties `json:"properties"` diff --git a/sdk/client/pollers/context.go b/sdk/client/pollers/context.go deleted file mode 100644 index 4abdc50d95..0000000000 --- a/sdk/client/pollers/context.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package pollers - -import "context" - -type pollKey int - -const ( - skipPollingDelayKey pollKey = iota -) - -// WithSkipPollingDelay returns a new context with the skip polling delay flag set. -// This is used to signal to PollUntilDone that it should not wait between polling attempts. -func WithSkipPollingDelay(ctx context.Context) context.Context { - return context.WithValue(ctx, skipPollingDelayKey, true) -} - -// ShouldSkipPollingDelay returns true if the context has the skip polling delay flag set. -func ShouldSkipPollingDelay(ctx context.Context) bool { - if v, ok := ctx.Value(skipPollingDelayKey).(bool); ok { - return v - } - return false -} diff --git a/sdk/client/pollers/poller.go b/sdk/client/pollers/poller.go index d3ce9c6f10..a91b5ed7a9 100644 --- a/sdk/client/pollers/poller.go +++ b/sdk/client/pollers/poller.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "net/http" "os" "sync" "time" @@ -251,23 +252,55 @@ func (p *Poller) FinalResult(model interface{}) error { } func (p *Poller) skipPollingDelay(ctx context.Context) bool { - if ShouldSkipPollingDelay(ctx) { - return true - } - if os.Getenv("GO_AZURE_SDK_SKIP_POLLING_DELAY") == "true" { return true } - if skipper, ok := p.poller.(delaySkipper); ok && skipper.SkipDelay() { - return true - } - if p.latestResponse != nil && p.latestResponse.HttpResponse != nil && p.latestResponse.HttpResponse.Response != nil && p.latestResponse.HttpResponse.Header != nil { - if p.latestResponse.HttpResponse.Header.Get("X-Go-Azure-SDK-Skip-Polling-Delay") == "true" { + if p.latestResponse.HttpResponse.Header.Get(client.SkipPollingDelayHeader) == "true" { return true } } return false } + +func PollingInProgress(resp *http.Response, pollInterval time.Duration) *PollResult { + return &PollResult{ + PollInterval: pollInterval, + HttpResponse: &client.Response{ + Response: resp, + }, + Status: PollingStatusInProgress, + } +} + +func PollingCancelled(resp *http.Response, pollInterval time.Duration) *PollResult { + return &PollResult{ + PollInterval: pollInterval, + HttpResponse: &client.Response{ + Response: resp, + }, + Status: PollingStatusCancelled, + } +} + +func PollingFailed(resp *http.Response, pollInterval time.Duration) *PollResult { + return &PollResult{ + PollInterval: pollInterval, + HttpResponse: &client.Response{ + Response: resp, + }, + Status: PollingStatusFailed, + } +} + +func PollingSucceeded(resp *http.Response, pollInterval time.Duration) *PollResult { + return &PollResult{ + PollInterval: pollInterval, + HttpResponse: &client.Response{ + Response: resp, + }, + Status: PollingStatusSucceeded, + } +} diff --git a/sdk/client/pollers/poller_test.go b/sdk/client/pollers/poller_test.go index 5319571440..86bb00a935 100644 --- a/sdk/client/pollers/poller_test.go +++ b/sdk/client/pollers/poller_test.go @@ -100,66 +100,6 @@ func TestPoller_ImmediatelyCancelled_WithDetails(t *testing.T) { } } -func TestPoller_SkipsDelayWhenSkipDelayReturnsTrue(t *testing.T) { - pollerType := fakePollerWithResults([]pollResult{ - pollers.PollResult{ - PollInterval: 1 * time.Hour, - Status: pollers.PollingStatusInProgress, - }, - pollers.PollResult{ - Status: pollers.PollingStatusSucceeded, - }, - }) - pollerType.skipDelay = true - poller := pollers.NewPoller(pollerType, 10*time.Millisecond, pollers.DefaultNumberOfDroppedConnectionsToAllow) - - // Since we're skipping the 1 hour delay, this should finish almost immediately - timeout := 100 * time.Millisecond - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - - start := time.Now() - if err := poller.PollUntilDone(ctx); err != nil { - t.Fatalf("polling: %+v", err) - } - duration := time.Since(start) - - if duration > timeout { - t.Fatalf("expected polling to finish within %v but took %v", timeout, duration) - } - - if poller.LatestStatus() != pollers.PollingStatusSucceeded { - t.Fatalf("expected LatestStatus to be Succeeded but got %q", string(poller.LatestStatus())) - } -} - -func TestPoller_SkipsDelayWhenContextFlagSet(t *testing.T) { - pollerType := fakePollerWithResults([]pollResult{ - pollers.PollResult{ - PollInterval: 1 * time.Hour, - Status: pollers.PollingStatusInProgress, - }, - pollers.PollResult{ - Status: pollers.PollingStatusSucceeded, - }, - }) - poller := pollers.NewPoller(pollerType, 10*time.Millisecond, pollers.DefaultNumberOfDroppedConnectionsToAllow) - - ctx := pollers.WithSkipPollingDelay(context.Background()) - ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) - defer cancel() - - start := time.Now() - if err := poller.PollUntilDone(ctx); err != nil { - t.Fatalf("polling: %+v", err) - } - duration := time.Since(start) - - if duration > 100*time.Millisecond { - t.Fatalf("expected polling to finish within 100ms but took %v", duration) - } -} - func TestPoller_SkipsDelayWhenEnvVarSet(t *testing.T) { pollerType := fakePollerWithResults([]pollResult{ pollers.PollResult{ @@ -195,7 +135,7 @@ func TestPoller_SkipsDelayWhenHeaderSet(t *testing.T) { Header: make(http.Header), }, } - expectedResponse.Header.Set("X-Go-Azure-SDK-Skip-Polling-Delay", "true") + expectedResponse.Header.Set(client.SkipPollingDelayHeader, "true") pollerType := fakePollerWithResults([]pollResult{ pollers.PollResult{ @@ -561,7 +501,6 @@ type errorResult struct { type fakePoller struct { count int responses []pollResult - skipDelay bool } func fakePollerWithResults(results []pollResult) *fakePoller { @@ -596,7 +535,3 @@ func (f *fakePoller) Poll(ctx context.Context) (*pollers.PollResult, error) { pollResult := result.(pollers.PollResult) return &pollResult, nil } - -func (f *fakePoller) SkipDelay() bool { - return f.skipDelay -} diff --git a/sdk/client/resourcemanager/poller_delete.go b/sdk/client/resourcemanager/poller_delete.go index 0467bd020e..9c7d6a011f 100644 --- a/sdk/client/resourcemanager/poller_delete.go +++ b/sdk/client/resourcemanager/poller_delete.go @@ -24,7 +24,7 @@ type deletePoller struct { resourcePath string } -func deletePollerFromResponse(response *client.Response, client *Client, pollingInterval time.Duration) (*deletePoller, error) { +func deletePollerFromResponse(response *client.Response, c *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`) @@ -47,9 +47,13 @@ func deletePollerFromResponse(response *client.Response, client *Client, polling return nil, fmt.Errorf("determining Resource Manager Resource Path from %q: %+v", originalUri, err) } + if s, ok := response.Header[client.SkipPollingDelayHeader]; ok && s[0] == "true" { + pollingInterval = 0 + } + return &deletePoller{ apiVersion: apiVersion, - client: client, + client: c, initialRetryDuration: pollingInterval, originalUri: originalUri, resourcePath: *resourcePath, @@ -106,13 +110,6 @@ func (p deletePoller) Poll(ctx context.Context) (result *pollers.PollResult, err return } -func (p deletePoller) SkipDelay() bool { - if p.client != nil { - return client.IsVcrReplaying(p.client.Transport) - } - return false -} - var _ client.Options = deleteOptions{} type deleteOptions struct { diff --git a/sdk/client/resourcemanager/poller_lro.go b/sdk/client/resourcemanager/poller_lro.go index 5a43171aad..84853522a8 100644 --- a/sdk/client/resourcemanager/poller_lro.go +++ b/sdk/client/resourcemanager/poller_lro.go @@ -41,9 +41,9 @@ func pollingUriForLongRunningOperation(resp *client.Response) string { return pollingUrl } -func longRunningOperationPollerFromResponse(resp *client.Response, client *client.Client) (*longRunningOperationPoller, error) { +func longRunningOperationPollerFromResponse(resp *client.Response, c *client.Client) (*longRunningOperationPoller, error) { poller := longRunningOperationPoller{ - client: client, + client: c, initialRetryDuration: 10 * time.Second, maxDroppedConnections: 3, } @@ -72,6 +72,10 @@ func longRunningOperationPollerFromResponse(resp *client.Response, client *clien } } + if s, ok := resp.Header[client.SkipPollingDelayHeader]; ok && s[0] == "true" { + poller.initialRetryDuration = 0 + } + return &poller, nil } @@ -214,13 +218,6 @@ func (p *longRunningOperationPoller) Poll(ctx context.Context) (result *pollers. return result, nil } -func (p *longRunningOperationPoller) SkipDelay() bool { - if p.client != nil { - return client.IsVcrReplaying(p.client.Transport) - } - return false -} - type operationResult struct { Name *string `json:"name"` // Some APIs (such as CosmosDbPostgreSQLCluster) return a DateTime value that doesn't match RFC3339 diff --git a/sdk/client/resourcemanager/poller_provisioning_state.go b/sdk/client/resourcemanager/poller_provisioning_state.go index cc581c28fd..a91435f7af 100644 --- a/sdk/client/resourcemanager/poller_provisioning_state.go +++ b/sdk/client/resourcemanager/poller_provisioning_state.go @@ -33,7 +33,7 @@ type provisioningStatePoller struct { maxDroppedConnections int } -func provisioningStatePollerFromResponse(response *client.Response, lroIsSelfReference bool, client *Client, pollingInterval time.Duration) (*provisioningStatePoller, error) { +func provisioningStatePollerFromResponse(response *client.Response, lroIsSelfReference bool, c *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`) @@ -61,9 +61,13 @@ func provisioningStatePollerFromResponse(response *client.Response, lroIsSelfRef resourcePath = *path } + if s, ok := response.Header[client.SkipPollingDelayHeader]; ok && s[0] == "true" { + pollingInterval = 0 + } + return &provisioningStatePoller{ apiVersion: apiVersion, - client: client, + client: c, initialRetryDuration: pollingInterval, originalUri: originalUri, resourcePath: resourcePath, @@ -159,13 +163,6 @@ func (p *provisioningStatePoller) Poll(ctx context.Context) (*pollers.PollResult }, nil } -func (p *provisioningStatePoller) SkipDelay() bool { - if p.client != nil { - return client.IsVcrReplaying(p.client.Transport) - } - return false -} - type provisioningStateResult struct { Properties provisioningStateResultProperties `json:"properties"`