diff --git a/sdk/client/resourcemanager/poller.go b/sdk/client/resourcemanager/poller.go index 113b04d9ae0..66d04205caa 100644 --- a/sdk/client/resourcemanager/poller.go +++ b/sdk/client/resourcemanager/poller.go @@ -6,6 +6,7 @@ package resourcemanager import ( "fmt" "net/http" + "net/url" "strings" "github.com/hashicorp/go-azure-sdk/sdk/client" @@ -17,11 +18,14 @@ func PollerFromResponse(response *client.Response, client *Client) (poller polle return pollers.Poller{}, fmt.Errorf("no HTTP Response was returned") } + originalRequestUri := response.Request.URL.String() + // If this is a LRO we should either have a 201/202 with a Polling URI header isLroStatus := response.StatusCode == http.StatusCreated || response.StatusCode == http.StatusAccepted methodIsDelete := strings.EqualFold(response.Request.Method, "DELETE") lroPollingUri := pollingUriForLongRunningOperation(response) - if isLroStatus && lroPollingUri != "" && !methodIsDelete { + lroIsSelfReference := isLROSelfReference(lroPollingUri, originalRequestUri) + if isLroStatus && lroPollingUri != "" && !methodIsDelete && !lroIsSelfReference { lro, lroErr := longRunningOperationPollerFromResponse(response, client.Client) if lroErr != nil { err = lroErr @@ -63,3 +67,20 @@ func PollerFromResponse(response *client.Response, client *Client) (poller polle return pollers.Poller{}, fmt.Errorf("no applicable pollers were found for the response") } + +func isLROSelfReference(lroPollingUri, originalRequestUri string) bool { + // Some APIs return a LRO URI of themselves, meaning that we should be checking a 200 OK is returned rather + // than polling as usual. Automation@2022-08-08 - DSCNodeConfiguration CreateOrUpdate is one such example. + first, err := url.Parse(lroPollingUri) + if err != nil { + return false + } + second, err := url.Parse(originalRequestUri) + if err != nil { + return false + } + + // The Query String can be a different API version / options and in some cases the Host + // is returned with `:443` - so the path should be sufficient as a check. + return strings.EqualFold(first.Path, second.Path) +} diff --git a/sdk/client/resourcemanager/poller_test.go b/sdk/client/resourcemanager/poller_test.go index 44cdae2c653..d85ce2456dc 100644 --- a/sdk/client/resourcemanager/poller_test.go +++ b/sdk/client/resourcemanager/poller_test.go @@ -26,7 +26,11 @@ func TestNewPoller_LongRunningOperation(t *testing.T) { Header: http.Header{ http.CanonicalHeaderKey("Location"): []string{"https://async-url-test.local/subscriptions/1234/providers/foo/operations/6789"}, }, - Request: &http.Request{}, + Request: &http.Request{ + URL: &url.URL{ + Path: "/example", + }, + }, }, }, valid: true, @@ -38,7 +42,11 @@ func TestNewPoller_LongRunningOperation(t *testing.T) { Header: http.Header{ http.CanonicalHeaderKey("Location"): []string{"https://async-url-test.local/subscriptions/1234/providers/foo/operations/6789"}, }, - Request: &http.Request{}, + Request: &http.Request{ + URL: &url.URL{ + Path: "/example", + }, + }, }, }, valid: true, @@ -49,7 +57,11 @@ func TestNewPoller_LongRunningOperation(t *testing.T) { Response: &http.Response{ StatusCode: http.StatusAccepted, Header: http.Header{}, - Request: &http.Request{}, + Request: &http.Request{ + URL: &url.URL{ + Path: "/example", + }, + }, }, }, valid: false, @@ -75,6 +87,51 @@ func TestNewPoller_LongRunningOperation(t *testing.T) { } } +func TestNewPoller_LongRunningOperationWithSelfReference(t *testing.T) { + testData := []struct { + response *client.Response + valid bool + }{ + { + response: &client.Response{ + Response: &http.Response{ + StatusCode: http.StatusCreated, + Header: http.Header{ + http.CanonicalHeaderKey("Content-Type"): []string{"application/json"}, + http.CanonicalHeaderKey("Location"): []string{"https://async-url-test.local/subscriptions/1234/providers/foo/operations/6789?api-version=2020-01-01"}, + }, + Request: &http.Request{ + Method: http.MethodPost, + URL: func() *url.URL { + u, _ := url.Parse("https://async-url-test.local/subscriptions/1234/providers/foo/operations/6789?api-version=2020-01-01") + return u + }(), + }, + }, + }, + valid: true, + }, + } + + for _, v := range testData { + localApi := environments.NewApiEndpoint("Example", "https://async-url-test.local", nil) + client, err := resourcemanager.NewResourceManagerClient(localApi, "example", "2020-02-01") + if err != nil { + t.Fatalf("building client: %+v", err) + } + _, err = resourcemanager.PollerFromResponse(v.response, client) + if v.valid { + if err != nil { + t.Fatal(fmt.Errorf("building poller from response: %+v", err)) + } + } else { + if err == nil { + t.Fatalf("expected an error but didn't get one") + } + } + } +} + func TestNewPoller_ProvisioningState_ValidResourceManagerResource(t *testing.T) { // validates that a Resource Manager Resource is valid // e.g. `/some/resource`