diff --git a/src/Microsoft.DotNet.ImageBuilder/src/Commands/BuildCommand.cs b/src/Microsoft.DotNet.ImageBuilder/src/Commands/BuildCommand.cs index 1330b661b..b3a02da86 100644 --- a/src/Microsoft.DotNet.ImageBuilder/src/Commands/BuildCommand.cs +++ b/src/Microsoft.DotNet.ImageBuilder/src/Commands/BuildCommand.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -381,7 +381,7 @@ private void CopyPlatformDataFromCachedPlatform(PlatformData dstPlatform, Platfo // When a cache hit occurs for a Dockerfile, we want to transfer some of the metadata about the previously // published image so we don't need to recalculate it again. dstPlatform.BaseImageDigest = srcPlatform.BaseImageDigest; - dstPlatform.Layers = new List(srcPlatform.Layers); + dstPlatform.Layers = new List(srcPlatform.Layers); } private RepoData CreateRepoData(RepoInfo repoInfo) => diff --git a/src/Microsoft.DotNet.ImageBuilder/src/Commands/IngestKustoImageInfoCommand.cs b/src/Microsoft.DotNet.ImageBuilder/src/Commands/IngestKustoImageInfoCommand.cs index 2e3db32bd..aa48510b6 100644 --- a/src/Microsoft.DotNet.ImageBuilder/src/Commands/IngestKustoImageInfoCommand.cs +++ b/src/Microsoft.DotNet.ImageBuilder/src/Commands/IngestKustoImageInfoCommand.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -99,9 +99,8 @@ public override async Task ExecuteAsync() for (int i = 0; i < platform.Layers.Count; i++) { - // TODO: Track layer size (currently set to 0) https://github.com/dotnet/docker-tools/issues/745 layerInfo.AppendLine(FormatLayerCsv( - platform.Layers[i], 0, platform.Layers.Count - i, sha, platform, image, repo.Repo, timestamp)); + platform.Layers[i].Digest, platform.Layers[i].Size, platform.Layers.Count - i, sha, platform, image, repo.Repo, timestamp)); } } } @@ -117,7 +116,7 @@ private static string FormatImageCsv(string imageId, PlatformData platform, Imag private static string FormatLayerCsv( string layerDigest, - int size, + long size, int ordinal, string imageDigest, PlatformData platform, diff --git a/src/Microsoft.DotNet.ImageBuilder/src/IManifestService.cs b/src/Microsoft.DotNet.ImageBuilder/src/IManifestService.cs index 3611b3e37..f4c2a848f 100644 --- a/src/Microsoft.DotNet.ImageBuilder/src/IManifestService.cs +++ b/src/Microsoft.DotNet.ImageBuilder/src/IManifestService.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Text.Json.Nodes; using System.Threading.Tasks; +using Microsoft.DotNet.ImageBuilder.Models.Image; #nullable enable namespace Microsoft.DotNet.ImageBuilder; @@ -15,7 +16,7 @@ public interface IManifestService { Task GetManifestAsync(string image, bool isDryRun); - public async Task> GetImageLayersAsync(string tag, bool isDryRun) + public async Task> GetImageLayersAsync(string tag, bool isDryRun) { if (isDryRun) { @@ -32,7 +33,9 @@ public async Task> GetImageLayersAsync(string tag, bool isDr } return ((JsonArray)manifestResult.Manifest["layers"]!) - .Select(layer => (layer!["digest"] ?? throw new InvalidOperationException("Expected digest property")).ToString()) + .Select(layer => new Layer( + Digest: (layer!["digest"] ?? throw new InvalidOperationException("Expected digest property")).ToString(), + Size: (layer!["size"] ?? throw new InvalidOperationException("Expected size property")).GetValue())) .Reverse(); } diff --git a/src/Microsoft.DotNet.ImageBuilder/src/ImageInfoHelper.cs b/src/Microsoft.DotNet.ImageBuilder/src/ImageInfoHelper.cs index e9dfaad96..b32dc8836 100644 --- a/src/Microsoft.DotNet.ImageBuilder/src/ImageInfoHelper.cs +++ b/src/Microsoft.DotNet.ImageBuilder/src/ImageInfoHelper.cs @@ -132,7 +132,7 @@ public static ImageArtifactDetails DeserializeImageArtifactDetails(string path) public static ImageArtifactDetails LoadFromContent(string imageInfoContent, ManifestInfo manifest, bool skipManifestValidation = false, bool useFilteredManifest = false) { - ImageArtifactDetails imageArtifactDetails = JsonConvert.DeserializeObject(imageInfoContent); + ImageArtifactDetails imageArtifactDetails = ImageArtifactDetails.FromJson(imageInfoContent); foreach (RepoData repoData in imageArtifactDetails.Repos) { @@ -160,7 +160,7 @@ public static ImageArtifactDetails LoadFromContent(string imageInfoContent, Mani { imageData.ManifestImage = manifestImage; } - + platformData.PlatformInfo = matchingManifestPlatform; platformData.ImageInfo = manifestImage; break; @@ -327,17 +327,17 @@ private static void MergeData(object srcObj, object targetObj, ImageInfoMergeOpt ReplaceValue(property, srcObj, targetObj); } - else if (srcObj is PlatformData && property.Name == nameof(PlatformData.Layers)) - { - // Layers are always unique and should never get merged. - // Layers are already sorted according to their position in the image. They should not be sorted alphabetically. - ReplaceValue(property, srcObj, targetObj, skipListSorting: true); - } else { MergeStringLists(property, srcObj, targetObj); } } + else if (typeof(IList).IsAssignableFrom(property.PropertyType)) + { + // Layers are always unique and should never get merged. + // Layers are already sorted according to their position in the image. They should not be sorted alphabetically. + ReplaceValue(property, srcObj, targetObj, skipListSorting: true); + } else if (typeof(IList).IsAssignableFrom(property.PropertyType)) { MergeLists(property, srcObj, targetObj, options); diff --git a/src/Microsoft.DotNet.ImageBuilder/src/JsonHelper.cs b/src/Microsoft.DotNet.ImageBuilder/src/JsonHelper.cs index 166e3661c..7552f4da5 100644 --- a/src/Microsoft.DotNet.ImageBuilder/src/JsonHelper.cs +++ b/src/Microsoft.DotNet.ImageBuilder/src/JsonHelper.cs @@ -12,15 +12,15 @@ namespace Microsoft.DotNet.ImageBuilder { public static class JsonHelper { - public static string SerializeObject(object value) + public static JsonSerializerSettings JsonSerializerSettings => new() { - JsonSerializerSettings settings = new() - { - ContractResolver = new CustomContractResolver(), - Formatting = Formatting.Indented - }; + ContractResolver = new CustomContractResolver(), + Formatting = Formatting.Indented + }; - return JsonConvert.SerializeObject(value, settings); + public static string SerializeObject(object value) + { + return JsonConvert.SerializeObject(value, JsonSerializerSettings); } private class CustomContractResolver : DefaultContractResolver diff --git a/src/Microsoft.DotNet.ImageBuilder/src/Models/Image/ImageArtifactDetails.cs b/src/Microsoft.DotNet.ImageBuilder/src/Models/Image/ImageArtifactDetails.cs index 6b7aa8c84..71f96f477 100644 --- a/src/Microsoft.DotNet.ImageBuilder/src/Models/Image/ImageArtifactDetails.cs +++ b/src/Microsoft.DotNet.ImageBuilder/src/Models/Image/ImageArtifactDetails.cs @@ -2,18 +2,93 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#nullable enable + +using System; using System.Collections.Generic; +using System.Runtime.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.DotNet.ImageBuilder.Models.Image; -namespace Microsoft.DotNet.ImageBuilder.Models.Image +public class ImageArtifactDetails { - public class ImageArtifactDetails + private static readonly JsonSerializerSettings s_jsonSettings = new() + { + Converters = + [ + new SchemaVersion2LayerConverter() + ] + }; + + public string SchemaVersion => "2.0"; + + public List Repos { get; set; } = []; + + public static ImageArtifactDetails FromJson(string json) + { + ImageArtifactDetails imageArtifactDetails = + JsonConvert.DeserializeObject(json, s_jsonSettings) + ?? throw new SerializationException( + $""" + Failed to deserialize {nameof(ImageArtifactDetails)} from content: + {json} + """); + + return imageArtifactDetails; + } + + private class SchemaVersion2LayerConverter : JsonConverter { - public string SchemaVersion + private static readonly JsonSerializer s_jsonSerializer = + JsonSerializer.Create(JsonHelper.JsonSerializerSettings); + + // We do not want to handle writing at all. We only want to convert + // the old Layer format to the new format, and all writing should be + // done using the new format (via the default conversion settings). + public override bool CanWrite => false; + + public override bool CanConvert(Type objectType) { - get { return "1.0"; } - set { } + return objectType == typeof(Layer); } - public List Repos { get; set; } = new List(); + public override object? ReadJson( + JsonReader reader, + Type objectType, + object? existingValue, + JsonSerializer serializer) + { + JToken token = JToken.Load(reader); + return token.Type switch + { + // If the token is an object, proceed as normal. + // We can't use the JsonSerializer passed into the method since + // it contains this converter. Doing so would cause a stack + // overflow since this method would be called again recursively. + JTokenType.Object => token.ToObject(s_jsonSerializer), + + // If we encounter a string, we want to convert it to the Layer + // object defined in schema version 2. Assume a size of 0. The + // next time an image is built, the size will be updated. + JTokenType.String => + new Layer( + Digest: token.Value() + ?? throw new JsonSerializationException( + $"Unable to serialize digest from '{token}'"), + Size: 0), + + // Handle null and other token types + JTokenType.Null => null, + _ => throw new JsonSerializationException( + $"Unexpected token type: {token.Type} when parsing Layer.") + }; + } + + public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } } } diff --git a/src/Microsoft.DotNet.ImageBuilder/src/Models/Image/PlatformData.cs b/src/Microsoft.DotNet.ImageBuilder/src/Models/Image/PlatformData.cs index 75f546bef..323191456 100644 --- a/src/Microsoft.DotNet.ImageBuilder/src/Models/Image/PlatformData.cs +++ b/src/Microsoft.DotNet.ImageBuilder/src/Models/Image/PlatformData.cs @@ -49,7 +49,7 @@ public PlatformData(ImageInfo imageInfo, PlatformInfo platformInfo) [JsonProperty(Required = Required.Always)] public string CommitUrl { get; set; } = string.Empty; - public List Layers { get; set; } = new(); + public List Layers { get; set; } = new(); /// /// Gets or sets whether the image or its associated tag names have changed since it was last published. @@ -78,7 +78,7 @@ public int CompareTo([AllowNull] PlatformData other) return 1; } - + if (HasDifferentTagState(other)) { return 1; @@ -134,5 +134,7 @@ private bool IsNullOrEmpty(List? list) => return new Version(fullVersion).ToString(2); } } + + public record Layer(string Digest, long Size); } #nullable disable diff --git a/src/Microsoft.DotNet.ImageBuilder/tests/BuildCommandTests.cs b/src/Microsoft.DotNet.ImageBuilder/tests/BuildCommandTests.cs index 1a2ad6261..5f1db15cd 100644 --- a/src/Microsoft.DotNet.ImageBuilder/tests/BuildCommandTests.cs +++ b/src/Microsoft.DotNet.ImageBuilder/tests/BuildCommandTests.cs @@ -43,16 +43,29 @@ public async Task BuildCommand_ImageInfoOutput_Basic() const string runtimeDepsRepo = "runtime-deps"; const string runtimeRepo = "runtime"; const string aspnetRepo = "aspnet"; + string runtimeDepsDigest = $"{runtimeDepsRepo}@sha256:c74364a9f125ca612f9a67e4a0551937b7a37c82fabb46172c4867b73edd638c"; string runtimeDigest = $"{runtimeRepo}@sha256:adc914a9f125ca612f9a67e4a0551937b7a37c82fabb46172c4867b73ed99227"; string aspnetDigest = $"{aspnetRepo}@sha256:781914a9f125ca612f9a67e4a0551937b7a37c82fabb46172c4867b73ed0045a"; - IEnumerable runtimeDepsLayers = new[] { - "sha256:777b2c648970480f50f5b4d0af8f9a8ea798eea43dbcf40ce4a8c7118736bdcf", - "sha256:b9dfc8eed8d66f1eae8ffe46be9a26fe047a7f6554e9dbc2df9da211e59b4786" }; - IEnumerable runtimeLayers = - runtimeDepsLayers.Concat(new[] { "sha256:466982335a8bacfe63b8f75a2e8c6484dfa7f7e92197550643b3c1457fa445b4" }); - IEnumerable aspnetLayers = - runtimeLayers.Concat(new[] { "sha256:d305fbfc4bd0d9f38662e979dced9831e3b5e4d85442397a8ec0a0e7bcf5458b" }); + + IEnumerable runtimeDepsLayers = + [ + new Layer("sha256:777b2c648970480f50f5b4d0af8f9a8ea798eea43dbcf40ce4a8c7118736bdcf", 0), + new Layer("sha256:b9dfc8eed8d66f1eae8ffe46be9a26fe047a7f6554e9dbc2df9da211e59b4786", 0) + ]; + + IEnumerable runtimeLayers = + [ + ..runtimeDepsLayers, + new Layer("sha256:466982335a8bacfe63b8f75a2e8c6484dfa7f7e92197550643b3c1457fa445b4", 0), + ]; + + IEnumerable aspnetLayers = + [ + ..runtimeLayers, + new Layer("sha256:d305fbfc4bd0d9f38662e979dced9831e3b5e4d85442397a8ec0a0e7bcf5458b", 0) + ]; + const string tag = "tag"; const string baseImageRepo = "baserepo"; string baseImageTag = $"{baseImageRepo}:basetag"; diff --git a/src/Microsoft.DotNet.ImageBuilder/tests/Helpers/ImageInfoHelper.cs b/src/Microsoft.DotNet.ImageBuilder/tests/Helpers/ImageInfoHelper.cs index ff89f09d8..c340a5889 100644 --- a/src/Microsoft.DotNet.ImageBuilder/tests/Helpers/ImageInfoHelper.cs +++ b/src/Microsoft.DotNet.ImageBuilder/tests/Helpers/ImageInfoHelper.cs @@ -137,7 +137,7 @@ public static PlatformData CreatePlatform( List simpleTags = null, string baseImageDigest = null, DateTime? created = null, - List layers = null, + List layers = null, bool isUnchanged = false, string commitUrl = "") { diff --git a/src/Microsoft.DotNet.ImageBuilder/tests/Helpers/ManifestServiceHelper.cs b/src/Microsoft.DotNet.ImageBuilder/tests/Helpers/ManifestServiceHelper.cs index 95baefdda..bae177b99 100644 --- a/src/Microsoft.DotNet.ImageBuilder/tests/Helpers/ManifestServiceHelper.cs +++ b/src/Microsoft.DotNet.ImageBuilder/tests/Helpers/ManifestServiceHelper.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Microsoft.DotNet.ImageBuilder.Models.Image; using Microsoft.DotNet.ImageBuilder.Tests.Helpers; using Moq; @@ -13,7 +14,7 @@ internal static class ManifestServiceHelper { public record ImageDigestResults(string Image, string Digest, int OnCallCount = 1); - public record ImageLayersResults(string Image, IEnumerable Layers); + public record ImageLayersResults(string Image, IEnumerable Layers); public static Mock CreateManifestServiceFactoryMock( IEnumerable? localImageDigestResults = null, @@ -67,7 +68,7 @@ public static Mock CreateManifestServiceMock( .ReturnsAsync(callIndex => callIndex >= onCallIndex ? digest : throw new Exception()); } - foreach ((string image, IEnumerable layers) in imageLayersResults) + foreach ((string image, IEnumerable layers) in imageLayersResults) { manifestServiceMock .Setup(o => o.GetImageLayersAsync(image, false)) diff --git a/src/Microsoft.DotNet.ImageBuilder/tests/ImageArtifactDetailsTests.cs b/src/Microsoft.DotNet.ImageBuilder/tests/ImageArtifactDetailsTests.cs new file mode 100644 index 000000000..4f2277d4e --- /dev/null +++ b/src/Microsoft.DotNet.ImageBuilder/tests/ImageArtifactDetailsTests.cs @@ -0,0 +1,290 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.DotNet.ImageBuilder.Models.Image; +using Shouldly; +using System.Linq; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.DotNet.ImageBuilder.Tests; + +public class ImageArtifactDetailsTests(ITestOutputHelper outputHelper) +{ + private readonly ITestOutputHelper _outputHelper = outputHelper; + + [Fact] + public void CanReadJsonSchemaVersion1() + { + ImageArtifactDetails details = ImageArtifactDetails.FromJson(JsonSchemaVersion1); + details.ShouldNotBeNull(); + + // When reading schema version 1.0, it will be automatically converted to version 2.0 + details.SchemaVersion.ShouldBe("2.0"); + + details.Repos.ShouldHaveSingleItem(); + RepoData repo = details.Repos.First(); + + repo.Images.ShouldHaveSingleItem(); + ImageData image = repo.Images.First(); + image.Platforms.Count.ShouldBe(2); + + for (int platformIndex = 0; platformIndex < image.Platforms.Count; platformIndex++) + { + PlatformData platform = image.Platforms[platformIndex]; + + platform.Layers.Count.ShouldBe(2); + + for (int layerIndex = 0; layerIndex < platform.Layers.Count; layerIndex++) + { + Layer layer = platform.Layers[layerIndex]; + string expectedLayerSha = $"sha256:platform{platformIndex + 1}layer{layerIndex + 1}sha"; + layer.ShouldBe(new Layer(Digest: expectedLayerSha, Size: 0)); + } + } + } + + [Fact] + public void CanReadJsonSchemaVersion2() + { + ImageArtifactDetails details = ImageArtifactDetails.FromJson(JsonSchemaVersion2); + details.ShouldNotBeNull(); + + // When reading schema version 1.0, it will be automatically converted to version 2.0 + details.SchemaVersion.ShouldBe("2.0"); + + details.Repos.ShouldHaveSingleItem(); + RepoData repo = details.Repos.First(); + + repo.Images.ShouldHaveSingleItem(); + ImageData image = repo.Images.First(); + image.Platforms.Count.ShouldBe(2); + + for (int platformIndex = 0; platformIndex < image.Platforms.Count; platformIndex++) + { + PlatformData platform = image.Platforms[platformIndex]; + + platform.Layers.Count.ShouldBe(2); + + for (int layerIndex = 0; layerIndex < platform.Layers.Count; layerIndex++) + { + Layer layer = platform.Layers[layerIndex]; + string expectedLayerSha = $"sha256:platform{platformIndex + 1}layer{layerIndex + 1}sha"; + layer.Digest.ShouldBe(expectedLayerSha); + layer.Size.ShouldBeGreaterThan(0); + } + } + } + + [Fact] + public void CanWriteJsonSchemaVersion2() + { + ImageArtifactDetails imageInfo = new() + { + Repos = + [ + new RepoData() + { + Repo = "testrepo", + Images = + [ + new ImageData() + { + Platforms = + [ + new PlatformData() + { + Layers = + [ + new Layer(Digest: "sha256:layer1", Size: 100), + new Layer(Digest: "sha256:layer2", Size: 200) + ] + } + ] + } + ] + } + ] + }; + + string expectedJson = + """ + { + "schemaVersion": "2.0", + "repos": [ + { + "repo": "testrepo", + "images": [ + { + "platforms": [ + { + "dockerfile": "", + "digest": "", + "osType": "", + "osVersion": "", + "architecture": "", + "created": "0001-01-01T00:00:00", + "commitUrl": "", + "layers": [ + { + "digest": "sha256:layer1", + "size": 100 + }, + { + "digest": "sha256:layer2", + "size": 200 + } + ] + } + ] + } + ] + } + ] + } + """; + + string actualJson = JsonHelper.SerializeObject(imageInfo); + + _outputHelper.WriteLine("Expected JSON:"); + _outputHelper.WriteLine(expectedJson); + _outputHelper.WriteLine("\nActual JSON:"); + _outputHelper.WriteLine(actualJson); + + // Normalize line endings and compare + actualJson.Replace("\r\n", "\n").ShouldBe(expectedJson.Replace("\r\n", "\n")); + } + + #region Test Data + + private const string JsonSchemaVersion1 = + """ + { + "schemaVersion": "1.0", + "repos": [ + { + "repo": "repo1", + "images": [ + { + "productVersion": "1.0.0", + "manifest": { + "digest": "repo1@sha256:manifestdigest", + "created": "2024-01-01T00:00:00.0000000Z", + "sharedTags": [ + "tag1", + "tag2", + "latest" + ] + }, + "platforms": [ + { + "dockerfile": "path/to/Dockerfile1", + "simpleTags": [ + "tag1-arch1", + "tag2-arch1" + ], + "digest": "repo1@sha256:platform1digest", + "baseImageDigest": "baseimage@sha256:basedigest1", + "osType": "OS1", + "osVersion": "osversion1", + "architecture": "arch1", + "created": "2024-01-01T00:00:01.0000000Z", + "commitUrl": "https://example.com/commit1", + "layers": [ + "sha256:platform1layer1sha", + "sha256:platform1layer2sha" + ] + }, + { + "dockerfile": "path/to/Dockerfile2", + "simpleTags": [ + "tag1-arch2", + "tag2-arch2" + ], + "digest": "repo1@sha256:platform2digest", + "baseImageDigest": "baseimage@sha256:basedigest2", + "osType": "OS2", + "osVersion": "osversion2", + "architecture": "arch2", + "created": "2024-01-01T00:00:02.0000000Z", + "commitUrl": "https://example.com/commit2", + "layers": [ + "sha256:platform2layer1sha", + "sha256:platform2layer2sha" + ] + } + ] + } + ] + } + ] + } + """; + + private const string JsonSchemaVersion2 = + """ + { + "schemaVersion": "2.0", + "repos": [ + { + "repo": "repo1", + "images": [ + { + "productVersion": "1.0.0", + "manifest": { + "digest": "repo1@sha256:manifestdigest", + "created": "2024-01-01T00:00:00.0000000Z", + "sharedTags": [ + "tag1", + "tag2", + "latest" + ] + }, + "platforms": [ + { + "dockerfile": "path/to/Dockerfile1", + "simpleTags": [ + "tag1-arch1", + "tag2-arch1" + ], + "digest": "repo1@sha256:platform1digest", + "baseImageDigest": "baseimage@sha256:basedigest1", + "osType": "OS1", + "osVersion": "osversion1", + "architecture": "arch1", + "created": "2024-01-01T00:00:01.0000000Z", + "commitUrl": "https://example.com/commit1", + "layers": [ + { "digest": "sha256:platform1layer1sha", "size": 1 }, + { "digest": "sha256:platform1layer2sha", "size": 2 } + ] + }, + { + "dockerfile": "path/to/Dockerfile2", + "simpleTags": [ + "tag1-arch2", + "tag2-arch2" + ], + "digest": "repo1@sha256:platform2digest", + "baseImageDigest": "baseimage@sha256:basedigest2", + "osType": "OS2", + "osVersion": "osversion2", + "architecture": "arch2", + "created": "2024-01-01T00:00:02.0000000Z", + "commitUrl": "https://example.com/commit2", + "layers": [ + { "digest": "sha256:platform2layer1sha", "size": 3 }, + { "digest": "sha256:platform2layer2sha", "size": 4 } + ] + } + ] + } + ] + } + ] + } + """; + + #endregion +} diff --git a/src/Microsoft.DotNet.ImageBuilder/tests/IngestKustoImageInfoCommandTests.cs b/src/Microsoft.DotNet.ImageBuilder/tests/IngestKustoImageInfoCommandTests.cs index 878e67cda..f606443d0 100644 --- a/src/Microsoft.DotNet.ImageBuilder/tests/IngestKustoImageInfoCommandTests.cs +++ b/src/Microsoft.DotNet.ImageBuilder/tests/IngestKustoImageInfoCommandTests.cs @@ -95,10 +95,10 @@ public async Task IngestKustoImageInfoCommand_MultipleRepos() { "t1" }, - layers: new List + layers: new List { - "qwe", - "asd" + new Layer("qwe", 123), + new Layer("asd", 456) }) }, { @@ -110,9 +110,9 @@ public async Task IngestKustoImageInfoCommand_MultipleRepos() { "t2" }, - layers: new List + layers: new List { - "qwe" + new Layer("qwe", 123) }) } }, @@ -137,9 +137,9 @@ public async Task IngestKustoImageInfoCommand_MultipleRepos() { "t3" }, - layers: new List + layers: new List { - "zxc" + new Layer("zxc", 789) }) }, ProductVersion = "2.0.5" @@ -159,10 +159,10 @@ public async Task IngestKustoImageInfoCommand_MultipleRepos() expectedImageData = expectedImageData.NormalizeLineEndings(Environment.NewLine).Trim(); string expectedLayerData = -@"""qwe"",""0"",""2"",""def"",""amd64"",""Linux"",""Ubuntu 24.04"",""1.0.2"",""1.0/sdk/os/Dockerfile"",""r1"",""2020-04-20 21:56:50"" -""asd"",""0"",""1"",""def"",""amd64"",""Linux"",""Ubuntu 24.04"",""1.0.2"",""1.0/sdk/os/Dockerfile"",""r1"",""2020-04-20 21:56:50"" -""qwe"",""0"",""1"",""ghi"",""amd64"",""Linux"",""Ubuntu 24.04"",""1.0.2"",""1.0/sdk/os2/Dockerfile"",""r1"",""2020-04-20 21:56:56"" -""zxc"",""0"",""1"",""jkl"",""amd64"",""Linux"",""Ubuntu 24.04"",""2.0.5"",""2.0/sdk/os/Dockerfile"",""r2"",""2020-04-20 21:56:58"""; +@"""qwe"",""123"",""2"",""def"",""amd64"",""Linux"",""Ubuntu 24.04"",""1.0.2"",""1.0/sdk/os/Dockerfile"",""r1"",""2020-04-20 21:56:50"" +""asd"",""456"",""1"",""def"",""amd64"",""Linux"",""Ubuntu 24.04"",""1.0.2"",""1.0/sdk/os/Dockerfile"",""r1"",""2020-04-20 21:56:50"" +""qwe"",""123"",""1"",""ghi"",""amd64"",""Linux"",""Ubuntu 24.04"",""1.0.2"",""1.0/sdk/os2/Dockerfile"",""r1"",""2020-04-20 21:56:56"" +""zxc"",""789"",""1"",""jkl"",""amd64"",""Linux"",""Ubuntu 24.04"",""2.0.5"",""2.0/sdk/os/Dockerfile"",""r2"",""2020-04-20 21:56:58"""; expectedLayerData = expectedLayerData.NormalizeLineEndings(Environment.NewLine).Trim(); await ValidateExecuteAsync(tempFolderContext, manifestPath, srcImageArtifactDetails, expectedImageData, expectedLayerData); @@ -229,9 +229,9 @@ public async Task SyndicatedTag() "t2", "t3" }, - layers: new List + layers: new List { - "zxc" + new Layer("zxc", 0) }) }, ProductVersion = "1.0.5" diff --git a/src/Microsoft.DotNet.ImageBuilder/tests/Microsoft.DotNet.ImageBuilder.Tests.csproj b/src/Microsoft.DotNet.ImageBuilder/tests/Microsoft.DotNet.ImageBuilder.Tests.csproj index d90b7c841..55deca0a6 100644 --- a/src/Microsoft.DotNet.ImageBuilder/tests/Microsoft.DotNet.ImageBuilder.Tests.csproj +++ b/src/Microsoft.DotNet.ImageBuilder/tests/Microsoft.DotNet.ImageBuilder.Tests.csproj @@ -9,6 +9,7 @@ + all