// WithOdataErrorUnlessStatusCode 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. // // NOTE(axw) this function is based on go-autorest/autorest/azure.WithErrorUnlessStatusCode. // The only difference is that we extract "odata.error", instead of "error", // from the response body; and we do not extract the message, which is in a // different format for odata.error, and irrelevant to us. func WithOdataErrorUnlessStatusCode(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 oe odataRequestError 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, &oe) resp.Body = ioutil.NopCloser(&b) if decodeErr != nil { return fmt.Errorf("ad: error response cannot be parsed: %q error: %v", b.String(), decodeErr) } else if oe.ServiceError == nil { oe.ServiceError = &odataServiceError{Code: "Unknown"} } e := azure.RequestError{ ServiceError: &azure.ServiceError{Code: oe.ServiceError.Code}, RequestID: azure.ExtractRequestID(resp), } if e.StatusCode == nil { e.StatusCode = resp.StatusCode } err = &e } return err }) } }
// 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() b, decodeErr := autorest.CopyAndDecode(autorest.EncodedAsJSON, resp.Body, &e) resp.Body = ioutil.NopCloser(&b) // replace body with in-memory reader if decodeErr != nil || e.ServiceError == nil { return fmt.Errorf("autorest/azure: error response cannot be parsed: %q error: %v", b.String(), err) } e.RequestID = ExtractRequestID(resp) e.StatusCode = resp.StatusCode err = &e } return err }) } }
// DoPollForAsynchronous returns a SendDecorator that polls if the http.Response is for an Azure // long-running operation. It will delay between requests for the duration specified in the // RetryAfter header or, if the header is absent, the passed delay. Polling may be canceled by // closing the optional channel on the http.Request. func DoPollForAsynchronous(delay time.Duration) autorest.SendDecorator { return func(s autorest.Sender) autorest.Sender { return autorest.SenderFunc(func(r *http.Request) (resp *http.Response, err error) { resp, err = s.Do(r) if err != nil { return resp, err } pollingCodes := []int{http.StatusAccepted, http.StatusCreated, http.StatusOK} if !autorest.ResponseHasStatusCode(resp, pollingCodes...) { return resp, nil } ps := pollingState{} for err == nil { err = updatePollingState(resp, &ps) if err != nil { break } if ps.hasTerminated() { if !ps.hasSucceeded() { err = ps } break } r, err = newPollingRequest(resp, ps) if err != nil { return resp, err } delay = autorest.GetRetryAfter(resp, delay) resp, err = autorest.SendWithSender(s, r, autorest.AfterDelay(delay)) } return resp, err }) } }
// call will call the supplied function, with exponential backoff // as long as the request returns an http.StatusTooManyRequests // status. func (c backoffAPIRequestCaller) call(f func() (autorest.Response, error)) error { var resp *http.Response return retry.Call(retry.CallArgs{ Func: func() error { autorestResp, err := f() resp = autorestResp.Response return err }, IsFatalError: func(err error) bool { return resp == nil || !autorest.ResponseHasStatusCode(resp, http.StatusTooManyRequests) }, NotifyFunc: func(err error, attempt int) { logger.Debugf("attempt %d: %v", attempt, err) }, Attempts: -1, Delay: retryDelay, MaxDelay: maxRetryDelay, MaxDuration: maxRetryDuration, BackoffFunc: retry.DoubleDelay, Clock: c.clock, }) }
// IsAsynchronousResponse returns true if the passed response indicates that the request will // complete asynchronously. Such responses have either an http.StatusCreated or an // http.StatusAccepted status code and provide the Azure-AsyncOperation header. func IsAsynchronousResponse(resp *http.Response) bool { return autorest.ResponseHasStatusCode(resp, http.StatusCreated, http.StatusAccepted) && GetAsyncOperation(resp) != "" }