Browse Source

feat(shell): add Object Lock management commands (#8141)

* feat(shell): add s3.bucket.lock command for Object Lock management

Add new weed shell command to view and enable S3 Object Lock on existing
buckets. This allows administrators to enable Object Lock without
recreating buckets, which is useful when buckets already contain data.

The command:
- Shows current Object Lock and Versioning status
- Enables Object Lock with -enable flag (irreversible, per AWS S3 spec)
- Automatically enables Versioning if not already enabled (required for Object Lock)

Usage:
  s3.bucket.lock -name <bucket>          # view status
  s3.bucket.lock -name <bucket> -enable  # enable Object Lock

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>

* feat(shell): add -withLock flag to s3.bucket.create command

Add support for creating buckets with Object Lock enabled directly from
weed shell. The flag automatically enables versioning as required by
Object Lock.

Usage:
  s3.bucket.create -name mybucket -withLock

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>

* Apply suggestion from @gemini-code-assist[bot]

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

---------

Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Chris Lu <chrislusf@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
pull/8142/head
Andrei Kvapil 1 day ago
committed by GitHub
parent
commit
b1d63d0943
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 21
      weed/shell/command_s3_bucket_create.go
  2. 152
      weed/shell/command_s3_bucket_lock.go

21
weed/shell/command_s3_bucket_create.go

@ -31,6 +31,7 @@ func (c *commandS3BucketCreate) Help() string {
Example: Example:
s3.bucket.create -name <bucket_name> s3.bucket.create -name <bucket_name>
s3.bucket.create -name <bucket_name> -owner <identity_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 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. S3 IAM authentication, as non-admin users can only access buckets they own.
@ -39,6 +40,10 @@ func (c *commandS3BucketCreate) Help() string {
The -owner value should match the identity name configured in your S3 IAM The -owner value should match the identity name configured in your S3 IAM
system (the "name" field in s3.json identities configuration). 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.
` `
} }
@ -51,6 +56,7 @@ func (c *commandS3BucketCreate) Do(args []string, commandEnv *CommandEnv, writer
bucketCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError) bucketCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
bucketName := bucketCommand.String("name", "", "bucket name") bucketName := bucketCommand.String("name", "", "bucket name")
bucketOwner := bucketCommand.String("owner", "", "bucket owner identity name (for S3 IAM authentication)") 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 { if err = bucketCommand.Parse(args); err != nil {
return nil return nil
} }
@ -95,6 +101,17 @@ func (c *commandS3BucketCreate) Do(args []string, commandEnv *CommandEnv, writer
entry.Extended[s3_constants.AmzIdentityId] = []byte(owner) 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)
}
if _, err := client.CreateEntry(context.Background(), &filer_pb.CreateEntryRequest{ if _, err := client.CreateEntry(context.Background(), &filer_pb.CreateEntryRequest{
Directory: filerBucketsPath, Directory: filerBucketsPath,
Entry: entry, Entry: entry,
@ -106,6 +123,10 @@ func (c *commandS3BucketCreate) Do(args []string, commandEnv *CommandEnv, writer
if owner != "" { if owner != "" {
fmt.Fprintln(writer, "bucket owner:", owner) fmt.Fprintln(writer, "bucket owner:", owner)
} }
if *withLock {
fmt.Fprintln(writer, "Object Lock: enabled")
fmt.Fprintln(writer, "Versioning: enabled")
}
return nil return nil

152
weed/shell/command_s3_bucket_lock.go

@ -0,0 +1,152 @@
package shell
import (
"context"
"flag"
"fmt"
"io"
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
"github.com/seaweedfs/seaweedfs/weed/util"
)
func init() {
Commands = append(Commands, &commandS3BucketLock{})
}
type commandS3BucketLock struct {
}
func (c *commandS3BucketLock) Name() string {
return "s3.bucket.lock"
}
func (c *commandS3BucketLock) Help() string {
return `view or enable Object Lock for an S3 bucket
Example:
# View the current Object Lock status of a bucket
s3.bucket.lock -name <bucket_name>
# Enable Object Lock on an existing bucket (irreversible)
s3.bucket.lock -name <bucket_name> -enable
Object Lock provides WORM (Write Once Read Many) protection for objects.
Once enabled, Object Lock cannot be disabled on a bucket.
Requirements:
- Versioning will be automatically enabled if not already enabled
- Object Lock can only be enabled, never disabled (following AWS S3 behavior)
After enabling Object Lock, you can:
- Set default retention policy on the bucket
- Set retention and legal hold on individual objects
`
}
func (c *commandS3BucketLock) HasTag(CommandTag) bool {
return false
}
func (c *commandS3BucketLock) Do(args []string, commandEnv *CommandEnv, writer io.Writer) (err error) {
bucketCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
bucketName := bucketCommand.String("name", "", "bucket name")
enableLock := bucketCommand.Bool("enable", false, "enable Object Lock on the bucket (irreversible)")
if err = bucketCommand.Parse(args); err != nil {
return err
}
if *bucketName == "" {
return fmt.Errorf("empty bucket name")
}
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
// Look up the bucket entry
lookupResp, err := client.LookupDirectoryEntry(context.Background(), &filer_pb.LookupDirectoryEntryRequest{
Directory: filerBucketsPath,
Name: *bucketName,
})
if err != nil {
return fmt.Errorf("lookup bucket %s: %w", *bucketName, err)
}
entry := lookupResp.Entry
// Check current Object Lock status
currentLockEnabled := false
currentVersioningEnabled := false
if entry.Extended != nil {
if lockStatus, ok := entry.Extended[s3_constants.ExtObjectLockEnabledKey]; ok {
currentLockEnabled = string(lockStatus) == s3_constants.ObjectLockEnabled
}
if versioningStatus, ok := entry.Extended[s3_constants.ExtVersioningKey]; ok {
currentVersioningEnabled = string(versioningStatus) == s3_constants.VersioningEnabled
}
}
// If -enable is provided, enable Object Lock
if *enableLock {
if currentLockEnabled {
fmt.Fprintf(writer, "Object Lock is already enabled on bucket %s\n", *bucketName)
return nil
}
if entry.Extended == nil {
entry.Extended = make(map[string][]byte)
}
// Enable versioning if not already enabled (required for Object Lock)
if !currentVersioningEnabled {
entry.Extended[s3_constants.ExtVersioningKey] = []byte(s3_constants.VersioningEnabled)
fmt.Fprintf(writer, "Enabling versioning on bucket %s (required for Object Lock)\n", *bucketName)
}
// Enable Object Lock
entry.Extended[s3_constants.ExtObjectLockEnabledKey] = []byte(s3_constants.ObjectLockEnabled)
fmt.Fprintf(writer, "Enabling Object Lock on bucket %s\n", *bucketName)
// Update the entry
if _, err := client.UpdateEntry(context.Background(), &filer_pb.UpdateEntryRequest{
Directory: filerBucketsPath,
Entry: entry,
}); err != nil {
return fmt.Errorf("failed to update bucket: %w", err)
}
fmt.Fprintf(writer, "Object Lock enabled successfully.\n")
fmt.Fprintf(writer, "WARNING: This action is irreversible. Object Lock cannot be disabled.\n")
return nil
}
// Display current status (no flags provided)
fmt.Fprintf(writer, "Bucket: %s\n", *bucketName)
fmt.Fprintf(writer, "Path: %s\n", util.NewFullPath(filerBucketsPath, *bucketName))
if currentVersioningEnabled {
fmt.Fprintf(writer, "Versioning: Enabled\n")
} else {
fmt.Fprintf(writer, "Versioning: Disabled\n")
}
if currentLockEnabled {
fmt.Fprintf(writer, "Object Lock: Enabled\n")
} else {
fmt.Fprintf(writer, "Object Lock: Disabled\n")
fmt.Fprintf(writer, "\nTo enable Object Lock, run:\n")
fmt.Fprintf(writer, " s3.bucket.lock -name %s -enable\n", *bucketName)
}
return nil
})
return err
}
Loading…
Cancel
Save