mirror of
https://github.com/docker/docs.git
synced 2026-11-05 00:14:17 +03:00
Vendor goamz to avoid bzr build dependency
Signed-off-by: Solomon Hykes <solomon@docker.com>
This commit is contained in:
1
vendor/src/launchpad.net/goamz/.lbox
vendored
Normal file
1
vendor/src/launchpad.net/goamz/.lbox
vendored
Normal file
@@ -0,0 +1 @@
|
||||
propose -for=lp:goamz -cr
|
||||
185
vendor/src/launchpad.net/goamz/LICENSE
vendored
Normal file
185
vendor/src/launchpad.net/goamz/LICENSE
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
This software is licensed under the LGPLv3, included below.
|
||||
|
||||
As a special exception to the GNU Lesser General Public License version 3
|
||||
("LGPL3"), the copyright holders of this Library give you permission to
|
||||
convey to a third party a Combined Work that links statically or dynamically
|
||||
to this Library without providing any Minimal Corresponding Source or
|
||||
Minimal Application Code as set out in 4d or providing the installation
|
||||
information set out in section 4e, provided that you comply with the other
|
||||
provisions of LGPL3 and provided that you meet, for the Application the
|
||||
terms and conditions of the license(s) which apply to the Application.
|
||||
|
||||
Except as stated in this special exception, the provisions of LGPL3 will
|
||||
continue to comply in full to this Library. If you modify this Library, you
|
||||
may apply this exception to your version of this Library, but you are not
|
||||
obliged to do so. If you do not wish to do so, delete this exception
|
||||
statement from your version. This exception does not (and cannot) modify any
|
||||
license terms which apply to the Application, with which you must still
|
||||
comply.
|
||||
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
74
vendor/src/launchpad.net/goamz/aws/attempt.go
vendored
Normal file
74
vendor/src/launchpad.net/goamz/aws/attempt.go
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// AttemptStrategy represents a strategy for waiting for an action
|
||||
// to complete successfully. This is an internal type used by the
|
||||
// implementation of other goamz packages.
|
||||
type AttemptStrategy struct {
|
||||
Total time.Duration // total duration of attempt.
|
||||
Delay time.Duration // interval between each try in the burst.
|
||||
Min int // minimum number of retries; overrides Total
|
||||
}
|
||||
|
||||
type Attempt struct {
|
||||
strategy AttemptStrategy
|
||||
last time.Time
|
||||
end time.Time
|
||||
force bool
|
||||
count int
|
||||
}
|
||||
|
||||
// Start begins a new sequence of attempts for the given strategy.
|
||||
func (s AttemptStrategy) Start() *Attempt {
|
||||
now := time.Now()
|
||||
return &Attempt{
|
||||
strategy: s,
|
||||
last: now,
|
||||
end: now.Add(s.Total),
|
||||
force: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Next waits until it is time to perform the next attempt or returns
|
||||
// false if it is time to stop trying.
|
||||
func (a *Attempt) Next() bool {
|
||||
now := time.Now()
|
||||
sleep := a.nextSleep(now)
|
||||
if !a.force && !now.Add(sleep).Before(a.end) && a.strategy.Min <= a.count {
|
||||
return false
|
||||
}
|
||||
a.force = false
|
||||
if sleep > 0 && a.count > 0 {
|
||||
time.Sleep(sleep)
|
||||
now = time.Now()
|
||||
}
|
||||
a.count++
|
||||
a.last = now
|
||||
return true
|
||||
}
|
||||
|
||||
func (a *Attempt) nextSleep(now time.Time) time.Duration {
|
||||
sleep := a.strategy.Delay - now.Sub(a.last)
|
||||
if sleep < 0 {
|
||||
return 0
|
||||
}
|
||||
return sleep
|
||||
}
|
||||
|
||||
// HasNext returns whether another attempt will be made if the current
|
||||
// one fails. If it returns true, the following call to Next is
|
||||
// guaranteed to return true.
|
||||
func (a *Attempt) HasNext() bool {
|
||||
if a.force || a.strategy.Min > a.count {
|
||||
return true
|
||||
}
|
||||
now := time.Now()
|
||||
if now.Add(a.nextSleep(now)).Before(a.end) {
|
||||
a.force = true
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
57
vendor/src/launchpad.net/goamz/aws/attempt_test.go
vendored
Normal file
57
vendor/src/launchpad.net/goamz/aws/attempt_test.go
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
package aws_test
|
||||
|
||||
import (
|
||||
"launchpad.net/goamz/aws"
|
||||
. "launchpad.net/gocheck"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (S) TestAttemptTiming(c *C) {
|
||||
testAttempt := aws.AttemptStrategy{
|
||||
Total: 0.25e9,
|
||||
Delay: 0.1e9,
|
||||
}
|
||||
want := []time.Duration{0, 0.1e9, 0.2e9, 0.2e9}
|
||||
got := make([]time.Duration, 0, len(want)) // avoid allocation when testing timing
|
||||
t0 := time.Now()
|
||||
for a := testAttempt.Start(); a.Next(); {
|
||||
got = append(got, time.Now().Sub(t0))
|
||||
}
|
||||
got = append(got, time.Now().Sub(t0))
|
||||
c.Assert(got, HasLen, len(want))
|
||||
const margin = 0.01e9
|
||||
for i, got := range want {
|
||||
lo := want[i] - margin
|
||||
hi := want[i] + margin
|
||||
if got < lo || got > hi {
|
||||
c.Errorf("attempt %d want %g got %g", i, want[i].Seconds(), got.Seconds())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (S) TestAttemptNextHasNext(c *C) {
|
||||
a := aws.AttemptStrategy{}.Start()
|
||||
c.Assert(a.Next(), Equals, true)
|
||||
c.Assert(a.Next(), Equals, false)
|
||||
|
||||
a = aws.AttemptStrategy{}.Start()
|
||||
c.Assert(a.Next(), Equals, true)
|
||||
c.Assert(a.HasNext(), Equals, false)
|
||||
c.Assert(a.Next(), Equals, false)
|
||||
|
||||
a = aws.AttemptStrategy{Total: 2e8}.Start()
|
||||
c.Assert(a.Next(), Equals, true)
|
||||
c.Assert(a.HasNext(), Equals, true)
|
||||
time.Sleep(2e8)
|
||||
c.Assert(a.HasNext(), Equals, true)
|
||||
c.Assert(a.Next(), Equals, true)
|
||||
c.Assert(a.Next(), Equals, false)
|
||||
|
||||
a = aws.AttemptStrategy{Total: 1e8, Min: 2}.Start()
|
||||
time.Sleep(1e8)
|
||||
c.Assert(a.Next(), Equals, true)
|
||||
c.Assert(a.HasNext(), Equals, true)
|
||||
c.Assert(a.Next(), Equals, true)
|
||||
c.Assert(a.HasNext(), Equals, false)
|
||||
c.Assert(a.Next(), Equals, false)
|
||||
}
|
||||
212
vendor/src/launchpad.net/goamz/aws/aws.go
vendored
Normal file
212
vendor/src/launchpad.net/goamz/aws/aws.go
vendored
Normal file
@@ -0,0 +1,212 @@
|
||||
//
|
||||
// goamz - Go packages to interact with the Amazon Web Services.
|
||||
//
|
||||
// https://wiki.ubuntu.com/goamz
|
||||
//
|
||||
// Copyright (c) 2011 Canonical Ltd.
|
||||
//
|
||||
|
||||
package aws
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Region defines the URLs where AWS services may be accessed.
|
||||
//
|
||||
// See http://goo.gl/d8BP1 for more details.
|
||||
type Region struct {
|
||||
Name string // the canonical name of this region.
|
||||
EC2Endpoint string
|
||||
S3Endpoint string
|
||||
S3BucketEndpoint string // Not needed by AWS S3. Use ${bucket} for bucket name.
|
||||
S3LocationConstraint bool // true if this region requires a LocationConstraint declaration.
|
||||
S3LowercaseBucket bool // true if the region requires bucket names to be lower case.
|
||||
SDBEndpoint string
|
||||
SNSEndpoint string
|
||||
SQSEndpoint string
|
||||
IAMEndpoint string
|
||||
}
|
||||
|
||||
var USEast = Region{
|
||||
"us-east-1",
|
||||
"https://ec2.us-east-1.amazonaws.com",
|
||||
"https://s3.amazonaws.com",
|
||||
"",
|
||||
false,
|
||||
false,
|
||||
"https://sdb.amazonaws.com",
|
||||
"https://sns.us-east-1.amazonaws.com",
|
||||
"https://sqs.us-east-1.amazonaws.com",
|
||||
"https://iam.amazonaws.com",
|
||||
}
|
||||
|
||||
var USWest = Region{
|
||||
"us-west-1",
|
||||
"https://ec2.us-west-1.amazonaws.com",
|
||||
"https://s3-us-west-1.amazonaws.com",
|
||||
"",
|
||||
true,
|
||||
true,
|
||||
"https://sdb.us-west-1.amazonaws.com",
|
||||
"https://sns.us-west-1.amazonaws.com",
|
||||
"https://sqs.us-west-1.amazonaws.com",
|
||||
"https://iam.amazonaws.com",
|
||||
}
|
||||
|
||||
var USWest2 = Region{
|
||||
"us-west-2",
|
||||
"https://ec2.us-west-2.amazonaws.com",
|
||||
"https://s3-us-west-2.amazonaws.com",
|
||||
"",
|
||||
true,
|
||||
true,
|
||||
"https://sdb.us-west-2.amazonaws.com",
|
||||
"https://sns.us-west-2.amazonaws.com",
|
||||
"https://sqs.us-west-2.amazonaws.com",
|
||||
"https://iam.amazonaws.com",
|
||||
}
|
||||
|
||||
var EUWest = Region{
|
||||
"eu-west-1",
|
||||
"https://ec2.eu-west-1.amazonaws.com",
|
||||
"https://s3-eu-west-1.amazonaws.com",
|
||||
"",
|
||||
true,
|
||||
true,
|
||||
"https://sdb.eu-west-1.amazonaws.com",
|
||||
"https://sns.eu-west-1.amazonaws.com",
|
||||
"https://sqs.eu-west-1.amazonaws.com",
|
||||
"https://iam.amazonaws.com",
|
||||
}
|
||||
|
||||
var APSoutheast = Region{
|
||||
"ap-southeast-1",
|
||||
"https://ec2.ap-southeast-1.amazonaws.com",
|
||||
"https://s3-ap-southeast-1.amazonaws.com",
|
||||
"",
|
||||
true,
|
||||
true,
|
||||
"https://sdb.ap-southeast-1.amazonaws.com",
|
||||
"https://sns.ap-southeast-1.amazonaws.com",
|
||||
"https://sqs.ap-southeast-1.amazonaws.com",
|
||||
"https://iam.amazonaws.com",
|
||||
}
|
||||
|
||||
var APSoutheast2 = Region{
|
||||
"ap-southeast-2",
|
||||
"https://ec2.ap-southeast-2.amazonaws.com",
|
||||
"https://s3-ap-southeast-2.amazonaws.com",
|
||||
"",
|
||||
true,
|
||||
true,
|
||||
"https://sdb.ap-southeast-2.amazonaws.com",
|
||||
"https://sns.ap-southeast-2.amazonaws.com",
|
||||
"https://sqs.ap-southeast-2.amazonaws.com",
|
||||
"https://iam.amazonaws.com",
|
||||
}
|
||||
|
||||
var APNortheast = Region{
|
||||
"ap-northeast-1",
|
||||
"https://ec2.ap-northeast-1.amazonaws.com",
|
||||
"https://s3-ap-northeast-1.amazonaws.com",
|
||||
"",
|
||||
true,
|
||||
true,
|
||||
"https://sdb.ap-northeast-1.amazonaws.com",
|
||||
"https://sns.ap-northeast-1.amazonaws.com",
|
||||
"https://sqs.ap-northeast-1.amazonaws.com",
|
||||
"https://iam.amazonaws.com",
|
||||
}
|
||||
|
||||
var SAEast = Region{
|
||||
"sa-east-1",
|
||||
"https://ec2.sa-east-1.amazonaws.com",
|
||||
"https://s3-sa-east-1.amazonaws.com",
|
||||
"",
|
||||
true,
|
||||
true,
|
||||
"https://sdb.sa-east-1.amazonaws.com",
|
||||
"https://sns.sa-east-1.amazonaws.com",
|
||||
"https://sqs.sa-east-1.amazonaws.com",
|
||||
"https://iam.amazonaws.com",
|
||||
}
|
||||
|
||||
var Regions = map[string]Region{
|
||||
APNortheast.Name: APNortheast,
|
||||
APSoutheast.Name: APSoutheast,
|
||||
APSoutheast2.Name: APSoutheast2,
|
||||
EUWest.Name: EUWest,
|
||||
USEast.Name: USEast,
|
||||
USWest.Name: USWest,
|
||||
USWest2.Name: USWest2,
|
||||
SAEast.Name: SAEast,
|
||||
}
|
||||
|
||||
type Auth struct {
|
||||
AccessKey, SecretKey string
|
||||
}
|
||||
|
||||
var unreserved = make([]bool, 128)
|
||||
var hex = "0123456789ABCDEF"
|
||||
|
||||
func init() {
|
||||
// RFC3986
|
||||
u := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890-_.~"
|
||||
for _, c := range u {
|
||||
unreserved[c] = true
|
||||
}
|
||||
}
|
||||
|
||||
// EnvAuth creates an Auth based on environment information.
|
||||
// The AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment
|
||||
// variables are used as the first preference, but EC2_ACCESS_KEY
|
||||
// and EC2_SECRET_KEY environment variables are also supported.
|
||||
func EnvAuth() (auth Auth, err error) {
|
||||
auth.AccessKey = os.Getenv("AWS_ACCESS_KEY_ID")
|
||||
auth.SecretKey = os.Getenv("AWS_SECRET_ACCESS_KEY")
|
||||
// We fallback to EC2_ env variables if the AWS_ variants are not used.
|
||||
if auth.AccessKey == "" && auth.SecretKey == "" {
|
||||
auth.AccessKey = os.Getenv("EC2_ACCESS_KEY")
|
||||
auth.SecretKey = os.Getenv("EC2_SECRET_KEY")
|
||||
}
|
||||
if auth.AccessKey == "" {
|
||||
err = errors.New("AWS_ACCESS_KEY_ID not found in environment")
|
||||
}
|
||||
if auth.SecretKey == "" {
|
||||
err = errors.New("AWS_SECRET_ACCESS_KEY not found in environment")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Encode takes a string and URI-encodes it in a way suitable
|
||||
// to be used in AWS signatures.
|
||||
func Encode(s string) string {
|
||||
encode := false
|
||||
for i := 0; i != len(s); i++ {
|
||||
c := s[i]
|
||||
if c > 127 || !unreserved[c] {
|
||||
encode = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !encode {
|
||||
return s
|
||||
}
|
||||
e := make([]byte, len(s)*3)
|
||||
ei := 0
|
||||
for i := 0; i != len(s); i++ {
|
||||
c := s[i]
|
||||
if c > 127 || !unreserved[c] {
|
||||
e[ei] = '%'
|
||||
e[ei+1] = hex[c>>4]
|
||||
e[ei+2] = hex[c&0xF]
|
||||
ei += 3
|
||||
} else {
|
||||
e[ei] = c
|
||||
ei += 1
|
||||
}
|
||||
}
|
||||
return string(e[:ei])
|
||||
}
|
||||
73
vendor/src/launchpad.net/goamz/aws/aws_test.go
vendored
Normal file
73
vendor/src/launchpad.net/goamz/aws/aws_test.go
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
package aws_test
|
||||
|
||||
import (
|
||||
"launchpad.net/goamz/aws"
|
||||
. "launchpad.net/gocheck"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
TestingT(t)
|
||||
}
|
||||
|
||||
var _ = Suite(&S{})
|
||||
|
||||
type S struct {
|
||||
environ []string
|
||||
}
|
||||
|
||||
func (s *S) SetUpSuite(c *C) {
|
||||
s.environ = os.Environ()
|
||||
}
|
||||
|
||||
func (s *S) TearDownTest(c *C) {
|
||||
os.Clearenv()
|
||||
for _, kv := range s.environ {
|
||||
l := strings.SplitN(kv, "=", 2)
|
||||
os.Setenv(l[0], l[1])
|
||||
}
|
||||
}
|
||||
|
||||
func (s *S) TestEnvAuthNoSecret(c *C) {
|
||||
os.Clearenv()
|
||||
_, err := aws.EnvAuth()
|
||||
c.Assert(err, ErrorMatches, "AWS_SECRET_ACCESS_KEY not found in environment")
|
||||
}
|
||||
|
||||
func (s *S) TestEnvAuthNoAccess(c *C) {
|
||||
os.Clearenv()
|
||||
os.Setenv("AWS_SECRET_ACCESS_KEY", "foo")
|
||||
_, err := aws.EnvAuth()
|
||||
c.Assert(err, ErrorMatches, "AWS_ACCESS_KEY_ID not found in environment")
|
||||
}
|
||||
|
||||
func (s *S) TestEnvAuth(c *C) {
|
||||
os.Clearenv()
|
||||
os.Setenv("AWS_SECRET_ACCESS_KEY", "secret")
|
||||
os.Setenv("AWS_ACCESS_KEY_ID", "access")
|
||||
auth, err := aws.EnvAuth()
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
|
||||
}
|
||||
|
||||
func (s *S) TestEnvAuthLegacy(c *C) {
|
||||
os.Clearenv()
|
||||
os.Setenv("EC2_SECRET_KEY", "secret")
|
||||
os.Setenv("EC2_ACCESS_KEY", "access")
|
||||
auth, err := aws.EnvAuth()
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
|
||||
}
|
||||
|
||||
func (s *S) TestEncode(c *C) {
|
||||
c.Assert(aws.Encode("foo"), Equals, "foo")
|
||||
c.Assert(aws.Encode("/"), Equals, "%2F")
|
||||
}
|
||||
|
||||
func (s *S) TestRegionsAreNamed(c *C) {
|
||||
for n, r := range aws.Regions {
|
||||
c.Assert(n, Equals, r.Name)
|
||||
}
|
||||
}
|
||||
1087
vendor/src/launchpad.net/goamz/ec2/ec2.go
vendored
Normal file
1087
vendor/src/launchpad.net/goamz/ec2/ec2.go
vendored
Normal file
File diff suppressed because it is too large
Load Diff
900
vendor/src/launchpad.net/goamz/ec2/ec2_test.go
vendored
Normal file
900
vendor/src/launchpad.net/goamz/ec2/ec2_test.go
vendored
Normal file
@@ -0,0 +1,900 @@
|
||||
package ec2_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"launchpad.net/goamz/aws"
|
||||
"launchpad.net/goamz/ec2"
|
||||
"launchpad.net/goamz/testutil"
|
||||
. "launchpad.net/gocheck"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
TestingT(t)
|
||||
}
|
||||
|
||||
var _ = Suite(&S{})
|
||||
|
||||
type S struct {
|
||||
ec2 *ec2.EC2
|
||||
}
|
||||
|
||||
var testServer = testutil.NewHTTPServer()
|
||||
|
||||
func (s *S) SetUpSuite(c *C) {
|
||||
testServer.Start()
|
||||
auth := aws.Auth{"abc", "123"}
|
||||
s.ec2 = ec2.New(auth, aws.Region{EC2Endpoint: testServer.URL})
|
||||
}
|
||||
|
||||
func (s *S) TearDownSuite(c *C) {
|
||||
testServer.Stop()
|
||||
}
|
||||
|
||||
func (s *S) TearDownTest(c *C) {
|
||||
testServer.Flush()
|
||||
}
|
||||
|
||||
func (s *S) TestRunInstancesErrorDump(c *C) {
|
||||
testServer.Response(400, nil, ErrorDump)
|
||||
|
||||
options := ec2.RunInstances{
|
||||
ImageId: "ami-a6f504cf", // Ubuntu Maverick, i386, instance store
|
||||
InstanceType: "t1.micro", // Doesn't work with micro, results in 400.
|
||||
}
|
||||
|
||||
msg := `AMIs with an instance-store root device are not supported for the instance type 't1\.micro'\.`
|
||||
|
||||
resp, err := s.ec2.RunInstances(&options)
|
||||
|
||||
testServer.WaitRequest()
|
||||
|
||||
c.Assert(resp, IsNil)
|
||||
c.Assert(err, ErrorMatches, msg+` \(UnsupportedOperation\)`)
|
||||
|
||||
ec2err, ok := err.(*ec2.Error)
|
||||
c.Assert(ok, Equals, true)
|
||||
c.Assert(ec2err.StatusCode, Equals, 400)
|
||||
c.Assert(ec2err.Code, Equals, "UnsupportedOperation")
|
||||
c.Assert(ec2err.Message, Matches, msg)
|
||||
c.Assert(ec2err.RequestId, Equals, "0503f4e9-bbd6-483c-b54f-c4ae9f3b30f4")
|
||||
}
|
||||
|
||||
func (s *S) TestRunInstancesErrorWithoutXML(c *C) {
|
||||
testServer.Response(500, nil, "")
|
||||
options := ec2.RunInstances{ImageId: "image-id"}
|
||||
|
||||
resp, err := s.ec2.RunInstances(&options)
|
||||
|
||||
testServer.WaitRequest()
|
||||
|
||||
c.Assert(resp, IsNil)
|
||||
c.Assert(err, ErrorMatches, "500 Internal Server Error")
|
||||
|
||||
ec2err, ok := err.(*ec2.Error)
|
||||
c.Assert(ok, Equals, true)
|
||||
c.Assert(ec2err.StatusCode, Equals, 500)
|
||||
c.Assert(ec2err.Code, Equals, "")
|
||||
c.Assert(ec2err.Message, Equals, "500 Internal Server Error")
|
||||
c.Assert(ec2err.RequestId, Equals, "")
|
||||
}
|
||||
|
||||
func (s *S) TestRunInstancesExample(c *C) {
|
||||
testServer.Response(200, nil, RunInstancesExample)
|
||||
|
||||
options := ec2.RunInstances{
|
||||
KeyName: "my-keys",
|
||||
ImageId: "image-id",
|
||||
InstanceType: "inst-type",
|
||||
SecurityGroups: []ec2.SecurityGroup{{Name: "g1"}, {Id: "g2"}, {Name: "g3"}, {Id: "g4"}},
|
||||
UserData: []byte("1234"),
|
||||
KernelId: "kernel-id",
|
||||
RamdiskId: "ramdisk-id",
|
||||
AvailZone: "zone",
|
||||
PlacementGroupName: "group",
|
||||
Monitoring: true,
|
||||
SubnetId: "subnet-id",
|
||||
DisableAPITermination: true,
|
||||
ShutdownBehavior: "terminate",
|
||||
PrivateIPAddress: "10.0.0.25",
|
||||
BlockDeviceMappings: []ec2.BlockDeviceMapping{{
|
||||
DeviceName: "device-name",
|
||||
VirtualName: "virtual-name",
|
||||
SnapshotId: "snapshot-id",
|
||||
VolumeType: "volume-type",
|
||||
VolumeSize: 10,
|
||||
DeleteOnTermination: true,
|
||||
IOPS: 1000,
|
||||
}},
|
||||
NetworkInterfaces: []ec2.RunNetworkInterface{{
|
||||
DeviceIndex: 0,
|
||||
SubnetId: "subnet-id",
|
||||
Description: "eth0",
|
||||
PrivateIPs: []ec2.PrivateIP{
|
||||
{Address: "10.0.0.25", IsPrimary: true},
|
||||
},
|
||||
DeleteOnTermination: true,
|
||||
SecurityGroupIds: []string{"sg-1", "sg-2"},
|
||||
SecondaryPrivateIPCount: 2,
|
||||
}, {
|
||||
Id: "eni-id",
|
||||
DeviceIndex: 1,
|
||||
PrivateIPs: []ec2.PrivateIP{{
|
||||
Address: "10.0.1.10",
|
||||
IsPrimary: true,
|
||||
}, {
|
||||
Address: "10.0.1.20",
|
||||
IsPrimary: false,
|
||||
}},
|
||||
}},
|
||||
}
|
||||
params := ec2.PrepareRunParams(options)
|
||||
c.Assert(params, DeepEquals, map[string]string{
|
||||
"Version": "2013-10-15",
|
||||
"Action": "RunInstances",
|
||||
})
|
||||
resp, err := s.ec2.RunInstances(&options)
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"RunInstances"})
|
||||
c.Assert(req.Form["ImageId"], DeepEquals, []string{"image-id"})
|
||||
c.Assert(req.Form["MinCount"], DeepEquals, []string{"1"})
|
||||
c.Assert(req.Form["MaxCount"], DeepEquals, []string{"1"})
|
||||
c.Assert(req.Form["KeyName"], DeepEquals, []string{"my-keys"})
|
||||
c.Assert(req.Form["InstanceType"], DeepEquals, []string{"inst-type"})
|
||||
c.Assert(req.Form["SecurityGroup.1"], DeepEquals, []string{"g1"})
|
||||
c.Assert(req.Form["SecurityGroup.2"], DeepEquals, []string{"g3"})
|
||||
c.Assert(req.Form["SecurityGroupId.1"], DeepEquals, []string{"g2"})
|
||||
c.Assert(req.Form["SecurityGroupId.2"], DeepEquals, []string{"g4"})
|
||||
c.Assert(req.Form["UserData"], DeepEquals, []string{"MTIzNA=="})
|
||||
c.Assert(req.Form["KernelId"], DeepEquals, []string{"kernel-id"})
|
||||
c.Assert(req.Form["RamdiskId"], DeepEquals, []string{"ramdisk-id"})
|
||||
c.Assert(req.Form["Placement.AvailabilityZone"], DeepEquals, []string{"zone"})
|
||||
c.Assert(req.Form["Placement.GroupName"], DeepEquals, []string{"group"})
|
||||
c.Assert(req.Form["Monitoring.Enabled"], DeepEquals, []string{"true"})
|
||||
c.Assert(req.Form["SubnetId"], DeepEquals, []string{"subnet-id"})
|
||||
c.Assert(req.Form["DisableApiTermination"], DeepEquals, []string{"true"})
|
||||
c.Assert(req.Form["InstanceInitiatedShutdownBehavior"], DeepEquals, []string{"terminate"})
|
||||
c.Assert(req.Form["PrivateIpAddress"], DeepEquals, []string{"10.0.0.25"})
|
||||
c.Assert(req.Form["BlockDeviceMapping.1.DeviceName"], DeepEquals, []string{"device-name"})
|
||||
c.Assert(req.Form["BlockDeviceMapping.1.VirtualName"], DeepEquals, []string{"virtual-name"})
|
||||
c.Assert(req.Form["BlockDeviceMapping.1.Ebs.SnapshotId"], DeepEquals, []string{"snapshot-id"})
|
||||
c.Assert(req.Form["BlockDeviceMapping.1.Ebs.VolumeType"], DeepEquals, []string{"volume-type"})
|
||||
c.Assert(req.Form["BlockDeviceMapping.1.Ebs.VolumeSize"], DeepEquals, []string{"10"})
|
||||
c.Assert(req.Form["BlockDeviceMapping.1.Ebs.Iops"], DeepEquals, []string{"1000"})
|
||||
c.Assert(req.Form["BlockDeviceMapping.1.Ebs.DeleteOnTermination"], DeepEquals, []string{"true"})
|
||||
c.Assert(req.Form["NetworkInterface.0.DeviceIndex"], DeepEquals, []string{"0"})
|
||||
c.Assert(req.Form["NetworkInterface.0.SubnetId"], DeepEquals, []string{"subnet-id"})
|
||||
c.Assert(req.Form["NetworkInterface.0.Description"], DeepEquals, []string{"eth0"})
|
||||
c.Assert(req.Form["NetworkInterface.0.SecurityGroupId.1"], DeepEquals, []string{"sg-1"})
|
||||
c.Assert(req.Form["NetworkInterface.0.SecurityGroupId.2"], DeepEquals, []string{"sg-2"})
|
||||
c.Assert(req.Form["NetworkInterface.0.DeleteOnTermination"], DeepEquals, []string{"true"})
|
||||
c.Assert(req.Form["NetworkInterface.0.SecondaryPrivateIpAddressCount"], DeepEquals, []string{"2"})
|
||||
c.Assert(req.Form["NetworkInterface.1.NetworkInterfaceId"], DeepEquals, []string{"eni-id"})
|
||||
c.Assert(req.Form["NetworkInterface.1.DeviceIndex"], DeepEquals, []string{"1"})
|
||||
c.Assert(req.Form["NetworkInterface.1.PrivateIpAddresses.0.PrivateIpAddress"], DeepEquals, []string{"10.0.1.10"})
|
||||
c.Assert(req.Form["NetworkInterface.1.PrivateIpAddresses.0.Primary"], DeepEquals, []string{"true"})
|
||||
c.Assert(req.Form["NetworkInterface.1.PrivateIpAddresses.1.PrivateIpAddress"], DeepEquals, []string{"10.0.1.20"})
|
||||
c.Assert(req.Form["NetworkInterface.1.PrivateIpAddresses.1.Primary"], DeepEquals, []string{"false"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
|
||||
c.Assert(resp.ReservationId, Equals, "r-47a5402e")
|
||||
c.Assert(resp.OwnerId, Equals, "999988887777")
|
||||
c.Assert(resp.SecurityGroups, DeepEquals, []ec2.SecurityGroup{{Name: "default", Id: "sg-67ad940e"}})
|
||||
c.Assert(resp.Instances, HasLen, 3)
|
||||
|
||||
i0 := resp.Instances[0]
|
||||
c.Assert(i0.InstanceId, Equals, "i-2ba64342")
|
||||
c.Assert(i0.InstanceType, Equals, "m1.small")
|
||||
c.Assert(i0.ImageId, Equals, "ami-60a54009")
|
||||
c.Assert(i0.Monitoring, Equals, "enabled")
|
||||
c.Assert(i0.KeyName, Equals, "example-key-name")
|
||||
c.Assert(i0.AMILaunchIndex, Equals, 0)
|
||||
c.Assert(i0.VirtType, Equals, "paravirtual")
|
||||
c.Assert(i0.Hypervisor, Equals, "xen")
|
||||
c.Assert(i0.SubnetId, Equals, "subnet-id")
|
||||
c.Assert(i0.VPCId, Equals, "vpc-id")
|
||||
c.Assert(i0.NetworkInterfaces, HasLen, 2)
|
||||
c.Assert(i0.NetworkInterfaces, DeepEquals, []ec2.NetworkInterface{{
|
||||
Id: "eni-c6bb50ae",
|
||||
SubnetId: "subnet-id",
|
||||
VPCId: "vpc-id",
|
||||
Description: "eth0",
|
||||
SourceDestCheck: true,
|
||||
OwnerId: "111122223333",
|
||||
Status: "in-use",
|
||||
Groups: []ec2.SecurityGroup{
|
||||
{Name: "vpc sg-1", Id: "sg-1"},
|
||||
{Name: "vpc sg-2", Id: "sg-2"},
|
||||
},
|
||||
MACAddress: "11:22:33:44:55:66",
|
||||
PrivateIPAddress: "10.0.0.25",
|
||||
PrivateIPs: []ec2.PrivateIP{{Address: "10.0.0.25", IsPrimary: true}},
|
||||
Attachment: ec2.NetworkInterfaceAttachment{
|
||||
Id: "eni-attach-0326646a",
|
||||
DeviceIndex: 0,
|
||||
Status: "attaching",
|
||||
AttachTime: "2011-12-20T08:29:31.000Z",
|
||||
DeleteOnTermination: true,
|
||||
},
|
||||
}, {
|
||||
Id: "eni-id",
|
||||
SubnetId: "subnet-id",
|
||||
VPCId: "vpc-id",
|
||||
SourceDestCheck: true,
|
||||
OwnerId: "111122223333",
|
||||
Status: "in-use",
|
||||
Groups: []ec2.SecurityGroup{{Name: "vpc default", Id: "sg-id"}},
|
||||
MACAddress: "11:22:33:44:55:66",
|
||||
PrivateIPAddress: "10.0.1.10",
|
||||
PrivateIPs: []ec2.PrivateIP{
|
||||
{Address: "10.0.1.10", IsPrimary: true},
|
||||
{Address: "10.0.1.20", IsPrimary: false},
|
||||
},
|
||||
Attachment: ec2.NetworkInterfaceAttachment{
|
||||
Id: "eni-attach-id",
|
||||
DeviceIndex: 1,
|
||||
Status: "attaching",
|
||||
AttachTime: "2011-12-20T08:29:31.000Z",
|
||||
DeleteOnTermination: false,
|
||||
},
|
||||
}})
|
||||
|
||||
i1 := resp.Instances[1]
|
||||
c.Assert(i1.InstanceId, Equals, "i-2bc64242")
|
||||
c.Assert(i1.InstanceType, Equals, "m1.small")
|
||||
c.Assert(i1.ImageId, Equals, "ami-60a54009")
|
||||
c.Assert(i1.Monitoring, Equals, "enabled")
|
||||
c.Assert(i1.KeyName, Equals, "example-key-name")
|
||||
c.Assert(i1.AMILaunchIndex, Equals, 1)
|
||||
c.Assert(i1.VirtType, Equals, "paravirtual")
|
||||
c.Assert(i1.Hypervisor, Equals, "xen")
|
||||
|
||||
i2 := resp.Instances[2]
|
||||
c.Assert(i2.InstanceId, Equals, "i-2be64332")
|
||||
c.Assert(i2.InstanceType, Equals, "m1.small")
|
||||
c.Assert(i2.ImageId, Equals, "ami-60a54009")
|
||||
c.Assert(i2.Monitoring, Equals, "enabled")
|
||||
c.Assert(i2.KeyName, Equals, "example-key-name")
|
||||
c.Assert(i2.AMILaunchIndex, Equals, 2)
|
||||
c.Assert(i2.VirtType, Equals, "paravirtual")
|
||||
c.Assert(i2.Hypervisor, Equals, "xen")
|
||||
}
|
||||
|
||||
func (s *S) TestTerminateInstancesExample(c *C) {
|
||||
testServer.Response(200, nil, TerminateInstancesExample)
|
||||
|
||||
resp, err := s.ec2.TerminateInstances([]string{"i-1", "i-2"})
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"TerminateInstances"})
|
||||
c.Assert(req.Form["InstanceId.1"], DeepEquals, []string{"i-1"})
|
||||
c.Assert(req.Form["InstanceId.2"], DeepEquals, []string{"i-2"})
|
||||
c.Assert(req.Form["UserData"], IsNil)
|
||||
c.Assert(req.Form["KernelId"], IsNil)
|
||||
c.Assert(req.Form["RamdiskId"], IsNil)
|
||||
c.Assert(req.Form["Placement.AvailabilityZone"], IsNil)
|
||||
c.Assert(req.Form["Placement.GroupName"], IsNil)
|
||||
c.Assert(req.Form["Monitoring.Enabled"], IsNil)
|
||||
c.Assert(req.Form["SubnetId"], IsNil)
|
||||
c.Assert(req.Form["DisableApiTermination"], IsNil)
|
||||
c.Assert(req.Form["InstanceInitiatedShutdownBehavior"], IsNil)
|
||||
c.Assert(req.Form["PrivateIpAddress"], IsNil)
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
|
||||
c.Assert(resp.StateChanges, HasLen, 1)
|
||||
c.Assert(resp.StateChanges[0].InstanceId, Equals, "i-3ea74257")
|
||||
c.Assert(resp.StateChanges[0].CurrentState.Code, Equals, 32)
|
||||
c.Assert(resp.StateChanges[0].CurrentState.Name, Equals, "shutting-down")
|
||||
c.Assert(resp.StateChanges[0].PreviousState.Code, Equals, 16)
|
||||
c.Assert(resp.StateChanges[0].PreviousState.Name, Equals, "running")
|
||||
}
|
||||
|
||||
func (s *S) TestDescribeInstancesExample1(c *C) {
|
||||
testServer.Response(200, nil, DescribeInstancesExample1)
|
||||
|
||||
filter := ec2.NewFilter()
|
||||
filter.Add("key1", "value1")
|
||||
filter.Add("key2", "value2", "value3")
|
||||
|
||||
resp, err := s.ec2.Instances([]string{"i-1", "i-2"}, nil)
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeInstances"})
|
||||
c.Assert(req.Form["InstanceId.1"], DeepEquals, []string{"i-1"})
|
||||
c.Assert(req.Form["InstanceId.2"], DeepEquals, []string{"i-2"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "98e3c9a4-848c-4d6d-8e8a-b1bdEXAMPLE")
|
||||
c.Assert(resp.Reservations, HasLen, 2)
|
||||
|
||||
expectedGroups := []ec2.SecurityGroup{{Name: "default", Id: "sg-67ad940e"}}
|
||||
r0 := resp.Reservations[0]
|
||||
c.Assert(r0.ReservationId, Equals, "r-b27e30d9")
|
||||
c.Assert(r0.OwnerId, Equals, "999988887777")
|
||||
c.Assert(r0.RequesterId, Equals, "854251627541")
|
||||
c.Assert(r0.SecurityGroups, DeepEquals, expectedGroups)
|
||||
c.Assert(r0.Instances, HasLen, 1)
|
||||
|
||||
r0i := r0.Instances[0]
|
||||
c.Assert(r0i.InstanceId, Equals, "i-c5cd56af")
|
||||
c.Assert(r0i.PrivateDNSName, Equals, "domU-12-31-39-10-56-34.compute-1.internal")
|
||||
c.Assert(r0i.DNSName, Equals, "ec2-174-129-165-232.compute-1.amazonaws.com")
|
||||
c.Assert(r0i.PrivateIPAddress, Equals, "10.198.85.190")
|
||||
c.Assert(r0i.IPAddress, Equals, "174.129.165.232")
|
||||
c.Assert(r0i.AvailZone, Equals, "us-east-1b")
|
||||
c.Assert(r0i.SecurityGroups, DeepEquals, expectedGroups)
|
||||
}
|
||||
|
||||
func (s *S) TestDescribeInstancesExample2(c *C) {
|
||||
testServer.Response(200, nil, DescribeInstancesExample2)
|
||||
|
||||
filter := ec2.NewFilter()
|
||||
filter.Add("key1", "value1")
|
||||
filter.Add("key2", "value2", "value3")
|
||||
|
||||
resp, err := s.ec2.Instances([]string{"i-1", "i-2"}, filter)
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeInstances"})
|
||||
c.Assert(req.Form["InstanceId.1"], DeepEquals, []string{"i-1"})
|
||||
c.Assert(req.Form["InstanceId.2"], DeepEquals, []string{"i-2"})
|
||||
c.Assert(req.Form["Filter.1.Name"], DeepEquals, []string{"key1"})
|
||||
c.Assert(req.Form["Filter.1.Value.1"], DeepEquals, []string{"value1"})
|
||||
c.Assert(req.Form["Filter.1.Value.2"], IsNil)
|
||||
c.Assert(req.Form["Filter.2.Name"], DeepEquals, []string{"key2"})
|
||||
c.Assert(req.Form["Filter.2.Value.1"], DeepEquals, []string{"value2"})
|
||||
c.Assert(req.Form["Filter.2.Value.2"], DeepEquals, []string{"value3"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
|
||||
c.Assert(resp.Reservations, HasLen, 1)
|
||||
|
||||
r0 := resp.Reservations[0]
|
||||
r0i := r0.Instances[0]
|
||||
c.Assert(r0i.State.Code, Equals, 16)
|
||||
c.Assert(r0i.State.Name, Equals, "running")
|
||||
|
||||
r0t0 := r0i.Tags[0]
|
||||
r0t1 := r0i.Tags[1]
|
||||
c.Assert(r0t0.Key, Equals, "webserver")
|
||||
c.Assert(r0t0.Value, Equals, "")
|
||||
c.Assert(r0t1.Key, Equals, "stack")
|
||||
c.Assert(r0t1.Value, Equals, "Production")
|
||||
}
|
||||
|
||||
func (s *S) TestDescribeImagesExample(c *C) {
|
||||
testServer.Response(200, nil, DescribeImagesExample)
|
||||
|
||||
filter := ec2.NewFilter()
|
||||
filter.Add("key1", "value1")
|
||||
filter.Add("key2", "value2", "value3")
|
||||
|
||||
resp, err := s.ec2.Images([]string{"ami-1", "ami-2"}, filter)
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeImages"})
|
||||
c.Assert(req.Form["ImageId.1"], DeepEquals, []string{"ami-1"})
|
||||
c.Assert(req.Form["ImageId.2"], DeepEquals, []string{"ami-2"})
|
||||
c.Assert(req.Form["Filter.1.Name"], DeepEquals, []string{"key1"})
|
||||
c.Assert(req.Form["Filter.1.Value.1"], DeepEquals, []string{"value1"})
|
||||
c.Assert(req.Form["Filter.1.Value.2"], IsNil)
|
||||
c.Assert(req.Form["Filter.2.Name"], DeepEquals, []string{"key2"})
|
||||
c.Assert(req.Form["Filter.2.Value.1"], DeepEquals, []string{"value2"})
|
||||
c.Assert(req.Form["Filter.2.Value.2"], DeepEquals, []string{"value3"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "4a4a27a2-2e7c-475d-b35b-ca822EXAMPLE")
|
||||
c.Assert(resp.Images, HasLen, 1)
|
||||
|
||||
i0 := resp.Images[0]
|
||||
c.Assert(i0.Id, Equals, "ami-a2469acf")
|
||||
c.Assert(i0.Type, Equals, "machine")
|
||||
c.Assert(i0.Name, Equals, "example-marketplace-amzn-ami.1")
|
||||
c.Assert(i0.Description, Equals, "Amazon Linux AMI i386 EBS")
|
||||
c.Assert(i0.Location, Equals, "aws-marketplace/example-marketplace-amzn-ami.1")
|
||||
c.Assert(i0.State, Equals, "available")
|
||||
c.Assert(i0.Public, Equals, true)
|
||||
c.Assert(i0.OwnerId, Equals, "123456789999")
|
||||
c.Assert(i0.OwnerAlias, Equals, "aws-marketplace")
|
||||
c.Assert(i0.Architecture, Equals, "i386")
|
||||
c.Assert(i0.KernelId, Equals, "aki-805ea7e9")
|
||||
c.Assert(i0.RootDeviceType, Equals, "ebs")
|
||||
c.Assert(i0.RootDeviceName, Equals, "/dev/sda1")
|
||||
c.Assert(i0.VirtualizationType, Equals, "paravirtual")
|
||||
c.Assert(i0.Hypervisor, Equals, "xen")
|
||||
|
||||
c.Assert(i0.BlockDevices, HasLen, 1)
|
||||
c.Assert(i0.BlockDevices[0].DeviceName, Equals, "/dev/sda1")
|
||||
c.Assert(i0.BlockDevices[0].SnapshotId, Equals, "snap-787e9403")
|
||||
c.Assert(i0.BlockDevices[0].VolumeSize, Equals, int64(8))
|
||||
c.Assert(i0.BlockDevices[0].DeleteOnTermination, Equals, true)
|
||||
}
|
||||
|
||||
func (s *S) TestCreateSnapshotExample(c *C) {
|
||||
testServer.Response(200, nil, CreateSnapshotExample)
|
||||
|
||||
resp, err := s.ec2.CreateSnapshot("vol-4d826724", "Daily Backup")
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"CreateSnapshot"})
|
||||
c.Assert(req.Form["VolumeId"], DeepEquals, []string{"vol-4d826724"})
|
||||
c.Assert(req.Form["Description"], DeepEquals, []string{"Daily Backup"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
|
||||
c.Assert(resp.Snapshot.Id, Equals, "snap-78a54011")
|
||||
c.Assert(resp.Snapshot.VolumeId, Equals, "vol-4d826724")
|
||||
c.Assert(resp.Snapshot.Status, Equals, "pending")
|
||||
c.Assert(resp.Snapshot.StartTime, Equals, "2008-05-07T12:51:50.000Z")
|
||||
c.Assert(resp.Snapshot.Progress, Equals, "60%")
|
||||
c.Assert(resp.Snapshot.OwnerId, Equals, "111122223333")
|
||||
c.Assert(resp.Snapshot.VolumeSize, Equals, "10")
|
||||
c.Assert(resp.Snapshot.Description, Equals, "Daily Backup")
|
||||
}
|
||||
|
||||
func (s *S) TestDeleteSnapshotsExample(c *C) {
|
||||
testServer.Response(200, nil, DeleteSnapshotExample)
|
||||
|
||||
resp, err := s.ec2.DeleteSnapshots([]string{"snap-78a54011"})
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"DeleteSnapshot"})
|
||||
c.Assert(req.Form["SnapshotId.1"], DeepEquals, []string{"snap-78a54011"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
|
||||
}
|
||||
|
||||
func (s *S) TestDescribeSnapshotsExample(c *C) {
|
||||
testServer.Response(200, nil, DescribeSnapshotsExample)
|
||||
|
||||
filter := ec2.NewFilter()
|
||||
filter.Add("key1", "value1")
|
||||
filter.Add("key2", "value2", "value3")
|
||||
|
||||
resp, err := s.ec2.Snapshots([]string{"snap-1", "snap-2"}, filter)
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeSnapshots"})
|
||||
c.Assert(req.Form["SnapshotId.1"], DeepEquals, []string{"snap-1"})
|
||||
c.Assert(req.Form["SnapshotId.2"], DeepEquals, []string{"snap-2"})
|
||||
c.Assert(req.Form["Filter.1.Name"], DeepEquals, []string{"key1"})
|
||||
c.Assert(req.Form["Filter.1.Value.1"], DeepEquals, []string{"value1"})
|
||||
c.Assert(req.Form["Filter.1.Value.2"], IsNil)
|
||||
c.Assert(req.Form["Filter.2.Name"], DeepEquals, []string{"key2"})
|
||||
c.Assert(req.Form["Filter.2.Value.1"], DeepEquals, []string{"value2"})
|
||||
c.Assert(req.Form["Filter.2.Value.2"], DeepEquals, []string{"value3"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
|
||||
c.Assert(resp.Snapshots, HasLen, 1)
|
||||
|
||||
s0 := resp.Snapshots[0]
|
||||
c.Assert(s0.Id, Equals, "snap-1a2b3c4d")
|
||||
c.Assert(s0.VolumeId, Equals, "vol-8875daef")
|
||||
c.Assert(s0.VolumeSize, Equals, "15")
|
||||
c.Assert(s0.Status, Equals, "pending")
|
||||
c.Assert(s0.StartTime, Equals, "2010-07-29T04:12:01.000Z")
|
||||
c.Assert(s0.Progress, Equals, "30%")
|
||||
c.Assert(s0.OwnerId, Equals, "111122223333")
|
||||
c.Assert(s0.Description, Equals, "Daily Backup")
|
||||
|
||||
c.Assert(s0.Tags, HasLen, 1)
|
||||
c.Assert(s0.Tags[0].Key, Equals, "Purpose")
|
||||
c.Assert(s0.Tags[0].Value, Equals, "demo_db_14_backup")
|
||||
}
|
||||
|
||||
func (s *S) checkCreateSGResponse(c *C, resp *ec2.CreateSecurityGroupResp, id, name, description, vpcId string) {
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"CreateSecurityGroup"})
|
||||
c.Assert(req.Form["GroupName"], DeepEquals, []string{name})
|
||||
c.Assert(req.Form["GroupDescription"], DeepEquals, []string{description})
|
||||
if vpcId != "" {
|
||||
c.Assert(req.Form["VpcId"], DeepEquals, []string{vpcId})
|
||||
}
|
||||
|
||||
c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
|
||||
c.Assert(resp.Name, Equals, name)
|
||||
c.Assert(resp.Id, Equals, id)
|
||||
}
|
||||
|
||||
func (s *S) TestCreateSecurityGroupExample(c *C) {
|
||||
testServer.Response(200, nil, CreateSecurityGroupExample)
|
||||
resp, err := s.ec2.CreateSecurityGroup("websrv", "Web Servers")
|
||||
c.Assert(err, IsNil)
|
||||
s.checkCreateSGResponse(c, resp, "sg-67ad940e", "websrv", "Web Servers", "")
|
||||
|
||||
testServer.Response(200, nil, CreateSecurityGroupExample)
|
||||
resp, err = s.ec2.CreateSecurityGroupVPC("vpc-id", "websrv", "Web Servers")
|
||||
c.Assert(err, IsNil)
|
||||
s.checkCreateSGResponse(c, resp, "sg-67ad940e", "websrv", "Web Servers", "vpc-id")
|
||||
}
|
||||
|
||||
func (s *S) TestDescribeSecurityGroupsExample(c *C) {
|
||||
testServer.Response(200, nil, DescribeSecurityGroupsExample)
|
||||
|
||||
resp, err := s.ec2.SecurityGroups([]ec2.SecurityGroup{{Name: "WebServers"}, {Name: "RangedPortsBySource"}}, nil)
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeSecurityGroups"})
|
||||
c.Assert(req.Form["GroupName.1"], DeepEquals, []string{"WebServers"})
|
||||
c.Assert(req.Form["GroupName.2"], DeepEquals, []string{"RangedPortsBySource"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
|
||||
c.Assert(resp.Groups, HasLen, 2)
|
||||
|
||||
g0 := resp.Groups[0]
|
||||
c.Assert(g0.OwnerId, Equals, "999988887777")
|
||||
c.Assert(g0.Name, Equals, "WebServers")
|
||||
c.Assert(g0.Id, Equals, "sg-67ad940e")
|
||||
c.Assert(g0.Description, Equals, "Web Servers")
|
||||
c.Assert(g0.IPPerms, HasLen, 1)
|
||||
|
||||
g0ipp := g0.IPPerms[0]
|
||||
c.Assert(g0ipp.Protocol, Equals, "tcp")
|
||||
c.Assert(g0ipp.FromPort, Equals, 80)
|
||||
c.Assert(g0ipp.ToPort, Equals, 80)
|
||||
c.Assert(g0ipp.SourceIPs, DeepEquals, []string{"0.0.0.0/0"})
|
||||
|
||||
g1 := resp.Groups[1]
|
||||
c.Assert(g1.OwnerId, Equals, "999988887777")
|
||||
c.Assert(g1.Name, Equals, "RangedPortsBySource")
|
||||
c.Assert(g1.Id, Equals, "sg-76abc467")
|
||||
c.Assert(g1.Description, Equals, "Group A")
|
||||
c.Assert(g1.IPPerms, HasLen, 1)
|
||||
|
||||
g1ipp := g1.IPPerms[0]
|
||||
c.Assert(g1ipp.Protocol, Equals, "tcp")
|
||||
c.Assert(g1ipp.FromPort, Equals, 6000)
|
||||
c.Assert(g1ipp.ToPort, Equals, 7000)
|
||||
c.Assert(g1ipp.SourceIPs, IsNil)
|
||||
}
|
||||
|
||||
func (s *S) TestDescribeSecurityGroupsExampleWithFilter(c *C) {
|
||||
testServer.Response(200, nil, DescribeSecurityGroupsExample)
|
||||
|
||||
filter := ec2.NewFilter()
|
||||
filter.Add("ip-permission.protocol", "tcp")
|
||||
filter.Add("ip-permission.from-port", "22")
|
||||
filter.Add("ip-permission.to-port", "22")
|
||||
filter.Add("ip-permission.group-name", "app_server_group", "database_group")
|
||||
|
||||
_, err := s.ec2.SecurityGroups(nil, filter)
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeSecurityGroups"})
|
||||
c.Assert(req.Form["Filter.1.Name"], DeepEquals, []string{"ip-permission.from-port"})
|
||||
c.Assert(req.Form["Filter.1.Value.1"], DeepEquals, []string{"22"})
|
||||
c.Assert(req.Form["Filter.2.Name"], DeepEquals, []string{"ip-permission.group-name"})
|
||||
c.Assert(req.Form["Filter.2.Value.1"], DeepEquals, []string{"app_server_group"})
|
||||
c.Assert(req.Form["Filter.2.Value.2"], DeepEquals, []string{"database_group"})
|
||||
c.Assert(req.Form["Filter.3.Name"], DeepEquals, []string{"ip-permission.protocol"})
|
||||
c.Assert(req.Form["Filter.3.Value.1"], DeepEquals, []string{"tcp"})
|
||||
c.Assert(req.Form["Filter.4.Name"], DeepEquals, []string{"ip-permission.to-port"})
|
||||
c.Assert(req.Form["Filter.4.Value.1"], DeepEquals, []string{"22"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *S) TestDescribeSecurityGroupsDumpWithGroup(c *C) {
|
||||
testServer.Response(200, nil, DescribeSecurityGroupsDump)
|
||||
|
||||
resp, err := s.ec2.SecurityGroups(nil, nil)
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeSecurityGroups"})
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(resp.Groups, HasLen, 1)
|
||||
c.Check(resp.Groups[0].IPPerms, HasLen, 2)
|
||||
|
||||
ipp0 := resp.Groups[0].IPPerms[0]
|
||||
c.Assert(ipp0.SourceIPs, IsNil)
|
||||
c.Check(ipp0.Protocol, Equals, "icmp")
|
||||
c.Assert(ipp0.SourceGroups, HasLen, 1)
|
||||
c.Check(ipp0.SourceGroups[0].OwnerId, Equals, "12345")
|
||||
c.Check(ipp0.SourceGroups[0].Name, Equals, "default")
|
||||
c.Check(ipp0.SourceGroups[0].Id, Equals, "sg-67ad940e")
|
||||
|
||||
ipp1 := resp.Groups[0].IPPerms[1]
|
||||
c.Check(ipp1.Protocol, Equals, "tcp")
|
||||
c.Assert(ipp0.SourceIPs, IsNil)
|
||||
c.Assert(ipp0.SourceGroups, HasLen, 1)
|
||||
c.Check(ipp1.SourceGroups[0].Id, Equals, "sg-76abc467")
|
||||
c.Check(ipp1.SourceGroups[0].OwnerId, Equals, "12345")
|
||||
c.Check(ipp1.SourceGroups[0].Name, Equals, "other")
|
||||
}
|
||||
|
||||
func (s *S) TestDeleteSecurityGroupExample(c *C) {
|
||||
testServer.Response(200, nil, DeleteSecurityGroupExample)
|
||||
|
||||
resp, err := s.ec2.DeleteSecurityGroup(ec2.SecurityGroup{Name: "websrv"})
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"DeleteSecurityGroup"})
|
||||
c.Assert(req.Form["GroupName"], DeepEquals, []string{"websrv"})
|
||||
c.Assert(req.Form["GroupId"], IsNil)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
|
||||
}
|
||||
|
||||
func (s *S) TestDeleteSecurityGroupExampleWithId(c *C) {
|
||||
testServer.Response(200, nil, DeleteSecurityGroupExample)
|
||||
|
||||
// ignore return and error - we're only want to check the parameter handling.
|
||||
s.ec2.DeleteSecurityGroup(ec2.SecurityGroup{Id: "sg-67ad940e", Name: "ignored"})
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["GroupName"], IsNil)
|
||||
c.Assert(req.Form["GroupId"], DeepEquals, []string{"sg-67ad940e"})
|
||||
}
|
||||
|
||||
func (s *S) TestAuthorizeSecurityGroupExample1(c *C) {
|
||||
testServer.Response(200, nil, AuthorizeSecurityGroupIngressExample)
|
||||
|
||||
perms := []ec2.IPPerm{{
|
||||
Protocol: "tcp",
|
||||
FromPort: 80,
|
||||
ToPort: 80,
|
||||
SourceIPs: []string{"205.192.0.0/16", "205.159.0.0/16"},
|
||||
}}
|
||||
resp, err := s.ec2.AuthorizeSecurityGroup(ec2.SecurityGroup{Name: "websrv"}, perms)
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"AuthorizeSecurityGroupIngress"})
|
||||
c.Assert(req.Form["GroupName"], DeepEquals, []string{"websrv"})
|
||||
c.Assert(req.Form["IpPermissions.1.IpProtocol"], DeepEquals, []string{"tcp"})
|
||||
c.Assert(req.Form["IpPermissions.1.FromPort"], DeepEquals, []string{"80"})
|
||||
c.Assert(req.Form["IpPermissions.1.ToPort"], DeepEquals, []string{"80"})
|
||||
c.Assert(req.Form["IpPermissions.1.IpRanges.1.CidrIp"], DeepEquals, []string{"205.192.0.0/16"})
|
||||
c.Assert(req.Form["IpPermissions.1.IpRanges.2.CidrIp"], DeepEquals, []string{"205.159.0.0/16"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
|
||||
}
|
||||
|
||||
func (s *S) TestAuthorizeSecurityGroupExample1WithId(c *C) {
|
||||
testServer.Response(200, nil, AuthorizeSecurityGroupIngressExample)
|
||||
|
||||
perms := []ec2.IPPerm{{
|
||||
Protocol: "tcp",
|
||||
FromPort: 80,
|
||||
ToPort: 80,
|
||||
SourceIPs: []string{"205.192.0.0/16", "205.159.0.0/16"},
|
||||
}}
|
||||
// ignore return and error - we're only want to check the parameter handling.
|
||||
s.ec2.AuthorizeSecurityGroup(ec2.SecurityGroup{Id: "sg-67ad940e", Name: "ignored"}, perms)
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["GroupName"], IsNil)
|
||||
c.Assert(req.Form["GroupId"], DeepEquals, []string{"sg-67ad940e"})
|
||||
}
|
||||
|
||||
func (s *S) TestAuthorizeSecurityGroupExample2(c *C) {
|
||||
testServer.Response(200, nil, AuthorizeSecurityGroupIngressExample)
|
||||
|
||||
perms := []ec2.IPPerm{{
|
||||
Protocol: "tcp",
|
||||
FromPort: 80,
|
||||
ToPort: 81,
|
||||
SourceGroups: []ec2.UserSecurityGroup{
|
||||
{OwnerId: "999988887777", Name: "OtherAccountGroup"},
|
||||
{Id: "sg-67ad940e"},
|
||||
},
|
||||
}}
|
||||
resp, err := s.ec2.AuthorizeSecurityGroup(ec2.SecurityGroup{Name: "websrv"}, perms)
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"AuthorizeSecurityGroupIngress"})
|
||||
c.Assert(req.Form["GroupName"], DeepEquals, []string{"websrv"})
|
||||
c.Assert(req.Form["IpPermissions.1.IpProtocol"], DeepEquals, []string{"tcp"})
|
||||
c.Assert(req.Form["IpPermissions.1.FromPort"], DeepEquals, []string{"80"})
|
||||
c.Assert(req.Form["IpPermissions.1.ToPort"], DeepEquals, []string{"81"})
|
||||
c.Assert(req.Form["IpPermissions.1.Groups.1.UserId"], DeepEquals, []string{"999988887777"})
|
||||
c.Assert(req.Form["IpPermissions.1.Groups.1.GroupName"], DeepEquals, []string{"OtherAccountGroup"})
|
||||
c.Assert(req.Form["IpPermissions.1.Groups.2.UserId"], IsNil)
|
||||
c.Assert(req.Form["IpPermissions.1.Groups.2.GroupName"], IsNil)
|
||||
c.Assert(req.Form["IpPermissions.1.Groups.2.GroupId"], DeepEquals, []string{"sg-67ad940e"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
|
||||
}
|
||||
|
||||
func (s *S) TestRevokeSecurityGroupExample(c *C) {
|
||||
// RevokeSecurityGroup is implemented by the same code as AuthorizeSecurityGroup
|
||||
// so there's no need to duplicate all the tests.
|
||||
testServer.Response(200, nil, RevokeSecurityGroupIngressExample)
|
||||
|
||||
resp, err := s.ec2.RevokeSecurityGroup(ec2.SecurityGroup{Name: "websrv"}, nil)
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"RevokeSecurityGroupIngress"})
|
||||
c.Assert(req.Form["GroupName"], DeepEquals, []string{"websrv"})
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
|
||||
}
|
||||
|
||||
func (s *S) TestCreateTags(c *C) {
|
||||
testServer.Response(200, nil, CreateTagsExample)
|
||||
|
||||
resp, err := s.ec2.CreateTags([]string{"ami-1a2b3c4d", "i-7f4d3a2b"}, []ec2.Tag{{"webserver", ""}, {"stack", "Production"}})
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Form["ResourceId.1"], DeepEquals, []string{"ami-1a2b3c4d"})
|
||||
c.Assert(req.Form["ResourceId.2"], DeepEquals, []string{"i-7f4d3a2b"})
|
||||
c.Assert(req.Form["Tag.1.Key"], DeepEquals, []string{"webserver"})
|
||||
c.Assert(req.Form["Tag.1.Value"], DeepEquals, []string{""})
|
||||
c.Assert(req.Form["Tag.2.Key"], DeepEquals, []string{"stack"})
|
||||
c.Assert(req.Form["Tag.2.Value"], DeepEquals, []string{"Production"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
|
||||
}
|
||||
|
||||
func (s *S) TestStartInstances(c *C) {
|
||||
testServer.Response(200, nil, StartInstancesExample)
|
||||
|
||||
resp, err := s.ec2.StartInstances("i-10a64379")
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"StartInstances"})
|
||||
c.Assert(req.Form["InstanceId.1"], DeepEquals, []string{"i-10a64379"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
|
||||
|
||||
s0 := resp.StateChanges[0]
|
||||
c.Assert(s0.InstanceId, Equals, "i-10a64379")
|
||||
c.Assert(s0.CurrentState.Code, Equals, 0)
|
||||
c.Assert(s0.CurrentState.Name, Equals, "pending")
|
||||
c.Assert(s0.PreviousState.Code, Equals, 80)
|
||||
c.Assert(s0.PreviousState.Name, Equals, "stopped")
|
||||
}
|
||||
|
||||
func (s *S) TestStopInstances(c *C) {
|
||||
testServer.Response(200, nil, StopInstancesExample)
|
||||
|
||||
resp, err := s.ec2.StopInstances("i-10a64379")
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"StopInstances"})
|
||||
c.Assert(req.Form["InstanceId.1"], DeepEquals, []string{"i-10a64379"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
|
||||
|
||||
s0 := resp.StateChanges[0]
|
||||
c.Assert(s0.InstanceId, Equals, "i-10a64379")
|
||||
c.Assert(s0.CurrentState.Code, Equals, 64)
|
||||
c.Assert(s0.CurrentState.Name, Equals, "stopping")
|
||||
c.Assert(s0.PreviousState.Code, Equals, 16)
|
||||
c.Assert(s0.PreviousState.Name, Equals, "running")
|
||||
}
|
||||
|
||||
func (s *S) TestRebootInstances(c *C) {
|
||||
testServer.Response(200, nil, RebootInstancesExample)
|
||||
|
||||
resp, err := s.ec2.RebootInstances("i-10a64379")
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"RebootInstances"})
|
||||
c.Assert(req.Form["InstanceId.1"], DeepEquals, []string{"i-10a64379"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
|
||||
}
|
||||
|
||||
func (s *S) TestSignatureWithEndpointPath(c *C) {
|
||||
ec2.FakeTime(true)
|
||||
defer ec2.FakeTime(false)
|
||||
|
||||
testServer.Response(200, nil, RebootInstancesExample)
|
||||
|
||||
// https://bugs.launchpad.net/goamz/+bug/1022749
|
||||
ec2 := ec2.New(s.ec2.Auth, aws.Region{EC2Endpoint: testServer.URL + "/services/Cloud"})
|
||||
|
||||
_, err := ec2.RebootInstances("i-10a64379")
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Form["Signature"], DeepEquals, []string{"gdG/vEm+c6ehhhfkrJy3+wuVzw/rzKR42TYelMwti7M="})
|
||||
}
|
||||
|
||||
func (s *S) TestAvailabilityZonesExample1(c *C) {
|
||||
testServer.Response(200, nil, DescribeAvailabilityZonesExample1)
|
||||
|
||||
resp, err := s.ec2.AvailabilityZones(nil)
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeAvailabilityZones"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
|
||||
c.Assert(resp.Zones, HasLen, 4)
|
||||
|
||||
z0 := resp.Zones[0]
|
||||
c.Assert(z0.Name, Equals, "us-east-1a")
|
||||
c.Assert(z0.Region, Equals, "us-east-1")
|
||||
c.Assert(z0.State, Equals, "available")
|
||||
c.Assert(z0.MessageSet, HasLen, 0)
|
||||
|
||||
z1 := resp.Zones[1]
|
||||
c.Assert(z1.Name, Equals, "us-east-1b")
|
||||
c.Assert(z1.Region, Equals, "us-east-1")
|
||||
c.Assert(z1.State, Equals, "available")
|
||||
c.Assert(z1.MessageSet, HasLen, 0)
|
||||
|
||||
z2 := resp.Zones[2]
|
||||
c.Assert(z2.Name, Equals, "us-east-1c")
|
||||
c.Assert(z2.Region, Equals, "us-east-1")
|
||||
c.Assert(z2.State, Equals, "available")
|
||||
c.Assert(z2.MessageSet, HasLen, 0)
|
||||
|
||||
z3 := resp.Zones[3]
|
||||
c.Assert(z3.Name, Equals, "us-east-1d")
|
||||
c.Assert(z3.Region, Equals, "us-east-1")
|
||||
c.Assert(z3.State, Equals, "available")
|
||||
c.Assert(z3.MessageSet, HasLen, 0)
|
||||
}
|
||||
|
||||
func (s *S) TestAvailabilityZonesExample2(c *C) {
|
||||
testServer.Response(200, nil, DescribeAvailabilityZonesExample2)
|
||||
|
||||
resp, err := s.ec2.AvailabilityZones(nil)
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeAvailabilityZones"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
|
||||
c.Assert(resp.Zones, HasLen, 2)
|
||||
|
||||
z0 := resp.Zones[0]
|
||||
c.Assert(z0.Name, Equals, "us-east-1a")
|
||||
c.Assert(z0.Region, Equals, "us-east-1")
|
||||
c.Assert(z0.State, Equals, "impaired")
|
||||
c.Assert(z0.MessageSet, HasLen, 0)
|
||||
|
||||
z1 := resp.Zones[1]
|
||||
c.Assert(z1.Name, Equals, "us-east-1b")
|
||||
c.Assert(z1.Region, Equals, "us-east-1")
|
||||
c.Assert(z1.State, Equals, "unavailable")
|
||||
c.Assert(z1.MessageSet, DeepEquals, []string{"us-east-1b is currently down for maintenance."})
|
||||
}
|
||||
|
||||
func (s *S) TestDescribeAccountAttributesExamples(c *C) {
|
||||
testServer.Response(200, nil, DescribeAccountAttributesExample)
|
||||
|
||||
resp, err := s.ec2.AccountAttributes("supported-platforms")
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
assertAttribute := func(name, value string) {
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeAccountAttributes"})
|
||||
c.Assert(req.Form["AttributeName.1"], DeepEquals, []string{name})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE")
|
||||
c.Assert(resp.Attributes, HasLen, 1)
|
||||
attr := resp.Attributes[0]
|
||||
c.Check(attr.Name, Equals, name)
|
||||
c.Check(attr.Values, DeepEquals, []string{value})
|
||||
}
|
||||
assertAttribute("supported-platforms", "VPC")
|
||||
|
||||
testServer.Response(200, nil, DescribeAccountAttributesExample2)
|
||||
resp, err = s.ec2.AccountAttributes("default-vpc")
|
||||
req = testServer.WaitRequest()
|
||||
assertAttribute("default-vpc", "vpc-xxxxxxxx")
|
||||
|
||||
testServer.Response(200, nil, DescribeAccountAttributesExample3)
|
||||
resp, err = s.ec2.AccountAttributes("default-vpc")
|
||||
req = testServer.WaitRequest()
|
||||
assertAttribute("default-vpc", "none")
|
||||
}
|
||||
204
vendor/src/launchpad.net/goamz/ec2/ec2i_test.go
vendored
Normal file
204
vendor/src/launchpad.net/goamz/ec2/ec2i_test.go
vendored
Normal file
@@ -0,0 +1,204 @@
|
||||
package ec2_test
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
|
||||
"launchpad.net/goamz/aws"
|
||||
"launchpad.net/goamz/ec2"
|
||||
"launchpad.net/goamz/testutil"
|
||||
. "launchpad.net/gocheck"
|
||||
)
|
||||
|
||||
// AmazonServer represents an Amazon EC2 server.
|
||||
type AmazonServer struct {
|
||||
auth aws.Auth
|
||||
}
|
||||
|
||||
func (s *AmazonServer) SetUp(c *C) {
|
||||
auth, err := aws.EnvAuth()
|
||||
if err != nil {
|
||||
c.Fatal(err.Error())
|
||||
}
|
||||
s.auth = auth
|
||||
}
|
||||
|
||||
// Suite cost per run: 0.02 USD
|
||||
var _ = Suite(&AmazonClientSuite{})
|
||||
|
||||
// AmazonClientSuite tests the client against a live EC2 server.
|
||||
type AmazonClientSuite struct {
|
||||
srv AmazonServer
|
||||
ClientTests
|
||||
}
|
||||
|
||||
func (s *AmazonClientSuite) SetUpSuite(c *C) {
|
||||
if !testutil.Amazon {
|
||||
c.Skip("AmazonClientSuite tests not enabled")
|
||||
}
|
||||
s.srv.SetUp(c)
|
||||
s.ec2 = ec2.New(s.srv.auth, aws.USEast)
|
||||
}
|
||||
|
||||
// ClientTests defines integration tests designed to test the client.
|
||||
// It is not used as a test suite in itself, but embedded within
|
||||
// another type.
|
||||
type ClientTests struct {
|
||||
ec2 *ec2.EC2
|
||||
}
|
||||
|
||||
var imageId = "ami-ccf405a5" // Ubuntu Maverick, i386, EBS store
|
||||
|
||||
// Cost: 0.00 USD
|
||||
func (s *ClientTests) TestRunInstancesError(c *C) {
|
||||
options := ec2.RunInstances{
|
||||
ImageId: "ami-a6f504cf", // Ubuntu Maverick, i386, instance store
|
||||
InstanceType: "t1.micro", // Doesn't work with micro, results in 400.
|
||||
}
|
||||
|
||||
resp, err := s.ec2.RunInstances(&options)
|
||||
|
||||
c.Assert(resp, IsNil)
|
||||
c.Assert(err, ErrorMatches, "AMI.*root device.*not supported.*")
|
||||
|
||||
ec2err, ok := err.(*ec2.Error)
|
||||
c.Assert(ok, Equals, true)
|
||||
c.Assert(ec2err.StatusCode, Equals, 400)
|
||||
c.Assert(ec2err.Code, Equals, "UnsupportedOperation")
|
||||
c.Assert(ec2err.Message, Matches, "AMI.*root device.*not supported.*")
|
||||
c.Assert(ec2err.RequestId, Matches, ".+")
|
||||
}
|
||||
|
||||
// Cost: 0.02 USD
|
||||
func (s *ClientTests) TestRunAndTerminate(c *C) {
|
||||
options := ec2.RunInstances{
|
||||
ImageId: imageId,
|
||||
InstanceType: "t1.micro",
|
||||
}
|
||||
resp1, err := s.ec2.RunInstances(&options)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(resp1.ReservationId, Matches, "r-[0-9a-f]*")
|
||||
c.Check(resp1.OwnerId, Matches, "[0-9]+")
|
||||
c.Check(resp1.Instances, HasLen, 1)
|
||||
c.Check(resp1.Instances[0].InstanceType, Equals, "t1.micro")
|
||||
|
||||
instId := resp1.Instances[0].InstanceId
|
||||
|
||||
resp2, err := s.ec2.Instances([]string{instId}, nil)
|
||||
c.Assert(err, IsNil)
|
||||
if c.Check(resp2.Reservations, HasLen, 1) && c.Check(len(resp2.Reservations[0].Instances), Equals, 1) {
|
||||
inst := resp2.Reservations[0].Instances[0]
|
||||
c.Check(inst.InstanceId, Equals, instId)
|
||||
}
|
||||
|
||||
resp3, err := s.ec2.TerminateInstances([]string{instId})
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(resp3.StateChanges, HasLen, 1)
|
||||
c.Check(resp3.StateChanges[0].InstanceId, Equals, instId)
|
||||
c.Check(resp3.StateChanges[0].CurrentState.Name, Equals, "shutting-down")
|
||||
c.Check(resp3.StateChanges[0].CurrentState.Code, Equals, 32)
|
||||
}
|
||||
|
||||
// Cost: 0.00 USD
|
||||
func (s *ClientTests) TestSecurityGroups(c *C) {
|
||||
name := "goamz-test"
|
||||
descr := "goamz security group for tests"
|
||||
|
||||
// Clean it up, if a previous test left it around and avoid leaving it around.
|
||||
s.ec2.DeleteSecurityGroup(ec2.SecurityGroup{Name: name})
|
||||
defer s.ec2.DeleteSecurityGroup(ec2.SecurityGroup{Name: name})
|
||||
|
||||
resp1, err := s.ec2.CreateSecurityGroup(name, descr)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp1.RequestId, Matches, ".+")
|
||||
c.Assert(resp1.Name, Equals, name)
|
||||
c.Assert(resp1.Id, Matches, ".+")
|
||||
|
||||
resp1, err = s.ec2.CreateSecurityGroup(name, descr)
|
||||
ec2err, _ := err.(*ec2.Error)
|
||||
c.Assert(resp1, IsNil)
|
||||
c.Assert(ec2err, NotNil)
|
||||
c.Assert(ec2err.Code, Equals, "InvalidGroup.Duplicate")
|
||||
|
||||
perms := []ec2.IPPerm{{
|
||||
Protocol: "tcp",
|
||||
FromPort: 0,
|
||||
ToPort: 1024,
|
||||
SourceIPs: []string{"127.0.0.1/24"},
|
||||
}}
|
||||
|
||||
resp2, err := s.ec2.AuthorizeSecurityGroup(ec2.SecurityGroup{Name: name}, perms)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp2.RequestId, Matches, ".+")
|
||||
|
||||
resp3, err := s.ec2.SecurityGroups(ec2.SecurityGroupNames(name), nil)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp3.RequestId, Matches, ".+")
|
||||
c.Assert(resp3.Groups, HasLen, 1)
|
||||
|
||||
g0 := resp3.Groups[0]
|
||||
c.Assert(g0.Name, Equals, name)
|
||||
c.Assert(g0.Description, Equals, descr)
|
||||
c.Assert(g0.IPPerms, HasLen, 1)
|
||||
c.Assert(g0.IPPerms[0].Protocol, Equals, "tcp")
|
||||
c.Assert(g0.IPPerms[0].FromPort, Equals, 0)
|
||||
c.Assert(g0.IPPerms[0].ToPort, Equals, 1024)
|
||||
c.Assert(g0.IPPerms[0].SourceIPs, DeepEquals, []string{"127.0.0.1/24"})
|
||||
|
||||
resp2, err = s.ec2.DeleteSecurityGroup(ec2.SecurityGroup{Name: name})
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp2.RequestId, Matches, ".+")
|
||||
}
|
||||
|
||||
var sessionId = func() string {
|
||||
buf := make([]byte, 8)
|
||||
// if we have no randomness, we'll just make do, so ignore the error.
|
||||
rand.Read(buf)
|
||||
return fmt.Sprintf("%x", buf)
|
||||
}()
|
||||
|
||||
// sessionName reutrns a name that is probably
|
||||
// unique to this test session.
|
||||
func sessionName(prefix string) string {
|
||||
return prefix + "-" + sessionId
|
||||
}
|
||||
|
||||
var allRegions = []aws.Region{
|
||||
aws.USEast,
|
||||
aws.USWest,
|
||||
aws.EUWest,
|
||||
aws.APSoutheast,
|
||||
aws.APNortheast,
|
||||
}
|
||||
|
||||
// Communicate with all EC2 endpoints to see if they are alive.
|
||||
func (s *ClientTests) TestRegions(c *C) {
|
||||
name := sessionName("goamz-region-test")
|
||||
perms := []ec2.IPPerm{{
|
||||
Protocol: "tcp",
|
||||
FromPort: 80,
|
||||
ToPort: 80,
|
||||
SourceIPs: []string{"127.0.0.1/32"},
|
||||
}}
|
||||
errs := make(chan error, len(allRegions))
|
||||
for _, region := range allRegions {
|
||||
go func(r aws.Region) {
|
||||
e := ec2.New(s.ec2.Auth, r)
|
||||
_, err := e.AuthorizeSecurityGroup(ec2.SecurityGroup{Name: name}, perms)
|
||||
errs <- err
|
||||
}(region)
|
||||
}
|
||||
for _ = range allRegions {
|
||||
err := <-errs
|
||||
if err != nil {
|
||||
ec2_err, ok := err.(*ec2.Error)
|
||||
if ok {
|
||||
c.Check(ec2_err.Code, Matches, "InvalidGroup.NotFound")
|
||||
} else {
|
||||
c.Errorf("Non-EC2 error: %s", err)
|
||||
}
|
||||
} else {
|
||||
c.Errorf("Test should have errored but it seems to have succeeded")
|
||||
}
|
||||
}
|
||||
}
|
||||
1072
vendor/src/launchpad.net/goamz/ec2/ec2t_test.go
vendored
Normal file
1072
vendor/src/launchpad.net/goamz/ec2/ec2t_test.go
vendored
Normal file
File diff suppressed because it is too large
Load Diff
84
vendor/src/launchpad.net/goamz/ec2/ec2test/filter.go
vendored
Normal file
84
vendor/src/launchpad.net/goamz/ec2/ec2test/filter.go
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
package ec2test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// filter holds an ec2 filter. A filter maps an attribute to a set of
|
||||
// possible values for that attribute. For an item to pass through the
|
||||
// filter, every attribute of the item mentioned in the filter must match
|
||||
// at least one of its given values.
|
||||
type filter map[string][]string
|
||||
|
||||
// newFilter creates a new filter from the Filter fields in the url form.
|
||||
//
|
||||
// The filtering is specified through a map of name=>values, where the
|
||||
// name is a well-defined key identifying the data to be matched,
|
||||
// and the list of values holds the possible values the filtered
|
||||
// item can take for the key to be included in the
|
||||
// result set. For example:
|
||||
//
|
||||
// Filter.1.Name=instance-type
|
||||
// Filter.1.Value.1=m1.small
|
||||
// Filter.1.Value.2=m1.large
|
||||
//
|
||||
func newFilter(form url.Values) filter {
|
||||
// TODO return an error if the fields are not well formed?
|
||||
names := make(map[int]string)
|
||||
values := make(map[int][]string)
|
||||
maxId := 0
|
||||
for name, fvalues := range form {
|
||||
var rest string
|
||||
var id int
|
||||
if x, _ := fmt.Sscanf(name, "Filter.%d.%s", &id, &rest); x != 2 {
|
||||
continue
|
||||
}
|
||||
if id > maxId {
|
||||
maxId = id
|
||||
}
|
||||
if rest == "Name" {
|
||||
names[id] = fvalues[0]
|
||||
continue
|
||||
}
|
||||
if !strings.HasPrefix(rest, "Value.") {
|
||||
continue
|
||||
}
|
||||
values[id] = append(values[id], fvalues[0])
|
||||
}
|
||||
|
||||
f := make(filter)
|
||||
for id, name := range names {
|
||||
f[name] = values[id]
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func notDigit(r rune) bool {
|
||||
return r < '0' || r > '9'
|
||||
}
|
||||
|
||||
// filterable represents an object that can be passed through a filter.
|
||||
type filterable interface {
|
||||
// matchAttr returns true if given attribute of the
|
||||
// object matches value. It returns an error if the
|
||||
// attribute is not recognised or the value is malformed.
|
||||
matchAttr(attr, value string) (bool, error)
|
||||
}
|
||||
|
||||
// ok returns true if x passes through the filter.
|
||||
func (f filter) ok(x filterable) (bool, error) {
|
||||
next:
|
||||
for a, vs := range f {
|
||||
for _, v := range vs {
|
||||
if ok, err := x.matchAttr(a, v); ok {
|
||||
continue next
|
||||
} else if err != nil {
|
||||
return false, fmt.Errorf("bad attribute or value %q=%q for type %T: %v", a, v, x, err)
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
2032
vendor/src/launchpad.net/goamz/ec2/ec2test/server.go
vendored
Normal file
2032
vendor/src/launchpad.net/goamz/ec2/ec2test/server.go
vendored
Normal file
File diff suppressed because it is too large
Load Diff
24
vendor/src/launchpad.net/goamz/ec2/export_test.go
vendored
Normal file
24
vendor/src/launchpad.net/goamz/ec2/export_test.go
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
package ec2
|
||||
|
||||
import (
|
||||
"launchpad.net/goamz/aws"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Sign(auth aws.Auth, method, path string, params map[string]string, host string) {
|
||||
sign(auth, method, path, params, host)
|
||||
}
|
||||
|
||||
func fixedTime() time.Time {
|
||||
return time.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
}
|
||||
|
||||
func FakeTime(fakeIt bool) {
|
||||
if fakeIt {
|
||||
timeNow = fixedTime
|
||||
} else {
|
||||
timeNow = time.Now
|
||||
}
|
||||
}
|
||||
|
||||
var PrepareRunParams = prepareRunParams
|
||||
212
vendor/src/launchpad.net/goamz/ec2/networkinterfaces.go
vendored
Normal file
212
vendor/src/launchpad.net/goamz/ec2/networkinterfaces.go
vendored
Normal file
@@ -0,0 +1,212 @@
|
||||
//
|
||||
// goamz - Go packages to interact with the Amazon Web Services.
|
||||
//
|
||||
// https://wiki.ubuntu.com/goamz
|
||||
//
|
||||
// Copyright (c) 2014 Canonical Ltd.
|
||||
//
|
||||
|
||||
package ec2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// NetworkInterfaceAttachment describes a network interface
|
||||
// attachment.
|
||||
//
|
||||
// See http://goo.gl/KtiKuV for more details.
|
||||
type NetworkInterfaceAttachment struct {
|
||||
Id string `xml:"attachmentId"`
|
||||
InstanceId string `xml:"instanceId"`
|
||||
InstanceOwnerId string `xml:"instanceOwnerId"`
|
||||
DeviceIndex int `xml:"deviceIndex"`
|
||||
Status string `xml:"status"`
|
||||
AttachTime string `xml:"attachTime"`
|
||||
DeleteOnTermination bool `xml:"deleteOnTermination"`
|
||||
}
|
||||
|
||||
// PrivateIP describes a private IP address of a network interface.
|
||||
//
|
||||
// See http://goo.gl/jtuQEJ for more details.
|
||||
type PrivateIP struct {
|
||||
Address string `xml:"privateIpAddress"`
|
||||
DNSName string `xml:"privateDnsName"`
|
||||
IsPrimary bool `xml:"primary"`
|
||||
}
|
||||
|
||||
// NetworkInterface describes a network interface for VPC.
|
||||
//
|
||||
// See http://goo.gl/G63OQL for more details.
|
||||
type NetworkInterface struct {
|
||||
Id string `xml:"networkInterfaceId"`
|
||||
SubnetId string `xml:"subnetId"`
|
||||
VPCId string `xml:"vpcId"`
|
||||
AvailZone string `xml:"availabilityZone"`
|
||||
Description string `xml:"description"`
|
||||
OwnerId string `xml:"ownerId"`
|
||||
RequesterId string `xml:"requesterId"`
|
||||
RequesterManaged bool `xml:"requesterManaged"`
|
||||
Status string `xml:"status"`
|
||||
MACAddress string `xml:"macAddress"`
|
||||
PrivateIPAddress string `xml:"privateIpAddress"`
|
||||
PrivateDNSName string `xml:"privateDnsName"`
|
||||
SourceDestCheck bool `xml:"sourceDestCheck"`
|
||||
Groups []SecurityGroup `xml:"groupSet>item"`
|
||||
Attachment NetworkInterfaceAttachment `xml:"attachment"`
|
||||
Tags []Tag `xml:"tagSet>item"`
|
||||
PrivateIPs []PrivateIP `xml:"privateIpAddressesSet>item"`
|
||||
}
|
||||
|
||||
// CreateNetworkInterface encapsulates options for the
|
||||
// CreateNetworkInterface call.
|
||||
//
|
||||
// SubnetId is the only required field.
|
||||
//
|
||||
// One or more private IP addresses can be specified by using the
|
||||
// PrivateIPs slice. Only one provided PrivateIP may be set as
|
||||
// primary.
|
||||
//
|
||||
// If PrivateIPs is empty, EC2 selects a primary private IP from the
|
||||
// subnet range.
|
||||
//
|
||||
// When SecondaryPrivateIPCount is non-zero, EC2 allocates that
|
||||
// number of IP addresses from within the subnet range. The number of
|
||||
// IP addresses you can assign to a network interface varies by
|
||||
// instance type.
|
||||
type CreateNetworkInterface struct {
|
||||
SubnetId string
|
||||
PrivateIPs []PrivateIP
|
||||
SecondaryPrivateIPCount int
|
||||
Description string
|
||||
SecurityGroupIds []string
|
||||
}
|
||||
|
||||
// CreateNetworkInterfaceResp is the response to a
|
||||
// CreateNetworkInterface request.
|
||||
//
|
||||
// See http://goo.gl/ze3VhA for more details.
|
||||
type CreateNetworkInterfaceResp struct {
|
||||
RequestId string `xml:"requestId"`
|
||||
NetworkInterface NetworkInterface `xml:"networkInterface"`
|
||||
}
|
||||
|
||||
// CreateNetworkInterface creates a network interface in the specified
|
||||
// subnet.
|
||||
//
|
||||
// See http://goo.gl/ze3VhA for more details.
|
||||
func (ec2 *EC2) CreateNetworkInterface(opts CreateNetworkInterface) (resp *CreateNetworkInterfaceResp, err error) {
|
||||
params := makeParamsVPC("CreateNetworkInterface")
|
||||
params["SubnetId"] = opts.SubnetId
|
||||
for i, ip := range opts.PrivateIPs {
|
||||
prefix := fmt.Sprintf("PrivateIpAddresses.%d.", i+1)
|
||||
params[prefix+"PrivateIpAddress"] = ip.Address
|
||||
params[prefix+"Primary"] = strconv.FormatBool(ip.IsPrimary)
|
||||
}
|
||||
if opts.Description != "" {
|
||||
params["Description"] = opts.Description
|
||||
}
|
||||
if opts.SecondaryPrivateIPCount > 0 {
|
||||
count := strconv.Itoa(opts.SecondaryPrivateIPCount)
|
||||
params["SecondaryPrivateIpAddressCount"] = count
|
||||
}
|
||||
for i, groupId := range opts.SecurityGroupIds {
|
||||
params["SecurityGroupId."+strconv.Itoa(i+1)] = groupId
|
||||
}
|
||||
resp = &CreateNetworkInterfaceResp{}
|
||||
err = ec2.query(params, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// DeleteNetworkInterface deletes the specified network interface,
|
||||
// which must have been previously detached.
|
||||
//
|
||||
// See http://goo.gl/MC1yOj for more details.
|
||||
func (ec2 *EC2) DeleteNetworkInterface(id string) (resp *SimpleResp, err error) {
|
||||
params := makeParamsVPC("DeleteNetworkInterface")
|
||||
params["NetworkInterfaceId"] = id
|
||||
resp = &SimpleResp{}
|
||||
err = ec2.query(params, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// NetworkInterfacesResp is the response to a NetworkInterfaces
|
||||
// request.
|
||||
//
|
||||
// See http://goo.gl/2LcXtM for more details.
|
||||
type NetworkInterfacesResp struct {
|
||||
RequestId string `xml:"requestId"`
|
||||
Interfaces []NetworkInterface `xml:"networkInterfaceSet>item"`
|
||||
}
|
||||
|
||||
// NetworkInterfaces returns a list of network interfaces.
|
||||
//
|
||||
// If the ids or filter parameters are provided, only matching
|
||||
// interfaces are returned.
|
||||
//
|
||||
// See http://goo.gl/2LcXtM for more details.
|
||||
func (ec2 *EC2) NetworkInterfaces(ids []string, filter *Filter) (resp *NetworkInterfacesResp, err error) {
|
||||
params := makeParamsVPC("DescribeNetworkInterfaces")
|
||||
for i, id := range ids {
|
||||
params["NetworkInterfaceId."+strconv.Itoa(i+1)] = id
|
||||
}
|
||||
filter.addParams(params)
|
||||
|
||||
resp = &NetworkInterfacesResp{}
|
||||
err = ec2.query(params, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// AttachNetworkInterfaceResp is the response to an
|
||||
// AttachNetworkInterface request.
|
||||
//
|
||||
// See http://goo.gl/rEbSii for more details.
|
||||
type AttachNetworkInterfaceResp struct {
|
||||
RequestId string `xml:"requestId"`
|
||||
AttachmentId string `xml:"attachmentId"`
|
||||
}
|
||||
|
||||
// AttachNetworkInterface attaches a network interface to an instance.
|
||||
//
|
||||
// See http://goo.gl/rEbSii for more details.
|
||||
func (ec2 *EC2) AttachNetworkInterface(interfaceId, instanceId string, deviceIndex int) (resp *AttachNetworkInterfaceResp, err error) {
|
||||
params := makeParamsVPC("AttachNetworkInterface")
|
||||
params["NetworkInterfaceId"] = interfaceId
|
||||
params["InstanceId"] = instanceId
|
||||
params["DeviceIndex"] = strconv.Itoa(deviceIndex)
|
||||
resp = &AttachNetworkInterfaceResp{}
|
||||
err = ec2.query(params, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// DetachNetworkInterface detaches a network interface from an
|
||||
// instance.
|
||||
//
|
||||
// See http://goo.gl/0Xc1px for more details.
|
||||
func (ec2 *EC2) DetachNetworkInterface(attachmentId string, force bool) (resp *SimpleResp, err error) {
|
||||
params := makeParamsVPC("DetachNetworkInterface")
|
||||
params["AttachmentId"] = attachmentId
|
||||
if force {
|
||||
// Force is optional.
|
||||
params["Force"] = "true"
|
||||
}
|
||||
resp = &SimpleResp{}
|
||||
err = ec2.query(params, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
360
vendor/src/launchpad.net/goamz/ec2/networkinterfaces_test.go
vendored
Normal file
360
vendor/src/launchpad.net/goamz/ec2/networkinterfaces_test.go
vendored
Normal file
@@ -0,0 +1,360 @@
|
||||
//
|
||||
// goamz - Go packages to interact with the Amazon Web Services.
|
||||
//
|
||||
// https://wiki.ubuntu.com/goamz
|
||||
//
|
||||
// Copyright (c) 2014 Canonical Ltd.
|
||||
//
|
||||
|
||||
package ec2_test
|
||||
|
||||
import (
|
||||
"launchpad.net/goamz/aws"
|
||||
"launchpad.net/goamz/ec2"
|
||||
. "launchpad.net/gocheck"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Network interface tests with example responses
|
||||
|
||||
func (s *S) TestCreateNetworkInterfaceExample(c *C) {
|
||||
testServer.Response(200, nil, CreateNetworkInterfaceExample)
|
||||
|
||||
resp, err := s.ec2.CreateNetworkInterface(ec2.CreateNetworkInterface{
|
||||
SubnetId: "subnet-b2a249da",
|
||||
PrivateIPs: []ec2.PrivateIP{
|
||||
{Address: "10.0.2.157", IsPrimary: true},
|
||||
},
|
||||
SecurityGroupIds: []string{"sg-1a2b3c4d"},
|
||||
})
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"CreateNetworkInterface"})
|
||||
c.Assert(req.Form["SubnetId"], DeepEquals, []string{"subnet-b2a249da"})
|
||||
c.Assert(req.Form["PrivateIpAddress"], HasLen, 0)
|
||||
c.Assert(
|
||||
req.Form["PrivateIpAddresses.1.PrivateIpAddress"],
|
||||
DeepEquals,
|
||||
[]string{"10.0.2.157"},
|
||||
)
|
||||
c.Assert(
|
||||
req.Form["PrivateIpAddresses.1.Primary"],
|
||||
DeepEquals,
|
||||
[]string{"true"},
|
||||
)
|
||||
c.Assert(req.Form["Description"], HasLen, 0)
|
||||
c.Assert(req.Form["SecurityGroupId.1"], DeepEquals, []string{"sg-1a2b3c4d"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "8dbe591e-5a22-48cb-b948-dd0aadd55adf")
|
||||
iface := resp.NetworkInterface
|
||||
c.Check(iface.Id, Equals, "eni-cfca76a6")
|
||||
c.Check(iface.SubnetId, Equals, "subnet-b2a249da")
|
||||
c.Check(iface.VPCId, Equals, "vpc-c31dafaa")
|
||||
c.Check(iface.AvailZone, Equals, "ap-southeast-1b")
|
||||
c.Check(iface.Description, Equals, "")
|
||||
c.Check(iface.OwnerId, Equals, "251839141158")
|
||||
c.Check(iface.RequesterManaged, Equals, false)
|
||||
c.Check(iface.Status, Equals, "available")
|
||||
c.Check(iface.MACAddress, Equals, "02:74:b0:72:79:61")
|
||||
c.Check(iface.PrivateIPAddress, Equals, "10.0.2.157")
|
||||
c.Check(iface.SourceDestCheck, Equals, true)
|
||||
c.Check(iface.Groups, DeepEquals, []ec2.SecurityGroup{
|
||||
{Id: "sg-1a2b3c4d", Name: "default"},
|
||||
})
|
||||
c.Check(iface.Tags, HasLen, 0)
|
||||
c.Check(iface.PrivateIPs, DeepEquals, []ec2.PrivateIP{
|
||||
{Address: "10.0.2.157", IsPrimary: true},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *S) TestDeleteNetworkInterfaceExample(c *C) {
|
||||
testServer.Response(200, nil, DeleteNetworkInterfaceExample)
|
||||
|
||||
resp, err := s.ec2.DeleteNetworkInterface("eni-id")
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"DeleteNetworkInterface"})
|
||||
c.Assert(req.Form["NetworkInterfaceId"], DeepEquals, []string{"eni-id"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "e1c6d73b-edaa-4e62-9909-6611404e1739")
|
||||
}
|
||||
|
||||
func (s *S) TestNetworkInterfacesExample(c *C) {
|
||||
testServer.Response(200, nil, DescribeNetworkInterfacesExample)
|
||||
|
||||
ids := []string{"eni-0f62d866", "eni-a66ed5cf"}
|
||||
resp, err := s.ec2.NetworkInterfaces(ids, nil)
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeNetworkInterfaces"})
|
||||
c.Assert(req.Form["NetworkInterfaceId.1"], DeepEquals, []string{ids[0]})
|
||||
c.Assert(req.Form["NetworkInterfaceId.2"], DeepEquals, []string{ids[1]})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "fc45294c-006b-457b-bab9-012f5b3b0e40")
|
||||
c.Check(resp.Interfaces, HasLen, 2)
|
||||
iface := resp.Interfaces[0]
|
||||
c.Check(iface.Id, Equals, ids[0])
|
||||
c.Check(iface.SubnetId, Equals, "subnet-c53c87ac")
|
||||
c.Check(iface.VPCId, Equals, "vpc-cc3c87a5")
|
||||
c.Check(iface.AvailZone, Equals, "ap-southeast-1b")
|
||||
c.Check(iface.Description, Equals, "")
|
||||
c.Check(iface.OwnerId, Equals, "053230519467")
|
||||
c.Check(iface.RequesterManaged, Equals, false)
|
||||
c.Check(iface.Status, Equals, "in-use")
|
||||
c.Check(iface.MACAddress, Equals, "02:81:60:cb:27:37")
|
||||
c.Check(iface.PrivateIPAddress, Equals, "10.0.0.146")
|
||||
c.Check(iface.SourceDestCheck, Equals, true)
|
||||
c.Check(iface.Groups, DeepEquals, []ec2.SecurityGroup{
|
||||
{Id: "sg-3f4b5653", Name: "default"},
|
||||
})
|
||||
c.Check(iface.Attachment, DeepEquals, ec2.NetworkInterfaceAttachment{
|
||||
Id: "eni-attach-6537fc0c",
|
||||
InstanceId: "i-22197876",
|
||||
InstanceOwnerId: "053230519467",
|
||||
DeviceIndex: 0,
|
||||
Status: "attached",
|
||||
AttachTime: "2012-07-01T21:45:27.000Z",
|
||||
DeleteOnTermination: true,
|
||||
})
|
||||
c.Check(iface.PrivateIPs, DeepEquals, []ec2.PrivateIP{
|
||||
{Address: "10.0.0.146", IsPrimary: true},
|
||||
{Address: "10.0.0.148", IsPrimary: false},
|
||||
{Address: "10.0.0.150", IsPrimary: false},
|
||||
})
|
||||
c.Check(iface.Tags, HasLen, 0)
|
||||
|
||||
iface = resp.Interfaces[1]
|
||||
c.Check(iface.Id, Equals, ids[1])
|
||||
c.Check(iface.SubnetId, Equals, "subnet-cd8a35a4")
|
||||
c.Check(iface.VPCId, Equals, "vpc-f28a359b")
|
||||
c.Check(iface.AvailZone, Equals, "ap-southeast-1b")
|
||||
c.Check(iface.Description, Equals, "Primary network interface")
|
||||
c.Check(iface.OwnerId, Equals, "053230519467")
|
||||
c.Check(iface.RequesterManaged, Equals, false)
|
||||
c.Check(iface.Status, Equals, "in-use")
|
||||
c.Check(iface.MACAddress, Equals, "02:78:d7:00:8a:1e")
|
||||
c.Check(iface.PrivateIPAddress, Equals, "10.0.1.233")
|
||||
c.Check(iface.SourceDestCheck, Equals, true)
|
||||
c.Check(iface.Groups, DeepEquals, []ec2.SecurityGroup{
|
||||
{Id: "sg-a2a0b2ce", Name: "quick-start-1"},
|
||||
})
|
||||
c.Check(iface.Attachment, DeepEquals, ec2.NetworkInterfaceAttachment{
|
||||
Id: "eni-attach-a99c57c0",
|
||||
InstanceId: "i-886401dc",
|
||||
InstanceOwnerId: "053230519467",
|
||||
DeviceIndex: 0,
|
||||
Status: "attached",
|
||||
AttachTime: "2012-06-27T20:08:44.000Z",
|
||||
DeleteOnTermination: true,
|
||||
})
|
||||
c.Check(iface.PrivateIPs, DeepEquals, []ec2.PrivateIP{
|
||||
{Address: "10.0.1.233", IsPrimary: true},
|
||||
{Address: "10.0.1.20", IsPrimary: false},
|
||||
})
|
||||
c.Check(iface.Tags, HasLen, 0)
|
||||
}
|
||||
|
||||
func (s *S) TestAttachNetworkInterfaceExample(c *C) {
|
||||
testServer.Response(200, nil, AttachNetworkInterfaceExample)
|
||||
|
||||
resp, err := s.ec2.AttachNetworkInterface("eni-id", "i-id", 0)
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"AttachNetworkInterface"})
|
||||
c.Assert(req.Form["NetworkInterfaceId"], DeepEquals, []string{"eni-id"})
|
||||
c.Assert(req.Form["InstanceId"], DeepEquals, []string{"i-id"})
|
||||
c.Assert(req.Form["DeviceIndex"], DeepEquals, []string{"0"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "ace8cd1e-e685-4e44-90fb-92014d907212")
|
||||
c.Assert(resp.AttachmentId, Equals, "eni-attach-d94b09b0")
|
||||
}
|
||||
|
||||
func (s *S) TestDetachNetworkInterfaceExample(c *C) {
|
||||
testServer.Response(200, nil, DetachNetworkInterfaceExample)
|
||||
|
||||
resp, err := s.ec2.DetachNetworkInterface("eni-attach-id", true)
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"DetachNetworkInterface"})
|
||||
c.Assert(req.Form["AttachmentId"], DeepEquals, []string{"eni-attach-id"})
|
||||
c.Assert(req.Form["Force"], DeepEquals, []string{"true"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "ce540707-0635-46bc-97da-33a8a362a0e8")
|
||||
}
|
||||
|
||||
// Network interface tests run against either a local test server or
|
||||
// live on EC2.
|
||||
|
||||
func (s *ServerTests) TestNetworkInterfaces(c *C) {
|
||||
vpcResp, err := s.ec2.CreateVPC("10.3.0.0/16", "")
|
||||
c.Assert(err, IsNil)
|
||||
vpcId := vpcResp.VPC.Id
|
||||
defer s.deleteVPCs(c, []string{vpcId})
|
||||
|
||||
subResp := s.createSubnet(c, vpcId, "10.3.1.0/24", "")
|
||||
subId := subResp.Subnet.Id
|
||||
defer s.deleteSubnets(c, []string{subId})
|
||||
|
||||
sg := s.makeTestGroupVPC(c, vpcId, "vpc-sg-1", "vpc test group1")
|
||||
defer s.deleteGroups(c, []ec2.SecurityGroup{sg})
|
||||
|
||||
instList, err := s.ec2.RunInstances(&ec2.RunInstances{
|
||||
ImageId: imageId,
|
||||
InstanceType: "t1.micro",
|
||||
SubnetId: subId,
|
||||
})
|
||||
c.Assert(err, IsNil)
|
||||
inst := instList.Instances[0]
|
||||
c.Assert(inst, NotNil)
|
||||
instId := inst.InstanceId
|
||||
defer terminateInstances(c, s.ec2, []string{instId})
|
||||
|
||||
ips1 := []ec2.PrivateIP{{Address: "10.3.1.10", IsPrimary: true}}
|
||||
resp1, err := s.ec2.CreateNetworkInterface(ec2.CreateNetworkInterface{
|
||||
SubnetId: subId,
|
||||
PrivateIPs: ips1,
|
||||
Description: "My first iface",
|
||||
})
|
||||
c.Assert(err, IsNil)
|
||||
assertNetworkInterface(c, resp1.NetworkInterface, "", subId, ips1)
|
||||
c.Check(resp1.NetworkInterface.Description, Equals, "My first iface")
|
||||
id1 := resp1.NetworkInterface.Id
|
||||
|
||||
ips2 := []ec2.PrivateIP{
|
||||
{Address: "10.3.1.20", IsPrimary: true},
|
||||
{Address: "10.3.1.22", IsPrimary: false},
|
||||
}
|
||||
resp2, err := s.ec2.CreateNetworkInterface(ec2.CreateNetworkInterface{
|
||||
SubnetId: subId,
|
||||
PrivateIPs: ips2,
|
||||
SecurityGroupIds: []string{sg.Id},
|
||||
})
|
||||
c.Assert(err, IsNil)
|
||||
assertNetworkInterface(c, resp2.NetworkInterface, "", subId, ips2)
|
||||
c.Assert(resp2.NetworkInterface.Groups, DeepEquals, []ec2.SecurityGroup{sg})
|
||||
id2 := resp2.NetworkInterface.Id
|
||||
|
||||
// We only check for the network interfaces we just created,
|
||||
// because the user might have others in his account (when testing
|
||||
// against the EC2 servers). In some cases it takes a short while
|
||||
// until both interfaces are created, so we need to retry a few
|
||||
// times to make sure.
|
||||
testAttempt := aws.AttemptStrategy{
|
||||
Total: 5 * time.Minute,
|
||||
Delay: 5 * time.Second,
|
||||
}
|
||||
var list *ec2.NetworkInterfacesResp
|
||||
done := false
|
||||
for a := testAttempt.Start(); a.Next(); {
|
||||
c.Logf("waiting for %v to be created", []string{id1, id2})
|
||||
list, err = s.ec2.NetworkInterfaces(nil, nil)
|
||||
if err != nil {
|
||||
c.Logf("retrying; NetworkInterfaces returned: %v", err)
|
||||
continue
|
||||
}
|
||||
found := 0
|
||||
for _, iface := range list.Interfaces {
|
||||
c.Logf("found NIC %v", iface)
|
||||
switch iface.Id {
|
||||
case id1:
|
||||
assertNetworkInterface(c, iface, id1, subId, ips1)
|
||||
found++
|
||||
case id2:
|
||||
assertNetworkInterface(c, iface, id2, subId, ips2)
|
||||
found++
|
||||
}
|
||||
if found == 2 {
|
||||
done = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if done {
|
||||
c.Logf("all NICs were created")
|
||||
break
|
||||
}
|
||||
}
|
||||
if !done {
|
||||
c.Fatalf("timeout while waiting for NICs %v", []string{id1, id2})
|
||||
}
|
||||
|
||||
list, err = s.ec2.NetworkInterfaces([]string{id1}, nil)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(list.Interfaces, HasLen, 1)
|
||||
assertNetworkInterface(c, list.Interfaces[0], id1, subId, ips1)
|
||||
|
||||
f := ec2.NewFilter()
|
||||
f.Add("network-interface-id", id2)
|
||||
list, err = s.ec2.NetworkInterfaces(nil, f)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(list.Interfaces, HasLen, 1)
|
||||
assertNetworkInterface(c, list.Interfaces[0], id2, subId, ips2)
|
||||
|
||||
// Attachment might fail if the instance is not running yet,
|
||||
// so we retry for a while until it succeeds.
|
||||
var attResp *ec2.AttachNetworkInterfaceResp
|
||||
for a := testAttempt.Start(); a.Next(); {
|
||||
attResp, err = s.ec2.AttachNetworkInterface(id2, instId, 1)
|
||||
if err != nil {
|
||||
c.Logf("AttachNetworkInterface returned: %v; retrying...", err)
|
||||
attResp = nil
|
||||
continue
|
||||
}
|
||||
c.Logf("AttachNetworkInterface succeeded")
|
||||
c.Check(attResp.AttachmentId, Not(Equals), "")
|
||||
break
|
||||
}
|
||||
if attResp == nil {
|
||||
c.Fatalf("timeout while waiting for AttachNetworkInterface to succeed")
|
||||
}
|
||||
|
||||
list, err = s.ec2.NetworkInterfaces([]string{id2}, nil)
|
||||
c.Assert(err, IsNil)
|
||||
att := list.Interfaces[0].Attachment
|
||||
c.Check(att.Id, Equals, attResp.AttachmentId)
|
||||
c.Check(att.InstanceId, Equals, instId)
|
||||
c.Check(att.DeviceIndex, Equals, 1)
|
||||
c.Check(att.Status, Matches, "(attaching|in-use)")
|
||||
|
||||
_, err = s.ec2.DetachNetworkInterface(att.Id, true)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
_, err = s.ec2.DeleteNetworkInterface(id1)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
// We might not be able to delete the interface until the
|
||||
// detachment is completed, so we need to retry here as well.
|
||||
for a := testAttempt.Start(); a.Next(); {
|
||||
_, err = s.ec2.DeleteNetworkInterface(id2)
|
||||
if err != nil {
|
||||
c.Logf("DeleteNetworkInterface returned: %v; retrying...", err)
|
||||
continue
|
||||
}
|
||||
c.Logf("DeleteNetworkInterface succeeded")
|
||||
return
|
||||
}
|
||||
c.Fatalf("timeout while waiting for DeleteNetworkInterface to succeed")
|
||||
}
|
||||
|
||||
func assertNetworkInterface(c *C, obtained ec2.NetworkInterface, expectId, expectSubId string, expectIPs []ec2.PrivateIP) {
|
||||
if expectId != "" {
|
||||
c.Check(obtained.Id, Equals, expectId)
|
||||
} else {
|
||||
c.Check(obtained.Id, Matches, `^eni-[0-9a-f]+$`)
|
||||
}
|
||||
c.Check(obtained.Status, Matches, "(available|pending|in-use)")
|
||||
if expectSubId != "" {
|
||||
c.Check(obtained.SubnetId, Equals, expectSubId)
|
||||
} else {
|
||||
c.Check(obtained.SubnetId, Matches, `^subnet-[0-9a-f]+$`)
|
||||
}
|
||||
c.Check(obtained.Attachment, DeepEquals, ec2.NetworkInterfaceAttachment{})
|
||||
if len(expectIPs) > 0 {
|
||||
c.Check(obtained.PrivateIPs, DeepEquals, expectIPs)
|
||||
c.Check(obtained.PrivateIPAddress, DeepEquals, expectIPs[0].Address)
|
||||
}
|
||||
}
|
||||
67
vendor/src/launchpad.net/goamz/ec2/privateips.go
vendored
Normal file
67
vendor/src/launchpad.net/goamz/ec2/privateips.go
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// goamz - Go packages to interact with the Amazon Web Services.
|
||||
//
|
||||
// https://wiki.ubuntu.com/goamz
|
||||
//
|
||||
// Copyright (c) 2014 Canonical Ltd.
|
||||
//
|
||||
|
||||
package ec2
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// AssignPrivateIPAddresses assigns secondary IP addresses to the
|
||||
// network interface interfaceId.
|
||||
//
|
||||
// If secondaryIPCount is non-zero, ipAddresses must be nil, and the
|
||||
// specified number of secondary IP addresses will be allocated within
|
||||
// the subnet's CIDR block range.
|
||||
//
|
||||
// allowReassignment specifies whether to allow reassignment of
|
||||
// addresses currently assigned to a different network interface.
|
||||
//
|
||||
// See http://goo.gl/MoeH0L more details.
|
||||
func (ec2 *EC2) AssignPrivateIPAddresses(interfaceId string, ipAddresses []string, secondaryIPCount int, allowReassignment bool) (resp *SimpleResp, err error) {
|
||||
params := makeParamsVPC("AssignPrivateIpAddresses")
|
||||
params["NetworkInterfaceId"] = interfaceId
|
||||
if secondaryIPCount > 0 {
|
||||
params["SecondaryPrivateIpAddressCount"] = strconv.Itoa(secondaryIPCount)
|
||||
} else {
|
||||
for i, ip := range ipAddresses {
|
||||
// PrivateIpAddress is zero indexed.
|
||||
n := strconv.Itoa(i)
|
||||
params["PrivateIpAddress."+n] = ip
|
||||
}
|
||||
}
|
||||
if allowReassignment {
|
||||
params["AllowReassignment"] = "true"
|
||||
}
|
||||
resp = &SimpleResp{}
|
||||
err = ec2.query(params, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// UnassignPrivateIPAddresses unassigns one or more secondary private
|
||||
// IP addresses from a network interface.
|
||||
//
|
||||
// See http://goo.gl/RjGZdB for more details.
|
||||
func (ec2 *EC2) UnassignPrivateIPAddresses(interfaceId string, ipAddresses []string) (resp *SimpleResp, err error) {
|
||||
params := makeParamsVPC("UnassignPrivateIpAddresses")
|
||||
params["NetworkInterfaceId"] = interfaceId
|
||||
for i, ip := range ipAddresses {
|
||||
// PrivateIpAddress is zero indexed.
|
||||
n := strconv.Itoa(i)
|
||||
params["PrivateIpAddress."+n] = ip
|
||||
}
|
||||
resp = &SimpleResp{}
|
||||
err = ec2.query(params, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
177
vendor/src/launchpad.net/goamz/ec2/privateips_test.go
vendored
Normal file
177
vendor/src/launchpad.net/goamz/ec2/privateips_test.go
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
//
|
||||
// goamz - Go packages to interact with the Amazon Web Services.
|
||||
//
|
||||
// https://wiki.ubuntu.com/goamz
|
||||
//
|
||||
// Copyright (c) 2014 Canonical Ltd.
|
||||
//
|
||||
|
||||
package ec2_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"launchpad.net/goamz/aws"
|
||||
"launchpad.net/goamz/ec2"
|
||||
. "launchpad.net/gocheck"
|
||||
)
|
||||
|
||||
// Private IP tests with example responses
|
||||
|
||||
func (s *S) TestAssignPrivateIPAddressesExample(c *C) {
|
||||
testServer.Response(200, nil, AssignPrivateIpAddressesExample)
|
||||
|
||||
resp, err := s.ec2.AssignPrivateIPAddresses("eni-id", []string{"1.2.3.4", "4.3.2.1"}, 0, true)
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"AssignPrivateIpAddresses"})
|
||||
c.Assert(req.Form["NetworkInterfaceId"], DeepEquals, []string{"eni-id"})
|
||||
c.Assert(req.Form["PrivateIpAddress.0"], DeepEquals, []string{"1.2.3.4"})
|
||||
c.Assert(req.Form["PrivateIpAddress.1"], DeepEquals, []string{"4.3.2.1"})
|
||||
c.Assert(req.Form["SecondaryPrivateIpAddressCount"], HasLen, 0)
|
||||
c.Assert(req.Form["AllowReassignment"], DeepEquals, []string{"true"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
|
||||
}
|
||||
|
||||
func (s *S) TestUnassignPrivateIPAddressesExample(c *C) {
|
||||
testServer.Response(200, nil, UnassignPrivateIpAddressesExample)
|
||||
|
||||
resp, err := s.ec2.UnassignPrivateIPAddresses("eni-id", []string{"1.2.3.4", "4.3.2.1"})
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"UnassignPrivateIpAddresses"})
|
||||
c.Assert(req.Form["NetworkInterfaceId"], DeepEquals, []string{"eni-id"})
|
||||
c.Assert(req.Form["PrivateIpAddress.0"], DeepEquals, []string{"1.2.3.4"})
|
||||
c.Assert(req.Form["PrivateIpAddress.1"], DeepEquals, []string{"4.3.2.1"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE")
|
||||
}
|
||||
|
||||
// Private IP tests run live against EC2.
|
||||
|
||||
func (s *ServerTests) TestAssignUnassignPrivateIPs(c *C) {
|
||||
vpcResp, err := s.ec2.CreateVPC("10.7.0.0/16", "")
|
||||
c.Assert(err, IsNil)
|
||||
vpcId := vpcResp.VPC.Id
|
||||
defer s.deleteVPCs(c, []string{vpcId})
|
||||
|
||||
subResp := s.createSubnet(c, vpcId, "10.7.1.0/24", "")
|
||||
subId := subResp.Subnet.Id
|
||||
defer s.deleteSubnets(c, []string{subId})
|
||||
|
||||
// Launch a m1.medium instance, so we can later assign up to 6
|
||||
// private IPs per NIC.
|
||||
instList, err := s.ec2.RunInstances(&ec2.RunInstances{
|
||||
ImageId: imageId,
|
||||
InstanceType: "m1.medium",
|
||||
SubnetId: subId,
|
||||
})
|
||||
c.Assert(err, IsNil)
|
||||
inst := instList.Instances[0]
|
||||
c.Assert(inst, NotNil)
|
||||
instId := inst.InstanceId
|
||||
defer terminateInstances(c, s.ec2, []string{instId})
|
||||
|
||||
// We need to wait for the instance to change state to 'running',
|
||||
// so its automatically created network interface on the created
|
||||
// subnet will appear.
|
||||
testAttempt := aws.AttemptStrategy{
|
||||
Total: 5 * time.Minute,
|
||||
Delay: 5 * time.Second,
|
||||
}
|
||||
var newNIC *ec2.NetworkInterface
|
||||
f := ec2.NewFilter()
|
||||
f.Add("subnet-id", subId)
|
||||
for a := testAttempt.Start(); a.Next(); {
|
||||
resp, err := s.ec2.NetworkInterfaces(nil, f)
|
||||
if err != nil {
|
||||
c.Logf("NetworkInterfaces returned: %v; retrying...", err)
|
||||
continue
|
||||
}
|
||||
for _, iface := range resp.Interfaces {
|
||||
c.Logf("found NIC %v", iface)
|
||||
if iface.Attachment.InstanceId == instId {
|
||||
c.Logf("found instance %v NIC", instId)
|
||||
newNIC = &iface
|
||||
break
|
||||
}
|
||||
}
|
||||
if newNIC != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if newNIC == nil {
|
||||
c.Fatalf("timeout while waiting for the NIC to appear")
|
||||
}
|
||||
|
||||
c.Check(newNIC.PrivateIPAddress, Matches, `^10\.7\.1\.\d+$`)
|
||||
c.Check(newNIC.PrivateIPs, HasLen, 1)
|
||||
|
||||
// Now let's try assigning some more private IPs.
|
||||
ips := []string{"10.7.1.25", "10.7.1.30"}
|
||||
_, err = s.ec2.AssignPrivateIPAddresses(newNIC.Id, ips, 0, false)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
expectIPs := append([]string{newNIC.PrivateIPAddress}, ips...)
|
||||
s.waitForAddresses(c, newNIC.Id, expectIPs, false)
|
||||
|
||||
// Try using SecondaryPrivateIPCount.
|
||||
_, err = s.ec2.AssignPrivateIPAddresses(newNIC.Id, nil, 2, false)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
expectIPs = append(expectIPs, []string{"10.7.1.*", "10.7.1.*"}...)
|
||||
ips = s.waitForAddresses(c, newNIC.Id, expectIPs, true)
|
||||
|
||||
// And finally, unassign them all, except the primary.
|
||||
_, err = s.ec2.UnassignPrivateIPAddresses(newNIC.Id, ips)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
expectIPs = []string{newNIC.PrivateIPAddress}
|
||||
s.waitForAddresses(c, newNIC.Id, expectIPs, false)
|
||||
}
|
||||
|
||||
func (s *ServerTests) waitForAddresses(c *C, nicId string, ips []string, skipPrimary bool) []string {
|
||||
// Wait for the given IPs to appear on the NIC, retrying if needed.
|
||||
testAttempt := aws.AttemptStrategy{
|
||||
Total: 5 * time.Minute,
|
||||
Delay: 5 * time.Second,
|
||||
}
|
||||
for a := testAttempt.Start(); a.Next(); {
|
||||
c.Logf("waiting for %v IPs on NIC %v", ips, nicId)
|
||||
resp, err := s.ec2.NetworkInterfaces([]string{nicId}, nil)
|
||||
if err != nil {
|
||||
c.Logf("NetworkInterfaces returned: %v; retrying...", err)
|
||||
continue
|
||||
}
|
||||
if len(resp.Interfaces) != 1 {
|
||||
c.Logf("found %d NICs; retrying", len(resp.Interfaces))
|
||||
continue
|
||||
}
|
||||
iface := resp.Interfaces[0]
|
||||
if len(iface.PrivateIPs) != len(ips) {
|
||||
c.Logf("addresses in %v: %v; still waiting", iface.Id, iface.PrivateIPs)
|
||||
continue
|
||||
}
|
||||
|
||||
var foundIPs []string
|
||||
for i, ip := range iface.PrivateIPs {
|
||||
if strings.HasSuffix(ips[i], ".*") {
|
||||
c.Check(ip.Address, Matches, ips[i])
|
||||
} else {
|
||||
c.Check(ip.Address, Equals, ips[i])
|
||||
}
|
||||
if skipPrimary && ip.IsPrimary {
|
||||
continue
|
||||
}
|
||||
foundIPs = append(foundIPs, ip.Address)
|
||||
}
|
||||
c.Logf("all addresses updated")
|
||||
return foundIPs
|
||||
}
|
||||
c.Fatalf("timeout while waiting for the IPs to get updated")
|
||||
return nil
|
||||
}
|
||||
1030
vendor/src/launchpad.net/goamz/ec2/responses_test.go
vendored
Normal file
1030
vendor/src/launchpad.net/goamz/ec2/responses_test.go
vendored
Normal file
File diff suppressed because it is too large
Load Diff
42
vendor/src/launchpad.net/goamz/ec2/sign.go
vendored
Normal file
42
vendor/src/launchpad.net/goamz/ec2/sign.go
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
package ec2
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"launchpad.net/goamz/aws"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// EC2 signing (http://goo.gl/fQmAN)
|
||||
|
||||
var b64 = base64.StdEncoding
|
||||
|
||||
func sign(auth aws.Auth, method, path string, params map[string]string, host string) {
|
||||
params["AWSAccessKeyId"] = auth.AccessKey
|
||||
params["SignatureVersion"] = "2"
|
||||
params["SignatureMethod"] = "HmacSHA256"
|
||||
|
||||
// AWS specifies that the parameters in a signed request must
|
||||
// be provided in the natural order of the keys. This is distinct
|
||||
// from the natural order of the encoded value of key=value.
|
||||
// Percent and equals affect the sorting order.
|
||||
var keys, sarray []string
|
||||
for k, _ := range params {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
sarray = append(sarray, aws.Encode(k)+"="+aws.Encode(params[k]))
|
||||
}
|
||||
joined := strings.Join(sarray, "&")
|
||||
payload := method + "\n" + host + "\n" + path + "\n" + joined
|
||||
hash := hmac.New(sha256.New, []byte(auth.SecretKey))
|
||||
hash.Write([]byte(payload))
|
||||
signature := make([]byte, b64.EncodedLen(hash.Size()))
|
||||
b64.Encode(signature, hash.Sum(nil))
|
||||
|
||||
params["Signature"] = string(signature)
|
||||
}
|
||||
68
vendor/src/launchpad.net/goamz/ec2/sign_test.go
vendored
Normal file
68
vendor/src/launchpad.net/goamz/ec2/sign_test.go
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
package ec2_test
|
||||
|
||||
import (
|
||||
"launchpad.net/goamz/aws"
|
||||
"launchpad.net/goamz/ec2"
|
||||
. "launchpad.net/gocheck"
|
||||
)
|
||||
|
||||
// EC2 ReST authentication docs: http://goo.gl/fQmAN
|
||||
|
||||
var testAuth = aws.Auth{"user", "secret"}
|
||||
|
||||
func (s *S) TestBasicSignature(c *C) {
|
||||
params := map[string]string{}
|
||||
ec2.Sign(testAuth, "GET", "/path", params, "localhost")
|
||||
c.Assert(params["SignatureVersion"], Equals, "2")
|
||||
c.Assert(params["SignatureMethod"], Equals, "HmacSHA256")
|
||||
expected := "6lSe5QyXum0jMVc7cOUz32/52ZnL7N5RyKRk/09yiK4="
|
||||
c.Assert(params["Signature"], Equals, expected)
|
||||
}
|
||||
|
||||
func (s *S) TestParamSignature(c *C) {
|
||||
params := map[string]string{
|
||||
"param1": "value1",
|
||||
"param2": "value2",
|
||||
"param3": "value3",
|
||||
}
|
||||
ec2.Sign(testAuth, "GET", "/path", params, "localhost")
|
||||
expected := "XWOR4+0lmK8bD8CGDGZ4kfuSPbb2JibLJiCl/OPu1oU="
|
||||
c.Assert(params["Signature"], Equals, expected)
|
||||
}
|
||||
|
||||
func (s *S) TestManyParams(c *C) {
|
||||
params := map[string]string{
|
||||
"param1": "value10",
|
||||
"param2": "value2",
|
||||
"param3": "value3",
|
||||
"param4": "value4",
|
||||
"param5": "value5",
|
||||
"param6": "value6",
|
||||
"param7": "value7",
|
||||
"param8": "value8",
|
||||
"param9": "value9",
|
||||
"param10": "value1",
|
||||
}
|
||||
ec2.Sign(testAuth, "GET", "/path", params, "localhost")
|
||||
expected := "di0sjxIvezUgQ1SIL6i+C/H8lL+U0CQ9frLIak8jkVg="
|
||||
c.Assert(params["Signature"], Equals, expected)
|
||||
}
|
||||
|
||||
func (s *S) TestEscaping(c *C) {
|
||||
params := map[string]string{"Nonce": "+ +"}
|
||||
ec2.Sign(testAuth, "GET", "/path", params, "localhost")
|
||||
c.Assert(params["Nonce"], Equals, "+ +")
|
||||
expected := "bqffDELReIqwjg/W0DnsnVUmfLK4wXVLO4/LuG+1VFA="
|
||||
c.Assert(params["Signature"], Equals, expected)
|
||||
}
|
||||
|
||||
func (s *S) TestSignatureExample1(c *C) {
|
||||
params := map[string]string{
|
||||
"Timestamp": "2009-02-01T12:53:20+00:00",
|
||||
"Version": "2007-11-07",
|
||||
"Action": "ListDomains",
|
||||
}
|
||||
ec2.Sign(aws.Auth{"access", "secret"}, "GET", "/", params, "sdb.amazonaws.com")
|
||||
expected := "okj96/5ucWBSc1uR2zXVfm6mDHtgfNv657rRtt/aunQ="
|
||||
c.Assert(params["Signature"], Equals, expected)
|
||||
}
|
||||
109
vendor/src/launchpad.net/goamz/ec2/subnets.go
vendored
Normal file
109
vendor/src/launchpad.net/goamz/ec2/subnets.go
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
//
|
||||
// goamz - Go packages to interact with the Amazon Web Services.
|
||||
//
|
||||
// https://wiki.ubuntu.com/goamz
|
||||
//
|
||||
// Copyright (c) 2014 Canonical Ltd.
|
||||
//
|
||||
|
||||
package ec2
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Subnet describes an Amazon VPC subnet.
|
||||
//
|
||||
// See http://goo.gl/CdkvO2 for more details.
|
||||
type Subnet struct {
|
||||
Id string `xml:"subnetId"`
|
||||
State string `xml:"state"`
|
||||
VPCId string `xml:"vpcId"`
|
||||
CIDRBlock string `xml:"cidrBlock"`
|
||||
AvailableIPCount int `xml:"availableIpAddressCount"`
|
||||
AvailZone string `xml:"availabilityZone"`
|
||||
DefaultForAZ bool `xml:"defaultForAz"`
|
||||
MapPublicIPOnLaunch bool `xml:"mapPublicIpOnLaunch"`
|
||||
Tags []Tag `xml:"tagSet>item"`
|
||||
}
|
||||
|
||||
// CreateSubnetResp is the response to a CreateSubnet request.
|
||||
//
|
||||
// See http://goo.gl/wLPhfI for more details.
|
||||
type CreateSubnetResp struct {
|
||||
RequestId string `xml:"requestId"`
|
||||
Subnet Subnet `xml:"subnet"`
|
||||
}
|
||||
|
||||
// CreateSubnet creates a subnet in an existing VPC.
|
||||
//
|
||||
// The vpcId and cidrBlock parameters specify the VPC id and CIDR
|
||||
// block respectively - these cannot be changed after creation. The
|
||||
// subnet's CIDR block can be the same as the VPC's CIDR block
|
||||
// (assuming a single subnet is wanted), or a subset of the VPC's CIDR
|
||||
// block. If more than one subnet is created in a VPC, their CIDR
|
||||
// blocks must not overlap. The smallest subnet (and VPC) that can be
|
||||
// created uses a /28 netmask (16 IP addresses), and the largest uses
|
||||
// a /16 netmask (65,536 IP addresses).
|
||||
//
|
||||
// availZone may be empty, an availability zone is automatically
|
||||
// selected.
|
||||
//
|
||||
// See http://goo.gl/wLPhfI for more details.
|
||||
func (ec2 *EC2) CreateSubnet(vpcId, cidrBlock, availZone string) (resp *CreateSubnetResp, err error) {
|
||||
params := makeParamsVPC("CreateSubnet")
|
||||
params["VpcId"] = vpcId
|
||||
params["CidrBlock"] = cidrBlock
|
||||
if availZone != "" {
|
||||
params["AvailabilityZone"] = availZone
|
||||
}
|
||||
resp = &CreateSubnetResp{}
|
||||
err = ec2.query(params, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// DeleteSubnet deletes the specified subnet. All running instances in
|
||||
// the subnet must have been terminated.
|
||||
//
|
||||
// See http://goo.gl/KmhcBM for more details.
|
||||
func (ec2 *EC2) DeleteSubnet(id string) (resp *SimpleResp, err error) {
|
||||
params := makeParamsVPC("DeleteSubnet")
|
||||
params["SubnetId"] = id
|
||||
resp = &SimpleResp{}
|
||||
err = ec2.query(params, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// SubnetsResp is the response to a Subnets request.
|
||||
//
|
||||
// See http://goo.gl/NTKQVI for more details.
|
||||
type SubnetsResp struct {
|
||||
RequestId string `xml:"requestId"`
|
||||
Subnets []Subnet `xml:"subnetSet>item"`
|
||||
}
|
||||
|
||||
// Subnets returns one or more subnets. Both parameters are optional,
|
||||
// and if specified will limit the returned subnets to the matching
|
||||
// ids or filtering rules.
|
||||
//
|
||||
// See http://goo.gl/NTKQVI for more details.
|
||||
func (ec2 *EC2) Subnets(ids []string, filter *Filter) (resp *SubnetsResp, err error) {
|
||||
params := makeParamsVPC("DescribeSubnets")
|
||||
for i, id := range ids {
|
||||
params["SubnetId."+strconv.Itoa(i+1)] = id
|
||||
}
|
||||
filter.addParams(params)
|
||||
|
||||
resp = &SubnetsResp{}
|
||||
err = ec2.query(params, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
247
vendor/src/launchpad.net/goamz/ec2/subnets_test.go
vendored
Normal file
247
vendor/src/launchpad.net/goamz/ec2/subnets_test.go
vendored
Normal file
@@ -0,0 +1,247 @@
|
||||
//
|
||||
// goamz - Go packages to interact with the Amazon Web Services.
|
||||
//
|
||||
// https://wiki.ubuntu.com/goamz
|
||||
//
|
||||
// Copyright (c) 2014 Canonical Ltd.
|
||||
//
|
||||
|
||||
package ec2_test
|
||||
|
||||
import (
|
||||
"launchpad.net/goamz/aws"
|
||||
"launchpad.net/goamz/ec2"
|
||||
. "launchpad.net/gocheck"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Subnet tests with example responses
|
||||
|
||||
func (s *S) TestCreateSubnetExample(c *C) {
|
||||
testServer.Response(200, nil, CreateSubnetExample)
|
||||
|
||||
resp, err := s.ec2.CreateSubnet("vpc-1a2b3c4d", "10.0.1.0/24", "us-east-1a")
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"CreateSubnet"})
|
||||
c.Assert(req.Form["VpcId"], DeepEquals, []string{"vpc-1a2b3c4d"})
|
||||
c.Assert(req.Form["CidrBlock"], DeepEquals, []string{"10.0.1.0/24"})
|
||||
c.Assert(req.Form["AvailabilityZone"], DeepEquals, []string{"us-east-1a"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE")
|
||||
subnet := resp.Subnet
|
||||
c.Check(subnet.Id, Equals, "subnet-9d4a7b6c")
|
||||
c.Check(subnet.State, Equals, "pending")
|
||||
c.Check(subnet.VPCId, Equals, "vpc-1a2b3c4d")
|
||||
c.Check(subnet.CIDRBlock, Equals, "10.0.1.0/24")
|
||||
c.Check(subnet.AvailableIPCount, Equals, 251)
|
||||
c.Check(subnet.AvailZone, Equals, "us-east-1a")
|
||||
c.Check(subnet.Tags, HasLen, 0)
|
||||
}
|
||||
|
||||
func (s *S) TestDeleteSubnetExample(c *C) {
|
||||
testServer.Response(200, nil, DeleteSubnetExample)
|
||||
|
||||
resp, err := s.ec2.DeleteSubnet("subnet-id")
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"DeleteSubnet"})
|
||||
c.Assert(req.Form["SubnetId"], DeepEquals, []string{"subnet-id"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE")
|
||||
}
|
||||
|
||||
func (s *S) TestSubnetsExample(c *C) {
|
||||
testServer.Response(200, nil, DescribeSubnetsExample)
|
||||
|
||||
ids := []string{"subnet-9d4a7b6c", "subnet-6e7f829e"}
|
||||
resp, err := s.ec2.Subnets(ids, nil)
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeSubnets"})
|
||||
c.Assert(req.Form["SubnetId.1"], DeepEquals, []string{ids[0]})
|
||||
c.Assert(req.Form["SubnetId.2"], DeepEquals, []string{ids[1]})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE")
|
||||
c.Check(resp.Subnets, HasLen, 2)
|
||||
subnet := resp.Subnets[0]
|
||||
c.Check(subnet.Id, Equals, "subnet-9d4a7b6c")
|
||||
c.Check(subnet.State, Equals, "available")
|
||||
c.Check(subnet.VPCId, Equals, "vpc-1a2b3c4d")
|
||||
c.Check(subnet.CIDRBlock, Equals, "10.0.1.0/24")
|
||||
c.Check(subnet.AvailableIPCount, Equals, 251)
|
||||
c.Check(subnet.AvailZone, Equals, "us-east-1a")
|
||||
c.Check(subnet.DefaultForAZ, Equals, false)
|
||||
c.Check(subnet.MapPublicIPOnLaunch, Equals, false)
|
||||
c.Check(subnet.Tags, HasLen, 0)
|
||||
subnet = resp.Subnets[1]
|
||||
c.Check(subnet.Id, Equals, "subnet-6e7f829e")
|
||||
c.Check(subnet.State, Equals, "available")
|
||||
c.Check(subnet.VPCId, Equals, "vpc-1a2b3c4d")
|
||||
c.Check(subnet.CIDRBlock, Equals, "10.0.0.0/24")
|
||||
c.Check(subnet.AvailableIPCount, Equals, 251)
|
||||
c.Check(subnet.AvailZone, Equals, "us-east-1a")
|
||||
c.Check(subnet.DefaultForAZ, Equals, false)
|
||||
c.Check(subnet.MapPublicIPOnLaunch, Equals, false)
|
||||
c.Check(subnet.Tags, HasLen, 0)
|
||||
}
|
||||
|
||||
// Subnet tests run against either a local test server or live on EC2.
|
||||
|
||||
func (s *ServerTests) TestSubnets(c *C) {
|
||||
resp, err := s.ec2.CreateVPC("10.2.0.0/16", "")
|
||||
c.Assert(err, IsNil)
|
||||
vpcId := resp.VPC.Id
|
||||
defer s.deleteVPCs(c, []string{vpcId})
|
||||
|
||||
resp1 := s.createSubnet(c, vpcId, "10.2.1.0/24", "")
|
||||
assertSubnet(c, resp1.Subnet, "", vpcId, "10.2.1.0/24")
|
||||
id1 := resp1.Subnet.Id
|
||||
|
||||
resp2, err := s.ec2.CreateSubnet(vpcId, "10.2.2.0/24", "")
|
||||
c.Assert(err, IsNil)
|
||||
assertSubnet(c, resp2.Subnet, "", vpcId, "10.2.2.0/24")
|
||||
id2 := resp2.Subnet.Id
|
||||
|
||||
// We only check for the subnets we just created, because the user
|
||||
// might have others in his account (when testing against the EC2
|
||||
// servers). In some cases it takes a short while until both
|
||||
// subnets are created, so we need to retry a few times to make
|
||||
// sure.
|
||||
testAttempt := aws.AttemptStrategy{
|
||||
Total: 2 * time.Minute,
|
||||
Delay: 5 * time.Second,
|
||||
}
|
||||
var list *ec2.SubnetsResp
|
||||
done := false
|
||||
for a := testAttempt.Start(); a.Next(); {
|
||||
c.Logf("waiting for %v to be created", []string{id1, id2})
|
||||
list, err = s.ec2.Subnets(nil, nil)
|
||||
if err != nil {
|
||||
c.Logf("retrying; Subnets returned: %v", err)
|
||||
continue
|
||||
}
|
||||
found := 0
|
||||
for _, subnet := range list.Subnets {
|
||||
c.Logf("found subnet %v", subnet)
|
||||
switch subnet.Id {
|
||||
case id1:
|
||||
assertSubnet(c, subnet, id1, vpcId, resp1.Subnet.CIDRBlock)
|
||||
found++
|
||||
case id2:
|
||||
assertSubnet(c, subnet, id2, vpcId, resp2.Subnet.CIDRBlock)
|
||||
found++
|
||||
}
|
||||
if found == 2 {
|
||||
done = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if done {
|
||||
c.Logf("all subnets were created")
|
||||
break
|
||||
}
|
||||
}
|
||||
if !done {
|
||||
c.Fatalf("timeout while waiting for subnets %v", []string{id1, id2})
|
||||
}
|
||||
|
||||
list, err = s.ec2.Subnets([]string{id1}, nil)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(list.Subnets, HasLen, 1)
|
||||
assertSubnet(c, list.Subnets[0], id1, vpcId, resp1.Subnet.CIDRBlock)
|
||||
|
||||
f := ec2.NewFilter()
|
||||
f.Add("cidr", resp2.Subnet.CIDRBlock)
|
||||
list, err = s.ec2.Subnets(nil, f)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(list.Subnets, HasLen, 1)
|
||||
assertSubnet(c, list.Subnets[0], id2, vpcId, resp2.Subnet.CIDRBlock)
|
||||
|
||||
_, err = s.ec2.DeleteSubnet(id1)
|
||||
c.Assert(err, IsNil)
|
||||
_, err = s.ec2.DeleteSubnet(id2)
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
// createSubnet ensures a subnet with the given vpcId and cidrBlock
|
||||
// gets created, retrying a few times with a timeout. This needs to be
|
||||
// done when testing against EC2 servers, because if the VPC was just
|
||||
// created it might take some time for it to show up, so the subnet
|
||||
// can be created.
|
||||
func (s *ServerTests) createSubnet(c *C, vpcId, cidrBlock, availZone string) *ec2.CreateSubnetResp {
|
||||
testAttempt := aws.AttemptStrategy{
|
||||
Total: 2 * time.Minute,
|
||||
Delay: 5 * time.Second,
|
||||
}
|
||||
for a := testAttempt.Start(); a.Next(); {
|
||||
resp, err := s.ec2.CreateSubnet(vpcId, cidrBlock, availZone)
|
||||
if errorCode(err) == "InvalidVpcID.NotFound" {
|
||||
c.Logf("VPC %v not created yet; retrying", vpcId)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
c.Logf("retrying; CreateSubnet returned: %v", err)
|
||||
continue
|
||||
}
|
||||
return resp
|
||||
}
|
||||
c.Fatalf("timeout while waiting for VPC and subnet")
|
||||
return nil
|
||||
}
|
||||
|
||||
// deleteSubnets ensures the given subnets are deleted, by retrying
|
||||
// until a timeout or all subnets cannot be found anymore. This
|
||||
// should be used to make sure tests leave no subnets around.
|
||||
func (s *ServerTests) deleteSubnets(c *C, ids []string) {
|
||||
testAttempt := aws.AttemptStrategy{
|
||||
Total: 2 * time.Minute,
|
||||
Delay: 5 * time.Second,
|
||||
}
|
||||
for a := testAttempt.Start(); a.Next(); {
|
||||
deleted := 0
|
||||
c.Logf("deleting subnets %v", ids)
|
||||
for _, id := range ids {
|
||||
_, err := s.ec2.DeleteSubnet(id)
|
||||
if err == nil || errorCode(err) == "InvalidSubnetID.NotFound" {
|
||||
c.Logf("subnet %s deleted", id)
|
||||
deleted++
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
c.Logf("retrying; DeleteSubnet returned: %v", err)
|
||||
}
|
||||
}
|
||||
if deleted == len(ids) {
|
||||
c.Logf("all subnets deleted")
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Fatalf("timeout while waiting %v subnets to get deleted!", ids)
|
||||
}
|
||||
|
||||
func assertSubnet(c *C, obtained ec2.Subnet, expectId, expectVpcId, expectCidr string) {
|
||||
if expectId != "" {
|
||||
c.Check(obtained.Id, Equals, expectId)
|
||||
} else {
|
||||
c.Check(obtained.Id, Matches, `^subnet-[0-9a-f]+$`)
|
||||
}
|
||||
c.Check(obtained.State, Matches, "(available|pending)")
|
||||
if expectVpcId != "" {
|
||||
c.Check(obtained.VPCId, Equals, expectVpcId)
|
||||
} else {
|
||||
c.Check(obtained.VPCId, Matches, `^vpc-[0-9a-f]+$`)
|
||||
}
|
||||
if expectCidr != "" {
|
||||
c.Check(obtained.CIDRBlock, Equals, expectCidr)
|
||||
} else {
|
||||
c.Check(obtained.CIDRBlock, Matches, `^\d+\.\d+\.\d+\.\d+/\d+$`)
|
||||
}
|
||||
c.Check(obtained.AvailZone, Not(Equals), "")
|
||||
c.Check(obtained.AvailableIPCount, Not(Equals), 0)
|
||||
c.Check(obtained.DefaultForAZ, Equals, false)
|
||||
c.Check(obtained.MapPublicIPOnLaunch, Equals, false)
|
||||
}
|
||||
105
vendor/src/launchpad.net/goamz/ec2/vpc.go
vendored
Normal file
105
vendor/src/launchpad.net/goamz/ec2/vpc.go
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
//
|
||||
// goamz - Go packages to interact with the Amazon Web Services.
|
||||
//
|
||||
// https://wiki.ubuntu.com/goamz
|
||||
//
|
||||
// Copyright (c) 2014 Canonical Ltd.
|
||||
//
|
||||
|
||||
package ec2
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// VPC describes a Virtual Private Cloud (VPC).
|
||||
//
|
||||
// See http://goo.gl/Uy6ZLL for more details.
|
||||
type VPC struct {
|
||||
Id string `xml:"vpcId"`
|
||||
State string `xml:"state"`
|
||||
CIDRBlock string `xml:"cidrBlock"`
|
||||
DHCPOptionsId string `xml:"dhcpOptionsId"`
|
||||
Tags []Tag `xml:"tagSet>item"`
|
||||
InstanceTenancy string `xml:"instanceTenancy"`
|
||||
IsDefault bool `xml:"isDefault"`
|
||||
}
|
||||
|
||||
// CreateVPCResp is the response to a CreateVPC request.
|
||||
//
|
||||
// See http://goo.gl/nkwjvN for more details.
|
||||
type CreateVPCResp struct {
|
||||
RequestId string `xml:"requestId"`
|
||||
VPC VPC `xml:"vpc"`
|
||||
}
|
||||
|
||||
// CreateVPC creates a VPC with the specified CIDR block.
|
||||
//
|
||||
// The smallest VPC that can be created uses a /28 netmask (16 IP
|
||||
// addresses), and the largest uses a /16 netmask (65,536 IP
|
||||
// addresses).
|
||||
//
|
||||
// The instanceTenancy value holds the tenancy options for instances
|
||||
// launched into the VPC - either "default" or "dedicated".
|
||||
//
|
||||
// See http://goo.gl/nkwjvN for more details.
|
||||
func (ec2 *EC2) CreateVPC(CIDRBlock, instanceTenancy string) (resp *CreateVPCResp, err error) {
|
||||
params := makeParamsVPC("CreateVpc")
|
||||
params["CidrBlock"] = CIDRBlock
|
||||
if instanceTenancy == "" {
|
||||
instanceTenancy = "default"
|
||||
}
|
||||
params["InstanceTenancy"] = instanceTenancy
|
||||
resp = &CreateVPCResp{}
|
||||
err = ec2.query(params, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// DeleteVPC deletes the VPC with the specified id. All gateways and
|
||||
// resources that are associated with the VPC must have been
|
||||
// previously deleted, including instances running in the VPC, and
|
||||
// non-default security groups and route tables associated with the
|
||||
// VPC.
|
||||
//
|
||||
// See http://goo.gl/bcxtbf for more details.
|
||||
func (ec2 *EC2) DeleteVPC(id string) (resp *SimpleResp, err error) {
|
||||
params := makeParamsVPC("DeleteVpc")
|
||||
params["VpcId"] = id
|
||||
resp = &SimpleResp{}
|
||||
err = ec2.query(params, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// VPCsResp is the response to a VPCs request.
|
||||
//
|
||||
// See http://goo.gl/Y5kHqG for more details.
|
||||
type VPCsResp struct {
|
||||
RequestId string `xml:"requestId"`
|
||||
VPCs []VPC `xml:"vpcSet>item"`
|
||||
}
|
||||
|
||||
// VPCs describes one or more VPCs. Both parameters are optional, and
|
||||
// if specified will limit the returned VPCs to the matching ids or
|
||||
// filtering rules.
|
||||
//
|
||||
// See http://goo.gl/Y5kHqG for more details.
|
||||
func (ec2 *EC2) VPCs(ids []string, filter *Filter) (resp *VPCsResp, err error) {
|
||||
params := makeParamsVPC("DescribeVpcs")
|
||||
for i, id := range ids {
|
||||
params["VpcId."+strconv.Itoa(i+1)] = id
|
||||
}
|
||||
filter.addParams(params)
|
||||
|
||||
resp = &VPCsResp{}
|
||||
err = ec2.query(params, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
196
vendor/src/launchpad.net/goamz/ec2/vpc_test.go
vendored
Normal file
196
vendor/src/launchpad.net/goamz/ec2/vpc_test.go
vendored
Normal file
@@ -0,0 +1,196 @@
|
||||
//
|
||||
// goamz - Go packages to interact with the Amazon Web Services.
|
||||
//
|
||||
// https://wiki.ubuntu.com/goamz
|
||||
//
|
||||
// Copyright (c) 2014 Canonical Ltd.
|
||||
//
|
||||
|
||||
package ec2_test
|
||||
|
||||
import (
|
||||
"launchpad.net/goamz/aws"
|
||||
"launchpad.net/goamz/ec2"
|
||||
. "launchpad.net/gocheck"
|
||||
"time"
|
||||
)
|
||||
|
||||
// VPC tests with example responses
|
||||
|
||||
func (s *S) TestCreateVPCExample(c *C) {
|
||||
testServer.Response(200, nil, CreateVpcExample)
|
||||
|
||||
resp, err := s.ec2.CreateVPC("10.0.0.0/16", "default")
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"CreateVpc"})
|
||||
c.Assert(req.Form["CidrBlock"], DeepEquals, []string{"10.0.0.0/16"})
|
||||
c.Assert(req.Form["InstanceTenancy"], DeepEquals, []string{"default"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE")
|
||||
vpc := resp.VPC
|
||||
c.Check(vpc.Id, Equals, "vpc-1a2b3c4d")
|
||||
c.Check(vpc.State, Equals, "pending")
|
||||
c.Check(vpc.CIDRBlock, Equals, "10.0.0.0/16")
|
||||
c.Check(vpc.DHCPOptionsId, Equals, "dopt-1a2b3c4d2")
|
||||
c.Check(vpc.Tags, HasLen, 0)
|
||||
c.Check(vpc.IsDefault, Equals, false)
|
||||
c.Check(vpc.InstanceTenancy, Equals, "default")
|
||||
}
|
||||
|
||||
func (s *S) TestDeleteVPCExample(c *C) {
|
||||
testServer.Response(200, nil, DeleteVpcExample)
|
||||
|
||||
resp, err := s.ec2.DeleteVPC("vpc-id")
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"DeleteVpc"})
|
||||
c.Assert(req.Form["VpcId"], DeepEquals, []string{"vpc-id"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE")
|
||||
}
|
||||
|
||||
func (s *S) TestVPCsExample(c *C) {
|
||||
testServer.Response(200, nil, DescribeVpcsExample)
|
||||
|
||||
resp, err := s.ec2.VPCs([]string{"vpc-1a2b3c4d"}, nil)
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeVpcs"})
|
||||
c.Assert(req.Form["VpcId.1"], DeepEquals, []string{"vpc-1a2b3c4d"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE")
|
||||
c.Assert(resp.VPCs, HasLen, 1)
|
||||
vpc := resp.VPCs[0]
|
||||
c.Check(vpc.Id, Equals, "vpc-1a2b3c4d")
|
||||
c.Check(vpc.State, Equals, "available")
|
||||
c.Check(vpc.CIDRBlock, Equals, "10.0.0.0/23")
|
||||
c.Check(vpc.DHCPOptionsId, Equals, "dopt-7a8b9c2d")
|
||||
c.Check(vpc.Tags, HasLen, 0)
|
||||
c.Check(vpc.IsDefault, Equals, false)
|
||||
c.Check(vpc.InstanceTenancy, Equals, "default")
|
||||
}
|
||||
|
||||
// VPC tests to run against either a local test server or live on EC2.
|
||||
|
||||
func (s *ServerTests) TestVPCs(c *C) {
|
||||
resp1, err := s.ec2.CreateVPC("10.0.0.0/16", "")
|
||||
c.Assert(err, IsNil)
|
||||
assertVPC(c, resp1.VPC, "", "10.0.0.0/16")
|
||||
id1 := resp1.VPC.Id
|
||||
|
||||
resp2, err := s.ec2.CreateVPC("10.1.0.0/16", "default")
|
||||
c.Assert(err, IsNil)
|
||||
assertVPC(c, resp2.VPC, "", "10.1.0.0/16")
|
||||
id2 := resp2.VPC.Id
|
||||
|
||||
// We only check for the VPCs we just created, because the user
|
||||
// might have others in his account (when testing against the EC2
|
||||
// servers). In some cases it takes a short while until both VPCs
|
||||
// are created, so we need to retry a few times to make sure.
|
||||
var list *ec2.VPCsResp
|
||||
done := false
|
||||
testAttempt := aws.AttemptStrategy{
|
||||
Total: 2 * time.Minute,
|
||||
Delay: 5 * time.Second,
|
||||
}
|
||||
for a := testAttempt.Start(); a.Next(); {
|
||||
c.Logf("waiting for %v to be created", []string{id1, id2})
|
||||
list, err = s.ec2.VPCs(nil, nil)
|
||||
if err != nil {
|
||||
c.Logf("retrying; VPCs returned: %v", err)
|
||||
continue
|
||||
}
|
||||
found := 0
|
||||
for _, vpc := range list.VPCs {
|
||||
c.Logf("found VPC %v", vpc)
|
||||
switch vpc.Id {
|
||||
case id1:
|
||||
assertVPC(c, vpc, id1, resp1.VPC.CIDRBlock)
|
||||
found++
|
||||
case id2:
|
||||
assertVPC(c, vpc, id2, resp2.VPC.CIDRBlock)
|
||||
found++
|
||||
}
|
||||
if found == 2 {
|
||||
done = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if done {
|
||||
c.Logf("all VPCs were created")
|
||||
break
|
||||
}
|
||||
}
|
||||
if !done {
|
||||
c.Fatalf("timeout while waiting for VPCs %v", []string{id1, id2})
|
||||
}
|
||||
|
||||
list, err = s.ec2.VPCs([]string{id1}, nil)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(list.VPCs, HasLen, 1)
|
||||
assertVPC(c, list.VPCs[0], id1, resp1.VPC.CIDRBlock)
|
||||
|
||||
f := ec2.NewFilter()
|
||||
f.Add("cidr", resp2.VPC.CIDRBlock)
|
||||
list, err = s.ec2.VPCs(nil, f)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(list.VPCs, HasLen, 1)
|
||||
assertVPC(c, list.VPCs[0], id2, resp2.VPC.CIDRBlock)
|
||||
|
||||
_, err = s.ec2.DeleteVPC(id1)
|
||||
c.Assert(err, IsNil)
|
||||
_, err = s.ec2.DeleteVPC(id2)
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
// deleteVPCs ensures the given VPCs are deleted, by retrying until a
|
||||
// timeout or all VPC cannot be found anymore. This should be used to
|
||||
// make sure tests leave no VPCs around.
|
||||
func (s *ServerTests) deleteVPCs(c *C, ids []string) {
|
||||
testAttempt := aws.AttemptStrategy{
|
||||
Total: 2 * time.Minute,
|
||||
Delay: 5 * time.Second,
|
||||
}
|
||||
for a := testAttempt.Start(); a.Next(); {
|
||||
deleted := 0
|
||||
c.Logf("deleting VPCs %v", ids)
|
||||
for _, id := range ids {
|
||||
_, err := s.ec2.DeleteVPC(id)
|
||||
if err == nil || errorCode(err) == "InvalidVpcID.NotFound" {
|
||||
c.Logf("VPC %s deleted", id)
|
||||
deleted++
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
c.Logf("retrying; DeleteVPC returned: %v", err)
|
||||
}
|
||||
}
|
||||
if deleted == len(ids) {
|
||||
c.Logf("all VPCs deleted")
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Fatalf("timeout while waiting %v VPCs to get deleted!", ids)
|
||||
}
|
||||
|
||||
func assertVPC(c *C, obtained ec2.VPC, expectId, expectCidr string) {
|
||||
if expectId != "" {
|
||||
c.Check(obtained.Id, Equals, expectId)
|
||||
} else {
|
||||
c.Check(obtained.Id, Matches, `^vpc-[0-9a-f]+$`)
|
||||
}
|
||||
c.Check(obtained.State, Matches, "(available|pending)")
|
||||
if expectCidr != "" {
|
||||
c.Check(obtained.CIDRBlock, Equals, expectCidr)
|
||||
} else {
|
||||
c.Check(obtained.CIDRBlock, Matches, `^\d+\.\d+\.\d+\.\d+/\d+$`)
|
||||
}
|
||||
c.Check(obtained.DHCPOptionsId, Matches, `^dopt-[0-9a-f]+$`)
|
||||
c.Check(obtained.IsDefault, Equals, false)
|
||||
c.Check(obtained.Tags, HasLen, 0)
|
||||
c.Check(obtained.InstanceTenancy, Matches, "(default|dedicated)")
|
||||
}
|
||||
9
vendor/src/launchpad.net/goamz/exp/mturk/export_test.go
vendored
Normal file
9
vendor/src/launchpad.net/goamz/exp/mturk/export_test.go
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
package mturk
|
||||
|
||||
import (
|
||||
"launchpad.net/goamz/aws"
|
||||
)
|
||||
|
||||
func Sign(auth aws.Auth, service, method, timestamp string, params map[string]string) {
|
||||
sign(auth, service, method, timestamp, params)
|
||||
}
|
||||
281
vendor/src/launchpad.net/goamz/exp/mturk/mturk.go
vendored
Normal file
281
vendor/src/launchpad.net/goamz/exp/mturk/mturk.go
vendored
Normal file
@@ -0,0 +1,281 @@
|
||||
//
|
||||
// goamz - Go packages to interact with the Amazon Web Services.
|
||||
//
|
||||
// https://wiki.ubuntu.com/goamz
|
||||
//
|
||||
// Copyright (c) 2011 Canonical Ltd.
|
||||
//
|
||||
// Written by Graham Miller <graham.miller@gmail.com>
|
||||
|
||||
// This package is in an experimental state, and does not currently
|
||||
// follow conventions and style of the rest of goamz or common
|
||||
// Go conventions. It must be polished before it's considered a
|
||||
// first-class package in goamz.
|
||||
package mturk
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"launchpad.net/goamz/aws"
|
||||
"net/http"
|
||||
//"net/http/httputil"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MTurk struct {
|
||||
aws.Auth
|
||||
URL *url.URL
|
||||
}
|
||||
|
||||
func New(auth aws.Auth) *MTurk {
|
||||
mt := &MTurk{Auth: auth}
|
||||
var err error
|
||||
mt.URL, err = url.Parse("http://mechanicalturk.amazonaws.com/")
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
return mt
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Request dispatching logic.
|
||||
|
||||
// Error encapsulates an error returned by MTurk.
|
||||
type Error struct {
|
||||
StatusCode int // HTTP status code (200, 403, ...)
|
||||
Code string // EC2 error code ("UnsupportedOperation", ...)
|
||||
Message string // The human-oriented error message
|
||||
RequestId string
|
||||
}
|
||||
|
||||
func (err *Error) Error() string {
|
||||
return err.Message
|
||||
}
|
||||
|
||||
// The request stanza included in several response types, for example
|
||||
// in a "CreateHITResponse". http://goo.gl/qGeKf
|
||||
type xmlRequest struct {
|
||||
RequestId string
|
||||
IsValid string
|
||||
Errors []Error `xml:"Errors>Error"`
|
||||
}
|
||||
|
||||
// Common price structure used in requests and responses
|
||||
// http://goo.gl/tE4AV
|
||||
type Price struct {
|
||||
Amount string
|
||||
CurrencyCode string
|
||||
FormattedPrice string
|
||||
}
|
||||
|
||||
// Really just a country string
|
||||
// http://goo.gl/mU4uG
|
||||
type Locale string
|
||||
|
||||
// Data structure used to specify requirements for the worker
|
||||
// used in CreateHIT, for example
|
||||
// http://goo.gl/LvRo9
|
||||
type QualificationRequirement struct {
|
||||
QualificationTypeId string
|
||||
Comparator string
|
||||
IntegerValue int
|
||||
LocaleValue Locale
|
||||
RequiredToPreview string
|
||||
}
|
||||
|
||||
// Data structure holding the contents of an "external"
|
||||
// question. http://goo.gl/NP8Aa
|
||||
type ExternalQuestion struct {
|
||||
XMLName xml.Name `xml:"http://mechanicalturk.amazonaws.com/AWSMechanicalTurkDataSchemas/2006-07-14/ExternalQuestion.xsd ExternalQuestion"`
|
||||
ExternalURL string
|
||||
FrameHeight int
|
||||
}
|
||||
|
||||
// The data structure representing a "human interface task" (HIT)
|
||||
// Currently only supports "external" questions, because Go
|
||||
// structs don't support union types. http://goo.gl/NP8Aa
|
||||
// This type is returned, for example, from SearchHITs
|
||||
// http://goo.gl/PskcX
|
||||
type HIT struct {
|
||||
Request xmlRequest
|
||||
|
||||
HITId string
|
||||
HITTypeId string
|
||||
CreationTime string
|
||||
Title string
|
||||
Description string
|
||||
Keywords string
|
||||
HITStatus string
|
||||
Reward Price
|
||||
LifetimeInSeconds uint
|
||||
AssignmentDurationInSeconds uint
|
||||
MaxAssignments uint
|
||||
AutoApprovalDelayInSeconds uint
|
||||
QualificationRequirement QualificationRequirement
|
||||
Question ExternalQuestion
|
||||
RequesterAnnotation string
|
||||
NumberofSimilarHITs uint
|
||||
HITReviewStatus string
|
||||
NumberOfAssignmentsPending uint
|
||||
NumberOfAssignmentsAvailable uint
|
||||
NumberOfAssignmentsCompleted uint
|
||||
}
|
||||
|
||||
// The main data structure returned by SearchHITs
|
||||
// http://goo.gl/PskcX
|
||||
type SearchHITsResult struct {
|
||||
NumResults uint
|
||||
PageNumber uint
|
||||
TotalNumResults uint
|
||||
HITs []HIT `xml:"HIT"`
|
||||
}
|
||||
|
||||
// The wrapper data structure returned by SearchHITs
|
||||
// http://goo.gl/PskcX
|
||||
type SearchHITsResponse struct {
|
||||
RequestId string `xml:"OperationRequest>RequestId"`
|
||||
SearchHITsResult SearchHITsResult
|
||||
}
|
||||
|
||||
// The wrapper data structure returned by CreateHIT
|
||||
// http://goo.gl/PskcX
|
||||
type CreateHITResponse struct {
|
||||
RequestId string `xml:"OperationRequest>RequestId"`
|
||||
HIT HIT
|
||||
}
|
||||
|
||||
// Corresponds to the "CreateHIT" operation of the Mechanical Turk
|
||||
// API. http://goo.gl/cDBRc Currently only supports "external"
|
||||
// questions (see "HIT" struct above). If "keywords", "maxAssignments",
|
||||
// "qualificationRequirement" or "requesterAnnotation" are the zero
|
||||
// value for their types, they will not be included in the request.
|
||||
func (mt *MTurk) CreateHIT(title, description string, question ExternalQuestion, reward Price, assignmentDurationInSeconds, lifetimeInSeconds uint, keywords string, maxAssignments uint, qualificationRequirement *QualificationRequirement, requesterAnnotation string) (h *HIT, err error) {
|
||||
params := make(map[string]string)
|
||||
params["Title"] = title
|
||||
params["Description"] = description
|
||||
params["Question"], err = xmlEncode(&question)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
params["Reward.1.Amount"] = reward.Amount
|
||||
params["Reward.1.CurrencyCode"] = reward.CurrencyCode
|
||||
params["AssignmentDurationInSeconds"] = strconv.FormatUint(uint64(assignmentDurationInSeconds), 10)
|
||||
|
||||
params["LifetimeInSeconds"] = strconv.FormatUint(uint64(lifetimeInSeconds), 10)
|
||||
if keywords != "" {
|
||||
params["Keywords"] = keywords
|
||||
}
|
||||
if maxAssignments != 0 {
|
||||
params["MaxAssignments"] = strconv.FormatUint(uint64(maxAssignments), 10)
|
||||
}
|
||||
if qualificationRequirement != nil {
|
||||
params["QualificationRequirement"], err = xmlEncode(qualificationRequirement)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if requesterAnnotation != "" {
|
||||
params["RequesterAnnotation"] = requesterAnnotation
|
||||
}
|
||||
|
||||
var response CreateHITResponse
|
||||
err = mt.query(params, "CreateHIT", &response)
|
||||
if err == nil {
|
||||
h = &response.HIT
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Corresponds to the "CreateHIT" operation of the Mechanical Turk
|
||||
// API, using an existing "hit type". http://goo.gl/cDBRc Currently only
|
||||
// supports "external" questions (see "HIT" struct above). If
|
||||
// "maxAssignments" or "requesterAnnotation" are the zero value for
|
||||
// their types, they will not be included in the request.
|
||||
func (mt *MTurk) CreateHITOfType(hitTypeId string, q ExternalQuestion, lifetimeInSeconds uint, maxAssignments uint, requesterAnnotation string) (h *HIT, err error) {
|
||||
params := make(map[string]string)
|
||||
params["HITTypeId"] = hitTypeId
|
||||
params["Question"], err = xmlEncode(&q)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
params["LifetimeInSeconds"] = strconv.FormatUint(uint64(lifetimeInSeconds), 10)
|
||||
if maxAssignments != 0 {
|
||||
params["MaxAssignments"] = strconv.FormatUint(uint64(maxAssignments), 10)
|
||||
}
|
||||
if requesterAnnotation != "" {
|
||||
params["RequesterAnnotation"] = requesterAnnotation
|
||||
}
|
||||
|
||||
var response CreateHITResponse
|
||||
err = mt.query(params, "CreateHIT", &response)
|
||||
if err == nil {
|
||||
h = &response.HIT
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Corresponds to "SearchHITs" operation of Mechanical Turk. http://goo.gl/PskcX
|
||||
// Currenlty supports none of the optional parameters.
|
||||
func (mt *MTurk) SearchHITs() (s *SearchHITsResult, err error) {
|
||||
params := make(map[string]string)
|
||||
var response SearchHITsResponse
|
||||
err = mt.query(params, "SearchHITs", &response)
|
||||
if err == nil {
|
||||
s = &response.SearchHITsResult
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Adds common parameters to the "params" map, signs the request,
|
||||
// adds the signature to the "params" map and sends the request
|
||||
// to the server. It then unmarshals the response in to the "resp"
|
||||
// parameter using xml.Unmarshal()
|
||||
func (mt *MTurk) query(params map[string]string, operation string, resp interface{}) error {
|
||||
service := "AWSMechanicalTurkRequester"
|
||||
timestamp := time.Now().UTC().Format("2006-01-02T15:04:05Z")
|
||||
|
||||
params["AWSAccessKeyId"] = mt.Auth.AccessKey
|
||||
params["Service"] = service
|
||||
params["Timestamp"] = timestamp
|
||||
params["Operation"] = operation
|
||||
|
||||
// make a copy
|
||||
url := *mt.URL
|
||||
|
||||
sign(mt.Auth, service, operation, timestamp, params)
|
||||
url.RawQuery = multimap(params).Encode()
|
||||
r, err := http.Get(url.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//dump, _ := httputil.DumpResponse(r, true)
|
||||
//println("DUMP:\n", string(dump))
|
||||
if r.StatusCode != 200 {
|
||||
return errors.New(fmt.Sprintf("%d: unexpected status code", r.StatusCode))
|
||||
}
|
||||
dec := xml.NewDecoder(r.Body)
|
||||
err = dec.Decode(resp)
|
||||
r.Body.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
func multimap(p map[string]string) url.Values {
|
||||
q := make(url.Values, len(p))
|
||||
for k, v := range p {
|
||||
q[k] = []string{v}
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
func xmlEncode(i interface{}) (s string, err error) {
|
||||
var buf []byte
|
||||
buf, err = xml.Marshal(i)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s = string(buf)
|
||||
return
|
||||
}
|
||||
95
vendor/src/launchpad.net/goamz/exp/mturk/mturk_test.go
vendored
Normal file
95
vendor/src/launchpad.net/goamz/exp/mturk/mturk_test.go
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
package mturk_test
|
||||
|
||||
import (
|
||||
"launchpad.net/goamz/aws"
|
||||
"launchpad.net/goamz/exp/mturk"
|
||||
"launchpad.net/goamz/testutil"
|
||||
. "launchpad.net/gocheck"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
TestingT(t)
|
||||
}
|
||||
|
||||
var _ = Suite(&S{})
|
||||
|
||||
type S struct {
|
||||
mturk *mturk.MTurk
|
||||
}
|
||||
|
||||
var testServer = testutil.NewHTTPServer()
|
||||
|
||||
func (s *S) SetUpSuite(c *C) {
|
||||
testServer.Start()
|
||||
auth := aws.Auth{"abc", "123"}
|
||||
u, err := url.Parse(testServer.URL)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
s.mturk = &mturk.MTurk{
|
||||
Auth: auth,
|
||||
URL: u,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *S) TearDownSuite(c *C) {
|
||||
testServer.Stop()
|
||||
}
|
||||
|
||||
func (s *S) TearDownTest(c *C) {
|
||||
testServer.Flush()
|
||||
}
|
||||
|
||||
func (s *S) TestCreateHIT(c *C) {
|
||||
testServer.Response(200, nil, BasicHitResponse)
|
||||
|
||||
question := mturk.ExternalQuestion{
|
||||
ExternalURL: "http://www.amazon.com",
|
||||
FrameHeight: 200,
|
||||
}
|
||||
reward := mturk.Price{
|
||||
Amount: "0.01",
|
||||
CurrencyCode: "USD",
|
||||
}
|
||||
hit, err := s.mturk.CreateHIT("title", "description", question, reward, 1, 2, "key1,key2", 3, nil, "annotation")
|
||||
|
||||
testServer.WaitRequest()
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(hit, NotNil)
|
||||
|
||||
c.Assert(hit.HITId, Equals, "28J4IXKO2L927XKJTHO34OCDNASCDW")
|
||||
c.Assert(hit.HITTypeId, Equals, "2XZ7D1X3V0FKQVW7LU51S7PKKGFKDF")
|
||||
}
|
||||
|
||||
func (s *S) TestSearchHITs(c *C) {
|
||||
testServer.Response(200, nil, SearchHITResponse)
|
||||
|
||||
hitResult, err := s.mturk.SearchHITs()
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(hitResult, NotNil)
|
||||
|
||||
c.Assert(hitResult.NumResults, Equals, uint(1))
|
||||
c.Assert(hitResult.PageNumber, Equals, uint(1))
|
||||
c.Assert(hitResult.TotalNumResults, Equals, uint(1))
|
||||
|
||||
c.Assert(len(hitResult.HITs), Equals, 1)
|
||||
c.Assert(hitResult.HITs[0].HITId, Equals, "2BU26DG67D1XTE823B3OQ2JF2XWF83")
|
||||
c.Assert(hitResult.HITs[0].HITTypeId, Equals, "22OWJ5OPB0YV6IGL5727KP9U38P5XR")
|
||||
c.Assert(hitResult.HITs[0].CreationTime, Equals, "2011-12-28T19:56:20Z")
|
||||
c.Assert(hitResult.HITs[0].Title, Equals, "test hit")
|
||||
c.Assert(hitResult.HITs[0].Description, Equals, "please disregard, testing only")
|
||||
c.Assert(hitResult.HITs[0].HITStatus, Equals, "Reviewable")
|
||||
c.Assert(hitResult.HITs[0].MaxAssignments, Equals, uint(1))
|
||||
c.Assert(hitResult.HITs[0].Reward.Amount, Equals, "0.01")
|
||||
c.Assert(hitResult.HITs[0].Reward.CurrencyCode, Equals, "USD")
|
||||
c.Assert(hitResult.HITs[0].AutoApprovalDelayInSeconds, Equals, uint(2592000))
|
||||
c.Assert(hitResult.HITs[0].AssignmentDurationInSeconds, Equals, uint(30))
|
||||
c.Assert(hitResult.HITs[0].NumberOfAssignmentsPending, Equals, uint(0))
|
||||
c.Assert(hitResult.HITs[0].NumberOfAssignmentsAvailable, Equals, uint(1))
|
||||
c.Assert(hitResult.HITs[0].NumberOfAssignmentsCompleted, Equals, uint(0))
|
||||
}
|
||||
9
vendor/src/launchpad.net/goamz/exp/mturk/responses_test.go
vendored
Normal file
9
vendor/src/launchpad.net/goamz/exp/mturk/responses_test.go
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
package mturk_test
|
||||
|
||||
var BasicHitResponse = `<?xml version="1.0"?>
|
||||
<CreateHITResponse><OperationRequest><RequestId>643b794b-66b6-4427-bb8a-4d3df5c9a20e</RequestId></OperationRequest><HIT><Request><IsValid>True</IsValid></Request><HITId>28J4IXKO2L927XKJTHO34OCDNASCDW</HITId><HITTypeId>2XZ7D1X3V0FKQVW7LU51S7PKKGFKDF</HITTypeId></HIT></CreateHITResponse>
|
||||
`
|
||||
|
||||
var SearchHITResponse = `<?xml version="1.0"?>
|
||||
<SearchHITsResponse><OperationRequest><RequestId>38862d9c-f015-4177-a2d3-924110a9d6f2</RequestId></OperationRequest><SearchHITsResult><Request><IsValid>True</IsValid></Request><NumResults>1</NumResults><TotalNumResults>1</TotalNumResults><PageNumber>1</PageNumber><HIT><HITId>2BU26DG67D1XTE823B3OQ2JF2XWF83</HITId><HITTypeId>22OWJ5OPB0YV6IGL5727KP9U38P5XR</HITTypeId><CreationTime>2011-12-28T19:56:20Z</CreationTime><Title>test hit</Title><Description>please disregard, testing only</Description><HITStatus>Reviewable</HITStatus><MaxAssignments>1</MaxAssignments><Reward><Amount>0.01</Amount><CurrencyCode>USD</CurrencyCode><FormattedPrice>$0.01</FormattedPrice></Reward><AutoApprovalDelayInSeconds>2592000</AutoApprovalDelayInSeconds><Expiration>2011-12-28T19:56:50Z</Expiration><AssignmentDurationInSeconds>30</AssignmentDurationInSeconds><NumberOfAssignmentsPending>0</NumberOfAssignmentsPending><NumberOfAssignmentsAvailable>1</NumberOfAssignmentsAvailable><NumberOfAssignmentsCompleted>0</NumberOfAssignmentsCompleted></HIT></SearchHITsResult></SearchHITsResponse>
|
||||
`
|
||||
22
vendor/src/launchpad.net/goamz/exp/mturk/sign.go
vendored
Normal file
22
vendor/src/launchpad.net/goamz/exp/mturk/sign.go
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
package mturk
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"launchpad.net/goamz/aws"
|
||||
)
|
||||
|
||||
var b64 = base64.StdEncoding
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Mechanical Turk signing (http://goo.gl/wrzfn)
|
||||
func sign(auth aws.Auth, service, method, timestamp string, params map[string]string) {
|
||||
payload := service + method + timestamp
|
||||
hash := hmac.New(sha1.New, []byte(auth.SecretKey))
|
||||
hash.Write([]byte(payload))
|
||||
signature := make([]byte, b64.EncodedLen(hash.Size()))
|
||||
b64.Encode(signature, hash.Sum(nil))
|
||||
|
||||
params["Signature"] = string(signature)
|
||||
}
|
||||
19
vendor/src/launchpad.net/goamz/exp/mturk/sign_test.go
vendored
Normal file
19
vendor/src/launchpad.net/goamz/exp/mturk/sign_test.go
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
package mturk_test
|
||||
|
||||
import (
|
||||
"launchpad.net/goamz/aws"
|
||||
"launchpad.net/goamz/exp/mturk"
|
||||
. "launchpad.net/gocheck"
|
||||
)
|
||||
|
||||
// Mechanical Turk REST authentication docs: http://goo.gl/wrzfn
|
||||
|
||||
var testAuth = aws.Auth{"user", "secret"}
|
||||
|
||||
// == fIJy9wCApBNL2R4J2WjJGtIBFX4=
|
||||
func (s *S) TestBasicSignature(c *C) {
|
||||
params := map[string]string{}
|
||||
mturk.Sign(testAuth, "AWSMechanicalTurkRequester", "CreateHIT", "2012-02-16T20:30:47Z", params)
|
||||
expected := "b/TnvzrdeD/L/EyzdFrznPXhido="
|
||||
c.Assert(params["Signature"], Equals, expected)
|
||||
}
|
||||
9
vendor/src/launchpad.net/goamz/exp/sdb/export_test.go
vendored
Normal file
9
vendor/src/launchpad.net/goamz/exp/sdb/export_test.go
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
package sdb
|
||||
|
||||
import (
|
||||
"launchpad.net/goamz/aws"
|
||||
)
|
||||
|
||||
func Sign(auth aws.Auth, method, path string, params map[string][]string, headers map[string][]string) {
|
||||
sign(auth, method, path, params, headers)
|
||||
}
|
||||
120
vendor/src/launchpad.net/goamz/exp/sdb/responses_test.go
vendored
Normal file
120
vendor/src/launchpad.net/goamz/exp/sdb/responses_test.go
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
package sdb_test
|
||||
|
||||
var TestCreateDomainXmlOK = `
|
||||
<?xml version="1.0"?>
|
||||
<CreateDomainResponse xmlns="http://sdb.amazonaws.com/doc/2009-04-15/">
|
||||
<ResponseMetadata>
|
||||
<RequestId>63264005-7a5f-e01a-a224-395c63b89f6d</RequestId>
|
||||
<BoxUsage>0.0055590279</BoxUsage>
|
||||
</ResponseMetadata>
|
||||
</CreateDomainResponse>
|
||||
`
|
||||
|
||||
var TestListDomainsXmlOK = `
|
||||
<?xml version="1.0"?>
|
||||
<ListDomainsResponse xmlns="http://sdb.amazonaws.com/doc/2009-04-15/">
|
||||
<ListDomainsResult>
|
||||
<DomainName>Account</DomainName>
|
||||
<DomainName>Domain</DomainName>
|
||||
<DomainName>Record</DomainName>
|
||||
</ListDomainsResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>15fcaf55-9914-63c2-21f3-951e31193790</RequestId>
|
||||
<BoxUsage>0.0000071759</BoxUsage>
|
||||
</ResponseMetadata>
|
||||
</ListDomainsResponse>
|
||||
`
|
||||
|
||||
var TestListDomainsWithNextTokenXmlOK = `
|
||||
<?xml version="1.0"?>
|
||||
<ListDomainsResponse xmlns="http://sdb.amazonaws.com/doc/2009-04-15/">
|
||||
<ListDomainsResult>
|
||||
<DomainName>Domain1-200706011651</DomainName>
|
||||
<DomainName>Domain2-200706011652</DomainName>
|
||||
<NextToken>TWV0ZXJpbmdUZXN0RG9tYWluMS0yMDA3MDYwMTE2NTY=</NextToken>
|
||||
</ListDomainsResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>eb13162f-1b95-4511-8b12-489b86acfd28</RequestId>
|
||||
<BoxUsage>0.0000219907</BoxUsage>
|
||||
</ResponseMetadata>
|
||||
</ListDomainsResponse>
|
||||
`
|
||||
|
||||
var TestDeleteDomainXmlOK = `
|
||||
<?xml version="1.0"?>
|
||||
<DeleteDomainResponse xmlns="http://sdb.amazonaws.com/doc/2009-04-15/">
|
||||
<ResponseMetadata>
|
||||
<RequestId>039e1e25-9a64-2a74-93da-2fda36122a97</RequestId>
|
||||
<BoxUsage>0.0055590278</BoxUsage>
|
||||
</ResponseMetadata>
|
||||
</DeleteDomainResponse>
|
||||
`
|
||||
|
||||
var TestDomainMetadataXmlNoSuchDomain = `
|
||||
<?xml version="1.0"?>
|
||||
<Response xmlns="http://sdb.amazonaws.com/doc/2009-04-15/">
|
||||
<Errors>
|
||||
<Error>
|
||||
<Code>NoSuchDomain</Code>
|
||||
<Message>The specified domain does not exist.</Message>
|
||||
<BoxUsage>0.0000071759</BoxUsage>
|
||||
</Error>
|
||||
</Errors>
|
||||
<RequestID>e050cea2-a772-f90e-2cb0-98ebd42c2898</RequestID>
|
||||
</Response>
|
||||
`
|
||||
|
||||
var TestPutAttrsXmlOK = `
|
||||
<?xml version="1.0"?>
|
||||
<PutAttributesResponse xmlns="http://sdb.amazonaws.com/doc/2009-04-15/">
|
||||
<ResponseMetadata>
|
||||
<RequestId>490206ce-8292-456c-a00f-61b335eb202b</RequestId>
|
||||
<BoxUsage>0.0000219907</BoxUsage>
|
||||
</ResponseMetadata>
|
||||
</PutAttributesResponse>
|
||||
`
|
||||
|
||||
var TestAttrsXmlOK = `
|
||||
<?xml version="1.0"?>
|
||||
<GetAttributesResponse xmlns="http://sdb.amazonaws.com/doc/2009-04-15/">
|
||||
<GetAttributesResult>
|
||||
<Attribute><Name>Color</Name><Value>Blue</Value></Attribute>
|
||||
<Attribute><Name>Size</Name><Value>Med</Value></Attribute>
|
||||
</GetAttributesResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>b1e8f1f7-42e9-494c-ad09-2674e557526d</RequestId>
|
||||
<BoxUsage>0.0000219942</BoxUsage>
|
||||
</ResponseMetadata>
|
||||
</GetAttributesResponse>
|
||||
`
|
||||
|
||||
var TestSelectXmlOK = `
|
||||
<?xml version="1.0"?>
|
||||
<SelectResponse xmlns="http://sdb.amazonaws.com/doc/2009-04-15/">
|
||||
<SelectResult>
|
||||
<Item>
|
||||
<Name>Item_03</Name>
|
||||
<Attribute><Name>Category</Name><Value>Clothes</Value></Attribute>
|
||||
<Attribute><Name>Subcategory</Name><Value>Pants</Value></Attribute>
|
||||
<Attribute><Name>Name</Name><Value>Sweatpants</Value></Attribute>
|
||||
<Attribute><Name>Color</Name><Value>Blue</Value></Attribute>
|
||||
<Attribute><Name>Color</Name><Value>Yellow</Value></Attribute>
|
||||
<Attribute><Name>Color</Name><Value>Pink</Value></Attribute>
|
||||
<Attribute><Name>Size</Name><Value>Large</Value></Attribute>
|
||||
</Item>
|
||||
<Item>
|
||||
<Name>Item_06</Name>
|
||||
<Attribute><Name>Category</Name><Value>Motorcycle Parts</Value></Attribute>
|
||||
<Attribute><Name>Subcategory</Name><Value>Bodywork</Value></Attribute>
|
||||
<Attribute><Name>Name</Name><Value>Fender Eliminator</Value></Attribute>
|
||||
<Attribute><Name>Color</Name><Value>Blue</Value></Attribute>
|
||||
<Attribute><Name>Make</Name><Value>Yamaha</Value></Attribute>
|
||||
<Attribute><Name>Model</Name><Value>R1</Value></Attribute>
|
||||
</Item>
|
||||
</SelectResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>b1e8f1f7-42e9-494c-ad09-2674e557526d</RequestId>
|
||||
<BoxUsage>0.0000219907</BoxUsage>
|
||||
</ResponseMetadata>
|
||||
</SelectResponse>
|
||||
`
|
||||
413
vendor/src/launchpad.net/goamz/exp/sdb/sdb.go
vendored
Normal file
413
vendor/src/launchpad.net/goamz/exp/sdb/sdb.go
vendored
Normal file
@@ -0,0 +1,413 @@
|
||||
//
|
||||
// goamz - Go packages to interact with the Amazon Web Services.
|
||||
//
|
||||
// https://wiki.ubuntu.com/goamz
|
||||
//
|
||||
// Copyright (c) 2011 AppsAttic Ltd.
|
||||
//
|
||||
// sdb package written by:
|
||||
//
|
||||
// Andrew Chilton <chilts@appsattic.com>
|
||||
// Brad Rydzewski <brad.rydzewski@gmail.com>
|
||||
|
||||
// This package is in an experimental state, and does not currently
|
||||
// follow conventions and style of the rest of goamz or common
|
||||
// Go conventions. It must be polished before it's considered a
|
||||
// first-class package in goamz.
|
||||
package sdb
|
||||
|
||||
// BUG: SelectResp isn't properly organized. It must change.
|
||||
|
||||
//
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"launchpad.net/goamz/aws"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const debug = false
|
||||
|
||||
// The SDB type encapsulates operations with a specific SimpleDB region.
|
||||
type SDB struct {
|
||||
aws.Auth
|
||||
aws.Region
|
||||
private byte // Reserve the right of using private data.
|
||||
}
|
||||
|
||||
// New creates a new SDB.
|
||||
func New(auth aws.Auth, region aws.Region) *SDB {
|
||||
return &SDB{auth, region, 0}
|
||||
}
|
||||
|
||||
// The Domain type represents a collection of items that are described
|
||||
// by name-value attributes.
|
||||
type Domain struct {
|
||||
*SDB
|
||||
Name string
|
||||
}
|
||||
|
||||
// Domain returns a Domain with the given name.
|
||||
func (sdb *SDB) Domain(name string) *Domain {
|
||||
return &Domain{sdb, name}
|
||||
}
|
||||
|
||||
// The Item type represent individual objects that contain one or more
|
||||
// name-value attributes stored within a SDB Domain as rows.
|
||||
type Item struct {
|
||||
*SDB
|
||||
*Domain
|
||||
Name string
|
||||
}
|
||||
|
||||
// Item returns an Item with the given name.
|
||||
func (domain *Domain) Item(name string) *Item {
|
||||
return &Item{domain.SDB, domain, name}
|
||||
}
|
||||
|
||||
// The Attr type represent categories of data that can be assigned to items.
|
||||
type Attr struct {
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Service-level operations.
|
||||
|
||||
// --- ListDomains
|
||||
|
||||
// Response to a ListDomains request.
|
||||
//
|
||||
// See http://goo.gl/3u0Cf for more details.
|
||||
type ListDomainsResp struct {
|
||||
Domains []string `xml:"ListDomainsResult>DomainName"`
|
||||
NextToken string `xml:"ListDomainsResult>NextToken"`
|
||||
ResponseMetadata ResponseMetadata
|
||||
}
|
||||
|
||||
// ListDomains lists all domains in sdb.
|
||||
//
|
||||
// See http://goo.gl/Dsw15 for more details.
|
||||
func (sdb *SDB) ListDomains() (resp *ListDomainsResp, err error) {
|
||||
return sdb.ListDomainsN(0, "")
|
||||
}
|
||||
|
||||
// ListDomainsN lists domains in sdb up to maxDomains.
|
||||
// If nextToken is not empty, domains listed will start at the given token.
|
||||
//
|
||||
// See http://goo.gl/Dsw15 for more details.
|
||||
func (sdb *SDB) ListDomainsN(maxDomains int, nextToken string) (resp *ListDomainsResp, err error) {
|
||||
params := makeParams("ListDomains")
|
||||
if maxDomains != 0 {
|
||||
params["MaxNumberOfDomains"] = []string{strconv.Itoa(maxDomains)}
|
||||
}
|
||||
if nextToken != "" {
|
||||
params["NextToken"] = []string{nextToken}
|
||||
}
|
||||
resp = &ListDomainsResp{}
|
||||
err = sdb.query(nil, nil, params, nil, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// --- SelectExpression
|
||||
|
||||
// Response to a Select request.
|
||||
//
|
||||
// See http://goo.gl/GTsSZ for more details.
|
||||
type SelectResp struct {
|
||||
Items []struct {
|
||||
Name string
|
||||
Attrs []Attr `xml:"Attribute"`
|
||||
} `xml:"SelectResult>Item"`
|
||||
ResponseMetadata ResponseMetadata
|
||||
}
|
||||
|
||||
// Select returns a set of items and attributes that match expr.
|
||||
// Select is similar to the standard SQL SELECT statement.
|
||||
//
|
||||
// See http://goo.gl/GTsSZ for more details.
|
||||
func (sdb *SDB) Select(expr string, consistent bool) (resp *SelectResp, err error) {
|
||||
resp = &SelectResp{}
|
||||
params := makeParams("Select")
|
||||
params["SelectExpression"] = []string{expr}
|
||||
if consistent {
|
||||
params["ConsistentRead"] = []string{"true"}
|
||||
}
|
||||
err = sdb.query(nil, nil, params, nil, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Domain-level operations.
|
||||
|
||||
// --- CreateDomain
|
||||
|
||||
// CreateDomain creates a new domain.
|
||||
//
|
||||
// See http://goo.gl/jDjGH for more details.
|
||||
func (domain *Domain) CreateDomain() (resp *SimpleResp, err error) {
|
||||
params := makeParams("CreateDomain")
|
||||
resp = &SimpleResp{}
|
||||
err = domain.SDB.query(domain, nil, params, nil, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteDomain deletes an existing domain.
|
||||
//
|
||||
// See http://goo.gl/S0dCL for more details.
|
||||
func (domain *Domain) DeleteDomain() (resp *SimpleResp, err error) {
|
||||
params := makeParams("DeleteDomain")
|
||||
resp = &SimpleResp{}
|
||||
err = domain.SDB.query(domain, nil, params, nil, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Item-level operations.
|
||||
|
||||
type PutAttrs struct {
|
||||
attrs []Attr
|
||||
expected []Attr
|
||||
replace map[string]bool
|
||||
missing map[string]bool
|
||||
}
|
||||
|
||||
func (pa *PutAttrs) Add(name, value string) {
|
||||
pa.attrs = append(pa.attrs, Attr{name, value})
|
||||
}
|
||||
|
||||
func (pa *PutAttrs) Replace(name, value string) {
|
||||
pa.Add(name, value)
|
||||
if pa.replace == nil {
|
||||
pa.replace = make(map[string]bool)
|
||||
}
|
||||
pa.replace[name] = true
|
||||
}
|
||||
|
||||
// The PutAttrs request will only succeed if the existing
|
||||
// item in SimpleDB contains a matching name / value pair.
|
||||
func (pa *PutAttrs) IfValue(name, value string) {
|
||||
pa.expected = append(pa.expected, Attr{name, value})
|
||||
}
|
||||
|
||||
// Flag to test the existence of an attribute while performing
|
||||
// conditional updates. X can be any positive integer or 0.
|
||||
//
|
||||
// This should set Expected.N.Name=name and Expected.N.Exists=false
|
||||
func (pa *PutAttrs) IfMissing(name string) {
|
||||
if pa.missing == nil {
|
||||
pa.missing = make(map[string]bool)
|
||||
}
|
||||
pa.missing[name] = true
|
||||
}
|
||||
|
||||
// PutAttrs adds attrs to item.
|
||||
//
|
||||
// See http://goo.gl/yTAV4 for more details.
|
||||
func (item *Item) PutAttrs(attrs *PutAttrs) (resp *SimpleResp, err error) {
|
||||
params := makeParams("PutAttributes")
|
||||
resp = &SimpleResp{}
|
||||
|
||||
// copy these attrs over to the parameters
|
||||
itemNum := 1
|
||||
for _, attr := range attrs.attrs {
|
||||
itemNumStr := strconv.Itoa(itemNum)
|
||||
|
||||
// do the name, value and replace
|
||||
params["Attribute."+itemNumStr+".Name"] = []string{attr.Name}
|
||||
params["Attribute."+itemNumStr+".Value"] = []string{attr.Value}
|
||||
|
||||
if _, ok := attrs.replace[attr.Name]; ok {
|
||||
params["Attribute."+itemNumStr+".Replace"] = []string{"true"}
|
||||
}
|
||||
|
||||
itemNum++
|
||||
}
|
||||
|
||||
//append expected values to params
|
||||
expectedNum := 1
|
||||
for _, attr := range attrs.expected {
|
||||
expectedNumStr := strconv.Itoa(expectedNum)
|
||||
params["Expected."+expectedNumStr+".Name"] = []string{attr.Name}
|
||||
params["Expected."+expectedNumStr+".Value"] = []string{attr.Value}
|
||||
|
||||
if attrs.missing[attr.Name] {
|
||||
params["Expected."+expectedNumStr+".Exists"] = []string{"false"}
|
||||
}
|
||||
expectedNum++
|
||||
}
|
||||
|
||||
err = item.query(params, nil, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Response to an Attrs request.
|
||||
//
|
||||
// See http://goo.gl/45X1M for more details.
|
||||
type AttrsResp struct {
|
||||
Attrs []Attr `xml:"GetAttributesResult>Attribute"`
|
||||
ResponseMetadata ResponseMetadata
|
||||
}
|
||||
|
||||
// Attrs returns one or more of the named attributes, or
|
||||
// all of item's attributes if names is nil.
|
||||
// If consistent is true, previous writes will necessarily
|
||||
// be observed.
|
||||
//
|
||||
// See http://goo.gl/45X1M for more details.
|
||||
func (item *Item) Attrs(names []string, consistent bool) (resp *AttrsResp, err error) {
|
||||
params := makeParams("GetAttributes")
|
||||
params["ItemName"] = []string{item.Name}
|
||||
if consistent {
|
||||
params["ConsistentRead"] = []string{"true"}
|
||||
}
|
||||
|
||||
// Copy these attributes over to the parameters
|
||||
for i, name := range names {
|
||||
params["AttributeName."+strconv.Itoa(i+1)] = []string{name}
|
||||
}
|
||||
|
||||
resp = &AttrsResp{}
|
||||
err = item.query(params, nil, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Generic data structures for all requests/responses.
|
||||
|
||||
// Error encapsulates an error returned by SDB.
|
||||
type Error struct {
|
||||
StatusCode int // HTTP status code (200, 403, ...)
|
||||
StatusMsg string // HTTP status message ("Service Unavailable", "Bad Request", ...)
|
||||
Code string // SimpleDB error code ("InvalidParameterValue", ...)
|
||||
Message string // The human-oriented error message
|
||||
RequestId string // A unique ID for this request
|
||||
BoxUsage float64 // The measure of machine utilization for this request.
|
||||
}
|
||||
|
||||
func (err *Error) Error() string {
|
||||
return err.Message
|
||||
}
|
||||
|
||||
// SimpleResp represents a response to an SDB request which on success
|
||||
// will return no other information besides ResponseMetadata.
|
||||
type SimpleResp struct {
|
||||
ResponseMetadata ResponseMetadata
|
||||
}
|
||||
|
||||
// ResponseMetadata
|
||||
type ResponseMetadata struct {
|
||||
RequestId string // A unique ID for tracking the request
|
||||
BoxUsage float64 // The measure of machine utilization for this request.
|
||||
}
|
||||
|
||||
func buildError(r *http.Response) error {
|
||||
err := Error{}
|
||||
err.StatusCode = r.StatusCode
|
||||
err.StatusMsg = r.Status
|
||||
xml.NewDecoder(r.Body).Decode(&err)
|
||||
return &err
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Request dispatching logic.
|
||||
|
||||
func (item *Item) query(params url.Values, headers http.Header, resp interface{}) error {
|
||||
return item.Domain.SDB.query(item.Domain, item, params, headers, resp)
|
||||
}
|
||||
|
||||
func (domain *Domain) query(item *Item, params url.Values, headers http.Header, resp interface{}) error {
|
||||
return domain.SDB.query(domain, item, params, headers, resp)
|
||||
}
|
||||
|
||||
func (sdb *SDB) query(domain *Domain, item *Item, params url.Values, headers http.Header, resp interface{}) error {
|
||||
// all SimpleDB operations have path="/"
|
||||
method := "GET"
|
||||
path := "/"
|
||||
|
||||
// if we have been given no headers or params, create them
|
||||
if headers == nil {
|
||||
headers = map[string][]string{}
|
||||
}
|
||||
if params == nil {
|
||||
params = map[string][]string{}
|
||||
}
|
||||
|
||||
// setup some default parameters
|
||||
params["Version"] = []string{"2009-04-15"}
|
||||
params["Timestamp"] = []string{time.Now().UTC().Format(time.RFC3339)}
|
||||
|
||||
// set the DomainName param (every request must have one)
|
||||
if domain != nil {
|
||||
params["DomainName"] = []string{domain.Name}
|
||||
}
|
||||
|
||||
// set the ItemName if we have one
|
||||
if item != nil {
|
||||
params["ItemName"] = []string{item.Name}
|
||||
}
|
||||
|
||||
// check the endpoint URL
|
||||
u, err := url.Parse(sdb.Region.SDBEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
headers["Host"] = []string{u.Host}
|
||||
sign(sdb.Auth, method, path, params, headers)
|
||||
|
||||
u.Path = path
|
||||
if len(params) > 0 {
|
||||
u.RawQuery = params.Encode()
|
||||
}
|
||||
req := http.Request{
|
||||
URL: u,
|
||||
Method: method,
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Close: true,
|
||||
Header: headers,
|
||||
}
|
||||
|
||||
if v, ok := headers["Content-Length"]; ok {
|
||||
req.ContentLength, _ = strconv.ParseInt(v[0], 10, 64)
|
||||
delete(headers, "Content-Length")
|
||||
}
|
||||
|
||||
r, err := http.DefaultClient.Do(&req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
if debug {
|
||||
dump, _ := httputil.DumpResponse(r, true)
|
||||
log.Printf("response:\n")
|
||||
log.Printf("%v\n}\n", string(dump))
|
||||
}
|
||||
|
||||
// status code is always 200 when successful (since we're always doing a GET)
|
||||
if r.StatusCode != 200 {
|
||||
return buildError(r)
|
||||
}
|
||||
|
||||
// everything was fine, so unmarshal the XML and return what it's err is (if any)
|
||||
err = xml.NewDecoder(r.Body).Decode(resp)
|
||||
return err
|
||||
}
|
||||
|
||||
func makeParams(action string) map[string][]string {
|
||||
params := make(map[string][]string)
|
||||
params["Action"] = []string{action}
|
||||
return params
|
||||
}
|
||||
222
vendor/src/launchpad.net/goamz/exp/sdb/sdb_test.go
vendored
Normal file
222
vendor/src/launchpad.net/goamz/exp/sdb/sdb_test.go
vendored
Normal file
@@ -0,0 +1,222 @@
|
||||
package sdb_test
|
||||
|
||||
import (
|
||||
"launchpad.net/goamz/aws"
|
||||
"launchpad.net/goamz/exp/sdb"
|
||||
"launchpad.net/goamz/testutil"
|
||||
. "launchpad.net/gocheck"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
TestingT(t)
|
||||
}
|
||||
|
||||
var _ = Suite(&S{})
|
||||
|
||||
type S struct {
|
||||
sdb *sdb.SDB
|
||||
}
|
||||
|
||||
var testServer = testutil.NewHTTPServer()
|
||||
|
||||
func (s *S) SetUpSuite(c *C) {
|
||||
testServer.Start()
|
||||
auth := aws.Auth{"abc", "123"}
|
||||
s.sdb = sdb.New(auth, aws.Region{SDBEndpoint: testServer.URL})
|
||||
}
|
||||
|
||||
func (s *S) TearDownSuite(c *C) {
|
||||
testServer.Stop()
|
||||
}
|
||||
|
||||
func (s *S) TearDownTest(c *C) {
|
||||
testServer.Flush()
|
||||
}
|
||||
|
||||
func (s *S) TestCreateDomainOK(c *C) {
|
||||
testServer.Response(200, nil, TestCreateDomainXmlOK)
|
||||
|
||||
domain := s.sdb.Domain("domain")
|
||||
resp, err := domain.CreateDomain()
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
|
||||
c.Assert(resp.ResponseMetadata.RequestId, Equals, "63264005-7a5f-e01a-a224-395c63b89f6d")
|
||||
c.Assert(resp.ResponseMetadata.BoxUsage, Equals, 0.0055590279)
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *S) TestListDomainsOK(c *C) {
|
||||
testServer.Response(200, nil, TestListDomainsXmlOK)
|
||||
|
||||
resp, err := s.sdb.ListDomains()
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
|
||||
c.Assert(resp.ResponseMetadata.RequestId, Equals, "15fcaf55-9914-63c2-21f3-951e31193790")
|
||||
c.Assert(resp.ResponseMetadata.BoxUsage, Equals, 0.0000071759)
|
||||
c.Assert(resp.Domains, DeepEquals, []string{"Account", "Domain", "Record"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *S) TestListDomainsWithNextTokenXmlOK(c *C) {
|
||||
testServer.Response(200, nil, TestListDomainsWithNextTokenXmlOK)
|
||||
|
||||
resp, err := s.sdb.ListDomains()
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
|
||||
c.Assert(resp.ResponseMetadata.RequestId, Equals, "eb13162f-1b95-4511-8b12-489b86acfd28")
|
||||
c.Assert(resp.ResponseMetadata.BoxUsage, Equals, 0.0000219907)
|
||||
c.Assert(resp.Domains, DeepEquals, []string{"Domain1-200706011651", "Domain2-200706011652"})
|
||||
c.Assert(resp.NextToken, Equals, "TWV0ZXJpbmdUZXN0RG9tYWluMS0yMDA3MDYwMTE2NTY=")
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *S) TestDeleteDomainOK(c *C) {
|
||||
testServer.Response(200, nil, TestDeleteDomainXmlOK)
|
||||
|
||||
domain := s.sdb.Domain("domain")
|
||||
resp, err := domain.DeleteDomain()
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
|
||||
c.Assert(resp.ResponseMetadata.RequestId, Equals, "039e1e25-9a64-2a74-93da-2fda36122a97")
|
||||
c.Assert(resp.ResponseMetadata.BoxUsage, Equals, 0.0055590278)
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *S) TestPutAttrsOK(c *C) {
|
||||
testServer.Response(200, nil, TestPutAttrsXmlOK)
|
||||
|
||||
domain := s.sdb.Domain("MyDomain")
|
||||
item := domain.Item("Item123")
|
||||
|
||||
putAttrs := new(sdb.PutAttrs)
|
||||
putAttrs.Add("FirstName", "john")
|
||||
putAttrs.Add("LastName", "smith")
|
||||
putAttrs.Replace("MiddleName", "jacob")
|
||||
|
||||
putAttrs.IfValue("FirstName", "john")
|
||||
putAttrs.IfMissing("FirstName")
|
||||
|
||||
resp, err := item.PutAttrs(putAttrs)
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/")
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"PutAttributes"})
|
||||
c.Assert(req.Form["ItemName"], DeepEquals, []string{"Item123"})
|
||||
c.Assert(req.Form["DomainName"], DeepEquals, []string{"MyDomain"})
|
||||
c.Assert(req.Form["Attribute.1.Name"], DeepEquals, []string{"FirstName"})
|
||||
c.Assert(req.Form["Attribute.1.Value"], DeepEquals, []string{"john"})
|
||||
c.Assert(req.Form["Attribute.2.Name"], DeepEquals, []string{"LastName"})
|
||||
c.Assert(req.Form["Attribute.2.Value"], DeepEquals, []string{"smith"})
|
||||
c.Assert(req.Form["Attribute.3.Name"], DeepEquals, []string{"MiddleName"})
|
||||
c.Assert(req.Form["Attribute.3.Value"], DeepEquals, []string{"jacob"})
|
||||
c.Assert(req.Form["Attribute.3.Replace"], DeepEquals, []string{"true"})
|
||||
|
||||
c.Assert(req.Form["Expected.1.Name"], DeepEquals, []string{"FirstName"})
|
||||
c.Assert(req.Form["Expected.1.Value"], DeepEquals, []string{"john"})
|
||||
c.Assert(req.Form["Expected.1.Exists"], DeepEquals, []string{"false"})
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.ResponseMetadata.RequestId, Equals, "490206ce-8292-456c-a00f-61b335eb202b")
|
||||
c.Assert(resp.ResponseMetadata.BoxUsage, Equals, 0.0000219907)
|
||||
|
||||
}
|
||||
|
||||
func (s *S) TestAttrsOK(c *C) {
|
||||
testServer.Response(200, nil, TestAttrsXmlOK)
|
||||
|
||||
domain := s.sdb.Domain("MyDomain")
|
||||
item := domain.Item("Item123")
|
||||
|
||||
resp, err := item.Attrs(nil, true)
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"GetAttributes"})
|
||||
c.Assert(req.Form["ItemName"], DeepEquals, []string{"Item123"})
|
||||
c.Assert(req.Form["DomainName"], DeepEquals, []string{"MyDomain"})
|
||||
c.Assert(req.Form["ConsistentRead"], DeepEquals, []string{"true"})
|
||||
|
||||
c.Assert(resp.Attrs[0].Name, Equals, "Color")
|
||||
c.Assert(resp.Attrs[0].Value, Equals, "Blue")
|
||||
c.Assert(resp.Attrs[1].Name, Equals, "Size")
|
||||
c.Assert(resp.Attrs[1].Value, Equals, "Med")
|
||||
c.Assert(resp.ResponseMetadata.RequestId, Equals, "b1e8f1f7-42e9-494c-ad09-2674e557526d")
|
||||
c.Assert(resp.ResponseMetadata.BoxUsage, Equals, 0.0000219942)
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *S) TestAttrsSelectOK(c *C) {
|
||||
testServer.Response(200, nil, TestAttrsXmlOK)
|
||||
|
||||
domain := s.sdb.Domain("MyDomain")
|
||||
item := domain.Item("Item123")
|
||||
|
||||
resp, err := item.Attrs([]string{"Color", "Size"}, true)
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"GetAttributes"})
|
||||
c.Assert(req.Form["ItemName"], DeepEquals, []string{"Item123"})
|
||||
c.Assert(req.Form["DomainName"], DeepEquals, []string{"MyDomain"})
|
||||
c.Assert(req.Form["ConsistentRead"], DeepEquals, []string{"true"})
|
||||
c.Assert(req.Form["AttributeName.1"], DeepEquals, []string{"Color"})
|
||||
c.Assert(req.Form["AttributeName.2"], DeepEquals, []string{"Size"})
|
||||
|
||||
c.Assert(resp.Attrs[0].Name, Equals, "Color")
|
||||
c.Assert(resp.Attrs[0].Value, Equals, "Blue")
|
||||
c.Assert(resp.Attrs[1].Name, Equals, "Size")
|
||||
c.Assert(resp.Attrs[1].Value, Equals, "Med")
|
||||
c.Assert(resp.ResponseMetadata.RequestId, Equals, "b1e8f1f7-42e9-494c-ad09-2674e557526d")
|
||||
c.Assert(resp.ResponseMetadata.BoxUsage, Equals, 0.0000219942)
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *S) TestSelectOK(c *C) {
|
||||
testServer.Response(200, nil, TestSelectXmlOK)
|
||||
|
||||
resp, err := s.sdb.Select("select Color from MyDomain where Color like 'Blue%'", true)
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
c.Assert(req.Form["Action"], DeepEquals, []string{"Select"})
|
||||
c.Assert(req.Form["ConsistentRead"], DeepEquals, []string{"true"})
|
||||
|
||||
c.Assert(resp.ResponseMetadata.RequestId, Equals, "b1e8f1f7-42e9-494c-ad09-2674e557526d")
|
||||
c.Assert(resp.ResponseMetadata.BoxUsage, Equals, 0.0000219907)
|
||||
c.Assert(len(resp.Items), Equals, 2)
|
||||
c.Assert(resp.Items[0].Name, Equals, "Item_03")
|
||||
c.Assert(resp.Items[1].Name, Equals, "Item_06")
|
||||
c.Assert(resp.Items[0].Attrs[0].Name, Equals, "Category")
|
||||
c.Assert(resp.Items[0].Attrs[0].Value, Equals, "Clothes")
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
51
vendor/src/launchpad.net/goamz/exp/sdb/sign.go
vendored
Normal file
51
vendor/src/launchpad.net/goamz/exp/sdb/sign.go
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
package sdb
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"launchpad.net/goamz/aws"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var b64 = base64.StdEncoding
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// SimpleDB signing (http://goo.gl/CaY81)
|
||||
|
||||
func sign(auth aws.Auth, method, path string, params url.Values, headers http.Header) {
|
||||
var host string
|
||||
for k, v := range headers {
|
||||
k = strings.ToLower(k)
|
||||
switch k {
|
||||
case "host":
|
||||
host = v[0]
|
||||
}
|
||||
}
|
||||
|
||||
// set up some defaults used for signing the request
|
||||
params["AWSAccessKeyId"] = []string{auth.AccessKey}
|
||||
params["SignatureVersion"] = []string{"2"}
|
||||
params["SignatureMethod"] = []string{"HmacSHA256"}
|
||||
|
||||
// join up all the incoming params
|
||||
var sarray []string
|
||||
for k, v := range params {
|
||||
sarray = append(sarray, aws.Encode(k)+"="+aws.Encode(v[0]))
|
||||
}
|
||||
sort.StringSlice(sarray).Sort()
|
||||
joined := strings.Join(sarray, "&")
|
||||
|
||||
// create the payload, sign it and create the signature
|
||||
payload := strings.Join([]string{method, host, "/", joined}, "\n")
|
||||
hash := hmac.New(sha256.New, []byte(auth.SecretKey))
|
||||
hash.Write([]byte(payload))
|
||||
signature := make([]byte, b64.EncodedLen(hash.Size()))
|
||||
b64.Encode(signature, hash.Sum(nil))
|
||||
|
||||
// add the signature to the outgoing params
|
||||
params["Signature"] = []string{string(signature)}
|
||||
}
|
||||
29
vendor/src/launchpad.net/goamz/exp/sdb/sign_test.go
vendored
Normal file
29
vendor/src/launchpad.net/goamz/exp/sdb/sign_test.go
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
package sdb_test
|
||||
|
||||
import (
|
||||
"launchpad.net/goamz/aws"
|
||||
"launchpad.net/goamz/exp/sdb"
|
||||
. "launchpad.net/gocheck"
|
||||
)
|
||||
|
||||
// SimpleDB ReST authentication docs: http://goo.gl/CaY81
|
||||
|
||||
var testAuth = aws.Auth{"access-key-id-s8eBOWuU", "secret-access-key-UkQjTLd9"}
|
||||
|
||||
func (s *S) TestSignExampleDomainCreate(c *C) {
|
||||
method := "GET"
|
||||
params := map[string][]string{
|
||||
"Action": {"CreateDomain"},
|
||||
"DomainName": {"MyDomain"},
|
||||
"Timestamp": {"2011-08-20T07:23:57+12:00"},
|
||||
"Version": {"2009-04-15"},
|
||||
}
|
||||
headers := map[string][]string{
|
||||
"Host": {"sdb.amazonaws.com"},
|
||||
}
|
||||
sdb.Sign(testAuth, method, "", params, headers)
|
||||
expected := "ot2JaeeqMRJqgAqW67hkzUlffgxdOz4RykbrECB+tDU="
|
||||
c.Assert(params["Signature"], DeepEquals, []string{expected})
|
||||
}
|
||||
|
||||
// Do a few test methods which takes combinations of params
|
||||
21
vendor/src/launchpad.net/goamz/exp/sns/Makefile
vendored
Normal file
21
vendor/src/launchpad.net/goamz/exp/sns/Makefile
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
include $(GOROOT)/src/Make.inc
|
||||
|
||||
TARG=launchpad.net/goamz/sns
|
||||
|
||||
GOFILES=\
|
||||
sns.go\
|
||||
sign.go\
|
||||
|
||||
include $(GOROOT)/src/Make.pkg
|
||||
|
||||
GOFMT=gofmt
|
||||
BADFMT=$(shell $(GOFMT) -l $(GOFILES) 2> /dev/null)
|
||||
|
||||
gofmt: $(BADFMT)
|
||||
@for F in $(BADFMT); do $(GOFMT) -w $$F && echo $$F; done
|
||||
|
||||
ifneq ($(BADFMT),)
|
||||
ifneq ($(MAKECMDGOALS), gofmt)
|
||||
#$(warning WARNING: make gofmt: $(BADFMT))
|
||||
endif
|
||||
endif
|
||||
1
vendor/src/launchpad.net/goamz/exp/sns/README
vendored
Normal file
1
vendor/src/launchpad.net/goamz/exp/sns/README
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Amazon Simple Notification Service API for Golang.
|
||||
164
vendor/src/launchpad.net/goamz/exp/sns/responses_test.go
vendored
Normal file
164
vendor/src/launchpad.net/goamz/exp/sns/responses_test.go
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
package sns_test
|
||||
|
||||
var TestListTopicsXmlOK = `
|
||||
<?xml version="1.0"?>
|
||||
<ListTopicsResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
|
||||
<ListTopicsResult>
|
||||
<Topics>
|
||||
<member>
|
||||
<TopicArn>arn:aws:sns:us-west-1:331995417492:Transcoding</TopicArn>
|
||||
</member>
|
||||
</Topics>
|
||||
</ListTopicsResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>bd10b26c-e30e-11e0-ba29-93c3aca2f103</RequestId>
|
||||
</ResponseMetadata>
|
||||
</ListTopicsResponse>
|
||||
`
|
||||
|
||||
var TestCreateTopicXmlOK = `
|
||||
<?xml version="1.0"?>
|
||||
<CreateTopicResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
|
||||
<CreateTopicResult>
|
||||
<TopicArn>arn:aws:sns:us-east-1:123456789012:My-Topic</TopicArn>
|
||||
</CreateTopicResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>a8dec8b3-33a4-11df-8963-01868b7c937a</RequestId>
|
||||
</ResponseMetadata>
|
||||
</CreateTopicResponse>
|
||||
`
|
||||
|
||||
var TestDeleteTopicXmlOK = `
|
||||
<DeleteTopicResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
|
||||
<ResponseMetadata>
|
||||
<RequestId>f3aa9ac9-3c3d-11df-8235-9dab105e9c32</RequestId>
|
||||
</ResponseMetadata>
|
||||
</DeleteTopicResponse>
|
||||
`
|
||||
|
||||
var TestListSubscriptionsXmlOK = `
|
||||
<ListSubscriptionsResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
|
||||
<ListSubscriptionsResult>
|
||||
<Subscriptions>
|
||||
<member>
|
||||
<TopicArn>arn:aws:sns:us-east-1:698519295917:My-Topic</TopicArn>
|
||||
<Protocol>email</Protocol>
|
||||
<SubscriptionArn>arn:aws:sns:us-east-1:123456789012:My-Topic:80289ba6-0fd4-4079-afb4-ce8c8260f0ca</SubscriptionArn>
|
||||
<Owner>123456789012</Owner>
|
||||
<Endpoint>example@amazon.com</Endpoint>
|
||||
</member>
|
||||
</Subscriptions>
|
||||
</ListSubscriptionsResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>384ac68d-3775-11df-8963-01868b7c937a</RequestId>
|
||||
</ResponseMetadata>
|
||||
</ListSubscriptionsResponse>
|
||||
`
|
||||
|
||||
var TestGetTopicAttributesXmlOK = `
|
||||
<GetTopicAttributesResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
|
||||
<GetTopicAttributesResult>
|
||||
<Attributes>
|
||||
<entry>
|
||||
<key>Owner</key>
|
||||
<value>123456789012</value>
|
||||
</entry>
|
||||
<entry>
|
||||
<key>Policy</key>
|
||||
<value>{"Version":"2008-10-17","Id":"us-east-1/698519295917/test__default_policy_ID","Statement" : [{"Effect":"Allow","Sid":"us-east-1/698519295917/test__default_statement_ID","Principal" : {"AWS": "*"},"Action":["SNS:GetTopicAttributes","SNS:SetTopicAttributes","SNS:AddPermission","SNS:RemovePermission","SNS:DeleteTopic","SNS:Subscribe","SNS:ListSubscriptionsByTopic","SNS:Publish","SNS:Receive"],"Resource":"arn:aws:sns:us-east-1:698519295917:test","Condition" : {"StringLike" : {"AWS:SourceArn": "arn:aws:*:*:698519295917:*"}}}]}</value>
|
||||
</entry>
|
||||
<entry>
|
||||
<key>TopicArn</key>
|
||||
<value>arn:aws:sns:us-east-1:123456789012:My-Topic</value>
|
||||
</entry>
|
||||
</Attributes>
|
||||
</GetTopicAttributesResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>057f074c-33a7-11df-9540-99d0768312d3</RequestId>
|
||||
</ResponseMetadata>
|
||||
</GetTopicAttributesResponse>
|
||||
`
|
||||
|
||||
var TestPublishXmlOK = `
|
||||
<PublishResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
|
||||
<PublishResult>
|
||||
<MessageId>94f20ce6-13c5-43a0-9a9e-ca52d816e90b</MessageId>
|
||||
</PublishResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>f187a3c1-376f-11df-8963-01868b7c937a</RequestId>
|
||||
</ResponseMetadata>
|
||||
</PublishResponse>
|
||||
`
|
||||
|
||||
var TestSetTopicAttributesXmlOK = `
|
||||
<SetTopicAttributesResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
|
||||
<ResponseMetadata>
|
||||
<RequestId>a8763b99-33a7-11df-a9b7-05d48da6f042</RequestId>
|
||||
</ResponseMetadata>
|
||||
</SetTopicAttributesResponse>
|
||||
`
|
||||
|
||||
var TestSubscribeXmlOK = `
|
||||
<SubscribeResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
|
||||
<SubscribeResult>
|
||||
<SubscriptionArn>pending confirmation</SubscriptionArn>
|
||||
</SubscribeResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>a169c740-3766-11df-8963-01868b7c937a</RequestId>
|
||||
</ResponseMetadata>
|
||||
</SubscribeResponse>
|
||||
`
|
||||
|
||||
var TestUnsubscribeXmlOK = `
|
||||
<UnsubscribeResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
|
||||
<ResponseMetadata>
|
||||
<RequestId>18e0ac39-3776-11df-84c0-b93cc1666b84</RequestId>
|
||||
</ResponseMetadata>
|
||||
</UnsubscribeResponse>
|
||||
`
|
||||
|
||||
var TestConfirmSubscriptionXmlOK = `
|
||||
<ConfirmSubscriptionResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
|
||||
<ConfirmSubscriptionResult>
|
||||
<SubscriptionArn>arn:aws:sns:us-east-1:123456789012:My-Topic:80289ba6-0fd4-4079-afb4-ce8c8260f0ca</SubscriptionArn>
|
||||
</ConfirmSubscriptionResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>7a50221f-3774-11df-a9b7-05d48da6f042</RequestId>
|
||||
</ResponseMetadata>
|
||||
</ConfirmSubscriptionResponse>
|
||||
`
|
||||
|
||||
var TestAddPermissionXmlOK = `
|
||||
<AddPermissionResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
|
||||
<ResponseMetadata>
|
||||
<RequestId>6a213e4e-33a8-11df-9540-99d0768312d3</RequestId>
|
||||
</ResponseMetadata>
|
||||
</AddPermissionResponse>
|
||||
`
|
||||
|
||||
var TestRemovePermissionXmlOK = `
|
||||
<RemovePermissionResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
|
||||
<ResponseMetadata>
|
||||
<RequestId>d170b150-33a8-11df-995a-2d6fbe836cc1</RequestId>
|
||||
</ResponseMetadata>
|
||||
</RemovePermissionResponse>
|
||||
`
|
||||
|
||||
var TestListSubscriptionsByTopicXmlOK = `
|
||||
<ListSubscriptionsByTopicResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
|
||||
<ListSubscriptionsByTopicResult>
|
||||
<Subscriptions>
|
||||
<member>
|
||||
<TopicArn>arn:aws:sns:us-east-1:123456789012:My-Topic</TopicArn>
|
||||
<Protocol>email</Protocol>
|
||||
<SubscriptionArn>arn:aws:sns:us-east-1:123456789012:My-Topic:80289ba6-0fd4-4079-afb4-ce8c8260f0ca</SubscriptionArn>
|
||||
<Owner>123456789012</Owner>
|
||||
<Endpoint>example@amazon.com</Endpoint>
|
||||
</member>
|
||||
</Subscriptions>
|
||||
</ListSubscriptionsByTopicResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>b9275252-3774-11df-9540-99d0768312d3</RequestId>
|
||||
</ResponseMetadata>
|
||||
</ListSubscriptionsByTopicResponse>
|
||||
`
|
||||
66
vendor/src/launchpad.net/goamz/exp/sns/sign.go
vendored
Normal file
66
vendor/src/launchpad.net/goamz/exp/sns/sign.go
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
package sns
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"launchpad.net/goamz/aws"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var b64 = base64.StdEncoding
|
||||
|
||||
/*
|
||||
func sign(auth aws.Auth, method, path string, params url.Values, headers http.Header) {
|
||||
var host string
|
||||
for k, v := range headers {
|
||||
k = strings.ToLower(k)
|
||||
switch k {
|
||||
case "host":
|
||||
host = v[0]
|
||||
}
|
||||
}
|
||||
|
||||
params["AWSAccessKeyId"] = []string{auth.AccessKey}
|
||||
params["SignatureVersion"] = []string{"2"}
|
||||
params["SignatureMethod"] = []string{"HmacSHA256"}
|
||||
|
||||
var sarry []string
|
||||
for k, v := range params {
|
||||
sarry = append(sarry, aws.Encode(k) + "=" + aws.Encode(v[0]))
|
||||
}
|
||||
|
||||
sort.StringSlice(sarry).Sort()
|
||||
joined := strings.Join(sarry, "&")
|
||||
|
||||
payload := strings.Join([]string{method, host, "/", joined}, "\n")
|
||||
hash := hmac.NewSHA256([]byte(auth.SecretKey))
|
||||
hash.Write([]byte(payload))
|
||||
signature := make([]byte, b64.EncodedLen(hash.Size()))
|
||||
b64.Encode(signature, hash.Sum())
|
||||
|
||||
params["Signature"] = []string{"AWS " + string(signature)}
|
||||
println("Payload:", payload)
|
||||
println("Signature:", strings.Join(params["Signature"], "|"))
|
||||
}*/
|
||||
|
||||
func sign(auth aws.Auth, method, path string, params map[string]string, host string) {
|
||||
params["AWSAccessKeyId"] = auth.AccessKey
|
||||
params["SignatureVersion"] = "2"
|
||||
params["SignatureMethod"] = "HmacSHA256"
|
||||
|
||||
var sarray []string
|
||||
for k, v := range params {
|
||||
sarray = append(sarray, aws.Encode(k)+"="+aws.Encode(v))
|
||||
}
|
||||
sort.StringSlice(sarray).Sort()
|
||||
joined := strings.Join(sarray, "&")
|
||||
payload := method + "\n" + host + "\n" + path + "\n" + joined
|
||||
hash := hmac.New(sha256.New, []byte(auth.SecretKey))
|
||||
hash.Write([]byte(payload))
|
||||
signature := make([]byte, b64.EncodedLen(hash.Size()))
|
||||
b64.Encode(signature, hash.Sum(nil))
|
||||
|
||||
params["Signature"] = string(signature)
|
||||
}
|
||||
444
vendor/src/launchpad.net/goamz/exp/sns/sns.go
vendored
Normal file
444
vendor/src/launchpad.net/goamz/exp/sns/sns.go
vendored
Normal file
@@ -0,0 +1,444 @@
|
||||
//
|
||||
// goamz - Go packages to interact with the Amazon Web Services.
|
||||
//
|
||||
// https://wiki.ubuntu.com/goamz
|
||||
//
|
||||
// Copyright (c) 2011 Memeo Inc.
|
||||
//
|
||||
// Written by Prudhvi Krishna Surapaneni <me@prudhvi.net>
|
||||
|
||||
// This package is in an experimental state, and does not currently
|
||||
// follow conventions and style of the rest of goamz or common
|
||||
// Go conventions. It must be polished before it's considered a
|
||||
// first-class package in goamz.
|
||||
package sns
|
||||
|
||||
// BUG(niemeyer): Package needs significant clean up.
|
||||
|
||||
// BUG(niemeyer): Topic values in responses are not being initialized
|
||||
// properly, since they're supposed to reference *SNS.
|
||||
|
||||
// BUG(niemeyer): Package needs documentation.
|
||||
|
||||
// BUG(niemeyer): Message.Message should be "Payload []byte"
|
||||
|
||||
// BUG(niemeyer): Message.SNS must be dropped.
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"launchpad.net/goamz/aws"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// The SNS type encapsulates operation with an SNS region.
|
||||
type SNS struct {
|
||||
aws.Auth
|
||||
aws.Region
|
||||
private byte // Reserve the right of using private data.
|
||||
}
|
||||
|
||||
type Topic struct {
|
||||
SNS *SNS
|
||||
TopicArn string
|
||||
}
|
||||
|
||||
func New(auth aws.Auth, region aws.Region) *SNS {
|
||||
return &SNS{auth, region, 0}
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
SNS *SNS
|
||||
Topic *Topic
|
||||
Message [8192]byte
|
||||
Subject string
|
||||
}
|
||||
|
||||
type Subscription struct {
|
||||
Endpoint string
|
||||
Owner string
|
||||
Protocol string
|
||||
SubscriptionArn string
|
||||
TopicArn string
|
||||
}
|
||||
|
||||
func (topic *Topic) Message(message [8192]byte, subject string) *Message {
|
||||
return &Message{topic.SNS, topic, message, subject}
|
||||
}
|
||||
|
||||
type ResponseMetadata struct {
|
||||
RequestId string `xml:"ResponseMetadata>RequestId"`
|
||||
BoxUsage float64 `xml:"ResponseMetadata>BoxUsage"`
|
||||
}
|
||||
|
||||
type ListTopicsResp struct {
|
||||
Topics []Topic `xml:"ListTopicsResult>Topics>member"`
|
||||
NextToken string
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
type CreateTopicResp struct {
|
||||
Topic Topic `xml:"CreateTopicResult"`
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
type DeleteTopicResp struct {
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
type ListSubscriptionsResp struct {
|
||||
Subscriptions []Subscription `xml:"ListSubscriptionsResult>Subscriptions>member"`
|
||||
NextToken string
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
type AttributeEntry struct {
|
||||
Key string `xml:"key"`
|
||||
Value string `xml:"value"`
|
||||
}
|
||||
|
||||
type GetTopicAttributesResp struct {
|
||||
Attributes []AttributeEntry `xml:"GetTopicAttributesResult>Attributes>entry"`
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
func makeParams(action string) map[string]string {
|
||||
params := make(map[string]string)
|
||||
params["Action"] = action
|
||||
return params
|
||||
}
|
||||
|
||||
// ListTopics
|
||||
//
|
||||
// See http://goo.gl/lfrMK for more details.
|
||||
func (sns *SNS) ListTopics(NextToken *string) (resp *ListTopicsResp, err error) {
|
||||
resp = &ListTopicsResp{}
|
||||
params := makeParams("ListTopics")
|
||||
if NextToken != nil {
|
||||
params["NextToken"] = *NextToken
|
||||
}
|
||||
err = sns.query(nil, nil, params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// CreateTopic
|
||||
//
|
||||
// See http://goo.gl/m9aAt for more details.
|
||||
func (sns *SNS) CreateTopic(Name string) (resp *CreateTopicResp, err error) {
|
||||
resp = &CreateTopicResp{}
|
||||
params := makeParams("CreateTopic")
|
||||
params["Name"] = Name
|
||||
err = sns.query(nil, nil, params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteTopic
|
||||
//
|
||||
// See http://goo.gl/OXNcY for more details.
|
||||
func (sns *SNS) DeleteTopic(topic Topic) (resp *DeleteTopicResp, err error) {
|
||||
resp = &DeleteTopicResp{}
|
||||
params := makeParams("DeleteTopic")
|
||||
params["TopicArn"] = topic.TopicArn
|
||||
err = sns.query(nil, nil, params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete
|
||||
//
|
||||
// Helper function for deleting a topic
|
||||
func (topic *Topic) Delete() (resp *DeleteTopicResp, err error) {
|
||||
return topic.SNS.DeleteTopic(*topic)
|
||||
}
|
||||
|
||||
// ListSubscriptions
|
||||
//
|
||||
// See http://goo.gl/k3aGn for more details.
|
||||
func (sns *SNS) ListSubscriptions(NextToken *string) (resp *ListSubscriptionsResp, err error) {
|
||||
resp = &ListSubscriptionsResp{}
|
||||
params := makeParams("ListSubscriptions")
|
||||
if NextToken != nil {
|
||||
params["NextToken"] = *NextToken
|
||||
}
|
||||
err = sns.query(nil, nil, params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// GetTopicAttributes
|
||||
//
|
||||
// See http://goo.gl/WXRoX for more details.
|
||||
func (sns *SNS) GetTopicAttributes(TopicArn string) (resp *GetTopicAttributesResp, err error) {
|
||||
resp = &GetTopicAttributesResp{}
|
||||
params := makeParams("GetTopicAttributes")
|
||||
params["TopicArn"] = TopicArn
|
||||
err = sns.query(nil, nil, params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
type PublishOpt struct {
|
||||
Message string
|
||||
MessageStructure string
|
||||
Subject string
|
||||
TopicArn string
|
||||
}
|
||||
|
||||
type PublishResp struct {
|
||||
MessageId string `xml:"PublishResult>MessageId"`
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
// Publish
|
||||
//
|
||||
// See http://goo.gl/AY2D8 for more details.
|
||||
func (sns *SNS) Publish(options *PublishOpt) (resp *PublishResp, err error) {
|
||||
resp = &PublishResp{}
|
||||
params := makeParams("Publish")
|
||||
|
||||
if options.Subject != "" {
|
||||
params["Subject"] = options.Subject
|
||||
}
|
||||
|
||||
if options.MessageStructure != "" {
|
||||
params["MessageStructure"] = options.MessageStructure
|
||||
}
|
||||
|
||||
if options.Message != "" {
|
||||
params["Message"] = options.Message
|
||||
}
|
||||
|
||||
if options.TopicArn != "" {
|
||||
params["TopicArn"] = options.TopicArn
|
||||
}
|
||||
|
||||
err = sns.query(nil, nil, params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
type SetTopicAttributesResponse struct {
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
// SetTopicAttributes
|
||||
//
|
||||
// See http://goo.gl/oVYW7 for more details.
|
||||
func (sns *SNS) SetTopicAttributes(AttributeName, AttributeValue, TopicArn string) (resp *SetTopicAttributesResponse, err error) {
|
||||
resp = &SetTopicAttributesResponse{}
|
||||
params := makeParams("SetTopicAttributes")
|
||||
|
||||
if AttributeName == "" || TopicArn == "" {
|
||||
return nil, errors.New("Invalid Attribute Name or TopicArn")
|
||||
}
|
||||
|
||||
params["AttributeName"] = AttributeName
|
||||
params["AttributeValue"] = AttributeValue
|
||||
params["TopicArn"] = TopicArn
|
||||
|
||||
err = sns.query(nil, nil, params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
type SubscribeResponse struct {
|
||||
SubscriptionArn string `xml:"SubscribeResult>SubscriptionArn"`
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
// Subscribe
|
||||
//
|
||||
// See http://goo.gl/c3iGS for more details.
|
||||
func (sns *SNS) Subscribe(Endpoint, Protocol, TopicArn string) (resp *SubscribeResponse, err error) {
|
||||
resp = &SubscribeResponse{}
|
||||
params := makeParams("Subscribe")
|
||||
|
||||
params["Endpoint"] = Endpoint
|
||||
params["Protocol"] = Protocol
|
||||
params["TopicArn"] = TopicArn
|
||||
|
||||
err = sns.query(nil, nil, params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
type UnsubscribeResponse struct {
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
// Unsubscribe
|
||||
//
|
||||
// See http://goo.gl/4l5Ge for more details.
|
||||
func (sns *SNS) Unsubscribe(SubscriptionArn string) (resp *UnsubscribeResponse, err error) {
|
||||
resp = &UnsubscribeResponse{}
|
||||
params := makeParams("Unsubscribe")
|
||||
|
||||
params["SubscriptionArn"] = SubscriptionArn
|
||||
|
||||
err = sns.query(nil, nil, params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
type ConfirmSubscriptionResponse struct {
|
||||
SubscriptionArn string `xml:"ConfirmSubscriptionResult>SubscriptionArn"`
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
type ConfirmSubscriptionOpt struct {
|
||||
AuthenticateOnUnsubscribe string
|
||||
Token string
|
||||
TopicArn string
|
||||
}
|
||||
|
||||
// ConfirmSubscription
|
||||
//
|
||||
// See http://goo.gl/3hXzH for more details.
|
||||
func (sns *SNS) ConfirmSubscription(options *ConfirmSubscriptionOpt) (resp *ConfirmSubscriptionResponse, err error) {
|
||||
resp = &ConfirmSubscriptionResponse{}
|
||||
params := makeParams("ConfirmSubscription")
|
||||
|
||||
if options.AuthenticateOnUnsubscribe != "" {
|
||||
params["AuthenticateOnUnsubscribe"] = options.AuthenticateOnUnsubscribe
|
||||
}
|
||||
|
||||
params["Token"] = options.Token
|
||||
params["TopicArn"] = options.TopicArn
|
||||
|
||||
err = sns.query(nil, nil, params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
type Permission struct {
|
||||
ActionName string
|
||||
AccountId string
|
||||
}
|
||||
|
||||
type AddPermissionResponse struct {
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
// AddPermission
|
||||
//
|
||||
// See http://goo.gl/mbY4a for more details.
|
||||
func (sns *SNS) AddPermission(permissions []Permission, Label, TopicArn string) (resp *AddPermissionResponse, err error) {
|
||||
resp = &AddPermissionResponse{}
|
||||
params := makeParams("AddPermission")
|
||||
|
||||
for i, p := range permissions {
|
||||
params["AWSAccountId.member."+strconv.Itoa(i+1)] = p.AccountId
|
||||
params["ActionName.member."+strconv.Itoa(i+1)] = p.ActionName
|
||||
}
|
||||
|
||||
params["Label"] = Label
|
||||
params["TopicArn"] = TopicArn
|
||||
|
||||
err = sns.query(nil, nil, params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
type RemovePermissionResponse struct {
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
// RemovePermission
|
||||
//
|
||||
// See http://goo.gl/wGl5j for more details.
|
||||
func (sns *SNS) RemovePermission(Label, TopicArn string) (resp *RemovePermissionResponse, err error) {
|
||||
resp = &RemovePermissionResponse{}
|
||||
params := makeParams("RemovePermission")
|
||||
|
||||
params["Label"] = Label
|
||||
params["TopicArn"] = TopicArn
|
||||
|
||||
err = sns.query(nil, nil, params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
type ListSubscriptionByTopicResponse struct {
|
||||
Subscriptions []Subscription `xml:"ListSubscriptionsByTopicResult>Subscriptions>member"`
|
||||
ResponseMetadata
|
||||
}
|
||||
|
||||
type ListSubscriptionByTopicOpt struct {
|
||||
NextToken string
|
||||
TopicArn string
|
||||
}
|
||||
|
||||
// ListSubscriptionByTopic
|
||||
//
|
||||
// See http://goo.gl/LaVcC for more details.
|
||||
func (sns *SNS) ListSubscriptionByTopic(options *ListSubscriptionByTopicOpt) (resp *ListSubscriptionByTopicResponse, err error) {
|
||||
resp = &ListSubscriptionByTopicResponse{}
|
||||
params := makeParams("ListSbubscriptionByTopic")
|
||||
|
||||
if options.NextToken != "" {
|
||||
params["NextToken"] = options.NextToken
|
||||
}
|
||||
|
||||
params["TopicArn"] = options.TopicArn
|
||||
|
||||
err = sns.query(nil, nil, params, resp)
|
||||
return
|
||||
}
|
||||
|
||||
type Error struct {
|
||||
StatusCode int
|
||||
Code string
|
||||
Message string
|
||||
RequestId string
|
||||
}
|
||||
|
||||
func (err *Error) Error() string {
|
||||
return err.Message
|
||||
}
|
||||
|
||||
type xmlErrors struct {
|
||||
RequestId string
|
||||
Errors []Error `xml:"Errors>Error"`
|
||||
}
|
||||
|
||||
func (sns *SNS) query(topic *Topic, message *Message, params map[string]string, resp interface{}) error {
|
||||
params["Timestamp"] = time.Now().UTC().Format(time.RFC3339)
|
||||
u, err := url.Parse(sns.Region.SNSEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sign(sns.Auth, "GET", "/", params, u.Host)
|
||||
u.RawQuery = multimap(params).Encode()
|
||||
r, err := http.Get(u.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
//dump, _ := http.DumpResponse(r, true)
|
||||
//println("DUMP:\n", string(dump))
|
||||
//return nil
|
||||
|
||||
if r.StatusCode != 200 {
|
||||
return buildError(r)
|
||||
}
|
||||
err = xml.NewDecoder(r.Body).Decode(resp)
|
||||
return err
|
||||
}
|
||||
|
||||
func buildError(r *http.Response) error {
|
||||
errors := xmlErrors{}
|
||||
xml.NewDecoder(r.Body).Decode(&errors)
|
||||
var err Error
|
||||
if len(errors.Errors) > 0 {
|
||||
err = errors.Errors[0]
|
||||
}
|
||||
err.RequestId = errors.RequestId
|
||||
err.StatusCode = r.StatusCode
|
||||
if err.Message == "" {
|
||||
err.Message = r.Status
|
||||
}
|
||||
return &err
|
||||
}
|
||||
|
||||
func multimap(p map[string]string) url.Values {
|
||||
q := make(url.Values, len(p))
|
||||
for k, v := range p {
|
||||
q[k] = []string{v}
|
||||
}
|
||||
return q
|
||||
}
|
||||
245
vendor/src/launchpad.net/goamz/exp/sns/sns_test.go
vendored
Normal file
245
vendor/src/launchpad.net/goamz/exp/sns/sns_test.go
vendored
Normal file
@@ -0,0 +1,245 @@
|
||||
package sns_test
|
||||
|
||||
import (
|
||||
"launchpad.net/goamz/aws"
|
||||
"launchpad.net/goamz/exp/sns"
|
||||
"launchpad.net/goamz/testutil"
|
||||
. "launchpad.net/gocheck"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
TestingT(t)
|
||||
}
|
||||
|
||||
var _ = Suite(&S{})
|
||||
|
||||
type S struct {
|
||||
sns *sns.SNS
|
||||
}
|
||||
|
||||
var testServer = testutil.NewHTTPServer()
|
||||
|
||||
func (s *S) SetUpSuite(c *C) {
|
||||
testServer.Start()
|
||||
auth := aws.Auth{"abc", "123"}
|
||||
s.sns = sns.New(auth, aws.Region{SNSEndpoint: testServer.URL})
|
||||
}
|
||||
|
||||
func (s *S) TearDownSuite(c *C) {
|
||||
testServer.Stop()
|
||||
}
|
||||
|
||||
func (s *S) TearDownTest(c *C) {
|
||||
testServer.Flush()
|
||||
}
|
||||
|
||||
func (s *S) TestListTopicsOK(c *C) {
|
||||
testServer.Response(200, nil, TestListTopicsXmlOK)
|
||||
|
||||
resp, err := s.sns.ListTopics(nil)
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
|
||||
c.Assert(resp.ResponseMetadata.RequestId, Equals, "bd10b26c-e30e-11e0-ba29-93c3aca2f103")
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *S) TestCreateTopic(c *C) {
|
||||
testServer.Response(200, nil, TestCreateTopicXmlOK)
|
||||
|
||||
resp, err := s.sns.CreateTopic("My-Topic")
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
|
||||
c.Assert(resp.Topic.TopicArn, Equals, "arn:aws:sns:us-east-1:123456789012:My-Topic")
|
||||
c.Assert(resp.ResponseMetadata.RequestId, Equals, "a8dec8b3-33a4-11df-8963-01868b7c937a")
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *S) TestDeleteTopic(c *C) {
|
||||
testServer.Response(200, nil, TestDeleteTopicXmlOK)
|
||||
|
||||
t := sns.Topic{nil, "arn:aws:sns:us-east-1:123456789012:My-Topic"}
|
||||
resp, err := s.sns.DeleteTopic(t)
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
|
||||
c.Assert(resp.ResponseMetadata.RequestId, Equals, "f3aa9ac9-3c3d-11df-8235-9dab105e9c32")
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *S) TestListSubscriptions(c *C) {
|
||||
testServer.Response(200, nil, TestListSubscriptionsXmlOK)
|
||||
|
||||
resp, err := s.sns.ListSubscriptions(nil)
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
|
||||
c.Assert(len(resp.Subscriptions), Not(Equals), 0)
|
||||
c.Assert(resp.Subscriptions[0].Protocol, Equals, "email")
|
||||
c.Assert(resp.Subscriptions[0].Endpoint, Equals, "example@amazon.com")
|
||||
c.Assert(resp.Subscriptions[0].SubscriptionArn, Equals, "arn:aws:sns:us-east-1:123456789012:My-Topic:80289ba6-0fd4-4079-afb4-ce8c8260f0ca")
|
||||
c.Assert(resp.Subscriptions[0].TopicArn, Equals, "arn:aws:sns:us-east-1:698519295917:My-Topic")
|
||||
c.Assert(resp.Subscriptions[0].Owner, Equals, "123456789012")
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *S) TestGetTopicAttributes(c *C) {
|
||||
testServer.Response(200, nil, TestGetTopicAttributesXmlOK)
|
||||
|
||||
resp, err := s.sns.GetTopicAttributes("arn:aws:sns:us-east-1:123456789012:My-Topic")
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
|
||||
c.Assert(len(resp.Attributes), Not(Equals), 0)
|
||||
c.Assert(resp.Attributes[0].Key, Equals, "Owner")
|
||||
c.Assert(resp.Attributes[0].Value, Equals, "123456789012")
|
||||
c.Assert(resp.Attributes[1].Key, Equals, "Policy")
|
||||
c.Assert(resp.Attributes[1].Value, Equals, `{"Version":"2008-10-17","Id":"us-east-1/698519295917/test__default_policy_ID","Statement" : [{"Effect":"Allow","Sid":"us-east-1/698519295917/test__default_statement_ID","Principal" : {"AWS": "*"},"Action":["SNS:GetTopicAttributes","SNS:SetTopicAttributes","SNS:AddPermission","SNS:RemovePermission","SNS:DeleteTopic","SNS:Subscribe","SNS:ListSubscriptionsByTopic","SNS:Publish","SNS:Receive"],"Resource":"arn:aws:sns:us-east-1:698519295917:test","Condition" : {"StringLike" : {"AWS:SourceArn": "arn:aws:*:*:698519295917:*"}}}]}`)
|
||||
c.Assert(resp.ResponseMetadata.RequestId, Equals, "057f074c-33a7-11df-9540-99d0768312d3")
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *S) TestPublish(c *C) {
|
||||
testServer.Response(200, nil, TestPublishXmlOK)
|
||||
|
||||
pubOpt := &sns.PublishOpt{"foobar", "", "subject", "arn:aws:sns:us-east-1:123456789012:My-Topic"}
|
||||
resp, err := s.sns.Publish(pubOpt)
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
|
||||
c.Assert(resp.MessageId, Equals, "94f20ce6-13c5-43a0-9a9e-ca52d816e90b")
|
||||
c.Assert(resp.ResponseMetadata.RequestId, Equals, "f187a3c1-376f-11df-8963-01868b7c937a")
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *S) TestSetTopicAttributes(c *C) {
|
||||
testServer.Response(200, nil, TestSetTopicAttributesXmlOK)
|
||||
|
||||
resp, err := s.sns.SetTopicAttributes("DisplayName", "MyTopicName", "arn:aws:sns:us-east-1:123456789012:My-Topic")
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
|
||||
c.Assert(resp.ResponseMetadata.RequestId, Equals, "a8763b99-33a7-11df-a9b7-05d48da6f042")
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *S) TestSubscribe(c *C) {
|
||||
testServer.Response(200, nil, TestSubscribeXmlOK)
|
||||
|
||||
resp, err := s.sns.Subscribe("example@amazon.com", "email", "arn:aws:sns:us-east-1:123456789012:My-Topic")
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
|
||||
c.Assert(resp.SubscriptionArn, Equals, "pending confirmation")
|
||||
c.Assert(resp.ResponseMetadata.RequestId, Equals, "a169c740-3766-11df-8963-01868b7c937a")
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *S) TestUnsubscribe(c *C) {
|
||||
testServer.Response(200, nil, TestUnsubscribeXmlOK)
|
||||
|
||||
resp, err := s.sns.Unsubscribe("arn:aws:sns:us-east-1:123456789012:My-Topic:a169c740-3766-11df-8963-01868b7c937a")
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
|
||||
c.Assert(resp.ResponseMetadata.RequestId, Equals, "18e0ac39-3776-11df-84c0-b93cc1666b84")
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *S) TestConfirmSubscription(c *C) {
|
||||
testServer.Response(200, nil, TestConfirmSubscriptionXmlOK)
|
||||
|
||||
opt := &sns.ConfirmSubscriptionOpt{"", "51b2ff3edb475b7d91550e0ab6edf0c1de2a34e6ebaf6", "arn:aws:sns:us-east-1:123456789012:My-Topic"}
|
||||
resp, err := s.sns.ConfirmSubscription(opt)
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
|
||||
c.Assert(resp.SubscriptionArn, Equals, "arn:aws:sns:us-east-1:123456789012:My-Topic:80289ba6-0fd4-4079-afb4-ce8c8260f0ca")
|
||||
c.Assert(resp.ResponseMetadata.RequestId, Equals, "7a50221f-3774-11df-a9b7-05d48da6f042")
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *S) TestAddPermission(c *C) {
|
||||
testServer.Response(200, nil, TestAddPermissionXmlOK)
|
||||
perm := make([]sns.Permission, 2)
|
||||
perm[0].ActionName = "Publish"
|
||||
perm[1].ActionName = "GetTopicAttributes"
|
||||
perm[0].AccountId = "987654321000"
|
||||
perm[1].AccountId = "876543210000"
|
||||
|
||||
resp, err := s.sns.AddPermission(perm, "NewPermission", "arn:aws:sns:us-east-1:123456789012:My-Topic")
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
|
||||
c.Assert(resp.RequestId, Equals, "6a213e4e-33a8-11df-9540-99d0768312d3")
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *S) TestRemovePermission(c *C) {
|
||||
testServer.Response(200, nil, TestRemovePermissionXmlOK)
|
||||
|
||||
resp, err := s.sns.RemovePermission("NewPermission", "arn:aws:sns:us-east-1:123456789012:My-Topic")
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
|
||||
c.Assert(resp.RequestId, Equals, "d170b150-33a8-11df-995a-2d6fbe836cc1")
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *S) TestListSubscriptionByTopic(c *C) {
|
||||
testServer.Response(200, nil, TestListSubscriptionsByTopicXmlOK)
|
||||
|
||||
opt := &sns.ListSubscriptionByTopicOpt{"", "arn:aws:sns:us-east-1:123456789012:My-Topic"}
|
||||
resp, err := s.sns.ListSubscriptionByTopic(opt)
|
||||
req := testServer.WaitRequest()
|
||||
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
|
||||
c.Assert(len(resp.Subscriptions), Not(Equals), 0)
|
||||
c.Assert(resp.Subscriptions[0].TopicArn, Equals, "arn:aws:sns:us-east-1:123456789012:My-Topic")
|
||||
c.Assert(resp.Subscriptions[0].SubscriptionArn, Equals, "arn:aws:sns:us-east-1:123456789012:My-Topic:80289ba6-0fd4-4079-afb4-ce8c8260f0ca")
|
||||
c.Assert(resp.Subscriptions[0].Owner, Equals, "123456789012")
|
||||
c.Assert(resp.Subscriptions[0].Endpoint, Equals, "example@amazon.com")
|
||||
c.Assert(resp.Subscriptions[0].Protocol, Equals, "email")
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
432
vendor/src/launchpad.net/goamz/iam/iam.go
vendored
Normal file
432
vendor/src/launchpad.net/goamz/iam/iam.go
vendored
Normal file
@@ -0,0 +1,432 @@
|
||||
// The iam package provides types and functions for interaction with the AWS
|
||||
// Identity and Access Management (IAM) service.
|
||||
package iam
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"launchpad.net/goamz/aws"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// The IAM type encapsulates operations operations with the IAM endpoint.
|
||||
type IAM struct {
|
||||
aws.Auth
|
||||
aws.Region
|
||||
}
|
||||
|
||||
// New creates a new IAM instance.
|
||||
func New(auth aws.Auth, region aws.Region) *IAM {
|
||||
return &IAM{auth, region}
|
||||
}
|
||||
|
||||
func (iam *IAM) query(params map[string]string, resp interface{}) error {
|
||||
params["Version"] = "2010-05-08"
|
||||
params["Timestamp"] = time.Now().In(time.UTC).Format(time.RFC3339)
|
||||
endpoint, err := url.Parse(iam.IAMEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sign(iam.Auth, "GET", "/", params, endpoint.Host)
|
||||
endpoint.RawQuery = multimap(params).Encode()
|
||||
r, err := http.Get(endpoint.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Body.Close()
|
||||
if r.StatusCode > 200 {
|
||||
return buildError(r)
|
||||
}
|
||||
return xml.NewDecoder(r.Body).Decode(resp)
|
||||
}
|
||||
|
||||
func (iam *IAM) postQuery(params map[string]string, resp interface{}) error {
|
||||
endpoint, err := url.Parse(iam.IAMEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
params["Version"] = "2010-05-08"
|
||||
params["Timestamp"] = time.Now().In(time.UTC).Format(time.RFC3339)
|
||||
sign(iam.Auth, "POST", "/", params, endpoint.Host)
|
||||
encoded := multimap(params).Encode()
|
||||
body := strings.NewReader(encoded)
|
||||
req, err := http.NewRequest("POST", endpoint.String(), body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Host", endpoint.Host)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("Content-Length", strconv.Itoa(len(encoded)))
|
||||
r, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Body.Close()
|
||||
if r.StatusCode > 200 {
|
||||
return buildError(r)
|
||||
}
|
||||
return xml.NewDecoder(r.Body).Decode(resp)
|
||||
}
|
||||
|
||||
func buildError(r *http.Response) error {
|
||||
var (
|
||||
err Error
|
||||
errors xmlErrors
|
||||
)
|
||||
xml.NewDecoder(r.Body).Decode(&errors)
|
||||
if len(errors.Errors) > 0 {
|
||||
err = errors.Errors[0]
|
||||
}
|
||||
err.StatusCode = r.StatusCode
|
||||
if err.Message == "" {
|
||||
err.Message = r.Status
|
||||
}
|
||||
return &err
|
||||
}
|
||||
|
||||
func multimap(p map[string]string) url.Values {
|
||||
q := make(url.Values, len(p))
|
||||
for k, v := range p {
|
||||
q[k] = []string{v}
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
// Response to a CreateUser request.
|
||||
//
|
||||
// See http://goo.gl/JS9Gz for more details.
|
||||
type CreateUserResp struct {
|
||||
RequestId string `xml:"ResponseMetadata>RequestId"`
|
||||
User User `xml:"CreateUserResult>User"`
|
||||
}
|
||||
|
||||
// User encapsulates a user managed by IAM.
|
||||
//
|
||||
// See http://goo.gl/BwIQ3 for more details.
|
||||
type User struct {
|
||||
Arn string
|
||||
Path string
|
||||
Id string `xml:"UserId"`
|
||||
Name string `xml:"UserName"`
|
||||
}
|
||||
|
||||
// CreateUser creates a new user in IAM.
|
||||
//
|
||||
// See http://goo.gl/JS9Gz for more details.
|
||||
func (iam *IAM) CreateUser(name, path string) (*CreateUserResp, error) {
|
||||
params := map[string]string{
|
||||
"Action": "CreateUser",
|
||||
"Path": path,
|
||||
"UserName": name,
|
||||
}
|
||||
resp := new(CreateUserResp)
|
||||
if err := iam.query(params, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Response for GetUser requests.
|
||||
//
|
||||
// See http://goo.gl/ZnzRN for more details.
|
||||
type GetUserResp struct {
|
||||
RequestId string `xml:"ResponseMetadata>RequestId"`
|
||||
User User `xml:"GetUserResult>User"`
|
||||
}
|
||||
|
||||
// GetUser gets a user from IAM.
|
||||
//
|
||||
// See http://goo.gl/ZnzRN for more details.
|
||||
func (iam *IAM) GetUser(name string) (*GetUserResp, error) {
|
||||
params := map[string]string{
|
||||
"Action": "GetUser",
|
||||
"UserName": name,
|
||||
}
|
||||
resp := new(GetUserResp)
|
||||
if err := iam.query(params, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// DeleteUser deletes a user from IAM.
|
||||
//
|
||||
// See http://goo.gl/jBuCG for more details.
|
||||
func (iam *IAM) DeleteUser(name string) (*SimpleResp, error) {
|
||||
params := map[string]string{
|
||||
"Action": "DeleteUser",
|
||||
"UserName": name,
|
||||
}
|
||||
resp := new(SimpleResp)
|
||||
if err := iam.query(params, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Response to a CreateGroup request.
|
||||
//
|
||||
// See http://goo.gl/n7NNQ for more details.
|
||||
type CreateGroupResp struct {
|
||||
Group Group `xml:"CreateGroupResult>Group"`
|
||||
RequestId string `xml:"ResponseMetadata>RequestId"`
|
||||
}
|
||||
|
||||
// Group encapsulates a group managed by IAM.
|
||||
//
|
||||
// See http://goo.gl/ae7Vs for more details.
|
||||
type Group struct {
|
||||
Arn string
|
||||
Id string `xml:"GroupId"`
|
||||
Name string `xml:"GroupName"`
|
||||
Path string
|
||||
}
|
||||
|
||||
// CreateGroup creates a new group in IAM.
|
||||
//
|
||||
// The path parameter can be used to identify which division or part of the
|
||||
// organization the user belongs to.
|
||||
//
|
||||
// If path is unset ("") it defaults to "/".
|
||||
//
|
||||
// See http://goo.gl/n7NNQ for more details.
|
||||
func (iam *IAM) CreateGroup(name string, path string) (*CreateGroupResp, error) {
|
||||
params := map[string]string{
|
||||
"Action": "CreateGroup",
|
||||
"GroupName": name,
|
||||
}
|
||||
if path != "" {
|
||||
params["Path"] = path
|
||||
}
|
||||
resp := new(CreateGroupResp)
|
||||
if err := iam.query(params, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Response to a ListGroups request.
|
||||
//
|
||||
// See http://goo.gl/W2TRj for more details.
|
||||
type GroupsResp struct {
|
||||
Groups []Group `xml:"ListGroupsResult>Groups>member"`
|
||||
RequestId string `xml:"ResponseMetadata>RequestId"`
|
||||
}
|
||||
|
||||
// Groups list the groups that have the specified path prefix.
|
||||
//
|
||||
// The parameter pathPrefix is optional. If pathPrefix is "", all groups are
|
||||
// returned.
|
||||
//
|
||||
// See http://goo.gl/W2TRj for more details.
|
||||
func (iam *IAM) Groups(pathPrefix string) (*GroupsResp, error) {
|
||||
params := map[string]string{
|
||||
"Action": "ListGroups",
|
||||
}
|
||||
if pathPrefix != "" {
|
||||
params["PathPrefix"] = pathPrefix
|
||||
}
|
||||
resp := new(GroupsResp)
|
||||
if err := iam.query(params, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// DeleteGroup deletes a group from IAM.
|
||||
//
|
||||
// See http://goo.gl/d5i2i for more details.
|
||||
func (iam *IAM) DeleteGroup(name string) (*SimpleResp, error) {
|
||||
params := map[string]string{
|
||||
"Action": "DeleteGroup",
|
||||
"GroupName": name,
|
||||
}
|
||||
resp := new(SimpleResp)
|
||||
if err := iam.query(params, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Response to a CreateAccessKey request.
|
||||
//
|
||||
// See http://goo.gl/L46Py for more details.
|
||||
type CreateAccessKeyResp struct {
|
||||
RequestId string `xml:"ResponseMetadata>RequestId"`
|
||||
AccessKey AccessKey `xml:"CreateAccessKeyResult>AccessKey"`
|
||||
}
|
||||
|
||||
// AccessKey encapsulates an access key generated for a user.
|
||||
//
|
||||
// See http://goo.gl/LHgZR for more details.
|
||||
type AccessKey struct {
|
||||
UserName string
|
||||
Id string `xml:"AccessKeyId"`
|
||||
Secret string `xml:"SecretAccessKey,omitempty"`
|
||||
Status string
|
||||
}
|
||||
|
||||
// CreateAccessKey creates a new access key in IAM.
|
||||
//
|
||||
// See http://goo.gl/L46Py for more details.
|
||||
func (iam *IAM) CreateAccessKey(userName string) (*CreateAccessKeyResp, error) {
|
||||
params := map[string]string{
|
||||
"Action": "CreateAccessKey",
|
||||
"UserName": userName,
|
||||
}
|
||||
resp := new(CreateAccessKeyResp)
|
||||
if err := iam.query(params, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Response to AccessKeys request.
|
||||
//
|
||||
// See http://goo.gl/Vjozx for more details.
|
||||
type AccessKeysResp struct {
|
||||
RequestId string `xml:"ResponseMetadata>RequestId"`
|
||||
AccessKeys []AccessKey `xml:"ListAccessKeysResult>AccessKeyMetadata>member"`
|
||||
}
|
||||
|
||||
// AccessKeys lists all acccess keys associated with a user.
|
||||
//
|
||||
// The userName parameter is optional. If set to "", the userName is determined
|
||||
// implicitly based on the AWS Access Key ID used to sign the request.
|
||||
//
|
||||
// See http://goo.gl/Vjozx for more details.
|
||||
func (iam *IAM) AccessKeys(userName string) (*AccessKeysResp, error) {
|
||||
params := map[string]string{
|
||||
"Action": "ListAccessKeys",
|
||||
}
|
||||
if userName != "" {
|
||||
params["UserName"] = userName
|
||||
}
|
||||
resp := new(AccessKeysResp)
|
||||
if err := iam.query(params, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// DeleteAccessKey deletes an access key from IAM.
|
||||
//
|
||||
// The userName parameter is optional. If set to "", the userName is determined
|
||||
// implicitly based on the AWS Access Key ID used to sign the request.
|
||||
//
|
||||
// See http://goo.gl/hPGhw for more details.
|
||||
func (iam *IAM) DeleteAccessKey(id, userName string) (*SimpleResp, error) {
|
||||
params := map[string]string{
|
||||
"Action": "DeleteAccessKey",
|
||||
"AccessKeyId": id,
|
||||
}
|
||||
if userName != "" {
|
||||
params["UserName"] = userName
|
||||
}
|
||||
resp := new(SimpleResp)
|
||||
if err := iam.query(params, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Response to a GetUserPolicy request.
|
||||
//
|
||||
// See http://goo.gl/BH04O for more details.
|
||||
type GetUserPolicyResp struct {
|
||||
Policy UserPolicy `xml:"GetUserPolicyResult"`
|
||||
RequestId string `xml:"ResponseMetadata>RequestId"`
|
||||
}
|
||||
|
||||
// UserPolicy encapsulates an IAM group policy.
|
||||
//
|
||||
// See http://goo.gl/C7hgS for more details.
|
||||
type UserPolicy struct {
|
||||
Name string `xml:"PolicyName"`
|
||||
UserName string `xml:"UserName"`
|
||||
Document string `xml:"PolicyDocument"`
|
||||
}
|
||||
|
||||
// GetUserPolicy gets a user policy in IAM.
|
||||
//
|
||||
// See http://goo.gl/BH04O for more details.
|
||||
func (iam *IAM) GetUserPolicy(userName, policyName string) (*GetUserPolicyResp, error) {
|
||||
params := map[string]string{
|
||||
"Action": "GetUserPolicy",
|
||||
"UserName": userName,
|
||||
"PolicyName": policyName,
|
||||
}
|
||||
resp := new(GetUserPolicyResp)
|
||||
if err := iam.query(params, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// PutUserPolicy creates a user policy in IAM.
|
||||
//
|
||||
// See http://goo.gl/ldCO8 for more details.
|
||||
func (iam *IAM) PutUserPolicy(userName, policyName, policyDocument string) (*SimpleResp, error) {
|
||||
params := map[string]string{
|
||||
"Action": "PutUserPolicy",
|
||||
"UserName": userName,
|
||||
"PolicyName": policyName,
|
||||
"PolicyDocument": policyDocument,
|
||||
}
|
||||
resp := new(SimpleResp)
|
||||
if err := iam.postQuery(params, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// DeleteUserPolicy deletes a user policy from IAM.
|
||||
//
|
||||
// See http://goo.gl/7Jncn for more details.
|
||||
func (iam *IAM) DeleteUserPolicy(userName, policyName string) (*SimpleResp, error) {
|
||||
params := map[string]string{
|
||||
"Action": "DeleteUserPolicy",
|
||||
"PolicyName": policyName,
|
||||
"UserName": userName,
|
||||
}
|
||||
resp := new(SimpleResp)
|
||||
if err := iam.query(params, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type SimpleResp struct {
|
||||
RequestId string `xml:"ResponseMetadata>RequestId"`
|
||||
}
|
||||
|
||||
type xmlErrors struct {
|
||||
Errors []Error `xml:"Error"`
|
||||
}
|
||||
|
||||
// Error encapsulates an IAM error.
|
||||
type Error struct {
|
||||
// HTTP status code of the error.
|
||||
StatusCode int
|
||||
|
||||
// AWS code of the error.
|
||||
Code string
|
||||
|
||||
// Message explaining the error.
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
var prefix string
|
||||
if e.Code != "" {
|
||||
prefix = e.Code + ": "
|
||||
}
|
||||
if prefix == "" && e.StatusCode > 0 {
|
||||
prefix = strconv.Itoa(e.StatusCode) + ": "
|
||||
}
|
||||
return prefix + e.Message
|
||||
}
|
||||
282
vendor/src/launchpad.net/goamz/iam/iam_test.go
vendored
Normal file
282
vendor/src/launchpad.net/goamz/iam/iam_test.go
vendored
Normal file
@@ -0,0 +1,282 @@
|
||||
package iam_test
|
||||
|
||||
import (
|
||||
"launchpad.net/goamz/aws"
|
||||
"launchpad.net/goamz/iam"
|
||||
"launchpad.net/goamz/testutil"
|
||||
. "launchpad.net/gocheck"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
TestingT(t)
|
||||
}
|
||||
|
||||
type S struct {
|
||||
iam *iam.IAM
|
||||
}
|
||||
|
||||
var _ = Suite(&S{})
|
||||
|
||||
var testServer = testutil.NewHTTPServer()
|
||||
|
||||
func (s *S) SetUpSuite(c *C) {
|
||||
testServer.Start()
|
||||
auth := aws.Auth{"abc", "123"}
|
||||
s.iam = iam.New(auth, aws.Region{IAMEndpoint: testServer.URL})
|
||||
}
|
||||
|
||||
func (s *S) TearDownSuite(c *C) {
|
||||
testServer.Stop()
|
||||
}
|
||||
|
||||
func (s *S) TearDownTest(c *C) {
|
||||
testServer.Flush()
|
||||
}
|
||||
|
||||
func (s *S) TestCreateUser(c *C) {
|
||||
testServer.Response(200, nil, CreateUserExample)
|
||||
resp, err := s.iam.CreateUser("Bob", "/division_abc/subdivision_xyz/")
|
||||
values := testServer.WaitRequest().URL.Query()
|
||||
c.Assert(values.Get("Action"), Equals, "CreateUser")
|
||||
c.Assert(values.Get("UserName"), Equals, "Bob")
|
||||
c.Assert(values.Get("Path"), Equals, "/division_abc/subdivision_xyz/")
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE")
|
||||
expected := iam.User{
|
||||
Path: "/division_abc/subdivision_xyz/",
|
||||
Name: "Bob",
|
||||
Id: "AIDACKCEVSQ6C2EXAMPLE",
|
||||
Arn: "arn:aws:iam::123456789012:user/division_abc/subdivision_xyz/Bob",
|
||||
}
|
||||
c.Assert(resp.User, DeepEquals, expected)
|
||||
}
|
||||
|
||||
func (s *S) TestCreateUserConflict(c *C) {
|
||||
testServer.Response(409, nil, DuplicateUserExample)
|
||||
resp, err := s.iam.CreateUser("Bob", "/division_abc/subdivision_xyz/")
|
||||
testServer.WaitRequest()
|
||||
c.Assert(resp, IsNil)
|
||||
c.Assert(err, NotNil)
|
||||
e, ok := err.(*iam.Error)
|
||||
c.Assert(ok, Equals, true)
|
||||
c.Assert(e.Message, Equals, "User with name Bob already exists.")
|
||||
c.Assert(e.Code, Equals, "EntityAlreadyExists")
|
||||
}
|
||||
|
||||
func (s *S) TestGetUser(c *C) {
|
||||
testServer.Response(200, nil, GetUserExample)
|
||||
resp, err := s.iam.GetUser("Bob")
|
||||
values := testServer.WaitRequest().URL.Query()
|
||||
c.Assert(values.Get("Action"), Equals, "GetUser")
|
||||
c.Assert(values.Get("UserName"), Equals, "Bob")
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE")
|
||||
expected := iam.User{
|
||||
Path: "/division_abc/subdivision_xyz/",
|
||||
Name: "Bob",
|
||||
Id: "AIDACKCEVSQ6C2EXAMPLE",
|
||||
Arn: "arn:aws:iam::123456789012:user/division_abc/subdivision_xyz/Bob",
|
||||
}
|
||||
c.Assert(resp.User, DeepEquals, expected)
|
||||
}
|
||||
|
||||
func (s *S) TestDeleteUser(c *C) {
|
||||
testServer.Response(200, nil, RequestIdExample)
|
||||
resp, err := s.iam.DeleteUser("Bob")
|
||||
values := testServer.WaitRequest().URL.Query()
|
||||
c.Assert(values.Get("Action"), Equals, "DeleteUser")
|
||||
c.Assert(values.Get("UserName"), Equals, "Bob")
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE")
|
||||
}
|
||||
|
||||
func (s *S) TestCreateGroup(c *C) {
|
||||
testServer.Response(200, nil, CreateGroupExample)
|
||||
resp, err := s.iam.CreateGroup("Admins", "/admins/")
|
||||
values := testServer.WaitRequest().URL.Query()
|
||||
c.Assert(values.Get("Action"), Equals, "CreateGroup")
|
||||
c.Assert(values.Get("GroupName"), Equals, "Admins")
|
||||
c.Assert(values.Get("Path"), Equals, "/admins/")
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.Group.Path, Equals, "/admins/")
|
||||
c.Assert(resp.Group.Name, Equals, "Admins")
|
||||
c.Assert(resp.Group.Id, Equals, "AGPACKCEVSQ6C2EXAMPLE")
|
||||
c.Assert(resp.RequestId, Equals, "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE")
|
||||
}
|
||||
|
||||
func (s *S) TestCreateGroupWithoutPath(c *C) {
|
||||
testServer.Response(200, nil, CreateGroupExample)
|
||||
_, err := s.iam.CreateGroup("Managers", "")
|
||||
values := testServer.WaitRequest().URL.Query()
|
||||
c.Assert(values.Get("Action"), Equals, "CreateGroup")
|
||||
c.Assert(err, IsNil)
|
||||
_, ok := map[string][]string(values)["Path"]
|
||||
c.Assert(ok, Equals, false)
|
||||
}
|
||||
|
||||
func (s *S) TestDeleteGroup(c *C) {
|
||||
testServer.Response(200, nil, RequestIdExample)
|
||||
resp, err := s.iam.DeleteGroup("Admins")
|
||||
values := testServer.WaitRequest().URL.Query()
|
||||
c.Assert(values.Get("Action"), Equals, "DeleteGroup")
|
||||
c.Assert(values.Get("GroupName"), Equals, "Admins")
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE")
|
||||
}
|
||||
|
||||
func (s *S) TestListGroups(c *C) {
|
||||
testServer.Response(200, nil, ListGroupsExample)
|
||||
resp, err := s.iam.Groups("/division_abc/")
|
||||
values := testServer.WaitRequest().URL.Query()
|
||||
c.Assert(values.Get("Action"), Equals, "ListGroups")
|
||||
c.Assert(values.Get("PathPrefix"), Equals, "/division_abc/")
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE")
|
||||
expected := []iam.Group{
|
||||
{
|
||||
Path: "/division_abc/subdivision_xyz/",
|
||||
Name: "Admins",
|
||||
Id: "AGPACKCEVSQ6C2EXAMPLE",
|
||||
Arn: "arn:aws:iam::123456789012:group/Admins",
|
||||
},
|
||||
{
|
||||
Path: "/division_abc/subdivision_xyz/product_1234/engineering/",
|
||||
Name: "Test",
|
||||
Id: "AGP2MAB8DPLSRHEXAMPLE",
|
||||
Arn: "arn:aws:iam::123456789012:group/division_abc/subdivision_xyz/product_1234/engineering/Test",
|
||||
},
|
||||
{
|
||||
Path: "/division_abc/subdivision_xyz/product_1234/",
|
||||
Name: "Managers",
|
||||
Id: "AGPIODR4TAW7CSEXAMPLE",
|
||||
Arn: "arn:aws:iam::123456789012:group/division_abc/subdivision_xyz/product_1234/Managers",
|
||||
},
|
||||
}
|
||||
c.Assert(resp.Groups, DeepEquals, expected)
|
||||
}
|
||||
|
||||
func (s *S) TestListGroupsWithoutPathPrefix(c *C) {
|
||||
testServer.Response(200, nil, ListGroupsExample)
|
||||
_, err := s.iam.Groups("")
|
||||
values := testServer.WaitRequest().URL.Query()
|
||||
c.Assert(values.Get("Action"), Equals, "ListGroups")
|
||||
c.Assert(err, IsNil)
|
||||
_, ok := map[string][]string(values)["PathPrefix"]
|
||||
c.Assert(ok, Equals, false)
|
||||
}
|
||||
|
||||
func (s *S) TestCreateAccessKey(c *C) {
|
||||
testServer.Response(200, nil, CreateAccessKeyExample)
|
||||
resp, err := s.iam.CreateAccessKey("Bob")
|
||||
values := testServer.WaitRequest().URL.Query()
|
||||
c.Assert(values.Get("Action"), Equals, "CreateAccessKey")
|
||||
c.Assert(values.Get("UserName"), Equals, "Bob")
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.AccessKey.UserName, Equals, "Bob")
|
||||
c.Assert(resp.AccessKey.Id, Equals, "AKIAIOSFODNN7EXAMPLE")
|
||||
c.Assert(resp.AccessKey.Secret, Equals, "wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY")
|
||||
c.Assert(resp.AccessKey.Status, Equals, "Active")
|
||||
}
|
||||
|
||||
func (s *S) TestDeleteAccessKey(c *C) {
|
||||
testServer.Response(200, nil, RequestIdExample)
|
||||
resp, err := s.iam.DeleteAccessKey("ysa8hasdhasdsi", "Bob")
|
||||
values := testServer.WaitRequest().URL.Query()
|
||||
c.Assert(values.Get("Action"), Equals, "DeleteAccessKey")
|
||||
c.Assert(values.Get("AccessKeyId"), Equals, "ysa8hasdhasdsi")
|
||||
c.Assert(values.Get("UserName"), Equals, "Bob")
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE")
|
||||
}
|
||||
|
||||
func (s *S) TestDeleteAccessKeyBlankUserName(c *C) {
|
||||
testServer.Response(200, nil, RequestIdExample)
|
||||
_, err := s.iam.DeleteAccessKey("ysa8hasdhasdsi", "")
|
||||
c.Assert(err, IsNil)
|
||||
values := testServer.WaitRequest().URL.Query()
|
||||
c.Assert(values.Get("Action"), Equals, "DeleteAccessKey")
|
||||
c.Assert(values.Get("AccessKeyId"), Equals, "ysa8hasdhasdsi")
|
||||
_, ok := map[string][]string(values)["UserName"]
|
||||
c.Assert(ok, Equals, false)
|
||||
}
|
||||
|
||||
func (s *S) TestAccessKeys(c *C) {
|
||||
testServer.Response(200, nil, ListAccessKeyExample)
|
||||
resp, err := s.iam.AccessKeys("Bob")
|
||||
values := testServer.WaitRequest().URL.Query()
|
||||
c.Assert(values.Get("Action"), Equals, "ListAccessKeys")
|
||||
c.Assert(values.Get("UserName"), Equals, "Bob")
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE")
|
||||
c.Assert(resp.AccessKeys, HasLen, 2)
|
||||
c.Assert(resp.AccessKeys[0].Id, Equals, "AKIAIOSFODNN7EXAMPLE")
|
||||
c.Assert(resp.AccessKeys[0].UserName, Equals, "Bob")
|
||||
c.Assert(resp.AccessKeys[0].Status, Equals, "Active")
|
||||
c.Assert(resp.AccessKeys[1].Id, Equals, "AKIAI44QH8DHBEXAMPLE")
|
||||
c.Assert(resp.AccessKeys[1].UserName, Equals, "Bob")
|
||||
c.Assert(resp.AccessKeys[1].Status, Equals, "Inactive")
|
||||
}
|
||||
|
||||
func (s *S) TestAccessKeysBlankUserName(c *C) {
|
||||
testServer.Response(200, nil, ListAccessKeyExample)
|
||||
_, err := s.iam.AccessKeys("")
|
||||
c.Assert(err, IsNil)
|
||||
values := testServer.WaitRequest().URL.Query()
|
||||
c.Assert(values.Get("Action"), Equals, "ListAccessKeys")
|
||||
_, ok := map[string][]string(values)["UserName"]
|
||||
c.Assert(ok, Equals, false)
|
||||
}
|
||||
|
||||
func (s *S) TestGetUserPolicy(c *C) {
|
||||
testServer.Response(200, nil, GetUserPolicyExample)
|
||||
resp, err := s.iam.GetUserPolicy("Bob", "AllAccessPolicy")
|
||||
values := testServer.WaitRequest().URL.Query()
|
||||
c.Assert(values.Get("Action"), Equals, "GetUserPolicy")
|
||||
c.Assert(values.Get("UserName"), Equals, "Bob")
|
||||
c.Assert(values.Get("PolicyName"), Equals, "AllAccessPolicy")
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.Policy.UserName, Equals, "Bob")
|
||||
c.Assert(resp.Policy.Name, Equals, "AllAccessPolicy")
|
||||
c.Assert(strings.TrimSpace(resp.Policy.Document), Equals, `{"Statement":[{"Effect":"Allow","Action":"*","Resource":"*"}]}`)
|
||||
c.Assert(resp.RequestId, Equals, "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE")
|
||||
}
|
||||
|
||||
func (s *S) TestPutUserPolicy(c *C) {
|
||||
document := `{
|
||||
"Statement": [
|
||||
{
|
||||
"Action": [
|
||||
"s3:*"
|
||||
],
|
||||
"Effect": "Allow",
|
||||
"Resource": [
|
||||
"arn:aws:s3:::8shsns19s90ajahadsj/*",
|
||||
"arn:aws:s3:::8shsns19s90ajahadsj"
|
||||
]
|
||||
}]
|
||||
}`
|
||||
testServer.Response(200, nil, RequestIdExample)
|
||||
resp, err := s.iam.PutUserPolicy("Bob", "AllAccessPolicy", document)
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "POST")
|
||||
c.Assert(req.FormValue("Action"), Equals, "PutUserPolicy")
|
||||
c.Assert(req.FormValue("PolicyName"), Equals, "AllAccessPolicy")
|
||||
c.Assert(req.FormValue("UserName"), Equals, "Bob")
|
||||
c.Assert(req.FormValue("PolicyDocument"), Equals, document)
|
||||
c.Assert(req.FormValue("Version"), Equals, "2010-05-08")
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE")
|
||||
}
|
||||
|
||||
func (s *S) TestDeleteUserPolicy(c *C) {
|
||||
testServer.Response(200, nil, RequestIdExample)
|
||||
resp, err := s.iam.DeleteUserPolicy("Bob", "AllAccessPolicy")
|
||||
values := testServer.WaitRequest().URL.Query()
|
||||
c.Assert(values.Get("Action"), Equals, "DeleteUserPolicy")
|
||||
c.Assert(values.Get("PolicyName"), Equals, "AllAccessPolicy")
|
||||
c.Assert(values.Get("UserName"), Equals, "Bob")
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.RequestId, Equals, "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE")
|
||||
}
|
||||
208
vendor/src/launchpad.net/goamz/iam/iami_test.go
vendored
Normal file
208
vendor/src/launchpad.net/goamz/iam/iami_test.go
vendored
Normal file
@@ -0,0 +1,208 @@
|
||||
package iam_test
|
||||
|
||||
import (
|
||||
"launchpad.net/goamz/aws"
|
||||
"launchpad.net/goamz/iam"
|
||||
"launchpad.net/goamz/testutil"
|
||||
. "launchpad.net/gocheck"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// AmazonServer represents an Amazon AWS server.
|
||||
type AmazonServer struct {
|
||||
auth aws.Auth
|
||||
}
|
||||
|
||||
func (s *AmazonServer) SetUp(c *C) {
|
||||
auth, err := aws.EnvAuth()
|
||||
if err != nil {
|
||||
c.Fatal(err)
|
||||
}
|
||||
s.auth = auth
|
||||
}
|
||||
|
||||
var _ = Suite(&AmazonClientSuite{})
|
||||
|
||||
// AmazonClientSuite tests the client against a live AWS server.
|
||||
type AmazonClientSuite struct {
|
||||
srv AmazonServer
|
||||
ClientTests
|
||||
}
|
||||
|
||||
func (s *AmazonClientSuite) SetUpSuite(c *C) {
|
||||
if !testutil.Amazon {
|
||||
c.Skip("AmazonClientSuite tests not enabled")
|
||||
}
|
||||
s.srv.SetUp(c)
|
||||
s.iam = iam.New(s.srv.auth, aws.USEast)
|
||||
}
|
||||
|
||||
// ClientTests defines integration tests designed to test the client.
|
||||
// It is not used as a test suite in itself, but embedded within
|
||||
// another type.
|
||||
type ClientTests struct {
|
||||
iam *iam.IAM
|
||||
}
|
||||
|
||||
func (s *ClientTests) TestCreateAndDeleteUser(c *C) {
|
||||
createResp, err := s.iam.CreateUser("gopher", "/gopher/")
|
||||
c.Assert(err, IsNil)
|
||||
getResp, err := s.iam.GetUser("gopher")
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(createResp.User, DeepEquals, getResp.User)
|
||||
_, err = s.iam.DeleteUser("gopher")
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *ClientTests) TestCreateUserError(c *C) {
|
||||
_, err := s.iam.CreateUser("gopher", "/gopher/")
|
||||
c.Assert(err, IsNil)
|
||||
defer s.iam.DeleteUser("gopher")
|
||||
_, err = s.iam.CreateUser("gopher", "/")
|
||||
iamErr, ok := err.(*iam.Error)
|
||||
c.Assert(ok, Equals, true)
|
||||
c.Assert(iamErr.StatusCode, Equals, 409)
|
||||
c.Assert(iamErr.Code, Equals, "EntityAlreadyExists")
|
||||
c.Assert(iamErr.Message, Equals, "User with name gopher already exists.")
|
||||
}
|
||||
|
||||
func (s *ClientTests) TestDeleteUserError(c *C) {
|
||||
_, err := s.iam.DeleteUser("gopher")
|
||||
iamErr, ok := err.(*iam.Error)
|
||||
c.Assert(ok, Equals, true)
|
||||
c.Assert(iamErr.StatusCode, Equals, 404)
|
||||
c.Assert(iamErr.Code, Equals, "NoSuchEntity")
|
||||
c.Assert(iamErr.Message, Equals, "The user with name gopher cannot be found.")
|
||||
}
|
||||
|
||||
func (s *ClientTests) TestGetUserError(c *C) {
|
||||
_, err := s.iam.GetUser("gopher")
|
||||
iamErr, ok := err.(*iam.Error)
|
||||
c.Assert(ok, Equals, true)
|
||||
c.Assert(iamErr.StatusCode, Equals, 404)
|
||||
c.Assert(iamErr.Code, Equals, "NoSuchEntity")
|
||||
c.Assert(iamErr.Message, Equals, "The user with name gopher cannot be found.")
|
||||
}
|
||||
|
||||
func (s *ClientTests) TestCreateListAndDeleteAccessKey(c *C) {
|
||||
createUserResp, err := s.iam.CreateUser("gopher", "/gopher/")
|
||||
c.Assert(err, IsNil)
|
||||
defer s.iam.DeleteUser(createUserResp.User.Name)
|
||||
createKeyResp, err := s.iam.CreateAccessKey(createUserResp.User.Name)
|
||||
c.Assert(err, IsNil)
|
||||
listKeyResp, err := s.iam.AccessKeys(createUserResp.User.Name)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(listKeyResp.AccessKeys, HasLen, 1)
|
||||
createKeyResp.AccessKey.Secret = ""
|
||||
c.Assert(listKeyResp.AccessKeys[0], DeepEquals, createKeyResp.AccessKey)
|
||||
_, err = s.iam.DeleteAccessKey(createKeyResp.AccessKey.Id, createUserResp.User.Name)
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *ClientTests) TestCreateAccessKeyError(c *C) {
|
||||
_, err := s.iam.CreateAccessKey("unknowngopher")
|
||||
c.Assert(err, NotNil)
|
||||
iamErr, ok := err.(*iam.Error)
|
||||
c.Assert(ok, Equals, true)
|
||||
c.Assert(iamErr.StatusCode, Equals, 404)
|
||||
c.Assert(iamErr.Code, Equals, "NoSuchEntity")
|
||||
c.Assert(iamErr.Message, Equals, "The user with name unknowngopher cannot be found.")
|
||||
}
|
||||
|
||||
func (s *ClientTests) TestListAccessKeysUserNotFound(c *C) {
|
||||
_, err := s.iam.AccessKeys("unknowngopher")
|
||||
c.Assert(err, NotNil)
|
||||
iamErr, ok := err.(*iam.Error)
|
||||
c.Assert(ok, Equals, true)
|
||||
c.Assert(iamErr.StatusCode, Equals, 404)
|
||||
c.Assert(iamErr.Code, Equals, "NoSuchEntity")
|
||||
c.Assert(iamErr.Message, Equals, "The user with name unknowngopher cannot be found.")
|
||||
}
|
||||
|
||||
func (s *ClientTests) TestListAccessKeysUserWithoutKeys(c *C) {
|
||||
createUserResp, err := s.iam.CreateUser("gopher", "/")
|
||||
c.Assert(err, IsNil)
|
||||
defer s.iam.DeleteUser(createUserResp.User.Name)
|
||||
resp, err := s.iam.AccessKeys(createUserResp.User.Name)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.AccessKeys, HasLen, 0)
|
||||
}
|
||||
|
||||
func (s *ClientTests) TestCreateListAndDeleteGroup(c *C) {
|
||||
cResp1, err := s.iam.CreateGroup("Finances", "/finances/")
|
||||
c.Assert(err, IsNil)
|
||||
cResp2, err := s.iam.CreateGroup("DevelopmentManagers", "/development/managers/")
|
||||
c.Assert(err, IsNil)
|
||||
lResp, err := s.iam.Groups("/development/")
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(lResp.Groups, HasLen, 1)
|
||||
c.Assert(cResp2.Group, DeepEquals, lResp.Groups[0])
|
||||
lResp, err = s.iam.Groups("")
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(lResp.Groups, HasLen, 2)
|
||||
if lResp.Groups[0].Name == cResp1.Group.Name {
|
||||
c.Assert([]iam.Group{cResp1.Group, cResp2.Group}, DeepEquals, lResp.Groups)
|
||||
} else {
|
||||
c.Assert([]iam.Group{cResp2.Group, cResp1.Group}, DeepEquals, lResp.Groups)
|
||||
}
|
||||
_, err = s.iam.DeleteGroup("DevelopmentManagers")
|
||||
c.Assert(err, IsNil)
|
||||
lResp, err = s.iam.Groups("/development/")
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(lResp.Groups, HasLen, 0)
|
||||
_, err = s.iam.DeleteGroup("Finances")
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *ClientTests) TestCreateGroupError(c *C) {
|
||||
_, err := s.iam.CreateGroup("Finances", "/finances/")
|
||||
c.Assert(err, IsNil)
|
||||
defer s.iam.DeleteGroup("Finances")
|
||||
_, err = s.iam.CreateGroup("Finances", "/something-else/")
|
||||
iamErr, ok := err.(*iam.Error)
|
||||
c.Assert(ok, Equals, true)
|
||||
c.Assert(iamErr.StatusCode, Equals, 409)
|
||||
c.Assert(iamErr.Code, Equals, "EntityAlreadyExists")
|
||||
c.Assert(iamErr.Message, Equals, "Group with name Finances already exists.")
|
||||
}
|
||||
|
||||
func (s *ClientTests) TestDeleteGroupError(c *C) {
|
||||
_, err := s.iam.DeleteGroup("Finances")
|
||||
iamErr, ok := err.(*iam.Error)
|
||||
c.Assert(ok, Equals, true)
|
||||
c.Assert(iamErr.StatusCode, Equals, 404)
|
||||
c.Assert(iamErr.Code, Equals, "NoSuchEntity")
|
||||
c.Assert(iamErr.Message, Equals, "The group with name Finances cannot be found.")
|
||||
}
|
||||
|
||||
func (s *ClientTests) TestPutGetAndDeleteUserPolicy(c *C) {
|
||||
userResp, err := s.iam.CreateUser("gopher", "/gopher/")
|
||||
c.Assert(err, IsNil)
|
||||
defer s.iam.DeleteUser(userResp.User.Name)
|
||||
document := `{
|
||||
"Statement": [
|
||||
{
|
||||
"Action": [
|
||||
"s3:*"
|
||||
],
|
||||
"Effect": "Allow",
|
||||
"Resource": [
|
||||
"arn:aws:s3:::8shsns19s90ajahadsj/*",
|
||||
"arn:aws:s3:::8shsns19s90ajahadsj"
|
||||
]
|
||||
}]
|
||||
}`
|
||||
_, err = s.iam.PutUserPolicy(userResp.User.Name, "EverythingS3", document)
|
||||
c.Assert(err, IsNil)
|
||||
resp, err := s.iam.GetUserPolicy(userResp.User.Name, "EverythingS3")
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(resp.Policy.Name, Equals, "EverythingS3")
|
||||
c.Assert(resp.Policy.UserName, Equals, userResp.User.Name)
|
||||
gotDocument, err := url.QueryUnescape(resp.Policy.Document)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(gotDocument, Equals, document)
|
||||
_, err = s.iam.DeleteUserPolicy(userResp.User.Name, "EverythingS3")
|
||||
c.Assert(err, IsNil)
|
||||
_, err = s.iam.GetUserPolicy(userResp.User.Name, "EverythingS3")
|
||||
c.Assert(err, NotNil)
|
||||
}
|
||||
39
vendor/src/launchpad.net/goamz/iam/iamt_test.go
vendored
Normal file
39
vendor/src/launchpad.net/goamz/iam/iamt_test.go
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
package iam_test
|
||||
|
||||
import (
|
||||
"launchpad.net/goamz/aws"
|
||||
"launchpad.net/goamz/iam"
|
||||
"launchpad.net/goamz/iam/iamtest"
|
||||
. "launchpad.net/gocheck"
|
||||
)
|
||||
|
||||
// LocalServer represents a local ec2test fake server.
|
||||
type LocalServer struct {
|
||||
auth aws.Auth
|
||||
region aws.Region
|
||||
srv *iamtest.Server
|
||||
}
|
||||
|
||||
func (s *LocalServer) SetUp(c *C) {
|
||||
srv, err := iamtest.NewServer()
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(srv, NotNil)
|
||||
|
||||
s.srv = srv
|
||||
s.region = aws.Region{IAMEndpoint: srv.URL()}
|
||||
}
|
||||
|
||||
// LocalServerSuite defines tests that will run
|
||||
// against the local iamtest server. It includes
|
||||
// tests from ClientTests.
|
||||
type LocalServerSuite struct {
|
||||
srv LocalServer
|
||||
ClientTests
|
||||
}
|
||||
|
||||
var _ = Suite(&LocalServerSuite{})
|
||||
|
||||
func (s *LocalServerSuite) SetUpSuite(c *C) {
|
||||
s.srv.SetUp(c)
|
||||
s.ClientTests.iam = iam.New(s.srv.auth, s.srv.region)
|
||||
}
|
||||
432
vendor/src/launchpad.net/goamz/iam/iamtest/server.go
vendored
Normal file
432
vendor/src/launchpad.net/goamz/iam/iamtest/server.go
vendored
Normal file
@@ -0,0 +1,432 @@
|
||||
// Package iamtest implements a fake IAM provider with the capability of
|
||||
// inducing errors on any given operation, and retrospectively determining what
|
||||
// operations have been carried out.
|
||||
package iamtest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"launchpad.net/goamz/iam"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type action struct {
|
||||
srv *Server
|
||||
w http.ResponseWriter
|
||||
req *http.Request
|
||||
reqId string
|
||||
}
|
||||
|
||||
// Server implements an IAM simulator for use in tests.
|
||||
type Server struct {
|
||||
reqId int
|
||||
url string
|
||||
listener net.Listener
|
||||
users []iam.User
|
||||
groups []iam.Group
|
||||
accessKeys []iam.AccessKey
|
||||
userPolicies []iam.UserPolicy
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func NewServer() (*Server, error) {
|
||||
l, err := net.Listen("tcp", "localhost:0")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot listen on localhost: %v", err)
|
||||
}
|
||||
srv := &Server{
|
||||
listener: l,
|
||||
url: "http://" + l.Addr().String(),
|
||||
}
|
||||
go http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
srv.serveHTTP(w, req)
|
||||
}))
|
||||
return srv, nil
|
||||
}
|
||||
|
||||
// Quit closes down the server.
|
||||
func (srv *Server) Quit() error {
|
||||
return srv.listener.Close()
|
||||
}
|
||||
|
||||
// URL returns a URL for the server.
|
||||
func (srv *Server) URL() string {
|
||||
return srv.url
|
||||
}
|
||||
|
||||
type xmlErrors struct {
|
||||
XMLName string `xml:"ErrorResponse"`
|
||||
Error iam.Error
|
||||
}
|
||||
|
||||
func (srv *Server) error(w http.ResponseWriter, err *iam.Error) {
|
||||
w.WriteHeader(err.StatusCode)
|
||||
xmlErr := xmlErrors{Error: *err}
|
||||
if e := xml.NewEncoder(w).Encode(xmlErr); e != nil {
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *Server) serveHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
req.ParseForm()
|
||||
srv.mutex.Lock()
|
||||
defer srv.mutex.Unlock()
|
||||
action := req.FormValue("Action")
|
||||
if action == "" {
|
||||
srv.error(w, &iam.Error{
|
||||
StatusCode: 400,
|
||||
Code: "MissingAction",
|
||||
Message: "Missing action",
|
||||
})
|
||||
}
|
||||
if a, ok := actions[action]; ok {
|
||||
reqId := fmt.Sprintf("req%0X", srv.reqId)
|
||||
srv.reqId++
|
||||
if resp, err := a(srv, w, req, reqId); err == nil {
|
||||
if err := xml.NewEncoder(w).Encode(resp); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
switch err.(type) {
|
||||
case *iam.Error:
|
||||
srv.error(w, err.(*iam.Error))
|
||||
default:
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
srv.error(w, &iam.Error{
|
||||
StatusCode: 400,
|
||||
Code: "InvalidAction",
|
||||
Message: "Invalid action: " + action,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *Server) createUser(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
|
||||
if err := srv.validate(req, []string{"UserName"}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
path := req.FormValue("Path")
|
||||
if path == "" {
|
||||
path = "/"
|
||||
}
|
||||
name := req.FormValue("UserName")
|
||||
for _, user := range srv.users {
|
||||
if user.Name == name {
|
||||
return nil, &iam.Error{
|
||||
StatusCode: 409,
|
||||
Code: "EntityAlreadyExists",
|
||||
Message: fmt.Sprintf("User with name %s already exists.", name),
|
||||
}
|
||||
}
|
||||
}
|
||||
user := iam.User{
|
||||
Id: "USER" + reqId + "EXAMPLE",
|
||||
Arn: fmt.Sprintf("arn:aws:iam:::123456789012:user%s%s", path, name),
|
||||
Name: name,
|
||||
Path: path,
|
||||
}
|
||||
srv.users = append(srv.users, user)
|
||||
return iam.CreateUserResp{
|
||||
RequestId: reqId,
|
||||
User: user,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (srv *Server) getUser(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
|
||||
if err := srv.validate(req, []string{"UserName"}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name := req.FormValue("UserName")
|
||||
index, err := srv.findUser(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return iam.GetUserResp{RequestId: reqId, User: srv.users[index]}, nil
|
||||
}
|
||||
|
||||
func (srv *Server) deleteUser(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
|
||||
if err := srv.validate(req, []string{"UserName"}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name := req.FormValue("UserName")
|
||||
index, err := srv.findUser(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(srv.users[index:], srv.users[index+1:])
|
||||
srv.users = srv.users[:len(srv.users)-1]
|
||||
return iam.SimpleResp{RequestId: reqId}, nil
|
||||
}
|
||||
|
||||
func (srv *Server) createAccessKey(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
|
||||
if err := srv.validate(req, []string{"UserName"}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userName := req.FormValue("UserName")
|
||||
if _, err := srv.findUser(userName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key := iam.AccessKey{
|
||||
Id: fmt.Sprintf("%s%d", userName, len(srv.accessKeys)),
|
||||
Secret: "",
|
||||
UserName: userName,
|
||||
Status: "Active",
|
||||
}
|
||||
srv.accessKeys = append(srv.accessKeys, key)
|
||||
return iam.CreateAccessKeyResp{RequestId: reqId, AccessKey: key}, nil
|
||||
}
|
||||
|
||||
func (srv *Server) deleteAccessKey(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
|
||||
if err := srv.validate(req, []string{"AccessKeyId", "UserName"}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key := req.FormValue("AccessKeyId")
|
||||
index := -1
|
||||
for i, ak := range srv.accessKeys {
|
||||
if ak.Id == key {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if index < 0 {
|
||||
return nil, &iam.Error{
|
||||
StatusCode: 404,
|
||||
Code: "NoSuchEntity",
|
||||
Message: "No such key.",
|
||||
}
|
||||
}
|
||||
copy(srv.accessKeys[index:], srv.accessKeys[index+1:])
|
||||
srv.accessKeys = srv.accessKeys[:len(srv.accessKeys)-1]
|
||||
return iam.SimpleResp{RequestId: reqId}, nil
|
||||
}
|
||||
|
||||
func (srv *Server) listAccessKeys(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
|
||||
if err := srv.validate(req, []string{"UserName"}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userName := req.FormValue("UserName")
|
||||
if _, err := srv.findUser(userName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var keys []iam.AccessKey
|
||||
for _, k := range srv.accessKeys {
|
||||
if k.UserName == userName {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
}
|
||||
return iam.AccessKeysResp{
|
||||
RequestId: reqId,
|
||||
AccessKeys: keys,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (srv *Server) createGroup(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
|
||||
if err := srv.validate(req, []string{"GroupName"}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name := req.FormValue("GroupName")
|
||||
path := req.FormValue("Path")
|
||||
for _, group := range srv.groups {
|
||||
if group.Name == name {
|
||||
return nil, &iam.Error{
|
||||
StatusCode: 409,
|
||||
Code: "EntityAlreadyExists",
|
||||
Message: fmt.Sprintf("Group with name %s already exists.", name),
|
||||
}
|
||||
}
|
||||
}
|
||||
group := iam.Group{
|
||||
Id: "GROUP " + reqId + "EXAMPLE",
|
||||
Arn: fmt.Sprintf("arn:aws:iam:::123456789012:group%s%s", path, name),
|
||||
Name: name,
|
||||
Path: path,
|
||||
}
|
||||
srv.groups = append(srv.groups, group)
|
||||
return iam.CreateGroupResp{
|
||||
RequestId: reqId,
|
||||
Group: group,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (srv *Server) listGroups(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
|
||||
pathPrefix := req.FormValue("PathPrefix")
|
||||
if pathPrefix == "" {
|
||||
return iam.GroupsResp{
|
||||
RequestId: reqId,
|
||||
Groups: srv.groups,
|
||||
}, nil
|
||||
}
|
||||
var groups []iam.Group
|
||||
for _, group := range srv.groups {
|
||||
if strings.HasPrefix(group.Path, pathPrefix) {
|
||||
groups = append(groups, group)
|
||||
}
|
||||
}
|
||||
return iam.GroupsResp{
|
||||
RequestId: reqId,
|
||||
Groups: groups,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (srv *Server) deleteGroup(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
|
||||
if err := srv.validate(req, []string{"GroupName"}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name := req.FormValue("GroupName")
|
||||
index := -1
|
||||
for i, group := range srv.groups {
|
||||
if group.Name == name {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if index == -1 {
|
||||
return nil, &iam.Error{
|
||||
StatusCode: 404,
|
||||
Code: "NoSuchEntity",
|
||||
Message: fmt.Sprintf("The group with name %s cannot be found.", name),
|
||||
}
|
||||
}
|
||||
copy(srv.groups[index:], srv.groups[index+1:])
|
||||
srv.groups = srv.groups[:len(srv.groups)-1]
|
||||
return iam.SimpleResp{RequestId: reqId}, nil
|
||||
}
|
||||
|
||||
func (srv *Server) putUserPolicy(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
|
||||
if err := srv.validate(req, []string{"UserName", "PolicyDocument", "PolicyName"}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var exists bool
|
||||
policyName := req.FormValue("PolicyName")
|
||||
userName := req.FormValue("UserName")
|
||||
for _, policy := range srv.userPolicies {
|
||||
if policyName == policy.Name && userName == policy.UserName {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exists {
|
||||
policy := iam.UserPolicy{
|
||||
Name: policyName,
|
||||
UserName: userName,
|
||||
Document: req.FormValue("PolicyDocument"),
|
||||
}
|
||||
var dumb interface{}
|
||||
if err := json.Unmarshal([]byte(policy.Document), &dumb); err != nil {
|
||||
return nil, &iam.Error{
|
||||
StatusCode: 400,
|
||||
Code: "MalformedPolicyDocument",
|
||||
Message: "Malformed policy document",
|
||||
}
|
||||
}
|
||||
srv.userPolicies = append(srv.userPolicies, policy)
|
||||
}
|
||||
return iam.SimpleResp{RequestId: reqId}, nil
|
||||
}
|
||||
|
||||
func (srv *Server) deleteUserPolicy(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
|
||||
if err := srv.validate(req, []string{"UserName", "PolicyName"}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
policyName := req.FormValue("PolicyName")
|
||||
userName := req.FormValue("UserName")
|
||||
index := -1
|
||||
for i, policy := range srv.userPolicies {
|
||||
if policyName == policy.Name && userName == policy.UserName {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if index < 0 {
|
||||
return nil, &iam.Error{
|
||||
StatusCode: 404,
|
||||
Code: "NoSuchEntity",
|
||||
Message: "No such user policy",
|
||||
}
|
||||
}
|
||||
copy(srv.userPolicies[index:], srv.userPolicies[index+1:])
|
||||
srv.userPolicies = srv.userPolicies[:len(srv.userPolicies)-1]
|
||||
return iam.SimpleResp{RequestId: reqId}, nil
|
||||
}
|
||||
|
||||
func (srv *Server) getUserPolicy(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
|
||||
if err := srv.validate(req, []string{"UserName", "PolicyName"}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
policyName := req.FormValue("PolicyName")
|
||||
userName := req.FormValue("UserName")
|
||||
index := -1
|
||||
for i, policy := range srv.userPolicies {
|
||||
if policyName == policy.Name && userName == policy.UserName {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if index < 0 {
|
||||
return nil, &iam.Error{
|
||||
StatusCode: 404,
|
||||
Code: "NoSuchEntity",
|
||||
Message: "No such user policy",
|
||||
}
|
||||
}
|
||||
return iam.GetUserPolicyResp{
|
||||
Policy: srv.userPolicies[index],
|
||||
RequestId: reqId,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (srv *Server) findUser(userName string) (int, error) {
|
||||
var (
|
||||
err error
|
||||
index = -1
|
||||
)
|
||||
for i, user := range srv.users {
|
||||
if user.Name == userName {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if index < 0 {
|
||||
err = &iam.Error{
|
||||
StatusCode: 404,
|
||||
Code: "NoSuchEntity",
|
||||
Message: fmt.Sprintf("The user with name %s cannot be found.", userName),
|
||||
}
|
||||
}
|
||||
return index, err
|
||||
}
|
||||
|
||||
// Validates the presence of required request parameters.
|
||||
func (srv *Server) validate(req *http.Request, required []string) error {
|
||||
for _, r := range required {
|
||||
if req.FormValue(r) == "" {
|
||||
return &iam.Error{
|
||||
StatusCode: 400,
|
||||
Code: "InvalidParameterCombination",
|
||||
Message: fmt.Sprintf("%s is required.", r),
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var actions = map[string]func(*Server, http.ResponseWriter, *http.Request, string) (interface{}, error){
|
||||
"CreateUser": (*Server).createUser,
|
||||
"DeleteUser": (*Server).deleteUser,
|
||||
"GetUser": (*Server).getUser,
|
||||
"CreateAccessKey": (*Server).createAccessKey,
|
||||
"DeleteAccessKey": (*Server).deleteAccessKey,
|
||||
"ListAccessKeys": (*Server).listAccessKeys,
|
||||
"PutUserPolicy": (*Server).putUserPolicy,
|
||||
"DeleteUserPolicy": (*Server).deleteUserPolicy,
|
||||
"GetUserPolicy": (*Server).getUserPolicy,
|
||||
"CreateGroup": (*Server).createGroup,
|
||||
"DeleteGroup": (*Server).deleteGroup,
|
||||
"ListGroups": (*Server).listGroups,
|
||||
}
|
||||
155
vendor/src/launchpad.net/goamz/iam/responses_test.go
vendored
Normal file
155
vendor/src/launchpad.net/goamz/iam/responses_test.go
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
package iam_test
|
||||
|
||||
// http://goo.gl/EUIvl
|
||||
var CreateUserExample = `
|
||||
<CreateUserResponse>
|
||||
<CreateUserResult>
|
||||
<User>
|
||||
<Path>/division_abc/subdivision_xyz/</Path>
|
||||
<UserName>Bob</UserName>
|
||||
<UserId>AIDACKCEVSQ6C2EXAMPLE</UserId>
|
||||
<Arn>arn:aws:iam::123456789012:user/division_abc/subdivision_xyz/Bob</Arn>
|
||||
</User>
|
||||
</CreateUserResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
|
||||
</ResponseMetadata>
|
||||
</CreateUserResponse>
|
||||
`
|
||||
|
||||
var DuplicateUserExample = `
|
||||
<ErrorResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
|
||||
<Error>
|
||||
<Type>Sender</Type>
|
||||
<Code>EntityAlreadyExists</Code>
|
||||
<Message>User with name Bob already exists.</Message>
|
||||
</Error>
|
||||
<RequestId>1d5f5000-1316-11e2-a60f-91a8e6fb6d21</RequestId>
|
||||
</ErrorResponse>
|
||||
`
|
||||
|
||||
var GetUserExample = `
|
||||
<GetUserResponse>
|
||||
<GetUserResult>
|
||||
<User>
|
||||
<Path>/division_abc/subdivision_xyz/</Path>
|
||||
<UserName>Bob</UserName>
|
||||
<UserId>AIDACKCEVSQ6C2EXAMPLE</UserId>
|
||||
<Arn>arn:aws:iam::123456789012:user/division_abc/subdivision_xyz/Bob</Arn>
|
||||
</User>
|
||||
</GetUserResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
|
||||
</ResponseMetadata>
|
||||
</GetUserResponse>
|
||||
`
|
||||
|
||||
var CreateGroupExample = `
|
||||
<CreateGroupResponse>
|
||||
<CreateGroupResult>
|
||||
<Group>
|
||||
<Path>/admins/</Path>
|
||||
<GroupName>Admins</GroupName>
|
||||
<GroupId>AGPACKCEVSQ6C2EXAMPLE</GroupId>
|
||||
<Arn>arn:aws:iam::123456789012:group/Admins</Arn>
|
||||
</Group>
|
||||
</CreateGroupResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
|
||||
</ResponseMetadata>
|
||||
</CreateGroupResponse>
|
||||
`
|
||||
|
||||
var ListGroupsExample = `
|
||||
<ListGroupsResponse>
|
||||
<ListGroupsResult>
|
||||
<Groups>
|
||||
<member>
|
||||
<Path>/division_abc/subdivision_xyz/</Path>
|
||||
<GroupName>Admins</GroupName>
|
||||
<GroupId>AGPACKCEVSQ6C2EXAMPLE</GroupId>
|
||||
<Arn>arn:aws:iam::123456789012:group/Admins</Arn>
|
||||
</member>
|
||||
<member>
|
||||
<Path>/division_abc/subdivision_xyz/product_1234/engineering/</Path>
|
||||
<GroupName>Test</GroupName>
|
||||
<GroupId>AGP2MAB8DPLSRHEXAMPLE</GroupId>
|
||||
<Arn>arn:aws:iam::123456789012:group/division_abc/subdivision_xyz/product_1234/engineering/Test</Arn>
|
||||
</member>
|
||||
<member>
|
||||
<Path>/division_abc/subdivision_xyz/product_1234/</Path>
|
||||
<GroupName>Managers</GroupName>
|
||||
<GroupId>AGPIODR4TAW7CSEXAMPLE</GroupId>
|
||||
<Arn>arn:aws:iam::123456789012:group/division_abc/subdivision_xyz/product_1234/Managers</Arn>
|
||||
</member>
|
||||
</Groups>
|
||||
<IsTruncated>false</IsTruncated>
|
||||
</ListGroupsResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
|
||||
</ResponseMetadata>
|
||||
</ListGroupsResponse>
|
||||
`
|
||||
|
||||
var RequestIdExample = `
|
||||
<AddUserToGroupResponse>
|
||||
<ResponseMetadata>
|
||||
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
|
||||
</ResponseMetadata>
|
||||
</AddUserToGroupResponse>
|
||||
`
|
||||
|
||||
var CreateAccessKeyExample = `
|
||||
<CreateAccessKeyResponse>
|
||||
<CreateAccessKeyResult>
|
||||
<AccessKey>
|
||||
<UserName>Bob</UserName>
|
||||
<AccessKeyId>AKIAIOSFODNN7EXAMPLE</AccessKeyId>
|
||||
<Status>Active</Status>
|
||||
<SecretAccessKey>wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY</SecretAccessKey>
|
||||
</AccessKey>
|
||||
</CreateAccessKeyResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
|
||||
</ResponseMetadata>
|
||||
</CreateAccessKeyResponse>
|
||||
`
|
||||
|
||||
var ListAccessKeyExample = `
|
||||
<ListAccessKeysResponse>
|
||||
<ListAccessKeysResult>
|
||||
<UserName>Bob</UserName>
|
||||
<AccessKeyMetadata>
|
||||
<member>
|
||||
<UserName>Bob</UserName>
|
||||
<AccessKeyId>AKIAIOSFODNN7EXAMPLE</AccessKeyId>
|
||||
<Status>Active</Status>
|
||||
</member>
|
||||
<member>
|
||||
<UserName>Bob</UserName>
|
||||
<AccessKeyId>AKIAI44QH8DHBEXAMPLE</AccessKeyId>
|
||||
<Status>Inactive</Status>
|
||||
</member>
|
||||
</AccessKeyMetadata>
|
||||
<IsTruncated>false</IsTruncated>
|
||||
</ListAccessKeysResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
|
||||
</ResponseMetadata>
|
||||
</ListAccessKeysResponse>
|
||||
`
|
||||
|
||||
var GetUserPolicyExample = `
|
||||
<GetUserPolicyResponse>
|
||||
<GetUserPolicyResult>
|
||||
<UserName>Bob</UserName>
|
||||
<PolicyName>AllAccessPolicy</PolicyName>
|
||||
<PolicyDocument>
|
||||
{"Statement":[{"Effect":"Allow","Action":"*","Resource":"*"}]}
|
||||
</PolicyDocument>
|
||||
</GetUserPolicyResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
|
||||
</ResponseMetadata>
|
||||
</GetUserPolicyResponse>
|
||||
`
|
||||
35
vendor/src/launchpad.net/goamz/iam/sign.go
vendored
Normal file
35
vendor/src/launchpad.net/goamz/iam/sign.go
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
package iam
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"launchpad.net/goamz/aws"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Version 2 signing (http://goo.gl/RSRp5)
|
||||
|
||||
var b64 = base64.StdEncoding
|
||||
|
||||
func sign(auth aws.Auth, method, path string, params map[string]string, host string) {
|
||||
params["AWSAccessKeyId"] = auth.AccessKey
|
||||
params["SignatureVersion"] = "2"
|
||||
params["SignatureMethod"] = "HmacSHA256"
|
||||
|
||||
var sarray []string
|
||||
for k, v := range params {
|
||||
sarray = append(sarray, aws.Encode(k)+"="+aws.Encode(v))
|
||||
}
|
||||
sort.StringSlice(sarray).Sort()
|
||||
joined := strings.Join(sarray, "&")
|
||||
payload := method + "\n" + host + "\n" + path + "\n" + joined
|
||||
hash := hmac.New(sha256.New, []byte(auth.SecretKey))
|
||||
hash.Write([]byte(payload))
|
||||
signature := make([]byte, b64.EncodedLen(hash.Size()))
|
||||
b64.Encode(signature, hash.Sum(nil))
|
||||
|
||||
params["Signature"] = string(signature)
|
||||
}
|
||||
31
vendor/src/launchpad.net/goamz/s3/export_test.go
vendored
Normal file
31
vendor/src/launchpad.net/goamz/s3/export_test.go
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
package s3
|
||||
|
||||
import (
|
||||
"launchpad.net/goamz/aws"
|
||||
)
|
||||
|
||||
var originalStrategy = attempts
|
||||
|
||||
func SetAttemptStrategy(s *aws.AttemptStrategy) {
|
||||
if s == nil {
|
||||
attempts = originalStrategy
|
||||
} else {
|
||||
attempts = *s
|
||||
}
|
||||
}
|
||||
|
||||
func AttemptStrategy() aws.AttemptStrategy {
|
||||
return attempts
|
||||
}
|
||||
|
||||
func Sign(auth aws.Auth, method, path string, params, headers map[string][]string) {
|
||||
sign(auth, method, path, params, headers)
|
||||
}
|
||||
|
||||
func SetListPartsMax(n int) {
|
||||
listPartsMax = n
|
||||
}
|
||||
|
||||
func SetListMultiMax(n int) {
|
||||
listMultiMax = n
|
||||
}
|
||||
410
vendor/src/launchpad.net/goamz/s3/multi.go
vendored
Normal file
410
vendor/src/launchpad.net/goamz/s3/multi.go
vendored
Normal file
@@ -0,0 +1,410 @@
|
||||
package s3
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"io"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Multi represents an unfinished multipart upload.
|
||||
//
|
||||
// Multipart uploads allow sending big objects in smaller chunks.
|
||||
// After all parts have been sent, the upload must be explicitly
|
||||
// completed by calling Complete with the list of parts.
|
||||
//
|
||||
// See http://goo.gl/vJfTG for an overview of multipart uploads.
|
||||
type Multi struct {
|
||||
Bucket *Bucket
|
||||
Key string
|
||||
UploadId string
|
||||
}
|
||||
|
||||
// That's the default. Here just for testing.
|
||||
var listMultiMax = 1000
|
||||
|
||||
type listMultiResp struct {
|
||||
NextKeyMarker string
|
||||
NextUploadIdMarker string
|
||||
IsTruncated bool
|
||||
Upload []Multi
|
||||
CommonPrefixes []string `xml:"CommonPrefixes>Prefix"`
|
||||
}
|
||||
|
||||
// ListMulti returns the list of unfinished multipart uploads in b.
|
||||
//
|
||||
// The prefix parameter limits the response to keys that begin with the
|
||||
// specified prefix. You can use prefixes to separate a bucket into different
|
||||
// groupings of keys (to get the feeling of folders, for example).
|
||||
//
|
||||
// The delim parameter causes the response to group all of the keys that
|
||||
// share a common prefix up to the next delimiter in a single entry within
|
||||
// the CommonPrefixes field. You can use delimiters to separate a bucket
|
||||
// into different groupings of keys, similar to how folders would work.
|
||||
//
|
||||
// See http://goo.gl/ePioY for details.
|
||||
func (b *Bucket) ListMulti(prefix, delim string) (multis []*Multi, prefixes []string, err error) {
|
||||
params := map[string][]string{
|
||||
"uploads": {""},
|
||||
"max-uploads": {strconv.FormatInt(int64(listMultiMax), 10)},
|
||||
"prefix": {prefix},
|
||||
"delimiter": {delim},
|
||||
}
|
||||
for attempt := attempts.Start(); attempt.Next(); {
|
||||
req := &request{
|
||||
method: "GET",
|
||||
bucket: b.Name,
|
||||
params: params,
|
||||
}
|
||||
var resp listMultiResp
|
||||
err := b.S3.query(req, &resp)
|
||||
if shouldRetry(err) && attempt.HasNext() {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for i := range resp.Upload {
|
||||
multi := &resp.Upload[i]
|
||||
multi.Bucket = b
|
||||
multis = append(multis, multi)
|
||||
}
|
||||
prefixes = append(prefixes, resp.CommonPrefixes...)
|
||||
if !resp.IsTruncated {
|
||||
return multis, prefixes, nil
|
||||
}
|
||||
params["key-marker"] = []string{resp.NextKeyMarker}
|
||||
params["upload-id-marker"] = []string{resp.NextUploadIdMarker}
|
||||
attempt = attempts.Start() // Last request worked.
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Multi returns a multipart upload handler for the provided key
|
||||
// inside b. If a multipart upload exists for key, it is returned,
|
||||
// otherwise a new multipart upload is initiated with contType and perm.
|
||||
func (b *Bucket) Multi(key, contType string, perm ACL) (*Multi, error) {
|
||||
multis, _, err := b.ListMulti(key, "")
|
||||
if err != nil && !hasCode(err, "NoSuchUpload") {
|
||||
return nil, err
|
||||
}
|
||||
for _, m := range multis {
|
||||
if m.Key == key {
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
return b.InitMulti(key, contType, perm)
|
||||
}
|
||||
|
||||
// InitMulti initializes a new multipart upload at the provided
|
||||
// key inside b and returns a value for manipulating it.
|
||||
//
|
||||
// See http://goo.gl/XP8kL for details.
|
||||
func (b *Bucket) InitMulti(key string, contType string, perm ACL) (*Multi, error) {
|
||||
headers := map[string][]string{
|
||||
"Content-Type": {contType},
|
||||
"Content-Length": {"0"},
|
||||
"x-amz-acl": {string(perm)},
|
||||
}
|
||||
params := map[string][]string{
|
||||
"uploads": {""},
|
||||
}
|
||||
req := &request{
|
||||
method: "POST",
|
||||
bucket: b.Name,
|
||||
path: key,
|
||||
headers: headers,
|
||||
params: params,
|
||||
}
|
||||
var err error
|
||||
var resp struct {
|
||||
UploadId string `xml:"UploadId"`
|
||||
}
|
||||
for attempt := attempts.Start(); attempt.Next(); {
|
||||
err = b.S3.query(req, &resp)
|
||||
if !shouldRetry(err) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Multi{Bucket: b, Key: key, UploadId: resp.UploadId}, nil
|
||||
}
|
||||
|
||||
// PutPart sends part n of the multipart upload, reading all the content from r.
|
||||
// Each part, except for the last one, must be at least 5MB in size.
|
||||
//
|
||||
// See http://goo.gl/pqZer for details.
|
||||
func (m *Multi) PutPart(n int, r io.ReadSeeker) (Part, error) {
|
||||
partSize, _, md5b64, err := seekerInfo(r)
|
||||
if err != nil {
|
||||
return Part{}, err
|
||||
}
|
||||
return m.putPart(n, r, partSize, md5b64)
|
||||
}
|
||||
|
||||
func (m *Multi) putPart(n int, r io.ReadSeeker, partSize int64, md5b64 string) (Part, error) {
|
||||
headers := map[string][]string{
|
||||
"Content-Length": {strconv.FormatInt(partSize, 10)},
|
||||
"Content-MD5": {md5b64},
|
||||
}
|
||||
params := map[string][]string{
|
||||
"uploadId": {m.UploadId},
|
||||
"partNumber": {strconv.FormatInt(int64(n), 10)},
|
||||
}
|
||||
for attempt := attempts.Start(); attempt.Next(); {
|
||||
_, err := r.Seek(0, 0)
|
||||
if err != nil {
|
||||
return Part{}, err
|
||||
}
|
||||
req := &request{
|
||||
method: "PUT",
|
||||
bucket: m.Bucket.Name,
|
||||
path: m.Key,
|
||||
headers: headers,
|
||||
params: params,
|
||||
payload: r,
|
||||
}
|
||||
err = m.Bucket.S3.prepare(req)
|
||||
if err != nil {
|
||||
return Part{}, err
|
||||
}
|
||||
hresp, err := m.Bucket.S3.run(req)
|
||||
if shouldRetry(err) && attempt.HasNext() {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return Part{}, err
|
||||
}
|
||||
hresp.Body.Close()
|
||||
etag := hresp.Header.Get("ETag")
|
||||
if etag == "" {
|
||||
return Part{}, errors.New("part upload succeeded with no ETag")
|
||||
}
|
||||
return Part{n, etag, partSize}, nil
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func seekerInfo(r io.ReadSeeker) (size int64, md5hex string, md5b64 string, err error) {
|
||||
_, err = r.Seek(0, 0)
|
||||
if err != nil {
|
||||
return 0, "", "", err
|
||||
}
|
||||
digest := md5.New()
|
||||
size, err = io.Copy(digest, r)
|
||||
if err != nil {
|
||||
return 0, "", "", err
|
||||
}
|
||||
sum := digest.Sum(nil)
|
||||
md5hex = hex.EncodeToString(sum)
|
||||
md5b64 = base64.StdEncoding.EncodeToString(sum)
|
||||
return size, md5hex, md5b64, nil
|
||||
}
|
||||
|
||||
type Part struct {
|
||||
N int `xml:"PartNumber"`
|
||||
ETag string
|
||||
Size int64
|
||||
}
|
||||
|
||||
type partSlice []Part
|
||||
|
||||
func (s partSlice) Len() int { return len(s) }
|
||||
func (s partSlice) Less(i, j int) bool { return s[i].N < s[j].N }
|
||||
func (s partSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
type listPartsResp struct {
|
||||
NextPartNumberMarker string
|
||||
IsTruncated bool
|
||||
Part []Part
|
||||
}
|
||||
|
||||
// That's the default. Here just for testing.
|
||||
var listPartsMax = 1000
|
||||
|
||||
// ListParts returns the list of previously uploaded parts in m,
|
||||
// ordered by part number.
|
||||
//
|
||||
// See http://goo.gl/ePioY for details.
|
||||
func (m *Multi) ListParts() ([]Part, error) {
|
||||
params := map[string][]string{
|
||||
"uploadId": {m.UploadId},
|
||||
"max-parts": {strconv.FormatInt(int64(listPartsMax), 10)},
|
||||
}
|
||||
var parts partSlice
|
||||
for attempt := attempts.Start(); attempt.Next(); {
|
||||
req := &request{
|
||||
method: "GET",
|
||||
bucket: m.Bucket.Name,
|
||||
path: m.Key,
|
||||
params: params,
|
||||
}
|
||||
var resp listPartsResp
|
||||
err := m.Bucket.S3.query(req, &resp)
|
||||
if shouldRetry(err) && attempt.HasNext() {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parts = append(parts, resp.Part...)
|
||||
if !resp.IsTruncated {
|
||||
sort.Sort(parts)
|
||||
return parts, nil
|
||||
}
|
||||
params["part-number-marker"] = []string{resp.NextPartNumberMarker}
|
||||
attempt = attempts.Start() // Last request worked.
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
type ReaderAtSeeker interface {
|
||||
io.ReaderAt
|
||||
io.ReadSeeker
|
||||
}
|
||||
|
||||
// PutAll sends all of r via a multipart upload with parts no larger
|
||||
// than partSize bytes, which must be set to at least 5MB.
|
||||
// Parts previously uploaded are either reused if their checksum
|
||||
// and size match the new part, or otherwise overwritten with the
|
||||
// new content.
|
||||
// PutAll returns all the parts of m (reused or not).
|
||||
func (m *Multi) PutAll(r ReaderAtSeeker, partSize int64) ([]Part, error) {
|
||||
old, err := m.ListParts()
|
||||
if err != nil && !hasCode(err, "NoSuchUpload") {
|
||||
return nil, err
|
||||
}
|
||||
reuse := 0 // Index of next old part to consider reusing.
|
||||
current := 1 // Part number of latest good part handled.
|
||||
totalSize, err := r.Seek(0, 2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
first := true // Must send at least one empty part if the file is empty.
|
||||
var result []Part
|
||||
NextSection:
|
||||
for offset := int64(0); offset < totalSize || first; offset += partSize {
|
||||
first = false
|
||||
if offset+partSize > totalSize {
|
||||
partSize = totalSize - offset
|
||||
}
|
||||
section := io.NewSectionReader(r, offset, partSize)
|
||||
_, md5hex, md5b64, err := seekerInfo(section)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for reuse < len(old) && old[reuse].N <= current {
|
||||
// Looks like this part was already sent.
|
||||
part := &old[reuse]
|
||||
etag := `"` + md5hex + `"`
|
||||
if part.N == current && part.Size == partSize && part.ETag == etag {
|
||||
// Checksum matches. Reuse the old part.
|
||||
result = append(result, *part)
|
||||
current++
|
||||
continue NextSection
|
||||
}
|
||||
reuse++
|
||||
}
|
||||
|
||||
// Part wasn't found or doesn't match. Send it.
|
||||
part, err := m.putPart(current, section, partSize, md5b64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, part)
|
||||
current++
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type completeUpload struct {
|
||||
XMLName xml.Name `xml:"CompleteMultipartUpload"`
|
||||
Parts completeParts `xml:"Part"`
|
||||
}
|
||||
|
||||
type completePart struct {
|
||||
PartNumber int
|
||||
ETag string
|
||||
}
|
||||
|
||||
type completeParts []completePart
|
||||
|
||||
func (p completeParts) Len() int { return len(p) }
|
||||
func (p completeParts) Less(i, j int) bool { return p[i].PartNumber < p[j].PartNumber }
|
||||
func (p completeParts) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
|
||||
// Complete assembles the given previously uploaded parts into the
|
||||
// final object. This operation may take several minutes.
|
||||
//
|
||||
// See http://goo.gl/2Z7Tw for details.
|
||||
func (m *Multi) Complete(parts []Part) error {
|
||||
params := map[string][]string{
|
||||
"uploadId": {m.UploadId},
|
||||
}
|
||||
c := completeUpload{}
|
||||
for _, p := range parts {
|
||||
c.Parts = append(c.Parts, completePart{p.N, p.ETag})
|
||||
}
|
||||
sort.Sort(c.Parts)
|
||||
data, err := xml.Marshal(&c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for attempt := attempts.Start(); attempt.Next(); {
|
||||
req := &request{
|
||||
method: "POST",
|
||||
bucket: m.Bucket.Name,
|
||||
path: m.Key,
|
||||
params: params,
|
||||
payload: bytes.NewReader(data),
|
||||
}
|
||||
err := m.Bucket.S3.query(req, nil)
|
||||
if shouldRetry(err) && attempt.HasNext() {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Abort deletes an unifinished multipart upload and any previously
|
||||
// uploaded parts for it.
|
||||
//
|
||||
// After a multipart upload is aborted, no additional parts can be
|
||||
// uploaded using it. However, if any part uploads are currently in
|
||||
// progress, those part uploads might or might not succeed. As a result,
|
||||
// it might be necessary to abort a given multipart upload multiple
|
||||
// times in order to completely free all storage consumed by all parts.
|
||||
//
|
||||
// NOTE: If the described scenario happens to you, please report back to
|
||||
// the goamz authors with details. In the future such retrying should be
|
||||
// handled internally, but it's not clear what happens precisely (Is an
|
||||
// error returned? Is the issue completely undetectable?).
|
||||
//
|
||||
// See http://goo.gl/dnyJw for details.
|
||||
func (m *Multi) Abort() error {
|
||||
params := map[string][]string{
|
||||
"uploadId": {m.UploadId},
|
||||
}
|
||||
for attempt := attempts.Start(); attempt.Next(); {
|
||||
req := &request{
|
||||
method: "DELETE",
|
||||
bucket: m.Bucket.Name,
|
||||
path: m.Key,
|
||||
params: params,
|
||||
}
|
||||
err := m.Bucket.S3.query(req, nil)
|
||||
if shouldRetry(err) && attempt.HasNext() {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
370
vendor/src/launchpad.net/goamz/s3/multi_test.go
vendored
Normal file
370
vendor/src/launchpad.net/goamz/s3/multi_test.go
vendored
Normal file
@@ -0,0 +1,370 @@
|
||||
package s3_test
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"launchpad.net/goamz/s3"
|
||||
. "launchpad.net/gocheck"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (s *S) TestInitMulti(c *C) {
|
||||
testServer.Response(200, nil, InitMultiResultDump)
|
||||
|
||||
b := s.s3.Bucket("sample")
|
||||
|
||||
multi, err := b.InitMulti("multi", "text/plain", s3.Private)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "POST")
|
||||
c.Assert(req.URL.Path, Equals, "/sample/multi")
|
||||
c.Assert(req.Header["Content-Type"], DeepEquals, []string{"text/plain"})
|
||||
c.Assert(req.Header["X-Amz-Acl"], DeepEquals, []string{"private"})
|
||||
c.Assert(req.Form["uploads"], DeepEquals, []string{""})
|
||||
|
||||
c.Assert(multi.UploadId, Matches, "JNbR_[A-Za-z0-9.]+QQ--")
|
||||
}
|
||||
|
||||
func (s *S) TestMultiNoPreviousUpload(c *C) {
|
||||
// Don't retry the NoSuchUpload error.
|
||||
s3.RetryAttempts(false)
|
||||
|
||||
testServer.Response(404, nil, NoSuchUploadErrorDump)
|
||||
testServer.Response(200, nil, InitMultiResultDump)
|
||||
|
||||
b := s.s3.Bucket("sample")
|
||||
|
||||
multi, err := b.Multi("multi", "text/plain", s3.Private)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/sample/")
|
||||
c.Assert(req.Form["uploads"], DeepEquals, []string{""})
|
||||
c.Assert(req.Form["prefix"], DeepEquals, []string{"multi"})
|
||||
|
||||
req = testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "POST")
|
||||
c.Assert(req.URL.Path, Equals, "/sample/multi")
|
||||
c.Assert(req.Form["uploads"], DeepEquals, []string{""})
|
||||
|
||||
c.Assert(multi.UploadId, Matches, "JNbR_[A-Za-z0-9.]+QQ--")
|
||||
}
|
||||
|
||||
func (s *S) TestMultiReturnOld(c *C) {
|
||||
testServer.Response(200, nil, ListMultiResultDump)
|
||||
|
||||
b := s.s3.Bucket("sample")
|
||||
|
||||
multi, err := b.Multi("multi1", "text/plain", s3.Private)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(multi.Key, Equals, "multi1")
|
||||
c.Assert(multi.UploadId, Equals, "iUVug89pPvSswrikD")
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/sample/")
|
||||
c.Assert(req.Form["uploads"], DeepEquals, []string{""})
|
||||
c.Assert(req.Form["prefix"], DeepEquals, []string{"multi1"})
|
||||
}
|
||||
|
||||
func (s *S) TestListParts(c *C) {
|
||||
testServer.Response(200, nil, InitMultiResultDump)
|
||||
testServer.Response(200, nil, ListPartsResultDump1)
|
||||
testServer.Response(404, nil, NoSuchUploadErrorDump) // :-(
|
||||
testServer.Response(200, nil, ListPartsResultDump2)
|
||||
|
||||
b := s.s3.Bucket("sample")
|
||||
|
||||
multi, err := b.InitMulti("multi", "text/plain", s3.Private)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
parts, err := multi.ListParts()
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(parts, HasLen, 3)
|
||||
c.Assert(parts[0].N, Equals, 1)
|
||||
c.Assert(parts[0].Size, Equals, int64(5))
|
||||
c.Assert(parts[0].ETag, Equals, `"ffc88b4ca90a355f8ddba6b2c3b2af5c"`)
|
||||
c.Assert(parts[1].N, Equals, 2)
|
||||
c.Assert(parts[1].Size, Equals, int64(5))
|
||||
c.Assert(parts[1].ETag, Equals, `"d067a0fa9dc61a6e7195ca99696b5a89"`)
|
||||
c.Assert(parts[2].N, Equals, 3)
|
||||
c.Assert(parts[2].Size, Equals, int64(5))
|
||||
c.Assert(parts[2].ETag, Equals, `"49dcd91231f801159e893fb5c6674985"`)
|
||||
testServer.WaitRequest()
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/sample/multi")
|
||||
c.Assert(req.Form.Get("uploadId"), Matches, "JNbR_[A-Za-z0-9.]+QQ--")
|
||||
c.Assert(req.Form["max-parts"], DeepEquals, []string{"1000"})
|
||||
|
||||
testServer.WaitRequest() // The internal error.
|
||||
req = testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/sample/multi")
|
||||
c.Assert(req.Form.Get("uploadId"), Matches, "JNbR_[A-Za-z0-9.]+QQ--")
|
||||
c.Assert(req.Form["max-parts"], DeepEquals, []string{"1000"})
|
||||
c.Assert(req.Form["part-number-marker"], DeepEquals, []string{"2"})
|
||||
}
|
||||
|
||||
func (s *S) TestPutPart(c *C) {
|
||||
headers := map[string]string{
|
||||
"ETag": `"26f90efd10d614f100252ff56d88dad8"`,
|
||||
}
|
||||
testServer.Response(200, nil, InitMultiResultDump)
|
||||
testServer.Response(200, headers, "")
|
||||
|
||||
b := s.s3.Bucket("sample")
|
||||
|
||||
multi, err := b.InitMulti("multi", "text/plain", s3.Private)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
part, err := multi.PutPart(1, strings.NewReader("<part 1>"))
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(part.N, Equals, 1)
|
||||
c.Assert(part.Size, Equals, int64(8))
|
||||
c.Assert(part.ETag, Equals, headers["ETag"])
|
||||
|
||||
testServer.WaitRequest()
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "PUT")
|
||||
c.Assert(req.URL.Path, Equals, "/sample/multi")
|
||||
c.Assert(req.Form.Get("uploadId"), Matches, "JNbR_[A-Za-z0-9.]+QQ--")
|
||||
c.Assert(req.Form["partNumber"], DeepEquals, []string{"1"})
|
||||
c.Assert(req.Header["Content-Length"], DeepEquals, []string{"8"})
|
||||
c.Assert(req.Header["Content-Md5"], DeepEquals, []string{"JvkO/RDWFPEAJS/1bYja2A=="})
|
||||
}
|
||||
|
||||
func readAll(r io.Reader) string {
|
||||
data, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func (s *S) TestPutAllNoPreviousUpload(c *C) {
|
||||
// Don't retry the NoSuchUpload error.
|
||||
s3.RetryAttempts(false)
|
||||
|
||||
etag1 := map[string]string{"ETag": `"etag1"`}
|
||||
etag2 := map[string]string{"ETag": `"etag2"`}
|
||||
etag3 := map[string]string{"ETag": `"etag3"`}
|
||||
testServer.Response(200, nil, InitMultiResultDump)
|
||||
testServer.Response(404, nil, NoSuchUploadErrorDump)
|
||||
testServer.Response(200, etag1, "")
|
||||
testServer.Response(200, etag2, "")
|
||||
testServer.Response(200, etag3, "")
|
||||
|
||||
b := s.s3.Bucket("sample")
|
||||
|
||||
multi, err := b.InitMulti("multi", "text/plain", s3.Private)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
parts, err := multi.PutAll(strings.NewReader("part1part2last"), 5)
|
||||
c.Assert(parts, HasLen, 3)
|
||||
c.Assert(parts[0].ETag, Equals, `"etag1"`)
|
||||
c.Assert(parts[1].ETag, Equals, `"etag2"`)
|
||||
c.Assert(parts[2].ETag, Equals, `"etag3"`)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
// Init
|
||||
testServer.WaitRequest()
|
||||
|
||||
// List old parts. Won't find anything.
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/sample/multi")
|
||||
|
||||
// Send part 1.
|
||||
req = testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "PUT")
|
||||
c.Assert(req.URL.Path, Equals, "/sample/multi")
|
||||
c.Assert(req.Form["partNumber"], DeepEquals, []string{"1"})
|
||||
c.Assert(req.Header["Content-Length"], DeepEquals, []string{"5"})
|
||||
c.Assert(readAll(req.Body), Equals, "part1")
|
||||
|
||||
// Send part 2.
|
||||
req = testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "PUT")
|
||||
c.Assert(req.URL.Path, Equals, "/sample/multi")
|
||||
c.Assert(req.Form["partNumber"], DeepEquals, []string{"2"})
|
||||
c.Assert(req.Header["Content-Length"], DeepEquals, []string{"5"})
|
||||
c.Assert(readAll(req.Body), Equals, "part2")
|
||||
|
||||
// Send part 3 with shorter body.
|
||||
req = testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "PUT")
|
||||
c.Assert(req.URL.Path, Equals, "/sample/multi")
|
||||
c.Assert(req.Form["partNumber"], DeepEquals, []string{"3"})
|
||||
c.Assert(req.Header["Content-Length"], DeepEquals, []string{"4"})
|
||||
c.Assert(readAll(req.Body), Equals, "last")
|
||||
}
|
||||
|
||||
func (s *S) TestPutAllZeroSizeFile(c *C) {
|
||||
// Don't retry the NoSuchUpload error.
|
||||
s3.RetryAttempts(false)
|
||||
|
||||
etag1 := map[string]string{"ETag": `"etag1"`}
|
||||
testServer.Response(200, nil, InitMultiResultDump)
|
||||
testServer.Response(404, nil, NoSuchUploadErrorDump)
|
||||
testServer.Response(200, etag1, "")
|
||||
|
||||
b := s.s3.Bucket("sample")
|
||||
|
||||
multi, err := b.InitMulti("multi", "text/plain", s3.Private)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
// Must send at least one part, so that completing it will work.
|
||||
parts, err := multi.PutAll(strings.NewReader(""), 5)
|
||||
c.Assert(parts, HasLen, 1)
|
||||
c.Assert(parts[0].ETag, Equals, `"etag1"`)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
// Init
|
||||
testServer.WaitRequest()
|
||||
|
||||
// List old parts. Won't find anything.
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/sample/multi")
|
||||
|
||||
// Send empty part.
|
||||
req = testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "PUT")
|
||||
c.Assert(req.URL.Path, Equals, "/sample/multi")
|
||||
c.Assert(req.Form["partNumber"], DeepEquals, []string{"1"})
|
||||
c.Assert(req.Header["Content-Length"], DeepEquals, []string{"0"})
|
||||
c.Assert(readAll(req.Body), Equals, "")
|
||||
}
|
||||
|
||||
func (s *S) TestPutAllResume(c *C) {
|
||||
etag2 := map[string]string{"ETag": `"etag2"`}
|
||||
testServer.Response(200, nil, InitMultiResultDump)
|
||||
testServer.Response(200, nil, ListPartsResultDump1)
|
||||
testServer.Response(200, nil, ListPartsResultDump2)
|
||||
testServer.Response(200, etag2, "")
|
||||
|
||||
b := s.s3.Bucket("sample")
|
||||
|
||||
multi, err := b.InitMulti("multi", "text/plain", s3.Private)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
// "part1" and "part3" match the checksums in ResultDump1.
|
||||
// The middle one is a mismatch (it refers to "part2").
|
||||
parts, err := multi.PutAll(strings.NewReader("part1partXpart3"), 5)
|
||||
c.Assert(parts, HasLen, 3)
|
||||
c.Assert(parts[0].N, Equals, 1)
|
||||
c.Assert(parts[0].Size, Equals, int64(5))
|
||||
c.Assert(parts[0].ETag, Equals, `"ffc88b4ca90a355f8ddba6b2c3b2af5c"`)
|
||||
c.Assert(parts[1].N, Equals, 2)
|
||||
c.Assert(parts[1].Size, Equals, int64(5))
|
||||
c.Assert(parts[1].ETag, Equals, `"etag2"`)
|
||||
c.Assert(parts[2].N, Equals, 3)
|
||||
c.Assert(parts[2].Size, Equals, int64(5))
|
||||
c.Assert(parts[2].ETag, Equals, `"49dcd91231f801159e893fb5c6674985"`)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
// Init
|
||||
testServer.WaitRequest()
|
||||
|
||||
// List old parts, broken in two requests.
|
||||
for i := 0; i < 2; i++ {
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/sample/multi")
|
||||
}
|
||||
|
||||
// Send part 2, as it didn't match the checksum.
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "PUT")
|
||||
c.Assert(req.URL.Path, Equals, "/sample/multi")
|
||||
c.Assert(req.Form["partNumber"], DeepEquals, []string{"2"})
|
||||
c.Assert(req.Header["Content-Length"], DeepEquals, []string{"5"})
|
||||
c.Assert(readAll(req.Body), Equals, "partX")
|
||||
}
|
||||
|
||||
func (s *S) TestMultiComplete(c *C) {
|
||||
testServer.Response(200, nil, InitMultiResultDump)
|
||||
// Note the 200 response. Completing will hold the connection on some
|
||||
// kind of long poll, and may return a late error even after a 200.
|
||||
testServer.Response(200, nil, InternalErrorDump)
|
||||
testServer.Response(200, nil, "")
|
||||
|
||||
b := s.s3.Bucket("sample")
|
||||
|
||||
multi, err := b.InitMulti("multi", "text/plain", s3.Private)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = multi.Complete([]s3.Part{{2, `"ETag2"`, 32}, {1, `"ETag1"`, 64}})
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
testServer.WaitRequest()
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "POST")
|
||||
c.Assert(req.URL.Path, Equals, "/sample/multi")
|
||||
c.Assert(req.Form.Get("uploadId"), Matches, "JNbR_[A-Za-z0-9.]+QQ--")
|
||||
|
||||
var payload struct {
|
||||
XMLName xml.Name
|
||||
Part []struct {
|
||||
PartNumber int
|
||||
ETag string
|
||||
}
|
||||
}
|
||||
|
||||
dec := xml.NewDecoder(req.Body)
|
||||
err = dec.Decode(&payload)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Assert(payload.XMLName.Local, Equals, "CompleteMultipartUpload")
|
||||
c.Assert(len(payload.Part), Equals, 2)
|
||||
c.Assert(payload.Part[0].PartNumber, Equals, 1)
|
||||
c.Assert(payload.Part[0].ETag, Equals, `"ETag1"`)
|
||||
c.Assert(payload.Part[1].PartNumber, Equals, 2)
|
||||
c.Assert(payload.Part[1].ETag, Equals, `"ETag2"`)
|
||||
}
|
||||
|
||||
func (s *S) TestMultiAbort(c *C) {
|
||||
testServer.Response(200, nil, InitMultiResultDump)
|
||||
testServer.Response(200, nil, "")
|
||||
|
||||
b := s.s3.Bucket("sample")
|
||||
|
||||
multi, err := b.InitMulti("multi", "text/plain", s3.Private)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = multi.Abort()
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
testServer.WaitRequest()
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "DELETE")
|
||||
c.Assert(req.URL.Path, Equals, "/sample/multi")
|
||||
c.Assert(req.Form.Get("uploadId"), Matches, "JNbR_[A-Za-z0-9.]+QQ--")
|
||||
}
|
||||
|
||||
func (s *S) TestListMulti(c *C) {
|
||||
testServer.Response(200, nil, ListMultiResultDump)
|
||||
|
||||
b := s.s3.Bucket("sample")
|
||||
|
||||
multis, prefixes, err := b.ListMulti("", "/")
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(prefixes, DeepEquals, []string{"a/", "b/"})
|
||||
c.Assert(multis, HasLen, 2)
|
||||
c.Assert(multis[0].Key, Equals, "multi1")
|
||||
c.Assert(multis[0].UploadId, Equals, "iUVug89pPvSswrikD")
|
||||
c.Assert(multis[1].Key, Equals, "multi2")
|
||||
c.Assert(multis[1].UploadId, Equals, "DkirwsSvPp98guVUi")
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/sample/")
|
||||
c.Assert(req.Form["uploads"], DeepEquals, []string{""})
|
||||
c.Assert(req.Form["prefix"], DeepEquals, []string{""})
|
||||
c.Assert(req.Form["delimiter"], DeepEquals, []string{"/"})
|
||||
c.Assert(req.Form["max-uploads"], DeepEquals, []string{"1000"})
|
||||
}
|
||||
198
vendor/src/launchpad.net/goamz/s3/responses_test.go
vendored
Normal file
198
vendor/src/launchpad.net/goamz/s3/responses_test.go
vendored
Normal file
@@ -0,0 +1,198 @@
|
||||
package s3_test
|
||||
|
||||
var GetObjectErrorDump = `
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Error><Code>NoSuchBucket</Code><Message>The specified bucket does not exist</Message>
|
||||
<BucketName>non-existent-bucket</BucketName><RequestId>3F1B667FAD71C3D8</RequestId>
|
||||
<HostId>L4ee/zrm1irFXY5F45fKXIRdOf9ktsKY/8TDVawuMK2jWRb1RF84i1uBzkdNqS5D</HostId></Error>
|
||||
`
|
||||
|
||||
var GetListResultDump1 = `
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01">
|
||||
<Name>quotes</Name>
|
||||
<Prefix>N</Prefix>
|
||||
<IsTruncated>false</IsTruncated>
|
||||
<Contents>
|
||||
<Key>Nelson</Key>
|
||||
<LastModified>2006-01-01T12:00:00.000Z</LastModified>
|
||||
<ETag>"828ef3fdfa96f00ad9f27c383fc9ac7f"</ETag>
|
||||
<Size>5</Size>
|
||||
<StorageClass>STANDARD</StorageClass>
|
||||
<Owner>
|
||||
<ID>bcaf161ca5fb16fd081034f</ID>
|
||||
<DisplayName>webfile</DisplayName>
|
||||
</Owner>
|
||||
</Contents>
|
||||
<Contents>
|
||||
<Key>Neo</Key>
|
||||
<LastModified>2006-01-01T12:00:00.000Z</LastModified>
|
||||
<ETag>"828ef3fdfa96f00ad9f27c383fc9ac7f"</ETag>
|
||||
<Size>4</Size>
|
||||
<StorageClass>STANDARD</StorageClass>
|
||||
<Owner>
|
||||
<ID>bcaf1ffd86a5fb16fd081034f</ID>
|
||||
<DisplayName>webfile</DisplayName>
|
||||
</Owner>
|
||||
</Contents>
|
||||
</ListBucketResult>
|
||||
`
|
||||
|
||||
var GetListResultDump2 = `
|
||||
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<Name>example-bucket</Name>
|
||||
<Prefix>photos/2006/</Prefix>
|
||||
<Marker>some-marker</Marker>
|
||||
<MaxKeys>1000</MaxKeys>
|
||||
<Delimiter>/</Delimiter>
|
||||
<IsTruncated>false</IsTruncated>
|
||||
|
||||
<CommonPrefixes>
|
||||
<Prefix>photos/2006/feb/</Prefix>
|
||||
</CommonPrefixes>
|
||||
<CommonPrefixes>
|
||||
<Prefix>photos/2006/jan/</Prefix>
|
||||
</CommonPrefixes>
|
||||
</ListBucketResult>
|
||||
`
|
||||
|
||||
var InitMultiResultDump = `
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<InitiateMultipartUploadResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<Bucket>sample</Bucket>
|
||||
<Key>multi</Key>
|
||||
<UploadId>JNbR_cMdwnGiD12jKAd6WK2PUkfj2VxA7i4nCwjE6t71nI9Tl3eVDPFlU0nOixhftH7I17ZPGkV3QA.l7ZD.QQ--</UploadId>
|
||||
</InitiateMultipartUploadResult>
|
||||
`
|
||||
|
||||
var ListPartsResultDump1 = `
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ListPartsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<Bucket>sample</Bucket>
|
||||
<Key>multi</Key>
|
||||
<UploadId>JNbR_cMdwnGiD12jKAd6WK2PUkfj2VxA7i4nCwjE6t71nI9Tl3eVDPFlU0nOixhftH7I17ZPGkV3QA.l7ZD.QQ--</UploadId>
|
||||
<Initiator>
|
||||
<ID>bb5c0f63b0b25f2d099c</ID>
|
||||
<DisplayName>joe</DisplayName>
|
||||
</Initiator>
|
||||
<Owner>
|
||||
<ID>bb5c0f63b0b25f2d099c</ID>
|
||||
<DisplayName>joe</DisplayName>
|
||||
</Owner>
|
||||
<StorageClass>STANDARD</StorageClass>
|
||||
<PartNumberMarker>0</PartNumberMarker>
|
||||
<NextPartNumberMarker>2</NextPartNumberMarker>
|
||||
<MaxParts>2</MaxParts>
|
||||
<IsTruncated>true</IsTruncated>
|
||||
<Part>
|
||||
<PartNumber>1</PartNumber>
|
||||
<LastModified>2013-01-30T13:45:51.000Z</LastModified>
|
||||
<ETag>"ffc88b4ca90a355f8ddba6b2c3b2af5c"</ETag>
|
||||
<Size>5</Size>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartNumber>2</PartNumber>
|
||||
<LastModified>2013-01-30T13:45:52.000Z</LastModified>
|
||||
<ETag>"d067a0fa9dc61a6e7195ca99696b5a89"</ETag>
|
||||
<Size>5</Size>
|
||||
</Part>
|
||||
</ListPartsResult>
|
||||
`
|
||||
|
||||
var ListPartsResultDump2 = `
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ListPartsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<Bucket>sample</Bucket>
|
||||
<Key>multi</Key>
|
||||
<UploadId>JNbR_cMdwnGiD12jKAd6WK2PUkfj2VxA7i4nCwjE6t71nI9Tl3eVDPFlU0nOixhftH7I17ZPGkV3QA.l7ZD.QQ--</UploadId>
|
||||
<Initiator>
|
||||
<ID>bb5c0f63b0b25f2d099c</ID>
|
||||
<DisplayName>joe</DisplayName>
|
||||
</Initiator>
|
||||
<Owner>
|
||||
<ID>bb5c0f63b0b25f2d099c</ID>
|
||||
<DisplayName>joe</DisplayName>
|
||||
</Owner>
|
||||
<StorageClass>STANDARD</StorageClass>
|
||||
<PartNumberMarker>2</PartNumberMarker>
|
||||
<NextPartNumberMarker>3</NextPartNumberMarker>
|
||||
<MaxParts>2</MaxParts>
|
||||
<IsTruncated>false</IsTruncated>
|
||||
<Part>
|
||||
<PartNumber>3</PartNumber>
|
||||
<LastModified>2013-01-30T13:46:50.000Z</LastModified>
|
||||
<ETag>"49dcd91231f801159e893fb5c6674985"</ETag>
|
||||
<Size>5</Size>
|
||||
</Part>
|
||||
</ListPartsResult>
|
||||
`
|
||||
|
||||
var ListMultiResultDump = `
|
||||
<?xml version="1.0"?>
|
||||
<ListMultipartUploadsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<Bucket>goamz-test-bucket-us-east-1-akiajk3wyewhctyqbf7a</Bucket>
|
||||
<KeyMarker/>
|
||||
<UploadIdMarker/>
|
||||
<NextKeyMarker>multi1</NextKeyMarker>
|
||||
<NextUploadIdMarker>iUVug89pPvSswrikD72p8uO62EzhNtpDxRmwC5WSiWDdK9SfzmDqe3xpP1kMWimyimSnz4uzFc3waVM5ufrKYQ--</NextUploadIdMarker>
|
||||
<Delimiter>/</Delimiter>
|
||||
<MaxUploads>1000</MaxUploads>
|
||||
<IsTruncated>false</IsTruncated>
|
||||
<Upload>
|
||||
<Key>multi1</Key>
|
||||
<UploadId>iUVug89pPvSswrikD</UploadId>
|
||||
<Initiator>
|
||||
<ID>bb5c0f63b0b25f2d0</ID>
|
||||
<DisplayName>gustavoniemeyer</DisplayName>
|
||||
</Initiator>
|
||||
<Owner>
|
||||
<ID>bb5c0f63b0b25f2d0</ID>
|
||||
<DisplayName>gustavoniemeyer</DisplayName>
|
||||
</Owner>
|
||||
<StorageClass>STANDARD</StorageClass>
|
||||
<Initiated>2013-01-30T18:15:47.000Z</Initiated>
|
||||
</Upload>
|
||||
<Upload>
|
||||
<Key>multi2</Key>
|
||||
<UploadId>DkirwsSvPp98guVUi</UploadId>
|
||||
<Initiator>
|
||||
<ID>bb5c0f63b0b25f2d0</ID>
|
||||
<DisplayName>joe</DisplayName>
|
||||
</Initiator>
|
||||
<Owner>
|
||||
<ID>bb5c0f63b0b25f2d0</ID>
|
||||
<DisplayName>joe</DisplayName>
|
||||
</Owner>
|
||||
<StorageClass>STANDARD</StorageClass>
|
||||
<Initiated>2013-01-30T18:15:47.000Z</Initiated>
|
||||
</Upload>
|
||||
<CommonPrefixes>
|
||||
<Prefix>a/</Prefix>
|
||||
</CommonPrefixes>
|
||||
<CommonPrefixes>
|
||||
<Prefix>b/</Prefix>
|
||||
</CommonPrefixes>
|
||||
</ListMultipartUploadsResult>
|
||||
`
|
||||
|
||||
var NoSuchUploadErrorDump = `
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Error>
|
||||
<Code>NoSuchUpload</Code>
|
||||
<Message>Not relevant</Message>
|
||||
<BucketName>sample</BucketName>
|
||||
<RequestId>3F1B667FAD71C3D8</RequestId>
|
||||
<HostId>kjhwqk</HostId>
|
||||
</Error>
|
||||
`
|
||||
|
||||
var InternalErrorDump = `
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Error>
|
||||
<Code>InternalError</Code>
|
||||
<Message>Not relevant</Message>
|
||||
<BucketName>sample</BucketName>
|
||||
<RequestId>3F1B667FAD71C3D8</RequestId>
|
||||
<HostId>kjhwqk</HostId>
|
||||
</Error>
|
||||
`
|
||||
572
vendor/src/launchpad.net/goamz/s3/s3.go
vendored
Normal file
572
vendor/src/launchpad.net/goamz/s3/s3.go
vendored
Normal file
@@ -0,0 +1,572 @@
|
||||
//
|
||||
// goamz - Go packages to interact with the Amazon Web Services.
|
||||
//
|
||||
// https://wiki.ubuntu.com/goamz
|
||||
//
|
||||
// Copyright (c) 2011 Canonical Ltd.
|
||||
//
|
||||
|
||||
package s3
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"launchpad.net/goamz/aws"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const debug = false
|
||||
|
||||
// The S3 type encapsulates operations with an S3 region.
|
||||
type S3 struct {
|
||||
aws.Auth
|
||||
aws.Region
|
||||
private byte // Reserve the right of using private data.
|
||||
}
|
||||
|
||||
// The Bucket type encapsulates operations with an S3 bucket.
|
||||
type Bucket struct {
|
||||
*S3
|
||||
Name string
|
||||
}
|
||||
|
||||
// The Owner type represents the owner of the object in an S3 bucket.
|
||||
type Owner struct {
|
||||
ID string
|
||||
DisplayName string
|
||||
}
|
||||
|
||||
var (
|
||||
attempts = defaultAttempts
|
||||
defaultAttempts = aws.AttemptStrategy{
|
||||
Min: 5,
|
||||
Total: 5 * time.Second,
|
||||
Delay: 200 * time.Millisecond,
|
||||
}
|
||||
)
|
||||
|
||||
// RetryAttempts sets whether failing S3 requests may be retried to cope
|
||||
// with eventual consistency or temporary failures. It should not be
|
||||
// called while operations are in progress.
|
||||
func RetryAttempts(retry bool) {
|
||||
if retry {
|
||||
attempts = defaultAttempts
|
||||
} else {
|
||||
attempts = aws.AttemptStrategy{}
|
||||
}
|
||||
}
|
||||
|
||||
// New creates a new S3.
|
||||
func New(auth aws.Auth, region aws.Region) *S3 {
|
||||
return &S3{auth, region, 0}
|
||||
}
|
||||
|
||||
// Bucket returns a Bucket with the given name.
|
||||
func (s3 *S3) Bucket(name string) *Bucket {
|
||||
if s3.Region.S3BucketEndpoint != "" || s3.Region.S3LowercaseBucket {
|
||||
name = strings.ToLower(name)
|
||||
}
|
||||
return &Bucket{s3, name}
|
||||
}
|
||||
|
||||
var createBucketConfiguration = `<CreateBucketConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<LocationConstraint>%s</LocationConstraint>
|
||||
</CreateBucketConfiguration>`
|
||||
|
||||
// locationConstraint returns an io.Reader specifying a LocationConstraint if
|
||||
// required for the region.
|
||||
//
|
||||
// See http://goo.gl/bh9Kq for details.
|
||||
func (s3 *S3) locationConstraint() io.Reader {
|
||||
constraint := ""
|
||||
if s3.Region.S3LocationConstraint {
|
||||
constraint = fmt.Sprintf(createBucketConfiguration, s3.Region.Name)
|
||||
}
|
||||
return strings.NewReader(constraint)
|
||||
}
|
||||
|
||||
type ACL string
|
||||
|
||||
const (
|
||||
Private = ACL("private")
|
||||
PublicRead = ACL("public-read")
|
||||
PublicReadWrite = ACL("public-read-write")
|
||||
AuthenticatedRead = ACL("authenticated-read")
|
||||
BucketOwnerRead = ACL("bucket-owner-read")
|
||||
BucketOwnerFull = ACL("bucket-owner-full-control")
|
||||
)
|
||||
|
||||
// PutBucket creates a new bucket.
|
||||
//
|
||||
// See http://goo.gl/ndjnR for details.
|
||||
func (b *Bucket) PutBucket(perm ACL) error {
|
||||
headers := map[string][]string{
|
||||
"x-amz-acl": {string(perm)},
|
||||
}
|
||||
req := &request{
|
||||
method: "PUT",
|
||||
bucket: b.Name,
|
||||
path: "/",
|
||||
headers: headers,
|
||||
payload: b.locationConstraint(),
|
||||
}
|
||||
return b.S3.query(req, nil)
|
||||
}
|
||||
|
||||
// DelBucket removes an existing S3 bucket. All objects in the bucket must
|
||||
// be removed before the bucket itself can be removed.
|
||||
//
|
||||
// See http://goo.gl/GoBrY for details.
|
||||
func (b *Bucket) DelBucket() (err error) {
|
||||
req := &request{
|
||||
method: "DELETE",
|
||||
bucket: b.Name,
|
||||
path: "/",
|
||||
}
|
||||
for attempt := attempts.Start(); attempt.Next(); {
|
||||
err = b.S3.query(req, nil)
|
||||
if !shouldRetry(err) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Get retrieves an object from an S3 bucket.
|
||||
//
|
||||
// See http://goo.gl/isCO7 for details.
|
||||
func (b *Bucket) Get(path string) (data []byte, err error) {
|
||||
body, err := b.GetReader(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err = ioutil.ReadAll(body)
|
||||
body.Close()
|
||||
return data, err
|
||||
}
|
||||
|
||||
// GetReader retrieves an object from an S3 bucket.
|
||||
// It is the caller's responsibility to call Close on rc when
|
||||
// finished reading.
|
||||
func (b *Bucket) GetReader(path string) (rc io.ReadCloser, err error) {
|
||||
req := &request{
|
||||
bucket: b.Name,
|
||||
path: path,
|
||||
}
|
||||
err = b.S3.prepare(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for attempt := attempts.Start(); attempt.Next(); {
|
||||
hresp, err := b.S3.run(req)
|
||||
if shouldRetry(err) && attempt.HasNext() {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return hresp.Body, nil
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Put inserts an object into the S3 bucket.
|
||||
//
|
||||
// See http://goo.gl/FEBPD for details.
|
||||
func (b *Bucket) Put(path string, data []byte, contType string, perm ACL) error {
|
||||
body := bytes.NewBuffer(data)
|
||||
return b.PutReader(path, body, int64(len(data)), contType, perm)
|
||||
}
|
||||
|
||||
// PutReader inserts an object into the S3 bucket by consuming data
|
||||
// from r until EOF.
|
||||
func (b *Bucket) PutReader(path string, r io.Reader, length int64, contType string, perm ACL) error {
|
||||
headers := map[string][]string{
|
||||
"Content-Length": {strconv.FormatInt(length, 10)},
|
||||
"Content-Type": {contType},
|
||||
"x-amz-acl": {string(perm)},
|
||||
}
|
||||
req := &request{
|
||||
method: "PUT",
|
||||
bucket: b.Name,
|
||||
path: path,
|
||||
headers: headers,
|
||||
payload: r,
|
||||
}
|
||||
return b.S3.query(req, nil)
|
||||
}
|
||||
|
||||
// Del removes an object from the S3 bucket.
|
||||
//
|
||||
// See http://goo.gl/APeTt for details.
|
||||
func (b *Bucket) Del(path string) error {
|
||||
req := &request{
|
||||
method: "DELETE",
|
||||
bucket: b.Name,
|
||||
path: path,
|
||||
}
|
||||
return b.S3.query(req, nil)
|
||||
}
|
||||
|
||||
// The ListResp type holds the results of a List bucket operation.
|
||||
type ListResp struct {
|
||||
Name string
|
||||
Prefix string
|
||||
Delimiter string
|
||||
Marker string
|
||||
MaxKeys int
|
||||
// IsTruncated is true if the results have been truncated because
|
||||
// there are more keys and prefixes than can fit in MaxKeys.
|
||||
// N.B. this is the opposite sense to that documented (incorrectly) in
|
||||
// http://goo.gl/YjQTc
|
||||
IsTruncated bool
|
||||
Contents []Key
|
||||
CommonPrefixes []string `xml:">Prefix"`
|
||||
}
|
||||
|
||||
// The Key type represents an item stored in an S3 bucket.
|
||||
type Key struct {
|
||||
Key string
|
||||
LastModified string
|
||||
Size int64
|
||||
// ETag gives the hex-encoded MD5 sum of the contents,
|
||||
// surrounded with double-quotes.
|
||||
ETag string
|
||||
StorageClass string
|
||||
Owner Owner
|
||||
}
|
||||
|
||||
// List returns information about objects in an S3 bucket.
|
||||
//
|
||||
// The prefix parameter limits the response to keys that begin with the
|
||||
// specified prefix.
|
||||
//
|
||||
// The delim parameter causes the response to group all of the keys that
|
||||
// share a common prefix up to the next delimiter in a single entry within
|
||||
// the CommonPrefixes field. You can use delimiters to separate a bucket
|
||||
// into different groupings of keys, similar to how folders would work.
|
||||
//
|
||||
// The marker parameter specifies the key to start with when listing objects
|
||||
// in a bucket. Amazon S3 lists objects in alphabetical order and
|
||||
// will return keys alphabetically greater than the marker.
|
||||
//
|
||||
// The max parameter specifies how many keys + common prefixes to return in
|
||||
// the response. The default is 1000.
|
||||
//
|
||||
// For example, given these keys in a bucket:
|
||||
//
|
||||
// index.html
|
||||
// index2.html
|
||||
// photos/2006/January/sample.jpg
|
||||
// photos/2006/February/sample2.jpg
|
||||
// photos/2006/February/sample3.jpg
|
||||
// photos/2006/February/sample4.jpg
|
||||
//
|
||||
// Listing this bucket with delimiter set to "/" would yield the
|
||||
// following result:
|
||||
//
|
||||
// &ListResp{
|
||||
// Name: "sample-bucket",
|
||||
// MaxKeys: 1000,
|
||||
// Delimiter: "/",
|
||||
// Contents: []Key{
|
||||
// {Key: "index.html", "index2.html"},
|
||||
// },
|
||||
// CommonPrefixes: []string{
|
||||
// "photos/",
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// Listing the same bucket with delimiter set to "/" and prefix set to
|
||||
// "photos/2006/" would yield the following result:
|
||||
//
|
||||
// &ListResp{
|
||||
// Name: "sample-bucket",
|
||||
// MaxKeys: 1000,
|
||||
// Delimiter: "/",
|
||||
// Prefix: "photos/2006/",
|
||||
// CommonPrefixes: []string{
|
||||
// "photos/2006/February/",
|
||||
// "photos/2006/January/",
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// See http://goo.gl/YjQTc for details.
|
||||
func (b *Bucket) List(prefix, delim, marker string, max int) (result *ListResp, err error) {
|
||||
params := map[string][]string{
|
||||
"prefix": {prefix},
|
||||
"delimiter": {delim},
|
||||
"marker": {marker},
|
||||
}
|
||||
if max != 0 {
|
||||
params["max-keys"] = []string{strconv.FormatInt(int64(max), 10)}
|
||||
}
|
||||
req := &request{
|
||||
bucket: b.Name,
|
||||
params: params,
|
||||
}
|
||||
result = &ListResp{}
|
||||
for attempt := attempts.Start(); attempt.Next(); {
|
||||
err = b.S3.query(req, result)
|
||||
if !shouldRetry(err) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// URL returns a non-signed URL that allows retriving the
|
||||
// object at path. It only works if the object is publicly
|
||||
// readable (see SignedURL).
|
||||
func (b *Bucket) URL(path string) string {
|
||||
req := &request{
|
||||
bucket: b.Name,
|
||||
path: path,
|
||||
}
|
||||
err := b.S3.prepare(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
u, err := req.url()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
u.RawQuery = ""
|
||||
return u.String()
|
||||
}
|
||||
|
||||
// SignedURL returns a signed URL that allows anyone holding the URL
|
||||
// to retrieve the object at path. The signature is valid until expires.
|
||||
func (b *Bucket) SignedURL(path string, expires time.Time) string {
|
||||
req := &request{
|
||||
bucket: b.Name,
|
||||
path: path,
|
||||
params: url.Values{"Expires": {strconv.FormatInt(expires.Unix(), 10)}},
|
||||
}
|
||||
err := b.S3.prepare(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
u, err := req.url()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return u.String()
|
||||
}
|
||||
|
||||
type request struct {
|
||||
method string
|
||||
bucket string
|
||||
path string
|
||||
signpath string
|
||||
params url.Values
|
||||
headers http.Header
|
||||
baseurl string
|
||||
payload io.Reader
|
||||
prepared bool
|
||||
}
|
||||
|
||||
func (req *request) url() (*url.URL, error) {
|
||||
u, err := url.Parse(req.baseurl)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad S3 endpoint URL %q: %v", req.baseurl, err)
|
||||
}
|
||||
u.RawQuery = req.params.Encode()
|
||||
u.Path = req.path
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// query prepares and runs the req request.
|
||||
// If resp is not nil, the XML data contained in the response
|
||||
// body will be unmarshalled on it.
|
||||
func (s3 *S3) query(req *request, resp interface{}) error {
|
||||
err := s3.prepare(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hresp, err := s3.run(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp != nil {
|
||||
err = xml.NewDecoder(hresp.Body).Decode(resp)
|
||||
}
|
||||
hresp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// prepare sets up req to be delivered to S3.
|
||||
func (s3 *S3) prepare(req *request) error {
|
||||
if !req.prepared {
|
||||
req.prepared = true
|
||||
if req.method == "" {
|
||||
req.method = "GET"
|
||||
}
|
||||
// Copy so they can be mutated without affecting on retries.
|
||||
params := make(url.Values)
|
||||
headers := make(http.Header)
|
||||
for k, v := range req.params {
|
||||
params[k] = v
|
||||
}
|
||||
for k, v := range req.headers {
|
||||
headers[k] = v
|
||||
}
|
||||
req.params = params
|
||||
req.headers = headers
|
||||
if !strings.HasPrefix(req.path, "/") {
|
||||
req.path = "/" + req.path
|
||||
}
|
||||
req.signpath = req.path
|
||||
if req.bucket != "" {
|
||||
req.baseurl = s3.Region.S3BucketEndpoint
|
||||
if req.baseurl == "" {
|
||||
// Use the path method to address the bucket.
|
||||
req.baseurl = s3.Region.S3Endpoint
|
||||
req.path = "/" + req.bucket + req.path
|
||||
} else {
|
||||
// Just in case, prevent injection.
|
||||
if strings.IndexAny(req.bucket, "/:@") >= 0 {
|
||||
return fmt.Errorf("bad S3 bucket: %q", req.bucket)
|
||||
}
|
||||
req.baseurl = strings.Replace(req.baseurl, "${bucket}", req.bucket, -1)
|
||||
}
|
||||
req.signpath = "/" + req.bucket + req.signpath
|
||||
}
|
||||
}
|
||||
|
||||
// Always sign again as it's not clear how far the
|
||||
// server has handled a previous attempt.
|
||||
u, err := url.Parse(req.baseurl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bad S3 endpoint URL %q: %v", req.baseurl, err)
|
||||
}
|
||||
req.headers["Host"] = []string{u.Host}
|
||||
req.headers["Date"] = []string{time.Now().In(time.UTC).Format(time.RFC1123)}
|
||||
sign(s3.Auth, req.method, req.signpath, req.params, req.headers)
|
||||
return nil
|
||||
}
|
||||
|
||||
// run sends req and returns the http response from the server.
|
||||
func (s3 *S3) run(req *request) (*http.Response, error) {
|
||||
if debug {
|
||||
log.Printf("Running S3 request: %#v", req)
|
||||
}
|
||||
|
||||
u, err := req.url()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hreq := http.Request{
|
||||
URL: u,
|
||||
Method: req.method,
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Close: true,
|
||||
Header: req.headers,
|
||||
}
|
||||
|
||||
if v, ok := req.headers["Content-Length"]; ok {
|
||||
hreq.ContentLength, _ = strconv.ParseInt(v[0], 10, 64)
|
||||
delete(req.headers, "Content-Length")
|
||||
}
|
||||
if req.payload != nil {
|
||||
hreq.Body = ioutil.NopCloser(req.payload)
|
||||
}
|
||||
|
||||
hresp, err := http.DefaultClient.Do(&hreq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if debug {
|
||||
dump, _ := httputil.DumpResponse(hresp, true)
|
||||
log.Printf("} -> %s\n", dump)
|
||||
}
|
||||
if hresp.StatusCode != 200 && hresp.StatusCode != 204 {
|
||||
return nil, buildError(hresp)
|
||||
}
|
||||
return hresp, err
|
||||
}
|
||||
|
||||
// Error represents an error in an operation with S3.
|
||||
type Error struct {
|
||||
StatusCode int // HTTP status code (200, 403, ...)
|
||||
Code string // EC2 error code ("UnsupportedOperation", ...)
|
||||
Message string // The human-oriented error message
|
||||
BucketName string
|
||||
RequestId string
|
||||
HostId string
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
func buildError(r *http.Response) error {
|
||||
if debug {
|
||||
log.Printf("got error (status code %v)", r.StatusCode)
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
log.Printf("\tread error: %v", err)
|
||||
} else {
|
||||
log.Printf("\tdata:\n%s\n\n", data)
|
||||
}
|
||||
r.Body = ioutil.NopCloser(bytes.NewBuffer(data))
|
||||
}
|
||||
|
||||
err := Error{}
|
||||
// TODO return error if Unmarshal fails?
|
||||
xml.NewDecoder(r.Body).Decode(&err)
|
||||
r.Body.Close()
|
||||
err.StatusCode = r.StatusCode
|
||||
if err.Message == "" {
|
||||
err.Message = r.Status
|
||||
}
|
||||
if debug {
|
||||
log.Printf("err: %#v\n", err)
|
||||
}
|
||||
return &err
|
||||
}
|
||||
|
||||
func shouldRetry(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
switch err {
|
||||
case io.ErrUnexpectedEOF, io.EOF:
|
||||
return true
|
||||
}
|
||||
switch e := err.(type) {
|
||||
case *net.DNSError:
|
||||
return true
|
||||
case *net.OpError:
|
||||
switch e.Op {
|
||||
case "read", "write":
|
||||
return true
|
||||
}
|
||||
case *Error:
|
||||
switch e.Code {
|
||||
case "InternalError", "NoSuchUpload", "NoSuchBucket":
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hasCode(err error, code string) bool {
|
||||
s3err, ok := err.(*Error)
|
||||
return ok && s3err.Code == code
|
||||
}
|
||||
283
vendor/src/launchpad.net/goamz/s3/s3_test.go
vendored
Normal file
283
vendor/src/launchpad.net/goamz/s3/s3_test.go
vendored
Normal file
@@ -0,0 +1,283 @@
|
||||
package s3_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"launchpad.net/goamz/aws"
|
||||
"launchpad.net/goamz/s3"
|
||||
"launchpad.net/goamz/testutil"
|
||||
. "launchpad.net/gocheck"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
TestingT(t)
|
||||
}
|
||||
|
||||
type S struct {
|
||||
s3 *s3.S3
|
||||
}
|
||||
|
||||
var _ = Suite(&S{})
|
||||
|
||||
var testServer = testutil.NewHTTPServer()
|
||||
|
||||
func (s *S) SetUpSuite(c *C) {
|
||||
testServer.Start()
|
||||
auth := aws.Auth{"abc", "123"}
|
||||
s.s3 = s3.New(auth, aws.Region{Name: "faux-region-1", S3Endpoint: testServer.URL})
|
||||
}
|
||||
|
||||
func (s *S) TearDownSuite(c *C) {
|
||||
s3.SetAttemptStrategy(nil)
|
||||
testServer.Stop()
|
||||
}
|
||||
|
||||
func (s *S) SetUpTest(c *C) {
|
||||
attempts := aws.AttemptStrategy{
|
||||
Total: 300 * time.Millisecond,
|
||||
Delay: 100 * time.Millisecond,
|
||||
}
|
||||
s3.SetAttemptStrategy(&attempts)
|
||||
}
|
||||
|
||||
func (s *S) TearDownTest(c *C) {
|
||||
testServer.Flush()
|
||||
}
|
||||
|
||||
// PutBucket docs: http://goo.gl/kBTCu
|
||||
|
||||
func (s *S) TestPutBucket(c *C) {
|
||||
testServer.Response(200, nil, "")
|
||||
|
||||
b := s.s3.Bucket("bucket")
|
||||
err := b.PutBucket(s3.Private)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "PUT")
|
||||
c.Assert(req.URL.Path, Equals, "/bucket/")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
}
|
||||
|
||||
// DeleteBucket docs: http://goo.gl/GoBrY
|
||||
|
||||
func (s *S) TestDelBucket(c *C) {
|
||||
testServer.Response(204, nil, "")
|
||||
|
||||
b := s.s3.Bucket("bucket")
|
||||
err := b.DelBucket()
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "DELETE")
|
||||
c.Assert(req.URL.Path, Equals, "/bucket/")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
}
|
||||
|
||||
// GetObject docs: http://goo.gl/isCO7
|
||||
|
||||
func (s *S) TestGet(c *C) {
|
||||
testServer.Response(200, nil, "content")
|
||||
|
||||
b := s.s3.Bucket("bucket")
|
||||
data, err := b.Get("name")
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/bucket/name")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(string(data), Equals, "content")
|
||||
}
|
||||
|
||||
func (s *S) TestURL(c *C) {
|
||||
testServer.Response(200, nil, "content")
|
||||
|
||||
b := s.s3.Bucket("bucket")
|
||||
url := b.URL("name")
|
||||
r, err := http.Get(url)
|
||||
c.Assert(err, IsNil)
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
r.Body.Close()
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(string(data), Equals, "content")
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/bucket/name")
|
||||
}
|
||||
|
||||
func (s *S) TestGetReader(c *C) {
|
||||
testServer.Response(200, nil, "content")
|
||||
|
||||
b := s.s3.Bucket("bucket")
|
||||
rc, err := b.GetReader("name")
|
||||
c.Assert(err, IsNil)
|
||||
data, err := ioutil.ReadAll(rc)
|
||||
rc.Close()
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(string(data), Equals, "content")
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/bucket/name")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
}
|
||||
|
||||
func (s *S) TestGetNotFound(c *C) {
|
||||
for i := 0; i < 10; i++ {
|
||||
testServer.Response(404, nil, GetObjectErrorDump)
|
||||
}
|
||||
|
||||
b := s.s3.Bucket("non-existent-bucket")
|
||||
data, err := b.Get("non-existent")
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/non-existent-bucket/non-existent")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
|
||||
s3err, _ := err.(*s3.Error)
|
||||
c.Assert(s3err, NotNil)
|
||||
c.Assert(s3err.StatusCode, Equals, 404)
|
||||
c.Assert(s3err.BucketName, Equals, "non-existent-bucket")
|
||||
c.Assert(s3err.RequestId, Equals, "3F1B667FAD71C3D8")
|
||||
c.Assert(s3err.HostId, Equals, "L4ee/zrm1irFXY5F45fKXIRdOf9ktsKY/8TDVawuMK2jWRb1RF84i1uBzkdNqS5D")
|
||||
c.Assert(s3err.Code, Equals, "NoSuchBucket")
|
||||
c.Assert(s3err.Message, Equals, "The specified bucket does not exist")
|
||||
c.Assert(s3err.Error(), Equals, "The specified bucket does not exist")
|
||||
c.Assert(data, IsNil)
|
||||
}
|
||||
|
||||
// PutObject docs: http://goo.gl/FEBPD
|
||||
|
||||
func (s *S) TestPutObject(c *C) {
|
||||
testServer.Response(200, nil, "")
|
||||
|
||||
b := s.s3.Bucket("bucket")
|
||||
err := b.Put("name", []byte("content"), "content-type", s3.Private)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "PUT")
|
||||
c.Assert(req.URL.Path, Equals, "/bucket/name")
|
||||
c.Assert(req.Header["Date"], Not(DeepEquals), []string{""})
|
||||
c.Assert(req.Header["Content-Type"], DeepEquals, []string{"content-type"})
|
||||
c.Assert(req.Header["Content-Length"], DeepEquals, []string{"7"})
|
||||
//c.Assert(req.Header["Content-MD5"], DeepEquals, "...")
|
||||
c.Assert(req.Header["X-Amz-Acl"], DeepEquals, []string{"private"})
|
||||
}
|
||||
|
||||
func (s *S) TestPutReader(c *C) {
|
||||
testServer.Response(200, nil, "")
|
||||
|
||||
b := s.s3.Bucket("bucket")
|
||||
buf := bytes.NewBufferString("content")
|
||||
err := b.PutReader("name", buf, int64(buf.Len()), "content-type", s3.Private)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "PUT")
|
||||
c.Assert(req.URL.Path, Equals, "/bucket/name")
|
||||
c.Assert(req.Header["Date"], Not(DeepEquals), []string{""})
|
||||
c.Assert(req.Header["Content-Type"], DeepEquals, []string{"content-type"})
|
||||
c.Assert(req.Header["Content-Length"], DeepEquals, []string{"7"})
|
||||
//c.Assert(req.Header["Content-MD5"], Equals, "...")
|
||||
c.Assert(req.Header["X-Amz-Acl"], DeepEquals, []string{"private"})
|
||||
}
|
||||
|
||||
// DelObject docs: http://goo.gl/APeTt
|
||||
|
||||
func (s *S) TestDelObject(c *C) {
|
||||
testServer.Response(200, nil, "")
|
||||
|
||||
b := s.s3.Bucket("bucket")
|
||||
err := b.Del("name")
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "DELETE")
|
||||
c.Assert(req.URL.Path, Equals, "/bucket/name")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
}
|
||||
|
||||
// Bucket List Objects docs: http://goo.gl/YjQTc
|
||||
|
||||
func (s *S) TestList(c *C) {
|
||||
testServer.Response(200, nil, GetListResultDump1)
|
||||
|
||||
b := s.s3.Bucket("quotes")
|
||||
|
||||
data, err := b.List("N", "", "", 0)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/quotes/")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
c.Assert(req.Form["prefix"], DeepEquals, []string{"N"})
|
||||
c.Assert(req.Form["delimiter"], DeepEquals, []string{""})
|
||||
c.Assert(req.Form["marker"], DeepEquals, []string{""})
|
||||
c.Assert(req.Form["max-keys"], DeepEquals, []string(nil))
|
||||
|
||||
c.Assert(data.Name, Equals, "quotes")
|
||||
c.Assert(data.Prefix, Equals, "N")
|
||||
c.Assert(data.IsTruncated, Equals, false)
|
||||
c.Assert(len(data.Contents), Equals, 2)
|
||||
|
||||
c.Assert(data.Contents[0].Key, Equals, "Nelson")
|
||||
c.Assert(data.Contents[0].LastModified, Equals, "2006-01-01T12:00:00.000Z")
|
||||
c.Assert(data.Contents[0].ETag, Equals, `"828ef3fdfa96f00ad9f27c383fc9ac7f"`)
|
||||
c.Assert(data.Contents[0].Size, Equals, int64(5))
|
||||
c.Assert(data.Contents[0].StorageClass, Equals, "STANDARD")
|
||||
c.Assert(data.Contents[0].Owner.ID, Equals, "bcaf161ca5fb16fd081034f")
|
||||
c.Assert(data.Contents[0].Owner.DisplayName, Equals, "webfile")
|
||||
|
||||
c.Assert(data.Contents[1].Key, Equals, "Neo")
|
||||
c.Assert(data.Contents[1].LastModified, Equals, "2006-01-01T12:00:00.000Z")
|
||||
c.Assert(data.Contents[1].ETag, Equals, `"828ef3fdfa96f00ad9f27c383fc9ac7f"`)
|
||||
c.Assert(data.Contents[1].Size, Equals, int64(4))
|
||||
c.Assert(data.Contents[1].StorageClass, Equals, "STANDARD")
|
||||
c.Assert(data.Contents[1].Owner.ID, Equals, "bcaf1ffd86a5fb16fd081034f")
|
||||
c.Assert(data.Contents[1].Owner.DisplayName, Equals, "webfile")
|
||||
}
|
||||
|
||||
func (s *S) TestListWithDelimiter(c *C) {
|
||||
testServer.Response(200, nil, GetListResultDump2)
|
||||
|
||||
b := s.s3.Bucket("quotes")
|
||||
|
||||
data, err := b.List("photos/2006/", "/", "some-marker", 1000)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
req := testServer.WaitRequest()
|
||||
c.Assert(req.Method, Equals, "GET")
|
||||
c.Assert(req.URL.Path, Equals, "/quotes/")
|
||||
c.Assert(req.Header["Date"], Not(Equals), "")
|
||||
c.Assert(req.Form["prefix"], DeepEquals, []string{"photos/2006/"})
|
||||
c.Assert(req.Form["delimiter"], DeepEquals, []string{"/"})
|
||||
c.Assert(req.Form["marker"], DeepEquals, []string{"some-marker"})
|
||||
c.Assert(req.Form["max-keys"], DeepEquals, []string{"1000"})
|
||||
|
||||
c.Assert(data.Name, Equals, "example-bucket")
|
||||
c.Assert(data.Prefix, Equals, "photos/2006/")
|
||||
c.Assert(data.Delimiter, Equals, "/")
|
||||
c.Assert(data.Marker, Equals, "some-marker")
|
||||
c.Assert(data.IsTruncated, Equals, false)
|
||||
c.Assert(len(data.Contents), Equals, 0)
|
||||
c.Assert(data.CommonPrefixes, DeepEquals, []string{"photos/2006/feb/", "photos/2006/jan/"})
|
||||
}
|
||||
|
||||
func (s *S) TestRetryAttempts(c *C) {
|
||||
s3.SetAttemptStrategy(nil)
|
||||
orig := s3.AttemptStrategy()
|
||||
s3.RetryAttempts(false)
|
||||
c.Assert(s3.AttemptStrategy(), Equals, aws.AttemptStrategy{})
|
||||
s3.RetryAttempts(true)
|
||||
c.Assert(s3.AttemptStrategy(), Equals, orig)
|
||||
}
|
||||
590
vendor/src/launchpad.net/goamz/s3/s3i_test.go
vendored
Normal file
590
vendor/src/launchpad.net/goamz/s3/s3i_test.go
vendored
Normal file
@@ -0,0 +1,590 @@
|
||||
package s3_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"launchpad.net/goamz/aws"
|
||||
"launchpad.net/goamz/s3"
|
||||
"launchpad.net/goamz/testutil"
|
||||
. "launchpad.net/gocheck"
|
||||
"net"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
// AmazonServer represents an Amazon S3 server.
|
||||
type AmazonServer struct {
|
||||
auth aws.Auth
|
||||
}
|
||||
|
||||
func (s *AmazonServer) SetUp(c *C) {
|
||||
auth, err := aws.EnvAuth()
|
||||
if err != nil {
|
||||
c.Fatal(err.Error())
|
||||
}
|
||||
s.auth = auth
|
||||
}
|
||||
|
||||
var _ = Suite(&AmazonClientSuite{Region: aws.USEast})
|
||||
var _ = Suite(&AmazonClientSuite{Region: aws.EUWest})
|
||||
var _ = Suite(&AmazonDomainClientSuite{Region: aws.USEast})
|
||||
|
||||
// AmazonClientSuite tests the client against a live S3 server.
|
||||
type AmazonClientSuite struct {
|
||||
aws.Region
|
||||
srv AmazonServer
|
||||
ClientTests
|
||||
}
|
||||
|
||||
func (s *AmazonClientSuite) SetUpSuite(c *C) {
|
||||
if !testutil.Amazon {
|
||||
c.Skip("live tests against AWS disabled (no -amazon)")
|
||||
}
|
||||
s.srv.SetUp(c)
|
||||
s.s3 = s3.New(s.srv.auth, s.Region)
|
||||
// In case tests were interrupted in the middle before.
|
||||
s.ClientTests.Cleanup()
|
||||
}
|
||||
|
||||
func (s *AmazonClientSuite) TearDownTest(c *C) {
|
||||
s.ClientTests.Cleanup()
|
||||
}
|
||||
|
||||
// AmazonDomainClientSuite tests the client against a live S3
|
||||
// server using bucket names in the endpoint domain name rather
|
||||
// than the request path.
|
||||
type AmazonDomainClientSuite struct {
|
||||
aws.Region
|
||||
srv AmazonServer
|
||||
ClientTests
|
||||
}
|
||||
|
||||
func (s *AmazonDomainClientSuite) SetUpSuite(c *C) {
|
||||
if !testutil.Amazon {
|
||||
c.Skip("live tests against AWS disabled (no -amazon)")
|
||||
}
|
||||
s.srv.SetUp(c)
|
||||
region := s.Region
|
||||
region.S3BucketEndpoint = "https://${bucket}.s3.amazonaws.com"
|
||||
s.s3 = s3.New(s.srv.auth, region)
|
||||
s.ClientTests.Cleanup()
|
||||
}
|
||||
|
||||
func (s *AmazonDomainClientSuite) TearDownTest(c *C) {
|
||||
s.ClientTests.Cleanup()
|
||||
}
|
||||
|
||||
// ClientTests defines integration tests designed to test the client.
|
||||
// It is not used as a test suite in itself, but embedded within
|
||||
// another type.
|
||||
type ClientTests struct {
|
||||
s3 *s3.S3
|
||||
authIsBroken bool
|
||||
}
|
||||
|
||||
func (s *ClientTests) Cleanup() {
|
||||
killBucket(testBucket(s.s3))
|
||||
}
|
||||
|
||||
func testBucket(s *s3.S3) *s3.Bucket {
|
||||
// Watch out! If this function is corrupted and made to match with something
|
||||
// people own, killBucket will happily remove *everything* inside the bucket.
|
||||
key := s.Auth.AccessKey
|
||||
if len(key) >= 8 {
|
||||
key = s.Auth.AccessKey[:8]
|
||||
}
|
||||
return s.Bucket(fmt.Sprintf("goamz-%s-%s", s.Region.Name, key))
|
||||
}
|
||||
|
||||
var attempts = aws.AttemptStrategy{
|
||||
Min: 5,
|
||||
Total: 20 * time.Second,
|
||||
Delay: 100 * time.Millisecond,
|
||||
}
|
||||
|
||||
func killBucket(b *s3.Bucket) {
|
||||
var err error
|
||||
for attempt := attempts.Start(); attempt.Next(); {
|
||||
err = b.DelBucket()
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if _, ok := err.(*net.DNSError); ok {
|
||||
return
|
||||
}
|
||||
e, ok := err.(*s3.Error)
|
||||
if ok && e.Code == "NoSuchBucket" {
|
||||
return
|
||||
}
|
||||
if ok && e.Code == "BucketNotEmpty" {
|
||||
// Errors are ignored here. Just retry.
|
||||
resp, err := b.List("", "", "", 1000)
|
||||
if err == nil {
|
||||
for _, key := range resp.Contents {
|
||||
_ = b.Del(key.Key)
|
||||
}
|
||||
}
|
||||
multis, _, _ := b.ListMulti("", "")
|
||||
for _, m := range multis {
|
||||
_ = m.Abort()
|
||||
}
|
||||
}
|
||||
}
|
||||
message := "cannot delete test bucket"
|
||||
if err != nil {
|
||||
message += ": " + err.Error()
|
||||
}
|
||||
panic(message)
|
||||
}
|
||||
|
||||
func get(url string) ([]byte, error) {
|
||||
for attempt := attempts.Start(); attempt.Next(); {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
if attempt.HasNext() {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
if attempt.HasNext() {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func (s *ClientTests) TestBasicFunctionality(c *C) {
|
||||
b := testBucket(s.s3)
|
||||
err := b.PutBucket(s3.PublicRead)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = b.Put("name", []byte("yo!"), "text/plain", s3.PublicRead)
|
||||
c.Assert(err, IsNil)
|
||||
defer b.Del("name")
|
||||
|
||||
data, err := b.Get("name")
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(string(data), Equals, "yo!")
|
||||
|
||||
data, err = get(b.URL("name"))
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(string(data), Equals, "yo!")
|
||||
|
||||
buf := bytes.NewBufferString("hey!")
|
||||
err = b.PutReader("name2", buf, int64(buf.Len()), "text/plain", s3.Private)
|
||||
c.Assert(err, IsNil)
|
||||
defer b.Del("name2")
|
||||
|
||||
rc, err := b.GetReader("name2")
|
||||
c.Assert(err, IsNil)
|
||||
data, err = ioutil.ReadAll(rc)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(string(data), Equals, "hey!")
|
||||
rc.Close()
|
||||
|
||||
data, err = get(b.SignedURL("name2", time.Now().Add(time.Hour)))
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(string(data), Equals, "hey!")
|
||||
|
||||
if !s.authIsBroken {
|
||||
data, err = get(b.SignedURL("name2", time.Now().Add(-time.Hour)))
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(string(data), Matches, "(?s).*AccessDenied.*")
|
||||
}
|
||||
|
||||
err = b.DelBucket()
|
||||
c.Assert(err, NotNil)
|
||||
|
||||
s3err, ok := err.(*s3.Error)
|
||||
c.Assert(ok, Equals, true)
|
||||
c.Assert(s3err.Code, Equals, "BucketNotEmpty")
|
||||
c.Assert(s3err.BucketName, Equals, b.Name)
|
||||
c.Assert(s3err.Message, Equals, "The bucket you tried to delete is not empty")
|
||||
|
||||
err = b.Del("name")
|
||||
c.Assert(err, IsNil)
|
||||
err = b.Del("name2")
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = b.DelBucket()
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *ClientTests) TestGetNotFound(c *C) {
|
||||
b := s.s3.Bucket("goamz-" + s.s3.Auth.AccessKey)
|
||||
data, err := b.Get("non-existent")
|
||||
|
||||
s3err, _ := err.(*s3.Error)
|
||||
c.Assert(s3err, NotNil)
|
||||
c.Assert(s3err.StatusCode, Equals, 404)
|
||||
c.Assert(s3err.Code, Equals, "NoSuchBucket")
|
||||
c.Assert(s3err.Message, Equals, "The specified bucket does not exist")
|
||||
c.Assert(data, IsNil)
|
||||
}
|
||||
|
||||
// Communicate with all endpoints to see if they are alive.
|
||||
func (s *ClientTests) TestRegions(c *C) {
|
||||
errs := make(chan error, len(aws.Regions))
|
||||
for _, region := range aws.Regions {
|
||||
go func(r aws.Region) {
|
||||
s := s3.New(s.s3.Auth, r)
|
||||
b := s.Bucket("goamz-" + s.Auth.AccessKey)
|
||||
_, err := b.Get("non-existent")
|
||||
errs <- err
|
||||
}(region)
|
||||
}
|
||||
for _ = range aws.Regions {
|
||||
err := <-errs
|
||||
if err != nil {
|
||||
s3_err, ok := err.(*s3.Error)
|
||||
if ok {
|
||||
c.Check(s3_err.Code, Matches, "NoSuchBucket")
|
||||
} else if _, ok = err.(*net.DNSError); ok {
|
||||
// Okay as well.
|
||||
} else {
|
||||
c.Errorf("Non-S3 error: %s", err)
|
||||
}
|
||||
} else {
|
||||
c.Errorf("Test should have errored but it seems to have succeeded")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var objectNames = []string{
|
||||
"index.html",
|
||||
"index2.html",
|
||||
"photos/2006/February/sample2.jpg",
|
||||
"photos/2006/February/sample3.jpg",
|
||||
"photos/2006/February/sample4.jpg",
|
||||
"photos/2006/January/sample.jpg",
|
||||
"test/bar",
|
||||
"test/foo",
|
||||
}
|
||||
|
||||
func keys(names ...string) []s3.Key {
|
||||
ks := make([]s3.Key, len(names))
|
||||
for i, name := range names {
|
||||
ks[i].Key = name
|
||||
}
|
||||
return ks
|
||||
}
|
||||
|
||||
// As the ListResp specifies all the parameters to the
|
||||
// request too, we use it to specify request parameters
|
||||
// and expected results. The Contents field is
|
||||
// used only for the key names inside it.
|
||||
var listTests = []s3.ListResp{
|
||||
// normal list.
|
||||
{
|
||||
Contents: keys(objectNames...),
|
||||
}, {
|
||||
Marker: objectNames[0],
|
||||
Contents: keys(objectNames[1:]...),
|
||||
}, {
|
||||
Marker: objectNames[0] + "a",
|
||||
Contents: keys(objectNames[1:]...),
|
||||
}, {
|
||||
Marker: "z",
|
||||
},
|
||||
|
||||
// limited results.
|
||||
{
|
||||
MaxKeys: 2,
|
||||
Contents: keys(objectNames[0:2]...),
|
||||
IsTruncated: true,
|
||||
}, {
|
||||
MaxKeys: 2,
|
||||
Marker: objectNames[0],
|
||||
Contents: keys(objectNames[1:3]...),
|
||||
IsTruncated: true,
|
||||
}, {
|
||||
MaxKeys: 2,
|
||||
Marker: objectNames[len(objectNames)-2],
|
||||
Contents: keys(objectNames[len(objectNames)-1:]...),
|
||||
},
|
||||
|
||||
// with delimiter
|
||||
{
|
||||
Delimiter: "/",
|
||||
CommonPrefixes: []string{"photos/", "test/"},
|
||||
Contents: keys("index.html", "index2.html"),
|
||||
}, {
|
||||
Delimiter: "/",
|
||||
Prefix: "photos/2006/",
|
||||
CommonPrefixes: []string{"photos/2006/February/", "photos/2006/January/"},
|
||||
}, {
|
||||
Delimiter: "/",
|
||||
Prefix: "t",
|
||||
CommonPrefixes: []string{"test/"},
|
||||
}, {
|
||||
Delimiter: "/",
|
||||
MaxKeys: 1,
|
||||
Contents: keys("index.html"),
|
||||
IsTruncated: true,
|
||||
}, {
|
||||
Delimiter: "/",
|
||||
MaxKeys: 1,
|
||||
Marker: "index2.html",
|
||||
CommonPrefixes: []string{"photos/"},
|
||||
IsTruncated: true,
|
||||
}, {
|
||||
Delimiter: "/",
|
||||
MaxKeys: 1,
|
||||
Marker: "photos/",
|
||||
CommonPrefixes: []string{"test/"},
|
||||
IsTruncated: false,
|
||||
}, {
|
||||
Delimiter: "Feb",
|
||||
CommonPrefixes: []string{"photos/2006/Feb"},
|
||||
Contents: keys("index.html", "index2.html", "photos/2006/January/sample.jpg", "test/bar", "test/foo"),
|
||||
},
|
||||
}
|
||||
|
||||
func (s *ClientTests) TestDoublePutBucket(c *C) {
|
||||
b := testBucket(s.s3)
|
||||
err := b.PutBucket(s3.PublicRead)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = b.PutBucket(s3.PublicRead)
|
||||
if err != nil {
|
||||
c.Assert(err, FitsTypeOf, new(s3.Error))
|
||||
c.Assert(err.(*s3.Error).Code, Equals, "BucketAlreadyOwnedByYou")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ClientTests) TestBucketList(c *C) {
|
||||
b := testBucket(s.s3)
|
||||
err := b.PutBucket(s3.Private)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
objData := make(map[string][]byte)
|
||||
for i, path := range objectNames {
|
||||
data := []byte(strings.Repeat("a", i))
|
||||
err := b.Put(path, data, "text/plain", s3.Private)
|
||||
c.Assert(err, IsNil)
|
||||
defer b.Del(path)
|
||||
objData[path] = data
|
||||
}
|
||||
|
||||
for i, t := range listTests {
|
||||
c.Logf("test %d", i)
|
||||
resp, err := b.List(t.Prefix, t.Delimiter, t.Marker, t.MaxKeys)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(resp.Name, Equals, b.Name)
|
||||
c.Check(resp.Delimiter, Equals, t.Delimiter)
|
||||
c.Check(resp.IsTruncated, Equals, t.IsTruncated)
|
||||
c.Check(resp.CommonPrefixes, DeepEquals, t.CommonPrefixes)
|
||||
checkContents(c, resp.Contents, objData, t.Contents)
|
||||
}
|
||||
}
|
||||
|
||||
func etag(data []byte) string {
|
||||
sum := md5.New()
|
||||
sum.Write(data)
|
||||
return fmt.Sprintf(`"%x"`, sum.Sum(nil))
|
||||
}
|
||||
|
||||
func checkContents(c *C, contents []s3.Key, data map[string][]byte, expected []s3.Key) {
|
||||
c.Assert(contents, HasLen, len(expected))
|
||||
for i, k := range contents {
|
||||
c.Check(k.Key, Equals, expected[i].Key)
|
||||
// TODO mtime
|
||||
c.Check(k.Size, Equals, int64(len(data[k.Key])))
|
||||
c.Check(k.ETag, Equals, etag(data[k.Key]))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ClientTests) TestMultiInitPutList(c *C) {
|
||||
b := testBucket(s.s3)
|
||||
err := b.PutBucket(s3.Private)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
multi, err := b.InitMulti("multi", "text/plain", s3.Private)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(multi.UploadId, Matches, ".+")
|
||||
defer multi.Abort()
|
||||
|
||||
var sent []s3.Part
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
p, err := multi.PutPart(i+1, strings.NewReader(fmt.Sprintf("<part %d>", i+1)))
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(p.N, Equals, i+1)
|
||||
c.Assert(p.Size, Equals, int64(8))
|
||||
c.Assert(p.ETag, Matches, ".+")
|
||||
sent = append(sent, p)
|
||||
}
|
||||
|
||||
s3.SetListPartsMax(2)
|
||||
|
||||
parts, err := multi.ListParts()
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(parts, HasLen, len(sent))
|
||||
for i := range parts {
|
||||
c.Assert(parts[i].N, Equals, sent[i].N)
|
||||
c.Assert(parts[i].Size, Equals, sent[i].Size)
|
||||
c.Assert(parts[i].ETag, Equals, sent[i].ETag)
|
||||
}
|
||||
|
||||
err = multi.Complete(parts)
|
||||
s3err, failed := err.(*s3.Error)
|
||||
c.Assert(failed, Equals, true)
|
||||
c.Assert(s3err.Code, Equals, "EntityTooSmall")
|
||||
|
||||
err = multi.Abort()
|
||||
c.Assert(err, IsNil)
|
||||
_, err = multi.ListParts()
|
||||
s3err, ok := err.(*s3.Error)
|
||||
c.Assert(ok, Equals, true)
|
||||
c.Assert(s3err.Code, Equals, "NoSuchUpload")
|
||||
}
|
||||
|
||||
// This may take a minute or more due to the minimum size accepted S3
|
||||
// on multipart upload parts.
|
||||
func (s *ClientTests) TestMultiComplete(c *C) {
|
||||
b := testBucket(s.s3)
|
||||
err := b.PutBucket(s3.Private)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
multi, err := b.InitMulti("multi", "text/plain", s3.Private)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(multi.UploadId, Matches, ".+")
|
||||
defer multi.Abort()
|
||||
|
||||
// Minimum size S3 accepts for all but the last part is 5MB.
|
||||
data1 := make([]byte, 5*1024*1024)
|
||||
data2 := []byte("<part 2>")
|
||||
|
||||
part1, err := multi.PutPart(1, bytes.NewReader(data1))
|
||||
c.Assert(err, IsNil)
|
||||
part2, err := multi.PutPart(2, bytes.NewReader(data2))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
// Purposefully reversed. The order requirement must be handled.
|
||||
err = multi.Complete([]s3.Part{part2, part1})
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
data, err := b.Get("multi")
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Assert(len(data), Equals, len(data1)+len(data2))
|
||||
for i := range data1 {
|
||||
if data[i] != data1[i] {
|
||||
c.Fatalf("uploaded object at byte %d: want %d, got %d", data1[i], data[i])
|
||||
}
|
||||
}
|
||||
c.Assert(string(data[len(data1):]), Equals, string(data2))
|
||||
}
|
||||
|
||||
type multiList []*s3.Multi
|
||||
|
||||
func (l multiList) Len() int { return len(l) }
|
||||
func (l multiList) Less(i, j int) bool { return l[i].Key < l[j].Key }
|
||||
func (l multiList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
|
||||
|
||||
func (s *ClientTests) TestListMulti(c *C) {
|
||||
b := testBucket(s.s3)
|
||||
err := b.PutBucket(s3.Private)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
// Ensure an empty state before testing its behavior.
|
||||
multis, _, err := b.ListMulti("", "")
|
||||
for _, m := range multis {
|
||||
err := m.Abort()
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
keys := []string{
|
||||
"a/multi2",
|
||||
"a/multi3",
|
||||
"b/multi4",
|
||||
"multi1",
|
||||
}
|
||||
for _, key := range keys {
|
||||
m, err := b.InitMulti(key, "", s3.Private)
|
||||
c.Assert(err, IsNil)
|
||||
defer m.Abort()
|
||||
}
|
||||
|
||||
// Amazon's implementation of the multiple-request listing for
|
||||
// multipart uploads in progress seems broken in multiple ways.
|
||||
// (next tokens are not provided, etc).
|
||||
//s3.SetListMultiMax(2)
|
||||
|
||||
multis, prefixes, err := b.ListMulti("", "")
|
||||
c.Assert(err, IsNil)
|
||||
for attempt := attempts.Start(); attempt.Next() && len(multis) < len(keys); {
|
||||
multis, prefixes, err = b.ListMulti("", "")
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
sort.Sort(multiList(multis))
|
||||
c.Assert(prefixes, IsNil)
|
||||
var gotKeys []string
|
||||
for _, m := range multis {
|
||||
gotKeys = append(gotKeys, m.Key)
|
||||
}
|
||||
c.Assert(gotKeys, DeepEquals, keys)
|
||||
for _, m := range multis {
|
||||
c.Assert(m.Bucket, Equals, b)
|
||||
c.Assert(m.UploadId, Matches, ".+")
|
||||
}
|
||||
|
||||
multis, prefixes, err = b.ListMulti("", "/")
|
||||
for attempt := attempts.Start(); attempt.Next() && len(prefixes) < 2; {
|
||||
multis, prefixes, err = b.ListMulti("", "")
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(prefixes, DeepEquals, []string{"a/", "b/"})
|
||||
c.Assert(multis, HasLen, 1)
|
||||
c.Assert(multis[0].Bucket, Equals, b)
|
||||
c.Assert(multis[0].Key, Equals, "multi1")
|
||||
c.Assert(multis[0].UploadId, Matches, ".+")
|
||||
|
||||
for attempt := attempts.Start(); attempt.Next() && len(multis) < 2; {
|
||||
multis, prefixes, err = b.ListMulti("", "")
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
multis, prefixes, err = b.ListMulti("a/", "/")
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(prefixes, IsNil)
|
||||
c.Assert(multis, HasLen, 2)
|
||||
c.Assert(multis[0].Bucket, Equals, b)
|
||||
c.Assert(multis[0].Key, Equals, "a/multi2")
|
||||
c.Assert(multis[0].UploadId, Matches, ".+")
|
||||
c.Assert(multis[1].Bucket, Equals, b)
|
||||
c.Assert(multis[1].Key, Equals, "a/multi3")
|
||||
c.Assert(multis[1].UploadId, Matches, ".+")
|
||||
}
|
||||
|
||||
func (s *ClientTests) TestMultiPutAllZeroLength(c *C) {
|
||||
b := testBucket(s.s3)
|
||||
err := b.PutBucket(s3.Private)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
multi, err := b.InitMulti("multi", "text/plain", s3.Private)
|
||||
c.Assert(err, IsNil)
|
||||
defer multi.Abort()
|
||||
|
||||
// This tests an edge case. Amazon requires at least one
|
||||
// part for multiprat uploads to work, even the part is empty.
|
||||
parts, err := multi.PutAll(strings.NewReader(""), 5*1024*1024)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(parts, HasLen, 1)
|
||||
c.Assert(parts[0].Size, Equals, int64(0))
|
||||
c.Assert(parts[0].ETag, Equals, `"d41d8cd98f00b204e9800998ecf8427e"`)
|
||||
|
||||
err = multi.Complete(parts)
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
79
vendor/src/launchpad.net/goamz/s3/s3t_test.go
vendored
Normal file
79
vendor/src/launchpad.net/goamz/s3/s3t_test.go
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
package s3_test
|
||||
|
||||
import (
|
||||
"launchpad.net/goamz/aws"
|
||||
"launchpad.net/goamz/s3"
|
||||
"launchpad.net/goamz/s3/s3test"
|
||||
. "launchpad.net/gocheck"
|
||||
)
|
||||
|
||||
type LocalServer struct {
|
||||
auth aws.Auth
|
||||
region aws.Region
|
||||
srv *s3test.Server
|
||||
config *s3test.Config
|
||||
}
|
||||
|
||||
func (s *LocalServer) SetUp(c *C) {
|
||||
srv, err := s3test.NewServer(s.config)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(srv, NotNil)
|
||||
|
||||
s.srv = srv
|
||||
s.region = aws.Region{
|
||||
Name: "faux-region-1",
|
||||
S3Endpoint: srv.URL(),
|
||||
S3LocationConstraint: true, // s3test server requires a LocationConstraint
|
||||
}
|
||||
}
|
||||
|
||||
// LocalServerSuite defines tests that will run
|
||||
// against the local s3test server. It includes
|
||||
// selected tests from ClientTests;
|
||||
// when the s3test functionality is sufficient, it should
|
||||
// include all of them, and ClientTests can be simply embedded.
|
||||
type LocalServerSuite struct {
|
||||
srv LocalServer
|
||||
clientTests ClientTests
|
||||
}
|
||||
|
||||
var (
|
||||
// run tests twice, once in us-east-1 mode, once not.
|
||||
_ = Suite(&LocalServerSuite{})
|
||||
_ = Suite(&LocalServerSuite{
|
||||
srv: LocalServer{
|
||||
config: &s3test.Config{
|
||||
Send409Conflict: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
func (s *LocalServerSuite) SetUpSuite(c *C) {
|
||||
s.srv.SetUp(c)
|
||||
s.clientTests.s3 = s3.New(s.srv.auth, s.srv.region)
|
||||
|
||||
// TODO Sadly the fake server ignores auth completely right now. :-(
|
||||
s.clientTests.authIsBroken = true
|
||||
s.clientTests.Cleanup()
|
||||
}
|
||||
|
||||
func (s *LocalServerSuite) TearDownTest(c *C) {
|
||||
s.clientTests.Cleanup()
|
||||
}
|
||||
|
||||
func (s *LocalServerSuite) TestBasicFunctionality(c *C) {
|
||||
s.clientTests.TestBasicFunctionality(c)
|
||||
}
|
||||
|
||||
func (s *LocalServerSuite) TestGetNotFound(c *C) {
|
||||
s.clientTests.TestGetNotFound(c)
|
||||
}
|
||||
|
||||
func (s *LocalServerSuite) TestBucketList(c *C) {
|
||||
s.clientTests.TestBucketList(c)
|
||||
}
|
||||
|
||||
func (s *LocalServerSuite) TestDoublePutBucket(c *C) {
|
||||
s.clientTests.TestDoublePutBucket(c)
|
||||
}
|
||||
628
vendor/src/launchpad.net/goamz/s3/s3test/server.go
vendored
Normal file
628
vendor/src/launchpad.net/goamz/s3/s3test/server.go
vendored
Normal file
@@ -0,0 +1,628 @@
|
||||
package s3test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"launchpad.net/goamz/s3"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const debug = false
|
||||
|
||||
type s3Error struct {
|
||||
statusCode int
|
||||
XMLName struct{} `xml:"Error"`
|
||||
Code string
|
||||
Message string
|
||||
BucketName string
|
||||
RequestId string
|
||||
HostId string
|
||||
}
|
||||
|
||||
type action struct {
|
||||
srv *Server
|
||||
w http.ResponseWriter
|
||||
req *http.Request
|
||||
reqId string
|
||||
}
|
||||
|
||||
// Config controls the internal behaviour of the Server. A nil config is the default
|
||||
// and behaves as if all configurations assume their default behaviour. Once passed
|
||||
// to NewServer, the configuration must not be modified.
|
||||
type Config struct {
|
||||
// Send409Conflict controls how the Server will respond to calls to PUT on a
|
||||
// previously existing bucket. The default is false, and corresponds to the
|
||||
// us-east-1 s3 enpoint. Setting this value to true emulates the behaviour of
|
||||
// all other regions.
|
||||
// http://docs.amazonwebservices.com/AmazonS3/latest/API/ErrorResponses.html
|
||||
Send409Conflict bool
|
||||
}
|
||||
|
||||
func (c *Config) send409Conflict() bool {
|
||||
if c != nil {
|
||||
return c.Send409Conflict
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Server is a fake S3 server for testing purposes.
|
||||
// All of the data for the server is kept in memory.
|
||||
type Server struct {
|
||||
url string
|
||||
reqId int
|
||||
listener net.Listener
|
||||
mu sync.Mutex
|
||||
buckets map[string]*bucket
|
||||
config *Config
|
||||
}
|
||||
|
||||
type bucket struct {
|
||||
name string
|
||||
acl s3.ACL
|
||||
ctime time.Time
|
||||
objects map[string]*object
|
||||
}
|
||||
|
||||
type object struct {
|
||||
name string
|
||||
mtime time.Time
|
||||
meta http.Header // metadata to return with requests.
|
||||
checksum []byte // also held as Content-MD5 in meta.
|
||||
data []byte
|
||||
}
|
||||
|
||||
// A resource encapsulates the subject of an HTTP request.
|
||||
// The resource referred to may or may not exist
|
||||
// when the request is made.
|
||||
type resource interface {
|
||||
put(a *action) interface{}
|
||||
get(a *action) interface{}
|
||||
post(a *action) interface{}
|
||||
delete(a *action) interface{}
|
||||
}
|
||||
|
||||
func NewServer(config *Config) (*Server, error) {
|
||||
l, err := net.Listen("tcp", "localhost:0")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot listen on localhost: %v", err)
|
||||
}
|
||||
srv := &Server{
|
||||
listener: l,
|
||||
url: "http://" + l.Addr().String(),
|
||||
buckets: make(map[string]*bucket),
|
||||
config: config,
|
||||
}
|
||||
go http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
srv.serveHTTP(w, req)
|
||||
}))
|
||||
return srv, nil
|
||||
}
|
||||
|
||||
// Quit closes down the server.
|
||||
func (srv *Server) Quit() {
|
||||
srv.listener.Close()
|
||||
}
|
||||
|
||||
// URL returns a URL for the server.
|
||||
func (srv *Server) URL() string {
|
||||
return srv.url
|
||||
}
|
||||
|
||||
func fatalf(code int, codeStr string, errf string, a ...interface{}) {
|
||||
panic(&s3Error{
|
||||
statusCode: code,
|
||||
Code: codeStr,
|
||||
Message: fmt.Sprintf(errf, a...),
|
||||
})
|
||||
}
|
||||
|
||||
// serveHTTP serves the S3 protocol.
|
||||
func (srv *Server) serveHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// ignore error from ParseForm as it's usually spurious.
|
||||
req.ParseForm()
|
||||
|
||||
srv.mu.Lock()
|
||||
defer srv.mu.Unlock()
|
||||
|
||||
if debug {
|
||||
log.Printf("s3test %q %q", req.Method, req.URL)
|
||||
}
|
||||
a := &action{
|
||||
srv: srv,
|
||||
w: w,
|
||||
req: req,
|
||||
reqId: fmt.Sprintf("%09X", srv.reqId),
|
||||
}
|
||||
srv.reqId++
|
||||
|
||||
var r resource
|
||||
defer func() {
|
||||
switch err := recover().(type) {
|
||||
case *s3Error:
|
||||
switch r := r.(type) {
|
||||
case objectResource:
|
||||
err.BucketName = r.bucket.name
|
||||
case bucketResource:
|
||||
err.BucketName = r.name
|
||||
}
|
||||
err.RequestId = a.reqId
|
||||
// TODO HostId
|
||||
w.Header().Set("Content-Type", `xml version="1.0" encoding="UTF-8"`)
|
||||
w.WriteHeader(err.statusCode)
|
||||
xmlMarshal(w, err)
|
||||
case nil:
|
||||
default:
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
r = srv.resourceForURL(req.URL)
|
||||
|
||||
var resp interface{}
|
||||
switch req.Method {
|
||||
case "PUT":
|
||||
resp = r.put(a)
|
||||
case "GET", "HEAD":
|
||||
resp = r.get(a)
|
||||
case "DELETE":
|
||||
resp = r.delete(a)
|
||||
case "POST":
|
||||
resp = r.post(a)
|
||||
default:
|
||||
fatalf(400, "MethodNotAllowed", "unknown http request method %q", req.Method)
|
||||
}
|
||||
if resp != nil && req.Method != "HEAD" {
|
||||
xmlMarshal(w, resp)
|
||||
}
|
||||
}
|
||||
|
||||
// xmlMarshal is the same as xml.Marshal except that
|
||||
// it panics on error. The marshalling should not fail,
|
||||
// but we want to know if it does.
|
||||
func xmlMarshal(w io.Writer, x interface{}) {
|
||||
if err := xml.NewEncoder(w).Encode(x); err != nil {
|
||||
panic(fmt.Errorf("error marshalling %#v: %v", x, err))
|
||||
}
|
||||
}
|
||||
|
||||
// In a fully implemented test server, each of these would have
|
||||
// its own resource type.
|
||||
var unimplementedBucketResourceNames = map[string]bool{
|
||||
"acl": true,
|
||||
"lifecycle": true,
|
||||
"policy": true,
|
||||
"location": true,
|
||||
"logging": true,
|
||||
"notification": true,
|
||||
"versions": true,
|
||||
"requestPayment": true,
|
||||
"versioning": true,
|
||||
"website": true,
|
||||
"uploads": true,
|
||||
}
|
||||
|
||||
var unimplementedObjectResourceNames = map[string]bool{
|
||||
"uploadId": true,
|
||||
"acl": true,
|
||||
"torrent": true,
|
||||
"uploads": true,
|
||||
}
|
||||
|
||||
var pathRegexp = regexp.MustCompile("/(([^/]+)(/(.*))?)?")
|
||||
|
||||
// resourceForURL returns a resource object for the given URL.
|
||||
func (srv *Server) resourceForURL(u *url.URL) (r resource) {
|
||||
m := pathRegexp.FindStringSubmatch(u.Path)
|
||||
if m == nil {
|
||||
fatalf(404, "InvalidURI", "Couldn't parse the specified URI")
|
||||
}
|
||||
bucketName := m[2]
|
||||
objectName := m[4]
|
||||
if bucketName == "" {
|
||||
return nullResource{} // root
|
||||
}
|
||||
b := bucketResource{
|
||||
name: bucketName,
|
||||
bucket: srv.buckets[bucketName],
|
||||
}
|
||||
q := u.Query()
|
||||
if objectName == "" {
|
||||
for name := range q {
|
||||
if unimplementedBucketResourceNames[name] {
|
||||
return nullResource{}
|
||||
}
|
||||
}
|
||||
return b
|
||||
|
||||
}
|
||||
if b.bucket == nil {
|
||||
fatalf(404, "NoSuchBucket", "The specified bucket does not exist")
|
||||
}
|
||||
objr := objectResource{
|
||||
name: objectName,
|
||||
version: q.Get("versionId"),
|
||||
bucket: b.bucket,
|
||||
}
|
||||
for name := range q {
|
||||
if unimplementedObjectResourceNames[name] {
|
||||
return nullResource{}
|
||||
}
|
||||
}
|
||||
if obj := objr.bucket.objects[objr.name]; obj != nil {
|
||||
objr.object = obj
|
||||
}
|
||||
return objr
|
||||
}
|
||||
|
||||
// nullResource has error stubs for all resource methods.
|
||||
type nullResource struct{}
|
||||
|
||||
func notAllowed() interface{} {
|
||||
fatalf(400, "MethodNotAllowed", "The specified method is not allowed against this resource")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nullResource) put(a *action) interface{} { return notAllowed() }
|
||||
func (nullResource) get(a *action) interface{} { return notAllowed() }
|
||||
func (nullResource) post(a *action) interface{} { return notAllowed() }
|
||||
func (nullResource) delete(a *action) interface{} { return notAllowed() }
|
||||
|
||||
const timeFormat = "2006-01-02T15:04:05.000Z07:00"
|
||||
|
||||
type bucketResource struct {
|
||||
name string
|
||||
bucket *bucket // non-nil if the bucket already exists.
|
||||
}
|
||||
|
||||
// GET on a bucket lists the objects in the bucket.
|
||||
// http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketGET.html
|
||||
func (r bucketResource) get(a *action) interface{} {
|
||||
if r.bucket == nil {
|
||||
fatalf(404, "NoSuchBucket", "The specified bucket does not exist")
|
||||
}
|
||||
delimiter := a.req.Form.Get("delimiter")
|
||||
marker := a.req.Form.Get("marker")
|
||||
maxKeys := -1
|
||||
if s := a.req.Form.Get("max-keys"); s != "" {
|
||||
i, err := strconv.Atoi(s)
|
||||
if err != nil || i < 0 {
|
||||
fatalf(400, "invalid value for max-keys: %q", s)
|
||||
}
|
||||
maxKeys = i
|
||||
}
|
||||
prefix := a.req.Form.Get("prefix")
|
||||
a.w.Header().Set("Content-Type", "application/xml")
|
||||
|
||||
if a.req.Method == "HEAD" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var objs orderedObjects
|
||||
|
||||
// first get all matching objects and arrange them in alphabetical order.
|
||||
for name, obj := range r.bucket.objects {
|
||||
if strings.HasPrefix(name, prefix) {
|
||||
objs = append(objs, obj)
|
||||
}
|
||||
}
|
||||
sort.Sort(objs)
|
||||
|
||||
if maxKeys <= 0 {
|
||||
maxKeys = 1000
|
||||
}
|
||||
resp := &s3.ListResp{
|
||||
Name: r.bucket.name,
|
||||
Prefix: prefix,
|
||||
Delimiter: delimiter,
|
||||
Marker: marker,
|
||||
MaxKeys: maxKeys,
|
||||
}
|
||||
|
||||
var prefixes []string
|
||||
for _, obj := range objs {
|
||||
if !strings.HasPrefix(obj.name, prefix) {
|
||||
continue
|
||||
}
|
||||
name := obj.name
|
||||
isPrefix := false
|
||||
if delimiter != "" {
|
||||
if i := strings.Index(obj.name[len(prefix):], delimiter); i >= 0 {
|
||||
name = obj.name[:len(prefix)+i+len(delimiter)]
|
||||
if prefixes != nil && prefixes[len(prefixes)-1] == name {
|
||||
continue
|
||||
}
|
||||
isPrefix = true
|
||||
}
|
||||
}
|
||||
if name <= marker {
|
||||
continue
|
||||
}
|
||||
if len(resp.Contents)+len(prefixes) >= maxKeys {
|
||||
resp.IsTruncated = true
|
||||
break
|
||||
}
|
||||
if isPrefix {
|
||||
prefixes = append(prefixes, name)
|
||||
} else {
|
||||
// Contents contains only keys not found in CommonPrefixes
|
||||
resp.Contents = append(resp.Contents, obj.s3Key())
|
||||
}
|
||||
}
|
||||
resp.CommonPrefixes = prefixes
|
||||
return resp
|
||||
}
|
||||
|
||||
// orderedObjects holds a slice of objects that can be sorted
|
||||
// by name.
|
||||
type orderedObjects []*object
|
||||
|
||||
func (s orderedObjects) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
func (s orderedObjects) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
func (s orderedObjects) Less(i, j int) bool {
|
||||
return s[i].name < s[j].name
|
||||
}
|
||||
|
||||
func (obj *object) s3Key() s3.Key {
|
||||
return s3.Key{
|
||||
Key: obj.name,
|
||||
LastModified: obj.mtime.Format(timeFormat),
|
||||
Size: int64(len(obj.data)),
|
||||
ETag: fmt.Sprintf(`"%x"`, obj.checksum),
|
||||
// TODO StorageClass
|
||||
// TODO Owner
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE on a bucket deletes the bucket if it's not empty.
|
||||
func (r bucketResource) delete(a *action) interface{} {
|
||||
b := r.bucket
|
||||
if b == nil {
|
||||
fatalf(404, "NoSuchBucket", "The specified bucket does not exist")
|
||||
}
|
||||
if len(b.objects) > 0 {
|
||||
fatalf(400, "BucketNotEmpty", "The bucket you tried to delete is not empty")
|
||||
}
|
||||
delete(a.srv.buckets, b.name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// PUT on a bucket creates the bucket.
|
||||
// http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketPUT.html
|
||||
func (r bucketResource) put(a *action) interface{} {
|
||||
var created bool
|
||||
if r.bucket == nil {
|
||||
if !validBucketName(r.name) {
|
||||
fatalf(400, "InvalidBucketName", "The specified bucket is not valid")
|
||||
}
|
||||
if loc := locationConstraint(a); loc == "" {
|
||||
fatalf(400, "InvalidRequets", "The unspecified location constraint is incompatible for the region specific endpoint this request was sent to.")
|
||||
}
|
||||
// TODO validate acl
|
||||
r.bucket = &bucket{
|
||||
name: r.name,
|
||||
// TODO default acl
|
||||
objects: make(map[string]*object),
|
||||
}
|
||||
a.srv.buckets[r.name] = r.bucket
|
||||
created = true
|
||||
}
|
||||
if !created && a.srv.config.send409Conflict() {
|
||||
fatalf(409, "BucketAlreadyOwnedByYou", "Your previous request to create the named bucket succeeded and you already own it.")
|
||||
}
|
||||
r.bucket.acl = s3.ACL(a.req.Header.Get("x-amz-acl"))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bucketResource) post(a *action) interface{} {
|
||||
fatalf(400, "Method", "bucket POST method not available")
|
||||
return nil
|
||||
}
|
||||
|
||||
// validBucketName returns whether name is a valid bucket name.
|
||||
// Here are the rules, from:
|
||||
// http://docs.amazonwebservices.com/AmazonS3/2006-03-01/dev/BucketRestrictions.html
|
||||
//
|
||||
// Can contain lowercase letters, numbers, periods (.), underscores (_),
|
||||
// and dashes (-). You can use uppercase letters for buckets only in the
|
||||
// US Standard region.
|
||||
//
|
||||
// Must start with a number or letter
|
||||
//
|
||||
// Must be between 3 and 255 characters long
|
||||
//
|
||||
// There's one extra rule (Must not be formatted as an IP address (e.g., 192.168.5.4)
|
||||
// but the real S3 server does not seem to check that rule, so we will not
|
||||
// check it either.
|
||||
//
|
||||
func validBucketName(name string) bool {
|
||||
if len(name) < 3 || len(name) > 255 {
|
||||
return false
|
||||
}
|
||||
r := name[0]
|
||||
if !(r >= '0' && r <= '9' || r >= 'a' && r <= 'z') {
|
||||
return false
|
||||
}
|
||||
for _, r := range name {
|
||||
switch {
|
||||
case r >= '0' && r <= '9':
|
||||
case r >= 'a' && r <= 'z':
|
||||
case r == '_' || r == '-':
|
||||
case r == '.':
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var responseParams = map[string]bool{
|
||||
"content-type": true,
|
||||
"content-language": true,
|
||||
"expires": true,
|
||||
"cache-control": true,
|
||||
"content-disposition": true,
|
||||
"content-encoding": true,
|
||||
}
|
||||
|
||||
type objectResource struct {
|
||||
name string
|
||||
version string
|
||||
bucket *bucket // always non-nil.
|
||||
object *object // may be nil.
|
||||
}
|
||||
|
||||
// GET on an object gets the contents of the object.
|
||||
// http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTObjectGET.html
|
||||
func (objr objectResource) get(a *action) interface{} {
|
||||
obj := objr.object
|
||||
if obj == nil {
|
||||
fatalf(404, "NoSuchKey", "The specified key does not exist.")
|
||||
}
|
||||
h := a.w.Header()
|
||||
// add metadata
|
||||
for name, d := range obj.meta {
|
||||
h[name] = d
|
||||
}
|
||||
// override header values in response to request parameters.
|
||||
for name, vals := range a.req.Form {
|
||||
if strings.HasPrefix(name, "response-") {
|
||||
name = name[len("response-"):]
|
||||
if !responseParams[name] {
|
||||
continue
|
||||
}
|
||||
h.Set(name, vals[0])
|
||||
}
|
||||
}
|
||||
if r := a.req.Header.Get("Range"); r != "" {
|
||||
fatalf(400, "NotImplemented", "range unimplemented")
|
||||
}
|
||||
// TODO Last-Modified-Since
|
||||
// TODO If-Modified-Since
|
||||
// TODO If-Unmodified-Since
|
||||
// TODO If-Match
|
||||
// TODO If-None-Match
|
||||
// TODO Connection: close ??
|
||||
// TODO x-amz-request-id
|
||||
h.Set("Content-Length", fmt.Sprint(len(obj.data)))
|
||||
h.Set("ETag", hex.EncodeToString(obj.checksum))
|
||||
h.Set("Last-Modified", obj.mtime.Format(time.RFC1123))
|
||||
if a.req.Method == "HEAD" {
|
||||
return nil
|
||||
}
|
||||
// TODO avoid holding the lock when writing data.
|
||||
_, err := a.w.Write(obj.data)
|
||||
if err != nil {
|
||||
// we can't do much except just log the fact.
|
||||
log.Printf("error writing data: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var metaHeaders = map[string]bool{
|
||||
"Content-MD5": true,
|
||||
"x-amz-acl": true,
|
||||
"Content-Type": true,
|
||||
"Content-Encoding": true,
|
||||
"Content-Disposition": true,
|
||||
}
|
||||
|
||||
// PUT on an object creates the object.
|
||||
func (objr objectResource) put(a *action) interface{} {
|
||||
// TODO Cache-Control header
|
||||
// TODO Expires header
|
||||
// TODO x-amz-server-side-encryption
|
||||
// TODO x-amz-storage-class
|
||||
|
||||
// TODO is this correct, or should we erase all previous metadata?
|
||||
obj := objr.object
|
||||
if obj == nil {
|
||||
obj = &object{
|
||||
name: objr.name,
|
||||
meta: make(http.Header),
|
||||
}
|
||||
}
|
||||
|
||||
var expectHash []byte
|
||||
if c := a.req.Header.Get("Content-MD5"); c != "" {
|
||||
var err error
|
||||
expectHash, err = hex.DecodeString(c)
|
||||
if err != nil || len(expectHash) != md5.Size {
|
||||
fatalf(400, "InvalidDigest", "The Content-MD5 you specified was invalid")
|
||||
}
|
||||
}
|
||||
sum := md5.New()
|
||||
// TODO avoid holding lock while reading data.
|
||||
data, err := ioutil.ReadAll(io.TeeReader(a.req.Body, sum))
|
||||
if err != nil {
|
||||
fatalf(400, "TODO", "read error")
|
||||
}
|
||||
gotHash := sum.Sum(nil)
|
||||
if expectHash != nil && bytes.Compare(gotHash, expectHash) != 0 {
|
||||
fatalf(400, "BadDigest", "The Content-MD5 you specified did not match what we received")
|
||||
}
|
||||
if a.req.ContentLength >= 0 && int64(len(data)) != a.req.ContentLength {
|
||||
fatalf(400, "IncompleteBody", "You did not provide the number of bytes specified by the Content-Length HTTP header")
|
||||
}
|
||||
|
||||
// PUT request has been successful - save data and metadata
|
||||
for key, values := range a.req.Header {
|
||||
key = http.CanonicalHeaderKey(key)
|
||||
if metaHeaders[key] || strings.HasPrefix(key, "X-Amz-Meta-") {
|
||||
obj.meta[key] = values
|
||||
}
|
||||
}
|
||||
obj.data = data
|
||||
obj.checksum = gotHash
|
||||
obj.mtime = time.Now()
|
||||
objr.bucket.objects[objr.name] = obj
|
||||
return nil
|
||||
}
|
||||
|
||||
func (objr objectResource) delete(a *action) interface{} {
|
||||
delete(objr.bucket.objects, objr.name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (objr objectResource) post(a *action) interface{} {
|
||||
fatalf(400, "MethodNotAllowed", "The specified method is not allowed against this resource")
|
||||
return nil
|
||||
}
|
||||
|
||||
type CreateBucketConfiguration struct {
|
||||
LocationConstraint string
|
||||
}
|
||||
|
||||
// locationConstraint parses the <CreateBucketConfiguration /> request body (if present).
|
||||
// If there is no body, an empty string will be returned.
|
||||
func locationConstraint(a *action) string {
|
||||
var body bytes.Buffer
|
||||
if _, err := io.Copy(&body, a.req.Body); err != nil {
|
||||
fatalf(400, "InvalidRequest", err.Error())
|
||||
}
|
||||
if body.Len() == 0 {
|
||||
return ""
|
||||
}
|
||||
var loc CreateBucketConfiguration
|
||||
if err := xml.NewDecoder(&body).Decode(&loc); err != nil {
|
||||
fatalf(400, "InvalidRequest", err.Error())
|
||||
}
|
||||
return loc.LocationConstraint
|
||||
}
|
||||
112
vendor/src/launchpad.net/goamz/s3/sign.go
vendored
Normal file
112
vendor/src/launchpad.net/goamz/s3/sign.go
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
package s3
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"launchpad.net/goamz/aws"
|
||||
"log"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var b64 = base64.StdEncoding
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// S3 signing (http://goo.gl/G1LrK)
|
||||
|
||||
var s3ParamsToSign = map[string]bool{
|
||||
"acl": true,
|
||||
"location": true,
|
||||
"logging": true,
|
||||
"notification": true,
|
||||
"partNumber": true,
|
||||
"policy": true,
|
||||
"requestPayment": true,
|
||||
"torrent": true,
|
||||
"uploadId": true,
|
||||
"uploads": true,
|
||||
"versionId": true,
|
||||
"versioning": true,
|
||||
"versions": true,
|
||||
"response-content-type": true,
|
||||
"response-content-language": true,
|
||||
"response-expires": true,
|
||||
"response-cache-control": true,
|
||||
"response-content-disposition": true,
|
||||
"response-content-encoding": true,
|
||||
}
|
||||
|
||||
func sign(auth aws.Auth, method, canonicalPath string, params, headers map[string][]string) {
|
||||
var md5, ctype, date, xamz string
|
||||
var xamzDate bool
|
||||
var sarray []string
|
||||
for k, v := range headers {
|
||||
k = strings.ToLower(k)
|
||||
switch k {
|
||||
case "content-md5":
|
||||
md5 = v[0]
|
||||
case "content-type":
|
||||
ctype = v[0]
|
||||
case "date":
|
||||
if !xamzDate {
|
||||
date = v[0]
|
||||
}
|
||||
default:
|
||||
if strings.HasPrefix(k, "x-amz-") {
|
||||
vall := strings.Join(v, ",")
|
||||
sarray = append(sarray, k+":"+vall)
|
||||
if k == "x-amz-date" {
|
||||
xamzDate = true
|
||||
date = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(sarray) > 0 {
|
||||
sort.StringSlice(sarray).Sort()
|
||||
xamz = strings.Join(sarray, "\n") + "\n"
|
||||
}
|
||||
|
||||
expires := false
|
||||
if v, ok := params["Expires"]; ok {
|
||||
// Query string request authentication alternative.
|
||||
expires = true
|
||||
date = v[0]
|
||||
params["AWSAccessKeyId"] = []string{auth.AccessKey}
|
||||
}
|
||||
|
||||
sarray = sarray[0:0]
|
||||
for k, v := range params {
|
||||
if s3ParamsToSign[k] {
|
||||
for _, vi := range v {
|
||||
if vi == "" {
|
||||
sarray = append(sarray, k)
|
||||
} else {
|
||||
// "When signing you do not encode these values."
|
||||
sarray = append(sarray, k+"="+vi)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(sarray) > 0 {
|
||||
sort.StringSlice(sarray).Sort()
|
||||
canonicalPath = canonicalPath + "?" + strings.Join(sarray, "&")
|
||||
}
|
||||
|
||||
payload := method + "\n" + md5 + "\n" + ctype + "\n" + date + "\n" + xamz + canonicalPath
|
||||
hash := hmac.New(sha1.New, []byte(auth.SecretKey))
|
||||
hash.Write([]byte(payload))
|
||||
signature := make([]byte, b64.EncodedLen(hash.Size()))
|
||||
b64.Encode(signature, hash.Sum(nil))
|
||||
|
||||
if expires {
|
||||
params["Signature"] = []string{string(signature)}
|
||||
} else {
|
||||
headers["Authorization"] = []string{"AWS " + auth.AccessKey + ":" + string(signature)}
|
||||
}
|
||||
if debug {
|
||||
log.Printf("Signature payload: %q", payload)
|
||||
log.Printf("Signature: %q", signature)
|
||||
}
|
||||
}
|
||||
132
vendor/src/launchpad.net/goamz/s3/sign_test.go
vendored
Normal file
132
vendor/src/launchpad.net/goamz/s3/sign_test.go
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
package s3_test
|
||||
|
||||
import (
|
||||
"launchpad.net/goamz/aws"
|
||||
"launchpad.net/goamz/s3"
|
||||
. "launchpad.net/gocheck"
|
||||
)
|
||||
|
||||
// S3 ReST authentication docs: http://goo.gl/G1LrK
|
||||
|
||||
var testAuth = aws.Auth{"0PN5J17HBGZHT7JJ3X82", "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o"}
|
||||
|
||||
func (s *S) TestSignExampleObjectGet(c *C) {
|
||||
method := "GET"
|
||||
path := "/johnsmith/photos/puppy.jpg"
|
||||
headers := map[string][]string{
|
||||
"Host": {"johnsmith.s3.amazonaws.com"},
|
||||
"Date": {"Tue, 27 Mar 2007 19:36:42 +0000"},
|
||||
}
|
||||
s3.Sign(testAuth, method, path, nil, headers)
|
||||
expected := "AWS 0PN5J17HBGZHT7JJ3X82:xXjDGYUmKxnwqr5KXNPGldn5LbA="
|
||||
c.Assert(headers["Authorization"], DeepEquals, []string{expected})
|
||||
}
|
||||
|
||||
func (s *S) TestSignExampleObjectPut(c *C) {
|
||||
method := "PUT"
|
||||
path := "/johnsmith/photos/puppy.jpg"
|
||||
headers := map[string][]string{
|
||||
"Host": {"johnsmith.s3.amazonaws.com"},
|
||||
"Date": {"Tue, 27 Mar 2007 21:15:45 +0000"},
|
||||
"Content-Type": {"image/jpeg"},
|
||||
"Content-Length": {"94328"},
|
||||
}
|
||||
s3.Sign(testAuth, method, path, nil, headers)
|
||||
expected := "AWS 0PN5J17HBGZHT7JJ3X82:hcicpDDvL9SsO6AkvxqmIWkmOuQ="
|
||||
c.Assert(headers["Authorization"], DeepEquals, []string{expected})
|
||||
}
|
||||
|
||||
func (s *S) TestSignExampleList(c *C) {
|
||||
method := "GET"
|
||||
path := "/johnsmith/"
|
||||
params := map[string][]string{
|
||||
"prefix": {"photos"},
|
||||
"max-keys": {"50"},
|
||||
"marker": {"puppy"},
|
||||
}
|
||||
headers := map[string][]string{
|
||||
"Host": {"johnsmith.s3.amazonaws.com"},
|
||||
"Date": {"Tue, 27 Mar 2007 19:42:41 +0000"},
|
||||
"User-Agent": {"Mozilla/5.0"},
|
||||
}
|
||||
s3.Sign(testAuth, method, path, params, headers)
|
||||
expected := "AWS 0PN5J17HBGZHT7JJ3X82:jsRt/rhG+Vtp88HrYL706QhE4w4="
|
||||
c.Assert(headers["Authorization"], DeepEquals, []string{expected})
|
||||
}
|
||||
|
||||
func (s *S) TestSignExampleFetch(c *C) {
|
||||
method := "GET"
|
||||
path := "/johnsmith/"
|
||||
params := map[string][]string{
|
||||
"acl": {""},
|
||||
}
|
||||
headers := map[string][]string{
|
||||
"Host": {"johnsmith.s3.amazonaws.com"},
|
||||
"Date": {"Tue, 27 Mar 2007 19:44:46 +0000"},
|
||||
}
|
||||
s3.Sign(testAuth, method, path, params, headers)
|
||||
expected := "AWS 0PN5J17HBGZHT7JJ3X82:thdUi9VAkzhkniLj96JIrOPGi0g="
|
||||
c.Assert(headers["Authorization"], DeepEquals, []string{expected})
|
||||
}
|
||||
|
||||
func (s *S) TestSignExampleDelete(c *C) {
|
||||
method := "DELETE"
|
||||
path := "/johnsmith/photos/puppy.jpg"
|
||||
params := map[string][]string{}
|
||||
headers := map[string][]string{
|
||||
"Host": {"s3.amazonaws.com"},
|
||||
"Date": {"Tue, 27 Mar 2007 21:20:27 +0000"},
|
||||
"User-Agent": {"dotnet"},
|
||||
"x-amz-date": {"Tue, 27 Mar 2007 21:20:26 +0000"},
|
||||
}
|
||||
s3.Sign(testAuth, method, path, params, headers)
|
||||
expected := "AWS 0PN5J17HBGZHT7JJ3X82:k3nL7gH3+PadhTEVn5Ip83xlYzk="
|
||||
c.Assert(headers["Authorization"], DeepEquals, []string{expected})
|
||||
}
|
||||
|
||||
func (s *S) TestSignExampleUpload(c *C) {
|
||||
method := "PUT"
|
||||
path := "/static.johnsmith.net/db-backup.dat.gz"
|
||||
params := map[string][]string{}
|
||||
headers := map[string][]string{
|
||||
"Host": {"static.johnsmith.net:8080"},
|
||||
"Date": {"Tue, 27 Mar 2007 21:06:08 +0000"},
|
||||
"User-Agent": {"curl/7.15.5"},
|
||||
"x-amz-acl": {"public-read"},
|
||||
"content-type": {"application/x-download"},
|
||||
"Content-MD5": {"4gJE4saaMU4BqNR0kLY+lw=="},
|
||||
"X-Amz-Meta-ReviewedBy": {"joe@johnsmith.net,jane@johnsmith.net"},
|
||||
"X-Amz-Meta-FileChecksum": {"0x02661779"},
|
||||
"X-Amz-Meta-ChecksumAlgorithm": {"crc32"},
|
||||
"Content-Disposition": {"attachment; filename=database.dat"},
|
||||
"Content-Encoding": {"gzip"},
|
||||
"Content-Length": {"5913339"},
|
||||
}
|
||||
s3.Sign(testAuth, method, path, params, headers)
|
||||
expected := "AWS 0PN5J17HBGZHT7JJ3X82:C0FlOtU8Ylb9KDTpZqYkZPX91iI="
|
||||
c.Assert(headers["Authorization"], DeepEquals, []string{expected})
|
||||
}
|
||||
|
||||
func (s *S) TestSignExampleListAllMyBuckets(c *C) {
|
||||
method := "GET"
|
||||
path := "/"
|
||||
headers := map[string][]string{
|
||||
"Host": {"s3.amazonaws.com"},
|
||||
"Date": {"Wed, 28 Mar 2007 01:29:59 +0000"},
|
||||
}
|
||||
s3.Sign(testAuth, method, path, nil, headers)
|
||||
expected := "AWS 0PN5J17HBGZHT7JJ3X82:Db+gepJSUbZKwpx1FR0DLtEYoZA="
|
||||
c.Assert(headers["Authorization"], DeepEquals, []string{expected})
|
||||
}
|
||||
|
||||
func (s *S) TestSignExampleUnicodeKeys(c *C) {
|
||||
method := "GET"
|
||||
path := "/dictionary/fran%C3%A7ais/pr%c3%a9f%c3%a8re"
|
||||
headers := map[string][]string{
|
||||
"Host": {"s3.amazonaws.com"},
|
||||
"Date": {"Wed, 28 Mar 2007 01:49:49 +0000"},
|
||||
}
|
||||
s3.Sign(testAuth, method, path, nil, headers)
|
||||
expected := "AWS 0PN5J17HBGZHT7JJ3X82:dxhSBHoI6eVSPcXJqEghlUzZMnY="
|
||||
c.Assert(headers["Authorization"], DeepEquals, []string{expected})
|
||||
}
|
||||
186
vendor/src/launchpad.net/goamz/testutil/http.go
vendored
Normal file
186
vendor/src/launchpad.net/goamz/testutil/http.go
vendored
Normal file
@@ -0,0 +1,186 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type HTTPServer struct {
|
||||
URL string
|
||||
Timeout time.Duration
|
||||
started bool
|
||||
listener net.Listener
|
||||
request chan *http.Request
|
||||
response chan ResponseFunc
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Status int
|
||||
Headers map[string]string
|
||||
Body string
|
||||
}
|
||||
|
||||
func NewHTTPServer() *HTTPServer {
|
||||
return &HTTPServer{URL: "http://localhost:4444", Timeout: 5 * time.Second}
|
||||
}
|
||||
|
||||
type ResponseFunc func(path string) Response
|
||||
|
||||
func (s *HTTPServer) Start() {
|
||||
if s.started {
|
||||
return
|
||||
}
|
||||
s.started = true
|
||||
s.request = make(chan *http.Request, 1024)
|
||||
s.response = make(chan ResponseFunc, 1024)
|
||||
u, err := url.Parse(s.URL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s.listener, err = net.Listen("tcp", u.Host)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
go http.Serve(s.listener, s)
|
||||
|
||||
s.Response(203, nil, "")
|
||||
for {
|
||||
// Wait for it to be up.
|
||||
resp, err := http.Get(s.URL)
|
||||
if err == nil && resp.StatusCode == 203 {
|
||||
break
|
||||
}
|
||||
time.Sleep(1e8)
|
||||
}
|
||||
s.WaitRequest() // Consume dummy request.
|
||||
}
|
||||
|
||||
func (s *HTTPServer) Stop() {
|
||||
if s.listener == nil {
|
||||
return
|
||||
}
|
||||
err := s.listener.Close()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s.listener = nil
|
||||
}
|
||||
|
||||
// Flush discards all pending requests and responses.
|
||||
func (s *HTTPServer) Flush() {
|
||||
for {
|
||||
select {
|
||||
case <-s.request:
|
||||
case <-s.response:
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func body(req *http.Request) string {
|
||||
data, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func (s *HTTPServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
req.ParseMultipartForm(1e6)
|
||||
data, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
req.Body = ioutil.NopCloser(bytes.NewBuffer(data))
|
||||
s.request <- req
|
||||
var resp Response
|
||||
select {
|
||||
case respFunc := <-s.response:
|
||||
resp = respFunc(req.URL.Path)
|
||||
case <-time.After(s.Timeout):
|
||||
const msg = "ERROR: Timeout waiting for test to prepare a response\n"
|
||||
fmt.Fprintf(os.Stderr, msg)
|
||||
resp = Response{500, nil, msg}
|
||||
}
|
||||
if resp.Headers != nil {
|
||||
h := w.Header()
|
||||
for k, v := range resp.Headers {
|
||||
h.Set(k, v)
|
||||
}
|
||||
}
|
||||
if resp.Status != 0 {
|
||||
w.WriteHeader(resp.Status)
|
||||
}
|
||||
w.Write([]byte(resp.Body))
|
||||
}
|
||||
|
||||
// WaitRequests returns the next n requests made to the http server from
|
||||
// the queue. If not enough requests were previously made, it waits until
|
||||
// the timeout value for them to be made.
|
||||
func (s *HTTPServer) WaitRequests(n int) []*http.Request {
|
||||
reqs := make([]*http.Request, 0, n)
|
||||
for i := 0; i < n; i++ {
|
||||
select {
|
||||
case req := <-s.request:
|
||||
reqs = append(reqs, req)
|
||||
case <-time.After(s.Timeout):
|
||||
panic("Timeout waiting for request")
|
||||
}
|
||||
}
|
||||
return reqs
|
||||
}
|
||||
|
||||
// WaitRequest returns the next request made to the http server from
|
||||
// the queue. If no requests were previously made, it waits until the
|
||||
// timeout value for one to be made.
|
||||
func (s *HTTPServer) WaitRequest() *http.Request {
|
||||
return s.WaitRequests(1)[0]
|
||||
}
|
||||
|
||||
// ResponseFunc prepares the test server to respond the following n
|
||||
// requests using f to build each response.
|
||||
func (s *HTTPServer) ResponseFunc(n int, f ResponseFunc) {
|
||||
for i := 0; i < n; i++ {
|
||||
s.response <- f
|
||||
}
|
||||
}
|
||||
|
||||
// ResponseMap maps request paths to responses.
|
||||
type ResponseMap map[string]Response
|
||||
|
||||
// ResponseMap prepares the test server to respond the following n
|
||||
// requests using the m to obtain the responses.
|
||||
func (s *HTTPServer) ResponseMap(n int, m ResponseMap) {
|
||||
f := func(path string) Response {
|
||||
for rpath, resp := range m {
|
||||
if rpath == path {
|
||||
return resp
|
||||
}
|
||||
}
|
||||
body := "Path not found in response map: " + path
|
||||
return Response{Status: 500, Body: body}
|
||||
}
|
||||
s.ResponseFunc(n, f)
|
||||
}
|
||||
|
||||
// Responses prepares the test server to respond the following n requests
|
||||
// using the provided response parameters.
|
||||
func (s *HTTPServer) Responses(n int, status int, headers map[string]string, body string) {
|
||||
f := func(path string) Response {
|
||||
return Response{status, headers, body}
|
||||
}
|
||||
s.ResponseFunc(n, f)
|
||||
}
|
||||
|
||||
// Response prepares the test server to respond the following request
|
||||
// using the provided response parameters.
|
||||
func (s *HTTPServer) Response(status int, headers map[string]string, body string) {
|
||||
s.Responses(1, status, headers, body)
|
||||
}
|
||||
30
vendor/src/launchpad.net/goamz/testutil/suite.go
vendored
Normal file
30
vendor/src/launchpad.net/goamz/testutil/suite.go
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"launchpad.net/goamz/aws"
|
||||
. "launchpad.net/gocheck"
|
||||
)
|
||||
|
||||
// Amazon must be used by all tested packages to determine whether to
|
||||
// run functional tests against the real AWS servers.
|
||||
var Amazon bool
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&Amazon, "amazon", false, "Enable tests against amazon server")
|
||||
}
|
||||
|
||||
type LiveSuite struct {
|
||||
auth aws.Auth
|
||||
}
|
||||
|
||||
func (s *LiveSuite) SetUpSuite(c *C) {
|
||||
if !Amazon {
|
||||
c.Skip("amazon tests not enabled (-amazon flag)")
|
||||
}
|
||||
auth, err := aws.EnvAuth()
|
||||
if err != nil {
|
||||
c.Fatal(err.Error())
|
||||
}
|
||||
s.auth = auth
|
||||
}
|
||||
Reference in New Issue
Block a user