Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bring more flexibility by allowing to use custom ContainerApi and ImageApi #81

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
65 changes: 33 additions & 32 deletions src/TestEnvironment.Docker/ContainerOperations/ContainerApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,37 +106,7 @@ public async Task StopContainerAsync(string id, CancellationToken cancellationTo
public async Task RemoveContainerAsync(string id, CancellationToken cancellationToken = default) =>
await _dockerClient.Containers.RemoveContainerAsync(id, new ContainerRemoveParameters { Force = true });

private async Task<ContainerListResponse> CreateContainer(ContainerParameters containerParameters, CancellationToken cancellationToken)
{
// Create new container
var createParams = GetCreateContainerParameters(containerParameters);

var containerInstance = await _dockerClient.Containers.CreateContainerAsync(createParams, cancellationToken);

// Run container
await _dockerClient.Containers.StartContainerAsync(containerInstance.ID, new ContainerStartParameters(), cancellationToken);

// Try to find container in docker session
#pragma warning disable CS8603 // Possible null reference return.
return await GetContainerAsync(containerParameters.Name, cancellationToken);
#pragma warning restore CS8603 // Possible null reference return.
}

private async Task<ContainerListResponse?> GetContainerAsync(string name, CancellationToken cancellationToken)
{
var containerName = $"/{name}";

var containers = await _dockerClient.Containers.ListContainersAsync(
new ContainersListParameters
{
All = true
},
cancellationToken);

return containers?.FirstOrDefault(x => x.Names.Contains(containerName));
}

private CreateContainerParameters GetCreateContainerParameters(ContainerParameters containerParameters)
protected virtual CreateContainerParameters GetCreateContainerParameters(ContainerParameters containerParameters)
{
var (name, imageName, tag, environmentVariables, ports, entrypoint, exposedPorts) =
(containerParameters.Name, containerParameters.ImageName, containerParameters.Tag, containerParameters.EnvironmentVariables, containerParameters.Ports, containerParameters.Entrypoint, containerParameters.ExposedPorts);
Expand All @@ -153,7 +123,8 @@ private CreateContainerParameters GetCreateContainerParameters(ContainerParamete
Hostname = name,
HostConfig = new HostConfig
{
PublishAllPorts = ports == null
PublishAllPorts = ports == null,
Binds = containerParameters.Binds
}
};

Expand All @@ -178,5 +149,35 @@ private CreateContainerParameters GetCreateContainerParameters(ContainerParamete

return createParams;
}

private async Task<ContainerListResponse> CreateContainer(ContainerParameters containerParameters, CancellationToken cancellationToken)
{
// Create new container
var createParams = GetCreateContainerParameters(containerParameters);

var containerInstance = await _dockerClient.Containers.CreateContainerAsync(createParams, cancellationToken);

// Run container
await _dockerClient.Containers.StartContainerAsync(containerInstance.ID, new ContainerStartParameters(), cancellationToken);

// Try to find container in docker session
#pragma warning disable CS8603 // Possible null reference return.
return await GetContainerAsync(containerParameters.Name, cancellationToken);
#pragma warning restore CS8603 // Possible null reference return.
}

private async Task<ContainerListResponse?> GetContainerAsync(string name, CancellationToken cancellationToken)
{
var containerName = $"/{name}";

var containers = await _dockerClient.Containers.ListContainersAsync(
new ContainersListParameters
{
All = true
},
cancellationToken);

return containers?.FirstOrDefault(x => x.Names.Contains(containerName));
}
}
}
2 changes: 2 additions & 0 deletions src/TestEnvironment.Docker/ContainerParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public record ContainerParameters(string Name, string ImageName)

public IList<ushort>? ExposedPorts { get; init; }

public IList<string>? Binds { get; init; }

public IContainerInitializer? ContainerInitializer { get; init; }

public IContainerWaiter? ContainerWaiter { get; init; }
Expand Down
6 changes: 4 additions & 2 deletions src/TestEnvironment.Docker/DockerEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,10 @@ public DockerEnvironment(string name, Container[] containers, IDockerClient dock
{
}

public DockerEnvironment(string name, Container[] containers, IImageApi imageApi, IContainerApi containerApi, ILogger? logger) =>
(Name, Containers, _imageApi, _containerApi, _logger) = (name, containers, imageApi, containerApi, logger);
public DockerEnvironment(string name, Container[] containers, IImageApi imageApi, IContainerApi containerApi, ILogger? logger)
: this(name, containers, imageApi, containerApi, null, logger)
{
}

public DockerEnvironment(string name, Container[] containers, IImageApi imageApi, IContainerApi containerApi, IDockerInitializer? dockerInitializer, ILogger? logger) =>
(Name, Containers, _imageApi, _containerApi, _dockerInitializer, _logger) = (name, containers, imageApi, containerApi, dockerInitializer, logger);
Expand Down
49 changes: 43 additions & 6 deletions src/TestEnvironment.Docker/DockerEnvironmentBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ namespace TestEnvironment.Docker
{
public class DockerEnvironmentBuilder : IDockerEnvironmentBuilder
{
private readonly Dictionary<ContainerParameters, Func<Container>> _containerFactories = new();
private readonly Dictionary<ContainerParameters, Func<IContainerApi, IImageApi, Container>> _containerFactories = new();
private IDictionary<string, string> _environmentVariables = new Dictionary<string, string>();
private bool _isWsl2 = false;
private bool _isDockerInDocker = false;
private string _environmentName = Guid.NewGuid().ToString().Substring(0, 10);
private Func<IDockerClient, ILogger?, IContainerApi>? _containerApiFactory;
private Func<IDockerClient, ILogger?, IImageApi>? _imageApiFactory;

public IDockerClient DockerClient { get; private set; }

Expand Down Expand Up @@ -88,7 +90,7 @@ public IDockerEnvironmentBuilder AddContainer(Func<ContainerParameters, IDockerC
public IDockerEnvironmentBuilder AddContainer<TParams>(TParams containerParameters, Func<TParams, IDockerClient, ILogger?, Container> containerFactory)
where TParams : ContainerParameters
{
_containerFactories.Add(containerParameters, () =>
_containerFactories.Add(containerParameters, (containerApi, imageApi) =>
{
var envParameters = containerParameters with
{
Expand All @@ -102,13 +104,48 @@ public IDockerEnvironmentBuilder AddContainer<TParams>(TParams containerParamete
return this;
}

public IDockerEnvironmentBuilder AddContainer<TParams>(TParams containerParameters, Func<TParams, IContainerApi, IImageApi, ILogger?, Container> containerFactory)
where TParams : ContainerParameters
{
_containerFactories.Add(containerParameters, (containerApi, imageApi) =>
{
var envParameters = containerParameters with
{
Name = GetContainerName(_environmentName, containerParameters.Name),
EnvironmentVariables = _environmentVariables.MergeDictionaries(containerParameters.EnvironmentVariables),
IsDockerInDocker = _isDockerInDocker,
};

return containerFactory(envParameters, containerApi, imageApi, Logger);
});
return this;
}

public IDockerEnvironmentBuilder WithContainerApi(Func<IDockerClient, ILogger?, IContainerApi> containerApiFactory)
{
_containerApiFactory = containerApiFactory;

return this;
}

public IDockerEnvironmentBuilder WithImageApi(Func<IDockerClient, ILogger?, IImageApi> imageApiFactory)
{
_imageApiFactory = imageApiFactory;

return this;
}

public IDockerEnvironment Build()
{
var containers = _containerFactories.Values.Select(cf => cf()).ToArray();
var containerApi = _containerApiFactory?.Invoke(DockerClient, Logger) ?? new ContainerApi(DockerClient, Logger);

var imageApi = _imageApiFactory?.Invoke(DockerClient, Logger) ?? new ImageApi(DockerClient, Logger);

var dockerInitializer = _isWsl2 ? new DockerInWs2Initializer(DockerClient, Logger) : null;

var containers = _containerFactories.Values.Select(cf => cf(containerApi, imageApi)).ToArray();

return _isWsl2
? new DockerEnvironment(_environmentName, containers, DockerClient, new DockerInWs2Initializer(DockerClient, Logger), Logger)
: new DockerEnvironment(_environmentName, containers, DockerClient, Logger);
return new DockerEnvironment(_environmentName, containers, imageApi, containerApi, dockerInitializer, Logger);
}
}
}
9 changes: 9 additions & 0 deletions src/TestEnvironment.Docker/IDockerEnvironmentBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using System.Collections.Generic;
using Docker.DotNet;
using Microsoft.Extensions.Logging;
using TestEnvironment.Docker.ContainerOperations;
using TestEnvironment.Docker.ImageOperations;

namespace TestEnvironment.Docker
{
Expand All @@ -24,6 +26,13 @@ public interface IDockerEnvironmentBuilder
IDockerEnvironmentBuilder AddContainer<TParams>(TParams containerParameters, Func<TParams, IDockerClient, ILogger?, Container> containerFactory)
where TParams : ContainerParameters;

IDockerEnvironmentBuilder AddContainer<TParams>(TParams containerParameters, Func<TParams, IContainerApi, IImageApi, ILogger?, Container> containerFactory)
where TParams : ContainerParameters;

IDockerEnvironmentBuilder WithContainerApi(Func<IDockerClient, ILogger?, IContainerApi> containerApiFactory);

IDockerEnvironmentBuilder WithImageApi(Func<IDockerClient, ILogger?, IImageApi> imageApiFactory);

IDockerEnvironment Build();
}
}
34 changes: 34 additions & 0 deletions test/TestEnvironment.Docker.Tests/CustomContainerApi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Docker.DotNet;
using Docker.DotNet.Models;
using Microsoft.Extensions.Logging;
using TestEnvironment.Docker.ContainerOperations;

namespace TestEnvironment.Docker.Tests
{
internal class CustomContainerApi : ContainerApi
{
private readonly string _key;
private readonly string _val;

public CustomContainerApi(string key, string val, IDockerClient dockerClient, ILogger logger)
: base(dockerClient, logger)
{
_key = key;
_val = val;
}

protected override CreateContainerParameters GetCreateContainerParameters(ContainerParameters containerParameters)
{
var createParams = base.GetCreateContainerParameters(containerParameters);

createParams.Env.Add($"{_key}={_val}");

return createParams;
}
}
}
41 changes: 41 additions & 0 deletions test/TestEnvironment.Docker.Tests/DockerEnvironmentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,47 @@ public async Task AddMsSqlContainer_WhenContainerIsUp_ShouldPrintMsSqlVersion()
await PrintMssqlVersion(mssql);
}

[Fact]
public async Task AddMongoContainerWithCustomApi_WhenContainerIsUp_ShouldHaveCustomVar()
{
// Arrange
const string key = "hello";
const string val = "world";
const string containerName = "my-mongo-cust";

#if DEBUG
var environment = new DockerEnvironmentBuilder(_logger)
#else
await using var environment = new DockerEnvironmentBuilder(_logger)
#endif
.SetName("test-env")
.WithContainerApi((api, l) => new CustomContainerApi(key, val, api, l))
#if WSL2
.UseWsl2()
#endif
#if DEBUG
.AddMongoContainer(p => p with
{
Name = containerName,
Reusable = true
})
#else
.AddMongoContainer(p => p with
{
Name = "my-mongo"
})
#endif
.Build();

// Act
await environment.UpAsync();

// Assert
var mongo = environment.GetContainer<MongoContainer>(containerName);
var varValue = await mongo.ExecAsync(new[] { $"printenv {key}" });
Assert.EndsWith(val, varValue);
}

[Fact]
public async Task AddMariaDbContainer_WhenContainerIsUp_ShouldPrintMariaDbVersion()
{
Expand Down