mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2024-01-19 02:48:24 +00:00
add ownership rest apis (#3765)
This commit is contained in:
parent
6fa3d0cc46
commit
e9584d9661
|
@ -16,6 +16,8 @@ import (
|
||||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var IdentityAnonymous *Identity
|
||||||
|
|
||||||
type Action string
|
type Action string
|
||||||
|
|
||||||
type Iam interface {
|
type Iam interface {
|
||||||
|
@ -32,10 +34,15 @@ type IdentityAccessManagement struct {
|
||||||
|
|
||||||
type Identity struct {
|
type Identity struct {
|
||||||
Name string
|
Name string
|
||||||
|
AccountId string
|
||||||
Credentials []*Credential
|
Credentials []*Credential
|
||||||
Actions []Action
|
Actions []Action
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Identity) isAnonymous() bool {
|
||||||
|
return i.Name == AccountAnonymous.Name
|
||||||
|
}
|
||||||
|
|
||||||
type Credential struct {
|
type Credential struct {
|
||||||
AccessKey string
|
AccessKey string
|
||||||
SecretKey string
|
SecretKey string
|
||||||
|
@ -125,9 +132,23 @@ func (iam *IdentityAccessManagement) loadS3ApiConfiguration(config *iam_pb.S3Api
|
||||||
for _, ident := range config.Identities {
|
for _, ident := range config.Identities {
|
||||||
t := &Identity{
|
t := &Identity{
|
||||||
Name: ident.Name,
|
Name: ident.Name,
|
||||||
|
AccountId: AccountAdmin.Id,
|
||||||
Credentials: nil,
|
Credentials: nil,
|
||||||
Actions: nil,
|
Actions: nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ident.Name == AccountAnonymous.Name {
|
||||||
|
if ident.AccountId != "" && ident.AccountId != AccountAnonymous.Id {
|
||||||
|
glog.Warningf("anonymous identity is associated with a non-anonymous account ID, the association is invalid")
|
||||||
|
}
|
||||||
|
t.AccountId = AccountAnonymous.Id
|
||||||
|
IdentityAnonymous = t
|
||||||
|
} else {
|
||||||
|
if len(ident.AccountId) > 0 {
|
||||||
|
t.AccountId = ident.AccountId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, action := range ident.Actions {
|
for _, action := range ident.Actions {
|
||||||
t.Actions = append(t.Actions, Action(action))
|
t.Actions = append(t.Actions, Action(action))
|
||||||
}
|
}
|
||||||
|
@ -139,6 +160,13 @@ func (iam *IdentityAccessManagement) loadS3ApiConfiguration(config *iam_pb.S3Api
|
||||||
}
|
}
|
||||||
identities = append(identities, t)
|
identities = append(identities, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if IdentityAnonymous == nil {
|
||||||
|
IdentityAnonymous = &Identity{
|
||||||
|
Name: AccountAnonymous.Name,
|
||||||
|
AccountId: AccountAnonymous.Id,
|
||||||
|
}
|
||||||
|
}
|
||||||
iam.m.Lock()
|
iam.m.Lock()
|
||||||
// atomically switch
|
// atomically switch
|
||||||
iam.identities = identities
|
iam.identities = identities
|
||||||
|
@ -173,7 +201,7 @@ func (iam *IdentityAccessManagement) lookupAnonymous() (identity *Identity, foun
|
||||||
iam.m.RLock()
|
iam.m.RLock()
|
||||||
defer iam.m.RUnlock()
|
defer iam.m.RUnlock()
|
||||||
for _, ident := range iam.identities {
|
for _, ident := range iam.identities {
|
||||||
if ident.Name == "anonymous" {
|
if ident.isAnonymous() {
|
||||||
return ident, true
|
return ident, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -259,6 +287,9 @@ func (iam *IdentityAccessManagement) authRequest(r *http.Request, action Action)
|
||||||
return identity, s3err.ErrAccessDenied
|
return identity, s3err.ErrAccessDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !identity.isAnonymous() {
|
||||||
|
r.Header.Set(s3_constants.AmzAccountId, identity.AccountId)
|
||||||
|
}
|
||||||
return identity, s3err.ErrNone
|
return identity, s3err.ErrNone
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package s3api
|
||||||
import (
|
import (
|
||||||
. "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
. "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
jsonpb "google.golang.org/protobuf/encoding/protojson"
|
jsonpb "google.golang.org/protobuf/encoding/protojson"
|
||||||
|
@ -124,5 +125,98 @@ func TestCanDo(t *testing.T) {
|
||||||
}
|
}
|
||||||
assert.Equal(t, true, ident5.canDo(ACTION_READ, "special_bucket", "/a/b/c/d.txt"))
|
assert.Equal(t, true, ident5.canDo(ACTION_READ, "special_bucket", "/a/b/c/d.txt"))
|
||||||
assert.Equal(t, true, ident5.canDo(ACTION_WRITE, "special_bucket", "/a/b/c/d.txt"))
|
assert.Equal(t, true, ident5.canDo(ACTION_WRITE, "special_bucket", "/a/b/c/d.txt"))
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoadS3ApiConfigurationTestCase struct {
|
||||||
|
pbIdent *iam_pb.Identity
|
||||||
|
expectIdent *Identity
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadS3ApiConfiguration(t *testing.T) {
|
||||||
|
testCases := map[string]*LoadS3ApiConfigurationTestCase{
|
||||||
|
"notSpecifyAccountId": {
|
||||||
|
pbIdent: &iam_pb.Identity{
|
||||||
|
Name: "notSpecifyAccountId",
|
||||||
|
Actions: []string{
|
||||||
|
"Read",
|
||||||
|
"Write",
|
||||||
|
},
|
||||||
|
Credentials: []*iam_pb.Credential{
|
||||||
|
{
|
||||||
|
AccessKey: "some_access_key1",
|
||||||
|
SecretKey: "some_secret_key2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectIdent: &Identity{
|
||||||
|
Name: "notSpecifyAccountId",
|
||||||
|
AccountId: AccountAdmin.Id,
|
||||||
|
Actions: []Action{
|
||||||
|
"Read",
|
||||||
|
"Write",
|
||||||
|
},
|
||||||
|
Credentials: []*Credential{
|
||||||
|
{
|
||||||
|
AccessKey: "some_access_key1",
|
||||||
|
SecretKey: "some_secret_key2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"specifiedAccountID": {
|
||||||
|
pbIdent: &iam_pb.Identity{
|
||||||
|
Name: "specifiedAccountID",
|
||||||
|
AccountId: "specifiedAccountID",
|
||||||
|
Actions: []string{
|
||||||
|
"Read",
|
||||||
|
"Write",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectIdent: &Identity{
|
||||||
|
Name: "specifiedAccountID",
|
||||||
|
AccountId: "specifiedAccountID",
|
||||||
|
Actions: []Action{
|
||||||
|
"Read",
|
||||||
|
"Write",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"anonymous": {
|
||||||
|
pbIdent: &iam_pb.Identity{
|
||||||
|
Name: "anonymous",
|
||||||
|
Actions: []string{
|
||||||
|
"Read",
|
||||||
|
"Write",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectIdent: &Identity{
|
||||||
|
Name: "anonymous",
|
||||||
|
AccountId: "anonymous",
|
||||||
|
Actions: []Action{
|
||||||
|
"Read",
|
||||||
|
"Write",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &iam_pb.S3ApiConfiguration{
|
||||||
|
Identities: make([]*iam_pb.Identity, 0),
|
||||||
|
}
|
||||||
|
for _, v := range testCases {
|
||||||
|
config.Identities = append(config.Identities, v.pbIdent)
|
||||||
|
}
|
||||||
|
|
||||||
|
iam := IdentityAccessManagement{}
|
||||||
|
err := iam.loadS3ApiConfiguration(config)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ident := range iam.identities {
|
||||||
|
tc := testCases[ident.Name]
|
||||||
|
if !reflect.DeepEqual(ident, tc.expectIdent) {
|
||||||
|
t.Error("not expect")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,6 +91,22 @@ func (s3a *S3ApiServer) getEntry(parentDirectoryPath, entryName string) (entry *
|
||||||
return filer_pb.GetEntry(s3a, fullPath)
|
return filer_pb.GetEntry(s3a, fullPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s3a *S3ApiServer) updateEntry(parentDirectoryPath string, newEntry *filer_pb.Entry) error {
|
||||||
|
updateEntryRequest := &filer_pb.UpdateEntryRequest{
|
||||||
|
Directory: parentDirectoryPath,
|
||||||
|
Entry: newEntry,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s3a.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
||||||
|
err := filer_pb.UpdateEntry(client, updateEntryRequest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func objectKey(key *string) *string {
|
func objectKey(key *string) *string {
|
||||||
if strings.HasPrefix(*key, "/") {
|
if strings.HasPrefix(*key, "/") {
|
||||||
t := (*key)[1:]
|
t := (*key)[1:]
|
||||||
|
|
|
@ -43,6 +43,7 @@ const (
|
||||||
// Non-Standard S3 HTTP request constants
|
// Non-Standard S3 HTTP request constants
|
||||||
const (
|
const (
|
||||||
AmzIdentityId = "s3-identity-id"
|
AmzIdentityId = "s3-identity-id"
|
||||||
|
AmzAccountId = "s3-account-id"
|
||||||
AmzAuthType = "s3-auth-type"
|
AmzAuthType = "s3-auth-type"
|
||||||
AmzIsAdmin = "s3-is-admin" // only set to http request header as a context
|
AmzIsAdmin = "s3-is-admin" // only set to http request header as a context
|
||||||
)
|
)
|
||||||
|
|
28
weed/s3api/s3api_acp.go
Normal file
28
weed/s3api/s3api_acp.go
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package s3api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
||||||
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getAccountId(r *http.Request) string {
|
||||||
|
id := r.Header.Get(s3_constants.AmzAccountId)
|
||||||
|
if len(id) == 0 {
|
||||||
|
return AccountAnonymous.Id
|
||||||
|
} else {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s3a *S3ApiServer) checkAccessByOwnership(r *http.Request, bucket string) s3err.ErrorCode {
|
||||||
|
metadata, errCode := s3a.bucketRegistry.GetBucketMetadata(bucket)
|
||||||
|
if errCode != s3err.ErrNone {
|
||||||
|
return errCode
|
||||||
|
}
|
||||||
|
accountId := getAccountId(r)
|
||||||
|
if accountId == AccountAdmin.Id || accountId == *metadata.Owner.ID {
|
||||||
|
return s3err.ErrNone
|
||||||
|
}
|
||||||
|
return s3err.ErrAccessDenied
|
||||||
|
}
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil"
|
||||||
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
@ -343,3 +345,159 @@ func (s3a *S3ApiServer) GetBucketLocationHandler(w http.ResponseWriter, r *http.
|
||||||
func (s3a *S3ApiServer) GetBucketRequestPaymentHandler(w http.ResponseWriter, r *http.Request) {
|
func (s3a *S3ApiServer) GetBucketRequestPaymentHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
writeSuccessResponseXML(w, r, RequestPaymentConfiguration{Payer: "BucketOwner"})
|
writeSuccessResponseXML(w, r, RequestPaymentConfiguration{Payer: "BucketOwner"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PutBucketOwnershipControls https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketOwnershipControls.html
|
||||||
|
func (s3a *S3ApiServer) PutBucketOwnershipControls(w http.ResponseWriter, r *http.Request) {
|
||||||
|
bucket, _ := s3_constants.GetBucketAndObject(r)
|
||||||
|
glog.V(3).Infof("PutBucketOwnershipControls %s", bucket)
|
||||||
|
|
||||||
|
errCode := s3a.checkAccessByOwnership(r, bucket)
|
||||||
|
if errCode != s3err.ErrNone {
|
||||||
|
s3err.WriteErrorResponse(w, r, errCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Body == nil || r.Body == http.NoBody {
|
||||||
|
s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var v s3.OwnershipControls
|
||||||
|
defer util.CloseRequest(r)
|
||||||
|
|
||||||
|
err := xmlutil.UnmarshalXML(&v, xml.NewDecoder(r.Body), "")
|
||||||
|
if err != nil {
|
||||||
|
s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(v.Rules) != 1 {
|
||||||
|
s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
printOwnership := true
|
||||||
|
ownership := *v.Rules[0].ObjectOwnership
|
||||||
|
switch ownership {
|
||||||
|
case s3_constants.OwnershipObjectWriter:
|
||||||
|
case s3_constants.OwnershipBucketOwnerPreferred:
|
||||||
|
case s3_constants.OwnershipBucketOwnerEnforced:
|
||||||
|
printOwnership = false
|
||||||
|
default:
|
||||||
|
s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bucketEntry, err := s3a.getEntry(s3a.option.BucketsPath, bucket)
|
||||||
|
if err != nil {
|
||||||
|
if err == filer_pb.ErrNotFound {
|
||||||
|
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucket)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
oldOwnership, ok := bucketEntry.Extended[s3_constants.ExtOwnershipKey]
|
||||||
|
if !ok || string(oldOwnership) != ownership {
|
||||||
|
if bucketEntry.Extended == nil {
|
||||||
|
bucketEntry.Extended = make(map[string][]byte)
|
||||||
|
}
|
||||||
|
bucketEntry.Extended[s3_constants.ExtOwnershipKey] = []byte(ownership)
|
||||||
|
err = s3a.updateEntry(s3a.option.BucketsPath, bucketEntry)
|
||||||
|
if err != nil {
|
||||||
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if printOwnership {
|
||||||
|
result := &s3.PutBucketOwnershipControlsInput{
|
||||||
|
OwnershipControls: &v,
|
||||||
|
}
|
||||||
|
s3err.WriteAwsXMLResponse(w, r, http.StatusOK, result)
|
||||||
|
} else {
|
||||||
|
writeSuccessResponseEmpty(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBucketOwnershipControls https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketOwnershipControls.html
|
||||||
|
func (s3a *S3ApiServer) GetBucketOwnershipControls(w http.ResponseWriter, r *http.Request) {
|
||||||
|
bucket, _ := s3_constants.GetBucketAndObject(r)
|
||||||
|
glog.V(3).Infof("GetBucketOwnershipControls %s", bucket)
|
||||||
|
|
||||||
|
errCode := s3a.checkAccessByOwnership(r, bucket)
|
||||||
|
if errCode != s3err.ErrNone {
|
||||||
|
s3err.WriteErrorResponse(w, r, errCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bucketEntry, err := s3a.getEntry(s3a.option.BucketsPath, bucket)
|
||||||
|
if err != nil {
|
||||||
|
if err == filer_pb.ErrNotFound {
|
||||||
|
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucket)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v, ok := bucketEntry.Extended[s3_constants.ExtOwnershipKey]
|
||||||
|
if !ok {
|
||||||
|
s3err.WriteErrorResponse(w, r, s3err.OwnershipControlsNotFoundError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ownership := string(v)
|
||||||
|
|
||||||
|
result := &s3.PutBucketOwnershipControlsInput{
|
||||||
|
OwnershipControls: &s3.OwnershipControls{
|
||||||
|
Rules: []*s3.OwnershipControlsRule{
|
||||||
|
{
|
||||||
|
ObjectOwnership: &ownership,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
s3err.WriteAwsXMLResponse(w, r, http.StatusOK, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteBucketOwnershipControls https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketOwnershipControls.html
|
||||||
|
func (s3a *S3ApiServer) DeleteBucketOwnershipControls(w http.ResponseWriter, r *http.Request) {
|
||||||
|
bucket, _ := s3_constants.GetBucketAndObject(r)
|
||||||
|
glog.V(3).Infof("PutBucketOwnershipControls %s", bucket)
|
||||||
|
|
||||||
|
errCode := s3a.checkAccessByOwnership(r, bucket)
|
||||||
|
if errCode != s3err.ErrNone {
|
||||||
|
s3err.WriteErrorResponse(w, r, errCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bucketEntry, err := s3a.getEntry(s3a.option.BucketsPath, bucket)
|
||||||
|
if err != nil {
|
||||||
|
if err == filer_pb.ErrNotFound {
|
||||||
|
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucket)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok := bucketEntry.Extended[s3_constants.ExtOwnershipKey]
|
||||||
|
if !ok {
|
||||||
|
s3err.WriteErrorResponse(w, r, s3err.OwnershipControlsNotFoundError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(bucketEntry.Extended, s3_constants.ExtOwnershipKey)
|
||||||
|
err = s3a.updateEntry(s3a.option.BucketsPath, bucketEntry)
|
||||||
|
if err != nil {
|
||||||
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
emptyOwnershipControls := &s3.OwnershipControls{
|
||||||
|
Rules: []*s3.OwnershipControlsRule{},
|
||||||
|
}
|
||||||
|
s3err.WriteAwsXMLResponse(w, r, http.StatusOK, emptyOwnershipControls)
|
||||||
|
}
|
||||||
|
|
|
@ -216,6 +216,14 @@ func (s3a *S3ApiServer) registerRouter(router *mux.Router) {
|
||||||
bucket.Methods("GET").HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.ListObjectsV2Handler, ACTION_LIST)), "LIST")).Queries("list-type", "2")
|
bucket.Methods("GET").HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.ListObjectsV2Handler, ACTION_LIST)), "LIST")).Queries("list-type", "2")
|
||||||
|
|
||||||
// buckets with query
|
// buckets with query
|
||||||
|
// PutBucketOwnershipControls
|
||||||
|
bucket.Methods("PUT").HandlerFunc(track(s3a.iam.Auth(s3a.PutBucketOwnershipControls, ACTION_ADMIN), "PUT")).Queries("ownershipControls", "")
|
||||||
|
|
||||||
|
//GetBucketOwnershipControls
|
||||||
|
bucket.Methods("GET").HandlerFunc(track(s3a.iam.Auth(s3a.GetBucketOwnershipControls, ACTION_READ), "GET")).Queries("ownershipControls", "")
|
||||||
|
|
||||||
|
//DeleteBucketOwnershipControls
|
||||||
|
bucket.Methods("DELETE").HandlerFunc(track(s3a.iam.Auth(s3a.DeleteBucketOwnershipControls, ACTION_ADMIN), "DELETE")).Queries("ownershipControls", "")
|
||||||
|
|
||||||
// raw buckets
|
// raw buckets
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -19,6 +20,16 @@ const (
|
||||||
MimeXML mimeType = "application/xml"
|
MimeXML mimeType = "application/xml"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func WriteAwsXMLResponse(w http.ResponseWriter, r *http.Request, statusCode int, result interface{}) {
|
||||||
|
var bytesBuffer bytes.Buffer
|
||||||
|
err := xmlutil.BuildXML(result, xml.NewEncoder(&bytesBuffer))
|
||||||
|
if err != nil {
|
||||||
|
WriteErrorResponse(w, r, ErrInternalError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
WriteResponse(w, r, statusCode, bytesBuffer.Bytes(), MimeXML)
|
||||||
|
}
|
||||||
|
|
||||||
func WriteXMLResponse(w http.ResponseWriter, r *http.Request, statusCode int, response interface{}) {
|
func WriteXMLResponse(w http.ResponseWriter, r *http.Request, statusCode int, response interface{}) {
|
||||||
WriteResponse(w, r, statusCode, EncodeXMLResponse(response), MimeXML)
|
WriteResponse(w, r, statusCode, EncodeXMLResponse(response), MimeXML)
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,6 +107,8 @@ const (
|
||||||
|
|
||||||
ErrTooManyRequest
|
ErrTooManyRequest
|
||||||
ErrRequestBytesExceed
|
ErrRequestBytesExceed
|
||||||
|
|
||||||
|
OwnershipControlsNotFoundError
|
||||||
)
|
)
|
||||||
|
|
||||||
// error code to APIError structure, these fields carry respective
|
// error code to APIError structure, these fields carry respective
|
||||||
|
@ -414,6 +416,12 @@ var errorCodeResponse = map[ErrorCode]APIError{
|
||||||
Description: "Simultaneous request bytes exceed limitations",
|
Description: "Simultaneous request bytes exceed limitations",
|
||||||
HTTPStatusCode: http.StatusTooManyRequests,
|
HTTPStatusCode: http.StatusTooManyRequests,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
OwnershipControlsNotFoundError: {
|
||||||
|
Code: "OwnershipControlsNotFoundError",
|
||||||
|
Description: "The bucket ownership controls were not found",
|
||||||
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAPIError provides API Error for input API error code.
|
// GetAPIError provides API Error for input API error code.
|
||||||
|
|
Loading…
Reference in a new issue