File: Dcp\Model\Container.cs
Web Access
Project: src\src\Aspire.Hosting\Aspire.Hosting.csproj (Aspire.Hosting)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Net.Sockets;
using System.Text.Json.Serialization;
using k8s.Models;
 
namespace Aspire.Hosting.Dcp.Model;
 
internal sealed class ContainerSpec
{
    // Container name displayed in Docker. If not specified, the metadata name + random suffix is used.
    [JsonPropertyName("containerName")]
    public string? ContainerName { get; set; }
 
    // Image to be used to create the container
    [JsonPropertyName("image")]
    public string? Image { get; set; }
 
    // Optional configuration to build an image from a Dockerfile instead of using a pre-built image
    [JsonPropertyName("build")]
    public BuildContext? Build { get; set; }
 
    // Volumes that should be mounted into the container
    [JsonPropertyName("volumeMounts")]
    public List<VolumeMount>? VolumeMounts { get; set; }
 
    // Exposed ports
    [JsonPropertyName("ports")]
    public List<ContainerPortSpec>? Ports { get; set; }
 
    // Environment variables to be used for the container
    [JsonPropertyName("env")]
    public List<EnvVar>? Env { get; set; }
 
    // Environment files to use to populate Container environment during startup
    [JsonPropertyName("envFiles")]
    public List<string>? EnvFiles { get; set; }
 
    // Container restart policy
    [JsonPropertyName("restartPolicy")]
    public string? RestartPolicy { get; set; } = ContainerRestartPolicy.None;
 
    // Command to run in the container (entrypoint)
    [JsonPropertyName("command")]
    public string? Command { get; set; }
 
    // Arguments to pass to the command that starts the container
    [JsonPropertyName("args")]
    public List<string>? Args { get; set; }
 
    // Optional labels to apply to the container instance
    [JsonPropertyName("labels")]
    public List<ContainerLabel>? Labels { get; set; }
 
    // Additional arguments to pass to the container run command
    [JsonPropertyName("runArgs")]
    public List<string>? RunArgs { get; set; }
 
    // Should this container be created and persisted between DCP runs?
    [JsonPropertyName("persistent")]
    public bool? Persistent { get; set; }
 
    [JsonPropertyName("networks")]
    public List<ContainerNetworkConnection>? Networks { get; set; }
 
    // Should this resource be stopped?
    [JsonPropertyName("stop")]
    public bool? Stop { get; set; }
 
    /// <summary>
    /// Optional lifecycle key for the resource (used to identify changes to persistent resources requiring a restart).
    /// If unset, DCP will calculate a default lifecycle key based on a hash of various resource spec properties.
    /// </summary>
    [JsonPropertyName("lifecycleKey")]
    public string? LifecycleKey { get; set; }
}
 
internal sealed class BuildContext
{
    // The path to the directory that will serve as the root of the image build context
    [JsonPropertyName("context")]
    public string? Context { get; set; }
 
    // Optional path to a specific Dockerfile to use in the build (defaults to looking for a Dockerfile in the root Context folder)
    [JsonPropertyName("dockerfile")]
    public string? Dockerfile { get; set;}
 
    // Optional build --build-args to pass to the build command
    [JsonPropertyName("args")]
    public List<EnvVar>? Args { get; set; }
 
    // Optional build secret mounts to pass to the build command
    [JsonPropertyName("secrets")]
    public List<BuildContextSecret>? Secrets { get; set; }
 
    // Optional specific stage to use when building a multiple stage Dockerfile
    [JsonPropertyName("stage")]
    public string? Stage { get; set; }
 
    // Optional additional tags to apply to the built image
    [JsonPropertyName("tags")]
    public List<string>? Tags { get; set; }
 
    // Optional labels to apply to the built image
    [JsonPropertyName("labels")]
    public List<ContainerLabel>? Labels { get; set; }
}
 
internal sealed class BuildContextSecret
{
    // The ID of the secret (a secret can be used in a Dockerfile with `RUN --mount-type=secret,id=<id>,target=<targetpath>`)
    [JsonPropertyName("id")]
    public string? Id { get; set; }
 
    // Type of the secret, can be "env" or "file".
    [JsonPropertyName("type")]
    public string? Type { get; set; }
 
    // Value of the secret to be used in the build when the type of the secret is "env".
    [JsonPropertyName("value")]
    public string? Value { get; set; }
 
    // Path to secret file/folder that will be mounted as a build secret using --secret
    [JsonPropertyName("source")]
    public string? Source { get; set; }
}
 
internal static class VolumeMountType
{
    // A volume mount to a host directory
    public const string Bind = "bind";
 
    // A volume mount to a volume managed by the container orchestrator
    public const string Volume = "volume";
}
 
internal sealed class VolumeMount
{
    [JsonPropertyName("type")]
    public string Type { get; set; } = VolumeMountType.Bind;
 
    // Bind mounts: the host directory to mount
    // Volume mounts: name of the volume to mount
    [JsonPropertyName("source")]
    public string? Source { get; set; }
 
    // The path within the container that the mount will use
    [JsonPropertyName("target")]
    public string? Target { get; set; }
 
    // True if the mounted file system is supposed to be read-only
    [JsonPropertyName("readOnly")]
    public bool IsReadOnly { get; set; } = false;
 
    /// <summary>
    /// Health probes to be run for the container.
    /// </summary>
    [JsonPropertyName("healthProbes")]
    public List<HealthProbe>? HealthProbes { get; set; }
}
 
internal sealed class ContainerNetworkConnection
{
    // DCP Resource name of a ContainerNetwork to connect to
    // A container won't start running until it can be connected to all specified networks
    [JsonPropertyName("name")]
	public string? Name { get; set; }
 
	// Aliases of the container on the network
	// This enables container DNS resolution
    [JsonPropertyName("aliases")]
	public List<string>? Aliases { get; set; }
}
 
internal sealed class ContainerLabel
{
    // The label key
    [JsonPropertyName("key")]
    public string? Key { get; set; }
 
    // The label value
    [JsonPropertyName("value")]
    public string? Value { get; set; }
}
 
internal static class ContainerRestartPolicy
{
    // Do not automatically restart the container when it exits (default)
    public const string None = "no";
 
    // Restart only if the container exits with non-zero status
    public const string OnFailure = "on-failure";
 
    // Restart container, except if container is explicitly stopped (or container daemon is stopped/restarted)
    public const string UnlessStopped = "unless-stopped";
 
    // Always try to restart the container
    public const string Always = "always";
}
 
internal static class PortProtocol
{
    public const string TCP = "TCP";
 
    public const string UDP = "UDP";
 
    public static string Canonicalize(string protocol)
    {
        var protocolUC = protocol.ToUpperInvariant();
        switch (protocolUC)
        {
            case TCP:
            case UDP:
                return protocolUC;
            default:
                throw new ArgumentException("Port protocol value must be 'TCP' or 'UDP'");
        }
    }
 
    public static ProtocolType ToProtocolType(string protocol)
    {
        var canonical = Canonicalize(protocol);
        switch (canonical)
        {
            case TCP:
                return ProtocolType.Tcp;
            case UDP:
                return ProtocolType.Udp;
            default:
                throw new ArgumentException("Supported protocols are TCP and UDP");
        }
    }
 
    public static string FromProtocolType(ProtocolType protocolType)
    {
        switch (protocolType)
        {
            case ProtocolType.Tcp:
                return TCP;
            case ProtocolType.Udp:
                return UDP;
            default:
                throw new ArgumentException("Supported protocols are TCP and UDP");
        }
    }
}
 
internal sealed class ContainerPortSpec
{
    // Optional: If specified, this must be a valid port number, 0 < x < 65536.
    [JsonPropertyName("hostPort")]
    public int? HostPort { get; set; }
 
    // Required: This must be a valid port number, 0 < x < 65536.
    [JsonPropertyName("containerPort")]
    public int? ContainerPort { get; set; }
 
    // The network protocol to be used, defaults to TCP
    [JsonPropertyName("protocol")]
    public string Protocol { get; set; } = PortProtocol.TCP;
 
    // Optional: What host IP to bind the external port to.
    [JsonPropertyName("hostIP")]
    public string? HostIP { get; set; }
}
 
internal sealed class ContainerStatus : V1Status
{
    // Container name displayed in Docker
    [JsonPropertyName("containerName")]
    public string? ContainerName { get; set; }
 
    // Current state of the Container.
    [JsonPropertyName("state")]
    public string? State { get; set; }
 
    // ID of the Container (if an attempt to start the Container was made)
    [JsonPropertyName("containerId")]
    public string? ContainerId { get; set; }
 
    // Timestamp of the Container start attempt
    [JsonPropertyName("startupTimestamp")]
    public DateTime? StartupTimestamp { get; set; }
 
    // Timestamp when the Container was terminated last
    [JsonPropertyName("finishTimestamp")]
    public DateTime? FinishTimestamp { get; set; }
 
    // Exit code of the Container.
    // Default is -1, meaning the exit code is not known, or the container is still running.
    [JsonPropertyName("exitCode")]
    public int ExitCode { get; set; } = Conventions.UnknownExitCode;
 
    // Effective values of environment variables, after all substitutions have been applied
    [JsonPropertyName("effectiveEnv")]
    public List<EnvVar>? EffectiveEnv { get; set; }
 
    // Effective values of launch arguments to be passed to the Container, after all substitutions are applied.
    [JsonPropertyName("effectiveArgs")]
    public List<string>? EffectiveArgs { get; set; }
 
    // Any ContainerNetworks this container is attached to
    [JsonPropertyName("networks")]
    public List<string>? Networks { get; set; }
 
    /// <summary>
    /// The health status of the container <see cref="HealthStatus"/> for allowed values.
    /// </summary>
    [JsonPropertyName("healthStatus")]
    public string? HealthStatus { get; set; }
 
    /// <summary>
    /// Latest results for health probes configured for the container.
    /// </summary>
    [JsonPropertyName("healthProbeResults")]
    public List<HealthProbeResult>? HealthProbeResults { get; set;}
 
    /// <summary>
    /// The lifecycle key for the resource (used to identify changes to persistent resources requiring a restart).
    /// </summary>
    [JsonPropertyName("lifecycleKey")]
    public string? LifecycleKey { get; set; }
 
    // Note: the ContainerStatus has "Message" property that represents a human-readable information about Container state.
    // It is provided by V1Status base class.
}
 
internal static class ContainerState
{
    // Pending is the initial Container state. No attempt has been made to run the container yet.
    public const string Pending = "Pending";
 
    // Building indicates an image is being built from a Dockerfile, but a container hasn't been created yet.
    public const string Building = "Building";
 
    // Starting indicates a container is in the process of starting (pulling images, waiting to join to initial networks, etc.)
    public const string Starting = "Starting";
 
    // A start attempt was made, but it failed
    public const string FailedToStart = "FailedToStart";
 
    // Container has been started and is executing
    public const string Running = "Running";
 
    // Container is paused
    public const string Paused = "Paused";
 
    // Container finished execution
    public const string Exited = "Exited";
 
    // Container is in the process of stopping (waiting for container processes to exit, etc.).
    public const string Stopping = "Stopping";
 
    // Unknown means for some reason container state is unavailable.
    public const string Unknown = "Unknown";
}
 
internal sealed class Container : CustomResource<ContainerSpec, ContainerStatus>
{
    [JsonConstructor]
    public Container(ContainerSpec spec) : base(spec) { }
 
    public static Container Create(string name, string image)
    {
        var c = new Container(new ContainerSpec { Image = image });
 
        c.Kind = Dcp.ContainerKind;
        c.ApiVersion = Dcp.GroupVersion.ToString();
        c.Metadata.Name = name;
        c.Metadata.NamespaceProperty = string.Empty;
 
        return c;
    }
 
    public bool LogsAvailable =>
        this.Status?.State == ContainerState.Starting
        || this.Status?.State == ContainerState.Building
        || this.Status?.State == ContainerState.Running
        || this.Status?.State == ContainerState.Paused
        || this.Status?.State == ContainerState.Stopping
        || this.Status?.State == ContainerState.Exited
        || (this.Status?.State == ContainerState.FailedToStart && this.Status?.ContainerId is not null);
}