add bucket tagging support (#9389)

This patch also simplifies object tagging support
This commit is contained in:
Bala FA 2020-05-05 21:18:13 +00:00 committed by GitHub
parent 6c62b1a2ea
commit 3773874cd3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 188 additions and 426 deletions

View file

@ -28,13 +28,13 @@ import (
"google.golang.org/api/googleapi"
minio "github.com/minio/minio-go/v6"
"github.com/minio/minio-go/v6/pkg/tags"
"github.com/minio/minio/cmd/config/etcd/dns"
"github.com/minio/minio/cmd/crypto"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/bucket/lifecycle"
objectlock "github.com/minio/minio/pkg/bucket/object/lock"
"github.com/minio/minio/pkg/bucket/object/tagging"
"github.com/minio/minio/pkg/bucket/policy"
"github.com/minio/minio/pkg/event"
"github.com/minio/minio/pkg/hash"
@ -157,6 +157,7 @@ const (
ErrInvalidRetentionDate
ErrPastObjectLockRetainDate
ErrUnknownWORMModeDirective
ErrBucketTaggingNotFound
ErrObjectLockInvalidHeaders
ErrInvalidTagDirective
// Add new error codes here.
@ -777,6 +778,11 @@ var errorCodes = errorCodeMap{
Description: "Bucket is missing ObjectLockConfiguration",
HTTPStatusCode: http.StatusBadRequest,
},
ErrBucketTaggingNotFound: {
Code: "NoSuchTagSet",
Description: "The TagSet does not exist",
HTTPStatusCode: http.StatusNotFound,
},
ErrObjectLockConfigurationNotFound: {
Code: "ObjectLockConfigurationNotFoundError",
Description: "Object Lock configuration does not exist for this bucket",
@ -1896,7 +1902,7 @@ func toAPIError(ctx context.Context, err error) APIError {
Description: e.Error(),
HTTPStatusCode: http.StatusBadRequest,
}
case tagging.Error:
case tags.Error:
apiErr = APIError{
Code: e.Code(),
Description: e.Error(),

View file

@ -194,7 +194,7 @@ func registerAPIRouter(router *mux.Router, encryptionEnabled, allowSSEKMS bool)
// GetBucketReplicationHandler - this is a dummy call.
bucket.Methods(http.MethodGet).HandlerFunc(
maxClients(collectAPIStats("getbucketreplication", httpTraceAll(api.GetBucketReplicationHandler)))).Queries("replication", "")
// GetBucketTaggingHandler - this is a dummy call.
// GetBucketTaggingHandler
bucket.Methods(http.MethodGet).HandlerFunc(
maxClients(collectAPIStats("getbuckettagging", httpTraceAll(api.GetBucketTaggingHandler)))).Queries("tagging", "")
//DeleteBucketWebsiteHandler
@ -244,6 +244,9 @@ func registerAPIRouter(router *mux.Router, encryptionEnabled, allowSSEKMS bool)
// PutBucketObjectLockConfig
bucket.Methods(http.MethodPut).HandlerFunc(
maxClients(collectAPIStats("putbucketobjectlockconfig", httpTraceAll(api.PutBucketObjectLockConfigHandler)))).Queries("object-lock", "")
// PutBucketTaggingHandler
bucket.Methods(http.MethodPut).HandlerFunc(
maxClients(collectAPIStats("putbuckettagging", httpTraceAll(api.PutBucketTaggingHandler)))).Queries("tagging", "")
// PutBucketVersioning
bucket.Methods(http.MethodPut).HandlerFunc(
maxClients(collectAPIStats("putbucketversioning", httpTraceAll(api.PutBucketVersioningHandler)))).Queries("versioning", "")

View file

@ -30,6 +30,7 @@ import (
"github.com/gorilla/mux"
"github.com/minio/minio-go/v6/pkg/set"
"github.com/minio/minio-go/v6/pkg/tags"
"github.com/minio/minio/cmd/config/etcd/dns"
"github.com/minio/minio/cmd/crypto"
xhttp "github.com/minio/minio/cmd/http"
@ -46,6 +47,7 @@ import (
const (
getBucketVersioningResponse = `<VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"/>`
objectLockConfig = "object-lock.xml"
bucketTaggingConfigFile = "tagging.xml"
)
// Check if there are buckets on server without corresponding entry in etcd backend and
@ -1131,3 +1133,113 @@ func (api objectAPIHandlers) GetBucketObjectLockConfigHandler(w http.ResponseWri
// Write success response.
writeSuccessResponseXML(w, configData)
}
// PutBucketTaggingHandler - PUT Bucket tagging.
// ----------
func (api objectAPIHandlers) PutBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "PutBucketTagging")
defer logger.AuditLog(w, r, "PutBucketTagging", mustGetClaimsFromToken(r))
vars := mux.Vars(r)
bucket := vars["bucket"]
objectAPI := api.ObjectAPI()
if objectAPI == nil {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
return
}
if s3Error := checkRequestAuthType(ctx, r, policy.PutBucketTaggingAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return
}
tags, err := tags.ParseBucketXML(io.LimitReader(r.Body, r.ContentLength))
if err != nil {
apiErr := errorCodes.ToAPIErr(ErrMalformedXML)
apiErr.Description = err.Error()
writeErrorResponse(ctx, w, apiErr, r.URL, guessIsBrowserReq(r))
return
}
data, err := xml.Marshal(tags)
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
}
configFile := path.Join(bucketConfigPrefix, bucket, bucketTaggingConfigFile)
if err = saveConfig(ctx, objectAPI, configFile, data); err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
}
// Write success response.
writeSuccessResponseHeadersOnly(w)
}
// GetBucketTaggingHandler - GET Bucket tagging.
// ----------
func (api objectAPIHandlers) GetBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "GetBucketTagging")
defer logger.AuditLog(w, r, "GetBucketTagging", mustGetClaimsFromToken(r))
vars := mux.Vars(r)
bucket := vars["bucket"]
objectAPI := api.ObjectAPI()
if objectAPI == nil {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
return
}
// check if user has permissions to perform this operation
if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketTaggingAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return
}
configFile := path.Join(bucketConfigPrefix, bucket, bucketTaggingConfigFile)
configData, err := readConfig(ctx, objectAPI, configFile)
if err != nil {
var aerr APIError
if err == errConfigNotFound {
aerr = errorCodes.ToAPIErr(ErrBucketTaggingNotFound)
} else {
aerr = toAPIError(ctx, err)
}
writeErrorResponse(ctx, w, aerr, r.URL, guessIsBrowserReq(r))
return
}
// Write success response.
writeSuccessResponseXML(w, configData)
}
// DeleteBucketTaggingHandler - DELETE Bucket tagging.
// ----------
func (api objectAPIHandlers) DeleteBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "DeleteBucketTagging")
defer logger.AuditLog(w, r, "DeleteBucketTagging", mustGetClaimsFromToken(r))
vars := mux.Vars(r)
bucket := vars["bucket"]
objectAPI := api.ObjectAPI()
if objectAPI == nil {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
return
}
if s3Error := checkRequestAuthType(ctx, r, policy.PutBucketTaggingAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return
}
configFile := path.Join(bucketConfigPrefix, bucket, bucketTaggingConfigFile)
if err := deleteConfig(ctx, objectAPI, configFile); err != nil && err != errConfigNotFound {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
}
// Write success response.
writeSuccessResponseHeadersOnly(w)
}

View file

@ -21,7 +21,6 @@ import (
"net/http"
"github.com/gorilla/mux"
"github.com/minio/minio/pkg/bucket/object/tagging"
"github.com/minio/minio/pkg/bucket/policy"
)
@ -59,12 +58,6 @@ func (api objectAPIHandlers) GetBucketReplicationHandler(w http.ResponseWriter,
w.(http.Flusher).Flush()
}
// DeleteBucketTaggingHandler - DELETE bucket tagging, a dummy api
func (api objectAPIHandlers) DeleteBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
writeSuccessResponseHeadersOnly(w)
w.(http.Flusher).Flush()
}
// DeleteBucketWebsiteHandler - DELETE bucket website, a dummy api
func (api objectAPIHandlers) DeleteBucketWebsiteHandler(w http.ResponseWriter, r *http.Request) {
writeSuccessResponseHeadersOnly(w)
@ -130,41 +123,3 @@ func (api objectAPIHandlers) GetBucketCorsHandler(w http.ResponseWriter, r *http
w.(http.Flusher).Flush()
}
// GetBucketTaggingHandler - GET bucket tagging, a dummy api
func (api objectAPIHandlers) GetBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "GetBucketTagging")
vars := mux.Vars(r)
bucket := vars["bucket"]
objAPI := api.ObjectAPI()
if objAPI == nil {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
return
}
// Allow getBucketTagging if policy action is set, since this is a dummy call
// we are simply re-purposing the bucketPolicyAction.
if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketPolicyAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return
}
// Validate if bucket exists, before proceeding further...
_, err := objAPI.GetBucketInfo(ctx, bucket)
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
}
tags := &tagging.Tagging{}
tags.TagSet.Tags = append(tags.TagSet.Tags, tagging.Tag{})
if err := xml.NewEncoder(w).Encode(tags); err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
}
w.(http.Flusher).Flush()
}

View file

@ -35,12 +35,12 @@ import (
jsoniter "github.com/json-iterator/go"
"github.com/minio/minio-go/v6/pkg/s3utils"
"github.com/minio/minio-go/v6/pkg/tags"
"github.com/minio/minio/cmd/config"
xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger"
bucketsse "github.com/minio/minio/pkg/bucket/encryption"
"github.com/minio/minio/pkg/bucket/lifecycle"
"github.com/minio/minio/pkg/bucket/object/tagging"
"github.com/minio/minio/pkg/bucket/policy"
"github.com/minio/minio/pkg/color"
"github.com/minio/minio/pkg/lock"
@ -1219,18 +1219,13 @@ func (fs *FSObjects) ListObjects(ctx context.Context, bucket, prefix, marker, de
}
// GetObjectTag - get object tags from an existing object
func (fs *FSObjects) GetObjectTag(ctx context.Context, bucket, object string) (tagging.Tagging, error) {
func (fs *FSObjects) GetObjectTag(ctx context.Context, bucket, object string) (*tags.Tags, error) {
oi, err := fs.GetObjectInfo(ctx, bucket, object, ObjectOptions{})
if err != nil {
return tagging.Tagging{}, err
return nil, err
}
tags, err := tagging.FromString(oi.UserTags)
if err != nil {
return tagging.Tagging{}, err
}
return tags, nil
return tags.ParseObjectTags(oi.UserTags)
}
// PutObjectTag - replace or add tags to an existing object

View file

@ -22,9 +22,9 @@ import (
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio-go/v6/pkg/tags"
bucketsse "github.com/minio/minio/pkg/bucket/encryption"
"github.com/minio/minio/pkg/bucket/lifecycle"
"github.com/minio/minio/pkg/bucket/object/tagging"
"github.com/minio/minio/pkg/bucket/policy"
"github.com/minio/minio/pkg/madmin"
@ -205,9 +205,9 @@ func (a GatewayUnsupported) PutObjectTag(ctx context.Context, bucket, object str
}
// GetObjectTag - not implemented.
func (a GatewayUnsupported) GetObjectTag(ctx context.Context, bucket, object string) (tagging.Tagging, error) {
func (a GatewayUnsupported) GetObjectTag(ctx context.Context, bucket, object string) (*tags.Tags, error) {
logger.LogIf(ctx, NotImplemented{})
return tagging.Tagging{}, NotImplemented{}
return nil, NotImplemented{}
}
// DeleteObjectTag - not implemented.

View file

@ -491,7 +491,6 @@ var notImplementedBucketResourceNames = map[string]bool{
"metrics": true,
"replication": true,
"requestPayment": true,
"tagging": true,
"versioning": true,
"website": true,
}

View file

@ -33,7 +33,6 @@ import (
xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/bucket/object/tagging"
"github.com/minio/minio/pkg/handlers"
"github.com/minio/minio/pkg/madmin"
)
@ -161,26 +160,6 @@ func extractMetadataFromMap(ctx context.Context, v map[string][]string, m map[st
return nil
}
// extractTags extracts tag key and value from given http header. It then
// - Parses the input format X-Amz-Tagging:"Key1=Value1&Key2=Value2" into a map[string]string
// with entries in the format X-Amg-Tag-Key1:Value1, X-Amz-Tag-Key2:Value2
// - Validates the tags
// - Returns the Tag in original string format "Key1=Value1&Key2=Value2"
func extractTags(ctx context.Context, tags string) (string, error) {
// Check if the metadata has tagging related header
if tags != "" {
tagging, err := tagging.FromString(tags)
if err != nil {
return "", err
}
if err := tagging.Validate(); err != nil {
return "", err
}
return tagging.String(), nil
}
return "", nil
}
// The Query string for the redirect URL the client is
// redirected on successful upload.
func getRedirectPostRawQuery(objInfo ObjectInfo) string {

View file

@ -22,9 +22,9 @@ import (
"net/http"
"github.com/minio/minio-go/v6/pkg/encrypt"
"github.com/minio/minio-go/v6/pkg/tags"
bucketsse "github.com/minio/minio/pkg/bucket/encryption"
"github.com/minio/minio/pkg/bucket/lifecycle"
"github.com/minio/minio/pkg/bucket/object/tagging"
"github.com/minio/minio/pkg/bucket/policy"
"github.com/minio/minio/pkg/madmin"
@ -138,6 +138,6 @@ type ObjectLayer interface {
// ObjectTagging operations
PutObjectTag(context.Context, string, string, string) error
GetObjectTag(context.Context, string, string) (tagging.Tagging, error)
GetObjectTag(context.Context, string, string) (*tags.Tags, error)
DeleteObjectTag(context.Context, string, string) error
}

View file

@ -34,13 +34,13 @@ import (
"github.com/gorilla/mux"
miniogo "github.com/minio/minio-go/v6"
"github.com/minio/minio-go/v6/pkg/encrypt"
"github.com/minio/minio-go/v6/pkg/tags"
"github.com/minio/minio/cmd/config/etcd/dns"
"github.com/minio/minio/cmd/config/storageclass"
"github.com/minio/minio/cmd/crypto"
xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger"
objectlock "github.com/minio/minio/pkg/bucket/object/lock"
"github.com/minio/minio/pkg/bucket/object/tagging"
"github.com/minio/minio/pkg/bucket/policy"
"github.com/minio/minio/pkg/event"
"github.com/minio/minio/pkg/handlers"
@ -647,24 +647,6 @@ func getCpObjMetadataFromHeader(ctx context.Context, r *http.Request, userMeta m
return defaultMeta, nil
}
// Extract tags relevant for an CopyObject operation based on conditional
// header values specified in X-Amz-Tagging-Directive.
func getCpObjTagsFromHeader(ctx context.Context, r *http.Request, tags string) (string, error) {
// if x-amz-tagging-directive says REPLACE then
// we extract tags from the input headers.
if isDirectiveReplace(r.Header.Get(xhttp.AmzTagDirective)) {
if tags := r.Header.Get(xhttp.AmzObjectTagging); tags != "" {
return extractTags(ctx, tags)
}
// If x-amz-tagging-directive is explicitly set to replace and x-amz-tagging is not set.
// The S3 behavior is to unset the tags.
return "", nil
}
// Copy is default behavior if x-amz-tagging-directive is not set.
return tags, nil
}
// getRemoteInstanceTransport contains a singleton roundtripper.
var getRemoteInstanceTransport http.RoundTripper
var getRemoteInstanceTransportOnce sync.Once
@ -1056,14 +1038,18 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
return
}
tags, err := getCpObjTagsFromHeader(ctx, r, srcInfo.UserTags)
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
objTags := srcInfo.UserTags
// If x-amz-tagging-directive header is REPLACE, get passed tags.
if isDirectiveReplace(r.Header.Get(xhttp.AmzTagDirective)) {
objTags = r.Header.Get(xhttp.AmzObjectTagging)
if _, err := tags.ParseObjectTags(objTags); err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
}
}
if tags != "" {
srcInfo.UserDefined[xhttp.AmzObjectTagging] = tags
if objTags != "" {
srcInfo.UserDefined[xhttp.AmzObjectTagging] = objTags
}
srcInfo.UserDefined = objectlock.FilterObjectLockMetadata(srcInfo.UserDefined, true, true)
@ -1265,12 +1251,13 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
return
}
if tags := r.Header.Get(xhttp.AmzObjectTagging); tags != "" {
metadata[xhttp.AmzObjectTagging], err = extractTags(ctx, tags)
if err != nil {
if objTags := r.Header.Get(xhttp.AmzObjectTagging); objTags != "" {
if _, err := tags.ParseObjectTags(objTags); err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
}
metadata[xhttp.AmzObjectTagging] = objTags
}
if rAuthType == authTypeStreamingSigned {
@ -2994,14 +2981,14 @@ func (api objectAPIHandlers) PutObjectTaggingHandler(w http.ResponseWriter, r *h
return
}
tagging, err := tagging.ParseTagging(io.LimitReader(r.Body, r.ContentLength))
tags, err := tags.ParseObjectXML(io.LimitReader(r.Body, r.ContentLength))
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
}
// Put object tags
err = objAPI.PutObjectTag(ctx, bucket, object, tagging.String())
err = objAPI.PutObjectTag(ctx, bucket, object, tags.String())
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
@ -3036,8 +3023,7 @@ func (api objectAPIHandlers) DeleteObjectTaggingHandler(w http.ResponseWriter, r
}
// Delete object tags
err = objAPI.DeleteObjectTag(ctx, bucket, object)
if err != nil {
if err = objAPI.DeleteObjectTag(ctx, bucket, object); err != nil && err != errConfigNotFound {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
}

View file

@ -26,13 +26,13 @@ import (
"sync"
"time"
"github.com/minio/minio-go/v6/pkg/tags"
"github.com/minio/minio/cmd/config/storageclass"
xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/bpool"
bucketsse "github.com/minio/minio/pkg/bucket/encryption"
"github.com/minio/minio/pkg/bucket/lifecycle"
"github.com/minio/minio/pkg/bucket/object/tagging"
"github.com/minio/minio/pkg/bucket/policy"
"github.com/minio/minio/pkg/dsync"
"github.com/minio/minio/pkg/madmin"
@ -1785,7 +1785,7 @@ func (s *xlSets) DeleteObjectTag(ctx context.Context, bucket, object string) err
}
// GetObjectTag - get object tags from an existing object
func (s *xlSets) GetObjectTag(ctx context.Context, bucket, object string) (tagging.Tagging, error) {
func (s *xlSets) GetObjectTag(ctx context.Context, bucket, object string) (*tags.Tags, error) {
return s.getHashedSet(object).GetObjectTag(ctx, bucket, object)
}

View file

@ -25,9 +25,9 @@ import (
"path"
"sync"
"github.com/minio/minio-go/v6/pkg/tags"
xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/bucket/object/tagging"
"github.com/minio/minio/pkg/mimedb"
"github.com/minio/minio/pkg/sync/errgroup"
)
@ -1018,7 +1018,7 @@ func (xl xlObjects) PutObjectTag(ctx context.Context, bucket, object string, tag
_, writeQuorum, err := objectQuorumFromMeta(ctx, xl, metaArr, errs)
if err != nil {
return err
return toObjectErr(err, bucket, object)
}
for i, xlMeta := range metaArr {
@ -1052,16 +1052,12 @@ func (xl xlObjects) DeleteObjectTag(ctx context.Context, bucket, object string)
}
// GetObjectTag - get object tags from an existing object
func (xl xlObjects) GetObjectTag(ctx context.Context, bucket, object string) (tagging.Tagging, error) {
func (xl xlObjects) GetObjectTag(ctx context.Context, bucket, object string) (*tags.Tags, error) {
// GetObjectInfo will return tag value as well
oi, err := xl.GetObjectInfo(ctx, bucket, object, ObjectOptions{})
if err != nil {
return tagging.Tagging{}, err
return nil, err
}
tags, err := tagging.FromString(oi.UserTags)
if err != nil {
return tagging.Tagging{}, err
}
return tags, nil
return tags.ParseObjectTags(oi.UserTags)
}

View file

@ -26,11 +26,11 @@ import (
"sync"
"time"
"github.com/minio/minio-go/v6/pkg/tags"
xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger"
bucketsse "github.com/minio/minio/pkg/bucket/encryption"
"github.com/minio/minio/pkg/bucket/lifecycle"
"github.com/minio/minio/pkg/bucket/object/tagging"
"github.com/minio/minio/pkg/bucket/policy"
"github.com/minio/minio/pkg/madmin"
"github.com/minio/minio/pkg/sync/errgroup"
@ -1588,7 +1588,7 @@ func (z *xlZones) DeleteObjectTag(ctx context.Context, bucket, object string) er
}
// GetObjectTag - get object tags from an existing object
func (z *xlZones) GetObjectTag(ctx context.Context, bucket, object string) (tagging.Tagging, error) {
func (z *xlZones) GetObjectTag(ctx context.Context, bucket, object string) (*tags.Tags, error) {
if z.SingleZone() {
return z.zones[0].GetObjectTag(ctx, bucket, object)
}
@ -1602,7 +1602,7 @@ func (z *xlZones) GetObjectTag(ctx context.Context, bucket, object string) (tagg
}
return tags, nil
}
return tagging.Tagging{}, BucketNotFound{
return nil, BucketNotFound{
Bucket: bucket,
}
}

2
go.mod
View file

@ -67,7 +67,7 @@ require (
github.com/minio/hdfs/v3 v3.0.1
github.com/minio/highwayhash v1.0.0
github.com/minio/lsync v1.0.1
github.com/minio/minio-go/v6 v6.0.55-0.20200424204115-7506d2996b22
github.com/minio/minio-go/v6 v6.0.55-0.20200425081427-89eebdef2af0
github.com/minio/parquet-go v0.0.0-20200414234858-838cfa8aae61
github.com/minio/sha256-simd v0.1.1
github.com/minio/simdjson-go v0.1.5-0.20200303142138-b17fe061ea37

View file

@ -19,14 +19,14 @@ package lifecycle
import (
"encoding/xml"
"github.com/minio/minio/pkg/bucket/object/tagging"
"github.com/minio/minio-go/v6/pkg/tags"
)
// And - a tag to combine a prefix and multiple tags for lifecycle configuration rule.
type And struct {
XMLName xml.Name `xml:"And"`
Prefix string `xml:"Prefix,omitempty"`
Tags []tagging.Tag `xml:"Tag,omitempty"`
XMLName xml.Name `xml:"And"`
Prefix string `xml:"Prefix,omitempty"`
Tags []tags.Tag `xml:"Tag,omitempty"`
}
var errDuplicateTagKey = Errorf("Duplicate Tag Keys are not allowed")

View file

@ -19,7 +19,7 @@ package lifecycle
import (
"encoding/xml"
"github.com/minio/minio/pkg/bucket/object/tagging"
"github.com/minio/minio-go/v6/pkg/tags"
)
var (
@ -31,7 +31,7 @@ type Filter struct {
XMLName xml.Name `xml:"Filter"`
Prefix string
And And
Tag tagging.Tag
Tag tags.Tag
}
// MarshalXML - produces the xml representation of the Filter struct
@ -89,5 +89,5 @@ func (f Filter) Validate() error {
// isEmpty - returns true if Filter tag is empty
func (f Filter) isEmpty() bool {
return f.And.isEmpty() && f.Prefix == "" && f.Tag == tagging.Tag{}
return f.And.isEmpty() && f.Prefix == "" && f.Tag.IsEmpty()
}

View file

@ -1,53 +0,0 @@
/*
* MinIO Cloud Storage, (C) 2020 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package tagging
import (
"fmt"
)
// Error is the generic type for any error happening during tag
// parsing.
type Error struct {
err error
code string
}
// Errorf - formats according to a format specifier and returns
// the string as a value that satisfies error of type tagging.Error
func Errorf(format, code string, a ...interface{}) error {
return Error{err: fmt.Errorf(format, a...), code: code}
}
// Unwrap the internal error.
func (e Error) Unwrap() error { return e.err }
// Error 'error' compatible method.
func (e Error) Error() string {
if e.err == nil {
return "tagging: cause <nil>"
}
return e.err.Error()
}
// Code returns appropriate error code.
func (e Error) Code() string {
if e.code == "" {
return "BadRequest"
}
return e.code
}

View file

@ -1,82 +0,0 @@
/*
* MinIO Cloud Storage, (C) 2020 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package tagging
import (
"encoding/xml"
"strings"
"unicode/utf8"
)
// Tag - single tag
type Tag struct {
XMLName xml.Name `xml:"Tag"`
Key string `xml:"Key,omitempty"`
Value string `xml:"Value,omitempty"`
}
// Validate - validates the tag element
func (t Tag) Validate() error {
if err := t.validateKey(); err != nil {
return err
}
if err := t.validateValue(); err != nil {
return err
}
return nil
}
// validateKey - checks if key is valid or not.
func (t Tag) validateKey() error {
// cannot be longer than maxTagKeyLength characters
if utf8.RuneCountInString(t.Key) > maxTagKeyLength {
return ErrInvalidTagKey
}
// cannot be empty
if len(t.Key) == 0 {
return ErrInvalidTagKey
}
// Tag key shouldn't have "&"
if strings.Contains(t.Key, "&") {
return ErrInvalidTagKey
}
return nil
}
// validateValue - checks if value is valid or not.
func (t Tag) validateValue() error {
// cannot be longer than maxTagValueLength characters
if utf8.RuneCountInString(t.Value) > maxTagValueLength {
return ErrInvalidTagValue
}
// Tag value shouldn't have "&"
if strings.Contains(t.Value, "&") {
return ErrInvalidTagValue
}
return nil
}
// IsEmpty - checks if tag is empty or not
func (t Tag) IsEmpty() bool {
return t.Key == "" && t.Value == ""
}
// String - returns a string in format "tag1=value1" for the
// current Tag
func (t Tag) String() string {
return t.Key + "=" + t.Value
}

View file

@ -1,112 +0,0 @@
/*
* MinIO Cloud Storage, (C) 2020 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package tagging
import (
"bytes"
"encoding/xml"
"io"
"net/url"
)
// S3 API limits for tags
// Ref: https://docs.aws.amazon.com/AmazonS3/latest/dev/object-tagging.html
const (
maxTags = 10
maxTagKeyLength = 128
maxTagValueLength = 256
)
// errors returned by tagging package
var (
ErrTooManyTags = Errorf("Object tags cannot be greater than 10", "BadRequest")
ErrInvalidTagKey = Errorf("The TagKey you have provided is invalid", "InvalidTag")
ErrInvalidTagValue = Errorf("The TagValue you have provided is invalid", "InvalidTag")
ErrInvalidTag = Errorf("Cannot provide multiple Tags with the same key", "InvalidTag")
)
// Tagging - object tagging interface
type Tagging struct {
XMLName xml.Name `xml:"Tagging"`
TagSet TagSet `xml:"TagSet"`
}
// Validate - validates the tagging configuration
func (t Tagging) Validate() error {
// Tagging can't have more than 10 tags
if len(t.TagSet.Tags) > maxTags {
return ErrTooManyTags
}
// Validate all the rules in the tagging config
for _, ts := range t.TagSet.Tags {
if err := ts.Validate(); err != nil {
return err
}
}
if t.TagSet.ContainsDuplicateTag() {
return ErrInvalidTag
}
return nil
}
// String - returns a string in format "tag1=value1&tag2=value2" with all the
// tags in this Tagging Struct
func (t Tagging) String() string {
var buf bytes.Buffer
for _, tag := range t.TagSet.Tags {
if buf.Len() > 0 {
buf.WriteString("&")
}
buf.WriteString(tag.String())
}
return buf.String()
}
// FromString - returns a Tagging struct when given a string in format
// "tag1=value1&tag2=value2"
func FromString(tagStr string) (Tagging, error) {
tags, err := url.ParseQuery(tagStr)
if err != nil {
return Tagging{}, err
}
var idx = 0
parsedTags := make([]Tag, len(tags))
for k := range tags {
parsedTags[idx].Key = k
parsedTags[idx].Value = tags.Get(k)
idx++
}
return Tagging{
TagSet: TagSet{
Tags: parsedTags,
},
}, nil
}
// ParseTagging - parses incoming xml data in given reader
// into Tagging interface. After parsing, also validates the
// parsed fields based on S3 API constraints.
func ParseTagging(reader io.Reader) (*Tagging, error) {
var t Tagging
if err := xml.NewDecoder(reader).Decode(&t); err != nil {
return nil, err
}
if err := t.Validate(); err != nil {
return nil, err
}
return &t, nil
}

View file

@ -1,41 +0,0 @@
/*
* MinIO Cloud Storage, (C) 2020 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package tagging
import (
"encoding/xml"
)
// TagSet - Set of tags under Tagging
type TagSet struct {
XMLName xml.Name `xml:"TagSet"`
Tags []Tag `xml:"Tag"`
}
// ContainsDuplicateTag - returns true if duplicate keys are present in TagSet
func (t TagSet) ContainsDuplicateTag() bool {
x := make(map[string]struct{}, len(t.Tags))
for _, t := range t.Tags {
if _, has := x[t.Key]; has {
return true
}
x[t.Key] = struct{}{}
}
return false
}

View file

@ -109,6 +109,11 @@ const (
// PutBucketObjectLockConfigurationAction - PutObjectLockConfiguration Rest API action
PutBucketObjectLockConfigurationAction = "s3:PutBucketObjectLockConfiguration"
// GetBucketTaggingAction - GetTagging Rest API action
GetBucketTaggingAction = "s3:GetBucketTagging"
// PutBucketTaggingAction - PutTagging Rest API action
PutBucketTaggingAction = "s3:PutBucketTagging"
// GetObjectTaggingAction - Get Object Tags API action
GetObjectTaggingAction = "s3:GetObjectTagging"
// PutObjectTaggingAction - Put Object Tags API action
@ -174,6 +179,8 @@ var supportedActions = map[Action]struct{}{
PutObjectLegalHoldAction: {},
PutBucketObjectLockConfigurationAction: {},
GetBucketObjectLockConfigurationAction: {},
PutBucketTaggingAction: {},
GetBucketTaggingAction: {},
BypassGovernanceRetentionAction: {},
GetObjectTaggingAction: {},
PutObjectTaggingAction: {},
@ -296,6 +303,8 @@ var actionConditionKeyMap = map[Action]condition.KeySet{
GetBucketObjectLockConfigurationAction: condition.NewKeySet(condition.CommonKeys...),
PutBucketObjectLockConfigurationAction: condition.NewKeySet(condition.CommonKeys...),
GetBucketTaggingAction: condition.NewKeySet(condition.CommonKeys...),
PutBucketTaggingAction: condition.NewKeySet(condition.CommonKeys...),
PutObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...),
GetObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...),
DeleteObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...),

View file

@ -115,6 +115,12 @@ const (
// PutBucketObjectLockConfigurationAction - PutBucketObjectLockConfiguration Rest API action
PutBucketObjectLockConfigurationAction = "s3:PutBucketObjectLockConfiguration"
// GetBucketTaggingAction - GetBucketTagging Rest API action
GetBucketTaggingAction = "s3:GetBucketTagging"
// PutBucketTaggingAction - PutBucketTagging Rest API action
PutBucketTaggingAction = "s3:PutBucketTagging"
// GetObjectTaggingAction - Get Object Tags API action
GetObjectTaggingAction = "s3:GetObjectTagging"
@ -164,6 +170,8 @@ var supportedActions = map[Action]struct{}{
PutObjectLegalHoldAction: {},
PutBucketObjectLockConfigurationAction: {},
GetBucketObjectLockConfigurationAction: {},
PutBucketTaggingAction: {},
GetBucketTaggingAction: {},
BypassGovernanceRetentionAction: {},
GetObjectTaggingAction: {},
PutObjectTaggingAction: {},
@ -333,6 +341,8 @@ var actionConditionKeyMap = map[Action]condition.KeySet{
GetBucketObjectLockConfigurationAction: condition.NewKeySet(condition.CommonKeys...),
PutBucketObjectLockConfigurationAction: condition.NewKeySet(condition.CommonKeys...),
GetBucketTaggingAction: condition.NewKeySet(condition.CommonKeys...),
PutBucketTaggingAction: condition.NewKeySet(condition.CommonKeys...),
PutObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...),
GetObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...),
DeleteObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...),