Skip to content

Commit 375e494

Browse files
baronfelmarcpopMSFT
authored andcommitted
Merged PR 41305: Added new set of credential env variables to be used separately for pull and...
Added new set of credential env variables to be used separately for pull and push operations. Old set of variables is used for fallback. ---- #### AI description (iteration 1) #### PR Classification New feature: Added support for separate credential environment variables for different registry modes (push, pull, pull from output). #### PR Summary This pull request introduces new environment variables for Docker credentials based on registry modes and updates the relevant classes and tests to support this feature. - `AuthHandshakeMessageHandler.cs`: Added `GetDockerCredentialsFromEnvironment` method to fetch credentials based on registry mode. - `Registry.cs`: Introduced `RegistryMode` enum and updated constructors to handle different registry modes. - `DefaultRegistryAPI.cs`: Updated to use registry mode when creating HTTP clients. - `ContainerHelpers.cs`: Added new constants for push and pull registry credentials. - Added unit tests in `AuthHandshakeMessageHandlerTests.cs` to verify the new credential fetching logic.
1 parent 9952265 commit 375e494

File tree

14 files changed

+179
-33
lines changed

14 files changed

+179
-33
lines changed

src/Containers/Microsoft.NET.Build.Containers/AuthHandshakeMessageHandler.cs

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Collections.Concurrent;
5+
using System.ComponentModel;
56
using System.Diagnostics;
67
using System.Diagnostics.CodeAnalysis;
78
using System.Net;
@@ -38,12 +39,14 @@ private sealed record AuthInfo(string Realm, string? Service, string? Scope);
3839

3940
private readonly string _registryName;
4041
private readonly ILogger _logger;
42+
private readonly RegistryMode _registryMode;
4143
private static ConcurrentDictionary<string, AuthenticationHeaderValue?> _authenticationHeaders = new();
4244

43-
public AuthHandshakeMessageHandler(string registryName, HttpMessageHandler innerHandler, ILogger logger) : base(innerHandler)
45+
public AuthHandshakeMessageHandler(string registryName, HttpMessageHandler innerHandler, ILogger logger, RegistryMode mode) : base(innerHandler)
4446
{
4547
_registryName = registryName;
4648
_logger = logger;
49+
_registryMode = mode;
4750
}
4851

4952
/// <summary>
@@ -156,14 +159,10 @@ public DateTimeOffset ResolvedExpiration
156159
/// </summary>
157160
private async Task<(AuthenticationHeaderValue, DateTimeOffset)?> GetAuthenticationAsync(string registry, string scheme, AuthInfo? bearerAuthInfo, CancellationToken cancellationToken)
158161
{
159-
// Allow overrides for auth via environment variables
160-
string? credU = Environment.GetEnvironmentVariable(ContainerHelpers.HostObjectUser) ?? Environment.GetEnvironmentVariable(ContainerHelpers.HostObjectUserLegacy);
161-
string? credP = Environment.GetEnvironmentVariable(ContainerHelpers.HostObjectPass) ?? Environment.GetEnvironmentVariable(ContainerHelpers.HostObjectPassLegacy);
162-
163-
// fetch creds for the host
162+
164163
DockerCredentials? privateRepoCreds;
165-
166-
if (!string.IsNullOrEmpty(credU) && !string.IsNullOrEmpty(credP))
164+
// Allow overrides for auth via environment variables
165+
if (GetDockerCredentialsFromEnvironment(_registryMode) is (string credU, string credP))
167166
{
168167
privateRepoCreds = new DockerCredentials(credU, credP);
169168
}
@@ -196,6 +195,63 @@ public DateTimeOffset ResolvedExpiration
196195
}
197196
}
198197

198+
internal static (string credU, string credP)? TryGetCredentialsFromEnvVars(string unameVar, string passwordVar)
199+
{
200+
var credU = Environment.GetEnvironmentVariable(unameVar);
201+
var credP = Environment.GetEnvironmentVariable(passwordVar);
202+
if (!string.IsNullOrEmpty(credU) && !string.IsNullOrEmpty(credP))
203+
{
204+
return (credU, credP);
205+
}
206+
else
207+
{
208+
return null;
209+
}
210+
}
211+
212+
/// <summary>
213+
/// Gets docker credentials from the environment variables based on registry mode.
214+
/// </summary>
215+
internal static (string credU, string credP)? GetDockerCredentialsFromEnvironment(RegistryMode mode)
216+
{
217+
if (mode == RegistryMode.Push)
218+
{
219+
if (TryGetCredentialsFromEnvVars(ContainerHelpers.PushHostObjectUser, ContainerHelpers.PushHostObjectPass) is (string, string) pushCreds)
220+
{
221+
return pushCreds;
222+
}
223+
224+
if (TryGetCredentialsFromEnvVars(ContainerHelpers.HostObjectUser, ContainerHelpers.HostObjectPass) is (string, string) genericCreds)
225+
{
226+
return genericCreds;
227+
}
228+
229+
return TryGetCredentialsFromEnvVars(ContainerHelpers.HostObjectUserLegacy, ContainerHelpers.HostObjectPassLegacy);
230+
}
231+
else if (mode == RegistryMode.Pull)
232+
{
233+
return TryGetCredentialsFromEnvVars(ContainerHelpers.PullHostObjectUser, ContainerHelpers.PullHostObjectPass);
234+
}
235+
else if (mode == RegistryMode.PullFromOutput)
236+
{
237+
if (TryGetCredentialsFromEnvVars(ContainerHelpers.PullHostObjectUser, ContainerHelpers.PullHostObjectPass) is (string, string) pullCreds)
238+
{
239+
return pullCreds;
240+
}
241+
242+
if (TryGetCredentialsFromEnvVars(ContainerHelpers.HostObjectUser, ContainerHelpers.HostObjectPass) is (string, string) genericCreds)
243+
{
244+
return genericCreds;
245+
}
246+
247+
return TryGetCredentialsFromEnvVars(ContainerHelpers.HostObjectUserLegacy, ContainerHelpers.HostObjectPassLegacy);
248+
}
249+
else
250+
{
251+
throw new InvalidEnumArgumentException(nameof(mode), (int)mode, typeof(RegistryMode));
252+
}
253+
}
254+
199255
/// <summary>
200256
/// Implements the Docker OAuth2 Authentication flow as documented at <see href="https://docs.docker.com/registry/spec/auth/oauth/"/>.
201257
/// </summary

src/Containers/Microsoft.NET.Build.Containers/ContainerBuilder.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ internal static async Task<int> ContainerizeAsync(
4545
logger.LogTrace("Trace logging: enabled.");
4646

4747
bool isLocalPull = string.IsNullOrEmpty(baseRegistry);
48-
Registry? sourceRegistry = isLocalPull ? null : new Registry(baseRegistry, logger);
48+
RegistryMode sourceRegistryMode = baseRegistry.Equals(outputRegistry, StringComparison.InvariantCultureIgnoreCase) ? RegistryMode.PullFromOutput : RegistryMode.Pull;
49+
Registry? sourceRegistry = isLocalPull ? null : new Registry(baseRegistry, logger, sourceRegistryMode);
4950
SourceImageReference sourceImageReference = new(sourceRegistry, baseImageName, baseImageTag);
5051

5152
DestinationImageReference destinationImageReference = DestinationImageReference.CreateFromSettings(

src/Containers/Microsoft.NET.Build.Containers/ContainerHelpers.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ public static class ContainerHelpers
2222
internal const string HostObjectPass = "DOTNET_CONTAINER_REGISTRY_PWORD";
2323
internal const string HostObjectPassLegacy = "SDK_CONTAINER_REGISTRY_PWORD";
2424

25+
internal const string PushHostObjectUser = "DOTNET_CONTAINER_PUSH_REGISTRY_UNAME";
26+
internal const string PushHostObjectPass = "DOTNET_CONTAINER_PUSH_REGISTRY_PWORD";
27+
28+
internal const string PullHostObjectUser = "DOTNET_CONTAINER_PULL_REGISTRY_UNAME";
29+
internal const string PullHostObjectPass = "DOTNET_CONTAINER_PULL_REGISTRY_PWORD";
30+
2531
internal const string DockerRegistryAlias = "docker.io";
2632

2733
/// <summary>

src/Containers/Microsoft.NET.Build.Containers/DestinationImageReference.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,10 @@ public static DestinationImageReference CreateFromSettings(
7575
}
7676
else if (!string.IsNullOrEmpty(outputRegistry))
7777
{
78-
destinationImageReference = new DestinationImageReference(new Registry(outputRegistry, loggerFactory.CreateLogger<Registry>()), repository, imageTags);
78+
destinationImageReference = new DestinationImageReference(
79+
new Registry(outputRegistry, loggerFactory.CreateLogger<Registry>(), RegistryMode.Push),
80+
repository,
81+
imageTags);
7982
}
8083
else
8184
{

src/Containers/Microsoft.NET.Build.Containers/Registry/DefaultRegistryAPI.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ internal class DefaultRegistryAPI : IRegistryAPI
2222
// Making this a round 30 for convenience.
2323
private static TimeSpan LongRequestTimeout = TimeSpan.FromMinutes(30);
2424

25-
internal DefaultRegistryAPI(string registryName, Uri baseUri, bool isInsecureRegistry, ILogger logger)
25+
internal DefaultRegistryAPI(string registryName, Uri baseUri, bool isInsecureRegistry, ILogger logger, RegistryMode mode)
2626
{
2727
_baseUri = baseUri;
2828
_logger = logger;
29-
_client = CreateClient(registryName, baseUri, isInsecureRegistry, logger);
29+
_client = CreateClient(registryName, baseUri, logger, isInsecureRegistry, mode);
3030
Manifest = new DefaultManifestOperations(_baseUri, registryName, _client, _logger);
3131
Blob = new DefaultBlobOperations(_baseUri, registryName, _client, _logger);
3232
}
@@ -35,11 +35,11 @@ internal DefaultRegistryAPI(string registryName, Uri baseUri, bool isInsecureReg
3535

3636
public IManifestOperations Manifest { get; }
3737

38-
private static HttpClient CreateClient(string registryName, Uri baseUri, bool isInsecureRegistry, ILogger logger)
38+
private static HttpClient CreateClient(string registryName, Uri baseUri, ILogger logger, bool isInsecureRegistry, RegistryMode mode)
3939
{
4040
HttpMessageHandler innerHandler = CreateHttpHandler(baseUri, isInsecureRegistry, logger);
4141

42-
HttpMessageHandler clientHandler = new AuthHandshakeMessageHandler(registryName, innerHandler, logger);
42+
HttpMessageHandler clientHandler = new AuthHandshakeMessageHandler(registryName, innerHandler, logger, mode);
4343

4444
if (baseUri.IsAmazonECRRegistry())
4545
{

src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ public RidGraphManifestPicker(string runtimeIdentifierGraphPath)
5353

5454
}
5555

56+
internal enum RegistryMode
57+
{
58+
Push,
59+
Pull,
60+
PullFromOutput
61+
}
62+
5663
internal sealed class Registry
5764
{
5865
private const string DockerHubRegistry1 = "registry-1.docker.io";
@@ -70,11 +77,24 @@ internal sealed class Registry
7077
/// </summary>
7178
public string RegistryName { get; }
7279

73-
internal Registry(string registryName, ILogger logger, IRegistryAPI? registryAPI = null, RegistrySettings? settings = null) :
80+
internal Registry(string registryName, ILogger logger, IRegistryAPI registryAPI, RegistrySettings? settings = null) :
7481
this(new Uri($"https://{registryName}"), logger, registryAPI, settings)
7582
{ }
7683

77-
internal Registry(Uri baseUri, ILogger logger, IRegistryAPI? registryAPI = null, RegistrySettings? settings = null)
84+
internal Registry(string registryName, ILogger logger, RegistryMode mode, RegistrySettings? settings = null) :
85+
this(new Uri($"https://{registryName}"), logger, new RegistryApiFactory(mode), settings)
86+
{ }
87+
88+
89+
internal Registry(Uri baseUri, ILogger logger, IRegistryAPI registryAPI, RegistrySettings? settings = null) :
90+
this(baseUri, logger, new RegistryApiFactory(registryAPI), settings)
91+
{ }
92+
93+
internal Registry(Uri baseUri, ILogger logger, RegistryMode mode, RegistrySettings? settings = null) :
94+
this(baseUri, logger, new RegistryApiFactory(mode), settings)
95+
{ }
96+
97+
private Registry(Uri baseUri, ILogger logger, RegistryApiFactory factory, RegistrySettings? settings = null)
7898
{
7999
RegistryName = DeriveRegistryName(baseUri);
80100

@@ -87,15 +107,15 @@ internal Registry(Uri baseUri, ILogger logger, IRegistryAPI? registryAPI = null,
87107

88108
_logger = logger;
89109
_settings = settings ?? new RegistrySettings(RegistryName);
90-
_registryAPI = registryAPI ?? new DefaultRegistryAPI(RegistryName, BaseUri, _settings.IsInsecure, logger);
110+
_registryAPI = factory.Create(RegistryName, BaseUri, logger, _settings.IsInsecure);
91111
}
92112

93113
private static string DeriveRegistryName(Uri baseUri)
94114
{
95115
var port = baseUri.Port == -1 ? string.Empty : $":{baseUri.Port}";
96116
if (baseUri.OriginalString.EndsWith(port, ignoreCase: true, culture: null))
97117
{
98-
// the port was part of the original assignment, so it's ok to consider it part of the 'name
118+
// the port was part of the original assignment, so it's ok to consider it part of the 'name'
99119
return baseUri.GetComponents(UriComponents.HostAndPort, UriFormat.Unescaped);
100120
}
101121
else
@@ -507,4 +527,25 @@ private async Task PushAsync(BuiltImage builtImage, SourceImageReference source,
507527
_logger.LogInformation(Strings.Registry_ManifestUploaded, RegistryName);
508528
}
509529
}
530+
531+
private readonly ref struct RegistryApiFactory
532+
{
533+
private readonly IRegistryAPI? _registryApi;
534+
private readonly RegistryMode? _mode;
535+
536+
public RegistryApiFactory(IRegistryAPI registryApi)
537+
{
538+
_registryApi = registryApi;
539+
}
540+
541+
public RegistryApiFactory(RegistryMode mode)
542+
{
543+
_mode = mode;
544+
}
545+
546+
public IRegistryAPI Create(string registryName, Uri baseUri, ILogger logger, bool isInsecureRegistry)
547+
{
548+
return _registryApi ?? new DefaultRegistryAPI(registryName, baseUri, isInsecureRegistry, logger, _mode!.Value);
549+
}
550+
}
510551
}

src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ internal async Task<bool> ExecuteAsync(CancellationToken cancellationToken)
6060
return !Log.HasLoggedErrors;
6161
}
6262

63-
Registry? sourceRegistry = IsLocalPull ? null : new Registry(BaseRegistry, logger);
63+
RegistryMode sourceRegistryMode = BaseRegistry.Equals(OutputRegistry, StringComparison.InvariantCultureIgnoreCase) ? RegistryMode.PullFromOutput : RegistryMode.Pull;
64+
Registry? sourceRegistry = IsLocalPull ? null : new Registry(BaseRegistry, logger, sourceRegistryMode);
6465
SourceImageReference sourceImageReference = new(sourceRegistry, BaseImageName, BaseImageTag);
6566

6667
DestinationImageReference destinationImageReference = DestinationImageReference.CreateFromSettings(

src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/CreateNewImageTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ public async System.Threading.Tasks.Task CreateNewImage_RootlessBaseImage()
225225
var logger = loggerFactory.CreateLogger(nameof(CreateNewImage_RootlessBaseImage));
226226

227227
// Build a rootless base runtime image.
228-
Registry registry = new Registry(DockerRegistryManager.LocalRegistry, logger);
228+
Registry registry = new(DockerRegistryManager.LocalRegistry, logger, RegistryMode.Push);
229229

230230
ImageBuilder imageBuilder = await registry.GetImageManifestAsync(
231231
DockerRegistryManager.RuntimeBaseImage,

src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerRegistryManager.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ public static async Task StartAndPopulateDockerRegistry(ITestOutputHelper testOu
4343
int spawnRegistryDelay = 1000; //ms
4444
StringBuilder failureReasons = new();
4545

46-
var pullRegistry = new Registry(BaseImageSource, logger);
47-
var pushRegistry = new Registry(LocalRegistry, logger);
46+
var pullRegistry = new Registry(BaseImageSource, logger, RegistryMode.Pull);
47+
var pushRegistry = new Registry(LocalRegistry, logger, RegistryMode.Push);
4848

4949
for (int spawnRegistryAttempt = 1; spawnRegistryAttempt <= spawnRegistryMaxRetry; spawnRegistryAttempt++)
5050
{

src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerRegistryTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public async Task GetFromRegistry()
2222
{
2323
var loggerFactory = new TestLoggerFactory(_testOutput);
2424
var logger = loggerFactory.CreateLogger(nameof(GetFromRegistry));
25-
Registry registry = new Registry(DockerRegistryManager.LocalRegistry, logger);
25+
Registry registry = new(DockerRegistryManager.LocalRegistry, logger, RegistryMode.Push);
2626
var ridgraphfile = ToolsetUtils.GetRuntimeGraphFilePath();
2727

2828
// Don't need rid graph for local registry image pulls - since we're only pushing single image manifests (not manifest lists)
@@ -74,9 +74,9 @@ public async Task WriteToPrivateBasicRegistry()
7474
// login to that registry
7575
ContainerCli.LoginCommand(_testOutput, "--username", "testuser", "--password", "testpassword", registryName).Execute().Should().Pass();
7676
// push an image to that registry using username/password
77-
Registry localAuthed = new(new Uri($"https://{registryName}"), logger, settings: new(registryName) { ParallelUploadEnabled = false, ForceChunkedUpload = true });
77+
Registry localAuthed = new(new Uri($"https://{registryName}"), logger, RegistryMode.Push, settings: new() { ParallelUploadEnabled = false, ForceChunkedUpload = true });
7878
var ridgraphfile = ToolsetUtils.GetRuntimeGraphFilePath();
79-
Registry mcr = new Registry(DockerRegistryManager.BaseImageSource, logger);
79+
Registry mcr = new(DockerRegistryManager.BaseImageSource, logger, RegistryMode.Pull);
8080

8181
var sourceImage = new SourceImageReference(mcr, DockerRegistryManager.RuntimeBaseImage, DockerRegistryManager.Net6ImageTag);
8282
var destinationImage = new DestinationImageReference(localAuthed, DockerRegistryManager.RuntimeBaseImage, new[] { DockerRegistryManager.Net6ImageTag });

0 commit comments

Comments
 (0)