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
1 change: 1 addition & 0 deletions .github/workflows/integration-wsl.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ jobs:
shell: pwsh
run: |
$projects = @(
'src/tests/AzureKeyVaultEmulator.IntegrationTests/AzureKeyVaultEmulator.IntegrationTests.csproj',
'src/tests/PostgreSql.IntegrationTests/PostgreSql.IntegrationTests.csproj',
'src/tests/Redis.IntegrationTests/Redis.IntegrationTests.csproj',
'src/tests/MsSql.IntegrationTests/MsSql.IntegrationTests.csproj',
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
# Mirrors Build:TestFilter in purview-build.json: the SDK categorises unit test projects as Unit,
# so the WSLC integration suites are never selected and the runner needs no WSLC host.
test-filter: "/*/*/*/*[Category=Unit]"
# All eight library projects are packable, so pack and validate the produced packages.
# All library projects are packable, so pack and validate the produced packages.
run-pack: true
validate-pack: true
secrets: inherit
Expand All @@ -46,4 +46,4 @@ jobs:
- name: Docker backend
run: dotnet test src/tests/Docker.IntegrationTests/Docker.IntegrationTests.csproj --configuration Release --treenode-filter '/*/*/*/*'
- name: Service modules on Docker
run: dotnet test src/tests/Modules.DockerIntegrationTests/Modules.DockerIntegrationTests.csproj --configuration Release --treenode-filter '/*/*/*/*'
run: dotnet test src/tests/Modules.Docker.IntegrationTests/Modules.Docker.IntegrationTests.csproj --configuration Release --treenode-filter '/*/*/*/*'
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
This repository contains `Purview.Containers`, a Testcontainers-style library for .NET that runs throwaway
Linux containers for integration testing on **Microsoft WSL Containers (WSLC)** — the Windows runtime with
no Docker installation — or on **Docker** through Testcontainers, plus the service modules (`PostgreSql`,
`Redis`, `MsSql`, `RabbitMq`, `Azurite`, `Nats`, `MySql`).
`Redis`, `MsSql`, `RabbitMq`, `Azurite`, `Nats`, `MySql`, `AzureKeyVaultEmulator`).
`docs/wiki/Backends.md` is the consumer guide to choosing and configuring a backend; keep it up to date
whenever a target framework, a backend package or the `buildTransitive` assets change.

Expand Down
4 changes: 4 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
<PackageVersion Include="TUnit.Core" Version="1.72.4" />
<PackageVersion Include="TUnit.Mocks" Version="1.72.4" />
<PackageVersion Include="Bogus" Version="35.6.5" />
<PackageVersion Include="Azure.Core" Version="1.63.0" />
<PackageVersion Include="Azure.Security.KeyVault.Certificates" Version="4.9.2" />
<PackageVersion Include="Azure.Security.KeyVault.Keys" Version="4.10.2" />
<PackageVersion Include="Azure.Security.KeyVault.Secrets" Version="4.11.2" />
<PackageVersion Include="Npgsql" Version="10.0.3" />
<PackageVersion Include="Microsoft.Data.SqlClient" Version="7.1.1" />
<PackageVersion Include="StackExchange.Redis" Version="3.3.1" />
Expand Down
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ The WSLC backend is built directly against the `Microsoft.WSL.Containers` NuGet
> [Consumer Requirements](docs/wiki/Consumer-Requirements.md) before adopting it.

> **Status: preview.** Backend-neutral abstractions, two backends (WSL Containers and Docker), wait
> strategies, the `Image`/`Tag` parser, registry auth, observability, hardening, and **seven service
> modules** (PostgreSQL, Redis, SQL Server, RabbitMQ, Azurite, NATS, MySQL). On WSLC, **images are shared
> strategies, the `Image`/`Tag` parser, registry auth, observability, hardening, and **eight service
> modules** (PostgreSQL, Redis, SQL Server, RabbitMQ, Azurite, NATS, MySQL, Azure Key Vault Emulator). On WSLC, **images are shared
> by default** (`StorageMode.Shared`): sessions reuse a stable image store
> (`%LOCALAPPDATA%\Purview\WslContainers\images`) so images are pulled once, not per session;
> `StorageMode.PerSession` provides isolation. Move the store with `PURVIEW_CONTAINERS_STORAGE_PATH` or
Expand Down Expand Up @@ -146,7 +146,7 @@ src/
Docker/ Docker backend (Purview.Containers.Docker)
Containers/ umbrella package (Purview.Containers): Core + both backends
PostgreSql/ Redis/ MsSql/ MySql/
RabbitMq/ Azurite/ Nats/ service modules
RabbitMq/ Azurite/ Nats/ AzureKeyVaultEmulator/ service modules
tests/ TUnit unit + integration projects (WSLC and Docker suites)
samples/
getting-started/ runnable samples (WslSample, DockerSample, AutoSample)
Expand Down Expand Up @@ -176,7 +176,7 @@ The project documentation lives in [`docs/wiki`](docs/wiki/Home.md) and is publi
Every package also ships its own `README.md` (from `src/src/<Project>/Sdk/README.md`), so
`dotnet add package Purview.Containers.<Module>` brings documentation specific to that package.

All seven service modules work today:
All eight service modules work today:

```csharp
await using var postgres = new PostgreSqlBuilder()
Expand Down Expand Up @@ -234,6 +234,16 @@ string connectionString = azurite.GetConnectionString();
Uri blob = azurite.GetBlobEndpoint();
```

```csharp
await using var keyVault = new AzureKeyVaultEmulatorBuilder().Build();

await keyVault.StartAsync(); // waits for the HTTPS listener

// Clients are already wired for the emulator (pinned certificate, emulated credential).
var secrets = keyVault.GetSecretClient();
await secrets.SetSecretAsync("mySecret", "myValue");
```

## Running the tests

Every test process owns a single shared WSLC session and, by default, uses the shared image store
Expand Down
3 changes: 2 additions & 1 deletion docs/wiki/Backends.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Backends: WSL Containers or Docker

`Purview.Containers` has **one API and two interchangeable backends**. The same test code, the same service
modules (`Purview.Containers.PostgreSql`, `Redis`, `MsSql`, `RabbitMq`, `Azurite`, `Nats`, `MySql`), and
modules (`Purview.Containers.PostgreSql`, `Redis`, `MsSql`, `RabbitMq`, `Azurite`, `Nats`, `MySql`,
`AzureKeyVaultEmulator`), and
the same connection-string accessors run on either runtime — you choose which one by the package you
reference, and can override it in code or from the environment.

Expand Down
2 changes: 1 addition & 1 deletion docs/wiki/Consumer-Requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ transitive references**, so a project that references the backend package direct
project that does — gets them automatically.

> A **service module** (`Purview.Containers.PostgreSql`, `Redis`, `MsSql`, `RabbitMq`, `Azurite`, `Nats`,
> `MySql`) is backend-neutral and does **not** bring a backend, so reference the module **and**
> `MySql`, `AzureKeyVaultEmulator`) is backend-neutral and does **not** bring a backend, so reference the module **and**
> `Purview.Containers.Wsl` (or another backend package) to run it. Use `Purview.Containers.Docker` to run
> the same module on Docker, and `PURVIEW_CONTAINERS_BACKEND` to choose between them.

Expand Down
1 change: 1 addition & 0 deletions docs/wiki/Home.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ This wiki is the project documentation hub. The packages are published under the
| `Purview.Containers.Azurite` | Azure Storage emulator (`azure-storage/azurite`), blob/queue/table endpoints. |
| `Purview.Containers.Nats` | NATS broker (`nats:2`), client + monitoring endpoints. |
| `Purview.Containers.MySql` | MySQL container (`mysql:8`), host-side `MySqlConnector` readiness. |
| `Purview.Containers.AzureKeyVaultEmulator` | Azure Key Vault emulator (`jamesgoulddev/azure-keyvault-emulator`), secrets/keys/certificates endpoints and certificate-pinned Azure SDK clients. |

## Feature highlights

Expand Down
2 changes: 2 additions & 0 deletions docs/wiki/Modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ that leans on the Testcontainers modules ports across unchanged:
| RabbitMQ | `amqp://user:pass@host:port/vhost` | `GetAmqpEndpoint()`, `GetManagementEndpoint()` |
| Azurite | Azure Storage string: `DefaultEndpointsProtocol=http`, `AccountName`, `AccountKey`, `Blob/Queue/TableEndpoint` | `GetBlobEndpoint()`, `GetQueueEndpoint()`, `GetTableEndpoint()` |
| NATS | `nats://host:port` | `GetClientEndpoint()`, `GetMonitoringEndpoint()` |
| Azure Key Vault Emulator | Vault URI: `https://127.0.0.1:{port}` | `GetVaultUri()`, `GetCertificate()`, `GetSecretClient()`, `GetKeyClient()`, `GetCertificateClient()` |

## Module status

Expand All @@ -151,6 +152,7 @@ that leans on the Testcontainers modules ports across unchanged:
| Azurite | `mcr.microsoft.com/azure-storage/azurite` | log `"successfully listening"` | Azure.Storage.* | ✅ implemented — blob/queue/table endpoints; the well-known `devstoreaccount1` key is a placeholder in `AzuriteAccount.Key` until the consuming repo supplies it |
| NATS | `nats:2` | log `"Listening for client connections"` | NATS.Client.Core | ✅ implemented — client + monitoring endpoints |
| MySQL | `mysql:8` | host `MySqlConnector` connection | MySqlConnector | ✅ implemented — uses a real connection poll (the image logs `"ready for connections"` during its temporary init server) |
| Azure Key Vault Emulator | `jamesgoulddev/azure-keyvault-emulator:3.1.3` (`3.1.3-arm` on ARM64) | HTTPS `GET /` | Azure.Security.KeyVault.* | ✅ implemented — secrets/keys/certificates clients that pin the emulator certificate; a self-signed certificate is generated (and by default installed into the host trust store) and mounted at `/certs`; persistence requires a fixed host port |

Note on RabbitMQ readiness: the default wait uses the canonical **`Server startup complete`** log signal rather than `rabbitmq-diagnostics ping` — under WSLC the exec-based probe races with the Erlang cookie setup and can trigger a startup failure (`eacces` reading `.erlang.cookie`). WSLC auto-provisions image `VOLUME` declarations on an ext4 device by default; bind-mounting a Windows directory onto one replaces it with drvfs, which ignores Unix `chown` and breaks permission-sensitive images — do not bind-mount onto image volumes unless you intend that.

Expand Down
8 changes: 4 additions & 4 deletions docs/wiki/Testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ just test '/*/*/*/*[Category=Integration]' --max-parallel-test-modules 1
Integration suites target whichever backend is selected. Automatic detection prefers WSLC and falls back
to Docker, so a WSLC host runs the WSLC suites and a Docker-only host runs the Docker ones; pin one with
`PURVIEW_CONTAINERS_BACKEND=wsl|docker` to fail loudly instead of falling back. The Docker suites
(`Docker.IntegrationTests` for the container contract, `Modules.DockerIntegrationTests` for the seven
(`Docker.IntegrationTests` for the container contract, `Modules.DockerIntegrationTests` for the eight
service modules) skip themselves when no daemon is reachable, and the WSLC suites skip themselves when the
host lacks the WSL Containers components. See [Backends: WSLC or Docker](Backends.md) for consumer-facing
setup and CI examples.

```powershell
just test src/tests/Docker.IntegrationTests/Docker.IntegrationTests.csproj # Docker contract
just test src/tests/Modules.DockerIntegrationTests/Modules.DockerIntegrationTests.csproj # the seven modules on Docker
just test src/tests/Modules.DockerIntegrationTests/Modules.DockerIntegrationTests.csproj # the eight modules on Docker
```

## Why test modules run serially
Expand Down Expand Up @@ -74,8 +74,8 @@ gh workflow run "Integration (WSL Containers)" --ref main
gh run watch
```

It runs `Wsl.IntegrationTests` and then the seven service-module suites (`PostgreSql`, `Redis`, `MsSql`,
`MySql`, `RabbitMq`, `Azurite`, `Nats`) one project at a time, so the shared WSLC image store is never
It runs `Wsl.IntegrationTests` and then the eight service-module suites (`AzureKeyVaultEmulator`,
`PostgreSql`, `Redis`, `MsSql`, `MySql`, `RabbitMq`, `Azurite`, `Nats`) one project at a time, so the shared WSLC image store is never
contended. Two optional inputs: `ref` (a branch, tag or SHA other than the selected one) and `filter`
(a TUnit treenode filter, defaulting to every test).

Expand Down
4 changes: 2 additions & 2 deletions docs/wiki/Using-in-Your-Tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,8 @@ diagnostics.
- The abstractions and every service module are portable `net10.0`; see
[Consumer Requirements](Consumer-Requirements.md) for the exact target-framework contract, the
`PCC0001`/`PCC0002` guards and the `EnableWindowsTargeting` workaround for non-Windows build agents.
- The per-module connection-string shapes (Redis, PostgreSQL, SQL Server, MySQL, RabbitMQ, Azurite, NATS)
are in [Modules](Modules.md#connection-strings).
- The per-module connection-string shapes (Redis, PostgreSQL, SQL Server, MySQL, RabbitMQ, Azurite, NATS,
Azure Key Vault Emulator) are in [Modules](Modules.md#connection-strings).
- A live sample of this shape is `samples/getting-started/AutoSample` (`just sample-auto`).

## Related
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "purview-containers",
"version": "1.0.0-prerelease.5",
"version": "1.0.0-prerelease.6",
"license": "MIT",
"author": {
"name": "Kieron Lanning",
Expand Down
6 changes: 6 additions & 0 deletions purview-build.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@
"payload/win-x64/*",
"payload/win-arm64/*"
],
"purview.containers.azurekeyvaultemulator": [
"lib/$(TFM)/Purview.Containers.AzureKeyVaultEmulator.dll",
"lib/$(TFM)/Purview.Containers.AzureKeyVaultEmulator.xml",
"README.md",
"purview-logo-light.png"
],
"purview.containers.azurite": [
"lib/$(TFM)/Purview.Containers.Azurite.dll",
"lib/$(TFM)/Purview.Containers.Azurite.xml",
Expand Down
5 changes: 4 additions & 1 deletion src/Containers.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<File Path="Directory.Build.targets" />
</Folder>
<Folder Name="/src/">
<Project Path="src/AzureKeyVaultEmulator/AzureKeyVaultEmulator.csproj" />
<Project Path="src/Azurite/Azurite.csproj" />
<Project Path="src/Containers/Containers.csproj" />
<Project Path="src/Core/Core.csproj" />
Expand All @@ -30,11 +31,13 @@
</Folder>

<Folder Name="/tests/">
<Project Path="tests/AzureKeyVaultEmulator.IntegrationTests/AzureKeyVaultEmulator.IntegrationTests.csproj" />
<Project Path="tests/AzureKeyVaultEmulator.UnitTests/AzureKeyVaultEmulator.UnitTests.csproj" />
<Project Path="tests/Azurite.IntegrationTests/Azurite.IntegrationTests.csproj" />
<Project Path="tests/Azurite.UnitTests/Azurite.UnitTests.csproj" />
<Project Path="tests/Docker.IntegrationTests/Docker.IntegrationTests.csproj" />
<Project Path="tests/Docker.UnitTests/Docker.UnitTests.csproj" />
<Project Path="tests/Modules.DockerIntegrationTests/Modules.DockerIntegrationTests.csproj" />
<Project Path="tests/Modules.Docker.IntegrationTests/Modules.Docker.IntegrationTests.csproj" />
<Project Path="tests/MsSql.IntegrationTests/MsSql.IntegrationTests.csproj" />
<Project Path="tests/MsSql.UnitTests/MsSql.UnitTests.csproj" />
<Project Path="tests/MySql.IntegrationTests/MySql.IntegrationTests.csproj" />
Expand Down
20 changes: 20 additions & 0 deletions src/src/AzureKeyVaultEmulator/AzureKeyVaultEmulator.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPackable>true</IsPackable>
<Description>Azure Key Vault Emulator module for Purview.Containers: throwaway vault emulators (secrets, keys, certificates) on WSL Containers or Docker.</Description>
<PackageTags>wsl;containers;docker;keyvault;azure;emulator;testing;integration</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Authors>Purview</Authors>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Azure.Core" />
<PackageReference Include="Azure.Security.KeyVault.Certificates" />
<PackageReference Include="Azure.Security.KeyVault.Keys" />
<PackageReference Include="Azure.Security.KeyVault.Secrets" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="../Core/Core.csproj" />
</ItemGroup>
</Project>
Loading
Loading