From 4fe5c7229e69b3590199cad323fc6e427430f6f5 Mon Sep 17 00:00:00 2001 From: Mario Toffia Date: Fri, 17 May 2019 06:16:22 +0200 Subject: [PATCH] Fixed Issue #85 and set version to 2.6.7 --- .../Ductus.FluentDocker.MsTest.csproj | 4 +- .../Ductus.FluentDocker.Tests.csproj | 3 + .../FluentDockerComposeTests.cs | 207 ++++++++------- .../MongoDbAndNetwork/docker-compose.yml | 18 ++ Ductus.FluentDocker/Commands/Service.cs | 36 +++ Ductus.FluentDocker/Commands/Stack.cs | 1 - .../Ductus.FluentDocker.csproj | 8 +- .../Model/Service/ServiceCreate.cs | 240 ++++++++++++++++++ .../Services/Impl/DockerContainerService.cs | 2 +- appveyor.yml | 2 +- 10 files changed, 420 insertions(+), 101 deletions(-) create mode 100644 Ductus.FluentDocker.Tests/Resources/ComposeTests/MongoDbAndNetwork/docker-compose.yml create mode 100644 Ductus.FluentDocker/Commands/Service.cs create mode 100644 Ductus.FluentDocker/Model/Service/ServiceCreate.cs diff --git a/Ductus.FluentDocker.MsTest/Ductus.FluentDocker.MsTest.csproj b/Ductus.FluentDocker.MsTest/Ductus.FluentDocker.MsTest.csproj index b386471..0015228 100644 --- a/Ductus.FluentDocker.MsTest/Ductus.FluentDocker.MsTest.csproj +++ b/Ductus.FluentDocker.MsTest/Ductus.FluentDocker.MsTest.csproj @@ -2,7 +2,7 @@ netcoreapp1.1;netcoreapp2.0;net452 - 2.6.6 + 2.6.7 Ductus.FluentDocker.MsTest Mario Toffia Apache-2.0 @@ -19,7 +19,7 @@ Documentation: https://github.com/mariotoffia/FluentDocker © 2016 - 2019 Mario Toffia $(DefineConstants);COREFX True - 2.6.6 + 2.6.7 ..\keypair.snk true true diff --git a/Ductus.FluentDocker.Tests/Ductus.FluentDocker.Tests.csproj b/Ductus.FluentDocker.Tests/Ductus.FluentDocker.Tests.csproj index 4dec2b3..d8f55e3 100644 --- a/Ductus.FluentDocker.Tests/Ductus.FluentDocker.Tests.csproj +++ b/Ductus.FluentDocker.Tests/Ductus.FluentDocker.Tests.csproj @@ -36,6 +36,9 @@ + + PreserveNewest + PreserveNewest diff --git a/Ductus.FluentDocker.Tests/FluentApiTests/FluentDockerComposeTests.cs b/Ductus.FluentDocker.Tests/FluentApiTests/FluentDockerComposeTests.cs index 91b4d9f..6a8fb28 100644 --- a/Ductus.FluentDocker.Tests/FluentApiTests/FluentDockerComposeTests.cs +++ b/Ductus.FluentDocker.Tests/FluentApiTests/FluentDockerComposeTests.cs @@ -1,10 +1,12 @@ using System; using System.IO; +using System.Linq; using System.Net; using System.Threading.Tasks; using Ductus.FluentDocker.Builders; using Ductus.FluentDocker.Common; using Ductus.FluentDocker.Model.Common; +using Ductus.FluentDocker.Services; using Ductus.FluentDocker.Tests.Extensions; using Microsoft.VisualStudio.TestTools.UnitTesting; using HttpExtensions = Ductus.FluentDocker.Extensions.HttpExtensions; @@ -13,14 +15,14 @@ namespace Ductus.FluentDocker.Tests.FluentApiTests { - [TestClass] - public class FluentDockerComposeTests : FluentDockerTestBase + [TestClass] + public class FluentDockerComposeTests : FluentDockerTestBase + { + [TestMethod] + public async Task WordPressDockerComposeServiceShallShowInstallScreen() { - [TestMethod] - public async Task WordPressDockerComposeServiceShallShowInstallScreen() - { - var file = Path.Combine(Directory.GetCurrentDirectory(), - (TemplateString) "Resources/ComposeTests/WordPress/docker-compose.yml"); + var file = Path.Combine(Directory.GetCurrentDirectory(), + (TemplateString) "Resources/ComposeTests/WordPress/docker-compose.yml"); // @formatter:off using (var svc = Fd @@ -30,24 +32,24 @@ public async Task WordPressDockerComposeServiceShallShowInstallScreen() .RemoveOrphans() .WaitForHttp("wordpress", "http://localhost:8000/wp-admin/install.php") .Build().Start()) - // @formatter:on - { - // We now have a running WordPress with a MySql database - var installPage = await "http://localhost:8000/wp-admin/install.php".Wget(); - - Assert.IsTrue(installPage.IndexOf("https://wordpress.org/", StringComparison.Ordinal) != -1); - Assert.AreEqual(1, svc.Hosts.Count); - Assert.AreEqual(2, svc.Containers.Count); - Assert.AreEqual(2, svc.Images.Count); - Assert.AreEqual(5, svc.Services.Count); - } - } + // @formatter:on + { + // We now have a running WordPress with a MySql database + var installPage = await "http://localhost:8000/wp-admin/install.php".Wget(); - [TestMethod] - public async Task ComposeWaitForHttpShallWork() - { - var file = Path.Combine(Directory.GetCurrentDirectory(), - (TemplateString) "Resources/ComposeTests/WordPress/docker-compose.yml"); + Assert.IsTrue(installPage.IndexOf("https://wordpress.org/", StringComparison.Ordinal) != -1); + Assert.AreEqual(1, svc.Hosts.Count); + Assert.AreEqual(2, svc.Containers.Count); + Assert.AreEqual(2, svc.Images.Count); + Assert.AreEqual(5, svc.Services.Count); + } + } + + [TestMethod] + public async Task ComposeWaitForHttpShallWork() + { + var file = Path.Combine(Directory.GetCurrentDirectory(), + (TemplateString) "Resources/ComposeTests/WordPress/docker-compose.yml"); // @formatter:off using (Fd @@ -58,23 +60,23 @@ public async Task ComposeWaitForHttpShallWork() .WaitForHttp("wordpress", "http://localhost:8000/wp-admin/install.php", continuation: (resp, cnt) => resp.Body.IndexOf("https://wordpress.org/", StringComparison.Ordinal) != -1 ? 0 : 500) .Build().Start()) - // @formatter:on - { - // Since we have waited - this shall now always work. - var installPage = await "http://localhost:8000/wp-admin/install.php".Wget(); - Assert.IsTrue(installPage.IndexOf("https://wordpress.org/", StringComparison.Ordinal) != -1); - } - } + // @formatter:on + { + // Since we have waited - this shall now always work. + var installPage = await "http://localhost:8000/wp-admin/install.php".Wget(); + Assert.IsTrue(installPage.IndexOf("https://wordpress.org/", StringComparison.Ordinal) != -1); + } + } - [TestMethod] - [ExpectedException(typeof(FluentDockerException))] - public void ComposeWaitForHttpThatFailShallBeAborted() - { - var file = Path.Combine(Directory.GetCurrentDirectory(), - (TemplateString) "Resources/ComposeTests/WordPress/docker-compose.yml"); + [TestMethod] + [ExpectedException(typeof(FluentDockerException))] + public void ComposeWaitForHttpThatFailShallBeAborted() + { + var file = Path.Combine(Directory.GetCurrentDirectory(), + (TemplateString) "Resources/ComposeTests/WordPress/docker-compose.yml"); - try - { + try + { // @formatter:off using (Fd .UseContainer() @@ -89,28 +91,28 @@ public void ComposeWaitForHttpThatFailShallBeAborted() return resp.Body.IndexOf("ALIBABA", StringComparison.Ordinal) != -1 ? 0 : 500; }) .Build().Start()) - // @formatter:on - - { - Assert.Fail("It should have thrown a FluentDockerException!"); - } - } - catch - { - // Manually remove containers since they are not cleaned up due to the error... - foreach (var container in Fd.Native().GetContainers()) - if (container.Name.StartsWith("wordpress")) - container.Dispose(); - - throw; - } - } + // @formatter:on - [TestMethod] - public async Task ComposeWaitForCustomLambdaShallWork() { - var file = Path.Combine(Directory.GetCurrentDirectory(), - (TemplateString) "Resources/ComposeTests/WordPress/docker-compose.yml"); + Assert.Fail("It should have thrown a FluentDockerException!"); + } + } + catch + { + // Manually remove containers since they are not cleaned up due to the error... + foreach (var container in Fd.Native().GetContainers()) + if (container.Name.StartsWith("wordpress")) + container.Dispose(); + + throw; + } + } + + [TestMethod] + public async Task ComposeWaitForCustomLambdaShallWork() + { + var file = Path.Combine(Directory.GetCurrentDirectory(), + (TemplateString) "Resources/ComposeTests/WordPress/docker-compose.yml"); // @formatter:off using (Fd @@ -126,39 +128,60 @@ public async Task ComposeWaitForCustomLambdaShallWork() res.Body.IndexOf("https://wordpress.org/", StringComparison.Ordinal) != -1 ? 0 : 500; }) .Build().Start()) - // @formatter:on - { - // Since we have waited - this shall now always work. - var installPage = await "http://localhost:8000/wp-admin/install.php".Wget(); - Assert.IsTrue(installPage.IndexOf("https://wordpress.org/", StringComparison.Ordinal) != -1); - } - } + // @formatter:on + { + // Since we have waited - this shall now always work. + var installPage = await "http://localhost:8000/wp-admin/install.php".Wget(); + Assert.IsTrue(installPage.IndexOf("https://wordpress.org/", StringComparison.Ordinal) != -1); + } + } - [TestMethod] - public void ComposeRunOnRemoteMachineShallWork() - { - var file = Path.Combine(Directory.GetCurrentDirectory(), - (TemplateString) "Resources/ComposeTests/WordPress/docker-compose.yml"); - - using ( - var svc = - new Builder().UseHost() - .UseSsh("192.168.1.27").WithName("remote-daemon") - .WithSshUser("solo").WithSshKeyPath("${E_LOCALAPPDATA}/lxss/home/martoffi/.ssh/id_rsa") - .UseContainer() - .UseCompose() - .FromFile(file) - .RemoveOrphans() - .WaitForHttp("wordpress", "http://localhost:8000/wp-admin/install.php", - continuation: (resp, cnt) => - resp.Body.IndexOf("https://wordpress.org/", StringComparison.Ordinal) != -1 ? 0 : 500) - .Build().Start()) - { - Assert.AreEqual(1, svc.Hosts.Count); - Assert.AreEqual(2, svc.Containers.Count); - Assert.AreEqual(2, svc.Images.Count); - Assert.AreEqual(5, svc.Services.Count); - } - } + [TestMethod] + public void ComposeRunOnRemoteMachineShallWork() + { + var file = Path.Combine(Directory.GetCurrentDirectory(), + (TemplateString) "Resources/ComposeTests/WordPress/docker-compose.yml"); + + using ( + var svc = + new Builder().UseHost() + .UseSsh("192.168.1.27").WithName("remote-daemon") + .WithSshUser("solo").WithSshKeyPath("${E_LOCALAPPDATA}/lxss/home/martoffi/.ssh/id_rsa") + .UseContainer() + .UseCompose() + .FromFile(file) + .RemoveOrphans() + .WaitForHttp("wordpress", "http://localhost:8000/wp-admin/install.php", + continuation: (resp, cnt) => + resp.Body.IndexOf("https://wordpress.org/", StringComparison.Ordinal) != -1 ? 0 : 500) + .Build().Start()) + { + Assert.AreEqual(1, svc.Hosts.Count); + Assert.AreEqual(2, svc.Containers.Count); + Assert.AreEqual(2, svc.Images.Count); + Assert.AreEqual(5, svc.Services.Count); + } + } + + [TestMethod] + public void Issue85() + { + var file = Path.Combine(Directory.GetCurrentDirectory(), + (TemplateString) "Resources/ComposeTests/MongoDbAndNetwork/docker-compose.yml"); + + using (var svc = Fd.UseContainer() + .UseCompose() + .FromFile(file) + .Build() + .Start()) + { + var c = (IContainerService) svc.Services.Single(s => s is IContainerService); + var nw = c.GetNetworks().Single(); + var ncfg = nw.GetConfiguration(true); + + Assert.AreEqual("mongodbandnetwork_mongodb-network", nw.Name); + Assert.AreEqual(ncfg.Id, nw.Id); + } } + } } \ No newline at end of file diff --git a/Ductus.FluentDocker.Tests/Resources/ComposeTests/MongoDbAndNetwork/docker-compose.yml b/Ductus.FluentDocker.Tests/Resources/ComposeTests/MongoDbAndNetwork/docker-compose.yml new file mode 100644 index 0000000..365550e --- /dev/null +++ b/Ductus.FluentDocker.Tests/Resources/ComposeTests/MongoDbAndNetwork/docker-compose.yml @@ -0,0 +1,18 @@ +version: '3.3' + +services: + mongodb: + image: mongo:latest + volumes: + - mongodb-data:/data + command: mongod --smallfiles --bind_ip=0.0.0.0 --logpath=/dev/null + expose: + - 27017 + networks: + - mongodb-network + +volumes: + mongodb-data: + +networks: + mongodb-network: \ No newline at end of file diff --git a/Ductus.FluentDocker/Commands/Service.cs b/Ductus.FluentDocker/Commands/Service.cs new file mode 100644 index 0000000..4b8aaed --- /dev/null +++ b/Ductus.FluentDocker/Commands/Service.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using Ductus.FluentDocker.Executors; +using Ductus.FluentDocker.Executors.Parsers; +using Ductus.FluentDocker.Extensions; +using Ductus.FluentDocker.Model.Common; +using Ductus.FluentDocker.Model.Containers; +using Ductus.FluentDocker.Model.Stacks; + +namespace Ductus.FluentDocker.Commands +{ + /// + /// Docker service commands + /// + /// + /// API 1.24+ + /// docker service create [OPTIONS] IMAGE [COMMAND] [ARG...] + /// + public static class Service + { + // TODO: Implement me! + public static CommandResponse> ServiceCreate(this DockerUri host, + Orchestrator orchestrator = Orchestrator.All, + string kubeConfigFile = null, + ICertificatePaths certificates = null, params string []stacks) + { + var args = $"{host.RenderBaseArgs(certificates)}"; + var opts = $"--orchestrator={orchestrator}"; // TODO: + + return // TODO: + new ProcessExecutor>( + "docker".ResolveBinary(), + $"{args} stack rm {opts} {string.Join(" ", stacks)}").Execute(); + } + } +} \ No newline at end of file diff --git a/Ductus.FluentDocker/Commands/Stack.cs b/Ductus.FluentDocker/Commands/Stack.cs index ea2f1bd..807c494 100644 --- a/Ductus.FluentDocker/Commands/Stack.cs +++ b/Ductus.FluentDocker/Commands/Stack.cs @@ -18,7 +18,6 @@ namespace Ductus.FluentDocker.Commands /// public static class Stack { - // TODO: https://docs.docker.com/engine/reference/commandline/stack_ps/ public static CommandResponse> StackLs(this DockerUri host, Orchestrator orchestrator = Orchestrator.All, bool kubeAllNamespaces = true, diff --git a/Ductus.FluentDocker/Ductus.FluentDocker.csproj b/Ductus.FluentDocker/Ductus.FluentDocker.csproj index d1c4a2e..630cfe6 100644 --- a/Ductus.FluentDocker/Ductus.FluentDocker.csproj +++ b/Ductus.FluentDocker/Ductus.FluentDocker.csproj @@ -1,7 +1,7 @@  netstandard1.6;netstandard2.0;net452 - 2.6.6 + 2.6.7 Ductus.FluentDocker Mario Toffia Apache-2.0 @@ -16,9 +16,9 @@ $(DefineConstants);COREFX $(DefineConstants);COREFX True - 2.6.6.0 - 2.6.6.0 - 2.6.6 + 2.6.7.0 + 2.6.7.0 + 2.6.7 ..\keypair.snk true true diff --git a/Ductus.FluentDocker/Model/Service/ServiceCreate.cs b/Ductus.FluentDocker/Model/Service/ServiceCreate.cs new file mode 100644 index 0000000..7f4b8c0 --- /dev/null +++ b/Ductus.FluentDocker/Model/Service/ServiceCreate.cs @@ -0,0 +1,240 @@ +using System.Text; +using Ductus.FluentDocker.Extensions; + +namespace Ductus.FluentDocker.Model.Service +{ + public sealed class ServiceCreate + { + /// + /// Detaches immediately when starting up. + /// + /// + /// -d, --detach + /// + public bool Detach { get; set; } + + /// + /// Your container will use the same DNS servers as the host by default, but you can override this with --dns. + /// + /// + /// The IP address of a DNS server. To specify multiple DNS servers, use multiple --dns flags. If + /// the container cannot reach any of the IP addresses you specify, Google’s public DNS server + /// 8.8.8.8 is added, so that your container can resolve internet domains. + /// --dns=[] + /// + public string[] Dns { get; set; } + + /// + /// A key-value pair representing a DNS option and its value. + /// + /// + /// See your operating system’s documentation for resolv.conf for valid options. + /// --dns-opt=[] + /// + public string[] DnsOpt { get; set; } + + /// + /// A DNS search domain to search non-fully-qualified hostnames. + /// + /// + /// It is possible to specify multiple DNS search prefixes. + /// --dns-search=[] + /// + public string[] DnsSearch { get; set; } + + /// + /// The hostname a container uses for itself. + /// + /// + /// Defaults to the container’s name if not specified. + /// --hostname + /// + public string Hostname { get; set; } + + /// + /// Command to run the health check. + /// + /// + /// --health-cmd + /// + public string HealthCheckCmd { get; set; } + + /// + /// The duration between in + /// (ms|s|m|h). + /// + /// + /// --health-interval + /// + public string HealthCheckInterval { get; set; } + + /// + /// Number of retries before it is marked as unhealthy. + /// + /// + /// --health-retries + /// + public int HealthCheckRetries { get; set; } + + /// + /// Start period for the container to initialize before counting retries towards unstable (ms|s|m|h). + /// + /// + /// --health-start-period + /// + public string HealthCheckInitialPeriod { get; set; } + + /// + /// Maximum time to allow one check to run (ms|s|m|h) + /// + /// + /// --health-timeout + /// + public string HealthCheckCmdTimeout { get; set; } + + /// + /// Disable any container-specified health check. + /// + /// + /// --no-healthcheck + /// + public bool? HealthCheckDisabled { get; set; } + + /// + /// Overwrite the default ENTRYPOINT of the image + /// + /// + /// --entrypoint + /// + public string EntryPointCmd { get; set; } + + public override string ToString() + { + var sb = new StringBuilder(); + + if (Detach) sb.Append("-d"); + + sb.OptionIfExists("--entrypoint ", EntryPointCmd); + + // Health Check + sb.OptionIfExists("--health-cmd ", HealthCheckCmd); + sb.OptionIfExists("--health-interval ", HealthCheckInterval); + sb.OptionIfExists("--health-start-period ", HealthCheckInitialPeriod); + sb.OptionIfExists("--health-timeout ", HealthCheckCmdTimeout); + if (HealthCheckRetries > 0) sb.AppendFormat(" --health-retries {0}", HealthCheckRetries); + if (HealthCheckDisabled.HasValue) sb.Append(" --no-healthcheck"); + + // Network settings + sb.OptionIfExists("--dns=", Dns); + sb.OptionIfExists("--dns-opt=", DnsOpt); + sb.OptionIfExists("--dns-search=", DnsSearch); + sb.OptionIfExists("--hostname ", Hostname); +// https://docs.docker.com/engine/reference/commandline/service_create/ + return sb.ToString(); + } + } +} +/* + +Usage: docker service create [OPTIONS] IMAGE [COMMAND] [ARG...] + +Create a new service + +Options: + --config config Specify configurations to + expose to the service + --constraint list Placement constraints + --container-label list Container labels + --credential-spec credential-spec Credential spec for managed + service account (Windows only) + --endpoint-mode string Endpoint mode (vip or dnsrr) + (default "vip") + -e, --env list Set environment variables + --env-file list Read in a file of environment + variables + --generic-resource list User defined resources + --group list Set one or more supplementary + user groups for the container + --host list Set one or more custom + host-to-IP mappings (host:ip) + --init Use an init inside each + service container to forward + signals and reap processes + --isolation string Service container isolation mode + -l, --label list Service labels + --limit-cpu decimal Limit CPUs + --limit-memory bytes Limit Memory + --log-driver string Logging driver for service + --log-opt list Logging driver options + --mode string Service mode (replicated or + global) (default "replicated") + --mount mount Attach a filesystem mount to + the service + --name string Service name + --network network Network attachments + --no-resolve-image Do not query the registry to + resolve image digest and + supported platforms + --placement-pref pref Add a placement preference + -p, --publish port Publish a port as a node port + -q, --quiet Suppress progress output + --read-only Mount the container's root + filesystem as read only + --replicas uint Number of tasks + --reserve-cpu decimal Reserve CPUs + --reserve-memory bytes Reserve Memory + --restart-condition string Restart when condition is met + ("none"|"on-failure"|"any") + (default "any") + --restart-delay duration Delay between restart attempts + (ns|us|ms|s|m|h) (default 5s) + --restart-max-attempts uint Maximum number of restarts + before giving up + --restart-window duration Window used to evaluate the + restart policy (ns|us|ms|s|m|h) + --rollback-delay duration Delay between task rollbacks + (ns|us|ms|s|m|h) (default 0s) + --rollback-failure-action string Action on rollback failure + ("pause"|"continue") (default + "pause") + --rollback-max-failure-ratio float Failure rate to tolerate + during a rollback (default 0) + --rollback-monitor duration Duration after each task + rollback to monitor for + failure (ns|us|ms|s|m|h) + (default 5s) + --rollback-order string Rollback order + ("start-first"|"stop-first") + (default "stop-first") + --rollback-parallelism uint Maximum number of tasks rolled + back simultaneously (0 to roll + back all at once) (default 1) + --secret secret Specify secrets to expose to + the service + --stop-grace-period duration Time to wait before force + killing a container + (ns|us|ms|s|m|h) (default 10s) + --stop-signal string Signal to stop the container + -t, --tty Allocate a pseudo-TTY + --update-delay duration Delay between updates + (ns|us|ms|s|m|h) (default 0s) + --update-failure-action string Action on update failure + ("pause"|"continue"|"rollback") (default "pause") + --update-max-failure-ratio float Failure rate to tolerate + during an update (default 0) + --update-monitor duration Duration after each task + update to monitor for failure + (ns|us|ms|s|m|h) (default 5s) + --update-order string Update order + ("start-first"|"stop-first") + (default "stop-first") + --update-parallelism uint Maximum number of tasks + updated simultaneously (0 to + update all at once) (default 1) + -u, --user string Username or UID (format: + [:]) + --with-registry-auth Send registry authentication + details to swarm agents + -w, --workdir string Working directory inside the + container +*/ \ No newline at end of file diff --git a/Ductus.FluentDocker/Services/Impl/DockerContainerService.cs b/Ductus.FluentDocker/Services/Impl/DockerContainerService.cs index aa7f2b8..89e834b 100644 --- a/Ductus.FluentDocker/Services/Impl/DockerContainerService.cs +++ b/Ductus.FluentDocker/Services/Impl/DockerContainerService.cs @@ -188,7 +188,7 @@ public IList GetNetworks() var list = new List(); foreach (var n in config.NetworkSettings.Networks) - list.Add(new DockerNetworkService(n.Value.NetworkID, n.Key, DockerHost, Certificates)); + list.Add(new DockerNetworkService(n.Key, n.Value.NetworkID, DockerHost, Certificates)); return list; } diff --git a/appveyor.yml b/appveyor.yml index b60eb4e..b9c0aa0 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,5 @@ #version: 0.0.0.{build} -version: 2.6.6 +version: 2.6.7 skip_non_tags: true image: Visual Studio 2017 configuration: Release