Skip to content

Commit

Permalink
fix: Add Keycloak health port 9000 starting from major version 25 (#1213
Browse files Browse the repository at this point in the history
)

Co-authored-by: Andre Hofmeister <[email protected]>
  • Loading branch information
paulomorgado and HofmeisterAn authored Aug 7, 2024
1 parent 9a59b3e commit 1a64762
Show file tree
Hide file tree
Showing 9 changed files with 191 additions and 7 deletions.
22 changes: 18 additions & 4 deletions src/Testcontainers.Keycloak/KeycloakBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ public sealed class KeycloakBuilder : ContainerBuilder<KeycloakBuilder, Keycloak

public const ushort KeycloakPort = 8080;

public const ushort KeycloakHealthPort = 9000;

public const string DefaultUsername = "admin";

public const string DefaultPassword = "admin";
Expand Down Expand Up @@ -60,7 +62,20 @@ public KeycloakBuilder WithPassword(string password)
public override KeycloakContainer Build()
{
Validate();
return new KeycloakContainer(DockerResourceConfiguration);

Predicate<System.Version> predicate = v => v.Major >= 25;

var image = DockerResourceConfiguration.Image;

// https://www.keycloak.org/docs/latest/release_notes/index.html#management-port-for-metrics-and-health-endpoints.
var isMajorVersionGreaterOrEqual25 = image.MatchLatestOrNightly() || image.MatchVersion(predicate);

var waitStrategy = Wait.ForUnixContainer()
.UntilHttpRequestIsSucceeded(request =>
request.ForPath("/health/ready").ForPort(isMajorVersionGreaterOrEqual25 ? KeycloakHealthPort : KeycloakPort));

var keycloakBuilder = DockerResourceConfiguration.WaitStrategies.Count() > 1 ? this : WithWaitStrategy(waitStrategy);
return new KeycloakContainer(keycloakBuilder.DockerResourceConfiguration);
}

/// <inheritdoc />
Expand All @@ -70,11 +85,10 @@ protected override KeycloakBuilder Init()
.WithImage(KeycloakImage)
.WithCommand("start-dev")
.WithPortBinding(KeycloakPort, true)
.WithPortBinding(KeycloakHealthPort, true)
.WithUsername(DefaultUsername)
.WithPassword(DefaultPassword)
.WithEnvironment("KC_HEALTH_ENABLED", "true")
.WithWaitStrategy(Wait.ForUnixContainer().UntilHttpRequestIsSucceeded(request =>
request.ForPath("/health/ready").ForPort(KeycloakPort)));
.WithEnvironment("KC_HEALTH_ENABLED", "true");
}

/// <inheritdoc />
Expand Down
1 change: 1 addition & 0 deletions src/Testcontainers.Keycloak/Usings.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
global using System;
global using System.Linq;
global using Docker.DotNet.Models;
global using DotNet.Testcontainers;
global using DotNet.Testcontainers.Builders;
Expand Down
22 changes: 22 additions & 0 deletions src/Testcontainers/Images/DockerImage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ namespace DotNet.Testcontainers.Images
{
using System;
using System.Linq;
using System.Text.RegularExpressions;
using JetBrains.Annotations;

/// <inheritdoc cref="IImage" />
Expand All @@ -10,6 +11,8 @@ public sealed class DockerImage : IImage
{
private const string LatestTag = "latest";

private const string NightlyTag = "nightly";

private static readonly Func<string, IImage> GetDockerImage = MatchImage.Match;

private static readonly char[] TrimChars = { ' ', ':', '/' };
Expand Down Expand Up @@ -107,6 +110,25 @@ public DockerImage(
/// <inheritdoc />
public string GetHostname() => _lazyHostname.Value;

/// <inheritdoc />
public bool MatchLatestOrNightly()
{
return MatchVersion((string tag) => LatestTag.Equals(tag) || NightlyTag.Equals(tag));
}

/// <inheritdoc />
public bool MatchVersion(Predicate<string> predicate)
{
return predicate(Tag);
}

/// <inheritdoc />
public bool MatchVersion(Predicate<Version> predicate)
{
var versionMatch = Regex.Match(Tag, "^\\d+(\\.\\d+)?(\\.\\d+)?", RegexOptions.None, TimeSpan.FromSeconds(1));
return versionMatch.Success && Version.TryParse(versionMatch.Value, out var version) && predicate(version);
}

private static string TrimOrDefault(string value, string defaultValue = default)
{
return string.IsNullOrEmpty(value) ? defaultValue : value.Trim(TrimChars);
Expand Down
19 changes: 19 additions & 0 deletions src/Testcontainers/Images/FutureDockerImage.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace DotNet.Testcontainers.Images
{
using System;
using System.Threading;
using System.Threading.Tasks;
using Docker.DotNet.Models;
Expand Down Expand Up @@ -74,6 +75,24 @@ public string GetHostname()
return _configuration.Image.GetHostname();
}

/// <inheritdoc />
public bool MatchLatestOrNightly()
{
return _configuration.Image.MatchLatestOrNightly();
}

/// <inheritdoc />
public bool MatchVersion(Predicate<string> predicate)
{
return _configuration.Image.MatchVersion(predicate);
}

/// <inheritdoc />
public bool MatchVersion(Predicate<System.Version> predicate)
{
return _configuration.Image.MatchVersion(predicate);
}

/// <inheritdoc />
public async Task CreateAsync(CancellationToken ct = default)
{
Expand Down
21 changes: 21 additions & 0 deletions src/Testcontainers/Images/IImage.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace DotNet.Testcontainers.Images
{
using System;
using JetBrains.Annotations;

/// <summary>
Expand Down Expand Up @@ -41,5 +42,25 @@ public interface IImage
/// <returns>The registry hostname.</returns>
[CanBeNull]
string GetHostname();

/// <summary>
/// Checks if the tag matches either the latest or nightly tag.
/// </summary>
/// <returns>True if the tag matches the latest or nightly tag, otherwise false.</returns>
bool MatchLatestOrNightly();

/// <summary>
/// Checks if the tag matches the specified predicate.
/// </summary>
/// <param name="predicate">The predicate to match the tag against.</param>
/// <returns>True if the tag matches the predicate, otherwise false.</returns>
bool MatchVersion(Predicate<string> predicate);

/// <summary>
/// Checks if the tag matches the specified predicate.
/// </summary>
/// <param name="predicate">The predicate to match the tag against.</param>
/// <returns>True if the tag matches the predicate, otherwise false.</returns>
bool MatchVersion(Predicate<Version> predicate);
}
}
29 changes: 26 additions & 3 deletions tests/Testcontainers.Keycloak.Tests/KeycloakContainerTest.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
namespace Testcontainers.Keycloak.Tests;
namespace Testcontainers.Keycloak;

public sealed class KeycloakContainerTest : IAsyncLifetime
public abstract class KeycloakContainerTest : IAsyncLifetime
{
private readonly KeycloakContainer _keycloakContainer = new KeycloakBuilder().Build();
private readonly KeycloakContainer _keycloakContainer;

private KeycloakContainerTest(KeycloakContainer keycloakContainer)
{
_keycloakContainer = keycloakContainer;
}

public Task InitializeAsync()
{
Expand Down Expand Up @@ -42,4 +47,22 @@ public async Task MasterRealmIsEnabled()
// Then
Assert.True(masterRealm.Enabled);
}

[UsedImplicitly]
public sealed class KeycloakDefaultConfiguration : KeycloakContainerTest
{
public KeycloakDefaultConfiguration()
: base(new KeycloakBuilder().Build())
{
}
}

[UsedImplicitly]
public sealed class KeycloakV25Configuration : KeycloakContainerTest
{
public KeycloakV25Configuration()
: base(new KeycloakBuilder().WithImage("quay.io/keycloak/keycloak:25.0").Build())
{
}
}
}
1 change: 1 addition & 0 deletions tests/Testcontainers.Keycloak.Tests/Usings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
global using System.Net;
global using System.Net.Http;
global using System.Threading.Tasks;
global using JetBrains.Annotations;
global using Keycloak.Net;
global using Xunit;
16 changes: 16 additions & 0 deletions tests/Testcontainers.Tests/Fixtures/Images/HealthCheckFixture.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace DotNet.Testcontainers.Tests.Fixtures
{
using System;
using System.IO;
using System.Threading.Tasks;
using DotNet.Testcontainers.Builders;
Expand Down Expand Up @@ -27,6 +28,21 @@ public string GetHostname()
return _image.GetHostname();
}

public bool MatchLatestOrNightly()
{
return _image.MatchLatestOrNightly();
}

public bool MatchVersion(Predicate<string> predicate)
{
return _image.MatchVersion(predicate);
}

public bool MatchVersion(Predicate<Version> predicate)
{
return _image.MatchVersion(predicate);
}

public Task InitializeAsync()
{
return _image.CreateAsync();
Expand Down
67 changes: 67 additions & 0 deletions tests/Testcontainers.Tests/Unit/Images/TestcontainersImageTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,72 @@ public void WhenImageNameGetsAssigned(DockerImageFixtureSerializable serializabl
Assert.Equal(expected.Tag, dockerImage.Tag);
Assert.Equal(expected.FullName, dockerImage.FullName);
}

[Fact]
public void MatchLatestOrNightly_TagIsLatest_ReturnsTrue()
{
// Given
IImage dockerImage = new DockerImage("foo:latest");

// When
var result = dockerImage.MatchLatestOrNightly();

// Then
Assert.True(result);
}

[Fact]
public void MatchLatestOrNightly_TagIsNightly_ReturnsTrue()
{
// Given
IImage dockerImage = new DockerImage("foo:nightly");

// When
var result = dockerImage.MatchLatestOrNightly();

// Then
Assert.True(result);
}

[Fact]
public void MatchLatestOrNightly_TagIsNeither_ReturnsFalse()
{
// Given
IImage dockerImage = new DockerImage("foo:1.0.0");

// When
var result = dockerImage.MatchLatestOrNightly();

// Then
Assert.False(result);
}

[Fact]
public void MatchVersion_ReturnsTrue_WhenVersionMatchesPredicate()
{
// Given
Predicate<Version> predicate = v => v.Major == 1 && v.Minor == 0 && v.Build == 0;
IImage dockerImage = new DockerImage("foo:1.0.0");

// When
var result = dockerImage.MatchVersion(predicate);

// Then
Assert.True(result);
}

[Fact]
public void MatchVersion_ReturnsFalse_WhenVersionDoesNotMatchPredicate()
{
// Given
Predicate<Version> predicate = v => v.Major == 0 && v.Minor == 0 && v.Build == 1;
IImage dockerImage = new DockerImage("foo:1.0.0");

// When
var result = dockerImage.MatchVersion(predicate);

// Then
Assert.False(result);
}
}
}

0 comments on commit 1a64762

Please sign in to comment.