Update aws-sdk-go (#4153)

Signed-off-by: bege13mot <bege13mot@gmail.com>
This commit is contained in:
Ivan Voronchihin 2018-07-18 08:26:04 +04:00 committed by Brian Brazil
parent 59d214d277
commit 1c6f2a1b68
57 changed files with 7196 additions and 457 deletions

View file

@ -61,6 +61,12 @@ func prettify(v reflect.Value, indent int, buf *bytes.Buffer) {
buf.WriteString("\n" + strings.Repeat(" ", indent) + "}")
case reflect.Slice:
strtype := v.Type().String()
if strtype == "[]uint8" {
fmt.Fprintf(buf, "<binary> len %d", v.Len())
break
}
nl, id, id2 := "", "", ""
if v.Len() > 3 {
nl, id, id2 = "\n", strings.Repeat(" ", indent), strings.Repeat(" ", indent+2)

View file

@ -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"
@ -11,9 +10,17 @@ import (
// A Config provides configuration to a service client instance.
type Config struct {
Config *aws.Config
Handlers request.Handlers
Endpoint, SigningRegion string
Config *aws.Config
Handlers request.Handlers
Endpoint string
SigningRegion string
SigningName string
// States that the signing name did not come from a modeled source but
// was derived based on other data. Used by service client constructors
// to determine if the signin name can be overriden based on metadata the
// service has.
SigningNameDerived bool
}
// ConfigProvider provides a generic way for a service client to receive
@ -22,6 +29,13 @@ type ConfigProvider interface {
ClientConfig(serviceName string, cfgs ...*aws.Config) Config
}
// ConfigNoResolveEndpointProvider same as ConfigProvider except it will not
// resolve the endpoint automatically. The service client's endpoint must be
// provided via the aws.Config.Endpoint field.
type ConfigNoResolveEndpointProvider interface {
ClientConfigNoResolveEndpoint(cfgs ...*aws.Config) Config
}
// A Client implements the base client request and response handling
// used by all service clients.
type Client struct {
@ -37,7 +51,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); {
@ -77,61 +91,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})
}

View file

@ -1,11 +1,11 @@
package client
import (
"math/rand"
"sync"
"strconv"
"time"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/internal/sdkrand"
)
// DefaultRetryer implements basic retry logic using exponential backoff for
@ -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
}
@ -30,31 +30,39 @@ func (d DefaultRetryer) MaxRetries() int {
return d.NumMaxRetries
}
var seededRand = rand.New(&lockedSource{src: rand.NewSource(time.Now().UnixNano())})
// RetryRules returns the delay duration before retrying this request again
func (d DefaultRetryer) RetryRules(r *request.Request) time.Duration {
// Set the upper limit of delay in retrying at ~five minutes
minTime := 30
throttle := d.shouldThrottle(r)
if throttle {
if delay, ok := getRetryDelay(r); ok {
return delay
}
minTime = 500
}
retryCount := r.RetryCount
if retryCount > 13 {
retryCount = 13
} else if throttle && retryCount > 8 {
if throttle && retryCount > 8 {
retryCount = 8
} else if retryCount > 13 {
retryCount = 13
}
delay := (1 << uint(retryCount)) * (seededRand.Intn(minTime) + minTime)
delay := (1 << uint(retryCount)) * (sdkrand.SeededRand.Intn(minTime) + minTime)
return time.Duration(delay) * time.Millisecond
}
// ShouldRetry returns true if the request should be retried.
func (d DefaultRetryer) ShouldRetry(r *request.Request) bool {
if r.HTTPResponse.StatusCode >= 500 {
// 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 && r.HTTPResponse.StatusCode != 501 {
return true
}
return r.IsErrorRetryable() || d.shouldThrottle(r)
@ -62,29 +70,47 @@ func (d DefaultRetryer) ShouldRetry(r *request.Request) bool {
// ShouldThrottle returns true if the request should be throttled.
func (d DefaultRetryer) shouldThrottle(r *request.Request) bool {
if r.HTTPResponse.StatusCode == 502 ||
r.HTTPResponse.StatusCode == 503 ||
r.HTTPResponse.StatusCode == 504 {
return true
switch r.HTTPResponse.StatusCode {
case 429:
case 502:
case 503:
case 504:
default:
return r.IsErrorThrottle()
}
return r.IsErrorThrottle()
return true
}
// lockedSource is a thread-safe implementation of rand.Source
type lockedSource struct {
lk sync.Mutex
src rand.Source
// This will look in the Retry-After header, RFC 7231, for how long
// it will wait before attempting another request
func getRetryDelay(r *request.Request) (time.Duration, bool) {
if !canUseRetryAfterHeader(r) {
return 0, false
}
delayStr := r.HTTPResponse.Header.Get("Retry-After")
if len(delayStr) == 0 {
return 0, false
}
delay, err := strconv.Atoi(delayStr)
if err != nil {
return 0, false
}
return time.Duration(delay) * time.Second, true
}
func (r *lockedSource) Int63() (n int64) {
r.lk.Lock()
n = r.src.Int63()
r.lk.Unlock()
return
}
// Will look at the status code to see if the retry header pertains to
// the status code.
func canUseRetryAfterHeader(r *request.Request) bool {
switch r.HTTPResponse.StatusCode {
case 429:
case 503:
default:
return false
}
func (r *lockedSource) Seed(seed int64) {
r.lk.Lock()
r.src.Seed(seed)
r.lk.Unlock()
return true
}

112
vendor/github.com/aws/aws-sdk-go/aws/client/logger.go generated vendored Normal file
View file

@ -0,0 +1,112 @@
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)
bodySeekable := aws.IsReaderSeekable(r.Body)
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 {
if !bodySeekable {
r.SetReaderBody(aws.ReadSeekCloser(r.HTTPRequest.Body))
}
// 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,
})
}

View file

@ -5,6 +5,7 @@ import (
"time"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/endpoints"
)
// UseServiceDefaultRetries instructs the config to use the service's own
@ -21,9 +22,9 @@ type RequestRetryer interface{}
//
// // Create Session with MaxRetry configuration to be shared by multiple
// // service clients.
// sess, err := session.NewSession(&aws.Config{
// sess := session.Must(session.NewSession(&aws.Config{
// MaxRetries: aws.Int(3),
// })
// }))
//
// // Create S3 service client with a specific Region.
// svc := s3.New(sess, &aws.Config{
@ -48,6 +49,17 @@ type Config struct {
// endpoint for a client.
Endpoint *string
// The resolver to use for looking up endpoints for AWS service clients
// 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"
@ -83,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.
@ -139,6 +151,15 @@ type Config struct {
// with accelerate.
S3UseAccelerate *bool
// S3DisableContentMD5Validation config option is temporarily disabled,
// For S3 GetObject API calls, #1837.
//
// Set this to `true` to disable the S3 service client from automatically
// adding the ContentMD5 to S3 Object Put and Upload API calls. This option
// will also disable the SDK from performing object ContentMD5 validation
// on GetObject API calls.
S3DisableContentMD5Validation *bool
// Set this to `true` to disable the EC2Metadata client from overriding the
// default http.Client's Timeout. This is helpful if you do not want the
// EC2Metadata client to create a new http.Client. This options is only
@ -149,13 +170,14 @@ type Config struct {
// the EC2Metadata overriding the timeout for default credentials chain.
//
// Example:
// sess, err := session.NewSession(aws.NewConfig().WithEC2MetadataDiableTimeoutOverride(true))
// sess := session.Must(session.NewSession(aws.NewConfig()
// .WithEC2MetadataDiableTimeoutOverride(true)))
//
// svc := s3.New(sess)
//
EC2MetadataDisableTimeoutOverride *bool
// Instructs the endpiont to be generated for a service client to
// Instructs the endpoint to be generated for a service client to
// be the dual stack endpoint. The dual stack endpoint will support
// both IPv4 and IPv6 addressing.
//
@ -169,7 +191,7 @@ type Config struct {
//
// Only supported with.
//
// sess, err := session.NewSession()
// sess := session.Must(session.NewSession())
//
// svc := s3.New(sess, &aws.Config{
// UseDualStack: aws.Bool(true),
@ -181,7 +203,26 @@ type Config struct {
// request delays. This value should only be used for testing. To adjust
// the delay of a request see the aws/client.DefaultRetryer and
// aws/request.Retryer.
//
// SleepDelay will prevent any Context from being used for canceling retry
// delay of an API operation. It is recommended to not use SleepDelay at all
// and specify a Retryer instead.
SleepDelay func(time.Duration)
// DisableRestProtocolURICleaning will not clean the URL path when making rest protocol requests.
// Will default to false. This would only be used for empty directory names in s3 requests.
//
// Example:
// sess := session.Must(session.NewSession(&aws.Config{
// DisableRestProtocolURICleaning: aws.Bool(true),
// }))
//
// svc := s3.New(sess)
// out, err := svc.GetObject(&s3.GetObjectInput {
// Bucket: aws.String("bucketname"),
// Key: aws.String("//foo//bar//moo"),
// })
DisableRestProtocolURICleaning *bool
}
// NewConfig returns a new Config pointer that can be chained with builder
@ -189,9 +230,9 @@ type Config struct {
//
// // Create Session with MaxRetry configuration to be shared by multiple
// // service clients.
// sess, err := session.NewSession(aws.NewConfig().
// sess := session.Must(session.NewSession(aws.NewConfig().
// WithMaxRetries(3),
// )
// ))
//
// // Create S3 service client with a specific Region.
// svc := s3.New(sess, aws.NewConfig().
@ -222,6 +263,13 @@ func (c *Config) WithEndpoint(endpoint string) *Config {
return c
}
// WithEndpointResolver sets a config EndpointResolver value returning a
// Config pointer for chaining.
func (c *Config) WithEndpointResolver(resolver endpoints.Resolver) *Config {
c.EndpointResolver = resolver
return c
}
// WithRegion sets a config Region value returning a Config pointer for
// chaining.
func (c *Config) WithRegion(region string) *Config {
@ -297,6 +345,15 @@ func (c *Config) WithS3Disable100Continue(disable bool) *Config {
func (c *Config) WithS3UseAccelerate(enable bool) *Config {
c.S3UseAccelerate = &enable
return c
}
// WithS3DisableContentMD5Validation sets a config
// S3DisableContentMD5Validation value returning a Config pointer for chaining.
func (c *Config) WithS3DisableContentMD5Validation(enable bool) *Config {
c.S3DisableContentMD5Validation = &enable
return c
}
// WithUseDualStack sets a config UseDualStack value returning a Config
@ -344,6 +401,10 @@ func mergeInConfig(dst *Config, other *Config) {
dst.Endpoint = other.Endpoint
}
if other.EndpointResolver != nil {
dst.EndpointResolver = other.EndpointResolver
}
if other.Region != nil {
dst.Region = other.Region
}
@ -392,6 +453,10 @@ func mergeInConfig(dst *Config, other *Config) {
dst.S3UseAccelerate = other.S3UseAccelerate
}
if other.S3DisableContentMD5Validation != nil {
dst.S3DisableContentMD5Validation = other.S3DisableContentMD5Validation
}
if other.UseDualStack != nil {
dst.UseDualStack = other.UseDualStack
}
@ -403,6 +468,14 @@ func mergeInConfig(dst *Config, other *Config) {
if other.SleepDelay != nil {
dst.SleepDelay = other.SleepDelay
}
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

71
vendor/github.com/aws/aws-sdk-go/aws/context.go generated vendored Normal file
View file

@ -0,0 +1,71 @@
package aws
import (
"time"
)
// Context is an copy of the Go v1.7 stdlib's context.Context interface.
// It is represented as a SDK interface to enable you to use the "WithContext"
// API methods with Go v1.6 and a Context type such as golang.org/x/net/context.
//
// See https://golang.org/pkg/context on how to use contexts.
type Context interface {
// Deadline returns the time when work done on behalf of this context
// should be canceled. Deadline returns ok==false when no deadline is
// set. Successive calls to Deadline return the same results.
Deadline() (deadline time.Time, ok bool)
// Done returns a channel that's closed when work done on behalf of this
// context should be canceled. Done may return nil if this context can
// never be canceled. Successive calls to Done return the same value.
Done() <-chan struct{}
// Err returns a non-nil error value after Done is closed. Err returns
// Canceled if the context was canceled or DeadlineExceeded if the
// context's deadline passed. No other values for Err are defined.
// After Done is closed, successive calls to Err return the same value.
Err() error
// Value returns the value associated with this context for key, or nil
// if no value is associated with key. Successive calls to Value with
// the same key returns the same result.
//
// Use context values only for request-scoped data that transits
// processes and API boundaries, not for passing optional parameters to
// functions.
Value(key interface{}) interface{}
}
// BackgroundContext returns a context that will never be canceled, has no
// values, and no deadline. This context is used by the SDK to provide
// backwards compatibility with non-context API operations and functionality.
//
// Go 1.6 and before:
// This context function is equivalent to context.Background in the Go stdlib.
//
// Go 1.7 and later:
// The context returned will be the value returned by context.Background()
//
// See https://golang.org/pkg/context for more information on Contexts.
func BackgroundContext() Context {
return backgroundCtx
}
// SleepWithContext will wait for the timer duration to expire, or the context
// is canceled. Which ever happens first. If the context is canceled the Context's
// error will be returned.
//
// Expects Context to always return a non-nil error if the Done channel is closed.
func SleepWithContext(ctx Context, dur time.Duration) error {
t := time.NewTimer(dur)
defer t.Stop()
select {
case <-t.C:
break
case <-ctx.Done():
return ctx.Err()
}
return nil
}

41
vendor/github.com/aws/aws-sdk-go/aws/context_1_6.go generated vendored Normal file
View file

@ -0,0 +1,41 @@
// +build !go1.7
package aws
import "time"
// 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.
type emptyCtx int
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
return
}
func (*emptyCtx) Done() <-chan struct{} {
return nil
}
func (*emptyCtx) Err() error {
return nil
}
func (*emptyCtx) Value(key interface{}) interface{} {
return nil
}
func (e *emptyCtx) String() string {
switch e {
case backgroundCtx:
return "aws.BackgroundContext"
}
return "unknown empty Context"
}
var (
backgroundCtx = new(emptyCtx)
)

9
vendor/github.com/aws/aws-sdk-go/aws/context_1_7.go generated vendored Normal file
View file

@ -0,0 +1,9 @@
// +build go1.7
package aws
import "context"
var (
backgroundCtx = context.Background()
)

View file

@ -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.

View file

@ -3,12 +3,10 @@ package corehandlers
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"regexp"
"runtime"
"strconv"
"time"
@ -27,7 +25,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) {
@ -36,18 +34,13 @@ var BuildContentLengthHandler = request.NamedHandler{Name: "core.BuildContentLen
if slength := r.HTTPRequest.Header.Get("Content-Length"); slength != "" {
length, _ = strconv.ParseInt(slength, 10, 64)
} else {
switch body := r.Body.(type) {
case nil:
length = 0
case lener:
length = int64(body.Len())
case io.Seeker:
r.BodyStart, _ = body.Seek(0, 1)
end, _ := body.Seek(0, 2)
body.Seek(r.BodyStart, 0) // make sure to seek back to original location
length = end - r.BodyStart
default:
panic("Cannot get length of body, must provide `ContentLength`")
if r.Body != nil {
var err error
length, err = aws.SeekerLen(r.Body)
if err != nil {
r.Error = awserr.New(request.ErrCodeSerialization, "failed to get request body's length", err)
return
}
}
}
@ -60,19 +53,12 @@ var BuildContentLengthHandler = request.NamedHandler{Name: "core.BuildContentLen
}
}}
// SDKVersionUserAgentHandler is a request handler for adding the SDK Version to the user agent.
var SDKVersionUserAgentHandler = request.NamedHandler{
Name: "core.SDKVersionUserAgentHandler",
Fn: request.MakeAddToUserAgentHandler(aws.SDKName, aws.SDKVersion,
runtime.Version(), runtime.GOOS, runtime.GOARCH),
}
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 signficant 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,44 +84,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{})),
}
return
}
// Catch all other request errors.
r.Error = awserr.New("RequestError", "send request failed", err)
r.Retryable = aws.Bool(true) // network errors are retryable
}
}}
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) {
@ -150,13 +187,22 @@ 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))
}
if r.WillRetry() {
r.RetryDelay = r.RetryRules(r)
r.Config.SleepDelay(r.RetryDelay)
if sleepFn := r.Config.SleepDelay; sleepFn != nil {
// Support SleepDelay for backwards compatibility and testing
sleepFn(r.RetryDelay)
} else if err := aws.SleepWithContext(r.Context(), r.RetryDelay); err != nil {
r.Error = awserr.New(request.CanceledErrorCode,
"request context canceled", err)
r.Retryable = aws.Bool(false)
return
}
// when the expired token exception occurs the credentials
// need to be expired locally so that the next request to

View file

@ -0,0 +1,37 @@
package corehandlers
import (
"os"
"runtime"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/request"
)
// SDKVersionUserAgentHandler is a request handler for adding the SDK Version
// to the user agent.
var SDKVersionUserAgentHandler = request.NamedHandler{
Name: "core.SDKVersionUserAgentHandler",
Fn: request.MakeAddToUserAgentHandler(aws.SDKName, aws.SDKVersion,
runtime.Version(), runtime.GOOS, runtime.GOARCH),
}
const execEnvVar = `AWS_EXECUTION_ENV`
const execEnvUAKey = `exec_env`
// AddHostExecEnvUserAgentHander is a request handler appending the SDK's
// execution environment to the user agent.
//
// If the environment variable AWS_EXECUTION_ENV is set, its value will be
// appended to the user agent string.
var AddHostExecEnvUserAgentHander = request.NamedHandler{
Name: "core.AddHostExecEnvUserAgentHander",
Fn: func(r *request.Request) {
v := os.Getenv(execEnvVar)
if len(v) == 0 {
return
}
request.AddToUserAgent(r, execEnvUAKey+"/"+v)
},
}

View file

@ -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

View file

@ -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.
//

View file

@ -111,7 +111,7 @@ func (m *EC2RoleProvider) Retrieve() (credentials.Value, error) {
}, nil
}
// A ec2RoleCredRespBody provides the shape for unmarshalling credential
// A ec2RoleCredRespBody provides the shape for unmarshaling credential
// request responses.
type ec2RoleCredRespBody struct {
// Success State

View file

@ -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

View file

@ -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
}

View file

@ -1,7 +1,81 @@
// Package stscreds are credential Providers to retrieve STS AWS credentials.
//
// STS provides multiple ways to retrieve credentials which can be used when making
// future AWS service API operation calls.
/*
Package stscreds are credential Providers to retrieve STS AWS credentials.
STS provides multiple ways to retrieve credentials which can be used when making
future AWS service API operation calls.
The SDK will ensure that per instance of credentials.Credentials all requests
to refresh the credentials will be synchronized. But, the SDK is unable to
ensure synchronous usage of the AssumeRoleProvider if the value is shared
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.
// 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})
Assume Role with static MFA Token
To assume an IAM role with a MFA token you can either specify a MFA token code
directly or provide a function to prompt the user each time the credentials
need to refresh the role's credentials. Specifying the TokenCode should be used
for short lived operations that will not need to be refreshed, and when you do
not want to have direct control over the user provides their MFA token.
With TokenCode the AssumeRoleProvider will be not be able to refresh the role's
credentials.
// Create the credentials from AssumeRoleProvider to assume the role
// referenced by the "myRoleARN" ARN using the MFA token code provided.
creds := stscreds.NewCredentials(sess, "myRoleArn", func(p *stscreds.AssumeRoleProvider) {
p.SerialNumber = aws.String("myTokenSerialNumber")
p.TokenCode = aws.String("00000000")
})
// Create service client value configured for credentials
// from assumed role.
svc := s3.New(sess, &aws.Config{Credentials: creds})
Assume Role with MFA Token Provider
To assume an IAM role with MFA for longer running tasks where the credentials
may need to be refreshed setting the TokenProvider field of AssumeRoleProvider
will allow the credential provider to prompt for new MFA token code when the
role's credentials need to be refreshed.
The StdinTokenProvider function is available to prompt on stdin to retrieve
the MFA token code from the user. You can also implement custom prompts by
satisfing the TokenProvider function signature.
Using StdinTokenProvider with multiple AssumeRoleProviders, or Credentials will
have undesirable results as the StdinTokenProvider will not be synchronized. A
single Credentials with an AssumeRoleProvider can be shared safely.
// Create the credentials from AssumeRoleProvider to assume the role
// referenced by the "myRoleARN" ARN. Prompting for MFA token from stdin.
creds := stscreds.NewCredentials(sess, "myRoleArn", func(p *stscreds.AssumeRoleProvider) {
p.SerialNumber = aws.String("myTokenSerialNumber")
p.TokenProvider = stscreds.StdinTokenProvider
})
// Create service client value configured for credentials
// from assumed role.
svc := s3.New(sess, &aws.Config{Credentials: creds})
*/
package stscreds
import (
@ -9,11 +83,31 @@ import (
"time"
"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/credentials"
"github.com/aws/aws-sdk-go/service/sts"
)
// StdinTokenProvider will prompt on stdout and read from stdin for a string value.
// An error is returned if reading from stdin fails.
//
// Use this function go read MFA tokens from stdin. The function makes no attempt
// to make atomic prompts from stdin across multiple gorouties.
//
// Using StdinTokenProvider with multiple AssumeRoleProviders, or Credentials will
// have undesirable results as the StdinTokenProvider will not be synchronized. A
// single Credentials with an AssumeRoleProvider can be shared safely
//
// Will wait forever until something is provided on the stdin.
func StdinTokenProvider() (string, error) {
var v string
fmt.Printf("Assume Role MFA token code: ")
_, err := fmt.Scanln(&v)
return v, err
}
// ProviderName provides a name of AssumeRole provider
const ProviderName = "AssumeRoleProvider"
@ -27,8 +121,15 @@ type AssumeRoler interface {
var DefaultDuration = time.Duration(15) * time.Minute
// AssumeRoleProvider retrieves temporary credentials from the STS service, and
// keeps track of their expiration time. This provider must be used explicitly,
// as it is not included in the credentials chain.
// keeps track of their expiration time.
//
// This credential provider will be used by the SDKs default credential change
// when shared configuration is enabled, and the shared config or shared credentials
// file configure assume role. See Session docs for how to do this.
//
// AssumeRoleProvider does not provide any synchronization and it is not safe
// to share this value across multiple Credentials, Sessions, or service clients
// without also sharing the same Credentials instance.
type AssumeRoleProvider struct {
credentials.Expiry
@ -65,8 +166,23 @@ type AssumeRoleProvider struct {
// assumed requires MFA (that is, if the policy includes a condition that tests
// for MFA). If the role being assumed requires MFA and if the TokenCode value
// is missing or expired, the AssumeRole call returns an "access denied" error.
//
// If SerialNumber is set and neither TokenCode nor TokenProvider are also
// set an error will be returned.
TokenCode *string
// Async method of providing MFA token code for assuming an IAM role with MFA.
// The value returned by the function will be used as the TokenCode in the Retrieve
// call. See StdinTokenProvider for a provider that prompts and reads from stdin.
//
// This token provider will be called when ever the assumed role's
// credentials need to be refreshed when SerialNumber is also set and
// TokenCode is not set.
//
// If both TokenCode and TokenProvider is set, TokenProvider will be used and
// TokenCode is ignored.
TokenProvider func() (string, error)
// ExpiryWindow will allow the credentials to trigger refreshing prior to
// the credentials actually expiring. This is beneficial so race conditions
// with expiring credentials do not cause request to fail unexpectedly
@ -85,6 +201,10 @@ type AssumeRoleProvider struct {
//
// Takes a Config provider to create the STS client. The ConfigProvider is
// satisfied by the session.Session type.
//
// It is safe to share the returned Credentials with multiple Sessions and
// service clients. All access to the credentials and refreshing them
// will be synchronized.
func NewCredentials(c client.ConfigProvider, roleARN string, options ...func(*AssumeRoleProvider)) *credentials.Credentials {
p := &AssumeRoleProvider{
Client: sts.New(c),
@ -103,7 +223,11 @@ func NewCredentials(c client.ConfigProvider, roleARN string, options ...func(*As
// AssumeRoleProvider. The credentials will expire every 15 minutes and the
// role will be named after a nanosecond timestamp of this operation.
//
// Takes an AssumeRoler which can be satisfiede by the STS client.
// Takes an AssumeRoler which can be satisfied by the STS client.
//
// It is safe to share the returned Credentials with multiple Sessions and
// service clients. All access to the credentials and refreshing them
// will be synchronized.
func NewCredentialsWithClient(svc AssumeRoler, roleARN string, options ...func(*AssumeRoleProvider)) *credentials.Credentials {
p := &AssumeRoleProvider{
Client: svc,
@ -139,12 +263,25 @@ func (p *AssumeRoleProvider) Retrieve() (credentials.Value, error) {
if p.Policy != nil {
input.Policy = p.Policy
}
if p.SerialNumber != nil && p.TokenCode != nil {
input.SerialNumber = p.SerialNumber
input.TokenCode = p.TokenCode
if p.SerialNumber != nil {
if p.TokenCode != nil {
input.SerialNumber = p.SerialNumber
input.TokenCode = p.TokenCode
} else if p.TokenProvider != nil {
input.SerialNumber = p.SerialNumber
code, err := p.TokenProvider()
if err != nil {
return credentials.Value{ProviderName: ProviderName}, err
}
input.TokenCode = aws.String(code)
} else {
return credentials.Value{ProviderName: ProviderName},
awserr.New("AssumeRoleTokenNotAvailable",
"assume role with MFA enabled, but neither TokenCode nor TokenProvider are set", nil)
}
}
roleOutput, err := p.Client.AssumeRole(input)
roleOutput, err := p.Client.AssumeRole(input)
if err != nil {
return credentials.Value{ProviderName: ProviderName}, err
}

View file

@ -9,18 +9,21 @@ package defaults
import (
"fmt"
"net"
"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"
"github.com/aws/aws-sdk-go/aws/credentials/endpointcreds"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/private/endpoints"
)
// A Defaults provides a collection of default values for SDK clients.
@ -56,7 +59,7 @@ func Config() *aws.Config {
WithMaxRetries(aws.UseServiceDefaultRetries).
WithLogger(aws.NewDefaultLogger()).
WithLogLevel(aws.LogOff).
WithSleepDelay(time.Sleep)
WithEndpointResolver(endpoints.DefaultResolver())
}
// Handlers returns the default request handlers.
@ -70,6 +73,7 @@ func Handlers() request.Handlers {
handlers.Validate.PushBackNamed(corehandlers.ValidateEndpointHandler)
handlers.Validate.AfterEachFn = request.HandlerListStopOnError
handlers.Build.PushBackNamed(corehandlers.SDKVersionUserAgentHandler)
handlers.Build.PushBackNamed(corehandlers.AddHostExecEnvUserAgentHander)
handlers.Build.AfterEachFn = request.HandlerListStopOnError
handlers.Sign.PushBackNamed(corehandlers.BuildContentLengthHandler)
handlers.Send.PushBackNamed(corehandlers.ValidateReqSigHandler)
@ -96,23 +100,80 @@ 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`
var lookupHostFn = net.LookupHost
return endpointcreds.NewProviderClient(cfg, handlers,
fmt.Sprintf("http://%s%s", host, uri),
func isLoopbackHost(host string) (bool, error) {
ip := net.ParseIP(host)
if ip != nil {
return ip.IsLoopback(), nil
}
// Host is not an ip, perform lookup
addrs, err := lookupHostFn(host)
if err != nil {
return false, err
}
for _, addr := range addrs {
if !net.ParseIP(addr).IsLoopback() {
return false, nil
}
}
return true, nil
}
func localHTTPCredProvider(cfg aws.Config, handlers request.Handlers, u string) credentials.Provider {
var errMsg string
parsed, err := url.Parse(u)
if err != nil {
errMsg = fmt.Sprintf("invalid URL, %v", err)
} else {
host := aws.URLHostname(parsed)
if len(host) == 0 {
errMsg = "unable to parse host from local HTTP cred provider URL"
} else if isLoopback, loopbackErr := isLoopbackHost(host); loopbackErr != nil {
errMsg = fmt.Sprintf("failed to resolve host %q, %v", host, loopbackErr)
} else if !isLoopback {
errMsg = fmt.Sprintf("invalid endpoint host, %q, only loopback hosts are allowed.", 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
},
@ -120,11 +181,14 @@ func ecsCredProvider(cfg aws.Config, handlers request.Handlers, uri string) cred
}
func ec2RoleProvider(cfg aws.Config, handlers request.Handlers) credentials.Provider {
endpoint, signingRegion := endpoints.EndpointForRegion(ec2metadata.ServiceName,
aws.StringValue(cfg.Region), true, false)
resolver := cfg.EndpointResolver
if resolver == nil {
resolver = endpoints.DefaultResolver()
}
e, _ := resolver.EndpointFor(endpoints.Ec2metadataServiceID, "")
return &ec2rolecreds.EC2RoleProvider{
Client: ec2metadata.NewClient(cfg, handlers, endpoint, signingRegion),
Client: ec2metadata.NewClient(cfg, handlers, e.URL, e.SigningRegion),
ExpiryWindow: 5 * time.Minute,
}
}

View file

@ -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
vendor/github.com/aws/aws-sdk-go/aws/doc.go generated vendored Normal file
View file

@ -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

View file

@ -133,7 +133,7 @@ func (c *EC2Metadata) Available() bool {
return true
}
// An EC2IAMInfo provides the shape for unmarshalling
// An EC2IAMInfo provides the shape for unmarshaling
// an IAM info from the metadata API
type EC2IAMInfo struct {
Code string
@ -142,7 +142,7 @@ type EC2IAMInfo struct {
InstanceProfileID string
}
// An EC2InstanceIdentityDocument provides the shape for unmarshalling
// An EC2InstanceIdentityDocument provides the shape for unmarshaling
// an instance identity document
type EC2InstanceIdentityDocument struct {
DevpayProductCodes []string `json:"devpayProductCodes"`

View file

@ -1,5 +1,10 @@
// Package ec2metadata provides the client for making API calls to the
// EC2 Metadata service.
//
// This package's client can be disabled completely by setting the environment
// variable "AWS_EC2_METADATA_DISABLED=true". This environment variable set to
// true instructs the SDK to disable the EC2 Metadata client. The client cannot
// be used while the environemnt variable is set to true, (case insensitive).
package ec2metadata
import (
@ -7,17 +12,21 @@ import (
"errors"
"io"
"net/http"
"os"
"strings"
"time"
"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/request"
)
// ServiceName is the name of the service.
const ServiceName = "ec2metadata"
const disableServiceEnvVar = "AWS_EC2_METADATA_DISABLED"
// A EC2Metadata is an EC2 Metadata service Client.
type EC2Metadata struct {
@ -75,6 +84,21 @@ func NewClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegio
svc.Handlers.Validate.Clear()
svc.Handlers.Validate.PushBack(validateEndpointHandler)
// Disable the EC2 Metadata service if the environment variable is set.
// This shortcirctes the service's functionality to always fail to send
// requests.
if strings.ToLower(os.Getenv(disableServiceEnvVar)) == "true" {
svc.Handlers.Send.SwapNamed(request.NamedHandler{
Name: corehandlers.SendHandler.Name,
Fn: func(r *request.Request) {
r.Error = awserr.New(
request.CanceledErrorCode,
"EC2 IMDS access disabled via "+disableServiceEnvVar+" env var",
nil)
},
})
}
// Add additional options to the service config
for _, option := range opts {
option(svc.Client)

View file

@ -0,0 +1,133 @@
package endpoints
import (
"encoding/json"
"fmt"
"io"
"github.com/aws/aws-sdk-go/aws/awserr"
)
type modelDefinition map[string]json.RawMessage
// A DecodeModelOptions are the options for how the endpoints model definition
// are decoded.
type DecodeModelOptions struct {
SkipCustomizations bool
}
// Set combines all of the option functions together.
func (d *DecodeModelOptions) Set(optFns ...func(*DecodeModelOptions)) {
for _, fn := range optFns {
fn(d)
}
}
// DecodeModel unmarshals a Regions and Endpoint model definition file into
// a endpoint Resolver. If the file format is not supported, or an error occurs
// when unmarshaling the model an error will be returned.
//
// 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.
//
// resolver, err := endpoints.DecodeModel(reader)
//
// partitions := resolver.(endpoints.EnumPartitions).Partitions()
// for _, p := range partitions {
// // ... inspect partitions
// }
func DecodeModel(r io.Reader, optFns ...func(*DecodeModelOptions)) (Resolver, error) {
var opts DecodeModelOptions
opts.Set(optFns...)
// Get the version of the partition file to determine what
// unmarshaling model to use.
modelDef := modelDefinition{}
if err := json.NewDecoder(r).Decode(&modelDef); err != nil {
return nil, newDecodeModelError("failed to decode endpoints model", err)
}
var version string
if b, ok := modelDef["version"]; ok {
version = string(b)
} else {
return nil, newDecodeModelError("endpoints version not found in model", nil)
}
if version == "3" {
return decodeV3Endpoints(modelDef, opts)
}
return nil, newDecodeModelError(
fmt.Sprintf("endpoints version %s, not supported", version), nil)
}
func decodeV3Endpoints(modelDef modelDefinition, opts DecodeModelOptions) (Resolver, error) {
b, ok := modelDef["partitions"]
if !ok {
return nil, newDecodeModelError("endpoints model missing partitions", nil)
}
ps := partitions{}
if err := json.Unmarshal(b, &ps); err != nil {
return nil, newDecodeModelError("failed to decode endpoints model", err)
}
if opts.SkipCustomizations {
return ps, nil
}
// Customization
for i := 0; i < len(ps); i++ {
p := &ps[i]
custAddEC2Metadata(p)
custAddS3DualStack(p)
custRmIotDataService(p)
}
return ps, nil
}
func custAddS3DualStack(p *partition) {
if p.ID != "aws" {
return
}
s, ok := p.Services["s3"]
if !ok {
return
}
s.Defaults.HasDualStack = boxedTrue
s.Defaults.DualStackHostname = "{service}.dualstack.{region}.{dnsSuffix}"
p.Services["s3"] = s
}
func custAddEC2Metadata(p *partition) {
p.Services["ec2metadata"] = service{
IsRegionalized: boxedFalse,
PartitionEndpoint: "aws-global",
Endpoints: endpoints{
"aws-global": endpoint{
Hostname: "169.254.169.254/latest",
Protocols: []string{"http"},
},
},
}
}
func custRmIotDataService(p *partition) {
delete(p.Services, "data.iot")
}
type decodeModelError struct {
awsError
}
func newDecodeModelError(msg string, err error) decodeModelError {
return decodeModelError{
awsError: awserr.New("DecodeEndpointsModelError", msg, err),
}
}

File diff suppressed because it is too large Load diff

66
vendor/github.com/aws/aws-sdk-go/aws/endpoints/doc.go generated vendored Normal file
View file

@ -0,0 +1,66 @@
// Package endpoints provides the types and functionality for defining regions
// and endpoints, as well as querying those definitions.
//
// The SDK's Regions and Endpoints metadata is code generated into the endpoints
// package, and is accessible via the DefaultResolver function. This function
// returns a endpoint Resolver will search the metadata and build an associated
// endpoint if one is found. The default resolver will search all partitions
// known by the SDK. e.g AWS Standard (aws), AWS China (aws-cn), and
// AWS GovCloud (US) (aws-us-gov).
// .
//
// Enumerating Regions and Endpoint Metadata
//
// Casting the Resolver returned by DefaultResolver to a EnumPartitions interface
// will allow you to get access to the list of underlying Partitions with the
// Partitions method. This is helpful if you want to limit the SDK's endpoint
// resolving to a single partition, or enumerate regions, services, and endpoints
// in the partition.
//
// resolver := endpoints.DefaultResolver()
// partitions := resolver.(endpoints.EnumPartitions).Partitions()
//
// for _, p := range partitions {
// fmt.Println("Regions for", p.ID())
// for id, _ := range p.Regions() {
// fmt.Println("*", id)
// }
//
// fmt.Println("Services for", p.ID())
// for id, _ := range p.Services() {
// fmt.Println("*", id)
// }
// }
//
// Using Custom Endpoints
//
// The endpoints package also gives you the ability to use your own logic how
// endpoints are resolved. This is a great way to define a custom endpoint
// for select services, without passing that logic down through your code.
//
// If a type implements the Resolver interface it can be used to resolve
// endpoints. To use this with the SDK's Session and Config set the value
// of the type to the EndpointsResolver field of aws.Config when initializing
// the session, or service client.
//
// In addition the ResolverFunc is a wrapper for a func matching the signature
// of Resolver.EndpointFor, converting it to a type that satisfies the
// Resolver interface.
//
//
// myCustomResolver := func(service, region string, optFns ...func(*endpoints.Options)) (endpoints.ResolvedEndpoint, error) {
// if service == endpoints.S3ServiceID {
// return endpoints.ResolvedEndpoint{
// URL: "s3.custom.endpoint.com",
// SigningRegion: "custom-signing-region",
// }, nil
// }
//
// return endpoints.DefaultResolver().EndpointFor(service, region, optFns...)
// }
//
// sess := session.Must(session.NewSession(&aws.Config{
// Region: aws.String("us-west-2"),
// EndpointResolver: endpoints.ResolverFunc(myCustomResolver),
// }))
package endpoints

View file

@ -0,0 +1,449 @@
package endpoints
import (
"fmt"
"regexp"
"github.com/aws/aws-sdk-go/aws/awserr"
)
// Options provide the configuration needed to direct how the
// endpoints will be resolved.
type Options struct {
// DisableSSL forces the endpoint to be resolved as HTTP.
// instead of HTTPS if the service supports it.
DisableSSL bool
// Sets the resolver to resolve the endpoint as a dualstack endpoint
// for the service. If dualstack support for a service is not known and
// StrictMatching is not enabled a dualstack endpoint for the service will
// be returned. This endpoint may not be valid. If StrictMatching is
// enabled only services that are known to support dualstack will return
// dualstack endpoints.
UseDualStack bool
// Enables strict matching of services and regions resolved endpoints.
// If the partition doesn't enumerate the exact service and region an
// error will be returned. This option will prevent returning endpoints
// that look valid, but may not resolve to any real endpoint.
StrictMatching bool
// Enables resolving a service endpoint based on the region provided if the
// service does not exist. The service endpoint ID will be used as the service
// domain name prefix. By default the endpoint resolver requires the service
// to be known when resolving endpoints.
//
// If resolving an endpoint on the partition list the provided region will
// be used to determine which partition's domain name pattern to the service
// endpoint ID with. If both the service and region are unkonwn and resolving
// the endpoint on partition list an UnknownEndpointError error will be returned.
//
// If resolving and endpoint on a partition specific resolver that partition's
// domain name pattern will be used with the service endpoint ID. If both
// region and service do not exist when resolving an endpoint on a specific
// partition the partition's domain pattern will be used to combine the
// endpoint and region together.
//
// This option is ignored if StrictMatching is enabled.
ResolveUnknownService bool
}
// Set combines all of the option functions together.
func (o *Options) Set(optFns ...func(*Options)) {
for _, fn := range optFns {
fn(o)
}
}
// DisableSSLOption sets the DisableSSL options. Can be used as a functional
// option when resolving endpoints.
func DisableSSLOption(o *Options) {
o.DisableSSL = true
}
// UseDualStackOption sets the UseDualStack option. Can be used as a functional
// option when resolving endpoints.
func UseDualStackOption(o *Options) {
o.UseDualStack = true
}
// StrictMatchingOption sets the StrictMatching option. Can be used as a functional
// option when resolving endpoints.
func StrictMatchingOption(o *Options) {
o.StrictMatching = true
}
// ResolveUnknownServiceOption sets the ResolveUnknownService option. Can be used
// as a functional option when resolving endpoints.
func ResolveUnknownServiceOption(o *Options) {
o.ResolveUnknownService = true
}
// A Resolver provides the interface for functionality to resolve endpoints.
// The build in Partition and DefaultResolver return value satisfy this interface.
type Resolver interface {
EndpointFor(service, region string, opts ...func(*Options)) (ResolvedEndpoint, error)
}
// ResolverFunc is a helper utility that wraps a function so it satisfies the
// Resolver interface. This is useful when you want to add additional endpoint
// resolving logic, or stub out specific endpoints with custom values.
type ResolverFunc func(service, region string, opts ...func(*Options)) (ResolvedEndpoint, error)
// EndpointFor wraps the ResolverFunc function to satisfy the Resolver interface.
func (fn ResolverFunc) EndpointFor(service, region string, opts ...func(*Options)) (ResolvedEndpoint, error) {
return fn(service, region, opts...)
}
var schemeRE = regexp.MustCompile("^([^:]+)://")
// AddScheme adds the HTTP or HTTPS schemes to a endpoint URL if there is no
// scheme. If disableSSL is true HTTP will set HTTP instead of the default HTTPS.
//
// If disableSSL is set, it will only set the URL's scheme if the URL does not
// contain a scheme.
func AddScheme(endpoint string, disableSSL bool) string {
if !schemeRE.MatchString(endpoint) {
scheme := "https"
if disableSSL {
scheme = "http"
}
endpoint = fmt.Sprintf("%s://%s", scheme, endpoint)
}
return endpoint
}
// EnumPartitions a provides a way to retrieve the underlying partitions that
// make up the SDK's default Resolver, or any resolver decoded from a model
// file.
//
// Use this interface with DefaultResolver and DecodeModels to get the list of
// Partitions.
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 {
id string
p *partition
}
// ID returns the identifier of the partition.
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.
//
// If the service cannot be found in the metadata the UnknownServiceError
// error will be returned. This validation will occur regardless if
// StrictMatching is enabled. To enable resolving unknown services set the
// "ResolveUnknownService" option to true. When StrictMatching is disabled
// this option allows the partition resolver to resolve a endpoint based on
// the service endpoint ID provided.
//
// When resolving endpoints you can choose to enable StrictMatching. This will
// require the provided service and region to be known by the partition.
// If the endpoint cannot be strictly resolved an error will be returned. This
// mode is useful to ensure the endpoint resolved is valid. Without
// StrictMatching enabled the endpoint returned my look valid but may not work.
// StrictMatching requires the SDK to be updated if you want to take advantage
// of new regions and services expansions.
//
// Errors that can be returned.
// * UnknownServiceError
// * UnknownEndpointError
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 {
rs := map[string]Region{}
for id, r := range p.p.Regions {
rs[id] = Region{
id: id,
desc: r.Description,
p: p.p,
}
}
return rs
}
// 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 {
ss := map[string]Service{}
for id := range p.p.Services {
ss[id] = Service{
id: id,
p: p.p,
}
}
return ss
}
// A Region provides information about a region, and ability to resolve an
// endpoint from the context of a region, given a service.
type Region struct {
id, desc string
p *partition
}
// ID returns the region's identifier.
func (r Region) ID() string { return r.id }
// Description returns the region's description. The region description
// is free text, it can be empty, and it may change between SDK releases.
func (r Region) Description() string { return r.desc }
// 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) {
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 {
ss := map[string]Service{}
for id, s := range r.p.Services {
if _, ok := s.Endpoints[r.id]; ok {
ss[id] = Service{
id: id,
p: r.p,
}
}
}
return ss
}
// A Service provides information about a service, and ability to resolve an
// endpoint from the context of a service, given a region.
type Service struct {
id string
p *partition
}
// ID returns the identifier for the service.
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) {
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 r, ok := s.p.Regions[id]; ok {
rs[id] = Region{
id: id,
desc: r.Description,
p: s.p,
}
}
}
return rs
}
// Endpoints returns a map of Endpoints indexed by their ID for all known
// endpoints for a service.
//
// 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{
id: id,
serviceID: s.id,
p: s.p,
}
}
return es
}
// A Endpoint provides information about endpoints, and provides the ability
// to resolve that endpoint for the service, and the region the endpoint
// represents.
type Endpoint struct {
id string
serviceID string
p *partition
}
// ID returns the identifier for an endpoint.
func (e Endpoint) ID() string { return e.id }
// ServiceID returns the identifier the endpoint belongs to.
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) {
return e.p.EndpointFor(e.serviceID, e.id, opts...)
}
// A ResolvedEndpoint is an endpoint that has been resolved based on a partition
// service, and region.
type ResolvedEndpoint struct {
// The endpoint URL
URL string
// The region that should be used for signing requests.
SigningRegion string
// The service name that should be used for signing requests.
SigningName string
// States that the signing name for this endpoint was derived from metadata
// passed in, but was not explicitly modeled.
SigningNameDerived bool
// The signing method that should be used for signing requests.
SigningMethod string
}
// So that the Error interface type can be included as an anonymous field
// in the requestError struct and not conflict with the error.Error() method.
type awsError awserr.Error
// A EndpointNotFoundError is returned when in StrictMatching mode, and the
// endpoint for the service and region cannot be found in any of the partitions.
type EndpointNotFoundError struct {
awsError
Partition string
Service string
Region string
}
// 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.
type UnknownServiceError struct {
awsError
Partition string
Service string
Known []string
}
// NewUnknownServiceError builds and returns UnknownServiceError.
func NewUnknownServiceError(p, s string, known []string) UnknownServiceError {
return UnknownServiceError{
awsError: awserr.New("UnknownServiceError",
"could not resolve endpoint for unknown service", nil),
Partition: p,
Service: s,
Known: known,
}
}
// String returns the string representation of the error.
func (e UnknownServiceError) Error() string {
extra := fmt.Sprintf("partition: %q, service: %q",
e.Partition, e.Service)
if len(e.Known) > 0 {
extra += fmt.Sprintf(", known: %v", e.Known)
}
return awserr.SprintError(e.Code(), e.Message(), extra, e.OrigErr())
}
// String returns the string representation of the error.
func (e UnknownServiceError) String() string {
return e.Error()
}
// A UnknownEndpointError is returned when in StrictMatching mode and the
// service is valid, but the region does not resolve to an endpoint. Includes
// a list of all known endpoints for the service.
type UnknownEndpointError struct {
awsError
Partition string
Service string
Region string
Known []string
}
// NewUnknownEndpointError builds and returns UnknownEndpointError.
func NewUnknownEndpointError(p, s, r string, known []string) UnknownEndpointError {
return UnknownEndpointError{
awsError: awserr.New("UnknownEndpointError",
"could not resolve endpoint", nil),
Partition: p,
Service: s,
Region: r,
Known: known,
}
}
// String returns the string representation of the error.
func (e UnknownEndpointError) Error() string {
extra := fmt.Sprintf("partition: %q, service: %q, region: %q",
e.Partition, e.Service, e.Region)
if len(e.Known) > 0 {
extra += fmt.Sprintf(", known: %v", e.Known)
}
return awserr.SprintError(e.Code(), e.Message(), extra, e.OrigErr())
}
// String returns the string representation of the error.
func (e UnknownEndpointError) String() string {
return e.Error()
}

View file

@ -0,0 +1,307 @@
package endpoints
import (
"fmt"
"regexp"
"strconv"
"strings"
)
type partitions []partition
func (ps partitions) EndpointFor(service, region string, opts ...func(*Options)) (ResolvedEndpoint, error) {
var opt Options
opt.Set(opts...)
for i := 0; i < len(ps); i++ {
if !ps[i].canResolveEndpoint(service, region, opt.StrictMatching) {
continue
}
return ps[i].EndpointFor(service, region, opts...)
}
// If loose matching fallback to first partition format to use
// when resolving the endpoint.
if !opt.StrictMatching && len(ps) > 0 {
return ps[0].EndpointFor(service, region, opts...)
}
return ResolvedEndpoint{}, NewUnknownEndpointError("all partitions", service, region, []string{})
}
// Partitions satisfies the EnumPartitions interface and returns a list
// of Partitions representing each partition represented in the SDK's
// endpoints model.
func (ps partitions) Partitions() []Partition {
parts := make([]Partition, 0, len(ps))
for i := 0; i < len(ps); i++ {
parts = append(parts, ps[i].Partition())
}
return parts
}
type partition struct {
ID string `json:"partition"`
Name string `json:"partitionName"`
DNSSuffix string `json:"dnsSuffix"`
RegionRegex regionRegex `json:"regionRegex"`
Defaults endpoint `json:"defaults"`
Regions regions `json:"regions"`
Services services `json:"services"`
}
func (p partition) Partition() Partition {
return Partition{
id: p.ID,
p: &p,
}
}
func (p partition) canResolveEndpoint(service, region string, strictMatch bool) bool {
s, hasService := p.Services[service]
_, hasEndpoint := s.Endpoints[region]
if hasEndpoint && hasService {
return true
}
if strictMatch {
return false
}
return p.RegionRegex.MatchString(region)
}
func (p partition) EndpointFor(service, region string, opts ...func(*Options)) (resolved ResolvedEndpoint, err error) {
var opt Options
opt.Set(opts...)
s, hasService := p.Services[service]
if !(hasService || opt.ResolveUnknownService) {
// Only return error if the resolver will not fallback to creating
// endpoint based on service endpoint ID passed in.
return resolved, NewUnknownServiceError(p.ID, service, serviceList(p.Services))
}
e, hasEndpoint := s.endpointForRegion(region)
if !hasEndpoint && opt.StrictMatching {
return resolved, NewUnknownEndpointError(p.ID, service, region, endpointList(s.Endpoints))
}
defs := []endpoint{p.Defaults, s.Defaults}
return e.resolve(service, region, p.DNSSuffix, defs, opt), nil
}
func serviceList(ss services) []string {
list := make([]string, 0, len(ss))
for k := range ss {
list = append(list, k)
}
return list
}
func endpointList(es endpoints) []string {
list := make([]string, 0, len(es))
for k := range es {
list = append(list, k)
}
return list
}
type regionRegex struct {
*regexp.Regexp
}
func (rr *regionRegex) UnmarshalJSON(b []byte) (err error) {
// Strip leading and trailing quotes
regex, err := strconv.Unquote(string(b))
if err != nil {
return fmt.Errorf("unable to strip quotes from regex, %v", err)
}
rr.Regexp, err = regexp.Compile(regex)
if err != nil {
return fmt.Errorf("unable to unmarshal region regex, %v", err)
}
return nil
}
type regions map[string]region
type region struct {
Description string `json:"description"`
}
type services map[string]service
type service struct {
PartitionEndpoint string `json:"partitionEndpoint"`
IsRegionalized boxedBool `json:"isRegionalized,omitempty"`
Defaults endpoint `json:"defaults"`
Endpoints endpoints `json:"endpoints"`
}
func (s *service) endpointForRegion(region string) (endpoint, bool) {
if s.IsRegionalized == boxedFalse {
return s.Endpoints[s.PartitionEndpoint], region == s.PartitionEndpoint
}
if e, ok := s.Endpoints[region]; ok {
return e, true
}
// Unable to find any matching endpoint, return
// blank that will be used for generic endpoint creation.
return endpoint{}, false
}
type endpoints map[string]endpoint
type endpoint struct {
Hostname string `json:"hostname"`
Protocols []string `json:"protocols"`
CredentialScope credentialScope `json:"credentialScope"`
// Custom fields not modeled
HasDualStack boxedBool `json:"-"`
DualStackHostname string `json:"-"`
// Signature Version not used
SignatureVersions []string `json:"signatureVersions"`
// SSLCommonName not used.
SSLCommonName string `json:"sslCommonName"`
}
const (
defaultProtocol = "https"
defaultSigner = "v4"
)
var (
protocolPriority = []string{"https", "http"}
signerPriority = []string{"v4", "v2"}
)
func getByPriority(s []string, p []string, def string) string {
if len(s) == 0 {
return def
}
for i := 0; i < len(p); i++ {
for j := 0; j < len(s); j++ {
if s[j] == p[i] {
return s[j]
}
}
}
return s[0]
}
func (e endpoint) resolve(service, region, dnsSuffix string, defs []endpoint, opts Options) ResolvedEndpoint {
var merged endpoint
for _, def := range defs {
merged.mergeIn(def)
}
merged.mergeIn(e)
e = merged
hostname := e.Hostname
// Offset the hostname for dualstack if enabled
if opts.UseDualStack && e.HasDualStack == boxedTrue {
hostname = e.DualStackHostname
}
u := strings.Replace(hostname, "{service}", service, 1)
u = strings.Replace(u, "{region}", region, 1)
u = strings.Replace(u, "{dnsSuffix}", dnsSuffix, 1)
scheme := getEndpointScheme(e.Protocols, opts.DisableSSL)
u = fmt.Sprintf("%s://%s", scheme, u)
signingRegion := e.CredentialScope.Region
if len(signingRegion) == 0 {
signingRegion = region
}
signingName := e.CredentialScope.Service
var signingNameDerived bool
if len(signingName) == 0 {
signingName = service
signingNameDerived = true
}
return ResolvedEndpoint{
URL: u,
SigningRegion: signingRegion,
SigningName: signingName,
SigningNameDerived: signingNameDerived,
SigningMethod: getByPriority(e.SignatureVersions, signerPriority, defaultSigner),
}
}
func getEndpointScheme(protocols []string, disableSSL bool) string {
if disableSSL {
return "http"
}
return getByPriority(protocols, protocolPriority, defaultProtocol)
}
func (e *endpoint) mergeIn(other endpoint) {
if len(other.Hostname) > 0 {
e.Hostname = other.Hostname
}
if len(other.Protocols) > 0 {
e.Protocols = other.Protocols
}
if len(other.SignatureVersions) > 0 {
e.SignatureVersions = other.SignatureVersions
}
if len(other.CredentialScope.Region) > 0 {
e.CredentialScope.Region = other.CredentialScope.Region
}
if len(other.CredentialScope.Service) > 0 {
e.CredentialScope.Service = other.CredentialScope.Service
}
if len(other.SSLCommonName) > 0 {
e.SSLCommonName = other.SSLCommonName
}
if other.HasDualStack != boxedBoolUnset {
e.HasDualStack = other.HasDualStack
}
if len(other.DualStackHostname) > 0 {
e.DualStackHostname = other.DualStackHostname
}
}
type credentialScope struct {
Region string `json:"region"`
Service string `json:"service"`
}
type boxedBool int
func (b *boxedBool) UnmarshalJSON(buf []byte) error {
v, err := strconv.ParseBool(string(buf))
if err != nil {
return err
}
if v {
*b = boxedTrue
} else {
*b = boxedFalse
}
return nil
}
const (
boxedBoolUnset boxedBool = iota
boxedFalse
boxedTrue
)

View file

@ -0,0 +1,337 @@
// +build codegen
package endpoints
import (
"fmt"
"io"
"reflect"
"strings"
"text/template"
"unicode"
)
// A CodeGenOptions are the options for code generating the endpoints into
// Go code from the endpoints model definition.
type CodeGenOptions struct {
// Options for how the model will be decoded.
DecodeModelOptions DecodeModelOptions
}
// Set combines all of the option functions together
func (d *CodeGenOptions) Set(optFns ...func(*CodeGenOptions)) {
for _, fn := range optFns {
fn(d)
}
}
// CodeGenModel given a endpoints model file will decode it and attempt to
// generate Go code from the model definition. Error will be returned if
// the code is unable to be generated, or decoded.
func CodeGenModel(modelFile io.Reader, outFile io.Writer, optFns ...func(*CodeGenOptions)) error {
var opts CodeGenOptions
opts.Set(optFns...)
resolver, err := DecodeModel(modelFile, func(d *DecodeModelOptions) {
*d = opts.DecodeModelOptions
})
if err != nil {
return err
}
tmpl := template.Must(template.New("tmpl").Funcs(funcMap).Parse(v3Tmpl))
if err := tmpl.ExecuteTemplate(outFile, "defaults", resolver); err != nil {
return fmt.Errorf("failed to execute template, %v", err)
}
return nil
}
func toSymbol(v string) string {
out := []rune{}
for _, c := range strings.Title(v) {
if !(unicode.IsNumber(c) || unicode.IsLetter(c)) {
continue
}
out = append(out, c)
}
return string(out)
}
func quoteString(v string) string {
return fmt.Sprintf("%q", v)
}
func regionConstName(p, r string) string {
return toSymbol(p) + toSymbol(r)
}
func partitionGetter(id string) string {
return fmt.Sprintf("%sPartition", toSymbol(id))
}
func partitionVarName(id string) string {
return fmt.Sprintf("%sPartition", strings.ToLower(toSymbol(id)))
}
func listPartitionNames(ps partitions) string {
names := []string{}
switch len(ps) {
case 1:
return ps[0].Name
case 2:
return fmt.Sprintf("%s and %s", ps[0].Name, ps[1].Name)
default:
for i, p := range ps {
if i == len(ps)-1 {
names = append(names, "and "+p.Name)
} else {
names = append(names, p.Name)
}
}
return strings.Join(names, ", ")
}
}
func boxedBoolIfSet(msg string, v boxedBool) string {
switch v {
case boxedTrue:
return fmt.Sprintf(msg, "boxedTrue")
case boxedFalse:
return fmt.Sprintf(msg, "boxedFalse")
default:
return ""
}
}
func stringIfSet(msg, v string) string {
if len(v) == 0 {
return ""
}
return fmt.Sprintf(msg, v)
}
func stringSliceIfSet(msg string, vs []string) string {
if len(vs) == 0 {
return ""
}
names := []string{}
for _, v := range vs {
names = append(names, `"`+v+`"`)
}
return fmt.Sprintf(msg, strings.Join(names, ","))
}
func endpointIsSet(v endpoint) bool {
return !reflect.DeepEqual(v, endpoint{})
}
func serviceSet(ps partitions) map[string]struct{} {
set := map[string]struct{}{}
for _, p := range ps {
for id := range p.Services {
set[id] = struct{}{}
}
}
return set
}
var funcMap = template.FuncMap{
"ToSymbol": toSymbol,
"QuoteString": quoteString,
"RegionConst": regionConstName,
"PartitionGetter": partitionGetter,
"PartitionVarName": partitionVarName,
"ListPartitionNames": listPartitionNames,
"BoxedBoolIfSet": boxedBoolIfSet,
"StringIfSet": stringIfSet,
"StringSliceIfSet": stringSliceIfSet,
"EndpointIsSet": endpointIsSet,
"ServicesSet": serviceSet,
}
const v3Tmpl = `
{{ define "defaults" -}}
// Code generated by aws/endpoints/v3model_codegen.go. DO NOT EDIT.
package endpoints
import (
"regexp"
)
{{ template "partition consts" . }}
{{ range $_, $partition := . }}
{{ template "partition region consts" $partition }}
{{ end }}
{{ template "service consts" . }}
{{ template "endpoint resolvers" . }}
{{- end }}
{{ define "partition consts" }}
// Partition identifiers
const (
{{ range $_, $p := . -}}
{{ ToSymbol $p.ID }}PartitionID = {{ QuoteString $p.ID }} // {{ $p.Name }} partition.
{{ end -}}
)
{{- end }}
{{ define "partition region consts" }}
// {{ .Name }} partition's regions.
const (
{{ range $id, $region := .Regions -}}
{{ ToSymbol $id }}RegionID = {{ QuoteString $id }} // {{ $region.Description }}.
{{ end -}}
)
{{- end }}
{{ define "service consts" }}
// Service identifiers
const (
{{ $serviceSet := ServicesSet . -}}
{{ range $id, $_ := $serviceSet -}}
{{ ToSymbol $id }}ServiceID = {{ QuoteString $id }} // {{ ToSymbol $id }}.
{{ end -}}
)
{{- end }}
{{ define "endpoint resolvers" }}
// DefaultResolver returns an Endpoint resolver that will be able
// to resolve endpoints for: {{ ListPartitionNames . }}.
//
// 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 . }}.
//
// partitions := endpoints.DefaultPartitions
// for _, p := range partitions {
// // ... inspect partitions
// }
func DefaultPartitions() []Partition {
return defaultPartitions.Partitions()
}
var defaultPartitions = partitions{
{{ range $_, $partition := . -}}
{{ PartitionVarName $partition.ID }},
{{ end }}
}
{{ range $_, $partition := . -}}
{{ $name := PartitionGetter $partition.ID -}}
// {{ $name }} returns the Resolver for {{ $partition.Name }}.
func {{ $name }}() Partition {
return {{ PartitionVarName $partition.ID }}.Partition()
}
var {{ PartitionVarName $partition.ID }} = {{ template "gocode Partition" $partition }}
{{ end }}
{{ end }}
{{ define "default partitions" }}
func DefaultPartitions() []Partition {
return []partition{
{{ range $_, $partition := . -}}
// {{ ToSymbol $partition.ID}}Partition(),
{{ end }}
}
}
{{ end }}
{{ define "gocode Partition" -}}
partition{
{{ StringIfSet "ID: %q,\n" .ID -}}
{{ StringIfSet "Name: %q,\n" .Name -}}
{{ StringIfSet "DNSSuffix: %q,\n" .DNSSuffix -}}
RegionRegex: {{ template "gocode RegionRegex" .RegionRegex }},
{{ if EndpointIsSet .Defaults -}}
Defaults: {{ template "gocode Endpoint" .Defaults }},
{{- end }}
Regions: {{ template "gocode Regions" .Regions }},
Services: {{ template "gocode Services" .Services }},
}
{{- end }}
{{ define "gocode RegionRegex" -}}
regionRegex{
Regexp: func() *regexp.Regexp{
reg, _ := regexp.Compile({{ QuoteString .Regexp.String }})
return reg
}(),
}
{{- end }}
{{ define "gocode Regions" -}}
regions{
{{ range $id, $region := . -}}
"{{ $id }}": {{ template "gocode Region" $region }},
{{ end -}}
}
{{- end }}
{{ define "gocode Region" -}}
region{
{{ StringIfSet "Description: %q,\n" .Description -}}
}
{{- end }}
{{ define "gocode Services" -}}
services{
{{ range $id, $service := . -}}
"{{ $id }}": {{ template "gocode Service" $service }},
{{ end }}
}
{{- end }}
{{ define "gocode Service" -}}
service{
{{ StringIfSet "PartitionEndpoint: %q,\n" .PartitionEndpoint -}}
{{ BoxedBoolIfSet "IsRegionalized: %s,\n" .IsRegionalized -}}
{{ if EndpointIsSet .Defaults -}}
Defaults: {{ template "gocode Endpoint" .Defaults -}},
{{- end }}
{{ if .Endpoints -}}
Endpoints: {{ template "gocode Endpoints" .Endpoints }},
{{- end }}
}
{{- end }}
{{ define "gocode Endpoints" -}}
endpoints{
{{ range $id, $endpoint := . -}}
"{{ $id }}": {{ template "gocode Endpoint" $endpoint }},
{{ end }}
}
{{- end }}
{{ define "gocode Endpoint" -}}
endpoint{
{{ StringIfSet "Hostname: %q,\n" .Hostname -}}
{{ StringIfSet "SSLCommonName: %q,\n" .SSLCommonName -}}
{{ StringSliceIfSet "Protocols: []string{%s},\n" .Protocols -}}
{{ StringSliceIfSet "SignatureVersions: []string{%s},\n" .SignatureVersions -}}
{{ if or .CredentialScope.Region .CredentialScope.Service -}}
CredentialScope: credentialScope{
{{ StringIfSet "Region: %q,\n" .CredentialScope.Region -}}
{{ StringIfSet "Service: %q,\n" .CredentialScope.Service -}}
},
{{- end }}
{{ BoxedBoolIfSet "HasDualStack: %s,\n" .HasDualStack -}}
{{ StringIfSet "DualStackHostname: %q,\n" .DualStackHostname -}}
}
{{- end }}
`

12
vendor/github.com/aws/aws-sdk-go/aws/jsonvalue.go generated vendored Normal file
View file

@ -0,0 +1,12 @@
package aws
// JSONValue is a representation of a grab bag type that will be marshaled
// into a json string. This type can be used just like any other map.
//
// Example:
//
// values := aws.JSONValue{
// "Foo": "Bar",
// }
// values["Baz"] = "Qux"
type JSONValue map[string]interface{}

View file

@ -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()

View file

@ -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
}

View file

@ -0,0 +1,11 @@
// +build appengine plan9
package request
import (
"strings"
)
func isErrConnectionReset(err error) bool {
return strings.Contains(err.Error(), "connection reset")
}

View file

@ -18,6 +18,7 @@ type Handlers struct {
UnmarshalError HandlerList
Retry HandlerList
AfterRetry HandlerList
Complete HandlerList
}
// Copy returns of this handler's lists.
@ -33,6 +34,7 @@ func (h *Handlers) Copy() Handlers {
UnmarshalMeta: h.UnmarshalMeta.copy(),
Retry: h.Retry.copy(),
AfterRetry: h.AfterRetry.copy(),
Complete: h.Complete.copy(),
}
}
@ -48,6 +50,7 @@ func (h *Handlers) Clear() {
h.ValidateResponse.Clear()
h.Retry.Clear()
h.AfterRetry.Clear()
h.Complete.Clear()
}
// A HandlerListRunItem represents an entry in the HandlerList which
@ -85,13 +88,17 @@ func (l *HandlerList) copy() HandlerList {
n := HandlerList{
AfterEachFn: l.AfterEachFn,
}
n.list = append([]NamedHandler{}, l.list...)
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.
@ -101,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.
@ -163,6 +222,16 @@ func HandlerListStopOnError(item HandlerListRunItem) bool {
return item.Request.Error == nil
}
// WithAppendUserAgent will add a string to the user agent prefixed with a
// single white space.
func WithAppendUserAgent(s string) Option {
return func(r *Request) {
r.Handlers.Build.PushBack(func(r2 *Request) {
AddToUserAgent(r, s)
})
}
}
// MakeAddToUserAgentHandler will add the name/version pair to the User-Agent request
// header. If the extra parameters are provided they will be added as metadata to the
// name/version pair resulting in the following format.

View file

@ -3,6 +3,8 @@ package request
import (
"io"
"sync"
"github.com/aws/aws-sdk-go/internal/sdkio"
)
// offsetReader is a thread-safe io.ReadCloser to prevent racing
@ -15,7 +17,7 @@ type offsetReader struct {
func newOffsetReader(buf io.ReadSeeker, offset int64) *offsetReader {
reader := &offsetReader{}
buf.Seek(offset, 0)
buf.Seek(offset, sdkio.SeekStart)
reader.buf = buf
return reader

View file

@ -4,6 +4,7 @@ import (
"bytes"
"fmt"
"io"
"net"
"net/http"
"net/url"
"reflect"
@ -13,6 +14,29 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/client/metadata"
"github.com/aws/aws-sdk-go/internal/sdkio"
)
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"
// ErrCodeInvalidPresignExpire is returned when the expire time provided to
// presign is invalid
ErrCodeInvalidPresignExpire = "InvalidPresignExpireError"
// 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.
@ -22,30 +46,37 @@ 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
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
// A value greater than 0 instructs the request to be signed as Presigned URL
// You should not set this field directly. Instead use Request's
// Presign or PresignRequest methods.
ExpireTime time.Duration
context aws.Context
built bool
// Need to persist an intermideant body betweend the input Body and HTTP
// Need to persist an intermediate body between the input Body and HTTP
// request body because the HTTP Client's transport can maintain a reference
// to the HTTP request's body after the client has returned. This value is
// safe to use concurrently and rewraps the input Body for each HTTP request.
// safe to use concurrently and wrap the input Body for each HTTP request.
safeBody *offsetReader
}
@ -55,14 +86,8 @@ type Operation struct {
HTTPMethod string
HTTPPath string
*Paginator
}
// Paginator keeps track of pagination configuration for an API operation.
type Paginator struct {
InputTokens []string
OutputTokens []string
LimitToken string
TruncationToken string
BeforePresignFn func(r *Request) error
}
// New returns a new Request pointer for the service API
@ -88,6 +113,8 @@ func New(cfg aws.Config, clientInfo metadata.ClientInfo, handlers Handlers,
err = awserr.New("InvalidEndpointURL", "invalid endpoint uri", err)
}
SanitizeHostForHeader(httpReq)
r := &Request{
Config: cfg,
ClientInfo: clientInfo,
@ -108,8 +135,99 @@ func New(cfg aws.Config, clientInfo metadata.ClientInfo, handlers Handlers,
return r
}
// A Option is a functional option that can augment or modify a request when
// 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.
//
// svc.PutObjectWithContext(ctx, params, request.WithLogLevel(aws.LogDebugWithHTTPBody)
func WithLogLevel(l aws.LogLevelType) Option {
return func(r *Request) {
r.Config.LogLevel = aws.LogLevel(l)
}
}
// ApplyOptions will apply each option to the request calling them in the order
// the were provided.
func (r *Request) ApplyOptions(opts ...Option) {
for _, opt := range opts {
opt(r)
}
}
// Context will always returns a non-nil context. If Request does not have a
// context aws.BackgroundContext will be returned.
func (r *Request) Context() aws.Context {
if r.context != nil {
return r.context
}
return aws.BackgroundContext()
}
// SetContext adds a Context to the current request that can be used to cancel
// a in-flight request. The Context value must not be nil, or this method will
// panic.
//
// Unlike http.Request.WithContext, SetContext does not return a copy of the
// Request. It is not safe to use use a single Request value for multiple
// requests. A new Request should be created for each API operation request.
//
// Go 1.6 and below:
// The http.Request's Cancel field will be set to the Done() value of
// the context. This will overwrite the Cancel field's value.
//
// Go 1.7 and above:
// The http.Request.WithContext will be used to set the context on the underlying
// http.Request. This will create a shallow copy of the http.Request. The SDK
// may create sub contexts in the future for nested requests such as retries.
func (r *Request) SetContext(ctx aws.Context) {
if ctx == nil {
panic("context cannot be nil")
}
setRequestContext(r, ctx)
}
// WillRetry returns if the request's can be retried.
func (r *Request) WillRetry() bool {
if !aws.IsReaderSeekable(r.Body) && r.HTTPRequest.Body != NoBody {
return false
}
return r.Error != nil && aws.BoolValue(r.Retryable) && r.RetryCount < r.MaxRetries()
}
@ -141,30 +259,70 @@ func (r *Request) SetStringBody(s string) {
// SetReaderBody will set the request's body reader.
func (r *Request) SetReaderBody(reader io.ReadSeeker) {
r.Body = reader
r.BodyStart, _ = reader.Seek(0, sdkio.SeekCurrent) // Get the Bodies current offset.
r.ResetBody()
}
// Presign returns the request's signed URL. Error will be returned
// if the signing fails.
func (r *Request) Presign(expireTime time.Duration) (string, error) {
r.ExpireTime = expireTime
//
// It is invalid to create a presigned URL with a expire duration 0 or less. An
// error is returned if expire duration is 0 or less.
func (r *Request) Presign(expire time.Duration) (string, error) {
r = r.copy()
// Presign requires all headers be hoisted. There is no way to retrieve
// the signed headers not hoisted without this. Making the presigned URL
// useless.
r.NotHoist = false
r.Sign()
if r.Error != nil {
return "", r.Error
}
return r.HTTPRequest.URL.String(), nil
u, _, err := getPresignedURL(r, expire)
return u, err
}
// PresignRequest behaves just like presign, but hoists all headers and signs them.
// Also returns the signed hash back to the user
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
// PresignRequest behaves just like presign, with the addition of returning a
// set of headers that were signed.
//
// It is invalid to create a presigned URL with a expire duration 0 or less. An
// error is returned if expire duration is 0 or less.
//
// 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(expire time.Duration) (string, http.Header, error) {
r = r.copy()
return getPresignedURL(r, expire)
}
// IsPresigned returns true if the request represents a presigned API url.
func (r *Request) IsPresigned() bool {
return r.ExpireTime != 0
}
func getPresignedURL(r *Request, expire time.Duration) (string, http.Header, error) {
if expire <= 0 {
return "", nil, awserr.New(
ErrCodeInvalidPresignExpire,
"presigned URL requires an expire duration greater than 0",
nil,
)
}
r.ExpireTime = expire
if r.Operation.BeforePresignFn != nil {
if err := r.Operation.BeforePresignFn(r); err != nil {
return "", nil, err
}
}
if err := r.Sign(); err != nil {
return "", nil, err
}
return r.HTTPRequest.URL.String(), r.SignedHeaderVals, nil
}
@ -184,7 +342,7 @@ func debugLogReqError(r *Request, stage string, retrying bool, err error) {
// Build will build the request's object so it can be signed and sent
// to the service. Build will also validate all the request's parameters.
// Anny additional build Handlers set on this request will be run
// Any additional build Handlers set on this request will be run
// in the order they were set.
//
// The request will only be built once. Multiple calls to build will have
@ -225,16 +383,55 @@ 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()
}
r.safeBody = newOffsetReader(r.Body, r.BodyStart)
r.HTTPRequest.Body = r.safeBody
// Go 1.8 tightened and clarified the rules code needs to use when building
// requests with the http package. Go 1.8 removed the automatic detection
// of if the Request.Body was empty, or actually had bytes in it. The SDK
// always sets the Request.Body even if it is empty and should not actually
// be sent. This is incorrect.
//
// Go 1.8 did add a http.NoBody value that the SDK can use to tell the http
// client that the request really should be sent without a body. The
// Request.Body cannot be set to nil, which is preferable, because the
// field is exported and could introduce nil pointer dereferences for users
// of the SDK if they used that field.
//
// Related golang/go#18257
l, err := aws.SeekerLen(r.Body)
if err != nil {
return nil, awserr.New(ErrCodeSerialization, "failed to compute request body size", err)
}
var body io.ReadCloser
if l == 0 {
body = NoBody
} else if l > 0 {
body = r.safeBody
} else {
// Hack to prevent sending bodies for methods where the body
// should be ignored by the server. Sending bodies on these
// methods without an associated ContentLength will cause the
// request to socket timeout because the server does not handle
// Transfer-Encoding: chunked bodies for these methods.
//
// This would only happen if a aws.ReaderSeekerCloser was used with
// a io.Reader that was not also an io.Seeker, or did not implement
// Len() method.
switch r.Operation.HTTPMethod {
case "GET", "HEAD", "DELETE":
body = NoBody
default:
body = r.safeBody
}
}
return body, nil
}
// GetBody will return an io.ReadSeeker of the Request's underlying
@ -257,6 +454,12 @@ func (r *Request) GetBody() io.ReadSeeker {
//
// Send will not close the request.Request's body.
func (r *Request) Send() error {
defer func() {
// Regardless of success or failure of the request trigger the Complete
// request handlers.
r.Handlers.Complete.Run(r)
}()
for {
if aws.BoolValue(r.Retryable) {
if r.Config.LogLevel.Matches(aws.LogDebugWithRequestRetries) {
@ -286,7 +489,7 @@ func (r *Request) Send() error {
r.Handlers.Send.Run(r)
if r.Error != nil {
if strings.Contains(r.Error.Error(), "net/http: request canceled") {
if !shouldRetryCancel(r) {
return r.Error
}
@ -294,7 +497,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)
@ -303,12 +506,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)
@ -321,7 +525,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)
@ -334,6 +538,17 @@ func (r *Request) Send() error {
return nil
}
// copy will copy a request which will allow for local manipulation of the
// request.
func (r *Request) copy() *Request {
req := &Request{}
*req = *r
req.Handlers = r.Handlers.Copy()
op := *r.Operation
req.Operation = &op
return req
}
// AddToUserAgent adds the string to the end of the request's current user agent.
func AddToUserAgent(r *Request, s string) {
curUA := r.HTTPRequest.Header.Get("User-Agent")
@ -342,3 +557,98 @@ func AddToUserAgent(r *Request, s string) {
}
r.HTTPRequest.Header.Set("User-Agent", s)
}
func shouldRetryCancel(r *Request) bool {
awsErr, ok := r.Error.(awserr.Error)
timeoutErr := false
errStr := r.Error.Error()
if ok {
if awsErr.Code() == CanceledErrorCode {
return false
}
err := awsErr.OrigErr()
netErr, netOK := err.(net.Error)
timeoutErr = netOK && netErr.Temporary()
if urlErr, ok := err.(*url.Error); !timeoutErr && ok {
errStr = urlErr.Err.Error()
}
}
// There can be two types of canceled errors here.
// The first being a net.Error and the other being an error.
// If the request was timed out, we want to continue the retry
// process. Otherwise, return the canceled error.
return timeoutErr ||
(errStr != "net/http: request canceled" &&
errStr != "net/http: request canceled while waiting for connection")
}
// SanitizeHostForHeader removes default port from host and updates request.Host
func SanitizeHostForHeader(r *http.Request) {
host := getHost(r)
port := portOnly(host)
if port != "" && isDefaultPort(r.URL.Scheme, port) {
r.Host = stripPort(host)
}
}
// Returns host from request
func getHost(r *http.Request) string {
if r.Host != "" {
return r.Host
}
return r.URL.Host
}
// Hostname returns u.Host, without any port number.
//
// If Host is an IPv6 literal with a port number, Hostname returns the
// IPv6 literal without the square brackets. IPv6 literals may include
// a zone identifier.
//
// Copied from the Go 1.8 standard library (net/url)
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]
}
// Port returns the port part of u.Host, without the leading colon.
// If u.Host doesn't contain a port, Port returns an empty string.
//
// Copied from the Go 1.8 standard library (net/url)
func portOnly(hostport string) string {
colon := strings.IndexByte(hostport, ':')
if colon == -1 {
return ""
}
if i := strings.Index(hostport, "]:"); i != -1 {
return hostport[i+len("]:"):]
}
if strings.Contains(hostport, "]") {
return ""
}
return hostport[colon+len(":"):]
}
// Returns true if the specified URI is using the standard port
// (i.e. port 80 for HTTP URIs or 443 for HTTPS URIs)
func isDefaultPort(scheme, port string) bool {
if port == "" {
return true
}
lowerCaseScheme := strings.ToLower(scheme)
if (lowerCaseScheme == "http" && port == "80") || (lowerCaseScheme == "https" && port == "443") {
return true
}
return false
}

View file

@ -0,0 +1,39 @@
// +build !go1.8
package request
import "io"
// NoBody is an io.ReadCloser with no bytes. Read always returns EOF
// and Close always returns nil. It can be used in an outgoing client
// request to explicitly signal that a request has zero bytes.
// An alternative, however, is to simply set Request.Body to nil.
//
// Copy of Go 1.8 NoBody type from net/http/http.go
type noBody struct{}
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 }
// NoBody is an empty reader that will trigger the Go HTTP client to not include
// and body in the HTTP request.
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
}

View file

@ -0,0 +1,33 @@
// +build go1.8
package request
import (
"net/http"
)
// NoBody is a http.NoBody reader instructing Go HTTP client to not include
// and body in the HTTP request.
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
}

View file

@ -0,0 +1,14 @@
// +build go1.7
package request
import "github.com/aws/aws-sdk-go/aws"
// setContext updates the Request to use the passed in context for cancellation.
// Context will also be used for request retry delay.
//
// Creates shallow copy of the http.Request with the WithContext method.
func setRequestContext(r *Request, ctx aws.Context) {
r.context = ctx
r.HTTPRequest = r.HTTPRequest.WithContext(ctx)
}

View file

@ -0,0 +1,14 @@
// +build !go1.7
package request
import "github.com/aws/aws-sdk-go/aws"
// setContext updates the Request to use the passed in context for cancellation.
// Context will also be used for request retry delay.
//
// Creates shallow copy of the http.Request with the WithContext method.
func setRequestContext(r *Request, ctx aws.Context) {
r.context = ctx
r.HTTPRequest.Cancel = ctx.Done()
}

View file

@ -2,29 +2,125 @@ package request
import (
"reflect"
"sync/atomic"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awsutil"
)
//type Paginater interface {
// HasNextPage() bool
// NextPage() *Request
// EachPage(fn func(data interface{}, isLastPage bool) (shouldContinue bool)) error
//}
// A Pagination provides paginating of SDK API operations which are paginatable.
// Generally you should not use this type directly, but use the "Pages" API
// operations method to automatically perform pagination for you. Such as,
// "S3.ListObjectsPages", and "S3.ListObjectsPagesWithContext" methods.
//
// Pagination differs from a Paginator type in that pagination is the type that
// does the pagination between API operations, and Paginator defines the
// configuration that will be used per page request.
//
// cont := true
// for p.Next() && cont {
// data := p.Page().(*s3.ListObjectsOutput)
// // process the page's data
// }
// return p.Err()
//
// See service client API operation Pages methods for examples how the SDK will
// use the Pagination type.
type Pagination struct {
// Function to return a Request value for each pagination request.
// Any configuration or handlers that need to be applied to the request
// prior to getting the next page should be done here before the request
// returned.
//
// NewRequest should always be built from the same API operations. It is
// undefined if different API operations are returned on subsequent calls.
NewRequest func() (*Request, error)
// HasNextPage returns true if this request has more pages of data available.
func (r *Request) HasNextPage() bool {
return len(r.nextPageTokens()) > 0
started bool
nextTokens []interface{}
err error
curPage interface{}
}
// nextPageTokens returns the tokens to use when asking for the next page of
// data.
// HasNextPage will return true if Pagination is able to determine that the API
// operation has additional pages. False will be returned if there are no more
// pages remaining.
//
// Will always return true if Next has not been called yet.
func (p *Pagination) HasNextPage() bool {
return !(p.started && len(p.nextTokens) == 0)
}
// Err returns the error Pagination encountered when retrieving the next page.
func (p *Pagination) Err() error {
return p.err
}
// Page returns the current page. Page should only be called after a successful
// call to Next. It is undefined what Page will return if Page is called after
// Next returns false.
func (p *Pagination) Page() interface{} {
return p.curPage
}
// Next will attempt to retrieve the next page for the API operation. When a page
// is retrieved true will be returned. If the page cannot be retrieved, or there
// are no more pages false will be returned.
//
// Use the Page method to retrieve the current page data. The data will need
// to be cast to the API operation's output type.
//
// Use the Err method to determine if an error occurred if Page returns false.
func (p *Pagination) Next() bool {
if !p.HasNextPage() {
return false
}
req, err := p.NewRequest()
if err != nil {
p.err = err
return false
}
if p.started {
for i, intok := range req.Operation.InputTokens {
awsutil.SetValueAtPath(req.Params, intok, p.nextTokens[i])
}
}
p.started = true
err = req.Send()
if err != nil {
p.err = err
return false
}
p.nextTokens = req.nextPageTokens()
p.curPage = req.Data
return true
}
// A Paginator is the configuration data that defines how an API operation
// should be paginated. This type is used by the API service models to define
// the generated pagination config for service APIs.
//
// The Pagination type is what provides iterating between pages of an API. It
// is only used to store the token metadata the SDK should use for performing
// pagination.
type Paginator struct {
InputTokens []string
OutputTokens []string
LimitToken string
TruncationToken string
}
// nextPageTokens returns the tokens to use when asking for the next page of data.
func (r *Request) nextPageTokens() []interface{} {
if r.Operation.Paginator == nil {
return nil
}
if r.Operation.TruncationToken != "" {
tr, _ := awsutil.ValuesAtPath(r.Data, r.Operation.TruncationToken)
if len(tr) == 0 {
@ -46,13 +142,28 @@ func (r *Request) nextPageTokens() []interface{} {
tokens := []interface{}{}
tokenAdded := false
for _, outToken := range r.Operation.OutputTokens {
v, _ := awsutil.ValuesAtPath(r.Data, outToken)
if len(v) > 0 {
tokens = append(tokens, v[0])
tokenAdded = true
} else {
vs, _ := awsutil.ValuesAtPath(r.Data, outToken)
if len(vs) == 0 {
tokens = append(tokens, nil)
continue
}
v := vs[0]
switch tv := v.(type) {
case *string:
if len(aws.StringValue(tv)) == 0 {
tokens = append(tokens, nil)
continue
}
case string:
if len(tv) == 0 {
tokens = append(tokens, nil)
continue
}
}
tokenAdded = true
tokens = append(tokens, v)
}
if !tokenAdded {
return nil
@ -61,9 +172,40 @@ func (r *Request) nextPageTokens() []interface{} {
return tokens
}
// Ensure a deprecated item is only logged once instead of each time its used.
func logDeprecatedf(logger aws.Logger, flag *int32, msg string) {
if logger == nil {
return
}
if atomic.CompareAndSwapInt32(flag, 0, 1) {
logger.Log(msg)
}
}
var (
logDeprecatedHasNextPage int32
logDeprecatedNextPage int32
logDeprecatedEachPage int32
)
// HasNextPage returns true if this request has more pages of data available.
//
// Deprecated Use Pagination type for configurable pagination of API operations
func (r *Request) HasNextPage() bool {
logDeprecatedf(r.Config.Logger, &logDeprecatedHasNextPage,
"Request.HasNextPage deprecated. Use Pagination type for configurable pagination of API operations")
return len(r.nextPageTokens()) > 0
}
// NextPage returns a new Request that can be executed to return the next
// page of result data. Call .Send() on this request to execute it.
//
// Deprecated Use Pagination type for configurable pagination of API operations
func (r *Request) NextPage() *Request {
logDeprecatedf(r.Config.Logger, &logDeprecatedNextPage,
"Request.NextPage deprecated. Use Pagination type for configurable pagination of API operations")
tokens := r.nextPageTokens()
if len(tokens) == 0 {
return nil
@ -90,7 +232,12 @@ func (r *Request) NextPage() *Request {
// as the structure "T". The lastPage value represents whether the page is
// the last page of data or not. The return value of this function should
// return true to keep iterating or false to stop.
//
// Deprecated Use Pagination type for configurable pagination of API operations
func (r *Request) EachPage(fn func(data interface{}, isLastPage bool) (shouldContinue bool)) error {
logDeprecatedf(r.Config.Logger, &logDeprecatedEachPage,
"Request.EachPage deprecated. Use Pagination type for configurable pagination of API operations")
for page := r; page != nil; page = page.NextPage() {
if err := page.Send(); err != nil {
return err

View file

@ -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,8 +38,8 @@ var throttleCodes = map[string]struct{}{
"ThrottlingException": {},
"RequestLimitExceeded": {},
"RequestThrottled": {},
"LimitExceededException": {}, // Deleting 10+ DynamoDb tables at once
"TooManyRequestsException": {}, // Lambda functions
"PriorRequestNotComplete": {}, // Route53
}
// credsExpiredCodes is a collection of error codes which signify the credentials
@ -67,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)
}

View file

@ -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)
}
}

View file

@ -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),
},

295
vendor/github.com/aws/aws-sdk-go/aws/request/waiter.go generated vendored Normal file
View file

@ -0,0 +1,295 @@
package request
import (
"fmt"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/awsutil"
)
// WaiterResourceNotReadyErrorCode is the error code returned by a waiter when
// the waiter's max attempts have been exhausted.
const WaiterResourceNotReadyErrorCode = "ResourceNotReady"
// A WaiterOption is a function that will update the Waiter value's fields to
// configure the waiter.
type WaiterOption func(*Waiter)
// WithWaiterMaxAttempts returns the maximum number of times the waiter should
// attempt to check the resource for the target state.
func WithWaiterMaxAttempts(max int) WaiterOption {
return func(w *Waiter) {
w.MaxAttempts = max
}
}
// WaiterDelay will return a delay the waiter should pause between attempts to
// check the resource state. The passed in attempt is the number of times the
// Waiter has checked the resource state.
//
// Attempt is the number of attempts the Waiter has made checking the resource
// state.
type WaiterDelay func(attempt int) time.Duration
// ConstantWaiterDelay returns a WaiterDelay that will always return a constant
// delay the waiter should use between attempts. It ignores the number of
// attempts made.
func ConstantWaiterDelay(delay time.Duration) WaiterDelay {
return func(attempt int) time.Duration {
return delay
}
}
// WithWaiterDelay will set the Waiter to use the WaiterDelay passed in.
func WithWaiterDelay(delayer WaiterDelay) WaiterOption {
return func(w *Waiter) {
w.Delay = delayer
}
}
// WithWaiterLogger returns a waiter option to set the logger a waiter
// should use to log warnings and errors to.
func WithWaiterLogger(logger aws.Logger) WaiterOption {
return func(w *Waiter) {
w.Logger = logger
}
}
// WithWaiterRequestOptions returns a waiter option setting the request
// options for each request the waiter makes. Appends to waiter's request
// options already set.
func WithWaiterRequestOptions(opts ...Option) WaiterOption {
return func(w *Waiter) {
w.RequestOptions = append(w.RequestOptions, opts...)
}
}
// 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.
type Waiter struct {
Name string
Acceptors []WaiterAcceptor
Logger aws.Logger
MaxAttempts int
Delay WaiterDelay
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.
func (w *Waiter) ApplyOptions(opts ...WaiterOption) {
for _, fn := range opts {
fn(w)
}
}
// WaiterState are states the waiter uses based on WaiterAcceptor definitions
// to identify if the resource state the waiter is waiting on has occurred.
type WaiterState int
// String returns the string representation of the waiter state.
func (s WaiterState) String() string {
switch s {
case SuccessWaiterState:
return "success"
case FailureWaiterState:
return "failure"
case RetryWaiterState:
return "retry"
default:
return "unknown waiter state"
}
}
// States the waiter acceptors will use to identify target resource states.
const (
SuccessWaiterState WaiterState = iota // waiter successful
FailureWaiterState // waiter failed
RetryWaiterState // waiter needs to be retried
)
// WaiterMatchMode is the mode that the waiter will use to match the WaiterAcceptor
// definition's Expected attribute.
type WaiterMatchMode int
// Modes the waiter will use when inspecting API response to identify target
// resource states.
const (
PathAllWaiterMatch WaiterMatchMode = iota // match on all paths
PathWaiterMatch // match on specific path
PathAnyWaiterMatch // match on any path
PathListWaiterMatch // match on list of paths
StatusWaiterMatch // match on status code
ErrorWaiterMatch // match on error
)
// String returns the string representation of the waiter match mode.
func (m WaiterMatchMode) String() string {
switch m {
case PathAllWaiterMatch:
return "pathAll"
case PathWaiterMatch:
return "path"
case PathAnyWaiterMatch:
return "pathAny"
case PathListWaiterMatch:
return "pathList"
case StatusWaiterMatch:
return "status"
case ErrorWaiterMatch:
return "error"
default:
return "unknown waiter match mode"
}
}
// WaitWithContext will make requests for the API operation using NewRequest to
// build API requests. The request's response will be compared against the
// Waiter's Acceptors to determine the successful state of the resource the
// waiter is inspecting.
//
// The passed in context must not be nil. If it is nil a panic will occur. The
// Context will be used to cancel the waiter's pending requests and retry delays.
// Use aws.BackgroundContext if no context is available.
//
// The waiter will continue until the target state defined by the Acceptors,
// or the max attempts expires.
//
// Will return the WaiterResourceNotReadyErrorCode error code if the waiter's
// retryer ShouldRetry returns false. This normally will happen when the max
// wait attempts expires.
func (w Waiter) WaitWithContext(ctx aws.Context) error {
for attempt := 1; ; attempt++ {
req, err := w.NewRequest(w.RequestOptions)
if err != nil {
waiterLogf(w.Logger, "unable to create request %v", err)
return err
}
req.Handlers.Build.PushBack(MakeAddToUserAgentFreeFormHandler("Waiter"))
err = req.Send()
// See if any of the acceptors match the request's response, or error
for _, a := range w.Acceptors {
if matched, matchErr := a.match(w.Name, w.Logger, req, err); matched {
return matchErr
}
}
// The Waiter should only check the resource state MaxAttempts times
// This is here instead of in the for loop above to prevent delaying
// unnecessary when the waiter will not retry.
if attempt == w.MaxAttempts {
break
}
// Delay to wait before inspecting the resource again
delay := w.Delay(attempt)
if sleepFn := req.Config.SleepDelay; sleepFn != nil {
// Support SleepDelay for backwards compatibility and testing
sleepFn(delay)
} 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)
}
}
}
return awserr.New(WaiterResourceNotReadyErrorCode, "exceeded wait attempts", nil)
}
// A WaiterAcceptor provides the information needed to wait for an API operation
// to complete.
type WaiterAcceptor struct {
State WaiterState
Matcher WaiterMatchMode
Argument string
Expected interface{}
}
// match returns if the acceptor found a match with the passed in request
// or error. True is returned if the acceptor made a match, error is returned
// if there was an error attempting to perform the match.
func (a *WaiterAcceptor) match(name string, l aws.Logger, req *Request, err error) (bool, error) {
result := false
var vals []interface{}
switch a.Matcher {
case PathAllWaiterMatch, PathWaiterMatch:
// Require all matches to be equal for result to match
vals, _ = awsutil.ValuesAtPath(req.Data, a.Argument)
if len(vals) == 0 {
break
}
result = true
for _, val := range vals {
if !awsutil.DeepEqual(val, a.Expected) {
result = false
break
}
}
case PathAnyWaiterMatch:
// Only a single match needs to equal for the result to match
vals, _ = awsutil.ValuesAtPath(req.Data, a.Argument)
for _, val := range vals {
if awsutil.DeepEqual(val, a.Expected) {
result = true
break
}
}
case PathListWaiterMatch:
// ignored matcher
case StatusWaiterMatch:
s := a.Expected.(int)
result = s == req.HTTPResponse.StatusCode
case ErrorWaiterMatch:
if aerr, ok := err.(awserr.Error); ok {
result = aerr.Code() == a.Expected.(string)
}
default:
waiterLogf(l, "WARNING: Waiter %s encountered unexpected matcher: %s",
name, a.Matcher)
}
if !result {
// If there was no matching result found there is nothing more to do
// for this response, retry the request.
return false, nil
}
switch a.State {
case SuccessWaiterState:
// waiter completed
return true, nil
case FailureWaiterState:
// Waiter failure state triggered
return true, awserr.New(WaiterResourceNotReadyErrorCode,
"failed waiting for successful resource state", err)
case RetryWaiterState:
// clear the error and retry the operation
return false, nil
default:
waiterLogf(l, "WARNING: Waiter %s encountered unexpected state: %s",
name, a.State)
return false, nil
}
}
func waiterLogf(logger aws.Logger, msg string, args ...interface{}) {
if logger != nil {
logger.Log(fmt.Sprintf(msg, args...))
}
}

View file

@ -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
@ -45,16 +45,16 @@ region, and profile loaded from the environment and shared config automatically.
Requires the AWS_PROFILE to be set, or "default" is used.
// Create Session
sess, err := session.NewSession()
sess := session.Must(session.NewSession())
// Create a Session with a custom region
sess, err := session.NewSession(&aws.Config{Region: aws.String("us-east-1")})
sess := session.Must(session.NewSession(&aws.Config{
Region: aws.String("us-east-1"),
}))
// Create a S3 client instance from a session
sess, err := session.NewSession()
if err != nil {
// Handle Session creation error
}
sess := session.Must(session.NewSession())
svc := s3.New(sess)
Create Session With Option Overrides
@ -67,23 +67,25 @@ Use NewSessionWithOptions when you want to provide the config profile, or
override the shared config state (AWS_SDK_LOAD_CONFIG).
// Equivalent to session.NewSession()
sess, err := session.NewSessionWithOptions(session.Options{})
sess := session.Must(session.NewSessionWithOptions(session.Options{
// Options
}))
// Specify profile to load for the session's config
sess, err := session.NewSessionWithOptions(session.Options{
sess := session.Must(session.NewSessionWithOptions(session.Options{
Profile: "profile_name",
})
}))
// Specify profile for config and region for requests
sess, err := session.NewSessionWithOptions(session.Options{
sess := session.Must(session.NewSessionWithOptions(session.Options{
Config: aws.Config{Region: aws.String("us-east-1")},
Profile: "profile_name",
})
}))
// Force enable Shared Config support
sess, err := session.NewSessionWithOptions(session.Options{
SharedConfigState: SharedConfigEnable,
})
sess := session.Must(session.NewSessionWithOptions(session.Options{
SharedConfigState: session.SharedConfigEnable,
}))
Adding Handlers
@ -93,7 +95,8 @@ handler logs every request and its payload made by a service client:
// Create a session, and add additional handlers for all service
// clients created with the Session to inherit. Adds logging handler.
sess, err := session.NewSession()
sess := session.Must(session.NewSession())
sess.Handlers.Send.PushFront(func(r *request.Request) {
// Log every request made and its payload
logger.Println("Request: %s/%s, Payload: %s",
@ -121,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
@ -138,15 +140,14 @@ the other two fields are also provided.
Assume Role values allow you to configure the SDK to assume an IAM role using
a set of credentials provided in a config file via the source_profile field.
Both "role_arn" and "source_profile" are required. The SDK does not support
assuming a role with MFA token Via the Session's constructor. You can use the
stscreds.AssumeRoleProvider credentials provider to specify custom
configuration and support for MFA.
Both "role_arn" and "source_profile" are required. The SDK supports assuming
a role with MFA token if the session option AssumeRoleTokenProvider
is set.
role_arn = arn:aws:iam::<account_number>:role/<role_name>
source_profile = profile_with_creds
external_id = 1234
mfa_serial = not supported!
mfa_serial = <serial or mfa arn>
role_session_name = session_name
Region is the region the SDK should use for looking up AWS service endpoints
@ -154,6 +155,37 @@ and signing requests.
region = us-east-1
Assume Role with MFA token
To create a session with support for assuming an IAM role with MFA set the
session option AssumeRoleTokenProvider to a function that will prompt for the
MFA token code when the SDK assumes the role and refreshes the role's credentials.
This allows you to configure the SDK via the shared config to assumea role
with MFA tokens.
In order for the SDK to assume a role with MFA the SharedConfigState
session option must be set to SharedConfigEnable, or AWS_SDK_LOAD_CONFIG
environment variable set.
The shared configuration instructs the SDK to assume an IAM role with MFA
when the mfa_serial configuration field is set in the shared config
(~/.aws/config) or shared credentials (~/.aws/credentials) file.
If mfa_serial is set in the configuration, the SDK will assume the role, and
the AssumeRoleTokenProvider session option is not set an an error will
be returned when creating the session.
sess := session.Must(session.NewSessionWithOptions(session.Options{
AssumeRoleTokenProvider: stscreds.StdinTokenProvider,
}))
// Create service client value configured for credentials
// from assumed role.
svc := s3.New(sess)
To setup assume role outside of a session see the stscrds.AssumeRoleProvider
documentation.
Environment Variables
When a Session is created several environment variables can be set to adjust
@ -218,6 +250,24 @@ $HOME/.aws/config on Linux/Unix based systems, and
AWS_CONFIG_FILE=$HOME/my_shared_config
Path to a custom Credentials Authority (CA) bundle PEM file that the SDK
will use instead of the default system's root CA bundle. Use this only
if you want to replace the CA bundle the SDK uses for TLS requests.
AWS_CA_BUNDLE=$HOME/my_custom_ca_bundle
Enabling this option will attempt to merge the Transport into the SDK's HTTP
client. If the client's Transport is not a http.Transport an error will be
returned. If the Transport's TLS config is set this option will cause the SDK
to overwrite the Transport's TLS config's RootCAs value. If the CA bundle file
contains multiple certificates all of them will be loaded.
The Session option CustomCABundle is also available when creating sessions
to also enable this feature. CustomCABundle session option field has priority
over the AWS_CA_BUNDLE environment variable, and will be used if both are set.
Setting a custom HTTPClient in the aws.Config options will override this setting.
To use this option and custom HTTP client, the HTTP client needs to be provided
when creating the session. Not the service client.
*/
package session

View file

@ -2,12 +2,15 @@ package session
import (
"os"
"path/filepath"
"strconv"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/defaults"
)
// 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
@ -75,6 +78,24 @@ type envConfig struct {
//
// AWS_CONFIG_FILE=$HOME/my_shared_config
SharedConfigFile string
// Sets the path to a custom Credentials Authroity (CA) Bundle PEM file
// 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.
//
// Enabling this option will attempt to merge the Transport
// into the SDK's HTTP client. If the client's Transport is
// not a http.Transport an error will be returned. If the
// Transport's TLS config is set this option will cause the
// SDK to overwrite the Transport's TLS config's RootCAs value.
//
// Setting a custom HTTPClient in the aws.Config options will override this setting.
// To use this option and custom HTTP client, the HTTP client needs to be provided
// when creating the session. Not the service client.
//
// AWS_CA_BUNDLE=$HOME/my_custom_ca_bundle
CustomCABundle string
}
var (
@ -98,6 +119,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.
@ -134,7 +161,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
@ -147,8 +174,17 @@ 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)
if len(cfg.SharedCredentialsFile) == 0 {
cfg.SharedCredentialsFile = defaults.SharedCredentialsFilename()
}
if len(cfg.SharedConfigFile) == 0 {
cfg.SharedConfigFile = defaults.SharedConfigFilename()
}
cfg.CustomCABundle = os.Getenv("AWS_CA_BUNDLE")
return cfg
}
@ -161,28 +197,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
}

View file

@ -1,7 +1,13 @@
package session
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
@ -10,8 +16,8 @@ import (
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
"github.com/aws/aws-sdk-go/aws/defaults"
"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/private/endpoints"
)
// A Session provides a central location to create service clients from and
@ -20,7 +26,7 @@ import (
// Sessions are safe to create service clients concurrently, but it is not safe
// to mutate the Session concurrently.
//
// The Session satisfies the service client's client.ClientConfigProvider.
// The Session satisfies the service client's client.ConfigProvider.
type Session struct {
Config *aws.Config
Handlers request.Handlers
@ -34,17 +40,17 @@ type Session struct {
// If the AWS_SDK_LOAD_CONFIG environment is set to a truthy value, the New
// method could now encounter an error when loading the configuration. When
// The environment variable is set, and an error occurs, New will return a
// session that will fail all requests reporting the error that occured while
// session that will fail all requests reporting the error that occurred while
// loading the session. Use NewSession to get the error when creating the
// session.
//
// If the AWS_SDK_LOAD_CONFIG environment variable is set to a truthy value
// the shared config file (~/.aws/config) will also be loaded, in addition to
// the shared credentials file (~/.aws/config). Values set in both the
// the shared credentials file (~/.aws/credentials). Values set in both the
// shared config, and shared credentials will be taken from the shared
// credentials file.
//
// Deprecated: Use NewSession functiions to create sessions instead. NewSession
// Deprecated: Use NewSession functions to create sessions instead. NewSession
// has the same functionality as New except an error can be returned when the
// func is called instead of waiting to receive an error until a request is made.
func New(cfgs ...*aws.Config) *Session {
@ -52,14 +58,19 @@ func New(cfgs ...*aws.Config) *Session {
envCfg := loadEnvConfig()
if envCfg.EnableSharedConfig {
s, err := newSession(envCfg, cfgs...)
var cfg aws.Config
cfg.MergeIn(cfgs...)
s, err := NewSessionWithOptions(Options{
Config: cfg,
SharedConfigState: SharedConfigEnable,
})
if err != nil {
// Old session.New expected all errors to be discovered when
// a request is made, and would report the errors then. This
// needs to be replicated if an error occurs while creating
// the session.
msg := "failed to create session with AWS_SDK_LOAD_CONFIG enabled. " +
"Use session.NewSession to handle errors occuring during session creation."
"Use session.NewSession to handle errors occurring during session creation."
// Session creation failed, need to report the error and prevent
// any requests from succeeding.
@ -73,7 +84,7 @@ func New(cfgs ...*aws.Config) *Session {
return s
}
return oldNewSession(cfgs...)
return deprecatedNewSession(cfgs...)
}
// NewSession returns a new Session created from SDK defaults, config files,
@ -83,18 +94,19 @@ func New(cfgs ...*aws.Config) *Session {
//
// If the AWS_SDK_LOAD_CONFIG environment variable is set to a truthy value
// the shared config file (~/.aws/config) will also be loaded in addition to
// the shared credentials file (~/.aws/config). Values set in both the
// the shared credentials file (~/.aws/credentials). Values set in both the
// shared config, and shared credentials will be taken from the shared
// credentials file. Enabling the Shared Config will also allow the Session
// to be built with retrieving credentials with AssumeRole set in the config.
//
// See the NewSessionWithOptions func for information on how to override or
// control through code how the Session will be created. Such as specifing the
// control through code how the Session will be created. Such as specifying the
// config profile, and controlling if shared config is enabled or not.
func NewSession(cfgs ...*aws.Config) (*Session, error) {
envCfg := loadEnvConfig()
opts := Options{}
opts.Config.MergeIn(cfgs...)
return newSession(envCfg, cfgs...)
return NewSessionWithOptions(opts)
}
// SharedConfigState provides the ability to optionally override the state
@ -124,7 +136,7 @@ type Options struct {
// Provides config values for the SDK to use when creating service clients
// and making API requests to services. Any value set in with this field
// will override the associated value provided by the SDK defaults,
// environment or config files where relevent.
// environment or config files where relevant.
//
// If not set, configuration values from from SDK defaults, environment,
// config will be used.
@ -147,6 +159,45 @@ type Options struct {
// will allow you to override the AWS_SDK_LOAD_CONFIG environment variable
// 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
// it is not set an error will be returned when creating the session.
//
// This token provider will be called when ever the assumed role's
// credentials need to be refreshed. Within the context of service clients
// all sharing the same session the SDK will ensure calls to the token
// provider are atomic. When sharing a token provider across multiple
// sessions additional synchronization logic is needed to ensure the
// token providers do not introduce race conditions. It is recommend to
// share the session where possible.
//
// stscreds.StdinTokenProvider is a basic implementation that will prompt
// from stdin for the MFA token code.
//
// This field is only used if the shared configuration is enabled, and
// the config enables assume role wit MFA via the mfa_serial field.
AssumeRoleTokenProvider func() (string, error)
// Reader for a custom Credentials Authority (CA) bundle in PEM format that
// the SDK will use instead of the default system's root CA bundle. Use this
// only if you want to replace the CA bundle the SDK uses for TLS requests.
//
// Enabling this option will attempt to merge the Transport into the SDK's HTTP
// client. If the client's Transport is not a http.Transport an error will be
// returned. If the Transport's TLS config is set this option will cause the SDK
// to overwrite the Transport's TLS config's RootCAs value. If the CA
// bundle reader contains multiple certificates all of them will be loaded.
//
// The Session option CustomCABundle is also available when creating sessions
// to also enable this feature. CustomCABundle session option field has priority
// over the AWS_CA_BUNDLE environment variable, and will be used if both are set.
CustomCABundle io.Reader
}
// NewSessionWithOptions returns a new Session created from SDK defaults, config files,
@ -155,29 +206,29 @@ type Options struct {
//
// If the AWS_SDK_LOAD_CONFIG environment variable is set to a truthy value
// the shared config file (~/.aws/config) will also be loaded in addition to
// the shared credentials file (~/.aws/config). Values set in both the
// the shared credentials file (~/.aws/credentials). Values set in both the
// shared config, and shared credentials will be taken from the shared
// credentials file. Enabling the Shared Config will also allow the Session
// to be built with retrieving credentials with AssumeRole set in the config.
//
// // Equivalent to session.New
// sess, err := session.NewSessionWithOptions(session.Options{})
// sess := session.Must(session.NewSessionWithOptions(session.Options{}))
//
// // Specify profile to load for the session's config
// sess, err := session.NewSessionWithOptions(session.Options{
// sess := session.Must(session.NewSessionWithOptions(session.Options{
// Profile: "profile_name",
// })
// }))
//
// // Specify profile for config and region for requests
// sess, err := session.NewSessionWithOptions(session.Options{
// sess := session.Must(session.NewSessionWithOptions(session.Options{
// Config: aws.Config{Region: aws.String("us-east-1")},
// Profile: "profile_name",
// })
// }))
//
// // Force enable Shared Config support
// sess, err := session.NewSessionWithOptions(session.Options{
// SharedConfigState: SharedConfigEnable,
// })
// sess := session.Must(session.NewSessionWithOptions(session.Options{
// SharedConfigState: session.SharedConfigEnable,
// }))
func NewSessionWithOptions(opts Options) (*Session, error) {
var envCfg envConfig
if opts.SharedConfigState == SharedConfigEnable {
@ -197,7 +248,18 @@ func NewSessionWithOptions(opts Options) (*Session, error) {
envCfg.EnableSharedConfig = true
}
return newSession(envCfg, &opts.Config)
// 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)
if err != nil {
return nil, awserr.New("LoadCustomCABundleError",
"failed to open custom CA bundle PEM file", err)
}
defer f.Close()
opts.CustomCABundle = f
}
return newSession(opts, envCfg, &opts.Config)
}
// Must is a helper function to ensure the Session is valid and there was no
@ -215,13 +277,18 @@ func Must(sess *Session, err error) *Session {
return sess
}
func oldNewSession(cfgs ...*aws.Config) *Session {
func deprecatedNewSession(cfgs ...*aws.Config) *Session {
cfg := defaults.Config()
handlers := defaults.Handlers()
// Apply the passed in configs so the configuration can be applied to the
// default credential chain
cfg.MergeIn(cfgs...)
if cfg.EndpointResolver == nil {
// An endpoint resolver is required for a session to be able to provide
// endpoints for service client configurations.
cfg.EndpointResolver = endpoints.DefaultResolver()
}
cfg.Credentials = defaults.CredChain(cfg, handlers)
// Reapply any passed in configs to override credentials if set
@ -237,7 +304,7 @@ func oldNewSession(cfgs ...*aws.Config) *Session {
return s
}
func newSession(envCfg envConfig, cfgs ...*aws.Config) (*Session, error) {
func newSession(opts Options, envCfg envConfig, cfgs ...*aws.Config) (*Session, error) {
cfg := defaults.Config()
handlers := defaults.Handlers()
@ -246,13 +313,18 @@ func newSession(envCfg envConfig, cfgs ...*aws.Config) (*Session, error) {
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)
@ -261,7 +333,9 @@ func newSession(envCfg envConfig, cfgs ...*aws.Config) (*Session, error) {
return nil, err
}
mergeConfigSrcs(cfg, userCfg, envCfg, sharedCfg, handlers)
if err := mergeConfigSrcs(cfg, userCfg, envCfg, sharedCfg, handlers, opts); err != nil {
return nil, err
}
s := &Session{
Config: cfg,
@ -270,10 +344,62 @@ func newSession(envCfg envConfig, cfgs ...*aws.Config) (*Session, error) {
initHandlers(s)
// Setup HTTP client with custom cert bundle if enabled
if opts.CustomCABundle != nil {
if err := loadCustomCABundle(s, opts.CustomCABundle); err != nil {
return nil, err
}
}
return s, nil
}
func mergeConfigSrcs(cfg, userCfg *aws.Config, envCfg envConfig, sharedCfg sharedConfig, handlers request.Handlers) {
func loadCustomCABundle(s *Session, bundle io.Reader) error {
var t *http.Transport
switch v := s.Config.HTTPClient.Transport.(type) {
case *http.Transport:
t = v
default:
if s.Config.HTTPClient.Transport != nil {
return awserr.New("LoadCustomCABundleError",
"unable to load custom CA bundle, HTTPClient's transport unsupported type", nil)
}
}
if t == nil {
t = &http.Transport{}
}
p, err := loadCertPool(bundle)
if err != nil {
return err
}
if t.TLSClientConfig == nil {
t.TLSClientConfig = &tls.Config{}
}
t.TLSClientConfig.RootCAs = p
s.Config.HTTPClient.Transport = t
return nil
}
func loadCertPool(r io.Reader) (*x509.CertPool, error) {
b, err := ioutil.ReadAll(r)
if err != nil {
return nil, awserr.New("LoadCustomCABundleError",
"failed to read custom CA bundle PEM file", err)
}
p := x509.NewCertPool()
if !p.AppendCertsFromPEM(b) {
return nil, awserr.New("LoadCustomCABundleError",
"failed to load custom CA bundle PEM file", err)
}
return p, nil
}
func mergeConfigSrcs(cfg, userCfg *aws.Config, envCfg envConfig, sharedCfg sharedConfig, handlers request.Handlers, sessOpts Options) error {
// Merge in user provided configuration
cfg.MergeIn(userCfg)
@ -297,6 +423,11 @@ func mergeConfigSrcs(cfg, userCfg *aws.Config, envCfg envConfig, sharedCfg share
cfgCp.Credentials = credentials.NewStaticCredentialsFromCreds(
sharedCfg.AssumeRoleSource.Creds,
)
if len(sharedCfg.AssumeRole.MFASerial) > 0 && sessOpts.AssumeRoleTokenProvider == nil {
// AssumeRole Token provider is required if doing Assume Role
// with MFA.
return AssumeRoleTokenProviderNotSetError{}
}
cfg.Credentials = stscreds.NewCredentials(
&Session{
Config: &cfgCp,
@ -306,11 +437,16 @@ func mergeConfigSrcs(cfg, userCfg *aws.Config, envCfg envConfig, sharedCfg share
func(opt *stscreds.AssumeRoleProvider) {
opt.RoleSessionName = sharedCfg.AssumeRole.RoleSessionName
// Assume role with external ID
if len(sharedCfg.AssumeRole.ExternalID) > 0 {
opt.ExternalID = aws.String(sharedCfg.AssumeRole.ExternalID)
}
// MFA not supported
// Assume role with MFA
if len(sharedCfg.AssumeRole.MFASerial) > 0 {
opt.SerialNumber = aws.String(sharedCfg.AssumeRole.MFASerial)
opt.TokenProvider = sessOpts.AssumeRoleTokenProvider
}
},
)
} else if len(sharedCfg.Creds.AccessKeyID) > 0 {
@ -331,6 +467,33 @@ func mergeConfigSrcs(cfg, userCfg *aws.Config, envCfg envConfig, sharedCfg share
})
}
}
return nil
}
// AssumeRoleTokenProviderNotSetError is an error returned when creating a session when the
// MFAToken option is not set when shared config is configured load assume a
// role with an MFA token.
type AssumeRoleTokenProviderNotSetError struct{}
// Code is the short id of the error.
func (e AssumeRoleTokenProviderNotSetError) Code() string {
return "AssumeRoleTokenProviderNotSetError"
}
// Message is the description of the error
func (e AssumeRoleTokenProviderNotSetError) Message() string {
return fmt.Sprintf("assume role with MFA enabled, but AssumeRoleTokenProvider session option not set.")
}
// OrigErr is the underlying error that caused the failure.
func (e AssumeRoleTokenProviderNotSetError) OrigErr() error {
return nil
}
// Error satisfies the error interface.
func (e AssumeRoleTokenProviderNotSetError) Error() string {
return awserr.SprintError(e.Code(), e.Message(), "", nil)
}
type credProviderError struct {
@ -375,19 +538,69 @@ func (s *Session) Copy(cfgs ...*aws.Config) *Session {
// configure the service client instances. Passing the Session to the service
// client's constructor (New) will use this method to configure the client.
func (s *Session) ClientConfig(serviceName string, cfgs ...*aws.Config) client.Config {
// Backwards compatibility, the error will be eaten if user calls ClientConfig
// directly. All SDK services will use ClientconfigWithError.
cfg, _ := s.clientConfigWithErr(serviceName, cfgs...)
return cfg
}
func (s *Session) clientConfigWithErr(serviceName string, cfgs ...*aws.Config) (client.Config, error) {
s = s.Copy(cfgs...)
endpoint, signingRegion := endpoints.NormalizeEndpoint(
aws.StringValue(s.Config.Endpoint),
serviceName,
aws.StringValue(s.Config.Region),
aws.BoolValue(s.Config.DisableSSL),
aws.BoolValue(s.Config.UseDualStack),
)
var resolved endpoints.ResolvedEndpoint
var err error
region := aws.StringValue(s.Config.Region)
if endpoint := aws.StringValue(s.Config.Endpoint); len(endpoint) != 0 {
resolved.URL = endpoints.AddScheme(endpoint, aws.BoolValue(s.Config.DisableSSL))
resolved.SigningRegion = region
} else {
resolved, err = s.Config.EndpointResolver.EndpointFor(
serviceName, region,
func(opt *endpoints.Options) {
opt.DisableSSL = aws.BoolValue(s.Config.DisableSSL)
opt.UseDualStack = aws.BoolValue(s.Config.UseDualStack)
// Support the condition where the service is modeled but its
// endpoint metadata is not available.
opt.ResolveUnknownService = true
},
)
}
return client.Config{
Config: s.Config,
Handlers: s.Handlers,
Endpoint: endpoint,
SigningRegion: signingRegion,
Config: s.Config,
Handlers: s.Handlers,
Endpoint: resolved.URL,
SigningRegion: resolved.SigningRegion,
SigningNameDerived: resolved.SigningNameDerived,
SigningName: resolved.SigningName,
}, err
}
// ClientConfigNoResolveEndpoint is the same as ClientConfig with the exception
// that the EndpointResolver will not be used to resolve the endpoint. The only
// endpoint set must come from the aws.Config.Endpoint field.
func (s *Session) ClientConfigNoResolveEndpoint(cfgs ...*aws.Config) client.Config {
s = s.Copy(cfgs...)
var resolved endpoints.ResolvedEndpoint
region := aws.StringValue(s.Config.Region)
if ep := aws.StringValue(s.Config.Endpoint); len(ep) > 0 {
resolved.URL = endpoints.AddScheme(ep, aws.BoolValue(s.Config.DisableSSL))
resolved.SigningRegion = region
}
return client.Config{
Config: s.Config,
Handlers: s.Handlers,
Endpoint: resolved.URL,
SigningRegion: resolved.SigningRegion,
SigningNameDerived: resolved.SigningNameDerived,
SigningName: resolved.SigningName,
}
}

View file

@ -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{

View file

@ -3,9 +3,17 @@ package aws
import (
"io"
"sync"
"github.com/aws/aws-sdk-go/internal/sdkio"
)
// ReadSeekCloser wraps a io.Reader returning a ReaderSeekerCloser
// ReadSeekCloser wraps a io.Reader returning a ReaderSeekerCloser. Should
// only be used with an io.Reader that is also an io.Seeker. Doing so may
// cause request signature errors, or request body's not sent for GET, HEAD
// and DELETE HTTP methods.
//
// Deprecated: Should only be used with io.ReadSeeker. If using for
// S3 PutObject to stream content use s3manager.Uploader instead.
func ReadSeekCloser(r io.Reader) ReaderSeekerCloser {
return ReaderSeekerCloser{r}
}
@ -16,6 +24,22 @@ type ReaderSeekerCloser struct {
r io.Reader
}
// IsReaderSeekable returns if the underlying reader type can be seeked. A
// io.Reader might not actually be seekable if it is the ReaderSeekerCloser
// type.
func IsReaderSeekable(r io.Reader) bool {
switch v := r.(type) {
case ReaderSeekerCloser:
return v.IsSeeker()
case *ReaderSeekerCloser:
return v.IsSeeker()
case io.ReadSeeker:
return true
default:
return false
}
}
// Read reads from the reader up to size of p. The number of bytes read, and
// error if it occurred will be returned.
//
@ -44,6 +68,77 @@ func (r ReaderSeekerCloser) Seek(offset int64, whence int) (int64, error) {
return int64(0), nil
}
// IsSeeker returns if the underlying reader is also a seeker.
func (r ReaderSeekerCloser) IsSeeker() bool {
_, ok := r.r.(io.Seeker)
return ok
}
// HasLen returns the length of the underlying reader if the value implements
// the Len() int method.
func (r ReaderSeekerCloser) HasLen() (int, bool) {
type lenner interface {
Len() int
}
if lr, ok := r.r.(lenner); ok {
return lr.Len(), true
}
return 0, false
}
// GetLen returns the length of the bytes remaining in the underlying reader.
// Checks first for Len(), then io.Seeker to determine the size of the
// underlying reader.
//
// Will return -1 if the length cannot be determined.
func (r ReaderSeekerCloser) GetLen() (int64, error) {
if l, ok := r.HasLen(); ok {
return int64(l), nil
}
if s, ok := r.r.(io.Seeker); ok {
return seekerLen(s)
}
return -1, nil
}
// SeekerLen attempts to get the number of bytes remaining at the seeker's
// current position. Returns the number of bytes remaining or error.
func SeekerLen(s io.Seeker) (int64, error) {
// Determine if the seeker is actually seekable. ReaderSeekerCloser
// hides the fact that a io.Readers might not actually be seekable.
switch v := s.(type) {
case ReaderSeekerCloser:
return v.GetLen()
case *ReaderSeekerCloser:
return v.GetLen()
}
return seekerLen(s)
}
func seekerLen(s io.Seeker) (int64, error) {
curOffset, err := s.Seek(0, sdkio.SeekCurrent)
if err != nil {
return 0, err
}
endOffset, err := s.Seek(0, sdkio.SeekEnd)
if err != nil {
return 0, err
}
_, err = s.Seek(curOffset, sdkio.SeekStart)
if err != nil {
return 0, err
}
return endOffset - curOffset, nil
}
// Close closes the ReaderSeekerCloser.
//
// If the ReaderSeekerCloser is not an io.Closer nothing will be done.
@ -102,5 +197,5 @@ func (b *WriteAtBuffer) WriteAt(p []byte, pos int64) (n int, err error) {
func (b *WriteAtBuffer) Bytes() []byte {
b.m.Lock()
defer b.m.Unlock()
return b.buf[:len(b.buf):len(b.buf)]
return b.buf
}

12
vendor/github.com/aws/aws-sdk-go/aws/url.go generated vendored Normal file
View file

@ -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
vendor/github.com/aws/aws-sdk-go/aws/url_1_7.go generated vendored Normal file
View file

@ -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]
}

View file

@ -5,4 +5,4 @@ package aws
const SDKName = "aws-sdk-go"
// SDKVersion is the version of this SDK
const SDKVersion = "1.5.1"
const SDKVersion = "1.13.43"

View file

@ -0,0 +1,10 @@
// +build !go1.7
package sdkio
// Copy of Go 1.7 io package's Seeker constants.
const (
SeekStart = 0 // seek relative to the origin of the file
SeekCurrent = 1 // seek relative to the current offset
SeekEnd = 2 // seek relative to the end
)

View file

@ -0,0 +1,12 @@
// +build go1.7
package sdkio
import "io"
// Alias for Go 1.7 io package Seeker constants
const (
SeekStart = io.SeekStart // seek relative to the origin of the file
SeekCurrent = io.SeekCurrent // seek relative to the current offset
SeekEnd = io.SeekEnd // seek relative to the end
)

View file

@ -0,0 +1,29 @@
package sdkrand
import (
"math/rand"
"sync"
"time"
)
// lockedSource is a thread-safe implementation of rand.Source
type lockedSource struct {
lk sync.Mutex
src rand.Source
}
func (r *lockedSource) Int63() (n int64) {
r.lk.Lock()
n = r.src.Int63()
r.lk.Unlock()
return
}
func (r *lockedSource) Seed(seed int64) {
r.lk.Lock()
r.src.Seed(seed)
r.lk.Unlock()
}
// SeededRand is a new RNG using a thread safe implementation of rand.Source
var SeededRand = rand.New(&lockedSource{src: rand.NewSource(time.Now().UnixNano())})

View file

@ -0,0 +1,40 @@
package shareddefaults
import (
"os"
"path/filepath"
"runtime"
)
// 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 filepath.Join(UserHomeDir(), ".aws", "credentials")
}
// 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 filepath.Join(UserHomeDir(), ".aws", "config")
}
// UserHomeDir returns the home directory for the user the process is
// running under.
func UserHomeDir() string {
if runtime.GOOS == "windows" { // Windows
return os.Getenv("USERPROFILE")
}
// *nix
return os.Getenv("HOME")
}

98
vendor/vendor.json vendored
View file

@ -83,52 +83,52 @@
"revisionTime": "2015-10-22T06:55:26Z"
},
{
"checksumSHA1": "WNfR3yhLjRC5/uccgju/bwrdsxQ=",
"checksumSHA1": "31gfqVWNGpxR1XW3bYR8xZXZydM=",
"path": "github.com/aws/aws-sdk-go/aws",
"revision": "707203bc55114ed114446bf57949c5c211d8b7c0",
"revisionTime": "2016-11-02T21:59:28Z"
"revision": "00862f8993535bf457ed2406a807763de9e69f3a",
"revisionTime": "2018-05-07T22:54:19Z"
},
{
"checksumSHA1": "Y9W+4GimK4Fuxq+vyIskVYFRnX4=",
"path": "github.com/aws/aws-sdk-go/aws/awserr",
"revision": "707203bc55114ed114446bf57949c5c211d8b7c0",
"revisionTime": "2016-11-02T21:59:28Z"
"revision": "00862f8993535bf457ed2406a807763de9e69f3a",
"revisionTime": "2018-05-07T22:54:19Z"
},
{
"checksumSHA1": "+q4vdl3l1Wom8K1wfIpJ4jlFsbY=",
"checksumSHA1": "yyYr41HZ1Aq0hWc3J5ijXwYEcac=",
"path": "github.com/aws/aws-sdk-go/aws/awsutil",
"revision": "707203bc55114ed114446bf57949c5c211d8b7c0",
"revisionTime": "2016-11-02T21:59:28Z"
"revision": "00862f8993535bf457ed2406a807763de9e69f3a",
"revisionTime": "2018-05-07T22:54:19Z"
},
{
"checksumSHA1": "/232RBWA3KnT7U+wciPS2+wmvR0=",
"checksumSHA1": "hicPtITUionsA+dgs1SpsZUkQu4=",
"path": "github.com/aws/aws-sdk-go/aws/client",
"revision": "707203bc55114ed114446bf57949c5c211d8b7c0",
"revisionTime": "2016-11-02T21:59:28Z"
"revision": "00862f8993535bf457ed2406a807763de9e69f3a",
"revisionTime": "2018-05-07T22:54:19Z"
},
{
"checksumSHA1": "ieAJ+Cvp/PKv1LpUEnUXpc3OI6E=",
"path": "github.com/aws/aws-sdk-go/aws/client/metadata",
"revision": "707203bc55114ed114446bf57949c5c211d8b7c0",
"revisionTime": "2016-11-02T21:59:28Z"
"revision": "00862f8993535bf457ed2406a807763de9e69f3a",
"revisionTime": "2018-05-07T22:54:19Z"
},
{
"checksumSHA1": "c1N3Loy3AS9zD+m5CzpPNAED39U=",
"checksumSHA1": "vVSUnICaD9IaBQisCfw0n8zLwig=",
"path": "github.com/aws/aws-sdk-go/aws/corehandlers",
"revision": "707203bc55114ed114446bf57949c5c211d8b7c0",
"revisionTime": "2016-11-02T21:59:28Z"
"revision": "00862f8993535bf457ed2406a807763de9e69f3a",
"revisionTime": "2018-05-07T22:54:19Z"
},
{
"checksumSHA1": "zu5C95rmCZff6NYZb62lEaT5ibE=",
"checksumSHA1": "Y+cPwQL0dZMyqp3wI+KJWmA9KQ8=",
"path": "github.com/aws/aws-sdk-go/aws/credentials",
"revision": "707203bc55114ed114446bf57949c5c211d8b7c0",
"revisionTime": "2016-11-02T21:59:28Z"
"revision": "00862f8993535bf457ed2406a807763de9e69f3a",
"revisionTime": "2018-05-07T22:54:19Z"
},
{
"checksumSHA1": "KQiUK/zr3mqnAXD7x/X55/iNme0=",
"checksumSHA1": "u3GOAJLmdvbuNUeUEcZSEAOeL/0=",
"path": "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds",
"revision": "707203bc55114ed114446bf57949c5c211d8b7c0",
"revisionTime": "2016-11-02T21:59:28Z"
"revision": "00862f8993535bf457ed2406a807763de9e69f3a",
"revisionTime": "2018-05-07T22:54:19Z"
},
{
"checksumSHA1": "NUJUTWlc1sV8b7WjfiYc4JZbXl0=",
@ -137,34 +137,40 @@
"revisionTime": "2016-11-02T21:59:28Z"
},
{
"checksumSHA1": "4Ipx+5xN0gso+cENC2MHMWmQlR4=",
"checksumSHA1": "JEYqmF83O5n5bHkupAzA6STm0no=",
"path": "github.com/aws/aws-sdk-go/aws/credentials/stscreds",
"revision": "707203bc55114ed114446bf57949c5c211d8b7c0",
"revisionTime": "2016-11-02T21:59:28Z"
"revision": "00862f8993535bf457ed2406a807763de9e69f3a",
"revisionTime": "2018-05-07T22:54:19Z"
},
{
"checksumSHA1": "DwhFsNluCFEwqzyp3hbJR3q2Wqs=",
"checksumSHA1": "HogDW/Wu6ZKTDrQ2HSzG8/vj9Ag=",
"path": "github.com/aws/aws-sdk-go/aws/defaults",
"revision": "707203bc55114ed114446bf57949c5c211d8b7c0",
"revisionTime": "2016-11-02T21:59:28Z"
"revision": "00862f8993535bf457ed2406a807763de9e69f3a",
"revisionTime": "2018-05-07T22:54:19Z"
},
{
"checksumSHA1": "8E0fEBUJY/1lJOyVxzTxMGQGInk=",
"checksumSHA1": "pDnK93CqjQ4ROSW8Y/RuHXjv52M=",
"path": "github.com/aws/aws-sdk-go/aws/ec2metadata",
"revision": "707203bc55114ed114446bf57949c5c211d8b7c0",
"revisionTime": "2016-11-02T21:59:28Z"
"revision": "00862f8993535bf457ed2406a807763de9e69f3a",
"revisionTime": "2018-05-07T22:54:19Z"
},
{
"checksumSHA1": "5Ac22YMTBmrX/CXaEIXzWljr8UY=",
"checksumSHA1": "0HSVALFunQjP52Td4Gb2GLsrWCo=",
"path": "github.com/aws/aws-sdk-go/aws/endpoints",
"revision": "00862f8993535bf457ed2406a807763de9e69f3a",
"revisionTime": "2018-05-07T22:54:19Z"
},
{
"checksumSHA1": "CntJM8T3dRgC0e1i66CIbP8uCSY=",
"path": "github.com/aws/aws-sdk-go/aws/request",
"revision": "707203bc55114ed114446bf57949c5c211d8b7c0",
"revisionTime": "2016-11-02T21:59:28Z"
"revision": "00862f8993535bf457ed2406a807763de9e69f3a",
"revisionTime": "2018-05-07T22:54:19Z"
},
{
"checksumSHA1": "eOo6evLMAxQfo7Qkc5/h5euN1Sw=",
"checksumSHA1": "J66FLqo++F1PXLUU8XQqYaageSc=",
"path": "github.com/aws/aws-sdk-go/aws/session",
"revision": "707203bc55114ed114446bf57949c5c211d8b7c0",
"revisionTime": "2016-11-02T21:59:28Z"
"revision": "00862f8993535bf457ed2406a807763de9e69f3a",
"revisionTime": "2018-05-07T22:54:19Z"
},
{
"checksumSHA1": "diXvBs1LRC0RJ9WK6sllWKdzC04=",
@ -172,6 +178,24 @@
"revision": "707203bc55114ed114446bf57949c5c211d8b7c0",
"revisionTime": "2016-11-02T21:59:28Z"
},
{
"checksumSHA1": "wjxQlU1PYxrDRFoL1Vek8Wch7jk=",
"path": "github.com/aws/aws-sdk-go/internal/sdkio",
"revision": "00862f8993535bf457ed2406a807763de9e69f3a",
"revisionTime": "2018-05-07T22:54:19Z"
},
{
"checksumSHA1": "MYLldFRnsZh21TfCkgkXCT3maPU=",
"path": "github.com/aws/aws-sdk-go/internal/sdkrand",
"revision": "00862f8993535bf457ed2406a807763de9e69f3a",
"revisionTime": "2018-05-07T22:54:19Z"
},
{
"checksumSHA1": "04ypv4x12l4q0TksA1zEVsmgpvw=",
"path": "github.com/aws/aws-sdk-go/internal/shareddefaults",
"revision": "00862f8993535bf457ed2406a807763de9e69f3a",
"revisionTime": "2018-05-07T22:54:19Z"
},
{
"checksumSHA1": "Esab5F8KswqkTdB4TtjSvZgs56k=",
"path": "github.com/aws/aws-sdk-go/private/endpoints",