Skip to content
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
13 changes: 12 additions & 1 deletion src/Containers/Microsoft.NET.Build.Containers/ImageBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ internal sealed class ImageBuilder
private readonly ManifestV2 _manifest;
private readonly ImageConfig _baseImageConfig;
private readonly ILogger _logger;
private Dictionary<string, string>? _annotations;

/// <summary>
/// This is a parser for ASPNETCORE_URLS based on https://github.com/dotnet/aspnetcore/blob/main/src/Http/Http/src/BindingAddress.cs
Expand Down Expand Up @@ -83,7 +84,8 @@ internal BuiltImage Build()
Config = newManifestConfig,
SchemaVersion = _manifest.SchemaVersion,
MediaType = ManifestMediaType,
Layers = _manifest.Layers
Layers = _manifest.Layers,
Annotations = ManifestMediaType == SchemaTypes.OciManifestV1 ? _annotations : null
};

return new BuiltImage()
Expand Down Expand Up @@ -121,6 +123,15 @@ internal void AddLayer(Layer l)
/// </summary>
internal void AddLabel(string name, string value) => _baseImageConfig.AddLabel(name, value);

/// <summary>
/// Adds an annotation to the generated OCI image manifest.
/// </summary>
internal void AddAnnotation(string name, string value)
{
_annotations ??= new(StringComparer.Ordinal);
_annotations[name] = value;
}

/// <summary>
/// Adds environment variables to a base image.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ internal static class ImageIndexGenerator
/// <returns>Returns json string of image index and image index mediaType.</returns>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="NotSupportedException"></exception>
internal static (string, string) GenerateImageIndex(BuiltImage[] images)
internal static (string, string) GenerateImageIndex(BuiltImage[] images, IReadOnlyDictionary<string, string>? annotations = null)
{
if (images.Length == 0)
{
Expand All @@ -37,7 +37,7 @@ internal static (string, string) GenerateImageIndex(BuiltImage[] images)
}
else if (manifestMediaType == SchemaTypes.OciManifestV1)
{
return (GenerateImageIndex(images, SchemaTypes.OciManifestV1, SchemaTypes.OciImageIndexV1), SchemaTypes.OciImageIndexV1);
return (GenerateImageIndex(images, SchemaTypes.OciManifestV1, SchemaTypes.OciImageIndexV1, annotations), SchemaTypes.OciImageIndexV1);
}
else
{
Expand All @@ -54,7 +54,7 @@ internal static (string, string) GenerateImageIndex(BuiltImage[] images)
/// <returns>Returns json string of image index and image index mediaType.</returns>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="NotSupportedException"></exception>
internal static string GenerateImageIndex(BuiltImage[] images, string manifestMediaType, string imageIndexMediaType)
internal static string GenerateImageIndex(BuiltImage[] images, string manifestMediaType, string imageIndexMediaType, IReadOnlyDictionary<string, string>? annotations = null)
{
if (images.Length == 0)
{
Expand Down Expand Up @@ -84,12 +84,28 @@ internal static string GenerateImageIndex(BuiltImage[] images, string manifestMe
{
schemaVersion = 2,
mediaType = imageIndexMediaType,
manifests = manifests
manifests = manifests,
annotations = CopyAnnotations(annotations)
};

return GetJsonStringFromImageIndex(imageIndex);
}

private static Dictionary<string, string>? CopyAnnotations(IReadOnlyDictionary<string, string>? annotations)
{
if (annotations is not { Count: > 0 })
{
return null;
}

Dictionary<string, string> result = new(annotations.Count, StringComparer.Ordinal);
foreach ((string key, string value) in annotations.OrderBy(annotation => annotation.Key, StringComparer.Ordinal))
{
result[key] = value;
}
return result;
}

internal static string GenerateImageIndexWithAnnotations(
string manifestMediaType,
string manifestDigest,
Expand Down
10 changes: 8 additions & 2 deletions src/Containers/Microsoft.NET.Build.Containers/ManifestListV2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@

namespace Microsoft.NET.Build.Containers;

public record struct ManifestListV2(int schemaVersion, string mediaType, PlatformSpecificManifest[] manifests);
public record struct ManifestListV2(int schemaVersion, string mediaType, PlatformSpecificManifest[] manifests)
{
public Dictionary<string, string>? annotations { get; init; }
}

public record struct PlatformInformation(string architecture, string os, string? variant, string[] features, [property: JsonPropertyName("os.version")][field: JsonPropertyName("os.version")] string? version);

public record struct PlatformSpecificManifest(string mediaType, long size, string digest, PlatformInformation platform);
public record struct ImageIndexV1(int schemaVersion, string mediaType, PlatformSpecificOciManifest[] manifests);
public record struct ImageIndexV1(int schemaVersion, string mediaType, PlatformSpecificOciManifest[] manifests)
{
public Dictionary<string, string>? annotations { get; init; }
}

public record struct PlatformSpecificOciManifest(string mediaType, long size, string digest, PlatformInformation platform, Dictionary<string, string> annotations);
7 changes: 7 additions & 0 deletions src/Containers/Microsoft.NET.Build.Containers/ManifestV2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ public class ManifestV2
[JsonPropertyName("layers")]
public required List<ManifestLayer> Layers { get; init; }

/// <summary>
/// Arbitrary metadata for this OCI image manifest.
/// </summary>
[JsonPropertyName("annotations")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public Dictionary<string, string>? Annotations { get; init; }

/// <summary>
/// Gets the digest for this manifest.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.BaseImageDigest.get -> str
Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.BaseImageDigest.set -> void
Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.ArchiveOutputPath.get -> string!
Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.ArchiveOutputPath.set -> void
Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.Annotations.get -> Microsoft.Build.Framework.ITaskItem![]!
Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.Annotations.set -> void
Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.LocalRegistry.get -> string!
Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.LocalRegistry.set -> void
Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.GeneratedArchiveOutputPath.get -> string!
Expand Down Expand Up @@ -96,6 +98,8 @@ Microsoft.NET.Build.Containers.ManifestLayer.urls.get -> string![]?
Microsoft.NET.Build.Containers.ManifestLayer.urls.set -> void
Microsoft.NET.Build.Containers.ManifestListV2
Microsoft.NET.Build.Containers.ManifestListV2.ManifestListV2() -> void
Microsoft.NET.Build.Containers.ManifestListV2.annotations.get -> System.Collections.Generic.Dictionary<string!, string!>?
Microsoft.NET.Build.Containers.ManifestListV2.annotations.init -> void
Microsoft.NET.Build.Containers.ManifestListV2.ManifestListV2(int schemaVersion, string! mediaType, Microsoft.NET.Build.Containers.PlatformSpecificManifest[]! manifests) -> void
Microsoft.NET.Build.Containers.ManifestListV2.manifests.get -> Microsoft.NET.Build.Containers.PlatformSpecificManifest[]!
Microsoft.NET.Build.Containers.ManifestListV2.manifests.set -> void
Expand All @@ -105,6 +109,8 @@ Microsoft.NET.Build.Containers.ManifestListV2.schemaVersion.get -> int
Microsoft.NET.Build.Containers.ManifestListV2.schemaVersion.set -> void
Microsoft.NET.Build.Containers.ImageIndexV1
Microsoft.NET.Build.Containers.ImageIndexV1.ImageIndexV1() -> void
Microsoft.NET.Build.Containers.ImageIndexV1.annotations.get -> System.Collections.Generic.Dictionary<string!, string!>?
Microsoft.NET.Build.Containers.ImageIndexV1.annotations.init -> void
Microsoft.NET.Build.Containers.ImageIndexV1.ImageIndexV1(int schemaVersion, string! mediaType, Microsoft.NET.Build.Containers.PlatformSpecificOciManifest[]! manifests) -> void
Microsoft.NET.Build.Containers.ImageIndexV1.manifests.get -> Microsoft.NET.Build.Containers.PlatformSpecificOciManifest[]!
Microsoft.NET.Build.Containers.ImageIndexV1.manifests.set -> void
Expand All @@ -114,6 +120,8 @@ Microsoft.NET.Build.Containers.ImageIndexV1.schemaVersion.get -> int
Microsoft.NET.Build.Containers.ImageIndexV1.schemaVersion.set -> void
Microsoft.NET.Build.Containers.ManifestV2
Microsoft.NET.Build.Containers.ManifestV2.Config.get -> Microsoft.NET.Build.Containers.ManifestConfig
Microsoft.NET.Build.Containers.ManifestV2.Annotations.get -> System.Collections.Generic.Dictionary<string!, string!>?
Microsoft.NET.Build.Containers.ManifestV2.Annotations.init -> void
Microsoft.NET.Build.Containers.ManifestV2.Config.init -> void
Microsoft.NET.Build.Containers.ManifestV2.GetDigest() -> string!
Microsoft.NET.Build.Containers.ManifestV2.KnownDigest.get -> string?
Expand Down Expand Up @@ -235,6 +243,8 @@ Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ImageTags.get -> string![]!
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ImageTags.set -> void
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.Labels.get -> Microsoft.Build.Framework.ITaskItem![]!
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.Labels.set -> void
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.Annotations.get -> Microsoft.Build.Framework.ITaskItem![]!
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.Annotations.set -> void
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.LocalRegistry.get -> string!
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.LocalRegistry.set -> void
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.OutputRegistry.get -> string!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ partial class CreateImageIndex
[Required]
public string[] ImageTags { get; set; }

/// <summary>
/// Annotations that the OCI image index will include in metadata.
/// </summary>
public ITaskItem[] Annotations { get; set; }

/// <summary>
/// The generated archive output path.
/// </summary>
Expand All @@ -90,9 +95,10 @@ public CreateImageIndex()
LocalRegistry = string.Empty;
Repository = string.Empty;
ImageTags = Array.Empty<string>();
Annotations = Array.Empty<ITaskItem>();
GeneratedArchiveOutputPath = string.Empty;
GeneratedImageIndex = string.Empty;

TaskResources = Resource.Manager;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,12 @@ internal async Task<bool> ExecuteAsync(CancellationToken cancellationToken)
return false;
}

var multiArchImage = CreateMultiArchImage(images, destinationImageReference.Kind);
Dictionary<string, string> annotations = new(StringComparer.Ordinal);
foreach (ITaskItem annotation in Annotations)
{
annotations[annotation.ItemSpec] = annotation.GetMetadata("Value");
}
var multiArchImage = CreateMultiArchImage(images, destinationImageReference.Kind, annotations);

GeneratedImageIndex = multiArchImage.ImageIndex;
GeneratedArchiveOutputPath = ArchiveOutputPath;
Expand Down Expand Up @@ -163,20 +168,20 @@ private BuiltImage[] ParseImages(DestinationImageReferenceKind destinationKind)
return (architecture, os);
}

private static MultiArchImage CreateMultiArchImage(BuiltImage[] images, DestinationImageReferenceKind destinationImageKind)
private static MultiArchImage CreateMultiArchImage(BuiltImage[] images, DestinationImageReferenceKind destinationImageKind, IReadOnlyDictionary<string, string> annotations)
{
switch (destinationImageKind)
{
case DestinationImageReferenceKind.LocalRegistry:
return new MultiArchImage()
{
// For multi-arch we publish only oci-formatted image tarballs.
ImageIndex = ImageIndexGenerator.GenerateImageIndex(images, SchemaTypes.OciManifestV1, SchemaTypes.OciImageIndexV1),
ImageIndex = ImageIndexGenerator.GenerateImageIndex(images, SchemaTypes.OciManifestV1, SchemaTypes.OciImageIndexV1, annotations),
ImageIndexMediaType = SchemaTypes.OciImageIndexV1,
Images = images
};
case DestinationImageReferenceKind.RemoteRegistry:
(string imageIndex, string mediaType) = ImageIndexGenerator.GenerateImageIndex(images);
(string imageIndex, string mediaType) = ImageIndexGenerator.GenerateImageIndex(images, annotations);
return new MultiArchImage()
{
ImageIndex = imageIndex,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ partial class CreateNewImage
/// </summary>
public ITaskItem[] Labels { get; set; }

/// <summary>
/// Annotations that the OCI image manifest will include in metadata.
/// </summary>
public ITaskItem[] Annotations { get; set; }

/// <summary>
/// Container environment variables to set.
/// </summary>
Expand Down Expand Up @@ -206,6 +211,7 @@ public CreateNewImage()
AppCommandArgs = Array.Empty<ITaskItem>();
AppCommandInstruction = "";
Labels = Array.Empty<ITaskItem>();
Annotations = Array.Empty<ITaskItem>();
ExposedPorts = Array.Empty<ITaskItem>();
ContainerEnvironmentVariables = Array.Empty<ITaskItem>();
ContainerRuntimeIdentifier = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ private async Task<bool> ExecuteAsyncCore(ILogger logger, ILoggerFactory msbuild
(baseImageLabel, baseImageDigest) = imageBuilder.AddBaseImageDigestLabel();
}
}

else
{
if (GenerateDigestLabel)
Expand All @@ -193,6 +194,11 @@ private async Task<bool> ExecuteAsyncCore(ILogger logger, ILoggerFactory msbuild
}
}

foreach (ITaskItem annotation in Annotations)
{
imageBuilder.AddAnnotation(annotation.ItemSpec, annotation.GetMetadata("Value"));
}

SetEnvironmentVariables(imageBuilder, ContainerEnvironmentVariables);

SetPorts(imageBuilder, ExposedPorts);
Expand Down
Loading
Loading