diff --git a/cmd/api-errors.go b/cmd/api-errors.go
index dcfef101e..99f45505b 100644
--- a/cmd/api-errors.go
+++ b/cmd/api-errors.go
@@ -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(),
diff --git a/cmd/api-router.go b/cmd/api-router.go
index 395d05e92..971c2ead6 100644
--- a/cmd/api-router.go
+++ b/cmd/api-router.go
@@ -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", "")
diff --git a/cmd/bucket-handlers.go b/cmd/bucket-handlers.go
index 5284e5311..ce3659f64 100644
--- a/cmd/bucket-handlers.go
+++ b/cmd/bucket-handlers.go
@@ -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 = ``
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)
+}
diff --git a/cmd/dummy-handlers.go b/cmd/dummy-handlers.go
index e7fbd5697..c58529c99 100644
--- a/cmd/dummy-handlers.go
+++ b/cmd/dummy-handlers.go
@@ -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()
-}
diff --git a/cmd/fs-v1.go b/cmd/fs-v1.go
index fc415ffcf..9c8c124cb 100644
--- a/cmd/fs-v1.go
+++ b/cmd/fs-v1.go
@@ -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
- }
-
- tags, err := tagging.FromString(oi.UserTags)
- if err != nil {
- return tagging.Tagging{}, err
+ return nil, err
}
- return tags, nil
+ return tags.ParseObjectTags(oi.UserTags)
}
// PutObjectTag - replace or add tags to an existing object
diff --git a/cmd/gateway-unsupported.go b/cmd/gateway-unsupported.go
index 0f004d70f..a90f5f511 100644
--- a/cmd/gateway-unsupported.go
+++ b/cmd/gateway-unsupported.go
@@ -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.
diff --git a/cmd/generic-handlers.go b/cmd/generic-handlers.go
index d45f1d6ae..32ea7c9f0 100644
--- a/cmd/generic-handlers.go
+++ b/cmd/generic-handlers.go
@@ -491,7 +491,6 @@ var notImplementedBucketResourceNames = map[string]bool{
"metrics": true,
"replication": true,
"requestPayment": true,
- "tagging": true,
"versioning": true,
"website": true,
}
diff --git a/cmd/handler-utils.go b/cmd/handler-utils.go
index 099e69a48..660926ebb 100644
--- a/cmd/handler-utils.go
+++ b/cmd/handler-utils.go
@@ -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 {
diff --git a/cmd/object-api-interface.go b/cmd/object-api-interface.go
index a3a0111d6..a8bb6fbda 100644
--- a/cmd/object-api-interface.go
+++ b/cmd/object-api-interface.go
@@ -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
}
diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go
index cb2cb6c4f..a50f443f2 100644
--- a/cmd/object-handlers.go
+++ b/cmd/object-handlers.go
@@ -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
}
diff --git a/cmd/xl-sets.go b/cmd/xl-sets.go
index 4e0a5b26d..dd57a1460 100644
--- a/cmd/xl-sets.go
+++ b/cmd/xl-sets.go
@@ -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)
}
diff --git a/cmd/xl-v1-object.go b/cmd/xl-v1-object.go
index 98627e936..61cadc0fe 100644
--- a/cmd/xl-v1-object.go
+++ b/cmd/xl-v1-object.go
@@ -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)
}
diff --git a/cmd/xl-zones.go b/cmd/xl-zones.go
index e896df128..559ac4ec2 100644
--- a/cmd/xl-zones.go
+++ b/cmd/xl-zones.go
@@ -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,
}
}
diff --git a/go.mod b/go.mod
index da961ac96..65cbd8986 100644
--- a/go.mod
+++ b/go.mod
@@ -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
diff --git a/pkg/bucket/lifecycle/and.go b/pkg/bucket/lifecycle/and.go
index 508cc6970..977a92745 100644
--- a/pkg/bucket/lifecycle/and.go
+++ b/pkg/bucket/lifecycle/and.go
@@ -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")
diff --git a/pkg/bucket/lifecycle/filter.go b/pkg/bucket/lifecycle/filter.go
index 7692e9d36..563abac92 100644
--- a/pkg/bucket/lifecycle/filter.go
+++ b/pkg/bucket/lifecycle/filter.go
@@ -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()
}
diff --git a/pkg/bucket/object/tagging/error.go b/pkg/bucket/object/tagging/error.go
deleted file mode 100644
index 89c4fab81..000000000
--- a/pkg/bucket/object/tagging/error.go
+++ /dev/null
@@ -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 "
- }
- return e.err.Error()
-}
-
-// Code returns appropriate error code.
-func (e Error) Code() string {
- if e.code == "" {
- return "BadRequest"
- }
- return e.code
-}
diff --git a/pkg/bucket/object/tagging/tag.go b/pkg/bucket/object/tagging/tag.go
deleted file mode 100644
index 2f2faa726..000000000
--- a/pkg/bucket/object/tagging/tag.go
+++ /dev/null
@@ -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
-}
diff --git a/pkg/bucket/object/tagging/tagging.go b/pkg/bucket/object/tagging/tagging.go
deleted file mode 100644
index 2b9b69560..000000000
--- a/pkg/bucket/object/tagging/tagging.go
+++ /dev/null
@@ -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
-}
diff --git a/pkg/bucket/object/tagging/tagset.go b/pkg/bucket/object/tagging/tagset.go
deleted file mode 100644
index b4997db95..000000000
--- a/pkg/bucket/object/tagging/tagset.go
+++ /dev/null
@@ -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
-}
diff --git a/pkg/bucket/policy/action.go b/pkg/bucket/policy/action.go
index c52568b35..0e0a3a55e 100644
--- a/pkg/bucket/policy/action.go
+++ b/pkg/bucket/policy/action.go
@@ -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...),
diff --git a/pkg/iam/policy/action.go b/pkg/iam/policy/action.go
index 35d133472..cda186c73 100644
--- a/pkg/iam/policy/action.go
+++ b/pkg/iam/policy/action.go
@@ -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...),