diff --git a/Directory.Packages.props b/Directory.Packages.props
index 6c54427c..aa725ae3 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -8,6 +8,7 @@
+
diff --git a/Microsoft.NET.Build.Containers.UnitTests/ContainerHelpersTests.cs b/Microsoft.NET.Build.Containers.UnitTests/ContainerHelpersTests.cs
index eddf49b5..fb2ae211 100644
--- a/Microsoft.NET.Build.Containers.UnitTests/ContainerHelpersTests.cs
+++ b/Microsoft.NET.Build.Containers.UnitTests/ContainerHelpersTests.cs
@@ -87,8 +87,8 @@ public void CanParsePort(string input, bool shouldParse, int? expectedPortNumber
if (shouldParse) {
Assert.NotNull(port);
- Assert.Equal(port.number, expectedPortNumber);
- Assert.Equal(port.type, expectedType);
+ Assert.Equal(port.Value.Number, expectedPortNumber);
+ Assert.Equal(port.Value.Type, expectedType);
} else {
Assert.Null(port);
Assert.NotNull(errors);
diff --git a/Microsoft.NET.Build.Containers/AmazonECRMessageHandler.cs b/Microsoft.NET.Build.Containers/AmazonECRMessageHandler.cs
index f0b5f2a6..dc5b8076 100644
--- a/Microsoft.NET.Build.Containers/AmazonECRMessageHandler.cs
+++ b/Microsoft.NET.Build.Containers/AmazonECRMessageHandler.cs
@@ -11,7 +11,7 @@ namespace Microsoft.NET.Build.Containers;
/// error message. The handler catches the generic error and provides a more informed error
/// message to let the user know they need to create the repository.
///
-public class AmazonECRMessageHandler : DelegatingHandler
+internal sealed class AmazonECRMessageHandler : DelegatingHandler
{
public AmazonECRMessageHandler(HttpMessageHandler innerHandler) : base(innerHandler) { }
diff --git a/Microsoft.NET.Build.Containers/AuthHandshakeMessageHandler.cs b/Microsoft.NET.Build.Containers/AuthHandshakeMessageHandler.cs
index 7718606c..c9820fed 100644
--- a/Microsoft.NET.Build.Containers/AuthHandshakeMessageHandler.cs
+++ b/Microsoft.NET.Build.Containers/AuthHandshakeMessageHandler.cs
@@ -18,7 +18,7 @@ namespace Microsoft.NET.Build.Containers;
///
/// A delegating handler that performs the Docker auth handshake as described in their docs if a request isn't authenticated
///
-public partial class AuthHandshakeMessageHandler : DelegatingHandler
+internal sealed partial class AuthHandshakeMessageHandler : DelegatingHandler
{
private const int MaxRequestRetries = 5; // Arbitrary but seems to work ok for chunked uploads to ghcr.io
diff --git a/Microsoft.NET.Build.Containers/BaseImageNotFoundException.cs b/Microsoft.NET.Build.Containers/BaseImageNotFoundException.cs
index eb65add2..6c717b15 100644
--- a/Microsoft.NET.Build.Containers/BaseImageNotFoundException.cs
+++ b/Microsoft.NET.Build.Containers/BaseImageNotFoundException.cs
@@ -3,8 +3,8 @@
namespace Microsoft.NET.Build.Containers;
-public class BaseImageNotFoundException : Exception
+public sealed class BaseImageNotFoundException : Exception
{
- public BaseImageNotFoundException(string specifiedRuntimeIdentifier, string repositoryName, string reference, IEnumerable supportedRuntimeIdentifiers)
+ internal BaseImageNotFoundException(string specifiedRuntimeIdentifier, string repositoryName, string reference, IEnumerable supportedRuntimeIdentifiers)
: base($"The RuntimeIdentifier '{specifiedRuntimeIdentifier}' is not supported by {repositoryName}:{reference}. The supported RuntimeIdentifiers are {String.Join(",", supportedRuntimeIdentifiers)}") {}
}
diff --git a/Microsoft.NET.Build.Containers/ContainerBuilder.cs b/Microsoft.NET.Build.Containers/ContainerBuilder.cs
index f0c0bcd5..a8b21058 100644
--- a/Microsoft.NET.Build.Containers/ContainerBuilder.cs
+++ b/Microsoft.NET.Build.Containers/ContainerBuilder.cs
@@ -1,22 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.NET.Build.Containers;
-
-using System;
-using System.IO;
using System.Text.Json;
-using System.Threading.Tasks;
+
+namespace Microsoft.NET.Build.Containers;
public static class ContainerBuilder
{
- private static LocalDocker GetLocalDaemon(string localDaemonType, Action logger) {
- var daemon = localDaemonType switch {
- KnownDaemonTypes.Docker => new LocalDocker(logger),
- _ => throw new ArgumentException($"Unknown local container daemon type '{localDaemonType}'. Valid local container daemon types are {String.Join(",", KnownDaemonTypes.SupportedLocalDaemonTypes)}", nameof(localDaemonType))
- };
- return daemon;
- }
public static async Task Containerize(DirectoryInfo folder, string workingDir, string registryName, string baseName, string baseTag, string[] entrypoint, string[] entrypointArgs, string imageName, string[] imageTags, string? outputRegistry, string[] labels, Port[] exposedPorts, string[] envVars, string containerRuntimeIdentifier, string ridGraphPath, string localContainerDaemon)
{
var isDaemonPull = String.IsNullOrEmpty(registryName);
@@ -104,4 +94,14 @@ public static async Task Containerize(DirectoryInfo folder, string workingDir, s
}
}
}
+
+ private static LocalDocker GetLocalDaemon(string localDaemonType, Action logger)
+ {
+ var daemon = localDaemonType switch
+ {
+ KnownDaemonTypes.Docker => new LocalDocker(logger),
+ _ => throw new ArgumentException($"Unknown local container daemon type '{localDaemonType}'. Valid local container daemon types are {String.Join(",", KnownDaemonTypes.SupportedLocalDaemonTypes)}", nameof(localDaemonType))
+ };
+ return daemon;
+ }
}
diff --git a/Microsoft.NET.Build.Containers/ContainerHelpers.cs b/Microsoft.NET.Build.Containers/ContainerHelpers.cs
index a419df2d..8bcc8f91 100644
--- a/Microsoft.NET.Build.Containers/ContainerHelpers.cs
+++ b/Microsoft.NET.Build.Containers/ContainerHelpers.cs
@@ -1,31 +1,20 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.NET.Build.Containers;
-
+#if NETFRAMEWORK
using System;
+#endif
+#if NET
using System.Diagnostics.CodeAnalysis;
+#endif
using System.Text.RegularExpressions;
-using static ReferenceParser;
-
-internal sealed record Label(string name, string value);
-
-// Explicitly lowercase to ease parsing - the incoming values are
-// lowercased by spec
-public enum PortType
-{
- tcp,
- udp
-}
-
-public record Port(int number, PortType type);
-
+namespace Microsoft.NET.Build.Containers;
public static class ContainerHelpers
{
- public const string HostObjectUser = "SDK_CONTAINER_REGISTRY_UNAME";
-
- public const string HostObjectPass = "SDK_CONTAINER_REGISTRY_PWORD";
+ internal const string HostObjectUser = "SDK_CONTAINER_REGISTRY_UNAME";
+
+ internal const string HostObjectPass = "SDK_CONTAINER_REGISTRY_PWORD";
///
/// Matches an environment variable name - must start with a letter or underscore, and can only contain letters, numbers, and underscores.
@@ -37,7 +26,7 @@ public static class ContainerHelpers
/// that have no registry component.
/// See normalize.go .
///
- public const string DefaultRegistry = "docker.io";
+ internal const string DefaultRegistry = "docker.io";
///
/// Matches if the string is not lowercase or numeric, or ., _, or -.
@@ -45,12 +34,101 @@ public static class ContainerHelpers
/// Technically the period should be allowed as well, but due to inconsistent support between cloud providers we're removing it.
private static Regex imageNameCharacters = new Regex(@"[^a-z0-9_\-/]");
+
+ ///
+ /// The enum contains possible error reasons during port parsing using or .
+ ///
+ [Flags]
+ public enum ParsePortError
+ {
+ MissingPortNumber,
+ InvalidPortNumber,
+ InvalidPortType,
+ UnknownPortFormat
+ }
+
+ ///
+ /// Tries to parse the port from and .
+ ///
+ /// The port number to parse.
+ /// The port type to parse (tcp or udp).
+ /// Parsed port.
+ /// The error occurred during parsing. Only returned when method returns .
+ /// when port was successfully parsed, otherwise.
+ public static bool TryParsePort(string? portNumber, string? portType, [NotNullWhen(true)] out Port? port, [NotNullWhen(false)] out ParsePortError? error)
+ {
+ var portNo = 0;
+ error = null;
+ if (String.IsNullOrEmpty(portNumber))
+ {
+ error = ParsePortError.MissingPortNumber;
+ }
+ else if (!int.TryParse(portNumber, out portNo))
+ {
+ error = ParsePortError.InvalidPortNumber;
+ }
+
+ if (!Enum.TryParse(portType, out PortType t))
+ {
+ if (portType is not null)
+ {
+ error = (error ?? ParsePortError.InvalidPortType) | ParsePortError.InvalidPortType;
+ }
+ else
+ {
+ t = PortType.tcp;
+ }
+ }
+
+ if (error is null)
+ {
+ port = new Port(portNo, t);
+ return true;
+ }
+ else
+ {
+ port = null;
+ return false;
+ }
+
+ }
+
+ ///
+ /// Tries to parse the port from .
+ ///
+ /// The port number to parse. Expected formats are: port number as int value, or value in format 'port number/port type' where
+ /// port type can be tcp or udp. If the port type is not present, it is assumed to be tcp.
+ /// Parsed port.
+ /// The error occurred during parsing. Only returned when method returns .
+ /// when port was successfully parsed, otherwise.
+ public static bool TryParsePort(string input, [NotNullWhen(true)] out Port? port, [NotNullWhen(false)] out ParsePortError? error)
+ {
+ var parts = input.Split('/');
+ if (parts.Length == 2)
+ {
+ string portNumber = parts[0];
+ string type = parts[1];
+ return TryParsePort(portNumber, type, out port, out error);
+ }
+ else if (parts.Length == 1)
+ {
+ string portNum = parts[0];
+ return TryParsePort(portNum, null, out port, out error);
+ }
+ else
+ {
+ error = ParsePortError.UnknownPortFormat;
+ port = null;
+ return false;
+ }
+ }
+
///
/// Ensures the given registry is valid.
///
///
///
- public static bool IsValidRegistry(string registryName) => AnchoredDomainRegexp.IsMatch(registryName);
+ internal static bool IsValidRegistry(string registryName) => ReferenceParser.AnchoredDomainRegexp.IsMatch(registryName);
///
/// Ensures the given image name is valid.
@@ -58,9 +136,9 @@ public static class ContainerHelpers
///
///
///
- public static bool IsValidImageName(string imageName)
+ internal static bool IsValidImageName(string imageName)
{
- return anchoredNameRegexp.IsMatch(imageName);
+ return ReferenceParser.anchoredNameRegexp.IsMatch(imageName);
}
///
@@ -69,16 +147,16 @@ public static bool IsValidImageName(string imageName)
///
///
///
- public static bool IsValidImageTag(string imageTag)
+ internal static bool IsValidImageTag(string imageTag)
{
- return anchoredTagRegexp.IsMatch(imageTag);
+ return ReferenceParser.anchoredTagRegexp.IsMatch(imageTag);
}
///
/// Given an already-validated registry domain, this is our hueristic to determine what HTTP protocol should be used to interact with it.
/// This is primarily for testing - in the real world almost all usage should be through HTTPS!
///
- public static Uri TryExpandRegistryToUri(string alreadyValidatedDomain)
+ internal static Uri TryExpandRegistryToUri(string alreadyValidatedDomain)
{
var prefix = alreadyValidatedDomain.StartsWith("localhost", StringComparison.Ordinal) ? "http" : "https";
return new Uri($"{prefix}://{alreadyValidatedDomain}");
@@ -89,7 +167,7 @@ public static Uri TryExpandRegistryToUri(string alreadyValidatedDomain)
///
///
///
- public static bool IsValidEnvironmentVariable(string envVar)
+ internal static bool IsValidEnvironmentVariable(string envVar)
{
return envVarRegex.IsMatch(envVar);
}
@@ -109,7 +187,7 @@ public static bool IsValidEnvironmentVariable(string envVar)
///
///
/// True if the parse was successful. When false is returned, all out vars are set to empty strings.
- public static bool TryParseFullyQualifiedContainerName(string fullyQualifiedContainerName,
+ internal static bool TryParseFullyQualifiedContainerName(string fullyQualifiedContainerName,
[NotNullWhen(true)] out string? containerRegistry,
[NotNullWhen(true)] out string? containerName,
out string? containerTag, // tag is always optional - we can't guarantee anything here
@@ -118,7 +196,7 @@ out string? containerDigest // digest is always optional - we can't guarantee an
{
/// if we don't have a reference at all, bail out
- var referenceMatch = ReferenceRegexp.Match(fullyQualifiedContainerName);
+ var referenceMatch = ReferenceParser.ReferenceRegexp.Match(fullyQualifiedContainerName);
if (referenceMatch is not { Success: true })
{
containerRegistry = null;
@@ -136,7 +214,7 @@ out string? containerDigest // digest is always optional - we can't guarantee an
// this will always be successful if the ReferenceRegexp matched, so it's safe to index into.
var namePortion = referenceMatch.Groups[1].Value;
// we try to decompose the reference name into registry and image name parts.
- var nameMatch = anchoredNameRegexp.Match(namePortion);
+ var nameMatch = ReferenceParser.anchoredNameRegexp.Match(namePortion);
if (nameMatch is { Success: true })
{
// the name regex has two groups:
@@ -183,7 +261,7 @@ out string? containerDigest // digest is always optional - we can't guarantee an
///
/// Checks if a given container image name adheres to the image name spec. If not, and recoverable, then normalizes invalid characters.
///
- public static bool NormalizeImageName(string containerImageName,
+ internal static bool NormalizeImageName(string containerImageName,
[NotNullWhen(false)] out string? normalizedImageName)
{
if (IsValidImageName(containerImageName))
@@ -202,73 +280,4 @@ public static bool NormalizeImageName(string containerImageName,
return false;
}
}
-
- [Flags]
- public enum ParsePortError
- {
- MissingPortNumber,
- InvalidPortNumber,
- InvalidPortType,
- UnknownPortFormat
- }
-
- public static bool TryParsePort(string? portNumber, string? portType, [NotNullWhen(true)] out Port? port, [NotNullWhen(false)] out ParsePortError? error)
- {
- var portNo = 0;
- error = null;
- if (String.IsNullOrEmpty(portNumber))
- {
- error = ParsePortError.MissingPortNumber;
- }
- else if (!int.TryParse(portNumber, out portNo))
- {
- error = ParsePortError.InvalidPortNumber;
- }
-
- if (!Enum.TryParse(portType, out PortType t))
- {
- if (portType is not null)
- {
- error = (error ?? ParsePortError.InvalidPortType) | ParsePortError.InvalidPortType;
- }
- else
- {
- t = PortType.tcp;
- }
- }
-
- if (error is null)
- {
- port = new Port(portNo, t);
- return true;
- }
- else
- {
- port = null;
- return false;
- }
-
- }
-
- public static bool TryParsePort(string input, [NotNullWhen(true)] out Port? port, [NotNullWhen(false)] out ParsePortError? error)
- {
- var parts = input.Split('/');
- if (parts.Length == 2)
- {
- string portNumber = parts[0];
- string type = parts[1];
- return TryParsePort(portNumber, type, out port, out error);
- }
- else if (parts.Length == 1)
- {
- string portNum = parts[0];
- return TryParsePort(portNum, null, out port, out error);
- }
- else
- {
- error = ParsePortError.UnknownPortFormat;
- port = null;
- return false;
- }
- }
}
diff --git a/Microsoft.NET.Build.Containers/ContainerHttpException.cs b/Microsoft.NET.Build.Containers/ContainerHttpException.cs
index a98d3472..4bcb5a9f 100644
--- a/Microsoft.NET.Build.Containers/ContainerHttpException.cs
+++ b/Microsoft.NET.Build.Containers/ContainerHttpException.cs
@@ -2,7 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
namespace Microsoft.NET.Build.Containers;
-public class ContainerHttpException : Exception
+
+internal sealed class ContainerHttpException : Exception
{
private const string ErrorPrefix = "Containerize: error CONTAINER004:";
string? jsonResponse;
@@ -13,4 +14,4 @@ public ContainerHttpException(string message, string? targetUri, string? jsonRes
jsonResponse = jsonResp;
uri = targetUri;
}
-}
\ No newline at end of file
+}
diff --git a/Microsoft.NET.Build.Containers/ContentStore.cs b/Microsoft.NET.Build.Containers/ContentStore.cs
index 74895818..7b8b0c98 100644
--- a/Microsoft.NET.Build.Containers/ContentStore.cs
+++ b/Microsoft.NET.Build.Containers/ContentStore.cs
@@ -5,7 +5,7 @@
namespace Microsoft.NET.Build.Containers;
-public static class ContentStore
+internal static class ContentStore
{
public static string ArtifactRoot { get; set; } = Path.Combine(Path.GetTempPath(), "Containers");
public static string ContentRoot
diff --git a/Microsoft.NET.Build.Containers/Credentials/CredentialRetrievalException.cs b/Microsoft.NET.Build.Containers/Credentials/CredentialRetrievalException.cs
index a27251a6..eef65050 100644
--- a/Microsoft.NET.Build.Containers/Credentials/CredentialRetrievalException.cs
+++ b/Microsoft.NET.Build.Containers/Credentials/CredentialRetrievalException.cs
@@ -1,8 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System;
-
namespace Microsoft.NET.Build.Containers.Credentials;
internal sealed class CredentialRetrievalException : Exception
diff --git a/Microsoft.NET.Build.Containers/DockerLoadException.cs b/Microsoft.NET.Build.Containers/DockerLoadException.cs
index eba2f1f1..b0269aae 100644
--- a/Microsoft.NET.Build.Containers/DockerLoadException.cs
+++ b/Microsoft.NET.Build.Containers/DockerLoadException.cs
@@ -1,11 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Runtime.Serialization;
namespace Microsoft.NET.Build.Containers;
-public class DockerLoadException : Exception
+internal sealed class DockerLoadException : Exception
{
public DockerLoadException()
{
@@ -18,8 +17,4 @@ public DockerLoadException(string? message) : base(message)
public DockerLoadException(string? message, Exception? innerException) : base(message, innerException)
{
}
-
- protected DockerLoadException(SerializationInfo info, StreamingContext context) : base(info, context)
- {
- }
}
diff --git a/Microsoft.NET.Build.Containers/Globals.cs b/Microsoft.NET.Build.Containers/Globals.cs
new file mode 100644
index 00000000..1d566348
--- /dev/null
+++ b/Microsoft.NET.Build.Containers/Globals.cs
@@ -0,0 +1,7 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Microsoft.NET.Build.Containers.UnitTests")]
+[assembly: InternalsVisibleTo("Microsoft.NET.Build.Containers.IntegrationTests")]
diff --git a/Microsoft.NET.Build.Containers/Image.cs b/Microsoft.NET.Build.Containers/Image.cs
index 71740fa4..f27350be 100644
--- a/Microsoft.NET.Build.Containers/Image.cs
+++ b/Microsoft.NET.Build.Containers/Image.cs
@@ -8,7 +8,7 @@
namespace Microsoft.NET.Build.Containers;
-public class Image
+internal class Image
{
private readonly HashSet _labels;
@@ -80,7 +80,7 @@ private JsonObject CreatePortMap()
var container = new JsonObject();
foreach (var port in _exposedPorts)
{
- container.Add($"{port.number}/{port.type}", new JsonObject());
+ container.Add($"{port.Number}/{port.Type}", new JsonObject());
}
return container;
}
@@ -155,7 +155,7 @@ private static HashSet ReadPortsFromConfig(JsonNode inputConfig)
&& property.Value is JsonObject propertyValue
&& ContainerHelpers.TryParsePort(propertyName, out var parsedPort, out var _))
{
- ports.Add(parsedPort);
+ ports.Add(parsedPort.Value);
}
}
return ports;
diff --git a/Microsoft.NET.Build.Containers/ImageReference.cs b/Microsoft.NET.Build.Containers/ImageReference.cs
index 5ca166ca..332a7058 100644
--- a/Microsoft.NET.Build.Containers/ImageReference.cs
+++ b/Microsoft.NET.Build.Containers/ImageReference.cs
@@ -6,7 +6,7 @@ namespace Microsoft.NET.Build.Containers;
///
/// Represents a reference to a Docker image. A reference is made of a registry, a repository (aka the image name) and a tag.
///
-public readonly record struct ImageReference(Registry? Registry, string Repository, string Tag) {
+internal readonly record struct ImageReference(Registry? Registry, string Repository, string Tag) {
public override string ToString()
{
if (Registry is {} reg) {
diff --git a/Microsoft.NET.Build.Containers/Label.cs b/Microsoft.NET.Build.Containers/Label.cs
new file mode 100644
index 00000000..ac4b97dc
--- /dev/null
+++ b/Microsoft.NET.Build.Containers/Label.cs
@@ -0,0 +1,6 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.NET.Build.Containers;
+
+internal sealed record Label(string name, string value);
diff --git a/Microsoft.NET.Build.Containers/Layer.cs b/Microsoft.NET.Build.Containers/Layer.cs
index 05e2497c..d48af117 100644
--- a/Microsoft.NET.Build.Containers/Layer.cs
+++ b/Microsoft.NET.Build.Containers/Layer.cs
@@ -9,7 +9,7 @@
namespace Microsoft.NET.Build.Containers;
-public record struct Layer
+internal record struct Layer
{
public Descriptor Descriptor { get; private set; }
diff --git a/Microsoft.NET.Build.Containers/LocalDaemons/ILocalDaemon.cs b/Microsoft.NET.Build.Containers/LocalDaemons/ILocalDaemon.cs
index 3270baaf..26cc2057 100644
--- a/Microsoft.NET.Build.Containers/LocalDaemons/ILocalDaemon.cs
+++ b/Microsoft.NET.Build.Containers/LocalDaemons/ILocalDaemon.cs
@@ -7,7 +7,7 @@ namespace Microsoft.NET.Build.Containers;
/// Abstracts over the concept of a local container runtime of some kind. Currently this is only modeled by Docker,
/// but users have expressed desires for Podman, nerdctl, etc as well so this kind of abstraction makes sense to have.
///
-public interface ILocalDaemon {
+internal interface ILocalDaemon {
///
/// Loads an image (presumably from a tarball) into the local container runtime.
@@ -19,8 +19,3 @@ public interface ILocalDaemon {
///
public Task IsAvailable();
}
-
-public static class KnownDaemonTypes {
- public const string Docker = nameof(Docker);
- public static readonly string[] SupportedLocalDaemonTypes = new [] { Docker };
-}
diff --git a/Microsoft.NET.Build.Containers/LocalDaemons/KnownDaemonTypes.cs b/Microsoft.NET.Build.Containers/LocalDaemons/KnownDaemonTypes.cs
new file mode 100644
index 00000000..e5d62db3
--- /dev/null
+++ b/Microsoft.NET.Build.Containers/LocalDaemons/KnownDaemonTypes.cs
@@ -0,0 +1,10 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.NET.Build.Containers;
+
+public static class KnownDaemonTypes
+{
+ public const string Docker = nameof(Docker);
+ public static readonly string[] SupportedLocalDaemonTypes = new [] { Docker };
+}
diff --git a/Microsoft.NET.Build.Containers/LocalDaemons/LocalDocker.cs b/Microsoft.NET.Build.Containers/LocalDaemons/LocalDocker.cs
index 7cd3fa96..293d4eb7 100644
--- a/Microsoft.NET.Build.Containers/LocalDaemons/LocalDocker.cs
+++ b/Microsoft.NET.Build.Containers/LocalDaemons/LocalDocker.cs
@@ -9,7 +9,7 @@
namespace Microsoft.NET.Build.Containers;
-public class LocalDocker : ILocalDaemon
+internal sealed class LocalDocker : ILocalDaemon
{
private readonly Action logger;
diff --git a/Microsoft.NET.Build.Containers/ManifestListV2.cs b/Microsoft.NET.Build.Containers/ManifestListV2.cs
new file mode 100644
index 00000000..44a294d6
--- /dev/null
+++ b/Microsoft.NET.Build.Containers/ManifestListV2.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Text.Json.Serialization;
+
+namespace Microsoft.NET.Build.Containers;
+
+public record struct ManifestListV2(int schemaVersion, string mediaType, PlatformSpecificManifest[] manifests);
+
+public record struct PlatformInformation(string architecture, string os, string? variant, string[] features, [property: JsonPropertyName("os.version")][field: JsonPropertyName("os.version")] string? version);
+
+public record struct PlatformSpecificManifest(string mediaType, long size, string digest, PlatformInformation platform);
diff --git a/Microsoft.NET.Build.Containers/ManifestV2.cs b/Microsoft.NET.Build.Containers/ManifestV2.cs
new file mode 100644
index 00000000..2c215c81
--- /dev/null
+++ b/Microsoft.NET.Build.Containers/ManifestV2.cs
@@ -0,0 +1,10 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.NET.Build.Containers;
+
+public record struct ManifestV2(int schemaVersion, string mediaType, ManifestConfig config, List layers);
+
+public record struct ManifestConfig(string mediaType, long size, string digest);
+
+public record struct ManifestLayer(string mediaType, long size, string digest, string[]? urls);
diff --git a/Microsoft.NET.Build.Containers/Microsoft.NET.Build.Containers.csproj b/Microsoft.NET.Build.Containers/Microsoft.NET.Build.Containers.csproj
index 264b8505..a164893d 100644
--- a/Microsoft.NET.Build.Containers/Microsoft.NET.Build.Containers.csproj
+++ b/Microsoft.NET.Build.Containers/Microsoft.NET.Build.Containers.csproj
@@ -1,4 +1,4 @@
-
+
net7.0;net472
@@ -22,6 +22,10 @@
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
@@ -31,21 +35,27 @@
-
-
-
+
+
+
+
-
+
+
+
+
+
+
diff --git a/Microsoft.NET.Build.Containers/Port.cs b/Microsoft.NET.Build.Containers/Port.cs
new file mode 100644
index 00000000..db2607ca
--- /dev/null
+++ b/Microsoft.NET.Build.Containers/Port.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.NET.Build.Containers;
+
+// Explicitly lowercase to ease parsing - the incoming values are
+// lowercased by spec
+public enum PortType
+{
+ tcp,
+ udp
+}
+
+public record struct Port(int Number, PortType Type);
diff --git a/Microsoft.NET.Build.Containers/PublicAPI/net472/PublicAPI.Shipped.txt b/Microsoft.NET.Build.Containers/PublicAPI/net472/PublicAPI.Shipped.txt
new file mode 100644
index 00000000..ab058de6
--- /dev/null
+++ b/Microsoft.NET.Build.Containers/PublicAPI/net472/PublicAPI.Shipped.txt
@@ -0,0 +1 @@
+#nullable enable
diff --git a/Microsoft.NET.Build.Containers/PublicAPI/net472/PublicAPI.Unshipped.txt b/Microsoft.NET.Build.Containers/PublicAPI/net472/PublicAPI.Unshipped.txt
new file mode 100644
index 00000000..5d542535
--- /dev/null
+++ b/Microsoft.NET.Build.Containers/PublicAPI/net472/PublicAPI.Unshipped.txt
@@ -0,0 +1,108 @@
+Microsoft.NET.Build.Containers.ContainerHelpers
+Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError
+Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError.InvalidPortNumber = 1 -> Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError
+Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError.InvalidPortType = 2 -> Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError
+Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError.MissingPortNumber = 0 -> Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError
+Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError.UnknownPortFormat = Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError.InvalidPortNumber | Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError.InvalidPortType -> Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError
+Microsoft.NET.Build.Containers.KnownStrings
+Microsoft.NET.Build.Containers.KnownStrings.ErrorCodes
+Microsoft.NET.Build.Containers.KnownStrings.Properties
+Microsoft.NET.Build.Containers.Port
+Microsoft.NET.Build.Containers.Port.Number.get -> int
+Microsoft.NET.Build.Containers.Port.Number.set -> void
+Microsoft.NET.Build.Containers.Port.Port() -> void
+Microsoft.NET.Build.Containers.Port.Port(int Number, Microsoft.NET.Build.Containers.PortType Type) -> void
+Microsoft.NET.Build.Containers.Port.Type.get -> Microsoft.NET.Build.Containers.PortType
+Microsoft.NET.Build.Containers.Port.Type.set -> void
+Microsoft.NET.Build.Containers.PortType
+Microsoft.NET.Build.Containers.PortType.tcp = 0 -> Microsoft.NET.Build.Containers.PortType
+Microsoft.NET.Build.Containers.PortType.udp = 1 -> Microsoft.NET.Build.Containers.PortType
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseImageName.get -> string!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseImageName.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseImageTag.get -> string!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseImageTag.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseRegistry.get -> string!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseRegistry.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ContainerEnvironmentVariables.get -> Microsoft.Build.Framework.ITaskItem![]!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ContainerEnvironmentVariables.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ContainerizeDirectory.get -> string!
+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.CreateNewImage() -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.Entrypoint.get -> Microsoft.Build.Framework.ITaskItem![]!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.Entrypoint.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.EntrypointArgs.get -> Microsoft.Build.Framework.ITaskItem![]!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.EntrypointArgs.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ExposedPorts.get -> Microsoft.Build.Framework.ITaskItem![]!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ExposedPorts.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GeneratedContainerConfiguration.get -> string!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GeneratedContainerConfiguration.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GeneratedContainerManifest.get -> string!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GeneratedContainerManifest.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ImageName.get -> string!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ImageName.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ImageTags.get -> string![]!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ImageTags.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.Labels.get -> Microsoft.Build.Framework.ITaskItem![]!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.Labels.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.LocalContainerDaemon.get -> string!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.LocalContainerDaemon.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.OutputRegistry.get -> string!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.OutputRegistry.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.PublishDirectory.get -> string!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.PublishDirectory.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.RuntimeIdentifierGraphPath.get -> string!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.RuntimeIdentifierGraphPath.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.WorkingDirectory.get -> string!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.WorkingDirectory.set -> void
+override Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ToolName.get -> string!
+override Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GenerateCommandLineCommands() -> string!
+override Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GenerateFullPathToTool() -> string!
+override Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GetProcessStartInfo(string! pathToTool, string! commandLineCommands, string! responseFileSwitch) -> System.Diagnostics.ProcessStartInfo!
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ContainerEnvironmentVariables.get -> Microsoft.Build.Framework.ITaskItem![]!
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ContainerEnvironmentVariables.set -> void
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ContainerImageName.get -> string!
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ContainerImageName.set -> void
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ContainerImageTag.get -> string!
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ContainerImageTag.set -> void
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ContainerImageTags.get -> string![]!
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ContainerImageTags.set -> void
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ContainerRegistry.get -> string!
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ContainerRegistry.set -> void
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.FullyQualifiedBaseImageName.get -> string!
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.FullyQualifiedBaseImageName.set -> void
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.NewContainerEnvironmentVariables.get -> Microsoft.Build.Framework.ITaskItem![]!
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.NewContainerImageName.get -> string!
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.NewContainerRegistry.get -> string!
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.NewContainerTags.get -> string![]!
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ParseContainerProperties() -> void
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ParsedContainerImage.get -> string!
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ParsedContainerRegistry.get -> string!
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ParsedContainerTag.get -> string!
+override Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.Execute() -> bool
+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.KnownStrings.ErrorCodes.CONTAINER001 -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.ErrorCodes.CONTAINER004 -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.ErrorCodes.CONTAINER005 -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.AssemblyName -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ComputeContainerConfig -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ContainerBaseImage -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ContainerBaseName -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ContainerBaseRegistry -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ContainerBaseTag -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ContainerEntrypoint -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ContainerEnvironmentVariable -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ContainerGenerateLabels -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ContainerImageName -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ContainerImageTag -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ContainerImageTags -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ContainerLabel -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ContainerPort -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ContainerRegistry -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ContainerWorkingDirectory -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.SelfContained -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.UseAppHost -> string!
diff --git a/Microsoft.NET.Build.Containers/PublicAPI/net7.0/PublicAPI.Shipped.txt b/Microsoft.NET.Build.Containers/PublicAPI/net7.0/PublicAPI.Shipped.txt
new file mode 100644
index 00000000..ab058de6
--- /dev/null
+++ b/Microsoft.NET.Build.Containers/PublicAPI/net7.0/PublicAPI.Shipped.txt
@@ -0,0 +1 @@
+#nullable enable
diff --git a/Microsoft.NET.Build.Containers/PublicAPI/net7.0/PublicAPI.Unshipped.txt b/Microsoft.NET.Build.Containers/PublicAPI/net7.0/PublicAPI.Unshipped.txt
new file mode 100644
index 00000000..51ade4b1
--- /dev/null
+++ b/Microsoft.NET.Build.Containers/PublicAPI/net7.0/PublicAPI.Unshipped.txt
@@ -0,0 +1,196 @@
+const Microsoft.NET.Build.Containers.KnownDaemonTypes.Docker = "Docker" -> string!
+Microsoft.NET.Build.Containers.BaseImageNotFoundException
+Microsoft.NET.Build.Containers.ContainerBuilder
+Microsoft.NET.Build.Containers.ContainerHelpers
+Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError
+Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError.InvalidPortNumber = 1 -> Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError
+Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError.InvalidPortType = 2 -> Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError
+Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError.MissingPortNumber = 0 -> Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError
+Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError.UnknownPortFormat = Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError.InvalidPortNumber | Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError.InvalidPortType -> Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError
+Microsoft.NET.Build.Containers.Descriptor
+Microsoft.NET.Build.Containers.Descriptor.Annotations.get -> System.Collections.Generic.Dictionary?
+Microsoft.NET.Build.Containers.Descriptor.Annotations.init -> void
+Microsoft.NET.Build.Containers.Descriptor.Data.get -> string?
+Microsoft.NET.Build.Containers.Descriptor.Data.init -> void
+Microsoft.NET.Build.Containers.Descriptor.Descriptor() -> void
+Microsoft.NET.Build.Containers.Descriptor.Descriptor(string! mediaType, string! digest, long size) -> void
+Microsoft.NET.Build.Containers.Descriptor.Digest.get -> string!
+Microsoft.NET.Build.Containers.Descriptor.Digest.init -> void
+Microsoft.NET.Build.Containers.Descriptor.MediaType.get -> string!
+Microsoft.NET.Build.Containers.Descriptor.MediaType.init -> void
+Microsoft.NET.Build.Containers.Descriptor.Size.get -> long
+Microsoft.NET.Build.Containers.Descriptor.Size.init -> void
+Microsoft.NET.Build.Containers.Descriptor.UncompressedDigest.get -> string?
+Microsoft.NET.Build.Containers.Descriptor.UncompressedDigest.init -> void
+Microsoft.NET.Build.Containers.Descriptor.Urls.get -> string![]?
+Microsoft.NET.Build.Containers.Descriptor.Urls.init -> void
+Microsoft.NET.Build.Containers.KnownDaemonTypes
+Microsoft.NET.Build.Containers.KnownStrings
+Microsoft.NET.Build.Containers.KnownStrings.ErrorCodes
+Microsoft.NET.Build.Containers.KnownStrings.Properties
+Microsoft.NET.Build.Containers.ManifestConfig
+Microsoft.NET.Build.Containers.ManifestConfig.digest.get -> string!
+Microsoft.NET.Build.Containers.ManifestConfig.digest.set -> void
+Microsoft.NET.Build.Containers.ManifestConfig.ManifestConfig() -> void
+Microsoft.NET.Build.Containers.ManifestConfig.ManifestConfig(string! mediaType, long size, string! digest) -> void
+Microsoft.NET.Build.Containers.ManifestConfig.mediaType.get -> string!
+Microsoft.NET.Build.Containers.ManifestConfig.mediaType.set -> void
+Microsoft.NET.Build.Containers.ManifestConfig.size.get -> long
+Microsoft.NET.Build.Containers.ManifestConfig.size.set -> void
+Microsoft.NET.Build.Containers.ManifestLayer
+Microsoft.NET.Build.Containers.ManifestLayer.digest.get -> string!
+Microsoft.NET.Build.Containers.ManifestLayer.digest.set -> void
+Microsoft.NET.Build.Containers.ManifestLayer.ManifestLayer() -> void
+Microsoft.NET.Build.Containers.ManifestLayer.ManifestLayer(string! mediaType, long size, string! digest, string![]? urls) -> void
+Microsoft.NET.Build.Containers.ManifestLayer.mediaType.get -> string!
+Microsoft.NET.Build.Containers.ManifestLayer.mediaType.set -> void
+Microsoft.NET.Build.Containers.ManifestLayer.size.get -> long
+Microsoft.NET.Build.Containers.ManifestLayer.size.set -> void
+Microsoft.NET.Build.Containers.ManifestLayer.urls.get -> string![]?
+Microsoft.NET.Build.Containers.ManifestLayer.urls.set -> void
+Microsoft.NET.Build.Containers.ManifestListV2
+Microsoft.NET.Build.Containers.ManifestListV2.ManifestListV2() -> void
+Microsoft.NET.Build.Containers.ManifestListV2.ManifestListV2(int schemaVersion, string! mediaType, Microsoft.NET.Build.Containers.PlatformSpecificManifest[]! manifests) -> void
+Microsoft.NET.Build.Containers.ManifestListV2.manifests.get -> Microsoft.NET.Build.Containers.PlatformSpecificManifest[]!
+Microsoft.NET.Build.Containers.ManifestListV2.manifests.set -> void
+Microsoft.NET.Build.Containers.ManifestListV2.mediaType.get -> string!
+Microsoft.NET.Build.Containers.ManifestListV2.mediaType.set -> void
+Microsoft.NET.Build.Containers.ManifestListV2.schemaVersion.get -> int
+Microsoft.NET.Build.Containers.ManifestListV2.schemaVersion.set -> void
+Microsoft.NET.Build.Containers.ManifestV2
+Microsoft.NET.Build.Containers.ManifestV2.config.get -> Microsoft.NET.Build.Containers.ManifestConfig
+Microsoft.NET.Build.Containers.ManifestV2.config.set -> void
+Microsoft.NET.Build.Containers.ManifestV2.layers.get -> System.Collections.Generic.List!
+Microsoft.NET.Build.Containers.ManifestV2.layers.set -> void
+Microsoft.NET.Build.Containers.ManifestV2.ManifestV2() -> void
+Microsoft.NET.Build.Containers.ManifestV2.ManifestV2(int schemaVersion, string! mediaType, Microsoft.NET.Build.Containers.ManifestConfig config, System.Collections.Generic.List! layers) -> void
+Microsoft.NET.Build.Containers.ManifestV2.mediaType.get -> string!
+Microsoft.NET.Build.Containers.ManifestV2.mediaType.set -> void
+Microsoft.NET.Build.Containers.ManifestV2.schemaVersion.get -> int
+Microsoft.NET.Build.Containers.ManifestV2.schemaVersion.set -> void
+Microsoft.NET.Build.Containers.PlatformInformation
+Microsoft.NET.Build.Containers.PlatformInformation.architecture.get -> string!
+Microsoft.NET.Build.Containers.PlatformInformation.architecture.set -> void
+Microsoft.NET.Build.Containers.PlatformInformation.features.get -> string![]!
+Microsoft.NET.Build.Containers.PlatformInformation.features.set -> void
+Microsoft.NET.Build.Containers.PlatformInformation.os.get -> string!
+Microsoft.NET.Build.Containers.PlatformInformation.os.set -> void
+Microsoft.NET.Build.Containers.PlatformInformation.PlatformInformation() -> void
+Microsoft.NET.Build.Containers.PlatformInformation.PlatformInformation(string! architecture, string! os, string? variant, string![]! features, string? version) -> void
+Microsoft.NET.Build.Containers.PlatformInformation.variant.get -> string?
+Microsoft.NET.Build.Containers.PlatformInformation.variant.set -> void
+Microsoft.NET.Build.Containers.PlatformInformation.version.get -> string?
+Microsoft.NET.Build.Containers.PlatformInformation.version.set -> void
+Microsoft.NET.Build.Containers.PlatformSpecificManifest
+Microsoft.NET.Build.Containers.PlatformSpecificManifest.digest.get -> string!
+Microsoft.NET.Build.Containers.PlatformSpecificManifest.digest.set -> void
+Microsoft.NET.Build.Containers.PlatformSpecificManifest.mediaType.get -> string!
+Microsoft.NET.Build.Containers.PlatformSpecificManifest.mediaType.set -> void
+Microsoft.NET.Build.Containers.PlatformSpecificManifest.platform.get -> Microsoft.NET.Build.Containers.PlatformInformation
+Microsoft.NET.Build.Containers.PlatformSpecificManifest.platform.set -> void
+Microsoft.NET.Build.Containers.PlatformSpecificManifest.PlatformSpecificManifest() -> void
+Microsoft.NET.Build.Containers.PlatformSpecificManifest.PlatformSpecificManifest(string! mediaType, long size, string! digest, Microsoft.NET.Build.Containers.PlatformInformation platform) -> void
+Microsoft.NET.Build.Containers.PlatformSpecificManifest.size.get -> long
+Microsoft.NET.Build.Containers.PlatformSpecificManifest.size.set -> void
+Microsoft.NET.Build.Containers.Port
+Microsoft.NET.Build.Containers.Port.Number.get -> int
+Microsoft.NET.Build.Containers.Port.Number.set -> void
+Microsoft.NET.Build.Containers.Port.Port() -> void
+Microsoft.NET.Build.Containers.Port.Port(int Number, Microsoft.NET.Build.Containers.PortType Type) -> void
+Microsoft.NET.Build.Containers.Port.Type.get -> Microsoft.NET.Build.Containers.PortType
+Microsoft.NET.Build.Containers.Port.Type.set -> void
+Microsoft.NET.Build.Containers.PortType
+Microsoft.NET.Build.Containers.PortType.tcp = 0 -> Microsoft.NET.Build.Containers.PortType
+Microsoft.NET.Build.Containers.PortType.udp = 1 -> Microsoft.NET.Build.Containers.PortType
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseImageName.get -> string!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseImageName.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseImageTag.get -> string!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseImageTag.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseRegistry.get -> string!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseRegistry.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ContainerEnvironmentVariables.get -> Microsoft.Build.Framework.ITaskItem![]!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ContainerEnvironmentVariables.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ContainerizeDirectory.get -> string!
+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.CreateNewImage() -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.Entrypoint.get -> Microsoft.Build.Framework.ITaskItem![]!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.Entrypoint.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.EntrypointArgs.get -> Microsoft.Build.Framework.ITaskItem![]!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.EntrypointArgs.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ExposedPorts.get -> Microsoft.Build.Framework.ITaskItem![]!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ExposedPorts.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GeneratedContainerConfiguration.get -> string!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GeneratedContainerConfiguration.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GeneratedContainerManifest.get -> string!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GeneratedContainerManifest.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ImageName.get -> string!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ImageName.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ImageTags.get -> string![]!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ImageTags.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.Labels.get -> Microsoft.Build.Framework.ITaskItem![]!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.Labels.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.LocalContainerDaemon.get -> string!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.LocalContainerDaemon.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.OutputRegistry.get -> string!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.OutputRegistry.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.PublishDirectory.get -> string!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.PublishDirectory.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.RuntimeIdentifierGraphPath.get -> string!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.RuntimeIdentifierGraphPath.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ToolExe.get -> string!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ToolExe.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ToolPath.get -> string!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ToolPath.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.WorkingDirectory.get -> string!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.WorkingDirectory.set -> void
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ContainerEnvironmentVariables.get -> Microsoft.Build.Framework.ITaskItem![]!
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ContainerEnvironmentVariables.set -> void
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ContainerImageName.get -> string!
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ContainerImageName.set -> void
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ContainerImageTag.get -> string!
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ContainerImageTag.set -> void
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ContainerImageTags.get -> string![]!
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ContainerImageTags.set -> void
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ContainerRegistry.get -> string!
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ContainerRegistry.set -> void
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.FullyQualifiedBaseImageName.get -> string!
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.FullyQualifiedBaseImageName.set -> void
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.NewContainerEnvironmentVariables.get -> Microsoft.Build.Framework.ITaskItem![]!
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.NewContainerImageName.get -> string!
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.NewContainerRegistry.get -> string!
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.NewContainerTags.get -> string![]!
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ParseContainerProperties() -> void
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ParsedContainerImage.get -> string!
+Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ParsedContainerRegistry.get -> string!
+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.Containerize(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.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![]!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.ErrorCodes.CONTAINER001 -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.ErrorCodes.CONTAINER004 -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.ErrorCodes.CONTAINER005 -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.AssemblyName -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ComputeContainerConfig -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ContainerBaseImage -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ContainerBaseName -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ContainerBaseRegistry -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ContainerBaseTag -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ContainerEntrypoint -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ContainerEnvironmentVariable -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ContainerGenerateLabels -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ContainerImageName -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ContainerImageTag -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ContainerImageTags -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ContainerLabel -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ContainerPort -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ContainerRegistry -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.ContainerWorkingDirectory -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.SelfContained -> string!
+static readonly Microsoft.NET.Build.Containers.KnownStrings.Properties.UseAppHost -> string!
diff --git a/Microsoft.NET.Build.Containers/ReferenceParser.cs b/Microsoft.NET.Build.Containers/ReferenceParser.cs
index 09d73daf..2057f644 100644
--- a/Microsoft.NET.Build.Containers/ReferenceParser.cs
+++ b/Microsoft.NET.Build.Containers/ReferenceParser.cs
@@ -14,7 +14,7 @@ namespace Microsoft.NET.Build.Containers;
/// Names of each member are deliberately non-.NET-standard, as they were kept aligned with their golang versions for easier comparison.
/// Visibility of each member is determined by golang rules - lowercase is private, uppercase is public. The exception is when a private member is used inside the golang module.
///
-public static class ReferenceParser
+internal static class ReferenceParser
{
///
@@ -303,4 +303,4 @@ private static string expression(params string[] segments)
/// anchored anchors the regular expression by adding start and end delimiters.
///
private static string anchored(params string[] segments) => $"^{expression(segments)}$";
-}
\ No newline at end of file
+}
diff --git a/Microsoft.NET.Build.Containers/Registry.cs b/Microsoft.NET.Build.Containers/Registry.cs
index 61db9180..c72ee206 100644
--- a/Microsoft.NET.Build.Containers/Registry.cs
+++ b/Microsoft.NET.Build.Containers/Registry.cs
@@ -13,23 +13,14 @@
namespace Microsoft.NET.Build.Containers;
-public record struct ManifestConfig(string mediaType, long size, string digest);
-public record struct ManifestLayer(string mediaType, long size, string digest, string[]? urls);
-public record struct ManifestV2(int schemaVersion, string mediaType, ManifestConfig config, List layers);
-
-public record struct PlatformInformation(string architecture, string os, string? variant, string[] features, [property:JsonPropertyName("os.version")][field: JsonPropertyName("os.version")] string? version);
-public record struct PlatformSpecificManifest(string mediaType, long size, string digest, PlatformInformation platform);
-public record struct ManifestListV2(int schemaVersion, string mediaType, PlatformSpecificManifest[] manifests);
-
-
-public record struct Registry
+internal sealed class Registry
{
private const string DockerManifestV2 = "application/vnd.docker.distribution.manifest.v2+json";
private const string DockerManifestListV2 = "application/vnd.docker.distribution.manifest.list.v2+json";
private const string DockerContainerV1 = "application/vnd.docker.container.image.v1+json";
- private readonly string RegistryName => BaseUri.GetComponents(UriComponents.HostAndPort, UriFormat.Unescaped);
+ private string RegistryName => BaseUri.GetComponents(UriComponents.HostAndPort, UriFormat.Unescaped);
public Registry(Uri baseUri)
{
@@ -37,7 +28,7 @@ public Registry(Uri baseUri)
_client = CreateClient();
}
- public readonly Uri BaseUri { get; }
+ public Uri BaseUri { get; }
///
/// The max chunk size for patch blob uploads.
@@ -45,12 +36,12 @@ public Registry(Uri baseUri)
///
/// This varies by registry target, for example Amazon Elastic Container Registry requires 5MB chunks for all but the last chunk.
///
- public readonly int MaxChunkSizeBytes => IsAmazonECRRegistry ? 5248080 : 1024 * 64;
+ public int MaxChunkSizeBytes => IsAmazonECRRegistry ? 5248080 : 1024 * 64;
///
/// Check to see if the registry is for Amazon Elastic Container Registry (ECR).
///
- public readonly bool IsAmazonECRRegistry
+ public bool IsAmazonECRRegistry
{
get
{
@@ -78,20 +69,20 @@ public readonly bool IsAmazonECRRegistry
///
/// Google Artifact Registry locations (one for each availability zone) are of the form "ZONE-docker.pkg.dev".
///
- public readonly bool IsGoogleArtifactRegistry {
+ public bool IsGoogleArtifactRegistry {
get => RegistryName.EndsWith("-docker.pkg.dev", StringComparison.Ordinal);
}
///
/// Google Artifact Registry doesn't support chunked upload, but we want the capability check to be agnostic to the target.
///
- private readonly bool SupportsChunkedUpload => !IsGoogleArtifactRegistry;
+ private bool SupportsChunkedUpload => !IsGoogleArtifactRegistry;
///
/// Pushing to ECR uses a much larger chunk size. To avoid getting too many socket disconnects trying to do too many
/// parallel uploads be more conservative and upload one layer at a time.
///
- private readonly bool SupportsParallelUploads => !IsAmazonECRRegistry;
+ private bool SupportsParallelUploads => !IsAmazonECRRegistry;
public async Task GetImageManifest(string repositoryName, string reference, string runtimeIdentifier, string runtimeIdentifierGraphPath)
{
@@ -251,7 +242,7 @@ public async Task Push(Layer layer, string repository, Action logProgres
}
}
- private readonly async Task UploadBlobChunked(string repository, string digest, Stream contents, HttpClient client, UriBuilder uploadUri) {
+ private async Task UploadBlobChunked(string repository, string digest, Stream contents, HttpClient client, UriBuilder uploadUri) {
Uri patchUri = uploadUri.Uri;
var localUploadUri = new UriBuilder(uploadUri.Uri);
localUploadUri.Query += $"&digest={Uri.EscapeDataString(digest)}";
@@ -295,7 +286,7 @@ private readonly async Task UploadBlobChunked(string repository, str
return new UriBuilder(patchUri);
}
- private readonly UriBuilder GetNextLocation(HttpResponseMessage response) {
+ private UriBuilder GetNextLocation(HttpResponseMessage response) {
if (response.Headers.Location is {IsAbsoluteUri: true })
{
return new UriBuilder(response.Headers.Location);
@@ -308,7 +299,7 @@ private readonly UriBuilder GetNextLocation(HttpResponseMessage response) {
}
}
- private readonly async Task UploadBlobWhole(string repository, string digest, Stream contents, HttpClient client, UriBuilder uploadUri) {
+ private async Task UploadBlobWhole(string repository, string digest, Stream contents, HttpClient client, UriBuilder uploadUri) {
StreamContent content = new StreamContent(contents);
content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
content.Headers.ContentLength = contents.Length;
@@ -321,7 +312,7 @@ private readonly async Task UploadBlobWhole(string repository, strin
return GetNextLocation(patchResponse);
}
- private readonly async Task StartUploadSession(string repository, string digest, HttpClient client) {
+ private async Task StartUploadSession(string repository, string digest, HttpClient client) {
Uri startUploadUri = new Uri(BaseUri, $"/v2/{repository}/blobs/uploads/");
HttpResponseMessage pushResponse = await client.PostAsync(startUploadUri, content: null).ConfigureAwait(false);
@@ -335,7 +326,7 @@ private readonly async Task StartUploadSession(string repository, st
return GetNextLocation(pushResponse);
}
- private readonly async Task UploadBlobContents(string repository, string digest, Stream contents, HttpClient client, UriBuilder uploadUri) {
+ private async Task UploadBlobContents(string repository, string digest, Stream contents, HttpClient client, UriBuilder uploadUri) {
if (SupportsChunkedUpload) return await UploadBlobChunked(repository, digest, contents, client, uploadUri).ConfigureAwait(false);
else return await UploadBlobWhole(repository, digest, contents, client, uploadUri).ConfigureAwait(false);
}
@@ -355,7 +346,7 @@ private static async Task FinishUploadSession(string digest, HttpClient client,
}
}
- private readonly async Task UploadBlob(string repository, string digest, Stream contents)
+ private async Task UploadBlob(string repository, string digest, Stream contents)
{
HttpClient client = GetClient();
@@ -375,7 +366,7 @@ private readonly async Task UploadBlob(string repository, string digest, Stream
}
- private readonly async Task BlobAlreadyUploaded(string repository, string digest, HttpClient client)
+ private async Task BlobAlreadyUploaded(string repository, string digest, HttpClient client)
{
HttpResponseMessage response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Head, new Uri(BaseUri, $"/v2/{repository}/blobs/{digest}"))).ConfigureAwait(false);
@@ -389,7 +380,7 @@ private readonly async Task BlobAlreadyUploaded(string repository, string
private readonly HttpClient _client;
- private readonly HttpClient GetClient()
+ private HttpClient GetClient()
{
return _client;
}
@@ -420,7 +411,7 @@ public async Task Push(Image x, ImageReference source, ImageReference destinatio
{
HttpClient client = GetClient();
- Registry destinationRegistry = (destination.Registry!).Value;
+ Registry destinationRegistry = destination.Registry!;
Func uploadLayerFunc = async (descriptor) =>
{
diff --git a/Microsoft.NET.Build.Containers/CreateNewImage.Interface.cs b/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.Interface.cs
similarity index 100%
rename from Microsoft.NET.Build.Containers/CreateNewImage.Interface.cs
rename to Microsoft.NET.Build.Containers/Tasks/CreateNewImage.Interface.cs
diff --git a/Microsoft.NET.Build.Containers/CreateNewImage.cs b/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs
similarity index 90%
rename from Microsoft.NET.Build.Containers/CreateNewImage.cs
rename to Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs
index 3250d5b3..15dd2b55 100644
--- a/Microsoft.NET.Build.Containers/CreateNewImage.cs
+++ b/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs
@@ -6,7 +6,7 @@
namespace Microsoft.NET.Build.Containers.Tasks;
-public partial class CreateNewImage : Microsoft.Build.Utilities.Task
+public sealed partial class CreateNewImage : Microsoft.Build.Utilities.Task
{
///
/// Unused. For interface parity with the ToolTask implementation of the task.
@@ -18,24 +18,115 @@ public partial class CreateNewImage : Microsoft.Build.Utilities.Task
///
public string ToolPath { get; set; }
- private bool IsDaemonPush { get => String.IsNullOrEmpty(OutputRegistry); }
+ private bool IsDaemonPush => string.IsNullOrEmpty(OutputRegistry);
- private bool IsDaemonPull { get => String.IsNullOrEmpty(BaseRegistry); }
+ private bool IsDaemonPull => string.IsNullOrEmpty(BaseRegistry);
+
+ public override bool Execute()
+ {
+ if (!Directory.Exists(PublishDirectory))
+ {
+ Log.LogError("{0} '{1}' does not exist", nameof(PublishDirectory), PublishDirectory);
+ return !Log.HasLoggedErrors;
+ }
+ ImageReference sourceImageReference = new(SourceRegistry.Value, BaseImageName, BaseImageTag);
+ var destinationImageReferences = ImageTags.Select(t => new ImageReference(DestinationRegistry.Value, ImageName, t));
+
+ var image = GetBaseImage();
+
+ if (image is null)
+ {
+ Log.LogError($"Couldn't find matching base image for {0} that matches RuntimeIdentifier {1}", sourceImageReference.RepositoryAndTag, ContainerRuntimeIdentifier);
+ return !Log.HasLoggedErrors;
+ }
+
+ SafeLog("Building image '{0}' with tags {1} on top of base image {2}", ImageName, String.Join(",", ImageTags), sourceImageReference);
+
+ Layer newLayer = Layer.FromDirectory(PublishDirectory, WorkingDirectory);
+ image.AddLayer(newLayer);
+ image.WorkingDirectory = WorkingDirectory;
+ image.SetEntrypoint(Entrypoint.Select(i => i.ItemSpec).ToArray(), EntrypointArgs.Select(i => i.ItemSpec).ToArray());
+
+ foreach (var label in Labels)
+ {
+ image.Label(label.ItemSpec, label.GetMetadata("Value"));
+ }
+
+ SetEnvironmentVariables(image, ContainerEnvironmentVariables);
+
+ SetPorts(image, ExposedPorts);
+
+ // at the end of this step, if any failed then bail out.
+ if (Log.HasLoggedErrors)
+ {
+ return false;
+ }
+
+ // at this point we're done with modifications and are just pushing the data other places
+ GeneratedContainerManifest = JsonSerializer.Serialize(image.Manifest);
+ GeneratedContainerConfiguration = image.Config.ToJsonString();
+
+ foreach (var destinationImageReference in destinationImageReferences)
+ {
+ if (IsDaemonPush)
+ {
+ var localDaemon = GetLocalDaemon(msg => Log.LogMessage(msg));
+ if (!localDaemon.IsAvailable().GetAwaiter().GetResult())
+ {
+ Log.LogError("The local daemon is not available, but pushing to a local daemon was requested. Please start the daemon and try again.");
+ return false;
+ }
+ try
+ {
+ localDaemon.Load(image, sourceImageReference, destinationImageReference).Wait();
+ SafeLog("Pushed container '{0}' to local daemon", destinationImageReference.RepositoryAndTag);
+ }
+ catch (AggregateException ex) when (ex.InnerException is DockerLoadException dle)
+ {
+ Log.LogErrorFromException(dle, showStackTrace: false);
+ }
+ }
+ else
+ {
+ try
+ {
+ destinationImageReference.Registry?.Push(image, sourceImageReference, destinationImageReference, message => SafeLog(message)).Wait();
+ SafeLog("Pushed container '{0}' to registry '{2}'", destinationImageReference.RepositoryAndTag, OutputRegistry);
+ }
+ catch (ContainerHttpException e)
+ {
+ if (BuildEngine != null)
+ {
+ Log.LogErrorFromException(e, true);
+ }
+ }
+ catch (Exception e)
+ {
+ if (BuildEngine != null)
+ {
+ Log.LogError("Failed to push to the output registry: {0}", e);
+ }
+ }
+ }
+ }
+
+ return !Log.HasLoggedErrors;
+ }
private void SetPorts(Image image, ITaskItem[] exposedPorts)
{
foreach (var port in exposedPorts)
{
var portNo = port.ItemSpec;
- var portTy = port.GetMetadata("Type");
- if (ContainerHelpers.TryParsePort(portNo, portTy, out var parsedPort, out var errors))
+ var portType = port.GetMetadata("Type");
+ if (ContainerHelpers.TryParsePort(portNo, portType, out Port? parsedPort, out ContainerHelpers.ParsePortError? errors))
{
- image.ExposePort(parsedPort.number, parsedPort.type);
+ image.ExposePort(parsedPort.Value.Number, parsedPort.Value.Type);
}
else
{
ContainerHelpers.ParsePortError parsedErrors = (ContainerHelpers.ParsePortError)errors!;
- var portString = portTy == null ? portNo : $"{portNo}/{portTy}";
+ var portString = portType == null ? portNo : $"{portNo}/{portType}";
if (parsedErrors.HasFlag(ContainerHelpers.ParsePortError.MissingPortNumber))
{
Log.LogError("ContainerPort item '{0}' does not specify the port number. Please ensure the item's Include is a port number, for example ' '", port.ItemSpec);
@@ -48,7 +139,7 @@ private void SetPorts(Image image, ITaskItem[] exposedPorts)
{
message += "an invalid port number '{0}' and an invalid port type '{1}'";
arguments.Add(portNo);
- arguments.Add(portTy!);
+ arguments.Add(portType!);
}
else if (parsedErrors.HasFlag(ContainerHelpers.ParsePortError.InvalidPortNumber))
{
@@ -58,7 +149,7 @@ private void SetPorts(Image image, ITaskItem[] exposedPorts)
else if (parsedErrors.HasFlag(ContainerHelpers.ParsePortError.InvalidPortNumber))
{
message += "an invalid port type '{0}'";
- arguments.Add(portTy!);
+ arguments.Add(portType!);
}
message += ". ContainerPort items must have an Include value that is an integer, and a Type value that is either 'tcp' or 'udp'";
@@ -116,93 +207,4 @@ private static void SetEnvironmentVariables(Image img, ITaskItem[] envVars)
private void SafeLog(string message, params object[] formatParams) {
if(BuildEngine != null) Log.LogMessage(MessageImportance.High, message, formatParams);
}
-
- public override bool Execute()
- {
- if (!Directory.Exists(PublishDirectory))
- {
- Log.LogError("{0} '{1}' does not exist", nameof(PublishDirectory), PublishDirectory);
- return !Log.HasLoggedErrors;
- }
- ImageReference sourceImageReference = new(SourceRegistry.Value, BaseImageName, BaseImageTag);
- var destinationImageReferences = ImageTags.Select(t => new ImageReference(DestinationRegistry.Value, ImageName, t));
-
- var image = GetBaseImage();
-
- if (image is null) {
- Log.LogError($"Couldn't find matching base image for {0} that matches RuntimeIdentifier {1}", sourceImageReference.RepositoryAndTag, ContainerRuntimeIdentifier);
- return !Log.HasLoggedErrors;
- }
-
- SafeLog("Building image '{0}' with tags {1} on top of base image {2}", ImageName, String.Join(",", ImageTags), sourceImageReference);
-
- Layer newLayer = Layer.FromDirectory(PublishDirectory, WorkingDirectory);
- image.AddLayer(newLayer);
- image.WorkingDirectory = WorkingDirectory;
- image.SetEntrypoint(Entrypoint.Select(i => i.ItemSpec).ToArray(), EntrypointArgs.Select(i => i.ItemSpec).ToArray());
-
- foreach (var label in Labels)
- {
- image.Label(label.ItemSpec, label.GetMetadata("Value"));
- }
-
- SetEnvironmentVariables(image, ContainerEnvironmentVariables);
-
- SetPorts(image, ExposedPorts);
-
- // at the end of this step, if any failed then bail out.
- if (Log.HasLoggedErrors)
- {
- return false;
- }
-
- // at this point we're done with modifications and are just pushing the data other places
- GeneratedContainerManifest = JsonSerializer.Serialize(image.Manifest);
- GeneratedContainerConfiguration = image.Config.ToJsonString();
-
- foreach (var destinationImageReference in destinationImageReferences)
- {
- if (IsDaemonPush)
- {
- var localDaemon = GetLocalDaemon(msg => Log.LogMessage(msg));
- if (!localDaemon.IsAvailable().GetAwaiter().GetResult()) {
- Log.LogError("The local daemon is not available, but pushing to a local daemon was requested. Please start the daemon and try again.");
- return false;
- }
- try
- {
- localDaemon.Load(image, sourceImageReference, destinationImageReference).Wait();
- SafeLog("Pushed container '{0}' to local daemon", destinationImageReference.RepositoryAndTag);
- }
- catch (AggregateException ex) when (ex.InnerException is DockerLoadException dle)
- {
- Log.LogErrorFromException(dle, showStackTrace: false);
- }
- }
- else
- {
- try
- {
- destinationImageReference.Registry?.Push(image, sourceImageReference, destinationImageReference, message => SafeLog(message)).Wait();
- SafeLog("Pushed container '{0}' to registry '{2}'", destinationImageReference.RepositoryAndTag, OutputRegistry);
- }
- catch (ContainerHttpException e)
- {
- if (BuildEngine != null)
- {
- Log.LogErrorFromException(e, true);
- }
- }
- catch (Exception e)
- {
- if (BuildEngine != null)
- {
- Log.LogError("Failed to push to the output registry: {0}", e);
- }
- }
- }
- }
-
- return !Log.HasLoggedErrors;
- }
}
diff --git a/Microsoft.NET.Build.Containers/CreateNewImageToolTask.cs b/Microsoft.NET.Build.Containers/Tasks/CreateNewImageToolTask.cs
similarity index 96%
rename from Microsoft.NET.Build.Containers/CreateNewImageToolTask.cs
rename to Microsoft.NET.Build.Containers/Tasks/CreateNewImageToolTask.cs
index 34b124cc..83bd6a9e 100644
--- a/Microsoft.NET.Build.Containers/CreateNewImageToolTask.cs
+++ b/Microsoft.NET.Build.Containers/Tasks/CreateNewImageToolTask.cs
@@ -1,8 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.NET.Build.Containers.Tasks;
-
using System;
using System.Diagnostics;
using System.Linq;
@@ -10,6 +8,8 @@ namespace Microsoft.NET.Build.Containers.Tasks;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
+namespace Microsoft.NET.Build.Containers.Tasks;
+
///
/// This task will shell out to the net7.0-targeted application for VS scenarios.
///
@@ -43,12 +43,7 @@ private string DotNetPath
///
///
///
- protected override ProcessStartInfo GetProcessStartInfo
- (
- string pathToTool,
- string commandLineCommands,
- string responseFileSwitch
- )
+ protected override ProcessStartInfo GetProcessStartInfo(string pathToTool, string commandLineCommands, string responseFileSwitch)
{
VSHostObject hostObj = new VSHostObject(HostObject as System.Collections.Generic.IEnumerable);
if (hostObj.ExtractCredentials(out string user, out string pass, (string s) => Log.LogWarning(s)))
diff --git a/Microsoft.NET.Build.Containers/ParseContainerProperties.cs b/Microsoft.NET.Build.Containers/Tasks/ParseContainerProperties.cs
similarity index 98%
rename from Microsoft.NET.Build.Containers/ParseContainerProperties.cs
rename to Microsoft.NET.Build.Containers/Tasks/ParseContainerProperties.cs
index 9318413b..fad9318a 100644
--- a/Microsoft.NET.Build.Containers/ParseContainerProperties.cs
+++ b/Microsoft.NET.Build.Containers/Tasks/ParseContainerProperties.cs
@@ -8,7 +8,7 @@
namespace Microsoft.NET.Build.Containers.Tasks;
-public class ParseContainerProperties : Microsoft.Build.Utilities.Task
+public sealed class ParseContainerProperties : Microsoft.Build.Utilities.Task
{
///
/// The full base image name. mcr.microsoft.com/dotnet/runtime:6.0, for example.
@@ -79,26 +79,6 @@ public ParseContainerProperties()
NewContainerEnvironmentVariables = Array.Empty();
}
- private static bool TryValidateTags(string[] inputTags, out string[] validTags, out string[] invalidTags)
- {
- var v = new List();
- var i = new List();
- foreach (var tag in inputTags)
- {
- if (ContainerHelpers.IsValidImageTag(tag))
- {
- v.Add(tag);
- }
- else
- {
- i.Add(tag);
- }
- }
- validTags = v.ToArray();
- invalidTags = i.ToArray();
- return invalidTags.Length == 0;
- }
-
public override bool Execute()
{
string[] validTags;
@@ -196,7 +176,7 @@ public override bool Execute()
return !Log.HasLoggedErrors;
}
- public void ValidateEnvironmentVariables()
+ private void ValidateEnvironmentVariables()
{
var filteredEnvVars = ContainerEnvironmentVariables.Where((x) => ContainerHelpers.IsValidEnvironmentVariable(x.ItemSpec)).ToArray();
var badEnvVars = ContainerEnvironmentVariables.Where((x) => !ContainerHelpers.IsValidEnvironmentVariable(x.ItemSpec));
@@ -216,4 +196,24 @@ public void ValidateEnvironmentVariables()
NewContainerEnvironmentVariables[i] = filteredEnvVars[i];
}
}
+
+ private static bool TryValidateTags(string[] inputTags, out string[] validTags, out string[] invalidTags)
+ {
+ var v = new List();
+ var i = new List();
+ foreach (var tag in inputTags)
+ {
+ if (ContainerHelpers.IsValidImageTag(tag))
+ {
+ v.Add(tag);
+ }
+ else
+ {
+ i.Add(tag);
+ }
+ }
+ validTags = v.ToArray();
+ invalidTags = i.ToArray();
+ return invalidTags.Length == 0;
+ }
}
diff --git a/containerize/Program.cs b/containerize/Program.cs
index 89ff1f0e..4188ef13 100644
--- a/containerize/Program.cs
+++ b/containerize/Program.cs
@@ -114,7 +114,7 @@
continue;
}
if (ContainerHelpers.TryParsePort(split[0], split[1], out var portInfo, out var portError)) {
- goodPorts.Add(portInfo);
+ goodPorts.Add(portInfo.Value);
} else {
var pe = (ContainerHelpers.ParsePortError)portError!;
badPorts.Add((port, pe));