Skip to content
Open
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
2 changes: 2 additions & 0 deletions Testcontainers.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
<Project Path="src/Testcontainers.FirebirdSql/Testcontainers.FirebirdSql.csproj"/>
<Project Path="src/Testcontainers.Firestore/Testcontainers.Firestore.csproj"/>
<Project Path="src/Testcontainers.Floci/Testcontainers.Floci.csproj"/>
<Project Path="src/Testcontainers.FlociAz/Testcontainers.FlociAz.csproj"/>
<Project Path="src/Testcontainers.GCloud/Testcontainers.GCloud.csproj"/>
<Project Path="src/Testcontainers.Grafana/Testcontainers.Grafana.csproj"/>
<Project Path="src/Testcontainers.InfluxDb/Testcontainers.InfluxDb.csproj"/>
Expand Down Expand Up @@ -108,6 +109,7 @@
<Project Path="tests/Testcontainers.FirebirdSql.Tests/Testcontainers.FirebirdSql.Tests.csproj"/>
<Project Path="tests/Testcontainers.Firestore.Tests/Testcontainers.Firestore.Tests.csproj"/>
<Project Path="tests/Testcontainers.Floci.Tests/Testcontainers.Floci.Tests.csproj"/>
<Project Path="tests/Testcontainers.FlociAz.Tests/Testcontainers.FlociAz.Tests.csproj"/>
<Project Path="tests/Testcontainers.Grafana.Tests/Testcontainers.Grafana.Tests.csproj"/>
<Project Path="tests/Testcontainers.InfluxDb.Tests/Testcontainers.InfluxDb.Tests.csproj"/>
<Project Path="tests/Testcontainers.JanusGraph.Tests/Testcontainers.JanusGraph.Tests.csproj"/>
Expand Down
90 changes: 90 additions & 0 deletions docs/modules/flociaz.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# FlociAz

[FlociAz](https://github.com/floci-io/floci-az) emulates Azure management and data-plane APIs in one container. The module starts in a Docker-safe mode: services that would otherwise create child containers use their mocked or embedded implementations.

Add the module to a test project:

```shell
dotnet add package Testcontainers.FlociAz
```

Start FlociAz and use its storage connection string or service-specific endpoints:

```csharp
await using var flociAz = new FlociAzBuilder("floci/floci-az:0.12.0")
.Build();

await flociAz.StartAsync();

var blobs = new BlobServiceClient(flociAz.GetConnectionString());
var keyVaultEndpoint = flociAz.GetServiceEndpoint("keyvault");
var armEndpoint = flociAz.GetEndpoint();
```

## Service compatibility

The following matrix is covered against FlociAz 0.12.0. “Real” means the test reaches the service's actual protocol or runtime, not only its ARM representation.

| Service | Verified compatibility |
|---------|------------------------|
| Blob Storage | Azure Storage SDK create, upload, and download |
| Queue Storage | Azure Storage SDK create, send, and receive |
| Table Storage | Azure Data Tables SDK create, insert, and read |
| Functions | Management lifecycle, mocked invocation, and real Node.js runtime execution |
| App Configuration | Key-value write and read |
| Cosmos DB for NoSQL | Database, container, and partitioned document lifecycle |
| Key Vault | Authenticated secret write and read |
| Event Hubs | Mocked namespace management only |
| Azure SQL Database | ARM server lifecycle in the default management-only provider |
| Azure Database for PostgreSQL | ARM lifecycle and real Npgsql query |
| Service Bus | Mocked queue/topic/subscription/rule topology and real Azure SDK AMQP send/receive |
| Azure Monitor | Workspace, collection endpoint/rule, log ingestion, and KQL query |
| AKS | Mocked ARM cluster lifecycle |
| Azure Container Instances | Mocked ARM container-group lifecycle |
| Virtual Machines | Mocked ARM VM lifecycle |
| API Management | ARM service lifecycle |
| Azure Cache for Redis | ARM lifecycle and real RESP write/read |
| Azure Container Registry | ARM lifecycle and real Registry V2 API |
| Microsoft Entra ID | OAuth client-credentials token issuance |
| Microsoft Graph | Service-principal discovery and seeded group membership |
| Communication Services Email | Send operation and inspection mailbox |
| Azure Resource Manager | Resource-group and service resource lifecycle |
| Virtual Network | ARM virtual-network lifecycle |
| Event Grid | Topic keys, event publication, and lifecycle |
| Managed Identity | User-assigned ARM lifecycle and IMDS token issuance |

### Upstream 0.12.0 boundaries

- Event Hubs AMQP is deliberately hard-coded to mocked mode upstream because Azure SDK connections reset.
- Azure Container Instances accepts `mocked=false`, but 0.12.0 still behaves as mocked mode; container-backed mode is planned upstream.
- AKS real mode starts k3s, but does not reliably transition the ARM resource from `Creating` to `Succeeded` in the containerized Testcontainers topology. The module therefore defaults it to mocked mode.
- Azure SQL's managed data plane requires explicit acceptance of the Microsoft SQL Server EULA. The module never accepts it on the user's behalf; enable and test that mode only after reviewing the license.

## Docker-backed services

Functions, PostgreSQL, Service Bus, Redis, and ACR have verified real modes. Grant FlociAz Docker access and opt individual services into real mode:

```csharp
await using var flociAz = new FlociAzBuilder("floci/floci-az:0.12.0")
.WithDockerSocket()
.WithEnvironment("FLOCI_AZ_SERVICES_FUNCTIONS_MOCKED", "false")
.WithEnvironment("FLOCI_AZ_SERVICES_POSTGRES_MOCKED", "false")
.WithEnvironment("FLOCI_AZ_SERVICES_SERVICE_BUS_MOCKED", "false")
.WithEnvironment("FLOCI_AZ_SERVICES_REDIS_MOCKED", "false")
.WithEnvironment("FLOCI_AZ_SERVICES_ACR_MOCKED", "false")
.Build();
```

!!! warning

The Docker socket provides root-equivalent access to the Docker host. Use `WithDockerSocket()` only with trusted images. Child containers and volumes are namespaced and registered with the Testcontainers Resource Reaper.

FlociAz `/connect` responses contain the child container's internal hostname and port. Resolve that pair to a host port before connecting from the test process:

```csharp
var mappedPort = await flociAz.GetSidecarMappedPublicPortAsync(
sidecarHostname,
sidecarPrivatePort);
```

Use `flociAz.Hostname` with the returned port. This works with local and remote Docker endpoints supported by Testcontainers.
1 change: 1 addition & 0 deletions docs/modules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ await moduleNameContainer.StartAsync();
| Firebird | `jacobalberty/firebird:v4.0` | [NuGet](https://www.nuget.org/packages/Testcontainers.FirebirdSql) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.FirebirdSql) |
| Firestore | `gcr.io/google.com/cloudsdktool/google-cloud-cli:446.0.1-emulators` | [NuGet](https://www.nuget.org/packages/Testcontainers.Firestore) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Firestore) |
| Floci | `floci/floci:1.5.13` | [NuGet](https://www.nuget.org/packages/Testcontainers.Floci) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Floci) |
| FlociAz | `floci/floci-az:0.12.0` | [NuGet](https://www.nuget.org/packages/Testcontainers.FlociAz) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.FlociAz) |
| Grafana | `grafana/grafana:12.2` | [NuGet](https://www.nuget.org/packages/Testcontainers.Grafana) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Grafana) |
| InfluxDB | `influxdb:2.7` | [NuGet](https://www.nuget.org/packages/Testcontainers.InfluxDb) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.InfluxDb) |
| JanusGraph | `janusgraph/janusgraph:1.0.0` | [NuGet](https://www.nuget.org/packages/Testcontainers.JanusGraph) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.JanusGraph) |
Expand Down
3 changes: 2 additions & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ nav:
- modules/pulsar.md # Apache
- modules/aspire-dashboard.md
- modules/eventhubs.md # Azure
- modules/flociaz.md # Azure
- modules/servicebus.md # Azure
- modules/clickhouse.md
- modules/db2.md
Expand All @@ -78,4 +79,4 @@ nav:
- modules/toxiproxy.md
- modules/valkey.md
- contributing.md
- contributing_docs.md
- contributing_docs.md
1 change: 1 addition & 0 deletions src/Testcontainers.FlociAz/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
root = true
122 changes: 122 additions & 0 deletions src/Testcontainers.FlociAz/FlociAzBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
namespace Testcontainers.FlociAz;

/// <inheritdoc cref="ContainerBuilder{TBuilderEntity, TContainerEntity, TConfigurationEntity}" />
[PublicAPI]
public sealed class FlociAzBuilder : ContainerBuilder<FlociAzBuilder, FlociAzContainer, FlociAzConfiguration>
{
private const string DockerSocket = "/var/run/docker.sock";

public const ushort FlociAzPort = 4577;

public const string AccountName = "devstoreaccount1";

public const string AccountKey = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==";

/// <summary>
/// Initializes a new instance of the <see cref="FlociAzBuilder" /> class.
/// </summary>
/// <param name="image">
/// The full Docker image name, including the image repository and tag
/// (e.g., <c>floci/floci-az:0.12.0</c>).
/// </param>
/// <remarks>
/// Docker image tags available at <see href="https://hub.docker.com/r/floci/floci-az/tags" />.
/// </remarks>
public FlociAzBuilder(string image)
: this(new DockerImage(image))
{
}

/// <summary>
/// Initializes a new instance of the <see cref="FlociAzBuilder" /> class.
/// </summary>
/// <param name="image">
/// An <see cref="IImage" /> instance that specifies the Docker image to be used
/// for the container builder configuration.
/// </param>
/// <remarks>
/// Docker image tags available at <see href="https://hub.docker.com/r/floci/floci-az/tags" />.
/// </remarks>
public FlociAzBuilder(IImage image)
: this(new FlociAzConfiguration())
{
DockerResourceConfiguration = Init().WithImage(image).DockerResourceConfiguration;
}

/// <summary>
/// Initializes a new instance of the <see cref="FlociAzBuilder" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
private FlociAzBuilder(FlociAzConfiguration resourceConfiguration)
: base(resourceConfiguration)
{
DockerResourceConfiguration = resourceConfiguration;
}

/// <inheritdoc />
protected override FlociAzConfiguration DockerResourceConfiguration { get; }

/// <summary>
/// Grants FlociAz access to the Docker daemon for services that use sidecar containers.
/// </summary>
/// <remarks>
/// The Docker socket provides root-equivalent access to the Docker host. Only enable it for
/// trusted images. FlociAz child containers and volumes receive a unique namespace that is
/// registered with the Testcontainers Resource Reaper.
/// </remarks>
/// <param name="dockerSocket">The host Docker socket path, or <c>null</c> to detect it.</param>
/// <returns>A configured instance of <see cref="FlociAzBuilder" />.</returns>
public FlociAzBuilder WithDockerSocket(string dockerSocket = null)
{
var endpoint = DockerResourceConfiguration.DockerEndpointAuthConfig.Endpoint;
var detectedSocket = endpoint.Scheme.Equals("unix", StringComparison.OrdinalIgnoreCase) ? endpoint.AbsolutePath : DockerSocket;
var source = dockerSocket ?? TestcontainersSettings.DockerSocketOverride ?? detectedSocket;
var resourceNamespace = "tc-" + Guid.NewGuid().ToString("N");

return WithBindMount(source, DockerSocket, AccessMode.ReadWrite)
.WithEnvironment("FLOCI_AZ_DOCKER_RESOURCE_NAMESPACE", resourceNamespace);
}

/// <inheritdoc />
public override FlociAzContainer Build()
{
Validate();
return new FlociAzContainer(DockerResourceConfiguration);
}

/// <inheritdoc />
protected override FlociAzBuilder Init()
{
return base.Init()
.WithPortBinding(FlociAzPort, true)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
.WithEnvironment("FLOCI_AZ_SERVICES_EVENT_HUB_ENABLED", "false")
.WithEnvironment("FLOCI_AZ_SERVICES_FUNCTIONS_MOCKED", "true")
.WithEnvironment("FLOCI_AZ_SERVICES_POSTGRES_MOCKED", "true")
.WithEnvironment("FLOCI_AZ_SERVICES_AKS_MOCKED", "true")
.WithEnvironment("FLOCI_AZ_SERVICES_ACR_MOCKED", "true")
.WithEnvironment("FLOCI_AZ_SERVICES_REDIS_MOCKED", "true")
.WithEnvironment("FLOCI_AZ_SERVICES_SERVICE_BUS_MOCKED", "true")
.WithEnvironment("FLOCI_AZ_SERVICES_COSMOS_MOCKED", "true")
.WithConnectionStringProvider(new FlociAzConnectionStringProvider())
.WithWaitStrategy(Wait.ForUnixContainer().UntilHttpRequestIsSucceeded(request =>
request.ForPath("/_floci/health").ForPort(FlociAzPort)));
}

/// <inheritdoc />
protected override FlociAzBuilder Clone(IResourceConfiguration<CreateContainerParameters> resourceConfiguration)
{
return Merge(DockerResourceConfiguration, new FlociAzConfiguration(resourceConfiguration));
}

/// <inheritdoc />
protected override FlociAzBuilder Clone(IContainerConfiguration resourceConfiguration)
{
return Merge(DockerResourceConfiguration, new FlociAzConfiguration(resourceConfiguration));
}

/// <inheritdoc />
protected override FlociAzBuilder Merge(FlociAzConfiguration oldValue, FlociAzConfiguration newValue)
{
return new FlociAzBuilder(new FlociAzConfiguration(oldValue, newValue));
}
}
41 changes: 41 additions & 0 deletions src/Testcontainers.FlociAz/FlociAzConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
namespace Testcontainers.FlociAz;

/// <inheritdoc cref="ContainerConfiguration" />
[PublicAPI]
public sealed class FlociAzConfiguration : ContainerConfiguration
{
/// <summary>
/// Initializes a new instance of the <see cref="FlociAzConfiguration" /> class.
/// </summary>
public FlociAzConfiguration()
{
}

/// <summary>
/// Initializes a new instance of the <see cref="FlociAzConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public FlociAzConfiguration(IResourceConfiguration<CreateContainerParameters> resourceConfiguration)
: base(resourceConfiguration)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="FlociAzConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The container configuration.</param>
public FlociAzConfiguration(IContainerConfiguration resourceConfiguration)
: base(resourceConfiguration)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="FlociAzConfiguration" /> class.
/// </summary>
/// <param name="oldValue">The old FlociAz configuration.</param>
/// <param name="newValue">The new FlociAz configuration.</param>
public FlociAzConfiguration(FlociAzConfiguration oldValue, FlociAzConfiguration newValue)
: base(oldValue, newValue)
{
}
}
13 changes: 13 additions & 0 deletions src/Testcontainers.FlociAz/FlociAzConnectionStringProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Testcontainers.FlociAz;

/// <summary>
/// Provides the FlociAz connection string.
/// </summary>
internal sealed class FlociAzConnectionStringProvider : ContainerConnectionStringProvider<FlociAzContainer, FlociAzConfiguration>
{
/// <inheritdoc />
protected override string GetHostConnectionString()
{
return Container.GetConnectionString();
}
}
Loading
Loading