Merge pull request #415 from stevvooe/update-goamz
Update goamz package dependency
This commit is contained in:
commit
cbfce39acb
6 changed files with 281 additions and 37 deletions
6
Godeps/Godeps.json
generated
6
Godeps/Godeps.json
generated
|
@ -12,15 +12,15 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/AdRoll/goamz/aws",
|
"ImportPath": "github.com/AdRoll/goamz/aws",
|
||||||
"Rev": "d3664b76d90508cdda5a6c92042f26eab5db3103"
|
"Rev": "cc210f45dcb9889c2769a274522be2bf70edfb99"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/AdRoll/goamz/cloudfront",
|
"ImportPath": "github.com/AdRoll/goamz/cloudfront",
|
||||||
"Rev": "d3664b76d90508cdda5a6c92042f26eab5db3103"
|
"Rev": "cc210f45dcb9889c2769a274522be2bf70edfb99"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/AdRoll/goamz/s3",
|
"ImportPath": "github.com/AdRoll/goamz/s3",
|
||||||
"Rev": "d3664b76d90508cdda5a6c92042f26eab5db3103"
|
"Rev": "cc210f45dcb9889c2769a274522be2bf70edfb99"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/MSOpenTech/azure-sdk-for-go/storage",
|
"ImportPath": "github.com/MSOpenTech/azure-sdk-for-go/storage",
|
||||||
|
|
7
Godeps/_workspace/src/github.com/AdRoll/goamz/aws/aws.go
generated
vendored
7
Godeps/_workspace/src/github.com/AdRoll/goamz/aws/aws.go
generated
vendored
|
@ -62,6 +62,7 @@ type Region struct {
|
||||||
SESEndpoint string
|
SESEndpoint string
|
||||||
IAMEndpoint string
|
IAMEndpoint string
|
||||||
ELBEndpoint string
|
ELBEndpoint string
|
||||||
|
KMSEndpoint string
|
||||||
DynamoDBEndpoint string
|
DynamoDBEndpoint string
|
||||||
CloudWatchServicepoint ServiceInfo
|
CloudWatchServicepoint ServiceInfo
|
||||||
AutoScalingEndpoint string
|
AutoScalingEndpoint string
|
||||||
|
@ -83,6 +84,7 @@ var Regions = map[string]Region{
|
||||||
USWest2.Name: USWest2,
|
USWest2.Name: USWest2,
|
||||||
USGovWest.Name: USGovWest,
|
USGovWest.Name: USGovWest,
|
||||||
SAEast.Name: SAEast,
|
SAEast.Name: SAEast,
|
||||||
|
CNNorth1.Name: CNNorth1,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Designates a signer interface suitable for signing AWS requests, params
|
// Designates a signer interface suitable for signing AWS requests, params
|
||||||
|
@ -208,7 +210,10 @@ func (a *Auth) Token() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
if time.Since(a.expiration) >= -30*time.Second { //in an ideal world this should be zero assuming the instance is synching it's clock
|
if time.Since(a.expiration) >= -30*time.Second { //in an ideal world this should be zero assuming the instance is synching it's clock
|
||||||
*a, _ = GetAuth("", "", "", time.Time{})
|
auth, err := GetAuth("", "", "", time.Time{})
|
||||||
|
if err == nil {
|
||||||
|
*a = auth
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return a.token
|
return a.token
|
||||||
}
|
}
|
||||||
|
|
34
Godeps/_workspace/src/github.com/AdRoll/goamz/aws/regions.go
generated
vendored
34
Godeps/_workspace/src/github.com/AdRoll/goamz/aws/regions.go
generated
vendored
|
@ -13,6 +13,7 @@ var USGovWest = Region{
|
||||||
"",
|
"",
|
||||||
"https://iam.us-gov.amazonaws.com",
|
"https://iam.us-gov.amazonaws.com",
|
||||||
"https://elasticloadbalancing.us-gov-west-1.amazonaws.com",
|
"https://elasticloadbalancing.us-gov-west-1.amazonaws.com",
|
||||||
|
"",
|
||||||
"https://dynamodb.us-gov-west-1.amazonaws.com",
|
"https://dynamodb.us-gov-west-1.amazonaws.com",
|
||||||
ServiceInfo{"https://monitoring.us-gov-west-1.amazonaws.com", V2Signature},
|
ServiceInfo{"https://monitoring.us-gov-west-1.amazonaws.com", V2Signature},
|
||||||
"https://autoscaling.us-gov-west-1.amazonaws.com",
|
"https://autoscaling.us-gov-west-1.amazonaws.com",
|
||||||
|
@ -36,6 +37,7 @@ var USEast = Region{
|
||||||
"https://email.us-east-1.amazonaws.com",
|
"https://email.us-east-1.amazonaws.com",
|
||||||
"https://iam.amazonaws.com",
|
"https://iam.amazonaws.com",
|
||||||
"https://elasticloadbalancing.us-east-1.amazonaws.com",
|
"https://elasticloadbalancing.us-east-1.amazonaws.com",
|
||||||
|
"https://kms.us-east-1.amazonaws.com",
|
||||||
"https://dynamodb.us-east-1.amazonaws.com",
|
"https://dynamodb.us-east-1.amazonaws.com",
|
||||||
ServiceInfo{"https://monitoring.us-east-1.amazonaws.com", V2Signature},
|
ServiceInfo{"https://monitoring.us-east-1.amazonaws.com", V2Signature},
|
||||||
"https://autoscaling.us-east-1.amazonaws.com",
|
"https://autoscaling.us-east-1.amazonaws.com",
|
||||||
|
@ -59,6 +61,7 @@ var USWest = Region{
|
||||||
"",
|
"",
|
||||||
"https://iam.amazonaws.com",
|
"https://iam.amazonaws.com",
|
||||||
"https://elasticloadbalancing.us-west-1.amazonaws.com",
|
"https://elasticloadbalancing.us-west-1.amazonaws.com",
|
||||||
|
"https://kms.us-west-1.amazonaws.com",
|
||||||
"https://dynamodb.us-west-1.amazonaws.com",
|
"https://dynamodb.us-west-1.amazonaws.com",
|
||||||
ServiceInfo{"https://monitoring.us-west-1.amazonaws.com", V2Signature},
|
ServiceInfo{"https://monitoring.us-west-1.amazonaws.com", V2Signature},
|
||||||
"https://autoscaling.us-west-1.amazonaws.com",
|
"https://autoscaling.us-west-1.amazonaws.com",
|
||||||
|
@ -82,6 +85,7 @@ var USWest2 = Region{
|
||||||
"https://email.us-west-2.amazonaws.com",
|
"https://email.us-west-2.amazonaws.com",
|
||||||
"https://iam.amazonaws.com",
|
"https://iam.amazonaws.com",
|
||||||
"https://elasticloadbalancing.us-west-2.amazonaws.com",
|
"https://elasticloadbalancing.us-west-2.amazonaws.com",
|
||||||
|
"https://kms.us-west-2.amazonaws.com",
|
||||||
"https://dynamodb.us-west-2.amazonaws.com",
|
"https://dynamodb.us-west-2.amazonaws.com",
|
||||||
ServiceInfo{"https://monitoring.us-west-2.amazonaws.com", V2Signature},
|
ServiceInfo{"https://monitoring.us-west-2.amazonaws.com", V2Signature},
|
||||||
"https://autoscaling.us-west-2.amazonaws.com",
|
"https://autoscaling.us-west-2.amazonaws.com",
|
||||||
|
@ -105,6 +109,7 @@ var EUWest = Region{
|
||||||
"https://email.eu-west-1.amazonaws.com",
|
"https://email.eu-west-1.amazonaws.com",
|
||||||
"https://iam.amazonaws.com",
|
"https://iam.amazonaws.com",
|
||||||
"https://elasticloadbalancing.eu-west-1.amazonaws.com",
|
"https://elasticloadbalancing.eu-west-1.amazonaws.com",
|
||||||
|
"https://kms.eu-west-1.amazonaws.com",
|
||||||
"https://dynamodb.eu-west-1.amazonaws.com",
|
"https://dynamodb.eu-west-1.amazonaws.com",
|
||||||
ServiceInfo{"https://monitoring.eu-west-1.amazonaws.com", V2Signature},
|
ServiceInfo{"https://monitoring.eu-west-1.amazonaws.com", V2Signature},
|
||||||
"https://autoscaling.eu-west-1.amazonaws.com",
|
"https://autoscaling.eu-west-1.amazonaws.com",
|
||||||
|
@ -128,6 +133,7 @@ var EUCentral = Region{
|
||||||
"",
|
"",
|
||||||
"https://iam.amazonaws.com",
|
"https://iam.amazonaws.com",
|
||||||
"https://elasticloadbalancing.eu-central-1.amazonaws.com",
|
"https://elasticloadbalancing.eu-central-1.amazonaws.com",
|
||||||
|
"https://kms.eu-central-1.amazonaws.com",
|
||||||
"https://dynamodb.eu-central-1.amazonaws.com",
|
"https://dynamodb.eu-central-1.amazonaws.com",
|
||||||
ServiceInfo{"https://monitoring.eu-central-1.amazonaws.com", V2Signature},
|
ServiceInfo{"https://monitoring.eu-central-1.amazonaws.com", V2Signature},
|
||||||
"https://autoscaling.eu-central-1.amazonaws.com",
|
"https://autoscaling.eu-central-1.amazonaws.com",
|
||||||
|
@ -151,6 +157,7 @@ var APSoutheast = Region{
|
||||||
"",
|
"",
|
||||||
"https://iam.amazonaws.com",
|
"https://iam.amazonaws.com",
|
||||||
"https://elasticloadbalancing.ap-southeast-1.amazonaws.com",
|
"https://elasticloadbalancing.ap-southeast-1.amazonaws.com",
|
||||||
|
"https://kms.ap-southeast-1.amazonaws.com",
|
||||||
"https://dynamodb.ap-southeast-1.amazonaws.com",
|
"https://dynamodb.ap-southeast-1.amazonaws.com",
|
||||||
ServiceInfo{"https://monitoring.ap-southeast-1.amazonaws.com", V2Signature},
|
ServiceInfo{"https://monitoring.ap-southeast-1.amazonaws.com", V2Signature},
|
||||||
"https://autoscaling.ap-southeast-1.amazonaws.com",
|
"https://autoscaling.ap-southeast-1.amazonaws.com",
|
||||||
|
@ -174,6 +181,7 @@ var APSoutheast2 = Region{
|
||||||
"",
|
"",
|
||||||
"https://iam.amazonaws.com",
|
"https://iam.amazonaws.com",
|
||||||
"https://elasticloadbalancing.ap-southeast-2.amazonaws.com",
|
"https://elasticloadbalancing.ap-southeast-2.amazonaws.com",
|
||||||
|
"https://kms.ap-southeast-2.amazonaws.com",
|
||||||
"https://dynamodb.ap-southeast-2.amazonaws.com",
|
"https://dynamodb.ap-southeast-2.amazonaws.com",
|
||||||
ServiceInfo{"https://monitoring.ap-southeast-2.amazonaws.com", V2Signature},
|
ServiceInfo{"https://monitoring.ap-southeast-2.amazonaws.com", V2Signature},
|
||||||
"https://autoscaling.ap-southeast-2.amazonaws.com",
|
"https://autoscaling.ap-southeast-2.amazonaws.com",
|
||||||
|
@ -197,6 +205,7 @@ var APNortheast = Region{
|
||||||
"",
|
"",
|
||||||
"https://iam.amazonaws.com",
|
"https://iam.amazonaws.com",
|
||||||
"https://elasticloadbalancing.ap-northeast-1.amazonaws.com",
|
"https://elasticloadbalancing.ap-northeast-1.amazonaws.com",
|
||||||
|
"https://kms.ap-northeast-1.amazonaws.com",
|
||||||
"https://dynamodb.ap-northeast-1.amazonaws.com",
|
"https://dynamodb.ap-northeast-1.amazonaws.com",
|
||||||
ServiceInfo{"https://monitoring.ap-northeast-1.amazonaws.com", V2Signature},
|
ServiceInfo{"https://monitoring.ap-northeast-1.amazonaws.com", V2Signature},
|
||||||
"https://autoscaling.ap-northeast-1.amazonaws.com",
|
"https://autoscaling.ap-northeast-1.amazonaws.com",
|
||||||
|
@ -220,6 +229,7 @@ var SAEast = Region{
|
||||||
"",
|
"",
|
||||||
"https://iam.amazonaws.com",
|
"https://iam.amazonaws.com",
|
||||||
"https://elasticloadbalancing.sa-east-1.amazonaws.com",
|
"https://elasticloadbalancing.sa-east-1.amazonaws.com",
|
||||||
|
"https://kms.sa-east-1.amazonaws.com",
|
||||||
"https://dynamodb.sa-east-1.amazonaws.com",
|
"https://dynamodb.sa-east-1.amazonaws.com",
|
||||||
ServiceInfo{"https://monitoring.sa-east-1.amazonaws.com", V2Signature},
|
ServiceInfo{"https://monitoring.sa-east-1.amazonaws.com", V2Signature},
|
||||||
"https://autoscaling.sa-east-1.amazonaws.com",
|
"https://autoscaling.sa-east-1.amazonaws.com",
|
||||||
|
@ -229,3 +239,27 @@ var SAEast = Region{
|
||||||
"https://cloudformation.sa-east-1.amazonaws.com",
|
"https://cloudformation.sa-east-1.amazonaws.com",
|
||||||
"https://elasticache.sa-east-1.amazonaws.com",
|
"https://elasticache.sa-east-1.amazonaws.com",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var CNNorth1 = Region{
|
||||||
|
"cn-north-1",
|
||||||
|
"https://ec2.cn-north-1.amazonaws.com.cn",
|
||||||
|
"https://s3.cn-north-1.amazonaws.com.cn",
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
"",
|
||||||
|
"https://sns.cn-north-1.amazonaws.com.cn",
|
||||||
|
"https://sqs.cn-north-1.amazonaws.com.cn",
|
||||||
|
"",
|
||||||
|
"https://iam.cn-north-1.amazonaws.com.cn",
|
||||||
|
"https://elasticloadbalancing.cn-north-1.amazonaws.com.cn",
|
||||||
|
"",
|
||||||
|
"https://dynamodb.cn-north-1.amazonaws.com.cn",
|
||||||
|
ServiceInfo{"https://monitoring.cn-north-1.amazonaws.com.cn", V4Signature},
|
||||||
|
"https://autoscaling.cn-north-1.amazonaws.com.cn",
|
||||||
|
ServiceInfo{"https://rds.cn-north-1.amazonaws.com.cn", V4Signature},
|
||||||
|
"",
|
||||||
|
"https://sts.cn-north-1.amazonaws.com.cn",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
}
|
||||||
|
|
25
Godeps/_workspace/src/github.com/AdRoll/goamz/s3/s3.go
generated
vendored
25
Godeps/_workspace/src/github.com/AdRoll/goamz/s3/s3.go
generated
vendored
|
@ -25,6 +25,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -70,9 +71,8 @@ type Options struct {
|
||||||
ContentMD5 string
|
ContentMD5 string
|
||||||
ContentDisposition string
|
ContentDisposition string
|
||||||
Range string
|
Range string
|
||||||
|
StorageClass StorageClass
|
||||||
// What else?
|
// What else?
|
||||||
//// The following become headers so they are []strings rather than strings... I think
|
|
||||||
// x-amz-storage-class []string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type CopyOptions struct {
|
type CopyOptions struct {
|
||||||
|
@ -96,7 +96,7 @@ 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, 0, aws.V2Signature}
|
return &S3{auth, region, 0, 0, aws.V2Signature, 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bucket returns a Bucket with the given name.
|
// Bucket returns a Bucket with the given name.
|
||||||
|
@ -164,6 +164,13 @@ const (
|
||||||
BucketOwnerFull = ACL("bucket-owner-full-control")
|
BucketOwnerFull = ACL("bucket-owner-full-control")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type StorageClass string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ReducedRedundancy = StorageClass("REDUCED_REDUNDANCY")
|
||||||
|
StandardStorage = StorageClass("STANDARD")
|
||||||
|
)
|
||||||
|
|
||||||
// PutBucket creates a new bucket.
|
// PutBucket creates a new bucket.
|
||||||
//
|
//
|
||||||
// See http://goo.gl/ndjnR for details.
|
// See http://goo.gl/ndjnR for details.
|
||||||
|
@ -401,6 +408,10 @@ func (o Options) addHeaders(headers map[string][]string) {
|
||||||
if len(o.ContentDisposition) != 0 {
|
if len(o.ContentDisposition) != 0 {
|
||||||
headers["Content-Disposition"] = []string{o.ContentDisposition}
|
headers["Content-Disposition"] = []string{o.ContentDisposition}
|
||||||
}
|
}
|
||||||
|
if len(o.StorageClass) != 0 {
|
||||||
|
headers["x-amz-storage-class"] = []string{string(o.StorageClass)}
|
||||||
|
|
||||||
|
}
|
||||||
for k, v := range o.Meta {
|
for k, v := range o.Meta {
|
||||||
headers["x-amz-meta-"+k] = v
|
headers["x-amz-meta-"+k] = v
|
||||||
}
|
}
|
||||||
|
@ -816,8 +827,8 @@ func (b *Bucket) SignedURLWithMethod(method, path string, expires time.Time, par
|
||||||
// UploadSignedURL returns a signed URL that allows anyone holding the URL
|
// UploadSignedURL returns a signed URL that allows anyone holding the URL
|
||||||
// to upload the object at path. The signature is valid until expires.
|
// to upload the object at path. The signature is valid until expires.
|
||||||
// contenttype is a string like image/png
|
// contenttype is a string like image/png
|
||||||
// path is the resource name in s3 terminalogy like images/ali.png [obviously exclusing the bucket name itself]
|
// name is the resource name in s3 terminology like images/ali.png [obviously excluding the bucket name itself]
|
||||||
func (b *Bucket) UploadSignedURL(path, method, content_type string, expires time.Time) string {
|
func (b *Bucket) UploadSignedURL(name, method, content_type string, expires time.Time) string {
|
||||||
expire_date := expires.Unix()
|
expire_date := expires.Unix()
|
||||||
if method != "POST" {
|
if method != "POST" {
|
||||||
method = "PUT"
|
method = "PUT"
|
||||||
|
@ -830,7 +841,7 @@ func (b *Bucket) UploadSignedURL(path, method, content_type string, expires time
|
||||||
tokenData = "x-amz-security-token:" + a.Token() + "\n"
|
tokenData = "x-amz-security-token:" + a.Token() + "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
stringToSign := method + "\n\n" + content_type + "\n" + strconv.FormatInt(expire_date, 10) + "\n" + tokenData + "/" + b.Name + "/" + path
|
stringToSign := method + "\n\n" + content_type + "\n" + strconv.FormatInt(expire_date, 10) + "\n" + tokenData + "/" + path.Join(b.Name, name)
|
||||||
secretKey := a.SecretKey
|
secretKey := a.SecretKey
|
||||||
accessId := a.AccessKey
|
accessId := a.AccessKey
|
||||||
mac := hmac.New(sha1.New, []byte(secretKey))
|
mac := hmac.New(sha1.New, []byte(secretKey))
|
||||||
|
@ -844,7 +855,7 @@ func (b *Bucket) UploadSignedURL(path, method, content_type string, expires time
|
||||||
log.Println("ERROR sining url for S3 upload", err)
|
log.Println("ERROR sining url for S3 upload", err)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
signedurl.Path += path
|
signedurl.Path = name
|
||||||
params := url.Values{}
|
params := url.Values{}
|
||||||
params.Add("AWSAccessKeyId", accessId)
|
params.Add("AWSAccessKeyId", accessId)
|
||||||
params.Add("Expires", strconv.FormatInt(expire_date, 10))
|
params.Add("Expires", strconv.FormatInt(expire_date, 10))
|
||||||
|
|
16
Godeps/_workspace/src/github.com/AdRoll/goamz/s3/s3_test.go
generated
vendored
16
Godeps/_workspace/src/github.com/AdRoll/goamz/s3/s3_test.go
generated
vendored
|
@ -230,6 +230,22 @@ func (s *S) TestPutObject(c *check.C) {
|
||||||
c.Assert(req.Header["X-Amz-Acl"], check.DeepEquals, []string{"private"})
|
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
|
// PutCopy docs: http://goo.gl/mhEHtA
|
||||||
func (s *S) TestPutCopy(c *check.C) {
|
func (s *S) TestPutCopy(c *check.C) {
|
||||||
testServer.Response(200, nil, PutCopyResultDump)
|
testServer.Response(200, nil, PutCopyResultDump)
|
||||||
|
|
196
Godeps/_workspace/src/github.com/AdRoll/goamz/s3/s3test/server.go
generated
vendored
196
Godeps/_workspace/src/github.com/AdRoll/goamz/s3/s3test/server.go
generated
vendored
|
@ -11,6 +11,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -51,6 +52,10 @@ type Config struct {
|
||||||
// all other regions.
|
// all other regions.
|
||||||
// http://docs.amazonwebservices.com/AmazonS3/latest/API/ErrorResponses.html
|
// http://docs.amazonwebservices.com/AmazonS3/latest/API/ErrorResponses.html
|
||||||
Send409Conflict bool
|
Send409Conflict bool
|
||||||
|
|
||||||
|
// Address on which to listen. By default, a random port is assigned by the
|
||||||
|
// operating system and the server listens on localhost.
|
||||||
|
ListenAddress string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) send409Conflict() bool {
|
func (c *Config) send409Conflict() bool {
|
||||||
|
@ -76,6 +81,7 @@ type bucket struct {
|
||||||
acl s3.ACL
|
acl s3.ACL
|
||||||
ctime time.Time
|
ctime time.Time
|
||||||
objects map[string]*object
|
objects map[string]*object
|
||||||
|
multipartUploads map[string][]*multipartUploadPart
|
||||||
}
|
}
|
||||||
|
|
||||||
type object struct {
|
type object struct {
|
||||||
|
@ -86,6 +92,12 @@ type object struct {
|
||||||
data []byte
|
data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type multipartUploadPart struct {
|
||||||
|
data []byte
|
||||||
|
etag string
|
||||||
|
lastModified time.Time
|
||||||
|
}
|
||||||
|
|
||||||
// A resource encapsulates the subject of an HTTP request.
|
// A resource encapsulates the subject of an HTTP request.
|
||||||
// The resource referred to may or may not exist
|
// The resource referred to may or may not exist
|
||||||
// when the request is made.
|
// when the request is made.
|
||||||
|
@ -97,7 +109,13 @@ type resource interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(config *Config) (*Server, error) {
|
func NewServer(config *Config) (*Server, error) {
|
||||||
l, err := net.Listen("tcp", "localhost:0")
|
listenAddress := "localhost:0"
|
||||||
|
|
||||||
|
if config != nil && config.ListenAddress != "" {
|
||||||
|
listenAddress = config.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)
|
||||||
}
|
}
|
||||||
|
@ -217,10 +235,8 @@ var unimplementedBucketResourceNames = map[string]bool{
|
||||||
}
|
}
|
||||||
|
|
||||||
var unimplementedObjectResourceNames = map[string]bool{
|
var unimplementedObjectResourceNames = map[string]bool{
|
||||||
"uploadId": true,
|
|
||||||
"acl": true,
|
"acl": true,
|
||||||
"torrent": true,
|
"torrent": true,
|
||||||
"uploads": true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var pathRegexp = regexp.MustCompile("/(([^/]+)(/(.*))?)?")
|
var pathRegexp = regexp.MustCompile("/(([^/]+)(/(.*))?)?")
|
||||||
|
@ -421,6 +437,7 @@ func (r bucketResource) put(a *action) interface{} {
|
||||||
name: r.name,
|
name: r.name,
|
||||||
// TODO default acl
|
// TODO default acl
|
||||||
objects: make(map[string]*object),
|
objects: make(map[string]*object),
|
||||||
|
multipartUploads: make(map[string][]*multipartUploadPart),
|
||||||
}
|
}
|
||||||
a.srv.buckets[r.name] = r.bucket
|
a.srv.buckets[r.name] = r.bucket
|
||||||
created = true
|
created = true
|
||||||
|
@ -615,12 +632,29 @@ 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
|
||||||
|
|
||||||
// TODO is this correct, or should we erase all previous metadata?
|
uploadId := a.req.URL.Query().Get("uploadId")
|
||||||
obj := objr.object
|
|
||||||
if obj == nil {
|
// Check that the upload ID is valid if this is a multipart upload
|
||||||
obj = &object{
|
if uploadId != "" {
|
||||||
name: objr.name,
|
if _, ok := objr.bucket.multipartUploads[uploadId]; !ok {
|
||||||
meta: make(http.Header),
|
fatalf(404, "NoSuchUpload", "The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
partNumberStr := a.req.URL.Query().Get("partNumber")
|
||||||
|
|
||||||
|
if partNumberStr == "" {
|
||||||
|
fatalf(400, "InvalidRequest", "Missing partNumber parameter")
|
||||||
|
}
|
||||||
|
|
||||||
|
partNumber, err := strconv.ParseUint(partNumberStr, 10, 32)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fatalf(400, "InvalidRequest", "partNumber is not a number")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parts are 1-indexed for multipart uploads
|
||||||
|
if uint(partNumber)-1 != uint(len(objr.bucket.multipartUploads[uploadId])) {
|
||||||
|
fatalf(400, "InvalidRequest", "Invalid part number")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -646,6 +680,22 @@ func (objr objectResource) put(a *action) interface{} {
|
||||||
fatalf(400, "IncompleteBody", "You did not provide the number of bytes specified by the Content-Length HTTP header")
|
fatalf(400, "IncompleteBody", "You did not provide the number of bytes specified by the Content-Length HTTP header")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
etag := fmt.Sprintf("\"%x\"", gotHash)
|
||||||
|
|
||||||
|
a.w.Header().Add("ETag", etag)
|
||||||
|
|
||||||
|
if uploadId == "" {
|
||||||
|
// For traditional uploads
|
||||||
|
|
||||||
|
// TODO is this correct, or should we erase all previous metadata?
|
||||||
|
obj := objr.object
|
||||||
|
if obj == nil {
|
||||||
|
obj = &object{
|
||||||
|
name: objr.name,
|
||||||
|
meta: make(http.Header),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// PUT request has been successful - save data and metadata
|
// PUT request has been successful - save data and metadata
|
||||||
for key, values := range a.req.Header {
|
for key, values := range a.req.Header {
|
||||||
key = http.CanonicalHeaderKey(key)
|
key = http.CanonicalHeaderKey(key)
|
||||||
|
@ -657,15 +707,143 @@ func (objr objectResource) put(a *action) interface{} {
|
||||||
obj.checksum = gotHash
|
obj.checksum = gotHash
|
||||||
obj.mtime = time.Now()
|
obj.mtime = time.Now()
|
||||||
objr.bucket.objects[objr.name] = obj
|
objr.bucket.objects[objr.name] = obj
|
||||||
|
} else {
|
||||||
|
// For multipart commit
|
||||||
|
|
||||||
|
parts := objr.bucket.multipartUploads[uploadId]
|
||||||
|
part := &multipartUploadPart{
|
||||||
|
data,
|
||||||
|
etag,
|
||||||
|
time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
objr.bucket.multipartUploads[uploadId] = append(parts, part)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (objr objectResource) delete(a *action) interface{} {
|
func (objr objectResource) delete(a *action) interface{} {
|
||||||
|
uploadId := a.req.URL.Query().Get("uploadId")
|
||||||
|
|
||||||
|
if uploadId == "" {
|
||||||
|
// Traditional object delete
|
||||||
delete(objr.bucket.objects, objr.name)
|
delete(objr.bucket.objects, objr.name)
|
||||||
|
} else {
|
||||||
|
// Multipart commit abort
|
||||||
|
_, ok := objr.bucket.multipartUploads[uploadId]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
fatalf(404, "NoSuchUpload", "The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(objr.bucket.multipartUploads, uploadId)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (objr objectResource) post(a *action) interface{} {
|
func (objr objectResource) post(a *action) interface{} {
|
||||||
|
// Check if we're initializing a multipart upload
|
||||||
|
if _, ok := a.req.URL.Query()["uploads"]; ok {
|
||||||
|
type multipartInitResponse struct {
|
||||||
|
XMLName struct{} `xml:"InitiateMultipartUploadResult"`
|
||||||
|
Bucket string
|
||||||
|
Key string
|
||||||
|
UploadId string
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadId := strconv.FormatInt(rand.Int63(), 16)
|
||||||
|
|
||||||
|
objr.bucket.multipartUploads[uploadId] = []*multipartUploadPart{}
|
||||||
|
|
||||||
|
return &multipartInitResponse{
|
||||||
|
Bucket: objr.bucket.name,
|
||||||
|
Key: objr.name,
|
||||||
|
UploadId: uploadId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we're completing a multipart upload
|
||||||
|
if uploadId := a.req.URL.Query().Get("uploadId"); uploadId != "" {
|
||||||
|
type multipartCompleteRequestPart struct {
|
||||||
|
XMLName struct{} `xml:"Part"`
|
||||||
|
PartNumber uint
|
||||||
|
ETag string
|
||||||
|
}
|
||||||
|
|
||||||
|
type multipartCompleteRequest struct {
|
||||||
|
XMLName struct{} `xml:"CompleteMultipartUpload"`
|
||||||
|
Part []multipartCompleteRequestPart
|
||||||
|
}
|
||||||
|
|
||||||
|
type multipartCompleteResponse struct {
|
||||||
|
XMLName struct{} `xml:"CompleteMultipartUploadResult"`
|
||||||
|
Location string
|
||||||
|
Bucket string
|
||||||
|
Key string
|
||||||
|
ETag string
|
||||||
|
}
|
||||||
|
|
||||||
|
parts, ok := objr.bucket.multipartUploads[uploadId]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
fatalf(404, "NoSuchUpload", "The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &multipartCompleteRequest{}
|
||||||
|
|
||||||
|
if err := xml.NewDecoder(a.req.Body).Decode(req); err != nil {
|
||||||
|
fatalf(400, "InvalidRequest", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Part) != len(parts) {
|
||||||
|
fatalf(400, "InvalidRequest", fmt.Sprintf("Number of parts does not match: expected %d, received %d", len(parts), len(req.Part)))
|
||||||
|
}
|
||||||
|
|
||||||
|
sum := md5.New()
|
||||||
|
data := &bytes.Buffer{}
|
||||||
|
w := io.MultiWriter(sum, data)
|
||||||
|
|
||||||
|
for i, p := range parts {
|
||||||
|
reqPart := req.Part[i]
|
||||||
|
|
||||||
|
if reqPart.PartNumber != uint(1+i) {
|
||||||
|
fatalf(400, "InvalidRequest", "Bad part number")
|
||||||
|
}
|
||||||
|
|
||||||
|
if reqPart.ETag != p.etag {
|
||||||
|
fatalf(400, "InvalidRequest", fmt.Sprintf("Invalid etag for part %d", reqPart.PartNumber))
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(p.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(objr.bucket.multipartUploads, uploadId)
|
||||||
|
|
||||||
|
obj := objr.object
|
||||||
|
|
||||||
|
if obj == nil {
|
||||||
|
obj = &object{
|
||||||
|
name: objr.name,
|
||||||
|
meta: make(http.Header),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.data = data.Bytes()
|
||||||
|
obj.checksum = sum.Sum(nil)
|
||||||
|
obj.mtime = time.Now()
|
||||||
|
objr.bucket.objects[objr.name] = obj
|
||||||
|
|
||||||
|
objectLocation := fmt.Sprintf("http://%s/%s/%s", a.srv.listener.Addr().String(), objr.bucket.name, objr.name)
|
||||||
|
|
||||||
|
return &multipartCompleteResponse{
|
||||||
|
Location: objectLocation,
|
||||||
|
Bucket: objr.bucket.name,
|
||||||
|
Key: objr.name,
|
||||||
|
ETag: uploadId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fatalf(400, "MethodNotAllowed", "The specified method is not allowed against this resource")
|
fatalf(400, "MethodNotAllowed", "The specified method is not allowed against this resource")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue