diff --git a/api/data/info.go b/api/data/info.go index 3a9110e8..f706ed2a 100644 --- a/api/data/info.go +++ b/api/data/info.go @@ -25,6 +25,7 @@ type ( Created time.Time BasicACL uint32 LocationConstraint string + ObjectLockEnabled bool } // ObjectInfo holds S3 object data. diff --git a/api/handler/put.go b/api/handler/put.go index 0413c31f..dab5c356 100644 --- a/api/handler/put.go +++ b/api/handler/put.go @@ -565,18 +565,36 @@ func (h *handler) CreateBucketHandler(w http.ResponseWriter, r *http.Request) { p.Policy = h.cfg.DefaultPolicy } + p.ObjectLockEnabled = isLockEnabled(r.Header) + cid, err := h.obj.CreateBucket(r.Context(), &p) if err != nil { h.logAndSendError(w, "could not create bucket", reqInfo, err) return } + if p.ObjectLockEnabled { + vp := &layer.PutVersioningParams{ + Bucket: reqInfo.BucketName, + Settings: &layer.BucketSettings{VersioningEnabled: true}, + } + if _, err = h.obj.PutBucketVersioning(r.Context(), vp); err != nil { + h.log.Error("couldn't enable bucket versioning", zap.Stringer("container_id", cid), zap.Error(err)) + } + } + h.log.Info("bucket is created", zap.String("container_id", cid.String())) api.WriteSuccessResponseHeadersOnly(w) } +func isLockEnabled(header http.Header) bool { + lockEnabledStr := header.Get(api.AmzBucketObjectLockEnabled) + lockEnabled, _ := strconv.ParseBool(lockEnabledStr) + return lockEnabled +} + func checkBucketName(bucketName string) error { if len(bucketName) < 3 || len(bucketName) > 63 { return errors.GetAPIError(errors.ErrInvalidBucketName) diff --git a/api/headers.go b/api/headers.go index 87999a48..f9d39c90 100644 --- a/api/headers.go +++ b/api/headers.go @@ -46,6 +46,7 @@ const ( AmzGrantWrite = "X-Amz-Grant-Write" AmzExpectedBucketOwner = "X-Amz-Expected-Bucket-Owner" AmzSourceExpectedBucketOwner = "X-Amz-Source-Expected-Bucket-Owner" + AmzBucketObjectLockEnabled = "X-Amz-Bucket-Object-Lock-Enabled" ContainerID = "X-Container-Id" diff --git a/api/layer/container.go b/api/layer/container.go index e22e214a..0bb63747 100644 --- a/api/layer/container.go +++ b/api/layer/container.go @@ -27,13 +27,17 @@ type ( } ) -const locationConstraintAttr = ".s3-location-constraint" +const ( + attributeLocationConstraint = ".s3-location-constraint" + attributeLockEnabled = "LockEnabled" +) func (n *layer) containerInfo(ctx context.Context, idCnr *cid.ID) (*data.BucketInfo, error) { var ( err error res *container.Container rid = api.GetRequestID(ctx) + log = n.log.With(zap.Stringer("cid", idCnr), zap.String("request_id", rid)) info = &data.BucketInfo{ CID: idCnr, @@ -42,10 +46,7 @@ func (n *layer) containerInfo(ctx context.Context, idCnr *cid.ID) (*data.BucketI ) res, err = n.neoFS.Container(ctx, *idCnr) if err != nil { - n.log.Error("could not fetch container", - zap.Stringer("cid", idCnr), - zap.String("request_id", rid), - zap.Error(err)) + log.Error("could not fetch container", zap.Error(err)) if strings.Contains(err.Error(), "container not found") { return nil, errors.GetAPIError(errors.ErrNoSuchBucket) @@ -63,26 +64,27 @@ func (n *layer) containerInfo(ctx context.Context, idCnr *cid.ID) (*data.BucketI case container.AttributeTimestamp: unix, err := strconv.ParseInt(attr.Value(), 10, 64) if err != nil { - n.log.Error("could not parse container creation time", - zap.Stringer("cid", idCnr), - zap.String("request_id", rid), - zap.String("created_at", val), - zap.Error(err)) + log.Error("could not parse container creation time", + zap.String("created_at", val), zap.Error(err)) continue } info.Created = time.Unix(unix, 0) - case locationConstraintAttr: + case attributeLocationConstraint: info.LocationConstraint = val + case attributeLockEnabled: + info.ObjectLockEnabled, err = strconv.ParseBool(val) + if err != nil { + log.Error("could not parse container object lock enabled attribute", + zap.String("lock_enabled", val), zap.Error(err)) + } } } - if err := n.bucketCache.Put(info); err != nil { - n.log.Warn("could not put bucket info into cache", - zap.Stringer("cid", idCnr), - zap.String("bucket_name", info.Name), - zap.Error(err)) + if err = n.bucketCache.Put(info); err != nil { + log.Warn("could not put bucket info into cache", + zap.String("bucket_name", info.Name), zap.Error(err)) } return info, nil @@ -133,9 +135,10 @@ func (n *layer) createContainer(ctx context.Context, p *CreateBucketParams) (*ci if p.LocationConstraint != "" { locConstAttr = container.NewAttribute() - locConstAttr.SetKey(locationConstraintAttr) + locConstAttr.SetKey(attributeLocationConstraint) locConstAttr.SetValue(p.LocationConstraint) } + //todo add lock enabled attr if bktInfo.CID, err = n.neoFS.CreateContainer(ctx, PrmContainerCreate{ Creator: *bktInfo.Owner, diff --git a/api/layer/layer.go b/api/layer/layer.go index 0e18081e..f93d6204 100644 --- a/api/layer/layer.go +++ b/api/layer/layer.go @@ -336,6 +336,7 @@ type ( EACL *eacl.Table SessionToken *session.Token LocationConstraint string + ObjectLockEnabled bool } // PutBucketACLParams stores put bucket acl request parameters. PutBucketACLParams struct {