Work with Amazon S3 object lock features using an AWS SDK
The following code examples show how to work with S3 object lock features.
- .NET
-
- AWS SDK for .NET
-
Note
There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository
. Run an interactive scenario demonstrating Amazon S3 object lock features.
using Amazon.S3; using Amazon.S3.Model; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Console; using Microsoft.Extensions.Logging.Debug; namespace S3ObjectLockScenario; public static class S3ObjectLockWorkflow { /* Before running this .NET code example, set up your development environment, including your credentials. This .NET example performs the following tasks: 1. Create test Amazon Simple Storage Service (S3) buckets with different lock policies. 2. Upload sample objects to each bucket. 3. Set some Legal Hold and Retention Periods on objects and buckets. 4. Investigate lock policies by viewing settings or attempting to delete or overwrite objects. 5. Clean up objects and buckets. */ public static S3ActionsWrapper _s3ActionsWrapper = null!; public static IConfiguration _configuration = null!; private static string _resourcePrefix = null!; private static string noLockBucketName = null!; private static string lockEnabledBucketName = null!; private static string retentionAfterCreationBucketName = null!; private static List<string> bucketNames = new List<string>(); private static List<string> fileNames = new List<string>(); public static async Task Main(string[] args) { // Set up dependency injection for the Amazon service. using var host = Host.CreateDefaultBuilder(args) .ConfigureLogging(logging => logging.AddFilter("System", LogLevel.Debug) .AddFilter<DebugLoggerProvider>("Microsoft", LogLevel.Information) .AddFilter<ConsoleLoggerProvider>("Microsoft", LogLevel.Trace)) .ConfigureServices((_, services) => services.AddAWSService<IAmazonS3>() .AddTransient<S3ActionsWrapper>() ) .Build(); _configuration = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("settings.json") // Load settings from .json file. .AddJsonFile("settings.local.json", true) // Optionally, load local settings. .Build(); ConfigurationSetup(); ServicesSetup(host); try { Console.WriteLine(new string('-', 80)); Console.WriteLine("Welcome to the Amazon Simple Storage Service (S3) Object Locking Workflow Scenario."); Console.WriteLine(new string('-', 80)); await Setup(true); await DemoActionChoices(); Console.WriteLine(new string('-', 80)); Console.WriteLine("Cleaning up resources."); Console.WriteLine(new string('-', 80)); await Cleanup(true); Console.WriteLine(new string('-', 80)); Console.WriteLine("Amazon S3 Object Locking Workflow is complete."); Console.WriteLine(new string('-', 80)); } catch (Exception ex) { Console.WriteLine(new string('-', 80)); Console.WriteLine($"There was a problem: {ex.Message}"); await Cleanup(true); Console.WriteLine(new string('-', 80)); } } /// <summary> /// Populate the services for use within the console application. /// </summary> /// <param name="host">The services host.</param> private static void ServicesSetup(IHost host) { _s3ActionsWrapper = host.Services.GetRequiredService<S3ActionsWrapper>(); } /// <summary> /// Any setup operations needed. /// </summary> public static void ConfigurationSetup() { _resourcePrefix = _configuration["resourcePrefix"] ?? "dotnet-example"; noLockBucketName = _resourcePrefix + "-no-lock"; lockEnabledBucketName = _resourcePrefix + "-lock-enabled"; retentionAfterCreationBucketName = _resourcePrefix + "-retention-after-creation"; bucketNames.Add(noLockBucketName); bucketNames.Add(lockEnabledBucketName); bucketNames.Add(retentionAfterCreationBucketName); } // <summary> /// Deploy necessary resources for the scenario. /// </summary> /// <param name="interactive">True to run as interactive.</param> /// <returns>True if successful.</returns> public static async Task<bool> Setup(bool interactive) { Console.WriteLine( "\nFor this workflow, we will use the AWS SDK for .NET to create several S3\n" + "buckets and files to demonstrate working with S3 locking features.\n"); Console.WriteLine(new string('-', 80)); Console.WriteLine("Press Enter when you are ready to start."); if (interactive) Console.ReadLine(); Console.WriteLine("\nS3 buckets can be created either with or without object lock enabled."); await _s3ActionsWrapper.CreateBucketWithObjectLock(noLockBucketName, false); await _s3ActionsWrapper.CreateBucketWithObjectLock(lockEnabledBucketName, true); await _s3ActionsWrapper.CreateBucketWithObjectLock(retentionAfterCreationBucketName, false); Console.WriteLine("Press Enter to continue."); if (interactive) Console.ReadLine(); Console.WriteLine("\nA bucket can be configured to use object locking with a default retention period."); await _s3ActionsWrapper.ModifyBucketDefaultRetention(retentionAfterCreationBucketName, true, ObjectLockRetentionMode.Governance, DateTime.UtcNow.AddDays(1)); Console.WriteLine("Press Enter to continue."); if (interactive) Console.ReadLine(); Console.WriteLine("\nObject lock policies can also be added to existing buckets."); await _s3ActionsWrapper.EnableObjectLockOnBucket(lockEnabledBucketName); Console.WriteLine("Press Enter to continue."); if (interactive) Console.ReadLine(); // Upload some files to the buckets. Console.WriteLine("\nNow let's add some test files:"); var fileName = _configuration["exampleFileName"] ?? "exampleFile.txt"; int fileCount = 2; // Create the file if it does not already exist. if (!File.Exists(fileName)) { await using StreamWriter sw = File.CreateText(fileName); await sw.WriteLineAsync( "This is a sample file for uploading to a bucket."); } foreach (var bucketName in bucketNames) { for (int i = 0; i < fileCount; i++) { var numberedFileName = Path.GetFileNameWithoutExtension(fileName) + i + Path.GetExtension(fileName); fileNames.Add(numberedFileName); await _s3ActionsWrapper.UploadFileAsync(bucketName, numberedFileName, fileName); } } Console.WriteLine("Press Enter to continue."); if (interactive) Console.ReadLine(); if (!interactive) return true; Console.WriteLine("\nNow we can set some object lock policies on individual files:"); foreach (var bucketName in bucketNames) { for (int i = 0; i < fileNames.Count; i++) { // No modifications to the objects in the first bucket. if (bucketName != bucketNames[0]) { var exampleFileName = fileNames[i]; switch (i) { case 0: { var question = $"\nWould you like to add a legal hold to {exampleFileName} in {bucketName}? (y/n)"; if (GetYesNoResponse(question)) { // Set a legal hold. await _s3ActionsWrapper.ModifyObjectLegalHold(bucketName, exampleFileName, ObjectLockLegalHoldStatus.On); } break; } case 1: { var question = $"\nWould you like to add a 1 day Governance retention period to {exampleFileName} in {bucketName}? (y/n)" + "\nReminder: Only a user with the s3:BypassGovernanceRetention permission will be able to delete this file or its bucket until the retention period has expired."; if (GetYesNoResponse(question)) { // Set a Governance mode retention period for 1 day. await _s3ActionsWrapper.ModifyObjectRetentionPeriod( bucketName, exampleFileName, ObjectLockRetentionMode.Governance, DateTime.UtcNow.AddDays(1)); } break; } } } } } Console.WriteLine(new string('-', 80)); return true; } // <summary> /// List all of the current buckets and objects. /// </summary> /// <param name="interactive">True to run as interactive.</param> /// <returns>The list of buckets and objects.</returns> public static async Task<List<S3ObjectVersion>> ListBucketsAndObjects(bool interactive) { var allObjects = new List<S3ObjectVersion>(); foreach (var bucketName in bucketNames) { var objectsInBucket = await _s3ActionsWrapper.ListBucketObjectsAndVersions(bucketName); foreach (var objectKey in objectsInBucket.Versions) { allObjects.Add(objectKey); } } if (interactive) { Console.WriteLine("\nCurrent buckets and objects:\n"); int i = 0; foreach (var bucketObject in allObjects) { i++; Console.WriteLine( $"{i}: {bucketObject.Key} \n\tBucket: {bucketObject.BucketName}\n\tVersion: {bucketObject.VersionId}"); } } return allObjects; } /// <summary> /// Present the user with the demo action choices. /// </summary> /// <returns>Async task.</returns> public static async Task<bool> DemoActionChoices() { var choices = new string[]{ "List all files in buckets.", "Attempt to delete a file.", "Attempt to delete a file with retention period bypass.", "Attempt to overwrite a file.", "View the object and bucket retention settings for a file.", "View the legal hold settings for a file.", "Finish the workflow."}; var choice = 0; // Keep asking the user until they choose to move on. while (choice != 6) { Console.WriteLine(new string('-', 80)); choice = GetChoiceResponse( "\nExplore the S3 locking features by selecting one of the following choices:" , choices); Console.WriteLine(new string('-', 80)); switch (choice) { case 0: { await ListBucketsAndObjects(true); break; } case 1: { Console.WriteLine("\nEnter the number of the object to delete:"); var allFiles = await ListBucketsAndObjects(true); var fileChoice = GetChoiceResponse(null, allFiles.Select(f => f.Key).ToArray()); await _s3ActionsWrapper.DeleteObjectFromBucket(allFiles[fileChoice].BucketName, allFiles[fileChoice].Key, false, allFiles[fileChoice].VersionId); break; } case 2: { Console.WriteLine("\nEnter the number of the object to delete:"); var allFiles = await ListBucketsAndObjects(true); var fileChoice = GetChoiceResponse(null, allFiles.Select(f => f.Key).ToArray()); await _s3ActionsWrapper.DeleteObjectFromBucket(allFiles[fileChoice].BucketName, allFiles[fileChoice].Key, true, allFiles[fileChoice].VersionId); break; } case 3: { var allFiles = await ListBucketsAndObjects(true); Console.WriteLine("\nEnter the number of the object to overwrite:"); var fileChoice = GetChoiceResponse(null, allFiles.Select(f => f.Key).ToArray()); // Create the file if it does not already exist. if (!File.Exists(allFiles[fileChoice].Key)) { await using StreamWriter sw = File.CreateText(allFiles[fileChoice].Key); await sw.WriteLineAsync( "This is a sample file for uploading to a bucket."); } await _s3ActionsWrapper.UploadFileAsync(allFiles[fileChoice].BucketName, allFiles[fileChoice].Key, allFiles[fileChoice].Key); break; } case 4: { var allFiles = await ListBucketsAndObjects(true); Console.WriteLine("\nEnter the number of the object and bucket to view:"); var fileChoice = GetChoiceResponse(null, allFiles.Select(f => f.Key).ToArray()); await _s3ActionsWrapper.GetObjectRetention(allFiles[fileChoice].BucketName, allFiles[fileChoice].Key); await _s3ActionsWrapper.GetBucketObjectLockConfiguration(allFiles[fileChoice].BucketName); break; } case 5: { var allFiles = await ListBucketsAndObjects(true); Console.WriteLine("\nEnter the number of the object to view:"); var fileChoice = GetChoiceResponse(null, allFiles.Select(f => f.Key).ToArray()); await _s3ActionsWrapper.GetObjectLegalHold(allFiles[fileChoice].BucketName, allFiles[fileChoice].Key); break; } } } return true; } // <summary> /// Clean up the resources from the scenario. /// </summary> /// <param name="interactive">True to run as interactive.</param> /// <returns>True if successful.</returns> public static async Task<bool> Cleanup(bool interactive) { Console.WriteLine(new string('-', 80)); if (!interactive || GetYesNoResponse("Do you want to clean up all files and buckets? (y/n) ")) { // Remove all locks and delete all buckets and objects. var allFiles = await ListBucketsAndObjects(false); foreach (var fileInfo in allFiles) { // Check for a legal hold. var legalHold = await _s3ActionsWrapper.GetObjectLegalHold(fileInfo.BucketName, fileInfo.Key); if (legalHold?.Status?.Value == ObjectLockLegalHoldStatus.On) { await _s3ActionsWrapper.ModifyObjectLegalHold(fileInfo.BucketName, fileInfo.Key, ObjectLockLegalHoldStatus.Off); } // Check for a retention period. var retention = await _s3ActionsWrapper.GetObjectRetention(fileInfo.BucketName, fileInfo.Key); var hasRetentionPeriod = retention?.Mode == ObjectLockRetentionMode.Governance && retention.RetainUntilDate > DateTime.UtcNow.Date; await _s3ActionsWrapper.DeleteObjectFromBucket(fileInfo.BucketName, fileInfo.Key, hasRetentionPeriod, fileInfo.VersionId); } foreach (var bucketName in bucketNames) { await _s3ActionsWrapper.DeleteBucketByName(bucketName); } } else { Console.WriteLine( "Ok, we'll leave the resources intact.\n" + "Don't forget to delete them when you're done with them or you might incur unexpected charges." ); } Console.WriteLine(new string('-', 80)); return true; } /// <summary> /// Helper method to get a yes or no response from the user. /// </summary> /// <param name="question">The question string to print on the console.</param> /// <returns>True if the user responds with a yes.</returns> private static bool GetYesNoResponse(string question) { Console.WriteLine(question); var ynResponse = Console.ReadLine(); var response = ynResponse != null && ynResponse.Equals("y", StringComparison.InvariantCultureIgnoreCase); return response; } /// <summary> /// Helper method to get a choice response from the user. /// </summary> /// <param name="question">The question string to print on the console.</param> /// <param name="choices">The choices to print on the console.</param> /// <returns>The index of the selected choice</returns> private static int GetChoiceResponse(string? question, string[] choices) { if (question != null) { Console.WriteLine(question); for (int i = 0; i < choices.Length; i++) { Console.WriteLine($"\t{i + 1}. {choices[i]}"); } } var choiceNumber = 0; while (choiceNumber < 1 || choiceNumber > choices.Length) { var choice = Console.ReadLine(); Int32.TryParse(choice, out choiceNumber); } return choiceNumber - 1; } }
A wrapper class for S3 functions.
using System.Net; using Amazon.S3; using Amazon.S3.Model; using Microsoft.Extensions.Configuration; namespace S3ObjectLockScenario; /// <summary> /// Encapsulate the Amazon S3 operations. /// </summary> public class S3ActionsWrapper { private readonly IAmazonS3 _amazonS3; /// <summary> /// Constructor for the S3ActionsWrapper. /// </summary> /// <param name="amazonS3">The injected S3 client.</param> public S3ActionsWrapper(IAmazonS3 amazonS3, IConfiguration configuration) { _amazonS3 = amazonS3; } /// <summary> /// Create a new Amazon S3 bucket with object lock actions. /// </summary> /// <param name="bucketName">The name of the bucket to create.</param> /// <param name="enableObjectLock">True to enable object lock on the bucket.</param> /// <returns>True if successful.</returns> public async Task<bool> CreateBucketWithObjectLock(string bucketName, bool enableObjectLock) { Console.WriteLine($"\tCreating bucket {bucketName} with object lock {enableObjectLock}."); try { var request = new PutBucketRequest { BucketName = bucketName, UseClientRegion = true, ObjectLockEnabledForBucket = enableObjectLock, }; var response = await _amazonS3.PutBucketAsync(request); return response.HttpStatusCode == System.Net.HttpStatusCode.OK; } catch (AmazonS3Exception ex) { Console.WriteLine($"Error creating bucket: '{ex.Message}'"); return false; } } /// <summary> /// Enable object lock on an existing bucket. /// </summary> /// <param name="bucketName">The name of the bucket to modify.</param> /// <returns>True if successful.</returns> public async Task<bool> EnableObjectLockOnBucket(string bucketName) { try { // First, enable Versioning on the bucket. await _amazonS3.PutBucketVersioningAsync(new PutBucketVersioningRequest() { BucketName = bucketName, VersioningConfig = new S3BucketVersioningConfig() { EnableMfaDelete = false, Status = VersionStatus.Enabled } }); var request = new PutObjectLockConfigurationRequest() { BucketName = bucketName, ObjectLockConfiguration = new ObjectLockConfiguration() { ObjectLockEnabled = new ObjectLockEnabled("Enabled"), }, }; var response = await _amazonS3.PutObjectLockConfigurationAsync(request); Console.WriteLine($"\tAdded an object lock policy to bucket {bucketName}."); return response.HttpStatusCode == System.Net.HttpStatusCode.OK; } catch (AmazonS3Exception ex) { Console.WriteLine($"Error modifying object lock: '{ex.Message}'"); return false; } } /// <summary> /// Set or modify a retention period on an object in an S3 bucket. /// </summary> /// <param name="bucketName">The bucket of the object.</param> /// <param name="objectKey">The key of the object.</param> /// <param name="retention">The retention mode.</param> /// <param name="retainUntilDate">The date retention expires.</param> /// <returns>True if successful.</returns> public async Task<bool> ModifyObjectRetentionPeriod(string bucketName, string objectKey, ObjectLockRetentionMode retention, DateTime retainUntilDate) { try { var request = new PutObjectRetentionRequest() { BucketName = bucketName, Key = objectKey, Retention = new ObjectLockRetention() { Mode = retention, RetainUntilDate = retainUntilDate } }; var response = await _amazonS3.PutObjectRetentionAsync(request); Console.WriteLine($"\tSet retention for {objectKey} in {bucketName} until {retainUntilDate:d}."); return response.HttpStatusCode == System.Net.HttpStatusCode.OK; } catch (AmazonS3Exception ex) { Console.WriteLine($"\tError modifying retention period: '{ex.Message}'"); return false; } } /// <summary> /// Set or modify a retention period on an S3 bucket. /// </summary> /// <param name="bucketName">The bucket to modify.</param> /// <param name="retention">The retention mode.</param> /// <param name="retainUntilDate">The date for retention until.</param> /// <returns>True if successful.</returns> public async Task<bool> ModifyBucketDefaultRetention(string bucketName, bool enableObjectLock, ObjectLockRetentionMode retention, DateTime retainUntilDate) { var enabledString = enableObjectLock ? "Enabled" : "Disabled"; var timeDifference = retainUntilDate.Subtract(DateTime.Now); try { // First, enable Versioning on the bucket. await _amazonS3.PutBucketVersioningAsync(new PutBucketVersioningRequest() { BucketName = bucketName, VersioningConfig = new S3BucketVersioningConfig() { EnableMfaDelete = false, Status = VersionStatus.Enabled } }); var request = new PutObjectLockConfigurationRequest() { BucketName = bucketName, ObjectLockConfiguration = new ObjectLockConfiguration() { ObjectLockEnabled = new ObjectLockEnabled(enabledString), Rule = new ObjectLockRule() { DefaultRetention = new DefaultRetention() { Mode = retention, Days = timeDifference.Days // Can be specified in days or years but not both. } } } }; var response = await _amazonS3.PutObjectLockConfigurationAsync(request); Console.WriteLine($"\tAdded a default retention to bucket {bucketName}."); return response.HttpStatusCode == System.Net.HttpStatusCode.OK; } catch (AmazonS3Exception ex) { Console.WriteLine($"\tError modifying object lock: '{ex.Message}'"); return false; } } /// <summary> /// Get the retention period for an S3 object. /// </summary> /// <param name="bucketName">The bucket of the object.</param> /// <param name="objectKey">The object key.</param> /// <returns>The object retention details.</returns> public async Task<ObjectLockRetention> GetObjectRetention(string bucketName, string objectKey) { try { var request = new GetObjectRetentionRequest() { BucketName = bucketName, Key = objectKey }; var response = await _amazonS3.GetObjectRetentionAsync(request); Console.WriteLine($"\tObject retention for {objectKey} in {bucketName}: " + $"\n\t{response.Retention.Mode} until {response.Retention.RetainUntilDate:d}."); return response.Retention; } catch (AmazonS3Exception ex) { Console.WriteLine($"\tUnable to fetch object lock retention: '{ex.Message}'"); return new ObjectLockRetention(); } } /// <summary> /// Set or modify a legal hold on an object in an S3 bucket. /// </summary> /// <param name="bucketName">The bucket of the object.</param> /// <param name="objectKey">The key of the object.</param> /// <param name="holdStatus">The On or Off status for the legal hold.</param> /// <returns>True if successful.</returns> public async Task<bool> ModifyObjectLegalHold(string bucketName, string objectKey, ObjectLockLegalHoldStatus holdStatus) { try { var request = new PutObjectLegalHoldRequest() { BucketName = bucketName, Key = objectKey, LegalHold = new ObjectLockLegalHold() { Status = holdStatus } }; var response = await _amazonS3.PutObjectLegalHoldAsync(request); Console.WriteLine($"\tModified legal hold for {objectKey} in {bucketName}."); return response.HttpStatusCode == System.Net.HttpStatusCode.OK; } catch (AmazonS3Exception ex) { Console.WriteLine($"\tError modifying legal hold: '{ex.Message}'"); return false; } } /// <summary> /// Get the legal hold details for an S3 object. /// </summary> /// <param name="bucketName">The bucket of the object.</param> /// <param name="objectKey">The object key.</param> /// <returns>The object legal hold details.</returns> public async Task<ObjectLockLegalHold> GetObjectLegalHold(string bucketName, string objectKey) { try { var request = new GetObjectLegalHoldRequest() { BucketName = bucketName, Key = objectKey }; var response = await _amazonS3.GetObjectLegalHoldAsync(request); Console.WriteLine($"\tObject legal hold for {objectKey} in {bucketName}: " + $"\n\tStatus: {response.LegalHold.Status}"); return response.LegalHold; } catch (AmazonS3Exception ex) { Console.WriteLine($"\tUnable to fetch legal hold: '{ex.Message}'"); return new ObjectLockLegalHold(); } } /// <summary> /// Get the object lock configuration details for an S3 bucket. /// </summary> /// <param name="bucketName">The bucket to get details.</param> /// <returns>The bucket's object lock configuration details.</returns> public async Task<ObjectLockConfiguration> GetBucketObjectLockConfiguration(string bucketName) { try { var request = new GetObjectLockConfigurationRequest() { BucketName = bucketName }; var response = await _amazonS3.GetObjectLockConfigurationAsync(request); Console.WriteLine($"\tBucket object lock config for {bucketName} in {bucketName}: " + $"\n\tEnabled: {response.ObjectLockConfiguration.ObjectLockEnabled}" + $"\n\tRule: {response.ObjectLockConfiguration.Rule?.DefaultRetention}"); return response.ObjectLockConfiguration; } catch (AmazonS3Exception ex) { Console.WriteLine($"\tUnable to fetch object lock config: '{ex.Message}'"); return new ObjectLockConfiguration(); } } /// <summary> /// Upload a file from the local computer to an Amazon S3 bucket. /// </summary> /// <param name="bucketName">The Amazon S3 bucket to use.</param> /// <param name="objectName">The object to upload.</param> /// <param name="filePath">The path, including file name, of the object to upload.</param> /// <returns>True if success.<returns> public async Task<bool> UploadFileAsync(string bucketName, string objectName, string filePath) { var request = new PutObjectRequest { BucketName = bucketName, Key = objectName, FilePath = filePath, ChecksumAlgorithm = ChecksumAlgorithm.SHA256 }; var response = await _amazonS3.PutObjectAsync(request); if (response.HttpStatusCode == System.Net.HttpStatusCode.OK) { Console.WriteLine($"\tSuccessfully uploaded {objectName} to {bucketName}."); return true; } else { Console.WriteLine($"\tCould not upload {objectName} to {bucketName}."); return false; } } /// <summary> /// List bucket objects and versions. /// </summary> /// <param name="bucketName">The Amazon S3 bucket to use.</param> /// <returns>The list of objects and versions.</returns> public async Task<ListVersionsResponse> ListBucketObjectsAndVersions(string bucketName) { var request = new ListVersionsRequest() { BucketName = bucketName }; var response = await _amazonS3.ListVersionsAsync(request); return response; } /// <summary> /// Delete an object from a specific bucket. /// </summary> /// <param name="bucketName">The Amazon S3 bucket to use.</param> /// <param name="objectKey">The key of the object to delete.</param> /// <param name="hasRetention">True if the object has retention settings.</param> /// <param name="versionId">Optional versionId.</param> /// <returns>True if successful.</returns> public async Task<bool> DeleteObjectFromBucket(string bucketName, string objectKey, bool hasRetention, string? versionId = null) { try { var request = new DeleteObjectRequest() { BucketName = bucketName, Key = objectKey, VersionId = versionId, }; if (hasRetention) { // Set the BypassGovernanceRetention header // if the file has retention settings. request.BypassGovernanceRetention = true; } await _amazonS3.DeleteObjectAsync(request); Console.WriteLine( $"Deleted {objectKey} in {bucketName}."); return true; } catch (AmazonS3Exception ex) { Console.WriteLine($"\tUnable to delete object {objectKey} in bucket {bucketName}: " + ex.Message); return false; } } /// <summary> /// Delete a specific bucket. /// </summary> /// <param name="bucketName">The Amazon S3 bucket to use.</param> /// <param name="objectKey">The key of the object to delete.</param> /// <param name="versionId">Optional versionId.</param> /// <returns>True if successful.</returns> public async Task<bool> DeleteBucketByName(string bucketName) { try { var request = new DeleteBucketRequest() { BucketName = bucketName, }; var response = await _amazonS3.DeleteBucketAsync(request); Console.WriteLine($"\tDelete for {bucketName} complete."); return response.HttpStatusCode == HttpStatusCode.OK; } catch (AmazonS3Exception ex) { Console.WriteLine($"\tUnable to delete bucket {bucketName}: " + ex.Message); return false; } } }
-
For API details, see the following topics in AWS SDK for .NET API Reference.
-
- Go
-
- SDK for Go V2
-
Note
There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository
. Run an interactive scenario demonstrating Amazon S3 object lock features.
import ( "context" "fmt" "log" "strings" "s3_object_lock/actions" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/feature/s3/manager" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3/types" "github.com/awsdocs/aws-doc-sdk-examples/gov2/demotools" ) // ObjectLockScenario contains the steps to run the S3 Object Lock workflow. type ObjectLockScenario struct { questioner demotools.IQuestioner resources Resources s3Actions *actions.S3Actions sdkConfig aws.Config } // NewObjectLockScenario constructs a new ObjectLockScenario instance. func NewObjectLockScenario(sdkConfig aws.Config, questioner demotools.IQuestioner) ObjectLockScenario { scenario := ObjectLockScenario{ questioner: questioner, resources: Resources{}, s3Actions: &actions.S3Actions{S3Client: s3.NewFromConfig(sdkConfig)}, sdkConfig: sdkConfig, } scenario.s3Actions.S3Manager = manager.NewUploader(scenario.s3Actions.S3Client) scenario.resources.init(scenario.s3Actions, questioner) return scenario } type nameLocked struct { name string locked bool } var createInfo = []nameLocked{ {"standard-bucket", false}, {"lock-bucket", true}, {"retention-bucket", false}, } // CreateBuckets creates the S3 buckets required for the workflow. func (scenario *ObjectLockScenario) CreateBuckets(ctx context.Context) { log.Println("Let's create some S3 buckets to use for this workflow.") success := false for !success { prefix := scenario.questioner.Ask( "This example creates three buckets. Enter a prefix to name your buckets (remember bucket names must be globally unique):") for _, info := range createInfo { log.Println(fmt.Sprintf("%s.%s", prefix, info.name)) bucketName, err := scenario.s3Actions.CreateBucketWithLock(ctx, fmt.Sprintf("%s.%s", prefix, info.name), scenario.sdkConfig.Region, info.locked) if err != nil { switch err.(type) { case *types.BucketAlreadyExists, *types.BucketAlreadyOwnedByYou: log.Printf("Couldn't create bucket %s.\n", bucketName) default: panic(err) } break } scenario.resources.demoBuckets[info.name] = &DemoBucket{ name: bucketName, objectKeys: []string{}, } log.Printf("Created bucket %s.\n", bucketName) } if len(scenario.resources.demoBuckets) < len(createInfo) { scenario.resources.deleteBuckets(ctx) } else { success = true } } log.Println("S3 buckets created.") log.Println(strings.Repeat("-", 88)) } // EnableLockOnBucket enables object locking on an existing bucket. func (scenario *ObjectLockScenario) EnableLockOnBucket(ctx context.Context) { log.Println("\nA bucket can be configured to use object locking.") scenario.questioner.Ask("Press Enter to continue.") var err error bucket := scenario.resources.demoBuckets["retention-bucket"] err = scenario.s3Actions.EnableObjectLockOnBucket(ctx, bucket.name) if err != nil { switch err.(type) { case *types.NoSuchBucket: log.Printf("Couldn't enable object locking on bucket %s.\n", bucket.name) default: panic(err) } } else { log.Printf("Object locking enabled on bucket %s.", bucket.name) } log.Println(strings.Repeat("-", 88)) } // SetDefaultRetentionPolicy sets a default retention governance policy on a bucket. func (scenario *ObjectLockScenario) SetDefaultRetentionPolicy(ctx context.Context) { log.Println("\nA bucket can be configured to use object locking with a default retention period.") bucket := scenario.resources.demoBuckets["retention-bucket"] retentionPeriod := scenario.questioner.AskInt("Enter the default retention period in days: ") err := scenario.s3Actions.ModifyDefaultBucketRetention(ctx, bucket.name, types.ObjectLockEnabledEnabled, int32(retentionPeriod), types.ObjectLockRetentionModeGovernance) if err != nil { switch err.(type) { case *types.NoSuchBucket: log.Printf("Couldn't configure a default retention period on bucket %s.\n", bucket.name) default: panic(err) } } else { log.Printf("Default retention policy set on bucket %s with %d day retention period.", bucket.name, retentionPeriod) bucket.retentionEnabled = true } log.Println(strings.Repeat("-", 88)) } // UploadTestObjects uploads test objects to the S3 buckets. func (scenario *ObjectLockScenario) UploadTestObjects(ctx context.Context) { log.Println("Uploading test objects to S3 buckets.") for _, info := range createInfo { bucket := scenario.resources.demoBuckets[info.name] for i := 0; i < 2; i++ { key, err := scenario.s3Actions.UploadObject(ctx, bucket.name, fmt.Sprintf("example-%d", i), fmt.Sprintf("Example object content #%d in bucket %s.", i, bucket.name)) if err != nil { switch err.(type) { case *types.NoSuchBucket: log.Printf("Couldn't upload %s to bucket %s.\n", key, bucket.name) default: panic(err) } } else { log.Printf("Uploaded %s to bucket %s.\n", key, bucket.name) bucket.objectKeys = append(bucket.objectKeys, key) } } } scenario.questioner.Ask("Test objects uploaded. Press Enter to continue.") log.Println(strings.Repeat("-", 88)) } // SetObjectLockConfigurations sets object lock configurations on the test objects. func (scenario *ObjectLockScenario) SetObjectLockConfigurations(ctx context.Context) { log.Println("Now let's set object lock configurations on individual objects.") buckets := []*DemoBucket{scenario.resources.demoBuckets["lock-bucket"], scenario.resources.demoBuckets["retention-bucket"]} for _, bucket := range buckets { for index, objKey := range bucket.objectKeys { switch index { case 0: if scenario.questioner.AskBool(fmt.Sprintf("\nDo you want to add a legal hold to %s in %s (y/n)? ", objKey, bucket.name), "y") { err := scenario.s3Actions.PutObjectLegalHold(ctx, bucket.name, objKey, "", types.ObjectLockLegalHoldStatusOn) if err != nil { switch err.(type) { case *types.NoSuchKey: log.Printf("Couldn't set legal hold on %s.\n", objKey) default: panic(err) } } else { log.Printf("Legal hold set on %s.\n", objKey) } } case 1: q := fmt.Sprintf("\nDo you want to add a 1 day Governance retention period to %s in %s?\n"+ "Reminder: Only a user with the s3:BypassGovernanceRetention permission is able to delete this object\n"+ "or its bucket until the retention period has expired. (y/n) ", objKey, bucket.name) if scenario.questioner.AskBool(q, "y") { err := scenario.s3Actions.PutObjectRetention(ctx, bucket.name, objKey, types.ObjectLockRetentionModeGovernance, 1) if err != nil { switch err.(type) { case *types.NoSuchKey: log.Printf("Couldn't set retention period on %s in %s.\n", objKey, bucket.name) default: panic(err) } } else { log.Printf("Retention period set to 1 for %s.", objKey) bucket.retentionEnabled = true } } } } } log.Println(strings.Repeat("-", 88)) } const ( ListAll = iota DeleteObject DeleteRetentionObject OverwriteObject ViewRetention ViewLegalHold Finish ) // InteractWithObjects allows the user to interact with the objects and test the object lock configurations. func (scenario *ObjectLockScenario) InteractWithObjects(ctx context.Context) { log.Println("Now you can interact with the objects to explore the object lock configurations.") interactiveChoices := []string{ "List all objects and buckets.", "Attempt to delete an object.", "Attempt to delete an object with retention period bypass.", "Attempt to overwrite a file.", "View the retention settings for an object.", "View the legal hold settings for an object.", "Finish the workflow."} choice := ListAll for choice != Finish { objList := scenario.GetAllObjects(ctx) objChoices := scenario.makeObjectChoiceList(objList) choice = scenario.questioner.AskChoice("Choose an action from the menu:\n", interactiveChoices) switch choice { case ListAll: log.Println("The current objects in the example buckets are:") for _, objChoice := range objChoices { log.Println("\t", objChoice) } case DeleteObject, DeleteRetentionObject: objChoice := scenario.questioner.AskChoice("Enter the number of the object to delete:\n", objChoices) obj := objList[objChoice] deleted, err := scenario.s3Actions.DeleteObject(ctx, obj.bucket, obj.key, obj.versionId, choice == DeleteRetentionObject) if err != nil { switch err.(type) { case *types.NoSuchKey: log.Println("Nothing to delete.") default: panic(err) } } else if deleted { log.Printf("Object %s deleted.\n", obj.key) } case OverwriteObject: objChoice := scenario.questioner.AskChoice("Enter the number of the object to overwrite:\n", objChoices) obj := objList[objChoice] _, err := scenario.s3Actions.UploadObject(ctx, obj.bucket, obj.key, fmt.Sprintf("New content in object %s.", obj.key)) if err != nil { switch err.(type) { case *types.NoSuchBucket: log.Println("Couldn't upload to nonexistent bucket.") default: panic(err) } } else { log.Printf("Uploaded new content to object %s.\n", obj.key) } case ViewRetention: objChoice := scenario.questioner.AskChoice("Enter the number of the object to view:\n", objChoices) obj := objList[objChoice] retention, err := scenario.s3Actions.GetObjectRetention(ctx, obj.bucket, obj.key) if err != nil { switch err.(type) { case *types.NoSuchKey: log.Printf("Can't get retention configuration for %s.\n", obj.key) default: panic(err) } } else if retention != nil { log.Printf("Object %s has retention mode %s until %v.\n", obj.key, retention.Mode, retention.RetainUntilDate) } else { log.Printf("Object %s does not have object retention configured.\n", obj.key) } case ViewLegalHold: objChoice := scenario.questioner.AskChoice("Enter the number of the object to view:\n", objChoices) obj := objList[objChoice] legalHold, err := scenario.s3Actions.GetObjectLegalHold(ctx, obj.bucket, obj.key, obj.versionId) if err != nil { switch err.(type) { case *types.NoSuchKey: log.Printf("Can't get legal hold configuration for %s.\n", obj.key) default: panic(err) } } else if legalHold != nil { log.Printf("Object %s has legal hold %v.", obj.key, *legalHold) } else { log.Printf("Object %s does not have legal hold configured.", obj.key) } case Finish: log.Println("Let's clean up.") } log.Println(strings.Repeat("-", 88)) } } type BucketKeyVersionId struct { bucket string key string versionId string } // GetAllObjects gets the object versions in the example S3 buckets and returns them in a flattened list. func (scenario *ObjectLockScenario) GetAllObjects(ctx context.Context) []BucketKeyVersionId { var objectList []BucketKeyVersionId for _, info := range createInfo { bucket := scenario.resources.demoBuckets[info.name] versions, err := scenario.s3Actions.ListObjectVersions(ctx, bucket.name) if err != nil { switch err.(type) { case *types.NoSuchBucket: log.Printf("Couldn't get object versions for %s.\n", bucket.name) default: panic(err) } } else { for _, version := range versions { objectList = append(objectList, BucketKeyVersionId{bucket: bucket.name, key: *version.Key, versionId: *version.VersionId}) } } } return objectList } // makeObjectChoiceList makes the object version list into a list of strings that are displayed // as choices. func (scenario *ObjectLockScenario) makeObjectChoiceList(bucketObjects []BucketKeyVersionId) []string { choices := make([]string, len(bucketObjects)) for i := 0; i < len(bucketObjects); i++ { choices[i] = fmt.Sprintf("%s in %s with VersionId %s.", bucketObjects[i].key, bucketObjects[i].bucket, bucketObjects[i].versionId) } return choices } // Run runs the S3 Object Lock workflow scenario. func (scenario *ObjectLockScenario) Run(ctx context.Context) { defer func() { if r := recover(); r != nil { log.Println("Something went wrong with the demo.") _, isMock := scenario.questioner.(*demotools.MockQuestioner) if isMock || scenario.questioner.AskBool("Do you want to see the full error message (y/n)?", "y") { log.Println(r) } scenario.resources.Cleanup(ctx) } }() log.Println(strings.Repeat("-", 88)) log.Println("Welcome to the Amazon S3 Object Lock Workflow Scenario.") log.Println(strings.Repeat("-", 88)) scenario.CreateBuckets(ctx) scenario.EnableLockOnBucket(ctx) scenario.SetDefaultRetentionPolicy(ctx) scenario.UploadTestObjects(ctx) scenario.SetObjectLockConfigurations(ctx) scenario.InteractWithObjects(ctx) scenario.resources.Cleanup(ctx) log.Println(strings.Repeat("-", 88)) log.Println("Thanks for watching!") log.Println(strings.Repeat("-", 88)) }
Define a struct that wraps S3 actions used in this example.
import ( "bytes" "context" "errors" "fmt" "log" "time" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/feature/s3/manager" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3/types" "github.com/aws/smithy-go" ) // S3Actions wraps S3 service actions. type S3Actions struct { S3Client *s3.Client S3Manager *manager.Uploader } // CreateBucketWithLock creates a new S3 bucket with optional object locking enabled // and waits for the bucket to exist before returning. func (actor S3Actions) CreateBucketWithLock(ctx context.Context, bucket string, region string, enableObjectLock bool) (string, error) { input := &s3.CreateBucketInput{ Bucket: aws.String(bucket), CreateBucketConfiguration: &types.CreateBucketConfiguration{ LocationConstraint: types.BucketLocationConstraint(region), }, } if enableObjectLock { input.ObjectLockEnabledForBucket = aws.Bool(true) } _, err := actor.S3Client.CreateBucket(ctx, input) if err != nil { var owned *types.BucketAlreadyOwnedByYou var exists *types.BucketAlreadyExists if errors.As(err, &owned) { log.Printf("You already own bucket %s.\n", bucket) err = owned } else if errors.As(err, &exists) { log.Printf("Bucket %s already exists.\n", bucket) err = exists } } else { err = s3.NewBucketExistsWaiter(actor.S3Client).Wait( ctx, &s3.HeadBucketInput{Bucket: aws.String(bucket)}, time.Minute) if err != nil { log.Printf("Failed attempt to wait for bucket %s to exist.\n", bucket) } } return bucket, err } // GetObjectLegalHold retrieves the legal hold status for an S3 object. func (actor S3Actions) GetObjectLegalHold(ctx context.Context, bucket string, key string, versionId string) (*types.ObjectLockLegalHoldStatus, error) { var status *types.ObjectLockLegalHoldStatus input := &s3.GetObjectLegalHoldInput{ Bucket: aws.String(bucket), Key: aws.String(key), VersionId: aws.String(versionId), } output, err := actor.S3Client.GetObjectLegalHold(ctx, input) if err != nil { var noSuchKeyErr *types.NoSuchKey var apiErr *smithy.GenericAPIError if errors.As(err, &noSuchKeyErr) { log.Printf("Object %s does not exist in bucket %s.\n", key, bucket) err = noSuchKeyErr } else if errors.As(err, &apiErr) { switch apiErr.ErrorCode() { case "NoSuchObjectLockConfiguration": log.Printf("Object %s does not have an object lock configuration.\n", key) err = nil case "InvalidRequest": log.Printf("Bucket %s does not have an object lock configuration.\n", bucket) err = nil } } } else { status = &output.LegalHold.Status } return status, err } // GetObjectLockConfiguration retrieves the object lock configuration for an S3 bucket. func (actor S3Actions) GetObjectLockConfiguration(ctx context.Context, bucket string) (*types.ObjectLockConfiguration, error) { var lockConfig *types.ObjectLockConfiguration input := &s3.GetObjectLockConfigurationInput{ Bucket: aws.String(bucket), } output, err := actor.S3Client.GetObjectLockConfiguration(ctx, input) if err != nil { var noBucket *types.NoSuchBucket var apiErr *smithy.GenericAPIError if errors.As(err, &noBucket) { log.Printf("Bucket %s does not exist.\n", bucket) err = noBucket } else if errors.As(err, &apiErr) && apiErr.ErrorCode() == "ObjectLockConfigurationNotFoundError" { log.Printf("Bucket %s does not have an object lock configuration.\n", bucket) err = nil } } else { lockConfig = output.ObjectLockConfiguration } return lockConfig, err } // GetObjectRetention retrieves the object retention configuration for an S3 object. func (actor S3Actions) GetObjectRetention(ctx context.Context, bucket string, key string) (*types.ObjectLockRetention, error) { var retention *types.ObjectLockRetention input := &s3.GetObjectRetentionInput{ Bucket: aws.String(bucket), Key: aws.String(key), } output, err := actor.S3Client.GetObjectRetention(ctx, input) if err != nil { var noKey *types.NoSuchKey var apiErr *smithy.GenericAPIError if errors.As(err, &noKey) { log.Printf("Object %s does not exist in bucket %s.\n", key, bucket) err = noKey } else if errors.As(err, &apiErr) { switch apiErr.ErrorCode() { case "NoSuchObjectLockConfiguration": err = nil case "InvalidRequest": log.Printf("Bucket %s does not have locking enabled.", bucket) err = nil } } } else { retention = output.Retention } return retention, err } // PutObjectLegalHold sets the legal hold configuration for an S3 object. func (actor S3Actions) PutObjectLegalHold(ctx context.Context, bucket string, key string, versionId string, legalHoldStatus types.ObjectLockLegalHoldStatus) error { input := &s3.PutObjectLegalHoldInput{ Bucket: aws.String(bucket), Key: aws.String(key), LegalHold: &types.ObjectLockLegalHold{ Status: legalHoldStatus, }, } if versionId != "" { input.VersionId = aws.String(versionId) } _, err := actor.S3Client.PutObjectLegalHold(ctx, input) if err != nil { var noKey *types.NoSuchKey if errors.As(err, &noKey) { log.Printf("Object %s does not exist in bucket %s.\n", key, bucket) err = noKey } } return err } // ModifyDefaultBucketRetention modifies the default retention period of an existing bucket. func (actor S3Actions) ModifyDefaultBucketRetention( ctx context.Context, bucket string, lockMode types.ObjectLockEnabled, retentionPeriod int32, retentionMode types.ObjectLockRetentionMode) error { input := &s3.PutObjectLockConfigurationInput{ Bucket: aws.String(bucket), ObjectLockConfiguration: &types.ObjectLockConfiguration{ ObjectLockEnabled: lockMode, Rule: &types.ObjectLockRule{ DefaultRetention: &types.DefaultRetention{ Days: aws.Int32(retentionPeriod), Mode: retentionMode, }, }, }, } _, err := actor.S3Client.PutObjectLockConfiguration(ctx, input) if err != nil { var noBucket *types.NoSuchBucket if errors.As(err, &noBucket) { log.Printf("Bucket %s does not exist.\n", bucket) err = noBucket } } return err } // EnableObjectLockOnBucket enables object locking on an existing bucket. func (actor S3Actions) EnableObjectLockOnBucket(ctx context.Context, bucket string) error { // Versioning must be enabled on the bucket before object locking is enabled. verInput := &s3.PutBucketVersioningInput{ Bucket: aws.String(bucket), VersioningConfiguration: &types.VersioningConfiguration{ MFADelete: types.MFADeleteDisabled, Status: types.BucketVersioningStatusEnabled, }, } _, err := actor.S3Client.PutBucketVersioning(ctx, verInput) if err != nil { var noBucket *types.NoSuchBucket if errors.As(err, &noBucket) { log.Printf("Bucket %s does not exist.\n", bucket) err = noBucket } return err } input := &s3.PutObjectLockConfigurationInput{ Bucket: aws.String(bucket), ObjectLockConfiguration: &types.ObjectLockConfiguration{ ObjectLockEnabled: types.ObjectLockEnabledEnabled, }, } _, err = actor.S3Client.PutObjectLockConfiguration(ctx, input) if err != nil { var noBucket *types.NoSuchBucket if errors.As(err, &noBucket) { log.Printf("Bucket %s does not exist.\n", bucket) err = noBucket } } return err } // PutObjectRetention sets the object retention configuration for an S3 object. func (actor S3Actions) PutObjectRetention(ctx context.Context, bucket string, key string, retentionMode types.ObjectLockRetentionMode, retentionPeriodDays int32) error { input := &s3.PutObjectRetentionInput{ Bucket: aws.String(bucket), Key: aws.String(key), Retention: &types.ObjectLockRetention{ Mode: retentionMode, RetainUntilDate: aws.Time(time.Now().AddDate(0, 0, int(retentionPeriodDays))), }, BypassGovernanceRetention: aws.Bool(true), } _, err := actor.S3Client.PutObjectRetention(ctx, input) if err != nil { var noKey *types.NoSuchKey if errors.As(err, &noKey) { log.Printf("Object %s does not exist in bucket %s.\n", key, bucket) err = noKey } } return err } // UploadObject uses the S3 upload manager to upload an object to a bucket. func (actor S3Actions) UploadObject(ctx context.Context, bucket string, key string, contents string) (string, error) { var outKey string input := &s3.PutObjectInput{ Bucket: aws.String(bucket), Key: aws.String(key), Body: bytes.NewReader([]byte(contents)), ChecksumAlgorithm: types.ChecksumAlgorithmSha256, } output, err := actor.S3Manager.Upload(ctx, input) if err != nil { var noBucket *types.NoSuchBucket if errors.As(err, &noBucket) { log.Printf("Bucket %s does not exist.\n", bucket) err = noBucket } } else { err := s3.NewObjectExistsWaiter(actor.S3Client).Wait(ctx, &s3.HeadObjectInput{ Bucket: aws.String(bucket), Key: aws.String(key), }, time.Minute) if err != nil { log.Printf("Failed attempt to wait for object %s to exist in %s.\n", key, bucket) } else { outKey = *output.Key } } return outKey, err } // ListObjectVersions lists all versions of all objects in a bucket. func (actor S3Actions) ListObjectVersions(ctx context.Context, bucket string) ([]types.ObjectVersion, error) { var err error var output *s3.ListObjectVersionsOutput var versions []types.ObjectVersion input := &s3.ListObjectVersionsInput{Bucket: aws.String(bucket)} versionPaginator := s3.NewListObjectVersionsPaginator(actor.S3Client, input) for versionPaginator.HasMorePages() { output, err = versionPaginator.NextPage(ctx) if err != nil { var noBucket *types.NoSuchBucket if errors.As(err, &noBucket) { log.Printf("Bucket %s does not exist.\n", bucket) err = noBucket } break } else { versions = append(versions, output.Versions...) } } return versions, err } // DeleteObject deletes an object from a bucket. func (actor S3Actions) DeleteObject(ctx context.Context, bucket string, key string, versionId string, bypassGovernance bool) (bool, error) { deleted := false input := &s3.DeleteObjectInput{ Bucket: aws.String(bucket), Key: aws.String(key), } if versionId != "" { input.VersionId = aws.String(versionId) } if bypassGovernance { input.BypassGovernanceRetention = aws.Bool(true) } _, err := actor.S3Client.DeleteObject(ctx, input) if err != nil { var noKey *types.NoSuchKey var apiErr *smithy.GenericAPIError if errors.As(err, &noKey) { log.Printf("Object %s does not exist in %s.\n", key, bucket) err = noKey } else if errors.As(err, &apiErr) { switch apiErr.ErrorCode() { case "AccessDenied": log.Printf("Access denied: cannot delete object %s from %s.\n", key, bucket) err = nil case "InvalidArgument": if bypassGovernance { log.Printf("You cannot specify bypass governance on a bucket without lock enabled.") err = nil } } } } else { err = s3.NewObjectNotExistsWaiter(actor.S3Client).Wait( ctx, &s3.HeadObjectInput{Bucket: aws.String(bucket), Key: aws.String(key)}, time.Minute) if err != nil { log.Printf("Failed attempt to wait for object %s in bucket %s to be deleted.\n", key, bucket) } else { deleted = true } } return deleted, err } // DeleteObjects deletes a list of objects from a bucket. func (actor S3Actions) DeleteObjects(ctx context.Context, bucket string, objects []types.ObjectIdentifier, bypassGovernance bool) error { if len(objects) == 0 { return nil } input := s3.DeleteObjectsInput{ Bucket: aws.String(bucket), Delete: &types.Delete{ Objects: objects, Quiet: aws.Bool(true), }, } if bypassGovernance { input.BypassGovernanceRetention = aws.Bool(true) } delOut, err := actor.S3Client.DeleteObjects(ctx, &input) if err != nil || len(delOut.Errors) > 0 { log.Printf("Error deleting objects from bucket %s.\n", bucket) if err != nil { var noBucket *types.NoSuchBucket if errors.As(err, &noBucket) { log.Printf("Bucket %s does not exist.\n", bucket) err = noBucket } } else if len(delOut.Errors) > 0 { for _, outErr := range delOut.Errors { log.Printf("%s: %s\n", *outErr.Key, *outErr.Message) } err = fmt.Errorf("%s", *delOut.Errors[0].Message) } } else { for _, delObjs := range delOut.Deleted { err = s3.NewObjectNotExistsWaiter(actor.S3Client).Wait( ctx, &s3.HeadObjectInput{Bucket: aws.String(bucket), Key: delObjs.Key}, time.Minute) if err != nil { log.Printf("Failed attempt to wait for object %s to be deleted.\n", *delObjs.Key) } else { log.Printf("Deleted %s.\n", *delObjs.Key) } } } return err }
Clean up resources.
import ( "context" "log" "s3_object_lock/actions" "time" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3/types" "github.com/awsdocs/aws-doc-sdk-examples/gov2/demotools" ) // DemoBucket contains metadata for buckets used in this example. type DemoBucket struct { name string retentionEnabled bool objectKeys []string } // Resources keeps track of AWS resources created during the ObjectLockScenario and handles // cleanup when the scenario finishes. type Resources struct { demoBuckets map[string]*DemoBucket s3Actions *actions.S3Actions questioner demotools.IQuestioner } // init initializes objects in the Resources struct. func (resources *Resources) init(s3Actions *actions.S3Actions, questioner demotools.IQuestioner) { resources.s3Actions = s3Actions resources.questioner = questioner resources.demoBuckets = map[string]*DemoBucket{} } // Cleanup deletes all AWS resources created during the ObjectLockScenario. func (resources *Resources) Cleanup(ctx context.Context) { defer func() { if r := recover(); r != nil { log.Printf("Something went wrong during cleanup.\n%v\n", r) log.Println("Use the AWS Management Console to remove any remaining resources " + "that were created for this scenario.") } }() wantDelete := resources.questioner.AskBool("Do you want to remove all of the AWS resources that were created "+ "during this demo (y/n)?", "y") if !wantDelete { log.Println("Be sure to remove resources when you're done with them to avoid unexpected charges!") return } log.Println("Removing objects from S3 buckets and deleting buckets...") resources.deleteBuckets(ctx) //resources.deleteRetentionObjects(resources.retentionBucket, resources.retentionObjects) log.Println("Cleanup complete.") } // deleteBuckets empties and then deletes all buckets created during the ObjectLockScenario. func (resources *Resources) deleteBuckets(ctx context.Context) { for _, info := range createInfo { bucket := resources.demoBuckets[info.name] resources.deleteObjects(ctx, bucket) _, err := resources.s3Actions.S3Client.DeleteBucket(ctx, &s3.DeleteBucketInput{ Bucket: aws.String(bucket.name), }) if err != nil { panic(err) } } for _, info := range createInfo { bucket := resources.demoBuckets[info.name] err := s3.NewBucketNotExistsWaiter(resources.s3Actions.S3Client).Wait( ctx, &s3.HeadBucketInput{Bucket: aws.String(bucket.name)}, time.Minute) if err != nil { log.Printf("Failed attempt to wait for bucket %s to be deleted.\n", bucket.name) } else { log.Printf("Deleted %s.\n", bucket.name) } } resources.demoBuckets = map[string]*DemoBucket{} } // deleteObjects deletes all objects in the specified bucket. func (resources *Resources) deleteObjects(ctx context.Context, bucket *DemoBucket) { lockConfig, err := resources.s3Actions.GetObjectLockConfiguration(ctx, bucket.name) if err != nil { panic(err) } versions, err := resources.s3Actions.ListObjectVersions(ctx, bucket.name) if err != nil { switch err.(type) { case *types.NoSuchBucket: log.Printf("No objects to get from %s.\n", bucket.name) default: panic(err) } } delObjects := make([]types.ObjectIdentifier, len(versions)) for i, version := range versions { if lockConfig != nil && lockConfig.ObjectLockEnabled == types.ObjectLockEnabledEnabled { status, err := resources.s3Actions.GetObjectLegalHold(ctx, bucket.name, *version.Key, *version.VersionId) if err != nil { switch err.(type) { case *types.NoSuchKey: log.Printf("Couldn't determine legal hold status for %s in %s.\n", *version.Key, bucket.name) default: panic(err) } } else if status != nil && *status == types.ObjectLockLegalHoldStatusOn { err = resources.s3Actions.PutObjectLegalHold(ctx, bucket.name, *version.Key, *version.VersionId, types.ObjectLockLegalHoldStatusOff) if err != nil { switch err.(type) { case *types.NoSuchKey: log.Printf("Couldn't turn off legal hold for %s in %s.\n", *version.Key, bucket.name) default: panic(err) } } } } delObjects[i] = types.ObjectIdentifier{Key: version.Key, VersionId: version.VersionId} } err = resources.s3Actions.DeleteObjects(ctx, bucket.name, delObjects, bucket.retentionEnabled) if err != nil { switch err.(type) { case *types.NoSuchBucket: log.Println("Nothing to delete.") default: panic(err) } } }
-
For API details, see the following topics in AWS SDK for Go API Reference.
-
- Java
-
- SDK for Java 2.x
-
Note
There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository
. Run an interactive scenario demonstrating Amazon S3 object lock features.
import software.amazon.awssdk.services.s3.model.ObjectLockLegalHold; import software.amazon.awssdk.services.s3.model.ObjectLockRetention; import java.io.BufferedWriter; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Scanner; import java.util.stream.Collectors; /* Before running this Java V2 code example, set up your development environment, including your credentials. For more information, see the following documentation topic: https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/setup.html This Java example performs the following tasks: 1. Create test Amazon Simple Storage Service (S3) buckets with different lock policies. 2. Upload sample objects to each bucket. 3. Set some Legal Hold and Retention Periods on objects and buckets. 4. Investigate lock policies by viewing settings or attempting to delete or overwrite objects. 5. Clean up objects and buckets. */ public class S3ObjectLockWorkflow { public static final String DASHES = new String(new char[80]).replace("\0", "-"); static String bucketName; static S3LockActions s3LockActions; private static final List<String> bucketNames = new ArrayList<>(); private static final List<String> fileNames = new ArrayList<>(); public static void main(String[] args) { final String usage = """ Usage: <bucketName> \s Where: bucketName - The Amazon S3 bucket name. """; if (args.length != 1) { System.out.println(usage); System.exit(1); } s3LockActions = new S3LockActions(); bucketName = args[0]; Scanner scanner = new Scanner(System.in); System.out.println(DASHES); System.out.println("Welcome to the Amazon Simple Storage Service (S3) Object Locking Workflow Scenario."); System.out.println("Press Enter to continue..."); scanner.nextLine(); configurationSetup(); System.out.println(DASHES); System.out.println(DASHES); setup(); System.out.println("Setup is complete. Press Enter to continue..."); scanner.nextLine(); System.out.println(DASHES); System.out.println(DASHES); System.out.println("Lets present the user with choices."); System.out.println("Press Enter to continue..."); scanner.nextLine(); demoActionChoices() ; System.out.println(DASHES); System.out.println(DASHES); System.out.println("Would you like to clean up the resources? (y/n)"); String delAns = scanner.nextLine().trim(); if (delAns.equalsIgnoreCase("y")) { cleanup(); System.out.println("Clean up is complete."); } System.out.println("Press Enter to continue..."); scanner.nextLine(); System.out.println(DASHES); System.out.println(DASHES); System.out.println("Amazon S3 Object Locking Workflow is complete."); System.out.println(DASHES); } // Present the user with the demo action choices. public static void demoActionChoices() { String[] choices = { "List all files in buckets.", "Attempt to delete a file.", "Attempt to delete a file with retention period bypass.", "Attempt to overwrite a file.", "View the object and bucket retention settings for a file.", "View the legal hold settings for a file.", "Finish the workflow." }; int choice = 0; while (true) { System.out.println(DASHES); choice = getChoiceResponse("Explore the S3 locking features by selecting one of the following choices:", choices); System.out.println(DASHES); System.out.println("You selected "+choices[choice]); switch (choice) { case 0 -> { s3LockActions.listBucketsAndObjects(bucketNames, true); } case 1 -> { System.out.println("Enter the number of the object to delete:"); List<S3InfoObject> allFiles = s3LockActions.listBucketsAndObjects(bucketNames, true); List<String> fileKeys = allFiles.stream().map(f -> f.getKeyName()).collect(Collectors.toList()); String[] fileKeysArray = fileKeys.toArray(new String[0]); int fileChoice = getChoiceResponse(null, fileKeysArray); String objectKey = fileKeys.get(fileChoice); String bucketName = allFiles.get(fileChoice).getBucketName(); String version = allFiles.get(fileChoice).getVersion(); s3LockActions.deleteObjectFromBucket(bucketName, objectKey, false, version); } case 2 -> { System.out.println("Enter the number of the object to delete:"); List<S3InfoObject> allFiles = s3LockActions.listBucketsAndObjects(bucketNames, true); List<String> fileKeys = allFiles.stream().map(f -> f.getKeyName()).collect(Collectors.toList()); String[] fileKeysArray = fileKeys.toArray(new String[0]); int fileChoice = getChoiceResponse(null, fileKeysArray); String objectKey = fileKeys.get(fileChoice); String bucketName = allFiles.get(fileChoice).getBucketName(); String version = allFiles.get(fileChoice).getVersion(); s3LockActions.deleteObjectFromBucket(bucketName, objectKey, true, version); } case 3 -> { System.out.println("Enter the number of the object to overwrite:"); List<S3InfoObject> allFiles = s3LockActions.listBucketsAndObjects(bucketNames, true); List<String> fileKeys = allFiles.stream().map(f -> f.getKeyName()).collect(Collectors.toList()); String[] fileKeysArray = fileKeys.toArray(new String[0]); int fileChoice = getChoiceResponse(null, fileKeysArray); String objectKey = fileKeys.get(fileChoice); String bucketName = allFiles.get(fileChoice).getBucketName(); // Attempt to overwrite the file. try (BufferedWriter writer = new BufferedWriter(new java.io.FileWriter(objectKey))) { writer.write("This is a modified text."); } catch (IOException e) { e.printStackTrace(); } s3LockActions.uploadFile(bucketName, objectKey, objectKey); } case 4 -> { System.out.println("Enter the number of the object to overwrite:"); List<S3InfoObject> allFiles = s3LockActions.listBucketsAndObjects(bucketNames, true); List<String> fileKeys = allFiles.stream().map(f -> f.getKeyName()).collect(Collectors.toList()); String[] fileKeysArray = fileKeys.toArray(new String[0]); int fileChoice = getChoiceResponse(null, fileKeysArray); String objectKey = fileKeys.get(fileChoice); String bucketName = allFiles.get(fileChoice).getBucketName(); s3LockActions.getObjectRetention(bucketName, objectKey); } case 5 -> { System.out.println("Enter the number of the object to view:"); List<S3InfoObject> allFiles = s3LockActions.listBucketsAndObjects(bucketNames, true); List<String> fileKeys = allFiles.stream().map(f -> f.getKeyName()).collect(Collectors.toList()); String[] fileKeysArray = fileKeys.toArray(new String[0]); int fileChoice = getChoiceResponse(null, fileKeysArray); String objectKey = fileKeys.get(fileChoice); String bucketName = allFiles.get(fileChoice).getBucketName(); s3LockActions.getObjectLegalHold(bucketName, objectKey); s3LockActions.getBucketObjectLockConfiguration(bucketName); } case 6 -> { System.out.println("Exiting the workflow..."); return; } default -> { System.out.println("Invalid choice. Please select again."); } } } } // Clean up the resources from the scenario. private static void cleanup() { List<S3InfoObject> allFiles = s3LockActions.listBucketsAndObjects(bucketNames, false); for (S3InfoObject fileInfo : allFiles) { String bucketName = fileInfo.getBucketName(); String key = fileInfo.getKeyName(); String version = fileInfo.getVersion(); if (bucketName.contains("lock-enabled") || (bucketName.contains("retention-after-creation"))) { ObjectLockLegalHold legalHold = s3LockActions.getObjectLegalHold(bucketName, key); if (legalHold != null) { String holdStatus = legalHold.status().name(); System.out.println(holdStatus); if (holdStatus.compareTo("ON") == 0) { s3LockActions.modifyObjectLegalHold(bucketName, key, false); } } // Check for a retention period. ObjectLockRetention retention = s3LockActions.getObjectRetention(bucketName, key); boolean hasRetentionPeriod ; hasRetentionPeriod = retention != null; s3LockActions.deleteObjectFromBucket(bucketName, key,hasRetentionPeriod, version); } else { System.out.println(bucketName +" objects do not have a legal lock"); s3LockActions.deleteObjectFromBucket(bucketName, key,false, version); } } // Delete the buckets. System.out.println("Delete "+bucketName); for (String bucket : bucketNames){ s3LockActions.deleteBucketByName(bucket); } } private static void setup() { Scanner scanner = new Scanner(System.in); System.out.println(""" For this workflow, we will use the AWS SDK for Java to create several S3 buckets and files to demonstrate working with S3 locking features. """); System.out.println("S3 buckets can be created either with or without object lock enabled."); System.out.println("Press Enter to continue..."); scanner.nextLine(); // Create three S3 buckets. s3LockActions.createBucketWithLockOptions(false, bucketNames.get(0)); s3LockActions.createBucketWithLockOptions(true, bucketNames.get(1)); s3LockActions.createBucketWithLockOptions(false, bucketNames.get(2)); System.out.println("Press Enter to continue."); scanner.nextLine(); System.out.println("Bucket "+bucketNames.get(2) +" will be configured to use object locking with a default retention period."); s3LockActions.modifyBucketDefaultRetention(bucketNames.get(2)); System.out.println("Press Enter to continue."); scanner.nextLine(); System.out.println("Object lock policies can also be added to existing buckets. For this example, we will use "+bucketNames.get(1)); s3LockActions.enableObjectLockOnBucket(bucketNames.get(1)); System.out.println("Press Enter to continue."); scanner.nextLine(); // Upload some files to the buckets. System.out.println("Now let's add some test files:"); String fileName = "exampleFile.txt"; int fileCount = 2; try (BufferedWriter writer = new BufferedWriter(new java.io.FileWriter(fileName))) { writer.write("This is a sample file for uploading to a bucket."); } catch (IOException e) { e.printStackTrace(); } for (String bucketName : bucketNames){ for (int i = 0; i < fileCount; i++) { // Get the file name without extension. String fileNameWithoutExtension = java.nio.file.Paths.get(fileName).getFileName().toString(); int extensionIndex = fileNameWithoutExtension.lastIndexOf('.'); if (extensionIndex > 0) { fileNameWithoutExtension = fileNameWithoutExtension.substring(0, extensionIndex); } // Create the numbered file names. String numberedFileName = fileNameWithoutExtension + i + getFileExtension(fileName); fileNames.add(numberedFileName); s3LockActions.uploadFile(bucketName, numberedFileName, fileName); } } String question = null; System.out.print("Press Enter to continue..."); scanner.nextLine(); System.out.println("Now we can set some object lock policies on individual files:"); for (String bucketName : bucketNames) { for (int i = 0; i < fileNames.size(); i++){ // No modifications to the objects in the first bucket. if (!bucketName.equals(bucketNames.get(0))) { String exampleFileName = fileNames.get(i); switch (i) { case 0 -> { question = "Would you like to add a legal hold to " + exampleFileName + " in " + bucketName + " (y/n)?"; System.out.println(question); String ans = scanner.nextLine().trim(); if (ans.equalsIgnoreCase("y")) { System.out.println("**** You have selected to put a legal hold " + exampleFileName); // Set a legal hold. s3LockActions.modifyObjectLegalHold(bucketName, exampleFileName, true); } } case 1 -> { """ Would you like to add a 1 day Governance retention period to %s in %s (y/n)? Reminder: Only a user with the s3:BypassGovernanceRetention permission will be able to delete this file or its bucket until the retention period has expired. """.formatted(exampleFileName, bucketName); System.out.println(question); String ans2 = scanner.nextLine().trim(); if (ans2.equalsIgnoreCase("y")) { s3LockActions.modifyObjectRetentionPeriod(bucketName, exampleFileName); } } } } } } } // Get file extension. private static String getFileExtension(String fileName) { int dotIndex = fileName.lastIndexOf('.'); if (dotIndex > 0) { return fileName.substring(dotIndex); } return ""; } public static void configurationSetup() { String noLockBucketName = bucketName + "-no-lock"; String lockEnabledBucketName = bucketName + "-lock-enabled"; String retentionAfterCreationBucketName = bucketName + "-retention-after-creation"; bucketNames.add(noLockBucketName); bucketNames.add(lockEnabledBucketName); bucketNames.add(retentionAfterCreationBucketName); } public static int getChoiceResponse(String question, String[] choices) { Scanner scanner = new Scanner(System.in); if (question != null) { System.out.println(question); for (int i = 0; i < choices.length; i++) { System.out.println("\t" + (i + 1) + ". " + choices[i]); } } int choiceNumber = 0; while (choiceNumber < 1 || choiceNumber > choices.length) { String choice = scanner.nextLine(); try { choiceNumber = Integer.parseInt(choice); } catch (NumberFormatException e) { System.out.println("Invalid choice. Please enter a valid number."); } } return choiceNumber - 1; } }
A wrapper class for S3 functions.
import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.BucketVersioningStatus; import software.amazon.awssdk.services.s3.model.ChecksumAlgorithm; import software.amazon.awssdk.services.s3.model.CreateBucketRequest; import software.amazon.awssdk.services.s3.model.DefaultRetention; import software.amazon.awssdk.services.s3.model.DeleteBucketRequest; import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; import software.amazon.awssdk.services.s3.model.GetObjectLegalHoldRequest; import software.amazon.awssdk.services.s3.model.GetObjectLegalHoldResponse; import software.amazon.awssdk.services.s3.model.GetObjectLockConfigurationRequest; import software.amazon.awssdk.services.s3.model.GetObjectLockConfigurationResponse; import software.amazon.awssdk.services.s3.model.GetObjectRetentionRequest; import software.amazon.awssdk.services.s3.model.GetObjectRetentionResponse; import software.amazon.awssdk.services.s3.model.HeadBucketRequest; import software.amazon.awssdk.services.s3.model.ListObjectVersionsRequest; import software.amazon.awssdk.services.s3.model.ListObjectVersionsResponse; import software.amazon.awssdk.services.s3.model.MFADelete; import software.amazon.awssdk.services.s3.model.ObjectLockConfiguration; import software.amazon.awssdk.services.s3.model.ObjectLockEnabled; import software.amazon.awssdk.services.s3.model.ObjectLockLegalHold; import software.amazon.awssdk.services.s3.model.ObjectLockLegalHoldStatus; import software.amazon.awssdk.services.s3.model.ObjectLockRetention; import software.amazon.awssdk.services.s3.model.ObjectLockRetentionMode; import software.amazon.awssdk.services.s3.model.ObjectLockRule; import software.amazon.awssdk.services.s3.model.PutBucketVersioningRequest; import software.amazon.awssdk.services.s3.model.PutObjectLegalHoldRequest; import software.amazon.awssdk.services.s3.model.PutObjectLockConfigurationRequest; import software.amazon.awssdk.services.s3.model.PutObjectRequest; import software.amazon.awssdk.services.s3.model.PutObjectResponse; import software.amazon.awssdk.services.s3.model.PutObjectRetentionRequest; import software.amazon.awssdk.services.s3.model.S3Exception; import software.amazon.awssdk.services.s3.model.VersioningConfiguration; import software.amazon.awssdk.services.s3.waiters.S3Waiter; import java.nio.file.Path; import java.nio.file.Paths; import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; // Contains application logic for the Amazon S3 operations used in this workflow. public class S3LockActions { private static S3Client getClient() { return S3Client.builder() .region(Region.US_EAST_1) .build(); } // Set or modify a retention period on an object in an S3 bucket. public void modifyObjectRetentionPeriod(String bucketName, String objectKey) { // Calculate the instant one day from now. Instant futureInstant = Instant.now().plus(1, ChronoUnit.DAYS); // Convert the Instant to a ZonedDateTime object with a specific time zone. ZonedDateTime zonedDateTime = futureInstant.atZone(ZoneId.systemDefault()); // Define a formatter for human-readable output. DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); // Format the ZonedDateTime object to a human-readable date string. String humanReadableDate = formatter.format(zonedDateTime); // Print the formatted date string. System.out.println("Formatted Date: " + humanReadableDate); ObjectLockRetention retention = ObjectLockRetention.builder() .mode(ObjectLockRetentionMode.GOVERNANCE) .retainUntilDate(futureInstant) .build(); PutObjectRetentionRequest retentionRequest = PutObjectRetentionRequest.builder() .bucket(bucketName) .key(objectKey) .retention(retention) .build(); getClient().putObjectRetention(retentionRequest); System.out.println("Set retention for "+objectKey +" in " +bucketName +" until "+ humanReadableDate +"."); } // Get the legal hold details for an S3 object. public ObjectLockLegalHold getObjectLegalHold(String bucketName, String objectKey) { try { GetObjectLegalHoldRequest legalHoldRequest = GetObjectLegalHoldRequest.builder() .bucket(bucketName) .key(objectKey) .build(); GetObjectLegalHoldResponse response = getClient().getObjectLegalHold(legalHoldRequest); System.out.println("Object legal hold for " + objectKey + " in " + bucketName + ":\n\tStatus: " + response.legalHold().status()); return response.legalHold(); } catch (S3Exception ex) { System.out.println("\tUnable to fetch legal hold: '" + ex.getMessage() + "'"); } return null; } // Create a new Amazon S3 bucket with object lock options. public void createBucketWithLockOptions(boolean enableObjectLock, String bucketName) { S3Waiter s3Waiter = getClient().waiter(); CreateBucketRequest bucketRequest = CreateBucketRequest.builder() .bucket(bucketName) .objectLockEnabledForBucket(enableObjectLock) .build(); getClient().createBucket(bucketRequest); HeadBucketRequest bucketRequestWait = HeadBucketRequest.builder() .bucket(bucketName) .build(); // Wait until the bucket is created and print out the response. s3Waiter.waitUntilBucketExists(bucketRequestWait); System.out.println(bucketName + " is ready"); } public List<S3InfoObject> listBucketsAndObjects(List<String> bucketNames, Boolean interactive) { AtomicInteger counter = new AtomicInteger(0); // Initialize counter. return bucketNames.stream() .flatMap(bucketName -> listBucketObjectsAndVersions(bucketName).versions().stream() .map(version -> { S3InfoObject s3InfoObject = new S3InfoObject(); s3InfoObject.setBucketName(bucketName); s3InfoObject.setVersion(version.versionId()); s3InfoObject.setKeyName(version.key()); return s3InfoObject; })) .peek(s3InfoObject -> { int i = counter.incrementAndGet(); // Increment and get the updated value. if (interactive) { System.out.println(i + ": "+ s3InfoObject.getKeyName()); System.out.printf("%5s Bucket name: %s\n", "", s3InfoObject.getBucketName()); System.out.printf("%5s Version: %s\n", "", s3InfoObject.getVersion()); } }) .collect(Collectors.toList()); } public ListObjectVersionsResponse listBucketObjectsAndVersions(String bucketName) { ListObjectVersionsRequest versionsRequest = ListObjectVersionsRequest.builder() .bucket(bucketName) .build(); return getClient().listObjectVersions(versionsRequest); } // Set or modify a retention period on an S3 bucket. public void modifyBucketDefaultRetention(String bucketName) { VersioningConfiguration versioningConfiguration = VersioningConfiguration.builder() .mfaDelete(MFADelete.DISABLED) .status(BucketVersioningStatus.ENABLED) .build(); PutBucketVersioningRequest versioningRequest = PutBucketVersioningRequest.builder() .bucket(bucketName) .versioningConfiguration(versioningConfiguration) .build(); getClient().putBucketVersioning(versioningRequest); DefaultRetention rention = DefaultRetention.builder() .days(1) .mode(ObjectLockRetentionMode.GOVERNANCE) .build(); ObjectLockRule lockRule = ObjectLockRule.builder() .defaultRetention(rention) .build(); ObjectLockConfiguration objectLockConfiguration = ObjectLockConfiguration.builder() .objectLockEnabled(ObjectLockEnabled.ENABLED) .rule(lockRule) .build(); PutObjectLockConfigurationRequest putObjectLockConfigurationRequest = PutObjectLockConfigurationRequest.builder() .bucket(bucketName) .objectLockConfiguration(objectLockConfiguration) .build(); getClient().putObjectLockConfiguration(putObjectLockConfigurationRequest) ; System.out.println("Added a default retention to bucket "+bucketName +"."); } // Enable object lock on an existing bucket. public void enableObjectLockOnBucket(String bucketName) { try { VersioningConfiguration versioningConfiguration = VersioningConfiguration.builder() .status(BucketVersioningStatus.ENABLED) .build(); PutBucketVersioningRequest putBucketVersioningRequest = PutBucketVersioningRequest.builder() .bucket(bucketName) .versioningConfiguration(versioningConfiguration) .build(); // Enable versioning on the bucket. getClient().putBucketVersioning(putBucketVersioningRequest); PutObjectLockConfigurationRequest request = PutObjectLockConfigurationRequest.builder() .bucket(bucketName) .objectLockConfiguration(ObjectLockConfiguration.builder() .objectLockEnabled(ObjectLockEnabled.ENABLED) .build()) .build(); getClient().putObjectLockConfiguration(request); System.out.println("Successfully enabled object lock on "+bucketName); } catch (S3Exception ex) { System.out.println("Error modifying object lock: '" + ex.getMessage() + "'"); } } public void uploadFile(String bucketName, String objectName, String filePath) { Path file = Paths.get(filePath); PutObjectRequest request = PutObjectRequest.builder() .bucket(bucketName) .key(objectName) .checksumAlgorithm(ChecksumAlgorithm.SHA256) .build(); PutObjectResponse response = getClient().putObject(request, file); if (response != null) { System.out.println("\tSuccessfully uploaded " + objectName + " to " + bucketName + "."); } else { System.out.println("\tCould not upload " + objectName + " to " + bucketName + "."); } } // Set or modify a legal hold on an object in an S3 bucket. public void modifyObjectLegalHold(String bucketName, String objectKey, boolean legalHoldOn) { ObjectLockLegalHold legalHold ; if (legalHoldOn) { legalHold = ObjectLockLegalHold.builder() .status(ObjectLockLegalHoldStatus.ON) .build(); } else { legalHold = ObjectLockLegalHold.builder() .status(ObjectLockLegalHoldStatus.OFF) .build(); } PutObjectLegalHoldRequest legalHoldRequest = PutObjectLegalHoldRequest.builder() .bucket(bucketName) .key(objectKey) .legalHold(legalHold) .build(); getClient().putObjectLegalHold(legalHoldRequest) ; System.out.println("Modified legal hold for "+ objectKey +" in "+bucketName +"."); } // Delete an object from a specific bucket. public void deleteObjectFromBucket(String bucketName, String objectKey, boolean hasRetention, String versionId) { try { DeleteObjectRequest objectRequest; if (hasRetention) { objectRequest = DeleteObjectRequest.builder() .bucket(bucketName) .key(objectKey) .versionId(versionId) .bypassGovernanceRetention(true) .build(); } else { objectRequest = DeleteObjectRequest.builder() .bucket(bucketName) .key(objectKey) .versionId(versionId) .build(); } getClient().deleteObject(objectRequest) ; System.out.println("The object was successfully deleted"); } catch (S3Exception e) { System.err.println(e.awsErrorDetails().errorMessage()); } } // Get the retention period for an S3 object. public ObjectLockRetention getObjectRetention(String bucketName, String key){ try { GetObjectRetentionRequest retentionRequest = GetObjectRetentionRequest.builder() .bucket(bucketName) .key(key) .build(); GetObjectRetentionResponse response = getClient().getObjectRetention(retentionRequest); System.out.println("tObject retention for "+key +" in "+ bucketName +": " + response.retention().mode() +" until "+ response.retention().retainUntilDate() +"."); return response.retention(); } catch (S3Exception e) { System.err.println(e.awsErrorDetails().errorMessage()); return null; } } public void deleteBucketByName(String bucketName) { try { DeleteBucketRequest request = DeleteBucketRequest.builder() .bucket(bucketName) .build(); getClient().deleteBucket(request); System.out.println(bucketName +" was deleted."); } catch (S3Exception e) { System.err.println(e.awsErrorDetails().errorMessage()); } } // Get the object lock configuration details for an S3 bucket. public void getBucketObjectLockConfiguration(String bucketName) { GetObjectLockConfigurationRequest objectLockConfigurationRequest = GetObjectLockConfigurationRequest.builder() .bucket(bucketName) .build(); GetObjectLockConfigurationResponse response = getClient().getObjectLockConfiguration(objectLockConfigurationRequest); System.out.println("Bucket object lock config for "+bucketName +": "); System.out.println("\tEnabled: "+response.objectLockConfiguration().objectLockEnabled()); System.out.println("\tRule: "+ response.objectLockConfiguration().rule().defaultRetention()); } }
-
For API details, see the following topics in AWS SDK for Java 2.x API Reference.
-
- JavaScript
-
- SDK for JavaScript (v3)
-
Note
There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository
. index.js - Entrypoint for the workflow. This orchestrates all of the steps. Visit GitHub to see the implementation details for Scenario, ScenarioInput, ScenarioOutput, and ScenarioAction.
import * as Scenarios from "@aws-doc-sdk-examples/lib/scenario/index.js"; import { exitOnFalse, loadState, saveState, } from "@aws-doc-sdk-examples/lib/scenario/steps-common.js"; import { welcome, welcomeContinue } from "./welcome.steps.js"; import { confirmCreateBuckets, confirmPopulateBuckets, confirmSetLegalHoldFileEnabled, confirmSetLegalHoldFileRetention, confirmSetRetentionPeriodFileEnabled, confirmSetRetentionPeriodFileRetention, confirmUpdateLockPolicy, confirmUpdateRetention, createBuckets, createBucketsAction, getBucketPrefix, populateBuckets, populateBucketsAction, setLegalHoldFileEnabledAction, setLegalHoldFileRetentionAction, setRetentionPeriodFileEnabledAction, setRetentionPeriodFileRetentionAction, updateLockPolicy, updateLockPolicyAction, updateRetention, updateRetentionAction, } from "./setup.steps.js"; /** * @param {Scenarios} scenarios * @param {Record<string, any>} initialState */ export const getWorkflowStages = (scenarios, initialState = {}) => { const client = new S3Client({}); return { deploy: new scenarios.Scenario( "S3 Object Locking - Deploy", [ welcome(scenarios), welcomeContinue(scenarios), exitOnFalse(scenarios, "welcomeContinue"), getBucketPrefix(scenarios), createBuckets(scenarios), confirmCreateBuckets(scenarios), exitOnFalse(scenarios, "confirmCreateBuckets"), createBucketsAction(scenarios, client), updateRetention(scenarios), confirmUpdateRetention(scenarios), exitOnFalse(scenarios, "confirmUpdateRetention"), updateRetentionAction(scenarios, client), populateBuckets(scenarios), confirmPopulateBuckets(scenarios), exitOnFalse(scenarios, "confirmPopulateBuckets"), populateBucketsAction(scenarios, client), updateLockPolicy(scenarios), confirmUpdateLockPolicy(scenarios), exitOnFalse(scenarios, "confirmUpdateLockPolicy"), updateLockPolicyAction(scenarios, client), confirmSetLegalHoldFileEnabled(scenarios), setLegalHoldFileEnabledAction(scenarios, client), confirmSetRetentionPeriodFileEnabled(scenarios), setRetentionPeriodFileEnabledAction(scenarios, client), confirmSetLegalHoldFileRetention(scenarios), setLegalHoldFileRetentionAction(scenarios, client), confirmSetRetentionPeriodFileRetention(scenarios), setRetentionPeriodFileRetentionAction(scenarios, client), saveState, ], initialState, ), demo: new scenarios.Scenario( "S3 Object Locking - Demo", [loadState, replAction(scenarios, client)], initialState, ), clean: new scenarios.Scenario( "S3 Object Locking - Destroy", [ loadState, confirmCleanup(scenarios), exitOnFalse(scenarios, "confirmCleanup"), cleanupAction(scenarios, client), ], initialState, ), }; }; // Call function if run directly import { fileURLToPath } from "node:url"; import { S3Client } from "@aws-sdk/client-s3"; import { cleanupAction, confirmCleanup } from "./clean.steps.js"; import { replAction } from "./repl.steps.js"; if (process.argv[1] === fileURLToPath(import.meta.url)) { const objectLockingScenarios = getWorkflowStages(Scenarios); Scenarios.parseScenarioArgs(objectLockingScenarios, { name: "Amazon S3 object locking workflow", description: "Work with Amazon Simple Storage Service (Amazon S3) object locking features.", synopsis: "node index.js --scenario <deploy | demo | clean> [-h|--help] [-y|--yes] [-v|--verbose]", }); }
welcome.steps.js - Output welcome messages to the console.
/** * @typedef {import("@aws-doc-sdk-examples/lib/scenario/index.js")} Scenarios */ /** * @param {Scenarios} scenarios */ const welcome = (scenarios) => new scenarios.ScenarioOutput( "welcome", "Welcome to the Amazon Simple Storage Service (S3) Object Locking Workflow Scenario. For this workflow, we will use the AWS SDK for JavaScript to create several S3 buckets and files to demonstrate working with S3 locking features.", { header: true }, ); /** * @param {Scenarios} scenarios */ const welcomeContinue = (scenarios) => new scenarios.ScenarioInput( "welcomeContinue", "Press Enter when you are ready to start.", { type: "confirm" }, ); export { welcome, welcomeContinue };
setup.steps.js - Deploy buckets, objects, and file settings.
import { BucketVersioningStatus, ChecksumAlgorithm, CreateBucketCommand, MFADeleteStatus, PutBucketVersioningCommand, PutObjectCommand, PutObjectLockConfigurationCommand, PutObjectLegalHoldCommand, PutObjectRetentionCommand, ObjectLockLegalHoldStatus, ObjectLockRetentionMode, GetBucketVersioningCommand, BucketAlreadyExists, BucketAlreadyOwnedByYou, S3ServiceException, waitUntilBucketExists, } from "@aws-sdk/client-s3"; import { retry } from "@aws-doc-sdk-examples/lib/utils/util-timers.js"; /** * @typedef {import("@aws-doc-sdk-examples/lib/scenario/index.js")} Scenarios */ /** * @typedef {import("@aws-sdk/client-s3").S3Client} S3Client */ /** * @param {Scenarios} scenarios */ const getBucketPrefix = (scenarios) => new scenarios.ScenarioInput( "bucketPrefix", "Provide a prefix that will be used for bucket creation.", { type: "input", default: "amzn-s3-demo-bucket" }, ); /** * @param {Scenarios} scenarios */ const createBuckets = (scenarios) => new scenarios.ScenarioOutput( "createBuckets", (state) => `The following buckets will be created: ${state.bucketPrefix}-no-lock with object lock False. ${state.bucketPrefix}-lock-enabled with object lock True. ${state.bucketPrefix}-retention-after-creation with object lock False.`, { preformatted: true }, ); /** * @param {Scenarios} scenarios */ const confirmCreateBuckets = (scenarios) => new scenarios.ScenarioInput("confirmCreateBuckets", "Create the buckets?", { type: "confirm", }); /** * @param {Scenarios} scenarios * @param {S3Client} client */ const createBucketsAction = (scenarios, client) => new scenarios.ScenarioAction("createBucketsAction", async (state) => { const noLockBucketName = `${state.bucketPrefix}-no-lock`; const lockEnabledBucketName = `${state.bucketPrefix}-lock-enabled`; const retentionBucketName = `${state.bucketPrefix}-retention-after-creation`; try { await client.send(new CreateBucketCommand({ Bucket: noLockBucketName })); await waitUntilBucketExists({ client }, { Bucket: noLockBucketName }); await client.send( new CreateBucketCommand({ Bucket: lockEnabledBucketName, ObjectLockEnabledForBucket: true, }), ); await waitUntilBucketExists( { client }, { Bucket: lockEnabledBucketName }, ); await client.send( new CreateBucketCommand({ Bucket: retentionBucketName }), ); await waitUntilBucketExists({ client }, { Bucket: retentionBucketName }); state.noLockBucketName = noLockBucketName; state.lockEnabledBucketName = lockEnabledBucketName; state.retentionBucketName = retentionBucketName; } catch (caught) { if ( caught instanceof BucketAlreadyExists || caught instanceof BucketAlreadyOwnedByYou ) { console.error(`${caught.name}: ${caught.message}`); state.earlyExit = true; } else { throw caught; } } }); /** * @param {Scenarios} scenarios */ const populateBuckets = (scenarios) => new scenarios.ScenarioOutput( "populateBuckets", (state) => `The following test files will be created: file0.txt in ${state.bucketPrefix}-no-lock. file1.txt in ${state.bucketPrefix}-no-lock. file0.txt in ${state.bucketPrefix}-lock-enabled. file1.txt in ${state.bucketPrefix}-lock-enabled. file0.txt in ${state.bucketPrefix}-retention-after-creation. file1.txt in ${state.bucketPrefix}-retention-after-creation.`, { preformatted: true }, ); /** * @param {Scenarios} scenarios */ const confirmPopulateBuckets = (scenarios) => new scenarios.ScenarioInput( "confirmPopulateBuckets", "Populate the buckets?", { type: "confirm" }, ); /** * @param {Scenarios} scenarios * @param {S3Client} client */ const populateBucketsAction = (scenarios, client) => new scenarios.ScenarioAction("populateBucketsAction", async (state) => { try { await client.send( new PutObjectCommand({ Bucket: state.noLockBucketName, Key: "file0.txt", Body: "Content", ChecksumAlgorithm: ChecksumAlgorithm.SHA256, }), ); await client.send( new PutObjectCommand({ Bucket: state.noLockBucketName, Key: "file1.txt", Body: "Content", ChecksumAlgorithm: ChecksumAlgorithm.SHA256, }), ); await client.send( new PutObjectCommand({ Bucket: state.lockEnabledBucketName, Key: "file0.txt", Body: "Content", ChecksumAlgorithm: ChecksumAlgorithm.SHA256, }), ); await client.send( new PutObjectCommand({ Bucket: state.lockEnabledBucketName, Key: "file1.txt", Body: "Content", ChecksumAlgorithm: ChecksumAlgorithm.SHA256, }), ); await client.send( new PutObjectCommand({ Bucket: state.retentionBucketName, Key: "file0.txt", Body: "Content", ChecksumAlgorithm: ChecksumAlgorithm.SHA256, }), ); await client.send( new PutObjectCommand({ Bucket: state.retentionBucketName, Key: "file1.txt", Body: "Content", ChecksumAlgorithm: ChecksumAlgorithm.SHA256, }), ); } catch (caught) { if (caught instanceof S3ServiceException) { console.error( `Error from S3 while uploading object. ${caught.name}: ${caught.message}`, ); } else { throw caught; } } }); /** * @param {Scenarios} scenarios */ const updateRetention = (scenarios) => new scenarios.ScenarioOutput( "updateRetention", (state) => `A bucket can be configured to use object locking with a default retention period. A default retention period will be configured for ${state.bucketPrefix}-retention-after-creation.`, { preformatted: true }, ); /** * @param {Scenarios} scenarios */ const confirmUpdateRetention = (scenarios) => new scenarios.ScenarioInput( "confirmUpdateRetention", "Configure default retention period?", { type: "confirm" }, ); /** * @param {Scenarios} scenarios * @param {S3Client} client */ const updateRetentionAction = (scenarios, client) => new scenarios.ScenarioAction("updateRetentionAction", async (state) => { await client.send( new PutBucketVersioningCommand({ Bucket: state.retentionBucketName, VersioningConfiguration: { MFADelete: MFADeleteStatus.Disabled, Status: BucketVersioningStatus.Enabled, }, }), ); const getBucketVersioning = new GetBucketVersioningCommand({ Bucket: state.retentionBucketName, }); await retry({ intervalInMs: 500, maxRetries: 10 }, async () => { const { Status } = await client.send(getBucketVersioning); if (Status !== "Enabled") { throw new Error("Bucket versioning is not enabled."); } }); await client.send( new PutObjectLockConfigurationCommand({ Bucket: state.retentionBucketName, ObjectLockConfiguration: { ObjectLockEnabled: "Enabled", Rule: { DefaultRetention: { Mode: "GOVERNANCE", Years: 1, }, }, }, }), ); }); /** * @param {Scenarios} scenarios */ const updateLockPolicy = (scenarios) => new scenarios.ScenarioOutput( "updateLockPolicy", (state) => `Object lock policies can also be added to existing buckets. An object lock policy will be added to ${state.bucketPrefix}-lock-enabled.`, { preformatted: true }, ); /** * @param {Scenarios} scenarios */ const confirmUpdateLockPolicy = (scenarios) => new scenarios.ScenarioInput( "confirmUpdateLockPolicy", "Add object lock policy?", { type: "confirm" }, ); /** * @param {Scenarios} scenarios * @param {S3Client} client */ const updateLockPolicyAction = (scenarios, client) => new scenarios.ScenarioAction("updateLockPolicyAction", async (state) => { await client.send( new PutObjectLockConfigurationCommand({ Bucket: state.lockEnabledBucketName, ObjectLockConfiguration: { ObjectLockEnabled: "Enabled", }, }), ); }); /** * @param {Scenarios} scenarios * @param {S3Client} client */ const confirmSetLegalHoldFileEnabled = (scenarios) => new scenarios.ScenarioInput( "confirmSetLegalHoldFileEnabled", (state) => `Would you like to add a legal hold to file0.txt in ${state.lockEnabledBucketName}?`, { type: "confirm", }, ); /** * @param {Scenarios} scenarios * @param {S3Client} client */ const setLegalHoldFileEnabledAction = (scenarios, client) => new scenarios.ScenarioAction( "setLegalHoldFileEnabledAction", async (state) => { await client.send( new PutObjectLegalHoldCommand({ Bucket: state.lockEnabledBucketName, Key: "file0.txt", LegalHold: { Status: ObjectLockLegalHoldStatus.ON, }, }), ); console.log( `Modified legal hold for file0.txt in ${state.lockEnabledBucketName}.`, ); }, { skipWhen: (state) => !state.confirmSetLegalHoldFileEnabled }, ); /** * @param {Scenarios} scenarios * @param {S3Client} client */ const confirmSetRetentionPeriodFileEnabled = (scenarios) => new scenarios.ScenarioInput( "confirmSetRetentionPeriodFileEnabled", (state) => `Would you like to add a 1 day Governance retention period to file1.txt in ${state.lockEnabledBucketName}? Reminder: Only a user with the s3:BypassGovernanceRetention permission will be able to delete this file or its bucket until the retention period has expired.`, { type: "confirm", }, ); /** * @param {Scenarios} scenarios * @param {S3Client} client */ const setRetentionPeriodFileEnabledAction = (scenarios, client) => new scenarios.ScenarioAction( "setRetentionPeriodFileEnabledAction", async (state) => { const retentionDate = new Date(); retentionDate.setDate(retentionDate.getDate() + 1); await client.send( new PutObjectRetentionCommand({ Bucket: state.lockEnabledBucketName, Key: "file1.txt", Retention: { Mode: ObjectLockRetentionMode.GOVERNANCE, RetainUntilDate: retentionDate, }, }), ); console.log( `Set retention for file1.txt in ${state.lockEnabledBucketName} until ${retentionDate.toISOString().split("T")[0]}.`, ); }, { skipWhen: (state) => !state.confirmSetRetentionPeriodFileEnabled }, ); /** * @param {Scenarios} scenarios * @param {S3Client} client */ const confirmSetLegalHoldFileRetention = (scenarios) => new scenarios.ScenarioInput( "confirmSetLegalHoldFileRetention", (state) => `Would you like to add a legal hold to file0.txt in ${state.retentionBucketName}?`, { type: "confirm", }, ); /** * @param {Scenarios} scenarios * @param {S3Client} client */ const setLegalHoldFileRetentionAction = (scenarios, client) => new scenarios.ScenarioAction( "setLegalHoldFileRetentionAction", async (state) => { await client.send( new PutObjectLegalHoldCommand({ Bucket: state.retentionBucketName, Key: "file0.txt", LegalHold: { Status: ObjectLockLegalHoldStatus.ON, }, }), ); console.log( `Modified legal hold for file0.txt in ${state.retentionBucketName}.`, ); }, { skipWhen: (state) => !state.confirmSetLegalHoldFileRetention }, ); /** * @param {Scenarios} scenarios */ const confirmSetRetentionPeriodFileRetention = (scenarios) => new scenarios.ScenarioInput( "confirmSetRetentionPeriodFileRetention", (state) => `Would you like to add a 1 day Governance retention period to file1.txt in ${state.retentionBucketName}? Reminder: Only a user with the s3:BypassGovernanceRetention permission will be able to delete this file or its bucket until the retention period has expired.`, { type: "confirm", }, ); /** * @param {Scenarios} scenarios * @param {S3Client} client */ const setRetentionPeriodFileRetentionAction = (scenarios, client) => new scenarios.ScenarioAction( "setRetentionPeriodFileRetentionAction", async (state) => { const retentionDate = new Date(); retentionDate.setDate(retentionDate.getDate() + 1); await client.send( new PutObjectRetentionCommand({ Bucket: state.retentionBucketName, Key: "file1.txt", Retention: { Mode: ObjectLockRetentionMode.GOVERNANCE, RetainUntilDate: retentionDate, }, BypassGovernanceRetention: true, }), ); console.log( `Set retention for file1.txt in ${state.retentionBucketName} until ${retentionDate.toISOString().split("T")[0]}.`, ); }, { skipWhen: (state) => !state.confirmSetRetentionPeriodFileRetention }, ); export { getBucketPrefix, createBuckets, confirmCreateBuckets, createBucketsAction, populateBuckets, confirmPopulateBuckets, populateBucketsAction, updateRetention, confirmUpdateRetention, updateRetentionAction, updateLockPolicy, confirmUpdateLockPolicy, updateLockPolicyAction, confirmSetLegalHoldFileEnabled, setLegalHoldFileEnabledAction, confirmSetRetentionPeriodFileEnabled, setRetentionPeriodFileEnabledAction, confirmSetLegalHoldFileRetention, setLegalHoldFileRetentionAction, confirmSetRetentionPeriodFileRetention, setRetentionPeriodFileRetentionAction, };
repl.steps.js - View and delete files in the buckets.
import { ChecksumAlgorithm, DeleteObjectCommand, GetObjectLegalHoldCommand, GetObjectLockConfigurationCommand, GetObjectRetentionCommand, ListObjectVersionsCommand, PutObjectCommand, } from "@aws-sdk/client-s3"; /** * @typedef {import("@aws-doc-sdk-examples/lib/scenario/index.js")} Scenarios */ /** * @typedef {import("@aws-sdk/client-s3").S3Client} S3Client */ const choices = { EXIT: 0, LIST_ALL_FILES: 1, DELETE_FILE: 2, DELETE_FILE_WITH_RETENTION: 3, OVERWRITE_FILE: 4, VIEW_RETENTION_SETTINGS: 5, VIEW_LEGAL_HOLD_SETTINGS: 6, }; /** * @param {Scenarios} scenarios */ const replInput = (scenarios) => new scenarios.ScenarioInput( "replChoice", "Explore the S3 locking features by selecting one of the following choices", { type: "select", choices: [ { name: "List all files in buckets", value: choices.LIST_ALL_FILES }, { name: "Attempt to delete a file.", value: choices.DELETE_FILE }, { name: "Attempt to delete a file with retention period bypass.", value: choices.DELETE_FILE_WITH_RETENTION, }, { name: "Attempt to overwrite a file.", value: choices.OVERWRITE_FILE }, { name: "View the object and bucket retention settings for a file.", value: choices.VIEW_RETENTION_SETTINGS, }, { name: "View the legal hold settings for a file.", value: choices.VIEW_LEGAL_HOLD_SETTINGS, }, { name: "Finish the workflow.", value: choices.EXIT }, ], }, ); /** * @param {S3Client} client * @param {string[]} buckets */ const getAllFiles = async (client, buckets) => { /** @type {{bucket: string, key: string, version: string}[]} */ const files = []; for (const bucket of buckets) { const objectsResponse = await client.send( new ListObjectVersionsCommand({ Bucket: bucket }), ); for (const version of objectsResponse.Versions || []) { const { Key, VersionId } = version; files.push({ bucket, key: Key, version: VersionId }); } } return files; }; /** * @param {Scenarios} scenarios * @param {S3Client} client */ const replAction = (scenarios, client) => new scenarios.ScenarioAction( "replAction", async (state) => { const files = await getAllFiles(client, [ state.noLockBucketName, state.lockEnabledBucketName, state.retentionBucketName, ]); const fileInput = new scenarios.ScenarioInput( "selectedFile", "Select a file:", { type: "select", choices: files.map((file, index) => ({ name: `${index + 1}: ${file.bucket}: ${file.key} (version: ${ file.version })`, value: index, })), }, ); const { replChoice } = state; switch (replChoice) { case choices.LIST_ALL_FILES: { const files = await getAllFiles(client, [ state.noLockBucketName, state.lockEnabledBucketName, state.retentionBucketName, ]); state.replOutput = files .map( (file) => `${file.bucket}: ${file.key} (version: ${file.version})`, ) .join("\n"); break; } case choices.DELETE_FILE: { /** @type {number} */ const fileToDelete = await fileInput.handle(state); const selectedFile = files[fileToDelete]; try { await client.send( new DeleteObjectCommand({ Bucket: selectedFile.bucket, Key: selectedFile.key, VersionId: selectedFile.version, }), ); state.replOutput = `Deleted ${selectedFile.key} in ${selectedFile.bucket}.`; } catch (err) { state.replOutput = `Unable to delete object ${selectedFile.key} in bucket ${selectedFile.bucket}: ${err.message}`; } break; } case choices.DELETE_FILE_WITH_RETENTION: { /** @type {number} */ const fileToDelete = await fileInput.handle(state); const selectedFile = files[fileToDelete]; try { await client.send( new DeleteObjectCommand({ Bucket: selectedFile.bucket, Key: selectedFile.key, VersionId: selectedFile.version, BypassGovernanceRetention: true, }), ); state.replOutput = `Deleted ${selectedFile.key} in ${selectedFile.bucket}.`; } catch (err) { state.replOutput = `Unable to delete object ${selectedFile.key} in bucket ${selectedFile.bucket}: ${err.message}`; } break; } case choices.OVERWRITE_FILE: { /** @type {number} */ const fileToOverwrite = await fileInput.handle(state); const selectedFile = files[fileToOverwrite]; try { await client.send( new PutObjectCommand({ Bucket: selectedFile.bucket, Key: selectedFile.key, Body: "New content", ChecksumAlgorithm: ChecksumAlgorithm.SHA256, }), ); state.replOutput = `Overwrote ${selectedFile.key} in ${selectedFile.bucket}.`; } catch (err) { state.replOutput = `Unable to overwrite object ${selectedFile.key} in bucket ${selectedFile.bucket}: ${err.message}`; } break; } case choices.VIEW_RETENTION_SETTINGS: { /** @type {number} */ const fileToView = await fileInput.handle(state); const selectedFile = files[fileToView]; try { const retention = await client.send( new GetObjectRetentionCommand({ Bucket: selectedFile.bucket, Key: selectedFile.key, VersionId: selectedFile.version, }), ); const bucketConfig = await client.send( new GetObjectLockConfigurationCommand({ Bucket: selectedFile.bucket, }), ); state.replOutput = `Object retention for ${selectedFile.key} in ${selectedFile.bucket}: ${retention.Retention?.Mode} until ${retention.Retention?.RetainUntilDate?.toISOString()}. Bucket object lock config for ${selectedFile.bucket} in ${selectedFile.bucket}: Enabled: ${bucketConfig.ObjectLockConfiguration?.ObjectLockEnabled} Rule: ${JSON.stringify(bucketConfig.ObjectLockConfiguration?.Rule?.DefaultRetention)}`; } catch (err) { state.replOutput = `Unable to fetch object lock retention: '${err.message}'`; } break; } case choices.VIEW_LEGAL_HOLD_SETTINGS: { /** @type {number} */ const fileToView = await fileInput.handle(state); const selectedFile = files[fileToView]; try { const legalHold = await client.send( new GetObjectLegalHoldCommand({ Bucket: selectedFile.bucket, Key: selectedFile.key, VersionId: selectedFile.version, }), ); state.replOutput = `Object legal hold for ${selectedFile.key} in ${selectedFile.bucket}: Status: ${legalHold.LegalHold?.Status}`; } catch (err) { state.replOutput = `Unable to fetch legal hold: '${err.message}'`; } break; } default: throw new Error(`Invalid replChoice: ${replChoice}`); } }, { whileConfig: { whileFn: ({ replChoice }) => replChoice !== choices.EXIT, input: replInput(scenarios), output: new scenarios.ScenarioOutput( "REPL output", (state) => state.replOutput, { preformatted: true }, ), }, }, ); export { replInput, replAction, choices };
clean.steps.js - Destroy all created resources.
import { DeleteObjectCommand, DeleteBucketCommand, ListObjectVersionsCommand, GetObjectLegalHoldCommand, GetObjectRetentionCommand, PutObjectLegalHoldCommand, } from "@aws-sdk/client-s3"; /** * @typedef {import("@aws-doc-sdk-examples/lib/scenario/index.js")} Scenarios */ /** * @typedef {import("@aws-sdk/client-s3").S3Client} S3Client */ /** * @param {Scenarios} scenarios */ const confirmCleanup = (scenarios) => new scenarios.ScenarioInput("confirmCleanup", "Clean up resources?", { type: "confirm", }); /** * @param {Scenarios} scenarios * @param {S3Client} client */ const cleanupAction = (scenarios, client) => new scenarios.ScenarioAction("cleanupAction", async (state) => { const { noLockBucketName, lockEnabledBucketName, retentionBucketName } = state; const buckets = [ noLockBucketName, lockEnabledBucketName, retentionBucketName, ]; for (const bucket of buckets) { /** @type {import("@aws-sdk/client-s3").ListObjectVersionsCommandOutput} */ let objectsResponse; try { objectsResponse = await client.send( new ListObjectVersionsCommand({ Bucket: bucket, }), ); } catch (e) { if (e instanceof Error && e.name === "NoSuchBucket") { console.log("Object's bucket has already been deleted."); continue; } throw e; } for (const version of objectsResponse.Versions || []) { const { Key, VersionId } = version; try { const legalHold = await client.send( new GetObjectLegalHoldCommand({ Bucket: bucket, Key, VersionId, }), ); if (legalHold.LegalHold?.Status === "ON") { await client.send( new PutObjectLegalHoldCommand({ Bucket: bucket, Key, VersionId, LegalHold: { Status: "OFF", }, }), ); } } catch (err) { console.log( `Unable to fetch legal hold for ${Key} in ${bucket}: '${err.message}'`, ); } try { const retention = await client.send( new GetObjectRetentionCommand({ Bucket: bucket, Key, VersionId, }), ); if (retention.Retention?.Mode === "GOVERNANCE") { await client.send( new DeleteObjectCommand({ Bucket: bucket, Key, VersionId, BypassGovernanceRetention: true, }), ); } } catch (err) { console.log( `Unable to fetch object lock retention for ${Key} in ${bucket}: '${err.message}'`, ); } await client.send( new DeleteObjectCommand({ Bucket: bucket, Key, VersionId, }), ); } await client.send(new DeleteBucketCommand({ Bucket: bucket })); console.log(`Delete for ${bucket} complete.`); } }); export { confirmCleanup, cleanupAction };
-
For API details, see the following topics in AWS SDK for JavaScript API Reference.
-
For a complete list of AWS SDK developer guides and code examples, see Developing with Amazon S3 using the AWS SDKs. This topic also includes information about getting started and details about previous SDK versions.