Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
45d274f
Add artifact output service
lbussell Jul 28, 2026
59945ee
Resolve merge output from artifact staging
lbussell Jul 28, 2026
a49b685
Resolve EOL data output from artifact staging
lbussell Jul 28, 2026
a1d49e7
Resolve annotation output from artifact staging
lbussell Jul 28, 2026
e88e9b2
Stage output path templates with ImageBuilder
lbussell Jul 28, 2026
d4d56e3
Document artifact output contract
lbussell Jul 28, 2026
b268151
Remove redundant output path check
lbussell Jul 28, 2026
95f1a8c
Simplify output directory creation
lbussell Jul 28, 2026
5b4d9c8
Resolve output directories through filesystem
lbussell Jul 28, 2026
b40d37a
Clarify artifact path resolution
lbussell Jul 28, 2026
caab7cd
Remove null suppression from output paths
lbussell Jul 28, 2026
119b8bb
Keep lexical paths outside filesystem abstraction
lbussell Jul 28, 2026
55aea77
Write EOL outputs through shared service
lbussell Jul 28, 2026
ca741ed
Explain artifact path resolution
lbussell Jul 28, 2026
4c7b638
Generalize artifact path service
lbussell Jul 28, 2026
d16ca4c
Resolve trim input from artifact staging
lbussell Jul 28, 2026
13a2133
Resolve copy input from artifact staging
lbussell Jul 28, 2026
633a0d7
Resolve manifest list input from artifact staging
lbussell Jul 28, 2026
0c6ae8b
Resolve merge inputs from artifact staging
lbussell Jul 28, 2026
7a4e5fb
Resolve Kusto input from artifact staging
lbussell Jul 28, 2026
bb4b1de
Resolve EOL inputs from artifact staging
lbussell Jul 28, 2026
e7506d3
Resolve image info publish input
lbussell Jul 28, 2026
7395818
Resolve notification input from artifact staging
lbussell Jul 28, 2026
547c5a0
Resolve ingestion input from artifact staging
lbussell Jul 28, 2026
9dd8cce
Resolve annotation input from artifact staging
lbussell Jul 28, 2026
e3fa0f7
Use artifact-relative publish inputs
lbussell Jul 28, 2026
a5e290a
Resolve annotation ingestion input
lbussell Jul 28, 2026
78798e8
Use relative annotation ingestion input
lbussell Jul 28, 2026
ac76b37
Consolidate publish stage artifacts
lbussell Jul 28, 2026
1be9918
Use artifact terminology in path resolution
lbussell Jul 28, 2026
c12d061
Default annotation upload switch to false
lbussell Jul 28, 2026
365117e
Clarify publish artifact attempt name
lbussell Jul 28, 2026
3bfaae2
Shorten publish artifact display name
lbussell Jul 28, 2026
4bde9d8
Require paths within artifact staging
lbussell Jul 28, 2026
08c2732
Clarify artifact service purpose
lbussell Aug 4, 2026
0d909e4
Document artifact path behavior
lbussell Aug 4, 2026
ede1020
Don't overwrite options
lbussell Aug 5, 2026
da28f90
Simplify artifact test helper setup
lbussell Aug 7, 2026
fa3f544
Always upload artifacts
lbussell Aug 7, 2026
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
5 changes: 3 additions & 2 deletions src/ImageBuilder.Tests/AnnotateEolDigestsCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,12 @@ private AnnotateEolDigestsCommand InitializeCommand(
AnnotateEolDigestsCommand command = new(
loggerServiceMock.Object,
lifecycleMetadataServiceMock.Object,
Mock.Of<IRegistryCredentialsProvider>());
Mock.Of<IRegistryCredentialsProvider>(),
TestHelper.CreateArtifactService(tempFolderContext.Path));
command.Options.RepoPrefix = RepoPrefix;
command.Options.AcrName = AcrName;
command.Options.EolDigestsListPath = eolDigestsListPath;
command.Options.AnnotationDigestsOutputPath = Path.Combine(tempFolderContext.Path, AnnotationsOutputPath);
command.Options.AnnotationDigestsOutputPath = AnnotationsOutputPath;
return command;
}

Expand Down
72 changes: 72 additions & 0 deletions src/ImageBuilder.Tests/ArtifactServiceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// 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 System;
using System.IO;
using Microsoft.DotNet.ImageBuilder.Configuration;
using Microsoft.DotNet.ImageBuilder.Tests.Helpers;
using Microsoft.Extensions.Options;
using Shouldly;

namespace Microsoft.DotNet.ImageBuilder.Tests;

[TestClass]
public class ArtifactServiceTests
{
private static readonly string s_outputRoot = Path.Combine(Path.GetTempPath(), "artifacts");

[TestMethod]
public void ResolvePath_RelativePath_ResolvesUnderArtifactStagingDirectory()
{
var service = CreateService(new InMemoryFileSystem());

string path = service.ResolvePath(Path.Combine("image-info", "input.json"));

path.ShouldBe(Path.Combine(s_outputRoot, "image-info", "input.json"));
}

[TestMethod]
public void WriteAllText_WritesUnderArtifactStagingDirectory()
{
var fileSystem = new InMemoryFileSystem();
var service = CreateService(fileSystem);
string expectedPath = Path.Combine(s_outputRoot, "image-info", "output.json");

service.WriteAllText(Path.Combine("image-info", "output.json"), "contents");

fileSystem.DirectoriesCreated.ShouldContain(Path.GetDirectoryName(expectedPath));
fileSystem.GetFileText(expectedPath).ShouldBe("contents");
}

[TestMethod]
public void WriteAllText_MissingArtifactStagingDirectory_Throws()
{
var service = new ArtifactService(
new InMemoryFileSystem(),
Options.Create(new BuildConfiguration()));

InvalidOperationException exception = Should.Throw<InvalidOperationException>(
() => service.WriteAllText("output.json", "contents"));

exception.Message.ShouldContain(nameof(BuildConfiguration.ArtifactStagingDirectory));
}

[TestMethod]
public void WriteAllText_PathOutsideArtifactStagingDirectory_Throws()
{
var service = CreateService(new InMemoryFileSystem());

Should.Throw<ArgumentException>(
() => service.WriteAllText(Path.Combine("..", "output.json"), "contents"));
}

private static ArtifactService CreateService(IFileSystem fileSystem) =>
new(
fileSystem,
Options.Create(
new BuildConfiguration
{
ArtifactStagingDirectory = s_outputRoot
}));
}
35 changes: 21 additions & 14 deletions src/ImageBuilder.Tests/CopyAcrImagesCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,12 @@ public async Task CopyAcrImagesCommand_CustomDockerfileName()
CopyAcrImagesCommand command = new(
TestHelper.CreateManifestJsonService(),
copyImageServiceMock.Object,
Mock.Of<ILogger<CopyAcrImagesCommand>>());
Mock.Of<ILogger<CopyAcrImagesCommand>>(),
TestHelper.CreateArtifactService(tempFolderContext.Path));
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";
command.Options.ImageInfoPath = Path.Combine(tempFolderContext.Path, "image-info.json");

const string runtimeRelativeDir = "1.0/runtime/os";
Directory.CreateDirectory(Path.Combine(tempFolderContext.Path, runtimeRelativeDir));
Expand Down Expand Up @@ -127,11 +128,12 @@ public async Task CopyAcrImagesCommand_SharedDockerfile()
var command = new CopyAcrImagesCommand(
TestHelper.CreateManifestJsonService(),
copyImageServiceMock.Object,
Mock.Of<ILogger<CopyAcrImagesCommand>>());
Mock.Of<ILogger<CopyAcrImagesCommand>>(),
TestHelper.CreateArtifactService(tempFolderContext.Path));
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";
command.Options.ImageInfoPath = Path.Combine(tempFolderContext.Path, "image-info.json");

const string runtimeRelativeDir = "1.0/runtime/os";
Directory.CreateDirectory(Path.Combine(tempFolderContext.Path, runtimeRelativeDir));
Expand Down Expand Up @@ -229,11 +231,12 @@ public async Task CopyAcrImagesCommand_RuntimeDepsSharing()
var command = new CopyAcrImagesCommand(
TestHelper.CreateManifestJsonService(),
copyImageServiceMock.Object,
Mock.Of<ILogger<CopyAcrImagesCommand>>());
Mock.Of<ILogger<CopyAcrImagesCommand>>(),
TestHelper.CreateArtifactService(tempFolderContext.Path));
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";
command.Options.ImageInfoPath = Path.Combine(tempFolderContext.Path, "image-info.json");

string dockerfileRelativePath = DockerfileHelper.CreateDockerfile("3.1/runtime-deps/os", tempFolderContext);

Expand Down Expand Up @@ -342,11 +345,12 @@ public async Task SyndicatedTags()
var command = new CopyAcrImagesCommand(
TestHelper.CreateManifestJsonService(),
copyImageServiceMock.Object,
Mock.Of<ILogger<CopyAcrImagesCommand>>());
Mock.Of<ILogger<CopyAcrImagesCommand>>(),
TestHelper.CreateArtifactService(tempFolderContext.Path));
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";
command.Options.ImageInfoPath = Path.Combine(tempFolderContext.Path, "image-info.json");

const string runtimeRelativeDir = "1.0/runtime/os";
Directory.CreateDirectory(Path.Combine(tempFolderContext.Path, runtimeRelativeDir));
Expand Down Expand Up @@ -456,11 +460,12 @@ public async Task CopyAcrImagesCommand_CopiesManifestListTags()
CopyAcrImagesCommand command = new(
TestHelper.CreateManifestJsonService(),
copyImageServiceMock.Object,
Mock.Of<ILogger<CopyAcrImagesCommand>>());
Mock.Of<ILogger<CopyAcrImagesCommand>>(),
TestHelper.CreateArtifactService(tempFolderContext.Path));
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";
command.Options.ImageInfoPath = Path.Combine(tempFolderContext.Path, "image-info.json");

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

Expand Down Expand Up @@ -562,11 +567,12 @@ public async Task CopyAcrImagesCommand_CopiesSyndicatedManifestListTags()
CopyAcrImagesCommand command = new(
TestHelper.CreateManifestJsonService(),
copyImageServiceMock.Object,
Mock.Of<ILogger<CopyAcrImagesCommand>>());
Mock.Of<ILogger<CopyAcrImagesCommand>>(),
TestHelper.CreateArtifactService(tempFolderContext.Path));
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";
command.Options.ImageInfoPath = Path.Combine(tempFolderContext.Path, "image-info.json");

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

Expand Down Expand Up @@ -678,11 +684,12 @@ public async Task CopyAcrImagesCommand_SkipsManifestListsWithNoManifestData()
CopyAcrImagesCommand command = new(
TestHelper.CreateManifestJsonService(),
copyImageServiceMock.Object,
Mock.Of<ILogger<CopyAcrImagesCommand>>());
Mock.Of<ILogger<CopyAcrImagesCommand>>(),
TestHelper.CreateArtifactService(tempFolderContext.Path));
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";
command.Options.ImageInfoPath = Path.Combine(tempFolderContext.Path, "image-info.json");

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

Expand Down
3 changes: 2 additions & 1 deletion src/ImageBuilder.Tests/CreateManifestListCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,8 @@ private static CreateManifestListCommand CreateCommand(
copyImageServiceMock.Object,
Mock.Of<ILogger<CreateManifestListCommand>>(),
dateTimeService,
Mock.Of<IRegistryCredentialsProvider>());
Mock.Of<IRegistryCredentialsProvider>(),
TestHelper.CreateArtifactService(Path.GetTempPath()));

private static void SetupCommand(
CreateManifestListCommand command,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1148,10 +1148,11 @@ private static GenerateEolAnnotationDataForPublishCommand InitializeCommand(
acrClientFactory: registryClientFactory,
acrContentClientFactory: registryContentClientFactory,
lifecycleMetadataService: lifecycleMetadataService,
registryCredentialsProvider: Mock.Of<IRegistryCredentialsProvider>());
registryCredentialsProvider: Mock.Of<IRegistryCredentialsProvider>(),
artifactService: TestHelper.CreateArtifactService(Path.GetDirectoryName(newEolDigestsListPath)));
command.Options.OldImageInfoPath = oldImageInfoPath;
command.Options.NewImageInfoPath = newImageInfoPath;
command.Options.EolDigestsListPath = newEolDigestsListPath;
command.Options.EolDigestsListPath = Path.GetFileName(newEolDigestsListPath);
command.Options.RegistryOptions = new() { RepoPrefix = repoPrefix, Registry = AcrName };
return command;
}
Expand Down
12 changes: 12 additions & 0 deletions src/ImageBuilder.Tests/Helpers/TestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.DotNet.ImageBuilder.Configuration;
using Microsoft.Extensions.Options;

namespace Microsoft.DotNet.ImageBuilder.Tests.Helpers
{
Expand All @@ -20,6 +22,16 @@ public static IManifestJsonService CreateManifestJsonService() =>
fileSystem: new FileSystem(),
logger: new LoggerFactory().CreateLogger<ManifestJsonService>());

public static IArtifactService CreateArtifactService(string artifactStagingDirectory)
{
BuildConfiguration buildConfiguration = new()
{
ArtifactStagingDirectory = artifactStagingDirectory
};

return new ArtifactService(new FileSystem(), Options.Create(buildConfiguration));
}

public static TempFolderContext UseTempFolder()
{
return new TempFolderContext();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,11 @@ private async Task ValidateExecuteAsync(
.Callback<string, string, string, string, IServiceConnection>(
(csv, _, _, table, _) => ingestedData.Add(table, csv));

IngestKustoImageInfoCommand command = new(TestHelper.CreateManifestJsonService(), Mock.Of<ILogger<IngestKustoImageInfoCommand>>(), kustoClientMock.Object);
IngestKustoImageInfoCommand command = new(
TestHelper.CreateManifestJsonService(),
Mock.Of<ILogger<IngestKustoImageInfoCommand>>(),
kustoClientMock.Object,
TestHelper.CreateArtifactService(tempFolderContext.Path));
command.Options.ImageInfoPath = imageInfoPath;
command.Options.Manifest = manifestPath;
command.Options.ImageTable = "ImageInfo";
Expand Down
Loading