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
73 changes: 66 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ concurrency:
jobs:
build-and-test:
runs-on: ubuntu-latest
timeout-minutes: 15
timeout-minutes: 25

steps:
- uses: actions/checkout@v5
Expand All @@ -26,14 +26,42 @@ jobs:
dotnet-version: '10.0.x'
dotnet-quality: 'preview'

- name: Setup Java 17
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: '17'
cache: maven
cache-dependency-path: examples/testcontainers/java/pom.xml

- name: Setup Go
uses: actions/setup-go@v6
with:
go-version-file: examples/testcontainers/go/go.mod
cache-dependency-path: examples/testcontainers/go/go.sum

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
cache: npm
cache-dependency-path: examples/testcontainers/typescript/package-lock.json

- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
cache: pip
cache-dependency-path: examples/testcontainers/python/requirements.lock

- name: Restore
run: dotnet restore MicroStack.slnx

- name: Build
run: dotnet build MicroStack.slnx -c Release --no-restore

- name: Test
run: dotnet test --project tests/MicroStack.Tests/MicroStack.Tests.csproj -c Release --no-build --filter-not-class MicroStack.Tests.ContainerSmokeTests
run: dotnet test --project tests/MicroStack.Tests/MicroStack.Tests.csproj -c Release --no-build

- name: Pack NuGet packages
run: dotnet pack src/MicroStack.Aspire.Hosting/MicroStack.Aspire.Hosting.csproj -c Release --no-build --output ./packages
Expand All @@ -47,12 +75,43 @@ jobs:
- name: Build container image
run: docker build -t microstack:ci .

- name: Smoke test container
- name: Test container contract
env:
MICROSTACK_TEST_IMAGE: microstack:ci
run: dotnet test --project tests/MicroStack.Testcontainers.Tests/MicroStack.Testcontainers.Tests.csproj -c Release

- name: Test .NET Testcontainers example
env:
MICROSTACK_TEST_IMAGE: microstack:ci
run: dotnet test --project examples/testcontainers/dotnet/MicroStack.Testcontainers.Example.csproj -c Release

- name: Test Java Testcontainers example
working-directory: examples/testcontainers/java
env:
MICROSTACK_TEST_IMAGE: microstack:ci
run: mvn --batch-mode test

- name: Test Go Testcontainers example
working-directory: examples/testcontainers/go
env:
MICROSTACK_TEST_IMAGE: microstack:ci
run: go test ./...

- name: Test Python Testcontainers example
env:
MICROSTACK_TEST_IMAGE: microstack:ci
run: |
python -m pip install --require-hashes -r examples/testcontainers/python/requirements.lock
python -m pytest -q examples/testcontainers/python

- name: Test TypeScript Testcontainers example
working-directory: examples/testcontainers/typescript
env:
MICROSTACK_TEST_IMAGE: microstack:ci
run: |
docker run --rm -d --name microstack-smoke -p 4566:4566 microstack:ci
sleep 3
curl -sf http://localhost:4566/_microstack/health || (docker logs microstack-smoke && exit 1)
docker stop microstack-smoke
npm ci
npm run typecheck
npm test

- name: Report container image size
run: docker images microstack
73 changes: 66 additions & 7 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ on:
jobs:
release:
runs-on: ubuntu-latest
timeout-minutes: 15
timeout-minutes: 25
environment: nuget.org
permissions:
contents: write
Expand All @@ -40,6 +40,34 @@ jobs:
dotnet-version: '10.0.x'
dotnet-quality: 'preview'

- name: Setup Java 17
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: '17'
cache: maven
cache-dependency-path: examples/testcontainers/java/pom.xml

- name: Setup Go
uses: actions/setup-go@v6
with:
go-version-file: examples/testcontainers/go/go.mod
cache-dependency-path: examples/testcontainers/go/go.sum

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
cache: npm
cache-dependency-path: examples/testcontainers/typescript/package-lock.json

- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
cache: pip
cache-dependency-path: examples/testcontainers/python/requirements.lock

- name: Remove existing tag if present
run: |
TAG="v${{ inputs.version }}"
Expand All @@ -64,7 +92,7 @@ jobs:
run: dotnet build MicroStack.slnx -c Release --no-restore

- name: Test
run: dotnet test --project tests/MicroStack.Tests/MicroStack.Tests.csproj -c Release --no-build --filter-not-class MicroStack.Tests.ContainerSmokeTests
run: dotnet test --project tests/MicroStack.Tests/MicroStack.Tests.csproj -c Release --no-build

- name: Pack NuGet packages
run: dotnet pack src/MicroStack.Aspire.Hosting/MicroStack.Aspire.Hosting.csproj -c Release --no-build --output ./packages
Expand Down Expand Up @@ -93,12 +121,43 @@ jobs:
-t ghcr.io/${{ github.repository_owner }}/microstack:latest
.

- name: Smoke test container
- name: Test container contract
env:
MICROSTACK_TEST_IMAGE: ghcr.io/${{ github.repository_owner }}/microstack:${{ inputs.version }}
run: dotnet test --project tests/MicroStack.Testcontainers.Tests/MicroStack.Testcontainers.Tests.csproj -c Release

- name: Test .NET Testcontainers example
env:
MICROSTACK_TEST_IMAGE: ghcr.io/${{ github.repository_owner }}/microstack:${{ inputs.version }}
run: dotnet test --project examples/testcontainers/dotnet/MicroStack.Testcontainers.Example.csproj -c Release

- name: Test Java Testcontainers example
working-directory: examples/testcontainers/java
env:
MICROSTACK_TEST_IMAGE: ghcr.io/${{ github.repository_owner }}/microstack:${{ inputs.version }}
run: mvn --batch-mode test

- name: Test Go Testcontainers example
working-directory: examples/testcontainers/go
env:
MICROSTACK_TEST_IMAGE: ghcr.io/${{ github.repository_owner }}/microstack:${{ inputs.version }}
run: go test ./...

- name: Test Python Testcontainers example
env:
MICROSTACK_TEST_IMAGE: ghcr.io/${{ github.repository_owner }}/microstack:${{ inputs.version }}
run: |
python -m pip install --require-hashes -r examples/testcontainers/python/requirements.lock
python -m pytest -q examples/testcontainers/python

- name: Test TypeScript Testcontainers example
working-directory: examples/testcontainers/typescript
env:
MICROSTACK_TEST_IMAGE: ghcr.io/${{ github.repository_owner }}/microstack:${{ inputs.version }}
run: |
docker run --rm -d --name microstack-smoke -p 4566:4566 ghcr.io/${{ github.repository_owner }}/microstack:${{ inputs.version }}
sleep 3
curl -sf http://localhost:4566/_microstack/health || (docker logs microstack-smoke && exit 1)
docker stop microstack-smoke
npm ci
npm run typecheck
npm test

- name: Push container image to GHCR
run: |
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,11 @@ volumes:

See [Docker](https://damianh.github.io/microstack/docker) for more options.

MicroStack also supports generic
[Testcontainers](https://damianh.github.io/microstack/testing) clients with random
port mappings. Runnable .NET, Java, Go, Python, and TypeScript examples are included
under `examples/testcontainers`.

## Using with AWS CLI

```bash
Expand Down
16 changes: 16 additions & 0 deletions docs/Docs/Content/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ MicroStack is configured via environment variables. All settings are consolidate
| `MICROSTACK_HOST` | `localhost` | Hostname for URL generation |
| `MICROSTACK_REGION` | `us-east-1` | Default AWS region |
| `MICROSTACK_ACCOUNT_ID` | `000000000000` | Default AWS account ID |
| `MICROSTACK_SQS_ENDPOINT_STRATEGY` | `request` | SQS queue URL source: `request` uses the caller's scheme, host, and port; `legacy` uses `MICROSTACK_HOST` and `GATEWAY_PORT` |
| `PERSIST_STATE` | `0` | Set to `1` to enable JSON state persistence |
| `STATE_DIR` | `<temp>/microstack-state` | Directory for persisted state files |
| `SERVICES` | *(all)* | Comma-separated list of services to enable |
Expand Down Expand Up @@ -50,6 +51,21 @@ Service aliases are supported:
docker run -e GATEWAY_PORT=5000 -p 5000:5000 ghcr.io/damianh/microstack:latest
```

`GATEWAY_PORT` controls the port inside the container. When Docker or Testcontainers
maps that port dynamically, SQS queue URLs use the incoming request authority by
default so SDKs can use returned URLs without rewriting them.

To retain the earlier configured-address behavior:

```bash
docker run \
-e MICROSTACK_SQS_ENDPOINT_STRATEGY=legacy \
-e MICROSTACK_HOST=localhost \
-e GATEWAY_PORT=4566 \
-p 4566:4566 \
ghcr.io/damianh/microstack:latest
```

## State Persistence

Enable persistence to survive restarts:
Expand Down
7 changes: 7 additions & 0 deletions docs/Docs/Content/docs/docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,10 @@ curl -X POST http://localhost:4566/_microstack/reset
```

Clears all in-memory state across all services.

## Dynamic Port Mapping

Container frameworks such as Testcontainers should map container port `4566` to a
random host port and wait for `/_microstack/health`. SQS automatically returns queue
URLs using the incoming request's mapped authority. See
[Integration Testing](/testing) for runnable examples in five languages.
36 changes: 36 additions & 0 deletions docs/Docs/Content/docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,42 @@ MicroStack is designed for integration testing with the AWS SDK for .NET. The re
approach uses **.NET Aspire** to spin up MicroStack as a container — matching how you
run in production.

## Testcontainers

MicroStack works with generic Testcontainers APIs in any language. The container
contract is:

- container port `4566`
- HTTP readiness at `/_microstack/health`
- dummy AWS credentials and an explicit signing region such as `us-east-1`
- path-style addressing for S3
- random host-port mappings; do not assume host port `4566`

Runnable examples are available for
[.NET](https://github.com/damianh/microstack/tree/main/examples/testcontainers/dotnet),
[Java](https://github.com/damianh/microstack/tree/main/examples/testcontainers/java),
[Go](https://github.com/damianh/microstack/tree/main/examples/testcontainers/go),
[Python](https://github.com/damianh/microstack/tree/main/examples/testcontainers/python),
and [TypeScript](https://github.com/damianh/microstack/tree/main/examples/testcontainers/typescript).
Each starts an isolated generic container and verifies SQS and S3 using the
language's AWS SDK.

Set `MICROSTACK_TEST_IMAGE` to test a local image:

```bash
docker build -t microstack:test .
export MICROSTACK_TEST_IMAGE=microstack:test
```

SQS queue URLs follow the incoming request's scheme, host, and mapped port, so they
can be passed back to the SDK unchanged. If tests run inside another container,
use an endpoint reachable on that container network rather than the host-published
endpoint. A single advertised address cannot be reachable from every network.

The release Dockerfile currently publishes a Linux x64 image. Docker Desktop can
run it from Windows and macOS hosts; ARM hosts require container emulation until
the Dockerfile release pipeline publishes ARM64 as well.

## Aspire-Based Testing

### 1. AppHost Setup
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<XunitAutoGeneratedEntryPoint>false</XunitAutoGeneratedEntryPoint>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AWSSDK.S3" Version="4.0.103.2" />
<PackageReference Include="AWSSDK.SQS" Version="4.0.100.13" />
<PackageReference Include="Testcontainers" Version="4.15.0" />
<PackageReference Include="xunit.v3" Version="4.0.1" />
</ItemGroup>

</Project>
Loading
Loading