From b8140fbf0efa4b680bb0b7efd40d69d02bd1a2b7 Mon Sep 17 00:00:00 2001 From: Chris Philips Date: Mon, 28 Aug 2023 15:12:11 -0700 Subject: [PATCH 1/7] refactor: username/password are not required --- src/Testcontainers.MongoDb/MongoDbBuilder.cs | 2 -- src/Testcontainers.MongoDb/MongoDbContainer.cs | 9 +++++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Testcontainers.MongoDb/MongoDbBuilder.cs b/src/Testcontainers.MongoDb/MongoDbBuilder.cs index 3aeb1a442..ca9c2abf3 100644 --- a/src/Testcontainers.MongoDb/MongoDbBuilder.cs +++ b/src/Testcontainers.MongoDb/MongoDbBuilder.cs @@ -80,11 +80,9 @@ protected override void Validate() base.Validate(); _ = Guard.Argument(DockerResourceConfiguration.Username, nameof(DockerResourceConfiguration.Username)) - .NotNull() .NotEmpty(); _ = Guard.Argument(DockerResourceConfiguration.Password, nameof(DockerResourceConfiguration.Password)) - .NotNull() .NotEmpty(); } diff --git a/src/Testcontainers.MongoDb/MongoDbContainer.cs b/src/Testcontainers.MongoDb/MongoDbContainer.cs index 88ad50832..a71cbe891 100644 --- a/src/Testcontainers.MongoDb/MongoDbContainer.cs +++ b/src/Testcontainers.MongoDb/MongoDbContainer.cs @@ -25,8 +25,13 @@ public string GetConnectionString() { // The MongoDb documentation recommends to use percent-encoding for username and password: https://www.mongodb.com/docs/manual/reference/connection-string/. var endpoint = new UriBuilder("mongodb://", Hostname, GetMappedPublicPort(MongoDbBuilder.MongoDbPort)); - endpoint.UserName = Uri.EscapeDataString(_configuration.Username); - endpoint.Password = Uri.EscapeDataString(_configuration.Password); + + if (_configuration.Username != null && _configuration.Password != null) + { + endpoint.UserName = Uri.EscapeDataString(_configuration.Username); + endpoint.Password = Uri.EscapeDataString(_configuration.Password); + } + return endpoint.ToString(); } From ce2bf95bf23c9680276f0db6da2e8ac3828e739e Mon Sep 17 00:00:00 2001 From: Chris Philips Date: Mon, 28 Aug 2023 15:12:41 -0700 Subject: [PATCH 2/7] feat: ability to disable default credentials --- src/Testcontainers.MongoDb/MongoDbBuilder.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Testcontainers.MongoDb/MongoDbBuilder.cs b/src/Testcontainers.MongoDb/MongoDbBuilder.cs index ca9c2abf3..bc8353fe5 100644 --- a/src/Testcontainers.MongoDb/MongoDbBuilder.cs +++ b/src/Testcontainers.MongoDb/MongoDbBuilder.cs @@ -12,12 +12,15 @@ public sealed class MongoDbBuilder : ContainerBuilder /// Initializes a new instance of the class. /// - public MongoDbBuilder() + public MongoDbBuilder(bool useDefaultCredentials = true) : this(new MongoDbConfiguration()) { + _useDefaultCredentials = useDefaultCredentials; DockerResourceConfiguration = Init().DockerResourceConfiguration; } @@ -66,12 +69,19 @@ public override MongoDbContainer Build() /// protected override MongoDbBuilder Init() { - return base.Init() + var builder = base.Init() .WithImage(MongoDbImage) .WithPortBinding(MongoDbPort, true) - .WithUsername(DefaultUsername) - .WithPassword(DefaultPassword) .WithWaitStrategy(Wait.ForUnixContainer().AddCustomWaitStrategy(new WaitUntil())); + + if (_useDefaultCredentials) + { + builder = builder + .WithUsername(DefaultUsername) + .WithPassword(DefaultPassword); + } + + return builder; } /// From 27611d1967b9795cb457c0438d75ee3327163a39 Mon Sep 17 00:00:00 2001 From: Chris Philips Date: Mon, 28 Aug 2023 15:24:00 -0700 Subject: [PATCH 3/7] chore: update doccomment --- src/Testcontainers.MongoDb/MongoDbBuilder.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Testcontainers.MongoDb/MongoDbBuilder.cs b/src/Testcontainers.MongoDb/MongoDbBuilder.cs index bc8353fe5..54ffc9ccd 100644 --- a/src/Testcontainers.MongoDb/MongoDbBuilder.cs +++ b/src/Testcontainers.MongoDb/MongoDbBuilder.cs @@ -17,6 +17,7 @@ public sealed class MongoDbBuilder : ContainerBuilder /// Initializes a new instance of the class. /// + /// When set to false, mongodb will run without authentication unless you explicitly specify credentials public MongoDbBuilder(bool useDefaultCredentials = true) : this(new MongoDbConfiguration()) { From a0c12be3deec80d4d23addee8140a2de8f630b0d Mon Sep 17 00:00:00 2001 From: Chris Philips Date: Tue, 29 Aug 2023 20:20:06 -0700 Subject: [PATCH 4/7] Update src/Testcontainers.MongoDb/MongoDbBuilder.cs Co-authored-by: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> --- src/Testcontainers.MongoDb/MongoDbBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Testcontainers.MongoDb/MongoDbBuilder.cs b/src/Testcontainers.MongoDb/MongoDbBuilder.cs index 54ffc9ccd..504121c8a 100644 --- a/src/Testcontainers.MongoDb/MongoDbBuilder.cs +++ b/src/Testcontainers.MongoDb/MongoDbBuilder.cs @@ -17,7 +17,7 @@ public sealed class MongoDbBuilder : ContainerBuilder /// Initializes a new instance of the class. /// - /// When set to false, mongodb will run without authentication unless you explicitly specify credentials + /// When set to false, MongoDb will run without authentication unless you explicitly specify a username and password. public MongoDbBuilder(bool useDefaultCredentials = true) : this(new MongoDbConfiguration()) { From 7d6fe7c713b3da7963354ffcde11b3b701f6111b Mon Sep 17 00:00:00 2001 From: Chris Philips Date: Tue, 29 Aug 2023 20:26:39 -0700 Subject: [PATCH 5/7] refactor: keep the fluent api in a single chain --- src/Testcontainers.MongoDb/MongoDbBuilder.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Testcontainers.MongoDb/MongoDbBuilder.cs b/src/Testcontainers.MongoDb/MongoDbBuilder.cs index 504121c8a..54cf249d2 100644 --- a/src/Testcontainers.MongoDb/MongoDbBuilder.cs +++ b/src/Testcontainers.MongoDb/MongoDbBuilder.cs @@ -70,19 +70,21 @@ public override MongoDbContainer Build() /// protected override MongoDbBuilder Init() { - var builder = base.Init() - .WithImage(MongoDbImage) - .WithPortBinding(MongoDbPort, true) - .WithWaitStrategy(Wait.ForUnixContainer().AddCustomWaitStrategy(new WaitUntil())); + string username = null; + string password = null; if (_useDefaultCredentials) { - builder = builder - .WithUsername(DefaultUsername) - .WithPassword(DefaultPassword); + username = DefaultUsername; + password = DefaultPassword; } - return builder; + return base.Init() + .WithImage(MongoDbImage) + .WithPortBinding(MongoDbPort, true) + .WithUsername(username) + .WithPassword(password) + .WithWaitStrategy(Wait.ForUnixContainer().AddCustomWaitStrategy(new WaitUntil())); } /// From 63df7666e4c730e24a0a7203e0544438ab928030 Mon Sep 17 00:00:00 2001 From: Chris Philips Date: Tue, 29 Aug 2023 20:37:33 -0700 Subject: [PATCH 6/7] feat: shell command also supports no-auth mode --- .../MongoDbShellCommand.cs | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/Testcontainers.MongoDb/MongoDbShellCommand.cs b/src/Testcontainers.MongoDb/MongoDbShellCommand.cs index 7c288c983..8ee8db7f8 100644 --- a/src/Testcontainers.MongoDb/MongoDbShellCommand.cs +++ b/src/Testcontainers.MongoDb/MongoDbShellCommand.cs @@ -5,7 +5,8 @@ namespace Testcontainers.MongoDb; /// internal sealed class MongoDbShellCommand : List { - private const string Format = "{0} --username '{1}' --password '{2}' --quiet --eval '{3}'"; + private const string AuthFormat = "{0} --username '{1}' --password '{2}' --quiet --eval '{3}'"; + private const string NoAuthFormat = "{0} --quiet --eval '{1}'"; private const string Sanitize = "'\"'\"'"; @@ -24,11 +25,22 @@ internal sealed class MongoDbShellCommand : List public MongoDbShellCommand(string js, string username, string password) { var sanitizedJs = js.Replace("'", Sanitize); - var sanitizedUsername = username.Replace("'", Sanitize); - var sanitizedPassword = password.Replace("'", Sanitize); - _mongoDbShellCommand.AppendFormat(Format, "mongosh", sanitizedUsername, sanitizedPassword, sanitizedJs); - _mongoDbShellCommand.Append(" || "); - _mongoDbShellCommand.AppendFormat(Format, "mongo", sanitizedUsername, sanitizedPassword, sanitizedJs); + + if (username != null && password != null) + { + var sanitizedUsername = username.Replace("'", Sanitize); + var sanitizedPassword = password.Replace("'", Sanitize); + _mongoDbShellCommand.AppendFormat(AuthFormat, "mongosh", sanitizedUsername, sanitizedPassword, sanitizedJs); + _mongoDbShellCommand.Append(" || "); + _mongoDbShellCommand.AppendFormat(AuthFormat, "mongo", sanitizedUsername, sanitizedPassword, sanitizedJs); + } + else + { + _mongoDbShellCommand.AppendFormat(NoAuthFormat, "mongosh", sanitizedJs); + _mongoDbShellCommand.Append(" || "); + _mongoDbShellCommand.AppendFormat(NoAuthFormat, "mongo", sanitizedJs); + } + Add("/bin/sh"); Add("-c"); Add(_mongoDbShellCommand.ToString()); From f470e4bb5fac0ee30794cfc8514c327ecdd75256 Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Wed, 30 Aug 2023 20:14:30 +0200 Subject: [PATCH 7/7] refactor: Allow WithUsername and WithPassword null or string.Empty --- src/Testcontainers.MongoDb/MongoDbBuilder.cs | 61 +++++++++++-------- .../MongoDbContainer.cs | 9 +-- .../MongoDbShellCommand.cs | 24 ++------ .../MongoDbContainerTest.cs | 9 +++ 4 files changed, 53 insertions(+), 50 deletions(-) diff --git a/src/Testcontainers.MongoDb/MongoDbBuilder.cs b/src/Testcontainers.MongoDb/MongoDbBuilder.cs index 54cf249d2..ea0808826 100644 --- a/src/Testcontainers.MongoDb/MongoDbBuilder.cs +++ b/src/Testcontainers.MongoDb/MongoDbBuilder.cs @@ -12,16 +12,12 @@ public sealed class MongoDbBuilder : ContainerBuilder /// Initializes a new instance of the class. /// - /// When set to false, MongoDb will run without authentication unless you explicitly specify a username and password. - public MongoDbBuilder(bool useDefaultCredentials = true) + public MongoDbBuilder() : this(new MongoDbConfiguration()) { - _useDefaultCredentials = useDefaultCredentials; DockerResourceConfiguration = Init().DockerResourceConfiguration; } @@ -45,8 +41,10 @@ private MongoDbBuilder(MongoDbConfiguration resourceConfiguration) /// A configured instance of . public MongoDbBuilder WithUsername(string username) { - return Merge(DockerResourceConfiguration, new MongoDbConfiguration(username: username)) - .WithEnvironment("MONGO_INITDB_ROOT_USERNAME", username); + var initDbRootUsername = username ?? string.Empty; + + return Merge(DockerResourceConfiguration, new MongoDbConfiguration(username: initDbRootUsername)) + .WithEnvironment("MONGO_INITDB_ROOT_USERNAME", initDbRootUsername); } /// @@ -56,47 +54,49 @@ public MongoDbBuilder WithUsername(string username) /// A configured instance of . public MongoDbBuilder WithPassword(string password) { - return Merge(DockerResourceConfiguration, new MongoDbConfiguration(password: password)) - .WithEnvironment("MONGO_INITDB_ROOT_PASSWORD", password); + var initDbRootPassword = password ?? string.Empty; + + return Merge(DockerResourceConfiguration, new MongoDbConfiguration(password: initDbRootPassword)) + .WithEnvironment("MONGO_INITDB_ROOT_PASSWORD", initDbRootPassword); } /// public override MongoDbContainer Build() { Validate(); - return new MongoDbContainer(DockerResourceConfiguration, TestcontainersSettings.Logger); + + // The wait strategy relies on the configuration of MongoDb. If credentials are + // provided, the log message "Waiting for connections" appears twice. + // If the user does not provide a custom waiting strategy, append the default MongoDb waiting strategy. + var mongoDbBuilder = DockerResourceConfiguration.WaitStrategies.Count() > 1 ? this : WithWaitStrategy(Wait.ForUnixContainer().AddCustomWaitStrategy(new WaitUntil(DockerResourceConfiguration))); + return new MongoDbContainer(mongoDbBuilder.DockerResourceConfiguration, TestcontainersSettings.Logger); } /// protected override MongoDbBuilder Init() { - string username = null; - string password = null; - - if (_useDefaultCredentials) - { - username = DefaultUsername; - password = DefaultPassword; - } - return base.Init() .WithImage(MongoDbImage) .WithPortBinding(MongoDbPort, true) - .WithUsername(username) - .WithPassword(password) - .WithWaitStrategy(Wait.ForUnixContainer().AddCustomWaitStrategy(new WaitUntil())); + .WithUsername(DefaultUsername) + .WithPassword(DefaultPassword); } /// protected override void Validate() { + const string message = "Missing username or password. Both must be specified for a user to be created."; + base.Validate(); _ = Guard.Argument(DockerResourceConfiguration.Username, nameof(DockerResourceConfiguration.Username)) - .NotEmpty(); + .NotNull(); _ = Guard.Argument(DockerResourceConfiguration.Password, nameof(DockerResourceConfiguration.Password)) - .NotEmpty(); + .NotNull(); + + _ = Guard.Argument(DockerResourceConfiguration, "Credentials") + .ThrowIf(argument => 1.Equals(new[] { argument.Value.Username, argument.Value.Password }.Count(string.IsNullOrEmpty)), argument => new ArgumentException(message, argument.Name)); } /// @@ -122,13 +122,24 @@ private sealed class WaitUntil : IWaitUntil { private static readonly string[] LineEndings = { "\r\n", "\n" }; + private readonly int _count; + + /// + /// Initializes a new instance of the class. + /// + /// The container configuration. + public WaitUntil(MongoDbConfiguration configuration) + { + _count = string.IsNullOrEmpty(configuration.Username) && string.IsNullOrEmpty(configuration.Password) ? 1 : 2; + } + /// public async Task UntilAsync(IContainer container) { var (stdout, stderr) = await container.GetLogsAsync(timestampsEnabled: false) .ConfigureAwait(false); - return 2.Equals(Array.Empty() + return _count.Equals(Array.Empty() .Concat(stdout.Split(LineEndings, StringSplitOptions.RemoveEmptyEntries)) .Concat(stderr.Split(LineEndings, StringSplitOptions.RemoveEmptyEntries)) .Count(line => line.Contains("Waiting for connections"))); diff --git a/src/Testcontainers.MongoDb/MongoDbContainer.cs b/src/Testcontainers.MongoDb/MongoDbContainer.cs index a71cbe891..88ad50832 100644 --- a/src/Testcontainers.MongoDb/MongoDbContainer.cs +++ b/src/Testcontainers.MongoDb/MongoDbContainer.cs @@ -25,13 +25,8 @@ public string GetConnectionString() { // The MongoDb documentation recommends to use percent-encoding for username and password: https://www.mongodb.com/docs/manual/reference/connection-string/. var endpoint = new UriBuilder("mongodb://", Hostname, GetMappedPublicPort(MongoDbBuilder.MongoDbPort)); - - if (_configuration.Username != null && _configuration.Password != null) - { - endpoint.UserName = Uri.EscapeDataString(_configuration.Username); - endpoint.Password = Uri.EscapeDataString(_configuration.Password); - } - + endpoint.UserName = Uri.EscapeDataString(_configuration.Username); + endpoint.Password = Uri.EscapeDataString(_configuration.Password); return endpoint.ToString(); } diff --git a/src/Testcontainers.MongoDb/MongoDbShellCommand.cs b/src/Testcontainers.MongoDb/MongoDbShellCommand.cs index 8ee8db7f8..7c288c983 100644 --- a/src/Testcontainers.MongoDb/MongoDbShellCommand.cs +++ b/src/Testcontainers.MongoDb/MongoDbShellCommand.cs @@ -5,8 +5,7 @@ namespace Testcontainers.MongoDb; /// internal sealed class MongoDbShellCommand : List { - private const string AuthFormat = "{0} --username '{1}' --password '{2}' --quiet --eval '{3}'"; - private const string NoAuthFormat = "{0} --quiet --eval '{1}'"; + private const string Format = "{0} --username '{1}' --password '{2}' --quiet --eval '{3}'"; private const string Sanitize = "'\"'\"'"; @@ -25,22 +24,11 @@ internal sealed class MongoDbShellCommand : List public MongoDbShellCommand(string js, string username, string password) { var sanitizedJs = js.Replace("'", Sanitize); - - if (username != null && password != null) - { - var sanitizedUsername = username.Replace("'", Sanitize); - var sanitizedPassword = password.Replace("'", Sanitize); - _mongoDbShellCommand.AppendFormat(AuthFormat, "mongosh", sanitizedUsername, sanitizedPassword, sanitizedJs); - _mongoDbShellCommand.Append(" || "); - _mongoDbShellCommand.AppendFormat(AuthFormat, "mongo", sanitizedUsername, sanitizedPassword, sanitizedJs); - } - else - { - _mongoDbShellCommand.AppendFormat(NoAuthFormat, "mongosh", sanitizedJs); - _mongoDbShellCommand.Append(" || "); - _mongoDbShellCommand.AppendFormat(NoAuthFormat, "mongo", sanitizedJs); - } - + var sanitizedUsername = username.Replace("'", Sanitize); + var sanitizedPassword = password.Replace("'", Sanitize); + _mongoDbShellCommand.AppendFormat(Format, "mongosh", sanitizedUsername, sanitizedPassword, sanitizedJs); + _mongoDbShellCommand.Append(" || "); + _mongoDbShellCommand.AppendFormat(Format, "mongo", sanitizedUsername, sanitizedPassword, sanitizedJs); Add("/bin/sh"); Add("-c"); Add(_mongoDbShellCommand.ToString()); diff --git a/tests/Testcontainers.MongoDb.Tests/MongoDbContainerTest.cs b/tests/Testcontainers.MongoDb.Tests/MongoDbContainerTest.cs index 7948d7e24..38728c372 100644 --- a/tests/Testcontainers.MongoDb.Tests/MongoDbContainerTest.cs +++ b/tests/Testcontainers.MongoDb.Tests/MongoDbContainerTest.cs @@ -57,6 +57,15 @@ public MongoDbDefaultConfiguration() } } + [UsedImplicitly] + public sealed class MongoDbNoAuthConfiguration : MongoDbContainerTest + { + public MongoDbNoAuthConfiguration() + : base(new MongoDbBuilder().WithUsername(string.Empty).WithPassword(string.Empty).Build()) + { + } + } + [UsedImplicitly] public sealed class MongoDbV5Configuration : MongoDbContainerTest {