summaryrefslogtreecommitdiff
path: root/Godeps/_workspace/src/github.com/mitchellh/goamz/aws
diff options
context:
space:
mode:
Diffstat (limited to 'Godeps/_workspace/src/github.com/mitchellh/goamz/aws')
-rw-r--r--Godeps/_workspace/src/github.com/mitchellh/goamz/aws/attempt.go74
-rw-r--r--Godeps/_workspace/src/github.com/mitchellh/goamz/aws/attempt_test.go57
-rw-r--r--Godeps/_workspace/src/github.com/mitchellh/goamz/aws/aws.go445
-rw-r--r--Godeps/_workspace/src/github.com/mitchellh/goamz/aws/aws_test.go203
-rw-r--r--Godeps/_workspace/src/github.com/mitchellh/goamz/aws/client.go125
-rw-r--r--Godeps/_workspace/src/github.com/mitchellh/goamz/aws/client_test.go121
6 files changed, 1025 insertions, 0 deletions
diff --git a/Godeps/_workspace/src/github.com/mitchellh/goamz/aws/attempt.go b/Godeps/_workspace/src/github.com/mitchellh/goamz/aws/attempt.go
new file mode 100644
index 0000000..c0654f5
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mitchellh/goamz/aws/attempt.go
@@ -0,0 +1,74 @@
+package aws
+
+import (
+ "time"
+)
+
+// AttemptStrategy represents a strategy for waiting for an action
+// to complete successfully. This is an internal type used by the
+// implementation of other goamz packages.
+type AttemptStrategy struct {
+ Total time.Duration // total duration of attempt.
+ Delay time.Duration // interval between each try in the burst.
+ Min int // minimum number of retries; overrides Total
+}
+
+type Attempt struct {
+ strategy AttemptStrategy
+ last time.Time
+ end time.Time
+ force bool
+ count int
+}
+
+// Start begins a new sequence of attempts for the given strategy.
+func (s AttemptStrategy) Start() *Attempt {
+ now := time.Now()
+ return &Attempt{
+ strategy: s,
+ last: now,
+ end: now.Add(s.Total),
+ force: true,
+ }
+}
+
+// Next waits until it is time to perform the next attempt or returns
+// false if it is time to stop trying.
+func (a *Attempt) Next() bool {
+ now := time.Now()
+ sleep := a.nextSleep(now)
+ if !a.force && !now.Add(sleep).Before(a.end) && a.strategy.Min <= a.count {
+ return false
+ }
+ a.force = false
+ if sleep > 0 && a.count > 0 {
+ time.Sleep(sleep)
+ now = time.Now()
+ }
+ a.count++
+ a.last = now
+ return true
+}
+
+func (a *Attempt) nextSleep(now time.Time) time.Duration {
+ sleep := a.strategy.Delay - now.Sub(a.last)
+ if sleep < 0 {
+ return 0
+ }
+ return sleep
+}
+
+// HasNext returns whether another attempt will be made if the current
+// one fails. If it returns true, the following call to Next is
+// guaranteed to return true.
+func (a *Attempt) HasNext() bool {
+ if a.force || a.strategy.Min > a.count {
+ return true
+ }
+ now := time.Now()
+ if now.Add(a.nextSleep(now)).Before(a.end) {
+ a.force = true
+ return true
+ }
+ return false
+}
diff --git a/Godeps/_workspace/src/github.com/mitchellh/goamz/aws/attempt_test.go b/Godeps/_workspace/src/github.com/mitchellh/goamz/aws/attempt_test.go
new file mode 100644
index 0000000..1fda5bf
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mitchellh/goamz/aws/attempt_test.go
@@ -0,0 +1,57 @@
+package aws_test
+
+import (
+ "github.com/mitchellh/goamz/aws"
+ . "github.com/motain/gocheck"
+ "time"
+)
+
+func (S) TestAttemptTiming(c *C) {
+ testAttempt := aws.AttemptStrategy{
+ Total: 0.25e9,
+ Delay: 0.1e9,
+ }
+ want := []time.Duration{0, 0.1e9, 0.2e9, 0.2e9}
+ got := make([]time.Duration, 0, len(want)) // avoid allocation when testing timing
+ t0 := time.Now()
+ for a := testAttempt.Start(); a.Next(); {
+ got = append(got, time.Now().Sub(t0))
+ }
+ got = append(got, time.Now().Sub(t0))
+ c.Assert(got, HasLen, len(want))
+ const margin = 0.01e9
+ for i, got := range want {
+ lo := want[i] - margin
+ hi := want[i] + margin
+ if got < lo || got > hi {
+ c.Errorf("attempt %d want %g got %g", i, want[i].Seconds(), got.Seconds())
+ }
+ }
+}
+
+func (S) TestAttemptNextHasNext(c *C) {
+ a := aws.AttemptStrategy{}.Start()
+ c.Assert(a.Next(), Equals, true)
+ c.Assert(a.Next(), Equals, false)
+
+ a = aws.AttemptStrategy{}.Start()
+ c.Assert(a.Next(), Equals, true)
+ c.Assert(a.HasNext(), Equals, false)
+ c.Assert(a.Next(), Equals, false)
+
+ a = aws.AttemptStrategy{Total: 2e8}.Start()
+ c.Assert(a.Next(), Equals, true)
+ c.Assert(a.HasNext(), Equals, true)
+ time.Sleep(2e8)
+ c.Assert(a.HasNext(), Equals, true)
+ c.Assert(a.Next(), Equals, true)
+ c.Assert(a.Next(), Equals, false)
+
+ a = aws.AttemptStrategy{Total: 1e8, Min: 2}.Start()
+ time.Sleep(1e8)
+ c.Assert(a.Next(), Equals, true)
+ c.Assert(a.HasNext(), Equals, true)
+ c.Assert(a.Next(), Equals, true)
+ c.Assert(a.HasNext(), Equals, false)
+ c.Assert(a.Next(), Equals, false)
+}
diff --git a/Godeps/_workspace/src/github.com/mitchellh/goamz/aws/aws.go b/Godeps/_workspace/src/github.com/mitchellh/goamz/aws/aws.go
new file mode 100644
index 0000000..cfc42c0
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mitchellh/goamz/aws/aws.go
@@ -0,0 +1,445 @@
+//
+// goamz - Go packages to interact with the Amazon Web Services.
+//
+// https://wiki.ubuntu.com/goamz
+//
+// Copyright (c) 2011 Canonical Ltd.
+//
+// Written by Gustavo Niemeyer <gustavo.niemeyer@canonical.com>
+//
+package aws
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "os"
+
+ "github.com/vaughan0/go-ini"
+)
+
+// Region defines the URLs where AWS services may be accessed.
+//
+// See http://goo.gl/d8BP1 for more details.
+type Region struct {
+ Name string // the canonical name of this region.
+ EC2Endpoint string
+ S3Endpoint string
+ S3BucketEndpoint string // Not needed by AWS S3. Use ${bucket} for bucket name.
+ S3LocationConstraint bool // true if this region requires a LocationConstraint declaration.
+ S3LowercaseBucket bool // true if the region requires bucket names to be lower case.
+ SDBEndpoint string
+ SNSEndpoint string
+ SQSEndpoint string
+ IAMEndpoint string
+ ELBEndpoint string
+ AutoScalingEndpoint string
+ RdsEndpoint string
+ Route53Endpoint string
+}
+
+var USGovWest = Region{
+ "us-gov-west-1",
+ "https://ec2.us-gov-west-1.amazonaws.com",
+ "https://s3-fips-us-gov-west-1.amazonaws.com",
+ "",
+ true,
+ true,
+ "",
+ "https://sns.us-gov-west-1.amazonaws.com",
+ "https://sqs.us-gov-west-1.amazonaws.com",
+ "https://iam.us-gov.amazonaws.com",
+ "https://elasticloadbalancing.us-gov-west-1.amazonaws.com",
+ "https://autoscaling.us-gov-west-1.amazonaws.com",
+ "https://rds.us-gov-west-1.amazonaws.com",
+ "https://route53.amazonaws.com",
+}
+
+var USEast = Region{
+ "us-east-1",
+ "https://ec2.us-east-1.amazonaws.com",
+ "https://s3.amazonaws.com",
+ "",
+ false,
+ false,
+ "https://sdb.amazonaws.com",
+ "https://sns.us-east-1.amazonaws.com",
+ "https://sqs.us-east-1.amazonaws.com",
+ "https://iam.amazonaws.com",
+ "https://elasticloadbalancing.us-east-1.amazonaws.com",
+ "https://autoscaling.us-east-1.amazonaws.com",
+ "https://rds.us-east-1.amazonaws.com",
+ "https://route53.amazonaws.com",
+}
+
+var USWest = Region{
+ "us-west-1",
+ "https://ec2.us-west-1.amazonaws.com",
+ "https://s3-us-west-1.amazonaws.com",
+ "",
+ true,
+ true,
+ "https://sdb.us-west-1.amazonaws.com",
+ "https://sns.us-west-1.amazonaws.com",
+ "https://sqs.us-west-1.amazonaws.com",
+ "https://iam.amazonaws.com",
+ "https://elasticloadbalancing.us-west-1.amazonaws.com",
+ "https://autoscaling.us-west-1.amazonaws.com",
+ "https://rds.us-west-1.amazonaws.com",
+ "https://route53.amazonaws.com",
+}
+
+var USWest2 = Region{
+ "us-west-2",
+ "https://ec2.us-west-2.amazonaws.com",
+ "https://s3-us-west-2.amazonaws.com",
+ "",
+ true,
+ true,
+ "https://sdb.us-west-2.amazonaws.com",
+ "https://sns.us-west-2.amazonaws.com",
+ "https://sqs.us-west-2.amazonaws.com",
+ "https://iam.amazonaws.com",
+ "https://elasticloadbalancing.us-west-2.amazonaws.com",
+ "https://autoscaling.us-west-2.amazonaws.com",
+ "https://rds.us-west-2.amazonaws.com",
+ "https://route53.amazonaws.com",
+}
+
+var EUWest = Region{
+ "eu-west-1",
+ "https://ec2.eu-west-1.amazonaws.com",
+ "https://s3-eu-west-1.amazonaws.com",
+ "",
+ true,
+ true,
+ "https://sdb.eu-west-1.amazonaws.com",
+ "https://sns.eu-west-1.amazonaws.com",
+ "https://sqs.eu-west-1.amazonaws.com",
+ "https://iam.amazonaws.com",
+ "https://elasticloadbalancing.eu-west-1.amazonaws.com",
+ "https://autoscaling.eu-west-1.amazonaws.com",
+ "https://rds.eu-west-1.amazonaws.com",
+ "https://route53.amazonaws.com",
+}
+
+var EUCentral = Region{
+ "eu-central-1",
+ "https://ec2.eu-central-1.amazonaws.com",
+ "https://s3-eu-central-1.amazonaws.com",
+ "",
+ true,
+ true,
+ "",
+ "https://sns.eu-central-1.amazonaws.com",
+ "https://sqs.eu-central-1.amazonaws.com",
+ "https://iam.amazonaws.com",
+ "https://elasticloadbalancing.eu-central-1.amazonaws.com",
+ "https://autoscaling.eu-central-1.amazonaws.com",
+ "https://rds.eu-central-1.amazonaws.com",
+ "https://route53.amazonaws.com",
+}
+
+var APSoutheast = Region{
+ "ap-southeast-1",
+ "https://ec2.ap-southeast-1.amazonaws.com",
+ "https://s3-ap-southeast-1.amazonaws.com",
+ "",
+ true,
+ true,
+ "https://sdb.ap-southeast-1.amazonaws.com",
+ "https://sns.ap-southeast-1.amazonaws.com",
+ "https://sqs.ap-southeast-1.amazonaws.com",
+ "https://iam.amazonaws.com",
+ "https://elasticloadbalancing.ap-southeast-1.amazonaws.com",
+ "https://autoscaling.ap-southeast-1.amazonaws.com",
+ "https://rds.ap-southeast-1.amazonaws.com",
+ "https://route53.amazonaws.com",
+}
+
+var APSoutheast2 = Region{
+ "ap-southeast-2",
+ "https://ec2.ap-southeast-2.amazonaws.com",
+ "https://s3-ap-southeast-2.amazonaws.com",
+ "",
+ true,
+ true,
+ "https://sdb.ap-southeast-2.amazonaws.com",
+ "https://sns.ap-southeast-2.amazonaws.com",
+ "https://sqs.ap-southeast-2.amazonaws.com",
+ "https://iam.amazonaws.com",
+ "https://elasticloadbalancing.ap-southeast-2.amazonaws.com",
+ "https://autoscaling.ap-southeast-2.amazonaws.com",
+ "https://rds.ap-southeast-2.amazonaws.com",
+ "https://route53.amazonaws.com",
+}
+
+var APNortheast = Region{
+ "ap-northeast-1",
+ "https://ec2.ap-northeast-1.amazonaws.com",
+ "https://s3-ap-northeast-1.amazonaws.com",
+ "",
+ true,
+ true,
+ "https://sdb.ap-northeast-1.amazonaws.com",
+ "https://sns.ap-northeast-1.amazonaws.com",
+ "https://sqs.ap-northeast-1.amazonaws.com",
+ "https://iam.amazonaws.com",
+ "https://elasticloadbalancing.ap-northeast-1.amazonaws.com",
+ "https://autoscaling.ap-northeast-1.amazonaws.com",
+ "https://rds.ap-northeast-1.amazonaws.com",
+ "https://route53.amazonaws.com",
+}
+
+var SAEast = Region{
+ "sa-east-1",
+ "https://ec2.sa-east-1.amazonaws.com",
+ "https://s3-sa-east-1.amazonaws.com",
+ "",
+ true,
+ true,
+ "https://sdb.sa-east-1.amazonaws.com",
+ "https://sns.sa-east-1.amazonaws.com",
+ "https://sqs.sa-east-1.amazonaws.com",
+ "https://iam.amazonaws.com",
+ "https://elasticloadbalancing.sa-east-1.amazonaws.com",
+ "https://autoscaling.sa-east-1.amazonaws.com",
+ "https://rds.sa-east-1.amazonaws.com",
+ "https://route53.amazonaws.com",
+}
+
+var CNNorth = Region{
+ "cn-north-1",
+ "https://ec2.cn-north-1.amazonaws.com.cn",
+ "https://s3.cn-north-1.amazonaws.com.cn",
+ "",
+ true,
+ true,
+ "",
+ "https://sns.cn-north-1.amazonaws.com.cn",
+ "https://sqs.cn-north-1.amazonaws.com.cn",
+ "https://iam.cn-north-1.amazonaws.com.cn",
+ "https://elasticloadbalancing.cn-north-1.amazonaws.com.cn",
+ "https://autoscaling.cn-north-1.amazonaws.com.cn",
+ "https://rds.cn-north-1.amazonaws.com.cn",
+ "https://route53.amazonaws.com",
+}
+
+var Regions = map[string]Region{
+ APNortheast.Name: APNortheast,
+ APSoutheast.Name: APSoutheast,
+ APSoutheast2.Name: APSoutheast2,
+ EUWest.Name: EUWest,
+ EUCentral.Name: EUCentral,
+ USEast.Name: USEast,
+ USWest.Name: USWest,
+ USWest2.Name: USWest2,
+ SAEast.Name: SAEast,
+ USGovWest.Name: USGovWest,
+ CNNorth.Name: CNNorth,
+}
+
+type Auth struct {
+ AccessKey, SecretKey, Token string
+}
+
+var unreserved = make([]bool, 128)
+var hex = "0123456789ABCDEF"
+
+func init() {
+ // RFC3986
+ u := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890-_.~"
+ for _, c := range u {
+ unreserved[c] = true
+ }
+}
+
+type credentials struct {
+ Code string
+ LastUpdated string
+ Type string
+ AccessKeyId string
+ SecretAccessKey string
+ Token string
+ Expiration string
+}
+
+// GetMetaData retrieves instance metadata about the current machine.
+//
+// See http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AESDG-chapter-instancedata.html for more details.
+func GetMetaData(path string) (contents []byte, err error) {
+ url := "http://169.254.169.254/latest/meta-data/" + path
+
+ resp, err := RetryingClient.Get(url)
+ if err != nil {
+ return
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != 200 {
+ err = fmt.Errorf("Code %d returned for url %s", resp.StatusCode, url)
+ return
+ }
+
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return
+ }
+ return []byte(body), err
+}
+
+func getInstanceCredentials() (cred credentials, err error) {
+ credentialPath := "iam/security-credentials/"
+
+ // Get the instance role
+ role, err := GetMetaData(credentialPath)
+ if err != nil {
+ return
+ }
+
+ // Get the instance role credentials
+ credentialJSON, err := GetMetaData(credentialPath + string(role))
+ if err != nil {
+ return
+ }
+
+ err = json.Unmarshal([]byte(credentialJSON), &cred)
+ return
+}
+
+// GetAuth creates an Auth based on either passed in credentials,
+// environment information or instance based role credentials.
+func GetAuth(accessKey string, secretKey string) (auth Auth, err error) {
+ // First try passed in credentials
+ if accessKey != "" && secretKey != "" {
+ return Auth{accessKey, secretKey, ""}, nil
+ }
+
+ // Next try to get auth from the environment
+ auth, err = SharedAuth()
+ if err == nil {
+ // Found auth, return
+ return
+ }
+
+ // Next try to get auth from the environment
+ auth, err = EnvAuth()
+ if err == nil {
+ // Found auth, return
+ return
+ }
+
+ // Next try getting auth from the instance role
+ cred, err := getInstanceCredentials()
+ if err == nil {
+ // Found auth, return
+ auth.AccessKey = cred.AccessKeyId
+ auth.SecretKey = cred.SecretAccessKey
+ auth.Token = cred.Token
+ return
+ }
+ err = errors.New("No valid AWS authentication found")
+ return
+}
+
+// SharedAuth creates an Auth based on shared credentials stored in
+// $HOME/.aws/credentials. The AWS_PROFILE environment variables is used to
+// select the profile.
+func SharedAuth() (auth Auth, err error) {
+ var profileName = os.Getenv("AWS_PROFILE")
+
+ if profileName == "" {
+ profileName = "default"
+ }
+
+ var credentialsFile = os.Getenv("AWS_CREDENTIAL_FILE")
+ if credentialsFile == "" {
+ var homeDir = os.Getenv("HOME")
+ if homeDir == "" {
+ err = errors.New("Could not get HOME")
+ return
+ }
+ credentialsFile = homeDir + "/.aws/credentials"
+ }
+
+ file, err := ini.LoadFile(credentialsFile)
+ if err != nil {
+ err = errors.New("Couldn't parse AWS credentials file")
+ return
+ }
+
+ var profile = file[profileName]
+ if profile == nil {
+ err = errors.New("Couldn't find profile in AWS credentials file")
+ return
+ }
+
+ auth.AccessKey = profile["aws_access_key_id"]
+ auth.SecretKey = profile["aws_secret_access_key"]
+
+ if auth.AccessKey == "" {
+ err = errors.New("AWS_ACCESS_KEY_ID not found in environment in credentials file")
+ }
+ if auth.SecretKey == "" {
+ err = errors.New("AWS_SECRET_ACCESS_KEY not found in credentials file")
+ }
+ return
+}
+
+// EnvAuth creates an Auth based on environment information.
+// The AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment
+// For accounts that require a security token, it is read from AWS_SECURITY_TOKEN
+// variables are used.
+func EnvAuth() (auth Auth, err error) {
+ auth.AccessKey = os.Getenv("AWS_ACCESS_KEY_ID")
+ if auth.AccessKey == "" {
+ auth.AccessKey = os.Getenv("AWS_ACCESS_KEY")
+ }
+
+ auth.SecretKey = os.Getenv("AWS_SECRET_ACCESS_KEY")
+ if auth.SecretKey == "" {
+ auth.SecretKey = os.Getenv("AWS_SECRET_KEY")
+ }
+
+ auth.Token = os.Getenv("AWS_SECURITY_TOKEN")
+
+ if auth.AccessKey == "" {
+ err = errors.New("AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY not found in environment")
+ }
+ if auth.SecretKey == "" {
+ err = errors.New("AWS_SECRET_ACCESS_KEY or AWS_SECRET_KEY not found in environment")
+ }
+ return
+}
+
+// Encode takes a string and URI-encodes it in a way suitable
+// to be used in AWS signatures.
+func Encode(s string) string {
+ encode := false
+ for i := 0; i != len(s); i++ {
+ c := s[i]
+ if c > 127 || !unreserved[c] {
+ encode = true
+ break
+ }
+ }
+ if !encode {
+ return s
+ }
+ e := make([]byte, len(s)*3)
+ ei := 0
+ for i := 0; i != len(s); i++ {
+ c := s[i]
+ if c > 127 || !unreserved[c] {
+ e[ei] = '%'
+ e[ei+1] = hex[c>>4]
+ e[ei+2] = hex[c&0xF]
+ ei += 3
+ } else {
+ e[ei] = c
+ ei += 1
+ }
+ }
+ return string(e[:ei])
+}
diff --git a/Godeps/_workspace/src/github.com/mitchellh/goamz/aws/aws_test.go b/Godeps/_workspace/src/github.com/mitchellh/goamz/aws/aws_test.go
new file mode 100644
index 0000000..78cbbaf
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mitchellh/goamz/aws/aws_test.go
@@ -0,0 +1,203 @@
+package aws_test
+
+import (
+ "github.com/mitchellh/goamz/aws"
+ . "github.com/motain/gocheck"
+ "io/ioutil"
+ "os"
+ "strings"
+ "testing"
+)
+
+func Test(t *testing.T) {
+ TestingT(t)
+}
+
+var _ = Suite(&S{})
+
+type S struct {
+ environ []string
+}
+
+func (s *S) SetUpSuite(c *C) {
+ s.environ = os.Environ()
+}
+
+func (s *S) TearDownTest(c *C) {
+ os.Clearenv()
+ for _, kv := range s.environ {
+ l := strings.SplitN(kv, "=", 2)
+ os.Setenv(l[0], l[1])
+ }
+}
+
+func (s *S) TestSharedAuthNoHome(c *C) {
+ os.Clearenv()
+ os.Setenv("AWS_PROFILE", "foo")
+ _, err := aws.SharedAuth()
+ c.Assert(err, ErrorMatches, "Could not get HOME")
+}
+
+func (s *S) TestSharedAuthNoCredentialsFile(c *C) {
+ os.Clearenv()
+ os.Setenv("AWS_PROFILE", "foo")
+ os.Setenv("HOME", "/tmp")
+ _, err := aws.SharedAuth()
+ c.Assert(err, ErrorMatches, "Couldn't parse AWS credentials file")
+}
+
+func (s *S) TestSharedAuthNoProfileInFile(c *C) {
+ os.Clearenv()
+ os.Setenv("AWS_PROFILE", "foo")
+
+ d, err := ioutil.TempDir("", "")
+ if err != nil {
+ panic(err)
+ }
+ defer os.RemoveAll(d)
+
+ err = os.Mkdir(d+"/.aws", 0755)
+ if err != nil {
+ panic(err)
+ }
+
+ ioutil.WriteFile(d+"/.aws/credentials", []byte("[bar]\n"), 0644)
+ os.Setenv("HOME", d)
+
+ _, err = aws.SharedAuth()
+ c.Assert(err, ErrorMatches, "Couldn't find profile in AWS credentials file")
+}
+
+func (s *S) TestSharedAuthNoKeysInProfile(c *C) {
+ os.Clearenv()
+ os.Setenv("AWS_PROFILE", "bar")
+
+ d, err := ioutil.TempDir("", "")
+ if err != nil {
+ panic(err)
+ }
+ defer os.RemoveAll(d)
+
+ err = os.Mkdir(d+"/.aws", 0755)
+ if err != nil {
+ panic(err)
+ }
+
+ ioutil.WriteFile(d+"/.aws/credentials", []byte("[bar]\nawsaccesskeyid = AK.."), 0644)
+ os.Setenv("HOME", d)
+
+ _, err = aws.SharedAuth()
+ c.Assert(err, ErrorMatches, "AWS_SECRET_ACCESS_KEY not found in credentials file")
+}
+
+func (s *S) TestSharedAuthDefaultCredentials(c *C) {
+ os.Clearenv()
+
+ d, err := ioutil.TempDir("", "")
+ if err != nil {
+ panic(err)
+ }
+ defer os.RemoveAll(d)
+
+ err = os.Mkdir(d+"/.aws", 0755)
+ if err != nil {
+ panic(err)
+ }
+
+ ioutil.WriteFile(d+"/.aws/credentials", []byte("[default]\naws_access_key_id = access\naws_secret_access_key = secret\n"), 0644)
+ os.Setenv("HOME", d)
+
+ auth, err := aws.SharedAuth()
+ c.Assert(err, IsNil)
+ c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
+}
+
+func (s *S) TestSharedAuth(c *C) {
+ os.Clearenv()
+ os.Setenv("AWS_PROFILE", "bar")
+
+ d, err := ioutil.TempDir("", "")
+ if err != nil {
+ panic(err)
+ }
+ defer os.RemoveAll(d)
+
+ err = os.Mkdir(d+"/.aws", 0755)
+ if err != nil {
+ panic(err)
+ }
+
+ ioutil.WriteFile(d+"/.aws/credentials", []byte("[bar]\naws_access_key_id = access\naws_secret_access_key = secret\n"), 0644)
+ os.Setenv("HOME", d)
+
+ auth, err := aws.SharedAuth()
+ c.Assert(err, IsNil)
+ c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
+}
+
+func (s *S) TestEnvAuthNoSecret(c *C) {
+ os.Clearenv()
+ _, err := aws.EnvAuth()
+ c.Assert(err, ErrorMatches, "AWS_SECRET_ACCESS_KEY or AWS_SECRET_KEY not found in environment")
+}
+
+func (s *S) TestEnvAuthNoAccess(c *C) {
+ os.Clearenv()
+ os.Setenv("AWS_SECRET_ACCESS_KEY", "foo")
+ _, err := aws.EnvAuth()
+ c.Assert(err, ErrorMatches, "AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY not found in environment")
+}
+
+func (s *S) TestEnvAuth(c *C) {
+ os.Clearenv()
+ os.Setenv("AWS_SECRET_ACCESS_KEY", "secret")
+ os.Setenv("AWS_ACCESS_KEY_ID", "access")
+ auth, err := aws.EnvAuth()
+ c.Assert(err, IsNil)
+ c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
+}
+
+func (s *S) TestEnvAuthWithToken(c *C) {
+ os.Clearenv()
+ os.Setenv("AWS_SECRET_ACCESS_KEY", "secret")
+ os.Setenv("AWS_ACCESS_KEY_ID", "access")
+ os.Setenv("AWS_SECURITY_TOKEN", "token")
+ auth, err := aws.EnvAuth()
+ c.Assert(err, IsNil)
+ c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access", Token: "token"})
+}
+
+func (s *S) TestEnvAuthAlt(c *C) {
+ os.Clearenv()
+ os.Setenv("AWS_SECRET_KEY", "secret")
+ os.Setenv("AWS_ACCESS_KEY", "access")
+ auth, err := aws.EnvAuth()
+ c.Assert(err, IsNil)
+ c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
+}
+
+func (s *S) TestGetAuthStatic(c *C) {
+ auth, err := aws.GetAuth("access", "secret")
+ c.Assert(err, IsNil)
+ c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
+}
+
+func (s *S) TestGetAuthEnv(c *C) {
+ os.Clearenv()
+ os.Setenv("AWS_SECRET_ACCESS_KEY", "secret")
+ os.Setenv("AWS_ACCESS_KEY_ID", "access")
+ auth, err := aws.GetAuth("", "")
+ c.Assert(err, IsNil)
+ c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
+}
+
+func (s *S) TestEncode(c *C) {
+ c.Assert(aws.Encode("foo"), Equals, "foo")
+ c.Assert(aws.Encode("/"), Equals, "%2F")
+}
+
+func (s *S) TestRegionsAreNamed(c *C) {
+ for n, r := range aws.Regions {
+ c.Assert(n, Equals, r.Name)
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/mitchellh/goamz/aws/client.go b/Godeps/_workspace/src/github.com/mitchellh/goamz/aws/client.go
new file mode 100644
index 0000000..ee53238
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mitchellh/goamz/aws/client.go
@@ -0,0 +1,125 @@
+package aws
+
+import (
+ "math"
+ "net"
+ "net/http"
+ "time"
+)
+
+type RetryableFunc func(*http.Request, *http.Response, error) bool
+type WaitFunc func(try int)
+type DeadlineFunc func() time.Time
+
+type ResilientTransport struct {
+ // Timeout is the maximum amount of time a dial will wait for
+ // a connect to complete.
+ //
+ // The default is no timeout.
+ //
+ // With or without a timeout, the operating system may impose
+ // its own earlier timeout. For instance, TCP timeouts are
+ // often around 3 minutes.
+ DialTimeout time.Duration
+
+ // MaxTries, if non-zero, specifies the number of times we will retry on
+ // failure. Retries are only attempted for temporary network errors or known
+ // safe failures.
+ MaxTries int
+ Deadline DeadlineFunc
+ ShouldRetry RetryableFunc
+ Wait WaitFunc
+ transport *http.Transport
+}
+
+// Convenience method for creating an http client
+func NewClient(rt *ResilientTransport) *http.Client {
+ rt.transport = &http.Transport{
+ Dial: func(netw, addr string) (net.Conn, error) {
+ c, err := net.DialTimeout(netw, addr, rt.DialTimeout)
+ if err != nil {
+ return nil, err
+ }
+ c.SetDeadline(rt.Deadline())
+ return c, nil
+ },
+ DisableKeepAlives: true,
+ Proxy: http.ProxyFromEnvironment,
+ }
+ // TODO: Would be nice is ResilientTransport allowed clients to initialize
+ // with http.Transport attributes.
+ return &http.Client{
+ Transport: rt,
+ }
+}
+
+var retryingTransport = &ResilientTransport{
+ Deadline: func() time.Time {
+ return time.Now().Add(5 * time.Second)
+ },
+ DialTimeout: 10 * time.Second,
+ MaxTries: 3,
+ ShouldRetry: awsRetry,
+ Wait: ExpBackoff,
+}
+
+// Exported default client
+var RetryingClient = NewClient(retryingTransport)
+
+func (t *ResilientTransport) RoundTrip(req *http.Request) (*http.Response, error) {
+ return t.tries(req)
+}
+
+// Retry a request a maximum of t.MaxTries times.
+// We'll only retry if the proper criteria are met.
+// If a wait function is specified, wait that amount of time
+// In between requests.
+func (t *ResilientTransport) tries(req *http.Request) (res *http.Response, err error) {
+ for try := 0; try < t.MaxTries; try += 1 {
+ res, err = t.transport.RoundTrip(req)
+
+ if !t.ShouldRetry(req, res, err) {
+ break
+ }
+ if res != nil {
+ res.Body.Close()
+ }
+ if t.Wait != nil {
+ t.Wait(try)
+ }
+ }
+
+ return
+}
+
+func ExpBackoff(try int) {
+ time.Sleep(100 * time.Millisecond *
+ time.Duration(math.Exp2(float64(try))))
+}
+
+func LinearBackoff(try int) {
+ time.Sleep(time.Duration(try*100) * time.Millisecond)
+}
+
+// Decide if we should retry a request.
+// In general, the criteria for retrying a request is described here
+// http://docs.aws.amazon.com/general/latest/gr/api-retries.html
+func awsRetry(req *http.Request, res *http.Response, err error) bool {
+ retry := false
+
+ // Retry if there's a temporary network error.
+ if neterr, ok := err.(net.Error); ok {
+ if neterr.Temporary() {
+ retry = true
+ }
+ }
+
+ // Retry if we get a 5xx series error.
+ if res != nil {
+ if res.StatusCode >= 500 && res.StatusCode < 600 {
+ retry = true
+ }
+ }
+
+ return retry
+}
diff --git a/Godeps/_workspace/src/github.com/mitchellh/goamz/aws/client_test.go b/Godeps/_workspace/src/github.com/mitchellh/goamz/aws/client_test.go
new file mode 100644
index 0000000..2f6b39c
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/mitchellh/goamz/aws/client_test.go
@@ -0,0 +1,121 @@
+package aws_test
+
+import (
+ "fmt"
+ "github.com/mitchellh/goamz/aws"
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+ "time"
+)
+
+// Retrieve the response from handler using aws.RetryingClient
+func serveAndGet(handler http.HandlerFunc) (body string, err error) {
+ ts := httptest.NewServer(handler)
+ defer ts.Close()
+ resp, err := aws.RetryingClient.Get(ts.URL)
+ if err != nil {
+ return
+ }
+ if resp.StatusCode != 200 {
+ return "", fmt.Errorf("Bad status code: %d", resp.StatusCode)
+ }
+ greeting, err := ioutil.ReadAll(resp.Body)
+ resp.Body.Close()
+ if err != nil {
+ return
+ }
+ return strings.TrimSpace(string(greeting)), nil
+}
+
+func TestClient_expected(t *testing.T) {
+ body := "foo bar"
+
+ resp, err := serveAndGet(func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintln(w, body)
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ if resp != body {
+ t.Fatal("Body not as expected.")
+ }
+}
+
+func TestClient_delay(t *testing.T) {
+ body := "baz"
+ wait := 4
+ resp, err := serveAndGet(func(w http.ResponseWriter, r *http.Request) {
+ if wait < 0 {
+ // If we dipped to zero delay and still failed.
+ t.Fatal("Never succeeded.")
+ }
+ wait -= 1
+ time.Sleep(time.Second * time.Duration(wait))
+ fmt.Fprintln(w, body)
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ if resp != body {
+ t.Fatal("Body not as expected.", resp)
+ }
+}
+
+func TestClient_no4xxRetry(t *testing.T) {
+ tries := 0
+
+ // Fail once before succeeding.
+ _, err := serveAndGet(func(w http.ResponseWriter, r *http.Request) {
+ tries += 1
+ http.Error(w, "error", 404)
+ })
+
+ if err == nil {
+ t.Fatal("should have error")
+ }
+
+ if tries != 1 {
+ t.Fatalf("should only try once: %d", tries)
+ }
+}
+
+func TestClient_retries(t *testing.T) {
+ body := "biz"
+ failed := false
+ // Fail once before succeeding.
+ resp, err := serveAndGet(func(w http.ResponseWriter, r *http.Request) {
+ if !failed {
+ http.Error(w, "error", 500)
+ failed = true
+ } else {
+ fmt.Fprintln(w, body)
+ }
+ })
+ if failed != true {
+ t.Error("We didn't retry!")
+ }
+ if err != nil {
+ t.Fatal(err)
+ }
+ if resp != body {
+ t.Fatal("Body not as expected.")
+ }
+}
+
+func TestClient_fails(t *testing.T) {
+ tries := 0
+ // Fail 3 times and return the last error.
+ _, err := serveAndGet(func(w http.ResponseWriter, r *http.Request) {
+ tries += 1
+ http.Error(w, "error", 500)
+ })
+ if err == nil {
+ t.Fatal(err)
+ }
+ if tries != 3 {
+ t.Fatal("Didn't retry enough")
+ }
+}