Skip to content

Commit

Permalink
Add support for setting the User metadata item (#374)
Browse files Browse the repository at this point in the history
  • Loading branch information
baronfel committed Mar 3, 2023
1 parent ec1cfff commit 95de4b3
Show file tree
Hide file tree
Showing 9 changed files with 72 additions and 28 deletions.
21 changes: 13 additions & 8 deletions Microsoft.NET.Build.Containers/ContainerBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.NET.Build.Containers.Resources;

namespace Microsoft.NET.Build.Containers;
Expand All @@ -16,16 +15,17 @@ public static async Task ContainerizeAsync(
string baseName,
string baseTag,
string[] entrypoint,
string[] entrypointArgs,
string[]? entrypointArgs,
string imageName,
string[] imageTags,
string? outputRegistry,
string[] labels,
Port[] exposedPorts,
string[] envVars,
string[]? labels,
Port[]? exposedPorts,
string[]? envVars,
string containerRuntimeIdentifier,
string ridGraphPath,
string localContainerDaemon,
string? containerUser,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
Expand Down Expand Up @@ -57,27 +57,32 @@ public static async Task ContainerizeAsync(

imageBuilder.SetEntryPoint(entrypoint, entrypointArgs);

foreach (string label in labels)
foreach (string label in labels ?? Array.Empty<string>())
{
string[] labelPieces = label.Split('=');

// labels are validated by System.CommandLine API
imageBuilder.AddLabel(labelPieces[0], TryUnquote(labelPieces[1]));
}

foreach (string envVar in envVars)
foreach (string envVar in envVars ?? Array.Empty<string>() )
{
string[] envPieces = envVar.Split('=', 2);

imageBuilder.AddEnvironmentVariable(envPieces[0], TryUnquote(envPieces[1]));
}

foreach ((int number, PortType type) in exposedPorts)
foreach ((int number, PortType type) in exposedPorts ?? Array.Empty<Port>())
{
// ports are validated by System.CommandLine API
imageBuilder.ExposePort(number, type);
}

if (containerUser is { } user)
{
imageBuilder.SetUser(user);
}

BuiltImage builtImage = imageBuilder.Build();

cancellationToken.ThrowIfCancellationRequested();
Expand Down
2 changes: 2 additions & 0 deletions Microsoft.NET.Build.Containers/ImageBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,6 @@ internal void AddLayer(Layer l)
/// Sets an entry point for the image.
/// </summary>
internal void SetEntryPoint(string[] executableArgs, string[]? args = null) => _baseImageConfig.SetEntryPoint(executableArgs, args);

internal void SetUser(string user) => _baseImageConfig.SetUser(user);
}
12 changes: 11 additions & 1 deletion Microsoft.NET.Build.Containers/ImageConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ internal sealed class ImageConfig
private readonly Dictionary<string, string> _environmentVariables;
private string? _newWorkingDirectory;
private (string[] ExecutableArgs, string[]? Args)? _newEntryPoint;
private string? _user;

/// <summary>
/// Models the file system of the image. Typically has a key 'type' with value 'layers' and a key 'diff_ids' with a list of layer digests.
Expand Down Expand Up @@ -45,8 +46,10 @@ internal ImageConfig(JsonNode config)
_architecture = GetArchitecture();
_os = GetOs();
_history = GetHistory();
_user = GetUser();
}

private string? GetUser() => _config["config"]?["User"]?.ToString();
private List<HistoryEntry> GetHistory() => _config["history"]?.AsArray().Select(node => node.Deserialize<HistoryEntry>()!).ToList() ?? new List<HistoryEntry>();
private string GetOs() => _config["os"]?.ToString() ?? throw new ArgumentException("Base image configuration should contain an 'os' property.");
private string GetArchitecture() => _config["architecture"]?.ToString() ?? throw new ArgumentException("Base image configuration should contain an 'architecture' property.");
Expand Down Expand Up @@ -90,9 +93,14 @@ internal string BuildConfig()
}
}

if (_user is not null)
{
newConfig["User"] = _user;
}

// These fields aren't (yet) supported by the task layer, but we should
// preserve them if they're already set in the base image.
foreach (string propertyName in new [] { "User", "Volumes", "StopSignal" })
foreach (string propertyName in new [] { "Volumes", "StopSignal" })
{
if (_config["config"]?[propertyName] is JsonNode propertyValue)
{
Expand Down Expand Up @@ -186,6 +194,8 @@ internal void AddLayer(Layer l)
_rootFsLayers.Add(l.Descriptor.UncompressedDigest!);
}

internal void SetUser(string user) => _user = user;

private HashSet<Port> GetExposedPorts()
{
HashSet<Port> ports = new();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ContainerizeDirectory.get ->
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ContainerizeDirectory.set -> void
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ContainerRuntimeIdentifier.get -> string!
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ContainerRuntimeIdentifier.set -> void
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ContainerUser.get -> string!
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ContainerUser.set -> void
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.CreateNewImage() -> void
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.Entrypoint.get -> Microsoft.Build.Framework.ITaskItem![]!
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.Entrypoint.set -> void
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const Microsoft.NET.Build.Containers.KnownDaemonTypes.Docker = "Docker" -> string!
Microsoft.NET.Build.Containers.BaseImageNotFoundException
Microsoft.NET.Build.Containers.Constants
static Microsoft.NET.Build.Containers.ContainerBuilder.ContainerizeAsync(System.IO.DirectoryInfo! folder, string! workingDir, string! registryName, string! baseName, string! baseTag, string![]! entrypoint, string![]? entrypointArgs, string! imageName, string![]! imageTags, string? outputRegistry, string![]? labels, Microsoft.NET.Build.Containers.Port[]? exposedPorts, string![]? envVars, string! containerRuntimeIdentifier, string! ridGraphPath, string! localContainerDaemon, string? containerUser, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task!
static readonly Microsoft.NET.Build.Containers.Constants.Version -> string!
Microsoft.NET.Build.Containers.ContainerBuilder
Microsoft.NET.Build.Containers.ContainerHelpers
Expand Down Expand Up @@ -118,6 +119,8 @@ Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ContainerizeDirectory.get ->
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ContainerizeDirectory.set -> void
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ContainerRuntimeIdentifier.get -> string!
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ContainerRuntimeIdentifier.set -> void
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ContainerUser.get -> string!
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ContainerUser.set -> void
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.CreateNewImage() -> void
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.Dispose() -> void
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.Entrypoint.get -> Microsoft.Build.Framework.ITaskItem![]!
Expand Down Expand Up @@ -173,7 +176,6 @@ Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ParsedContainerReg
Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ParsedContainerTag.get -> string!
override Microsoft.NET.Build.Containers.Tasks.CreateNewImage.Execute() -> bool
override Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.Execute() -> bool
static Microsoft.NET.Build.Containers.ContainerBuilder.ContainerizeAsync(System.IO.DirectoryInfo! folder, string! workingDir, string! registryName, string! baseName, string! baseTag, string![]! entrypoint, string![]! entrypointArgs, string! imageName, string![]! imageTags, string? outputRegistry, string![]! labels, Microsoft.NET.Build.Containers.Port[]! exposedPorts, string![]! envVars, string! containerRuntimeIdentifier, string! ridGraphPath, string! localContainerDaemon, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task!
static Microsoft.NET.Build.Containers.ContainerHelpers.TryParsePort(string! input, out Microsoft.NET.Build.Containers.Port? port, out Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError? error) -> bool
static Microsoft.NET.Build.Containers.ContainerHelpers.TryParsePort(string? portNumber, string? portType, out Microsoft.NET.Build.Containers.Port? port, out Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError? error) -> bool
static readonly Microsoft.NET.Build.Containers.KnownDaemonTypes.SupportedLocalDaemonTypes -> string![]!
Expand Down
10 changes: 10 additions & 0 deletions Microsoft.NET.Build.Containers/Tasks/CreateNewImage.Interface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,15 @@ partial class CreateNewImage
[Required]
public string RuntimeIdentifierGraphPath { get; set; }

/// <summary>
/// The username or UID which is a platform-specific structure that allows specific control over which user the process run as.
/// This acts as a default value to use when the value is not specified when creating a container.
/// For Linux based systems, all of the following are valid: user, uid, user:group, uid:gid, uid:group, user:gid.
/// If group/gid is not specified, the default group and supplementary groups of the given user/uid in /etc/passwd and /etc/group from the container are applied.
/// If group/gid is specified, supplementary groups from the container are ignored.
/// </summary>
public string ContainerUser { get; set; }

[Output]
public string GeneratedContainerManifest { get; set; }

Expand Down Expand Up @@ -141,6 +150,7 @@ public CreateNewImage()
ContainerRuntimeIdentifier = "";
RuntimeIdentifierGraphPath = "";
LocalContainerDaemon = "";
ContainerUser = "";

GeneratedContainerConfiguration = "";
GeneratedContainerManifest = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ internal string GenerateCommandLineCommandsInt()
builder.AppendSwitchIfNotNull("--workingdirectory ", WorkingDirectory);
ITaskItem[] sanitizedEntryPoints = Entrypoint.Where(e => !string.IsNullOrWhiteSpace(e.ItemSpec)).ToArray();
builder.AppendSwitchIfNotNull("--entrypoint ", sanitizedEntryPoints, delimiter: " ");

//optional options
if (!string.IsNullOrWhiteSpace(BaseImageTag))
{
Expand Down Expand Up @@ -185,10 +185,17 @@ internal string GenerateCommandLineCommandsInt()
{
builder.AppendSwitchIfNotNull("--rid ", ContainerRuntimeIdentifier);
}

if (!string.IsNullOrWhiteSpace(RuntimeIdentifierGraphPath))
{
builder.AppendSwitchIfNotNull("--ridgraphpath ", RuntimeIdentifierGraphPath);
}

if (!string.IsNullOrWhiteSpace(ContainerUser))
{
builder.AppendSwitchIfNotNull("--container-user ", ContainerUser);
}

return builder.ToString();
}

Expand Down
37 changes: 21 additions & 16 deletions containerize/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using System.CommandLine.Parsing;
using System.Text;

#pragma warning disable CA1852
#pragma warning disable CA1852

var publishDirectoryArg = new Argument<DirectoryInfo>(
name: "PublishDirectory",
Expand Down Expand Up @@ -164,6 +164,8 @@

var ridGraphPathOpt = new Option<string>(name: "--ridgraphpath", description: "Path to the RID graph file.");

var containerUserOpt = new Option<string>(name: "--container-user", description: "User to run the container as.");

RootCommand root = new RootCommand("Containerize an application without Docker.")
{
publishDirectoryArg,
Expand All @@ -181,27 +183,29 @@
envVarsOpt,
ridOpt,
ridGraphPathOpt,
localContainerDaemonOpt
localContainerDaemonOpt,
containerUserOpt
};

root.SetHandler(async (context) =>
{
DirectoryInfo _publishDir = context.ParseResult.GetValueForArgument(publishDirectoryArg);
string _baseReg = context.ParseResult.GetValueForOption(baseRegistryOpt) ?? "";
string _baseName = context.ParseResult.GetValueForOption(baseImageNameOpt) ?? "";
string _baseTag = context.ParseResult.GetValueForOption(baseImageTagOpt) ?? "";
string _baseReg = context.ParseResult.GetValueForOption(baseRegistryOpt)!;
string _baseName = context.ParseResult.GetValueForOption(baseImageNameOpt)!;
string _baseTag = context.ParseResult.GetValueForOption(baseImageTagOpt)!;
string? _outputReg = context.ParseResult.GetValueForOption(outputRegistryOpt);
string _name = context.ParseResult.GetValueForOption(imageNameOpt) ?? "";
string[] _tags = context.ParseResult.GetValueForOption(imageTagsOpt) ?? Array.Empty<string>();
string _workingDir = context.ParseResult.GetValueForOption(workingDirectoryOpt) ?? "";
string[] _entrypoint = context.ParseResult.GetValueForOption(entrypointOpt) ?? Array.Empty<string>();
string[] _entrypointArgs = context.ParseResult.GetValueForOption(entrypointArgsOpt) ?? Array.Empty<string>();
string[] _labels = context.ParseResult.GetValueForOption(labelsOpt) ?? Array.Empty<string>();
Port[] _ports = context.ParseResult.GetValueForOption(portsOpt) ?? Array.Empty<Port>();
string[] _envVars = context.ParseResult.GetValueForOption(envVarsOpt) ?? Array.Empty<string>();
string _rid = context.ParseResult.GetValueForOption(ridOpt) ?? "";
string _ridGraphPath = context.ParseResult.GetValueForOption(ridGraphPathOpt) ?? "";
string _localContainerDaemon = context.ParseResult.GetValueForOption(localContainerDaemonOpt) ?? "";
string _name = context.ParseResult.GetValueForOption(imageNameOpt)!;
string[] _tags = context.ParseResult.GetValueForOption(imageTagsOpt)!;
string _workingDir = context.ParseResult.GetValueForOption(workingDirectoryOpt)!;
string[] _entrypoint = context.ParseResult.GetValueForOption(entrypointOpt)!;
string[]? _entrypointArgs = context.ParseResult.GetValueForOption(entrypointArgsOpt);
string[]? _labels = context.ParseResult.GetValueForOption(labelsOpt);
Port[]? _ports = context.ParseResult.GetValueForOption(portsOpt);
string[]? _envVars = context.ParseResult.GetValueForOption(envVarsOpt);
string _rid = context.ParseResult.GetValueForOption(ridOpt)!;
string _ridGraphPath = context.ParseResult.GetValueForOption(ridGraphPathOpt)!;
string _localContainerDaemon = context.ParseResult.GetValueForOption(localContainerDaemonOpt)!;
string? _containerUser = context.ParseResult.GetValueForOption(containerUserOpt);
await ContainerBuilder.ContainerizeAsync(
_publishDir,
_workingDir,
Expand All @@ -219,6 +223,7 @@ await ContainerBuilder.ContainerizeAsync(
_rid,
_ridGraphPath,
_localContainerDaemon,
_containerUser,
context.GetCancellationToken()).ConfigureAwait(false);
});

Expand Down
3 changes: 2 additions & 1 deletion packaging/build/Microsoft.NET.Build.Containers.targets
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,9 @@
ExposedPorts="@(ContainerPort)"
ContainerEnvironmentVariables="@(ContainerEnvironmentVariables)"
ContainerRuntimeIdentifier="$(ContainerRuntimeIdentifier)"
ContainerUser="$(ContainerUser)"
RuntimeIdentifierGraphPath="$(RuntimeIdentifierGraphPath)"> <!-- The RID graph path is provided as a property by the SDK. -->

<Output TaskParameter="GeneratedContainerManifest" PropertyName="GeneratedContainerManifest" />
<Output TaskParameter="GeneratedContainerConfiguration" PropertyName="GeneratedContainerConfiguration" />
</CreateNewImage>
Expand Down

0 comments on commit 95de4b3

Please sign in to comment.