Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for using Digest for base image #44461

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,20 +4,25 @@
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)
baronfel marked this conversation as resolved.
Show resolved Hide resolved
{
public override string ToString()
{
if (Registry is { } reg)
string sourceImageReference = RepositoryAndTag;

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

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

return sourceImageReference;
}

/// <summary>
Expand Down
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 @@ -162,7 +166,9 @@ public override bool Execute()

ParsedContainerRegistry = outputReg ?? "";
ParsedContainerImage = outputImage ?? "";
ParsedContainerTag = outputTag ?? "";
// If no Tag was provided, default to "latest"
ParsedContainerTag = outputTag ?? "latest";
ParsedContainerDigest = outputDigest ?? "";
NewContainerRegistry = ContainerRegistry;
NewContainerTags = validTags;

Expand All @@ -172,6 +178,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 @@ -16,16 +16,18 @@ public class DockerRegistryManager
public const string Net7ImageTag = "7.0";
public const string Net8ImageTag = "8.0";
public const string Net9PreviewImageTag = "9.0-preview";
public const string Net9PreviewImageDigest = "sha256:8f530963f22ca799550c29580e4dfc3a674f57ba56267fc82670de773d9a2506";
public const string RuntimeFrameworkVersion = "9.0.0-preview.3.24172.9";
public const string Net8PreviewWindowsSpecificImageTag = $"{Net8ImageTag}-nanoserver-ltsc2022";
public const string LocalRegistry = "localhost:5010";
public const string FullyQualifiedBaseImageDefault = $"{BaseImageSource}/{RuntimeBaseImage}:{Net9PreviewImageTag}";
public const string FullyQualifiedBaseImageAspNet = $"{BaseImageSource}/{AspNetBaseImage}:{Net9PreviewImageTag}";
public const string FullyQualifiedBaseImageAspNetDigest = $"{BaseImageSource}/{AspNetBaseImage}@{Net9PreviewImageDigest}";
private static string? s_registryContainerId;

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 +76,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