diff --git a/src/Testcontainers/Builders/Base64Provider.cs b/src/Testcontainers/Builders/Base64Provider.cs index de0671e57..465477792 100644 --- a/src/Testcontainers/Builders/Base64Provider.cs +++ b/src/Testcontainers/Builders/Base64Provider.cs @@ -39,15 +39,40 @@ public Base64Provider(JsonElement jsonElement, ILogger logger) } /// - /// Gets a predicate that determines whether a contains a Docker registry key. + /// Determines whether the specified JSON property contains a Docker registry + /// that matches the given registry host. /// - public static Func HasDockerRegistryKey { get; } - = (property, hostname) => property.Name.Equals(hostname, StringComparison.OrdinalIgnoreCase) || property.Name.EndsWith("://" + hostname, StringComparison.OrdinalIgnoreCase); + /// The JSON property to check. + /// The registry host to match against. + /// true if the property contains a matching Docker registry; otherwise, false. + 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; + } + } /// 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)); } /// @@ -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)) { @@ -120,5 +145,27 @@ public IDockerRegistryAuthenticationConfiguration GetAuthConfig(string hostname) _logger.DockerRegistryCredentialFound(hostname); return new DockerRegistryAuthenticationConfiguration(authProperty.Name, credential[0], credential[1]); } + + /// + /// Tries to extract the host from the specified value. + /// + /// The string to extract the host from. + /// The extracted host if successful; otherwise, the original string. + /// true if the host was successfully extracted; otherwise, false. + 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; + } + } } } diff --git a/src/Testcontainers/Builders/CredsHelperProvider.cs b/src/Testcontainers/Builders/CredsHelperProvider.cs index 4e2ab9387..6072f53a1 100644 --- a/src/Testcontainers/Builders/CredsHelperProvider.cs +++ b/src/Testcontainers/Builders/CredsHelperProvider.cs @@ -39,7 +39,7 @@ public CredsHelperProvider(JsonElement jsonElement, ILogger logger) /// 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)); } /// @@ -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)) { diff --git a/tests/Testcontainers.Tests/Unit/Configurations/DockerRegistryAuthenticationProviderTest.cs b/tests/Testcontainers.Tests/Unit/Configurations/DockerRegistryAuthenticationProviderTest.cs index 8be2f65b3..855d190c0 100644 --- a/tests/Testcontainers.Tests/Unit/Configurations/DockerRegistryAuthenticationProviderTest.cs +++ b/tests/Testcontainers.Tests/Unit/Configurations/DockerRegistryAuthenticationProviderTest.cs @@ -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; @@ -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]