Skip to content

Commit

Permalink
Merge pull request #590 from hashicorp/b/poller-automation-dscnodecon…
Browse files Browse the repository at this point in the history
…figuration

client/resourcemanager: handling self-referenced LROs
  • Loading branch information
tombuildsstuff authored Aug 2, 2023
2 parents 91dd68f + d8ee0f4 commit e6e8e2b
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 4 deletions.
23 changes: 22 additions & 1 deletion sdk/client/resourcemanager/poller.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package resourcemanager
import (
"fmt"
"net/http"
"net/url"
"strings"

"github.com/hashicorp/go-azure-sdk/sdk/client"
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
63 changes: 60 additions & 3 deletions sdk/client/resourcemanager/poller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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`
Expand Down

0 comments on commit e6e8e2b

Please sign in to comment.