Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 3 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -450,14 +450,12 @@ jobs:
AWS_SERVICE_URL: http://localhost:4566

services:
moto:
image: motoserver/moto:5.1.22
floci:
image: floci/floci:1.5.19
ports:
- 4566:4566
env:
MOTO_PORT: "4566"
options: >-
--health-cmd "curl -sf http://localhost:4566/moto-api/"
--health-cmd "curl -sf http://localhost:4566/_localstack/health"
--health-interval 5s
--health-timeout 3s
--health-retries 10
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ If you're only working on a specific area, use the individual compose files:
| Redis | `docker-compose-redis.yaml` | Testing Redis messaging gateway |
| MQTT | `docker-compose-mqtt.yaml` | Testing MQTT messaging gateway |
| RocketMQ | `docker-compose-rocketmq.yaml` | Testing RocketMQ messaging gateway |
| Moto (AWS) | `docker-compose-aws.yaml` | Testing AWS services locally |
| Floci (AWS) | `docker-compose-aws.yaml` | Testing AWS services locally |
| Jaeger | `docker-compose-jaeger.yaml` | Testing distributed tracing |
| Prometheus | `docker-compose-prometheus.yaml` | Testing metrics collection |

Expand Down
1 change: 0 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,6 @@
<PackageVersion Include="TUnit" Version="0.67.10" />
<PackageVersion Include="YamlDotNet" Version="16.3.0" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="Xunit.SkippableFact" Version="1.5.61" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
10 changes: 4 additions & 6 deletions docker-compose-aws.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
services:
moto:
image: motoserver/moto:5.1.22
floci:
image: floci/floci:1.5.19
ports:
- "4566:4566" # Moto Gateway (same port as former LocalStack for compatibility)
environment:
- "MOTO_PORT=4566"
- "4566:4566" # Floci AWS emulator (same port as Moto/LocalStack for compatibility)
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:4566/moto-api/"]
test: ["CMD-SHELL", "curl -sf http://localhost:4566/_localstack/health"]
interval: 5s
timeout: 3s
retries: 10
Expand Down
27 changes: 22 additions & 5 deletions samples/Scheduler/AwsTaskQueue/GreetingsPumper/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Threading;
using System.Threading.Tasks;
using Amazon;
using Amazon.Runtime;
using Amazon.Runtime.CredentialManagement;
using Greetings.Ports.Commands;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -30,11 +31,10 @@ private static async Task Main(string[] args)
.ConfigureServices((hostContext, services) =>

{
if (new CredentialProfileStoreChain().TryGetAWSCredentials("default", out var credentials))
var serviceURL = Environment.GetEnvironmentVariable("AWS_SERVICE_URL") ?? string.Empty;
var credentials = ResolveCredentials(serviceURL);
if (credentials != null)
{
// EventBridge Scheduler is not implemented by Moto, so this sample
// always targets real AWS and ignores AWS_SERVICE_URL.
var serviceURL = string.Empty;
var region = RegionEndpoint.USEast1;
var awsConnection = new AWSMessagingGatewayConnection(credentials, region,
cfg =>
Expand Down Expand Up @@ -77,7 +77,8 @@ private static async Task Main(string[] args)
.UseScheduler(new AwsSchedulerFactory(awsConnection, "brighter-scheduler")
{
SchedulerTopicOrQueue = new RoutingKey("message-scheduler-topic"),
OnConflict = OnSchedulerConflict.Overwrite
OnConflict = OnSchedulerConflict.Overwrite,
MakeRole = OnMissingRole.Create
})
.AutoFromAssemblies([typeof(GreetingEvent).Assembly]);
}
Expand All @@ -92,6 +93,22 @@ private static async Task Main(string[] args)
await host.RunAsync();
}

// When AWS_SERVICE_URL points at a local AWS emulator (Floci) we use a random
// 12-digit access key, which Floci interprets as the account ID for per-account
// namespace isolation — so this sample's resources stay separate from anything
// else running against the same emulator. Without AWS_SERVICE_URL we fall back
// to the default profile in the local credential chain.
private static AWSCredentials ResolveCredentials(string serviceURL)
{
if (!string.IsNullOrWhiteSpace(serviceURL))
{
var accountId = Random.Shared.NextInt64(100_000_000_000L, 999_999_999_999L + 1).ToString();
return new BasicAWSCredentials(accountId, "test");
}

return new CredentialProfileStoreChain().TryGetAWSCredentials("default", out var creds) ? creds : null;
}

internal sealed class RunCommandProcessor(IAmACommandProcessor commandProcessor, ILogger<RunCommandProcessor> logger) : IHostedService
{
public async Task StartAsync(CancellationToken cancellationToken)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ THE SOFTWARE. */
using System;
using System.Threading.Tasks;
using Amazon;
using Amazon.Runtime;
using Amazon.Runtime.CredentialManagement;
using Greetings.Ports.Commands;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -89,11 +90,10 @@ public static async Task Main(string[] args)
};

//create the gateway
if (new CredentialProfileStoreChain().TryGetAWSCredentials("default", out var credentials))
var serviceURL = Environment.GetEnvironmentVariable("AWS_SERVICE_URL") ?? string.Empty;
var credentials = ResolveCredentials(serviceURL);
if (credentials != null)
{
// EventBridge Scheduler is not implemented by Moto, so this sample
// always targets real AWS and ignores AWS_SERVICE_URL.
var serviceURL = string.Empty;
var region = RegionEndpoint.USEast1;
var awsConnection = new AWSMessagingGatewayConnection(credentials, region,
cfg =>
Expand Down Expand Up @@ -146,4 +146,20 @@ public static async Task Main(string[] args)

await host.RunAsync();
}

// When AWS_SERVICE_URL points at a local AWS emulator (Floci) we use a random
// 12-digit access key, which Floci interprets as the account ID for per-account
// namespace isolation — so this sample's resources stay separate from anything
// else running against the same emulator. Without AWS_SERVICE_URL we fall back
// to the default profile in the local credential chain.
private static AWSCredentials ResolveCredentials(string serviceURL)
{
if (!string.IsNullOrWhiteSpace(serviceURL))
{
var accountId = Random.Shared.NextInt64(100_000_000_000L, 999_999_999_999L + 1).ToString();
return new BasicAWSCredentials(accountId, "test");
}

return new CredentialProfileStoreChain().TryGetAWSCredentials("default", out var creds) ? creds : null;
}
}
10 changes: 5 additions & 5 deletions samples/Scheduler/QuartzTaskQueue/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ This sample demonstrates how to use [Quartz.NET](https://www.quartz-scheduler.ne
- [.NET 6.0 SDK](https://dotnet.microsoft.com/download/dotnet/6.0)
- [Quartz.NET](https://www.nuget.org/packages/Quartz) (added via NuGet)
- AWS credentials (if using AWS SQS/SNS integration)
- Moto (optional, for local AWS service emulation)
- [Floci](https://github.com/floci-io/floci) (optional, for local AWS service emulation)

## Getting Started

Expand Down Expand Up @@ -36,9 +36,9 @@ This sample demonstrates how to use [Quartz.NET](https://www.quartz-scheduler.ne

2. **Running the Application**

To run with Moto for AWS SQS/SNS emulation:
To run with Floci for AWS SQS/SNS emulation:

- Start Moto:
- Start Floci:

```sh
docker-compose -f docker-compose-aws.yaml up
Expand All @@ -48,7 +48,7 @@ To run with Moto for AWS SQS/SNS emulation:

3. **Inspecting AWS Mock Services**

To inspect the queues and topics in Moto, you can use the AWS CLI pointed to the Moto endpoint:
To inspect the queues and topics in Floci, you can use the AWS CLI pointed to the Floci endpoint:

- Ensure that you set the region to us-east-1

Expand All @@ -67,4 +67,4 @@ aws configure set region us-east-1

- [Quartz.NET Documentation](https://www.quartz-scheduler.net/documentation/)
- [Brighter Documentation](https://github.com/BrighterCommand/Brighter)
- [Moto Documentation](https://docs.getmoto.org/)
- [Floci Documentation](https://floci.io/)
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,18 @@ namespace Paramore.Brighter.AWS.Tests.Helpers;

public static class CredentialsChain
{
// Per-process random 12-digit account ID; Floci treats a 12-digit access key
// as the AWS account ID for per-account resource isolation, so each test-runner
// process sees its own namespace and can share a single Floci container with
// other processes (e.g. V3 vs V4) without name collisions.
private static readonly string s_localAccountId =
Random.Shared.NextInt64(100_000_000_000L, 999_999_999_999L + 1).ToString();

public static (AWSCredentials credentials, RegionEndpoint region) GetAwsCredentials()
{
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AWS_SERVICE_URL")))
{
return (new BasicAWSCredentials("test", "test"), RegionEndpoint.USEast1);
return (new BasicAWSCredentials(s_localAccountId, "test"), RegionEndpoint.USEast1);
}

//Try to get the details out of the credential store chain
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public AWSValidateInfrastructureByUrlTests()
//Now change the subscription to validate, just check what we made
subscription = new SqsSubscription<MyCommand>(
subscriptionName: new SubscriptionName(subscriptionName),
channelName: channel.Name,
channelName: new ChannelName(queueUrl),
routingKey: routingKey,
findQueueBy: QueueFindBy.Url,
messagePumpType: MessagePumpType.Reactor,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,9 @@ public SqsMessageProducerResourcesAreTaggedAsyncTests()
});
}

[SkippableFact]
[Fact]
public async Task When_posting_a_message_resources_are_tagged_async()
{
Skip.If(
!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("AWS_SERVICE_URL")),
"SQS tag verification fails against Moto; runs against real AWS.");

// arrange
await _messageProducer.SendAsync(_message);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="System.Text.Json" />
<PackageReference Include="xunit" />
<PackageReference Include="Xunit.SkippableFact" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,18 @@ namespace Paramore.Brighter.AWS.V4.Tests.Helpers;

public static class CredentialsChain
{
// Per-process random 12-digit account ID; Floci treats a 12-digit access key
// as the AWS account ID for per-account resource isolation, so each test-runner
// process sees its own namespace and can share a single Floci container with
// other processes (e.g. V3 vs V4) without name collisions.
private static readonly string s_localAccountId =
Random.Shared.NextInt64(100_000_000_000L, 999_999_999_999L + 1).ToString();

public static (AWSCredentials credentials, RegionEndpoint region) GetAwsCredentials()
{
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AWS_SERVICE_URL")))
{
return (new BasicAWSCredentials("test", "test"), RegionEndpoint.USEast1);
return (new BasicAWSCredentials(s_localAccountId, "test"), RegionEndpoint.USEast1);
}

//Try to get the details out of the credential store chain
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,11 @@ public SqsBufferedConsumerTestsAsync()
new SnsPublication { MakeChannels = OnMissingChannel.Create });
}

[SkippableTheory]
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task When_a_message_consumer_reads_multiple_messages_async(bool fairQueue)
{
// TODO: remove once Moto pin in #4096 is bumped to 5.1.23+
Skip.If(fairQueue && !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("AWS_SERVICE_URL")),
"SQS fair queues require Moto >= 5.1.23; pinned image is 5.1.22. Runs against real AWS.");

var partitionOne = fairQueue ? new PartitionKey(Uuid.NewAsString()) : PartitionKey.Empty;
var partitionTwo = fairQueue ? new PartitionKey(Uuid.NewAsString()) : PartitionKey.Empty;
var routingKey = new RoutingKey(_topicName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,11 @@ public SqsMessageProducerSendAsyncTests()
});
}

[SkippableTheory]
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task When_posting_a_message_via_the_producer_async(bool fairQueue)
{
// TODO: remove once Moto pin in #4096 is bumped to 5.1.23+
Skip.If(fairQueue && !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("AWS_SERVICE_URL")),
"SQS fair queues require Moto >= 5.1.23; pinned image is 5.1.22. Runs against real AWS.");

// arrange
_message.Header.Subject = "test subject";
_message.Header.PartitionKey = fairQueue ? new PartitionKey(Uuid.NewAsString()) : PartitionKey.Empty;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,11 @@ public SqsBufferedConsumerTests()
});
}

[SkippableTheory]
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task When_a_message_consumer_reads_multiple_messages(bool fairQueue)
{
// TODO: remove once Moto pin in #4096 is bumped to 5.1.23+
Skip.If(fairQueue && !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("AWS_SERVICE_URL")),
"SQS fair queues require Moto >= 5.1.23; pinned image is 5.1.22. Runs against real AWS.");

var partitionOne = fairQueue ? new PartitionKey(Uuid.NewAsString()) : PartitionKey.Empty;
var partitionTwo = fairQueue ? new PartitionKey(Uuid.NewAsString()) : PartitionKey.Empty;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,11 @@ public SqsMessageProducerSendTests()
new SnsPublication{Topic = new RoutingKey(_topicName), MakeChannels = OnMissingChannel.Create});
}

[SkippableTheory]
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task When_posting_a_message_via_the_producer(bool fairQueue)
{
// TODO: remove once Moto pin in #4096 is bumped to 5.1.23+
Skip.If(fairQueue && !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("AWS_SERVICE_URL")),
"SQS fair queues require Moto >= 5.1.23; pinned image is 5.1.22. Runs against real AWS.");

//arrange
_message.Header.Subject = "test subject";
_message.Header.PartitionKey = fairQueue ? new PartitionKey(Uuid.NewAsString()) : PartitionKey.Empty;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,11 @@ public SQSBufferedConsumerTestsAsync()
);
}

[SkippableTheory]
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task When_a_message_consumer_reads_multiple_messages_async(bool fairQueue)
{
// TODO: remove once Moto pin in #4096 is bumped to 5.1.23+
Skip.If(fairQueue && !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("AWS_SERVICE_URL")),
"SQS fair queues require Moto >= 5.1.23; pinned image is 5.1.22. Runs against real AWS.");

var partitionOne = fairQueue ? new PartitionKey(Uuid.NewAsString()) : PartitionKey.Empty;
var partitionTwo = fairQueue ? new PartitionKey(Uuid.NewAsString()) : PartitionKey.Empty;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public AWSValidateInfrastructureByUrlTests()
//Now change the subscription to validate, just check what we made
subscription = new SqsSubscription<MyCommand>(
subscriptionName: new SubscriptionName(subscriptionName),
channelName: channel.Name,
channelName: new ChannelName(queueUrl),
routingKey: routingKey,
findQueueBy: QueueFindBy.Url,
messagePumpType: MessagePumpType.Reactor,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,11 @@ public SqsMessageProducerSendAsyncTests()
});
}

[SkippableTheory]
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task When_posting_a_message_via_the_producer_async(bool fairQueue)
{
// TODO: remove once Moto pin in #4096 is bumped to 5.1.23+
Skip.If(fairQueue && !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("AWS_SERVICE_URL")),
"SQS fair queues require Moto >= 5.1.23; pinned image is 5.1.22. Runs against real AWS.");

// arrange
_message.Header.Subject = "test subject";
_message.Header.PartitionKey = fairQueue ? new PartitionKey(Uuid.NewAsString()) : PartitionKey.Empty;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,11 @@ public SQSBufferedConsumerTests()
new SqsPublication(channelName: channelName, makeChannels: OnMissingChannel.Create));
}

[SkippableTheory]
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task When_a_message_consumer_reads_multiple_messages(bool fairQueue)
{
// TODO: remove once Moto pin in #4096 is bumped to 5.1.23+
Skip.If(fairQueue && !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("AWS_SERVICE_URL")),
"SQS fair queues require Moto >= 5.1.23; pinned image is 5.1.22. Runs against real AWS.");

var partitionOne = fairQueue ? new PartitionKey(Uuid.NewAsString()) : PartitionKey.Empty;
var partitionTwo = fairQueue ? new PartitionKey(Uuid.NewAsString()) : PartitionKey.Empty;

Expand Down
Loading
Loading