Add OCI container annotation support#55415
Conversation
|
Azure Pipelines: Successfully started running 1 pipeline(s). 2 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
|
Azure Pipelines: Successfully started running 1 pipeline(s). 2 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
Adds first-class OCI manifest/index annotation support to the .NET SDK container publishing pipeline, enabling registry consumers to read metadata from image manifests and multi-arch indexes (not just config labels).
Changes:
- Introduces a
ContainerAnnotationMSBuild item and wires it through container build/publish tasks. - Emits annotations into single-platform OCI manifests and top-level multi-arch OCI indexes, with opt-in conventional annotations derived from existing package/source metadata.
- Enforces/auto-selects OCI output when annotations are present and rejects explicitly-selected Docker format.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| test/Microsoft.NET.Build.Containers.UnitTests/ImageIndexGeneratorTests.cs | Adds unit coverage for index-level OCI annotations and Docker manifest list behavior. |
| test/Microsoft.NET.Build.Containers.UnitTests/ImageBuilderTests.cs | Adds unit coverage for manifest-level OCI annotations. |
| test/Microsoft.NET.Build.Containers.IntegrationTests/TargetsTests.cs | Validates conventional annotation generation and the “annotations require OCI” enforcement at the MSBuild layer. |
| src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets | Defines annotation-related properties/items, generates conventional annotations, auto-selects OCI, and errors on Docker format when annotations exist. |
| src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.Interface.cs | Exposes Annotations task parameter for single-image creation. |
| src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs | Plumbs MSBuild ContainerAnnotation items into ImageBuilder. |
| src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.Interface.cs | Exposes Annotations task parameter for multi-arch index creation. |
| src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs | Collects annotations and passes them into index generation. |
| src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net11.0/PublicAPI.Unshipped.txt | Declares newly added public API surface for annotations on tasks/manifests/indexes. |
| src/Containers/Microsoft.NET.Build.Containers/ManifestV2.cs | Adds Annotations to OCI manifest model with null-when-empty serialization behavior. |
| src/Containers/Microsoft.NET.Build.Containers/ManifestListV2.cs | Adds index/list-level annotations properties to the manifest list and OCI index models. |
| src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs | Extends index generation APIs to accept optional annotations and serialize them into OCI indexes. |
| src/Containers/Microsoft.NET.Build.Containers/ImageBuilder.cs | Adds annotation capture and emission for OCI manifests. |
|
@dotnet-policy-service agree |
…annotations # Conflicts: # test/Microsoft.NET.Build.Containers.IntegrationTests/TargetsTests.cs
|
@baronfel I think this is useful. Annotations on manifest are used security tooling and inventory tooling. If you want me to change it from opt-in to default for OCI image type with a opt-out option, just let me know :) |
|
Thanks for the ping - I saw this and was thinking about how I'd like the concept to be expressed. Usually we'd do the design-side of things over issue/discussion before an implementation to help hammer out some of those details, but happy to iterate here. My main questions are things like:
So a split of questions related to physical layout of the annotations, the UX of defining them, and then opportunities for being better OCI citizens by pre-seeding specific annotations. We do a lot of this with Labels already, but Labels were more 'settled' IMO. |
|
Yes, I went straight to PR thinking my issue at hand was simply enough but after having AI dig into various tools then ya answer is more complex. Can read the TL;DR and jump to proposed changes.
AI Research 🤖I did some research across the OCI specifications, Buildx/Podman/Skopeo, registries, and consumers including Kargo, Flux, Argo CD, Harbor, Renovate, Syft, Trivy, and Crane. Formats and physical placementOCI defines annotations independently on several object types:
There is no OCI inheritance or merging between these locations. Index annotations do not flow to child manifests, child annotations do not flow to their index, and config labels do not become annotations. Docker schema-2 manifests and manifest lists do not define annotations. My conclusion is that Existing consumersThe public tooling I found has varied—but concrete—scope expectations:
Config labels remain independently important. Argo CD Image Updater and Dependabot read standard metadata from config labels, and normal Skopeo inspection primarily exposes labels. I therefore do not think annotations should replace the existing labels. Kargo is the motivating concrete example here. Its behavior is OCI-compliant, but it is an application convention rather than an OCI-required placement rule. For a multi-platform tag it reads the root index, while Flux and some other consumers resolve the index and read a child manifest. Supporting both requires deliberate duplication because nothing flows automatically. The public tools are also not exhaustive. OCI intentionally permits arbitrary namespaced annotations, and internal inventory, policy, provenance, promotion, or deployment systems may depend on organization-specific keys. Annotation conventionsOCI defines standard My proposed conventional placement would be:
The container tooling should respect the defined formats—for example RFC 3339 for Proposed MSBuild UXThe current My preference is to use one annotation item type, <ItemGroup>
<ContainerAnnotation Include="org.opencontainers.image.source"
Value="$(RepositoryUrl)"
Scope="Manifest,Index" />
<ContainerAnnotation Include="com.example.pipeline.id"
Value="$(PipelineId)"
Scope="Index" />
</ItemGroup>Proposed semantics:
This follows the model exposed by Buildx, which distinguishes I would keep annotation generation opt-in initially because enabling it changes the output media type and manifest digests, and not every local image-store path can preserve OCI annotations. Local Docker/Podman paths that require conversion through Docker archives should either preserve OCI output or clearly reject/document annotations rather than silently losing them. Based on this research, I think the PR should be revised around explicit manifest/index scope, ensure generated annotations are forwarded into multi-RID child manifests, retain equivalent config labels, and add end-to-end coverage for both the root index and child manifests. Does that scope model align with how you would like the concept expressed? |
Summary
ContainerAnnotationMSBuild item for OCI image metadataMotivation
OCI annotations describe the published manifest or image index itself. Registry consumers cannot reliably obtain this metadata from configuration labels, particularly for multi-platform images where the tag resolves to an image index.
Generated annotations are opt-in to preserve the existing behavior where output format follows the base image. Architecture-specific base image and .NET toolset metadata remain labels and are not copied to the top-level index.
Validation
git diff --checkpassesRunning the integration tests requires a built redist SDK; the attempted redist build was blocked by a pre-existing missing
project.assets.jsonin the Blazor WebAssembly tool chain after the Containers projects compiled and packed.