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
4 changes: 0 additions & 4 deletions src/Testcontainers/Builders/BuildConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ public static T Combine<T>(T oldValue, T newValue)
/// <typeparam name="T">Type of <see cref="IEnumerable{T}" />.</typeparam>
/// <returns>An updated configuration.</returns>
public static IEnumerable<T> Combine<T>(IEnumerable<T> oldValue, IEnumerable<T> newValue)
where T : class
{
if (newValue == null && oldValue == null)
{
Expand All @@ -51,7 +50,6 @@ public static IEnumerable<T> Combine<T>(IEnumerable<T> oldValue, IEnumerable<T>
/// <typeparam name="T">Type of <see cref="IReadOnlyList{T}" />.</typeparam>
/// <returns>An updated configuration.</returns>
public static IReadOnlyList<T> Combine<T>(IReadOnlyList<T> oldValue, IReadOnlyList<T> newValue)
where T : class
{
if (newValue == null && oldValue == null)
{
Expand All @@ -75,8 +73,6 @@ public static IReadOnlyList<T> Combine<T>(IReadOnlyList<T> oldValue, IReadOnlyLi
/// <typeparam name="TValue">The type of values in the read-only dictionary.</typeparam>
/// <returns>An updated configuration.</returns>
public static IReadOnlyDictionary<TKey, TValue> Combine<TKey, TValue>(IReadOnlyDictionary<TKey, TValue> oldValue, IReadOnlyDictionary<TKey, TValue> newValue)
where TKey : class
where TValue : class
{
if (newValue == null && oldValue == null)
{
Expand Down
118 changes: 118 additions & 0 deletions src/Testcontainers/Containers/SocatBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
namespace DotNet.Testcontainers.Containers
{
using System;
using System.Collections.Generic;
using System.Linq;
using Docker.DotNet.Models;
using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Configurations;
using JetBrains.Annotations;

/// <inheritdoc cref="ContainerBuilder{TBuilderEntity, TContainerEntity, TConfigurationEntity}" />
[PublicAPI]
public sealed class SocatBuilder : ContainerBuilder<SocatBuilder, SocatContainer, SocatConfiguration>
{
public const string SocatImage = "alpine/socat:1.7.4.3-r0";

/// <summary>
/// Initializes a new instance of the <see cref="SocatBuilder" /> class.
/// </summary>
public SocatBuilder()
: this(new SocatConfiguration())
{
DockerResourceConfiguration = Init().DockerResourceConfiguration;
}

/// <summary>
/// Initializes a new instance of the <see cref="SocatBuilder" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
private SocatBuilder(SocatConfiguration resourceConfiguration)
: base(resourceConfiguration)
{
DockerResourceConfiguration = resourceConfiguration;
}

/// <inheritdoc />
protected override SocatConfiguration DockerResourceConfiguration { get; }

/// <summary>
/// Sets the Socat target.
/// </summary>
/// <param name="exposedPort">The Socat exposed port.</param>
/// <param name="host">The Socat target host.</param>
/// <returns>A configured instance of <see cref="SocatBuilder" />.</returns>
public SocatBuilder WithTarget(int exposedPort, string host)
{
return WithTarget(exposedPort, host, exposedPort);
}

/// <summary>
/// Sets the Socat target.
/// </summary>
/// <param name="exposedPort">The Socat exposed port.</param>
/// <param name="host">The Socat target host.</param>
/// <param name="internalPort">The Socat target port.</param>
/// <returns>A configured instance of <see cref="SocatBuilder" />.</returns>
public SocatBuilder WithTarget(int exposedPort, string host, int internalPort)
{
var targets = new Dictionary<int, string> { { exposedPort, $"{host}:{internalPort}" } };
return Merge(DockerResourceConfiguration, new SocatConfiguration(targets))
.WithPortBinding(exposedPort, true);
}

/// <inheritdoc />
public override SocatContainer Build()
{
Validate();

const string argument = "socat TCP-LISTEN:{0},fork,reuseaddr TCP:{1}";

var command = string.Join(" & ", DockerResourceConfiguration.Targets
.Select(item => string.Format(argument, item.Key, item.Value)));

var waitStrategy = DockerResourceConfiguration.Targets
.Aggregate(Wait.ForUnixContainer(), (waitStrategy, item) => waitStrategy.UntilPortIsAvailable(item.Key));

var socatBuilder = WithCommand(command).WithWaitStrategy(waitStrategy);
return new SocatContainer(socatBuilder.DockerResourceConfiguration);
}

/// <inheritdoc />
protected override SocatBuilder Init()
{
return base.Init()
.WithImage(SocatImage)
.WithEntrypoint("/bin/sh", "-c");
}

/// <inheritdoc />
protected override void Validate()
{
const string message = "Missing targets. One target must be specified to be created.";

base.Validate();

_ = Guard.Argument(DockerResourceConfiguration.Targets, nameof(DockerResourceConfiguration.Targets))
.ThrowIf(argument => argument.Value.Count == 0, argument => new ArgumentException(message, argument.Name));
}

/// <inheritdoc />
protected override SocatBuilder Clone(IResourceConfiguration<CreateContainerParameters> resourceConfiguration)
{
return Merge(DockerResourceConfiguration, new SocatConfiguration(resourceConfiguration));
}

/// <inheritdoc />
protected override SocatBuilder Clone(IContainerConfiguration resourceConfiguration)
{
return Merge(DockerResourceConfiguration, new SocatConfiguration(resourceConfiguration));
}

/// <inheritdoc />
protected override SocatBuilder Merge(SocatConfiguration oldValue, SocatConfiguration newValue)
{
return new SocatBuilder(new SocatConfiguration(oldValue, newValue));
}
}
}
69 changes: 69 additions & 0 deletions src/Testcontainers/Containers/SocatConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
namespace DotNet.Testcontainers.Containers
{
using System.Collections.Generic;
using Docker.DotNet.Models;
using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Configurations;
using JetBrains.Annotations;

/// <inheritdoc cref="ContainerConfiguration" />
[PublicAPI]
public sealed class SocatConfiguration : ContainerConfiguration
{
/// <summary>
/// Initializes a new instance of the <see cref="SocatConfiguration" /> class.
/// </summary>
/// <param name="targets">A list of target addresses.</param>
public SocatConfiguration(
IReadOnlyDictionary<int, string> targets = null)
{
Targets = targets;
}

/// <summary>
/// Initializes a new instance of the <see cref="SocatConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public SocatConfiguration(IResourceConfiguration<CreateContainerParameters> resourceConfiguration)
: base(resourceConfiguration)
{
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
}

/// <summary>
/// Initializes a new instance of the <see cref="SocatConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public SocatConfiguration(IContainerConfiguration resourceConfiguration)
: base(resourceConfiguration)
{
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
}

/// <summary>
/// Initializes a new instance of the <see cref="SocatConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public SocatConfiguration(SocatConfiguration resourceConfiguration)
: this(new SocatConfiguration(), resourceConfiguration)
{
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
}

/// <summary>
/// Initializes a new instance of the <see cref="SocatConfiguration" /> class.
/// </summary>
/// <param name="oldValue">The old Docker resource configuration.</param>
/// <param name="newValue">The new Docker resource configuration.</param>
public SocatConfiguration(SocatConfiguration oldValue, SocatConfiguration newValue)
: base(oldValue, newValue)
{
Targets = BuildConfiguration.Combine(oldValue.Targets, newValue.Targets);
}

/// <summary>
/// Gets a list of target addresses.
/// </summary>
public IReadOnlyDictionary<int, string> Targets { get; }
}
}
21 changes: 21 additions & 0 deletions src/Testcontainers/Containers/SocatContainer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace DotNet.Testcontainers.Containers
{
using JetBrains.Annotations;

/// <inheritdoc cref="DockerContainer" />
[PublicAPI]
public sealed class SocatContainer : DockerContainer
{
private readonly SocatConfiguration _configuration;

/// <summary>
/// Initializes a new instance of the <see cref="SocatContainer" /> class.
/// </summary>
/// <param name="configuration">The container configuration.</param>
public SocatContainer(SocatConfiguration configuration)
: base(configuration)
{
_configuration = configuration;
}
}
}
2 changes: 2 additions & 0 deletions tests/Testcontainers.Commons/CommonImages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ public static class CommonImages
{
public static readonly IImage Ryuk = new DockerImage("testcontainers/ryuk:0.9.0");

public static readonly IImage HelloWorld = new DockerImage("testcontainers/helloworld:1.2.0");

public static readonly IImage Alpine = new DockerImage("alpine:3.17");

public static readonly IImage Socat = new DockerImage("alpine/socat:1.8.0.0");
Expand Down
71 changes: 71 additions & 0 deletions tests/Testcontainers.Platform.Linux.Tests/SocatContainerTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
namespace Testcontainers.Tests;

public sealed class SocatContainerTest : IAsyncLifetime
{
private const string HelloWorldAlias = "hello-world-container";

private readonly INetwork _network;

private readonly IContainer _helloWorldContainer;

private readonly IContainer _socatContainer;

public SocatContainerTest()
{
_network = new NetworkBuilder()
.Build();

_helloWorldContainer = new ContainerBuilder()
.WithImage(CommonImages.HelloWorld)
.WithNetwork(_network)
.WithNetworkAliases(HelloWorldAlias)
.Build();

_socatContainer = new SocatBuilder()
.WithNetwork(_network)
.WithTarget(8080, HelloWorldAlias)
.WithTarget(8081, HelloWorldAlias, 8080)
.Build();
}

public async Task InitializeAsync()
{
await _helloWorldContainer.StartAsync()
.ConfigureAwait(false);

await _socatContainer.StartAsync()
.ConfigureAwait(false);
}

public async Task DisposeAsync()
{
await _socatContainer.DisposeAsync()
.ConfigureAwait(false);

await _helloWorldContainer.DisposeAsync()
.ConfigureAwait(false);

await _network.DisposeAsync()
.ConfigureAwait(false);
}

[Theory]
[InlineData(8080)]
[InlineData(8081)]
public async Task RequestTargetContainer(int containerPort)
{
// Given
using var httpClient = new HttpClient();
httpClient.BaseAddress = new UriBuilder(Uri.UriSchemeHttp, _socatContainer.Hostname, _socatContainer.GetMappedPublicPort(containerPort)).Uri;

// When
using var httpResponse = await httpClient.GetAsync("/ping")
.ConfigureAwait(true);

var response = await httpResponse.Content.ReadAsStringAsync()
.ConfigureAwait(true);

// Then
Assert.Equal("PONG", response);
}
}
2 changes: 2 additions & 0 deletions tests/Testcontainers.Platform.Linux.Tests/Usings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
global using System.IO;
global using System.Linq;
global using System.Net;
global using System.Net.Http;
global using System.Net.Sockets;
global using System.Text;
global using System.Threading;
Expand All @@ -16,6 +17,7 @@
global using DotNet.Testcontainers.Commons;
global using DotNet.Testcontainers.Configurations;
global using DotNet.Testcontainers.Containers;
global using DotNet.Testcontainers.Networks;
global using ICSharpCode.SharpZipLib.Tar;
global using JetBrains.Annotations;
global using Microsoft.Extensions.Logging.Abstractions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public abstract class WebDriverContainerTest : IAsyncLifetime
private WebDriverContainerTest(WebDriverContainer webDriverContainer)
{
_helloWorldContainer = new ContainerBuilder()
.WithImage("testcontainers/helloworld:1.1.0")
.WithImage(CommonImages.HelloWorld)
.WithNetwork(webDriverContainer.GetNetwork())
.WithNetworkAliases(_helloWorldBaseAddress.Host)
.WithPortBinding(_helloWorldBaseAddress.Port, true)
Expand Down