forked from TrueCloudLab/frostfs-s3-gw
[#195] Implement PUT, GET locks to certain object
Signed-off-by: Denis Kirillov <denis@nspcc.ru>
This commit is contained in:
parent
8553158b81
commit
7d6271be8a
10 changed files with 337 additions and 36 deletions
|
@ -104,3 +104,9 @@ func (o *ObjectInfo) Address() *address.Address {
|
||||||
|
|
||||||
// TagsObject returns name of system object for tags.
|
// TagsObject returns name of system object for tags.
|
||||||
func (o *ObjectInfo) TagsObject() string { return ".tagset." + o.Name + "." + o.Version() }
|
func (o *ObjectInfo) TagsObject() string { return ".tagset." + o.Name + "." + o.Version() }
|
||||||
|
|
||||||
|
// LegalHoldObject returns name of system object for lock object.
|
||||||
|
func (o *ObjectInfo) LegalHoldObject() string { return ".lock." + o.Name + "." + o.Version() }
|
||||||
|
|
||||||
|
// RetentionObject returns name of system object for retention lock object.
|
||||||
|
func (o *ObjectInfo) RetentionObject() string { return ".retention." + o.Name + "." + o.Version() }
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
package data
|
package data
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
ObjectLockConfiguration struct {
|
ObjectLockConfiguration struct {
|
||||||
|
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ObjectLockConfiguration" json:"-"`
|
||||||
ObjectLockEnabled string `xml:"ObjectLockEnabled" json:"ObjectLockEnabled"`
|
ObjectLockEnabled string `xml:"ObjectLockEnabled" json:"ObjectLockEnabled"`
|
||||||
Rule *ObjectLockRule `xml:"Rule" json:"Rule"`
|
Rule *ObjectLockRule `xml:"Rule" json:"Rule"`
|
||||||
}
|
}
|
||||||
|
@ -18,6 +22,17 @@ type (
|
||||||
Years int64 `xml:"Years" json:"Years"`
|
Years int64 `xml:"Years" json:"Years"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LegalHold struct {
|
||||||
|
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ LegalHold" json:"-"`
|
||||||
|
Status string `xml:"Status" json:"Status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
Retention struct {
|
||||||
|
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ Retention" json:"-"`
|
||||||
|
Mode string `xml:"Mode" json:"Mode"`
|
||||||
|
RetainUntilDate string `xml:"RetainUntilDate" json:"RetainUntilDate"`
|
||||||
|
}
|
||||||
|
|
||||||
ObjectLock struct {
|
ObjectLock struct {
|
||||||
Until time.Time
|
Until time.Time
|
||||||
LegalHold bool
|
LegalHold bool
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-s3-gw/api"
|
"github.com/nspcc-dev/neofs-s3-gw/api"
|
||||||
|
@ -20,6 +21,7 @@ const (
|
||||||
governanceMode = "GOVERNANCE"
|
governanceMode = "GOVERNANCE"
|
||||||
complianceMode = "COMPLIANCE"
|
complianceMode = "COMPLIANCE"
|
||||||
legalHoldOn = "ON"
|
legalHoldOn = "ON"
|
||||||
|
legalHoldOff = "OFF"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h *handler) PutBucketObjectLockConfigHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) PutBucketObjectLockConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -108,6 +110,240 @@ func (h *handler) GetBucketObjectLockConfigHandler(w http.ResponseWriter, r *htt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *handler) PutObjectLegalHoldHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
reqInfo := api.GetReqInfo(r.Context())
|
||||||
|
|
||||||
|
bktInfo, err := h.obj.GetBucketInfo(r.Context(), reqInfo.BucketName)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = checkOwner(bktInfo, r.Header.Get(api.AmzExpectedBucketOwner)); err != nil {
|
||||||
|
h.logAndSendError(w, "expected owner doesn't match", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bktInfo.ObjectLockEnabled {
|
||||||
|
h.logAndSendError(w, "object lock disabled", reqInfo,
|
||||||
|
apiErrors.GetAPIError(apiErrors.ErrObjectLockConfigurationNotFound))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
legalHold := &data.LegalHold{}
|
||||||
|
if err = xml.NewDecoder(r.Body).Decode(legalHold); err != nil {
|
||||||
|
h.logAndSendError(w, "couldn't parse legal hold configuration", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if legalHold.Status != legalHoldOn && legalHold.Status != legalHoldOff {
|
||||||
|
h.logAndSendError(w, "invalid legal hold status", reqInfo,
|
||||||
|
fmt.Errorf("invalid status %s", legalHold.Status))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &layer.HeadObjectParams{
|
||||||
|
Bucket: reqInfo.BucketName,
|
||||||
|
Object: reqInfo.ObjectName,
|
||||||
|
VersionID: reqInfo.URL.Query().Get(api.QueryVersionID),
|
||||||
|
}
|
||||||
|
|
||||||
|
objInfo, err := h.obj.GetObjectInfo(r.Context(), p)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "could not get object info", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lockInfo, err := h.obj.HeadSystemObject(r.Context(), bktInfo, objInfo.LegalHoldObject())
|
||||||
|
if err != nil && !apiErrors.IsS3Error(err, apiErrors.ErrNoSuchKey) {
|
||||||
|
h.logAndSendError(w, "couldn't head lock object", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if lockInfo == nil && legalHold.Status == legalHoldOff ||
|
||||||
|
lockInfo != nil && legalHold.Status == legalHoldOn {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if lockInfo != nil {
|
||||||
|
if err = h.obj.DeleteSystemObject(r.Context(), bktInfo, objInfo.LegalHoldObject()); err != nil {
|
||||||
|
h.logAndSendError(w, "couldn't delete legal hold", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ps := &layer.PutSystemObjectParams{
|
||||||
|
BktInfo: bktInfo,
|
||||||
|
ObjName: objInfo.LegalHoldObject(),
|
||||||
|
Lock: &data.ObjectLock{LegalHold: true},
|
||||||
|
}
|
||||||
|
if _, err = h.obj.PutSystemObject(r.Context(), ps); err != nil {
|
||||||
|
h.logAndSendError(w, "couldn't put legal hold", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) GetObjectLegalHoldHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
reqInfo := api.GetReqInfo(r.Context())
|
||||||
|
|
||||||
|
bktInfo, err := h.obj.GetBucketInfo(r.Context(), reqInfo.BucketName)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = checkOwner(bktInfo, r.Header.Get(api.AmzExpectedBucketOwner)); err != nil {
|
||||||
|
h.logAndSendError(w, "expected owner doesn't match", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bktInfo.ObjectLockEnabled {
|
||||||
|
h.logAndSendError(w, "object lock disabled", reqInfo,
|
||||||
|
apiErrors.GetAPIError(apiErrors.ErrObjectLockConfigurationNotFound))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &layer.HeadObjectParams{
|
||||||
|
Bucket: reqInfo.BucketName,
|
||||||
|
Object: reqInfo.ObjectName,
|
||||||
|
VersionID: reqInfo.URL.Query().Get(api.QueryVersionID),
|
||||||
|
}
|
||||||
|
|
||||||
|
objInfo, err := h.obj.GetObjectInfo(r.Context(), p)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "could not get object info", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lockInfo, err := h.obj.HeadSystemObject(r.Context(), bktInfo, objInfo.LegalHoldObject())
|
||||||
|
if err != nil && !apiErrors.IsS3Error(err, apiErrors.ErrNoSuchKey) {
|
||||||
|
h.logAndSendError(w, "couldn't head lock object", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
legalHold := &data.LegalHold{Status: legalHoldOff}
|
||||||
|
if lockInfo != nil {
|
||||||
|
legalHold.Status = legalHoldOn
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = api.EncodeToResponse(w, legalHold); err != nil {
|
||||||
|
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) PutObjectRetentionHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
reqInfo := api.GetReqInfo(r.Context())
|
||||||
|
|
||||||
|
bktInfo, err := h.obj.GetBucketInfo(r.Context(), reqInfo.BucketName)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = checkOwner(bktInfo, r.Header.Get(api.AmzExpectedBucketOwner)); err != nil {
|
||||||
|
h.logAndSendError(w, "expected owner doesn't match", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bktInfo.ObjectLockEnabled {
|
||||||
|
h.logAndSendError(w, "object lock disabled", reqInfo,
|
||||||
|
apiErrors.GetAPIError(apiErrors.ErrObjectLockConfigurationNotFound))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
retention := &data.Retention{}
|
||||||
|
if err = xml.NewDecoder(r.Body).Decode(retention); err != nil {
|
||||||
|
h.logAndSendError(w, "couldn't parse object retention", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lock, err := formObjectLockFromRetention(retention, r.Header)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "invalid retention configuration", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &layer.HeadObjectParams{
|
||||||
|
Bucket: reqInfo.BucketName,
|
||||||
|
Object: reqInfo.ObjectName,
|
||||||
|
VersionID: reqInfo.URL.Query().Get(api.QueryVersionID),
|
||||||
|
}
|
||||||
|
|
||||||
|
objInfo, err := h.obj.GetObjectInfo(r.Context(), p)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "could not get object info", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lockInfo, err := h.obj.HeadSystemObject(r.Context(), bktInfo, objInfo.RetentionObject())
|
||||||
|
if err != nil && !apiErrors.IsS3Error(err, apiErrors.ErrNoSuchKey) {
|
||||||
|
h.logAndSendError(w, "couldn't head lock object", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if lockInfo != nil && lockInfo.Headers[layer.AttributeComplianceMode] != "" {
|
||||||
|
h.logAndSendError(w, "couldn't change compliance lock mode", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ps := &layer.PutSystemObjectParams{
|
||||||
|
BktInfo: bktInfo,
|
||||||
|
ObjName: objInfo.RetentionObject(),
|
||||||
|
Lock: lock,
|
||||||
|
}
|
||||||
|
if _, err = h.obj.PutSystemObject(r.Context(), ps); err != nil {
|
||||||
|
h.logAndSendError(w, "couldn't put legal hold", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) GetObjectRetentionHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
reqInfo := api.GetReqInfo(r.Context())
|
||||||
|
|
||||||
|
bktInfo, err := h.obj.GetBucketInfo(r.Context(), reqInfo.BucketName)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = checkOwner(bktInfo, r.Header.Get(api.AmzExpectedBucketOwner)); err != nil {
|
||||||
|
h.logAndSendError(w, "expected owner doesn't match", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bktInfo.ObjectLockEnabled {
|
||||||
|
h.logAndSendError(w, "object lock disabled", reqInfo,
|
||||||
|
apiErrors.GetAPIError(apiErrors.ErrObjectLockConfigurationNotFound))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &layer.HeadObjectParams{
|
||||||
|
Bucket: reqInfo.BucketName,
|
||||||
|
Object: reqInfo.ObjectName,
|
||||||
|
VersionID: reqInfo.URL.Query().Get(api.QueryVersionID),
|
||||||
|
}
|
||||||
|
|
||||||
|
objInfo, err := h.obj.GetObjectInfo(r.Context(), p)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "could not get object info", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lockInfo, err := h.obj.HeadSystemObject(r.Context(), bktInfo, objInfo.RetentionObject())
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "couldn't head lock object", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
retention := &data.Retention{
|
||||||
|
Mode: governanceMode,
|
||||||
|
RetainUntilDate: lockInfo.Headers[layer.AttributeRetainUntil],
|
||||||
|
}
|
||||||
|
if lockInfo.Headers[layer.AttributeComplianceMode] != "" {
|
||||||
|
retention.Mode = complianceMode
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = api.EncodeToResponse(w, retention); err != nil {
|
||||||
|
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func checkLockConfiguration(conf *data.ObjectLockConfiguration) error {
|
func checkLockConfiguration(conf *data.ObjectLockConfiguration) error {
|
||||||
if conf.ObjectLockEnabled != "" && conf.ObjectLockEnabled != enabledValue {
|
if conf.ObjectLockEnabled != "" && conf.ObjectLockEnabled != enabledValue {
|
||||||
return fmt.Errorf("invalid ObjectLockEnabled value: %s", conf.ObjectLockEnabled)
|
return fmt.Errorf("invalid ObjectLockEnabled value: %s", conf.ObjectLockEnabled)
|
||||||
|
@ -180,3 +416,34 @@ func existLockHeaders(header http.Header) bool {
|
||||||
header.Get(api.AmzObjectLockLegalHold) != "" ||
|
header.Get(api.AmzObjectLockLegalHold) != "" ||
|
||||||
header.Get(api.AmzObjectLockRetainUntilDate) != ""
|
header.Get(api.AmzObjectLockRetainUntilDate) != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func formObjectLockFromRetention(retention *data.Retention, header http.Header) (*data.ObjectLock, error) {
|
||||||
|
var err error
|
||||||
|
var bypassGovernance bool
|
||||||
|
bypass := header.Get(api.AmzBypassGovernanceRetention)
|
||||||
|
if bypass != "" {
|
||||||
|
if bypassGovernance, err = strconv.ParseBool(bypass); err != nil {
|
||||||
|
return nil, fmt.Errorf("couldn't parse '%s' header", api.AmzBypassGovernanceRetention)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if retention.Mode != governanceMode && retention.Mode != complianceMode {
|
||||||
|
return nil, fmt.Errorf("invalid retention mode: %s", retention.Mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
retentionDate, err := time.Parse(time.RFC3339, retention.RetainUntilDate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("couldn't parse retain until date: %s", retention.RetainUntilDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
lock := &data.ObjectLock{
|
||||||
|
Until: retentionDate,
|
||||||
|
IsCompliance: retention.Mode == complianceMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !lock.IsCompliance && !bypassGovernance {
|
||||||
|
return nil, fmt.Errorf("you cannot bypase governance mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
return lock, nil
|
||||||
|
}
|
||||||
|
|
|
@ -11,22 +11,6 @@ func (h *handler) SelectObjectContentHandler(w http.ResponseWriter, r *http.Requ
|
||||||
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) GetObjectRetentionHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *handler) GetObjectLegalHoldHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *handler) PutObjectRetentionHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *handler) PutObjectLegalHoldHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *handler) GetBucketLifecycleHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) GetBucketLifecycleHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,7 @@ const (
|
||||||
AmzObjectLockLegalHold = "X-Amz-Object-Lock-Legal-Hold"
|
AmzObjectLockLegalHold = "X-Amz-Object-Lock-Legal-Hold"
|
||||||
AmzObjectLockMode = "X-Amz-Object-Lock-Mode"
|
AmzObjectLockMode = "X-Amz-Object-Lock-Mode"
|
||||||
AmzObjectLockRetainUntilDate = "X-Amz-Object-Lock-Retain-Until-Date"
|
AmzObjectLockRetainUntilDate = "X-Amz-Object-Lock-Retain-Until-Date"
|
||||||
|
AmzBypassGovernanceRetention = "X-Amz-Bypass-Governance-Retention"
|
||||||
|
|
||||||
ContainerID = "X-Container-Id"
|
ContainerID = "X-Container-Id"
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,7 @@ func (n *layer) GetBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) (*d
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *layer) DeleteBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) error {
|
func (n *layer) DeleteBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) error {
|
||||||
return n.deleteSystemObject(ctx, bktInfo, bktInfo.CORSObjectName())
|
return n.DeleteSystemObject(ctx, bktInfo, bktInfo.CORSObjectName())
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkCORS(cors *data.CORSConfiguration) error {
|
func checkCORS(cors *data.CORSConfiguration) error {
|
||||||
|
|
|
@ -352,6 +352,7 @@ type (
|
||||||
Metadata map[string]string
|
Metadata map[string]string
|
||||||
Prefix string
|
Prefix string
|
||||||
Reader io.Reader
|
Reader io.Reader
|
||||||
|
Lock *data.ObjectLock
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListObjectVersionsParams stores list objects versions parameters.
|
// ListObjectVersionsParams stores list objects versions parameters.
|
||||||
|
@ -398,11 +399,13 @@ type (
|
||||||
DeleteBucket(ctx context.Context, p *DeleteBucketParams) error
|
DeleteBucket(ctx context.Context, p *DeleteBucketParams) error
|
||||||
|
|
||||||
GetObject(ctx context.Context, p *GetObjectParams) error
|
GetObject(ctx context.Context, p *GetObjectParams) error
|
||||||
|
HeadSystemObject(ctx context.Context, bktInfo *data.BucketInfo, name string) (*data.ObjectInfo, error)
|
||||||
GetObjectInfo(ctx context.Context, p *HeadObjectParams) (*data.ObjectInfo, error)
|
GetObjectInfo(ctx context.Context, p *HeadObjectParams) (*data.ObjectInfo, error)
|
||||||
GetObjectTagging(ctx context.Context, p *data.ObjectInfo) (map[string]string, error)
|
GetObjectTagging(ctx context.Context, p *data.ObjectInfo) (map[string]string, error)
|
||||||
GetBucketTagging(ctx context.Context, bucket string) (map[string]string, error)
|
GetBucketTagging(ctx context.Context, bucket string) (map[string]string, error)
|
||||||
|
|
||||||
PutObject(ctx context.Context, p *PutObjectParams) (*data.ObjectInfo, error)
|
PutObject(ctx context.Context, p *PutObjectParams) (*data.ObjectInfo, error)
|
||||||
|
PutSystemObject(ctx context.Context, p *PutSystemObjectParams) (*data.ObjectInfo, error)
|
||||||
PutObjectTagging(ctx context.Context, p *PutTaggingParams) error
|
PutObjectTagging(ctx context.Context, p *PutTaggingParams) error
|
||||||
PutBucketTagging(ctx context.Context, bucket string, tagSet map[string]string) error
|
PutBucketTagging(ctx context.Context, bucket string, tagSet map[string]string) error
|
||||||
|
|
||||||
|
@ -413,6 +416,7 @@ type (
|
||||||
ListObjectVersions(ctx context.Context, p *ListObjectVersionsParams) (*ListObjectVersionsInfo, error)
|
ListObjectVersions(ctx context.Context, p *ListObjectVersionsParams) (*ListObjectVersionsInfo, error)
|
||||||
|
|
||||||
DeleteObjects(ctx context.Context, bucket string, objects []*VersionedObject) ([]*VersionedObject, error)
|
DeleteObjects(ctx context.Context, bucket string, objects []*VersionedObject) ([]*VersionedObject, error)
|
||||||
|
DeleteSystemObject(ctx context.Context, bktInfo *data.BucketInfo, name string) error
|
||||||
DeleteObjectTagging(ctx context.Context, p *data.ObjectInfo) error
|
DeleteObjectTagging(ctx context.Context, p *data.ObjectInfo) error
|
||||||
DeleteBucketTagging(ctx context.Context, bucket string) error
|
DeleteBucketTagging(ctx context.Context, bucket string) error
|
||||||
|
|
||||||
|
@ -631,7 +635,7 @@ func (n *layer) GetObjectTagging(ctx context.Context, oi *data.ObjectInfo) (map[
|
||||||
Owner: oi.Owner,
|
Owner: oi.Owner,
|
||||||
}
|
}
|
||||||
|
|
||||||
objInfo, err := n.headSystemObject(ctx, bktInfo, oi.TagsObject())
|
objInfo, err := n.HeadSystemObject(ctx, bktInfo, oi.TagsObject())
|
||||||
if err != nil && !errors.IsS3Error(err, errors.ErrNoSuchKey) {
|
if err != nil && !errors.IsS3Error(err, errors.ErrNoSuchKey) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -646,7 +650,7 @@ func (n *layer) GetBucketTagging(ctx context.Context, bucketName string) (map[st
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
objInfo, err := n.headSystemObject(ctx, bktInfo, formBucketTagObjectName(bucketName))
|
objInfo, err := n.HeadSystemObject(ctx, bktInfo, formBucketTagObjectName(bucketName))
|
||||||
if err != nil && !errors.IsS3Error(err, errors.ErrNoSuchKey) {
|
if err != nil && !errors.IsS3Error(err, errors.ErrNoSuchKey) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -686,13 +690,10 @@ func (n *layer) PutObjectTagging(ctx context.Context, p *PutTaggingParams) error
|
||||||
Reader: nil,
|
Reader: nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := n.putSystemObject(ctx, s); err != nil {
|
_, err := n.PutSystemObject(ctx, s)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PutBucketTagging into storage.
|
// PutBucketTagging into storage.
|
||||||
func (n *layer) PutBucketTagging(ctx context.Context, bucketName string, tagSet map[string]string) error {
|
func (n *layer) PutBucketTagging(ctx context.Context, bucketName string, tagSet map[string]string) error {
|
||||||
bktInfo, err := n.GetBucketInfo(ctx, bucketName)
|
bktInfo, err := n.GetBucketInfo(ctx, bucketName)
|
||||||
|
@ -708,20 +709,17 @@ func (n *layer) PutBucketTagging(ctx context.Context, bucketName string, tagSet
|
||||||
Reader: nil,
|
Reader: nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = n.putSystemObject(ctx, s); err != nil {
|
_, err = n.PutSystemObject(ctx, s)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteObjectTagging from storage.
|
// DeleteObjectTagging from storage.
|
||||||
func (n *layer) DeleteObjectTagging(ctx context.Context, p *data.ObjectInfo) error {
|
func (n *layer) DeleteObjectTagging(ctx context.Context, p *data.ObjectInfo) error {
|
||||||
bktInfo, err := n.GetBucketInfo(ctx, p.Bucket)
|
bktInfo, err := n.GetBucketInfo(ctx, p.Bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return n.deleteSystemObject(ctx, bktInfo, p.TagsObject())
|
return n.DeleteSystemObject(ctx, bktInfo, p.TagsObject())
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteBucketTagging from storage.
|
// DeleteBucketTagging from storage.
|
||||||
|
@ -731,7 +729,7 @@ func (n *layer) DeleteBucketTagging(ctx context.Context, bucketName string) erro
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return n.deleteSystemObject(ctx, bktInfo, formBucketTagObjectName(bucketName))
|
return n.DeleteSystemObject(ctx, bktInfo, formBucketTagObjectName(bucketName))
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopyObject from one bucket into another bucket.
|
// CopyObject from one bucket into another bucket.
|
||||||
|
|
|
@ -202,6 +202,7 @@ func (n *layer) objectPut(ctx context.Context, bkt *data.BucketInfo, p *PutObjec
|
||||||
|
|
||||||
if p.Lock != nil {
|
if p.Lock != nil {
|
||||||
// todo form lock system object
|
// todo form lock system object
|
||||||
|
// attributes = append(attributes, attributesFromLock(p.Lock)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
meta, err := n.objectHead(ctx, bkt.CID, id)
|
meta, err := n.objectHead(ctx, bkt.CID, id)
|
||||||
|
|
|
@ -14,7 +14,12 @@ import (
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (n *layer) putSystemObject(ctx context.Context, p *PutSystemObjectParams) (*data.ObjectInfo, error) {
|
const (
|
||||||
|
AttributeComplianceMode = ".s3-compliance-mode"
|
||||||
|
AttributeRetainUntil = ".s3-retain-until"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (n *layer) PutSystemObject(ctx context.Context, p *PutSystemObjectParams) (*data.ObjectInfo, error) {
|
||||||
objInfo, err := n.putSystemObjectIntoNeoFS(ctx, p)
|
objInfo, err := n.putSystemObjectIntoNeoFS(ctx, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -27,7 +32,7 @@ func (n *layer) putSystemObject(ctx context.Context, p *PutSystemObjectParams) (
|
||||||
return objInfo, nil
|
return objInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *layer) headSystemObject(ctx context.Context, bkt *data.BucketInfo, objName string) (*data.ObjectInfo, error) {
|
func (n *layer) HeadSystemObject(ctx context.Context, bkt *data.BucketInfo, objName string) (*data.ObjectInfo, error) {
|
||||||
if objInfo := n.systemCache.GetObject(systemObjectKey(bkt, objName)); objInfo != nil {
|
if objInfo := n.systemCache.GetObject(systemObjectKey(bkt, objName)); objInfo != nil {
|
||||||
return objInfo, nil
|
return objInfo, nil
|
||||||
}
|
}
|
||||||
|
@ -44,7 +49,7 @@ func (n *layer) headSystemObject(ctx context.Context, bkt *data.BucketInfo, objN
|
||||||
return versions.getLast(), nil
|
return versions.getLast(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *layer) deleteSystemObject(ctx context.Context, bktInfo *data.BucketInfo, name string) error {
|
func (n *layer) DeleteSystemObject(ctx context.Context, bktInfo *data.BucketInfo, name string) error {
|
||||||
f := &findParams{
|
f := &findParams{
|
||||||
attr: [2]string{objectSystemAttributeName, name},
|
attr: [2]string{objectSystemAttributeName, name},
|
||||||
cid: bktInfo.CID,
|
cid: bktInfo.CID,
|
||||||
|
@ -92,6 +97,12 @@ func (n *layer) putSystemObjectIntoNeoFS(ctx context.Context, p *PutSystemObject
|
||||||
v = tagEmptyMark
|
v = tagEmptyMark
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.Lock != nil {
|
||||||
|
// todo form lock system object
|
||||||
|
|
||||||
|
prm.Attributes = append(prm.Attributes, attributesFromLock(p.Lock)...)
|
||||||
|
}
|
||||||
|
|
||||||
prm.Attributes = append(prm.Attributes, [2]string{k, v})
|
prm.Attributes = append(prm.Attributes, [2]string{k, v})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,3 +273,21 @@ func (n *layer) PutBucketSettings(ctx context.Context, p *PutSettingsParams) err
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func attributesFromLock(lock *data.ObjectLock) []*object.Attribute {
|
||||||
|
var result []*object.Attribute
|
||||||
|
if !lock.LegalHold {
|
||||||
|
attrRetainUntil := object.NewAttribute()
|
||||||
|
attrRetainUntil.SetKey(AttributeRetainUntil)
|
||||||
|
attrRetainUntil.SetValue(lock.Until.Format(time.RFC3339))
|
||||||
|
result = append(result, attrRetainUntil)
|
||||||
|
if lock.IsCompliance {
|
||||||
|
attrCompliance := object.NewAttribute()
|
||||||
|
attrCompliance.SetKey(AttributeComplianceMode)
|
||||||
|
attrCompliance.SetValue(strconv.FormatBool(true))
|
||||||
|
result = append(result, attrCompliance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
|
@ -399,7 +399,7 @@ func contains(list []string, elem string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *layer) getBucketSettings(ctx context.Context, bktInfo *data.BucketInfo) (*data.BucketSettings, error) {
|
func (n *layer) getBucketSettings(ctx context.Context, bktInfo *data.BucketInfo) (*data.BucketSettings, error) {
|
||||||
objInfo, err := n.headSystemObject(ctx, bktInfo, bktInfo.SettingsObjectName())
|
objInfo, err := n.HeadSystemObject(ctx, bktInfo, bktInfo.SettingsObjectName())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue