github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/azure/internal/ad/client.go (about)

     1  // This file is based on code from Azure/azure-sdk-for-go
     2  // and Azure/go-autorest, which are both
     3  // Copyright Microsoft Corporation. See the LICENSE
     4  // file in this directory for details.
     5  //
     6  // NOTE(axw) this file contains a client for a subset of the
     7  // Microsoft Graph API, which is not currently supported by
     8  // the Azure SDK. When it is, this will be deleted.
     9  
    10  package ad
    11  
    12  import (
    13  	"bytes"
    14  	"encoding/json"
    15  	"fmt"
    16  	"io/ioutil"
    17  	"net/http"
    18  
    19  	"github.com/Azure/go-autorest/autorest"
    20  	"github.com/Azure/go-autorest/autorest/azure"
    21  	"github.com/juju/juju/provider/azure/internal/useragent"
    22  )
    23  
    24  const (
    25  	// APIVersion is the version of the Active Directory API
    26  	APIVersion = "1.6"
    27  )
    28  
    29  var (
    30  	// utf8BOM is the UTF-8 BOM, which may come at the beginning
    31  	// of a UTF-8 stream. Since it has no effect on the stream,
    32  	// it can be stripped off and ignored.
    33  	utf8BOM = []byte("\ufeff")
    34  )
    35  
    36  type ManagementClient struct {
    37  	autorest.Client
    38  	BaseURI    string
    39  	APIVersion string
    40  }
    41  
    42  func NewManagementClient(baseURI string) ManagementClient {
    43  	client := autorest.NewClientWithUserAgent("arm-graphrbac/" + APIVersion)
    44  	useragent.UpdateClient(&client)
    45  	return ManagementClient{
    46  		Client:     client,
    47  		BaseURI:    baseURI,
    48  		APIVersion: APIVersion,
    49  	}
    50  }
    51  
    52  // WithOdataErrorUnlessStatusCode returns a RespondDecorator that emits an
    53  // azure.RequestError by reading the response body unless the response HTTP status code
    54  // is among the set passed.
    55  //
    56  // If there is a chance service may return responses other than the Azure error
    57  // format and the response cannot be parsed into an error, a decoding error will
    58  // be returned containing the response body. In any case, the Responder will
    59  // return an error if the status code is not satisfied.
    60  //
    61  // If this Responder returns an error, the response body will be replaced with
    62  // an in-memory reader, which needs no further closing.
    63  //
    64  // NOTE(axw) this function is based on go-autorest/autorest/azure.WithErrorUnlessStatusCode.
    65  // The only difference is that we extract "odata.error", instead of "error",
    66  // from the response body; and we do not extract the message, which is in a
    67  // different format for odata.error, and irrelevant to us.
    68  func WithOdataErrorUnlessStatusCode(codes ...int) autorest.RespondDecorator {
    69  	return func(r autorest.Responder) autorest.Responder {
    70  		return autorest.ResponderFunc(func(resp *http.Response) error {
    71  			err := r.Respond(resp)
    72  			if err == nil && !autorest.ResponseHasStatusCode(resp, codes...) {
    73  				var oe odataRequestError
    74  				defer resp.Body.Close()
    75  
    76  				// Copy and replace the Body in case it does not contain an error object.
    77  				// This will leave the Body available to the caller.
    78  				data, readErr := ioutil.ReadAll(resp.Body)
    79  				if readErr != nil {
    80  					return readErr
    81  				}
    82  				resp.Body = ioutil.NopCloser(bytes.NewReader(data))
    83  				decodeErr := json.Unmarshal(bytes.TrimPrefix(data, utf8BOM), &oe)
    84  
    85  				if decodeErr != nil {
    86  					return fmt.Errorf("ad: error response cannot be parsed: %q error: %v", data, decodeErr)
    87  				} else if oe.ServiceError == nil {
    88  					oe.ServiceError = &odataServiceError{Code: "Unknown"}
    89  				}
    90  
    91  				e := azure.RequestError{
    92  					ServiceError: &azure.ServiceError{
    93  						Code:    oe.ServiceError.Code,
    94  						Message: oe.ServiceError.Message.Value,
    95  					},
    96  					RequestID: azure.ExtractRequestID(resp),
    97  				}
    98  				if e.StatusCode == nil {
    99  					e.StatusCode = resp.StatusCode
   100  				}
   101  				err = &e
   102  			}
   103  			return err
   104  		})
   105  	}
   106  }
   107  
   108  // See https://msdn.microsoft.com/en-us/library/azure/ad/graph/howto/azure-ad-graph-api-error-codes-and-error-handling
   109  type odataRequestError struct {
   110  	ServiceError *odataServiceError `json:"odata.error"`
   111  }
   112  
   113  type odataServiceError struct {
   114  	Code    string                   `json:"code"`
   115  	Message odataServiceErrorMessage `json:"message"`
   116  }
   117  
   118  type odataServiceErrorMessage struct {
   119  	Lang  string `json:"lang"`
   120  	Value string `json:"value"`
   121  }