File: ManifestV2.cs
Web Access
Project: src\src\sdk\src\Containers\Microsoft.NET.Build.Containers\Microsoft.NET.Build.Containers.csproj (Microsoft.NET.Build.Containers)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.Json;
using System.Text.Json.Serialization;

namespace Microsoft.NET.Build.Containers;

/// <summary>
/// The struct represents image manifest specification.
/// </summary>
/// <remarks>
/// https://github.com/opencontainers/image-spec/blob/main/manifest.md
/// </remarks>
public class ManifestV2
{
    /// <summary>
    /// The digest of this manifest, as provided by the registry via the
    /// <c>Docker-Content-Digest</c> response header. This value follows the
    /// OCI descriptor digest format.
    /// <see href="https://github.com/opencontainers/image-spec/blob/a4c6ade7bb82b316d45391f572727a63e268b252/descriptor.md#digests"/>
    /// </summary>
    [JsonIgnore]
    public string? KnownDigest { get; set; }

    /// <summary>
    /// This REQUIRED property specifies the image manifest schema version.
    /// For this version of the specification, this MUST be 2 to ensure backward compatibility with older versions of Docker.
    /// The value of this field will not change. This field MAY be removed in a future version of the specification.
    /// </summary>
    [JsonPropertyName("schemaVersion")]
    public required int SchemaVersion { get; init; }

    /// <summary>
    /// This property SHOULD be used and remain compatible with earlier versions of this specification and with other similar external formats.
    /// When used, this field MUST contain the media type application/vnd.oci.image.manifest.v1+json. This field usage differs from the descriptor use of mediaType.
    /// </summary>
    [JsonPropertyName("mediaType")]
    public string? MediaType { get; init; }

    /// <summary>
    /// This REQUIRED property references a configuration object for a container, by digest.
    /// </summary>
    [JsonPropertyName("config")]
    public required ManifestConfig Config { get; init; }

    /// <summary>
    /// Each item in the array MUST be a descriptor. The array MUST have the base layer at index 0.
    /// Subsequent layers MUST then follow in stack order (i.e. from layers[0] to layers[len(layers)-1]).
    /// The final filesystem layout MUST match the result of applying the layers to an empty directory.
    /// The ownership, mode, and other attributes of the initial empty directory are unspecified.
    /// </summary>
    [JsonPropertyName("layers")]
    public required List<ManifestLayer> Layers { get; init; }

    /// <summary>
    /// Gets the digest for this manifest.
    /// </summary>
    public string GetDigest() => KnownDigest ??= DigestUtils.ComputeSha256Digest(JsonSerializer.SerializeToNode(this)?.ToJsonString() ?? string.Empty);
}

public record struct ManifestConfig(string mediaType, long size, string digest);

public record struct ManifestLayer(string mediaType, long size, string digest, [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)][field: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] string[]? urls);