Skip to content
Draft
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
179 changes: 156 additions & 23 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);
}
522 changes: 292 additions & 230 deletions src/ImageBuilder/Commands/BuildCommand.cs

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion src/ImageBuilder/DockerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public void CreateManifestList(string manifestListTag, IEnumerable<string> image
string platform,
IEnumerable<string> tags,
IDictionary<string, string?> buildArgs,
IDictionary<string, string> labels,
IEnumerable<string> dockerBuildOptions,
bool isRetryEnabled,
bool isDryRun)
Expand All @@ -44,12 +45,16 @@ public void CreateManifestList(string manifestListTag, IEnumerable<string> image
.Select(buildArg => $" --build-arg {buildArg.Key}={buildArg.Value}");
string buildArgsString = string.Join(string.Empty, buildArgList);

IEnumerable<string> labelList = labels
.Select(label => $" --label {label.Key}={label.Value}");
string labelsString = string.Join(string.Empty, labelList);

IEnumerable<string> dockerBuildOptionList = dockerBuildOptions
.Where(option => !string.IsNullOrWhiteSpace(option))
.Select(option => $" {option}");
string dockerBuildOptionsString = string.Join(string.Empty, dockerBuildOptionList);

string dockerArgs = $"build --platform {platform} {tagArgs} -f {dockerfilePath}{buildArgsString}{dockerBuildOptionsString} {buildContextPath}";
string dockerArgs = $"build --platform {platform} {tagArgs} -f {dockerfilePath}{buildArgsString}{labelsString}{dockerBuildOptionsString} {buildContextPath}";

if (isRetryEnabled)
{
Expand Down
28 changes: 21 additions & 7 deletions src/ImageBuilder/DockerServiceCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.DotNet.ImageBuilder.Models.Manifest;

namespace Microsoft.DotNet.ImageBuilder
Expand All @@ -31,9 +29,25 @@ public DockerServiceCache(IDockerService inner)
public Architecture Architecture => _inner.Architecture;

public string? BuildImage(
string dockerfilePath, string buildContextPath, string platform, IEnumerable<string> tags,
IDictionary<string, string?> buildArgs, IEnumerable<string> dockerBuildOptions, bool isRetryEnabled, bool isDryRun) =>
_inner.BuildImage(dockerfilePath, buildContextPath, platform, tags, buildArgs, dockerBuildOptions, isRetryEnabled, isDryRun);
string dockerfilePath,
string buildContextPath,
string platform,
IEnumerable<string> tags,
IDictionary<string, string?> buildArgs,
IDictionary<string, string> labels,
IEnumerable<string> dockerBuildOptions,
bool isRetryEnabled,
bool isDryRun) =>
_inner.BuildImage(
dockerfilePath,
buildContextPath,
platform,
tags,
buildArgs,
labels,
dockerBuildOptions,
isRetryEnabled,
isDryRun);

public (Architecture Arch, string? Variant) GetImageArch(string image, bool isDryRun) =>
_architectureCache.GetOrAdd(image, _ =>_inner.GetImageArch(image, isDryRun));
Expand All @@ -49,10 +63,10 @@ public DateTime GetCreatedDate(string image, bool isDryRun) =>

public long GetImageSize(string image, bool isDryRun) =>
_imageSizeCache.GetOrAdd(image, _ => _inner.GetImageSize(image, isDryRun));

public bool LocalImageExists(string tag, bool isDryRun) =>
_localImageExistsCache.GetOrAdd(tag, _ => _inner.LocalImageExists(tag, isDryRun));

public void PullImage(string image, string? platform, bool isDryRun)
{
_pulledImages.GetOrAdd(image, _ =>
Expand Down
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: 8 additions & 1 deletion src/ImageBuilder/IDockerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.DotNet.ImageBuilder.Models.Manifest;

namespace Microsoft.DotNet.ImageBuilder
Expand All @@ -23,12 +22,20 @@ public interface IDockerService

void CreateManifestList(string manifestListTag, IEnumerable<string> images, bool isDryRun);

/// <summary>
/// Builds a Docker image.
/// </summary>
/// <param name="labels">
/// Labels to apply to the image. Each entry translates to a <c>--label key=value</c> option on the
/// <c>docker build</c> command.
/// </param>
string? BuildImage(
string dockerfilePath,
string buildContextPath,
string platform,
IEnumerable<string> tags,
IDictionary<string, string?> buildArgs,
IDictionary<string, string> labels,
IEnumerable<string> dockerBuildOptions,
bool isRetryEnabled,
bool isDryRun);
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
16 changes: 16 additions & 0 deletions src/ImageBuilder/ImageBuilderLabels.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// 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>
/// Custom (non-OCI) image label keys applied to built images.
/// </summary>
public static class ImageBuilderLabels
{
/// <summary>
/// Path of the Dockerfile the image was built from, relative to the root of the source repository.
/// </summary>
public const string Dockerfile = "com.microsoft.imagebuilder.dockerfile";
}
32 changes: 32 additions & 0 deletions src/ImageBuilder/OciAnnotations.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// 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>
/// Well-known OCI image annotation keys applied to built images as Docker labels.
/// See https://github.com/opencontainers/image-spec/blob/main/annotations.md.
/// </summary>
public static class OciAnnotations
{
/// <summary>
/// URL of the source code repository the image was built from.
/// </summary>
public const string Source = "org.opencontainers.image.source";

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

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

/// <summary>
/// Digest of the base image the image was built from.
/// </summary>
public const string BaseDigest = "org.opencontainers.image.base.digest";
}