vendor: add github.com/Azure/azure-sdk-for-go and dependencies
This commit is contained in:
+302
@@ -0,0 +1,302 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/date"
|
||||
)
|
||||
|
||||
const (
|
||||
headerAsyncOperation = "Azure-AsyncOperation"
|
||||
)
|
||||
|
||||
const (
|
||||
operationInProgress string = "InProgress"
|
||||
operationCanceled string = "Canceled"
|
||||
operationFailed string = "Failed"
|
||||
operationSucceeded string = "Succeeded"
|
||||
)
|
||||
|
||||
// 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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getAsyncOperation(resp *http.Response) string {
|
||||
return resp.Header.Get(http.CanonicalHeaderKey(headerAsyncOperation))
|
||||
}
|
||||
|
||||
func hasSucceeded(state string) bool {
|
||||
return state == operationSucceeded
|
||||
}
|
||||
|
||||
func hasTerminated(state string) bool {
|
||||
switch state {
|
||||
case operationCanceled, operationFailed, operationSucceeded:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func hasFailed(state string) bool {
|
||||
return state == operationFailed
|
||||
}
|
||||
|
||||
type provisioningTracker interface {
|
||||
state() string
|
||||
hasSucceeded() bool
|
||||
hasTerminated() bool
|
||||
}
|
||||
|
||||
type operationResource struct {
|
||||
// Note:
|
||||
// The specification states services should return the "id" field. However some return it as
|
||||
// "operationId".
|
||||
ID string `json:"id"`
|
||||
OperationID string `json:"operationId"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
Properties map[string]interface{} `json:"properties"`
|
||||
OperationError ServiceError `json:"error"`
|
||||
StartTime date.Time `json:"startTime"`
|
||||
EndTime date.Time `json:"endTime"`
|
||||
PercentComplete float64 `json:"percentComplete"`
|
||||
}
|
||||
|
||||
func (or operationResource) state() string {
|
||||
return or.Status
|
||||
}
|
||||
|
||||
func (or operationResource) hasSucceeded() bool {
|
||||
return hasSucceeded(or.state())
|
||||
}
|
||||
|
||||
func (or operationResource) hasTerminated() bool {
|
||||
return hasTerminated(or.state())
|
||||
}
|
||||
|
||||
type provisioningProperties struct {
|
||||
ProvisioningState string `json:"provisioningState"`
|
||||
}
|
||||
|
||||
type provisioningStatus struct {
|
||||
Properties provisioningProperties `json:"properties,omitempty"`
|
||||
ProvisioningError ServiceError `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (ps provisioningStatus) state() string {
|
||||
return ps.Properties.ProvisioningState
|
||||
}
|
||||
|
||||
func (ps provisioningStatus) hasSucceeded() bool {
|
||||
return hasSucceeded(ps.state())
|
||||
}
|
||||
|
||||
func (ps provisioningStatus) hasTerminated() bool {
|
||||
return hasTerminated(ps.state())
|
||||
}
|
||||
|
||||
func (ps provisioningStatus) hasProvisioningError() bool {
|
||||
return ps.ProvisioningError != ServiceError{}
|
||||
}
|
||||
|
||||
type pollingResponseFormat string
|
||||
|
||||
const (
|
||||
usesOperationResponse pollingResponseFormat = "OperationResponse"
|
||||
usesProvisioningStatus pollingResponseFormat = "ProvisioningStatus"
|
||||
formatIsUnknown pollingResponseFormat = ""
|
||||
)
|
||||
|
||||
type pollingState struct {
|
||||
responseFormat pollingResponseFormat
|
||||
uri string
|
||||
state string
|
||||
code string
|
||||
message string
|
||||
}
|
||||
|
||||
func (ps pollingState) hasSucceeded() bool {
|
||||
return hasSucceeded(ps.state)
|
||||
}
|
||||
|
||||
func (ps pollingState) hasTerminated() bool {
|
||||
return hasTerminated(ps.state)
|
||||
}
|
||||
|
||||
func (ps pollingState) hasFailed() bool {
|
||||
return hasFailed(ps.state)
|
||||
}
|
||||
|
||||
func (ps pollingState) Error() string {
|
||||
return fmt.Sprintf("Long running operation terminated with status '%s': Code=%q Message=%q", ps.state, ps.code, ps.message)
|
||||
}
|
||||
|
||||
// updatePollingState maps the operation status -- retrieved from either a provisioningState
|
||||
// field, the status field of an OperationResource, or inferred from the HTTP status code --
|
||||
// into a well-known states. Since the process begins from the initial request, the state
|
||||
// always comes from either a the provisioningState returned or is inferred from the HTTP
|
||||
// status code. Subsequent requests will read an Azure OperationResource object if the
|
||||
// service initially returned the Azure-AsyncOperation header. The responseFormat field notes
|
||||
// the expected response format.
|
||||
func updatePollingState(resp *http.Response, ps *pollingState) error {
|
||||
// Determine the response shape
|
||||
// -- The first response will always be a provisioningStatus response; only the polling requests,
|
||||
// depending on the header returned, may be something otherwise.
|
||||
var pt provisioningTracker
|
||||
if ps.responseFormat == usesOperationResponse {
|
||||
pt = &operationResource{}
|
||||
} else {
|
||||
pt = &provisioningStatus{}
|
||||
}
|
||||
|
||||
// If this is the first request (that is, the polling response shape is unknown), determine how
|
||||
// to poll and what to expect
|
||||
if ps.responseFormat == formatIsUnknown {
|
||||
req := resp.Request
|
||||
if req == nil {
|
||||
return autorest.NewError("azure", "updatePollingState", "Azure Polling Error - Original HTTP request is missing")
|
||||
}
|
||||
|
||||
// Prefer the Azure-AsyncOperation header
|
||||
ps.uri = getAsyncOperation(resp)
|
||||
if ps.uri != "" {
|
||||
ps.responseFormat = usesOperationResponse
|
||||
} else {
|
||||
ps.responseFormat = usesProvisioningStatus
|
||||
}
|
||||
|
||||
// Else, use the Location header
|
||||
if ps.uri == "" {
|
||||
ps.uri = autorest.GetLocation(resp)
|
||||
}
|
||||
|
||||
// Lastly, requests against an existing resource, use the last request URI
|
||||
if ps.uri == "" {
|
||||
m := strings.ToUpper(req.Method)
|
||||
if m == http.MethodPatch || m == http.MethodPut || m == http.MethodGet {
|
||||
ps.uri = req.URL.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read and interpret the response (saving the Body in case no polling is necessary)
|
||||
b := &bytes.Buffer{}
|
||||
err := autorest.Respond(resp,
|
||||
autorest.ByCopying(b),
|
||||
autorest.ByUnmarshallingJSON(pt),
|
||||
autorest.ByClosing())
|
||||
resp.Body = ioutil.NopCloser(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Interpret the results
|
||||
// -- Terminal states apply regardless
|
||||
// -- Unknown states are per-service inprogress states
|
||||
// -- Otherwise, infer state from HTTP status code
|
||||
if pt.hasTerminated() {
|
||||
ps.state = pt.state()
|
||||
} else if pt.state() != "" {
|
||||
ps.state = operationInProgress
|
||||
} else {
|
||||
switch resp.StatusCode {
|
||||
case http.StatusAccepted:
|
||||
ps.state = operationInProgress
|
||||
|
||||
case http.StatusNoContent, http.StatusCreated, http.StatusOK:
|
||||
ps.state = operationSucceeded
|
||||
|
||||
default:
|
||||
ps.state = operationFailed
|
||||
}
|
||||
}
|
||||
|
||||
if ps.state == operationInProgress && ps.uri == "" {
|
||||
return autorest.NewError("azure", "updatePollingState", "Azure Polling Error - Unable to obtain polling URI for %s %s", resp.Request.Method, resp.Request.URL)
|
||||
}
|
||||
|
||||
// For failed operation, check for error code and message in
|
||||
// -- Operation resource
|
||||
// -- Response
|
||||
// -- Otherwise, Unknown
|
||||
if ps.hasFailed() {
|
||||
if ps.responseFormat == usesOperationResponse {
|
||||
or := pt.(*operationResource)
|
||||
ps.code = or.OperationError.Code
|
||||
ps.message = or.OperationError.Message
|
||||
} else {
|
||||
p := pt.(*provisioningStatus)
|
||||
if p.hasProvisioningError() {
|
||||
ps.code = p.ProvisioningError.Code
|
||||
ps.message = p.ProvisioningError.Message
|
||||
} else {
|
||||
ps.code = "Unknown"
|
||||
ps.message = "None"
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newPollingRequest(resp *http.Response, ps pollingState) (*http.Request, error) {
|
||||
req := resp.Request
|
||||
if req == nil {
|
||||
return nil, autorest.NewError("azure", "newPollingRequest", "Azure Polling Error - Original HTTP request is missing")
|
||||
}
|
||||
|
||||
reqPoll, err := autorest.Prepare(&http.Request{Cancel: req.Cancel},
|
||||
autorest.AsGet(),
|
||||
autorest.WithBaseURL(ps.uri))
|
||||
if err != nil {
|
||||
return nil, autorest.NewErrorWithError(err, "azure", "newPollingRequest", nil, "Failure creating poll request to %s", ps.uri)
|
||||
}
|
||||
|
||||
return reqPoll, nil
|
||||
}
|
||||
+1116
File diff suppressed because it is too large
Load Diff
+180
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
Package azure provides Azure-specific implementations used with AutoRest.
|
||||
|
||||
See the included examples for more detail.
|
||||
*/
|
||||
package azure
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"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.
|
||||
type ServiceError struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Details *[]interface{} `json:"details"`
|
||||
}
|
||||
|
||||
func (se ServiceError) Error() string {
|
||||
if se.Details != nil {
|
||||
d, err := json.Marshal(*(se.Details))
|
||||
if err != nil {
|
||||
return fmt.Sprintf("Code=%q Message=%q Details=%v", se.Code, se.Message, *se.Details)
|
||||
}
|
||||
return fmt.Sprintf("Code=%q Message=%q Details=%v", se.Code, se.Message, string(d))
|
||||
}
|
||||
return fmt.Sprintf("Code=%q Message=%q", se.Code, se.Message)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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
|
||||
})
|
||||
}
|
||||
}
|
||||
+431
@@ -0,0 +1,431 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/mocks"
|
||||
)
|
||||
|
||||
const (
|
||||
headerAuthorization = "Authorization"
|
||||
longDelay = 5 * time.Second
|
||||
retryDelay = 10 * time.Millisecond
|
||||
testLogPrefix = "azure:"
|
||||
)
|
||||
|
||||
// Use a Client Inspector to set the request identifier.
|
||||
func ExampleWithClientID() {
|
||||
uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6"
|
||||
req, _ := autorest.Prepare(&http.Request{},
|
||||
autorest.AsGet(),
|
||||
autorest.WithBaseURL("https://microsoft.com/a/b/c/"))
|
||||
|
||||
c := autorest.Client{Sender: mocks.NewSender()}
|
||||
c.RequestInspector = WithReturningClientID(uuid)
|
||||
|
||||
autorest.SendWithSender(c, req)
|
||||
fmt.Printf("Inspector added the %s header with the value %s\n",
|
||||
HeaderClientID, req.Header.Get(HeaderClientID))
|
||||
fmt.Printf("Inspector added the %s header with the value %s\n",
|
||||
HeaderReturnClientID, req.Header.Get(HeaderReturnClientID))
|
||||
// Output:
|
||||
// Inspector added the x-ms-client-request-id header with the value 71FDB9F4-5E49-4C12-B266-DE7B4FD999A6
|
||||
// Inspector added the x-ms-return-client-request-id header with the value true
|
||||
}
|
||||
|
||||
func TestWithReturningClientIDReturnsError(t *testing.T) {
|
||||
var errIn error
|
||||
uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6"
|
||||
_, errOut := autorest.Prepare(&http.Request{},
|
||||
withErrorPrepareDecorator(&errIn),
|
||||
WithReturningClientID(uuid))
|
||||
|
||||
if errOut == nil || errIn != errOut {
|
||||
t.Fatalf("azure: WithReturningClientID failed to exit early when receiving an error -- expected (%v), received (%v)",
|
||||
errIn, errOut)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithClientID(t *testing.T) {
|
||||
uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6"
|
||||
req, _ := autorest.Prepare(&http.Request{},
|
||||
WithClientID(uuid))
|
||||
|
||||
if req.Header.Get(HeaderClientID) != uuid {
|
||||
t.Fatalf("azure: WithClientID failed to set %s -- expected %s, received %s",
|
||||
HeaderClientID, uuid, req.Header.Get(HeaderClientID))
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithReturnClientID(t *testing.T) {
|
||||
b := false
|
||||
req, _ := autorest.Prepare(&http.Request{},
|
||||
WithReturnClientID(b))
|
||||
|
||||
if req.Header.Get(HeaderReturnClientID) != strconv.FormatBool(b) {
|
||||
t.Fatalf("azure: WithReturnClientID failed to set %s -- expected %s, received %s",
|
||||
HeaderClientID, strconv.FormatBool(b), req.Header.Get(HeaderClientID))
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractClientID(t *testing.T) {
|
||||
uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6"
|
||||
resp := mocks.NewResponse()
|
||||
mocks.SetResponseHeader(resp, HeaderClientID, uuid)
|
||||
|
||||
if ExtractClientID(resp) != uuid {
|
||||
t.Fatalf("azure: ExtractClientID failed to extract the %s -- expected %s, received %s",
|
||||
HeaderClientID, uuid, ExtractClientID(resp))
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractRequestID(t *testing.T) {
|
||||
uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6"
|
||||
resp := mocks.NewResponse()
|
||||
mocks.SetResponseHeader(resp, HeaderRequestID, uuid)
|
||||
|
||||
if ExtractRequestID(resp) != uuid {
|
||||
t.Fatalf("azure: ExtractRequestID failed to extract the %s -- expected %s, received %s",
|
||||
HeaderRequestID, uuid, ExtractRequestID(resp))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAzureError_ReturnsTrueForAzureError(t *testing.T) {
|
||||
if !IsAzureError(&RequestError{}) {
|
||||
t.Fatalf("azure: IsAzureError failed to return true for an Azure Service error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAzureError_ReturnsFalseForNonAzureError(t *testing.T) {
|
||||
if IsAzureError(fmt.Errorf("An Error")) {
|
||||
t.Fatalf("azure: IsAzureError return true for an non-Azure Service error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewErrorWithError_UsesReponseStatusCode(t *testing.T) {
|
||||
e := NewErrorWithError(fmt.Errorf("Error"), "packageType", "method", mocks.NewResponseWithStatus("Forbidden", http.StatusForbidden), "message")
|
||||
if e.StatusCode != http.StatusForbidden {
|
||||
t.Fatalf("azure: NewErrorWithError failed to use the Status Code of the passed Response -- expected %v, received %v", http.StatusForbidden, e.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewErrorWithError_ReturnsUnwrappedError(t *testing.T) {
|
||||
e1 := RequestError{}
|
||||
e1.ServiceError = &ServiceError{Code: "42", Message: "A Message"}
|
||||
e1.StatusCode = 200
|
||||
e1.RequestID = "A RequestID"
|
||||
e2 := NewErrorWithError(&e1, "packageType", "method", nil, "message")
|
||||
|
||||
if !reflect.DeepEqual(e1, e2) {
|
||||
t.Fatalf("azure: NewErrorWithError wrapped an RequestError -- expected %T, received %T", e1, e2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewErrorWithError_WrapsAnError(t *testing.T) {
|
||||
e1 := fmt.Errorf("Inner Error")
|
||||
var e2 interface{} = NewErrorWithError(e1, "packageType", "method", nil, "message")
|
||||
|
||||
if _, ok := e2.(RequestError); !ok {
|
||||
t.Fatalf("azure: NewErrorWithError failed to wrap a standard error -- received %T", e2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithErrorUnlessStatusCode_NotAnAzureError(t *testing.T) {
|
||||
body := `<html>
|
||||
<head>
|
||||
<title>IIS Error page</title>
|
||||
</head>
|
||||
<body>Some non-JSON error page</body>
|
||||
</html>`
|
||||
r := mocks.NewResponseWithContent(body)
|
||||
r.Request = mocks.NewRequest()
|
||||
r.StatusCode = http.StatusBadRequest
|
||||
r.Status = http.StatusText(r.StatusCode)
|
||||
|
||||
err := autorest.Respond(r,
|
||||
WithErrorUnlessStatusCode(http.StatusOK),
|
||||
autorest.ByClosing())
|
||||
ok, _ := err.(*RequestError)
|
||||
if ok != nil {
|
||||
t.Fatalf("azure: azure.RequestError returned from malformed response: %v", err)
|
||||
}
|
||||
|
||||
// the error body should still be there
|
||||
defer r.Body.Close()
|
||||
b, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(b) != body {
|
||||
t.Fatalf("response body is wrong. got=%q exptected=%q", string(b), body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithErrorUnlessStatusCode_FoundAzureErrorWithoutDetails(t *testing.T) {
|
||||
j := `{
|
||||
"error": {
|
||||
"code": "InternalError",
|
||||
"message": "Azure is having trouble right now."
|
||||
}
|
||||
}`
|
||||
uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6"
|
||||
r := mocks.NewResponseWithContent(j)
|
||||
mocks.SetResponseHeader(r, HeaderRequestID, uuid)
|
||||
r.Request = mocks.NewRequest()
|
||||
r.StatusCode = http.StatusInternalServerError
|
||||
r.Status = http.StatusText(r.StatusCode)
|
||||
|
||||
err := autorest.Respond(r,
|
||||
WithErrorUnlessStatusCode(http.StatusOK),
|
||||
autorest.ByClosing())
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("azure: returned nil error for proper error response")
|
||||
}
|
||||
azErr, ok := err.(*RequestError)
|
||||
if !ok {
|
||||
t.Fatalf("azure: returned error is not azure.RequestError: %T", err)
|
||||
}
|
||||
|
||||
expected := "autorest/azure: Service returned an error. Status=500 Code=\"InternalError\" Message=\"Azure is having trouble right now.\""
|
||||
if !reflect.DeepEqual(expected, azErr.Error()) {
|
||||
t.Fatalf("azure: service error is not unmarshaled properly.\nexpected=%v\ngot=%v", expected, azErr.Error())
|
||||
}
|
||||
|
||||
if expected := http.StatusInternalServerError; azErr.StatusCode != expected {
|
||||
t.Fatalf("azure: got wrong StatusCode=%d Expected=%d", azErr.StatusCode, expected)
|
||||
}
|
||||
if expected := uuid; azErr.RequestID != expected {
|
||||
t.Fatalf("azure: wrong request ID in error. expected=%q; got=%q", expected, azErr.RequestID)
|
||||
}
|
||||
|
||||
_ = azErr.Error()
|
||||
|
||||
// the error body should still be there
|
||||
defer r.Body.Close()
|
||||
b, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(b) != j {
|
||||
t.Fatalf("response body is wrong. got=%q expected=%q", string(b), j)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestWithErrorUnlessStatusCode_FoundAzureErrorWithDetails(t *testing.T) {
|
||||
j := `{
|
||||
"error": {
|
||||
"code": "InternalError",
|
||||
"message": "Azure is having trouble right now.",
|
||||
"details": [{"code": "conflict1", "message":"error message1"},
|
||||
{"code": "conflict2", "message":"error message2"}]
|
||||
}
|
||||
}`
|
||||
uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6"
|
||||
r := mocks.NewResponseWithContent(j)
|
||||
mocks.SetResponseHeader(r, HeaderRequestID, uuid)
|
||||
r.Request = mocks.NewRequest()
|
||||
r.StatusCode = http.StatusInternalServerError
|
||||
r.Status = http.StatusText(r.StatusCode)
|
||||
|
||||
err := autorest.Respond(r,
|
||||
WithErrorUnlessStatusCode(http.StatusOK),
|
||||
autorest.ByClosing())
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("azure: returned nil error for proper error response")
|
||||
}
|
||||
azErr, ok := err.(*RequestError)
|
||||
if !ok {
|
||||
t.Fatalf("azure: returned error is not azure.RequestError: %T", err)
|
||||
}
|
||||
|
||||
if expected := "InternalError"; azErr.ServiceError.Code != expected {
|
||||
t.Fatalf("azure: wrong error code. expected=%q; got=%q", expected, azErr.ServiceError.Code)
|
||||
}
|
||||
if azErr.ServiceError.Message == "" {
|
||||
t.Fatalf("azure: error message is not unmarshaled properly")
|
||||
}
|
||||
b, _ := json.Marshal(*azErr.ServiceError.Details)
|
||||
if string(b) != `[{"code":"conflict1","message":"error message1"},{"code":"conflict2","message":"error message2"}]` {
|
||||
t.Fatalf("azure: error details is not unmarshaled properly")
|
||||
}
|
||||
|
||||
if expected := http.StatusInternalServerError; azErr.StatusCode != expected {
|
||||
t.Fatalf("azure: got wrong StatusCode=%v Expected=%d", azErr.StatusCode, expected)
|
||||
}
|
||||
if expected := uuid; azErr.RequestID != expected {
|
||||
t.Fatalf("azure: wrong request ID in error. expected=%q; got=%q", expected, azErr.RequestID)
|
||||
}
|
||||
|
||||
_ = azErr.Error()
|
||||
|
||||
// the error body should still be there
|
||||
defer r.Body.Close()
|
||||
b, err = ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(b) != j {
|
||||
t.Fatalf("response body is wrong. got=%q expected=%q", string(b), j)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestWithErrorUnlessStatusCode_NoAzureError(t *testing.T) {
|
||||
j := `{
|
||||
"Status":"NotFound"
|
||||
}`
|
||||
uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6"
|
||||
r := mocks.NewResponseWithContent(j)
|
||||
mocks.SetResponseHeader(r, HeaderRequestID, uuid)
|
||||
r.Request = mocks.NewRequest()
|
||||
r.StatusCode = http.StatusInternalServerError
|
||||
r.Status = http.StatusText(r.StatusCode)
|
||||
|
||||
err := autorest.Respond(r,
|
||||
WithErrorUnlessStatusCode(http.StatusOK),
|
||||
autorest.ByClosing())
|
||||
if err == nil {
|
||||
t.Fatalf("azure: returned nil error for proper error response")
|
||||
}
|
||||
azErr, ok := err.(*RequestError)
|
||||
if !ok {
|
||||
t.Fatalf("azure: returned error is not azure.RequestError: %T", err)
|
||||
}
|
||||
|
||||
expected := &ServiceError{
|
||||
Code: "Unknown",
|
||||
Message: "Unknown service error",
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(expected, azErr.ServiceError) {
|
||||
t.Fatalf("azure: service error is not unmarshaled properly. expected=%q\ngot=%q", expected, azErr.ServiceError)
|
||||
}
|
||||
|
||||
if expected := http.StatusInternalServerError; azErr.StatusCode != expected {
|
||||
t.Fatalf("azure: got wrong StatusCode=%v Expected=%d", azErr.StatusCode, expected)
|
||||
}
|
||||
if expected := uuid; azErr.RequestID != expected {
|
||||
t.Fatalf("azure: wrong request ID in error. expected=%q; got=%q", expected, azErr.RequestID)
|
||||
}
|
||||
|
||||
_ = azErr.Error()
|
||||
|
||||
// the error body should still be there
|
||||
defer r.Body.Close()
|
||||
b, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(b) != j {
|
||||
t.Fatalf("response body is wrong. got=%q expected=%q", string(b), j)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestRequestErrorString_WithError(t *testing.T) {
|
||||
j := `{
|
||||
"error": {
|
||||
"code": "InternalError",
|
||||
"message": "Conflict",
|
||||
"details": [{"code": "conflict1", "message":"error message1"}]
|
||||
}
|
||||
}`
|
||||
uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6"
|
||||
r := mocks.NewResponseWithContent(j)
|
||||
mocks.SetResponseHeader(r, HeaderRequestID, uuid)
|
||||
r.Request = mocks.NewRequest()
|
||||
r.StatusCode = http.StatusInternalServerError
|
||||
r.Status = http.StatusText(r.StatusCode)
|
||||
|
||||
err := autorest.Respond(r,
|
||||
WithErrorUnlessStatusCode(http.StatusOK),
|
||||
autorest.ByClosing())
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("azure: returned nil error for proper error response")
|
||||
}
|
||||
azErr, _ := err.(*RequestError)
|
||||
expected := "autorest/azure: Service returned an error. Status=500 Code=\"InternalError\" Message=\"Conflict\" Details=[{\"code\":\"conflict1\",\"message\":\"error message1\"}]"
|
||||
if expected != azErr.Error() {
|
||||
t.Fatalf("azure: send wrong RequestError.\nexpected=%v\ngot=%v", expected, azErr.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func withErrorPrepareDecorator(e *error) autorest.PrepareDecorator {
|
||||
return func(p autorest.Preparer) autorest.Preparer {
|
||||
return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) {
|
||||
*e = fmt.Errorf("azure: Faux Prepare Error")
|
||||
return r, *e
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func withAsyncResponseDecorator(n int) autorest.SendDecorator {
|
||||
i := 0
|
||||
return func(s autorest.Sender) autorest.Sender {
|
||||
return autorest.SenderFunc(func(r *http.Request) (*http.Response, error) {
|
||||
resp, err := s.Do(r)
|
||||
if err == nil {
|
||||
if i < n {
|
||||
resp.StatusCode = http.StatusCreated
|
||||
resp.Header = http.Header{}
|
||||
resp.Header.Add(http.CanonicalHeaderKey(headerAsyncOperation), mocks.TestURL)
|
||||
i++
|
||||
} else {
|
||||
resp.StatusCode = http.StatusOK
|
||||
resp.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
|
||||
}
|
||||
}
|
||||
return resp, err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type mockAuthorizer struct{}
|
||||
|
||||
func (ma mockAuthorizer) WithAuthorization() autorest.PrepareDecorator {
|
||||
return autorest.WithHeader(headerAuthorization, mocks.TestAuthorizationHeader)
|
||||
}
|
||||
|
||||
type mockFailingAuthorizer struct{}
|
||||
|
||||
func (mfa mockFailingAuthorizer) WithAuthorization() autorest.PrepareDecorator {
|
||||
return func(p autorest.Preparer) autorest.Preparer {
|
||||
return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) {
|
||||
return r, fmt.Errorf("ERROR: mockFailingAuthorizer returned expected error")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type mockInspector struct {
|
||||
wasInvoked bool
|
||||
}
|
||||
|
||||
func (mi *mockInspector) WithInspection() autorest.PrepareDecorator {
|
||||
return func(p autorest.Preparer) autorest.Preparer {
|
||||
return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) {
|
||||
mi.wasInvoked = true
|
||||
return p.Prepare(r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (mi *mockInspector) ByInspecting() autorest.RespondDecorator {
|
||||
return func(r autorest.Responder) autorest.Responder {
|
||||
return autorest.ResponderFunc(func(resp *http.Response) error {
|
||||
mi.wasInvoked = true
|
||||
return r.Respond(resp)
|
||||
})
|
||||
}
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var environments = map[string]Environment{
|
||||
"AZURECHINACLOUD": ChinaCloud,
|
||||
"AZUREGERMANCLOUD": GermanCloud,
|
||||
"AZUREPUBLICCLOUD": PublicCloud,
|
||||
"AZUREUSGOVERNMENTCLOUD": USGovernmentCloud,
|
||||
}
|
||||
|
||||
// Environment represents a set of endpoints for each of Azure's Clouds.
|
||||
type Environment struct {
|
||||
Name string `json:"name"`
|
||||
ManagementPortalURL string `json:"managementPortalURL"`
|
||||
PublishSettingsURL string `json:"publishSettingsURL"`
|
||||
ServiceManagementEndpoint string `json:"serviceManagementEndpoint"`
|
||||
ResourceManagerEndpoint string `json:"resourceManagerEndpoint"`
|
||||
ActiveDirectoryEndpoint string `json:"activeDirectoryEndpoint"`
|
||||
GalleryEndpoint string `json:"galleryEndpoint"`
|
||||
KeyVaultEndpoint string `json:"keyVaultEndpoint"`
|
||||
GraphEndpoint string `json:"graphEndpoint"`
|
||||
StorageEndpointSuffix string `json:"storageEndpointSuffix"`
|
||||
SQLDatabaseDNSSuffix string `json:"sqlDatabaseDNSSuffix"`
|
||||
TrafficManagerDNSSuffix string `json:"trafficManagerDNSSuffix"`
|
||||
KeyVaultDNSSuffix string `json:"keyVaultDNSSuffix"`
|
||||
ServiceBusEndpointSuffix string `json:"serviceBusEndpointSuffix"`
|
||||
ServiceManagementVMDNSSuffix string `json:"serviceManagementVMDNSSuffix"`
|
||||
ResourceManagerVMDNSSuffix string `json:"resourceManagerVMDNSSuffix"`
|
||||
ContainerRegistryDNSSuffix string `json:"containerRegistryDNSSuffix"`
|
||||
}
|
||||
|
||||
var (
|
||||
// PublicCloud is the default public Azure cloud environment
|
||||
PublicCloud = Environment{
|
||||
Name: "AzurePublicCloud",
|
||||
ManagementPortalURL: "https://manage.windowsazure.com/",
|
||||
PublishSettingsURL: "https://manage.windowsazure.com/publishsettings/index",
|
||||
ServiceManagementEndpoint: "https://management.core.windows.net/",
|
||||
ResourceManagerEndpoint: "https://management.azure.com/",
|
||||
ActiveDirectoryEndpoint: "https://login.microsoftonline.com/",
|
||||
GalleryEndpoint: "https://gallery.azure.com/",
|
||||
KeyVaultEndpoint: "https://vault.azure.net/",
|
||||
GraphEndpoint: "https://graph.windows.net/",
|
||||
StorageEndpointSuffix: "core.windows.net",
|
||||
SQLDatabaseDNSSuffix: "database.windows.net",
|
||||
TrafficManagerDNSSuffix: "trafficmanager.net",
|
||||
KeyVaultDNSSuffix: "vault.azure.net",
|
||||
ServiceBusEndpointSuffix: "servicebus.azure.com",
|
||||
ServiceManagementVMDNSSuffix: "cloudapp.net",
|
||||
ResourceManagerVMDNSSuffix: "cloudapp.azure.com",
|
||||
ContainerRegistryDNSSuffix: "azurecr.io",
|
||||
}
|
||||
|
||||
// USGovernmentCloud is the cloud environment for the US Government
|
||||
USGovernmentCloud = Environment{
|
||||
Name: "AzureUSGovernmentCloud",
|
||||
ManagementPortalURL: "https://manage.windowsazure.us/",
|
||||
PublishSettingsURL: "https://manage.windowsazure.us/publishsettings/index",
|
||||
ServiceManagementEndpoint: "https://management.core.usgovcloudapi.net/",
|
||||
ResourceManagerEndpoint: "https://management.usgovcloudapi.net/",
|
||||
ActiveDirectoryEndpoint: "https://login.microsoftonline.com/",
|
||||
GalleryEndpoint: "https://gallery.usgovcloudapi.net/",
|
||||
KeyVaultEndpoint: "https://vault.usgovcloudapi.net/",
|
||||
GraphEndpoint: "https://graph.usgovcloudapi.net/",
|
||||
StorageEndpointSuffix: "core.usgovcloudapi.net",
|
||||
SQLDatabaseDNSSuffix: "database.usgovcloudapi.net",
|
||||
TrafficManagerDNSSuffix: "usgovtrafficmanager.net",
|
||||
KeyVaultDNSSuffix: "vault.usgovcloudapi.net",
|
||||
ServiceBusEndpointSuffix: "servicebus.usgovcloudapi.net",
|
||||
ServiceManagementVMDNSSuffix: "usgovcloudapp.net",
|
||||
ResourceManagerVMDNSSuffix: "cloudapp.windowsazure.us",
|
||||
ContainerRegistryDNSSuffix: "azurecr.io",
|
||||
}
|
||||
|
||||
// ChinaCloud is the cloud environment operated in China
|
||||
ChinaCloud = Environment{
|
||||
Name: "AzureChinaCloud",
|
||||
ManagementPortalURL: "https://manage.chinacloudapi.com/",
|
||||
PublishSettingsURL: "https://manage.chinacloudapi.com/publishsettings/index",
|
||||
ServiceManagementEndpoint: "https://management.core.chinacloudapi.cn/",
|
||||
ResourceManagerEndpoint: "https://management.chinacloudapi.cn/",
|
||||
ActiveDirectoryEndpoint: "https://login.chinacloudapi.cn/",
|
||||
GalleryEndpoint: "https://gallery.chinacloudapi.cn/",
|
||||
KeyVaultEndpoint: "https://vault.azure.cn/",
|
||||
GraphEndpoint: "https://graph.chinacloudapi.cn/",
|
||||
StorageEndpointSuffix: "core.chinacloudapi.cn",
|
||||
SQLDatabaseDNSSuffix: "database.chinacloudapi.cn",
|
||||
TrafficManagerDNSSuffix: "trafficmanager.cn",
|
||||
KeyVaultDNSSuffix: "vault.azure.cn",
|
||||
ServiceBusEndpointSuffix: "servicebus.chinacloudapi.net",
|
||||
ServiceManagementVMDNSSuffix: "chinacloudapp.cn",
|
||||
ResourceManagerVMDNSSuffix: "cloudapp.azure.cn",
|
||||
ContainerRegistryDNSSuffix: "azurecr.io",
|
||||
}
|
||||
|
||||
// GermanCloud is the cloud environment operated in Germany
|
||||
GermanCloud = Environment{
|
||||
Name: "AzureGermanCloud",
|
||||
ManagementPortalURL: "http://portal.microsoftazure.de/",
|
||||
PublishSettingsURL: "https://manage.microsoftazure.de/publishsettings/index",
|
||||
ServiceManagementEndpoint: "https://management.core.cloudapi.de/",
|
||||
ResourceManagerEndpoint: "https://management.microsoftazure.de/",
|
||||
ActiveDirectoryEndpoint: "https://login.microsoftonline.de/",
|
||||
GalleryEndpoint: "https://gallery.cloudapi.de/",
|
||||
KeyVaultEndpoint: "https://vault.microsoftazure.de/",
|
||||
GraphEndpoint: "https://graph.cloudapi.de/",
|
||||
StorageEndpointSuffix: "core.cloudapi.de",
|
||||
SQLDatabaseDNSSuffix: "database.cloudapi.de",
|
||||
TrafficManagerDNSSuffix: "azuretrafficmanager.de",
|
||||
KeyVaultDNSSuffix: "vault.microsoftazure.de",
|
||||
ServiceBusEndpointSuffix: "servicebus.cloudapi.de",
|
||||
ServiceManagementVMDNSSuffix: "azurecloudapp.de",
|
||||
ResourceManagerVMDNSSuffix: "cloudapp.microsoftazure.de",
|
||||
ContainerRegistryDNSSuffix: "azurecr.io",
|
||||
}
|
||||
)
|
||||
|
||||
// EnvironmentFromName returns an Environment based on the common name specified
|
||||
func EnvironmentFromName(name string) (Environment, error) {
|
||||
name = strings.ToUpper(name)
|
||||
env, ok := environments[name]
|
||||
if !ok {
|
||||
return env, fmt.Errorf("autorest/azure: There is no cloud environment matching the name %q", name)
|
||||
}
|
||||
return env, nil
|
||||
}
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
// test
|
||||
package azure
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEnvironmentFromName(t *testing.T) {
|
||||
name := "azurechinacloud"
|
||||
if env, _ := EnvironmentFromName(name); env != ChinaCloud {
|
||||
t.Errorf("Expected to get ChinaCloud for %q", name)
|
||||
}
|
||||
|
||||
name = "AzureChinaCloud"
|
||||
if env, _ := EnvironmentFromName(name); env != ChinaCloud {
|
||||
t.Errorf("Expected to get ChinaCloud for %q", name)
|
||||
}
|
||||
|
||||
name = "azuregermancloud"
|
||||
if env, _ := EnvironmentFromName(name); env != GermanCloud {
|
||||
t.Errorf("Expected to get GermanCloud for %q", name)
|
||||
}
|
||||
|
||||
name = "AzureGermanCloud"
|
||||
if env, _ := EnvironmentFromName(name); env != GermanCloud {
|
||||
t.Errorf("Expected to get GermanCloud for %q", name)
|
||||
}
|
||||
|
||||
name = "azurepubliccloud"
|
||||
if env, _ := EnvironmentFromName(name); env != PublicCloud {
|
||||
t.Errorf("Expected to get PublicCloud for %q", name)
|
||||
}
|
||||
|
||||
name = "AzurePublicCloud"
|
||||
if env, _ := EnvironmentFromName(name); env != PublicCloud {
|
||||
t.Errorf("Expected to get PublicCloud for %q", name)
|
||||
}
|
||||
|
||||
name = "azureusgovernmentcloud"
|
||||
if env, _ := EnvironmentFromName(name); env != USGovernmentCloud {
|
||||
t.Errorf("Expected to get USGovernmentCloud for %q", name)
|
||||
}
|
||||
|
||||
name = "AzureUSGovernmentCloud"
|
||||
if env, _ := EnvironmentFromName(name); env != USGovernmentCloud {
|
||||
t.Errorf("Expected to get USGovernmentCloud for %q", name)
|
||||
}
|
||||
|
||||
name = "thisisnotarealcloudenv"
|
||||
if _, err := EnvironmentFromName(name); err == nil {
|
||||
t.Errorf("Expected to get an error for %q", name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeserializeEnvironment(t *testing.T) {
|
||||
env := `{
|
||||
"name": "--name--",
|
||||
"ActiveDirectoryEndpoint": "--active-directory-endpoint--",
|
||||
"galleryEndpoint": "--gallery-endpoint--",
|
||||
"graphEndpoint": "--graph-endpoint--",
|
||||
"keyVaultDNSSuffix": "--key-vault-dns-suffix--",
|
||||
"keyVaultEndpoint": "--key-vault-endpoint--",
|
||||
"managementPortalURL": "--management-portal-url--",
|
||||
"publishSettingsURL": "--publish-settings-url--",
|
||||
"resourceManagerEndpoint": "--resource-manager-endpoint--",
|
||||
"serviceBusEndpointSuffix": "--service-bus-endpoint-suffix--",
|
||||
"serviceManagementEndpoint": "--service-management-endpoint--",
|
||||
"sqlDatabaseDNSSuffix": "--sql-database-dns-suffix--",
|
||||
"storageEndpointSuffix": "--storage-endpoint-suffix--",
|
||||
"trafficManagerDNSSuffix": "--traffic-manager-dns-suffix--",
|
||||
"serviceManagementVMDNSSuffix": "--asm-vm-dns-suffix--",
|
||||
"resourceManagerVMDNSSuffix": "--arm-vm-dns-suffix--",
|
||||
"containerRegistryDNSSuffix": "--container-registry-dns-suffix--"
|
||||
}`
|
||||
|
||||
testSubject := Environment{}
|
||||
err := json.Unmarshal([]byte(env), &testSubject)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to unmarshal: %s", err)
|
||||
}
|
||||
|
||||
if "--name--" != testSubject.Name {
|
||||
t.Errorf("Expected Name to be \"--name--\", but got %q", testSubject.Name)
|
||||
}
|
||||
if "--management-portal-url--" != testSubject.ManagementPortalURL {
|
||||
t.Errorf("Expected ManagementPortalURL to be \"--management-portal-url--\", but got %q", testSubject.ManagementPortalURL)
|
||||
}
|
||||
if "--publish-settings-url--" != testSubject.PublishSettingsURL {
|
||||
t.Errorf("Expected PublishSettingsURL to be \"--publish-settings-url--\", but got %q", testSubject.PublishSettingsURL)
|
||||
}
|
||||
if "--service-management-endpoint--" != testSubject.ServiceManagementEndpoint {
|
||||
t.Errorf("Expected ServiceManagementEndpoint to be \"--service-management-endpoint--\", but got %q", testSubject.ServiceManagementEndpoint)
|
||||
}
|
||||
if "--resource-manager-endpoint--" != testSubject.ResourceManagerEndpoint {
|
||||
t.Errorf("Expected ResourceManagerEndpoint to be \"--resource-manager-endpoint--\", but got %q", testSubject.ResourceManagerEndpoint)
|
||||
}
|
||||
if "--active-directory-endpoint--" != testSubject.ActiveDirectoryEndpoint {
|
||||
t.Errorf("Expected ActiveDirectoryEndpoint to be \"--active-directory-endpoint--\", but got %q", testSubject.ActiveDirectoryEndpoint)
|
||||
}
|
||||
if "--gallery-endpoint--" != testSubject.GalleryEndpoint {
|
||||
t.Errorf("Expected GalleryEndpoint to be \"--gallery-endpoint--\", but got %q", testSubject.GalleryEndpoint)
|
||||
}
|
||||
if "--key-vault-endpoint--" != testSubject.KeyVaultEndpoint {
|
||||
t.Errorf("Expected KeyVaultEndpoint to be \"--key-vault-endpoint--\", but got %q", testSubject.KeyVaultEndpoint)
|
||||
}
|
||||
if "--graph-endpoint--" != testSubject.GraphEndpoint {
|
||||
t.Errorf("Expected GraphEndpoint to be \"--graph-endpoint--\", but got %q", testSubject.GraphEndpoint)
|
||||
}
|
||||
if "--storage-endpoint-suffix--" != testSubject.StorageEndpointSuffix {
|
||||
t.Errorf("Expected StorageEndpointSuffix to be \"--storage-endpoint-suffix--\", but got %q", testSubject.StorageEndpointSuffix)
|
||||
}
|
||||
if "--sql-database-dns-suffix--" != testSubject.SQLDatabaseDNSSuffix {
|
||||
t.Errorf("Expected sql-database-dns-suffix to be \"--sql-database-dns-suffix--\", but got %q", testSubject.SQLDatabaseDNSSuffix)
|
||||
}
|
||||
if "--key-vault-dns-suffix--" != testSubject.KeyVaultDNSSuffix {
|
||||
t.Errorf("Expected StorageEndpointSuffix to be \"--key-vault-dns-suffix--\", but got %q", testSubject.KeyVaultDNSSuffix)
|
||||
}
|
||||
if "--service-bus-endpoint-suffix--" != testSubject.ServiceBusEndpointSuffix {
|
||||
t.Errorf("Expected StorageEndpointSuffix to be \"--service-bus-endpoint-suffix--\", but got %q", testSubject.ServiceBusEndpointSuffix)
|
||||
}
|
||||
if "--asm-vm-dns-suffix--" != testSubject.ServiceManagementVMDNSSuffix {
|
||||
t.Errorf("Expected ServiceManagementVMDNSSuffix to be \"--asm-vm-dns-suffix--\", but got %q", testSubject.ServiceManagementVMDNSSuffix)
|
||||
}
|
||||
if "--arm-vm-dns-suffix--" != testSubject.ResourceManagerVMDNSSuffix {
|
||||
t.Errorf("Expected ResourceManagerVMDNSSuffix to be \"--arm-vm-dns-suffix--\", but got %q", testSubject.ResourceManagerVMDNSSuffix)
|
||||
}
|
||||
if "--container-registry-dns-suffix--" != testSubject.ContainerRegistryDNSSuffix {
|
||||
t.Errorf("Expected ContainerRegistryDNSSuffix to be \"--container-registry-dns-suffix--\", but got %q", testSubject.ContainerRegistryDNSSuffix)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundTripSerialization(t *testing.T) {
|
||||
env := Environment{
|
||||
Name: "--unit-test--",
|
||||
ManagementPortalURL: "--management-portal-url",
|
||||
PublishSettingsURL: "--publish-settings-url--",
|
||||
ServiceManagementEndpoint: "--service-management-endpoint--",
|
||||
ResourceManagerEndpoint: "--resource-management-endpoint--",
|
||||
ActiveDirectoryEndpoint: "--active-directory-endpoint--",
|
||||
GalleryEndpoint: "--gallery-endpoint--",
|
||||
KeyVaultEndpoint: "--key-vault--endpoint--",
|
||||
GraphEndpoint: "--graph-endpoint--",
|
||||
StorageEndpointSuffix: "--storage-endpoint-suffix--",
|
||||
SQLDatabaseDNSSuffix: "--sql-database-dns-suffix--",
|
||||
TrafficManagerDNSSuffix: "--traffic-manager-dns-suffix--",
|
||||
KeyVaultDNSSuffix: "--key-vault-dns-suffix--",
|
||||
ServiceBusEndpointSuffix: "--service-bus-endpoint-suffix--",
|
||||
ServiceManagementVMDNSSuffix: "--asm-vm-dns-suffix--",
|
||||
ResourceManagerVMDNSSuffix: "--arm-vm-dns-suffix--",
|
||||
ContainerRegistryDNSSuffix: "--container-registry-dns-suffix--",
|
||||
}
|
||||
|
||||
bytes, err := json.Marshal(env)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal: %s", err)
|
||||
}
|
||||
|
||||
testSubject := Environment{}
|
||||
err = json.Unmarshal(bytes, &testSubject)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to unmarshal: %s", err)
|
||||
}
|
||||
|
||||
if env.Name != testSubject.Name {
|
||||
t.Errorf("Expected Name to be %q, but got %q", env.Name, testSubject.Name)
|
||||
}
|
||||
if env.ManagementPortalURL != testSubject.ManagementPortalURL {
|
||||
t.Errorf("Expected ManagementPortalURL to be %q, but got %q", env.ManagementPortalURL, testSubject.ManagementPortalURL)
|
||||
}
|
||||
if env.PublishSettingsURL != testSubject.PublishSettingsURL {
|
||||
t.Errorf("Expected PublishSettingsURL to be %q, but got %q", env.PublishSettingsURL, testSubject.PublishSettingsURL)
|
||||
}
|
||||
if env.ServiceManagementEndpoint != testSubject.ServiceManagementEndpoint {
|
||||
t.Errorf("Expected ServiceManagementEndpoint to be %q, but got %q", env.ServiceManagementEndpoint, testSubject.ServiceManagementEndpoint)
|
||||
}
|
||||
if env.ResourceManagerEndpoint != testSubject.ResourceManagerEndpoint {
|
||||
t.Errorf("Expected ResourceManagerEndpoint to be %q, but got %q", env.ResourceManagerEndpoint, testSubject.ResourceManagerEndpoint)
|
||||
}
|
||||
if env.ActiveDirectoryEndpoint != testSubject.ActiveDirectoryEndpoint {
|
||||
t.Errorf("Expected ActiveDirectoryEndpoint to be %q, but got %q", env.ActiveDirectoryEndpoint, testSubject.ActiveDirectoryEndpoint)
|
||||
}
|
||||
if env.GalleryEndpoint != testSubject.GalleryEndpoint {
|
||||
t.Errorf("Expected GalleryEndpoint to be %q, but got %q", env.GalleryEndpoint, testSubject.GalleryEndpoint)
|
||||
}
|
||||
if env.KeyVaultEndpoint != testSubject.KeyVaultEndpoint {
|
||||
t.Errorf("Expected KeyVaultEndpoint to be %q, but got %q", env.KeyVaultEndpoint, testSubject.KeyVaultEndpoint)
|
||||
}
|
||||
if env.GraphEndpoint != testSubject.GraphEndpoint {
|
||||
t.Errorf("Expected GraphEndpoint to be %q, but got %q", env.GraphEndpoint, testSubject.GraphEndpoint)
|
||||
}
|
||||
if env.StorageEndpointSuffix != testSubject.StorageEndpointSuffix {
|
||||
t.Errorf("Expected StorageEndpointSuffix to be %q, but got %q", env.StorageEndpointSuffix, testSubject.StorageEndpointSuffix)
|
||||
}
|
||||
if env.SQLDatabaseDNSSuffix != testSubject.SQLDatabaseDNSSuffix {
|
||||
t.Errorf("Expected SQLDatabaseDNSSuffix to be %q, but got %q", env.SQLDatabaseDNSSuffix, testSubject.SQLDatabaseDNSSuffix)
|
||||
}
|
||||
if env.TrafficManagerDNSSuffix != testSubject.TrafficManagerDNSSuffix {
|
||||
t.Errorf("Expected TrafficManagerDNSSuffix to be %q, but got %q", env.TrafficManagerDNSSuffix, testSubject.TrafficManagerDNSSuffix)
|
||||
}
|
||||
if env.KeyVaultDNSSuffix != testSubject.KeyVaultDNSSuffix {
|
||||
t.Errorf("Expected KeyVaultDNSSuffix to be %q, but got %q", env.KeyVaultDNSSuffix, testSubject.KeyVaultDNSSuffix)
|
||||
}
|
||||
if env.ServiceBusEndpointSuffix != testSubject.ServiceBusEndpointSuffix {
|
||||
t.Errorf("Expected ServiceBusEndpointSuffix to be %q, but got %q", env.ServiceBusEndpointSuffix, testSubject.ServiceBusEndpointSuffix)
|
||||
}
|
||||
if env.ServiceManagementVMDNSSuffix != testSubject.ServiceManagementVMDNSSuffix {
|
||||
t.Errorf("Expected ServiceManagementVMDNSSuffix to be %q, but got %q", env.ServiceManagementVMDNSSuffix, testSubject.ServiceManagementVMDNSSuffix)
|
||||
}
|
||||
if env.ResourceManagerVMDNSSuffix != testSubject.ResourceManagerVMDNSSuffix {
|
||||
t.Errorf("Expected ResourceManagerVMDNSSuffix to be %q, but got %q", env.ResourceManagerVMDNSSuffix, testSubject.ResourceManagerVMDNSSuffix)
|
||||
}
|
||||
if env.ContainerRegistryDNSSuffix != testSubject.ContainerRegistryDNSSuffix {
|
||||
t.Errorf("Expected ContainerRegistryDNSSuffix to be %q, but got %q", env.ContainerRegistryDNSSuffix, testSubject.ContainerRegistryDNSSuffix)
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
# autorest azure example
|
||||
|
||||
## Usage (device mode)
|
||||
|
||||
This shows how to use the example for device auth.
|
||||
|
||||
1. Execute this. It will save your token to /tmp/azure-example-token:
|
||||
|
||||
```
|
||||
./example -tenantId "13de0a15-b5db-44b9-b682-b4ba82afbd29" -subscriptionId "aff271ee-e9be-4441-b9bb-42f5af4cbaeb" -mode "device" -tokenCachePath "/tmp/azure-example-token"
|
||||
```
|
||||
|
||||
2. Execute it again, it will load the token from cache and not prompt for auth again.
|
||||
|
||||
## Usage (certificate mode)
|
||||
|
||||
This example covers how to make an authenticated call to the Azure Resource Manager APIs, using certificate-based authentication.
|
||||
|
||||
0. Export some required variables
|
||||
|
||||
```
|
||||
export SUBSCRIPTION_ID="aff271ee-e9be-4441-b9bb-42f5af4cbaeb"
|
||||
export TENANT_ID="13de0a15-b5db-44b9-b682-b4ba82afbd29"
|
||||
export RESOURCE_GROUP="someresourcegroup"
|
||||
```
|
||||
|
||||
* replace both values with your own
|
||||
|
||||
1. Create a private key
|
||||
|
||||
```
|
||||
openssl genrsa -out "example.key" 2048
|
||||
```
|
||||
|
||||
|
||||
|
||||
2. Create the certificate
|
||||
|
||||
```
|
||||
openssl req -new -key "example.key" -subj "/CN=example" -out "example.csr"
|
||||
|
||||
openssl x509 -req -in "example.csr" -signkey "example.key" -out "example.crt" -days 10000
|
||||
```
|
||||
|
||||
|
||||
|
||||
3. Create the PKCS12 version of the certificate (with no password)
|
||||
|
||||
```
|
||||
openssl pkcs12 -export -out "example.pfx" -inkey "example.key" -in "example.crt" -passout pass:
|
||||
```
|
||||
|
||||
|
||||
|
||||
4. Register a new Azure AD Application with the certificate contents
|
||||
|
||||
```
|
||||
certificateContents="$(tail -n+2 "example.key" | head -n-1)"
|
||||
|
||||
azure ad app create \
|
||||
--name "example-azuread-app" \
|
||||
--home-page="http://example-azuread-app/home" \
|
||||
--identifier-uris "http://example-azuread-app/app" \
|
||||
--key-usage "Verify" \
|
||||
--end-date "2020-01-01" \
|
||||
--key-value "${certificateContents}"
|
||||
```
|
||||
|
||||
|
||||
|
||||
5. Create a new service principal using the "Application Id" from the previous step
|
||||
|
||||
```
|
||||
azure ad sp create "APPLICATION_ID"
|
||||
```
|
||||
|
||||
* Replace APPLICATION_ID with the "Application Id" returned in step 4
|
||||
|
||||
|
||||
|
||||
6. Grant your service principal necessary permissions
|
||||
|
||||
```
|
||||
azure role assignment create \
|
||||
--resource-group "${RESOURCE_GROUP}" \
|
||||
--roleName "Contributor" \
|
||||
--subscription "${SUBSCRIPTION_ID}" \
|
||||
--spn "http://example-azuread-app/app"
|
||||
```
|
||||
|
||||
* Replace SUBSCRIPTION_ID with your subscription id
|
||||
* Replace RESOURCE_GROUP with the resource group for the assignment
|
||||
* Ensure that the `spn` parameter matches an `identifier-url` from Step 4
|
||||
|
||||
|
||||
|
||||
7. Run this example app to see your resource groups
|
||||
|
||||
```
|
||||
go run main.go \
|
||||
--tenantId="${TENANT_ID}" \
|
||||
--subscriptionId="${SUBSCRIPTION_ID}" \
|
||||
--applicationId="http://example-azuread-app/app" \
|
||||
--certificatePath="certificate.pfx"
|
||||
```
|
||||
|
||||
|
||||
You should see something like this as output:
|
||||
|
||||
```
|
||||
2015/11/08 18:28:39 Using these settings:
|
||||
2015/11/08 18:28:39 * certificatePath: certificate.pfx
|
||||
2015/11/08 18:28:39 * applicationID: http://example-azuread-app/app
|
||||
2015/11/08 18:28:39 * tenantID: 13de0a15-b5db-44b9-b682-b4ba82afbd29
|
||||
2015/11/08 18:28:39 * subscriptionID: aff271ee-e9be-4441-b9bb-42f5af4cbaeb
|
||||
2015/11/08 18:28:39 loading certificate...
|
||||
2015/11/08 18:28:39 retrieve oauth token...
|
||||
2015/11/08 18:28:39 querying the list of resource groups...
|
||||
2015/11/08 18:28:50
|
||||
2015/11/08 18:28:50 Groups: {"value":[{"id":"/subscriptions/aff271ee-e9be-4441-b9bb-42f5af4cbaeb/resourceGroups/kube-66f30810","name":"kube-66f30810","location":"westus","tags":{},"properties":{"provisioningState":"Succeeded"}}]}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Notes
|
||||
|
||||
You may need to wait sometime between executing step 4, step 5 and step 6. If you issue those requests too quickly, you might hit an AD server that is not consistent with the server where the resource was created.
|
||||
+258
@@ -0,0 +1,258 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"golang.org/x/crypto/pkcs12"
|
||||
)
|
||||
|
||||
const (
|
||||
resourceGroupURLTemplate = "https://management.azure.com"
|
||||
apiVersion = "2015-01-01"
|
||||
nativeAppClientID = "a87032a7-203c-4bf7-913c-44c50d23409a"
|
||||
resource = "https://management.core.windows.net/"
|
||||
)
|
||||
|
||||
var (
|
||||
mode string
|
||||
tenantID string
|
||||
subscriptionID string
|
||||
applicationID string
|
||||
|
||||
tokenCachePath string
|
||||
forceRefresh bool
|
||||
impatient bool
|
||||
|
||||
certificatePath string
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&mode, "mode", "device", "mode of operation for SPT creation")
|
||||
flag.StringVar(&certificatePath, "certificatePath", "", "path to pk12/pfx certificate")
|
||||
flag.StringVar(&applicationID, "applicationId", "", "application id")
|
||||
flag.StringVar(&tenantID, "tenantId", "", "tenant id")
|
||||
flag.StringVar(&subscriptionID, "subscriptionId", "", "subscription id")
|
||||
flag.StringVar(&tokenCachePath, "tokenCachePath", "", "location of oauth token cache")
|
||||
flag.BoolVar(&forceRefresh, "forceRefresh", false, "pass true to force a token refresh")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
log.Printf("mode(%s) certPath(%s) appID(%s) tenantID(%s), subID(%s)\n",
|
||||
mode, certificatePath, applicationID, tenantID, subscriptionID)
|
||||
|
||||
if mode == "certificate" &&
|
||||
(strings.TrimSpace(tenantID) == "" || strings.TrimSpace(subscriptionID) == "") {
|
||||
log.Fatalln("Bad usage. Using certificate mode. Please specify tenantID, subscriptionID")
|
||||
}
|
||||
|
||||
if mode != "certificate" && mode != "device" {
|
||||
log.Fatalln("Bad usage. Mode must be one of 'certificate' or 'device'.")
|
||||
}
|
||||
|
||||
if mode == "device" && strings.TrimSpace(applicationID) == "" {
|
||||
log.Println("Using device mode auth. Will use `azkube` clientID since none was specified on the comand line.")
|
||||
applicationID = nativeAppClientID
|
||||
}
|
||||
|
||||
if mode == "certificate" && strings.TrimSpace(certificatePath) == "" {
|
||||
log.Fatalln("Bad usage. Mode 'certificate' requires the 'certificatePath' argument.")
|
||||
}
|
||||
|
||||
if strings.TrimSpace(tenantID) == "" || strings.TrimSpace(subscriptionID) == "" || strings.TrimSpace(applicationID) == "" {
|
||||
log.Fatalln("Bad usage. Must specify the 'tenantId' and 'subscriptionId'")
|
||||
}
|
||||
}
|
||||
|
||||
func getSptFromCachedToken(oauthConfig adal.OAuthConfig, clientID, resource string, callbacks ...adal.TokenRefreshCallback) (*adal.ServicePrincipalToken, error) {
|
||||
token, err := adal.LoadToken(tokenCachePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load token from cache: %v", err)
|
||||
}
|
||||
|
||||
spt, _ := adal.NewServicePrincipalTokenFromManualToken(
|
||||
oauthConfig,
|
||||
clientID,
|
||||
resource,
|
||||
*token,
|
||||
callbacks...)
|
||||
|
||||
return spt, nil
|
||||
}
|
||||
|
||||
func decodePkcs12(pkcs []byte, password string) (*x509.Certificate, *rsa.PrivateKey, error) {
|
||||
privateKey, certificate, err := pkcs12.Decode(pkcs, password)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
rsaPrivateKey, isRsaKey := privateKey.(*rsa.PrivateKey)
|
||||
if !isRsaKey {
|
||||
return nil, nil, fmt.Errorf("PKCS#12 certificate must contain an RSA private key")
|
||||
}
|
||||
|
||||
return certificate, rsaPrivateKey, nil
|
||||
}
|
||||
|
||||
func getSptFromCertificate(oauthConfig adal.OAuthConfig, clientID, resource, certicatePath string, callbacks ...adal.TokenRefreshCallback) (*adal.ServicePrincipalToken, error) {
|
||||
certData, err := ioutil.ReadFile(certificatePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read the certificate file (%s): %v", certificatePath, err)
|
||||
}
|
||||
|
||||
certificate, rsaPrivateKey, err := decodePkcs12(certData, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode pkcs12 certificate while creating spt: %v", err)
|
||||
}
|
||||
|
||||
spt, _ := adal.NewServicePrincipalTokenFromCertificate(
|
||||
oauthConfig,
|
||||
clientID,
|
||||
certificate,
|
||||
rsaPrivateKey,
|
||||
resource,
|
||||
callbacks...)
|
||||
|
||||
return spt, nil
|
||||
}
|
||||
|
||||
func getSptFromDeviceFlow(oauthConfig adal.OAuthConfig, clientID, resource string, callbacks ...adal.TokenRefreshCallback) (*adal.ServicePrincipalToken, error) {
|
||||
oauthClient := &autorest.Client{}
|
||||
deviceCode, err := adal.InitiateDeviceAuth(oauthClient, oauthConfig, clientID, resource)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to start device auth flow: %s", err)
|
||||
}
|
||||
|
||||
fmt.Println(*deviceCode.Message)
|
||||
|
||||
token, err := adal.WaitForUserCompletion(oauthClient, deviceCode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to finish device auth flow: %s", err)
|
||||
}
|
||||
|
||||
spt, err := adal.NewServicePrincipalTokenFromManualToken(
|
||||
oauthConfig,
|
||||
clientID,
|
||||
resource,
|
||||
*token,
|
||||
callbacks...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get oauth token from device flow: %v", err)
|
||||
}
|
||||
|
||||
return spt, nil
|
||||
}
|
||||
|
||||
func printResourceGroups(client *autorest.Client) error {
|
||||
p := map[string]interface{}{"subscription-id": subscriptionID}
|
||||
q := map[string]interface{}{"api-version": apiVersion}
|
||||
|
||||
req, _ := autorest.Prepare(&http.Request{},
|
||||
autorest.AsGet(),
|
||||
autorest.WithBaseURL(resourceGroupURLTemplate),
|
||||
autorest.WithPathParameters("/subscriptions/{subscription-id}/resourcegroups", p),
|
||||
autorest.WithQueryParameters(q))
|
||||
|
||||
resp, err := autorest.SendWithSender(client, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
value := struct {
|
||||
ResourceGroups []struct {
|
||||
Name string `json:"name"`
|
||||
} `json:"value"`
|
||||
}{}
|
||||
|
||||
defer resp.Body.Close()
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
err = dec.Decode(&value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var groupNames = make([]string, len(value.ResourceGroups))
|
||||
for i, name := range value.ResourceGroups {
|
||||
groupNames[i] = name.Name
|
||||
}
|
||||
|
||||
log.Println("Groups:", strings.Join(groupNames, ", "))
|
||||
return err
|
||||
}
|
||||
|
||||
func saveToken(spt adal.Token) {
|
||||
if tokenCachePath != "" {
|
||||
err := adal.SaveToken(tokenCachePath, 0600, spt)
|
||||
if err != nil {
|
||||
log.Println("error saving token", err)
|
||||
} else {
|
||||
log.Println("saved token to", tokenCachePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var spt *adal.ServicePrincipalToken
|
||||
var err error
|
||||
|
||||
callback := func(t adal.Token) error {
|
||||
log.Println("refresh callback was called")
|
||||
saveToken(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
oauthConfig, err := adal.NewOAuthConfig(azure.PublicCloud.ActiveDirectoryEndpoint, tenantID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if tokenCachePath != "" {
|
||||
log.Println("tokenCachePath specified; attempting to load from", tokenCachePath)
|
||||
spt, err = getSptFromCachedToken(*oauthConfig, applicationID, resource, callback)
|
||||
if err != nil {
|
||||
spt = nil // just in case, this is the condition below
|
||||
log.Println("loading from cache failed:", err)
|
||||
}
|
||||
}
|
||||
|
||||
if spt == nil {
|
||||
log.Println("authenticating via 'mode'", mode)
|
||||
switch mode {
|
||||
case "device":
|
||||
spt, err = getSptFromDeviceFlow(*oauthConfig, applicationID, resource, callback)
|
||||
case "certificate":
|
||||
spt, err = getSptFromCertificate(*oauthConfig, applicationID, resource, certificatePath, callback)
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalln("failed to retrieve token:", err)
|
||||
}
|
||||
|
||||
// should save it as soon as you get it since Refresh won't be called for some time
|
||||
if tokenCachePath != "" {
|
||||
saveToken(spt.Token)
|
||||
}
|
||||
}
|
||||
|
||||
client := &autorest.Client{}
|
||||
client.Authorizer = autorest.NewBearerAuthorizer(spt)
|
||||
|
||||
printResourceGroups(client)
|
||||
|
||||
if forceRefresh {
|
||||
err = spt.Refresh()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
printResourceGroups(client)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user