diff --git a/Ductus.FluentDocker.Tests/CommandTests/ComposeCommandTests.cs b/Ductus.FluentDocker.Tests/CommandTests/ComposeCommandTests.cs index 56a581bc..195c766c 100644 --- a/Ductus.FluentDocker.Tests/CommandTests/ComposeCommandTests.cs +++ b/Ductus.FluentDocker.Tests/CommandTests/ComposeCommandTests.cs @@ -1,10 +1,8 @@ using System.IO; -using System.Linq; using System.Threading.Tasks; using Ductus.FluentDocker.Commands; using Ductus.FluentDocker.Extensions; using Ductus.FluentDocker.Model.Common; -using Ductus.FluentDocker.Services; using Ductus.FluentDocker.Services.Extensions; using Ductus.FluentDocker.Services.Impl; using Ductus.FluentDocker.Tests.Compose; @@ -33,7 +31,13 @@ public async Task try { - var result = DockerHost.Host.ComposeUp(composeFile: file, certificates: DockerHost.Certificates); + var result = DockerHost.Host + .ComposeUpCommand(new Commands.Compose.ComposeUpCommandArgs + { + ComposeFiles = new System.Collections.Generic.List() { file }, + Certificates = DockerHost.Certificates + }); + Assert.IsTrue(result.Success); var ids = DockerHost.Host.ComposePs(composeFile: file, certificates: DockerHost.Certificates); @@ -83,8 +87,13 @@ public void Issue79_DockerComposeOnDockerMachineShallWork() typeof(NsResolver).ResourceExtract(fullPath); var hostService = new DockerHostService("wifi-test"); + var composeResponse = hostService.Host - .ComposeUp(composeFile: file, certificates: hostService.Certificates); + .ComposeUpCommand(new Commands.Compose.ComposeUpCommandArgs + { + ComposeFiles = new System.Collections.Generic.List() { file }, + Certificates = hostService.Certificates + }); } } } diff --git a/Ductus.FluentDocker/Builders/CompositeBuilder.cs b/Ductus.FluentDocker/Builders/CompositeBuilder.cs index 631efea9..32621889 100644 --- a/Ductus.FluentDocker/Builders/CompositeBuilder.cs +++ b/Ductus.FluentDocker/Builders/CompositeBuilder.cs @@ -179,6 +179,17 @@ public CompositeBuilder FromFile(params string[] composeFile) return this; } + /// + /// Explicitly sets the project directory. + /// + /// The project dir, if none set it to an empty string. + /// Itself for fluent access. + public CompositeBuilder UseProjectDir(TemplateString projectDir) + { + _config.ProjectDirectory = projectDir; + return this; + } + public CompositeBuilder ForceRecreate() { _config.ForceRecreate = true; diff --git a/Ductus.FluentDocker/Commands/Compose.cs b/Ductus.FluentDocker/Commands/Compose.cs index c6bc4979..6dd7d7cf 100644 --- a/Ductus.FluentDocker/Commands/Compose.cs +++ b/Ductus.FluentDocker/Commands/Compose.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using Ductus.FluentDocker.Executors; using Ductus.FluentDocker.Executors.Parsers; using Ductus.FluentDocker.Extensions; @@ -406,64 +407,107 @@ public static CommandResponse> ComposeDown(this DockerUri host, st $"{args} down {options}", cwd.NeedCwd ? cwd.Cwd : null).ExecutionEnvironment(env).Execute(); } + [Obsolete("Use ComposeUpCommand(...)")] public static CommandResponse> ComposeUp(this DockerUri host, string altProjectName = null, bool forceRecreate = false, bool noRecreate = false, bool dontBuild = false, bool buildBeforeCreate = false, TimeSpan? timeout = null, - bool removeOphans = false, + bool removeOrphans = false, bool useColor = false, bool noStart = false, string[] services = null /*all*/, IDictionary env = null, ICertificatePaths certificates = null, params string[] composeFile) { - if (forceRecreate && noRecreate) + return host.ComposeUpCommand(new ComposeUpCommandArgs { - throw new InvalidOperationException($"{nameof(forceRecreate)} and {nameof(noRecreate)} are incompatible."); + AltProjectName = altProjectName, + ForceRecreate = forceRecreate, + NoRecreate = noRecreate, + DontBuild = dontBuild, + BuildBeforeCreate = buildBeforeCreate, + Timeout = timeout, + RemoveOrphans = removeOrphans, + UseColor = useColor, + NoStart = noStart, + Services = services, + Env = env, + Certificates = certificates, + ComposeFiles = composeFile + }); + } + + public struct ComposeUpCommandArgs + { + public string AltProjectName {get;set;} + public bool ForceRecreate {get;set;} + public bool NoRecreate {get;set;} + public bool DontBuild {get;set;} + public bool BuildBeforeCreate {get;set;} + public TimeSpan? Timeout {get;set;} + public bool RemoveOrphans {get;set;} + public bool UseColor {get;set;} + public bool NoStart {get;set;} + public IList Services {get;set;} + public IDictionary Env {get;set;} + public ICertificatePaths Certificates {get;set;} + public IList ComposeFiles {get;set;} + public TemplateString ProjectDirectory {get;set;} + } + + public static CommandResponse> ComposeUpCommand(this DockerUri host, ComposeUpCommandArgs ca) + { + if (ca.ForceRecreate && ca.NoRecreate) + { + throw new InvalidOperationException("ForceRecreate and NoRecreate are incompatible."); } - var cwd = WorkingDirectory(composeFile); + var cwd = WorkingDirectory(ca.ComposeFiles.ToArray()); - var args = $"{host.RenderBaseArgs(certificates)}"; + var args = $"{host.RenderBaseArgs(ca.Certificates)}"; - if (null != composeFile && 0 != composeFile.Length) - foreach (var cf in composeFile) + if (null != ca.ComposeFiles && 0 != ca.ComposeFiles.Count) + foreach (var cf in ca.ComposeFiles) if (!string.IsNullOrEmpty(cf)) args += $" -f \"{cf}\""; - if (!string.IsNullOrEmpty(altProjectName)) - args += $" -p {altProjectName}"; + if (!string.IsNullOrEmpty(ca.AltProjectName)) + args += $" -p {ca.AltProjectName}"; - var options = noStart ? "--no-start" : "--detach"; + var options = ca.NoStart ? "--no-start" : "--detach"; - if (forceRecreate) + if (ca.ForceRecreate) options += " --force-recreate"; - if (noRecreate) + if (ca.NoRecreate) options += " --no-recreate"; - if (dontBuild) + if (ca.DontBuild) options += " --no-build"; - if (buildBeforeCreate) + if (ca.BuildBeforeCreate) options += " --build"; - if (!useColor) + if (!ca.UseColor) options += " --no-color"; - if (null != timeout) - options += $" -t {Math.Round(timeout.Value.TotalSeconds, 0)}"; + if (null != ca.Timeout) + options += $" -t {Math.Round(ca.Timeout.Value.TotalSeconds, 0)}"; - if (removeOphans) + if (ca.RemoveOrphans) options += " --remove-orphans"; - if (null != services && 0 != services.Length) - options += " " + string.Join(" ", services); + if (!string.IsNullOrEmpty(ca.ProjectDirectory)) { + options += $" --project-directory {ca.ProjectDirectory.Rendered}"; + } + + if (null != ca.Services && 0 != ca.Services.Count) + options += " " + string.Join(" ", ca.Services); return new ProcessExecutor>( "docker-compose".ResolveBinary(), - $"{args} up {options}", cwd.NeedCwd ? cwd.Cwd : null).ExecutionEnvironment(env).Execute(); + $"{args} up {options}", cwd.NeedCwd ? cwd.Cwd : null).ExecutionEnvironment(ca.Env).Execute(); } public static CommandResponse> ComposeRm(this DockerUri host, string altProjectName = null, diff --git a/Ductus.FluentDocker/Model/Compose/DockerComposeConfig.cs b/Ductus.FluentDocker/Model/Compose/DockerComposeConfig.cs index 0c1e637e..7a870750 100644 --- a/Ductus.FluentDocker/Model/Compose/DockerComposeConfig.cs +++ b/Ductus.FluentDocker/Model/Compose/DockerComposeConfig.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Ductus.FluentDocker.Model.Images; +using Ductus.FluentDocker.Model.Common; namespace Ductus.FluentDocker.Model.Compose { @@ -24,6 +25,7 @@ public class DockerComposeConfig public bool StopOnDispose { get; set; } = true; public bool KeepContainers { get; set; } public IDictionary EnvironmentNameValue { get; set; } = new Dictionary(); + public TemplateString ProjectDirectory {get;set;} public IDictionary ContainerConfiguration { get; } = new Dictionary(); diff --git a/Ductus.FluentDocker/Services/Impl/DockerComposeCompositeService.cs b/Ductus.FluentDocker/Services/Impl/DockerComposeCompositeService.cs index bf0635bf..2c1221a0 100644 --- a/Ductus.FluentDocker/Services/Impl/DockerComposeCompositeService.cs +++ b/Ductus.FluentDocker/Services/Impl/DockerComposeCompositeService.cs @@ -7,6 +7,7 @@ using Ductus.FluentDocker.Extensions; using Ductus.FluentDocker.Model.Compose; using Ductus.FluentDocker.Model.Containers; +using static Ductus.FluentDocker.Commands.Compose; namespace Ductus.FluentDocker.Services.Impl { @@ -129,14 +130,24 @@ public override void Start() State = ServiceRunningState.Starting; - var result = host.Host.ComposeUp(_config.AlternativeServiceName, _config.ForceRecreate, - _config.NoRecreate, _config.NoBuild, _config.ForceBuild, - _config.TimeoutSeconds == TimeSpan.Zero ? (TimeSpan?)null : _config.TimeoutSeconds, _config.RemoveOrphans, - _config.UseColor, - true/*noStart*/, - _config.Services, - _config.EnvironmentNameValue, - host.Certificates, _config.ComposeFilePath.ToArray()); + var result = host.Host.ComposeUpCommand( + new ComposeUpCommandArgs + { + AltProjectName = _config.AlternativeServiceName, + ForceRecreate = _config.ForceRecreate, + NoRecreate = _config.NoRecreate, + DontBuild = _config.NoBuild, + BuildBeforeCreate = _config.ForceBuild, + Timeout = _config.TimeoutSeconds == TimeSpan.Zero ? (TimeSpan?)null : _config.TimeoutSeconds, + RemoveOrphans = _config.RemoveOrphans, + UseColor = _config.UseColor, + NoStart = true, + Services = _config.Services, + Env = _config.EnvironmentNameValue, + Certificates = host.Certificates, + ComposeFiles = _config.ComposeFilePath.ToArray(), + ProjectDirectory = _config.ProjectDirectory + }); if (!result.Success) { @@ -147,14 +158,24 @@ public override void Start() State = ServiceRunningState.Starting; - result = host.Host.ComposeUp(_config.AlternativeServiceName, - false/*forceRecreate*/, false/*noRecreate*/, false/*dontBuild*/, false/*buildBeforeCreate*/, - _config.TimeoutSeconds == TimeSpan.Zero ? (TimeSpan?)null : _config.TimeoutSeconds, _config.RemoveOrphans, - _config.UseColor, - false/*noStart*/, - _config.Services, - _config.EnvironmentNameValue, - host.Certificates, _config.ComposeFilePath.ToArray()); + result = host.Host.ComposeUpCommand( + new ComposeUpCommandArgs + { + AltProjectName = _config.AlternativeServiceName, + ForceRecreate = false, + NoRecreate = false, + DontBuild = false, + BuildBeforeCreate = false, + Timeout = _config.TimeoutSeconds == TimeSpan.Zero ? (TimeSpan?)null : _config.TimeoutSeconds, + RemoveOrphans = _config.RemoveOrphans, + UseColor = _config.UseColor, + NoStart = false, + Services = _config.Services, + Env = _config.EnvironmentNameValue, + Certificates = host.Certificates, + ComposeFiles = _config.ComposeFilePath.ToArray(), + ProjectDirectory = _config.ProjectDirectory + }); if (!result.Success) { diff --git a/Examples/.vscode/launch.json b/Examples/.vscode/launch.json new file mode 100644 index 00000000..5b07fb86 --- /dev/null +++ b/Examples/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/Simple/bin/Debug/netcoreapp3.1/Simple.dll", + "args": [], + "cwd": "${workspaceFolder}/Simple", + // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/Examples/Examples.sln b/Examples/Examples.sln index 64b51a61..3c92f496 100644 --- a/Examples/Examples.sln +++ b/Examples/Examples.sln @@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DockerInDockerLinux", "Dock EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventDriven", "EventDriven\EventDriven.csproj", "{75D101EB-5CE3-457E-8843-76898CB5513B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Simple", "Simple\Simple.csproj", "{25359F72-CCB4-4373-9857-D0AC45F8510F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -44,5 +46,17 @@ Global {75D101EB-5CE3-457E-8843-76898CB5513B}.Release|x64.Build.0 = Release|Any CPU {75D101EB-5CE3-457E-8843-76898CB5513B}.Release|x86.ActiveCfg = Release|Any CPU {75D101EB-5CE3-457E-8843-76898CB5513B}.Release|x86.Build.0 = Release|Any CPU + {25359F72-CCB4-4373-9857-D0AC45F8510F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {25359F72-CCB4-4373-9857-D0AC45F8510F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {25359F72-CCB4-4373-9857-D0AC45F8510F}.Debug|x64.ActiveCfg = Debug|Any CPU + {25359F72-CCB4-4373-9857-D0AC45F8510F}.Debug|x64.Build.0 = Debug|Any CPU + {25359F72-CCB4-4373-9857-D0AC45F8510F}.Debug|x86.ActiveCfg = Debug|Any CPU + {25359F72-CCB4-4373-9857-D0AC45F8510F}.Debug|x86.Build.0 = Debug|Any CPU + {25359F72-CCB4-4373-9857-D0AC45F8510F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {25359F72-CCB4-4373-9857-D0AC45F8510F}.Release|Any CPU.Build.0 = Release|Any CPU + {25359F72-CCB4-4373-9857-D0AC45F8510F}.Release|x64.ActiveCfg = Release|Any CPU + {25359F72-CCB4-4373-9857-D0AC45F8510F}.Release|x64.Build.0 = Release|Any CPU + {25359F72-CCB4-4373-9857-D0AC45F8510F}.Release|x86.ActiveCfg = Release|Any CPU + {25359F72-CCB4-4373-9857-D0AC45F8510F}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Examples/Simple/Program.cs b/Examples/Simple/Program.cs new file mode 100644 index 00000000..3bdfb224 --- /dev/null +++ b/Examples/Simple/Program.cs @@ -0,0 +1,125 @@ +using System; +using System.Linq; +using System.Diagnostics; +using Ductus.FluentDocker.Model.Containers; +using Ductus.FluentDocker.Builders; +using Ductus.FluentDocker.Extensions; +using Ductus.FluentDocker.Services; + +namespace Simple +{ + class Program + { + static void RunSingleContainerFluentAPI() + { + using ( + var container = + new Builder().UseContainer() + .UseImage("postgres:9.6-alpine") + .ExposePort(5432) + .WithEnvironment("POSTGRES_PASSWORD=mysecretpassword") + .WaitForPort("5432/tcp", 30000) + .Build() + .Start()) + { + + var config = container.GetConfiguration(true); + var running = ServiceRunningState.Running == config.State.ToServiceState(); + + Console.WriteLine(running ? "Service is running" : "Failed to start nginx instance..."); + + } + } + + static void PerformanceSingleContainer() + { + + Stopwatch stopwatch = new Stopwatch(); + + stopwatch.Start(); + var hosts = new Hosts().Discover(); + var host = hosts.FirstOrDefault(x => x.IsNative) ?? hosts.FirstOrDefault(x => x.Name == "default"); + + Console.WriteLine("Hosts discovered in {0} s", TimeSpan.FromMilliseconds(stopwatch.ElapsedMilliseconds).TotalSeconds); + + var _container = host.Create("nginx:alpine", + prms: new ContainerCreateParams + { + Name = "test", + Network = "host", + PortMappings = new string[] { "9111:80", "9112:443" }, + Volumes = new string[] { "/data/log:/var/log:rw" }, + RestartPolicy = RestartPolicy.Always + }); + + Console.WriteLine("Create container: " + TimeSpan.FromMilliseconds(stopwatch.ElapsedMilliseconds).TotalSeconds); + + try + { + stopwatch.Restart(); + _container.Start(); + + Console.WriteLine("Start container: " + TimeSpan.FromMilliseconds(stopwatch.ElapsedMilliseconds).TotalSeconds); + } + finally + { + + stopwatch.Restart(); + _container.Dispose(); + Console.WriteLine("Dispose container: " + TimeSpan.FromMilliseconds(stopwatch.ElapsedMilliseconds).TotalSeconds); + + stopwatch.Stop(); + + } + + } + + static void PerformanceSingleContainerFluentAPI() + { + + Stopwatch stopwatch = new Stopwatch(); + + stopwatch.Start(); + var container = new Builder().UseContainer() + .UseImage("postgres:9.6-alpine") + .ExposePort(5432) + .WithEnvironment("POSTGRES_PASSWORD=mysecretpassword") + .WaitForPort("5432/tcp", 30000) + .Build(); + + Console.WriteLine("Build container: " + TimeSpan.FromMilliseconds(stopwatch.ElapsedMilliseconds).TotalSeconds); + + try + { + stopwatch.Restart(); + container.Start(); + Console.WriteLine("Start container: " + TimeSpan.FromMilliseconds(stopwatch.ElapsedMilliseconds).TotalSeconds); + + stopwatch.Restart(); + var config = container.GetConfiguration(true); + Console.WriteLine("Get configuration: " + TimeSpan.FromMilliseconds(stopwatch.ElapsedMilliseconds).TotalSeconds); + + var running = ServiceRunningState.Running == config.State.ToServiceState(); + Console.WriteLine(running ? "Service is running" : "Failed to start nginx instance..."); + + + } + finally + { + + stopwatch.Restart(); + container.Dispose(); + Console.WriteLine("Dispose container: " + TimeSpan.FromMilliseconds(stopwatch.ElapsedMilliseconds).TotalSeconds); + + stopwatch.Stop(); + } + } + + static void Main(string[] args) + { + //RunSingleContainerFluentAPI(); + //PerformanceSingleContainer(); + //PerformanceSingleContainerFluentAPI(); + } + } +} diff --git a/Examples/Simple/README.md b/Examples/Simple/README.md new file mode 100644 index 00000000..3fdaff39 --- /dev/null +++ b/Examples/Simple/README.md @@ -0,0 +1,3 @@ +# Simple + +Simple test project to demonstrate how to use _FluentDocker_ in various ways. \ No newline at end of file diff --git a/Examples/Simple/Simple.csproj b/Examples/Simple/Simple.csproj new file mode 100644 index 00000000..fb5ef0ac --- /dev/null +++ b/Examples/Simple/Simple.csproj @@ -0,0 +1,11 @@ + + + + Exe + netcoreapp3.1 + + + + + +