From fcbd4df81c370185ccdc10f948b43949f6ffcf2a Mon Sep 17 00:00:00 2001
From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com>
Date: Sun, 4 Sep 2022 16:44:26 +0200
Subject: [PATCH] feat(#569): Support relative base directories other than the
working directory with WithDockerfileDirectory
---
CHANGELOG.md | 3 +-
.../Builders/CommonDirectoryPath.cs | 124 ++++++++++++++++++
.../Builders/IImageFromDockerfileBuilder.cs | 9 ++
.../Builders/ImageFromDockerfileBuilder.cs | 8 ++
.../Unit/Builders/CommonDirectoryPathTest.cs | 35 +++++
5 files changed, 178 insertions(+), 1 deletion(-)
create mode 100644 src/Testcontainers/Builders/CommonDirectoryPath.cs
create mode 100644 tests/Testcontainers.Tests/Unit/Builders/CommonDirectoryPathTest.cs
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 15229dc96..ddb6e512d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,7 +11,8 @@
- 528 Do not require the Docker host configuration (`DockerEndpointAuthConfig`) on `TestcontainersSettings` initialization
- 538 Support optional username and password in MongoDB connection string (@the-avid-engineer)
- 540 Add Docker registry authentication provider for `DOCKER_AUTH_CONFIG` environment variable (@vova-lantsov-dev)
-- 541 Allow MsSqlTestcontainerConfiguration custom database names (@enginexon)
+- 541 Allow `MsSqlTestcontainerConfiguration` custom database names (@enginexon)
+- 558 Support relative base directories other than the working directory with `WithDockerfileDirectory`.
### Changed
diff --git a/src/Testcontainers/Builders/CommonDirectoryPath.cs b/src/Testcontainers/Builders/CommonDirectoryPath.cs
new file mode 100644
index 000000000..2bbdfca2c
--- /dev/null
+++ b/src/Testcontainers/Builders/CommonDirectoryPath.cs
@@ -0,0 +1,124 @@
+namespace DotNet.Testcontainers.Builders
+{
+ using System;
+ using System.IO;
+ using System.Linq;
+ using System.Runtime.CompilerServices;
+ using JetBrains.Annotations;
+
+ ///
+ /// Resolves common directory paths.
+ ///
+ [PublicAPI]
+ public readonly struct CommonDirectoryPath
+ {
+ private static readonly string WorkingDirectoryPath = Directory.GetCurrentDirectory();
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The directory path.
+ [PublicAPI]
+ public CommonDirectoryPath(string directoryPath)
+ {
+ this.DirectoryPath = directoryPath;
+ }
+
+ ///
+ /// Gets the directory path.
+ ///
+ [PublicAPI]
+ public string DirectoryPath { get; }
+
+ ///
+ /// Resolves the first bin directory upwards the directory tree.
+ ///
+ /// The first bin directory upwards the directory tree.
+ /// Thrown when the bin directory was not found upwards the directory tree.
+ [PublicAPI]
+ public static CommonDirectoryPath GetBinDirectory()
+ {
+ var indexOfBinDirectory = WorkingDirectoryPath.LastIndexOf("bin", StringComparison.OrdinalIgnoreCase);
+
+ if (indexOfBinDirectory > -1)
+ {
+ return new CommonDirectoryPath(WorkingDirectoryPath.Substring(0, indexOfBinDirectory));
+ }
+
+ const string message = "Cannot find 'bin' and resolve the base directory in the directory tree.";
+ throw new DirectoryNotFoundException(message);
+ }
+
+ ///
+ /// Resolves the first Git directory upwards the directory tree.
+ ///
+ ///
+ /// Start node is the caller file path directory. End node is the root directory.
+ ///
+ /// The caller file path.
+ /// The first Git directory upwards the directory tree.
+ /// Thrown when the Git directory was not found upwards the directory tree.
+ [PublicAPI]
+ public static CommonDirectoryPath GetGitDirectory([CallerFilePath, NotNull] string filePath = "")
+ {
+ return new CommonDirectoryPath(GetDirectoryPath(Path.GetDirectoryName(filePath), ".git"));
+ }
+
+ ///
+ /// Resolves the first Visual Studio solution file upwards the directory tree.
+ ///
+ ///
+ /// Start node is the caller file path directory. End node is the root directory.
+ ///
+ /// The caller file path.
+ /// The first Visual Studio solution file upwards the directory tree.
+ /// Thrown when the Visual Studio solution file was not found upwards the directory tree.
+ [PublicAPI]
+ public static CommonDirectoryPath GetSolutionDirectory([CallerFilePath, NotNull] string filePath = "")
+ {
+ return new CommonDirectoryPath(GetDirectoryPath(Path.GetDirectoryName(filePath), "*.sln"));
+ }
+
+ ///
+ /// Resolves the first CSharp project file upwards the directory tree.
+ ///
+ ///
+ /// Start node is the caller file path directory. End node is the root directory.
+ ///
+ /// The caller file path.
+ /// The first CSharp project file upwards the directory tree.
+ /// Thrown when the CSharp project file was not found upwards the directory tree.
+ [PublicAPI]
+ public static CommonDirectoryPath GetProjectDirectory([CallerFilePath, NotNull] string filePath = "")
+ {
+ return new CommonDirectoryPath(GetDirectoryPath(Path.GetDirectoryName(filePath), "*.csproj"));
+ }
+
+ ///
+ /// Resolves the caller file path directory.
+ ///
+ /// The caller file path.
+ /// The caller file path directory.
+ [PublicAPI]
+ public static CommonDirectoryPath GetCallerFileDirectory([CallerFilePath, NotNull] string filePath = "")
+ {
+ return new CommonDirectoryPath(Path.GetDirectoryName(filePath));
+ }
+
+ private static string GetDirectoryPath(string path, string searchPattern)
+ {
+ return GetDirectoryPath(Directory.Exists(path) ? new DirectoryInfo(path) : null, searchPattern);
+ }
+
+ private static string GetDirectoryPath(DirectoryInfo path, string searchPattern)
+ {
+ if (path != null)
+ {
+ return path.EnumerateFileSystemInfos(searchPattern, SearchOption.TopDirectoryOnly).Any() ? path.FullName : GetDirectoryPath(path.Parent, searchPattern);
+ }
+
+ var message = $"Cannot find '{searchPattern}' and resolve the base directory in the directory tree.";
+ throw new DirectoryNotFoundException(message);
+ }
+ }
+}
diff --git a/src/Testcontainers/Builders/IImageFromDockerfileBuilder.cs b/src/Testcontainers/Builders/IImageFromDockerfileBuilder.cs
index 98a0a6c61..2131cd198 100644
--- a/src/Testcontainers/Builders/IImageFromDockerfileBuilder.cs
+++ b/src/Testcontainers/Builders/IImageFromDockerfileBuilder.cs
@@ -42,6 +42,15 @@ public interface IImageFromDockerfileBuilder : IAbstractBuilder
+ /// Sets the base directory of the Dockerfile.
+ ///
+ /// Common directory path that contains the Dockerfile base directory.
+ /// Dockerfile base directory.
+ /// A configured instance of .
+ [PublicAPI]
+ IImageFromDockerfileBuilder WithDockerfileDirectory(CommonDirectoryPath commonDirectoryPath, string dockerfileDirectory);
+
///
/// If true, Testcontainer will remove the existing Docker image. Otherwise, Testcontainer will keep the Docker image.
///
diff --git a/src/Testcontainers/Builders/ImageFromDockerfileBuilder.cs b/src/Testcontainers/Builders/ImageFromDockerfileBuilder.cs
index e0183d6b9..972d4804c 100644
--- a/src/Testcontainers/Builders/ImageFromDockerfileBuilder.cs
+++ b/src/Testcontainers/Builders/ImageFromDockerfileBuilder.cs
@@ -2,6 +2,7 @@ namespace DotNet.Testcontainers.Builders
{
using System;
using System.Collections.Generic;
+ using System.IO;
using System.Threading.Tasks;
using DotNet.Testcontainers.Clients;
using DotNet.Testcontainers.Configurations;
@@ -59,6 +60,13 @@ public IImageFromDockerfileBuilder WithDockerfileDirectory(string dockerfileDire
return this.MergeNewConfiguration(new ImageFromDockerfileConfiguration(dockerfileDirectory: dockerfileDirectory));
}
+ ///
+ public IImageFromDockerfileBuilder WithDockerfileDirectory(CommonDirectoryPath commonDirectoryPath, string dockerfileDirectory)
+ {
+ var baseDirectoryPath = Path.Combine(commonDirectoryPath.DirectoryPath, dockerfileDirectory);
+ return this.WithDockerfileDirectory(baseDirectoryPath);
+ }
+
///
public IImageFromDockerfileBuilder WithDeleteIfExists(bool deleteIfExists)
{
diff --git a/tests/Testcontainers.Tests/Unit/Builders/CommonDirectoryPathTest.cs b/tests/Testcontainers.Tests/Unit/Builders/CommonDirectoryPathTest.cs
new file mode 100644
index 000000000..ee075f0a1
--- /dev/null
+++ b/tests/Testcontainers.Tests/Unit/Builders/CommonDirectoryPathTest.cs
@@ -0,0 +1,35 @@
+namespace DotNet.Testcontainers.Tests.Unit
+{
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+ using DotNet.Testcontainers.Builders;
+ using Xunit;
+
+ public sealed class CommonDirectoryPathTest
+ {
+ public static IEnumerable