Skip to content

Commit 3eb955c

Browse files
committed
Merge origin/v0.1 into main
+ version bump to 0.2
2 parents 139a7e1 + 0818510 commit 3eb955c

File tree

8 files changed

+120
-32
lines changed

8 files changed

+120
-32
lines changed

Microsoft.NET.Build.Containers/ContainerHelpers.cs

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ public static class ContainerHelpers
99

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

12+
/// <summary>
13+
/// Matches if the string is not lowercase or numeric, or ., _, or -.
14+
/// </summary>
15+
private static Regex imageNameCharacters = new Regex("[^a-zA-Z0-9._-]");
16+
1217
/// <summary>
1318
/// Given some "fully qualified" image name (e.g. mcr.microsoft.com/dotnet/runtime), return
1419
/// a valid UriBuilder. This means appending 'https' if the URI is not absolute, otherwise UriBuilder will throw.
@@ -39,8 +44,8 @@ public static bool IsValidRegistry(string registryName)
3944
{
4045
// No scheme prefixed onto the registry
4146
if (string.IsNullOrEmpty(registryName) ||
42-
(!registryName.StartsWith("http://") &&
43-
!registryName.StartsWith("https://") &&
47+
(!registryName.StartsWith("http://") &&
48+
!registryName.StartsWith("https://") &&
4449
!registryName.StartsWith("docker://")))
4550
{
4651
return false;
@@ -89,9 +94,9 @@ public static bool IsValidImageTag(string imageTag)
8994
/// <param name="containerName"></param>
9095
/// <param name="containerTag"></param>
9196
/// <returns>True if the parse was successful. When false is returned, all out vars are set to empty strings.</returns>
92-
public static bool TryParseFullyQualifiedContainerName(string fullyQualifiedContainerName,
93-
[NotNullWhen(true)] out string? containerRegistry,
94-
[NotNullWhen(true)] out string? containerName,
97+
public static bool TryParseFullyQualifiedContainerName(string fullyQualifiedContainerName,
98+
[NotNullWhen(true)] out string? containerRegistry,
99+
[NotNullWhen(true)] out string? containerName,
95100
[NotNullWhen(true)] out string? containerTag)
96101
{
97102
Uri? uri = ContainerImageToUri(fullyQualifiedContainerName);
@@ -115,4 +120,27 @@ public static bool TryParseFullyQualifiedContainerName(string fullyQualifiedCont
115120
containerTag = indexOfColon == -1 ? "" : image.Substring(indexOfColon + 1);
116121
return true;
117122
}
123+
124+
/// <summary>
125+
/// Checks if a given container image name adheres to the image name spec. If not, and recoverable, then normalizes invalid characters.
126+
/// </summary>
127+
public static bool NormalizeImageName(string containerImageName, [NotNullWhen(false)] out string? normalizedImageName)
128+
{
129+
if (IsValidImageName(containerImageName))
130+
{
131+
normalizedImageName = null;
132+
return true;
133+
}
134+
else
135+
{
136+
if (Char.IsUpper(containerImageName, 0))
137+
{
138+
containerImageName = Char.ToLowerInvariant(containerImageName[0]) + containerImageName[1..];
139+
} else if (!Char.IsLetterOrDigit(containerImageName, 0)) {
140+
throw new ArgumentException("The first character of the image name must be a lowercase letter or a digit.");
141+
}
142+
normalizedImageName = imageNameCharacters.Replace(containerImageName, "-");
143+
return false;
144+
}
145+
}
118146
}

Microsoft.NET.Build.Containers/CreateNewImage.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,20 @@ public override bool Execute()
109109

110110
Layer newLayer = Layer.FromDirectory(PublishDirectory, WorkingDirectory);
111111
image.AddLayer(newLayer);
112+
image.WorkingDirectory = WorkingDirectory;
112113
image.SetEntrypoint(Entrypoint.Select(i => i.ItemSpec).ToArray(), EntrypointArgs.Select(i => i.ItemSpec).ToArray());
113114

114115
if (OutputRegistry.StartsWith("docker://"))
115116
{
116-
LocalDocker.Load(image, ImageName, ImageTag, BaseImageName).Wait();
117+
try
118+
{
119+
LocalDocker.Load(image, ImageName, ImageTag, BaseImageName).Wait();
120+
}
121+
catch (AggregateException ex) when (ex.InnerException is DockerLoadException dle)
122+
{
123+
Log.LogErrorFromException(dle, showStackTrace: false);
124+
return !Log.HasLoggedErrors;
125+
}
117126
}
118127
else
119128
{
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System.Runtime.Serialization;
2+
3+
namespace Microsoft.NET.Build.Containers
4+
{
5+
public class DockerLoadException : Exception
6+
{
7+
public DockerLoadException()
8+
{
9+
}
10+
11+
public DockerLoadException(string? message) : base(message)
12+
{
13+
}
14+
15+
public DockerLoadException(string? message, Exception? innerException) : base(message, innerException)
16+
{
17+
}
18+
19+
protected DockerLoadException(SerializationInfo info, StreamingContext context) : base(info, context)
20+
{
21+
}
22+
}
23+
}

Microsoft.NET.Build.Containers/Layer.cs

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,14 @@ public record struct Layer
1111

1212
public static Layer FromDirectory(string directory, string containerPath)
1313
{
14-
DirectoryInfo di = new(directory);
15-
16-
IEnumerable<(string path, string containerPath)> fileList =
17-
di.GetFileSystemInfos()
18-
.Where(fsi => fsi is FileInfo).Select(
19-
fsi =>
20-
{
21-
string destinationPath =
22-
Path.Join(containerPath,
23-
Path.GetRelativePath(directory, fsi.FullName))
24-
.Replace(Path.DirectorySeparatorChar, '/');
25-
return (fsi.FullName, destinationPath);
26-
});
27-
14+
var fileList =
15+
new DirectoryInfo(directory)
16+
.EnumerateFiles("*", SearchOption.AllDirectories)
17+
.Select(fsi =>
18+
{
19+
string destinationPath = Path.Join(containerPath, Path.GetRelativePath(directory, fsi.FullName)).Replace(Path.DirectorySeparatorChar, '/');
20+
return (fsi.FullName, destinationPath);
21+
});
2822
return FromFiles(fileList);
2923
}
3024

Microsoft.NET.Build.Containers/LocalDocker.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public static async Task Load(Image x, string name, string tag, string baseName)
1414
ProcessStartInfo loadInfo = new("docker", $"load");
1515
loadInfo.RedirectStandardInput = true;
1616
loadInfo.RedirectStandardOutput = true;
17+
loadInfo.RedirectStandardError = true;
1718

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

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

3132
await loadProcess.WaitForExitAsync();
33+
34+
if (loadProcess.ExitCode != 0)
35+
{
36+
throw new DockerLoadException($"Failed to load image to local Docker daemon. stdout: {await loadProcess.StandardError.ReadToEndAsync()}");
37+
}
3238
}
3339

3440
public static async Task WriteImageToStream(Image x, string name, string tag, Stream imageStream)

Microsoft.NET.Build.Containers/ParseContainerProperties.cs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,6 @@ public ParseContainerProperties()
6262

6363
public override bool Execute()
6464
{
65-
if (!ContainerHelpers.IsValidImageName(ContainerImageName))
66-
{
67-
Log.LogError($"Invalid {nameof(ContainerImageName)}: {0}", ContainerImageName);
68-
return !Log.HasLoggedErrors;
69-
}
7065

7166
if (!string.IsNullOrEmpty(ContainerImageTag) && !ContainerHelpers.IsValidImageTag(ContainerImageTag))
7267
{
@@ -106,11 +101,29 @@ public override bool Execute()
106101
return !Log.HasLoggedErrors;
107102
}
108103

104+
try
105+
{
106+
if (!ContainerHelpers.NormalizeImageName(ContainerImageName, out string? normalizedImageName))
107+
{
108+
Log.LogWarning(null, "CONTAINER001", null, null, 0, 0, 0, 0, $"{nameof(ContainerImageName)} was not a valid container image name, it was normalized to {normalizedImageName}");
109+
NewContainerImageName = normalizedImageName;
110+
}
111+
else
112+
{
113+
// name was valid already
114+
NewContainerImageName = ContainerImageName;
115+
}
116+
}
117+
catch (ArgumentException)
118+
{
119+
Log.LogError($"Invalid {nameof(ContainerImageName)}: {{0}}", ContainerImageName);
120+
return !Log.HasLoggedErrors;
121+
}
122+
109123
ParsedContainerRegistry = outputReg;
110124
ParsedContainerImage = outputImage;
111125
ParsedContainerTag = outputTag;
112126
NewContainerRegistry = registryToUse;
113-
NewContainerImageName = ContainerImageName;
114127
NewContainerTag = ContainerImageTag;
115128

116129
if (BuildEngine != null)

Test.Microsoft.NET.Build.Containers.Filesystem/TargetsTests.cs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,26 @@ public void CanSetEntrypointArgsToUseAppHost(bool useAppHost, params string[] en
7676
["UseAppHost"] = useAppHost.ToString()
7777
});
7878
Assert.IsTrue(project.Build("ComputeContainerConfig"));
79+
var computedEntrypointArgs = project.GetItems("ContainerEntrypoint").Select(i => i.EvaluatedInclude).ToArray();
80+
foreach (var (First, Second) in entrypointArgs.Zip(computedEntrypointArgs))
7981
{
80-
var computedEntrypointArgs = project.GetItems("ContainerEntrypoint").Select(i => i.EvaluatedInclude).ToArray();
81-
foreach (var (First, Second) in entrypointArgs.Zip(computedEntrypointArgs))
82-
{
83-
Assert.AreEqual(First, Second);
84-
}
82+
Assert.AreEqual(First, Second);
8583
}
8684
}
85+
86+
[DataRow("WebApplication44", "webApplication44", true)]
87+
[DataRow("friendly-suspicious-alligator", "friendly-suspicious-alligator", true)]
88+
[DataRow("*friendly-suspicious-alligator", "", false)]
89+
[DataRow("web/app2+7", "web-app2-7", true)]
90+
[TestMethod]
91+
public void CanNormalizeInputContainerNames(string projectName, string expectedContainerImageName, bool shouldPass)
92+
{
93+
var project = InitProject(new()
94+
{
95+
["AssemblyName"] = projectName
96+
});
97+
var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None);
98+
Assert.AreEqual(shouldPass, instance.Build(new[]{"ComputeContainerConfig"}, null, null, out var outputs), "Build should have succeeded");
99+
Assert.AreEqual(expectedContainerImageName, instance.GetPropertyValue("ContainerImageName"));
100+
}
87101
}

version.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
3-
"version": "0.1-alpha.{height}",
3+
"version": "0.2-alpha.{height}",
4+
"versionHeightOffset": -1,
45
"nugetPackageVersion": {
56
"semVer": 2
67
},

0 commit comments

Comments
 (0)