diff --git a/src/AzureStorage/Blob/AzureStorageBlobDefaultOptions.cs b/src/AzureStorage/Blob/AzureStorageBlobDefaultOptions.cs
index b123d781..09681528 100644
--- a/src/AzureStorage/Blob/AzureStorageBlobDefaultOptions.cs
+++ b/src/AzureStorage/Blob/AzureStorageBlobDefaultOptions.cs
@@ -21,7 +21,8 @@ public override void Configure(ContainerResourceBuilder builder)
builder
.Name(name)
.Image("mcr.microsoft.com/azure-storage/azurite")
- .InternalPort(10000);
+ .InternalPort(10000)
+ .PreferLocalImage();
}
}
}
diff --git a/src/AzureStorage/Queue/AzureStorageQueueDefaultOptions.cs b/src/AzureStorage/Queue/AzureStorageQueueDefaultOptions.cs
index 0a920484..8c88b82c 100644
--- a/src/AzureStorage/Queue/AzureStorageQueueDefaultOptions.cs
+++ b/src/AzureStorage/Queue/AzureStorageQueueDefaultOptions.cs
@@ -21,7 +21,8 @@ public override void Configure(ContainerResourceBuilder builder)
builder
.Name(name)
.Image("mcr.microsoft.com/azure-storage/azurite")
- .InternalPort(10001);
+ .InternalPort(10001)
+ .PreferLocalImage();
}
}
}
diff --git a/src/ClickHouse/ClickHouseDefaultOptions.cs b/src/ClickHouse/ClickHouseDefaultOptions.cs
index 539192c7..3598133c 100644
--- a/src/ClickHouse/ClickHouseDefaultOptions.cs
+++ b/src/ClickHouse/ClickHouseDefaultOptions.cs
@@ -14,7 +14,8 @@ public override void Configure(ContainerResourceBuilder builder)
builder
.Name("clickhouse-server")
.Image("clickhouse/clickhouse-server")
- .InternalPort(8123);
+ .InternalPort(8123)
+ .PreferLocalImage();
}
}
}
diff --git a/src/Core/ContainerResourceOptions.cs b/src/Core/ContainerResourceOptions.cs
index 995f6818..87948f15 100644
--- a/src/Core/ContainerResourceOptions.cs
+++ b/src/Core/ContainerResourceOptions.cs
@@ -1,22 +1,26 @@
-using System;
+using System;
using System.Collections;
using System.Collections.Generic;
+using System.IO;
+using System.Linq;
using Microsoft.Extensions.Configuration;
+using Newtonsoft.Json;
+using JsonSerializer = System.Text.Json.JsonSerializer;
namespace Squadron
{
- ///
- /// Abstract base class for container resource options
- ///
+ ///
+ /// Abstract base class for container resource options
+ ///
public abstract class ContainerResourceOptions
{
- ///
- /// Configures the resource
- ///
- /// The builder.
+ ///
+ /// Configures the resource
+ ///
+ /// The builder.
public abstract void Configure(ContainerResourceBuilder builder);
-
+
public static DockerConfiguration DefaultDockerConfigResolver()
{
IConfigurationRoot configuration = new ConfigurationBuilder()
@@ -27,8 +31,78 @@ public static DockerConfiguration DefaultDockerConfigResolver()
IConfigurationSection section = configuration.GetSection("Squadron:Docker");
- DockerConfiguration containerConfig = section.Get();
- return containerConfig ?? new DockerConfiguration();
+ DockerConfiguration containerConfig = section.Get() ?? new DockerConfiguration();
+
+ AddLocalDockerAuthentication(containerConfig);
+
+ return containerConfig;
+ }
+
+ private static void AddLocalDockerAuthentication(DockerConfiguration containerConfig)
+ {
+ var dockerAuthRootObject = TryGetDockerAuthRootObject();
+
+ if (dockerAuthRootObject != null)
+ {
+ foreach (var auth in dockerAuthRootObject.Auths)
+ {
+ if(!Uri.TryCreate(auth.Key, UriKind.RelativeOrAbsolute, out Uri address))
+ {
+ continue;
+ }
+
+ if (containerConfig.Registries.Any(p =>
+ p.Address.Equals(address.ToString(), StringComparison.InvariantCultureIgnoreCase)) ||
+ string.IsNullOrEmpty(auth.Value.Email) ||
+ string.IsNullOrEmpty(auth.Value.Auth))
+ {
+ continue;
+ }
+
+ var decryptedToken = Convert.FromBase64String(auth.Value.Auth);
+ var token = System.Text.Encoding.UTF8.GetString(decryptedToken);
+ var parts = token.Split(':');
+
+ if (parts.Length != 2)
+ {
+ continue;
+ }
+
+ containerConfig.Registries.Add(new DockerRegistryConfiguration
+ {
+ Name = address.Host,
+ Address = address.ToString(),
+ Username = parts[0],
+ Password = parts[1]
+ });
+ }
+ }
+ }
+
+ private static DockerAuthRootObject? TryGetDockerAuthRootObject()
+ {
+ var dockerConfigPath = Environment.GetEnvironmentVariable("DOCKER_CONFIG");
+ if (string.IsNullOrEmpty(dockerConfigPath))
+ {
+ return null;
+ }
+
+ var configFilePath = Path.Combine(dockerConfigPath, "config.json");
+
+ if (!File.Exists(configFilePath))
+ {
+ return null;
+ }
+
+ try
+ {
+ var jsonString = File.ReadAllText(configFilePath);
+
+ return JsonSerializer.Deserialize(jsonString);
+ }
+ catch { }
+
+ return null;
}
}
}
diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj
index 148af10f..afd27f7e 100644
--- a/src/Core/Core.csproj
+++ b/src/Core/Core.csproj
@@ -32,6 +32,7 @@
+
diff --git a/src/Core/DockerAuth.cs b/src/Core/DockerAuth.cs
new file mode 100644
index 00000000..9db1b203
--- /dev/null
+++ b/src/Core/DockerAuth.cs
@@ -0,0 +1,23 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Squadron
+{
+ public class DockerAuth
+ {
+ [JsonPropertyName("auth")]
+ public string Auth { get; set; }
+
+ [JsonPropertyName("email")]
+ public string Email { get; set; }
+ }
+
+ public class DockerAuthRootObject
+ {
+ [JsonPropertyName("auths")]
+ public Dictionary Auths { get; set; }
+
+ [JsonPropertyName("HttpHeaders")]
+ public Dictionary HttpHeaders { get; set; }
+ }
+}
diff --git a/src/Core/DockerConfiguration.cs b/src/Core/DockerConfiguration.cs
index 629024a0..e79aaa46 100644
--- a/src/Core/DockerConfiguration.cs
+++ b/src/Core/DockerConfiguration.cs
@@ -2,18 +2,19 @@
namespace Squadron
{
- ///
- /// Docker configuration
- ///
+ ///
+ /// Docker configuration
+ ///
public class DockerConfiguration
{
- ///
- /// Gets or sets the registries.
- ///
- ///
- /// The registries.
- ///
- public IEnumerable Registries { get; set; }
+ ///
+ /// Gets or sets the registries.
+ ///
+ ///
+ /// The registries.
+ ///
+ public IList Registries { get; set; } =
+ new List();
public ContainerAddressMode DefaultAddressMode { get; internal set; } = ContainerAddressMode.Port;
}
diff --git a/src/Core/DockerContainerManager.cs b/src/Core/DockerContainerManager.cs
index f3d624c6..c07003d8 100644
--- a/src/Core/DockerContainerManager.cs
+++ b/src/Core/DockerContainerManager.cs
@@ -34,29 +34,30 @@ public class DockerContainerManager : IDockerContainerManager
/// The settings.
///
public DockerContainerManager(ContainerResourceSettings settings,
- DockerConfiguration dockerConfiguration)
+ DockerConfiguration dockerConfiguration)
{
_settings = settings;
_dockerConfiguration = dockerConfiguration;
Client = new DockerClientConfiguration(
- LocalDockerUri(),
- null,
- TimeSpan.FromMinutes(5)
- )
- .CreateClient(Version.Parse("1.25"));
+ LocalDockerUri(),
+ null,
+ TimeSpan.FromMinutes(5)
+ )
+ .CreateClient(Version.Parse("1.25"));
_authConfig = GetAuthConfig();
_variableResolver = new VariableResolver(_settings.Variables);
}
-
+
public ContainerInstance Instance { get; } = new ContainerInstance();
-
+
public DockerClient Client { get; }
private AuthConfig GetAuthConfig()
{
if (_settings.RegistryName != null)
{
- DockerRegistryConfiguration registryConfig = _dockerConfiguration.Registries
+ DockerRegistryConfiguration? registryConfig = _dockerConfiguration
+ .Registries
.FirstOrDefault(x => x.Name.Equals(
_settings.RegistryName,
StringComparison.InvariantCultureIgnoreCase));
@@ -65,17 +66,42 @@ private AuthConfig GetAuthConfig()
{
throw new InvalidOperationException(
$"No container registry with name '{_settings.RegistryName}'" +
- "found in configuration");
+ "found in configuration");
}
- return new AuthConfig
- {
- ServerAddress = registryConfig.Address,
- Username = registryConfig.Username,
- Password = registryConfig.Password
- };
+ return GetAuthConfig(registryConfig);
+ }
+
+ return TrySetDefaultAuthConfig(_settings.Image);
+ }
+
+ private AuthConfig GetAuthConfig(DockerRegistryConfiguration registryConfig)
+ {
+ return new AuthConfig
+ {
+ Username = string.IsNullOrEmpty(registryConfig.Username)?null:registryConfig.Username,
+ Password = string.IsNullOrEmpty(registryConfig.Password)?null:registryConfig.Password,
+ ServerAddress = registryConfig.Address
+ };
+ }
+
+ private AuthConfig TrySetDefaultAuthConfig(string imageName)
+ {
+ var registryName = "index.docker.io";
+
+ try
+ {
+ registryName = new Uri(imageName).Host;
}
- return new AuthConfig();
+ catch{}
+
+ DockerRegistryConfiguration? registryConfig = _dockerConfiguration
+ .Registries
+ .FirstOrDefault(x => x.Name.Equals(
+ registryName,
+ StringComparison.InvariantCultureIgnoreCase));
+
+ return registryConfig != null ? GetAuthConfig(registryConfig) : new AuthConfig();
}
private static Uri LocalDockerUri()
@@ -91,9 +117,7 @@ private static Uri LocalDockerUri()
return new Uri("npipe://./pipe/docker_engine");
#else
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
- return isWindows ?
- new Uri("npipe://./pipe/docker_engine") :
- new Uri("unix:/var/run/docker.sock");
+ return isWindows ? new Uri("npipe://./pipe/docker_engine") : new Uri("unix:/var/run/docker.sock");
#endif
}
@@ -121,10 +145,7 @@ public async Task CreateAndStartContainerAsync()
///
public async Task StopContainerAsync()
{
- var stopOptions = new ContainerStopParameters
- {
- WaitBeforeKillSeconds = 5
- };
+ var stopOptions = new ContainerStopParameters { WaitBeforeKillSeconds = 5 };
bool stopped = await Client.Containers
.StopContainerAsync(Instance.Id, stopOptions, default);
@@ -153,11 +174,7 @@ public async Task ResumeAsync()
///
public async Task RemoveContainerAsync()
{
- var removeOptions = new ContainerRemoveParameters
- {
- Force = true,
- RemoveVolumes = true
- };
+ var removeOptions = new ContainerRemoveParameters { Force = true, RemoveVolumes = true };
try
{
@@ -190,8 +207,7 @@ await Client.Containers.ExtractArchiveToContainerAsync(
Instance.Id,
new ContainerPathStatParameters
{
- AllowOverwriteDirWithFile = true,
- Path = context.DestinationFolder.Replace("\\", "/")
+ AllowOverwriteDirWithFile = true, Path = context.DestinationFolder.Replace("\\", "/")
}, archiver.Stream);
});
}
@@ -200,17 +216,16 @@ await Client.Containers.ExtractArchiveToContainerAsync(
public async Task InvokeCommandAsync(ContainerExecCreateParameters parameters)
{
ContainerExecCreateResponse response = await Client.Exec
- .ExecCreateContainerAsync( Instance.Id, parameters);
+ .ExecCreateContainerAsync(Instance.Id, parameters);
if (string.IsNullOrEmpty(response.ID))
{
return null;
-
}
- using MultiplexedStream stream =
- await Client.Exec.StartAndAttachContainerExecAsync(response.ID, false);
-
+ using MultiplexedStream stream =
+ await Client.Exec.StartAndAttachContainerExecAsync(response.ID, false);
+
(var stdout, var stderr) = await stream
.ReadOutputToEndAsync(CancellationToken.None);
@@ -233,9 +248,7 @@ private async Task CreateLogStreamAsync()
var containerStatsParameters = new ContainerLogsParameters
{
- Follow = true,
- ShowStderr = true,
- ShowStdout = true
+ Follow = true, ShowStderr = true, ShowStdout = true
};
Instance.LogStream = await Client
@@ -273,7 +286,6 @@ await Retry(async () =>
{
_settings.Logger.Information("Container started");
}
-
});
}
catch (Exception ex)
@@ -318,8 +330,8 @@ private async Task CreateContainerAsync()
new PortBinding()
{
HostIP = containerPortMapping.HostIp ?? "",
- HostPort = containerPortMapping.ExternalPort != 0 ?
- containerPortMapping.ExternalPort.ToString()
+ HostPort = containerPortMapping.ExternalPort != 0
+ ? containerPortMapping.ExternalPort.ToString()
: ""
});
@@ -435,7 +447,7 @@ await Client.Images.ListImagesAsync(
catch (Exception ex)
{
throw new ContainerException(
- $"Error in ImageExists: {_settings.ImageFullname }", ex);
+ $"Error in ImageExists: {_settings.ImageFullname}", ex);
}
}
@@ -464,7 +476,7 @@ await Client.Images.CreateImageAsync(
catch (Exception ex)
{
throw new ContainerException(
- $"Error in PullImage: {_settings.ImageFullname }", ex);
+ $"Error in PullImage: {_settings.ImageFullname}", ex);
}
}
@@ -508,6 +520,7 @@ private async Task ResolveHostAddressAsync()
Instance.Address = inspectResponse.NetworkSettings.IPAddress;
Instance.HostPort = _settings.InternalPort;
}
+
Instance.IsRunning = inspectResponse.State.Running;
bindingsResolved = true;
@@ -554,6 +567,7 @@ private ContainerAddressMode GetAddressMode()
//Overide by user setting
addressMode = _settings.AddressMode;
}
+
if (addressMode == ContainerAddressMode.Auto)
{
//Default to port when not defined
@@ -633,10 +647,7 @@ await Retry(async () =>
{
await Client.Networks.ConnectNetworkAsync(
networkId,
- new NetworkConnectParameters
- {
- Container = Instance.Id
- });
+ new NetworkConnectParameters { Container = Instance.Id });
});
}
}
@@ -665,10 +676,7 @@ private async Task CreateNetwork(string networkName)
string uniqueNetworkName = UniqueNameGenerator.CreateNetworkName(networkName);
NetworksCreateResponse response = await Client.Networks.CreateNetworkAsync(
- new NetworksCreateParameters
- {
- Name = uniqueNetworkName
- });
+ new NetworksCreateParameters { Name = uniqueNetworkName });
Networks.Add(networkName, uniqueNetworkName);
return response.ID;
@@ -678,28 +686,28 @@ private async Task RemoveNetworkIfUnused(string networkName)
{
string uniqueNetworkName = Networks[networkName];
await Retry(async () =>
- {
- NetworkResponse? inspectResponse = (await Client.Networks.ListNetworksAsync())
- .FirstOrDefault(n => n.Name == uniqueNetworkName);
+ {
+ NetworkResponse? inspectResponse = (await Client.Networks.ListNetworksAsync())
+ .FirstOrDefault(n => n.Name == uniqueNetworkName);
- if (inspectResponse != null && !inspectResponse.Containers.Any())
+ if (inspectResponse != null && !inspectResponse.Containers.Any())
+ {
+ try
{
- try
- {
- await Client.Networks.DeleteNetworkAsync(inspectResponse.ID);
- }
- catch (DockerApiException ex) when (ex.StatusCode == HttpStatusCode.Forbidden)
- {
- _settings.Logger.Warning(
- $"Cloud not remove network {inspectResponse.ID}. {ex.ResponseBody}");
- }
- catch (DockerNetworkNotFoundException)
- {
- _settings.Logger.Information(
- $"Network {inspectResponse.ID} has already been removed.");
- }
+ await Client.Networks.DeleteNetworkAsync(inspectResponse.ID);
}
- });
+ catch (DockerApiException ex) when (ex.StatusCode == HttpStatusCode.Forbidden)
+ {
+ _settings.Logger.Warning(
+ $"Cloud not remove network {inspectResponse.ID}. {ex.ResponseBody}");
+ }
+ catch (DockerNetworkNotFoundException)
+ {
+ _settings.Logger.Information(
+ $"Network {inspectResponse.ID} has already been removed.");
+ }
+ }
+ });
}
private Task Retry(Func> execute)
@@ -714,7 +722,7 @@ private Task Retry(Func execute)
{
return Policy
.Handle()
- .WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(2), RetryAction)
+ .WaitAndRetryAsync(10, _ => TimeSpan.FromSeconds(5), RetryAction)
.ExecuteAsync(async () => await execute());
}
diff --git a/src/Core/DockerRegistryConfiguration.cs b/src/Core/DockerRegistryConfiguration.cs
index 757dc03a..edb3f454 100644
--- a/src/Core/DockerRegistryConfiguration.cs
+++ b/src/Core/DockerRegistryConfiguration.cs
@@ -1,41 +1,40 @@
-namespace Squadron
-{
+namespace Squadron
+{
///
/// Docker registry configuration
///
- public class DockerRegistryConfiguration
- {
+ public class DockerRegistryConfiguration
+ {
///
/// Name that can be used in the container resource
///
///
/// The name.
///
- public string Name { get; set; }
-
+ public string Name { get; set; }
+
///
/// Adress without protocol
///
///
/// The address.
///
- public string Address { get; set; }
-
+ public string Address { get; set; }
+
///
/// Gets or sets the username.
///
///
/// The username.
///
- public string Username { get; set; }
-
+ public string Username { get; set; }
+
///
/// Gets or sets the password.
///
///
/// The password.
///
- public string Password { get; set; }
-
- }
-}
+ public string Password { get; set; }
+ }
+}
diff --git a/src/Elasticsearch/ElasticsearchDefaultOptions.cs b/src/Elasticsearch/ElasticsearchDefaultOptions.cs
index 10471434..1c1c0b4f 100644
--- a/src/Elasticsearch/ElasticsearchDefaultOptions.cs
+++ b/src/Elasticsearch/ElasticsearchDefaultOptions.cs
@@ -18,7 +18,8 @@ public override void Configure(ContainerResourceBuilder builder)
.InternalPort(9200)
.WaitTimeout(60 * 5)
.AddEnvironmentVariable("discovery.type=single-node")
- .AddEnvironmentVariable($"cluster.name={name}");
+ .AddEnvironmentVariable($"cluster.name={name}")
+ .PreferLocalImage();
}
}
}
diff --git a/src/MariaDB/MariaDBDefaultOptions.cs b/src/MariaDB/MariaDBDefaultOptions.cs
index f8e68394..e24a3f5b 100644
--- a/src/MariaDB/MariaDBDefaultOptions.cs
+++ b/src/MariaDB/MariaDBDefaultOptions.cs
@@ -26,7 +26,8 @@ public override void Configure(ContainerResourceBuilder builder)
.WaitTimeout(60)
.Username(User)
.Password(Password)
- .InternalPort(3306);
+ .InternalPort(3306)
+ .PreferLocalImage();
}
}
}
diff --git a/src/Mongo/MongoDefaultOptions.cs b/src/Mongo/MongoDefaultOptions.cs
index 754f22bd..723f5901 100644
--- a/src/Mongo/MongoDefaultOptions.cs
+++ b/src/Mongo/MongoDefaultOptions.cs
@@ -18,7 +18,8 @@ public override void Configure(ContainerResourceBuilder builder)
builder
.Name("mongodb")
.Image("mongo:latest")
- .InternalPort(27017);
+ .InternalPort(27017)
+ .PreferLocalImage();
}
}
}
diff --git a/src/MySql/MySqlDefaultOptions.cs b/src/MySql/MySqlDefaultOptions.cs
index 1630f69d..4c0612c3 100644
--- a/src/MySql/MySqlDefaultOptions.cs
+++ b/src/MySql/MySqlDefaultOptions.cs
@@ -25,7 +25,8 @@ public override void Configure(ContainerResourceBuilder builder)
.WaitTimeout(60)
.Username(User)
.Password(Password)
- .InternalPort(3306);
+ .InternalPort(3306)
+ .PreferLocalImage();
}
}
}
diff --git a/src/Nats.Tests/Nats.Tests.csproj b/src/Nats.Tests/Nats.Tests.csproj
index 1aefa5b6..0c64e290 100644
--- a/src/Nats.Tests/Nats.Tests.csproj
+++ b/src/Nats.Tests/Nats.Tests.csproj
@@ -12,7 +12,7 @@
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/src/Nats/NatsDefaultOptions.cs b/src/Nats/NatsDefaultOptions.cs
index 99f2b8f5..a98024fb 100644
--- a/src/Nats/NatsDefaultOptions.cs
+++ b/src/Nats/NatsDefaultOptions.cs
@@ -26,7 +26,8 @@ public override void Configure(ContainerResourceBuilder builder)
.Image("nats:latest")
.AddVariable("nats-monitoring", VariableType.DynamicPort)
.InternalPort(4222) // automatically dynamic because no use of ExternalPort
- .AddPortMapping(8222, "nats-monitoring");
+ .AddPortMapping(8222, "nats-monitoring")
+ .PreferLocalImage();
}
}
}
diff --git a/src/Neo4j/Neo4jDefaultOptions.cs b/src/Neo4j/Neo4jDefaultOptions.cs
index 4d726677..a5213642 100644
--- a/src/Neo4j/Neo4jDefaultOptions.cs
+++ b/src/Neo4j/Neo4jDefaultOptions.cs
@@ -22,7 +22,8 @@ public override void Configure(ContainerResourceBuilder builder)
.Image("neo4j:latest")
.InternalPort(7687)
.AddEnvironmentVariable("NEO4J_AUTH=none")
- .WaitTimeout(180);
+ .WaitTimeout(180)
+ .PreferLocalImage();
}
}
}
diff --git a/src/PostgreSql/PostgreSqlDefaultOptions.cs b/src/PostgreSql/PostgreSqlDefaultOptions.cs
index b184bec8..745c11c1 100644
--- a/src/PostgreSql/PostgreSqlDefaultOptions.cs
+++ b/src/PostgreSql/PostgreSqlDefaultOptions.cs
@@ -1,6 +1,6 @@
-using System;
+using System;
-namespace Squadron
+namespace Squadron
{
///
/// Default PostgreSQL resource options
@@ -18,7 +18,8 @@ public override void Configure(ContainerResourceBuilder builder)
.Image("postgres:latest")
.Username("postgres")
.Password(Guid.NewGuid().ToString("N").Substring(12))
- .InternalPort(5432);
+ .InternalPort(5432)
+ .PreferLocalImage();
}
}
-}
+}
diff --git a/src/RabbitMQ/RabbitMQDefaultOptions.cs b/src/RabbitMQ/RabbitMQDefaultOptions.cs
index 89f8b0b2..bb2bb345 100644
--- a/src/RabbitMQ/RabbitMQDefaultOptions.cs
+++ b/src/RabbitMQ/RabbitMQDefaultOptions.cs
@@ -17,7 +17,8 @@ public override void Configure(ContainerResourceBuilder builder)
.WaitTimeout(60)
.InternalPort(5672)
.Username("guest")
- .Password("guest");
+ .Password("guest")
+ .PreferLocalImage();
}
}
}
diff --git a/src/RavenDB/RavenDBDefaultOptions.cs b/src/RavenDB/RavenDBDefaultOptions.cs
index 7d887144..f801b799 100644
--- a/src/RavenDB/RavenDBDefaultOptions.cs
+++ b/src/RavenDB/RavenDBDefaultOptions.cs
@@ -14,7 +14,8 @@ public override void Configure(ContainerResourceBuilder builder)
builder
.Name("ravendb")
.Image("ravendb/ravendb:ubuntu-latest")
- .InternalPort(8080);
+ .InternalPort(8080)
+ .PreferLocalImage();
}
}
}
diff --git a/src/Redis/RedisDefaultOptions.cs b/src/Redis/RedisDefaultOptions.cs
index 456b6772..95559606 100644
--- a/src/Redis/RedisDefaultOptions.cs
+++ b/src/Redis/RedisDefaultOptions.cs
@@ -20,7 +20,8 @@ public override void Configure(ContainerResourceBuilder builder)
builder
.Name("redis")
.Image("redis:latest")
- .InternalPort(6379);
+ .InternalPort(6379)
+ .PreferLocalImage();
}
}
}
diff --git a/src/S3/S3DefaultOptions.cs b/src/S3/S3DefaultOptions.cs
index c8bc45db..a7e3d5b8 100644
--- a/src/S3/S3DefaultOptions.cs
+++ b/src/S3/S3DefaultOptions.cs
@@ -24,7 +24,8 @@ public override void Configure(ContainerResourceBuilder builder)
.AddCmd("server", "/data")
.Username(AccessKey)
.Password(SecretKey)
- .InternalPort(9000);
+ .InternalPort(9000)
+ .PreferLocalImage();
}
}
}
diff --git a/src/SqlServer.Tests/Resources/SqlServer2019Options.cs b/src/SqlServer.Tests/Resources/SqlServer2019Options.cs
index 45477562..274ca9c9 100644
--- a/src/SqlServer.Tests/Resources/SqlServer2019Options.cs
+++ b/src/SqlServer.Tests/Resources/SqlServer2019Options.cs
@@ -13,7 +13,7 @@ public class SqlServer2019Options : ContainerResourceOptions
///
public override void Configure(ContainerResourceBuilder builder)
{
- var password = "_Qtp" + Guid.NewGuid().ToString("N");
+ var password = "Qtp!" + Guid.NewGuid().ToString("N").Substring(0,6);
builder
.Name("mssql")
.Image("mcr.microsoft.com/mssql/server:2019-latest")
@@ -22,7 +22,7 @@ public override void Configure(ContainerResourceBuilder builder)
.Password(password)
.WaitTimeout(60 * 5)
.AddEnvironmentVariable("ACCEPT_EULA=Y")
- .AddEnvironmentVariable($"SA_PASSWORD={password}");
+ .AddEnvironmentVariable($"MSSQL_SA_PASSWORD={password}");
}
}
}
diff --git a/src/SqlServer/SqlCommand.cs b/src/SqlServer/SqlCommand.cs
index 801445c7..6f612eca 100644
--- a/src/SqlServer/SqlCommand.cs
+++ b/src/SqlServer/SqlCommand.cs
@@ -11,8 +11,8 @@ private SqlCommand(
string command,
ContainerResourceSettings settings)
{
- _command.Append("/opt/mssql-tools/bin/sqlcmd ");
- _command.Append($"-S localhost -U {settings.Username} -P {settings.Password} ");
+ _command.Append("/opt/mssql-tools18/bin/sqlcmd ");
+ _command.Append($"-S localhost -U {settings.Username} -P {settings.Password} -C ");
_command.Append(command);
}
diff --git a/src/SqlServer/SqlServerDefaultOptions.cs b/src/SqlServer/SqlServerDefaultOptions.cs
index 26d5da9b..1e0c1d7b 100644
--- a/src/SqlServer/SqlServerDefaultOptions.cs
+++ b/src/SqlServer/SqlServerDefaultOptions.cs
@@ -22,7 +22,8 @@ public override void Configure(ContainerResourceBuilder builder)
.Username("sa")
.Password(password)
.AddEnvironmentVariable("ACCEPT_EULA=Y")
- .AddEnvironmentVariable($"SA_PASSWORD={password}");
+ .AddEnvironmentVariable($"SA_PASSWORD={password}")
+ .PreferLocalImage();
}
}
}
diff --git a/src/SqlServer/SqlServerResource.cs b/src/SqlServer/SqlServerResource.cs
index b8e1aac7..47ce924f 100644
--- a/src/SqlServer/SqlServerResource.cs
+++ b/src/SqlServer/SqlServerResource.cs
@@ -20,17 +20,17 @@ public partial class SqlServerResource
IAsyncLifetime
where TOptions : ContainerResourceOptions, new()
{
- ///
- /// Sync lock
- ///
+ ///
+ /// Sync lock
+ ///
protected readonly SemaphoreSlim _sync = new SemaphoreSlim(1,1);
- ///
- /// The databases
- ///
+ ///
+ /// The databases
+ ///
protected readonly HashSet _databases = new HashSet();
- ///
- /// The SqlServer connection string
- ///
+ ///
+ /// The SqlServer connection string
+ ///
protected string _serverConnectionString;
///
@@ -158,11 +158,11 @@ await Manager.InvokeCommandAsync(
}
}
- ///
- /// Creates the database connection string.
- ///
- /// Name of the database.
- ///
+ ///
+ /// Creates the database connection string.
+ ///
+ /// Name of the database.
+ ///
protected string CreateDatabaseConnectionString(string databaseName)
=> $"{_serverConnectionString}Database={databaseName}";
@@ -173,7 +173,7 @@ private string CreateServerConnectionString()
.Append($"User ID={Settings.Username};")
.Append($"Password={Settings.Password};")
.Append("MultipleActiveResultSets=True;")
- .Append("TrustServerCertificate=True;")
+ .Append("Encrypt=no;")
.ToString();
internal async Task DeployAndExecute(string sqlScript)