62797237b9
Update Azure SDK with release v16.2.1 Update Azure autorest SDK with release v10.8.1 Signed-off-by: Yu Wang <yuwa@microsoft.com>
301 lines
10 KiB
Go
301 lines
10 KiB
Go
// Package azure provides Azure-specific implementations used with AutoRest.
|
|
// See the included examples for more detail.
|
|
package azure
|
|
|
|
// Copyright 2017 Microsoft Corporation
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/Azure/go-autorest/autorest"
|
|
)
|
|
|
|
const (
|
|
// HeaderClientID is the Azure extension header to set a user-specified request ID.
|
|
HeaderClientID = "x-ms-client-request-id"
|
|
|
|
// HeaderReturnClientID is the Azure extension header to set if the user-specified request ID
|
|
// should be included in the response.
|
|
HeaderReturnClientID = "x-ms-return-client-request-id"
|
|
|
|
// HeaderRequestID is the Azure extension header of the service generated request ID returned
|
|
// in the response.
|
|
HeaderRequestID = "x-ms-request-id"
|
|
)
|
|
|
|
// ServiceError encapsulates the error response from an Azure service.
|
|
// It adhears to the OData v4 specification for error responses.
|
|
type ServiceError struct {
|
|
Code string `json:"code"`
|
|
Message string `json:"message"`
|
|
Target *string `json:"target"`
|
|
Details []map[string]interface{} `json:"details"`
|
|
InnerError map[string]interface{} `json:"innererror"`
|
|
}
|
|
|
|
func (se ServiceError) Error() string {
|
|
result := fmt.Sprintf("Code=%q Message=%q", se.Code, se.Message)
|
|
|
|
if se.Target != nil {
|
|
result += fmt.Sprintf(" Target=%q", *se.Target)
|
|
}
|
|
|
|
if se.Details != nil {
|
|
d, err := json.Marshal(se.Details)
|
|
if err != nil {
|
|
result += fmt.Sprintf(" Details=%v", se.Details)
|
|
}
|
|
result += fmt.Sprintf(" Details=%v", string(d))
|
|
}
|
|
|
|
if se.InnerError != nil {
|
|
d, err := json.Marshal(se.InnerError)
|
|
if err != nil {
|
|
result += fmt.Sprintf(" InnerError=%v", se.InnerError)
|
|
}
|
|
result += fmt.Sprintf(" InnerError=%v", string(d))
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// UnmarshalJSON implements the json.Unmarshaler interface for the ServiceError type.
|
|
func (se *ServiceError) UnmarshalJSON(b []byte) error {
|
|
// per the OData v4 spec the details field must be an array of JSON objects.
|
|
// unfortunately not all services adhear to the spec and just return a single
|
|
// object instead of an array with one object. so we have to perform some
|
|
// shenanigans to accommodate both cases.
|
|
// http://docs.oasis-open.org/odata/odata-json-format/v4.0/os/odata-json-format-v4.0-os.html#_Toc372793091
|
|
|
|
type serviceError1 struct {
|
|
Code string `json:"code"`
|
|
Message string `json:"message"`
|
|
Target *string `json:"target"`
|
|
Details []map[string]interface{} `json:"details"`
|
|
InnerError map[string]interface{} `json:"innererror"`
|
|
}
|
|
|
|
type serviceError2 struct {
|
|
Code string `json:"code"`
|
|
Message string `json:"message"`
|
|
Target *string `json:"target"`
|
|
Details map[string]interface{} `json:"details"`
|
|
InnerError map[string]interface{} `json:"innererror"`
|
|
}
|
|
|
|
se1 := serviceError1{}
|
|
err := json.Unmarshal(b, &se1)
|
|
if err == nil {
|
|
se.populate(se1.Code, se1.Message, se1.Target, se1.Details, se1.InnerError)
|
|
return nil
|
|
}
|
|
|
|
se2 := serviceError2{}
|
|
err = json.Unmarshal(b, &se2)
|
|
if err == nil {
|
|
se.populate(se2.Code, se2.Message, se2.Target, nil, se2.InnerError)
|
|
se.Details = append(se.Details, se2.Details)
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (se *ServiceError) populate(code, message string, target *string, details []map[string]interface{}, inner map[string]interface{}) {
|
|
se.Code = code
|
|
se.Message = message
|
|
se.Target = target
|
|
se.Details = details
|
|
se.InnerError = inner
|
|
}
|
|
|
|
// RequestError describes an error response returned by Azure service.
|
|
type RequestError struct {
|
|
autorest.DetailedError
|
|
|
|
// The error returned by the Azure service.
|
|
ServiceError *ServiceError `json:"error"`
|
|
|
|
// The request id (from the x-ms-request-id-header) of the request.
|
|
RequestID string
|
|
}
|
|
|
|
// Error returns a human-friendly error message from service error.
|
|
func (e RequestError) Error() string {
|
|
return fmt.Sprintf("autorest/azure: Service returned an error. Status=%v %v",
|
|
e.StatusCode, e.ServiceError)
|
|
}
|
|
|
|
// IsAzureError returns true if the passed error is an Azure Service error; false otherwise.
|
|
func IsAzureError(e error) bool {
|
|
_, ok := e.(*RequestError)
|
|
return ok
|
|
}
|
|
|
|
// Resource contains details about an Azure resource.
|
|
type Resource struct {
|
|
SubscriptionID string
|
|
ResourceGroup string
|
|
Provider string
|
|
ResourceType string
|
|
ResourceName string
|
|
}
|
|
|
|
// ParseResourceID parses a resource ID into a ResourceDetails struct.
|
|
// See https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-template-functions-resource#return-value-4.
|
|
func ParseResourceID(resourceID string) (Resource, error) {
|
|
|
|
const resourceIDPatternText = `(?i)subscriptions/(.+)/resourceGroups/(.+)/providers/(.+?)/(.+?)/(.+)`
|
|
resourceIDPattern := regexp.MustCompile(resourceIDPatternText)
|
|
match := resourceIDPattern.FindStringSubmatch(resourceID)
|
|
|
|
if len(match) == 0 {
|
|
return Resource{}, fmt.Errorf("parsing failed for %s. Invalid resource Id format", resourceID)
|
|
}
|
|
|
|
v := strings.Split(match[5], "/")
|
|
resourceName := v[len(v)-1]
|
|
|
|
result := Resource{
|
|
SubscriptionID: match[1],
|
|
ResourceGroup: match[2],
|
|
Provider: match[3],
|
|
ResourceType: match[4],
|
|
ResourceName: resourceName,
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// NewErrorWithError creates a new Error conforming object from the
|
|
// passed packageType, method, statusCode of the given resp (UndefinedStatusCode
|
|
// if resp is nil), message, and original error. message is treated as a format
|
|
// string to which the optional args apply.
|
|
func NewErrorWithError(original error, packageType string, method string, resp *http.Response, message string, args ...interface{}) RequestError {
|
|
if v, ok := original.(*RequestError); ok {
|
|
return *v
|
|
}
|
|
|
|
statusCode := autorest.UndefinedStatusCode
|
|
if resp != nil {
|
|
statusCode = resp.StatusCode
|
|
}
|
|
return RequestError{
|
|
DetailedError: autorest.DetailedError{
|
|
Original: original,
|
|
PackageType: packageType,
|
|
Method: method,
|
|
StatusCode: statusCode,
|
|
Message: fmt.Sprintf(message, args...),
|
|
},
|
|
}
|
|
}
|
|
|
|
// WithReturningClientID returns a PrepareDecorator that adds an HTTP extension header of
|
|
// x-ms-client-request-id whose value is the passed, undecorated UUID (e.g.,
|
|
// "0F39878C-5F76-4DB8-A25D-61D2C193C3CA"). It also sets the x-ms-return-client-request-id
|
|
// header to true such that UUID accompanies the http.Response.
|
|
func WithReturningClientID(uuid string) autorest.PrepareDecorator {
|
|
preparer := autorest.CreatePreparer(
|
|
WithClientID(uuid),
|
|
WithReturnClientID(true))
|
|
|
|
return func(p autorest.Preparer) autorest.Preparer {
|
|
return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) {
|
|
r, err := p.Prepare(r)
|
|
if err != nil {
|
|
return r, err
|
|
}
|
|
return preparer.Prepare(r)
|
|
})
|
|
}
|
|
}
|
|
|
|
// WithClientID returns a PrepareDecorator that adds an HTTP extension header of
|
|
// x-ms-client-request-id whose value is passed, undecorated UUID (e.g.,
|
|
// "0F39878C-5F76-4DB8-A25D-61D2C193C3CA").
|
|
func WithClientID(uuid string) autorest.PrepareDecorator {
|
|
return autorest.WithHeader(HeaderClientID, uuid)
|
|
}
|
|
|
|
// WithReturnClientID returns a PrepareDecorator that adds an HTTP extension header of
|
|
// x-ms-return-client-request-id whose boolean value indicates if the value of the
|
|
// x-ms-client-request-id header should be included in the http.Response.
|
|
func WithReturnClientID(b bool) autorest.PrepareDecorator {
|
|
return autorest.WithHeader(HeaderReturnClientID, strconv.FormatBool(b))
|
|
}
|
|
|
|
// ExtractClientID extracts the client identifier from the x-ms-client-request-id header set on the
|
|
// http.Request sent to the service (and returned in the http.Response)
|
|
func ExtractClientID(resp *http.Response) string {
|
|
return autorest.ExtractHeaderValue(HeaderClientID, resp)
|
|
}
|
|
|
|
// ExtractRequestID extracts the Azure server generated request identifier from the
|
|
// x-ms-request-id header.
|
|
func ExtractRequestID(resp *http.Response) string {
|
|
return autorest.ExtractHeaderValue(HeaderRequestID, resp)
|
|
}
|
|
|
|
// WithErrorUnlessStatusCode returns a RespondDecorator that emits an
|
|
// azure.RequestError by reading the response body unless the response HTTP status code
|
|
// is among the set passed.
|
|
//
|
|
// If there is a chance service may return responses other than the Azure error
|
|
// format and the response cannot be parsed into an error, a decoding error will
|
|
// be returned containing the response body. In any case, the Responder will
|
|
// return an error if the status code is not satisfied.
|
|
//
|
|
// If this Responder returns an error, the response body will be replaced with
|
|
// an in-memory reader, which needs no further closing.
|
|
func WithErrorUnlessStatusCode(codes ...int) autorest.RespondDecorator {
|
|
return func(r autorest.Responder) autorest.Responder {
|
|
return autorest.ResponderFunc(func(resp *http.Response) error {
|
|
err := r.Respond(resp)
|
|
if err == nil && !autorest.ResponseHasStatusCode(resp, codes...) {
|
|
var e RequestError
|
|
defer resp.Body.Close()
|
|
|
|
// Copy and replace the Body in case it does not contain an error object.
|
|
// This will leave the Body available to the caller.
|
|
b, decodeErr := autorest.CopyAndDecode(autorest.EncodedAsJSON, resp.Body, &e)
|
|
resp.Body = ioutil.NopCloser(&b)
|
|
if decodeErr != nil {
|
|
return fmt.Errorf("autorest/azure: error response cannot be parsed: %q error: %v", b.String(), decodeErr)
|
|
} else if e.ServiceError == nil {
|
|
// Check if error is unwrapped ServiceError
|
|
if err := json.Unmarshal(b.Bytes(), &e.ServiceError); err != nil || e.ServiceError.Message == "" {
|
|
e.ServiceError = &ServiceError{
|
|
Code: "Unknown",
|
|
Message: "Unknown service error",
|
|
}
|
|
}
|
|
}
|
|
|
|
e.RequestID = ExtractRequestID(resp)
|
|
if e.StatusCode == nil {
|
|
e.StatusCode = resp.StatusCode
|
|
}
|
|
err = &e
|
|
}
|
|
return err
|
|
})
|
|
}
|
|
}
|