mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-05-06 22:18:28 +00:00
Upgrade AWS SDK to the latest version
This commit is contained in:
+14
@@ -0,0 +1,14 @@
|
||||
Please fill out the sections below to help us address your issue.
|
||||
|
||||
### Version of AWS SDK for Go?
|
||||
|
||||
|
||||
### Version of Go (`go version`)?
|
||||
|
||||
|
||||
### What issue did you see?
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
If you have have an runnable example, please include it.
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
For changes to files under the `/model/` folder, and manual edits to autogenerated code (e.g. `/service/s3/api.go`) please create an Issue instead of a PR for those type of changes.
|
||||
|
||||
If there is an existing bug or feature this PR is answers please reference it here.
|
||||
+2
-1
@@ -1,12 +1,13 @@
|
||||
language: go
|
||||
|
||||
sudo: false
|
||||
sudo: required
|
||||
|
||||
go:
|
||||
- 1.5
|
||||
- 1.6
|
||||
- 1.7
|
||||
- 1.8
|
||||
- 1.9
|
||||
- tip
|
||||
|
||||
# Use Go 1.5's vendoring experiment for 1.5 tests.
|
||||
|
||||
+1121
File diff suppressed because it is too large
Load Diff
+5
@@ -0,0 +1,5 @@
|
||||
### SDK Features
|
||||
|
||||
### SDK Enhancements
|
||||
|
||||
### SDK Bugs
|
||||
|
||||
+5
@@ -64,6 +64,11 @@ Please be aware of the following notes prior to opening a pull request:
|
||||
SDK's test coverage percentage are unlikely to be merged until tests have
|
||||
been added.
|
||||
|
||||
5. The JSON files under the SDK's `models` folder are sourced from outside the SDK.
|
||||
Such as `models/apis/ec2/2016-11-15/api.json`. We will not accept pull requests
|
||||
directly on these models. If you discover an issue with the models please
|
||||
create a Github [issue](issues) describing the issue.
|
||||
|
||||
### Testing
|
||||
|
||||
To run the tests locally, running the `make unit` command will `go get` the
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/go-ini/ini"
|
||||
packages = ["."]
|
||||
revision = "300e940a926eb277d3901b20bdfcc54928ad3642"
|
||||
version = "v1.25.4"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/jmespath/go-jmespath"
|
||||
packages = ["."]
|
||||
revision = "0b12d6b5"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "51a86a867df617990082dec6b868e4efe2fdb2ed0e02a3daa93cd30f962b5085"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
|
||||
ignored = [
|
||||
# Testing/Example/Codegen dependencies
|
||||
"github.com/stretchr/testify",
|
||||
"github.com/stretchr/testify/assert",
|
||||
"github.com/stretchr/testify/require",
|
||||
"github.com/go-sql-driver/mysql",
|
||||
"github.com/gucumber/gucumber",
|
||||
"github.com/pkg/errors",
|
||||
"golang.org/x/net",
|
||||
"golang.org/x/net/html",
|
||||
"golang.org/x/net/http2",
|
||||
"golang.org/x/text",
|
||||
"golang.org/x/text/html",
|
||||
"golang.org/x/tools",
|
||||
"golang.org/x/tools/go/loader",
|
||||
]
|
||||
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/go-ini/ini"
|
||||
version = "1.25.4"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/jmespath/go-jmespath"
|
||||
revision = "0b12d6b5"
|
||||
#version = "0.2.2"
|
||||
+15
-3
@@ -5,7 +5,8 @@ LINTIGNORESTUTTER='service/[^/]+/(api|service)\.go:.+(and that stutters)'
|
||||
LINTIGNOREINFLECT='service/[^/]+/(api|errors|service)\.go:.+(method|const) .+ should be '
|
||||
LINTIGNOREINFLECTS3UPLOAD='service/s3/s3manager/upload\.go:.+struct field SSEKMSKeyId should be '
|
||||
LINTIGNOREDEPS='vendor/.+\.go'
|
||||
UNIT_TEST_TAGS="example codegen"
|
||||
LINTIGNOREPKGCOMMENT='service/[^/]+/doc_custom.go:.+package comment should be of the form'
|
||||
UNIT_TEST_TAGS="example codegen awsinclude"
|
||||
|
||||
SDK_WITH_VENDOR_PKGS=$(shell go list -tags ${UNIT_TEST_TAGS} ./... | grep -v "/vendor/src")
|
||||
SDK_ONLY_PKGS=$(shell go list ./... | grep -v "/vendor/")
|
||||
@@ -64,13 +65,16 @@ integration: get-deps-tests integ-custom smoke-tests performance
|
||||
integ-custom:
|
||||
go test -tags "integration" ./awstesting/integration/customizations/...
|
||||
|
||||
cleanup-integ:
|
||||
go run -tags "integration" ./awstesting/cmd/bucket_cleanup/main.go "aws-sdk-go-integration"
|
||||
|
||||
smoke-tests: get-deps-tests
|
||||
gucumber -go-tags "integration" ./awstesting/integration/smoke
|
||||
|
||||
performance: get-deps-tests
|
||||
AWS_TESTING_LOG_RESULTS=${log-detailed} AWS_TESTING_REGION=$(region) AWS_TESTING_DB_TABLE=$(table) gucumber -go-tags "integration" ./awstesting/performance
|
||||
|
||||
sandbox-tests: sandbox-test-go15 sandbox-test-go15-novendorexp sandbox-test-go16 sandbox-test-go17 sandbox-test-go18 sandbox-test-gotip
|
||||
sandbox-tests: sandbox-test-go15 sandbox-test-go15-novendorexp sandbox-test-go16 sandbox-test-go17 sandbox-test-go18 sandbox-test-go19 sandbox-test-gotip
|
||||
|
||||
sandbox-build-go15:
|
||||
docker build -f ./awstesting/sandbox/Dockerfile.test.go1.5 -t "aws-sdk-go-1.5" .
|
||||
@@ -107,6 +111,13 @@ sandbox-go18: sandbox-build-go18
|
||||
sandbox-test-go18: sandbox-build-go18
|
||||
docker run -t aws-sdk-go-1.8
|
||||
|
||||
sandbox-build-go19:
|
||||
docker build -f ./awstesting/sandbox/Dockerfile.test.go1.8 -t "aws-sdk-go-1.9" .
|
||||
sandbox-go19: sandbox-build-go19
|
||||
docker run -i -t aws-sdk-go-1.9 bash
|
||||
sandbox-test-go19: sandbox-build-go19
|
||||
docker run -t aws-sdk-go-1.9
|
||||
|
||||
sandbox-build-gotip:
|
||||
@echo "Run make update-aws-golang-tip, if this test fails because missing aws-golang:tip container"
|
||||
docker build -f ./awstesting/sandbox/Dockerfile.test.gotip -t "aws-sdk-go-tip" .
|
||||
@@ -123,7 +134,7 @@ verify: get-deps-verify lint vet
|
||||
lint:
|
||||
@echo "go lint SDK and vendor packages"
|
||||
@lint=`if [ \( -z "${SDK_GO_1_4}" \) -a \( -z "${SDK_GO_1_5}" \) ]; then golint ./...; else echo "skipping golint"; fi`; \
|
||||
lint=`echo "$$lint" | grep -E -v -e ${LINTIGNOREDOT} -e ${LINTIGNOREDOC} -e ${LINTIGNORECONST} -e ${LINTIGNORESTUTTER} -e ${LINTIGNOREINFLECT} -e ${LINTIGNOREDEPS} -e ${LINTIGNOREINFLECTS3UPLOAD}`; \
|
||||
lint=`echo "$$lint" | grep -E -v -e ${LINTIGNOREDOT} -e ${LINTIGNOREDOC} -e ${LINTIGNORECONST} -e ${LINTIGNORESTUTTER} -e ${LINTIGNOREINFLECT} -e ${LINTIGNOREDEPS} -e ${LINTIGNOREINFLECTS3UPLOAD} -e ${LINTIGNOREPKGCOMMENT}`; \
|
||||
echo "$$lint"; \
|
||||
if [ "$$lint" != "" ] && [ "$$lint" != "skipping golint" ]; then exit 1; fi
|
||||
|
||||
@@ -149,6 +160,7 @@ get-deps-tests:
|
||||
go get github.com/stretchr/testify
|
||||
go get github.com/smartystreets/goconvey
|
||||
go get golang.org/x/net/html
|
||||
go get golang.org/x/net/http2
|
||||
|
||||
get-deps-verify:
|
||||
@echo "go get SDK verification utilities"
|
||||
|
||||
+382
-67
@@ -1,9 +1,6 @@
|
||||
# AWS SDK for Go
|
||||
[](http://docs.aws.amazon.com/sdk-for-go/api) [](https://gitter.im/aws/aws-sdk-go?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://travis-ci.org/aws/aws-sdk-go) [](https://github.com/aws/aws-sdk-go/blob/master/LICENSE.txt)
|
||||
|
||||
[](http://docs.aws.amazon.com/sdk-for-go/api)
|
||||
[](https://gitter.im/aws/aws-sdk-go?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://travis-ci.org/aws/aws-sdk-go)
|
||||
[](https://github.com/aws/aws-sdk-go/blob/master/LICENSE.txt)
|
||||
# AWS SDK for Go
|
||||
|
||||
aws-sdk-go is the official AWS SDK for the Go programming language.
|
||||
|
||||
@@ -31,16 +28,17 @@ These two processes will still include the `vendor` folder and it should be dele
|
||||
## Getting Help
|
||||
|
||||
Please use these community resources for getting help. We use the GitHub issues for tracking bugs and feature requests.
|
||||
|
||||
* Ask a question on [StackOverflow](http://stackoverflow.com/) and tag it with the [`aws-sdk-go`](http://stackoverflow.com/questions/tagged/aws-sdk-go) tag.
|
||||
* Come join the AWS SDK for Go community chat on [gitter](https://gitter.im/aws/aws-sdk-go).
|
||||
* Open a support ticket with [AWS Support](http://docs.aws.amazon.com/awssupport/latest/user/getting-started.html).
|
||||
* If you think you may of found a bug, please open an [issue](https://github.com/aws/aws-sdk-go/issues/new).
|
||||
* If you think you may have found a bug, please open an [issue](https://github.com/aws/aws-sdk-go/issues/new).
|
||||
|
||||
## Opening Issues
|
||||
|
||||
If you encounter a bug with the AWS SDK for Go we would like to hear about it. Search the [existing issues]( https://github.com/aws/aws-sdk-go/issues) and see if others are also experiencing the issue before opening a new issue. Please include the version of AWS SDK for Go, Go language, and OS you’re using. Please also include repro case when appropriate.
|
||||
If you encounter a bug with the AWS SDK for Go we would like to hear about it. Search the [existing issues](https://github.com/aws/aws-sdk-go/issues) and see if others are also experiencing the issue before opening a new issue. Please include the version of AWS SDK for Go, Go language, and OS you’re using. Please also include repro case when appropriate.
|
||||
|
||||
The GitHub issues are intended for bug reports and feature requests. For help and questions with using AWS SDK for GO please make use of the resources listed in the [Getting Help]( https://github.com/aws/aws-sdk-go#getting-help) section. Keeping the list of open issues lean will help us respond in a timely manner.
|
||||
The GitHub issues are intended for bug reports and feature requests. For help and questions with using AWS SDK for GO please make use of the resources listed in the [Getting Help](https://github.com/aws/aws-sdk-go#getting-help) section. Keeping the list of open issues lean will help us respond in a timely manner.
|
||||
|
||||
## Reference Documentation
|
||||
|
||||
@@ -52,80 +50,397 @@ The GitHub issues are intended for bug reports and feature requests. For help an
|
||||
|
||||
[`SDK Examples`](https://github.com/aws/aws-sdk-go/tree/master/example) - Included in the SDK's repo are a several hand crafted examples using the SDK features and AWS services.
|
||||
|
||||
## Configuring Credentials
|
||||
## Overview of SDK's Packages
|
||||
|
||||
Before using the SDK, ensure that you've configured credentials. The best
|
||||
way to configure credentials on a development machine is to use the
|
||||
`~/.aws/credentials` file, which might look like:
|
||||
The SDK is composed of two main components, SDK core, and service clients.
|
||||
The SDK core packages are all available under the aws package at the root of
|
||||
the SDK. Each client for a supported AWS service is available within its own
|
||||
package under the service folder at the root of the SDK.
|
||||
|
||||
```
|
||||
[default]
|
||||
aws_access_key_id = AKID1234567890
|
||||
aws_secret_access_key = MY-SECRET-KEY
|
||||
```
|
||||
* aws - SDK core, provides common shared types such as Config, Logger,
|
||||
and utilities to make working with API parameters easier.
|
||||
|
||||
You can learn more about the credentials file from this
|
||||
[blog post](http://blogs.aws.amazon.com/security/post/Tx3D6U6WSFGOK2H/A-New-and-Standardized-Way-to-Manage-Credentials-in-the-AWS-SDKs).
|
||||
* awserr - Provides the error interface that the SDK will use for all
|
||||
errors that occur in the SDK's processing. This includes service API
|
||||
response errors as well. The Error type is made up of a code and message.
|
||||
Cast the SDK's returned error type to awserr.Error and call the Code
|
||||
method to compare returned error to specific error codes. See the package's
|
||||
documentation for additional values that can be extracted such as RequestID.
|
||||
|
||||
Alternatively, you can set the following environment variables:
|
||||
* credentials - Provides the types and built in credentials providers
|
||||
the SDK will use to retrieve AWS credentials to make API requests with.
|
||||
Nested under this folder are also additional credentials providers such as
|
||||
stscreds for assuming IAM roles, and ec2rolecreds for EC2 Instance roles.
|
||||
|
||||
```
|
||||
AWS_ACCESS_KEY_ID=AKID1234567890
|
||||
AWS_SECRET_ACCESS_KEY=MY-SECRET-KEY
|
||||
```
|
||||
* endpoints - Provides the AWS Regions and Endpoints metadata for the SDK.
|
||||
Use this to lookup AWS service endpoint information such as which services
|
||||
are in a region, and what regions a service is in. Constants are also provided
|
||||
for all region identifiers, e.g UsWest2RegionID for "us-west-2".
|
||||
|
||||
### AWS shared config file (`~/.aws/config`)
|
||||
The AWS SDK for Go added support the shared config file in release [v1.3.0](https://github.com/aws/aws-sdk-go/releases/tag/v1.3.0). You can opt into enabling support for the shared config by setting the environment variable `AWS_SDK_LOAD_CONFIG` to a truthy value. See the [Session](https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/sessions.html) docs for more information about this feature.
|
||||
* session - Provides initial default configuration, and load
|
||||
configuration from external sources such as environment and shared
|
||||
credentials file.
|
||||
|
||||
## Using the Go SDK
|
||||
* request - Provides the API request sending, and retry logic for the SDK.
|
||||
This package also includes utilities for defining your own request
|
||||
retryer, and configuring how the SDK processes the request.
|
||||
|
||||
To use a service in the SDK, create a service variable by calling the `New()`
|
||||
function. Once you have a service client, you can call API operations which each
|
||||
return response data and a possible error.
|
||||
* service - Clients for AWS services. All services supported by the SDK are
|
||||
available under this folder.
|
||||
|
||||
To list a set of instance IDs from EC2, you could run:
|
||||
## How to Use the SDK's AWS Service Clients
|
||||
|
||||
The SDK includes the Go types and utilities you can use to make requests to
|
||||
AWS service APIs. Within the service folder at the root of the SDK you'll find
|
||||
a package for each AWS service the SDK supports. All service clients follows
|
||||
a common pattern of creation and usage.
|
||||
|
||||
When creating a client for an AWS service you'll first need to have a Session
|
||||
value constructed. The Session provides shared configuration that can be shared
|
||||
between your service clients. When service clients are created you can pass
|
||||
in additional configuration via the aws.Config type to override configuration
|
||||
provided by in the Session to create service client instances with custom
|
||||
configuration.
|
||||
|
||||
Once the service's client is created you can use it to make API requests the
|
||||
AWS service. These clients are safe to use concurrently.
|
||||
|
||||
## Configuring the SDK
|
||||
|
||||
In the AWS SDK for Go, you can configure settings for service clients, such
|
||||
as the log level and maximum number of retries. Most settings are optional;
|
||||
however, for each service client, you must specify a region and your credentials.
|
||||
The SDK uses these values to send requests to the correct AWS region and sign
|
||||
requests with the correct credentials. You can specify these values as part
|
||||
of a session or as environment variables.
|
||||
|
||||
See the SDK's [configuration guide][config_guide] for more information.
|
||||
|
||||
See the [session][session_pkg] package documentation for more information on how to use Session
|
||||
with the SDK.
|
||||
|
||||
See the [Config][config_typ] type in the [aws][aws_pkg] package for more information on configuration
|
||||
options.
|
||||
|
||||
[config_guide]: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html
|
||||
[session_pkg]: https://docs.aws.amazon.com/sdk-for-go/api/aws/session/
|
||||
[config_typ]: https://docs.aws.amazon.com/sdk-for-go/api/aws/#Config
|
||||
[aws_pkg]: https://docs.aws.amazon.com/sdk-for-go/api/aws/
|
||||
|
||||
### Configuring Credentials
|
||||
|
||||
When using the SDK you'll generally need your AWS credentials to authenticate
|
||||
with AWS services. The SDK supports multiple methods of supporting these
|
||||
credentials. By default the SDK will source credentials automatically from
|
||||
its default credential chain. See the session package for more information
|
||||
on this chain, and how to configure it. The common items in the credential
|
||||
chain are the following:
|
||||
|
||||
* Environment Credentials - Set of environment variables that are useful
|
||||
when sub processes are created for specific roles.
|
||||
|
||||
* Shared Credentials file (~/.aws/credentials) - This file stores your
|
||||
credentials based on a profile name and is useful for local development.
|
||||
|
||||
* EC2 Instance Role Credentials - Use EC2 Instance Role to assign credentials
|
||||
to application running on an EC2 instance. This removes the need to manage
|
||||
credential files in production.
|
||||
|
||||
Credentials can be configured in code as well by setting the Config's Credentials
|
||||
value to a custom provider or using one of the providers included with the
|
||||
SDK to bypass the default credential chain and use a custom one. This is
|
||||
helpful when you want to instruct the SDK to only use a specific set of
|
||||
credentials or providers.
|
||||
|
||||
This example creates a credential provider for assuming an IAM role, "myRoleARN"
|
||||
and configures the S3 service client to use that role for API requests.
|
||||
|
||||
```go
|
||||
package main
|
||||
// Initial credentials loaded from SDK's default credential chain. Such as
|
||||
// the environment, shared credentials (~/.aws/credentials), or EC2 Instance
|
||||
// Role. These credentials will be used to to make the STS Assume Role API.
|
||||
sess := session.Must(session.NewSession())
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
// Create the credentials from AssumeRoleProvider to assume the role
|
||||
// referenced by the "myRoleARN" ARN.
|
||||
creds := stscreds.NewCredentials(sess, "myRoleArn")
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
sess, err := session.NewSession()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Create an EC2 service object in the "us-west-2" region
|
||||
// Note that you can also configure your region globally by
|
||||
// exporting the AWS_REGION environment variable
|
||||
svc := ec2.New(sess, &aws.Config{Region: aws.String("us-west-2")})
|
||||
|
||||
// Call the DescribeInstances Operation
|
||||
resp, err := svc.DescribeInstances(nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// resp has all of the response data, pull out instance IDs:
|
||||
fmt.Println("> Number of reservation sets: ", len(resp.Reservations))
|
||||
for idx, res := range resp.Reservations {
|
||||
fmt.Println(" > Number of instances: ", len(res.Instances))
|
||||
for _, inst := range resp.Reservations[idx].Instances {
|
||||
fmt.Println(" - Instance ID: ", *inst.InstanceId)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Create service client value configured for credentials
|
||||
// from assumed role.
|
||||
svc := s3.New(sess, &aws.Config{Credentials: creds})
|
||||
```
|
||||
|
||||
You can find more information and operations in our
|
||||
[API documentation](http://docs.aws.amazon.com/sdk-for-go/api/).
|
||||
See the [credentials][credentials_pkg] package documentation for more information on credential
|
||||
providers included with the SDK, and how to customize the SDK's usage of
|
||||
credentials.
|
||||
|
||||
The SDK has support for the shared configuration file (~/.aws/config). This
|
||||
support can be enabled by setting the environment variable, "AWS_SDK_LOAD_CONFIG=1",
|
||||
or enabling the feature in code when creating a Session via the
|
||||
Option's SharedConfigState parameter.
|
||||
|
||||
```go
|
||||
sess := session.Must(session.NewSessionWithOptions(session.Options{
|
||||
SharedConfigState: session.SharedConfigEnable,
|
||||
}))
|
||||
```
|
||||
|
||||
[credentials_pkg]: ttps://docs.aws.amazon.com/sdk-for-go/api/aws/credentials
|
||||
|
||||
### Configuring AWS Region
|
||||
|
||||
In addition to the credentials you'll need to specify the region the SDK
|
||||
will use to make AWS API requests to. In the SDK you can specify the region
|
||||
either with an environment variable, or directly in code when a Session or
|
||||
service client is created. The last value specified in code wins if the region
|
||||
is specified multiple ways.
|
||||
|
||||
To set the region via the environment variable set the "AWS_REGION" to the
|
||||
region you want to the SDK to use. Using this method to set the region will
|
||||
allow you to run your application in multiple regions without needing additional
|
||||
code in the application to select the region.
|
||||
|
||||
AWS_REGION=us-west-2
|
||||
|
||||
The endpoints package includes constants for all regions the SDK knows. The
|
||||
values are all suffixed with RegionID. These values are helpful, because they
|
||||
reduce the need to type the region string manually.
|
||||
|
||||
To set the region on a Session use the aws package's Config struct parameter
|
||||
Region to the AWS region you want the service clients created from the session to
|
||||
use. This is helpful when you want to create multiple service clients, and
|
||||
all of the clients make API requests to the same region.
|
||||
|
||||
```go
|
||||
sess := session.Must(session.NewSession(&aws.Config{
|
||||
Region: aws.String(endpoints.UsWest2RegionID),
|
||||
}))
|
||||
```
|
||||
|
||||
See the [endpoints][endpoints_pkg] package for the AWS Regions and Endpoints metadata.
|
||||
|
||||
In addition to setting the region when creating a Session you can also set
|
||||
the region on a per service client bases. This overrides the region of a
|
||||
Session. This is helpful when you want to create service clients in specific
|
||||
regions different from the Session's region.
|
||||
|
||||
```go
|
||||
svc := s3.New(sess, &aws.Config{
|
||||
Region: aws.String(endpoints.UsWest2RegionID),
|
||||
})
|
||||
```
|
||||
|
||||
See the [Config][config_typ] type in the [aws][aws_pkg] package for more information and additional
|
||||
options such as setting the Endpoint, and other service client configuration options.
|
||||
|
||||
[endpoints_pkg]: https://docs.aws.amazon.com/sdk-for-go/api/aws/endpoints/
|
||||
|
||||
## Making API Requests
|
||||
|
||||
Once the client is created you can make an API request to the service.
|
||||
Each API method takes a input parameter, and returns the service response
|
||||
and an error. The SDK provides methods for making the API call in multiple ways.
|
||||
|
||||
In this list we'll use the S3 ListObjects API as an example for the different
|
||||
ways of making API requests.
|
||||
|
||||
* ListObjects - Base API operation that will make the API request to the service.
|
||||
|
||||
* ListObjectsRequest - API methods suffixed with Request will construct the
|
||||
API request, but not send it. This is also helpful when you want to get a
|
||||
presigned URL for a request, and share the presigned URL instead of your
|
||||
application making the request directly.
|
||||
|
||||
* ListObjectsPages - Same as the base API operation, but uses a callback to
|
||||
automatically handle pagination of the API's response.
|
||||
|
||||
* ListObjectsWithContext - Same as base API operation, but adds support for
|
||||
the Context pattern. This is helpful for controlling the canceling of in
|
||||
flight requests. See the Go standard library context package for more
|
||||
information. This method also takes request package's Option functional
|
||||
options as the variadic argument for modifying how the request will be
|
||||
made, or extracting information from the raw HTTP response.
|
||||
|
||||
* ListObjectsPagesWithContext - same as ListObjectsPages, but adds support for
|
||||
the Context pattern. Similar to ListObjectsWithContext this method also
|
||||
takes the request package's Option function option types as the variadic
|
||||
argument.
|
||||
|
||||
In addition to the API operations the SDK also includes several higher level
|
||||
methods that abstract checking for and waiting for an AWS resource to be in
|
||||
a desired state. In this list we'll use WaitUntilBucketExists to demonstrate
|
||||
the different forms of waiters.
|
||||
|
||||
* WaitUntilBucketExists. - Method to make API request to query an AWS service for
|
||||
a resource's state. Will return successfully when that state is accomplished.
|
||||
|
||||
* WaitUntilBucketExistsWithContext - Same as WaitUntilBucketExists, but adds
|
||||
support for the Context pattern. In addition these methods take request
|
||||
package's WaiterOptions to configure the waiter, and how underlying request
|
||||
will be made by the SDK.
|
||||
|
||||
The API method will document which error codes the service might return for
|
||||
the operation. These errors will also be available as const strings prefixed
|
||||
with "ErrCode" in the service client's package. If there are no errors listed
|
||||
in the API's SDK documentation you'll need to consult the AWS service's API
|
||||
documentation for the errors that could be returned.
|
||||
|
||||
```go
|
||||
ctx := context.Background()
|
||||
|
||||
result, err := svc.GetObjectWithContext(ctx, &s3.GetObjectInput{
|
||||
Bucket: aws.String("my-bucket"),
|
||||
Key: aws.String("my-key"),
|
||||
})
|
||||
if err != nil {
|
||||
// Cast err to awserr.Error to handle specific error codes.
|
||||
aerr, ok := err.(awserr.Error)
|
||||
if ok && aerr.Code() == s3.ErrCodeNoSuchKey {
|
||||
// Specific error code handling
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Make sure to close the body when done with it for S3 GetObject APIs or
|
||||
// will leak connections.
|
||||
defer result.Body.Close()
|
||||
|
||||
fmt.Println("Object Size:", aws.StringValue(result.ContentLength))
|
||||
```
|
||||
|
||||
### API Request Pagination and Resource Waiters
|
||||
|
||||
Pagination helper methods are suffixed with "Pages", and provide the
|
||||
functionality needed to round trip API page requests. Pagination methods
|
||||
take a callback function that will be called for each page of the API's response.
|
||||
|
||||
```go
|
||||
objects := []string{}
|
||||
err := svc.ListObjectsPagesWithContext(ctx, &s3.ListObjectsInput{
|
||||
Bucket: aws.String(myBucket),
|
||||
}, func(p *s3.ListObjectsOutput, lastPage bool) bool {
|
||||
for _, o := range p.Contents {
|
||||
objects = append(objects, aws.StringValue(o.Key))
|
||||
}
|
||||
return true // continue paging
|
||||
})
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to list objects for bucket, %s, %v", myBucket, err))
|
||||
}
|
||||
|
||||
fmt.Println("Objects in bucket:", objects)
|
||||
```
|
||||
|
||||
Waiter helper methods provide the functionality to wait for an AWS resource
|
||||
state. These methods abstract the logic needed to to check the state of an
|
||||
AWS resource, and wait until that resource is in a desired state. The waiter
|
||||
will block until the resource is in the state that is desired, an error occurs,
|
||||
or the waiter times out. If a resource times out the error code returned will
|
||||
be request.WaiterResourceNotReadyErrorCode.
|
||||
|
||||
```go
|
||||
err := svc.WaitUntilBucketExistsWithContext(ctx, &s3.HeadBucketInput{
|
||||
Bucket: aws.String(myBucket),
|
||||
})
|
||||
if err != nil {
|
||||
aerr, ok := err.(awserr.Error)
|
||||
if ok && aerr.Code() == request.WaiterResourceNotReadyErrorCode {
|
||||
fmt.Fprintf(os.Stderr, "timed out while waiting for bucket to exist")
|
||||
}
|
||||
panic(fmt.Errorf("failed to wait for bucket to exist, %v", err))
|
||||
}
|
||||
fmt.Println("Bucket", myBucket, "exists")
|
||||
```
|
||||
|
||||
## Complete SDK Example
|
||||
|
||||
This example shows a complete working Go file which will upload a file to S3
|
||||
and use the Context pattern to implement timeout logic that will cancel the
|
||||
request if it takes too long. This example highlights how to use sessions,
|
||||
create a service client, make a request, handle the error, and process the
|
||||
response.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
)
|
||||
|
||||
// Uploads a file to S3 given a bucket and object key. Also takes a duration
|
||||
// value to terminate the update if it doesn't complete within that time.
|
||||
//
|
||||
// The AWS Region needs to be provided in the AWS shared config or on the
|
||||
// environment variable as `AWS_REGION`. Credentials also must be provided
|
||||
// Will default to shared config file, but can load from environment if provided.
|
||||
//
|
||||
// Usage:
|
||||
// # Upload myfile.txt to myBucket/myKey. Must complete within 10 minutes or will fail
|
||||
// go run withContext.go -b mybucket -k myKey -d 10m < myfile.txt
|
||||
func main() {
|
||||
var bucket, key string
|
||||
var timeout time.Duration
|
||||
|
||||
flag.StringVar(&bucket, "b", "", "Bucket name.")
|
||||
flag.StringVar(&key, "k", "", "Object key name.")
|
||||
flag.DurationVar(&timeout, "d", 0, "Upload timeout.")
|
||||
flag.Parse()
|
||||
|
||||
// All clients require a Session. The Session provides the client with
|
||||
// shared configuration such as region, endpoint, and credentials. A
|
||||
// Session should be shared where possible to take advantage of
|
||||
// configuration and credential caching. See the session package for
|
||||
// more information.
|
||||
sess := session.Must(session.NewSession())
|
||||
|
||||
// Create a new instance of the service's client with a Session.
|
||||
// Optional aws.Config values can also be provided as variadic arguments
|
||||
// to the New function. This option allows you to provide service
|
||||
// specific configuration.
|
||||
svc := s3.New(sess)
|
||||
|
||||
// Create a context with a timeout that will abort the upload if it takes
|
||||
// more than the passed in timeout.
|
||||
ctx := context.Background()
|
||||
var cancelFn func()
|
||||
if timeout > 0 {
|
||||
ctx, cancelFn = context.WithTimeout(ctx, timeout)
|
||||
}
|
||||
// Ensure the context is canceled to prevent leaking.
|
||||
// See context package for more information, https://golang.org/pkg/context/
|
||||
defer cancelFn()
|
||||
|
||||
// Uploads the object to S3. The Context will interrupt the request if the
|
||||
// timeout expires.
|
||||
_, err := svc.PutObjectWithContext(ctx, &s3.PutObjectInput{
|
||||
Bucket: aws.String(bucket),
|
||||
Key: aws.String(key),
|
||||
Body: os.Stdin,
|
||||
})
|
||||
if err != nil {
|
||||
if aerr, ok := err.(awserr.Error); ok && aerr.Code() == request.CanceledErrorCode {
|
||||
// If the SDK can determine the request or retry delay was canceled
|
||||
// by a context the CanceledErrorCode error code will be returned.
|
||||
fmt.Fprintf(os.Stderr, "upload canceled due to timeout, %v\n", err)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "failed to upload object, %v\n", err)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("successfully uploaded file to %s/%s\n", bucket, key)
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
// Package arn provides a parser for interacting with Amazon Resource Names.
|
||||
package arn
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
arnDelimiter = ":"
|
||||
arnSections = 6
|
||||
arnPrefix = "arn:"
|
||||
|
||||
// zero-indexed
|
||||
sectionPartition = 1
|
||||
sectionService = 2
|
||||
sectionRegion = 3
|
||||
sectionAccountID = 4
|
||||
sectionResource = 5
|
||||
|
||||
// errors
|
||||
invalidPrefix = "arn: invalid prefix"
|
||||
invalidSections = "arn: not enough sections"
|
||||
)
|
||||
|
||||
// ARN captures the individual fields of an Amazon Resource Name.
|
||||
// See http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html for more information.
|
||||
type ARN struct {
|
||||
// The partition that the resource is in. For standard AWS regions, the partition is "aws". If you have resources in
|
||||
// other partitions, the partition is "aws-partitionname". For example, the partition for resources in the China
|
||||
// (Beijing) region is "aws-cn".
|
||||
Partition string
|
||||
|
||||
// The service namespace that identifies the AWS product (for example, Amazon S3, IAM, or Amazon RDS). For a list of
|
||||
// namespaces, see
|
||||
// http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html#genref-aws-service-namespaces.
|
||||
Service string
|
||||
|
||||
// The region the resource resides in. Note that the ARNs for some resources do not require a region, so this
|
||||
// component might be omitted.
|
||||
Region string
|
||||
|
||||
// The ID of the AWS account that owns the resource, without the hyphens. For example, 123456789012. Note that the
|
||||
// ARNs for some resources don't require an account number, so this component might be omitted.
|
||||
AccountID string
|
||||
|
||||
// The content of this part of the ARN varies by service. It often includes an indicator of the type of resource —
|
||||
// for example, an IAM user or Amazon RDS database - followed by a slash (/) or a colon (:), followed by the
|
||||
// resource name itself. Some services allows paths for resource names, as described in
|
||||
// http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html#arns-paths.
|
||||
Resource string
|
||||
}
|
||||
|
||||
// Parse parses an ARN into its constituent parts.
|
||||
//
|
||||
// Some example ARNs:
|
||||
// arn:aws:elasticbeanstalk:us-east-1:123456789012:environment/My App/MyEnvironment
|
||||
// arn:aws:iam::123456789012:user/David
|
||||
// arn:aws:rds:eu-west-1:123456789012:db:mysql-db
|
||||
// arn:aws:s3:::my_corporate_bucket/exampleobject.png
|
||||
func Parse(arn string) (ARN, error) {
|
||||
if !strings.HasPrefix(arn, arnPrefix) {
|
||||
return ARN{}, errors.New(invalidPrefix)
|
||||
}
|
||||
sections := strings.SplitN(arn, arnDelimiter, arnSections)
|
||||
if len(sections) != arnSections {
|
||||
return ARN{}, errors.New(invalidSections)
|
||||
}
|
||||
return ARN{
|
||||
Partition: sections[sectionPartition],
|
||||
Service: sections[sectionService],
|
||||
Region: sections[sectionRegion],
|
||||
AccountID: sections[sectionAccountID],
|
||||
Resource: sections[sectionResource],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// String returns the canonical representation of the ARN
|
||||
func (arn ARN) String() string {
|
||||
return arnPrefix +
|
||||
arn.Partition + arnDelimiter +
|
||||
arn.Service + arnDelimiter +
|
||||
arn.Region + arnDelimiter +
|
||||
arn.AccountID + arnDelimiter +
|
||||
arn.Resource
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
// +build go1.7
|
||||
|
||||
package arn
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseARN(t *testing.T) {
|
||||
cases := []struct {
|
||||
input string
|
||||
arn ARN
|
||||
err error
|
||||
}{
|
||||
{
|
||||
input: "invalid",
|
||||
err: errors.New(invalidPrefix),
|
||||
},
|
||||
{
|
||||
input: "arn:nope",
|
||||
err: errors.New(invalidSections),
|
||||
},
|
||||
{
|
||||
input: "arn:aws:ecr:us-west-2:123456789012:repository/foo/bar",
|
||||
arn: ARN{
|
||||
Partition: "aws",
|
||||
Service: "ecr",
|
||||
Region: "us-west-2",
|
||||
AccountID: "123456789012",
|
||||
Resource: "repository/foo/bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "arn:aws:elasticbeanstalk:us-east-1:123456789012:environment/My App/MyEnvironment",
|
||||
arn: ARN{
|
||||
Partition: "aws",
|
||||
Service: "elasticbeanstalk",
|
||||
Region: "us-east-1",
|
||||
AccountID: "123456789012",
|
||||
Resource: "environment/My App/MyEnvironment",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "arn:aws:iam::123456789012:user/David",
|
||||
arn: ARN{
|
||||
Partition: "aws",
|
||||
Service: "iam",
|
||||
Region: "",
|
||||
AccountID: "123456789012",
|
||||
Resource: "user/David",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "arn:aws:rds:eu-west-1:123456789012:db:mysql-db",
|
||||
arn: ARN{
|
||||
Partition: "aws",
|
||||
Service: "rds",
|
||||
Region: "eu-west-1",
|
||||
AccountID: "123456789012",
|
||||
Resource: "db:mysql-db",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "arn:aws:s3:::my_corporate_bucket/exampleobject.png",
|
||||
arn: ARN{
|
||||
Partition: "aws",
|
||||
Service: "s3",
|
||||
Region: "",
|
||||
AccountID: "",
|
||||
Resource: "my_corporate_bucket/exampleobject.png",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.input, func(t *testing.T) {
|
||||
spec, err := Parse(tc.input)
|
||||
if tc.arn != spec {
|
||||
t.Errorf("Expected %q to parse as %v, but got %v", tc.input, tc.arn, spec)
|
||||
}
|
||||
if err == nil && tc.err != nil {
|
||||
t.Errorf("Expected err to be %v, but got nil", tc.err)
|
||||
} else if err != nil && tc.err == nil {
|
||||
t.Errorf("Expected err to be nil, but got %v", err)
|
||||
} else if err != nil && tc.err != nil && err.Error() != tc.err.Error() {
|
||||
t.Errorf("Expected err to be %v, but got %v", tc.err, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+3
-59
@@ -2,7 +2,6 @@ package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http/httputil"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/client/metadata"
|
||||
@@ -46,7 +45,7 @@ func New(cfg aws.Config, info metadata.ClientInfo, handlers request.Handlers, op
|
||||
svc := &Client{
|
||||
Config: cfg,
|
||||
ClientInfo: info,
|
||||
Handlers: handlers,
|
||||
Handlers: handlers.Copy(),
|
||||
}
|
||||
|
||||
switch retryer, ok := cfg.Retryer.(request.Retryer); {
|
||||
@@ -86,61 +85,6 @@ func (c *Client) AddDebugHandlers() {
|
||||
return
|
||||
}
|
||||
|
||||
c.Handlers.Send.PushFront(logRequest)
|
||||
c.Handlers.Send.PushBack(logResponse)
|
||||
}
|
||||
|
||||
const logReqMsg = `DEBUG: Request %s/%s Details:
|
||||
---[ REQUEST POST-SIGN ]-----------------------------
|
||||
%s
|
||||
-----------------------------------------------------`
|
||||
|
||||
const logReqErrMsg = `DEBUG ERROR: Request %s/%s:
|
||||
---[ REQUEST DUMP ERROR ]-----------------------------
|
||||
%s
|
||||
-----------------------------------------------------`
|
||||
|
||||
func logRequest(r *request.Request) {
|
||||
logBody := r.Config.LogLevel.Matches(aws.LogDebugWithHTTPBody)
|
||||
dumpedBody, err := httputil.DumpRequestOut(r.HTTPRequest, logBody)
|
||||
if err != nil {
|
||||
r.Config.Logger.Log(fmt.Sprintf(logReqErrMsg, r.ClientInfo.ServiceName, r.Operation.Name, err))
|
||||
return
|
||||
}
|
||||
|
||||
if logBody {
|
||||
// Reset the request body because dumpRequest will re-wrap the r.HTTPRequest's
|
||||
// Body as a NoOpCloser and will not be reset after read by the HTTP
|
||||
// client reader.
|
||||
r.ResetBody()
|
||||
}
|
||||
|
||||
r.Config.Logger.Log(fmt.Sprintf(logReqMsg, r.ClientInfo.ServiceName, r.Operation.Name, string(dumpedBody)))
|
||||
}
|
||||
|
||||
const logRespMsg = `DEBUG: Response %s/%s Details:
|
||||
---[ RESPONSE ]--------------------------------------
|
||||
%s
|
||||
-----------------------------------------------------`
|
||||
|
||||
const logRespErrMsg = `DEBUG ERROR: Response %s/%s:
|
||||
---[ RESPONSE DUMP ERROR ]-----------------------------
|
||||
%s
|
||||
-----------------------------------------------------`
|
||||
|
||||
func logResponse(r *request.Request) {
|
||||
var msg = "no response data"
|
||||
if r.HTTPResponse != nil {
|
||||
logBody := r.Config.LogLevel.Matches(aws.LogDebugWithHTTPBody)
|
||||
dumpedBody, err := httputil.DumpResponse(r.HTTPResponse, logBody)
|
||||
if err != nil {
|
||||
r.Config.Logger.Log(fmt.Sprintf(logRespErrMsg, r.ClientInfo.ServiceName, r.Operation.Name, err))
|
||||
return
|
||||
}
|
||||
|
||||
msg = string(dumpedBody)
|
||||
} else if r.Error != nil {
|
||||
msg = r.Error.Error()
|
||||
}
|
||||
r.Config.Logger.Log(fmt.Sprintf(logRespMsg, r.ClientInfo.ServiceName, r.Operation.Name, msg))
|
||||
c.Handlers.Send.PushFrontNamed(request.NamedHandler{Name: "awssdk.client.LogRequest", Fn: logRequest})
|
||||
c.Handlers.Send.PushBackNamed(request.NamedHandler{Name: "awssdk.client.LogResponse", Fn: logResponse})
|
||||
}
|
||||
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/client/metadata"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
)
|
||||
|
||||
func pushBackTestHandler(name string, list *request.HandlerList) *bool {
|
||||
called := false
|
||||
(*list).PushBackNamed(request.NamedHandler{
|
||||
Name: name,
|
||||
Fn: func(r *request.Request) {
|
||||
called = true
|
||||
},
|
||||
})
|
||||
|
||||
return &called
|
||||
}
|
||||
|
||||
func pushFrontTestHandler(name string, list *request.HandlerList) *bool {
|
||||
called := false
|
||||
(*list).PushFrontNamed(request.NamedHandler{
|
||||
Name: name,
|
||||
Fn: func(r *request.Request) {
|
||||
called = true
|
||||
},
|
||||
})
|
||||
|
||||
return &called
|
||||
}
|
||||
|
||||
func TestNewClient_CopyHandlers(t *testing.T) {
|
||||
handlers := request.Handlers{}
|
||||
firstCalled := pushBackTestHandler("first", &handlers.Send)
|
||||
secondCalled := pushBackTestHandler("second", &handlers.Send)
|
||||
|
||||
var clientHandlerCalled *bool
|
||||
c := New(aws.Config{}, metadata.ClientInfo{}, handlers,
|
||||
func(c *Client) {
|
||||
clientHandlerCalled = pushFrontTestHandler("client handler", &c.Handlers.Send)
|
||||
},
|
||||
)
|
||||
|
||||
if e, a := 2, handlers.Send.Len(); e != a {
|
||||
t.Errorf("expect %d original handlers, got %d", e, a)
|
||||
}
|
||||
if e, a := 3, c.Handlers.Send.Len(); e != a {
|
||||
t.Errorf("expect %d client handlers, got %d", e, a)
|
||||
}
|
||||
|
||||
handlers.Send.Run(nil)
|
||||
if !*firstCalled {
|
||||
t.Errorf("expect first handler to of been called")
|
||||
}
|
||||
*firstCalled = false
|
||||
if !*secondCalled {
|
||||
t.Errorf("expect second handler to of been called")
|
||||
}
|
||||
*secondCalled = false
|
||||
if *clientHandlerCalled {
|
||||
t.Errorf("expect client handler to not of been called, but was")
|
||||
}
|
||||
|
||||
c.Handlers.Send.Run(nil)
|
||||
if !*firstCalled {
|
||||
t.Errorf("expect client's first handler to of been called")
|
||||
}
|
||||
if !*secondCalled {
|
||||
t.Errorf("expect client's second handler to of been called")
|
||||
}
|
||||
if !*clientHandlerCalled {
|
||||
t.Errorf("expect client's client handler to of been called")
|
||||
}
|
||||
|
||||
}
|
||||
+8
-2
@@ -15,11 +15,11 @@ import (
|
||||
// the MaxRetries method:
|
||||
//
|
||||
// type retryer struct {
|
||||
// service.DefaultRetryer
|
||||
// client.DefaultRetryer
|
||||
// }
|
||||
//
|
||||
// // This implementation always has 100 max retries
|
||||
// func (d retryer) MaxRetries() uint { return 100 }
|
||||
// func (d retryer) MaxRetries() int { return 100 }
|
||||
type DefaultRetryer struct {
|
||||
NumMaxRetries int
|
||||
}
|
||||
@@ -54,6 +54,12 @@ func (d DefaultRetryer) RetryRules(r *request.Request) time.Duration {
|
||||
|
||||
// ShouldRetry returns true if the request should be retried.
|
||||
func (d DefaultRetryer) ShouldRetry(r *request.Request) bool {
|
||||
// If one of the other handlers already set the retry state
|
||||
// we don't want to override it based on the service's state
|
||||
if r.Retryable != nil {
|
||||
return *r.Retryable
|
||||
}
|
||||
|
||||
if r.HTTPResponse.StatusCode >= 500 {
|
||||
return true
|
||||
}
|
||||
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http/httputil"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
)
|
||||
|
||||
const logReqMsg = `DEBUG: Request %s/%s Details:
|
||||
---[ REQUEST POST-SIGN ]-----------------------------
|
||||
%s
|
||||
-----------------------------------------------------`
|
||||
|
||||
const logReqErrMsg = `DEBUG ERROR: Request %s/%s:
|
||||
---[ REQUEST DUMP ERROR ]-----------------------------
|
||||
%s
|
||||
------------------------------------------------------`
|
||||
|
||||
type logWriter struct {
|
||||
// Logger is what we will use to log the payload of a response.
|
||||
Logger aws.Logger
|
||||
// buf stores the contents of what has been read
|
||||
buf *bytes.Buffer
|
||||
}
|
||||
|
||||
func (logger *logWriter) Write(b []byte) (int, error) {
|
||||
return logger.buf.Write(b)
|
||||
}
|
||||
|
||||
type teeReaderCloser struct {
|
||||
// io.Reader will be a tee reader that is used during logging.
|
||||
// This structure will read from a body and write the contents to a logger.
|
||||
io.Reader
|
||||
// Source is used just to close when we are done reading.
|
||||
Source io.ReadCloser
|
||||
}
|
||||
|
||||
func (reader *teeReaderCloser) Close() error {
|
||||
return reader.Source.Close()
|
||||
}
|
||||
|
||||
func logRequest(r *request.Request) {
|
||||
logBody := r.Config.LogLevel.Matches(aws.LogDebugWithHTTPBody)
|
||||
dumpedBody, err := httputil.DumpRequestOut(r.HTTPRequest, logBody)
|
||||
if err != nil {
|
||||
r.Config.Logger.Log(fmt.Sprintf(logReqErrMsg, r.ClientInfo.ServiceName, r.Operation.Name, err))
|
||||
return
|
||||
}
|
||||
|
||||
if logBody {
|
||||
// Reset the request body because dumpRequest will re-wrap the r.HTTPRequest's
|
||||
// Body as a NoOpCloser and will not be reset after read by the HTTP
|
||||
// client reader.
|
||||
r.ResetBody()
|
||||
}
|
||||
|
||||
r.Config.Logger.Log(fmt.Sprintf(logReqMsg, r.ClientInfo.ServiceName, r.Operation.Name, string(dumpedBody)))
|
||||
}
|
||||
|
||||
const logRespMsg = `DEBUG: Response %s/%s Details:
|
||||
---[ RESPONSE ]--------------------------------------
|
||||
%s
|
||||
-----------------------------------------------------`
|
||||
|
||||
const logRespErrMsg = `DEBUG ERROR: Response %s/%s:
|
||||
---[ RESPONSE DUMP ERROR ]-----------------------------
|
||||
%s
|
||||
-----------------------------------------------------`
|
||||
|
||||
func logResponse(r *request.Request) {
|
||||
lw := &logWriter{r.Config.Logger, bytes.NewBuffer(nil)}
|
||||
r.HTTPResponse.Body = &teeReaderCloser{
|
||||
Reader: io.TeeReader(r.HTTPResponse.Body, lw),
|
||||
Source: r.HTTPResponse.Body,
|
||||
}
|
||||
|
||||
handlerFn := func(req *request.Request) {
|
||||
body, err := httputil.DumpResponse(req.HTTPResponse, false)
|
||||
if err != nil {
|
||||
lw.Logger.Log(fmt.Sprintf(logRespErrMsg, req.ClientInfo.ServiceName, req.Operation.Name, err))
|
||||
return
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(lw.buf)
|
||||
if err != nil {
|
||||
lw.Logger.Log(fmt.Sprintf(logRespErrMsg, req.ClientInfo.ServiceName, req.Operation.Name, err))
|
||||
return
|
||||
}
|
||||
lw.Logger.Log(fmt.Sprintf(logRespMsg, req.ClientInfo.ServiceName, req.Operation.Name, string(body)))
|
||||
if req.Config.LogLevel.Matches(aws.LogDebugWithHTTPBody) {
|
||||
lw.Logger.Log(string(b))
|
||||
}
|
||||
}
|
||||
|
||||
const handlerName = "awsdk.client.LogResponse.ResponseBody"
|
||||
|
||||
r.Handlers.Unmarshal.SetBackNamed(request.NamedHandler{
|
||||
Name: handlerName, Fn: handlerFn,
|
||||
})
|
||||
r.Handlers.UnmarshalError.SetBackNamed(request.NamedHandler{
|
||||
Name: handlerName, Fn: handlerFn,
|
||||
})
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type mockCloser struct {
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (closer *mockCloser) Read(b []byte) (int, error) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
func (closer *mockCloser) Close() error {
|
||||
closer.closed = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestTeeReaderCloser(t *testing.T) {
|
||||
expected := "FOO"
|
||||
buf := bytes.NewBuffer([]byte(expected))
|
||||
lw := bytes.NewBuffer(nil)
|
||||
c := &mockCloser{}
|
||||
closer := teeReaderCloser{
|
||||
io.TeeReader(buf, lw),
|
||||
c,
|
||||
}
|
||||
|
||||
b := make([]byte, len(expected))
|
||||
_, err := closer.Read(b)
|
||||
closer.Close()
|
||||
|
||||
if expected != lw.String() {
|
||||
t.Errorf("Expected %q, but received %q", expected, lw.String())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Expected 'nil', but received %v", err)
|
||||
}
|
||||
|
||||
if !c.closed {
|
||||
t.Error("Expected 'true', but received 'false'")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogWriter(t *testing.T) {
|
||||
expected := "FOO"
|
||||
lw := &logWriter{nil, bytes.NewBuffer(nil)}
|
||||
lw.Write([]byte(expected))
|
||||
|
||||
if expected != lw.buf.String() {
|
||||
t.Errorf("Expected %q, but received %q", expected, lw.buf.String())
|
||||
}
|
||||
}
|
||||
+12
-1
@@ -53,6 +53,13 @@ type Config struct {
|
||||
// to use based on region.
|
||||
EndpointResolver endpoints.Resolver
|
||||
|
||||
// EnforceShouldRetryCheck is used in the AfterRetryHandler to always call
|
||||
// ShouldRetry regardless of whether or not if request.Retryable is set.
|
||||
// This will utilize ShouldRetry method of custom retryers. If EnforceShouldRetryCheck
|
||||
// is not set, then ShouldRetry will only be called if request.Retryable is nil.
|
||||
// Proper handling of the request.Retryable field is important when setting this field.
|
||||
EnforceShouldRetryCheck *bool
|
||||
|
||||
// The region to send requests to. This parameter is required and must
|
||||
// be configured globally or on a per-client basis unless otherwise
|
||||
// noted. A full list of regions is found in the "Regions and Endpoints"
|
||||
@@ -88,7 +95,7 @@ type Config struct {
|
||||
// recoverable failures.
|
||||
//
|
||||
// When nil or the value does not implement the request.Retryer interface,
|
||||
// the request.DefaultRetryer will be used.
|
||||
// the client.DefaultRetryer will be used.
|
||||
//
|
||||
// When both Retryer and MaxRetries are non-nil, the former is used and
|
||||
// the latter ignored.
|
||||
@@ -443,6 +450,10 @@ func mergeInConfig(dst *Config, other *Config) {
|
||||
if other.DisableRestProtocolURICleaning != nil {
|
||||
dst.DisableRestProtocolURICleaning = other.DisableRestProtocolURICleaning
|
||||
}
|
||||
|
||||
if other.EnforceShouldRetryCheck != nil {
|
||||
dst.EnforceShouldRetryCheck = other.EnforceShouldRetryCheck
|
||||
}
|
||||
}
|
||||
|
||||
// Copy will return a shallow copy of the Config object. If any additional
|
||||
|
||||
+3
-3
@@ -4,9 +4,9 @@ package aws
|
||||
|
||||
import "time"
|
||||
|
||||
// An emptyCtx is a copy of the the Go 1.7 context.emptyCtx type. This
|
||||
// is copied to provide a 1.6 and 1.5 safe version of context that is compatible
|
||||
// with Go 1.7's Context.
|
||||
// An emptyCtx is a copy of the Go 1.7 context.emptyCtx type. This is copied to
|
||||
// provide a 1.6 and 1.5 safe version of context that is compatible with Go
|
||||
// 1.7's Context.
|
||||
//
|
||||
// An emptyCtx is never canceled, has no values, and has no deadline. It is not
|
||||
// struct{}, since vars of this type must have distinct addresses.
|
||||
|
||||
+18
@@ -311,6 +311,24 @@ func TimeValue(v *time.Time) time.Time {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// SecondsTimeValue converts an int64 pointer to a time.Time value
|
||||
// representing seconds since Epoch or time.Time{} if the pointer is nil.
|
||||
func SecondsTimeValue(v *int64) time.Time {
|
||||
if v != nil {
|
||||
return time.Unix((*v / 1000), 0)
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// MillisecondsTimeValue converts an int64 pointer to a time.Time value
|
||||
// representing milliseconds sinch Epoch or time.Time{} if the pointer is nil.
|
||||
func MillisecondsTimeValue(v *int64) time.Time {
|
||||
if v != nil {
|
||||
return time.Unix(0, (*v * 1000000))
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// TimeUnixMilli returns a Unix timestamp in milliseconds from "January 1, 1970 UTC".
|
||||
// The result is undefined if the Unix time cannot be represented by an int64.
|
||||
// Which includes calling TimeUnixMilli on a zero Time is undefined.
|
||||
|
||||
+33
@@ -435,3 +435,36 @@ func TestTimeMap(t *testing.T) {
|
||||
assert.Equal(t, in, out2, "Unexpected value at idx %d", idx)
|
||||
}
|
||||
}
|
||||
|
||||
type TimeValueTestCase struct {
|
||||
in int64
|
||||
outSecs time.Time
|
||||
outMillis time.Time
|
||||
}
|
||||
|
||||
var testCasesTimeValue = []TimeValueTestCase{
|
||||
{
|
||||
in: int64(1501558289000),
|
||||
outSecs: time.Unix(1501558289, 0),
|
||||
outMillis: time.Unix(1501558289, 0),
|
||||
},
|
||||
{
|
||||
in: int64(1501558289001),
|
||||
outSecs: time.Unix(1501558289, 0),
|
||||
outMillis: time.Unix(1501558289, 1*1000000),
|
||||
},
|
||||
}
|
||||
|
||||
func TestSecondsTimeValue(t *testing.T) {
|
||||
for idx, testCase := range testCasesTimeValue {
|
||||
out := SecondsTimeValue(&testCase.in)
|
||||
assert.Equal(t, testCase.outSecs, out, "Unexpected value for time value at %d", idx)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMillisecondsTimeValue(t *testing.T) {
|
||||
for idx, testCase := range testCasesTimeValue {
|
||||
out := MillisecondsTimeValue(&testCase.in)
|
||||
assert.Equal(t, testCase.outMillis, out, "Unexpected value for time value at %d", idx)
|
||||
}
|
||||
}
|
||||
|
||||
+86
-45
@@ -27,7 +27,7 @@ type lener interface {
|
||||
// or will use the HTTPRequest.Header's "Content-Length" if defined. If unable
|
||||
// to determine request body length and no "Content-Length" was specified it will panic.
|
||||
//
|
||||
// The Content-Length will only be aded to the request if the length of the body
|
||||
// The Content-Length will only be added to the request if the length of the body
|
||||
// is greater than 0. If the body is empty or the current `Content-Length`
|
||||
// header is <= 0, the header will also be stripped.
|
||||
var BuildContentLengthHandler = request.NamedHandler{Name: "core.BuildContentLengthHandler", Fn: func(r *request.Request) {
|
||||
@@ -71,8 +71,8 @@ var reStatusCode = regexp.MustCompile(`^(\d{3})`)
|
||||
|
||||
// ValidateReqSigHandler is a request handler to ensure that the request's
|
||||
// signature doesn't expire before it is sent. This can happen when a request
|
||||
// is built and signed signficantly before it is sent. Or significant delays
|
||||
// occur whne retrying requests that would cause the signature to expire.
|
||||
// is built and signed significantly before it is sent. Or significant delays
|
||||
// occur when retrying requests that would cause the signature to expire.
|
||||
var ValidateReqSigHandler = request.NamedHandler{
|
||||
Name: "core.ValidateReqSigHandler",
|
||||
Fn: func(r *request.Request) {
|
||||
@@ -98,54 +98,95 @@ var ValidateReqSigHandler = request.NamedHandler{
|
||||
}
|
||||
|
||||
// SendHandler is a request handler to send service request using HTTP client.
|
||||
var SendHandler = request.NamedHandler{Name: "core.SendHandler", Fn: func(r *request.Request) {
|
||||
var err error
|
||||
r.HTTPResponse, err = r.Config.HTTPClient.Do(r.HTTPRequest)
|
||||
if err != nil {
|
||||
// Prevent leaking if an HTTPResponse was returned. Clean up
|
||||
// the body.
|
||||
if r.HTTPResponse != nil {
|
||||
r.HTTPResponse.Body.Close()
|
||||
var SendHandler = request.NamedHandler{
|
||||
Name: "core.SendHandler",
|
||||
Fn: func(r *request.Request) {
|
||||
sender := sendFollowRedirects
|
||||
if r.DisableFollowRedirects {
|
||||
sender = sendWithoutFollowRedirects
|
||||
}
|
||||
// Capture the case where url.Error is returned for error processing
|
||||
// response. e.g. 301 without location header comes back as string
|
||||
// error and r.HTTPResponse is nil. Other url redirect errors will
|
||||
// comeback in a similar method.
|
||||
if e, ok := err.(*url.Error); ok && e.Err != nil {
|
||||
if s := reStatusCode.FindStringSubmatch(e.Err.Error()); s != nil {
|
||||
code, _ := strconv.ParseInt(s[1], 10, 64)
|
||||
r.HTTPResponse = &http.Response{
|
||||
StatusCode: int(code),
|
||||
Status: http.StatusText(int(code)),
|
||||
Body: ioutil.NopCloser(bytes.NewReader([]byte{})),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if request.NoBody == r.HTTPRequest.Body {
|
||||
// Strip off the request body if the NoBody reader was used as a
|
||||
// place holder for a request body. This prevents the SDK from
|
||||
// making requests with a request body when it would be invalid
|
||||
// to do so.
|
||||
//
|
||||
// Use a shallow copy of the http.Request to ensure the race condition
|
||||
// of transport on Body will not trigger
|
||||
reqOrig, reqCopy := r.HTTPRequest, *r.HTTPRequest
|
||||
reqCopy.Body = nil
|
||||
r.HTTPRequest = &reqCopy
|
||||
defer func() {
|
||||
r.HTTPRequest = reqOrig
|
||||
}()
|
||||
}
|
||||
if r.HTTPResponse == nil {
|
||||
// Add a dummy request response object to ensure the HTTPResponse
|
||||
// value is consistent.
|
||||
|
||||
var err error
|
||||
r.HTTPResponse, err = sender(r)
|
||||
if err != nil {
|
||||
handleSendError(r, err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func sendFollowRedirects(r *request.Request) (*http.Response, error) {
|
||||
return r.Config.HTTPClient.Do(r.HTTPRequest)
|
||||
}
|
||||
|
||||
func sendWithoutFollowRedirects(r *request.Request) (*http.Response, error) {
|
||||
transport := r.Config.HTTPClient.Transport
|
||||
if transport == nil {
|
||||
transport = http.DefaultTransport
|
||||
}
|
||||
|
||||
return transport.RoundTrip(r.HTTPRequest)
|
||||
}
|
||||
|
||||
func handleSendError(r *request.Request, err error) {
|
||||
// Prevent leaking if an HTTPResponse was returned. Clean up
|
||||
// the body.
|
||||
if r.HTTPResponse != nil {
|
||||
r.HTTPResponse.Body.Close()
|
||||
}
|
||||
// Capture the case where url.Error is returned for error processing
|
||||
// response. e.g. 301 without location header comes back as string
|
||||
// error and r.HTTPResponse is nil. Other URL redirect errors will
|
||||
// comeback in a similar method.
|
||||
if e, ok := err.(*url.Error); ok && e.Err != nil {
|
||||
if s := reStatusCode.FindStringSubmatch(e.Err.Error()); s != nil {
|
||||
code, _ := strconv.ParseInt(s[1], 10, 64)
|
||||
r.HTTPResponse = &http.Response{
|
||||
StatusCode: int(0),
|
||||
Status: http.StatusText(int(0)),
|
||||
StatusCode: int(code),
|
||||
Status: http.StatusText(int(code)),
|
||||
Body: ioutil.NopCloser(bytes.NewReader([]byte{})),
|
||||
}
|
||||
}
|
||||
// Catch all other request errors.
|
||||
r.Error = awserr.New("RequestError", "send request failed", err)
|
||||
r.Retryable = aws.Bool(true) // network errors are retryable
|
||||
|
||||
// Override the error with a context canceled error, if that was canceled.
|
||||
ctx := r.Context()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
r.Error = awserr.New(request.CanceledErrorCode,
|
||||
"request context canceled", ctx.Err())
|
||||
r.Retryable = aws.Bool(false)
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}}
|
||||
if r.HTTPResponse == nil {
|
||||
// Add a dummy request response object to ensure the HTTPResponse
|
||||
// value is consistent.
|
||||
r.HTTPResponse = &http.Response{
|
||||
StatusCode: int(0),
|
||||
Status: http.StatusText(int(0)),
|
||||
Body: ioutil.NopCloser(bytes.NewReader([]byte{})),
|
||||
}
|
||||
}
|
||||
// Catch all other request errors.
|
||||
r.Error = awserr.New("RequestError", "send request failed", err)
|
||||
r.Retryable = aws.Bool(true) // network errors are retryable
|
||||
|
||||
// Override the error with a context canceled error, if that was canceled.
|
||||
ctx := r.Context()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
r.Error = awserr.New(request.CanceledErrorCode,
|
||||
"request context canceled", ctx.Err())
|
||||
r.Retryable = aws.Bool(false)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateResponseHandler is a request handler to validate service response.
|
||||
var ValidateResponseHandler = request.NamedHandler{Name: "core.ValidateResponseHandler", Fn: func(r *request.Request) {
|
||||
@@ -160,7 +201,7 @@ var ValidateResponseHandler = request.NamedHandler{Name: "core.ValidateResponseH
|
||||
var AfterRetryHandler = request.NamedHandler{Name: "core.AfterRetryHandler", Fn: func(r *request.Request) {
|
||||
// If one of the other handlers already set the retry state
|
||||
// we don't want to override it based on the service's state
|
||||
if r.Retryable == nil {
|
||||
if r.Retryable == nil || aws.BoolValue(r.Config.EnforceShouldRetryCheck) {
|
||||
r.Retryable = aws.Bool(r.ShouldRetry(r))
|
||||
}
|
||||
|
||||
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
// +build go1.8
|
||||
|
||||
package corehandlers_test
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/awstesting"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
func TestSendHandler_HEADNoBody(t *testing.T) {
|
||||
TLSBundleCertFile, TLSBundleKeyFile, TLSBundleCAFile, err := awstesting.CreateTLSBundleFiles()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer awstesting.CleanupTLSBundleFiles(TLSBundleCertFile, TLSBundleKeyFile, TLSBundleCAFile)
|
||||
|
||||
endpoint, err := awstesting.CreateTLSServer(TLSBundleCertFile, TLSBundleKeyFile, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, got %v", err)
|
||||
}
|
||||
|
||||
transport := http.DefaultTransport.(*http.Transport)
|
||||
// test server's certificate is self-signed certificate
|
||||
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
http2.ConfigureTransport(transport)
|
||||
|
||||
sess, err := session.NewSessionWithOptions(session.Options{
|
||||
Config: aws.Config{
|
||||
HTTPClient: &http.Client{},
|
||||
Endpoint: aws.String(endpoint),
|
||||
Region: aws.String("mock-region"),
|
||||
Credentials: credentials.AnonymousCredentials,
|
||||
S3ForcePathStyle: aws.Bool(true),
|
||||
},
|
||||
})
|
||||
|
||||
svc := s3.New(sess)
|
||||
|
||||
req, _ := svc.HeadObjectRequest(&s3.HeadObjectInput{
|
||||
Bucket: aws.String("bucketname"),
|
||||
Key: aws.String("keyname"),
|
||||
})
|
||||
|
||||
if e, a := request.NoBody, req.HTTPRequest.Body; e != a {
|
||||
t.Fatalf("expect %T request body, got %T", e, a)
|
||||
}
|
||||
|
||||
err = req.Send()
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, got %v", err)
|
||||
}
|
||||
if e, a := http.StatusOK, req.HTTPResponse.StatusCode; e != a {
|
||||
t.Errorf("expect %d status code, got %d", e, a)
|
||||
}
|
||||
}
|
||||
+33
@@ -206,6 +206,39 @@ func TestSendHandlerError(t *testing.T) {
|
||||
assert.NotNil(t, r.HTTPResponse)
|
||||
}
|
||||
|
||||
func TestSendWithoutFollowRedirects(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/original":
|
||||
w.Header().Set("Location", "/redirected")
|
||||
w.WriteHeader(301)
|
||||
case "/redirected":
|
||||
t.Fatalf("expect not to redirect, but was")
|
||||
}
|
||||
}))
|
||||
|
||||
svc := awstesting.NewClient(&aws.Config{
|
||||
DisableSSL: aws.Bool(true),
|
||||
Endpoint: aws.String(server.URL),
|
||||
})
|
||||
svc.Handlers.Clear()
|
||||
svc.Handlers.Send.PushBackNamed(corehandlers.SendHandler)
|
||||
|
||||
r := svc.NewRequest(&request.Operation{
|
||||
Name: "Operation",
|
||||
HTTPPath: "/original",
|
||||
}, nil, nil)
|
||||
r.DisableFollowRedirects = true
|
||||
|
||||
err := r.Send()
|
||||
if err != nil {
|
||||
t.Errorf("expect no error, got %v", err)
|
||||
}
|
||||
if e, a := 301, r.HTTPResponse.StatusCode; e != a {
|
||||
t.Errorf("expect %d status code, got %d", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateReqSigHandler(t *testing.T) {
|
||||
cases := []struct {
|
||||
Req *request.Request
|
||||
|
||||
+8
-6
@@ -13,7 +13,7 @@ var (
|
||||
//
|
||||
// @readonly
|
||||
ErrNoValidProvidersFoundInChain = awserr.New("NoCredentialProviders",
|
||||
`no valid providers in chain. Deprecated.
|
||||
`no valid providers in chain. Deprecated.
|
||||
For verbose messaging see aws.Config.CredentialsChainVerboseErrors`,
|
||||
nil)
|
||||
)
|
||||
@@ -39,16 +39,18 @@ var (
|
||||
// does not return any credentials ChainProvider will return the error
|
||||
// ErrNoValidProvidersFoundInChain
|
||||
//
|
||||
// creds := NewChainCredentials(
|
||||
// []Provider{
|
||||
// &EnvProvider{},
|
||||
// &EC2RoleProvider{
|
||||
// creds := credentials.NewChainCredentials(
|
||||
// []credentials.Provider{
|
||||
// &credentials.EnvProvider{},
|
||||
// &ec2rolecreds.EC2RoleProvider{
|
||||
// Client: ec2metadata.New(sess),
|
||||
// },
|
||||
// })
|
||||
//
|
||||
// // Usage of ChainCredentials with aws.Config
|
||||
// svc := ec2.New(&aws.Config{Credentials: creds})
|
||||
// svc := ec2.New(session.Must(session.NewSession(&aws.Config{
|
||||
// Credentials: creds,
|
||||
// })))
|
||||
//
|
||||
type ChainProvider struct {
|
||||
Providers []Provider
|
||||
|
||||
+28
-5
@@ -14,7 +14,7 @@
|
||||
//
|
||||
// Example of using the environment variable credentials.
|
||||
//
|
||||
// creds := NewEnvCredentials()
|
||||
// creds := credentials.NewEnvCredentials()
|
||||
//
|
||||
// // Retrieve the credentials value
|
||||
// credValue, err := creds.Get()
|
||||
@@ -26,7 +26,7 @@
|
||||
// This may be helpful to proactively expire credentials and refresh them sooner
|
||||
// than they would naturally expire on their own.
|
||||
//
|
||||
// creds := NewCredentials(&EC2RoleProvider{})
|
||||
// creds := credentials.NewCredentials(&ec2rolecreds.EC2RoleProvider{})
|
||||
// creds.Expire()
|
||||
// credsValue, err := creds.Get()
|
||||
// // New credentials will be retrieved instead of from cache.
|
||||
@@ -43,7 +43,7 @@
|
||||
// func (m *MyProvider) Retrieve() (Value, error) {...}
|
||||
// func (m *MyProvider) IsExpired() bool {...}
|
||||
//
|
||||
// creds := NewCredentials(&MyProvider{})
|
||||
// creds := credentials.NewCredentials(&MyProvider{})
|
||||
// credValue, err := creds.Get()
|
||||
//
|
||||
package credentials
|
||||
@@ -60,7 +60,9 @@ import (
|
||||
// when making service API calls. For example, when accessing public
|
||||
// s3 buckets.
|
||||
//
|
||||
// svc := s3.New(&aws.Config{Credentials: AnonymousCredentials})
|
||||
// svc := s3.New(session.Must(session.NewSession(&aws.Config{
|
||||
// Credentials: credentials.AnonymousCredentials,
|
||||
// })))
|
||||
// // Access public S3 buckets.
|
||||
//
|
||||
// @readonly
|
||||
@@ -88,7 +90,7 @@ type Value struct {
|
||||
// The Provider should not need to implement its own mutexes, because
|
||||
// that will be managed by Credentials.
|
||||
type Provider interface {
|
||||
// Refresh returns nil if it successfully retrieved the value.
|
||||
// Retrieve returns nil if it successfully retrieved the value.
|
||||
// Error is returned if the value were not obtainable, or empty.
|
||||
Retrieve() (Value, error)
|
||||
|
||||
@@ -97,6 +99,27 @@ type Provider interface {
|
||||
IsExpired() bool
|
||||
}
|
||||
|
||||
// An ErrorProvider is a stub credentials provider that always returns an error
|
||||
// this is used by the SDK when construction a known provider is not possible
|
||||
// due to an error.
|
||||
type ErrorProvider struct {
|
||||
// The error to be returned from Retrieve
|
||||
Err error
|
||||
|
||||
// The provider name to set on the Retrieved returned Value
|
||||
ProviderName string
|
||||
}
|
||||
|
||||
// Retrieve will always return the error that the ErrorProvider was created with.
|
||||
func (p ErrorProvider) Retrieve() (Value, error) {
|
||||
return Value{ProviderName: p.ProviderName}, p.Err
|
||||
}
|
||||
|
||||
// IsExpired will always return not expired.
|
||||
func (p ErrorProvider) IsExpired() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// A Expiry provides shared expiration logic to be used by credentials
|
||||
// providers to implement expiry functionality.
|
||||
//
|
||||
|
||||
+1
@@ -29,6 +29,7 @@ var (
|
||||
// Environment variables used:
|
||||
//
|
||||
// * Access Key ID: AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY
|
||||
//
|
||||
// * Secret Access Key: AWS_SECRET_ACCESS_KEY or AWS_SECRET_KEY
|
||||
type EnvProvider struct {
|
||||
retrieved bool
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
// +build !go1.8
|
||||
|
||||
// Package plugincreds provides usage of Go plugins for providing credentials
|
||||
// to the SDK. Only available with Go 1.8 and above.
|
||||
package plugincreds
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
// +build go1.8
|
||||
|
||||
// Package plugincreds implements a credentials provider sourced from a Go
|
||||
// plugin. This package allows you to use a Go plugin to retrieve AWS credentials
|
||||
// for the SDK to use for service API calls.
|
||||
//
|
||||
// As of Go 1.8 plugins are only supported on the Linux platform.
|
||||
//
|
||||
// Plugin Symbol Name
|
||||
//
|
||||
// The "GetAWSSDKCredentialProvider" is the symbol name that will be used to
|
||||
// lookup the credentials provider getter from the plugin. If you want to use a
|
||||
// custom symbol name you should use GetPluginProviderFnsByName to lookup the
|
||||
// symbol by a custom name.
|
||||
//
|
||||
// This symbol is a function that returns two additional functions. One to
|
||||
// retrieve the credentials, and another to determine if the credentials have
|
||||
// expired.
|
||||
//
|
||||
// Plugin Symbol Signature
|
||||
//
|
||||
// The plugin credential provider requires the symbol to match the
|
||||
// following signature.
|
||||
//
|
||||
// func() (RetrieveFn func() (key, secret, token string, err error), IsExpiredFn func() bool)
|
||||
//
|
||||
// Plugin Implementation Exmaple
|
||||
//
|
||||
// The following is an example implementation of a SDK credential provider using
|
||||
// the plugin provider in this package. See the SDK's example/aws/credential/plugincreds/plugin
|
||||
// folder for a runnable example of this.
|
||||
//
|
||||
// package main
|
||||
//
|
||||
// func main() {}
|
||||
//
|
||||
// var myCredProvider provider
|
||||
//
|
||||
// // Build: go build -o plugin.so -buildmode=plugin plugin.go
|
||||
// func init() {
|
||||
// // Initialize a mock credential provider with stubs
|
||||
// myCredProvider = provider{"a","b","c"}
|
||||
// }
|
||||
//
|
||||
// // GetAWSSDKCredentialProvider is the symbol SDK will lookup and use to
|
||||
// // get the credential provider's retrieve and isExpired functions.
|
||||
// func GetAWSSDKCredentialProvider() (func() (key, secret, token string, err error), func() bool) {
|
||||
// return myCredProvider.Retrieve, myCredProvider.IsExpired
|
||||
// }
|
||||
//
|
||||
// // mock implementation of a type that returns retrieves credentials and
|
||||
// // returns if they have expired.
|
||||
// type provider struct {
|
||||
// key, secret, token string
|
||||
// }
|
||||
//
|
||||
// func (p provider) Retrieve() (key, secret, token string, err error) {
|
||||
// return p.key, p.secret, p.token, nil
|
||||
// }
|
||||
//
|
||||
// func (p *provider) IsExpired() bool {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// Configuring SDK for Plugin Credentials
|
||||
//
|
||||
// To configure the SDK to use a plugin's credential provider you'll need to first
|
||||
// open the plugin file using the plugin standard library package. Once you have
|
||||
// a handle to the plugin you can use the NewCredentials function of this package
|
||||
// to create a new credentials.Credentials value that can be set as the
|
||||
// credentials loader of a Session or Config. See the SDK's example/aws/credential/plugincreds
|
||||
// folder for a runnable example of this.
|
||||
//
|
||||
// // Open plugin, and load it into the process.
|
||||
// p, err := plugin.Open("somefile.so")
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
//
|
||||
// // Create a new Credentials value which will source the provider's Retrieve
|
||||
// // and IsExpired functions from the plugin.
|
||||
// creds, err := plugincreds.NewCredentials(p)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
//
|
||||
// // Example to configure a Session with the newly created credentials that
|
||||
// // will be sourced using the plugin's functionality.
|
||||
// sess := session.Must(session.NewSession(&aws.Config{
|
||||
// Credentials: creds,
|
||||
// }))
|
||||
package plugincreds
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"plugin"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
)
|
||||
|
||||
// ProviderSymbolName the symbol name the SDK will use to lookup the plugin
|
||||
// provider value from.
|
||||
const ProviderSymbolName = `GetAWSSDKCredentialProvider`
|
||||
|
||||
// ProviderName is the name this credentials provider will label any returned
|
||||
// credentials Value with.
|
||||
const ProviderName = `PluginCredentialsProvider`
|
||||
|
||||
const (
|
||||
// ErrCodeLookupSymbolError failed to lookup symbol
|
||||
ErrCodeLookupSymbolError = "LookupSymbolError"
|
||||
|
||||
// ErrCodeInvalidSymbolError symbol invalid
|
||||
ErrCodeInvalidSymbolError = "InvalidSymbolError"
|
||||
|
||||
// ErrCodePluginRetrieveNil Retrieve function was nil
|
||||
ErrCodePluginRetrieveNil = "PluginRetrieveNilError"
|
||||
|
||||
// ErrCodePluginIsExpiredNil IsExpired Function was nil
|
||||
ErrCodePluginIsExpiredNil = "PluginIsExpiredNilError"
|
||||
|
||||
// ErrCodePluginProviderRetrieve plugin provider's retrieve returned error
|
||||
ErrCodePluginProviderRetrieve = "PluginProviderRetrieveError"
|
||||
)
|
||||
|
||||
// Provider is the credentials provider that will use the plugin provided
|
||||
// Retrieve and IsExpired functions to retrieve credentials.
|
||||
type Provider struct {
|
||||
RetrieveFn func() (key, secret, token string, err error)
|
||||
IsExpiredFn func() bool
|
||||
}
|
||||
|
||||
// NewCredentials returns a new Credentials loader using the plugin provider.
|
||||
// If the symbol isn't found or is invalid in the plugin an error will be
|
||||
// returned.
|
||||
func NewCredentials(p *plugin.Plugin) (*credentials.Credentials, error) {
|
||||
retrieve, isExpired, err := GetPluginProviderFns(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return credentials.NewCredentials(Provider{
|
||||
RetrieveFn: retrieve,
|
||||
IsExpiredFn: isExpired,
|
||||
}), nil
|
||||
}
|
||||
|
||||
// Retrieve will return the credentials Value if they were successfully retrieved
|
||||
// from the underlying plugin provider. An error will be returned otherwise.
|
||||
func (p Provider) Retrieve() (credentials.Value, error) {
|
||||
creds := credentials.Value{
|
||||
ProviderName: ProviderName,
|
||||
}
|
||||
|
||||
k, s, t, err := p.RetrieveFn()
|
||||
if err != nil {
|
||||
return creds, awserr.New(ErrCodePluginProviderRetrieve,
|
||||
"failed to retrieve credentials with plugin provider", err)
|
||||
}
|
||||
|
||||
creds.AccessKeyID = k
|
||||
creds.SecretAccessKey = s
|
||||
creds.SessionToken = t
|
||||
|
||||
return creds, nil
|
||||
}
|
||||
|
||||
// IsExpired will return the expired state of the underlying plugin provider.
|
||||
func (p Provider) IsExpired() bool {
|
||||
return p.IsExpiredFn()
|
||||
}
|
||||
|
||||
// GetPluginProviderFns returns the plugin's Retrieve and IsExpired functions
|
||||
// returned by the plugin's credential provider getter.
|
||||
//
|
||||
// Uses ProviderSymbolName as the symbol name when lookup up the symbol. If you
|
||||
// want to use a different symbol name, use GetPluginProviderFnsByName.
|
||||
func GetPluginProviderFns(p *plugin.Plugin) (func() (key, secret, token string, err error), func() bool, error) {
|
||||
return GetPluginProviderFnsByName(p, ProviderSymbolName)
|
||||
}
|
||||
|
||||
// GetPluginProviderFnsByName returns the plugin's Retrieve and IsExpired functions
|
||||
// returned by the plugin's credential provider getter.
|
||||
//
|
||||
// Same as GetPluginProviderFns, but takes a custom symbolName to lookup with.
|
||||
func GetPluginProviderFnsByName(p *plugin.Plugin, symbolName string) (func() (key, secret, token string, err error), func() bool, error) {
|
||||
sym, err := p.Lookup(symbolName)
|
||||
if err != nil {
|
||||
return nil, nil, awserr.New(ErrCodeLookupSymbolError,
|
||||
fmt.Sprintf("failed to lookup %s plugin provider symbol", symbolName), err)
|
||||
}
|
||||
|
||||
fn, ok := sym.(func() (func() (key, secret, token string, err error), func() bool))
|
||||
if !ok {
|
||||
return nil, nil, awserr.New(ErrCodeInvalidSymbolError,
|
||||
fmt.Sprintf("symbol %T, does not match the 'func() (func() (key, secret, token string, err error), func() bool)' type", sym), nil)
|
||||
}
|
||||
|
||||
retrieveFn, isExpiredFn := fn()
|
||||
if retrieveFn == nil {
|
||||
return nil, nil, awserr.New(ErrCodePluginRetrieveNil,
|
||||
"the plugin provider retrieve function cannot be nil", nil)
|
||||
}
|
||||
if isExpiredFn == nil {
|
||||
return nil, nil, awserr.New(ErrCodePluginIsExpiredNil,
|
||||
"the plugin provider isExpired function cannot be nil", nil)
|
||||
}
|
||||
|
||||
return retrieveFn, isExpiredFn, nil
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
// +build go1.8,awsinclude
|
||||
|
||||
package plugincreds
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
)
|
||||
|
||||
func TestProvider_Passthrough(t *testing.T) {
|
||||
p := Provider{
|
||||
RetrieveFn: func() (string, string, string, error) {
|
||||
return "key", "secret", "token", nil
|
||||
},
|
||||
IsExpiredFn: func() bool {
|
||||
return false
|
||||
},
|
||||
}
|
||||
|
||||
actual, err := p.Retrieve()
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, got %v", err)
|
||||
}
|
||||
|
||||
expect := credentials.Value{
|
||||
AccessKeyID: "key",
|
||||
SecretAccessKey: "secret",
|
||||
SessionToken: "token",
|
||||
ProviderName: ProviderName,
|
||||
}
|
||||
if expect != actual {
|
||||
t.Errorf("expect %+v credentials, got %+v", expect, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvider_Error(t *testing.T) {
|
||||
expectErr := fmt.Errorf("expect error")
|
||||
|
||||
p := Provider{
|
||||
RetrieveFn: func() (string, string, string, error) {
|
||||
return "", "", "", expectErr
|
||||
},
|
||||
IsExpiredFn: func() bool {
|
||||
return false
|
||||
},
|
||||
}
|
||||
|
||||
actual, err := p.Retrieve()
|
||||
if err == nil {
|
||||
t.Fatalf("expect error, got none")
|
||||
}
|
||||
|
||||
aerr := err.(awserr.Error)
|
||||
if e, a := ErrCodePluginProviderRetrieve, aerr.Code(); e != a {
|
||||
t.Errorf("expect %s error code, got %s", e, a)
|
||||
}
|
||||
|
||||
if e, a := expectErr, aerr.OrigErr(); e != a {
|
||||
t.Errorf("expect %v cause error, got %v", e, a)
|
||||
}
|
||||
|
||||
expect := credentials.Value{
|
||||
ProviderName: ProviderName,
|
||||
}
|
||||
if expect != actual {
|
||||
t.Errorf("expect %+v credentials, got %+v", expect, actual)
|
||||
}
|
||||
}
|
||||
+16
-17
@@ -3,11 +3,11 @@ package credentials
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/go-ini/ini"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/internal/shareddefaults"
|
||||
)
|
||||
|
||||
// SharedCredsProviderName provides a name of SharedCreds provider
|
||||
@@ -15,8 +15,6 @@ const SharedCredsProviderName = "SharedCredentialsProvider"
|
||||
|
||||
var (
|
||||
// ErrSharedCredentialsHomeNotFound is emitted when the user directory cannot be found.
|
||||
//
|
||||
// @readonly
|
||||
ErrSharedCredentialsHomeNotFound = awserr.New("UserHomeNotFound", "user home directory not found.", nil)
|
||||
)
|
||||
|
||||
@@ -117,22 +115,23 @@ func loadProfile(filename, profile string) (Value, error) {
|
||||
//
|
||||
// Will return an error if the user's home directory path cannot be found.
|
||||
func (p *SharedCredentialsProvider) filename() (string, error) {
|
||||
if p.Filename == "" {
|
||||
if p.Filename = os.Getenv("AWS_SHARED_CREDENTIALS_FILE"); p.Filename != "" {
|
||||
return p.Filename, nil
|
||||
}
|
||||
|
||||
homeDir := os.Getenv("HOME") // *nix
|
||||
if homeDir == "" { // Windows
|
||||
homeDir = os.Getenv("USERPROFILE")
|
||||
}
|
||||
if homeDir == "" {
|
||||
return "", ErrSharedCredentialsHomeNotFound
|
||||
}
|
||||
|
||||
p.Filename = filepath.Join(homeDir, ".aws", "credentials")
|
||||
if len(p.Filename) != 0 {
|
||||
return p.Filename, nil
|
||||
}
|
||||
|
||||
if p.Filename = os.Getenv("AWS_SHARED_CREDENTIALS_FILE"); len(p.Filename) != 0 {
|
||||
return p.Filename, nil
|
||||
}
|
||||
|
||||
if home := shareddefaults.UserHomeDir(); len(home) == 0 {
|
||||
// Backwards compatibility of home directly not found error being returned.
|
||||
// This error is too verbose, failure when opening the file would of been
|
||||
// a better error to return.
|
||||
return "", ErrSharedCredentialsHomeNotFound
|
||||
}
|
||||
|
||||
p.Filename = shareddefaults.SharedCredentialsFilename()
|
||||
|
||||
return p.Filename, nil
|
||||
}
|
||||
|
||||
|
||||
Generated
Vendored
+20
@@ -5,6 +5,7 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/internal/shareddefaults"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -97,6 +98,25 @@ func TestSharedCredentialsProviderColonInCredFile(t *testing.T) {
|
||||
assert.Empty(t, creds.SessionToken, "Expect no token")
|
||||
}
|
||||
|
||||
func TestSharedCredentialsProvider_DefaultFilename(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("USERPROFILE", "profile_dir")
|
||||
os.Setenv("HOME", "home_dir")
|
||||
|
||||
// default filename and profile
|
||||
p := SharedCredentialsProvider{}
|
||||
|
||||
filename, err := p.filename()
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, got %v", err)
|
||||
}
|
||||
|
||||
if e, a := shareddefaults.SharedCredentialsFilename(), filename; e != a {
|
||||
t.Errorf("expect %q filename, got %q", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSharedCredentialsProvider(b *testing.B) {
|
||||
os.Clearenv()
|
||||
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -12,7 +12,7 @@ between multiple Credentials, Sessions or service clients.
|
||||
Assume Role
|
||||
|
||||
To assume an IAM role using STS with the SDK you can create a new Credentials
|
||||
with the SDKs's stscreds package.
|
||||
with the SDKs's stscreds package.
|
||||
|
||||
// Initial credentials loaded from SDK's default credential chain. Such as
|
||||
// the environment, shared credentials (~/.aws/credentials), or EC2 Instance
|
||||
|
||||
+38
-8
@@ -10,10 +10,12 @@ package defaults
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/aws/corehandlers"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
|
||||
@@ -96,23 +98,51 @@ func CredChain(cfg *aws.Config, handlers request.Handlers) *credentials.Credenti
|
||||
})
|
||||
}
|
||||
|
||||
// RemoteCredProvider returns a credenitials provider for the default remote
|
||||
const (
|
||||
httpProviderEnvVar = "AWS_CONTAINER_CREDENTIALS_FULL_URI"
|
||||
ecsCredsProviderEnvVar = "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"
|
||||
)
|
||||
|
||||
// RemoteCredProvider returns a credentials provider for the default remote
|
||||
// endpoints such as EC2 or ECS Roles.
|
||||
func RemoteCredProvider(cfg aws.Config, handlers request.Handlers) credentials.Provider {
|
||||
ecsCredURI := os.Getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI")
|
||||
if u := os.Getenv(httpProviderEnvVar); len(u) > 0 {
|
||||
return localHTTPCredProvider(cfg, handlers, u)
|
||||
}
|
||||
|
||||
if len(ecsCredURI) > 0 {
|
||||
return ecsCredProvider(cfg, handlers, ecsCredURI)
|
||||
if uri := os.Getenv(ecsCredsProviderEnvVar); len(uri) > 0 {
|
||||
u := fmt.Sprintf("http://169.254.170.2%s", uri)
|
||||
return httpCredProvider(cfg, handlers, u)
|
||||
}
|
||||
|
||||
return ec2RoleProvider(cfg, handlers)
|
||||
}
|
||||
|
||||
func ecsCredProvider(cfg aws.Config, handlers request.Handlers, uri string) credentials.Provider {
|
||||
const host = `169.254.170.2`
|
||||
func localHTTPCredProvider(cfg aws.Config, handlers request.Handlers, u string) credentials.Provider {
|
||||
var errMsg string
|
||||
|
||||
return endpointcreds.NewProviderClient(cfg, handlers,
|
||||
fmt.Sprintf("http://%s%s", host, uri),
|
||||
parsed, err := url.Parse(u)
|
||||
if err != nil {
|
||||
errMsg = fmt.Sprintf("invalid URL, %v", err)
|
||||
} else if host := aws.URLHostname(parsed); !(host == "localhost" || host == "127.0.0.1") {
|
||||
errMsg = fmt.Sprintf("invalid host address, %q, only localhost and 127.0.0.1 are valid.", host)
|
||||
}
|
||||
|
||||
if len(errMsg) > 0 {
|
||||
if cfg.Logger != nil {
|
||||
cfg.Logger.Log("Ignoring, HTTP credential provider", errMsg, err)
|
||||
}
|
||||
return credentials.ErrorProvider{
|
||||
Err: awserr.New("CredentialsEndpointError", errMsg, err),
|
||||
ProviderName: endpointcreds.ProviderName,
|
||||
}
|
||||
}
|
||||
|
||||
return httpCredProvider(cfg, handlers, u)
|
||||
}
|
||||
|
||||
func httpCredProvider(cfg aws.Config, handlers request.Handlers, u string) credentials.Provider {
|
||||
return endpointcreds.NewProviderClient(cfg, handlers, u,
|
||||
func(p *endpointcreds.Provider) {
|
||||
p.ExpiryWindow = 5 * time.Minute
|
||||
},
|
||||
|
||||
+64
-20
@@ -6,39 +6,83 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials/endpointcreds"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHTTPCredProvider(t *testing.T) {
|
||||
cases := []struct {
|
||||
Host string
|
||||
Fail bool
|
||||
}{
|
||||
{"localhost", false}, {"127.0.0.1", false},
|
||||
{"www.example.com", true}, {"169.254.170.2", true},
|
||||
}
|
||||
|
||||
defer os.Clearenv()
|
||||
|
||||
for i, c := range cases {
|
||||
u := fmt.Sprintf("http://%s/abc/123", c.Host)
|
||||
os.Setenv(httpProviderEnvVar, u)
|
||||
|
||||
provider := RemoteCredProvider(aws.Config{}, request.Handlers{})
|
||||
if provider == nil {
|
||||
t.Fatalf("%d, expect provider not to be nil, but was", i)
|
||||
}
|
||||
|
||||
if c.Fail {
|
||||
creds, err := provider.Retrieve()
|
||||
if err == nil {
|
||||
t.Fatalf("%d, expect error but got none", i)
|
||||
} else {
|
||||
aerr := err.(awserr.Error)
|
||||
if e, a := "CredentialsEndpointError", aerr.Code(); e != a {
|
||||
t.Errorf("%d, expect %s error code, got %s", i, e, a)
|
||||
}
|
||||
}
|
||||
if e, a := endpointcreds.ProviderName, creds.ProviderName; e != a {
|
||||
t.Errorf("%d, expect %s provider name got %s", i, e, a)
|
||||
}
|
||||
} else {
|
||||
httpProvider := provider.(*endpointcreds.Provider)
|
||||
if e, a := u, httpProvider.Client.Endpoint; e != a {
|
||||
t.Errorf("%d, expect %q endpoint, got %q", i, e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestECSCredProvider(t *testing.T) {
|
||||
defer os.Clearenv()
|
||||
os.Setenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/abc/123")
|
||||
os.Setenv(ecsCredsProviderEnvVar, "/abc/123")
|
||||
|
||||
provider := RemoteCredProvider(aws.Config{}, request.Handlers{})
|
||||
if provider == nil {
|
||||
t.Fatalf("expect provider not to be nil, but was")
|
||||
}
|
||||
|
||||
assert.NotNil(t, provider)
|
||||
|
||||
ecsProvider, ok := provider.(*endpointcreds.Provider)
|
||||
assert.NotNil(t, ecsProvider)
|
||||
assert.True(t, ok)
|
||||
|
||||
assert.Equal(t, fmt.Sprintf("http://169.254.170.2/abc/123"),
|
||||
ecsProvider.Client.Endpoint)
|
||||
httpProvider := provider.(*endpointcreds.Provider)
|
||||
if httpProvider == nil {
|
||||
t.Fatalf("expect provider not to be nil, but was")
|
||||
}
|
||||
if e, a := "http://169.254.170.2/abc/123", httpProvider.Client.Endpoint; e != a {
|
||||
t.Errorf("expect %q endpoint, got %q", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultEC2RoleProvider(t *testing.T) {
|
||||
provider := RemoteCredProvider(aws.Config{}, request.Handlers{})
|
||||
if provider == nil {
|
||||
t.Fatalf("expect provider not to be nil, but was")
|
||||
}
|
||||
|
||||
assert.NotNil(t, provider)
|
||||
|
||||
ec2Provider, ok := provider.(*ec2rolecreds.EC2RoleProvider)
|
||||
assert.NotNil(t, ec2Provider)
|
||||
assert.True(t, ok)
|
||||
|
||||
fmt.Println(ec2Provider.Client.Endpoint)
|
||||
|
||||
assert.Equal(t, fmt.Sprintf("http://169.254.169.254/latest"),
|
||||
ec2Provider.Client.Endpoint)
|
||||
ec2Provider := provider.(*ec2rolecreds.EC2RoleProvider)
|
||||
if ec2Provider == nil {
|
||||
t.Fatalf("expect provider not to be nil, but was")
|
||||
}
|
||||
if e, a := "http://169.254.169.254/latest", ec2Provider.Client.Endpoint; e != a {
|
||||
t.Errorf("expect %q endpoint, got %q", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package defaults
|
||||
|
||||
import (
|
||||
"github.com/aws/aws-sdk-go/internal/shareddefaults"
|
||||
)
|
||||
|
||||
// SharedCredentialsFilename returns the SDK's default file path
|
||||
// for the shared credentials file.
|
||||
//
|
||||
// Builds the shared config file path based on the OS's platform.
|
||||
//
|
||||
// - Linux/Unix: $HOME/.aws/credentials
|
||||
// - Windows: %USERPROFILE%\.aws\credentials
|
||||
func SharedCredentialsFilename() string {
|
||||
return shareddefaults.SharedCredentialsFilename()
|
||||
}
|
||||
|
||||
// SharedConfigFilename returns the SDK's default file path for
|
||||
// the shared config file.
|
||||
//
|
||||
// Builds the shared config file path based on the OS's platform.
|
||||
//
|
||||
// - Linux/Unix: $HOME/.aws/config
|
||||
// - Windows: %USERPROFILE%\.aws\config
|
||||
func SharedConfigFilename() string {
|
||||
return shareddefaults.SharedConfigFilename()
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
// Package aws provides the core SDK's utilities and shared types. Use this package's
|
||||
// utilities to simplify setting and reading API operations parameters.
|
||||
//
|
||||
// Value and Pointer Conversion Utilities
|
||||
//
|
||||
// This package includes a helper conversion utility for each scalar type the SDK's
|
||||
// API use. These utilities make getting a pointer of the scalar, and dereferencing
|
||||
// a pointer easier.
|
||||
//
|
||||
// Each conversion utility comes in two forms. Value to Pointer and Pointer to Value.
|
||||
// The Pointer to value will safely dereference the pointer and return its value.
|
||||
// If the pointer was nil, the scalar's zero value will be returned.
|
||||
//
|
||||
// The value to pointer functions will be named after the scalar type. So get a
|
||||
// *string from a string value use the "String" function. This makes it easy to
|
||||
// to get pointer of a literal string value, because getting the address of a
|
||||
// literal requires assigning the value to a variable first.
|
||||
//
|
||||
// var strPtr *string
|
||||
//
|
||||
// // Without the SDK's conversion functions
|
||||
// str := "my string"
|
||||
// strPtr = &str
|
||||
//
|
||||
// // With the SDK's conversion functions
|
||||
// strPtr = aws.String("my string")
|
||||
//
|
||||
// // Convert *string to string value
|
||||
// str = aws.StringValue(strPtr)
|
||||
//
|
||||
// In addition to scalars the aws package also includes conversion utilities for
|
||||
// map and slice for commonly types used in API parameters. The map and slice
|
||||
// conversion functions use similar naming pattern as the scalar conversion
|
||||
// functions.
|
||||
//
|
||||
// var strPtrs []*string
|
||||
// var strs []string = []string{"Go", "Gophers", "Go"}
|
||||
//
|
||||
// // Convert []string to []*string
|
||||
// strPtrs = aws.StringSlice(strs)
|
||||
//
|
||||
// // Convert []*string to []string
|
||||
// strs = aws.StringValueSlice(strPtrs)
|
||||
//
|
||||
// SDK Default HTTP Client
|
||||
//
|
||||
// The SDK will use the http.DefaultClient if a HTTP client is not provided to
|
||||
// the SDK's Session, or service client constructor. This means that if the
|
||||
// http.DefaultClient is modified by other components of your application the
|
||||
// modifications will be picked up by the SDK as well.
|
||||
//
|
||||
// In some cases this might be intended, but it is a better practice to create
|
||||
// a custom HTTP Client to share explicitly through your application. You can
|
||||
// configure the SDK to use the custom HTTP Client by setting the HTTPClient
|
||||
// value of the SDK's Config type when creating a Session or service client.
|
||||
package aws
|
||||
+365
-34
@@ -1,4 +1,4 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
|
||||
// Code generated by aws/endpoints/v3model_codegen.go. DO NOT EDIT.
|
||||
|
||||
package endpoints
|
||||
|
||||
@@ -47,6 +47,7 @@ const (
|
||||
ApigatewayServiceID = "apigateway" // Apigateway.
|
||||
ApplicationAutoscalingServiceID = "application-autoscaling" // ApplicationAutoscaling.
|
||||
Appstream2ServiceID = "appstream2" // Appstream2.
|
||||
AthenaServiceID = "athena" // Athena.
|
||||
AutoscalingServiceID = "autoscaling" // Autoscaling.
|
||||
BatchServiceID = "batch" // Batch.
|
||||
BudgetsServiceID = "budgets" // Budgets.
|
||||
@@ -54,12 +55,14 @@ const (
|
||||
CloudformationServiceID = "cloudformation" // Cloudformation.
|
||||
CloudfrontServiceID = "cloudfront" // Cloudfront.
|
||||
CloudhsmServiceID = "cloudhsm" // Cloudhsm.
|
||||
Cloudhsmv2ServiceID = "cloudhsmv2" // Cloudhsmv2.
|
||||
CloudsearchServiceID = "cloudsearch" // Cloudsearch.
|
||||
CloudtrailServiceID = "cloudtrail" // Cloudtrail.
|
||||
CodebuildServiceID = "codebuild" // Codebuild.
|
||||
CodecommitServiceID = "codecommit" // Codecommit.
|
||||
CodedeployServiceID = "codedeploy" // Codedeploy.
|
||||
CodepipelineServiceID = "codepipeline" // Codepipeline.
|
||||
CodestarServiceID = "codestar" // Codestar.
|
||||
CognitoIdentityServiceID = "cognito-identity" // CognitoIdentity.
|
||||
CognitoIdpServiceID = "cognito-idp" // CognitoIdp.
|
||||
CognitoSyncServiceID = "cognito-sync" // CognitoSync.
|
||||
@@ -83,11 +86,14 @@ const (
|
||||
ElasticmapreduceServiceID = "elasticmapreduce" // Elasticmapreduce.
|
||||
ElastictranscoderServiceID = "elastictranscoder" // Elastictranscoder.
|
||||
EmailServiceID = "email" // Email.
|
||||
EntitlementMarketplaceServiceID = "entitlement.marketplace" // EntitlementMarketplace.
|
||||
EsServiceID = "es" // Es.
|
||||
EventsServiceID = "events" // Events.
|
||||
FirehoseServiceID = "firehose" // Firehose.
|
||||
GameliftServiceID = "gamelift" // Gamelift.
|
||||
GlacierServiceID = "glacier" // Glacier.
|
||||
GlueServiceID = "glue" // Glue.
|
||||
GreengrassServiceID = "greengrass" // Greengrass.
|
||||
HealthServiceID = "health" // Health.
|
||||
IamServiceID = "iam" // Iam.
|
||||
ImportexportServiceID = "importexport" // Importexport.
|
||||
@@ -102,7 +108,9 @@ const (
|
||||
MachinelearningServiceID = "machinelearning" // Machinelearning.
|
||||
MarketplacecommerceanalyticsServiceID = "marketplacecommerceanalytics" // Marketplacecommerceanalytics.
|
||||
MeteringMarketplaceServiceID = "metering.marketplace" // MeteringMarketplace.
|
||||
MghServiceID = "mgh" // Mgh.
|
||||
MobileanalyticsServiceID = "mobileanalytics" // Mobileanalytics.
|
||||
ModelsLexServiceID = "models.lex" // ModelsLex.
|
||||
MonitoringServiceID = "monitoring" // Monitoring.
|
||||
MturkRequesterServiceID = "mturk-requester" // MturkRequester.
|
||||
OpsworksServiceID = "opsworks" // Opsworks.
|
||||
@@ -131,6 +139,7 @@ const (
|
||||
StsServiceID = "sts" // Sts.
|
||||
SupportServiceID = "support" // Support.
|
||||
SwfServiceID = "swf" // Swf.
|
||||
TaggingServiceID = "tagging" // Tagging.
|
||||
WafServiceID = "waf" // Waf.
|
||||
WafRegionalServiceID = "waf-regional" // WafRegional.
|
||||
WorkdocsServiceID = "workdocs" // Workdocs.
|
||||
@@ -141,17 +150,20 @@ const (
|
||||
// DefaultResolver returns an Endpoint resolver that will be able
|
||||
// to resolve endpoints for: AWS Standard, AWS China, and AWS GovCloud (US).
|
||||
//
|
||||
// Casting the return value of this func to a EnumPartitions will
|
||||
// allow you to get a list of the partitions in the order the endpoints
|
||||
// will be resolved in.
|
||||
// Use DefaultPartitions() to get the list of the default partitions.
|
||||
func DefaultResolver() Resolver {
|
||||
return defaultPartitions
|
||||
}
|
||||
|
||||
// DefaultPartitions returns a list of the partitions the SDK is bundled
|
||||
// with. The available partitions are: AWS Standard, AWS China, and AWS GovCloud (US).
|
||||
//
|
||||
// resolver := endpoints.DefaultResolver()
|
||||
// partitions := resolver.(endpoints.EnumPartitions).Partitions()
|
||||
// partitions := endpoints.DefaultPartitions
|
||||
// for _, p := range partitions {
|
||||
// // ... inspect partitions
|
||||
// }
|
||||
func DefaultResolver() Resolver {
|
||||
return defaultPartitions
|
||||
func DefaultPartitions() []Partition {
|
||||
return defaultPartitions.Partitions()
|
||||
}
|
||||
|
||||
var defaultPartitions = partitions{
|
||||
@@ -249,11 +261,14 @@ var awsPartition = partition{
|
||||
Endpoints: endpoints{
|
||||
"ap-northeast-1": endpoint{},
|
||||
"ap-northeast-2": endpoint{},
|
||||
"ap-south-1": endpoint{},
|
||||
"ap-southeast-1": endpoint{},
|
||||
"ap-southeast-2": endpoint{},
|
||||
"ca-central-1": endpoint{},
|
||||
"eu-central-1": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
"eu-west-2": endpoint{},
|
||||
"sa-east-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
"us-west-1": endpoint{},
|
||||
@@ -299,6 +314,17 @@ var awsPartition = partition{
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
"athena": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"ap-northeast-1": endpoint{},
|
||||
"ap-southeast-1": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
"autoscaling": service{
|
||||
Defaults: endpoint{
|
||||
Protocols: []string{"http", "https"},
|
||||
@@ -323,7 +349,15 @@ var awsPartition = partition{
|
||||
"batch": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"us-east-1": endpoint{},
|
||||
"ap-northeast-1": endpoint{},
|
||||
"ap-southeast-1": endpoint{},
|
||||
"ap-southeast-2": endpoint{},
|
||||
"eu-central-1": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
"eu-west-2": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
"budgets": service{
|
||||
@@ -345,6 +379,7 @@ var awsPartition = partition{
|
||||
"ap-southeast-1": endpoint{},
|
||||
"ap-southeast-2": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
"eu-west-2": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
@@ -398,6 +433,15 @@ var awsPartition = partition{
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
"cloudhsmv2": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"eu-west-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
"cloudsearch": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
@@ -435,19 +479,36 @@ var awsPartition = partition{
|
||||
"codebuild": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"eu-west-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
"ap-northeast-1": endpoint{},
|
||||
"ap-southeast-1": endpoint{},
|
||||
"ap-southeast-2": endpoint{},
|
||||
"ca-central-1": endpoint{},
|
||||
"eu-central-1": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
"eu-west-2": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
"us-west-1": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
"codecommit": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"eu-west-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
"ap-northeast-1": endpoint{},
|
||||
"ap-northeast-2": endpoint{},
|
||||
"ap-south-1": endpoint{},
|
||||
"ap-southeast-1": endpoint{},
|
||||
"ap-southeast-2": endpoint{},
|
||||
"ca-central-1": endpoint{},
|
||||
"eu-central-1": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
"eu-west-2": endpoint{},
|
||||
"sa-east-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
"us-west-1": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
"codedeploy": service{
|
||||
@@ -473,13 +534,32 @@ var awsPartition = partition{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"ap-northeast-1": endpoint{},
|
||||
"ap-northeast-2": endpoint{},
|
||||
"ap-south-1": endpoint{},
|
||||
"ap-southeast-1": endpoint{},
|
||||
"ap-southeast-2": endpoint{},
|
||||
"ca-central-1": endpoint{},
|
||||
"eu-central-1": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
"eu-west-2": endpoint{},
|
||||
"sa-east-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
"us-west-1": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
"codestar": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"ap-southeast-1": endpoint{},
|
||||
"ap-southeast-2": endpoint{},
|
||||
"eu-central-1": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
"sa-east-1": endpoint{},
|
||||
"eu-west-2": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
"us-west-1": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
@@ -488,6 +568,8 @@ var awsPartition = partition{
|
||||
Endpoints: endpoints{
|
||||
"ap-northeast-1": endpoint{},
|
||||
"ap-northeast-2": endpoint{},
|
||||
"ap-south-1": endpoint{},
|
||||
"ap-southeast-1": endpoint{},
|
||||
"ap-southeast-2": endpoint{},
|
||||
"eu-central-1": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
@@ -502,6 +584,8 @@ var awsPartition = partition{
|
||||
Endpoints: endpoints{
|
||||
"ap-northeast-1": endpoint{},
|
||||
"ap-northeast-2": endpoint{},
|
||||
"ap-south-1": endpoint{},
|
||||
"ap-southeast-1": endpoint{},
|
||||
"ap-southeast-2": endpoint{},
|
||||
"eu-central-1": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
@@ -516,6 +600,8 @@ var awsPartition = partition{
|
||||
Endpoints: endpoints{
|
||||
"ap-northeast-1": endpoint{},
|
||||
"ap-northeast-2": endpoint{},
|
||||
"ap-south-1": endpoint{},
|
||||
"ap-southeast-1": endpoint{},
|
||||
"ap-southeast-2": endpoint{},
|
||||
"eu-central-1": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
@@ -614,11 +700,16 @@ var awsPartition = partition{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"ap-northeast-1": endpoint{},
|
||||
"ap-northeast-2": endpoint{},
|
||||
"ap-southeast-1": endpoint{},
|
||||
"ap-southeast-2": endpoint{},
|
||||
"ca-central-1": endpoint{},
|
||||
"eu-central-1": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
"eu-west-2": endpoint{},
|
||||
"sa-east-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
@@ -755,15 +846,17 @@ var awsPartition = partition{
|
||||
"elasticfilesystem": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"eu-west-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
"ap-southeast-2": endpoint{},
|
||||
"eu-central-1": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
"elasticloadbalancing": service{
|
||||
Defaults: endpoint{
|
||||
Protocols: []string{"http", "https"},
|
||||
Protocols: []string{"https"},
|
||||
},
|
||||
Endpoints: endpoints{
|
||||
"ap-northeast-1": endpoint{},
|
||||
@@ -829,6 +922,16 @@ var awsPartition = partition{
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
"entitlement.marketplace": service{
|
||||
Defaults: endpoint{
|
||||
CredentialScope: credentialScope{
|
||||
Service: "aws-marketplace",
|
||||
},
|
||||
},
|
||||
Endpoints: endpoints{
|
||||
"us-east-1": endpoint{},
|
||||
},
|
||||
},
|
||||
"es": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
@@ -837,8 +940,10 @@ var awsPartition = partition{
|
||||
"ap-south-1": endpoint{},
|
||||
"ap-southeast-1": endpoint{},
|
||||
"ap-southeast-2": endpoint{},
|
||||
"ca-central-1": endpoint{},
|
||||
"eu-central-1": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
"eu-west-2": endpoint{},
|
||||
"sa-east-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
@@ -868,9 +973,12 @@ var awsPartition = partition{
|
||||
"firehose": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"eu-west-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
"ap-northeast-1": endpoint{},
|
||||
"eu-central-1": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
"gamelift": service{
|
||||
@@ -880,10 +988,15 @@ var awsPartition = partition{
|
||||
"ap-northeast-2": endpoint{},
|
||||
"ap-south-1": endpoint{},
|
||||
"ap-southeast-1": endpoint{},
|
||||
"ap-southeast-2": endpoint{},
|
||||
"ca-central-1": endpoint{},
|
||||
"eu-central-1": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
"eu-west-2": endpoint{},
|
||||
"sa-east-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
"us-west-1": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
@@ -895,6 +1008,7 @@ var awsPartition = partition{
|
||||
"ap-northeast-1": endpoint{},
|
||||
"ap-northeast-2": endpoint{},
|
||||
"ap-south-1": endpoint{},
|
||||
"ap-southeast-1": endpoint{},
|
||||
"ap-southeast-2": endpoint{},
|
||||
"ca-central-1": endpoint{},
|
||||
"eu-central-1": endpoint{},
|
||||
@@ -906,6 +1020,27 @@ var awsPartition = partition{
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
"glue": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
"greengrass": service{
|
||||
IsRegionalized: boxedTrue,
|
||||
Defaults: endpoint{
|
||||
Protocols: []string{"https"},
|
||||
},
|
||||
Endpoints: endpoints{
|
||||
"ap-northeast-1": endpoint{},
|
||||
"ap-southeast-2": endpoint{},
|
||||
"eu-central-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
"health": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
@@ -949,6 +1084,7 @@ var awsPartition = partition{
|
||||
"ap-southeast-2": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-west-1": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
@@ -1022,11 +1158,14 @@ var awsPartition = partition{
|
||||
Endpoints: endpoints{
|
||||
"ap-northeast-1": endpoint{},
|
||||
"ap-northeast-2": endpoint{},
|
||||
"ap-south-1": endpoint{},
|
||||
"ap-southeast-1": endpoint{},
|
||||
"ap-southeast-2": endpoint{},
|
||||
"ca-central-1": endpoint{},
|
||||
"eu-central-1": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
"eu-west-2": endpoint{},
|
||||
"sa-east-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
"us-west-1": endpoint{},
|
||||
@@ -1036,7 +1175,16 @@ var awsPartition = partition{
|
||||
"lightsail": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"us-east-1": endpoint{},
|
||||
"ap-northeast-1": endpoint{},
|
||||
"ap-south-1": endpoint{},
|
||||
"ap-southeast-1": endpoint{},
|
||||
"ap-southeast-2": endpoint{},
|
||||
"eu-central-1": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
"eu-west-2": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
"logs": service{
|
||||
@@ -1094,12 +1242,28 @@ var awsPartition = partition{
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
"mgh": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
"mobileanalytics": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"us-east-1": endpoint{},
|
||||
},
|
||||
},
|
||||
"models.lex": service{
|
||||
Defaults: endpoint{
|
||||
CredentialScope: credentialScope{
|
||||
Service: "lex",
|
||||
},
|
||||
},
|
||||
Endpoints: endpoints{
|
||||
"us-east-1": endpoint{},
|
||||
},
|
||||
},
|
||||
"monitoring": service{
|
||||
Defaults: endpoint{
|
||||
Protocols: []string{"http", "https"},
|
||||
@@ -1346,6 +1510,7 @@ var awsPartition = partition{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"ap-northeast-1": endpoint{},
|
||||
"ap-northeast-2": endpoint{},
|
||||
"ap-southeast-1": endpoint{},
|
||||
"ap-southeast-2": endpoint{},
|
||||
"ca-central-1": endpoint{},
|
||||
@@ -1370,20 +1535,27 @@ var awsPartition = partition{
|
||||
"sms": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"ap-northeast-1": endpoint{},
|
||||
"ap-northeast-2": endpoint{},
|
||||
"ap-south-1": endpoint{},
|
||||
"ap-southeast-2": endpoint{},
|
||||
"eu-central-1": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
"snowball": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"ap-northeast-1": endpoint{},
|
||||
"ap-south-1": endpoint{},
|
||||
"ap-southeast-2": endpoint{},
|
||||
"ca-central-1": endpoint{},
|
||||
"eu-central-1": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
"eu-west-2": endpoint{},
|
||||
"sa-east-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
"us-west-1": endpoint{},
|
||||
@@ -1440,10 +1612,13 @@ var awsPartition = partition{
|
||||
Endpoints: endpoints{
|
||||
"ap-northeast-1": endpoint{},
|
||||
"ap-northeast-2": endpoint{},
|
||||
"ap-south-1": endpoint{},
|
||||
"ap-southeast-1": endpoint{},
|
||||
"ap-southeast-2": endpoint{},
|
||||
"ca-central-1": endpoint{},
|
||||
"eu-central-1": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
"eu-west-2": endpoint{},
|
||||
"sa-east-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
@@ -1455,8 +1630,10 @@ var awsPartition = partition{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"ap-northeast-1": endpoint{},
|
||||
"ap-southeast-2": endpoint{},
|
||||
"eu-central-1": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
"eu-west-2": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
@@ -1467,6 +1644,7 @@ var awsPartition = partition{
|
||||
Endpoints: endpoints{
|
||||
"ap-northeast-1": endpoint{},
|
||||
"ap-northeast-2": endpoint{},
|
||||
"ap-south-1": endpoint{},
|
||||
"ap-southeast-1": endpoint{},
|
||||
"ap-southeast-2": endpoint{},
|
||||
"ca-central-1": endpoint{},
|
||||
@@ -1482,7 +1660,7 @@ var awsPartition = partition{
|
||||
},
|
||||
"streams.dynamodb": service{
|
||||
Defaults: endpoint{
|
||||
Protocols: []string{"http", "http", "https", "https"},
|
||||
Protocols: []string{"http", "https"},
|
||||
CredentialScope: credentialScope{
|
||||
Service: "dynamodb",
|
||||
},
|
||||
@@ -1537,9 +1715,33 @@ var awsPartition = partition{
|
||||
"eu-west-2": endpoint{},
|
||||
"sa-east-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
"us-west-1": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
"us-east-1-fips": endpoint{
|
||||
Hostname: "sts-fips.us-east-1.amazonaws.com",
|
||||
CredentialScope: credentialScope{
|
||||
Region: "us-east-1",
|
||||
},
|
||||
},
|
||||
"us-east-2": endpoint{},
|
||||
"us-east-2-fips": endpoint{
|
||||
Hostname: "sts-fips.us-east-2.amazonaws.com",
|
||||
CredentialScope: credentialScope{
|
||||
Region: "us-east-2",
|
||||
},
|
||||
},
|
||||
"us-west-1": endpoint{},
|
||||
"us-west-1-fips": endpoint{
|
||||
Hostname: "sts-fips.us-west-1.amazonaws.com",
|
||||
CredentialScope: credentialScope{
|
||||
Region: "us-west-1",
|
||||
},
|
||||
},
|
||||
"us-west-2": endpoint{},
|
||||
"us-west-2-fips": endpoint{
|
||||
Hostname: "sts-fips.us-west-2.amazonaws.com",
|
||||
CredentialScope: credentialScope{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"support": service{
|
||||
@@ -1567,6 +1769,25 @@ var awsPartition = partition{
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
"tagging": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"ap-northeast-1": endpoint{},
|
||||
"ap-northeast-2": endpoint{},
|
||||
"ap-south-1": endpoint{},
|
||||
"ap-southeast-1": endpoint{},
|
||||
"ap-southeast-2": endpoint{},
|
||||
"ca-central-1": endpoint{},
|
||||
"eu-central-1": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
"eu-west-2": endpoint{},
|
||||
"sa-east-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
"us-west-1": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
"waf": service{
|
||||
PartitionEndpoint: "aws-global",
|
||||
IsRegionalized: boxedFalse,
|
||||
@@ -1586,6 +1807,7 @@ var awsPartition = partition{
|
||||
"ap-northeast-1": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-west-1": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
},
|
||||
@@ -1608,6 +1830,7 @@ var awsPartition = partition{
|
||||
"ap-southeast-2": endpoint{},
|
||||
"eu-central-1": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
"eu-west-2": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-west-2": endpoint{},
|
||||
},
|
||||
@@ -1620,8 +1843,10 @@ var awsPartition = partition{
|
||||
"ap-south-1": endpoint{},
|
||||
"ap-southeast-1": endpoint{},
|
||||
"ap-southeast-2": endpoint{},
|
||||
"ca-central-1": endpoint{},
|
||||
"eu-central-1": endpoint{},
|
||||
"eu-west-1": endpoint{},
|
||||
"eu-west-2": endpoint{},
|
||||
"sa-east-1": endpoint{},
|
||||
"us-east-1": endpoint{},
|
||||
"us-east-2": endpoint{},
|
||||
@@ -1658,6 +1883,18 @@ var awscnPartition = partition{
|
||||
},
|
||||
},
|
||||
Services: services{
|
||||
"application-autoscaling": service{
|
||||
Defaults: endpoint{
|
||||
Hostname: "autoscaling.{region}.amazonaws.com",
|
||||
Protocols: []string{"http", "https"},
|
||||
CredentialScope: credentialScope{
|
||||
Service: "application-autoscaling",
|
||||
},
|
||||
},
|
||||
Endpoints: endpoints{
|
||||
"cn-north-1": endpoint{},
|
||||
},
|
||||
},
|
||||
"autoscaling": service{
|
||||
Defaults: endpoint{
|
||||
Protocols: []string{"http", "https"},
|
||||
@@ -1678,6 +1915,12 @@ var awscnPartition = partition{
|
||||
"cn-north-1": endpoint{},
|
||||
},
|
||||
},
|
||||
"codedeploy": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"cn-north-1": endpoint{},
|
||||
},
|
||||
},
|
||||
"config": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
@@ -1717,6 +1960,18 @@ var awscnPartition = partition{
|
||||
},
|
||||
},
|
||||
},
|
||||
"ecr": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"cn-north-1": endpoint{},
|
||||
},
|
||||
},
|
||||
"ecs": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"cn-north-1": endpoint{},
|
||||
},
|
||||
},
|
||||
"elasticache": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
@@ -1731,7 +1986,7 @@ var awscnPartition = partition{
|
||||
},
|
||||
"elasticloadbalancing": service{
|
||||
Defaults: endpoint{
|
||||
Protocols: []string{"http", "https"},
|
||||
Protocols: []string{"https"},
|
||||
},
|
||||
Endpoints: endpoints{
|
||||
"cn-north-1": endpoint{},
|
||||
@@ -1772,6 +2027,16 @@ var awscnPartition = partition{
|
||||
},
|
||||
},
|
||||
},
|
||||
"iot": service{
|
||||
Defaults: endpoint{
|
||||
CredentialScope: credentialScope{
|
||||
Service: "execute-api",
|
||||
},
|
||||
},
|
||||
Endpoints: endpoints{
|
||||
"cn-north-1": endpoint{},
|
||||
},
|
||||
},
|
||||
"kinesis": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
@@ -1813,6 +2078,12 @@ var awscnPartition = partition{
|
||||
"cn-north-1": endpoint{},
|
||||
},
|
||||
},
|
||||
"snowball": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"cn-north-1": endpoint{},
|
||||
},
|
||||
},
|
||||
"sns": service{
|
||||
Defaults: endpoint{
|
||||
Protocols: []string{"http", "https"},
|
||||
@@ -1830,6 +2101,12 @@ var awscnPartition = partition{
|
||||
"cn-north-1": endpoint{},
|
||||
},
|
||||
},
|
||||
"ssm": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"cn-north-1": endpoint{},
|
||||
},
|
||||
},
|
||||
"storagegateway": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
@@ -1838,7 +2115,7 @@ var awscnPartition = partition{
|
||||
},
|
||||
"streams.dynamodb": service{
|
||||
Defaults: endpoint{
|
||||
Protocols: []string{"http", "http", "https", "https"},
|
||||
Protocols: []string{"http", "https"},
|
||||
CredentialScope: credentialScope{
|
||||
Service: "dynamodb",
|
||||
},
|
||||
@@ -1855,6 +2132,12 @@ var awscnPartition = partition{
|
||||
},
|
||||
"swf": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"cn-north-1": endpoint{},
|
||||
},
|
||||
},
|
||||
"tagging": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"cn-north-1": endpoint{},
|
||||
},
|
||||
@@ -1888,6 +2171,18 @@ var awsusgovPartition = partition{
|
||||
},
|
||||
},
|
||||
Services: services{
|
||||
"acm": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"us-gov-west-1": endpoint{},
|
||||
},
|
||||
},
|
||||
"apigateway": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"us-gov-west-1": endpoint{},
|
||||
},
|
||||
},
|
||||
"autoscaling": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
@@ -1914,6 +2209,12 @@ var awsusgovPartition = partition{
|
||||
"us-gov-west-1": endpoint{},
|
||||
},
|
||||
},
|
||||
"codedeploy": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"us-gov-west-1": endpoint{},
|
||||
},
|
||||
},
|
||||
"config": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
@@ -1971,6 +2272,12 @@ var awsusgovPartition = partition{
|
||||
},
|
||||
},
|
||||
},
|
||||
"events": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"us-gov-west-1": endpoint{},
|
||||
},
|
||||
},
|
||||
"glacier": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
@@ -2004,6 +2311,12 @@ var awsusgovPartition = partition{
|
||||
"us-gov-west-1": endpoint{},
|
||||
},
|
||||
},
|
||||
"lambda": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"us-gov-west-1": endpoint{},
|
||||
},
|
||||
},
|
||||
"logs": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
@@ -2028,6 +2341,12 @@ var awsusgovPartition = partition{
|
||||
"us-gov-west-1": endpoint{},
|
||||
},
|
||||
},
|
||||
"rekognition": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"us-gov-west-1": endpoint{},
|
||||
},
|
||||
},
|
||||
"s3": service{
|
||||
Defaults: endpoint{
|
||||
SignatureVersions: []string{"s3", "s3v4"},
|
||||
@@ -2045,6 +2364,12 @@ var awsusgovPartition = partition{
|
||||
},
|
||||
},
|
||||
},
|
||||
"sms": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"us-gov-west-1": endpoint{},
|
||||
},
|
||||
},
|
||||
"snowball": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
@@ -2068,6 +2393,12 @@ var awsusgovPartition = partition{
|
||||
},
|
||||
},
|
||||
},
|
||||
"ssm": service{
|
||||
|
||||
Endpoints: endpoints{
|
||||
"us-gov-west-1": endpoint{},
|
||||
},
|
||||
},
|
||||
"streams.dynamodb": service{
|
||||
Defaults: endpoint{
|
||||
CredentialScope: credentialScope{
|
||||
|
||||
+2
-2
@@ -21,12 +21,12 @@
|
||||
// partitions := resolver.(endpoints.EnumPartitions).Partitions()
|
||||
//
|
||||
// for _, p := range partitions {
|
||||
// fmt.Println("Regions for", p.Name)
|
||||
// fmt.Println("Regions for", p.ID())
|
||||
// for id, _ := range p.Regions() {
|
||||
// fmt.Println("*", id)
|
||||
// }
|
||||
//
|
||||
// fmt.Println("Services for", p.Name)
|
||||
// fmt.Println("Services for", p.ID())
|
||||
// for id, _ := range p.Services() {
|
||||
// fmt.Println("*", id)
|
||||
// }
|
||||
|
||||
+77
-35
@@ -124,6 +124,49 @@ type EnumPartitions interface {
|
||||
Partitions() []Partition
|
||||
}
|
||||
|
||||
// RegionsForService returns a map of regions for the partition and service.
|
||||
// If either the partition or service does not exist false will be returned
|
||||
// as the second parameter.
|
||||
//
|
||||
// This example shows how to get the regions for DynamoDB in the AWS partition.
|
||||
// rs, exists := endpoints.RegionsForService(endpoints.DefaultPartitions(), endpoints.AwsPartitionID, endpoints.DynamodbServiceID)
|
||||
//
|
||||
// This is equivalent to using the partition directly.
|
||||
// rs := endpoints.AwsPartition().Services()[endpoints.DynamodbServiceID].Regions()
|
||||
func RegionsForService(ps []Partition, partitionID, serviceID string) (map[string]Region, bool) {
|
||||
for _, p := range ps {
|
||||
if p.ID() != partitionID {
|
||||
continue
|
||||
}
|
||||
if _, ok := p.p.Services[serviceID]; !ok {
|
||||
break
|
||||
}
|
||||
|
||||
s := Service{
|
||||
id: serviceID,
|
||||
p: p.p,
|
||||
}
|
||||
return s.Regions(), true
|
||||
}
|
||||
|
||||
return map[string]Region{}, false
|
||||
}
|
||||
|
||||
// PartitionForRegion returns the first partition which includes the region
|
||||
// passed in. This includes both known regions and regions which match
|
||||
// a pattern supported by the partition which may include regions that are
|
||||
// not explicitly known by the partition. Use the Regions method of the
|
||||
// returned Partition if explicit support is needed.
|
||||
func PartitionForRegion(ps []Partition, regionID string) (Partition, bool) {
|
||||
for _, p := range ps {
|
||||
if _, ok := p.p.Regions[regionID]; ok || p.p.RegionRegex.MatchString(regionID) {
|
||||
return p, true
|
||||
}
|
||||
}
|
||||
|
||||
return Partition{}, false
|
||||
}
|
||||
|
||||
// A Partition provides the ability to enumerate the partition's regions
|
||||
// and services.
|
||||
type Partition struct {
|
||||
@@ -132,7 +175,7 @@ type Partition struct {
|
||||
}
|
||||
|
||||
// ID returns the identifier of the partition.
|
||||
func (p *Partition) ID() string { return p.id }
|
||||
func (p Partition) ID() string { return p.id }
|
||||
|
||||
// EndpointFor attempts to resolve the endpoint based on service and region.
|
||||
// See Options for information on configuring how the endpoint is resolved.
|
||||
@@ -155,13 +198,13 @@ func (p *Partition) ID() string { return p.id }
|
||||
// Errors that can be returned.
|
||||
// * UnknownServiceError
|
||||
// * UnknownEndpointError
|
||||
func (p *Partition) EndpointFor(service, region string, opts ...func(*Options)) (ResolvedEndpoint, error) {
|
||||
func (p Partition) EndpointFor(service, region string, opts ...func(*Options)) (ResolvedEndpoint, error) {
|
||||
return p.p.EndpointFor(service, region, opts...)
|
||||
}
|
||||
|
||||
// Regions returns a map of Regions indexed by their ID. This is useful for
|
||||
// enumerating over the regions in a partition.
|
||||
func (p *Partition) Regions() map[string]Region {
|
||||
func (p Partition) Regions() map[string]Region {
|
||||
rs := map[string]Region{}
|
||||
for id := range p.p.Regions {
|
||||
rs[id] = Region{
|
||||
@@ -175,7 +218,7 @@ func (p *Partition) Regions() map[string]Region {
|
||||
|
||||
// Services returns a map of Service indexed by their ID. This is useful for
|
||||
// enumerating over the services in a partition.
|
||||
func (p *Partition) Services() map[string]Service {
|
||||
func (p Partition) Services() map[string]Service {
|
||||
ss := map[string]Service{}
|
||||
for id := range p.p.Services {
|
||||
ss[id] = Service{
|
||||
@@ -195,16 +238,16 @@ type Region struct {
|
||||
}
|
||||
|
||||
// ID returns the region's identifier.
|
||||
func (r *Region) ID() string { return r.id }
|
||||
func (r Region) ID() string { return r.id }
|
||||
|
||||
// ResolveEndpoint resolves an endpoint from the context of the region given
|
||||
// a service. See Partition.EndpointFor for usage and errors that can be returned.
|
||||
func (r *Region) ResolveEndpoint(service string, opts ...func(*Options)) (ResolvedEndpoint, error) {
|
||||
func (r Region) ResolveEndpoint(service string, opts ...func(*Options)) (ResolvedEndpoint, error) {
|
||||
return r.p.EndpointFor(service, r.id, opts...)
|
||||
}
|
||||
|
||||
// Services returns a list of all services that are known to be in this region.
|
||||
func (r *Region) Services() map[string]Service {
|
||||
func (r Region) Services() map[string]Service {
|
||||
ss := map[string]Service{}
|
||||
for id, s := range r.p.Services {
|
||||
if _, ok := s.Endpoints[r.id]; ok {
|
||||
@@ -226,17 +269,38 @@ type Service struct {
|
||||
}
|
||||
|
||||
// ID returns the identifier for the service.
|
||||
func (s *Service) ID() string { return s.id }
|
||||
func (s Service) ID() string { return s.id }
|
||||
|
||||
// ResolveEndpoint resolves an endpoint from the context of a service given
|
||||
// a region. See Partition.EndpointFor for usage and errors that can be returned.
|
||||
func (s *Service) ResolveEndpoint(region string, opts ...func(*Options)) (ResolvedEndpoint, error) {
|
||||
func (s Service) ResolveEndpoint(region string, opts ...func(*Options)) (ResolvedEndpoint, error) {
|
||||
return s.p.EndpointFor(s.id, region, opts...)
|
||||
}
|
||||
|
||||
// Regions returns a map of Regions that the service is present in.
|
||||
//
|
||||
// A region is the AWS region the service exists in. Whereas a Endpoint is
|
||||
// an URL that can be resolved to a instance of a service.
|
||||
func (s Service) Regions() map[string]Region {
|
||||
rs := map[string]Region{}
|
||||
for id := range s.p.Services[s.id].Endpoints {
|
||||
if _, ok := s.p.Regions[id]; ok {
|
||||
rs[id] = Region{
|
||||
id: id,
|
||||
p: s.p,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rs
|
||||
}
|
||||
|
||||
// Endpoints returns a map of Endpoints indexed by their ID for all known
|
||||
// endpoints for a service.
|
||||
func (s *Service) Endpoints() map[string]Endpoint {
|
||||
//
|
||||
// A region is the AWS region the service exists in. Whereas a Endpoint is
|
||||
// an URL that can be resolved to a instance of a service.
|
||||
func (s Service) Endpoints() map[string]Endpoint {
|
||||
es := map[string]Endpoint{}
|
||||
for id := range s.p.Services[s.id].Endpoints {
|
||||
es[id] = Endpoint{
|
||||
@@ -259,15 +323,15 @@ type Endpoint struct {
|
||||
}
|
||||
|
||||
// ID returns the identifier for an endpoint.
|
||||
func (e *Endpoint) ID() string { return e.id }
|
||||
func (e Endpoint) ID() string { return e.id }
|
||||
|
||||
// ServiceID returns the identifier the endpoint belongs to.
|
||||
func (e *Endpoint) ServiceID() string { return e.serviceID }
|
||||
func (e Endpoint) ServiceID() string { return e.serviceID }
|
||||
|
||||
// ResolveEndpoint resolves an endpoint from the context of a service and
|
||||
// region the endpoint represents. See Partition.EndpointFor for usage and
|
||||
// errors that can be returned.
|
||||
func (e *Endpoint) ResolveEndpoint(opts ...func(*Options)) (ResolvedEndpoint, error) {
|
||||
func (e Endpoint) ResolveEndpoint(opts ...func(*Options)) (ResolvedEndpoint, error) {
|
||||
return e.p.EndpointFor(e.serviceID, e.id, opts...)
|
||||
}
|
||||
|
||||
@@ -300,28 +364,6 @@ type EndpointNotFoundError struct {
|
||||
Region string
|
||||
}
|
||||
|
||||
//// NewEndpointNotFoundError builds and returns NewEndpointNotFoundError.
|
||||
//func NewEndpointNotFoundError(p, s, r string) EndpointNotFoundError {
|
||||
// return EndpointNotFoundError{
|
||||
// awsError: awserr.New("EndpointNotFoundError", "unable to find endpoint", nil),
|
||||
// Partition: p,
|
||||
// Service: s,
|
||||
// Region: r,
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// Error returns string representation of the error.
|
||||
//func (e EndpointNotFoundError) Error() string {
|
||||
// extra := fmt.Sprintf("partition: %q, service: %q, region: %q",
|
||||
// e.Partition, e.Service, e.Region)
|
||||
// return awserr.SprintError(e.Code(), e.Message(), extra, e.OrigErr())
|
||||
//}
|
||||
//
|
||||
//// String returns the string representation of the error.
|
||||
//func (e EndpointNotFoundError) String() string {
|
||||
// return e.Error()
|
||||
//}
|
||||
|
||||
// A UnknownServiceError is returned when the service does not resolve to an
|
||||
// endpoint. Includes a list of all known services for the partition. Returned
|
||||
// when a partition does not support the service.
|
||||
|
||||
+91
@@ -84,6 +84,22 @@ func TestEnumRegionServices(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnumServiceRegions(t *testing.T) {
|
||||
p := testPartitions[0].Partition()
|
||||
|
||||
rs := p.Services()["service1"].Regions()
|
||||
if e, a := 2, len(rs); e != a {
|
||||
t.Errorf("expect %d regions, got %d", e, a)
|
||||
}
|
||||
|
||||
if _, ok := rs["us-east-1"]; !ok {
|
||||
t.Errorf("expect region to be found")
|
||||
}
|
||||
if _, ok := rs["us-west-2"]; !ok {
|
||||
t.Errorf("expect region to be found")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnumServicesEndpoints(t *testing.T) {
|
||||
p := testPartitions[0].Partition()
|
||||
|
||||
@@ -242,3 +258,78 @@ func TestOptionsSet(t *testing.T) {
|
||||
t.Errorf("expect %v options got %v", expect, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegionsForService(t *testing.T) {
|
||||
ps := DefaultPartitions()
|
||||
|
||||
var expect map[string]Region
|
||||
var serviceID string
|
||||
for _, s := range ps[0].Services() {
|
||||
expect = s.Regions()
|
||||
serviceID = s.ID()
|
||||
if len(expect) > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
actual, ok := RegionsForService(ps, ps[0].ID(), serviceID)
|
||||
if !ok {
|
||||
t.Fatalf("expect regions to be found, was not")
|
||||
}
|
||||
|
||||
if len(actual) == 0 {
|
||||
t.Fatalf("expect service %s to have regions", serviceID)
|
||||
}
|
||||
if e, a := len(expect), len(actual); e != a {
|
||||
t.Fatalf("expect %d regions, got %d", e, a)
|
||||
}
|
||||
|
||||
for id, r := range actual {
|
||||
if e, a := id, r.ID(); e != a {
|
||||
t.Errorf("expect %s region id, got %s", e, a)
|
||||
}
|
||||
if _, ok := expect[id]; !ok {
|
||||
t.Errorf("expect %s region to be found", id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegionsForService_NotFound(t *testing.T) {
|
||||
ps := testPartitions.Partitions()
|
||||
|
||||
actual, ok := RegionsForService(ps, ps[0].ID(), "service-not-exists")
|
||||
if ok {
|
||||
t.Fatalf("expect no regions to be found, but were")
|
||||
}
|
||||
if len(actual) != 0 {
|
||||
t.Errorf("expect no regions, got %v", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPartitionForRegion(t *testing.T) {
|
||||
ps := DefaultPartitions()
|
||||
expect := ps[len(ps)%2]
|
||||
|
||||
var regionID string
|
||||
for id := range expect.Regions() {
|
||||
regionID = id
|
||||
break
|
||||
}
|
||||
|
||||
actual, ok := PartitionForRegion(ps, regionID)
|
||||
if !ok {
|
||||
t.Fatalf("expect partition to be found")
|
||||
}
|
||||
if e, a := expect.ID(), actual.ID(); e != a {
|
||||
t.Errorf("expect %s partition, got %s", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPartitionForRegion_NotFound(t *testing.T) {
|
||||
ps := DefaultPartitions()
|
||||
|
||||
actual, ok := PartitionForRegion(ps, "regionNotExists")
|
||||
if ok {
|
||||
t.Errorf("expect no partition to be found, got %v", actual)
|
||||
}
|
||||
}
|
||||
|
||||
+11
-8
@@ -158,7 +158,7 @@ var funcMap = template.FuncMap{
|
||||
|
||||
const v3Tmpl = `
|
||||
{{ define "defaults" -}}
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
|
||||
// Code generated by aws/endpoints/v3model_codegen.go. DO NOT EDIT.
|
||||
|
||||
package endpoints
|
||||
|
||||
@@ -209,17 +209,20 @@ import (
|
||||
// DefaultResolver returns an Endpoint resolver that will be able
|
||||
// to resolve endpoints for: {{ ListPartitionNames . }}.
|
||||
//
|
||||
// Casting the return value of this func to a EnumPartitions will
|
||||
// allow you to get a list of the partitions in the order the endpoints
|
||||
// will be resolved in.
|
||||
// Use DefaultPartitions() to get the list of the default partitions.
|
||||
func DefaultResolver() Resolver {
|
||||
return defaultPartitions
|
||||
}
|
||||
|
||||
// DefaultPartitions returns a list of the partitions the SDK is bundled
|
||||
// with. The available partitions are: {{ ListPartitionNames . }}.
|
||||
//
|
||||
// resolver := endpoints.DefaultResolver()
|
||||
// partitions := resolver.(endpoints.EnumPartitions).Partitions()
|
||||
// partitions := endpoints.DefaultPartitions
|
||||
// for _, p := range partitions {
|
||||
// // ... inspect partitions
|
||||
// }
|
||||
func DefaultResolver() Resolver {
|
||||
return defaultPartitions
|
||||
func DefaultPartitions() []Partition {
|
||||
return defaultPartitions.Partitions()
|
||||
}
|
||||
|
||||
var defaultPartitions = partitions{
|
||||
|
||||
+2
-1
@@ -4,7 +4,8 @@ package aws
|
||||
// into a json string. This type can be used just like any other map.
|
||||
//
|
||||
// Example:
|
||||
// values := JSONValue{
|
||||
//
|
||||
// values := aws.JSONValue{
|
||||
// "Foo": "Bar",
|
||||
// }
|
||||
// values["Baz"] = "Qux"
|
||||
|
||||
+2
-2
@@ -26,14 +26,14 @@ func (l *LogLevelType) Value() LogLevelType {
|
||||
|
||||
// Matches returns true if the v LogLevel is enabled by this LogLevel. Should be
|
||||
// used with logging sub levels. Is safe to use on nil value LogLevelTypes. If
|
||||
// LogLevel is nill, will default to LogOff comparison.
|
||||
// LogLevel is nil, will default to LogOff comparison.
|
||||
func (l *LogLevelType) Matches(v LogLevelType) bool {
|
||||
c := l.Value()
|
||||
return c&v == v
|
||||
}
|
||||
|
||||
// AtLeast returns true if this LogLevel is at least high enough to satisfies v.
|
||||
// Is safe to use on nil value LogLevelTypes. If LogLevel is nill, will default
|
||||
// Is safe to use on nil value LogLevelTypes. If LogLevel is nil, will default
|
||||
// to LogOff comparison.
|
||||
func (l *LogLevelType) AtLeast(v LogLevelType) bool {
|
||||
c := l.Value()
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// +build !appengine,!plan9
|
||||
|
||||
package request
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func isErrConnectionReset(err error) bool {
|
||||
if opErr, ok := err.(*net.OpError); ok {
|
||||
if sysErr, ok := opErr.Err.(*os.SyscallError); ok {
|
||||
return sysErr.Err == syscall.ECONNRESET
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// +build appengine plan9
|
||||
|
||||
package request
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func isErrConnectionReset(err error) bool {
|
||||
return strings.Contains(err.Error(), "connection reset")
|
||||
}
|
||||
Generated
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
// +build appengine plan9
|
||||
|
||||
package request_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var stubConnectionResetError = errors.New("connection reset")
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// +build !appengine,!plan9
|
||||
|
||||
package request_test
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var stubConnectionResetError = &net.OpError{Err: &os.SyscallError{Syscall: "read", Err: syscall.ECONNRESET}}
|
||||
+69
-13
@@ -88,13 +88,17 @@ func (l *HandlerList) copy() HandlerList {
|
||||
n := HandlerList{
|
||||
AfterEachFn: l.AfterEachFn,
|
||||
}
|
||||
if len(l.list) == 0 {
|
||||
return n
|
||||
}
|
||||
|
||||
n.list = append(make([]NamedHandler, 0, len(l.list)), l.list...)
|
||||
return n
|
||||
}
|
||||
|
||||
// Clear clears the handler list.
|
||||
func (l *HandlerList) Clear() {
|
||||
l.list = []NamedHandler{}
|
||||
l.list = l.list[0:0]
|
||||
}
|
||||
|
||||
// Len returns the number of handlers in the list.
|
||||
@@ -104,33 +108,85 @@ func (l *HandlerList) Len() int {
|
||||
|
||||
// PushBack pushes handler f to the back of the handler list.
|
||||
func (l *HandlerList) PushBack(f func(*Request)) {
|
||||
l.list = append(l.list, NamedHandler{"__anonymous", f})
|
||||
}
|
||||
|
||||
// PushFront pushes handler f to the front of the handler list.
|
||||
func (l *HandlerList) PushFront(f func(*Request)) {
|
||||
l.list = append([]NamedHandler{{"__anonymous", f}}, l.list...)
|
||||
l.PushBackNamed(NamedHandler{"__anonymous", f})
|
||||
}
|
||||
|
||||
// PushBackNamed pushes named handler f to the back of the handler list.
|
||||
func (l *HandlerList) PushBackNamed(n NamedHandler) {
|
||||
if cap(l.list) == 0 {
|
||||
l.list = make([]NamedHandler, 0, 5)
|
||||
}
|
||||
l.list = append(l.list, n)
|
||||
}
|
||||
|
||||
// PushFront pushes handler f to the front of the handler list.
|
||||
func (l *HandlerList) PushFront(f func(*Request)) {
|
||||
l.PushFrontNamed(NamedHandler{"__anonymous", f})
|
||||
}
|
||||
|
||||
// PushFrontNamed pushes named handler f to the front of the handler list.
|
||||
func (l *HandlerList) PushFrontNamed(n NamedHandler) {
|
||||
l.list = append([]NamedHandler{n}, l.list...)
|
||||
if cap(l.list) == len(l.list) {
|
||||
// Allocating new list required
|
||||
l.list = append([]NamedHandler{n}, l.list...)
|
||||
} else {
|
||||
// Enough room to prepend into list.
|
||||
l.list = append(l.list, NamedHandler{})
|
||||
copy(l.list[1:], l.list)
|
||||
l.list[0] = n
|
||||
}
|
||||
}
|
||||
|
||||
// Remove removes a NamedHandler n
|
||||
func (l *HandlerList) Remove(n NamedHandler) {
|
||||
newlist := []NamedHandler{}
|
||||
for _, m := range l.list {
|
||||
if m.Name != n.Name {
|
||||
newlist = append(newlist, m)
|
||||
l.RemoveByName(n.Name)
|
||||
}
|
||||
|
||||
// RemoveByName removes a NamedHandler by name.
|
||||
func (l *HandlerList) RemoveByName(name string) {
|
||||
for i := 0; i < len(l.list); i++ {
|
||||
m := l.list[i]
|
||||
if m.Name == name {
|
||||
// Shift array preventing creating new arrays
|
||||
copy(l.list[i:], l.list[i+1:])
|
||||
l.list[len(l.list)-1] = NamedHandler{}
|
||||
l.list = l.list[:len(l.list)-1]
|
||||
|
||||
// decrement list so next check to length is correct
|
||||
i--
|
||||
}
|
||||
}
|
||||
l.list = newlist
|
||||
}
|
||||
|
||||
// SwapNamed will swap out any existing handlers with the same name as the
|
||||
// passed in NamedHandler returning true if handlers were swapped. False is
|
||||
// returned otherwise.
|
||||
func (l *HandlerList) SwapNamed(n NamedHandler) (swapped bool) {
|
||||
for i := 0; i < len(l.list); i++ {
|
||||
if l.list[i].Name == n.Name {
|
||||
l.list[i].Fn = n.Fn
|
||||
swapped = true
|
||||
}
|
||||
}
|
||||
|
||||
return swapped
|
||||
}
|
||||
|
||||
// SetBackNamed will replace the named handler if it exists in the handler list.
|
||||
// If the handler does not exist the handler will be added to the end of the list.
|
||||
func (l *HandlerList) SetBackNamed(n NamedHandler) {
|
||||
if !l.SwapNamed(n) {
|
||||
l.PushBackNamed(n)
|
||||
}
|
||||
}
|
||||
|
||||
// SetFrontNamed will replace the named handler if it exists in the handler list.
|
||||
// If the handler does not exist the handler will be added to the beginning of
|
||||
// the list.
|
||||
func (l *HandlerList) SetFrontNamed(n NamedHandler) {
|
||||
if !l.SwapNamed(n) {
|
||||
l.PushFrontNamed(n)
|
||||
}
|
||||
}
|
||||
|
||||
// Run executes all handlers in the list with a given request object.
|
||||
|
||||
+188
-9
@@ -1,12 +1,13 @@
|
||||
package request_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/awstesting/unit"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
)
|
||||
|
||||
func TestHandlerList(t *testing.T) {
|
||||
@@ -18,8 +19,12 @@ func TestHandlerList(t *testing.T) {
|
||||
r.Data = s
|
||||
})
|
||||
l.Run(r)
|
||||
assert.Equal(t, "a", s)
|
||||
assert.Equal(t, "a", r.Data)
|
||||
if e, a := "a", s; e != a {
|
||||
t.Errorf("expect %q update got %q", e, a)
|
||||
}
|
||||
if e, a := "a", r.Data.(string); e != a {
|
||||
t.Errorf("expect %q data update got %q", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleHandlers(t *testing.T) {
|
||||
@@ -41,9 +46,110 @@ func TestNamedHandlers(t *testing.T) {
|
||||
l.PushBackNamed(named)
|
||||
l.PushBackNamed(named2)
|
||||
l.PushBack(func(r *request.Request) {})
|
||||
assert.Equal(t, 4, l.Len())
|
||||
if e, a := 4, l.Len(); e != a {
|
||||
t.Errorf("expect %d list length, got %d", e, a)
|
||||
}
|
||||
l.Remove(named)
|
||||
assert.Equal(t, 2, l.Len())
|
||||
if e, a := 2, l.Len(); e != a {
|
||||
t.Errorf("expect %d list length, got %d", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwapHandlers(t *testing.T) {
|
||||
firstHandlerCalled := 0
|
||||
swappedOutHandlerCalled := 0
|
||||
swappedInHandlerCalled := 0
|
||||
|
||||
l := request.HandlerList{}
|
||||
named := request.NamedHandler{Name: "Name", Fn: func(r *request.Request) {
|
||||
firstHandlerCalled++
|
||||
}}
|
||||
named2 := request.NamedHandler{Name: "SwapOutName", Fn: func(r *request.Request) {
|
||||
swappedOutHandlerCalled++
|
||||
}}
|
||||
l.PushBackNamed(named)
|
||||
l.PushBackNamed(named2)
|
||||
l.PushBackNamed(named)
|
||||
|
||||
l.SwapNamed(request.NamedHandler{Name: "SwapOutName", Fn: func(r *request.Request) {
|
||||
swappedInHandlerCalled++
|
||||
}})
|
||||
|
||||
l.Run(&request.Request{})
|
||||
|
||||
if e, a := 2, firstHandlerCalled; e != a {
|
||||
t.Errorf("expect first handler to be called %d, was called %d times", e, a)
|
||||
}
|
||||
if n := swappedOutHandlerCalled; n != 0 {
|
||||
t.Errorf("expect swapped out handler to not be called, was called %d times", n)
|
||||
}
|
||||
if e, a := 1, swappedInHandlerCalled; e != a {
|
||||
t.Errorf("expect swapped in handler to be called %d, was called %d times", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetBackNamed_Exists(t *testing.T) {
|
||||
firstHandlerCalled := 0
|
||||
swappedOutHandlerCalled := 0
|
||||
swappedInHandlerCalled := 0
|
||||
|
||||
l := request.HandlerList{}
|
||||
named := request.NamedHandler{Name: "Name", Fn: func(r *request.Request) {
|
||||
firstHandlerCalled++
|
||||
}}
|
||||
named2 := request.NamedHandler{Name: "SwapOutName", Fn: func(r *request.Request) {
|
||||
swappedOutHandlerCalled++
|
||||
}}
|
||||
l.PushBackNamed(named)
|
||||
l.PushBackNamed(named2)
|
||||
|
||||
l.SetBackNamed(request.NamedHandler{Name: "SwapOutName", Fn: func(r *request.Request) {
|
||||
swappedInHandlerCalled++
|
||||
}})
|
||||
|
||||
l.Run(&request.Request{})
|
||||
|
||||
if e, a := 1, firstHandlerCalled; e != a {
|
||||
t.Errorf("expect first handler to be called %d, was called %d times", e, a)
|
||||
}
|
||||
if n := swappedOutHandlerCalled; n != 0 {
|
||||
t.Errorf("expect swapped out handler to not be called, was called %d times", n)
|
||||
}
|
||||
if e, a := 1, swappedInHandlerCalled; e != a {
|
||||
t.Errorf("expect swapped in handler to be called %d, was called %d times", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetBackNamed_NotExists(t *testing.T) {
|
||||
firstHandlerCalled := 0
|
||||
secondHandlerCalled := 0
|
||||
swappedInHandlerCalled := 0
|
||||
|
||||
l := request.HandlerList{}
|
||||
named := request.NamedHandler{Name: "Name", Fn: func(r *request.Request) {
|
||||
firstHandlerCalled++
|
||||
}}
|
||||
named2 := request.NamedHandler{Name: "OtherName", Fn: func(r *request.Request) {
|
||||
secondHandlerCalled++
|
||||
}}
|
||||
l.PushBackNamed(named)
|
||||
l.PushBackNamed(named2)
|
||||
|
||||
l.SetBackNamed(request.NamedHandler{Name: "SwapOutName", Fn: func(r *request.Request) {
|
||||
swappedInHandlerCalled++
|
||||
}})
|
||||
|
||||
l.Run(&request.Request{})
|
||||
|
||||
if e, a := 1, firstHandlerCalled; e != a {
|
||||
t.Errorf("expect first handler to be called %d, was called %d times", e, a)
|
||||
}
|
||||
if e, a := 1, secondHandlerCalled; e != a {
|
||||
t.Errorf("expect second handler to be called %d, was called %d times", e, a)
|
||||
}
|
||||
if e, a := 1, swappedInHandlerCalled; e != a {
|
||||
t.Errorf("expect swapped in handler to be called %d, was called %d times", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoggedHandlers(t *testing.T) {
|
||||
@@ -61,7 +167,10 @@ func TestLoggedHandlers(t *testing.T) {
|
||||
l.PushBackNamed(named2)
|
||||
l.Run(&request.Request{Config: cfg})
|
||||
|
||||
assert.Equal(t, expectedHandlers, loggedHandlers)
|
||||
if !reflect.DeepEqual(expectedHandlers, loggedHandlers) {
|
||||
t.Errorf("expect handlers executed %v to match logged handlers, %v",
|
||||
expectedHandlers, loggedHandlers)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStopHandlers(t *testing.T) {
|
||||
@@ -79,9 +188,79 @@ func TestStopHandlers(t *testing.T) {
|
||||
called++
|
||||
}})
|
||||
l.PushBackNamed(request.NamedHandler{Name: "name3", Fn: func(r *request.Request) {
|
||||
assert.Fail(t, "third handler should not be called")
|
||||
t.Fatalf("third handler should not be called")
|
||||
}})
|
||||
l.Run(&request.Request{})
|
||||
|
||||
assert.Equal(t, 2, called, "Expect only two handlers to be called")
|
||||
if e, a := 2, called; e != a {
|
||||
t.Errorf("expect %d handlers called, got %d", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNewRequest(b *testing.B) {
|
||||
svc := s3.New(unit.Session)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
r, _ := svc.GetObjectRequest(nil)
|
||||
if r == nil {
|
||||
b.Fatal("r should not be nil")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkHandlersCopy(b *testing.B) {
|
||||
handlers := request.Handlers{}
|
||||
|
||||
handlers.Validate.PushBack(func(r *request.Request) {})
|
||||
handlers.Validate.PushBack(func(r *request.Request) {})
|
||||
handlers.Build.PushBack(func(r *request.Request) {})
|
||||
handlers.Build.PushBack(func(r *request.Request) {})
|
||||
handlers.Send.PushBack(func(r *request.Request) {})
|
||||
handlers.Send.PushBack(func(r *request.Request) {})
|
||||
handlers.Unmarshal.PushBack(func(r *request.Request) {})
|
||||
handlers.Unmarshal.PushBack(func(r *request.Request) {})
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
h := handlers.Copy()
|
||||
if e, a := handlers.Validate.Len(), h.Validate.Len(); e != a {
|
||||
b.Fatalf("expected %d handlers got %d", e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkHandlersPushBack(b *testing.B) {
|
||||
handlers := request.Handlers{}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
h := handlers.Copy()
|
||||
h.Validate.PushBack(func(r *request.Request) {})
|
||||
h.Validate.PushBack(func(r *request.Request) {})
|
||||
h.Validate.PushBack(func(r *request.Request) {})
|
||||
h.Validate.PushBack(func(r *request.Request) {})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkHandlersPushFront(b *testing.B) {
|
||||
handlers := request.Handlers{}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
h := handlers.Copy()
|
||||
h.Validate.PushFront(func(r *request.Request) {})
|
||||
h.Validate.PushFront(func(r *request.Request) {})
|
||||
h.Validate.PushFront(func(r *request.Request) {})
|
||||
h.Validate.PushFront(func(r *request.Request) {})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkHandlersClear(b *testing.B) {
|
||||
handlers := request.Handlers{}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
h := handlers.Copy()
|
||||
h.Validate.PushFront(func(r *request.Request) {})
|
||||
h.Validate.PushFront(func(r *request.Request) {})
|
||||
h.Validate.PushFront(func(r *request.Request) {})
|
||||
h.Validate.PushFront(func(r *request.Request) {})
|
||||
h.Clear()
|
||||
}
|
||||
}
|
||||
|
||||
+92
-38
@@ -16,10 +16,23 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws/client/metadata"
|
||||
)
|
||||
|
||||
// CanceledErrorCode is the error code that will be returned by an
|
||||
// API request that was canceled. Requests given a aws.Context may
|
||||
// return this error when canceled.
|
||||
const CanceledErrorCode = "RequestCanceled"
|
||||
const (
|
||||
// ErrCodeSerialization is the serialization error code that is received
|
||||
// during protocol unmarshaling.
|
||||
ErrCodeSerialization = "SerializationError"
|
||||
|
||||
// ErrCodeRead is an error that is returned during HTTP reads.
|
||||
ErrCodeRead = "ReadError"
|
||||
|
||||
// ErrCodeResponseTimeout is the connection timeout error that is received
|
||||
// during body reads.
|
||||
ErrCodeResponseTimeout = "ResponseTimeout"
|
||||
|
||||
// CanceledErrorCode is the error code that will be returned by an
|
||||
// API request that was canceled. Requests given a aws.Context may
|
||||
// return this error when canceled.
|
||||
CanceledErrorCode = "RequestCanceled"
|
||||
)
|
||||
|
||||
// A Request is the service request to be made.
|
||||
type Request struct {
|
||||
@@ -28,23 +41,24 @@ type Request struct {
|
||||
Handlers Handlers
|
||||
|
||||
Retryer
|
||||
Time time.Time
|
||||
ExpireTime time.Duration
|
||||
Operation *Operation
|
||||
HTTPRequest *http.Request
|
||||
HTTPResponse *http.Response
|
||||
Body io.ReadSeeker
|
||||
BodyStart int64 // offset from beginning of Body that the request body starts
|
||||
Params interface{}
|
||||
Error error
|
||||
Data interface{}
|
||||
RequestID string
|
||||
RetryCount int
|
||||
Retryable *bool
|
||||
RetryDelay time.Duration
|
||||
NotHoist bool
|
||||
SignedHeaderVals http.Header
|
||||
LastSignedAt time.Time
|
||||
Time time.Time
|
||||
ExpireTime time.Duration
|
||||
Operation *Operation
|
||||
HTTPRequest *http.Request
|
||||
HTTPResponse *http.Response
|
||||
Body io.ReadSeeker
|
||||
BodyStart int64 // offset from beginning of Body that the request body starts
|
||||
Params interface{}
|
||||
Error error
|
||||
Data interface{}
|
||||
RequestID string
|
||||
RetryCount int
|
||||
Retryable *bool
|
||||
RetryDelay time.Duration
|
||||
NotHoist bool
|
||||
SignedHeaderVals http.Header
|
||||
LastSignedAt time.Time
|
||||
DisableFollowRedirects bool
|
||||
|
||||
context aws.Context
|
||||
|
||||
@@ -114,6 +128,40 @@ func New(cfg aws.Config, clientInfo metadata.ClientInfo, handlers Handlers,
|
||||
// using a WithContext API operation method.
|
||||
type Option func(*Request)
|
||||
|
||||
// WithGetResponseHeader builds a request Option which will retrieve a single
|
||||
// header value from the HTTP Response. If there are multiple values for the
|
||||
// header key use WithGetResponseHeaders instead to access the http.Header
|
||||
// map directly. The passed in val pointer must be non-nil.
|
||||
//
|
||||
// This Option can be used multiple times with a single API operation.
|
||||
//
|
||||
// var id2, versionID string
|
||||
// svc.PutObjectWithContext(ctx, params,
|
||||
// request.WithGetResponseHeader("x-amz-id-2", &id2),
|
||||
// request.WithGetResponseHeader("x-amz-version-id", &versionID),
|
||||
// )
|
||||
func WithGetResponseHeader(key string, val *string) Option {
|
||||
return func(r *Request) {
|
||||
r.Handlers.Complete.PushBack(func(req *Request) {
|
||||
*val = req.HTTPResponse.Header.Get(key)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithGetResponseHeaders builds a request Option which will retrieve the
|
||||
// headers from the HTTP response and assign them to the passed in headers
|
||||
// variable. The passed in headers pointer must be non-nil.
|
||||
//
|
||||
// var headers http.Header
|
||||
// svc.PutObjectWithContext(ctx, params, request.WithGetResponseHeaders(&headers))
|
||||
func WithGetResponseHeaders(headers *http.Header) Option {
|
||||
return func(r *Request) {
|
||||
r.Handlers.Complete.PushBack(func(req *Request) {
|
||||
*headers = req.HTTPResponse.Header
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithLogLevel is a request option that will set the request to use a specific
|
||||
// log level when the request is made.
|
||||
//
|
||||
@@ -221,11 +269,17 @@ func (r *Request) Presign(expireTime time.Duration) (string, error) {
|
||||
return r.HTTPRequest.URL.String(), nil
|
||||
}
|
||||
|
||||
// PresignRequest behaves just like presign, but hoists all headers and signs them.
|
||||
// Also returns the signed hash back to the user
|
||||
// PresignRequest behaves just like presign, with the addition of returning a
|
||||
// set of headers that were signed.
|
||||
//
|
||||
// Returns the URL string for the API operation with signature in the query string,
|
||||
// and the HTTP headers that were included in the signature. These headers must
|
||||
// be included in any HTTP request made with the presigned URL.
|
||||
//
|
||||
// To prevent hoisting any headers to the query string set NotHoist to true on
|
||||
// this Request value prior to calling PresignRequest.
|
||||
func (r *Request) PresignRequest(expireTime time.Duration) (string, http.Header, error) {
|
||||
r.ExpireTime = expireTime
|
||||
r.NotHoist = true
|
||||
r.Sign()
|
||||
if r.Error != nil {
|
||||
return "", nil, r.Error
|
||||
@@ -290,10 +344,7 @@ func (r *Request) Sign() error {
|
||||
return r.Error
|
||||
}
|
||||
|
||||
// ResetBody rewinds the request body backto its starting position, and
|
||||
// set's the HTTP Request body reference. When the body is read prior
|
||||
// to being sent in the HTTP request it will need to be rewound.
|
||||
func (r *Request) ResetBody() {
|
||||
func (r *Request) getNextRequestBody() (io.ReadCloser, error) {
|
||||
if r.safeBody != nil {
|
||||
r.safeBody.Close()
|
||||
}
|
||||
@@ -315,14 +366,14 @@ func (r *Request) ResetBody() {
|
||||
// Related golang/go#18257
|
||||
l, err := computeBodyLength(r.Body)
|
||||
if err != nil {
|
||||
r.Error = awserr.New("SerializationError", "failed to compute request body size", err)
|
||||
return
|
||||
return nil, awserr.New(ErrCodeSerialization, "failed to compute request body size", err)
|
||||
}
|
||||
|
||||
var body io.ReadCloser
|
||||
if l == 0 {
|
||||
r.HTTPRequest.Body = noBodyReader
|
||||
body = NoBody
|
||||
} else if l > 0 {
|
||||
r.HTTPRequest.Body = r.safeBody
|
||||
body = r.safeBody
|
||||
} else {
|
||||
// Hack to prevent sending bodies for methods where the body
|
||||
// should be ignored by the server. Sending bodies on these
|
||||
@@ -334,11 +385,13 @@ func (r *Request) ResetBody() {
|
||||
// a io.Reader that was not also an io.Seeker.
|
||||
switch r.Operation.HTTPMethod {
|
||||
case "GET", "HEAD", "DELETE":
|
||||
r.HTTPRequest.Body = noBodyReader
|
||||
body = NoBody
|
||||
default:
|
||||
r.HTTPRequest.Body = r.safeBody
|
||||
body = r.safeBody
|
||||
}
|
||||
}
|
||||
|
||||
return body, nil
|
||||
}
|
||||
|
||||
// Attempts to compute the length of the body of the reader using the
|
||||
@@ -440,7 +493,7 @@ func (r *Request) Send() error {
|
||||
r.Handlers.Retry.Run(r)
|
||||
r.Handlers.AfterRetry.Run(r)
|
||||
if r.Error != nil {
|
||||
debugLogReqError(r, "Send Request", false, r.Error)
|
||||
debugLogReqError(r, "Send Request", false, err)
|
||||
return r.Error
|
||||
}
|
||||
debugLogReqError(r, "Send Request", true, err)
|
||||
@@ -449,12 +502,13 @@ func (r *Request) Send() error {
|
||||
r.Handlers.UnmarshalMeta.Run(r)
|
||||
r.Handlers.ValidateResponse.Run(r)
|
||||
if r.Error != nil {
|
||||
err := r.Error
|
||||
r.Handlers.UnmarshalError.Run(r)
|
||||
err := r.Error
|
||||
|
||||
r.Handlers.Retry.Run(r)
|
||||
r.Handlers.AfterRetry.Run(r)
|
||||
if r.Error != nil {
|
||||
debugLogReqError(r, "Validate Response", false, r.Error)
|
||||
debugLogReqError(r, "Validate Response", false, err)
|
||||
return r.Error
|
||||
}
|
||||
debugLogReqError(r, "Validate Response", true, err)
|
||||
@@ -467,7 +521,7 @@ func (r *Request) Send() error {
|
||||
r.Handlers.Retry.Run(r)
|
||||
r.Handlers.AfterRetry.Run(r)
|
||||
if r.Error != nil {
|
||||
debugLogReqError(r, "Unmarshal Response", false, r.Error)
|
||||
debugLogReqError(r, "Unmarshal Response", false, err)
|
||||
return r.Error
|
||||
}
|
||||
debugLogReqError(r, "Unmarshal Response", true, err)
|
||||
|
||||
+20
-2
@@ -16,6 +16,24 @@ func (noBody) Read([]byte) (int, error) { return 0, io.EOF }
|
||||
func (noBody) Close() error { return nil }
|
||||
func (noBody) WriteTo(io.Writer) (int64, error) { return 0, nil }
|
||||
|
||||
// Is an empty reader that will trigger the Go HTTP client to not include
|
||||
// NoBody is an empty reader that will trigger the Go HTTP client to not include
|
||||
// and body in the HTTP request.
|
||||
var noBodyReader = noBody{}
|
||||
var NoBody = noBody{}
|
||||
|
||||
// ResetBody rewinds the request body back to its starting position, and
|
||||
// set's the HTTP Request body reference. When the body is read prior
|
||||
// to being sent in the HTTP request it will need to be rewound.
|
||||
//
|
||||
// ResetBody will automatically be called by the SDK's build handler, but if
|
||||
// the request is being used directly ResetBody must be called before the request
|
||||
// is Sent. SetStringBody, SetBufferBody, and SetReaderBody will automatically
|
||||
// call ResetBody.
|
||||
func (r *Request) ResetBody() {
|
||||
body, err := r.getNextRequestBody()
|
||||
if err != nil {
|
||||
r.Error = err
|
||||
return
|
||||
}
|
||||
|
||||
r.HTTPRequest.Body = body
|
||||
}
|
||||
|
||||
+27
-3
@@ -2,8 +2,32 @@
|
||||
|
||||
package request
|
||||
|
||||
import "net/http"
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Is a http.NoBody reader instructing Go HTTP client to not include
|
||||
// NoBody is a http.NoBody reader instructing Go HTTP client to not include
|
||||
// and body in the HTTP request.
|
||||
var noBodyReader = http.NoBody
|
||||
var NoBody = http.NoBody
|
||||
|
||||
// ResetBody rewinds the request body back to its starting position, and
|
||||
// set's the HTTP Request body reference. When the body is read prior
|
||||
// to being sent in the HTTP request it will need to be rewound.
|
||||
//
|
||||
// ResetBody will automatically be called by the SDK's build handler, but if
|
||||
// the request is being used directly ResetBody must be called before the request
|
||||
// is Sent. SetStringBody, SetBufferBody, and SetReaderBody will automatically
|
||||
// call ResetBody.
|
||||
//
|
||||
// Will also set the Go 1.8's http.Request.GetBody member to allow retrying
|
||||
// PUT/POST redirects.
|
||||
func (r *Request) ResetBody() {
|
||||
body, err := r.getNextRequestBody()
|
||||
if err != nil {
|
||||
r.Error = err
|
||||
return
|
||||
}
|
||||
|
||||
r.HTTPRequest.Body = body
|
||||
r.HTTPRequest.GetBody = r.getNextRequestBody
|
||||
}
|
||||
|
||||
+62
-2
@@ -1,15 +1,23 @@
|
||||
// +build go1.8
|
||||
|
||||
package request
|
||||
package request_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/awstesting"
|
||||
"github.com/aws/aws-sdk-go/awstesting/unit"
|
||||
)
|
||||
|
||||
func TestResetBody_WithEmptyBody(t *testing.T) {
|
||||
r := Request{
|
||||
r := request.Request{
|
||||
HTTPRequest: &http.Request{},
|
||||
}
|
||||
|
||||
@@ -23,3 +31,55 @@ func TestResetBody_WithEmptyBody(t *testing.T) {
|
||||
r.HTTPRequest.Body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequest_FollowPUTRedirects(t *testing.T) {
|
||||
const bodySize = 1024
|
||||
|
||||
redirectHit := 0
|
||||
endpointHit := 0
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/redirect-me":
|
||||
u := *r.URL
|
||||
u.Path = "/endpoint"
|
||||
w.Header().Set("Location", u.String())
|
||||
w.WriteHeader(307)
|
||||
redirectHit++
|
||||
case "/endpoint":
|
||||
b := bytes.Buffer{}
|
||||
io.Copy(&b, r.Body)
|
||||
r.Body.Close()
|
||||
if e, a := bodySize, b.Len(); e != a {
|
||||
t.Fatalf("expect %d body size, got %d", e, a)
|
||||
}
|
||||
endpointHit++
|
||||
default:
|
||||
t.Fatalf("unexpected endpoint used, %q", r.URL.String())
|
||||
}
|
||||
}))
|
||||
|
||||
svc := awstesting.NewClient(&aws.Config{
|
||||
Region: unit.Session.Config.Region,
|
||||
DisableSSL: aws.Bool(true),
|
||||
Endpoint: aws.String(server.URL),
|
||||
})
|
||||
|
||||
req := svc.NewRequest(&request.Operation{
|
||||
Name: "Operation",
|
||||
HTTPMethod: "PUT",
|
||||
HTTPPath: "/redirect-me",
|
||||
}, &struct{}{}, &struct{}{})
|
||||
req.SetReaderBody(bytes.NewReader(make([]byte, bodySize)))
|
||||
|
||||
err := req.Send()
|
||||
if err != nil {
|
||||
t.Errorf("expect no error, got %v", err)
|
||||
}
|
||||
if e, a := 1, redirectHit; e != a {
|
||||
t.Errorf("expect %d redirect hits, got %d", e, a)
|
||||
}
|
||||
if e, a := 1, endpointHit; e != a {
|
||||
t.Errorf("expect %d endpoint hits, got %d", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
+11
-4
@@ -2,8 +2,6 @@ package request
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCopy(t *testing.T) {
|
||||
@@ -15,6 +13,15 @@ func TestCopy(t *testing.T) {
|
||||
req.Handlers = handlers
|
||||
|
||||
r := req.copy()
|
||||
assert.NotEqual(t, req, r)
|
||||
assert.Equal(t, req.Operation.HTTPMethod, r.Operation.HTTPMethod)
|
||||
|
||||
if r == req {
|
||||
t.Fatal("expect request pointer copy to be different")
|
||||
}
|
||||
if r.Operation == req.Operation {
|
||||
t.Errorf("expect request operation pointer to be different")
|
||||
}
|
||||
|
||||
if e, a := req.Operation.HTTPMethod, r.Operation.HTTPMethod; e != a {
|
||||
t.Errorf("expect %q http method, got %q", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
+73
@@ -381,6 +381,79 @@ func TestPaginationNilToken(t *testing.T) {
|
||||
assert.Equal(t, []string{"first.example.com.", "second.example.com.", "third.example.com."}, results)
|
||||
}
|
||||
|
||||
func TestPaginationNilInput(t *testing.T) {
|
||||
// Code generation doesn't have a great way to verify the code is correct
|
||||
// other than being run via unit tests in the SDK. This should be fixed
|
||||
// So code generation can be validated independently.
|
||||
|
||||
client := s3.New(unit.Session)
|
||||
client.Handlers.Validate.Clear()
|
||||
client.Handlers.Send.Clear() // mock sending
|
||||
client.Handlers.Unmarshal.Clear()
|
||||
client.Handlers.UnmarshalMeta.Clear()
|
||||
client.Handlers.ValidateResponse.Clear()
|
||||
client.Handlers.Unmarshal.PushBack(func(r *request.Request) {
|
||||
r.Data = &s3.ListObjectsOutput{}
|
||||
})
|
||||
|
||||
gotToEnd := false
|
||||
numPages := 0
|
||||
err := client.ListObjectsPages(nil, func(p *s3.ListObjectsOutput, last bool) bool {
|
||||
numPages++
|
||||
if last {
|
||||
gotToEnd = true
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, but got %v", err)
|
||||
}
|
||||
if e, a := 1, numPages; e != a {
|
||||
t.Errorf("expect %d number pages but got %d", e, a)
|
||||
}
|
||||
if !gotToEnd {
|
||||
t.Errorf("expect to of gotten to end, did not")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPaginationWithContextNilInput(t *testing.T) {
|
||||
// Code generation doesn't have a great way to verify the code is correct
|
||||
// other than being run via unit tests in the SDK. This should be fixed
|
||||
// So code generation can be validated independently.
|
||||
|
||||
client := s3.New(unit.Session)
|
||||
client.Handlers.Validate.Clear()
|
||||
client.Handlers.Send.Clear() // mock sending
|
||||
client.Handlers.Unmarshal.Clear()
|
||||
client.Handlers.UnmarshalMeta.Clear()
|
||||
client.Handlers.ValidateResponse.Clear()
|
||||
client.Handlers.Unmarshal.PushBack(func(r *request.Request) {
|
||||
r.Data = &s3.ListObjectsOutput{}
|
||||
})
|
||||
|
||||
gotToEnd := false
|
||||
numPages := 0
|
||||
ctx := &awstesting.FakeContext{DoneCh: make(chan struct{})}
|
||||
err := client.ListObjectsPagesWithContext(ctx, nil, func(p *s3.ListObjectsOutput, last bool) bool {
|
||||
numPages++
|
||||
if last {
|
||||
gotToEnd = true
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, but got %v", err)
|
||||
}
|
||||
if e, a := 1, numPages; e != a {
|
||||
t.Errorf("expect %d number pages but got %d", e, a)
|
||||
}
|
||||
if !gotToEnd {
|
||||
t.Errorf("expect to of gotten to end, did not")
|
||||
}
|
||||
}
|
||||
|
||||
type testPageInput struct {
|
||||
NextToken string
|
||||
}
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ func TestResetBody_ExcludeUnseekableBodyByMethod(t *testing.T) {
|
||||
|
||||
r.SetReaderBody(reader)
|
||||
|
||||
if a, e := r.HTTPRequest.Body == noBodyReader, c.IsNoBody; a != e {
|
||||
if a, e := r.HTTPRequest.Body == NoBody, c.IsNoBody; a != e {
|
||||
t.Errorf("%d, expect body to be set to noBody(%t), but was %t", i, e, a)
|
||||
}
|
||||
}
|
||||
|
||||
+456
-52
@@ -8,18 +8,23 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/aws/client"
|
||||
"github.com/aws/aws-sdk-go/aws/client/metadata"
|
||||
"github.com/aws/aws-sdk-go/aws/corehandlers"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/aws/signer/v4"
|
||||
"github.com/aws/aws-sdk-go/awstesting"
|
||||
"github.com/aws/aws-sdk-go/awstesting/unit"
|
||||
"github.com/aws/aws-sdk-go/private/protocol/jsonrpc"
|
||||
"github.com/aws/aws-sdk-go/private/protocol/rest"
|
||||
)
|
||||
|
||||
@@ -91,9 +96,15 @@ func TestRequestRecoverRetry5xx(t *testing.T) {
|
||||
out := &testData{}
|
||||
r := s.NewRequest(&request.Operation{Name: "Operation"}, nil, out)
|
||||
err := r.Send()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, int(r.RetryCount))
|
||||
assert.Equal(t, "valid", out.Data)
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, but got %v", err)
|
||||
}
|
||||
if e, a := 2, int(r.RetryCount); e != a {
|
||||
t.Errorf("expect %d retry count, got %d", e, a)
|
||||
}
|
||||
if e, a := "valid", out.Data; e != a {
|
||||
t.Errorf("expect %q output got %q", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
// test that retries occur for 4xx status codes with a response type that can be retried - see `shouldRetry`
|
||||
@@ -117,9 +128,15 @@ func TestRequestRecoverRetry4xxRetryable(t *testing.T) {
|
||||
out := &testData{}
|
||||
r := s.NewRequest(&request.Operation{Name: "Operation"}, nil, out)
|
||||
err := r.Send()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, int(r.RetryCount))
|
||||
assert.Equal(t, "valid", out.Data)
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, but got %v", err)
|
||||
}
|
||||
if e, a := 2, int(r.RetryCount); e != a {
|
||||
t.Errorf("expect %d retry count, got %d", e, a)
|
||||
}
|
||||
if e, a := "valid", out.Data; e != a {
|
||||
t.Errorf("expect %q output got %q", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
// test that retries don't occur for 4xx status codes with a response type that can't be retried
|
||||
@@ -135,15 +152,22 @@ func TestRequest4xxUnretryable(t *testing.T) {
|
||||
out := &testData{}
|
||||
r := s.NewRequest(&request.Operation{Name: "Operation"}, nil, out)
|
||||
err := r.Send()
|
||||
assert.NotNil(t, err)
|
||||
if e, ok := err.(awserr.RequestFailure); ok {
|
||||
assert.Equal(t, 401, e.StatusCode())
|
||||
} else {
|
||||
assert.Fail(t, "Expected error to be a service failure")
|
||||
if err == nil {
|
||||
t.Fatalf("expect error, but did not get one")
|
||||
}
|
||||
aerr := err.(awserr.RequestFailure)
|
||||
if e, a := 401, aerr.StatusCode(); e != a {
|
||||
t.Errorf("expect %d status code, got %d", e, a)
|
||||
}
|
||||
if e, a := "SignatureDoesNotMatch", aerr.Code(); e != a {
|
||||
t.Errorf("expect %q error code, got %q", e, a)
|
||||
}
|
||||
if e, a := "Signature does not match.", aerr.Message(); e != a {
|
||||
t.Errorf("expect %q error message, got %q", e, a)
|
||||
}
|
||||
if e, a := 0, int(r.RetryCount); e != a {
|
||||
t.Errorf("expect %d retry count, got %d", e, a)
|
||||
}
|
||||
assert.Equal(t, "SignatureDoesNotMatch", err.(awserr.Error).Code())
|
||||
assert.Equal(t, "Signature does not match.", err.(awserr.Error).Message())
|
||||
assert.Equal(t, 0, int(r.RetryCount))
|
||||
}
|
||||
|
||||
func TestRequestExhaustRetries(t *testing.T) {
|
||||
@@ -171,22 +195,31 @@ func TestRequestExhaustRetries(t *testing.T) {
|
||||
})
|
||||
r := s.NewRequest(&request.Operation{Name: "Operation"}, nil, nil)
|
||||
err := r.Send()
|
||||
assert.NotNil(t, err)
|
||||
if e, ok := err.(awserr.RequestFailure); ok {
|
||||
assert.Equal(t, 500, e.StatusCode())
|
||||
} else {
|
||||
assert.Fail(t, "Expected error to be a service failure")
|
||||
if err == nil {
|
||||
t.Fatalf("expect error, but did not get one")
|
||||
}
|
||||
aerr := err.(awserr.RequestFailure)
|
||||
if e, a := 500, aerr.StatusCode(); e != a {
|
||||
t.Errorf("expect %d status code, got %d", e, a)
|
||||
}
|
||||
if e, a := "UnknownError", aerr.Code(); e != a {
|
||||
t.Errorf("expect %q error code, got %q", e, a)
|
||||
}
|
||||
if e, a := "An error occurred.", aerr.Message(); e != a {
|
||||
t.Errorf("expect %q error message, got %q", e, a)
|
||||
}
|
||||
if e, a := 3, int(r.RetryCount); e != a {
|
||||
t.Errorf("expect %d retry count, got %d", e, a)
|
||||
}
|
||||
assert.Equal(t, "UnknownError", err.(awserr.Error).Code())
|
||||
assert.Equal(t, "An error occurred.", err.(awserr.Error).Message())
|
||||
assert.Equal(t, 3, int(r.RetryCount))
|
||||
|
||||
expectDelays := []struct{ min, max time.Duration }{{30, 59}, {60, 118}, {120, 236}}
|
||||
for i, v := range delays {
|
||||
min := expectDelays[i].min * time.Millisecond
|
||||
max := expectDelays[i].max * time.Millisecond
|
||||
assert.True(t, min <= v && v <= max,
|
||||
"Expect delay to be within range, i:%d, v:%s, min:%s, max:%s", i, v, min, max)
|
||||
if !(min <= v && v <= max) {
|
||||
t.Errorf("Expect delay to be within range, i:%d, v:%s, min:%s, max:%s",
|
||||
i, v, min, max)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,14 +255,26 @@ func TestRequestRecoverExpiredCreds(t *testing.T) {
|
||||
out := &testData{}
|
||||
r := s.NewRequest(&request.Operation{Name: "Operation"}, nil, out)
|
||||
err := r.Send()
|
||||
assert.Nil(t, err)
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, got %v", err)
|
||||
}
|
||||
|
||||
assert.False(t, credExpiredBeforeRetry, "Expect valid creds before retry check")
|
||||
assert.True(t, credExpiredAfterRetry, "Expect expired creds after retry check")
|
||||
assert.False(t, s.Config.Credentials.IsExpired(), "Expect valid creds after cred expired recovery")
|
||||
if credExpiredBeforeRetry {
|
||||
t.Errorf("Expect valid creds before retry check")
|
||||
}
|
||||
if !credExpiredAfterRetry {
|
||||
t.Errorf("Expect expired creds after retry check")
|
||||
}
|
||||
if s.Config.Credentials.IsExpired() {
|
||||
t.Errorf("Expect valid creds after cred expired recovery")
|
||||
}
|
||||
|
||||
assert.Equal(t, 1, int(r.RetryCount))
|
||||
assert.Equal(t, "valid", out.Data)
|
||||
if e, a := 1, int(r.RetryCount); e != a {
|
||||
t.Errorf("expect %d retry count, got %d", e, a)
|
||||
}
|
||||
if e, a := "valid", out.Data; e != a {
|
||||
t.Errorf("expect %q output got %q", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeAddtoUserAgentHandler(t *testing.T) {
|
||||
@@ -238,7 +283,9 @@ func TestMakeAddtoUserAgentHandler(t *testing.T) {
|
||||
r.HTTPRequest.Header.Set("User-Agent", "foo/bar")
|
||||
fn(r)
|
||||
|
||||
assert.Equal(t, "foo/bar name/version (extra1; extra2)", r.HTTPRequest.Header.Get("User-Agent"))
|
||||
if e, a := "foo/bar name/version (extra1; extra2)", r.HTTPRequest.Header.Get("User-Agent"); e != a {
|
||||
t.Errorf("expect %q user agent, got %q", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeAddtoUserAgentFreeFormHandler(t *testing.T) {
|
||||
@@ -247,7 +294,9 @@ func TestMakeAddtoUserAgentFreeFormHandler(t *testing.T) {
|
||||
r.HTTPRequest.Header.Set("User-Agent", "foo/bar")
|
||||
fn(r)
|
||||
|
||||
assert.Equal(t, "foo/bar name/version (extra1; extra2)", r.HTTPRequest.Header.Get("User-Agent"))
|
||||
if e, a := "foo/bar name/version (extra1; extra2)", r.HTTPRequest.Header.Get("User-Agent"); e != a {
|
||||
t.Errorf("expect %q user agent, got %q", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestUserAgent(t *testing.T) {
|
||||
@@ -256,11 +305,15 @@ func TestRequestUserAgent(t *testing.T) {
|
||||
|
||||
req := s.NewRequest(&request.Operation{Name: "Operation"}, nil, &testData{})
|
||||
req.HTTPRequest.Header.Set("User-Agent", "foo/bar")
|
||||
assert.NoError(t, req.Build())
|
||||
if err := req.Build(); err != nil {
|
||||
t.Fatalf("expect no error, got %v", err)
|
||||
}
|
||||
|
||||
expectUA := fmt.Sprintf("foo/bar %s/%s (%s; %s; %s)",
|
||||
aws.SDKName, aws.SDKVersion, runtime.Version(), runtime.GOOS, runtime.GOARCH)
|
||||
assert.Equal(t, expectUA, req.HTTPRequest.Header.Get("User-Agent"))
|
||||
if e, a := expectUA, req.HTTPRequest.Header.Get("User-Agent"); e != a {
|
||||
t.Errorf("expect %q user agent, got %q", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestThrottleRetries(t *testing.T) {
|
||||
@@ -288,22 +341,31 @@ func TestRequestThrottleRetries(t *testing.T) {
|
||||
})
|
||||
r := s.NewRequest(&request.Operation{Name: "Operation"}, nil, nil)
|
||||
err := r.Send()
|
||||
assert.NotNil(t, err)
|
||||
if e, ok := err.(awserr.RequestFailure); ok {
|
||||
assert.Equal(t, 500, e.StatusCode())
|
||||
} else {
|
||||
assert.Fail(t, "Expected error to be a service failure")
|
||||
if err == nil {
|
||||
t.Fatalf("expect error, but did not get one")
|
||||
}
|
||||
aerr := err.(awserr.RequestFailure)
|
||||
if e, a := 500, aerr.StatusCode(); e != a {
|
||||
t.Errorf("expect %d status code, got %d", e, a)
|
||||
}
|
||||
if e, a := "Throttling", aerr.Code(); e != a {
|
||||
t.Errorf("expect %q error code, got %q", e, a)
|
||||
}
|
||||
if e, a := "An error occurred.", aerr.Message(); e != a {
|
||||
t.Errorf("expect %q error message, got %q", e, a)
|
||||
}
|
||||
if e, a := 3, int(r.RetryCount); e != a {
|
||||
t.Errorf("expect %d retry count, got %d", e, a)
|
||||
}
|
||||
assert.Equal(t, "Throttling", err.(awserr.Error).Code())
|
||||
assert.Equal(t, "An error occurred.", err.(awserr.Error).Message())
|
||||
assert.Equal(t, 3, int(r.RetryCount))
|
||||
|
||||
expectDelays := []struct{ min, max time.Duration }{{500, 999}, {1000, 1998}, {2000, 3996}}
|
||||
for i, v := range delays {
|
||||
min := expectDelays[i].min * time.Millisecond
|
||||
max := expectDelays[i].max * time.Millisecond
|
||||
assert.True(t, min <= v && v <= max,
|
||||
"Expect delay to be within range, i:%d, v:%s, min:%s, max:%s", i, v, min, max)
|
||||
if !(min <= v && v <= max) {
|
||||
t.Errorf("Expect delay to be within range, i:%d, v:%s, min:%s, max:%s",
|
||||
i, v, min, max)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,9 +401,15 @@ func TestRequestRecoverTimeoutWithNilBody(t *testing.T) {
|
||||
out := &testData{}
|
||||
r := s.NewRequest(&request.Operation{Name: "Operation"}, nil, out)
|
||||
err := r.Send()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, int(r.RetryCount))
|
||||
assert.Equal(t, "valid", out.Data)
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, but got %v", err)
|
||||
}
|
||||
if e, a := 1, int(r.RetryCount); e != a {
|
||||
t.Errorf("expect %d retry count, got %d", e, a)
|
||||
}
|
||||
if e, a := "valid", out.Data; e != a {
|
||||
t.Errorf("expect %q output got %q", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestRecoverTimeoutWithNilResponse(t *testing.T) {
|
||||
@@ -376,9 +444,15 @@ func TestRequestRecoverTimeoutWithNilResponse(t *testing.T) {
|
||||
out := &testData{}
|
||||
r := s.NewRequest(&request.Operation{Name: "Operation"}, nil, out)
|
||||
err := r.Send()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, int(r.RetryCount))
|
||||
assert.Equal(t, "valid", out.Data)
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, but got %v", err)
|
||||
}
|
||||
if e, a := 1, int(r.RetryCount); e != a {
|
||||
t.Errorf("expect %d retry count, got %d", e, a)
|
||||
}
|
||||
if e, a := "valid", out.Data; e != a {
|
||||
t.Errorf("expect %q output got %q", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequest_NoBody(t *testing.T) {
|
||||
@@ -438,3 +512,333 @@ func TestRequest_NoBody(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsSerializationErrorRetryable(t *testing.T) {
|
||||
testCases := []struct {
|
||||
err error
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
err: awserr.New(request.ErrCodeSerialization, "foo error", nil),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
err: awserr.New("ErrFoo", "foo error", nil),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
err: nil,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
err: awserr.New(request.ErrCodeSerialization, "foo error", stubConnectionResetError),
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, c := range testCases {
|
||||
r := &request.Request{
|
||||
Error: c.err,
|
||||
}
|
||||
if r.IsErrorRetryable() != c.expected {
|
||||
t.Errorf("Case %d: Expected %v, but received %v", i+1, c.expected, !c.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithLogLevel(t *testing.T) {
|
||||
r := &request.Request{}
|
||||
|
||||
opt := request.WithLogLevel(aws.LogDebugWithHTTPBody)
|
||||
r.ApplyOptions(opt)
|
||||
|
||||
if !r.Config.LogLevel.Matches(aws.LogDebugWithHTTPBody) {
|
||||
t.Errorf("expect log level to be set, but was not, %v",
|
||||
r.Config.LogLevel.Value())
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithGetResponseHeader(t *testing.T) {
|
||||
r := &request.Request{}
|
||||
|
||||
var val, val2 string
|
||||
r.ApplyOptions(
|
||||
request.WithGetResponseHeader("x-a-header", &val),
|
||||
request.WithGetResponseHeader("x-second-header", &val2),
|
||||
)
|
||||
|
||||
r.HTTPResponse = &http.Response{
|
||||
Header: func() http.Header {
|
||||
h := http.Header{}
|
||||
h.Set("x-a-header", "first")
|
||||
h.Set("x-second-header", "second")
|
||||
return h
|
||||
}(),
|
||||
}
|
||||
r.Handlers.Complete.Run(r)
|
||||
|
||||
if e, a := "first", val; e != a {
|
||||
t.Errorf("expect %q header value got %q", e, a)
|
||||
}
|
||||
if e, a := "second", val2; e != a {
|
||||
t.Errorf("expect %q header value got %q", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithGetResponseHeaders(t *testing.T) {
|
||||
r := &request.Request{}
|
||||
|
||||
var headers http.Header
|
||||
opt := request.WithGetResponseHeaders(&headers)
|
||||
|
||||
r.ApplyOptions(opt)
|
||||
|
||||
r.HTTPResponse = &http.Response{
|
||||
Header: func() http.Header {
|
||||
h := http.Header{}
|
||||
h.Set("x-a-header", "headerValue")
|
||||
return h
|
||||
}(),
|
||||
}
|
||||
r.Handlers.Complete.Run(r)
|
||||
|
||||
if e, a := "headerValue", headers.Get("x-a-header"); e != a {
|
||||
t.Errorf("expect %q header value got %q", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
type connResetCloser struct {
|
||||
}
|
||||
|
||||
func (rc *connResetCloser) Read(b []byte) (int, error) {
|
||||
return 0, stubConnectionResetError
|
||||
}
|
||||
|
||||
func (rc *connResetCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestSerializationErrConnectionReset(t *testing.T) {
|
||||
count := 0
|
||||
handlers := request.Handlers{}
|
||||
handlers.Send.PushBack(func(r *request.Request) {
|
||||
count++
|
||||
r.HTTPResponse = &http.Response{}
|
||||
r.HTTPResponse.Body = &connResetCloser{}
|
||||
})
|
||||
|
||||
handlers.Sign.PushBackNamed(v4.SignRequestHandler)
|
||||
handlers.Build.PushBackNamed(jsonrpc.BuildHandler)
|
||||
handlers.Unmarshal.PushBackNamed(jsonrpc.UnmarshalHandler)
|
||||
handlers.UnmarshalMeta.PushBackNamed(jsonrpc.UnmarshalMetaHandler)
|
||||
handlers.UnmarshalError.PushBackNamed(jsonrpc.UnmarshalErrorHandler)
|
||||
handlers.AfterRetry.PushBackNamed(corehandlers.AfterRetryHandler)
|
||||
|
||||
op := &request.Operation{
|
||||
Name: "op",
|
||||
HTTPMethod: "POST",
|
||||
HTTPPath: "/",
|
||||
}
|
||||
|
||||
meta := metadata.ClientInfo{
|
||||
ServiceName: "fooService",
|
||||
SigningName: "foo",
|
||||
SigningRegion: "foo",
|
||||
Endpoint: "localhost",
|
||||
APIVersion: "2001-01-01",
|
||||
JSONVersion: "1.1",
|
||||
TargetPrefix: "Foo",
|
||||
}
|
||||
cfg := unit.Session.Config.Copy()
|
||||
cfg.MaxRetries = aws.Int(5)
|
||||
|
||||
req := request.New(
|
||||
*cfg,
|
||||
meta,
|
||||
handlers,
|
||||
client.DefaultRetryer{NumMaxRetries: 5},
|
||||
op,
|
||||
&struct {
|
||||
}{},
|
||||
&struct {
|
||||
}{},
|
||||
)
|
||||
|
||||
osErr := stubConnectionResetError
|
||||
req.ApplyOptions(request.WithResponseReadTimeout(time.Second))
|
||||
err := req.Send()
|
||||
if err == nil {
|
||||
t.Error("Expected rror 'SerializationError', but received nil")
|
||||
}
|
||||
if aerr, ok := err.(awserr.Error); ok && aerr.Code() != "SerializationError" {
|
||||
t.Errorf("Expected 'SerializationError', but received %q", aerr.Code())
|
||||
} else if !ok {
|
||||
t.Errorf("Expected 'awserr.Error', but received %v", reflect.TypeOf(err))
|
||||
} else if aerr.OrigErr().Error() != osErr.Error() {
|
||||
t.Errorf("Expected %q, but received %q", osErr.Error(), aerr.OrigErr().Error())
|
||||
}
|
||||
|
||||
if count != 6 {
|
||||
t.Errorf("Expected '6', but received %d", count)
|
||||
}
|
||||
}
|
||||
|
||||
type testRetryer struct {
|
||||
shouldRetry bool
|
||||
}
|
||||
|
||||
func (d *testRetryer) MaxRetries() int {
|
||||
return 3
|
||||
}
|
||||
|
||||
// RetryRules returns the delay duration before retrying this request again
|
||||
func (d *testRetryer) RetryRules(r *request.Request) time.Duration {
|
||||
return time.Duration(time.Millisecond)
|
||||
}
|
||||
|
||||
func (d *testRetryer) ShouldRetry(r *request.Request) bool {
|
||||
d.shouldRetry = true
|
||||
if r.Retryable != nil {
|
||||
return *r.Retryable
|
||||
}
|
||||
|
||||
if r.HTTPResponse.StatusCode >= 500 {
|
||||
return true
|
||||
}
|
||||
return r.IsErrorRetryable()
|
||||
}
|
||||
|
||||
func TestEnforceShouldRetryCheck(t *testing.T) {
|
||||
tp := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
ResponseHeaderTimeout: 1 * time.Millisecond,
|
||||
}
|
||||
|
||||
client := &http.Client{Transport: tp}
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// This server should wait forever. Requests will timeout and the SDK should
|
||||
// attempt to retry.
|
||||
select {}
|
||||
}))
|
||||
|
||||
retryer := &testRetryer{}
|
||||
s := awstesting.NewClient(&aws.Config{
|
||||
Region: aws.String("mock-region"),
|
||||
MaxRetries: aws.Int(0),
|
||||
Endpoint: aws.String(server.URL),
|
||||
DisableSSL: aws.Bool(true),
|
||||
Retryer: retryer,
|
||||
HTTPClient: client,
|
||||
EnforceShouldRetryCheck: aws.Bool(true),
|
||||
})
|
||||
|
||||
s.Handlers.Validate.Clear()
|
||||
s.Handlers.Unmarshal.PushBack(unmarshal)
|
||||
s.Handlers.UnmarshalError.PushBack(unmarshalError)
|
||||
|
||||
out := &testData{}
|
||||
r := s.NewRequest(&request.Operation{Name: "Operation"}, nil, out)
|
||||
err := r.Send()
|
||||
if err == nil {
|
||||
t.Fatalf("expect error, but got nil")
|
||||
}
|
||||
if e, a := 3, int(r.RetryCount); e != a {
|
||||
t.Errorf("expect %d retry count, got %d", e, a)
|
||||
}
|
||||
if !retryer.shouldRetry {
|
||||
t.Errorf("expect 'true' for ShouldRetry, but got %v", retryer.shouldRetry)
|
||||
}
|
||||
}
|
||||
|
||||
type errReader struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (reader *errReader) Read(b []byte) (int, error) {
|
||||
return 0, reader.err
|
||||
}
|
||||
|
||||
func (reader *errReader) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestIsNoBodyReader(t *testing.T) {
|
||||
cases := []struct {
|
||||
reader io.ReadCloser
|
||||
expect bool
|
||||
}{
|
||||
{ioutil.NopCloser(bytes.NewReader([]byte("abc"))), false},
|
||||
{ioutil.NopCloser(bytes.NewReader(nil)), false},
|
||||
{nil, false},
|
||||
{request.NoBody, true},
|
||||
}
|
||||
|
||||
for i, c := range cases {
|
||||
if e, a := c.expect, request.NoBody == c.reader; e != a {
|
||||
t.Errorf("%d, expect %t match, but was %t", i, e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequest_TemporaryRetry(t *testing.T) {
|
||||
done := make(chan struct{})
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Length", "1024")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
w.Write(make([]byte, 100))
|
||||
|
||||
f := w.(http.Flusher)
|
||||
f.Flush()
|
||||
|
||||
<-done
|
||||
}))
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 100 * time.Millisecond,
|
||||
}
|
||||
|
||||
svc := awstesting.NewClient(&aws.Config{
|
||||
Region: unit.Session.Config.Region,
|
||||
MaxRetries: aws.Int(1),
|
||||
HTTPClient: client,
|
||||
DisableSSL: aws.Bool(true),
|
||||
Endpoint: aws.String(server.URL),
|
||||
})
|
||||
|
||||
req := svc.NewRequest(&request.Operation{
|
||||
Name: "name", HTTPMethod: "GET", HTTPPath: "/path",
|
||||
}, &struct{}{}, &struct{}{})
|
||||
|
||||
req.Handlers.Unmarshal.PushBack(func(r *request.Request) {
|
||||
defer req.HTTPResponse.Body.Close()
|
||||
_, err := io.Copy(ioutil.Discard, req.HTTPResponse.Body)
|
||||
r.Error = awserr.New(request.ErrCodeSerialization, "error", err)
|
||||
})
|
||||
|
||||
err := req.Send()
|
||||
if err == nil {
|
||||
t.Errorf("expect error, got none")
|
||||
}
|
||||
close(done)
|
||||
|
||||
aerr := err.(awserr.Error)
|
||||
if e, a := request.ErrCodeSerialization, aerr.Code(); e != a {
|
||||
t.Errorf("expect %q error code, got %q", e, a)
|
||||
}
|
||||
|
||||
if e, a := 1, req.RetryCount; e != a {
|
||||
t.Errorf("expect %d retries, got %d", e, a)
|
||||
}
|
||||
|
||||
type temporary interface {
|
||||
Temporary() bool
|
||||
}
|
||||
|
||||
terr := aerr.OrigErr().(temporary)
|
||||
if !terr.Temporary() {
|
||||
t.Errorf("expect temporary error, was not")
|
||||
}
|
||||
}
|
||||
|
||||
+79
-20
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
// Retryer is an interface to control retry logic for a given service.
|
||||
// The default implementation used by most services is the service.DefaultRetryer
|
||||
// The default implementation used by most services is the client.DefaultRetryer
|
||||
// structure, which contains basic retry logic using exponential backoff.
|
||||
type Retryer interface {
|
||||
RetryRules(*Request) time.Duration
|
||||
@@ -26,8 +26,10 @@ func WithRetryer(cfg *aws.Config, retryer Retryer) *aws.Config {
|
||||
// retryableCodes is a collection of service response codes which are retry-able
|
||||
// without any further action.
|
||||
var retryableCodes = map[string]struct{}{
|
||||
"RequestError": {},
|
||||
"RequestTimeout": {},
|
||||
"RequestError": {},
|
||||
"RequestTimeout": {},
|
||||
ErrCodeResponseTimeout: {},
|
||||
"RequestTimeoutException": {}, // Glacier's flavor of RequestTimeout
|
||||
}
|
||||
|
||||
var throttleCodes = map[string]struct{}{
|
||||
@@ -36,7 +38,6 @@ var throttleCodes = map[string]struct{}{
|
||||
"ThrottlingException": {},
|
||||
"RequestLimitExceeded": {},
|
||||
"RequestThrottled": {},
|
||||
"LimitExceededException": {}, // Deleting 10+ DynamoDb tables at once
|
||||
"TooManyRequestsException": {}, // Lambda functions
|
||||
"PriorRequestNotComplete": {}, // Route53
|
||||
}
|
||||
@@ -68,35 +69,93 @@ func isCodeExpiredCreds(code string) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
var validParentCodes = map[string]struct{}{
|
||||
ErrCodeSerialization: {},
|
||||
ErrCodeRead: {},
|
||||
}
|
||||
|
||||
type temporaryError interface {
|
||||
Temporary() bool
|
||||
}
|
||||
|
||||
func isNestedErrorRetryable(parentErr awserr.Error) bool {
|
||||
if parentErr == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if _, ok := validParentCodes[parentErr.Code()]; !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
err := parentErr.OrigErr()
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if aerr, ok := err.(awserr.Error); ok {
|
||||
return isCodeRetryable(aerr.Code())
|
||||
}
|
||||
|
||||
if t, ok := err.(temporaryError); ok {
|
||||
return t.Temporary()
|
||||
}
|
||||
|
||||
return isErrConnectionReset(err)
|
||||
}
|
||||
|
||||
// IsErrorRetryable returns whether the error is retryable, based on its Code.
|
||||
// Returns false if the request has no Error set.
|
||||
func (r *Request) IsErrorRetryable() bool {
|
||||
if r.Error != nil {
|
||||
if err, ok := r.Error.(awserr.Error); ok {
|
||||
return isCodeRetryable(err.Code())
|
||||
// Returns false if error is nil.
|
||||
func IsErrorRetryable(err error) bool {
|
||||
if err != nil {
|
||||
if aerr, ok := err.(awserr.Error); ok {
|
||||
return isCodeRetryable(aerr.Code()) || isNestedErrorRetryable(aerr)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsErrorThrottle returns whether the error is to be throttled based on its code.
|
||||
// Returns false if the request has no Error set
|
||||
func (r *Request) IsErrorThrottle() bool {
|
||||
if r.Error != nil {
|
||||
if err, ok := r.Error.(awserr.Error); ok {
|
||||
return isCodeThrottle(err.Code())
|
||||
// Returns false if error is nil.
|
||||
func IsErrorThrottle(err error) bool {
|
||||
if err != nil {
|
||||
if aerr, ok := err.(awserr.Error); ok {
|
||||
return isCodeThrottle(aerr.Code())
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsErrorExpired returns whether the error code is a credential expiry error.
|
||||
// Returns false if the request has no Error set.
|
||||
func (r *Request) IsErrorExpired() bool {
|
||||
if r.Error != nil {
|
||||
if err, ok := r.Error.(awserr.Error); ok {
|
||||
return isCodeExpiredCreds(err.Code())
|
||||
// IsErrorExpiredCreds returns whether the error code is a credential expiry error.
|
||||
// Returns false if error is nil.
|
||||
func IsErrorExpiredCreds(err error) bool {
|
||||
if err != nil {
|
||||
if aerr, ok := err.(awserr.Error); ok {
|
||||
return isCodeExpiredCreds(aerr.Code())
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsErrorRetryable returns whether the error is retryable, based on its Code.
|
||||
// Returns false if the request has no Error set.
|
||||
//
|
||||
// Alias for the utility function IsErrorRetryable
|
||||
func (r *Request) IsErrorRetryable() bool {
|
||||
return IsErrorRetryable(r.Error)
|
||||
}
|
||||
|
||||
// IsErrorThrottle returns whether the error is to be throttled based on its code.
|
||||
// Returns false if the request has no Error set
|
||||
//
|
||||
// Alias for the utility function IsErrorThrottle
|
||||
func (r *Request) IsErrorThrottle() bool {
|
||||
return IsErrorThrottle(r.Error)
|
||||
}
|
||||
|
||||
// IsErrorExpired returns whether the error code is a credential expiry error.
|
||||
// Returns false if the request has no Error set.
|
||||
//
|
||||
// Alias for the utility function IsErrorExpiredCreds
|
||||
func (r *Request) IsErrorExpired() bool {
|
||||
return IsErrorExpiredCreds(r.Error)
|
||||
}
|
||||
|
||||
+49
-3
@@ -1,10 +1,10 @@
|
||||
package request
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
)
|
||||
|
||||
@@ -12,5 +12,51 @@ func TestRequestThrottling(t *testing.T) {
|
||||
req := Request{}
|
||||
|
||||
req.Error = awserr.New("Throttling", "", nil)
|
||||
assert.True(t, req.IsErrorThrottle())
|
||||
if e, a := true, req.IsErrorThrottle(); e != a {
|
||||
t.Errorf("expect %t to be throttled, was %t", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
type mockTempError bool
|
||||
|
||||
func (e mockTempError) Error() string {
|
||||
return fmt.Sprintf("mock temporary error: %t", e.Temporary())
|
||||
}
|
||||
func (e mockTempError) Temporary() bool {
|
||||
return bool(e)
|
||||
}
|
||||
|
||||
func TestIsErrorRetryable(t *testing.T) {
|
||||
cases := []struct {
|
||||
Err error
|
||||
IsTemp bool
|
||||
}{
|
||||
{
|
||||
Err: awserr.New(ErrCodeSerialization, "temporary error", mockTempError(true)),
|
||||
IsTemp: true,
|
||||
},
|
||||
{
|
||||
Err: awserr.New(ErrCodeSerialization, "temporary error", mockTempError(false)),
|
||||
IsTemp: false,
|
||||
},
|
||||
{
|
||||
Err: awserr.New(ErrCodeSerialization, "some error", errors.New("blah")),
|
||||
IsTemp: false,
|
||||
},
|
||||
{
|
||||
Err: awserr.New("SomeError", "some error", nil),
|
||||
IsTemp: false,
|
||||
},
|
||||
{
|
||||
Err: awserr.New("RequestError", "some error", nil),
|
||||
IsTemp: true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, c := range cases {
|
||||
retryable := IsErrorRetryable(c.Err)
|
||||
if e, a := c.IsTemp, retryable; e != a {
|
||||
t.Errorf("%d, expect %t temporary error, got %t", i, e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
package request
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
)
|
||||
|
||||
var timeoutErr = awserr.New(
|
||||
ErrCodeResponseTimeout,
|
||||
"read on body has reached the timeout limit",
|
||||
nil,
|
||||
)
|
||||
|
||||
type readResult struct {
|
||||
n int
|
||||
err error
|
||||
}
|
||||
|
||||
// timeoutReadCloser will handle body reads that take too long.
|
||||
// We will return a ErrReadTimeout error if a timeout occurs.
|
||||
type timeoutReadCloser struct {
|
||||
reader io.ReadCloser
|
||||
duration time.Duration
|
||||
}
|
||||
|
||||
// Read will spin off a goroutine to call the reader's Read method. We will
|
||||
// select on the timer's channel or the read's channel. Whoever completes first
|
||||
// will be returned.
|
||||
func (r *timeoutReadCloser) Read(b []byte) (int, error) {
|
||||
timer := time.NewTimer(r.duration)
|
||||
c := make(chan readResult, 1)
|
||||
|
||||
go func() {
|
||||
n, err := r.reader.Read(b)
|
||||
timer.Stop()
|
||||
c <- readResult{n: n, err: err}
|
||||
}()
|
||||
|
||||
select {
|
||||
case data := <-c:
|
||||
return data.n, data.err
|
||||
case <-timer.C:
|
||||
return 0, timeoutErr
|
||||
}
|
||||
}
|
||||
|
||||
func (r *timeoutReadCloser) Close() error {
|
||||
return r.reader.Close()
|
||||
}
|
||||
|
||||
const (
|
||||
// HandlerResponseTimeout is what we use to signify the name of the
|
||||
// response timeout handler.
|
||||
HandlerResponseTimeout = "ResponseTimeoutHandler"
|
||||
)
|
||||
|
||||
// adaptToResponseTimeoutError is a handler that will replace any top level error
|
||||
// to a ErrCodeResponseTimeout, if its child is that.
|
||||
func adaptToResponseTimeoutError(req *Request) {
|
||||
if err, ok := req.Error.(awserr.Error); ok {
|
||||
aerr, ok := err.OrigErr().(awserr.Error)
|
||||
if ok && aerr.Code() == ErrCodeResponseTimeout {
|
||||
req.Error = aerr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithResponseReadTimeout is a request option that will wrap the body in a timeout read closer.
|
||||
// This will allow for per read timeouts. If a timeout occurred, we will return the
|
||||
// ErrCodeResponseTimeout.
|
||||
//
|
||||
// svc.PutObjectWithContext(ctx, params, request.WithTimeoutReadCloser(30 * time.Second)
|
||||
func WithResponseReadTimeout(duration time.Duration) Option {
|
||||
return func(r *Request) {
|
||||
|
||||
var timeoutHandler = NamedHandler{
|
||||
HandlerResponseTimeout,
|
||||
func(req *Request) {
|
||||
req.HTTPResponse.Body = &timeoutReadCloser{
|
||||
reader: req.HTTPResponse.Body,
|
||||
duration: duration,
|
||||
}
|
||||
}}
|
||||
|
||||
// remove the handler so we are not stomping over any new durations.
|
||||
r.Handlers.Send.RemoveByName(HandlerResponseTimeout)
|
||||
r.Handlers.Send.PushBackNamed(timeoutHandler)
|
||||
|
||||
r.Handlers.Unmarshal.PushBack(adaptToResponseTimeoutError)
|
||||
r.Handlers.UnmarshalError.PushBack(adaptToResponseTimeoutError)
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+76
@@ -0,0 +1,76 @@
|
||||
package request_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/client"
|
||||
"github.com/aws/aws-sdk-go/aws/client/metadata"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/aws/signer/v4"
|
||||
"github.com/aws/aws-sdk-go/awstesting/unit"
|
||||
"github.com/aws/aws-sdk-go/private/protocol/jsonrpc"
|
||||
)
|
||||
|
||||
func BenchmarkTimeoutReadCloser(b *testing.B) {
|
||||
resp := `
|
||||
{
|
||||
"Bar": "qux"
|
||||
}
|
||||
`
|
||||
|
||||
handlers := request.Handlers{}
|
||||
|
||||
handlers.Send.PushBack(func(r *request.Request) {
|
||||
r.HTTPResponse = &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: ioutil.NopCloser(bytes.NewBuffer([]byte(resp))),
|
||||
}
|
||||
})
|
||||
handlers.Sign.PushBackNamed(v4.SignRequestHandler)
|
||||
handlers.Build.PushBackNamed(jsonrpc.BuildHandler)
|
||||
handlers.Unmarshal.PushBackNamed(jsonrpc.UnmarshalHandler)
|
||||
handlers.UnmarshalMeta.PushBackNamed(jsonrpc.UnmarshalMetaHandler)
|
||||
handlers.UnmarshalError.PushBackNamed(jsonrpc.UnmarshalErrorHandler)
|
||||
|
||||
op := &request.Operation{
|
||||
Name: "op",
|
||||
HTTPMethod: "POST",
|
||||
HTTPPath: "/",
|
||||
}
|
||||
|
||||
meta := metadata.ClientInfo{
|
||||
ServiceName: "fooService",
|
||||
SigningName: "foo",
|
||||
SigningRegion: "foo",
|
||||
Endpoint: "localhost",
|
||||
APIVersion: "2001-01-01",
|
||||
JSONVersion: "1.1",
|
||||
TargetPrefix: "Foo",
|
||||
}
|
||||
|
||||
req := request.New(
|
||||
*unit.Session.Config,
|
||||
meta,
|
||||
handlers,
|
||||
client.DefaultRetryer{NumMaxRetries: 5},
|
||||
op,
|
||||
&struct {
|
||||
Foo *string
|
||||
}{},
|
||||
&struct {
|
||||
Bar *string
|
||||
}{},
|
||||
)
|
||||
|
||||
req.ApplyOptions(request.WithResponseReadTimeout(15 * time.Second))
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := req.Send()
|
||||
if err != nil {
|
||||
b.Errorf("Expected no error, but received %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
package request
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
)
|
||||
|
||||
type testReader struct {
|
||||
duration time.Duration
|
||||
count int
|
||||
}
|
||||
|
||||
func (r *testReader) Read(b []byte) (int, error) {
|
||||
if r.count > 0 {
|
||||
r.count--
|
||||
return len(b), nil
|
||||
}
|
||||
time.Sleep(r.duration)
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
func (r *testReader) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestTimeoutReadCloser(t *testing.T) {
|
||||
reader := timeoutReadCloser{
|
||||
reader: &testReader{
|
||||
duration: time.Second,
|
||||
count: 5,
|
||||
},
|
||||
duration: time.Millisecond,
|
||||
}
|
||||
b := make([]byte, 100)
|
||||
_, err := reader.Read(b)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeoutReadCloserSameDuration(t *testing.T) {
|
||||
reader := timeoutReadCloser{
|
||||
reader: &testReader{
|
||||
duration: time.Millisecond,
|
||||
count: 5,
|
||||
},
|
||||
duration: time.Millisecond,
|
||||
}
|
||||
b := make([]byte, 100)
|
||||
_, err := reader.Read(b)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithResponseReadTimeout(t *testing.T) {
|
||||
r := Request{
|
||||
HTTPResponse: &http.Response{
|
||||
Body: ioutil.NopCloser(bytes.NewReader(nil)),
|
||||
},
|
||||
}
|
||||
r.ApplyOptions(WithResponseReadTimeout(time.Second))
|
||||
err := r.Send()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
v, ok := r.HTTPResponse.Body.(*timeoutReadCloser)
|
||||
if !ok {
|
||||
t.Error("Expected the body to be a timeoutReadCloser")
|
||||
}
|
||||
if v.duration != time.Second {
|
||||
t.Errorf("Expected %v, but receive %v\n", time.Second, v.duration)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdaptToResponseTimeout(t *testing.T) {
|
||||
testCases := []struct {
|
||||
childErr error
|
||||
r Request
|
||||
expectedRootCode string
|
||||
}{
|
||||
{
|
||||
childErr: awserr.New(ErrCodeResponseTimeout, "timeout!", nil),
|
||||
r: Request{
|
||||
Error: awserr.New("ErrTest", "FooBar", awserr.New(ErrCodeResponseTimeout, "timeout!", nil)),
|
||||
},
|
||||
expectedRootCode: ErrCodeResponseTimeout,
|
||||
},
|
||||
{
|
||||
childErr: awserr.New(ErrCodeResponseTimeout+"1", "timeout!", nil),
|
||||
r: Request{
|
||||
Error: awserr.New("ErrTest", "FooBar", awserr.New(ErrCodeResponseTimeout+"1", "timeout!", nil)),
|
||||
},
|
||||
expectedRootCode: "ErrTest",
|
||||
},
|
||||
{
|
||||
r: Request{
|
||||
Error: awserr.New("ErrTest", "FooBar", nil),
|
||||
},
|
||||
expectedRootCode: "ErrTest",
|
||||
},
|
||||
}
|
||||
|
||||
for i, c := range testCases {
|
||||
adaptToResponseTimeoutError(&c.r)
|
||||
if aerr, ok := c.r.Error.(awserr.Error); !ok {
|
||||
t.Errorf("Case %d: Expected 'awserr', but received %v", i+1, c.r.Error)
|
||||
} else if aerr.Code() != c.expectedRootCode {
|
||||
t.Errorf("Case %d: Expected %q, but received %s", i+1, c.expectedRootCode, aerr.Code())
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -220,7 +220,7 @@ type ErrParamMinLen struct {
|
||||
func NewErrParamMinLen(field string, min int) *ErrParamMinLen {
|
||||
return &ErrParamMinLen{
|
||||
errInvalidParam: errInvalidParam{
|
||||
code: ParamMinValueErrCode,
|
||||
code: ParamMinLenErrCode,
|
||||
field: field,
|
||||
msg: fmt.Sprintf("minimum field size of %v", min),
|
||||
},
|
||||
|
||||
+17
-15
@@ -66,8 +66,8 @@ func WithWaiterRequestOptions(opts ...Option) WaiterOption {
|
||||
}
|
||||
}
|
||||
|
||||
// A Waiter provides the functionality to performing blocking call which will
|
||||
// wait for an resource state to be satisfied a service.
|
||||
// A Waiter provides the functionality to perform a blocking call which will
|
||||
// wait for a resource state to be satisfied by a service.
|
||||
//
|
||||
// This type should not be used directly. The API operations provided in the
|
||||
// service packages prefixed with "WaitUntil" should be used instead.
|
||||
@@ -79,8 +79,9 @@ type Waiter struct {
|
||||
MaxAttempts int
|
||||
Delay WaiterDelay
|
||||
|
||||
RequestOptions []Option
|
||||
NewRequest func([]Option) (*Request, error)
|
||||
RequestOptions []Option
|
||||
NewRequest func([]Option) (*Request, error)
|
||||
SleepWithContext func(aws.Context, time.Duration) error
|
||||
}
|
||||
|
||||
// ApplyOptions updates the waiter with the list of waiter options provided.
|
||||
@@ -178,14 +179,8 @@ func (w Waiter) WaitWithContext(ctx aws.Context) error {
|
||||
|
||||
// See if any of the acceptors match the request's response, or error
|
||||
for _, a := range w.Acceptors {
|
||||
var matched bool
|
||||
matched, err = a.match(w.Name, w.Logger, req, err)
|
||||
if err != nil {
|
||||
// Error occurred during current waiter call
|
||||
return err
|
||||
} else if matched {
|
||||
// Match was found can stop here and return
|
||||
return nil
|
||||
if matched, matchErr := a.match(w.Name, w.Logger, req, err); matched {
|
||||
return matchErr
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,8 +196,15 @@ func (w Waiter) WaitWithContext(ctx aws.Context) error {
|
||||
if sleepFn := req.Config.SleepDelay; sleepFn != nil {
|
||||
// Support SleepDelay for backwards compatibility and testing
|
||||
sleepFn(delay)
|
||||
} else if err := aws.SleepWithContext(ctx, delay); err != nil {
|
||||
return awserr.New(CanceledErrorCode, "waiter context canceled", err)
|
||||
} else {
|
||||
sleepCtxFn := w.SleepWithContext
|
||||
if sleepCtxFn == nil {
|
||||
sleepCtxFn = aws.SleepWithContext
|
||||
}
|
||||
|
||||
if err := sleepCtxFn(ctx, delay); err != nil {
|
||||
return awserr.New(CanceledErrorCode, "waiter context canceled", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,7 +276,7 @@ func (a *WaiterAcceptor) match(name string, l aws.Logger, req *Request, err erro
|
||||
return true, nil
|
||||
case FailureWaiterState:
|
||||
// Waiter failure state triggered
|
||||
return false, awserr.New("ResourceNotReady",
|
||||
return true, awserr.New(WaiterResourceNotReadyErrorCode,
|
||||
"failed waiting for successful resource state", err)
|
||||
case RetryWaiterState:
|
||||
// clear the error and retry the operation
|
||||
|
||||
+122
-27
@@ -15,6 +15,8 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws/client"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/awstesting"
|
||||
"github.com/aws/aws-sdk-go/awstesting/unit"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
)
|
||||
|
||||
type mockClient struct {
|
||||
@@ -98,8 +100,9 @@ func TestWaiterPathAll(t *testing.T) {
|
||||
})
|
||||
|
||||
w := request.Waiter{
|
||||
MaxAttempts: 10,
|
||||
Delay: request.ConstantWaiterDelay(0),
|
||||
MaxAttempts: 10,
|
||||
Delay: request.ConstantWaiterDelay(0),
|
||||
SleepWithContext: aws.SleepWithContext,
|
||||
Acceptors: []request.WaiterAcceptor{
|
||||
{
|
||||
State: request.SuccessWaiterState,
|
||||
@@ -162,8 +165,9 @@ func TestWaiterPath(t *testing.T) {
|
||||
})
|
||||
|
||||
w := request.Waiter{
|
||||
MaxAttempts: 10,
|
||||
Delay: request.ConstantWaiterDelay(0),
|
||||
MaxAttempts: 10,
|
||||
Delay: request.ConstantWaiterDelay(0),
|
||||
SleepWithContext: aws.SleepWithContext,
|
||||
Acceptors: []request.WaiterAcceptor{
|
||||
{
|
||||
State: request.SuccessWaiterState,
|
||||
@@ -226,8 +230,9 @@ func TestWaiterFailure(t *testing.T) {
|
||||
})
|
||||
|
||||
w := request.Waiter{
|
||||
MaxAttempts: 10,
|
||||
Delay: request.ConstantWaiterDelay(0),
|
||||
MaxAttempts: 10,
|
||||
Delay: request.ConstantWaiterDelay(0),
|
||||
SleepWithContext: aws.SleepWithContext,
|
||||
Acceptors: []request.WaiterAcceptor{
|
||||
{
|
||||
State: request.SuccessWaiterState,
|
||||
@@ -271,7 +276,9 @@ func TestWaiterError(t *testing.T) {
|
||||
{State: aws.String("pending")},
|
||||
},
|
||||
},
|
||||
{ // Request 2, error case
|
||||
{ // Request 1, error case retry
|
||||
},
|
||||
{ // Request 2, error case failure
|
||||
},
|
||||
{ // Request 3
|
||||
States: []*MockState{
|
||||
@@ -280,6 +287,9 @@ func TestWaiterError(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
reqErrs := make([]error, len(resps))
|
||||
reqErrs[1] = awserr.New("MockException", "mock exception message", nil)
|
||||
reqErrs[2] = awserr.New("FailureException", "mock failure exception message", nil)
|
||||
|
||||
numBuiltReq := 0
|
||||
svc.Handlers.Build.PushBack(func(r *request.Request) {
|
||||
@@ -305,17 +315,18 @@ func TestWaiterError(t *testing.T) {
|
||||
reqNum++
|
||||
})
|
||||
svc.Handlers.UnmarshalMeta.PushBack(func(r *request.Request) {
|
||||
if reqNum == 1 {
|
||||
r.Error = awserr.New("MockException", "mock exception message", nil)
|
||||
// If there was an error unmarshal error will be called instead of unmarshal
|
||||
// need to increment count here also
|
||||
// If there was an error unmarshal error will be called instead of unmarshal
|
||||
// need to increment count here also
|
||||
if err := reqErrs[reqNum]; err != nil {
|
||||
r.Error = err
|
||||
reqNum++
|
||||
}
|
||||
})
|
||||
|
||||
w := request.Waiter{
|
||||
MaxAttempts: 10,
|
||||
Delay: request.ConstantWaiterDelay(0),
|
||||
MaxAttempts: 10,
|
||||
Delay: request.ConstantWaiterDelay(0),
|
||||
SleepWithContext: aws.SleepWithContext,
|
||||
Acceptors: []request.WaiterAcceptor{
|
||||
{
|
||||
State: request.SuccessWaiterState,
|
||||
@@ -329,14 +340,30 @@ func TestWaiterError(t *testing.T) {
|
||||
Argument: "",
|
||||
Expected: "MockException",
|
||||
},
|
||||
{
|
||||
State: request.FailureWaiterState,
|
||||
Matcher: request.ErrorWaiterMatch,
|
||||
Argument: "",
|
||||
Expected: "FailureException",
|
||||
},
|
||||
},
|
||||
NewRequest: BuildNewMockRequest(svc, &MockInput{}),
|
||||
}
|
||||
|
||||
err := w.WaitWithContext(aws.BackgroundContext())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 3, numBuiltReq)
|
||||
assert.Equal(t, 3, reqNum)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, but did not get one")
|
||||
}
|
||||
aerr := err.(awserr.Error)
|
||||
if e, a := request.WaiterResourceNotReadyErrorCode, aerr.Code(); e != a {
|
||||
t.Errorf("expect %q error code, got %q", e, a)
|
||||
}
|
||||
if e, a := 3, numBuiltReq; e != a {
|
||||
t.Errorf("expect %d built requests got %d", e, a)
|
||||
}
|
||||
if e, a := 3, reqNum; e != a {
|
||||
t.Errorf("expect %d reqNum got %d", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWaiterStatus(t *testing.T) {
|
||||
@@ -366,8 +393,9 @@ func TestWaiterStatus(t *testing.T) {
|
||||
})
|
||||
|
||||
w := request.Waiter{
|
||||
MaxAttempts: 10,
|
||||
Delay: request.ConstantWaiterDelay(0),
|
||||
MaxAttempts: 10,
|
||||
Delay: request.ConstantWaiterDelay(0),
|
||||
SleepWithContext: aws.SleepWithContext,
|
||||
Acceptors: []request.WaiterAcceptor{
|
||||
{
|
||||
State: request.SuccessWaiterState,
|
||||
@@ -424,9 +452,10 @@ func TestWaiter_WithContextCanceled(t *testing.T) {
|
||||
reqCount := 0
|
||||
|
||||
w := request.Waiter{
|
||||
Name: "TestWaiter",
|
||||
MaxAttempts: 10,
|
||||
Delay: request.ConstantWaiterDelay(1 * time.Millisecond),
|
||||
Name: "TestWaiter",
|
||||
MaxAttempts: 10,
|
||||
Delay: request.ConstantWaiterDelay(1 * time.Millisecond),
|
||||
SleepWithContext: aws.SleepWithContext,
|
||||
Acceptors: []request.WaiterAcceptor{
|
||||
{
|
||||
State: request.SuccessWaiterState,
|
||||
@@ -452,6 +481,16 @@ func TestWaiter_WithContextCanceled(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
w.SleepWithContext = func(c aws.Context, delay time.Duration) error {
|
||||
context := c.(*awstesting.FakeContext)
|
||||
select {
|
||||
case <-context.DoneCh:
|
||||
return context.Err()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
err := w.WaitWithContext(ctx)
|
||||
|
||||
if err == nil {
|
||||
@@ -475,9 +514,10 @@ func TestWaiter_WithContext(t *testing.T) {
|
||||
statuses := []int{http.StatusNotFound, http.StatusOK}
|
||||
|
||||
w := request.Waiter{
|
||||
Name: "TestWaiter",
|
||||
MaxAttempts: 10,
|
||||
Delay: request.ConstantWaiterDelay(1 * time.Millisecond),
|
||||
Name: "TestWaiter",
|
||||
MaxAttempts: 10,
|
||||
Delay: request.ConstantWaiterDelay(1 * time.Millisecond),
|
||||
SleepWithContext: aws.SleepWithContext,
|
||||
Acceptors: []request.WaiterAcceptor{
|
||||
{
|
||||
State: request.SuccessWaiterState,
|
||||
@@ -520,9 +560,10 @@ func TestWaiter_AttemptsExpires(t *testing.T) {
|
||||
reqCount := 0
|
||||
|
||||
w := request.Waiter{
|
||||
Name: "TestWaiter",
|
||||
MaxAttempts: 2,
|
||||
Delay: request.ConstantWaiterDelay(1 * time.Millisecond),
|
||||
Name: "TestWaiter",
|
||||
MaxAttempts: 2,
|
||||
Delay: request.ConstantWaiterDelay(1 * time.Millisecond),
|
||||
SleepWithContext: aws.SleepWithContext,
|
||||
Acceptors: []request.WaiterAcceptor{
|
||||
{
|
||||
State: request.SuccessWaiterState,
|
||||
@@ -557,3 +598,57 @@ func TestWaiter_AttemptsExpires(t *testing.T) {
|
||||
t.Errorf("expect %d requests, got %d", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWaiterNilInput(t *testing.T) {
|
||||
// Code generation doesn't have a great way to verify the code is correct
|
||||
// other than being run via unit tests in the SDK. This should be fixed
|
||||
// So code generation can be validated independently.
|
||||
|
||||
client := s3.New(unit.Session)
|
||||
client.Handlers.Validate.Clear()
|
||||
client.Handlers.Send.Clear() // mock sending
|
||||
client.Handlers.Send.PushBack(func(r *request.Request) {
|
||||
r.HTTPResponse = &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
}
|
||||
})
|
||||
client.Handlers.Unmarshal.Clear()
|
||||
client.Handlers.UnmarshalMeta.Clear()
|
||||
client.Handlers.ValidateResponse.Clear()
|
||||
client.Config.SleepDelay = func(dur time.Duration) {}
|
||||
|
||||
// Ensure waiters do not panic on nil input. It doesn't make sense to
|
||||
// call a waiter without an input, Validation will
|
||||
err := client.WaitUntilBucketExists(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, but got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWaiterWithContextNilInput(t *testing.T) {
|
||||
// Code generation doesn't have a great way to verify the code is correct
|
||||
// other than being run via unit tests in the SDK. This should be fixed
|
||||
// So code generation can be validated independently.
|
||||
|
||||
client := s3.New(unit.Session)
|
||||
client.Handlers.Validate.Clear()
|
||||
client.Handlers.Send.Clear() // mock sending
|
||||
client.Handlers.Send.PushBack(func(r *request.Request) {
|
||||
r.HTTPResponse = &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
}
|
||||
})
|
||||
client.Handlers.Unmarshal.Clear()
|
||||
client.Handlers.UnmarshalMeta.Clear()
|
||||
client.Handlers.ValidateResponse.Clear()
|
||||
|
||||
// Ensure waiters do not panic on nil input
|
||||
ctx := &awstesting.FakeContext{DoneCh: make(chan struct{})}
|
||||
err := client.WaitUntilBucketExistsWithContext(ctx, nil,
|
||||
request.WithWaiterDelay(request.ConstantWaiterDelay(0)),
|
||||
request.WithWaiterMaxAttempts(1),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, but got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
+118
-194
@@ -2,158 +2,166 @@ package session
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"io/ioutil"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/aws/aws-sdk-go/awstesting"
|
||||
)
|
||||
|
||||
func createTLSServer(cert, key []byte, done <-chan struct{}) (*httptest.Server, error) {
|
||||
c, err := tls.X509KeyPair(cert, key)
|
||||
var TLSBundleCertFile string
|
||||
var TLSBundleKeyFile string
|
||||
var TLSBundleCAFile string
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
var err error
|
||||
|
||||
TLSBundleCertFile, TLSBundleKeyFile, TLSBundleCAFile, err = awstesting.CreateTLSBundleFiles()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
panic(err)
|
||||
}
|
||||
|
||||
s := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
s.TLS = &tls.Config{
|
||||
Certificates: []tls.Certificate{c},
|
||||
}
|
||||
s.TLS.BuildNameToCertificate()
|
||||
s.StartTLS()
|
||||
fmt.Println("TestMain", TLSBundleCertFile, TLSBundleKeyFile)
|
||||
|
||||
go func() {
|
||||
<-done
|
||||
s.Close()
|
||||
}()
|
||||
code := m.Run()
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func setupTestCAFile(b []byte) (string, error) {
|
||||
bundleFile, err := ioutil.TempFile(os.TempDir(), "aws-sdk-go-session-test")
|
||||
err = awstesting.CleanupTLSBundleFiles(TLSBundleCertFile, TLSBundleKeyFile, TLSBundleCAFile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = bundleFile.Write(b)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer bundleFile.Close()
|
||||
return bundleFile.Name(), nil
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func TestNewSession_WithCustomCABundle_Env(t *testing.T) {
|
||||
oldEnv := initSessionTestEnv()
|
||||
defer popEnv(oldEnv)
|
||||
defer awstesting.PopEnv(oldEnv)
|
||||
|
||||
done := make(chan struct{})
|
||||
server, err := createTLSServer(testTLSBundleCert, testTLSBundleKey, done)
|
||||
assert.NoError(t, err)
|
||||
endpoint, err := awstesting.CreateTLSServer(TLSBundleCertFile, TLSBundleKeyFile, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, got %v", err)
|
||||
}
|
||||
|
||||
// Write bundle to file
|
||||
caFilename, err := setupTestCAFile(testTLSBundleCA)
|
||||
defer func() {
|
||||
os.Remove(caFilename)
|
||||
}()
|
||||
assert.NoError(t, err)
|
||||
|
||||
os.Setenv("AWS_CA_BUNDLE", caFilename)
|
||||
os.Setenv("AWS_CA_BUNDLE", TLSBundleCAFile)
|
||||
|
||||
s, err := NewSession(&aws.Config{
|
||||
HTTPClient: &http.Client{},
|
||||
Endpoint: aws.String(server.URL),
|
||||
Endpoint: aws.String(endpoint),
|
||||
Region: aws.String("mock-region"),
|
||||
Credentials: credentials.AnonymousCredentials,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, s)
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, got %v", err)
|
||||
}
|
||||
if s == nil {
|
||||
t.Fatalf("expect session to be created, got none")
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest("GET", *s.Config.Endpoint, nil)
|
||||
resp, err := s.Config.HTTPClient.Do(req)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, got %v", err)
|
||||
}
|
||||
if e, a := http.StatusOK, resp.StatusCode; e != a {
|
||||
t.Errorf("expect %d status code, got %d", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewSession_WithCustomCABundle_EnvNotExists(t *testing.T) {
|
||||
oldEnv := initSessionTestEnv()
|
||||
defer popEnv(oldEnv)
|
||||
defer awstesting.PopEnv(oldEnv)
|
||||
|
||||
os.Setenv("AWS_CA_BUNDLE", "file-not-exists")
|
||||
|
||||
s, err := NewSession()
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "LoadCustomCABundleError", err.(awserr.Error).Code())
|
||||
assert.Nil(t, s)
|
||||
if err == nil {
|
||||
t.Fatalf("expect error, got none")
|
||||
}
|
||||
if e, a := "LoadCustomCABundleError", err.(awserr.Error).Code(); e != a {
|
||||
t.Errorf("expect %s error code, got %s", e, a)
|
||||
}
|
||||
if s != nil {
|
||||
t.Errorf("expect nil session, got %v", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewSession_WithCustomCABundle_Option(t *testing.T) {
|
||||
oldEnv := initSessionTestEnv()
|
||||
defer popEnv(oldEnv)
|
||||
defer awstesting.PopEnv(oldEnv)
|
||||
|
||||
done := make(chan struct{})
|
||||
server, err := createTLSServer(testTLSBundleCert, testTLSBundleKey, done)
|
||||
assert.NoError(t, err)
|
||||
endpoint, err := awstesting.CreateTLSServer(TLSBundleCertFile, TLSBundleKeyFile, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, got %v", err)
|
||||
}
|
||||
|
||||
s, err := NewSessionWithOptions(Options{
|
||||
Config: aws.Config{
|
||||
HTTPClient: &http.Client{},
|
||||
Endpoint: aws.String(server.URL),
|
||||
Endpoint: aws.String(endpoint),
|
||||
Region: aws.String("mock-region"),
|
||||
Credentials: credentials.AnonymousCredentials,
|
||||
},
|
||||
CustomCABundle: bytes.NewReader(testTLSBundleCA),
|
||||
CustomCABundle: bytes.NewReader(awstesting.TLSBundleCA),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, s)
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, got %v", err)
|
||||
}
|
||||
if s == nil {
|
||||
t.Fatalf("expect session to be created, got none")
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest("GET", *s.Config.Endpoint, nil)
|
||||
resp, err := s.Config.HTTPClient.Do(req)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, got %v", err)
|
||||
}
|
||||
if e, a := http.StatusOK, resp.StatusCode; e != a {
|
||||
t.Errorf("expect %d status code, got %d", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewSession_WithCustomCABundle_OptionPriority(t *testing.T) {
|
||||
oldEnv := initSessionTestEnv()
|
||||
defer popEnv(oldEnv)
|
||||
defer awstesting.PopEnv(oldEnv)
|
||||
|
||||
done := make(chan struct{})
|
||||
server, err := createTLSServer(testTLSBundleCert, testTLSBundleKey, done)
|
||||
assert.NoError(t, err)
|
||||
endpoint, err := awstesting.CreateTLSServer(TLSBundleCertFile, TLSBundleKeyFile, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, got %v", err)
|
||||
}
|
||||
|
||||
os.Setenv("AWS_CA_BUNDLE", "file-not-exists")
|
||||
|
||||
s, err := NewSessionWithOptions(Options{
|
||||
Config: aws.Config{
|
||||
HTTPClient: &http.Client{},
|
||||
Endpoint: aws.String(server.URL),
|
||||
Endpoint: aws.String(endpoint),
|
||||
Region: aws.String("mock-region"),
|
||||
Credentials: credentials.AnonymousCredentials,
|
||||
},
|
||||
CustomCABundle: bytes.NewReader(testTLSBundleCA),
|
||||
CustomCABundle: bytes.NewReader(awstesting.TLSBundleCA),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, s)
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, got %v", err)
|
||||
}
|
||||
if s == nil {
|
||||
t.Fatalf("expect session to be created, got none")
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest("GET", *s.Config.Endpoint, nil)
|
||||
resp, err := s.Config.HTTPClient.Do(req)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, got %v", err)
|
||||
}
|
||||
if e, a := http.StatusOK, resp.StatusCode; e != a {
|
||||
t.Errorf("expect %d status code, got %d", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
type mockRoundTripper struct{}
|
||||
@@ -164,7 +172,7 @@ func (m *mockRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
|
||||
func TestNewSession_WithCustomCABundle_UnsupportedTransport(t *testing.T) {
|
||||
oldEnv := initSessionTestEnv()
|
||||
defer popEnv(oldEnv)
|
||||
defer awstesting.PopEnv(oldEnv)
|
||||
|
||||
s, err := NewSessionWithOptions(Options{
|
||||
Config: aws.Config{
|
||||
@@ -172,25 +180,35 @@ func TestNewSession_WithCustomCABundle_UnsupportedTransport(t *testing.T) {
|
||||
Transport: &mockRoundTripper{},
|
||||
},
|
||||
},
|
||||
CustomCABundle: bytes.NewReader(testTLSBundleCA),
|
||||
CustomCABundle: bytes.NewReader(awstesting.TLSBundleCA),
|
||||
})
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "LoadCustomCABundleError", err.(awserr.Error).Code())
|
||||
assert.Contains(t, err.(awserr.Error).Message(), "transport unsupported type")
|
||||
assert.Nil(t, s)
|
||||
if err == nil {
|
||||
t.Fatalf("expect error, got none")
|
||||
}
|
||||
if e, a := "LoadCustomCABundleError", err.(awserr.Error).Code(); e != a {
|
||||
t.Errorf("expect %s error code, got %s", e, a)
|
||||
}
|
||||
if s != nil {
|
||||
t.Errorf("expect nil session, got %v", s)
|
||||
}
|
||||
aerrMsg := err.(awserr.Error).Message()
|
||||
if e, a := "transport unsupported type", aerrMsg; !strings.Contains(a, e) {
|
||||
t.Errorf("expect %s to be in %s", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewSession_WithCustomCABundle_TransportSet(t *testing.T) {
|
||||
oldEnv := initSessionTestEnv()
|
||||
defer popEnv(oldEnv)
|
||||
defer awstesting.PopEnv(oldEnv)
|
||||
|
||||
done := make(chan struct{})
|
||||
server, err := createTLSServer(testTLSBundleCert, testTLSBundleKey, done)
|
||||
assert.NoError(t, err)
|
||||
endpoint, err := awstesting.CreateTLSServer(TLSBundleCertFile, TLSBundleKeyFile, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, got %v", err)
|
||||
}
|
||||
|
||||
s, err := NewSessionWithOptions(Options{
|
||||
Config: aws.Config{
|
||||
Endpoint: aws.String(server.URL),
|
||||
Endpoint: aws.String(endpoint),
|
||||
Region: aws.String("mock-region"),
|
||||
Credentials: credentials.AnonymousCredentials,
|
||||
HTTPClient: &http.Client{
|
||||
@@ -205,115 +223,21 @@ func TestNewSession_WithCustomCABundle_TransportSet(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
CustomCABundle: bytes.NewReader(testTLSBundleCA),
|
||||
CustomCABundle: bytes.NewReader(awstesting.TLSBundleCA),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, s)
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, got %v", err)
|
||||
}
|
||||
if s == nil {
|
||||
t.Fatalf("expect session to be created, got none")
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest("GET", *s.Config.Endpoint, nil)
|
||||
resp, err := s.Config.HTTPClient.Do(req)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, got %v", err)
|
||||
}
|
||||
if e, a := http.StatusOK, resp.StatusCode; e != a {
|
||||
t.Errorf("expect %d status code, got %d", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
/* Cert generation steps
|
||||
# Create the CA key
|
||||
openssl genrsa -des3 -out ca.key 1024
|
||||
|
||||
# Create the CA Cert
|
||||
openssl req -new -sha256 -x509 -days 3650 \
|
||||
-subj "/C=GO/ST=Gopher/O=Testing ROOT CA" \
|
||||
-key ca.key -out ca.crt
|
||||
|
||||
# Create config
|
||||
cat > csr_details.txt <<-EOF
|
||||
|
||||
[req]
|
||||
default_bits = 1024
|
||||
prompt = no
|
||||
default_md = sha256
|
||||
req_extensions = SAN
|
||||
distinguished_name = dn
|
||||
|
||||
[ dn ]
|
||||
C=GO
|
||||
ST=Gopher
|
||||
O=Testing Certificate
|
||||
OU=Testing IP
|
||||
|
||||
[SAN]
|
||||
subjectAltName = IP:127.0.0.1
|
||||
EOF
|
||||
|
||||
# Create certificate signing request
|
||||
openssl req -new -sha256 -nodes -newkey rsa:1024 \
|
||||
-config <( cat csr_details.txt ) \
|
||||
-keyout ia.key -out ia.csr
|
||||
|
||||
# Create a signed certificate
|
||||
openssl x509 -req -days 3650 \
|
||||
-CAcreateserial \
|
||||
-extfile <( cat csr_details.txt ) \
|
||||
-extensions SAN \
|
||||
-CA ca.crt -CAkey ca.key -in ia.csr -out ia.crt
|
||||
|
||||
# Verify
|
||||
openssl req -noout -text -in ia.csr
|
||||
openssl x509 -noout -text -in ia.crt
|
||||
*/
|
||||
var (
|
||||
// ca.crt
|
||||
testTLSBundleCA = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIICiTCCAfKgAwIBAgIJAJ5X1olt05XjMA0GCSqGSIb3DQEBCwUAMDgxCzAJBgNV
|
||||
BAYTAkdPMQ8wDQYDVQQIEwZHb3BoZXIxGDAWBgNVBAoTD1Rlc3RpbmcgUk9PVCBD
|
||||
QTAeFw0xNzAzMDkwMDAyMDZaFw0yNzAzMDcwMDAyMDZaMDgxCzAJBgNVBAYTAkdP
|
||||
MQ8wDQYDVQQIEwZHb3BoZXIxGDAWBgNVBAoTD1Rlc3RpbmcgUk9PVCBDQTCBnzAN
|
||||
BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAw/8DN+t9XQR60jx42rsQ2WE2Dx85rb3n
|
||||
GQxnKZZLNddsT8rDyxJNP18aFalbRbFlyln5fxWxZIblu9Xkm/HRhOpbSimSqo1y
|
||||
uDx21NVZ1YsOvXpHby71jx3gPrrhSc/t/zikhi++6D/C6m1CiIGuiJ0GBiJxtrub
|
||||
UBMXT0QtI2ECAwEAAaOBmjCBlzAdBgNVHQ4EFgQU8XG3X/YHBA6T04kdEkq6+4GV
|
||||
YykwaAYDVR0jBGEwX4AU8XG3X/YHBA6T04kdEkq6+4GVYymhPKQ6MDgxCzAJBgNV
|
||||
BAYTAkdPMQ8wDQYDVQQIEwZHb3BoZXIxGDAWBgNVBAoTD1Rlc3RpbmcgUk9PVCBD
|
||||
QYIJAJ5X1olt05XjMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADgYEAeILv
|
||||
z49+uxmPcfOZzonuOloRcpdvyjiXblYxbzz6ch8GsE7Q886FTZbvwbgLhzdwSVgG
|
||||
G8WHkodDUsymVepdqAamS3f8PdCUk8xIk9mop8LgaB9Ns0/TssxDvMr3sOD2Grb3
|
||||
xyWymTWMcj6uCiEBKtnUp4rPiefcvCRYZ17/hLE=
|
||||
-----END CERTIFICATE-----
|
||||
`)
|
||||
|
||||
// ai.crt
|
||||
testTLSBundleCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIICGjCCAYOgAwIBAgIJAIIu+NOoxxM0MA0GCSqGSIb3DQEBBQUAMDgxCzAJBgNV
|
||||
BAYTAkdPMQ8wDQYDVQQIEwZHb3BoZXIxGDAWBgNVBAoTD1Rlc3RpbmcgUk9PVCBD
|
||||
QTAeFw0xNzAzMDkwMDAzMTRaFw0yNzAzMDcwMDAzMTRaMFExCzAJBgNVBAYTAkdP
|
||||
MQ8wDQYDVQQIDAZHb3BoZXIxHDAaBgNVBAoME1Rlc3RpbmcgQ2VydGlmaWNhdGUx
|
||||
EzARBgNVBAsMClRlc3RpbmcgSVAwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB
|
||||
AN1hWHeioo/nASvbrjwCQzXCiWiEzGkw353NxsAB54/NqDL3LXNATtiSJu8kJBrm
|
||||
Ah12IFLtWLGXjGjjYlHbQWnOR6awveeXnQZukJyRWh7m/Qlt9Ho0CgZE1U+832ac
|
||||
5GWVldNxW1Lz4I+W9/ehzqe8I80RS6eLEKfUFXGiW+9RAgMBAAGjEzARMA8GA1Ud
|
||||
EQQIMAaHBH8AAAEwDQYJKoZIhvcNAQEFBQADgYEAdF4WQHfVdPCbgv9sxgJjcR1H
|
||||
Hgw9rZ47gO1IiIhzglnLXQ6QuemRiHeYFg4kjcYBk1DJguxzDTGnUwhUXOibAB+S
|
||||
zssmrkdYYvn9aUhjc3XK3tjAoDpsPpeBeTBamuUKDHoH/dNRXxerZ8vu6uPR3Pgs
|
||||
5v/KCV6IAEcvNyOXMPo=
|
||||
-----END CERTIFICATE-----
|
||||
`)
|
||||
|
||||
// ai.key
|
||||
testTLSBundleKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXAIBAAKBgQDdYVh3oqKP5wEr2648AkM1wolohMxpMN+dzcbAAeePzagy9y1z
|
||||
QE7YkibvJCQa5gIddiBS7Vixl4xo42JR20FpzkemsL3nl50GbpCckVoe5v0JbfR6
|
||||
NAoGRNVPvN9mnORllZXTcVtS8+CPlvf3oc6nvCPNEUunixCn1BVxolvvUQIDAQAB
|
||||
AoGBAMISrcirddGrlLZLLrKC1ULS2T0cdkqdQtwHYn4+7S5+/z42vMx1iumHLsSk
|
||||
rVY7X41OWkX4trFxhvEIrc/O48bo2zw78P7flTxHy14uxXnllU8cLThE29SlUU7j
|
||||
AVBNxJZMsXMlS/DowwD4CjFe+x4Pu9wZcReF2Z9ntzMpySABAkEA+iWoJCPE2JpS
|
||||
y78q3HYYgpNY3gF3JqQ0SI/zTNkb3YyEIUffEYq0Y9pK13HjKtdsSuX4osTIhQkS
|
||||
+UgRp6tCAQJBAOKPYTfQ2FX8ijgUpHZRuEAVaxASAS0UATiLgzXxLvOh/VC2at5x
|
||||
wjOX6sD65pPz/0D8Qj52Cq6Q1TQ+377SDVECQAIy0od+yPweXxvrUjUd1JlRMjbB
|
||||
TIrKZqs8mKbUQapw0bh5KTy+O1elU4MRPS3jNtBxtP25PQnuSnxmZcFTgAECQFzg
|
||||
DiiFcsn9FuRagfkHExMiNJuH5feGxeFaP9WzI144v9GAllrOI6Bm3JNzx2ZLlg4b
|
||||
20Qju8lIEj6yr6JYFaECQHM1VSojGRKpOl9Ox/R4yYSA9RV5Gyn00/aJNxVYyPD5
|
||||
i3acL2joQm2kLD/LO8paJ4+iQdRXCOMMIpjxSNjGQjQ=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`)
|
||||
)
|
||||
|
||||
+4
-5
@@ -23,7 +23,7 @@ additional config if the AWS_SDK_LOAD_CONFIG environment variable is set.
|
||||
Alternatively you can explicitly create a Session with shared config enabled.
|
||||
To do this you can use NewSessionWithOptions to configure how the Session will
|
||||
be created. Using the NewSessionWithOptions with SharedConfigState set to
|
||||
SharedConfigEnabled will create the session as if the AWS_SDK_LOAD_CONFIG
|
||||
SharedConfigEnable will create the session as if the AWS_SDK_LOAD_CONFIG
|
||||
environment variable was set.
|
||||
|
||||
Creating Sessions
|
||||
@@ -84,7 +84,7 @@ override the shared config state (AWS_SDK_LOAD_CONFIG).
|
||||
|
||||
// Force enable Shared Config support
|
||||
sess := session.Must(session.NewSessionWithOptions(session.Options{
|
||||
SharedConfigState: SharedConfigEnable,
|
||||
SharedConfigState: session.SharedConfigEnable,
|
||||
}))
|
||||
|
||||
Adding Handlers
|
||||
@@ -124,9 +124,8 @@ file (~/.aws/config) and shared credentials file (~/.aws/credentials). Both
|
||||
files have the same format.
|
||||
|
||||
If both config files are present the configuration from both files will be
|
||||
read. The Session will be created from configuration values from the shared
|
||||
credentials file (~/.aws/credentials) over those in the shared credentials
|
||||
file (~/.aws/config).
|
||||
read. The Session will be created from configuration values from the shared
|
||||
credentials file (~/.aws/credentials) over those in the shared config file (~/.aws/config).
|
||||
|
||||
Credentials are the values the SDK should use for authenticating requests with
|
||||
AWS Services. They arfrom a configuration file will need to include both
|
||||
|
||||
+13
-30
@@ -2,12 +2,14 @@ package session
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
)
|
||||
|
||||
// EnvProviderName provides a name of the provider when config is loaded from environment.
|
||||
const EnvProviderName = "EnvConfigCredentials"
|
||||
|
||||
// envConfig is a collection of environment values the SDK will read
|
||||
// setup config from. All environment values are optional. But some values
|
||||
// such as credentials require multiple values to be complete or the values
|
||||
@@ -77,7 +79,7 @@ type envConfig struct {
|
||||
SharedConfigFile string
|
||||
|
||||
// Sets the path to a custom Credentials Authroity (CA) Bundle PEM file
|
||||
// that the SDK will use instead of the the system's root CA bundle.
|
||||
// that the SDK will use instead of the system's root CA bundle.
|
||||
// Only use this if you want to configure the SDK to use a custom set
|
||||
// of CAs.
|
||||
//
|
||||
@@ -116,6 +118,12 @@ var (
|
||||
"AWS_PROFILE",
|
||||
"AWS_DEFAULT_PROFILE", // Only read if AWS_SDK_LOAD_CONFIG is also set
|
||||
}
|
||||
sharedCredsFileEnvKey = []string{
|
||||
"AWS_SHARED_CREDENTIALS_FILE",
|
||||
}
|
||||
sharedConfigFileEnvKey = []string{
|
||||
"AWS_CONFIG_FILE",
|
||||
}
|
||||
)
|
||||
|
||||
// loadEnvConfig retrieves the SDK's environment configuration.
|
||||
@@ -152,7 +160,7 @@ func envConfigLoad(enableSharedConfig bool) envConfig {
|
||||
if len(cfg.Creds.AccessKeyID) == 0 || len(cfg.Creds.SecretAccessKey) == 0 {
|
||||
cfg.Creds = credentials.Value{}
|
||||
} else {
|
||||
cfg.Creds.ProviderName = "EnvConfigCredentials"
|
||||
cfg.Creds.ProviderName = EnvProviderName
|
||||
}
|
||||
|
||||
regionKeys := regionEnvKeys
|
||||
@@ -165,8 +173,8 @@ func envConfigLoad(enableSharedConfig bool) envConfig {
|
||||
setFromEnvVal(&cfg.Region, regionKeys)
|
||||
setFromEnvVal(&cfg.Profile, profileKeys)
|
||||
|
||||
cfg.SharedCredentialsFile = sharedCredentialsFilename()
|
||||
cfg.SharedConfigFile = sharedConfigFilename()
|
||||
setFromEnvVal(&cfg.SharedCredentialsFile, sharedCredsFileEnvKey)
|
||||
setFromEnvVal(&cfg.SharedConfigFile, sharedConfigFileEnvKey)
|
||||
|
||||
cfg.CustomCABundle = os.Getenv("AWS_CA_BUNDLE")
|
||||
|
||||
@@ -181,28 +189,3 @@ func setFromEnvVal(dst *string, keys []string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sharedCredentialsFilename() string {
|
||||
if name := os.Getenv("AWS_SHARED_CREDENTIALS_FILE"); len(name) > 0 {
|
||||
return name
|
||||
}
|
||||
|
||||
return filepath.Join(userHomeDir(), ".aws", "credentials")
|
||||
}
|
||||
|
||||
func sharedConfigFilename() string {
|
||||
if name := os.Getenv("AWS_CONFIG_FILE"); len(name) > 0 {
|
||||
return name
|
||||
}
|
||||
|
||||
return filepath.Join(userHomeDir(), ".aws", "config")
|
||||
}
|
||||
|
||||
func userHomeDir() string {
|
||||
homeDir := os.Getenv("HOME") // *nix
|
||||
if len(homeDir) == 0 { // windows
|
||||
homeDir = os.Getenv("USERPROFILE")
|
||||
}
|
||||
|
||||
return homeDir
|
||||
}
|
||||
|
||||
+70
-82
@@ -2,17 +2,16 @@ package session
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/aws/aws-sdk-go/awstesting"
|
||||
)
|
||||
|
||||
func TestLoadEnvConfig_Creds(t *testing.T) {
|
||||
env := stashEnv()
|
||||
defer popEnv(env)
|
||||
env := awstesting.StashEnv()
|
||||
defer awstesting.PopEnv(env)
|
||||
|
||||
cases := []struct {
|
||||
Env map[string]string
|
||||
@@ -83,26 +82,30 @@ func TestLoadEnvConfig_Creds(t *testing.T) {
|
||||
}
|
||||
|
||||
cfg := loadEnvConfig()
|
||||
assert.Equal(t, c.Val, cfg.Creds)
|
||||
if !reflect.DeepEqual(c.Val, cfg.Creds) {
|
||||
t.Errorf("expect credentials to match.\n%s",
|
||||
awstesting.SprintExpectActual(c.Val, cfg.Creds))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadEnvConfig(t *testing.T) {
|
||||
env := stashEnv()
|
||||
defer popEnv(env)
|
||||
env := awstesting.StashEnv()
|
||||
defer awstesting.PopEnv(env)
|
||||
|
||||
cases := []struct {
|
||||
Env map[string]string
|
||||
Region, Profile string
|
||||
CustomCABundle string
|
||||
UseSharedConfigCall bool
|
||||
Config envConfig
|
||||
}{
|
||||
{
|
||||
Env: map[string]string{
|
||||
"AWS_REGION": "region",
|
||||
"AWS_PROFILE": "profile",
|
||||
},
|
||||
Region: "region", Profile: "profile",
|
||||
Config: envConfig{
|
||||
Region: "region", Profile: "profile",
|
||||
},
|
||||
},
|
||||
{
|
||||
Env: map[string]string{
|
||||
@@ -111,7 +114,9 @@ func TestLoadEnvConfig(t *testing.T) {
|
||||
"AWS_PROFILE": "profile",
|
||||
"AWS_DEFAULT_PROFILE": "default_profile",
|
||||
},
|
||||
Region: "region", Profile: "profile",
|
||||
Config: envConfig{
|
||||
Region: "region", Profile: "profile",
|
||||
},
|
||||
},
|
||||
{
|
||||
Env: map[string]string{
|
||||
@@ -121,7 +126,10 @@ func TestLoadEnvConfig(t *testing.T) {
|
||||
"AWS_DEFAULT_PROFILE": "default_profile",
|
||||
"AWS_SDK_LOAD_CONFIG": "1",
|
||||
},
|
||||
Region: "region", Profile: "profile",
|
||||
Config: envConfig{
|
||||
Region: "region", Profile: "profile",
|
||||
EnableSharedConfig: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Env: map[string]string{
|
||||
@@ -135,14 +143,20 @@ func TestLoadEnvConfig(t *testing.T) {
|
||||
"AWS_DEFAULT_PROFILE": "default_profile",
|
||||
"AWS_SDK_LOAD_CONFIG": "1",
|
||||
},
|
||||
Region: "default_region", Profile: "default_profile",
|
||||
Config: envConfig{
|
||||
Region: "default_region", Profile: "default_profile",
|
||||
EnableSharedConfig: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Env: map[string]string{
|
||||
"AWS_REGION": "region",
|
||||
"AWS_PROFILE": "profile",
|
||||
},
|
||||
Region: "region", Profile: "profile",
|
||||
Config: envConfig{
|
||||
Region: "region", Profile: "profile",
|
||||
EnableSharedConfig: true,
|
||||
},
|
||||
UseSharedConfigCall: true,
|
||||
},
|
||||
{
|
||||
@@ -152,7 +166,10 @@ func TestLoadEnvConfig(t *testing.T) {
|
||||
"AWS_PROFILE": "profile",
|
||||
"AWS_DEFAULT_PROFILE": "default_profile",
|
||||
},
|
||||
Region: "region", Profile: "profile",
|
||||
Config: envConfig{
|
||||
Region: "region", Profile: "profile",
|
||||
EnableSharedConfig: true,
|
||||
},
|
||||
UseSharedConfigCall: true,
|
||||
},
|
||||
{
|
||||
@@ -163,7 +180,10 @@ func TestLoadEnvConfig(t *testing.T) {
|
||||
"AWS_DEFAULT_PROFILE": "default_profile",
|
||||
"AWS_SDK_LOAD_CONFIG": "1",
|
||||
},
|
||||
Region: "region", Profile: "profile",
|
||||
Config: envConfig{
|
||||
Region: "region", Profile: "profile",
|
||||
EnableSharedConfig: true,
|
||||
},
|
||||
UseSharedConfigCall: true,
|
||||
},
|
||||
{
|
||||
@@ -171,7 +191,10 @@ func TestLoadEnvConfig(t *testing.T) {
|
||||
"AWS_DEFAULT_REGION": "default_region",
|
||||
"AWS_DEFAULT_PROFILE": "default_profile",
|
||||
},
|
||||
Region: "default_region", Profile: "default_profile",
|
||||
Config: envConfig{
|
||||
Region: "default_region", Profile: "default_profile",
|
||||
EnableSharedConfig: true,
|
||||
},
|
||||
UseSharedConfigCall: true,
|
||||
},
|
||||
{
|
||||
@@ -180,22 +203,40 @@ func TestLoadEnvConfig(t *testing.T) {
|
||||
"AWS_DEFAULT_PROFILE": "default_profile",
|
||||
"AWS_SDK_LOAD_CONFIG": "1",
|
||||
},
|
||||
Region: "default_region", Profile: "default_profile",
|
||||
Config: envConfig{
|
||||
Region: "default_region", Profile: "default_profile",
|
||||
EnableSharedConfig: true,
|
||||
},
|
||||
UseSharedConfigCall: true,
|
||||
},
|
||||
{
|
||||
Env: map[string]string{
|
||||
"AWS_CA_BUNDLE": "custom_ca_bundle",
|
||||
},
|
||||
CustomCABundle: "custom_ca_bundle",
|
||||
Config: envConfig{
|
||||
CustomCABundle: "custom_ca_bundle",
|
||||
},
|
||||
},
|
||||
{
|
||||
Env: map[string]string{
|
||||
"AWS_CA_BUNDLE": "custom_ca_bundle",
|
||||
},
|
||||
CustomCABundle: "custom_ca_bundle",
|
||||
Config: envConfig{
|
||||
CustomCABundle: "custom_ca_bundle",
|
||||
EnableSharedConfig: true,
|
||||
},
|
||||
UseSharedConfigCall: true,
|
||||
},
|
||||
{
|
||||
Env: map[string]string{
|
||||
"AWS_SHARED_CREDENTIALS_FILE": "/path/to/credentials/file",
|
||||
"AWS_CONFIG_FILE": "/path/to/config/file",
|
||||
},
|
||||
Config: envConfig{
|
||||
SharedCredentialsFile: "/path/to/credentials/file",
|
||||
SharedConfigFile: "/path/to/config/file",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
@@ -212,55 +253,16 @@ func TestLoadEnvConfig(t *testing.T) {
|
||||
cfg = loadEnvConfig()
|
||||
}
|
||||
|
||||
assert.Equal(t, c.Region, cfg.Region)
|
||||
assert.Equal(t, c.Profile, cfg.Profile)
|
||||
assert.Equal(t, c.CustomCABundle, cfg.CustomCABundle)
|
||||
if !reflect.DeepEqual(c.Config, cfg) {
|
||||
t.Errorf("expect config to match.\n%s",
|
||||
awstesting.SprintExpectActual(c.Config, cfg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSharedCredsFilename(t *testing.T) {
|
||||
env := stashEnv()
|
||||
defer popEnv(env)
|
||||
|
||||
os.Setenv("USERPROFILE", "profile_dir")
|
||||
expect := filepath.Join("profile_dir", ".aws", "credentials")
|
||||
name := sharedCredentialsFilename()
|
||||
assert.Equal(t, expect, name)
|
||||
|
||||
os.Setenv("HOME", "home_dir")
|
||||
expect = filepath.Join("home_dir", ".aws", "credentials")
|
||||
name = sharedCredentialsFilename()
|
||||
assert.Equal(t, expect, name)
|
||||
|
||||
expect = filepath.Join("path/to/credentials/file")
|
||||
os.Setenv("AWS_SHARED_CREDENTIALS_FILE", expect)
|
||||
name = sharedCredentialsFilename()
|
||||
assert.Equal(t, expect, name)
|
||||
}
|
||||
|
||||
func TestSharedConfigFilename(t *testing.T) {
|
||||
env := stashEnv()
|
||||
defer popEnv(env)
|
||||
|
||||
os.Setenv("USERPROFILE", "profile_dir")
|
||||
expect := filepath.Join("profile_dir", ".aws", "config")
|
||||
name := sharedConfigFilename()
|
||||
assert.Equal(t, expect, name)
|
||||
|
||||
os.Setenv("HOME", "home_dir")
|
||||
expect = filepath.Join("home_dir", ".aws", "config")
|
||||
name = sharedConfigFilename()
|
||||
assert.Equal(t, expect, name)
|
||||
|
||||
expect = filepath.Join("path/to/config/file")
|
||||
os.Setenv("AWS_CONFIG_FILE", expect)
|
||||
name = sharedConfigFilename()
|
||||
assert.Equal(t, expect, name)
|
||||
}
|
||||
|
||||
func TestSetEnvValue(t *testing.T) {
|
||||
env := stashEnv()
|
||||
defer popEnv(env)
|
||||
env := awstesting.StashEnv()
|
||||
defer awstesting.PopEnv(env)
|
||||
|
||||
os.Setenv("empty_key", "")
|
||||
os.Setenv("second_key", "2")
|
||||
@@ -271,21 +273,7 @@ func TestSetEnvValue(t *testing.T) {
|
||||
"empty_key", "first_key", "second_key", "third_key",
|
||||
})
|
||||
|
||||
assert.Equal(t, "2", dst)
|
||||
}
|
||||
|
||||
func stashEnv() []string {
|
||||
env := os.Environ()
|
||||
os.Clearenv()
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
func popEnv(env []string) {
|
||||
os.Clearenv()
|
||||
|
||||
for _, e := range env {
|
||||
p := strings.SplitN(e, "=", 2)
|
||||
os.Setenv(p[0], p[1])
|
||||
if e, a := "2", dst; e != a {
|
||||
t.Errorf("expect %s value from environment, got %s", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
+23
-7
@@ -155,6 +155,10 @@ type Options struct {
|
||||
// and enable or disable the shared config functionality.
|
||||
SharedConfigState SharedConfigState
|
||||
|
||||
// Ordered list of files the session will load configuration from.
|
||||
// It will override environment variable AWS_SHARED_CREDENTIALS_FILE, AWS_CONFIG_FILE.
|
||||
SharedConfigFiles []string
|
||||
|
||||
// When the SDK's shared config is configured to assume a role with MFA
|
||||
// this option is required in order to provide the mechanism that will
|
||||
// retrieve the MFA token. There is no default value for this field. If
|
||||
@@ -218,7 +222,7 @@ type Options struct {
|
||||
//
|
||||
// // Force enable Shared Config support
|
||||
// sess := session.Must(session.NewSessionWithOptions(session.Options{
|
||||
// SharedConfigState: SharedConfigEnable,
|
||||
// SharedConfigState: session.SharedConfigEnable,
|
||||
// }))
|
||||
func NewSessionWithOptions(opts Options) (*Session, error) {
|
||||
var envCfg envConfig
|
||||
@@ -239,6 +243,13 @@ func NewSessionWithOptions(opts Options) (*Session, error) {
|
||||
envCfg.EnableSharedConfig = true
|
||||
}
|
||||
|
||||
if len(envCfg.SharedCredentialsFile) == 0 {
|
||||
envCfg.SharedCredentialsFile = defaults.SharedCredentialsFilename()
|
||||
}
|
||||
if len(envCfg.SharedConfigFile) == 0 {
|
||||
envCfg.SharedConfigFile = defaults.SharedConfigFilename()
|
||||
}
|
||||
|
||||
// Only use AWS_CA_BUNDLE if session option is not provided.
|
||||
if len(envCfg.CustomCABundle) != 0 && opts.CustomCABundle == nil {
|
||||
f, err := os.Open(envCfg.CustomCABundle)
|
||||
@@ -304,13 +315,18 @@ func newSession(opts Options, envCfg envConfig, cfgs ...*aws.Config) (*Session,
|
||||
userCfg := &aws.Config{}
|
||||
userCfg.MergeIn(cfgs...)
|
||||
|
||||
// Order config files will be loaded in with later files overwriting
|
||||
// Ordered config files will be loaded in with later files overwriting
|
||||
// previous config file values.
|
||||
cfgFiles := []string{envCfg.SharedConfigFile, envCfg.SharedCredentialsFile}
|
||||
if !envCfg.EnableSharedConfig {
|
||||
// The shared config file (~/.aws/config) is only loaded if instructed
|
||||
// to load via the envConfig.EnableSharedConfig (AWS_SDK_LOAD_CONFIG).
|
||||
cfgFiles = cfgFiles[1:]
|
||||
var cfgFiles []string
|
||||
if opts.SharedConfigFiles != nil {
|
||||
cfgFiles = opts.SharedConfigFiles
|
||||
} else {
|
||||
cfgFiles = []string{envCfg.SharedConfigFile, envCfg.SharedCredentialsFile}
|
||||
if !envCfg.EnableSharedConfig {
|
||||
// The shared config file (~/.aws/config) is only loaded if instructed
|
||||
// to load via the envConfig.EnableSharedConfig (AWS_SDK_LOAD_CONFIG).
|
||||
cfgFiles = cfgFiles[1:]
|
||||
}
|
||||
}
|
||||
|
||||
// Load additional config from file(s)
|
||||
|
||||
+39
-15
@@ -14,12 +14,13 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/defaults"
|
||||
"github.com/aws/aws-sdk-go/awstesting"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
)
|
||||
|
||||
func TestNewDefaultSession(t *testing.T) {
|
||||
oldEnv := initSessionTestEnv()
|
||||
defer popEnv(oldEnv)
|
||||
defer awstesting.PopEnv(oldEnv)
|
||||
|
||||
s := New(&aws.Config{Region: aws.String("region")})
|
||||
|
||||
@@ -31,7 +32,7 @@ func TestNewDefaultSession(t *testing.T) {
|
||||
|
||||
func TestNew_WithCustomCreds(t *testing.T) {
|
||||
oldEnv := initSessionTestEnv()
|
||||
defer popEnv(oldEnv)
|
||||
defer awstesting.PopEnv(oldEnv)
|
||||
|
||||
customCreds := credentials.NewStaticCredentials("AKID", "SECRET", "TOKEN")
|
||||
s := New(&aws.Config{Credentials: customCreds})
|
||||
@@ -49,7 +50,7 @@ func (w mockLogger) Log(args ...interface{}) {
|
||||
|
||||
func TestNew_WithSessionLoadError(t *testing.T) {
|
||||
oldEnv := initSessionTestEnv()
|
||||
defer popEnv(oldEnv)
|
||||
defer awstesting.PopEnv(oldEnv)
|
||||
|
||||
os.Setenv("AWS_SDK_LOAD_CONFIG", "1")
|
||||
os.Setenv("AWS_CONFIG_FILE", testConfigFilename)
|
||||
@@ -72,7 +73,7 @@ func TestNew_WithSessionLoadError(t *testing.T) {
|
||||
|
||||
func TestSessionCopy(t *testing.T) {
|
||||
oldEnv := initSessionTestEnv()
|
||||
defer popEnv(oldEnv)
|
||||
defer awstesting.PopEnv(oldEnv)
|
||||
|
||||
os.Setenv("AWS_REGION", "orig_region")
|
||||
|
||||
@@ -100,7 +101,7 @@ func TestSessionClientConfig(t *testing.T) {
|
||||
|
||||
func TestNewSession_NoCredentials(t *testing.T) {
|
||||
oldEnv := initSessionTestEnv()
|
||||
defer popEnv(oldEnv)
|
||||
defer awstesting.PopEnv(oldEnv)
|
||||
|
||||
s, err := NewSession()
|
||||
assert.NoError(t, err)
|
||||
@@ -111,7 +112,7 @@ func TestNewSession_NoCredentials(t *testing.T) {
|
||||
|
||||
func TestNewSessionWithOptions_OverrideProfile(t *testing.T) {
|
||||
oldEnv := initSessionTestEnv()
|
||||
defer popEnv(oldEnv)
|
||||
defer awstesting.PopEnv(oldEnv)
|
||||
|
||||
os.Setenv("AWS_SDK_LOAD_CONFIG", "1")
|
||||
os.Setenv("AWS_SHARED_CREDENTIALS_FILE", testConfigFilename)
|
||||
@@ -134,7 +135,7 @@ func TestNewSessionWithOptions_OverrideProfile(t *testing.T) {
|
||||
|
||||
func TestNewSessionWithOptions_OverrideSharedConfigEnable(t *testing.T) {
|
||||
oldEnv := initSessionTestEnv()
|
||||
defer popEnv(oldEnv)
|
||||
defer awstesting.PopEnv(oldEnv)
|
||||
|
||||
os.Setenv("AWS_SDK_LOAD_CONFIG", "0")
|
||||
os.Setenv("AWS_SHARED_CREDENTIALS_FILE", testConfigFilename)
|
||||
@@ -157,7 +158,7 @@ func TestNewSessionWithOptions_OverrideSharedConfigEnable(t *testing.T) {
|
||||
|
||||
func TestNewSessionWithOptions_OverrideSharedConfigDisable(t *testing.T) {
|
||||
oldEnv := initSessionTestEnv()
|
||||
defer popEnv(oldEnv)
|
||||
defer awstesting.PopEnv(oldEnv)
|
||||
|
||||
os.Setenv("AWS_SDK_LOAD_CONFIG", "1")
|
||||
os.Setenv("AWS_SHARED_CREDENTIALS_FILE", testConfigFilename)
|
||||
@@ -178,6 +179,29 @@ func TestNewSessionWithOptions_OverrideSharedConfigDisable(t *testing.T) {
|
||||
assert.Contains(t, creds.ProviderName, "SharedConfigCredentials")
|
||||
}
|
||||
|
||||
func TestNewSessionWithOptions_OverrideSharedConfigFiles(t *testing.T) {
|
||||
oldEnv := initSessionTestEnv()
|
||||
defer awstesting.PopEnv(oldEnv)
|
||||
|
||||
os.Setenv("AWS_SDK_LOAD_CONFIG", "1")
|
||||
os.Setenv("AWS_SHARED_CREDENTIALS_FILE", testConfigFilename)
|
||||
os.Setenv("AWS_PROFILE", "config_file_load_order")
|
||||
|
||||
s, err := NewSessionWithOptions(Options{
|
||||
SharedConfigFiles: []string{testConfigOtherFilename},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "shared_config_other_region", *s.Config.Region)
|
||||
|
||||
creds, err := s.Config.Credentials.Get()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "shared_config_other_akid", creds.AccessKeyID)
|
||||
assert.Equal(t, "shared_config_other_secret", creds.SecretAccessKey)
|
||||
assert.Empty(t, creds.SessionToken)
|
||||
assert.Contains(t, creds.ProviderName, "SharedConfigCredentials")
|
||||
}
|
||||
|
||||
func TestNewSessionWithOptions_Overrides(t *testing.T) {
|
||||
cases := []struct {
|
||||
InEnvs map[string]string
|
||||
@@ -235,7 +259,7 @@ func TestNewSessionWithOptions_Overrides(t *testing.T) {
|
||||
|
||||
for _, c := range cases {
|
||||
oldEnv := initSessionTestEnv()
|
||||
defer popEnv(oldEnv)
|
||||
defer awstesting.PopEnv(oldEnv)
|
||||
|
||||
for k, v := range c.InEnvs {
|
||||
os.Setenv(k, v)
|
||||
@@ -279,7 +303,7 @@ const assumeRoleRespMsg = `
|
||||
|
||||
func TestSesisonAssumeRole(t *testing.T) {
|
||||
oldEnv := initSessionTestEnv()
|
||||
defer popEnv(oldEnv)
|
||||
defer awstesting.PopEnv(oldEnv)
|
||||
|
||||
os.Setenv("AWS_REGION", "us-east-1")
|
||||
os.Setenv("AWS_SDK_LOAD_CONFIG", "1")
|
||||
@@ -302,7 +326,7 @@ func TestSesisonAssumeRole(t *testing.T) {
|
||||
|
||||
func TestSessionAssumeRole_WithMFA(t *testing.T) {
|
||||
oldEnv := initSessionTestEnv()
|
||||
defer popEnv(oldEnv)
|
||||
defer awstesting.PopEnv(oldEnv)
|
||||
|
||||
os.Setenv("AWS_REGION", "us-east-1")
|
||||
os.Setenv("AWS_SDK_LOAD_CONFIG", "1")
|
||||
@@ -345,7 +369,7 @@ func TestSessionAssumeRole_WithMFA(t *testing.T) {
|
||||
|
||||
func TestSessionAssumeRole_WithMFA_NoTokenProvider(t *testing.T) {
|
||||
oldEnv := initSessionTestEnv()
|
||||
defer popEnv(oldEnv)
|
||||
defer awstesting.PopEnv(oldEnv)
|
||||
|
||||
os.Setenv("AWS_REGION", "us-east-1")
|
||||
os.Setenv("AWS_SDK_LOAD_CONFIG", "1")
|
||||
@@ -363,7 +387,7 @@ func TestSessionAssumeRole_DisableSharedConfig(t *testing.T) {
|
||||
// Backwards compatibility with Shared config disabled
|
||||
// assume role should not be built into the config.
|
||||
oldEnv := initSessionTestEnv()
|
||||
defer popEnv(oldEnv)
|
||||
defer awstesting.PopEnv(oldEnv)
|
||||
|
||||
os.Setenv("AWS_SDK_LOAD_CONFIG", "0")
|
||||
os.Setenv("AWS_SHARED_CREDENTIALS_FILE", testConfigFilename)
|
||||
@@ -383,7 +407,7 @@ func TestSessionAssumeRole_InvalidSourceProfile(t *testing.T) {
|
||||
// Backwards compatibility with Shared config disabled
|
||||
// assume role should not be built into the config.
|
||||
oldEnv := initSessionTestEnv()
|
||||
defer popEnv(oldEnv)
|
||||
defer awstesting.PopEnv(oldEnv)
|
||||
|
||||
os.Setenv("AWS_SDK_LOAD_CONFIG", "1")
|
||||
os.Setenv("AWS_SHARED_CREDENTIALS_FILE", testConfigFilename)
|
||||
@@ -396,7 +420,7 @@ func TestSessionAssumeRole_InvalidSourceProfile(t *testing.T) {
|
||||
}
|
||||
|
||||
func initSessionTestEnv() (oldEnv []string) {
|
||||
oldEnv = stashEnv()
|
||||
oldEnv = awstesting.StashEnv()
|
||||
os.Setenv("AWS_CONFIG_FILE", "file_not_exists")
|
||||
os.Setenv("AWS_SHARED_CREDENTIALS_FILE", "file_not_exists")
|
||||
|
||||
|
||||
+1
-1
@@ -113,7 +113,7 @@ func loadSharedConfigIniFiles(filenames []string) ([]sharedConfigFile, error) {
|
||||
|
||||
f, err := ini.Load(b)
|
||||
if err != nil {
|
||||
return nil, SharedConfigLoadError{Filename: filename}
|
||||
return nil, SharedConfigLoadError{Filename: filename, Err: err}
|
||||
}
|
||||
|
||||
files = append(files, sharedConfigFile{
|
||||
|
||||
+73
-27
@@ -3,6 +3,8 @@ package v4_test
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -10,7 +12,6 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws/signer/v4"
|
||||
"github.com/aws/aws-sdk-go/awstesting/unit"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var standaloneSignCases = []struct {
|
||||
@@ -40,24 +41,43 @@ func TestPresignHandler(t *testing.T) {
|
||||
req.Time = time.Unix(0, 0)
|
||||
urlstr, err := req.Presign(5 * time.Minute)
|
||||
|
||||
assert.NoError(t, err)
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, got %v", err)
|
||||
}
|
||||
|
||||
expectedHost := "bucket.s3.mock-region.amazonaws.com"
|
||||
expectedDate := "19700101T000000Z"
|
||||
expectedHeaders := "content-disposition;host;x-amz-acl"
|
||||
expectedSig := "2d76a414208c0eac2a23ef9c834db9635ecd5a0fbb447a00ad191f82d854f55b"
|
||||
expectedSig := "a46583256431b09eb45ba4af2e6286d96a9835ed13721023dc8076dfdcb90fcb"
|
||||
expectedCred := "AKID/19700101/mock-region/s3/aws4_request"
|
||||
|
||||
u, _ := url.Parse(urlstr)
|
||||
urlQ := u.Query()
|
||||
assert.Equal(t, expectedHost, u.Host)
|
||||
assert.Equal(t, expectedSig, urlQ.Get("X-Amz-Signature"))
|
||||
assert.Equal(t, expectedCred, urlQ.Get("X-Amz-Credential"))
|
||||
assert.Equal(t, expectedHeaders, urlQ.Get("X-Amz-SignedHeaders"))
|
||||
assert.Equal(t, expectedDate, urlQ.Get("X-Amz-Date"))
|
||||
assert.Equal(t, "300", urlQ.Get("X-Amz-Expires"))
|
||||
if e, a := expectedHost, u.Host; e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
if e, a := expectedSig, urlQ.Get("X-Amz-Signature"); e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
if e, a := expectedCred, urlQ.Get("X-Amz-Credential"); e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
if e, a := expectedHeaders, urlQ.Get("X-Amz-SignedHeaders"); e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
if e, a := expectedDate, urlQ.Get("X-Amz-Date"); e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
if e, a := "300", urlQ.Get("X-Amz-Expires"); e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
if e, a := "UNSIGNED-PAYLOAD", urlQ.Get("X-Amz-Content-Sha256"); e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
|
||||
assert.NotContains(t, urlstr, "+") // + encoded as %20
|
||||
if e, a := "+", urlstr; strings.Contains(a, e) { // + encoded as %20
|
||||
t.Errorf("expect %v not to be in %v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPresignRequest(t *testing.T) {
|
||||
@@ -71,30 +91,50 @@ func TestPresignRequest(t *testing.T) {
|
||||
req.Time = time.Unix(0, 0)
|
||||
urlstr, headers, err := req.PresignRequest(5 * time.Minute)
|
||||
|
||||
assert.NoError(t, err)
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, got %v", err)
|
||||
}
|
||||
|
||||
expectedHost := "bucket.s3.mock-region.amazonaws.com"
|
||||
expectedDate := "19700101T000000Z"
|
||||
expectedHeaders := "content-disposition;host;x-amz-acl;x-amz-content-sha256"
|
||||
expectedSig := "a5b2b500dfbf2eab5b4f55bec3e3752e04536ea1d5c047aa93bc9f1130a72cd2"
|
||||
expectedHeaders := "content-disposition;host;x-amz-acl"
|
||||
expectedSig := "a46583256431b09eb45ba4af2e6286d96a9835ed13721023dc8076dfdcb90fcb"
|
||||
expectedCred := "AKID/19700101/mock-region/s3/aws4_request"
|
||||
expectedHeaderMap := http.Header{
|
||||
"x-amz-acl": []string{"public-read"},
|
||||
"content-disposition": []string{"a+b c$d"},
|
||||
"x-amz-content-sha256": []string{"UNSIGNED-PAYLOAD"},
|
||||
"x-amz-acl": []string{"public-read"},
|
||||
"content-disposition": []string{"a+b c$d"},
|
||||
}
|
||||
|
||||
u, _ := url.Parse(urlstr)
|
||||
urlQ := u.Query()
|
||||
assert.Equal(t, expectedHost, u.Host)
|
||||
assert.Equal(t, expectedSig, urlQ.Get("X-Amz-Signature"))
|
||||
assert.Equal(t, expectedCred, urlQ.Get("X-Amz-Credential"))
|
||||
assert.Equal(t, expectedHeaders, urlQ.Get("X-Amz-SignedHeaders"))
|
||||
assert.Equal(t, expectedDate, urlQ.Get("X-Amz-Date"))
|
||||
assert.Equal(t, expectedHeaderMap, headers)
|
||||
assert.Equal(t, "300", urlQ.Get("X-Amz-Expires"))
|
||||
if e, a := expectedHost, u.Host; e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
if e, a := expectedSig, urlQ.Get("X-Amz-Signature"); e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
if e, a := expectedCred, urlQ.Get("X-Amz-Credential"); e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
if e, a := expectedHeaders, urlQ.Get("X-Amz-SignedHeaders"); e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
if e, a := expectedDate, urlQ.Get("X-Amz-Date"); e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
if e, a := expectedHeaderMap, headers; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
if e, a := "300", urlQ.Get("X-Amz-Expires"); e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
if e, a := "UNSIGNED-PAYLOAD", urlQ.Get("X-Amz-Content-Sha256"); e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
|
||||
assert.NotContains(t, urlstr, "+") // + encoded as %20
|
||||
if e, a := "+", urlstr; strings.Contains(a, e) { // + encoded as %20
|
||||
t.Errorf("expect %v not to be in %v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStandaloneSign_CustomURIEscape(t *testing.T) {
|
||||
@@ -107,14 +147,20 @@ func TestStandaloneSign_CustomURIEscape(t *testing.T) {
|
||||
|
||||
host := "https://subdomain.us-east-1.es.amazonaws.com"
|
||||
req, err := http.NewRequest("GET", host, nil)
|
||||
assert.NoError(t, err)
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, got %v", err)
|
||||
}
|
||||
|
||||
req.URL.Path = `/log-*/_search`
|
||||
req.URL.Opaque = "//subdomain.us-east-1.es.amazonaws.com/log-%2A/_search"
|
||||
|
||||
_, err = signer.Sign(req, nil, "es", "us-east-1", time.Unix(0, 0))
|
||||
assert.NoError(t, err)
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, got %v", err)
|
||||
}
|
||||
|
||||
actual := req.Header.Get("Authorization")
|
||||
assert.Equal(t, expectSig, actual)
|
||||
if e, a := expectSig, actual; e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
+43
-42
@@ -45,7 +45,7 @@
|
||||
// If signing a request intended for HTTP2 server, and you're using Go 1.6.2
|
||||
// through 1.7.4 you should use the URL.RawPath as the pre-escaped form of the
|
||||
// request URL. https://github.com/golang/go/issues/16847 points to a bug in
|
||||
// Go pre 1.8 that failes to make HTTP2 requests using absolute URL in the HTTP
|
||||
// Go pre 1.8 that fails to make HTTP2 requests using absolute URL in the HTTP
|
||||
// message. URL.Opaque generally will force Go to make requests with absolute URL.
|
||||
// URL.RawPath does not do this, but RawPath must be a valid escaping of Path
|
||||
// or url.EscapedPath will ignore the RawPath escaping.
|
||||
@@ -55,7 +55,6 @@
|
||||
package v4
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
@@ -402,7 +401,7 @@ var SignRequestHandler = request.NamedHandler{
|
||||
}
|
||||
|
||||
// SignSDKRequest signs an AWS request with the V4 signature. This
|
||||
// request handler is bested used only with the SDK's built in service client's
|
||||
// request handler should only be used with the SDK's built in service client's
|
||||
// API operation requests.
|
||||
//
|
||||
// This function should not be used on its on its own, but in conjunction with
|
||||
@@ -503,6 +502,8 @@ func (ctx *signingCtx) build(disableHeaderHoisting bool) {
|
||||
ctx.buildTime() // no depends
|
||||
ctx.buildCredentialString() // no depends
|
||||
|
||||
ctx.buildBodyDigest()
|
||||
|
||||
unsignedHeaders := ctx.Request.Header
|
||||
if ctx.isPresign {
|
||||
if !disableHeaderHoisting {
|
||||
@@ -514,7 +515,6 @@ func (ctx *signingCtx) build(disableHeaderHoisting bool) {
|
||||
}
|
||||
}
|
||||
|
||||
ctx.buildBodyDigest()
|
||||
ctx.buildCanonicalHeaders(ignoredHeaders, unsignedHeaders)
|
||||
ctx.buildCanonicalString() // depends on canon headers / signed headers
|
||||
ctx.buildStringToSign() // depends on canon string
|
||||
@@ -604,14 +604,18 @@ func (ctx *signingCtx) buildCanonicalHeaders(r rule, header http.Header) {
|
||||
headerValues := make([]string, len(headers))
|
||||
for i, k := range headers {
|
||||
if k == "host" {
|
||||
headerValues[i] = "host:" + ctx.Request.URL.Host
|
||||
if ctx.Request.Host != "" {
|
||||
headerValues[i] = "host:" + ctx.Request.Host
|
||||
} else {
|
||||
headerValues[i] = "host:" + ctx.Request.URL.Host
|
||||
}
|
||||
} else {
|
||||
headerValues[i] = k + ":" +
|
||||
strings.Join(ctx.SignedHeaderVals[k], ",")
|
||||
}
|
||||
}
|
||||
|
||||
ctx.canonicalHeaders = strings.Join(stripExcessSpaces(headerValues), "\n")
|
||||
stripExcessSpaces(headerValues)
|
||||
ctx.canonicalHeaders = strings.Join(headerValues, "\n")
|
||||
}
|
||||
|
||||
func (ctx *signingCtx) buildCanonicalString() {
|
||||
@@ -713,49 +717,46 @@ func makeSha256Reader(reader io.ReadSeeker) []byte {
|
||||
return hash.Sum(nil)
|
||||
}
|
||||
|
||||
const doubleSpaces = " "
|
||||
const doubleSpace = " "
|
||||
|
||||
var doubleSpaceBytes = []byte(doubleSpaces)
|
||||
// stripExcessSpaces will rewrite the passed in slice's string values to not
|
||||
// contain muliple side-by-side spaces.
|
||||
func stripExcessSpaces(vals []string) {
|
||||
var j, k, l, m, spaces int
|
||||
for i, str := range vals {
|
||||
// Trim trailing spaces
|
||||
for j = len(str) - 1; j >= 0 && str[j] == ' '; j-- {
|
||||
}
|
||||
|
||||
func stripExcessSpaces(headerVals []string) []string {
|
||||
vals := make([]string, len(headerVals))
|
||||
for i, str := range headerVals {
|
||||
// Trim leading and trailing spaces
|
||||
trimmed := strings.TrimSpace(str)
|
||||
// Trim leading spaces
|
||||
for k = 0; k < j && str[k] == ' '; k++ {
|
||||
}
|
||||
str = str[k : j+1]
|
||||
|
||||
idx := strings.Index(trimmed, doubleSpaces)
|
||||
var buf []byte
|
||||
for idx > -1 {
|
||||
// Multiple adjacent spaces found
|
||||
if buf == nil {
|
||||
// first time create the buffer
|
||||
buf = []byte(trimmed)
|
||||
}
|
||||
// Strip multiple spaces.
|
||||
j = strings.Index(str, doubleSpace)
|
||||
if j < 0 {
|
||||
vals[i] = str
|
||||
continue
|
||||
}
|
||||
|
||||
stripToIdx := -1
|
||||
for j := idx + 1; j < len(buf); j++ {
|
||||
if buf[j] != ' ' {
|
||||
buf = append(buf[:idx+1], buf[j:]...)
|
||||
stripToIdx = j
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if stripToIdx >= 0 {
|
||||
idx = bytes.Index(buf[stripToIdx:], doubleSpaceBytes)
|
||||
if idx >= 0 {
|
||||
idx += stripToIdx
|
||||
buf := []byte(str)
|
||||
for k, m, l = j, j, len(buf); k < l; k++ {
|
||||
if buf[k] == ' ' {
|
||||
if spaces == 0 {
|
||||
// First space.
|
||||
buf[m] = buf[k]
|
||||
m++
|
||||
}
|
||||
spaces++
|
||||
} else {
|
||||
idx = -1
|
||||
// End of multiple spaces.
|
||||
spaces = 0
|
||||
buf[m] = buf[k]
|
||||
m++
|
||||
}
|
||||
}
|
||||
|
||||
if buf != nil {
|
||||
vals[i] = string(buf)
|
||||
} else {
|
||||
vals[i] = trimmed
|
||||
}
|
||||
vals[i] = string(buf[:m])
|
||||
}
|
||||
return vals
|
||||
}
|
||||
|
||||
+211
-64
@@ -6,12 +6,11 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
@@ -20,30 +19,44 @@ import (
|
||||
|
||||
func TestStripExcessHeaders(t *testing.T) {
|
||||
vals := []string{
|
||||
"",
|
||||
"123",
|
||||
"1 2 3",
|
||||
"1 2 3 ",
|
||||
" 1 2 3",
|
||||
"1 2 3",
|
||||
"1 23",
|
||||
"1 2 3",
|
||||
"1 2 ",
|
||||
" 1 2 ",
|
||||
"12 3",
|
||||
"12 3 1",
|
||||
"12 3 1",
|
||||
"12 3 1abc123",
|
||||
}
|
||||
|
||||
expected := []string{
|
||||
"",
|
||||
"123",
|
||||
"1 2 3",
|
||||
"1 2 3",
|
||||
"1 2 3",
|
||||
"1 2 3",
|
||||
"1 23",
|
||||
"1 2 3",
|
||||
"1 2",
|
||||
"1 2",
|
||||
"12 3",
|
||||
"12 3 1",
|
||||
"12 3 1",
|
||||
"12 3 1abc123",
|
||||
}
|
||||
|
||||
newVals := stripExcessSpaces(vals)
|
||||
for i := 0; i < len(newVals); i++ {
|
||||
assert.Equal(t, expected[i], newVals[i], "test: %d", i)
|
||||
stripExcessSpaces(vals)
|
||||
for i := 0; i < len(vals); i++ {
|
||||
if e, a := expected[i], vals[i]; e != a {
|
||||
t.Errorf("%d, expect %v, got %v", i, e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,12 +106,24 @@ func TestPresignRequest(t *testing.T) {
|
||||
expectedTarget := "prefix.Operation"
|
||||
|
||||
q := req.URL.Query()
|
||||
assert.Equal(t, expectedSig, q.Get("X-Amz-Signature"))
|
||||
assert.Equal(t, expectedCred, q.Get("X-Amz-Credential"))
|
||||
assert.Equal(t, expectedHeaders, q.Get("X-Amz-SignedHeaders"))
|
||||
assert.Equal(t, expectedDate, q.Get("X-Amz-Date"))
|
||||
assert.Empty(t, q.Get("X-Amz-Meta-Other-Header"))
|
||||
assert.Equal(t, expectedTarget, q.Get("X-Amz-Target"))
|
||||
if e, a := expectedSig, q.Get("X-Amz-Signature"); e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
if e, a := expectedCred, q.Get("X-Amz-Credential"); e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
if e, a := expectedHeaders, q.Get("X-Amz-SignedHeaders"); e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
if e, a := expectedDate, q.Get("X-Amz-Date"); e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
if a := q.Get("X-Amz-Meta-Other-Header"); len(a) != 0 {
|
||||
t.Errorf("expect %v to be empty", a)
|
||||
}
|
||||
if e, a := expectedTarget, q.Get("X-Amz-Target"); e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPresignBodyWithArrayRequest(t *testing.T) {
|
||||
@@ -115,12 +140,24 @@ func TestPresignBodyWithArrayRequest(t *testing.T) {
|
||||
expectedTarget := "prefix.Operation"
|
||||
|
||||
q := req.URL.Query()
|
||||
assert.Equal(t, expectedSig, q.Get("X-Amz-Signature"))
|
||||
assert.Equal(t, expectedCred, q.Get("X-Amz-Credential"))
|
||||
assert.Equal(t, expectedHeaders, q.Get("X-Amz-SignedHeaders"))
|
||||
assert.Equal(t, expectedDate, q.Get("X-Amz-Date"))
|
||||
assert.Empty(t, q.Get("X-Amz-Meta-Other-Header"))
|
||||
assert.Equal(t, expectedTarget, q.Get("X-Amz-Target"))
|
||||
if e, a := expectedSig, q.Get("X-Amz-Signature"); e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
if e, a := expectedCred, q.Get("X-Amz-Credential"); e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
if e, a := expectedHeaders, q.Get("X-Amz-SignedHeaders"); e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
if e, a := expectedDate, q.Get("X-Amz-Date"); e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
if a := q.Get("X-Amz-Meta-Other-Header"); len(a) != 0 {
|
||||
t.Errorf("expect %v to be empty, was not", a)
|
||||
}
|
||||
if e, a := expectedTarget, q.Get("X-Amz-Target"); e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignRequest(t *testing.T) {
|
||||
@@ -132,8 +169,12 @@ func TestSignRequest(t *testing.T) {
|
||||
expectedSig := "AWS4-HMAC-SHA256 Credential=AKID/19700101/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-date;x-amz-meta-other-header;x-amz-meta-other-header_with_underscore;x-amz-security-token;x-amz-target, Signature=ea766cabd2ec977d955a3c2bae1ae54f4515d70752f2207618396f20aa85bd21"
|
||||
|
||||
q := req.Header
|
||||
assert.Equal(t, expectedSig, q.Get("Authorization"))
|
||||
assert.Equal(t, expectedDate, q.Get("X-Amz-Date"))
|
||||
if e, a := expectedSig, q.Get("Authorization"); e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
if e, a := expectedDate, q.Get("X-Amz-Date"); e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignBodyS3(t *testing.T) {
|
||||
@@ -141,7 +182,9 @@ func TestSignBodyS3(t *testing.T) {
|
||||
signer := buildSigner()
|
||||
signer.Sign(req, body, "s3", "us-east-1", time.Now())
|
||||
hash := req.Header.Get("X-Amz-Content-Sha256")
|
||||
assert.Equal(t, "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", hash)
|
||||
if e, a := "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", hash; e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignBodyGlacier(t *testing.T) {
|
||||
@@ -149,7 +192,9 @@ func TestSignBodyGlacier(t *testing.T) {
|
||||
signer := buildSigner()
|
||||
signer.Sign(req, body, "glacier", "us-east-1", time.Now())
|
||||
hash := req.Header.Get("X-Amz-Content-Sha256")
|
||||
assert.Equal(t, "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", hash)
|
||||
if e, a := "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", hash; e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPresignEmptyBodyS3(t *testing.T) {
|
||||
@@ -157,7 +202,9 @@ func TestPresignEmptyBodyS3(t *testing.T) {
|
||||
signer := buildSigner()
|
||||
signer.Presign(req, body, "s3", "us-east-1", 5*time.Minute, time.Now())
|
||||
hash := req.Header.Get("X-Amz-Content-Sha256")
|
||||
assert.Equal(t, "UNSIGNED-PAYLOAD", hash)
|
||||
if e, a := "UNSIGNED-PAYLOAD", hash; e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignPrecomputedBodyChecksum(t *testing.T) {
|
||||
@@ -166,7 +213,9 @@ func TestSignPrecomputedBodyChecksum(t *testing.T) {
|
||||
signer := buildSigner()
|
||||
signer.Sign(req, body, "dynamodb", "us-east-1", time.Now())
|
||||
hash := req.Header.Get("X-Amz-Content-Sha256")
|
||||
assert.Equal(t, "PRECOMPUTED", hash)
|
||||
if e, a := "PRECOMPUTED", hash; e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnonymousCredentials(t *testing.T) {
|
||||
@@ -183,14 +232,26 @@ func TestAnonymousCredentials(t *testing.T) {
|
||||
SignSDKRequest(r)
|
||||
|
||||
urlQ := r.HTTPRequest.URL.Query()
|
||||
assert.Empty(t, urlQ.Get("X-Amz-Signature"))
|
||||
assert.Empty(t, urlQ.Get("X-Amz-Credential"))
|
||||
assert.Empty(t, urlQ.Get("X-Amz-SignedHeaders"))
|
||||
assert.Empty(t, urlQ.Get("X-Amz-Date"))
|
||||
if a := urlQ.Get("X-Amz-Signature"); len(a) != 0 {
|
||||
t.Errorf("expect %v to be empty, was not", a)
|
||||
}
|
||||
if a := urlQ.Get("X-Amz-Credential"); len(a) != 0 {
|
||||
t.Errorf("expect %v to be empty, was not", a)
|
||||
}
|
||||
if a := urlQ.Get("X-Amz-SignedHeaders"); len(a) != 0 {
|
||||
t.Errorf("expect %v to be empty, was not", a)
|
||||
}
|
||||
if a := urlQ.Get("X-Amz-Date"); len(a) != 0 {
|
||||
t.Errorf("expect %v to be empty, was not", a)
|
||||
}
|
||||
|
||||
hQ := r.HTTPRequest.Header
|
||||
assert.Empty(t, hQ.Get("Authorization"))
|
||||
assert.Empty(t, hQ.Get("X-Amz-Date"))
|
||||
if a := hQ.Get("Authorization"); len(a) != 0 {
|
||||
t.Errorf("expect %v to be empty, was not", a)
|
||||
}
|
||||
if a := hQ.Get("X-Amz-Date"); len(a) != 0 {
|
||||
t.Errorf("expect %v to be empty, was not", a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIgnoreResignRequestWithValidCreds(t *testing.T) {
|
||||
@@ -216,7 +277,9 @@ func TestIgnoreResignRequestWithValidCreds(t *testing.T) {
|
||||
// when it is resigned.
|
||||
return time.Now().Add(1 * time.Second)
|
||||
})
|
||||
assert.NotEqual(t, sig, r.HTTPRequest.Header.Get("Authorization"))
|
||||
if e, a := sig, r.HTTPRequest.Header.Get("Authorization"); e == a {
|
||||
t.Errorf("expect %v to be %v, but was not", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIgnorePreResignRequestWithValidCreds(t *testing.T) {
|
||||
@@ -243,7 +306,9 @@ func TestIgnorePreResignRequestWithValidCreds(t *testing.T) {
|
||||
// when it is resigned.
|
||||
return time.Now().Add(1 * time.Second)
|
||||
})
|
||||
assert.NotEqual(t, sig, r.HTTPRequest.URL.Query().Get("X-Amz-Signature"))
|
||||
if e, a := sig, r.HTTPRequest.URL.Query().Get("X-Amz-Signature"); e == a {
|
||||
t.Errorf("expect %v to be %v, but was not", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResignRequestExpiredCreds(t *testing.T) {
|
||||
@@ -267,8 +332,12 @@ func TestResignRequestExpiredCreds(t *testing.T) {
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.NotEmpty(t, origSignedHeaders)
|
||||
assert.NotContains(t, origSignedHeaders, "authorization")
|
||||
if a := origSignedHeaders; len(a) == 0 {
|
||||
t.Errorf("expect not to be empty, but was")
|
||||
}
|
||||
if e, a := origSignedHeaders, "authorization"; strings.Contains(a, e) {
|
||||
t.Errorf("expect %v to not be in %v, but was", e, a)
|
||||
}
|
||||
origSignedAt := r.LastSignedAt
|
||||
|
||||
creds.Expire()
|
||||
@@ -279,7 +348,9 @@ func TestResignRequestExpiredCreds(t *testing.T) {
|
||||
return time.Now().Add(1 * time.Second)
|
||||
})
|
||||
updatedQuerySig := r.HTTPRequest.Header.Get("Authorization")
|
||||
assert.NotEqual(t, querySig, updatedQuerySig)
|
||||
if e, a := querySig, updatedQuerySig; e == a {
|
||||
t.Errorf("expect %v to be %v, was not", e, a)
|
||||
}
|
||||
|
||||
var updatedSignedHeaders string
|
||||
for _, p := range strings.Split(updatedQuerySig, ", ") {
|
||||
@@ -288,9 +359,15 @@ func TestResignRequestExpiredCreds(t *testing.T) {
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.NotEmpty(t, updatedSignedHeaders)
|
||||
assert.NotContains(t, updatedQuerySig, "authorization")
|
||||
assert.NotEqual(t, origSignedAt, r.LastSignedAt)
|
||||
if a := updatedSignedHeaders; len(a) == 0 {
|
||||
t.Errorf("expect not to be empty, but was")
|
||||
}
|
||||
if e, a := updatedQuerySig, "authorization"; strings.Contains(a, e) {
|
||||
t.Errorf("expect %v to not be in %v, but was", e, a)
|
||||
}
|
||||
if e, a := origSignedAt, r.LastSignedAt; e == a {
|
||||
t.Errorf("expect %v to be %v, was not", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreResignRequestExpiredCreds(t *testing.T) {
|
||||
@@ -315,7 +392,9 @@ func TestPreResignRequestExpiredCreds(t *testing.T) {
|
||||
SignSDKRequest(r)
|
||||
querySig := r.HTTPRequest.URL.Query().Get("X-Amz-Signature")
|
||||
signedHeaders := r.HTTPRequest.URL.Query().Get("X-Amz-SignedHeaders")
|
||||
assert.NotEmpty(t, signedHeaders)
|
||||
if a := signedHeaders; len(a) == 0 {
|
||||
t.Errorf("expect not to be empty, but was")
|
||||
}
|
||||
origSignedAt := r.LastSignedAt
|
||||
|
||||
creds.Expire()
|
||||
@@ -324,11 +403,19 @@ func TestPreResignRequestExpiredCreds(t *testing.T) {
|
||||
// Simulate the request occurred 15 minutes in the past
|
||||
return time.Now().Add(-48 * time.Hour)
|
||||
})
|
||||
assert.NotEqual(t, querySig, r.HTTPRequest.URL.Query().Get("X-Amz-Signature"))
|
||||
if e, a := querySig, r.HTTPRequest.URL.Query().Get("X-Amz-Signature"); e == a {
|
||||
t.Errorf("expect %v to be %v, was not", e, a)
|
||||
}
|
||||
resignedHeaders := r.HTTPRequest.URL.Query().Get("X-Amz-SignedHeaders")
|
||||
assert.Equal(t, signedHeaders, resignedHeaders)
|
||||
assert.NotContains(t, signedHeaders, "x-amz-signedHeaders")
|
||||
assert.NotEqual(t, origSignedAt, r.LastSignedAt)
|
||||
if e, a := signedHeaders, resignedHeaders; e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
if e, a := signedHeaders, "x-amz-signedHeaders"; strings.Contains(a, e) {
|
||||
t.Errorf("expect %v to not be in %v, but was", e, a)
|
||||
}
|
||||
if e, a := origSignedAt, r.LastSignedAt; e == a {
|
||||
t.Errorf("expect %v to be %v, was not", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResignRequestExpiredRequest(t *testing.T) {
|
||||
@@ -352,8 +439,12 @@ func TestResignRequestExpiredRequest(t *testing.T) {
|
||||
// Simulate the request occurred 15 minutes in the past
|
||||
return time.Now().Add(15 * time.Minute)
|
||||
})
|
||||
assert.NotEqual(t, querySig, r.HTTPRequest.Header.Get("Authorization"))
|
||||
assert.NotEqual(t, origSignedAt, r.LastSignedAt)
|
||||
if e, a := querySig, r.HTTPRequest.Header.Get("Authorization"); e == a {
|
||||
t.Errorf("expect %v to be %v, was not", e, a)
|
||||
}
|
||||
if e, a := origSignedAt, r.LastSignedAt; e == a {
|
||||
t.Errorf("expect %v to be %v, was not", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignWithRequestBody(t *testing.T) {
|
||||
@@ -365,19 +456,29 @@ func TestSignWithRequestBody(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
b, err := ioutil.ReadAll(r.Body)
|
||||
r.Body.Close()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectBody, b)
|
||||
if err != nil {
|
||||
t.Errorf("expect no error, got %v", err)
|
||||
}
|
||||
if e, a := expectBody, b; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
req, err := http.NewRequest("POST", server.URL, nil)
|
||||
|
||||
_, err = signer.Sign(req, bytes.NewReader(expectBody), "service", "region", time.Now())
|
||||
assert.NoError(t, err)
|
||||
if err != nil {
|
||||
t.Errorf("expect not no error, got %v", err)
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
if err != nil {
|
||||
t.Errorf("expect not no error, got %v", err)
|
||||
}
|
||||
if e, a := http.StatusOK, resp.StatusCode; e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignWithRequestBody_Overwrite(t *testing.T) {
|
||||
@@ -389,8 +490,12 @@ func TestSignWithRequestBody_Overwrite(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
b, err := ioutil.ReadAll(r.Body)
|
||||
r.Body.Close()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(expectBody), len(b))
|
||||
if err != nil {
|
||||
t.Errorf("expect not no error, got %v", err)
|
||||
}
|
||||
if e, a := len(expectBody), len(b); e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
@@ -399,11 +504,17 @@ func TestSignWithRequestBody_Overwrite(t *testing.T) {
|
||||
_, err = signer.Sign(req, nil, "service", "region", time.Now())
|
||||
req.ContentLength = 0
|
||||
|
||||
assert.NoError(t, err)
|
||||
if err != nil {
|
||||
t.Errorf("expect not no error, got %v", err)
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
if err != nil {
|
||||
t.Errorf("expect not no error, got %v", err)
|
||||
}
|
||||
if e, a := http.StatusOK, resp.StatusCode; e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCanonicalRequest(t *testing.T) {
|
||||
@@ -421,7 +532,9 @@ func TestBuildCanonicalRequest(t *testing.T) {
|
||||
|
||||
ctx.buildCanonicalString()
|
||||
expected := "https://example.org/bucket/key-._~,!@#$%^&*()?Foo=z&Foo=o&Foo=m&Foo=a"
|
||||
assert.Equal(t, expected, ctx.Request.URL.String())
|
||||
if e, a := expected, ctx.Request.URL.String(); e != a {
|
||||
t.Errorf("expect %v, got %v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignWithBody_ReplaceRequestBody(t *testing.T) {
|
||||
@@ -463,7 +576,27 @@ func TestSignWithBody_NoReplaceRequestBody(t *testing.T) {
|
||||
}
|
||||
|
||||
if req.Body != origBody {
|
||||
t.Errorf("expeect request body to not be chagned")
|
||||
t.Errorf("expect request body to not be chagned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestHost(t *testing.T) {
|
||||
req, body := buildRequest("dynamodb", "us-east-1", "{}")
|
||||
req.URL.RawQuery = "Foo=z&Foo=o&Foo=m&Foo=a"
|
||||
req.Host = "myhost"
|
||||
ctx := &signingCtx{
|
||||
ServiceName: "dynamodb",
|
||||
Region: "us-east-1",
|
||||
Request: req,
|
||||
Body: body,
|
||||
Query: req.URL.Query(),
|
||||
Time: time.Now(),
|
||||
ExpireTime: 5 * time.Second,
|
||||
}
|
||||
|
||||
ctx.buildCanonicalHeaders(ignoredHeaders, ctx.Request.Header)
|
||||
if !strings.Contains(ctx.canonicalHeaders, "host:"+req.Host) {
|
||||
t.Errorf("canonical host header invalid")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,15 +616,29 @@ func BenchmarkSignRequest(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStripExcessSpaces(b *testing.B) {
|
||||
vals := []string{
|
||||
`AWS4-HMAC-SHA256 Credential=AKIDFAKEIDFAKEID/20160628/us-west-2/s3/aws4_request, SignedHeaders=host;x-amz-date, Signature=1234567890abcdef1234567890abcdef1234567890abcdef`,
|
||||
`123 321 123 321`,
|
||||
` 123 321 123 321 `,
|
||||
}
|
||||
var stripExcessSpaceCases = []string{
|
||||
`AWS4-HMAC-SHA256 Credential=AKIDFAKEIDFAKEID/20160628/us-west-2/s3/aws4_request, SignedHeaders=host;x-amz-date, Signature=1234567890abcdef1234567890abcdef1234567890abcdef`,
|
||||
`123 321 123 321`,
|
||||
` 123 321 123 321 `,
|
||||
` 123 321 123 321 `,
|
||||
"123",
|
||||
"1 2 3",
|
||||
" 1 2 3",
|
||||
"1 2 3",
|
||||
"1 23",
|
||||
"1 2 3",
|
||||
"1 2 ",
|
||||
" 1 2 ",
|
||||
"12 3",
|
||||
"12 3 1",
|
||||
"12 3 1",
|
||||
"12 3 1abc123",
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
func BenchmarkStripExcessSpaces(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
stripExcessSpaces(vals)
|
||||
// Make sure to start with a copy of the cases
|
||||
cases := append([]string{}, stripExcessSpaceCases...)
|
||||
stripExcessSpaces(cases)
|
||||
}
|
||||
}
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// +build go1.8
|
||||
|
||||
package aws
|
||||
|
||||
import "net/url"
|
||||
|
||||
// URLHostname will extract the Hostname without port from the URL value.
|
||||
//
|
||||
// Wrapper of net/url#URL.Hostname for backwards Go version compatibility.
|
||||
func URLHostname(url *url.URL) string {
|
||||
return url.Hostname()
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// +build !go1.8
|
||||
|
||||
package aws
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// URLHostname will extract the Hostname without port from the URL value.
|
||||
//
|
||||
// Copy of Go 1.8's net/url#URL.Hostname functionality.
|
||||
func URLHostname(url *url.URL) string {
|
||||
return stripPort(url.Host)
|
||||
|
||||
}
|
||||
|
||||
// stripPort is copy of Go 1.8 url#URL.Hostname functionality.
|
||||
// https://golang.org/src/net/url/url.go
|
||||
func stripPort(hostport string) string {
|
||||
colon := strings.IndexByte(hostport, ':')
|
||||
if colon == -1 {
|
||||
return hostport
|
||||
}
|
||||
if i := strings.IndexByte(hostport, ']'); i != -1 {
|
||||
return strings.TrimPrefix(hostport[:i], "[")
|
||||
}
|
||||
return hostport[:colon]
|
||||
}
|
||||
+1
-1
@@ -5,4 +5,4 @@ package aws
|
||||
const SDKName = "aws-sdk-go"
|
||||
|
||||
// SDKVersion is the version of this SDK
|
||||
const SDKVersion = "1.8.0"
|
||||
const SDKVersion = "1.12.1"
|
||||
|
||||
+8
-2
@@ -149,8 +149,8 @@ func objectsAreEqual(expected, actual interface{}) bool {
|
||||
// Copied locally to prevent non-test build dependencies on testify
|
||||
func equal(t *testing.T, expected, actual interface{}, msgAndArgs ...interface{}) bool {
|
||||
if !objectsAreEqual(expected, actual) {
|
||||
t.Errorf("Not Equal:\n\t%#v (expected)\n\t%#v (actual), %s",
|
||||
expected, actual, messageFromMsgAndArgs(msgAndArgs))
|
||||
t.Errorf("%s\n%s", messageFromMsgAndArgs(msgAndArgs),
|
||||
SprintExpectActual(expected, actual))
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -188,3 +188,9 @@ func queryValueKeys(v url.Values) []string {
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
|
||||
// SprintExpectActual returns a string for test failure cases when the actual
|
||||
// value is not the same as the expected.
|
||||
func SprintExpectActual(expect, actual interface{}) string {
|
||||
return fmt.Sprintf("expect: %+v\nactual: %+v\n", expect, actual)
|
||||
}
|
||||
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
// +build integration
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
)
|
||||
|
||||
// Searches the buckets of an account that match the prefix, and deletes
|
||||
// those buckets, and all objects within. Before deleting will prompt user
|
||||
// to confirm bucket should be deleted. Positive confirmation is required.
|
||||
//
|
||||
// Usage:
|
||||
// go run deleteBuckets.go <bucketPrefix>
|
||||
func main() {
|
||||
sess := session.Must(session.NewSession())
|
||||
|
||||
svc := s3.New(sess)
|
||||
buckets, err := svc.ListBuckets(&s3.ListBucketsInput{})
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to list buckets, %v", err))
|
||||
}
|
||||
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Fprintln(os.Stderr, "bucket prefix required")
|
||||
os.Exit(1)
|
||||
}
|
||||
bucketPrefix := os.Args[1]
|
||||
|
||||
var failed bool
|
||||
for _, b := range buckets.Buckets {
|
||||
bucket := aws.StringValue(b.Name)
|
||||
|
||||
if !strings.HasPrefix(bucket, bucketPrefix) {
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf("Delete bucket %q? [y/N]: ", bucket)
|
||||
var v string
|
||||
if _, err := fmt.Scanln(&v); err != nil || !(v == "Y" || v == "y") {
|
||||
fmt.Println("\tSkipping")
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Println("\tDeleting")
|
||||
if err := deleteBucket(svc, bucket); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to delete bucket %q, %v", bucket, err)
|
||||
failed = true
|
||||
}
|
||||
}
|
||||
|
||||
if failed {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func deleteBucket(svc *s3.S3, bucket string) error {
|
||||
bucketName := &bucket
|
||||
|
||||
objs, err := svc.ListObjects(&s3.ListObjectsInput{Bucket: bucketName})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list bucket %q objects, %v", bucketName, err)
|
||||
}
|
||||
|
||||
for _, o := range objs.Contents {
|
||||
svc.DeleteObject(&s3.DeleteObjectInput{Bucket: bucketName, Key: o.Key})
|
||||
}
|
||||
|
||||
uploads, err := svc.ListMultipartUploads(&s3.ListMultipartUploadsInput{Bucket: bucketName})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list bucket %q multipart objects, %v", bucketName, err)
|
||||
}
|
||||
|
||||
for _, u := range uploads.Uploads {
|
||||
svc.AbortMultipartUpload(&s3.AbortMultipartUploadInput{
|
||||
Bucket: bucketName,
|
||||
Key: u.Key,
|
||||
UploadId: u.UploadId,
|
||||
})
|
||||
}
|
||||
|
||||
_, err = svc.DeleteBucket(&s3.DeleteBucketInput{Bucket: bucketName})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete bucket %q, %v", bucketName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
package awstesting
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func availableLocalAddr(ip string) (string, error) {
|
||||
l, err := net.Listen("tcp", ip+":0")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
return l.Addr().String(), nil
|
||||
}
|
||||
|
||||
// CreateTLSServer will create the TLS server on an open port using the
|
||||
// certificate and key. The address will be returned that the server is running on.
|
||||
func CreateTLSServer(cert, key string, mux *http.ServeMux) (string, error) {
|
||||
addr, err := availableLocalAddr("127.0.0.1")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if mux == nil {
|
||||
mux = http.NewServeMux()
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {})
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := http.ListenAndServeTLS(addr, cert, key, mux); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
for i := 0; i < 60; i++ {
|
||||
if _, err := http.Get("https://" + addr); err != nil && !strings.Contains(err.Error(), "connection refused") {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
return "https://" + addr, nil
|
||||
}
|
||||
|
||||
// CreateTLSBundleFiles returns the temporary filenames for the certificate
|
||||
// key, and CA PEM content. These files should be deleted when no longer
|
||||
// needed. CleanupTLSBundleFiles can be used for this cleanup.
|
||||
func CreateTLSBundleFiles() (cert, key, ca string, err error) {
|
||||
cert, err = createTmpFile(TLSBundleCert)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
key, err = createTmpFile(TLSBundleKey)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
ca, err = createTmpFile(TLSBundleCA)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
return cert, key, ca, nil
|
||||
}
|
||||
|
||||
// CleanupTLSBundleFiles takes variadic list of files to be deleted.
|
||||
func CleanupTLSBundleFiles(files ...string) error {
|
||||
for _, file := range files {
|
||||
if err := os.Remove(file); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createTmpFile(b []byte) (string, error) {
|
||||
bundleFile, err := ioutil.TempFile(os.TempDir(), "aws-sdk-go-session-test")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, err = bundleFile.Write(b)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer bundleFile.Close()
|
||||
return bundleFile.Name(), nil
|
||||
}
|
||||
|
||||
/* Cert generation steps
|
||||
# Create the CA key
|
||||
openssl genrsa -des3 -out ca.key 1024
|
||||
|
||||
# Create the CA Cert
|
||||
openssl req -new -sha256 -x509 -days 3650 \
|
||||
-subj "/C=GO/ST=Gopher/O=Testing ROOT CA" \
|
||||
-key ca.key -out ca.crt
|
||||
|
||||
# Create config
|
||||
cat > csr_details.txt <<-EOF
|
||||
|
||||
[req]
|
||||
default_bits = 1024
|
||||
prompt = no
|
||||
default_md = sha256
|
||||
req_extensions = SAN
|
||||
distinguished_name = dn
|
||||
|
||||
[ dn ]
|
||||
C=GO
|
||||
ST=Gopher
|
||||
O=Testing Certificate
|
||||
OU=Testing IP
|
||||
|
||||
[SAN]
|
||||
subjectAltName = IP:127.0.0.1
|
||||
EOF
|
||||
|
||||
# Create certificate signing request
|
||||
openssl req -new -sha256 -nodes -newkey rsa:1024 \
|
||||
-config <( cat csr_details.txt ) \
|
||||
-keyout ia.key -out ia.csr
|
||||
|
||||
# Create a signed certificate
|
||||
openssl x509 -req -days 3650 \
|
||||
-CAcreateserial \
|
||||
-extfile <( cat csr_details.txt ) \
|
||||
-extensions SAN \
|
||||
-CA ca.crt -CAkey ca.key -in ia.csr -out ia.crt
|
||||
|
||||
# Verify
|
||||
openssl req -noout -text -in ia.csr
|
||||
openssl x509 -noout -text -in ia.crt
|
||||
*/
|
||||
var (
|
||||
// TLSBundleCA ca.crt
|
||||
TLSBundleCA = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIICiTCCAfKgAwIBAgIJAJ5X1olt05XjMA0GCSqGSIb3DQEBCwUAMDgxCzAJBgNV
|
||||
BAYTAkdPMQ8wDQYDVQQIEwZHb3BoZXIxGDAWBgNVBAoTD1Rlc3RpbmcgUk9PVCBD
|
||||
QTAeFw0xNzAzMDkwMDAyMDZaFw0yNzAzMDcwMDAyMDZaMDgxCzAJBgNVBAYTAkdP
|
||||
MQ8wDQYDVQQIEwZHb3BoZXIxGDAWBgNVBAoTD1Rlc3RpbmcgUk9PVCBDQTCBnzAN
|
||||
BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAw/8DN+t9XQR60jx42rsQ2WE2Dx85rb3n
|
||||
GQxnKZZLNddsT8rDyxJNP18aFalbRbFlyln5fxWxZIblu9Xkm/HRhOpbSimSqo1y
|
||||
uDx21NVZ1YsOvXpHby71jx3gPrrhSc/t/zikhi++6D/C6m1CiIGuiJ0GBiJxtrub
|
||||
UBMXT0QtI2ECAwEAAaOBmjCBlzAdBgNVHQ4EFgQU8XG3X/YHBA6T04kdEkq6+4GV
|
||||
YykwaAYDVR0jBGEwX4AU8XG3X/YHBA6T04kdEkq6+4GVYymhPKQ6MDgxCzAJBgNV
|
||||
BAYTAkdPMQ8wDQYDVQQIEwZHb3BoZXIxGDAWBgNVBAoTD1Rlc3RpbmcgUk9PVCBD
|
||||
QYIJAJ5X1olt05XjMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADgYEAeILv
|
||||
z49+uxmPcfOZzonuOloRcpdvyjiXblYxbzz6ch8GsE7Q886FTZbvwbgLhzdwSVgG
|
||||
G8WHkodDUsymVepdqAamS3f8PdCUk8xIk9mop8LgaB9Ns0/TssxDvMr3sOD2Grb3
|
||||
xyWymTWMcj6uCiEBKtnUp4rPiefcvCRYZ17/hLE=
|
||||
-----END CERTIFICATE-----
|
||||
`)
|
||||
|
||||
// TLSBundleCert ai.crt
|
||||
TLSBundleCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIICGjCCAYOgAwIBAgIJAIIu+NOoxxM0MA0GCSqGSIb3DQEBBQUAMDgxCzAJBgNV
|
||||
BAYTAkdPMQ8wDQYDVQQIEwZHb3BoZXIxGDAWBgNVBAoTD1Rlc3RpbmcgUk9PVCBD
|
||||
QTAeFw0xNzAzMDkwMDAzMTRaFw0yNzAzMDcwMDAzMTRaMFExCzAJBgNVBAYTAkdP
|
||||
MQ8wDQYDVQQIDAZHb3BoZXIxHDAaBgNVBAoME1Rlc3RpbmcgQ2VydGlmaWNhdGUx
|
||||
EzARBgNVBAsMClRlc3RpbmcgSVAwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB
|
||||
AN1hWHeioo/nASvbrjwCQzXCiWiEzGkw353NxsAB54/NqDL3LXNATtiSJu8kJBrm
|
||||
Ah12IFLtWLGXjGjjYlHbQWnOR6awveeXnQZukJyRWh7m/Qlt9Ho0CgZE1U+832ac
|
||||
5GWVldNxW1Lz4I+W9/ehzqe8I80RS6eLEKfUFXGiW+9RAgMBAAGjEzARMA8GA1Ud
|
||||
EQQIMAaHBH8AAAEwDQYJKoZIhvcNAQEFBQADgYEAdF4WQHfVdPCbgv9sxgJjcR1H
|
||||
Hgw9rZ47gO1IiIhzglnLXQ6QuemRiHeYFg4kjcYBk1DJguxzDTGnUwhUXOibAB+S
|
||||
zssmrkdYYvn9aUhjc3XK3tjAoDpsPpeBeTBamuUKDHoH/dNRXxerZ8vu6uPR3Pgs
|
||||
5v/KCV6IAEcvNyOXMPo=
|
||||
-----END CERTIFICATE-----
|
||||
`)
|
||||
|
||||
// TLSBundleKey ai.key
|
||||
TLSBundleKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXAIBAAKBgQDdYVh3oqKP5wEr2648AkM1wolohMxpMN+dzcbAAeePzagy9y1z
|
||||
QE7YkibvJCQa5gIddiBS7Vixl4xo42JR20FpzkemsL3nl50GbpCckVoe5v0JbfR6
|
||||
NAoGRNVPvN9mnORllZXTcVtS8+CPlvf3oc6nvCPNEUunixCn1BVxolvvUQIDAQAB
|
||||
AoGBAMISrcirddGrlLZLLrKC1ULS2T0cdkqdQtwHYn4+7S5+/z42vMx1iumHLsSk
|
||||
rVY7X41OWkX4trFxhvEIrc/O48bo2zw78P7flTxHy14uxXnllU8cLThE29SlUU7j
|
||||
AVBNxJZMsXMlS/DowwD4CjFe+x4Pu9wZcReF2Z9ntzMpySABAkEA+iWoJCPE2JpS
|
||||
y78q3HYYgpNY3gF3JqQ0SI/zTNkb3YyEIUffEYq0Y9pK13HjKtdsSuX4osTIhQkS
|
||||
+UgRp6tCAQJBAOKPYTfQ2FX8ijgUpHZRuEAVaxASAS0UATiLgzXxLvOh/VC2at5x
|
||||
wjOX6sD65pPz/0D8Qj52Cq6Q1TQ+377SDVECQAIy0od+yPweXxvrUjUd1JlRMjbB
|
||||
TIrKZqs8mKbUQapw0bh5KTy+O1elU4MRPS3jNtBxtP25PQnuSnxmZcFTgAECQFzg
|
||||
DiiFcsn9FuRagfkHExMiNJuH5feGxeFaP9WzI144v9GAllrOI6Bm3JNzx2ZLlg4b
|
||||
20Qju8lIEj6yr6JYFaECQHM1VSojGRKpOl9Ox/R4yYSA9RV5Gyn00/aJNxVYyPD5
|
||||
i3acL2joQm2kLD/LO8paJ4+iQdRXCOMMIpjxSNjGQjQ=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`)
|
||||
)
|
||||
Generated
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
// +build integration
|
||||
|
||||
package s3manager
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/awstesting/integration"
|
||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||
)
|
||||
|
||||
func TestGetBucketRegion(t *testing.T) {
|
||||
expectRegion := aws.StringValue(integration.Session.Config.Region)
|
||||
|
||||
ctx := aws.BackgroundContext()
|
||||
region, err := s3manager.GetBucketRegion(ctx, integration.Session,
|
||||
aws.StringValue(bucketName), expectRegion)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, got %v", err)
|
||||
}
|
||||
|
||||
if e, a := expectRegion, region; e != a {
|
||||
t.Errorf("expect %s bucket region, got %s", e, a)
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+85
-38
@@ -1,6 +1,6 @@
|
||||
// +build integration
|
||||
|
||||
// Package s3manager provides
|
||||
// Package s3manager provides integration tests for the service/s3/s3manager package
|
||||
package s3manager
|
||||
|
||||
import (
|
||||
@@ -9,12 +9,13 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/awstesting/integration"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
@@ -26,45 +27,64 @@ var integMD512MB = fmt.Sprintf("%x", md5.Sum(integBuf12MB))
|
||||
var bucketName *string
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
setup()
|
||||
defer teardown() // only called if we panic
|
||||
result := m.Run()
|
||||
teardown()
|
||||
os.Exit(result)
|
||||
if err := setup(); err != nil {
|
||||
panic(fmt.Sprintf("failed to setup integration test, %v", err))
|
||||
}
|
||||
|
||||
var result int
|
||||
|
||||
defer func() {
|
||||
if err := teardown(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "teardown failed, %v", err)
|
||||
}
|
||||
if r := recover(); r != nil {
|
||||
fmt.Println("S3Manager integration test hit a panic,", r)
|
||||
result = 1
|
||||
}
|
||||
os.Exit(result)
|
||||
}()
|
||||
|
||||
result = m.Run()
|
||||
}
|
||||
|
||||
func setup() {
|
||||
// Create a bucket for testing
|
||||
func setup() error {
|
||||
svc := s3.New(integration.Session)
|
||||
|
||||
// Create a bucket for testing
|
||||
bucketName = aws.String(
|
||||
fmt.Sprintf("aws-sdk-go-integration-%d-%s", time.Now().Unix(), integration.UniqueID()))
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
_, err := svc.CreateBucket(&s3.CreateBucketInput{Bucket: bucketName})
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
_, err := svc.CreateBucket(&s3.CreateBucketInput{Bucket: bucketName})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create bucket %q, %v", *bucketName, err)
|
||||
}
|
||||
|
||||
for {
|
||||
_, err := svc.HeadBucket(&s3.HeadBucketInput{Bucket: bucketName})
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
err = svc.WaitUntilBucketExists(&s3.HeadBucketInput{Bucket: bucketName})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to wait for bucket %q to exist, %v", bucketName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete the bucket
|
||||
func teardown() {
|
||||
func teardown() error {
|
||||
svc := s3.New(integration.Session)
|
||||
|
||||
objs, _ := svc.ListObjects(&s3.ListObjectsInput{Bucket: bucketName})
|
||||
objs, err := svc.ListObjects(&s3.ListObjectsInput{Bucket: bucketName})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list bucket %q objects, %v", bucketName, err)
|
||||
}
|
||||
|
||||
for _, o := range objs.Contents {
|
||||
svc.DeleteObject(&s3.DeleteObjectInput{Bucket: bucketName, Key: o.Key})
|
||||
}
|
||||
|
||||
uploads, _ := svc.ListMultipartUploads(&s3.ListMultipartUploadsInput{Bucket: bucketName})
|
||||
uploads, err := svc.ListMultipartUploads(&s3.ListMultipartUploadsInput{Bucket: bucketName})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list bucket %q multipart objects, %v", bucketName, err)
|
||||
}
|
||||
|
||||
for _, u := range uploads.Uploads {
|
||||
svc.AbortMultipartUpload(&s3.AbortMultipartUploadInput{
|
||||
Bucket: bucketName,
|
||||
@@ -73,7 +93,12 @@ func teardown() {
|
||||
})
|
||||
}
|
||||
|
||||
svc.DeleteBucket(&s3.DeleteBucketInput{Bucket: bucketName})
|
||||
_, err = svc.DeleteBucket(&s3.DeleteBucketInput{Bucket: bucketName})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete bucket %q, %v", bucketName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type dlwriter struct {
|
||||
@@ -106,8 +131,12 @@ func validate(t *testing.T, key string, md5value string) {
|
||||
|
||||
w := newDLWriter(1024 * 1024 * 20)
|
||||
n, err := mgr.Download(w, params)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, md5value, fmt.Sprintf("%x", md5.Sum(w.buf[0:n])))
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, got %v", err)
|
||||
}
|
||||
if e, a := md5value, fmt.Sprintf("%x", md5.Sum(w.buf[0:n])); e != a {
|
||||
t.Errorf("expect %s md5 value, got %s", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUploadConcurrently(t *testing.T) {
|
||||
@@ -119,9 +148,17 @@ func TestUploadConcurrently(t *testing.T) {
|
||||
Body: bytes.NewReader(integBuf12MB),
|
||||
})
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, "", out.UploadID)
|
||||
assert.Regexp(t, `^https?://.+/`+key+`$`, out.Location)
|
||||
if err != nil {
|
||||
t.Fatalf("expect no error, got %v", err)
|
||||
}
|
||||
if len(out.UploadID) == 0 {
|
||||
t.Errorf("expect upload ID but was empty")
|
||||
}
|
||||
|
||||
re := regexp.MustCompile(`^https?://.+/` + key + `$`)
|
||||
if e, a := re.String(), out.Location; !re.MatchString(a) {
|
||||
t.Errorf("expect %s to match URL regexp %q, did not", e, a)
|
||||
}
|
||||
|
||||
validate(t, key, integMD512MB)
|
||||
}
|
||||
@@ -149,15 +186,25 @@ func TestUploadFailCleanup(t *testing.T) {
|
||||
Key: &key,
|
||||
Body: bytes.NewReader(integBuf12MB),
|
||||
})
|
||||
assert.Error(t, err)
|
||||
assert.NotContains(t, err.Error(), "MissingRegion")
|
||||
uploadID := ""
|
||||
if merr, ok := err.(s3manager.MultiUploadFailure); ok {
|
||||
uploadID = merr.UploadID()
|
||||
if err == nil {
|
||||
t.Fatalf("expect error, but did not get one")
|
||||
}
|
||||
|
||||
aerr := err.(awserr.Error)
|
||||
if e, a := "MissingRegion", aerr.Code(); strings.Contains(a, e) {
|
||||
t.Errorf("expect %q to not be in error code %q", e, a)
|
||||
}
|
||||
|
||||
uploadID := ""
|
||||
merr := err.(s3manager.MultiUploadFailure)
|
||||
if uploadID = merr.UploadID(); len(uploadID) == 0 {
|
||||
t.Errorf("expect upload ID to not be empty, but was")
|
||||
}
|
||||
assert.NotEmpty(t, uploadID)
|
||||
|
||||
_, err = svc.ListParts(&s3.ListPartsInput{
|
||||
Bucket: bucketName, Key: &key, UploadId: &uploadID})
|
||||
assert.Error(t, err)
|
||||
Bucket: bucketName, Key: &key, UploadId: &uploadID,
|
||||
})
|
||||
if err == nil {
|
||||
t.Errorf("expect error for list parts, but got none")
|
||||
}
|
||||
}
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
FROM ubuntu:12.04
|
||||
FROM golang:1.9
|
||||
|
||||
ADD . /go/src/github.com/aws/aws-sdk-go
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
vim \
|
||||
&& rm -rf /var/list/apt/lists/*
|
||||
|
||||
WORKDIR /go/src/github.com/aws/aws-sdk-go
|
||||
CMD ["make", "unit"]
|
||||
+27
@@ -2,6 +2,8 @@ package awstesting
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/private/util"
|
||||
@@ -92,3 +94,28 @@ func (c *FakeContext) Err() error {
|
||||
func (c *FakeContext) Value(key interface{}) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
// StashEnv stashes the current environment variables and returns an array of
|
||||
// all environment values as key=val strings.
|
||||
func StashEnv() []string {
|
||||
env := os.Environ()
|
||||
os.Clearenv()
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
// PopEnv takes the list of the environment values and injects them into the
|
||||
// process's environment variable data. Clears any existing environment values
|
||||
// that may already exist.
|
||||
func PopEnv(env []string) {
|
||||
os.Clearenv()
|
||||
|
||||
for _, e := range env {
|
||||
p := strings.SplitN(e, "=", 2)
|
||||
k, v := p[0], ""
|
||||
if len(p) > 1 {
|
||||
v = p[1]
|
||||
}
|
||||
os.Setenv(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
+20
-1
@@ -16,6 +16,25 @@
|
||||
<script type="text/javascript" src="/sdk-for-go/api/lib/godoc/jquery.treeview.js"></script>
|
||||
<script type="text/javascript" src="/sdk-for-go/api/lib/godoc/jquery.treeview.edit.js"></script>
|
||||
|
||||
<script src="https://a0.awsstatic.com/s_code/js/1.0/awshome_s_code.js%22></script>"></script>
|
||||
<!-- SiteCatalyst code version: H.25.1. Copyright 1996-2012 Adobe, Inc. All Rights Reserved -->
|
||||
<script><!--
|
||||
/************* DO NOT ALTER ANYTHING BELOW THIS LINE ! **************/
|
||||
var s_code=s.t();if(s_code)document.write(s_code)//--></script>
|
||||
<script language="JavaScript" type="text/javascript"><!--
|
||||
if(navigator.appVersion.indexOf('MSIE')>=0)document.write(unescape('%3C')+'\!-'+'-')
|
||||
//--></script>
|
||||
<noscript>
|
||||
<img src="//amazonwebservices.d2.sc.omtrdc.net/b/ss/awsamazonalldev2/1/H.25.1--NS/0" height="1" width="1" border="0" alt="" />
|
||||
</noscript>
|
||||
<!--/DO NOT REMOVE/-->
|
||||
<!-- End SiteCatalyst code version: H.25.1. -->
|
||||
<script type="text/javascript">
|
||||
window.onload = function() {
|
||||
var a = document.getElementById('feedback-link');
|
||||
a.href = "https://docs.aws.amazon.com/forms/aws-doc-feedback?hidden_service_name=awssdkgo&topic_url=" + document.URL
|
||||
};
|
||||
</script>
|
||||
<script>
|
||||
((window.gitter = {}).chat = {}).options = {
|
||||
room: 'aws/aws-sdk-go'
|
||||
@@ -106,7 +125,7 @@
|
||||
</div>
|
||||
<div id="mobile-nav" onclick="toggleMobileNav()"></div>
|
||||
<div class="top_link">
|
||||
<a class="title" href="https://aws.amazon.com/forms/aws-doc-feedback?hidden_service_name=godoc&hidden_file_name=godoc&hidden_guide_name=godoc&hidden_api_version=HEAD">Feedback</a>
|
||||
<a id="feedback-link" class="title" href="">Feedback</a>
|
||||
</div>
|
||||
<div class="top_link">
|
||||
<a class="title" href="https://aws.amazon.com/blogs/developer/category/go/">Blog</a>
|
||||
|
||||
Generated
Vendored
+6
-6
@@ -146,14 +146,14 @@
|
||||
</div> <!-- #pkg-callgraph -->
|
||||
|
||||
{{with .Consts}}
|
||||
<h2 id="pkg-constants">Constants</h2>
|
||||
<h2 id="pkg-constants">Constants <a class="permalink" href="#pkg-constants">¶</a></h2>
|
||||
{{range .}}
|
||||
<pre>{{node_html $ .Decl true}}</pre>
|
||||
{{comment_html .Doc}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{with .Vars}}
|
||||
<h2 id="pkg-variables">Variables</h2>
|
||||
<h2 id="pkg-variables">Variables <a class="permalink" href="#pkg-variables">¶</a></h2>
|
||||
{{range .}}
|
||||
<pre>{{node_html $ .Decl true}}</pre>
|
||||
{{comment_html .Doc}}
|
||||
@@ -162,7 +162,7 @@
|
||||
{{range .Funcs}}
|
||||
{{/* Name is a string - no need for FSet */}}
|
||||
{{$name_html := html .Name}}
|
||||
<h2 id="{{$name_html}}">func <a href="{{posLink_url $ .Decl}}">{{$name_html}}</a></h2>
|
||||
<h2 id="{{$name_html}}">func <a href="{{posLink_url $ .Decl}}">{{$name_html}}</a> <a class="permalink" href="#{{$name_html}}">¶</a></h2>
|
||||
<pre>{{node_html $ .Decl true}}</pre>
|
||||
{{comment_html .Doc}}
|
||||
{{example_html $ .Name}}
|
||||
@@ -172,7 +172,7 @@
|
||||
{{range .Types}}
|
||||
{{$tname := .Name}}
|
||||
{{$tname_html := html .Name}}
|
||||
<h2 id="{{$tname_html}}">type <a href="{{posLink_url $ .Decl}}">{{$tname_html}}</a></h2>
|
||||
<h2 id="{{$tname_html}}">type <a href="{{posLink_url $ .Decl}}">{{$tname_html}}</a> <a class="permalink" href="#{{$tname_html}}">¶</a></h2>
|
||||
<pre>{{node_html $ .Decl true}}</pre>
|
||||
{{comment_html .Doc}}
|
||||
|
||||
@@ -192,7 +192,7 @@
|
||||
|
||||
{{range .Funcs}}
|
||||
{{$name_html := html .Name}}
|
||||
<h3 id="{{$name_html}}">func <a href="{{posLink_url $ .Decl}}">{{$name_html}}</a></h3>
|
||||
<h3 id="{{$name_html}}">func <a href="{{posLink_url $ .Decl}}">{{$name_html}}</a> <a class="permalink" href="#{{$name_html}}">¶</a></h3>
|
||||
<pre>{{node_html $ .Decl true}}</pre>
|
||||
{{comment_html .Doc}}
|
||||
{{example_html $ .Name}}
|
||||
@@ -201,7 +201,7 @@
|
||||
|
||||
{{range .Methods}}
|
||||
{{$name_html := html .Name}}
|
||||
<h3 id="{{$tname_html}}.{{$name_html}}">func ({{html .Recv}}) <a href="{{posLink_url $ .Decl}}">{{$name_html}}</a></h3>
|
||||
<h3 id="{{$tname_html}}.{{$name_html}}">func ({{html .Recv}}) <a href="{{posLink_url $ .Decl}}">{{$name_html}}</a> <a class="permalink" href="#{{$tname_html}}.{{$name_html}}">¶</a></h3>
|
||||
<pre>{{node_html $ .Decl true}}</pre>
|
||||
{{comment_html .Doc}}
|
||||
{{$name := printf "%s_%s" $tname .Name}}
|
||||
|
||||
Generated
Vendored
+104
-106
@@ -51,7 +51,7 @@
|
||||
</div>
|
||||
{{example_html $ ""}}
|
||||
|
||||
<div id="pkg-operations" class="toggle">
|
||||
<div id="pkg-operations" class="toggleVisible">
|
||||
<div class="collapsed">
|
||||
<h2 class="toggleButton" title="Click to show Index section">Operations ▹</h2>
|
||||
</div>
|
||||
@@ -61,82 +61,84 @@
|
||||
<!-- Table of contents for API; must be named manual-nav to turn off auto nav. -->
|
||||
<div id="manual-nav">
|
||||
<dl>
|
||||
{{range .Funcs}}
|
||||
{{range .Funcs -}}
|
||||
{{$name_html := html .Name}}
|
||||
<dd><a href="#{{$name_html}}">{{node_html $ .Decl false | sanitize}}</a></dd>
|
||||
{{end}}
|
||||
{{range .Types}}
|
||||
{{$tname_html := html .Name}}
|
||||
{{range .Funcs}}
|
||||
{{$name_html := html .Name}}
|
||||
{{end -}}
|
||||
{{range .Types -}}
|
||||
{{$tname_html := html .Name -}}
|
||||
{{range .Funcs -}}
|
||||
{{ $name_html := html .Name -}}
|
||||
<dd><a href="#{{$name_html}}">{{node_html $ .Decl false | sanitize}}</a></dd>
|
||||
{{end}}
|
||||
{{range .Methods}}
|
||||
{{if (and (ne .Name "String") (ne .Name "GoString")) }}
|
||||
{{ if (not (is_setter $.PDoc.Name .)) }}
|
||||
{{ if (not (is_paginator .)) }}
|
||||
{{ if (ne .Name "Validate") }}
|
||||
{{$name_html := html .Name}}
|
||||
<dd><a href="#{{$tname_html}}.{{$name_html}}">{{client_html $ .Decl false | sanitize}}</a></dd>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{if $.Notes}}
|
||||
{{range $marker, $item := $.Notes}}
|
||||
{{end -}}
|
||||
{{range .Methods -}}
|
||||
{{if (and (ne .Name "String") (ne .Name "GoString")) -}}
|
||||
{{ if (not (is_setter $.PDoc.Name .)) -}}
|
||||
{{ if (not (is_paginator .)) -}}
|
||||
{{ if (ne .Name "Validate") -}}
|
||||
{{ $name_html := html .Name -}}
|
||||
{{ if not (is_op_deprecated $.PDoc.Name .Name) -}}
|
||||
<dd> <a href="#{{$tname_html}}.{{$name_html}}">{{.Name}}</a></dd>
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{if $.Notes -}}
|
||||
{{range $marker, $item := $.Notes -}}
|
||||
<dd><a href="#pkg-note-{{$marker}}">{{noteTitle $marker | html}}s</a></dd>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
</dl>
|
||||
</div><!-- #manual-nav -->
|
||||
</div><!-- .expanded -->
|
||||
</div><!-- #pkg-index -->
|
||||
{{ if $.HasPaginators }}
|
||||
<div id="pkg-pagination " class="toggle">
|
||||
{{ if $.HasPaginators -}}
|
||||
<div id="pkg-pagination " class="toggleVisible">
|
||||
<div class="collapsed">
|
||||
<h2 class="toggleButton" title="Click to show Index section">Paginators ▹</h2>
|
||||
</div>
|
||||
<div class="expanded">
|
||||
<h2 class="toggleButton" title="Click to hide Index section">Paginators ▾</h2>
|
||||
{{range .Types -}}
|
||||
{{$tname_html := html .Name}}
|
||||
{{ $tname_html := html .Name -}}
|
||||
{{range .Methods -}}
|
||||
{{if is_paginator . -}}
|
||||
{{$name_html := html .Name}}
|
||||
<dd><a href="#{{$tname_html}}.{{$name_html}}">{{client_html $ .Decl false | sanitize}}</a></dd>
|
||||
{{end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
<div id="pkg-types" class="toggle">
|
||||
{{ end -}}
|
||||
<div id="pkg-types" class="toggleVisible">
|
||||
<div class="collapsed">
|
||||
<h2 class="toggleButton" title="Click to show Index section">Types ▹</h2>
|
||||
</div>
|
||||
<div class="expanded">
|
||||
<h2 class="toggleButton" title="Click to hide Index section">Types ▾</h2>
|
||||
{{if .Vars}}
|
||||
{{ if .Vars -}}
|
||||
<dd><a href="#pkg-variables">Variables</a></dd>
|
||||
{{end}}
|
||||
{{ end -}}
|
||||
<dl>
|
||||
{{range .Types}}
|
||||
{{$tname_html := html .Name}}
|
||||
{{ range .Types -}}
|
||||
{{ $tname_html := html .Name -}}
|
||||
<dt><a href="#{{$tname_html}}">type {{$tname_html}}</a></dd>
|
||||
{{range .Methods}}
|
||||
{{ if is_setter $.PDoc.Name .}}
|
||||
{{$name_html := html .Name}}
|
||||
<dd><a href="#{{$tname_html}}.{{$name_html}}">{{name_only_html $ . | sanitize}}</a></dd>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{ range .Methods -}}
|
||||
{{ if is_setter $.PDoc.Name . -}}
|
||||
{{ $name_html := html .Name -}}
|
||||
<dd><a href="#{{$tname_html}}.{{$name_html}}">{{node_html $ .Decl false | sanitize}}</a></dd>
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if $.Examples}}
|
||||
{{ if $.Examples -}}
|
||||
<div id="pkg-examples" class="toggle">
|
||||
<div class="collapsed">
|
||||
<h2 class="toggleButton" title="Click to show Index section">Examples ▹</h2>
|
||||
@@ -145,14 +147,14 @@
|
||||
<h2 class="toggleButton" title="Click to hide Index section">Examples ▾</h2>
|
||||
<div id="pkg-examples">
|
||||
<dl>
|
||||
{{range $.Examples}}
|
||||
{{ range $.Examples -}}
|
||||
<dd><a class="exampleLink" href="#example_{{.Name}}">{{example_name .Name}}</a></dd>
|
||||
{{end}}
|
||||
{{ end -}}
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{ end -}}
|
||||
|
||||
<div id="pkg-callgraph" class="toggle" style="display: none">
|
||||
<div class="collapsed">
|
||||
@@ -190,94 +192,90 @@
|
||||
<ul style="margin-left: 0.5in" id="callgraph-0" class="treeview"></ul>
|
||||
</div>
|
||||
</div> <!-- #pkg-callgraph -->
|
||||
|
||||
{{with .Consts}}
|
||||
{{ with .Consts -}}
|
||||
<div id="pkg-consts" class="toggle">
|
||||
<div class="collapsed">
|
||||
<h2 class="toggleButton" title="Click to show Index section">Constants ▹</h2>
|
||||
</div>
|
||||
<div class="expanded">
|
||||
<h2 class="toggleButton" title="Click to hide Index section">Constants ▾</h2>
|
||||
{{range .}}
|
||||
{{ range . -}}
|
||||
<pre>{{node_html $ .Decl true}}</pre>
|
||||
{{comment_html .Doc}}
|
||||
{{end}}
|
||||
{{ comment_html .Doc -}}
|
||||
{{ end -}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{with .Vars}}
|
||||
<h2 id="pkg-variables">Variables</h2>
|
||||
{{range .}}
|
||||
{{ end -}}
|
||||
{{ with .Vars -}}
|
||||
<h2 id="pkg-variables">Variables <a class="permalink" href="#pkg-variables">¶</a></h2>
|
||||
{{ range . -}}
|
||||
<pre>{{node_html $ .Decl true}}</pre>
|
||||
{{comment_html .Doc}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{range .Funcs}}
|
||||
{{/* Name is a string - no need for FSet */}}
|
||||
{{$name_html := html .Name}}
|
||||
<h2 id="{{$name_html}}">func <a href="{{posLink_url $ .Decl}}">{{$name_html}}</a></h2>
|
||||
{{ comment_html .Doc -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ range .Funcs -}}
|
||||
{{/* Name is a string - no need for FSet */ -}}
|
||||
{{ $name_html := html .Name -}}
|
||||
<h2 id="{{$name_html}}">func <a href="{{posLink_url $ .Decl}}">{{$name_html}}</a> <a class="permalink" href="#{{$name_html}}">¶</a></h2>
|
||||
<pre>{{node_html $ .Decl true}}</pre>
|
||||
{{comment_html .Doc}}
|
||||
{{example_html $ .Name}}
|
||||
{{callgraph_html $ "" .Name}}
|
||||
|
||||
{{end}}
|
||||
{{range .Types}}
|
||||
{{$tname := .Name}}
|
||||
{{$tname_html := html .Name}}
|
||||
<h2 id="{{$tname_html}}">type <a href="{{posLink_url $ .Decl}}">{{$tname_html}}</a></h2>
|
||||
{{ end -}}
|
||||
{{ range .Types -}}
|
||||
{{$tname := .Name -}}
|
||||
{{$tname_html := html .Name -}}
|
||||
<h2 id="{{$tname_html}}">type <a href="{{posLink_url $ .Decl}}">{{$tname_html}}</a> <a class="permalink" href="#{{$tname_html}}">¶</a></h2>
|
||||
<pre>{{node_html $ .Decl true}}</pre>
|
||||
{{comment_html .Doc}}
|
||||
|
||||
{{range .Consts}}
|
||||
{{ range .Consts -}}
|
||||
<pre>{{node_html $ .Decl true}}</pre>
|
||||
{{ comment_html .Doc -}}
|
||||
{{ end -}}
|
||||
{{ range .Vars -}}
|
||||
<pre>{{node_html $ .Decl true}}</pre>
|
||||
{{comment_html .Doc}}
|
||||
{{end}}
|
||||
|
||||
{{range .Vars}}
|
||||
<pre>{{node_html $ .Decl true}}</pre>
|
||||
{{comment_html .Doc}}
|
||||
{{end}}
|
||||
|
||||
{{example_html $ $tname}}
|
||||
{{implements_html $ $tname}}
|
||||
{{methodset_html $ $tname}}
|
||||
|
||||
{{range .Funcs}}
|
||||
{{$name_html := html .Name}}
|
||||
<h3 id="{{$name_html}}">func <a href="{{posLink_url $ .Decl}}">{{$name_html}}</a></h3>
|
||||
{{ end -}}
|
||||
{{ example_html $ $tname -}}
|
||||
{{ implements_html $ $tname -}}
|
||||
{{ methodset_html $ $tname -}}
|
||||
{{ range .Funcs -}}
|
||||
{{ $name_html := html .Name -}}
|
||||
<h3 id="{{$name_html}}">func <a href="{{posLink_url $ .Decl}}">{{$name_html}}</a> <a class="permalink" href="#{{$name_html}}">¶</a></h3>
|
||||
<pre>{{node_html $ .Decl true}}</pre>
|
||||
{{comment_html .Doc}}
|
||||
{{example_html $ .Name}}
|
||||
{{callgraph_html $ "" .Name}}
|
||||
{{end}}
|
||||
|
||||
{{range .Methods}}
|
||||
{{$name_html := html .Name}}
|
||||
<h3 id="{{$tname_html}}.{{$name_html}}">func ({{html .Recv}}) <a href="{{posLink_url $ .Decl}}">{{$name_html}}</a></h3>
|
||||
{{ end -}}
|
||||
{{ range .Methods -}}
|
||||
{{ $name_html := html .Name -}}
|
||||
{{ if is_op_deprecated $.PDoc.Name .Name -}}
|
||||
<h3 id="{{$tname_html}}.{{$name_html}}">func ({{html .Recv}}) <a href="{{posLink_url $ .Decl}}">{{$name_html}}</a><div class="deprecated">Deprecated</div> <a class="permalink" href="#{{$tname_html}}.{{$name_html}}">¶</a></h3>
|
||||
{{ else }}
|
||||
<h3 id="{{$tname_html}}.{{$name_html}}">func ({{html .Recv}}) <a href="{{posLink_url $ .Decl}}">{{$name_html}}</a> <a class="permalink" href="#{{$tname_html}}.{{$name_html}}">¶</a></h3>
|
||||
{{ end -}}
|
||||
<pre>{{node_html $ .Decl true}}</pre>
|
||||
{{comment_html .Doc}}
|
||||
{{$name := printf "%s_%s" $tname .Name}}
|
||||
{{example_html $ $name}}
|
||||
{{callgraph_html $ .Recv .Name}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{with $.Notes}}
|
||||
{{range $marker, $content := .}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ with $.Notes -}}
|
||||
{{ range $marker, $content := . -}}
|
||||
<h2 id="pkg-note-{{$marker}}">{{noteTitle $marker | html}}s</h2>
|
||||
<ul style="list-style: none; padding: 0;">
|
||||
{{range .}}
|
||||
{{ range . -}}
|
||||
<li><a href="{{posLink_url $ .}}">☞</a> {{html .Body}}</li>
|
||||
{{end}}
|
||||
{{ end -}}
|
||||
</ul>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
|
||||
{{with .PAst}}
|
||||
{{range $filename, $ast := .}}
|
||||
{{ with .PAst -}}
|
||||
{{ range $filename, $ast := . -}}
|
||||
<a href="{{$filename|srcLink|html}}">{{$filename|filename|html}}</a>:<pre>{{node_html $ $ast false}}</pre>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
|
||||
+19
-12
@@ -3,11 +3,18 @@
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
a > img {
|
||||
max-height:70%;
|
||||
float:left;
|
||||
padding-left:22px;
|
||||
padding-top:8px;
|
||||
div.deprecated {
|
||||
background-color: #a39f9f;
|
||||
border-radius: 5px;
|
||||
font-weight: bold;
|
||||
display: inline;
|
||||
padding: 2px;
|
||||
overflow: auto;
|
||||
color: #ffffff;
|
||||
font-family: "Georgia", Times, serif;
|
||||
text-align: center;
|
||||
font-size: small;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.packages {
|
||||
@@ -758,13 +765,6 @@ a.error {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
a > img {
|
||||
max-height:100%;
|
||||
float:left;
|
||||
padding-left:10px;
|
||||
padding-top:2px;
|
||||
}
|
||||
|
||||
div#mobile-nav {
|
||||
display:inline-block;
|
||||
float:right;
|
||||
@@ -1041,3 +1041,10 @@ a.error {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.permalink {
|
||||
display: none;
|
||||
}
|
||||
:hover > .permalink {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
+405
@@ -0,0 +1,405 @@
|
||||
// Package sdk is the official AWS SDK for the Go programming language.
|
||||
//
|
||||
// The AWS SDK for Go provides APIs and utilities that developers can use to
|
||||
// build Go applications that use AWS services, such as Amazon Elastic Compute
|
||||
// Cloud (Amazon EC2) and Amazon Simple Storage Service (Amazon S3).
|
||||
//
|
||||
// The SDK removes the complexity of coding directly against a web service
|
||||
// interface. It hides a lot of the lower-level plumbing, such as authentication,
|
||||
// request retries, and error handling.
|
||||
//
|
||||
// The SDK also includes helpful utilities on top of the AWS APIs that add additional
|
||||
// capabilities and functionality. For example, the Amazon S3 Download and Upload
|
||||
// Manager will automatically split up large objects into multiple parts and
|
||||
// transfer them concurrently.
|
||||
//
|
||||
// See the s3manager package documentation for more information.
|
||||
// https://docs.aws.amazon.com/sdk-for-go/api/service/s3/s3manager/
|
||||
//
|
||||
// Getting More Information
|
||||
//
|
||||
// Checkout the Getting Started Guide and API Reference Docs detailed the SDK's
|
||||
// components and details on each AWS client the SDK supports.
|
||||
//
|
||||
// The Getting Started Guide provides examples and detailed description of how
|
||||
// to get setup with the SDK.
|
||||
// https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/welcome.html
|
||||
//
|
||||
// The API Reference Docs include a detailed breakdown of the SDK's components
|
||||
// such as utilities and AWS clients. Use this as a reference of the Go types
|
||||
// included with the SDK, such as AWS clients, API operations, and API parameters.
|
||||
// https://docs.aws.amazon.com/sdk-for-go/api/
|
||||
//
|
||||
// Overview of SDK's Packages
|
||||
//
|
||||
// The SDK is composed of two main components, SDK core, and service clients.
|
||||
// The SDK core packages are all available under the aws package at the root of
|
||||
// the SDK. Each client for a supported AWS service is available within its own
|
||||
// package under the service folder at the root of the SDK.
|
||||
//
|
||||
// * aws - SDK core, provides common shared types such as Config, Logger,
|
||||
// and utilities to make working with API parameters easier.
|
||||
//
|
||||
// * awserr - Provides the error interface that the SDK will use for all
|
||||
// errors that occur in the SDK's processing. This includes service API
|
||||
// response errors as well. The Error type is made up of a code and message.
|
||||
// Cast the SDK's returned error type to awserr.Error and call the Code
|
||||
// method to compare returned error to specific error codes. See the package's
|
||||
// documentation for additional values that can be extracted such as RequestId.
|
||||
//
|
||||
// * credentials - Provides the types and built in credentials providers
|
||||
// the SDK will use to retrieve AWS credentials to make API requests with.
|
||||
// Nested under this folder are also additional credentials providers such as
|
||||
// stscreds for assuming IAM roles, and ec2rolecreds for EC2 Instance roles.
|
||||
//
|
||||
// * endpoints - Provides the AWS Regions and Endpoints metadata for the SDK.
|
||||
// Use this to lookup AWS service endpoint information such as which services
|
||||
// are in a region, and what regions a service is in. Constants are also provided
|
||||
// for all region identifiers, e.g UsWest2RegionID for "us-west-2".
|
||||
//
|
||||
// * session - Provides initial default configuration, and load
|
||||
// configuration from external sources such as environment and shared
|
||||
// credentials file.
|
||||
//
|
||||
// * request - Provides the API request sending, and retry logic for the SDK.
|
||||
// This package also includes utilities for defining your own request
|
||||
// retryer, and configuring how the SDK processes the request.
|
||||
//
|
||||
// * service - Clients for AWS services. All services supported by the SDK are
|
||||
// available under this folder.
|
||||
//
|
||||
// How to Use the SDK's AWS Service Clients
|
||||
//
|
||||
// The SDK includes the Go types and utilities you can use to make requests to
|
||||
// AWS service APIs. Within the service folder at the root of the SDK you'll find
|
||||
// a package for each AWS service the SDK supports. All service clients follows
|
||||
// a common pattern of creation and usage.
|
||||
//
|
||||
// When creating a client for an AWS service you'll first need to have a Session
|
||||
// value constructed. The Session provides shared configuration that can be shared
|
||||
// between your service clients. When service clients are created you can pass
|
||||
// in additional configuration via the aws.Config type to override configuration
|
||||
// provided by in the Session to create service client instances with custom
|
||||
// configuration.
|
||||
//
|
||||
// Once the service's client is created you can use it to make API requests the
|
||||
// AWS service. These clients are safe to use concurrently.
|
||||
//
|
||||
// Configuring the SDK
|
||||
//
|
||||
// In the AWS SDK for Go, you can configure settings for service clients, such
|
||||
// as the log level and maximum number of retries. Most settings are optional;
|
||||
// however, for each service client, you must specify a region and your credentials.
|
||||
// The SDK uses these values to send requests to the correct AWS region and sign
|
||||
// requests with the correct credentials. You can specify these values as part
|
||||
// of a session or as environment variables.
|
||||
//
|
||||
// See the SDK's configuration guide for more information.
|
||||
// https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html
|
||||
//
|
||||
// See the session package documentation for more information on how to use Session
|
||||
// with the SDK.
|
||||
// https://docs.aws.amazon.com/sdk-for-go/api/aws/session/
|
||||
//
|
||||
// See the Config type in the aws package for more information on configuration
|
||||
// options.
|
||||
// https://docs.aws.amazon.com/sdk-for-go/api/aws/#Config
|
||||
//
|
||||
// Configuring Credentials
|
||||
//
|
||||
// When using the SDK you'll generally need your AWS credentials to authenticate
|
||||
// with AWS services. The SDK supports multiple methods of supporting these
|
||||
// credentials. By default the SDK will source credentials automatically from
|
||||
// its default credential chain. See the session package for more information
|
||||
// on this chain, and how to configure it. The common items in the credential
|
||||
// chain are the following:
|
||||
//
|
||||
// * Environment Credentials - Set of environment variables that are useful
|
||||
// when sub processes are created for specific roles.
|
||||
//
|
||||
// * Shared Credentials file (~/.aws/credentials) - This file stores your
|
||||
// credentials based on a profile name and is useful for local development.
|
||||
//
|
||||
// * EC2 Instance Role Credentials - Use EC2 Instance Role to assign credentials
|
||||
// to application running on an EC2 instance. This removes the need to manage
|
||||
// credential files in production.
|
||||
//
|
||||
// Credentials can be configured in code as well by setting the Config's Credentials
|
||||
// value to a custom provider or using one of the providers included with the
|
||||
// SDK to bypass the default credential chain and use a custom one. This is
|
||||
// helpful when you want to instruct the SDK to only use a specific set of
|
||||
// credentials or providers.
|
||||
//
|
||||
// This example creates a credential provider for assuming an IAM role, "myRoleARN"
|
||||
// and configures the S3 service client to use that role for API requests.
|
||||
//
|
||||
// // Initial credentials loaded from SDK's default credential chain. Such as
|
||||
// // the environment, shared credentials (~/.aws/credentials), or EC2 Instance
|
||||
// // Role. These credentials will be used to to make the STS Assume Role API.
|
||||
// sess := session.Must(session.NewSession())
|
||||
//
|
||||
// // Create the credentials from AssumeRoleProvider to assume the role
|
||||
// // referenced by the "myRoleARN" ARN.
|
||||
// creds := stscreds.NewCredentials(sess, "myRoleArn")
|
||||
//
|
||||
// // Create service client value configured for credentials
|
||||
// // from assumed role.
|
||||
// svc := s3.New(sess, &aws.Config{Credentials: creds})/
|
||||
//
|
||||
// See the credentials package documentation for more information on credential
|
||||
// providers included with the SDK, and how to customize the SDK's usage of
|
||||
// credentials.
|
||||
// https://docs.aws.amazon.com/sdk-for-go/api/aws/credentials
|
||||
//
|
||||
// The SDK has support for the shared configuration file (~/.aws/config). This
|
||||
// support can be enabled by setting the environment variable, "AWS_SDK_LOAD_CONFIG=1",
|
||||
// or enabling the feature in code when creating a Session via the
|
||||
// Option's SharedConfigState parameter.
|
||||
//
|
||||
// sess := session.Must(session.NewSessionWithOptions(session.Options{
|
||||
// SharedConfigState: session.SharedConfigEnable,
|
||||
// }))
|
||||
//
|
||||
// Configuring AWS Region
|
||||
//
|
||||
// In addition to the credentials you'll need to specify the region the SDK
|
||||
// will use to make AWS API requests to. In the SDK you can specify the region
|
||||
// either with an environment variable, or directly in code when a Session or
|
||||
// service client is created. The last value specified in code wins if the region
|
||||
// is specified multiple ways.
|
||||
//
|
||||
// To set the region via the environment variable set the "AWS_REGION" to the
|
||||
// region you want to the SDK to use. Using this method to set the region will
|
||||
// allow you to run your application in multiple regions without needing additional
|
||||
// code in the application to select the region.
|
||||
//
|
||||
// AWS_REGION=us-west-2
|
||||
//
|
||||
// The endpoints package includes constants for all regions the SDK knows. The
|
||||
// values are all suffixed with RegionID. These values are helpful, because they
|
||||
// reduce the need to type the region string manually.
|
||||
//
|
||||
// To set the region on a Session use the aws package's Config struct parameter
|
||||
// Region to the AWS region you want the service clients created from the session to
|
||||
// use. This is helpful when you want to create multiple service clients, and
|
||||
// all of the clients make API requests to the same region.
|
||||
//
|
||||
// sess := session.Must(session.NewSession(&aws.Config{
|
||||
// Region: aws.String(endpoints.UsWest2RegionID),
|
||||
// }))
|
||||
//
|
||||
// See the endpoints package for the AWS Regions and Endpoints metadata.
|
||||
// https://docs.aws.amazon.com/sdk-for-go/api/aws/endpoints/
|
||||
//
|
||||
// In addition to setting the region when creating a Session you can also set
|
||||
// the region on a per service client bases. This overrides the region of a
|
||||
// Session. This is helpful when you want to create service clients in specific
|
||||
// regions different from the Session's region.
|
||||
//
|
||||
// svc := s3.New(sess, &aws.Config{
|
||||
// Region: aws.String(ednpoints.UsWest2RegionID),
|
||||
// })
|
||||
//
|
||||
// See the Config type in the aws package for more information and additional
|
||||
// options such as setting the Endpoint, and other service client configuration options.
|
||||
// https://docs.aws.amazon.com/sdk-for-go/api/aws/#Config
|
||||
//
|
||||
// Making API Requests
|
||||
//
|
||||
// Once the client is created you can make an API request to the service.
|
||||
// Each API method takes a input parameter, and returns the service response
|
||||
// and an error. The SDK provides methods for making the API call in multiple ways.
|
||||
//
|
||||
// In this list we'll use the S3 ListObjects API as an example for the different
|
||||
// ways of making API requests.
|
||||
//
|
||||
// * ListObjects - Base API operation that will make the API request to the service.
|
||||
//
|
||||
// * ListObjectsRequest - API methods suffixed with Request will construct the
|
||||
// API request, but not send it. This is also helpful when you want to get a
|
||||
// presigned URL for a request, and share the presigned URL instead of your
|
||||
// application making the request directly.
|
||||
//
|
||||
// * ListObjectsPages - Same as the base API operation, but uses a callback to
|
||||
// automatically handle pagination of the API's response.
|
||||
//
|
||||
// * ListObjectsWithContext - Same as base API operation, but adds support for
|
||||
// the Context pattern. This is helpful for controlling the canceling of in
|
||||
// flight requests. See the Go standard library context package for more
|
||||
// information. This method also takes request package's Option functional
|
||||
// options as the variadic argument for modifying how the request will be
|
||||
// made, or extracting information from the raw HTTP response.
|
||||
//
|
||||
// * ListObjectsPagesWithContext - same as ListObjectsPages, but adds support for
|
||||
// the Context pattern. Similar to ListObjectsWithContext this method also
|
||||
// takes the request package's Option function option types as the variadic
|
||||
// argument.
|
||||
//
|
||||
// In addition to the API operations the SDK also includes several higher level
|
||||
// methods that abstract checking for and waiting for an AWS resource to be in
|
||||
// a desired state. In this list we'll use WaitUntilBucketExists to demonstrate
|
||||
// the different forms of waiters.
|
||||
//
|
||||
// * WaitUntilBucketExists. - Method to make API request to query an AWS service for
|
||||
// a resource's state. Will return successfully when that state is accomplished.
|
||||
//
|
||||
// * WaitUntilBucketExistsWithContext - Same as WaitUntilBucketExists, but adds
|
||||
// support for the Context pattern. In addition these methods take request
|
||||
// package's WaiterOptions to configure the waiter, and how underlying request
|
||||
// will be made by the SDK.
|
||||
//
|
||||
// The API method will document which error codes the service might return for
|
||||
// the operation. These errors will also be available as const strings prefixed
|
||||
// with "ErrCode" in the service client's package. If there are no errors listed
|
||||
// in the API's SDK documentation you'll need to consult the AWS service's API
|
||||
// documentation for the errors that could be returned.
|
||||
//
|
||||
// ctx := context.Background()
|
||||
//
|
||||
// result, err := svc.GetObjectWithContext(ctx, &s3.GetObjectInput{
|
||||
// Bucket: aws.String("my-bucket"),
|
||||
// Key: aws.String("my-key"),
|
||||
// })
|
||||
// if err != nil {
|
||||
// // Cast err to awserr.Error to handle specific error codes.
|
||||
// aerr, ok := err.(awserr.Error)
|
||||
// if ok && aerr.Code() == s3.ErrCodeNoSuchKey {
|
||||
// // Specific error code handling
|
||||
// }
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// // Make sure to close the body when done with it for S3 GetObject APIs or
|
||||
// // will leak connections.
|
||||
// defer result.Body.Close()
|
||||
//
|
||||
// fmt.Println("Object Size:", aws.StringValue(result.ContentLength))
|
||||
//
|
||||
// API Request Pagination and Resource Waiters
|
||||
//
|
||||
// Pagination helper methods are suffixed with "Pages", and provide the
|
||||
// functionality needed to round trip API page requests. Pagination methods
|
||||
// take a callback function that will be called for each page of the API's response.
|
||||
//
|
||||
// objects := []string{}
|
||||
// err := svc.ListObjectsPagesWithContext(ctx, &s3.ListObjectsInput{
|
||||
// Bucket: aws.String(myBucket),
|
||||
// }, func(p *s3.ListObjectsOutput, lastPage bool) bool {
|
||||
// for _, o := range p.Contents {
|
||||
// objects = append(objects, aws.StringValue(o.Key))
|
||||
// }
|
||||
// return true // continue paging
|
||||
// })
|
||||
// if err != nil {
|
||||
// panic(fmt.Sprintf("failed to list objects for bucket, %s, %v", myBucket, err))
|
||||
// }
|
||||
//
|
||||
// fmt.Println("Objects in bucket:", objects)
|
||||
//
|
||||
// Waiter helper methods provide the functionality to wait for an AWS resource
|
||||
// state. These methods abstract the logic needed to to check the state of an
|
||||
// AWS resource, and wait until that resource is in a desired state. The waiter
|
||||
// will block until the resource is in the state that is desired, an error occurs,
|
||||
// or the waiter times out. If a resource times out the error code returned will
|
||||
// be request.WaiterResourceNotReadyErrorCode.
|
||||
//
|
||||
// err := svc.WaitUntilBucketExistsWithContext(ctx, &s3.HeadBucketInput{
|
||||
// Bucket: aws.String(myBucket),
|
||||
// })
|
||||
// if err != nil {
|
||||
// aerr, ok := err.(awserr.Error)
|
||||
// if ok && aerr.Code() == request.WaiterResourceNotReadyErrorCode {
|
||||
// fmt.Fprintf(os.Stderr, "timed out while waiting for bucket to exist")
|
||||
// }
|
||||
// panic(fmt.Errorf("failed to wait for bucket to exist, %v", err))
|
||||
// }
|
||||
// fmt.Println("Bucket", myBucket, "exists")
|
||||
//
|
||||
// Complete SDK Example
|
||||
//
|
||||
// This example shows a complete working Go file which will upload a file to S3
|
||||
// and use the Context pattern to implement timeout logic that will cancel the
|
||||
// request if it takes too long. This example highlights how to use sessions,
|
||||
// create a service client, make a request, handle the error, and process the
|
||||
// response.
|
||||
//
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// "context"
|
||||
// "flag"
|
||||
// "fmt"
|
||||
// "os"
|
||||
// "time"
|
||||
//
|
||||
// "github.com/aws/aws-sdk-go/aws"
|
||||
// "github.com/aws/aws-sdk-go/aws/awserr"
|
||||
// "github.com/aws/aws-sdk-go/aws/request"
|
||||
// "github.com/aws/aws-sdk-go/aws/session"
|
||||
// "github.com/aws/aws-sdk-go/service/s3"
|
||||
// )
|
||||
//
|
||||
// // Uploads a file to S3 given a bucket and object key. Also takes a duration
|
||||
// // value to terminate the update if it doesn't complete within that time.
|
||||
// //
|
||||
// // The AWS Region needs to be provided in the AWS shared config or on the
|
||||
// // environment variable as `AWS_REGION`. Credentials also must be provided
|
||||
// // Will default to shared config file, but can load from environment if provided.
|
||||
// //
|
||||
// // Usage:
|
||||
// // # Upload myfile.txt to myBucket/myKey. Must complete within 10 minutes or will fail
|
||||
// // go run withContext.go -b mybucket -k myKey -d 10m < myfile.txt
|
||||
// func main() {
|
||||
// var bucket, key string
|
||||
// var timeout time.Duration
|
||||
//
|
||||
// flag.StringVar(&bucket, "b", "", "Bucket name.")
|
||||
// flag.StringVar(&key, "k", "", "Object key name.")
|
||||
// flag.DurationVar(&timeout, "d", 0, "Upload timeout.")
|
||||
// flag.Parse()
|
||||
//
|
||||
// // All clients require a Session. The Session provides the client with
|
||||
// // shared configuration such as region, endpoint, and credentials. A
|
||||
// // Session should be shared where possible to take advantage of
|
||||
// // configuration and credential caching. See the session package for
|
||||
// // more information.
|
||||
// sess := session.Must(session.NewSession())
|
||||
//
|
||||
// // Create a new instance of the service's client with a Session.
|
||||
// // Optional aws.Config values can also be provided as variadic arguments
|
||||
// // to the New function. This option allows you to provide service
|
||||
// // specific configuration.
|
||||
// svc := s3.New(sess)
|
||||
//
|
||||
// // Create a context with a timeout that will abort the upload if it takes
|
||||
// // more than the passed in timeout.
|
||||
// ctx := context.Background()
|
||||
// var cancelFn func()
|
||||
// if timeout > 0 {
|
||||
// ctx, cancelFn = context.WithTimeout(ctx, timeout)
|
||||
// }
|
||||
// // Ensure the context is canceled to prevent leaking.
|
||||
// // See context package for more information, https://golang.org/pkg/context/
|
||||
// defer cancelFn()
|
||||
//
|
||||
// // Uploads the object to S3. The Context will interrupt the request if the
|
||||
// // timeout expires.
|
||||
// _, err := svc.PutObjectWithContext(ctx, &s3.PutObjectInput{
|
||||
// Bucket: aws.String(bucket),
|
||||
// Key: aws.String(key),
|
||||
// Body: os.Stdin,
|
||||
// })
|
||||
// if err != nil {
|
||||
// if aerr, ok := err.(awserr.Error); ok && aerr.Code() == request.CanceledErrorCode {
|
||||
// // If the SDK can determine the request or retry delay was canceled
|
||||
// // by a context the CanceledErrorCode error code will be returned.
|
||||
// fmt.Fprintf(os.Stderr, "upload canceled due to timeout, %v\n", err)
|
||||
// } else {
|
||||
// fmt.Fprintf(os.Stderr, "failed to upload object, %v\n", err)
|
||||
// }
|
||||
// os.Exit(1)
|
||||
// }
|
||||
//
|
||||
// fmt.Printf("successfully uploaded file to %s/%s\n", bucket, key)
|
||||
// }
|
||||
package sdk
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
Retrieve Credentials with Go Plugin
|
||||
===
|
||||
|
||||
This example demonstrates how you can take advantage of Go 1.8's new Plugin
|
||||
functionality to retrieve AWS credentials dynamically from a plugin compiled
|
||||
separate from your application.
|
||||
|
||||
Usage
|
||||
---
|
||||
|
||||
Example Plugin
|
||||
---
|
||||
|
||||
You can find the plugin at `plugin/plugin.go` nested within this example. The plugin
|
||||
demonstrates what symbol the SDK will use when lookup up the credential provider
|
||||
and the type signature that needs to be implemented.
|
||||
|
||||
Compile the plugin with:
|
||||
|
||||
go build -tags example -o myPlugin.so -buildmode=plugin plugin/plugin.go
|
||||
|
||||
JSON Credentials File
|
||||
---
|
||||
|
||||
This example plugin will read the credentials from a JSON file pointed to by
|
||||
the `PLUGIN_CREDS_FILE` environment variable. The contents of the file are
|
||||
the credentials, Key, Secret, and Token. The `Token` filed does not need to be
|
||||
set if your credentials do not have one.
|
||||
|
||||
```json
|
||||
{
|
||||
"Key": "MyAWSCredAccessKeyID",
|
||||
"Secret": "MyAWSCredSecretKey",
|
||||
"Token": "MyAWSCredToken"
|
||||
}
|
||||
```
|
||||
|
||||
Example Application
|
||||
---
|
||||
|
||||
The `main.go` file in this folder demonstrates how you can configure the SDK to
|
||||
use a plugin to retrieve credentials with.
|
||||
|
||||
Compile and run application:
|
||||
|
||||
go build -tags example -o myApp main.go
|
||||
|
||||
PLUGIN_CREDS_FILE=pathToCreds.json ./myApp myPlugin.so myBucket myObjectKey
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
// +build example,go18
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"plugin"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials/plugincreds"
|
||||
"github.com/aws/aws-sdk-go/aws/endpoints"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||
)
|
||||
|
||||
// Example application which loads a Go Plugin file, and uses the credential
|
||||
// provider defined within the plugin to get credentials for making a S3
|
||||
// request.
|
||||
//
|
||||
// The example will derive the bucket's region automatically if a AWS_REGION
|
||||
// environment variable is not defined.
|
||||
//
|
||||
// Build:
|
||||
// go build -tags example -o myApp main.go
|
||||
//
|
||||
// Usage:
|
||||
// ./myApp <compiled plugin> <bucket> <object key>
|
||||
func main() {
|
||||
if len(os.Args) < 4 {
|
||||
exitErrorf("Usage: myApp <compiled plugin>, <bucket> <object key>")
|
||||
}
|
||||
|
||||
pluginFilename := os.Args[1]
|
||||
bucket := os.Args[2]
|
||||
key := os.Args[3]
|
||||
|
||||
// Open plugin, and load it into the process.
|
||||
p, err := plugin.Open(pluginFilename)
|
||||
if err != nil {
|
||||
exitErrorf("failed to open plugin, %s, %v", pluginFilename, err)
|
||||
}
|
||||
|
||||
// Create a new Credentials value which will source the provider's Retrieve
|
||||
// and IsExpired functions from the plugin.
|
||||
creds, err := plugincreds.NewCredentials(p)
|
||||
if err != nil {
|
||||
exitErrorf("failed to load plugin provider, %v", err)
|
||||
}
|
||||
|
||||
// Example to configure a Session with the newly created credentials that
|
||||
// will be sourced using the plugin's functionality.
|
||||
sess := session.Must(session.NewSession(&aws.Config{
|
||||
Credentials: creds,
|
||||
}))
|
||||
|
||||
// If the region is not available attempt to derive the bucket's region
|
||||
// from a query to S3 for the bucket's metadata
|
||||
region := aws.StringValue(sess.Config.Region)
|
||||
if len(region) == 0 {
|
||||
region, err = s3manager.GetBucketRegion(context.Background(), sess, bucket, endpoints.UsEast1RegionID)
|
||||
if err != nil {
|
||||
exitErrorf("failed to get bucket region, %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Create the S3 service client for the target region
|
||||
svc := s3.New(sess, aws.NewConfig().WithRegion(region))
|
||||
|
||||
// Get the object's details
|
||||
result, err := svc.HeadObject(&s3.HeadObjectInput{
|
||||
Bucket: aws.String(bucket),
|
||||
Key: aws.String(key),
|
||||
})
|
||||
fmt.Println(result, err)
|
||||
}
|
||||
|
||||
func exitErrorf(format string, args ...interface{}) {
|
||||
fmt.Fprintf(os.Stderr, format+"\n", args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
Generated
Vendored
+61
@@ -0,0 +1,61 @@
|
||||
// +build example,go18
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Example plugin that will retrieve credentials from a JSON file that the
|
||||
// "PLUGIN_CREDS_FILE" environment variable points to
|
||||
//
|
||||
// Build with:
|
||||
// go build -tags example -o plugin.so -buildmode=plugin plugin.go
|
||||
func main() {}
|
||||
|
||||
var myCredProvider provider
|
||||
|
||||
func init() {
|
||||
// Initialize a mock credential provider with stubs
|
||||
myCredProvider = provider{Filename: os.Getenv("PLUGIN_CREDS_FILE")}
|
||||
}
|
||||
|
||||
// GetAWSSDKCredentialProvider is the symbol SDK will lookup and use to
|
||||
// get the credential provider's retrieve and isExpired functions.
|
||||
func GetAWSSDKCredentialProvider() (func() (key, secret, token string, err error), func() bool) {
|
||||
return myCredProvider.Retrieve, myCredProvider.IsExpired
|
||||
}
|
||||
|
||||
// mock implementation of a type that returns retrieves credentials and
|
||||
// returns if they have expired.
|
||||
type provider struct {
|
||||
Filename string
|
||||
|
||||
loaded bool
|
||||
}
|
||||
|
||||
func (p *provider) Retrieve() (key, secret, token string, err error) {
|
||||
f, err := os.Open(p.Filename)
|
||||
if err != nil {
|
||||
return "", "", "", errors.Wrapf(err, "failed to open credentials file, %q", p.Filename)
|
||||
}
|
||||
decoder := json.NewDecoder(f)
|
||||
|
||||
creds := struct {
|
||||
Key, Secret, Token string
|
||||
}{}
|
||||
|
||||
if err := decoder.Decode(&creds); err != nil {
|
||||
return "", "", "", errors.Wrap(err, "failed to decode credentials file")
|
||||
}
|
||||
|
||||
p.loaded = true
|
||||
return creds.Key, creds.Secret, creds.Token, nil
|
||||
}
|
||||
|
||||
func (p *provider) IsExpired() bool {
|
||||
return !p.loaded
|
||||
}
|
||||
+9
-1
@@ -3,4 +3,12 @@ Custom Endpoint Example
|
||||
|
||||
This example provides examples on how you can provide custom endpoints, and logic to how endpoints are resolved by the SDK.
|
||||
|
||||
The example creates multiple clients with different endpoint configuraiton. From a custom endpoint resolver that wraps the defeault resolver so that any S3 service client created uses the custom endpoint, to how you can provide your own logic to a single service's endpoint resolving.
|
||||
The example creates multiple clients with different endpoint configuration. From a custom endpoint resolver that wraps the default resolver so that any Amazon S3 service client created uses the custom endpoint, to how you can provide your own logic to a single service's endpoint resolving.
|
||||
|
||||
|
||||
Usage
|
||||
---
|
||||
|
||||
```sh
|
||||
go run -tags example customeEndpoint.go
|
||||
```
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
Enumerate Regions and Endpoints Example
|
||||
===
|
||||
|
||||
Demostrates how the SDK's endpoints can be enumerated over to discover regions, services, and endpoints defined by the SDK's Regions and Endpoints metadata.
|
||||
Demonstrates how the SDK's endpoints can be enumerated over to discover regions, services, and endpoints defined by the SDK's Regions and Endpoints metadata.
|
||||
|
||||
Usage
|
||||
---
|
||||
@@ -10,7 +10,7 @@ The following parameters can be used to enumerate the SDK's partition metadata.
|
||||
|
||||
Example:
|
||||
|
||||
go run enumEndpoints.go -p aws -services -r us-west-2
|
||||
go run -tags example enumEndpoints.go -p aws -services -r us-west-2
|
||||
|
||||
Output:
|
||||
|
||||
|
||||
+1
-1
@@ -10,4 +10,4 @@ Will default to shared config file, but can load from environment if provided.
|
||||
## Usage:
|
||||
|
||||
# Upload myfile.txt to myBucket/myKey. Must complete within 10 minutes or will fail
|
||||
go run withContext.go -b mybucket -k myKey -d 10m < myfile.txt
|
||||
go run -tags example withContext.go -b mybucket -k myKey -d 10m < myfile.txt
|
||||
|
||||
+21
-6
@@ -10,6 +10,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
)
|
||||
@@ -36,21 +38,34 @@ func main() {
|
||||
sess := session.Must(session.NewSession())
|
||||
svc := s3.New(sess)
|
||||
|
||||
// Create a context with a timeout that will abort the upload if it takes
|
||||
// more than the passed in timeout.
|
||||
ctx := context.Background()
|
||||
var cancelFn func()
|
||||
if timeout > 0 {
|
||||
ctx, cancelFn = context.WithTimeout(ctx, timeout)
|
||||
}
|
||||
// Ensure the context is canceled to prevent leaking.
|
||||
// See context package for more information, https://golang.org/pkg/context/
|
||||
defer cancelFn()
|
||||
|
||||
// Uploads the object to S3. The Context will interrupt the request
|
||||
resp, err := svc.PutObjectWithContext(ctx, &s3.PutObjectInput{
|
||||
// Uploads the object to S3. The Context will interrupt the request if the
|
||||
// timeout expires.
|
||||
_, err := svc.PutObjectWithContext(ctx, &s3.PutObjectInput{
|
||||
Bucket: aws.String(bucket),
|
||||
Key: aws.String(key),
|
||||
Body: os.Stdin,
|
||||
})
|
||||
if err != nil {
|
||||
if aerr, ok := err.(awserr.Error); ok && aerr.Code() == request.CanceledErrorCode {
|
||||
// If the SDK can determine the request or retry delay was canceled
|
||||
// by a context the CanceledErrorCode error code will be returned.
|
||||
fmt.Fprintf(os.Stderr, "upload canceled due to timeout, %v\n", err)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "failed to upload object, %v\n", err)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println(resp, err)
|
||||
|
||||
// Cleanup context
|
||||
cancelFn()
|
||||
fmt.Printf("successfully uploaded file to %s/%s\n", bucket, key)
|
||||
}
|
||||
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
# Example
|
||||
|
||||
`scan` is an example how to use Amazon DynamoDB's `expression` package to fill
|
||||
the member fields of Amazon DynamoDB's Operation input types.
|
||||
|
||||
## Representing DynamoDB Expressions
|
||||
|
||||
In the example, the variable `filt` represents a `FilterExpression`. Note that
|
||||
DynamoDB item attributes are represented using the function `Name()` and
|
||||
DynamoDB item values are similarly represented using the function `Value()`. In
|
||||
this context, the string `"Artist"` represents the name of the item attribute
|
||||
that we want to evaluate and the string `"No One You Know"` represents the value
|
||||
we want to evaluate the item attribute against. The relationship between the two
|
||||
[operands](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Syntax)
|
||||
are specified using the method `Equal()`.
|
||||
|
||||
Similarly, the variable `proj` represents a `ProjectionExpression`. The list of
|
||||
item attribute names comprising the `ProjectionExpression` are specified as
|
||||
arguments to the function `NamesList()`. The `expression` package utilizes the
|
||||
type safety of Go and if an item value were to be used as an argument to the
|
||||
function `NamesList()`, a compile time error is returned. The pattern of
|
||||
representing DynamoDB Expressions by indicating relationships between `operands`
|
||||
with functions is consistent throughout the whole `expression` package.
|
||||
|
||||
```go
|
||||
filt := expression.Name("Artist").Equal(expression.Value("No One You Know"))
|
||||
// let :a be an ExpressionAttributeValue representing the string "No One You Know"
|
||||
// equivalent FilterExpression: "Artist = :a"
|
||||
|
||||
proj := expression.NamesList(expression.Name("SongTitle"), expression.Name("AlbumTitle"))
|
||||
// equivalent ProjectionExpression: "SongTitle, AlbumTitle"
|
||||
```
|
||||
|
||||
## Creating an `Expression`
|
||||
|
||||
In the example, the variable `expr` is an instance of an `Expression` type. An
|
||||
`Expression` is built using a builder pattern. First, a new `Builder` is
|
||||
initialized by the `NewBuilder()` function. Then, types representing DynamoDB
|
||||
Expressions are added to the `Builder` by methods `WithFilter()` and
|
||||
`WithProjection()`. The `Build()` method returns an instance of an `Expression`
|
||||
and an error. The error will be either an `InvalidParameterError` or an
|
||||
`UnsetParameterError`.
|
||||
|
||||
```go
|
||||
filt := expression.Name("Artist").Equal(expression.Value("No One You Know"))
|
||||
proj := expression.NamesList(expression.Name("SongTitle"), expression.Name("AlbumTitle"))
|
||||
|
||||
expr, err := expression.NewBuilder().WithFilter(filt).WithProjection(proj).Build()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
```
|
||||
|
||||
## Filling in the fields of a DynamoDB `Scan` API
|
||||
|
||||
In the example, the getter methods of the `Expression` type is used to get the
|
||||
formatted DynamoDB Expression strings. The `ExpressionAttributeNames` and
|
||||
`ExpressionAttributeValues` member field of the DynamoDB API must always be
|
||||
assigned when using an `Expression` since all item attribute names and values
|
||||
are aliased. That means that if the `ExpressionAttributeNames` and
|
||||
`ExpressionAttributeValues` member is not assigned with the corresponding
|
||||
`Names()` and `Values()` methods, the DynamoDB operation will run into a logic
|
||||
error.
|
||||
|
||||
```go
|
||||
filt := expression.Name("Artist").Equal(expression.Value("No One You Know"))
|
||||
proj := expression.NamesList(expression.Name("SongTitle"), expression.Name("AlbumTitle"))
|
||||
expr, err := expression.NewBuilder().WithFilter(filt).WithProjection(proj).Build()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
input := &dynamodb.ScanInput{
|
||||
ExpressionAttributeNames: expr.Names(),
|
||||
ExpressionAttributeValues: expr.Values(),
|
||||
FilterExpression: expr.Filter(),
|
||||
ProjectionExpression: expr.Projection(),
|
||||
TableName: aws.String("Music"),
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
`go run -tags example scan.go -table "<table_name>" -region "<optional_region>"`
|
||||
|
||||
## Output
|
||||
|
||||
```
|
||||
{
|
||||
Count: #SomeNumber,
|
||||
Items: [{
|
||||
AlbumTitle: {
|
||||
#SomeAlbumTitle
|
||||
},
|
||||
SongTitle: {
|
||||
#SomeSongTitle
|
||||
}
|
||||
}],
|
||||
...
|
||||
ScannedCount: #SomeNumber,
|
||||
}
|
||||
```
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
// +build example
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/dynamodb"
|
||||
"github.com/aws/aws-sdk-go/service/dynamodb/expression"
|
||||
)
|
||||
|
||||
func exitWithError(err error) {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := Config{}
|
||||
if err := cfg.Load(); err != nil {
|
||||
exitWithError(fmt.Errorf("failed to load config, %v", err))
|
||||
}
|
||||
|
||||
// Create the config specifying the Region for the DynamoDB table.
|
||||
// If Config.Region is not set the region must come from the shared
|
||||
// config or AWS_REGION environment variable.
|
||||
awscfg := &aws.Config{}
|
||||
if len(cfg.Region) > 0 {
|
||||
awscfg.WithRegion(cfg.Region)
|
||||
}
|
||||
|
||||
// Create the session that the DynamoDB service will use.
|
||||
sess := session.Must(session.NewSession(awscfg))
|
||||
|
||||
// Create the DynamoDB service client to make the query request with.
|
||||
svc := dynamodb.New(sess)
|
||||
|
||||
// Create the Expression to fill the input struct with.
|
||||
filt := expression.Name("Artist").Equal(expression.Value("No One You Know"))
|
||||
proj := expression.NamesList(expression.Name("SongTitle"), expression.Name("AlbumTitle"))
|
||||
expr, err := expression.NewBuilder().WithFilter(filt).WithProjection(proj).Build()
|
||||
if err != nil {
|
||||
exitWithError(fmt.Errorf("failed to create the Expression, %v", err))
|
||||
}
|
||||
|
||||
// Build the query input parameters
|
||||
params := &dynamodb.ScanInput{
|
||||
ExpressionAttributeNames: expr.Names(),
|
||||
ExpressionAttributeValues: expr.Values(),
|
||||
FilterExpression: expr.Filter(),
|
||||
ProjectionExpression: expr.Projection(),
|
||||
TableName: aws.String(cfg.Table),
|
||||
}
|
||||
if cfg.Limit > 0 {
|
||||
params.Limit = aws.Int64(cfg.Limit)
|
||||
}
|
||||
|
||||
// Make the DynamoDB Query API call
|
||||
result, err := svc.Scan(params)
|
||||
if err != nil {
|
||||
exitWithError(fmt.Errorf("failed to make Query API call, %v", err))
|
||||
}
|
||||
|
||||
fmt.Println(result)
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Table string // required
|
||||
Region string // optional
|
||||
Limit int64 // optional
|
||||
}
|
||||
|
||||
func (c *Config) Load() error {
|
||||
flag.Int64Var(&c.Limit, "limit", 0, "Limit is the max items to be returned, 0 is no limit")
|
||||
flag.StringVar(&c.Table, "table", "", "Table to Query on")
|
||||
flag.StringVar(&c.Region, "region", "", "AWS Region the table is in")
|
||||
flag.Parse()
|
||||
|
||||
if len(c.Table) == 0 {
|
||||
flag.PrintDefaults()
|
||||
return fmt.Errorf("table name is required.")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user