├── docs └── _config.yml ├── .github └── FUNDING.yml ├── Ductus.FluentDocker.Tests ├── MultiContainerTestFiles │ ├── app.js │ ├── NsResolver.cs │ ├── package.txt │ ├── nginx.conf │ └── index.js ├── Resources │ ├── Scripts │ │ ├── envtest.bat │ │ └── envtest.sh │ ├── hellotest │ │ ├── hellotest │ │ │ ├── Dockerfile │ │ │ └── hello │ │ └── docker-compose.yml │ ├── Issue │ │ └── 111 │ │ │ └── server.py │ ├── ComposeTests │ │ ├── RabbitMQ │ │ │ └── docker-compose.yml │ │ ├── MongoDbAndNetwork │ │ │ └── docker-compose.yml │ │ └── WordPress │ │ │ └── docker-compose.yml │ └── StackTests │ │ └── WordPress │ │ └── stack.yml ├── Compose │ ├── NsResolver.cs │ ├── node │ │ ├── Dockerfile │ │ ├── package.txt │ │ └── index.js │ ├── nginx │ │ ├── Dockerfile │ │ ├── nginx.conf │ │ └── Dockerfile_custom │ ├── redis │ │ └── Dockerfile │ └── docker-compose.yml ├── CommandTests │ ├── DockerInfoCommandTests.cs │ └── ImageTests.cs ├── Common │ ├── UnitTestingTraceListener.cs │ └── LoggerTests.cs ├── Model │ ├── Containers │ │ ├── ContainerTests.cs │ │ └── ContainerCreateParamsTests.cs │ └── Builders │ │ ├── FileBuilder │ │ ├── CopyCommandTests.cs │ │ └── CmdCommandTests.cs │ │ └── CmdCommandTests.cs ├── ProcessTests │ └── ProcessEnvironmentTest.cs └── ExtensionTests │ ├── ResourceExtensionsTests.cs │ ├── CommandExtensionTest.cs │ └── EnironmentExtensionTests.cs ├── keypair.snk ├── .vscode ├── extensions.json ├── test.json ├── settings.json ├── tasks.json └── launch.json ├── icon └── fluent-docker.png ├── global.json ├── Examples ├── Simple │ ├── README.md │ └── Simple.csproj ├── .vscode │ ├── settings.json │ ├── tasks.json │ └── launch.json ├── global.json ├── DockerInDockerLinux │ ├── build.sh │ ├── run.sh │ ├── DockerInDockerLinux.csproj │ ├── Dockerfile │ ├── Program.cs │ └── README.md ├── README.md └── EventDriven │ ├── EventDriven.csproj │ ├── README.md │ └── Program.cs ├── Ductus.FluentDocker ├── Model │ ├── Builders │ │ ├── ICommand.cs │ │ ├── MountType.cs │ │ ├── NetworkWithAlias.cs │ │ ├── ImageBuilderConfig.cs │ │ ├── FileBuilder │ │ │ ├── MaintainerCommand.cs │ │ │ ├── RunCommand.cs │ │ │ ├── WorkdirCommand.cs │ │ │ ├── AddCommand.cs │ │ │ ├── ExposeCommand.cs │ │ │ ├── ShellCommand.cs │ │ │ ├── EntrypointCommand.cs │ │ │ ├── CmdCommand.cs │ │ │ ├── EnvCommand.cs │ │ │ ├── VolumeCommand.cs │ │ │ ├── LabelCommand.cs │ │ │ ├── UserCommand.cs │ │ │ ├── ArgCommand.cs │ │ │ ├── CopyURLCommand.cs │ │ │ ├── FromCommand.cs │ │ │ └── CopyCommand.cs │ │ ├── HostBuilderConfig.cs │ │ └── FileBuilderConfig.cs │ ├── Compose │ │ ├── ISecret.cs │ │ ├── IPortsDefinition.cs │ │ ├── PortMode.cs │ │ ├── IServiceVolumeDefinition.cs │ │ ├── VolumeType.cs │ │ ├── ComposeVersion.cs │ │ ├── ComposeVolumeDefinition.cs │ │ ├── ContainerIsolationType.cs │ │ ├── ResourcesItemDefinition.cs │ │ ├── UlimitDefinition.cs │ │ ├── DockerComposeFileConfig.cs │ │ ├── ConfigurationDefinition.cs │ │ ├── ConfigurationItemDefinition.cs │ │ ├── TmpFsDefinition.cs │ │ ├── ResourcesDefinition.cs │ │ ├── PortsShortDefinition.cs │ │ ├── PortsLongDefinition.cs │ │ ├── LoggingDefinition.cs │ │ ├── ShortSecret.cs │ │ ├── RestartPolicyDefinition.cs │ │ ├── DockerComposeConfig.cs │ │ ├── ContainerSpecificConfig.cs │ │ └── ConfigLongDefinition.cs │ ├── Images │ │ ├── ImageRemovalOption.cs │ │ ├── DockerImageRmRowResponse.cs │ │ └── DockerImageRowResponse.cs │ ├── Containers │ │ ├── Diff.cs │ │ ├── HealthState.cs │ │ ├── DiffType.cs │ │ ├── Health.cs │ │ ├── UnixSignal.cs │ │ ├── ContainerIsolationTechnology.cs │ │ ├── CertificatePaths.cs │ │ ├── ContainerMount.cs │ │ ├── ContainerRuntime.cs │ │ ├── BridgeNetwork.cs │ │ ├── ContainerState.cs │ │ ├── RestartPolicy.cs │ │ ├── ULimitItem.cs │ │ ├── CommandResponse.cs │ │ ├── Container.cs │ │ ├── HostIpEndpoint.cs │ │ ├── Processes.cs │ │ ├── ContainerNetworkSettings.cs │ │ ├── ContainerConfig.cs │ │ ├── VolumeMount.cs │ │ └── ImageConfig.cs │ ├── Networks │ │ ├── IpamConfig.cs │ │ ├── NetworkType.cs │ │ ├── Ipam.cs │ │ ├── NetworkRow.cs │ │ ├── NetworkedContainer.cs │ │ └── NetworkConfiguration.cs │ ├── Events │ │ ├── EventAction.cs │ │ ├── EventType.cs │ │ ├── EventScope.cs │ │ ├── EventActor.cs │ │ ├── ImagePullEvent.cs │ │ ├── ContainerStartEvent.cs │ │ ├── ContainerCreateEvent.cs │ │ ├── ContainerDestroyEvent.cs │ │ ├── ContainerStopEvent.cs │ │ ├── ContainerDieEvent.cs │ │ ├── ContainerKillEvent.cs │ │ ├── FdEvent.cs │ │ ├── UnknownEvent.cs │ │ ├── NetworkConnectEvent.cs │ │ └── NetworkDisconnectEvent.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 ├── Common │ ├── Constants.cs │ ├── Logger.cs │ ├── FdOs.cs │ ├── FluentDockerException.cs │ ├── Result.cs │ ├── Option.cs │ ├── RequestResponse.cs │ ├── DeprecatedAttribute.cs │ ├── ExperimentalAttribute.cs │ ├── FeatureAttribute.cs │ └── ResultExtensions.cs ├── Resources │ ├── IResourceWriter.cs │ ├── ResourceInfo.cs │ ├── ResourceStream.cs │ ├── FileResourceWriter.cs │ └── ResourceReader.cs ├── Services │ ├── ServiceRunningState.cs │ ├── IVolumeService.cs │ ├── StateChangeEventArgs.cs │ ├── ICompositeService.cs │ ├── FluentDockerNotSupportedException.cs │ ├── Logging.cs │ ├── IContainerImageService.cs │ ├── IEngineScope.cs │ ├── Impl │ │ ├── ServiceHooks.cs │ │ └── ServiceBase.cs │ ├── INetworkService.cs │ ├── IService.cs │ └── Extensions │ │ └── HostExtensions.cs ├── Executors │ ├── IProcessResponseParser.cs │ ├── Parsers │ │ ├── ProcessExitAwareResponseParser.cs │ │ ├── NoLineResponseParser.cs │ │ ├── SingleStringResponseParser.cs │ │ ├── MachineStartStopResponseParser.cs │ │ ├── MachineRmResponseParser.cs │ │ ├── StringListResponseParser.cs │ │ ├── MachineCreateResponseParser.cs │ │ ├── NetworkInspectResponseParser.cs │ │ ├── VolumeInspectResponseParser.cs │ │ ├── ClientImageInspectCommandResponder.cs │ │ ├── StackLsResponseParser.cs │ │ ├── ClientImagesResponseParser.cs │ │ ├── MachineEnvResponseParser.cs │ │ ├── ClientDiffResponseParser.cs │ │ ├── MachineLsResponseParser.cs │ │ ├── ImageRmResponseParser.cs │ │ ├── ClientInspectContainersResponseParser.cs │ │ ├── ClientContainerInspectCommandResponder.cs │ │ ├── StackPsResponseParser.cs │ │ └── BaseInfoResponseParser.cs │ ├── Mappers │ │ ├── StringMapper.cs │ │ └── JObjectStreamMapper.cs │ ├── IStreamMapper.cs │ ├── StreamProcessExecutor.cs │ └── ProcessExecutionResult.cs ├── Extensions │ ├── OsExtensions.cs │ ├── ComparisonExtensions.cs │ ├── CompressionExtensions.cs │ ├── StringExtensions.cs │ ├── ConversionExtension.cs │ └── ServiceExtensions.cs ├── Builders │ ├── RepositoryBuilder.cs │ └── IBuilder.cs └── Commands │ ├── Service.cs │ ├── Images.cs │ └── CommandDefaults.cs ├── Makefile ├── NuGet.config ├── coverletArgs.runsettings ├── FluentDocker.sln.DotSettings ├── .env.example ├── GitVersion.yml ├── Ductus.FluentDocker.XUnit ├── FluentDockerTestBase.cs └── PostgresTestBase.cs └── Ductus.FluentDocker.MsTest ├── PostgresTestBase.cs └── FluentDockerComposeTestBase.cs /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-hacker -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [mariotoffia] 2 | -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/MultiContainerTestFiles/app.js: -------------------------------------------------------------------------------- 1 | test; -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Resources/Scripts/envtest.bat: -------------------------------------------------------------------------------- 1 | echo %FD_CUSTOM_ENV% 2 | -------------------------------------------------------------------------------- /keypair.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotoffia/FluentDocker/HEAD/keypair.snk -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Resources/Scripts/envtest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo $FD_CUSTOM_ENV 3 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-dotnettools.csharp" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /icon/fluent-docker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotoffia/FluentDocker/HEAD/icon/fluent-docker.png -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Resources/hellotest/hellotest/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | COPY hello / 3 | CMD ["/hello"] -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "9.0.107", 4 | "rollForward": "latestFeature" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/Simple/README.md: -------------------------------------------------------------------------------- 1 | # Simple 2 | 3 | Simple test project to demonstrate how to use _FluentDocker_ in various ways. -------------------------------------------------------------------------------- /Examples/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "Ductus", 4 | "mysecretpassword" 5 | ] 6 | } -------------------------------------------------------------------------------- /Examples/global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "6.0.100", 4 | "rollForward": "latestMajor", 5 | "allowPrerelease": true 6 | } 7 | } -------------------------------------------------------------------------------- /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 | } 8 | -------------------------------------------------------------------------------- /Examples/DockerInDockerLinux/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## Builds the docker image 3 | dotnet build 4 | docker build -t docker-in-docker -f Dockerfile . 5 | -------------------------------------------------------------------------------- /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 | } 7 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/PortMode.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Compose 2 | { 3 | public enum PortMode 4 | { 5 | Host, 6 | Ingress 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Examples/DockerInDockerLinux/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## Will execute the built docker container 3 | docker run --rm -v /var/run/docker.sock:/var/run/docker.sock docker-in-docker -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Resources/hellotest/hellotest/hello: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotoffia/FluentDocker/HEAD/Ductus.FluentDocker.Tests/Resources/hellotest/hellotest/hello -------------------------------------------------------------------------------- /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 | } 7 | -------------------------------------------------------------------------------- /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 | } 9 | -------------------------------------------------------------------------------- /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 | } 10 | -------------------------------------------------------------------------------- /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 | } 8 | -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Resources/hellotest/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.4" 2 | 3 | services: 4 | hellotest: 5 | image: hellotest:v2 6 | build: 7 | context: hellotest 8 | dockerfile: Dockerfile -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/ComposeVersion.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Compose 2 | { 3 | public enum ComposeVersion 4 | { 5 | Unknown = 0, 6 | V1 = 1, 7 | V2 = 2 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /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 | } 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/Compose/ContainerIsolationType.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Compose 2 | { 3 | public enum ContainerIsolationType 4 | { 5 | Default, 6 | Process, 7 | HyperV 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /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 | } 9 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Containers/HealthState.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Containers 2 | { 3 | public enum HealthState 4 | { 5 | Starting, 6 | Unhealthy, 7 | Healthy, 8 | Unknown 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /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 | } 11 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Containers/Health.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Containers 2 | { 3 | public class Health 4 | { 5 | public HealthState Status { get; set; } 6 | public int FailingStreak { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /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.Tests/Compose/node/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:alpine 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/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/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 | } 9 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Images/DockerImageRmRowResponse.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Images 2 | { 3 | public sealed class DockerRmImageRowResponse 4 | { 5 | public string Id { get; set; } 6 | public string Command { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Compose/nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | # Set nginx base image 2 | FROM nginx: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/Resources/Issue/111/server.py: -------------------------------------------------------------------------------- 1 | import http.server 2 | import socketserver 3 | 4 | PORT = 8000 5 | Handler = http.server.SimpleHTTPRequestHandler 6 | 7 | with socketserver.TCPServer(("", PORT), Handler) as httpd: 8 | print("serving at port", PORT) 9 | httpd.serve_forever() -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Networks/NetworkType.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Networks 2 | { 3 | public enum NetworkType 4 | { 5 | Unknown = 0, 6 | Bridge, 7 | Host, 8 | Overlay, 9 | Ipvlan, 10 | Macvlan, 11 | None, 12 | Custom 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | In this folder, there are samples of the different features of the library that is not captured in a unit test or is more clean to do as a sample. 4 | 5 | 👀 The Test project sample will also be converted into a sample and the _Test_ folder will be removed from the project. -------------------------------------------------------------------------------- /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/Builders/NetworkWithAlias.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Services; 2 | 3 | namespace Ductus.FluentDocker.Model.Builders 4 | { 5 | public class NetworkWithAlias 6 | { 7 | public T Network { get; set; } 8 | 9 | public string Alias { get; set; } 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Events/EventAction.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Events 2 | { 3 | public enum EventAction 4 | { 5 | Unspecified, 6 | Pull, 7 | Create, 8 | Start, 9 | Kill, 10 | Die, 11 | Connect, 12 | Disconnect, 13 | Stop, 14 | Destroy 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Resources/ComposeTests/RabbitMQ/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | 3 | services: 4 | rabbitmq: 5 | image: library/rabbitmq:3.9-alpine 6 | ports: 7 | - "5672:5672" 8 | healthcheck: 9 | test: nc -vn 127.0.0.1 5672 || exit 1 10 | interval: 1s 11 | retries: 100 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 | } 13 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Events/EventType.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Events 2 | { 3 | public enum EventType 4 | { 5 | Generic, 6 | Image, 7 | Container, 8 | Network, 9 | Plugin, 10 | Volume, 11 | Daemon, 12 | Service, 13 | Node, 14 | Secret, 15 | Config 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /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 | } 15 | -------------------------------------------------------------------------------- /Examples/DockerInDockerLinux/DockerInDockerLinux.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net8.0 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/Simple/Simple.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | UNAME_S := $(shell uname -s) 2 | 3 | .PHONY: build 4 | build: 5 | act -j build --env-file .env 6 | 7 | 8 | .PHONY: dep 9 | dep: 10 | ifeq ($(UNAME_S),Darwin) 11 | @echo "▶️ Ensuring required .NET SDKs are present (macOS)…" 12 | @bash scripts/ensure-dotnet-sdks 13 | else 14 | @echo "ℹ️ Skipping .NET SDK check (host OS: $(UNAME_S))" 15 | endif -------------------------------------------------------------------------------- /Examples/EventDriven/EventDriven.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /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 | } 13 | -------------------------------------------------------------------------------- /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/Common/Logger.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace Ductus.FluentDocker.Common 4 | { 5 | public static class Logger 6 | { 7 | internal static bool Enabled = true; 8 | 9 | public static void Log(string message) 10 | { 11 | if (!Enabled) 12 | return; 13 | 14 | Trace.WriteLine(message, Constants.DebugCategory); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /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 | } 12 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /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 | } 19 | -------------------------------------------------------------------------------- /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/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/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 | } 15 | -------------------------------------------------------------------------------- /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 | } 16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Examples/DockerInDockerLinux/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/sdk:3.1 2 | 3 | WORKDIR /App 4 | COPY bin/Debug/netcoreapp3.1 . 5 | 6 | RUN apt-get -qq update && apt-get -qq install wget 7 | RUN wget --quiet https://download.docker.com/linux/static/stable/x86_64/docker-20.10.8.tgz && \ 8 | tar -xzf docker-20.10.8.tgz 9 | 10 | RUN cp docker/* /usr/bin/ && rm -rf docker 11 | 12 | ENTRYPOINT ["dotnet", "DockerInDockerLinux.dll"] -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Resources/ComposeTests/MongoDbAndNetwork/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.3' 2 | 3 | services: 4 | mongodb: 5 | image: mongo:latest 6 | volumes: 7 | - mongodb-data:/data 8 | command: mongod --smallfiles --bind_ip=0.0.0.0 --logpath=/dev/null 9 | expose: 10 | - 27017 11 | networks: 12 | - mongodb-network 13 | 14 | volumes: 15 | mongodb-data: 16 | 17 | networks: 18 | mongodb-network: -------------------------------------------------------------------------------- /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/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 | } 13 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Events/EventScope.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Events 2 | { 3 | /// 4 | /// The scope of the . 5 | /// 6 | public enum EventScope 7 | { 8 | /// 9 | /// Unknown 10 | /// 11 | Unknown, 12 | /// 13 | /// Local scope, i.e. the event originated from local docker host. 14 | /// 15 | Local 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /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 | } 17 | -------------------------------------------------------------------------------- /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 | } 15 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Events/EventActor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Ductus.FluentDocker.Model.Events 5 | { 6 | /// 7 | /// The actor of a such as container id, image name or c# class name. 8 | /// 9 | public class EventActor 10 | { 11 | public string Id { get; internal set; } 12 | public IList> Labels { get; internal set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Common/FdOs.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace Ductus.FluentDocker.Common 4 | { 5 | public static class FdOs 6 | { 7 | public static bool IsWindows() 8 | => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); 9 | 10 | public static bool IsOsx() 11 | => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); 12 | 13 | public static bool IsLinux() 14 | => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Builders/FileBuilder/MaintainerCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Builders.FileBuilder 2 | { 3 | public sealed class MaintainerCommand : ICommand 4 | { 5 | public MaintainerCommand(string maintainer) 6 | { 7 | Maintainer = maintainer; 8 | } 9 | 10 | public string Maintainer { get; } 11 | 12 | public override string ToString() 13 | { 14 | return $"MAINTAINER {Maintainer}"; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /coverletArgs.runsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | xml,opencover 8 | output/coverage.opencover.xml 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Builders/FileBuilder/RunCommand.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Model.Common; 2 | 3 | namespace Ductus.FluentDocker.Model.Builders.FileBuilder 4 | { 5 | public sealed class RunCommand : ICommand 6 | { 7 | public RunCommand(TemplateString run) 8 | { 9 | Run = run; 10 | } 11 | 12 | public TemplateString Run { get; } 13 | 14 | public override string ToString() 15 | { 16 | return $"RUN {Run}"; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /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 | } 20 | -------------------------------------------------------------------------------- /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 | } 14 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Builders/FileBuilder/WorkdirCommand.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Model.Common; 2 | 3 | namespace Ductus.FluentDocker.Model.Builders.FileBuilder 4 | { 5 | public sealed class WorkdirCommand : ICommand 6 | { 7 | public WorkdirCommand(string workdir) 8 | { 9 | Workdir = workdir; 10 | } 11 | 12 | public string Workdir { get; } 13 | 14 | public override string ToString() 15 | { 16 | return $"WORKDIR {Workdir}"; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /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 | } 13 | -------------------------------------------------------------------------------- /FluentDocker.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | True 3 | True -------------------------------------------------------------------------------- /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/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 | } 17 | -------------------------------------------------------------------------------- /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 | } 16 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Example .env file for local testing with act 2 | # Copy this file to .env and fill in your values 3 | # Usage: act -j build --env-file .env 4 | 5 | # Required for SonarCloud scanning 6 | SONAR_TOKEN=your-sonar-token-here 7 | 8 | # Required for NuGet package publishing 9 | NUGET_API_KEY=your-nuget-api-key-here 10 | 11 | # GitHub token for API access (can be a PAT token) 12 | GITHUB_TOKEN=your-github-token-here 13 | 14 | # For simulating branches/PRs with act (optional) 15 | # ACT_BRANCH=main 16 | # ACT_EVENT=push 17 | -------------------------------------------------------------------------------- /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 | } 19 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Examples/EventDriven/README.md: -------------------------------------------------------------------------------- 1 | # EventDriven 2 | 3 | This project will show how to subscribe to events on a certain docker _daemon_ and what they are for firing up a single container. 4 | 5 | Example Run: 6 | 7 | ```bash 8 | Number of hosts:1 9 | unix:///var/run/docker.sock native Running 10 | Spinning up a postgres and wait for ready state... 11 | Service is running 12 | Events: 13 | Ductus.FluentDocker.Model.Events.ContainerCreateEvent 14 | Ductus.FluentDocker.Model.Events.NetworkConnectEvent 15 | Ductus.FluentDocker.Model.Events.ContainerStartEvent 16 | ``` -------------------------------------------------------------------------------- /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 | } 13 | -------------------------------------------------------------------------------- /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 | } 17 | -------------------------------------------------------------------------------- /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 | } 17 | -------------------------------------------------------------------------------- /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 | } 25 | -------------------------------------------------------------------------------- /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 | } 19 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Containers/ContainerRuntime.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Containers 2 | { 3 | public enum ContainerRuntime 4 | { 5 | /// 6 | /// Default runtime provided with docker. This is the 7 | /// default and is not necessary to specify. 8 | /// 9 | Default = 0, 10 | /// 11 | /// The NVIDIA container runtime to utilize the GPU. 12 | /// 13 | /// 14 | /// See https://github.com/NVIDIA/nvidia-docker/wiki/Usage for usage. 15 | /// 16 | Nvidia = 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/Parsers/ProcessExitAwareResponseParser.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Model.Containers; 2 | 3 | namespace Ductus.FluentDocker.Executors.Parsers 4 | { 5 | public sealed class ProcessExitAwareResponseParser : IProcessResponseParser 6 | { 7 | public CommandResponse Response { get; private set; } 8 | 9 | public IProcessResponse Process(ProcessExecutionResult response) 10 | { 11 | Response = response.ToResponse(response.ExitCode == 0, string.Empty, $"ExitCode={response.ExitCode}"); 12 | return this; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Services/FluentDockerNotSupportedException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Ductus.FluentDocker.Common; 3 | 4 | namespace Ductus.FluentDocker.Services 5 | { 6 | public class FluentDockerNotSupportedException : FluentDockerException 7 | { 8 | public FluentDockerNotSupportedException() 9 | { 10 | } 11 | 12 | public FluentDockerNotSupportedException(string message) : base(message) 13 | { 14 | } 15 | 16 | public FluentDockerNotSupportedException(string message, Exception innerException) : base(message, innerException) 17 | { 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Compose/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | nginx: 3 | build: ./nginx 4 | links: 5 | - node1:node1 6 | - node2:node2 7 | - node3:node3 8 | ports: 9 | - "8090:80" 10 | node1: 11 | build: ./node 12 | links: 13 | - redis 14 | ports: 15 | - "8080" 16 | node2: 17 | build: ./node 18 | links: 19 | - redis 20 | ports: 21 | - "8080" 22 | node3: 23 | build: ./node 24 | links: 25 | - redis 26 | ports: 27 | - "8080" 28 | redis: 29 | image: redis 30 | ports: 31 | - "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 | } 27 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/Mappers/StringMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ductus.FluentDocker.Executors.Mappers 4 | { 5 | public sealed class StringMapper : IStreamMapper 6 | { 7 | public string OnData(string data, bool isStdErr) 8 | { 9 | return data; 10 | } 11 | 12 | public string OnProcessEnd(int exitCode) 13 | { 14 | if (exitCode != 0) 15 | { 16 | Error = $"Process exited with exit code {exitCode}"; 17 | } 18 | 19 | return null; 20 | } 21 | 22 | public string Error { get; private set; } = string.Empty; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /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 | } 23 | -------------------------------------------------------------------------------- /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 | } 18 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Builders/FileBuilder/AddCommand.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Model.Common; 2 | 3 | namespace Ductus.FluentDocker.Model.Builders.FileBuilder 4 | { 5 | public sealed class AddCommand : ICommand 6 | { 7 | public AddCommand(TemplateString source, TemplateString destination) 8 | { 9 | Source = source; 10 | Destination = destination; 11 | } 12 | 13 | public TemplateString Source { get; internal set; } 14 | public TemplateString Destination { get; } 15 | 16 | public override string ToString() 17 | { 18 | return $"ADD {Source} {Destination}"; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /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 | } 18 | -------------------------------------------------------------------------------- /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 | } 20 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/Mappers/JObjectStreamMapper.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Linq; 2 | 3 | namespace Ductus.FluentDocker.Executors.Mappers 4 | { 5 | public sealed class JObjectStreamMapper : IStreamMapper 6 | { 7 | public string Error { get; private set; } = string.Empty; 8 | 9 | public JObject OnData(string data, bool isStdErr) 10 | { 11 | if (null == data) 12 | return null; 13 | 14 | return JObject.Parse(data); 15 | } 16 | 17 | public JObject OnProcessEnd(int exitCode) 18 | { 19 | if (exitCode != 0) 20 | Error = $"Process exited with exit code {exitCode}"; 21 | 22 | return null; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Builders/FileBuilder/ExposeCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace Ductus.FluentDocker.Model.Builders.FileBuilder 5 | { 6 | public sealed class ExposeCommand : ICommand 7 | { 8 | public ExposeCommand(params int[] ports) 9 | => Ports = ports.Select(p => p.ToString()) ?? Enumerable.Empty(); 10 | 11 | public ExposeCommand(params string[] ports) 12 | => Ports = ports ?? Enumerable.Empty(); 13 | 14 | public IEnumerable Ports { get; } 15 | 16 | public override string ToString() 17 | { 18 | return $"EXPOSE {string.Join(" ", Ports)}"; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /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 | } 19 | -------------------------------------------------------------------------------- /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.StdOutAsArray; 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 | } 21 | -------------------------------------------------------------------------------- /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.Tests/Common/UnitTestingTraceListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace Ductus.FluentDocker.Tests.Common 5 | { 6 | internal sealed class UnitTestingTraceListener : TraceListener 7 | { 8 | private static readonly Action doNothing = (x) => { /* Do nothing */ }; 9 | public Action OnWrite { get; set; } = doNothing; 10 | public Action OnWriteLine { get; set; } = doNothing; 11 | public override string Name { get; set; } = "Unit Testing Trace Listener"; 12 | 13 | public override void Write(string message) => OnWrite(message); 14 | 15 | public override void WriteLine(string message) => OnWriteLine(message); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Builders/FileBuilder/ShellCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ductus.FluentDocker.Model.Builders.FileBuilder 4 | { 5 | public sealed class ShellCommand : ICommand 6 | { 7 | public ShellCommand(string shell, params string[] args) 8 | { 9 | Shell = shell; 10 | Arguments = args ?? Array.Empty(); 11 | } 12 | 13 | public string Shell { get; } 14 | public string[] Arguments { get; } 15 | 16 | public override string ToString() 17 | { 18 | if (Arguments.Length == 0) { 19 | return $"SHELL [\"{Shell}\"]"; 20 | } 21 | 22 | return $"SHELL [\"{Shell}\",\"{string.Join("\",\"", Arguments)}\"]"; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Builders/FileBuilderConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Ductus.FluentDocker.Model.Common; 5 | 6 | namespace Ductus.FluentDocker.Model.Builders 7 | { 8 | public sealed class FileBuilderConfig 9 | { 10 | public string DockerFileString { get; set; } 11 | public TemplateString UseFile { get; set; } 12 | public IList Commands { get; } = new List(); 13 | 14 | public override string ToString() 15 | { 16 | var sb = new StringBuilder(); 17 | foreach (var cmd in Commands) 18 | { 19 | sb.AppendLine(cmd.ToString()); 20 | } 21 | return sb.ToString(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Model/Containers/ContainerTests.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Model.Common; 2 | using Ductus.FluentDocker.Extensions; 3 | using Ductus.FluentDocker.Model.Containers; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using Newtonsoft.Json; 6 | 7 | namespace Ductus.FluentDocker.Tests.Model.Containers 8 | { 9 | [TestClass] 10 | public class ContainerTests 11 | { 12 | [TestMethod] 13 | public void TestWithNoCreated() 14 | { 15 | var data = ((TemplateString)"Model/Containers/inspect_no_create.json").FromFile(); 16 | var obj = JsonConvert.DeserializeObject(data); 17 | 18 | Assert.AreEqual(obj.Created, default(System.DateTime)); 19 | } 20 | 21 | } 22 | } -------------------------------------------------------------------------------- /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)) 13 | return Orchestrator.All; 14 | 15 | value = value.ToLower(); 16 | 17 | if (value.Equals("kubernetes")) 18 | return Orchestrator.Kubernetes; 19 | return value.Equals("swarm") ? Orchestrator.Swarm : Orchestrator.All; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /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/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 long ExitCode { get; set; } 17 | public string Error { get; set; } 18 | public DateTime StartedAt { get; set; } 19 | public DateTime FinishedAt { get; set; } 20 | public Health Health { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /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 satisfies 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/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.StdOutAsArray.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 | } 21 | -------------------------------------------------------------------------------- /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 | } 23 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Containers/ULimitItem.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Containers 2 | { 3 | /// 4 | /// A item with a and at least a soft limit but may include a hard as well. 5 | /// 6 | public class ULimitItem 7 | { 8 | public ULimitItem(Ulimit ulimit, string soft, string hard = null) 9 | { 10 | Ulimit = ulimit; 11 | Soft = soft; 12 | Hard = hard; 13 | } 14 | 15 | public Ulimit Ulimit { get; } 16 | public string Soft { get; } 17 | public string Hard { get; } 18 | 19 | public override string ToString() 20 | { 21 | return !string.IsNullOrEmpty(Hard) ? $"{Ulimit.ToString().ToLower()}={Soft}:{Hard}" : $"{Ulimit}={Soft}"; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /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.StdOutAsArray.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 | } 21 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Builders/FileBuilder/EntrypointCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ductus.FluentDocker.Model.Builders.FileBuilder 4 | { 5 | public sealed class EntrypointCommand : ICommand 6 | { 7 | public EntrypointCommand(string executable, params string[] args) 8 | { 9 | Executable = executable; 10 | Arguments = args ?? Array.Empty(); 11 | } 12 | 13 | public string Executable { get; } 14 | public string[] Arguments { get; } 15 | 16 | public override string ToString() 17 | { 18 | if (Arguments.Length == 0) { 19 | return $"ENTRYPOINT [\"{Executable}\"]"; 20 | } 21 | 22 | return $"ENTRYPOINT [\"{Executable}\",\"{string.Join("\",\"", Arguments)}\"]"; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /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/Builders/FileBuilder/CmdCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace Ductus.FluentDocker.Model.Builders.FileBuilder 5 | { 6 | public sealed class CmdCommand : ICommand 7 | { 8 | public CmdCommand(string cmd, params string[] args) 9 | { 10 | Cmd = cmd; 11 | Arguments = (args ?? Array.Empty()) 12 | .Select(arg => $"\"{arg}\"") 13 | .ToArray(); 14 | } 15 | 16 | public string Cmd { get; } 17 | public string[] Arguments { get; } 18 | 19 | public override string ToString() 20 | { 21 | var args = string.Join(", ", Arguments); 22 | 23 | args = string.IsNullOrEmpty(args) ? "" : $", {args}"; 24 | 25 | return $"CMD [\"{Cmd}\"{args}]"; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /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 | } 27 | -------------------------------------------------------------------------------- /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 | } 25 | -------------------------------------------------------------------------------- /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.StdOutAsArray)); 19 | return this; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /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 | } 25 | -------------------------------------------------------------------------------- /Examples/DockerInDockerLinux/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Ductus.FluentDocker.Commands; 4 | using Ductus.FluentDocker.Services; 5 | 6 | namespace DockerInDockerLinux 7 | { 8 | class Program 9 | { 10 | static void Main(string[] args) 11 | { 12 | var hosts = new Hosts().Discover(); 13 | 14 | var docker = hosts.FirstOrDefault(x => x.IsNative) ?? hosts.FirstOrDefault(x => x.Name == "default"); 15 | Console.WriteLine($"Docker host: {docker?.Host.Host}, {docker?.Host.AbsolutePath}, {docker?.Host.AbsoluteUri}"); 16 | 17 | var containers = docker?.GetContainers(); 18 | Console.WriteLine(docker?.Host.Host); 19 | Console.WriteLine($"Number of containers: {containers?.Count}"); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /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 | } 32 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Builders/FileBuilder/EnvCommand.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Extensions; 2 | using Ductus.FluentDocker.Model.Common; 3 | using System.Linq; 4 | 5 | namespace Ductus.FluentDocker.Model.Builders.FileBuilder 6 | { 7 | public sealed class EnvCommand : ICommand 8 | { 9 | public EnvCommand(params TemplateString[] nameValue) 10 | { 11 | if (nameValue == null || 0 == nameValue.Length) { 12 | NameValue = new string[0]; 13 | } else { 14 | NameValue = NameValue = nameValue.WrapValue().ToArray(); 15 | } 16 | } 17 | 18 | public string[] NameValue { get; internal set; } 19 | 20 | public override string ToString() 21 | { 22 | if (0 == NameValue.Length) { 23 | return ""; 24 | } 25 | 26 | return $"ENV {string.Join(" ", NameValue)}"; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Builders/FileBuilder/VolumeCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Ductus.FluentDocker.Extensions; 5 | using Ductus.FluentDocker.Model.Common; 6 | 7 | namespace Ductus.FluentDocker.Model.Builders.FileBuilder 8 | { 9 | public sealed class VolumeCommand : ICommand 10 | { 11 | public VolumeCommand(params TemplateString[] mountpoints) 12 | { 13 | var list = new List(); 14 | 15 | foreach (var s in mountpoints.Select(s => s.Rendered)) 16 | { 17 | list.Add(s.WrapWithChar("\"")); 18 | } 19 | 20 | Mountpoints = list.ToArray(); 21 | } 22 | 23 | public string[] Mountpoints { get; } 24 | 25 | public override string ToString() 26 | { 27 | return $"VOLUME [\"{string.Join(",", Mountpoints)}\"]"; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Containers/Container.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Containers 2 | { 3 | public sealed class Container 4 | { 5 | public string Id { get; set; } 6 | public string Image { get; set; } 7 | public System.DateTime Created { get; set; } 8 | public string ResolvConfPath { get; set; } 9 | public string HostnamePath { get; set; } 10 | public string HostsPath { get; set; } 11 | public string LogPath { get; set; } 12 | public string Name { get; set; } 13 | public int RestartCount { get; set; } 14 | public string Driver { get; set; } 15 | public string[] Args { get; set; } 16 | public ContainerState State { get; set; } 17 | public ContainerMount[] Mounts { get; set; } 18 | public ContainerConfig Config { get; set; } 19 | public ContainerNetworkSettings NetworkSettings { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Events/ImagePullEvent.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Events 2 | { 3 | /// 4 | /// Emitted when a remote image has been pulled onto local store. 5 | /// 6 | public sealed class ImagePullEvent : FdEvent 7 | { 8 | public ImagePullEvent() 9 | { 10 | Action = EventAction.Pull; 11 | Type = EventType.Image; 12 | } 13 | 14 | /// 15 | /// The actor is the image name and label 16 | /// 17 | /// 18 | /// The contain the "image name:label". 19 | /// 20 | public sealed class ImagePullActor : EventActor 21 | { 22 | /// 23 | /// Name of the image without label. 24 | /// 25 | public string Name { get; set; } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Examples/.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 | "label": "build", 8 | "command": "dotnet", 9 | "type": "shell", 10 | "args": [ 11 | "build", 12 | // Ask dotnet build to generate full paths for file names. 13 | "/property:GenerateFullPaths=true", 14 | // Do not generate summary otherwise it leads to duplicate errors in Problems panel 15 | "/consoleloggerparameters:NoSummary" 16 | ], 17 | "group": "build", 18 | "presentation": { 19 | "reveal": "silent" 20 | }, 21 | "problemMatcher": "$msCompile" 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /.vscode/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "RunConfiguration": { 3 | "TestSessionTimeout": 30000, 4 | "InIsolation": true, 5 | "CollectSourceInformation": true, 6 | "DisableAppDomain": false, 7 | "DisableParallelization": true, 8 | "MaxCpuCount": 1, 9 | "ResultsDirectory": "${workspaceFolder}/TestResults" 10 | }, 11 | "LoggerRunSettings": { 12 | "Loggers": [ 13 | { 14 | "LogFileName": "${workspaceFolder}/TestResults/test-logs.trx", 15 | "LogFilePrefixName": "test", 16 | "LogFileExtension": ".trx", 17 | "StandardOutputEnabled": true, 18 | "StandardErrorEnabled": true, 19 | "DebugLogsEnabled": true, 20 | "ConsoleLoggerParameters": "Verbose" 21 | } 22 | ] 23 | }, 24 | "TestAdapterRunSettings": { 25 | "TestSessionTimeout": 60000, 26 | "VSTestConsoleDisplayLogsFromAttachedDebugger": true 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /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.StdOutAsArray.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 | } 23 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Builders/FileBuilder/LabelCommand.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Extensions; 2 | using Ductus.FluentDocker.Model.Common; 3 | using System.Linq; 4 | 5 | namespace Ductus.FluentDocker.Model.Builders.FileBuilder 6 | { 7 | public sealed class LabelCommand : ICommand 8 | { 9 | public LabelCommand(params TemplateString[] nameValue) 10 | { 11 | if (nameValue == null || 0 == nameValue.Length) 12 | { 13 | NameValue = new string[0]; 14 | } 15 | else 16 | { 17 | NameValue = nameValue.WrapValue().ToArray(); 18 | } 19 | } 20 | 21 | public string[] NameValue { get; internal set; } 22 | 23 | public override string ToString() 24 | { 25 | if (0 == NameValue.Length) 26 | { 27 | return ""; 28 | } 29 | 30 | return $"LABEL {string.Join(" ", NameValue)}"; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Examples/DockerInDockerLinux/README.md: -------------------------------------------------------------------------------- 1 | # DockerInDockerLinux 2 | 3 | This is a simple example of how to run Docker commands in parent, host, environment within a Docker container. Since the docker container do mount the host docker socket on the default linux socket, _FluentDocker_ will execute the commands as it would if it was running on the host. 4 | 5 | The example is showing two steps necessary to achieve this. 6 | 7 | 1. Make sure that the docker client binary is installed and on the containers environment PATH (see _build.sh_). 8 | 2. Mount the docker socket properly when running the container (_see run.sh_) 9 | 10 | This reflects [Issue #199](https://github.com/mariotoffia/FluentDocker/issues/199) for _Linux_ and [Issue #99](https://github.com/mariotoffia/FluentDocker/issues/99) for _Windows_. 11 | 12 | When running the container it should _at least_ output one container (this _docker-in-docker_ container). -------------------------------------------------------------------------------- /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 | } 28 | -------------------------------------------------------------------------------- /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 | } 28 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace Ductus.FluentDocker.Extensions { 3 | public static class StringExtensions { 4 | 5 | /// 6 | /// This function will wrap the string s with string c (start and end) if 7 | /// not already existent. If already exist, it will leave it, hence it 8 | /// do not double wrap. 9 | /// 10 | /// The string to wrap. 11 | /// The string to check and wrap with if not existing. 12 | /// The wrapped string. 13 | public static string WrapWithChar(this string s, string c) { 14 | 15 | if (!s.StartsWith(c)) { 16 | s = c + s; 17 | } 18 | 19 | if (!s.EndsWith(c)) { 20 | s = s + c; 21 | } 22 | 23 | return s; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /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 | } 28 | -------------------------------------------------------------------------------- /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 | } 29 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Services/IEngineScope.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ductus.FluentDocker.Services 4 | { 5 | public enum EngineScopeType 6 | { 7 | Unknown = 0, 8 | Windows = 1, 9 | Linux = 2 10 | } 11 | 12 | /// 13 | /// Set the docker target engine (if windows) to either Windows or Linux. 14 | /// 15 | public interface IEngineScope : IDisposable 16 | { 17 | /// 18 | /// The current scope in the engine scope 19 | /// 20 | EngineScopeType Scope { get; } 21 | /// 22 | /// Manually alter the scope to be linux 23 | /// 24 | /// If successful or if not altered it returns true, false otherwise 25 | bool UseLinux(); 26 | /// 27 | /// Manually alter the scope to be windows 28 | /// 29 | /// If successful or if not altered it returns true, false otherwise 30 | bool UseWindows(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /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/Events/ContainerStartEvent.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Events 2 | { 3 | /// 4 | /// Emitted when a container has been started. 5 | /// 6 | public sealed class ContainerStartEvent : FdEvent 7 | { 8 | public ContainerStartEvent() 9 | { 10 | Action = EventAction.Start; 11 | Type = EventType.Container; 12 | } 13 | 14 | /// 15 | /// Contains the container hash, and which image is was created from. 16 | /// 17 | /// 18 | /// The actor is the hash of the container. 19 | /// 20 | public sealed class ContainerStartActor : EventActor 21 | { 22 | /// 23 | /// The image name and label such as "alpine:latest". 24 | /// 25 | public string Image { get; set; } 26 | /// 27 | /// Name of the container. 28 | /// 29 | public string Name { get; set; } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /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/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 | } 26 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Events/ContainerCreateEvent.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Events 2 | { 3 | /// 4 | /// Emitted when a container has been created (not started). 5 | /// 6 | public sealed class ContainerCreateEvent : FdEvent 7 | { 8 | public ContainerCreateEvent() 9 | { 10 | Action = EventAction.Create; 11 | Type = EventType.Container; 12 | } 13 | 14 | /// 15 | /// Contains the container hash, and which image is was created from. 16 | /// 17 | /// 18 | /// The actor is the hash of the container. 19 | /// 20 | public sealed class ContainerCreateActor : EventActor 21 | { 22 | /// 23 | /// The image name and label such as "alpine:latest". 24 | /// 25 | public string Image { get; set; } 26 | /// 27 | /// Name of the container. 28 | /// 29 | public string Name { get; set; } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /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 | } 41 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Events/ContainerDestroyEvent.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Events 2 | { 3 | /// 4 | /// Emitted when a container has been removed from local disk (-rm operation). 5 | /// 6 | public sealed class ContainerDestroyEvent : FdEvent 7 | { 8 | public ContainerDestroyEvent() 9 | { 10 | Action = EventAction.Destroy; 11 | Type = EventType.Container; 12 | } 13 | 14 | /// 15 | /// Contains the container hash, and which image is was created from. 16 | /// 17 | /// 18 | /// The actor is the hash of the container. 19 | /// 20 | public sealed class ContainerDestroyActor : EventActor 21 | { 22 | /// 23 | /// The image name and label such as "alpine:latest". 24 | /// 25 | public string Image { get; set; } 26 | /// 27 | /// Name of the container. 28 | /// 29 | public string Name { get; set; } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Events/ContainerStopEvent.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Events 2 | { 3 | /// 4 | /// Emitted when a container has been fully stopped ( is before this event). 5 | /// 6 | public sealed class ContainerStopEvent : FdEvent 7 | { 8 | public ContainerStopEvent() 9 | { 10 | Action = EventAction.Stop; 11 | Type = EventType.Container; 12 | } 13 | 14 | /// 15 | /// Contains the container hash, and which image is was created from. 16 | /// 17 | /// 18 | /// The actor is the hash of the container. 19 | /// 20 | public sealed class ContainerStopActor : EventActor 21 | { 22 | /// 23 | /// The image name and label such as "alpine:latest". 24 | /// 25 | public string Image { get; set; } 26 | /// 27 | /// Name of the container. 28 | /// 29 | public string Name { get; set; } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/ProcessTests/ProcessEnvironmentTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using Ductus.FluentDocker.Common; 4 | using Ductus.FluentDocker.Executors; 5 | using Ductus.FluentDocker.Executors.Parsers; 6 | using Ductus.FluentDocker.Model.Common; 7 | using Microsoft.VisualStudio.TestTools.UnitTesting; 8 | 9 | namespace Ductus.FluentDocker.Tests.ProcessTests 10 | { 11 | [TestClass] 12 | 13 | public class ProcessEnvironmentTest 14 | { 15 | [TestMethod] 16 | public void ProcessShallPassCustomEnvironment() 17 | { 18 | var cmd = "Resources/Scripts/envtest." + (FdOs.IsWindows() ? "bat" : "sh"); 19 | var file = Path.Combine(Directory.GetCurrentDirectory(), (TemplateString)cmd); 20 | 21 | var executor = new ProcessExecutor>(file, string.Empty); 22 | executor.Env["FD_CUSTOM_ENV"] = "My test environment variable"; 23 | 24 | var result = executor.Execute(); 25 | Assert.AreEqual("My test environment variable", result.Data[FdOs.IsWindows() ? 1 : 0]); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Builders/FileBuilder/UserCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Ductus.FluentDocker.Common; 5 | using Ductus.FluentDocker.Extensions; 6 | using Ductus.FluentDocker.Model.Common; 7 | 8 | namespace Ductus.FluentDocker.Model.Builders.FileBuilder 9 | { 10 | public sealed class UserCommand : ICommand 11 | { 12 | public UserCommand(TemplateString user, TemplateString group = null) 13 | { 14 | if (null == user || string.IsNullOrEmpty(user.Rendered)) 15 | { 16 | throw new FluentDockerException("Must specify username or user id"); 17 | } 18 | 19 | 20 | User = user.Rendered; 21 | 22 | if (null != group && !string.IsNullOrEmpty(group.Rendered)) 23 | { 24 | Group = group.Rendered; 25 | } 26 | } 27 | 28 | public string User { get; } 29 | public string Group { get; } 30 | 31 | public override string ToString() 32 | { 33 | if (string.IsNullOrEmpty(Group)) 34 | { 35 | return $"USER {User}"; 36 | } 37 | 38 | return $"USER {User}:{Group}"; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/Parsers/NetworkInspectResponseParser.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 NetworkInspectResponseParser : 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 | } 31 | -------------------------------------------------------------------------------- /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 | } 53 | -------------------------------------------------------------------------------- /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 | } 48 | -------------------------------------------------------------------------------- /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 | } 30 | -------------------------------------------------------------------------------- /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 | } 42 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "dotnet.defaultSolution": "FluentDocker.sln", 3 | "omnisharp.useModernNet": true, 4 | "omnisharp.enableEditorConfigSupport": true, 5 | "csharp.format.enable": true, 6 | "editor.formatOnType": true, 7 | "dotnet.server.useOmnisharp": true, 8 | "omnisharp.enableMsBuildLoadProjectsOnDemand": false, 9 | "omnisharp.maxProjectResults": 1000, 10 | "omnisharp.useEditorFormattingSettings": true, 11 | "dotnet.backgroundAnalysis.analyzerDiagnosticsScope": "openFiles", 12 | "dotnet-test-explorer.testProjectPath": "**/*Tests.csproj", 13 | "dotnet-test-explorer.testArguments": "-f net8.0 --logger \"console;verbosity=detailed\"", 14 | "dotnet-test-explorer.autoWatch": false, 15 | "dotnet-test-explorer.autoExpandTree": true, 16 | "dotnet-test-explorer.showCodeLens": true, 17 | "dotnet-test-explorer.codeLensFailed": "❌", 18 | "dotnet-test-explorer.codeLensPassed": "✅", 19 | "dotnet-test-explorer.runInParallel": false, 20 | "dotnet-test-explorer.treeMode": "full", 21 | "dotnet.completion.showCompletionItemsFromUnimportedNamespaces": true, 22 | "dotnet.formatting.organizeImportsOnFormat": true 23 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Services/INetworkService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Ductus.FluentDocker.Model.Common; 3 | using Ductus.FluentDocker.Model.Containers; 4 | using Ductus.FluentDocker.Model.Networks; 5 | 6 | namespace Ductus.FluentDocker.Services 7 | { 8 | public interface INetworkService : IService 9 | { 10 | string Id { get; } 11 | DockerUri DockerHost { get; } 12 | ICertificatePaths Certificates { get; } 13 | NetworkConfiguration GetConfiguration(bool fresh = false); 14 | 15 | INetworkService Attach(IContainerService container, bool detatchOnDisposeNetwork, string alias = null); 16 | INetworkService Attach(string containerId, bool detatchOnDisposeNetwork, string alias = null); 17 | [Obsolete("Please use the properly spelled `Detach` method instead.")] 18 | INetworkService Detatch(IContainerService container, bool force = false); 19 | INetworkService Detach(IContainerService container, bool force = false); 20 | [Obsolete("Please use the properly spelled `Detach` method instead.")] 21 | INetworkService Detatch(string containerId, bool force = false); 22 | INetworkService Detach(string containerId, bool force = false); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Examples/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | // Use IntelliSense to find out which attributes exist for C# debugging 6 | // Use hover for the description of the existing attributes 7 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/Simple/bin/Debug/netcoreapp3.1/Simple.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/Simple", 16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console 17 | "console": "internalConsole", 18 | "stopAtEntry": false 19 | }, 20 | { 21 | "name": ".NET Core Attach", 22 | "type": "coreclr", 23 | "request": "attach" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /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 | [TestClass] 9 | public class ResourceExtensionsTests 10 | { 11 | [TestMethod] 12 | public void QueryResourcesRecusivelyShallWork() 13 | { 14 | var resources = typeof(NsResolver); 15 | 16 | var res = 17 | resources.ResourceQuery().Where(x => x.Resource.Equals("Dockerfile") || x.Resource.Equals("index.js")).ToArray(); 18 | 19 | Assert.AreEqual(4, res.Length); 20 | Assert.AreEqual(3, res.Count(x => x.Resource == "Dockerfile")); 21 | Assert.AreEqual(1, res.Count(x => x.Resource == "index.js")); 22 | } 23 | 24 | [TestMethod] 25 | public void QueryResourcesNonRecurivelyShallWork() 26 | { 27 | var resources = typeof(NsResolver); 28 | 29 | var res = resources.ResourceQuery(false).ToArray(); 30 | 31 | Assert.AreEqual(1, res.Length); 32 | Assert.AreEqual("docker-compose.yml", res[0].Resource); 33 | } 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 | } 32 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Events/ContainerDieEvent.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Events 2 | { 3 | /// 4 | /// Emitted when a container has been buried (exited). 5 | /// 6 | public sealed class ContainerDieEvent : FdEvent 7 | { 8 | public ContainerDieEvent() 9 | { 10 | Action = EventAction.Die; 11 | Type = EventType.Container; 12 | } 13 | 14 | /// 15 | /// Contains the container hash, and which image is was created from. 16 | /// 17 | /// 18 | /// The actor is the hash of the container. 19 | /// 20 | public sealed class ContainerDieActor : EventActor 21 | { 22 | /// 23 | /// The image name and label such as "alpine:latest". 24 | /// 25 | public string Image { get; set; } 26 | /// 27 | /// Name of the container. 28 | /// 29 | public string Name { get; set; } 30 | /// 31 | /// The exit code that the container returned when died. 32 | /// 33 | public string ExitCode { get; set; } 34 | } 35 | } 36 | } 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) 29 | throw new FluentDockerException("A feature must have a valid Id"); 30 | 31 | foreach (var dependency in Dependencies) 32 | if (!dependency.GetInterfaces().Contains(typeof(IFeature))) 33 | throw new FluentDockerException($"Feature {Id} dependency is dependant on non IFeature type ({dependency})." + 34 | " This is no allowed"); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Builders/FileBuilder/ArgCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Ductus.FluentDocker.Common; 5 | using Ductus.FluentDocker.Extensions; 6 | using Ductus.FluentDocker.Model.Common; 7 | 8 | namespace Ductus.FluentDocker.Model.Builders.FileBuilder 9 | { 10 | public sealed class ArgCommand : ICommand 11 | { 12 | public ArgCommand(TemplateString name, TemplateString defaultValue = null) 13 | { 14 | if (null == name || string.IsNullOrEmpty(name.Rendered)) 15 | { 16 | throw new FluentDockerException("Must, at least, specify the argument name in a ARG"); 17 | } 18 | 19 | 20 | Name = name.Rendered; 21 | 22 | if (null != defaultValue && !string.IsNullOrEmpty(defaultValue.Rendered)) 23 | { 24 | DefaultValue = defaultValue.Rendered; 25 | } 26 | } 27 | 28 | public string Name { get; } 29 | public string DefaultValue { get; } 30 | 31 | public override string ToString() 32 | { 33 | if (string.IsNullOrEmpty(DefaultValue)) 34 | { 35 | return $"ARG {Name}"; 36 | } 37 | 38 | return $"ARG {Name}={DefaultValue}"; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /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 | } 29 | -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Model/Containers/ContainerCreateParamsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Ductus.FluentDocker.Model.Containers; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace Ductus.FluentDocker.Tests.Model.Containers 6 | { 7 | [TestClass] 8 | public class ContainerCreateParamsTests 9 | { 10 | [TestMethod] 11 | public void NvidiaRuntimeGeneratesRuntimeOption() 12 | { 13 | var prms = new ContainerCreateParams { Runtime = ContainerRuntime.Nvidia }; 14 | var opts = prms.ToString(); 15 | Assert.IsTrue(opts.Contains(" --runtime=nvidia")); 16 | } 17 | 18 | [DataTestMethod] 19 | [DataRow(ContainerIsolationTechnology.Hyperv, "hyperv")] 20 | [DataRow(ContainerIsolationTechnology.Process, "process")] 21 | [DataRow(ContainerIsolationTechnology.Default, "default")] 22 | public void ContainerIsolationGeneratesRuntimeOption(ContainerIsolationTechnology isolationTechnology, string expectedIsolationParameter) 23 | { 24 | var prms = new ContainerCreateParams { Isolation = isolationTechnology}; 25 | var opts = prms.ToString(); 26 | Assert.IsTrue(opts.Contains($" --isolation {expectedIsolationParameter}")); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Events/ContainerKillEvent.cs: -------------------------------------------------------------------------------- 1 | namespace Ductus.FluentDocker.Model.Events 2 | { 3 | /// 4 | /// Emitted when a container has been sent a kill signal (but is not yet dead). 5 | /// 6 | public sealed class ContainerKillEvent : FdEvent 7 | { 8 | public ContainerKillEvent() 9 | { 10 | Action = EventAction.Kill; 11 | Type = EventType.Container; 12 | } 13 | 14 | /// 15 | /// Contains the container hash, and which image is was created from. 16 | /// 17 | /// 18 | /// The actor is the hash of the container. 19 | /// 20 | public sealed class ContainerKillActor : EventActor 21 | { 22 | /// 23 | /// The image name and label such as "alpine:latest". 24 | /// 25 | public string Image { get; set; } 26 | /// 27 | /// Name of the container. 28 | /// 29 | public string Name { get; set; } 30 | /// 31 | /// The signal that the container has been signalled. 32 | /// 33 | public string Signal { get; set; } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Model/Builders/FileBuilder/CopyCommandTests.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Model.Builders.FileBuilder; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace Ductus.FluentDocker.Tests.Model.Builders.FileBuilder 5 | { 6 | [TestClass] 7 | public class CopyCommandTests 8 | { 9 | [TestMethod] 10 | public void CopyCommandShallDoubleQuoteWrapAllArguments() 11 | { 12 | var cp = new CopyCommand("entrypoint.sh", "/worker/entrypoint.sh"); 13 | Assert.AreEqual("COPY [\"entrypoint.sh\",\"/worker/entrypoint.sh\"]", cp.ToString()); 14 | } 15 | 16 | [TestMethod] 17 | public void CopyCommandShallNotAddDoubleQuoteWrapForArgumentsWithDoubleQuote() 18 | { 19 | var cp = new CopyCommand("entrypoint.sh", "\"/worker/entrypoint.sh\""); 20 | Assert.AreEqual("COPY [\"entrypoint.sh\",\"/worker/entrypoint.sh\"]", cp.ToString()); 21 | } 22 | 23 | [TestMethod] 24 | public void CopyCommandShallEnsureBothSidesAreDoubleQuotedEvenIfArgumentHasOnlyOneSide() 25 | { 26 | var cp = new CopyCommand("entrypoint.sh", "\"/worker/entrypoint.sh"); 27 | Assert.AreEqual("COPY [\"entrypoint.sh\",\"/worker/entrypoint.sh\"]", cp.ToString()); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /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) 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 | } 38 | -------------------------------------------------------------------------------- /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.StdOutAsArray; 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 | } 39 | -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Model/Builders/FileBuilder/CmdCommandTests.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Model.Builders.FileBuilder; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace Ductus.FluentDocker.Tests.Model.Builders.FileBuilder 5 | { 6 | [TestClass] 7 | public class FileBuilderTest 8 | { 9 | [TestMethod] 10 | public void FromWithAliasShallRenderUsingAS() 11 | { 12 | var from = new FromCommand("mcr.microsoft.com/dotnet/sdk:5.0", "net5.0"); 13 | Assert.AreEqual("FROM mcr.microsoft.com/dotnet/sdk:5.0 AS net5.0", from.ToString()); 14 | } 15 | 16 | [TestMethod] 17 | public void ShellShallHaveCommandAndArgsSeparated() 18 | { 19 | var shell = new ShellCommand("cmd", "/S", "/C"); 20 | Assert.AreEqual("SHELL [\"cmd\",\"/S\",\"/C\"]", shell.ToString()); 21 | } 22 | 23 | [TestMethod] 24 | public void ShellShallSingleCommandNoArgument() 25 | { 26 | var shell = new ShellCommand("cmd"); 27 | Assert.AreEqual("SHELL [\"cmd\"]", shell.ToString()); 28 | } 29 | 30 | [TestMethod] 31 | public void ShellShallSingleCommandOneArgument() 32 | { 33 | var shell = new ShellCommand("cmd","/S"); 34 | Assert.AreEqual("SHELL [\"cmd\",\"/S\"]", shell.ToString()); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /GitVersion.yml: -------------------------------------------------------------------------------- 1 | # GitVersion configuration file 2 | # This file is used by the GitVersion GitHub Action in the CI pipeline 3 | mode: Mainline 4 | major-version-bump-message: '\+semver:\s?(breaking|major)' 5 | minor-version-bump-message: '\+semver:\s?(feature|minor)' 6 | patch-version-bump-message: '\+semver:\s?(fix|patch)' 7 | no-bump-message: '\+semver:\s?(none|skip)' 8 | assembly-versioning-scheme: MajorMinorPatch 9 | assembly-file-versioning-scheme: MajorMinorPatchTag 10 | tag-prefix: '' # Tags should only be the actual Semver version, no 'v' prefix 11 | continuous-delivery-fallback-tag: 'ci' 12 | commit-message-incrementing: Enabled 13 | branches: 14 | master: 15 | regex: ^master$|^main$ 16 | tag: '' 17 | increment: Minor 18 | prevent-increment-of-merged-branch-version: true 19 | track-merge-target: false 20 | feature: 21 | regex: ^features?[/-] 22 | tag: 'beta' 23 | increment: Inherit 24 | pull-request: 25 | regex: ^(pull|pull\-requests|pr)[/-] 26 | tag: 'pr' 27 | increment: Inherit 28 | hotfix: 29 | regex: ^hotfix[/-] 30 | tag: 'beta' 31 | increment: Patch 32 | develop: 33 | regex: ^dev(elop)?(ment)?$ 34 | tag: 'alpha' 35 | increment: Minor 36 | prevent-increment-of-merged-branch-version: false 37 | ignore: 38 | sha: [] 39 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Containers/ContainerConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Ductus.FluentDocker.Model.Containers 5 | { 6 | public sealed class ContainerConfig 7 | { 8 | public string Hostname { get; set; } 9 | [Obsolete("Please use the properly spelled `DomainName` method instead.")] 10 | public string Domainname 11 | { 12 | get => DomainName; 13 | set => DomainName = value; 14 | } 15 | public string DomainName { get; set; } 16 | public string User { get; set; } 17 | public bool AttachStdin { get; set; } 18 | public bool AttachStdout { get; set; } 19 | public bool AttachStderr { get; set; } 20 | public IDictionary ExposedPorts { get; set; } 21 | public bool Tty { get; set; } 22 | public bool OpenStdin { get; set; } 23 | public bool StdinOnce { get; set; } 24 | public string[] Env { get; set; } 25 | public string[] Cmd { get; set; } 26 | public string Image { get; set; } 27 | public IDictionary Volumes { get; set; } 28 | public string WorkingDir { get; set; } 29 | public string[] EntryPoint { get; set; } 30 | public IDictionary Labels { get; set; } 31 | public string StopSignal { get; set; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /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 | /// 15 | /// If this is not set, check the to see if it has a hostname instead. 16 | /// 17 | public IPAddress IpAddress { get; set; } 18 | 19 | /// 20 | /// The hostname of the machine. 21 | /// 22 | /// 23 | /// This may be set using eg. --generic-ip-address=my-host-name and therefore 24 | /// docker-machine will not emit an , instead it will return 25 | /// a hostname of the machine. 26 | /// 27 | public string Hostname { get; set; } 28 | 29 | public int MemorySizeMb { get; set; } 30 | public int StorageSizeMb { get; set; } 31 | public int CpuCount { get; set; } 32 | public bool RequireTls { get; set; } 33 | public MachineAuthConfig AuthConfig { get; set; } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.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 | "label": "build", 8 | "command": "dotnet build", 9 | "type": "shell", 10 | "group": { 11 | "kind": "build", 12 | "isDefault": true 13 | }, 14 | "presentation": { 15 | "reveal": "silent" 16 | }, 17 | "problemMatcher": "$msCompile" 18 | }, 19 | { 20 | "label": "test", 21 | "command": "./use-net8.sh test -f net8.0", 22 | "type": "shell", 23 | "group": { 24 | "kind": "test", 25 | "isDefault": true 26 | }, 27 | "presentation": { 28 | "reveal": "always" 29 | }, 30 | "problemMatcher": "$msCompile" 31 | }, 32 | { 33 | "label": "clean", 34 | "command": "dotnet clean", 35 | "type": "shell", 36 | "group": "build", 37 | "presentation": { 38 | "reveal": "silent" 39 | }, 40 | "problemMatcher": "$msCompile" 41 | } 42 | ] 43 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker/Commands/Service.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Ductus.FluentDocker.Executors; 4 | using Ductus.FluentDocker.Executors.Parsers; 5 | using Ductus.FluentDocker.Extensions; 6 | using Ductus.FluentDocker.Model.Common; 7 | using Ductus.FluentDocker.Model.Containers; 8 | using Ductus.FluentDocker.Model.Stacks; 9 | 10 | namespace Ductus.FluentDocker.Commands 11 | { 12 | /// 13 | /// Docker service commands 14 | /// 15 | /// 16 | /// API 1.24+ 17 | /// docker service create [OPTIONS] IMAGE [COMMAND] [ARG...] 18 | /// 19 | public static class Service 20 | { 21 | // TODO: Implement me! 22 | public static CommandResponse> ServiceCreate(this DockerUri host, 23 | Orchestrator orchestrator = Orchestrator.All, 24 | string kubeConfigFile = null, 25 | ICertificatePaths certificates = null, params string[] stacks) 26 | { 27 | var args = $"{host.RenderBaseArgs(certificates)}"; 28 | var opts = $"--orchestrator={orchestrator}"; // TODO: 29 | 30 | return // TODO: 31 | new ProcessExecutor>( 32 | "docker".ResolveBinary(), 33 | $"{args} stack rm {opts} {string.Join(" ", stacks)}").Execute(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Commands/Images.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Ductus.FluentDocker.Executors; 3 | using Ductus.FluentDocker.Executors.Parsers; 4 | using Ductus.FluentDocker.Extensions; 5 | using Ductus.FluentDocker.Model.Common; 6 | using Ductus.FluentDocker.Model.Containers; 7 | using Ductus.FluentDocker.Model.Images; 8 | 9 | namespace Ductus.FluentDocker.Commands 10 | { 11 | public static class Images 12 | { 13 | /// 14 | /// List images. TODO: Not implemented - DO NOT USE THIS METHOD!! 15 | /// 16 | public static CommandResponse> Rm( 17 | this DockerUri host, 18 | ICertificatePaths certificates = null, 19 | bool force = false, bool prune = false, 20 | params string[] imageId) 21 | { 22 | 23 | // TODO: Need to implement executor properly. 24 | var options = ""; 25 | 26 | if (!prune) { 27 | options = "--no-prune"; 28 | } 29 | 30 | if (force) { 31 | options += "--force"; 32 | } 33 | 34 | return 35 | new ProcessExecutor>( 36 | "docker".ResolveBinary(), 37 | $"{host.RenderBaseArgs(certificates)} images rm {options} {string.Join(" ", imageId)}").Execute(); 38 | } 39 | 40 | } 41 | } -------------------------------------------------------------------------------- /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 | internal static string Prefix = "emb"; 8 | 9 | /// 10 | /// Uri to use when managing 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 | } 49 | -------------------------------------------------------------------------------- /Ductus.FluentDocker.XUnit/FluentDockerTestBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Ductus.FluentDocker.Builders; 3 | using Ductus.FluentDocker.Services; 4 | 5 | namespace Ductus.FluentDocker.XUnit 6 | { 7 | public abstract class FluentDockerTestBase : IDisposable 8 | { 9 | protected IContainerService Container; 10 | 11 | protected abstract ContainerBuilder Build(); 12 | 13 | public FluentDockerTestBase() 14 | { 15 | Container = Build().Build(); 16 | try 17 | { 18 | Container.Start(); 19 | } 20 | catch 21 | { 22 | Container.Dispose(); 23 | throw; 24 | } 25 | 26 | OnContainerInitialized(); 27 | } 28 | 29 | public void Dispose() 30 | { 31 | OnContainerTearDown(); 32 | 33 | var c = Container; 34 | Container = null; 35 | try 36 | { 37 | c?.Dispose(); 38 | } 39 | catch 40 | { 41 | // Ignore 42 | } 43 | } 44 | 45 | /// 46 | /// Invoked just before the container is teared down. 47 | /// 48 | protected virtual void OnContainerTearDown() 49 | { 50 | } 51 | 52 | /// 53 | /// Invoked after a container has been created and started. 54 | /// 55 | protected virtual void OnContainerInitialized() 56 | { 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /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.StdOutAsArray; 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 | } 37 | -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/ExtensionTests/CommandExtensionTest.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Ductus.FluentDocker.Common; 3 | using Ductus.FluentDocker.Extensions; 4 | using Ductus.FluentDocker.Extensions.Utils; 5 | using Ductus.FluentDocker.Model.Common; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | namespace Ductus.FluentDocker.Tests.ExtensionTests 9 | { 10 | [TestClass] 11 | public class CommandExtensionTest 12 | { 13 | [TestMethod, ExpectedException(typeof(FluentDockerException))] 14 | public void MissingDockerComposeShallThrowExceptionInResolveBinary() 15 | { 16 | var dockerFile = Path.Combine(Directory.GetCurrentDirectory(), FdOs.IsWindows() ? "docker.exe" : "docker"); 17 | try 18 | { 19 | using (StreamWriter outputFile = new StreamWriter(dockerFile)) 20 | { 21 | outputFile.WriteLine("fake docker client to satisfy DockerBinariesResolver"); 22 | } 23 | 24 | var resolver = new DockerBinariesResolver(SudoMechanism.None, "", Directory.GetCurrentDirectory()); 25 | "docker-compose".ResolveBinary(resolver, false); 26 | Assert.Fail("Shall never reach here since it shall throw a FluentDockerException"); 27 | } finally { 28 | System.IO.File.Delete(dockerFile); 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Model/Builders/CmdCommandTests.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Model.Builders.FileBuilder; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace Ductus.FluentDocker.Tests.Model.Builders 5 | { 6 | [TestClass] 7 | public class CmdCommandTests 8 | { 9 | [TestMethod] 10 | public void SimpleConstructor() 11 | { 12 | var cmd = new CmdCommand("/bin/bash", "arg1", "arg2"); 13 | 14 | Assert.AreEqual("/bin/bash", cmd.Cmd); 15 | 16 | Assert.AreEqual(2, cmd.Arguments.Length); 17 | Assert.AreEqual("\"arg1\"", cmd.Arguments[0]); 18 | Assert.AreEqual("\"arg2\"", cmd.Arguments[1]); 19 | } 20 | 21 | [TestMethod] 22 | public void ToStringWithParams() 23 | { 24 | var cmd = new CmdCommand("/bin/bash", "arg1", "arg2"); 25 | 26 | Assert.AreEqual("CMD [\"/bin/bash\", \"arg1\", \"arg2\"]", cmd.ToString()); 27 | } 28 | 29 | [TestMethod] 30 | public void ConstructorNoParams() 31 | { 32 | var cmd = new CmdCommand("/bin/bash"); 33 | 34 | Assert.AreEqual("/bin/bash", cmd.Cmd); 35 | 36 | Assert.AreEqual(0, cmd.Arguments.Length); 37 | } 38 | 39 | [TestMethod] 40 | public void ToStringNoParams() 41 | { 42 | var cmd = new CmdCommand("/bin/bash"); 43 | 44 | Assert.AreEqual("CMD [\"/bin/bash\"]", cmd.ToString()); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /.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": "Debug Tests", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | "program": "dotnet", 13 | "args": [ 14 | "test", 15 | "${workspaceFolder}/Ductus.FluentDocker.Tests/Ductus.FluentDocker.Tests.csproj", 16 | "-f", 17 | "net8.0", 18 | "--filter", 19 | "FullyQualifiedName=${command:dotnet-test-explorer.pickMSTestClass}", 20 | "--settings", 21 | "${workspaceFolder}/.vscode/test.runsettings" 22 | ], 23 | "cwd": "${workspaceFolder}", 24 | "console": "internalConsole", 25 | "stopAtEntry": false, 26 | "internalConsoleOptions": "openOnSessionStart", 27 | "justMyCode": false 28 | }, 29 | { 30 | "name": ".NET Core Attach", 31 | "type": "coreclr", 32 | "request": "attach", 33 | "processId": "${command:pickProcess}" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /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 hierarchy). 16 | /// 17 | Option Root { get; } 18 | 19 | /// 20 | /// Gets the Children 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 successful, or and exception is thrown if any errors occurs. 40 | /// If any errors occurs during build time. 41 | new T Build(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /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 | } 40 | -------------------------------------------------------------------------------- /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 | } 38 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Builders/FileBuilder/CopyURLCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Ductus.FluentDocker.Extensions; 4 | using Ductus.FluentDocker.Model.Common; 5 | 6 | namespace Ductus.FluentDocker.Model.Builders.FileBuilder 7 | { 8 | public sealed class CopyURLCommand : CopyCommand 9 | { 10 | /// 11 | /// This generates the _COPY_ command. 12 | /// 13 | /// The _URL_ to download the file from 14 | /// The directory and filename where the file will be downloaded as. 15 | /// To directory. 16 | /// Optional --chown user:group. 17 | /// 18 | /// Optional source location from earlier build stage FROM ... AS alias. This will 19 | /// generate --from=aliasname in the _COPY_ command and hence reference a earlier 20 | /// _FROM ... AS aliasname_ buildstep as source. 21 | /// 22 | public CopyURLCommand(Uri url, TemplateString from, TemplateString to, 23 | TemplateString chownUserAndGroup = null, TemplateString fromAlias = null) : 24 | base(from, to, chownUserAndGroup, fromAlias) 25 | { 26 | FromURL = url; 27 | } 28 | 29 | public Uri FromURL { get; } 30 | public override string ToString() 31 | { 32 | return base.ToString(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /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 | } 53 | -------------------------------------------------------------------------------- /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.StdOutAsArray) 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/Model/Events/FdEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ductus.FluentDocker.Model.Events 4 | { 5 | public abstract class FdEvent 6 | { 7 | /// 8 | /// The type of the event. 9 | /// 10 | /// 11 | /// If not present the is provided. 12 | /// 13 | public EventType Type { get; set; } 14 | 15 | /// 16 | /// The scope of the event. 17 | /// 18 | public EventScope Scope { get; set; } 19 | 20 | /// 21 | /// The event action 22 | /// 23 | public EventAction Action { get; set; } 24 | 25 | /// 26 | /// The actor that is the originator of this event. 27 | /// 28 | public EventActor EventActor { get; set; } 29 | 30 | /// 31 | /// Timestamp in nanoseconds. 32 | /// 33 | public DateTime Time { get; set; } 34 | 35 | } 36 | 37 | /// 38 | /// Base event in the system. 39 | /// 40 | /// 41 | public abstract class FdEvent : FdEvent where T : EventActor 42 | { 43 | /// 44 | /// The actor that is the originator of this event. 45 | /// 46 | public new T EventActor 47 | { 48 | get => (T)base.EventActor; 49 | set => base.EventActor = value; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /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.StdOutAsArray; 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 | } 38 | -------------------------------------------------------------------------------- /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/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.StdOutAsArray; 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 | } 45 | -------------------------------------------------------------------------------- /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.StdOutAsArray; 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 | } 38 | -------------------------------------------------------------------------------- /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", false, 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 | } 46 | -------------------------------------------------------------------------------- /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 Array.Empty(); 42 | } 43 | 44 | return log.Split(new[] { "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Services/IService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Ductus.FluentDocker.Common; 3 | 4 | namespace Ductus.FluentDocker.Services 5 | { 6 | public sealed class ServiceDelegates 7 | { 8 | public delegate void StateChange(object sender, StateChangeEventArgs evt); 9 | } 10 | 11 | public interface IService : IDisposable 12 | { 13 | string Name { get; } 14 | ServiceRunningState State { get; } 15 | /// 16 | /// Starts a service either from scratch or un-pause the service if earlier paused by . 17 | /// 18 | void Start(); 19 | /// 20 | /// Pauses the service (if it supports such) and may be resumed by . 21 | /// 22 | /// If any the service do not support this operation. 23 | /// 24 | /// Some services may implement this functionality and some it makes no sense or it is impossible 25 | /// to pause the service. When the service is paused it will be reflected as 26 | /// in the property. 27 | /// 28 | void Pause(); 29 | void Stop(); 30 | void Remove(bool force = false); 31 | IService AddHook(ServiceRunningState state, Action hook, string uniqueName = null); 32 | IService RemoveHook(string uniqueName); 33 | 34 | event ServiceDelegates.StateChange StateChange; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/Parsers/ImageRmResponseParser.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Ductus.FluentDocker.Model.Containers; 3 | using Ductus.FluentDocker.Model.Images; 4 | using Ductus.FluentDocker.Extensions; 5 | using System; 6 | 7 | namespace Ductus.FluentDocker.Executors.Parsers 8 | { 9 | public sealed class ImageRmResponseParser : IProcessResponseParser> 10 | { 11 | public CommandResponse> Response { get; private set; } 12 | public IProcessResponse> Process(ProcessExecutionResult response) 13 | { 14 | if (response.ExitCode != 0) 15 | { 16 | Response = response.ToErrorResponse((IList)new List()); 17 | return this; 18 | } 19 | 20 | var list = new List(); 21 | foreach (var row in response.StdOutAsArray) 22 | { 23 | var items = row.Split(new string[]{": "},1,StringSplitOptions.RemoveEmptyEntries); 24 | if (items.Length != 2) 25 | { 26 | continue; 27 | } 28 | 29 | 30 | list.Add(new DockerRmImageRowResponse 31 | { 32 | Id = items[1].Contains("sha") ? items[1].ToPlainId() : items[1], 33 | Command = items[0] 34 | }); 35 | } 36 | 37 | Response = response.ToResponse(true, string.Empty, (IList)list); 38 | return this; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /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 virtual ServiceRunningState State 20 | { 21 | get => _state; 22 | protected set 23 | { 24 | if (_state == value) 25 | { 26 | return; 27 | } 28 | 29 | _state = value; 30 | this.StateChange?.Invoke(this, new StateChangeEventArgs(this, value)); 31 | _hooks.Execute(this, _state); 32 | } 33 | } 34 | 35 | public abstract void Start(); 36 | public abstract void Pause(); 37 | public abstract void Stop(); 38 | public abstract void Remove(bool force = false); 39 | 40 | public IService AddHook(ServiceRunningState state, Action hook, string uniqueName = null) 41 | { 42 | _hooks.AddHook(uniqueName ?? Guid.NewGuid().ToString(), state, hook); 43 | return this; 44 | } 45 | 46 | public IService RemoveHook(string uniqueName) 47 | { 48 | _hooks.RemoveHook(uniqueName); 49 | return this; 50 | } 51 | 52 | public event ServiceDelegates.StateChange StateChange; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Executors/Parsers/ClientInspectContainersResponseParser.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Ductus.FluentDocker.Model.Containers; 4 | using Newtonsoft.Json; 5 | 6 | namespace Ductus.FluentDocker.Executors.Parsers 7 | { 8 | public sealed class ClientInspectContainersResponseParser : IProcessResponseParser> 9 | { 10 | public CommandResponse> Response { get; private set; } 11 | 12 | public IProcessResponse> Process(ProcessExecutionResult response) 13 | { 14 | if (response.ExitCode != 0) 15 | { 16 | Response = response.ToErrorResponse((IList)new List()); 17 | return this; 18 | } 19 | 20 | if (string.IsNullOrEmpty(response.StdOut)) 21 | { 22 | Response = response.ToResponse(false, "Empty response", (IList)new List()); 23 | return this; 24 | } 25 | 26 | var containers = JsonConvert.DeserializeObject(response.StdOut); 27 | foreach (var c in containers) 28 | { 29 | c.Name = TrimIfBeginsWithSlash(c.Name); 30 | } 31 | 32 | Response = response.ToResponse(true, string.Empty, (IList)containers.ToList()); 33 | return this; 34 | } 35 | 36 | private static string TrimIfBeginsWithSlash(string name) 37 | { 38 | if (!string.IsNullOrEmpty(name) && name.StartsWith("/")) 39 | return name.Substring(1); 40 | return name; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /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 | } 63 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Events/UnknownEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Ductus.FluentDocker.Model.Events 5 | { 6 | /// 7 | /// All events that currently FluentDocker do not handle. Never rely or use 8 | /// this event for any logic if you're not prepare at any time replace that 9 | /// with a managed one!!! 10 | /// 11 | public sealed class UnknownEvent : FdEvent 12 | { 13 | public UnknownEvent(string action, string type) 14 | { 15 | if (!Enum.TryParse(action, out var enumAction)) 16 | enumAction = EventAction.Unspecified; 17 | 18 | if (!Enum.TryParse(type, out var enumType)) 19 | enumType = EventType.Generic; 20 | 21 | Action = enumAction; 22 | Type = enumType; 23 | ActionRaw = action; 24 | TypeRaw = type; 25 | } 26 | 27 | /// 28 | /// The raw string gotten from the event stream. 29 | /// 30 | public string ActionRaw { get; } 31 | /// 32 | /// The raw string gotten from the event stream. 33 | /// 34 | public string TypeRaw { get; } 35 | /// 36 | /// Contains Id and all attributes it could gather. 37 | /// 38 | public sealed class UnknownActor : EventActor 39 | { 40 | /// 41 | /// Attributes gathered from the raw data. 42 | /// 43 | public IList> Attributes { get; internal set; } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Events/NetworkConnectEvent.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Model.Networks; 2 | 3 | namespace Ductus.FluentDocker.Model.Events 4 | { 5 | /// 6 | /// Emitted when a container has been connected to a network. 7 | /// 8 | public sealed class NetworkConnectEvent : FdEvent 9 | { 10 | public NetworkConnectEvent() 11 | { 12 | Action = EventAction.Connect; 13 | Type = EventType.Network; 14 | } 15 | 16 | /// 17 | /// Contains the network and container hash, along with which network it connected to. 18 | /// 19 | /// 20 | /// The actor is the hash of the network. 21 | /// 22 | public sealed class NetworkConnectActor : EventActor 23 | { 24 | /// 25 | /// The id (hash) of the container that connected to network. 26 | /// 27 | public string ContainerId { get; set; } 28 | /// 29 | /// Name of the network. 30 | /// 31 | public string Name { get; set; } 32 | /// 33 | /// The type of the network. If it is specified in . 34 | /// 35 | public NetworkType Type { get; set; } 36 | /// 37 | /// If the name of the network type is present here. Otherwise null. 38 | /// 39 | public string CustomType { get; set; } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /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.StdOutAsArray; 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("/")) 45 | return name.Substring(1); 46 | return name; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /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 | } 37 | -------------------------------------------------------------------------------- /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 | } 43 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Events/NetworkDisconnectEvent.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Model.Networks; 2 | 3 | namespace Ductus.FluentDocker.Model.Events 4 | { 5 | /// 6 | /// Emitted when a container has disconnected from a network. 7 | /// 8 | public sealed class NetworkDisconnectEvent : FdEvent 9 | { 10 | public NetworkDisconnectEvent() 11 | { 12 | Action = EventAction.Disconnect; 13 | Type = EventType.Network; 14 | } 15 | 16 | /// 17 | /// Contains the network and container hash, along with which network it connected to. 18 | /// 19 | /// 20 | /// The actor is the hash of the network. 21 | /// 22 | public sealed class NetworkDisconnectActor : EventActor 23 | { 24 | /// 25 | /// The id (hash) of the container that connected to network. 26 | /// 27 | public string ContainerId { get; set; } 28 | /// 29 | /// Name of the network. 30 | /// 31 | public string Name { get; set; } 32 | /// 33 | /// The type of the network. If it is specified in . 34 | /// 35 | public NetworkType Type { get; set; } 36 | /// 37 | /// If the name of the network type is present here. Otherwise null. 38 | /// 39 | public string CustomType { get; set; } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /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.StdOutAsArray; 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 | } 40 | -------------------------------------------------------------------------------- /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; } = int.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 | } 40 | -------------------------------------------------------------------------------- /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 | } 52 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Compose/DockerComposeConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Ductus.FluentDocker.Model.Images; 4 | using Ductus.FluentDocker.Model.Common; 5 | 6 | namespace Ductus.FluentDocker.Model.Compose 7 | { 8 | public class DockerComposeConfig 9 | { 10 | /// 11 | /// Fully qualified path to the docker-compose file. 12 | /// 13 | public IList ComposeFilePath { get; set; } = new List(); 14 | 15 | public ComposeVersion ComposeVersion { get; set; } 16 | public bool AlwaysPull { get; set; } 17 | public bool ForceRecreate { get; set; } 18 | public bool NoRecreate { get; set; } 19 | public bool NoBuild { get; set; } 20 | public bool ForceBuild { get; set; } 21 | public TimeSpan TimeoutSeconds { get; set; } 22 | public bool RemoveOrphans { get; set; } 23 | public string AlternativeServiceName { get; set; } 24 | public bool UseColor { get; set; } 25 | public bool Wait { get; set; } 26 | public int? WaitTimeoutSeconds { get; set; } 27 | public bool KeepVolumes { get; set; } 28 | public ImageRemovalOption ImageRemoval { get; set; } 29 | public string[] Services { get; set; } 30 | public bool StopOnDispose { get; set; } = true; 31 | public bool KeepContainers { get; set; } 32 | public IDictionary EnvironmentNameValue { get; set; } = new Dictionary(); 33 | public TemplateString ProjectDirectory {get;set;} 34 | 35 | public IDictionary ContainerConfiguration { get; } = 36 | new Dictionary(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /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 | } 64 | -------------------------------------------------------------------------------- /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 | } 49 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Services/Extensions/HostExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using Ductus.FluentDocker.Commands; 4 | using Ductus.FluentDocker.Common; 5 | using Ductus.FluentDocker.Executors; 6 | using Ductus.FluentDocker.Model.Containers; 7 | using Ductus.FluentDocker.Model.Events; 8 | 9 | namespace Ductus.FluentDocker.Services.Extensions 10 | { 11 | public static class HostExtensions 12 | { 13 | /// 14 | /// Read the events from the event stream. 15 | /// 16 | /// The host to attach to when listening for its events 17 | /// The cancellation token for logs, especially needed when is set to true. 18 | /// A optional set of filters to narrow the amount of events. 19 | /// A optional since filter. 20 | /// A optional stop reading events. 21 | /// A console stream to consume the incoming s. 22 | public static ConsoleStream Events(this IHostService host, CancellationToken token = default, 23 | string[] filters = null, DateTime? since = null, DateTime? until = null) 24 | { 25 | return host.Host.FdEvents(token, filters, since, until, host.Certificates); 26 | } 27 | 28 | public static Result Build(this IHostService host, string name, string tag, string workdir = null, 29 | ContainerBuildParams prms = null) 30 | { 31 | var res = host.Host.Build(name, tag, workdir, prms, host.Certificates); 32 | return res.Success ? res.Data[0].ToSuccess() : string.Empty.ToFailure(res.Error, res.Log); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Ductus.FluentDocker.XUnit/PostgresTestBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Ductus.FluentDocker.Builders; 7 | using Ductus.FluentDocker.Services.Extensions; 8 | 9 | namespace Ductus.FluentDocker.XUnit 10 | { 11 | public abstract class PostgresTestBase : FluentDockerTestBase 12 | { 13 | protected const string PostgresConnectionString = 14 | "Server={0};Port={1};Userid={2};Password={3};" + 15 | "Pooling=false;MinPoolSize=1;MaxPoolSize=20;" + 16 | "Timeout=15;SslMode=Disable;Database={4}"; 17 | 18 | protected const string PostgresUser = "postgres"; 19 | protected const string PostgresDb = "postgres"; 20 | protected readonly string DockerImage; 21 | protected readonly string PostgresPassword; 22 | 23 | protected string ConnectionString; 24 | 25 | protected PostgresTestBase(string password = "mysecretpassword", string image = "postgres:alpine") 26 | { 27 | PostgresPassword = password; 28 | DockerImage = image; 29 | } 30 | 31 | protected override ContainerBuilder Build() 32 | { 33 | return new Builder().UseContainer() 34 | .UseImage("postgres:alpine") 35 | .WithEnvironment( 36 | $"POSTGRES_PASSWORD={PostgresPassword}", 37 | "POSTGRES_HOST_AUTH_METHOD=trust") 38 | .ExposePort(5432) 39 | .WaitForPort("5432/tcp", 30000 /*30s*/); 40 | } 41 | 42 | protected override void OnContainerInitialized() 43 | { 44 | var ep = Container.ToHostExposedEndpoint("5432/tcp"); 45 | ConnectionString = string.Format(PostgresConnectionString, ep.Address, ep.Port, PostgresUser, 46 | PostgresPassword, PostgresDb); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Builders/FileBuilder/FromCommand.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Common; 2 | using Ductus.FluentDocker.Model.Common; 3 | 4 | namespace Ductus.FluentDocker.Model.Builders.FileBuilder 5 | { 6 | public sealed class FromCommand : ICommand 7 | { 8 | /// 9 | /// Specifies the _FROM_ command. 10 | /// 11 | /// The image to derive from and a optional (colon) tag, e.g. myimg:mytag 12 | /// An optional alias. 13 | /// An optional platform such linux/amd64 or windows/amd64. 14 | public FromCommand(TemplateString imageAndTag, TemplateString asName = null, TemplateString platform = null) 15 | { 16 | if (null == imageAndTag || string.IsNullOrEmpty(imageAndTag.Rendered)) { 17 | throw new FluentDockerException("FROM requires at least an image name"); 18 | } 19 | 20 | ImageAndTag = imageAndTag; 21 | 22 | if (null != asName && !string.IsNullOrEmpty(asName.Rendered)) 23 | { 24 | Alias = asName.Rendered; 25 | } 26 | 27 | if (null != platform && !string.IsNullOrEmpty(platform.Rendered)) 28 | { 29 | Platform = platform.Rendered; 30 | } 31 | } 32 | 33 | public string ImageAndTag { get; } 34 | public string Platform { get; } 35 | public string Alias { get; } 36 | 37 | public override string ToString() 38 | { 39 | var s = "FROM"; 40 | 41 | if (!string.IsNullOrEmpty(Platform)) { 42 | s = $"{s} --platform={Platform}"; 43 | } 44 | 45 | s = $"{s} {ImageAndTag}"; 46 | 47 | if (!string.IsNullOrEmpty(Alias)) { 48 | s = $"{s} AS {Alias}"; 49 | } 50 | 51 | return s; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Extensions/ConversionExtension.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Text.RegularExpressions; 3 | 4 | namespace Ductus.FluentDocker.Extensions 5 | { 6 | public static class ConversionExtension 7 | { 8 | /// 9 | /// Converts a numeric expression combined with an optional suffix to denote 10 | /// b, k, m, g. 11 | /// 12 | /// The value to be parsed. 13 | /// An optional custom array of suffix. But has to be among b, k, m, g. 14 | /// If successful the number, otherwise is returned. 15 | public static long Convert(this string value, params string[] unit) 16 | { 17 | if (null == unit || 0 == unit.Length) 18 | unit = new[] { "b", "k", "m", "g" }; 19 | 20 | if (string.IsNullOrWhiteSpace(value)) 21 | return long.MinValue; 22 | 23 | var regex = new Regex(@"(\d+)([a-zA-Z]+)"); 24 | var result = regex.Match(value); 25 | 26 | if (!result.Success) 27 | return long.MinValue; 28 | 29 | var digits = result.Groups[1].Value; 30 | var letters = result.Groups[2].Value; 31 | 32 | if (!unit.Contains(letters)) 33 | return long.MinValue; 34 | 35 | if (!long.TryParse(digits, out var val)) 36 | return long.MinValue; 37 | 38 | if (letters == "b") 39 | return val; 40 | 41 | switch (letters) 42 | { 43 | case "b": 44 | return val; 45 | case "k": 46 | return val * 1024; 47 | case "m": 48 | return val * 1024 * 1024; 49 | case "g": 50 | return val * 1024 * 1024 * 1024; 51 | default: 52 | return long.MinValue; 53 | } 54 | } 55 | } 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 25 | { get; set; } 26 | 27 | public List ExecuteOnRunningArguments { get; set; } 28 | public List ExecuteOnDisposingArguments { get; set; } 29 | 30 | public sealed class WaitForHttpParams 31 | { 32 | public string Url { get; set; } 33 | public long Timeout { get; set; } 34 | public Func Continuation { get; set; } 35 | public HttpMethod Method { get; set; } 36 | public string ContentType { get; set; } 37 | public string Body { get; set; } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /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 | [Obsolete("Please use the properly spelled `StdOutAsArray` method instead.")] 23 | public string[] StdOutAsArry => StdOutAsArray; 24 | public string[] StdOutAsArray => StdOut.Split(new[] { "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries); 25 | [Obsolete("Please use the properly spelled `StdErrAsArray` method instead.")] 26 | public string[] StdErrAsArry => StdErrAsArray; 27 | public string[] StdErrAsArray => StdErr.Split(new[] { "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries); 28 | 29 | public CommandResponse ToResponse(bool success, string error, T data) 30 | { 31 | var log = new List(StdOutAsArray); 32 | if (!string.IsNullOrEmpty(StdErr)) 33 | { 34 | log.AddRange(StdErrAsArray); 35 | } 36 | 37 | return new CommandResponse(success, log, error, data); 38 | } 39 | 40 | public CommandResponse ToErrorResponse(T data) 41 | { 42 | var err = StdErr; 43 | if (string.IsNullOrWhiteSpace(err)) 44 | { 45 | err = $"Error when executing command, exit code {ExitCode}"; 46 | } 47 | 48 | return ToResponse(false, err, data); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /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 since 2 minutes ago' whereas 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 | } 52 | -------------------------------------------------------------------------------- /Ductus.FluentDocker/Model/Builders/FileBuilder/CopyCommand.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Extensions; 2 | using Ductus.FluentDocker.Model.Common; 3 | 4 | namespace Ductus.FluentDocker.Model.Builders.FileBuilder 5 | { 6 | public class CopyCommand : ICommand 7 | { 8 | /// 9 | /// This generates the _COPY_ command. 10 | /// 11 | /// From directory. 12 | /// To directory. 13 | /// Optional --chown user:group. 14 | /// 15 | /// Optional source location from earlier build stage FROM ... AS alias. This will 16 | /// generate --from=aliasname in the _COPY_ command and hence reference a earlier 17 | /// _FROM ... AS aliasname_ buildstep as source. 18 | /// 19 | public CopyCommand(TemplateString from, TemplateString to, 20 | TemplateString chownUserAndGroup = null, TemplateString fromAlias = null) 21 | { 22 | From = from.Rendered.WrapWithChar("\""); 23 | To = to.Rendered.WrapWithChar("\""); 24 | 25 | if (null != chownUserAndGroup && !string.IsNullOrEmpty(chownUserAndGroup.Rendered)) { 26 | Chown = chownUserAndGroup.Rendered; 27 | } 28 | 29 | if (null != fromAlias && !string.IsNullOrEmpty(fromAlias.Rendered)) { 30 | Alias = fromAlias.Rendered; 31 | } 32 | } 33 | 34 | public string From { get; } 35 | public string To { get; } 36 | public string Alias { get; } 37 | public string Chown { get; } 38 | 39 | public override string ToString() 40 | { 41 | string s = "COPY"; 42 | 43 | if (!string.IsNullOrEmpty(Chown)) { 44 | s = $"{s} --chown={Chown}"; 45 | } 46 | 47 | if (!string.IsNullOrEmpty(Alias)) { 48 | s = $"{s} --from={Alias}"; 49 | } 50 | 51 | return $"{s} [{From},{To}]"; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Examples/EventDriven/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Ductus.FluentDocker; 4 | using Ductus.FluentDocker.Builders; 5 | using Ductus.FluentDocker.Extensions; 6 | using Ductus.FluentDocker.Model.Events; 7 | using Ductus.FluentDocker.Services; 8 | using Ductus.FluentDocker.Services.Extensions; 9 | 10 | namespace EventDriven 11 | { 12 | class Program 13 | { 14 | static void Main(string[] args) 15 | { 16 | var hosts = new Hosts().Discover(); 17 | Console.WriteLine($"Number of hosts:{hosts.Count}"); 18 | 19 | foreach (var host in hosts) 20 | { 21 | Console.WriteLine($"{host.Host} {host.Name} {host.State}"); 22 | } 23 | 24 | Console.WriteLine("Spinning up a postgres and wait for ready state..."); 25 | using (var events = Fd.Native().Events()) 26 | { 27 | using ( 28 | var container = 29 | new Builder().UseContainer() 30 | .UseImage("postgres:9.6-alpine") 31 | .ExposePort(5432) 32 | .WithEnvironment("POSTGRES_PASSWORD=mysecretpassword") 33 | .WaitForPort("5432/tcp", 30000) 34 | .Build() 35 | .Start()) 36 | { 37 | var config = container.GetConfiguration(true); 38 | Console.WriteLine(ServiceRunningState.Running == config.State.ToServiceState() 39 | ? "Service is running" 40 | : "Failed to start nginx instance..."); 41 | 42 | FdEvent evt; 43 | var list = new List(); 44 | while ((evt = events.TryRead(5000)) != null) 45 | { 46 | list.Add(evt); 47 | } 48 | 49 | Console.WriteLine("Events:"); 50 | foreach (var e in list) 51 | { 52 | Console.WriteLine(e); 53 | } 54 | } 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /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 | } 59 | -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/ExtensionTests/EnironmentExtensionTests.cs: -------------------------------------------------------------------------------- 1 | using Ductus.FluentDocker.Extensions; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace Ductus.FluentDocker.Tests.ExtensionTests 5 | { 6 | [TestClass] 7 | public class EnvironmentExtensionTests 8 | { 9 | [TestMethod] 10 | public void NullStringShallGiveNullReturnInExtract() 11 | { 12 | var t = EnvironmentExtensions.Extract(null); 13 | Assert.IsNull(t); 14 | } 15 | [TestMethod] 16 | public void EmptyStringShallGiveNullReturnInExtract() 17 | { 18 | var t = "".Extract(); 19 | Assert.IsNull(t); 20 | } 21 | [TestMethod] 22 | public void OnlyWhitespaceStringShallGiveNullReturnInExtract() 23 | { 24 | var t = " ".Extract(); 25 | Assert.IsNull(t); 26 | } 27 | [TestMethod] 28 | public void SingleNameNotEqualSignGivesStringShallGiveNameAnEmptyStringReturnInExtract() 29 | { 30 | var t = "CUSTOM_ENV".Extract(); 31 | Assert.AreEqual("CUSTOM_ENV", t.Item1); 32 | Assert.IsTrue(t.Item2 == string.Empty); 33 | } 34 | [TestMethod] 35 | public void SingleNameWithEqualSignGivesStringShallGiveNameAnEmptyStringReturnInExtract() 36 | { 37 | var t = "CUSTOM_ENV=".Extract(); 38 | Assert.AreEqual("CUSTOM_ENV", t.Item1); 39 | Assert.IsTrue(t.Item2 == string.Empty); 40 | } 41 | [TestMethod] 42 | public void NameValueShallReturnNameAndValue() 43 | { 44 | var t = "CUSTOM_ENV=custom value".Extract(); 45 | Assert.AreEqual("CUSTOM_ENV", t.Item1); 46 | Assert.AreEqual("custom value", t.Item2); 47 | } 48 | [TestMethod] 49 | public void ItShallBePossibleToHaveEqualSignsInTheValue() 50 | { 51 | var t = "CUSTOM_ENV=custom value with = sign shall be possible".Extract(); 52 | Assert.AreEqual("CUSTOM_ENV", t.Item1); 53 | Assert.AreEqual("custom value with = sign shall be possible", t.Item2); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Ductus.FluentDocker.Tests/Common/LoggerTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using Ductus.FluentDocker.Common; 5 | using Ductus.FluentDocker.Services; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | namespace Ductus.FluentDocker.Tests.Common 9 | { 10 | [TestClass] 11 | public class LoggerTests 12 | { 13 | [TestMethod] 14 | public void ShouldNotLogToTraceListenerWhenLoggerIsDisabled() 15 | { 16 | Logging.Disabled(); 17 | 18 | // Arrange 19 | var messages = new List 20 | { 21 | "message 1", 22 | "message 2", 23 | "message 3", 24 | }; 25 | 26 | var actualTraceMessages = new List(); 27 | 28 | Trace.Listeners.Add(new UnitTestingTraceListener 29 | { 30 | OnWriteLine = actualTraceMessages.Add 31 | }); 32 | 33 | // Act 34 | messages.ForEach(Logger.Log); 35 | Trace.Flush(); 36 | 37 | // Assert 38 | Assert.IsFalse(actualTraceMessages.Any()); 39 | } 40 | 41 | [TestMethod] 42 | public void ShouldLogToTraceListenerWhenLoggerIsEnabled() 43 | { 44 | Logging.Enabled(); 45 | 46 | // Arrange 47 | var messages = new List 48 | { 49 | "message 1", 50 | "message 2", 51 | "message 3", 52 | }; 53 | 54 | var expectedTraceMessages = messages 55 | .Select(message => $"Ductus.FluentDocker: {message}") 56 | .ToList(); 57 | 58 | var actualTraceMessages = new List(); 59 | 60 | Trace.Listeners.Add(new UnitTestingTraceListener 61 | { 62 | OnWriteLine = actualTraceMessages.Add 63 | }); 64 | 65 | // Act 66 | messages.ForEach(Logger.Log); 67 | Trace.Flush(); 68 | 69 | // Assert 70 | CollectionAssert.AreEqual(expectedTraceMessages, actualTraceMessages); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /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 | } 78 | -------------------------------------------------------------------------------- /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 | if (uriString == DockerHostUrlMacOrLinux || uriString == DockerHostUrlWindowsNative) 17 | this.IsStandardDaemon = true; 18 | } 19 | 20 | public static string GetDockerHostEnvironmentPathOrDefault() 21 | { 22 | var env = Environment.GetEnvironmentVariable(DockerHost); 23 | if (null != env) 24 | { 25 | return env; 26 | } 27 | 28 | if (FdOs.IsWindows()) 29 | { 30 | return CommandExtensions.IsToolbox() ? DockerHostUrlLegacy : DockerHostUrlWindowsNative; 31 | } 32 | 33 | return CommandExtensions.IsToolbox() ? DockerHostUrlLegacy : DockerHostUrlMacOrLinux; 34 | } 35 | 36 | /// 37 | /// Returns true if the DockerUri has a "standard" daemon URI. 38 | /// 39 | /// True if standard daemon, false otherwise. 40 | /// 41 | /// If it is a standard daemon URI, there's no need to add the -H flag 42 | /// 43 | public bool IsStandardDaemon {get;} 44 | 45 | public override string ToString() 46 | { 47 | var baseString = base.ToString(); 48 | 49 | if (Scheme == "ssh") 50 | return baseString.TrimEnd('/'); 51 | 52 | if (Scheme == "npipe") 53 | return baseString.Substring(0, 6) + "//" + baseString.Substring(6); 54 | 55 | return baseString; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /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 | } 41 | --------------------------------------------------------------------------------