Skip to content

Commit

Permalink
Add support for using Digest for base image
Browse files Browse the repository at this point in the history
Add support for using image digest (sha256) for the base image when using the `PublishContainer` target.

For example:
`mcr.microsoft.com/dotnet/aspnet@sha256:6cec36412a215aad2a033cfe259890482be0a1dcb680e81fccc393b2d4069455`
  • Loading branch information
BeyondEvil committed Oct 26, 2024
1 parent 88ef730 commit 7f70916
Show file tree
Hide file tree
Showing 15 changed files with 81 additions and 24 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 @@ -62,17 +63,18 @@ internal static async Task<int> ContainerizeAsync(
{
try
{
string imageReference = !string.IsNullOrEmpty(baseImageDigest) ? baseImageDigest : baseImageTag;
var ridGraphPicker = new RidGraphManifestPicker(ridGraphPath);
imageBuilder = await registry.GetImageManifestAsync(
baseImageName,
baseImageTag,
imageReference,
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 @@ -111,6 +113,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 @@ -134,4 +137,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 @@ -148,6 +148,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 @@ -232,6 +234,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 @@ -284,4 +287,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.PlatformSpecificManifest[]! manifests) -> void
Microsoft.NET.Build.Containers.ImageIndexV1.Deconstruct(out int schemaVersion, out string! mediaType, out Microsoft.NET.Build.Containers.PlatformSpecificManifest[]! manifests) -> void
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,43 @@
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)
if (Registry is { } reg)
{
return $"{reg.RegistryName}/{Repository}:{Tag}";
}
else
if (Digest is { } dig)
{
return $"{reg.RegistryName}/{Repository}@{dig}";
}
else
{
return $"{reg.RegistryName}/{Repository}:{Tag}";
}
}
else
{
return RepositoryAndTag;
if (Digest is { } dig)
{
return RepositoryAndDigest;
}
else
{
return RepositoryAndTag;
}
}
}

/// <summary>
/// Returns the repository and tag as a formatted string. Used in cases
/// </summary>
public readonly string RepositoryAndTag => $"{Repository}:{Tag}";

/// <summary>
/// Returns the repository and digest as a formatted string.
/// </summary>
public readonly string RepositoryAndDigest => $"{Repository}@{Digest}";
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ partial class CreateNewImage
[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 @@ -184,6 +190,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 @@ -79,18 +79,19 @@ internal async Task<bool> ExecuteAsync(CancellationToken cancellationToken)
{
try
{
string imageReference = !string.IsNullOrEmpty(BaseImageDigest) ? BaseImageDigest : BaseImageTag;
var picker = new RidGraphManifestPicker(RuntimeIdentifierGraphPath);
imageBuilder = await registry.GetImageManifestAsync(
BaseImageName,
BaseImageTag,
imageReference,
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 @@ -119,6 +119,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 @@ -244,6 +245,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 @@ -25,7 +25,7 @@ public class DockerRegistryManager

internal class SameArchManifestPicker : IManifestPicker
{
public PlatformSpecificManifest? PickBestManifestForRid(IReadOnlyDictionary<string, PlatformSpecificManifest> manifestList, string runtimeIdentifier)
public PlatformSpecificManifest? PickBestManifestForRid(IReadOnlyDictionary<string, PlatformSpecificManifest> manifestList, string runtimeIdentifier)
{
return manifestList.Values.SingleOrDefault(m => m.platform.os == "linux" && m.platform.architecture == "amd64");
}
Expand Down Expand Up @@ -74,7 +74,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 7f70916

Please sign in to comment.