feat: Added ChangeBucketOwner, ListBucketsAndOwners action implementation in azure backend. Fixed acl key bug in getting container metadata. Added container owner in ListBuckets action

This commit is contained in:
jonaustin09
2024-01-17 08:27:37 -08:00
committed by Ben McClelland
parent d404f96320
commit 240db54feb
+106 -12
View File
@@ -19,9 +19,11 @@ import (
"context"
"encoding/base64"
"encoding/binary"
"encoding/json"
"fmt"
"io"
"math"
"net/url"
"os"
"strconv"
"strings"
@@ -35,12 +37,19 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/versity/versitygw/auth"
"github.com/versity/versitygw/backend"
"github.com/versity/versitygw/s3err"
"github.com/versity/versitygw/s3response"
)
const aclKey string = "Acl"
// When getting container metadata with GetProperties method the sdk returns
// the first letter capital, when accessing the metadata after listing the containers
// it returns the first letter lower
type aclKey string
const aclKeyCapital aclKey = "Acl"
const aclKeyLower aclKey = "acl"
type Azure struct {
backend.BackendUnsupported
@@ -55,8 +64,15 @@ type Azure struct {
var _ backend.Backend = &Azure{}
func New(accountName, accountKey, serviceURL, sasToken string) (*Azure, error) {
url := serviceURL
if serviceURL == "" && accountName != "" {
// if not otherwise specified, use the typical form:
// http(s)://<account>.blob.core.windows.net/
url = fmt.Sprintf("https://%s.blob.core.windows.net/", accountName)
}
if sasToken != "" {
client, err := azblob.NewClientWithNoCredential(serviceURL+"?"+sasToken, nil)
client, err := azblob.NewClientWithNoCredential(url+"?"+sasToken, nil)
if err != nil {
return nil, fmt.Errorf("init client: %w", err)
}
@@ -68,13 +84,6 @@ func New(accountName, accountKey, serviceURL, sasToken string) (*Azure, error) {
accountName = os.Getenv("AZURE_CLIENT_ID")
}
url := serviceURL
if serviceURL == "" && accountName != "" {
// if not otherwise specified, use the typical form:
// http(s)://<account>.blob.core.windows.net/
url = fmt.Sprintf("https://%s.blob.core.windows.net/", accountName)
}
if accountName == "" || accountKey == "" {
cred, err := azidentity.NewDefaultAzureCredential(nil)
if err != nil {
@@ -108,7 +117,7 @@ func (az *Azure) String() string {
func (az *Azure) CreateBucket(ctx context.Context, input *s3.CreateBucketInput, acl []byte) error {
meta := map[string]*string{
aclKey: backend.GetStringPtr(string(acl)),
string(aclKeyCapital): backend.GetStringPtr(string(acl)),
}
_, err := az.client.CreateContainer(ctx, *input.Bucket, &container.CreateOptions{Metadata: meta})
return azureErrToS3Err(err)
@@ -135,6 +144,9 @@ func (az *Azure) ListBuckets(ctx context.Context, owner string, isAdmin bool) (s
}
result.Buckets.Bucket = buckets
// If the gateway is initialized with shared key credentials
// provide user account name as the owner of the buckets
result.Owner.ID = az.getAccountNameFromURL()
return result, nil
}
@@ -644,7 +656,7 @@ func (az *Azure) PutBucketAcl(ctx context.Context, bucket string, data []byte) e
return err
}
meta := map[string]*string{
aclKey: backend.GetStringPtr(string(data)),
string(aclKeyCapital): backend.GetStringPtr(string(data)),
}
_, err = client.SetMetadata(ctx, &container.SetMetadataOptions{
Metadata: meta,
@@ -665,7 +677,7 @@ func (az *Azure) GetBucketAcl(ctx context.Context, input *s3.GetBucketAclInput)
return nil, azureErrToS3Err(err)
}
aclPtr, ok := props.Metadata[aclKey]
aclPtr, ok := props.Metadata[string(aclKeyCapital)]
if !ok {
return nil, s3err.GetAPIError(s3err.ErrInternalError)
}
@@ -673,6 +685,59 @@ func (az *Azure) GetBucketAcl(ctx context.Context, input *s3.GetBucketAclInput)
return []byte(*aclPtr), nil
}
func (az *Azure) ChangeBucketOwner(ctx context.Context, bucket, newOwner string) error {
client, err := az.getContainerClient(bucket)
if err != nil {
return err
}
props, err := client.GetProperties(ctx, nil)
if err != nil {
return azureErrToS3Err(err)
}
acl, err := getAclFromMetadata(props.Metadata, aclKeyCapital)
if err != nil {
return err
}
acl.Owner = newOwner
newAcl, err := json.Marshal(acl)
if err != nil {
return fmt.Errorf("marshal acl: %w", err)
}
err = az.PutBucketAcl(ctx, bucket, newAcl)
if err != nil {
return err
}
return nil
}
func (az *Azure) ListBucketsAndOwners(ctx context.Context) (buckets []s3response.Bucket, err error) {
pager := az.client.NewListContainersPager(nil)
for pager.More() {
resp, err := pager.NextPage(ctx)
if err != nil {
return buckets, azureErrToS3Err(err)
}
for _, v := range resp.ContainerItems {
acl, err := getAclFromMetadata(v.Metadata, aclKeyLower)
if err != nil {
return buckets, err
}
buckets = append(buckets, s3response.Bucket{
Name: *v.Name,
Owner: acl.Owner,
})
}
}
return buckets, nil
}
func (az *Azure) getContainerURL(cntr string) string {
return fmt.Sprintf("%v/%v", az.serviceURL, cntr)
}
@@ -714,6 +779,20 @@ func (az *Azure) getBlockBlobClient(cntr, blb string) (*blockblob.Client, error)
return blockblob.NewClientWithSharedKeyCredential(blobURL, az.sharedkeyCreds, nil)
}
func (az *Azure) getAccountNameFromURL() string {
urlParts, err := url.Parse(az.serviceURL)
if err != nil {
return ""
}
acc := urlParts.Path
if strings.HasSuffix(acc, ".blob.core.windows.net/") {
return strings.TrimSuffix(acc, ".blob.core.windows.net")
}
return acc[1:]
}
func parseMetadata(m map[string]string) map[string]*string {
if m == nil {
return nil
@@ -834,3 +913,18 @@ func parseRange(rg string) (offset, count int64, err error) {
return offset, count - offset + 1, nil
}
func getAclFromMetadata(meta map[string]*string, key aclKey) (*auth.ACL, error) {
aclPtr, ok := meta[string(key)]
if !ok {
return nil, s3err.GetAPIError(s3err.ErrInternalError)
}
var acl auth.ACL
err := json.Unmarshal([]byte(*aclPtr), &acl)
if err != nil {
return nil, fmt.Errorf("unmarshal acl: %w", err)
}
return &acl, nil
}