| File: Dcp\Model\Executable.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. namespace Aspire.Hosting.Dcp.Model; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; using Aspire.Hosting.ApplicationModel; using k8s.Models; #pragma warning disable ASPIREEXTENSION001 // Launch configuration types are experimental. internal sealed class ExecutableSpec { /// <summary> /// Path to Executable binary /// </summary> [JsonPropertyName("executablePath")] public string? ExecutablePath { get; set; } /// <summary> /// The working directory for the Executable /// </summary> [JsonPropertyName("workingDirectory")] public string? WorkingDirectory { get; set; } /// <summary> /// Launch arguments to be passed to the Executable /// </summary> [JsonPropertyName("args")] public List<string>? Args { get; set; } /// <summary> /// Environment variables to be set for the Executable /// </summary> [JsonPropertyName("env")] public List<EnvVar>? Env { get; set; } /// <summary> /// Environment files to use to populate Executable environment during startup. /// </summary> [JsonPropertyName("envFiles")] public List<string>? EnvFiles { get; set; } /// <summary> /// The execution type for the Executable /// </summary> [JsonPropertyName("executionType")] public string? ExecutionType { get; set; } /// <summary> /// Fallback execution types in case the primary execution type is not supported or startup fails. /// </summary> [JsonPropertyName("fallbackExecutionTypes")] public List<string>? FallbackExecutionTypes { get; set; } /// <summary> /// Health probes to be run for the Executable. /// </summary> [JsonPropertyName("healthProbes")] public List<HealthProbe>? HealthProbes { get; set; } /// <summary> /// Should this Executable be created and persisted between DCP runs? /// Persistent executables are only compatible with the Process execution type. /// </summary> [JsonPropertyName("persistent")] public bool? Persistent { get; set; } /// <summary> /// Optional parent process PID used to scope persistent Executable cleanup to a process lifecycle. /// When set, <see cref="MonitorTimestamp"/> must also be set and <see cref="Persistent"/> must be true. /// </summary> [JsonPropertyName("monitorPid")] public int? MonitorPid { get; set; } /// <summary> /// Optional parent process identity timestamp used with <see cref="MonitorPid"/> to guard against PID reuse. /// </summary> [JsonPropertyName("monitorTimestamp")] [JsonConverter(typeof(KubernetesMicroTimeJsonConverter))] public DateTime? MonitorTimestamp { get; set; } /// <summary> /// Should this resource be started? If set to false, we will not attempt /// to start the resource until Start is set to true (or null). /// </summary> [JsonPropertyName("start")] public bool? Start { get; set; } /// <summary> /// Should this resource be stopped? /// </summary> [JsonPropertyName("stop")] public bool? Stop { get; set; } /// <summary> /// Controls how ambient environment variables are applied to the Executable. /// </summary> [JsonPropertyName("ambientEnvironment")] public AmbientEnvironment? AmbientEnvironment { get; set; } /// <summary> /// Public PEM certificates to be configured for the Executable. /// </summary> [JsonPropertyName("pemCertificates")] public ExecutablePemCertificates? PemCertificates { get; set; } /// <summary> /// Terminal configuration for interactive PTY access. /// When set, DCP allocates a pseudo-terminal for the process and forwards /// I/O over a Unix domain socket using <see href="https://github.com/dotnet/hex1b">Hex1b</see>'s HMP v1 framing. /// </summary> [JsonPropertyName("terminal")] public TerminalSpec? Terminal { get; set; } } internal sealed class AmbientEnvironment { /// <summary> /// Gets or sets the default behavior for applying ambient environment variables. /// </summary> [JsonPropertyName("behavior")] public string? Behavior { get; set; } = AmbientEnvironmentBehavior.Inherit; } internal static class AmbientEnvironmentBehavior { /// <summary> /// The Executable will inherit the environment of the Aspire app host process. /// This is the default behavior. /// </summary> public const string Inherit = "Inherit"; /// <summary> /// The Executable will not inherit any environment variables from the Aspire app host process. /// </summary> public const string DoNotInherit = "DoNotInherit"; } internal static class ExecutionType { /// <summary> /// Executable will be run directly by the controller, as a child process /// </summary> public const string Process = "Process"; /// <summary> /// Executable will be run via an IDE such as Visual Studio or Visual Studio Code. /// </summary> public const string IDE = "IDE"; } internal sealed class ExecutablePemCertificates { // The list of public PEM encoded certificates for the executable. [JsonPropertyName("certificates")] public List<PemCertificate>? Certificates { get; set; } // Indicates whether to continue starting the Executable if there are issues setting up any certificates for // the executable. [JsonPropertyName("continueOnError")] public bool ContinueOnError { get; set; } } internal sealed record ExecutableStatus : V1Status { /// <summary> /// The execution ID is the identifier for the actual-state counterpart of the Executable. /// For ExecutionType == Process it is the process ID. Process IDs will be eventually reused by OS, /// but a combination of process ID and startup timestamp is unique for each Executable instance. /// For ExecutionType == IDE it is the IDE session ID. /// </summary> [JsonPropertyName("executionID")] public string? ExecutionID { get; set; } /// <summary> /// The process ID of the Executable. /// </summary> [JsonPropertyName("pid")] public int ProcessId { get; set; } /// <summary> /// The current state of the process/IDE session started for this executable /// </summary> [JsonPropertyName("state")] public string? State { get; set; } = ExecutableState.Unknown; /// <summary> /// Start (attempt) timestamp. /// </summary> [JsonPropertyName("startupTimestamp")] public DateTime? StartupTimestamp { get; set; } /// <summary> /// The time when the replica finished execution /// </summary> [JsonPropertyName("finishTimestamp")] public DateTime? FinishTimestamp { get; set; } /// <summary> /// Exit code of the process associated with the Executable. /// The value is equal to UnknownExitCode if the Executable was not started, is still running, or the exit code is not available. /// </summary> [JsonPropertyName("exitCode")] public int? ExitCode { get; set; } /// <summary> /// The path of a temporary file that contains captured standard output data from the Executable process. /// </summary> [JsonPropertyName("stdOutFile")] public string? StdOutFile { get; set; } /// <summary> /// The path of a temporary file that contains captured standard error data from the Executable process. /// </summary> [JsonPropertyName("stdErrFile")] public string? StdErrFile { get; set; } /// <summary> /// Effective values of environment variables, after all substitutions have been applied /// </summary> [JsonPropertyName("effectiveEnv")] public List<EnvVar>? EffectiveEnv { get; set; } /// <summary> /// Effective values of launch arguments to be passed to the Executable, after all substitutions are applied. /// </summary> [JsonPropertyName("effectiveArgs")] public List<string>? EffectiveArgs { get; set; } /// <summary> /// The health status of the Executable <see cref="HealthStatus"/> for allowed values. /// </summary> [JsonPropertyName("healthStatus")] public string? HealthStatus { get; set; } /// <summary> /// Latest results for health probes configured for the Executable. /// </summary> [JsonPropertyName("healthProbeResults")] public List<HealthProbeResult>? HealthProbeResults { get; set; } } internal static class ExecutableState { /// <summary> /// Executable was successfully started and was running last time we checked. /// </summary> public const string Running = "Running"; /// <summary> /// Terminated means the Executable was killed by the controller (e.g. as a result of scale-down, or object deletion). /// </summary> public const string Terminated = "Terminated"; /// <summary> /// Failed to start means the Executable could not be started (e.g. because of invalid path to program file). /// </summary> public const string FailedToStart = "FailedToStart"; /// <summary> /// Finished means the Executable ran to completion. /// </summary> public const string Finished = "Finished"; /// <summary> /// Unknown means we are not tracking the actual-state counterpart of the Executable (process or IDE run session). /// As a result, we do not know whether it already finished, and what is the exit code, if any. /// This can happen if a controller launches a process and then terminates. /// When a new controller instance comes online, it may see non-zero ExecutionID Status, /// but it does not track the corresponding process or IDE session. /// </summary> public const string Unknown = "Unknown"; // The Executable has been scheduled to launch, but we will need to re-evaluate its state in a subsequent // reconciliation loop. public const string Starting = "Starting"; // Executable is stopping (DCP is trying to stop the process) public const string Stopping = "Stopping"; } internal sealed class Executable : CustomResource<ExecutableSpec, ExecutableStatus>, IKubernetesStaticMetadata { public const string LaunchConfigurationsAnnotation = "executable.usvc-dev.developer.microsoft.com/launch-configurations"; [JsonConstructor] public Executable(ExecutableSpec spec) : base(spec) { } public static Executable Create(string name, string executablePath) { var exe = new Executable(new ExecutableSpec { ExecutablePath = executablePath, }); exe.Kind = Dcp.ExecutableKind; exe.ApiVersion = Dcp.GroupVersion.ToString(); exe.Metadata.Name = name; exe.Metadata.NamespaceProperty = string.Empty; return exe; } public bool LogsAvailable => !string.IsNullOrEmpty(this.Status?.State); public bool TryGetProjectLaunchConfiguration([NotNullWhen(true)] out ProjectLaunchConfiguration? launchConfiguration) { launchConfiguration = null; if (this.TryGetAnnotationAsObjectList(LaunchConfigurationsAnnotation, out List<ProjectLaunchConfiguration>? launchConfigurations)) { // Aspire currently supports only one project launch configuration per Executable. launchConfiguration = launchConfigurations?.FirstOrDefault(); } return launchConfiguration is not null; } public static string ObjectKind => Dcp.ExecutableKind; }