...

Source file src/github.com/Azure/go-autorest/autorest/azure/azure.go

Documentation: github.com/Azure/go-autorest/autorest/azure

     1  // Package azure provides Azure-specific implementations used with AutoRest.
     2  // See the included examples for more detail.
     3  package azure
     4  
     5  // Copyright 2017 Microsoft Corporation
     6  //
     7  //  Licensed under the Apache License, Version 2.0 (the "License");
     8  //  you may not use this file except in compliance with the License.
     9  //  You may obtain a copy of the License at
    10  //
    11  //      http://www.apache.org/licenses/LICENSE-2.0
    12  //
    13  //  Unless required by applicable law or agreed to in writing, software
    14  //  distributed under the License is distributed on an "AS IS" BASIS,
    15  //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    16  //  See the License for the specific language governing permissions and
    17  //  limitations under the License.
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/json"
    22  	"fmt"
    23  	"io/ioutil"
    24  	"net/http"
    25  	"regexp"
    26  	"strconv"
    27  	"strings"
    28  
    29  	"github.com/Azure/go-autorest/autorest"
    30  )
    31  
    32  const (
    33  	// HeaderClientID is the Azure extension header to set a user-specified request ID.
    34  	HeaderClientID = "x-ms-client-request-id"
    35  
    36  	// HeaderReturnClientID is the Azure extension header to set if the user-specified request ID
    37  	// should be included in the response.
    38  	HeaderReturnClientID = "x-ms-return-client-request-id"
    39  
    40  	// HeaderContentType is the type of the content in the HTTP response.
    41  	HeaderContentType = "Content-Type"
    42  
    43  	// HeaderRequestID is the Azure extension header of the service generated request ID returned
    44  	// in the response.
    45  	HeaderRequestID = "x-ms-request-id"
    46  )
    47  
    48  // ServiceError encapsulates the error response from an Azure service.
    49  // It adhears to the OData v4 specification for error responses.
    50  type ServiceError struct {
    51  	Code           string                   `json:"code"`
    52  	Message        string                   `json:"message"`
    53  	Target         *string                  `json:"target"`
    54  	Details        []map[string]interface{} `json:"details"`
    55  	InnerError     map[string]interface{}   `json:"innererror"`
    56  	AdditionalInfo []map[string]interface{} `json:"additionalInfo"`
    57  }
    58  
    59  func (se ServiceError) Error() string {
    60  	result := fmt.Sprintf("Code=%q Message=%q", se.Code, se.Message)
    61  
    62  	if se.Target != nil {
    63  		result += fmt.Sprintf(" Target=%q", *se.Target)
    64  	}
    65  
    66  	if se.Details != nil {
    67  		d, err := json.Marshal(se.Details)
    68  		if err != nil {
    69  			result += fmt.Sprintf(" Details=%v", se.Details)
    70  		}
    71  		result += fmt.Sprintf(" Details=%s", d)
    72  	}
    73  
    74  	if se.InnerError != nil {
    75  		d, err := json.Marshal(se.InnerError)
    76  		if err != nil {
    77  			result += fmt.Sprintf(" InnerError=%v", se.InnerError)
    78  		}
    79  		result += fmt.Sprintf(" InnerError=%s", d)
    80  	}
    81  
    82  	if se.AdditionalInfo != nil {
    83  		d, err := json.Marshal(se.AdditionalInfo)
    84  		if err != nil {
    85  			result += fmt.Sprintf(" AdditionalInfo=%v", se.AdditionalInfo)
    86  		}
    87  		result += fmt.Sprintf(" AdditionalInfo=%s", d)
    88  	}
    89  
    90  	return result
    91  }
    92  
    93  // UnmarshalJSON implements the json.Unmarshaler interface for the ServiceError type.
    94  func (se *ServiceError) UnmarshalJSON(b []byte) error {
    95  	// http://docs.oasis-open.org/odata/odata-json-format/v4.0/os/odata-json-format-v4.0-os.html#_Toc372793091
    96  
    97  	type serviceErrorInternal struct {
    98  		Code           string                   `json:"code"`
    99  		Message        string                   `json:"message"`
   100  		Target         *string                  `json:"target,omitempty"`
   101  		AdditionalInfo []map[string]interface{} `json:"additionalInfo,omitempty"`
   102  		// not all services conform to the OData v4 spec.
   103  		// the following fields are where we've seen discrepancies
   104  
   105  		// spec calls for []map[string]interface{} but have seen map[string]interface{}
   106  		Details interface{} `json:"details,omitempty"`
   107  
   108  		// spec calls for map[string]interface{} but have seen []map[string]interface{} and string
   109  		InnerError interface{} `json:"innererror,omitempty"`
   110  	}
   111  
   112  	sei := serviceErrorInternal{}
   113  	if err := json.Unmarshal(b, &sei); err != nil {
   114  		return err
   115  	}
   116  
   117  	// copy the fields we know to be correct
   118  	se.AdditionalInfo = sei.AdditionalInfo
   119  	se.Code = sei.Code
   120  	se.Message = sei.Message
   121  	se.Target = sei.Target
   122  
   123  	// converts an []interface{} to []map[string]interface{}
   124  	arrayOfObjs := func(v interface{}) ([]map[string]interface{}, bool) {
   125  		arrayOf, ok := v.([]interface{})
   126  		if !ok {
   127  			return nil, false
   128  		}
   129  		final := []map[string]interface{}{}
   130  		for _, item := range arrayOf {
   131  			as, ok := item.(map[string]interface{})
   132  			if !ok {
   133  				return nil, false
   134  			}
   135  			final = append(final, as)
   136  		}
   137  		return final, true
   138  	}
   139  
   140  	// convert the remaining fields, falling back to raw JSON if necessary
   141  
   142  	if c, ok := arrayOfObjs(sei.Details); ok {
   143  		se.Details = c
   144  	} else if c, ok := sei.Details.(map[string]interface{}); ok {
   145  		se.Details = []map[string]interface{}{c}
   146  	} else if sei.Details != nil {
   147  		// stuff into Details
   148  		se.Details = []map[string]interface{}{
   149  			{"raw": sei.Details},
   150  		}
   151  	}
   152  
   153  	if c, ok := sei.InnerError.(map[string]interface{}); ok {
   154  		se.InnerError = c
   155  	} else if c, ok := arrayOfObjs(sei.InnerError); ok {
   156  		// if there's only one error extract it
   157  		if len(c) == 1 {
   158  			se.InnerError = c[0]
   159  		} else {
   160  			// multiple errors, stuff them into the value
   161  			se.InnerError = map[string]interface{}{
   162  				"multi": c,
   163  			}
   164  		}
   165  	} else if c, ok := sei.InnerError.(string); ok {
   166  		se.InnerError = map[string]interface{}{"error": c}
   167  	} else if sei.InnerError != nil {
   168  		// stuff into InnerError
   169  		se.InnerError = map[string]interface{}{
   170  			"raw": sei.InnerError,
   171  		}
   172  	}
   173  	return nil
   174  }
   175  
   176  // RequestError describes an error response returned by Azure service.
   177  type RequestError struct {
   178  	autorest.DetailedError
   179  
   180  	// The error returned by the Azure service.
   181  	ServiceError *ServiceError `json:"error" xml:"Error"`
   182  
   183  	// The request id (from the x-ms-request-id-header) of the request.
   184  	RequestID string
   185  }
   186  
   187  // Error returns a human-friendly error message from service error.
   188  func (e RequestError) Error() string {
   189  	return fmt.Sprintf("autorest/azure: Service returned an error. Status=%v %v",
   190  		e.StatusCode, e.ServiceError)
   191  }
   192  
   193  // IsAzureError returns true if the passed error is an Azure Service error; false otherwise.
   194  func IsAzureError(e error) bool {
   195  	_, ok := e.(*RequestError)
   196  	return ok
   197  }
   198  
   199  // Resource contains details about an Azure resource.
   200  type Resource struct {
   201  	SubscriptionID string
   202  	ResourceGroup  string
   203  	Provider       string
   204  	ResourceType   string
   205  	ResourceName   string
   206  }
   207  
   208  // String function returns a string in form of azureResourceID
   209  func (r Resource) String() string {
   210  	return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/%s/%s/%s", r.SubscriptionID, r.ResourceGroup, r.Provider, r.ResourceType, r.ResourceName)
   211  }
   212  
   213  // ParseResourceID parses a resource ID into a ResourceDetails struct.
   214  // See https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-functions-resource?tabs=json#resourceid.
   215  func ParseResourceID(resourceID string) (Resource, error) {
   216  
   217  	const resourceIDPatternText = `(?i)^/subscriptions/(.+)/resourceGroups/(.+)/providers/(.+?)/(.+?)/(.+)$`
   218  	resourceIDPattern := regexp.MustCompile(resourceIDPatternText)
   219  	match := resourceIDPattern.FindStringSubmatch(resourceID)
   220  
   221  	if len(match) == 0 {
   222  		return Resource{}, fmt.Errorf("parsing failed for %s. Invalid resource Id format", resourceID)
   223  	}
   224  
   225  	v := strings.Split(match[5], "/")
   226  	resourceName := v[len(v)-1]
   227  
   228  	result := Resource{
   229  		SubscriptionID: match[1],
   230  		ResourceGroup:  match[2],
   231  		Provider:       match[3],
   232  		ResourceType:   match[4],
   233  		ResourceName:   resourceName,
   234  	}
   235  
   236  	return result, nil
   237  }
   238  
   239  // NewErrorWithError creates a new Error conforming object from the
   240  // passed packageType, method, statusCode of the given resp (UndefinedStatusCode
   241  // if resp is nil), message, and original error. message is treated as a format
   242  // string to which the optional args apply.
   243  func NewErrorWithError(original error, packageType string, method string, resp *http.Response, message string, args ...interface{}) RequestError {
   244  	if v, ok := original.(*RequestError); ok {
   245  		return *v
   246  	}
   247  
   248  	statusCode := autorest.UndefinedStatusCode
   249  	if resp != nil {
   250  		statusCode = resp.StatusCode
   251  	}
   252  	return RequestError{
   253  		DetailedError: autorest.DetailedError{
   254  			Original:    original,
   255  			PackageType: packageType,
   256  			Method:      method,
   257  			StatusCode:  statusCode,
   258  			Message:     fmt.Sprintf(message, args...),
   259  		},
   260  	}
   261  }
   262  
   263  // WithReturningClientID returns a PrepareDecorator that adds an HTTP extension header of
   264  // x-ms-client-request-id whose value is the passed, undecorated UUID (e.g.,
   265  // "0F39878C-5F76-4DB8-A25D-61D2C193C3CA"). It also sets the x-ms-return-client-request-id
   266  // header to true such that UUID accompanies the http.Response.
   267  func WithReturningClientID(uuid string) autorest.PrepareDecorator {
   268  	preparer := autorest.CreatePreparer(
   269  		WithClientID(uuid),
   270  		WithReturnClientID(true))
   271  
   272  	return func(p autorest.Preparer) autorest.Preparer {
   273  		return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) {
   274  			r, err := p.Prepare(r)
   275  			if err != nil {
   276  				return r, err
   277  			}
   278  			return preparer.Prepare(r)
   279  		})
   280  	}
   281  }
   282  
   283  // WithClientID returns a PrepareDecorator that adds an HTTP extension header of
   284  // x-ms-client-request-id whose value is passed, undecorated UUID (e.g.,
   285  // "0F39878C-5F76-4DB8-A25D-61D2C193C3CA").
   286  func WithClientID(uuid string) autorest.PrepareDecorator {
   287  	return autorest.WithHeader(HeaderClientID, uuid)
   288  }
   289  
   290  // WithReturnClientID returns a PrepareDecorator that adds an HTTP extension header of
   291  // x-ms-return-client-request-id whose boolean value indicates if the value of the
   292  // x-ms-client-request-id header should be included in the http.Response.
   293  func WithReturnClientID(b bool) autorest.PrepareDecorator {
   294  	return autorest.WithHeader(HeaderReturnClientID, strconv.FormatBool(b))
   295  }
   296  
   297  // ExtractClientID extracts the client identifier from the x-ms-client-request-id header set on the
   298  // http.Request sent to the service (and returned in the http.Response)
   299  func ExtractClientID(resp *http.Response) string {
   300  	return autorest.ExtractHeaderValue(HeaderClientID, resp)
   301  }
   302  
   303  // ExtractRequestID extracts the Azure server generated request identifier from the
   304  // x-ms-request-id header.
   305  func ExtractRequestID(resp *http.Response) string {
   306  	return autorest.ExtractHeaderValue(HeaderRequestID, resp)
   307  }
   308  
   309  // WithErrorUnlessStatusCode returns a RespondDecorator that emits an
   310  // azure.RequestError by reading the response body unless the response HTTP status code
   311  // is among the set passed.
   312  //
   313  // If there is a chance service may return responses other than the Azure error
   314  // format and the response cannot be parsed into an error, a decoding error will
   315  // be returned containing the response body. In any case, the Responder will
   316  // return an error if the status code is not satisfied.
   317  //
   318  // If this Responder returns an error, the response body will be replaced with
   319  // an in-memory reader, which needs no further closing.
   320  func WithErrorUnlessStatusCode(codes ...int) autorest.RespondDecorator {
   321  	return func(r autorest.Responder) autorest.Responder {
   322  		return autorest.ResponderFunc(func(resp *http.Response) error {
   323  			err := r.Respond(resp)
   324  			if err == nil && !autorest.ResponseHasStatusCode(resp, codes...) {
   325  				var e RequestError
   326  				defer resp.Body.Close()
   327  
   328  				encodedAs := autorest.EncodedAsJSON
   329  				if strings.Contains(resp.Header.Get("Content-Type"), "xml") {
   330  					encodedAs = autorest.EncodedAsXML
   331  				}
   332  
   333  				// Copy and replace the Body in case it does not contain an error object.
   334  				// This will leave the Body available to the caller.
   335  				b, decodeErr := autorest.CopyAndDecode(encodedAs, resp.Body, &e)
   336  				resp.Body = ioutil.NopCloser(&b)
   337  				if decodeErr != nil {
   338  					return fmt.Errorf("autorest/azure: error response cannot be parsed: %q error: %v", b, decodeErr)
   339  				}
   340  				if e.ServiceError == nil {
   341  					// Check if error is unwrapped ServiceError
   342  					decoder := autorest.NewDecoder(encodedAs, bytes.NewReader(b.Bytes()))
   343  					if err := decoder.Decode(&e.ServiceError); err != nil {
   344  						return fmt.Errorf("autorest/azure: error response cannot be parsed: %q error: %v", b, err)
   345  					}
   346  
   347  					// for example, should the API return the literal value `null` as the response
   348  					if e.ServiceError == nil {
   349  						e.ServiceError = &ServiceError{
   350  							Code:    "Unknown",
   351  							Message: "Unknown service error",
   352  							Details: []map[string]interface{}{
   353  								{
   354  									"HttpResponse.Body": b.String(),
   355  								},
   356  							},
   357  						}
   358  					}
   359  				}
   360  
   361  				if e.ServiceError != nil && e.ServiceError.Message == "" {
   362  					// if we're here it means the returned error wasn't OData v4 compliant.
   363  					// try to unmarshal the body in hopes of getting something.
   364  					rawBody := map[string]interface{}{}
   365  					decoder := autorest.NewDecoder(encodedAs, bytes.NewReader(b.Bytes()))
   366  					if err := decoder.Decode(&rawBody); err != nil {
   367  						return fmt.Errorf("autorest/azure: error response cannot be parsed: %q error: %v", b, err)
   368  					}
   369  
   370  					e.ServiceError = &ServiceError{
   371  						Code:    "Unknown",
   372  						Message: "Unknown service error",
   373  					}
   374  					if len(rawBody) > 0 {
   375  						e.ServiceError.Details = []map[string]interface{}{rawBody}
   376  					}
   377  				}
   378  				e.Response = resp
   379  				e.RequestID = ExtractRequestID(resp)
   380  				if e.StatusCode == nil {
   381  					e.StatusCode = resp.StatusCode
   382  				}
   383  				err = &e
   384  			}
   385  			return err
   386  		})
   387  	}
   388  }
   389  

View as plain text