Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Frends.AmazonS3.CreateBucket/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
# Changelog

## [2.0.0] - 2025-06-23
### Changed
- Made Task Harmonization changes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of this changes like moved, created, should be marked as [Breaking] - see Task Development Guidelines. There should be also some describe how to upgrade to new version of task.

- Moved Connection.BucketName to Input
- Renamed ACL to Acl inside Connection
- Moved Connection.ObjectLockEnabledForBucket to Options and renamed to ObjectLockEnabled
- Refactored CreateBucket.cs to function with Harmonization changes/additions
- Made change to target .NET 8 in main and unit test projects instead of .NET 6
### Added
- Made Task Harmonization additions
- Created Input definition file
- Created Options definition file
- Added ThrowErrorOnFailure to Options
- Added ErrorMessageOnFailure to Options
- Added BucketName to Result
- Added Error to Result
- Added migrations file

## [1.0.0] - 2023-07-28
### Added
- Initial implementation
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public class AWSCredsUnitTests
private readonly string? _accessKey = Environment.GetEnvironmentVariable("HiQ_AWSS3Test_AccessKey");
private readonly string? _secretAccessKey = Environment.GetEnvironmentVariable("HiQ_AWSS3Test_SecretAccessKey");
private Connection _connection = new();
private Options _options = new();
private Input _input = new();
private string? _bucketName;

[TestInitialize]
Expand All @@ -26,7 +28,11 @@ public void Init()
AwsAccessKeyId = _accessKey,
AwsSecretAccessKey = _secretAccessKey,
Region = Region.EuCentral1,
ObjectLockEnabledForBucket = false
};

_options = new Options
{
ObjectLockEnabled = false
};
}

Expand Down Expand Up @@ -59,42 +65,42 @@ public async Task CleanUp()
[TestMethod]
public async Task CreateBucket_SuccessTest()
{
var acl = ACLs.Private;
var acl = Acls.Private;
_bucketName = $"ritteambuckettest{acl.ToString().ToLower()}";
_connection.BucketName = _bucketName;
_connection.ACL = acl;
_input.BucketName = _bucketName;
_connection.Acl = acl;

var result = await AmazonS3.CreateBucket(_connection, default);
var result = await AmazonS3.CreateBucket(_input, _connection, _options, default);
Assert.IsTrue(result.Success);
Assert.AreEqual("eu-central-1", result.BucketLocation);
}

[TestMethod]
public async Task CreateBucket_BucketAlreadyExistsTest()
{
var acl = ACLs.Private;
var acl = Acls.Private;
_bucketName = $"ritteambuckettest{acl.ToString().ToLower()}";
_connection.BucketName = _bucketName;
_connection.ACL = acl;
_input.BucketName = _bucketName;
_connection.Acl = acl;

var result = await AmazonS3.CreateBucket(_connection, default);
var result = await AmazonS3.CreateBucket(_input, _connection, _options, default);
Assert.IsTrue(result.Success);
Assert.AreEqual("eu-central-1", result.BucketLocation);

var result2 = await AmazonS3.CreateBucket(_connection, default);
var result2 = await AmazonS3.CreateBucket(_input, _connection, _options, default);
Assert.IsTrue(result2.Success);
Assert.AreEqual("Bucket already exists.", result2.BucketLocation);
}

[TestMethod]
public async Task CreateBucket_ExceptionHandlingTest()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test failed, please check it

{
var acl = ACLs.PublicRead;
var acl = Acls.PublicRead;
_bucketName = $"ritteambuckettest{acl.ToString().ToLower()}";
_connection.BucketName = _bucketName;
_connection.ACL = acl;
_input.BucketName = _bucketName;
_connection.Acl = acl;

var ex = await Assert.ThrowsExceptionAsync<AmazonS3Exception>(() => AmazonS3.CreateBucket(_connection, default));
var ex = await Assert.ThrowsExceptionAsync<AmazonS3Exception>(() => AmazonS3.CreateBucket(_input, _connection, _options, default));
Assert.IsNotNull(ex.InnerException);
Assert.AreEqual("Access Denied", ex.InnerException.Message);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Target framework should be .NET 6.0 according to coding guidelines.

The coding guidelines specify that .csproj files should target .NET 6, but this test project targets .NET 8.0. Please update to maintain consistency with the guidelines.

-    <TargetFramework>net8.0</TargetFramework>
+    <TargetFramework>net6.0</TargetFramework>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
🤖 Prompt for AI Agents
In
Frends.AmazonS3.CreateBucket/Frends.AmazonS3.CreateBucket.Test/Frends.AmazonS3.CreateBucket.Tests.csproj
at line 4, the TargetFramework is set to net8.0, but coding guidelines require
targeting .NET 6.0. Change the TargetFramework value from net8.0 to net6.0 to
comply with the guidelines.

<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,49 @@ namespace Frends.AmazonS3.CreateBucket;
/// </summary>
public class AmazonS3
{
/// <summary>
/// Error handling
/// </summary>
private static class ErrorHandler
{
internal static Result Handle(Exception ex, bool throwError, string customMessage)
{
var error = new Error
{
Message = $"{customMessage} {ex.Message}",
AdditionalInfo = ex
};

if (throwError)
throw new Exception(error.Message, ex);

return new Result(false, null, null, error);
}
}

/// <summary>
/// Create AWS S3 Bucket.
/// [Documentation](https://tasks.frends.com/tasks/frends-tasks/Frends.AmazonS3.CreateBucket)
/// </summary>
/// <param name="input">Input parameters.</param>
/// <param name="connection">Connection parameters</param>
/// <param name="options">Options regarding object lock and error handling.</param>
/// <param name="cancellationToken">Token generated by Frends to stop this Task.</param>
/// <returns>Object { bool success, string BucketLocation } </returns>
public static async Task<Result> CreateBucket([PropertyTab] Connection connection, CancellationToken cancellationToken)
/// <returns>Object { bool success, string BucketLocation, string BucketName, Error Error { string Message, Exception AdditionalInfo } } </returns>
public static async Task<Result> CreateBucket([PropertyTab] Input input, [PropertyTab] Connection connection, [PropertyTab] Options options, CancellationToken cancellationToken)
Comment on lines +45 to +50
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance XML documentation for comprehensive coverage.

The XML documentation needs to include <frendsdocs> and <example> tags as required by the coding guidelines for public methods in Frends tasks.

    /// <summary>
    /// Create AWS S3 Bucket.
    /// [Documentation](https://tasks.frends.com/tasks/frends-tasks/Frends.AmazonS3.CreateBucket)
    /// </summary>
+   /// <frendsdocs>
+   /// Creates an Amazon S3 bucket with specified configuration options.
+   /// </frendsdocs>
    /// <param name="input">Input parameters.</param>
    /// <param name="connection">Connection parameters</param>
    /// <param name="options">Options regarding object lock and error handling.</param>
    /// <param name="cancellationToken">Token generated by Frends to stop this Task.</param>
    /// <returns>Object { bool success, string BucketLocation, string BucketName, Error Error { string Message, Exception AdditionalInfo } } </returns>
+   /// <example>
+   /// var input = new Input { BucketName = "my-bucket" };
+   /// var connection = new Connection { AwsAccessKeyId = "key", AwsSecretAccessKey = "secret", Region = Region.EuCentral1 };
+   /// var options = new Options { ObjectLockEnabled = false };
+   /// var result = await AmazonS3.CreateBucket(input, connection, options, CancellationToken.None);
+   /// </example>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// <param name="input">Input parameters.</param>
/// <param name="connection">Connection parameters</param>
/// <param name="options">Options regarding object lock and error handling.</param>
/// <param name="cancellationToken">Token generated by Frends to stop this Task.</param>
/// <returns>Object { bool success, string BucketLocation } </returns>
public static async Task<Result> CreateBucket([PropertyTab] Connection connection, CancellationToken cancellationToken)
/// <returns>Object { bool success, string BucketLocation, string BucketName, Error Error { string Message, Exception AdditionalInfo } } </returns>
public static async Task<Result> CreateBucket([PropertyTab] Input input, [PropertyTab] Connection connection, [PropertyTab] Options options, CancellationToken cancellationToken)
/// <summary>
/// Create AWS S3 Bucket.
/// [Documentation](https://tasks.frends.com/tasks/frends-tasks/Frends.AmazonS3.CreateBucket)
/// </summary>
/// <frendsdocs>
/// Creates an Amazon S3 bucket with specified configuration options.
/// </frendsdocs>
/// <param name="input">Input parameters.</param>
/// <param name="connection">Connection parameters</param>
/// <param name="options">Options regarding object lock and error handling.</param>
/// <param name="cancellationToken">Token generated by Frends to stop this Task.</param>
/// <returns>Object { bool success, string BucketLocation, string BucketName, Error Error { string Message, Exception AdditionalInfo } } </returns>
/// <example>
/// var input = new Input { BucketName = "my-bucket" };
/// var connection = new Connection { AwsAccessKeyId = "key", AwsSecretAccessKey = "secret", Region = Region.EuCentral1 };
/// var options = new Options { ObjectLockEnabled = false };
/// var result = await AmazonS3.CreateBucket(input, connection, options, CancellationToken.None);
/// </example>
public static async Task<Result> CreateBucket([PropertyTab] Input input, [PropertyTab] Connection connection, [PropertyTab] Options options, CancellationToken cancellationToken)
🤖 Prompt for AI Agents
In Frends.AmazonS3.CreateBucket/Frends.AmazonS3.CreateBucket/CreateBucket.cs
around lines 23 to 28, the XML documentation for the CreateBucket method lacks
the required <frendsdocs> and <example> tags. Add these tags to the XML comments
to comply with Frends task coding guidelines, providing detailed documentation
and usage examples for the public method.

{
try
{
using IAmazonS3 s3Client = new AmazonS3Client(connection.AwsAccessKeyId, connection.AwsSecretAccessKey, RegionSelection(connection.Region));
var bucketName = connection.BucketName;
var bucketName = input.BucketName;
if (!await AmazonS3Util.DoesS3BucketExistV2Async(s3Client, bucketName))
{
var putBucketRequest = new PutBucketRequest
{
BucketName = bucketName,
UseClientRegion = true,
CannedACL = GetS3CannedACL(connection.ACL),
ObjectLockEnabledForBucket = connection.ObjectLockEnabledForBucket,
CannedACL = GetS3CannedACL(connection.Acl),
ObjectLockEnabledForBucket = options.ObjectLockEnabled,
};

PutBucketResponse putBucketResponse = await s3Client.PutBucketAsync(putBucketRequest, cancellationToken);
Expand All @@ -46,20 +68,20 @@ public static async Task<Result> CreateBucket([PropertyTab] Connection connectio
BucketName = bucketName
};
var response = await s3Client.GetBucketLocationAsync(getBucketLocationRequest, cancellationToken);
return new Result(true, response.Location.ToString());
return new Result(true, response.Location.ToString(), bucketName);
}
else
{
return new Result(true, $"Bucket already exists.");
return new Result(true, $"Bucket already exists.", bucketName);
}
}
catch (AmazonS3Exception e)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using ErrorHandler.Handle(ex, options.ThrowErrorOnFailure, options.ErrorMessageOnFailure) here and in second catch, so the error handling respects the new options.

{
throw new AmazonS3Exception("Failed to create the bucket.", e);
return ErrorHandler.Handle(e, options.ThrowErrorOnFailure, options.ErrorMessageOnFailure);
}
catch (Exception e)
{
throw new Exception("Unexpected error occurred while creating the bucket.", e);
return ErrorHandler.Handle(e, options.ThrowErrorOnFailure, options.ErrorMessageOnFailure);
}
}

Expand Down Expand Up @@ -96,17 +118,17 @@ private static RegionEndpoint RegionSelection(Region region)
}

[ExcludeFromCodeCoverage(Justification = "can only test S3CannedACL.Private")]
private static S3CannedACL GetS3CannedACL(ACLs acl)
private static S3CannedACL GetS3CannedACL(Acls acl)
{
return acl switch
{
ACLs.Private => S3CannedACL.Private,
ACLs.PublicRead => S3CannedACL.PublicRead,
ACLs.PublicReadWrite => S3CannedACL.PublicReadWrite,
ACLs.AuthenticatedRead => S3CannedACL.AuthenticatedRead,
ACLs.BucketOwnerRead => S3CannedACL.BucketOwnerRead,
ACLs.BucketOwnerFullControl => S3CannedACL.BucketOwnerFullControl,
ACLs.LogDeliveryWrite => S3CannedACL.LogDeliveryWrite,
Acls.Private => S3CannedACL.Private,
Acls.PublicRead => S3CannedACL.PublicRead,
Acls.PublicReadWrite => S3CannedACL.PublicReadWrite,
Acls.AuthenticatedRead => S3CannedACL.AuthenticatedRead,
Acls.BucketOwnerRead => S3CannedACL.BucketOwnerRead,
Acls.BucketOwnerFullControl => S3CannedACL.BucketOwnerFullControl,
Acls.LogDeliveryWrite => S3CannedACL.LogDeliveryWrite,
_ => S3CannedACL.NoACL,
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,6 @@ public class Connection
[PasswordPropertyText]
public string AwsSecretAccessKey { get; set; }

/// <summary>
/// AWS S3 bucket's name.
/// See https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-s3-bucket-naming-requirements.html
/// </summary>
/// <example>bucket</example>
[DisplayFormat(DataFormatString = "Text")]
public string BucketName { get; set; }

/// <summary>
/// AWS S3 bucket's region.
/// </summary>
Expand All @@ -41,14 +33,7 @@ public class Connection
/// <summary>
/// Access control list.
/// </summary>
/// <example>ACL.Private</example>
[DefaultValue(ACLs.Private)]
public ACLs ACL { get; set; }

/// <summary>
/// Specifies whether you want S3 Object Lock to be enabled for the new bucket.
/// </summary>
/// <example>false</example>
[DefaultValue(false)]
public bool ObjectLockEnabledForBucket { get; set; }
/// <example>Acl.Private</example>
[DefaultValue(Acls.Private)]
public Acls Acl { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public enum Region
/// <summary>
/// Access control list.
/// </summary>
public enum ACLs
public enum Acls
{
/// <summary>
/// Owner gets FULL_CONTROL. No one else has access rights (default).
Expand Down Expand Up @@ -63,7 +63,7 @@ public enum ACLs
BucketOwnerRead,

/// <summary>
/// Both the object owner and the bucket owner get FULL_CONTROL over the object. If you specify this canned ACL when creating a bucket, Amazon S3 ignores it.
/// Both the object owner and the bucket owner get FULL_CONTROL over the object. If you specify this canned Acl when creating a bucket, Amazon S3 ignores it.
/// </summary>
BucketOwnerFullControl,

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.ComponentModel.DataAnnotations;

namespace Frends.AmazonS3.CreateBucket.Definitions
{
/// <summary>
/// Input parameters.
/// </summary>
public class Input
{
/// <summary>
/// AWS S3 bucket's name.
/// See https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-s3-bucket-naming-requirements.html
/// </summary>
/// <example>bucket</example>
[DisplayFormat(DataFormatString = "Text")]
public string BucketName { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace Frends.AmazonS3.CreateBucket.Definitions
{
/// <summary>
/// Option parameters.
/// </summary>
public class Options
{
/// <summary>
/// Specifies whether you want S3 Object Lock to be enabled for the new bucket.
/// </summary>
/// <example>false</example>
[DefaultValue(false)]
public bool ObjectLockEnabled { get; set; }

/// <summary>
/// Gets or sets a value indicating whether an error should stop the Task and throw an exception.
/// If set to true, an exception will be thrown when an error occurs. If set to false, Task will try to continue and the error message will be added into Result.ErrorMessage and Result.Success will be set to false.
/// </summary>
/// <example>true</example>
[DefaultValue(true)]
public bool ThrowErrorOnFailure { get; set; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error handling option are currently unused. This should be passed into a error handler (e.g., ErrorHandler.Handle)


/// <summary>
/// Gets or sets a custom error message that will be used when an error occurs and ThrowErrorOnFailure is true.
/// When ThrowErrorOnFailure is false, this message will be combined with the task's own error message.
/// </summary>
/// <example>Custom error message for bucket creation failure.</example>
[DisplayFormat(DataFormatString = "Text")]
[DefaultValue("Failed to create Amazon S3 bucket.")]
public string ErrorMessageOnFailure { get; set; } = "Failed to create Amazon S3 bucket.";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error handling option are currently unused. This should be passed into a error handler (e.g., ErrorHandler.Handle)

}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,24 @@
namespace Frends.AmazonS3.CreateBucket.Definitions;
using System.ComponentModel.DataAnnotations;

namespace Frends.AmazonS3.CreateBucket.Definitions;

/// <summary>
/// Error that occurred during the task.
/// </summary>
public class Error
{
/// <summary>
/// Summary of the error.
/// </summary>
/// <example>Unable to join strings.</example>
public string Message { get; set; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Property currently unused. See error handler comments


/// <summary>
/// Additional information about the error.
/// </summary>
/// <example>object { Exception Exception }</example>
public object AdditionalInfo { get; set; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Property currently unused. See error handler comments

}

/// <summary>
/// Task's result.
Expand All @@ -18,9 +38,26 @@ public class Result
/// <example>eu-central-1</example>
public string BucketLocation { get; private set; }

internal Result(bool success, string bucketLocation)
/// <summary>
/// AWS S3 bucket's name.
/// See https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-s3-bucket-naming-requirements.html
/// </summary>
/// <example>bucket</example>
[DisplayFormat(DataFormatString = "Text")]
public string BucketName { get; private set; }

/// <summary>
/// Error that occurred during task execution.
/// </summary>
/// <example>object { string Message, object { Exception Exception } AdditionalInfo }</example>
public Error Error { get; private set; }

internal Result(bool success, string bucketLocation, string bucketName, Error error = null)
{
Success = success;
BucketLocation = bucketLocation;
BucketName = bucketName;
Error = error;

}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks>
<Version>1.0.0</Version>
<TargetFrameworks>net8.0</TargetFrameworks>
<Version>2.0.0</Version>
<Authors>Frends</Authors>
<Copyright>Frends</Copyright>
<Company>Frends</Company>
Expand Down
Loading
Loading