Commit 9737b411 authored by David Ashpole's avatar David Ashpole

updated cadvisor to latest version; updated aws dependency to same as cadvisor

parent 6dfe5c49
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -14,13 +14,13 @@ package awserr ...@@ -14,13 +14,13 @@ package awserr
// if err != nil { // if err != nil {
// if awsErr, ok := err.(awserr.Error); ok { // if awsErr, ok := err.(awserr.Error); ok {
// // Get error details // // Get error details
// log.Println("Error:", err.Code(), err.Message()) // log.Println("Error:", awsErr.Code(), awsErr.Message())
// //
// // Prints out full error message, including original error if there was one. // // Prints out full error message, including original error if there was one.
// log.Println("Error:", err.Error()) // log.Println("Error:", awsErr.Error())
// //
// // Get original error // // Get original error
// if origErr := err.Err(); origErr != nil { // if origErr := awsErr.OrigErr(); origErr != nil {
// // operate on original error. // // operate on original error.
// } // }
// } else { // } else {
...@@ -42,15 +42,55 @@ type Error interface { ...@@ -42,15 +42,55 @@ type Error interface {
OrigErr() error OrigErr() error
} }
// BatchError is a batch of errors which also wraps lower level errors with
// code, message, and original errors. Calling Error() will include all errors
// that occurred in the batch.
//
// Deprecated: Replaced with BatchedErrors. Only defined for backwards
// compatibility.
type BatchError interface {
// Satisfy the generic error interface.
error
// Returns the short phrase depicting the classification of the error.
Code() string
// Returns the error details message.
Message() string
// Returns the original error if one was set. Nil is returned if not set.
OrigErrs() []error
}
// BatchedErrors is a batch of errors which also wraps lower level errors with
// code, message, and original errors. Calling Error() will include all errors
// that occurred in the batch.
//
// Replaces BatchError
type BatchedErrors interface {
// Satisfy the base Error interface.
Error
// Returns the original error if one was set. Nil is returned if not set.
OrigErrs() []error
}
// New returns an Error object described by the code, message, and origErr. // New returns an Error object described by the code, message, and origErr.
// //
// If origErr satisfies the Error interface it will not be wrapped within a new // If origErr satisfies the Error interface it will not be wrapped within a new
// Error object and will instead be returned. // Error object and will instead be returned.
func New(code, message string, origErr error) Error { func New(code, message string, origErr error) Error {
if e, ok := origErr.(Error); ok && e != nil { var errs []error
return e if origErr != nil {
errs = append(errs, origErr)
} }
return newBaseError(code, message, origErr) return newBaseError(code, message, errs)
}
// NewBatchError returns an BatchedErrors with a collection of errors as an
// array of errors.
func NewBatchError(code, message string, errs []error) BatchedErrors {
return newBaseError(code, message, errs)
} }
// A RequestFailure is an interface to extract request failure information from // A RequestFailure is an interface to extract request failure information from
...@@ -63,9 +103,9 @@ func New(code, message string, origErr error) Error { ...@@ -63,9 +103,9 @@ func New(code, message string, origErr error) Error {
// output, err := s3manage.Upload(svc, input, opts) // output, err := s3manage.Upload(svc, input, opts)
// if err != nil { // if err != nil {
// if reqerr, ok := err.(RequestFailure); ok { // if reqerr, ok := err.(RequestFailure); ok {
// log.Printf("Request failed", reqerr.Code(), reqerr.Message(), reqerr.RequestID()) // log.Println("Request failed", reqerr.Code(), reqerr.Message(), reqerr.RequestID())
// } else { // } else {
// log.Printf("Error:", err.Error() // log.Println("Error:", err.Error())
// } // }
// } // }
// //
......
...@@ -31,23 +31,27 @@ type baseError struct { ...@@ -31,23 +31,27 @@ type baseError struct {
// Optional original error this error is based off of. Allows building // Optional original error this error is based off of. Allows building
// chained errors. // chained errors.
origErr error errs []error
} }
// newBaseError returns an error object for the code, message, and err. // newBaseError returns an error object for the code, message, and errors.
// //
// code is a short no whitespace phrase depicting the classification of // code is a short no whitespace phrase depicting the classification of
// the error that is being created. // the error that is being created.
// //
// message is the free flow string containing detailed information about the error. // message is the free flow string containing detailed information about the
// error.
// //
// origErr is the error object which will be nested under the new error to be returned. // origErrs is the error objects which will be nested under the new errors to
func newBaseError(code, message string, origErr error) *baseError { // be returned.
return &baseError{ func newBaseError(code, message string, origErrs []error) *baseError {
b := &baseError{
code: code, code: code,
message: message, message: message,
origErr: origErr, errs: origErrs,
} }
return b
} }
// Error returns the string representation of the error. // Error returns the string representation of the error.
...@@ -56,7 +60,12 @@ func newBaseError(code, message string, origErr error) *baseError { ...@@ -56,7 +60,12 @@ func newBaseError(code, message string, origErr error) *baseError {
// //
// Satisfies the error interface. // Satisfies the error interface.
func (b baseError) Error() string { func (b baseError) Error() string {
return SprintError(b.code, b.message, "", b.origErr) size := len(b.errs)
if size > 0 {
return SprintError(b.code, b.message, "", errorList(b.errs))
}
return SprintError(b.code, b.message, "", nil)
} }
// String returns the string representation of the error. // String returns the string representation of the error.
...@@ -75,10 +84,28 @@ func (b baseError) Message() string { ...@@ -75,10 +84,28 @@ func (b baseError) Message() string {
return b.message return b.message
} }
// OrigErr returns the original error if one was set. Nil is returned if no error // OrigErr returns the original error if one was set. Nil is returned if no
// was set. // error was set. This only returns the first element in the list. If the full
// list is needed, use BatchedErrors.
func (b baseError) OrigErr() error { func (b baseError) OrigErr() error {
return b.origErr switch len(b.errs) {
case 0:
return nil
case 1:
return b.errs[0]
default:
if err, ok := b.errs[0].(Error); ok {
return NewBatchError(err.Code(), err.Message(), b.errs[1:])
}
return NewBatchError("BatchedErrors",
"multiple errors occurred", b.errs)
}
}
// OrigErrs returns the original errors if one was set. An empty slice is
// returned if no error was set.
func (b baseError) OrigErrs() []error {
return b.errs
} }
// So that the Error interface type can be included as an anonymous field // So that the Error interface type can be included as an anonymous field
...@@ -94,8 +121,8 @@ type requestError struct { ...@@ -94,8 +121,8 @@ type requestError struct {
requestID string requestID string
} }
// newRequestError returns a wrapped error with additional information for request // newRequestError returns a wrapped error with additional information for
// status code, and service requestID. // request status code, and service requestID.
// //
// Should be used to wrap all request which involve service requests. Even if // Should be used to wrap all request which involve service requests. Even if
// the request failed without a service response, but had an HTTP status code // the request failed without a service response, but had an HTTP status code
...@@ -133,3 +160,35 @@ func (r requestError) StatusCode() int { ...@@ -133,3 +160,35 @@ func (r requestError) StatusCode() int {
func (r requestError) RequestID() string { func (r requestError) RequestID() string {
return r.requestID return r.requestID
} }
// OrigErrs returns the original errors if one was set. An empty slice is
// returned if no error was set.
func (r requestError) OrigErrs() []error {
if b, ok := r.awsError.(BatchedErrors); ok {
return b.OrigErrs()
}
return []error{r.OrigErr()}
}
// An error list that satisfies the golang interface
type errorList []error
// Error returns the string representation of the error.
//
// Satisfies the error interface.
func (e errorList) Error() string {
msg := ""
// How do we want to handle the array size being zero
if size := len(e); size > 0 {
for i := 0; i < size; i++ {
msg += fmt.Sprintf("%s", e[i].Error())
// We check the next index to see if it is within the slice.
// If it is, then we append a newline. We do this, because unit tests
// could be broken with the additional '\n'
if i+1 < size {
msg += "\n"
}
}
}
return msg
}
...@@ -3,6 +3,7 @@ package awsutil ...@@ -3,6 +3,7 @@ package awsutil
import ( import (
"io" "io"
"reflect" "reflect"
"time"
) )
// Copy deeply copies a src structure to dst. Useful for copying request and // Copy deeply copies a src structure to dst. Useful for copying request and
...@@ -49,7 +50,14 @@ func rcopy(dst, src reflect.Value, root bool) { ...@@ -49,7 +50,14 @@ func rcopy(dst, src reflect.Value, root bool) {
} else { } else {
e := src.Type().Elem() e := src.Type().Elem()
if dst.CanSet() && !src.IsNil() { if dst.CanSet() && !src.IsNil() {
dst.Set(reflect.New(e)) if _, ok := src.Interface().(*time.Time); !ok {
dst.Set(reflect.New(e))
} else {
tempValue := reflect.New(e)
tempValue.Elem().Set(src.Elem())
// Sets time.Time's unexported values
dst.Set(tempValue)
}
} }
if src.Elem().IsValid() { if src.Elem().IsValid() {
// Keep the current root state since the depth hasn't changed // Keep the current root state since the depth hasn't changed
......
...@@ -106,8 +106,8 @@ func rValuesAtPath(v interface{}, path string, createPath, caseSensitive, nilTer ...@@ -106,8 +106,8 @@ func rValuesAtPath(v interface{}, path string, createPath, caseSensitive, nilTer
if indexStar || index != nil { if indexStar || index != nil {
nextvals = []reflect.Value{} nextvals = []reflect.Value{}
for _, value := range values { for _, valItem := range values {
value := reflect.Indirect(value) value := reflect.Indirect(valItem)
if value.Kind() != reflect.Slice { if value.Kind() != reflect.Slice {
continue continue
} }
......
...@@ -61,6 +61,12 @@ func prettify(v reflect.Value, indent int, buf *bytes.Buffer) { ...@@ -61,6 +61,12 @@ func prettify(v reflect.Value, indent int, buf *bytes.Buffer) {
buf.WriteString("\n" + strings.Repeat(" ", indent) + "}") buf.WriteString("\n" + strings.Repeat(" ", indent) + "}")
case reflect.Slice: case reflect.Slice:
strtype := v.Type().String()
if strtype == "[]uint8" {
fmt.Fprintf(buf, "<binary> len %d", v.Len())
break
}
nl, id, id2 := "", "", "" nl, id, id2 := "", "", ""
if v.Len() > 3 { if v.Len() > 3 {
nl, id, id2 = "\n", strings.Repeat(" ", indent), strings.Repeat(" ", indent+2) nl, id, id2 = "\n", strings.Repeat(" ", indent), strings.Repeat(" ", indent+2)
...@@ -91,6 +97,10 @@ func prettify(v reflect.Value, indent int, buf *bytes.Buffer) { ...@@ -91,6 +97,10 @@ func prettify(v reflect.Value, indent int, buf *bytes.Buffer) {
buf.WriteString("\n" + strings.Repeat(" ", indent) + "}") buf.WriteString("\n" + strings.Repeat(" ", indent) + "}")
default: default:
if !v.IsValid() {
fmt.Fprint(buf, "<invalid value>")
return
}
format := "%v" format := "%v"
switch v.Interface().(type) { switch v.Interface().(type) {
case string: case string:
......
...@@ -2,7 +2,6 @@ package client ...@@ -2,7 +2,6 @@ package client
import ( import (
"fmt" "fmt"
"io/ioutil"
"net/http/httputil" "net/http/httputil"
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
...@@ -12,9 +11,11 @@ import ( ...@@ -12,9 +11,11 @@ import (
// A Config provides configuration to a service client instance. // A Config provides configuration to a service client instance.
type Config struct { type Config struct {
Config *aws.Config Config *aws.Config
Handlers request.Handlers Handlers request.Handlers
Endpoint, SigningRegion string Endpoint string
SigningRegion string
SigningName string
} }
// ConfigProvider provides a generic way for a service client to receive // ConfigProvider provides a generic way for a service client to receive
...@@ -87,16 +88,24 @@ const logReqMsg = `DEBUG: Request %s/%s Details: ...@@ -87,16 +88,24 @@ const logReqMsg = `DEBUG: Request %s/%s Details:
%s %s
-----------------------------------------------------` -----------------------------------------------------`
const logReqErrMsg = `DEBUG ERROR: Request %s/%s:
---[ REQUEST DUMP ERROR ]-----------------------------
%s
-----------------------------------------------------`
func logRequest(r *request.Request) { func logRequest(r *request.Request) {
logBody := r.Config.LogLevel.Matches(aws.LogDebugWithHTTPBody) logBody := r.Config.LogLevel.Matches(aws.LogDebugWithHTTPBody)
dumpedBody, _ := httputil.DumpRequestOut(r.HTTPRequest, logBody) 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 logBody {
// Reset the request body because dumpRequest will re-wrap the r.HTTPRequest's // 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 // Body as a NoOpCloser and will not be reset after read by the HTTP
// client reader. // client reader.
r.Body.Seek(r.BodyStart, 0) r.ResetBody()
r.HTTPRequest.Body = ioutil.NopCloser(r.Body)
} }
r.Config.Logger.Log(fmt.Sprintf(logReqMsg, r.ClientInfo.ServiceName, r.Operation.Name, string(dumpedBody))) r.Config.Logger.Log(fmt.Sprintf(logReqMsg, r.ClientInfo.ServiceName, r.Operation.Name, string(dumpedBody)))
...@@ -107,11 +116,21 @@ const logRespMsg = `DEBUG: Response %s/%s Details: ...@@ -107,11 +116,21 @@ const logRespMsg = `DEBUG: Response %s/%s Details:
%s %s
-----------------------------------------------------` -----------------------------------------------------`
const logRespErrMsg = `DEBUG ERROR: Response %s/%s:
---[ RESPONSE DUMP ERROR ]-----------------------------
%s
-----------------------------------------------------`
func logResponse(r *request.Request) { func logResponse(r *request.Request) {
var msg = "no response data" var msg = "no response data"
if r.HTTPResponse != nil { if r.HTTPResponse != nil {
logBody := r.Config.LogLevel.Matches(aws.LogDebugWithHTTPBody) logBody := r.Config.LogLevel.Matches(aws.LogDebugWithHTTPBody)
dumpedBody, _ := httputil.DumpResponse(r.HTTPResponse, logBody) 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) msg = string(dumpedBody)
} else if r.Error != nil { } else if r.Error != nil {
msg = r.Error.Error() msg = r.Error.Error()
......
package client package client
import ( import (
"math"
"math/rand" "math/rand"
"sync"
"time" "time"
"github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/request"
...@@ -30,16 +30,61 @@ func (d DefaultRetryer) MaxRetries() int { ...@@ -30,16 +30,61 @@ func (d DefaultRetryer) MaxRetries() int {
return d.NumMaxRetries return d.NumMaxRetries
} }
var seededRand = rand.New(&lockedSource{src: rand.NewSource(time.Now().UnixNano())})
// RetryRules returns the delay duration before retrying this request again // RetryRules returns the delay duration before retrying this request again
func (d DefaultRetryer) RetryRules(r *request.Request) time.Duration { func (d DefaultRetryer) RetryRules(r *request.Request) time.Duration {
delay := int(math.Pow(2, float64(r.RetryCount))) * (rand.Intn(30) + 30) // Set the upper limit of delay in retrying at ~five minutes
minTime := 30
throttle := d.shouldThrottle(r)
if throttle {
minTime = 500
}
retryCount := r.RetryCount
if retryCount > 13 {
retryCount = 13
} else if throttle && retryCount > 8 {
retryCount = 8
}
delay := (1 << uint(retryCount)) * (seededRand.Intn(minTime) + minTime)
return time.Duration(delay) * time.Millisecond return time.Duration(delay) * time.Millisecond
} }
// ShouldRetry returns if the request should be retried. // ShouldRetry returns true if the request should be retried.
func (d DefaultRetryer) ShouldRetry(r *request.Request) bool { func (d DefaultRetryer) ShouldRetry(r *request.Request) bool {
if r.HTTPResponse.StatusCode >= 500 { if r.HTTPResponse.StatusCode >= 500 {
return true return true
} }
return r.IsErrorRetryable() return r.IsErrorRetryable() || d.shouldThrottle(r)
}
// 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
}
return r.IsErrorThrottle()
}
// 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()
} }
...@@ -2,7 +2,7 @@ package aws ...@@ -2,7 +2,7 @@ package aws
import "time" import "time"
// String returns a pointer to of the string value passed in. // String returns a pointer to the string value passed in.
func String(v string) *string { func String(v string) *string {
return &v return &v
} }
...@@ -61,7 +61,7 @@ func StringValueMap(src map[string]*string) map[string]string { ...@@ -61,7 +61,7 @@ func StringValueMap(src map[string]*string) map[string]string {
return dst return dst
} }
// Bool returns a pointer to of the bool value passed in. // Bool returns a pointer to the bool value passed in.
func Bool(v bool) *bool { func Bool(v bool) *bool {
return &v return &v
} }
...@@ -120,7 +120,7 @@ func BoolValueMap(src map[string]*bool) map[string]bool { ...@@ -120,7 +120,7 @@ func BoolValueMap(src map[string]*bool) map[string]bool {
return dst return dst
} }
// Int returns a pointer to of the int value passed in. // Int returns a pointer to the int value passed in.
func Int(v int) *int { func Int(v int) *int {
return &v return &v
} }
...@@ -179,7 +179,7 @@ func IntValueMap(src map[string]*int) map[string]int { ...@@ -179,7 +179,7 @@ func IntValueMap(src map[string]*int) map[string]int {
return dst return dst
} }
// Int64 returns a pointer to of the int64 value passed in. // Int64 returns a pointer to the int64 value passed in.
func Int64(v int64) *int64 { func Int64(v int64) *int64 {
return &v return &v
} }
...@@ -238,7 +238,7 @@ func Int64ValueMap(src map[string]*int64) map[string]int64 { ...@@ -238,7 +238,7 @@ func Int64ValueMap(src map[string]*int64) map[string]int64 {
return dst return dst
} }
// Float64 returns a pointer to of the float64 value passed in. // Float64 returns a pointer to the float64 value passed in.
func Float64(v float64) *float64 { func Float64(v float64) *float64 {
return &v return &v
} }
...@@ -297,7 +297,7 @@ func Float64ValueMap(src map[string]*float64) map[string]float64 { ...@@ -297,7 +297,7 @@ func Float64ValueMap(src map[string]*float64) map[string]float64 {
return dst return dst
} }
// Time returns a pointer to of the time.Time value passed in. // Time returns a pointer to the time.Time value passed in.
func Time(v time.Time) *time.Time { func Time(v time.Time) *time.Time {
return &v return &v
} }
...@@ -311,6 +311,18 @@ func TimeValue(v *time.Time) time.Time { ...@@ -311,6 +311,18 @@ func TimeValue(v *time.Time) time.Time {
return time.Time{} 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.
//
// This utility is useful for service API's such as CloudWatch Logs which require
// their unix time values to be in milliseconds.
//
// See Go stdlib https://golang.org/pkg/time/#Time.UnixNano for more information.
func TimeUnixMilli(t time.Time) int64 {
return t.UnixNano() / int64(time.Millisecond/time.Nanosecond)
}
// TimeSlice converts a slice of time.Time values into a slice of // TimeSlice converts a slice of time.Time values into a slice of
// time.Time pointers // time.Time pointers
func TimeSlice(src []time.Time) []*time.Time { func TimeSlice(src []time.Time) []*time.Time {
......
...@@ -10,9 +10,11 @@ import ( ...@@ -10,9 +10,11 @@ import (
"regexp" "regexp"
"runtime" "runtime"
"strconv" "strconv"
"time"
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/request"
) )
...@@ -24,30 +26,38 @@ type lener interface { ...@@ -24,30 +26,38 @@ type lener interface {
// BuildContentLengthHandler builds the content length of a request based on the body, // BuildContentLengthHandler builds the content length of a request based on the body,
// or will use the HTTPRequest.Header's "Content-Length" if defined. If unable // 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. // 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
// 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) { var BuildContentLengthHandler = request.NamedHandler{Name: "core.BuildContentLengthHandler", Fn: func(r *request.Request) {
var length int64
if slength := r.HTTPRequest.Header.Get("Content-Length"); slength != "" { if slength := r.HTTPRequest.Header.Get("Content-Length"); slength != "" {
length, _ := strconv.ParseInt(slength, 10, 64) length, _ = strconv.ParseInt(slength, 10, 64)
r.HTTPRequest.ContentLength = length } else {
return 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`")
}
} }
var length int64 if length > 0 {
switch body := r.Body.(type) { r.HTTPRequest.ContentLength = length
case nil: r.HTTPRequest.Header.Set("Content-Length", fmt.Sprintf("%d", length))
length = 0 } else {
case lener: r.HTTPRequest.ContentLength = 0
length = int64(body.Len()) r.HTTPRequest.Header.Del("Content-Length")
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`")
} }
r.HTTPRequest.ContentLength = length
r.HTTPRequest.Header.Set("Content-Length", fmt.Sprintf("%d", length))
}} }}
// SDKVersionUserAgentHandler is a request handler for adding the SDK Version to the user agent. // SDKVersionUserAgentHandler is a request handler for adding the SDK Version to the user agent.
...@@ -59,11 +69,44 @@ var SDKVersionUserAgentHandler = request.NamedHandler{ ...@@ -59,11 +69,44 @@ var SDKVersionUserAgentHandler = request.NamedHandler{
var reStatusCode = regexp.MustCompile(`^(\d{3})`) var reStatusCode = regexp.MustCompile(`^(\d{3})`)
// ValidateReqSigHandler is a request handler to ensure that the request's
// signature doesn't expire before it is sent. This can happen when a request
// is built and signed signficantly before it is sent. Or significant delays
// occur whne retrying requests that would cause the signature to expire.
var ValidateReqSigHandler = request.NamedHandler{
Name: "core.ValidateReqSigHandler",
Fn: func(r *request.Request) {
// Unsigned requests are not signed
if r.Config.Credentials == credentials.AnonymousCredentials {
return
}
signedTime := r.Time
if !r.LastSignedAt.IsZero() {
signedTime = r.LastSignedAt
}
// 10 minutes to allow for some clock skew/delays in transmission.
// Would be improved with aws/aws-sdk-go#423
if signedTime.Add(10 * time.Minute).After(time.Now()) {
return
}
fmt.Println("request expired, resigning")
r.Sign()
},
}
// SendHandler is a request handler to send service request using HTTP client. // 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 SendHandler = request.NamedHandler{Name: "core.SendHandler", Fn: func(r *request.Request) {
var err error var err error
r.HTTPResponse, err = r.Config.HTTPClient.Do(r.HTTPRequest) r.HTTPResponse, err = r.Config.HTTPClient.Do(r.HTTPRequest)
if err != nil { if err != nil {
// 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 // Capture the case where url.Error is returned for error processing
// response. e.g. 301 without location header comes back as string // response. e.g. 301 without location header comes back as string
// error and r.HTTPResponse is nil. Other url redirect errors will // error and r.HTTPResponse is nil. Other url redirect errors will
......
package corehandlers package corehandlers
import ( import "github.com/aws/aws-sdk-go/aws/request"
"fmt"
"reflect"
"strconv"
"strings"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request"
)
// ValidateParametersHandler is a request handler to validate the input parameters. // ValidateParametersHandler is a request handler to validate the input parameters.
// Validating parameters only has meaning if done prior to the request being sent. // Validating parameters only has meaning if done prior to the request being sent.
var ValidateParametersHandler = request.NamedHandler{Name: "core.ValidateParametersHandler", Fn: func(r *request.Request) { var ValidateParametersHandler = request.NamedHandler{Name: "core.ValidateParametersHandler", Fn: func(r *request.Request) {
if r.ParamsFilled() { if !r.ParamsFilled() {
v := validator{errors: []string{}}
v.validateAny(reflect.ValueOf(r.Params), "")
if count := len(v.errors); count > 0 {
format := "%d validation errors:\n- %s"
msg := fmt.Sprintf(format, count, strings.Join(v.errors, "\n- "))
r.Error = awserr.New("InvalidParameter", msg, nil)
}
}
}}
// A validator validates values. Collects validations errors which occurs.
type validator struct {
errors []string
}
// validateAny will validate any struct, slice or map type. All validations
// are also performed recursively for nested types.
func (v *validator) validateAny(value reflect.Value, path string) {
value = reflect.Indirect(value)
if !value.IsValid() {
return return
} }
switch value.Kind() { if v, ok := r.Params.(request.Validator); ok {
case reflect.Struct: if err := v.Validate(); err != nil {
v.validateStruct(value, path) r.Error = err
case reflect.Slice:
for i := 0; i < value.Len(); i++ {
v.validateAny(value.Index(i), path+fmt.Sprintf("[%d]", i))
}
case reflect.Map:
for _, n := range value.MapKeys() {
v.validateAny(value.MapIndex(n), path+fmt.Sprintf("[%q]", n.String()))
}
}
}
// validateStruct will validate the struct value's fields. If the structure has
// nested types those types will be validated also.
func (v *validator) validateStruct(value reflect.Value, path string) {
prefix := "."
if path == "" {
prefix = ""
}
for i := 0; i < value.Type().NumField(); i++ {
f := value.Type().Field(i)
if strings.ToLower(f.Name[0:1]) == f.Name[0:1] {
continue
}
fvalue := value.FieldByName(f.Name)
err := validateField(f, fvalue, validateFieldRequired, validateFieldMin)
if err != nil {
v.errors = append(v.errors, fmt.Sprintf("%s: %s", err.Error(), path+prefix+f.Name))
continue
}
v.validateAny(fvalue, path+prefix+f.Name)
}
}
type validatorFunc func(f reflect.StructField, fvalue reflect.Value) error
func validateField(f reflect.StructField, fvalue reflect.Value, funcs ...validatorFunc) error {
for _, fn := range funcs {
if err := fn(f, fvalue); err != nil {
return err
} }
} }
return nil }}
}
// Validates that a field has a valid value provided for required fields.
func validateFieldRequired(f reflect.StructField, fvalue reflect.Value) error {
if f.Tag.Get("required") == "" {
return nil
}
switch fvalue.Kind() {
case reflect.Ptr, reflect.Slice, reflect.Map:
if fvalue.IsNil() {
return fmt.Errorf("missing required parameter")
}
default:
if !fvalue.IsValid() {
return fmt.Errorf("missing required parameter")
}
}
return nil
}
// Validates that if a value is provided for a field, that value must be at
// least a minimum length.
func validateFieldMin(f reflect.StructField, fvalue reflect.Value) error {
minStr := f.Tag.Get("min")
if minStr == "" {
return nil
}
min, _ := strconv.ParseInt(minStr, 10, 64)
kind := fvalue.Kind()
if kind == reflect.Ptr {
if fvalue.IsNil() {
return nil
}
fvalue = fvalue.Elem()
}
switch fvalue.Kind() {
case reflect.String:
if int64(fvalue.Len()) < min {
return fmt.Errorf("field too short, minimum length %d", min)
}
case reflect.Slice, reflect.Map:
if fvalue.IsNil() {
return nil
}
if int64(fvalue.Len()) < min {
return fmt.Errorf("field too short, minimum length %d", min)
}
// TODO min can also apply to number minimum value.
}
return nil
}
...@@ -8,8 +8,14 @@ var ( ...@@ -8,8 +8,14 @@ var (
// ErrNoValidProvidersFoundInChain Is returned when there are no valid // ErrNoValidProvidersFoundInChain Is returned when there are no valid
// providers in the ChainProvider. // providers in the ChainProvider.
// //
// This has been deprecated. For verbose error messaging set
// aws.Config.CredentialsChainVerboseErrors to true
//
// @readonly // @readonly
ErrNoValidProvidersFoundInChain = awserr.New("NoCredentialProviders", "no valid providers in chain", nil) ErrNoValidProvidersFoundInChain = awserr.New("NoCredentialProviders",
`no valid providers in chain. Deprecated.
For verbose messaging see aws.Config.CredentialsChainVerboseErrors`,
nil)
) )
// A ChainProvider will search for a provider which returns credentials // A ChainProvider will search for a provider which returns credentials
...@@ -28,7 +34,7 @@ var ( ...@@ -28,7 +34,7 @@ var (
// //
// Example of ChainProvider to be used with an EnvProvider and EC2RoleProvider. // Example of ChainProvider to be used with an EnvProvider and EC2RoleProvider.
// In this example EnvProvider will first check if any credentials are available // In this example EnvProvider will first check if any credentials are available
// vai the environment variables. If there are none ChainProvider will check // via the environment variables. If there are none ChainProvider will check
// the next Provider in the list, EC2RoleProvider in this case. If EC2RoleProvider // the next Provider in the list, EC2RoleProvider in this case. If EC2RoleProvider
// does not return any credentials ChainProvider will return the error // does not return any credentials ChainProvider will return the error
// ErrNoValidProvidersFoundInChain // ErrNoValidProvidersFoundInChain
...@@ -45,8 +51,9 @@ var ( ...@@ -45,8 +51,9 @@ var (
// svc := ec2.New(&aws.Config{Credentials: creds}) // svc := ec2.New(&aws.Config{Credentials: creds})
// //
type ChainProvider struct { type ChainProvider struct {
Providers []Provider Providers []Provider
curr Provider curr Provider
VerboseErrors bool
} }
// NewChainCredentials returns a pointer to a new Credentials object // NewChainCredentials returns a pointer to a new Credentials object
...@@ -63,17 +70,23 @@ func NewChainCredentials(providers []Provider) *Credentials { ...@@ -63,17 +70,23 @@ func NewChainCredentials(providers []Provider) *Credentials {
// If a provider is found it will be cached and any calls to IsExpired() // If a provider is found it will be cached and any calls to IsExpired()
// will return the expired state of the cached provider. // will return the expired state of the cached provider.
func (c *ChainProvider) Retrieve() (Value, error) { func (c *ChainProvider) Retrieve() (Value, error) {
var errs []error
for _, p := range c.Providers { for _, p := range c.Providers {
if creds, err := p.Retrieve(); err == nil { creds, err := p.Retrieve()
if err == nil {
c.curr = p c.curr = p
return creds, nil return creds, nil
} }
errs = append(errs, err)
} }
c.curr = nil c.curr = nil
// TODO better error reporting. maybe report error for each failed retrieve? var err error
err = ErrNoValidProvidersFoundInChain
return Value{}, ErrNoValidProvidersFoundInChain if c.VerboseErrors {
err = awserr.NewBatchError("NoCredentialProviders", "no valid providers in chain", errs)
}
return Value{}, err
} }
// IsExpired will returned the expired state of the currently cached provider // IsExpired will returned the expired state of the currently cached provider
......
...@@ -76,6 +76,9 @@ type Value struct { ...@@ -76,6 +76,9 @@ type Value struct {
// AWS Session Token // AWS Session Token
SessionToken string SessionToken string
// Provider used to get credentials
ProviderName string
} }
// A Provider is the interface for any component which will provide credentials // A Provider is the interface for any component which will provide credentials
......
...@@ -14,6 +14,9 @@ import ( ...@@ -14,6 +14,9 @@ import (
"github.com/aws/aws-sdk-go/aws/ec2metadata" "github.com/aws/aws-sdk-go/aws/ec2metadata"
) )
// ProviderName provides a name of EC2Role provider
const ProviderName = "EC2RoleProvider"
// A EC2RoleProvider retrieves credentials from the EC2 service, and keeps track if // A EC2RoleProvider retrieves credentials from the EC2 service, and keeps track if
// those credentials are expired. // those credentials are expired.
// //
...@@ -85,17 +88,17 @@ func NewCredentialsWithClient(client *ec2metadata.EC2Metadata, options ...func(* ...@@ -85,17 +88,17 @@ func NewCredentialsWithClient(client *ec2metadata.EC2Metadata, options ...func(*
func (m *EC2RoleProvider) Retrieve() (credentials.Value, error) { func (m *EC2RoleProvider) Retrieve() (credentials.Value, error) {
credsList, err := requestCredList(m.Client) credsList, err := requestCredList(m.Client)
if err != nil { if err != nil {
return credentials.Value{}, err return credentials.Value{ProviderName: ProviderName}, err
} }
if len(credsList) == 0 { if len(credsList) == 0 {
return credentials.Value{}, awserr.New("EmptyEC2RoleList", "empty EC2 Role list", nil) return credentials.Value{ProviderName: ProviderName}, awserr.New("EmptyEC2RoleList", "empty EC2 Role list", nil)
} }
credsName := credsList[0] credsName := credsList[0]
roleCreds, err := requestCred(m.Client, credsName) roleCreds, err := requestCred(m.Client, credsName)
if err != nil { if err != nil {
return credentials.Value{}, err return credentials.Value{ProviderName: ProviderName}, err
} }
m.SetExpiration(roleCreds.Expiration, m.ExpiryWindow) m.SetExpiration(roleCreds.Expiration, m.ExpiryWindow)
...@@ -104,10 +107,11 @@ func (m *EC2RoleProvider) Retrieve() (credentials.Value, error) { ...@@ -104,10 +107,11 @@ func (m *EC2RoleProvider) Retrieve() (credentials.Value, error) {
AccessKeyID: roleCreds.AccessKeyID, AccessKeyID: roleCreds.AccessKeyID,
SecretAccessKey: roleCreds.SecretAccessKey, SecretAccessKey: roleCreds.SecretAccessKey,
SessionToken: roleCreds.Token, SessionToken: roleCreds.Token,
ProviderName: ProviderName,
}, nil }, nil
} }
// A ec2RoleCredRespBody provides the shape for unmarshalling credential // A ec2RoleCredRespBody provides the shape for unmarshaling credential
// request responses. // request responses.
type ec2RoleCredRespBody struct { type ec2RoleCredRespBody struct {
// Success State // Success State
...@@ -128,7 +132,7 @@ const iamSecurityCredsPath = "/iam/security-credentials" ...@@ -128,7 +132,7 @@ const iamSecurityCredsPath = "/iam/security-credentials"
func requestCredList(client *ec2metadata.EC2Metadata) ([]string, error) { func requestCredList(client *ec2metadata.EC2Metadata) ([]string, error) {
resp, err := client.GetMetadata(iamSecurityCredsPath) resp, err := client.GetMetadata(iamSecurityCredsPath)
if err != nil { if err != nil {
return nil, awserr.New("EC2RoleRequestError", "failed to list EC2 Roles", err) return nil, awserr.New("EC2RoleRequestError", "no EC2 instance role found", err)
} }
credsList := []string{} credsList := []string{}
...@@ -138,7 +142,7 @@ func requestCredList(client *ec2metadata.EC2Metadata) ([]string, error) { ...@@ -138,7 +142,7 @@ func requestCredList(client *ec2metadata.EC2Metadata) ([]string, error) {
} }
if err := s.Err(); err != nil { if err := s.Err(); err != nil {
return nil, awserr.New("SerializationError", "failed to read list of EC2 Roles", err) return nil, awserr.New("SerializationError", "failed to read EC2 instance role from metadata service", err)
} }
return credsList, nil return credsList, nil
...@@ -153,7 +157,7 @@ func requestCred(client *ec2metadata.EC2Metadata, credsName string) (ec2RoleCred ...@@ -153,7 +157,7 @@ func requestCred(client *ec2metadata.EC2Metadata, credsName string) (ec2RoleCred
if err != nil { if err != nil {
return ec2RoleCredRespBody{}, return ec2RoleCredRespBody{},
awserr.New("EC2RoleRequestError", awserr.New("EC2RoleRequestError",
fmt.Sprintf("failed to get %s EC2 Role credentials", credsName), fmt.Sprintf("failed to get %s EC2 instance role credentials", credsName),
err) err)
} }
...@@ -161,7 +165,7 @@ func requestCred(client *ec2metadata.EC2Metadata, credsName string) (ec2RoleCred ...@@ -161,7 +165,7 @@ func requestCred(client *ec2metadata.EC2Metadata, credsName string) (ec2RoleCred
if err := json.NewDecoder(strings.NewReader(resp)).Decode(&respCreds); err != nil { if err := json.NewDecoder(strings.NewReader(resp)).Decode(&respCreds); err != nil {
return ec2RoleCredRespBody{}, return ec2RoleCredRespBody{},
awserr.New("SerializationError", awserr.New("SerializationError",
fmt.Sprintf("failed to decode %s EC2 Role credentials", credsName), fmt.Sprintf("failed to decode %s EC2 instance role credentials", credsName),
err) err)
} }
......
// Package endpointcreds provides support for retrieving credentials from an
// arbitrary HTTP endpoint.
//
// The credentials endpoint Provider can receive both static and refreshable
// credentials that will expire. Credentials are static when an "Expiration"
// value is not provided in the endpoint's response.
//
// Static credentials will never expire once they have been retrieved. The format
// of the static credentials response:
// {
// "AccessKeyId" : "MUA...",
// "SecretAccessKey" : "/7PC5om....",
// }
//
// Refreshable credentials will expire within the "ExpiryWindow" of the Expiration
// value in the response. The format of the refreshable credentials response:
// {
// "AccessKeyId" : "MUA...",
// "SecretAccessKey" : "/7PC5om....",
// "Token" : "AQoDY....=",
// "Expiration" : "2016-02-25T06:03:31Z"
// }
//
// Errors should be returned in the following format and only returned with 400
// or 500 HTTP status codes.
// {
// "code": "ErrorCode",
// "message": "Helpful error message."
// }
package endpointcreds
import (
"encoding/json"
"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/credentials"
"github.com/aws/aws-sdk-go/aws/request"
)
// ProviderName is the name of the credentials provider.
const ProviderName = `CredentialsEndpointProvider`
// Provider satisfies the credentials.Provider interface, and is a client to
// retrieve credentials from an arbitrary endpoint.
type Provider struct {
staticCreds bool
credentials.Expiry
// Requires a AWS Client to make HTTP requests to the endpoint with.
// the Endpoint the request will be made to is provided by the aws.Config's
// Endpoint value.
Client *client.Client
// 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
// due to ExpiredTokenException exceptions.
//
// So a ExpiryWindow of 10s would cause calls to IsExpired() to return true
// 10 seconds before the credentials are actually expired.
//
// If ExpiryWindow is 0 or less it will be ignored.
ExpiryWindow time.Duration
}
// NewProviderClient returns a credentials Provider for retrieving AWS credentials
// from arbitrary endpoint.
func NewProviderClient(cfg aws.Config, handlers request.Handlers, endpoint string, options ...func(*Provider)) credentials.Provider {
p := &Provider{
Client: client.New(
cfg,
metadata.ClientInfo{
ServiceName: "CredentialsEndpoint",
Endpoint: endpoint,
},
handlers,
),
}
p.Client.Handlers.Unmarshal.PushBack(unmarshalHandler)
p.Client.Handlers.UnmarshalError.PushBack(unmarshalError)
p.Client.Handlers.Validate.Clear()
p.Client.Handlers.Validate.PushBack(validateEndpointHandler)
for _, option := range options {
option(p)
}
return p
}
// NewCredentialsClient returns a Credentials wrapper for retrieving credentials
// from an arbitrary endpoint concurrently. The client will request the
func NewCredentialsClient(cfg aws.Config, handlers request.Handlers, endpoint string, options ...func(*Provider)) *credentials.Credentials {
return credentials.NewCredentials(NewProviderClient(cfg, handlers, endpoint, options...))
}
// IsExpired returns true if the credentials retrieved are expired, or not yet
// retrieved.
func (p *Provider) IsExpired() bool {
if p.staticCreds {
return false
}
return p.Expiry.IsExpired()
}
// Retrieve will attempt to request the credentials from the endpoint the Provider
// was configured for. And error will be returned if the retrieval fails.
func (p *Provider) Retrieve() (credentials.Value, error) {
resp, err := p.getCredentials()
if err != nil {
return credentials.Value{ProviderName: ProviderName},
awserr.New("CredentialsEndpointError", "failed to load credentials", err)
}
if resp.Expiration != nil {
p.SetExpiration(*resp.Expiration, p.ExpiryWindow)
} else {
p.staticCreds = true
}
return credentials.Value{
AccessKeyID: resp.AccessKeyID,
SecretAccessKey: resp.SecretAccessKey,
SessionToken: resp.Token,
ProviderName: ProviderName,
}, nil
}
type getCredentialsOutput struct {
Expiration *time.Time
AccessKeyID string
SecretAccessKey string
Token string
}
type errorOutput struct {
Code string `json:"code"`
Message string `json:"message"`
}
func (p *Provider) getCredentials() (*getCredentialsOutput, error) {
op := &request.Operation{
Name: "GetCredentials",
HTTPMethod: "GET",
}
out := &getCredentialsOutput{}
req := p.Client.NewRequest(op, nil, out)
req.HTTPRequest.Header.Set("Accept", "application/json")
return out, req.Send()
}
func validateEndpointHandler(r *request.Request) {
if len(r.ClientInfo.Endpoint) == 0 {
r.Error = aws.ErrMissingEndpoint
}
}
func unmarshalHandler(r *request.Request) {
defer r.HTTPResponse.Body.Close()
out := r.Data.(*getCredentialsOutput)
if err := json.NewDecoder(r.HTTPResponse.Body).Decode(&out); err != nil {
r.Error = awserr.New("SerializationError",
"failed to decode endpoint credentials",
err,
)
}
}
func unmarshalError(r *request.Request) {
defer r.HTTPResponse.Body.Close()
var errOut errorOutput
if err := json.NewDecoder(r.HTTPResponse.Body).Decode(&errOut); err != nil {
r.Error = awserr.New("SerializationError",
"failed to decode endpoint credentials",
err,
)
}
// Response body format is not consistent between metadata endpoints.
// Grab the error message as a string and include that as the source error
r.Error = awserr.New(errOut.Code, errOut.Message, nil)
}
...@@ -6,6 +6,9 @@ import ( ...@@ -6,6 +6,9 @@ import (
"github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/awserr"
) )
// EnvProviderName provides a name of Env provider
const EnvProviderName = "EnvProvider"
var ( var (
// ErrAccessKeyIDNotFound is returned when the AWS Access Key ID can't be // ErrAccessKeyIDNotFound is returned when the AWS Access Key ID can't be
// found in the process's environment. // found in the process's environment.
...@@ -52,11 +55,11 @@ func (e *EnvProvider) Retrieve() (Value, error) { ...@@ -52,11 +55,11 @@ func (e *EnvProvider) Retrieve() (Value, error) {
} }
if id == "" { if id == "" {
return Value{}, ErrAccessKeyIDNotFound return Value{ProviderName: EnvProviderName}, ErrAccessKeyIDNotFound
} }
if secret == "" { if secret == "" {
return Value{}, ErrSecretAccessKeyNotFound return Value{ProviderName: EnvProviderName}, ErrSecretAccessKeyNotFound
} }
e.retrieved = true e.retrieved = true
...@@ -64,6 +67,7 @@ func (e *EnvProvider) Retrieve() (Value, error) { ...@@ -64,6 +67,7 @@ func (e *EnvProvider) Retrieve() (Value, error) {
AccessKeyID: id, AccessKeyID: id,
SecretAccessKey: secret, SecretAccessKey: secret,
SessionToken: os.Getenv("AWS_SESSION_TOKEN"), SessionToken: os.Getenv("AWS_SESSION_TOKEN"),
ProviderName: EnvProviderName,
}, nil }, nil
} }
......
...@@ -10,6 +10,9 @@ import ( ...@@ -10,6 +10,9 @@ import (
"github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/awserr"
) )
// SharedCredsProviderName provides a name of SharedCreds provider
const SharedCredsProviderName = "SharedCredentialsProvider"
var ( var (
// ErrSharedCredentialsHomeNotFound is emitted when the user directory cannot be found. // ErrSharedCredentialsHomeNotFound is emitted when the user directory cannot be found.
// //
...@@ -55,12 +58,12 @@ func (p *SharedCredentialsProvider) Retrieve() (Value, error) { ...@@ -55,12 +58,12 @@ func (p *SharedCredentialsProvider) Retrieve() (Value, error) {
filename, err := p.filename() filename, err := p.filename()
if err != nil { if err != nil {
return Value{}, err return Value{ProviderName: SharedCredsProviderName}, err
} }
creds, err := loadProfile(filename, p.profile()) creds, err := loadProfile(filename, p.profile())
if err != nil { if err != nil {
return Value{}, err return Value{ProviderName: SharedCredsProviderName}, err
} }
p.retrieved = true p.retrieved = true
...@@ -78,23 +81,23 @@ func (p *SharedCredentialsProvider) IsExpired() bool { ...@@ -78,23 +81,23 @@ func (p *SharedCredentialsProvider) IsExpired() bool {
func loadProfile(filename, profile string) (Value, error) { func loadProfile(filename, profile string) (Value, error) {
config, err := ini.Load(filename) config, err := ini.Load(filename)
if err != nil { if err != nil {
return Value{}, awserr.New("SharedCredsLoad", "failed to load shared credentials file", err) return Value{ProviderName: SharedCredsProviderName}, awserr.New("SharedCredsLoad", "failed to load shared credentials file", err)
} }
iniProfile, err := config.GetSection(profile) iniProfile, err := config.GetSection(profile)
if err != nil { if err != nil {
return Value{}, awserr.New("SharedCredsLoad", "failed to get profile", err) return Value{ProviderName: SharedCredsProviderName}, awserr.New("SharedCredsLoad", "failed to get profile", err)
} }
id, err := iniProfile.GetKey("aws_access_key_id") id, err := iniProfile.GetKey("aws_access_key_id")
if err != nil { if err != nil {
return Value{}, awserr.New("SharedCredsAccessKey", return Value{ProviderName: SharedCredsProviderName}, awserr.New("SharedCredsAccessKey",
fmt.Sprintf("shared credentials %s in %s did not contain aws_access_key_id", profile, filename), fmt.Sprintf("shared credentials %s in %s did not contain aws_access_key_id", profile, filename),
err) err)
} }
secret, err := iniProfile.GetKey("aws_secret_access_key") secret, err := iniProfile.GetKey("aws_secret_access_key")
if err != nil { if err != nil {
return Value{}, awserr.New("SharedCredsSecret", return Value{ProviderName: SharedCredsProviderName}, awserr.New("SharedCredsSecret",
fmt.Sprintf("shared credentials %s in %s did not contain aws_secret_access_key", profile, filename), fmt.Sprintf("shared credentials %s in %s did not contain aws_secret_access_key", profile, filename),
nil) nil)
} }
...@@ -106,6 +109,7 @@ func loadProfile(filename, profile string) (Value, error) { ...@@ -106,6 +109,7 @@ func loadProfile(filename, profile string) (Value, error) {
AccessKeyID: id.String(), AccessKeyID: id.String(),
SecretAccessKey: secret.String(), SecretAccessKey: secret.String(),
SessionToken: token.String(), SessionToken: token.String(),
ProviderName: SharedCredsProviderName,
}, nil }, nil
} }
......
...@@ -4,6 +4,9 @@ import ( ...@@ -4,6 +4,9 @@ import (
"github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/awserr"
) )
// StaticProviderName provides a name of Static provider
const StaticProviderName = "StaticProvider"
var ( var (
// ErrStaticCredentialsEmpty is emitted when static credentials are empty. // ErrStaticCredentialsEmpty is emitted when static credentials are empty.
// //
...@@ -11,7 +14,7 @@ var ( ...@@ -11,7 +14,7 @@ var (
ErrStaticCredentialsEmpty = awserr.New("EmptyStaticCreds", "static credentials are empty", nil) ErrStaticCredentialsEmpty = awserr.New("EmptyStaticCreds", "static credentials are empty", nil)
) )
// A StaticProvider is a set of credentials which are set pragmatically, // A StaticProvider is a set of credentials which are set programmatically,
// and will never expire. // and will never expire.
type StaticProvider struct { type StaticProvider struct {
Value Value
...@@ -27,12 +30,22 @@ func NewStaticCredentials(id, secret, token string) *Credentials { ...@@ -27,12 +30,22 @@ func NewStaticCredentials(id, secret, token string) *Credentials {
}}) }})
} }
// NewStaticCredentialsFromCreds returns a pointer to a new Credentials object
// wrapping the static credentials value provide. Same as NewStaticCredentials
// but takes the creds Value instead of individual fields
func NewStaticCredentialsFromCreds(creds Value) *Credentials {
return NewCredentials(&StaticProvider{Value: creds})
}
// Retrieve returns the credentials or error if the credentials are invalid. // Retrieve returns the credentials or error if the credentials are invalid.
func (s *StaticProvider) Retrieve() (Value, error) { func (s *StaticProvider) Retrieve() (Value, error) {
if s.AccessKeyID == "" || s.SecretAccessKey == "" { if s.AccessKeyID == "" || s.SecretAccessKey == "" {
return Value{}, ErrStaticCredentialsEmpty return Value{ProviderName: StaticProviderName}, ErrStaticCredentialsEmpty
} }
if len(s.Value.ProviderName) == 0 {
s.Value.ProviderName = StaticProviderName
}
return s.Value, nil return s.Value, nil
} }
......
// 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
import (
"fmt"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/service/sts"
)
// ProviderName provides a name of AssumeRole provider
const ProviderName = "AssumeRoleProvider"
// AssumeRoler represents the minimal subset of the STS client API used by this provider.
type AssumeRoler interface {
AssumeRole(input *sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error)
}
// DefaultDuration is the default amount of time in minutes that the credentials
// will be valid for.
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.
type AssumeRoleProvider struct {
credentials.Expiry
// STS client to make assume role request with.
Client AssumeRoler
// Role to be assumed.
RoleARN string
// Session name, if you wish to reuse the credentials elsewhere.
RoleSessionName string
// Expiry duration of the STS credentials. Defaults to 15 minutes if not set.
Duration time.Duration
// Optional ExternalID to pass along, defaults to nil if not set.
ExternalID *string
// The policy plain text must be 2048 bytes or shorter. However, an internal
// conversion compresses it into a packed binary format with a separate limit.
// The PackedPolicySize response element indicates by percentage how close to
// the upper size limit the policy is, with 100% equaling the maximum allowed
// size.
Policy *string
// The identification number of the MFA device that is associated with the user
// who is making the AssumeRole call. Specify this value if the trust policy
// of the role being assumed includes a condition that requires MFA authentication.
// The value is either the serial number for a hardware device (such as GAHT12345678)
// or an Amazon Resource Name (ARN) for a virtual device (such as arn:aws:iam::123456789012:mfa/user).
SerialNumber *string
// The value provided by the MFA device, if the trust policy of the role being
// 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.
TokenCode *string
// 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
// due to ExpiredTokenException exceptions.
//
// So a ExpiryWindow of 10s would cause calls to IsExpired() to return true
// 10 seconds before the credentials are actually expired.
//
// If ExpiryWindow is 0 or less it will be ignored.
ExpiryWindow time.Duration
}
// NewCredentials returns a pointer to a new Credentials object wrapping the
// AssumeRoleProvider. The credentials will expire every 15 minutes and the
// role will be named after a nanosecond timestamp of this operation.
//
// Takes a Config provider to create the STS client. The ConfigProvider is
// satisfied by the session.Session type.
func NewCredentials(c client.ConfigProvider, roleARN string, options ...func(*AssumeRoleProvider)) *credentials.Credentials {
p := &AssumeRoleProvider{
Client: sts.New(c),
RoleARN: roleARN,
Duration: DefaultDuration,
}
for _, option := range options {
option(p)
}
return credentials.NewCredentials(p)
}
// NewCredentialsWithClient returns a pointer to a new Credentials object wrapping the
// 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.
func NewCredentialsWithClient(svc AssumeRoler, roleARN string, options ...func(*AssumeRoleProvider)) *credentials.Credentials {
p := &AssumeRoleProvider{
Client: svc,
RoleARN: roleARN,
Duration: DefaultDuration,
}
for _, option := range options {
option(p)
}
return credentials.NewCredentials(p)
}
// Retrieve generates a new set of temporary credentials using STS.
func (p *AssumeRoleProvider) Retrieve() (credentials.Value, error) {
// Apply defaults where parameters are not set.
if p.RoleSessionName == "" {
// Try to work out a role name that will hopefully end up unique.
p.RoleSessionName = fmt.Sprintf("%d", time.Now().UTC().UnixNano())
}
if p.Duration == 0 {
// Expire as often as AWS permits.
p.Duration = DefaultDuration
}
input := &sts.AssumeRoleInput{
DurationSeconds: aws.Int64(int64(p.Duration / time.Second)),
RoleArn: aws.String(p.RoleARN),
RoleSessionName: aws.String(p.RoleSessionName),
ExternalId: p.ExternalID,
}
if p.Policy != nil {
input.Policy = p.Policy
}
if p.SerialNumber != nil && p.TokenCode != nil {
input.SerialNumber = p.SerialNumber
input.TokenCode = p.TokenCode
}
roleOutput, err := p.Client.AssumeRole(input)
if err != nil {
return credentials.Value{ProviderName: ProviderName}, err
}
// We will proactively generate new credentials before they expire.
p.SetExpiration(*roleOutput.Credentials.Expiration, p.ExpiryWindow)
return credentials.Value{
AccessKeyID: *roleOutput.Credentials.AccessKeyId,
SecretAccessKey: *roleOutput.Credentials.SecretAccessKey,
SessionToken: *roleOutput.Credentials.SessionToken,
ProviderName: ProviderName,
}, nil
}
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
package defaults package defaults
import ( import (
"fmt"
"net/http" "net/http"
"os" "os"
"time" "time"
...@@ -16,9 +17,10 @@ import ( ...@@ -16,9 +17,10 @@ import (
"github.com/aws/aws-sdk-go/aws/corehandlers" "github.com/aws/aws-sdk-go/aws/corehandlers"
"github.com/aws/aws-sdk-go/aws/credentials" "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/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/ec2metadata"
"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/aws/aws-sdk-go/aws/request" "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. // A Defaults provides a collection of default values for SDK clients.
...@@ -54,7 +56,8 @@ func Config() *aws.Config { ...@@ -54,7 +56,8 @@ func Config() *aws.Config {
WithMaxRetries(aws.UseServiceDefaultRetries). WithMaxRetries(aws.UseServiceDefaultRetries).
WithLogger(aws.NewDefaultLogger()). WithLogger(aws.NewDefaultLogger()).
WithLogLevel(aws.LogOff). WithLogLevel(aws.LogOff).
WithSleepDelay(time.Sleep) WithSleepDelay(time.Sleep).
WithEndpointResolver(endpoints.DefaultResolver())
} }
// Handlers returns the default request handlers. // Handlers returns the default request handlers.
...@@ -66,8 +69,11 @@ func Handlers() request.Handlers { ...@@ -66,8 +69,11 @@ func Handlers() request.Handlers {
var handlers request.Handlers var handlers request.Handlers
handlers.Validate.PushBackNamed(corehandlers.ValidateEndpointHandler) handlers.Validate.PushBackNamed(corehandlers.ValidateEndpointHandler)
handlers.Validate.AfterEachFn = request.HandlerListStopOnError
handlers.Build.PushBackNamed(corehandlers.SDKVersionUserAgentHandler) handlers.Build.PushBackNamed(corehandlers.SDKVersionUserAgentHandler)
handlers.Build.AfterEachFn = request.HandlerListStopOnError
handlers.Sign.PushBackNamed(corehandlers.BuildContentLengthHandler) handlers.Sign.PushBackNamed(corehandlers.BuildContentLengthHandler)
handlers.Send.PushBackNamed(corehandlers.ValidateReqSigHandler)
handlers.Send.PushBackNamed(corehandlers.SendHandler) handlers.Send.PushBackNamed(corehandlers.SendHandler)
handlers.AfterRetry.PushBackNamed(corehandlers.AfterRetryHandler) handlers.AfterRetry.PushBackNamed(corehandlers.AfterRetryHandler)
handlers.ValidateResponse.PushBackNamed(corehandlers.ValidateResponseHandler) handlers.ValidateResponse.PushBackNamed(corehandlers.ValidateResponseHandler)
...@@ -81,15 +87,48 @@ func Handlers() request.Handlers { ...@@ -81,15 +87,48 @@ func Handlers() request.Handlers {
// is available if you need to reset the credentials of an // is available if you need to reset the credentials of an
// existing service client or session's Config. // existing service client or session's Config.
func CredChain(cfg *aws.Config, handlers request.Handlers) *credentials.Credentials { func CredChain(cfg *aws.Config, handlers request.Handlers) *credentials.Credentials {
endpoint, signingRegion := endpoints.EndpointForRegion(ec2metadata.ServiceName, *cfg.Region, true) return credentials.NewCredentials(&credentials.ChainProvider{
VerboseErrors: aws.BoolValue(cfg.CredentialsChainVerboseErrors),
return credentials.NewChainCredentials( Providers: []credentials.Provider{
[]credentials.Provider{
&credentials.EnvProvider{}, &credentials.EnvProvider{},
&credentials.SharedCredentialsProvider{Filename: "", Profile: ""}, &credentials.SharedCredentialsProvider{Filename: "", Profile: ""},
&ec2rolecreds.EC2RoleProvider{ RemoteCredProvider(*cfg, handlers),
Client: ec2metadata.NewClient(*cfg, handlers, endpoint, signingRegion), },
ExpiryWindow: 5 * time.Minute, })
}, }
})
// RemoteCredProvider returns a credenitials 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 len(ecsCredURI) > 0 {
return ecsCredProvider(cfg, handlers, ecsCredURI)
}
return ec2RoleProvider(cfg, handlers)
}
func ecsCredProvider(cfg aws.Config, handlers request.Handlers, uri string) credentials.Provider {
const host = `169.254.170.2`
return endpointcreds.NewProviderClient(cfg, handlers,
fmt.Sprintf("http://%s%s", host, uri),
func(p *endpointcreds.Provider) {
p.ExpiryWindow = 5 * time.Minute
},
)
}
func ec2RoleProvider(cfg aws.Config, handlers request.Handlers) credentials.Provider {
resolver := cfg.EndpointResolver
if resolver == nil {
resolver = endpoints.DefaultResolver()
}
e, _ := resolver.EndpointFor(endpoints.Ec2metadataServiceID, "")
return &ec2rolecreds.EC2RoleProvider{
Client: ec2metadata.NewClient(cfg, handlers, e.URL, e.SigningRegion),
ExpiryWindow: 5 * time.Minute,
}
} }
package ec2metadata package ec2metadata
import ( import (
"encoding/json"
"fmt"
"net/http"
"path" "path"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/request"
) )
// GetMetadata uses the path provided to request // GetMetadata uses the path provided to request information from the EC2
// instance metdata service. The content will be returned as a string, or
// error if the request failed.
func (c *EC2Metadata) GetMetadata(p string) (string, error) { func (c *EC2Metadata) GetMetadata(p string) (string, error) {
op := &request.Operation{ op := &request.Operation{
Name: "GetMetadata", Name: "GetMetadata",
...@@ -20,6 +28,89 @@ func (c *EC2Metadata) GetMetadata(p string) (string, error) { ...@@ -20,6 +28,89 @@ func (c *EC2Metadata) GetMetadata(p string) (string, error) {
return output.Content, req.Send() return output.Content, req.Send()
} }
// GetUserData returns the userdata that was configured for the service. If
// there is no user-data setup for the EC2 instance a "NotFoundError" error
// code will be returned.
func (c *EC2Metadata) GetUserData() (string, error) {
op := &request.Operation{
Name: "GetUserData",
HTTPMethod: "GET",
HTTPPath: path.Join("/", "user-data"),
}
output := &metadataOutput{}
req := c.NewRequest(op, nil, output)
req.Handlers.UnmarshalError.PushBack(func(r *request.Request) {
if r.HTTPResponse.StatusCode == http.StatusNotFound {
r.Error = awserr.New("NotFoundError", "user-data not found", r.Error)
}
})
return output.Content, req.Send()
}
// GetDynamicData uses the path provided to request information from the EC2
// instance metadata service for dynamic data. The content will be returned
// as a string, or error if the request failed.
func (c *EC2Metadata) GetDynamicData(p string) (string, error) {
op := &request.Operation{
Name: "GetDynamicData",
HTTPMethod: "GET",
HTTPPath: path.Join("/", "dynamic", p),
}
output := &metadataOutput{}
req := c.NewRequest(op, nil, output)
return output.Content, req.Send()
}
// GetInstanceIdentityDocument retrieves an identity document describing an
// instance. Error is returned if the request fails or is unable to parse
// the response.
func (c *EC2Metadata) GetInstanceIdentityDocument() (EC2InstanceIdentityDocument, error) {
resp, err := c.GetDynamicData("instance-identity/document")
if err != nil {
return EC2InstanceIdentityDocument{},
awserr.New("EC2MetadataRequestError",
"failed to get EC2 instance identity document", err)
}
doc := EC2InstanceIdentityDocument{}
if err := json.NewDecoder(strings.NewReader(resp)).Decode(&doc); err != nil {
return EC2InstanceIdentityDocument{},
awserr.New("SerializationError",
"failed to decode EC2 instance identity document", err)
}
return doc, nil
}
// IAMInfo retrieves IAM info from the metadata API
func (c *EC2Metadata) IAMInfo() (EC2IAMInfo, error) {
resp, err := c.GetMetadata("iam/info")
if err != nil {
return EC2IAMInfo{},
awserr.New("EC2MetadataRequestError",
"failed to get EC2 IAM info", err)
}
info := EC2IAMInfo{}
if err := json.NewDecoder(strings.NewReader(resp)).Decode(&info); err != nil {
return EC2IAMInfo{},
awserr.New("SerializationError",
"failed to decode EC2 IAM info", err)
}
if info.Code != "Success" {
errMsg := fmt.Sprintf("failed to get EC2 IAM Info (%s)", info.Code)
return EC2IAMInfo{},
awserr.New("EC2MetadataError", errMsg, nil)
}
return info, nil
}
// Region returns the region the instance is running in. // Region returns the region the instance is running in.
func (c *EC2Metadata) Region() (string, error) { func (c *EC2Metadata) Region() (string, error) {
resp, err := c.GetMetadata("placement/availability-zone") resp, err := c.GetMetadata("placement/availability-zone")
...@@ -41,3 +132,31 @@ func (c *EC2Metadata) Available() bool { ...@@ -41,3 +132,31 @@ func (c *EC2Metadata) Available() bool {
return true return true
} }
// An EC2IAMInfo provides the shape for unmarshaling
// an IAM info from the metadata API
type EC2IAMInfo struct {
Code string
LastUpdated time.Time
InstanceProfileArn string
InstanceProfileID string
}
// An EC2InstanceIdentityDocument provides the shape for unmarshaling
// an instance identity document
type EC2InstanceIdentityDocument struct {
DevpayProductCodes []string `json:"devpayProductCodes"`
AvailabilityZone string `json:"availabilityZone"`
PrivateIP string `json:"privateIp"`
Version string `json:"version"`
Region string `json:"region"`
InstanceID string `json:"instanceId"`
BillingProducts []string `json:"billingProducts"`
InstanceType string `json:"instanceType"`
AccountID string `json:"accountId"`
PendingTime time.Time `json:"pendingTime"`
ImageID string `json:"imageId"`
KernelID string `json:"kernelId"`
RamdiskID string `json:"ramdiskId"`
Architecture string `json:"architecture"`
}
...@@ -3,8 +3,9 @@ ...@@ -3,8 +3,9 @@
package ec2metadata package ec2metadata
import ( import (
"io/ioutil" "bytes"
"net" "errors"
"io"
"net/http" "net/http"
"time" "time"
...@@ -26,6 +27,7 @@ type EC2Metadata struct { ...@@ -26,6 +27,7 @@ type EC2Metadata struct {
// New creates a new instance of the EC2Metadata client with a session. // New creates a new instance of the EC2Metadata client with a session.
// This client is safe to use across multiple goroutines. // This client is safe to use across multiple goroutines.
// //
//
// Example: // Example:
// // Create a EC2Metadata client from just a session. // // Create a EC2Metadata client from just a session.
// svc := ec2metadata.New(mySession) // svc := ec2metadata.New(mySession)
...@@ -40,22 +42,19 @@ func New(p client.ConfigProvider, cfgs ...*aws.Config) *EC2Metadata { ...@@ -40,22 +42,19 @@ func New(p client.ConfigProvider, cfgs ...*aws.Config) *EC2Metadata {
// NewClient returns a new EC2Metadata client. Should be used to create // NewClient returns a new EC2Metadata client. Should be used to create
// a client when not using a session. Generally using just New with a session // a client when not using a session. Generally using just New with a session
// is preferred. // is preferred.
//
// If an unmodified HTTP client is provided from the stdlib default, or no client
// the EC2RoleProvider's EC2Metadata HTTP client's timeout will be shortened.
// To disable this set Config.EC2MetadataDisableTimeoutOverride to false. Enabled by default.
func NewClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegion string, opts ...func(*client.Client)) *EC2Metadata { func NewClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegion string, opts ...func(*client.Client)) *EC2Metadata {
// If the default http client is provided, replace it with a custom if !aws.BoolValue(cfg.EC2MetadataDisableTimeoutOverride) && httpClientZero(cfg.HTTPClient) {
// client using default timeouts. // If the http client is unmodified and this feature is not disabled
if cfg.HTTPClient == http.DefaultClient { // set custom timeouts for EC2Metadata requests.
cfg.HTTPClient = &http.Client{ cfg.HTTPClient = &http.Client{
Transport: &http.Transport{ // use a shorter timeout than default because the metadata
Proxy: http.ProxyFromEnvironment, // service is local if it is running, and to fail faster
Dial: (&net.Dialer{ // if not running on an ec2 instance.
// use a shorter timeout than default because the metadata Timeout: 5 * time.Second,
// service is local if it is running, and to fail faster
// if not running on an ec2 instance.
Timeout: 5 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
},
} }
} }
...@@ -84,29 +83,38 @@ func NewClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegio ...@@ -84,29 +83,38 @@ func NewClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegio
return svc return svc
} }
func httpClientZero(c *http.Client) bool {
return c == nil || (c.Transport == nil && c.CheckRedirect == nil && c.Jar == nil && c.Timeout == 0)
}
type metadataOutput struct { type metadataOutput struct {
Content string Content string
} }
func unmarshalHandler(r *request.Request) { func unmarshalHandler(r *request.Request) {
defer r.HTTPResponse.Body.Close() defer r.HTTPResponse.Body.Close()
b, err := ioutil.ReadAll(r.HTTPResponse.Body) b := &bytes.Buffer{}
if err != nil { if _, err := io.Copy(b, r.HTTPResponse.Body); err != nil {
r.Error = awserr.New("SerializationError", "unable to unmarshal EC2 metadata respose", err) r.Error = awserr.New("SerializationError", "unable to unmarshal EC2 metadata respose", err)
return
} }
data := r.Data.(*metadataOutput) if data, ok := r.Data.(*metadataOutput); ok {
data.Content = string(b) data.Content = b.String()
}
} }
func unmarshalError(r *request.Request) { func unmarshalError(r *request.Request) {
defer r.HTTPResponse.Body.Close() defer r.HTTPResponse.Body.Close()
_, err := ioutil.ReadAll(r.HTTPResponse.Body) b := &bytes.Buffer{}
if err != nil { if _, err := io.Copy(b, r.HTTPResponse.Body); err != nil {
r.Error = awserr.New("SerializationError", "unable to unmarshal EC2 metadata error respose", err) r.Error = awserr.New("SerializationError", "unable to unmarshal EC2 metadata error respose", err)
return
} }
// TODO extract the error... // Response body format is not consistent between metadata endpoints.
// Grab the error message as a string and include that as the source error
r.Error = awserr.New("EC2MetadataError", "failed to make EC2Metadata request", errors.New(b.String()))
} }
func validateEndpointHandler(r *request.Request) { func validateEndpointHandler(r *request.Request) {
......
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),
}
}
// 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.Name)
// for id, _ := range p.Regions() {
// fmt.Println("*", id)
// }
//
// fmt.Println("Services for", p.Name)
// 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
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 {
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
if len(signingName) == 0 {
signingName = service
}
return ResolvedEndpoint{
URL: u,
SigningRegion: signingRegion,
SigningName: signingName,
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
)
// +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" -}}
// THIS FILE IS AUTOMATICALLY GENERATED. 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 . }}.
//
// 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 := endpoints.DefaultResolver()
// partitions := resolver.(endpoints.EnumPartitions).Partitions()
// for _, p := range partitions {
// // ... inspect partitions
// }
func DefaultResolver() Resolver {
return defaultPartitions
}
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 }}
`
...@@ -79,6 +79,20 @@ type Logger interface { ...@@ -79,6 +79,20 @@ type Logger interface {
Log(...interface{}) Log(...interface{})
} }
// A LoggerFunc is a convenience type to convert a function taking a variadic
// list of arguments and wrap it so the Logger interface can be used.
//
// Example:
// s3.New(sess, &aws.Config{Logger: aws.LoggerFunc(func(args ...interface{}) {
// fmt.Fprintln(os.Stdout, args...)
// })})
type LoggerFunc func(...interface{})
// Log calls the wrapped function with the arguments provided
func (f LoggerFunc) Log(args ...interface{}) {
f(args...)
}
// NewDefaultLogger returns a Logger which will write log messages to stdout, and // NewDefaultLogger returns a Logger which will write log messages to stdout, and
// use same formatting runes as the stdlib log.Logger // use same formatting runes as the stdlib log.Logger
func NewDefaultLogger() Logger { func NewDefaultLogger() Logger {
......
...@@ -50,9 +50,28 @@ func (h *Handlers) Clear() { ...@@ -50,9 +50,28 @@ func (h *Handlers) Clear() {
h.AfterRetry.Clear() h.AfterRetry.Clear()
} }
// A HandlerListRunItem represents an entry in the HandlerList which
// is being run.
type HandlerListRunItem struct {
Index int
Handler NamedHandler
Request *Request
}
// A HandlerList manages zero or more handlers in a list. // A HandlerList manages zero or more handlers in a list.
type HandlerList struct { type HandlerList struct {
list []NamedHandler list []NamedHandler
// Called after each request handler in the list is called. If set
// and the func returns true the HandlerList will continue to iterate
// over the request handlers. If false is returned the HandlerList
// will stop iterating.
//
// Should be used if extra logic to be performed between each handler
// in the list. This can be used to terminate a list's iteration
// based on a condition such as error like, HandlerListStopOnError.
// Or for logging like HandlerListLogItem.
AfterEachFn func(item HandlerListRunItem) bool
} }
// A NamedHandler is a struct that contains a name and function callback. // A NamedHandler is a struct that contains a name and function callback.
...@@ -63,7 +82,9 @@ type NamedHandler struct { ...@@ -63,7 +82,9 @@ type NamedHandler struct {
// copy creates a copy of the handler list. // copy creates a copy of the handler list.
func (l *HandlerList) copy() HandlerList { func (l *HandlerList) copy() HandlerList {
var n HandlerList n := HandlerList{
AfterEachFn: l.AfterEachFn,
}
n.list = append([]NamedHandler{}, l.list...) n.list = append([]NamedHandler{}, l.list...)
return n return n
} }
...@@ -111,11 +132,37 @@ func (l *HandlerList) Remove(n NamedHandler) { ...@@ -111,11 +132,37 @@ func (l *HandlerList) Remove(n NamedHandler) {
// Run executes all handlers in the list with a given request object. // Run executes all handlers in the list with a given request object.
func (l *HandlerList) Run(r *Request) { func (l *HandlerList) Run(r *Request) {
for _, f := range l.list { for i, h := range l.list {
f.Fn(r) h.Fn(r)
item := HandlerListRunItem{
Index: i, Handler: h, Request: r,
}
if l.AfterEachFn != nil && !l.AfterEachFn(item) {
return
}
} }
} }
// HandlerListLogItem logs the request handler and the state of the
// request's Error value. Always returns true to continue iterating
// request handlers in a HandlerList.
func HandlerListLogItem(item HandlerListRunItem) bool {
if item.Request.Config.Logger == nil {
return true
}
item.Request.Config.Logger.Log("DEBUG: RequestHandler",
item.Index, item.Handler.Name, item.Request.Error)
return true
}
// HandlerListStopOnError returns false to stop the HandlerList iterating
// over request handlers if Request.Error is not nil. True otherwise
// to continue iterating.
func HandlerListStopOnError(item HandlerListRunItem) bool {
return item.Request.Error == nil
}
// MakeAddToUserAgentHandler will add the name/version pair to the User-Agent request // 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 // header. If the extra parameters are provided they will be added as metadata to the
// name/version pair resulting in the following format. // name/version pair resulting in the following format.
......
package request
import (
"io"
"net/http"
"net/url"
)
func copyHTTPRequest(r *http.Request, body io.ReadCloser) *http.Request {
req := new(http.Request)
*req = *r
req.URL = &url.URL{}
*req.URL = *r.URL
req.Body = body
req.Header = http.Header{}
for k, v := range r.Header {
for _, vv := range v {
req.Header.Add(k, vv)
}
}
return req
}
package request
import (
"io"
"sync"
)
// offsetReader is a thread-safe io.ReadCloser to prevent racing
// with retrying requests
type offsetReader struct {
buf io.ReadSeeker
lock sync.Mutex
closed bool
}
func newOffsetReader(buf io.ReadSeeker, offset int64) *offsetReader {
reader := &offsetReader{}
buf.Seek(offset, 0)
reader.buf = buf
return reader
}
// Close will close the instance of the offset reader's access to
// the underlying io.ReadSeeker.
func (o *offsetReader) Close() error {
o.lock.Lock()
defer o.lock.Unlock()
o.closed = true
return nil
}
// Read is a thread-safe read of the underlying io.ReadSeeker
func (o *offsetReader) Read(p []byte) (int, error) {
o.lock.Lock()
defer o.lock.Unlock()
if o.closed {
return 0, io.EOF
}
return o.buf.Read(p)
}
// Seek is a thread-safe seeking operation.
func (o *offsetReader) Seek(offset int64, whence int) (int64, error) {
o.lock.Lock()
defer o.lock.Unlock()
return o.buf.Seek(offset, whence)
}
// CloseAndCopy will return a new offsetReader with a copy of the old buffer
// and close the old buffer.
func (o *offsetReader) CloseAndCopy(offset int64) *offsetReader {
o.Close()
return newOffsetReader(o.buf, offset)
}
// +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 }
// Is an empty reader that will trigger the Go HTTP client to not include
// and body in the HTTP request.
var noBodyReader = noBody{}
// +build go1.8
package request
import "net/http"
// Is a http.NoBody reader instructing Go HTTP client to not include
// and body in the HTTP request.
var noBodyReader = http.NoBody
...@@ -26,8 +26,11 @@ func WithRetryer(cfg *aws.Config, retryer Retryer) *aws.Config { ...@@ -26,8 +26,11 @@ func WithRetryer(cfg *aws.Config, retryer Retryer) *aws.Config {
// retryableCodes is a collection of service response codes which are retry-able // retryableCodes is a collection of service response codes which are retry-able
// without any further action. // without any further action.
var retryableCodes = map[string]struct{}{ var retryableCodes = map[string]struct{}{
"RequestError": {}, "RequestError": {},
"RequestTimeout": {}, "RequestTimeout": {},
}
var throttleCodes = map[string]struct{}{
"ProvisionedThroughputExceededException": {}, "ProvisionedThroughputExceededException": {},
"Throttling": {}, "Throttling": {},
"ThrottlingException": {}, "ThrottlingException": {},
...@@ -35,6 +38,7 @@ var retryableCodes = map[string]struct{}{ ...@@ -35,6 +38,7 @@ var retryableCodes = map[string]struct{}{
"RequestThrottled": {}, "RequestThrottled": {},
"LimitExceededException": {}, // Deleting 10+ DynamoDb tables at once "LimitExceededException": {}, // Deleting 10+ DynamoDb tables at once
"TooManyRequestsException": {}, // Lambda functions "TooManyRequestsException": {}, // Lambda functions
"PriorRequestNotComplete": {}, // Route53
} }
// credsExpiredCodes is a collection of error codes which signify the credentials // credsExpiredCodes is a collection of error codes which signify the credentials
...@@ -46,6 +50,11 @@ var credsExpiredCodes = map[string]struct{}{ ...@@ -46,6 +50,11 @@ var credsExpiredCodes = map[string]struct{}{
"RequestExpired": {}, // EC2 Only "RequestExpired": {}, // EC2 Only
} }
func isCodeThrottle(code string) bool {
_, ok := throttleCodes[code]
return ok
}
func isCodeRetryable(code string) bool { func isCodeRetryable(code string) bool {
if _, ok := retryableCodes[code]; ok { if _, ok := retryableCodes[code]; ok {
return true return true
...@@ -70,6 +79,17 @@ func (r *Request) IsErrorRetryable() bool { ...@@ -70,6 +79,17 @@ func (r *Request) IsErrorRetryable() bool {
return false 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())
}
}
return false
}
// IsErrorExpired returns whether the error code is a credential expiry error. // IsErrorExpired returns whether the error code is a credential expiry error.
// Returns false if the request has no Error set. // Returns false if the request has no Error set.
func (r *Request) IsErrorExpired() bool { func (r *Request) IsErrorExpired() bool {
......
package request
import (
"bytes"
"fmt"
"github.com/aws/aws-sdk-go/aws/awserr"
)
const (
// InvalidParameterErrCode is the error code for invalid parameters errors
InvalidParameterErrCode = "InvalidParameter"
// ParamRequiredErrCode is the error code for required parameter errors
ParamRequiredErrCode = "ParamRequiredError"
// ParamMinValueErrCode is the error code for fields with too low of a
// number value.
ParamMinValueErrCode = "ParamMinValueError"
// ParamMinLenErrCode is the error code for fields without enough elements.
ParamMinLenErrCode = "ParamMinLenError"
)
// Validator provides a way for types to perform validation logic on their
// input values that external code can use to determine if a type's values
// are valid.
type Validator interface {
Validate() error
}
// An ErrInvalidParams provides wrapping of invalid parameter errors found when
// validating API operation input parameters.
type ErrInvalidParams struct {
// Context is the base context of the invalid parameter group.
Context string
errs []ErrInvalidParam
}
// Add adds a new invalid parameter error to the collection of invalid
// parameters. The context of the invalid parameter will be updated to reflect
// this collection.
func (e *ErrInvalidParams) Add(err ErrInvalidParam) {
err.SetContext(e.Context)
e.errs = append(e.errs, err)
}
// AddNested adds the invalid parameter errors from another ErrInvalidParams
// value into this collection. The nested errors will have their nested context
// updated and base context to reflect the merging.
//
// Use for nested validations errors.
func (e *ErrInvalidParams) AddNested(nestedCtx string, nested ErrInvalidParams) {
for _, err := range nested.errs {
err.SetContext(e.Context)
err.AddNestedContext(nestedCtx)
e.errs = append(e.errs, err)
}
}
// Len returns the number of invalid parameter errors
func (e ErrInvalidParams) Len() int {
return len(e.errs)
}
// Code returns the code of the error
func (e ErrInvalidParams) Code() string {
return InvalidParameterErrCode
}
// Message returns the message of the error
func (e ErrInvalidParams) Message() string {
return fmt.Sprintf("%d validation error(s) found.", len(e.errs))
}
// Error returns the string formatted form of the invalid parameters.
func (e ErrInvalidParams) Error() string {
w := &bytes.Buffer{}
fmt.Fprintf(w, "%s: %s\n", e.Code(), e.Message())
for _, err := range e.errs {
fmt.Fprintf(w, "- %s\n", err.Message())
}
return w.String()
}
// OrigErr returns the invalid parameters as a awserr.BatchedErrors value
func (e ErrInvalidParams) OrigErr() error {
return awserr.NewBatchError(
InvalidParameterErrCode, e.Message(), e.OrigErrs())
}
// OrigErrs returns a slice of the invalid parameters
func (e ErrInvalidParams) OrigErrs() []error {
errs := make([]error, len(e.errs))
for i := 0; i < len(errs); i++ {
errs[i] = e.errs[i]
}
return errs
}
// An ErrInvalidParam represents an invalid parameter error type.
type ErrInvalidParam interface {
awserr.Error
// Field name the error occurred on.
Field() string
// SetContext updates the context of the error.
SetContext(string)
// AddNestedContext updates the error's context to include a nested level.
AddNestedContext(string)
}
type errInvalidParam struct {
context string
nestedContext string
field string
code string
msg string
}
// Code returns the error code for the type of invalid parameter.
func (e *errInvalidParam) Code() string {
return e.code
}
// Message returns the reason the parameter was invalid, and its context.
func (e *errInvalidParam) Message() string {
return fmt.Sprintf("%s, %s.", e.msg, e.Field())
}
// Error returns the string version of the invalid parameter error.
func (e *errInvalidParam) Error() string {
return fmt.Sprintf("%s: %s", e.code, e.Message())
}
// OrigErr returns nil, Implemented for awserr.Error interface.
func (e *errInvalidParam) OrigErr() error {
return nil
}
// Field Returns the field and context the error occurred.
func (e *errInvalidParam) Field() string {
field := e.context
if len(field) > 0 {
field += "."
}
if len(e.nestedContext) > 0 {
field += fmt.Sprintf("%s.", e.nestedContext)
}
field += e.field
return field
}
// SetContext updates the base context of the error.
func (e *errInvalidParam) SetContext(ctx string) {
e.context = ctx
}
// AddNestedContext prepends a context to the field's path.
func (e *errInvalidParam) AddNestedContext(ctx string) {
if len(e.nestedContext) == 0 {
e.nestedContext = ctx
} else {
e.nestedContext = fmt.Sprintf("%s.%s", ctx, e.nestedContext)
}
}
// An ErrParamRequired represents an required parameter error.
type ErrParamRequired struct {
errInvalidParam
}
// NewErrParamRequired creates a new required parameter error.
func NewErrParamRequired(field string) *ErrParamRequired {
return &ErrParamRequired{
errInvalidParam{
code: ParamRequiredErrCode,
field: field,
msg: fmt.Sprintf("missing required field"),
},
}
}
// An ErrParamMinValue represents a minimum value parameter error.
type ErrParamMinValue struct {
errInvalidParam
min float64
}
// NewErrParamMinValue creates a new minimum value parameter error.
func NewErrParamMinValue(field string, min float64) *ErrParamMinValue {
return &ErrParamMinValue{
errInvalidParam: errInvalidParam{
code: ParamMinValueErrCode,
field: field,
msg: fmt.Sprintf("minimum field value of %v", min),
},
min: min,
}
}
// MinValue returns the field's require minimum value.
//
// float64 is returned for both int and float min values.
func (e *ErrParamMinValue) MinValue() float64 {
return e.min
}
// An ErrParamMinLen represents a minimum length parameter error.
type ErrParamMinLen struct {
errInvalidParam
min int
}
// NewErrParamMinLen creates a new minimum length parameter error.
func NewErrParamMinLen(field string, min int) *ErrParamMinLen {
return &ErrParamMinLen{
errInvalidParam: errInvalidParam{
code: ParamMinValueErrCode,
field: field,
msg: fmt.Sprintf("minimum field size of %v", min),
},
min: min,
}
}
// MinLen returns the field's required minimum length.
func (e *ErrParamMinLen) MinLen() int {
return e.min
}
/*
Package session provides configuration for the SDK's service clients.
Sessions can be shared across all service clients that share the same base
configuration. The Session is built from the SDK's default configuration and
request handlers.
Sessions should be cached when possible, because creating a new Session will
load all configuration values from the environment, and config files each time
the Session is created. Sharing the Session value across all of your service
clients will ensure the configuration is loaded the fewest number of times possible.
Concurrency
Sessions are safe to use concurrently as long as the Session is not being
modified. The SDK will not modify the Session once the Session has been created.
Creating service clients concurrently from a shared Session is safe.
Sessions from Shared Config
Sessions can be created using the method above that will only load the
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
environment variable was set.
Creating Sessions
When creating Sessions optional aws.Config values can be passed in that will
override the default, or loaded config values the Session is being created
with. This allows you to provide additional, or case based, configuration
as needed.
By default NewSession will only load credentials from the shared credentials
file (~/.aws/credentials). If the AWS_SDK_LOAD_CONFIG environment variable is
set to a truthy value the Session will be created from the configuration
values from the shared config (~/.aws/config) and shared credentials
(~/.aws/credentials) files. See the section Sessions from Shared Config for
more information.
Create a Session with the default config and request handlers. With credentials
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()
// Create a Session with a custom region
sess, err := 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
}
svc := s3.New(sess)
Create Session With Option Overrides
In addition to NewSession, Sessions can be created using NewSessionWithOptions.
This func allows you to control and override how the Session will be created
through code instead of being driven by environment variables only.
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{})
// Specify profile to load for the session's config
sess, err := session.NewSessionWithOptions(session.Options{
Profile: "profile_name",
})
// Specify profile for config and region for requests
sess, err := 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,
})
Adding Handlers
You can add handlers to a session for processing HTTP requests. All service
clients that use the session inherit the handlers. For example, the following
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.Handlers.Send.PushFront(func(r *request.Request) {
// Log every request made and its payload
logger.Println("Request: %s/%s, Payload: %s",
r.ClientInfo.ServiceName, r.Operation, r.Params)
})
Deprecated "New" function
The New session function has been deprecated because it does not provide good
way to return errors that occur when loading the configuration files and values.
Because of this, NewSession was created so errors can be retrieved when
creating a session fails.
Shared Config Fields
By default the SDK will only load the shared credentials file's (~/.aws/credentials)
credentials values, and all other config is provided by the environment variables,
SDK defaults, and user provided aws.Config values.
If the AWS_SDK_LOAD_CONFIG environment variable is set, or SharedConfigEnable
option is used to create the Session the full shared config values will be
loaded. This includes credentials, region, and support for assume role. In
addition the Session will load its configuration from both the shared config
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).
Credentials are the values the SDK should use for authenticating requests with
AWS Services. They arfrom a configuration file will need to include both
aws_access_key_id and aws_secret_access_key must be provided together in the
same file to be considered valid. The values will be ignored if not a complete
group. aws_session_token is an optional field that can be provided if both of
the other two fields are also provided.
aws_access_key_id = AKID
aws_secret_access_key = SECRET
aws_session_token = TOKEN
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.
role_arn = arn:aws:iam::<account_number>:role/<role_name>
source_profile = profile_with_creds
external_id = 1234
mfa_serial = not supported!
role_session_name = session_name
Region is the region the SDK should use for looking up AWS service endpoints
and signing requests.
region = us-east-1
Environment Variables
When a Session is created several environment variables can be set to adjust
how the SDK functions, and what configuration data it loads when creating
Sessions. All environment values are optional, but some values like credentials
require multiple of the values to set or the partial values will be ignored.
All environment variable values are strings unless otherwise noted.
Environment configuration values. If set both Access Key ID and Secret Access
Key must be provided. Session Token and optionally also be provided, but is
not required.
# Access Key ID
AWS_ACCESS_KEY_ID=AKID
AWS_ACCESS_KEY=AKID # only read if AWS_ACCESS_KEY_ID is not set.
# Secret Access Key
AWS_SECRET_ACCESS_KEY=SECRET
AWS_SECRET_KEY=SECRET=SECRET # only read if AWS_SECRET_ACCESS_KEY is not set.
# Session Token
AWS_SESSION_TOKEN=TOKEN
Region value will instruct the SDK where to make service API requests to. If is
not provided in the environment the region must be provided before a service
client request is made.
AWS_REGION=us-east-1
# AWS_DEFAULT_REGION is only read if AWS_SDK_LOAD_CONFIG is also set,
# and AWS_REGION is not also set.
AWS_DEFAULT_REGION=us-east-1
Profile name the SDK should load use when loading shared config from the
configuration files. If not provided "default" will be used as the profile name.
AWS_PROFILE=my_profile
# AWS_DEFAULT_PROFILE is only read if AWS_SDK_LOAD_CONFIG is also set,
# and AWS_PROFILE is not also set.
AWS_DEFAULT_PROFILE=my_profile
SDK load config instructs the SDK to load the shared config in addition to
shared credentials. This also expands the configuration loaded so the shared
credentials will have parity with the shared config file. This also enables
Region and Profile support for the AWS_DEFAULT_REGION and AWS_DEFAULT_PROFILE
env values as well.
AWS_SDK_LOAD_CONFIG=1
Shared credentials file path can be set to instruct the SDK to use an alternative
file for the shared credentials. If not set the file will be loaded from
$HOME/.aws/credentials on Linux/Unix based systems, and
%USERPROFILE%\.aws\credentials on Windows.
AWS_SHARED_CREDENTIALS_FILE=$HOME/my_shared_credentials
Shared config file path can be set to instruct the SDK to use an alternative
file for the shared config. If not set the file will be loaded from
$HOME/.aws/config on Linux/Unix based systems, and
%USERPROFILE%\.aws\config on Windows.
AWS_CONFIG_FILE=$HOME/my_shared_config
*/
package session
package session
import (
"os"
"path/filepath"
"strconv"
"github.com/aws/aws-sdk-go/aws/credentials"
)
// 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
// will be ignored.
type envConfig struct {
// Environment configuration values. If set both Access Key ID and Secret Access
// Key must be provided. Session Token and optionally also be provided, but is
// not required.
//
// # Access Key ID
// AWS_ACCESS_KEY_ID=AKID
// AWS_ACCESS_KEY=AKID # only read if AWS_ACCESS_KEY_ID is not set.
//
// # Secret Access Key
// AWS_SECRET_ACCESS_KEY=SECRET
// AWS_SECRET_KEY=SECRET=SECRET # only read if AWS_SECRET_ACCESS_KEY is not set.
//
// # Session Token
// AWS_SESSION_TOKEN=TOKEN
Creds credentials.Value
// Region value will instruct the SDK where to make service API requests to. If is
// not provided in the environment the region must be provided before a service
// client request is made.
//
// AWS_REGION=us-east-1
//
// # AWS_DEFAULT_REGION is only read if AWS_SDK_LOAD_CONFIG is also set,
// # and AWS_REGION is not also set.
// AWS_DEFAULT_REGION=us-east-1
Region string
// Profile name the SDK should load use when loading shared configuration from the
// shared configuration files. If not provided "default" will be used as the
// profile name.
//
// AWS_PROFILE=my_profile
//
// # AWS_DEFAULT_PROFILE is only read if AWS_SDK_LOAD_CONFIG is also set,
// # and AWS_PROFILE is not also set.
// AWS_DEFAULT_PROFILE=my_profile
Profile string
// SDK load config instructs the SDK to load the shared config in addition to
// shared credentials. This also expands the configuration loaded from the shared
// credentials to have parity with the shared config file. This also enables
// Region and Profile support for the AWS_DEFAULT_REGION and AWS_DEFAULT_PROFILE
// env values as well.
//
// AWS_SDK_LOAD_CONFIG=1
EnableSharedConfig bool
// Shared credentials file path can be set to instruct the SDK to use an alternate
// file for the shared credentials. If not set the file will be loaded from
// $HOME/.aws/credentials on Linux/Unix based systems, and
// %USERPROFILE%\.aws\credentials on Windows.
//
// AWS_SHARED_CREDENTIALS_FILE=$HOME/my_shared_credentials
SharedCredentialsFile string
// Shared config file path can be set to instruct the SDK to use an alternate
// file for the shared config. If not set the file will be loaded from
// $HOME/.aws/config on Linux/Unix based systems, and
// %USERPROFILE%\.aws\config on Windows.
//
// AWS_CONFIG_FILE=$HOME/my_shared_config
SharedConfigFile string
}
var (
credAccessEnvKey = []string{
"AWS_ACCESS_KEY_ID",
"AWS_ACCESS_KEY",
}
credSecretEnvKey = []string{
"AWS_SECRET_ACCESS_KEY",
"AWS_SECRET_KEY",
}
credSessionEnvKey = []string{
"AWS_SESSION_TOKEN",
}
regionEnvKeys = []string{
"AWS_REGION",
"AWS_DEFAULT_REGION", // Only read if AWS_SDK_LOAD_CONFIG is also set
}
profileEnvKeys = []string{
"AWS_PROFILE",
"AWS_DEFAULT_PROFILE", // Only read if AWS_SDK_LOAD_CONFIG is also set
}
)
// loadEnvConfig retrieves the SDK's environment configuration.
// See `envConfig` for the values that will be retrieved.
//
// If the environment variable `AWS_SDK_LOAD_CONFIG` is set to a truthy value
// the shared SDK config will be loaded in addition to the SDK's specific
// configuration values.
func loadEnvConfig() envConfig {
enableSharedConfig, _ := strconv.ParseBool(os.Getenv("AWS_SDK_LOAD_CONFIG"))
return envConfigLoad(enableSharedConfig)
}
// loadEnvSharedConfig retrieves the SDK's environment configuration, and the
// SDK shared config. See `envConfig` for the values that will be retrieved.
//
// Loads the shared configuration in addition to the SDK's specific configuration.
// This will load the same values as `loadEnvConfig` if the `AWS_SDK_LOAD_CONFIG`
// environment variable is set.
func loadSharedEnvConfig() envConfig {
return envConfigLoad(true)
}
func envConfigLoad(enableSharedConfig bool) envConfig {
cfg := envConfig{}
cfg.EnableSharedConfig = enableSharedConfig
setFromEnvVal(&cfg.Creds.AccessKeyID, credAccessEnvKey)
setFromEnvVal(&cfg.Creds.SecretAccessKey, credSecretEnvKey)
setFromEnvVal(&cfg.Creds.SessionToken, credSessionEnvKey)
// Require logical grouping of credentials
if len(cfg.Creds.AccessKeyID) == 0 || len(cfg.Creds.SecretAccessKey) == 0 {
cfg.Creds = credentials.Value{}
} else {
cfg.Creds.ProviderName = "EnvConfigCredentials"
}
regionKeys := regionEnvKeys
profileKeys := profileEnvKeys
if !cfg.EnableSharedConfig {
regionKeys = regionKeys[:1]
profileKeys = profileKeys[:1]
}
setFromEnvVal(&cfg.Region, regionKeys)
setFromEnvVal(&cfg.Profile, profileKeys)
cfg.SharedCredentialsFile = sharedCredentialsFilename()
cfg.SharedConfigFile = sharedConfigFilename()
return cfg
}
func setFromEnvVal(dst *string, keys []string) {
for _, k := range keys {
if v := os.Getenv(k); len(v) > 0 {
*dst = v
break
}
}
}
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
}
package session
import (
"fmt"
"io/ioutil"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/go-ini/ini"
)
const (
// Static Credentials group
accessKeyIDKey = `aws_access_key_id` // group required
secretAccessKey = `aws_secret_access_key` // group required
sessionTokenKey = `aws_session_token` // optional
// Assume Role Credentials group
roleArnKey = `role_arn` // group required
sourceProfileKey = `source_profile` // group required
externalIDKey = `external_id` // optional
mfaSerialKey = `mfa_serial` // optional
roleSessionNameKey = `role_session_name` // optional
// Additional Config fields
regionKey = `region`
// DefaultSharedConfigProfile is the default profile to be used when
// loading configuration from the config files if another profile name
// is not provided.
DefaultSharedConfigProfile = `default`
)
type assumeRoleConfig struct {
RoleARN string
SourceProfile string
ExternalID string
MFASerial string
RoleSessionName string
}
// sharedConfig represents the configuration fields of the SDK config files.
type sharedConfig struct {
// Credentials values from the config file. Both aws_access_key_id
// and aws_secret_access_key must be provided together in the same file
// to be considered valid. The values will be ignored if not a complete group.
// aws_session_token is an optional field that can be provided if both of the
// other two fields are also provided.
//
// aws_access_key_id
// aws_secret_access_key
// aws_session_token
Creds credentials.Value
AssumeRole assumeRoleConfig
AssumeRoleSource *sharedConfig
// Region is the region the SDK should use for looking up AWS service endpoints
// and signing requests.
//
// region
Region string
}
type sharedConfigFile struct {
Filename string
IniData *ini.File
}
// loadSharedConfig retrieves the configuration from the list of files
// using the profile provided. The order the files are listed will determine
// precedence. Values in subsequent files will overwrite values defined in
// earlier files.
//
// For example, given two files A and B. Both define credentials. If the order
// of the files are A then B, B's credential values will be used instead of A's.
//
// See sharedConfig.setFromFile for information how the config files
// will be loaded.
func loadSharedConfig(profile string, filenames []string) (sharedConfig, error) {
if len(profile) == 0 {
profile = DefaultSharedConfigProfile
}
files, err := loadSharedConfigIniFiles(filenames)
if err != nil {
return sharedConfig{}, err
}
cfg := sharedConfig{}
if err = cfg.setFromIniFiles(profile, files); err != nil {
return sharedConfig{}, err
}
if len(cfg.AssumeRole.SourceProfile) > 0 {
if err := cfg.setAssumeRoleSource(profile, files); err != nil {
return sharedConfig{}, err
}
}
return cfg, nil
}
func loadSharedConfigIniFiles(filenames []string) ([]sharedConfigFile, error) {
files := make([]sharedConfigFile, 0, len(filenames))
for _, filename := range filenames {
b, err := ioutil.ReadFile(filename)
if err != nil {
// Skip files which can't be opened and read for whatever reason
continue
}
f, err := ini.Load(b)
if err != nil {
return nil, SharedConfigLoadError{Filename: filename}
}
files = append(files, sharedConfigFile{
Filename: filename, IniData: f,
})
}
return files, nil
}
func (cfg *sharedConfig) setAssumeRoleSource(origProfile string, files []sharedConfigFile) error {
var assumeRoleSrc sharedConfig
// Multiple level assume role chains are not support
if cfg.AssumeRole.SourceProfile == origProfile {
assumeRoleSrc = *cfg
assumeRoleSrc.AssumeRole = assumeRoleConfig{}
} else {
err := assumeRoleSrc.setFromIniFiles(cfg.AssumeRole.SourceProfile, files)
if err != nil {
return err
}
}
if len(assumeRoleSrc.Creds.AccessKeyID) == 0 {
return SharedConfigAssumeRoleError{RoleARN: cfg.AssumeRole.RoleARN}
}
cfg.AssumeRoleSource = &assumeRoleSrc
return nil
}
func (cfg *sharedConfig) setFromIniFiles(profile string, files []sharedConfigFile) error {
// Trim files from the list that don't exist.
for _, f := range files {
if err := cfg.setFromIniFile(profile, f); err != nil {
if _, ok := err.(SharedConfigProfileNotExistsError); ok {
// Ignore proviles missings
continue
}
return err
}
}
return nil
}
// setFromFile loads the configuration from the file using
// the profile provided. A sharedConfig pointer type value is used so that
// multiple config file loadings can be chained.
//
// Only loads complete logically grouped values, and will not set fields in cfg
// for incomplete grouped values in the config. Such as credentials. For example
// if a config file only includes aws_access_key_id but no aws_secret_access_key
// the aws_access_key_id will be ignored.
func (cfg *sharedConfig) setFromIniFile(profile string, file sharedConfigFile) error {
section, err := file.IniData.GetSection(profile)
if err != nil {
// Fallback to to alternate profile name: profile <name>
section, err = file.IniData.GetSection(fmt.Sprintf("profile %s", profile))
if err != nil {
return SharedConfigProfileNotExistsError{Profile: profile, Err: err}
}
}
// Shared Credentials
akid := section.Key(accessKeyIDKey).String()
secret := section.Key(secretAccessKey).String()
if len(akid) > 0 && len(secret) > 0 {
cfg.Creds = credentials.Value{
AccessKeyID: akid,
SecretAccessKey: secret,
SessionToken: section.Key(sessionTokenKey).String(),
ProviderName: fmt.Sprintf("SharedConfigCredentials: %s", file.Filename),
}
}
// Assume Role
roleArn := section.Key(roleArnKey).String()
srcProfile := section.Key(sourceProfileKey).String()
if len(roleArn) > 0 && len(srcProfile) > 0 {
cfg.AssumeRole = assumeRoleConfig{
RoleARN: roleArn,
SourceProfile: srcProfile,
ExternalID: section.Key(externalIDKey).String(),
MFASerial: section.Key(mfaSerialKey).String(),
RoleSessionName: section.Key(roleSessionNameKey).String(),
}
}
// Region
if v := section.Key(regionKey).String(); len(v) > 0 {
cfg.Region = v
}
return nil
}
// SharedConfigLoadError is an error for the shared config file failed to load.
type SharedConfigLoadError struct {
Filename string
Err error
}
// Code is the short id of the error.
func (e SharedConfigLoadError) Code() string {
return "SharedConfigLoadError"
}
// Message is the description of the error
func (e SharedConfigLoadError) Message() string {
return fmt.Sprintf("failed to load config file, %s", e.Filename)
}
// OrigErr is the underlying error that caused the failure.
func (e SharedConfigLoadError) OrigErr() error {
return e.Err
}
// Error satisfies the error interface.
func (e SharedConfigLoadError) Error() string {
return awserr.SprintError(e.Code(), e.Message(), "", e.Err)
}
// SharedConfigProfileNotExistsError is an error for the shared config when
// the profile was not find in the config file.
type SharedConfigProfileNotExistsError struct {
Profile string
Err error
}
// Code is the short id of the error.
func (e SharedConfigProfileNotExistsError) Code() string {
return "SharedConfigProfileNotExistsError"
}
// Message is the description of the error
func (e SharedConfigProfileNotExistsError) Message() string {
return fmt.Sprintf("failed to get profile, %s", e.Profile)
}
// OrigErr is the underlying error that caused the failure.
func (e SharedConfigProfileNotExistsError) OrigErr() error {
return e.Err
}
// Error satisfies the error interface.
func (e SharedConfigProfileNotExistsError) Error() string {
return awserr.SprintError(e.Code(), e.Message(), "", e.Err)
}
// SharedConfigAssumeRoleError is an error for the shared config when the
// profile contains assume role information, but that information is invalid
// or not complete.
type SharedConfigAssumeRoleError struct {
RoleARN string
}
// Code is the short id of the error.
func (e SharedConfigAssumeRoleError) Code() string {
return "SharedConfigAssumeRoleError"
}
// Message is the description of the error
func (e SharedConfigAssumeRoleError) Message() string {
return fmt.Sprintf("failed to load assume role for %s, source profile has no shared credentials",
e.RoleARN)
}
// OrigErr is the underlying error that caused the failure.
func (e SharedConfigAssumeRoleError) OrigErr() error {
return nil
}
// Error satisfies the error interface.
func (e SharedConfigAssumeRoleError) Error() string {
return awserr.SprintError(e.Code(), e.Message(), "", nil)
}
package v4
import (
"net/http"
"strings"
)
// validator houses a set of rule needed for validation of a
// string value
type rules []rule
// rule interface allows for more flexible rules and just simply
// checks whether or not a value adheres to that rule
type rule interface {
IsValid(value string) bool
}
// IsValid will iterate through all rules and see if any rules
// apply to the value and supports nested rules
func (r rules) IsValid(value string) bool {
for _, rule := range r {
if rule.IsValid(value) {
return true
}
}
return false
}
// mapRule generic rule for maps
type mapRule map[string]struct{}
// IsValid for the map rule satisfies whether it exists in the map
func (m mapRule) IsValid(value string) bool {
_, ok := m[value]
return ok
}
// whitelist is a generic rule for whitelisting
type whitelist struct {
rule
}
// IsValid for whitelist checks if the value is within the whitelist
func (w whitelist) IsValid(value string) bool {
return w.rule.IsValid(value)
}
// blacklist is a generic rule for blacklisting
type blacklist struct {
rule
}
// IsValid for whitelist checks if the value is within the whitelist
func (b blacklist) IsValid(value string) bool {
return !b.rule.IsValid(value)
}
type patterns []string
// IsValid for patterns checks each pattern and returns if a match has
// been found
func (p patterns) IsValid(value string) bool {
for _, pattern := range p {
if strings.HasPrefix(http.CanonicalHeaderKey(value), pattern) {
return true
}
}
return false
}
// inclusiveRules rules allow for rules to depend on one another
type inclusiveRules []rule
// IsValid will return true if all rules are true
func (r inclusiveRules) IsValid(value string) bool {
for _, rule := range r {
if !rule.IsValid(value) {
return false
}
}
return true
}
// +build go1.5
package v4
import (
"net/url"
"strings"
)
func getURIPath(u *url.URL) string {
var uri string
if len(u.Opaque) > 0 {
uri = "/" + strings.Join(strings.Split(u.Opaque, "/")[3:], "/")
} else {
uri = u.EscapedPath()
}
if len(uri) == 0 {
uri = "/"
}
return uri
}
...@@ -5,7 +5,13 @@ import ( ...@@ -5,7 +5,13 @@ import (
"sync" "sync"
) )
// 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 { func ReadSeekCloser(r io.Reader) ReaderSeekerCloser {
return ReaderSeekerCloser{r} return ReaderSeekerCloser{r}
} }
...@@ -44,6 +50,12 @@ func (r ReaderSeekerCloser) Seek(offset int64, whence int) (int64, error) { ...@@ -44,6 +50,12 @@ func (r ReaderSeekerCloser) Seek(offset int64, whence int) (int64, error) {
return int64(0), nil 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
}
// Close closes the ReaderSeekerCloser. // Close closes the ReaderSeekerCloser.
// //
// If the ReaderSeekerCloser is not an io.Closer nothing will be done. // If the ReaderSeekerCloser is not an io.Closer nothing will be done.
...@@ -61,23 +73,41 @@ func (r ReaderSeekerCloser) Close() error { ...@@ -61,23 +73,41 @@ func (r ReaderSeekerCloser) Close() error {
type WriteAtBuffer struct { type WriteAtBuffer struct {
buf []byte buf []byte
m sync.Mutex m sync.Mutex
// GrowthCoeff defines the growth rate of the internal buffer. By
// default, the growth rate is 1, where expanding the internal
// buffer will allocate only enough capacity to fit the new expected
// length.
GrowthCoeff float64
}
// NewWriteAtBuffer creates a WriteAtBuffer with an internal buffer
// provided by buf.
func NewWriteAtBuffer(buf []byte) *WriteAtBuffer {
return &WriteAtBuffer{buf: buf}
} }
// WriteAt writes a slice of bytes to a buffer starting at the position provided // WriteAt writes a slice of bytes to a buffer starting at the position provided
// The number of bytes written will be returned, or error. Can overwrite previous // The number of bytes written will be returned, or error. Can overwrite previous
// written slices if the write ats overlap. // written slices if the write ats overlap.
func (b *WriteAtBuffer) WriteAt(p []byte, pos int64) (n int, err error) { func (b *WriteAtBuffer) WriteAt(p []byte, pos int64) (n int, err error) {
pLen := len(p)
expLen := pos + int64(pLen)
b.m.Lock() b.m.Lock()
defer b.m.Unlock() defer b.m.Unlock()
expLen := pos + int64(len(p))
if int64(len(b.buf)) < expLen { if int64(len(b.buf)) < expLen {
newBuf := make([]byte, expLen) if int64(cap(b.buf)) < expLen {
copy(newBuf, b.buf) if b.GrowthCoeff < 1 {
b.buf = newBuf b.GrowthCoeff = 1
}
newBuf := make([]byte, expLen, int64(b.GrowthCoeff*float64(expLen)))
copy(newBuf, b.buf)
b.buf = newBuf
}
b.buf = b.buf[:expLen]
} }
copy(b.buf[pos:], p) copy(b.buf[pos:], p)
return len(p), nil return pLen, nil
} }
// Bytes returns a slice of bytes written to the buffer. // Bytes returns a slice of bytes written to the buffer.
......
...@@ -5,4 +5,4 @@ package aws ...@@ -5,4 +5,4 @@ package aws
const SDKName = "aws-sdk-go" const SDKName = "aws-sdk-go"
// SDKVersion is the version of this SDK // SDKVersion is the version of this SDK
const SDKVersion = "1.0.8" const SDKVersion = "1.6.10"
// Package endpoints validates regional endpoints for services.
package endpoints
//go:generate go run ../model/cli/gen-endpoints/main.go endpoints.json endpoints_map.go
//go:generate gofmt -s -w endpoints_map.go
import (
"fmt"
"regexp"
"strings"
)
// NormalizeEndpoint takes and endpoint and service API information to return a
// normalized endpoint and signing region. If the endpoint is not an empty string
// the service name and region will be used to look up the service's API endpoint.
// If the endpoint is provided the scheme will be added if it is not present.
func NormalizeEndpoint(endpoint, serviceName, region string, disableSSL bool) (normEndpoint, signingRegion string) {
if endpoint == "" {
return EndpointForRegion(serviceName, region, disableSSL)
}
return AddScheme(endpoint, disableSSL), ""
}
// EndpointForRegion returns an endpoint and its signing region for a service and region.
// if the service and region pair are not found endpoint and signingRegion will be empty.
func EndpointForRegion(svcName, region string, disableSSL bool) (endpoint, signingRegion string) {
derivedKeys := []string{
region + "/" + svcName,
region + "/*",
"*/" + svcName,
"*/*",
}
for _, key := range derivedKeys {
if val, ok := endpointsMap.Endpoints[key]; ok {
ep := val.Endpoint
ep = strings.Replace(ep, "{region}", region, -1)
ep = strings.Replace(ep, "{service}", svcName, -1)
endpoint = ep
signingRegion = val.SigningRegion
break
}
}
return AddScheme(endpoint, disableSSL), signingRegion
}
// Regular expression to determine if the endpoint string is prefixed with a scheme.
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 be added instead of the default HTTPS.
func AddScheme(endpoint string, disableSSL bool) string {
if endpoint != "" && !schemeRE.MatchString(endpoint) {
scheme := "https"
if disableSSL {
scheme = "http"
}
endpoint = fmt.Sprintf("%s://%s", scheme, endpoint)
}
return endpoint
}
{
"version": 2,
"endpoints": {
"*/*": {
"endpoint": "{service}.{region}.amazonaws.com"
},
"cn-north-1/*": {
"endpoint": "{service}.{region}.amazonaws.com.cn",
"signatureVersion": "v4"
},
"us-gov-west-1/iam": {
"endpoint": "iam.us-gov.amazonaws.com"
},
"us-gov-west-1/sts": {
"endpoint": "sts.us-gov-west-1.amazonaws.com"
},
"us-gov-west-1/s3": {
"endpoint": "s3-{region}.amazonaws.com"
},
"*/cloudfront": {
"endpoint": "cloudfront.amazonaws.com",
"signingRegion": "us-east-1"
},
"*/cloudsearchdomain": {
"endpoint": "",
"signingRegion": "us-east-1"
},
"*/data.iot": {
"endpoint": "",
"signingRegion": "us-east-1"
},
"*/ec2metadata": {
"endpoint": "http://169.254.169.254/latest",
"signingRegion": "us-east-1"
},
"*/iam": {
"endpoint": "iam.amazonaws.com",
"signingRegion": "us-east-1"
},
"*/importexport": {
"endpoint": "importexport.amazonaws.com",
"signingRegion": "us-east-1"
},
"*/route53": {
"endpoint": "route53.amazonaws.com",
"signingRegion": "us-east-1"
},
"*/sts": {
"endpoint": "sts.amazonaws.com",
"signingRegion": "us-east-1"
},
"*/waf": {
"endpoint": "waf.amazonaws.com",
"signingRegion": "us-east-1"
},
"us-east-1/sdb": {
"endpoint": "sdb.amazonaws.com",
"signingRegion": "us-east-1"
},
"us-east-1/s3": {
"endpoint": "s3.amazonaws.com"
},
"us-west-1/s3": {
"endpoint": "s3-{region}.amazonaws.com"
},
"us-west-2/s3": {
"endpoint": "s3-{region}.amazonaws.com"
},
"eu-west-1/s3": {
"endpoint": "s3-{region}.amazonaws.com"
},
"ap-southeast-1/s3": {
"endpoint": "s3-{region}.amazonaws.com"
},
"ap-southeast-2/s3": {
"endpoint": "s3-{region}.amazonaws.com"
},
"ap-northeast-1/s3": {
"endpoint": "s3-{region}.amazonaws.com"
},
"ap-northeast-2/s3": {
"endpoint": "s3-{region}.amazonaws.com"
},
"sa-east-1/s3": {
"endpoint": "s3-{region}.amazonaws.com"
},
"eu-central-1/s3": {
"endpoint": "{service}.{region}.amazonaws.com",
"signatureVersion": "v4"
}
}
}
package endpoints
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
type endpointStruct struct {
Version int
Endpoints map[string]endpointEntry
}
type endpointEntry struct {
Endpoint string
SigningRegion string
}
var endpointsMap = endpointStruct{
Version: 2,
Endpoints: map[string]endpointEntry{
"*/*": {
Endpoint: "{service}.{region}.amazonaws.com",
},
"*/cloudfront": {
Endpoint: "cloudfront.amazonaws.com",
SigningRegion: "us-east-1",
},
"*/cloudsearchdomain": {
Endpoint: "",
SigningRegion: "us-east-1",
},
"*/data.iot": {
Endpoint: "",
SigningRegion: "us-east-1",
},
"*/ec2metadata": {
Endpoint: "http://169.254.169.254/latest",
SigningRegion: "us-east-1",
},
"*/iam": {
Endpoint: "iam.amazonaws.com",
SigningRegion: "us-east-1",
},
"*/importexport": {
Endpoint: "importexport.amazonaws.com",
SigningRegion: "us-east-1",
},
"*/route53": {
Endpoint: "route53.amazonaws.com",
SigningRegion: "us-east-1",
},
"*/sts": {
Endpoint: "sts.amazonaws.com",
SigningRegion: "us-east-1",
},
"*/waf": {
Endpoint: "waf.amazonaws.com",
SigningRegion: "us-east-1",
},
"ap-northeast-1/s3": {
Endpoint: "s3-{region}.amazonaws.com",
},
"ap-northeast-2/s3": {
Endpoint: "s3-{region}.amazonaws.com",
},
"ap-southeast-1/s3": {
Endpoint: "s3-{region}.amazonaws.com",
},
"ap-southeast-2/s3": {
Endpoint: "s3-{region}.amazonaws.com",
},
"cn-north-1/*": {
Endpoint: "{service}.{region}.amazonaws.com.cn",
},
"eu-central-1/s3": {
Endpoint: "{service}.{region}.amazonaws.com",
},
"eu-west-1/s3": {
Endpoint: "s3-{region}.amazonaws.com",
},
"sa-east-1/s3": {
Endpoint: "s3-{region}.amazonaws.com",
},
"us-east-1/s3": {
Endpoint: "s3.amazonaws.com",
},
"us-east-1/sdb": {
Endpoint: "sdb.amazonaws.com",
SigningRegion: "us-east-1",
},
"us-gov-west-1/iam": {
Endpoint: "iam.us-gov.amazonaws.com",
},
"us-gov-west-1/s3": {
Endpoint: "s3-{region}.amazonaws.com",
},
"us-gov-west-1/sts": {
Endpoint: "sts.us-gov-west-1.amazonaws.com",
},
"us-west-1/s3": {
Endpoint: "s3-{region}.amazonaws.com",
},
"us-west-2/s3": {
Endpoint: "s3-{region}.amazonaws.com",
},
},
}
// Package ec2query provides serialisation of AWS EC2 requests and responses. // Package ec2query provides serialization of AWS EC2 requests and responses.
package ec2query package ec2query
//go:generate go run ../../../models/protocol_tests/generate.go ../../../models/protocol_tests/input/ec2.json build_test.go //go:generate go run -tags codegen ../../../models/protocol_tests/generate.go ../../../models/protocol_tests/input/ec2.json build_test.go
import ( import (
"net/url" "net/url"
...@@ -11,6 +11,9 @@ import ( ...@@ -11,6 +11,9 @@ import (
"github.com/aws/aws-sdk-go/private/protocol/query/queryutil" "github.com/aws/aws-sdk-go/private/protocol/query/queryutil"
) )
// BuildHandler is a named request handler for building ec2query protocol requests
var BuildHandler = request.NamedHandler{Name: "awssdk.ec2query.Build", Fn: Build}
// Build builds a request for the EC2 protocol. // Build builds a request for the EC2 protocol.
func Build(r *request.Request) { func Build(r *request.Request) {
body := url.Values{ body := url.Values{
......
package ec2query package ec2query
//go:generate go run ../../../models/protocol_tests/generate.go ../../../models/protocol_tests/output/ec2.json unmarshal_test.go //go:generate go run -tags codegen ../../../models/protocol_tests/generate.go ../../../models/protocol_tests/output/ec2.json unmarshal_test.go
import ( import (
"encoding/xml" "encoding/xml"
...@@ -11,6 +11,15 @@ import ( ...@@ -11,6 +11,15 @@ import (
"github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil" "github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil"
) )
// UnmarshalHandler is a named request handler for unmarshaling ec2query protocol requests
var UnmarshalHandler = request.NamedHandler{Name: "awssdk.ec2query.Unmarshal", Fn: Unmarshal}
// UnmarshalMetaHandler is a named request handler for unmarshaling ec2query protocol request metadata
var UnmarshalMetaHandler = request.NamedHandler{Name: "awssdk.ec2query.UnmarshalMeta", Fn: UnmarshalMeta}
// UnmarshalErrorHandler is a named request handler for unmarshaling ec2query protocol request errors
var UnmarshalErrorHandler = request.NamedHandler{Name: "awssdk.ec2query.UnmarshalError", Fn: UnmarshalError}
// Unmarshal unmarshals a response body for the EC2 protocol. // Unmarshal unmarshals a response body for the EC2 protocol.
func Unmarshal(r *request.Request) { func Unmarshal(r *request.Request) {
defer r.HTTPResponse.Body.Close() defer r.HTTPResponse.Body.Close()
...@@ -33,7 +42,7 @@ type xmlErrorResponse struct { ...@@ -33,7 +42,7 @@ type xmlErrorResponse struct {
XMLName xml.Name `xml:"Response"` XMLName xml.Name `xml:"Response"`
Code string `xml:"Errors>Error>Code"` Code string `xml:"Errors>Error>Code"`
Message string `xml:"Errors>Error>Message"` Message string `xml:"Errors>Error>Message"`
RequestID string `xml:"RequestId"` RequestID string `xml:"RequestID"`
} }
// UnmarshalError unmarshals a response error for the EC2 protocol. // UnmarshalError unmarshals a response error for the EC2 protocol.
......
package protocol
import (
"crypto/rand"
"fmt"
"reflect"
)
// RandReader is the random reader the protocol package will use to read
// random bytes from. This is exported for testing, and should not be used.
var RandReader = rand.Reader
const idempotencyTokenFillTag = `idempotencyToken`
// CanSetIdempotencyToken returns true if the struct field should be
// automatically populated with a Idempotency token.
//
// Only *string and string type fields that are tagged with idempotencyToken
// which are not already set can be auto filled.
func CanSetIdempotencyToken(v reflect.Value, f reflect.StructField) bool {
switch u := v.Interface().(type) {
// To auto fill an Idempotency token the field must be a string,
// tagged for auto fill, and have a zero value.
case *string:
return u == nil && len(f.Tag.Get(idempotencyTokenFillTag)) != 0
case string:
return len(u) == 0 && len(f.Tag.Get(idempotencyTokenFillTag)) != 0
}
return false
}
// GetIdempotencyToken returns a randomly generated idempotency token.
func GetIdempotencyToken() string {
b := make([]byte, 16)
RandReader.Read(b)
return UUIDVersion4(b)
}
// SetIdempotencyToken will set the value provided with a Idempotency Token.
// Given that the value can be set. Will panic if value is not setable.
func SetIdempotencyToken(v reflect.Value) {
if v.Kind() == reflect.Ptr {
if v.IsNil() && v.CanSet() {
v.Set(reflect.New(v.Type().Elem()))
}
v = v.Elem()
}
v = reflect.Indirect(v)
if !v.CanSet() {
panic(fmt.Sprintf("unable to set idempotnecy token %v", v))
}
b := make([]byte, 16)
_, err := rand.Read(b)
if err != nil {
// TODO handle error
return
}
v.Set(reflect.ValueOf(UUIDVersion4(b)))
}
// UUIDVersion4 returns a Version 4 random UUID from the byte slice provided
func UUIDVersion4(u []byte) string {
// https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_.28random.29
// 13th character is "4"
u[6] = (u[6] | 0x40) & 0x4F
// 17th character is "8", "9", "a", or "b"
u[8] = (u[8] | 0x80) & 0xBF
return fmt.Sprintf(`%X-%X-%X-%X-%X`, u[0:4], u[4:6], u[6:8], u[8:10], u[10:])
}
// Package jsonutil provides JSON serialisation of AWS requests and responses. // Package jsonutil provides JSON serialization of AWS requests and responses.
package jsonutil package jsonutil
import ( import (
...@@ -9,6 +9,8 @@ import ( ...@@ -9,6 +9,8 @@ import (
"sort" "sort"
"strconv" "strconv"
"time" "time"
"github.com/aws/aws-sdk-go/private/protocol"
) )
var timeType = reflect.ValueOf(time.Time{}).Type() var timeType = reflect.ValueOf(time.Time{}).Type()
...@@ -85,11 +87,8 @@ func buildStruct(value reflect.Value, buf *bytes.Buffer, tag reflect.StructTag) ...@@ -85,11 +87,8 @@ func buildStruct(value reflect.Value, buf *bytes.Buffer, tag reflect.StructTag)
first := true first := true
for i := 0; i < t.NumField(); i++ { for i := 0; i < t.NumField(); i++ {
member := value.Field(i) member := value.Field(i)
if (member.Kind() == reflect.Ptr || member.Kind() == reflect.Slice || member.Kind() == reflect.Map) && member.IsNil() {
continue // ignore unset fields
}
field := t.Field(i) field := t.Field(i)
if field.PkgPath != "" { if field.PkgPath != "" {
continue // ignore unexported fields continue // ignore unexported fields
} }
...@@ -99,6 +98,18 @@ func buildStruct(value reflect.Value, buf *bytes.Buffer, tag reflect.StructTag) ...@@ -99,6 +98,18 @@ func buildStruct(value reflect.Value, buf *bytes.Buffer, tag reflect.StructTag)
if field.Tag.Get("location") != "" { if field.Tag.Get("location") != "" {
continue // ignore non-body elements continue // ignore non-body elements
} }
if field.Tag.Get("ignore") != "" {
continue
}
if protocol.CanSetIdempotencyToken(member, field) {
token := protocol.GetIdempotencyToken()
member = reflect.ValueOf(&token)
}
if (member.Kind() == reflect.Ptr || member.Kind() == reflect.Slice || member.Kind() == reflect.Map) && member.IsNil() {
continue // ignore unset fields
}
if first { if first {
first = false first = false
...@@ -112,7 +123,8 @@ func buildStruct(value reflect.Value, buf *bytes.Buffer, tag reflect.StructTag) ...@@ -112,7 +123,8 @@ func buildStruct(value reflect.Value, buf *bytes.Buffer, tag reflect.StructTag)
name = locName name = locName
} }
fmt.Fprintf(buf, "%q:", name) writeString(name, buf)
buf.WriteString(`:`)
err := buildAny(member, buf, field.Tag) err := buildAny(member, buf, field.Tag)
if err != nil { if err != nil {
...@@ -151,7 +163,7 @@ func (sv sortedValues) Less(i, j int) bool { return sv[i].String() < sv[j].Strin ...@@ -151,7 +163,7 @@ func (sv sortedValues) Less(i, j int) bool { return sv[i].String() < sv[j].Strin
func buildMap(value reflect.Value, buf *bytes.Buffer, tag reflect.StructTag) error { func buildMap(value reflect.Value, buf *bytes.Buffer, tag reflect.StructTag) error {
buf.WriteString("{") buf.WriteString("{")
var sv sortedValues = value.MapKeys() sv := sortedValues(value.MapKeys())
sort.Sort(sv) sort.Sort(sv)
for i, k := range sv { for i, k := range sv {
...@@ -159,7 +171,9 @@ func buildMap(value reflect.Value, buf *bytes.Buffer, tag reflect.StructTag) err ...@@ -159,7 +171,9 @@ func buildMap(value reflect.Value, buf *bytes.Buffer, tag reflect.StructTag) err
buf.WriteByte(',') buf.WriteByte(',')
} }
fmt.Fprintf(buf, "%q:", k) writeString(k.String(), buf)
buf.WriteString(`:`)
buildAny(value.MapIndex(k), buf, "") buildAny(value.MapIndex(k), buf, "")
} }
......
// Package jsonrpc provides JSON RPC utilities for serialisation of AWS // Package jsonrpc provides JSON RPC utilities for serialization of AWS
// requests and responses. // requests and responses.
package jsonrpc package jsonrpc
//go:generate go run ../../../models/protocol_tests/generate.go ../../../models/protocol_tests/input/json.json build_test.go //go:generate go run -tags codegen ../../../models/protocol_tests/generate.go ../../../models/protocol_tests/input/json.json build_test.go
//go:generate go run ../../../models/protocol_tests/generate.go ../../../models/protocol_tests/output/json.json unmarshal_test.go //go:generate go run -tags codegen ../../../models/protocol_tests/generate.go ../../../models/protocol_tests/output/json.json unmarshal_test.go
import ( import (
"encoding/json" "encoding/json"
...@@ -18,6 +18,18 @@ import ( ...@@ -18,6 +18,18 @@ import (
var emptyJSON = []byte("{}") var emptyJSON = []byte("{}")
// BuildHandler is a named request handler for building jsonrpc protocol requests
var BuildHandler = request.NamedHandler{Name: "awssdk.jsonrpc.Build", Fn: Build}
// UnmarshalHandler is a named request handler for unmarshaling jsonrpc protocol requests
var UnmarshalHandler = request.NamedHandler{Name: "awssdk.jsonrpc.Unmarshal", Fn: Unmarshal}
// UnmarshalMetaHandler is a named request handler for unmarshaling jsonrpc protocol request metadata
var UnmarshalMetaHandler = request.NamedHandler{Name: "awssdk.jsonrpc.UnmarshalMeta", Fn: UnmarshalMeta}
// UnmarshalErrorHandler is a named request handler for unmarshaling jsonrpc protocol request errors
var UnmarshalErrorHandler = request.NamedHandler{Name: "awssdk.jsonrpc.UnmarshalError", Fn: UnmarshalError}
// Build builds a JSON payload for a JSON RPC request. // Build builds a JSON payload for a JSON RPC request.
func Build(req *request.Request) { func Build(req *request.Request) {
var buf []byte var buf []byte
......
// Package query provides serialisation of AWS query requests, and responses. // Package query provides serialization of AWS query requests, and responses.
package query package query
//go:generate go run ../../../models/protocol_tests/generate.go ../../../models/protocol_tests/input/query.json build_test.go //go:generate go run -tags codegen ../../../models/protocol_tests/generate.go ../../../models/protocol_tests/input/query.json build_test.go
import ( import (
"net/url" "net/url"
...@@ -11,6 +11,9 @@ import ( ...@@ -11,6 +11,9 @@ import (
"github.com/aws/aws-sdk-go/private/protocol/query/queryutil" "github.com/aws/aws-sdk-go/private/protocol/query/queryutil"
) )
// BuildHandler is a named request handler for building query protocol requests
var BuildHandler = request.NamedHandler{Name: "awssdk.query.Build", Fn: Build}
// Build builds a request for an AWS Query service. // Build builds a request for an AWS Query service.
func Build(r *request.Request) { func Build(r *request.Request) {
body := url.Values{ body := url.Values{
......
...@@ -9,6 +9,8 @@ import ( ...@@ -9,6 +9,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/aws/aws-sdk-go/private/protocol"
) )
// Parse parses an object i and fills a url.Values object. The isEC2 flag // Parse parses an object i and fills a url.Values object. The isEC2 flag
...@@ -68,14 +70,23 @@ func (q *queryParser) parseStruct(v url.Values, value reflect.Value, prefix stri ...@@ -68,14 +70,23 @@ func (q *queryParser) parseStruct(v url.Values, value reflect.Value, prefix stri
t := value.Type() t := value.Type()
for i := 0; i < value.NumField(); i++ { for i := 0; i < value.NumField(); i++ {
if c := t.Field(i).Name[0:1]; strings.ToLower(c) == c { elemValue := elemOf(value.Field(i))
field := t.Field(i)
if field.PkgPath != "" {
continue // ignore unexported fields continue // ignore unexported fields
} }
if field.Tag.Get("ignore") != "" {
continue
}
elemValue := elemOf(value.Field(i))
field := t.Field(i)
var name string
if protocol.CanSetIdempotencyToken(value.Field(i), field) {
token := protocol.GetIdempotencyToken()
elemValue = reflect.ValueOf(token)
}
var name string
if q.isEC2 { if q.isEC2 {
name = field.Tag.Get("queryName") name = field.Tag.Get("queryName")
} }
......
package query package query
//go:generate go run ../../../models/protocol_tests/generate.go ../../../models/protocol_tests/output/query.json unmarshal_test.go //go:generate go run -tags codegen ../../../models/protocol_tests/generate.go ../../../models/protocol_tests/output/query.json unmarshal_test.go
import ( import (
"encoding/xml" "encoding/xml"
...@@ -10,6 +10,12 @@ import ( ...@@ -10,6 +10,12 @@ import (
"github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil" "github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil"
) )
// UnmarshalHandler is a named request handler for unmarshaling query protocol requests
var UnmarshalHandler = request.NamedHandler{Name: "awssdk.query.Unmarshal", Fn: Unmarshal}
// UnmarshalMetaHandler is a named request handler for unmarshaling query protocol request metadata
var UnmarshalMetaHandler = request.NamedHandler{Name: "awssdk.query.UnmarshalMeta", Fn: UnmarshalMeta}
// Unmarshal unmarshals a response for an AWS Query service. // Unmarshal unmarshals a response for an AWS Query service.
func Unmarshal(r *request.Request) { func Unmarshal(r *request.Request) {
defer r.HTTPResponse.Body.Close() defer r.HTTPResponse.Body.Close()
......
...@@ -2,7 +2,7 @@ package query ...@@ -2,7 +2,7 @@ package query
import ( import (
"encoding/xml" "encoding/xml"
"io" "io/ioutil"
"github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/request"
...@@ -15,19 +15,52 @@ type xmlErrorResponse struct { ...@@ -15,19 +15,52 @@ type xmlErrorResponse struct {
RequestID string `xml:"RequestId"` RequestID string `xml:"RequestId"`
} }
type xmlServiceUnavailableResponse struct {
XMLName xml.Name `xml:"ServiceUnavailableException"`
}
// UnmarshalErrorHandler is a name request handler to unmarshal request errors
var UnmarshalErrorHandler = request.NamedHandler{Name: "awssdk.query.UnmarshalError", Fn: UnmarshalError}
// UnmarshalError unmarshals an error response for an AWS Query service. // UnmarshalError unmarshals an error response for an AWS Query service.
func UnmarshalError(r *request.Request) { func UnmarshalError(r *request.Request) {
defer r.HTTPResponse.Body.Close() defer r.HTTPResponse.Body.Close()
resp := &xmlErrorResponse{} bodyBytes, err := ioutil.ReadAll(r.HTTPResponse.Body)
err := xml.NewDecoder(r.HTTPResponse.Body).Decode(resp) if err != nil {
if err != nil && err != io.EOF { r.Error = awserr.New("SerializationError", "failed to read from query HTTP response body", err)
r.Error = awserr.New("SerializationError", "failed to decode query XML error response", err) return
} else { }
// First check for specific error
resp := xmlErrorResponse{}
decodeErr := xml.Unmarshal(bodyBytes, &resp)
if decodeErr == nil {
reqID := resp.RequestID
if reqID == "" {
reqID = r.RequestID
}
r.Error = awserr.NewRequestFailure( r.Error = awserr.NewRequestFailure(
awserr.New(resp.Code, resp.Message, nil), awserr.New(resp.Code, resp.Message, nil),
r.HTTPResponse.StatusCode, r.HTTPResponse.StatusCode,
resp.RequestID, reqID,
) )
return
} }
// Check for unhandled error
servUnavailResp := xmlServiceUnavailableResponse{}
unavailErr := xml.Unmarshal(bodyBytes, &servUnavailResp)
if unavailErr == nil {
r.Error = awserr.NewRequestFailure(
awserr.New("ServiceUnavailableException", "service is unavailable", nil),
r.HTTPResponse.StatusCode,
r.RequestID,
)
return
}
// Failed to retrieve any error message from the response body
r.Error = awserr.New("SerializationError",
"failed to decode query XML error response", decodeErr)
} }
...@@ -14,6 +14,7 @@ import ( ...@@ -14,6 +14,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/request"
) )
...@@ -39,18 +40,36 @@ func init() { ...@@ -39,18 +40,36 @@ func init() {
} }
} }
// BuildHandler is a named request handler for building rest protocol requests
var BuildHandler = request.NamedHandler{Name: "awssdk.rest.Build", Fn: Build}
// Build builds the REST component of a service request. // Build builds the REST component of a service request.
func Build(r *request.Request) { func Build(r *request.Request) {
if r.ParamsFilled() { if r.ParamsFilled() {
v := reflect.ValueOf(r.Params).Elem() v := reflect.ValueOf(r.Params).Elem()
buildLocationElements(r, v) buildLocationElements(r, v, false)
buildBody(r, v)
}
}
// BuildAsGET builds the REST component of a service request with the ability to hoist
// data from the body.
func BuildAsGET(r *request.Request) {
if r.ParamsFilled() {
v := reflect.ValueOf(r.Params).Elem()
buildLocationElements(r, v, true)
buildBody(r, v) buildBody(r, v)
} }
} }
func buildLocationElements(r *request.Request, v reflect.Value) { func buildLocationElements(r *request.Request, v reflect.Value, buildGETQuery bool) {
query := r.HTTPRequest.URL.Query() query := r.HTTPRequest.URL.Query()
// Setup the raw path to match the base path pattern. This is needed
// so that when the path is mutated a custom escaped version can be
// stored in RawPath that will be used by the Go client.
r.HTTPRequest.URL.RawPath = r.HTTPRequest.URL.Path
for i := 0; i < v.NumField(); i++ { for i := 0; i < v.NumField(); i++ {
m := v.Field(i) m := v.Field(i)
if n := v.Type().Field(i).Name; n[0:1] == strings.ToLower(n[0:1]) { if n := v.Type().Field(i).Name; n[0:1] == strings.ToLower(n[0:1]) {
...@@ -69,6 +88,9 @@ func buildLocationElements(r *request.Request, v reflect.Value) { ...@@ -69,6 +88,9 @@ func buildLocationElements(r *request.Request, v reflect.Value) {
if !m.IsValid() { if !m.IsValid() {
continue continue
} }
if field.Tag.Get("ignore") != "" {
continue
}
var err error var err error
switch field.Tag.Get("location") { switch field.Tag.Get("location") {
...@@ -80,6 +102,10 @@ func buildLocationElements(r *request.Request, v reflect.Value) { ...@@ -80,6 +102,10 @@ func buildLocationElements(r *request.Request, v reflect.Value) {
err = buildURI(r.HTTPRequest.URL, m, name) err = buildURI(r.HTTPRequest.URL, m, name)
case "querystring": case "querystring":
err = buildQueryString(query, m, name) err = buildQueryString(query, m, name)
default:
if buildGETQuery {
err = buildQueryString(query, m, name)
}
} }
r.Error = err r.Error = err
} }
...@@ -89,7 +115,9 @@ func buildLocationElements(r *request.Request, v reflect.Value) { ...@@ -89,7 +115,9 @@ func buildLocationElements(r *request.Request, v reflect.Value) {
} }
r.HTTPRequest.URL.RawQuery = query.Encode() r.HTTPRequest.URL.RawQuery = query.Encode()
updatePath(r.HTTPRequest.URL, r.HTTPRequest.URL.Path) if !aws.BoolValue(r.Config.DisableRestProtocolURICleaning) {
cleanPath(r.HTTPRequest.URL)
}
} }
func buildBody(r *request.Request, v reflect.Value) { func buildBody(r *request.Request, v reflect.Value) {
...@@ -153,10 +181,11 @@ func buildURI(u *url.URL, v reflect.Value, name string) error { ...@@ -153,10 +181,11 @@ func buildURI(u *url.URL, v reflect.Value, name string) error {
return awserr.New("SerializationError", "failed to encode REST request", err) return awserr.New("SerializationError", "failed to encode REST request", err)
} }
uri := u.Path u.Path = strings.Replace(u.Path, "{"+name+"}", value, -1)
uri = strings.Replace(uri, "{"+name+"}", EscapePath(value, true), -1) u.Path = strings.Replace(u.Path, "{"+name+"+}", value, -1)
uri = strings.Replace(uri, "{"+name+"+}", EscapePath(value, false), -1)
u.Path = uri u.RawPath = strings.Replace(u.RawPath, "{"+name+"}", EscapePath(value, true), -1)
u.RawPath = strings.Replace(u.RawPath, "{"+name+"+}", EscapePath(value, false), -1)
return nil return nil
} }
...@@ -190,25 +219,17 @@ func buildQueryString(query url.Values, v reflect.Value, name string) error { ...@@ -190,25 +219,17 @@ func buildQueryString(query url.Values, v reflect.Value, name string) error {
return nil return nil
} }
func updatePath(url *url.URL, urlPath string) { func cleanPath(u *url.URL) {
scheme, query := url.Scheme, url.RawQuery hasSlash := strings.HasSuffix(u.Path, "/")
hasSlash := strings.HasSuffix(urlPath, "/") // clean up path, removing duplicate `/`
u.Path = path.Clean(u.Path)
u.RawPath = path.Clean(u.RawPath)
// clean up path if hasSlash && !strings.HasSuffix(u.Path, "/") {
urlPath = path.Clean(urlPath) u.Path += "/"
if hasSlash && !strings.HasSuffix(urlPath, "/") { u.RawPath += "/"
urlPath += "/"
} }
// get formatted URL minus scheme so we can build this into Opaque
url.Scheme, url.Path, url.RawQuery = "", "", ""
s := url.String()
url.Scheme = scheme
url.RawQuery = query
// build opaque URI
url.Opaque = s + urlPath
} }
// EscapePath escapes part of a URL path in Amazon style // EscapePath escapes part of a URL path in Amazon style
...@@ -219,8 +240,7 @@ func EscapePath(path string, encodeSep bool) string { ...@@ -219,8 +240,7 @@ func EscapePath(path string, encodeSep bool) string {
if noEscape[c] || (c == '/' && !encodeSep) { if noEscape[c] || (c == '/' && !encodeSep) {
buf.WriteByte(c) buf.WriteByte(c)
} else { } else {
buf.WriteByte('%') fmt.Fprintf(&buf, "%%%02X", c)
buf.WriteString(strings.ToUpper(strconv.FormatUint(uint64(c), 16)))
} }
} }
return buf.String() return buf.String()
......
package rest package rest
import ( import (
"bytes"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"reflect" "reflect"
...@@ -10,11 +12,16 @@ import ( ...@@ -10,11 +12,16 @@ import (
"strings" "strings"
"time" "time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/request"
) )
// UnmarshalHandler is a named request handler for unmarshaling rest protocol requests
var UnmarshalHandler = request.NamedHandler{Name: "awssdk.rest.Unmarshal", Fn: Unmarshal}
// UnmarshalMetaHandler is a named request handler for unmarshaling rest protocol request metadata
var UnmarshalMetaHandler = request.NamedHandler{Name: "awssdk.rest.UnmarshalMeta", Fn: UnmarshalMeta}
// Unmarshal unmarshals the REST component of a response in a REST service. // Unmarshal unmarshals the REST component of a response in a REST service.
func Unmarshal(r *request.Request) { func Unmarshal(r *request.Request) {
if r.DataFilled() { if r.DataFilled() {
...@@ -26,6 +33,10 @@ func Unmarshal(r *request.Request) { ...@@ -26,6 +33,10 @@ func Unmarshal(r *request.Request) {
// UnmarshalMeta unmarshals the REST metadata of a response in a REST service // UnmarshalMeta unmarshals the REST metadata of a response in a REST service
func UnmarshalMeta(r *request.Request) { func UnmarshalMeta(r *request.Request) {
r.RequestID = r.HTTPResponse.Header.Get("X-Amzn-Requestid") r.RequestID = r.HTTPResponse.Header.Get("X-Amzn-Requestid")
if r.RequestID == "" {
// Alternative version of request id in the header
r.RequestID = r.HTTPResponse.Header.Get("X-Amz-Request-Id")
}
if r.DataFilled() { if r.DataFilled() {
v := reflect.Indirect(reflect.ValueOf(r.Data)) v := reflect.Indirect(reflect.ValueOf(r.Data))
unmarshalLocationElements(r, v) unmarshalLocationElements(r, v)
...@@ -41,6 +52,7 @@ func unmarshalBody(r *request.Request, v reflect.Value) { ...@@ -41,6 +52,7 @@ func unmarshalBody(r *request.Request, v reflect.Value) {
if payload.IsValid() { if payload.IsValid() {
switch payload.Interface().(type) { switch payload.Interface().(type) {
case []byte: case []byte:
defer r.HTTPResponse.Body.Close()
b, err := ioutil.ReadAll(r.HTTPResponse.Body) b, err := ioutil.ReadAll(r.HTTPResponse.Body)
if err != nil { if err != nil {
r.Error = awserr.New("SerializationError", "failed to decode REST response", err) r.Error = awserr.New("SerializationError", "failed to decode REST response", err)
...@@ -48,6 +60,7 @@ func unmarshalBody(r *request.Request, v reflect.Value) { ...@@ -48,6 +60,7 @@ func unmarshalBody(r *request.Request, v reflect.Value) {
payload.Set(reflect.ValueOf(b)) payload.Set(reflect.ValueOf(b))
} }
case *string: case *string:
defer r.HTTPResponse.Body.Close()
b, err := ioutil.ReadAll(r.HTTPResponse.Body) b, err := ioutil.ReadAll(r.HTTPResponse.Body)
if err != nil { if err != nil {
r.Error = awserr.New("SerializationError", "failed to decode REST response", err) r.Error = awserr.New("SerializationError", "failed to decode REST response", err)
...@@ -57,11 +70,19 @@ func unmarshalBody(r *request.Request, v reflect.Value) { ...@@ -57,11 +70,19 @@ func unmarshalBody(r *request.Request, v reflect.Value) {
} }
default: default:
switch payload.Type().String() { switch payload.Type().String() {
case "io.ReadSeeker": case "io.ReadCloser":
payload.Set(reflect.ValueOf(aws.ReadSeekCloser(r.HTTPResponse.Body)))
case "aws.ReadSeekCloser", "io.ReadCloser":
payload.Set(reflect.ValueOf(r.HTTPResponse.Body)) payload.Set(reflect.ValueOf(r.HTTPResponse.Body))
case "io.ReadSeeker":
b, err := ioutil.ReadAll(r.HTTPResponse.Body)
if err != nil {
r.Error = awserr.New("SerializationError",
"failed to read response body", err)
return
}
payload.Set(reflect.ValueOf(ioutil.NopCloser(bytes.NewReader(b))))
default: default:
io.Copy(ioutil.Discard, r.HTTPResponse.Body)
defer r.HTTPResponse.Body.Close()
r.Error = awserr.New("SerializationError", r.Error = awserr.New("SerializationError",
"failed to decode REST response", "failed to decode REST response",
fmt.Errorf("unknown payload type %s", payload.Type())) fmt.Errorf("unknown payload type %s", payload.Type()))
......
// Package restxml provides RESTful XML serialisation of AWS // Package restxml provides RESTful XML serialization of AWS
// requests and responses. // requests and responses.
package restxml package restxml
//go:generate go run ../../../models/protocol_tests/generate.go ../../../models/protocol_tests/input/rest-xml.json build_test.go //go:generate go run -tags codegen ../../../models/protocol_tests/generate.go ../../../models/protocol_tests/input/rest-xml.json build_test.go
//go:generate go run ../../../models/protocol_tests/generate.go ../../../models/protocol_tests/output/rest-xml.json unmarshal_test.go //go:generate go run -tags codegen ../../../models/protocol_tests/generate.go ../../../models/protocol_tests/output/rest-xml.json unmarshal_test.go
import ( import (
"bytes" "bytes"
...@@ -16,6 +16,18 @@ import ( ...@@ -16,6 +16,18 @@ import (
"github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil" "github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil"
) )
// BuildHandler is a named request handler for building restxml protocol requests
var BuildHandler = request.NamedHandler{Name: "awssdk.restxml.Build", Fn: Build}
// UnmarshalHandler is a named request handler for unmarshaling restxml protocol requests
var UnmarshalHandler = request.NamedHandler{Name: "awssdk.restxml.Unmarshal", Fn: Unmarshal}
// UnmarshalMetaHandler is a named request handler for unmarshaling restxml protocol request metadata
var UnmarshalMetaHandler = request.NamedHandler{Name: "awssdk.restxml.UnmarshalMeta", Fn: UnmarshalMeta}
// UnmarshalErrorHandler is a named request handler for unmarshaling restxml protocol request errors
var UnmarshalErrorHandler = request.NamedHandler{Name: "awssdk.restxml.UnmarshalError", Fn: UnmarshalError}
// Build builds a request payload for the REST XML protocol. // Build builds a request payload for the REST XML protocol.
func Build(r *request.Request) { func Build(r *request.Request) {
rest.Build(r) rest.Build(r)
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment