Skip to content

Commit

Permalink
feat(#601): Add WithImagePullPolicy
Browse files Browse the repository at this point in the history
  • Loading branch information
BenasB authored and HofmeisterAn committed Oct 10, 2022
1 parent 1de1d1a commit eef6d37
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 5 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Keep in mind to enable the correct Docker engine on Windows host systems to matc
To configure a container, use the `TestcontainersBuilder<TestcontainersContainer>` builder, that provides:

- `WithImage` specifies an `IMAGE[:TAG]` to derive the container from.
- `WithImagePullPolicy` specifies an image pull policy used to determine if the image should be pulled when starting the container e. g. `--pull "always"|"missing"|"never"`.
- `WithWorkingDirectory` specifies and overrides the `WORKDIR` for the instruction sets.
- `WithEntrypoint` specifies and overrides the `ENTRYPOINT` that will run as an executable.
- `WithCommand` specifies and overrides the `COMMAND` instruction provided from the Dockerfile.
Expand Down Expand Up @@ -186,4 +187,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
[3]: https://github.com/testcontainers/testcontainers-dotnet/blob/develop/tests/Testcontainers.Tests/Unit/Containers/Unix/Modules/MessageBrokers
[slack-workspace]: https://slack.testcontainers.org/
[moby-ryuk]: https://github.com/testcontainers/moby-ryuk
[xunit]: https://xunit.net
[xunit]: https://xunit.net
8 changes: 8 additions & 0 deletions src/Testcontainers/Builders/ITestcontainersBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ public interface ITestcontainersBuilder<out TDockerContainer> : IAbstractBuilder
[PublicAPI]
ITestcontainersBuilder<TDockerContainer> WithImage(IDockerImage image);

/// <summary>
/// Sets the image pull policy, which is used to determine if the image should be pulled before starting the container.
/// </summary>
/// <param name="imagePullPolicy">The image pull policy that determines if the image should be pulled.</param>
/// <returns>A configured instance of <see cref="ITestcontainersBuilder{TDockerContainer}" />.</returns>
[PublicAPI]
ITestcontainersBuilder<TDockerContainer> WithImagePullPolicy(Func<ImagesListResponse, bool> imagePullPolicy);

/// <summary>
/// Sets the name of the Testcontainer.
/// </summary>
Expand Down
12 changes: 10 additions & 2 deletions src/Testcontainers/Builders/TestcontainersBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ public TestcontainersBuilder()
waitStrategies: Wait.ForUnixContainer().Build(),
startupCallback: (_, ct) => Task.CompletedTask,
autoRemove: false,
privileged: false),
privileged: false,
imagePullPolicy: PullPolicy.Missing),
_ => { })
{
}
Expand Down Expand Up @@ -93,6 +94,12 @@ public ITestcontainersBuilder<TDockerContainer> WithImage(IDockerImage image)
return this.MergeNewConfiguration(new TestcontainersConfiguration(image: PrependHubImageNamePrefix(image)));
}

/// <inheritdoc cref="ITestcontainersBuilder{TDockerContainer}" />
public ITestcontainersBuilder<TDockerContainer> WithImagePullPolicy(Func<ImagesListResponse, bool> imagePullPolicy)
{
return this.MergeNewConfiguration(new TestcontainersConfiguration(imagePullPolicy: imagePullPolicy));
}

/// <inheritdoc cref="ITestcontainersBuilder{TDockerContainer}" />
public ITestcontainersBuilder<TDockerContainer> WithName(string name)
{
Expand Down Expand Up @@ -348,6 +355,7 @@ protected virtual ITestcontainersBuilder<TDockerContainer> MergeNewConfiguration

var image = BuildConfiguration.Combine(dockerResourceConfiguration.Image, this.DockerResourceConfiguration.Image);
var name = BuildConfiguration.Combine(dockerResourceConfiguration.Name, this.DockerResourceConfiguration.Name);
var imagePullPolicy = BuildConfiguration.Combine(dockerResourceConfiguration.ImagePullPolicy, this.DockerResourceConfiguration.ImagePullPolicy);
var hostname = BuildConfiguration.Combine(dockerResourceConfiguration.Hostname, this.DockerResourceConfiguration.Hostname);
var workingDirectory = BuildConfiguration.Combine(dockerResourceConfiguration.WorkingDirectory, this.DockerResourceConfiguration.WorkingDirectory);
var entrypoint = BuildConfiguration.Combine(dockerResourceConfiguration.Entrypoint, this.DockerResourceConfiguration.Entrypoint);
Expand All @@ -367,7 +375,7 @@ protected virtual ITestcontainersBuilder<TDockerContainer> MergeNewConfiguration
var parameterModifiers = BuildConfiguration.Combine(dockerResourceConfiguration.ParameterModifiers, this.DockerResourceConfiguration.ParameterModifiers);
var startupCallback = BuildConfiguration.Combine(dockerResourceConfiguration.StartupCallback, this.DockerResourceConfiguration.StartupCallback);

var updatedDockerResourceConfiguration = new TestcontainersConfiguration(dockerEndpointAuthConfig, dockerRegistryAuthConfig, image, name, hostname, workingDirectory, entrypoint, command, environments, labels, exposedPorts, portBindings, mounts, networks, networkAliases, outputConsumer, waitStrategies, parameterModifiers, startupCallback, autoRemove, privileged);
var updatedDockerResourceConfiguration = new TestcontainersConfiguration(dockerEndpointAuthConfig, dockerRegistryAuthConfig, image, imagePullPolicy, name, hostname, workingDirectory, entrypoint, command, environments, labels, exposedPorts, portBindings, mounts, networks, networkAliases, outputConsumer, waitStrategies, parameterModifiers, startupCallback, autoRemove, privileged);
return new TestcontainersBuilder<TDockerContainer>(updatedDockerResourceConfiguration, this.mergeModuleConfiguration);
}

Expand Down
6 changes: 4 additions & 2 deletions src/Testcontainers/Clients/TestcontainersClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,10 @@ public async Task<string> RunAsync(ITestcontainersConfiguration configuration, C
.ConfigureAwait(false);
}

if (!await this.images.ExistsWithNameAsync(configuration.Image.FullName, ct)
.ConfigureAwait(false))
var cachedImage = await this.images.ByNameAsync(configuration.Image.FullName, ct)
.ConfigureAwait(false);

if (configuration.ImagePullPolicy(cachedImage))
{
var authConfig = default(DockerRegistryAuthenticationConfiguration).Equals(configuration.DockerRegistryAuthConfig)
? this.registryAuthenticationProvider.GetAuthConfig(configuration.Image.GetHostname()) : configuration.DockerRegistryAuthConfig;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ public interface ITestcontainersConfiguration : IDockerResourceConfiguration
/// </summary>
IDockerImage Image { get; }

/// <summary>
/// Gets the policy determining if an image should be pulled when creating the container.
/// </summary>
/// <remarks>
/// Allows the user to decide based on the cached image (if it's possible to retrieve one).
/// </remarks>
Func<ImagesListResponse, bool> ImagePullPolicy { get; }

/// <summary>
/// Gets the name.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public TestcontainersConfiguration(IDockerResourceConfiguration dockerResourceCo
/// <param name="dockerEndpointAuthenticationConfiguration">The Docker endpoint authentication configuration.</param>
/// <param name="dockerRegistryAuthenticationConfiguration">The Docker registry authentication configuration.</param>
/// <param name="image">The Docker image.</param>
/// <param name="imagePullPolicy">The Docker image pull policy.</param>
/// <param name="name">The name.</param>
/// <param name="hostname">The hostname.</param>
/// <param name="workingDirectory">The working directory.</param>
Expand All @@ -51,6 +52,7 @@ public TestcontainersConfiguration(IDockerResourceConfiguration dockerResourceCo
IDockerEndpointAuthenticationConfiguration dockerEndpointAuthenticationConfiguration = null,
IDockerRegistryAuthenticationConfiguration dockerRegistryAuthenticationConfiguration = null,
IDockerImage image = null,
Func<ImagesListResponse, bool> imagePullPolicy = null,
string name = null,
string hostname = null,
string workingDirectory = null,
Expand All @@ -75,6 +77,7 @@ public TestcontainersConfiguration(IDockerResourceConfiguration dockerResourceCo
this.Privileged = privileged;
this.DockerRegistryAuthConfig = dockerRegistryAuthenticationConfiguration;
this.Image = image;
this.ImagePullPolicy = imagePullPolicy;
this.Name = name;
this.Hostname = hostname;
this.WorkingDirectory = workingDirectory;
Expand Down Expand Up @@ -106,6 +109,9 @@ public TestcontainersConfiguration(IDockerResourceConfiguration dockerResourceCo
/// <inheritdoc />
public IDockerImage Image { get; }

/// <inheritdoc />
public Func<ImagesListResponse, bool> ImagePullPolicy { get; }

/// <inheritdoc />
public string Name { get; }

Expand Down
44 changes: 44 additions & 0 deletions src/Testcontainers/Images/PullPolicy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
namespace DotNet.Testcontainers.Images
{
using System;
using Docker.DotNet.Models;

public static class PullPolicy
{
/// <summary>
/// Gets the policy of never pulling the image.
/// </summary>
public static Func<ImagesListResponse, bool> Never
{
get
{
return _ => false;
}
}

/// <summary>
/// Gets the policy of pulling the image if it's not cached.
/// </summary>
/// <remarks>
/// This is the default behavior.
/// </remarks>
public static Func<ImagesListResponse, bool> Missing
{
get
{
return cachedImage => cachedImage == null;
}
}

/// <summary>
/// Gets the policy of always pulling the image.
/// </summary>
public static Func<ImagesListResponse, bool> Always
{
get
{
return _ => true;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace DotNet.Testcontainers.Tests.Unit.Containers.Unix
using DotNet.Testcontainers.Clients;
using DotNet.Testcontainers.Configurations;
using DotNet.Testcontainers.Containers;
using DotNet.Testcontainers.Images;
using DotNet.Testcontainers.Tests.Fixtures;
using Xunit;

Expand Down Expand Up @@ -489,6 +490,26 @@ await using (ITestcontainersContainer testcontainer = testcontainersBuilder.Buil
Assert.EndsWith(name, testcontainer.Name);
}
}

[Fact]
public async Task PullPolicyNever()
{
// Given
// An image that actually exists but was not used/pulled previously
// If this image is cached/pulled before, this test will fail
const string uncachedImage = "alpine:edge";

// When
var testcontainersBuilder = new TestcontainersBuilder<TestcontainersContainer>()
.WithImage(uncachedImage)
.WithImagePullPolicy(PullPolicy.Never);

// Then
await using (ITestcontainersContainer testcontainer = testcontainersBuilder.Build())
{
await Assert.ThrowsAnyAsync<Exception>(() => testcontainer.StartAsync());
}
}
}
}
}

0 comments on commit eef6d37

Please sign in to comment.