testcontainers-dotnet icon indicating copy to clipboard operation
testcontainers-dotnet copied to clipboard

[Enhancement]: Allow setting up ElasticsearchContainer with HTTP

Open Jsehahn opened this issue 7 months ago • 2 comments

Problem

The Elasticsearch container is setup with HTTPS by default, which forces you to do clunky stuff like this:

/// <summary>
/// Get the Elasticsearch SSL-certificate file from the container, so it can be passed along
/// to dependent container(s) required to connect to Elasticsearch
/// </summary>
private static async Task<byte[]> ExportCertificateAsync(ElasticsearchContainer container)
{
    return await container.ReadFileAsync("/usr/share/elasticsearch/config/certs/http_ca.crt")
        .ConfigureAwait(false);
}

We can work around this behaviour by disabling xpack security, like so:

  private ElasticsearchContainer BuildElasticSearchContainer()
  {
      return new ElasticsearchBuilder()
          .WithImage(ElasticsearchImageVersion)
          .WithNetwork(_network)
          .WithNetworkAliases(ElasticsearchContainerName)
          .WithEnvironment("xpack.security.enabled", "false")
          .WithEnvironment("xpack.security.http.ssl.enabled", "false")
          .Build();
    }

But because ElasticsearchContainer.GetConnectionString() always created an HTTPS endpoint, and this is a sealed class, we're left with needing work-arounds.

  public string GetConnectionString()
  {
      var endpoint = new UriBuilder(Uri.UriSchemeHttps, Hostname, GetMappedPublicPort(ElasticsearchBuilder.ElasticsearchHttpsPort));
      endpoint.UserName = _configuration.Username;
      endpoint.Password = _configuration.Password;
      return endpoint.ToString();
  }

This is one of the workarounds:

public static class ElasticsearchContainerExtensions
{
    public static string GetHttpConnectionString(this ElasticsearchContainer container) =>
        container.GetConnectionString().Replace("https://", "http://");
}

...

var clientSettings = new ElasticsearchClientSettings(new Uri(_elasticsearchContainer.GetHttpConnectionString()));

Solution

My preferred solution would be to Implement an extension method on the ElasticsearchBuilder, called WithHttp or something. This disables the relevant xpacks and sets the correct connection string.

Benefit

Easily set up an HTTP connection without tricky workarounds that I don't fully trust. A secure HTTPS connection is hardly required for a test container, and this saves you from having to pass certificates around (which is also hacky).

Alternatives

As mentioned, there is a workaround.

Would you like to help contributing this enhancement?

Yes

Jsehahn avatar Jun 11 '25 13:06 Jsehahn

Thanks for creating the issue. But can't you just configure the Elasticsearch client the same way we do in our tests? Or are you using a different client?

https://github.com/testcontainers/testcontainers-dotnet/blob/b1244cc099763388c8f26938eca72247a41f0bac/tests/Testcontainers.Elasticsearch.Tests/ElasticsearchContainerTest.cs#L24-L25

HofmeisterAn avatar Jun 11 '25 17:06 HofmeisterAn

I've tried that, but can't get it to work. The other containers in the cluster aren't able to communicate with the Elastic Client.

Jsehahn avatar Jun 12 '25 11:06 Jsehahn

So my idea is to check whether SSL is enabled in the GetConnectionString() method, e.g. by checking the environment variables, and then return the appropriate connection string.

Just keep in mind that for container to container communication, you cannot use this method, you need to manually build the connection string using the network alias.

WDYT?

HofmeisterAn avatar Jun 18 '25 15:06 HofmeisterAn

Sorry for the late reply. When you say environment variables, do you mean these two?

 .WithEnvironment("xpack.security.enabled", "false")
 .WithEnvironment("xpack.security.http.ssl.enabled", "false")

And if so, why not add an extension method like WithHtpp() that sets those variables for you?

jessehahn avatar Jul 23 '25 11:07 jessehahn

And if so, why not add an extension method like WithHtpp() that sets those variables for you?

We don't offer APIs for every possible configuration because there are simply too many, and it's too difficult to support them across different versions. Our modules are opinionated and follow best practices to support most common use cases. They won't cover everything, but developers can override the configuration or fall back to the generic container builder if needed.

For this issue, I'll update the string GetConnectionString() method to support configurations where security is disabled. That said, I still think it's better to extract the certificate as suggested in the documentation (something we can also build in OOB).

HofmeisterAn avatar Jul 26 '25 09:07 HofmeisterAn

We don't offer APIs for every possible configuration because there are simply too many, and it's too difficult to support them across different versions.

Alright, I understand. Thanks for implementing the other solution!

jessehahn avatar Jul 30 '25 07:07 jessehahn