Contributing a module
Experimental. This project is an experiment: the public API, defaults and packaging can change between prereleases, and there is no production support guarantee.
Contributing a module
Section titled “Contributing a module”How to add a new service module to Purview.Containers.
src/src/MyService/ MyService.csproj -> PackageId Purview.Containers.MyService MyServiceConfiguration.cs -> immutable record, module fields MyServiceBuilder.cs -> fluent builder MyServiceContainer.cs -> container, connection string / endpointssrc/tests/MyService.UnitTests/src/tests/MyService.IntegrationTests/- Reference the abstractions:
<ProjectReference Include="../Core/Core.csproj" />. A module never references a backend package; the Purview SDK supplies the pack defaults and theInternalsVisibleToentries for the module’s test projects. - Configuration record — derive from
ContainerConfiguration, add module fields; credentials asSecret:
public sealed record MyServiceConfiguration : ContainerConfiguration{ public string Username { get; init; } = "default"; public Secret Password { get; init; } = Secret.From("default");}- Builder — subclass
ContainerBuilder<TBuilder, TContainer, TConfiguration>; default image/ports in the constructor; overrideBuildConfiguration()to snapshot module fields, andCreateContainer(...):
public class MyServiceBuilder : ContainerBuilder<MyServiceBuilder, MyServiceContainer, MyServiceConfiguration>{ public const ushort DefaultPort = 9000;
public MyServiceBuilder() { WithImage("myservice:latest").WithPortBinding(DefaultPort, assignRandomHostPort: true); }
public MyServiceBuilder WithPassword(string password) { this.password = Secret.From(password); WithEnvironment("MYSERVICE_PASSWORD", password); return this; }
protected override MyServiceConfiguration BuildConfiguration() { MyServiceConfiguration configuration = base.BuildConfiguration(); return configuration with { Username = username, Password = password, WaitStrategies = configuration.WaitStrategies.Count > 0 ? configuration.WaitStrategies : new[] { (IWaitStrategy)Wait.ForTcpPort(DefaultPort) }, }; }
protected override MyServiceContainer CreateContainer(MyServiceConfiguration configuration) => new(configuration, Backend);}-
Container — expose
GetConnectionString()/ endpoints usingGetMappedPublicPort, built from runtime state (never cached before start). Add a provider so the polymorphicIContainer.GetConnectionString()works, and wire it in the builder constructor:sealed class MyServiceConnectionStringProvider : ContainerConnectionStringProvider<MyServiceContainer, MyServiceConfiguration>{protected override string GetHostConnectionString() => Container.GetConnectionString();}// in the builder constructor:WithImage("myservice:latest").WithPortBinding(DefaultPort, assignRandomHostPort: true).WithConnectionStringProvider(new MyServiceConnectionStringProvider()); -
Wait strategy — prefer verifying the service itself (exec a readiness command or a host client connection), not merely that a TCP port is open. See Wait Strategies. Default waits are applied in
BuildConfiguration()unless the caller supplied their own. -
Secrets — passwords/usernames go into a
Secret-typed field; configurationToString()redacts sensitive values automatically. -
Tests — unit tests use
BuildConfigurationForTesting()(internal test hook on the module builder); integration tests use TUnit, the sharedWslcTest.SkipIfUnavailableAsync()helper fromsrc/tests/SharedTestingFramework, and the real client.
Conventions
Section titled “Conventions”- Package ID
Purview.Containers.MyService(namespace prefixPurview). - Module is thin: no session management, no port allocation logic, no output buffering.
- Default networking is
Bridged(from the core defaults); ports use native random allocation unless a fixed host port is explicitly requested. - The module is backend-neutral (
net10.0, referencesPurview.Containers.Coreonly) and therefore does not bring a backend or inherit its consumer requirements. A consumer references the module and a backend package (Purview.Containers.Wslfor WSLC,Purview.Containers.Dockerfor Docker). See Consumer Requirements. - If a Testcontainers capability has no WSLC equivalent (e.g. UDP, TTY,
--user), throwContainerNotSupportedExceptionat build/validation rather than silently ignoring it.