mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-29 11:15:34 +00:00
* shell: refuse s3.bucket.create on an existing bucket CreateEntry without o_excl replaces the bucket entry, dropping every extended attribute: lifecycle configuration, owner, versioning and the irreversible Object Lock flag. Send o_excl so a re-run fails with 'bucket already exists' instead of silently resetting the bucket. * filer: fail exclusive creates when the lookup itself fails CreateEntry discards FindEntry errors, so an o_excl create hitting a transient store failure would take the insert path and upsert over the entry it was meant to preserve. Propagate the lookup error when o_excl is set; non-exclusive creates keep their existing semantics. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * shell: test s3.bucket.create requests an exclusive create Exercises the command end to end through a fake filer gRPC server and asserts the OExcl flag reaches the wire along with the already-exists error path. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * shell: synchronize captured requests and assert the exact bucket error The fake filer records CreateEntry requests on the gRPC server goroutine, so reads need the same mutex; the test also now checks for the exact "bucket my-bucket already exists" message rather than any error that mentions existence. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
144 lines
3.9 KiB
Go
144 lines
3.9 KiB
Go
package shell
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3bucket"
|
|
)
|
|
|
|
func init() {
|
|
Commands = append(Commands, &commandS3BucketCreate{})
|
|
}
|
|
|
|
type commandS3BucketCreate struct {
|
|
}
|
|
|
|
func (c *commandS3BucketCreate) Name() string {
|
|
return "s3.bucket.create"
|
|
}
|
|
|
|
func (c *commandS3BucketCreate) Help() string {
|
|
return `create a bucket with a given name
|
|
|
|
Example:
|
|
s3.bucket.create -name <bucket_name>
|
|
s3.bucket.create -name <bucket_name> -owner <identity_name>
|
|
s3.bucket.create -name <bucket_name> -withLock
|
|
|
|
The -owner flag sets the bucket owner identity. This is important when using
|
|
S3 IAM authentication, as non-admin users can only access buckets they own.
|
|
If not specified, the bucket will have no owner and will only be accessible
|
|
by admin users.
|
|
|
|
The -owner value should match the identity name configured in your S3 IAM
|
|
system (the "name" field in s3.json identities configuration).
|
|
|
|
The -withLock flag enables S3 Object Lock on the bucket. This provides WORM
|
|
(Write Once Read Many) protection for objects. Once enabled, Object Lock
|
|
cannot be disabled. Versioning is automatically enabled when using this flag.
|
|
`
|
|
}
|
|
|
|
func (c *commandS3BucketCreate) HasTag(CommandTag) bool {
|
|
return false
|
|
}
|
|
|
|
func (c *commandS3BucketCreate) Do(args []string, commandEnv *CommandEnv, writer io.Writer) (err error) {
|
|
|
|
bucketCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
|
|
bucketName := bucketCommand.String("name", "", "bucket name")
|
|
bucketOwner := bucketCommand.String("owner", "", "bucket owner identity name (for S3 IAM authentication)")
|
|
withLock := bucketCommand.Bool("withLock", false, "enable Object Lock on the bucket (requires and enables versioning)")
|
|
if err = bucketCommand.Parse(args); err != nil {
|
|
return nil
|
|
}
|
|
|
|
if *bucketName == "" {
|
|
return fmt.Errorf("empty bucket name")
|
|
}
|
|
|
|
err = s3bucket.VerifyS3BucketName(*bucketName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Trim whitespace from owner and treat whitespace-only as empty
|
|
owner := strings.TrimSpace(*bucketOwner)
|
|
|
|
err = commandEnv.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
|
|
|
resp, err := client.GetFilerConfiguration(context.Background(), &filer_pb.GetFilerConfigurationRequest{})
|
|
if err != nil {
|
|
return fmt.Errorf("get filer configuration: %w", err)
|
|
}
|
|
filerBucketsPath := resp.DirBuckets
|
|
|
|
fmt.Fprintln(writer, "create bucket under", filerBucketsPath)
|
|
|
|
entry := &filer_pb.Entry{
|
|
Name: *bucketName,
|
|
IsDirectory: true,
|
|
Attributes: &filer_pb.FuseAttributes{
|
|
Mtime: time.Now().Unix(),
|
|
Crtime: time.Now().Unix(),
|
|
FileMode: uint32(0777 | os.ModeDir),
|
|
},
|
|
}
|
|
|
|
// Set bucket owner if specified
|
|
if owner != "" {
|
|
if entry.Extended == nil {
|
|
entry.Extended = make(map[string][]byte)
|
|
}
|
|
entry.Extended[s3_constants.AmzIdentityId] = []byte(owner)
|
|
}
|
|
|
|
// Enable Object Lock if specified
|
|
if *withLock {
|
|
if entry.Extended == nil {
|
|
entry.Extended = make(map[string][]byte)
|
|
}
|
|
// Enable versioning (required for Object Lock)
|
|
entry.Extended[s3_constants.ExtVersioningKey] = []byte(s3_constants.VersioningEnabled)
|
|
// Enable Object Lock
|
|
entry.Extended[s3_constants.ExtObjectLockEnabledKey] = []byte(s3_constants.ObjectLockEnabled)
|
|
}
|
|
|
|
createErr := filer_pb.CreateEntry(context.Background(), client, &filer_pb.CreateEntryRequest{
|
|
Directory: filerBucketsPath,
|
|
Entry: entry,
|
|
OExcl: true,
|
|
})
|
|
if errors.Is(createErr, filer_pb.ErrEntryAlreadyExists) {
|
|
return fmt.Errorf("bucket %s already exists", *bucketName)
|
|
}
|
|
if createErr != nil {
|
|
return createErr
|
|
}
|
|
|
|
fmt.Fprintln(writer, "created bucket", *bucketName)
|
|
if owner != "" {
|
|
fmt.Fprintln(writer, "bucket owner:", owner)
|
|
}
|
|
if *withLock {
|
|
fmt.Fprintln(writer, "Object Lock: enabled")
|
|
fmt.Fprintln(writer, "Versioning: enabled")
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return err
|
|
|
|
}
|