Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for setting the User metadata item #374

Merged
merged 2 commits into from
Mar 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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; }
baronfel marked this conversation as resolved.
Show resolved Hide resolved

[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