vendor: update all dependencies to latest versions
This commit is contained in:
+214
-56
@@ -1,7 +1,23 @@
|
||||
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 (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
@@ -23,6 +39,152 @@ const (
|
||||
operationSucceeded string = "Succeeded"
|
||||
)
|
||||
|
||||
var pollingCodes = [...]int{http.StatusAccepted, http.StatusCreated, http.StatusOK}
|
||||
|
||||
// Future provides a mechanism to access the status and results of an asynchronous request.
|
||||
// Since futures are stateful they should be passed by value to avoid race conditions.
|
||||
type Future struct {
|
||||
req *http.Request
|
||||
resp *http.Response
|
||||
ps pollingState
|
||||
}
|
||||
|
||||
// NewFuture returns a new Future object initialized with the specified request.
|
||||
func NewFuture(req *http.Request) Future {
|
||||
return Future{req: req}
|
||||
}
|
||||
|
||||
// Response returns the last HTTP response or nil if there isn't one.
|
||||
func (f Future) Response() *http.Response {
|
||||
return f.resp
|
||||
}
|
||||
|
||||
// Status returns the last status message of the operation.
|
||||
func (f Future) Status() string {
|
||||
if f.ps.State == "" {
|
||||
return "Unknown"
|
||||
}
|
||||
return f.ps.State
|
||||
}
|
||||
|
||||
// PollingMethod returns the method used to monitor the status of the asynchronous operation.
|
||||
func (f Future) PollingMethod() PollingMethodType {
|
||||
return f.ps.PollingMethod
|
||||
}
|
||||
|
||||
// Done queries the service to see if the operation has completed.
|
||||
func (f *Future) Done(sender autorest.Sender) (bool, error) {
|
||||
// exit early if this future has terminated
|
||||
if f.ps.hasTerminated() {
|
||||
return true, f.errorInfo()
|
||||
}
|
||||
|
||||
resp, err := sender.Do(f.req)
|
||||
f.resp = resp
|
||||
if err != nil || !autorest.ResponseHasStatusCode(resp, pollingCodes[:]...) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
err = updatePollingState(resp, &f.ps)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if f.ps.hasTerminated() {
|
||||
return true, f.errorInfo()
|
||||
}
|
||||
|
||||
f.req, err = newPollingRequest(f.ps)
|
||||
return false, err
|
||||
}
|
||||
|
||||
// GetPollingDelay returns a duration the application should wait before checking
|
||||
// the status of the asynchronous request and true; this value is returned from
|
||||
// the service via the Retry-After response header. If the header wasn't returned
|
||||
// then the function returns the zero-value time.Duration and false.
|
||||
func (f Future) GetPollingDelay() (time.Duration, bool) {
|
||||
if f.resp == nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
retry := f.resp.Header.Get(autorest.HeaderRetryAfter)
|
||||
if retry == "" {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
d, err := time.ParseDuration(retry + "s")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return d, true
|
||||
}
|
||||
|
||||
// WaitForCompletion will return when one of the following conditions is met: the long
|
||||
// running operation has completed, the provided context is cancelled, or the client's
|
||||
// polling duration has been exceeded. It will retry failed polling attempts based on
|
||||
// the retry value defined in the client up to the maximum retry attempts.
|
||||
func (f Future) WaitForCompletion(ctx context.Context, client autorest.Client) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, client.PollingDuration)
|
||||
defer cancel()
|
||||
|
||||
done, err := f.Done(client)
|
||||
for attempts := 0; !done; done, err = f.Done(client) {
|
||||
if attempts >= client.RetryAttempts {
|
||||
return autorest.NewErrorWithError(err, "azure", "WaitForCompletion", f.resp, "the number of retries has been exceeded")
|
||||
}
|
||||
// we want delayAttempt to be zero in the non-error case so
|
||||
// that DelayForBackoff doesn't perform exponential back-off
|
||||
var delayAttempt int
|
||||
var delay time.Duration
|
||||
if err == nil {
|
||||
// check for Retry-After delay, if not present use the client's polling delay
|
||||
var ok bool
|
||||
delay, ok = f.GetPollingDelay()
|
||||
if !ok {
|
||||
delay = client.PollingDelay
|
||||
}
|
||||
} else {
|
||||
// there was an error polling for status so perform exponential
|
||||
// back-off based on the number of attempts using the client's retry
|
||||
// duration. update attempts after delayAttempt to avoid off-by-one.
|
||||
delayAttempt = attempts
|
||||
delay = client.RetryDuration
|
||||
attempts++
|
||||
}
|
||||
// wait until the delay elapses or the context is cancelled
|
||||
delayElapsed := autorest.DelayForBackoff(delay, delayAttempt, ctx.Done())
|
||||
if !delayElapsed {
|
||||
return autorest.NewErrorWithError(ctx.Err(), "azure", "WaitForCompletion", f.resp, "context has been cancelled")
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// if the operation failed the polling state will contain
|
||||
// error information and implements the error interface
|
||||
func (f *Future) errorInfo() error {
|
||||
if !f.ps.hasSucceeded() {
|
||||
return f.ps
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (f Future) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(&f.ps)
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (f *Future) UnmarshalJSON(data []byte) error {
|
||||
err := json.Unmarshal(data, &f.ps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.req, err = newPollingRequest(f.ps)
|
||||
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
|
||||
@@ -34,8 +196,7 @@ func DoPollForAsynchronous(delay time.Duration) autorest.SendDecorator {
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
pollingCodes := []int{http.StatusAccepted, http.StatusCreated, http.StatusOK}
|
||||
if !autorest.ResponseHasStatusCode(resp, pollingCodes...) {
|
||||
if !autorest.ResponseHasStatusCode(resp, pollingCodes[:]...) {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -52,10 +213,11 @@ func DoPollForAsynchronous(delay time.Duration) autorest.SendDecorator {
|
||||
break
|
||||
}
|
||||
|
||||
r, err = newPollingRequest(resp, ps)
|
||||
r, err = newPollingRequest(ps)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
r.Cancel = resp.Request.Cancel
|
||||
|
||||
delay = autorest.GetRetryAfter(resp, delay)
|
||||
resp, err = autorest.SendWithSender(s, r,
|
||||
@@ -72,20 +234,15 @@ func getAsyncOperation(resp *http.Response) string {
|
||||
}
|
||||
|
||||
func hasSucceeded(state string) bool {
|
||||
return state == operationSucceeded
|
||||
return strings.EqualFold(state, operationSucceeded)
|
||||
}
|
||||
|
||||
func hasTerminated(state string) bool {
|
||||
switch state {
|
||||
case operationCanceled, operationFailed, operationSucceeded:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return strings.EqualFold(state, operationCanceled) || strings.EqualFold(state, operationFailed) || strings.EqualFold(state, operationSucceeded)
|
||||
}
|
||||
|
||||
func hasFailed(state string) bool {
|
||||
return state == operationFailed
|
||||
return strings.EqualFold(state, operationFailed)
|
||||
}
|
||||
|
||||
type provisioningTracker interface {
|
||||
@@ -146,36 +303,42 @@ func (ps provisioningStatus) hasProvisioningError() bool {
|
||||
return ps.ProvisioningError != ServiceError{}
|
||||
}
|
||||
|
||||
type pollingResponseFormat string
|
||||
// PollingMethodType defines a type used for enumerating polling mechanisms.
|
||||
type PollingMethodType string
|
||||
|
||||
const (
|
||||
usesOperationResponse pollingResponseFormat = "OperationResponse"
|
||||
usesProvisioningStatus pollingResponseFormat = "ProvisioningStatus"
|
||||
formatIsUnknown pollingResponseFormat = ""
|
||||
// PollingAsyncOperation indicates the polling method uses the Azure-AsyncOperation header.
|
||||
PollingAsyncOperation PollingMethodType = "AsyncOperation"
|
||||
|
||||
// PollingLocation indicates the polling method uses the Location header.
|
||||
PollingLocation PollingMethodType = "Location"
|
||||
|
||||
// PollingUnknown indicates an unknown polling method and is the default value.
|
||||
PollingUnknown PollingMethodType = ""
|
||||
)
|
||||
|
||||
type pollingState struct {
|
||||
responseFormat pollingResponseFormat
|
||||
uri string
|
||||
state string
|
||||
code string
|
||||
message string
|
||||
PollingMethod PollingMethodType `json:"pollingMethod"`
|
||||
URI string `json:"uri"`
|
||||
State string `json:"state"`
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func (ps pollingState) hasSucceeded() bool {
|
||||
return hasSucceeded(ps.state)
|
||||
return hasSucceeded(ps.State)
|
||||
}
|
||||
|
||||
func (ps pollingState) hasTerminated() bool {
|
||||
return hasTerminated(ps.state)
|
||||
return hasTerminated(ps.State)
|
||||
}
|
||||
|
||||
func (ps pollingState) hasFailed() bool {
|
||||
return hasFailed(ps.state)
|
||||
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)
|
||||
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
|
||||
@@ -190,7 +353,7 @@ func updatePollingState(resp *http.Response, ps *pollingState) error {
|
||||
// -- 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 {
|
||||
if ps.PollingMethod == PollingAsyncOperation {
|
||||
pt = &operationResource{}
|
||||
} else {
|
||||
pt = &provisioningStatus{}
|
||||
@@ -198,30 +361,30 @@ func updatePollingState(resp *http.Response, ps *pollingState) error {
|
||||
|
||||
// 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 {
|
||||
if ps.PollingMethod == PollingUnknown {
|
||||
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
|
||||
ps.URI = getAsyncOperation(resp)
|
||||
if ps.URI != "" {
|
||||
ps.PollingMethod = PollingAsyncOperation
|
||||
} else {
|
||||
ps.responseFormat = usesProvisioningStatus
|
||||
ps.PollingMethod = PollingLocation
|
||||
}
|
||||
|
||||
// Else, use the Location header
|
||||
if ps.uri == "" {
|
||||
ps.uri = autorest.GetLocation(resp)
|
||||
if ps.URI == "" {
|
||||
ps.URI = autorest.GetLocation(resp)
|
||||
}
|
||||
|
||||
// Lastly, requests against an existing resource, use the last request URI
|
||||
if ps.uri == "" {
|
||||
if ps.URI == "" {
|
||||
m := strings.ToUpper(req.Method)
|
||||
if m == http.MethodPatch || m == http.MethodPut || m == http.MethodGet {
|
||||
ps.uri = req.URL.String()
|
||||
ps.URI = req.URL.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,23 +405,23 @@ func updatePollingState(resp *http.Response, ps *pollingState) error {
|
||||
// -- Unknown states are per-service inprogress states
|
||||
// -- Otherwise, infer state from HTTP status code
|
||||
if pt.hasTerminated() {
|
||||
ps.state = pt.state()
|
||||
ps.State = pt.state()
|
||||
} else if pt.state() != "" {
|
||||
ps.state = operationInProgress
|
||||
ps.State = operationInProgress
|
||||
} else {
|
||||
switch resp.StatusCode {
|
||||
case http.StatusAccepted:
|
||||
ps.state = operationInProgress
|
||||
ps.State = operationInProgress
|
||||
|
||||
case http.StatusNoContent, http.StatusCreated, http.StatusOK:
|
||||
ps.state = operationSucceeded
|
||||
ps.State = operationSucceeded
|
||||
|
||||
default:
|
||||
ps.state = operationFailed
|
||||
ps.State = operationFailed
|
||||
}
|
||||
}
|
||||
|
||||
if ps.state == operationInProgress && ps.uri == "" {
|
||||
if strings.EqualFold(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)
|
||||
}
|
||||
|
||||
@@ -267,35 +430,30 @@ func updatePollingState(resp *http.Response, ps *pollingState) error {
|
||||
// -- Response
|
||||
// -- Otherwise, Unknown
|
||||
if ps.hasFailed() {
|
||||
if ps.responseFormat == usesOperationResponse {
|
||||
if ps.PollingMethod == PollingAsyncOperation {
|
||||
or := pt.(*operationResource)
|
||||
ps.code = or.OperationError.Code
|
||||
ps.message = or.OperationError.Message
|
||||
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
|
||||
ps.Code = p.ProvisioningError.Code
|
||||
ps.Message = p.ProvisioningError.Message
|
||||
} else {
|
||||
ps.code = "Unknown"
|
||||
ps.message = "None"
|
||||
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},
|
||||
func newPollingRequest(ps pollingState) (*http.Request, error) {
|
||||
reqPoll, err := autorest.Prepare(&http.Request{},
|
||||
autorest.AsGet(),
|
||||
autorest.WithBaseURL(ps.uri))
|
||||
autorest.WithBaseURL(ps.URI))
|
||||
if err != nil {
|
||||
return nil, autorest.NewErrorWithError(err, "azure", "newPollingRequest", nil, "Failure creating poll request to %s", ps.uri)
|
||||
return nil, autorest.NewErrorWithError(err, "azure", "newPollingRequest", nil, "Failure creating poll request to %s", ps.URI)
|
||||
}
|
||||
|
||||
return reqPoll, nil
|
||||
|
||||
+259
-48
@@ -1,6 +1,23 @@
|
||||
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 (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
@@ -131,27 +148,27 @@ func TestProvisioningStatus_HasTerminatedReturnsFalseForUnknownStates(t *testing
|
||||
}
|
||||
|
||||
func TestPollingState_HasSucceededReturnsFalseIfNotSuccess(t *testing.T) {
|
||||
if (pollingState{state: "not a success string"}).hasSucceeded() {
|
||||
if (pollingState{State: "not a success string"}).hasSucceeded() {
|
||||
t.Fatalf("azure: pollingState#hasSucceeded failed to return false for a canceled operation")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPollingState_HasSucceededReturnsTrueIfSuccessful(t *testing.T) {
|
||||
if !(pollingState{state: operationSucceeded}).hasSucceeded() {
|
||||
if !(pollingState{State: operationSucceeded}).hasSucceeded() {
|
||||
t.Fatalf("azure: pollingState#hasSucceeded failed to return true for a successful operation")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPollingState_HasTerminatedReturnsTrueForKnownStates(t *testing.T) {
|
||||
for _, state := range []string{operationSucceeded, operationCanceled, operationFailed} {
|
||||
if !(pollingState{state: state}).hasTerminated() {
|
||||
if !(pollingState{State: state}).hasTerminated() {
|
||||
t.Fatalf("azure: pollingState#hasTerminated failed to return true for the '%s' state", state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPollingState_HasTerminatedReturnsFalseForUnknownStates(t *testing.T) {
|
||||
if (pollingState{state: "not a known state"}).hasTerminated() {
|
||||
if (pollingState{State: "not a known state"}).hasTerminated() {
|
||||
t.Fatalf("azure: pollingState#hasTerminated returned true for a non-terminal operation")
|
||||
}
|
||||
}
|
||||
@@ -168,7 +185,7 @@ func TestUpdatePollingState_ReturnsTerminatedForKnownProvisioningStates(t *testi
|
||||
for _, state := range []string{operationSucceeded, operationCanceled, operationFailed} {
|
||||
resp := mocks.NewResponseWithContent(fmt.Sprintf(pollingStateFormat, state))
|
||||
resp.StatusCode = 42
|
||||
ps := &pollingState{responseFormat: usesProvisioningStatus}
|
||||
ps := &pollingState{PollingMethod: PollingLocation}
|
||||
updatePollingState(resp, ps)
|
||||
if !ps.hasTerminated() {
|
||||
t.Fatalf("azure: updatePollingState failed to return a terminating pollingState for the '%s' state", state)
|
||||
@@ -179,7 +196,7 @@ func TestUpdatePollingState_ReturnsTerminatedForKnownProvisioningStates(t *testi
|
||||
func TestUpdatePollingState_ReturnsSuccessForSuccessfulProvisioningState(t *testing.T) {
|
||||
resp := mocks.NewResponseWithContent(fmt.Sprintf(pollingStateFormat, operationSucceeded))
|
||||
resp.StatusCode = 42
|
||||
ps := &pollingState{responseFormat: usesProvisioningStatus}
|
||||
ps := &pollingState{PollingMethod: PollingLocation}
|
||||
updatePollingState(resp, ps)
|
||||
if !ps.hasSucceeded() {
|
||||
t.Fatalf("azure: updatePollingState failed to return a successful pollingState for the '%s' state", operationSucceeded)
|
||||
@@ -190,7 +207,7 @@ func TestUpdatePollingState_ReturnsInProgressForAllOtherProvisioningStates(t *te
|
||||
s := "not a recognized state"
|
||||
resp := mocks.NewResponseWithContent(fmt.Sprintf(pollingStateFormat, s))
|
||||
resp.StatusCode = 42
|
||||
ps := &pollingState{responseFormat: usesProvisioningStatus}
|
||||
ps := &pollingState{PollingMethod: PollingLocation}
|
||||
updatePollingState(resp, ps)
|
||||
if ps.hasTerminated() {
|
||||
t.Fatalf("azure: updatePollingState returned terminated for unknown state '%s'", s)
|
||||
@@ -201,7 +218,7 @@ func TestUpdatePollingState_ReturnsSuccessWhenProvisioningStateFieldIsAbsentForS
|
||||
for _, sc := range []int{http.StatusOK, http.StatusCreated, http.StatusNoContent} {
|
||||
resp := mocks.NewResponseWithContent(pollingStateEmpty)
|
||||
resp.StatusCode = sc
|
||||
ps := &pollingState{responseFormat: usesProvisioningStatus}
|
||||
ps := &pollingState{PollingMethod: PollingLocation}
|
||||
updatePollingState(resp, ps)
|
||||
if !ps.hasSucceeded() {
|
||||
t.Fatalf("azure: updatePollingState failed to return success when the provisionState field is absent for Status Code %d", sc)
|
||||
@@ -212,7 +229,7 @@ func TestUpdatePollingState_ReturnsSuccessWhenProvisioningStateFieldIsAbsentForS
|
||||
func TestUpdatePollingState_ReturnsInProgressWhenProvisioningStateFieldIsAbsentForAccepted(t *testing.T) {
|
||||
resp := mocks.NewResponseWithContent(pollingStateEmpty)
|
||||
resp.StatusCode = http.StatusAccepted
|
||||
ps := &pollingState{responseFormat: usesProvisioningStatus}
|
||||
ps := &pollingState{PollingMethod: PollingLocation}
|
||||
updatePollingState(resp, ps)
|
||||
if ps.hasTerminated() {
|
||||
t.Fatalf("azure: updatePollingState returned terminated when the provisionState field is absent for Status Code Accepted")
|
||||
@@ -222,7 +239,7 @@ func TestUpdatePollingState_ReturnsInProgressWhenProvisioningStateFieldIsAbsentF
|
||||
func TestUpdatePollingState_ReturnsFailedWhenProvisioningStateFieldIsAbsentForUnknownStatusCodes(t *testing.T) {
|
||||
resp := mocks.NewResponseWithContent(pollingStateEmpty)
|
||||
resp.StatusCode = 42
|
||||
ps := &pollingState{responseFormat: usesProvisioningStatus}
|
||||
ps := &pollingState{PollingMethod: PollingLocation}
|
||||
updatePollingState(resp, ps)
|
||||
if !ps.hasTerminated() || ps.hasSucceeded() {
|
||||
t.Fatalf("azure: updatePollingState did not return failed when the provisionState field is absent for an unknown Status Code")
|
||||
@@ -233,7 +250,7 @@ func TestUpdatePollingState_ReturnsTerminatedForKnownOperationResourceStates(t *
|
||||
for _, state := range []string{operationSucceeded, operationCanceled, operationFailed} {
|
||||
resp := mocks.NewResponseWithContent(fmt.Sprintf(operationResourceFormat, state))
|
||||
resp.StatusCode = 42
|
||||
ps := &pollingState{responseFormat: usesOperationResponse}
|
||||
ps := &pollingState{PollingMethod: PollingAsyncOperation}
|
||||
updatePollingState(resp, ps)
|
||||
if !ps.hasTerminated() {
|
||||
t.Fatalf("azure: updatePollingState failed to return a terminating pollingState for the '%s' state", state)
|
||||
@@ -244,7 +261,7 @@ func TestUpdatePollingState_ReturnsTerminatedForKnownOperationResourceStates(t *
|
||||
func TestUpdatePollingState_ReturnsSuccessForSuccessfulOperationResourceState(t *testing.T) {
|
||||
resp := mocks.NewResponseWithContent(fmt.Sprintf(operationResourceFormat, operationSucceeded))
|
||||
resp.StatusCode = 42
|
||||
ps := &pollingState{responseFormat: usesOperationResponse}
|
||||
ps := &pollingState{PollingMethod: PollingAsyncOperation}
|
||||
updatePollingState(resp, ps)
|
||||
if !ps.hasSucceeded() {
|
||||
t.Fatalf("azure: updatePollingState failed to return a successful pollingState for the '%s' state", operationSucceeded)
|
||||
@@ -255,7 +272,7 @@ func TestUpdatePollingState_ReturnsInProgressForAllOtherOperationResourceStates(
|
||||
s := "not a recognized state"
|
||||
resp := mocks.NewResponseWithContent(fmt.Sprintf(operationResourceFormat, s))
|
||||
resp.StatusCode = 42
|
||||
ps := &pollingState{responseFormat: usesOperationResponse}
|
||||
ps := &pollingState{PollingMethod: PollingAsyncOperation}
|
||||
updatePollingState(resp, ps)
|
||||
if ps.hasTerminated() {
|
||||
t.Fatalf("azure: updatePollingState returned terminated for unknown state '%s'", s)
|
||||
@@ -266,7 +283,7 @@ func TestUpdatePollingState_CopiesTheResponseBody(t *testing.T) {
|
||||
s := fmt.Sprintf(pollingStateFormat, operationSucceeded)
|
||||
resp := mocks.NewResponseWithContent(s)
|
||||
resp.StatusCode = 42
|
||||
ps := &pollingState{responseFormat: usesOperationResponse}
|
||||
ps := &pollingState{PollingMethod: PollingAsyncOperation}
|
||||
updatePollingState(resp, ps)
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
@@ -280,7 +297,7 @@ func TestUpdatePollingState_CopiesTheResponseBody(t *testing.T) {
|
||||
func TestUpdatePollingState_ClosesTheOriginalResponseBody(t *testing.T) {
|
||||
resp := mocks.NewResponse()
|
||||
b := resp.Body.(*mocks.Body)
|
||||
ps := &pollingState{responseFormat: usesProvisioningStatus}
|
||||
ps := &pollingState{PollingMethod: PollingLocation}
|
||||
updatePollingState(resp, ps)
|
||||
if b.IsOpen() {
|
||||
t.Fatal("azure: updatePollingState failed to close the original http.Response Body")
|
||||
@@ -298,23 +315,23 @@ func TestUpdatePollingState_FailsWhenResponseLacksRequest(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdatePollingState_SetsTheResponseFormatWhenUsingTheAzureAsyncOperationHeader(t *testing.T) {
|
||||
func TestUpdatePollingState_SetsThePollingMethodWhenUsingTheAzureAsyncOperationHeader(t *testing.T) {
|
||||
ps := pollingState{}
|
||||
updatePollingState(newAsynchronousResponse(), &ps)
|
||||
|
||||
if ps.responseFormat != usesOperationResponse {
|
||||
if ps.PollingMethod != PollingAsyncOperation {
|
||||
t.Fatal("azure: updatePollingState failed to set the correct response format when using the Azure-AsyncOperation header")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdatePollingState_SetsTheResponseFormatWhenUsingTheAzureAsyncOperationHeaderIsMissing(t *testing.T) {
|
||||
func TestUpdatePollingState_SetsThePollingMethodWhenUsingTheAzureAsyncOperationHeaderIsMissing(t *testing.T) {
|
||||
resp := newAsynchronousResponse()
|
||||
resp.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
|
||||
|
||||
ps := pollingState{}
|
||||
updatePollingState(resp, &ps)
|
||||
|
||||
if ps.responseFormat != usesProvisioningStatus {
|
||||
if ps.PollingMethod != PollingLocation {
|
||||
t.Fatal("azure: updatePollingState failed to set the correct response format when the Azure-AsyncOperation header is absent")
|
||||
}
|
||||
}
|
||||
@@ -323,10 +340,10 @@ func TestUpdatePollingState_DoesNotChangeAnExistingReponseFormat(t *testing.T) {
|
||||
resp := newAsynchronousResponse()
|
||||
resp.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
|
||||
|
||||
ps := pollingState{responseFormat: usesOperationResponse}
|
||||
ps := pollingState{PollingMethod: PollingAsyncOperation}
|
||||
updatePollingState(resp, &ps)
|
||||
|
||||
if ps.responseFormat != usesOperationResponse {
|
||||
if ps.PollingMethod != PollingAsyncOperation {
|
||||
t.Fatal("azure: updatePollingState failed to leave an existing response format setting")
|
||||
}
|
||||
}
|
||||
@@ -337,7 +354,7 @@ func TestUpdatePollingState_PrefersTheAzureAsyncOperationHeader(t *testing.T) {
|
||||
ps := pollingState{}
|
||||
updatePollingState(resp, &ps)
|
||||
|
||||
if ps.uri != mocks.TestAzureAsyncURL {
|
||||
if ps.URI != mocks.TestAzureAsyncURL {
|
||||
t.Fatal("azure: updatePollingState failed to prefer the Azure-AsyncOperation header")
|
||||
}
|
||||
}
|
||||
@@ -349,7 +366,7 @@ func TestUpdatePollingState_PrefersLocationWhenTheAzureAsyncOperationHeaderMissi
|
||||
ps := pollingState{}
|
||||
updatePollingState(resp, &ps)
|
||||
|
||||
if ps.uri != mocks.TestLocationURL {
|
||||
if ps.URI != mocks.TestLocationURL {
|
||||
t.Fatal("azure: updatePollingState failed to prefer the Location header when the Azure-AsyncOperation header is missing")
|
||||
}
|
||||
}
|
||||
@@ -363,7 +380,7 @@ func TestUpdatePollingState_UsesTheObjectLocationIfAsyncHeadersAreMissing(t *tes
|
||||
ps := pollingState{}
|
||||
updatePollingState(resp, &ps)
|
||||
|
||||
if ps.uri != mocks.TestURL {
|
||||
if ps.URI != mocks.TestURL {
|
||||
t.Fatal("azure: updatePollingState failed to use the Object URL when the asynchronous headers are missing")
|
||||
}
|
||||
}
|
||||
@@ -378,7 +395,7 @@ func TestUpdatePollingState_RecognizesLowerCaseHTTPVerbs(t *testing.T) {
|
||||
ps := pollingState{}
|
||||
updatePollingState(resp, &ps)
|
||||
|
||||
if ps.uri != mocks.TestURL {
|
||||
if ps.URI != mocks.TestURL {
|
||||
t.Fatalf("azure: updatePollingState failed to recognize the lower-case HTTP verb '%s'", m)
|
||||
}
|
||||
}
|
||||
@@ -398,32 +415,22 @@ func TestUpdatePollingState_ReturnsAnErrorIfAsyncHeadersAreMissingForANewOrDelet
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewPollingRequest_FailsWhenResponseLacksRequest(t *testing.T) {
|
||||
resp := newAsynchronousResponse()
|
||||
resp.Request = nil
|
||||
|
||||
_, err := newPollingRequest(resp, pollingState{})
|
||||
if err == nil {
|
||||
t.Fatal("azure: newPollingRequest failed to return an error when the http.Response lacked the original http.Request")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewPollingRequest_ReturnsAnErrorWhenPrepareFails(t *testing.T) {
|
||||
_, err := newPollingRequest(newAsynchronousResponse(), pollingState{responseFormat: usesOperationResponse, uri: mocks.TestBadURL})
|
||||
_, err := newPollingRequest(pollingState{PollingMethod: PollingAsyncOperation, URI: mocks.TestBadURL})
|
||||
if err == nil {
|
||||
t.Fatal("azure: newPollingRequest failed to return an error when Prepare fails")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewPollingRequest_DoesNotReturnARequestWhenPrepareFails(t *testing.T) {
|
||||
req, _ := newPollingRequest(newAsynchronousResponse(), pollingState{responseFormat: usesOperationResponse, uri: mocks.TestBadURL})
|
||||
req, _ := newPollingRequest(pollingState{PollingMethod: PollingAsyncOperation, URI: mocks.TestBadURL})
|
||||
if req != nil {
|
||||
t.Fatal("azure: newPollingRequest returned an http.Request when Prepare failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewPollingRequest_ReturnsAGetRequest(t *testing.T) {
|
||||
req, _ := newPollingRequest(newAsynchronousResponse(), pollingState{responseFormat: usesOperationResponse, uri: mocks.TestAzureAsyncURL})
|
||||
req, _ := newPollingRequest(pollingState{PollingMethod: PollingAsyncOperation, URI: mocks.TestAzureAsyncURL})
|
||||
if req.Method != "GET" {
|
||||
t.Fatalf("azure: newPollingRequest did not create an HTTP GET request -- actual method %v", req.Method)
|
||||
}
|
||||
@@ -629,7 +636,7 @@ func TestDoPollForAsynchronous_PollsForStatusAccepted(t *testing.T) {
|
||||
r, _ := autorest.SendWithSender(client, mocks.NewRequest(),
|
||||
DoPollForAsynchronous(time.Millisecond))
|
||||
|
||||
if client.Attempts() < 4 {
|
||||
if client.Attempts() < client.NumResponses() {
|
||||
t.Fatalf("azure: DoPollForAsynchronous stopped polling before receiving a terminated OperationResource")
|
||||
}
|
||||
|
||||
@@ -652,7 +659,7 @@ func TestDoPollForAsynchronous_PollsForStatusCreated(t *testing.T) {
|
||||
r, _ := autorest.SendWithSender(client, mocks.NewRequest(),
|
||||
DoPollForAsynchronous(time.Millisecond))
|
||||
|
||||
if client.Attempts() < 4 {
|
||||
if client.Attempts() < client.NumResponses() {
|
||||
t.Fatalf("azure: DoPollForAsynchronous stopped polling before receiving a terminated OperationResource")
|
||||
}
|
||||
|
||||
@@ -676,7 +683,7 @@ func TestDoPollForAsynchronous_PollsUntilProvisioningStatusTerminates(t *testing
|
||||
r, _ := autorest.SendWithSender(client, mocks.NewRequest(),
|
||||
DoPollForAsynchronous(time.Millisecond))
|
||||
|
||||
if client.Attempts() < 4 {
|
||||
if client.Attempts() < client.NumResponses() {
|
||||
t.Fatalf("azure: DoPollForAsynchronous stopped polling before receiving a terminated OperationResource")
|
||||
}
|
||||
|
||||
@@ -700,7 +707,7 @@ func TestDoPollForAsynchronous_PollsUntilProvisioningStatusSucceeds(t *testing.T
|
||||
r, _ := autorest.SendWithSender(client, mocks.NewRequest(),
|
||||
DoPollForAsynchronous(time.Millisecond))
|
||||
|
||||
if client.Attempts() < 4 {
|
||||
if client.Attempts() < client.NumResponses() {
|
||||
t.Fatalf("azure: DoPollForAsynchronous stopped polling before receiving a terminated OperationResource")
|
||||
}
|
||||
|
||||
@@ -721,7 +728,7 @@ func TestDoPollForAsynchronous_PollsUntilOperationResourceHasTerminated(t *testi
|
||||
r, _ := autorest.SendWithSender(client, mocks.NewRequest(),
|
||||
DoPollForAsynchronous(time.Millisecond))
|
||||
|
||||
if client.Attempts() < 4 {
|
||||
if client.Attempts() < client.NumResponses() {
|
||||
t.Fatalf("azure: DoPollForAsynchronous stopped polling before receiving a terminated OperationResource")
|
||||
}
|
||||
|
||||
@@ -742,7 +749,7 @@ func TestDoPollForAsynchronous_PollsUntilOperationResourceHasSucceeded(t *testin
|
||||
r, _ := autorest.SendWithSender(client, mocks.NewRequest(),
|
||||
DoPollForAsynchronous(time.Millisecond))
|
||||
|
||||
if client.Attempts() < 4 {
|
||||
if client.Attempts() < client.NumResponses() {
|
||||
t.Fatalf("azure: DoPollForAsynchronous stopped polling before receiving a terminated OperationResource")
|
||||
}
|
||||
|
||||
@@ -871,7 +878,7 @@ func TestDoPollForAsynchronous_ReturnsErrorForLastErrorResponse(t *testing.T) {
|
||||
r1.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
|
||||
r2 := newProvisioningStatusResponse("busy")
|
||||
r2.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
|
||||
r3 := newAsynchronousResponseWithError()
|
||||
r3 := newAsynchronousResponseWithError("400 Bad Request", http.StatusBadRequest)
|
||||
r3.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
|
||||
|
||||
client := mocks.NewSender()
|
||||
@@ -918,7 +925,7 @@ func TestDoPollForAsynchronous_ReturnsOperationResourceErrorForFailedOperations(
|
||||
|
||||
func TestDoPollForAsynchronous_ReturnsErrorForFirstPutRequest(t *testing.T) {
|
||||
// Return 400 bad response with error code and message in first put
|
||||
r1 := newAsynchronousResponseWithError()
|
||||
r1 := newAsynchronousResponseWithError("400 Bad Request", http.StatusBadRequest)
|
||||
client := mocks.NewSender()
|
||||
client.AppendResponse(r1)
|
||||
|
||||
@@ -1010,6 +1017,210 @@ func TestDoPollForAsynchronous_StopsPollingIfItReceivesAnInvalidOperationResourc
|
||||
autorest.ByClosing())
|
||||
}
|
||||
|
||||
func TestFuture_PollsUntilProvisioningStatusSucceeds(t *testing.T) {
|
||||
r1 := newAsynchronousResponse()
|
||||
r1.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
|
||||
r2 := newProvisioningStatusResponse("busy")
|
||||
r2.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
|
||||
r3 := newProvisioningStatusResponse(operationSucceeded)
|
||||
r3.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
|
||||
|
||||
client := mocks.NewSender()
|
||||
client.AppendResponse(r1)
|
||||
client.AppendAndRepeatResponse(r2, 2)
|
||||
client.AppendResponse(r3)
|
||||
|
||||
future := NewFuture(mocks.NewRequest())
|
||||
|
||||
for done, err := future.Done(client); !done; done, err = future.Done(client) {
|
||||
if future.PollingMethod() != PollingLocation {
|
||||
t.Fatalf("azure: wrong future polling method")
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("azure: TestFuture polling Done failed")
|
||||
}
|
||||
delay, ok := future.GetPollingDelay()
|
||||
if !ok {
|
||||
t.Fatalf("expected Retry-After value")
|
||||
}
|
||||
time.Sleep(delay)
|
||||
}
|
||||
|
||||
if client.Attempts() < client.NumResponses() {
|
||||
t.Fatalf("azure: TestFuture stopped polling before receiving a terminated OperationResource")
|
||||
}
|
||||
|
||||
autorest.Respond(future.Response(),
|
||||
autorest.ByClosing())
|
||||
}
|
||||
|
||||
func TestFuture_Marshalling(t *testing.T) {
|
||||
client := mocks.NewSender()
|
||||
client.AppendResponse(newAsynchronousResponse())
|
||||
|
||||
future := NewFuture(mocks.NewRequest())
|
||||
done, err := future.Done(client)
|
||||
if err != nil {
|
||||
t.Fatalf("azure: TestFuture marshalling Done failed")
|
||||
}
|
||||
if done {
|
||||
t.Fatalf("azure: TestFuture marshalling shouldn't be done")
|
||||
}
|
||||
if future.PollingMethod() != PollingAsyncOperation {
|
||||
t.Fatalf("azure: wrong future polling method")
|
||||
}
|
||||
|
||||
data, err := json.Marshal(future)
|
||||
if err != nil {
|
||||
t.Fatalf("azure: TestFuture failed to marshal")
|
||||
}
|
||||
|
||||
var future2 Future
|
||||
err = json.Unmarshal(data, &future2)
|
||||
if err != nil {
|
||||
t.Fatalf("azure: TestFuture failed to unmarshal")
|
||||
}
|
||||
|
||||
if future.ps.Code != future2.ps.Code {
|
||||
t.Fatalf("azure: TestFuture marshalling codes don't match")
|
||||
}
|
||||
if future.ps.Message != future2.ps.Message {
|
||||
t.Fatalf("azure: TestFuture marshalling messages don't match")
|
||||
}
|
||||
if future.ps.PollingMethod != future2.ps.PollingMethod {
|
||||
t.Fatalf("azure: TestFuture marshalling response formats don't match")
|
||||
}
|
||||
if future.ps.State != future2.ps.State {
|
||||
t.Fatalf("azure: TestFuture marshalling states don't match")
|
||||
}
|
||||
if future.ps.URI != future2.ps.URI {
|
||||
t.Fatalf("azure: TestFuture marshalling URIs don't match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFuture_WaitForCompletion(t *testing.T) {
|
||||
r1 := newAsynchronousResponse()
|
||||
r1.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
|
||||
r2 := newProvisioningStatusResponse("busy")
|
||||
r2.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
|
||||
r3 := newAsynchronousResponseWithError("Internal server error", http.StatusInternalServerError)
|
||||
r3.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
|
||||
r3.Header.Del(http.CanonicalHeaderKey("Retry-After"))
|
||||
r4 := newProvisioningStatusResponse(operationSucceeded)
|
||||
r4.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
|
||||
|
||||
sender := mocks.NewSender()
|
||||
sender.AppendResponse(r1)
|
||||
sender.AppendError(errors.New("transient network failure"))
|
||||
sender.AppendAndRepeatResponse(r2, 2)
|
||||
sender.AppendResponse(r3)
|
||||
sender.AppendResponse(r4)
|
||||
|
||||
future := NewFuture(mocks.NewRequest())
|
||||
|
||||
client := autorest.Client{
|
||||
PollingDelay: 1 * time.Second,
|
||||
PollingDuration: autorest.DefaultPollingDuration,
|
||||
RetryAttempts: autorest.DefaultRetryAttempts,
|
||||
RetryDuration: 1 * time.Second,
|
||||
Sender: sender,
|
||||
}
|
||||
|
||||
err := future.WaitForCompletion(context.Background(), client)
|
||||
if err != nil {
|
||||
t.Fatalf("azure: WaitForCompletion returned non-nil error")
|
||||
}
|
||||
|
||||
if sender.Attempts() < sender.NumResponses() {
|
||||
t.Fatalf("azure: TestFuture stopped polling before receiving a terminated OperationResource")
|
||||
}
|
||||
|
||||
autorest.Respond(future.Response(),
|
||||
autorest.ByClosing())
|
||||
}
|
||||
|
||||
func TestFuture_WaitForCompletionTimedOut(t *testing.T) {
|
||||
r1 := newAsynchronousResponse()
|
||||
r1.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
|
||||
r2 := newProvisioningStatusResponse("busy")
|
||||
r2.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
|
||||
|
||||
sender := mocks.NewSender()
|
||||
sender.AppendResponse(r1)
|
||||
sender.AppendAndRepeatResponseWithDelay(r2, 1*time.Second, 5)
|
||||
|
||||
future := NewFuture(mocks.NewRequest())
|
||||
|
||||
client := autorest.Client{
|
||||
PollingDelay: autorest.DefaultPollingDelay,
|
||||
PollingDuration: 2 * time.Second,
|
||||
RetryAttempts: autorest.DefaultRetryAttempts,
|
||||
RetryDuration: 1 * time.Second,
|
||||
Sender: sender,
|
||||
}
|
||||
|
||||
err := future.WaitForCompletion(context.Background(), client)
|
||||
if err == nil {
|
||||
t.Fatalf("azure: WaitForCompletion returned nil error, should have timed out")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFuture_WaitForCompletionRetriesExceeded(t *testing.T) {
|
||||
r1 := newAsynchronousResponse()
|
||||
r1.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
|
||||
|
||||
sender := mocks.NewSender()
|
||||
sender.AppendResponse(r1)
|
||||
sender.AppendAndRepeatError(errors.New("transient network failure"), autorest.DefaultRetryAttempts+1)
|
||||
|
||||
future := NewFuture(mocks.NewRequest())
|
||||
|
||||
client := autorest.Client{
|
||||
PollingDelay: autorest.DefaultPollingDelay,
|
||||
PollingDuration: autorest.DefaultPollingDuration,
|
||||
RetryAttempts: autorest.DefaultRetryAttempts,
|
||||
RetryDuration: 100 * time.Millisecond,
|
||||
Sender: sender,
|
||||
}
|
||||
|
||||
err := future.WaitForCompletion(context.Background(), client)
|
||||
if err == nil {
|
||||
t.Fatalf("azure: WaitForCompletion returned nil error, should have errored out")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFuture_WaitForCompletionCancelled(t *testing.T) {
|
||||
r1 := newAsynchronousResponse()
|
||||
r1.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
|
||||
r2 := newProvisioningStatusResponse("busy")
|
||||
r2.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
|
||||
|
||||
sender := mocks.NewSender()
|
||||
sender.AppendResponse(r1)
|
||||
sender.AppendAndRepeatResponseWithDelay(r2, 1*time.Second, 5)
|
||||
|
||||
future := NewFuture(mocks.NewRequest())
|
||||
|
||||
client := autorest.Client{
|
||||
PollingDelay: autorest.DefaultPollingDelay,
|
||||
PollingDuration: autorest.DefaultPollingDuration,
|
||||
RetryAttempts: autorest.DefaultRetryAttempts,
|
||||
RetryDuration: autorest.DefaultRetryDuration,
|
||||
Sender: sender,
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
time.Sleep(2 * time.Second)
|
||||
cancel()
|
||||
}()
|
||||
|
||||
err := future.WaitForCompletion(ctx, client)
|
||||
if err == nil {
|
||||
t.Fatalf("azure: WaitForCompletion returned nil error, should have been cancelled")
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
operationResourceIllegal = `
|
||||
This is not JSON and should fail...badly.
|
||||
@@ -1085,8 +1296,8 @@ func newAsynchronousResponse() *http.Response {
|
||||
return r
|
||||
}
|
||||
|
||||
func newAsynchronousResponseWithError() *http.Response {
|
||||
r := mocks.NewResponseWithStatus("400 Bad Request", http.StatusBadRequest)
|
||||
func newAsynchronousResponseWithError(response string, status int) *http.Response {
|
||||
r := mocks.NewResponseWithStatus(response, status)
|
||||
mocks.SetRetryHeader(r, retryDelay)
|
||||
r.Request = mocks.NewRequestForURL(mocks.TestURL)
|
||||
r.Body = mocks.NewBody(errorResponse)
|
||||
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
// 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.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode/utf16"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/dimchansky/utfbom"
|
||||
)
|
||||
|
||||
// ClientSetup includes authentication details and cloud specific
|
||||
// parameters for ARM clients
|
||||
type ClientSetup struct {
|
||||
*autorest.BearerAuthorizer
|
||||
File
|
||||
BaseURI string
|
||||
}
|
||||
|
||||
// File represents the authentication file
|
||||
type File struct {
|
||||
ClientID string `json:"clientId,omitempty"`
|
||||
ClientSecret string `json:"clientSecret,omitempty"`
|
||||
SubscriptionID string `json:"subscriptionId,omitempty"`
|
||||
TenantID string `json:"tenantId,omitempty"`
|
||||
ActiveDirectoryEndpoint string `json:"activeDirectoryEndpointUrl,omitempty"`
|
||||
ResourceManagerEndpoint string `json:"resourceManagerEndpointUrl,omitempty"`
|
||||
GraphResourceID string `json:"activeDirectoryGraphResourceId,omitempty"`
|
||||
SQLManagementEndpoint string `json:"sqlManagementEndpointUrl,omitempty"`
|
||||
GalleryEndpoint string `json:"galleryEndpointUrl,omitempty"`
|
||||
ManagementEndpoint string `json:"managementEndpointUrl,omitempty"`
|
||||
}
|
||||
|
||||
// GetClientSetup provides an authorizer, base URI, subscriptionID and
|
||||
// tenantID parameters from an Azure CLI auth file
|
||||
func GetClientSetup(baseURI string) (auth ClientSetup, err error) {
|
||||
fileLocation := os.Getenv("AZURE_AUTH_LOCATION")
|
||||
if fileLocation == "" {
|
||||
return auth, errors.New("auth file not found. Environment variable AZURE_AUTH_LOCATION is not set")
|
||||
}
|
||||
|
||||
contents, err := ioutil.ReadFile(fileLocation)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Auth file might be encoded
|
||||
decoded, err := decode(contents)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = json.Unmarshal(decoded, &auth.File)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
resource, err := getResourceForToken(auth.File, baseURI)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
auth.BaseURI = resource
|
||||
|
||||
config, err := adal.NewOAuthConfig(auth.ActiveDirectoryEndpoint, auth.TenantID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
spToken, err := adal.NewServicePrincipalToken(*config, auth.ClientID, auth.ClientSecret, resource)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
auth.BearerAuthorizer = autorest.NewBearerAuthorizer(spToken)
|
||||
return
|
||||
}
|
||||
|
||||
func decode(b []byte) ([]byte, error) {
|
||||
reader, enc := utfbom.Skip(bytes.NewReader(b))
|
||||
|
||||
switch enc {
|
||||
case utfbom.UTF16LittleEndian:
|
||||
u16 := make([]uint16, (len(b)/2)-1)
|
||||
err := binary.Read(reader, binary.LittleEndian, &u16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(string(utf16.Decode(u16))), nil
|
||||
case utfbom.UTF16BigEndian:
|
||||
u16 := make([]uint16, (len(b)/2)-1)
|
||||
err := binary.Read(reader, binary.BigEndian, &u16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(string(utf16.Decode(u16))), nil
|
||||
}
|
||||
return ioutil.ReadAll(reader)
|
||||
}
|
||||
|
||||
func getResourceForToken(f File, baseURI string) (string, error) {
|
||||
// Compare dafault base URI from the SDK to the endpoints from the public cloud
|
||||
// Base URI and token resource are the same string. This func finds the authentication
|
||||
// file field that matches the SDK base URI. The SDK defines the public cloud
|
||||
// endpoint as its default base URI
|
||||
if !strings.HasSuffix(baseURI, "/") {
|
||||
baseURI += "/"
|
||||
}
|
||||
switch baseURI {
|
||||
case azure.PublicCloud.ServiceManagementEndpoint:
|
||||
return f.ManagementEndpoint, nil
|
||||
case azure.PublicCloud.ResourceManagerEndpoint:
|
||||
return f.ResourceManagerEndpoint, nil
|
||||
case azure.PublicCloud.ActiveDirectoryEndpoint:
|
||||
return f.ActiveDirectoryEndpoint, nil
|
||||
case azure.PublicCloud.GalleryEndpoint:
|
||||
return f.GalleryEndpoint, nil
|
||||
case azure.PublicCloud.GraphEndpoint:
|
||||
return f.GraphResourceID, nil
|
||||
}
|
||||
return "", fmt.Errorf("auth: base URI not found in endpoints")
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
// 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.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
expectedFile = File{
|
||||
ClientID: "client-id-123",
|
||||
ClientSecret: "client-secret-456",
|
||||
SubscriptionID: "sub-id-789",
|
||||
TenantID: "tenant-id-123",
|
||||
ActiveDirectoryEndpoint: "https://login.microsoftonline.com",
|
||||
ResourceManagerEndpoint: "https://management.azure.com/",
|
||||
GraphResourceID: "https://graph.windows.net/",
|
||||
SQLManagementEndpoint: "https://management.core.windows.net:8443/",
|
||||
GalleryEndpoint: "https://gallery.azure.com/",
|
||||
ManagementEndpoint: "https://management.core.windows.net/",
|
||||
}
|
||||
)
|
||||
|
||||
func TestGetClientSetup(t *testing.T) {
|
||||
os.Setenv("AZURE_AUTH_LOCATION", filepath.Join(getCredsPath(), "credsutf16le.json"))
|
||||
setup, err := GetClientSetup("https://management.azure.com")
|
||||
if err != nil {
|
||||
t.Logf("GetClientSetup failed, got error %v", err)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if setup.BaseURI != "https://management.azure.com/" {
|
||||
t.Logf("auth.BaseURI not set correctly, expected 'https://management.azure.com/', got '%s'", setup.BaseURI)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(expectedFile, setup.File) {
|
||||
t.Logf("auth.File not set correctly, expected %v, got %v", expectedFile, setup.File)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if setup.BearerAuthorizer == nil {
|
||||
t.Log("auth.Authorizer not set correctly, got nil")
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeAndUnmarshal(t *testing.T) {
|
||||
tests := []string{
|
||||
"credsutf8.json",
|
||||
"credsutf16le.json",
|
||||
"credsutf16be.json",
|
||||
}
|
||||
creds := getCredsPath()
|
||||
for _, test := range tests {
|
||||
b, err := ioutil.ReadFile(filepath.Join(creds, test))
|
||||
if err != nil {
|
||||
t.Logf("error reading file '%s': %s", test, err)
|
||||
t.Fail()
|
||||
}
|
||||
decoded, err := decode(b)
|
||||
if err != nil {
|
||||
t.Logf("error decoding file '%s': %s", test, err)
|
||||
t.Fail()
|
||||
}
|
||||
var got File
|
||||
err = json.Unmarshal(decoded, &got)
|
||||
if err != nil {
|
||||
t.Logf("error unmarshaling file '%s': %s", test, err)
|
||||
t.Fail()
|
||||
}
|
||||
if !reflect.DeepEqual(expectedFile, got) {
|
||||
t.Logf("unmarshaled map expected %v, got %v", expectedFile, got)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getCredsPath() string {
|
||||
gopath := os.Getenv("GOPATH")
|
||||
return filepath.Join(gopath, "src", "github.com", "Azure", "go-autorest", "testdata")
|
||||
}
|
||||
|
||||
func areMapsEqual(a, b map[string]string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for k := range a {
|
||||
if a[k] != b[k] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
+21
-1
@@ -5,6 +5,20 @@ 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"
|
||||
@@ -165,7 +179,13 @@ func WithErrorUnlessStatusCode(codes ...int) autorest.RespondDecorator {
|
||||
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"}
|
||||
// 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)
|
||||
|
||||
+82
@@ -1,5 +1,19 @@
|
||||
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"
|
||||
@@ -333,6 +347,74 @@ func TestWithErrorUnlessStatusCode_NoAzureError(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestWithErrorUnlessStatusCode_UnwrappedError(t *testing.T) {
|
||||
j := `{
|
||||
"target": null,
|
||||
"code": "InternalError",
|
||||
"message": "Azure is having trouble right now.",
|
||||
"details": [{"code": "conflict1", "message":"error message1"},
|
||||
{"code": "conflict2", "message":"error message2"}],
|
||||
"innererror": []
|
||||
}`
|
||||
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.Fatal("azure: returned nil error for proper error response")
|
||||
}
|
||||
|
||||
azErr, ok := err.(*RequestError)
|
||||
if !ok {
|
||||
t.Fatalf("returned error is not azure.RequestError: %T", err)
|
||||
}
|
||||
|
||||
if expected := http.StatusInternalServerError; azErr.StatusCode != expected {
|
||||
t.Logf("Incorrect StatusCode got: %v want: %d", azErr.StatusCode, expected)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if expected := "Azure is having trouble right now."; azErr.ServiceError.Message != expected {
|
||||
t.Logf("Incorrect Message\n\tgot: %q\n\twant: %q", azErr.Message, expected)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if expected := uuid; azErr.RequestID != expected {
|
||||
t.Logf("Incorrect request ID\n\tgot: %q\n\twant: %q", azErr.RequestID, expected)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
expectedServiceErrorDetails := `[{"code":"conflict1","message":"error message1"},{"code":"conflict2","message":"error message2"}]`
|
||||
if azErr.ServiceError == nil {
|
||||
t.Logf("`ServiceError` was nil when it shouldn't have been.")
|
||||
t.Fail()
|
||||
} else if azErr.ServiceError.Details == nil {
|
||||
t.Logf("`ServiceError.Details` was nil when it should have been %q", expectedServiceErrorDetails)
|
||||
t.Fail()
|
||||
} else if details, _ := json.Marshal(*azErr.ServiceError.Details); expectedServiceErrorDetails != string(details) {
|
||||
t.Logf("Error detaisl was not unmarshaled properly.\n\tgot: %q\n\twant: %q", string(details), expectedServiceErrorDetails)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
// the error body should still be there
|
||||
defer r.Body.Close()
|
||||
b, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
t.Error(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": {
|
||||
|
||||
+14
@@ -1,5 +1,19 @@
|
||||
package cli
|
||||
|
||||
// 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 (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
|
||||
+26
-1
@@ -1,5 +1,19 @@
|
||||
package cli
|
||||
|
||||
// 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"
|
||||
@@ -48,8 +62,19 @@ func (t Token) ToADALToken() (converted adal.Token, err error) {
|
||||
}
|
||||
|
||||
// AccessTokensPath returns the path where access tokens are stored from the Azure CLI
|
||||
// TODO(#199): add unit test.
|
||||
func AccessTokensPath() (string, error) {
|
||||
return homedir.Expand("~/.azure/accessTokens.json")
|
||||
// Azure-CLI allows user to customize the path of access tokens thorugh environment variable.
|
||||
var accessTokenPath = os.Getenv("AZURE_ACCESS_TOKEN_FILE")
|
||||
var err error
|
||||
|
||||
// Fallback logic to default path on non-cloud-shell environment.
|
||||
// TODO(#200): remove the dependency on hard-coding path.
|
||||
if accessTokenPath == "" {
|
||||
accessTokenPath, err = homedir.Expand("~/.azure/accessTokens.json")
|
||||
}
|
||||
|
||||
return accessTokenPath, err
|
||||
}
|
||||
|
||||
// ParseExpirationDate parses either a Azure CLI or CloudShell date into a time object
|
||||
|
||||
+49
-3
@@ -1,10 +1,31 @@
|
||||
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"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// EnvironmentFilepathName captures the name of the environment variable containing the path to the file
|
||||
// to be used while populating the Azure Environment.
|
||||
const EnvironmentFilepathName = "AZURE_ENVIRONMENT_FILEPATH"
|
||||
|
||||
var environments = map[string]Environment{
|
||||
"AZURECHINACLOUD": ChinaCloud,
|
||||
"AZUREGERMANCLOUD": GermanCloud,
|
||||
@@ -62,10 +83,10 @@ var (
|
||||
PublishSettingsURL: "https://manage.windowsazure.us/publishsettings/index",
|
||||
ServiceManagementEndpoint: "https://management.core.usgovcloudapi.net/",
|
||||
ResourceManagerEndpoint: "https://management.usgovcloudapi.net/",
|
||||
ActiveDirectoryEndpoint: "https://login.microsoftonline.com/",
|
||||
ActiveDirectoryEndpoint: "https://login.microsoftonline.us/",
|
||||
GalleryEndpoint: "https://gallery.usgovcloudapi.net/",
|
||||
KeyVaultEndpoint: "https://vault.usgovcloudapi.net/",
|
||||
GraphEndpoint: "https://graph.usgovcloudapi.net/",
|
||||
GraphEndpoint: "https://graph.windows.net/",
|
||||
StorageEndpointSuffix: "core.usgovcloudapi.net",
|
||||
SQLDatabaseDNSSuffix: "database.usgovcloudapi.net",
|
||||
TrafficManagerDNSSuffix: "usgovtrafficmanager.net",
|
||||
@@ -119,12 +140,37 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
// EnvironmentFromName returns an Environment based on the common name specified
|
||||
// EnvironmentFromName returns an Environment based on the common name specified.
|
||||
func EnvironmentFromName(name string) (Environment, error) {
|
||||
// IMPORTANT
|
||||
// As per @radhikagupta5:
|
||||
// This is technical debt, fundamentally here because Kubernetes is not currently accepting
|
||||
// contributions to the providers. Once that is an option, the provider should be updated to
|
||||
// directly call `EnvironmentFromFile`. Until then, we rely on dispatching Azure Stack environment creation
|
||||
// from this method based on the name that is provided to us.
|
||||
if strings.EqualFold(name, "AZURESTACKCLOUD") {
|
||||
return EnvironmentFromFile(os.Getenv(EnvironmentFilepathName))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// EnvironmentFromFile loads an Environment from a configuration file available on disk.
|
||||
// This function is particularly useful in the Hybrid Cloud model, where one must define their own
|
||||
// endpoints.
|
||||
func EnvironmentFromFile(location string) (unmarshaled Environment, err error) {
|
||||
fileContents, err := ioutil.ReadFile(location)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = json.Unmarshal(fileContents, &unmarshaled)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
+68
@@ -1,11 +1,79 @@
|
||||
// test
|
||||
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"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// This correlates to the expected contents of ./testdata/test_environment_1.json
|
||||
var testEnvironment1 = 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--",
|
||||
}
|
||||
|
||||
func TestEnvironment_EnvironmentFromFile(t *testing.T) {
|
||||
got, err := EnvironmentFromFile(filepath.Join("testdata", "test_environment_1.json"))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if got != testEnvironment1 {
|
||||
t.Logf("got: %v want: %v", got, testEnvironment1)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvironment_EnvironmentFromName_Stack(t *testing.T) {
|
||||
_, currentFile, _, _ := runtime.Caller(0)
|
||||
prevEnvFilepathValue := os.Getenv(EnvironmentFilepathName)
|
||||
os.Setenv(EnvironmentFilepathName, filepath.Join(path.Dir(currentFile), "testdata", "test_environment_1.json"))
|
||||
defer os.Setenv(EnvironmentFilepathName, prevEnvFilepathValue)
|
||||
|
||||
got, err := EnvironmentFromName("AZURESTACKCLOUD")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if got != testEnvironment1 {
|
||||
t.Logf("got: %v want: %v", got, testEnvironment1)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvironmentFromName(t *testing.T) {
|
||||
name := "azurechinacloud"
|
||||
if env, _ := EnvironmentFromName(name); env != ChinaCloud {
|
||||
|
||||
+14
@@ -1,5 +1,19 @@
|
||||
package main
|
||||
|
||||
// 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 (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
// 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.
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
)
|
||||
|
||||
// DoRetryWithRegistration tries to register the resource provider in case it is unregistered.
|
||||
// It also handles request retries
|
||||
func DoRetryWithRegistration(client autorest.Client) autorest.SendDecorator {
|
||||
return func(s autorest.Sender) autorest.Sender {
|
||||
return autorest.SenderFunc(func(r *http.Request) (resp *http.Response, err error) {
|
||||
rr := autorest.NewRetriableRequest(r)
|
||||
for currentAttempt := 0; currentAttempt < client.RetryAttempts; currentAttempt++ {
|
||||
err = rr.Prepare()
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
resp, err = autorest.SendWithSender(s, rr.Request(),
|
||||
autorest.DoRetryForStatusCodes(client.RetryAttempts, client.RetryDuration, autorest.StatusCodesForRetry...),
|
||||
)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusConflict || client.SkipResourceProviderRegistration {
|
||||
return resp, err
|
||||
}
|
||||
var re RequestError
|
||||
err = autorest.Respond(
|
||||
resp,
|
||||
autorest.ByUnmarshallingJSON(&re),
|
||||
)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
err = re
|
||||
|
||||
if re.ServiceError != nil && re.ServiceError.Code == "MissingSubscriptionRegistration" {
|
||||
regErr := register(client, r, re)
|
||||
if regErr != nil {
|
||||
return resp, fmt.Errorf("failed auto registering Resource Provider: %s. Original error: %s", regErr, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return resp, fmt.Errorf("failed request: %s", err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getProvider(re RequestError) (string, error) {
|
||||
if re.ServiceError != nil {
|
||||
if re.ServiceError.Details != nil && len(*re.ServiceError.Details) > 0 {
|
||||
detail := (*re.ServiceError.Details)[0].(map[string]interface{})
|
||||
return detail["target"].(string), nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("provider was not found in the response")
|
||||
}
|
||||
|
||||
func register(client autorest.Client, originalReq *http.Request, re RequestError) error {
|
||||
subID := getSubscription(originalReq.URL.Path)
|
||||
if subID == "" {
|
||||
return errors.New("missing parameter subscriptionID to register resource provider")
|
||||
}
|
||||
providerName, err := getProvider(re)
|
||||
if err != nil {
|
||||
return fmt.Errorf("missing parameter provider to register resource provider: %s", err)
|
||||
}
|
||||
newURL := url.URL{
|
||||
Scheme: originalReq.URL.Scheme,
|
||||
Host: originalReq.URL.Host,
|
||||
}
|
||||
|
||||
// taken from the resources SDK
|
||||
// with almost identical code, this sections are easier to mantain
|
||||
// It is also not a good idea to import the SDK here
|
||||
// https://github.com/Azure/azure-sdk-for-go/blob/9f366792afa3e0ddaecdc860e793ba9d75e76c27/arm/resources/resources/providers.go#L252
|
||||
pathParameters := map[string]interface{}{
|
||||
"resourceProviderNamespace": autorest.Encode("path", providerName),
|
||||
"subscriptionId": autorest.Encode("path", subID),
|
||||
}
|
||||
|
||||
const APIVersion = "2016-09-01"
|
||||
queryParameters := map[string]interface{}{
|
||||
"api-version": APIVersion,
|
||||
}
|
||||
|
||||
preparer := autorest.CreatePreparer(
|
||||
autorest.AsPost(),
|
||||
autorest.WithBaseURL(newURL.String()),
|
||||
autorest.WithPathParameters("/subscriptions/{subscriptionId}/providers/{resourceProviderNamespace}/register", pathParameters),
|
||||
autorest.WithQueryParameters(queryParameters),
|
||||
)
|
||||
|
||||
req, err := preparer.Prepare(&http.Request{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Cancel = originalReq.Cancel
|
||||
|
||||
resp, err := autorest.SendWithSender(client, req,
|
||||
autorest.DoRetryForStatusCodes(client.RetryAttempts, client.RetryDuration, autorest.StatusCodesForRetry...),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
type Provider struct {
|
||||
RegistrationState *string `json:"registrationState,omitempty"`
|
||||
}
|
||||
var provider Provider
|
||||
|
||||
err = autorest.Respond(
|
||||
resp,
|
||||
WithErrorUnlessStatusCode(http.StatusOK),
|
||||
autorest.ByUnmarshallingJSON(&provider),
|
||||
autorest.ByClosing(),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// poll for registered provisioning state
|
||||
now := time.Now()
|
||||
for err == nil && time.Since(now) < client.PollingDuration {
|
||||
// taken from the resources SDK
|
||||
// https://github.com/Azure/azure-sdk-for-go/blob/9f366792afa3e0ddaecdc860e793ba9d75e76c27/arm/resources/resources/providers.go#L45
|
||||
preparer := autorest.CreatePreparer(
|
||||
autorest.AsGet(),
|
||||
autorest.WithBaseURL(newURL.String()),
|
||||
autorest.WithPathParameters("/subscriptions/{subscriptionId}/providers/{resourceProviderNamespace}", pathParameters),
|
||||
autorest.WithQueryParameters(queryParameters),
|
||||
)
|
||||
req, err = preparer.Prepare(&http.Request{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Cancel = originalReq.Cancel
|
||||
|
||||
resp, err := autorest.SendWithSender(client, req,
|
||||
autorest.DoRetryForStatusCodes(client.RetryAttempts, client.RetryDuration, autorest.StatusCodesForRetry...),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = autorest.Respond(
|
||||
resp,
|
||||
WithErrorUnlessStatusCode(http.StatusOK),
|
||||
autorest.ByUnmarshallingJSON(&provider),
|
||||
autorest.ByClosing(),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if provider.RegistrationState != nil &&
|
||||
*provider.RegistrationState == "Registered" {
|
||||
break
|
||||
}
|
||||
|
||||
delayed := autorest.DelayWithRetryAfter(resp, originalReq.Cancel)
|
||||
if !delayed {
|
||||
autorest.DelayForBackoff(client.PollingDelay, 0, originalReq.Cancel)
|
||||
}
|
||||
}
|
||||
if !(time.Since(now) < client.PollingDuration) {
|
||||
return errors.New("polling for resource provider registration has exceeded the polling duration")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func getSubscription(path string) string {
|
||||
parts := strings.Split(path, "/")
|
||||
for i, v := range parts {
|
||||
if v == "subscriptions" && (i+1) < len(parts) {
|
||||
return parts[i+1]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
// 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.
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/mocks"
|
||||
)
|
||||
|
||||
func TestDoRetryWithRegistration(t *testing.T) {
|
||||
client := mocks.NewSender()
|
||||
// first response, should retry because it is a transient error
|
||||
client.AppendResponse(mocks.NewResponseWithStatus("Internal server error", http.StatusInternalServerError))
|
||||
// response indicates the resource provider has not been registered
|
||||
client.AppendResponse(mocks.NewResponseWithBodyAndStatus(mocks.NewBody(`{
|
||||
"error":{
|
||||
"code":"MissingSubscriptionRegistration",
|
||||
"message":"The subscription registration is in 'Unregistered' state. The subscription must be registered to use namespace 'Microsoft.EventGrid'. See https://aka.ms/rps-not-found for how to register subscriptions.",
|
||||
"details":[
|
||||
{
|
||||
"code":"MissingSubscriptionRegistration",
|
||||
"target":"Microsoft.EventGrid",
|
||||
"message":"The subscription registration is in 'Unregistered' state. The subscription must be registered to use namespace 'Microsoft.EventGrid'. See https://aka.ms/rps-not-found for how to register subscriptions."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`), http.StatusConflict, "MissingSubscriptionRegistration"))
|
||||
// first poll response, still not ready
|
||||
client.AppendResponse(mocks.NewResponseWithBodyAndStatus(mocks.NewBody(`{
|
||||
"registrationState": "Registering"
|
||||
}
|
||||
`), http.StatusOK, "200 OK"))
|
||||
// last poll response, respurce provider has been registered
|
||||
client.AppendResponse(mocks.NewResponseWithBodyAndStatus(mocks.NewBody(`{
|
||||
"registrationState": "Registered"
|
||||
}
|
||||
`), http.StatusOK, "200 OK"))
|
||||
// retry original request, response is successful
|
||||
client.AppendResponse(mocks.NewResponseWithStatus("200 OK", http.StatusOK))
|
||||
|
||||
req := mocks.NewRequestForURL("https://lol/subscriptions/rofl")
|
||||
req.Body = mocks.NewBody("lolol")
|
||||
r, err := autorest.SendWithSender(client, req,
|
||||
DoRetryWithRegistration(autorest.Client{
|
||||
PollingDelay: time.Second,
|
||||
PollingDuration: time.Second * 10,
|
||||
RetryAttempts: 5,
|
||||
RetryDuration: time.Second,
|
||||
Sender: client,
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("got error: %v", err)
|
||||
}
|
||||
|
||||
autorest.Respond(r,
|
||||
autorest.ByDiscardingBody(),
|
||||
autorest.ByClosing(),
|
||||
)
|
||||
|
||||
if r.StatusCode != http.StatusOK {
|
||||
t.Fatalf("azure: Sender#DoRetryWithRegistration -- Got: StatusCode %v; Want: StatusCode 200 OK", r.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoRetrySkipRegistration(t *testing.T) {
|
||||
client := mocks.NewSender()
|
||||
// first response, should retry because it is a transient error
|
||||
client.AppendResponse(mocks.NewResponseWithStatus("Internal server error", http.StatusInternalServerError))
|
||||
// response indicates the resource provider has not been registered
|
||||
client.AppendResponse(mocks.NewResponseWithBodyAndStatus(mocks.NewBody(`{
|
||||
"error":{
|
||||
"code":"MissingSubscriptionRegistration",
|
||||
"message":"The subscription registration is in 'Unregistered' state. The subscription must be registered to use namespace 'Microsoft.EventGrid'. See https://aka.ms/rps-not-found for how to register subscriptions.",
|
||||
"details":[
|
||||
{
|
||||
"code":"MissingSubscriptionRegistration",
|
||||
"target":"Microsoft.EventGrid",
|
||||
"message":"The subscription registration is in 'Unregistered' state. The subscription must be registered to use namespace 'Microsoft.EventGrid'. See https://aka.ms/rps-not-found for how to register subscriptions."
|
||||
}
|
||||
]
|
||||
}
|
||||
}`), http.StatusConflict, "MissingSubscriptionRegistration"))
|
||||
|
||||
req := mocks.NewRequestForURL("https://lol/subscriptions/rofl")
|
||||
req.Body = mocks.NewBody("lolol")
|
||||
r, err := autorest.SendWithSender(client, req,
|
||||
DoRetryWithRegistration(autorest.Client{
|
||||
PollingDelay: time.Second,
|
||||
PollingDuration: time.Second * 10,
|
||||
RetryAttempts: 5,
|
||||
RetryDuration: time.Second,
|
||||
Sender: client,
|
||||
SkipResourceProviderRegistration: true,
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("got error: %v", err)
|
||||
}
|
||||
|
||||
autorest.Respond(r,
|
||||
autorest.ByDiscardingBody(),
|
||||
autorest.ByClosing(),
|
||||
)
|
||||
|
||||
if r.StatusCode != http.StatusConflict {
|
||||
t.Fatalf("azure: Sender#DoRetryWithRegistration -- Got: StatusCode %v; Want: StatusCode 409 Conflict", r.StatusCode)
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"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--"
|
||||
}
|
||||
Reference in New Issue
Block a user