vendor: update all dependencies to latest versions

This commit is contained in:
Nick Craig-Wood
2017-09-30 15:27:27 +01:00
parent 911d121bb9
commit b017fcfe9a
3048 changed files with 537055 additions and 189679 deletions
+2
View File
@@ -7,6 +7,8 @@
# Folders
_obj
_test
.DS_Store
.idea/
# Architecture specific extensions/prefixes
*.[568vq]
+3 -2
View File
@@ -2,9 +2,10 @@ sudo: false
language: go
go:
- 1.7
go:
- 1.8
- 1.7
- 1.6
install:
- go get -u github.com/golang/lint/golint
+23
View File
@@ -0,0 +1,23 @@
DIR?=./autorest/
default: build
build: fmt
go install $(DIR)
test:
go test $(DIR) || exit 1
vet:
@echo "go vet ."
@go vet $(DIR)... ; if [ $$? -eq 1 ]; then \
echo ""; \
echo "Vet found suspicious constructs. Please check the reported constructs"; \
echo "and fix them if necessary before submitting the code for review."; \
exit 1; \
fi
fmt:
gofmt -w $(DIR)
.PHONY: build test vet fmt
+6
View File
@@ -0,0 +1,6 @@
// +build !windows
package adal
// msiPath is the path to the MSI Extension settings file (to discover the endpoint)
var msiPath = "/var/lib/waagent/ManagedIdentity-Settings"
+11
View File
@@ -0,0 +1,11 @@
// +build windows
package adal
import (
"os"
"strings"
)
// msiPath is the path to the MSI Extension settings file (to discover the endpoint)
var msiPath = strings.Join([]string{os.Getenv("SystemDrive"), "WindowsAzure/Config/ManagedIdentity-Settings"}, "/")
+32 -27
View File
@@ -15,12 +15,12 @@ import (
"strings"
"time"
"github.com/Azure/go-autorest/autorest/date"
"github.com/dgrijalva/jwt-go"
)
const (
defaultRefresh = 5 * time.Minute
tokenBaseDate = "1970-01-01T00:00:00Z"
// OAuthGrantTypeDeviceCode is the "grant_type" identifier used in device flow
OAuthGrantTypeDeviceCode = "device_code"
@@ -31,16 +31,10 @@ const (
// OAuthGrantTypeRefreshToken is the "grant_type" identifier used in refresh token flows
OAuthGrantTypeRefreshToken = "refresh_token"
// managedIdentitySettingsPath is the path to the MSI Extension settings file (to discover the endpoint)
managedIdentitySettingsPath = "/var/lib/waagent/ManagedIdentity-Settings"
// metadataHeader is the header required by MSI extension
metadataHeader = "Metadata"
)
var expirationBase time.Time
func init() {
expirationBase, _ = time.Parse(time.RFC3339, tokenBaseDate)
}
// OAuthTokenProvider is an interface which should be implemented by an access token retriever
type OAuthTokenProvider interface {
OAuthToken() string
@@ -76,7 +70,10 @@ func (t Token) Expires() time.Time {
if err != nil {
s = -3600
}
return expirationBase.Add(time.Duration(s) * time.Second).UTC()
expiration := date.NewUnixTimeFromSeconds(float64(s))
return time.Time(expiration).UTC()
}
// IsExpired returns true if the Token is expired, false otherwise.
@@ -135,9 +132,7 @@ type ServicePrincipalMSISecret struct {
}
// SetAuthenticationValues is a method of the interface ServicePrincipalSecret.
// MSI extension requires the authority field to be set to the real tenant authority endpoint
func (msiSecret *ServicePrincipalMSISecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error {
v.Set("authority", spt.oauthConfig.AuthorityEndpoint.String())
return nil
}
@@ -261,41 +256,43 @@ func NewServicePrincipalTokenFromCertificate(oauthConfig OAuthConfig, clientID s
)
}
// NewServicePrincipalTokenFromMSI creates a ServicePrincipalToken via the MSI VM Extension.
func NewServicePrincipalTokenFromMSI(oauthConfig OAuthConfig, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
return newServicePrincipalTokenFromMSI(oauthConfig, resource, managedIdentitySettingsPath, callbacks...)
// GetMSIVMEndpoint gets the MSI endpoint on Virtual Machines.
func GetMSIVMEndpoint() (string, error) {
return getMSIVMEndpoint(msiPath)
}
func newServicePrincipalTokenFromMSI(oauthConfig OAuthConfig, resource, settingsPath string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
func getMSIVMEndpoint(path string) (string, error) {
// Read MSI settings
bytes, err := ioutil.ReadFile(settingsPath)
bytes, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
return "", err
}
msiSettings := struct {
URL string `json:"url"`
}{}
err = json.Unmarshal(bytes, &msiSettings)
if err != nil {
return nil, err
return "", err
}
return msiSettings.URL, nil
}
// NewServicePrincipalTokenFromMSI creates a ServicePrincipalToken via the MSI VM Extension.
func NewServicePrincipalTokenFromMSI(msiEndpoint, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
// We set the oauth config token endpoint to be MSI's endpoint
// We leave the authority as-is so MSI can POST it with the token request
msiEndpointURL, err := url.Parse(msiSettings.URL)
msiEndpointURL, err := url.Parse(msiEndpoint)
if err != nil {
return nil, err
}
msiTokenEndpointURL, err := msiEndpointURL.Parse("/oauth2/token")
oauthConfig, err := NewOAuthConfig(msiEndpointURL.String(), "")
if err != nil {
return nil, err
}
oauthConfig.TokenEndpoint = *msiTokenEndpointURL
spt := &ServicePrincipalToken{
oauthConfig: oauthConfig,
oauthConfig: *oauthConfig,
secret: &ServicePrincipalMSISecret{},
resource: resource,
autoRefresh: true,
@@ -364,16 +361,24 @@ func (spt *ServicePrincipalToken) refreshInternal(resource string) error {
req.ContentLength = int64(len(s))
req.Header.Set(contentType, mimeTypeFormPost)
if _, ok := spt.secret.(*ServicePrincipalMSISecret); ok {
req.Header.Set(metadataHeader, "true")
}
resp, err := spt.sender.Do(req)
if err != nil {
return fmt.Errorf("adal: Failed to execute the refresh request. Error = '%v'", err)
}
defer resp.Body.Close()
rb, err := ioutil.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("adal: Refresh request failed. Status Code = '%d'", resp.StatusCode)
if err != nil {
return fmt.Errorf("adal: Refresh request failed. Status Code = '%d'. Failed reading response body", resp.StatusCode)
}
return fmt.Errorf("adal: Refresh request failed. Status Code = '%d'. Response body: %s", resp.StatusCode, string(rb))
}
rb, err := ioutil.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("adal: Failed to read a new service principal token during refresh. Error = '%v'", err)
}
+69 -28
View File
@@ -17,6 +17,7 @@ import (
"testing"
"time"
"github.com/Azure/go-autorest/autorest/date"
"github.com/Azure/go-autorest/autorest/mocks"
)
@@ -133,6 +134,44 @@ func TestServicePrincipalTokenRefreshUsesPOST(t *testing.T) {
}
}
func TestServicePrincipalTokenFromMSIRefreshUsesPOST(t *testing.T) {
resource := "https://resource"
cb := func(token Token) error { return nil }
spt, err := NewServicePrincipalTokenFromMSI("http://msiendpoint/", resource, cb)
if err != nil {
t.Fatalf("Failed to get MSI SPT: %v", err)
}
body := mocks.NewBody(newTokenJSON("test", "test"))
resp := mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK")
c := mocks.NewSender()
s := DecorateSender(c,
(func() SendDecorator {
return func(s Sender) Sender {
return SenderFunc(func(r *http.Request) (*http.Response, error) {
if r.Method != "POST" {
t.Fatalf("adal: ServicePrincipalToken#Refresh did not correctly set HTTP method -- expected %v, received %v", "POST", r.Method)
}
if h := r.Header.Get("Metadata"); h != "true" {
t.Fatalf("adal: ServicePrincipalToken#Refresh did not correctly set Metadata header for MSI")
}
return resp, nil
})
}
})())
spt.SetSender(s)
err = spt.Refresh()
if err != nil {
t.Fatalf("adal: ServicePrincipalToken#Refresh returned an unexpected error (%v)", err)
}
if body.IsOpen() {
t.Fatalf("the response was not closed!")
}
}
func TestServicePrincipalTokenRefreshSetsMimeType(t *testing.T) {
spt := newServicePrincipalToken()
@@ -342,7 +381,7 @@ func TestServicePrincipalTokenRefreshReturnsErrorIfNotOk(t *testing.T) {
func TestServicePrincipalTokenRefreshUnmarshals(t *testing.T) {
spt := newServicePrincipalToken()
expiresOn := strconv.Itoa(int(time.Now().Add(3600 * time.Second).Sub(expirationBase).Seconds()))
expiresOn := strconv.Itoa(int(time.Now().Add(3600 * time.Second).Sub(date.UnixEpoch()).Seconds()))
j := newTokenJSON(expiresOn, "resource")
resp := mocks.NewResponseWithContent(j)
c := mocks.NewSender()
@@ -430,7 +469,7 @@ func TestRefreshCallback(t *testing.T) {
return nil
})
expiresOn := strconv.Itoa(int(time.Now().Add(3600 * time.Second).Sub(expirationBase).Seconds()))
expiresOn := strconv.Itoa(int(time.Now().Add(3600 * time.Second).Sub(date.UnixEpoch()).Seconds()))
sender := mocks.NewSender()
j := newTokenJSON(expiresOn, "resource")
@@ -451,7 +490,7 @@ func TestRefreshCallbackErrorPropagates(t *testing.T) {
return fmt.Errorf(errorText)
})
expiresOn := strconv.Itoa(int(time.Now().Add(3600 * time.Second).Sub(expirationBase).Seconds()))
expiresOn := strconv.Itoa(int(time.Now().Add(3600 * time.Second).Sub(date.UnixEpoch()).Seconds()))
sender := mocks.NewSender()
j := newTokenJSON(expiresOn, "resource")
@@ -476,32 +515,9 @@ func TestServicePrincipalTokenManualRefreshFailsWithoutRefresh(t *testing.T) {
func TestNewServicePrincipalTokenFromMSI(t *testing.T) {
resource := "https://resource"
cb := func(token Token) error { return nil }
tempSettingsFile, err := ioutil.TempFile("", "ManagedIdentity-Settings")
if err != nil {
t.Fatal("Couldn't write temp settings file")
}
defer os.Remove(tempSettingsFile.Name())
settingsContents := []byte(`{
"url": "http://msiendpoint/"
}`)
if _, err := tempSettingsFile.Write(settingsContents); err != nil {
t.Fatal("Couldn't fill temp settings file")
}
oauthConfig, err := NewOAuthConfig("http://adendpoint", "1-2-3-4")
if err != nil {
t.Fatal("Failed to construct oauthconfig")
}
spt, err := newServicePrincipalTokenFromMSI(
*oauthConfig,
resource,
tempSettingsFile.Name(),
cb)
spt, err := NewServicePrincipalTokenFromMSI("http://msiendpoint/", resource, cb)
if err != nil {
t.Fatalf("Failed to get MSI SPT: %v", err)
}
@@ -520,6 +536,31 @@ func TestNewServicePrincipalTokenFromMSI(t *testing.T) {
}
}
func TestGetVMEndpoint(t *testing.T) {
tempSettingsFile, err := ioutil.TempFile("", "ManagedIdentity-Settings")
if err != nil {
t.Fatal("Couldn't write temp settings file")
}
defer os.Remove(tempSettingsFile.Name())
settingsContents := []byte(`{
"url": "http://msiendpoint/"
}`)
if _, err := tempSettingsFile.Write(settingsContents); err != nil {
t.Fatal("Couldn't fill temp settings file")
}
endpoint, err := getMSIVMEndpoint(tempSettingsFile.Name())
if err != nil {
t.Fatal("Coudn't get VM endpoint")
}
if endpoint != "http://msiendpoint/" {
t.Fatal("Didn't get correct endpoint")
}
}
func newToken() *Token {
return &Token{
AccessToken: "ASECRETVALUE",
@@ -554,7 +595,7 @@ func expireToken(t *Token) *Token {
func setTokenToExpireAt(t *Token, expireAt time.Time) *Token {
t.ExpiresIn = "3600"
t.ExpiresOn = strconv.Itoa(int(expireAt.Sub(expirationBase).Seconds()))
t.ExpiresOn = strconv.Itoa(int(expireAt.Sub(date.UnixEpoch()).Seconds()))
t.NotBefore = t.ExpiresOn
return t
}
+110
View File
@@ -3,10 +3,18 @@ package autorest
import (
"fmt"
"net/http"
"net/url"
"strings"
"github.com/Azure/go-autorest/autorest/adal"
)
const (
bearerChallengeHeader = "Www-Authenticate"
bearer = "Bearer"
tenantID = "tenantID"
)
// Authorizer is the interface that provides a PrepareDecorator used to supply request
// authorization. Most often, the Authorizer decorator runs last so it has access to the full
// state of the formed HTTP request.
@@ -55,3 +63,105 @@ func (ba *BearerAuthorizer) WithAuthorization() PrepareDecorator {
})
}
}
// BearerAuthorizerCallbackFunc is the authentication callback signature.
type BearerAuthorizerCallbackFunc func(tenantID, resource string) (*BearerAuthorizer, error)
// BearerAuthorizerCallback implements bearer authorization via a callback.
type BearerAuthorizerCallback struct {
sender Sender
callback BearerAuthorizerCallbackFunc
}
// NewBearerAuthorizerCallback creates a bearer authorization callback. The callback
// is invoked when the HTTP request is submitted.
func NewBearerAuthorizerCallback(sender Sender, callback BearerAuthorizerCallbackFunc) *BearerAuthorizerCallback {
if sender == nil {
sender = &http.Client{}
}
return &BearerAuthorizerCallback{sender: sender, callback: callback}
}
// WithAuthorization returns a PrepareDecorator that adds an HTTP Authorization header whose value
// is "Bearer " followed by the token. The BearerAuthorizer is obtained via a user-supplied callback.
//
// By default, the token will be automatically refreshed through the Refresher interface.
func (bacb *BearerAuthorizerCallback) WithAuthorization() PrepareDecorator {
return func(p Preparer) Preparer {
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
// make a copy of the request and remove the body as it's not
// required and avoids us having to create a copy of it.
rCopy := *r
removeRequestBody(&rCopy)
resp, err := bacb.sender.Do(&rCopy)
if err == nil && resp.StatusCode == 401 {
defer resp.Body.Close()
if hasBearerChallenge(resp) {
bc, err := newBearerChallenge(resp)
if err != nil {
return r, err
}
if bacb.callback != nil {
ba, err := bacb.callback(bc.values[tenantID], bc.values["resource"])
if err != nil {
return r, err
}
return ba.WithAuthorization()(p).Prepare(r)
}
}
}
return r, err
})
}
}
// returns true if the HTTP response contains a bearer challenge
func hasBearerChallenge(resp *http.Response) bool {
authHeader := resp.Header.Get(bearerChallengeHeader)
if len(authHeader) == 0 || strings.Index(authHeader, bearer) < 0 {
return false
}
return true
}
type bearerChallenge struct {
values map[string]string
}
func newBearerChallenge(resp *http.Response) (bc bearerChallenge, err error) {
challenge := strings.TrimSpace(resp.Header.Get(bearerChallengeHeader))
trimmedChallenge := challenge[len(bearer)+1:]
// challenge is a set of key=value pairs that are comma delimited
pairs := strings.Split(trimmedChallenge, ",")
if len(pairs) < 1 {
err = fmt.Errorf("challenge '%s' contains no pairs", challenge)
return bc, err
}
bc.values = make(map[string]string)
for i := range pairs {
trimmedPair := strings.TrimSpace(pairs[i])
pair := strings.Split(trimmedPair, "=")
if len(pair) == 2 {
// remove the enclosing quotes
key := strings.Trim(pair[0], "\"")
value := strings.Trim(pair[1], "\"")
switch key {
case "authorization", "authorization_uri":
// strip the tenant ID from the authorization URL
asURL, err := url.Parse(value)
if err != nil {
return bc, err
}
bc.values[tenantID] = asURL.Path[1:]
default:
bc.values[key] = value
}
}
}
return bc, err
}
+37
View File
@@ -135,3 +135,40 @@ func TestServicePrincipalTokenWithAuthorizationReturnsErrorIfConnotRefresh(t *te
t.Fatal("azure: BearerAuthorizer#WithAuthorization failed to return an error when refresh fails")
}
}
func TestBearerAuthorizerCallback(t *testing.T) {
tenantString := "123-tenantID-456"
resourceString := "https://fake.resource.net"
s := mocks.NewSender()
resp := mocks.NewResponseWithStatus("401 Unauthorized", http.StatusUnauthorized)
mocks.SetResponseHeader(resp, bearerChallengeHeader, bearer+" \"authorization\"=\"https://fake.net/"+tenantString+"\",\"resource\"=\""+resourceString+"\"")
s.AppendResponse(resp)
auth := NewBearerAuthorizerCallback(s, func(tenantID, resource string) (*BearerAuthorizer, error) {
if tenantID != tenantString {
t.Fatal("BearerAuthorizerCallback: bad tenant ID")
}
if resource != resourceString {
t.Fatal("BearerAuthorizerCallback: bad resource")
}
oauthConfig, err := adal.NewOAuthConfig(TestActiveDirectoryEndpoint, tenantID)
if err != nil {
t.Fatalf("azure: NewOAuthConfig returned an error (%v)", err)
}
spt, err := adal.NewServicePrincipalToken(*oauthConfig, "id", "secret", resource)
if err != nil {
t.Fatalf("azure: NewServicePrincipalToken returned an error (%v)", err)
}
spt.SetSender(s)
return NewBearerAuthorizer(spt), nil
})
_, err := Prepare(mocks.NewRequest(), auth.WithAuthorization())
if err == nil {
t.Fatal("azure: BearerAuthorizerCallback#WithAuthorization failed to return an error when refresh fails")
}
}
+51
View File
@@ -0,0 +1,51 @@
package cli
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"github.com/dimchansky/utfbom"
"github.com/mitchellh/go-homedir"
)
// Profile represents a Profile from the Azure CLI
type Profile struct {
InstallationID string `json:"installationId"`
Subscriptions []Subscription `json:"subscriptions"`
}
// Subscription represents a Subscription from the Azure CLI
type Subscription struct {
EnvironmentName string `json:"environmentName"`
ID string `json:"id"`
IsDefault bool `json:"isDefault"`
Name string `json:"name"`
State string `json:"state"`
TenantID string `json:"tenantId"`
}
// ProfilePath returns the path where the Azure Profile is stored from the Azure CLI
func ProfilePath() (string, error) {
return homedir.Expand("~/.azure/azureProfile.json")
}
// LoadProfile restores a Profile object from a file located at 'path'.
func LoadProfile(path string) (result Profile, err error) {
var contents []byte
contents, err = ioutil.ReadFile(path)
if err != nil {
err = fmt.Errorf("failed to open file (%s) while loading token: %v", path, err)
return
}
reader := utfbom.SkipOnly(bytes.NewReader(contents))
dec := json.NewDecoder(reader)
if err = dec.Decode(&result); err != nil {
err = fmt.Errorf("failed to decode contents of file (%s) into a Profile representation: %v", path, err)
return
}
return
}
+89
View File
@@ -0,0 +1,89 @@
package cli
import (
"encoding/json"
"fmt"
"os"
"strconv"
"time"
"github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/date"
"github.com/mitchellh/go-homedir"
)
// Token represents an AccessToken from the Azure CLI
type Token struct {
AccessToken string `json:"accessToken"`
Authority string `json:"_authority"`
ClientID string `json:"_clientId"`
ExpiresOn string `json:"expiresOn"`
IdentityProvider string `json:"identityProvider"`
IsMRRT bool `json:"isMRRT"`
RefreshToken string `json:"refreshToken"`
Resource string `json:"resource"`
TokenType string `json:"tokenType"`
UserID string `json:"userId"`
}
// ToADALToken converts an Azure CLI `Token`` to an `adal.Token``
func (t Token) ToADALToken() (converted adal.Token, err error) {
tokenExpirationDate, err := ParseExpirationDate(t.ExpiresOn)
if err != nil {
err = fmt.Errorf("Error parsing Token Expiration Date %q: %+v", t.ExpiresOn, err)
return
}
difference := tokenExpirationDate.Sub(date.UnixEpoch())
converted = adal.Token{
AccessToken: t.AccessToken,
Type: t.TokenType,
ExpiresIn: "3600",
ExpiresOn: strconv.Itoa(int(difference.Seconds())),
RefreshToken: t.RefreshToken,
Resource: t.Resource,
}
return
}
// AccessTokensPath returns the path where access tokens are stored from the Azure CLI
func AccessTokensPath() (string, error) {
return homedir.Expand("~/.azure/accessTokens.json")
}
// ParseExpirationDate parses either a Azure CLI or CloudShell date into a time object
func ParseExpirationDate(input string) (*time.Time, error) {
// CloudShell (and potentially the Azure CLI in future)
expirationDate, cloudShellErr := time.Parse(time.RFC3339, input)
if cloudShellErr != nil {
// Azure CLI (Python) e.g. 2017-08-31 19:48:57.998857 (plus the local timezone)
const cliFormat = "2006-01-02 15:04:05.999999"
expirationDate, cliErr := time.ParseInLocation(cliFormat, input, time.Local)
if cliErr == nil {
return &expirationDate, nil
}
return nil, fmt.Errorf("Error parsing expiration date %q.\n\nCloudShell Error: \n%+v\n\nCLI Error:\n%+v", input, cloudShellErr, cliErr)
}
return &expirationDate, nil
}
// LoadTokens restores a set of Token objects from a file located at 'path'.
func LoadTokens(path string) ([]Token, error) {
file, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("failed to open file (%s) while loading token: %v", path, err)
}
defer file.Close()
var tokens []Token
dec := json.NewDecoder(file)
if err = dec.Decode(&tokens); err != nil {
return nil, fmt.Errorf("failed to decode contents of file (%s) into a `cli.Token` representation: %v", path, err)
}
return tokens, nil
}
+1
View File
@@ -35,6 +35,7 @@ var (
statusCodesForRetry = []int{
http.StatusRequestTimeout, // 408
http.StatusTooManyRequests, // 429
http.StatusInternalServerError, // 500
http.StatusBadGateway, // 502
http.StatusServiceUnavailable, // 503
+2
View File
@@ -1,3 +1,5 @@
// +build go1.7
package date
import (
+4
View File
@@ -31,6 +31,9 @@ type DetailedError struct {
// Service Error is the response body of failed API in bytes
ServiceError []byte
// Response is the response object that was returned during failure if applicable.
Response *http.Response
}
// NewError creates a new Error conforming object from the passed packageType, method, and
@@ -67,6 +70,7 @@ func NewErrorWithError(original error, packageType string, method string, resp *
Method: method,
StatusCode: statusCode,
Message: fmt.Sprintf(message, args...),
Response: resp,
}
}
+14 -53
View File
@@ -7,18 +7,6 @@ import (
"net/http"
)
// NOTE: the GetBody() method on the http.Request object is new in 1.8.
// at present we support 1.7 and 1.8 so for now the branches specific
// to 1.8 have been commented out.
// RetriableRequest provides facilities for retrying an HTTP request.
type RetriableRequest struct {
req *http.Request
//rc io.ReadCloser
br *bytes.Reader
reset bool
}
// NewRetriableRequest returns a wrapper around an HTTP request that support retry logic.
func NewRetriableRequest(req *http.Request) *RetriableRequest {
return &RetriableRequest{req: req}
@@ -29,49 +17,22 @@ func (rr *RetriableRequest) Request() *http.Request {
return rr.req
}
// Prepare signals that the request is about to be sent.
func (rr *RetriableRequest) Prepare() (err error) {
// preserve the request body; this is to support retry logic as
// the underlying transport will always close the reqeust body
if rr.req.Body != nil {
if rr.reset {
/*if rr.rc != nil {
rr.req.Body = rr.rc
} else */if rr.br != nil {
_, err = rr.br.Seek(0, io.SeekStart)
}
rr.reset = false
if err != nil {
return err
}
func (rr *RetriableRequest) prepareFromByteReader() (err error) {
// fall back to making a copy (only do this once)
b := []byte{}
if rr.req.ContentLength > 0 {
b = make([]byte, rr.req.ContentLength)
_, err = io.ReadFull(rr.req.Body, b)
if err != nil {
return err
}
/*if rr.req.GetBody != nil {
// this will allow us to preserve the body without having to
// make a copy. note we need to do this on each iteration
rr.rc, err = rr.req.GetBody()
if err != nil {
return err
}
} else */if rr.br == nil {
// fall back to making a copy (only do this once)
b := []byte{}
if rr.req.ContentLength > 0 {
b = make([]byte, rr.req.ContentLength)
_, err = io.ReadFull(rr.req.Body, b)
if err != nil {
return err
}
} else {
b, err = ioutil.ReadAll(rr.req.Body)
if err != nil {
return err
}
}
rr.br = bytes.NewReader(b)
rr.req.Body = ioutil.NopCloser(rr.br)
} else {
b, err = ioutil.ReadAll(rr.req.Body)
if err != nil {
return err
}
// indicates that the request body needs to be reset
rr.reset = true
}
rr.br = bytes.NewReader(b)
rr.req.Body = ioutil.NopCloser(rr.br)
return err
}
+44
View File
@@ -0,0 +1,44 @@
// +build !go1.8
package autorest
import (
"bytes"
"net/http"
)
// RetriableRequest provides facilities for retrying an HTTP request.
type RetriableRequest struct {
req *http.Request
br *bytes.Reader
reset bool
}
// Prepare signals that the request is about to be sent.
func (rr *RetriableRequest) Prepare() (err error) {
// preserve the request body; this is to support retry logic as
// the underlying transport will always close the reqeust body
if rr.req.Body != nil {
if rr.reset {
if rr.br != nil {
_, err = rr.br.Seek(0, 0 /*io.SeekStart*/)
}
rr.reset = false
if err != nil {
return err
}
}
if rr.br == nil {
// fall back to making a copy (only do this once)
err = rr.prepareFromByteReader()
}
// indicates that the request body needs to be reset
rr.reset = true
}
return err
}
func removeRequestBody(req *http.Request) {
req.Body = nil
req.ContentLength = 0
}
+56
View File
@@ -0,0 +1,56 @@
// +build go1.8
package autorest
import (
"bytes"
"io"
"net/http"
)
// RetriableRequest provides facilities for retrying an HTTP request.
type RetriableRequest struct {
req *http.Request
rc io.ReadCloser
br *bytes.Reader
reset bool
}
// Prepare signals that the request is about to be sent.
func (rr *RetriableRequest) Prepare() (err error) {
// preserve the request body; this is to support retry logic as
// the underlying transport will always close the reqeust body
if rr.req.Body != nil {
if rr.reset {
if rr.rc != nil {
rr.req.Body = rr.rc
} else if rr.br != nil {
_, err = rr.br.Seek(0, io.SeekStart)
}
rr.reset = false
if err != nil {
return err
}
}
if rr.req.GetBody != nil {
// this will allow us to preserve the body without having to
// make a copy. note we need to do this on each iteration
rr.rc, err = rr.req.GetBody()
if err != nil {
return err
}
} else if rr.br == nil {
// fall back to making a copy (only do this once)
err = rr.prepareFromByteReader()
}
// indicates that the request body needs to be reset
rr.reset = true
}
return err
}
func removeRequestBody(req *http.Request) {
req.Body = nil
req.GetBody = nil
req.ContentLength = 0
}
+20 -1
View File
@@ -5,6 +5,7 @@ import (
"log"
"math"
"net/http"
"strconv"
"time"
)
@@ -209,13 +210,31 @@ func DoRetryForStatusCodes(attempts int, backoff time.Duration, codes ...int) Se
if err != nil || !ResponseHasStatusCode(resp, codes...) {
return resp, err
}
DelayForBackoff(backoff, attempt, r.Cancel)
delayed := DelayWithRetryAfter(resp, r.Cancel)
if !delayed {
DelayForBackoff(backoff, attempt, r.Cancel)
}
}
return resp, err
})
}
}
// DelayWithRetryAfter invokes time.After for the duration specified in the "Retry-After" header in
// responses with status code 429
func DelayWithRetryAfter(resp *http.Response, cancel <-chan struct{}) bool {
retryAfter, _ := strconv.Atoi(resp.Header.Get("Retry-After"))
if resp.StatusCode == http.StatusTooManyRequests && retryAfter > 0 {
select {
case <-time.After(time.Duration(retryAfter) * time.Second):
return true
case <-cancel:
return false
}
}
return false
}
// DoRetryForDuration returns a SendDecorator that retries the request until the total time is equal
// to or greater than the specified duration, exponentially backing off between requests using the
// supplied backoff time.Duration (which may be zero). Retrying may be canceled by closing the
+30
View File
@@ -765,3 +765,33 @@ func newAcceptedResponse() *http.Response {
mocks.SetAcceptedHeaders(resp)
return resp
}
func TestDelayWithRetryAfterWithSuccess(t *testing.T) {
after, retries := 5, 2
totalSecs := after * retries
client := mocks.NewSender()
resp := mocks.NewResponseWithStatus("429 Too many requests", http.StatusTooManyRequests)
mocks.SetResponseHeader(resp, "Retry-After", fmt.Sprintf("%v", after))
client.AppendAndRepeatResponse(resp, retries)
client.AppendResponse(mocks.NewResponseWithStatus("200 OK", http.StatusOK))
d := time.Second * time.Duration(totalSecs)
start := time.Now()
r, _ := SendWithSender(client, mocks.NewRequest(),
DoRetryForStatusCodes(5, time.Duration(time.Second), http.StatusTooManyRequests),
)
if time.Since(start) < d {
t.Fatal("autorest: DelayWithRetryAfter failed stopped too soon")
}
Respond(r,
ByDiscardingBody(),
ByClosing())
if client.Attempts() != 3 {
t.Fatalf("autorest: Sender#DelayWithRetryAfter -- Got: StatusCode %v in %v attempts; Want: StatusCode 200 OK in 2 attempts -- ",
r.Status, client.Attempts()-1)
}
}
+42
View File
@@ -0,0 +1,42 @@
package utils
import (
"fmt"
"os"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure"
)
// GetAuthorizer gets an Azure Service Principal authorizer.
// This func assumes "AZURE_TENANT_ID", "AZURE_CLIENT_ID",
// "AZURE_CLIENT_SECRET" are set as environment variables.
func GetAuthorizer(env azure.Environment) (*autorest.BearerAuthorizer, error) {
tenantID := GetEnvVarOrExit("AZURE_TENANT_ID")
oauthConfig, err := adal.NewOAuthConfig(env.ActiveDirectoryEndpoint, tenantID)
if err != nil {
return nil, err
}
clientID := GetEnvVarOrExit("AZURE_CLIENT_ID")
clientSecret := GetEnvVarOrExit("AZURE_CLIENT_SECRET")
spToken, err := adal.NewServicePrincipalToken(*oauthConfig, clientID, clientSecret, env.ResourceManagerEndpoint)
if err != nil {
return nil, err
}
return autorest.NewBearerAuthorizer(spToken), nil
}
// GetEnvVarOrExit returns the value of specified environment variable or terminates if it's not defined.
func GetEnvVarOrExit(varName string) string {
value := os.Getenv(varName)
if value == "" {
fmt.Printf("Missing environment variable %s\n", varName)
os.Exit(1)
}
return value
}
+18
View File
@@ -0,0 +1,18 @@
package utils
import (
"bytes"
"os/exec"
)
// GetCommit returns git HEAD (short)
func GetCommit() string {
cmd := exec.Command("git", "rev-parse", "HEAD")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return ""
}
return string(out.Bytes()[:7])
}
+13 -2
View File
@@ -205,14 +205,14 @@ func validateString(x reflect.Value, v Constraint) error {
return createError(x, v, fmt.Sprintf("rule must be integer value for %v constraint; got: %v", v.Name, v.Rule))
}
if len(s) > v.Rule.(int) {
return createError(x, v, fmt.Sprintf("value length must be less than %v", v.Rule))
return createError(x, v, fmt.Sprintf("value length must be less than or equal to %v", v.Rule))
}
case MinLength:
if _, ok := v.Rule.(int); !ok {
return createError(x, v, fmt.Sprintf("rule must be integer value for %v constraint; got: %v", v.Name, v.Rule))
}
if len(s) < v.Rule.(int) {
return createError(x, v, fmt.Sprintf("value length must be greater than %v", v.Rule))
return createError(x, v, fmt.Sprintf("value length must be greater than or equal to %v", v.Rule))
}
case ReadOnly:
if len(s) > 0 {
@@ -273,6 +273,17 @@ func validateArrayMap(x reflect.Value, v Constraint) error {
if x.Len() != 0 {
return createError(x, v, "readonly parameter; must send as nil or empty in request")
}
case Pattern:
reg, err := regexp.Compile(v.Rule.(string))
if err != nil {
return createError(x, v, err.Error())
}
keys := x.MapKeys()
for _, k := range keys {
if !reg.MatchString(k.String()) {
return createError(k, v, fmt.Sprintf("map key doesn't match pattern %v", v.Rule))
}
}
default:
return createError(x, v, fmt.Sprintf("constraint %v is not applicable to array, slice and map type", v.Name))
}
+10 -8
View File
@@ -706,7 +706,7 @@ func TestValidateString_MaxLengthInvalid(t *testing.T) {
Chain: nil,
}
require.Equal(t, strings.Contains(validateString(reflect.ValueOf(x), c).Error(),
fmt.Sprintf("value length must be less than %v", c.Rule)), true)
fmt.Sprintf("value length must be less than or equal to %v", c.Rule)), true)
}
func TestValidateString_MaxLengthValid(t *testing.T) {
@@ -741,7 +741,7 @@ func TestValidateString_MinLengthInvalid(t *testing.T) {
Chain: nil,
}
require.Equal(t, strings.Contains(validateString(reflect.ValueOf(x), c).Error(),
fmt.Sprintf("value length must be greater than %v", c.Rule)), true)
fmt.Sprintf("value length must be greater than or equal to %v", c.Rule)), true)
}
func TestValidateString_MinLengthValid(t *testing.T) {
@@ -1389,7 +1389,7 @@ func TestValidatePointer_StringInvalid(t *testing.T) {
}}}
require.Equal(t, validatePtr(reflect.ValueOf(x), c).Error(),
createError(reflect.ValueOf("hello"), c.Chain[0],
"value length must be less than 2").Error())
"value length must be less than or equal to 2").Error())
}
func TestValidatePointer_ArrayValid(t *testing.T) {
@@ -1496,7 +1496,7 @@ func TestValidatePointer_StructWithError(t *testing.T) {
}
require.Equal(t, validatePtr(reflect.ValueOf(x), c).Error(),
createError(reflect.ValueOf("100"), c.Chain[0].Chain[0],
"value length must be less than 2").Error())
"value length must be less than or equal to 2").Error())
}
func TestValidatePointer_WithNilStruct(t *testing.T) {
@@ -1579,7 +1579,7 @@ func TestValidateStruct_WithChainConstraint(t *testing.T) {
},
}
require.Equal(t, validateStruct(reflect.ValueOf(x), c).Error(),
createError(reflect.ValueOf("100"), c.Chain[0].Chain[0], "value length must be less than 2").Error())
createError(reflect.ValueOf("100"), c.Chain[0].Chain[0], "value length must be less than or equal to 2").Error())
}
func TestValidateStruct_WithoutChainConstraint(t *testing.T) {
@@ -1794,6 +1794,7 @@ func TestValidate_MapValidationWithoutError(t *testing.T) {
{"M", Empty, false, nil},
{"M", MinItems, 1, nil},
{"M", UniqueItems, true, nil},
{"M", Pattern, "^[a-z]+$", nil},
},
},
},
@@ -1807,6 +1808,7 @@ func TestValidate_MapValidationWithoutError(t *testing.T) {
{"M", Empty, false, nil},
{"M", MinItems, 1, nil},
{"M", UniqueItems, true, nil},
{"M", Pattern, "^[a-z]+$", nil},
},
},
},
@@ -2005,7 +2007,7 @@ func TestValidate_String(t *testing.T) {
}
require.Equal(t, Validate(v).Error(),
createError(reflect.ValueOf(s), v[0].Constraints[1].Chain[0],
"value length must be less than 3").Error())
"value length must be less than or equal to 3").Error())
// required paramter
s = ""
@@ -2063,7 +2065,7 @@ func TestValidate_StringStruct(t *testing.T) {
// }
require.Equal(t, Validate(v).Error(),
createError(reflect.ValueOf(s), v[0].Constraints[0].Chain[0].Chain[1],
"value length must be less than 3").Error())
"value length must be less than or equal to 3").Error())
// required paramter - can't be Empty
s = ""
@@ -2348,7 +2350,7 @@ func TestValidate_StructInStruct(t *testing.T) {
}
require.Equal(t, Validate(v).Error(),
createError(reflect.ValueOf(p.C.I), v[0].Constraints[0].Chain[0].Chain[0],
"value length must be greater than 7").Error())
"value length must be greater than or equal to 7").Error())
// required paramter - can't be Empty
p = &Product{
+13 -11
View File
@@ -1,42 +1,44 @@
hash: 51202aefdfe9c4a992f96ab58f6cacf21cdbd1b66efe955c9030bca736ac816d
updated: 2017-02-14T17:07:23.015382703-08:00
hash: 6e0121d946623e7e609280b1b18627e1c8a767fdece54cb97c4447c1167cbc46
updated: 2017-08-31T13:58:01.034822883+01:00
imports:
- name: github.com/dgrijalva/jwt-go
version: a601269ab70c205d26370c16f7c81e9017c14e04
version: 2268707a8f0843315e2004ee4f1d021dc08baedf
subpackages:
- .
- name: github.com/dimchansky/utfbom
version: 6c6132ff69f0f6c088739067407b5d32c52e1d0f
- name: github.com/mitchellh/go-homedir
version: b8bc1bf767474819792c23f32d8286a45736f1c6
- name: golang.org/x/crypto
version: 453249f01cfeb54c3d549ddb75ff152ca243f9d8
version: 81e90905daefcd6fd217b62423c0908922eadb30
repo: https://github.com/golang/crypto.git
vcs: git
subpackages:
- pkcs12
- pkcs12/internal/rc2
- name: golang.org/x/net
version: 61557ac0112b576429a0df080e1c2cef5dfbb642
version: 66aacef3dd8a676686c7ae3716979581e8b03c47
repo: https://github.com/golang/net.git
vcs: git
subpackages:
- .
- name: golang.org/x/text
version: 06d6eba81293389cafdff7fca90d75592194b2d9
version: 21e35d45962262c8ee80f6cb048dcf95ad0e9d79
repo: https://github.com/golang/text.git
vcs: git
subpackages:
- .
testImports:
- name: github.com/davecgh/go-spew
version: 346938d642f2ec3594ed81d874461961cd0faa76
version: 04cdfd42973bb9c8589fd6a731800cf222fde1a9
subpackages:
- spew
- name: github.com/Masterminds/semver
version: 59c29afe1a994eacb71c833025ca7acf874bb1da
- name: github.com/pmezard/go-difflib
version: 792786c7400a136282c1664665ae0a8db921c6c2
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
subpackages:
- difflib
- name: github.com/stretchr/testify
version: 4d4bfba8f1d1027c4fdbe371823030df51419987
version: 890a5c3458b43e6104ff5da8dfa139d013d77544
subpackages:
- assert
- require
+7 -13
View File
@@ -4,25 +4,19 @@ import:
subpackages:
- .
- package: golang.org/x/crypto
vcs: git
repo: https://github.com/golang/crypto.git
subpackages:
- /pkcs12
- package: golang.org/x/net
vcs: git
subpackages:
- pkcs12
- package: golang.org/x/net
repo: https://github.com/golang/net.git
vcs: git
subpackages:
- .
- package: golang.org/x/text
vcs: git
repo: https://github.com/golang/text.git
vcs: git
subpackages:
- .
testImports:
- package: github.com/stretchr/testify
vcs: git
repo: https://github.com/stretchr/testify.git
subpackages:
- /require
- package: github.com/Masterminds/semver
version: ~1.2.2
- package: github.com/mitchellh/go-homedir
- package: github.com/dimchansky/utfbom