diff --git a/src/BuslyCLI.Console/Config/AmazonsqsTransportConfig.cs b/src/BuslyCLI.Console/Config/AmazonsqsTransportConfig.cs index cc241bb..244a00c 100644 --- a/src/BuslyCLI.Console/Config/AmazonsqsTransportConfig.cs +++ b/src/BuslyCLI.Console/Config/AmazonsqsTransportConfig.cs @@ -3,6 +3,11 @@ public class AmazonsqsTransportConfig : ITransportConfig { + + // Local Stack Only public string ServiceUrl { get; set; } public string RegionName { get; set; } + public string AccessKey { get; set; } + public string SecretKey { get; set; } + } \ No newline at end of file diff --git a/src/BuslyCLI.Console/Config/Validators/AmazonsqsTransportConfigValidator.cs b/src/BuslyCLI.Console/Config/Validators/AmazonsqsTransportConfigValidator.cs new file mode 100644 index 0000000..41d75b9 --- /dev/null +++ b/src/BuslyCLI.Console/Config/Validators/AmazonsqsTransportConfigValidator.cs @@ -0,0 +1,19 @@ +using FluentValidation; + +namespace BuslyCLI.Config.Validators; + +public class AmazonsqsTransportConfigValidator : AbstractValidator +{ + public AmazonsqsTransportConfigValidator() + { + RuleFor(x => x.RegionName) + .NotEmpty(); + + RuleFor(x => x) + .Must(x => + (string.IsNullOrEmpty(x.AccessKey) && string.IsNullOrEmpty(x.SecretKey)) // both empty + || (!string.IsNullOrEmpty(x.AccessKey) && !string.IsNullOrEmpty(x.SecretKey)) // both set + ) + .WithMessage("AWS AccessKey and SecretKey are mutually dependent: if one is set, the other must also be set."); + } +} \ No newline at end of file diff --git a/src/BuslyCLI.Console/Config/Validators/TransportConfigValidator.cs b/src/BuslyCLI.Console/Config/Validators/TransportConfigValidator.cs index 97f1a7c..24a33c6 100644 --- a/src/BuslyCLI.Console/Config/Validators/TransportConfigValidator.cs +++ b/src/BuslyCLI.Console/Config/Validators/TransportConfigValidator.cs @@ -16,6 +16,7 @@ public TransportConfigValidator() v.Add(new LearningTransportConfigValidator()); v.Add(new RabbitMQTransportConfigValidator()); v.Add(new AzureServiceBusTransportConfigValidator()); + v.Add(new AmazonsqsTransportConfigValidator()); }); // RuleFor(x => x.LearningTransportConfig) diff --git a/src/BuslyCLI.Console/Factories/RawEndpointFactory.cs b/src/BuslyCLI.Console/Factories/RawEndpointFactory.cs index 7d8ca9a..dce9d65 100644 --- a/src/BuslyCLI.Console/Factories/RawEndpointFactory.cs +++ b/src/BuslyCLI.Console/Factories/RawEndpointFactory.cs @@ -1,4 +1,5 @@ -using Amazon.Runtime; +using Amazon; +using Amazon.Runtime; using Amazon.SimpleNotificationService; using Amazon.SQS; using BuslyCLI.Config; @@ -73,19 +74,27 @@ private static ManagementApiConfiguration CreateManagementApiConfig(ManagementAp private TransportDefinition CreateAmazonSQSTransport(AmazonsqsTransportConfig amazonsqsTransportConfig) { - var credentials = new BasicAWSCredentials("test", "test"); - - var sqsClient = new AmazonSQSClient(credentials, new AmazonSQSConfig + var credentials = new BasicAWSCredentials(amazonsqsTransportConfig.AccessKey, amazonsqsTransportConfig.SecretKey); + var amazonSqsConfig = new AmazonSQSConfig(); + var amazonSnsConfig = new AmazonSimpleNotificationServiceConfig(); + if (!string.IsNullOrWhiteSpace(amazonsqsTransportConfig.RegionName)) { - ServiceURL = amazonsqsTransportConfig.ServiceUrl, - AuthenticationRegion = amazonsqsTransportConfig.RegionName, - }); - var snsClient = new AmazonSimpleNotificationServiceClient(credentials, new AmazonSimpleNotificationServiceConfig + amazonSqsConfig.RegionEndpoint = RegionEndpoint.GetBySystemName(amazonsqsTransportConfig.RegionName); + amazonSnsConfig.RegionEndpoint = RegionEndpoint.GetBySystemName(amazonsqsTransportConfig.RegionName); + } + + // If ServiceUrl is passed, we are assuming we are using LocalStack + // Without this, local stack will try to really authenticate with aws which will fail + if (!string.IsNullOrWhiteSpace(amazonsqsTransportConfig.ServiceUrl)) { - ServiceURL = amazonsqsTransportConfig.ServiceUrl, - AuthenticationRegion = amazonsqsTransportConfig.RegionName, - }); + amazonSnsConfig.ServiceURL = amazonsqsTransportConfig.ServiceUrl; + amazonSqsConfig.ServiceURL = amazonsqsTransportConfig.ServiceUrl; + } + + var sqsClient = new AmazonSQSClient(credentials, amazonSqsConfig); + + var snsClient = new AmazonSimpleNotificationServiceClient(credentials, amazonSnsConfig); return new SqsTransport(sqsClient, snsClient); } diff --git a/tests/BuslyCLI.Console.Tests/Config/Validators/AmazonsqsTransportConfigValidatorTests.cs b/tests/BuslyCLI.Console.Tests/Config/Validators/AmazonsqsTransportConfigValidatorTests.cs new file mode 100644 index 0000000..f4a7654 --- /dev/null +++ b/tests/BuslyCLI.Console.Tests/Config/Validators/AmazonsqsTransportConfigValidatorTests.cs @@ -0,0 +1,95 @@ +using BuslyCLI.Config; +using BuslyCLI.Config.Validators; +using FluentValidation.TestHelper; + +namespace BuslyCLI.Console.Tests.Config.Validators; + +[TestFixture] +public class AmazonsqsTransportConfigValidatorTests +{ + private readonly AmazonsqsTransportConfigValidator _validator; + + public AmazonsqsTransportConfigValidatorTests() + { + _validator = new AmazonsqsTransportConfigValidator(); + } + + [Test] + public async Task ShouldErrorWhenRegionNameIsNotPassed() + { + // Arrange + var amazonsqsTransportConfig = new AmazonsqsTransportConfig + { + RegionName = null + }; + + // Act + var result = await _validator.TestValidateAsync(amazonsqsTransportConfig); + + // Assert + result.ShouldHaveValidationErrorFor(c => c.RegionName) + .WithErrorMessage("'Region Name' must not be empty."); + } + + [Test] + public async Task ShouldNotErrorWhenRegionNameIsPassed() + { + // Arrange + var amazonsqsTransportConfig = new AmazonsqsTransportConfig + { + RegionName = "us-east-1" + }; + // Act + var result = await _validator.TestValidateAsync(amazonsqsTransportConfig); + + // Assert + result.ShouldNotHaveValidationErrorFor(c => c.RegionName); + } + + [Test] + public async Task ShouldNotErrorWhenServiceUrlIsPassed() + { + // Arrange + var amazonsqsTransportConfig = new AmazonsqsTransportConfig + { + ServiceUrl = "us-east-1" + }; + // Act + var result = await _validator.TestValidateAsync(amazonsqsTransportConfig); + + // Assert + result.ShouldNotHaveValidationErrorFor(c => c.ServiceUrl); + } + + [Test] + public async Task ShouldErrorWhenAccessKeyIsPassedWithoutSecretKey() + { + // Arrange + var amazonsqsTransportConfig = new AmazonsqsTransportConfig() + { + AccessKey = "BLAHBLAHBLAH", + SecretKey = null + }; + // Act + var result = await _validator.TestValidateAsync(amazonsqsTransportConfig); + + // Assert + result.ShouldHaveValidationErrors().WithErrorMessage("AWS AccessKey and SecretKey are mutually dependent: if one is set, the other must also be set."); + } + + [Test] + public async Task ShouldErrorWhenSecretIsPassedWithoutAccessKey() + { + // Arrange + var amazonsqsTransportConfig = new AmazonsqsTransportConfig() + { + AccessKey = null, + SecretKey = "BLAHBLAHBLAH" + }; + // Act + var result = await _validator.TestValidateAsync(amazonsqsTransportConfig); + + // Assert + result.ShouldHaveValidationErrors().WithErrorMessage("AWS AccessKey and SecretKey are mutually dependent: if one is set, the other must also be set."); + } +} \ No newline at end of file diff --git a/website/docs/transports/amazon-sqs.md b/website/docs/transports/amazon-sqs.md new file mode 100644 index 0000000..8a4aa93 --- /dev/null +++ b/website/docs/transports/amazon-sqs.md @@ -0,0 +1,88 @@ +# Amazon SQS + +The **Amazon SQS Transport** is used to communicate to Amazon SQS. It is suitable for development, testing, and production environments. + +## Configuration + +To use the Amazon SQS Transport, define it under `transports` and reference it as `current-transport`. + +### Example + +```yaml +current-transport: local-stack-amazon-sqs + +transports: + - name: local-stack-amazon-sqs + amazonsqs-transport-config: + region-name: us-east-1 + access-key: test + secret-key: test + service-url: http://127.0.0.1:32813/ # (optional) Only used when connecting to local-stack +``` + +:::info + +The Amazon SQS transport implementation currently works only with **AWS access key and secret key authentication**. Pull requests that add support for additional authentication methods are welcome and greatly appreciated! + +::: + +--- + +## `amazonsqs-transport-config` Fields + +| Field | Required | Type | Default | Description | +| ------------- | -------- | ------ | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `region-name` | **Yes** | string | — | The AWS region. All Region codes can be found [here](https://docs.aws.amazon.com/global-infrastructure/latest/regions/aws-regions.html) (EX: us-east-1, us-east2, us-west1..etc). | +| `access-key` | **Yes** | string | — | The AWS Access Key. | +| `secret-key` | **Yes** | string | — | The AWS Secret Key. | +| `service-url` | No | string | — | The service URL used to connect to LocalStack for local development. | + +--- + +## Field Details + +### `region-name` (required) + +The AWS Region SQS is hosted in. + +Examples: + +```yaml +region-name: us-east-1 +``` + +--- + +### `access-key` (required) + +The AWS Access Key. + +Examples: + +```yaml +access-key: test +``` + +--- + +### `secret-key` (required) + +The AWS Secret Key. + +Examples: + +```yaml +secret-key: test +``` + +--- + +### `service-url` (optional) + +The service URL used to connect to LocalStack for local development. + +Examples: + +```yaml +service-url: http://127.0.0.1:32813/ +``` diff --git a/website/docs/transports/rabbitmq.md b/website/docs/transports/rabbitmq.md index 5215c6b..47dd171 100644 --- a/website/docs/transports/rabbitmq.md +++ b/website/docs/transports/rabbitmq.md @@ -55,6 +55,8 @@ amqp-connection-string: amqp://guest:guest@localhost:5672/ amqp-connection-string: amqps://user:pass@rabbitmq.example.com:5671/my-vhost ``` +--- + ### `management-api` (optional) Allows Busly to interact with the RabbitMQ Management API for monitoring or queue management.