Skip to content

Commit

Permalink
fix: Get sqlcmd utility file path from container instead of const fil…
Browse files Browse the repository at this point in the history
…e path (#1221)

Co-authored-by: Geoffrey MARC <[email protected]>
Co-authored-by: Andre Hofmeister <[email protected]>
  • Loading branch information
3 people authored Aug 29, 2024
1 parent fffd384 commit b2699cc
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 8 deletions.
17 changes: 12 additions & 5 deletions src/Testcontainers.MsSql/MsSqlBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,17 +126,24 @@ private MsSqlBuilder WithUsername(string username)

/// <inheritdoc cref="IWaitUntil" />
/// <remarks>
/// Uses the sqlcmd utility scripting variables to detect readiness of the MsSql container:
/// Uses the <c>sqlcmd</c> utility scripting variables to detect readiness of the MsSql container:
/// https://learn.microsoft.com/en-us/sql/tools/sqlcmd/sqlcmd-utility?view=sql-server-linux-ver15#sqlcmd-scripting-variables.
/// </remarks>
private sealed class WaitUntil : IWaitUntil
{
private readonly string[] _command = { "/opt/mssql-tools/bin/sqlcmd", "-Q", "SELECT 1;" };

/// <inheritdoc />
public async Task<bool> UntilAsync(IContainer container)
public Task<bool> UntilAsync(IContainer container)
{
return UntilAsync(container as MsSqlContainer);
}

/// <inheritdoc cref="IWaitUntil.UntilAsync" />
private async Task<bool> UntilAsync(MsSqlContainer container)
{
var execResult = await container.ExecAsync(_command)
var sqlCmdFilePath = await container.GetSqlCmdFilePathAsync()
.ConfigureAwait(false);

var execResult = await container.ExecAsync(new[] { sqlCmdFilePath, "-C", "-Q", "SELECT 1;" })
.ConfigureAwait(false);

return 0L.Equals(execResult.ExitCode);
Expand Down
36 changes: 35 additions & 1 deletion src/Testcontainers.MsSql/MsSqlContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ namespace Testcontainers.MsSql;
[PublicAPI]
public sealed class MsSqlContainer : DockerContainer, IDatabaseContainer
{
private static readonly string[] FindSqlCmdFilePath = { "/bin/sh", "-c", "find /opt/mssql-tools*/bin/sqlcmd -type f -print -quit" };

private readonly Lazy<Task<string>> _lazySqlCmdFilePath;

private readonly MsSqlConfiguration _configuration;

/// <summary>
Expand All @@ -13,6 +17,7 @@ public sealed class MsSqlContainer : DockerContainer, IDatabaseContainer
public MsSqlContainer(MsSqlConfiguration configuration)
: base(configuration)
{
_lazySqlCmdFilePath = new Lazy<Task<string>>(FindSqlCmdFilePathAsync);
_configuration = configuration;
}

Expand All @@ -31,6 +36,19 @@ public string GetConnectionString()
return string.Join(";", properties.Select(property => string.Join("=", property.Key, property.Value)));
}

/// <summary>
/// Gets the <c>sqlcmd</c> utility file path.
/// </summary>
/// <remarks>
/// The file path represents the path from the container, not from the Docker or test host.
/// </remarks>
/// <param name="ct">Cancellation token.</param>
/// <returns>Task that completes when the <c>sqlcmd</c> utility file path has been found.</returns>
public Task<string> GetSqlCmdFilePathAsync(CancellationToken ct = default)
{
return _lazySqlCmdFilePath.Value;
}

/// <summary>
/// Executes the SQL script in the MsSql container.
/// </summary>
Expand All @@ -41,10 +59,26 @@ public async Task<ExecResult> ExecScriptAsync(string scriptContent, Cancellation
{
var scriptFilePath = string.Join("/", string.Empty, "tmp", Guid.NewGuid().ToString("D"), Path.GetRandomFileName());

var sqlCmdFilePath = await GetSqlCmdFilePathAsync(ct)
.ConfigureAwait(false);

await CopyAsync(Encoding.Default.GetBytes(scriptContent), scriptFilePath, Unix.FileMode644, ct)
.ConfigureAwait(false);

return await ExecAsync(new[] { "/opt/mssql-tools/bin/sqlcmd", "-b", "-r", "1", "-U", _configuration.Username, "-P", _configuration.Password, "-i", scriptFilePath }, ct)
return await ExecAsync(new[] { sqlCmdFilePath, "-C", "-b", "-r", "1", "-U", _configuration.Username, "-P", _configuration.Password, "-i", scriptFilePath }, ct)
.ConfigureAwait(false);
}

private async Task<string> FindSqlCmdFilePathAsync()
{
var findSqlCmdFilePathExecResult = await ExecAsync(FindSqlCmdFilePath)
.ConfigureAwait(false);

if (findSqlCmdFilePathExecResult.ExitCode == 0)
{
return findSqlCmdFilePathExecResult.Stdout.Trim();
}

throw new NotSupportedException("The sqlcmd binary could not be found.");
}
}
27 changes: 25 additions & 2 deletions tests/Testcontainers.MsSql.Tests/MsSqlContainerTest.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
namespace Testcontainers.MsSql;

public sealed class MsSqlContainerTest : IAsyncLifetime
public abstract class MsSqlContainerTest : IAsyncLifetime
{
private readonly MsSqlContainer _msSqlContainer = new MsSqlBuilder().Build();
private readonly MsSqlContainer _msSqlContainer;

public MsSqlContainerTest(MsSqlContainer msSqlContainer)
{
_msSqlContainer = msSqlContainer;
}

public Task InitializeAsync()
{
Expand Down Expand Up @@ -43,4 +48,22 @@ public async Task ExecScriptReturnsSuccessful()
Assert.True(0L.Equals(execResult.ExitCode), execResult.Stderr);
Assert.Empty(execResult.Stderr);
}

[UsedImplicitly]
public sealed class MsSqlDefaultConfiguration : MsSqlContainerTest
{
public MsSqlDefaultConfiguration()
: base(new MsSqlBuilder().Build())
{
}
}

[UsedImplicitly]
public sealed class MsSqlTools18Configuration : MsSqlContainerTest
{
public MsSqlTools18Configuration()
: base(new MsSqlBuilder().WithImage("mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04").Build())
{
}
}
}
1 change: 1 addition & 0 deletions tests/Testcontainers.MsSql.Tests/Usings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
global using System.Data.Common;
global using System.Threading.Tasks;
global using DotNet.Testcontainers.Commons;
global using JetBrains.Annotations;
global using Microsoft.Data.SqlClient;
global using Xunit;

0 comments on commit b2699cc

Please sign in to comment.