mirror of
https://github.com/minio/minio.git
synced 2024-12-24 22:25:54 -05:00
add bucket tagging support (#9389)
This patch also simplifies object tagging support
This commit is contained in:
parent
6c62b1a2ea
commit
3773874cd3
@ -28,13 +28,13 @@ import (
|
|||||||
"google.golang.org/api/googleapi"
|
"google.golang.org/api/googleapi"
|
||||||
|
|
||||||
minio "github.com/minio/minio-go/v6"
|
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/config/etcd/dns"
|
||||||
"github.com/minio/minio/cmd/crypto"
|
"github.com/minio/minio/cmd/crypto"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
"github.com/minio/minio/pkg/bucket/lifecycle"
|
"github.com/minio/minio/pkg/bucket/lifecycle"
|
||||||
objectlock "github.com/minio/minio/pkg/bucket/object/lock"
|
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/bucket/policy"
|
||||||
"github.com/minio/minio/pkg/event"
|
"github.com/minio/minio/pkg/event"
|
||||||
"github.com/minio/minio/pkg/hash"
|
"github.com/minio/minio/pkg/hash"
|
||||||
@ -157,6 +157,7 @@ const (
|
|||||||
ErrInvalidRetentionDate
|
ErrInvalidRetentionDate
|
||||||
ErrPastObjectLockRetainDate
|
ErrPastObjectLockRetainDate
|
||||||
ErrUnknownWORMModeDirective
|
ErrUnknownWORMModeDirective
|
||||||
|
ErrBucketTaggingNotFound
|
||||||
ErrObjectLockInvalidHeaders
|
ErrObjectLockInvalidHeaders
|
||||||
ErrInvalidTagDirective
|
ErrInvalidTagDirective
|
||||||
// Add new error codes here.
|
// Add new error codes here.
|
||||||
@ -777,6 +778,11 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "Bucket is missing ObjectLockConfiguration",
|
Description: "Bucket is missing ObjectLockConfiguration",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrBucketTaggingNotFound: {
|
||||||
|
Code: "NoSuchTagSet",
|
||||||
|
Description: "The TagSet does not exist",
|
||||||
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
ErrObjectLockConfigurationNotFound: {
|
ErrObjectLockConfigurationNotFound: {
|
||||||
Code: "ObjectLockConfigurationNotFoundError",
|
Code: "ObjectLockConfigurationNotFoundError",
|
||||||
Description: "Object Lock configuration does not exist for this bucket",
|
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(),
|
Description: e.Error(),
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
}
|
}
|
||||||
case tagging.Error:
|
case tags.Error:
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: e.Code(),
|
Code: e.Code(),
|
||||||
Description: e.Error(),
|
Description: e.Error(),
|
||||||
|
@ -194,7 +194,7 @@ func registerAPIRouter(router *mux.Router, encryptionEnabled, allowSSEKMS bool)
|
|||||||
// GetBucketReplicationHandler - this is a dummy call.
|
// GetBucketReplicationHandler - this is a dummy call.
|
||||||
bucket.Methods(http.MethodGet).HandlerFunc(
|
bucket.Methods(http.MethodGet).HandlerFunc(
|
||||||
maxClients(collectAPIStats("getbucketreplication", httpTraceAll(api.GetBucketReplicationHandler)))).Queries("replication", "")
|
maxClients(collectAPIStats("getbucketreplication", httpTraceAll(api.GetBucketReplicationHandler)))).Queries("replication", "")
|
||||||
// GetBucketTaggingHandler - this is a dummy call.
|
// GetBucketTaggingHandler
|
||||||
bucket.Methods(http.MethodGet).HandlerFunc(
|
bucket.Methods(http.MethodGet).HandlerFunc(
|
||||||
maxClients(collectAPIStats("getbuckettagging", httpTraceAll(api.GetBucketTaggingHandler)))).Queries("tagging", "")
|
maxClients(collectAPIStats("getbuckettagging", httpTraceAll(api.GetBucketTaggingHandler)))).Queries("tagging", "")
|
||||||
//DeleteBucketWebsiteHandler
|
//DeleteBucketWebsiteHandler
|
||||||
@ -244,6 +244,9 @@ func registerAPIRouter(router *mux.Router, encryptionEnabled, allowSSEKMS bool)
|
|||||||
// PutBucketObjectLockConfig
|
// PutBucketObjectLockConfig
|
||||||
bucket.Methods(http.MethodPut).HandlerFunc(
|
bucket.Methods(http.MethodPut).HandlerFunc(
|
||||||
maxClients(collectAPIStats("putbucketobjectlockconfig", httpTraceAll(api.PutBucketObjectLockConfigHandler)))).Queries("object-lock", "")
|
maxClients(collectAPIStats("putbucketobjectlockconfig", httpTraceAll(api.PutBucketObjectLockConfigHandler)))).Queries("object-lock", "")
|
||||||
|
// PutBucketTaggingHandler
|
||||||
|
bucket.Methods(http.MethodPut).HandlerFunc(
|
||||||
|
maxClients(collectAPIStats("putbuckettagging", httpTraceAll(api.PutBucketTaggingHandler)))).Queries("tagging", "")
|
||||||
// PutBucketVersioning
|
// PutBucketVersioning
|
||||||
bucket.Methods(http.MethodPut).HandlerFunc(
|
bucket.Methods(http.MethodPut).HandlerFunc(
|
||||||
maxClients(collectAPIStats("putbucketversioning", httpTraceAll(api.PutBucketVersioningHandler)))).Queries("versioning", "")
|
maxClients(collectAPIStats("putbucketversioning", httpTraceAll(api.PutBucketVersioningHandler)))).Queries("versioning", "")
|
||||||
|
@ -30,6 +30,7 @@ import (
|
|||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
"github.com/minio/minio-go/v6/pkg/set"
|
"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/config/etcd/dns"
|
||||||
"github.com/minio/minio/cmd/crypto"
|
"github.com/minio/minio/cmd/crypto"
|
||||||
xhttp "github.com/minio/minio/cmd/http"
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
@ -46,6 +47,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
getBucketVersioningResponse = `<VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"/>`
|
getBucketVersioningResponse = `<VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"/>`
|
||||||
objectLockConfig = "object-lock.xml"
|
objectLockConfig = "object-lock.xml"
|
||||||
|
bucketTaggingConfigFile = "tagging.xml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Check if there are buckets on server without corresponding entry in etcd backend and
|
// 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.
|
// Write success response.
|
||||||
writeSuccessResponseXML(w, configData)
|
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)
|
||||||
|
}
|
||||||
|
@ -21,7 +21,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/minio/minio/pkg/bucket/object/tagging"
|
|
||||||
"github.com/minio/minio/pkg/bucket/policy"
|
"github.com/minio/minio/pkg/bucket/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -59,12 +58,6 @@ func (api objectAPIHandlers) GetBucketReplicationHandler(w http.ResponseWriter,
|
|||||||
w.(http.Flusher).Flush()
|
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
|
// DeleteBucketWebsiteHandler - DELETE bucket website, a dummy api
|
||||||
func (api objectAPIHandlers) DeleteBucketWebsiteHandler(w http.ResponseWriter, r *http.Request) {
|
func (api objectAPIHandlers) DeleteBucketWebsiteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
writeSuccessResponseHeadersOnly(w)
|
writeSuccessResponseHeadersOnly(w)
|
||||||
@ -130,41 +123,3 @@ func (api objectAPIHandlers) GetBucketCorsHandler(w http.ResponseWriter, r *http
|
|||||||
|
|
||||||
w.(http.Flusher).Flush()
|
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()
|
|
||||||
}
|
|
||||||
|
13
cmd/fs-v1.go
13
cmd/fs-v1.go
@ -35,12 +35,12 @@ import (
|
|||||||
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
"github.com/minio/minio-go/v6/pkg/s3utils"
|
"github.com/minio/minio-go/v6/pkg/s3utils"
|
||||||
|
"github.com/minio/minio-go/v6/pkg/tags"
|
||||||
"github.com/minio/minio/cmd/config"
|
"github.com/minio/minio/cmd/config"
|
||||||
xhttp "github.com/minio/minio/cmd/http"
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
bucketsse "github.com/minio/minio/pkg/bucket/encryption"
|
bucketsse "github.com/minio/minio/pkg/bucket/encryption"
|
||||||
"github.com/minio/minio/pkg/bucket/lifecycle"
|
"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/bucket/policy"
|
||||||
"github.com/minio/minio/pkg/color"
|
"github.com/minio/minio/pkg/color"
|
||||||
"github.com/minio/minio/pkg/lock"
|
"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
|
// 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{})
|
oi, err := fs.GetObjectInfo(ctx, bucket, object, ObjectOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tagging.Tagging{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tags, err := tagging.FromString(oi.UserTags)
|
return tags.ParseObjectTags(oi.UserTags)
|
||||||
if err != nil {
|
|
||||||
return tagging.Tagging{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tags, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutObjectTag - replace or add tags to an existing object
|
// PutObjectTag - replace or add tags to an existing object
|
||||||
|
@ -22,9 +22,9 @@ import (
|
|||||||
|
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
|
|
||||||
|
"github.com/minio/minio-go/v6/pkg/tags"
|
||||||
bucketsse "github.com/minio/minio/pkg/bucket/encryption"
|
bucketsse "github.com/minio/minio/pkg/bucket/encryption"
|
||||||
"github.com/minio/minio/pkg/bucket/lifecycle"
|
"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/bucket/policy"
|
||||||
|
|
||||||
"github.com/minio/minio/pkg/madmin"
|
"github.com/minio/minio/pkg/madmin"
|
||||||
@ -205,9 +205,9 @@ func (a GatewayUnsupported) PutObjectTag(ctx context.Context, bucket, object str
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetObjectTag - not implemented.
|
// 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{})
|
logger.LogIf(ctx, NotImplemented{})
|
||||||
return tagging.Tagging{}, NotImplemented{}
|
return nil, NotImplemented{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteObjectTag - not implemented.
|
// DeleteObjectTag - not implemented.
|
||||||
|
@ -491,7 +491,6 @@ var notImplementedBucketResourceNames = map[string]bool{
|
|||||||
"metrics": true,
|
"metrics": true,
|
||||||
"replication": true,
|
"replication": true,
|
||||||
"requestPayment": true,
|
"requestPayment": true,
|
||||||
"tagging": true,
|
|
||||||
"versioning": true,
|
"versioning": true,
|
||||||
"website": true,
|
"website": true,
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,6 @@ import (
|
|||||||
xhttp "github.com/minio/minio/cmd/http"
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/auth"
|
"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/handlers"
|
||||||
"github.com/minio/minio/pkg/madmin"
|
"github.com/minio/minio/pkg/madmin"
|
||||||
)
|
)
|
||||||
@ -161,26 +160,6 @@ func extractMetadataFromMap(ctx context.Context, v map[string][]string, m map[st
|
|||||||
return nil
|
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
|
// The Query string for the redirect URL the client is
|
||||||
// redirected on successful upload.
|
// redirected on successful upload.
|
||||||
func getRedirectPostRawQuery(objInfo ObjectInfo) string {
|
func getRedirectPostRawQuery(objInfo ObjectInfo) string {
|
||||||
|
@ -22,9 +22,9 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/minio/minio-go/v6/pkg/encrypt"
|
"github.com/minio/minio-go/v6/pkg/encrypt"
|
||||||
|
"github.com/minio/minio-go/v6/pkg/tags"
|
||||||
bucketsse "github.com/minio/minio/pkg/bucket/encryption"
|
bucketsse "github.com/minio/minio/pkg/bucket/encryption"
|
||||||
"github.com/minio/minio/pkg/bucket/lifecycle"
|
"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/bucket/policy"
|
||||||
|
|
||||||
"github.com/minio/minio/pkg/madmin"
|
"github.com/minio/minio/pkg/madmin"
|
||||||
@ -138,6 +138,6 @@ type ObjectLayer interface {
|
|||||||
|
|
||||||
// ObjectTagging operations
|
// ObjectTagging operations
|
||||||
PutObjectTag(context.Context, string, string, string) error
|
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
|
DeleteObjectTag(context.Context, string, string) error
|
||||||
}
|
}
|
||||||
|
@ -34,13 +34,13 @@ import (
|
|||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
miniogo "github.com/minio/minio-go/v6"
|
miniogo "github.com/minio/minio-go/v6"
|
||||||
"github.com/minio/minio-go/v6/pkg/encrypt"
|
"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/etcd/dns"
|
||||||
"github.com/minio/minio/cmd/config/storageclass"
|
"github.com/minio/minio/cmd/config/storageclass"
|
||||||
"github.com/minio/minio/cmd/crypto"
|
"github.com/minio/minio/cmd/crypto"
|
||||||
xhttp "github.com/minio/minio/cmd/http"
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
objectlock "github.com/minio/minio/pkg/bucket/object/lock"
|
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/bucket/policy"
|
||||||
"github.com/minio/minio/pkg/event"
|
"github.com/minio/minio/pkg/event"
|
||||||
"github.com/minio/minio/pkg/handlers"
|
"github.com/minio/minio/pkg/handlers"
|
||||||
@ -647,24 +647,6 @@ func getCpObjMetadataFromHeader(ctx context.Context, r *http.Request, userMeta m
|
|||||||
return defaultMeta, nil
|
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.
|
// getRemoteInstanceTransport contains a singleton roundtripper.
|
||||||
var getRemoteInstanceTransport http.RoundTripper
|
var getRemoteInstanceTransport http.RoundTripper
|
||||||
var getRemoteInstanceTransportOnce sync.Once
|
var getRemoteInstanceTransportOnce sync.Once
|
||||||
@ -1056,14 +1038,18 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tags, err := getCpObjTagsFromHeader(ctx, r, srcInfo.UserTags)
|
objTags := srcInfo.UserTags
|
||||||
if err != nil {
|
// 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))
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if tags != "" {
|
if objTags != "" {
|
||||||
srcInfo.UserDefined[xhttp.AmzObjectTagging] = tags
|
srcInfo.UserDefined[xhttp.AmzObjectTagging] = objTags
|
||||||
}
|
}
|
||||||
|
|
||||||
srcInfo.UserDefined = objectlock.FilterObjectLockMetadata(srcInfo.UserDefined, true, true)
|
srcInfo.UserDefined = objectlock.FilterObjectLockMetadata(srcInfo.UserDefined, true, true)
|
||||||
@ -1265,12 +1251,13 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if tags := r.Header.Get(xhttp.AmzObjectTagging); tags != "" {
|
if objTags := r.Header.Get(xhttp.AmzObjectTagging); objTags != "" {
|
||||||
metadata[xhttp.AmzObjectTagging], err = extractTags(ctx, tags)
|
if _, err := tags.ParseObjectTags(objTags); err != nil {
|
||||||
if err != nil {
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
metadata[xhttp.AmzObjectTagging] = objTags
|
||||||
}
|
}
|
||||||
|
|
||||||
if rAuthType == authTypeStreamingSigned {
|
if rAuthType == authTypeStreamingSigned {
|
||||||
@ -2994,14 +2981,14 @@ func (api objectAPIHandlers) PutObjectTaggingHandler(w http.ResponseWriter, r *h
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tagging, err := tagging.ParseTagging(io.LimitReader(r.Body, r.ContentLength))
|
tags, err := tags.ParseObjectXML(io.LimitReader(r.Body, r.ContentLength))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put object tags
|
// Put object tags
|
||||||
err = objAPI.PutObjectTag(ctx, bucket, object, tagging.String())
|
err = objAPI.PutObjectTag(ctx, bucket, object, tags.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
@ -3036,8 +3023,7 @@ func (api objectAPIHandlers) DeleteObjectTaggingHandler(w http.ResponseWriter, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete object tags
|
// Delete object tags
|
||||||
err = objAPI.DeleteObjectTag(ctx, bucket, object)
|
if err = objAPI.DeleteObjectTag(ctx, bucket, object); err != nil && err != errConfigNotFound {
|
||||||
if err != nil {
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -26,13 +26,13 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/minio-go/v6/pkg/tags"
|
||||||
"github.com/minio/minio/cmd/config/storageclass"
|
"github.com/minio/minio/cmd/config/storageclass"
|
||||||
xhttp "github.com/minio/minio/cmd/http"
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/bpool"
|
"github.com/minio/minio/pkg/bpool"
|
||||||
bucketsse "github.com/minio/minio/pkg/bucket/encryption"
|
bucketsse "github.com/minio/minio/pkg/bucket/encryption"
|
||||||
"github.com/minio/minio/pkg/bucket/lifecycle"
|
"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/bucket/policy"
|
||||||
"github.com/minio/minio/pkg/dsync"
|
"github.com/minio/minio/pkg/dsync"
|
||||||
"github.com/minio/minio/pkg/madmin"
|
"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
|
// 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)
|
return s.getHashedSet(object).GetObjectTag(ctx, bucket, object)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,9 +25,9 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/minio/minio-go/v6/pkg/tags"
|
||||||
xhttp "github.com/minio/minio/cmd/http"
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"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/mimedb"
|
||||||
"github.com/minio/minio/pkg/sync/errgroup"
|
"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)
|
_, writeQuorum, err := objectQuorumFromMeta(ctx, xl, metaArr, errs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return toObjectErr(err, bucket, object)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, xlMeta := range metaArr {
|
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
|
// 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
|
// GetObjectInfo will return tag value as well
|
||||||
oi, err := xl.GetObjectInfo(ctx, bucket, object, ObjectOptions{})
|
oi, err := xl.GetObjectInfo(ctx, bucket, object, ObjectOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tagging.Tagging{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tags, err := tagging.FromString(oi.UserTags)
|
return tags.ParseObjectTags(oi.UserTags)
|
||||||
if err != nil {
|
|
||||||
return tagging.Tagging{}, err
|
|
||||||
}
|
|
||||||
return tags, nil
|
|
||||||
}
|
}
|
||||||
|
@ -26,11 +26,11 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/minio-go/v6/pkg/tags"
|
||||||
xhttp "github.com/minio/minio/cmd/http"
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
bucketsse "github.com/minio/minio/pkg/bucket/encryption"
|
bucketsse "github.com/minio/minio/pkg/bucket/encryption"
|
||||||
"github.com/minio/minio/pkg/bucket/lifecycle"
|
"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/bucket/policy"
|
||||||
"github.com/minio/minio/pkg/madmin"
|
"github.com/minio/minio/pkg/madmin"
|
||||||
"github.com/minio/minio/pkg/sync/errgroup"
|
"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
|
// 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() {
|
if z.SingleZone() {
|
||||||
return z.zones[0].GetObjectTag(ctx, bucket, object)
|
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 tags, nil
|
||||||
}
|
}
|
||||||
return tagging.Tagging{}, BucketNotFound{
|
return nil, BucketNotFound{
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2
go.mod
2
go.mod
@ -67,7 +67,7 @@ require (
|
|||||||
github.com/minio/hdfs/v3 v3.0.1
|
github.com/minio/hdfs/v3 v3.0.1
|
||||||
github.com/minio/highwayhash v1.0.0
|
github.com/minio/highwayhash v1.0.0
|
||||||
github.com/minio/lsync v1.0.1
|
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/parquet-go v0.0.0-20200414234858-838cfa8aae61
|
||||||
github.com/minio/sha256-simd v0.1.1
|
github.com/minio/sha256-simd v0.1.1
|
||||||
github.com/minio/simdjson-go v0.1.5-0.20200303142138-b17fe061ea37
|
github.com/minio/simdjson-go v0.1.5-0.20200303142138-b17fe061ea37
|
||||||
|
@ -19,14 +19,14 @@ package lifecycle
|
|||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"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.
|
// And - a tag to combine a prefix and multiple tags for lifecycle configuration rule.
|
||||||
type And struct {
|
type And struct {
|
||||||
XMLName xml.Name `xml:"And"`
|
XMLName xml.Name `xml:"And"`
|
||||||
Prefix string `xml:"Prefix,omitempty"`
|
Prefix string `xml:"Prefix,omitempty"`
|
||||||
Tags []tagging.Tag `xml:"Tag,omitempty"`
|
Tags []tags.Tag `xml:"Tag,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var errDuplicateTagKey = Errorf("Duplicate Tag Keys are not allowed")
|
var errDuplicateTagKey = Errorf("Duplicate Tag Keys are not allowed")
|
||||||
|
@ -19,7 +19,7 @@ package lifecycle
|
|||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
|
||||||
"github.com/minio/minio/pkg/bucket/object/tagging"
|
"github.com/minio/minio-go/v6/pkg/tags"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -31,7 +31,7 @@ type Filter struct {
|
|||||||
XMLName xml.Name `xml:"Filter"`
|
XMLName xml.Name `xml:"Filter"`
|
||||||
Prefix string
|
Prefix string
|
||||||
And And
|
And And
|
||||||
Tag tagging.Tag
|
Tag tags.Tag
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalXML - produces the xml representation of the Filter struct
|
// 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
|
// isEmpty - returns true if Filter tag is empty
|
||||||
func (f Filter) isEmpty() bool {
|
func (f Filter) isEmpty() bool {
|
||||||
return f.And.isEmpty() && f.Prefix == "" && f.Tag == tagging.Tag{}
|
return f.And.isEmpty() && f.Prefix == "" && f.Tag.IsEmpty()
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -109,6 +109,11 @@ const (
|
|||||||
// PutBucketObjectLockConfigurationAction - PutObjectLockConfiguration Rest API action
|
// PutBucketObjectLockConfigurationAction - PutObjectLockConfiguration Rest API action
|
||||||
PutBucketObjectLockConfigurationAction = "s3:PutBucketObjectLockConfiguration"
|
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 - Get Object Tags API action
|
||||||
GetObjectTaggingAction = "s3:GetObjectTagging"
|
GetObjectTaggingAction = "s3:GetObjectTagging"
|
||||||
// PutObjectTaggingAction - Put Object Tags API action
|
// PutObjectTaggingAction - Put Object Tags API action
|
||||||
@ -174,6 +179,8 @@ var supportedActions = map[Action]struct{}{
|
|||||||
PutObjectLegalHoldAction: {},
|
PutObjectLegalHoldAction: {},
|
||||||
PutBucketObjectLockConfigurationAction: {},
|
PutBucketObjectLockConfigurationAction: {},
|
||||||
GetBucketObjectLockConfigurationAction: {},
|
GetBucketObjectLockConfigurationAction: {},
|
||||||
|
PutBucketTaggingAction: {},
|
||||||
|
GetBucketTaggingAction: {},
|
||||||
BypassGovernanceRetentionAction: {},
|
BypassGovernanceRetentionAction: {},
|
||||||
GetObjectTaggingAction: {},
|
GetObjectTaggingAction: {},
|
||||||
PutObjectTaggingAction: {},
|
PutObjectTaggingAction: {},
|
||||||
@ -296,6 +303,8 @@ var actionConditionKeyMap = map[Action]condition.KeySet{
|
|||||||
|
|
||||||
GetBucketObjectLockConfigurationAction: condition.NewKeySet(condition.CommonKeys...),
|
GetBucketObjectLockConfigurationAction: condition.NewKeySet(condition.CommonKeys...),
|
||||||
PutBucketObjectLockConfigurationAction: condition.NewKeySet(condition.CommonKeys...),
|
PutBucketObjectLockConfigurationAction: condition.NewKeySet(condition.CommonKeys...),
|
||||||
|
GetBucketTaggingAction: condition.NewKeySet(condition.CommonKeys...),
|
||||||
|
PutBucketTaggingAction: condition.NewKeySet(condition.CommonKeys...),
|
||||||
PutObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...),
|
PutObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...),
|
||||||
GetObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...),
|
GetObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...),
|
||||||
DeleteObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...),
|
DeleteObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...),
|
||||||
|
@ -115,6 +115,12 @@ const (
|
|||||||
// PutBucketObjectLockConfigurationAction - PutBucketObjectLockConfiguration Rest API action
|
// PutBucketObjectLockConfigurationAction - PutBucketObjectLockConfiguration Rest API action
|
||||||
PutBucketObjectLockConfigurationAction = "s3:PutBucketObjectLockConfiguration"
|
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 - Get Object Tags API action
|
||||||
GetObjectTaggingAction = "s3:GetObjectTagging"
|
GetObjectTaggingAction = "s3:GetObjectTagging"
|
||||||
|
|
||||||
@ -164,6 +170,8 @@ var supportedActions = map[Action]struct{}{
|
|||||||
PutObjectLegalHoldAction: {},
|
PutObjectLegalHoldAction: {},
|
||||||
PutBucketObjectLockConfigurationAction: {},
|
PutBucketObjectLockConfigurationAction: {},
|
||||||
GetBucketObjectLockConfigurationAction: {},
|
GetBucketObjectLockConfigurationAction: {},
|
||||||
|
PutBucketTaggingAction: {},
|
||||||
|
GetBucketTaggingAction: {},
|
||||||
BypassGovernanceRetentionAction: {},
|
BypassGovernanceRetentionAction: {},
|
||||||
GetObjectTaggingAction: {},
|
GetObjectTaggingAction: {},
|
||||||
PutObjectTaggingAction: {},
|
PutObjectTaggingAction: {},
|
||||||
@ -333,6 +341,8 @@ var actionConditionKeyMap = map[Action]condition.KeySet{
|
|||||||
|
|
||||||
GetBucketObjectLockConfigurationAction: condition.NewKeySet(condition.CommonKeys...),
|
GetBucketObjectLockConfigurationAction: condition.NewKeySet(condition.CommonKeys...),
|
||||||
PutBucketObjectLockConfigurationAction: condition.NewKeySet(condition.CommonKeys...),
|
PutBucketObjectLockConfigurationAction: condition.NewKeySet(condition.CommonKeys...),
|
||||||
|
GetBucketTaggingAction: condition.NewKeySet(condition.CommonKeys...),
|
||||||
|
PutBucketTaggingAction: condition.NewKeySet(condition.CommonKeys...),
|
||||||
PutObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...),
|
PutObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...),
|
||||||
GetObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...),
|
GetObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...),
|
||||||
DeleteObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...),
|
DeleteObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...),
|
||||||
|
Loading…
Reference in New Issue
Block a user