Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 48 additions & 5 deletions provider/pkg/azure/azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,64 @@ func BuildUserAgent(partnerID string) (userAgent string) {
return
}

// IsNotFound returns true if the error is a HTTP 404.
// IsNotFound returns true if the error is a HTTP 404 with a valid Azure "not found" error code.
// This helps distinguish legitimate Azure "not found" responses from proxy/WAF 404 responses.
func IsNotFound(err error) bool {
Comment thread
EronWright marked this conversation as resolved.
validNotFoundCodes := map[string]bool{
"NotFound": true,
"ResourceNotFound": true,
"ResourceGroupNotFound": true,
}

if requestError, ok := err.(azure.RequestError); ok {
return requestError.StatusCode == http.StatusNotFound
if requestError.StatusCode == http.StatusNotFound {
// Check if ServiceError contains a valid Azure error code
if requestError.ServiceError != nil && validNotFoundCodes[requestError.ServiceError.Code] {
return true
}
logging.V(3).Infof("Received HTTP 404 without valid Azure error code (ServiceError=%v). "+
"This may indicate a proxy/WAF response rather than a legitimate Azure resource not found error.",
requestError.ServiceError)
return false
}
}
if requestError, ok := err.(*azure.RequestError); ok {
return requestError.StatusCode == http.StatusNotFound
if requestError.StatusCode == http.StatusNotFound {
// Check if ServiceError contains a valid Azure error code
if requestError.ServiceError != nil && validNotFoundCodes[requestError.ServiceError.Code] {
return true
}
logging.V(3).Infof("Received HTTP 404 without valid Azure error code (ServiceError=%v). "+
"This may indicate a proxy/WAF response rather than a legitimate Azure resource not found error.",
requestError.ServiceError)
return false
}
}

if responseError, ok := err.(*azcore.ResponseError); ok {
return responseError.StatusCode == http.StatusNotFound
if responseError.StatusCode == http.StatusNotFound {
// Check if ErrorCode contains a valid Azure error code
if validNotFoundCodes[responseError.ErrorCode] {
return true
}
logging.V(3).Infof("Received HTTP 404 without valid Azure error code (ErrorCode=%q). "+
"This may indicate a proxy/WAF response rather than a legitimate Azure resource not found error.",
responseError.ErrorCode)
return false
}
}

if responseError, ok := err.(*PulumiAzcoreResponseError); ok {
return responseError.StatusCode == http.StatusNotFound
if responseError.StatusCode == http.StatusNotFound {
// Check if ErrorCode contains a valid Azure error code
if validNotFoundCodes[responseError.ErrorCode] {
return true
}
logging.V(3).Infof("Received HTTP 404 without valid Azure error code (ErrorCode=%q). "+
"This may indicate a proxy/WAF response rather than a legitimate Azure resource not found error.",
responseError.ErrorCode)
return false
}
}

return false
Expand Down
96 changes: 89 additions & 7 deletions provider/pkg/azure/azure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,34 +76,116 @@ func TestBuildUserAgent(t *testing.T) {
}

func TestIsNotFound(t *testing.T) {
t.Run("autorest", func(t *testing.T) {
assert.True(t, IsNotFound(&autorestAzure.RequestError{
t.Run("autorest with valid error code", func(t *testing.T) {
Comment thread
EronWright marked this conversation as resolved.
// Test all valid "not found" error codes
validCodes := []string{"NotFound", "ResourceNotFound", "ResourceGroupNotFound"}
for _, code := range validCodes {
assert.True(t, IsNotFound(&autorestAzure.RequestError{
DetailedError: autorest.DetailedError{
StatusCode: http.StatusNotFound,
},
ServiceError: &autorestAzure.ServiceError{
Code: code,
},
}), "Should return true for error code: "+code)
}
})

t.Run("autorest with 404 but no error code", func(t *testing.T) {
// 404 without ServiceError (e.g., proxy/WAF response)
assert.False(t, IsNotFound(&autorestAzure.RequestError{
DetailedError: autorest.DetailedError{
StatusCode: http.StatusNotFound,
},
ServiceError: nil,
}))
})

t.Run("autorest with 404 but invalid error code", func(t *testing.T) {
// 404 with non-matching error code
assert.False(t, IsNotFound(&autorestAzure.RequestError{
DetailedError: autorest.DetailedError{
StatusCode: http.StatusNotFound,
},
ServiceError: &autorestAzure.ServiceError{
Code: "SomeOtherError",
},
}))
})

t.Run("autorest with non-404 status", func(t *testing.T) {
assert.False(t, IsNotFound(&autorestAzure.RequestError{
DetailedError: autorest.DetailedError{
StatusCode: http.StatusForbidden,
},
ServiceError: &autorestAzure.ServiceError{
Code: "ResourceNotFound",
},
}))
})

t.Run("azcore", func(t *testing.T) {
assert.True(t, IsNotFound(&azcore.ResponseError{
t.Run("azcore with valid error code", func(t *testing.T) {
// Test all valid "not found" error codes
validCodes := []string{"NotFound", "ResourceNotFound", "ResourceGroupNotFound"}
for _, code := range validCodes {
assert.True(t, IsNotFound(&azcore.ResponseError{
StatusCode: http.StatusNotFound,
ErrorCode: code,
}), "Should return true for error code: "+code)
}
})

t.Run("azcore with 404 but no error code", func(t *testing.T) {
// 404 without ErrorCode (e.g., proxy/WAF response)
assert.False(t, IsNotFound(&azcore.ResponseError{
StatusCode: http.StatusNotFound,
ErrorCode: "",
}))
})

t.Run("azcore with 404 but invalid error code", func(t *testing.T) {
// 404 with non-matching error code
assert.False(t, IsNotFound(&azcore.ResponseError{
StatusCode: http.StatusNotFound,
ErrorCode: "SomeOtherError",
}))
})

t.Run("azcore with non-404 status", func(t *testing.T) {
assert.False(t, IsNotFound(&azcore.ResponseError{
StatusCode: http.StatusForbidden,
ErrorCode: "ResourceNotFound",
}))
})

t.Run("provider with valid error code", func(t *testing.T) {
// Test all valid "not found" error codes
validCodes := []string{"NotFound", "ResourceNotFound", "ResourceGroupNotFound"}
for _, code := range validCodes {
assert.True(t, IsNotFound(&PulumiAzcoreResponseError{
StatusCode: http.StatusNotFound,
ErrorCode: code,
}), "Should return true for error code: "+code)
}
})

t.Run("provider with 404 but no error code", func(t *testing.T) {
// 404 without ErrorCode (e.g., proxy/WAF response)
assert.False(t, IsNotFound(&PulumiAzcoreResponseError{
StatusCode: http.StatusNotFound,
ErrorCode: "",
}))
})

t.Run("provider", func(t *testing.T) {
assert.True(t, IsNotFound(&PulumiAzcoreResponseError{
t.Run("provider with 404 but invalid error code", func(t *testing.T) {
// 404 with non-matching error code
assert.False(t, IsNotFound(&PulumiAzcoreResponseError{
StatusCode: http.StatusNotFound,
ErrorCode: "ResourceNotFound",
ErrorCode: "SomeOtherError",
}))
})

t.Run("provider with non-404 status", func(t *testing.T) {
assert.False(t, IsNotFound(&PulumiAzcoreResponseError{
StatusCode: http.StatusForbidden,
ErrorCode: "Unauthorized",
Expand Down
14 changes: 12 additions & 2 deletions provider/pkg/azure/client_azcore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -854,11 +854,21 @@ func TestNewResponseError(t *testing.T) {
assert.Equal(t, `Status=409 Message="{"foo": "bar"}"`, err.Error())
})

t.Run("404 is recognized by IsNotFound", func(t *testing.T) {
t.Run("404 with valid error code is recognized by IsNotFound", func(t *testing.T) {
resp := &http.Response{
StatusCode: 404,
Body: io.NopCloser(strings.NewReader(`not found`)),
Body: io.NopCloser(strings.NewReader(`{"error": {"code": "ResourceNotFound", "message": "not found"}}`)),
Header: http.Header{"X-Ms-Error-Code": []string{"ResourceNotFound"}},
}
assert.True(t, IsNotFound(newResponseError(resp)))
})

t.Run("404 without error code is NOT recognized by IsNotFound", func(t *testing.T) {
// This simulates a proxy/WAF 404 response without Azure error codes
resp := &http.Response{
StatusCode: 404,
Body: io.NopCloser(strings.NewReader(`not found`)),
}
assert.False(t, IsNotFound(newResponseError(resp)))
})
}
Loading