File: ApplicationModel\Docker\DockerfileStatements.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.
 
#pragma warning disable ASPIREDOCKERFILEBUILDER001 // Type is for evaluation purposes only and is subject to change or removal in future updates
 
using System.Text.Encodings.Web;
using System.Text.Json;
 
namespace Aspire.Hosting.ApplicationModel.Docker;
 
/// <summary>
/// Represents a FROM statement in a Dockerfile.
/// </summary>
internal class DockerfileFromStatement : DockerfileStatement
{
    private readonly string _imageReference;
    private readonly string? _stageName;
 
    public DockerfileFromStatement(string imageReference, string? stageName = null)
    {
        _imageReference = imageReference;
        _stageName = stageName;
    }
 
    public override async Task WriteStatementAsync(StreamWriter writer, CancellationToken cancellationToken = default)
    {
        var statement = _stageName is not null 
            ? $"FROM {_imageReference} AS {_stageName}" 
            : $"FROM {_imageReference}";
        
        await writer.WriteLineAsync(statement).ConfigureAwait(false);
    }
}
 
/// <summary>
/// Represents a WORKDIR statement in a Dockerfile.
/// </summary>
internal class DockerfileWorkDirStatement : DockerfileStatement
{
    private readonly string _path;
 
    public DockerfileWorkDirStatement(string path)
    {
        _path = path;
    }
 
    public override async Task WriteStatementAsync(StreamWriter writer, CancellationToken cancellationToken = default)
    {
        await writer.WriteLineAsync($"WORKDIR {_path}").ConfigureAwait(false);
    }
}
 
/// <summary>
/// Represents a RUN statement in a Dockerfile.
/// </summary>
internal class DockerfileRunStatement : DockerfileStatement
{
    private readonly string _command;
 
    public DockerfileRunStatement(string command)
    {
        _command = command;
    }
 
    public override async Task WriteStatementAsync(StreamWriter writer, CancellationToken cancellationToken = default)
    {
        await writer.WriteLineAsync($"RUN {_command}").ConfigureAwait(false);
    }
}
 
/// <summary>
/// Represents a COPY statement in a Dockerfile.
/// </summary>
internal class DockerfileCopyStatement : DockerfileStatement
{
    private readonly string _source;
    private readonly string _destination;
 
    public DockerfileCopyStatement(string source, string destination)
    {
        _source = source;
        _destination = destination;
    }
 
    public override async Task WriteStatementAsync(StreamWriter writer, CancellationToken cancellationToken = default)
    {
        await writer.WriteLineAsync($"COPY {_source} {_destination}").ConfigureAwait(false);
    }
}
 
/// <summary>
/// Represents a COPY --from statement in a Dockerfile.
/// </summary>
internal class DockerfileCopyFromStatement : DockerfileStatement
{
    private readonly string _stage;
    private readonly string _source;
    private readonly string _destination;
 
    public DockerfileCopyFromStatement(string stage, string source, string destination)
    {
        _stage = stage;
        _source = source;
        _destination = destination;
    }
 
    public override async Task WriteStatementAsync(StreamWriter writer, CancellationToken cancellationToken = default)
    {
        await writer.WriteLineAsync($"COPY --from={_stage} {_source} {_destination}").ConfigureAwait(false);
    }
}
 
/// <summary>
/// Represents an ENV statement in a Dockerfile.
/// </summary>
internal class DockerfileEnvStatement : DockerfileStatement
{
    private readonly string _name;
    private readonly string _value;
 
    public DockerfileEnvStatement(string name, string value)
    {
        _name = name;
        _value = value;
    }
 
    public override async Task WriteStatementAsync(StreamWriter writer, CancellationToken cancellationToken = default)
    {
        await writer.WriteLineAsync($"ENV {_name}={_value}").ConfigureAwait(false);
    }
}
 
/// <summary>
/// Represents an EXPOSE statement in a Dockerfile.
/// </summary>
internal class DockerfileExposeStatement : DockerfileStatement
{
    private readonly int _port;
 
    public DockerfileExposeStatement(int port)
    {
        _port = port;
    }
 
    public override async Task WriteStatementAsync(StreamWriter writer, CancellationToken cancellationToken = default)
    {
        await writer.WriteLineAsync($"EXPOSE {_port}").ConfigureAwait(false);
    }
}
 
/// <summary>
/// Represents a CMD statement in a Dockerfile.
/// </summary>
internal class DockerfileCmdStatement : DockerfileStatement
{
    private readonly string[] _command;
 
    public DockerfileCmdStatement(string[] command)
    {
        _command = command;
    }
 
    public override async Task WriteStatementAsync(StreamWriter writer, CancellationToken cancellationToken = default)
    {
        var options = new JsonSerializerOptions
        {
            Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
        };
        var commandJson = JsonSerializer.Serialize(_command, options);
        await writer.WriteLineAsync($"CMD {commandJson}").ConfigureAwait(false);
    }
}
 
/// <summary>
/// Represents a USER statement in a Dockerfile.
/// </summary>
internal class DockerfileUserStatement : DockerfileStatement
{
    private readonly string _user;
 
    public DockerfileUserStatement(string user)
    {
        _user = user;
    }
 
    public override async Task WriteStatementAsync(StreamWriter writer, CancellationToken cancellationToken = default)
    {
        await writer.WriteLineAsync($"USER {_user}").ConfigureAwait(false);
    }
}
 
/// <summary>
/// Represents an ARG statement in a Dockerfile.
/// </summary>
internal class DockerfileArgStatement : DockerfileStatement
{
    private readonly string _name;
    private readonly string? _defaultValue;
 
    public DockerfileArgStatement(string name)
    {
        _name = name;
        _defaultValue = null;
    }
 
    public DockerfileArgStatement(string name, string defaultValue)
    {
        _name = name;
        _defaultValue = defaultValue;
    }
 
    public override async Task WriteStatementAsync(StreamWriter writer, CancellationToken cancellationToken = default)
    {
        var statement = _defaultValue is not null 
            ? $"ARG {_name}={_defaultValue}" 
            : $"ARG {_name}";
        
        await writer.WriteLineAsync(statement).ConfigureAwait(false);
    }
}
 
/// <summary>
/// Represents an ENTRYPOINT statement in a Dockerfile.
/// </summary>
internal class DockerfileEntrypointStatement : DockerfileStatement
{
    private readonly string[] _command;
 
    public DockerfileEntrypointStatement(string[] command)
    {
        _command = command;
    }
 
    public override async Task WriteStatementAsync(StreamWriter writer, CancellationToken cancellationToken = default)
    {
        var options = new JsonSerializerOptions
        {
            Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
        };
        var commandJson = JsonSerializer.Serialize(_command, options);
        await writer.WriteLineAsync($"ENTRYPOINT {commandJson}").ConfigureAwait(false);
    }
}
 
/// <summary>
/// Represents a RUN statement with mount options in a Dockerfile.
/// </summary>
internal class DockerfileRunWithMountsStatement : DockerfileStatement
{
    private readonly string _command;
    private readonly List<string> _mounts;
 
    public DockerfileRunWithMountsStatement(string command, IEnumerable<string> mounts)
    {
        _command = command;
        _mounts = mounts.ToList();
    }
 
    public override async Task WriteStatementAsync(StreamWriter writer, CancellationToken cancellationToken = default)
    {
        var mountOptions = string.Join(" ", _mounts.Select(m => $"--mount={m}"));
        await writer.WriteLineAsync($"RUN {mountOptions} \\\n    {_command}").ConfigureAwait(false);
    }
}
 
/// <summary>
/// Represents an empty line in a Dockerfile.
/// </summary>
internal class DockerfileEmptyLineStatement : DockerfileStatement
{
    public override async Task WriteStatementAsync(StreamWriter writer, CancellationToken cancellationToken = default)
    {
        await writer.WriteLineAsync().ConfigureAwait(false);
    }
}
 
/// <summary>
/// Represents a COPY statement with chown option in a Dockerfile.
/// </summary>
internal class DockerfileCopyWithChownStatement : DockerfileStatement
{
    private readonly string _source;
    private readonly string _destination;
    private readonly string _chown;
 
    public DockerfileCopyWithChownStatement(string source, string destination, string chown)
    {
        _source = source;
        _destination = destination;
        _chown = chown;
    }
 
    public override async Task WriteStatementAsync(StreamWriter writer, CancellationToken cancellationToken = default)
    {
        await writer.WriteLineAsync($"COPY --chown={_chown} {_source} {_destination}").ConfigureAwait(false);
    }
}
 
/// <summary>
/// Represents a COPY --from statement with chown option in a Dockerfile.
/// </summary>
internal class DockerfileCopyFromWithChownStatement : DockerfileStatement
{
    private readonly string _stage;
    private readonly string _source;
    private readonly string _destination;
    private readonly string _chown;
 
    public DockerfileCopyFromWithChownStatement(string stage, string source, string destination, string chown)
    {
        _stage = stage;
        _source = source;
        _destination = destination;
        _chown = chown;
    }
 
    public override async Task WriteStatementAsync(StreamWriter writer, CancellationToken cancellationToken = default)
    {
        await writer.WriteLineAsync($"COPY --from={_stage} --chown={_chown} {_source} {_destination}").ConfigureAwait(false);
    }
}
 
/// <summary>
/// Represents a comment in a Dockerfile.
/// </summary>
internal class DockerfileCommentStatement : DockerfileStatement
{
    private readonly string _comment;
 
    public DockerfileCommentStatement(string comment)
    {
        _comment = comment;
    }
 
    public override async Task WriteStatementAsync(StreamWriter writer, CancellationToken cancellationToken = default)
    {
        // Split by newlines to handle multi-line comments
        var lines = _comment.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
        
        foreach (var line in lines)
        {
            await writer.WriteLineAsync($"# {line}").ConfigureAwait(false);
        }
    }
}