Skip to content

Commit

Permalink
refactor(#461): Rebase on develop, add minor improvments
Browse files Browse the repository at this point in the history
  • Loading branch information
HofmeisterAn committed Jun 16, 2022
1 parent 9731c89 commit 7a25edc
Show file tree
Hide file tree
Showing 23 changed files with 216 additions and 92 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ jobs:
uses: actions/checkout@v2

- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}

- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v2

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2
8 changes: 6 additions & 2 deletions src/Testcontainers/Builders/Base64Provider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
using JetBrains.Annotations;
using Microsoft.Extensions.Logging;

/// <inheritdoc />
/// <inheritdoc cref="IDockerEndpointAuthenticationProvider" />
internal sealed class Base64Provider : IDockerRegistryAuthenticationProvider
{
private readonly JsonElement rootElement;
Expand Down Expand Up @@ -41,7 +41,11 @@ public Base64Provider(JsonElement jsonElement, ILogger logger)
/// <inheritdoc />
public bool IsApplicable(string hostname)
{
return !default(JsonElement).Equals(this.rootElement) && this.rootElement.EnumerateObject().Any();
#if NETSTANDARD2_1_OR_GREATER
return !default(JsonElement).Equals(this.rootElement) && !JsonValueKind.Null.Equals(this.rootElement.ValueKind) && this.rootElement.EnumerateObject().Any(property => property.Name.Contains(hostname, StringComparison.OrdinalIgnoreCase));
#else
return !default(JsonElement).Equals(this.rootElement) && !JsonValueKind.Null.Equals(this.rootElement.ValueKind) && this.rootElement.EnumerateObject().Any(property => property.Name.IndexOf(hostname, StringComparison.OrdinalIgnoreCase) >= 0);
#endif
}

/// <inheritdoc />
Expand Down
89 changes: 66 additions & 23 deletions src/Testcontainers/Builders/CredsHelperProvider.cs
Original file line number Diff line number Diff line change
@@ -1,66 +1,109 @@
namespace DotNet.Testcontainers.Builders
{
using System;
using System.Linq;
using System.Text.Json;
using DotNet.Testcontainers.Configurations;
using JetBrains.Annotations;
using Microsoft.Extensions.Logging;

/// <inheritdoc />
/// <inheritdoc cref="IDockerEndpointAuthenticationProvider" />
internal sealed class CredsHelperProvider : IDockerRegistryAuthenticationProvider
{
private const string TokenUsername = "<token>";
private readonly JsonElement dockerConfig;
private readonly JsonElement rootElement;

private readonly ILogger logger;

public CredsHelperProvider(JsonElement dockerConfig, ILogger logger)
/// <summary>
/// Initializes a new instance of the <see cref="CredsHelperProvider" /> class.
/// </summary>
/// <param name="jsonDocument">The JSON document that holds the Docker config credsHelper node.</param>
/// <param name="logger">The logger.</param>
[PublicAPI]
public CredsHelperProvider(JsonDocument jsonDocument, ILogger logger)
: this(jsonDocument.RootElement, logger)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="CredsHelperProvider" /> class.
/// </summary>
/// <param name="jsonElement">The JSON element that holds the Docker config credsHelper node.</param>
/// <param name="logger">The logger.</param>
[PublicAPI]
public CredsHelperProvider(JsonElement jsonElement, ILogger logger)
{
this.dockerConfig = dockerConfig;
this.rootElement = jsonElement.TryGetProperty("credHelpers", out var credHelpers) ? credHelpers : default;
this.logger = logger;
}

/// <inheritdoc />
public bool IsApplicable(string hostname)
{
return this.FindCredHelperName(hostname) != null;
#if NETSTANDARD2_1_OR_GREATER
return !default(JsonElement).Equals(this.rootElement) && !JsonValueKind.Null.Equals(this.rootElement.ValueKind) && this.rootElement.EnumerateObject().Any(property => property.Name.Contains(hostname, StringComparison.OrdinalIgnoreCase));
#else
return !default(JsonElement).Equals(this.rootElement) && !JsonValueKind.Null.Equals(this.rootElement.ValueKind) && this.rootElement.EnumerateObject().Any(property => property.Name.IndexOf(hostname, StringComparison.OrdinalIgnoreCase) >= 0);
#endif
}

/// <inheritdoc />
public IDockerRegistryAuthenticationConfiguration GetAuthConfig(string hostname)
{
this.logger.SearchingDockerRegistryCredential("CredsHelper");
var credHelperName = this.FindCredHelperName(hostname);
if (credHelperName == null)
this.logger.SearchingDockerRegistryCredential("CredHelpers");

if (!this.IsApplicable(hostname))
{
return null;
}

#if NETSTANDARD2_1_OR_GREATER
var registryEndpointProperty = this.rootElement.EnumerateObject().LastOrDefault(property => property.Name.Contains(hostname, StringComparison.OrdinalIgnoreCase));
#else
var registryEndpointProperty = this.rootElement.EnumerateObject().LastOrDefault(property => property.Name.IndexOf(hostname, StringComparison.OrdinalIgnoreCase) >= 0);
#endif

if (!JsonValueKind.String.Equals(registryEndpointProperty.Value.ValueKind))
{
return null;
}

var credentialProviderOutput = ExternalProcessCredentialProvider.GetCredentialProviderOutput(credHelperName, hostname);
if (credentialProviderOutput == null)
if (string.IsNullOrEmpty(registryEndpointProperty.Value.GetString()))
{
return null;
}

DockerRegistryAuthenticationConfiguration dockerRegistryAuthenticationConfiguration;
var credentialProviderOutput = DockerCredentialProcess.Get(registryEndpointProperty.Value.GetString(), hostname);
if (string.IsNullOrEmpty(credentialProviderOutput))
{
return null;
}

JsonElement credential;

try
{
var credentialProviderOutputJson = JsonSerializer.Deserialize<JsonElement>(credentialProviderOutput);
var username = credentialProviderOutputJson.GetProperty("Username").GetString();
var secret = credentialProviderOutputJson.GetProperty("Secret").GetString();
dockerRegistryAuthenticationConfiguration = username == TokenUsername ? new DockerRegistryAuthenticationConfiguration(hostname, identityToken: secret) : new DockerRegistryAuthenticationConfiguration(hostname, username, secret);
credential = JsonDocument.Parse(credentialProviderOutput).RootElement;
}
catch (JsonException)
{
return null;
}

var username = credential.TryGetProperty("Username", out var usernameProperty) ? usernameProperty.GetString() : null;

var password = credential.TryGetProperty("Secret", out var passwordProperty) ? passwordProperty.GetString() : null;

this.logger.DockerRegistryCredentialFound(hostname);
return dockerRegistryAuthenticationConfiguration;
}

private string FindCredHelperName(string hostname)
{
return this.dockerConfig.TryGetProperty("credHelpers", out var credHelpersProperty) && credHelpersProperty.ValueKind != JsonValueKind.Null && credHelpersProperty.TryGetProperty(hostname, out var credHelperProperty)
? credHelperProperty.GetString()
: null;
if ("<token>".Equals(username, StringComparison.OrdinalIgnoreCase))
{
return new DockerRegistryAuthenticationConfiguration(hostname, null, null, password);
}
else
{
return new DockerRegistryAuthenticationConfiguration(hostname, username, password);
}
}
}
}
6 changes: 3 additions & 3 deletions src/Testcontainers/Builders/CredsStoreProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using JetBrains.Annotations;
using Microsoft.Extensions.Logging;

/// <inheritdoc />
/// <inheritdoc cref="IDockerEndpointAuthenticationProvider" />
internal sealed class CredsStoreProvider : IDockerRegistryAuthenticationProvider
{
private static readonly JsonSerializerOptions JsonSerializerOptions = new JsonSerializerOptions();
Expand Down Expand Up @@ -59,8 +59,8 @@ public IDockerRegistryAuthenticationConfiguration GetAuthConfig(string hostname)
return null;
}

var credentialProviderOutput = ExternalProcessCredentialProvider.GetCredentialProviderOutput(this.rootElement.GetString(), hostname);
if (credentialProviderOutput == null)
var credentialProviderOutput = DockerCredentialProcess.Get(this.rootElement.GetString(), hostname);
if (string.IsNullOrEmpty(credentialProviderOutput))
{
return null;
}
Expand Down
41 changes: 41 additions & 0 deletions src/Testcontainers/Builders/DockerCredentialProcess.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
namespace DotNet.Testcontainers.Builders
{
using System;
using System.Diagnostics;

internal static class DockerCredentialProcess
{
public static string Get(string credentialProviderName, string hostname)
{
var dockerCredentialStartInfo = new ProcessStartInfo();
dockerCredentialStartInfo.FileName = "docker-credential-" + credentialProviderName;
dockerCredentialStartInfo.Arguments = "get";
dockerCredentialStartInfo.RedirectStandardInput = true;
dockerCredentialStartInfo.RedirectStandardOutput = true;
dockerCredentialStartInfo.UseShellExecute = false;

var dockerCredentialProcess = new Process();
dockerCredentialProcess.StartInfo = dockerCredentialStartInfo;

try
{
if (!dockerCredentialProcess.Start())
{
return null;
}

dockerCredentialProcess.StandardInput.WriteLine(hostname);
dockerCredentialProcess.StandardInput.Close();
return dockerCredentialProcess.StandardOutput.ReadToEnd().Trim();
}
catch (Exception)
{
return null;
}
finally
{
dockerCredentialProcess.Dispose();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using System.Linq;
using DotNet.Testcontainers.Configurations;

/// <inheritdoc />
/// <inheritdoc cref="IDockerEndpointAuthenticationProvider" />
internal sealed class DockerEndpointAuthenticationProvider : IDockerEndpointAuthenticationProvider
{
/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
using JetBrains.Annotations;
using Microsoft.Extensions.Logging;

/// <inheritdoc />
/// <inheritdoc cref="IDockerRegistryAuthenticationProvider" />
internal sealed class DockerRegistryAuthenticationProvider : IDockerRegistryAuthenticationProvider
{
private const string DockerHub = "index.docker.io";
Expand Down Expand Up @@ -83,10 +83,9 @@ private IDockerRegistryAuthenticationConfiguration GetUncachedAuthConfig(string
{
authConfig = new IDockerRegistryAuthenticationProvider[]
{
new CredsHelperProvider(dockerConfigDocument.RootElement, this.logger),
new CredsStoreProvider(dockerConfigDocument, this.logger),
new Base64Provider(dockerConfigDocument, this.logger),
}.AsParallel()
new CredsHelperProvider(dockerConfigDocument, this.logger), new CredsStoreProvider(dockerConfigDocument, this.logger), new Base64Provider(dockerConfigDocument, this.logger),
}
.AsParallel()
.Select(authenticationProvider => authenticationProvider.GetAuthConfig(hostname))
.FirstOrDefault(authenticationProvider => authenticationProvider != null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using System;
using DotNet.Testcontainers.Configurations;

/// <inheritdoc />
/// <inheritdoc cref="IDockerRegistryAuthenticationProvider" />
internal sealed class EnvironmentEndpointAuthenticationProvider : IDockerEndpointAuthenticationProvider
{
private readonly Uri dockerEngine;
Expand Down
28 changes: 0 additions & 28 deletions src/Testcontainers/Builders/IAuthenticationProvider.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace DotNet.Testcontainers.Builders
{
using DotNet.Testcontainers.Configurations;
using JetBrains.Annotations;

/// <summary>
/// A Docker endpoint authentication provider.
/// </summary>
[PublicAPI]
internal interface IDockerEndpointAuthenticationProvider
{
/// <summary>
/// Is true when the authentication provider contains any Docker endpoint credentials, otherwise false.
/// </summary>
/// <returns>True when the authentication provider contains any Docker endpoint credentials, otherwise false.</returns>
[PublicAPI]
bool IsApplicable();

/// <summary>
/// Gets the Docker endpoint authentication configuration.
/// </summary>
/// <returns>The Docker endpoint authentication configuration or null if no configuration matches the hostname.</returns>
[PublicAPI]
[CanBeNull]
IDockerEndpointAuthenticationConfiguration GetAuthConfig();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace DotNet.Testcontainers.Builders
{
using DotNet.Testcontainers.Configurations;
using JetBrains.Annotations;

/// <summary>
/// A Docker registry authentication provider.
/// </summary>
[PublicAPI]
internal interface IDockerRegistryAuthenticationProvider
{
/// <summary>
/// Is true when the authentication provider contains any Docker registry credentials, otherwise false.
/// </summary>
/// <param name="hostname">The Docker hostname.</param>
/// <returns>True when the authentication provider contains any Docker registry credentials, otherwise false.</returns>
[PublicAPI]
bool IsApplicable(string hostname);

/// <summary>
/// Gets the Docker registry authentication configuration.
/// </summary>
/// <param name="hostname">The Docker hostname.</param>
/// <returns>The Docker registry authentication configuration or null if no configuration matches the hostname.</returns>
[PublicAPI]
[CanBeNull]
IDockerRegistryAuthenticationConfiguration GetAuthConfig(string hostname);
}
}
2 changes: 1 addition & 1 deletion src/Testcontainers/Builders/ITestcontainersBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ public interface ITestcontainersBuilder<out TDockerContainer> : IAbstractBuilder
/// <param name="password">Password to authenticate.</param>
/// <returns>A configured instance of <see cref="ITestcontainersBuilder{TDockerContainer}" />.</returns>
[PublicAPI]
[Obsolete("Be aware we will replace this method in the future. Instead, we will use the local Docker credential store. Then, no additional configurations are necessary.")]
[Obsolete("Use the local Docker credential store.")]
ITestcontainersBuilder<TDockerContainer> WithRegistryAuthentication(string registryEndpoint, string username, string password);

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using System.Runtime.InteropServices;
using DotNet.Testcontainers.Configurations;

/// <inheritdoc />
/// <inheritdoc cref="IDockerRegistryAuthenticationProvider" />
internal sealed class NpipeEndpointAuthenticationProvider : IDockerEndpointAuthenticationProvider
{
#pragma warning disable S1075
Expand Down
4 changes: 2 additions & 2 deletions src/Testcontainers/Builders/TestcontainersBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace DotNet.Testcontainers.Builders
/// </summary>
/// <example>
/// <code>
/// var builder = new builder&lt;TestcontainersContainer&gt;()
/// var builder = new TestcontainersBuilder&lt;TestcontainersContainer&gt;()
/// .WithName(&quot;nginx&quot;)
/// .WithImage(&quot;nginx&quot;)
/// .WithEntrypoint(&quot;...&quot;)
Expand Down Expand Up @@ -52,7 +52,7 @@ public TestcontainersBuilder()
labels: DefaultLabels.Instance,
outputConsumer: Consume.DoNotConsumeStdoutAndStderr(),
waitStrategies: Wait.ForUnixContainer().Build(),
startupCallback: (testcontainers, ct) => Task.CompletedTask,
startupCallback: (_, ct) => Task.CompletedTask,
autoRemove: false,
privileged: false),
_ => { })
Expand Down
Loading

0 comments on commit 7a25edc

Please sign in to comment.