Skip to content

Commit 3a1f601

Browse files
committed
add ability to specify single or multiple tags, verified exclsivity of each, and added test
1 parent 2625c4f commit 3a1f601

File tree

5 files changed

+78
-24
lines changed

5 files changed

+78
-24
lines changed

Microsoft.NET.Build.Containers/ParseContainerProperties.cs

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,11 @@ public class ParseContainerProperties : Microsoft.Build.Utilities.Task
2626
/// <summary>
2727
/// The tag for the container to be created.
2828
/// </summary>
29-
[Required]
30-
public string[] ContainerImageTag { get; set; }
29+
public string ContainerImageTag { get; set; }
30+
/// <summary>
31+
/// The tags for the container to be created.
32+
/// </summary>
33+
public string[] ContainerImageTags { get; set; }
3134

3235
[Output]
3336
public string ParsedContainerRegistry { get; private set; }
@@ -52,7 +55,8 @@ public ParseContainerProperties()
5255
FullyQualifiedBaseImageName = "";
5356
ContainerRegistry = "";
5457
ContainerImageName = "";
55-
ContainerImageTag = Array.Empty<string>();
58+
ContainerImageTag = "";
59+
ContainerImageTags = Array.Empty<string>();
5660
ParsedContainerRegistry = "";
5761
ParsedContainerImage = "";
5862
ParsedContainerTag = "";
@@ -61,13 +65,18 @@ public ParseContainerProperties()
6165
NewContainerTags = Array.Empty<string>();
6266
}
6367

64-
private static bool TryValidateTags(string[] inputTags, out string[] validTags, out string[] invalidTags) {
68+
private static bool TryValidateTags(string[] inputTags, out string[] validTags, out string[] invalidTags)
69+
{
6570
var v = new List<string>();
6671
var i = new List<string>();
67-
foreach (var tag in inputTags) {
68-
if (ContainerHelpers.IsValidImageTag(tag)) {
72+
foreach (var tag in inputTags)
73+
{
74+
if (ContainerHelpers.IsValidImageTag(tag))
75+
{
6976
v.Add(tag);
70-
} else {
77+
}
78+
else
79+
{
7180
i.Add(tag);
7281
}
7382
}
@@ -79,18 +88,40 @@ private static bool TryValidateTags(string[] inputTags, out string[] validTags,
7988
public override bool Execute()
8089
{
8190
string[] validTags;
82-
if (ContainerImageTag.Length != 0 && !TryValidateTags(ContainerImageTag, out var valids, out var invalids))
91+
if (!String.IsNullOrEmpty(ContainerImageTag) && ContainerImageTags.Length >= 1)
92+
{
93+
Log.LogError(null, "CONTAINER005", "Container.AmbiguousTags", null, 0, 0, 0, 0, $"Both {nameof(ContainerImageTag)} and {nameof(ContainerImageTags)} were provided, but only one or the other is allowed.");
94+
return !Log.HasLoggedErrors;
95+
}
96+
97+
if (!String.IsNullOrEmpty(ContainerImageTag))
98+
{
99+
if (ContainerHelpers.IsValidImageTag(ContainerImageTag))
100+
{
101+
validTags = new[] { ContainerImageTag };
102+
}
103+
else
104+
{
105+
validTags = Array.Empty<string>();
106+
Log.LogError(null, "CONTAINER003", "Container.InvalidTag", null, 0, 0, 0, 0, $"Invalid {nameof(ContainerImageTag)} provided: {{0}}. Image tags must be alphanumeric, underscore, hyphen, or period.", ContainerImageTag);
107+
}
108+
}
109+
else if (ContainerImageTags.Length != 0 && !TryValidateTags(ContainerImageTags, out var valids, out var invalids))
83110
{
84111
validTags = valids;
85-
if (invalids.Any()) {
86-
(string message, string args) = invalids switch {
87-
{ Length: 1 } => ($"Invalid {nameof(ContainerImageTag)} provided: {{0}}. {nameof(ContainerImageTag)} must be a semicolon-delimited list of valid image tags. Image tags must be alphanumeric, underscore, hyphen, or period.", invalids[0]),
88-
_ => ($"Invalid {nameof(ContainerImageTag)}s provided: {{0}}. {nameof(ContainerImageTag)} must be a semicolon-delimited list of valid image tags. Image tags must be alphanumeric, underscore, hyphen, or period.", String.Join(", ", invalids))
112+
if (invalids.Any())
113+
{
114+
(string message, string args) = invalids switch
115+
{
116+
{ Length: 1 } => ($"Invalid {nameof(ContainerImageTags)} provided: {{0}}. {nameof(ContainerImageTags)} must be a semicolon-delimited list of valid image tags. Image tags must be alphanumeric, underscore, hyphen, or period.", invalids[0]),
117+
_ => ($"Invalid {nameof(ContainerImageTags)} provided: {{0}}. {nameof(ContainerImageTags)} must be a semicolon-delimited list of valid image tags. Image tags must be alphanumeric, underscore, hyphen, or period.", String.Join(", ", invalids))
89118
};
90119
Log.LogError(null, "CONTAINER003", "Container.InvalidTag", null, 0, 0, 0, 0, message, args);
91120
return !Log.HasLoggedErrors;
92121
}
93-
} else {
122+
}
123+
else
124+
{
94125
validTags = Array.Empty<string>();
95126
}
96127

Microsoft.NET.Build.Containers/build/Microsoft.NET.Build.Containers.targets

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
<ContainerRegistry Condition="'$(ContainerRegistry)' == ''">docker://</ContainerRegistry>
2222
<!-- Note: spaces will be replaced with '-' in ContainerImageName and ContainerImageTag -->
2323
<ContainerImageName Condition="'$(ContainerImageName)' == ''">$(AssemblyName)</ContainerImageName>
24-
<ContainerImageTag Condition="'$(ContainerImageTag)' == ''">$(Version)</ContainerImageTag>
24+
<!-- Only default a tag name if no tag names at all are provided -->
25+
<ContainerImageTag Condition="'$(ContainerImageTag)' == '' and '$(ContainerImageTags)' == ''">$(Version)</ContainerImageTag>
2526
<ContainerWorkingDirectory Condition="'$(ContainerWorkingDirectory)' == ''">/app</ContainerWorkingDirectory>
2627
<!-- Could be semicolon-delimited -->
2728
</PropertyGroup>
@@ -48,7 +49,8 @@
4849
<ParseContainerProperties FullyQualifiedBaseImageName="$(ContainerBaseImage)"
4950
ContainerRegistry="$(ContainerRegistry)"
5051
ContainerImageName="$(ContainerImageName)"
51-
ContainerImageTag="$(ContainerImageTag)">
52+
ContainerImageTag="$(ContainerImageTag)"
53+
ContainerImageTags="$(ContainerImageTags)">
5254

5355
<Output TaskParameter="ParsedContainerRegistry" PropertyName="ContainerBaseRegistry" />
5456
<Output TaskParameter="ParsedContainerImage" PropertyName="ContainerBaseName" />

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public void ParseContainerProperties_EndToEnd()
9393
pcp.FullyQualifiedBaseImageName = "https://mcr.microsoft.com/dotnet/runtime:6.0";
9494
pcp.ContainerRegistry = "http://localhost:5010";
9595
pcp.ContainerImageName = "dotnet/testimage";
96-
pcp.ContainerImageTag = new [] {"5.0", "latest"};
96+
pcp.ContainerImageTags = new [] {"5.0", "latest"};
9797

9898
Assert.IsTrue(pcp.Execute());
9999
Assert.AreEqual("https://mcr.microsoft.com", pcp.ParsedContainerRegistry);

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

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public void Baseline()
1515
task.FullyQualifiedBaseImageName = "https://mcr.microsoft.com/dotnet/runtime:6.0";
1616
task.ContainerRegistry = "http://localhost:5010";
1717
task.ContainerImageName = "dotnet/testimage";
18-
task.ContainerImageTag = new[] { "5.0" };
18+
task.ContainerImageTags = new[] { "5.0" };
1919

2020
Assert.IsTrue(task.Execute());
2121
Assert.AreEqual("https://mcr.microsoft.com", task.ParsedContainerRegistry);
@@ -33,7 +33,7 @@ public void BaseRegistriesWithNoSchemeGetHttps()
3333
task.FullyQualifiedBaseImageName = "mcr.microsoft.com/dotnet/runtime:6.0";
3434
task.ContainerRegistry = "http://localhost:5010";
3535
task.ContainerImageName = "dotnet/testimage";
36-
task.ContainerImageTag = new[] { "5.0" };
36+
task.ContainerImageTags = new[] { "5.0" };
3737

3838
Assert.IsTrue(task.Execute());
3939
Assert.AreEqual("https://mcr.microsoft.com", task.ParsedContainerRegistry);
@@ -52,7 +52,7 @@ public void UserRegistriesWithNoSchemeGetHttps()
5252
task.FullyQualifiedBaseImageName = "mcr.microsoft.com/dotnet/runtime:6.0";
5353
task.ContainerRegistry = "localhost:5010";
5454
task.ContainerImageName = "dotnet/testimage";
55-
task.ContainerImageTag = new[] { "5.0" };
55+
task.ContainerImageTags = new[] { "5.0" };
5656

5757
Assert.IsTrue(task.Execute());
5858
Assert.AreEqual("https://mcr.microsoft.com", task.ParsedContainerRegistry);
@@ -73,7 +73,7 @@ public void SpacesGetReplacedWithDashes()
7373

7474
// Spaces in the "new" container info don't pass the regex.
7575
task.ContainerImageName = "dotnet/testimage";
76-
task.ContainerImageTag = new[] { "5.0" };
76+
task.ContainerImageTags = new[] { "5.0" };
7777

7878
Assert.IsTrue(task.Execute());
7979
Assert.AreEqual("https://mcr-microsoft-com", task.ParsedContainerRegistry);
@@ -94,7 +94,7 @@ public void RegexCatchesInvalidContainerNames()
9494

9595
// Spaces in the "new" container info don't pass the regex.
9696
task.ContainerImageName = "dotnet testimage";
97-
task.ContainerImageTag = new[] { "5.0" };
97+
task.ContainerImageTags = new[] { "5.0" };
9898

9999
Assert.IsFalse(task.Execute());
100100
// To do: Verify output contains expected error
@@ -109,7 +109,23 @@ public void RegexCatchesInvalidContainerTags()
109109
task.ContainerRegistry = "http://localhost:5010";
110110
// Spaces in the "new" container info don't pass the regex.
111111
task.ContainerImageName = "dotnet/testimage";
112-
task.ContainerImageTag = new[] { "5.0" };
112+
task.ContainerImageTags = new[] { "5.0" };
113+
114+
Assert.IsFalse(task.Execute());
115+
// To do: Verify output contains expected error
116+
}
117+
118+
[TestMethod]
119+
[Ignore("Task logging in tests unsupported.")]
120+
public void CanOnlySupplyOneOfTagAndTags()
121+
{
122+
ParseContainerProperties task = new ParseContainerProperties();
123+
task.FullyQualifiedBaseImageName = "mcr.microsoft.com/dotnet/runtime:6 0";
124+
task.ContainerRegistry = "http://localhost:5010";
125+
// Spaces in the "new" container info don't pass the regex.
126+
task.ContainerImageName = "dotnet/testimage";
127+
task.ContainerImageTag = "a.b";
128+
task.ContainerImageTags = new[] { "5.0" };
113129

114130
Assert.IsFalse(task.Execute());
115131
// To do: Verify output contains expected error

docs/ContainerCustomization.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,21 @@ By default, the value used will be the `AssemblyName` of the project.
5151
> **Note**
5252
> Image names can only contain lowercase alphanumeric characters, periods, underscores, and dashes, and must start with a letter or number - any other characters will result in an error being thrown.
5353
54-
## ContainerImageTag
54+
## ContainerImageTag(s)
5555

56-
This property controls the tag that is generated for the image. Tags are often used to refer to different versions of an application, but they can also refer to different operating system distributions, or even just different baked-in configuration.
56+
This property controls the tag that is generated for the image. Tags are often used to refer to different versions of an application, but they can also refer to different operating system distributions, or even just different baked-in configuration. This property also can be used to push multiple tags - simply use a semicolon-delimited set of tags in the `ContainerImageTags` property, similar to setting multiple `TargetFrameworks`.
5757

5858
By default, the value used will be the `Version` of the project.
5959

6060
```xml
6161
<ContainerImageTag>1.2.3-alpha2</ContainerImageTag>
6262
```
6363

64+
```xml
65+
<ContainerImageTags>1.2.3-alpha2;latest</ContainerImageTags>
66+
```
67+
68+
6469
> **Note**
6570
> Tags can only contain up to 127 alphanumeric characters, periods, underscores, and dashes. They must start with an alphanumeric character or an underscore. Any other form will result in an error being thrown.
6671

0 commit comments

Comments
 (0)