Skip to content
Open
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
255 changes: 230 additions & 25 deletions src/ImageBuilder.Tests/BuildCommandTests.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// 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.Collections.Generic;
using System.Linq;
using Microsoft.DotNet.ImageBuilder.Models.Image;

namespace Microsoft.DotNet.ImageBuilder.Commands.Build;

/// <summary>
/// Extension methods for image artifact details used during build command processing.
/// </summary>
internal static class ImageArtifactDetailsExtensions
{
/// <summary>
/// Enumerates all platform data entries from image-info repo and image groups.
/// </summary>
/// <param name="imageArtifactDetails">The image artifact details to enumerate.</param>
/// <returns>All platform data entries contained in the image artifact details.</returns>
internal static IEnumerable<PlatformData> EnumeratePlatforms(this ImageArtifactDetails imageArtifactDetails) =>
imageArtifactDetails.Repos
.Where(repoData => repoData.Images != null)
.SelectMany(repoData => repoData.Images)
.SelectMany(imageData => imageData.Platforms);
}
516 changes: 298 additions & 218 deletions src/ImageBuilder/Commands/BuildCommand.cs

Large diffs are not rendered by default.

35 changes: 20 additions & 15 deletions src/ImageBuilder/GitHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,31 +24,36 @@ public static class GitHelper

public static string GetCommitSha(string filePath, bool useFullHash = false)
{
// Don't make the assumption that the current working directory is a Git repository
// Find the Git repo that contains the file being checked.
DirectoryInfo directory = new FileInfo(filePath).Directory;
while (!directory.GetDirectories(".git").Any())
{
directory = directory.Parent;

if (directory is null)
{
throw new InvalidOperationException($"File '{filePath}' is not contained within a Git repository.");
}
}

filePath = Path.GetRelativePath(directory.FullName, filePath);
string repoRoot = GetRepoRoot(filePath);
filePath = Path.GetRelativePath(repoRoot, filePath);

string format = useFullHash ? "H" : "h";
return ExecuteHelper.Execute(
new ProcessStartInfo("git", $"log -1 --format=format:%{format} {filePath}")
{
WorkingDirectory = directory.FullName
WorkingDirectory = repoRoot
},
false,
$"Unable to retrieve the latest commit SHA for {filePath}");
}

// Don't make the assumption that the current working directory is a Git repository.
// Walk up from the given path to find the root of the containing Git repository.
public static string GetRepoRoot(string path)
{
DirectoryInfo directory = Directory.Exists(path) ? new DirectoryInfo(path) : new FileInfo(path).Directory;

// The repository root is marked by a ".git" entry. It's a directory in a normal
// checkout, but a file (a gitdir pointer) in linked worktrees and submodules.
while (!directory.EnumerateFileSystemInfos(".git").Any())
{
directory = directory.Parent
?? throw new InvalidOperationException($"'{path}' is not contained within a Git repository.");
}

return directory.FullName;
}

public static Uri GetArchiveUrl(IGitHubBranchRef branchRef) =>
new Uri($"https://github.com/{branchRef.Owner}/{branchRef.Repo}/archive/{branchRef.Branch}.zip");

Expand Down
3 changes: 3 additions & 0 deletions src/ImageBuilder/GitService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ public string GetCommitSha(string filePath, bool useFullHash = false)
return GitHelper.GetCommitSha(filePath, useFullHash);
}

/// <inheritdoc/>
public string GetRepoRoot(string path) => GitHelper.GetRepoRoot(path);

public IRepository CloneRepository(string sourceUrl, string workdirPath, CloneOptions options)
{
_logger.LogInformation($"Cloning repository {sourceUrl} to {workdirPath}");
Expand Down
9 changes: 9 additions & 0 deletions src/ImageBuilder/IGitService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ public interface IGitService
{
string GetCommitSha(string filePath, bool useFullHash = false);

/// <summary>
/// Gets the absolute path to the root of the Git repository that contains the given path.
/// </summary>
/// <param name="path">
/// An absolute path to a file or directory that resides within a Git repository's working tree.
/// </param>
/// <returns>The absolute path to the containing repository's root directory.</returns>
string GetRepoRoot(string path);

IRepository CloneRepository(string sourceUrl, string workdirPath, CloneOptions options);

void Stage(IRepository repository, string path);
Expand Down
42 changes: 42 additions & 0 deletions src/ImageBuilder/ImageBuilderAnnotations.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// 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.

namespace Microsoft.DotNet.ImageBuilder;

/// <summary>
/// Annotation keys, in ImageBuilder's own namespace, that describe how the subject image was built.
/// These are recorded on the image's referrer artifact.
/// </summary>
/// <remarks>
/// Standard <c>org.opencontainers.image.*</c> annotations are deliberately not used here because OCI
/// annotations describe the artifact they are placed on. On a referrer artifact they would describe the
/// referrer itself rather than the subject image, so a custom namespace is used to describe the subject.
/// </remarks>
public static class ImageBuilderAnnotations
{
/// <summary>
/// URL of the source code repository the image was built from.
/// </summary>
public const string Source = "vnd.microsoft.imagebuilder.source";

/// <summary>
/// Source control revision (commit) the image was built from.
/// </summary>
public const string Revision = "vnd.microsoft.imagebuilder.revision";

/// <summary>
/// Path of the Dockerfile the image was built from, relative to the root of the source repository.
/// </summary>
public const string Dockerfile = "vnd.microsoft.imagebuilder.dockerfile";

/// <summary>
/// Image reference of the base image the image was built from.
/// </summary>
public const string BaseName = "vnd.microsoft.imagebuilder.base.name";

/// <summary>
/// Digest of the base image the image was built from.
/// </summary>
public const string BaseDigest = "vnd.microsoft.imagebuilder.base.digest";
}
5 changes: 5 additions & 0 deletions src/ImageBuilder/Oras/OciArtifactType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ namespace Microsoft.DotNet.ImageBuilder.Oras;
/// </summary>
public static class OciArtifactType
{
/// <summary>
/// ImageBuilder metadata referrer for image build information stored in manifest annotations.
/// </summary>
public const string ImageInfo = "application/vnd.microsoft.imagebuilder.image-info.v1";

/// <summary>
/// Notary v2 signature envelope.
/// </summary>
Expand Down
Loading