Skip to content

Commit 6fc49c8

Browse files
authored
Autocorrect image names if they are correctable (#108)
* correct input container image names if they are correctable * another test * propose warning code * remove unused pattern
1 parent 0955d2e commit 6fc49c8

File tree

3 files changed

+71
-16
lines changed

3 files changed

+71
-16
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/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
}

0 commit comments

Comments
 (0)