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

fix: Get sqlcmd utility file path from container instead of const file path #1221

Merged
merged 7 commits into from
Aug 29, 2024
Merged
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
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;
Loading