vendor: update all dependencies to latest versions
This commit is contained in:
+2
@@ -7,6 +7,8 @@
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
.DS_Store
|
||||
.idea/
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
|
||||
+3
-2
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -35,6 +35,7 @@ var (
|
||||
|
||||
statusCodesForRetry = []int{
|
||||
http.StatusRequestTimeout, // 408
|
||||
http.StatusTooManyRequests, // 429
|
||||
http.StatusInternalServerError, // 500
|
||||
http.StatusBadGateway, // 502
|
||||
http.StatusServiceUnavailable, // 503
|
||||
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
// +build go1.7
|
||||
|
||||
package date
|
||||
|
||||
import (
|
||||
|
||||
+4
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user