Adds custom registry User-Agent header to s3 HTTP requests
Uses docker/goamz instead of AdRoll/goamz Adds a registry UA string param to the storage parameters when constructing the storage driver for the registry App. This could be used by other storage drivers as well Signed-off-by: Brian Bland <brian.bland@docker.com>
This commit is contained in:
parent
47a064d419
commit
2dc1af12a1
32 changed files with 214 additions and 3566 deletions
24
Godeps/Godeps.json
generated
24
Godeps/Godeps.json
generated
|
@ -41,18 +41,6 @@
|
||||||
"ImportPath": "google.golang.org/cloud/storage",
|
"ImportPath": "google.golang.org/cloud/storage",
|
||||||
"Rev": "2400193c85c3561d13880d34e0e10c4315bb02af"
|
"Rev": "2400193c85c3561d13880d34e0e10c4315bb02af"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ImportPath": "github.com/AdRoll/goamz/aws",
|
|
||||||
"Rev": "aa6e716d710a0c7941cb2075cfbb9661f16d21f1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/AdRoll/goamz/cloudfront",
|
|
||||||
"Rev": "aa6e716d710a0c7941cb2075cfbb9661f16d21f1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/AdRoll/goamz/s3",
|
|
||||||
"Rev": "aa6e716d710a0c7941cb2075cfbb9661f16d21f1"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/Azure/azure-sdk-for-go/storage",
|
"ImportPath": "github.com/Azure/azure-sdk-for-go/storage",
|
||||||
"Rev": "97d9593768bbbbd316f9c055dfc5f780933cd7fc"
|
"Rev": "97d9593768bbbbd316f9c055dfc5f780933cd7fc"
|
||||||
|
@ -87,6 +75,18 @@
|
||||||
"ImportPath": "github.com/denverdino/aliyungo/common",
|
"ImportPath": "github.com/denverdino/aliyungo/common",
|
||||||
"Rev": "6ffb587da9da6d029d0ce517b85fecc82172d502"
|
"Rev": "6ffb587da9da6d029d0ce517b85fecc82172d502"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/docker/goamz/aws",
|
||||||
|
"Rev": "fb9c4c25c583d56a0544da8d1094294908c68ee8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/docker/goamz/cloudfront",
|
||||||
|
"Rev": "fb9c4c25c583d56a0544da8d1094294908c68ee8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/docker/goamz/s3",
|
||||||
|
"Rev": "fb9c4c25c583d56a0544da8d1094294908c68ee8"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/docker/libtrust",
|
"ImportPath": "github.com/docker/libtrust",
|
||||||
"Rev": "fa567046d9b14f6aa788882a950d69651d230b21"
|
"Rev": "fa567046d9b14f6aa788882a950d69651d230b21"
|
||||||
|
|
57
Godeps/_workspace/src/github.com/AdRoll/goamz/aws/attempt_test.go
generated
vendored
57
Godeps/_workspace/src/github.com/AdRoll/goamz/aws/attempt_test.go
generated
vendored
|
@ -1,57 +0,0 @@
|
||||||
package aws_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/AdRoll/goamz/aws"
|
|
||||||
"gopkg.in/check.v1"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (S) TestAttemptTiming(c *check.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, check.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 *check.C) {
|
|
||||||
a := aws.AttemptStrategy{}.Start()
|
|
||||||
c.Assert(a.Next(), check.Equals, true)
|
|
||||||
c.Assert(a.Next(), check.Equals, false)
|
|
||||||
|
|
||||||
a = aws.AttemptStrategy{}.Start()
|
|
||||||
c.Assert(a.Next(), check.Equals, true)
|
|
||||||
c.Assert(a.HasNext(), check.Equals, false)
|
|
||||||
c.Assert(a.Next(), check.Equals, false)
|
|
||||||
|
|
||||||
a = aws.AttemptStrategy{Total: 2e8}.Start()
|
|
||||||
c.Assert(a.Next(), check.Equals, true)
|
|
||||||
c.Assert(a.HasNext(), check.Equals, true)
|
|
||||||
time.Sleep(2e8)
|
|
||||||
c.Assert(a.HasNext(), check.Equals, true)
|
|
||||||
c.Assert(a.Next(), check.Equals, true)
|
|
||||||
c.Assert(a.Next(), check.Equals, false)
|
|
||||||
|
|
||||||
a = aws.AttemptStrategy{Total: 1e8, Min: 2}.Start()
|
|
||||||
time.Sleep(1e8)
|
|
||||||
c.Assert(a.Next(), check.Equals, true)
|
|
||||||
c.Assert(a.HasNext(), check.Equals, true)
|
|
||||||
c.Assert(a.Next(), check.Equals, true)
|
|
||||||
c.Assert(a.HasNext(), check.Equals, false)
|
|
||||||
c.Assert(a.Next(), check.Equals, false)
|
|
||||||
}
|
|
140
Godeps/_workspace/src/github.com/AdRoll/goamz/aws/aws_test.go
generated
vendored
140
Godeps/_workspace/src/github.com/AdRoll/goamz/aws/aws_test.go
generated
vendored
|
@ -1,140 +0,0 @@
|
||||||
package aws_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/AdRoll/goamz/aws"
|
|
||||||
"gopkg.in/check.v1"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test(t *testing.T) {
|
|
||||||
check.TestingT(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = check.Suite(&S{})
|
|
||||||
|
|
||||||
type S struct {
|
|
||||||
environ []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) SetUpSuite(c *check.C) {
|
|
||||||
s.environ = os.Environ()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TearDownTest(c *check.C) {
|
|
||||||
os.Clearenv()
|
|
||||||
for _, kv := range s.environ {
|
|
||||||
l := strings.SplitN(kv, "=", 2)
|
|
||||||
os.Setenv(l[0], l[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestEnvAuthNoSecret(c *check.C) {
|
|
||||||
os.Clearenv()
|
|
||||||
_, err := aws.EnvAuth()
|
|
||||||
c.Assert(err, check.ErrorMatches, "AWS_SECRET_ACCESS_KEY or AWS_SECRET_KEY not found in environment")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestEnvAuthNoAccess(c *check.C) {
|
|
||||||
os.Clearenv()
|
|
||||||
os.Setenv("AWS_SECRET_ACCESS_KEY", "foo")
|
|
||||||
_, err := aws.EnvAuth()
|
|
||||||
c.Assert(err, check.ErrorMatches, "AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY not found in environment")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestEnvAuth(c *check.C) {
|
|
||||||
os.Clearenv()
|
|
||||||
os.Setenv("AWS_SECRET_ACCESS_KEY", "secret")
|
|
||||||
os.Setenv("AWS_ACCESS_KEY_ID", "access")
|
|
||||||
auth, err := aws.EnvAuth()
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(auth, check.Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestEnvAuthAlt(c *check.C) {
|
|
||||||
os.Clearenv()
|
|
||||||
os.Setenv("AWS_SECRET_KEY", "secret")
|
|
||||||
os.Setenv("AWS_ACCESS_KEY", "access")
|
|
||||||
auth, err := aws.EnvAuth()
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(auth, check.Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestGetAuthStatic(c *check.C) {
|
|
||||||
exptdate := time.Now().Add(time.Hour)
|
|
||||||
auth, err := aws.GetAuth("access", "secret", "token", exptdate)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(auth.AccessKey, check.Equals, "access")
|
|
||||||
c.Assert(auth.SecretKey, check.Equals, "secret")
|
|
||||||
c.Assert(auth.Token(), check.Equals, "token")
|
|
||||||
c.Assert(auth.Expiration(), check.Equals, exptdate)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestGetAuthEnv(c *check.C) {
|
|
||||||
os.Clearenv()
|
|
||||||
os.Setenv("AWS_SECRET_ACCESS_KEY", "secret")
|
|
||||||
os.Setenv("AWS_ACCESS_KEY_ID", "access")
|
|
||||||
auth, err := aws.GetAuth("", "", "", time.Time{})
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(auth, check.Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestEncode(c *check.C) {
|
|
||||||
c.Assert(aws.Encode("foo"), check.Equals, "foo")
|
|
||||||
c.Assert(aws.Encode("/"), check.Equals, "%2F")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestRegionsAreNamed(c *check.C) {
|
|
||||||
for n, r := range aws.Regions {
|
|
||||||
c.Assert(n, check.Equals, r.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestCredentialsFileAuth(c *check.C) {
|
|
||||||
file, err := ioutil.TempFile("", "creds")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
c.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
iniFile := `
|
|
||||||
|
|
||||||
[default] ; comment 123
|
|
||||||
aws_access_key_id = keyid1 ;comment
|
|
||||||
aws_secret_access_key=key1
|
|
||||||
|
|
||||||
[profile2]
|
|
||||||
aws_access_key_id = keyid2 ;comment
|
|
||||||
aws_secret_access_key=key2
|
|
||||||
aws_session_token=token1
|
|
||||||
|
|
||||||
`
|
|
||||||
_, err = file.WriteString(iniFile)
|
|
||||||
if err != nil {
|
|
||||||
c.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = file.Close()
|
|
||||||
if err != nil {
|
|
||||||
c.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check non-existant profile
|
|
||||||
_, err = aws.CredentialFileAuth(file.Name(), "no profile", 30*time.Minute)
|
|
||||||
c.Assert(err, check.Not(check.Equals), nil)
|
|
||||||
|
|
||||||
defaultProfile, err := aws.CredentialFileAuth(file.Name(), "default", 30*time.Minute)
|
|
||||||
c.Assert(err, check.Equals, nil)
|
|
||||||
c.Assert(defaultProfile.AccessKey, check.Equals, "keyid1")
|
|
||||||
c.Assert(defaultProfile.SecretKey, check.Equals, "key1")
|
|
||||||
c.Assert(defaultProfile.Token(), check.Equals, "")
|
|
||||||
|
|
||||||
profile2, err := aws.CredentialFileAuth(file.Name(), "profile2", 30*time.Minute)
|
|
||||||
c.Assert(err, check.Equals, nil)
|
|
||||||
c.Assert(profile2.AccessKey, check.Equals, "keyid2")
|
|
||||||
c.Assert(profile2.SecretKey, check.Equals, "key2")
|
|
||||||
c.Assert(profile2.Token(), check.Equals, "token1")
|
|
||||||
}
|
|
29
Godeps/_workspace/src/github.com/AdRoll/goamz/aws/export_test.go
generated
vendored
29
Godeps/_workspace/src/github.com/AdRoll/goamz/aws/export_test.go
generated
vendored
|
@ -1,29 +0,0 @@
|
||||||
package aws
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// V4Signer:
|
|
||||||
// Exporting methods for testing
|
|
||||||
|
|
||||||
func (s *V4Signer) RequestTime(req *http.Request) time.Time {
|
|
||||||
return s.requestTime(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *V4Signer) CanonicalRequest(req *http.Request) string {
|
|
||||||
return s.canonicalRequest(req, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *V4Signer) StringToSign(t time.Time, creq string) string {
|
|
||||||
return s.stringToSign(t, creq)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *V4Signer) Signature(t time.Time, sts string) string {
|
|
||||||
return s.signature(t, sts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *V4Signer) Authorization(header http.Header, t time.Time, signature string) string {
|
|
||||||
return s.authorization(header, t, signature)
|
|
||||||
}
|
|
303
Godeps/_workspace/src/github.com/AdRoll/goamz/aws/retry_test.go
generated
vendored
303
Godeps/_workspace/src/github.com/AdRoll/goamz/aws/retry_test.go
generated
vendored
|
@ -1,303 +0,0 @@
|
||||||
package aws
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type testInput struct {
|
|
||||||
res *http.Response
|
|
||||||
err error
|
|
||||||
numRetries int
|
|
||||||
}
|
|
||||||
|
|
||||||
type testResult struct {
|
|
||||||
shouldRetry bool
|
|
||||||
delay time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
type testCase struct {
|
|
||||||
input testInput
|
|
||||||
defaultResult testResult
|
|
||||||
dynamoDBResult testResult
|
|
||||||
}
|
|
||||||
|
|
||||||
var testCases = []testCase{
|
|
||||||
// Test nil fields
|
|
||||||
testCase{
|
|
||||||
input: testInput{
|
|
||||||
err: nil,
|
|
||||||
res: nil,
|
|
||||||
numRetries: 0,
|
|
||||||
},
|
|
||||||
defaultResult: testResult{
|
|
||||||
shouldRetry: false,
|
|
||||||
delay: 300 * time.Millisecond,
|
|
||||||
},
|
|
||||||
dynamoDBResult: testResult{
|
|
||||||
shouldRetry: false,
|
|
||||||
delay: 25 * time.Millisecond,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// Test 3 different throttling exceptions
|
|
||||||
testCase{
|
|
||||||
input: testInput{
|
|
||||||
err: &Error{
|
|
||||||
Code: "Throttling",
|
|
||||||
},
|
|
||||||
numRetries: 0,
|
|
||||||
},
|
|
||||||
defaultResult: testResult{
|
|
||||||
shouldRetry: true,
|
|
||||||
delay: 617165505 * time.Nanosecond, // account for randomness with known seed
|
|
||||||
},
|
|
||||||
dynamoDBResult: testResult{
|
|
||||||
shouldRetry: true,
|
|
||||||
delay: 25 * time.Millisecond,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
testCase{
|
|
||||||
input: testInput{
|
|
||||||
err: &Error{
|
|
||||||
Code: "ThrottlingException",
|
|
||||||
},
|
|
||||||
numRetries: 0,
|
|
||||||
},
|
|
||||||
defaultResult: testResult{
|
|
||||||
shouldRetry: true,
|
|
||||||
delay: 579393152 * time.Nanosecond, // account for randomness with known seed
|
|
||||||
},
|
|
||||||
dynamoDBResult: testResult{
|
|
||||||
shouldRetry: true,
|
|
||||||
delay: 25 * time.Millisecond,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
testCase{
|
|
||||||
input: testInput{
|
|
||||||
err: &Error{
|
|
||||||
Code: "ProvisionedThroughputExceededException",
|
|
||||||
},
|
|
||||||
numRetries: 1,
|
|
||||||
},
|
|
||||||
defaultResult: testResult{
|
|
||||||
shouldRetry: true,
|
|
||||||
delay: 1105991654 * time.Nanosecond, // account for randomness with known seed
|
|
||||||
},
|
|
||||||
dynamoDBResult: testResult{
|
|
||||||
shouldRetry: true,
|
|
||||||
delay: 50 * time.Millisecond,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// Test a fake throttling exception
|
|
||||||
testCase{
|
|
||||||
input: testInput{
|
|
||||||
err: &Error{
|
|
||||||
Code: "MyMadeUpThrottlingCode",
|
|
||||||
},
|
|
||||||
numRetries: 0,
|
|
||||||
},
|
|
||||||
defaultResult: testResult{
|
|
||||||
shouldRetry: false,
|
|
||||||
delay: 300 * time.Millisecond,
|
|
||||||
},
|
|
||||||
dynamoDBResult: testResult{
|
|
||||||
shouldRetry: false,
|
|
||||||
delay: 25 * time.Millisecond,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// Test 5xx errors
|
|
||||||
testCase{
|
|
||||||
input: testInput{
|
|
||||||
res: &http.Response{
|
|
||||||
StatusCode: http.StatusInternalServerError,
|
|
||||||
},
|
|
||||||
numRetries: 1,
|
|
||||||
},
|
|
||||||
defaultResult: testResult{
|
|
||||||
shouldRetry: true,
|
|
||||||
delay: 600 * time.Millisecond,
|
|
||||||
},
|
|
||||||
dynamoDBResult: testResult{
|
|
||||||
shouldRetry: true,
|
|
||||||
delay: 50 * time.Millisecond,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
testCase{
|
|
||||||
input: testInput{
|
|
||||||
res: &http.Response{
|
|
||||||
StatusCode: http.StatusServiceUnavailable,
|
|
||||||
},
|
|
||||||
numRetries: 1,
|
|
||||||
},
|
|
||||||
defaultResult: testResult{
|
|
||||||
shouldRetry: true,
|
|
||||||
delay: 600 * time.Millisecond,
|
|
||||||
},
|
|
||||||
dynamoDBResult: testResult{
|
|
||||||
shouldRetry: true,
|
|
||||||
delay: 50 * time.Millisecond,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// Test a random 400 error
|
|
||||||
testCase{
|
|
||||||
input: testInput{
|
|
||||||
res: &http.Response{
|
|
||||||
StatusCode: http.StatusNotFound,
|
|
||||||
},
|
|
||||||
numRetries: 1,
|
|
||||||
},
|
|
||||||
defaultResult: testResult{
|
|
||||||
shouldRetry: false,
|
|
||||||
delay: 600 * time.Millisecond,
|
|
||||||
},
|
|
||||||
dynamoDBResult: testResult{
|
|
||||||
shouldRetry: false,
|
|
||||||
delay: 50 * time.Millisecond,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// Test a temporary net.Error
|
|
||||||
testCase{
|
|
||||||
input: testInput{
|
|
||||||
res: &http.Response{},
|
|
||||||
err: &net.DNSError{
|
|
||||||
IsTimeout: true,
|
|
||||||
},
|
|
||||||
numRetries: 2,
|
|
||||||
},
|
|
||||||
defaultResult: testResult{
|
|
||||||
shouldRetry: true,
|
|
||||||
delay: 1200 * time.Millisecond,
|
|
||||||
},
|
|
||||||
dynamoDBResult: testResult{
|
|
||||||
shouldRetry: true,
|
|
||||||
delay: 100 * time.Millisecond,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// Test a non-temporary net.Error
|
|
||||||
testCase{
|
|
||||||
input: testInput{
|
|
||||||
res: &http.Response{},
|
|
||||||
err: &net.DNSError{
|
|
||||||
IsTimeout: false,
|
|
||||||
},
|
|
||||||
numRetries: 3,
|
|
||||||
},
|
|
||||||
defaultResult: testResult{
|
|
||||||
shouldRetry: false,
|
|
||||||
delay: 2400 * time.Millisecond,
|
|
||||||
},
|
|
||||||
dynamoDBResult: testResult{
|
|
||||||
shouldRetry: false,
|
|
||||||
delay: 200 * time.Millisecond,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// Assert failure after hitting max default retries
|
|
||||||
testCase{
|
|
||||||
input: testInput{
|
|
||||||
err: &Error{
|
|
||||||
Code: "ProvisionedThroughputExceededException",
|
|
||||||
},
|
|
||||||
numRetries: defaultMaxRetries,
|
|
||||||
},
|
|
||||||
defaultResult: testResult{
|
|
||||||
shouldRetry: false,
|
|
||||||
delay: 4313582352 * time.Nanosecond, // account for randomness with known seed
|
|
||||||
},
|
|
||||||
dynamoDBResult: testResult{
|
|
||||||
shouldRetry: true,
|
|
||||||
delay: 200 * time.Millisecond,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// Assert failure after hitting max DynamoDB retries
|
|
||||||
testCase{
|
|
||||||
input: testInput{
|
|
||||||
err: &Error{
|
|
||||||
Code: "ProvisionedThroughputExceededException",
|
|
||||||
},
|
|
||||||
numRetries: dynamoDBMaxRetries,
|
|
||||||
},
|
|
||||||
defaultResult: testResult{
|
|
||||||
shouldRetry: false,
|
|
||||||
delay: maxDelay,
|
|
||||||
},
|
|
||||||
dynamoDBResult: testResult{
|
|
||||||
shouldRetry: false,
|
|
||||||
delay: maxDelay,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// Assert we never go over the maxDelay value
|
|
||||||
testCase{
|
|
||||||
input: testInput{
|
|
||||||
numRetries: 25,
|
|
||||||
},
|
|
||||||
defaultResult: testResult{
|
|
||||||
shouldRetry: false,
|
|
||||||
delay: maxDelay,
|
|
||||||
},
|
|
||||||
dynamoDBResult: testResult{
|
|
||||||
shouldRetry: false,
|
|
||||||
delay: maxDelay,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDefaultRetryPolicy(t *testing.T) {
|
|
||||||
rand.Seed(0)
|
|
||||||
var policy RetryPolicy
|
|
||||||
policy = &DefaultRetryPolicy{}
|
|
||||||
for _, test := range testCases {
|
|
||||||
res := test.input.res
|
|
||||||
err := test.input.err
|
|
||||||
numRetries := test.input.numRetries
|
|
||||||
|
|
||||||
shouldRetry := policy.ShouldRetry("", res, err, numRetries)
|
|
||||||
if shouldRetry != test.defaultResult.shouldRetry {
|
|
||||||
t.Errorf("ShouldRetry returned %v, expected %v res=%#v err=%#v numRetries=%d", shouldRetry, test.defaultResult.shouldRetry, res, err, numRetries)
|
|
||||||
}
|
|
||||||
delay := policy.Delay("", res, err, numRetries)
|
|
||||||
if delay != test.defaultResult.delay {
|
|
||||||
t.Errorf("Delay returned %v, expected %v res=%#v err=%#v numRetries=%d", delay, test.defaultResult.delay, res, err, numRetries)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDynamoDBRetryPolicy(t *testing.T) {
|
|
||||||
var policy RetryPolicy
|
|
||||||
policy = &DynamoDBRetryPolicy{}
|
|
||||||
for _, test := range testCases {
|
|
||||||
res := test.input.res
|
|
||||||
err := test.input.err
|
|
||||||
numRetries := test.input.numRetries
|
|
||||||
|
|
||||||
shouldRetry := policy.ShouldRetry("", res, err, numRetries)
|
|
||||||
if shouldRetry != test.dynamoDBResult.shouldRetry {
|
|
||||||
t.Errorf("ShouldRetry returned %v, expected %v res=%#v err=%#v numRetries=%d", shouldRetry, test.dynamoDBResult.shouldRetry, res, err, numRetries)
|
|
||||||
}
|
|
||||||
delay := policy.Delay("", res, err, numRetries)
|
|
||||||
if delay != test.dynamoDBResult.delay {
|
|
||||||
t.Errorf("Delay returned %v, expected %v res=%#v err=%#v numRetries=%d", delay, test.dynamoDBResult.delay, res, err, numRetries)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNeverRetryPolicy(t *testing.T) {
|
|
||||||
var policy RetryPolicy
|
|
||||||
policy = &NeverRetryPolicy{}
|
|
||||||
for _, test := range testCases {
|
|
||||||
res := test.input.res
|
|
||||||
err := test.input.err
|
|
||||||
numRetries := test.input.numRetries
|
|
||||||
|
|
||||||
shouldRetry := policy.ShouldRetry("", res, err, numRetries)
|
|
||||||
if shouldRetry {
|
|
||||||
t.Errorf("ShouldRetry returned %v, expected %v res=%#v err=%#v numRetries=%d", shouldRetry, false, res, err, numRetries)
|
|
||||||
}
|
|
||||||
delay := policy.Delay("", res, err, numRetries)
|
|
||||||
if delay != time.Duration(0) {
|
|
||||||
t.Errorf("Delay returned %v, expected %v res=%#v err=%#v numRetries=%d", delay, time.Duration(0), res, err, numRetries)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
569
Godeps/_workspace/src/github.com/AdRoll/goamz/aws/sign_test.go
generated
vendored
569
Godeps/_workspace/src/github.com/AdRoll/goamz/aws/sign_test.go
generated
vendored
|
@ -1,569 +0,0 @@
|
||||||
package aws_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/AdRoll/goamz/aws"
|
|
||||||
"gopkg.in/check.v1"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = check.Suite(&V4SignerSuite{})
|
|
||||||
|
|
||||||
type V4SignerSuite struct {
|
|
||||||
auth aws.Auth
|
|
||||||
region aws.Region
|
|
||||||
cases []V4SignerSuiteCase
|
|
||||||
}
|
|
||||||
|
|
||||||
type V4SignerSuiteCase struct {
|
|
||||||
label string
|
|
||||||
request V4SignerSuiteCaseRequest
|
|
||||||
canonicalRequest string
|
|
||||||
stringToSign string
|
|
||||||
signature string
|
|
||||||
authorization string
|
|
||||||
}
|
|
||||||
|
|
||||||
type V4SignerSuiteCaseRequest struct {
|
|
||||||
method string
|
|
||||||
host string
|
|
||||||
url string
|
|
||||||
headers []string
|
|
||||||
body string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *V4SignerSuite) SetUpSuite(c *check.C) {
|
|
||||||
s.auth = aws.Auth{AccessKey: "AKIDEXAMPLE", SecretKey: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY"}
|
|
||||||
s.region = aws.USEast
|
|
||||||
|
|
||||||
// Test cases from the Signature Version 4 Test Suite (http://goo.gl/nguvs0)
|
|
||||||
s.cases = append(s.cases,
|
|
||||||
|
|
||||||
// get-header-key-duplicate
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "get-header-key-duplicate",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "POST",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "/",
|
|
||||||
headers: []string{"DATE:Mon, 09 Sep 2011 23:36:00 GMT", "ZOO:zoobar", "zoo:foobar", "zoo:zoobar"},
|
|
||||||
},
|
|
||||||
canonicalRequest: "POST\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\nzoo:foobar,zoobar,zoobar\n\ndate;host;zoo\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n3c52f0eaae2b61329c0a332e3fa15842a37bc5812cf4d80eb64784308850e313",
|
|
||||||
signature: "54afcaaf45b331f81cd2edb974f7b824ff4dd594cbbaa945ed636b48477368ed",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host;zoo, Signature=54afcaaf45b331f81cd2edb974f7b824ff4dd594cbbaa945ed636b48477368ed",
|
|
||||||
},
|
|
||||||
|
|
||||||
// get-header-value-order
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "get-header-value-order",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "POST",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "/",
|
|
||||||
headers: []string{"DATE:Mon, 09 Sep 2011 23:36:00 GMT", "p:z", "p:a", "p:p", "p:a"},
|
|
||||||
},
|
|
||||||
canonicalRequest: "POST\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\np:a,a,p,z\n\ndate;host;p\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n94c0389fefe0988cbbedc8606f0ca0b485b48da010d09fc844b45b697c8924fe",
|
|
||||||
signature: "d2973954263943b11624a11d1c963ca81fb274169c7868b2858c04f083199e3d",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host;p, Signature=d2973954263943b11624a11d1c963ca81fb274169c7868b2858c04f083199e3d",
|
|
||||||
},
|
|
||||||
|
|
||||||
// get-header-value-trim
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "get-header-value-trim",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "POST",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "/",
|
|
||||||
headers: []string{"DATE:Mon, 09 Sep 2011 23:36:00 GMT", "p: phfft "},
|
|
||||||
},
|
|
||||||
canonicalRequest: "POST\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\np:phfft\n\ndate;host;p\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\ndddd1902add08da1ac94782b05f9278c08dc7468db178a84f8950d93b30b1f35",
|
|
||||||
signature: "debf546796015d6f6ded8626f5ce98597c33b47b9164cf6b17b4642036fcb592",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host;p, Signature=debf546796015d6f6ded8626f5ce98597c33b47b9164cf6b17b4642036fcb592",
|
|
||||||
},
|
|
||||||
|
|
||||||
// get-empty
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "get-relative-relative",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "GET",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "",
|
|
||||||
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
|
||||||
},
|
|
||||||
canonicalRequest: "GET\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n366b91fb121d72a00f46bbe8d395f53a102b06dfb7e79636515208ed3fa606b1",
|
|
||||||
signature: "b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
|
||||||
},
|
|
||||||
|
|
||||||
// get-single-relative
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "get-relative-relative",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "GET",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "/.",
|
|
||||||
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
|
||||||
},
|
|
||||||
canonicalRequest: "GET\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n366b91fb121d72a00f46bbe8d395f53a102b06dfb7e79636515208ed3fa606b1",
|
|
||||||
signature: "b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
|
||||||
},
|
|
||||||
|
|
||||||
// get-multiple-relative
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "get-relative-relative",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "GET",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "/./././",
|
|
||||||
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
|
||||||
},
|
|
||||||
canonicalRequest: "GET\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n366b91fb121d72a00f46bbe8d395f53a102b06dfb7e79636515208ed3fa606b1",
|
|
||||||
signature: "b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
|
||||||
},
|
|
||||||
|
|
||||||
// get-relative-relative
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "get-relative-relative",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "GET",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "/foo/bar/../..",
|
|
||||||
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
|
||||||
},
|
|
||||||
canonicalRequest: "GET\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n366b91fb121d72a00f46bbe8d395f53a102b06dfb7e79636515208ed3fa606b1",
|
|
||||||
signature: "b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
|
||||||
},
|
|
||||||
|
|
||||||
// get-relative
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "get-relative",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "GET",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "/foo/..",
|
|
||||||
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
|
||||||
},
|
|
||||||
canonicalRequest: "GET\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n366b91fb121d72a00f46bbe8d395f53a102b06dfb7e79636515208ed3fa606b1",
|
|
||||||
signature: "b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
|
||||||
},
|
|
||||||
|
|
||||||
// get-slash-dot-slash
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "get-slash-dot-slash",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "GET",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "/./",
|
|
||||||
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
|
||||||
},
|
|
||||||
canonicalRequest: "GET\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n366b91fb121d72a00f46bbe8d395f53a102b06dfb7e79636515208ed3fa606b1",
|
|
||||||
signature: "b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
|
||||||
},
|
|
||||||
|
|
||||||
// get-slash-pointless-dot
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "get-slash-pointless-dot",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "GET",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "/./foo",
|
|
||||||
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
|
||||||
},
|
|
||||||
canonicalRequest: "GET\n/foo\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n8021a97572ee460f87ca67f4e8c0db763216d84715f5424a843a5312a3321e2d",
|
|
||||||
signature: "910e4d6c9abafaf87898e1eb4c929135782ea25bb0279703146455745391e63a",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=910e4d6c9abafaf87898e1eb4c929135782ea25bb0279703146455745391e63a",
|
|
||||||
},
|
|
||||||
|
|
||||||
// get-slash
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "get-slash",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "GET",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "//",
|
|
||||||
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
|
||||||
},
|
|
||||||
canonicalRequest: "GET\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n366b91fb121d72a00f46bbe8d395f53a102b06dfb7e79636515208ed3fa606b1",
|
|
||||||
signature: "b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
|
||||||
},
|
|
||||||
|
|
||||||
// get-slashes
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "get-slashes",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "GET",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "//foo//",
|
|
||||||
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
|
||||||
},
|
|
||||||
canonicalRequest: "GET\n/foo/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n6bb4476ee8745730c9cb79f33a0c70baa6d8af29c0077fa12e4e8f1dd17e7098",
|
|
||||||
signature: "b00392262853cfe3201e47ccf945601079e9b8a7f51ee4c3d9ee4f187aa9bf19",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b00392262853cfe3201e47ccf945601079e9b8a7f51ee4c3d9ee4f187aa9bf19",
|
|
||||||
},
|
|
||||||
|
|
||||||
// get-space
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "get-space",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "GET",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "/%20/foo",
|
|
||||||
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
|
||||||
},
|
|
||||||
canonicalRequest: "GET\n/%20/foo\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n69c45fb9fe3fd76442b5086e50b2e9fec8298358da957b293ef26e506fdfb54b",
|
|
||||||
signature: "f309cfbd10197a230c42dd17dbf5cca8a0722564cb40a872d25623cfa758e374",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=f309cfbd10197a230c42dd17dbf5cca8a0722564cb40a872d25623cfa758e374",
|
|
||||||
},
|
|
||||||
|
|
||||||
// get-unreserved
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "get-unreserved",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "GET",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "/-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
|
|
||||||
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
|
||||||
},
|
|
||||||
canonicalRequest: "GET\n/-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\ndf63ee3247c0356c696a3b21f8d8490b01fa9cd5bc6550ef5ef5f4636b7b8901",
|
|
||||||
signature: "830cc36d03f0f84e6ee4953fbe701c1c8b71a0372c63af9255aa364dd183281e",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=830cc36d03f0f84e6ee4953fbe701c1c8b71a0372c63af9255aa364dd183281e",
|
|
||||||
},
|
|
||||||
|
|
||||||
// get-utf8
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "get-utf8",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "GET",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "/%E1%88%B4",
|
|
||||||
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
|
||||||
},
|
|
||||||
canonicalRequest: "GET\n/%E1%88%B4\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n27ba31df5dbc6e063d8f87d62eb07143f7f271c5330a917840586ac1c85b6f6b",
|
|
||||||
signature: "8d6634c189aa8c75c2e51e106b6b5121bed103fdb351f7d7d4381c738823af74",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=8d6634c189aa8c75c2e51e106b6b5121bed103fdb351f7d7d4381c738823af74",
|
|
||||||
},
|
|
||||||
|
|
||||||
// get-vanilla-empty-query-key
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "get-vanilla-empty-query-key",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "GET",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "/?foo=bar",
|
|
||||||
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
|
||||||
},
|
|
||||||
canonicalRequest: "GET\n/\nfoo=bar\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n0846c2945b0832deb7a463c66af5c4f8bd54ec28c438e67a214445b157c9ddf8",
|
|
||||||
signature: "56c054473fd260c13e4e7393eb203662195f5d4a1fada5314b8b52b23f985e9f",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=56c054473fd260c13e4e7393eb203662195f5d4a1fada5314b8b52b23f985e9f",
|
|
||||||
},
|
|
||||||
|
|
||||||
// get-vanilla-query-order-key-case
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "get-vanilla-query-order-key-case",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "GET",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "/?foo=Zoo&foo=aha",
|
|
||||||
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
|
||||||
},
|
|
||||||
canonicalRequest: "GET\n/\nfoo=Zoo&foo=aha\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\ne25f777ba161a0f1baf778a87faf057187cf5987f17953320e3ca399feb5f00d",
|
|
||||||
signature: "be7148d34ebccdc6423b19085378aa0bee970bdc61d144bd1a8c48c33079ab09",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=be7148d34ebccdc6423b19085378aa0bee970bdc61d144bd1a8c48c33079ab09",
|
|
||||||
},
|
|
||||||
|
|
||||||
// get-vanilla-query-order-key
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "get-vanilla-query-order-key",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "GET",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "/?a=foo&b=foo",
|
|
||||||
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
|
||||||
},
|
|
||||||
canonicalRequest: "GET\n/\na=foo&b=foo\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n2f23d14fe13caebf6dfda346285c6d9c14f49eaca8f5ec55c627dd7404f7a727",
|
|
||||||
signature: "0dc122f3b28b831ab48ba65cb47300de53fbe91b577fe113edac383730254a3b",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=0dc122f3b28b831ab48ba65cb47300de53fbe91b577fe113edac383730254a3b",
|
|
||||||
},
|
|
||||||
|
|
||||||
// get-vanilla-query-order-value
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "get-vanilla-query-order-value",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "GET",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "/?foo=b&foo=a",
|
|
||||||
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
|
||||||
},
|
|
||||||
canonicalRequest: "GET\n/\nfoo=a&foo=b\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n33dffc220e89131f8f6157a35c40903daa658608d9129ff9489e5cf5bbd9b11b",
|
|
||||||
signature: "feb926e49e382bec75c9d7dcb2a1b6dc8aa50ca43c25d2bc51143768c0875acc",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=feb926e49e382bec75c9d7dcb2a1b6dc8aa50ca43c25d2bc51143768c0875acc",
|
|
||||||
},
|
|
||||||
|
|
||||||
// get-vanilla-query-unreserved
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "get-vanilla-query-unreserved",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "GET",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "/?-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz=-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
|
|
||||||
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
|
||||||
},
|
|
||||||
canonicalRequest: "GET\n/\n-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz=-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\nd2578f3156d4c9d180713d1ff20601d8a3eed0dd35447d24603d7d67414bd6b5",
|
|
||||||
signature: "f1498ddb4d6dae767d97c466fb92f1b59a2c71ca29ac954692663f9db03426fb",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=f1498ddb4d6dae767d97c466fb92f1b59a2c71ca29ac954692663f9db03426fb",
|
|
||||||
},
|
|
||||||
|
|
||||||
// get-vanilla-query
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "get-vanilla-query",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "GET",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "/",
|
|
||||||
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
|
||||||
},
|
|
||||||
canonicalRequest: "GET\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n366b91fb121d72a00f46bbe8d395f53a102b06dfb7e79636515208ed3fa606b1",
|
|
||||||
signature: "b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
|
||||||
},
|
|
||||||
|
|
||||||
// get-vanilla-ut8-query
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "get-vanilla-ut8-query",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "GET",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "/?ሴ=bar",
|
|
||||||
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
|
||||||
},
|
|
||||||
canonicalRequest: "GET\n/\n%E1%88%B4=bar\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\nde5065ff39c131e6c2e2bd19cd9345a794bf3b561eab20b8d97b2093fc2a979e",
|
|
||||||
signature: "6fb359e9a05394cc7074e0feb42573a2601abc0c869a953e8c5c12e4e01f1a8c",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=6fb359e9a05394cc7074e0feb42573a2601abc0c869a953e8c5c12e4e01f1a8c",
|
|
||||||
},
|
|
||||||
|
|
||||||
// get-vanilla
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "get-vanilla",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "GET",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "/",
|
|
||||||
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
|
||||||
},
|
|
||||||
canonicalRequest: "GET\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n366b91fb121d72a00f46bbe8d395f53a102b06dfb7e79636515208ed3fa606b1",
|
|
||||||
signature: "b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
|
||||||
},
|
|
||||||
|
|
||||||
// post-header-key-case
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "post-header-key-case",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "POST",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "/",
|
|
||||||
headers: []string{"DATE:Mon, 09 Sep 2011 23:36:00 GMT"},
|
|
||||||
},
|
|
||||||
canonicalRequest: "POST\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n05da62cee468d24ae84faff3c39f1b85540de60243c1bcaace39c0a2acc7b2c4",
|
|
||||||
signature: "22902d79e148b64e7571c3565769328423fe276eae4b26f83afceda9e767f726",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=22902d79e148b64e7571c3565769328423fe276eae4b26f83afceda9e767f726",
|
|
||||||
},
|
|
||||||
|
|
||||||
// post-header-key-sort
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "post-header-key-sort",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "POST",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "/",
|
|
||||||
headers: []string{"DATE:Mon, 09 Sep 2011 23:36:00 GMT", "ZOO:zoobar"},
|
|
||||||
},
|
|
||||||
canonicalRequest: "POST\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\nzoo:zoobar\n\ndate;host;zoo\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n34e1bddeb99e76ee01d63b5e28656111e210529efeec6cdfd46a48e4c734545d",
|
|
||||||
signature: "b7a95a52518abbca0964a999a880429ab734f35ebbf1235bd79a5de87756dc4a",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host;zoo, Signature=b7a95a52518abbca0964a999a880429ab734f35ebbf1235bd79a5de87756dc4a",
|
|
||||||
},
|
|
||||||
|
|
||||||
// post-header-value-case
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "post-header-value-case",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "POST",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "/",
|
|
||||||
headers: []string{"DATE:Mon, 09 Sep 2011 23:36:00 GMT", "zoo:ZOOBAR"},
|
|
||||||
},
|
|
||||||
canonicalRequest: "POST\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\nzoo:ZOOBAR\n\ndate;host;zoo\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n3aae6d8274b8c03e2cc96fc7d6bda4b9bd7a0a184309344470b2c96953e124aa",
|
|
||||||
signature: "273313af9d0c265c531e11db70bbd653f3ba074c1009239e8559d3987039cad7",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host;zoo, Signature=273313af9d0c265c531e11db70bbd653f3ba074c1009239e8559d3987039cad7",
|
|
||||||
},
|
|
||||||
|
|
||||||
// post-vanilla-empty-query-value
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "post-vanilla-empty-query-value",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "POST",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "/?foo=bar",
|
|
||||||
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
|
||||||
},
|
|
||||||
canonicalRequest: "POST\n/\nfoo=bar\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\ncd4f39132d8e60bb388831d734230460872b564871c47f5de62e62d1a68dbe1e",
|
|
||||||
signature: "b6e3b79003ce0743a491606ba1035a804593b0efb1e20a11cba83f8c25a57a92",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b6e3b79003ce0743a491606ba1035a804593b0efb1e20a11cba83f8c25a57a92",
|
|
||||||
},
|
|
||||||
|
|
||||||
// post-vanilla-query
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "post-vanilla-query",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "POST",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "/?foo=bar",
|
|
||||||
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
|
||||||
},
|
|
||||||
canonicalRequest: "POST\n/\nfoo=bar\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\ncd4f39132d8e60bb388831d734230460872b564871c47f5de62e62d1a68dbe1e",
|
|
||||||
signature: "b6e3b79003ce0743a491606ba1035a804593b0efb1e20a11cba83f8c25a57a92",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b6e3b79003ce0743a491606ba1035a804593b0efb1e20a11cba83f8c25a57a92",
|
|
||||||
},
|
|
||||||
|
|
||||||
// post-vanilla
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "post-vanilla",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "POST",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "/",
|
|
||||||
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
|
||||||
},
|
|
||||||
canonicalRequest: "POST\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n05da62cee468d24ae84faff3c39f1b85540de60243c1bcaace39c0a2acc7b2c4",
|
|
||||||
signature: "22902d79e148b64e7571c3565769328423fe276eae4b26f83afceda9e767f726",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=22902d79e148b64e7571c3565769328423fe276eae4b26f83afceda9e767f726",
|
|
||||||
},
|
|
||||||
|
|
||||||
// post-x-www-form-urlencoded-parameters
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "post-x-www-form-urlencoded-parameters",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "POST",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "/",
|
|
||||||
headers: []string{"Content-Type:application/x-www-form-urlencoded; charset=utf8", "Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
|
||||||
body: "foo=bar",
|
|
||||||
},
|
|
||||||
canonicalRequest: "POST\n/\n\ncontent-type:application/x-www-form-urlencoded; charset=utf8\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ncontent-type;date;host\n3ba8907e7a252327488df390ed517c45b96dead033600219bdca7107d1d3f88a",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\nc4115f9e54b5cecf192b1eaa23b8e88ed8dc5391bd4fde7b3fff3d9c9fe0af1f",
|
|
||||||
signature: "b105eb10c6d318d2294de9d49dd8b031b55e3c3fe139f2e637da70511e9e7b71",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=content-type;date;host, Signature=b105eb10c6d318d2294de9d49dd8b031b55e3c3fe139f2e637da70511e9e7b71",
|
|
||||||
},
|
|
||||||
|
|
||||||
// post-x-www-form-urlencoded
|
|
||||||
V4SignerSuiteCase{
|
|
||||||
label: "post-x-www-form-urlencoded",
|
|
||||||
request: V4SignerSuiteCaseRequest{
|
|
||||||
method: "POST",
|
|
||||||
host: "host.foo.com",
|
|
||||||
url: "/",
|
|
||||||
headers: []string{"Content-Type:application/x-www-form-urlencoded", "Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
|
||||||
body: "foo=bar",
|
|
||||||
},
|
|
||||||
canonicalRequest: "POST\n/\n\ncontent-type:application/x-www-form-urlencoded\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ncontent-type;date;host\n3ba8907e7a252327488df390ed517c45b96dead033600219bdca7107d1d3f88a",
|
|
||||||
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n4c5c6e4b52fb5fb947a8733982a8a5a61b14f04345cbfe6e739236c76dd48f74",
|
|
||||||
signature: "5a15b22cf462f047318703b92e6f4f38884e4a7ab7b1d6426ca46a8bd1c26cbc",
|
|
||||||
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=content-type;date;host, Signature=5a15b22cf462f047318703b92e6f4f38884e4a7ab7b1d6426ca46a8bd1c26cbc",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *V4SignerSuite) TestCases(c *check.C) {
|
|
||||||
signer := aws.NewV4Signer(s.auth, "host", s.region)
|
|
||||||
|
|
||||||
for _, testCase := range s.cases {
|
|
||||||
|
|
||||||
req, err := http.NewRequest(testCase.request.method, "http://"+testCase.request.host+testCase.request.url, strings.NewReader(testCase.request.body))
|
|
||||||
c.Assert(err, check.IsNil, check.Commentf("Testcase: %s", testCase.label))
|
|
||||||
for _, v := range testCase.request.headers {
|
|
||||||
h := strings.SplitN(v, ":", 2)
|
|
||||||
req.Header.Add(h[0], h[1])
|
|
||||||
}
|
|
||||||
req.Header.Set("host", req.Host)
|
|
||||||
|
|
||||||
t := signer.RequestTime(req)
|
|
||||||
|
|
||||||
canonicalRequest := signer.CanonicalRequest(req)
|
|
||||||
c.Check(canonicalRequest, check.Equals, testCase.canonicalRequest, check.Commentf("Testcase: %s", testCase.label))
|
|
||||||
|
|
||||||
stringToSign := signer.StringToSign(t, canonicalRequest)
|
|
||||||
c.Check(stringToSign, check.Equals, testCase.stringToSign, check.Commentf("Testcase: %s", testCase.label))
|
|
||||||
|
|
||||||
signature := signer.Signature(t, stringToSign)
|
|
||||||
c.Check(signature, check.Equals, testCase.signature, check.Commentf("Testcase: %s", testCase.label))
|
|
||||||
|
|
||||||
authorization := signer.Authorization(req.Header, t, signature)
|
|
||||||
c.Check(authorization, check.Equals, testCase.authorization, check.Commentf("Testcase: %s", testCase.label))
|
|
||||||
|
|
||||||
signer.Sign(req)
|
|
||||||
c.Check(req.Header.Get("Authorization"), check.Equals, testCase.authorization, check.Commentf("Testcase: %s", testCase.label))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleV4Signer() {
|
|
||||||
// Get auth from env vars
|
|
||||||
auth, err := aws.EnvAuth()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a signer with the auth, name of the service, and aws region
|
|
||||||
signer := aws.NewV4Signer(auth, "dynamodb", aws.USEast)
|
|
||||||
|
|
||||||
// Create a request
|
|
||||||
req, err := http.NewRequest("POST", aws.USEast.DynamoDBEndpoint, strings.NewReader("sample_request"))
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Date or x-amz-date header is required to sign a request
|
|
||||||
req.Header.Add("Date", time.Now().UTC().Format(http.TimeFormat))
|
|
||||||
|
|
||||||
// Sign the request
|
|
||||||
signer.Sign(req)
|
|
||||||
|
|
||||||
// Issue signed request
|
|
||||||
http.DefaultClient.Do(req)
|
|
||||||
}
|
|
52
Godeps/_workspace/src/github.com/AdRoll/goamz/cloudfront/cloudfront_test.go
generated
vendored
52
Godeps/_workspace/src/github.com/AdRoll/goamz/cloudfront/cloudfront_test.go
generated
vendored
|
@ -1,52 +0,0 @@
|
||||||
package cloudfront
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/pem"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/url"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSignedCannedURL(t *testing.T) {
|
|
||||||
rawKey, err := ioutil.ReadFile("testdata/key.pem")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pemKey, _ := pem.Decode(rawKey)
|
|
||||||
privateKey, err := x509.ParsePKCS1PrivateKey(pemKey.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cf := &CloudFront{
|
|
||||||
key: privateKey,
|
|
||||||
keyPairId: "test-key-pair-1231245",
|
|
||||||
BaseURL: "https://cloudfront.com",
|
|
||||||
}
|
|
||||||
|
|
||||||
expireTime, err := time.Parse(time.RFC3339, "2014-03-28T14:00:21Z")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
query := make(url.Values)
|
|
||||||
query.Add("test", "value")
|
|
||||||
|
|
||||||
uri, err := cf.CannedSignedURL("test", "test=value", expireTime)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
parsed, err := url.Parse(uri)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
signature := parsed.Query().Get("Signature")
|
|
||||||
if signature == "" {
|
|
||||||
t.Fatal("Encoded signature is empty")
|
|
||||||
}
|
|
||||||
}
|
|
6
Godeps/_workspace/src/github.com/AdRoll/goamz/cloudfront/testdata/key.pub
generated
vendored
6
Godeps/_workspace/src/github.com/AdRoll/goamz/cloudfront/testdata/key.pub
generated
vendored
|
@ -1,6 +0,0 @@
|
||||||
-----BEGIN PUBLIC KEY-----
|
|
||||||
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC0yMzp9DkPAE99DhsEaGkqougL
|
|
||||||
vtmDKri4bZj0fFjmGmjyyjz9hlrsr87LHVWzH/7igK7040HG1UqypX3ijtJa9+6B
|
|
||||||
KHwBBctboU3y4GfwFwVAOumY9UytFpyPlgUFrffZLQAywKkT24OgcfEj0G5kiQn7
|
|
||||||
60wFnmSUtOuITo708QIDAQAB
|
|
||||||
-----END PUBLIC KEY-----
|
|
27
Godeps/_workspace/src/github.com/AdRoll/goamz/s3/export_test.go
generated
vendored
27
Godeps/_workspace/src/github.com/AdRoll/goamz/s3/export_test.go
generated
vendored
|
@ -1,27 +0,0 @@
|
||||||
package s3
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/AdRoll/goamz/aws"
|
|
||||||
)
|
|
||||||
|
|
||||||
var originalStrategy = attempts
|
|
||||||
|
|
||||||
func SetAttemptStrategy(s *aws.AttemptStrategy) {
|
|
||||||
if s == nil {
|
|
||||||
attempts = originalStrategy
|
|
||||||
} else {
|
|
||||||
attempts = *s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
205
Godeps/_workspace/src/github.com/AdRoll/goamz/s3/lifecycle_test.go
generated
vendored
205
Godeps/_workspace/src/github.com/AdRoll/goamz/s3/lifecycle_test.go
generated
vendored
|
@ -1,205 +0,0 @@
|
||||||
package s3_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/xml"
|
|
||||||
"github.com/AdRoll/goamz/s3"
|
|
||||||
"gopkg.in/check.v1"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *S) TestLifecycleConfiguration(c *check.C) {
|
|
||||||
date, err := time.Parse(s3.LifecycleRuleDateFormat, "2014-09-10")
|
|
||||||
c.Check(err, check.IsNil)
|
|
||||||
|
|
||||||
conf := &s3.LifecycleConfiguration{}
|
|
||||||
|
|
||||||
rule := s3.NewLifecycleRule("transition-days", "/")
|
|
||||||
rule.SetTransitionDays(7)
|
|
||||||
conf.AddRule(rule)
|
|
||||||
|
|
||||||
rule = s3.NewLifecycleRule("transition-date", "/")
|
|
||||||
rule.SetTransitionDate(date)
|
|
||||||
conf.AddRule(rule)
|
|
||||||
|
|
||||||
rule = s3.NewLifecycleRule("expiration-days", "")
|
|
||||||
rule.SetExpirationDays(1)
|
|
||||||
conf.AddRule(rule)
|
|
||||||
|
|
||||||
rule = s3.NewLifecycleRule("expiration-date", "")
|
|
||||||
rule.SetExpirationDate(date)
|
|
||||||
conf.AddRule(rule)
|
|
||||||
|
|
||||||
rule = s3.NewLifecycleRule("noncurrent-transition", "")
|
|
||||||
rule.SetNoncurrentVersionTransitionDays(11)
|
|
||||||
conf.AddRule(rule)
|
|
||||||
|
|
||||||
rule = s3.NewLifecycleRule("noncurrent-expiration", "")
|
|
||||||
rule.SetNoncurrentVersionExpirationDays(1011)
|
|
||||||
|
|
||||||
// Test Disable() and Enable() toggling
|
|
||||||
c.Check(rule.Status, check.Equals, s3.LifecycleRuleStatusEnabled)
|
|
||||||
rule.Disable()
|
|
||||||
c.Check(rule.Status, check.Equals, s3.LifecycleRuleStatusDisabled)
|
|
||||||
rule.Enable()
|
|
||||||
c.Check(rule.Status, check.Equals, s3.LifecycleRuleStatusEnabled)
|
|
||||||
rule.Disable()
|
|
||||||
c.Check(rule.Status, check.Equals, s3.LifecycleRuleStatusDisabled)
|
|
||||||
|
|
||||||
conf.AddRule(rule)
|
|
||||||
|
|
||||||
doc, err := xml.MarshalIndent(conf, "", " ")
|
|
||||||
c.Check(err, check.IsNil)
|
|
||||||
|
|
||||||
expectedDoc := `<LifecycleConfiguration>
|
|
||||||
<Rule>
|
|
||||||
<ID>transition-days</ID>
|
|
||||||
<Prefix>/</Prefix>
|
|
||||||
<Status>Enabled</Status>
|
|
||||||
<Transition>
|
|
||||||
<Days>7</Days>
|
|
||||||
<StorageClass>GLACIER</StorageClass>
|
|
||||||
</Transition>
|
|
||||||
</Rule>
|
|
||||||
<Rule>
|
|
||||||
<ID>transition-date</ID>
|
|
||||||
<Prefix>/</Prefix>
|
|
||||||
<Status>Enabled</Status>
|
|
||||||
<Transition>
|
|
||||||
<Date>2014-09-10</Date>
|
|
||||||
<StorageClass>GLACIER</StorageClass>
|
|
||||||
</Transition>
|
|
||||||
</Rule>
|
|
||||||
<Rule>
|
|
||||||
<ID>expiration-days</ID>
|
|
||||||
<Prefix></Prefix>
|
|
||||||
<Status>Enabled</Status>
|
|
||||||
<Expiration>
|
|
||||||
<Days>1</Days>
|
|
||||||
</Expiration>
|
|
||||||
</Rule>
|
|
||||||
<Rule>
|
|
||||||
<ID>expiration-date</ID>
|
|
||||||
<Prefix></Prefix>
|
|
||||||
<Status>Enabled</Status>
|
|
||||||
<Expiration>
|
|
||||||
<Date>2014-09-10</Date>
|
|
||||||
</Expiration>
|
|
||||||
</Rule>
|
|
||||||
<Rule>
|
|
||||||
<ID>noncurrent-transition</ID>
|
|
||||||
<Prefix></Prefix>
|
|
||||||
<Status>Enabled</Status>
|
|
||||||
<NoncurrentVersionTransition>
|
|
||||||
<NoncurrentDays>11</NoncurrentDays>
|
|
||||||
<StorageClass>GLACIER</StorageClass>
|
|
||||||
</NoncurrentVersionTransition>
|
|
||||||
</Rule>
|
|
||||||
<Rule>
|
|
||||||
<ID>noncurrent-expiration</ID>
|
|
||||||
<Prefix></Prefix>
|
|
||||||
<Status>Disabled</Status>
|
|
||||||
<NoncurrentVersionExpiration>
|
|
||||||
<NoncurrentDays>1011</NoncurrentDays>
|
|
||||||
</NoncurrentVersionExpiration>
|
|
||||||
</Rule>
|
|
||||||
</LifecycleConfiguration>`
|
|
||||||
|
|
||||||
c.Check(string(doc), check.Equals, expectedDoc)
|
|
||||||
|
|
||||||
// Unmarshalling test
|
|
||||||
conf2 := &s3.LifecycleConfiguration{}
|
|
||||||
err = xml.Unmarshal(doc, conf2)
|
|
||||||
c.Check(err, check.IsNil)
|
|
||||||
s.checkLifecycleConfigurationEqual(c, conf, conf2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) checkLifecycleConfigurationEqual(c *check.C, conf, conf2 *s3.LifecycleConfiguration) {
|
|
||||||
c.Check(len(*conf2.Rules), check.Equals, len(*conf.Rules))
|
|
||||||
for i, rule := range *conf2.Rules {
|
|
||||||
confRules := *conf.Rules
|
|
||||||
c.Check(rule, check.DeepEquals, confRules[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) checkLifecycleRequest(c *check.C, req *http.Request) {
|
|
||||||
// ?lifecycle= is the only query param
|
|
||||||
v, ok := req.Form["lifecycle"]
|
|
||||||
c.Assert(ok, check.Equals, true)
|
|
||||||
c.Assert(v, check.HasLen, 1)
|
|
||||||
c.Assert(v[0], check.Equals, "")
|
|
||||||
|
|
||||||
c.Assert(req.Header["X-Amz-Date"], check.HasLen, 1)
|
|
||||||
c.Assert(req.Header["X-Amz-Date"][0], check.Not(check.Equals), "")
|
|
||||||
|
|
||||||
// Lifecycle methods require V4 auth
|
|
||||||
usesV4 := strings.HasPrefix(req.Header["Authorization"][0], "AWS4-HMAC-SHA256")
|
|
||||||
c.Assert(usesV4, check.Equals, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestPutLifecycleConfiguration(c *check.C) {
|
|
||||||
testServer.Response(200, nil, "")
|
|
||||||
|
|
||||||
conf := &s3.LifecycleConfiguration{}
|
|
||||||
rule := s3.NewLifecycleRule("id", "")
|
|
||||||
rule.SetTransitionDays(7)
|
|
||||||
conf.AddRule(rule)
|
|
||||||
|
|
||||||
doc, err := xml.Marshal(conf)
|
|
||||||
c.Check(err, check.IsNil)
|
|
||||||
|
|
||||||
b := s.s3.Bucket("bucket")
|
|
||||||
err = b.PutLifecycleConfiguration(conf)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "PUT")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/bucket/")
|
|
||||||
c.Assert(req.Header["Content-Md5"], check.HasLen, 1)
|
|
||||||
c.Assert(req.Header["Content-Md5"][0], check.Not(check.Equals), "")
|
|
||||||
s.checkLifecycleRequest(c, req)
|
|
||||||
|
|
||||||
// Check we sent the correct xml serialization
|
|
||||||
data, err := ioutil.ReadAll(req.Body)
|
|
||||||
req.Body.Close()
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
header := "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
|
||||||
c.Assert(string(data), check.Equals, header+string(doc))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestGetLifecycleConfiguration(c *check.C) {
|
|
||||||
conf := &s3.LifecycleConfiguration{}
|
|
||||||
rule := s3.NewLifecycleRule("id", "")
|
|
||||||
rule.SetTransitionDays(7)
|
|
||||||
conf.AddRule(rule)
|
|
||||||
|
|
||||||
doc, err := xml.Marshal(conf)
|
|
||||||
c.Check(err, check.IsNil)
|
|
||||||
|
|
||||||
testServer.Response(200, nil, string(doc))
|
|
||||||
|
|
||||||
b := s.s3.Bucket("bucket")
|
|
||||||
conf2, err := b.GetLifecycleConfiguration()
|
|
||||||
c.Check(err, check.IsNil)
|
|
||||||
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "GET")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/bucket/")
|
|
||||||
s.checkLifecycleRequest(c, req)
|
|
||||||
s.checkLifecycleConfigurationEqual(c, conf, conf2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestDeleteLifecycleConfiguration(c *check.C) {
|
|
||||||
testServer.Response(200, nil, "")
|
|
||||||
|
|
||||||
b := s.s3.Bucket("bucket")
|
|
||||||
err := b.DeleteLifecycleConfiguration()
|
|
||||||
c.Check(err, check.IsNil)
|
|
||||||
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "DELETE")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/bucket/")
|
|
||||||
s.checkLifecycleRequest(c, req)
|
|
||||||
}
|
|
480
Godeps/_workspace/src/github.com/AdRoll/goamz/s3/multi_test.go
generated
vendored
480
Godeps/_workspace/src/github.com/AdRoll/goamz/s3/multi_test.go
generated
vendored
|
@ -1,480 +0,0 @@
|
||||||
package s3_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/xml"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/AdRoll/goamz/s3"
|
|
||||||
"gopkg.in/check.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *S) TestInitMulti(c *check.C) {
|
|
||||||
testServer.Response(200, nil, InitMultiResultDump)
|
|
||||||
b := s.s3.Bucket("sample")
|
|
||||||
|
|
||||||
metadata := make(map[string][]string)
|
|
||||||
metadata["key1"] = []string{"value1"}
|
|
||||||
metadata["key2"] = []string{"value2"}
|
|
||||||
options := s3.Options{
|
|
||||||
SSE: true,
|
|
||||||
Meta: metadata,
|
|
||||||
ContentEncoding: "text/utf8",
|
|
||||||
CacheControl: "no-cache",
|
|
||||||
RedirectLocation: "http://github.com/AdRoll/goamz",
|
|
||||||
ContentMD5: "0000000000000000",
|
|
||||||
}
|
|
||||||
|
|
||||||
multi, err := b.InitMulti("multi", "text/plain", s3.Private, options)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "POST")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/sample/multi")
|
|
||||||
c.Assert(req.Header["Content-Type"], check.DeepEquals, []string{"text/plain"})
|
|
||||||
c.Assert(req.Header["X-Amz-Acl"], check.DeepEquals, []string{"private"})
|
|
||||||
c.Assert(req.Form["uploads"], check.DeepEquals, []string{""})
|
|
||||||
|
|
||||||
c.Assert(req.Header["X-Amz-Server-Side-Encryption"], check.DeepEquals, []string{"AES256"})
|
|
||||||
c.Assert(req.Header["Content-Encoding"], check.DeepEquals, []string{"text/utf8"})
|
|
||||||
c.Assert(req.Header["Cache-Control"], check.DeepEquals, []string{"no-cache"})
|
|
||||||
c.Assert(req.Header["Content-Md5"], check.DeepEquals, []string{"0000000000000000"})
|
|
||||||
c.Assert(req.Header["X-Amz-Website-Redirect-Location"], check.DeepEquals, []string{"http://github.com/AdRoll/goamz"})
|
|
||||||
c.Assert(req.Header["X-Amz-Meta-Key1"], check.DeepEquals, []string{"value1"})
|
|
||||||
c.Assert(req.Header["X-Amz-Meta-Key2"], check.DeepEquals, []string{"value2"})
|
|
||||||
|
|
||||||
c.Assert(multi.UploadId, check.Matches, "JNbR_[A-Za-z0-9.]+QQ--")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestMultiNoPreviousUpload(c *check.C) {
|
|
||||||
// Don't retry the NoSuchUpload error.
|
|
||||||
s.DisableRetries()
|
|
||||||
|
|
||||||
testServer.Response(404, nil, NoSuchUploadErrorDump)
|
|
||||||
testServer.Response(200, nil, InitMultiResultDump)
|
|
||||||
|
|
||||||
b := s.s3.Bucket("sample")
|
|
||||||
|
|
||||||
multi, err := b.Multi("multi", "text/plain", s3.Private, s3.Options{})
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "GET")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/sample/")
|
|
||||||
c.Assert(req.Form["uploads"], check.DeepEquals, []string{""})
|
|
||||||
c.Assert(req.Form["prefix"], check.DeepEquals, []string{"multi"})
|
|
||||||
|
|
||||||
req = testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "POST")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/sample/multi")
|
|
||||||
c.Assert(req.Form["uploads"], check.DeepEquals, []string{""})
|
|
||||||
|
|
||||||
c.Assert(multi.UploadId, check.Matches, "JNbR_[A-Za-z0-9.]+QQ--")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestMultiReturnOld(c *check.C) {
|
|
||||||
testServer.Response(200, nil, ListMultiResultDump)
|
|
||||||
|
|
||||||
b := s.s3.Bucket("sample")
|
|
||||||
|
|
||||||
multi, err := b.Multi("multi1", "text/plain", s3.Private, s3.Options{})
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(multi.Key, check.Equals, "multi1")
|
|
||||||
c.Assert(multi.UploadId, check.Equals, "iUVug89pPvSswrikD")
|
|
||||||
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "GET")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/sample/")
|
|
||||||
c.Assert(req.Form["uploads"], check.DeepEquals, []string{""})
|
|
||||||
c.Assert(req.Form["prefix"], check.DeepEquals, []string{"multi1"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestListParts(c *check.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, s3.Options{})
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
parts, err := multi.ListParts()
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(parts, check.HasLen, 3)
|
|
||||||
c.Assert(parts[0].N, check.Equals, 1)
|
|
||||||
c.Assert(parts[0].Size, check.Equals, int64(5))
|
|
||||||
c.Assert(parts[0].ETag, check.Equals, `"ffc88b4ca90a355f8ddba6b2c3b2af5c"`)
|
|
||||||
c.Assert(parts[1].N, check.Equals, 2)
|
|
||||||
c.Assert(parts[1].Size, check.Equals, int64(5))
|
|
||||||
c.Assert(parts[1].ETag, check.Equals, `"d067a0fa9dc61a6e7195ca99696b5a89"`)
|
|
||||||
c.Assert(parts[2].N, check.Equals, 3)
|
|
||||||
c.Assert(parts[2].Size, check.Equals, int64(5))
|
|
||||||
c.Assert(parts[2].ETag, check.Equals, `"49dcd91231f801159e893fb5c6674985"`)
|
|
||||||
testServer.WaitRequest()
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "GET")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/sample/multi")
|
|
||||||
c.Assert(req.Form.Get("uploadId"), check.Matches, "JNbR_[A-Za-z0-9.]+QQ--")
|
|
||||||
c.Assert(req.Form["max-parts"], check.DeepEquals, []string{"1000"})
|
|
||||||
|
|
||||||
testServer.WaitRequest() // The internal error.
|
|
||||||
req = testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "GET")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/sample/multi")
|
|
||||||
c.Assert(req.Form.Get("uploadId"), check.Matches, "JNbR_[A-Za-z0-9.]+QQ--")
|
|
||||||
c.Assert(req.Form["max-parts"], check.DeepEquals, []string{"1000"})
|
|
||||||
c.Assert(req.Form["part-number-marker"], check.DeepEquals, []string{"2"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestPutPart(c *check.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, s3.Options{})
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
part, err := multi.PutPart(1, strings.NewReader("<part 1>"))
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(part.N, check.Equals, 1)
|
|
||||||
c.Assert(part.Size, check.Equals, int64(8))
|
|
||||||
c.Assert(part.ETag, check.Equals, headers["ETag"])
|
|
||||||
|
|
||||||
testServer.WaitRequest()
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "PUT")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/sample/multi")
|
|
||||||
c.Assert(req.Form.Get("uploadId"), check.Matches, "JNbR_[A-Za-z0-9.]+QQ--")
|
|
||||||
c.Assert(req.Form["partNumber"], check.DeepEquals, []string{"1"})
|
|
||||||
c.Assert(req.Header["Content-Length"], check.DeepEquals, []string{"8"})
|
|
||||||
c.Assert(req.Header["Content-Md5"], check.DeepEquals, []string{"JvkO/RDWFPEAJS/1bYja2A=="})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestPutPartCopy(c *check.C) {
|
|
||||||
testServer.Response(200, nil, InitMultiResultDump)
|
|
||||||
// PutPartCopy makes a Head request internally to verify access to the source object
|
|
||||||
// and obtain its size
|
|
||||||
testServer.Response(200, nil, "content")
|
|
||||||
testServer.Response(200, nil, PutCopyResultDump)
|
|
||||||
|
|
||||||
b := s.s3.Bucket("sample")
|
|
||||||
|
|
||||||
multi, err := b.InitMulti("multi", "text/plain", s3.Private, s3.Options{})
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
res, part, err := multi.PutPartCopy(1, s3.CopyOptions{}, "source-bucket/\u00FCber-fil\u00E9.jpg")
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(part.N, check.Equals, 1)
|
|
||||||
c.Assert(part.Size, check.Equals, int64(7))
|
|
||||||
c.Assert(res, check.DeepEquals, &s3.CopyObjectResult{
|
|
||||||
ETag: `"9b2cf535f27731c974343645a3985328"`,
|
|
||||||
LastModified: `2009-10-28T22:32:00`})
|
|
||||||
|
|
||||||
// Verify the Head request
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "POST")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/sample/multi")
|
|
||||||
c.Assert(req.Header["Date"], check.Not(check.Equals), "")
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
testServer.WaitRequest()
|
|
||||||
req = testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "PUT")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/sample/multi")
|
|
||||||
c.Assert(req.Form.Get("uploadId"), check.Matches, "JNbR_[A-Za-z0-9.]+QQ--")
|
|
||||||
c.Assert(req.Form["partNumber"], check.DeepEquals, []string{"1"})
|
|
||||||
c.Assert(req.Header["X-Amz-Copy-Source"], check.DeepEquals, []string{`source-bucket%2F%C3%BCber-fil%C3%A9.jpg`})
|
|
||||||
}
|
|
||||||
|
|
||||||
func readAll(r io.Reader) string {
|
|
||||||
data, err := ioutil.ReadAll(r)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return string(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestPutAllNoPreviousUpload(c *check.C) {
|
|
||||||
// Don't retry the NoSuchUpload error.
|
|
||||||
s.DisableRetries()
|
|
||||||
|
|
||||||
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, s3.Options{})
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
parts, err := multi.PutAll(strings.NewReader("part1part2last"), 5)
|
|
||||||
c.Assert(parts, check.HasLen, 3)
|
|
||||||
c.Assert(parts[0].ETag, check.Equals, `"etag1"`)
|
|
||||||
c.Assert(parts[1].ETag, check.Equals, `"etag2"`)
|
|
||||||
c.Assert(parts[2].ETag, check.Equals, `"etag3"`)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
// Init
|
|
||||||
testServer.WaitRequest()
|
|
||||||
|
|
||||||
// List old parts. Won't find anything.
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "GET")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/sample/multi")
|
|
||||||
|
|
||||||
// Send part 1.
|
|
||||||
req = testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "PUT")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/sample/multi")
|
|
||||||
c.Assert(req.Form["partNumber"], check.DeepEquals, []string{"1"})
|
|
||||||
c.Assert(req.Header["Content-Length"], check.DeepEquals, []string{"5"})
|
|
||||||
c.Assert(readAll(req.Body), check.Equals, "part1")
|
|
||||||
|
|
||||||
// Send part 2.
|
|
||||||
req = testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "PUT")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/sample/multi")
|
|
||||||
c.Assert(req.Form["partNumber"], check.DeepEquals, []string{"2"})
|
|
||||||
c.Assert(req.Header["Content-Length"], check.DeepEquals, []string{"5"})
|
|
||||||
c.Assert(readAll(req.Body), check.Equals, "part2")
|
|
||||||
|
|
||||||
// Send part 3 with shorter body.
|
|
||||||
req = testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "PUT")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/sample/multi")
|
|
||||||
c.Assert(req.Form["partNumber"], check.DeepEquals, []string{"3"})
|
|
||||||
c.Assert(req.Header["Content-Length"], check.DeepEquals, []string{"4"})
|
|
||||||
c.Assert(readAll(req.Body), check.Equals, "last")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestPutAllZeroSizeFile(c *check.C) {
|
|
||||||
// Don't retry the NoSuchUpload error.
|
|
||||||
s.DisableRetries()
|
|
||||||
|
|
||||||
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, s3.Options{})
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
// Must send at least one part, so that completing it will work.
|
|
||||||
parts, err := multi.PutAll(strings.NewReader(""), 5)
|
|
||||||
c.Assert(parts, check.HasLen, 1)
|
|
||||||
c.Assert(parts[0].ETag, check.Equals, `"etag1"`)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
// Init
|
|
||||||
testServer.WaitRequest()
|
|
||||||
|
|
||||||
// List old parts. Won't find anything.
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "GET")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/sample/multi")
|
|
||||||
|
|
||||||
// Send empty part.
|
|
||||||
req = testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "PUT")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/sample/multi")
|
|
||||||
c.Assert(req.Form["partNumber"], check.DeepEquals, []string{"1"})
|
|
||||||
c.Assert(req.Header["Content-Length"], check.DeepEquals, []string{"0"})
|
|
||||||
c.Assert(readAll(req.Body), check.Equals, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestPutAllResume(c *check.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, s3.Options{})
|
|
||||||
c.Assert(err, check.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, check.HasLen, 3)
|
|
||||||
c.Assert(parts[0].N, check.Equals, 1)
|
|
||||||
c.Assert(parts[0].Size, check.Equals, int64(5))
|
|
||||||
c.Assert(parts[0].ETag, check.Equals, `"ffc88b4ca90a355f8ddba6b2c3b2af5c"`)
|
|
||||||
c.Assert(parts[1].N, check.Equals, 2)
|
|
||||||
c.Assert(parts[1].Size, check.Equals, int64(5))
|
|
||||||
c.Assert(parts[1].ETag, check.Equals, `"etag2"`)
|
|
||||||
c.Assert(parts[2].N, check.Equals, 3)
|
|
||||||
c.Assert(parts[2].Size, check.Equals, int64(5))
|
|
||||||
c.Assert(parts[2].ETag, check.Equals, `"49dcd91231f801159e893fb5c6674985"`)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
// Init
|
|
||||||
testServer.WaitRequest()
|
|
||||||
|
|
||||||
// List old parts, broken in two requests.
|
|
||||||
for i := 0; i < 2; i++ {
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "GET")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/sample/multi")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send part 2, as it didn't match the checksum.
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "PUT")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/sample/multi")
|
|
||||||
c.Assert(req.Form["partNumber"], check.DeepEquals, []string{"2"})
|
|
||||||
c.Assert(req.Header["Content-Length"], check.DeepEquals, []string{"5"})
|
|
||||||
c.Assert(readAll(req.Body), check.Equals, "partX")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestMultiComplete(c *check.C) {
|
|
||||||
testServer.Response(200, nil, InitMultiResultDump)
|
|
||||||
testServer.Response(200, nil, MultiCompleteDump)
|
|
||||||
|
|
||||||
b := s.s3.Bucket("sample")
|
|
||||||
|
|
||||||
multi, err := b.InitMulti("multi", "text/plain", s3.Private, s3.Options{})
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
err = multi.Complete([]s3.Part{{2, `"ETag2"`, 32}, {1, `"ETag1"`, 64}})
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
testServer.WaitRequest()
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "POST")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/sample/multi")
|
|
||||||
c.Assert(req.Form.Get("uploadId"), check.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, check.IsNil)
|
|
||||||
|
|
||||||
c.Assert(payload.XMLName.Local, check.Equals, "CompleteMultipartUpload")
|
|
||||||
c.Assert(len(payload.Part), check.Equals, 2)
|
|
||||||
c.Assert(payload.Part[0].PartNumber, check.Equals, 1)
|
|
||||||
c.Assert(payload.Part[0].ETag, check.Equals, `"ETag1"`)
|
|
||||||
c.Assert(payload.Part[1].PartNumber, check.Equals, 2)
|
|
||||||
c.Assert(payload.Part[1].ETag, check.Equals, `"ETag2"`)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestMultiCompleteError(c *check.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)
|
|
||||||
|
|
||||||
b := s.s3.Bucket("sample")
|
|
||||||
|
|
||||||
multi, err := b.InitMulti("multi", "text/plain", s3.Private, s3.Options{})
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
err = multi.Complete([]s3.Part{{2, `"ETag2"`, 32}, {1, `"ETag1"`, 64}})
|
|
||||||
c.Assert(err, check.NotNil)
|
|
||||||
|
|
||||||
testServer.WaitRequest()
|
|
||||||
testServer.WaitRequest()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestMultiAbort(c *check.C) {
|
|
||||||
testServer.Response(200, nil, InitMultiResultDump)
|
|
||||||
testServer.Response(200, nil, "")
|
|
||||||
|
|
||||||
b := s.s3.Bucket("sample")
|
|
||||||
|
|
||||||
multi, err := b.InitMulti("multi", "text/plain", s3.Private, s3.Options{})
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
err = multi.Abort()
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
testServer.WaitRequest()
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "DELETE")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/sample/multi")
|
|
||||||
c.Assert(req.Form.Get("uploadId"), check.Matches, "JNbR_[A-Za-z0-9.]+QQ--")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestListMulti(c *check.C) {
|
|
||||||
testServer.Response(200, nil, ListMultiResultDump)
|
|
||||||
|
|
||||||
b := s.s3.Bucket("sample")
|
|
||||||
|
|
||||||
multis, prefixes, err := b.ListMulti("", "/")
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(prefixes, check.DeepEquals, []string{"a/", "b/"})
|
|
||||||
c.Assert(multis, check.HasLen, 2)
|
|
||||||
c.Assert(multis[0].Key, check.Equals, "multi1")
|
|
||||||
c.Assert(multis[0].UploadId, check.Equals, "iUVug89pPvSswrikD")
|
|
||||||
c.Assert(multis[1].Key, check.Equals, "multi2")
|
|
||||||
c.Assert(multis[1].UploadId, check.Equals, "DkirwsSvPp98guVUi")
|
|
||||||
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "GET")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/sample/")
|
|
||||||
c.Assert(req.Form["uploads"], check.DeepEquals, []string{""})
|
|
||||||
c.Assert(req.Form["prefix"], check.DeepEquals, []string{""})
|
|
||||||
c.Assert(req.Form["delimiter"], check.DeepEquals, []string{"/"})
|
|
||||||
c.Assert(req.Form["max-uploads"], check.DeepEquals, []string{"1000"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestMultiCompleteSupportRadosGW(c *check.C) {
|
|
||||||
testServer.Response(200, nil, InitMultiResultDump)
|
|
||||||
testServer.Response(200, nil, MultiCompleteDump)
|
|
||||||
s.s3.Region.Name = "generic"
|
|
||||||
b := s.s3.Bucket("sample")
|
|
||||||
|
|
||||||
multi, err := b.InitMulti("multi", "text/plain", s3.Private, s3.Options{})
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
err = multi.Complete([]s3.Part{{2, `"ETag2"`, 32}, {1, `"ETag1"`, 64}})
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
testServer.WaitRequest()
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "POST")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/sample/multi")
|
|
||||||
c.Assert(req.Form.Get("uploadId"), check.Matches, "JNbR_[A-Za-z0-9.]+QQ--")
|
|
||||||
c.Assert(req.Header["Content-Length"], check.NotNil)
|
|
||||||
|
|
||||||
var payload struct {
|
|
||||||
XMLName xml.Name
|
|
||||||
Part []struct {
|
|
||||||
PartNumber int
|
|
||||||
ETag string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dec := xml.NewDecoder(req.Body)
|
|
||||||
err = dec.Decode(&payload)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
c.Assert(payload.XMLName.Local, check.Equals, "CompleteMultipartUpload")
|
|
||||||
c.Assert(len(payload.Part), check.Equals, 2)
|
|
||||||
c.Assert(payload.Part[0].PartNumber, check.Equals, 1)
|
|
||||||
c.Assert(payload.Part[0].ETag, check.Equals, `"ETag1"`)
|
|
||||||
c.Assert(payload.Part[1].PartNumber, check.Equals, 2)
|
|
||||||
c.Assert(payload.Part[1].ETag, check.Equals, `"ETag2"`)
|
|
||||||
}
|
|
248
Godeps/_workspace/src/github.com/AdRoll/goamz/s3/responses_test.go
generated
vendored
248
Godeps/_workspace/src/github.com/AdRoll/goamz/s3/responses_test.go
generated
vendored
|
@ -1,248 +0,0 @@
|
||||||
package s3_test
|
|
||||||
|
|
||||||
var PutCopyResultDump = `
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<CopyObjectResult>
|
|
||||||
<LastModified>2009-10-28T22:32:00</LastModified>
|
|
||||||
<ETag>"9b2cf535f27731c974343645a3985328"</ETag>
|
|
||||||
</CopyObjectResult>
|
|
||||||
`
|
|
||||||
|
|
||||||
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 MultiCompleteDump = `
|
|
||||||
<CompleteMultipartUploadResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
|
||||||
<Location>http://Example-Bucket.s3.amazonaws.com/Example-Object</Location>
|
|
||||||
<Bucket>Example-Bucket</Bucket>
|
|
||||||
<Key>Example-Object</Key>
|
|
||||||
<ETag>"3858f62230ac3c915f300c664312c11f-9"</ETag>
|
|
||||||
</CompleteMultipartUploadResult>
|
|
||||||
`
|
|
||||||
|
|
||||||
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>
|
|
||||||
`
|
|
||||||
|
|
||||||
var GetServiceDump = `
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01">
|
|
||||||
<Owner>
|
|
||||||
<ID>bcaf1ffd86f461ca5fb16fd081034f</ID>
|
|
||||||
<DisplayName>webfile</DisplayName>
|
|
||||||
</Owner>
|
|
||||||
<Buckets>
|
|
||||||
<Bucket>
|
|
||||||
<Name>quotes</Name>
|
|
||||||
<CreationDate>2006-02-03T16:45:09.000Z</CreationDate>
|
|
||||||
</Bucket>
|
|
||||||
<Bucket>
|
|
||||||
<Name>samples</Name>
|
|
||||||
<CreationDate>2006-02-03T16:41:58.000Z</CreationDate>
|
|
||||||
</Bucket>
|
|
||||||
</Buckets>
|
|
||||||
</ListAllMyBucketsResult>
|
|
||||||
`
|
|
||||||
|
|
||||||
var GetLocationUsStandard = `
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<LocationConstraint xmlns="http://s3.amazonaws.com/doc/2006-03-01/"/>
|
|
||||||
`
|
|
||||||
|
|
||||||
var GetLocationUsWest1 = `
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<LocationConstraint xmlns="http://s3.amazonaws.com/doc/2006-03-01/">us-west-1</LocationConstraint>
|
|
||||||
`
|
|
||||||
|
|
||||||
var BucketWebsiteConfigurationDump = `<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<WebsiteConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><RedirectAllRequestsTo><HostName>example.com</HostName></RedirectAllRequestsTo></WebsiteConfiguration>`
|
|
513
Godeps/_workspace/src/github.com/AdRoll/goamz/s3/s3_test.go
generated
vendored
513
Godeps/_workspace/src/github.com/AdRoll/goamz/s3/s3_test.go
generated
vendored
|
@ -1,513 +0,0 @@
|
||||||
package s3_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/AdRoll/goamz/aws"
|
|
||||||
"github.com/AdRoll/goamz/s3"
|
|
||||||
"github.com/AdRoll/goamz/testutil"
|
|
||||||
"gopkg.in/check.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test(t *testing.T) {
|
|
||||||
check.TestingT(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
type S struct {
|
|
||||||
s3 *s3.S3
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = check.Suite(&S{})
|
|
||||||
|
|
||||||
var testServer = testutil.NewHTTPServer()
|
|
||||||
|
|
||||||
func (s *S) SetUpSuite(c *check.C) {
|
|
||||||
testServer.Start()
|
|
||||||
auth := aws.Auth{AccessKey: "abc", SecretKey: "123"}
|
|
||||||
s.s3 = s3.New(auth, aws.Region{Name: "faux-region-1", S3Endpoint: testServer.URL})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TearDownSuite(c *check.C) {
|
|
||||||
s3.SetAttemptStrategy(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) SetUpTest(c *check.C) {
|
|
||||||
attempts := aws.AttemptStrategy{
|
|
||||||
Total: 300 * time.Millisecond,
|
|
||||||
Delay: 100 * time.Millisecond,
|
|
||||||
}
|
|
||||||
s3.SetAttemptStrategy(&attempts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TearDownTest(c *check.C) {
|
|
||||||
testServer.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) DisableRetries() {
|
|
||||||
s3.SetAttemptStrategy(&aws.AttemptStrategy{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// PutBucket docs: http://goo.gl/kBTCu
|
|
||||||
|
|
||||||
func (s *S) TestPutBucket(c *check.C) {
|
|
||||||
testServer.Response(200, nil, "")
|
|
||||||
|
|
||||||
b := s.s3.Bucket("bucket")
|
|
||||||
err := b.PutBucket(s3.Private)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "PUT")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/bucket/")
|
|
||||||
c.Assert(req.Header["Date"], check.Not(check.Equals), "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// PutBucketWebsite docs: http://goo.gl/TpRlUy
|
|
||||||
|
|
||||||
func (s *S) TestPutBucketWebsite(c *check.C) {
|
|
||||||
testServer.Response(200, nil, "")
|
|
||||||
|
|
||||||
b := s.s3.Bucket("bucket")
|
|
||||||
config := s3.WebsiteConfiguration{
|
|
||||||
RedirectAllRequestsTo: &s3.RedirectAllRequestsTo{HostName: "example.com"},
|
|
||||||
}
|
|
||||||
err := b.PutBucketWebsite(config)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
body, err := ioutil.ReadAll(req.Body)
|
|
||||||
req.Body.Close()
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(string(body), check.Equals, BucketWebsiteConfigurationDump)
|
|
||||||
c.Assert(req.Method, check.Equals, "PUT")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/bucket/")
|
|
||||||
c.Assert(req.URL.RawQuery, check.Equals, "website=")
|
|
||||||
c.Assert(req.Header["Date"], check.Not(check.Equals), "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Head docs: http://bit.ly/17K1ylI
|
|
||||||
|
|
||||||
func (s *S) TestHead(c *check.C) {
|
|
||||||
testServer.Response(200, nil, "content")
|
|
||||||
|
|
||||||
b := s.s3.Bucket("bucket")
|
|
||||||
resp, err := b.Head("name", nil)
|
|
||||||
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "HEAD")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/bucket/name")
|
|
||||||
c.Assert(req.Header["Date"], check.Not(check.Equals), "")
|
|
||||||
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(resp.ContentLength, check.FitsTypeOf, int64(0))
|
|
||||||
c.Assert(resp, check.FitsTypeOf, &http.Response{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteBucket docs: http://goo.gl/GoBrY
|
|
||||||
|
|
||||||
func (s *S) TestDelBucket(c *check.C) {
|
|
||||||
testServer.Response(204, nil, "")
|
|
||||||
|
|
||||||
b := s.s3.Bucket("bucket")
|
|
||||||
err := b.DelBucket()
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "DELETE")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/bucket/")
|
|
||||||
c.Assert(req.Header["Date"], check.Not(check.Equals), "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetObject docs: http://goo.gl/isCO7
|
|
||||||
|
|
||||||
func (s *S) TestGet(c *check.C) {
|
|
||||||
testServer.Response(200, nil, "content")
|
|
||||||
|
|
||||||
b := s.s3.Bucket("bucket")
|
|
||||||
data, err := b.Get("name")
|
|
||||||
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "GET")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/bucket/name")
|
|
||||||
c.Assert(req.Header["Date"], check.Not(check.Equals), "")
|
|
||||||
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(string(data), check.Equals, "content")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestGetWithPlus(c *check.C) {
|
|
||||||
testServer.Response(200, nil, "content")
|
|
||||||
|
|
||||||
b := s.s3.Bucket("bucket")
|
|
||||||
_, err := b.Get("has+plus")
|
|
||||||
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(req.RequestURI, check.Equals, "http://localhost:4444/bucket/has%2Bplus")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestURL(c *check.C) {
|
|
||||||
testServer.Response(200, nil, "content")
|
|
||||||
|
|
||||||
b := s.s3.Bucket("bucket")
|
|
||||||
url := b.URL("name")
|
|
||||||
r, err := http.Get(url)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
data, err := ioutil.ReadAll(r.Body)
|
|
||||||
r.Body.Close()
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(string(data), check.Equals, "content")
|
|
||||||
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "GET")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/bucket/name")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestGetReader(c *check.C) {
|
|
||||||
testServer.Response(200, nil, "content")
|
|
||||||
|
|
||||||
b := s.s3.Bucket("bucket")
|
|
||||||
rc, err := b.GetReader("name")
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
data, err := ioutil.ReadAll(rc)
|
|
||||||
rc.Close()
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(string(data), check.Equals, "content")
|
|
||||||
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "GET")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/bucket/name")
|
|
||||||
c.Assert(req.Header["Date"], check.Not(check.Equals), "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestGetNotFound(c *check.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, check.Equals, "GET")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/non-existent-bucket/non-existent")
|
|
||||||
c.Assert(req.Header["Date"], check.Not(check.Equals), "")
|
|
||||||
|
|
||||||
s3err, _ := err.(*s3.Error)
|
|
||||||
c.Assert(s3err, check.NotNil)
|
|
||||||
c.Assert(s3err.StatusCode, check.Equals, 404)
|
|
||||||
c.Assert(s3err.BucketName, check.Equals, "non-existent-bucket")
|
|
||||||
c.Assert(s3err.RequestId, check.Equals, "3F1B667FAD71C3D8")
|
|
||||||
c.Assert(s3err.HostId, check.Equals, "L4ee/zrm1irFXY5F45fKXIRdOf9ktsKY/8TDVawuMK2jWRb1RF84i1uBzkdNqS5D")
|
|
||||||
c.Assert(s3err.Code, check.Equals, "NoSuchBucket")
|
|
||||||
c.Assert(s3err.Message, check.Equals, "The specified bucket does not exist")
|
|
||||||
c.Assert(s3err.Error(), check.Equals, "The specified bucket does not exist")
|
|
||||||
c.Assert(data, check.IsNil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PutObject docs: http://goo.gl/FEBPD
|
|
||||||
|
|
||||||
func (s *S) TestPutObject(c *check.C) {
|
|
||||||
testServer.Response(200, nil, "")
|
|
||||||
const DISPOSITION = "attachment; filename=\"0x1a2b3c.jpg\""
|
|
||||||
|
|
||||||
b := s.s3.Bucket("bucket")
|
|
||||||
err := b.Put("name", []byte("content"), "content-type", s3.Private, s3.Options{ContentDisposition: DISPOSITION})
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "PUT")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/bucket/name")
|
|
||||||
c.Assert(req.Header["Date"], check.Not(check.DeepEquals), []string{""})
|
|
||||||
c.Assert(req.Header["Content-Type"], check.DeepEquals, []string{"content-type"})
|
|
||||||
c.Assert(req.Header["Content-Length"], check.DeepEquals, []string{"7"})
|
|
||||||
c.Assert(req.Header["Content-Disposition"], check.DeepEquals, []string{DISPOSITION})
|
|
||||||
//c.Assert(req.Header["Content-MD5"], gocheck.DeepEquals, "...")
|
|
||||||
c.Assert(req.Header["X-Amz-Acl"], check.DeepEquals, []string{"private"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestPutObjectReducedRedundancy(c *check.C) {
|
|
||||||
testServer.Response(200, nil, "")
|
|
||||||
|
|
||||||
b := s.s3.Bucket("bucket")
|
|
||||||
err := b.Put("name", []byte("content"), "content-type", s3.Private, s3.Options{StorageClass: s3.ReducedRedundancy})
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "PUT")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/bucket/name")
|
|
||||||
c.Assert(req.Header["Date"], check.Not(check.DeepEquals), []string{""})
|
|
||||||
c.Assert(req.Header["Content-Type"], check.DeepEquals, []string{"content-type"})
|
|
||||||
c.Assert(req.Header["Content-Length"], check.DeepEquals, []string{"7"})
|
|
||||||
c.Assert(req.Header["X-Amz-Storage-Class"], check.DeepEquals, []string{"REDUCED_REDUNDANCY"})
|
|
||||||
}
|
|
||||||
|
|
||||||
// PutCopy docs: http://goo.gl/mhEHtA
|
|
||||||
func (s *S) TestPutCopy(c *check.C) {
|
|
||||||
testServer.Response(200, nil, PutCopyResultDump)
|
|
||||||
|
|
||||||
b := s.s3.Bucket("bucket")
|
|
||||||
res, err := b.PutCopy("name", s3.Private, s3.CopyOptions{},
|
|
||||||
// 0xFC is ü - 0xE9 is é
|
|
||||||
"source-bucket/\u00FCber-fil\u00E9.jpg")
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(res, check.DeepEquals, &s3.CopyObjectResult{
|
|
||||||
ETag: `"9b2cf535f27731c974343645a3985328"`,
|
|
||||||
LastModified: `2009-10-28T22:32:00`})
|
|
||||||
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "PUT")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/bucket/name")
|
|
||||||
c.Assert(req.Header["Date"], check.Not(check.DeepEquals), []string{""})
|
|
||||||
c.Assert(req.Header["Content-Length"], check.DeepEquals, []string{"0"})
|
|
||||||
c.Assert(req.Header["X-Amz-Copy-Source"], check.DeepEquals, []string{`source-bucket%2F%C3%BCber-fil%C3%A9.jpg`})
|
|
||||||
c.Assert(req.Header["X-Amz-Acl"], check.DeepEquals, []string{"private"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestPutObjectReadTimeout(c *check.C) {
|
|
||||||
s.s3.ReadTimeout = 50 * time.Millisecond
|
|
||||||
defer func() {
|
|
||||||
s.s3.ReadTimeout = 0
|
|
||||||
}()
|
|
||||||
|
|
||||||
b := s.s3.Bucket("bucket")
|
|
||||||
err := b.Put("name", []byte("content"), "content-type", s3.Private, s3.Options{})
|
|
||||||
|
|
||||||
// Make sure that we get a timeout error.
|
|
||||||
c.Assert(err, check.NotNil)
|
|
||||||
|
|
||||||
// Set the response after the request times out so that the next request will work.
|
|
||||||
testServer.Response(200, nil, "")
|
|
||||||
|
|
||||||
// This time set the response within our timeout period so that we expect the call
|
|
||||||
// to return successfully.
|
|
||||||
go func() {
|
|
||||||
time.Sleep(25 * time.Millisecond)
|
|
||||||
testServer.Response(200, nil, "")
|
|
||||||
}()
|
|
||||||
err = b.Put("name", []byte("content"), "content-type", s3.Private, s3.Options{})
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestPutReader(c *check.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, s3.Options{})
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "PUT")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/bucket/name")
|
|
||||||
c.Assert(req.Header["Date"], check.Not(check.DeepEquals), []string{""})
|
|
||||||
c.Assert(req.Header["Content-Type"], check.DeepEquals, []string{"content-type"})
|
|
||||||
c.Assert(req.Header["Content-Length"], check.DeepEquals, []string{"7"})
|
|
||||||
//c.Assert(req.Header["Content-MD5"], gocheck.Equals, "...")
|
|
||||||
c.Assert(req.Header["X-Amz-Acl"], check.DeepEquals, []string{"private"})
|
|
||||||
}
|
|
||||||
|
|
||||||
// DelObject docs: http://goo.gl/APeTt
|
|
||||||
|
|
||||||
func (s *S) TestDelObject(c *check.C) {
|
|
||||||
testServer.Response(200, nil, "")
|
|
||||||
|
|
||||||
b := s.s3.Bucket("bucket")
|
|
||||||
err := b.Del("name")
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "DELETE")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/bucket/name")
|
|
||||||
c.Assert(req.Header["Date"], check.Not(check.Equals), "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestDelMultiObjects(c *check.C) {
|
|
||||||
testServer.Response(200, nil, "")
|
|
||||||
|
|
||||||
b := s.s3.Bucket("bucket")
|
|
||||||
objects := []s3.Object{s3.Object{Key: "test"}}
|
|
||||||
err := b.DelMulti(s3.Delete{
|
|
||||||
Quiet: false,
|
|
||||||
Objects: objects,
|
|
||||||
})
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "POST")
|
|
||||||
c.Assert(req.URL.RawQuery, check.Equals, "delete=")
|
|
||||||
c.Assert(req.Header["Date"], check.Not(check.Equals), "")
|
|
||||||
c.Assert(req.Header["Content-MD5"], check.Not(check.Equals), "")
|
|
||||||
c.Assert(req.Header["Content-Type"], check.Not(check.Equals), "")
|
|
||||||
c.Assert(req.ContentLength, check.Not(check.Equals), "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bucket List Objects docs: http://goo.gl/YjQTc
|
|
||||||
|
|
||||||
func (s *S) TestList(c *check.C) {
|
|
||||||
testServer.Response(200, nil, GetListResultDump1)
|
|
||||||
|
|
||||||
b := s.s3.Bucket("quotes")
|
|
||||||
|
|
||||||
data, err := b.List("N", "", "", 0)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "GET")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/quotes/")
|
|
||||||
c.Assert(req.Header["Date"], check.Not(check.Equals), "")
|
|
||||||
c.Assert(req.Form["prefix"], check.DeepEquals, []string{"N"})
|
|
||||||
c.Assert(req.Form["delimiter"], check.DeepEquals, []string{""})
|
|
||||||
c.Assert(req.Form["marker"], check.DeepEquals, []string{""})
|
|
||||||
c.Assert(req.Form["max-keys"], check.DeepEquals, []string(nil))
|
|
||||||
|
|
||||||
c.Assert(data.Name, check.Equals, "quotes")
|
|
||||||
c.Assert(data.Prefix, check.Equals, "N")
|
|
||||||
c.Assert(data.IsTruncated, check.Equals, false)
|
|
||||||
c.Assert(len(data.Contents), check.Equals, 2)
|
|
||||||
|
|
||||||
c.Assert(data.Contents[0].Key, check.Equals, "Nelson")
|
|
||||||
c.Assert(data.Contents[0].LastModified, check.Equals, "2006-01-01T12:00:00.000Z")
|
|
||||||
c.Assert(data.Contents[0].ETag, check.Equals, `"828ef3fdfa96f00ad9f27c383fc9ac7f"`)
|
|
||||||
c.Assert(data.Contents[0].Size, check.Equals, int64(5))
|
|
||||||
c.Assert(data.Contents[0].StorageClass, check.Equals, "STANDARD")
|
|
||||||
c.Assert(data.Contents[0].Owner.ID, check.Equals, "bcaf161ca5fb16fd081034f")
|
|
||||||
c.Assert(data.Contents[0].Owner.DisplayName, check.Equals, "webfile")
|
|
||||||
|
|
||||||
c.Assert(data.Contents[1].Key, check.Equals, "Neo")
|
|
||||||
c.Assert(data.Contents[1].LastModified, check.Equals, "2006-01-01T12:00:00.000Z")
|
|
||||||
c.Assert(data.Contents[1].ETag, check.Equals, `"828ef3fdfa96f00ad9f27c383fc9ac7f"`)
|
|
||||||
c.Assert(data.Contents[1].Size, check.Equals, int64(4))
|
|
||||||
c.Assert(data.Contents[1].StorageClass, check.Equals, "STANDARD")
|
|
||||||
c.Assert(data.Contents[1].Owner.ID, check.Equals, "bcaf1ffd86a5fb16fd081034f")
|
|
||||||
c.Assert(data.Contents[1].Owner.DisplayName, check.Equals, "webfile")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestListWithDelimiter(c *check.C) {
|
|
||||||
testServer.Response(200, nil, GetListResultDump2)
|
|
||||||
|
|
||||||
b := s.s3.Bucket("quotes")
|
|
||||||
|
|
||||||
data, err := b.List("photos/2006/", "/", "some-marker", 1000)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(req.Method, check.Equals, "GET")
|
|
||||||
c.Assert(req.URL.Path, check.Equals, "/quotes/")
|
|
||||||
c.Assert(req.Header["Date"], check.Not(check.Equals), "")
|
|
||||||
c.Assert(req.Form["prefix"], check.DeepEquals, []string{"photos/2006/"})
|
|
||||||
c.Assert(req.Form["delimiter"], check.DeepEquals, []string{"/"})
|
|
||||||
c.Assert(req.Form["marker"], check.DeepEquals, []string{"some-marker"})
|
|
||||||
c.Assert(req.Form["max-keys"], check.DeepEquals, []string{"1000"})
|
|
||||||
|
|
||||||
c.Assert(data.Name, check.Equals, "example-bucket")
|
|
||||||
c.Assert(data.Prefix, check.Equals, "photos/2006/")
|
|
||||||
c.Assert(data.Delimiter, check.Equals, "/")
|
|
||||||
c.Assert(data.Marker, check.Equals, "some-marker")
|
|
||||||
c.Assert(data.IsTruncated, check.Equals, false)
|
|
||||||
c.Assert(len(data.Contents), check.Equals, 0)
|
|
||||||
c.Assert(data.CommonPrefixes, check.DeepEquals, []string{"photos/2006/feb/", "photos/2006/jan/"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestExists(c *check.C) {
|
|
||||||
testServer.Response(200, nil, "")
|
|
||||||
|
|
||||||
b := s.s3.Bucket("bucket")
|
|
||||||
result, err := b.Exists("name")
|
|
||||||
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
|
|
||||||
c.Assert(req.Method, check.Equals, "HEAD")
|
|
||||||
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(result, check.Equals, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestExistsNotFound404(c *check.C) {
|
|
||||||
testServer.Response(404, nil, "")
|
|
||||||
|
|
||||||
b := s.s3.Bucket("bucket")
|
|
||||||
result, err := b.Exists("name")
|
|
||||||
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
|
|
||||||
c.Assert(req.Method, check.Equals, "HEAD")
|
|
||||||
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(result, check.Equals, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestExistsNotFound403(c *check.C) {
|
|
||||||
testServer.Response(403, nil, "")
|
|
||||||
|
|
||||||
b := s.s3.Bucket("bucket")
|
|
||||||
result, err := b.Exists("name")
|
|
||||||
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
|
|
||||||
c.Assert(req.Method, check.Equals, "HEAD")
|
|
||||||
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(result, check.Equals, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestGetService(c *check.C) {
|
|
||||||
testServer.Response(200, nil, GetServiceDump)
|
|
||||||
|
|
||||||
expected := s3.GetServiceResp{
|
|
||||||
Owner: s3.Owner{
|
|
||||||
ID: "bcaf1ffd86f461ca5fb16fd081034f",
|
|
||||||
DisplayName: "webfile",
|
|
||||||
},
|
|
||||||
Buckets: []s3.BucketInfo{
|
|
||||||
s3.BucketInfo{
|
|
||||||
Name: "quotes",
|
|
||||||
CreationDate: "2006-02-03T16:45:09.000Z",
|
|
||||||
},
|
|
||||||
s3.BucketInfo{
|
|
||||||
Name: "samples",
|
|
||||||
CreationDate: "2006-02-03T16:41:58.000Z",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
received, err := s.s3.GetService()
|
|
||||||
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(*received, check.DeepEquals, expected)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestLocation(c *check.C) {
|
|
||||||
testServer.Response(200, nil, GetLocationUsStandard)
|
|
||||||
expectedUsStandard := "us-east-1"
|
|
||||||
|
|
||||||
bucketUsStandard := s.s3.Bucket("us-east-1")
|
|
||||||
resultUsStandard, err := bucketUsStandard.Location()
|
|
||||||
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(resultUsStandard, check.Equals, expectedUsStandard)
|
|
||||||
|
|
||||||
testServer.Response(200, nil, GetLocationUsWest1)
|
|
||||||
expectedUsWest1 := "us-west-1"
|
|
||||||
|
|
||||||
bucketUsWest1 := s.s3.Bucket("us-west-1")
|
|
||||||
resultUsWest1, err := bucketUsWest1.Location()
|
|
||||||
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(resultUsWest1, check.Equals, expectedUsWest1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestSupportRadosGW(c *check.C) {
|
|
||||||
testServer.Response(200, nil, "content")
|
|
||||||
s.s3.Region.Name = "generic"
|
|
||||||
b := s.s3.Bucket("bucket")
|
|
||||||
_, err := b.Get("rgw")
|
|
||||||
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(req.RequestURI, check.Equals, "/bucket/rgw")
|
|
||||||
}
|
|
603
Godeps/_workspace/src/github.com/AdRoll/goamz/s3/s3i_test.go
generated
vendored
603
Godeps/_workspace/src/github.com/AdRoll/goamz/s3/s3i_test.go
generated
vendored
|
@ -1,603 +0,0 @@
|
||||||
package s3_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/md5"
|
|
||||||
"fmt"
|
|
||||||
"github.com/AdRoll/goamz/aws"
|
|
||||||
"github.com/AdRoll/goamz/s3"
|
|
||||||
"github.com/AdRoll/goamz/testutil"
|
|
||||||
"gopkg.in/check.v1"
|
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AmazonServer represents an Amazon S3 server.
|
|
||||||
type AmazonServer struct {
|
|
||||||
auth aws.Auth
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *AmazonServer) SetUp(c *check.C) {
|
|
||||||
auth, err := aws.EnvAuth()
|
|
||||||
if err != nil {
|
|
||||||
c.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
s.auth = auth
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = check.Suite(&AmazonClientSuite{Region: aws.USEast})
|
|
||||||
var _ = check.Suite(&AmazonClientSuite{Region: aws.EUWest})
|
|
||||||
var _ = check.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 *check.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 *check.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 *check.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 *check.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 *check.C) {
|
|
||||||
b := testBucket(s.s3)
|
|
||||||
err := b.PutBucket(s3.PublicRead)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
err = b.Put("name", []byte("yo!"), "text/plain", s3.PublicRead, s3.Options{})
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
defer b.Del("name")
|
|
||||||
|
|
||||||
data, err := b.Get("name")
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(string(data), check.Equals, "yo!")
|
|
||||||
|
|
||||||
data, err = get(b.URL("name"))
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(string(data), check.Equals, "yo!")
|
|
||||||
|
|
||||||
buf := bytes.NewBufferString("hey!")
|
|
||||||
err = b.PutReader("name2", buf, int64(buf.Len()), "text/plain", s3.Private, s3.Options{})
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
defer b.Del("name2")
|
|
||||||
|
|
||||||
rc, err := b.GetReader("name2")
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
data, err = ioutil.ReadAll(rc)
|
|
||||||
c.Check(err, check.IsNil)
|
|
||||||
c.Check(string(data), check.Equals, "hey!")
|
|
||||||
rc.Close()
|
|
||||||
|
|
||||||
data, err = get(b.SignedURL("name2", time.Now().Add(time.Hour)))
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(string(data), check.Equals, "hey!")
|
|
||||||
|
|
||||||
if !s.authIsBroken {
|
|
||||||
data, err = get(b.SignedURL("name2", time.Now().Add(-time.Hour)))
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(string(data), check.Matches, "(?s).*AccessDenied.*")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = b.DelBucket()
|
|
||||||
c.Assert(err, check.NotNil)
|
|
||||||
|
|
||||||
s3err, ok := err.(*s3.Error)
|
|
||||||
c.Assert(ok, check.Equals, true)
|
|
||||||
c.Assert(s3err.Code, check.Equals, "BucketNotEmpty")
|
|
||||||
c.Assert(s3err.BucketName, check.Equals, b.Name)
|
|
||||||
c.Assert(s3err.Message, check.Equals, "The bucket you tried to delete is not empty")
|
|
||||||
|
|
||||||
err = b.Del("name")
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
err = b.Del("name2")
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
err = b.DelBucket()
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ClientTests) TestGetNotFound(c *check.C) {
|
|
||||||
b := s.s3.Bucket("goamz-" + s.s3.Auth.AccessKey)
|
|
||||||
data, err := b.Get("non-existent")
|
|
||||||
|
|
||||||
s3err, _ := err.(*s3.Error)
|
|
||||||
c.Assert(s3err, check.NotNil)
|
|
||||||
c.Assert(s3err.StatusCode, check.Equals, 404)
|
|
||||||
c.Assert(s3err.Code, check.Equals, "NoSuchBucket")
|
|
||||||
c.Assert(s3err.Message, check.Equals, "The specified bucket does not exist")
|
|
||||||
c.Assert(data, check.IsNil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Communicate with all endpoints to see if they are alive.
|
|
||||||
func (s *ClientTests) TestRegions(c *check.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, check.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 *check.C) {
|
|
||||||
b := testBucket(s.s3)
|
|
||||||
err := b.PutBucket(s3.PublicRead)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
err = b.PutBucket(s3.PublicRead)
|
|
||||||
if err != nil {
|
|
||||||
c.Assert(err, check.FitsTypeOf, new(s3.Error))
|
|
||||||
c.Assert(err.(*s3.Error).Code, check.Equals, "BucketAlreadyOwnedByYou")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ClientTests) TestBucketList(c *check.C) {
|
|
||||||
b := testBucket(s.s3)
|
|
||||||
err := b.PutBucket(s3.Private)
|
|
||||||
c.Assert(err, check.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, s3.Options{})
|
|
||||||
c.Assert(err, check.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, check.IsNil)
|
|
||||||
c.Check(resp.Name, check.Equals, b.Name)
|
|
||||||
c.Check(resp.Delimiter, check.Equals, t.Delimiter)
|
|
||||||
c.Check(resp.IsTruncated, check.Equals, t.IsTruncated)
|
|
||||||
c.Check(resp.CommonPrefixes, check.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 *check.C, contents []s3.Key, data map[string][]byte, expected []s3.Key) {
|
|
||||||
c.Assert(contents, check.HasLen, len(expected))
|
|
||||||
for i, k := range contents {
|
|
||||||
c.Check(k.Key, check.Equals, expected[i].Key)
|
|
||||||
// TODO mtime
|
|
||||||
c.Check(k.Size, check.Equals, int64(len(data[k.Key])))
|
|
||||||
c.Check(k.ETag, check.Equals, etag(data[k.Key]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ClientTests) TestMultiInitPutList(c *check.C) {
|
|
||||||
b := testBucket(s.s3)
|
|
||||||
err := b.PutBucket(s3.Private)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
multi, err := b.InitMulti("multi", "text/plain", s3.Private, s3.Options{})
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(multi.UploadId, check.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, check.IsNil)
|
|
||||||
c.Assert(p.N, check.Equals, i+1)
|
|
||||||
c.Assert(p.Size, check.Equals, int64(8))
|
|
||||||
c.Assert(p.ETag, check.Matches, ".+")
|
|
||||||
sent = append(sent, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
s3.SetListPartsMax(2)
|
|
||||||
|
|
||||||
parts, err := multi.ListParts()
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(parts, check.HasLen, len(sent))
|
|
||||||
for i := range parts {
|
|
||||||
c.Assert(parts[i].N, check.Equals, sent[i].N)
|
|
||||||
c.Assert(parts[i].Size, check.Equals, sent[i].Size)
|
|
||||||
c.Assert(parts[i].ETag, check.Equals, sent[i].ETag)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = multi.Complete(parts)
|
|
||||||
s3err, failed := err.(*s3.Error)
|
|
||||||
c.Assert(failed, check.Equals, true)
|
|
||||||
c.Assert(s3err.Code, check.Equals, "EntityTooSmall")
|
|
||||||
|
|
||||||
err = multi.Abort()
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
_, err = multi.ListParts()
|
|
||||||
s3err, ok := err.(*s3.Error)
|
|
||||||
c.Assert(ok, check.Equals, true)
|
|
||||||
c.Assert(s3err.Code, check.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 *check.C) {
|
|
||||||
b := testBucket(s.s3)
|
|
||||||
err := b.PutBucket(s3.Private)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
contentType := "text/plain"
|
|
||||||
meta := make(map[string][]string)
|
|
||||||
meta["X-Amz-Meta-TestField"] = []string{"testValue"}
|
|
||||||
options := s3.Options{ContentEncoding: "identity", ContentDisposition: "inline", Meta: meta}
|
|
||||||
multi, err := b.InitMulti("multi", contentType, s3.Private, options)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(multi.UploadId, check.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, check.IsNil)
|
|
||||||
part2, err := multi.PutPart(2, bytes.NewReader(data2))
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
// Purposefully reversed. The order requirement must be handled.
|
|
||||||
err = multi.Complete([]s3.Part{part2, part1})
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
data, err := b.Get("multi")
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
c.Assert(len(data), check.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):]), check.Equals, string(data2))
|
|
||||||
|
|
||||||
resp, err := b.GetResponse("multi")
|
|
||||||
c.Assert(resp.Header.Get("Content-Type"), check.Equals, contentType)
|
|
||||||
c.Assert(resp.Header.Get("x-amz-acl"), check.Equals, s3.Private)
|
|
||||||
c.Assert(resp.Header.Get("Content-MD5"), check.Equals, options.ContentMD5)
|
|
||||||
c.Assert(resp.Header.Get("Content-Encoding"), check.Equals, options.ContentEncoding)
|
|
||||||
c.Assert(resp.Header.Get("Content-Disposition"), check.Equals, options.ContentDisposition)
|
|
||||||
for k, values := range meta {
|
|
||||||
c.Assert(resp.Header.Get(k), check.Equals, strings.Join(values, ","))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 *check.C) {
|
|
||||||
b := testBucket(s.s3)
|
|
||||||
err := b.PutBucket(s3.Private)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
// Ensure an empty state before testing its behavior.
|
|
||||||
multis, _, err := b.ListMulti("", "")
|
|
||||||
for _, m := range multis {
|
|
||||||
err := m.Abort()
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
}
|
|
||||||
|
|
||||||
keys := []string{
|
|
||||||
"a/multi2",
|
|
||||||
"a/multi3",
|
|
||||||
"b/multi4",
|
|
||||||
"multi1",
|
|
||||||
}
|
|
||||||
for _, key := range keys {
|
|
||||||
m, err := b.InitMulti(key, "", s3.Private, s3.Options{})
|
|
||||||
c.Assert(err, check.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, check.IsNil)
|
|
||||||
for attempt := attempts.Start(); attempt.Next() && len(multis) < len(keys); {
|
|
||||||
multis, prefixes, err = b.ListMulti("", "")
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
}
|
|
||||||
sort.Sort(multiList(multis))
|
|
||||||
c.Assert(prefixes, check.IsNil)
|
|
||||||
var gotKeys []string
|
|
||||||
for _, m := range multis {
|
|
||||||
gotKeys = append(gotKeys, m.Key)
|
|
||||||
}
|
|
||||||
c.Assert(gotKeys, check.DeepEquals, keys)
|
|
||||||
for _, m := range multis {
|
|
||||||
c.Assert(m.Bucket, check.Equals, b)
|
|
||||||
c.Assert(m.UploadId, check.Matches, ".+")
|
|
||||||
}
|
|
||||||
|
|
||||||
multis, prefixes, err = b.ListMulti("", "/")
|
|
||||||
for attempt := attempts.Start(); attempt.Next() && len(prefixes) < 2; {
|
|
||||||
multis, prefixes, err = b.ListMulti("", "")
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
}
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(prefixes, check.DeepEquals, []string{"a/", "b/"})
|
|
||||||
c.Assert(multis, check.HasLen, 1)
|
|
||||||
c.Assert(multis[0].Bucket, check.Equals, b)
|
|
||||||
c.Assert(multis[0].Key, check.Equals, "multi1")
|
|
||||||
c.Assert(multis[0].UploadId, check.Matches, ".+")
|
|
||||||
|
|
||||||
for attempt := attempts.Start(); attempt.Next() && len(multis) < 2; {
|
|
||||||
multis, prefixes, err = b.ListMulti("", "")
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
}
|
|
||||||
multis, prefixes, err = b.ListMulti("a/", "/")
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(prefixes, check.IsNil)
|
|
||||||
c.Assert(multis, check.HasLen, 2)
|
|
||||||
c.Assert(multis[0].Bucket, check.Equals, b)
|
|
||||||
c.Assert(multis[0].Key, check.Equals, "a/multi2")
|
|
||||||
c.Assert(multis[0].UploadId, check.Matches, ".+")
|
|
||||||
c.Assert(multis[1].Bucket, check.Equals, b)
|
|
||||||
c.Assert(multis[1].Key, check.Equals, "a/multi3")
|
|
||||||
c.Assert(multis[1].UploadId, check.Matches, ".+")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ClientTests) TestMultiPutAllZeroLength(c *check.C) {
|
|
||||||
b := testBucket(s.s3)
|
|
||||||
err := b.PutBucket(s3.Private)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
multi, err := b.InitMulti("multi", "text/plain", s3.Private, s3.Options{})
|
|
||||||
c.Assert(err, check.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, check.IsNil)
|
|
||||||
c.Assert(parts, check.HasLen, 1)
|
|
||||||
c.Assert(parts[0].Size, check.Equals, int64(0))
|
|
||||||
c.Assert(parts[0].ETag, check.Equals, `"d41d8cd98f00b204e9800998ecf8427e"`)
|
|
||||||
|
|
||||||
err = multi.Complete(parts)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
}
|
|
87
Godeps/_workspace/src/github.com/AdRoll/goamz/s3/s3t_test.go
generated
vendored
87
Godeps/_workspace/src/github.com/AdRoll/goamz/s3/s3t_test.go
generated
vendored
|
@ -1,87 +0,0 @@
|
||||||
package s3_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/AdRoll/goamz/aws"
|
|
||||||
"github.com/AdRoll/goamz/s3"
|
|
||||||
"github.com/AdRoll/goamz/s3/s3test"
|
|
||||||
"github.com/AdRoll/goamz/testutil"
|
|
||||||
"gopkg.in/check.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LocalServer struct {
|
|
||||||
auth aws.Auth
|
|
||||||
region aws.Region
|
|
||||||
srv *s3test.Server
|
|
||||||
config *s3test.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *LocalServer) SetUp(c *check.C) {
|
|
||||||
srv, err := s3test.NewServer(s.config)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(srv, check.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.
|
|
||||||
_ = check.Suite(&LocalServerSuite{})
|
|
||||||
_ = check.Suite(&LocalServerSuite{
|
|
||||||
srv: LocalServer{
|
|
||||||
config: &s3test.Config{
|
|
||||||
Send409Conflict: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *LocalServerSuite) SetUpSuite(c *check.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 *check.C) {
|
|
||||||
s.clientTests.Cleanup()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *LocalServerSuite) TestBasicFunctionality(c *check.C) {
|
|
||||||
s.clientTests.TestBasicFunctionality(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *LocalServerSuite) TestGetNotFound(c *check.C) {
|
|
||||||
s.clientTests.TestGetNotFound(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *LocalServerSuite) TestBucketList(c *check.C) {
|
|
||||||
s.clientTests.TestBucketList(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *LocalServerSuite) TestDoublePutBucket(c *check.C) {
|
|
||||||
s.clientTests.TestDoublePutBucket(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *LocalServerSuite) TestMultiComplete(c *check.C) {
|
|
||||||
if !testutil.Amazon {
|
|
||||||
c.Skip("live tests against AWS disabled (no -amazon)")
|
|
||||||
}
|
|
||||||
s.clientTests.TestMultiComplete(c)
|
|
||||||
}
|
|
148
Godeps/_workspace/src/github.com/AdRoll/goamz/s3/sign_test.go
generated
vendored
148
Godeps/_workspace/src/github.com/AdRoll/goamz/s3/sign_test.go
generated
vendored
|
@ -1,148 +0,0 @@
|
||||||
package s3_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/AdRoll/goamz/aws"
|
|
||||||
"github.com/AdRoll/goamz/s3"
|
|
||||||
"gopkg.in/check.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// S3 ReST authentication docs: http://goo.gl/G1LrK
|
|
||||||
|
|
||||||
var testAuth = aws.Auth{AccessKey: "0PN5J17HBGZHT7JJ3X82", SecretKey: "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o"}
|
|
||||||
|
|
||||||
func (s *S) TestSignExampleObjectGet(c *check.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"], check.DeepEquals, []string{expected})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestSignExampleObjectPut(c *check.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"], check.DeepEquals, []string{expected})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestSignExampleList(c *check.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"], check.DeepEquals, []string{expected})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestSignExampleFetch(c *check.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"], check.DeepEquals, []string{expected})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestSignExampleDelete(c *check.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"], check.DeepEquals, []string{expected})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestSignExampleUpload(c *check.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"], check.DeepEquals, []string{expected})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestSignExampleListAllMyBuckets(c *check.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"], check.DeepEquals, []string{expected})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestSignExampleUnicodeKeys(c *check.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"], check.DeepEquals, []string{expected})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestSignExampleCustomSSE(c *check.C) {
|
|
||||||
method := "GET"
|
|
||||||
path := "/secret/config"
|
|
||||||
params := map[string][]string{}
|
|
||||||
headers := map[string][]string{
|
|
||||||
"Host": {"secret.johnsmith.net:8080"},
|
|
||||||
"Date": {"Tue, 27 Mar 2007 21:06:08 +0000"},
|
|
||||||
"x-amz-server-side-encryption-customer-key": {"MWJhakVna1dQT1B0SDFMeGtVVnRQRTFGaU1ldFJrU0I="},
|
|
||||||
"x-amz-server-side-encryption-customer-key-MD5": {"glIqxpqQ4a9aoK/iLttKzQ=="},
|
|
||||||
"x-amz-server-side-encryption-customer-algorithm": {"AES256"},
|
|
||||||
}
|
|
||||||
s3.Sign(testAuth, method, path, params, headers)
|
|
||||||
expected := "AWS 0PN5J17HBGZHT7JJ3X82:Xq6PWmIo0aOWq+LDjCEiCGgbmHE="
|
|
||||||
c.Assert(headers["Authorization"], check.DeepEquals, []string{expected})
|
|
||||||
}
|
|
|
@ -307,20 +307,42 @@ func (s *V4Signer) canonicalURI(u *url.URL) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *V4Signer) canonicalQueryString(u *url.URL) string {
|
func (s *V4Signer) canonicalQueryString(u *url.URL) string {
|
||||||
var a []string
|
keyValues := make(map[string]string, len(u.Query()))
|
||||||
|
keys := make([]string, len(u.Query()))
|
||||||
|
|
||||||
|
key_i := 0
|
||||||
for k, vs := range u.Query() {
|
for k, vs := range u.Query() {
|
||||||
k = url.QueryEscape(k)
|
k = url.QueryEscape(k)
|
||||||
for _, v := range vs {
|
|
||||||
if v == "" {
|
a := make([]string, len(vs))
|
||||||
a = append(a, k+"=")
|
for idx, v := range vs {
|
||||||
} else {
|
v = url.QueryEscape(v)
|
||||||
v = url.QueryEscape(v)
|
a[idx] = fmt.Sprintf("%s=%s", k, v)
|
||||||
a = append(a, k+"="+v)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
keyValues[k] = strings.Join(a, "&")
|
||||||
|
keys[key_i] = k
|
||||||
|
key_i++
|
||||||
}
|
}
|
||||||
sort.Strings(a)
|
|
||||||
return strings.Join(a, "&")
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
query := make([]string, len(keys))
|
||||||
|
for idx, key := range keys {
|
||||||
|
query[idx] = keyValues[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
query_str := strings.Join(query, "&")
|
||||||
|
|
||||||
|
// AWS V4 signing requires that the space characters
|
||||||
|
// are encoded as %20 instead of +. On the other hand
|
||||||
|
// golangs url.QueryEscape as well as url.Values.Encode()
|
||||||
|
// both encode the space as a + character. See:
|
||||||
|
// http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
|
||||||
|
// https://github.com/golang/go/issues/4013
|
||||||
|
// https://groups.google.com/forum/#!topic/golang-nuts/BB443qEjPIk
|
||||||
|
|
||||||
|
return strings.Replace(query_str, "+", "%20", -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *V4Signer) canonicalHeaders(h http.Header) string {
|
func (s *V4Signer) canonicalHeaders(h http.Header) string {
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/AdRoll/goamz/aws"
|
"github.com/docker/goamz/aws"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
|
@ -30,7 +30,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdRoll/goamz/aws"
|
"github.com/docker/goamz/aws"
|
||||||
)
|
)
|
||||||
|
|
||||||
const debug = false
|
const debug = false
|
||||||
|
@ -39,10 +39,9 @@ const debug = false
|
||||||
type S3 struct {
|
type S3 struct {
|
||||||
aws.Auth
|
aws.Auth
|
||||||
aws.Region
|
aws.Region
|
||||||
ConnectTimeout time.Duration
|
Signature int
|
||||||
ReadTimeout time.Duration
|
Client *http.Client
|
||||||
Signature int
|
private byte // Reserve the right of using private data.
|
||||||
private byte // Reserve the right of using private data.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The Bucket type encapsulates operations with an S3 bucket.
|
// The Bucket type encapsulates operations with an S3 bucket.
|
||||||
|
@ -61,6 +60,8 @@ type Owner struct {
|
||||||
//
|
//
|
||||||
type Options struct {
|
type Options struct {
|
||||||
SSE bool
|
SSE bool
|
||||||
|
SSEKMS bool
|
||||||
|
SSEKMSKeyId string
|
||||||
SSECustomerAlgorithm string
|
SSECustomerAlgorithm string
|
||||||
SSECustomerKey string
|
SSECustomerKey string
|
||||||
SSECustomerKeyMD5 string
|
SSECustomerKeyMD5 string
|
||||||
|
@ -96,7 +97,13 @@ var attempts = aws.AttemptStrategy{
|
||||||
|
|
||||||
// New creates a new S3.
|
// New creates a new S3.
|
||||||
func New(auth aws.Auth, region aws.Region) *S3 {
|
func New(auth aws.Auth, region aws.Region) *S3 {
|
||||||
return &S3{auth, region, 0, 0, aws.V2Signature, 0}
|
return &S3{
|
||||||
|
Auth: auth,
|
||||||
|
Region: region,
|
||||||
|
Signature: aws.V2Signature,
|
||||||
|
Client: http.DefaultClient,
|
||||||
|
private: 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bucket returns a Bucket with the given name.
|
// Bucket returns a Bucket with the given name.
|
||||||
|
@ -171,6 +178,13 @@ const (
|
||||||
StandardStorage = StorageClass("STANDARD")
|
StandardStorage = StorageClass("STANDARD")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ServerSideEncryption string
|
||||||
|
|
||||||
|
const (
|
||||||
|
S3Managed = ServerSideEncryption("AES256")
|
||||||
|
KMSManaged = ServerSideEncryption("aws:kms")
|
||||||
|
)
|
||||||
|
|
||||||
// PutBucket creates a new bucket.
|
// PutBucket creates a new bucket.
|
||||||
//
|
//
|
||||||
// See http://goo.gl/ndjnR for details.
|
// See http://goo.gl/ndjnR for details.
|
||||||
|
@ -344,7 +358,7 @@ func (b *Bucket) Put(path string, data []byte, contType string, perm ACL, option
|
||||||
func (b *Bucket) PutCopy(path string, perm ACL, options CopyOptions, source string) (*CopyObjectResult, error) {
|
func (b *Bucket) PutCopy(path string, perm ACL, options CopyOptions, source string) (*CopyObjectResult, error) {
|
||||||
headers := map[string][]string{
|
headers := map[string][]string{
|
||||||
"x-amz-acl": {string(perm)},
|
"x-amz-acl": {string(perm)},
|
||||||
"x-amz-copy-source": {url.QueryEscape(source)},
|
"x-amz-copy-source": {escapePath(source)},
|
||||||
}
|
}
|
||||||
options.addHeaders(headers)
|
options.addHeaders(headers)
|
||||||
req := &request{
|
req := &request{
|
||||||
|
@ -383,7 +397,12 @@ func (b *Bucket) PutReader(path string, r io.Reader, length int64, contType stri
|
||||||
// addHeaders adds o's specified fields to headers
|
// addHeaders adds o's specified fields to headers
|
||||||
func (o Options) addHeaders(headers map[string][]string) {
|
func (o Options) addHeaders(headers map[string][]string) {
|
||||||
if o.SSE {
|
if o.SSE {
|
||||||
headers["x-amz-server-side-encryption"] = []string{"AES256"}
|
headers["x-amz-server-side-encryption"] = []string{string(S3Managed)}
|
||||||
|
} else if o.SSEKMS {
|
||||||
|
headers["x-amz-server-side-encryption"] = []string{string(KMSManaged)}
|
||||||
|
if len(o.SSEKMSKeyId) != 0 {
|
||||||
|
headers["x-amz-server-side-encryption-aws-kms-key-id"] = []string{o.SSEKMSKeyId}
|
||||||
|
}
|
||||||
} else if len(o.SSECustomerAlgorithm) != 0 && len(o.SSECustomerKey) != 0 && len(o.SSECustomerKeyMD5) != 0 {
|
} else if len(o.SSECustomerAlgorithm) != 0 && len(o.SSECustomerKey) != 0 && len(o.SSECustomerKeyMD5) != 0 {
|
||||||
// Amazon-managed keys and customer-managed keys are mutually exclusive
|
// Amazon-managed keys and customer-managed keys are mutually exclusive
|
||||||
headers["x-amz-server-side-encryption-customer-algorithm"] = []string{o.SSECustomerAlgorithm}
|
headers["x-amz-server-side-encryption-customer-algorithm"] = []string{o.SSECustomerAlgorithm}
|
||||||
|
@ -886,6 +905,12 @@ func (b *Bucket) PostFormArgsEx(path string, expires time.Time, redirect string,
|
||||||
"key": path,
|
"key": path,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if token := b.S3.Auth.Token(); token != "" {
|
||||||
|
fields["x-amz-security-token"] = token
|
||||||
|
conditions = append(conditions,
|
||||||
|
fmt.Sprintf("{\"x-amz-security-token\": \"%s\"}", token))
|
||||||
|
}
|
||||||
|
|
||||||
if conds != nil {
|
if conds != nil {
|
||||||
conditions = append(conditions, conds...)
|
conditions = append(conditions, conds...)
|
||||||
}
|
}
|
||||||
|
@ -1123,7 +1148,6 @@ func (s3 *S3) setupHttpRequest(req *request) (*http.Request, error) {
|
||||||
Method: req.method,
|
Method: req.method,
|
||||||
ProtoMajor: 1,
|
ProtoMajor: 1,
|
||||||
ProtoMinor: 1,
|
ProtoMinor: 1,
|
||||||
Close: true,
|
|
||||||
Header: req.headers,
|
Header: req.headers,
|
||||||
Form: req.params,
|
Form: req.params,
|
||||||
}
|
}
|
||||||
|
@ -1143,28 +1167,7 @@ func (s3 *S3) setupHttpRequest(req *request) (*http.Request, error) {
|
||||||
// If resp is not nil, the XML data contained in the response
|
// If resp is not nil, the XML data contained in the response
|
||||||
// body will be unmarshalled on it.
|
// body will be unmarshalled on it.
|
||||||
func (s3 *S3) doHttpRequest(hreq *http.Request, resp interface{}) (*http.Response, error) {
|
func (s3 *S3) doHttpRequest(hreq *http.Request, resp interface{}) (*http.Response, error) {
|
||||||
c := http.Client{
|
hresp, err := s3.Client.Do(hreq)
|
||||||
Transport: &http.Transport{
|
|
||||||
Dial: func(netw, addr string) (c net.Conn, err error) {
|
|
||||||
deadline := time.Now().Add(s3.ReadTimeout)
|
|
||||||
if s3.ConnectTimeout > 0 {
|
|
||||||
c, err = net.DialTimeout(netw, addr, s3.ConnectTimeout)
|
|
||||||
} else {
|
|
||||||
c, err = net.Dial(netw, addr)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if s3.ReadTimeout > 0 {
|
|
||||||
err = c.SetDeadline(deadline)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
},
|
|
||||||
Proxy: http.ProxyFromEnvironment,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
hresp, err := c.Do(hreq)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -1296,3 +1299,7 @@ func hasCode(err error, code string) bool {
|
||||||
s3err, ok := err.(*Error)
|
s3err, ok := err.(*Error)
|
||||||
return ok && s3err.Code == code
|
return ok && s3err.Code == code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func escapePath(s string) string {
|
||||||
|
return (&url.URL{Path: s}).String()
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/AdRoll/goamz/s3"
|
"github.com/docker/goamz/s3"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
@ -44,6 +44,17 @@ type action struct {
|
||||||
reqId string
|
reqId string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A Clock reports the current time.
|
||||||
|
type Clock interface {
|
||||||
|
Now() time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type realClock struct{}
|
||||||
|
|
||||||
|
func (c *realClock) Now() time.Time {
|
||||||
|
return time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
// Config controls the internal behaviour of the Server. A nil config is the default
|
// 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
|
// and behaves as if all configurations assume their default behaviour. Once passed
|
||||||
// to NewServer, the configuration must not be modified.
|
// to NewServer, the configuration must not be modified.
|
||||||
|
@ -58,6 +69,10 @@ type Config struct {
|
||||||
// Address on which to listen. By default, a random port is assigned by the
|
// Address on which to listen. By default, a random port is assigned by the
|
||||||
// operating system and the server listens on localhost.
|
// operating system and the server listens on localhost.
|
||||||
ListenAddress string
|
ListenAddress string
|
||||||
|
|
||||||
|
// Clock used to set mtime when updating an object. If nil,
|
||||||
|
// use the real clock.
|
||||||
|
Clock Clock
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) send409Conflict() bool {
|
func (c *Config) send409Conflict() bool {
|
||||||
|
@ -76,6 +91,7 @@ type Server struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
buckets map[string]*bucket
|
buckets map[string]*bucket
|
||||||
config *Config
|
config *Config
|
||||||
|
closed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type bucket struct {
|
type bucket struct {
|
||||||
|
@ -129,10 +145,18 @@ type resource interface {
|
||||||
func NewServer(config *Config) (*Server, error) {
|
func NewServer(config *Config) (*Server, error) {
|
||||||
listenAddress := "localhost:0"
|
listenAddress := "localhost:0"
|
||||||
|
|
||||||
if config != nil && config.ListenAddress != "" {
|
if config == nil {
|
||||||
|
config = &Config{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.ListenAddress != "" {
|
||||||
listenAddress = config.ListenAddress
|
listenAddress = config.ListenAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.Clock == nil {
|
||||||
|
config.Clock = &realClock{}
|
||||||
|
}
|
||||||
|
|
||||||
l, err := net.Listen("tcp", listenAddress)
|
l, err := net.Listen("tcp", listenAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot listen on localhost: %v", err)
|
return nil, fmt.Errorf("cannot listen on localhost: %v", err)
|
||||||
|
@ -151,6 +175,10 @@ func NewServer(config *Config) (*Server, error) {
|
||||||
|
|
||||||
// Quit closes down the server.
|
// Quit closes down the server.
|
||||||
func (srv *Server) Quit() {
|
func (srv *Server) Quit() {
|
||||||
|
srv.mu.Lock()
|
||||||
|
srv.closed = true
|
||||||
|
srv.mu.Unlock()
|
||||||
|
|
||||||
srv.listener.Close()
|
srv.listener.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,6 +203,13 @@ func (srv *Server) serveHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
srv.mu.Lock()
|
srv.mu.Lock()
|
||||||
defer srv.mu.Unlock()
|
defer srv.mu.Unlock()
|
||||||
|
|
||||||
|
if srv.closed {
|
||||||
|
hj := w.(http.Hijacker)
|
||||||
|
conn, _, _ := hj.Hijack()
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if debug {
|
if debug {
|
||||||
log.Printf("s3test %q %q", req.Method, req.URL)
|
log.Printf("s3test %q %q", req.Method, req.URL)
|
||||||
}
|
}
|
||||||
|
@ -360,15 +395,28 @@ func (r bucketResource) get(a *action) interface{} {
|
||||||
if maxKeys <= 0 {
|
if maxKeys <= 0 {
|
||||||
maxKeys = 1000
|
maxKeys = 1000
|
||||||
}
|
}
|
||||||
resp := &s3.ListResp{
|
|
||||||
Name: r.bucket.name,
|
type commonPrefix struct {
|
||||||
Prefix: prefix,
|
Prefix string
|
||||||
Delimiter: delimiter,
|
|
||||||
Marker: marker,
|
|
||||||
MaxKeys: maxKeys,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var prefixes []string
|
type serverListResponse struct {
|
||||||
|
s3.ListResp
|
||||||
|
CommonPrefixes []commonPrefix
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := &serverListResponse{
|
||||||
|
ListResp: s3.ListResp{
|
||||||
|
Name: r.bucket.name,
|
||||||
|
Prefix: prefix,
|
||||||
|
Delimiter: delimiter,
|
||||||
|
Marker: marker,
|
||||||
|
MaxKeys: maxKeys,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var prefixes []commonPrefix
|
||||||
|
var lastName string
|
||||||
for _, obj := range objs {
|
for _, obj := range objs {
|
||||||
if !strings.HasPrefix(obj.name, prefix) {
|
if !strings.HasPrefix(obj.name, prefix) {
|
||||||
continue
|
continue
|
||||||
|
@ -378,7 +426,7 @@ func (r bucketResource) get(a *action) interface{} {
|
||||||
if delimiter != "" {
|
if delimiter != "" {
|
||||||
if i := strings.Index(obj.name[len(prefix):], delimiter); i >= 0 {
|
if i := strings.Index(obj.name[len(prefix):], delimiter); i >= 0 {
|
||||||
name = obj.name[:len(prefix)+i+len(delimiter)]
|
name = obj.name[:len(prefix)+i+len(delimiter)]
|
||||||
if prefixes != nil && prefixes[len(prefixes)-1] == name {
|
if prefixes != nil && prefixes[len(prefixes)-1].Prefix == name {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
isPrefix = true
|
isPrefix = true
|
||||||
|
@ -389,14 +437,16 @@ func (r bucketResource) get(a *action) interface{} {
|
||||||
}
|
}
|
||||||
if len(resp.Contents)+len(prefixes) >= maxKeys {
|
if len(resp.Contents)+len(prefixes) >= maxKeys {
|
||||||
resp.IsTruncated = true
|
resp.IsTruncated = true
|
||||||
|
resp.NextMarker = lastName
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if isPrefix {
|
if isPrefix {
|
||||||
prefixes = append(prefixes, name)
|
prefixes = append(prefixes, commonPrefix{Prefix: name})
|
||||||
} else {
|
} else {
|
||||||
// Contents contains only keys not found in CommonPrefixes
|
// Contents contains only keys not found in CommonPrefixes
|
||||||
resp.Contents = append(resp.Contents, obj.s3Key())
|
resp.Contents = append(resp.Contents, obj.s3Key())
|
||||||
}
|
}
|
||||||
|
lastName = name
|
||||||
}
|
}
|
||||||
resp.CommonPrefixes = prefixes
|
resp.CommonPrefixes = prefixes
|
||||||
return resp
|
return resp
|
||||||
|
@ -649,7 +699,7 @@ func (objr objectResource) get(a *action) interface{} {
|
||||||
// TODO x-amz-request-id
|
// TODO x-amz-request-id
|
||||||
h.Set("Content-Length", fmt.Sprint(len(data)))
|
h.Set("Content-Length", fmt.Sprint(len(data)))
|
||||||
h.Set("ETag", "\""+hex.EncodeToString(obj.checksum)+"\"")
|
h.Set("ETag", "\""+hex.EncodeToString(obj.checksum)+"\"")
|
||||||
h.Set("Last-Modified", obj.mtime.Format(lastModifiedTimeFormat))
|
h.Set("Last-Modified", obj.mtime.UTC().Format(lastModifiedTimeFormat))
|
||||||
|
|
||||||
if status != http.StatusOK {
|
if status != http.StatusOK {
|
||||||
a.w.WriteHeader(status)
|
a.w.WriteHeader(status)
|
||||||
|
@ -682,6 +732,8 @@ func (objr objectResource) put(a *action) interface{} {
|
||||||
// TODO x-amz-server-side-encryption
|
// TODO x-amz-server-side-encryption
|
||||||
// TODO x-amz-storage-class
|
// TODO x-amz-storage-class
|
||||||
|
|
||||||
|
var res interface{}
|
||||||
|
|
||||||
uploadId := a.req.URL.Query().Get("uploadId")
|
uploadId := a.req.URL.Query().Get("uploadId")
|
||||||
var partNumber uint
|
var partNumber uint
|
||||||
|
|
||||||
|
@ -751,6 +803,7 @@ func (objr objectResource) put(a *action) interface{} {
|
||||||
obj.meta[key] = values
|
obj.meta[key] = values
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
obj.mtime = a.srv.config.Clock.Now()
|
||||||
|
|
||||||
if copySource := a.req.Header.Get("X-Amz-Copy-Source"); copySource != "" {
|
if copySource := a.req.Header.Get("X-Amz-Copy-Source"); copySource != "" {
|
||||||
idx := strings.IndexByte(copySource, '/')
|
idx := strings.IndexByte(copySource, '/')
|
||||||
|
@ -786,11 +839,15 @@ func (objr objectResource) put(a *action) interface{} {
|
||||||
obj.meta[k] = make([]string, len(v))
|
obj.meta[k] = make([]string, len(v))
|
||||||
copy(obj.meta[k], v)
|
copy(obj.meta[k], v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
res = &s3.CopyObjectResult{
|
||||||
|
ETag: etag,
|
||||||
|
LastModified: obj.mtime.UTC().Format(time.RFC3339),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
obj.data = data
|
obj.data = data
|
||||||
obj.checksum = gotHash
|
obj.checksum = gotHash
|
||||||
}
|
}
|
||||||
obj.mtime = time.Now()
|
|
||||||
objr.bucket.objects[objr.name] = obj
|
objr.bucket.objects[objr.name] = obj
|
||||||
} else {
|
} else {
|
||||||
// For multipart commit
|
// For multipart commit
|
||||||
|
@ -800,13 +857,13 @@ func (objr objectResource) put(a *action) interface{} {
|
||||||
index: partNumber,
|
index: partNumber,
|
||||||
data: data,
|
data: data,
|
||||||
etag: etag,
|
etag: etag,
|
||||||
lastModified: time.Now(),
|
lastModified: a.srv.config.Clock.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
objr.bucket.multipartUploads[uploadId] = append(parts, part)
|
objr.bucket.multipartUploads[uploadId] = append(parts, part)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func (objr objectResource) delete(a *action) interface{} {
|
func (objr objectResource) delete(a *action) interface{} {
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"github.com/AdRoll/goamz/aws"
|
"github.com/docker/goamz/aws"
|
||||||
"log"
|
"log"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
|
@ -30,6 +31,7 @@ import (
|
||||||
storagedriver "github.com/docker/distribution/registry/storage/driver"
|
storagedriver "github.com/docker/distribution/registry/storage/driver"
|
||||||
"github.com/docker/distribution/registry/storage/driver/factory"
|
"github.com/docker/distribution/registry/storage/driver/factory"
|
||||||
storagemiddleware "github.com/docker/distribution/registry/storage/driver/middleware"
|
storagemiddleware "github.com/docker/distribution/registry/storage/driver/middleware"
|
||||||
|
"github.com/docker/distribution/version"
|
||||||
"github.com/docker/libtrust"
|
"github.com/docker/libtrust"
|
||||||
"github.com/garyburd/redigo/redis"
|
"github.com/garyburd/redigo/redis"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
@ -83,12 +85,12 @@ type App struct {
|
||||||
// NewApp takes a configuration and returns a configured app, ready to serve
|
// NewApp takes a configuration and returns a configured app, ready to serve
|
||||||
// requests. The app only implements ServeHTTP and can be wrapped in other
|
// requests. The app only implements ServeHTTP and can be wrapped in other
|
||||||
// handlers accordingly.
|
// handlers accordingly.
|
||||||
func NewApp(ctx context.Context, configuration *configuration.Configuration) *App {
|
func NewApp(ctx context.Context, config *configuration.Configuration) *App {
|
||||||
app := &App{
|
app := &App{
|
||||||
Config: configuration,
|
Config: config,
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
router: v2.RouterWithPrefix(configuration.HTTP.Prefix),
|
router: v2.RouterWithPrefix(config.HTTP.Prefix),
|
||||||
isCache: configuration.Proxy.RemoteURL != "",
|
isCache: config.Proxy.RemoteURL != "",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the handler dispatchers.
|
// Register the handler dispatchers.
|
||||||
|
@ -102,8 +104,15 @@ func NewApp(ctx context.Context, configuration *configuration.Configuration) *Ap
|
||||||
app.register(v2.RouteNameBlobUpload, blobUploadDispatcher)
|
app.register(v2.RouteNameBlobUpload, blobUploadDispatcher)
|
||||||
app.register(v2.RouteNameBlobUploadChunk, blobUploadDispatcher)
|
app.register(v2.RouteNameBlobUploadChunk, blobUploadDispatcher)
|
||||||
|
|
||||||
|
// override the storage driver's UA string for registry outbound HTTP requests
|
||||||
|
storageParams := config.Storage.Parameters()
|
||||||
|
if storageParams == nil {
|
||||||
|
storageParams = make(configuration.Parameters)
|
||||||
|
}
|
||||||
|
storageParams["useragent"] = fmt.Sprintf("docker-distribution/%s %s", version.Version, runtime.Version())
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
app.driver, err = factory.Create(configuration.Storage.Type(), configuration.Storage.Parameters())
|
app.driver, err = factory.Create(config.Storage.Type(), storageParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO(stevvooe): Move the creation of a service into a protected
|
// TODO(stevvooe): Move the creation of a service into a protected
|
||||||
// method, where this is created lazily. Its status can be queried via
|
// method, where this is created lazily. Its status can be queried via
|
||||||
|
@ -112,7 +121,7 @@ func NewApp(ctx context.Context, configuration *configuration.Configuration) *Ap
|
||||||
}
|
}
|
||||||
|
|
||||||
purgeConfig := uploadPurgeDefaultConfig()
|
purgeConfig := uploadPurgeDefaultConfig()
|
||||||
if mc, ok := configuration.Storage["maintenance"]; ok {
|
if mc, ok := config.Storage["maintenance"]; ok {
|
||||||
if v, ok := mc["uploadpurging"]; ok {
|
if v, ok := mc["uploadpurging"]; ok {
|
||||||
purgeConfig, ok = v.(map[interface{}]interface{})
|
purgeConfig, ok = v.(map[interface{}]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -135,15 +144,15 @@ func NewApp(ctx context.Context, configuration *configuration.Configuration) *Ap
|
||||||
|
|
||||||
startUploadPurger(app, app.driver, ctxu.GetLogger(app), purgeConfig)
|
startUploadPurger(app, app.driver, ctxu.GetLogger(app), purgeConfig)
|
||||||
|
|
||||||
app.driver, err = applyStorageMiddleware(app.driver, configuration.Middleware["storage"])
|
app.driver, err = applyStorageMiddleware(app.driver, config.Middleware["storage"])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.configureSecret(configuration)
|
app.configureSecret(config)
|
||||||
app.configureEvents(configuration)
|
app.configureEvents(config)
|
||||||
app.configureRedis(configuration)
|
app.configureRedis(config)
|
||||||
app.configureLogHook(configuration)
|
app.configureLogHook(config)
|
||||||
|
|
||||||
// Generate an ephemeral key to be used for signing converted manifests
|
// Generate an ephemeral key to be used for signing converted manifests
|
||||||
// for clients that don't support schema2.
|
// for clients that don't support schema2.
|
||||||
|
@ -152,8 +161,8 @@ func NewApp(ctx context.Context, configuration *configuration.Configuration) *Ap
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.HTTP.Host != "" {
|
if config.HTTP.Host != "" {
|
||||||
u, err := url.Parse(configuration.HTTP.Host)
|
u, err := url.Parse(config.HTTP.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf(`could not parse http "host" parameter: %v`, err))
|
panic(fmt.Sprintf(`could not parse http "host" parameter: %v`, err))
|
||||||
}
|
}
|
||||||
|
@ -167,7 +176,7 @@ func NewApp(ctx context.Context, configuration *configuration.Configuration) *Ap
|
||||||
}
|
}
|
||||||
|
|
||||||
// configure deletion
|
// configure deletion
|
||||||
if d, ok := configuration.Storage["delete"]; ok {
|
if d, ok := config.Storage["delete"]; ok {
|
||||||
e, ok := d["enabled"]
|
e, ok := d["enabled"]
|
||||||
if ok {
|
if ok {
|
||||||
if deleteEnabled, ok := e.(bool); ok && deleteEnabled {
|
if deleteEnabled, ok := e.(bool); ok && deleteEnabled {
|
||||||
|
@ -178,7 +187,7 @@ func NewApp(ctx context.Context, configuration *configuration.Configuration) *Ap
|
||||||
|
|
||||||
// configure redirects
|
// configure redirects
|
||||||
var redirectDisabled bool
|
var redirectDisabled bool
|
||||||
if redirectConfig, ok := configuration.Storage["redirect"]; ok {
|
if redirectConfig, ok := config.Storage["redirect"]; ok {
|
||||||
v := redirectConfig["disable"]
|
v := redirectConfig["disable"]
|
||||||
switch v := v.(type) {
|
switch v := v.(type) {
|
||||||
case bool:
|
case bool:
|
||||||
|
@ -194,7 +203,7 @@ func NewApp(ctx context.Context, configuration *configuration.Configuration) *Ap
|
||||||
}
|
}
|
||||||
|
|
||||||
// configure storage caches
|
// configure storage caches
|
||||||
if cc, ok := configuration.Storage["cache"]; ok {
|
if cc, ok := config.Storage["cache"]; ok {
|
||||||
v, ok := cc["blobdescriptor"]
|
v, ok := cc["blobdescriptor"]
|
||||||
if !ok {
|
if !ok {
|
||||||
// Backwards compatible: "layerinfo" == "blobdescriptor"
|
// Backwards compatible: "layerinfo" == "blobdescriptor"
|
||||||
|
@ -223,7 +232,7 @@ func NewApp(ctx context.Context, configuration *configuration.Configuration) *Ap
|
||||||
ctxu.GetLogger(app).Infof("using inmemory blob descriptor cache")
|
ctxu.GetLogger(app).Infof("using inmemory blob descriptor cache")
|
||||||
default:
|
default:
|
||||||
if v != "" {
|
if v != "" {
|
||||||
ctxu.GetLogger(app).Warnf("unknown cache type %q, caching disabled", configuration.Storage["cache"])
|
ctxu.GetLogger(app).Warnf("unknown cache type %q, caching disabled", config.Storage["cache"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -236,15 +245,15 @@ func NewApp(ctx context.Context, configuration *configuration.Configuration) *Ap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app.registry, err = applyRegistryMiddleware(app.Context, app.registry, configuration.Middleware["registry"])
|
app.registry, err = applyRegistryMiddleware(app.Context, app.registry, config.Middleware["registry"])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
authType := configuration.Auth.Type()
|
authType := config.Auth.Type()
|
||||||
|
|
||||||
if authType != "" {
|
if authType != "" {
|
||||||
accessController, err := auth.GetAccessController(configuration.Auth.Type(), configuration.Auth.Parameters())
|
accessController, err := auth.GetAccessController(config.Auth.Type(), config.Auth.Parameters())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("unable to configure authorization (%s): %v", authType, err))
|
panic(fmt.Sprintf("unable to configure authorization (%s): %v", authType, err))
|
||||||
}
|
}
|
||||||
|
@ -253,13 +262,13 @@ func NewApp(ctx context.Context, configuration *configuration.Configuration) *Ap
|
||||||
}
|
}
|
||||||
|
|
||||||
// configure as a pull through cache
|
// configure as a pull through cache
|
||||||
if configuration.Proxy.RemoteURL != "" {
|
if config.Proxy.RemoteURL != "" {
|
||||||
app.registry, err = proxy.NewRegistryPullThroughCache(ctx, app.registry, app.driver, configuration.Proxy)
|
app.registry, err = proxy.NewRegistryPullThroughCache(ctx, app.registry, app.driver, config.Proxy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err.Error())
|
panic(err.Error())
|
||||||
}
|
}
|
||||||
app.isCache = true
|
app.isCache = true
|
||||||
ctxu.GetLogger(app).Info("Registry configured as a proxy cache to ", configuration.Proxy.RemoteURL)
|
ctxu.GetLogger(app).Info("Registry configured as a proxy cache to ", config.Proxy.RemoteURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
|
@ -10,10 +10,10 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdRoll/goamz/cloudfront"
|
|
||||||
"github.com/docker/distribution/context"
|
"github.com/docker/distribution/context"
|
||||||
storagedriver "github.com/docker/distribution/registry/storage/driver"
|
storagedriver "github.com/docker/distribution/registry/storage/driver"
|
||||||
storagemiddleware "github.com/docker/distribution/registry/storage/driver/middleware"
|
storagemiddleware "github.com/docker/distribution/registry/storage/driver/middleware"
|
||||||
|
"github.com/docker/goamz/cloudfront"
|
||||||
)
|
)
|
||||||
|
|
||||||
// cloudFrontStorageMiddleware provides an simple implementation of layerHandler that
|
// cloudFrontStorageMiddleware provides an simple implementation of layerHandler that
|
||||||
|
|
|
@ -26,11 +26,12 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdRoll/goamz/aws"
|
|
||||||
"github.com/AdRoll/goamz/s3"
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/goamz/aws"
|
||||||
|
"github.com/docker/goamz/s3"
|
||||||
|
|
||||||
"github.com/docker/distribution/context"
|
"github.com/docker/distribution/context"
|
||||||
|
"github.com/docker/distribution/registry/client/transport"
|
||||||
storagedriver "github.com/docker/distribution/registry/storage/driver"
|
storagedriver "github.com/docker/distribution/registry/storage/driver"
|
||||||
"github.com/docker/distribution/registry/storage/driver/base"
|
"github.com/docker/distribution/registry/storage/driver/base"
|
||||||
"github.com/docker/distribution/registry/storage/driver/factory"
|
"github.com/docker/distribution/registry/storage/driver/factory"
|
||||||
|
@ -58,6 +59,7 @@ type DriverParameters struct {
|
||||||
V4Auth bool
|
V4Auth bool
|
||||||
ChunkSize int64
|
ChunkSize int64
|
||||||
RootDirectory string
|
RootDirectory string
|
||||||
|
UserAgent string
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -168,7 +170,7 @@ func FromParameters(parameters map[string]interface{}) (*Driver, error) {
|
||||||
case int, uint, int32, uint32, uint64:
|
case int, uint, int32, uint32, uint64:
|
||||||
chunkSize = reflect.ValueOf(v).Convert(reflect.TypeOf(chunkSize)).Int()
|
chunkSize = reflect.ValueOf(v).Convert(reflect.TypeOf(chunkSize)).Int()
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("invalid valud for chunksize: %#v", chunkSizeParam)
|
return nil, fmt.Errorf("invalid value for chunksize: %#v", chunkSizeParam)
|
||||||
}
|
}
|
||||||
|
|
||||||
if chunkSize < minChunkSize {
|
if chunkSize < minChunkSize {
|
||||||
|
@ -181,6 +183,11 @@ func FromParameters(parameters map[string]interface{}) (*Driver, error) {
|
||||||
rootDirectory = ""
|
rootDirectory = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userAgent, ok := parameters["useragent"]
|
||||||
|
if !ok {
|
||||||
|
userAgent = ""
|
||||||
|
}
|
||||||
|
|
||||||
params := DriverParameters{
|
params := DriverParameters{
|
||||||
fmt.Sprint(accessKey),
|
fmt.Sprint(accessKey),
|
||||||
fmt.Sprint(secretKey),
|
fmt.Sprint(secretKey),
|
||||||
|
@ -191,6 +198,7 @@ func FromParameters(parameters map[string]interface{}) (*Driver, error) {
|
||||||
v4AuthBool,
|
v4AuthBool,
|
||||||
chunkSize,
|
chunkSize,
|
||||||
fmt.Sprint(rootDirectory),
|
fmt.Sprint(rootDirectory),
|
||||||
|
fmt.Sprint(userAgent),
|
||||||
}
|
}
|
||||||
|
|
||||||
return New(params)
|
return New(params)
|
||||||
|
@ -209,7 +217,16 @@ func New(params DriverParameters) (*Driver, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
s3obj := s3.New(auth, params.Region)
|
s3obj := s3.New(auth, params.Region)
|
||||||
bucket := s3obj.Bucket(params.Bucket)
|
|
||||||
|
if params.UserAgent != "" {
|
||||||
|
s3obj.Client = &http.Client{
|
||||||
|
Transport: transport.NewTransport(http.DefaultTransport,
|
||||||
|
transport.NewHeaderRequestModifier(http.Header{
|
||||||
|
http.CanonicalHeaderKey("User-Agent"): []string{params.UserAgent},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if params.V4Auth {
|
if params.V4Auth {
|
||||||
s3obj.Signature = aws.V4Signature
|
s3obj.Signature = aws.V4Signature
|
||||||
|
@ -219,6 +236,8 @@ func New(params DriverParameters) (*Driver, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bucket := s3obj.Bucket(params.Bucket)
|
||||||
|
|
||||||
// TODO Currently multipart uploads have no timestamps, so this would be unwise
|
// TODO Currently multipart uploads have no timestamps, so this would be unwise
|
||||||
// if you initiated a new s3driver while another one is running on the same bucket.
|
// if you initiated a new s3driver while another one is running on the same bucket.
|
||||||
// multis, _, err := bucket.ListMulti("", "")
|
// multis, _, err := bucket.ListMulti("", "")
|
||||||
|
|
|
@ -6,10 +6,10 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/AdRoll/goamz/aws"
|
|
||||||
"github.com/docker/distribution/context"
|
"github.com/docker/distribution/context"
|
||||||
storagedriver "github.com/docker/distribution/registry/storage/driver"
|
storagedriver "github.com/docker/distribution/registry/storage/driver"
|
||||||
"github.com/docker/distribution/registry/storage/driver/testsuites"
|
"github.com/docker/distribution/registry/storage/driver/testsuites"
|
||||||
|
"github.com/docker/goamz/aws"
|
||||||
|
|
||||||
"gopkg.in/check.v1"
|
"gopkg.in/check.v1"
|
||||||
)
|
)
|
||||||
|
@ -69,6 +69,7 @@ func init() {
|
||||||
v4AuthBool,
|
v4AuthBool,
|
||||||
minChunkSize,
|
minChunkSize,
|
||||||
rootDirectory,
|
rootDirectory,
|
||||||
|
"",
|
||||||
}
|
}
|
||||||
|
|
||||||
return New(parameters)
|
return New(parameters)
|
||||||
|
|
Loading…
Reference in a new issue