Skip to content
Merged
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
289 changes: 289 additions & 0 deletions src/ImageBuilder.Tests/CopyAcrImagesCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -438,5 +438,294 @@ public async Task SyndicatedTags()

copyImageServiceMock.VerifyNoOtherCalls();
}

/// <summary>
/// Verifies that manifest list shared tags are copied alongside platform tags.
/// </summary>
[Fact]
public async Task CopyAcrImagesCommand_CopiesManifestListTags()
{
using TempFolderContext tempFolderContext = TestHelper.UseTempFolder();

Mock<ICopyImageService> copyImageServiceMock = new();

CopyAcrImagesCommand command = new(
TestHelper.CreateManifestJsonService(),
copyImageServiceMock.Object,
Mock.Of<ILogger<CopyAcrImagesCommand>>());
command.Options.Manifest = Path.Combine(tempFolderContext.Path, "manifest.json");
command.Options.SourceRepoPrefix = command.Options.RepoPrefix = "test/";
command.Options.SourceRegistry = SourceRegistry;
command.Options.ImageInfoPath = "image-info.json";

string dockerfileRelativePath = DockerfileHelper.CreateDockerfile("1.0/runtime/os", tempFolderContext);

Manifest manifest = CreateManifest(
CreateRepo("runtime",
CreateImage(
new Platform[]
{
CreatePlatform(dockerfileRelativePath, new string[] { "tag1" })
},
new Dictionary<string, Tag>
{
{ "shared1", new Tag() },
{ "shared2", new Tag() }
}))
);
manifest.Registry = DestinationRegistry;

File.WriteAllText(Path.Combine(tempFolderContext.Path, command.Options.Manifest),
JsonConvert.SerializeObject(manifest));

ImageArtifactDetails imageArtifactDetails = new ImageArtifactDetails
{
Repos =
{
new RepoData
{
Repo = "runtime",
Images =
{
new ImageData
{
Platforms =
{
CreatePlatform(
PathHelper.NormalizePath(dockerfileRelativePath),
simpleTags: new List<string> { "tag1" })
},
Manifest = new ManifestData
{
SharedTags = { "shared1", "shared2" }
}
}
}
}
}
};

File.WriteAllText(command.Options.ImageInfoPath, JsonConvert.SerializeObject(imageArtifactDetails));

command.LoadManifest();
await command.ExecuteAsync();

// Platform tag should be copied
copyImageServiceMock.Verify(o =>
o.ImportImageAsync(
new string[] { $"test/runtime:tag1" },
DestinationRegistry,
"test/runtime:tag1",
SourceRegistry,
null,
false));

// Manifest list shared tags should also be copied
copyImageServiceMock.Verify(o =>
o.ImportImageAsync(
new string[] { $"test/runtime:shared1" },
DestinationRegistry,
"test/runtime:shared1",
SourceRegistry,
null,
false));

copyImageServiceMock.Verify(o =>
o.ImportImageAsync(
new string[] { $"test/runtime:shared2" },
DestinationRegistry,
"test/runtime:shared2",
SourceRegistry,
null,
false));

copyImageServiceMock.VerifyNoOtherCalls();
}

/// <summary>
/// Verifies that syndicated manifest list shared tags are copied to the syndicated repo.
/// </summary>
[Fact]
public async Task CopyAcrImagesCommand_CopiesSyndicatedManifestListTags()
{
using TempFolderContext tempFolderContext = TestHelper.UseTempFolder();

Mock<ICopyImageService> copyImageServiceMock = new();

CopyAcrImagesCommand command = new(
TestHelper.CreateManifestJsonService(),
copyImageServiceMock.Object,
Mock.Of<ILogger<CopyAcrImagesCommand>>());
command.Options.Manifest = Path.Combine(tempFolderContext.Path, "manifest.json");
command.Options.SourceRepoPrefix = command.Options.RepoPrefix = "test/";
command.Options.SourceRegistry = SourceRegistry;
command.Options.ImageInfoPath = "image-info.json";

string dockerfileRelativePath = DockerfileHelper.CreateDockerfile("1.0/runtime/os", tempFolderContext);

Manifest manifest = CreateManifest(
CreateRepo("runtime",
CreateImage(
new Platform[]
{
CreatePlatform(dockerfileRelativePath, new string[] { "tag1" })
},
new Dictionary<string, Tag>
{
{
"shared1",
new Tag
{
Syndication = new TagSyndication
{
Repo = "runtime2",
DestinationTags = new string[] { "syn-shared1" }
}
}
}
}))
);
manifest.Registry = DestinationRegistry;

File.WriteAllText(Path.Combine(tempFolderContext.Path, command.Options.Manifest),
JsonConvert.SerializeObject(manifest));

ImageArtifactDetails imageArtifactDetails = new ImageArtifactDetails
{
Repos =
{
new RepoData
{
Repo = "runtime",
Images =
{
new ImageData
{
Platforms =
{
CreatePlatform(
PathHelper.NormalizePath(dockerfileRelativePath),
simpleTags: new List<string> { "tag1" })
},
Manifest = new ManifestData
{
SharedTags = { "shared1" }
}
}
}
}
}
};

File.WriteAllText(command.Options.ImageInfoPath, JsonConvert.SerializeObject(imageArtifactDetails));

command.LoadManifest();
await command.ExecuteAsync();

// Platform tag
copyImageServiceMock.Verify(o =>
o.ImportImageAsync(
new string[] { $"test/runtime:tag1" },
DestinationRegistry,
"test/runtime:tag1",
SourceRegistry,
null,
false));

// Primary manifest list shared tag
copyImageServiceMock.Verify(o =>
o.ImportImageAsync(
new string[] { $"test/runtime:shared1" },
DestinationRegistry,
"test/runtime:shared1",
SourceRegistry,
null,
false));

// Syndicated manifest list shared tag - source should be from syndicated repo
copyImageServiceMock.Verify(o =>
o.ImportImageAsync(
new string[] { $"test/runtime2:syn-shared1" },
DestinationRegistry,
"test/runtime2:syn-shared1",
SourceRegistry,
null,
false));

copyImageServiceMock.VerifyNoOtherCalls();
}

/// <summary>
/// Verifies that images without ManifestData do not produce manifest list tag copies.
/// </summary>
[Fact]
public async Task CopyAcrImagesCommand_SkipsManifestListsWithNoManifestData()
{
using TempFolderContext tempFolderContext = TestHelper.UseTempFolder();

Mock<ICopyImageService> copyImageServiceMock = new();

CopyAcrImagesCommand command = new(
TestHelper.CreateManifestJsonService(),
copyImageServiceMock.Object,
Mock.Of<ILogger<CopyAcrImagesCommand>>());
command.Options.Manifest = Path.Combine(tempFolderContext.Path, "manifest.json");
command.Options.SourceRepoPrefix = command.Options.RepoPrefix = "test/";
command.Options.SourceRegistry = SourceRegistry;
command.Options.ImageInfoPath = "image-info.json";

string dockerfileRelativePath = DockerfileHelper.CreateDockerfile("1.0/runtime/os", tempFolderContext);

Manifest manifest = CreateManifest(
CreateRepo("runtime",
CreateImage(
CreatePlatform(dockerfileRelativePath, new string[] { "tag1" })))
);
manifest.Registry = DestinationRegistry;

File.WriteAllText(Path.Combine(tempFolderContext.Path, command.Options.Manifest),
JsonConvert.SerializeObject(manifest));

// No ManifestData on the image - only platform tags
ImageArtifactDetails imageArtifactDetails = new ImageArtifactDetails
{
Repos =
{
new RepoData
{
Repo = "runtime",
Images =
{
new ImageData
{
Platforms =
{
CreatePlatform(
PathHelper.NormalizePath(dockerfileRelativePath),
simpleTags: new List<string> { "tag1" })
}
}
}
}
}
};

File.WriteAllText(command.Options.ImageInfoPath, JsonConvert.SerializeObject(imageArtifactDetails));

command.LoadManifest();
await command.ExecuteAsync();

// Only platform tag should be copied - no manifest list tags
copyImageServiceMock.Verify(o =>
o.ImportImageAsync(
new string[] { $"test/runtime:tag1" },
DestinationRegistry,
It.IsAny<string>(),
SourceRegistry,
null,
false));

copyImageServiceMock.VerifyNoOtherCalls();
}
}
}
10 changes: 7 additions & 3 deletions src/ImageBuilder.Tests/CopyImageServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public async Task ImportImageAsync_ExternalSourceRegistry_DoesNotRequireSourceRe
var mockOras = new Mock<IOrasService>();
mockOras
.Setup(o => o.GetReferrersAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Array.Empty<string>());
.ReturnsAsync(Array.Empty<ReferrerInfo>());

var service = new CopyImageService(
Mock.Of<ILogger<CopyImageService>>(),
Expand Down Expand Up @@ -115,7 +115,11 @@ public async Task ImportImageAsync_CopiesReferrersAlongWithSourceImage()
var mockOras = new Mock<IOrasService>();
mockOras
.Setup(o => o.GetReferrersAsync("myacr.azurecr.io/repo:tag", It.IsAny<CancellationToken>()))
.ReturnsAsync(["myacr.azurecr.io/repo@sha256:ref1", "myacr.azurecr.io/repo@sha256:ref2"]);
.ReturnsAsync(new List<ReferrerInfo>
{
new("myacr.azurecr.io/repo@sha256:ref1", "application/vnd.cncf.notary.signature"),
new("myacr.azurecr.io/repo@sha256:ref2", "application/vnd.cncf.notary.signature")
});

var service = new CopyImageService(
Mock.Of<ILogger<CopyImageService>>(),
Expand Down Expand Up @@ -181,7 +185,7 @@ public async Task ImportImageAsync_NoReferrers_ImportsOnlySourceImage()
var mockOras = new Mock<IOrasService>();
mockOras
.Setup(o => o.GetReferrersAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Array.Empty<string>());
.ReturnsAsync(Array.Empty<ReferrerInfo>());

var service = new CopyImageService(
Mock.Of<ILogger<CopyImageService>>(),
Expand Down
Loading
Loading