vendor: update all dependencies to latest versions

This commit is contained in:
Nick Craig-Wood
2018-01-16 13:20:59 +00:00
parent 8e83fb6fb9
commit 7d3a17725d
4878 changed files with 1974213 additions and 201199 deletions
+7
View File
@@ -0,0 +1,7 @@
Thank you for your contribution to Go-AutoRest! We will triage and review it as soon as we can.
As part of submitting, please make sure you can make the following assertions:
- [ ] I've tested my changes, adding unit tests if applicable.
- [ ] I've added Apache 2.0 Headers to the top of any new source files.
- [ ] I'm submitting this PR to the `dev` branch, except in the case of urgent bug fixes warranting their own release.
- [ ] If I'm targeting `master`, I've updated [CHANGELOG.md](https://github.com/Azure/go-autorest/blob/master/CHANGELOG.md) to address the changes I'm making.
+2 -1
View File
@@ -3,9 +3,9 @@ sudo: false
language: go
go:
- 1.9
- 1.8
- 1.7
- 1.6
install:
- go get -u github.com/golang/lint/golint
@@ -15,6 +15,7 @@ install:
- glide install
script:
- grep -L -r --include *.go --exclude-dir vendor -P "Copyright (\d{4}|\(c\)) Microsoft" ./ | tee /dev/stderr | test -z "$(< /dev/stdin)"
- test -z "$(gofmt -s -l -w ./autorest/. | tee /dev/stderr)"
- test -z "$(golint ./autorest/... | tee /dev/stderr)"
- go vet ./autorest/...
+162 -1
View File
@@ -1,5 +1,166 @@
# CHANGELOG
## v9.7.1
### Bug Fixes
- Use correct AAD and Graph endpoints for US Gov environment.
## v9.7.0
### New Features
- Added support for application/octet-stream MIME types.
## v9.6.1
### Bug Fixes
- Ensure Authorization header is added to request when polling for registration status.
## v9.6.0
### New Features
- Added support for acquiring tokens via MSI with a user assigned identity.
## v9.5.3
### Bug Fixes
- Don't remove encoding of existing URL Query parameters when calling autorest.WithQueryParameters.
- Set correct Content Type when using autorest.WithFormData.
## v9.5.2
### Bug Fixes
- Check for nil *http.Response before dereferencing it.
## v9.5.1
### Bug Fixes
- Don't count http.StatusTooManyRequests (429) against the retry cap.
- Use retry logic when SkipResourceProviderRegistration is set to true.
## v9.5.0
### New Features
- Added support for username + password, API key, authoriazation code and cognitive services authentication.
- Added field SkipResourceProviderRegistration to clients to provide a way to skip auto-registration of RPs.
- Added utility function AsStringSlice() to convert its parameters to a string slice.
### Bug Fixes
- When checking for authentication failures look at the error type not the status code as it could vary.
## v9.4.2
### Bug Fixes
- Validate parameters when creating credentials.
- Don't retry requests if the returned status is a 401 (http.StatusUnauthorized) as it will never succeed.
## v9.4.1
### Bug Fixes
- Update the AccessTokensPath() to read access tokens path through AZURE_ACCESS_TOKEN_FILE. If this
environment variable is not set, it will fall back to use default path set by Azure CLI.
- Use case-insensitive string comparison for polling states.
## v9.4.0
### New Features
- Added WaitForCompletion() to Future as a default polling implementation.
### Bug Fixes
- Method Future.Done() shouldn't update polling status for unexpected HTTP status codes.
## v9.3.1
### Bug Fixes
- DoRetryForStatusCodes will retry if sender.Do returns a non-nil error.
## v9.3.0
### New Features
- Added PollingMethod() to Future so callers know what kind of polling mechanism is used.
- Added azure.ChangeToGet() which transforms an http.Request into a GET (to be used with LROs).
## v9.2.0
### New Features
- Added support for custom Azure Stack endpoints.
- Added type azure.Future used to track the status of long-running operations.
### Bug Fixes
- Preserve the original error in DoRetryWithRegistration when registration fails.
## v9.1.1
- Fixes a bug regarding the cookie jar on `autorest.Client.Sender`.
## v9.1.0
### New Features
- In cases where there is a non-empty error from the service, attempt to unmarshal it instead of uniformly calling it an "Unknown" error.
- Support for loading Azure CLI Authentication files.
- Automatically register your subscription with the Azure Resource Provider if it hadn't been previously.
### Bug Fixes
- RetriableRequest can now tolerate a ReadSeekable body being read but not reset.
- Adding missing Apache Headers
## v9.0.0
> **IMPORTANT:** This release was intially labeled incorrectly as `v8.4.0`. From the time it was released, it should have been marked `v9.0.0` because it contains breaking changes to the MSI packages. We appologize for any inconvenience this causes.
Adding MSI Endpoint Support and CLI token rehydration.
## v8.3.1
Pick up bug fix in adal for MSI support.
## v8.3.0
Updates to Error string formats for clarity. Also, adding a copy of the http.Response to errors for an improved debugging experience.
## v8.2.0
### New Features
- Add support for bearer authentication callbacks
- Support 429 response codes that include "Retry-After" header
- Support validation constraint "Pattern" for map keys
### Bug Fixes
- Make RetriableRequest work with multiple versions of Go
## v8.1.1
Updates the RetriableRequest to take advantage of GetBody() added in Go 1.8.
## v8.1.0
Adds RetriableRequest type for more efficient handling of retrying HTTP requests.
## v8.0.0
ADAL refactored into its own package.
Support for UNIX time.
## v7.3.1
- Version Testing now removed from production bits that are shipped with the library.
## v7.3.0
- Exposing new `RespondDecorator`, `ByDiscardingBody`. This allows operations
to acknowledge that they do not need either the entire or a trailing portion
@@ -15,7 +176,7 @@
- Added telemetry.
## v7.2.3
- Fixing bug in calls to `DelayForBackoff` that caused doubling of delay
- Fixing bug in calls to `DelayForBackoff` that caused doubling of delay
duration.
## v7.2.2
+34
View File
@@ -218,6 +218,40 @@ if (err == nil) {
}
```
#### Username password authenticate
```Go
spt, err := adal.NewServicePrincipalTokenFromUsernamePassword(
oauthConfig,
applicationID,
username,
password,
resource,
callbacks...)
if (err == nil) {
token := spt.Token
}
```
#### Authorization code authenticate
``` Go
spt, err := adal.NewServicePrincipalTokenFromAuthorizationCode(
oauthConfig,
applicationID,
clientSecret,
authorizationCode,
redirectURI,
resource,
callbacks...)
err = spt.Refresh()
if (err == nil) {
token := spt.Token
}
```
### Command Line Tool
A command line tool is available in `cmd/adal.go` that can acquire a token for a given resource. It supports all flows mentioned above.
+14
View File
@@ -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 (
"flag"
"fmt"
+30
View File
@@ -1,5 +1,19 @@
package adal
// 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 (
"fmt"
"net/url"
@@ -18,8 +32,24 @@ type OAuthConfig struct {
DeviceCodeEndpoint url.URL
}
// IsZero returns true if the OAuthConfig object is zero-initialized.
func (oac OAuthConfig) IsZero() bool {
return oac == OAuthConfig{}
}
func validateStringParam(param, name string) error {
if len(param) == 0 {
return fmt.Errorf("parameter '" + name + "' cannot be empty")
}
return nil
}
// NewOAuthConfig returns an OAuthConfig with tenant specific urls
func NewOAuthConfig(activeDirectoryEndpoint, tenantID string) (*OAuthConfig, error) {
if err := validateStringParam(activeDirectoryEndpoint, "activeDirectoryEndpoint"); err != nil {
return nil, err
}
// it's legal for tenantID to be empty so don't validate it
const activeDirectoryEndpointTemplate = "%s/oauth2/%s?api-version=%s"
u, err := url.Parse(activeDirectoryEndpoint)
if err != nil {
+14
View File
@@ -1,5 +1,19 @@
package adal
// 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 (
"testing"
)
+14
View File
@@ -1,5 +1,19 @@
package adal
// 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.
/*
This file is largely based on rjw57/oauth2device's code, with the follow differences:
* scope -> resource, and only allow a single one
+14
View File
@@ -1,5 +1,19 @@
package adal
// 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"
+14
View File
@@ -2,5 +2,19 @@
package adal
// 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.
// msiPath is the path to the MSI Extension settings file (to discover the endpoint)
var msiPath = "/var/lib/waagent/ManagedIdentity-Settings"
+14
View File
@@ -2,6 +2,20 @@
package adal
// 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 (
"os"
"strings"
+14
View File
@@ -1,5 +1,19 @@
package adal
// 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"
+14
View File
@@ -1,5 +1,19 @@
package adal
// 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"
"io/ioutil"
+14
View File
@@ -1,5 +1,19 @@
package adal
// 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 (
"net/http"
)
+242 -4
View File
@@ -1,5 +1,19 @@
package adal
// 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/rand"
"crypto/rsa"
@@ -28,9 +42,15 @@ const (
// OAuthGrantTypeClientCredentials is the "grant_type" identifier used in credential flows
OAuthGrantTypeClientCredentials = "client_credentials"
// OAuthGrantTypeUserPass is the "grant_type" identifier used in username and password auth flows
OAuthGrantTypeUserPass = "password"
// OAuthGrantTypeRefreshToken is the "grant_type" identifier used in refresh token flows
OAuthGrantTypeRefreshToken = "refresh_token"
// OAuthGrantTypeAuthorizationCode is the "grant_type" identifier used in authorization code flows
OAuthGrantTypeAuthorizationCode = "authorization_code"
// metadataHeader is the header required by MSI extension
metadataHeader = "Metadata"
)
@@ -40,6 +60,12 @@ type OAuthTokenProvider interface {
OAuthToken() string
}
// TokenRefreshError is an interface used by errors returned during token refresh.
type TokenRefreshError interface {
error
Response() *http.Response
}
// Refresher is an interface for token refresh functionality
type Refresher interface {
Refresh() error
@@ -64,6 +90,11 @@ type Token struct {
Type string `json:"token_type"`
}
// IsZero returns true if the token object is zero-initialized.
func (t Token) IsZero() bool {
return t == Token{}
}
// Expires returns the time.Time when the Token expires.
func (t Token) Expires() time.Time {
s, err := strconv.Atoi(t.ExpiresOn)
@@ -131,6 +162,34 @@ type ServicePrincipalCertificateSecret struct {
type ServicePrincipalMSISecret struct {
}
// ServicePrincipalUsernamePasswordSecret implements ServicePrincipalSecret for username and password auth.
type ServicePrincipalUsernamePasswordSecret struct {
Username string
Password string
}
// ServicePrincipalAuthorizationCodeSecret implements ServicePrincipalSecret for authorization code auth.
type ServicePrincipalAuthorizationCodeSecret struct {
ClientSecret string
AuthorizationCode string
RedirectURI string
}
// SetAuthenticationValues is a method of the interface ServicePrincipalSecret.
func (secret *ServicePrincipalAuthorizationCodeSecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error {
v.Set("code", secret.AuthorizationCode)
v.Set("client_secret", secret.ClientSecret)
v.Set("redirect_uri", secret.RedirectURI)
return nil
}
// SetAuthenticationValues is a method of the interface ServicePrincipalSecret.
func (secret *ServicePrincipalUsernamePasswordSecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error {
v.Set("username", secret.Username)
v.Set("password", secret.Password)
return nil
}
// SetAuthenticationValues is a method of the interface ServicePrincipalSecret.
func (msiSecret *ServicePrincipalMSISecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error {
return nil
@@ -196,8 +255,27 @@ type ServicePrincipalToken struct {
refreshCallbacks []TokenRefreshCallback
}
func validateOAuthConfig(oac OAuthConfig) error {
if oac.IsZero() {
return fmt.Errorf("parameter 'oauthConfig' cannot be zero-initialized")
}
return nil
}
// NewServicePrincipalTokenWithSecret create a ServicePrincipalToken using the supplied ServicePrincipalSecret implementation.
func NewServicePrincipalTokenWithSecret(oauthConfig OAuthConfig, id string, resource string, secret ServicePrincipalSecret, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
if err := validateOAuthConfig(oauthConfig); err != nil {
return nil, err
}
if err := validateStringParam(id, "id"); err != nil {
return nil, err
}
if err := validateStringParam(resource, "resource"); err != nil {
return nil, err
}
if secret == nil {
return nil, fmt.Errorf("parameter 'secret' cannot be nil")
}
spt := &ServicePrincipalToken{
oauthConfig: oauthConfig,
secret: secret,
@@ -213,6 +291,18 @@ func NewServicePrincipalTokenWithSecret(oauthConfig OAuthConfig, id string, reso
// NewServicePrincipalTokenFromManualToken creates a ServicePrincipalToken using the supplied token
func NewServicePrincipalTokenFromManualToken(oauthConfig OAuthConfig, clientID string, resource string, token Token, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
if err := validateOAuthConfig(oauthConfig); err != nil {
return nil, err
}
if err := validateStringParam(clientID, "clientID"); err != nil {
return nil, err
}
if err := validateStringParam(resource, "resource"); err != nil {
return nil, err
}
if token.IsZero() {
return nil, fmt.Errorf("parameter 'token' cannot be zero-initialized")
}
spt, err := NewServicePrincipalTokenWithSecret(
oauthConfig,
clientID,
@@ -231,6 +321,18 @@ func NewServicePrincipalTokenFromManualToken(oauthConfig OAuthConfig, clientID s
// NewServicePrincipalToken creates a ServicePrincipalToken from the supplied Service Principal
// credentials scoped to the named resource.
func NewServicePrincipalToken(oauthConfig OAuthConfig, clientID string, secret string, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
if err := validateOAuthConfig(oauthConfig); err != nil {
return nil, err
}
if err := validateStringParam(clientID, "clientID"); err != nil {
return nil, err
}
if err := validateStringParam(secret, "secret"); err != nil {
return nil, err
}
if err := validateStringParam(resource, "resource"); err != nil {
return nil, err
}
return NewServicePrincipalTokenWithSecret(
oauthConfig,
clientID,
@@ -242,8 +344,23 @@ func NewServicePrincipalToken(oauthConfig OAuthConfig, clientID string, secret s
)
}
// NewServicePrincipalTokenFromCertificate create a ServicePrincipalToken from the supplied pkcs12 bytes.
// NewServicePrincipalTokenFromCertificate creates a ServicePrincipalToken from the supplied pkcs12 bytes.
func NewServicePrincipalTokenFromCertificate(oauthConfig OAuthConfig, clientID string, certificate *x509.Certificate, privateKey *rsa.PrivateKey, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
if err := validateOAuthConfig(oauthConfig); err != nil {
return nil, err
}
if err := validateStringParam(clientID, "clientID"); err != nil {
return nil, err
}
if err := validateStringParam(resource, "resource"); err != nil {
return nil, err
}
if certificate == nil {
return nil, fmt.Errorf("parameter 'certificate' cannot be nil")
}
if privateKey == nil {
return nil, fmt.Errorf("parameter 'privateKey' cannot be nil")
}
return NewServicePrincipalTokenWithSecret(
oauthConfig,
clientID,
@@ -256,6 +373,70 @@ func NewServicePrincipalTokenFromCertificate(oauthConfig OAuthConfig, clientID s
)
}
// NewServicePrincipalTokenFromUsernamePassword creates a ServicePrincipalToken from the username and password.
func NewServicePrincipalTokenFromUsernamePassword(oauthConfig OAuthConfig, clientID string, username string, password string, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
if err := validateOAuthConfig(oauthConfig); err != nil {
return nil, err
}
if err := validateStringParam(clientID, "clientID"); err != nil {
return nil, err
}
if err := validateStringParam(username, "username"); err != nil {
return nil, err
}
if err := validateStringParam(password, "password"); err != nil {
return nil, err
}
if err := validateStringParam(resource, "resource"); err != nil {
return nil, err
}
return NewServicePrincipalTokenWithSecret(
oauthConfig,
clientID,
resource,
&ServicePrincipalUsernamePasswordSecret{
Username: username,
Password: password,
},
callbacks...,
)
}
// NewServicePrincipalTokenFromAuthorizationCode creates a ServicePrincipalToken from the
func NewServicePrincipalTokenFromAuthorizationCode(oauthConfig OAuthConfig, clientID string, clientSecret string, authorizationCode string, redirectURI string, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
if err := validateOAuthConfig(oauthConfig); err != nil {
return nil, err
}
if err := validateStringParam(clientID, "clientID"); err != nil {
return nil, err
}
if err := validateStringParam(clientSecret, "clientSecret"); err != nil {
return nil, err
}
if err := validateStringParam(authorizationCode, "authorizationCode"); err != nil {
return nil, err
}
if err := validateStringParam(redirectURI, "redirectURI"); err != nil {
return nil, err
}
if err := validateStringParam(resource, "resource"); err != nil {
return nil, err
}
return NewServicePrincipalTokenWithSecret(
oauthConfig,
clientID,
resource,
&ServicePrincipalAuthorizationCodeSecret{
ClientSecret: clientSecret,
AuthorizationCode: authorizationCode,
RedirectURI: redirectURI,
},
callbacks...,
)
}
// GetMSIVMEndpoint gets the MSI endpoint on Virtual Machines.
func GetMSIVMEndpoint() (string, error) {
return getMSIVMEndpoint(msiPath)
@@ -279,7 +460,29 @@ func getMSIVMEndpoint(path string) (string, error) {
}
// NewServicePrincipalTokenFromMSI creates a ServicePrincipalToken via the MSI VM Extension.
// It will use the system assigned identity when creating the token.
func NewServicePrincipalTokenFromMSI(msiEndpoint, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
return newServicePrincipalTokenFromMSI(msiEndpoint, resource, nil, callbacks...)
}
// NewServicePrincipalTokenFromMSIWithUserAssignedID creates a ServicePrincipalToken via the MSI VM Extension.
// It will use the specified user assigned identity when creating the token.
func NewServicePrincipalTokenFromMSIWithUserAssignedID(msiEndpoint, resource string, userAssignedID string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
return newServicePrincipalTokenFromMSI(msiEndpoint, resource, &userAssignedID, callbacks...)
}
func newServicePrincipalTokenFromMSI(msiEndpoint, resource string, userAssignedID *string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
if err := validateStringParam(msiEndpoint, "msiEndpoint"); err != nil {
return nil, err
}
if err := validateStringParam(resource, "resource"); err != nil {
return nil, err
}
if userAssignedID != nil {
if err := validateStringParam(*userAssignedID, "userAssignedID"); err != nil {
return nil, err
}
}
// We set the oauth config token endpoint to be MSI's endpoint
msiEndpointURL, err := url.Parse(msiEndpoint)
if err != nil {
@@ -301,9 +504,33 @@ func NewServicePrincipalTokenFromMSI(msiEndpoint, resource string, callbacks ...
refreshCallbacks: callbacks,
}
if userAssignedID != nil {
spt.clientID = *userAssignedID
}
return spt, nil
}
// internal type that implements TokenRefreshError
type tokenRefreshError struct {
message string
resp *http.Response
}
// Error implements the error interface which is part of the TokenRefreshError interface.
func (tre tokenRefreshError) Error() string {
return tre.message
}
// Response implements the TokenRefreshError interface, it returns the raw HTTP response from the refresh operation.
func (tre tokenRefreshError) Response() *http.Response {
return tre.resp
}
func newTokenRefreshError(message string, resp *http.Response) TokenRefreshError {
return tokenRefreshError{message: message, resp: resp}
}
// EnsureFresh will refresh the token if it will expire within the refresh window (as set by
// RefreshWithin) and autoRefresh flag is on.
func (spt *ServicePrincipalToken) EnsureFresh() error {
@@ -336,6 +563,17 @@ func (spt *ServicePrincipalToken) RefreshExchange(resource string) error {
return spt.refreshInternal(resource)
}
func (spt *ServicePrincipalToken) getGrantType() string {
switch spt.secret.(type) {
case *ServicePrincipalUsernamePasswordSecret:
return OAuthGrantTypeUserPass
case *ServicePrincipalAuthorizationCodeSecret:
return OAuthGrantTypeAuthorizationCode
default:
return OAuthGrantTypeClientCredentials
}
}
func (spt *ServicePrincipalToken) refreshInternal(resource string) error {
v := url.Values{}
v.Set("client_id", spt.clientID)
@@ -345,7 +583,7 @@ func (spt *ServicePrincipalToken) refreshInternal(resource string) error {
v.Set("grant_type", OAuthGrantTypeRefreshToken)
v.Set("refresh_token", spt.RefreshToken)
} else {
v.Set("grant_type", OAuthGrantTypeClientCredentials)
v.Set("grant_type", spt.getGrantType())
err := spt.secret.SetAuthenticationValues(spt, &v)
if err != nil {
return err
@@ -374,9 +612,9 @@ func (spt *ServicePrincipalToken) refreshInternal(resource string) error {
if resp.StatusCode != http.StatusOK {
if err != nil {
return fmt.Errorf("adal: Refresh request failed. Status Code = '%d'. Failed reading response body", resp.StatusCode)
return newTokenRefreshError(fmt.Sprintf("adal: Refresh request failed. Status Code = '%d'. Failed reading response body", resp.StatusCode), resp)
}
return fmt.Errorf("adal: Refresh request failed. Status Code = '%d'. Response body: %s", resp.StatusCode, string(rb))
return newTokenRefreshError(fmt.Sprintf("adal: Refresh request failed. Status Code = '%d'. Response body: %s", resp.StatusCode, string(rb)), resp)
}
if err != nil {
+85
View File
@@ -1,5 +1,19 @@
package adal
// 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/rand"
"crypto/rsa"
@@ -275,6 +289,39 @@ func TestServicePrincipalTokenCertficateRefreshSetsBody(t *testing.T) {
})
}
func TestServicePrincipalTokenUsernamePasswordRefreshSetsBody(t *testing.T) {
spt := newServicePrincipalTokenUsernamePassword(t)
testServicePrincipalTokenRefreshSetsBody(t, spt, func(t *testing.T, b []byte) {
body := string(b)
values, _ := url.ParseQuery(body)
if values["client_id"][0] != "id" ||
values["grant_type"][0] != "password" ||
values["username"][0] != "username" ||
values["password"][0] != "password" ||
values["resource"][0] != "resource" {
t.Fatalf("adal: ServicePrincipalTokenUsernamePassword#Refresh did not correctly set the HTTP Request Body.")
}
})
}
func TestServicePrincipalTokenAuthorizationCodeRefreshSetsBody(t *testing.T) {
spt := newServicePrincipalTokenAuthorizationCode(t)
testServicePrincipalTokenRefreshSetsBody(t, spt, func(t *testing.T, b []byte) {
body := string(b)
values, _ := url.ParseQuery(body)
if values["client_id"][0] != "id" ||
values["grant_type"][0] != OAuthGrantTypeAuthorizationCode ||
values["code"][0] != "code" ||
values["client_secret"][0] != "clientSecret" ||
values["redirect_uri"][0] != "http://redirectUri/getToken" ||
values["resource"][0] != "resource" {
t.Fatalf("adal: ServicePrincipalTokenAuthorizationCode#Refresh did not correctly set the HTTP Request Body.")
}
})
}
func TestServicePrincipalTokenSecretRefreshSetsBody(t *testing.T) {
spt := newServicePrincipalToken()
testServicePrincipalTokenRefreshSetsBody(t, spt, func(t *testing.T, b []byte) {
@@ -536,6 +583,34 @@ func TestNewServicePrincipalTokenFromMSI(t *testing.T) {
}
}
func TestNewServicePrincipalTokenFromMSIWithUserAssignedID(t *testing.T) {
resource := "https://resource"
userID := "abc123"
cb := func(token Token) error { return nil }
spt, err := NewServicePrincipalTokenFromMSIWithUserAssignedID("http://msiendpoint/", resource, userID, cb)
if err != nil {
t.Fatalf("Failed to get MSI SPT: %v", err)
}
// check some of the SPT fields
if _, ok := spt.secret.(*ServicePrincipalMSISecret); !ok {
t.Fatal("SPT secret was not of MSI type")
}
if spt.resource != resource {
t.Fatal("SPT came back with incorrect resource")
}
if len(spt.refreshCallbacks) != 1 {
t.Fatal("SPT had incorrect refresh callbacks.")
}
if spt.clientID != userID {
t.Fatal("SPT had incorrect client ID")
}
}
func TestGetVMEndpoint(t *testing.T) {
tempSettingsFile, err := ioutil.TempFile("", "ManagedIdentity-Settings")
if err != nil {
@@ -638,3 +713,13 @@ func newServicePrincipalTokenCertificate(t *testing.T) *ServicePrincipalToken {
spt, _ := NewServicePrincipalTokenFromCertificate(TestOAuthConfig, "id", certificate, privateKey, "resource")
return spt
}
func newServicePrincipalTokenUsernamePassword(t *testing.T) *ServicePrincipalToken {
spt, _ := NewServicePrincipalTokenFromUsernamePassword(TestOAuthConfig, "id", "username", "password", "resource")
return spt
}
func newServicePrincipalTokenAuthorizationCode(t *testing.T) *ServicePrincipalToken {
spt, _ := NewServicePrincipalTokenFromAuthorizationCode(TestOAuthConfig, "id", "clientSecret", "code", "http://redirectUri/getToken", "resource")
return spt
}
+72 -4
View File
@@ -1,5 +1,19 @@
package autorest
// 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 (
"fmt"
"net/http"
@@ -10,9 +24,12 @@ import (
)
const (
bearerChallengeHeader = "Www-Authenticate"
bearer = "Bearer"
tenantID = "tenantID"
bearerChallengeHeader = "Www-Authenticate"
bearer = "Bearer"
tenantID = "tenantID"
apiKeyAuthorizerHeader = "Ocp-Apim-Subscription-Key"
bingAPISdkHeader = "X-BingApis-SDK-Client"
golangBingAPISdkHeaderValue = "Go-SDK"
)
// Authorizer is the interface that provides a PrepareDecorator used to supply request
@@ -30,6 +47,53 @@ func (na NullAuthorizer) WithAuthorization() PrepareDecorator {
return WithNothing()
}
// APIKeyAuthorizer implements API Key authorization.
type APIKeyAuthorizer struct {
headers map[string]interface{}
queryParameters map[string]interface{}
}
// NewAPIKeyAuthorizerWithHeaders creates an ApiKeyAuthorizer with headers.
func NewAPIKeyAuthorizerWithHeaders(headers map[string]interface{}) *APIKeyAuthorizer {
return NewAPIKeyAuthorizer(headers, nil)
}
// NewAPIKeyAuthorizerWithQueryParameters creates an ApiKeyAuthorizer with query parameters.
func NewAPIKeyAuthorizerWithQueryParameters(queryParameters map[string]interface{}) *APIKeyAuthorizer {
return NewAPIKeyAuthorizer(nil, queryParameters)
}
// NewAPIKeyAuthorizer creates an ApiKeyAuthorizer with headers.
func NewAPIKeyAuthorizer(headers map[string]interface{}, queryParameters map[string]interface{}) *APIKeyAuthorizer {
return &APIKeyAuthorizer{headers: headers, queryParameters: queryParameters}
}
// WithAuthorization returns a PrepareDecorator that adds an HTTP headers and Query Paramaters
func (aka *APIKeyAuthorizer) WithAuthorization() PrepareDecorator {
return func(p Preparer) Preparer {
return DecoratePreparer(p, WithHeaders(aka.headers), WithQueryParameters(aka.queryParameters))
}
}
// CognitiveServicesAuthorizer implements authorization for Cognitive Services.
type CognitiveServicesAuthorizer struct {
subscriptionKey string
}
// NewCognitiveServicesAuthorizer is
func NewCognitiveServicesAuthorizer(subscriptionKey string) *CognitiveServicesAuthorizer {
return &CognitiveServicesAuthorizer{subscriptionKey: subscriptionKey}
}
// WithAuthorization is
func (csa *CognitiveServicesAuthorizer) WithAuthorization() PrepareDecorator {
headers := make(map[string]interface{})
headers[apiKeyAuthorizerHeader] = csa.subscriptionKey
headers[bingAPISdkHeader] = golangBingAPISdkHeaderValue
return NewAPIKeyAuthorizerWithHeaders(headers).WithAuthorization()
}
// BearerAuthorizer implements the bearer authorization
type BearerAuthorizer struct {
tokenProvider adal.OAuthTokenProvider
@@ -55,7 +119,11 @@ func (ba *BearerAuthorizer) WithAuthorization() PrepareDecorator {
if ok {
err := refresher.EnsureFresh()
if err != nil {
return r, NewErrorWithError(err, "azure.BearerAuthorizer", "WithAuthorization", nil,
var resp *http.Response
if tokError, ok := err.(adal.TokenRefreshError); ok {
resp = tokError.Response()
}
return r, NewErrorWithError(err, "azure.BearerAuthorizer", "WithAuthorization", resp,
"Failed to refresh the Token for request to %s", r.URL)
}
}
+56
View File
@@ -1,5 +1,19 @@
package autorest
// 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 (
"fmt"
"net/http"
@@ -172,3 +186,45 @@ func TestBearerAuthorizerCallback(t *testing.T) {
t.Fatal("azure: BearerAuthorizerCallback#WithAuthorization failed to return an error when refresh fails")
}
}
func TestApiKeyAuthorization(t *testing.T) {
headers := make(map[string]interface{})
queryParameters := make(map[string]interface{})
dummyAuthHeader := "dummyAuthHeader"
dummyAuthHeaderValue := "dummyAuthHeaderValue"
dummyAuthQueryParameter := "dummyAuthQueryParameter"
dummyAuthQueryParameterValue := "dummyAuthQueryParameterValue"
headers[dummyAuthHeader] = dummyAuthHeaderValue
queryParameters[dummyAuthQueryParameter] = dummyAuthQueryParameterValue
aka := NewAPIKeyAuthorizer(headers, queryParameters)
req, err := Prepare(mocks.NewRequest(), aka.WithAuthorization())
if err != nil {
t.Fatalf("azure: APIKeyAuthorizer#WithAuthorization returned an error (%v)", err)
} else if req.Header.Get(http.CanonicalHeaderKey(dummyAuthHeader)) != dummyAuthHeaderValue {
t.Fatalf("azure: APIKeyAuthorizer#WithAuthorization failed to set %s header", dummyAuthHeader)
} else if req.URL.Query().Get(dummyAuthQueryParameter) != dummyAuthQueryParameterValue {
t.Fatalf("azure: APIKeyAuthorizer#WithAuthorization failed to set %s query parameter", dummyAuthQueryParameterValue)
}
}
func TestCognitivesServicesAuthorization(t *testing.T) {
subscriptionKey := "dummyKey"
csa := NewCognitiveServicesAuthorizer(subscriptionKey)
req, err := Prepare(mocks.NewRequest(), csa.WithAuthorization())
if err != nil {
t.Fatalf("azure: CognitiveServicesAuthorizer#WithAuthorization returned an error (%v)", err)
} else if req.Header.Get(http.CanonicalHeaderKey(bingAPISdkHeader)) != golangBingAPISdkHeaderValue {
t.Fatalf("azure: CognitiveServicesAuthorizer#WithAuthorization failed to set %s header", bingAPISdkHeader)
} else if req.Header.Get(http.CanonicalHeaderKey(apiKeyAuthorizerHeader)) != subscriptionKey {
t.Fatalf("azure: CognitiveServicesAuthorizer#WithAuthorization failed to set %s header", apiKeyAuthorizerHeader)
}
}
+17
View File
@@ -57,6 +57,20 @@ generated clients, see the Client described below.
*/
package autorest
// 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 (
"net/http"
"time"
@@ -73,6 +87,9 @@ const (
// ResponseHasStatusCode returns true if the status code in the HTTP Response is in the passed set
// and false otherwise.
func ResponseHasStatusCode(resp *http.Response, codes ...int) bool {
if resp == nil {
return false
}
return containsInt(codes, resp.StatusCode)
}
+14
View File
@@ -1,5 +1,19 @@
package autorest
// 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 (
"net/http"
"testing"
+214 -56
View File
@@ -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
View File
@@ -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
View File
@@ -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")
}
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
}
}
@@ -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--"
}
+34 -7
View File
@@ -1,5 +1,19 @@
package autorest
// 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"
"fmt"
@@ -21,6 +35,9 @@ const (
// DefaultRetryAttempts is number of attempts for retry status codes (5xx).
DefaultRetryAttempts = 3
// DefaultRetryDuration is the duration to wait between retries.
DefaultRetryDuration = 30 * time.Second
)
var (
@@ -33,7 +50,8 @@ var (
Version(),
)
statusCodesForRetry = []int{
// StatusCodesForRetry are a defined group of status code for which the client will retry
StatusCodesForRetry = []int{
http.StatusRequestTimeout, // 408
http.StatusTooManyRequests, // 429
http.StatusInternalServerError, // 500
@@ -148,6 +166,9 @@ type Client struct {
UserAgent string
Jar http.CookieJar
// Set to true to skip attempted registration of resource providers (false by default).
SkipResourceProviderRegistration bool
}
// NewClientWithUserAgent returns an instance of a Client with the UserAgent set to the passed
@@ -157,9 +178,10 @@ func NewClientWithUserAgent(ua string) Client {
PollingDelay: DefaultPollingDelay,
PollingDuration: DefaultPollingDuration,
RetryAttempts: DefaultRetryAttempts,
RetryDuration: 30 * time.Second,
RetryDuration: DefaultRetryDuration,
UserAgent: defaultUserAgent,
}
c.Sender = c.sender()
c.AddToUserAgent(ua)
return c
}
@@ -185,12 +207,17 @@ func (c Client) Do(r *http.Request) (*http.Response, error) {
c.WithInspection(),
c.WithAuthorization())
if err != nil {
return nil, NewErrorWithError(err, "autorest/Client", "Do", nil, "Preparing request failed")
var resp *http.Response
if detErr, ok := err.(DetailedError); ok {
// if the authorization failed (e.g. invalid credentials) there will
// be a response associated with the error, be sure to return it.
resp = detErr.Response
}
return resp, NewErrorWithError(err, "autorest/Client", "Do", nil, "Preparing request failed")
}
resp, err := SendWithSender(c.sender(), r,
DoRetryForStatusCodes(c.RetryAttempts, c.RetryDuration, statusCodesForRetry...))
Respond(resp,
c.ByInspecting())
resp, err := SendWithSender(c.sender(), r)
Respond(resp, c.ByInspecting())
return resp, err
}
+60
View File
@@ -1,5 +1,19 @@
package autorest
// 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"
"fmt"
@@ -7,6 +21,7 @@ import (
"log"
"math/rand"
"net/http"
"net/http/httptest"
"reflect"
"testing"
"time"
@@ -331,6 +346,51 @@ func TestClientByInspectingSetsDefault(t *testing.T) {
}
}
func TestCookies(t *testing.T) {
second := "second"
expected := http.Cookie{
Name: "tastes",
Value: "delicious",
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, &expected)
b, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatalf("autorest: ioutil.ReadAll failed reading request body: %s", err)
}
if string(b) == second {
cookie, err := r.Cookie(expected.Name)
if err != nil {
t.Fatalf("autorest: r.Cookie could not get request cookie: %s", err)
}
if cookie == nil {
t.Fatalf("autorest: got nil cookie, expecting %v", expected)
}
if cookie.Value != expected.Value {
t.Fatalf("autorest: got cookie value '%s', expecting '%s'", cookie.Value, expected.Name)
}
}
}))
defer server.Close()
client := NewClientWithUserAgent("")
_, err := SendWithSender(client, mocks.NewRequestForURL(server.URL))
if err != nil {
t.Fatalf("autorest: first request failed: %s", err)
}
r2, err := http.NewRequest(http.MethodGet, server.URL, mocks.NewBody(second))
if err != nil {
t.Fatalf("autorest: failed creating second request: %s", err)
}
_, err = SendWithSender(client, r2)
if err != nil {
t.Fatalf("autorest: second request failed: %s", err)
}
}
func randomString(n int) string {
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
r := rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
+14
View File
@@ -5,6 +5,20 @@ time.Time types. And both convert to time.Time through a ToTime method.
*/
package date
// 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 (
"fmt"
"time"
+14
View File
@@ -1,5 +1,19 @@
package date
// 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"
+14
View File
@@ -1,5 +1,19 @@
package date
// 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 (
"regexp"
"time"
+14
View File
@@ -1,5 +1,19 @@
package date
// 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"
+14
View File
@@ -1,5 +1,19 @@
package date
// 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 (
"errors"
"time"
+14
View File
@@ -1,5 +1,19 @@
package date
// 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"
+14
View File
@@ -1,5 +1,19 @@
package date
// 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/binary"
+14
View File
@@ -2,6 +2,20 @@
package date
// 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/binary"
+14
View File
@@ -1,5 +1,19 @@
package date
// 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 (
"strings"
"time"
+14
View File
@@ -1,5 +1,19 @@
package autorest
// 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 (
"fmt"
"net/http"
+14
View File
@@ -1,5 +1,19 @@
package autorest
// 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 (
"fmt"
"net/http"
+14
View File
@@ -1,5 +1,19 @@
package mocks
// 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 (
"fmt"
"net/http"
-1
View File
@@ -1 +0,0 @@
package mocks
+60 -3
View File
@@ -3,10 +3,25 @@ Package mocks provides mocks and helpers used in testing.
*/
package mocks
// 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 (
"fmt"
"io"
"net/http"
"time"
)
// Body implements acceptable body over a string.
@@ -65,10 +80,17 @@ func (body *Body) reset() *Body {
return body
}
type response struct {
r *http.Response
e error
d time.Duration
}
// Sender implements a simple null sender.
type Sender struct {
attempts int
responses []*http.Response
responses []response
numResponses int
repeatResponse []int
err error
repeatError int
@@ -85,12 +107,15 @@ func (c *Sender) Do(r *http.Request) (resp *http.Response, err error) {
c.attempts++
if len(c.responses) > 0 {
resp = c.responses[0]
resp = c.responses[0].r
if resp != nil {
if b, ok := resp.Body.(*Body); ok {
b.reset()
}
} else {
err = c.responses[0].e
}
time.Sleep(c.responses[0].d)
c.repeatResponse[0]--
if c.repeatResponse[0] == 0 {
c.responses = c.responses[1:]
@@ -121,16 +146,43 @@ func (c *Sender) AppendResponse(resp *http.Response) {
c.AppendAndRepeatResponse(resp, 1)
}
// AppendResponseWithDelay adds the passed http.Response to the response stack with the specified delay.
func (c *Sender) AppendResponseWithDelay(resp *http.Response, delay time.Duration) {
c.AppendAndRepeatResponseWithDelay(resp, delay, 1)
}
// AppendAndRepeatResponse adds the passed http.Response to the response stack along with a
// repeat count. A negative repeat count will return the response for all remaining calls to Do.
func (c *Sender) AppendAndRepeatResponse(resp *http.Response, repeat int) {
c.appendAndRepeat(response{r: resp}, repeat)
}
// AppendAndRepeatResponseWithDelay adds the passed http.Response to the response stack with the specified
// delay along with a repeat count. A negative repeat count will return the response for all remaining calls to Do.
func (c *Sender) AppendAndRepeatResponseWithDelay(resp *http.Response, delay time.Duration, repeat int) {
c.appendAndRepeat(response{r: resp, d: delay}, repeat)
}
// AppendError adds the passed error to the response stack.
func (c *Sender) AppendError(err error) {
c.AppendAndRepeatError(err, 1)
}
// AppendAndRepeatError adds the passed error to the response stack along with a repeat
// count. A negative repeat count will return the response for all remaining calls to Do.
func (c *Sender) AppendAndRepeatError(err error, repeat int) {
c.appendAndRepeat(response{e: err}, repeat)
}
func (c *Sender) appendAndRepeat(resp response, repeat int) {
if c.responses == nil {
c.responses = []*http.Response{resp}
c.responses = []response{resp}
c.repeatResponse = []int{repeat}
} else {
c.responses = append(c.responses, resp)
c.repeatResponse = append(c.repeatResponse, repeat)
}
c.numResponses++
}
// Attempts returns the number of times Do was called.
@@ -155,6 +207,11 @@ func (c *Sender) SetEmitErrorAfter(ea int) {
c.emitErrorAfter = ea
}
// NumResponses returns the number of responses that have been added to the sender.
func (c *Sender) NumResponses() int {
return c.numResponses
}
// T is a simple testing struct.
type T struct {
Name string `json:"name" xml:"Name"`
-1
View File
@@ -1 +0,0 @@
package mocks
+56 -4
View File
@@ -1,5 +1,19 @@
package autorest
// 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"
@@ -13,8 +27,9 @@ import (
)
const (
mimeTypeJSON = "application/json"
mimeTypeFormPost = "application/x-www-form-urlencoded"
mimeTypeJSON = "application/json"
mimeTypeOctetStream = "application/octet-stream"
mimeTypeFormPost = "application/x-www-form-urlencoded"
headerAuthorization = "Authorization"
headerContentType = "Content-Type"
@@ -98,6 +113,28 @@ func WithHeader(header string, value string) PrepareDecorator {
}
}
// WithHeaders returns a PrepareDecorator that sets the specified HTTP headers of the http.Request to
// the passed value. It canonicalizes the passed headers name (via http.CanonicalHeaderKey) before
// adding them.
func WithHeaders(headers map[string]interface{}) PrepareDecorator {
h := ensureValueStrings(headers)
return func(p Preparer) Preparer {
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
r, err := p.Prepare(r)
if err == nil {
if r.Header == nil {
r.Header = make(http.Header)
}
for name, value := range h {
r.Header.Set(http.CanonicalHeaderKey(name), value)
}
}
return r, err
})
}
}
// WithBearerAuthorization returns a PrepareDecorator that adds an HTTP Authorization header whose
// value is "Bearer " followed by the supplied token.
func WithBearerAuthorization(token string) PrepareDecorator {
@@ -128,6 +165,11 @@ func AsJSON() PrepareDecorator {
return AsContentType(mimeTypeJSON)
}
// AsOctetStream returns a PrepareDecorator that adds the "application/octet-stream" Content-Type header.
func AsOctetStream() PrepareDecorator {
return AsContentType(mimeTypeOctetStream)
}
// WithMethod returns a PrepareDecorator that sets the HTTP method of the passed request. The
// decorator does not validate that the passed method string is a known HTTP method.
func WithMethod(method string) PrepareDecorator {
@@ -201,6 +243,11 @@ func WithFormData(v url.Values) PrepareDecorator {
r, err := p.Prepare(r)
if err == nil {
s := v.Encode()
if r.Header == nil {
r.Header = make(http.Header)
}
r.Header.Set(http.CanonicalHeaderKey(headerContentType), mimeTypeFormPost)
r.ContentLength = int64(len(s))
r.Body = ioutil.NopCloser(strings.NewReader(s))
}
@@ -416,11 +463,16 @@ func WithQueryParameters(queryParameters map[string]interface{}) PrepareDecorato
if r.URL == nil {
return r, NewError("autorest", "WithQueryParameters", "Invoked with a nil URL")
}
v := r.URL.Query()
for key, value := range parameters {
v.Add(key, value)
d, err := url.QueryUnescape(value)
if err != nil {
return r, err
}
v.Add(key, d)
}
r.URL.RawQuery = createQuery(v)
r.URL.RawQuery = v.Encode()
}
return r, err
})
+53 -3
View File
@@ -1,5 +1,19 @@
package autorest
// 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 (
"fmt"
"io/ioutil"
@@ -469,7 +483,7 @@ func TestPrepareWithNullRequest(t *testing.T) {
}
}
func TestWithFormDataSetsContentLength(t *testing.T) {
func TestWithFormData(t *testing.T) {
v := url.Values{}
v.Add("name", "Rob Pike")
v.Add("age", "42")
@@ -493,6 +507,10 @@ func TestWithFormDataSetsContentLength(t *testing.T) {
if r.ContentLength != int64(len(b)) {
t.Fatalf("autorest:WithFormData set Content-Length to %v, expected %v", r.ContentLength, len(b))
}
if expected, got := r.Header.Get(http.CanonicalHeaderKey(headerContentType)), mimeTypeFormPost; expected != got {
t.Fatalf("autorest:WithFormData Content Type not set or set to wrong value. Expected %v and got %v", expected, got)
}
}
func TestWithMultiPartFormDataSetsContentLength(t *testing.T) {
@@ -746,7 +764,39 @@ func TestModifyingExistingRequest(t *testing.T) {
if err != nil {
t.Fatalf("autorest: Preparing an existing request returned an error (%v)", err)
}
if r.URL.String() != "https:/search?q=golang" && r.URL.Host != "bing.com" {
t.Fatalf("autorest: Preparing an existing request failed (%s)", r.URL)
if r.URL.Host != "bing.com" {
t.Fatalf("autorest: Preparing an existing request failed when setting the host (%s)", r.URL)
}
if r.URL.Path != "/search" {
t.Fatalf("autorest: Preparing an existing request failed when setting the path (%s)", r.URL.Path)
}
if r.URL.RawQuery != "q=golang" {
t.Fatalf("autorest: Preparing an existing request failed when setting the query parameters (%s)", r.URL.RawQuery)
}
}
func TestModifyingRequestWithExistingQueryParameters(t *testing.T) {
r, err := Prepare(
mocks.NewRequestForURL("https://bing.com"),
WithPath("search"),
WithQueryParameters(map[string]interface{}{"q": "golang the best"}),
WithQueryParameters(map[string]interface{}{"pq": "golang+encoded"}),
)
if err != nil {
t.Fatalf("autorest: Preparing an existing request returned an error (%v)", err)
}
if r.URL.Host != "bing.com" {
t.Fatalf("autorest: Preparing an existing request failed when setting the host (%s)", r.URL)
}
if r.URL.Path != "/search" {
t.Fatalf("autorest: Preparing an existing request failed when setting the path (%s)", r.URL.Path)
}
if r.URL.RawQuery != "pq=golang+encoded&q=golang+the+best" {
t.Fatalf("autorest: Preparing an existing request failed when setting the query parameters (%s)", r.URL.RawQuery)
}
}
+14
View File
@@ -1,5 +1,19 @@
package autorest
// 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"
+14
View File
@@ -1,5 +1,19 @@
package autorest
// 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"
+14
View File
@@ -1,5 +1,19 @@
package autorest
// 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"
"io"
+23 -13
View File
@@ -1,17 +1,31 @@
// +build !go1.8
// 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 autorest
import (
"bytes"
"io/ioutil"
"net/http"
)
// RetriableRequest provides facilities for retrying an HTTP request.
type RetriableRequest struct {
req *http.Request
br *bytes.Reader
reset bool
req *http.Request
br *bytes.Reader
}
// Prepare signals that the request is about to be sent.
@@ -19,21 +33,17 @@ 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 {
_, err = rr.br.Seek(0, 0 /*io.SeekStart*/)
rr.req.Body = ioutil.NopCloser(rr.br)
}
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
}
+26 -16
View File
@@ -1,19 +1,33 @@
// +build go1.8
// 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 autorest
import (
"bytes"
"io"
"io/ioutil"
"net/http"
)
// RetriableRequest provides facilities for retrying an HTTP request.
type RetriableRequest struct {
req *http.Request
rc io.ReadCloser
br *bytes.Reader
reset bool
req *http.Request
rc io.ReadCloser
br *bytes.Reader
}
// Prepare signals that the request is about to be sent.
@@ -21,16 +35,14 @@ 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.rc != nil {
rr.req.Body = rr.rc
} else if rr.br != nil {
_, err = rr.br.Seek(0, io.SeekStart)
rr.req.Body = ioutil.NopCloser(rr.br)
}
if err != nil {
return err
}
if rr.req.GetBody != nil {
// this will allow us to preserve the body without having to
@@ -43,8 +55,6 @@ func (rr *RetriableRequest) Prepare() (err error) {
// 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
}
+26 -2
View File
@@ -1,5 +1,19 @@
package autorest
// 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 (
"fmt"
"log"
@@ -201,19 +215,26 @@ func DoRetryForStatusCodes(attempts int, backoff time.Duration, codes ...int) Se
rr := NewRetriableRequest(r)
// Increment to add the first call (attempts denotes number of retries)
attempts++
for attempt := 0; attempt < attempts; attempt++ {
for attempt := 0; attempt < attempts; {
err = rr.Prepare()
if err != nil {
return resp, err
}
resp, err = s.Do(rr.Request())
if err != nil || !ResponseHasStatusCode(resp, codes...) {
// we want to retry if err is not nil (e.g. transient network failure). note that for failed authentication
// resp and err will both have a value, so in this case we don't want to retry as it will never succeed.
if err == nil && !ResponseHasStatusCode(resp, codes...) || IsTokenRefreshError(err) {
return resp, err
}
delayed := DelayWithRetryAfter(resp, r.Cancel)
if !delayed {
DelayForBackoff(backoff, attempt, r.Cancel)
}
// don't count a 429 against the number of attempts
// so that we continue to retry until it succeeds
if resp == nil || resp.StatusCode != http.StatusTooManyRequests {
attempt++
}
}
return resp, err
})
@@ -223,6 +244,9 @@ func DoRetryForStatusCodes(attempts int, backoff time.Duration, codes ...int) Se
// 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 {
if resp == nil {
return false
}
retryAfter, _ := strconv.Atoi(resp.Header.Get("Retry-After"))
if resp.StatusCode == http.StatusTooManyRequests && retryAfter > 0 {
select {
+34 -2
View File
@@ -1,5 +1,19 @@
package autorest
// 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"
"fmt"
@@ -767,7 +781,7 @@ func newAcceptedResponse() *http.Response {
}
func TestDelayWithRetryAfterWithSuccess(t *testing.T) {
after, retries := 5, 2
after, retries := 2, 2
totalSecs := after * retries
client := mocks.NewSender()
@@ -779,7 +793,7 @@ func TestDelayWithRetryAfterWithSuccess(t *testing.T) {
d := time.Second * time.Duration(totalSecs)
start := time.Now()
r, _ := SendWithSender(client, mocks.NewRequest(),
DoRetryForStatusCodes(5, time.Duration(time.Second), http.StatusTooManyRequests),
DoRetryForStatusCodes(1, time.Duration(time.Second), http.StatusTooManyRequests),
)
if time.Since(start) < d {
@@ -795,3 +809,21 @@ func TestDelayWithRetryAfterWithSuccess(t *testing.T) {
r.Status, client.Attempts()-1)
}
}
func TestDoRetryForStatusCodes_NilResponse(t *testing.T) {
client := mocks.NewSender()
client.AppendResponse(nil)
client.SetError(fmt.Errorf("faux error"))
r, err := SendWithSender(client, mocks.NewRequest(),
DoRetryForStatusCodes(3, time.Duration(1*time.Second), StatusCodesForRetry...),
)
Respond(r,
ByDiscardingBody(),
ByClosing())
if err != nil || client.Attempts() != 2 {
t.Fatalf("autorest: Sender#TestDoRetryForStatusCodes_NilResponse -- Got: non-nil error or wrong number of attempts - %v", err)
}
}
+14
View File
@@ -3,6 +3,20 @@ Package to provides helpers to ease working with pointer values of marshalled st
*/
package to
// 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.
// String returns a string value for the passed string pointer. It returns the empty string if the
// pointer is nil.
func String(s *string) string {
+14
View File
@@ -1,5 +1,19 @@
package to
// 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 (
"reflect"
"testing"
+67 -27
View File
@@ -1,15 +1,31 @@
package autorest
// 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"
"encoding/xml"
"fmt"
"io"
"net/http"
"net/url"
"reflect"
"sort"
"strings"
"github.com/Azure/go-autorest/autorest/adal"
)
// EncodedAs is a series of constants specifying various data encodings
@@ -123,13 +139,38 @@ func MapToValues(m map[string]interface{}) url.Values {
return v
}
// String method converts interface v to string. If interface is a list, it
// joins list elements using separator.
func String(v interface{}, sep ...string) string {
if len(sep) > 0 {
return ensureValueString(strings.Join(v.([]string), sep[0]))
// AsStringSlice method converts interface{} to []string. This expects a
//that the parameter passed to be a slice or array of a type that has the underlying
//type a string.
func AsStringSlice(s interface{}) ([]string, error) {
v := reflect.ValueOf(s)
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
return nil, NewError("autorest", "AsStringSlice", "the value's type is not an array.")
}
return ensureValueString(v)
stringSlice := make([]string, 0, v.Len())
for i := 0; i < v.Len(); i++ {
stringSlice = append(stringSlice, v.Index(i).String())
}
return stringSlice, nil
}
// String method converts interface v to string. If interface is a list, it
// joins list elements using the seperator. Note that only sep[0] will be used for
// joining if any separator is specified.
func String(v interface{}, sep ...string) string {
if len(sep) == 0 {
return ensureValueString(v)
}
stringSlice, ok := v.([]string)
if ok == false {
var err error
stringSlice, err = AsStringSlice(v)
if err != nil {
panic(fmt.Sprintf("autorest: Couldn't convert value to a string %s.", err))
}
}
return ensureValueString(strings.Join(stringSlice, sep[0]))
}
// Encode method encodes url path and query parameters.
@@ -153,26 +194,25 @@ func queryEscape(s string) string {
return url.QueryEscape(s)
}
// This method is same as Encode() method of "net/url" go package,
// except it does not encode the query parameters because they
// already come encoded. It formats values map in query format (bar=foo&a=b).
func createQuery(v url.Values) string {
var buf bytes.Buffer
keys := make([]string, 0, len(v))
for k := range v {
keys = append(keys, k)
// ChangeToGet turns the specified http.Request into a GET (it assumes it wasn't).
// This is mainly useful for long-running operations that use the Azure-AsyncOperation
// header, so we change the initial PUT into a GET to retrieve the final result.
func ChangeToGet(req *http.Request) *http.Request {
req.Method = "GET"
req.Body = nil
req.ContentLength = 0
req.Header.Del("Content-Length")
return req
}
// IsTokenRefreshError returns true if the specified error implements the TokenRefreshError
// interface. If err is a DetailedError it will walk the chain of Original errors.
func IsTokenRefreshError(err error) bool {
if _, ok := err.(adal.TokenRefreshError); ok {
return true
}
sort.Strings(keys)
for _, k := range keys {
vs := v[k]
prefix := url.QueryEscape(k) + "="
for _, v := range vs {
if buf.Len() > 0 {
buf.WriteByte('&')
}
buf.WriteString(prefix)
buf.WriteString(v)
}
if de, ok := err.(DetailedError); ok {
return IsTokenRefreshError(de.Original)
}
return buf.String()
return false
}
+56 -2
View File
@@ -1,5 +1,19 @@
package autorest
// 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"
@@ -215,11 +229,51 @@ func ExampleString() {
func TestStringWithValidString(t *testing.T) {
i := 123
if String(i) != "123" {
t.Fatal("autorest: String method failed to convert integer 123 to string")
if got, want := String(i), "123"; got != want {
t.Logf("got: %q\nwant: %q", got, want)
t.Fail()
}
}
func TestStringWithStringSlice(t *testing.T) {
s := []string{"string1", "string2"}
if got, want := String(s, ","), "string1,string2"; got != want {
t.Logf("got: %q\nwant: %q", got, want)
t.Fail()
}
}
func TestStringWithEnum(t *testing.T) {
type TestEnumType string
s := TestEnumType("string1")
if got, want := String(s), "string1"; got != want {
t.Logf("got: %q\nwant: %q", got, want)
t.Fail()
}
}
func TestStringWithEnumSlice(t *testing.T) {
type TestEnumType string
s := []TestEnumType{"string1", "string2"}
if got, want := String(s, ","), "string1,string2"; got != want {
t.Logf("got: %q\nwant: %q", got, want)
t.Fail()
}
}
func ExampleAsStringSlice() {
type TestEnumType string
a := []TestEnumType{"value1", "value2"}
b, _ := AsStringSlice(a)
for _, c := range b {
fmt.Println(c)
}
// Output:
// value1
// value2
}
func TestEncodeWithValidPath(t *testing.T) {
s := Encode("Path", "Hello Gopher")
if s != "Hello%20Gopher" {
+14
View File
@@ -1,5 +1,19 @@
package utils
// 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 (
"fmt"
"os"
+14
View File
@@ -1,5 +1,19 @@
package utils
// 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"
"os/exec"
+16 -5
View File
@@ -3,6 +3,20 @@ Package validation provides methods for validating parameter value using reflect
*/
package validation
// 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 (
"fmt"
"reflect"
@@ -91,15 +105,12 @@ func validateStruct(x reflect.Value, v Constraint, name ...string) error {
return createError(x, v, fmt.Sprintf("field %q doesn't exist", v.Target))
}
if err := Validate([]Validation{
return Validate([]Validation{
{
TargetValue: getInterfaceValue(f),
Constraints: []Constraint{v},
},
}); err != nil {
return err
}
return nil
})
}
func validatePtr(x reflect.Value, v Constraint) error {
@@ -1,5 +1,19 @@
package validation
// 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 (
"fmt"
"reflect"
+14
View File
@@ -1,5 +1,19 @@
package autorest
// 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"
"fmt"
Binary file not shown.
Binary file not shown.
+12
View File
@@ -0,0 +1,12 @@
{
"clientId": "client-id-123",
"clientSecret": "client-secret-456",
"subscriptionId": "sub-id-789",
"tenantId": "tenant-id-123",
"activeDirectoryEndpointUrl": "https://login.microsoftonline.com",
"resourceManagerEndpointUrl": "https://management.azure.com/",
"activeDirectoryGraphResourceId": "https://graph.windows.net/",
"sqlManagementEndpointUrl": "https://management.core.windows.net:8443/",
"galleryEndpointUrl": "https://gallery.azure.com/",
"managementEndpointUrl": "https://management.core.windows.net/"
}