Skip to content

Commit

Permalink
Add support for using a pinned Digest for Container base images (dotn…
Browse files Browse the repository at this point in the history
…et#44461)

Co-authored-by: Chet Husk <chusk3@gmail.com>
  • Loading branch information
BeyondEvil and baronfel committed Dec 20, 2024
1 parent bfa1bbc commit 6acc5b3
Show file tree
Hide file tree
Showing 16 changed files with 100 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ internal static async Task<int> ContainerizeAsync(
string baseRegistry,
string baseImageName,
string baseImageTag,
string? baseImageDigest,
string[] entrypoint,
string[] entrypointArgs,
string[] defaultArgs,
Expand Down Expand Up @@ -47,7 +48,7 @@ internal static async Task<int> ContainerizeAsync(
bool isLocalPull = string.IsNullOrEmpty(baseRegistry);
RegistryMode sourceRegistryMode = baseRegistry.Equals(outputRegistry, StringComparison.InvariantCultureIgnoreCase) ? RegistryMode.PullFromOutput : RegistryMode.Pull;
Registry? sourceRegistry = isLocalPull ? null : new Registry(baseRegistry, logger, sourceRegistryMode);
SourceImageReference sourceImageReference = new(sourceRegistry, baseImageName, baseImageTag);
SourceImageReference sourceImageReference = new(sourceRegistry, baseImageName, baseImageTag, baseImageDigest);

DestinationImageReference destinationImageReference = DestinationImageReference.CreateFromSettings(
imageName,
Expand All @@ -65,14 +66,14 @@ internal static async Task<int> ContainerizeAsync(
var ridGraphPicker = new RidGraphManifestPicker(ridGraphPath);
imageBuilder = await registry.GetImageManifestAsync(
baseImageName,
baseImageTag,
sourceImageReference.Reference,
containerRuntimeIdentifier,
ridGraphPicker,
cancellationToken).ConfigureAwait(false);
}
catch (RepositoryNotFoundException)
{
logger.LogError(Resource.FormatString(nameof(Strings.RepositoryNotFound), baseImageName, baseImageTag, registry.RegistryName));
logger.LogError(Resource.FormatString(nameof(Strings.RepositoryNotFound), baseImageName, baseImageTag, baseImageDigest, registry.RegistryName));
return 1;
}
catch (UnableToAccessRepositoryException)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public static class Properties
public static readonly string ContainerBaseRegistry = nameof(ContainerBaseRegistry);
public static readonly string ContainerBaseName = nameof(ContainerBaseName);
public static readonly string ContainerBaseTag = nameof(ContainerBaseTag);
public static readonly string ContainerBaseDigest = nameof(ContainerBaseDigest);

public static readonly string ContainerGenerateLabels = nameof(ContainerGenerateLabels);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseImageName.get -> string!
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseImageName.set -> void
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseImageTag.get -> string!
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseImageTag.set -> void
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseImageDigest.get -> string!
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseImageDigest.set -> void
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseRegistry.get -> string!
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseRegistry.set -> void
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ContainerEnvironmentVariables.get -> Microsoft.Build.Framework.ITaskItem![]!
Expand Down Expand Up @@ -113,6 +115,7 @@ Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ParseContainerProp
Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ParsedContainerImage.get -> string!
Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ParsedContainerRegistry.get -> string!
Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ParsedContainerTag.get -> string!
Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ParsedContainerDigest.get -> string!
override Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.Execute() -> bool
static Microsoft.NET.Build.Containers.ContainerHelpers.TryParsePort(string! input, out Microsoft.NET.Build.Containers.Port? port, out Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError? error) -> bool
static Microsoft.NET.Build.Containers.ContainerHelpers.TryParsePort(string? portNumber, string? portType, out Microsoft.NET.Build.Containers.Port? port, out Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError? error) -> bool
Expand All @@ -136,4 +139,4 @@ Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.SdkVersion.get
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.SdkVersion.set -> void
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.TargetFrameworkVersion.get -> string!
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.TargetFrameworkVersion.set -> void
override Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.Execute() -> bool
override Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.Execute() -> bool
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseImageName.get -> string!
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseImageName.set -> void
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseImageTag.get -> string!
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseImageTag.set -> void
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseImageDigest.get -> string!
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseImageDigest.set -> void
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseRegistry.get -> string!
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseRegistry.set -> void
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.Cancel() -> void
Expand Down Expand Up @@ -262,6 +264,7 @@ Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ParseContainerProp
Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ParsedContainerImage.get -> string!
Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ParsedContainerRegistry.get -> string!
Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ParsedContainerTag.get -> string!
Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ParsedContainerDigest.get -> string!
override Microsoft.NET.Build.Containers.Tasks.CreateNewImage.Execute() -> bool
override Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.Execute() -> bool
static Microsoft.NET.Build.Containers.ContainerHelpers.TryParsePort(string! input, out Microsoft.NET.Build.Containers.Port? port, out Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError? error) -> bool
Expand Down Expand Up @@ -321,4 +324,4 @@ static Microsoft.NET.Build.Containers.ImageIndexV1.operator ==(Microsoft.NET.Bui
override Microsoft.NET.Build.Containers.ImageIndexV1.GetHashCode() -> int
~override Microsoft.NET.Build.Containers.ImageIndexV1.Equals(object obj) -> bool
Microsoft.NET.Build.Containers.ImageIndexV1.Equals(Microsoft.NET.Build.Containers.ImageIndexV1 other) -> bool
Microsoft.NET.Build.Containers.ImageIndexV1.Deconstruct(out int schemaVersion, out string! mediaType, out Microsoft.NET.Build.Containers.PlatformSpecificOciManifest[]! manifests) -> void
Microsoft.NET.Build.Containers.ImageIndexV1.Deconstruct(out int schemaVersion, out string! mediaType, out Microsoft.NET.Build.Containers.PlatformSpecificOciManifest[]! manifests) -> void
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,35 @@
namespace Microsoft.NET.Build.Containers;

/// <summary>
/// Represents a reference to a Docker image. A reference is made of a registry, a repository (aka the image name) and a tag.
/// Represents a reference to a Docker image. A reference is made of a registry, a repository (aka the image name) and a tag or digest.
/// </summary>
internal readonly record struct SourceImageReference(Registry? Registry, string Repository, string Tag)
internal readonly record struct SourceImageReference(Registry? Registry, string Repository, string? Tag, string? Digest)
{
public override string ToString()
{
if (Registry is { } reg)
string sourceImageReference = Repository;

if (Registry is { } reg)
{
return $"{reg.RegistryName}/{Repository}:{Tag}";
}
else
sourceImageReference = $"{reg.RegistryName}/{sourceImageReference}";
}

if (!string.IsNullOrEmpty(Tag))
{
return RepositoryAndTag;
sourceImageReference = $"{sourceImageReference}:{Tag}";
}

if (!string.IsNullOrEmpty(Digest))
{
sourceImageReference = $"{sourceImageReference}@{Digest}";
}

return sourceImageReference;
}

/// <summary>
/// Returns the repository and tag as a formatted string. Used in cases
/// </summary>
public readonly string RepositoryAndTag => $"{Repository}:{Tag}";
public string Reference
=> !string.IsNullOrEmpty(Digest) ? Digest : !string.IsNullOrEmpty(Tag) ? Tag : "latest";
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,14 @@ partial class CreateNewImage
/// The base image tag.
/// Ex: 6.0
/// </summary>
[Required]
public string BaseImageTag { get; set; }

/// <summary>
/// The base image digest.
/// Ex: sha256:12345...
/// </summary>
public string BaseImageDigest { get; set; }

/// <summary>
/// The registry to push to.
/// </summary>
Expand Down Expand Up @@ -187,6 +192,7 @@ public CreateNewImage()
BaseRegistry = "";
BaseImageName = "";
BaseImageTag = "";
BaseImageDigest = "";
OutputRegistry = "";
ArchiveOutputPath = "";
Repository = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ internal async Task<bool> ExecuteAsync(CancellationToken cancellationToken)

RegistryMode sourceRegistryMode = BaseRegistry.Equals(OutputRegistry, StringComparison.InvariantCultureIgnoreCase) ? RegistryMode.PullFromOutput : RegistryMode.Pull;
Registry? sourceRegistry = IsLocalPull ? null : new Registry(BaseRegistry, logger, sourceRegistryMode);
SourceImageReference sourceImageReference = new(sourceRegistry, BaseImageName, BaseImageTag);
SourceImageReference sourceImageReference = new(sourceRegistry, BaseImageName, BaseImageTag, BaseImageDigest);

DestinationImageReference destinationImageReference = DestinationImageReference.CreateFromSettings(
Repository,
Expand All @@ -82,15 +82,15 @@ internal async Task<bool> ExecuteAsync(CancellationToken cancellationToken)
var picker = new RidGraphManifestPicker(RuntimeIdentifierGraphPath);
imageBuilder = await registry.GetImageManifestAsync(
BaseImageName,
BaseImageTag,
sourceImageReference.Reference,
ContainerRuntimeIdentifier,
picker,
cancellationToken).ConfigureAwait(false);
}
catch (RepositoryNotFoundException)
{
telemetry.LogUnknownRepository();
Log.LogErrorWithCodeFromResources(nameof(Strings.RepositoryNotFound), BaseImageName, BaseImageTag, registry.RegistryName);
Log.LogErrorWithCodeFromResources(nameof(Strings.RepositoryNotFound), BaseImageName, BaseImageTag, BaseImageDigest, registry.RegistryName);
return !Log.HasLoggedErrors;
}
catch (UnableToAccessRepositoryException)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ internal string GenerateCommandLineCommandsInt()
{
builder.AppendSwitchIfNotNull("--baseimagetag ", BaseImageTag);
}
if (!string.IsNullOrWhiteSpace(BaseImageDigest))
{
builder.AppendSwitchIfNotNull("--baseimagedigest ", BaseImageDigest);
}
if (!string.IsNullOrWhiteSpace(OutputRegistry))
{
builder.AppendSwitchIfNotNull("--outputregistry ", OutputRegistry);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ public sealed class ParseContainerProperties : Microsoft.Build.Utilities.Task
[Output]
public string ParsedContainerTag { get; private set; }

[Output]
public string ParsedContainerDigest { get; private set; }

[Output]
public string NewContainerRegistry { get; private set; }

Expand All @@ -71,6 +74,7 @@ public ParseContainerProperties()
ParsedContainerRegistry = "";
ParsedContainerImage = "";
ParsedContainerTag = "";
ParsedContainerDigest = "";
NewContainerRegistry = "";
NewContainerRepository = "";
NewContainerTags = Array.Empty<string>();
Expand Down Expand Up @@ -132,7 +136,7 @@ public override bool Execute()
out string? outputReg,
out string? outputImage,
out string? outputTag,
out string? _outputDigest,
out string? outputDigest,
out bool isRegistrySpecified))
{
Log.LogErrorWithCodeFromResources(nameof(Strings.BaseImageNameParsingFailed), nameof(FullyQualifiedBaseImageName), FullyQualifiedBaseImageName);
Expand Down Expand Up @@ -163,6 +167,7 @@ public override bool Execute()
ParsedContainerRegistry = outputReg ?? "";
ParsedContainerImage = outputImage ?? "";
ParsedContainerTag = outputTag ?? "";
ParsedContainerDigest = outputDigest ?? "";
NewContainerRegistry = ContainerRegistry;
NewContainerTags = validTags;

Expand All @@ -172,6 +177,7 @@ public override bool Execute()
Log.LogMessage(MessageImportance.Low, "Host: {0}", ParsedContainerRegistry);
Log.LogMessage(MessageImportance.Low, "Image: {0}", ParsedContainerImage);
Log.LogMessage(MessageImportance.Low, "Tag: {0}", ParsedContainerTag);
Log.LogMessage(MessageImportance.Low, "Digest: {0}", ParsedContainerDigest);
Log.LogMessage(MessageImportance.Low, "Image Name: {0}", NewContainerRepository);
Log.LogMessage(MessageImportance.Low, "Image Tags: {0}", string.Join(", ", NewContainerTags));
}
Expand Down
9 changes: 9 additions & 0 deletions src/Containers/containerize/ContainerizeCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ internal class ContainerizeCommand : CliRootCommand
DefaultValueFactory = (_) => "latest"
};

internal CliOption<string> BaseImageDigestOption { get; } = new("--baseimagedigest")
{
Description = "The base image digest. Ex: sha256:6cec3641...",
Required = false
};

internal CliOption<string> OutputRegistryOption { get; } = new("--outputregistry")
{
Description = "The registry to push to.",
Expand Down Expand Up @@ -204,6 +210,7 @@ internal ContainerizeCommand() : base("Containerize an application without Docke
Options.Add(BaseRegistryOption);
Options.Add(BaseImageNameOption);
Options.Add(BaseImageTagOption);
Options.Add(BaseImageDigestOption);
Options.Add(OutputRegistryOption);
Options.Add(ArchiveOutputPathOption);
Options.Add(RepositoryOption);
Expand Down Expand Up @@ -232,6 +239,7 @@ internal ContainerizeCommand() : base("Containerize an application without Docke
string _baseReg = parseResult.GetValue(BaseRegistryOption)!;
string _baseName = parseResult.GetValue(BaseImageNameOption)!;
string _baseTag = parseResult.GetValue(BaseImageTagOption)!;
string? _baseDigest = parseResult.GetValue(BaseImageDigestOption);
string? _outputReg = parseResult.GetValue(OutputRegistryOption);
string? _archiveOutputPath = parseResult.GetValue(ArchiveOutputPathOption);
string _name = parseResult.GetValue(RepositoryOption)!;
Expand Down Expand Up @@ -264,6 +272,7 @@ await ContainerBuilder.ContainerizeAsync(
_baseReg,
_baseName,
_baseTag,
_baseDigest,
_entrypoint,
_entrypointArgs,
_defaultArgs,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
<Output TaskParameter="ParsedContainerRegistry" PropertyName="ContainerBaseRegistry" />
<Output TaskParameter="ParsedContainerImage" PropertyName="ContainerBaseName" />
<Output TaskParameter="ParsedContainerTag" PropertyName="ContainerBaseTag" />
<Output TaskParameter="ParsedContainerDigest" PropertyName="ContainerBaseDigest" />
<Output TaskParameter="NewContainerRegistry" PropertyName="ContainerRegistry" />
<Output TaskParameter="NewContainerRepository" PropertyName="ContainerRepository" />
<Output TaskParameter="NewContainerTags" ItemName="ContainerImageTags" />
Expand Down Expand Up @@ -254,6 +255,7 @@
BaseRegistry="$(ContainerBaseRegistry)"
BaseImageName="$(ContainerBaseName)"
BaseImageTag="$(ContainerBaseTag)"
BaseImageDigest="$(ContainerBaseDigest)"
LocalRegistry="$(LocalRegistry)"
OutputRegistry="$(ContainerRegistry)"
ArchiveOutputPath="$(ContainerArchiveOutputPath)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ public async System.Threading.Tasks.Task CreateNewImage_RootlessBaseImage()

BuiltImage builtImage = imageBuilder.Build();

var sourceReference = new SourceImageReference(registry, DockerRegistryManager.RuntimeBaseImage, DockerRegistryManager.Net8ImageTag);
var sourceReference = new SourceImageReference(registry, DockerRegistryManager.RuntimeBaseImage, DockerRegistryManager.Net8ImageTag, null);
var destinationReference = new DestinationImageReference(registry, RootlessBase, new[] { "latest" });

await registry.PushAsync(builtImage, sourceReference, destinationReference, cancellationToken: default).ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ public class DockerRegistryManager
public const string Net7ImageTag = "7.0";
public const string Net8ImageTag = "8.0";
public const string Net9ImageTag = "9.0";
public const string Net9ImageDigest = "sha256:d8f01f752bf9bd3ff630319181a2ccfbeecea4080a1912095a34002f61bfa345";
public const string Net8PreviewWindowsSpecificImageTag = $"{Net8ImageTag}-nanoserver-ltsc2022";
public const string LocalRegistry = "localhost:5010";
public const string FullyQualifiedBaseImageDefault = $"{BaseImageSource}/{RuntimeBaseImage}:{Net9ImageTag}";
public const string FullyQualifiedBaseImageAspNet = $"{BaseImageSource}/{AspNetBaseImage}:{Net9ImageTag}";
public const string FullyQualifiedBaseImageAspNetDigest = $"{BaseImageSource}/{AspNetBaseImage}@{Net9ImageDigest}";
private static string? s_registryContainerId;

internal class SameArchManifestPicker : IManifestPicker
Expand Down Expand Up @@ -78,7 +80,7 @@ public static async Task StartAndPopulateDockerRegistry(ITestOutputHelper testOu
var ridjson = Path.Combine(Path.GetDirectoryName(dotnetdll)!, "RuntimeIdentifierGraph.json");

var image = await pullRegistry.GetImageManifestAsync(RuntimeBaseImage, tag, "linux-x64", new SameArchManifestPicker(), CancellationToken.None);
var source = new SourceImageReference(pullRegistry, RuntimeBaseImage, tag);
var source = new SourceImageReference(pullRegistry, RuntimeBaseImage, tag, null);
var dest = new DestinationImageReference(pushRegistry, RuntimeBaseImage, [tag]);
logger.LogInformation($"Pushing image for {BaseImageSource}/{RuntimeBaseImage}:{tag}");
await pushRegistry.PushAsync(image.Build(), source, dest, CancellationToken.None);
Expand Down
Loading

0 comments on commit 6acc5b3

Please sign in to comment.