forked from TrueCloudLab/frostfs-s3-gw
Compare commits
4 commits
95dfa4657a
...
8e61b055da
Author | SHA1 | Date | |
---|---|---|---|
8e61b055da | |||
971006a28c | |||
527e0dc612 | |||
3213dd7236 |
22 changed files with 372 additions and 70 deletions
|
@ -1,4 +1,4 @@
|
||||||
FROM golang:1.21 as builder
|
FROM golang:1.21 AS builder
|
||||||
|
|
||||||
ARG BUILD=now
|
ARG BUILD=now
|
||||||
ARG REPO=git.frostfs.info/TrueCloudLab/frostfs-s3-gw
|
ARG REPO=git.frostfs.info/TrueCloudLab/frostfs-s3-gw
|
||||||
|
|
|
@ -87,7 +87,9 @@ type (
|
||||||
func (b *BucketInfo) SettingsObjectName() string { return bktSettingsObject }
|
func (b *BucketInfo) SettingsObjectName() string { return bktSettingsObject }
|
||||||
|
|
||||||
// CORSObjectName returns a system name for a bucket CORS configuration file.
|
// CORSObjectName returns a system name for a bucket CORS configuration file.
|
||||||
func (b *BucketInfo) CORSObjectName() string { return bktCORSConfigurationObject }
|
func (b *BucketInfo) CORSObjectName() string {
|
||||||
|
return b.CID.EncodeToString() + bktCORSConfigurationObject
|
||||||
|
}
|
||||||
|
|
||||||
// VersionID returns object version from ObjectInfo.
|
// VersionID returns object version from ObjectInfo.
|
||||||
func (o *ObjectInfo) VersionID() string { return o.ID.EncodeToString() }
|
func (o *ObjectInfo) VersionID() string { return o.ID.EncodeToString() }
|
||||||
|
|
|
@ -237,9 +237,18 @@ func (h *handler) DeleteBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
sessionToken = boxData.Gate.SessionTokenForDelete()
|
sessionToken = boxData.Gate.SessionTokenForDelete()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
skipObjCheck := false
|
||||||
|
if value, ok := r.Header[api.AmzForceBucketDelete]; ok {
|
||||||
|
s := value[0]
|
||||||
|
if s == "true" {
|
||||||
|
skipObjCheck = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err = h.obj.DeleteBucket(r.Context(), &layer.DeleteBucketParams{
|
if err = h.obj.DeleteBucket(r.Context(), &layer.DeleteBucketParams{
|
||||||
BktInfo: bktInfo,
|
BktInfo: bktInfo,
|
||||||
SessionToken: sessionToken,
|
SessionToken: sessionToken,
|
||||||
|
SkipCheck: skipObjCheck,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
h.logAndSendError(w, "couldn't delete bucket", reqInfo, err)
|
h.logAndSendError(w, "couldn't delete bucket", reqInfo, err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -232,7 +232,7 @@ func (h *handler) ListBucketObjectVersionsHandler(w http.ResponseWriter, r *http
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response := encodeListObjectVersionsToResponse(info, p.BktInfo.Name, h.cfg.MD5Enabled())
|
response := encodeListObjectVersionsToResponse(p, info, p.BktInfo.Name, h.cfg.MD5Enabled())
|
||||||
if err = middleware.EncodeToResponse(w, response); err != nil {
|
if err = middleware.EncodeToResponse(w, response); err != nil {
|
||||||
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
||||||
}
|
}
|
||||||
|
@ -264,24 +264,28 @@ func parseListObjectVersionsRequest(reqInfo *middleware.ReqInfo) (*layer.ListObj
|
||||||
return &res, nil
|
return &res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func encodeListObjectVersionsToResponse(info *layer.ListObjectVersionsInfo, bucketName string, md5Enabled bool) *ListObjectsVersionsResponse {
|
func encodeListObjectVersionsToResponse(p *layer.ListObjectVersionsParams, info *layer.ListObjectVersionsInfo, bucketName string, md5Enabled bool) *ListObjectsVersionsResponse {
|
||||||
res := ListObjectsVersionsResponse{
|
res := ListObjectsVersionsResponse{
|
||||||
Name: bucketName,
|
Name: bucketName,
|
||||||
IsTruncated: info.IsTruncated,
|
IsTruncated: info.IsTruncated,
|
||||||
KeyMarker: info.KeyMarker,
|
KeyMarker: s3PathEncode(info.KeyMarker, p.Encode),
|
||||||
NextKeyMarker: info.NextKeyMarker,
|
NextKeyMarker: s3PathEncode(info.NextKeyMarker, p.Encode),
|
||||||
NextVersionIDMarker: info.NextVersionIDMarker,
|
NextVersionIDMarker: info.NextVersionIDMarker,
|
||||||
VersionIDMarker: info.VersionIDMarker,
|
VersionIDMarker: info.VersionIDMarker,
|
||||||
|
Prefix: s3PathEncode(p.Prefix, p.Encode),
|
||||||
|
Delimiter: s3PathEncode(p.Delimiter, p.Encode),
|
||||||
|
EncodingType: p.Encode,
|
||||||
|
MaxKeys: p.MaxKeys,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, prefix := range info.CommonPrefixes {
|
for _, prefix := range info.CommonPrefixes {
|
||||||
res.CommonPrefixes = append(res.CommonPrefixes, CommonPrefix{Prefix: prefix})
|
res.CommonPrefixes = append(res.CommonPrefixes, CommonPrefix{Prefix: s3PathEncode(prefix, p.Encode)})
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ver := range info.Version {
|
for _, ver := range info.Version {
|
||||||
res.Version = append(res.Version, ObjectVersionResponse{
|
res.Version = append(res.Version, ObjectVersionResponse{
|
||||||
IsLatest: ver.IsLatest,
|
IsLatest: ver.IsLatest,
|
||||||
Key: ver.NodeVersion.FilePath,
|
Key: s3PathEncode(ver.NodeVersion.FilePath, p.Encode),
|
||||||
LastModified: ver.NodeVersion.Created.UTC().Format(time.RFC3339),
|
LastModified: ver.NodeVersion.Created.UTC().Format(time.RFC3339),
|
||||||
Owner: Owner{
|
Owner: Owner{
|
||||||
ID: ver.NodeVersion.Owner.String(),
|
ID: ver.NodeVersion.Owner.String(),
|
||||||
|
@ -297,7 +301,7 @@ func encodeListObjectVersionsToResponse(info *layer.ListObjectVersionsInfo, buck
|
||||||
for _, del := range info.DeleteMarker {
|
for _, del := range info.DeleteMarker {
|
||||||
res.DeleteMarker = append(res.DeleteMarker, DeleteMarkerEntry{
|
res.DeleteMarker = append(res.DeleteMarker, DeleteMarkerEntry{
|
||||||
IsLatest: del.IsLatest,
|
IsLatest: del.IsLatest,
|
||||||
Key: del.NodeVersion.FilePath,
|
Key: s3PathEncode(del.NodeVersion.FilePath, p.Encode),
|
||||||
LastModified: del.NodeVersion.Created.UTC().Format(time.RFC3339),
|
LastModified: del.NodeVersion.Created.UTC().Format(time.RFC3339),
|
||||||
Owner: Owner{
|
Owner: Owner{
|
||||||
ID: del.NodeVersion.Owner.String(),
|
ID: del.NodeVersion.Owner.String(),
|
||||||
|
|
|
@ -675,6 +675,49 @@ func TestMintVersioningListObjectVersionsVersionIDContinuation(t *testing.T) {
|
||||||
require.Equal(t, page1.NextVersionIDMarker, page2.VersionIDMarker)
|
require.Equal(t, page1.NextVersionIDMarker, page2.VersionIDMarker)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestListObjectVersionsEncoding(t *testing.T) {
|
||||||
|
hc := prepareHandlerContext(t)
|
||||||
|
|
||||||
|
bktName := "bucket-for-listing-versions-encoding"
|
||||||
|
bktInfo := createTestBucket(hc, bktName)
|
||||||
|
putBucketVersioning(t, hc, bktName, true)
|
||||||
|
|
||||||
|
objects := []string{"foo()/bar", "foo()/bar/xyzzy", "auux ab/thud", "asdf+b"}
|
||||||
|
for _, objName := range objects {
|
||||||
|
createTestObject(hc, bktInfo, objName, encryption.Params{})
|
||||||
|
}
|
||||||
|
deleteObject(t, hc, bktName, "auux ab/thud", "")
|
||||||
|
|
||||||
|
listResponse := listObjectsVersionsURL(hc, bktName, "foo(", ")", "", "", -1)
|
||||||
|
|
||||||
|
require.Len(t, listResponse.CommonPrefixes, 1)
|
||||||
|
require.Equal(t, "foo%28%29", listResponse.CommonPrefixes[0].Prefix)
|
||||||
|
require.Len(t, listResponse.Version, 0)
|
||||||
|
require.Len(t, listResponse.DeleteMarker, 0)
|
||||||
|
require.Equal(t, "foo%28", listResponse.Prefix)
|
||||||
|
require.Equal(t, "%29", listResponse.Delimiter)
|
||||||
|
require.Equal(t, "url", listResponse.EncodingType)
|
||||||
|
require.Equal(t, maxObjectList, listResponse.MaxKeys)
|
||||||
|
|
||||||
|
listResponse = listObjectsVersions(hc, bktName, "", "", "", "", 1)
|
||||||
|
require.Empty(t, listResponse.EncodingType)
|
||||||
|
|
||||||
|
listResponse = listObjectsVersionsURL(hc, bktName, "", "", listResponse.NextKeyMarker, listResponse.NextVersionIDMarker, 3)
|
||||||
|
|
||||||
|
require.Len(t, listResponse.CommonPrefixes, 0)
|
||||||
|
require.Len(t, listResponse.Version, 2)
|
||||||
|
require.Equal(t, "auux%20ab/thud", listResponse.Version[0].Key)
|
||||||
|
require.False(t, listResponse.Version[0].IsLatest)
|
||||||
|
require.Equal(t, "foo%28%29/bar", listResponse.Version[1].Key)
|
||||||
|
require.Len(t, listResponse.DeleteMarker, 1)
|
||||||
|
require.Equal(t, "auux%20ab/thud", listResponse.DeleteMarker[0].Key)
|
||||||
|
require.True(t, listResponse.DeleteMarker[0].IsLatest)
|
||||||
|
require.Equal(t, "asdf%2Bb", listResponse.KeyMarker)
|
||||||
|
require.Equal(t, "foo%28%29/bar", listResponse.NextKeyMarker)
|
||||||
|
require.Equal(t, "url", listResponse.EncodingType)
|
||||||
|
require.Equal(t, 3, listResponse.MaxKeys)
|
||||||
|
}
|
||||||
|
|
||||||
func checkVersionsNames(t *testing.T, versions *ListObjectsVersionsResponse, names []string) {
|
func checkVersionsNames(t *testing.T, versions *ListObjectsVersionsResponse, names []string) {
|
||||||
for i, v := range versions.Version {
|
for i, v := range versions.Version {
|
||||||
require.Equal(t, names[i], v.Key)
|
require.Equal(t, names[i], v.Key)
|
||||||
|
@ -777,6 +820,14 @@ func listObjectsV1(hc *handlerContext, bktName, prefix, delimiter, marker string
|
||||||
}
|
}
|
||||||
|
|
||||||
func listObjectsVersions(hc *handlerContext, bktName, prefix, delimiter, keyMarker, versionIDMarker string, maxKeys int) *ListObjectsVersionsResponse {
|
func listObjectsVersions(hc *handlerContext, bktName, prefix, delimiter, keyMarker, versionIDMarker string, maxKeys int) *ListObjectsVersionsResponse {
|
||||||
|
return listObjectsVersionsBase(hc, bktName, prefix, delimiter, keyMarker, versionIDMarker, maxKeys, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func listObjectsVersionsURL(hc *handlerContext, bktName, prefix, delimiter, keyMarker, versionIDMarker string, maxKeys int) *ListObjectsVersionsResponse {
|
||||||
|
return listObjectsVersionsBase(hc, bktName, prefix, delimiter, keyMarker, versionIDMarker, maxKeys, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func listObjectsVersionsBase(hc *handlerContext, bktName, prefix, delimiter, keyMarker, versionIDMarker string, maxKeys int, encode bool) *ListObjectsVersionsResponse {
|
||||||
query := prepareCommonListObjectsQuery(prefix, delimiter, maxKeys)
|
query := prepareCommonListObjectsQuery(prefix, delimiter, maxKeys)
|
||||||
if len(keyMarker) != 0 {
|
if len(keyMarker) != 0 {
|
||||||
query.Add("key-marker", keyMarker)
|
query.Add("key-marker", keyMarker)
|
||||||
|
@ -784,6 +835,9 @@ func listObjectsVersions(hc *handlerContext, bktName, prefix, delimiter, keyMark
|
||||||
if len(versionIDMarker) != 0 {
|
if len(versionIDMarker) != 0 {
|
||||||
query.Add("version-id-marker", versionIDMarker)
|
query.Add("version-id-marker", versionIDMarker)
|
||||||
}
|
}
|
||||||
|
if encode {
|
||||||
|
query.Add("encoding-type", "url")
|
||||||
|
}
|
||||||
|
|
||||||
w, r := prepareTestFullRequest(hc, bktName, "", query, nil)
|
w, r := prepareTestFullRequest(hc, bktName, "", query, nil)
|
||||||
hc.Handler().ListBucketObjectVersionsHandler(w, r)
|
hc.Handler().ListBucketObjectVersionsHandler(w, r)
|
||||||
|
|
|
@ -176,6 +176,9 @@ type ListObjectsVersionsResponse struct {
|
||||||
DeleteMarker []DeleteMarkerEntry `xml:"DeleteMarker"`
|
DeleteMarker []DeleteMarkerEntry `xml:"DeleteMarker"`
|
||||||
Version []ObjectVersionResponse `xml:"Version"`
|
Version []ObjectVersionResponse `xml:"Version"`
|
||||||
CommonPrefixes []CommonPrefix `xml:"CommonPrefixes"`
|
CommonPrefixes []CommonPrefix `xml:"CommonPrefixes"`
|
||||||
|
Prefix string `xml:"Prefix"`
|
||||||
|
Delimiter string `xml:"Delimiter,omitempty"`
|
||||||
|
MaxKeys int `xml:"MaxKeys"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// VersioningConfiguration contains VersioningConfiguration XML representation.
|
// VersioningConfiguration contains VersioningConfiguration XML representation.
|
||||||
|
|
|
@ -62,6 +62,7 @@ const (
|
||||||
AmzMaxParts = "X-Amz-Max-Parts"
|
AmzMaxParts = "X-Amz-Max-Parts"
|
||||||
AmzPartNumberMarker = "X-Amz-Part-Number-Marker"
|
AmzPartNumberMarker = "X-Amz-Part-Number-Marker"
|
||||||
AmzStorageClass = "X-Amz-Storage-Class"
|
AmzStorageClass = "X-Amz-Storage-Class"
|
||||||
|
AmzForceBucketDelete = "X-Amz-Force-Delete-Bucket"
|
||||||
|
|
||||||
AmzServerSideEncryptionCustomerAlgorithm = "x-amz-server-side-encryption-customer-algorithm"
|
AmzServerSideEncryptionCustomerAlgorithm = "x-amz-server-side-encryption-customer-algorithm"
|
||||||
AmzServerSideEncryptionCustomerKey = "x-amz-server-side-encryption-customer-key"
|
AmzServerSideEncryptionCustomerKey = "x-amz-server-side-encryption-customer-key"
|
||||||
|
|
|
@ -233,7 +233,7 @@ func (c *Cache) PutSettings(owner user.ID, bktInfo *data.BucketInfo, settings *d
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) GetCORS(owner user.ID, bkt *data.BucketInfo) *data.CORSConfiguration {
|
func (c *Cache) GetCORS(owner user.ID, bkt *data.BucketInfo) *data.CORSConfiguration {
|
||||||
key := bkt.Name + bkt.CORSObjectName()
|
key := bkt.CORSObjectName()
|
||||||
|
|
||||||
if !c.accessCache.Get(owner, key) {
|
if !c.accessCache.Get(owner, key) {
|
||||||
return nil
|
return nil
|
||||||
|
@ -243,7 +243,7 @@ func (c *Cache) GetCORS(owner user.ID, bkt *data.BucketInfo) *data.CORSConfigura
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) PutCORS(owner user.ID, bkt *data.BucketInfo, cors *data.CORSConfiguration) {
|
func (c *Cache) PutCORS(owner user.ID, bkt *data.BucketInfo, cors *data.CORSConfiguration) {
|
||||||
key := bkt.Name + bkt.CORSObjectName()
|
key := bkt.CORSObjectName()
|
||||||
|
|
||||||
if err := c.systemCache.PutCORS(key, cors); err != nil {
|
if err := c.systemCache.PutCORS(key, cors); err != nil {
|
||||||
c.logger.Warn(logs.CouldntCacheCors, zap.String("bucket", bkt.Name), zap.Error(err))
|
c.logger.Warn(logs.CouldntCacheCors, zap.String("bucket", bkt.Name), zap.Error(err))
|
||||||
|
@ -255,5 +255,5 @@ func (c *Cache) PutCORS(owner user.ID, bkt *data.BucketInfo, cors *data.CORSConf
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) DeleteCORS(bktInfo *data.BucketInfo) {
|
func (c *Cache) DeleteCORS(bktInfo *data.BucketInfo) {
|
||||||
c.systemCache.Delete(bktInfo.Name + bktInfo.CORSObjectName())
|
c.systemCache.Delete(bktInfo.CORSObjectName())
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,8 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||||
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -37,30 +39,35 @@ func (n *Layer) PutBucketCORS(ctx context.Context, p *PutCORSParams) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
prm := PrmObjectCreate{
|
prm := PrmObjectCreate{
|
||||||
Container: p.BktInfo.CID,
|
|
||||||
Payload: &buf,
|
Payload: &buf,
|
||||||
Filepath: p.BktInfo.CORSObjectName(),
|
Filepath: p.BktInfo.CORSObjectName(),
|
||||||
CreationTime: TimeNow(ctx),
|
CreationTime: TimeNow(ctx),
|
||||||
CopiesNumber: p.CopiesNumbers,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, objID, _, _, err := n.objectPutAndHash(ctx, prm, p.BktInfo)
|
var corsBkt *data.BucketInfo
|
||||||
|
if n.corsCnrInfo == nil {
|
||||||
|
corsBkt = p.BktInfo
|
||||||
|
prm.CopiesNumber = p.CopiesNumbers
|
||||||
|
} else {
|
||||||
|
corsBkt = n.corsCnrInfo
|
||||||
|
prm.PrmAuth.PrivateKey = &n.gateKey.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
prm.Container = corsBkt.CID
|
||||||
|
|
||||||
|
_, objID, _, _, err := n.objectPutAndHash(ctx, prm, corsBkt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("put system object: %w", err)
|
return fmt.Errorf("put cors object: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
objIDToDelete, err := n.treeService.PutBucketCORS(ctx, p.BktInfo, objID)
|
objToDelete, err := n.treeService.PutBucketCORS(ctx, p.BktInfo, newAddress(corsBkt.CID, objID))
|
||||||
objIDToDeleteNotFound := errorsStd.Is(err, ErrNoNodeToRemove)
|
objToDeleteNotFound := errorsStd.Is(err, ErrNoNodeToRemove)
|
||||||
if err != nil && !objIDToDeleteNotFound {
|
if err != nil && !objToDeleteNotFound {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !objIDToDeleteNotFound {
|
if !objToDeleteNotFound {
|
||||||
if err = n.objectDelete(ctx, p.BktInfo, objIDToDelete); err != nil {
|
n.deleteCORSObject(ctx, p.BktInfo, objToDelete)
|
||||||
n.reqLogger(ctx).Error(logs.CouldntDeleteCorsObject, zap.Error(err),
|
|
||||||
zap.String("cnrID", p.BktInfo.CID.EncodeToString()),
|
|
||||||
zap.String("objID", objIDToDelete.EncodeToString()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
n.cache.PutCORS(n.BearerOwner(ctx), p.BktInfo, cors)
|
n.cache.PutCORS(n.BearerOwner(ctx), p.BktInfo, cors)
|
||||||
|
@ -68,12 +75,25 @@ func (n *Layer) PutBucketCORS(ctx context.Context, p *PutCORSParams) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deleteCORSObject removes object and logs in case of error.
|
||||||
|
func (n *Layer) deleteCORSObject(ctx context.Context, bktInfo *data.BucketInfo, addr oid.Address) {
|
||||||
|
var prmAuth PrmAuth
|
||||||
|
corsBkt := bktInfo
|
||||||
|
if !addr.Container().Equals(bktInfo.CID) && !addr.Container().Equals(cid.ID{}) {
|
||||||
|
corsBkt = &data.BucketInfo{CID: addr.Container()}
|
||||||
|
prmAuth.PrivateKey = &n.gateKey.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := n.objectDeleteWithAuth(ctx, corsBkt, addr.Object(), prmAuth); err != nil {
|
||||||
|
n.reqLogger(ctx).Error(logs.CouldntDeleteCorsObject, zap.Error(err),
|
||||||
|
zap.String("cnrID", corsBkt.CID.EncodeToString()),
|
||||||
|
zap.String("objID", addr.Object().EncodeToString()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (n *Layer) GetBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) (*data.CORSConfiguration, error) {
|
func (n *Layer) GetBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) (*data.CORSConfiguration, error) {
|
||||||
cors, err := n.getCORS(ctx, bktInfo)
|
cors, err := n.getCORS(ctx, bktInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errorsStd.Is(err, ErrNodeNotFound) {
|
|
||||||
return nil, fmt.Errorf("%w: %s", errors.GetAPIError(errors.ErrNoSuchCORSConfiguration), err.Error())
|
|
||||||
}
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,14 +101,22 @@ 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 {
|
||||||
objID, err := n.treeService.DeleteBucketCORS(ctx, bktInfo)
|
obj, err := n.treeService.DeleteBucketCORS(ctx, bktInfo)
|
||||||
objIDNotFound := errorsStd.Is(err, ErrNoNodeToRemove)
|
objNotFound := errorsStd.Is(err, ErrNoNodeToRemove)
|
||||||
if err != nil && !objIDNotFound {
|
if err != nil && !objNotFound {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !objIDNotFound {
|
|
||||||
if err = n.objectDelete(ctx, bktInfo, objID); err != nil {
|
if !objNotFound {
|
||||||
return err
|
var prmAuth PrmAuth
|
||||||
|
corsBkt := bktInfo
|
||||||
|
if !obj.Container().Equals(bktInfo.CID) && !obj.Container().Equals(cid.ID{}) {
|
||||||
|
corsBkt = &data.BucketInfo{CID: obj.Container()}
|
||||||
|
prmAuth.PrivateKey = &n.gateKey.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = n.objectDeleteWithAuth(ctx, corsBkt, obj.Object(), prmAuth); err != nil {
|
||||||
|
return fmt.Errorf("delete cors object: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,8 @@ type (
|
||||||
cache *Cache
|
cache *Cache
|
||||||
treeService TreeService
|
treeService TreeService
|
||||||
features FeatureSettings
|
features FeatureSettings
|
||||||
|
gateKey *keys.PrivateKey
|
||||||
|
corsCnrInfo *data.BucketInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
Config struct {
|
Config struct {
|
||||||
|
@ -64,6 +66,8 @@ type (
|
||||||
Resolver BucketResolver
|
Resolver BucketResolver
|
||||||
TreeService TreeService
|
TreeService TreeService
|
||||||
Features FeatureSettings
|
Features FeatureSettings
|
||||||
|
GateKey *keys.PrivateKey
|
||||||
|
CORSCnrInfo *data.BucketInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// AnonymousKey contains data for anonymous requests.
|
// AnonymousKey contains data for anonymous requests.
|
||||||
|
@ -166,6 +170,7 @@ type (
|
||||||
DeleteBucketParams struct {
|
DeleteBucketParams struct {
|
||||||
BktInfo *data.BucketInfo
|
BktInfo *data.BucketInfo
|
||||||
SessionToken *session.Container
|
SessionToken *session.Container
|
||||||
|
SkipCheck bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListObjectVersionsParams stores list objects versions parameters.
|
// ListObjectVersionsParams stores list objects versions parameters.
|
||||||
|
@ -236,6 +241,8 @@ func NewLayer(log *zap.Logger, frostFS FrostFS, config *Config) *Layer {
|
||||||
cache: config.Cache,
|
cache: config.Cache,
|
||||||
treeService: config.TreeService,
|
treeService: config.TreeService,
|
||||||
features: config.Features,
|
features: config.Features,
|
||||||
|
gateKey: config.GateKey,
|
||||||
|
corsCnrInfo: config.CORSCnrInfo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,6 +295,10 @@ func (n *Layer) reqLogger(ctx context.Context) *zap.Logger {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Layer) prepareAuthParameters(ctx context.Context, prm *PrmAuth, bktOwner user.ID) {
|
func (n *Layer) prepareAuthParameters(ctx context.Context, prm *PrmAuth, bktOwner user.ID) {
|
||||||
|
if prm.BearerToken != nil || prm.PrivateKey != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if bd, err := middleware.GetBoxData(ctx); err == nil && bd.Gate.BearerToken != nil {
|
if bd, err := middleware.GetBoxData(ctx); err == nil && bd.Gate.BearerToken != nil {
|
||||||
if bd.Gate.BearerToken.Impersonate() || bktOwner.Equals(bearer.ResolveIssuer(*bd.Gate.BearerToken)) {
|
if bd.Gate.BearerToken.Impersonate() || bktOwner.Equals(bearer.ResolveIssuer(*bd.Gate.BearerToken)) {
|
||||||
prm.BearerToken = bd.Gate.BearerToken
|
prm.BearerToken = bd.Gate.BearerToken
|
||||||
|
@ -802,10 +813,25 @@ func (n *Layer) DeleteBucket(ctx context.Context, p *DeleteBucketParams) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(res) != 0 {
|
if len(res) != 0 && !p.SkipCheck {
|
||||||
return errors.GetAPIError(errors.ErrBucketNotEmpty)
|
return errors.GetAPIError(errors.ErrBucketNotEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
n.cache.DeleteBucket(p.BktInfo)
|
n.cache.DeleteBucket(p.BktInfo)
|
||||||
return n.frostFS.DeleteContainer(ctx, p.BktInfo.CID, p.SessionToken)
|
|
||||||
|
corsObj, err := n.treeService.GetBucketCORS(ctx, p.BktInfo)
|
||||||
|
if err != nil {
|
||||||
|
n.reqLogger(ctx).Error(logs.GetBucketCors, zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = n.frostFS.DeleteContainer(ctx, p.BktInfo.CID, p.SessionToken)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("delete container: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !corsObj.Container().Equals(p.BktInfo.CID) && !corsObj.Container().Equals(cid.ID{}) {
|
||||||
|
n.deleteCORSObject(ctx, p.BktInfo, corsObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -151,7 +151,17 @@ func (n *Layer) initFrostFSObjectPayloadReader(ctx context.Context, p getFrostFS
|
||||||
|
|
||||||
// objectGet returns an object with payload in the object.
|
// objectGet returns an object with payload in the object.
|
||||||
func (n *Layer) objectGet(ctx context.Context, bktInfo *data.BucketInfo, objID oid.ID) (*object.Object, error) {
|
func (n *Layer) objectGet(ctx context.Context, bktInfo *data.BucketInfo, objID oid.ID) (*object.Object, error) {
|
||||||
|
return n.objectGetBase(ctx, bktInfo, objID, PrmAuth{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// objectGetWithAuth returns an object with payload in the object. Uses provided PrmAuth.
|
||||||
|
func (n *Layer) objectGetWithAuth(ctx context.Context, bktInfo *data.BucketInfo, objID oid.ID, auth PrmAuth) (*object.Object, error) {
|
||||||
|
return n.objectGetBase(ctx, bktInfo, objID, auth)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Layer) objectGetBase(ctx context.Context, bktInfo *data.BucketInfo, objID oid.ID, auth PrmAuth) (*object.Object, error) {
|
||||||
prm := PrmObjectRead{
|
prm := PrmObjectRead{
|
||||||
|
PrmAuth: auth,
|
||||||
Container: bktInfo.CID,
|
Container: bktInfo.CID,
|
||||||
Object: objID,
|
Object: objID,
|
||||||
WithHeader: true,
|
WithHeader: true,
|
||||||
|
@ -460,7 +470,17 @@ func (n *Layer) headVersion(ctx context.Context, bkt *data.BucketInfo, p *HeadOb
|
||||||
|
|
||||||
// objectDelete puts tombstone object into frostfs.
|
// objectDelete puts tombstone object into frostfs.
|
||||||
func (n *Layer) objectDelete(ctx context.Context, bktInfo *data.BucketInfo, idObj oid.ID) error {
|
func (n *Layer) objectDelete(ctx context.Context, bktInfo *data.BucketInfo, idObj oid.ID) error {
|
||||||
|
return n.objectDeleteBase(ctx, bktInfo, idObj, PrmAuth{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// objectDeleteWithAuth puts tombstone object into frostfs. Uses provided PrmAuth.
|
||||||
|
func (n *Layer) objectDeleteWithAuth(ctx context.Context, bktInfo *data.BucketInfo, idObj oid.ID, auth PrmAuth) error {
|
||||||
|
return n.objectDeleteBase(ctx, bktInfo, idObj, auth)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Layer) objectDeleteBase(ctx context.Context, bktInfo *data.BucketInfo, idObj oid.ID, auth PrmAuth) error {
|
||||||
prm := PrmObjectDelete{
|
prm := PrmObjectDelete{
|
||||||
|
PrmAuth: auth,
|
||||||
Container: bktInfo.CID,
|
Container: bktInfo.CID,
|
||||||
Object: idObj,
|
Object: idObj,
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -159,19 +160,26 @@ func (n *Layer) getCORS(ctx context.Context, bkt *data.BucketInfo) (*data.CORSCo
|
||||||
return cors, nil
|
return cors, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
objID, err := n.treeService.GetBucketCORS(ctx, bkt)
|
addr, err := n.treeService.GetBucketCORS(ctx, bkt)
|
||||||
objIDNotFound := errorsStd.Is(err, ErrNodeNotFound)
|
objNotFound := errorsStd.Is(err, ErrNodeNotFound)
|
||||||
if err != nil && !objIDNotFound {
|
if err != nil && !objNotFound {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if objIDNotFound {
|
if objNotFound {
|
||||||
return nil, fmt.Errorf("%w: %s", errors.GetAPIError(errors.ErrNoSuchCORSConfiguration), err.Error())
|
return nil, fmt.Errorf("%w: %s", errors.GetAPIError(errors.ErrNoSuchCORSConfiguration), err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
obj, err := n.objectGet(ctx, bkt, objID)
|
var prmAuth PrmAuth
|
||||||
|
corsBkt := bkt
|
||||||
|
if !addr.Container().Equals(bkt.CID) && !addr.Container().Equals(cid.ID{}) {
|
||||||
|
corsBkt = &data.BucketInfo{CID: addr.Container()}
|
||||||
|
prmAuth.PrivateKey = &n.gateKey.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
obj, err := n.objectGetWithAuth(ctx, corsBkt, addr.Object(), prmAuth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("get cors object: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cors := &data.CORSConfiguration{}
|
cors := &data.CORSConfiguration{}
|
||||||
|
|
|
@ -110,36 +110,39 @@ func (t *TreeServiceMock) GetSettingsNode(_ context.Context, bktInfo *data.Bucke
|
||||||
return settings, nil
|
return settings, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TreeServiceMock) GetBucketCORS(_ context.Context, bktInfo *data.BucketInfo) (oid.ID, error) {
|
func (t *TreeServiceMock) GetBucketCORS(_ context.Context, bktInfo *data.BucketInfo) (oid.Address, error) {
|
||||||
systemMap, ok := t.system[bktInfo.CID.EncodeToString()]
|
systemMap, ok := t.system[bktInfo.CID.EncodeToString()]
|
||||||
if !ok {
|
if !ok {
|
||||||
return oid.ID{}, nil
|
return oid.Address{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
node, ok := systemMap["cors"]
|
node, ok := systemMap["cors"]
|
||||||
if !ok {
|
if !ok {
|
||||||
return oid.ID{}, nil
|
return oid.Address{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return node.OID, nil
|
var addr oid.Address
|
||||||
|
addr.SetContainer(bktInfo.CID)
|
||||||
|
addr.SetObject(node.OID)
|
||||||
|
return addr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TreeServiceMock) PutBucketCORS(_ context.Context, bktInfo *data.BucketInfo, objID oid.ID) (oid.ID, error) {
|
func (t *TreeServiceMock) PutBucketCORS(_ context.Context, bktInfo *data.BucketInfo, addr oid.Address) (oid.Address, error) {
|
||||||
systemMap, ok := t.system[bktInfo.CID.EncodeToString()]
|
systemMap, ok := t.system[bktInfo.CID.EncodeToString()]
|
||||||
if !ok {
|
if !ok {
|
||||||
systemMap = make(map[string]*data.BaseNodeVersion)
|
systemMap = make(map[string]*data.BaseNodeVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
systemMap["cors"] = &data.BaseNodeVersion{
|
systemMap["cors"] = &data.BaseNodeVersion{
|
||||||
OID: objID,
|
OID: addr.Object(),
|
||||||
}
|
}
|
||||||
|
|
||||||
t.system[bktInfo.CID.EncodeToString()] = systemMap
|
t.system[bktInfo.CID.EncodeToString()] = systemMap
|
||||||
|
|
||||||
return oid.ID{}, ErrNoNodeToRemove
|
return oid.Address{}, ErrNoNodeToRemove
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TreeServiceMock) DeleteBucketCORS(context.Context, *data.BucketInfo) (oid.ID, error) {
|
func (t *TreeServiceMock) DeleteBucketCORS(context.Context, *data.BucketInfo) (oid.Address, error) {
|
||||||
panic("implement me")
|
panic("implement me")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,17 +21,17 @@ type TreeService interface {
|
||||||
// GetBucketCORS gets an object id that corresponds to object with bucket CORS.
|
// GetBucketCORS gets an object id that corresponds to object with bucket CORS.
|
||||||
//
|
//
|
||||||
// If object id is not found returns ErrNodeNotFound error.
|
// If object id is not found returns ErrNodeNotFound error.
|
||||||
GetBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) (oid.ID, error)
|
GetBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) (oid.Address, error)
|
||||||
|
|
||||||
// PutBucketCORS puts a node to a system tree and returns objectID of a previous cors config which must be deleted in FrostFS.
|
// PutBucketCORS puts a node to a system tree and returns objectID of a previous cors config which must be deleted in FrostFS.
|
||||||
//
|
//
|
||||||
// If object id to remove is not found returns ErrNoNodeToRemove error.
|
// If object id to remove is not found returns ErrNoNodeToRemove error.
|
||||||
PutBucketCORS(ctx context.Context, bktInfo *data.BucketInfo, objID oid.ID) (oid.ID, error)
|
PutBucketCORS(ctx context.Context, bktInfo *data.BucketInfo, addr oid.Address) (oid.Address, error)
|
||||||
|
|
||||||
// DeleteBucketCORS removes a node from a system tree and returns objID which must be deleted in FrostFS.
|
// DeleteBucketCORS removes a node from a system tree and returns objID which must be deleted in FrostFS.
|
||||||
//
|
//
|
||||||
// If object id to remove is not found returns ErrNoNodeToRemove error.
|
// If object id to remove is not found returns ErrNoNodeToRemove error.
|
||||||
DeleteBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) (oid.ID, error)
|
DeleteBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) (oid.Address, error)
|
||||||
|
|
||||||
GetObjectTagging(ctx context.Context, bktInfo *data.BucketInfo, objVersion *data.NodeVersion) (map[string]string, error)
|
GetObjectTagging(ctx context.Context, bktInfo *data.BucketInfo, objVersion *data.NodeVersion) (map[string]string, error)
|
||||||
PutObjectTagging(ctx context.Context, bktInfo *data.BucketInfo, objVersion *data.NodeVersion, tagSet map[string]string) error
|
PutObjectTagging(ctx context.Context, bktInfo *data.BucketInfo, objVersion *data.NodeVersion, tagSet map[string]string) error
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/cache"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/cache"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/handler"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/handler"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
||||||
s3middleware "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
s3middleware "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||||
|
@ -37,6 +38,8 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/wallet"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/wallet"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/metrics"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/metrics"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/service/tree"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/service/tree"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
||||||
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
|
||||||
treepool "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool/tree"
|
treepool "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool/tree"
|
||||||
|
@ -153,13 +156,13 @@ func (a *App) init(ctx context.Context) {
|
||||||
a.setRuntimeParameters()
|
a.setRuntimeParameters()
|
||||||
a.initFrostfsID(ctx)
|
a.initFrostfsID(ctx)
|
||||||
a.initPolicyStorage(ctx)
|
a.initPolicyStorage(ctx)
|
||||||
a.initAPI()
|
a.initAPI(ctx)
|
||||||
a.initMetrics()
|
a.initMetrics()
|
||||||
a.initServers(ctx)
|
a.initServers(ctx)
|
||||||
a.initTracing(ctx)
|
a.initTracing(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) initLayer() {
|
func (a *App) initLayer(ctx context.Context) {
|
||||||
a.initResolver()
|
a.initResolver()
|
||||||
|
|
||||||
// prepare random key for anonymous requests
|
// prepare random key for anonymous requests
|
||||||
|
@ -171,6 +174,14 @@ func (a *App) initLayer() {
|
||||||
var gateOwner user.ID
|
var gateOwner user.ID
|
||||||
user.IDFromKey(&gateOwner, a.key.PrivateKey.PublicKey)
|
user.IDFromKey(&gateOwner, a.key.PrivateKey.PublicKey)
|
||||||
|
|
||||||
|
var corsCnrInfo *data.BucketInfo
|
||||||
|
if a.cfg.IsSet(cfgContainersCORS) {
|
||||||
|
corsCnrInfo, err = a.fetchContainerInfo(ctx, cfgContainersCORS)
|
||||||
|
if err != nil {
|
||||||
|
a.log.Fatal(logs.CouldNotFetchCORSContainerInfo, zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
layerCfg := &layer.Config{
|
layerCfg := &layer.Config{
|
||||||
Cache: layer.NewCache(getCacheOptions(a.cfg, a.log)),
|
Cache: layer.NewCache(getCacheOptions(a.cfg, a.log)),
|
||||||
AnonKey: layer.AnonymousKey{
|
AnonKey: layer.AnonymousKey{
|
||||||
|
@ -180,6 +191,8 @@ func (a *App) initLayer() {
|
||||||
Resolver: a.bucketResolver,
|
Resolver: a.bucketResolver,
|
||||||
TreeService: tree.NewTree(services.NewPoolWrapper(a.treePool), a.log),
|
TreeService: tree.NewTree(services.NewPoolWrapper(a.treePool), a.log),
|
||||||
Features: a.settings,
|
Features: a.settings,
|
||||||
|
GateKey: a.key,
|
||||||
|
CORSCnrInfo: corsCnrInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepare object layer
|
// prepare object layer
|
||||||
|
@ -434,8 +447,8 @@ func (s *appSettings) RetryStrategy() handler.RetryStrategy {
|
||||||
return s.retryStrategy
|
return s.retryStrategy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) initAPI() {
|
func (a *App) initAPI(ctx context.Context) {
|
||||||
a.initLayer()
|
a.initLayer(ctx)
|
||||||
a.initHandler()
|
a.initHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1034,3 +1047,32 @@ func (a *App) tryReconnect(ctx context.Context, sr *http.Server) bool {
|
||||||
|
|
||||||
return len(a.unbindServers) == 0
|
return len(a.unbindServers) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) fetchContainerInfo(ctx context.Context, cfgKey string) (info *data.BucketInfo, err error) {
|
||||||
|
containerString := a.cfg.GetString(cfgKey)
|
||||||
|
|
||||||
|
var id cid.ID
|
||||||
|
if err = id.DecodeString(containerString); err != nil {
|
||||||
|
if id, err = a.bucketResolver.Resolve(ctx, containerString); err != nil {
|
||||||
|
return nil, fmt.Errorf("resolve container name %s: %w", containerString, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return getContainerInfo(ctx, id, a.pool)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getContainerInfo(ctx context.Context, id cid.ID, frostFSPool *pool.Pool) (*data.BucketInfo, error) {
|
||||||
|
prm := pool.PrmContainerGet{
|
||||||
|
ContainerID: id,
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := frostFSPool.GetContainer(ctx, prm)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &data.BucketInfo{
|
||||||
|
CID: id,
|
||||||
|
HomomorphicHashDisabled: container.IsHomomorphicHashingDisabled(res),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
|
@ -176,6 +176,9 @@ const ( // Settings.
|
||||||
|
|
||||||
cfgSourceIPHeader = "source_ip_header"
|
cfgSourceIPHeader = "source_ip_header"
|
||||||
|
|
||||||
|
// Containers.
|
||||||
|
cfgContainersCORS = "containers.cors"
|
||||||
|
|
||||||
// Command line args.
|
// Command line args.
|
||||||
cmdHelp = "help"
|
cmdHelp = "help"
|
||||||
cmdVersion = "version"
|
cmdVersion = "version"
|
||||||
|
|
|
@ -216,3 +216,5 @@ S3_GW_RETRY_MAX_BACKOFF=30s
|
||||||
# Backoff strategy. `exponential` and `constant` are allowed.
|
# Backoff strategy. `exponential` and `constant` are allowed.
|
||||||
S3_GW_RETRY_STRATEGY=exponential
|
S3_GW_RETRY_STRATEGY=exponential
|
||||||
|
|
||||||
|
# Containers properties
|
||||||
|
S3_GW_CONTAINERS_CORS=AZjLTXfK4vs4ovxMic2xEJKSymMNLqdwq9JT64ASFCRj
|
||||||
|
|
|
@ -252,3 +252,7 @@ retry:
|
||||||
max_backoff: 30s
|
max_backoff: 30s
|
||||||
# Backoff strategy. `exponential` and `constant` are allowed.
|
# Backoff strategy. `exponential` and `constant` are allowed.
|
||||||
strategy: exponential
|
strategy: exponential
|
||||||
|
|
||||||
|
# Containers properties
|
||||||
|
containers:
|
||||||
|
cors: AZjLTXfK4vs4ovxMic2xEJKSymMNLqdwq9JT64ASFCRj
|
||||||
|
|
|
@ -192,6 +192,7 @@ There are some custom types used for brevity:
|
||||||
| `proxy` | [Proxy contract configuration](#proxy-section) |
|
| `proxy` | [Proxy contract configuration](#proxy-section) |
|
||||||
| `namespaces` | [Namespaces configuration](#namespaces-section) |
|
| `namespaces` | [Namespaces configuration](#namespaces-section) |
|
||||||
| `retry` | [Retry configuration](#retry-section) |
|
| `retry` | [Retry configuration](#retry-section) |
|
||||||
|
| `containers` | [Containers configuration](#containers-section) |
|
||||||
|
|
||||||
### General section
|
### General section
|
||||||
|
|
||||||
|
@ -708,3 +709,15 @@ retry:
|
||||||
| `max_backoff` | `duration` | yes | `30s` | Max delay before next attempt. |
|
| `max_backoff` | `duration` | yes | `30s` | Max delay before next attempt. |
|
||||||
| `strategy` | `string` | yes | `exponential` | Backoff strategy. `exponential` and `constant` are allowed. |
|
| `strategy` | `string` | yes | `exponential` | Backoff strategy. `exponential` and `constant` are allowed. |
|
||||||
|
|
||||||
|
# `containers` section
|
||||||
|
|
||||||
|
Section for well-known containers to store s3-related data and settings.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
containers:
|
||||||
|
cors: AZjLTXfK4vs4ovxMic2xEJKSymMNLqdwq9JT64ASFCRj
|
||||||
|
```
|
||||||
|
|
||||||
|
| Parameter | Type | SIGHUP reload | Default value | Description |
|
||||||
|
|-----------|----------|---------------|---------------|--------------------------------------------------------------------------------------|
|
||||||
|
| `cors` | `string` | no | | Container name for CORS configurations. If not set, container of the bucket is used. |
|
||||||
|
|
51
docs/extensions.md
Normal file
51
docs/extensions.md
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
# S3 API Extension
|
||||||
|
|
||||||
|
## Bucket operations management
|
||||||
|
|
||||||
|
### Action to delete bucket (DeleteBucket)
|
||||||
|
|
||||||
|
Deletes bucket with all objects in it.
|
||||||
|
|
||||||
|
#### Request Parameters
|
||||||
|
|
||||||
|
- **Bucket**
|
||||||
|
|
||||||
|
Specifies the bucket being deleted.
|
||||||
|
|
||||||
|
|
||||||
|
#### Errors
|
||||||
|
|
||||||
|
- **NoSuchEntity**
|
||||||
|
|
||||||
|
The request was rejected because it referenced a resource entity that does not exist.
|
||||||
|
|
||||||
|
HTTP Status Code: 404
|
||||||
|
|
||||||
|
- **ServiceFailure**
|
||||||
|
|
||||||
|
The request processing has failed because of an unknown error, exception or failure.
|
||||||
|
|
||||||
|
HTTP Status Code: 500
|
||||||
|
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
Sample Request
|
||||||
|
|
||||||
|
```text
|
||||||
|
DELETE / HTTP/1.1
|
||||||
|
Host: data.s3.<Region>.frostfs-s3-gw.com
|
||||||
|
Date: Wed, 01 Mar 2024 12:00:00 GMT
|
||||||
|
Authorization: authorization string
|
||||||
|
```
|
||||||
|
|
||||||
|
Sample Response
|
||||||
|
|
||||||
|
```text
|
||||||
|
HTTP/1.1 204 No Content
|
||||||
|
x-amz-id-2: JuKZqmXuiwFeDQxhD7M8KtsKobSzWA1QEjLbTMTagkKdBX2z7Il/jGhDeJ3j6s80
|
||||||
|
x-amz-request-id: 32FE2CEB32F5EE25
|
||||||
|
Date: Wed, 01 Mar 2006 12:00:00 GMT
|
||||||
|
Connection: close
|
||||||
|
Server: AmazonS3
|
||||||
|
```
|
|
@ -149,4 +149,5 @@ const (
|
||||||
UnexpectedMultiNodeIDsInSubTreeMultiParts = "unexpected multi node ids in sub tree multi parts"
|
UnexpectedMultiNodeIDsInSubTreeMultiParts = "unexpected multi node ids in sub tree multi parts"
|
||||||
FoundSeveralSystemNodes = "found several system nodes, latest be used"
|
FoundSeveralSystemNodes = "found several system nodes, latest be used"
|
||||||
FailedToParsePartInfo = "failed to parse part info"
|
FailedToParsePartInfo = "failed to parse part info"
|
||||||
|
CouldNotFetchCORSContainerInfo = "couldn't fetch CORS container info"
|
||||||
)
|
)
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||||
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
@ -84,6 +85,7 @@ const (
|
||||||
ownerKeyKV = "ownerKey"
|
ownerKeyKV = "ownerKey"
|
||||||
lockConfigurationKV = "LockConfiguration"
|
lockConfigurationKV = "LockConfiguration"
|
||||||
oidKV = "OID"
|
oidKV = "OID"
|
||||||
|
cidKV = "CID"
|
||||||
|
|
||||||
isCombinedKV = "IsCombined"
|
isCombinedKV = "IsCombined"
|
||||||
isUnversionedKV = "IsUnversioned"
|
isUnversionedKV = "IsUnversioned"
|
||||||
|
@ -443,31 +445,32 @@ func (c *Tree) PutSettingsNode(ctx context.Context, bktInfo *data.BucketInfo, se
|
||||||
return c.service.MoveNode(ctx, bktInfo, systemTree, node.ID[ind], 0, meta)
|
return c.service.MoveNode(ctx, bktInfo, systemTree, node.ID[ind], 0, meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Tree) GetBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) (oid.ID, error) {
|
func (c *Tree) GetBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) (oid.Address, error) {
|
||||||
node, err := c.getSystemNode(ctx, bktInfo, []string{corsFilename})
|
node, err := c.getSystemNode(ctx, bktInfo, []string{corsFilename})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return oid.ID{}, err
|
return oid.Address{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return node.ObjID, nil
|
return getCORSAddress(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Tree) PutBucketCORS(ctx context.Context, bktInfo *data.BucketInfo, objID oid.ID) (oid.ID, error) {
|
func (c *Tree) PutBucketCORS(ctx context.Context, bktInfo *data.BucketInfo, addr oid.Address) (oid.Address, error) {
|
||||||
node, err := c.getSystemNode(ctx, bktInfo, []string{corsFilename})
|
node, err := c.getSystemNode(ctx, bktInfo, []string{corsFilename})
|
||||||
isErrNotFound := errors.Is(err, layer.ErrNodeNotFound)
|
isErrNotFound := errors.Is(err, layer.ErrNodeNotFound)
|
||||||
if err != nil && !isErrNotFound {
|
if err != nil && !isErrNotFound {
|
||||||
return oid.ID{}, fmt.Errorf("couldn't get node: %w", err)
|
return oid.Address{}, fmt.Errorf("couldn't get node: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
meta := make(map[string]string)
|
meta := make(map[string]string)
|
||||||
meta[FileNameKey] = corsFilename
|
meta[FileNameKey] = corsFilename
|
||||||
meta[oidKV] = objID.EncodeToString()
|
meta[oidKV] = addr.Object().EncodeToString()
|
||||||
|
meta[cidKV] = addr.Container().EncodeToString()
|
||||||
|
|
||||||
if isErrNotFound {
|
if isErrNotFound {
|
||||||
if _, err = c.service.AddNode(ctx, bktInfo, systemTree, 0, meta); err != nil {
|
if _, err = c.service.AddNode(ctx, bktInfo, systemTree, 0, meta); err != nil {
|
||||||
return oid.ID{}, err
|
return oid.Address{}, err
|
||||||
}
|
}
|
||||||
return oid.ID{}, layer.ErrNoNodeToRemove
|
return oid.Address{}, layer.ErrNoNodeToRemove
|
||||||
}
|
}
|
||||||
|
|
||||||
ind := node.GetLatestNodeIndex()
|
ind := node.GetLatestNodeIndex()
|
||||||
|
@ -475,13 +478,18 @@ func (c *Tree) PutBucketCORS(ctx context.Context, bktInfo *data.BucketInfo, objI
|
||||||
c.reqLogger(ctx).Warn(logs.FoundSeveralBucketCorsNodes)
|
c.reqLogger(ctx).Warn(logs.FoundSeveralBucketCorsNodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
return node.ObjID, c.service.MoveNode(ctx, bktInfo, systemTree, node.ID[ind], 0, meta)
|
prevAddr, err := getCORSAddress(node)
|
||||||
|
if err != nil {
|
||||||
|
return oid.Address{}, fmt.Errorf("couldn't get cors object addr: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return prevAddr, c.service.MoveNode(ctx, bktInfo, systemTree, node.ID[ind], 0, meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Tree) DeleteBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) (oid.ID, error) {
|
func (c *Tree) DeleteBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) (oid.Address, error) {
|
||||||
node, err := c.getSystemNode(ctx, bktInfo, []string{corsFilename})
|
node, err := c.getSystemNode(ctx, bktInfo, []string{corsFilename})
|
||||||
if err != nil && !errors.Is(err, layer.ErrNodeNotFound) {
|
if err != nil && !errors.Is(err, layer.ErrNodeNotFound) {
|
||||||
return oid.ID{}, err
|
return oid.Address{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if node != nil {
|
if node != nil {
|
||||||
|
@ -490,10 +498,30 @@ func (c *Tree) DeleteBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) (
|
||||||
c.reqLogger(ctx).Warn(logs.FoundSeveralBucketCorsNodes)
|
c.reqLogger(ctx).Warn(logs.FoundSeveralBucketCorsNodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
return node.ObjID, c.service.RemoveNode(ctx, bktInfo, systemTree, node.ID[ind])
|
addr, err := getCORSAddress(node)
|
||||||
|
if err != nil {
|
||||||
|
return oid.Address{}, fmt.Errorf("couldn't get cors object addr: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr, c.service.RemoveNode(ctx, bktInfo, systemTree, node.ID[ind])
|
||||||
}
|
}
|
||||||
|
|
||||||
return oid.ID{}, layer.ErrNoNodeToRemove
|
return oid.Address{}, layer.ErrNoNodeToRemove
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCORSAddress(node *treeNode) (oid.Address, error) {
|
||||||
|
var addr oid.Address
|
||||||
|
addr.SetObject(node.ObjID)
|
||||||
|
|
||||||
|
if cidStr, ok := node.Get(cidKV); ok {
|
||||||
|
var cnrID cid.ID
|
||||||
|
if err := cnrID.DecodeString(cidStr); err != nil {
|
||||||
|
return oid.Address{}, fmt.Errorf("couldn't decode cid: %w", err)
|
||||||
|
}
|
||||||
|
addr.SetContainer(cnrID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Tree) GetObjectTagging(ctx context.Context, bktInfo *data.BucketInfo, objVersion *data.NodeVersion) (map[string]string, error) {
|
func (c *Tree) GetObjectTagging(ctx context.Context, bktInfo *data.BucketInfo, objVersion *data.NodeVersion) (map[string]string, error) {
|
||||||
|
|
Loading…
Reference in a new issue