├── .gitignore ├── .lbox ├── .travis.yml ├── LICENSE ├── README.md ├── TODO.md ├── autoscaling ├── astest │ ├── http.go │ └── responses.go ├── autoscaling.go ├── autoscaling_test.go └── sign.go ├── aws ├── attempt.go ├── attempt_test.go ├── aws.go ├── aws_test.go ├── client.go ├── export_test.go ├── regions.go ├── retry.go ├── retry_test.go ├── sign.go └── sign_test.go ├── cloudfront ├── cloudfront.go ├── cloudfront_test.go └── testdata │ ├── key.pem │ └── key.pub ├── cloudwatch ├── ChangeLog ├── README.md ├── cloudwatch.go └── cloudwatch_test.go ├── dynamodb ├── .gitignore ├── Makefile ├── README.md ├── attribute.go ├── batch.go ├── batch_test.go ├── dynamizer │ ├── dynamizer.go │ └── dynamizer_test.go ├── dynamo_query_builder.go ├── dynamo_query_builder_test.go ├── dynamodb.go ├── dynamodb_test.go ├── item.go ├── item_test.go ├── marshaller.go ├── marshaller_test.go ├── query.go ├── query_builder.go ├── query_builder_test.go ├── retry_test.go ├── scan.go ├── table.go └── table_test.go ├── ec2 ├── ec2.go ├── ec2_test.go ├── ec2i_test.go ├── ec2t_test.go ├── ec2test │ ├── filter.go │ └── server.go ├── export_test.go └── responses_test.go ├── ecommerce └── ecommerce.go ├── elasticache ├── elasticache.go ├── elasticache_test.go └── responses_test.go ├── elb ├── elb.go ├── elb_test.go ├── elbi_test.go ├── elbt_test.go ├── elbtest │ └── server.go ├── response_test.go └── suite_test.go ├── exp ├── mturk │ ├── example_test.go │ ├── export_test.go │ ├── mturk.go │ ├── mturk_test.go │ ├── responses_test.go │ ├── sign.go │ └── sign_test.go ├── sdb │ ├── export_test.go │ ├── responses_test.go │ ├── sdb.go │ ├── sdb_test.go │ ├── sign.go │ └── sign_test.go └── ses │ ├── delivery_notification.go │ ├── delivery_notification_requests_test.go │ ├── delivery_notification_test.go │ ├── mailbox_simulator.go │ ├── responses_test.go │ ├── ses.go │ └── ses_test.go ├── iam ├── iam.go ├── iam_test.go ├── iami_test.go ├── iamt_test.go ├── iamtest │ └── server.go └── responses_test.go ├── kinesis ├── kinesis.go ├── kinesis_test.go ├── query.go ├── responses_test.go └── types.go ├── kms ├── kms.go ├── kms_test.go ├── requestStruct.go ├── responseStruct.go └── response_test.go ├── rds ├── rds.go ├── rds_test.go ├── responses_test.go └── types.go ├── route53 ├── route53.go └── route53_test.go ├── s3 ├── export_test.go ├── lifecycle.go ├── lifecycle_test.go ├── multi.go ├── multi_test.go ├── responses_test.go ├── s3.go ├── s3_test.go ├── s3aclxml.go ├── s3aclxml_test.go ├── s3i_test.go ├── s3t_test.go ├── s3test │ └── server.go ├── sign.go └── sign_test.go ├── sns ├── http_notifications.go ├── http_notifications_test.go ├── responses_test.go ├── sns.go ├── sns_test.go └── structs.go ├── sqs ├── Makefile ├── README.md ├── md5.go ├── responses_test.go ├── sqs.go ├── sqs_test.go └── suite_test.go ├── sts ├── responses_test.go ├── sts.go └── sts_test.go └── testutil ├── http.go └── suite.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.iml 3 | *.idea 4 | -------------------------------------------------------------------------------- /.lbox: -------------------------------------------------------------------------------- 1 | propose -for=lp:goamz -cr 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.2.x 5 | - 1.3.x 6 | - 1.4.x 7 | - 1.5.x 8 | - 1.7.x 9 | - 1.8.x 10 | - 1.9.x 11 | - master 12 | 13 | after_script: 14 | - FIXIT=$(go fmt ./...); if [ -n "${FIXIT}" ]; then FIXED=$(echo $FIXIT | wc -l); echo "gofmt - ${FIXED} file(s) not formatted correctly, please run gofmt to fix them:\n ${FIXIT} " && exit 1; fi 15 | 16 | script: 17 | - go test -v ./autoscaling/ 18 | - go test -v ./aws/ 19 | - go test -v ./cloudfront/ 20 | - go test -v ./cloudwatch/ 21 | - go test -v ./dynamodb/ 22 | - go test -v ./ec2/ 23 | - go test -v ./elasticache/ 24 | - go test -v ./elb/ 25 | - go test -v ./iam/ 26 | - go test -v ./kinesis/ 27 | - go test -v ./rds/ 28 | - go test -v ./route53/ 29 | - go test -v ./s3/ 30 | - go test -v ./sns/ 31 | - go test -v ./sqs/ 32 | - go test -v ./sts/ 33 | - go test -v ./exp/mturk/ 34 | - go test -v ./exp/sdb/ 35 | - go test -v ./exp/ses/ 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GoAMZ 2 | 3 | [![Sourcegraph](https://sourcegraph.com/github.com/AdRoll/goamz/-/badge.svg)](https://sourcegraph.com/github.com/AdRoll/goamz?badge) 4 | 5 | [![Build Status](https://travis-ci.org/AdRoll/goamz.png?branch=master)](https://travis-ci.org/AdRoll/goamz) 6 | 7 | The _goamz_ package enables Go programs to interact with Amazon Web Services. 8 | 9 | This is a fork of the version [developed within Canonical](https://wiki.ubuntu.com/goamz) with additional functionality and services from [a number of contributors](https://github.com/AdRoll/goamz/contributors)! 10 | 11 | The API of AWS is very comprehensive, though, and goamz doesn't even scratch the surface of it. That said, it's fairly well tested, and is the foundation in which further calls can easily be integrated. We'll continue extending the API as necessary - Pull Requests are _very_ welcome! 12 | 13 | The following packages are available at the moment: 14 | 15 | ``` 16 | github.com/AdRoll/goamz/aws 17 | github.com/AdRoll/goamz/cloudwatch 18 | github.com/AdRoll/goamz/dynamodb 19 | github.com/AdRoll/goamz/ec2 20 | github.com/AdRoll/goamz/elb 21 | github.com/AdRoll/goamz/iam 22 | github.com/AdRoll/goamz/kinesis 23 | github.com/AdRoll/goamz/s3 24 | github.com/AdRoll/goamz/sqs 25 | github.com/AdRoll/goamz/sns 26 | 27 | github.com/AdRoll/goamz/exp/mturk 28 | github.com/AdRoll/goamz/exp/sdb 29 | github.com/AdRoll/goamz/exp/ses 30 | ``` 31 | 32 | Packages under `exp/` are still in an experimental or unfinished/unpolished state. 33 | 34 | ## API documentation 35 | 36 | The API documentation is currently available at: 37 | 38 | [http://godoc.org/github.com/AdRoll/goamz](http://godoc.org/github.com/AdRoll/goamz) 39 | 40 | ## How to build and install goamz 41 | 42 | Just use `go get` with any of the available packages. For example: 43 | 44 | * `$ go get github.com/AdRoll/goamz/ec2` 45 | * `$ go get github.com/AdRoll/goamz/s3` 46 | 47 | ## Running tests 48 | 49 | To run tests, first install gocheck with: 50 | 51 | `$ go get launchpad.net/gocheck` 52 | 53 | Then run go test as usual: 54 | 55 | `$ go test github.com/AdRoll/goamz/...` 56 | 57 | _Note:_ running all tests with the command `go test ./...` will currently fail as tests do not tear down their HTTP listeners. 58 | 59 | If you want to run integration tests (costs money), set up the EC2 environment variables as usual, and run: 60 | 61 | `$ gotest -i` 62 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | TODO 4 | ==== 5 | 6 | here will go things that we are activly pursuing. 7 | 8 | 9 | Brain Storm 10 | =========== 11 | 12 | This is an unsorted, list of things that are 'nice to do'. From this list we will move things up to the active TODO list. Please feel absolutely free to contribute. 13 | 14 | 1. Investigate if we can install `localdynamodb` on the ci to run tests against it. This way we can run behavioral tests that test the actual behavior. And keep them up to date with updates of `localdynamodb`. 15 | 16 | 2. Graduate items in `/exp` 17 | 18 | 3. Discusse "two tiered api". Basically the idea is to have a 'low level' api that is one to one match of the AWS http api, and a `higher level` api that abstracts away the details and is as go idiomatic as possible hiding away hairy details. 19 | 20 | 4. Find contributors with experience with each submodule (aws service), and review and plan for new refactored and cleaned up api. 21 | 22 | 5. It will be very helpfull to have better docs, sample code, best practices (for some people, goamz is their first exposure to AWS, and having a few essential tips on credentials and IAM roles and the like is very helpful). 23 | 24 | 6. If you've given any talks, presentation on goamz please let us know. We'd love to feature your video, slides, website here. 25 | -------------------------------------------------------------------------------- /autoscaling/astest/http.go: -------------------------------------------------------------------------------- 1 | package astest 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net" 8 | "net/http" 9 | "net/url" 10 | "os" 11 | "time" 12 | ) 13 | 14 | type HTTPServer struct { 15 | URL string 16 | Timeout time.Duration 17 | started bool 18 | request chan *http.Request 19 | response chan ResponseFunc 20 | } 21 | 22 | type Response struct { 23 | Status int 24 | Headers map[string]string 25 | Body string 26 | } 27 | 28 | func NewHTTPServer() *HTTPServer { 29 | return &HTTPServer{URL: "http://localhost:4444", Timeout: 5 * time.Second} 30 | } 31 | 32 | type ResponseFunc func(path string) Response 33 | 34 | func (s *HTTPServer) Start() { 35 | if s.started { 36 | return 37 | } 38 | s.started = true 39 | s.request = make(chan *http.Request, 1024) 40 | s.response = make(chan ResponseFunc, 1024) 41 | u, err := url.Parse(s.URL) 42 | if err != nil { 43 | panic(err) 44 | } 45 | l, err := net.Listen("tcp", u.Host) 46 | if err != nil { 47 | panic(err) 48 | } 49 | go http.Serve(l, s) 50 | 51 | s.Response(203, nil, "") 52 | for { 53 | // Wait for it to be up. 54 | resp, err := http.Get(s.URL) 55 | if err == nil && resp.StatusCode == 203 { 56 | break 57 | } 58 | time.Sleep(1e8) 59 | } 60 | s.WaitRequest() // Consume dummy request. 61 | } 62 | 63 | // Flush discards all pending requests and responses. 64 | func (s *HTTPServer) Flush() { 65 | for { 66 | select { 67 | case <-s.request: 68 | case <-s.response: 69 | default: 70 | return 71 | } 72 | } 73 | } 74 | 75 | func body(req *http.Request) string { 76 | data, err := ioutil.ReadAll(req.Body) 77 | if err != nil { 78 | panic(err) 79 | } 80 | return string(data) 81 | } 82 | 83 | func (s *HTTPServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { 84 | req.ParseMultipartForm(1e6) 85 | data, err := ioutil.ReadAll(req.Body) 86 | if err != nil { 87 | panic(err) 88 | } 89 | req.Body = ioutil.NopCloser(bytes.NewBuffer(data)) 90 | s.request <- req 91 | var resp Response 92 | select { 93 | case respFunc := <-s.response: 94 | resp = respFunc(req.URL.Path) 95 | case <-time.After(s.Timeout): 96 | const msg = "ERROR: Timeout waiting for test to prepare a response\n" 97 | fmt.Fprintf(os.Stderr, msg) 98 | resp = Response{500, nil, msg} 99 | } 100 | if resp.Headers != nil { 101 | h := w.Header() 102 | for k, v := range resp.Headers { 103 | h.Set(k, v) 104 | } 105 | } 106 | if resp.Status != 0 { 107 | w.WriteHeader(resp.Status) 108 | } 109 | w.Write([]byte(resp.Body)) 110 | } 111 | 112 | // WaitRequests returns the next n requests made to the http server from 113 | // the queue. If not enough requests were previously made, it waits until 114 | // the timeout value for them to be made. 115 | func (s *HTTPServer) WaitRequests(n int) []*http.Request { 116 | reqs := make([]*http.Request, 0, n) 117 | for i := 0; i < n; i++ { 118 | select { 119 | case req := <-s.request: 120 | reqs = append(reqs, req) 121 | case <-time.After(s.Timeout): 122 | panic("Timeout waiting for request") 123 | } 124 | } 125 | return reqs 126 | } 127 | 128 | // WaitRequest returns the next request made to the http server from 129 | // the queue. If no requests were previously made, it waits until the 130 | // timeout value for one to be made. 131 | func (s *HTTPServer) WaitRequest() *http.Request { 132 | return s.WaitRequests(1)[0] 133 | } 134 | 135 | // ResponseFunc prepares the test server to respond the following n 136 | // requests using f to build each response. 137 | func (s *HTTPServer) ResponseFunc(n int, f ResponseFunc) { 138 | for i := 0; i < n; i++ { 139 | s.response <- f 140 | } 141 | } 142 | 143 | // ResponseMap maps request paths to responses. 144 | type ResponseMap map[string]Response 145 | 146 | // ResponseMap prepares the test server to respond the following n 147 | // requests using the m to obtain the responses. 148 | func (s *HTTPServer) ResponseMap(n int, m ResponseMap) { 149 | f := func(path string) Response { 150 | for rpath, resp := range m { 151 | if rpath == path { 152 | return resp 153 | } 154 | } 155 | body := "Path not found in response map: " + path 156 | return Response{Status: 500, Body: body} 157 | } 158 | s.ResponseFunc(n, f) 159 | } 160 | 161 | // Responses prepares the test server to respond the following n requests 162 | // using the provided response parameters. 163 | func (s *HTTPServer) Responses(n int, status int, headers map[string]string, body string) { 164 | f := func(path string) Response { 165 | return Response{status, headers, body} 166 | } 167 | s.ResponseFunc(n, f) 168 | } 169 | 170 | // Response prepares the test server to respond the following request 171 | // using the provided response parameters. 172 | func (s *HTTPServer) Response(status int, headers map[string]string, body string) { 173 | s.Responses(1, status, headers, body) 174 | } 175 | -------------------------------------------------------------------------------- /autoscaling/sign.go: -------------------------------------------------------------------------------- 1 | package autoscaling 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/sha256" 6 | "encoding/base64" 7 | "github.com/AdRoll/goamz/aws" 8 | "sort" 9 | "strings" 10 | ) 11 | 12 | // ---------------------------------------------------------------------------- 13 | // AutoScaling signing (http://goo.gl/fQmAN) 14 | 15 | var b64 = base64.StdEncoding 16 | 17 | func sign(auth aws.Auth, method, path string, params map[string]string, host string) { 18 | params["AWSAccessKeyId"] = auth.AccessKey 19 | params["SignatureVersion"] = "2" 20 | params["SignatureMethod"] = "HmacSHA256" 21 | if auth.Token() != "" { 22 | params["SecurityToken"] = auth.Token() 23 | } 24 | 25 | // AWS specifies that the parameters in a signed request must 26 | // be provided in the natural order of the keys. This is distinct 27 | // from the natural order of the encoded value of key=value. 28 | // Percent and gocheck.Equals affect the sorting order. 29 | var keys, sarray []string 30 | for k, _ := range params { 31 | keys = append(keys, k) 32 | } 33 | sort.Strings(keys) 34 | for _, k := range keys { 35 | sarray = append(sarray, aws.Encode(k)+"="+aws.Encode(params[k])) 36 | } 37 | // Check whether path has any length and if not set it to / 38 | if len(path) == 0 { 39 | path = "/" 40 | } 41 | joined := strings.Join(sarray, "&") 42 | payload := method + "\n" + host + "\n" + path + "\n" + joined 43 | hash := hmac.New(sha256.New, []byte(auth.SecretKey)) 44 | hash.Write([]byte(payload)) 45 | signature := make([]byte, b64.EncodedLen(hash.Size())) 46 | b64.Encode(signature, hash.Sum(nil)) 47 | 48 | params["Signature"] = string(signature) 49 | } 50 | -------------------------------------------------------------------------------- /aws/attempt.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // AttemptStrategy represents a strategy for waiting for an action 8 | // to complete successfully. This is an internal type used by the 9 | // implementation of other goamz packages. 10 | type AttemptStrategy struct { 11 | Total time.Duration // total duration of attempt. 12 | Delay time.Duration // interval between each try in the burst. 13 | Min int // minimum number of retries; overrides Total 14 | } 15 | 16 | type Attempt struct { 17 | strategy AttemptStrategy 18 | last time.Time 19 | end time.Time 20 | force bool 21 | count int 22 | } 23 | 24 | // Start begins a new sequence of attempts for the given strategy. 25 | func (s AttemptStrategy) Start() *Attempt { 26 | now := time.Now() 27 | return &Attempt{ 28 | strategy: s, 29 | last: now, 30 | end: now.Add(s.Total), 31 | force: true, 32 | } 33 | } 34 | 35 | // Next waits until it is time to perform the next attempt or returns 36 | // false if it is time to stop trying. 37 | func (a *Attempt) Next() bool { 38 | now := time.Now() 39 | sleep := a.nextSleep(now) 40 | if !a.force && !now.Add(sleep).Before(a.end) && a.strategy.Min <= a.count { 41 | return false 42 | } 43 | a.force = false 44 | if sleep > 0 && a.count > 0 { 45 | time.Sleep(sleep) 46 | now = time.Now() 47 | } 48 | a.count++ 49 | a.last = now 50 | return true 51 | } 52 | 53 | func (a *Attempt) nextSleep(now time.Time) time.Duration { 54 | sleep := a.strategy.Delay - now.Sub(a.last) 55 | if sleep < 0 { 56 | return 0 57 | } 58 | return sleep 59 | } 60 | 61 | // HasNext returns whether another attempt will be made if the current 62 | // one fails. If it returns true, the following call to Next is 63 | // guaranteed to return true. 64 | func (a *Attempt) HasNext() bool { 65 | if a.force || a.strategy.Min > a.count { 66 | return true 67 | } 68 | now := time.Now() 69 | if now.Add(a.nextSleep(now)).Before(a.end) { 70 | a.force = true 71 | return true 72 | } 73 | return false 74 | } 75 | -------------------------------------------------------------------------------- /aws/attempt_test.go: -------------------------------------------------------------------------------- 1 | package aws_test 2 | 3 | import ( 4 | "github.com/AdRoll/goamz/aws" 5 | "gopkg.in/check.v1" 6 | "time" 7 | ) 8 | 9 | func (S) TestAttemptTiming(c *check.C) { 10 | testAttempt := aws.AttemptStrategy{ 11 | Total: 0.25e9, 12 | Delay: 0.1e9, 13 | } 14 | want := []time.Duration{0, 0.1e9, 0.2e9, 0.2e9} 15 | got := make([]time.Duration, 0, len(want)) // avoid allocation when testing timing 16 | t0 := time.Now() 17 | for a := testAttempt.Start(); a.Next(); { 18 | got = append(got, time.Now().Sub(t0)) 19 | } 20 | got = append(got, time.Now().Sub(t0)) 21 | c.Assert(got, check.HasLen, len(want)) 22 | const margin = 0.01e9 23 | for i, got := range want { 24 | lo := want[i] - margin 25 | hi := want[i] + margin 26 | if got < lo || got > hi { 27 | c.Errorf("attempt %d want %g got %g", i, want[i].Seconds(), got.Seconds()) 28 | } 29 | } 30 | } 31 | 32 | func (S) TestAttemptNextHasNext(c *check.C) { 33 | a := aws.AttemptStrategy{}.Start() 34 | c.Assert(a.Next(), check.Equals, true) 35 | c.Assert(a.Next(), check.Equals, false) 36 | 37 | a = aws.AttemptStrategy{}.Start() 38 | c.Assert(a.Next(), check.Equals, true) 39 | c.Assert(a.HasNext(), check.Equals, false) 40 | c.Assert(a.Next(), check.Equals, false) 41 | 42 | a = aws.AttemptStrategy{Total: 2e8}.Start() 43 | c.Assert(a.Next(), check.Equals, true) 44 | c.Assert(a.HasNext(), check.Equals, true) 45 | time.Sleep(2e8) 46 | c.Assert(a.HasNext(), check.Equals, true) 47 | c.Assert(a.Next(), check.Equals, true) 48 | c.Assert(a.Next(), check.Equals, false) 49 | 50 | a = aws.AttemptStrategy{Total: 1e8, Min: 2}.Start() 51 | time.Sleep(1e8) 52 | c.Assert(a.Next(), check.Equals, true) 53 | c.Assert(a.HasNext(), check.Equals, true) 54 | c.Assert(a.Next(), check.Equals, true) 55 | c.Assert(a.HasNext(), check.Equals, false) 56 | c.Assert(a.Next(), check.Equals, false) 57 | } 58 | -------------------------------------------------------------------------------- /aws/aws_test.go: -------------------------------------------------------------------------------- 1 | package aws_test 2 | 3 | import ( 4 | "github.com/AdRoll/goamz/aws" 5 | "gopkg.in/check.v1" 6 | "io/ioutil" 7 | "os" 8 | "strings" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | func Test(t *testing.T) { 14 | check.TestingT(t) 15 | } 16 | 17 | var _ = check.Suite(&S{}) 18 | 19 | type S struct { 20 | environ []string 21 | } 22 | 23 | func (s *S) SetUpSuite(c *check.C) { 24 | s.environ = os.Environ() 25 | } 26 | 27 | func (s *S) TearDownTest(c *check.C) { 28 | os.Clearenv() 29 | for _, kv := range s.environ { 30 | l := strings.SplitN(kv, "=", 2) 31 | os.Setenv(l[0], l[1]) 32 | } 33 | } 34 | 35 | func (s *S) TestEnvAuthNoSecret(c *check.C) { 36 | os.Clearenv() 37 | _, err := aws.EnvAuth() 38 | c.Assert(err, check.ErrorMatches, "AWS_SECRET_ACCESS_KEY or AWS_SECRET_KEY not found in environment") 39 | } 40 | 41 | func (s *S) TestEnvAuthNoAccess(c *check.C) { 42 | os.Clearenv() 43 | os.Setenv("AWS_SECRET_ACCESS_KEY", "foo") 44 | _, err := aws.EnvAuth() 45 | c.Assert(err, check.ErrorMatches, "AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY not found in environment") 46 | } 47 | 48 | func (s *S) TestEnvAuth(c *check.C) { 49 | os.Clearenv() 50 | os.Setenv("AWS_SECRET_ACCESS_KEY", "secret") 51 | os.Setenv("AWS_ACCESS_KEY_ID", "access") 52 | auth, err := aws.EnvAuth() 53 | c.Assert(err, check.IsNil) 54 | c.Assert(auth, check.Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"}) 55 | } 56 | 57 | func (s *S) TestEnvAuthAlt(c *check.C) { 58 | os.Clearenv() 59 | os.Setenv("AWS_SECRET_KEY", "secret") 60 | os.Setenv("AWS_ACCESS_KEY", "access") 61 | auth, err := aws.EnvAuth() 62 | c.Assert(err, check.IsNil) 63 | c.Assert(auth, check.Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"}) 64 | } 65 | 66 | func (s *S) TestGetAuthStatic(c *check.C) { 67 | exptdate := time.Now().Add(time.Hour) 68 | auth, err := aws.GetAuth("access", "secret", "token", exptdate) 69 | c.Assert(err, check.IsNil) 70 | c.Assert(auth.AccessKey, check.Equals, "access") 71 | c.Assert(auth.SecretKey, check.Equals, "secret") 72 | c.Assert(auth.Token(), check.Equals, "token") 73 | c.Assert(auth.Expiration(), check.Equals, exptdate) 74 | } 75 | 76 | func (s *S) TestGetAuthEnv(c *check.C) { 77 | os.Clearenv() 78 | os.Setenv("AWS_SECRET_ACCESS_KEY", "secret") 79 | os.Setenv("AWS_ACCESS_KEY_ID", "access") 80 | auth, err := aws.GetAuth("", "", "", time.Time{}) 81 | c.Assert(err, check.IsNil) 82 | c.Assert(auth, check.Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"}) 83 | } 84 | 85 | func (s *S) TestEncode(c *check.C) { 86 | c.Assert(aws.Encode("foo"), check.Equals, "foo") 87 | c.Assert(aws.Encode("/"), check.Equals, "%2F") 88 | } 89 | 90 | func (s *S) TestRegionsAreNamed(c *check.C) { 91 | for n, r := range aws.Regions { 92 | c.Assert(n, check.Equals, r.Name) 93 | } 94 | } 95 | 96 | func (s *S) TestCredentialsFileAuth(c *check.C) { 97 | file, err := ioutil.TempFile("", "creds") 98 | 99 | if err != nil { 100 | c.Fatal(err) 101 | } 102 | 103 | iniFile := ` 104 | 105 | [default] ; comment 123 106 | aws_access_key_id = keyid1 ;comment 107 | aws_secret_access_key=key1 108 | 109 | [profile2] 110 | aws_access_key_id = keyid2 ;comment 111 | aws_secret_access_key=key2 112 | aws_session_token=token1 113 | 114 | ` 115 | _, err = file.WriteString(iniFile) 116 | if err != nil { 117 | c.Fatal(err) 118 | } 119 | 120 | err = file.Close() 121 | if err != nil { 122 | c.Fatal(err) 123 | } 124 | 125 | // check non-existant profile 126 | _, err = aws.CredentialFileAuth(file.Name(), "no profile", 30*time.Minute) 127 | c.Assert(err, check.Not(check.Equals), nil) 128 | 129 | defaultProfile, err := aws.CredentialFileAuth(file.Name(), "default", 30*time.Minute) 130 | c.Assert(err, check.Equals, nil) 131 | c.Assert(defaultProfile.AccessKey, check.Equals, "keyid1") 132 | c.Assert(defaultProfile.SecretKey, check.Equals, "key1") 133 | c.Assert(defaultProfile.Token(), check.Equals, "") 134 | 135 | profile2, err := aws.CredentialFileAuth(file.Name(), "profile2", 30*time.Minute) 136 | c.Assert(err, check.Equals, nil) 137 | c.Assert(profile2.AccessKey, check.Equals, "keyid2") 138 | c.Assert(profile2.SecretKey, check.Equals, "key2") 139 | c.Assert(profile2.Token(), check.Equals, "token1") 140 | } 141 | -------------------------------------------------------------------------------- /aws/client.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "math" 5 | "net" 6 | "net/http" 7 | "time" 8 | ) 9 | 10 | type RetryableFunc func(*http.Request, *http.Response, error) bool 11 | type WaitFunc func(try int) 12 | type DeadlineFunc func() time.Time 13 | 14 | type ResilientTransport struct { 15 | // Timeout is the maximum amount of time a dial will wait for 16 | // a connect to complete. 17 | // 18 | // The default is no timeout. 19 | // 20 | // With or without a timeout, the operating system may impose 21 | // its own earlier timeout. For instance, TCP timeouts are 22 | // often around 3 minutes. 23 | DialTimeout time.Duration 24 | 25 | // MaxTries, if non-zero, specifies the number of times we will retry on 26 | // failure. Retries are only attempted for temporary network errors or known 27 | // safe failures. 28 | MaxTries int 29 | Deadline DeadlineFunc 30 | ShouldRetry RetryableFunc 31 | Wait WaitFunc 32 | transport *http.Transport 33 | } 34 | 35 | // Convenience method for creating an http client 36 | func NewClient(rt *ResilientTransport) *http.Client { 37 | rt.transport = &http.Transport{ 38 | Dial: func(netw, addr string) (net.Conn, error) { 39 | c, err := net.DialTimeout(netw, addr, rt.DialTimeout) 40 | if err != nil { 41 | return nil, err 42 | } 43 | c.SetDeadline(rt.Deadline()) 44 | return c, nil 45 | }, 46 | Proxy: http.ProxyFromEnvironment, 47 | } 48 | // TODO: Would be nice is ResilientTransport allowed clients to initialize 49 | // with http.Transport attributes. 50 | return &http.Client{ 51 | Transport: rt, 52 | } 53 | } 54 | 55 | var retryingTransport = &ResilientTransport{ 56 | Deadline: func() time.Time { 57 | return time.Now().Add(5 * time.Second) 58 | }, 59 | DialTimeout: 10 * time.Second, 60 | MaxTries: 3, 61 | ShouldRetry: awsRetry, 62 | Wait: ExpBackoff, 63 | } 64 | 65 | // Exported default client 66 | var RetryingClient = NewClient(retryingTransport) 67 | 68 | func (t *ResilientTransport) RoundTrip(req *http.Request) (*http.Response, error) { 69 | return t.tries(req) 70 | } 71 | 72 | // Retry a request a maximum of t.MaxTries times. 73 | // We'll only retry if the proper criteria are met. 74 | // If a wait function is specified, wait that amount of time 75 | // In between requests. 76 | func (t *ResilientTransport) tries(req *http.Request) (res *http.Response, err error) { 77 | for try := 0; try < t.MaxTries; try += 1 { 78 | res, err = t.transport.RoundTrip(req) 79 | 80 | if !t.ShouldRetry(req, res, err) { 81 | break 82 | } 83 | if res != nil { 84 | res.Body.Close() 85 | } 86 | if t.Wait != nil { 87 | t.Wait(try) 88 | } 89 | } 90 | 91 | return 92 | } 93 | 94 | func ExpBackoff(try int) { 95 | time.Sleep(100 * time.Millisecond * 96 | time.Duration(math.Exp2(float64(try)))) 97 | } 98 | 99 | func LinearBackoff(try int) { 100 | time.Sleep(time.Duration(try*100) * time.Millisecond) 101 | } 102 | 103 | // Decide if we should retry a request. 104 | // In general, the criteria for retrying a request is described here 105 | // http://docs.aws.amazon.com/general/latest/gr/api-retries.html 106 | func awsRetry(req *http.Request, res *http.Response, err error) bool { 107 | retry := false 108 | 109 | // Retry if there's a temporary network error. 110 | if neterr, ok := err.(net.Error); ok { 111 | if neterr.Temporary() { 112 | retry = true 113 | } 114 | } 115 | 116 | // Retry if we get a 5xx series error. 117 | if res != nil { 118 | if res.StatusCode >= 500 && res.StatusCode < 600 { 119 | retry = true 120 | } 121 | } 122 | 123 | return retry 124 | } 125 | -------------------------------------------------------------------------------- /aws/export_test.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | ) 7 | 8 | // V4Signer: 9 | // Exporting methods for testing 10 | 11 | func (s *V4Signer) RequestTime(req *http.Request) time.Time { 12 | return s.requestTime(req) 13 | } 14 | 15 | func (s *V4Signer) CanonicalRequest(req *http.Request) string { 16 | return s.canonicalRequest(req, "") 17 | } 18 | 19 | func (s *V4Signer) StringToSign(t time.Time, creq string) string { 20 | return s.stringToSign(t, creq) 21 | } 22 | 23 | func (s *V4Signer) Signature(t time.Time, sts string) string { 24 | return s.signature(t, sts) 25 | } 26 | 27 | func (s *V4Signer) Authorization(header http.Header, t time.Time, signature string) string { 28 | return s.authorization(header, t, signature) 29 | } 30 | -------------------------------------------------------------------------------- /aws/retry.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "math/rand" 5 | "net" 6 | "net/http" 7 | "time" 8 | ) 9 | 10 | const ( 11 | maxDelay = 20 * time.Second 12 | defaultScale = 300 * time.Millisecond 13 | throttlingScale = 500 * time.Millisecond 14 | throttlingScaleRange = throttlingScale / 4 15 | defaultMaxRetries = 3 16 | dynamoDBScale = 25 * time.Millisecond 17 | dynamoDBMaxRetries = 10 18 | ) 19 | 20 | // A RetryPolicy encapsulates a strategy for implementing client retries. 21 | // 22 | // Default implementations are provided which match the AWS SDKs. 23 | type RetryPolicy interface { 24 | // ShouldRetry returns whether a client should retry a failed request. 25 | ShouldRetry(target string, r *http.Response, err error, numRetries int) bool 26 | 27 | // Delay returns the time a client should wait before issuing a retry. 28 | Delay(target string, r *http.Response, err error, numRetries int) time.Duration 29 | } 30 | 31 | // DefaultRetryPolicy implements the AWS SDK default retry policy. 32 | // 33 | // It will retry up to 3 times, and uses an exponential backoff with a scale 34 | // factor of 300ms (300ms, 600ms, 1200ms). If the retry is because of 35 | // throttling, the delay will also include some randomness. 36 | // 37 | // See https://github.com/aws/aws-sdk-java/blob/master/aws-java-sdk-core/src/main/java/com/amazonaws/retry/PredefinedRetryPolicies.java#L90. 38 | type DefaultRetryPolicy struct { 39 | } 40 | 41 | // ShouldRetry implements the RetryPolicy ShouldRetry method. 42 | func (policy DefaultRetryPolicy) ShouldRetry(target string, r *http.Response, err error, numRetries int) bool { 43 | return shouldRetry(r, err, numRetries, defaultMaxRetries) 44 | } 45 | 46 | // Delay implements the RetryPolicy Delay method. 47 | func (policy DefaultRetryPolicy) Delay(target string, r *http.Response, err error, numRetries int) time.Duration { 48 | scale := defaultScale 49 | if err, ok := err.(*Error); ok && isThrottlingException(err) { 50 | scale = throttlingScale + time.Duration(rand.Int63n(int64(throttlingScaleRange))) 51 | } 52 | return exponentialBackoff(numRetries, scale) 53 | } 54 | 55 | // DynamoDBRetryPolicy implements the AWS SDK DynamoDB retry policy. 56 | // 57 | // It will retry up to 10 times, and uses an exponential backoff with a scale 58 | // factor of 25ms (25ms, 50ms, 100ms, ...). 59 | // 60 | // See https://github.com/aws/aws-sdk-java/blob/master/aws-java-sdk-core/src/main/java/com/amazonaws/retry/PredefinedRetryPolicies.java#L103. 61 | type DynamoDBRetryPolicy struct { 62 | } 63 | 64 | // ShouldRetry implements the RetryPolicy ShouldRetry method. 65 | func (policy DynamoDBRetryPolicy) ShouldRetry(target string, r *http.Response, err error, numRetries int) bool { 66 | return shouldRetry(r, err, numRetries, dynamoDBMaxRetries) 67 | } 68 | 69 | // Delay implements the RetryPolicy Delay method. 70 | func (policy DynamoDBRetryPolicy) Delay(target string, r *http.Response, err error, numRetries int) time.Duration { 71 | return exponentialBackoff(numRetries, dynamoDBScale) 72 | } 73 | 74 | // NeverRetryPolicy never retries requests and returns immediately on failure. 75 | type NeverRetryPolicy struct { 76 | } 77 | 78 | // ShouldRetry implements the RetryPolicy ShouldRetry method. 79 | func (policy NeverRetryPolicy) ShouldRetry(target string, r *http.Response, err error, numRetries int) bool { 80 | return false 81 | } 82 | 83 | // Delay implements the RetryPolicy Delay method. 84 | func (policy NeverRetryPolicy) Delay(target string, r *http.Response, err error, numRetries int) time.Duration { 85 | return time.Duration(0) 86 | } 87 | 88 | // shouldRetry determines if we should retry the request. 89 | // 90 | // See http://docs.aws.amazon.com/general/latest/gr/api-retries.html. 91 | func shouldRetry(r *http.Response, err error, numRetries int, maxRetries int) bool { 92 | // Once we've exceeded the max retry attempts, game over. 93 | if numRetries >= maxRetries { 94 | return false 95 | } 96 | 97 | // Always retry temporary network errors. 98 | if err, ok := err.(net.Error); ok && err.Temporary() { 99 | return true 100 | } 101 | 102 | // Always retry 5xx responses. 103 | if r != nil && r.StatusCode >= 500 { 104 | return true 105 | } 106 | 107 | // Always retry throttling exceptions. 108 | if err, ok := err.(ServiceError); ok && isThrottlingException(err) { 109 | return true 110 | } 111 | 112 | // Other classes of failures indicate a problem with the request. Retrying 113 | // won't help. 114 | return false 115 | } 116 | 117 | func exponentialBackoff(numRetries int, scale time.Duration) time.Duration { 118 | if numRetries < 0 { 119 | return time.Duration(0) 120 | } 121 | 122 | delay := (1 << uint(numRetries)) * scale 123 | if delay > maxDelay { 124 | return maxDelay 125 | } 126 | return delay 127 | } 128 | 129 | func isThrottlingException(err ServiceError) bool { 130 | switch err.ErrorCode() { 131 | case "Throttling", "ThrottlingException", "ProvisionedThroughputExceededException": 132 | return true 133 | default: 134 | return false 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /cloudfront/cloudfront.go: -------------------------------------------------------------------------------- 1 | package cloudfront 2 | 3 | import ( 4 | "crypto" 5 | "crypto/rsa" 6 | "crypto/sha1" 7 | "encoding/base64" 8 | "encoding/json" 9 | "fmt" 10 | "github.com/AdRoll/goamz/aws" 11 | "net/url" 12 | "strconv" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | type CloudFront struct { 18 | BaseURL string 19 | keyPairId string 20 | key *rsa.PrivateKey 21 | } 22 | 23 | var base64Replacer = strings.NewReplacer("=", "_", "+", "-", "/", "~") 24 | 25 | func NewKeyLess(auth aws.Auth, baseurl string) *CloudFront { 26 | return &CloudFront{keyPairId: auth.AccessKey, BaseURL: baseurl} 27 | } 28 | 29 | func New(baseurl string, key *rsa.PrivateKey, keyPairId string) *CloudFront { 30 | return &CloudFront{ 31 | BaseURL: baseurl, 32 | keyPairId: keyPairId, 33 | key: key, 34 | } 35 | } 36 | 37 | type epochTime struct { 38 | EpochTime int64 `json:"AWS:EpochTime"` 39 | } 40 | 41 | type condition struct { 42 | DateLessThan epochTime 43 | } 44 | 45 | type statement struct { 46 | Resource string 47 | Condition condition 48 | } 49 | 50 | type policy struct { 51 | Statement []statement 52 | } 53 | 54 | func buildPolicy(resource string, expireTime time.Time) ([]byte, error) { 55 | p := &policy{ 56 | Statement: []statement{ 57 | statement{ 58 | Resource: resource, 59 | Condition: condition{ 60 | DateLessThan: epochTime{ 61 | EpochTime: expireTime.Truncate(time.Millisecond).Unix(), 62 | }, 63 | }, 64 | }, 65 | }, 66 | } 67 | 68 | return json.Marshal(p) 69 | } 70 | 71 | func (cf *CloudFront) generateSignature(policy []byte) (string, error) { 72 | hash := sha1.New() 73 | _, err := hash.Write(policy) 74 | if err != nil { 75 | return "", err 76 | } 77 | 78 | hashed := hash.Sum(nil) 79 | var signed []byte 80 | if cf.key.Validate() == nil { 81 | signed, err = rsa.SignPKCS1v15(nil, cf.key, crypto.SHA1, hashed) 82 | if err != nil { 83 | return "", err 84 | } 85 | } else { 86 | signed = hashed 87 | } 88 | encoded := base64Replacer.Replace(base64.StdEncoding.EncodeToString(signed)) 89 | 90 | return encoded, nil 91 | } 92 | 93 | // Creates a signed url using RSAwithSHA1 as specified by 94 | // http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-creating-signed-url-canned-policy.html#private-content-canned-policy-creating-signature 95 | func (cf *CloudFront) CannedSignedURL(path, queryString string, expires time.Time) (string, error) { 96 | resource := cf.BaseURL + path 97 | if queryString != "" { 98 | resource = path + "?" + queryString 99 | } 100 | 101 | policy, err := buildPolicy(resource, expires) 102 | if err != nil { 103 | return "", err 104 | } 105 | 106 | signature, err := cf.generateSignature(policy) 107 | if err != nil { 108 | return "", err 109 | } 110 | 111 | // TOOD: Do this once 112 | uri, err := url.Parse(cf.BaseURL) 113 | if err != nil { 114 | return "", err 115 | } 116 | 117 | uri.RawQuery = queryString 118 | if queryString != "" { 119 | uri.RawQuery += "&" 120 | } 121 | 122 | expireTime := expires.Truncate(time.Millisecond).Unix() 123 | 124 | uri.Path = path 125 | uri.RawQuery += fmt.Sprintf("Expires=%d&Signature=%s&Key-Pair-Id=%s", expireTime, signature, cf.keyPairId) 126 | 127 | return uri.String(), nil 128 | } 129 | 130 | func (cloudfront *CloudFront) SignedURL(path, querystrings string, expires time.Time) string { 131 | policy := `{"Statement":[{"Resource":"` + path + "?" + querystrings + `,"Condition":{"DateLessThan":{"AWS:EpochTime":` + strconv.FormatInt(expires.Truncate(time.Millisecond).Unix(), 10) + `}}}]}` 132 | 133 | hash := sha1.New() 134 | hash.Write([]byte(policy)) 135 | b := hash.Sum(nil) 136 | he := base64.StdEncoding.EncodeToString(b) 137 | 138 | policySha1 := he 139 | 140 | url := cloudfront.BaseURL + path + "?" + querystrings + "&Expires=" + strconv.FormatInt(expires.Unix(), 10) + "&Signature=" + policySha1 + "&Key-Pair-Id=" + cloudfront.keyPairId 141 | 142 | return url 143 | } 144 | -------------------------------------------------------------------------------- /cloudfront/cloudfront_test.go: -------------------------------------------------------------------------------- 1 | package cloudfront 2 | 3 | import ( 4 | "crypto/x509" 5 | "encoding/pem" 6 | "io/ioutil" 7 | "net/url" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func TestSignedCannedURL(t *testing.T) { 13 | rawKey, err := ioutil.ReadFile("testdata/key.pem") 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | 18 | pemKey, _ := pem.Decode(rawKey) 19 | privateKey, err := x509.ParsePKCS1PrivateKey(pemKey.Bytes) 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | 24 | cf := &CloudFront{ 25 | key: privateKey, 26 | keyPairId: "test-key-pair-1231245", 27 | BaseURL: "https://cloudfront.com", 28 | } 29 | 30 | expireTime, err := time.Parse(time.RFC3339, "2014-03-28T14:00:21Z") 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | 35 | query := make(url.Values) 36 | query.Add("test", "value") 37 | 38 | uri, err := cf.CannedSignedURL("test", "test=value", expireTime) 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | 43 | parsed, err := url.Parse(uri) 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | 48 | signature := parsed.Query().Get("Signature") 49 | if signature == "" { 50 | t.Fatal("Encoded signature is empty") 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /cloudfront/testdata/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQC0yMzp9DkPAE99DhsEaGkqougLvtmDKri4bZj0fFjmGmjyyjz9 3 | hlrsr87LHVWzH/7igK7040HG1UqypX3ijtJa9+6BKHwBBctboU3y4GfwFwVAOumY 4 | 9UytFpyPlgUFrffZLQAywKkT24OgcfEj0G5kiQn760wFnmSUtOuITo708QIDAQAB 5 | AoGAJUA6+PoZx72Io3wElSPuh5qJteHdb+mdpmLu4XG936wRc/W4G4VTtvGC6tdg 6 | kUhGfOWHJ26sXwwUGDuBdO146m0DkBTuIooy97afpL6hXgL5v4ELHbbuFJcf4Geg 7 | /UAuexvRT1HenYFQ/iXM0LlqI33i8cFRc1A+j0Gseo07gAECQQDYFCn7OUokX+Q8 8 | M2Cwhu7JT1obmP2HwsBtXl0CDDxtOQkuYJP/UqvtdYPz/kRn3yQjoynaCTHYrFz/ 9 | H8oN1nNhAkEA1i9TEpo7RbanIyT4vbc1/5xfjE7Pj0lnGku0QXFp/S+8YxbqhjrQ 10 | 4Qp7TTXIPPqvQhhEpAGGspM460K3F6h7kQJBANJCbMeFa9wRY2ohJIkiA+HoUWph 11 | aPNeUxkZpa+EcJhn08NJPzpIG/ypSYl3duEMhYIYF3WPVO3ea2/mYxsr/oECQFj5 12 | td/fdEoEk7AU1sQxDNyPwF2QC8dxbcRNuKcLD0Wfg/oB9hEm88jYytoLQpCabx3c 13 | 6P7cp3EdmaKZx2erlRECQDYTSK2tS0+VoXSV9JbU08Pbu53j3Zhmp4l0csP+l7EU 14 | U+rRQzKho4X9vpR/VpRGXbw8tTIhojNpHh5ofryVfgk= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /cloudfront/testdata/key.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC0yMzp9DkPAE99DhsEaGkqougL 3 | vtmDKri4bZj0fFjmGmjyyjz9hlrsr87LHVWzH/7igK7040HG1UqypX3ijtJa9+6B 4 | KHwBBctboU3y4GfwFwVAOumY9UytFpyPlgUFrffZLQAywKkT24OgcfEj0G5kiQn7 5 | 60wFnmSUtOuITo708QIDAQAB 6 | -----END PUBLIC KEY----- 7 | -------------------------------------------------------------------------------- /cloudwatch/ChangeLog: -------------------------------------------------------------------------------- 1 | 2013-10-21 Carlos Salguero 2 | 3 | * Removed Namespace from the constructor as not all AWS API method needs it 4 | and methods like ListMetrics you could need to call the method without a 5 | Namespace to list all available metrics 6 | 7 | * Added ListMetrics method 8 | -------------------------------------------------------------------------------- /cloudwatch/README.md: -------------------------------------------------------------------------------- 1 | #GoLang AWS Cloudwatch 2 | 3 | ## Installation 4 | Please refer to the project's main page at [https://github.com/AdRoll/goamz](https://github.com/AdRoll/goamz) for instructions about how to install. 5 | 6 | ## Available methods 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
GetMetricStatisticsGets statistics for the specified metric.
ListMetricsReturns a list of valid metrics stored for the AWS account.
PutMetricDataPublishes metric data points to Amazon CloudWatch.
22 | 23 | [Please refer to AWS Cloudwatch's documentation for more info](http://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_Operations.html) 24 | 25 | ##Examples 26 | ####Get Metric Statistics 27 | 28 | ```go 29 | import ( 30 | "fmt" 31 | "time" 32 | "os" 33 | "github.com/AdRoll/goamz/aws" 34 | "github.com/AdRoll/goamz/cloudwatch" 35 | ) 36 | 37 | func test_get_metric_statistics() { 38 | region := aws.Regions["a_region"] 39 | namespace:= "AWS/ELB" 40 | dimension := &cloudwatch.Dimension{ 41 | Name: "LoadBalancerName", 42 | Value: "your_value", 43 | } 44 | metricName := "RequestCount" 45 | now := time.Now() 46 | prev := now.Add(time.Duration(600)*time.Second*-1) // 600 secs = 10 minutes 47 | 48 | auth, err := aws.GetAuth("your_AccessKeyId", "your_SecretAccessKey", "", now) 49 | if err != nil { 50 | fmt.Printf("Error: %+v\n", err) 51 | os.Exit(1) 52 | } 53 | 54 | cw, err := cloudwatch.NewCloudWatch(auth, region.CloudWatchServicepoint) 55 | request := &cloudwatch.GetMetricStatisticsRequest { 56 | Dimensions: []cloudwatch.Dimension{*dimension}, 57 | EndTime: now, 58 | StartTime: prev, 59 | MetricName: metricName, 60 | Unit: cloudwatch.UnitCount, // Not mandatory 61 | Period: 60, 62 | Statistics: []string{cloudwatch.StatisticDatapointSum}, 63 | Namespace: namespace, 64 | } 65 | 66 | response, err := cw.GetMetricStatistics(request) 67 | if err == nil { 68 | fmt.Printf("%+v\n", response) 69 | } else { 70 | fmt.Printf("Error: %+v\n", err) 71 | } 72 | } 73 | ``` 74 | ####List Metrics 75 | 76 | ```go 77 | import ( 78 | "fmt" 79 | "time" 80 | "os" 81 | "github.com/AdRoll/goamz/aws" 82 | "github.com/AdRoll/goamz/cloudwatch" 83 | ) 84 | 85 | func test_list_metrics() { 86 | region := aws.Regions["us-east-1"] // Any region here 87 | now := time.Now() 88 | 89 | auth, err := aws.GetAuth("an AccessKeyId", "a SecretAccessKey", "", now) 90 | if err != nil { 91 | fmt.Printf("Error: %+v\n", err) 92 | os.Exit(1) 93 | } 94 | cw, err := cloudwatch.NewCloudWatch(auth, region.CloudWatchServicepoint) 95 | request := &cloudwatch.ListMetricsRequest{Namespace: "AWS/EC2"} 96 | 97 | response, err := cw.ListMetrics(request) 98 | if err == nil { 99 | fmt.Printf("%+v\n", response) 100 | } else { 101 | fmt.Printf("Error: %+v\n", err) 102 | } 103 | } 104 | ``` -------------------------------------------------------------------------------- /cloudwatch/cloudwatch_test.go: -------------------------------------------------------------------------------- 1 | package cloudwatch_test 2 | 3 | import ( 4 | "github.com/AdRoll/goamz/aws" 5 | "github.com/AdRoll/goamz/cloudwatch" 6 | "github.com/AdRoll/goamz/testutil" 7 | "gopkg.in/check.v1" 8 | "testing" 9 | ) 10 | 11 | func Test(t *testing.T) { 12 | check.TestingT(t) 13 | } 14 | 15 | type S struct { 16 | cw *cloudwatch.CloudWatch 17 | } 18 | 19 | var _ = check.Suite(&S{}) 20 | 21 | var testServer = testutil.NewHTTPServer() 22 | 23 | func (s *S) SetUpSuite(c *check.C) { 24 | testServer.Start() 25 | auth := aws.Auth{AccessKey: "abc", SecretKey: "123"} 26 | s.cw, _ = cloudwatch.NewCloudWatch(auth, aws.ServiceInfo{Endpoint: testServer.URL, Signer: aws.V2Signature}) 27 | } 28 | 29 | func (s *S) TearDownTest(c *check.C) { 30 | testServer.Flush() 31 | } 32 | 33 | func getTestAlarm() *cloudwatch.MetricAlarm { 34 | alarm := new(cloudwatch.MetricAlarm) 35 | 36 | alarm.AlarmDescription = "Test Description" 37 | alarm.AlarmName = "TestAlarm" 38 | alarm.MetricName = "TestMetric" 39 | alarm.Namespace = "TestNamespace" 40 | alarm.ComparisonOperator = "LessThanThreshold" 41 | alarm.Threshold = 1 42 | alarm.EvaluationPeriods = 5 43 | alarm.Period = 60 44 | alarm.Statistic = "Sum" 45 | 46 | return alarm 47 | } 48 | 49 | func (s *S) TestPutAlarm(c *check.C) { 50 | testServer.Response(200, nil, "123") 51 | 52 | alarm := getTestAlarm() 53 | 54 | _, err := s.cw.PutMetricAlarm(alarm) 55 | c.Assert(err, check.IsNil) 56 | 57 | req := testServer.WaitRequest() 58 | c.Assert(req.Method, check.Equals, "POST") 59 | c.Assert(req.URL.Path, check.Equals, "/") 60 | c.Assert(req.Form["Action"], check.DeepEquals, []string{"PutMetricAlarm"}) 61 | c.Assert(req.Form["AlarmName"], check.DeepEquals, []string{"TestAlarm"}) 62 | c.Assert(req.Form["ComparisonOperator"], check.DeepEquals, []string{"LessThanThreshold"}) 63 | c.Assert(req.Form["EvaluationPeriods"], check.DeepEquals, []string{"5"}) 64 | c.Assert(req.Form["Threshold"], check.DeepEquals, []string{"1.0000000000E+00"}) 65 | c.Assert(req.Form["Period"], check.DeepEquals, []string{"60"}) 66 | c.Assert(req.Form["Statistic"], check.DeepEquals, []string{"Sum"}) 67 | } 68 | 69 | func (s *S) TestPutAlarmWithAction(c *check.C) { 70 | testServer.Response(200, nil, "123") 71 | 72 | alarm := getTestAlarm() 73 | 74 | var actions []cloudwatch.AlarmAction 75 | action := new(cloudwatch.AlarmAction) 76 | action.ARN = "123" 77 | actions = append(actions, *action) 78 | 79 | alarm.AlarmActions = actions 80 | 81 | _, err := s.cw.PutMetricAlarm(alarm) 82 | c.Assert(err, check.IsNil) 83 | 84 | req := testServer.WaitRequest() 85 | c.Assert(req.Method, check.Equals, "POST") 86 | c.Assert(req.URL.Path, check.Equals, "/") 87 | c.Assert(req.Form["Action"], check.DeepEquals, []string{"PutMetricAlarm"}) 88 | c.Assert(req.Form["AlarmActions.member.1"], check.DeepEquals, []string{"123"}) 89 | c.Assert(req.Form["AlarmName"], check.DeepEquals, []string{"TestAlarm"}) 90 | c.Assert(req.Form["ComparisonOperator"], check.DeepEquals, []string{"LessThanThreshold"}) 91 | c.Assert(req.Form["EvaluationPeriods"], check.DeepEquals, []string{"5"}) 92 | c.Assert(req.Form["Threshold"], check.DeepEquals, []string{"1.0000000000E+00"}) 93 | c.Assert(req.Form["Period"], check.DeepEquals, []string{"60"}) 94 | c.Assert(req.Form["Statistic"], check.DeepEquals, []string{"Sum"}) 95 | } 96 | 97 | func (s *S) TestPutAlarmInvalidComapirsonOperator(c *check.C) { 98 | testServer.Response(200, nil, "123") 99 | 100 | alarm := getTestAlarm() 101 | 102 | alarm.ComparisonOperator = "LessThan" 103 | 104 | _, err := s.cw.PutMetricAlarm(alarm) 105 | c.Assert(err, check.NotNil) 106 | c.Assert(err.Error(), check.Equals, "ComparisonOperator is not valid") 107 | } 108 | 109 | func (s *S) TestPutAlarmInvalidStatistic(c *check.C) { 110 | testServer.Response(200, nil, "123") 111 | 112 | alarm := getTestAlarm() 113 | 114 | alarm.Statistic = "Count" 115 | 116 | _, err := s.cw.PutMetricAlarm(alarm) 117 | c.Assert(err, check.NotNil) 118 | c.Assert(err.Error(), check.Equals, "Invalid statistic value supplied") 119 | } 120 | -------------------------------------------------------------------------------- /dynamodb/.gitignore: -------------------------------------------------------------------------------- 1 | dynamodb_local* 2 | -------------------------------------------------------------------------------- /dynamodb/Makefile: -------------------------------------------------------------------------------- 1 | DYNAMODB_LOCAL_VERSION = latest 2 | 3 | launch: DynamoDBLocal.jar 4 | cd dynamodb_local_$(DYNAMODB_LOCAL_VERSION) && java -Djava.library.path=./DynamoDBLocal_lib -jar DynamoDBLocal.jar 5 | 6 | DynamoDBLocal.jar: dynamodb_local_$(DYNAMODB_LOCAL_VERSION).tar.gz 7 | mkdir -p dynamodb_local_$(DYNAMODB_LOCAL_VERSION) 8 | [ -f dynamodb_local_$(DYNAMODB_LOCAL_VERSION)/DynamoDBLocal.jar ] || tar -C dynamodb_local_$(DYNAMODB_LOCAL_VERSION) -zxf dynamodb_local_$(DYNAMODB_LOCAL_VERSION).tar.gz 9 | 10 | dynamodb_local_$(DYNAMODB_LOCAL_VERSION).tar.gz: 11 | curl -L -O http://dynamodb-local.s3-website-us-west-2.amazonaws.com/dynamodb_local_$(DYNAMODB_LOCAL_VERSION).tar.gz 12 | 13 | clean: 14 | rm -rf dynamodb_local_$(DYNAMODB_LOCAL_VERSION)* 15 | -------------------------------------------------------------------------------- /dynamodb/README.md: -------------------------------------------------------------------------------- 1 | # Running integration tests 2 | 3 | ## against DynamoDB local 4 | 5 | To download and launch DynamoDB local: 6 | 7 | ```sh 8 | $ make 9 | ``` 10 | 11 | To test: 12 | 13 | ```sh 14 | $ go test -v -amazon 15 | ``` 16 | 17 | ## against real DynamoDB server on us-east 18 | 19 | _WARNING_: Some dangerous operations such as `DeleteTable` will be performed during the tests. Please be careful. 20 | 21 | To test: 22 | 23 | ```sh 24 | $ go test -v -amazon -local=false 25 | ``` 26 | 27 | _Note_: Running tests against real DynamoDB will take several minutes. 28 | -------------------------------------------------------------------------------- /dynamodb/dynamo_query_builder_test.go: -------------------------------------------------------------------------------- 1 | package dynamodb 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "github.com/AdRoll/goamz/aws" 7 | "github.com/AdRoll/goamz/dynamodb/dynamizer" 8 | "reflect" 9 | "testing" 10 | ) 11 | 12 | func TestDynamoQuery(t *testing.T) { 13 | region := aws.Region{DynamoDBEndpoint: "http://127.0.0.1:8000"} 14 | auth := aws.Auth{AccessKey: "DUMMY_KEY", SecretKey: "DUMMY_SECRET"} 15 | server := New(auth, region) 16 | desc := TableDescriptionT{ 17 | TableName: "DynamoDBTestMyTable", 18 | AttributeDefinitions: []AttributeDefinitionT{ 19 | AttributeDefinitionT{"TestHashKey", "S"}, 20 | }, 21 | KeySchema: []KeySchemaT{ 22 | KeySchemaT{"TestHashKey", "HASH"}, 23 | }, 24 | ProvisionedThroughput: ProvisionedThroughputT{ 25 | ReadCapacityUnits: 1, 26 | WriteCapacityUnits: 1, 27 | }, 28 | } 29 | pk, err := desc.BuildPrimaryKey() 30 | if err != nil { 31 | panic(err) 32 | } 33 | table := server.NewTable(desc.TableName, pk) 34 | 35 | testGetQuery(t, table, true, `{"TableName":"DynamoDBTestMyTable","ConsistentRead":true,"Key":{"TestHashKey":{"S":"NewHashKeyVal"}}}`) 36 | testGetQuery(t, table, false, `{"TableName":"DynamoDBTestMyTable","Key":{"TestHashKey":{"S":"NewHashKeyVal"}}}`) 37 | testPutQuery(t, table, `{"TableName":"DynamoDBTestMyTable","Item":{"Attr1":{"S":"Attr1Val"},"Attr2":{"N":"12"},"TestHashKey":{"S":"NewHashKeyVal"}}}`) 38 | testDeleteQuery(t, table, false, `{"TableName":"DynamoDBTestMyTable","Key":{"TestHashKey":{"S":"NewHashKeyVal"}}}`) 39 | } 40 | 41 | func TestDynamoQueryWithRange(t *testing.T) { 42 | region := aws.Region{DynamoDBEndpoint: "http://127.0.0.1:8000"} 43 | auth := aws.Auth{AccessKey: "DUMMY_KEY", SecretKey: "DUMMY_SECRET"} 44 | server := New(auth, region) 45 | desc := TableDescriptionT{ 46 | TableName: "DynamoDBTestMyTable", 47 | AttributeDefinitions: []AttributeDefinitionT{ 48 | AttributeDefinitionT{"TestHashKey", "S"}, 49 | AttributeDefinitionT{"TestRangeKey", "N"}, 50 | }, 51 | KeySchema: []KeySchemaT{ 52 | KeySchemaT{"TestHashKey", "HASH"}, 53 | KeySchemaT{"TestRangeKey", "RANGE"}, 54 | }, 55 | ProvisionedThroughput: ProvisionedThroughputT{ 56 | ReadCapacityUnits: 1, 57 | WriteCapacityUnits: 1, 58 | }, 59 | } 60 | pk, err := desc.BuildPrimaryKey() 61 | if err != nil { 62 | panic(err) 63 | } 64 | table := server.NewTable(desc.TableName, pk) 65 | 66 | testGetQuery(t, table, true, `{"TableName":"DynamoDBTestMyTable","ConsistentRead":true,"Key":{"TestHashKey":{"S":"NewHashKeyVal"},"TestRangeKey":{"N":"12"}}}`) 67 | testGetQuery(t, table, false, `{"TableName":"DynamoDBTestMyTable","Key":{"TestHashKey":{"S":"NewHashKeyVal"},"TestRangeKey":{"N":"12"}}}`) 68 | testPutQuery(t, table, `{"TableName":"DynamoDBTestMyTable","Item":{"Attr1":{"S":"Attr1Val"},"Attr2":{"N":"12"},"TestHashKey":{"S":"NewHashKeyVal"},"TestRangeKey":{"N":"12"}}}`) 69 | testDeleteQuery(t, table, false, `{"TableName":"DynamoDBTestMyTable","Key":{"TestHashKey":{"S":"NewHashKeyVal"},"TestRangeKey":{"N":"12"}}}`) 70 | } 71 | 72 | func testPutQuery(t *testing.T, table *Table, expected string) { 73 | var key *Key 74 | if table.Key.HasRange() { 75 | key = &Key{HashKey: "NewHashKeyVal", RangeKey: "12"} 76 | } else { 77 | key = &Key{HashKey: "NewHashKeyVal"} 78 | } 79 | 80 | data := map[string]interface{}{ 81 | "Attr1": "Attr1Val", 82 | "Attr2": 12} 83 | item, err := dynamizer.ToDynamo(data) 84 | if err != nil { 85 | t.Fatal(err) 86 | } 87 | 88 | q := NewDynamoQuery(table) 89 | if err := q.AddItem(key, item); err != nil { 90 | t.Fatal(err) 91 | } 92 | 93 | actual, err := q.Marshal() 94 | if err != nil { 95 | t.Fatal(err) 96 | } 97 | compareJSONStrings(t, expected, actual) 98 | } 99 | 100 | func testGetQuery(t *testing.T, table *Table, consistent bool, expected string) { 101 | var key *Key 102 | if table.Key.HasRange() { 103 | key = &Key{HashKey: "NewHashKeyVal", RangeKey: "12"} 104 | } else { 105 | key = &Key{HashKey: "NewHashKeyVal"} 106 | } 107 | 108 | q := NewDynamoQuery(table) 109 | if err := q.AddKey(key); err != nil { 110 | t.Fatal(err) 111 | } 112 | if err := q.SetConsistentRead(consistent); err != nil { 113 | t.Fatal(err) 114 | } 115 | 116 | actual, err := q.Marshal() 117 | if err != nil { 118 | t.Fatal(err) 119 | } 120 | compareJSONStrings(t, expected, actual) 121 | } 122 | 123 | func testDeleteQuery(t *testing.T, table *Table, consistent bool, expected string) { 124 | var key *Key 125 | if table.Key.HasRange() { 126 | key = &Key{HashKey: "NewHashKeyVal", RangeKey: "12"} 127 | } else { 128 | key = &Key{HashKey: "NewHashKeyVal"} 129 | } 130 | 131 | q := NewDynamoQuery(table) 132 | if err := q.AddKey(key); err != nil { 133 | t.Fatal(err) 134 | } 135 | 136 | actual, err := q.Marshal() 137 | if err != nil { 138 | t.Fatal(err) 139 | } 140 | compareJSONStrings(t, expected, actual) 141 | } 142 | 143 | // What we're trying to do here is compare the JSON encoded values, but we can't 144 | // to a simple encode + string compare since JSON encoding is not ordered. So 145 | // what we do is JSON encode, then JSON decode into untyped maps, and then 146 | // finally do a recursive comparison. 147 | func compareJSONStrings(t *testing.T, expected string, actual []byte) { 148 | var expectedBytes bytes.Buffer 149 | expectedBytes.WriteString(expected) 150 | var expectedUntyped, actualUntyped map[string]interface{} 151 | err := json.Unmarshal(expectedBytes.Bytes(), &expectedUntyped) 152 | if err != nil { 153 | t.Fatal(err) 154 | } 155 | err = json.Unmarshal(actual, &actualUntyped) 156 | if err != nil { 157 | t.Fatal(err) 158 | } 159 | if !reflect.DeepEqual(expectedUntyped, actualUntyped) { 160 | t.Fatalf("Expected %s, got %s", expected, actual) 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /dynamodb/dynamodb.go: -------------------------------------------------------------------------------- 1 | package dynamodb 2 | 3 | import simplejson "github.com/bitly/go-simplejson" 4 | import ( 5 | "bytes" 6 | "errors" 7 | "io/ioutil" 8 | "net/http" 9 | "strings" 10 | "time" 11 | 12 | "github.com/AdRoll/goamz/aws" 13 | ) 14 | 15 | type Server struct { 16 | Auth aws.Auth 17 | Region aws.Region 18 | RetryPolicy aws.RetryPolicy 19 | } 20 | 21 | func New(auth aws.Auth, region aws.Region) *Server { 22 | return &Server{auth, region, aws.DynamoDBRetryPolicy{}} 23 | } 24 | 25 | // Specific error constants 26 | var ErrNotFound = errors.New("Item not found") 27 | var ErrNotProcessed = errors.New("Key was not processed in the batch request, should retry") 28 | 29 | // Error represents an error in an operation with Dynamodb (following goamz/s3) 30 | type Error struct { 31 | StatusCode int // HTTP status code (200, 403, ...) 32 | Status string 33 | Code string // Dynamodb error code ("MalformedQueryString", ...) 34 | Message string // The human-oriented error message 35 | } 36 | 37 | func (e Error) Error() string { 38 | if e.Message != "" { 39 | return e.Code + ": " + e.Message 40 | } 41 | return e.Code 42 | } 43 | 44 | func (e Error) ErrorCode() string { 45 | return e.Code 46 | } 47 | 48 | func buildError(r *http.Response, jsonBody []byte) error { 49 | 50 | ddbError := Error{ 51 | StatusCode: r.StatusCode, 52 | Status: r.Status, 53 | } 54 | 55 | json, err := simplejson.NewJson(jsonBody) 56 | if err != nil { 57 | return err 58 | } 59 | message := json.Get("Message").MustString() 60 | if message == "" { 61 | message = json.Get("message").MustString() 62 | } 63 | ddbError.Message = message 64 | 65 | // Of the form: com.amazon.coral.validate#ValidationException 66 | // We only want the last part 67 | codeStr := json.Get("__type").MustString() 68 | hashIndex := strings.Index(codeStr, "#") 69 | if hashIndex > 0 { 70 | codeStr = codeStr[hashIndex+1:] 71 | } 72 | ddbError.Code = codeStr 73 | 74 | return &ddbError 75 | } 76 | 77 | func (s *Server) queryServer(target string, query Query) ([]byte, error) { 78 | numRetries := 0 79 | for { 80 | qs, err := query.Marshal() 81 | if err != nil { 82 | return nil, err 83 | } 84 | data := bytes.NewReader(qs) 85 | 86 | hreq, err := http.NewRequest("POST", s.Region.DynamoDBEndpoint+"/", data) 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | hreq.Header.Set("Content-Type", "application/x-amz-json-1.0") 92 | hreq.Header.Set("X-Amz-Date", time.Now().UTC().Format(aws.ISO8601BasicFormat)) 93 | hreq.Header.Set("X-Amz-Target", target) 94 | 95 | token := s.Auth.Token() 96 | if token != "" { 97 | hreq.Header.Set("X-Amz-Security-Token", token) 98 | } 99 | 100 | signer := aws.NewV4Signer(s.Auth, "dynamodb", s.Region) 101 | signer.Sign(hreq) 102 | 103 | resp, err := http.DefaultClient.Do(hreq) 104 | if err != nil { 105 | if s.RetryPolicy.ShouldRetry(target, resp, err, numRetries) { 106 | time.Sleep(s.RetryPolicy.Delay(target, resp, err, numRetries)) 107 | numRetries++ 108 | continue 109 | } 110 | return nil, err 111 | } 112 | 113 | defer resp.Body.Close() 114 | 115 | body, err := ioutil.ReadAll(resp.Body) 116 | if err != nil { 117 | if s.RetryPolicy.ShouldRetry(target, resp, err, numRetries) { 118 | time.Sleep(s.RetryPolicy.Delay(target, resp, err, numRetries)) 119 | numRetries++ 120 | continue 121 | } 122 | return nil, err 123 | } 124 | 125 | // http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ErrorHandling.html 126 | // "A response code of 200 indicates the operation was successful." 127 | if resp.StatusCode != 200 { 128 | err := buildError(resp, body) 129 | if s.RetryPolicy.ShouldRetry(target, resp, err, numRetries) { 130 | time.Sleep(s.RetryPolicy.Delay(target, resp, err, numRetries)) 131 | numRetries++ 132 | continue 133 | } 134 | return nil, err 135 | } 136 | 137 | return body, nil 138 | } 139 | } 140 | 141 | func target(name string) string { 142 | return "DynamoDB_20120810." + name 143 | } 144 | -------------------------------------------------------------------------------- /dynamodb/dynamodb_test.go: -------------------------------------------------------------------------------- 1 | package dynamodb 2 | 3 | import ( 4 | "flag" 5 | "github.com/AdRoll/goamz/aws" 6 | "gopkg.in/check.v1" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | const TIMEOUT = 3 * time.Minute 12 | 13 | var amazon = flag.Bool("amazon", false, "Enable tests against dynamodb") 14 | var local = flag.Bool("local", true, "Use DynamoDB local on 8080 instead of real server on us-east.") 15 | 16 | var dynamodb_region aws.Region 17 | var dynamodb_auth aws.Auth 18 | 19 | type DynamoDBTest struct { 20 | server *Server 21 | aws.Region // Exports Region 22 | TableDescriptionT TableDescriptionT 23 | table *Table 24 | } 25 | 26 | // Delete all items in the table 27 | func (s *DynamoDBTest) TearDownTest(c *check.C) { 28 | pk, err := s.TableDescriptionT.BuildPrimaryKey() 29 | if err != nil { 30 | c.Fatal(err) 31 | } 32 | 33 | attrs, err := s.table.Scan(nil) 34 | if err != nil { 35 | c.Fatal(err) 36 | } 37 | for _, a := range attrs { 38 | key := &Key{ 39 | HashKey: a[pk.KeyAttribute.Name].Value, 40 | } 41 | if pk.HasRange() { 42 | key.RangeKey = a[pk.RangeAttribute.Name].Value 43 | } 44 | if ok, err := s.table.DeleteItem(key); !ok { 45 | c.Fatal(err) 46 | } 47 | } 48 | } 49 | 50 | func (s *DynamoDBTest) TearDownSuite(c *check.C) { 51 | // return immediately in the case of calling c.Skip() in SetUpSuite() 52 | if s.server == nil { 53 | return 54 | } 55 | 56 | // check whether the table exists 57 | if tables, err := s.server.ListTables(); err != nil { 58 | c.Fatal(err) 59 | } else { 60 | if !findTableByName(tables, s.TableDescriptionT.TableName) { 61 | return 62 | } 63 | } 64 | 65 | // Delete the table and wait 66 | if _, err := s.server.DeleteTable(s.TableDescriptionT); err != nil { 67 | c.Fatal(err) 68 | } 69 | 70 | done := make(chan bool) 71 | timeout := time.After(TIMEOUT) 72 | go func() { 73 | for { 74 | select { 75 | case <-done: 76 | return 77 | default: 78 | tables, err := s.server.ListTables() 79 | if err != nil { 80 | c.Fatal(err) 81 | } 82 | if findTableByName(tables, s.TableDescriptionT.TableName) { 83 | time.Sleep(5 * time.Second) 84 | } else { 85 | done <- true 86 | return 87 | } 88 | } 89 | } 90 | }() 91 | select { 92 | case <-done: 93 | break 94 | case <-timeout: 95 | c.Error("Expect the table to be deleted but timed out") 96 | close(done) 97 | } 98 | } 99 | 100 | func (s *DynamoDBTest) WaitUntilStatus(c *check.C, status string) { 101 | // We should wait until the table is in specified status because a real DynamoDB has some delay for ready 102 | done := make(chan bool) 103 | timeout := time.After(TIMEOUT) 104 | go func() { 105 | for { 106 | select { 107 | case <-done: 108 | return 109 | default: 110 | desc, err := s.table.DescribeTable() 111 | if err != nil { 112 | c.Fatal(err) 113 | } 114 | if desc.TableStatus == status { 115 | done <- true 116 | return 117 | } 118 | time.Sleep(5 * time.Second) 119 | } 120 | } 121 | }() 122 | select { 123 | case <-done: 124 | break 125 | case <-timeout: 126 | c.Errorf("Expect a status to be %s, but timed out", status) 127 | close(done) 128 | } 129 | } 130 | 131 | func setUpAuth(c *check.C) { 132 | if !*amazon { 133 | c.Skip("Test against amazon not enabled.") 134 | } 135 | if *local { 136 | c.Log("Using local server") 137 | dynamodb_region = aws.Region{DynamoDBEndpoint: "http://127.0.0.1:8000"} 138 | dynamodb_auth = aws.Auth{AccessKey: "DUMMY_KEY", SecretKey: "DUMMY_SECRET"} 139 | } else { 140 | c.Log("Using REAL AMAZON SERVER") 141 | dynamodb_region = aws.USWest2 142 | auth, err := aws.EnvAuth() 143 | if err != nil { 144 | c.Fatal(err) 145 | } 146 | dynamodb_auth = auth 147 | } 148 | } 149 | 150 | func findTableByName(tables []string, name string) bool { 151 | for _, t := range tables { 152 | if t == name { 153 | return true 154 | } 155 | } 156 | return false 157 | } 158 | 159 | func Test(t *testing.T) { 160 | check.TestingT(t) 161 | } 162 | -------------------------------------------------------------------------------- /dynamodb/query.go: -------------------------------------------------------------------------------- 1 | package dynamodb 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | simplejson "github.com/bitly/go-simplejson" 8 | ) 9 | 10 | type Query interface { 11 | Marshal() ([]byte, error) 12 | } 13 | 14 | type ScanQuery interface { 15 | Query 16 | AddExclusiveStartKey(key StartKey) error 17 | AddExclusiveStartTableName(table string) error 18 | } 19 | 20 | func (t *Table) Query(attributeComparisons []AttributeComparison) ([]map[string]*Attribute, error) { 21 | q := NewQuery(t) 22 | q.AddKeyConditions(attributeComparisons) 23 | return RunQuery(q, t) 24 | } 25 | 26 | func (t *Table) QueryOnIndex(attributeComparisons []AttributeComparison, indexName string) ([]map[string]*Attribute, error) { 27 | q := NewQuery(t) 28 | q.AddKeyConditions(attributeComparisons) 29 | q.AddIndex(indexName) 30 | return RunQuery(q, t) 31 | } 32 | 33 | func (t *Table) LimitedQuery(attributeComparisons []AttributeComparison, limit int64) ([]map[string]*Attribute, error) { 34 | q := NewQuery(t) 35 | q.AddKeyConditions(attributeComparisons) 36 | q.AddLimit(limit) 37 | return RunQuery(q, t) 38 | } 39 | 40 | func (t *Table) LimitedQueryOnIndex(attributeComparisons []AttributeComparison, indexName string, limit int64) ([]map[string]*Attribute, error) { 41 | q := NewQuery(t) 42 | q.AddKeyConditions(attributeComparisons) 43 | q.AddIndex(indexName) 44 | q.AddLimit(limit) 45 | return RunQuery(q, t) 46 | } 47 | 48 | func (t *Table) CountQuery(attributeComparisons []AttributeComparison) (int64, error) { 49 | q := NewQuery(t) 50 | q.AddKeyConditions(attributeComparisons) 51 | q.AddSelect("COUNT") 52 | jsonResponse, err := t.Server.queryServer(target("Query"), q) 53 | if err != nil { 54 | return 0, err 55 | } 56 | json, err := simplejson.NewJson(jsonResponse) 57 | if err != nil { 58 | return 0, err 59 | } 60 | 61 | itemCount, err := json.Get("Count").Int64() 62 | if err != nil { 63 | return 0, err 64 | } 65 | 66 | return itemCount, nil 67 | } 68 | 69 | func (t *Table) QueryTable(q Query) ([]map[string]*Attribute, StartKey, error) { 70 | jsonResponse, err := t.Server.queryServer(target("Query"), q) 71 | if err != nil { 72 | return nil, nil, err 73 | } 74 | 75 | json, err := simplejson.NewJson(jsonResponse) 76 | if err != nil { 77 | return nil, nil, err 78 | } 79 | 80 | itemCount, err := json.Get("Count").Int() 81 | if err != nil { 82 | message := fmt.Sprintf("Unexpected response %s", jsonResponse) 83 | return nil, nil, errors.New(message) 84 | } 85 | 86 | results := make([]map[string]*Attribute, itemCount) 87 | 88 | for i, _ := range results { 89 | item, err := json.Get("Items").GetIndex(i).Map() 90 | if err != nil { 91 | message := fmt.Sprintf("Unexpected response %s", jsonResponse) 92 | return nil, nil, errors.New(message) 93 | } 94 | results[i] = parseAttributes(item) 95 | } 96 | 97 | var lastEvaluatedKey StartKey 98 | if lastKeyMap := json.Get("LastEvaluatedKey").MustMap(); lastKeyMap != nil { 99 | lastEvaluatedKey = lastKeyMap 100 | } 101 | 102 | return results, lastEvaluatedKey, nil 103 | 104 | } 105 | 106 | func (t *Table) QueryCallbackIterator(attributeComparisons []AttributeComparison, cb func(map[string]*Attribute) error) error { 107 | q := NewQuery(t) 108 | q.AddKeyConditions(attributeComparisons) 109 | return t.QueryTableCallbackIterator(q, cb) 110 | } 111 | 112 | func (t *Table) QueryOnIndexCallbackIterator(attributeComparisons []AttributeComparison, indexName string, cb func(map[string]*Attribute) error) error { 113 | q := NewQuery(t) 114 | q.AddKeyConditions(attributeComparisons) 115 | q.AddIndex(indexName) 116 | return t.QueryTableCallbackIterator(q, cb) 117 | } 118 | 119 | func (t *Table) LimitedQueryCallbackIterator(attributeComparisons []AttributeComparison, limit int64, cb func(map[string]*Attribute) error) error { 120 | q := NewQuery(t) 121 | q.AddKeyConditions(attributeComparisons) 122 | q.AddLimit(limit) 123 | return t.QueryTableCallbackIterator(q, cb) 124 | } 125 | 126 | func (t *Table) LimitedQueryOnIndexCallbackIterator(attributeComparisons []AttributeComparison, indexName string, limit int64, cb func(map[string]*Attribute) error) error { 127 | q := NewQuery(t) 128 | q.AddKeyConditions(attributeComparisons) 129 | q.AddIndex(indexName) 130 | q.AddLimit(limit) 131 | return t.QueryTableCallbackIterator(q, cb) 132 | } 133 | 134 | func (t *Table) QueryTableCallbackIterator(query ScanQuery, cb func(map[string]*Attribute) error) error { 135 | for { 136 | results, lastEvaluatedKey, err := t.QueryTable(query) 137 | if err != nil { 138 | return err 139 | } 140 | for _, item := range results { 141 | if err := cb(item); err != nil { 142 | return err 143 | } 144 | } 145 | 146 | if lastEvaluatedKey == nil { 147 | break 148 | } 149 | query.AddExclusiveStartKey(lastEvaluatedKey) 150 | } 151 | 152 | return nil 153 | } 154 | 155 | func RunQuery(q Query, t *Table) ([]map[string]*Attribute, error) { 156 | 157 | result, _, err := t.QueryTable(q) 158 | 159 | if err != nil { 160 | return nil, err 161 | 162 | } 163 | 164 | return result, err 165 | 166 | } 167 | -------------------------------------------------------------------------------- /dynamodb/retry_test.go: -------------------------------------------------------------------------------- 1 | package dynamodb 2 | 3 | import ( 4 | "encoding/json" 5 | "gopkg.in/check.v1" 6 | "io" 7 | "io/ioutil" 8 | "net/http" 9 | "net/http/httptest" 10 | "strings" 11 | "time" 12 | 13 | "github.com/AdRoll/goamz/aws" 14 | ) 15 | 16 | type RetrySuite struct { 17 | NumCallsToFail int 18 | ExpectError bool 19 | ErrorType string 20 | ErrorStatusCode int 21 | TableDescriptionT TableDescriptionT 22 | DynamoDBTest 23 | } 24 | 25 | func (s *RetrySuite) SetUpSuite(c *check.C) { 26 | setUpAuth(c) 27 | s.TableDescriptionT = TableDescriptionT{ 28 | TableName: "DynamoDBTestMyTable", 29 | AttributeDefinitions: []AttributeDefinitionT{ 30 | AttributeDefinitionT{"TestHashKey", "S"}, 31 | }, 32 | KeySchema: []KeySchemaT{ 33 | KeySchemaT{"TestHashKey", "HASH"}, 34 | }, 35 | ProvisionedThroughput: ProvisionedThroughputT{ 36 | ReadCapacityUnits: 1, 37 | WriteCapacityUnits: 1, 38 | }, 39 | } 40 | s.DynamoDBTest.TableDescriptionT = s.TableDescriptionT 41 | s.server = New(dynamodb_auth, dynamodb_region) 42 | pk, err := s.TableDescriptionT.BuildPrimaryKey() 43 | if err != nil { 44 | c.Skip(err.Error()) 45 | } 46 | s.table = s.server.NewTable(s.TableDescriptionT.TableName, pk) 47 | 48 | // Cleanup 49 | s.TearDownSuite(c) 50 | _, err = s.server.CreateTable(s.TableDescriptionT) 51 | if err != nil { 52 | c.Fatal(err) 53 | } 54 | s.WaitUntilStatus(c, "ACTIVE") 55 | } 56 | 57 | // Expect to succeed after a throttling exception. 58 | var retry_suite_1 = &RetrySuite{ 59 | NumCallsToFail: 1, 60 | ExpectError: false, 61 | ErrorType: "com.amazonaws.dynamodb.v20120810#ProvisionedThroughputExceededException", 62 | ErrorStatusCode: 400, 63 | } 64 | 65 | // Expect to succeed after 2 500 responses. 66 | var retry_suite_2 = &RetrySuite{ 67 | NumCallsToFail: 2, 68 | ExpectError: false, 69 | ErrorType: "not a reason to retry", 70 | ErrorStatusCode: 500, 71 | } 72 | 73 | // Expect to fail after exceeding max retries. 74 | var retry_suite_3 = &RetrySuite{ 75 | NumCallsToFail: 3, 76 | ExpectError: true, // retry twice 77 | ErrorType: "not a reason to retry", 78 | ErrorStatusCode: 500, 79 | } 80 | 81 | // Expect to fail due to not having a reason to retry. 82 | var retry_suite_4 = &RetrySuite{ 83 | NumCallsToFail: 1, 84 | ExpectError: true, 85 | ErrorType: "not a reason to retry", 86 | ErrorStatusCode: 400, 87 | } 88 | 89 | var _ = check.Suite(retry_suite_1) 90 | var _ = check.Suite(retry_suite_2) 91 | var _ = check.Suite(retry_suite_3) 92 | var _ = check.Suite(retry_suite_4) 93 | 94 | type retryPolicy struct { 95 | numCalls int 96 | } 97 | 98 | func (w *retryPolicy) ShouldRetry(target string, r *http.Response, err error, numRetries int) bool { 99 | w.numCalls++ 100 | dynamodbPolicy := aws.DynamoDBRetryPolicy{} 101 | if !dynamodbPolicy.ShouldRetry(target, r, err, numRetries) { 102 | return false 103 | } 104 | return w.numCalls < 3 105 | } 106 | 107 | func (w *retryPolicy) Delay(target string, r *http.Response, err error, numRetries int) time.Duration { 108 | return 0 109 | } 110 | 111 | func (s *RetrySuite) TestRetryPolicy(c *check.C) { 112 | // Save off the real endpoint, and then point it at a local proxy. 113 | endpoint := s.table.Server.Region.DynamoDBEndpoint 114 | policy := s.table.Server.RetryPolicy 115 | defer func() { 116 | s.table.Server.Region.DynamoDBEndpoint = endpoint 117 | s.table.Server.RetryPolicy = policy 118 | }() 119 | numCalls := 0 120 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 121 | numCalls++ 122 | // Fake a failure the requested amount of times. 123 | if numCalls <= s.NumCallsToFail { 124 | b, _ := json.Marshal(map[string]interface{}{ 125 | "__type": s.ErrorType, 126 | "Code": "blah", 127 | }) 128 | w.WriteHeader(s.ErrorStatusCode) 129 | io.WriteString(w, string(b)) 130 | return 131 | } 132 | 133 | // Otherwise, proxy to actual DynamoDB endpoint. We reformat the request 134 | // with the same content. 135 | body, err := ioutil.ReadAll(r.Body) 136 | if err != nil { 137 | c.Fatal(err) 138 | } 139 | newr, err := http.NewRequest("POST", endpoint+"/", strings.NewReader(string(body))) 140 | headersToKeep := map[string]bool{ 141 | "Content-Type": true, 142 | "X-Amz-Date": true, 143 | "X-Amz-Target": true, 144 | "X-Amz-Security-Token": true, 145 | } 146 | for h, _ := range r.Header { 147 | if _, ok := headersToKeep[h]; ok { 148 | newr.Header.Set(h, r.Header.Get(h)) 149 | } 150 | } 151 | 152 | signer := aws.NewV4Signer(s.table.Server.Auth, "dynamodb", s.table.Server.Region) 153 | signer.Sign(newr) 154 | 155 | resp, err := http.DefaultClient.Do(newr) 156 | if err != nil { 157 | c.Fatal(err) 158 | } 159 | body, err = ioutil.ReadAll(resp.Body) 160 | defer resp.Body.Close() 161 | if err != nil { 162 | c.Fatal(err) 163 | } 164 | w.WriteHeader(resp.StatusCode) 165 | io.WriteString(w, string(body)) 166 | })) 167 | defer server.Close() 168 | rp := &retryPolicy{} 169 | s.table.Server.RetryPolicy = rp 170 | s.table.Server.Region.DynamoDBEndpoint = server.URL 171 | 172 | // Now make the request. 173 | k := &Key{HashKey: "NewHashKeyVal"} 174 | in := map[string]interface{}{ 175 | "Attr1": "Attr1Val", 176 | "Attr2": 12, 177 | } 178 | err := s.table.PutDocument(k, in) 179 | if s.ExpectError { 180 | if err == nil { 181 | c.Fatalf("Expected error") 182 | } 183 | } else { 184 | if err != nil { 185 | c.Fatal(err) 186 | } 187 | } 188 | if rp.numCalls != s.NumCallsToFail { 189 | c.Fatalf("Expected %d failed calls, saw %d", s.NumCallsToFail, rp.numCalls) 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /dynamodb/scan.go: -------------------------------------------------------------------------------- 1 | package dynamodb 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | 8 | simplejson "github.com/bitly/go-simplejson" 9 | ) 10 | 11 | func (t *Table) FetchPartialResults(query ScanQuery) ([]map[string]*Attribute, StartKey, error) { 12 | jsonResponse, err := t.Server.queryServer(target("Scan"), query) 13 | if err != nil { 14 | return nil, nil, err 15 | } 16 | 17 | json, err := simplejson.NewJson(jsonResponse) 18 | if err != nil { 19 | return nil, nil, err 20 | } 21 | 22 | itemCount, err := json.Get("Count").Int() 23 | if err != nil { 24 | message := fmt.Sprintf("Unexpected response %s", jsonResponse) 25 | return nil, nil, errors.New(message) 26 | } 27 | 28 | results := make([]map[string]*Attribute, itemCount) 29 | for i, _ := range results { 30 | item, err := json.Get("Items").GetIndex(i).Map() 31 | if err != nil { 32 | message := fmt.Sprintf("Unexpected response %s", jsonResponse) 33 | return nil, nil, errors.New(message) 34 | } 35 | results[i] = parseAttributes(item) 36 | } 37 | 38 | var lastEvaluatedKey StartKey 39 | if lastKeyMap := json.Get("LastEvaluatedKey").MustMap(); lastKeyMap != nil { 40 | lastEvaluatedKey = lastKeyMap 41 | } 42 | 43 | return results, lastEvaluatedKey, nil 44 | } 45 | 46 | func (t *Table) FetchResultCallbackIterator(query ScanQuery, cb func(map[string]*Attribute) error) error { 47 | for { 48 | results, lastEvaluatedKey, err := t.FetchPartialResults(query) 49 | if err != nil { 50 | return err 51 | } 52 | for _, item := range results { 53 | if err := cb(item); err != nil { 54 | return err 55 | } 56 | } 57 | 58 | if lastEvaluatedKey == nil { 59 | break 60 | } 61 | query.AddExclusiveStartKey(lastEvaluatedKey) 62 | } 63 | 64 | return nil 65 | } 66 | 67 | func (t *Table) ScanPartial(attributeComparisons []AttributeComparison, exclusiveStartKey StartKey) ([]map[string]*Attribute, StartKey, error) { 68 | return t.ParallelScanPartialLimit(attributeComparisons, exclusiveStartKey, 0, 0, 0) 69 | } 70 | 71 | func (t *Table) ScanPartialLimit(attributeComparisons []AttributeComparison, exclusiveStartKey StartKey, limit int64) ([]map[string]*Attribute, StartKey, error) { 72 | return t.ParallelScanPartialLimit(attributeComparisons, exclusiveStartKey, 0, 0, limit) 73 | } 74 | 75 | func (t *Table) ParallelScanPartial(attributeComparisons []AttributeComparison, exclusiveStartKey StartKey, segment, totalSegments int) ([]map[string]*Attribute, StartKey, error) { 76 | return t.ParallelScanPartialLimit(attributeComparisons, exclusiveStartKey, segment, totalSegments, 0) 77 | } 78 | 79 | func (t *Table) ParallelScanPartialLimit(attributeComparisons []AttributeComparison, exclusiveStartKey StartKey, segment, totalSegments int, limit int64) ([]map[string]*Attribute, StartKey, error) { 80 | q := NewQuery(t) 81 | q.AddScanFilter(attributeComparisons) 82 | if exclusiveStartKey != nil { 83 | q.AddExclusiveStartKey(exclusiveStartKey) 84 | } 85 | if totalSegments > 0 { 86 | q.AddParallelScanConfiguration(segment, totalSegments) 87 | } 88 | if limit > 0 { 89 | q.AddLimit(limit) 90 | } 91 | return t.FetchPartialResults(q) 92 | } 93 | 94 | func (t *Table) FetchResults(query ScanQuery) ([]map[string]*Attribute, error) { 95 | results, _, err := t.FetchPartialResults(query) 96 | return results, err 97 | } 98 | 99 | func (t *Table) Scan(attributeComparisons []AttributeComparison) ([]map[string]*Attribute, error) { 100 | q := NewQuery(t) 101 | q.AddScanFilter(attributeComparisons) 102 | return t.FetchResults(q) 103 | } 104 | 105 | func (t *Table) ParallelScan(attributeComparisons []AttributeComparison, segment int, totalSegments int) ([]map[string]*Attribute, error) { 106 | q := NewQuery(t) 107 | q.AddScanFilter(attributeComparisons) 108 | q.AddParallelScanConfiguration(segment, totalSegments) 109 | return t.FetchResults(q) 110 | } 111 | 112 | func (t *Table) ScanCallbackIterator(attributeComparisons []AttributeComparison, cb func(map[string]*Attribute) error) error { 113 | q := NewQuery(t) 114 | q.AddScanFilter(attributeComparisons) 115 | return t.FetchResultCallbackIterator(q, cb) 116 | } 117 | 118 | func parseKey(t *Table, s map[string]interface{}) *Key { 119 | k := &Key{} 120 | 121 | hk := t.Key.KeyAttribute 122 | if v, ok := s[hk.Name].(map[string]interface{}); ok { 123 | switch hk.Type { 124 | case TYPE_NUMBER, TYPE_STRING, TYPE_BINARY: 125 | if key, ok := v[hk.Type].(string); ok { 126 | k.HashKey = key 127 | } else { 128 | log.Printf("type assertion to string failed for : %s\n", hk.Type) 129 | return nil 130 | } 131 | default: 132 | log.Printf("invalid primary key hash type : %s\n", hk.Type) 133 | return nil 134 | } 135 | } else { 136 | log.Printf("type assertion to map[string]interface{} failed for : %s\n", hk.Name) 137 | return nil 138 | } 139 | 140 | if t.Key.HasRange() { 141 | rk := t.Key.RangeAttribute 142 | if v, ok := s[rk.Name].(map[string]interface{}); ok { 143 | switch rk.Type { 144 | case TYPE_NUMBER, TYPE_STRING, TYPE_BINARY: 145 | if key, ok := v[rk.Type].(string); ok { 146 | k.RangeKey = key 147 | } else { 148 | log.Printf("type assertion to string failed for : %s\n", rk.Type) 149 | return nil 150 | } 151 | default: 152 | log.Printf("invalid primary key range type : %s\n", rk.Type) 153 | return nil 154 | } 155 | } else { 156 | log.Printf("type assertion to map[string]interface{} failed for : %s\n", rk.Name) 157 | return nil 158 | } 159 | } 160 | 161 | return k 162 | } 163 | -------------------------------------------------------------------------------- /dynamodb/table_test.go: -------------------------------------------------------------------------------- 1 | package dynamodb 2 | 3 | import ( 4 | "fmt" 5 | "gopkg.in/check.v1" 6 | ) 7 | 8 | type TableSuite struct { 9 | TableDescriptionT TableDescriptionT 10 | DynamoDBTest 11 | } 12 | 13 | func (s *TableSuite) SetUpSuite(c *check.C) { 14 | setUpAuth(c) 15 | s.DynamoDBTest.TableDescriptionT = s.TableDescriptionT 16 | s.server = New(dynamodb_auth, dynamodb_region) 17 | pk, err := s.TableDescriptionT.BuildPrimaryKey() 18 | if err != nil { 19 | c.Skip(err.Error()) 20 | } 21 | s.table = s.server.NewTable(s.TableDescriptionT.TableName, pk) 22 | 23 | // Cleanup 24 | s.TearDownSuite(c) 25 | } 26 | 27 | var table_suite = &TableSuite{ 28 | TableDescriptionT: TableDescriptionT{ 29 | TableName: "DynamoDBTestMyTable", 30 | AttributeDefinitions: []AttributeDefinitionT{ 31 | AttributeDefinitionT{"TestHashKey", "S"}, 32 | AttributeDefinitionT{"TestRangeKey", "N"}, 33 | }, 34 | KeySchema: []KeySchemaT{ 35 | KeySchemaT{"TestHashKey", "HASH"}, 36 | KeySchemaT{"TestRangeKey", "RANGE"}, 37 | }, 38 | ProvisionedThroughput: ProvisionedThroughputT{ 39 | ReadCapacityUnits: 1, 40 | WriteCapacityUnits: 1, 41 | }, 42 | }, 43 | } 44 | 45 | var table_suite_gsi = &TableSuite{ 46 | TableDescriptionT: TableDescriptionT{ 47 | TableName: "DynamoDBTestMyTable2", 48 | AttributeDefinitions: []AttributeDefinitionT{ 49 | AttributeDefinitionT{"UserId", "S"}, 50 | AttributeDefinitionT{"OSType", "S"}, 51 | AttributeDefinitionT{"IMSI", "S"}, 52 | }, 53 | KeySchema: []KeySchemaT{ 54 | KeySchemaT{"UserId", "HASH"}, 55 | KeySchemaT{"OSType", "RANGE"}, 56 | }, 57 | ProvisionedThroughput: ProvisionedThroughputT{ 58 | ReadCapacityUnits: 1, 59 | WriteCapacityUnits: 1, 60 | }, 61 | GlobalSecondaryIndexes: []GlobalSecondaryIndexT{ 62 | GlobalSecondaryIndexT{ 63 | IndexName: "IMSIIndex", 64 | KeySchema: []KeySchemaT{ 65 | KeySchemaT{"IMSI", "HASH"}, 66 | }, 67 | Projection: ProjectionT{ 68 | ProjectionType: "KEYS_ONLY", 69 | }, 70 | ProvisionedThroughput: ProvisionedThroughputT{ 71 | ReadCapacityUnits: 1, 72 | WriteCapacityUnits: 1, 73 | }, 74 | }, 75 | }, 76 | }, 77 | } 78 | 79 | func (s *TableSuite) TestCreateListTableGsi(c *check.C) { 80 | status, err := s.server.CreateTable(s.TableDescriptionT) 81 | if err != nil { 82 | fmt.Printf("err %#v", err) 83 | c.Fatal(err) 84 | } 85 | if status != "ACTIVE" && status != "CREATING" { 86 | c.Error("Expect status to be ACTIVE or CREATING") 87 | } 88 | 89 | s.WaitUntilStatus(c, "ACTIVE") 90 | 91 | tables, err := s.server.ListTables() 92 | if err != nil { 93 | c.Fatal(err) 94 | } 95 | c.Check(len(tables), check.Not(check.Equals), 0) 96 | c.Check(findTableByName(tables, s.TableDescriptionT.TableName), check.Equals, true) 97 | } 98 | 99 | var _ = check.Suite(table_suite) 100 | var _ = check.Suite(table_suite_gsi) 101 | -------------------------------------------------------------------------------- /ec2/ec2test/filter.go: -------------------------------------------------------------------------------- 1 | package ec2test 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "strings" 7 | ) 8 | 9 | // filter holds an ec2 filter. A filter maps an attribute to a set of 10 | // possible values for that attribute. For an item to pass through the 11 | // filter, every attribute of the item mentioned in the filter must match 12 | // at least one of its given values. 13 | type filter map[string][]string 14 | 15 | // newFilter creates a new filter from the Filter fields in the url form. 16 | // 17 | // The filtering is specified through a map of name=>values, where the 18 | // name is a well-defined key identifying the data to be matched, 19 | // and the list of values holds the possible values the filtered 20 | // item can take for the key to be included in the 21 | // result set. For example: 22 | // 23 | // Filter.1.Name=instance-type 24 | // Filter.1.Value.1=m1.small 25 | // Filter.1.Value.2=m1.large 26 | // 27 | func newFilter(form url.Values) filter { 28 | // TODO return an error if the fields are not well formed? 29 | names := make(map[int]string) 30 | values := make(map[int][]string) 31 | maxId := 0 32 | for name, fvalues := range form { 33 | var rest string 34 | var id int 35 | if x, _ := fmt.Sscanf(name, "Filter.%d.%s", &id, &rest); x != 2 { 36 | continue 37 | } 38 | if id > maxId { 39 | maxId = id 40 | } 41 | if rest == "Name" { 42 | names[id] = fvalues[0] 43 | continue 44 | } 45 | if !strings.HasPrefix(rest, "Value.") { 46 | continue 47 | } 48 | values[id] = append(values[id], fvalues[0]) 49 | } 50 | 51 | f := make(filter) 52 | for id, name := range names { 53 | f[name] = values[id] 54 | } 55 | return f 56 | } 57 | 58 | func notDigit(r rune) bool { 59 | return r < '0' || r > '9' 60 | } 61 | 62 | // filterable represents an object that can be passed through a filter. 63 | type filterable interface { 64 | // matchAttr returns true if given attribute of the 65 | // object matches value. It returns an error if the 66 | // attribute is not recognised or the value is malformed. 67 | matchAttr(attr, value string) (bool, error) 68 | } 69 | 70 | // ok returns true if x passes through the filter. 71 | func (f filter) ok(x filterable) (bool, error) { 72 | next: 73 | for a, vs := range f { 74 | for _, v := range vs { 75 | if ok, err := x.matchAttr(a, v); ok { 76 | continue next 77 | } else if err != nil { 78 | return false, fmt.Errorf("bad attribute or value %q=%q for type %T: %v", a, v, x, err) 79 | } 80 | } 81 | return false, nil 82 | } 83 | return true, nil 84 | } 85 | -------------------------------------------------------------------------------- /ec2/export_test.go: -------------------------------------------------------------------------------- 1 | package ec2 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | func fixedTime() time.Time { 8 | return time.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC) 9 | } 10 | 11 | func FakeTime(fakeIt bool) { 12 | if fakeIt { 13 | timeNow = fixedTime 14 | } else { 15 | timeNow = time.Now 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ecommerce/ecommerce.go: -------------------------------------------------------------------------------- 1 | package ecommerce 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/AdRoll/goamz/aws" 7 | ) 8 | 9 | // ProductAdvertising provides methods for querying the product advertising API 10 | type ProductAdvertising struct { 11 | service aws.Service 12 | associateTag string 13 | } 14 | 15 | // New creates a new ProductAdvertising client 16 | func New(auth aws.Auth, associateTag string) (p *ProductAdvertising, err error) { 17 | serviceInfo := aws.ServiceInfo{Endpoint: "https://webservices.amazon.com", Signer: aws.V2Signature} 18 | if service, err := aws.NewService(auth, serviceInfo); err == nil { 19 | p = &ProductAdvertising{*service, associateTag} 20 | } 21 | return 22 | } 23 | 24 | // PerformOperation is the main method used for interacting with the product advertising API 25 | func (p *ProductAdvertising) PerformOperation(operation string, params map[string]string) (resp *http.Response, err error) { 26 | params["Operation"] = operation 27 | return p.query(params) 28 | } 29 | 30 | func (p *ProductAdvertising) query(params map[string]string) (resp *http.Response, err error) { 31 | params["Service"] = "AWSECommerceService" 32 | params["AssociateTag"] = p.associateTag 33 | return p.service.Query("GET", "/onca/xml", params) 34 | } 35 | -------------------------------------------------------------------------------- /elasticache/elasticache_test.go: -------------------------------------------------------------------------------- 1 | package elasticache 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/AdRoll/goamz/aws" 7 | "github.com/AdRoll/goamz/testutil" 8 | check "gopkg.in/check.v1" 9 | ) 10 | 11 | type S struct { 12 | elasticache *ElastiCache 13 | } 14 | 15 | var testServer = testutil.NewHTTPServer() 16 | 17 | func Test(t *testing.T) { 18 | check.TestingT(t) 19 | } 20 | 21 | func (s *S) SetUpSuite(c *check.C) { 22 | testServer.Start() 23 | auth := aws.Auth{AccessKey: "abc", SecretKey: "123"} 24 | s.elasticache = New(auth, aws.Region{ElastiCacheEndpoint: testServer.URL}) 25 | } 26 | 27 | func (s *S) TearDownTest(c *check.C) { 28 | testServer.Flush() 29 | } 30 | 31 | func (s *S) TestDescribeReplicationGroup(c *check.C) { 32 | testServer.Start() 33 | auth := aws.Auth{AccessKey: "abc", SecretKey: "123"} 34 | s.elasticache = New(auth, aws.Region{EC2Endpoint: aws.ServiceInfo{Endpoint: testServer.URL, Signer: aws.V2Signature}}) 35 | 36 | testServer.Response(200, nil, DescribeReplicationGroupsResponse) 37 | 38 | resp, err := s.elasticache.DescribeReplicationGroup("test") 39 | req := testServer.WaitRequest() 40 | 41 | c.Assert(err, check.IsNil) 42 | c.Assert(req.URL.Query().Get("Action"), check.Equals, "DescribeReplicationGroup") 43 | 44 | c.Assert(resp.Status, check.Equals, "available") 45 | } 46 | -------------------------------------------------------------------------------- /elasticache/responses_test.go: -------------------------------------------------------------------------------- 1 | package elasticache 2 | 3 | var DescribeReplicationGroupsResponse = ` 4 | 5 | 6 | 7 | 8 | example-test-001 9 | example-test-002 10 | example-test-003 11 | 12 | 13 | 14 | 0001 15 | 16 | 6379 17 |
example-test.4q8cbh.ng.0001.euw1.cache.amazonaws.com
18 |
19 | available 20 | 21 | 22 | example-test-001 23 | 24 | 6379 25 |
example-test-001.4q8cbh.0001.euw1.cache.amazonaws.com
26 |
27 | eu-west-1a 28 | 0001 29 | primary 30 |
31 | 32 | example-test-002 33 | 34 | 6379 35 |
example-test-002.4q8cbh.0001.euw1.cache.amazonaws.com
36 |
37 | eu-west-1b 38 | 0001 39 | replica 40 |
41 | 42 | example-test-003 43 | 44 | 6379 45 |
example-test-003.4q8cbh.0001.euw1.cache.amazonaws.com
46 |
47 | eu-west-1c 48 | 0001 49 | replica 50 |
51 |
52 |
53 |
54 | example-test 55 | available 56 | 57 | CH Doc API test 58 |
59 |
60 |
61 | 62 | a84997a2-8141-11e4-b193-ff2ad2b98e65 63 | 64 |
65 | ` 66 | -------------------------------------------------------------------------------- /elb/suite_test.go: -------------------------------------------------------------------------------- 1 | package elb_test 2 | 3 | import ( 4 | "fmt" 5 | "gopkg.in/check.v1" 6 | "net/http" 7 | "net/url" 8 | "os" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | func Test(t *testing.T) { 14 | check.TestingT(t) 15 | } 16 | 17 | type HTTPSuite struct{} 18 | 19 | var testServer = NewTestHTTPServer("http://localhost:4444", 5*time.Second) 20 | 21 | func (s *HTTPSuite) SetUpSuite(c *check.C) { 22 | testServer.Start() 23 | } 24 | 25 | func (s *HTTPSuite) TearDownTest(c *check.C) { 26 | testServer.FlushRequests() 27 | } 28 | 29 | type TestHTTPServer struct { 30 | URL string 31 | Timeout time.Duration 32 | started bool 33 | request chan *http.Request 34 | response chan *testResponse 35 | pending chan bool 36 | } 37 | 38 | type testResponse struct { 39 | Status int 40 | Headers map[string]string 41 | Body string 42 | } 43 | 44 | func NewTestHTTPServer(url string, timeout time.Duration) *TestHTTPServer { 45 | return &TestHTTPServer{URL: url, Timeout: timeout} 46 | } 47 | 48 | func (s *TestHTTPServer) Start() { 49 | if s.started { 50 | return 51 | } 52 | s.started = true 53 | 54 | s.request = make(chan *http.Request, 64) 55 | s.response = make(chan *testResponse, 64) 56 | s.pending = make(chan bool, 64) 57 | 58 | url, _ := url.Parse(s.URL) 59 | go http.ListenAndServe(url.Host, s) 60 | 61 | s.PrepareResponse(202, nil, "Nothing.") 62 | for { 63 | // Wait for it to be up. 64 | resp, err := http.Get(s.URL) 65 | if err == nil && resp.StatusCode == 202 { 66 | break 67 | } 68 | time.Sleep(1e8) 69 | } 70 | s.WaitRequest() // Consume dummy request. 71 | } 72 | 73 | // FlushRequests discards requests which were not yet consumed by WaitRequest. 74 | func (s *TestHTTPServer) FlushRequests() { 75 | for { 76 | select { 77 | case <-s.request: 78 | default: 79 | return 80 | } 81 | } 82 | } 83 | 84 | func (s *TestHTTPServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { 85 | s.request <- req 86 | var resp *testResponse 87 | select { 88 | case resp = <-s.response: 89 | case <-time.After(s.Timeout): 90 | fmt.Fprintf(os.Stderr, "ERROR: Timeout waiting for test to provide response\n") 91 | resp = &testResponse{500, nil, ""} 92 | } 93 | if resp.Headers != nil { 94 | h := w.Header() 95 | for k, v := range resp.Headers { 96 | h.Set(k, v) 97 | } 98 | } 99 | if resp.Status != 0 { 100 | w.WriteHeader(resp.Status) 101 | } 102 | w.Write([]byte(resp.Body)) 103 | } 104 | 105 | func (s *TestHTTPServer) WaitRequest() *http.Request { 106 | select { 107 | case req := <-s.request: 108 | req.ParseForm() 109 | return req 110 | case <-time.After(s.Timeout): 111 | panic("Timeout waiting for goamz request") 112 | } 113 | panic("unreached") 114 | } 115 | 116 | func (s *TestHTTPServer) PrepareResponse(status int, headers map[string]string, body string) { 117 | s.response <- &testResponse{status, headers, body} 118 | } 119 | -------------------------------------------------------------------------------- /exp/mturk/example_test.go: -------------------------------------------------------------------------------- 1 | package mturk_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/AdRoll/goamz/aws" 6 | "github.com/AdRoll/goamz/exp/mturk" 7 | ) 8 | 9 | var turk *mturk.MTurk 10 | 11 | func ExampleNew() { 12 | // These are your AWS tokens. Note that Turk do not support IAM. 13 | // So you'll have to use your main profile's tokens. 14 | var auth = aws.Auth{AccessKey: "", SecretKey: ""} 15 | turk = mturk.New(auth, true) // true to use sandbox mode 16 | } 17 | 18 | func Examplemturk_CreateHIT_withExternalQuestion() { 19 | question := mturk.ExternalQuestion{ 20 | ExternalURL: "http://www.amazon.com", 21 | FrameHeight: 200, 22 | } 23 | reward := mturk.Price{ 24 | Amount: "0.01", 25 | CurrencyCode: "USD", 26 | } 27 | 28 | hit, err := turk.CreateHIT("title", "description", question, reward, 30, 30, "key1,key2", 3, nil, "annotation") 29 | 30 | if err == nil { 31 | fmt.Println(hit) 32 | } 33 | } 34 | 35 | func Examplemturk_CreateHIT_withHTMLQuestion() { 36 | question := mturk.HTMLQuestion{ 37 | HTMLContent: mturk.HTMLContent{` 39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 | 47 |

What's up?

48 |

49 |

50 | 51 | 52 | 53 | ]]>`}, 54 | FrameHeight: 200, 55 | } 56 | reward := mturk.Price{ 57 | Amount: "0.01", 58 | CurrencyCode: "USD", 59 | } 60 | 61 | hit, err := turk.CreateHIT("title", "description", question, reward, 30, 30, "key1,key2", 3, nil, "") 62 | 63 | if err == nil { 64 | fmt.Println(hit) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /exp/mturk/export_test.go: -------------------------------------------------------------------------------- 1 | package mturk 2 | 3 | import ( 4 | "github.com/AdRoll/goamz/aws" 5 | ) 6 | 7 | func Sign(auth aws.Auth, service, method, timestamp string, params map[string]string) { 8 | sign(auth, service, method, timestamp, params) 9 | } 10 | -------------------------------------------------------------------------------- /exp/mturk/mturk_test.go: -------------------------------------------------------------------------------- 1 | package mturk_test 2 | 3 | import ( 4 | "github.com/AdRoll/goamz/aws" 5 | "github.com/AdRoll/goamz/exp/mturk" 6 | "github.com/AdRoll/goamz/testutil" 7 | "gopkg.in/check.v1" 8 | "net/url" 9 | "testing" 10 | ) 11 | 12 | func Test(t *testing.T) { 13 | check.TestingT(t) 14 | } 15 | 16 | var _ = check.Suite(&S{}) 17 | 18 | type S struct { 19 | mturk *mturk.MTurk 20 | } 21 | 22 | var testServer = testutil.NewHTTPServer() 23 | 24 | func (s *S) SetUpSuite(c *check.C) { 25 | testServer.Start() 26 | auth := aws.Auth{AccessKey: "abc", SecretKey: "123"} 27 | u, err := url.Parse(testServer.URL) 28 | if err != nil { 29 | panic(err.Error()) 30 | } 31 | 32 | s.mturk = &mturk.MTurk{ 33 | Auth: auth, 34 | URL: u, 35 | } 36 | } 37 | 38 | func (s *S) TearDownTest(c *check.C) { 39 | testServer.Flush() 40 | } 41 | 42 | func (s *S) TestCreateHITExternalQuestion(c *check.C) { 43 | testServer.Response(200, nil, BasicHitResponse) 44 | 45 | question := mturk.ExternalQuestion{ 46 | ExternalURL: "http://www.amazon.com", 47 | FrameHeight: 200, 48 | } 49 | reward := mturk.Price{ 50 | Amount: "0.01", 51 | CurrencyCode: "USD", 52 | } 53 | hit, err := s.mturk.CreateHIT("title", "description", question, reward, 1, 2, "key1,key2", 3, nil, "annotation") 54 | 55 | testServer.WaitRequest() 56 | 57 | c.Assert(err, check.IsNil) 58 | c.Assert(hit, check.NotNil) 59 | 60 | c.Assert(hit.HITId, check.Equals, "28J4IXKO2L927XKJTHO34OCDNASCDW") 61 | c.Assert(hit.HITTypeId, check.Equals, "2XZ7D1X3V0FKQVW7LU51S7PKKGFKDF") 62 | } 63 | 64 | func (s *S) TestCreateHITHTMLQuestion(c *check.C) { 65 | testServer.Response(200, nil, BasicHitResponse) 66 | 67 | question := mturk.HTMLQuestion{ 68 | HTMLContent: mturk.HTMLContent{` 70 | 71 | 72 | 73 | 74 | 75 | 76 |
77 | 78 |

What's up?

79 |

80 |

81 | 82 | 83 | 84 | ]]>`}, 85 | FrameHeight: 200, 86 | } 87 | reward := mturk.Price{ 88 | Amount: "0.01", 89 | CurrencyCode: "USD", 90 | } 91 | hit, err := s.mturk.CreateHIT("title", "description", question, reward, 1, 2, "key1,key2", 3, nil, "annotation") 92 | 93 | testServer.WaitRequest() 94 | 95 | c.Assert(err, check.IsNil) 96 | c.Assert(hit, check.NotNil) 97 | 98 | c.Assert(hit.HITId, check.Equals, "28J4IXKO2L927XKJTHO34OCDNASCDW") 99 | c.Assert(hit.HITTypeId, check.Equals, "2XZ7D1X3V0FKQVW7LU51S7PKKGFKDF") 100 | } 101 | 102 | func (s *S) TestSearchHITs(c *check.C) { 103 | testServer.Response(200, nil, SearchHITResponse) 104 | 105 | hitResult, err := s.mturk.SearchHITs() 106 | 107 | c.Assert(err, check.IsNil) 108 | c.Assert(hitResult, check.NotNil) 109 | 110 | c.Assert(hitResult.NumResults, check.Equals, uint(1)) 111 | c.Assert(hitResult.PageNumber, check.Equals, uint(1)) 112 | c.Assert(hitResult.TotalNumResults, check.Equals, uint(1)) 113 | 114 | c.Assert(len(hitResult.HITs), check.Equals, 1) 115 | c.Assert(hitResult.HITs[0].HITId, check.Equals, "2BU26DG67D1XTE823B3OQ2JF2XWF83") 116 | c.Assert(hitResult.HITs[0].HITTypeId, check.Equals, "22OWJ5OPB0YV6IGL5727KP9U38P5XR") 117 | c.Assert(hitResult.HITs[0].CreationTime, check.Equals, "2011-12-28T19:56:20Z") 118 | c.Assert(hitResult.HITs[0].Title, check.Equals, "test hit") 119 | c.Assert(hitResult.HITs[0].Description, check.Equals, "please disregard, testing only") 120 | c.Assert(hitResult.HITs[0].HITStatus, check.Equals, "Reviewable") 121 | c.Assert(hitResult.HITs[0].MaxAssignments, check.Equals, uint(1)) 122 | c.Assert(hitResult.HITs[0].Reward.Amount, check.Equals, "0.01") 123 | c.Assert(hitResult.HITs[0].Reward.CurrencyCode, check.Equals, "USD") 124 | c.Assert(hitResult.HITs[0].AutoApprovalDelayInSeconds, check.Equals, uint(2592000)) 125 | c.Assert(hitResult.HITs[0].AssignmentDurationInSeconds, check.Equals, uint(30)) 126 | c.Assert(hitResult.HITs[0].NumberOfAssignmentsPending, check.Equals, uint(0)) 127 | c.Assert(hitResult.HITs[0].NumberOfAssignmentsAvailable, check.Equals, uint(1)) 128 | c.Assert(hitResult.HITs[0].NumberOfAssignmentsCompleted, check.Equals, uint(0)) 129 | } 130 | 131 | func (s *S) TestGetAssignmentsForHIT_NoAnswer(c *check.C) { 132 | testServer.Response(200, nil, GetAssignmentsForHITNoAnswerResponse) 133 | 134 | assignments, err := s.mturk.GetAssignmentsForHIT("emptyassignment") 135 | 136 | testServer.WaitRequest() 137 | 138 | c.Assert(err, check.IsNil) 139 | c.Assert(assignments, check.IsNil) 140 | } 141 | 142 | func (s *S) TestGetAssignmentsForHIT_Answer(c *check.C) { 143 | testServer.Response(200, nil, GetAssignmentsForHITAnswerResponse) 144 | 145 | assignment, err := s.mturk.GetAssignmentsForHIT("emptyassignment") 146 | 147 | testServer.WaitRequest() 148 | 149 | c.Assert(err, check.IsNil) 150 | c.Assert(assignment, check.NotNil) 151 | 152 | c.Assert(assignment[0].AssignmentId, check.Equals, "2QKNTL0XULRGFAQWUWDD05FP94V2O3") 153 | c.Assert(assignment[0].WorkerId, check.Equals, "A1ZUQ2YDM61713") 154 | c.Assert(assignment[0].HITId, check.Equals, "2W36VCPWZ9RN5DX1MBJ7VN3D6WEPAM") 155 | c.Assert(assignment[0].AssignmentStatus, check.Equals, "Submitted") 156 | c.Assert(assignment[0].AutoApprovalTime, check.Equals, "2014-02-26T09:39:48Z") 157 | c.Assert(assignment[0].AcceptTime, check.Equals, "2014-01-27T09:39:38Z") 158 | c.Assert(assignment[0].SubmitTime, check.Equals, "2014-01-27T09:39:48Z") 159 | c.Assert(assignment[0].ApprovalTime, check.Equals, "") 160 | 161 | answers := assignment[0].Answers() 162 | c.Assert(len(answers), check.Equals, 4) 163 | c.Assert(answers["tags"], check.Equals, "asd") 164 | c.Assert(answers["text_in_image"], check.Equals, "asd") 165 | c.Assert(answers["is_pattern"], check.Equals, "yes") 166 | c.Assert(answers["is_map"], check.Equals, "yes") 167 | } 168 | -------------------------------------------------------------------------------- /exp/mturk/responses_test.go: -------------------------------------------------------------------------------- 1 | package mturk_test 2 | 3 | var BasicHitResponse = ` 4 | 643b794b-66b6-4427-bb8a-4d3df5c9a20eTrue28J4IXKO2L927XKJTHO34OCDNASCDW2XZ7D1X3V0FKQVW7LU51S7PKKGFKDF 5 | ` 6 | 7 | var SearchHITResponse = ` 8 | 38862d9c-f015-4177-a2d3-924110a9d6f2True1112BU26DG67D1XTE823B3OQ2JF2XWF8322OWJ5OPB0YV6IGL5727KP9U38P5XR2011-12-28T19:56:20Ztest hitplease disregard, testing onlyReviewable10.01USD$0.0125920002011-12-28T19:56:50Z30010 9 | ` 10 | 11 | var GetAssignmentsForHITNoAnswerResponse = ` 12 | 536934be-a35b-4e4e-9822-72fbf36d5862True001 13 | ` 14 | 15 | var GetAssignmentsForHITAnswerResponse = ` 16 | 2f113bdf-2e3e-4c5a-a396-3ed01384ecb9True1112QKNTL0XULRGFAQWUWDD05FP94V2O3A1ZUQ2YDM617132W36VCPWZ9RN5DX1MBJ7VN3D6WEPAMSubmitted2014-02-26T09:39:48Z2014-01-27T09:39:38Z2014-01-27T09:39:48Z<?xml version="1.0" encoding="UTF-8" standalone="no"?> 17 | <QuestionFormAnswers xmlns="http://mechanicalturk.amazonaws.com/AWSMechanicalTurkDataSchemas/2005-10-01/QuestionFormAnswers.xsd"> 18 | <Answer> 19 | <QuestionIdentifier>tags</QuestionIdentifier> 20 | <FreeText>asd</FreeText> 21 | </Answer> 22 | <Answer> 23 | <QuestionIdentifier>text_in_image</QuestionIdentifier> 24 | <FreeText>asd</FreeText> 25 | </Answer> 26 | <Answer> 27 | <QuestionIdentifier>is_pattern</QuestionIdentifier> 28 | <FreeText>yes</FreeText> 29 | </Answer> 30 | <Answer> 31 | <QuestionIdentifier>is_map</QuestionIdentifier> 32 | <FreeText>yes</FreeText> 33 | </Answer> 34 | </QuestionFormAnswers> 35 | 36 | ` 37 | -------------------------------------------------------------------------------- /exp/mturk/sign.go: -------------------------------------------------------------------------------- 1 | package mturk 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/sha1" 6 | "encoding/base64" 7 | "github.com/AdRoll/goamz/aws" 8 | ) 9 | 10 | var b64 = base64.StdEncoding 11 | 12 | // ---------------------------------------------------------------------------- 13 | // Mechanical Turk signing (http://goo.gl/wrzfn) 14 | func sign(auth aws.Auth, service, method, timestamp string, params map[string]string) { 15 | payload := service + method + timestamp 16 | hash := hmac.New(sha1.New, []byte(auth.SecretKey)) 17 | hash.Write([]byte(payload)) 18 | signature := make([]byte, b64.EncodedLen(hash.Size())) 19 | b64.Encode(signature, hash.Sum(nil)) 20 | 21 | params["Signature"] = string(signature) 22 | } 23 | -------------------------------------------------------------------------------- /exp/mturk/sign_test.go: -------------------------------------------------------------------------------- 1 | package mturk_test 2 | 3 | import ( 4 | "github.com/AdRoll/goamz/aws" 5 | "github.com/AdRoll/goamz/exp/mturk" 6 | "gopkg.in/check.v1" 7 | ) 8 | 9 | // Mechanical Turk REST authentication docs: http://goo.gl/wrzfn 10 | 11 | var testAuth = aws.Auth{AccessKey: "user", SecretKey: "secret"} 12 | 13 | // == fIJy9wCApBNL2R4J2WjJGtIBFX4= 14 | func (s *S) TestBasicSignature(c *check.C) { 15 | params := map[string]string{} 16 | mturk.Sign(testAuth, "AWSMechanicalTurkRequester", "CreateHIT", "2012-02-16T20:30:47Z", params) 17 | expected := "b/TnvzrdeD/L/EyzdFrznPXhido=" 18 | c.Assert(params["Signature"], check.Equals, expected) 19 | } 20 | -------------------------------------------------------------------------------- /exp/sdb/export_test.go: -------------------------------------------------------------------------------- 1 | package sdb 2 | 3 | import ( 4 | "github.com/AdRoll/goamz/aws" 5 | ) 6 | 7 | func Sign(auth aws.Auth, method, path string, params map[string][]string, headers map[string][]string) { 8 | sign(auth, method, path, params, headers) 9 | } 10 | -------------------------------------------------------------------------------- /exp/sdb/responses_test.go: -------------------------------------------------------------------------------- 1 | package sdb_test 2 | 3 | var TestCreateDomainXmlOK = ` 4 | 5 | 6 | 7 | 63264005-7a5f-e01a-a224-395c63b89f6d 8 | 0.0055590279 9 | 10 | 11 | ` 12 | 13 | var TestListDomainsXmlOK = ` 14 | 15 | 16 | 17 | Account 18 | Domain 19 | Record 20 | 21 | 22 | 15fcaf55-9914-63c2-21f3-951e31193790 23 | 0.0000071759 24 | 25 | 26 | ` 27 | 28 | var TestListDomainsWithNextTokenXmlOK = ` 29 | 30 | 31 | 32 | Domain1-200706011651 33 | Domain2-200706011652 34 | TWV0ZXJpbmdUZXN0RG9tYWluMS0yMDA3MDYwMTE2NTY= 35 | 36 | 37 | eb13162f-1b95-4511-8b12-489b86acfd28 38 | 0.0000219907 39 | 40 | 41 | ` 42 | 43 | var TestDeleteDomainXmlOK = ` 44 | 45 | 46 | 47 | 039e1e25-9a64-2a74-93da-2fda36122a97 48 | 0.0055590278 49 | 50 | 51 | ` 52 | 53 | var TestDomainMetadataXmlNoSuchDomain = ` 54 | 55 | 56 | 57 | 58 | NoSuchDomain 59 | The specified domain does not exist. 60 | 0.0000071759 61 | 62 | 63 | e050cea2-a772-f90e-2cb0-98ebd42c2898 64 | 65 | ` 66 | 67 | var TestPutAttrsXmlOK = ` 68 | 69 | 70 | 71 | 490206ce-8292-456c-a00f-61b335eb202b 72 | 0.0000219907 73 | 74 | 75 | ` 76 | 77 | var TestDeleteAttrsXmlOK = ` 78 | 79 | 80 | 81 | 05ae667c-cfac-41a8-ab37-a9c897c4c3ca 82 | 0.0000219907 83 | 84 | 85 | ` 86 | 87 | var TestAttrsXmlOK = ` 88 | 89 | 90 | 91 | ColorBlue 92 | SizeMed 93 | 94 | 95 | b1e8f1f7-42e9-494c-ad09-2674e557526d 96 | 0.0000219942 97 | 98 | 99 | ` 100 | 101 | var TestSelectXmlOK = ` 102 | 103 | 104 | 105 | 106 | Item_03 107 | CategoryClothes 108 | SubcategoryPants 109 | NameSweatpants 110 | ColorBlue 111 | ColorYellow 112 | ColorPink 113 | SizeLarge 114 | 115 | 116 | Item_06 117 | CategoryMotorcycle Parts 118 | SubcategoryBodywork 119 | NameFender Eliminator 120 | ColorBlue 121 | MakeYamaha 122 | ModelR1 123 | 124 | 125 | 126 | b1e8f1f7-42e9-494c-ad09-2674e557526d 127 | 0.0000219907 128 | 129 | 130 | ` 131 | -------------------------------------------------------------------------------- /exp/sdb/sign.go: -------------------------------------------------------------------------------- 1 | package sdb 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/sha256" 6 | "encoding/base64" 7 | "github.com/AdRoll/goamz/aws" 8 | "net/http" 9 | "net/url" 10 | "sort" 11 | "strings" 12 | ) 13 | 14 | var b64 = base64.StdEncoding 15 | 16 | // ---------------------------------------------------------------------------- 17 | // SimpleDB signing (http://goo.gl/CaY81) 18 | 19 | func sign(auth aws.Auth, method, path string, params url.Values, headers http.Header) { 20 | var host string 21 | for k, v := range headers { 22 | k = strings.ToLower(k) 23 | switch k { 24 | case "host": 25 | host = v[0] 26 | } 27 | } 28 | 29 | // set up some defaults used for signing the request 30 | if auth.Token() != "" { 31 | params["SecurityToken"] = []string{auth.Token()} 32 | } 33 | params["AWSAccessKeyId"] = []string{auth.AccessKey} 34 | params["SignatureVersion"] = []string{"2"} 35 | params["SignatureMethod"] = []string{"HmacSHA256"} 36 | 37 | // join up all the incoming params 38 | var sarray []string 39 | for k, v := range params { 40 | sarray = append(sarray, aws.Encode(k)+"="+aws.Encode(v[0])) 41 | } 42 | sort.StringSlice(sarray).Sort() 43 | joined := strings.Join(sarray, "&") 44 | 45 | // create the payload, sign it and create the signature 46 | payload := strings.Join([]string{method, host, "/", joined}, "\n") 47 | hash := hmac.New(sha256.New, []byte(auth.SecretKey)) 48 | hash.Write([]byte(payload)) 49 | signature := make([]byte, b64.EncodedLen(hash.Size())) 50 | b64.Encode(signature, hash.Sum(nil)) 51 | 52 | // add the signature to the outgoing params 53 | params["Signature"] = []string{string(signature)} 54 | } 55 | -------------------------------------------------------------------------------- /exp/sdb/sign_test.go: -------------------------------------------------------------------------------- 1 | package sdb_test 2 | 3 | import ( 4 | "github.com/AdRoll/goamz/aws" 5 | "github.com/AdRoll/goamz/exp/sdb" 6 | "gopkg.in/check.v1" 7 | ) 8 | 9 | // SimpleDB ReST authentication docs: http://goo.gl/CaY81 10 | 11 | var testAuth = aws.Auth{AccessKey: "access-key-id-s8eBOWuU", SecretKey: "secret-access-key-UkQjTLd9"} 12 | 13 | func (s *S) TestSignExampleDomainCreate(c *check.C) { 14 | method := "GET" 15 | params := map[string][]string{ 16 | "Action": {"CreateDomain"}, 17 | "DomainName": {"MyDomain"}, 18 | "Timestamp": {"2011-08-20T07:23:57+12:00"}, 19 | "Version": {"2009-04-15"}, 20 | } 21 | headers := map[string][]string{ 22 | "Host": {"sdb.amazonaws.com"}, 23 | } 24 | sdb.Sign(testAuth, method, "", params, headers) 25 | expected := "ot2JaeeqMRJqgAqW67hkzUlffgxdOz4RykbrECB+tDU=" 26 | c.Assert(params["Signature"], check.DeepEquals, []string{expected}) 27 | } 28 | 29 | // Do a few test methods which takes combinations of params 30 | -------------------------------------------------------------------------------- /exp/ses/mailbox_simulator.go: -------------------------------------------------------------------------------- 1 | package ses 2 | 3 | var ( 4 | tEST_TO_ADDRESSES = []string{ 5 | "success@simulator.amazonses.com", 6 | "bounce@simulator.amazonses.com", 7 | "ooto@simulator.amazonses.com", 8 | "complaint@simulator.amazonses.com", 9 | "suppressionlist@simulator.amazonses.com"} 10 | tEST_CC_ADDRESSES = []string{} 11 | tEST_BCC_ADDRESSES = []string{} 12 | ) 13 | 14 | const ( 15 | tEST_EMAIL_SUBJECT = "goamz TestSESIntegration" 16 | tEST_TEXT_BODY = "This is a test email send by SimulateDelivery." 17 | 18 | tEST_HTML_BODY = ` 19 | 20 | 21 |

This is a test email send by SimulateDelivery.

22 |

Foo bar baz

23 | 24 | 25 | ` 26 | ) 27 | 28 | // This is an helper function to send emails to the mailbox simulator. 29 | // http://docs.aws.amazon.com/ses/latest/DeveloperGuide/mailbox-simulator.html 30 | // 31 | // from: the source email address registered in your SES account 32 | func (s *SES) SimulateDelivery(from string) (*SendEmailResponse, error) { 33 | destination := NewDestination(tEST_TO_ADDRESSES, 34 | tEST_CC_ADDRESSES, tEST_BCC_ADDRESSES) 35 | message := NewMessage(tEST_EMAIL_SUBJECT, tEST_TEXT_BODY, tEST_HTML_BODY) 36 | 37 | return s.SendEmail(from, destination, message) 38 | } 39 | -------------------------------------------------------------------------------- /exp/ses/responses_test.go: -------------------------------------------------------------------------------- 1 | package ses_test 2 | 3 | var TestSendEmailError = ` 4 | 5 | 6 | 7 | Sender 8 | MessageRejected 9 | Email address is not verified. 10 | 11 | 21d1e58d-28b2-4d5f-a974-669c3c67674f 12 | 13 | ` 14 | 15 | var TestSendEmailOk = ` 16 | 17 | 18 | 19 | 00000131d51d2292-159ad6eb-077c-46e6-ad09-ae7c05925ed4-000000 20 | 21 | 22 | d5964849-c866-11e0-9beb-01a62d68c57f 23 | 24 | 25 | ` 26 | 27 | var TestSendRawEmailOk = ` 28 | 29 | 30 | 31 | 00000131d51d6b36-1d4f9293-0aee-4503-b573-9ae4e70e9e38-000000 32 | 33 | 34 | e0abcdfa-c866-11e0-b6d0-273d09173b49 35 | 36 | 37 | ` 38 | -------------------------------------------------------------------------------- /exp/ses/ses_test.go: -------------------------------------------------------------------------------- 1 | package ses_test 2 | 3 | import ( 4 | "encoding/base64" 5 | "testing" 6 | 7 | "github.com/AdRoll/goamz/aws" 8 | "github.com/AdRoll/goamz/exp/ses" 9 | "github.com/AdRoll/goamz/testutil" 10 | "gopkg.in/check.v1" 11 | ) 12 | 13 | func Test(t *testing.T) { 14 | check.TestingT(t) 15 | } 16 | 17 | var _ = check.Suite(&S{}) 18 | var testServer = testutil.NewHTTPServer() 19 | 20 | type S struct { 21 | sesService *ses.SES 22 | } 23 | 24 | func (s *S) SetUpSuite(c *check.C) { 25 | testServer.Start() 26 | auth := aws.Auth{AccessKey: "abc", SecretKey: "123"} 27 | sesService := ses.New(auth, aws.Region{SESEndpoint: testServer.URL}) 28 | s.sesService = sesService 29 | } 30 | 31 | func (s *S) TearDownTest(c *check.C) { 32 | testServer.Flush() 33 | } 34 | 35 | func (s *S) TestSendEmailError(c *check.C) { 36 | testServer.Response(400, nil, TestSendEmailError) 37 | 38 | resp, err := s.sesService.SendEmail("foo@example.com", 39 | ses.NewDestination([]string{"unauthorized@example.com"}, []string{}, []string{}), 40 | ses.NewMessage("subject", "textBody", "htmlBody")) 41 | _ = testServer.WaitRequest() 42 | 43 | c.Assert(resp, check.IsNil) 44 | c.Assert(err.Error(), check.Equals, "Email address is not verified. (MessageRejected)") 45 | } 46 | 47 | func (s *S) TestSendEmail(c *check.C) { 48 | testServer.Response(200, nil, TestSendEmailOk) 49 | 50 | resp, err := s.sesService.SendEmail("foo@example.com", 51 | ses.NewDestination([]string{"to1@example.com", "to2@example.com"}, 52 | []string{"cc1@example.com", "cc2@example.com"}, 53 | []string{"bcc1@example.com", "bcc2@example.com"}), 54 | ses.NewMessage("subject", "textBody", "htmlBody")) 55 | req := testServer.WaitRequest() 56 | 57 | c.Assert(req.Method, check.Equals, "POST") 58 | c.Assert(req.URL.Path, check.Equals, "/") 59 | c.Assert(req.Header["Date"], check.Not(check.Equals), "") 60 | c.Assert(req.FormValue("Source"), check.Equals, "foo@example.com") 61 | c.Assert(req.FormValue("Destination.ToAddresses.member.1"), check.Equals, "to1@example.com") 62 | c.Assert(req.FormValue("Destination.ToAddresses.member.2"), check.Equals, "to2@example.com") 63 | c.Assert(req.FormValue("Destination.CcAddresses.member.1"), check.Equals, "cc1@example.com") 64 | c.Assert(req.FormValue("Destination.CcAddresses.member.2"), check.Equals, "cc2@example.com") 65 | c.Assert(req.FormValue("Destination.BccAddresses.member.1"), check.Equals, "bcc1@example.com") 66 | c.Assert(req.FormValue("Destination.BccAddresses.member.2"), check.Equals, "bcc2@example.com") 67 | 68 | c.Assert(req.FormValue("Message.Subject.Data"), check.Equals, "subject") 69 | c.Assert(req.FormValue("Message.Subject.Charset"), check.Equals, "utf-8") 70 | 71 | c.Assert(req.FormValue("Message.Body.Text.Data"), check.Equals, "textBody") 72 | c.Assert(req.FormValue("Message.Body.Text.Charset"), check.Equals, "utf-8") 73 | 74 | c.Assert(req.FormValue("Message.Body.Html.Data"), check.Equals, "htmlBody") 75 | c.Assert(req.FormValue("Message.Body.Html.Charset"), check.Equals, "utf-8") 76 | 77 | c.Assert(err, check.IsNil) 78 | c.Assert(resp.SendEmailResult, check.NotNil) 79 | c.Assert(resp.ResponseMetadata, check.NotNil) 80 | } 81 | 82 | func (s *S) TestSendRawEmailError(c *check.C) { 83 | testServer.Response(400, nil, TestSendEmailError) 84 | 85 | resp, err := s.sesService.SendRawEmail(nil, rawMessage) 86 | _ = testServer.WaitRequest() 87 | 88 | c.Assert(resp, check.IsNil) 89 | c.Assert(err.Error(), check.Equals, "Email address is not verified. (MessageRejected)") 90 | } 91 | 92 | func (s *S) TestSendRawEmailNoDestinations(c *check.C) { 93 | testServer.Response(200, nil, TestSendRawEmailOk) 94 | 95 | resp, err := s.sesService.SendRawEmail(nil, rawMessage) 96 | req := testServer.WaitRequest() 97 | 98 | c.Assert(req.Method, check.Equals, "POST") 99 | c.Assert(req.URL.Path, check.Equals, "/") 100 | c.Assert(req.Header["Date"], check.Not(check.Equals), "") 101 | c.Assert(req.FormValue("Source"), check.Equals, "") 102 | 103 | c.Assert(req.FormValue("RawMessage.Data"), check.Equals, 104 | base64.StdEncoding.EncodeToString(rawMessage)) 105 | 106 | c.Assert(err, check.IsNil) 107 | c.Assert(resp.SendRawEmailResult, check.NotNil) 108 | c.Assert(resp.ResponseMetadata, check.NotNil) 109 | } 110 | 111 | func (s *S) TestSendRawEmailWithDestinations(c *check.C) { 112 | testServer.Response(200, nil, TestSendRawEmailOk) 113 | 114 | resp, err := s.sesService.SendRawEmail([]string{ 115 | "to1@example.com", 116 | "cc2@example.com", 117 | "bcc1@example.com", 118 | "other@example.com", 119 | }, rawMessage) 120 | req := testServer.WaitRequest() 121 | 122 | c.Assert(req.Method, check.Equals, "POST") 123 | c.Assert(req.URL.Path, check.Equals, "/") 124 | c.Assert(req.Header["Date"], check.Not(check.Equals), "") 125 | c.Assert(req.FormValue("Source"), check.Equals, "") 126 | 127 | c.Assert(req.FormValue("Destinations.member.1"), check.Equals, 128 | "to1@example.com") 129 | c.Assert(req.FormValue("Destinations.member.2"), check.Equals, 130 | "cc2@example.com") 131 | c.Assert(req.FormValue("Destinations.member.3"), check.Equals, 132 | "bcc1@example.com") 133 | c.Assert(req.FormValue("Destinations.member.4"), check.Equals, 134 | "other@example.com") 135 | c.Assert(req.FormValue("RawMessage.Data"), check.Equals, 136 | base64.StdEncoding.EncodeToString(rawMessage)) 137 | 138 | c.Assert(err, check.IsNil) 139 | c.Assert(resp.SendRawEmailResult, check.NotNil) 140 | c.Assert(resp.ResponseMetadata, check.NotNil) 141 | } 142 | 143 | var rawMessage = []byte(`To: "to1@example.com", "to2@example.com" 144 | Cc: "cc1@example.com", "cc2@example.com" 145 | Bcc: "bcc1@example.com", "bcc2@example.com" 146 | From: foo@example.com 147 | Subject: Test Subject 148 | Content-Type: multipart/alternative; boundary=001a1147f9d0b5b8ce0525380c4b 149 | MIME-Version: 1.0 150 | 151 | --001a1147f9d0b5b8ce0525380c4b 152 | Content-Type: text/plain; charset=UTF-8 153 | 154 | Text Body 155 | 156 | --001a1147f9d0b5b8ce0525380c4b 157 | Content-Type: text/html; charset=UTF-8 158 | Content-Transfer-Encoding: quoted-printable 159 | 160 |

HTML

body

161 | 162 | --001a1147f9d0b5b8ce0525380c4b-- 163 | `) 164 | -------------------------------------------------------------------------------- /iam/iamt_test.go: -------------------------------------------------------------------------------- 1 | package iam_test 2 | 3 | import ( 4 | "github.com/AdRoll/goamz/aws" 5 | "github.com/AdRoll/goamz/iam" 6 | "github.com/AdRoll/goamz/iam/iamtest" 7 | "gopkg.in/check.v1" 8 | ) 9 | 10 | // LocalServer represents a local ec2test fake server. 11 | type LocalServer struct { 12 | auth aws.Auth 13 | region aws.Region 14 | srv *iamtest.Server 15 | } 16 | 17 | func (s *LocalServer) SetUp(c *check.C) { 18 | srv, err := iamtest.NewServer() 19 | c.Assert(err, check.IsNil) 20 | c.Assert(srv, check.NotNil) 21 | 22 | s.srv = srv 23 | s.region = aws.Region{IAMEndpoint: srv.URL()} 24 | } 25 | 26 | // LocalServerSuite defines tests that will run 27 | // against the local iamtest server. It includes 28 | // tests from ClientTests. 29 | type LocalServerSuite struct { 30 | srv LocalServer 31 | ClientTests 32 | } 33 | 34 | var _ = check.Suite(&LocalServerSuite{}) 35 | 36 | func (s *LocalServerSuite) SetUpSuite(c *check.C) { 37 | s.srv.SetUp(c) 38 | s.ClientTests.iam = iam.New(s.srv.auth, s.srv.region) 39 | } 40 | -------------------------------------------------------------------------------- /iam/responses_test.go: -------------------------------------------------------------------------------- 1 | package iam_test 2 | 3 | // http://goo.gl/EUIvl 4 | var CreateUserExample = ` 5 | 6 | 7 | 8 | /division_abc/subdivision_xyz/ 9 | Bob 10 | AIDACKCEVSQ6C2EXAMPLE 11 | arn:aws:iam::123456789012:user/division_abc/subdivision_xyz/Bob 12 | 13 | 14 | 15 | 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE 16 | 17 | 18 | ` 19 | 20 | var DuplicateUserExample = ` 21 | 22 | 23 | Sender 24 | EntityAlreadyExists 25 | User with name Bob already exists. 26 | 27 | 1d5f5000-1316-11e2-a60f-91a8e6fb6d21 28 | 29 | ` 30 | 31 | var GetUserExample = ` 32 | 33 | 34 | 35 | /division_abc/subdivision_xyz/ 36 | Bob 37 | AIDACKCEVSQ6C2EXAMPLE 38 | arn:aws:iam::123456789012:user/division_abc/subdivision_xyz/Bob 39 | 40 | 41 | 42 | 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE 43 | 44 | 45 | ` 46 | 47 | var CreateGroupExample = ` 48 | 49 | 50 | 51 | /admins/ 52 | Admins 53 | AGPACKCEVSQ6C2EXAMPLE 54 | arn:aws:iam::123456789012:group/Admins 55 | 56 | 57 | 58 | 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE 59 | 60 | 61 | ` 62 | 63 | var ListGroupsExample = ` 64 | 65 | 66 | 67 | 68 | /division_abc/subdivision_xyz/ 69 | Admins 70 | AGPACKCEVSQ6C2EXAMPLE 71 | arn:aws:iam::123456789012:group/Admins 72 | 73 | 74 | /division_abc/subdivision_xyz/product_1234/engineering/ 75 | Test 76 | AGP2MAB8DPLSRHEXAMPLE 77 | arn:aws:iam::123456789012:group/division_abc/subdivision_xyz/product_1234/engineering/Test 78 | 79 | 80 | /division_abc/subdivision_xyz/product_1234/ 81 | Managers 82 | AGPIODR4TAW7CSEXAMPLE 83 | arn:aws:iam::123456789012:group/division_abc/subdivision_xyz/product_1234/Managers 84 | 85 | 86 | false 87 | 88 | 89 | 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE 90 | 91 | 92 | ` 93 | 94 | var RequestIdExample = ` 95 | 96 | 97 | 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE 98 | 99 | 100 | ` 101 | 102 | var CreateAccessKeyExample = ` 103 | 104 | 105 | 106 | Bob 107 | AKIAIOSFODNN7EXAMPLE 108 | Active 109 | wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY 110 | 111 | 112 | 113 | 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE 114 | 115 | 116 | ` 117 | 118 | var ListAccessKeyExample = ` 119 | 120 | 121 | Bob 122 | 123 | 124 | Bob 125 | AKIAIOSFODNN7EXAMPLE 126 | Active 127 | 128 | 129 | Bob 130 | AKIAI44QH8DHBEXAMPLE 131 | Inactive 132 | 133 | 134 | false 135 | 136 | 137 | 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE 138 | 139 | 140 | ` 141 | 142 | var GetUserPolicyExample = ` 143 | 144 | 145 | Bob 146 | AllAccessPolicy 147 | 148 | {"Statement":[{"Effect":"Allow","Action":"*","Resource":"*"}]} 149 | 150 | 151 | 152 | 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE 153 | 154 | 155 | ` 156 | -------------------------------------------------------------------------------- /kinesis/kinesis_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // goamz - Go packages to interact with the Amazon Web Services. 3 | // 4 | // https://wiki.ubuntu.com/goamz 5 | // 6 | // Written by Tim Bart 7 | package kinesis_test 8 | 9 | import ( 10 | "encoding/base64" 11 | "encoding/json" 12 | "fmt" 13 | "github.com/AdRoll/goamz/kinesis" 14 | "path/filepath" 15 | "reflect" 16 | "runtime" 17 | "testing" 18 | ) 19 | 20 | // assert fails the test if the condition is false. 21 | func assert(tb testing.TB, condition bool, msg string, v ...interface{}) { 22 | if !condition { 23 | _, file, line, _ := runtime.Caller(1) 24 | fmt.Printf("\033[31m%s:%d: "+msg+"\033[39m\n\n", append([]interface{}{filepath.Base(file), line}, v...)...) 25 | tb.FailNow() 26 | } 27 | } 28 | 29 | // ok fails the test if an err is not nil. 30 | func ok(tb testing.TB, err error) { 31 | if err != nil { 32 | _, file, line, _ := runtime.Caller(1) 33 | fmt.Printf("\033[31m%s:%d: unexpected error: %s\033[39m\n\n", filepath.Base(file), line, err.Error()) 34 | tb.FailNow() 35 | } 36 | } 37 | 38 | // equals fails the test if exp is not equal to act. 39 | func equals(tb testing.TB, exp, act interface{}) { 40 | if !reflect.DeepEqual(exp, act) { 41 | _, file, line, _ := runtime.Caller(1) 42 | fmt.Printf("\033[31m%s:%d:\n\n\texp: %#v\n\n\tgot: %#v\033[39m\n\n", filepath.Base(file), line, exp, act) 43 | tb.FailNow() 44 | } 45 | } 46 | 47 | func TestDescribeStreamResponse(t *testing.T) { 48 | resp := &kinesis.DescribeStreamResponse{} 49 | err := json.Unmarshal([]byte(describeStream), resp) 50 | 51 | ok(t, err) 52 | equals(t, false, resp.StreamDescription.HasMoreShards) 53 | equals(t, 3, len(resp.StreamDescription.Shards)) 54 | equals(t, "arn:aws:kinesis:us-east-1:052958737983:exampleStreamName", resp.StreamDescription.StreamARN) 55 | equals(t, "exampleStreamName", resp.StreamDescription.StreamName) 56 | equals(t, kinesis.StreamStatusActive, resp.StreamDescription.StreamStatus) 57 | } 58 | 59 | func TestGetRecordsResponse(t *testing.T) { 60 | resp := &kinesis.GetRecordsResponse{} 61 | err := json.Unmarshal([]byte(getRecords), resp) 62 | 63 | ok(t, err) 64 | equals(t, 1, len(resp.Records)) 65 | equals(t, "XzxkYXRhPl8w", base64.StdEncoding.EncodeToString(resp.Records[0].Data)) 66 | equals(t, "partitionKey", resp.Records[0].PartitionKey) 67 | equals(t, "21269319989652663814458848515492872193", resp.Records[0].SequenceNumber) 68 | } 69 | 70 | func TestGetShardIteratorResponse(t *testing.T) { 71 | resp := &kinesis.GetShardIteratorResponse{} 72 | err := json.Unmarshal([]byte(getShardIterator), resp) 73 | 74 | ok(t, err) 75 | equals(t, "AAAAAAAAAAETY", resp.ShardIterator[:13]) 76 | } 77 | 78 | func TestListStreams(t *testing.T) { 79 | resp := &kinesis.ListStreamResponse{} 80 | err := json.Unmarshal([]byte(listStreams), resp) 81 | 82 | ok(t, err) 83 | equals(t, false, resp.HasMoreStreams) 84 | equals(t, 1, len(resp.StreamNames)) 85 | equals(t, "exampleStreamName", resp.StreamNames[0]) 86 | } 87 | 88 | func TestPutRecord(t *testing.T) { 89 | resp := &kinesis.PutRecordResponse{} 90 | err := json.Unmarshal([]byte(putRecord), resp) 91 | 92 | ok(t, err) 93 | equals(t, "21269319989653637946712965403778482177", resp.SequenceNumber) 94 | equals(t, "shardId-000000000001", resp.ShardId) 95 | } 96 | 97 | func TestPutRecords(t *testing.T) { 98 | resp := &kinesis.PutRecordsResponse{} 99 | err := json.Unmarshal([]byte(putRecords), resp) 100 | 101 | ok(t, err) 102 | equals(t, 0, resp.FailedRecordCount) 103 | equals(t, 1, len(resp.Records)) 104 | equals(t, "49543463076548007577105092703039560359975228518395019266", resp.Records[0].SequenceNumber) 105 | equals(t, "shardId-000000000000", resp.Records[0].ShardId) 106 | } 107 | -------------------------------------------------------------------------------- /kinesis/query.go: -------------------------------------------------------------------------------- 1 | package kinesis 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type msi map[string]interface{} 8 | type Query struct { 9 | buffer msi 10 | } 11 | 12 | func NewEmptyQuery() *Query { 13 | return &Query{msi{}} 14 | } 15 | 16 | func NewQueryWithStream(streamName string) *Query { 17 | q := &Query{msi{}} 18 | q.AddStreamName(streamName) 19 | return q 20 | } 21 | 22 | func (q *Query) AddExclusiveStartShardId(shardId string) { 23 | q.buffer["ExclusiveStartShardId"] = shardId 24 | } 25 | 26 | func (q *Query) AddLimit(limit int) { 27 | q.buffer["Limit"] = limit 28 | } 29 | 30 | func (q *Query) AddStreamName(name string) { 31 | q.buffer["StreamName"] = name 32 | } 33 | 34 | func (q *Query) AddShardCount(count int) { 35 | q.buffer["ShardCount"] = count 36 | } 37 | 38 | func (q *Query) AddShardIterator(iterator string) { 39 | q.buffer["ShardIterator"] = iterator 40 | } 41 | 42 | func (q *Query) AddShardId(id string) { 43 | q.buffer["ShardId"] = id 44 | } 45 | 46 | func (q *Query) AddShardIteratorType(t ShardIteratorType) { 47 | q.buffer["ShardIteratorType"] = t 48 | } 49 | 50 | func (q *Query) AddStartingSequenceNumber(sequenceNumber string) { 51 | q.buffer["StartingSequenceNumber"] = sequenceNumber 52 | } 53 | 54 | func (q *Query) AddData(data []byte) { 55 | q.buffer["Data"] = data 56 | } 57 | 58 | func (q *Query) AddExplicitHashKey(hashKey string) { 59 | q.buffer["ExplicitHashKey"] = hashKey 60 | } 61 | 62 | func (q *Query) AddPartitionKey(partitionKey string) { 63 | q.buffer["PartitionKey"] = partitionKey 64 | } 65 | 66 | func (q *Query) AddSequenceNumberForOrdering(sequenceNumber string) { 67 | q.buffer["SequenceNumberForOrdering"] = sequenceNumber 68 | } 69 | 70 | func (q *Query) AddRecords(records []PutRecordsRequestEntry) { 71 | q.buffer["Records"] = records 72 | } 73 | 74 | func (q *Query) AddShardToMerge(shard string) { 75 | q.buffer["ShardToMerge"] = shard 76 | } 77 | 78 | func (q *Query) AddAdjacentShardToMerge(shard string) { 79 | q.buffer["AdjacentShardToMerge"] = shard 80 | } 81 | 82 | func (q *Query) AddShardToSplit(shard string) { 83 | q.buffer["ShardToSplit"] = shard 84 | } 85 | 86 | func (q *Query) AddNewStartingHashKey(hashKey string) { 87 | q.buffer["NewStartingHashKey"] = hashKey 88 | } 89 | 90 | func (q *Query) String() string { 91 | bytes, err := json.Marshal(q.buffer) 92 | if err != nil { 93 | panic(err) 94 | } 95 | return string(bytes) 96 | } 97 | -------------------------------------------------------------------------------- /kinesis/responses_test.go: -------------------------------------------------------------------------------- 1 | package kinesis_test 2 | 3 | var describeStream string = ` 4 | { 5 | "StreamDescription": { 6 | "HasMoreShards": false, 7 | "Shards": [ 8 | { 9 | "HashKeyRange": { 10 | "EndingHashKey": "113427455640312821154458202477256070484", 11 | "StartingHashKey": "0" 12 | }, 13 | "SequenceNumberRange": { 14 | "EndingSequenceNumber": "21269319989741826081360214168359141376", 15 | "StartingSequenceNumber": "21267647932558653966460912964485513216" 16 | }, 17 | "ShardId": "shardId-000000000000" 18 | }, 19 | { 20 | "HashKeyRange": { 21 | "EndingHashKey": "226854911280625642308916404954512140969", 22 | "StartingHashKey": "113427455640312821154458202477256070485" 23 | }, 24 | "SequenceNumberRange": { 25 | "StartingSequenceNumber": "21267647932558653966460912964485513217" 26 | }, 27 | "ShardId": "shardId-000000000001" 28 | }, 29 | { 30 | "HashKeyRange": { 31 | "EndingHashKey": "340282366920938463463374607431768211455", 32 | "StartingHashKey": "226854911280625642308916404954512140970" 33 | }, 34 | "SequenceNumberRange": { 35 | "StartingSequenceNumber": "21267647932558653966460912964485513218" 36 | }, 37 | "ShardId": "shardId-000000000002" 38 | } 39 | ], 40 | "StreamARN": "arn:aws:kinesis:us-east-1:052958737983:exampleStreamName", 41 | "StreamName": "exampleStreamName", 42 | "StreamStatus": "ACTIVE" 43 | } 44 | }` 45 | 46 | var getRecords string = `{ 47 | "NextShardIterator": "AAAAAAAAAAHsW8zCWf9164uy8Epue6WS3w6wmj4a4USt+CNvMd6uXQ+HL5vAJMznqqC0DLKsIjuoiTi1BpT6nW0LN2M2D56zM5H8anHm30Gbri9ua+qaGgj+3XTyvbhpERfrezgLHbPB/rIcVpykJbaSj5tmcXYRmFnqZBEyHwtZYFmh6hvWVFkIwLuMZLMrpWhG5r5hzkE=", 48 | "Records": [ 49 | { 50 | "Data": "XzxkYXRhPl8w", 51 | "PartitionKey": "partitionKey", 52 | "SequenceNumber": "21269319989652663814458848515492872193" 53 | } 54 | ] 55 | }` 56 | 57 | var getShardIterator string = `{ 58 | "ShardIterator": "AAAAAAAAAAETYyAYzd665+8e0X7JTsASDM/Hr2rSwc0X2qz93iuA3udrjTH+ikQvpQk/1ZcMMLzRdAesqwBGPnsthzU0/CBlM/U8/8oEqGwX3pKw0XyeDNRAAZyXBo3MqkQtCpXhr942BRTjvWKhFz7OmCb2Ncfr8Tl2cBktooi6kJhr+djN5WYkB38Rr3akRgCl9qaU4dY=" 59 | }` 60 | 61 | var listStreams string = `{ 62 | "HasMoreStreams": false, 63 | "StreamNames": [ 64 | "exampleStreamName" 65 | ] 66 | }` 67 | 68 | var putRecord string = `{ 69 | "SequenceNumber": "21269319989653637946712965403778482177", 70 | "ShardId": "shardId-000000000001" 71 | }` 72 | 73 | var putRecords string = `{ 74 | "FailedRecordCount": 0, 75 | "Records": [ 76 | { 77 | "SequenceNumber": "49543463076548007577105092703039560359975228518395019266", 78 | "ShardId": "shardId-000000000000" 79 | } 80 | ] 81 | }` 82 | -------------------------------------------------------------------------------- /kinesis/types.go: -------------------------------------------------------------------------------- 1 | package kinesis 2 | 3 | import ( 4 | "fmt" 5 | "github.com/AdRoll/goamz/aws" 6 | ) 7 | 8 | type ShardIteratorType string 9 | type StreamStatus string 10 | 11 | const ( 12 | 13 | // Start reading exactly from the position denoted by a specific sequence number. 14 | ShardIteratorAtSequenceNumber ShardIteratorType = "AT_SEQUENCE_NUMBER" 15 | 16 | // Start reading right after the position denoted by a specific sequence number. 17 | ShardIteratorAfterSequenceNumber ShardIteratorType = "AFTER_SEQUENCE_NUMBER" 18 | 19 | // Start reading at the last untrimmed record in the shard in the system, 20 | // which is the oldest data record in the shard. 21 | ShardIteratorTrimHorizon ShardIteratorType = "TRIM_HORIZON" 22 | 23 | // Start reading just after the most recent record in the shard, 24 | // so that you always read the most recent data in the shard. 25 | ShardIteratorLatest ShardIteratorType = "LATEST" 26 | 27 | // The stream is being created. Upon receiving a CreateStream request, 28 | // Amazon Kinesis immediately returns and sets StreamStatus to CREATING. 29 | StreamStatusCreating StreamStatus = "CREATING" 30 | 31 | // The stream is being deleted. After a DeleteStream request, 32 | // the specified stream is in the DELETING state until Amazon Kinesis completes the deletion. 33 | StreamStatusDeleting StreamStatus = "DELETING" 34 | 35 | // The stream exists and is ready for read and write operations or deletion. 36 | // You should perform read and write operations only on an ACTIVE stream. 37 | StreamStatusActive StreamStatus = "ACTIVE" 38 | 39 | // Shards in the stream are being merged or split. 40 | // Read and write operations continue to work while the stream is in the UPDATING state. 41 | StreamStatusUpdating StreamStatus = "UPDATING" 42 | ) 43 | 44 | // Main Kinesis object 45 | type Kinesis struct { 46 | aws.Auth 47 | aws.Region 48 | } 49 | 50 | // The range of possible hash key values for the shard, which is a set of ordered contiguous positive integers. 51 | type HashKeyRange struct { 52 | EndingHashKey string 53 | StartingHashKey string 54 | } 55 | 56 | func (h HashKeyRange) String() string { 57 | return fmt.Sprintf("{EndingHashKey: %s, StartingHashKey: %s}\n", 58 | h.EndingHashKey, h.StartingHashKey) 59 | } 60 | 61 | // The range of possible sequence numbers for the shard. 62 | type SequenceNumberRange struct { 63 | EndingSequenceNumber string 64 | StartingSequenceNumber string 65 | } 66 | 67 | func (s SequenceNumberRange) String() string { 68 | return fmt.Sprintf("{EndingSequenceNumber: %s, StartingSequenceNumber: %s}\n", 69 | s.EndingSequenceNumber, s.StartingSequenceNumber) 70 | } 71 | 72 | // A uniquely identified group of data records in an Amazon Kinesis stream. 73 | type Shard struct { 74 | AdjacentParentShardId string 75 | HashKeyRange HashKeyRange 76 | ParentShardId string 77 | SequenceNumberRange SequenceNumberRange 78 | ShardId string 79 | } 80 | 81 | // Description of a Stream 82 | type StreamDescription struct { 83 | HasMoreShards bool 84 | Shards []Shard 85 | StreamARN string 86 | StreamName string 87 | StreamStatus StreamStatus 88 | } 89 | 90 | // The unit of data of the Amazon Kinesis stream, which is composed of a sequence number, 91 | // a partition key, and a data blob. 92 | type Record struct { 93 | Data []byte 94 | PartitionKey string 95 | SequenceNumber string 96 | } 97 | 98 | // Represents the output of a DescribeStream operation. 99 | type DescribeStreamResponse struct { 100 | StreamDescription StreamDescription 101 | } 102 | 103 | // Represents the output of a GetRecords operation. 104 | type GetRecordsResponse struct { 105 | NextShardIterator string 106 | Records []Record 107 | } 108 | 109 | // Represents the output of a GetShardIterator operation. 110 | type GetShardIteratorResponse struct { 111 | ShardIterator string 112 | } 113 | 114 | // Represents the output of a ListStreams operation. 115 | type ListStreamResponse struct { 116 | HasMoreStreams bool 117 | StreamNames []string 118 | } 119 | 120 | // Represents the output of a PutRecord operation. 121 | type PutRecordResponse struct { 122 | SequenceNumber string 123 | ShardId string 124 | } 125 | 126 | // The unit of data put to the Amazon Kinesis stream by PutRecords, which includes 127 | // a partition key, a hash key, and a data blob. 128 | type PutRecordsRequestEntry struct { 129 | PartitionKey string 130 | HashKey string `json:"ExplicitHashKey,omitempty"` 131 | Data []byte 132 | } 133 | 134 | // Represents the output of a PutRecords operation. 135 | type PutRecordsResponse struct { 136 | FailedRecordCount int 137 | Records []PutRecordsResultEntry 138 | } 139 | 140 | type PutRecordsResultEntry struct { 141 | ErrorCode string 142 | ErrorMessage string 143 | SequenceNumber string 144 | ShardId string 145 | } 146 | 147 | // Error represents an error in an operation with Kinesis(following goamz/Dynamodb) 148 | type Error struct { 149 | StatusCode int // HTTP status code (200, 403, ...) 150 | Status string 151 | Code string `json:"__type"` 152 | Message string `json:"message"` 153 | } 154 | 155 | func (e Error) Error() string { 156 | return fmt.Sprintf("[HTTP %d] %s : %s\n", e.StatusCode, e.Code, e.Message) 157 | } 158 | -------------------------------------------------------------------------------- /kms/kms.go: -------------------------------------------------------------------------------- 1 | // 2 | // gokms - Go packages to interact with Amazon KMS (Key Management Service) 3 | // 4 | // 5 | // Written by Kiko Hsieh 6 | // 7 | package kms 8 | 9 | import ( 10 | "bytes" 11 | "encoding/json" 12 | "fmt" 13 | "github.com/AdRoll/goamz/aws" 14 | "io/ioutil" 15 | "net/http" 16 | "time" 17 | ) 18 | 19 | const ( 20 | httpMethod = "POST" 21 | contentType = "application/x-amz-json-1.1" 22 | targetPrefix = "TrentService." 23 | serverName = "kms" 24 | ) 25 | 26 | type KMS struct { 27 | aws.Auth 28 | aws.Region 29 | } 30 | 31 | func New(auth aws.Auth, region aws.Region) *KMS { 32 | return &KMS{auth, region} 33 | } 34 | 35 | func (k *KMS) query(requstInfo KMSAction) ([]byte, error) { 36 | b, err := json.Marshal(requstInfo) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | hreq, err := http.NewRequest(httpMethod, k.Region.KMSEndpoint+"/", bytes.NewBuffer(b)) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | hreq.Header.Set("Content-Type", contentType) 47 | hreq.Header.Set("X-Amz-Date", time.Now().UTC().Format(aws.ISO8601BasicFormat)) 48 | hreq.Header.Set("X-Amz-Target", targetPrefix+requstInfo.ActionName()) 49 | 50 | if k.Auth.Token() != "" { 51 | hreq.Header.Set("X-Amz-Security-Token", k.Auth.Token()) 52 | } 53 | 54 | //All KMS operations require Signature Version 4 55 | //http://docs.aws.amazon.com/kms/latest/APIReference/Welcome.html 56 | signer := aws.NewV4Signer(k.Auth, serverName, k.Region) 57 | signer.Sign(hreq) 58 | 59 | r, err := http.DefaultClient.Do(hreq) 60 | 61 | if err != nil { 62 | return nil, err 63 | } 64 | 65 | body, _ := ioutil.ReadAll(r.Body) 66 | 67 | defer r.Body.Close() 68 | 69 | if r.StatusCode != 200 { 70 | return nil, buildError(body, r.StatusCode) 71 | } 72 | 73 | return body, err 74 | } 75 | 76 | type KMSError struct { 77 | StatusCode int `json:",omitempty"` 78 | Type string `json:"__type"` 79 | Message string `json:"message"` 80 | } 81 | 82 | func (k *KMSError) Error() string { 83 | return fmt.Sprintf("Type: %s, Code: %d, Message: %s", 84 | k.Type, k.StatusCode, k.Message, 85 | ) 86 | 87 | } 88 | 89 | func buildError(body []byte, statusCode int) error { 90 | err := KMSError{StatusCode: statusCode} 91 | json.Unmarshal(body, &err) 92 | return &err 93 | } 94 | 95 | // ================== Action ======================== 96 | 97 | func (k *KMS) DescribeKey(info DescribeKeyInfo) (DescribeKeyResp, error) { 98 | resp := DescribeKeyResp{} 99 | bResp, err := k.query(&info) 100 | 101 | if err != nil { 102 | return resp, err 103 | } 104 | 105 | err = json.Unmarshal(bResp, &resp) 106 | 107 | return resp, err 108 | } 109 | 110 | func (k *KMS) ListAliases(info ListAliasesInfo) (ListAliasesResp, error) { 111 | resp := ListAliasesResp{} 112 | bResp, err := k.query(&info) 113 | 114 | if err != nil { 115 | return resp, err 116 | } 117 | 118 | err = json.Unmarshal(bResp, &resp) 119 | 120 | return resp, err 121 | } 122 | 123 | func (k *KMS) Encrypt(info EncryptInfo) (EncryptResp, error) { 124 | resp := EncryptResp{} 125 | bResp, err := k.query(&info) 126 | 127 | if err != nil { 128 | return resp, err 129 | } 130 | 131 | err = json.Unmarshal(bResp, &resp) 132 | 133 | return resp, err 134 | } 135 | 136 | func (k *KMS) Decrypt(info DecryptInfo) (DecryptResp, error) { 137 | resp := DecryptResp{} 138 | bResp, err := k.query(&info) 139 | 140 | if err != nil { 141 | return resp, err 142 | } 143 | 144 | err = json.Unmarshal(bResp, &resp) 145 | 146 | return resp, err 147 | } 148 | 149 | func (k *KMS) EnableKey(info EnableKeyInfo) error { 150 | _, err := k.query(&info) 151 | 152 | return err 153 | } 154 | 155 | func (k *KMS) DisableKey(info DisableKeyInfo) error { 156 | _, err := k.query(&info) 157 | 158 | return err 159 | } 160 | -------------------------------------------------------------------------------- /kms/kms_test.go: -------------------------------------------------------------------------------- 1 | package kms_test 2 | 3 | import ( 4 | "github.com/AdRoll/goamz/aws" 5 | "github.com/AdRoll/goamz/kms" 6 | "github.com/AdRoll/goamz/testutil" 7 | "gopkg.in/check.v1" 8 | "testing" 9 | ) 10 | 11 | func Test(t *testing.T) { 12 | check.TestingT(t) 13 | } 14 | 15 | type S struct { 16 | kms *kms.KMS 17 | } 18 | 19 | var _ = check.Suite(&S{}) 20 | 21 | var testServer = testutil.NewHTTPServer() 22 | 23 | func (s *S) SetUpSuite(c *check.C) { 24 | testServer.Start() 25 | auth := aws.Auth{AccessKey: "fake_akey", SecretKey: "fake_skey"} 26 | s.kms = kms.New(auth, aws.Region{KMSEndpoint: testServer.URL}) 27 | } 28 | 29 | func (s *S) TearDownTest(c *check.C) { 30 | testServer.Flush() 31 | } 32 | 33 | func (s *S) TestDescribeKey(c *check.C) { 34 | testServer.Response(200, nil, DescribeKeyExample) 35 | 36 | desc, err := s.kms.DescribeKey(kms.DescribeKeyInfo{KeyId: "alias/test"}) 37 | header := testServer.WaitRequest().Header 38 | 39 | c.Assert(header.Get("Content-Type"), check.Equals, "application/x-amz-json-1.1") 40 | c.Assert(header.Get("X-Amz-Target"), check.Equals, "TrentService.DescribeKey") 41 | 42 | c.Assert(err, check.IsNil) 43 | 44 | c.Assert(desc.KeyMetadata.AWSAccountId, check.Equals, "987654321") 45 | c.Assert(desc.KeyMetadata.Arn, check.Equals, "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012") 46 | c.Assert(desc.KeyMetadata.CreationDate, check.Equals, 123456.789) 47 | c.Assert(desc.KeyMetadata.Description, check.Equals, "This is a test") 48 | c.Assert(desc.KeyMetadata.Enabled, check.Equals, true) 49 | c.Assert(desc.KeyMetadata.KeyId, check.Equals, "12345678-1234-1234-1234-123456789012") 50 | c.Assert(desc.KeyMetadata.KeyUsage, check.Equals, "ENCRYPT_DECRYPT") 51 | } 52 | 53 | func (s *S) TestErrorCase(c *check.C) { 54 | testServer.Response(400, nil, ErrorExample) 55 | 56 | _, err := s.kms.DescribeKey(kms.DescribeKeyInfo{KeyId: "alias/test"}) 57 | 58 | c.Assert(err, check.ErrorMatches, "Type: TestException, Code: 400, Message: This is a error test") 59 | } 60 | -------------------------------------------------------------------------------- /kms/requestStruct.go: -------------------------------------------------------------------------------- 1 | package kms 2 | 3 | type KMSAction interface { 4 | ActionName() string 5 | } 6 | 7 | type ProduceKeyOpt struct { 8 | EncryptionContext map[string]string `json:",omitempty"` 9 | GrantTokens []string `json:",omitempty"` 10 | } 11 | 12 | //The following structs are the parameters when requesting to AWS KMS 13 | //http://docs.aws.amazon.com/kms/latest/APIReference/API_Operations.html 14 | type DescribeKeyInfo struct { 15 | //4 forms for KeyId - http://docs.aws.amazon.com/kms/latest/APIReference/API_DescribeKey.html 16 | //1. Key ARN 17 | //2. Alias ARN 18 | //3. Globally Unique Key 19 | //4. Alias Name 20 | KeyId string 21 | } 22 | 23 | func (d *DescribeKeyInfo) ActionName() string { 24 | return "DescribeKey" 25 | } 26 | 27 | type ListAliasesInfo struct { 28 | //These parameters are optional. See http://docs.aws.amazon.com/kms/latest/APIReference/API_ListAliases.html 29 | Limit int `json:",omitempty"` 30 | Marker string `json:",omitempty"` 31 | } 32 | 33 | func (l *ListAliasesInfo) ActionName() string { 34 | return "ListAliases" 35 | } 36 | 37 | type EncryptInfo struct { 38 | //4 forms for KeyId - http://docs.aws.amazon.com/kms/latest/APIReference/API_Encrypt.html 39 | //1. Key ARN 40 | //2. Alias ARN 41 | //3. Globally Unique Key 42 | //4. Alias Name 43 | KeyId string 44 | ProduceKeyOpt 45 | Plaintext []byte 46 | } 47 | 48 | func (e *EncryptInfo) ActionName() string { 49 | return "Encrypt" 50 | } 51 | 52 | type DecryptInfo struct { 53 | CiphertextBlob []byte 54 | ProduceKeyOpt 55 | } 56 | 57 | func (d *DecryptInfo) ActionName() string { 58 | return "Decrypt" 59 | } 60 | 61 | type EnableKeyInfo struct { 62 | //2 forms for KeyId - http://docs.aws.amazon.com/kms/latest/APIReference/API_EnableKey.html 63 | //1. Key ARN 64 | //2. Globally Unique Key 65 | KeyId string 66 | } 67 | 68 | func (e *EnableKeyInfo) ActionName() string { 69 | return "EnableKey" 70 | } 71 | 72 | type DisableKeyInfo struct { 73 | //2 forms for KeyId - http://docs.aws.amazon.com/kms/latest/APIReference/API_DisableKey.html 74 | //1. Key ARN 75 | //2. Globally Unique KeyId 76 | KeyId string 77 | } 78 | 79 | func (d *DisableKeyInfo) ActionName() string { 80 | return "DisableKey" 81 | } 82 | -------------------------------------------------------------------------------- /kms/responseStruct.go: -------------------------------------------------------------------------------- 1 | package kms 2 | 3 | //The rersponse body from KMS, based on which action you take 4 | //http://docs.aws.amazon.com/kms/latest/APIReference/API_Operations.html 5 | type DescribeKeyResp struct { 6 | KeyMetadata struct { 7 | AWSAccountId string 8 | Arn string 9 | CreationDate float64 10 | Description string 11 | Enabled bool 12 | KeyId string 13 | KeyUsage string 14 | } 15 | } 16 | 17 | type AliasInfo struct { 18 | AliasArn string 19 | AliasName string 20 | TargetKeyId string 21 | } 22 | 23 | type ListAliasesResp struct { 24 | Aliases []AliasInfo 25 | NextMarker string 26 | Truncated bool 27 | } 28 | 29 | type EncryptResp struct { 30 | CiphertextBlob []byte 31 | KeyId string 32 | } 33 | 34 | type DecryptResp struct { 35 | KeyId string 36 | Plaintext []byte 37 | } 38 | 39 | //For some actions, we just only check if it is success by status code. (200) 40 | //1. EnableKey 41 | //2. DisableKey 42 | -------------------------------------------------------------------------------- /kms/response_test.go: -------------------------------------------------------------------------------- 1 | package kms_test 2 | 3 | var DescribeKeyExample = ` 4 | { 5 | "KeyMetadata": { 6 | "AWSAccountId": "987654321", 7 | "Arn": "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012", 8 | "CreationDate": 123456.789, 9 | "Description": "This is a test", 10 | "Enabled": true, 11 | "KeyId": "12345678-1234-1234-1234-123456789012", 12 | "KeyUsage": "ENCRYPT_DECRYPT" 13 | } 14 | } 15 | ` 16 | 17 | var ErrorExample = ` 18 | { 19 | "__type": "TestException", 20 | "message": "This is a error test" 21 | } 22 | ` 23 | -------------------------------------------------------------------------------- /rds/rds.go: -------------------------------------------------------------------------------- 1 | package rds 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "log" 10 | "net/http" 11 | "net/http/httputil" 12 | "strconv" 13 | "time" 14 | 15 | "github.com/AdRoll/goamz/aws" 16 | ) 17 | 18 | const debug = false 19 | 20 | const ( 21 | ServiceName = "rds" 22 | ApiVersion = "2013-09-09" 23 | ) 24 | 25 | // The RDS type encapsulates operations within a specific EC2 region. 26 | type RDS struct { 27 | Service aws.AWSService 28 | Auth aws.Auth 29 | Region aws.Region 30 | } 31 | 32 | // New creates a new RDS Client. 33 | func New(auth aws.Auth, region aws.Region) (*RDS, error) { 34 | service, err := aws.NewService(auth, region.RDSEndpoint) 35 | if err != nil { 36 | return nil, err 37 | } 38 | return &RDS{ 39 | Service: service, 40 | Auth: auth, 41 | Region: region, 42 | }, nil 43 | } 44 | 45 | // ---------------------------------------------------------------------------- 46 | // Request dispatching logic. 47 | 48 | // query dispatches a request to the RDS API signed with a version 2 signature 49 | func (rds *RDS) query(method, path string, params map[string]string, resp interface{}) error { 50 | // Add basic RDS param 51 | params["Version"] = ApiVersion 52 | 53 | r, err := rds.Service.Query(method, path, params) 54 | if err != nil { 55 | return err 56 | } 57 | defer r.Body.Close() 58 | 59 | if debug { 60 | dump, _ := httputil.DumpResponse(r, true) 61 | log.Printf("response:\n") 62 | log.Printf("%v\n}\n", string(dump)) 63 | } 64 | 65 | if r.StatusCode != 200 { 66 | return rds.Service.BuildError(r) 67 | } 68 | err = xml.NewDecoder(r.Body).Decode(resp) 69 | return err 70 | } 71 | 72 | // ---------------------------------------------------------------------------- 73 | // API methods and corresponding response types. 74 | 75 | // Response to a DescribeDBInstances request 76 | // 77 | // See http://goo.gl/KSPlAl for more details. 78 | type DescribeDBInstancesResponse struct { 79 | DBInstances []DBInstance `xml:"DescribeDBInstancesResult>DBInstances>DBInstance"` // The list of database instances 80 | Marker string `xml:"DescribeDBInstancesResult>Marker"` // An optional pagination token provided by a previous request 81 | RequestId string `xml:"ResponseMetadata>RequestId"` 82 | } 83 | 84 | // DescribeDBInstances - Returns a description of each Database Instance 85 | // Supports pagination by using the "Marker" parameter, and "maxRecords" for subsequent calls 86 | // Unfortunately RDS does not currently support filtering 87 | // 88 | // See http://goo.gl/lzZMyz for more details. 89 | func (rds *RDS) DescribeDBInstances(id string, maxRecords int, marker string) (*DescribeDBInstancesResponse, error) { 90 | 91 | params := aws.MakeParams("DescribeDBInstances") 92 | 93 | if id != "" { 94 | params["DBInstanceIdentifier"] = id 95 | } 96 | 97 | if maxRecords != 0 { 98 | params["MaxRecords"] = strconv.Itoa(maxRecords) 99 | } 100 | if marker != "" { 101 | params["Marker"] = marker 102 | } 103 | 104 | resp := &DescribeDBInstancesResponse{} 105 | err := rds.query("POST", "/", params, resp) 106 | return resp, err 107 | } 108 | 109 | type DownloadDBLogFilePortionResponse struct { 110 | Marker string `xml:"DownloadDBLogFilePortionResult>Marker"` 111 | LogFileData string `xml:"DownloadDBLogFilePortionResult>LogFileData"` 112 | AdditionalDataPending string `xml:"DownloadDBLogFilePortionResult>AdditionalDataPending"` 113 | RequestId string `xml:"ResponseMetadata>RequestId"` 114 | } 115 | 116 | // DownloadDBLogFilePortion - Downloads all or a portion of the specified log file 117 | // 118 | // See http://goo.gl/Gfpz9l for more details. 119 | func (rds *RDS) DownloadDBLogFilePortion(id, filename, marker string, numberOfLines int) (*DownloadDBLogFilePortionResponse, error) { 120 | 121 | params := aws.MakeParams("DownloadDBLogFilePortion") 122 | 123 | params["DBInstanceIdentifier"] = id 124 | params["LogFileName"] = filename 125 | 126 | if marker != "" { 127 | params["Marker"] = marker 128 | } 129 | if numberOfLines != 0 { 130 | params["NumberOfLines"] = strconv.Itoa(numberOfLines) 131 | } 132 | 133 | resp := &DownloadDBLogFilePortionResponse{} 134 | err := rds.query("POST", "/", params, resp) 135 | return resp, err 136 | } 137 | 138 | // DownloadCompleteDBLogFile - Downloads the contents of the specified database log file 139 | // 140 | // See http://goo.gl/plC66B for more details. 141 | 142 | func (rds *RDS) DownloadCompleteDBLogFile(id, filename string) (io.ReadCloser, error) { 143 | url := fmt.Sprintf( 144 | "%s/v13/downloadCompleteLogFile/%s/%s", 145 | rds.Region.RDSEndpoint.Endpoint, 146 | id, 147 | filename, 148 | ) 149 | hreq, err := http.NewRequest("GET", url, nil) 150 | if err != nil { 151 | if debug { 152 | log.Printf("Error http.NewRequest GET %s", url) 153 | } 154 | return nil, err 155 | } 156 | token := rds.Auth.Token() 157 | if token != "" { 158 | hreq.Header.Set("X-Amz-Security-Token", token) 159 | } 160 | hreq.Header.Set("X-Amz-Date", time.Now().UTC().Format(aws.ISO8601BasicFormat)) 161 | signer := aws.NewV4Signer(rds.Auth, "rds", rds.Region) 162 | signer.Sign(hreq) 163 | resp, err := http.DefaultClient.Do(hreq) 164 | if err != nil { 165 | if debug { 166 | log.Print("Error calling Amazon") 167 | } 168 | return nil, err 169 | } 170 | if resp.StatusCode == 200 { 171 | return resp.Body, nil 172 | } else { 173 | defer resp.Body.Close() 174 | body, err := ioutil.ReadAll(resp.Body) 175 | if err != nil { 176 | if debug { 177 | log.Printf("Could not read response body") 178 | } 179 | return nil, err 180 | } 181 | msg := fmt.Sprintf( 182 | "Responce:\n\tStatusCode: %d\n\tBody: %s\n", 183 | resp.StatusCode, 184 | string(body), 185 | ) 186 | if debug { 187 | log.Printf(msg) 188 | } 189 | err = errors.New(msg) 190 | return nil, err 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /rds/rds_test.go: -------------------------------------------------------------------------------- 1 | package rds_test 2 | 3 | import ( 4 | "github.com/AdRoll/goamz/aws" 5 | "github.com/AdRoll/goamz/rds" 6 | "github.com/AdRoll/goamz/testutil" 7 | "gopkg.in/check.v1" 8 | "testing" 9 | ) 10 | 11 | func Test(t *testing.T) { 12 | check.TestingT(t) 13 | } 14 | 15 | var _ = check.Suite(&S{}) 16 | 17 | type S struct { 18 | rds *rds.RDS 19 | } 20 | 21 | var testServer = testutil.NewHTTPServer() 22 | 23 | func (s *S) SetUpSuite(c *check.C) { 24 | var err error 25 | testServer.Start() 26 | auth := aws.Auth{AccessKey: "abc", SecretKey: "123"} 27 | s.rds, err = rds.New(auth, aws.Region{RDSEndpoint: aws.ServiceInfo{testServer.URL, aws.V2Signature}}) 28 | c.Assert(err, check.IsNil) 29 | } 30 | 31 | func (s *S) TearDownTest(c *check.C) { 32 | testServer.Flush() 33 | } 34 | 35 | func (s *S) TestDescribeDBInstancesExample1(c *check.C) { 36 | testServer.Response(200, nil, DescribeDBInstancesExample1) 37 | 38 | resp, err := s.rds.DescribeDBInstances("simcoprod01", 0, "") 39 | 40 | req := testServer.WaitRequest() 41 | c.Assert(req.Form["Action"], check.DeepEquals, []string{"DescribeDBInstances"}) 42 | c.Assert(req.Form["DBInstanceIdentifier"], check.DeepEquals, []string{"simcoprod01"}) 43 | 44 | c.Assert(err, check.IsNil) 45 | c.Assert(resp.RequestId, check.Equals, "9135fff3-8509-11e0-bd9b-a7b1ece36d51") 46 | c.Assert(resp.DBInstances, check.HasLen, 1) 47 | 48 | db0 := resp.DBInstances[0] 49 | c.Assert(db0.AllocatedStorage, check.Equals, 10) 50 | c.Assert(db0.AutoMinorVersionUpgrade, check.Equals, true) 51 | c.Assert(db0.AvailabilityZone, check.Equals, "us-east-1a") 52 | c.Assert(db0.BackupRetentionPeriod, check.Equals, 1) 53 | 54 | c.Assert(db0.DBInstanceClass, check.Equals, "db.m1.large") 55 | c.Assert(db0.DBInstanceIdentifier, check.Equals, "simcoprod01") 56 | c.Assert(db0.DBInstanceStatus, check.Equals, "available") 57 | c.Assert(db0.DBName, check.Equals, "simcoprod") 58 | 59 | c.Assert(db0.Endpoint.Address, check.Equals, "simcoprod01.cu7u2t4uz396.us-east-1.rds.amazonaws.com") 60 | c.Assert(db0.Endpoint.Port, check.Equals, 3306) 61 | c.Assert(db0.Engine, check.Equals, "mysql") 62 | c.Assert(db0.EngineVersion, check.Equals, "5.1.50") 63 | c.Assert(db0.InstanceCreateTime, check.Equals, "2011-05-23T06:06:43.110Z") 64 | 65 | c.Assert(db0.LatestRestorableTime, check.Equals, "2011-05-23T06:50:00Z") 66 | c.Assert(db0.LicenseModel, check.Equals, "general-public-license") 67 | c.Assert(db0.MasterUsername, check.Equals, "master") 68 | c.Assert(db0.MultiAZ, check.Equals, false) 69 | c.Assert(db0.OptionGroupMemberships, check.HasLen, 1) 70 | c.Assert(db0.OptionGroupMemberships[0].Name, check.Equals, "default.mysql5.1") 71 | c.Assert(db0.OptionGroupMemberships[0].Status, check.Equals, "in-sync") 72 | 73 | c.Assert(db0.PreferredBackupWindow, check.Equals, "00:00-00:30") 74 | c.Assert(db0.PreferredMaintenanceWindow, check.Equals, "sat:07:30-sat:08:00") 75 | c.Assert(db0.PubliclyAccessible, check.Equals, false) 76 | } 77 | -------------------------------------------------------------------------------- /rds/responses_test.go: -------------------------------------------------------------------------------- 1 | package rds_test 2 | 3 | var DescribeDBInstancesExample1 = ` 4 | 5 | 6 | 7 | 8 | 9 | 2011-05-23T06:50:00Z 10 | mysql 11 | 12 | 1 13 | false 14 | general-public-license 15 | available 16 | 5.1.50 17 | 18 | 3306 19 |
simcoprod01.cu7u2t4uz396.us-east-1.rds.amazonaws.com
20 |
21 | simcoprod01 22 | simcoprod 23 | 24 | 25 | in-sync 26 | default.mysql5.1 27 | 28 | 29 | 30 | 31 | active 32 | default 33 | 34 | 35 | 00:00-00:30 36 | true 37 | sat:07:30-sat:08:00 38 | us-east-1a 39 | 2011-05-23T06:06:43.110Z 40 | 10 41 | 42 | 43 | default.mysql5.1 44 | in-sync 45 | 46 | 47 | db.m1.large 48 | master 49 | false 50 |
51 |
52 |
53 | 54 | 9135fff3-8509-11e0-bd9b-a7b1ece36d51 55 | 56 |
57 | ` 58 | -------------------------------------------------------------------------------- /route53/route53_test.go: -------------------------------------------------------------------------------- 1 | package route53_test 2 | 3 | import ( 4 | "github.com/AdRoll/goamz/route53" 5 | . "gopkg.in/check.v1" 6 | "testing" 7 | ) 8 | 9 | // Hook up gocheck into the "go test" runner. 10 | func Test(t *testing.T) { TestingT(t) } 11 | 12 | // This is a fixure used by a suite of tests 13 | type Route53Suite struct{} 14 | 15 | var _ = Suite(&Route53Suite{}) 16 | 17 | // validate AliasTarget behaviour 18 | func (s *Route53Suite) TestChangeAliasTargetBehavior(c *C) { 19 | record := route53.ResourceRecordValue{Value: "127.0.0.1"} 20 | records := []route53.ResourceRecordValue{record} 21 | change := route53.Change{} 22 | change.Action = "CREATE" 23 | change.Name = "test.localdomain" 24 | change.Type = "A" 25 | change.TTL = 300 26 | change.Values = records 27 | alias_target := route53.AliasTarget{HostedZoneId: "WIOJWAOFIEFAJ", DNSName: "test.localdomain"} 28 | // AliasTarget should be a nil pointer by default 29 | c.Assert(change.AliasTarget, IsNil) 30 | // AliasTarget pass by ref 31 | change.AliasTarget = &alias_target 32 | c.Assert(change.AliasTarget.HostedZoneId, Equals, "WIOJWAOFIEFAJ") 33 | c.Assert(change.AliasTarget.DNSName, Equals, "test.localdomain") 34 | c.Assert(change.AliasTarget.EvaluateTargetHealth, Equals, false) 35 | } 36 | -------------------------------------------------------------------------------- /s3/export_test.go: -------------------------------------------------------------------------------- 1 | package s3 2 | 3 | import ( 4 | "github.com/AdRoll/goamz/aws" 5 | ) 6 | 7 | var originalStrategy = attempts 8 | 9 | func SetAttemptStrategy(s *aws.AttemptStrategy) { 10 | if s == nil { 11 | attempts = originalStrategy 12 | } else { 13 | attempts = *s 14 | } 15 | } 16 | 17 | func Sign(auth aws.Auth, method, path string, params, headers map[string][]string) { 18 | sign(auth, method, path, params, headers) 19 | } 20 | 21 | func SetListPartsMax(n int) { 22 | listPartsMax = n 23 | } 24 | 25 | func SetListMultiMax(n int) { 26 | listMultiMax = n 27 | } 28 | -------------------------------------------------------------------------------- /s3/lifecycle.go: -------------------------------------------------------------------------------- 1 | package s3 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/base64" 6 | "encoding/xml" 7 | "net/url" 8 | "strconv" 9 | "time" 10 | ) 11 | 12 | // Implements an interface for s3 bucket lifecycle configuration 13 | // See goo.gl/d0bbDf for details. 14 | 15 | const ( 16 | LifecycleRuleStatusEnabled = "Enabled" 17 | LifecycleRuleStatusDisabled = "Disabled" 18 | LifecycleRuleDateFormat = "2006-01-02" 19 | StorageClassGlacier = "GLACIER" 20 | ) 21 | 22 | type Expiration struct { 23 | Days *uint `xml:"Days,omitempty"` 24 | Date string `xml:"Date,omitempty"` 25 | } 26 | 27 | // Returns Date as a time.Time. 28 | func (r *Expiration) ParseDate() (time.Time, error) { 29 | return time.Parse(LifecycleRuleDateFormat, r.Date) 30 | } 31 | 32 | type Transition struct { 33 | Days *uint `xml:"Days,omitempty"` 34 | Date string `xml:"Date,omitempty"` 35 | StorageClass string `xml:"StorageClass"` 36 | } 37 | 38 | // Returns Date as a time.Time. 39 | func (r *Transition) ParseDate() (time.Time, error) { 40 | return time.Parse(LifecycleRuleDateFormat, r.Date) 41 | } 42 | 43 | type NoncurrentVersionExpiration struct { 44 | Days *uint `xml:"NoncurrentDays,omitempty"` 45 | } 46 | 47 | type NoncurrentVersionTransition struct { 48 | Days *uint `xml:"NoncurrentDays,omitempty"` 49 | StorageClass string `xml:"StorageClass"` 50 | } 51 | 52 | type LifecycleRule struct { 53 | ID string `xml:"ID"` 54 | Prefix string `xml:"Prefix"` 55 | Status string `xml:"Status"` 56 | NoncurrentVersionTransition *NoncurrentVersionTransition `xml:"NoncurrentVersionTransition,omitempty"` 57 | NoncurrentVersionExpiration *NoncurrentVersionExpiration `xml:"NoncurrentVersionExpiration,omitempty"` 58 | Transition *Transition `xml:"Transition,omitempty"` 59 | Expiration *Expiration `xml:"Expiration,omitempty"` 60 | } 61 | 62 | // Create a lifecycle rule with arbitrary identifier id and object name prefix 63 | // for which the rules should apply. 64 | func NewLifecycleRule(id, prefix string) *LifecycleRule { 65 | rule := &LifecycleRule{ 66 | ID: id, 67 | Prefix: prefix, 68 | Status: LifecycleRuleStatusEnabled, 69 | } 70 | return rule 71 | } 72 | 73 | // Adds a transition rule in days. Overwrites any previous transition rule. 74 | func (r *LifecycleRule) SetTransitionDays(days uint) { 75 | r.Transition = &Transition{ 76 | Days: &days, 77 | StorageClass: StorageClassGlacier, 78 | } 79 | } 80 | 81 | // Adds a transition rule as a date. Overwrites any previous transition rule. 82 | func (r *LifecycleRule) SetTransitionDate(date time.Time) { 83 | r.Transition = &Transition{ 84 | Date: date.Format(LifecycleRuleDateFormat), 85 | StorageClass: StorageClassGlacier, 86 | } 87 | } 88 | 89 | // Adds an expiration rule in days. Overwrites any previous expiration rule. 90 | // Days must be > 0. 91 | func (r *LifecycleRule) SetExpirationDays(days uint) { 92 | r.Expiration = &Expiration{ 93 | Days: &days, 94 | } 95 | } 96 | 97 | // Adds an expiration rule as a date. Overwrites any previous expiration rule. 98 | func (r *LifecycleRule) SetExpirationDate(date time.Time) { 99 | r.Expiration = &Expiration{ 100 | Date: date.Format(LifecycleRuleDateFormat), 101 | } 102 | } 103 | 104 | // Adds a noncurrent version transition rule. Overwrites any previous 105 | // noncurrent version transition rule. 106 | func (r *LifecycleRule) SetNoncurrentVersionTransitionDays(days uint) { 107 | r.NoncurrentVersionTransition = &NoncurrentVersionTransition{ 108 | Days: &days, 109 | StorageClass: StorageClassGlacier, 110 | } 111 | } 112 | 113 | // Adds a noncurrent version expiration rule. Days must be > 0. Overwrites 114 | // any previous noncurrent version expiration rule. 115 | func (r *LifecycleRule) SetNoncurrentVersionExpirationDays(days uint) { 116 | r.NoncurrentVersionExpiration = &NoncurrentVersionExpiration{ 117 | Days: &days, 118 | } 119 | } 120 | 121 | // Marks the rule as disabled. 122 | func (r *LifecycleRule) Disable() { 123 | r.Status = LifecycleRuleStatusDisabled 124 | } 125 | 126 | // Marks the rule as enabled (default). 127 | func (r *LifecycleRule) Enable() { 128 | r.Status = LifecycleRuleStatusEnabled 129 | } 130 | 131 | type LifecycleConfiguration struct { 132 | XMLName xml.Name `xml:"LifecycleConfiguration"` 133 | Rules *[]*LifecycleRule `xml:"Rule,omitempty"` 134 | } 135 | 136 | // Adds a LifecycleRule to the configuration. 137 | func (c *LifecycleConfiguration) AddRule(r *LifecycleRule) { 138 | var rules []*LifecycleRule 139 | if c.Rules != nil { 140 | rules = *c.Rules 141 | } 142 | rules = append(rules, r) 143 | c.Rules = &rules 144 | } 145 | 146 | // Sets the bucket's lifecycle configuration. 147 | func (b *Bucket) PutLifecycleConfiguration(c *LifecycleConfiguration) error { 148 | doc, err := xml.Marshal(c) 149 | if err != nil { 150 | return err 151 | } 152 | 153 | buf := makeXmlBuffer(doc) 154 | digest := md5.New() 155 | size, err := digest.Write(buf.Bytes()) 156 | if err != nil { 157 | return err 158 | } 159 | 160 | headers := map[string][]string{ 161 | "Content-Length": {strconv.FormatInt(int64(size), 10)}, 162 | "Content-MD5": {base64.StdEncoding.EncodeToString(digest.Sum(nil))}, 163 | } 164 | 165 | req := &request{ 166 | path: "/", 167 | method: "PUT", 168 | bucket: b.Name, 169 | headers: headers, 170 | payload: buf, 171 | params: url.Values{"lifecycle": {""}}, 172 | } 173 | 174 | return b.S3.queryV4Sign(req, nil) 175 | } 176 | 177 | // Retrieves the lifecycle configuration for the bucket. AWS returns an error 178 | // if no lifecycle found. 179 | func (b *Bucket) GetLifecycleConfiguration() (*LifecycleConfiguration, error) { 180 | req := &request{ 181 | method: "GET", 182 | bucket: b.Name, 183 | path: "/", 184 | params: url.Values{"lifecycle": {""}}, 185 | } 186 | 187 | conf := &LifecycleConfiguration{} 188 | err := b.S3.queryV4Sign(req, conf) 189 | return conf, err 190 | } 191 | 192 | // Delete the bucket's lifecycle configuration. 193 | func (b *Bucket) DeleteLifecycleConfiguration() error { 194 | req := &request{ 195 | method: "DELETE", 196 | bucket: b.Name, 197 | path: "/", 198 | params: url.Values{"lifecycle": {""}}, 199 | } 200 | 201 | return b.S3.queryV4Sign(req, nil) 202 | } 203 | -------------------------------------------------------------------------------- /s3/s3aclxml.go: -------------------------------------------------------------------------------- 1 | package s3 2 | 3 | import "encoding/xml" 4 | 5 | const AllUsersUri = "http://acs.amazonaws.com/groups/global/AllUsers" 6 | 7 | type Grantee struct { 8 | XMLName xml.Name `xml:"Grantee"` 9 | Type string `xml:"type,attr"` 10 | URI string `xml:"URI"` 11 | ID string `xml:"ID"` 12 | DisplayName string `xml:"DisplayName"` 13 | } 14 | 15 | type Grant struct { 16 | XMLName xml.Name `xml:"Grant"` 17 | Grantee []Grantee 18 | Permission string `xml:"Permission"` 19 | } 20 | type AccessControlListGrants struct { 21 | XMLName xml.Name `xml:"AccessControlList"` 22 | Grant []Grant 23 | } 24 | 25 | type AclOwner struct { 26 | XMLName xml.Name `xml:"Owner"` 27 | ID string `xml:"ID"` 28 | DisplayName string `xml:"DisplayName"` 29 | } 30 | 31 | type AccessControlList struct { 32 | Owner AclOwner 33 | Grants AccessControlListGrants 34 | } 35 | 36 | func ParseAclFromXml(aclxml string) (AccessControlList, error) { 37 | b := []byte(aclxml) 38 | 39 | var acl AccessControlList 40 | err := xml.Unmarshal(b, &acl) 41 | if err != nil { 42 | return acl, err 43 | } 44 | 45 | return acl, nil 46 | } 47 | 48 | func GetCannedPolicyByAcl(acl AccessControlList) ACL { 49 | for _, grant := range acl.Grants.Grant { 50 | //fmt.Printf("Permission: %q\n", grant.Permission) 51 | //fmt.Printf("Grantee Type: %q\n", grant.Grantee[0].URI) 52 | for _, gratee := range grant.Grantee { 53 | if gratee.URI == AllUsersUri { 54 | return PublicRead 55 | } 56 | } 57 | } 58 | 59 | return Private 60 | } 61 | -------------------------------------------------------------------------------- /s3/s3aclxml_test.go: -------------------------------------------------------------------------------- 1 | package s3_test 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/AdRoll/goamz/s3" 7 | "gopkg.in/check.v1" 8 | ) 9 | 10 | const xmlResponseSimple = ` 11 | 12 | 13 | 14 | owner1 15 | My service user 16 | 17 | 18 | 19 | 20 | 21 | http://acs.amazonaws.com/groups/global/AllUsers 22 | 23 | READ 24 | 25 | 26 | 27 | 28 | owner1 29 | My service user 30 | 31 | FULL_CONTROL 32 | 33 | 34 | 35 | 36 | ` 37 | 38 | const xmlResponseExtended = ` 39 | 40 | 41 | Owner-canonical-user-ID 42 | display-name 43 | 44 | 45 | 46 | 47 | Owner-canonical-user-ID 48 | display-name 49 | 50 | FULL_CONTROL 51 | 52 | 53 | 54 | 55 | user1-canonical-user-ID 56 | display-name 57 | 58 | WRITE 59 | 60 | 61 | 62 | 63 | user2-canonical-user-ID 64 | display-name 65 | 66 | READ 67 | 68 | 69 | 70 | 71 | http://acs.amazonaws.com/groups/global/AllUsers 72 | 73 | READ 74 | 75 | 76 | 77 | http://acs.amazonaws.com/groups/s3/LogDelivery 78 | 79 | WRITE 80 | 81 | 82 | 83 | ` 84 | 85 | func (s *S) TestShouldParseXmlSimpleACLResponse(c *check.C) { 86 | acl, err := s3.ParseAclFromXml(xmlResponseSimple) 87 | 88 | c.Assert(err, check.Equals, nil) 89 | c.Assert(acl.Owner.ID, check.Equals, "owner1") 90 | c.Assert(acl.Owner.DisplayName, check.Equals, "My service user") 91 | c.Assert(acl.Grants.Grant[0].Grantee[0].Type, check.Equals, "Group") 92 | c.Assert(acl.Grants.Grant[0].Permission, check.Equals, "READ") 93 | } 94 | 95 | func (s *S) TestShouldParseXmlExtendedACLResponse(c *check.C) { 96 | acl, err := s3.ParseAclFromXml(xmlResponseExtended) 97 | 98 | c.Assert(err, check.Equals, nil) 99 | c.Assert(acl.Owner.ID, check.Equals, "Owner-canonical-user-ID") 100 | c.Assert(acl.Owner.DisplayName, check.Equals, "display-name") 101 | c.Assert(acl.Grants.Grant[0].Grantee[0].Type, check.Equals, "CanonicalUser") 102 | c.Assert(acl.Grants.Grant[0].Permission, check.Equals, "FULL_CONTROL") 103 | } 104 | 105 | func (s *S) TestShouldNotGetCannedPolicyByAclWithEmptyXmlInput(c *check.C) { 106 | _, err := s3.ParseAclFromXml("") 107 | 108 | c.Assert(err, check.DeepEquals, errors.New("EOF")) 109 | } 110 | 111 | func (s *S) TestShouldGetCannedPolicyByAclFromXmlSimpleResponse(c *check.C) { 112 | acl, _ := s3.ParseAclFromXml(xmlResponseSimple) 113 | 114 | cannedPolicy := s3.GetCannedPolicyByAcl(acl) 115 | 116 | c.Assert(cannedPolicy, check.Equals, s3.ACL(s3.PublicRead)) 117 | } 118 | -------------------------------------------------------------------------------- /s3/s3t_test.go: -------------------------------------------------------------------------------- 1 | package s3_test 2 | 3 | import ( 4 | "github.com/AdRoll/goamz/aws" 5 | "github.com/AdRoll/goamz/s3" 6 | "github.com/AdRoll/goamz/s3/s3test" 7 | "github.com/AdRoll/goamz/testutil" 8 | "gopkg.in/check.v1" 9 | "io/ioutil" 10 | "time" 11 | ) 12 | 13 | type LocalServer struct { 14 | auth aws.Auth 15 | region aws.Region 16 | srv *s3test.Server 17 | config *s3test.Config 18 | clock fakeClock 19 | } 20 | 21 | func (s *LocalServer) SetUp(c *check.C) { 22 | if s.config == nil { 23 | s.config = &s3test.Config{} 24 | } 25 | if s.config.Clock == nil { 26 | s.config.Clock = &s.clock 27 | } 28 | srv, err := s3test.NewServer(s.config) 29 | c.Assert(err, check.IsNil) 30 | c.Assert(srv, check.NotNil) 31 | 32 | s.srv = srv 33 | s.region = aws.Region{ 34 | Name: "faux-region-1", 35 | S3Endpoint: srv.URL(), 36 | S3LocationConstraint: true, // s3test server requires a LocationConstraint 37 | } 38 | } 39 | 40 | // LocalServerSuite defines tests that will run 41 | // against the local s3test server. It includes 42 | // selected tests from ClientTests; 43 | // when the s3test functionality is sufficient, it should 44 | // include all of them, and ClientTests can be simply embedded. 45 | type LocalServerSuite struct { 46 | srv LocalServer 47 | clientTests ClientTests 48 | } 49 | 50 | var ( 51 | // run tests twice, once in us-east-1 mode, once not. 52 | _ = check.Suite(&LocalServerSuite{}) 53 | _ = check.Suite(&LocalServerSuite{ 54 | srv: LocalServer{ 55 | config: &s3test.Config{ 56 | Send409Conflict: true, 57 | }, 58 | }, 59 | }) 60 | ) 61 | 62 | func (s *LocalServerSuite) SetUpSuite(c *check.C) { 63 | s.srv.SetUp(c) 64 | s.clientTests.s3 = s3.New(s.srv.auth, s.srv.region) 65 | 66 | // TODO Sadly the fake server ignores auth completely right now. :-( 67 | s.clientTests.authIsBroken = true 68 | s.clientTests.Cleanup() 69 | } 70 | 71 | func (s *LocalServerSuite) TearDownTest(c *check.C) { 72 | s.clientTests.Cleanup() 73 | } 74 | 75 | func (s *LocalServerSuite) TestBasicFunctionality(c *check.C) { 76 | s.clientTests.TestBasicFunctionality(c) 77 | } 78 | 79 | func (s *LocalServerSuite) TestGetNotFound(c *check.C) { 80 | s.clientTests.TestGetNotFound(c) 81 | } 82 | 83 | func (s *LocalServerSuite) TestBucketList(c *check.C) { 84 | s.clientTests.TestBucketList(c) 85 | } 86 | 87 | func (s *LocalServerSuite) TestDoublePutBucket(c *check.C) { 88 | s.clientTests.TestDoublePutBucket(c) 89 | } 90 | 91 | func (s *LocalServerSuite) TestMultiComplete(c *check.C) { 92 | if !testutil.Amazon { 93 | c.Skip("live tests against AWS disabled (no -amazon)") 94 | } 95 | s.clientTests.TestMultiComplete(c) 96 | } 97 | 98 | func (s *LocalServerSuite) TestGetHeaders(c *check.C) { 99 | b := s.clientTests.s3.Bucket("bucket") 100 | err := b.PutBucket(s3.Private) 101 | c.Assert(err, check.IsNil) 102 | 103 | // Test with a fake time that has a one-digit day (where 104 | // amzFormat "2 Jan" differs from RFC1123 "02 Jan") and a 105 | // non-UTC time zone, regardless of the time and timezone of 106 | // the host running the tests. 107 | ft, err := time.Parse(time.RFC3339, "2006-01-02T07:04:05-08:00") 108 | c.Assert(err, check.IsNil) 109 | s.srv.clock.now = &ft 110 | err = b.Put("name", []byte("content"), "text/plain", s3.Private, s3.Options{}) 111 | s.srv.clock.now = nil 112 | 113 | c.Assert(err, check.IsNil) 114 | defer b.Del("name") 115 | resp, err := b.GetResponse("name") 116 | c.Assert(err, check.IsNil) 117 | 118 | content, err := ioutil.ReadAll(resp.Body) 119 | c.Assert(err, check.IsNil) 120 | c.Check(content, check.DeepEquals, []byte("content")) 121 | 122 | c.Check(resp.Header.Get("Last-Modified"), check.Equals, "Mon, 2 Jan 2006 15:04:05 GMT") 123 | } 124 | 125 | type fakeClock struct { 126 | // Time to return for Now(). If nil, return current time. 127 | now *time.Time 128 | } 129 | 130 | func (c *fakeClock) Now() time.Time { 131 | if c.now != nil { 132 | return *c.now 133 | } else { 134 | return time.Now() 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /s3/sign.go: -------------------------------------------------------------------------------- 1 | package s3 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/sha1" 6 | "encoding/base64" 7 | "github.com/AdRoll/goamz/aws" 8 | "log" 9 | "sort" 10 | "strings" 11 | ) 12 | 13 | var b64 = base64.StdEncoding 14 | 15 | // ---------------------------------------------------------------------------- 16 | // S3 signing (http://goo.gl/G1LrK) 17 | 18 | var s3ParamsToSign = map[string]bool{ 19 | "acl": true, 20 | "location": true, 21 | "logging": true, 22 | "notification": true, 23 | "partNumber": true, 24 | "policy": true, 25 | "requestPayment": true, 26 | "torrent": true, 27 | "uploadId": true, 28 | "uploads": true, 29 | "versionId": true, 30 | "versioning": true, 31 | "versions": true, 32 | "response-content-type": true, 33 | "response-content-language": true, 34 | "response-expires": true, 35 | "response-cache-control": true, 36 | "response-content-disposition": true, 37 | "response-content-encoding": true, 38 | "website": true, 39 | "delete": true, 40 | } 41 | 42 | func sign(auth aws.Auth, method, canonicalPath string, params, headers map[string][]string) { 43 | var md5, ctype, date, xamz string 44 | var xamzDate bool 45 | var keys, sarray []string 46 | xheaders := make(map[string]string) 47 | for k, v := range headers { 48 | k = strings.ToLower(k) 49 | switch k { 50 | case "content-md5": 51 | md5 = v[0] 52 | case "content-type": 53 | ctype = v[0] 54 | case "date": 55 | if !xamzDate { 56 | date = v[0] 57 | } 58 | default: 59 | if strings.HasPrefix(k, "x-amz-") { 60 | keys = append(keys, k) 61 | xheaders[k] = strings.Join(v, ",") 62 | if k == "x-amz-date" { 63 | xamzDate = true 64 | date = "" 65 | } 66 | } 67 | } 68 | } 69 | if len(keys) > 0 { 70 | sort.StringSlice(keys).Sort() 71 | for i := range keys { 72 | key := keys[i] 73 | value := xheaders[key] 74 | sarray = append(sarray, key+":"+value) 75 | } 76 | xamz = strings.Join(sarray, "\n") + "\n" 77 | } 78 | 79 | expires := false 80 | if v, ok := params["Expires"]; ok { 81 | // Query string request authentication alternative. 82 | expires = true 83 | date = v[0] 84 | params["AWSAccessKeyId"] = []string{auth.AccessKey} 85 | } 86 | 87 | sarray = sarray[0:0] 88 | for k, v := range params { 89 | if s3ParamsToSign[k] { 90 | for _, vi := range v { 91 | if vi == "" { 92 | sarray = append(sarray, k) 93 | } else { 94 | // "When signing you do not encode these values." 95 | sarray = append(sarray, k+"="+vi) 96 | } 97 | } 98 | } 99 | } 100 | if len(sarray) > 0 { 101 | sort.StringSlice(sarray).Sort() 102 | canonicalPath = canonicalPath + "?" + strings.Join(sarray, "&") 103 | } 104 | 105 | payload := method + "\n" + md5 + "\n" + ctype + "\n" + date + "\n" + xamz + canonicalPath 106 | hash := hmac.New(sha1.New, []byte(auth.SecretKey)) 107 | hash.Write([]byte(payload)) 108 | signature := make([]byte, b64.EncodedLen(hash.Size())) 109 | b64.Encode(signature, hash.Sum(nil)) 110 | 111 | if expires { 112 | params["Signature"] = []string{string(signature)} 113 | } else { 114 | headers["Authorization"] = []string{"AWS " + auth.AccessKey + ":" + string(signature)} 115 | } 116 | if debug { 117 | log.Printf("Signature payload: %q", payload) 118 | log.Printf("Signature: %q", signature) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /s3/sign_test.go: -------------------------------------------------------------------------------- 1 | package s3_test 2 | 3 | import ( 4 | "github.com/AdRoll/goamz/aws" 5 | "github.com/AdRoll/goamz/s3" 6 | "gopkg.in/check.v1" 7 | ) 8 | 9 | // S3 ReST authentication docs: http://goo.gl/G1LrK 10 | 11 | var testAuth = aws.Auth{AccessKey: "0PN5J17HBGZHT7JJ3X82", SecretKey: "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o"} 12 | 13 | func (s *S) TestSignExampleObjectGet(c *check.C) { 14 | method := "GET" 15 | path := "/johnsmith/photos/puppy.jpg" 16 | headers := map[string][]string{ 17 | "Host": {"johnsmith.s3.amazonaws.com"}, 18 | "Date": {"Tue, 27 Mar 2007 19:36:42 +0000"}, 19 | } 20 | s3.Sign(testAuth, method, path, nil, headers) 21 | expected := "AWS 0PN5J17HBGZHT7JJ3X82:xXjDGYUmKxnwqr5KXNPGldn5LbA=" 22 | c.Assert(headers["Authorization"], check.DeepEquals, []string{expected}) 23 | } 24 | 25 | func (s *S) TestSignExampleObjectPut(c *check.C) { 26 | method := "PUT" 27 | path := "/johnsmith/photos/puppy.jpg" 28 | headers := map[string][]string{ 29 | "Host": {"johnsmith.s3.amazonaws.com"}, 30 | "Date": {"Tue, 27 Mar 2007 21:15:45 +0000"}, 31 | "Content-Type": {"image/jpeg"}, 32 | "Content-Length": {"94328"}, 33 | } 34 | s3.Sign(testAuth, method, path, nil, headers) 35 | expected := "AWS 0PN5J17HBGZHT7JJ3X82:hcicpDDvL9SsO6AkvxqmIWkmOuQ=" 36 | c.Assert(headers["Authorization"], check.DeepEquals, []string{expected}) 37 | } 38 | 39 | func (s *S) TestSignExampleList(c *check.C) { 40 | method := "GET" 41 | path := "/johnsmith/" 42 | params := map[string][]string{ 43 | "prefix": {"photos"}, 44 | "max-keys": {"50"}, 45 | "marker": {"puppy"}, 46 | } 47 | headers := map[string][]string{ 48 | "Host": {"johnsmith.s3.amazonaws.com"}, 49 | "Date": {"Tue, 27 Mar 2007 19:42:41 +0000"}, 50 | "User-Agent": {"Mozilla/5.0"}, 51 | } 52 | s3.Sign(testAuth, method, path, params, headers) 53 | expected := "AWS 0PN5J17HBGZHT7JJ3X82:jsRt/rhG+Vtp88HrYL706QhE4w4=" 54 | c.Assert(headers["Authorization"], check.DeepEquals, []string{expected}) 55 | } 56 | 57 | func (s *S) TestSignExampleFetch(c *check.C) { 58 | method := "GET" 59 | path := "/johnsmith/" 60 | params := map[string][]string{ 61 | "acl": {""}, 62 | } 63 | headers := map[string][]string{ 64 | "Host": {"johnsmith.s3.amazonaws.com"}, 65 | "Date": {"Tue, 27 Mar 2007 19:44:46 +0000"}, 66 | } 67 | s3.Sign(testAuth, method, path, params, headers) 68 | expected := "AWS 0PN5J17HBGZHT7JJ3X82:thdUi9VAkzhkniLj96JIrOPGi0g=" 69 | c.Assert(headers["Authorization"], check.DeepEquals, []string{expected}) 70 | } 71 | 72 | func (s *S) TestSignExampleDelete(c *check.C) { 73 | method := "DELETE" 74 | path := "/johnsmith/photos/puppy.jpg" 75 | params := map[string][]string{} 76 | headers := map[string][]string{ 77 | "Host": {"s3.amazonaws.com"}, 78 | "Date": {"Tue, 27 Mar 2007 21:20:27 +0000"}, 79 | "User-Agent": {"dotnet"}, 80 | "x-amz-date": {"Tue, 27 Mar 2007 21:20:26 +0000"}, 81 | } 82 | s3.Sign(testAuth, method, path, params, headers) 83 | expected := "AWS 0PN5J17HBGZHT7JJ3X82:k3nL7gH3+PadhTEVn5Ip83xlYzk=" 84 | c.Assert(headers["Authorization"], check.DeepEquals, []string{expected}) 85 | } 86 | 87 | func (s *S) TestSignExampleUpload(c *check.C) { 88 | method := "PUT" 89 | path := "/static.johnsmith.net/db-backup.dat.gz" 90 | params := map[string][]string{} 91 | headers := map[string][]string{ 92 | "Host": {"static.johnsmith.net:8080"}, 93 | "Date": {"Tue, 27 Mar 2007 21:06:08 +0000"}, 94 | "User-Agent": {"curl/7.15.5"}, 95 | "x-amz-acl": {"public-read"}, 96 | "content-type": {"application/x-download"}, 97 | "Content-MD5": {"4gJE4saaMU4BqNR0kLY+lw=="}, 98 | "X-Amz-Meta-ReviewedBy": {"joe@johnsmith.net,jane@johnsmith.net"}, 99 | "X-Amz-Meta-FileChecksum": {"0x02661779"}, 100 | "X-Amz-Meta-ChecksumAlgorithm": {"crc32"}, 101 | "Content-Disposition": {"attachment; filename=database.dat"}, 102 | "Content-Encoding": {"gzip"}, 103 | "Content-Length": {"5913339"}, 104 | } 105 | s3.Sign(testAuth, method, path, params, headers) 106 | expected := "AWS 0PN5J17HBGZHT7JJ3X82:C0FlOtU8Ylb9KDTpZqYkZPX91iI=" 107 | c.Assert(headers["Authorization"], check.DeepEquals, []string{expected}) 108 | } 109 | 110 | func (s *S) TestSignExampleListAllMyBuckets(c *check.C) { 111 | method := "GET" 112 | path := "/" 113 | headers := map[string][]string{ 114 | "Host": {"s3.amazonaws.com"}, 115 | "Date": {"Wed, 28 Mar 2007 01:29:59 +0000"}, 116 | } 117 | s3.Sign(testAuth, method, path, nil, headers) 118 | expected := "AWS 0PN5J17HBGZHT7JJ3X82:Db+gepJSUbZKwpx1FR0DLtEYoZA=" 119 | c.Assert(headers["Authorization"], check.DeepEquals, []string{expected}) 120 | } 121 | 122 | func (s *S) TestSignExampleUnicodeKeys(c *check.C) { 123 | method := "GET" 124 | path := "/dictionary/fran%C3%A7ais/pr%c3%a9f%c3%a8re" 125 | headers := map[string][]string{ 126 | "Host": {"s3.amazonaws.com"}, 127 | "Date": {"Wed, 28 Mar 2007 01:49:49 +0000"}, 128 | } 129 | s3.Sign(testAuth, method, path, nil, headers) 130 | expected := "AWS 0PN5J17HBGZHT7JJ3X82:dxhSBHoI6eVSPcXJqEghlUzZMnY=" 131 | c.Assert(headers["Authorization"], check.DeepEquals, []string{expected}) 132 | } 133 | 134 | func (s *S) TestSignExampleCustomSSE(c *check.C) { 135 | method := "GET" 136 | path := "/secret/config" 137 | params := map[string][]string{} 138 | headers := map[string][]string{ 139 | "Host": {"secret.johnsmith.net:8080"}, 140 | "Date": {"Tue, 27 Mar 2007 21:06:08 +0000"}, 141 | "x-amz-server-side-encryption-customer-key": {"MWJhakVna1dQT1B0SDFMeGtVVnRQRTFGaU1ldFJrU0I="}, 142 | "x-amz-server-side-encryption-customer-key-MD5": {"glIqxpqQ4a9aoK/iLttKzQ=="}, 143 | "x-amz-server-side-encryption-customer-algorithm": {"AES256"}, 144 | } 145 | s3.Sign(testAuth, method, path, params, headers) 146 | expected := "AWS 0PN5J17HBGZHT7JJ3X82:Xq6PWmIo0aOWq+LDjCEiCGgbmHE=" 147 | c.Assert(headers["Authorization"], check.DeepEquals, []string{expected}) 148 | } 149 | -------------------------------------------------------------------------------- /sns/http_notifications.go: -------------------------------------------------------------------------------- 1 | package sns 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | const ( 8 | MESSAGE_TYPE_SUBSCRIPTION_CONFIRMATION = "SubscriptionConfirmation" 9 | MESSAGE_TYPE_UNSUBSCRIBE_CONFIRMATION = "UnsubscribeConfirmation" 10 | MESSAGE_TYPE_NOTIFICATION = "Notification" 11 | ) 12 | 13 | // Json http notifications 14 | // SNS posts those to your http url endpoint if http is selected as delivery method. 15 | // http://docs.aws.amazon.com/sns/latest/dg/json-formats.html#http-subscription-confirmation-json 16 | // http://docs.aws.amazon.com/sns/latest/dg/json-formats.html#http-notification-json 17 | // http://docs.aws.amazon.com/sns/latest/dg/json-formats.html#http-unsubscribe-confirmation-json 18 | type HttpNotification struct { 19 | Type string `json:"Type"` 20 | MessageId string `json:"MessageId"` 21 | Token string `json:"Token" optional` // Only for subscribe and unsubscribe 22 | TopicArn string `json:"TopicArn"` 23 | Subject string `json:"Subject" optional` // Only for Notification 24 | Message string `json:"Message"` 25 | SubscribeURL string `json:"SubscribeURL" optional` // Only for subscribe and unsubscribe 26 | Timestamp time.Time `json:"Timestamp"` 27 | SignatureVersion string `json:"SignatureVersion"` 28 | Signature string `json:"Signature"` 29 | SigningCertURL string `json:"SigningCertURL"` 30 | UnsubscribeURL string `json:"UnsubscribeURL" optional` // Only for notifications 31 | } 32 | -------------------------------------------------------------------------------- /sns/http_notifications_test.go: -------------------------------------------------------------------------------- 1 | package sns_test 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/AdRoll/goamz/sns" 6 | "gopkg.in/check.v1" 7 | ) 8 | 9 | var HttpSubscriptionConfirmationRequest = ` 10 | { 11 | "Type" : "SubscriptionConfirmation", 12 | "MessageId" : "165545c9-2a5c-472c-8df2-7ff2be2b3b1b", 13 | "Token" : "2336412f37fb687f5d51e6e241d09c805a5a57b30d712f794cc5f6a988666d92768dd60a747ba6f3beb71854e285d6ad02428b09ceece29417f1f02d609c582afbacc99c583a916b9981dd2728f4ae6fdb82efd087cc3b7849e05798d2d2785c03b0879594eeac82c01f235d0e717736", 14 | "TopicArn" : "arn:aws:sns:us-east-1:123456789012:MyTopic", 15 | "Message" : "You have chosen to subscribe to the topic arn:aws:sns:us-east-1:123456789012:MyTopic.\nTo confirm the subscription, visit the SubscribeURL included in this message.", 16 | "SubscribeURL" : "https://sns.us-east-1.amazonaws.com/?Action=ConfirmSubscription&TopicArn=arn:aws:sns:us-east-1:123456789012:MyTopic&Token=2336412f37fb687f5d51e6e241d09c805a5a57b30d712f794cc5f6a988666d92768dd60a747ba6f3beb71854e285d6ad02428b09ceece29417f1f02d609c582afbacc99c583a916b9981dd2728f4ae6fdb82efd087cc3b7849e05798d2d2785c03b0879594eeac82c01f235d0e717736", 17 | "Timestamp" : "2012-04-26T20:45:04.751Z", 18 | "SignatureVersion" : "1", 19 | "Signature" : "EXAMPLEpH+DcEwjAPg8O9mY8dReBSwksfg2S7WKQcikcNKWLQjwu6A4VbeS0QHVCkhRS7fUQvi2egU3N858fiTDN6bkkOxYDVrY0Ad8L10Hs3zH81mtnPk5uvvolIC1CXGu43obcgFxeL3khZl8IKvO61GWB6jI9b5+gLPoBc1Q=", 20 | "SigningCertURL" : "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem" 21 | } 22 | ` 23 | 24 | var HttpNotificationRequest = ` 25 | { 26 | "Type" : "Notification", 27 | "MessageId" : "22b80b92-fdea-4c2c-8f9d-bdfb0c7bf324", 28 | "TopicArn" : "arn:aws:sns:us-east-1:123456789012:MyTopic", 29 | "Subject" : "My First Message", 30 | "Message" : "Hello world!", 31 | "Timestamp" : "2012-05-02T00:54:06.655Z", 32 | "SignatureVersion" : "1", 33 | "Signature" : "EXAMPLEw6JRNwm1LFQL4ICB0bnXrdB8ClRMTQFGBqwLpGbM78tJ4etTwC5zU7O3tS6tGpey3ejedNdOJ+1fkIp9F2/LmNVKb5aFlYq+9rk9ZiPph5YlLmWsDcyC5T+Sy9/umic5S0UQc2PEtgdpVBahwNOdMW4JPwk0kAJJztnc=", 34 | "SigningCertURL" : "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem", 35 | "UnsubscribeURL" : "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:123456789012:MyTopic:c9135db0-26c4-47ec-8998-413945fb5a96" 36 | } 37 | ` 38 | 39 | var HttpUnsubscribeConfirmationRequest = ` 40 | { 41 | "Type" : "UnsubscribeConfirmation", 42 | "MessageId" : "47138184-6831-46b8-8f7c-afc488602d7d", 43 | "Token" : "2336412f37fb687f5d51e6e241d09c805a5a57b30d712f7948a98bac386edfe3e10314e873973b3e0a3c09119b722dedf2b5e31c59b13edbb26417c19f109351e6f2169efa9085ffe97e10535f4179ac1a03590b0f541f209c190f9ae23219ed6c470453e06c19b5ba9fcbb27daeb7c7", 44 | "TopicArn" : "arn:aws:sns:us-east-1:123456789012:MyTopic", 45 | "Message" : "You have chosen to deactivate subscription arn:aws:sns:us-east-1:123456789012:MyTopic:2bcfbf39-05c3-41de-beaa-fcfcc21c8f55.\nTo cancel this operation and restore the subscription, visit the SubscribeURL included in this message.", 46 | "SubscribeURL" : "https://sns.us-east-1.amazonaws.com/?Action=ConfirmSubscription&TopicArn=arn:aws:sns:us-east-1:123456789012:MyTopic&Token=2336412f37fb687f5d51e6e241d09c805a5a57b30d712f7948a98bac386edfe3e10314e873973b3e0a3c09119b722dedf2b5e31c59b13edbb26417c19f109351e6f2169efa9085ffe97e10535f4179ac1a03590b0f541f209c190f9ae23219ed6c470453e06c19b5ba9fcbb27daeb7c7", 47 | "Timestamp" : "2012-04-26T20:06:41.581Z", 48 | "SignatureVersion" : "1", 49 | "Signature" : "EXAMPLEHXgJmXqnqsHTlqOCk7TIZsnk8zpJJoQbr8leD+8kAHcke3ClC4VPOvdpZo9s/vR9GOznKab6sjGxE8uwqDI9HwpDm8lGxSlFGuwCruWeecnt7MdJCNh0XK4XQCbtGoXB762ePJfaSWi9tYwzW65zAFU04WkNBkNsIf60=", 50 | "SigningCertURL" : "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem" 51 | } 52 | ` 53 | 54 | func (s *S) TestHttpSubscriptionConfirmationRequestUnmarshalling(c *check.C) { 55 | notification := sns.HttpNotification{} 56 | err := json.Unmarshal([]byte(HttpSubscriptionConfirmationRequest), ¬ification) 57 | c.Assert(err, check.IsNil) 58 | c.Assert(notification.Token, check.NotNil) 59 | c.Assert(notification.Subject, check.Equals, "") 60 | c.Assert(notification.SubscribeURL, check.NotNil) 61 | c.Assert(notification.UnsubscribeURL, check.Equals, "") 62 | c.Assert(notification.Type, check.Equals, sns.MESSAGE_TYPE_SUBSCRIPTION_CONFIRMATION) 63 | } 64 | 65 | func (s *S) TestHttpNotificationRequestUnmarshalling(c *check.C) { 66 | notification := sns.HttpNotification{} 67 | err := json.Unmarshal([]byte(HttpNotificationRequest), ¬ification) 68 | c.Assert(err, check.IsNil) 69 | c.Assert(notification.Token, check.Equals, "") 70 | c.Assert(notification.Subject, check.NotNil) 71 | c.Assert(notification.SubscribeURL, check.Equals, "") 72 | c.Assert(notification.UnsubscribeURL, check.NotNil) 73 | c.Assert(notification.Type, check.Equals, sns.MESSAGE_TYPE_NOTIFICATION) 74 | } 75 | 76 | func (s *S) TestHttpUnsubscribeConfirmationRequestUnmarshalling(c *check.C) { 77 | notification := sns.HttpNotification{} 78 | err := json.Unmarshal([]byte(HttpUnsubscribeConfirmationRequest), ¬ification) 79 | c.Assert(err, check.IsNil) 80 | c.Assert(notification.Token, check.NotNil) 81 | c.Assert(notification.Subject, check.Equals, "") 82 | c.Assert(notification.SubscribeURL, check.NotNil) 83 | c.Assert(notification.UnsubscribeURL, check.Equals, "") 84 | c.Assert(notification.Type, check.Equals, sns.MESSAGE_TYPE_UNSUBSCRIBE_CONFIRMATION) 85 | } 86 | -------------------------------------------------------------------------------- /sns/structs.go: -------------------------------------------------------------------------------- 1 | package sns 2 | 3 | import ( 4 | "github.com/AdRoll/goamz/aws" 5 | ) 6 | 7 | type Topic struct { 8 | TopicArn string 9 | } 10 | 11 | type Subscription struct { 12 | Endpoint string 13 | Owner string 14 | Protocol string 15 | SubscriptionArn string 16 | TopicArn string 17 | } 18 | 19 | type Attribute struct { 20 | Key string `xml:"key"` 21 | Value string `xml:"value"` 22 | } 23 | 24 | type Permission struct { 25 | ActionName string 26 | AccountId string 27 | } 28 | 29 | type PlatformApplication struct { 30 | Attributes []Attribute `xml:"Attributes>entry"` 31 | PlatformApplicationArn string 32 | } 33 | 34 | type Endpoint struct { 35 | EndpointArn string `xml:"EndpointArn"` 36 | Attributes []Attribute `xml:"Attributes>entry"` 37 | } 38 | 39 | // ============ Request ============ 40 | 41 | type PublishOptions struct { 42 | Message string 43 | MessageStructure string 44 | Subject string 45 | TopicArn string 46 | TargetArn string 47 | } 48 | 49 | type PlatformEndpointOptions struct { 50 | Attributes []Attribute 51 | PlatformApplicationArn string 52 | CustomUserData string 53 | Token string 54 | } 55 | 56 | // ============ Response ============ 57 | 58 | type ListTopicsResponse struct { 59 | NextToken string `xml:"ListTopicsResult>NextToken"` 60 | Topics []Topic `xml:"ListTopicsResult>Topics>member"` 61 | ResponseMetadata aws.ResponseMetadata 62 | Error aws.Error 63 | } 64 | 65 | type CreateTopicResponse struct { 66 | Topic Topic `xml:"CreateTopicResult"` 67 | ResponseMetadata aws.ResponseMetadata 68 | } 69 | 70 | type DeleteTopicResponse struct { 71 | ResponseMetadata aws.ResponseMetadata 72 | } 73 | 74 | type ListSubscriptionsResponse struct { 75 | NextToken string `xml:"ListSubscriptionsResult>NextToken"` 76 | Subscriptions []Subscription `xml:"ListSubscriptionsResult>Subscriptions>member"` 77 | ResponseMetadata aws.ResponseMetadata 78 | } 79 | 80 | type GetTopicAttributesResponse struct { 81 | Attributes []Attribute `xml:"GetTopicAttributesResult>Attributes>entry"` 82 | ResponseMetadata aws.ResponseMetadata 83 | } 84 | 85 | type SetTopicAttributesResponse struct { 86 | ResponseMetadata aws.ResponseMetadata 87 | } 88 | 89 | type PublishResponse struct { 90 | MessageId string `xml:"PublishResult>MessageId"` 91 | ResponseMetadata aws.ResponseMetadata 92 | } 93 | 94 | type SubscribeResponse struct { 95 | SubscriptionArn string `xml:"SubscribeResult>SubscriptionArn"` 96 | ResponseMetadata aws.ResponseMetadata 97 | } 98 | 99 | type UnsubscribeResponse struct { 100 | ResponseMetadata aws.ResponseMetadata 101 | } 102 | 103 | type GetSubscriptionAttributesResponse struct { 104 | Attributes []Attribute `xml:"GetSubscriptionAttributesResult>Attributes>entry"` 105 | ResponseMetadata aws.ResponseMetadata 106 | } 107 | 108 | type SetSubscriptionAttributesResponse struct { 109 | ResponseMetadata aws.ResponseMetadata 110 | } 111 | 112 | type ConfirmSubscriptionResponse struct { 113 | SubscriptionArn string `xml:"ConfirmSubscriptionResult>SubscriptionArn"` 114 | ResponseMetadata aws.ResponseMetadata 115 | } 116 | 117 | type AddPermissionResponse struct { 118 | ResponseMetadata aws.ResponseMetadata 119 | } 120 | 121 | type RemovePermissionResponse struct { 122 | ResponseMetadata aws.ResponseMetadata 123 | } 124 | 125 | type ListSubscriptionByTopicResponse struct { 126 | NextToken string `xml:"ListSubscriptionsByTopicResult>NextToken"` 127 | Subscriptions []Subscription `xml:"ListSubscriptionsByTopicResult>Subscriptions>member"` 128 | ResponseMetadata aws.ResponseMetadata 129 | } 130 | 131 | type CreatePlatformApplicationResponse struct { 132 | PlatformApplicationArn string `xml:"CreatePlatformApplicationResult>PlatformApplicationArn"` 133 | ResponseMetadata aws.ResponseMetadata 134 | } 135 | 136 | type CreatePlatformEndpointResponse struct { 137 | EndpointArn string `xml:"CreatePlatformEndpointResult>EndpointArn"` 138 | ResponseMetadata aws.ResponseMetadata 139 | } 140 | 141 | type DeleteEndpointResponse struct { 142 | ResponseMetadata aws.ResponseMetadata 143 | } 144 | 145 | type DeletePlatformApplicationResponse struct { 146 | ResponseMetadata aws.ResponseMetadata 147 | } 148 | 149 | type GetEndpointAttributesResponse struct { 150 | Attributes []Attribute `xml:"GetEndpointAttributesResult>Attributes>entry"` 151 | ResponseMetadata aws.ResponseMetadata 152 | } 153 | 154 | type GetPlatformApplicationAttributesResponse struct { 155 | Attributes []Attribute `xml:"GetPlatformApplicationAttributesResult>Attributes>entry"` 156 | ResponseMetadata aws.ResponseMetadata 157 | } 158 | 159 | type ListEndpointsByPlatformApplicationResponse struct { 160 | NextToken string `xml:"ListEndpointsByPlatformApplicationResult>NextToken"` 161 | Endpoints []Endpoint `xml:"ListEndpointsByPlatformApplicationResult>Endpoints>member"` 162 | ResponseMetadata aws.ResponseMetadata 163 | } 164 | 165 | type ListPlatformApplicationsResponse struct { 166 | NextToken string `xml:"ListPlatformApplicationsResult>NextToken"` 167 | PlatformApplications []PlatformApplication `xml:"ListPlatformApplicationsResult>PlatformApplications>member"` 168 | ResponseMetadata aws.ResponseMetadata 169 | } 170 | 171 | type SetEndpointAttributesResponse struct { 172 | ResponseMetadata aws.ResponseMetadata 173 | } 174 | 175 | type SetPlatformApplicationAttributesResponse struct { 176 | ResponseMetadata aws.ResponseMetadata 177 | } 178 | -------------------------------------------------------------------------------- /sqs/Makefile: -------------------------------------------------------------------------------- 1 | include $(GOROOT)/src/Make.inc 2 | 3 | TARG=launchpad.net/goamz/sqs 4 | 5 | GOFILES=\ 6 | sqs.go\ 7 | sign.go\ 8 | 9 | include $(GOROOT)/src/Make.pkg 10 | 11 | GOFMT=gofmt 12 | BADFMT=$(shell $(GOFMT) -l $(GOFILES) 2> /dev/null) 13 | 14 | gofmt: $(BADFMT) 15 | @for F in $(BADFMT); do $(GOFMT) -w $$F && echo $$F; done 16 | 17 | ifneq ($(BADFMT),) 18 | ifneq ($(MAKECMDGOALS), gofmt) 19 | #$(warning WARNING: make gofmt: $(BADFMT)) 20 | endif 21 | endif 22 | -------------------------------------------------------------------------------- /sqs/README.md: -------------------------------------------------------------------------------- 1 | Amazon Simple Queue Service API Client Written in Golang. 2 | ========================================================= 3 | 4 | Merged from https://github.com/Mistobaan/sqs 5 | 6 | Installation 7 | ------------ 8 | 9 | go get github.com/AdRoll/goamz/sqs 10 | 11 | 12 | Testing 13 | ------- 14 | 15 | go test . 16 | -------------------------------------------------------------------------------- /sqs/md5.go: -------------------------------------------------------------------------------- 1 | package sqs 2 | 3 | /* 4 | * Performs the MD5 algorithm for attribute responses described in 5 | * the AWS documentation here: http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/SQSMessageAttributes.html#sqs-attrib-md5 6 | */ 7 | 8 | import ( 9 | "crypto/md5" 10 | "encoding/binary" 11 | "sort" 12 | ) 13 | 14 | // Returns length of string as an Big Endian byte array 15 | func getStringLengthAsByteArray(s string) []byte { 16 | var res []byte = make([]byte, 4) 17 | binary.BigEndian.PutUint32(res, uint32(len(s))) 18 | 19 | return res 20 | } 21 | 22 | // How to calculate the MD5 of Attributes 23 | func calculateAttributeMD5(attributes map[string]string) []byte { 24 | 25 | // We're going to walk attributes in alpha-sorted order 26 | var keys []string 27 | 28 | for k := range attributes { 29 | keys = append(keys, k) 30 | } 31 | 32 | sort.Strings(keys) 33 | 34 | // Now we'll build our encoded string 35 | var encoded []byte 36 | 37 | for _, k := range keys { 38 | v := attributes[k] 39 | t := "String" 40 | 41 | encodedItems := [][]byte{ 42 | getStringLengthAsByteArray(k), 43 | []byte(k), // Name 44 | getStringLengthAsByteArray(t), 45 | []byte(t), // Data Type ("String") 46 | []byte{0x01}, // "String Value" (0x01) 47 | getStringLengthAsByteArray(v), 48 | []byte(v), // Value 49 | } 50 | 51 | // Append each of these to our encoding 52 | for _, item := range encodedItems { 53 | encoded = append(encoded, item...) 54 | } 55 | } 56 | 57 | res := md5.Sum(encoded) 58 | 59 | // Return MD5 sum 60 | return res[:] 61 | } 62 | -------------------------------------------------------------------------------- /sqs/suite_test.go: -------------------------------------------------------------------------------- 1 | package sqs 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/AdRoll/goamz/aws" 7 | "gopkg.in/check.v1" 8 | "net/http" 9 | "net/url" 10 | "os" 11 | "testing" 12 | "time" 13 | ) 14 | 15 | func Test(t *testing.T) { 16 | check.TestingT(t) 17 | } 18 | 19 | var integration = flag.Bool("i", false, "Enable integration tests") 20 | 21 | type SuiteI struct { 22 | auth aws.Auth 23 | } 24 | 25 | func (s *SuiteI) SetUpSuite(c *check.C) { 26 | if !*integration { 27 | c.Skip("Integration tests not enabled (-i flag)") 28 | } 29 | auth, err := aws.EnvAuth() 30 | if err != nil { 31 | c.Fatal(err.Error()) 32 | } 33 | s.auth = auth 34 | } 35 | 36 | type HTTPSuite struct{} 37 | 38 | var testServer = NewTestHTTPServer("http://localhost:4455", 5e9) 39 | 40 | func (s *HTTPSuite) SetUpSuite(c *check.C) { 41 | testServer.Start() 42 | } 43 | 44 | func (s *HTTPSuite) TearDownTest(c *check.C) { 45 | testServer.FlushRequests() 46 | } 47 | 48 | type TestHTTPServer struct { 49 | URL string 50 | Timeout time.Duration 51 | started bool 52 | request chan *http.Request 53 | response chan *testResponse 54 | pending chan bool 55 | } 56 | 57 | type testResponse struct { 58 | Status int 59 | Headers map[string]string 60 | Body string 61 | } 62 | 63 | func NewTestHTTPServer(url string, timeout time.Duration) *TestHTTPServer { 64 | return &TestHTTPServer{URL: url, Timeout: timeout} 65 | } 66 | 67 | func (s *TestHTTPServer) Start() { 68 | if s.started { 69 | return 70 | } 71 | s.started = true 72 | 73 | s.request = make(chan *http.Request, 64) 74 | s.response = make(chan *testResponse, 64) 75 | s.pending = make(chan bool, 64) 76 | 77 | url, _ := url.Parse(s.URL) 78 | go func() { 79 | err := http.ListenAndServe(url.Host, s) 80 | if err != nil { 81 | panic(err) 82 | } 83 | }() 84 | 85 | s.PrepareResponse(202, nil, "Nothing.") 86 | for { 87 | // Wait for it to be up. 88 | resp, err := http.Get(s.URL) 89 | if err == nil && resp.StatusCode == 202 { 90 | break 91 | } 92 | fmt.Fprintf(os.Stderr, "\nWaiting for fake server to be up... ") 93 | time.Sleep(1e8) 94 | } 95 | fmt.Fprintf(os.Stderr, "done\n\n") 96 | s.WaitRequest() // Consume dummy request. 97 | } 98 | 99 | // FlushRequests discards requests which were not yet consumed by WaitRequest. 100 | func (s *TestHTTPServer) FlushRequests() { 101 | for { 102 | select { 103 | case <-s.request: 104 | default: 105 | return 106 | } 107 | } 108 | } 109 | 110 | func (s *TestHTTPServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { 111 | req.ParseForm() 112 | s.request <- req 113 | var resp *testResponse 114 | select { 115 | case resp = <-s.response: 116 | case <-time.After(s.Timeout): 117 | fmt.Fprintf(os.Stderr, "ERROR: Timeout waiting for test to provide response\n") 118 | resp = &testResponse{500, nil, ""} 119 | } 120 | if resp.Headers != nil { 121 | h := w.Header() 122 | for k, v := range resp.Headers { 123 | h.Set(k, v) 124 | } 125 | } 126 | if resp.Status != 0 { 127 | w.WriteHeader(resp.Status) 128 | } 129 | w.Write([]byte(resp.Body)) 130 | } 131 | 132 | func (s *TestHTTPServer) WaitRequest() *http.Request { 133 | select { 134 | case req := <-s.request: 135 | return req 136 | case <-time.After(s.Timeout): 137 | panic("Timeout waiting for goamz request") 138 | } 139 | panic("unreached") 140 | } 141 | 142 | func (s *TestHTTPServer) PrepareResponse(status int, headers map[string]string, body string) { 143 | s.response <- &testResponse{status, headers, body} 144 | } 145 | -------------------------------------------------------------------------------- /sts/responses_test.go: -------------------------------------------------------------------------------- 1 | package sts_test 2 | 3 | var AssumeRoleResponse = ` 4 | 6 | 7 | 8 | 9 | AQoDYXdzEPT//////////wEXAMPLEtc764bNrC9SAPBSM22wDOk4x4HIZ8j4FZTwdQW 10 | LWsKWHGBuFqwAeMicRXmxfpSPfIeoIYRqTflfKD8YUuwthAx7mSEI/qkPpKPi/kMcGd 11 | QrmGdeehM4IC1NtBmUpp2wUE8phUZampKsburEDy0KPkyQDYwT7WZ0wq5VSXDvp75YU 12 | 9HFvlRd8Tx6q6fE8YQcHNVXAkiY9q6d+xo0rKwT38xVqr7ZD0u0iPPkUL64lIZbqBAz 13 | +scqKmlzm8FDrypNC9Yjc8fPOLn9FX9KSYvKTr4rvx3iSIlTJabIQwj2ICCR/oLxBA== 14 | 15 | 16 | wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY 17 | 18 | 2011-07-15T23:28:33.359Z 19 | AKIAIOSFODNN7EXAMPLE 20 | 21 | 22 | arn:aws:sts::123456789012:assumed-role/demo/Bob 23 | ARO123EXAMPLE123:Bob 24 | 25 | 6 26 | 27 | 28 | c6104cbe-af31-11e0-8154-cbc7ccf896c7 29 | 30 | 31 | ` 32 | 33 | var GetFederationTokenResponse = ` 34 | 36 | 37 | 38 | 39 | AQoDYXdzEPT//////////wEXAMPLEtc764bNrC9SAPBSM22wDOk4x4HIZ8j4FZTwdQW 40 | LWsKWHGBuFqwAeMicRXmxfpSPfIeoIYRqTflfKD8YUuwthAx7mSEI/qkPpKPi/kMcGd 41 | QrmGdeehM4IC1NtBmUpp2wUE8phUZampKsburEDy0KPkyQDYwT7WZ0wq5VSXDvp75YU 42 | 9HFvlRd8Tx6q6fE8YQcHNVXAkiY9q6d+xo0rKwT38xVqr7ZD0u0iPPkUL64lIZbqBAz 43 | +scqKmlzm8FDrypNC9Yjc8fPOLn9FX9KSYvKTr4rvx3iSIlTJabIQwj2ICCR/oLxBA== 44 | 45 | 46 | wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY 47 | 48 | 2011-07-15T23:28:33.359Z 49 | AKIAIOSFODNN7EXAMPLE 50 | 51 | 52 | arn:aws:sts::123456789012:federated-user/Bob 53 | 123456789012:Bob 54 | 55 | 6 56 | 57 | 58 | c6104cbe-af31-11e0-8154-cbc7ccf896c7 59 | 60 | 61 | ` 62 | 63 | var GetSessionTokenResponse = ` 64 | 65 | 66 | 67 | 68 | AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT+FvwqnKwRcOIfrRh3c/L 69 | To6UDdyJwOOvEVPvLXCrrrUtdnniCEXAMPLE/IvU1dYUg2RVAJBanLiHb4IgRmpRV3z 70 | rkuWJOgQs8IZZaIv2BXIa2R4OlgkBN9bkUDNCJiBeb/AXlzBBko7b15fjrBs2+cTQtp 71 | Z3CYWFXG8C5zqx37wnOE49mRl/+OtkIKGO7fAE 72 | 73 | 74 | wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY 75 | 76 | 2011-07-11T19:55:29.611Z 77 | AKIAIOSFODNN7EXAMPLE 78 | 79 | 80 | 81 | 58c5dbae-abef-11e0-8cfe-09039844ac7d 82 | 83 | 84 | ` 85 | -------------------------------------------------------------------------------- /sts/sts_test.go: -------------------------------------------------------------------------------- 1 | package sts_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "gopkg.in/check.v1" 8 | 9 | "github.com/AdRoll/goamz/aws" 10 | "github.com/AdRoll/goamz/sts" 11 | "github.com/AdRoll/goamz/testutil" 12 | ) 13 | 14 | func Test(t *testing.T) { 15 | check.TestingT(t) 16 | } 17 | 18 | var _ = check.Suite(&S{}) 19 | 20 | type S struct { 21 | sts *sts.STS 22 | } 23 | 24 | var testServer = testutil.NewHTTPServer() 25 | 26 | var mockTest bool 27 | 28 | func (s *S) SetUpSuite(c *check.C) { 29 | testServer.Start() 30 | auth := aws.Auth{AccessKey: "abc", SecretKey: "123"} 31 | s.sts = sts.New(auth, aws.Region{STSEndpoint: testServer.URL}) 32 | } 33 | 34 | func (s *S) TearDownTest(c *check.C) { 35 | testServer.Flush() 36 | } 37 | 38 | func (s *S) TestAssumeRole(c *check.C) { 39 | testServer.Response(200, nil, AssumeRoleResponse) 40 | request := &sts.AssumeRoleParams{ 41 | DurationSeconds: 3600, 42 | ExternalId: "123ABC", 43 | Policy: `{"Version":"2012-10-17","Statement":[{"Sid":"Stmt1","Effect":"Allow","Action":"s3:*","Resource":"*"}]}`, 44 | RoleArn: "arn:aws:iam::123456789012:role/demo", 45 | RoleSessionName: "Bob", 46 | } 47 | resp, err := s.sts.AssumeRole(request) 48 | c.Assert(err, check.IsNil) 49 | values := testServer.WaitRequest().PostForm 50 | // Post request test 51 | c.Assert(values.Get("Version"), check.Equals, "2011-06-15") 52 | c.Assert(values.Get("Action"), check.Equals, "AssumeRole") 53 | c.Assert(values.Get("DurationSeconds"), check.Equals, "3600") 54 | c.Assert(values.Get("ExternalId"), check.Equals, "123ABC") 55 | c.Assert(values.Get("Policy"), check.Equals, `{"Version":"2012-10-17","Statement":[{"Sid":"Stmt1","Effect":"Allow","Action":"s3:*","Resource":"*"}]}`) 56 | c.Assert(values.Get("RoleArn"), check.Equals, "arn:aws:iam::123456789012:role/demo") 57 | c.Assert(values.Get("RoleSessionName"), check.Equals, "Bob") 58 | // Response test 59 | exp, _ := time.Parse(time.RFC3339, "2011-07-15T23:28:33.359Z") 60 | c.Assert(resp.RequestId, check.Equals, "c6104cbe-af31-11e0-8154-cbc7ccf896c7") 61 | c.Assert(resp.PackedPolicySize, check.Equals, 6) 62 | c.Assert(resp.AssumedRoleUser, check.DeepEquals, sts.AssumedRoleUser{ 63 | Arn: "arn:aws:sts::123456789012:assumed-role/demo/Bob", 64 | AssumedRoleId: "ARO123EXAMPLE123:Bob", 65 | }) 66 | c.Assert(resp.Credentials, check.DeepEquals, sts.Credentials{ 67 | SessionToken: ` 68 | AQoDYXdzEPT//////////wEXAMPLEtc764bNrC9SAPBSM22wDOk4x4HIZ8j4FZTwdQW 69 | LWsKWHGBuFqwAeMicRXmxfpSPfIeoIYRqTflfKD8YUuwthAx7mSEI/qkPpKPi/kMcGd 70 | QrmGdeehM4IC1NtBmUpp2wUE8phUZampKsburEDy0KPkyQDYwT7WZ0wq5VSXDvp75YU 71 | 9HFvlRd8Tx6q6fE8YQcHNVXAkiY9q6d+xo0rKwT38xVqr7ZD0u0iPPkUL64lIZbqBAz 72 | +scqKmlzm8FDrypNC9Yjc8fPOLn9FX9KSYvKTr4rvx3iSIlTJabIQwj2ICCR/oLxBA== 73 | `, 74 | SecretAccessKey: ` 75 | wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY 76 | `, 77 | AccessKeyId: "AKIAIOSFODNN7EXAMPLE", 78 | Expiration: exp, 79 | }) 80 | 81 | } 82 | 83 | func (s *S) TestGetFederationToken(c *check.C) { 84 | testServer.Response(200, nil, GetFederationTokenResponse) 85 | resp, err := s.sts.GetFederationToken( 86 | "Bob", 87 | `{"Version":"2012-10-17","Statement":[{"Sid":"Stmt1","Effect":"Allow","Action":"s3:*","Resource":"*"}]}`, 88 | 3600, 89 | ) 90 | c.Assert(err, check.IsNil) 91 | values := testServer.WaitRequest().PostForm 92 | // Post request test 93 | c.Assert(values.Get("Version"), check.Equals, "2011-06-15") 94 | c.Assert(values.Get("Action"), check.Equals, "GetFederationToken") 95 | c.Assert(values.Get("DurationSeconds"), check.Equals, "3600") 96 | c.Assert(values.Get("Policy"), check.Equals, `{"Version":"2012-10-17","Statement":[{"Sid":"Stmt1","Effect":"Allow","Action":"s3:*","Resource":"*"}]}`) 97 | c.Assert(values.Get("Name"), check.Equals, "Bob") 98 | // Response test 99 | exp, _ := time.Parse(time.RFC3339, "2011-07-15T23:28:33.359Z") 100 | c.Assert(resp.RequestId, check.Equals, "c6104cbe-af31-11e0-8154-cbc7ccf896c7") 101 | c.Assert(resp.PackedPolicySize, check.Equals, 6) 102 | c.Assert(resp.FederatedUser, check.DeepEquals, sts.FederatedUser{ 103 | Arn: "arn:aws:sts::123456789012:federated-user/Bob", 104 | FederatedUserId: "123456789012:Bob", 105 | }) 106 | c.Assert(resp.Credentials, check.DeepEquals, sts.Credentials{ 107 | SessionToken: ` 108 | AQoDYXdzEPT//////////wEXAMPLEtc764bNrC9SAPBSM22wDOk4x4HIZ8j4FZTwdQW 109 | LWsKWHGBuFqwAeMicRXmxfpSPfIeoIYRqTflfKD8YUuwthAx7mSEI/qkPpKPi/kMcGd 110 | QrmGdeehM4IC1NtBmUpp2wUE8phUZampKsburEDy0KPkyQDYwT7WZ0wq5VSXDvp75YU 111 | 9HFvlRd8Tx6q6fE8YQcHNVXAkiY9q6d+xo0rKwT38xVqr7ZD0u0iPPkUL64lIZbqBAz 112 | +scqKmlzm8FDrypNC9Yjc8fPOLn9FX9KSYvKTr4rvx3iSIlTJabIQwj2ICCR/oLxBA== 113 | `, 114 | SecretAccessKey: ` 115 | wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY 116 | `, 117 | AccessKeyId: "AKIAIOSFODNN7EXAMPLE", 118 | Expiration: exp, 119 | }) 120 | 121 | } 122 | 123 | func (s *S) TestGetSessionToken(c *check.C) { 124 | testServer.Response(200, nil, GetSessionTokenResponse) 125 | resp, err := s.sts.GetSessionToken(3600, "YourMFADeviceSerialNumber", "123456") 126 | c.Assert(err, check.IsNil) 127 | values := testServer.WaitRequest().PostForm 128 | // Post request test 129 | c.Assert(values.Get("Version"), check.Equals, "2011-06-15") 130 | c.Assert(values.Get("Action"), check.Equals, "GetSessionToken") 131 | c.Assert(values.Get("DurationSeconds"), check.Equals, "3600") 132 | c.Assert(values.Get("SerialNumber"), check.Equals, "YourMFADeviceSerialNumber") 133 | c.Assert(values.Get("TokenCode"), check.Equals, "123456") 134 | // Response test 135 | exp, _ := time.Parse(time.RFC3339, "2011-07-11T19:55:29.611Z") 136 | c.Assert(resp.RequestId, check.Equals, "58c5dbae-abef-11e0-8cfe-09039844ac7d") 137 | c.Assert(resp.Credentials, check.DeepEquals, sts.Credentials{ 138 | SessionToken: ` 139 | AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT+FvwqnKwRcOIfrRh3c/L 140 | To6UDdyJwOOvEVPvLXCrrrUtdnniCEXAMPLE/IvU1dYUg2RVAJBanLiHb4IgRmpRV3z 141 | rkuWJOgQs8IZZaIv2BXIa2R4OlgkBN9bkUDNCJiBeb/AXlzBBko7b15fjrBs2+cTQtp 142 | Z3CYWFXG8C5zqx37wnOE49mRl/+OtkIKGO7fAE 143 | `, 144 | SecretAccessKey: ` 145 | wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY 146 | `, 147 | AccessKeyId: "AKIAIOSFODNN7EXAMPLE", 148 | Expiration: exp, 149 | }) 150 | 151 | } 152 | -------------------------------------------------------------------------------- /testutil/http.go: -------------------------------------------------------------------------------- 1 | package testutil 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net" 8 | "net/http" 9 | "net/url" 10 | "os" 11 | "time" 12 | ) 13 | 14 | type HTTPServer struct { 15 | URL string 16 | Timeout time.Duration 17 | started bool 18 | request chan *http.Request 19 | response chan ResponseFunc 20 | } 21 | 22 | type Response struct { 23 | Status int 24 | Headers map[string]string 25 | Body string 26 | } 27 | 28 | func NewHTTPServer() *HTTPServer { 29 | return &HTTPServer{URL: "http://localhost:4444", Timeout: 5 * time.Second} 30 | } 31 | 32 | type ResponseFunc func(path string) Response 33 | 34 | func (s *HTTPServer) Start() { 35 | if s.started { 36 | return 37 | } 38 | s.started = true 39 | s.request = make(chan *http.Request, 1024) 40 | s.response = make(chan ResponseFunc, 1024) 41 | u, err := url.Parse(s.URL) 42 | if err != nil { 43 | panic(err) 44 | } 45 | l, err := net.Listen("tcp", u.Host) 46 | if err != nil { 47 | panic(err) 48 | } 49 | go http.Serve(l, s) 50 | 51 | s.Response(203, nil, "") 52 | for { 53 | // Wait for it to be up. 54 | resp, err := http.Get(s.URL) 55 | if err == nil && resp.StatusCode == 203 { 56 | break 57 | } 58 | time.Sleep(1e8) 59 | } 60 | s.WaitRequest() // Consume dummy request. 61 | } 62 | 63 | // Flush discards all pending requests and responses. 64 | func (s *HTTPServer) Flush() { 65 | for { 66 | select { 67 | case <-s.request: 68 | case <-s.response: 69 | default: 70 | return 71 | } 72 | } 73 | } 74 | 75 | func body(req *http.Request) string { 76 | data, err := ioutil.ReadAll(req.Body) 77 | if err != nil { 78 | panic(err) 79 | } 80 | return string(data) 81 | } 82 | 83 | func (s *HTTPServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { 84 | req.ParseMultipartForm(1e6) 85 | data, err := ioutil.ReadAll(req.Body) 86 | if err != nil { 87 | panic(err) 88 | } 89 | req.Body = ioutil.NopCloser(bytes.NewBuffer(data)) 90 | s.request <- req 91 | var resp Response 92 | select { 93 | case respFunc := <-s.response: 94 | resp = respFunc(req.URL.Path) 95 | case <-time.After(s.Timeout): 96 | const msg = "ERROR: Timeout waiting for test to prepare a response\n" 97 | fmt.Fprintf(os.Stderr, msg) 98 | resp = Response{500, nil, msg} 99 | } 100 | if resp.Headers != nil { 101 | h := w.Header() 102 | for k, v := range resp.Headers { 103 | h.Set(k, v) 104 | } 105 | } 106 | if resp.Status != 0 { 107 | w.WriteHeader(resp.Status) 108 | } 109 | w.Write([]byte(resp.Body)) 110 | } 111 | 112 | // WaitRequests returns the next n requests made to the http server from 113 | // the queue. If not enough requests were previously made, it waits until 114 | // the timeout value for them to be made. 115 | func (s *HTTPServer) WaitRequests(n int) []*http.Request { 116 | reqs := make([]*http.Request, 0, n) 117 | for i := 0; i < n; i++ { 118 | select { 119 | case req := <-s.request: 120 | reqs = append(reqs, req) 121 | case <-time.After(s.Timeout): 122 | panic("Timeout waiting for request") 123 | } 124 | } 125 | return reqs 126 | } 127 | 128 | // WaitRequest returns the next request made to the http server from 129 | // the queue. If no requests were previously made, it waits until the 130 | // timeout value for one to be made. 131 | func (s *HTTPServer) WaitRequest() *http.Request { 132 | return s.WaitRequests(1)[0] 133 | } 134 | 135 | // ResponseFunc prepares the test server to respond the following n 136 | // requests using f to build each response. 137 | func (s *HTTPServer) ResponseFunc(n int, f ResponseFunc) { 138 | for i := 0; i < n; i++ { 139 | s.response <- f 140 | } 141 | } 142 | 143 | // ResponseMap maps request paths to responses. 144 | type ResponseMap map[string]Response 145 | 146 | // ResponseMap prepares the test server to respond the following n 147 | // requests using the m to obtain the responses. 148 | func (s *HTTPServer) ResponseMap(n int, m ResponseMap) { 149 | f := func(path string) Response { 150 | for rpath, resp := range m { 151 | if rpath == path { 152 | return resp 153 | } 154 | } 155 | body := "Path not found in response map: " + path 156 | return Response{Status: 500, Body: body} 157 | } 158 | s.ResponseFunc(n, f) 159 | } 160 | 161 | // Responses prepares the test server to respond the following n requests 162 | // using the provided response parameters. 163 | func (s *HTTPServer) Responses(n int, status int, headers map[string]string, body string) { 164 | f := func(path string) Response { 165 | return Response{status, headers, body} 166 | } 167 | s.ResponseFunc(n, f) 168 | } 169 | 170 | // Response prepares the test server to respond the following request 171 | // using the provided response parameters. 172 | func (s *HTTPServer) Response(status int, headers map[string]string, body string) { 173 | s.Responses(1, status, headers, body) 174 | } 175 | -------------------------------------------------------------------------------- /testutil/suite.go: -------------------------------------------------------------------------------- 1 | package testutil 2 | 3 | import ( 4 | "flag" 5 | "github.com/AdRoll/goamz/aws" 6 | "gopkg.in/check.v1" 7 | ) 8 | 9 | // Amazon must be used by all tested packages to determine whether to 10 | // run functional tests against the real AWS servers. 11 | var Amazon bool 12 | 13 | func init() { 14 | flag.BoolVar(&Amazon, "amazon", false, "Enable tests against amazon server") 15 | } 16 | 17 | type LiveSuite struct { 18 | auth aws.Auth 19 | } 20 | 21 | func (s *LiveSuite) SetUpSuite(c *check.C) { 22 | if !Amazon { 23 | c.Skip("amazon tests not enabled (-amazon flag)") 24 | } 25 | auth, err := aws.EnvAuth() 26 | if err != nil { 27 | c.Fatal(err.Error()) 28 | } 29 | s.auth = auth 30 | } 31 | --------------------------------------------------------------------------------