Skip to content

Commit 49f0358

Browse files
committed
default ContainerUser to rootless users on .NET 8 and above
1 parent 8549159 commit 49f0358

File tree

5 files changed

+65
-40
lines changed

5 files changed

+65
-40
lines changed

Microsoft.NET.Build.Containers.IntegrationTests/CapturingLogger.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ public class CapturingLogger : ILogger
1919
private List<BuildErrorEventArgs> _errors = new();
2020
public IReadOnlyList<BuildErrorEventArgs> Errors {get { return _errors; } }
2121

22+
public List<string> AllMessages => Errors.Select(e => e.Message!).Concat(Warnings.Select(w => w.Message!)).Concat(Messages.Select(m => m.Message!)).ToList();
23+
2224
public void Initialize(IEventSource eventSource)
2325
{
2426
eventSource.MessageRaised += (o, e) => _messages.Add(e);

Microsoft.NET.Build.Containers.IntegrationTests/ParseContainerPropertiesTests.cs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@ public class ParseContainerPropertiesTests
1616
[DockerDaemonAvailableFact]
1717
public void Baseline()
1818
{
19-
var (project, _) = ProjectInitializer.InitProject(new () {
19+
var (project, _, d) = ProjectInitializer.InitProject(new () {
2020
[ContainerBaseImage] = "mcr.microsoft.com/dotnet/runtime:7.0",
2121
[ContainerRegistry] = "localhost:5010",
2222
[ContainerImageName] = "dotnet/testimage",
2323
[ContainerImageTags] = "7.0;latest"
2424
});
25+
using var _ = d;
2526
var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None);
2627
Assert.True(instance.Build(new[]{ComputeContainerConfig}, null, null, out var outputs));
2728

@@ -37,11 +38,11 @@ public void Baseline()
3738
[DockerDaemonAvailableFact]
3839
public void SpacesGetReplacedWithDashes()
3940
{
40-
var (project, _) = ProjectInitializer.InitProject(new () {
41+
var (project, _, d) = ProjectInitializer.InitProject(new () {
4142
[ContainerBaseImage] = "mcr microsoft com/dotnet runtime:7.0",
4243
[ContainerRegistry] = "localhost:5010"
4344
});
44-
45+
using var _ = d;
4546
var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None);
4647
Assert.True(instance.Build(new[]{ComputeContainerConfig}, null, null, out var outputs));
4748

@@ -53,13 +54,13 @@ public void SpacesGetReplacedWithDashes()
5354
[DockerDaemonAvailableFact]
5455
public void RegexCatchesInvalidContainerNames()
5556
{
56-
var (project, logs) = ProjectInitializer.InitProject(new () {
57+
var (project, logs, d) = ProjectInitializer.InitProject(new () {
5758
[ContainerBaseImage] = "mcr.microsoft.com/dotnet/runtime:7.0",
5859
[ContainerRegistry] = "localhost:5010",
5960
[ContainerImageName] = "dotnet testimage",
6061
[ContainerImageTag] = "5.0"
6162
});
62-
63+
using var _ = d;
6364
var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None);
6465
Assert.True(instance.Build(new[]{ComputeContainerConfig}, new [] { logs }, null, out var outputs));
6566
Assert.Contains(logs.Messages, m => m.Message?.Contains("'ContainerImageName' was not a valid container image name, it was normalized to 'dotnet-testimage'") == true);
@@ -68,13 +69,13 @@ public void RegexCatchesInvalidContainerNames()
6869
[DockerDaemonAvailableFact]
6970
public void RegexCatchesInvalidContainerTags()
7071
{
71-
var (project, logs) = ProjectInitializer.InitProject(new () {
72+
var (project, logs, d) = ProjectInitializer.InitProject(new () {
7273
[ContainerBaseImage] = "mcr.microsoft.com/dotnet/runtime:7.0",
7374
[ContainerRegistry] = "localhost:5010",
7475
[ContainerImageName] = "dotnet/testimage",
7576
[ContainerImageTag] = "5 0"
7677
});
77-
78+
using var _ = d;
7879
var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None);
7980
Assert.False(instance.Build(new[]{ComputeContainerConfig}, new [] { logs }, null, out var outputs));
8081

@@ -85,14 +86,14 @@ public void RegexCatchesInvalidContainerTags()
8586
[DockerDaemonAvailableFact]
8687
public void CanOnlySupplyOneOfTagAndTags()
8788
{
88-
var (project, logs) = ProjectInitializer.InitProject(new () {
89+
var (project, logs, d) = ProjectInitializer.InitProject(new () {
8990
[ContainerBaseImage] = "mcr.microsoft.com/dotnet/runtime:7.0",
9091
[ContainerRegistry] = "localhost:5010",
9192
[ContainerImageName] = "dotnet/testimage",
9293
[ContainerImageTag] = "5.0",
9394
[ContainerImageTags] = "latest;oldest"
9495
});
95-
96+
using var _ = d;
9697
var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None);
9798
Assert.False(instance.Build(new[]{ComputeContainerConfig}, new [] { logs }, null, out var outputs));
9899

Microsoft.NET.Build.Containers.IntegrationTests/ProjectInitializer.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,17 @@ private static string CombineFiles(string propsFile, string targetsFile)
3535
return tempTargetLocation;
3636
}
3737

38-
public static (Project, CapturingLogger) InitProject(Dictionary<string, string> bonusProps, [CallerMemberName]string projectName = "")
38+
public static (Project, CapturingLogger, IDisposable) InitProject(Dictionary<string, string> bonusProps, [CallerMemberName]string projectName = "")
3939
{
4040
var props = new Dictionary<string, string>();
4141
// required parameters
4242
props["TargetFileName"] = "foo.dll";
4343
props["AssemblyName"] = "foo";
44-
props["_TargetFrameworkVersionWithoutV"] = "7.0";
44+
props["TargetFrameworkVersion"] = "v7.0";
4545
props["_NativeExecutableExtension"] = ".exe"; //TODO: windows/unix split here
4646
props["Version"] = "1.0.0"; // TODO: need to test non-compliant version strings here
4747
props["NetCoreSdkVersion"] = "7.0.100"; // TODO: float this to current SDK?
48-
// test setup parameters so that we can load the props/targets/tasks
48+
// test setup parameters so that we can load the props/targets/tasks
4949
props["ContainerCustomTasksAssembly"] = Path.GetFullPath(Path.Combine(".", "Microsoft.NET.Build.Containers.dll"));
5050
props["_IsTest"] = "true";
5151

@@ -63,6 +63,8 @@ public static (Project, CapturingLogger) InitProject(Dictionary<string, string>
6363
{
6464
props[kvp.Key] = kvp.Value;
6565
}
66-
return (collection.LoadProject(_combinedTargetsLocation, props, null), logs);
66+
// derived properties, since these might be set by bonusProps
67+
props["_TargetFrameworkVersionWithoutV"] = props["TargetFrameworkVersion"].TrimStart('v');
68+
return (collection.LoadProject(_combinedTargetsLocation, props, null), logs, collection);
6769
}
6870
}

Microsoft.NET.Build.Containers.IntegrationTests/TargetsTests.cs

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Xunit;
88
using Microsoft.NET.Build.Containers.IntegrationTests;
99
using Microsoft.NET.Build.Containers.UnitTests;
10+
using System.Linq;
1011

1112
namespace Microsoft.NET.Build.Containers.Targets.IntegrationTests;
1213

@@ -18,10 +19,11 @@ public class TargetsTests
1819
[DockerDaemonAvailableTheory]
1920
public void CanSetEntrypointArgsToUseAppHost(bool useAppHost, params string[] entrypointArgs)
2021
{
21-
var (project, _) = ProjectInitializer.InitProject(new()
22+
var (project, _, d) = ProjectInitializer.InitProject(new()
2223
{
2324
[UseAppHost] = useAppHost.ToString()
2425
}, projectName: $"{nameof(CanSetEntrypointArgsToUseAppHost)}_{useAppHost}_{String.Join("_", entrypointArgs)}");
26+
using var _ = d;
2527
Assert.True(project.Build(ComputeContainerConfig));
2628
var computedEntrypointArgs = project.GetItems(ContainerEntrypoint).Select(i => i.EvaluatedInclude).ToArray();
2729
foreach (var (First, Second) in entrypointArgs.Zip(computedEntrypointArgs))
@@ -38,10 +40,11 @@ public void CanSetEntrypointArgsToUseAppHost(bool useAppHost, params string[] en
3840
[DockerDaemonAvailableTheory]
3941
public void CanNormalizeInputContainerNames(string projectName, string expectedContainerImageName, bool shouldPass)
4042
{
41-
var (project, _) = ProjectInitializer.InitProject(new()
43+
var (project, _, d) = ProjectInitializer.InitProject(new()
4244
{
4345
[AssemblyName] = projectName
4446
}, projectName: $"{nameof(CanNormalizeInputContainerNames)}_{projectName}_{expectedContainerImageName}_{shouldPass}");
47+
using var _ = d;
4548
var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None);
4649
instance.Build(new[] { ComputeContainerConfig }, null, null, out var outputs).Should().Be(shouldPass, "Build should have succeeded");
4750
Assert.Equal(expectedContainerImageName, instance.GetPropertyValue(ContainerImageName));
@@ -56,11 +59,12 @@ public void CanNormalizeInputContainerNames(string projectName, string expectedC
5659
[DockerDaemonAvailableTheory]
5760
public void CanWarnOnInvalidSDKVersions(string sdkVersion, bool isAllowed)
5861
{
59-
var (project, _) = ProjectInitializer.InitProject(new()
62+
var (project, _, d) = ProjectInitializer.InitProject(new()
6063
{
6164
["NETCoreSdkVersion"] = sdkVersion,
6265
["PublishProfile"] = "DefaultContainer"
6366
}, projectName: $"{nameof(CanWarnOnInvalidSDKVersions)}_{sdkVersion}_{isAllowed}");
67+
using var _ = d;
6468
var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None);
6569
var derivedIsAllowed = Boolean.Parse(project.GetProperty("_IsSDKContainerAllowedVersion").EvaluatedValue);
6670
// var buildResult = instance.Build(new[]{"_ContainerVerifySDKVersion"}, null, null, out var outputs);
@@ -72,10 +76,11 @@ public void CanWarnOnInvalidSDKVersions(string sdkVersion, bool isAllowed)
7276
[DockerDaemonAvailableTheory]
7377
public void GetsConventionalLabelsByDefault(bool shouldEvaluateLabels)
7478
{
75-
var (project, _) = ProjectInitializer.InitProject(new()
79+
var (project, _, d) = ProjectInitializer.InitProject(new()
7680
{
7781
[ContainerGenerateLabels] = shouldEvaluateLabels.ToString()
7882
}, projectName: $"{nameof(GetsConventionalLabelsByDefault)}_{shouldEvaluateLabels}");
83+
using var _ = d;
7984
var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None);
8085
instance.Build(new[] { ComputeContainerConfig }, null, null, out var outputs).Should().BeTrue("Build should have succeeded");
8186
if (shouldEvaluateLabels)
@@ -98,14 +103,15 @@ public void ShouldNotIncludeSourceControlLabelsUnlessUserOptsIn(bool includeSour
98103
var commitHash = "abcdef";
99104
var repoUrl = "https://git.cosmere.com/shard/whimsy.git";
100105

101-
var (project, _) = ProjectInitializer.InitProject(new()
106+
var (project, logger, d) = ProjectInitializer.InitProject(new()
102107
{
103108
["PublishRepositoryUrl"] = includeSourceControl.ToString(),
104109
["PrivateRepositoryUrl"] = repoUrl,
105110
["SourceRevisionId"] = commitHash
106111
}, projectName: $"{nameof(ShouldNotIncludeSourceControlLabelsUnlessUserOptsIn)}_{includeSourceControl}");
112+
using var _ = d;
107113
var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None);
108-
instance.Build(new[] { ComputeContainerConfig }, null, null, out var outputs).Should().BeTrue("Build should have succeeded");
114+
instance.Build(new[] { ComputeContainerConfig }, null, null, out var outputs).Should().BeTrue("Build should have succeeded but failed due to {0}", String.Join("\n", logger.AllMessages));
109115
var labels = instance.GetItems(ContainerLabel);
110116
if (includeSourceControl)
111117
{
@@ -121,29 +127,30 @@ public void ShouldNotIncludeSourceControlLabelsUnlessUserOptsIn(bool includeSour
121127
};
122128
}
123129

124-
[InlineData("7.0.100", "7.0", "7.0")]
125-
[InlineData("7.0.100-preview.7", "7.0", "7.0")]
126-
[InlineData("7.0.100-rc.1", "7.0", "7.0")]
127-
[InlineData("8.0.100", "8.0", "8.0")]
128-
[InlineData("8.0.100", "7.0", "7.0")]
129-
[InlineData("8.0.100-preview.7", "8.0", "8.0-preview.7")]
130-
[InlineData("8.0.100-rc.1", "8.0", "8.0-rc.1")]
131-
[InlineData("8.0.100-rc.1", "7.0", "7.0")]
132-
[InlineData("8.0.200", "8.0", "8.0")]
133-
[InlineData("8.0.200", "7.0", "7.0")]
134-
[InlineData("8.0.200-preview3", "7.0", "7.0")]
135-
[InlineData("8.0.200-preview3", "8.0", "8.0")]
136-
[InlineData("6.0.100", "6.0", "6.0")]
137-
[InlineData("6.0.100-preview.1", "6.0", "6.0")]
130+
[InlineData("7.0.100", "v7.0", "7.0")]
131+
[InlineData("7.0.100-preview.7", "v7.0", "7.0")]
132+
[InlineData("7.0.100-rc.1", "v7.0", "7.0")]
133+
[InlineData("8.0.100", "v8.0", "8.0")]
134+
[InlineData("8.0.100", "v7.0", "7.0")]
135+
[InlineData("8.0.100-preview.7", "v8.0", "8.0-preview.7")]
136+
[InlineData("8.0.100-rc.1", "v8.0", "8.0-rc.1")]
137+
[InlineData("8.0.100-rc.1", "v7.0", "7.0")]
138+
[InlineData("8.0.200", "v8.0", "8.0")]
139+
[InlineData("8.0.200", "v7.0", "7.0")]
140+
[InlineData("8.0.200-preview3", "v7.0", "7.0")]
141+
[InlineData("8.0.200-preview3", "v8.0", "8.0")]
142+
[InlineData("6.0.100", "v6.0", "6.0")]
143+
[InlineData("6.0.100-preview.1", "v6.0", "6.0")]
138144
[Theory]
139145
public void CanComputeTagsForSupportedSDKVersions(string sdkVersion, string tfm, string expectedTag)
140146
{
141-
var (project, logger) = ProjectInitializer.InitProject(new()
147+
var (project, logger, d) = ProjectInitializer.InitProject(new()
142148
{
143149
["NETCoreSdkVersion"] = sdkVersion,
144-
["_TargetFrameworkVersionWithoutV"] = tfm,
150+
["TargetFrameworkVersion"] = tfm,
145151
["PublishProfile"] = "DefaultContainer"
146152
}, projectName: $"{nameof(CanComputeTagsForSupportedSDKVersions)}_{sdkVersion}_{tfm}_{expectedTag}");
153+
using var _ = d;
147154
var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None);
148155
instance.Build(new[]{"_ComputeContainerBaseImageTag"}, null, null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors));
149156
var computedTag = instance.GetProperty("_ContainerBaseImageTag").EvaluatedValue;

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

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
OR $(NETCoreSdkVersion.Contains('-')) == false
1212
)
1313
)">true</_IsSDKContainerAllowedVersion>
14+
<_ContainerIsTargetingNet8TFM>$([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '8.0'))</_ContainerIsTargetingNet8TFM>
1415
</PropertyGroup>
1516

1617
<ItemGroup>
@@ -50,7 +51,10 @@
5051
<_ContainerBaseImageName Condition="'$(_ContainerBaseImageName)' == ''">dotnet/runtime</_ContainerBaseImageName>
5152

5253
<!-- Compute base runtime image -->
53-
<ContainerBaseImage Condition="'$(ContainerBaseImage)' == ''">$(_ContainerBaseRegistry)/$(_ContainerBaseImageName):$(_ContainerBaseImageTag)</ContainerBaseImage>
54+
<_ContainerIsUsingMicrosoftDefaultImages Condition="'$(ContainerBaseImage)' != ''">false</_ContainerIsUsingMicrosoftDefaultImages>
55+
<_ContainerIsUsingMicrosoftDefaultImages Condition="'$(ContainerBaseImage)' == ''">true</_ContainerIsUsingMicrosoftDefaultImages>
56+
57+
<ContainerBaseImage Condition="'$(_ContainerIsUsingMicrosoftDefaultImages)' == 'true'">$(_ContainerBaseRegistry)/$(_ContainerBaseImageName):$(_ContainerBaseImageTag)</ContainerBaseImage>
5458
</PropertyGroup>
5559
</Target>
5660

@@ -83,20 +87,29 @@
8387
For builds that have a RID, we default to that RID. Otherwise, we default to the RID of the currently-executing SDK. -->
8488
<ContainerRuntimeIdentifier Condition="'$(ContainerRuntimeIdentifier)' == '' and '$(IsRidAgnostic)' != 'true'">$(RuntimeIdentifier)</ContainerRuntimeIdentifier>
8589
<ContainerRuntimeIdentifier Condition="'$(ContainerRuntimeIdentifier)' == '' ">$(NETCoreSdkPortableRuntimeIdentifier)</ContainerRuntimeIdentifier>
90+
<_ContainerIsTargetingWindows>false</_ContainerIsTargetingWindows>
91+
<_ContainerIsTargetingWindows Condition="$(ContainerRuntimeIdentifier.StartsWith('win'))">true</_ContainerIsTargetingWindows>
8692

8793
<!-- Set the WorkingDirectory depending on the RID -->
88-
<ContainerWorkingDirectory Condition="'$(ContainerWorkingDirectory)' == '' and !$(ContainerRuntimeIdentifier.StartsWith('win')) ">/app</ContainerWorkingDirectory>
89-
<ContainerWorkingDirectory Condition="'$(ContainerWorkingDirectory)' == '' and $(ContainerRuntimeIdentifier.StartsWith('win')) ">C:\app</ContainerWorkingDirectory>
94+
<ContainerWorkingDirectory Condition="'$(ContainerWorkingDirectory)' == '' and !$(_ContainerIsTargetingWindows) ">/app</ContainerWorkingDirectory>
95+
<ContainerWorkingDirectory Condition="'$(ContainerWorkingDirectory)' == '' and $(_ContainerIsTargetingWindows) ">C:\app</ContainerWorkingDirectory>
9096
</PropertyGroup>
9197

9298
<ItemGroup Label="Entrypoint Assignment">
9399
<!-- For non-apphosts, we need to invoke `dotnet` `app` as separate args -->
94100
<ContainerEntrypoint Condition="'$(ContainerEntrypoint)' == '' and '$(UseAppHost)' != 'true'" Include="dotnet;$(TargetFileName)" />
95101
<!-- For apphosts, we need to invoke `app` as a single arg -->
96-
<ContainerEntrypoint Condition="'$(ContainerEntrypoint)' == '' and '$(UseAppHost)' == 'true' and !$(ContainerRuntimeIdentifier.StartsWith('win'))" Include="$(ContainerWorkingDirectory)/$(AssemblyName)$(_NativeExecutableExtension)" />
97-
<ContainerEntrypoint Condition="'$(ContainerEntrypoint)' == '' and '$(UseAppHost)' == 'true' and $(ContainerRuntimeIdentifier.StartsWith('win'))" Include="$(AssemblyName)$(_NativeExecutableExtension)" />
102+
<ContainerEntrypoint Condition="'$(ContainerEntrypoint)' == '' and '$(UseAppHost)' == 'true' and !$(_ContainerIsTargetingWindows)" Include="$(ContainerWorkingDirectory)/$(AssemblyName)$(_NativeExecutableExtension)" />
103+
<ContainerEntrypoint Condition="'$(ContainerEntrypoint)' == '' and '$(UseAppHost)' == 'true' and $(_ContainerIsTargetingWindows)" Include="$(AssemblyName)$(_NativeExecutableExtension)" />
98104
</ItemGroup>
99105

106+
<!-- We only set a default user when the base image is Microsoft-authored, and we're targeting a version of those images that supports a nonroot user -->
107+
<PropertyGroup Label="ContainerUser Assignment" Condition="$(_ContainerIsUsingMicrosoftDefaultImages) and $(_ContainerIsTargetingNet8TFM) and '$(ContainerUser)' == ''">
108+
<ContainerUser Condition="$(_ContainerIsTargetingWindows)">ContainerUser</ContainerUser>
109+
<!-- For Linux, the MS images have an 'app' user set to user id 64198 but some container runtimes work better with user ids instead of names, so we defer to that -->
110+
<ContainerUser Condition="!$(_ContainerIsTargetingWindows)">64198</ContainerUser>
111+
</PropertyGroup>
112+
100113
<ParseContainerProperties FullyQualifiedBaseImageName="$(ContainerBaseImage)"
101114
ContainerRegistry="$(ContainerRegistry)"
102115
ContainerImageName="$(ContainerImageName)"

0 commit comments

Comments
 (0)