From 4ea5b1264a18ff2de8ede0fc434cc05d9c58332d Mon Sep 17 00:00:00 2001 From: Mariusz Matysek Date: Thu, 29 Aug 2024 08:37:15 +0200 Subject: [PATCH] Use docker auth (#141) - Use docker auth - Fix Sql Server Provider - Use prefer local images on every default resource options --- .../Blob/AzureStorageBlobDefaultOptions.cs | 3 +- .../Queue/AzureStorageQueueDefaultOptions.cs | 3 +- src/ClickHouse/ClickHouseDefaultOptions.cs | 3 +- src/Core/ContainerResourceOptions.cs | 96 +++++++++-- src/Core/Core.csproj | 1 + src/Core/DockerAuth.cs | 23 +++ src/Core/DockerConfiguration.cs | 21 +-- src/Core/DockerContainerManager.cs | 152 +++++++++--------- src/Core/DockerRegistryConfiguration.cs | 27 ++-- .../ElasticsearchDefaultOptions.cs | 3 +- src/MariaDB/MariaDBDefaultOptions.cs | 3 +- src/Mongo/MongoDefaultOptions.cs | 3 +- src/MySql/MySqlDefaultOptions.cs | 3 +- src/Nats.Tests/Nats.Tests.csproj | 2 +- src/Nats/NatsDefaultOptions.cs | 3 +- src/Neo4j/Neo4jDefaultOptions.cs | 3 +- src/PostgreSql/PostgreSqlDefaultOptions.cs | 9 +- src/RabbitMQ/RabbitMQDefaultOptions.cs | 3 +- src/RavenDB/RavenDBDefaultOptions.cs | 3 +- src/Redis/RedisDefaultOptions.cs | 3 +- src/S3/S3DefaultOptions.cs | 3 +- .../Resources/SqlServer2019Options.cs | 4 +- src/SqlServer/SqlCommand.cs | 4 +- src/SqlServer/SqlServerDefaultOptions.cs | 3 +- src/SqlServer/SqlServerResource.cs | 30 ++-- 25 files changed, 266 insertions(+), 145 deletions(-) create mode 100644 src/Core/DockerAuth.cs 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)