Skip to content

Commit

Permalink
Merge origin/v0.1 into main
Browse files Browse the repository at this point in the history
+ version bump to 0.2
  • Loading branch information
rainersigwald committed Aug 11, 2022
2 parents 139a7e1 + 0818510 commit 3eb955c
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 32 deletions.
38 changes: 33 additions & 5 deletions Microsoft.NET.Build.Containers/ContainerHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ public static class ContainerHelpers

private static Regex imageNameRegex = new Regex("^[a-z0-9]+([._-][a-z0-9]+)*(/[a-z0-9]+([._-][a-z0-9]+)*)*$");

/// <summary>
/// Matches if the string is not lowercase or numeric, or ., _, or -.
/// </summary>
private static Regex imageNameCharacters = new Regex("[^a-zA-Z0-9._-]");

/// <summary>
/// Given some "fully qualified" image name (e.g. mcr.microsoft.com/dotnet/runtime), return
/// a valid UriBuilder. This means appending 'https' if the URI is not absolute, otherwise UriBuilder will throw.
Expand Down Expand Up @@ -39,8 +44,8 @@ public static bool IsValidRegistry(string registryName)
{
// No scheme prefixed onto the registry
if (string.IsNullOrEmpty(registryName) ||
(!registryName.StartsWith("http://") &&
!registryName.StartsWith("https://") &&
(!registryName.StartsWith("http://") &&
!registryName.StartsWith("https://") &&
!registryName.StartsWith("docker://")))
{
return false;
Expand Down Expand Up @@ -89,9 +94,9 @@ public static bool IsValidImageTag(string imageTag)
/// <param name="containerName"></param>
/// <param name="containerTag"></param>
/// <returns>True if the parse was successful. When false is returned, all out vars are set to empty strings.</returns>
public static bool TryParseFullyQualifiedContainerName(string fullyQualifiedContainerName,
[NotNullWhen(true)] out string? containerRegistry,
[NotNullWhen(true)] out string? containerName,
public static bool TryParseFullyQualifiedContainerName(string fullyQualifiedContainerName,
[NotNullWhen(true)] out string? containerRegistry,
[NotNullWhen(true)] out string? containerName,
[NotNullWhen(true)] out string? containerTag)
{
Uri? uri = ContainerImageToUri(fullyQualifiedContainerName);
Expand All @@ -115,4 +120,27 @@ public static bool TryParseFullyQualifiedContainerName(string fullyQualifiedCont
containerTag = indexOfColon == -1 ? "" : image.Substring(indexOfColon + 1);
return true;
}

/// <summary>
/// Checks if a given container image name adheres to the image name spec. If not, and recoverable, then normalizes invalid characters.
/// </summary>
public static bool NormalizeImageName(string containerImageName, [NotNullWhen(false)] out string? normalizedImageName)
{
if (IsValidImageName(containerImageName))
{
normalizedImageName = null;
return true;
}
else
{
if (Char.IsUpper(containerImageName, 0))
{
containerImageName = Char.ToLowerInvariant(containerImageName[0]) + containerImageName[1..];
} else if (!Char.IsLetterOrDigit(containerImageName, 0)) {
throw new ArgumentException("The first character of the image name must be a lowercase letter or a digit.");
}
normalizedImageName = imageNameCharacters.Replace(containerImageName, "-");
return false;
}
}
}
11 changes: 10 additions & 1 deletion Microsoft.NET.Build.Containers/CreateNewImage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,20 @@ public override bool Execute()

Layer newLayer = Layer.FromDirectory(PublishDirectory, WorkingDirectory);
image.AddLayer(newLayer);
image.WorkingDirectory = WorkingDirectory;
image.SetEntrypoint(Entrypoint.Select(i => i.ItemSpec).ToArray(), EntrypointArgs.Select(i => i.ItemSpec).ToArray());

if (OutputRegistry.StartsWith("docker://"))
{
LocalDocker.Load(image, ImageName, ImageTag, BaseImageName).Wait();
try
{
LocalDocker.Load(image, ImageName, ImageTag, BaseImageName).Wait();
}
catch (AggregateException ex) when (ex.InnerException is DockerLoadException dle)
{
Log.LogErrorFromException(dle, showStackTrace: false);
return !Log.HasLoggedErrors;
}
}
else
{
Expand Down
23 changes: 23 additions & 0 deletions Microsoft.NET.Build.Containers/DockerLoadException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Runtime.Serialization;

namespace Microsoft.NET.Build.Containers
{
public class DockerLoadException : Exception
{
public DockerLoadException()
{
}

public DockerLoadException(string? message) : base(message)
{
}

public DockerLoadException(string? message, Exception? innerException) : base(message, innerException)
{
}

protected DockerLoadException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}
22 changes: 8 additions & 14 deletions Microsoft.NET.Build.Containers/Layer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,14 @@ public record struct Layer

public static Layer FromDirectory(string directory, string containerPath)
{
DirectoryInfo di = new(directory);

IEnumerable<(string path, string containerPath)> fileList =
di.GetFileSystemInfos()
.Where(fsi => fsi is FileInfo).Select(
fsi =>
{
string destinationPath =
Path.Join(containerPath,
Path.GetRelativePath(directory, fsi.FullName))
.Replace(Path.DirectorySeparatorChar, '/');
return (fsi.FullName, destinationPath);
});

var fileList =
new DirectoryInfo(directory)
.EnumerateFiles("*", SearchOption.AllDirectories)
.Select(fsi =>
{
string destinationPath = Path.Join(containerPath, Path.GetRelativePath(directory, fsi.FullName)).Replace(Path.DirectorySeparatorChar, '/');
return (fsi.FullName, destinationPath);
});
return FromFiles(fileList);
}

Expand Down
6 changes: 6 additions & 0 deletions Microsoft.NET.Build.Containers/LocalDocker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public static async Task Load(Image x, string name, string tag, string baseName)
ProcessStartInfo loadInfo = new("docker", $"load");
loadInfo.RedirectStandardInput = true;
loadInfo.RedirectStandardOutput = true;
loadInfo.RedirectStandardError = true;

using Process? loadProcess = Process.Start(loadInfo);

Expand All @@ -29,6 +30,11 @@ public static async Task Load(Image x, string name, string tag, string baseName)
loadProcess.StandardInput.Close();

await loadProcess.WaitForExitAsync();

if (loadProcess.ExitCode != 0)
{
throw new DockerLoadException($"Failed to load image to local Docker daemon. stdout: {await loadProcess.StandardError.ReadToEndAsync()}");
}
}

public static async Task WriteImageToStream(Image x, string name, string tag, Stream imageStream)
Expand Down
25 changes: 19 additions & 6 deletions Microsoft.NET.Build.Containers/ParseContainerProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,6 @@ public ParseContainerProperties()

public override bool Execute()
{
if (!ContainerHelpers.IsValidImageName(ContainerImageName))
{
Log.LogError($"Invalid {nameof(ContainerImageName)}: {0}", ContainerImageName);
return !Log.HasLoggedErrors;
}

if (!string.IsNullOrEmpty(ContainerImageTag) && !ContainerHelpers.IsValidImageTag(ContainerImageTag))
{
Expand Down Expand Up @@ -106,11 +101,29 @@ public override bool Execute()
return !Log.HasLoggedErrors;
}

try
{
if (!ContainerHelpers.NormalizeImageName(ContainerImageName, out string? normalizedImageName))
{
Log.LogWarning(null, "CONTAINER001", null, null, 0, 0, 0, 0, $"{nameof(ContainerImageName)} was not a valid container image name, it was normalized to {normalizedImageName}");
NewContainerImageName = normalizedImageName;
}
else
{
// name was valid already
NewContainerImageName = ContainerImageName;
}
}
catch (ArgumentException)
{
Log.LogError($"Invalid {nameof(ContainerImageName)}: {{0}}", ContainerImageName);
return !Log.HasLoggedErrors;
}

ParsedContainerRegistry = outputReg;
ParsedContainerImage = outputImage;
ParsedContainerTag = outputTag;
NewContainerRegistry = registryToUse;
NewContainerImageName = ContainerImageName;
NewContainerTag = ContainerImageTag;

if (BuildEngine != null)
Expand Down
24 changes: 19 additions & 5 deletions Test.Microsoft.NET.Build.Containers.Filesystem/TargetsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,26 @@ public void CanSetEntrypointArgsToUseAppHost(bool useAppHost, params string[] en
["UseAppHost"] = useAppHost.ToString()
});
Assert.IsTrue(project.Build("ComputeContainerConfig"));
var computedEntrypointArgs = project.GetItems("ContainerEntrypoint").Select(i => i.EvaluatedInclude).ToArray();
foreach (var (First, Second) in entrypointArgs.Zip(computedEntrypointArgs))
{
var computedEntrypointArgs = project.GetItems("ContainerEntrypoint").Select(i => i.EvaluatedInclude).ToArray();
foreach (var (First, Second) in entrypointArgs.Zip(computedEntrypointArgs))
{
Assert.AreEqual(First, Second);
}
Assert.AreEqual(First, Second);
}
}

[DataRow("WebApplication44", "webApplication44", true)]
[DataRow("friendly-suspicious-alligator", "friendly-suspicious-alligator", true)]
[DataRow("*friendly-suspicious-alligator", "", false)]
[DataRow("web/app2+7", "web-app2-7", true)]
[TestMethod]
public void CanNormalizeInputContainerNames(string projectName, string expectedContainerImageName, bool shouldPass)
{
var project = InitProject(new()
{
["AssemblyName"] = projectName
});
var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None);
Assert.AreEqual(shouldPass, instance.Build(new[]{"ComputeContainerConfig"}, null, null, out var outputs), "Build should have succeeded");
Assert.AreEqual(expectedContainerImageName, instance.GetPropertyValue("ContainerImageName"));
}
}
3 changes: 2 additions & 1 deletion version.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
"version": "0.1-alpha.{height}",
"version": "0.2-alpha.{height}",
"versionHeightOffset": -1,
"nugetPackageVersion": {
"semVer": 2
},
Expand Down

0 comments on commit 3eb955c

Please sign in to comment.