├── docs └── _config.yml ├── Ductus.FluentDocker.Tests ├── MultiContainerTestFiles │ ├── app.js │ ├── NsResolver.cs │ ├── package.txt │ ├── nginx.conf │ └── index.js ├── Compose │ ├── NsResolver.cs │ ├── nginx │ │ ├── Dockerfile │ │ ├── nginx.conf │ │ └── Dockerfile_custom │ ├── node │ │ ├── Dockerfile │ │ ├── package.txt │ │ └── index.js │ ├── redis │ │ └── Dockerfile │ └── docker-compose.yml ├── CommandTests │ ├── DockerInfoCommandTests.cs │ └── ImageTests.cs ├── Resources │ ├── StackTests │ │ └── WordPress │ │ │ └── stack.yml │ └── ComposeTests │ │ └── WordPress │ │ └── docker-compose.yml ├── Extensions │ ├── Utilities.cs │ └── HttpExtensions.cs ├── ExtensionTests │ └── ResourceExtensionsTests.cs ├── Model │ └── Common │ │ └── TemplateStringTests.cs ├── ServiceTests │ ├── DockerComposeTests.cs │ └── NetworkServiceTests.cs ├── FluentApiTests │ ├── ImageBuilderTests.cs │ └── RemoteDaemonTests.cs ├── FluentDockerTestBase.cs └── Ductus.FluentDocker.Tests.csproj ├── keypair.snk ├── Ductus.FluentDocker ├── Model │ ├── Builders │ │ ├── ICommand.cs │ │ ├── MountType.cs │ │ ├── AddCommand.cs │ │ ├── ImageBuilderConfig.cs │ │ ├── RunCommand.cs │ │ ├── HostBuilderConfig.cs │ │ ├── FileBuilderConfig.cs │ │ └── ContainerBuilderConfig.cs │ ├── Compose │ │ ├── ISecret.cs │ │ ├── IPortsDefinition.cs │ │ ├── PortMode.cs │ │ ├── IServiceVolumeDefinition.cs │ │ ├── VolumeType.cs │ │ ├── ComposeVolumeDefinition.cs │ │ ├── ContainerIsolationType.cs │ │ ├── ResourcesItemDefinition.cs │ │ ├── UlimitDefinition.cs │ │ ├── DockerComposeFileConfig.cs │ │ ├── ConfigurationDefinition.cs │ │ ├── ConfigurationItemDefinition.cs │ │ ├── TmpFsDefinition.cs │ │ ├── ResourcesDefinition.cs │ │ ├── PortsShortDefinition.cs │ │ ├── DockerComposeConfig.cs │ │ ├── PortsLongDefinition.cs │ │ ├── LoggingDefinition.cs │ │ ├── ShortSecret.cs │ │ ├── RestartPolicyDefinition.cs │ │ ├── ContainerSpecificConfig.cs │ │ ├── ConfigLongDefinition.cs │ │ ├── ShortServiceVolumeDefinition.cs │ │ ├── DeployConfigDefinition.cs │ │ ├── LongServiceVolumeDefinition.cs │ │ ├── PlacementDefinition.cs │ │ └── LongSecret.cs │ ├── Containers │ │ ├── Diff.cs │ │ ├── DiffType.cs │ │ ├── UnixSignal.cs │ │ ├── ContainerIsolationTechnology.cs │ │ ├── CertificatePaths.cs │ │ ├── ContainerMount.cs │ │ ├── BridgeNetwork.cs │ │ ├── ContainerState.cs │ │ ├── RestartPolicy.cs │ │ ├── Container.cs │ │ ├── CommandResponse.cs │ │ ├── ContainerConfig.cs │ │ ├── HostIpEndpoint.cs │ │ ├── Processes.cs │ │ ├── ContainerNetworkSettings.cs │ │ ├── VolumeMount.cs │ │ └── ImageConfig.cs │ ├── Images │ │ ├── ImageRemovalOption.cs │ │ └── DockerImageRowResponse.cs │ ├── Networks │ │ ├── IpamConfig.cs │ │ ├── Ipam.cs │ │ ├── NetworkRow.cs │ │ ├── NetworkedContainer.cs │ │ └── NetworkConfiguration.cs │ ├── Machines │ │ ├── MachineLsResponse.cs │ │ ├── MachineAuthConfig.cs │ │ └── MachineConfiguration.cs │ ├── Stacks │ │ ├── Orchestrator.cs │ │ ├── StackLsResponse.cs │ │ └── StackPsResponse.cs │ ├── Volumes │ │ └── Volume.cs │ ├── DockerInfoBase.cs │ ├── Common │ │ ├── SudoMechanism.cs │ │ ├── EmbeddedUri.cs │ │ ├── DockerUri.cs │ │ └── TemplateString.cs │ └── IFeature.cs ├── Common │ ├── Constants.cs │ ├── FluentDockerException.cs │ ├── Result.cs │ ├── Option.cs │ ├── RequestResponse.cs │ ├── ExperimentalAttribute.cs │ ├── FdOs.cs │ ├── DeprecatedAttribute.cs │ ├── Logger.cs │ ├── FeatureAttribute.cs │ ├── ResultExtensions.cs │ └── DirectoryHelper.cs ├── Resources │ ├── IResourceWriter.cs │ ├── ResourceInfo.cs │ ├── ResourceStream.cs │ ├── FileResourceWriter.cs │ └── ResourceReader.cs ├── Services │ ├── ServiceRunningState.cs │ ├── StateChangeEventArgs.cs │ ├── IVolumeService.cs │ ├── ICompositeService.cs │ ├── Logging.cs │ ├── Extensions │ │ └── HostExtensions.cs │ ├── IService.cs │ ├── INetworkService.cs │ ├── IContainerImageService.cs │ ├── Impl │ │ ├── ServiceHooks.cs │ │ ├── ServiceBase.cs │ │ ├── DockerImageService.cs │ │ └── DockerVolumeService.cs │ ├── Hosts.cs │ └── IContainerService.cs ├── Extensions │ ├── OsExtensions.cs │ ├── ComparisonExtensions.cs │ ├── CompressionExtensions.cs │ ├── ConversionExtension.cs │ ├── Utils │ │ └── DockerBinary.cs │ └── ServiceExtensions.cs ├── Executors │ ├── IProcessResponseParser.cs │ ├── Parsers │ │ ├── IgnoreErrorResponseParser.cs │ │ ├── NoLineResponseParser.cs │ │ ├── SingleStringResponseParser.cs │ │ ├── MachineStartStopResponseParser.cs │ │ ├── MachineRmResponseParser.cs │ │ ├── StringListResponseParser.cs │ │ ├── MachineCreateResponseParser.cs │ │ ├── NetworkLsResponseParser.cs │ │ ├── VolumeInspectResponseParser.cs │ │ ├── ClientImageInspectCommandResponder.cs │ │ ├── StackLsResponseParser.cs │ │ ├── MachineLsResponseParser.cs │ │ ├── ClientImagesResponseParser.cs │ │ ├── MachineEnvResponseParser.cs │ │ ├── StackPsResponseParser.cs │ │ ├── ClientDiffResponseParser.cs │ │ ├── ClientContainerInspectCommandResponder.cs │ │ ├── BaseInfoResponseParser.cs │ │ ├── MachineInspectResponseParser.cs │ │ └── ClientTopResponseParser.cs │ ├── Mappers │ │ └── StringMapper.cs │ ├── IStreamMapper.cs │ ├── StreamProcessExecutor.cs │ ├── ProcessExecutionResult.cs │ ├── ProcessExecutor.cs │ └── AsyncProcessExecutor.cs ├── Builders │ ├── RepositoryBuilder.cs │ ├── IBuilder.cs │ ├── HostBuilder.cs │ ├── BaseBuilder.cs │ ├── MachineBuilder.cs │ └── VolumeBuilder.cs └── Commands │ ├── CommandDefaults.cs │ ├── ClientStreams.cs │ └── Info.cs ├── .circleci └── config.yml ├── FluentDocker.sln.DotSettings ├── Tests ├── Tests.csproj └── Program.cs ├── appveyor.yml ├── FluentDocker.Features └── FluentDocker.Features.Elk │ └── FluentDocker.Features.Elk.csproj ├── .vscode ├── tasks.json └── launch.json ├── Ductus.FluentDocker.MsTest ├── FluentDockerTestBase.cs ├── PostgresTestBase.cs ├── FluentDockerComposeTestBase.cs └── Ductus.FluentDocker.MsTest.csproj ├── data └── docker_network_inspect.json └── .gitattributes /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-hacker -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/MultiContainerTestFiles/app.js: -------------------------------------------------------------------------------- 1 | test; -------------------------------------------------------------------------------- /keypair.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/volkovku/FluentDocker/master/keypair.snk -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Builders/ICommand.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Builders 2 | { 3 | public interface ICommand 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/ISecret.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Compose 2 | { 3 | public interface ISecret 4 | { 5 | 6 | } 7 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Compose/NsResolver.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Tests.Compose 2 | { 3 | public sealed class NsResolver 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/IPortsDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Compose 2 | { 3 | public interface IPortsDefinition 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/PortMode.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Compose 2 | { 3 | public enum PortMode 4 | { 5 | Host, 6 | Ingress 7 | } 8 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/MultiContainerTestFiles/NsResolver.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Tests.MultiContainerTestFiles 2 | { 3 | public class NsResolver 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/IServiceVolumeDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Compose 2 | { 3 | public interface IServiceVolumeDefinition 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Builders/MountType.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Builders 2 | { 3 | public enum MountType 4 | { 5 | ReadOnly = 0, 6 | ReadWrite = 1 7 | } 8 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/VolumeType.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Compose 2 | { 3 | public enum VolumeType 4 | { 5 | Volume, 6 | Bind, 7 | TmpFs 8 | } 9 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Common/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Common 2 | { 3 | internal static class Constants 4 | { 5 | internal const string DebugCategory = "Ductus.FluentDocker"; 6 | } 7 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/ComposeVolumeDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Compose 2 | { 3 | public sealed class ComposeVolumeDefinition 4 | { 5 | public string Name { get; set; } 6 | } 7 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/ContainerIsolationType.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Compose 2 | { 3 | public enum ContainerIsolationType 4 | { 5 | Default, 6 | Process, 7 | HyperV 8 | } 9 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Containers/Diff.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Containers 2 | { 3 | public sealed class Diff 4 | { 5 | public DiffType Type { get; set; } 6 | public string Item { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Images/ImageRemovalOption.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Images 2 | { 3 | public enum ImageRemovalOption 4 | { 5 | None = 0, 6 | Local = 1, 7 | All = 2 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Containers/DiffType.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Containers 2 | { 3 | public enum DiffType 4 | { 5 | Added = 1, 6 | Removed = 2, 7 | Updated = 3, 8 | Created = 4 9 | } 10 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Containers/UnixSignal.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable InconsistentNaming 2 | namespace Ductus.FluentDocker.Model.Containers 3 | { 4 | public enum UnixSignal 5 | { 6 | SIGHUP, 7 | SIGTERM, 8 | SIGKILL 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Networks/IpamConfig.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Networks 2 | { 3 | public sealed class IpamConfig 4 | { 5 | public string Subnet { get; set; } 6 | public string Gateway { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/ResourcesItemDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Compose 2 | { 3 | public sealed class ResourcesItemDefinition 4 | { 5 | public string Cpus { get; set; } 6 | public string Memory { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Compose/nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | # Set nginx base image 2 | FROM nginx:1.13.6-alpine 3 | 4 | # File Author / Maintainer 5 | MAINTAINER Anand Mani Sankar 6 | 7 | # Copy custom configuration file from the current directory 8 | COPY nginx.conf /etc/nginx/nginx.conf 9 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Containers/ContainerIsolationTechnology.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Containers 2 | { 3 | public enum ContainerIsolationTechnology 4 | { 5 | Unknown = 0, 6 | Default = 1, 7 | Process = 2, 8 | Hyperv = 3 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Compose/node/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mhart/alpine-node 2 | MAINTAINER Mario Toffia 3 | 4 | WORKDIR /src 5 | ADD index.js /src 6 | ADD package.txt /src/package.json 7 | 8 | RUN npm install 9 | 10 | EXPOSE 8080 11 | CMD ["node", "index.js"] -------------------------------------------------------------------------------- /Ductus.FluentDocker/Resources/IResourceWriter.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace Ductus.FluentDocker.Resources 4 | { 5 | public interface IResourceWriter 6 | { 7 | IResourceWriter Write(ResourceStream stream); 8 | IResourceWriter Write(ResourceReader resources); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Images/DockerImageRowResponse.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Images 2 | { 3 | public sealed class DockerImageRowResponse 4 | { 5 | public string Id { get; set; } 6 | public string Name { get; set; } 7 | public string []Tags { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Compose/node/package.txt: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "Anand Mani Sankar", 7 | "license": "ISC", 8 | "dependencies": { 9 | "express": "^4.13.4", 10 | "redis": "^2.6.0-2" 11 | } 12 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Networks/Ipam.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Ductus.FluentDocker.Model.Networks 4 | { 5 | public sealed class Ipam 6 | { 7 | public string Driver { get; set; } 8 | 9 | //TODO: "Options": null 10 | public IList Config { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Services/ServiceRunningState.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Services 2 | { 3 | public enum ServiceRunningState 4 | { 5 | Unknown = 0, 6 | Starting = 1, 7 | Running = 2, 8 | Paused = 3, 9 | Stopping = 4, 10 | Stopped = 5, 11 | Removing = 6, 12 | Removed = 7 13 | } 14 | } -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | working_directory: /temp 5 | docker: 6 | - image: microsoft/dotnet:sdk 7 | environment: 8 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 9 | DOTNET_CLI_TELEMETRY_OPTOUT: 1 10 | steps: 11 | - checkout 12 | - run: dotnet restore 13 | - run: dotnet build -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Machines/MachineLsResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Ductus.FluentDocker.Services; 3 | 4 | namespace Ductus.FluentDocker.Model.Machines 5 | { 6 | public sealed class MachineLsResponse 7 | { 8 | public string Name { get; set; } 9 | public ServiceRunningState State { get; set; } 10 | public Uri Docker { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/MultiContainerTestFiles/package.txt: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "Anand Mani Sankar", 7 | "license": "ISC", 8 | "dependencies": { 9 | "express": "^4.12.3", 10 | "hiredis": "^0.2.0", 11 | "mocha": "^2.2.1", 12 | "redis": "^0.12.1" 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Machines/MachineAuthConfig.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Machines 2 | { 3 | public sealed class MachineAuthConfig 4 | { 5 | public string CertDir { get; set; } 6 | public string CaCertPath { get; set; } 7 | public string ClientKeyPath { get; set; } 8 | public string ClientCertPath { get; set; } 9 | public string StorePath { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Stacks/Orchestrator.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Stacks 2 | { 3 | public enum Orchestrator 4 | { 5 | /// 6 | /// All orchestrator. 7 | /// 8 | All, 9 | /// 10 | /// Docker Swarm 11 | /// 12 | Swarm, 13 | /// 14 | /// Kubernetes 15 | /// 16 | Kubernetes 17 | } 18 | } -------------------------------------------------------------------------------- /FluentDocker.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | True -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Compose/redis/Dockerfile: -------------------------------------------------------------------------------- 1 | # Set the base image to Ubuntu 2 | FROM ubuntu 3 | 4 | # File Author / Maintainer 5 | MAINTAINER Anand Mani Sankar 6 | 7 | # Update the repository and install Redis Server 8 | RUN apt-get update && apt-get install -y redis-server 9 | 10 | # Expose Redis port 6379 11 | EXPOSE 6379 12 | 13 | # Run Redis Server 14 | ENTRYPOINT ["/usr/bin/redis-server"] -------------------------------------------------------------------------------- /Ductus.FluentDocker/Extensions/OsExtensions.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Common; 2 | 3 | namespace Ductus.FluentDocker.Extensions 4 | { 5 | public static class OsExtensions 6 | { 7 | public static string ToMsysPath(this string path) 8 | { 9 | if (!FdOs.IsWindows()) 10 | return path; 11 | 12 | return "//" + char.ToLower(path[0]) + path.Substring(2).Replace('\\', '/'); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/IProcessResponseParser.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Model.Containers; 2 | 3 | namespace Ductus.FluentDocker.Executors 4 | { 5 | public interface IProcessResponse 6 | { 7 | CommandResponse Response { get; } 8 | } 9 | 10 | public interface IProcessResponseParser : IProcessResponse 11 | { 12 | IProcessResponse Process(ProcessExecutionResult response); 13 | } 14 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Resources/ResourceInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace Ductus.FluentDocker.Resources 4 | { 5 | public sealed class ResourceInfo 6 | { 7 | public string Resource { get; set; } 8 | public string Namespace { get; set; } 9 | public string Root { get; set; } 10 | public string RelativeRootNamespace { get; set; } 11 | public Assembly Assembly { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Builders/AddCommand.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Model.Common; 2 | 3 | namespace Ductus.FluentDocker.Model.Builders 4 | { 5 | public sealed class AddCommand : ICommand 6 | { 7 | public TemplateString Source { get; set; } 8 | public TemplateString Destination { get; set; } 9 | 10 | public override string ToString() 11 | { 12 | return $"ADD {Source} {Destination}"; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Builders/ImageBuilderConfig.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Model.Containers; 2 | 3 | namespace Ductus.FluentDocker.Model.Builders 4 | { 5 | public sealed class ImageBuilderConfig 6 | { 7 | public bool VerifyExistence { get; set; } 8 | public string ImageName { get; set; } 9 | public ContainerBuildParams Params { get; } = new ContainerBuildParams(); 10 | public bool IsWindowsHost { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Volumes/Volume.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ductus.FluentDocker.Model.Volumes 4 | { 5 | public sealed class Volume 6 | { 7 | public DateTime Created { get; set; } 8 | public string Driver { get; set; } 9 | //public object Labels { get; set; } 10 | public string Name { get; set; } 11 | public string Scope { get; set; } 12 | //public object Options { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Services/StateChangeEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ductus.FluentDocker.Services 4 | { 5 | public sealed class StateChangeEventArgs : EventArgs 6 | { 7 | internal StateChangeEventArgs(IService service, ServiceRunningState state) 8 | { 9 | Service = service; 10 | State = state; 11 | } 12 | 13 | public IService Service { get; } 14 | public ServiceRunningState State { get; } 15 | } 16 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Services/IVolumeService.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Model.Common; 2 | using Ductus.FluentDocker.Model.Containers; 3 | using Ductus.FluentDocker.Model.Volumes; 4 | 5 | namespace Ductus.FluentDocker.Services 6 | { 7 | public interface IVolumeService : IService 8 | { 9 | DockerUri DockerHost { get; } 10 | ICertificatePaths Certificates { get; } 11 | 12 | Volume GetConfiguration(bool fresh = false); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Builders/RunCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Ductus.FluentDocker.Model.Common; 4 | 5 | namespace Ductus.FluentDocker.Model.Builders 6 | { 7 | public sealed class RunCommand : ICommand 8 | { 9 | public IList Lines { get; set; } 10 | 11 | public override string ToString() 12 | { 13 | return "RUN " + string.Join($"\\{Environment.NewLine}", Lines); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/UlimitDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Compose 2 | { 3 | /// 4 | /// Override the default ulimits for a container. 5 | /// 6 | /// 7 | /// Specify same value to render a single line in the compose file. 8 | /// 9 | public sealed class UlimitDefinition 10 | { 11 | public long MappingSoft { get; set; } 12 | public long MappingHard { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /Tests/Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.0 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Common/FluentDockerException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ductus.FluentDocker.Common 4 | { 5 | public class FluentDockerException : Exception 6 | { 7 | public FluentDockerException() 8 | { 9 | } 10 | 11 | public FluentDockerException(string message) : base(message) 12 | { 13 | } 14 | 15 | public FluentDockerException(string message, Exception innerException) : base(message, innerException) 16 | { 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | #version: 0.0.0.{build} 2 | version: 2.6.6 3 | skip_non_tags: true 4 | image: Visual Studio 2017 5 | configuration: Release 6 | dotnet_csproj: 7 | patch: true 8 | file: '**\*.csproj' 9 | version: '{version}' 10 | package_version: '{version}' 11 | assembly_version: '{version}' 12 | file_version: '{version}' 13 | informational_version: '{version}' 14 | build: 15 | publish_nuget: false 16 | verbosity: minimal 17 | before_build: 18 | - nuget restore 19 | test: off -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Builders/HostBuilderConfig.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Builders 2 | { 3 | public sealed class HostBuilderConfig 4 | { 5 | public bool UseNative { get; set; } = false; 6 | public string Name { get; set; } = "default"; 7 | public int MemoryMb { get; set; } = 1024; 8 | public int CpuCount { get; set; } = 1; 9 | public string Driver { get; set; } = "virtualbox"; 10 | public int StorageSizeMb { get; set; } = 20*1024*1024; 11 | } 12 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Services/ICompositeService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Ductus.FluentDocker.Services 4 | { 5 | public interface ICompositeService : IService 6 | { 7 | IReadOnlyCollection Hosts { get; } 8 | IReadOnlyCollection Containers { get; } 9 | IReadOnlyCollection Images { get; } 10 | IReadOnlyCollection Services { get; } 11 | new ICompositeService Start(); 12 | } 13 | } -------------------------------------------------------------------------------- /FluentDocker.Features/FluentDocker.Features.Elk/FluentDocker.Features.Elk.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;net462 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "taskName": "build", 8 | "command": "dotnet build", 9 | "type": "shell", 10 | "group": "build", 11 | "presentation": { 12 | "reveal": "silent" 13 | }, 14 | "problemMatcher": "$msCompile" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Containers/CertificatePaths.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Containers 2 | { 3 | public interface ICertificatePaths 4 | { 5 | string CaCertificate { get; } 6 | string ClientCertificate { get; } 7 | string ClientKey { get; } 8 | } 9 | 10 | public sealed class CertificatePaths : ICertificatePaths 11 | { 12 | public string CaCertificate { get; set; } 13 | public string ClientCertificate { get; set; } 14 | public string ClientKey { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Containers/ContainerMount.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable InconsistentNaming 2 | 3 | namespace Ductus.FluentDocker.Model.Containers 4 | { 5 | public sealed class ContainerMount 6 | { 7 | public string Name { get; set; } 8 | public string Source { get; set; } 9 | public string Destination { get; set; } 10 | public string Driver { get; set; } 11 | public string Mode { get; set; } 12 | public bool RW { get; set; } 13 | public string Propagation { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Resources/ResourceStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace Ductus.FluentDocker.Resources 5 | { 6 | public sealed class ResourceStream : IDisposable 7 | { 8 | public ResourceStream(Stream stream, ResourceInfo info) 9 | { 10 | Stream = stream; 11 | Info = info; 12 | } 13 | 14 | public Stream Stream { get; } 15 | public ResourceInfo Info { get; } 16 | public void Dispose() 17 | { 18 | Stream.Dispose(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Networks/NetworkRow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ductus.FluentDocker.Model.Networks 4 | { 5 | public sealed class NetworkRow 6 | { 7 | public string Id { get; set; } 8 | public string Name { get; set; } 9 | public string Driver { get; set; } 10 | public string Scope { get; set; } 11 | 12 | // ReSharper disable once InconsistentNaming 13 | public bool IPv6 { get; set; } 14 | 15 | public bool Internal { get; set; } 16 | public DateTime Created { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Common/Result.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Common 2 | { 3 | public sealed class Result 4 | { 5 | internal Result(bool success, T value, string log, string error) 6 | { 7 | Value = value; 8 | IsSuccess = success; 9 | IsFailure = !success; 10 | Log = log; 11 | Error = error; 12 | } 13 | 14 | public bool IsSuccess { get; } 15 | public bool IsFailure { get; } 16 | public T Value { get; } 17 | public string Log { get; } 18 | public string Error { get; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/Parsers/IgnoreErrorResponseParser.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Model.Containers; 2 | 3 | namespace Ductus.FluentDocker.Executors.Parsers 4 | { 5 | public sealed class IgnoreErrorResponseParser : IProcessResponseParser 6 | { 7 | public CommandResponse Response { get; private set; } 8 | 9 | public IProcessResponse Process(ProcessExecutionResult response) 10 | { 11 | Response = response.ToResponse(true, string.Empty, $"ExitCode={response.ExitCode}"); 12 | return this; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/DockerComposeFileConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Ductus.FluentDocker.Model.Containers; 3 | 4 | namespace Ductus.FluentDocker.Model.Compose 5 | { 6 | public sealed class DockerComposeFileConfig : DockerComposeConfig 7 | { 8 | public string Version { get; set; } = "3.3"; 9 | public IList ServiceDefinitions { get; } = new List(); 10 | public IList VolumeDefinitions { get; } = new List(); 11 | } 12 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/ConfigurationDefinition.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Ductus.FluentDocker.Model.Compose 4 | { 5 | /// 6 | /// 7 | /// 8 | /// 9 | /// Note: config definitions are only supported in version 3.3 and higher of the compose file format. 10 | /// 11 | public sealed class ConfigurationDefinition 12 | { 13 | public IDictionary Items { get; set; } = 14 | new Dictionary(); 15 | } 16 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/ConfigurationItemDefinition.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Ductus.FluentDocker.Model.Compose 4 | { 5 | /// 6 | /// 7 | /// 8 | /// 9 | /// Note: config definitions are only supported in version 3.3 and higher of the compose file format. 10 | /// 11 | public sealed class ConfigurationItemDefinition 12 | { 13 | public string Name { get; set; } 14 | public IDictionary NameValues { get; set; } = new Dictionary(); 15 | } 16 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Common/Option.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Common 2 | { 3 | public sealed class Option where T : class 4 | { 5 | public Option(T value) 6 | { 7 | Value = value; 8 | HasValue = null != value; 9 | } 10 | 11 | public T Value { get; } 12 | public bool HasValue { get; } 13 | 14 | public static implicit operator T(Option option) 15 | { 16 | return option.Value; 17 | } 18 | 19 | public static explicit operator Option(T value) 20 | { 21 | return new Option(value); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Networks/NetworkedContainer.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Networks 2 | { 3 | public sealed class NetworkedContainer 4 | { 5 | public string Name { get; set; } 6 | 7 | // ReSharper disable once InconsistentNaming 8 | public string EndpointID { get; set; } 9 | 10 | public string MacAddress { get; set; } 11 | 12 | // ReSharper disable once InconsistentNaming 13 | public string IPv4Address { get; set; } 14 | 15 | // ReSharper disable once InconsistentNaming 16 | public string IPv6Address { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/Mappers/StringMapper.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Executors.Mappers 2 | { 3 | public sealed class StringMapper : IStreamMapper 4 | { 5 | public string OnData(string data, bool isStdErr) 6 | { 7 | return data; 8 | } 9 | 10 | public string OnProcessEnd(int exitCode) 11 | { 12 | if (exitCode != 0) 13 | { 14 | Error = $"Process exited with exit code {exitCode}"; 15 | } 16 | 17 | return null; 18 | } 19 | 20 | public string Error { get; private set; } = string.Empty; 21 | } 22 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Compose/docker-compose.yml: -------------------------------------------------------------------------------- 1 | nginx: 2 | build: ./nginx 3 | links: 4 | - node1:node1 5 | - node2:node2 6 | - node3:node3 7 | ports: 8 | - "8090:80" 9 | node1: 10 | build: ./node 11 | links: 12 | - redis 13 | ports: 14 | - "8080" 15 | node2: 16 | build: ./node 17 | links: 18 | - redis 19 | ports: 20 | - "8080" 21 | node3: 22 | build: ./node 23 | links: 24 | - redis 25 | ports: 26 | - "8080" 27 | redis: 28 | image: redis 29 | ports: 30 | - "6379" -------------------------------------------------------------------------------- /Ductus.FluentDocker/Services/Logging.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Common; 2 | 3 | namespace Ductus.FluentDocker.Services 4 | { 5 | /// 6 | /// Controls Logging. 7 | /// 8 | public static class Logging 9 | { 10 | /// 11 | /// Enables logging. 12 | /// 13 | public static void Enabled() 14 | { 15 | Logger.Enabled = true; 16 | } 17 | 18 | /// 19 | /// Disables logging. 20 | /// 21 | public static void Disabled() 22 | { 23 | Logger.Enabled = false; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/CommandTests/DockerInfoCommandTests.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Ductus.FluentDocker.Commands; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace Ductus.FluentDocker.Tests.CommandTests 6 | { 7 | [TestClass] 8 | public class DockerInfoCommandTests : FluentDockerTestBase 9 | { 10 | [TestMethod] 11 | public void GetServerClientVersionInfoShallSucceed() 12 | { 13 | var result = DockerHost.Host.Version(DockerHost.Certificates); 14 | Assert.IsTrue(result.Success); 15 | Debug.WriteLine(result.Data.ToString()); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Common/RequestResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Http.Headers; 4 | 5 | namespace Ductus.FluentDocker.Common 6 | { 7 | public struct RequestResponse 8 | { 9 | internal RequestResponse(HttpResponseHeaders headers, HttpStatusCode code, string body, Exception err) 10 | { 11 | Headers = headers; 12 | Code = code; 13 | Body = body; 14 | Err = err; 15 | } 16 | 17 | public HttpResponseHeaders Headers { get; } 18 | public HttpStatusCode Code { get; } 19 | public string Body { get; } 20 | public Exception Err { get; } 21 | } 22 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/Parsers/NoLineResponseParser.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Model.Containers; 2 | 3 | namespace Ductus.FluentDocker.Executors.Parsers 4 | { 5 | public sealed class NoLineResponseParser : IProcessResponseParser 6 | { 7 | public CommandResponse Response { get; private set; } 8 | 9 | public IProcessResponse Process(ProcessExecutionResult response) 10 | { 11 | Response = response.ExitCode != 0 12 | ? response.ToErrorResponse(string.Empty) 13 | : response.ToResponse(true, string.Empty, string.Empty); 14 | return this; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/DockerInfoBase.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model 2 | { 3 | public sealed class DockerInfoBase 4 | { 5 | public string ClientVersion { get; set; } 6 | public string ClientApiVersion { get; set; } 7 | public string ServerVersion { get; set; } 8 | public string ServerApiVersion { get; set; } 9 | public string ServerOs { get; set; } 10 | 11 | public override string ToString() 12 | { 13 | return 14 | $"Client.Version = {ClientVersion} Client.ApiVersion = {ClientApiVersion} Server.Version = {ServerVersion} Server.ApiVersion = {ServerApiVersion} Server.Os = {ServerOs}"; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Services/Extensions/HostExtensions.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Commands; 2 | using Ductus.FluentDocker.Common; 3 | using Ductus.FluentDocker.Model.Containers; 4 | 5 | namespace Ductus.FluentDocker.Services.Extensions 6 | { 7 | public static class HostExtensions 8 | { 9 | public static Result Build(this IHostService host, string name, string tag, string workdir = null, 10 | ContainerBuildParams prms = null) 11 | { 12 | var res = host.Host.Build(name, tag, workdir, prms, host.Certificates); 13 | return res.Success ? res.Data[0].ToSuccess() : string.Empty.ToFailure(res.Error, res.Log); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Extensions/ComparisonExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ductus.FluentDocker.Extensions 4 | { 5 | public static class ComparisonExtensions 6 | { 7 | public static bool IsApproximatelyEqualTo(this double initialValue, double value, 8 | double maximumDifferenceAllowed = 0.00001d) 9 | { 10 | return Math.Abs(initialValue - value) < maximumDifferenceAllowed; 11 | } 12 | 13 | public static bool IsApproximatelyEqualTo(this float initialValue, float value, 14 | float maximumDifferenceAllowed = 0.00001f) 15 | { 16 | return Math.Abs(initialValue - value) < maximumDifferenceAllowed; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Containers/BridgeNetwork.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable InconsistentNaming 2 | 3 | namespace Ductus.FluentDocker.Model.Containers 4 | { 5 | public sealed class BridgeNetwork 6 | { 7 | public string []Aliases { get; set; } 8 | public string NetworkID { get; set; } 9 | public string EndpointID { get; set; } 10 | public string Gateway { get; set; } 11 | public string IPAddress { get; set; } 12 | public int IPPrefixLen { get; set; } 13 | public string IPv6Gateway { get; set; } 14 | public string GlobalIPv6Address { get; set; } 15 | public int GlobalIPv6PrefixLen { get; set; } 16 | public string MacAddress { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Services/IService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ductus.FluentDocker.Services 4 | { 5 | public sealed class ServiceDelegates 6 | { 7 | public delegate void StateChange(object sender, StateChangeEventArgs evt); 8 | } 9 | 10 | public interface IService : IDisposable 11 | { 12 | string Name { get; } 13 | ServiceRunningState State { get; } 14 | void Start(); 15 | void Stop(); 16 | void Remove(bool force = false); 17 | IService AddHook(ServiceRunningState state, Action hook, string uniqueName = null); 18 | IService RemoveHook(string uniqueName); 19 | 20 | event ServiceDelegates.StateChange StateChange; 21 | } 22 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Resources/StackTests/WordPress/stack.yml: -------------------------------------------------------------------------------- 1 | version: '3.1' 2 | services: 3 | db: 4 | image: mysql:5.7 5 | volumes: 6 | - db_data:/var/lib/mysql 7 | restart: always 8 | environment: 9 | MYSQL_ROOT_PASSWORD: somewordpress 10 | MYSQL_DATABASE: wordpress 11 | MYSQL_USER: wordpress 12 | MYSQL_PASSWORD: wordpress 13 | 14 | wordpress: 15 | depends_on: 16 | - db 17 | image: wordpress:latest 18 | ports: 19 | - "8000:80" 20 | restart: always 21 | environment: 22 | WORDPRESS_DB_HOST: db:3306 23 | WORDPRESS_DB_USER: wordpress 24 | WORDPRESS_DB_PASSWORD: wordpress 25 | volumes: 26 | db_data: -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/Parsers/SingleStringResponseParser.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Model.Containers; 2 | 3 | namespace Ductus.FluentDocker.Executors.Parsers 4 | { 5 | public sealed class SingleStringResponseParser : IProcessResponseParser 6 | { 7 | public CommandResponse Response { get; private set; } 8 | 9 | public IProcessResponse Process(ProcessExecutionResult response) 10 | { 11 | var arr = response.StdOutAsArry; 12 | 13 | Response = arr.Length == 0 14 | ? response.ToResponse(false, "No line", string.Empty) 15 | : response.ToResponse(true, string.Empty, arr[0]); 16 | 17 | return this; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Stacks/StackLsResponse.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Stacks 2 | { 3 | public sealed class StackLsResponse 4 | { 5 | public string Name { get; set; } 6 | public int Services { get; set; } 7 | public Orchestrator Orchestrator { get; set; } 8 | public string Namespace { get; set; } 9 | 10 | public static Orchestrator ToOrchestrator(string value) 11 | { 12 | if (string.IsNullOrEmpty(value)) return Orchestrator.All; 13 | 14 | value = value.ToLower(); 15 | 16 | if (value.Equals("kubernetes")) return Orchestrator.Kubernetes; 17 | return value.Equals("swarm") ? Orchestrator.Swarm : Orchestrator.All; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Resources/ComposeTests/WordPress/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.3' 2 | 3 | services: 4 | db: 5 | image: mysql:5.7 6 | volumes: 7 | - db_data:/var/lib/mysql 8 | restart: always 9 | environment: 10 | MYSQL_ROOT_PASSWORD: somewordpress 11 | MYSQL_DATABASE: wordpress 12 | MYSQL_USER: wordpress 13 | MYSQL_PASSWORD: wordpress 14 | 15 | wordpress: 16 | depends_on: 17 | - db 18 | image: wordpress:latest 19 | ports: 20 | - "8000:80" 21 | restart: always 22 | environment: 23 | WORDPRESS_DB_HOST: db:3306 24 | WORDPRESS_DB_USER: wordpress 25 | WORDPRESS_DB_PASSWORD: wordpress 26 | volumes: 27 | db_data: -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Containers/ContainerState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | // ReSharper disable InconsistentNaming 4 | 5 | namespace Ductus.FluentDocker.Model.Containers 6 | { 7 | public sealed class ContainerState 8 | { 9 | public string Status { get; set; } 10 | public bool Running { get; set; } 11 | public bool Paused { get; set; } 12 | public bool Restarting { get; set; } 13 | public bool OOMKilled { get; set; } 14 | public bool Dead { get; set; } 15 | public int Pid { get; set; } 16 | public int ExitCode { get; set; } 17 | public string Error { get; set; } 18 | public DateTime StartedAt { get; set; } 19 | public DateTime FinishedAt { get; set; } 20 | } 21 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/MultiContainerTestFiles/nginx.conf: -------------------------------------------------------------------------------- 1 | 2 | user nginx; 3 | worker_processes 4; 4 | 5 | events { worker_connections 1024; } 6 | 7 | http { 8 | 9 | upstream node-app { 10 | least_conn; 11 | server node1:8080 weight=10 max_fails=3 fail_timeout=30s; 12 | server node2:8080 weight=10 max_fails=3 fail_timeout=30s; 13 | } 14 | 15 | server { 16 | listen 80; 17 | 18 | location / { 19 | proxy_pass http://node-app; 20 | proxy_http_version 1.1; 21 | proxy_set_header Upgrade $http_upgrade; 22 | proxy_set_header Connection 'upgrade'; 23 | proxy_set_header Host $host; 24 | proxy_cache_bypass $http_upgrade; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/Parsers/MachineStartStopResponseParser.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Ductus.FluentDocker.Model.Containers; 3 | 4 | namespace Ductus.FluentDocker.Executors.Parsers 5 | { 6 | public sealed class MachineStartStopResponseParser : IProcessResponseParser 7 | { 8 | public CommandResponse Response { get; private set; } 9 | 10 | public IProcessResponse Process(ProcessExecutionResult response) 11 | { 12 | var success = 13 | response.StdOutAsArry.All( 14 | line => !line.StartsWith("Host does not exist") && !line.StartsWith("Incorrect Usage.")); 15 | 16 | Response = response.ToResponse(success, string.Empty, string.Empty); 17 | return this; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Containers/RestartPolicy.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Containers 2 | { 3 | public enum RestartPolicy 4 | { 5 | /// 6 | /// Do not automatically restart the container. (the default). 7 | /// 8 | No = 0, 9 | /// 10 | /// Restart the container if it exits due to an error, which manifests as a non-zero exit code. 11 | /// 12 | OnFailure = 1, 13 | /// 14 | /// Restart the container unless it is explicitly stopped or Docker itself is stopped or restarted. 15 | /// 16 | UnlessStopped = 2, 17 | /// 18 | /// Always restart the container if it stops. 19 | /// 20 | Always = 3 21 | } 22 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/IStreamMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ductus.FluentDocker.Executors 4 | { 5 | public interface IStreamMapper where T : class 6 | { 7 | /// 8 | /// Invoked by the each time it gets new data. 9 | /// 10 | /// The data from the stdin or stderr from the spawned process. 11 | /// It is set to false when stdin, otherwise it is stderr. 12 | /// When data satisifies the type T it will be returned, otherwise null is returned. 13 | T OnData(string data, bool isStdErr); 14 | 15 | T OnProcessEnd(int exitCode); 16 | 17 | string Error { get; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/Parsers/MachineRmResponseParser.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Ductus.FluentDocker.Model.Containers; 3 | 4 | namespace Ductus.FluentDocker.Executors.Parsers 5 | { 6 | public sealed class MachineRmResponseParser : IProcessResponseParser 7 | { 8 | public CommandResponse Response { get; private set; } 9 | 10 | public IProcessResponse Process(ProcessExecutionResult response) 11 | { 12 | var success = 13 | response.StdOutAsArry.All( 14 | line => !line.StartsWith("Error") && !line.StartsWith("Can't remove") && !line.StartsWith("Incorrect Usage.")); 15 | 16 | Response = response.ToResponse(success, string.Empty, string.Empty); 17 | return this; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Machines/MachineConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace Ductus.FluentDocker.Model.Machines 4 | { 5 | public sealed class MachineConfiguration 6 | { 7 | public string Name { get; set; } 8 | public string DriverName { get; set; } 9 | public string StorePath { get; set; } 10 | 11 | /// 12 | /// Gets or sets the ip address. If none is set. 13 | /// 14 | public IPAddress IpAddress { get; set; } 15 | 16 | public int MemorySizeMb { get; set; } 17 | public int StorageSizeMb { get; set; } 18 | public int CpuCount { get; set; } 19 | public bool RequireTls { get; set; } 20 | public MachineAuthConfig AuthConfig { get; set; } 21 | } 22 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Compose/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | worker_processes 4; 2 | 3 | events { worker_connections 1024; } 4 | 5 | http { 6 | 7 | upstream node-app { 8 | least_conn; 9 | server node1:8080 weight=10 max_fails=3 fail_timeout=30s; 10 | server node2:8080 weight=10 max_fails=3 fail_timeout=30s; 11 | server node3:8080 weight=10 max_fails=3 fail_timeout=30s; 12 | } 13 | 14 | server { 15 | listen 80; 16 | 17 | location / { 18 | proxy_pass http://node-app; 19 | proxy_http_version 1.1; 20 | proxy_set_header Upgrade $http_upgrade; 21 | proxy_set_header Connection 'upgrade'; 22 | proxy_set_header Host $host; 23 | proxy_cache_bypass $http_upgrade; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Containers/Container.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Containers 2 | { 3 | public sealed class Container 4 | { 5 | public string Image { get; set; } 6 | public string ResolvConfPath { get; set; } 7 | public string HostnamePath { get; set; } 8 | public string HostsPath { get; set; } 9 | public string LogPath { get; set; } 10 | public string Name { get; set; } 11 | public int RestartCount { get; set; } 12 | public string Driver { get; set; } 13 | public string[] Args { get; set; } 14 | public ContainerState State { get; set; } 15 | public ContainerMount[] Mounts { get; set; } 16 | public ContainerConfig Config { get; set; } 17 | public ContainerNetworkSettings NetworkSettings { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Extensions/CompressionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using SharpCompress.Common; 3 | using SharpCompress.Readers; 4 | 5 | namespace Ductus.FluentDocker.Extensions 6 | { 7 | public static class CompressionExtensions 8 | { 9 | public static void UnTar(this string file, string destPath) 10 | { 11 | using (var stream = File.OpenRead(file)) 12 | { 13 | using (var reader = ReaderFactory.Open(stream)) 14 | { 15 | while (reader.MoveToNextEntry()) 16 | { 17 | if (!reader.Entry.IsDirectory) 18 | { 19 | reader.WriteEntryToDirectory(destPath, new ExtractionOptions { ExtractFullPath = true, Overwrite = true}); 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Services/INetworkService.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Model.Common; 2 | using Ductus.FluentDocker.Model.Containers; 3 | using Ductus.FluentDocker.Model.Networks; 4 | 5 | namespace Ductus.FluentDocker.Services 6 | { 7 | public interface INetworkService : IService 8 | { 9 | string Id { get; } 10 | DockerUri DockerHost { get; } 11 | ICertificatePaths Certificates { get; } 12 | NetworkConfiguration GetConfiguration(bool fresh = false); 13 | 14 | INetworkService Attach(IContainerService container, bool detatchOnDisposeNetwork); 15 | INetworkService Attach(string containerId, bool detatchOnDisposeNetwork); 16 | INetworkService Detatch(IContainerService container, bool force = false); 17 | INetworkService Detatch(string containerId, bool force = false); 18 | } 19 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Common/SudoMechanism.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Common; 2 | 3 | namespace Ductus.FluentDocker.Model.Common 4 | { 5 | /// 6 | /// Sets the sudo mechanism the library shall use to talk to the docker daemon. 7 | /// 8 | [Experimental] 9 | public enum SudoMechanism 10 | { 11 | /// 12 | /// No sudo needed to talk to the docker daemon. 13 | /// 14 | None = 0, 15 | /// 16 | /// Sudo is needed but no password needs to be provided. The user do have NOPASSWD in /etc/sudoer. 17 | /// 18 | NoPassword = 1, 19 | /// 20 | /// Sudo is needed and a password must be provided that will be piped in the stdin to sudo using sudo -S. 21 | /// 22 | Password = 2 23 | } 24 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/Parsers/StringListResponseParser.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Ductus.FluentDocker.Model.Containers; 3 | 4 | namespace Ductus.FluentDocker.Executors.Parsers 5 | { 6 | public sealed class StringListResponseParser : IProcessResponseParser> 7 | { 8 | public CommandResponse> Response { get; private set; } 9 | 10 | public IProcessResponse> Process(ProcessExecutionResult response) 11 | { 12 | if (response.ExitCode != 0) 13 | { 14 | Response = response.ToErrorResponse((IList) new List()); 15 | return this; 16 | } 17 | 18 | Response = response.ToResponse(true, string.Empty, (IList) new List(response.StdOutAsArry)); 19 | return this; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/TmpFsDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Compose 2 | { 3 | /// 4 | /// Definition of a temporary filesystem. 5 | /// 6 | /// 7 | /// Note: Version 3.6 file format and up when using other than . 8 | /// 9 | public sealed class TmpFsDefinition 10 | { 11 | /// 12 | /// Specifies the type. Default is tmpfs. 13 | /// 14 | public string Type { get; set; } = "tmpfs"; 15 | /// 16 | /// The target mount point e.g. /app. 17 | /// 18 | public string Target { get; set; } 19 | /// 20 | /// specifies the size of the tmpfs mount in bytes. Unlimited by default. 21 | /// 22 | public long Size { get; set; } = -1; 23 | } 24 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Containers/CommandResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Ductus.FluentDocker.Model.Containers 5 | { 6 | public sealed class CommandResponse 7 | { 8 | public CommandResponse(bool success, IList log, string error = "", T data = default(T)) 9 | { 10 | Success = success; 11 | Log = log; 12 | Error = error; 13 | Data = data; 14 | } 15 | 16 | public bool Success { get; private set; } 17 | public IList Log { get; } 18 | public string Error { get; private set; } 19 | public T Data { get; } 20 | 21 | public override string ToString() 22 | { 23 | if (null == Log) 24 | { 25 | return base.ToString(); 26 | } 27 | 28 | return string.Join(Environment.NewLine, Log); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/Parsers/MachineCreateResponseParser.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Ductus.FluentDocker.Model.Containers; 3 | 4 | namespace Ductus.FluentDocker.Executors.Parsers 5 | { 6 | public sealed class MachineCreateResponseParser : IProcessResponseParser 7 | { 8 | public CommandResponse Response { get; private set; } 9 | 10 | public IProcessResponse Process(ProcessExecutionResult response) 11 | { 12 | var success = response.ExitCode == 0 || 13 | response.StdOutAsArry.All( 14 | line => 15 | !line.StartsWith("Error") && !line.StartsWith("Can't remove") && 16 | !line.StartsWith("Incorrect Usage.")); 17 | 18 | Response = response.ToResponse(success, "Error Creating Machine", string.Empty); 19 | return this; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Common/ExperimentalAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ductus.FluentDocker.Common 4 | { 5 | /// 6 | /// Specifies that the implementation is experimental and is subject to change. 7 | /// 8 | [AttributeUsage(AttributeTargets.All)] 9 | public sealed class ExperimentalAttribute : Attribute 10 | { 11 | public ExperimentalAttribute(string documentation = null, string targetVersion = null) 12 | { 13 | Documentation = documentation ?? string.Empty; 14 | TargetVersion = targetVersion ?? string.Empty; 15 | 16 | } 17 | 18 | /// 19 | /// Current target version when this is to be released. 20 | /// 21 | public string TargetVersion { get; set; } 22 | /// 23 | /// Optional documentation for the experimental feature. 24 | /// 25 | public string Documentation { get; set; } 26 | } 27 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Extensions/Utilities.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Commands; 2 | using Ductus.FluentDocker.Common; 3 | using Ductus.FluentDocker.Model.Common; 4 | using Ductus.FluentDocker.Model.Containers; 5 | 6 | namespace Ductus.FluentDocker.Tests.Extensions 7 | { 8 | public static class Utilities 9 | { 10 | public static void LinuxMode() 11 | { 12 | if (FdOs.IsWindows()) 13 | Info.LinuxDaemon(null); 14 | } 15 | 16 | /// 17 | /// Sets the docker daemon to linux if on windows system. 18 | /// 19 | /// The uri to host, may be null for default. 20 | /// The certificates to communicate, many be null. 21 | public static void LinuxMode(this DockerUri host, ICertificatePaths certificates = null) 22 | { 23 | if (FdOs.IsWindows()) 24 | host.LinuxDaemon(certificates); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Common/FdOs.cs: -------------------------------------------------------------------------------- 1 | #if COREFX 2 | using System.Runtime.InteropServices; 3 | #else 4 | using System; 5 | #endif 6 | 7 | namespace Ductus.FluentDocker.Common 8 | { 9 | public static class FdOs 10 | { 11 | 12 | #if COREFX 13 | public static bool IsWindows() 14 | => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); 15 | 16 | public static bool IsOsx() 17 | => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); 18 | 19 | public static bool IsLinux() 20 | => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); 21 | #else 22 | public static bool IsWindows() 23 | => Environment.OSVersion.Platform != PlatformID.MacOSX && 24 | Environment.OSVersion.Platform != PlatformID.Unix; 25 | 26 | public static bool IsOsx() 27 | => Environment.OSVersion.Platform == PlatformID.MacOSX; 28 | 29 | public static bool IsLinux() 30 | => Environment.OSVersion.Platform == PlatformID.Unix; 31 | #endif 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Common/DeprecatedAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ductus.FluentDocker.Common 4 | { 5 | /// 6 | /// Specifies that the implementation is deprecated and is subject to be removed. 7 | /// 8 | [AttributeUsage(AttributeTargets.All)] 9 | public class DeprecatedAttribute : Attribute 10 | { 11 | public DeprecatedAttribute(string documentation = null, string targetVersion = null) 12 | { 13 | Documentation = documentation ?? string.Empty; 14 | TargetVersion = targetVersion ?? string.Empty; 15 | } 16 | 17 | /// 18 | /// Current target version when this is to be removed. 19 | /// 20 | public string TargetVersion { get; set; } 21 | 22 | /// 23 | /// Optional documentation for the deprecation. 24 | /// 25 | public string Documentation { get; set; } 26 | } 27 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Networks/NetworkConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Ductus.FluentDocker.Model.Networks 5 | { 6 | public sealed class NetworkConfiguration 7 | { 8 | public string Name { get; set; } 9 | public string Id { get; set; } 10 | public DateTime Created { get; set; } 11 | public string Scope { get; set; } 12 | public string Driver { get; set; } 13 | public bool EnableIPv6 { get; set; } 14 | public bool Internal { get; set; } 15 | public bool Attachable { get; set; } 16 | public bool Ingress { get; set; } 17 | public bool ConfigOnly { get; set; } 18 | 19 | // ReSharper disable once InconsistentNaming 20 | public Ipam IPAM { get; set; } 21 | 22 | public IDictionary ConfigFrom { get; set; } 23 | public IDictionary Containers { get; set; } 24 | 25 | public IDictionary Options { get; set; } 26 | } 27 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Common/Logger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | #if COREFX 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Logging; 5 | 6 | #endif 7 | 8 | namespace Ductus.FluentDocker.Common 9 | { 10 | internal static class Logger 11 | { 12 | internal static bool Enabled = true; 13 | 14 | public static void Log(string message) 15 | { 16 | if (!Enabled) return; 17 | 18 | #if COREFX 19 | ILogger.Value.LogTrace(message); 20 | } 21 | 22 | 23 | private static readonly Lazy ILogger 24 | = new Lazy(() => 25 | { 26 | var factory = new ServiceCollection().AddLogging().BuildServiceProvider().GetRequiredService(); 27 | factory.AddDebug(); 28 | return factory.CreateLogger(Constants.DebugCategory); 29 | }); 30 | #else 31 | System.Diagnostics.Debugger.Log((int)System.Diagnostics.TraceLevel.Verbose, Constants.DebugCategory, message); 32 | } 33 | #endif 34 | } 35 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Services/IContainerImageService.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Model.Common; 2 | using Ductus.FluentDocker.Model.Containers; 3 | 4 | namespace Ductus.FluentDocker.Services 5 | { 6 | public interface IContainerImageService : IService 7 | { 8 | string Id { get; } 9 | 10 | string Tag { get; } 11 | 12 | DockerUri DockerHost { get; } 13 | 14 | /// 15 | /// Paths to where certificates resides for this service. 16 | /// 17 | ICertificatePaths Certificates { get; } 18 | 19 | /// 20 | /// Gets the configuration from the docker host for this image. 21 | /// 22 | /// If a new copy is wanted or a cached one. If non has been requested it will fetch one and cache it. 23 | /// 24 | /// This is not cached, thus it will go to the docker daemon each time. 25 | /// 26 | ImageConfig GetConfiguration(bool fresh = false); 27 | } 28 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | "program": "${workspaceFolder}/Tests/bin/Debug/netcoreapp2.0/Tests.dll", 13 | "args": [], 14 | "cwd": "${workspaceFolder}", 15 | "console": "internalConsole", 16 | "stopAtEntry": false, 17 | "internalConsoleOptions": "openOnSessionStart" 18 | }, 19 | { 20 | "name": ".NET Core Attach", 21 | "type": "coreclr", 22 | "request": "attach", 23 | "processId": "${command:pickProcess}" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Compose/nginx/Dockerfile_custom: -------------------------------------------------------------------------------- 1 | # Set the base image to Ubuntu 2 | FROM ubuntu 3 | 4 | # File Author / Maintainer 5 | MAINTAINER Anand Mani Sankar 6 | 7 | # Install Nginx 8 | 9 | # Add application repository URL to the default sources 10 | # RUN echo "deb http://archive.ubuntu.com/ubuntu/ raring main universe" >> /etc/apt/sources.list 11 | 12 | # Update the repository 13 | RUN apt-get update 14 | 15 | # Install necessary tools 16 | RUN apt-get install -y nano wget dialog net-tools 17 | 18 | # Download and Install Nginx 19 | RUN apt-get install -y nginx 20 | 21 | # Remove the default Nginx configuration file 22 | RUN rm -v /etc/nginx/nginx.conf 23 | 24 | # Copy a configuration file from the current directory 25 | ADD nginx.conf /etc/nginx/ 26 | 27 | # Append "daemon off;" to the configuration file 28 | RUN echo "daemon off;" >> /etc/nginx/nginx.conf 29 | 30 | # Expose ports 31 | EXPOSE 80 32 | 33 | # Set the default command to execute when creating a new container 34 | CMD service nginx start -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Compose/node/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | http = require('http'), 3 | redis = require('redis'); 4 | 5 | var app = express(); 6 | 7 | console.log(process.env.REDIS_PORT_6379_TCP_ADDR + ':' + process.env.REDIS_PORT_6379_TCP_PORT); 8 | 9 | // APPROACH 1: Using environment variables created by Docker 10 | // var client = redis.createClient( 11 | // process.env.REDIS_PORT_6379_TCP_PORT, 12 | // process.env.REDIS_PORT_6379_TCP_ADDR 13 | // ); 14 | 15 | // APPROACH 2: Using host entries created by Docker in /etc/hosts (RECOMMENDED) 16 | var client = redis.createClient('6379', 'redis'); 17 | 18 | 19 | app.get('/', function(req, res, next) { 20 | client.incr('counter', function(err, counter) { 21 | if(err) return next(err); 22 | res.send('This page has been viewed ' + counter + ' times!'); 23 | }); 24 | }); 25 | 26 | http.createServer(app).listen(process.env.PORT || 8080, function() { 27 | console.log('Listening on port ' + (process.env.PORT || 8080)); 28 | }); -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/ResourcesDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Compose 2 | { 3 | /// 4 | /// 5 | /// 6 | /// 7 | /// Note: This replaces the older resource constraint options for non swarm mode in Compose files prior to version 3 8 | /// (cpu_shares, cpu_quota, cpuset, mem_limit, memswap_limit, mem_swappiness), as described in Upgrading 9 | /// version 2.x to 3.x. 10 | /// In this general example, the redis service is constrained to use no more than 50M of memory and 0.50 (50%) of 11 | /// available processing time (CPU), and has 20M of memory and 0.25 CPU time reserved (as always available to it). 12 | /// resources: 13 | /// limits: 14 | /// cpus: '0.50' 15 | /// memory: 50M 16 | /// reservations: 17 | /// cpus: '0.25' 18 | /// memory: 20M 19 | /// 20 | public sealed class ResourcesDefinition 21 | { 22 | public ResourcesItemDefinition Limits { get; set; } 23 | public ResourcesItemDefinition Reservations { get; set; } 24 | } 25 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/MultiContainerTestFiles/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | http = require('http'), 3 | redis = require('redis'); 4 | 5 | var app = express(); 6 | 7 | console.log(process.env.REDIS_PORT_6379_TCP_ADDR + ':' + process.env.REDIS_PORT_6379_TCP_PORT); 8 | 9 | // APPROACH 1: Using environment variables created by Docker 10 | // var client = redis.createClient( 11 | // process.env.REDIS_PORT_6379_TCP_PORT, 12 | // process.env.REDIS_PORT_6379_TCP_ADDR 13 | // ); 14 | 15 | // APPROACH 2: Using host entries created by Docker in /etc/hosts (RECOMMENDED) 16 | var client = redis.createClient('6379', 'redis'); 17 | 18 | 19 | app.get('/', function (req, res, next) { 20 | client.incr('counter', function (err, counter) { 21 | if (err) return next(err); 22 | res.send('This page has been viewed ' + counter + ' times!'); 23 | }); 24 | }); 25 | 26 | http.createServer(app).listen(process.env.PORT || 8080, function () { 27 | console.log('Listening on port ' + (process.env.PORT || 8080)); 28 | }); -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Containers/ContainerConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Ductus.FluentDocker.Model.Containers 4 | { 5 | public sealed class ContainerConfig 6 | { 7 | public string Hostname { get; set; } 8 | public string Domainname { get; set; } 9 | public string User { get; set; } 10 | public bool AttachStdin { get; set; } 11 | public bool AttachStdout { get; set; } 12 | public bool AttachStderr { get; set; } 13 | public IDictionary ExposedPorts { get; set; } 14 | public bool Tty { get; set; } 15 | public bool OpenStdin { get; set; } 16 | public bool StdinOnce { get; set; } 17 | public string[] Env { get; set; } 18 | public string[] Cmd { get; set; } 19 | public string Image { get; set; } 20 | public IDictionary Volumes { get; set; } 21 | public string WorkingDir { get; set; } 22 | public string[] EntryPoint { get; set; } 23 | public IDictionary Labels { get; set; } 24 | public string StopSignal { get; set; } 25 | } 26 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Extensions/HttpExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Net; 3 | using System.Threading.Tasks; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | 6 | namespace Ductus.FluentDocker.Tests.Extensions 7 | { 8 | public static class HttpExtensions 9 | { 10 | public static async Task Wget(this string path, bool assertOk = true, bool assertDataStream = true) 11 | { 12 | var request = WebRequest.Create(path); 13 | using (var response = await request.GetResponseAsync()) 14 | { 15 | if (assertOk) 16 | { 17 | Assert.AreEqual("OK", ((HttpWebResponse)response).StatusDescription); 18 | } 19 | 20 | var dataStream = response.GetResponseStream(); 21 | 22 | if (assertDataStream) 23 | { 24 | Assert.IsNotNull(dataStream); 25 | } 26 | 27 | if (null == dataStream) 28 | { 29 | return string.Empty; 30 | } 31 | 32 | using (var reader = new StreamReader(dataStream)) 33 | { 34 | var responseFromServer = reader.ReadToEnd(); 35 | return responseFromServer; 36 | } 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/Parsers/NetworkLsResponseParser.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Model.Containers; 2 | using Ductus.FluentDocker.Model.Networks; 3 | using Newtonsoft.Json; 4 | 5 | namespace Ductus.FluentDocker.Executors.Parsers 6 | { 7 | public sealed class NetworkLsResponseParser : IProcessResponseParser 8 | { 9 | public CommandResponse Response { get; private set; } 10 | 11 | public IProcessResponse Process(ProcessExecutionResult response) 12 | { 13 | if (string.IsNullOrEmpty(response.StdOut)) 14 | { 15 | Response = response.ToResponse(false, "No response", new NetworkConfiguration()); 16 | return this; 17 | } 18 | 19 | var resp = JsonConvert.DeserializeObject(response.StdOut); 20 | if (null == resp || 0 == resp.Length) 21 | { 22 | Response = response.ToResponse(false, "No response", new NetworkConfiguration()); 23 | return this; 24 | } 25 | 26 | Response = response.ToResponse(true, string.Empty, resp[0]); 27 | return this; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Containers/HostIpEndpoint.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace Ductus.FluentDocker.Model.Containers 4 | { 5 | public class HostIpEndpoint : IPEndPoint 6 | { 7 | private string _hostIp; 8 | private string _hostPort; 9 | 10 | public HostIpEndpoint() : base(0, 0) 11 | { 12 | } 13 | 14 | public HostIpEndpoint(long address, int port) : base(address, port) 15 | { 16 | } 17 | 18 | public HostIpEndpoint(IPAddress address, int port) : base(address, port) 19 | { 20 | } 21 | 22 | public string HostIp 23 | { 24 | get { return _hostIp; } 25 | set 26 | { 27 | _hostIp = value; 28 | 29 | IPAddress addr; 30 | if (!IPAddress.TryParse(value, out addr)) 31 | { 32 | addr = IPAddress.None; 33 | } 34 | 35 | Address = addr; 36 | } 37 | } 38 | 39 | public string HostPort 40 | { 41 | get { return _hostPort; } 42 | set 43 | { 44 | _hostPort = value; 45 | 46 | int port; 47 | int.TryParse(value, out port); 48 | Port = port; 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Containers/Processes.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text; 3 | 4 | namespace Ductus.FluentDocker.Model.Containers 5 | { 6 | public sealed class Processes 7 | { 8 | public IList Columns { get; set; } 9 | public IList Rows { get; set; } 10 | 11 | public override string ToString() 12 | { 13 | if (null == Columns) 14 | { 15 | return string.Empty; 16 | } 17 | 18 | var sb = new StringBuilder(); 19 | 20 | foreach (var column in Columns) 21 | { 22 | sb.Append(column).Append("\t"); 23 | } 24 | 25 | sb.AppendLine(); 26 | sb.AppendLine("-----------------------------------------------------"); 27 | 28 | if (null == Rows) 29 | { 30 | sb.AppendLine("No Processes"); 31 | return sb.ToString(); 32 | } 33 | 34 | foreach (var row in Rows) 35 | { 36 | foreach (var column in row.FullRow) 37 | { 38 | sb.Append(column).Append("\t"); 39 | } 40 | 41 | sb.AppendLine(); 42 | } 43 | 44 | return sb.ToString(); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/PortsShortDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Compose 2 | { 3 | /// 4 | /// Either specify both ports (HOST:CONTAINER), or just the container port (an ephemeral host port is chosen). 5 | /// 6 | /// 7 | /// Note: When mapping ports in the HOST:CONTAINER format, you may experience erroneous results when using a container 8 | /// port lower than 60, because YAML parses numbers in the format xx:yy as a base-60 value. For this reason, we recommend 9 | /// always explicitly specifying your port mappings as strings. 10 | /// 11 | /// 12 | /// ports: 13 | /// - "3000" 14 | /// - "3000-3005" 15 | /// - "8000:8000" 16 | /// - "9090-9091:8080-8081" 17 | /// - "49100:22" 18 | /// - "127.0.0.1:8001:8001" 19 | /// - "127.0.0.1:5000-5010:5000-5010" 20 | /// - "6060:6060/udp" 21 | /// 22 | public sealed class PortsShortDefinition : IPortsDefinition 23 | { 24 | /// 25 | /// The entry on the specified form of this class documentation. 26 | /// 27 | public string Entry { get; set; } 28 | } 29 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Services/Impl/ServiceHooks.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Linq; 4 | 5 | namespace Ductus.FluentDocker.Services.Impl 6 | { 7 | public class ServiceHooks 8 | { 9 | private readonly ConcurrentDictionary _hooks = new ConcurrentDictionary(); 10 | 11 | public void AddHook(string uniqueName, ServiceRunningState state, Action hook) 12 | { 13 | _hooks.TryAdd(uniqueName, new HookItem {State = state, Hook = hook}); 14 | } 15 | 16 | public void RemoveHook(string uniqueName) 17 | { 18 | HookItem item; 19 | _hooks.TryRemove(uniqueName, out item); 20 | } 21 | 22 | public void Clear() 23 | { 24 | _hooks.Clear(); 25 | } 26 | 27 | public void Execute(IService service, ServiceRunningState state) 28 | { 29 | foreach (var hook in _hooks.Values.Where(x => x.State == state)) 30 | { 31 | hook.Hook(service); 32 | } 33 | } 34 | 35 | private class HookItem 36 | { 37 | internal Action Hook; 38 | internal ServiceRunningState State; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/ExtensionTests/ResourceExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Ductus.FluentDocker.Extensions; 3 | using Ductus.FluentDocker.Tests.Compose; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | 6 | namespace Ductus.FluentDocker.Tests.ExtensionTests 7 | { 8 | #if !COREFX 9 | [TestClass] 10 | public class ResourceExtensionsTests 11 | { 12 | [TestMethod] 13 | public void QueryResourcesRecusivelyShallWork() 14 | { 15 | var resources = typeof(NsResolver); 16 | 17 | var res = 18 | resources.ResuorceQuery().Where(x => x.Resource.Equals("Dockerfile") || x.Resource.Equals("index.js")).ToArray(); 19 | 20 | Assert.AreEqual(4, res.Length); 21 | Assert.AreEqual(3, res.Count(x => x.Resource == "Dockerfile")); 22 | Assert.AreEqual(1, res.Count(x => x.Resource == "index.js")); 23 | } 24 | 25 | [TestMethod] 26 | public void QueryResourcesNonRecurivelyShallWork() 27 | { 28 | var resources = typeof(NsResolver); 29 | 30 | var res = resources.ResuorceQuery(false).ToArray(); 31 | 32 | Assert.AreEqual(1, res.Length); 33 | Assert.AreEqual("docker-compose.yml", res[0].Resource); 34 | } 35 | } 36 | #endif 37 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Common/FeatureAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using Ductus.FluentDocker.Model; 6 | 7 | namespace Ductus.FluentDocker.Common 8 | { 9 | [AttributeUsage(AttributeTargets.Class)] 10 | public sealed class FeatureAttribute : Attribute 11 | { 12 | public FeatureAttribute() 13 | { 14 | Dependencies = new Type[0]; 15 | } 16 | 17 | public FeatureAttribute(string id, IEnumerable dependencies = null) 18 | { 19 | Id = id; 20 | Dependencies = dependencies; 21 | } 22 | 23 | public string Id { get; set; } 24 | public IEnumerable Dependencies { get; set; } 25 | 26 | public void Validate() 27 | { 28 | if (null == Id) throw new FluentDockerException("A feature must have a valid Id"); 29 | 30 | foreach (var dependency in Dependencies) 31 | if (!dependency.GetInterfaces().Contains(typeof(IFeature))) 32 | throw new FluentDockerException($"Feature {Id} dependency is dependant on non IFeature type ({dependency})." + 33 | " This is no allowed"); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/Parsers/VolumeInspectResponseParser.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Ductus.FluentDocker.Model.Containers; 3 | using Ductus.FluentDocker.Model.Volumes; 4 | using Newtonsoft.Json; 5 | 6 | namespace Ductus.FluentDocker.Executors.Parsers 7 | { 8 | public sealed class VolumeInspectResponseParser : IProcessResponseParser> 9 | { 10 | public CommandResponse> Response { get; private set; } 11 | 12 | public IProcessResponse> Process(ProcessExecutionResult response) 13 | { 14 | if (string.IsNullOrEmpty(response.StdOut)) 15 | { 16 | Response = response.ToResponse(false, "No response", (IList) new List()); 17 | return this; 18 | } 19 | 20 | var resp = JsonConvert.DeserializeObject(response.StdOut); 21 | if (null == resp || 0 == resp.Length) 22 | { 23 | Response = response.ToResponse(false, "No response", (IList) new List()); 24 | return this; 25 | } 26 | 27 | Response = response.ToResponse(true, string.Empty, (IList)new List(resp)); 28 | return this; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/DockerComposeConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Ductus.FluentDocker.Model.Images; 4 | 5 | namespace Ductus.FluentDocker.Model.Compose 6 | { 7 | public class DockerComposeConfig 8 | { 9 | /// 10 | /// Fully qualified path to the docker-compose file. 11 | /// 12 | public IList ComposeFilePath { get; set; } = new List(); 13 | public bool ForceRecreate { get; set; } 14 | public bool NoRecreate { get; set; } 15 | public bool NoBuild { get; set; } 16 | public bool ForceBuild { get; set; } 17 | public TimeSpan TimeoutSeconds { get; set; } 18 | public bool RemoveOrphans { get; set; } 19 | public string AlternativeServiceName { get; set; } 20 | public bool UseColor { get; set; } 21 | public bool KeepVolumes { get; set; } 22 | public ImageRemovalOption ImageRemoval { get; set; } 23 | public string []Services { get; set; } 24 | public bool StopOnDispose { get; set; } = true; 25 | 26 | public IDictionary ContainerConfiguration { get; } = 27 | new Dictionary(); 28 | } 29 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Model/Common/TemplateStringTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Ductus.FluentDocker.Common; 3 | using Ductus.FluentDocker.Model.Common; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using Ductus.FluentDocker.Extensions; 6 | 7 | namespace Ductus.FluentDocker.Tests.Model.Common 8 | { 9 | [TestClass] 10 | public class TemplateStringTests 11 | { 12 | [TestMethod] 13 | public void UnifiedSeparatorWillBeTranslatedOnWindows() 14 | { 15 | if (!FdOs.IsWindows()) return; 16 | 17 | var path = new TemplateString(@"${TEMP}/folder/${RND}"); 18 | 19 | Assert.IsTrue(path.ToString().IndexOf(@"\folder\", StringComparison.Ordinal) != -1); 20 | } 21 | 22 | [TestMethod] 23 | public void SpacesInTemplateStringIsEscaped() 24 | { 25 | var path = new TemplateString(@"${TEMP}/folder with space/${RND}"); 26 | Assert.IsTrue(path.EscapePath().ToString().StartsWith("\"")); 27 | } 28 | 29 | [TestMethod] 30 | public void NoSpacesInTemplateStringIsNotEscaped() 31 | { 32 | var path = new TemplateString(@"${TEMP}/folder/${RND}"); 33 | Assert.IsFalse(path.EscapePath().ToString().StartsWith("\"")); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Builders/RepositoryBuilder.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Commands; 2 | using Ductus.FluentDocker.Services; 3 | 4 | namespace Ductus.FluentDocker.Builders 5 | { 6 | public sealed class RepositoryBuilder 7 | { 8 | private string _server; 9 | private string _user; 10 | private string _password; 11 | public RepositoryBuilder(string server, string user = null, string pass = null) 12 | { 13 | _server = server; 14 | _user = user; 15 | _password = pass; 16 | } 17 | 18 | public RepositoryBuilder Server(string server) 19 | { 20 | _server = server; 21 | return this; 22 | } 23 | public RepositoryBuilder User(string user) 24 | { 25 | _user = user; 26 | return this; 27 | } 28 | public RepositoryBuilder Password(string password) 29 | { 30 | _password = password; 31 | return this; 32 | } 33 | 34 | public RepositoryBuilder Build(IHostService host) 35 | { 36 | host?.Host.Login(_server, _user, _password); 37 | return this; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Containers/ContainerNetworkSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | // ReSharper disable InconsistentNaming 4 | 5 | namespace Ductus.FluentDocker.Model.Containers 6 | { 7 | public sealed class ContainerNetworkSettings 8 | { 9 | public string Bridge { get; set; } 10 | public string SandboxID { get; set; } 11 | public bool HairpinMode { get; set; } 12 | public string LinkLocalIPv6Address { get; set; } 13 | public string LinkLocalIPv6PrefixLen { get; set; } 14 | public string SandboxKey { get; set; } 15 | public string SecondaryIPAddresses { get; set; } 16 | public string SecondaryIPv6Addresses { get; set; } 17 | public string EndpointID { get; set; } 18 | public string Gateway { get; set; } 19 | public string GlobalIPv6Address { get; set; } 20 | public string GlobalIPv6PrefixLen { get; set; } 21 | public string IPAddress { get; set; } 22 | public string IPPrefixLen { get; set; } 23 | public string IPv6Gateway { get; set; } 24 | public string MacAddress { get; set; } 25 | public Dictionary Ports { get; set; } 26 | public Dictionary Networks { get; set; } 27 | } 28 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/Parsers/ClientImageInspectCommandResponder.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Ductus.FluentDocker.Model.Containers; 3 | using Newtonsoft.Json; 4 | 5 | namespace Ductus.FluentDocker.Executors.Parsers 6 | { 7 | public sealed class ClientImageInspectCommandResponder : IProcessResponseParser 8 | { 9 | public CommandResponse Response { get; private set; } 10 | 11 | public IProcessResponse Process(ProcessExecutionResult response) 12 | { 13 | if (string.IsNullOrEmpty(response.StdOut)) 14 | { 15 | Response = response.ToResponse(false, "Empty response", new ImageConfig()); 16 | return this; 17 | } 18 | 19 | if (response.ExitCode != 0) 20 | { 21 | Response = response.ToErrorResponse(new ImageConfig()); 22 | return this; 23 | } 24 | 25 | 26 | var arr = response.StdOutAsArry; 27 | var sb = new StringBuilder(); 28 | for (var i = 1; i < arr.Length - 1; i++) 29 | { 30 | sb.AppendLine(arr[i]); 31 | } 32 | 33 | var container = sb.ToString(); 34 | Response = response.ToResponse(true, string.Empty, JsonConvert.DeserializeObject(container)); 35 | return this; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/Parsers/StackLsResponseParser.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Ductus.FluentDocker.Model.Containers; 4 | using Ductus.FluentDocker.Model.Stacks; 5 | 6 | namespace Ductus.FluentDocker.Executors.Parsers 7 | { 8 | public class StackLsResponseParser : IProcessResponseParser> 9 | { 10 | public CommandResponse> Response { get; private set; } 11 | 12 | public IProcessResponse> Process(ProcessExecutionResult response) 13 | { 14 | var list = new List(); 15 | var rows = response.StdOutAsArry; 16 | 17 | if (rows.Length > 0) 18 | { 19 | list.AddRange(from row in rows 20 | select row.Split(';') 21 | into s 22 | where s.Length == 3 23 | select new StackLsResponse 24 | { 25 | Name = s[0], 26 | Services = null == s[1] ? -1 : int.Parse(s[1]), 27 | Orchestrator = StackLsResponse.ToOrchestrator(s[2]), 28 | Namespace = s[3] 29 | }); 30 | } 31 | 32 | Response = response.ToResponse(true, string.Empty, (IList) list); 33 | return this; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/StreamProcessExecutor.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Threading; 3 | using Ductus.FluentDocker.Common; 4 | 5 | namespace Ductus.FluentDocker.Executors 6 | { 7 | public sealed class StreamProcessExecutor where T : class, IStreamMapper, new() 8 | where TE : class 9 | { 10 | private readonly string _arguments; 11 | private readonly string _command; 12 | private readonly string _workingdir; 13 | 14 | public StreamProcessExecutor(string command, string arguments, string workingdir = null) 15 | { 16 | _command = command; 17 | _arguments = arguments; 18 | _workingdir = workingdir; 19 | } 20 | 21 | public ConsoleStream Execute(CancellationToken token = default(CancellationToken)) 22 | { 23 | Logger.Log($"cmd: {_command} - arg: {_arguments}"); 24 | return new ConsoleStream(new ProcessStartInfo 25 | { 26 | CreateNoWindow = true, 27 | RedirectStandardOutput = true, 28 | RedirectStandardError = true, 29 | RedirectStandardInput = true, 30 | UseShellExecute = false, 31 | Arguments = _arguments, 32 | FileName = _command, 33 | WorkingDirectory = _workingdir 34 | }, new T(), token); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Common/EmbeddedUri.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ductus.FluentDocker.Model.Common 4 | { 5 | public sealed class EmbeddedUri : Uri 6 | { 7 | public static string Prefix = "emb"; 8 | 9 | /// 10 | /// Uri to use when manageing embedded resources. 11 | /// 12 | /// Uri on format embedded:AssemblyName/namespace/resource 13 | public EmbeddedUri(string embedded) : base(embedded) 14 | { 15 | var split = embedded.Split(':'); 16 | if (split[0].ToLower() != Prefix) 17 | { 18 | throw new ArgumentException($"Incorrect scheme (expecting: {Prefix}) for embedded uri - {embedded}", 19 | nameof(embedded)); 20 | } 21 | 22 | var s = split[1].Split('/'); 23 | Host = s[0]; 24 | Namespace = s[1]; 25 | 26 | if (s.Length > 2) 27 | { 28 | Resource = s[2]; 29 | } 30 | } 31 | 32 | public new string Host { get; } 33 | 34 | public string Assembly => Host; 35 | public string Namespace { get; } 36 | public string Resource { get; } 37 | 38 | public static implicit operator EmbeddedUri(string uri) 39 | { 40 | if (null == uri) 41 | { 42 | return null; 43 | } 44 | 45 | return new EmbeddedUri(uri); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/Parsers/MachineLsResponseParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Ductus.FluentDocker.Model.Containers; 5 | using Ductus.FluentDocker.Model.Machines; 6 | using Ductus.FluentDocker.Services; 7 | 8 | namespace Ductus.FluentDocker.Executors.Parsers 9 | { 10 | public class MachineLsResponseParser : IProcessResponseParser> 11 | { 12 | public CommandResponse> Response { get; private set; } 13 | 14 | public IProcessResponse> Process(ProcessExecutionResult response) 15 | { 16 | var list = new List(); 17 | var rows = response.StdOutAsArry; 18 | 19 | if (rows.Length > 0) 20 | { 21 | list.AddRange(from row in rows 22 | select row.Split(';') 23 | into s 24 | where s.Length == 3 25 | select new MachineLsResponse 26 | { 27 | State = s[1] == "Running" ? ServiceRunningState.Running : ServiceRunningState.Stopped, 28 | Name = s[0], 29 | Docker = string.IsNullOrWhiteSpace(s[2]) ? null : new Uri(s[2]) 30 | }); 31 | } 32 | 33 | Response = response.ToResponse(true, string.Empty, (IList) list); 34 | return this; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Builders/IBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Ductus.FluentDocker.Common; 3 | using Ductus.FluentDocker.Services; 4 | 5 | namespace Ductus.FluentDocker.Builders 6 | { 7 | public interface IBuilder 8 | { 9 | /// 10 | /// Gets a parent builder if any. 11 | /// 12 | Option Parent { get; } 13 | 14 | /// 15 | /// Gets the root builder (if hiearchy). 16 | /// 17 | Option Root { get; } 18 | 19 | /// 20 | /// Gets the Childrens of this builder. 21 | /// 22 | IReadOnlyCollection Children { get; } 23 | 24 | /// 25 | /// Creates a new child and makes this as parent. 26 | /// 27 | /// 28 | IBuilder Create(); 29 | 30 | IService Build(); 31 | } 32 | 33 | public interface IBuilder : IBuilder 34 | { 35 | /// 36 | /// Builds using the configuration and returns the instance. 37 | /// 38 | /// The type to build from this builder. 39 | /// A instance if successfull, or and exception is thrown if any errors occurs. 40 | /// If any errors occurs during buildtime. 41 | new T Build(); 42 | } 43 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/PortsLongDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Compose 2 | { 3 | /// 4 | /// The long form syntax allows the configuration of additional fields that can’t be expressed in the 5 | /// form. 6 | /// 7 | /// 8 | /// Note: The long syntax is new in v3.2 9 | /// 10 | /// 11 | /// ports: 12 | /// - target: 80 13 | /// published: 8080 14 | /// protocol: tcp 15 | /// mode: host 16 | /// 17 | public sealed class PortsLongDefinition : IPortsDefinition 18 | { 19 | /// 20 | /// Target, inside container, port e.g 80 21 | /// 22 | public int Target { get; set; } 23 | /// 24 | /// Published, out from the container, port e.g. 8080. 25 | /// 26 | public int Published { get; set; } 27 | /// 28 | /// The protocol e.g. udp or tcp. Default is tcp. 29 | /// 30 | public string Protocol { get; set; } = "tcp"; 31 | /// 32 | /// The mode of publishing (host or ingress). Default is host. 33 | /// 34 | /// 35 | /// Use host for publishing a host port on each node, or ingress for a swarm mode port to be load balanced. 36 | /// 37 | public PortMode Mode { get; set; } = PortMode.Host; 38 | } 39 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Common/DockerUri.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Ductus.FluentDocker.Common; 3 | using Ductus.FluentDocker.Extensions; 4 | 5 | namespace Ductus.FluentDocker.Model.Common 6 | { 7 | public sealed class DockerUri : Uri 8 | { 9 | private const string DockerHost = "DOCKER_HOST"; 10 | private const string DockerHostUrlWindowsNative = "npipe://./pipe/docker_engine"; 11 | private const string DockerHostUrlLegacy = "tcp://localhost:2375"; 12 | private const string DockerHostUrlMacOrLinux = "unix:///var/run/docker.sock"; 13 | 14 | public DockerUri(string uriString) : base(uriString) 15 | { 16 | } 17 | 18 | public static string GetDockerHostEnvronmentPathOrDefault() 19 | { 20 | string env = Environment.GetEnvironmentVariable(DockerHost); 21 | if (null != env) 22 | { 23 | return env; 24 | } 25 | 26 | if (FdOs.IsWindows()) 27 | { 28 | return CommandExtensions.IsToolbox() ? DockerHostUrlLegacy : DockerHostUrlWindowsNative; 29 | } 30 | 31 | return CommandExtensions.IsToolbox() ? DockerHostUrlLegacy : DockerHostUrlMacOrLinux; 32 | } 33 | 34 | public override string ToString() 35 | { 36 | if (Scheme == "npipe") 37 | { 38 | var s = base.ToString(); 39 | return s.Substring(0, 6) + "//" + s.Substring(6); 40 | } 41 | return base.ToString(); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/LoggingDefinition.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Ductus.FluentDocker.Model.Compose 4 | { 5 | /// 6 | /// Logging configuration for the service. 7 | /// 8 | /// 9 | /// For example: 10 | /// logging: 11 | /// driver: syslog 12 | /// options: 13 | /// syslog-address: "tcp://192.168.0.42:123" 14 | /// 15 | public sealed class LoggingDefinition 16 | { 17 | /// 18 | /// Specifies the driver to use for logging. 19 | /// 20 | /// 21 | /// The default value is json-file. 22 | /// Valid drivers are: json-file, syslog, none 23 | /// Note: Only the json-file and journald drivers make the logs available directly from docker-compose up and 24 | /// docker-compose logs. Using any other driver does not print any logs. 25 | /// 26 | public string Driver { get; set; } = "json-file"; 27 | 28 | /// 29 | /// Specify logging options for the logging driver. 30 | /// 31 | /// 32 | /// For example syslog option: syslog-address: "tcp://192.168.0.42:123". 33 | /// Note: Logging options available depend on which logging driver you use. 34 | /// 35 | public IDictionary Options { get; set; } = new Dictionary(); 36 | } 37 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Resources/FileResourceWriter.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Ductus.FluentDocker.Model.Common; 3 | 4 | namespace Ductus.FluentDocker.Resources 5 | { 6 | public sealed class FileResourceWriter : IResourceWriter 7 | { 8 | private readonly TemplateString _basePath; 9 | 10 | public FileResourceWriter(TemplateString basePath) 11 | { 12 | _basePath = basePath; 13 | } 14 | 15 | public IResourceWriter Write(ResourceStream stream) 16 | { 17 | var dir = string.IsNullOrEmpty(stream.Info.RelativeRootNamespace) 18 | ? _basePath.Rendered 19 | : Path.Combine(_basePath, stream.Info.RelativeRootNamespace.Replace('.', Path.PathSeparator)); 20 | 21 | if (!Directory.Exists(dir)) 22 | { 23 | Directory.CreateDirectory(dir); 24 | } 25 | 26 | using (var fileStream = new FileStream(Path.Combine(dir, stream.Info.Resource), FileMode.Create)) 27 | { 28 | stream.Stream.CopyTo(fileStream); 29 | fileStream.Flush(); 30 | } 31 | 32 | return this; 33 | } 34 | 35 | public IResourceWriter Write(ResourceReader resources) 36 | { 37 | foreach (var resource in resources) 38 | { 39 | try 40 | { 41 | Write(resource); 42 | } 43 | finally 44 | { 45 | resource.Dispose(); 46 | } 47 | } 48 | 49 | return this; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/Parsers/ClientImagesResponseParser.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Ductus.FluentDocker.Model.Containers; 3 | using Ductus.FluentDocker.Model.Images; 4 | using Ductus.FluentDocker.Extensions; 5 | 6 | namespace Ductus.FluentDocker.Executors.Parsers 7 | { 8 | public sealed class ClientImagesResponseParser : IProcessResponseParser> 9 | { 10 | public CommandResponse> Response { get; private set; } 11 | public IProcessResponse> Process(ProcessExecutionResult response) 12 | { 13 | if (response.ExitCode != 0) 14 | { 15 | Response = response.ToErrorResponse((IList) new List()); 16 | return this; 17 | } 18 | 19 | var list = new List(); 20 | foreach (var row in response.StdOutAsArry) 21 | { 22 | var items = row.Split(';'); 23 | if (items.Length != 3) 24 | { 25 | continue; 26 | } 27 | 28 | list.Add(new DockerImageRowResponse 29 | { 30 | Id = items[0].ToPlainId(), 31 | Name = items[1], 32 | Tags = new[] {items[2]} 33 | }); 34 | } 35 | 36 | Response = response.ToResponse(true,string.Empty,(IList)list); 37 | return this; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/Parsers/MachineEnvResponseParser.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Ductus.FluentDocker.Model.Containers; 3 | 4 | namespace Ductus.FluentDocker.Executors.Parsers 5 | { 6 | public sealed class MachineEnvResponseParser : IProcessResponseParser> 7 | { 8 | public CommandResponse> Response { get; private set; } 9 | 10 | public IProcessResponse> Process(ProcessExecutionResult response) 11 | { 12 | var dict = new Dictionary(); 13 | if (string.IsNullOrEmpty(response.StdOut)) 14 | { 15 | Response = response.ToResponse(false, "No data", (IDictionary) dict); 16 | return this; 17 | } 18 | 19 | var lines = response.StdOutAsArry; 20 | foreach (var line in lines) 21 | { 22 | if (line.StartsWith("export") || line.StartsWith("SET ")) 23 | { 24 | var idx = line.StartsWith("SET ") ? 4 : 8; 25 | var nv = line.Substring(idx, line.Length - idx).Split('='); 26 | var key = nv[0].Trim(); 27 | var value = nv[1].Trim(); 28 | 29 | dict.Add(key, 4 == idx ? value : value.Substring(1, value.Length - 2)); 30 | } 31 | } 32 | 33 | Response = response.ToResponse(true, string.Empty, (IDictionary) dict); 34 | return this; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/Parsers/StackPsResponseParser.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Ductus.FluentDocker.Model.Containers; 4 | using Ductus.FluentDocker.Model.Stacks; 5 | 6 | namespace Ductus.FluentDocker.Executors.Parsers 7 | { 8 | public class StackPsResponseParser : IProcessResponseParser> 9 | { 10 | public CommandResponse> Response { get; private set; } 11 | 12 | public IProcessResponse> Process(ProcessExecutionResult response) 13 | { 14 | var list = new List(); 15 | var rows = response.StdOutAsArry; 16 | if (rows.Length > 0) 17 | list.AddRange(from row in rows 18 | select row.Split(';') 19 | into s 20 | where s.Length == 3 21 | select new StackPsResponse 22 | { 23 | Id = s[0], 24 | Stack = s[1].Split('_')[0], 25 | Name = s[1].Split('_')[1], 26 | Image = s[2].Split(':')[0], 27 | ImageVersion = s[2].Split(':')[1], 28 | Node = s[3], 29 | DesiredState = s[4], 30 | CurrentState = s[5], 31 | Error = s[6] ?? string.Empty, 32 | Ports = s[7] ?? string.Empty 33 | }); 34 | 35 | Response = response.ToResponse(true, string.Empty, (IList) list); 36 | return this; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Services/Hosts.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Ductus.FluentDocker.Commands; 5 | using Ductus.FluentDocker.Extensions; 6 | using Ductus.FluentDocker.Services.Impl; 7 | 8 | namespace Ductus.FluentDocker.Services 9 | { 10 | public sealed class Hosts 11 | { 12 | public IList Discover(bool preferNative = false) 13 | { 14 | var list = new List(); 15 | 16 | var native = Native(); 17 | if (null != native) list.Add(native); 18 | 19 | if (list.Count > 0 && preferNative) 20 | return list; 21 | 22 | if (!Machine.IsPresent()) return list; 23 | 24 | var ls = Machine.Ls(); 25 | if (ls.Success) 26 | list.AddRange(from machine in ls.Data select FromMachineName(machine.Name)); 27 | 28 | return list; 29 | } 30 | 31 | public IHostService Native() 32 | { 33 | if (CommandExtensions.IsEmulatedNative() || CommandExtensions.IsNative()) 34 | return new DockerHostService("native", true, false, null, 35 | Environment.GetEnvironmentVariable(DockerHostService.DockerCertPath)); 36 | 37 | return null; 38 | } 39 | 40 | public IHostService FromMachineName(string name, bool isWindowsHost = false, bool throwIfNotStarted = false) 41 | { 42 | return new DockerHostService(name, false, isWindowsHost, throwIfNotStarted); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Commands/CommandDefaults.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Extensions; 2 | 3 | namespace Ductus.FluentDocker.Commands 4 | { 5 | public static class CommandDefaults 6 | { 7 | static CommandDefaults() 8 | { 9 | AutoDetect(); 10 | } 11 | 12 | public static string MachineDriver { get; set; } = "virtualbox"; 13 | /// 14 | /// TODO: This property is currently not used! 15 | /// 16 | public static string MachineExtraDefaultCreateArgs { get; set; } = string.Empty; 17 | 18 | /// 19 | /// Tries to autodetect the . 20 | /// 21 | /// 22 | /// http://docker-saigon.github.io/post/Docker-Beta/ 23 | /// 24 | public static void AutoDetect() 25 | { 26 | if (Common.FdOs.IsWindows() || Common.FdOs.IsOsx()) 27 | { 28 | // Prefer non toolbox on windows and mac 29 | if (!CommandExtensions.IsToolbox()) 30 | { 31 | MachineDriver = Common.FdOs.IsWindows() ? "hyperv" : "xhyve"; 32 | 33 | if (Common.FdOs.IsWindows()) 34 | { 35 | // TODO: Is it possible instead to use the proxy to proxy this machine 36 | // TODO: for the default docker NAT switch instead? 37 | MachineExtraDefaultCreateArgs = "--hyperv-virtual-switch external-switch {0}"; 38 | } 39 | } 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Ductus.FluentDocker.MsTest/FluentDockerTestBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Ductus.FluentDocker.Builders; 3 | using Ductus.FluentDocker.Services; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | 6 | namespace Ductus.FluentDocker.MsTest 7 | { 8 | public abstract class FluentDockerTestBase 9 | { 10 | protected IContainerService Container; 11 | 12 | protected abstract ContainerBuilder Build(); 13 | 14 | [TestInitialize] 15 | public void Initialize() 16 | { 17 | Container = Build().Build(); 18 | try 19 | { 20 | Container.Start(); 21 | } 22 | catch 23 | { 24 | Container.Dispose(); 25 | throw; 26 | } 27 | 28 | OnContainerInitialized(); 29 | } 30 | 31 | [TestCleanup] 32 | public void TeardownContainer() 33 | { 34 | OnContainerTearDown(); 35 | 36 | var c = Container; 37 | Container = null; 38 | try 39 | { 40 | c?.Dispose(); 41 | } 42 | catch 43 | { 44 | // Ignore 45 | } 46 | } 47 | 48 | /// 49 | /// Invoked just before the container is teared down. 50 | /// 51 | protected virtual void OnContainerTearDown() 52 | { 53 | } 54 | 55 | /// 56 | /// Invoked after a container has been created and started. 57 | /// 58 | protected virtual void OnContainerInitialized() 59 | { 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/Parsers/ClientDiffResponseParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Ductus.FluentDocker.Model.Containers; 5 | 6 | namespace Ductus.FluentDocker.Executors.Parsers 7 | { 8 | public sealed class ClientDiffResponseParser : IProcessResponseParser> 9 | { 10 | public CommandResponse> Response { get; private set; } 11 | 12 | public IProcessResponse> Process(ProcessExecutionResult response) 13 | { 14 | var rows = response.StdOutAsArry; 15 | if (0 != response.ExitCode) 16 | { 17 | Response = response.ToErrorResponse((IList) new List()); 18 | return this; 19 | } 20 | 21 | Response = response.ToResponse(true, string.Empty, 22 | (IList) rows.Select(row => new Diff {Type = ToDiffType(row[0]), Item = row.Substring(2).Trim()}).ToList()); 23 | 24 | return this; 25 | } 26 | 27 | private static DiffType ToDiffType(char type) 28 | { 29 | switch (type) 30 | { 31 | case 'A': 32 | return DiffType.Added; 33 | case 'U': 34 | return DiffType.Updated; 35 | case 'R': 36 | return DiffType.Removed; 37 | case 'C': 38 | return DiffType.Created; 39 | default: 40 | throw new NotImplementedException($"The diff type {type} is not implemented"); 41 | } 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Common/ResultExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Ductus.FluentDocker.Common 5 | { 6 | public static class ResultExtensions 7 | { 8 | public static Result ToSuccess(this T data, string log = null) 9 | { 10 | return new Result(true, data, log ?? string.Empty, string.Empty); 11 | } 12 | 13 | public static Result ToSuccess(this T data, IList log) 14 | { 15 | return new Result(true, data, log.FromLog(), string.Empty); 16 | } 17 | public static Result ToFailure(this T data, string error, string log = null) 18 | { 19 | return new Result(false, data, log ?? string.Empty, error); 20 | } 21 | 22 | public static Result ToFailure(this T data, string error, IList log) 23 | { 24 | return new Result(false, data, log.FromLog(), error); 25 | } 26 | 27 | public static string FromLog(this IList entries) 28 | { 29 | if (null == entries || 0 == entries.Count) 30 | { 31 | return string.Empty; 32 | } 33 | 34 | return string.Join(Environment.NewLine, entries); 35 | } 36 | 37 | public static string[] ToEntires(this string log) 38 | { 39 | if (string.IsNullOrEmpty(log)) 40 | { 41 | return new string[0]; 42 | } 43 | 44 | return log.Split(new[] { "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Services/Impl/ServiceBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ductus.FluentDocker.Services.Impl 4 | { 5 | public abstract class ServiceBase : IService 6 | { 7 | private readonly ServiceHooks _hooks = new ServiceHooks(); 8 | private ServiceRunningState _state = ServiceRunningState.Unknown; 9 | 10 | protected ServiceBase(string name) 11 | { 12 | Name = name; 13 | } 14 | 15 | public abstract void Dispose(); 16 | 17 | public string Name { get; } 18 | 19 | public ServiceRunningState State 20 | { 21 | get => _state; 22 | protected set 23 | { 24 | if (_state == value) 25 | { 26 | return; 27 | } 28 | 29 | _state = value; 30 | StateChange?.Invoke(this, new StateChangeEventArgs(this, value)); 31 | _hooks.Execute(this, _state); 32 | } 33 | } 34 | 35 | public abstract void Start(); 36 | public abstract void Stop(); 37 | public abstract void Remove(bool force = false); 38 | 39 | public IService AddHook(ServiceRunningState state, Action hook, string uniqueName = null) 40 | { 41 | _hooks.AddHook(uniqueName ?? Guid.NewGuid().ToString(), state, hook); 42 | return this; 43 | } 44 | 45 | public IService RemoveHook(string uniqueName) 46 | { 47 | _hooks.RemoveHook(uniqueName); 48 | return this; 49 | } 50 | 51 | public event ServiceDelegates.StateChange StateChange; 52 | } 53 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/CommandTests/ImageTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Ductus.FluentDocker.Commands; 3 | using Ductus.FluentDocker.Model.Containers; 4 | using Ductus.FluentDocker.Services; 5 | using Ductus.FluentDocker.Tests.Extensions; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | namespace Ductus.FluentDocker.Tests.CommandTests 9 | { 10 | [TestClass] 11 | public sealed class ImageTests : FluentDockerTestBase 12 | { 13 | [ClassInitialize] 14 | public static void Initialize(TestContext ctx) 15 | { 16 | Utilities.LinuxMode(); 17 | } 18 | 19 | [TestMethod] 20 | public void ImageConfigurationShallBeRetrievable() 21 | { 22 | var result = DockerHost.Host.Pull("postgres:10-alpine"); 23 | Assert.IsTrue(result.Success); 24 | 25 | var config = DockerHost.GetImages().First(x => x.Name == "postgres").GetConfiguration(true); 26 | Assert.IsNotNull(config); 27 | } 28 | 29 | [TestMethod] 30 | public void ImageIsExposedOnARunningContainer() 31 | { 32 | using (var container = DockerHost.Create("postgres:9.6-alpine", 33 | new ContainerCreateParams 34 | { 35 | Environment = new[] {"POSTGRES_PASSWORD=mysecretpassword"} 36 | })) 37 | { 38 | var config = container.GetConfiguration(); 39 | var image = container.Image.GetConfiguration(); 40 | 41 | Assert.AreEqual(config.Image, image.Id); 42 | } 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Containers/VolumeMount.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace Ductus.FluentDocker.Model.Containers 4 | { 5 | public sealed class VolumeMount 6 | { 7 | /// 8 | /// Host path in MSYS or linux compatible format. 9 | /// 10 | public string Source { get; set; } 11 | 12 | /// 13 | /// Inside docker container path in MSYS or linux compatible format. 14 | /// 15 | public string Destination { get; set; } 16 | 17 | /// 18 | /// Mode of the mount (e.g. 'Z'). 19 | /// 20 | public string Mode { get; set; } 21 | 22 | /// 23 | /// Which access 'ro' or 'rw'. 24 | /// 25 | public bool RW { get; set; } 26 | 27 | public override string ToString() 28 | { 29 | var sb = new StringBuilder(); 30 | if (!string.IsNullOrEmpty(Source)) 31 | { 32 | sb.Append(Source); 33 | } 34 | 35 | if (!string.IsNullOrEmpty(Destination)) 36 | { 37 | if (sb.Length > 0) 38 | { 39 | sb.Append(':'); 40 | } 41 | sb.Append(Destination); 42 | } 43 | 44 | if (!string.IsNullOrEmpty(Mode)) 45 | { 46 | if (sb.Length > 0) 47 | { 48 | sb.Append(':'); 49 | } 50 | sb.Append(Mode); 51 | } 52 | 53 | if (sb.Length > 0) 54 | { 55 | sb.Append(':'); 56 | } 57 | sb.Append(RW ? "rw " : "ro"); 58 | 59 | return sb.ToString(); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/Parsers/ClientContainerInspectCommandResponder.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Ductus.FluentDocker.Model.Containers; 3 | using Newtonsoft.Json; 4 | 5 | namespace Ductus.FluentDocker.Executors.Parsers 6 | { 7 | public sealed class ClientContainerInspectCommandResponder : IProcessResponseParser 8 | { 9 | public CommandResponse Response { get; private set; } 10 | 11 | public IProcessResponse Process(ProcessExecutionResult response) 12 | { 13 | if (string.IsNullOrEmpty(response.StdOut)) 14 | { 15 | Response = response.ToResponse(false, "Empty response", new Container()); 16 | return this; 17 | } 18 | 19 | if (response.ExitCode != 0) 20 | { 21 | Response = response.ToErrorResponse(new Container()); 22 | return this; 23 | } 24 | 25 | 26 | var arr = response.StdOutAsArry; 27 | var sb = new StringBuilder(); 28 | for (var i = 1; i < arr.Length - 1; i++) 29 | { 30 | sb.AppendLine(arr[i]); 31 | } 32 | 33 | var container = sb.ToString(); 34 | var obj = JsonConvert.DeserializeObject(container); 35 | 36 | obj.Name = TrimIfBeginsWithSlash(obj.Name); 37 | 38 | Response = response.ToResponse(true, string.Empty, obj); 39 | return this; 40 | } 41 | 42 | private static string TrimIfBeginsWithSlash(string name) 43 | { 44 | if (!string.IsNullOrEmpty(name) && name.StartsWith("/")) return name.Substring(1); 45 | return name; 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/ProcessExecutionResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using Ductus.FluentDocker.Model.Containers; 5 | 6 | namespace Ductus.FluentDocker.Executors 7 | { 8 | public sealed class ProcessExecutionResult 9 | { 10 | internal ProcessExecutionResult(string process, string stdOut, string stdErr, int exitCode) 11 | { 12 | Command = process; 13 | StdOut = stdOut; 14 | StdErr = stdErr; 15 | ExitCode = exitCode; 16 | } 17 | 18 | public string Command { get; } 19 | public string StdOut { get; } 20 | public string StdErr { get; } 21 | public int ExitCode { get; } 22 | public string[] StdOutAsArry => StdOut.Split(new[] {"\n", "\r\n"}, StringSplitOptions.RemoveEmptyEntries); 23 | public string[] StdErrAsArry => StdErr.Split(new[] {"\n", "\r\n"}, StringSplitOptions.RemoveEmptyEntries); 24 | 25 | public CommandResponse ToResponse(bool success, string error, T data) 26 | { 27 | var log = new List(StdOutAsArry); 28 | if (!string.IsNullOrEmpty(StdErr)) 29 | { 30 | log.AddRange(StdErrAsArry); 31 | } 32 | 33 | return new CommandResponse(success, log, error, data); 34 | } 35 | 36 | public CommandResponse ToErrorResponse(T data) 37 | { 38 | var err = StdErr; 39 | if (string.IsNullOrWhiteSpace(err)) 40 | { 41 | err = $"Error when executing command, exit code {ExitCode}"; 42 | } 43 | 44 | return ToResponse(false, err, data); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker.MsTest/PostgresTestBase.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Builders; 2 | using Ductus.FluentDocker.Services.Extensions; 3 | 4 | namespace Ductus.FluentDocker.MsTest 5 | { 6 | public abstract class PostgresTestBase : FluentDockerTestBase 7 | { 8 | protected const string PostgresConnectionString = 9 | "Server={0};Port={1};Userid={2};Password={3};" + 10 | "Pooling=false;MinPoolSize=1;MaxPoolSize=20;" + 11 | "Timeout=15;SslMode=Disable;Database={4}"; 12 | 13 | protected const string PostgresUser = "postgres"; 14 | protected const string PostgresDb = "postgres"; 15 | protected readonly string DockerImage; 16 | protected readonly string PostgresPassword; 17 | 18 | protected string ConnectionString; 19 | 20 | protected PostgresTestBase(string password = "mysecretpassword", string image = "kiasaki/alpine-postgres") 21 | { 22 | PostgresPassword = password; 23 | DockerImage = image; 24 | } 25 | 26 | protected override ContainerBuilder Build() 27 | { 28 | return new Builder().UseContainer() 29 | .UseImage("kiasaki/alpine-postgres") 30 | .WithEnvironment($"POSTGRES_PASSWORD={PostgresPassword}") 31 | .ExposePort(5432) 32 | .WaitForPort("5432/tcp", 30000 /*30s*/); 33 | } 34 | 35 | protected override void OnContainerInitialized() 36 | { 37 | var ep = Container.ToHostExposedEndpoint("5432/tcp"); 38 | ConnectionString = string.Format(PostgresConnectionString, ep.Address, ep.Port, PostgresUser, 39 | PostgresPassword, PostgresDb); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/ShortSecret.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Compose 2 | { 3 | /// 4 | /// The short syntax variant only specifies the secret name. 5 | /// 6 | /// 7 | /// This grants the container access to the secret and mounts it at /run/secrets/[secret_name] within the container. The 8 | /// source name and destination mountpoint are both set to the secret name. 9 | /// Note: The secret must already exist or be defined in the top-level secrets configuration of this stack file, or stack 10 | /// deployment fails. This requires docker compose file 3.1 or greater. 11 | /// 12 | public class ShortSecret : ISecret 13 | { 14 | /// 15 | /// Name of the secret. 16 | /// 17 | public string Name { get; set; } 18 | 19 | /// 20 | /// The path, relative or absolute, to the secret file. 21 | /// 22 | /// 23 | /// 24 | public string FilePath { get; set; } 25 | 26 | /// 27 | /// Defines the secret as external resource. 28 | /// 29 | /// 30 | /// If this is set to true, the is discarded since it is assumed that it is already 31 | /// defined in Docker, either by running the docker secret create command or by another stack deployment. If the external 32 | /// secret does not exist, the stack deployment fails with a secret not found error. 33 | /// 34 | public bool IsExternal { get; set; } 35 | } 36 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Extensions/ConversionExtension.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | namespace Ductus.FluentDocker.Extensions 4 | { 5 | public static class ConversionExtension 6 | { 7 | /// 8 | /// Converts a numeric expression combined with an optional suffix to denote 9 | /// b, k, m, g. 10 | /// 11 | /// The value to be parsed. 12 | /// An optional custom array of suffix. But has to be among b, k, m, g. 13 | /// If successful the number, otherwise is returned. 14 | public static long Convert(this string value, params string[] unit) 15 | { 16 | if (null == unit || 0 == unit.Length) unit = new[] {"b", "k", "m", "g"}; 17 | 18 | if (string.IsNullOrWhiteSpace(value)) return long.MinValue; 19 | 20 | var num = value.Substring(0, value.Length - 2); 21 | var u = value.Substring(value.Length - 2, 1).ToLower(); 22 | 23 | if (char.IsDigit(u[0])) return !long.TryParse(value, out var n) ? long.MinValue : n; 24 | 25 | if (!unit.Contains(u)) return long.MinValue; 26 | 27 | if (!long.TryParse(num, out var val)) 28 | return long.MinValue; 29 | 30 | if (u == "b") return val; 31 | 32 | switch (u) 33 | { 34 | case "b": 35 | return val; 36 | case "k": 37 | return val * 1024; 38 | case "m": 39 | return val * 1024 * 1024; 40 | case "g": 41 | return val * 1024 * 1024 * 1024; 42 | default: 43 | return long.MinValue; 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/RestartPolicyDefinition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ductus.FluentDocker.Model.Compose 4 | { 5 | /// 6 | /// Configures if and how to restart containers when they exit. Replaces 7 | /// . 8 | /// 9 | public sealed class RestartPolicyDefinition 10 | { 11 | /// 12 | /// One of none, on-failure or any (default: any). 13 | /// 14 | public string Condition { get; set; } = "any"; 15 | /// 16 | /// How long to wait between restart attempts, specified as a duration (default: 0). 17 | /// 18 | /// 19 | /// For example delay: 5s 20 | /// 21 | public string Delay { get; set; } = "0"; 22 | /// 23 | /// How many times to attempt to restart a container before giving up (default: never give up). 24 | /// 25 | /// 26 | /// If the restart does not succeed within the configured window, this attempt doesn’t count toward the 27 | /// configured max_attempts value. For example, if max_attempts is set to ‘2’, and the restart fails on the first 28 | /// attempt, more than two restarts may be attempted. 29 | /// 30 | public int MaxAttempts { get; set; } = Int32.MaxValue; 31 | /// 32 | /// How long to wait before deciding if a restart has succeeded. 33 | /// 34 | /// 35 | /// This specified as a duration (default: decide immediately). For example window: 120s. 36 | /// 37 | public string Window { get; set; } 38 | } 39 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/Parsers/BaseInfoResponseParser.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Model; 2 | using Ductus.FluentDocker.Model.Containers; 3 | 4 | namespace Ductus.FluentDocker.Executors.Parsers 5 | { 6 | public sealed class BaseInfoResponseParser : IProcessResponseParser 7 | { 8 | public CommandResponse Response { get; private set; } 9 | 10 | public IProcessResponse Process(ProcessExecutionResult response) 11 | { 12 | if (response.ExitCode != 0) 13 | { 14 | Response = 15 | response.ToErrorResponse(new DockerInfoBase 16 | { 17 | ClientApiVersion = string.Empty, 18 | ClientVersion = string.Empty, 19 | ServerApiVersion = string.Empty, 20 | ServerVersion = string.Empty 21 | }); 22 | return this; 23 | } 24 | 25 | var s = response.StdOut.TrimEnd('\r','\n').Split(';'); 26 | if (s.Length != 5) 27 | { 28 | Response = 29 | response.ToErrorResponse(new DockerInfoBase 30 | { 31 | ClientApiVersion = string.Empty, 32 | ClientVersion = string.Empty, 33 | ServerApiVersion = string.Empty, 34 | ServerVersion = string.Empty 35 | }); 36 | return this; 37 | } 38 | 39 | 40 | Response = response.ToResponse(true, string.Empty, new DockerInfoBase 41 | { 42 | ClientApiVersion = s[3], 43 | ClientVersion = s[2], 44 | ServerApiVersion = s[1], 45 | ServerVersion = s[0], 46 | ServerOs = s[4] 47 | }); 48 | return this; 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Resources/ResourceReader.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Ductus.FluentDocker.Resources 6 | { 7 | public sealed class ResourceReader : IEnumerable 8 | { 9 | private readonly ResourceInfo[] _resources; 10 | 11 | public ResourceReader(IEnumerable resources) 12 | { 13 | _resources = resources.ToArray(); 14 | } 15 | 16 | public IEnumerator GetEnumerator() 17 | { 18 | return new ResourceStreamEnumerator(_resources); 19 | } 20 | 21 | IEnumerator IEnumerable.GetEnumerator() 22 | { 23 | return GetEnumerator(); 24 | } 25 | 26 | private sealed class ResourceStreamEnumerator : IEnumerator 27 | { 28 | private readonly ResourceInfo[] _resources; 29 | private int _pos = -1; 30 | 31 | internal ResourceStreamEnumerator(ResourceInfo[] resources) 32 | { 33 | _resources = resources; 34 | } 35 | 36 | public void Dispose() 37 | { 38 | _pos = -2; 39 | } 40 | 41 | public bool MoveNext() 42 | { 43 | return ++_pos < _resources.Length; 44 | } 45 | 46 | public void Reset() 47 | { 48 | _pos = -1; 49 | } 50 | 51 | public ResourceStream Current 52 | { 53 | get 54 | { 55 | var res = _resources[_pos]; 56 | return new ResourceStream(res.Assembly.GetManifestResourceStream($"{res.Namespace}.{res.Resource}"), res); 57 | } 58 | } 59 | 60 | object IEnumerator.Current => Current; 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/ServiceTests/DockerComposeTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Ductus.FluentDocker.Model.Common; 7 | using Ductus.FluentDocker.Model.Compose; 8 | using Ductus.FluentDocker.Services.Impl; 9 | using Ductus.FluentDocker.Tests.Extensions; 10 | using Ductus.FluentDocker.Services.Extensions; 11 | using Microsoft.VisualStudio.TestTools.UnitTesting; 12 | // ReSharper disable StringLiteralTypo 13 | 14 | namespace Ductus.FluentDocker.Tests.ServiceTests 15 | { 16 | [TestClass] 17 | public class DockerComposeTests : FluentDockerTestBase 18 | { 19 | [TestMethod] 20 | public async Task WordPressDockerComposeServiceShallShowInstallScreen() 21 | { 22 | var file = Path.Combine(Directory.GetCurrentDirectory(), 23 | (TemplateString) "Resources/ComposeTests/WordPress/docker-compose.yml"); 24 | 25 | using (var svc = new DockerComposeCompositeService(DockerHost, new DockerComposeConfig 26 | { 27 | ComposeFilePath = new List { file }, ForceRecreate = true, RemoveOrphans = true, 28 | StopOnDispose = true 29 | })) 30 | { 31 | svc.Start(); 32 | 33 | svc.Containers.First(x => x.Name == "wordpress").WaitForHttp("http://localhost:8000/wp-admin/install.php"); 34 | 35 | // We now have a running WordPress with a MySql database 36 | var installPage = await $"http://localhost:8000/wp-admin/install.php".Wget(); 37 | 38 | Assert.IsTrue(installPage.IndexOf("https://wordpress.org/", StringComparison.Ordinal) != -1); 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Containers/ImageConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | // ReSharper disable ClassNeverInstantiated.Global 4 | 5 | namespace Ductus.FluentDocker.Model.Containers 6 | { 7 | public sealed class ImageConfig 8 | { 9 | public string Id { get; set; } 10 | public string[] RepoTags { get; set; } 11 | public string[] RepoDigests { get; set; } 12 | public string Parent { get; set; } 13 | public string Comment { get; set; } 14 | public DateTime Created { get; set; } 15 | public string Container { get; set; } 16 | public ContainerConfig ContainerConfig { get; set; } 17 | public string DockerVersion { get; set; } 18 | public string Author { get; set; } 19 | public ContainerConfig Config { get; set; } 20 | public string Architecture { get; set; } 21 | public string Os { get; set; } 22 | public long Size { get; set; } 23 | public long VirtualSize { get; set; } 24 | public GraphDriver GraphDriver { get; set; } 25 | public FileSystem RootFS { get; set; } 26 | public IDictionary Metadata { get; set; } 27 | } 28 | 29 | public sealed class GraphDriver 30 | { 31 | public string Name { get; set; } 32 | public GraphDriverData Data { get; set; } 33 | } 34 | 35 | public sealed class GraphDriverData 36 | { 37 | public string LowerDir { get; set; } 38 | public string MergedDir { get; set; } 39 | public string UpperDir { get; set; } 40 | public string WorkDir { get; set; } 41 | } 42 | 43 | public sealed class FileSystem 44 | { 45 | public string Type { get; set; } 46 | public string[] Layers { get; set; } 47 | } 48 | } -------------------------------------------------------------------------------- /data/docker_network_inspect.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name": "bridge", 4 | "Id": "1da1d2ba3e0e218059d41c5bd0009a57a4ed6917db53dfbbe604f536311b87e6", 5 | "Created": "2018-04-19T19:55:05.71085Z", 6 | "Scope": "local", 7 | "Driver": "bridge", 8 | "EnableIPv6": false, 9 | "IPAM": { 10 | "Driver": "default", 11 | "Options": null, 12 | "Config": [ 13 | { 14 | "Subnet": "172.17.0.0/16", 15 | "Gateway": "172.17.0.1" 16 | } 17 | ] 18 | }, 19 | "Internal": false, 20 | "Attachable": false, 21 | "Ingress": false, 22 | "ConfigFrom": { 23 | "Network": "" 24 | }, 25 | "ConfigOnly": false, 26 | "Containers": { 27 | "86c345fd6115e56de9f3e798d97db544e0df8ed658d7370d2543524a83894d7a": { 28 | "Name": "nifi", 29 | "EndpointID": "78ce77249fb68f5d805e8c47a248801af66579c2f89d10b2d9635de53699e8aa", 30 | "MacAddress": "02:42:ac:11:00:02", 31 | "IPv4Address": "172.17.0.2/16", 32 | "IPv6Address": "" 33 | } 34 | }, 35 | "Options": { 36 | "com.docker.network.bridge.default_bridge": "true", 37 | "com.docker.network.bridge.enable_icc": "true", 38 | "com.docker.network.bridge.enable_ip_masquerade": "true", 39 | "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0", 40 | "com.docker.network.bridge.name": "docker0", 41 | "com.docker.network.driver.mtu": "1500" 42 | }, 43 | "Labels": {} 44 | } 45 | ] 46 | -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/FluentApiTests/ImageBuilderTests.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Tests.Extensions; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace Ductus.FluentDocker.Tests.FluentApiTests 5 | { 6 | [TestClass] 7 | public class ImageBuilderTests 8 | { 9 | [ClassInitialize] 10 | public static void Initialize(TestContext ctx) 11 | { 12 | Utilities.LinuxMode(); 13 | } 14 | 15 | [TestMethod] 16 | [Ignore] 17 | public void BuildImageFromFileWithCopyAndRunInstructionShallWork() 18 | { 19 | using ( 20 | var image = 21 | Fd.DefineImage("mariotoffia/unittest:latest") 22 | .From("ubuntu:14.04") 23 | .Maintainer("Mario Toffia ") 24 | .Run("apt-get update") 25 | .Run("apt-get install -y software-properties-common python") 26 | .Run("add-apt-repository ppa:chris-lea/node.js") 27 | .Run("echo \"deb http://us.archive.ubuntu.com/ubuntu/ precise universe\" >> /etc/apt/sources.list") 28 | .Run("apt-get update") 29 | .Run("apt-get install -y nodejs") 30 | .Run("mkdir /var/www") 31 | .Add("emb:Ductus.FluentDocker.Tests/Ductus.FluentDocker.Tests.MultiContainerTestFiles/app.js", "/var/www/app.js") 32 | .Command("/usr/bin/node", "/var/www/app.js") 33 | .Build()) 34 | { 35 | var config = image.GetConfiguration(true); 36 | Assert.IsNotNull(config); 37 | Assert.AreEqual(2,config.Config.Cmd.Length); 38 | Assert.AreEqual("/usr/bin/node", config.Config.Cmd[0]); 39 | Assert.AreEqual("/var/www/app.js", config.Config.Cmd[1]); 40 | } 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Extensions/Utils/DockerBinary.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Ductus.FluentDocker.Model.Common; 3 | 4 | namespace Ductus.FluentDocker.Extensions.Utils 5 | { 6 | public enum DockerBinaryType 7 | { 8 | DockerClient = 1, 9 | Machine = 2, 10 | Compose = 3, 11 | Cli = 4 12 | } 13 | 14 | public sealed class DockerBinary 15 | { 16 | internal DockerBinary(string path, string binary, SudoMechanism sudo, string password) 17 | { 18 | Path = path; 19 | Binary = binary.ToLower(); 20 | Type = Translate(binary); 21 | Sudo = sudo; 22 | SudoPassword = password; 23 | 24 | var isToolbox = Environment.GetEnvironmentVariable("DOCKER_TOOLBOX_INSTALL_PATH")?.Equals(Path); 25 | IsToolbox = isToolbox ?? false; 26 | } 27 | 28 | public static DockerBinaryType Translate(string binary) 29 | { 30 | switch (binary.ToLower()) 31 | { 32 | case "docker": 33 | case "docker.exe": 34 | return DockerBinaryType.DockerClient; 35 | case "docker-machine": 36 | case "docker-machine.exe": 37 | return DockerBinaryType.Machine; 38 | case "docker-compose": 39 | case "docker-compose.exe": 40 | return DockerBinaryType.Compose; 41 | case "dockercli": 42 | case "dockercli.exe": 43 | return DockerBinaryType.Cli; 44 | default: 45 | throw new Exception($"Cannot determine the docker type on binary {binary}"); 46 | } 47 | } 48 | 49 | public string FqPath => System.IO.Path.Combine(Path, Binary); 50 | public string Path { get; } 51 | public string Binary { get; } 52 | public DockerBinaryType Type { get; } 53 | public bool IsToolbox { get; } 54 | public SudoMechanism Sudo { get; } 55 | public string SudoPassword { get; } 56 | } 57 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/ContainerSpecificConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Http; 4 | using Ductus.FluentDocker.Common; 5 | using Ductus.FluentDocker.Model.Common; 6 | using Ductus.FluentDocker.Services; 7 | 8 | namespace Ductus.FluentDocker.Model.Compose 9 | { 10 | public sealed class ContainerSpecificConfig 11 | { 12 | /// 13 | /// Name of the container matching the compose file service name. 14 | /// 15 | public string Name { get; set; } 16 | public Tuple WaitForPort { get; set; } 17 | public List WaitForHttp { get; } = new List(); 18 | public List> WaitLambda { get; } = new List>(); 19 | public Tuple WaitForProcess { get; set; } 20 | public List> CpToOnStart { get; set; } 21 | public List> CpFromOnDispose { get; set; } 22 | 23 | public Tuple /*condition*/> ExportOnDispose { get; set; } 25 | 26 | public List ExecuteOnRunningArguments { get; set; } 27 | public List ExecuteOnDisposingArguments { get; set; } 28 | 29 | public sealed class WaitForHttpParams 30 | { 31 | public string Url { get; set; } 32 | public long Timeout { get; set; } 33 | public Func Continuation { get; set; } 34 | public HttpMethod Method { get; set; } 35 | public string ContentType { get; set; } 36 | public string Body { get; set; } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Builders/FileBuilderConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text; 3 | using Ductus.FluentDocker.Model.Common; 4 | 5 | namespace Ductus.FluentDocker.Model.Builders 6 | { 7 | public sealed class FileBuilderConfig 8 | { 9 | public string DockerFileString { get; set; } 10 | public TemplateString UseFile { get; set; } 11 | public string From { get; set; } 12 | public string Maintainer { get; set; } 13 | public IList AddRunCommands { get; } = new List(); 14 | public string Workdir { get; set; } 15 | public IList Expose { get; set; } 16 | public IList Command { get; } = new List(); 17 | 18 | public override string ToString() 19 | { 20 | var sb = new StringBuilder(); 21 | 22 | sb.Append("FROM ").AppendLine(From); 23 | if (!string.IsNullOrWhiteSpace(Maintainer)) 24 | { 25 | sb.Append("MAINTAINER ").AppendLine(Maintainer); 26 | } 27 | 28 | if (0 != AddRunCommands.Count) 29 | { 30 | foreach (var command in AddRunCommands) 31 | { 32 | sb.AppendLine(command.ToString()); 33 | } 34 | } 35 | 36 | if (!string.IsNullOrWhiteSpace(Workdir)) 37 | { 38 | sb.Append("WORKDIR ").AppendLine(Workdir); 39 | } 40 | 41 | if (null != Expose && 0 != Expose.Count) 42 | { 43 | sb.Append("EXPOSE ").AppendLine(string.Join(" ", Expose)); 44 | } 45 | 46 | if (0 != Command.Count) 47 | { 48 | sb.Append("CMD ["); 49 | for (var i = 0; i < Command.Count; i++) 50 | { 51 | sb.Append('"').Append(Command[i]).Append('"').Append(i == Command.Count - 1 ? string.Empty : ","); 52 | } 53 | sb.AppendLine("]"); 54 | } 55 | 56 | return sb.ToString(); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Stacks/StackPsResponse.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Stacks 2 | { 3 | public class StackPsResponse 4 | { 5 | public string Id { get; set; } 6 | /// 7 | /// The stack that this belongs to. 8 | /// 9 | public string Stack { get; set; } 10 | /// 11 | /// The name of the instance without the stack name 12 | /// 13 | /// 14 | /// The output of a docker stack will emit stack_name where this property is just the name. 15 | /// 16 | public string Name { get; set; } 17 | /// 18 | /// The name of the image without any version. 19 | /// 20 | public string Image { get; set; } 21 | /// 22 | /// The version of the . 23 | /// 24 | public string ImageVersion { get; set; } 25 | /// 26 | /// The node name. 27 | /// 28 | public string Node { get; set; } 29 | /// 30 | /// The desired state. 31 | /// 32 | public string DesiredState { get; set; } 33 | /// 34 | /// The actual state. This may be different from . 35 | /// 36 | /// 37 | /// Even if this state is the it cannot be compared 38 | /// directly since it may e.g. be 'Running sicne 2 minutes ago' wherease the 39 | /// is 'Running'. 40 | /// 41 | public string CurrentState { get; set; } 42 | /// 43 | /// If any error otherwise . 44 | /// 45 | public string Error { get; set; } 46 | /// 47 | /// Any exposed ports, otherwise . 48 | /// 49 | public string Ports { get; set; } 50 | } 51 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Extensions/ServiceExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using Ductus.FluentDocker.Common; 4 | using Ductus.FluentDocker.Services; 5 | 6 | namespace Ductus.FluentDocker.Extensions 7 | { 8 | public static class ServiceExtensions 9 | { 10 | private static void WaitForState(this IContainerService container, ServiceRunningState state) 11 | { 12 | WaitForStateObject so; 13 | using (var mre = new ManualResetEventSlim()) 14 | { 15 | so = new WaitForStateObject {Mre = mre, Container = container, State = state}; 16 | using (new Timer(Callback, so, 0, 500)) 17 | { 18 | mre.Wait(); 19 | } 20 | } 21 | 22 | if (so.Exception != null) 23 | throw so.Exception; 24 | } 25 | 26 | private static void Callback(object state) 27 | { 28 | var obj = (WaitForStateObject) state; 29 | var containerState = obj.Container.GetConfiguration(true).State; 30 | if (!string.IsNullOrWhiteSpace(containerState.Error)) 31 | { 32 | obj.Exception = new FluentDockerException($"Unable to start container: {containerState.Error}"); 33 | obj.Mre.Set(); 34 | } 35 | 36 | if (containerState.ToServiceState() == obj.State) 37 | obj.Mre.Set(); 38 | } 39 | 40 | public static void WaitForRunning(this IContainerService container) 41 | { 42 | WaitForState(container, ServiceRunningState.Running); 43 | } 44 | 45 | public static void WaitForStopped(this IContainerService container) 46 | { 47 | WaitForState(container, ServiceRunningState.Stopped); 48 | } 49 | 50 | private sealed class WaitForStateObject 51 | { 52 | public ManualResetEventSlim Mre { get; set; } 53 | public IContainerService Container { get; set; } 54 | public ServiceRunningState State { get; set; } 55 | public Exception Exception { get; set; } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Services/Impl/DockerImageService.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Commands; 2 | using Ductus.FluentDocker.Model.Common; 3 | using Ductus.FluentDocker.Model.Containers; 4 | 5 | namespace Ductus.FluentDocker.Services.Impl 6 | { 7 | public sealed class DockerImageService : ServiceBase, IContainerImageService 8 | { 9 | private ImageConfig _containerConfigCache; 10 | private readonly bool _isWindowsHost; 11 | 12 | public DockerImageService(string name, string id, string tag, DockerUri dockerHost, ICertificatePaths certificate, bool isWindowsHost) 13 | : base(name) 14 | { 15 | _isWindowsHost = isWindowsHost; 16 | Id = id; 17 | Tag = tag; 18 | Certificates = certificate; 19 | DockerHost = dockerHost; 20 | State = ServiceRunningState.Running; 21 | } 22 | 23 | public override void Dispose() 24 | { 25 | Stop(); 26 | } 27 | 28 | public override void Start() 29 | { 30 | State = ServiceRunningState.Starting; 31 | State = ServiceRunningState.Running; 32 | } 33 | 34 | public override void Stop() 35 | { 36 | State = ServiceRunningState.Stopping; 37 | State = ServiceRunningState.Stopped; 38 | } 39 | 40 | public override void Remove(bool force = false) 41 | { 42 | State = ServiceRunningState.Removing; 43 | // TODO: Remove image 44 | State = ServiceRunningState.Removed; 45 | } 46 | 47 | public string Id { get; } 48 | public string Tag { get; } 49 | public DockerUri DockerHost { get; } 50 | public ICertificatePaths Certificates { get; } 51 | 52 | public ImageConfig GetConfiguration(bool fresh = false) 53 | { 54 | if (!fresh && null != _containerConfigCache) 55 | { 56 | return _containerConfigCache; 57 | } 58 | 59 | _containerConfigCache = DockerHost.InspectImage(Id, Certificates).Data; 60 | return _containerConfigCache; 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/ConfigLongDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Compose 2 | { 3 | /// 4 | /// The long syntax provides more granularity in how the config is created within the service’s task containers. 5 | /// 6 | /// 7 | /// Note: config definitions are only supported in version 3.3 and higher of the compose file format. 8 | /// 9 | public sealed class ConfigLongDefinition 10 | { 11 | /// 12 | /// The name of the config as it exists in Docker. 13 | /// 14 | public string Source { get; set; } 15 | /// 16 | /// The path and name of the file to be mounted in the service’s task containers. Defaults to "/source" if not 17 | /// specified. 18 | /// 19 | public string Target { get; set; } 20 | /// 21 | /// The numeric UID that owns the mounted config file within in the service’s task containers. Defaults 22 | /// to 0 on Linux if not specified. Not supported on Windows. 23 | /// 24 | public string Uid { get; set; } 25 | /// 26 | /// The numeric GID that owns the mounted config file within in the service’s task containers. Defaults 27 | /// to 0 on Linux if not specified. Not supported on Windows. 28 | /// 29 | public string Gid { get; set; } 30 | /// 31 | /// The permissions for the file that is mounted within the service’s task containers, in octal notation. 32 | /// 33 | /// 34 | /// For instance, 0444 represents world-readable. The default is 0444. Configs cannot be writable because they 35 | /// are mounted in a temporary filesystem, so if you set the writable bit, it is ignored. The executable bit can 36 | /// be set. If you aren’t familiar with UNIX file permission modes, you may find this permissions calculator useful. 37 | /// 38 | public string Mode { get; set; } 39 | } 40 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/ShortServiceVolumeDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Compose 2 | { 3 | /// 4 | /// Mount host paths or named volumes, specified as sub-options to a service. 5 | /// 6 | /// 7 | /// You can mount a host path as part of a definition for a single service, and there is no need to define it in the top 8 | /// level volumes key. But, if you want to reuse a volume across multiple services, then define a named volume in the 9 | /// top-level volumes key. Use named volumes with services, swarms, and stack files. Note: The top-level volumes key 10 | /// defines a named volume and references it from each service’s volumes list. This replaces volumes_from in earlier 11 | /// versions of the Compose file format. See Use volumes and Volume Plugins for general information on volumes. 12 | /// 13 | public sealed class ShortServiceVolumeDefinition : IServiceVolumeDefinition 14 | { 15 | /// 16 | /// Single volume mapping entry. 17 | /// 18 | /// 19 | /// Optionally specify a path on the host machine (HOST:CONTAINER), or an access mode (HOST:CONTAINER:ro). 20 | /// You can mount a relative path on the host, that expands relative to the directory of the Compose configuration 21 | /// file being used. Relative paths should always begin with . or ... 22 | /// 23 | /// 24 | /// volumes: 25 | /// # Just specify a path and let the Engine create a volume 26 | /// - /var/lib/mysql 27 | /// # Specify an absolute path mapping 28 | /// - /opt/data:/var/lib/mysql 29 | /// # Path on the host, relative to the Compose file 30 | /// - ./cache:/tmp/cache 31 | /// # User-relative path 32 | /// - ~/configs:/etc/configs/:ro 33 | /// # Named volume 34 | /// - datavolume:/var/lib/mysql 35 | /// 36 | public string Entry { get; set; } 37 | } 38 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker.MsTest/FluentDockerComposeTestBase.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Builders; 2 | using Ductus.FluentDocker.Common; 3 | using Ductus.FluentDocker.Model.Common; 4 | using Ductus.FluentDocker.Services; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | 7 | namespace Ductus.FluentDocker.MsTest 8 | { 9 | [Experimental] 10 | public abstract class FluentDockerComposeTestBase 11 | { 12 | protected ICompositeService Service; 13 | protected readonly string ComposeFile; 14 | 15 | protected FluentDockerComposeTestBase(TemplateString fqPathDockerComposeFile) 16 | { 17 | ComposeFile = fqPathDockerComposeFile; 18 | } 19 | // https://github.com/libgit2/libgit2sharp/tree/master/LibGit2Sharp.Tests 20 | [TestInitialize] 21 | public void Initialize() 22 | { 23 | Service = Build().Build(); 24 | try 25 | { 26 | Service.Start(); 27 | } 28 | catch 29 | { 30 | Service.Dispose(); 31 | throw; 32 | } 33 | 34 | OnServiceInitialized(); 35 | } 36 | 37 | [TestCleanup] 38 | public void TeardownContainer() 39 | { 40 | OnServiceTearDown(); 41 | 42 | var c = Service; 43 | Service = null; 44 | try 45 | { 46 | c?.Dispose(); 47 | } 48 | catch 49 | { 50 | // Ignore 51 | } 52 | } 53 | 54 | protected virtual CompositeBuilder Build() 55 | { 56 | return new Builder() 57 | .UseContainer() 58 | .UseCompose() 59 | .FromFile(ComposeFile) 60 | .RemoveOrphans(); 61 | } 62 | 63 | /// 64 | /// Invoked just before the service is teared down. 65 | /// 66 | protected virtual void OnServiceTearDown() 67 | { 68 | } 69 | 70 | /// 71 | /// Invoked after a container has been created and started. 72 | /// 73 | protected virtual void OnServiceInitialized() 74 | { 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Services/Impl/DockerVolumeService.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Commands; 2 | using Ductus.FluentDocker.Common; 3 | using Ductus.FluentDocker.Model.Common; 4 | using Ductus.FluentDocker.Model.Containers; 5 | using Ductus.FluentDocker.Model.Volumes; 6 | 7 | namespace Ductus.FluentDocker.Services.Impl 8 | { 9 | public sealed class DockerVolumeService : ServiceBase, IVolumeService 10 | { 11 | private Volume _config; 12 | private readonly bool _removeOnDispose; 13 | 14 | public DockerVolumeService(string name, DockerUri host, ICertificatePaths certificates, bool removeOnDispose) : base(name) 15 | { 16 | DockerHost = host; 17 | Certificates = certificates; 18 | _removeOnDispose = removeOnDispose; 19 | } 20 | 21 | public override void Dispose() 22 | { 23 | if (_removeOnDispose) 24 | { 25 | Remove(force: true); 26 | } 27 | } 28 | 29 | public override void Start() 30 | { 31 | State = ServiceRunningState.Starting; 32 | State = ServiceRunningState.Running; 33 | } 34 | 35 | public override void Stop() 36 | { 37 | State = ServiceRunningState.Stopping; 38 | State = ServiceRunningState.Stopped; 39 | } 40 | 41 | public override void Remove(bool force = false) 42 | { 43 | State = ServiceRunningState.Removing; 44 | DockerHost.VolumeRm(Certificates, force, Name); 45 | State = ServiceRunningState.Removed; 46 | } 47 | 48 | public DockerUri DockerHost { get; } 49 | public ICertificatePaths Certificates { get; } 50 | public Volume GetConfiguration(bool fresh = false) 51 | { 52 | if (!fresh && null != _config) 53 | return _config; 54 | 55 | var result = DockerHost.VolumeInspect(certificates: Certificates, volume: Name); 56 | if (!result.Success) 57 | throw new FluentDockerException($"Could not run docker volume inspect for volume name: {Name}"); 58 | 59 | return _config = result.Data[0]; 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/FluentDockerTestBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Ductus.FluentDocker.Commands; 4 | using Ductus.FluentDocker.Services; 5 | using Ductus.FluentDocker.Tests.Extensions; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | namespace Ductus.FluentDocker.Tests 9 | { 10 | public abstract class FluentDockerTestBase 11 | { 12 | protected IHostService DockerHost; 13 | private bool _createdHost; 14 | 15 | [TestInitialize] 16 | public void Initialize() 17 | { 18 | EnsureDockerHost(); 19 | } 20 | 21 | [TestCleanup] 22 | public void Teardown() 23 | { 24 | if (_createdHost) 25 | { 26 | "test-machine".Delete(true /*force*/); 27 | } 28 | } 29 | 30 | private void EnsureDockerHost() 31 | { 32 | if (DockerHost?.State == ServiceRunningState.Running) 33 | { 34 | return; 35 | } 36 | 37 | var hosts = new Hosts().Discover(); 38 | DockerHost = hosts.FirstOrDefault(x => x.IsNative) ?? hosts.FirstOrDefault(x => x.Name == "default"); 39 | 40 | if (null != DockerHost) 41 | { 42 | if (DockerHost.State != ServiceRunningState.Running) 43 | { 44 | DockerHost.Start(); 45 | DockerHost.Host.LinuxMode(DockerHost.Certificates); 46 | } 47 | 48 | return; 49 | } 50 | 51 | if (hosts.Count > 0) 52 | { 53 | DockerHost = hosts.First(); 54 | } 55 | 56 | if (null != DockerHost) 57 | { 58 | return; 59 | } 60 | 61 | if (_createdHost) 62 | { 63 | throw new Exception("Failed to initialize the test class, tried to create a docker host but failed"); 64 | } 65 | 66 | var res = "test-machine".Create(1024, 20000, 1); 67 | Assert.AreEqual(true, res.Success); 68 | 69 | var start = "test-machine".Start(); 70 | Assert.AreEqual(true, start.Success); 71 | 72 | _createdHost = true; 73 | EnsureDockerHost(); 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/FluentApiTests/RemoteDaemonTests.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Builders; 2 | using Ductus.FluentDocker.Services; 3 | using Ductus.FluentDocker.Tests.Extensions; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | 6 | namespace Ductus.FluentDocker.Tests.FluentApiTests 7 | { 8 | [TestClass] 9 | public class RemoteDaemonTests 10 | { 11 | [ClassInitialize] 12 | public static void Initialize(TestContext ctx) 13 | { 14 | Utilities.LinuxMode(); 15 | } 16 | 17 | [TestMethod] 18 | [Ignore] 19 | public void CreateSshConnectionToRemoteDockerAndCreateContainerShallWork() 20 | { 21 | using ( 22 | var container = Fd.UseHost() 23 | .UseSsh("192.168.1.34").WithName("remote-daemon") 24 | .WithSshUser("solo").WithSshKeyPath("${E_LOCALAPPDATA}/lxss/home/martoffi/.ssh/id_rsa") 25 | .UseContainer() 26 | .UseImage("postgres:9.6-alpine") 27 | .WithEnvironment("POSTGRES_PASSWORD=mysecretpassword") 28 | .Build()) 29 | { 30 | Assert.AreEqual(ServiceRunningState.Stopped, container.State); 31 | } 32 | } 33 | 34 | [TestMethod] 35 | [Ignore] 36 | public void UseNamedDockerMachineForRemoteSshDaemonConnectionShallWork() 37 | { 38 | var remoteHost = new Builder().UseHost() 39 | .UseSsh("192.168.1.34").WithName("remote-daemon") 40 | .WithSshUser("solo").WithSshKeyPath("${E_LOCALAPPDATA}/lxss/home/martoffi/.ssh/id_rsa").Build(); 41 | 42 | Assert.IsTrue(remoteHost.Host.ToString().StartsWith("tcp://")); 43 | 44 | using ( 45 | var container = Fd.UseHost() 46 | .UseMachine().WithName("remote-daemon") 47 | .UseContainer() 48 | .UseImage("postgres:9.6-alpine") 49 | .WithEnvironment("POSTGRES_PASSWORD=mysecretpassword") 50 | .Build()) 51 | { 52 | Assert.AreEqual(ServiceRunningState.Stopped, container.State); 53 | } 54 | } 55 | 56 | } 57 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Ductus.FluentDocker.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0;net462 5 | $(DefineConstants);COREFX 6 | $(DefineConstants);COREFX 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | PreserveNewest 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Builders/HostBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Ductus.FluentDocker.Services; 3 | 4 | namespace Ductus.FluentDocker.Builders 5 | { 6 | public sealed class HostBuilder : BaseBuilder 7 | { 8 | internal HostBuilder(IBuilder builder) : base(builder) 9 | { 10 | } 11 | 12 | public override IHostService Build() 13 | { 14 | return IsNative ? new Hosts().Native() : null; 15 | } 16 | 17 | protected override IBuilder InternalCreate() 18 | { 19 | return new HostBuilder(this); 20 | } 21 | 22 | public bool IsNative { get; private set; } 23 | 24 | public HostBuilder UseNative() 25 | { 26 | IsNative = true; 27 | return this; 28 | } 29 | 30 | public MachineBuilder UseMachine() 31 | { 32 | var existing = Childs.FirstOrDefault(x => x is MachineBuilder); 33 | if (null != existing) 34 | { 35 | return (MachineBuilder) existing; 36 | } 37 | 38 | var builder = new MachineBuilder(this); 39 | Childs.Add(builder); 40 | return builder; 41 | } 42 | 43 | public RemoteSshHostBuilder UseSsh(string ipAddress = null) 44 | { 45 | var builder = new RemoteSshHostBuilder(this, ipAddress); 46 | Childs.Add(builder); 47 | return builder; 48 | } 49 | 50 | public ImageBuilder DefineImage(string image = null) 51 | { 52 | var builder = new ImageBuilder(this).AsImageName(image); 53 | Childs.Add(builder); 54 | return builder; 55 | } 56 | 57 | public ContainerBuilder UseContainer() 58 | { 59 | var builder = new ContainerBuilder(this); 60 | Childs.Add(builder); 61 | return builder; 62 | } 63 | 64 | public NetworkBuilder UseNetwork(string name = null) 65 | { 66 | var builder = new NetworkBuilder(this, name); 67 | Childs.Add(builder); 68 | return builder; 69 | } 70 | 71 | public VolumeBuilder UseVolume(string name = null) 72 | { 73 | var builder = new VolumeBuilder(this, name); 74 | Childs.Add(builder); 75 | return builder; 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/DeployConfigDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Compose 2 | { 3 | public sealed class DeployConfigDefinition 4 | { 5 | /// 6 | /// The number of container at a time for the operation. 7 | /// 8 | /// 9 | /// Rollback: The number of containers to rollback at a time. If set to 0, all containers rollback simultaneously. 10 | /// Update: The number of containers to update at a time. 11 | /// 12 | public int Parallelism { get; set; } = 0; 13 | /// 14 | /// The time to wait. 15 | /// 16 | /// 17 | /// Rollback: The time to wait between each container group’s rollback (default 0s). 18 | /// Update: The time to wait between updating a group of containers. 19 | /// 20 | public string Delay { get; set; } 21 | /// 22 | /// What to do if fails (default pause). 23 | /// 24 | /// 25 | /// Rollback: What to do if a rollback fails. One of continue or pause. 26 | /// Update: What to do if an update fails. One of continue, rollback, or pause. 27 | /// 28 | public string FailureAction { get; set; } = "pause"; 29 | /// 30 | /// Duration after each task update to monitor for failure. 31 | /// 32 | /// 33 | /// (ns|us|ms|s|m|h) - default 0s. 34 | /// 35 | public string Monitor { get; set; } = "0s"; 36 | /// 37 | /// Failure rate to tolerate during rollback or update, default 0. 38 | /// 39 | public int MaxFailureRatio { get; set; } = 0; 40 | /// 41 | /// Order of operations during updates or rollback. 42 | /// 43 | /// 44 | /// Note: Only supported for v3.4 and higher. 45 | /// One of stop-first (old task is stopped before starting new one), or start-first (new task is started first, 46 | /// and the running tasks briefly overlap) (default stop-first). 47 | /// 48 | public string Order { get; set; } = "stop-first"; 49 | } 50 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker.MsTest/Ductus.FluentDocker.MsTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp1.1;netcoreapp2.0;net452 5 | 2.6.6 6 | Ductus.FluentDocker.MsTest 7 | Mario Toffia 8 | Apache-2.0 9 | https://github.com/mariotoffia/FluentDocker 10 | https://pbs.twimg.com/profile_images/378800000124779041/fbbb494a7eef5f9278c6967b6072ca3e.png 11 | false 12 | Docker;Docker-Compose;Docker Compose;Docker-Machine;Docker Machine;Linux;Windows;Mac;Test;NET Core 13 | git 14 | https://github.com/mariotoffia/FluentDocker 15 | 16 | Ms Test Support to allow for create, run one or more docker images while testing using docker, compose, machine (Linux, Windows, Mac) using netcore or full framework. 17 | Documentation: https://github.com/mariotoffia/FluentDocker 18 | 19 | © 2016 - 2019 Mario Toffia 20 | $(DefineConstants);COREFX 21 | True 22 | 2.6.6 23 | ..\keypair.snk 24 | true 25 | true 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Builders/ContainerBuilderConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Ductus.FluentDocker.Model.Common; 4 | using Ductus.FluentDocker.Model.Compose; 5 | using Ductus.FluentDocker.Model.Containers; 6 | using Ductus.FluentDocker.Services; 7 | 8 | namespace Ductus.FluentDocker.Model.Builders 9 | { 10 | public sealed class ContainerBuilderConfig 11 | { 12 | public ContainerBuilderConfig() 13 | { 14 | CreateParams = new ContainerCreateParams(); 15 | } 16 | 17 | public bool VerifyExistence { get; set; } 18 | public ContainerCreateParams CreateParams { get; } 19 | public string Image { get; set; } 20 | public bool IsWindowsImage { get; set; } 21 | public bool StopOnDispose { get; set; } = true; 22 | public bool DeleteOnDispose { get; set; } = true; 23 | public bool DeleteVolumeOnDispose { get; set; } = false; 24 | public bool DeleteNamedVolumeOnDispose { get; set; } = false; 25 | public string Command { get; set; } 26 | public string[] Arguments { get; set; } 27 | public Tuple WaitForPort { get; set; } 28 | 29 | public List WaitForHttp { get; } = 30 | new List(); 31 | 32 | public List> WaitLambda { get; } = new List>(); 33 | 34 | public Tuple WaitForProcess { get; set; } 35 | public List> CpToOnStart { get; set; } 36 | public List> CpFromOnDispose { get; set; } 37 | 38 | public Tuple /*condition*/> ExportOnDispose { get; set; } 40 | 41 | public List Networks { get; set; } 42 | public List NetworkNames { get; set; } 43 | public List ExecuteOnRunningArguments { get; set; } 44 | public List ExecuteOnDisposingArguments { get; set; } 45 | } 46 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/Parsers/MachineInspectResponseParser.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Ductus.FluentDocker.Model.Containers; 3 | using Ductus.FluentDocker.Model.Machines; 4 | using Newtonsoft.Json; 5 | using Newtonsoft.Json.Linq; 6 | 7 | namespace Ductus.FluentDocker.Executors.Parsers 8 | { 9 | public sealed class MachineInspectResponseParser : IProcessResponseParser 10 | { 11 | public CommandResponse Response { get; private set; } 12 | 13 | public IProcessResponse Process(ProcessExecutionResult response) 14 | { 15 | if (string.IsNullOrEmpty(response.StdOut)) 16 | { 17 | Response = response.ToResponse(false, "No response", 18 | new MachineConfiguration {AuthConfig = new MachineAuthConfig()}); 19 | return this; 20 | } 21 | 22 | var j = JObject.Parse(response.StdOut); 23 | 24 | var str = j["HostOptions"]["AuthOptions"].ToString(); 25 | var ip = j["Driver"]["IPAddress"].Value(); 26 | var authConfig = JsonConvert.DeserializeObject(str); 27 | 28 | int memsize = 0; 29 | if (null != j["Driver"]["Memory"]) 30 | { 31 | memsize = j["Driver"]["Memory"].Value(); 32 | } 33 | 34 | if (null != j["Driver"]["MemSize"]) 35 | { 36 | memsize = j["Driver"]["MemSize"].Value(); 37 | } 38 | 39 | var config = new MachineConfiguration 40 | { 41 | AuthConfig = authConfig, 42 | IpAddress = string.IsNullOrEmpty(ip) ? IPAddress.None : IPAddress.Parse(ip), 43 | DriverName = null != j["DriverName"] ? j["DriverName"].Value() : "unknown", 44 | MemorySizeMb = memsize, 45 | Name = null != j["Name"] ? j["Name"].Value() : string.Empty, 46 | RequireTls = j["HostOptions"]["EngineOptions"]["TlsVerify"].Value(), 47 | StorageSizeMb = j["Driver"]["DiskSize"]?.Value() ?? 0, 48 | CpuCount = j["Driver"]["CPU"]?.Value() ?? 0, 49 | StorePath = j["Driver"]["StorePath"]?.Value() ?? string.Empty 50 | }; 51 | 52 | Response = response.ToResponse(true, string.Empty, config); 53 | return this; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/LongServiceVolumeDefinition.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Ductus.FluentDocker.Model.Compose 4 | { 5 | /// 6 | /// Mount host paths or named volumes, specified as sub-options to a service. 7 | /// 8 | /// 9 | /// You can mount a host path as part of a definition for a single service, and there is no need to define it in the top 10 | /// level volumes key. But, if you want to reuse a volume across multiple services, then define a named volume in the 11 | /// top-level volumes key. Use named volumes with services, swarms, and stack files. Note: The top-level volumes key 12 | /// defines a named volume and references it from each service’s volumes list. This replaces volumes_from in earlier 13 | /// versions of the Compose file format. See Use volumes and Volume Plugins for general information on volumes. Note: The 14 | /// long syntax is new in v3.2 15 | /// 16 | public sealed class LongServiceVolumeDefinition : IServiceVolumeDefinition 17 | { 18 | /// 19 | /// the source of the mount, a path on the host for a bind mount, or the name of a volume defined in the top-level 20 | /// volumes key. Not applicable for a tmpfs mount. 21 | /// 22 | public string Source { get; set; } 23 | 24 | /// 25 | /// the path in the container where the volume is mounted. 26 | /// 27 | public string Target { get; set; } 28 | 29 | /// 30 | /// The mount type. 31 | /// 32 | public VolumeType Type { get; set; } 33 | 34 | /// 35 | /// flag to set the volume as read-only. 36 | /// 37 | public bool IsReadOnly { get; set; } 38 | 39 | /// 40 | /// Options if any. 41 | /// 42 | /// 43 | /// * bind-> propagation: the propagation mode used for the bind. 44 | /// * volume -> nocopy: flag to disable copying of data from a container when a volume is created. 45 | /// * tmpfs -> size: the size for the tmpfs mount in bytes. 46 | /// 47 | public IDictionary Options { get; set; } = new Dictionary(); 48 | } 49 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/PlacementDefinition.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Ductus.FluentDocker.Model.Compose 4 | { 5 | /// 6 | /// Specify placement of constraints and preferences. 7 | /// 8 | public sealed class PlacementDefinition 9 | { 10 | /// 11 | /// Limit the set of nodes where a task can be scheduled by defining constraint expressions. 12 | /// 13 | /// 14 | /// Multiple constraints find nodes that satisfy every expression (AND match). Constraints can match node or 15 | /// Docker Engine labels as follows: 16 | /// 17 | /// 18 | /// node attribute 19 | /// matches 20 | /// example 21 | /// 22 | /// 23 | /// node.id 24 | /// Node ID 25 | /// node.id==2ivku8v2gvtg4 26 | /// 27 | /// 28 | /// node.hostname 29 | /// Node hostname 30 | /// node.hostname!=node-2 31 | /// 32 | /// 33 | /// node.role 34 | /// Node role 35 | /// node.role==manager 36 | /// 37 | /// 38 | /// node.labels 39 | /// user defined node labels 40 | /// node.labels.security==high 41 | /// 42 | /// 43 | /// engine.labels 44 | /// Docker Engine's labels 45 | /// engine.labels.operatingsystem==ubuntu 14.04 46 | /// 47 | /// 48 | /// Engine.labels apply to Docker Engine labels like operating system, drivers, etc. Swarm administrators add 49 | /// node.labels for operational purposes by using the docker node update command. 50 | /// 51 | public IList Constraints { get; set; } = new List(); 52 | /// 53 | /// Set up the service to divide tasks evenly over different categories of nodes. 54 | /// 55 | /// 56 | /// For example spread: node.labels.zone 57 | /// 58 | public IDictionary Preferences { get; set; } = new Dictionary(); 59 | } 60 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Commands/ClientStreams.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using Ductus.FluentDocker.Executors; 4 | using Ductus.FluentDocker.Executors.Mappers; 5 | using Ductus.FluentDocker.Extensions; 6 | using Ductus.FluentDocker.Model.Common; 7 | using Ductus.FluentDocker.Model.Containers; 8 | 9 | namespace Ductus.FluentDocker.Commands 10 | { 11 | public static class ClientStreams 12 | { 13 | public static ConsoleStream Logs(this DockerUri host, string id, 14 | CancellationToken cancellationToken = default(CancellationToken), 15 | bool follow = false, bool showTimeStamps = false, DateTime? since = null, int? numLines = null, 16 | ICertificatePaths certificates = null) 17 | { 18 | var args = $"{host.RenderBaseArgs(certificates)}"; 19 | 20 | var options = string.Empty; 21 | if (follow) 22 | { 23 | options += " -f"; 24 | } 25 | 26 | if (null != since) 27 | { 28 | options += $" --since {since}"; 29 | } 30 | 31 | options += numLines.HasValue ? $" --tail={numLines}" : " --tail=all"; 32 | 33 | if (showTimeStamps) 34 | { 35 | options += " -t"; 36 | } 37 | 38 | return 39 | new StreamProcessExecutor( 40 | "docker".ResolveBinary(), 41 | $"{args} logs {options} {id}").Execute(cancellationToken); 42 | } 43 | 44 | public static ConsoleStream Events(this DockerUri host, 45 | CancellationToken cancellationToken = default(CancellationToken), 46 | string []filters = null, DateTime ?since = null, DateTime? until = null, 47 | ICertificatePaths certificates = null) 48 | { 49 | var args = $"{host.RenderBaseArgs(certificates)}"; 50 | 51 | var options = string.Empty; 52 | if (null != since) 53 | { 54 | options += $" --since {since}"; 55 | } 56 | 57 | if (null != until) 58 | { 59 | options += $" --since {until}"; 60 | } 61 | 62 | if (null != filters && 0 != filters.Length) 63 | { 64 | foreach (var filter in filters) 65 | { 66 | options += $" --filter={filter}"; 67 | } 68 | } 69 | 70 | return 71 | new StreamProcessExecutor( 72 | "docker".ResolveBinary(), 73 | $"{args} events {options}").Execute(cancellationToken); 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/ServiceTests/NetworkServiceTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Ductus.FluentDocker.Commands; 4 | using Ductus.FluentDocker.Services; 5 | using Ductus.FluentDocker.Tests.Extensions; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | namespace Ductus.FluentDocker.Tests.ServiceTests 9 | { 10 | [TestClass] 11 | public sealed class NetworkServiceTests 12 | { 13 | private static IHostService _host; 14 | private static bool _createdHost; 15 | 16 | [ClassInitialize] 17 | public static void Initialize(TestContext ctx) 18 | { 19 | var hosts = new Hosts().Discover(); 20 | _host = hosts.FirstOrDefault(x => x.IsNative) ?? hosts.FirstOrDefault(x => x.Name == "default"); 21 | 22 | if (null != _host && _host.State != ServiceRunningState.Running) 23 | { 24 | _host.Start(); 25 | _host.Host.LinuxMode(_host.Certificates); 26 | return; 27 | } 28 | 29 | if (null == _host && hosts.Count > 0) 30 | _host = hosts.First(); 31 | 32 | if (null == _host) 33 | { 34 | if (_createdHost) 35 | throw new Exception("Failed to initialize the test class, tried to create a docker host but failed"); 36 | 37 | var res = "test-machine".Create(1024, 20000, 1); 38 | Assert.AreEqual(true, res.Success); 39 | 40 | var start = "test-machine".Start(); 41 | Assert.AreEqual(true, start.Success); 42 | 43 | _createdHost = true; 44 | Initialize(ctx); 45 | } 46 | } 47 | 48 | [ClassCleanup] 49 | public static void TearDown() 50 | { 51 | if (_createdHost) 52 | "test-machine".Delete(true /*force*/); 53 | } 54 | 55 | [TestMethod] 56 | public void DiscoverNetworksShallWork() 57 | { 58 | var networks = _host.GetNetworks(); 59 | Assert.IsTrue(networks.Count > 0); 60 | Assert.IsTrue(networks.Count(x => x.Name == "bridge") == 1); 61 | Assert.IsTrue(networks.Count(x => x.Name == "host") == 1); 62 | } 63 | 64 | [TestMethod] 65 | public void NetworkIsDeletedWhenDisposedAndFlagIsSet() 66 | { 67 | using (var nw = _host.CreateNetwork("unit-test-network", removeOnDispose: true)) 68 | { 69 | Assert.IsTrue(nw.Id.Length > 0); 70 | Assert.AreEqual("unit-test-network", nw.Name); 71 | } 72 | 73 | var networks = _host.GetNetworks(); 74 | Assert.IsTrue(networks.Count(x => x.Name == "unit-test-network") == 0); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Builders/BaseBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.ObjectModel; 3 | using System.Linq; 4 | using System.Reflection; 5 | using Ductus.FluentDocker.Common; 6 | using Ductus.FluentDocker.Services; 7 | 8 | namespace Ductus.FluentDocker.Builders 9 | { 10 | public abstract class BaseBuilder : IBuilder 11 | { 12 | private readonly Option _parent; 13 | protected readonly IList Childs = new List(); 14 | 15 | protected BaseBuilder(IBuilder parent) 16 | { 17 | _parent = new Option(parent); 18 | } 19 | 20 | Option IBuilder.Parent => _parent; 21 | 22 | Option IBuilder.Root 23 | { 24 | get 25 | { 26 | var root = new Option(null); 27 | for (var p = ((IBuilder)this).Parent; p.HasValue; p = p.Value.Parent) 28 | { 29 | root = p; 30 | } 31 | 32 | return root; 33 | } 34 | } 35 | 36 | public IReadOnlyCollection Children => new ReadOnlyCollection(Childs); 37 | 38 | public abstract T Build(); 39 | 40 | IBuilder IBuilder.Create() 41 | { 42 | var builder = InternalCreate(); 43 | Childs.Add(builder); 44 | return builder; 45 | } 46 | 47 | IService IBuilder.Build() 48 | { 49 | return (IService)Build(); 50 | } 51 | public Builder Builder() 52 | { 53 | var builder = FindBuilder(); 54 | if (!builder.HasValue) 55 | { 56 | throw new FluentDockerException("Cannot find a parent Builder instance, bug in your code"); 57 | } 58 | 59 | return builder.Value; 60 | } 61 | 62 | protected abstract IBuilder InternalCreate(); 63 | 64 | protected Option FindBuilder() 65 | { 66 | for (var parent = ((IBuilder)this).Parent; parent.HasValue; parent = parent.Value.Parent) 67 | { 68 | if (parent.Value is Builder value) 69 | { 70 | return new Option(value); 71 | } 72 | } 73 | 74 | return new Option(null); 75 | } 76 | 77 | protected Option FindHostService() 78 | { 79 | for (var parent = ((IBuilder)this).Parent; parent.HasValue; parent = parent.Value.Parent) 80 | { 81 | var hostService = parent.Value.GetType().GetTypeInfo().DeclaredMethods.SingleOrDefault(x => x.Name == "Build")?.ReturnType == typeof(IHostService); 82 | if (hostService) 83 | { 84 | return new Option(((IBuilder)parent.Value).Build()); 85 | } 86 | } 87 | 88 | return new Option(null); 89 | } 90 | 91 | } 92 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Builders/MachineBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Ductus.FluentDocker.Commands; 3 | using Ductus.FluentDocker.Common; 4 | using Ductus.FluentDocker.Model.Builders; 5 | using Ductus.FluentDocker.Services; 6 | 7 | namespace Ductus.FluentDocker.Builders 8 | { 9 | public sealed class MachineBuilder : BaseBuilder 10 | { 11 | private readonly HostBuilderConfig _config = new HostBuilderConfig(); 12 | 13 | internal MachineBuilder(IBuilder parent) : base(parent) 14 | { 15 | } 16 | 17 | public override IHostService Build() 18 | { 19 | var machine = new Hosts().FromMachineName(_config.Name); 20 | if (null != machine) 21 | { 22 | if (machine.State != ServiceRunningState.Running) 23 | { 24 | machine.Start(); 25 | } 26 | 27 | return machine; 28 | } 29 | 30 | var resp = _config.Name.Create(_config.MemoryMb, _config.StorageSizeMb, _config.CpuCount); 31 | if (!resp.Success) 32 | { 33 | throw new FluentDockerException($"Could not create machine {_config.Name} Log: {resp}"); 34 | } 35 | 36 | return Build(); 37 | } 38 | 39 | protected override IBuilder InternalCreate() 40 | { 41 | return new MachineBuilder(this); 42 | } 43 | 44 | public MachineBuilder UseDriver(string driver) 45 | { 46 | _config.Driver = driver; 47 | return this; 48 | } 49 | 50 | public MachineBuilder WithName(string machineName) 51 | { 52 | _config.Name = machineName; 53 | return this; 54 | } 55 | 56 | public MachineBuilder CpuCount(int numCpus) 57 | { 58 | _config.CpuCount = numCpus; 59 | return this; 60 | } 61 | 62 | public MachineBuilder Memory(int memoryMb) 63 | { 64 | _config.MemoryMb = memoryMb; 65 | return this; 66 | } 67 | 68 | public MachineBuilder StorageSize(int storageMb) 69 | { 70 | _config.StorageSizeMb = storageMb; 71 | return this; 72 | } 73 | 74 | public HostBuilder Host() 75 | { 76 | return (HostBuilder) ((IBuilder) this).Parent.Value; 77 | } 78 | 79 | public ImageBuilder DefineImage(string image = null) 80 | { 81 | var builder = new ImageBuilder(this).AsImageName(image); 82 | Childs.Add(builder); 83 | return builder; 84 | } 85 | 86 | public ContainerBuilder UseContainer() 87 | { 88 | var builder = new ContainerBuilder(this); 89 | Childs.Add(builder); 90 | return builder; 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/Parsers/ClientTopResponseParser.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Ductus.FluentDocker.Model.Containers; 3 | 4 | namespace Ductus.FluentDocker.Executors.Parsers 5 | { 6 | public class ClientTopResponseParser : IProcessResponseParser 7 | { 8 | public CommandResponse Response { get; private set; } 9 | 10 | public IProcessResponse Process(ProcessExecutionResult response) 11 | { 12 | var rows = response.StdOutAsArry; 13 | if (response.ExitCode != 0 || 0 == rows.Length) 14 | { 15 | Response = response.ToErrorResponse(new Processes {Columns = new List(), Rows = new List()}); 16 | return this; 17 | } 18 | 19 | var columns = ParseColumns(rows[0]); 20 | if (0 == columns.Count) 21 | { 22 | Response = response.ToResponse(false, "No Process Columns Found", 23 | new Processes {Columns = new List(), Rows = new List()}); 24 | return this; 25 | } 26 | 27 | var processes = new Processes {Columns = ColumnSplit(columns, rows[0]), Rows = new List()}; 28 | for (var i = 1; i < rows.Length; i++) 29 | { 30 | var row = ColumnSplit(columns, rows[i]); 31 | processes.Rows.Add(ProcessRow.ToRow(processes.Columns, row)); 32 | } 33 | 34 | Response = response.ToResponse(true, string.Empty, processes); 35 | return this; 36 | } 37 | 38 | private IList ColumnSplit(IList columns, string row) 39 | { 40 | var list = new List(); 41 | for (var i = 0; i < columns.Count; i++) 42 | { 43 | if (i == columns.Count - 1) 44 | { 45 | list.Add(row.Substring(columns[i]).Trim()); 46 | break; 47 | } 48 | 49 | list.Add(row.Substring(columns[i], columns[i + 1] - columns[i]).Trim()); 50 | } 51 | 52 | return list; 53 | } 54 | 55 | private IList ParseColumns(string row) 56 | { 57 | var list = new List(); 58 | if (string.IsNullOrWhiteSpace(row)) 59 | { 60 | return list; 61 | } 62 | 63 | var inText = false; 64 | for (var i = 0; i < row.Length; i++) 65 | { 66 | if (row[i] != ' ') 67 | { 68 | if (!inText) 69 | { 70 | inText = true; 71 | list.Add(i); 72 | } 73 | 74 | continue; 75 | } 76 | 77 | inText = false; 78 | } 79 | 80 | return list; 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/ProcessExecutor.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Text; 3 | using Ductus.FluentDocker.Common; 4 | using Ductus.FluentDocker.Extensions; 5 | using Ductus.FluentDocker.Model.Containers; 6 | 7 | namespace Ductus.FluentDocker.Executors 8 | { 9 | public sealed class ProcessExecutor where T : IProcessResponseParser, IProcessResponse, new() 10 | { 11 | private readonly string _command; 12 | private readonly string _arguments; 13 | private readonly string _workingdir; 14 | 15 | public ProcessExecutor(string command, string arguments, string workingdir = null) 16 | { 17 | _workingdir = workingdir; 18 | if (command.StartsWith("echo") || command.StartsWith("sudo")) 19 | { 20 | _command = CommandExtensions.DefaultShell; 21 | _arguments = $"-c \"{command} {arguments}\""; 22 | 23 | return; 24 | } 25 | 26 | _command = command; 27 | _arguments = arguments; 28 | } 29 | 30 | public CommandResponse Execute() 31 | { 32 | var startInfo = new ProcessStartInfo 33 | { 34 | CreateNoWindow = true, 35 | RedirectStandardOutput = true, 36 | RedirectStandardError = true, 37 | UseShellExecute = false, 38 | Arguments = _arguments, 39 | FileName = _command, 40 | WorkingDirectory = _workingdir 41 | }; 42 | 43 | 44 | Logger.Log($"cmd: {_command} - arg: {_arguments}"); 45 | 46 | using (var process = new Process {StartInfo = startInfo}) 47 | { 48 | var output = new StringBuilder(); 49 | var err = new StringBuilder(); 50 | 51 | process.OutputDataReceived += (sender, args) => 52 | { 53 | if (!string.IsNullOrEmpty(args.Data)) 54 | { 55 | output.AppendLine(args.Data); 56 | } 57 | }; 58 | 59 | process.ErrorDataReceived += (sender, args) => 60 | { 61 | if (!string.IsNullOrEmpty(args.Data)) 62 | { 63 | err.AppendLine(args.Data); 64 | } 65 | }; 66 | 67 | if (!process.Start()) 68 | { 69 | throw new FluentDockerException($"Could not start process {_command}"); 70 | } 71 | 72 | process.BeginOutputReadLine(); 73 | process.BeginErrorReadLine(); 74 | 75 | process.WaitForExit(); 76 | 77 | return 78 | new T().Process(new ProcessExecutionResult(_command, output.ToString(), err.ToString(), process.ExitCode)) 79 | .Response; 80 | } 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/AsyncProcessExecutor.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Text; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Ductus.FluentDocker.Common; 6 | using Ductus.FluentDocker.Model.Containers; 7 | 8 | namespace Ductus.FluentDocker.Executors 9 | { 10 | public sealed class AsyncProcessExecutor where T : IProcessResponseParser, IProcessResponse, new() 11 | { 12 | private readonly string _arguments; 13 | private readonly string _command; 14 | private readonly string _workingdir; 15 | 16 | public AsyncProcessExecutor(string command, string arguments, string workingdir = null) 17 | { 18 | _command = command; 19 | _arguments = arguments; 20 | _workingdir = workingdir; 21 | } 22 | 23 | public Task> Execute(CancellationToken cancellationToken = default(CancellationToken)) 24 | { 25 | return Task.Factory.StartNew(() => InternalExecute(cancellationToken), cancellationToken); 26 | } 27 | 28 | private CommandResponse InternalExecute(CancellationToken cancellationToken) 29 | { 30 | var startInfo = new ProcessStartInfo 31 | { 32 | CreateNoWindow = true, 33 | RedirectStandardOutput = true, 34 | RedirectStandardError = true, 35 | UseShellExecute = false, 36 | Arguments = _arguments, 37 | FileName = _command, 38 | WorkingDirectory = _workingdir 39 | }; 40 | 41 | Logger.Log($"cmd: {_command} - arg: {_arguments}"); 42 | 43 | using (var process = new Process {StartInfo = startInfo}) 44 | { 45 | var output = new StringBuilder(); 46 | var err = new StringBuilder(); 47 | 48 | process.OutputDataReceived += (sender, args) => 49 | { 50 | if (!string.IsNullOrEmpty(args.Data)) 51 | { 52 | output.AppendLine(args.Data); 53 | } 54 | }; 55 | 56 | process.ErrorDataReceived += (sender, args) => 57 | { 58 | if (!string.IsNullOrEmpty(args.Data)) 59 | { 60 | err.AppendLine(args.Data); 61 | } 62 | }; 63 | 64 | if (!process.Start()) 65 | { 66 | throw new FluentDockerException($"Could not start process {_command}"); 67 | } 68 | 69 | process.BeginOutputReadLine(); 70 | process.BeginErrorReadLine(); 71 | 72 | while (!process.WaitForExit(1000)) 73 | { 74 | cancellationToken.ThrowIfCancellationRequested(); 75 | } 76 | 77 | return 78 | new T().Process(new ProcessExecutionResult(_command, output.ToString(), err.ToString(), process.ExitCode)) 79 | .Response; 80 | } 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Commands/Info.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Ductus.FluentDocker.Executors; 3 | using Ductus.FluentDocker.Executors.Parsers; 4 | using Ductus.FluentDocker.Extensions; 5 | using Ductus.FluentDocker.Model; 6 | using Ductus.FluentDocker.Model.Common; 7 | using Ductus.FluentDocker.Model.Containers; 8 | 9 | namespace Ductus.FluentDocker.Commands 10 | { 11 | public static class Info 12 | { 13 | /// 14 | /// Get the docker version both client and server version. It includes the server operating system (linux, windows). 15 | /// 16 | /// The docker daemon to contact. 17 | /// Path to certificates if any. 18 | /// A response with the versions if successfull. 19 | public static CommandResponse Version(this DockerUri host, ICertificatePaths certificates = null) 20 | { 21 | var args = $"{host.RenderBaseArgs(certificates)}"; 22 | var fmt = "{{.Server.Version}};{{.Server.APIVersion}};{{.Client.Version}};{{.Client.APIVersion}};{{.Server.Os}}"; 23 | 24 | return 25 | new ProcessExecutor( 26 | "docker".ResolveBinary(), 27 | $"{args} version -f \"{fmt}\"").Execute(); 28 | } 29 | 30 | public static CommandResponse Switch(this DockerUri host, ICertificatePaths certificates = null) 31 | { 32 | var args = $"{host.RenderBaseArgs(certificates)}"; 33 | 34 | return new ProcessExecutor( 35 | "dockercli".ResolveBinary(), $"{args} -SwitchDaemon").Execute(); 36 | } 37 | 38 | public static CommandResponse LinuxDaemon(this DockerUri host, ICertificatePaths certificates = null) 39 | { 40 | var version = host.Version(certificates); 41 | if (version.Data.ServerOs.ToLower().Equals("linux")) 42 | { 43 | return new CommandResponse(true,new string[0]); 44 | } 45 | var args = $"{host.RenderBaseArgs(certificates)}"; 46 | 47 | return new ProcessExecutor( 48 | "dockercli".ResolveBinary(), $"{args} -SwitchDaemon").Execute(); 49 | } 50 | public static CommandResponse WindowsDaemon(this DockerUri host, ICertificatePaths certificates = null) 51 | { 52 | var version = host.Version(certificates); 53 | if (version.Data.ServerOs.ToLower().Equals("windows")) 54 | { 55 | return new CommandResponse(true, new string[0]); 56 | } 57 | var args = $"{host.RenderBaseArgs(certificates)}"; 58 | 59 | return new ProcessExecutor( 60 | "dockercli".ResolveBinary(), $"{args} -SwitchDaemon").Execute(); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Builders/VolumeBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Ductus.FluentDocker.Common; 5 | using Ductus.FluentDocker.Services; 6 | 7 | namespace Ductus.FluentDocker.Builders 8 | { 9 | public sealed class VolumeBuilder : BaseBuilder 10 | { 11 | private readonly List _labels = new List(); 12 | private readonly Dictionary _options = new Dictionary(); 13 | private string _driver; 14 | private string _name; 15 | private bool _removeOnDispose; 16 | private bool _reuseIfExist; 17 | 18 | public VolumeBuilder(IBuilder parent, string name = null) : base(parent) 19 | { 20 | _name = name; 21 | } 22 | 23 | public override IVolumeService Build() 24 | { 25 | var host = FindHostService(); 26 | if (!host.HasValue) 27 | throw new FluentDockerException( 28 | $"Cannot build volume {_name} since no host service is defined"); 29 | 30 | if (_reuseIfExist) 31 | { 32 | var volume = host.Value.GetVolumes().FirstOrDefault(x => x.Name == _name); 33 | if (null != volume) return volume; 34 | } 35 | 36 | return host.Value.CreateVolume(_name, _driver, 0 == _labels.Count ? null : _labels.ToArray(), 37 | 0 == _options.Count ? null : _options, _removeOnDispose); 38 | } 39 | 40 | public VolumeBuilder WithName(string name) 41 | { 42 | _name = name; 43 | return this; 44 | } 45 | 46 | public VolumeBuilder UseingDriver(string driver) 47 | { 48 | _driver = driver; 49 | return this; 50 | } 51 | 52 | public VolumeBuilder RemoveOnDispose() 53 | { 54 | _removeOnDispose = true; 55 | return this; 56 | } 57 | 58 | public VolumeBuilder ReuseIfExist() 59 | { 60 | _reuseIfExist = true; 61 | return this; 62 | } 63 | 64 | public VolumeBuilder UseLabel(params string[] label) 65 | { 66 | if (null != label && 0 != label.Length) 67 | { 68 | _labels.AddRange(label); 69 | } 70 | return this; 71 | } 72 | 73 | public VolumeBuilder UseOption(params string[] nameValue) 74 | { 75 | if (null == nameValue || 0 == nameValue.Length) 76 | { 77 | return this; 78 | } 79 | 80 | foreach(var s in nameValue) 81 | { 82 | var splt = s.Split('='); 83 | if (splt.Length < 2 || string.IsNullOrEmpty(splt[0]) || string.IsNullOrEmpty(splt[1])) 84 | { 85 | continue; 86 | } 87 | _options.Add(splt[0], splt[1]); 88 | } 89 | return this; 90 | } 91 | 92 | protected override IBuilder InternalCreate() 93 | { 94 | return new VolumeBuilder(this); 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /Tests/Program.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Builders; 2 | using Ductus.FluentDocker.Extensions; 3 | using Ductus.FluentDocker.Services; 4 | using System; 5 | using System.Diagnostics; 6 | using Ductus.FluentDocker.Executors; 7 | 8 | namespace Tests 9 | { 10 | class Program 11 | { 12 | static void Main(string[] args) 13 | { 14 | //SudoMechanism.Password.SetSudo(""); 15 | //SudoMechanism.NoPassword.SetSudo(); 16 | //SudoMechanism.None.SetSudo(); 17 | //RunPs(); 18 | //RunPsCloneStdOut(); 19 | //RunContainer(); 20 | } 21 | // https://docs.microsoft.com/en-us/dotnet/standard/exceptions/how-to-use-finally-blocks 22 | 23 | // http://csharptest.net/532/using-processstart-to-capture-console-output/index.html 24 | static void RunPs() 25 | { 26 | var process = new Process() 27 | { 28 | StartInfo = new ProcessStartInfo 29 | { 30 | FileName = @"C:\windows\system32\windowspowershell\v1.0\powershell.exe", 31 | Arguments = "-NoLogo -NoExit -Command docker ps", 32 | RedirectStandardOutput = false, 33 | UseShellExecute = true, 34 | CreateNoWindow = false, 35 | } 36 | }; 37 | process.Start(); 38 | } 39 | 40 | private static void RunContainer() 41 | { 42 | var hosts = new Hosts().Discover(); 43 | Console.WriteLine($"Number of hosts:{hosts.Count}"); 44 | foreach (var host in hosts) 45 | { 46 | Console.WriteLine($"{host.Host} {host.Name} {host.State}"); 47 | } 48 | 49 | Console.WriteLine("Spinning up a postgres and wait for ready state..."); 50 | using ( 51 | var container = 52 | new Builder().UseContainer() 53 | .UseImage("postgres:9.6-alpine") 54 | .ExposePort(5432) 55 | .WithEnvironment("POSTGRES_PASSWORD=mysecretpassword") 56 | .WaitForPort("5432/tcp", 30000 /*30s*/) 57 | .Build() 58 | .Start()) 59 | { 60 | var config = container.GetConfiguration(true); 61 | if (ServiceRunningState.Running == config.State.ToServiceState()) 62 | { 63 | Console.WriteLine("Service is running"); 64 | } 65 | else 66 | { 67 | Console.WriteLine("Failed to start nginx instance..."); 68 | } 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Common/TemplateString.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using Ductus.FluentDocker.Common; 7 | 8 | namespace Ductus.FluentDocker.Model.Common 9 | { 10 | public sealed class TemplateString 11 | { 12 | private static readonly Dictionary> Templates; 13 | 14 | static TemplateString() 15 | { 16 | Templates = 17 | new Dictionary> 18 | { 19 | { 20 | "${TMP}", () => 21 | { 22 | var path = Path.GetTempPath(); 23 | if (path.StartsWith("/var/") && FdOs.IsOsx()) path = "/private/" + path; 24 | 25 | return path.Substring(0, path.Length - 1); 26 | } 27 | }, 28 | { 29 | "${TEMP}", () => 30 | { 31 | var path = Path.GetTempPath(); 32 | if (path.StartsWith("/var/") && FdOs.IsOsx()) path = "/private/" + path; 33 | 34 | return path.Substring(0, path.Length - 1); 35 | } 36 | }, 37 | {"${RND}", Path.GetRandomFileName}, 38 | {"${PWD}", Directory.GetCurrentDirectory} 39 | }; 40 | } 41 | 42 | public TemplateString(string str) 43 | { 44 | Original = str; 45 | Rendered = Render(ToTargetOs(str)); 46 | } 47 | 48 | public string Original { get; } 49 | public string Rendered { get; } 50 | 51 | private static string ToTargetOs(string str) 52 | { 53 | if (string.IsNullOrEmpty(str) || str.StartsWith("emb:")) return str; 54 | 55 | return !FdOs.IsWindows() ? str : str.Replace('/', '\\'); 56 | } 57 | 58 | private static string Render(string str) 59 | { 60 | str = Templates.Keys.Where(key => -1 != str.IndexOf(key, StringComparison.Ordinal)) 61 | .Aggregate(str, (current, key) => current.Replace(key, Templates[key]())); 62 | 63 | return RenderEnvironment(str); 64 | } 65 | 66 | private static string RenderEnvironment(string str) 67 | { 68 | foreach (DictionaryEntry env in Environment.GetEnvironmentVariables()) 69 | { 70 | var tmpEnv = "${E_" + env.Key + "}"; 71 | if (-1 != str.IndexOf(tmpEnv, StringComparison.Ordinal)) str = str.Replace(tmpEnv, (string) env.Value); 72 | } 73 | 74 | return str; 75 | } 76 | 77 | public static implicit operator TemplateString(string str) 78 | { 79 | return null == str ? null : new TemplateString(str); 80 | } 81 | 82 | public static implicit operator string(TemplateString str) 83 | { 84 | return str?.Rendered; 85 | } 86 | 87 | public override string ToString() 88 | { 89 | return Rendered; 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/LongSecret.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Compose 2 | { 3 | /// 4 | /// The long syntax provides more granularity in how the secret is created within the service’s task containers. 5 | /// 6 | /// 7 | /// Note: The secret must already exist or be defined in the top-level secrets configuration of this stack file, or stack 8 | /// deployment fails. This requires docker compose file 3.1 or greater. 9 | /// 10 | /// 11 | /// The following example sets name of the my_secret to redis_secret within the container, sets the mode to 0440 12 | /// (group-readable) and sets the user and group to 103. The redis service does not have access to the my_other_secret 13 | /// secret. 14 | /// version: "3.1" 15 | /// services: 16 | /// redis: 17 | /// image: redis:latest 18 | /// deploy: 19 | /// replicas: 1 20 | /// secrets: 21 | /// - source: my_secret 22 | /// target: redis_secret 23 | /// uid: '103' 24 | /// gid: '103' 25 | /// mode: 0440 26 | /// secrets: 27 | /// my_secret: 28 | /// file: ./my_secret.txt 29 | /// my_other_secret: 30 | /// external: true 31 | /// 32 | public sealed class LongSecret : ISecret 33 | { 34 | /// 35 | /// The name of the secret as it exists in Docker. 36 | /// 37 | public string Source { get; set; } 38 | 39 | /// 40 | /// The name of the file to be mounted in /run/secrets/ in the service’s task containers. Defaults to source if not 41 | /// specified. 42 | /// 43 | public string Target { get; set; } 44 | 45 | /// 46 | /// The numeric UID that owns the file within /run/secrets/ in the service’s task containers. It default to 0 if not 47 | /// specified. 48 | /// 49 | public int Uid { get; set; } 50 | 51 | /// 52 | /// The numeric GID that owns the file within /run/secrets/ in the service’s task containers. It default to 0 if not 53 | /// specified. 54 | /// 55 | public int Gid { get; set; } 56 | 57 | /// 58 | /// The permissions for the file to be mounted in /run/secrets/ in the service’s task containers, in octal notation. 59 | /// 60 | /// 61 | /// For instance, 0444 represents world-readable. 62 | /// Secrets cannot be writable because they are mounted in a temporary filesystem, so if you set the writable bit, it is 63 | /// ignored. The executable bit can be set. If you aren’t familiar with UNIX file permission modes, you may find this 64 | /// permissions calculator useful. Defaults to 0444. 65 | /// 66 | public string Mode { get; set; } = "0444"; 67 | } 68 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Common/DirectoryHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Threading; 7 | 8 | namespace Ductus.FluentDocker.Common 9 | { 10 | /// 11 | /// Helper to proper delete a directory and subdirectories. 12 | /// 13 | /// 14 | /// This class is taken from the . 15 | /// 16 | public static class DirectoryHelper 17 | { 18 | private static readonly Dictionary ToRename = new Dictionary 19 | { 20 | {"dot_git", ".git"}, 21 | {"gitmodules", ".gitmodules"} 22 | }; 23 | 24 | private static readonly Type[] Whitelist = {typeof(IOException), typeof(UnauthorizedAccessException)}; 25 | 26 | public static void CopyFilesRecursively(DirectoryInfo source, DirectoryInfo target) 27 | { 28 | // From http://stackoverflow.com/questions/58744/best-way-to-copy-the-entire-contents-of-a-directory-in-c/58779#58779 29 | 30 | foreach (var dir in source.GetDirectories()) 31 | CopyFilesRecursively(dir, target.CreateSubdirectory(Rename(dir.Name))); 32 | foreach (var file in source.GetFiles()) file.CopyTo(Path.Combine(target.FullName, Rename(file.Name))); 33 | } 34 | 35 | private static string Rename(string name) 36 | { 37 | return ToRename.ContainsKey(name) ? ToRename[name] : name; 38 | } 39 | 40 | public static void DeleteDirectory(string directoryPath) 41 | { 42 | if (!Directory.Exists(directoryPath)) return; 43 | 44 | NormalizeAttributes(directoryPath); 45 | DeleteDirectory(directoryPath, 5, 16, 2); 46 | } 47 | 48 | private static void NormalizeAttributes(string directoryPath) 49 | { 50 | var filePaths = Directory.GetFiles(directoryPath); 51 | var subdirectoryPaths = Directory.GetDirectories(directoryPath); 52 | 53 | foreach (var filePath in filePaths) File.SetAttributes(filePath, FileAttributes.Normal); 54 | foreach (var subdirectoryPath in subdirectoryPaths) NormalizeAttributes(subdirectoryPath); 55 | File.SetAttributes(directoryPath, FileAttributes.Normal); 56 | } 57 | 58 | private static void DeleteDirectory(string directoryPath, int maxAttempts, int initialTimeout, int timeoutFactor) 59 | { 60 | for (var attempt = 1; attempt <= maxAttempts; attempt++) 61 | try 62 | { 63 | Directory.Delete(directoryPath, true); 64 | return; 65 | } 66 | catch (Exception ex) 67 | { 68 | var caughtExceptionType = ex.GetType(); 69 | 70 | if (!Whitelist.Any(knownExceptionType => knownExceptionType.IsAssignableFrom(caughtExceptionType))) throw; 71 | 72 | if (attempt >= maxAttempts) continue; 73 | Thread.Sleep(initialTimeout * (int) Math.Pow(timeoutFactor, attempt - 1)); 74 | } 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/IFeature.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Ductus.FluentDocker.Services; 4 | 5 | namespace Ductus.FluentDocker.Model 6 | { 7 | public static class FeatureConstants 8 | { 9 | /// 10 | /// Keeps the data and services when is . 11 | /// 12 | public const string KeepOnDispose = "global.keep.on.dispose"; 13 | 14 | /// 15 | /// The to use in the when is 16 | /// invoked. 17 | /// 18 | /// 19 | /// Ownership is not transferred to the since this may be shared among several. If no 20 | /// is passed to a and no special pattern for the specific feature 21 | /// then the shall strive to use the native first, then the "default" after that the first 22 | /// available it can get through docker machine. 23 | /// 24 | public const string HostService = "globa.host.service"; 25 | } 26 | 27 | public interface IFeature : IDisposable 28 | { 29 | /// 30 | /// The globally id and version if the feature separated with a single slash '/'. 31 | /// 32 | /// 33 | /// For example: 'git/2.1.4'. 34 | /// 35 | string Id { get; } 36 | 37 | /// 38 | /// Currently bound services to the feature. It is never null. 39 | /// 40 | /// 41 | /// Those will not appear in this property until has been invoked and will be removed 42 | /// when has been invoked. The timeline between those two calls there may 43 | /// be a set of services here. Note that it may be composite services and therefore a single instance represent 44 | /// several services. Those are not flatten out in this property. Note that if any 45 | /// is used, it too will be exposed through this property. 46 | /// 47 | IEnumerable Services { get; } 48 | 49 | /// 50 | /// Initializes the feature. 51 | /// 52 | /// Any settings that the feature needs to execute. 53 | /// 54 | /// Some features are stateless and some requires some form of a state. E.g. it may 55 | /// need credentials or urls to perform its work each time is 56 | /// invoked. 57 | /// 58 | void Initialize(IDictionary settings = null); 59 | 60 | /// 61 | /// Executes the feature's functionality. 62 | /// 63 | /// The arguments. 64 | void Execute(params string[] arguments); 65 | } 66 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Services/IContainerService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Ductus.FluentDocker.Model.Common; 4 | using Ductus.FluentDocker.Model.Containers; 5 | 6 | namespace Ductus.FluentDocker.Services 7 | { 8 | public interface IContainerService : IService 9 | { 10 | /// 11 | /// The container id of the running container. 12 | /// 13 | string Id { get; } 14 | 15 | /// 16 | /// The instance id if multiple instances of same container. May be . 17 | /// 18 | string InstanceId { get; } 19 | 20 | /// 21 | /// If part of a service, the name of the service is in this property. Otherwise 22 | /// 23 | string Service { get; } 24 | 25 | /// 26 | /// The to the docker daemon in control of this service. 27 | /// 28 | DockerUri DockerHost { get; } 29 | 30 | /// 31 | /// When set to true the container is stopped automatically on . 32 | /// 33 | bool StopOnDispose { get; set; } 34 | 35 | /// 36 | /// When set to true the container is removed automaticallyh on . 37 | /// 38 | bool RemoveOnDispose { get; set; } 39 | 40 | /// 41 | /// Dettermines if this container is based on a windows image or linux image. 42 | /// 43 | bool IsWindowsContainer { get; } 44 | 45 | /// 46 | /// Paths to where certificates resides for this service. 47 | /// 48 | ICertificatePaths Certificates { get; } 49 | 50 | /// 51 | /// The image the running container is based on. 52 | /// 53 | IContainerImageService Image { get; } 54 | 55 | /// 56 | /// Gets the configuration from the docker host for this container. 57 | /// 58 | /// If a new copy is wanted or a cached one. If non has been requested it will fetch one and cache it. 59 | /// 60 | /// This is not cached, thus it will go to the docker daemon each time. 61 | /// 62 | Container GetConfiguration(bool fresh = false); 63 | 64 | /// 65 | /// Overridden to handle fluent access. 66 | /// 67 | /// 68 | new IContainerService Start(); 69 | 70 | /// 71 | /// Gets all volumes attached to this container. 72 | /// 73 | /// A list with zero or more volumes. 74 | IList GetVolumes(); 75 | 76 | /// 77 | /// Gets all networks that this container is attached to. 78 | /// 79 | /// A list with one or more networks. 80 | IList GetNetworks(); 81 | } 82 | } --------------------------------------------------------------------------------