forked from TrueCloudLab/frostfs-s3-gw
[#196] Add bucket tagging
Signed-off-by: Denis Kirillov <denis@nspcc.ru>
This commit is contained in:
parent
16da1aba64
commit
987185b9e1
10 changed files with 229 additions and 76 deletions
|
@ -67,7 +67,10 @@ const (
|
||||||
ErrNoSuchUpload
|
ErrNoSuchUpload
|
||||||
ErrNoSuchVersion
|
ErrNoSuchVersion
|
||||||
ErrInvalidVersion
|
ErrInvalidVersion
|
||||||
ErrInvalidTag
|
ErrInvalidArgument
|
||||||
|
ErrInvalidTagKey
|
||||||
|
ErrInvalidTagValue
|
||||||
|
ErrInvalidTagsSizeExceed
|
||||||
ErrNotImplemented
|
ErrNotImplemented
|
||||||
ErrPreconditionFailed
|
ErrPreconditionFailed
|
||||||
ErrNotModified
|
ErrNotModified
|
||||||
|
@ -300,7 +303,6 @@ const (
|
||||||
ErrEvaluatorInvalidTimestampFormatPatternSymbol
|
ErrEvaluatorInvalidTimestampFormatPatternSymbol
|
||||||
ErrEvaluatorBindingDoesNotExist
|
ErrEvaluatorBindingDoesNotExist
|
||||||
ErrMissingHeaders
|
ErrMissingHeaders
|
||||||
ErrInvalidArgument
|
|
||||||
ErrInvalidColumnIndex
|
ErrInvalidColumnIndex
|
||||||
|
|
||||||
ErrAdminConfigNotificationTargetsFailed
|
ErrAdminConfigNotificationTargetsFailed
|
||||||
|
@ -537,10 +539,28 @@ var errorCodes = errorCodeMap{
|
||||||
Description: "Invalid version id specified",
|
Description: "Invalid version id specified",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
ErrInvalidTag: {
|
ErrInvalidArgument: {
|
||||||
ErrCode: ErrInvalidTag,
|
ErrCode: ErrInvalidArgument,
|
||||||
|
Code: "InvalidArgument",
|
||||||
|
Description: "The specified argument was invalid",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
ErrInvalidTagKey: {
|
||||||
|
ErrCode: ErrInvalidTagKey,
|
||||||
Code: "InvalidTag",
|
Code: "InvalidTag",
|
||||||
Description: "You have passed bad tag input - duplicate keys, key/values are too long, system tags were sent.",
|
Description: "The TagValue you have provided is invalid",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
ErrInvalidTagValue: {
|
||||||
|
ErrCode: ErrInvalidTagValue,
|
||||||
|
Code: "InvalidTag",
|
||||||
|
Description: "The TagKey you have provided is invalid",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
ErrInvalidTagsSizeExceed: {
|
||||||
|
ErrCode: ErrInvalidTagsSizeExceed,
|
||||||
|
Code: "BadRequest",
|
||||||
|
Description: "Object tags cannot be greater than 10",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
ErrNotImplemented: {
|
ErrNotImplemented: {
|
||||||
|
@ -1853,12 +1873,6 @@ var errorCodes = errorCodeMap{
|
||||||
Description: "Some headers in the query are missing from the file. Check the file and try again.",
|
Description: "Some headers in the query are missing from the file. Check the file and try again.",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
ErrInvalidArgument: {
|
|
||||||
ErrCode: ErrInvalidArgument,
|
|
||||||
Code: "InvalidArgument",
|
|
||||||
Description: "The specified argument was invalid.",
|
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
|
||||||
},
|
|
||||||
ErrInvalidColumnIndex: {
|
ErrInvalidColumnIndex: {
|
||||||
ErrCode: ErrInvalidColumnIndex,
|
ErrCode: ErrInvalidColumnIndex,
|
||||||
Code: "InvalidColumnIndex",
|
Code: "InvalidColumnIndex",
|
||||||
|
|
|
@ -19,10 +19,6 @@ func (h *handler) DeleteBucketEncryptionHandler(w http.ResponseWriter, r *http.R
|
||||||
h.logAndSendError(w, "not supported", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotSupported))
|
h.logAndSendError(w, "not supported", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotSupported))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) PutBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
h.logAndSendError(w, "not supported", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotSupported))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *handler) PutBucketNotificationHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) PutBucketNotificationHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
h.logAndSendError(w, "not supported", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotSupported))
|
h.logAndSendError(w, "not supported", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotSupported))
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl"
|
"github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl"
|
||||||
|
@ -31,6 +32,11 @@ type createBucketParams struct {
|
||||||
func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
var newEaclTable *eacl.Table
|
var newEaclTable *eacl.Table
|
||||||
reqInfo := api.GetReqInfo(r.Context())
|
reqInfo := api.GetReqInfo(r.Context())
|
||||||
|
tagSet, err := parseTaggingHeader(r.Header)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "could not parse tagging header", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if containsACLHeaders(r) {
|
if containsACLHeaders(r) {
|
||||||
objectACL, err := parseACLHeaders(r)
|
objectACL, err := parseACLHeaders(r)
|
||||||
|
@ -102,6 +108,13 @@ func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tagSet != nil {
|
||||||
|
if err = h.obj.PutObjectTagging(r.Context(), &layer.PutTaggingParams{ObjectInfo: info, TagSet: tagSet}); err != nil {
|
||||||
|
h.logAndSendError(w, "could not upload object tagging", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if newEaclTable != nil {
|
if newEaclTable != nil {
|
||||||
p := &layer.PutBucketACLParams{
|
p := &layer.PutBucketACLParams{
|
||||||
Name: reqInfo.BucketName,
|
Name: reqInfo.BucketName,
|
||||||
|
@ -129,6 +142,28 @@ func containsACLHeaders(r *http.Request) bool {
|
||||||
r.Header.Get(api.AmzGrantFullControl) != "" || r.Header.Get(api.AmzGrantWrite) != ""
|
r.Header.Get(api.AmzGrantFullControl) != "" || r.Header.Get(api.AmzGrantWrite) != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseTaggingHeader(header http.Header) (map[string]string, error) {
|
||||||
|
var tagSet map[string]string
|
||||||
|
if tagging := header.Get(api.AmzTagging); len(tagging) > 0 {
|
||||||
|
queries, err := url.ParseQuery(tagging)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.GetAPIError(errors.ErrInvalidArgument)
|
||||||
|
}
|
||||||
|
if len(queries) > maxTags {
|
||||||
|
return nil, errors.GetAPIError(errors.ErrInvalidTagsSizeExceed)
|
||||||
|
}
|
||||||
|
tagSet = make(map[string]string, len(queries))
|
||||||
|
for k, v := range queries {
|
||||||
|
tag := Tag{Key: k, Value: v[0]}
|
||||||
|
if err = checkTag(tag); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tagSet[tag.Key] = tag.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tagSet, nil
|
||||||
|
}
|
||||||
|
|
||||||
func parseMetadata(r *http.Request) map[string]string {
|
func parseMetadata(r *http.Request) map[string]string {
|
||||||
res := make(map[string]string)
|
res := make(map[string]string)
|
||||||
for k, v := range r.Header {
|
for k, v := range r.Header {
|
||||||
|
|
|
@ -2,7 +2,9 @@ package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
|
@ -14,6 +16,7 @@ import (
|
||||||
const (
|
const (
|
||||||
allowedTagChars = "+-=._:/@"
|
allowedTagChars = "+-=._:/@"
|
||||||
|
|
||||||
|
maxTags = 10
|
||||||
keyTagMaxLength = 128
|
keyTagMaxLength = 128
|
||||||
valueTagMaxLength = 256
|
valueTagMaxLength = 256
|
||||||
)
|
)
|
||||||
|
@ -21,14 +24,9 @@ const (
|
||||||
func (h *handler) PutObjectTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) PutObjectTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := api.GetReqInfo(r.Context())
|
reqInfo := api.GetReqInfo(r.Context())
|
||||||
|
|
||||||
tagging := new(Tagging)
|
tagSet, err := readTagSet(r.Body)
|
||||||
if err := xml.NewDecoder(r.Body).Decode(tagging); err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not decode body", reqInfo, errors.GetAPIError(errors.ErrMalformedXML))
|
h.logAndSendError(w, "could not read tag set", reqInfo, err)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := checkTagSet(tagging.TagSet); err != nil {
|
|
||||||
h.logAndSendError(w, "some tags are invalid", reqInfo, err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,11 +42,6 @@ func (h *handler) PutObjectTaggingHandler(w http.ResponseWriter, r *http.Request
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tagSet := make(map[string]string, len(tagging.TagSet))
|
|
||||||
for _, tag := range tagging.TagSet {
|
|
||||||
tagSet[tag.Key] = tag.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
p2 := &layer.PutTaggingParams{
|
p2 := &layer.PutTaggingParams{
|
||||||
ObjectInfo: objInfo,
|
ObjectInfo: objInfo,
|
||||||
TagSet: tagSet,
|
TagSet: tagSet,
|
||||||
|
@ -81,31 +74,12 @@ func (h *handler) GetObjectTaggingHandler(w http.ResponseWriter, r *http.Request
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tagging := &Tagging{}
|
|
||||||
for k, v := range tagSet {
|
|
||||||
tagging.TagSet = append(tagging.TagSet, Tag{Key: k, Value: v})
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set(api.AmzVersionID, objInfo.Version())
|
w.Header().Set(api.AmzVersionID, objInfo.Version())
|
||||||
if err = api.EncodeToResponse(w, tagging); err != nil {
|
if err = api.EncodeToResponse(w, encodeTagging(tagSet)); err != nil {
|
||||||
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkTagSet(tagSet []Tag) error {
|
|
||||||
if len(tagSet) > 10 {
|
|
||||||
return errors.GetAPIError(errors.ErrInvalidTag)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tag := range tagSet {
|
|
||||||
if err := checkTag(tag); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *handler) DeleteObjectTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) DeleteObjectTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := api.GetReqInfo(r.Context())
|
reqInfo := api.GetReqInfo(r.Context())
|
||||||
|
|
||||||
|
@ -128,34 +102,114 @@ func (h *handler) DeleteObjectTaggingHandler(w http.ResponseWriter, r *http.Requ
|
||||||
w.WriteHeader(http.StatusNoContent)
|
w.WriteHeader(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *handler) PutBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
reqInfo := api.GetReqInfo(r.Context())
|
||||||
|
|
||||||
|
tagSet, err := readTagSet(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "could not read tag set", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.obj.PutBucketTagging(r.Context(), reqInfo.BucketName, tagSet); err != nil {
|
||||||
|
h.logAndSendError(w, "could not put object tagging", reqInfo, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) GetBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
reqInfo := api.GetReqInfo(r.Context())
|
||||||
|
|
||||||
|
tagSet, err := h.obj.GetBucketTagging(r.Context(), reqInfo.BucketName)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "could not get object tagging", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = api.EncodeToResponse(w, encodeTagging(tagSet)); err != nil {
|
||||||
|
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) DeleteBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
reqInfo := api.GetReqInfo(r.Context())
|
||||||
|
if err := h.obj.DeleteBucketTagging(r.Context(), reqInfo.BucketName); err != nil {
|
||||||
|
h.logAndSendError(w, "could not delete object tagging", reqInfo, err)
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readTagSet(reader io.Reader) (map[string]string, error) {
|
||||||
|
tagging := new(Tagging)
|
||||||
|
if err := xml.NewDecoder(reader).Decode(tagging); err != nil {
|
||||||
|
return nil, errors.GetAPIError(errors.ErrMalformedXML)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checkTagSet(tagging.TagSet); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tagSet := make(map[string]string, len(tagging.TagSet))
|
||||||
|
for _, tag := range tagging.TagSet {
|
||||||
|
tagSet[tag.Key] = tag.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
return tagSet, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeTagging(tagSet map[string]string) *Tagging {
|
||||||
|
tagging := &Tagging{}
|
||||||
|
for k, v := range tagSet {
|
||||||
|
tagging.TagSet = append(tagging.TagSet, Tag{Key: k, Value: v})
|
||||||
|
}
|
||||||
|
sort.Slice(tagging.TagSet, func(i, j int) bool {
|
||||||
|
return tagging.TagSet[i].Key < tagging.TagSet[j].Key
|
||||||
|
})
|
||||||
|
|
||||||
|
return tagging
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkTagSet(tagSet []Tag) error {
|
||||||
|
if len(tagSet) > maxTags {
|
||||||
|
return errors.GetAPIError(errors.ErrInvalidTagsSizeExceed)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tag := range tagSet {
|
||||||
|
if err := checkTag(tag); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func checkTag(tag Tag) error {
|
func checkTag(tag Tag) error {
|
||||||
if len(tag.Key) < 1 || len(tag.Key) > keyTagMaxLength {
|
if len(tag.Key) < 1 || len(tag.Key) > keyTagMaxLength {
|
||||||
return errors.GetAPIError(errors.ErrInvalidTag)
|
return errors.GetAPIError(errors.ErrInvalidTagKey)
|
||||||
}
|
}
|
||||||
if len(tag.Value) < 1 || len(tag.Value) > valueTagMaxLength {
|
if len(tag.Value) > valueTagMaxLength {
|
||||||
return errors.GetAPIError(errors.ErrInvalidTag)
|
return errors.GetAPIError(errors.ErrInvalidTagValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(tag.Key, "aws:") {
|
if strings.HasPrefix(tag.Key, "aws:") {
|
||||||
return errors.GetAPIError(errors.ErrInvalidTag)
|
return errors.GetAPIError(errors.ErrInvalidTagKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := checkCharacters(tag.Key); err != nil {
|
if !isValidTag(tag.Key) {
|
||||||
return err
|
return errors.GetAPIError(errors.ErrInvalidTagKey)
|
||||||
}
|
}
|
||||||
if err := checkCharacters(tag.Value); err != nil {
|
if !isValidTag(tag.Value) {
|
||||||
return err
|
return errors.GetAPIError(errors.ErrInvalidTagValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkCharacters(str string) error {
|
func isValidTag(str string) bool {
|
||||||
for _, r := range str {
|
for _, r := range str {
|
||||||
if !unicode.IsLetter(r) && !unicode.IsDigit(r) &&
|
if !unicode.IsLetter(r) && !unicode.IsDigit(r) &&
|
||||||
!unicode.IsSpace(r) && !strings.ContainsRune(allowedTagChars, r) {
|
!unicode.IsSpace(r) && !strings.ContainsRune(allowedTagChars, r) {
|
||||||
return errors.GetAPIError(errors.ErrInvalidTag)
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,6 @@ func TestTagsValidity(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{tag: Tag{}, valid: false},
|
{tag: Tag{}, valid: false},
|
||||||
{tag: Tag{Key: "", Value: "1"}, valid: false},
|
{tag: Tag{Key: "", Value: "1"}, valid: false},
|
||||||
{tag: Tag{Key: "2", Value: ""}, valid: false},
|
|
||||||
{tag: Tag{Key: "aws:key", Value: "val"}, valid: false},
|
{tag: Tag{Key: "aws:key", Value: "val"}, valid: false},
|
||||||
{tag: Tag{Key: "key~", Value: "val"}, valid: false},
|
{tag: Tag{Key: "key~", Value: "val"}, valid: false},
|
||||||
{tag: Tag{Key: "key\\", Value: "val"}, valid: false},
|
{tag: Tag{Key: "key\\", Value: "val"}, valid: false},
|
||||||
|
|
|
@ -83,18 +83,10 @@ func (h *handler) GetBucketReplicationHandler(w http.ResponseWriter, r *http.Req
|
||||||
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) GetBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *handler) DeleteBucketWebsiteHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) DeleteBucketWebsiteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) DeleteBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *handler) GetBucketObjectLockConfigHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) GetBucketObjectLockConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ const (
|
||||||
AmzMetadataDirective = "X-Amz-Metadata-Directive"
|
AmzMetadataDirective = "X-Amz-Metadata-Directive"
|
||||||
AmzVersionID = "X-Amz-Version-Id"
|
AmzVersionID = "X-Amz-Version-Id"
|
||||||
AmzTaggingCount = "X-Amz-Tagging-Count"
|
AmzTaggingCount = "X-Amz-Tagging-Count"
|
||||||
|
AmzTagging = "X-Amz-Tagging"
|
||||||
|
|
||||||
LastModified = "Last-Modified"
|
LastModified = "Last-Modified"
|
||||||
Date = "Date"
|
Date = "Date"
|
||||||
|
|
|
@ -164,9 +164,11 @@ type (
|
||||||
GetObject(ctx context.Context, p *GetObjectParams) error
|
GetObject(ctx context.Context, p *GetObjectParams) error
|
||||||
GetObjectInfo(ctx context.Context, p *HeadObjectParams) (*ObjectInfo, error)
|
GetObjectInfo(ctx context.Context, p *HeadObjectParams) (*ObjectInfo, error)
|
||||||
GetObjectTagging(ctx context.Context, p *ObjectInfo) (map[string]string, error)
|
GetObjectTagging(ctx context.Context, p *ObjectInfo) (map[string]string, error)
|
||||||
|
GetBucketTagging(ctx context.Context, bucket string) (map[string]string, error)
|
||||||
|
|
||||||
PutObject(ctx context.Context, p *PutObjectParams) (*ObjectInfo, error)
|
PutObject(ctx context.Context, p *PutObjectParams) (*ObjectInfo, error)
|
||||||
PutObjectTagging(ctx context.Context, p *PutTaggingParams) error
|
PutObjectTagging(ctx context.Context, p *PutTaggingParams) error
|
||||||
|
PutBucketTagging(ctx context.Context, bucket string, tagSet map[string]string) error
|
||||||
|
|
||||||
CopyObject(ctx context.Context, p *CopyObjectParams) (*ObjectInfo, error)
|
CopyObject(ctx context.Context, p *CopyObjectParams) (*ObjectInfo, error)
|
||||||
|
|
||||||
|
@ -176,10 +178,14 @@ type (
|
||||||
|
|
||||||
DeleteObjects(ctx context.Context, bucket string, objects []*VersionedObject) []error
|
DeleteObjects(ctx context.Context, bucket string, objects []*VersionedObject) []error
|
||||||
DeleteObjectTagging(ctx context.Context, p *ObjectInfo) error
|
DeleteObjectTagging(ctx context.Context, p *ObjectInfo) error
|
||||||
|
DeleteBucketTagging(ctx context.Context, bucket string) error
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const tagPrefix = "S3-Tag-"
|
const (
|
||||||
|
tagPrefix = "S3-Tag-"
|
||||||
|
tagEmptyMark = "\\"
|
||||||
|
)
|
||||||
|
|
||||||
func (t *VersionedObject) String() string {
|
func (t *VersionedObject) String() string {
|
||||||
return t.Name + ":" + t.VersionID
|
return t.Name + ":" + t.VersionID
|
||||||
|
@ -387,17 +393,38 @@ func (n *layer) GetObjectTagging(ctx context.Context, oi *ObjectInfo) (map[strin
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return formTagSet(objInfo), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBucketTagging from storage.
|
||||||
|
func (n *layer) GetBucketTagging(ctx context.Context, bucketName string) (map[string]string, error) {
|
||||||
|
bktInfo, err := n.GetBucketInfo(ctx, bucketName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
objInfo, err := n.getSystemObject(ctx, bktInfo, formBucketTagObjectName(bucketName))
|
||||||
|
if err != nil && !errors.IsS3Error(err, errors.ErrNoSuchKey) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return formTagSet(objInfo), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func formTagSet(objInfo *ObjectInfo) map[string]string {
|
||||||
var tagSet map[string]string
|
var tagSet map[string]string
|
||||||
if objInfo != nil {
|
if objInfo != nil {
|
||||||
tagSet = make(map[string]string, len(objInfo.Headers))
|
tagSet = make(map[string]string, len(objInfo.Headers))
|
||||||
for k, v := range objInfo.Headers {
|
for k, v := range objInfo.Headers {
|
||||||
if strings.HasPrefix(k, tagPrefix) {
|
if strings.HasPrefix(k, tagPrefix) {
|
||||||
|
if v == tagEmptyMark {
|
||||||
|
v = ""
|
||||||
|
}
|
||||||
tagSet[strings.TrimPrefix(k, tagPrefix)] = v
|
tagSet[strings.TrimPrefix(k, tagPrefix)] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return tagSet
|
||||||
return tagSet, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutObjectTagging into storage.
|
// PutObjectTagging into storage.
|
||||||
|
@ -415,9 +442,27 @@ func (n *layer) PutObjectTagging(ctx context.Context, p *PutTaggingParams) error
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PutBucketTagging into storage.
|
||||||
|
func (n *layer) PutBucketTagging(ctx context.Context, bucketName string, tagSet map[string]string) error {
|
||||||
|
bktInfo, err := n.GetBucketInfo(ctx, bucketName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = n.putSystemObject(ctx, bktInfo, formBucketTagObjectName(bucketName), tagSet, tagPrefix); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteObjectTagging from storage.
|
// DeleteObjectTagging from storage.
|
||||||
func (n *layer) DeleteObjectTagging(ctx context.Context, p *ObjectInfo) error {
|
func (n *layer) DeleteObjectTagging(ctx context.Context, p *ObjectInfo) error {
|
||||||
oid, err := n.objectFindID(ctx, &findParams{cid: p.CID(), attr: objectSystemAttributeName, val: p.TagsObject()})
|
return n.deleteSystemObject(ctx, p.CID(), p.TagsObject())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *layer) deleteSystemObject(ctx context.Context, bktCID *cid.ID, name string) error {
|
||||||
|
oid, err := n.objectFindID(ctx, &findParams{cid: bktCID, attr: objectSystemAttributeName, val: name})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.IsS3Error(err, errors.ErrNoSuchKey) {
|
if errors.IsS3Error(err, errors.ErrNoSuchKey) {
|
||||||
return nil
|
return nil
|
||||||
|
@ -425,7 +470,17 @@ func (n *layer) DeleteObjectTagging(ctx context.Context, p *ObjectInfo) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return n.objectDelete(ctx, p.CID(), oid)
|
return n.objectDelete(ctx, bktCID, oid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteBucketTagging from storage.
|
||||||
|
func (n *layer) DeleteBucketTagging(ctx context.Context, bucketName string) error {
|
||||||
|
bktInfo, err := n.GetBucketInfo(ctx, bucketName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return n.deleteSystemObject(ctx, bktInfo.CID, formBucketTagObjectName(bucketName))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *layer) putSystemObject(ctx context.Context, bktInfo *cache.BucketInfo, objName string, metadata map[string]string, prefix string) (*object.ID, error) {
|
func (n *layer) putSystemObject(ctx context.Context, bktInfo *cache.BucketInfo, objName string, metadata map[string]string, prefix string) (*object.ID, error) {
|
||||||
|
@ -453,6 +508,9 @@ func (n *layer) putSystemObject(ctx context.Context, bktInfo *cache.BucketInfo,
|
||||||
for k, v := range metadata {
|
for k, v := range metadata {
|
||||||
attr := object.NewAttribute()
|
attr := object.NewAttribute()
|
||||||
attr.SetKey(prefix + k)
|
attr.SetKey(prefix + k)
|
||||||
|
if prefix == tagPrefix && v == "" {
|
||||||
|
v = tagEmptyMark
|
||||||
|
}
|
||||||
attr.SetValue(v)
|
attr.SetValue(v)
|
||||||
attributes = append(attributes, attr)
|
attributes = append(attributes, attr)
|
||||||
}
|
}
|
||||||
|
|
|
@ -198,3 +198,7 @@ func GetBoxData(ctx context.Context) (*accessbox.Box, error) {
|
||||||
}
|
}
|
||||||
return boxData, nil
|
return boxData, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func formBucketTagObjectName(name string) string {
|
||||||
|
return ".tagset." + name
|
||||||
|
}
|
||||||
|
|
|
@ -209,9 +209,9 @@ See also `GetObject` and other method parameters.
|
||||||
|
|
||||||
| | Method | Comments |
|
| | Method | Comments |
|
||||||
|----|---------------------|----------|
|
|----|---------------------|----------|
|
||||||
| 🔴 | DeleteBucketTagging | |
|
| 🟢 | DeleteBucketTagging | |
|
||||||
| 🔴 | GetBucketTagging | |
|
| 🟢 | GetBucketTagging | |
|
||||||
| 🔴 | PutBucketTagging | |
|
| 🟢 | PutBucketTagging | |
|
||||||
|
|
||||||
## Tiering
|
## Tiering
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue