Table of Contents

Dependency injection

The OpenShell.Net.DependencyInjection package registers OpenShell clients with Microsoft.Extensions.DependencyInjection, for ASP.NET Core and Generic Host apps.

dotnet add package OpenShell.Net.DependencyInjection

Register from configuration

AddOpenShell binds OpenShellOptions from a configuration section and registers a singleton IOpenShellClient, plus its per-area clients (IHealthClient, ISandboxClient, IExecClient, IWorkspaceClient, ISandboxTemplateClient, IProviderClient, IConfigClient, IPolicyClient, ISandboxWatchClient). The options are validated when the host starts, so a bad address, an unreadable certificate, or a token headed for plaintext HTTP fails at startup instead of on the first call.

var builder = WebApplication.CreateBuilder(args);

// Binds the "OpenShell" section (see appsettings.json) and validates it when the host starts.
builder.Services.AddOpenShell(builder.Configuration.GetSection("OpenShell"));
builder.Services.AddHealthChecks().AddOpenShellHealthCheck();

var app = builder.Build();
app.MapHealthChecks("/healthz");

app.MapGet("/sandboxes", async (ISandboxClient sandboxes, CancellationToken cancellationToken) =>
{
    var list = await sandboxes.List().ToListAsync(cancellationToken);
    return list.Select(s => new { s.Name, Phase = s.Phase.ToString() });
});

app.Run();
{
  "OpenShell": {
    "Address": "https://gateway.example.com",
    "Workspace": "team-a",
    "CaCertificatePath": "/etc/openshell/ca.crt",
    "ClientCertificatePath": "/etc/openshell/client.crt",
    "ClientKeyPath": "/etc/openshell/client.key",
    "DefaultTimeout": "00:00:30",
    "RouteToOwnerReplica": true,
    "Retry": {
      "MaxAttempts": 6,
      "MaxBackoff": "00:00:10"
    }
  }
}
Key Meaning
Address The gateway URL. Required.
Workspace The default workspace (default).
Token A static bearer token. Keep it in user secrets, environment variables, or a vault, not in the JSON file.
CaCertificatePath A PEM file of CA certificates that the gateway certificate must chain to.
ClientCertificatePath, ClientKeyPath A PEM client certificate and key for mTLS.
InsecureSkipVerify Skip server certificate validation (development only).
AllowInsecureAuth Allow credentials over plaintext HTTP to a non-loopback host.
DefaultTimeout Deadline for unary calls, as a TimeSpan (00:00:30).
Retry MaxAttempts, InitialBackoff, MaxBackoff, BackoffMultiplier, Jitter, RetryableStatusCodes; unset values keep the defaults.
RouteToOwnerReplica Owner routing (true).

The client is created on first use and disposed with the container. services.AddHealthChecks().AddOpenShellHealthCheck() adds a health check that calls the gateway health RPC; pass gatewayName to check a named registration.

Configure in code

services.AddOpenShell(options =>
{
    options.Address = new Uri("https://gateway.example.com");
    options.Workspace = "team-a";
    options.Token = Environment.GetEnvironmentVariable("OPENSHELL_TOKEN");
    options.DefaultTimeout = TimeSpan.FromSeconds(30);
    options.Retry = new RetryOptions { MaxAttempts = 6 };
});

OpenShellOptions.Auth and OpenShellOptions.Tls take any IAuthProvider or TlsOptions and win over Token and the certificate paths. The AddOpenShell(name, (provider, options) => ...) overload configures them from other services.

The fluent builder

AddOpenShellClient returns an IOpenShellClientBuilder with a method for each setting. A name registers a separate, keyed client for each gateway:

services.AddOpenShellClient("prod")
    .WithAddress("https://gateway.example.com")
    .WithWorkspace("team-a")
    .WithClientCredentials(
        "https://idp.example.com/realms/openshell",
        "openshell-service",
        cancellationToken => ValueTask.FromResult(Environment.GetEnvironmentVariable("OPENSHELL_OIDC_CLIENT_SECRET")!),
        "sandbox:read",
        "sandbox:write")
    .WithCaCertificate("/etc/openshell/ca.crt")
    .WithTimeout(TimeSpan.FromSeconds(30))
    .WithRetry(retry => retry.MaxAttempts = 6);

services.AddOpenShellClient("local")
    .WithAddress("http://127.0.0.1:8080")
    .WithoutRetry();

WithClientCredentials registers an OIDC ClientCredentialsAuth as a container singleton, so the container disposes it. Other methods: WithBearerToken, WithAuth, WithCaCertificate, WithClientCertificate, WithTls, WithInsecureTls, AllowInsecureAuth, WithTimeout, WithRetry, WithoutRetry, and BindConfiguration.

// Build the auth provider from other services, for example a token cache.
services.AddOpenShellClient()
    .WithAddress("https://gateway.example.com")
    .WithAuth(provider => provider.GetRequiredService<ITokenCache>().AsAuthProvider());

Named clients

A named client is a keyed IOpenShellClient. Inject it with [FromKeyedServices("name")], or resolve it at run time from IOpenShellClientFactory. The per-area clients are registered for the default (unnamed) gateway only.

internal sealed class SandboxJanitor(
    [FromKeyedServices("prod")] IOpenShellClient prod,
    IOpenShellClientFactory factory)
{
    public async Task CleanUpAsync(CancellationToken cancellationToken)
    {
        var expired = new ListOptions { LabelSelector = "ttl=expired" };
        await foreach (var sandbox in prod.Sandboxes.List(expired).WithCancellation(cancellationToken))
        {
            await prod.Sandboxes.DeleteAsync(sandbox.Name, new DeleteOptions { AllowMissing = true }, cancellationToken);
        }

        // Or resolve a client by name at run time. The factory creates one client per name and owns it:
        // do not dispose it.
        var local = factory.Create("local");
        Console.WriteLine(await local.Health.CheckAsync(cancellationToken));
    }
}

Logging

The registered clients use the container's ILoggerFactory; see Observability.