Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 52 additions & 5 deletions src/Testcontainers/Builders/Base64Provider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,40 @@ public Base64Provider(JsonElement jsonElement, ILogger logger)
}

/// <summary>
/// Gets a predicate that determines whether a <see cref="JsonProperty" /> contains a Docker registry key.
/// Determines whether the specified JSON property contains a Docker registry
/// that matches the given registry host.
/// </summary>
public static Func<JsonProperty, string, bool> HasDockerRegistryKey { get; }
= (property, hostname) => property.Name.Equals(hostname, StringComparison.OrdinalIgnoreCase) || property.Name.EndsWith("://" + hostname, StringComparison.OrdinalIgnoreCase);
/// <param name="property">The JSON property to check.</param>
/// <param name="registryHost">The registry host to match against.</param>
/// <returns><c>true</c> if the property contains a matching Docker registry; otherwise, <c>false</c>.</returns>
public static bool HasDockerRegistryName(JsonProperty property, string registryHost)
{
var propertyName = property.Name;

if (propertyName.Equals(registryHost, StringComparison.OrdinalIgnoreCase))
{
return true;
}

if (propertyName.EndsWith("://" + registryHost, StringComparison.OrdinalIgnoreCase))
{
return true;
}

if (TryGetHost(propertyName, out var propertyNameNormalized) && TryGetHost(registryHost, out var registryHostNormalized))
{
return string.Equals(propertyNameNormalized, registryHostNormalized, StringComparison.OrdinalIgnoreCase);
}
else
{
return false;
}
}

/// <inheritdoc />
public bool IsApplicable(string hostname)
{
return !JsonValueKind.Undefined.Equals(_rootElement.ValueKind) && !JsonValueKind.Null.Equals(_rootElement.ValueKind) && _rootElement.EnumerateObject().Any(property => HasDockerRegistryKey(property, hostname));
return !JsonValueKind.Undefined.Equals(_rootElement.ValueKind) && !JsonValueKind.Null.Equals(_rootElement.ValueKind) && _rootElement.EnumerateObject().Any(property => HasDockerRegistryName(property, hostname));
}

/// <inheritdoc />
Expand All @@ -60,7 +85,7 @@ public IDockerRegistryAuthenticationConfiguration GetAuthConfig(string hostname)
return null;
}

var authProperty = _rootElement.EnumerateObject().LastOrDefault(property => HasDockerRegistryKey(property, hostname));
var authProperty = _rootElement.EnumerateObject().LastOrDefault(property => HasDockerRegistryName(property, hostname));

if (JsonValueKind.Undefined.Equals(authProperty.Value.ValueKind))
{
Expand Down Expand Up @@ -120,5 +145,27 @@ public IDockerRegistryAuthenticationConfiguration GetAuthConfig(string hostname)
_logger.DockerRegistryCredentialFound(hostname);
return new DockerRegistryAuthenticationConfiguration(authProperty.Name, credential[0], credential[1]);
}

/// <summary>
/// Tries to extract the host from the specified value.
/// </summary>
/// <param name="value">The string to extract the host from.</param>
/// <param name="host">The extracted host if successful; otherwise, the original string.</param>
/// <returns><c>true</c> if the host was successfully extracted; otherwise, <c>false</c>.</returns>
private static bool TryGetHost(string value, out string host)
{
var uriToParse = value.Contains("://") ? value : "dummy://" + value;

if (Uri.TryCreate(uriToParse, UriKind.Absolute, out var uri))
{
host = uri.Port == -1 || uri.IsDefaultPort ? uri.Host : uri.Host + ":" + uri.Port;
return true;
}
else
{
host = value;
return false;
}
}
}
}
4 changes: 2 additions & 2 deletions src/Testcontainers/Builders/CredsHelperProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public CredsHelperProvider(JsonElement jsonElement, ILogger logger)
/// <inheritdoc />
public bool IsApplicable(string hostname)
{
return !JsonValueKind.Undefined.Equals(_rootElement.ValueKind) && !JsonValueKind.Null.Equals(_rootElement.ValueKind) && _rootElement.EnumerateObject().Any(property => Base64Provider.HasDockerRegistryKey(property, hostname));
return !JsonValueKind.Undefined.Equals(_rootElement.ValueKind) && !JsonValueKind.Null.Equals(_rootElement.ValueKind) && _rootElement.EnumerateObject().Any(property => Base64Provider.HasDockerRegistryName(property, hostname));
}

/// <inheritdoc />
Expand All @@ -52,7 +52,7 @@ public IDockerRegistryAuthenticationConfiguration GetAuthConfig(string hostname)
return null;
}

var registryEndpointProperty = _rootElement.EnumerateObject().LastOrDefault(property => Base64Provider.HasDockerRegistryKey(property, hostname));
var registryEndpointProperty = _rootElement.EnumerateObject().LastOrDefault(property => Base64Provider.HasDockerRegistryName(property, hostname));

if (!JsonValueKind.String.Equals(registryEndpointProperty.Value.ValueKind))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,29 @@ public sealed class Base64ProviderTest
private readonly WarnLogger _warnLogger = new WarnLogger();

[Theory]
[InlineData("{\"auths\":{\"ghcr.io\":{}}}")]
[InlineData("{\"auths\":{\"://ghcr.io\":{}}}")]
public void ResolvePartialDockerRegistry(string jsonDocument)
[InlineData("{\"auths\":{\"ghcr.io\":{}}}", "ghcr.io", true)]
[InlineData("{\"auths\":{\"ghcr.io\":{}}}", "ghcr", false)]
[InlineData("{\"auths\":{\"http://ghcr.io\":{}}}", "ghcr.io", true)]
[InlineData("{\"auths\":{\"https://ghcr.io\":{}}}", "ghcr.io", true)]
[InlineData("{\"auths\":{\"registry.example.com:5000\":{}}}", "registry.example.com:5000", true)]
[InlineData("{\"auths\":{\"localhost:5000\":{}}}", "localhost:5000", true)]
[InlineData("{\"auths\":{\"registry.example.com:5000\":{}}}", "registry.example.com", false)]
[InlineData("{\"auths\":{\"localhost:5000\":{}}}", "localhost", false)]
[InlineData("{\"auths\":{\"https://registry.example.com:5000\":{}}}", "registry.example.com:5000", true)]
[InlineData("{\"auths\":{\"http://localhost:8080\":{}}}", "localhost:8080", true)]
[InlineData("{\"auths\":{\"docker.io\":{}}}", "docker.io", true)]
[InlineData("{\"auths\":{\"docker.io\":{}}}", "index.docker.io", false)]
[InlineData("{\"auths\":{\"index.docker.io\":{}}}", "docker.io", false)]
[InlineData("{\"auths\":{\"https://index.docker.io/v1/\":{}}}", "index.docker.io", true)]
[InlineData("{\"auths\":{\"registry.k8s.io\":{}}}", "registry.k8s.io", true)]
[InlineData("{\"auths\":{\"gcr.io\":{}}}", "gcr.io", true)]
[InlineData("{\"auths\":{\"us-docker.pkg.dev\":{}}}", "us-docker.pkg.dev", true)]
[InlineData("{\"auths\":{\"quay.io\":{}}}", "quay.io", true)]
[InlineData("{\"auths\":{\"localhost\":{}}}", "localhost", true)]
[InlineData("{\"auths\":{\"127.0.0.1:5000\":{}}}", "127.0.0.1:5000", true)]
[InlineData("{\"auths\":{\"[::1]:5000\":{}}}", "[::1]:5000", true)]
[InlineData("{\"auths\":{\"https://registry.example.com/v2\":{}}}", "registry.example.com", true)]
public void ResolvePartialDockerRegistry(string jsonDocument, string hostname, bool expectedResult)
{
// Given
var jsonElement = JsonDocument.Parse(jsonDocument).RootElement;
Expand All @@ -77,8 +97,7 @@ public void ResolvePartialDockerRegistry(string jsonDocument)
var authenticationProvider = new Base64Provider(jsonElement, NullLogger.Instance);

// Then
Assert.False(authenticationProvider.IsApplicable("ghcr"));
Assert.True(authenticationProvider.IsApplicable("ghcr.io"));
Assert.Equal(expectedResult, authenticationProvider.IsApplicable(hostname));
}

[Theory]
Expand Down