diff --git a/Ductus.FluentDocker/Builders/DockerContextAwareRemoteBuilder.cs b/Ductus.FluentDocker/Builders/DockerContextAwareRemoteBuilder.cs new file mode 100644 index 00000000..bd0c14ca --- /dev/null +++ b/Ductus.FluentDocker/Builders/DockerContextAwareRemoteBuilder.cs @@ -0,0 +1,27 @@ +// TODO: https://www.docker.com/blog/how-to-deploy-on-remote-docker-hosts-with-docker-compose/ + +using Ductus.FluentDocker.Builders; +using Ductus.FluentDocker.Services; + +namespace Ductus.FluentDocker.Builders +{ + + public class DockerContextAwareRemoteBuilder : BaseBuilder + { + private string _uri; + internal DockerContextAwareRemoteBuilder(IBuilder parent, string uri = null) : base(parent) + { + _uri = uri; + } + + public override IHostService Build() + { + throw new System.NotImplementedException(); + } + + protected override IBuilder InternalCreate() + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Ductus.FluentDocker/Builders/DockerContextBuilder.cs b/Ductus.FluentDocker/Builders/DockerContextBuilder.cs new file mode 100644 index 00000000..7355d0eb --- /dev/null +++ b/Ductus.FluentDocker/Builders/DockerContextBuilder.cs @@ -0,0 +1,8 @@ +namespace Ductus.FluentDocker.Builders +{ + // TODO: Implement one for docker context - https://docs.docker.com/engine/reference/commandline/context/ + public class DockerContextBuilder + { + + } +} \ No newline at end of file diff --git a/Ductus.FluentDocker/Builders/HostBuilder.cs b/Ductus.FluentDocker/Builders/HostBuilder.cs index a0c089a5..7165dc33 100644 --- a/Ductus.FluentDocker/Builders/HostBuilder.cs +++ b/Ductus.FluentDocker/Builders/HostBuilder.cs @@ -3,76 +3,84 @@ namespace Ductus.FluentDocker.Builders { - public sealed class HostBuilder : BaseBuilder - { - internal HostBuilder(IBuilder builder) : base(builder) + public sealed class HostBuilder : BaseBuilder { - } + internal HostBuilder(IBuilder builder) : base(builder) + { + } - public override IHostService Build() - { - return IsNative ? new Hosts().Native() : null; - } + public override IHostService Build() + { + return IsNative ? new Hosts().Native() : null; + } - protected override IBuilder InternalCreate() - { - return new HostBuilder(this); - } + protected override IBuilder InternalCreate() + { + return new HostBuilder(this); + } - public bool IsNative { get; private set; } + public bool IsNative { get; private set; } - public HostBuilder UseNative() - { - IsNative = true; - return this; - } + public HostBuilder UseNative() + { + IsNative = true; + return this; + } - public MachineBuilder UseMachine() - { - var existing = Childs.FirstOrDefault(x => x is MachineBuilder); - if (null != existing) - { - return (MachineBuilder)existing; - } + public MachineBuilder UseMachine() + { + var existing = Childs.FirstOrDefault(x => x is MachineBuilder); + if (null != existing) + { + return (MachineBuilder)existing; + } - var builder = new MachineBuilder(this); - Childs.Add(builder); - return builder; - } + var builder = new MachineBuilder(this); + Childs.Add(builder); + return builder; + } - public RemoteSshHostBuilder UseSsh(string ipAddress = null) - { - var builder = new RemoteSshHostBuilder(this, ipAddress); - Childs.Add(builder); - return builder; - } + public RemoteSshHostBuilder UseSsh(string ipAddress = null) + { + var builder = new RemoteSshHostBuilder(this, ipAddress); + Childs.Add(builder); + return builder; + } - public ImageBuilder DefineImage(string image = null) - { - var builder = new ImageBuilder(this).AsImageName(image); - Childs.Add(builder); - return builder; - } + // Same as but uses docker context instead of docker-machine + public DockerContextAwareRemoteBuilder UseContextSsh(string uri = null) + { + var builder = new DockerContextAwareRemoteBuilder(this, uri); + Childs.Add(builder); + return builder; + } - public ContainerBuilder UseContainer() - { - var builder = new ContainerBuilder(this); - Childs.Add(builder); - return builder; - } + public ImageBuilder DefineImage(string image = null) + { + var builder = new ImageBuilder(this).AsImageName(image); + Childs.Add(builder); + return builder; + } - public NetworkBuilder UseNetwork(string name = null) - { - var builder = new NetworkBuilder(this, name); - Childs.Add(builder); - return builder; - } + public ContainerBuilder UseContainer() + { + var builder = new ContainerBuilder(this); + Childs.Add(builder); + return builder; + } - public VolumeBuilder UseVolume(string name = null) - { - var builder = new VolumeBuilder(this, name); - Childs.Add(builder); - return builder; + public NetworkBuilder UseNetwork(string name = null) + { + var builder = new NetworkBuilder(this, name); + Childs.Add(builder); + return builder; + } + + public VolumeBuilder UseVolume(string name = null) + { + var builder = new VolumeBuilder(this, name); + Childs.Add(builder); + return builder; + } } - } } diff --git a/Ductus.FluentDocker/Builders/RemoteSshHostBuilder.cs b/Ductus.FluentDocker/Builders/RemoteSshHostBuilder.cs index 70e1fe84..fb9b1564 100644 --- a/Ductus.FluentDocker/Builders/RemoteSshHostBuilder.cs +++ b/Ductus.FluentDocker/Builders/RemoteSshHostBuilder.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using Ductus.FluentDocker.Commands; using Ductus.FluentDocker.Common; using Ductus.FluentDocker.Model.Common; @@ -7,164 +6,164 @@ namespace Ductus.FluentDocker.Builders { - /// - /// Sets up a remote docker session in the docker-machine registry. - /// - /// - /// Setup a SSH key for the remote host by: ssh-keygen -t rsa. - /// And then copy it to the remote machine: - /// ssh-copy-id {username}@{host} - /// Later, Docker Machine will be sending commands over SSH on our behalf, so you'll need to be able to enter sudo mode - /// without entering your password. You may want to only enable this while we configure Docker Machine. SSH to the remote - /// machine and edit the sudoers file: sudo nano /etc/sudoers - /// And add the following to the end of the file where {username} is your username on the remote machine: - /// {username} ALL=(ALL) NOPASSWD:ALL - /// Save the file, logout and login again and you should be able to enter sudo mode without entering your password. - /// Docker Machine will SSH to the remote machine to configure the Docker engine. The Docker client will then connect on - /// TCP port 2376. You'll need to make sure this port is open on your firewall. - /// After docker is configured use this builder to set the ip, name and other options to connect to the remote machine. - /// This is only required once per host since it is stored in the docker-machine registry and this class will use the - /// name as the key to look it up. - /// - public sealed class RemoteSshHostBuilder : BaseBuilder - { - private string _ipAddress; - private string _name; - private int _port = -1; - private string _sshUser; - - private string _sshKeyPath = FdOs.IsWindows() - ? ((TemplateString)"${E_LOCALAPPDATA}/lxss/home/martoffi/.ssh/id_rsa").Rendered - : "~/.ssh/id_rsa"; - - internal RemoteSshHostBuilder(IBuilder parent, string ipAddress = null) : base(parent) - { - _ipAddress = ipAddress; - } - /// - /// Creates or looks up the named remote host using docker-machine registry. + /// Sets up a remote docker session in the docker-machine registry. /// - /// A host service if successful. - /// If any errors occurs. /// - /// This method first checks if a entry with the specified name already exist. If so it will return it without - /// checking the ip or other properties. If you want to update the entry please delete it first and then - /// use this method. If the entry is not found in the docker-machine registry. It will create it and discover it - /// again. + /// Setup a SSH key for the remote host by: ssh-keygen -t rsa. + /// And then copy it to the remote machine: + /// ssh-copy-id {username}@{host} + /// Later, Docker Machine will be sending commands over SSH on our behalf, so you'll need to be able to enter sudo mode + /// without entering your password. You may want to only enable this while we configure Docker Machine. SSH to the remote + /// machine and edit the sudoers file: sudo nano /etc/sudoers + /// And add the following to the end of the file where {username} is your username on the remote machine: + /// {username} ALL=(ALL) NOPASSWD:ALL + /// Save the file, logout and login again and you should be able to enter sudo mode without entering your password. + /// Docker Machine will SSH to the remote machine to configure the Docker engine. The Docker client will then connect on + /// TCP port 2376. You'll need to make sure this port is open on your firewall. + /// After docker is configured use this builder to set the ip, name and other options to connect to the remote machine. + /// This is only required once per host since it is stored in the docker-machine registry and this class will use the + /// name as the key to look it up. /// - public override IHostService Build() - { - if (string.IsNullOrEmpty(_name)) - throw new FluentDockerException("Cannot create machine (for remote docker access) since no name is set"); - - var machine = new Hosts().FromMachineName(_name); - if (null != machine) - return machine; - - if (string.IsNullOrEmpty(_ipAddress)) - throw new FluentDockerException("Cannot create machine (for remote docker access) since no ip address is set"); - - var opts = new List { $"--generic-ip-address={_ipAddress}" }; - - if (_port != -1) - opts.Add($" --generic-ssh-port={_port}"); - if (!string.IsNullOrEmpty(_sshKeyPath)) - opts.Add($"--generic-ssh-key=\"{_sshKeyPath}\""); - if (!string.IsNullOrEmpty(_sshUser)) - opts.Add($"--generic-ssh-user={_sshUser}"); - - - var resp = _name.Create("generic", opts.ToArray()); - if (!resp.Success) - throw new FluentDockerException( - $"Could not create machine (for remote docker host access) {_name} Log: {resp}"); - - return new Hosts().FromMachineName(_name); - } - - /// - /// Name of the remote docker host to set or use to lookup. - /// - /// The name. - /// Itself for fluent access. - /// - /// If a docker-machine with specified name is already created, this will be used. If not - /// found it will create a machine with specified ip address and possibly other configuration before - /// returning the host on the function. - /// - public RemoteSshHostBuilder WithName(string name) - { - _name = name; - return this; - } - - /// - /// The remote daemon IP address. - /// - /// The address to use when communicating with the remote daemon using ssh. - /// Itself for fluent access. - public RemoteSshHostBuilder UseIpAddress(string ipAddress) - { - _ipAddress = ipAddress; - return this; - } - - /// - /// If other than the default port (22) set it here. - /// - /// The port the remote SSH daemon is using. - /// Itself for fluent access. - public RemoteSshHostBuilder UsePort(int port) - { - _port = port; - return this; - } - - /// - /// The fully qualified path to the key file to use when doing SSH authentication. - /// - /// The fully qualified path to the key file for SSH communication. - /// Itself for fluent access. - public RemoteSshHostBuilder WithSshKeyPath(TemplateString path) - { - _sshKeyPath = FdOs.IsWindows() ? path.Rendered.Replace('\\', '/') : path.Rendered; - return this; - } - - /// - /// The user (if other than root) to use when doing SSH communication. - /// - /// The user to use. - /// Itself for fluent access. - public RemoteSshHostBuilder WithSshUser(string user) - { - _sshUser = user; - return this; - } - - public HostBuilder Host() - { - return (HostBuilder)((IBuilder)this).Parent.Value; - } - - public ImageBuilder DefineImage(string image = null) - { - var builder = new ImageBuilder(this).AsImageName(image); - Childs.Add(builder); - return builder; - } - - public ContainerBuilder UseContainer() - { - var builder = new ContainerBuilder(this); - Childs.Add(builder); - return builder; - } - - protected override IBuilder InternalCreate() + public sealed class RemoteSshHostBuilder : BaseBuilder { - return new RemoteSshHostBuilder(this); + private string _ipAddress; + private string _name; + private int _port = -1; + private string _sshUser; + + private string _sshKeyPath = FdOs.IsWindows() + ? ((TemplateString)"${E_LOCALAPPDATA}/lxss/home/martoffi/.ssh/id_rsa").Rendered + : "~/.ssh/id_rsa"; + + internal RemoteSshHostBuilder(IBuilder parent, string ipAddress = null) : base(parent) + { + _ipAddress = ipAddress; + } + + /// + /// Creates or looks up the named remote host using docker-machine registry. + /// + /// A host service if successful. + /// If any errors occurs. + /// + /// This method first checks if a entry with the specified name already exist. If so it will return it without + /// checking the ip or other properties. If you want to update the entry please delete it first and then + /// use this method. If the entry is not found in the docker-machine registry. It will create it and discover it + /// again. + /// + public override IHostService Build() + { + if (string.IsNullOrEmpty(_ipAddress)) + throw new FluentDockerException("Cannot create machine (for remote docker access) since no ip address is set"); + + if (string.IsNullOrEmpty(_name)) + throw new FluentDockerException("Cannot create machine (for remote docker access) since no name is set"); + + var machine = new Hosts().FromMachineName(_name); + if (null != machine) + return machine; + + var opts = new List { $"--generic-ip-address={_ipAddress}" }; + + if (_port != -1) + opts.Add($" --generic-ssh-port={_port}"); + if (!string.IsNullOrEmpty(_sshKeyPath)) + opts.Add($"--generic-ssh-key=\"{_sshKeyPath}\""); + if (!string.IsNullOrEmpty(_sshUser)) + opts.Add($"--generic-ssh-user={_sshUser}"); + + + var resp = _name.Create("generic", opts.ToArray()); + if (!resp.Success) + throw new FluentDockerException( + $"Could not create machine (for remote docker host access) {_name} Log: {resp}"); + + return new Hosts().FromMachineName(_name); + } + + /// + /// Name of the remote docker host to set or use to lookup. + /// + /// The name. + /// Itself for fluent access. + /// + /// If a docker-machine with specified name is already created, this will be used. If not + /// found it will create a machine with specified ip address and possibly other configuration before + /// returning the host on the function. + /// + public RemoteSshHostBuilder WithName(string name) + { + _name = name; + return this; + } + + /// + /// The remote daemon IP address. + /// + /// The address or URI to use when communicating with the remote daemon using ssh. + /// Itself for fluent access. + public RemoteSshHostBuilder UseIpAddress(string ipAddress) + { + _ipAddress = ipAddress; + return this; + } + + /// + /// If other than the default port (22) set it here. + /// + /// The port the remote SSH daemon is using. + /// Itself for fluent access. + public RemoteSshHostBuilder UsePort(int port) + { + _port = port; + return this; + } + + /// + /// The fully qualified path to the key file to use when doing SSH authentication. + /// + /// The fully qualified path to the key file for SSH communication. + /// Itself for fluent access. + public RemoteSshHostBuilder WithSshKeyPath(TemplateString path) + { + _sshKeyPath = FdOs.IsWindows() ? path.Rendered.Replace('\\', '/') : path.Rendered; + return this; + } + + /// + /// The user (if other than root) to use when doing SSH communication. + /// + /// The user to use. + /// Itself for fluent access. + public RemoteSshHostBuilder WithSshUser(string user) + { + _sshUser = user; + return this; + } + + public HostBuilder Host() + { + return (HostBuilder)((IBuilder)this).Parent.Value; + } + + public ImageBuilder DefineImage(string image = null) + { + var builder = new ImageBuilder(this).AsImageName(image); + Childs.Add(builder); + return builder; + } + + public ContainerBuilder UseContainer() + { + var builder = new ContainerBuilder(this); + Childs.Add(builder); + return builder; + } + + protected override IBuilder InternalCreate() + { + return new RemoteSshHostBuilder(this); + } } - } } diff --git a/Ductus.FluentDocker/Commands/Context.cs b/Ductus.FluentDocker/Commands/Context.cs new file mode 100644 index 00000000..69984085 --- /dev/null +++ b/Ductus.FluentDocker/Commands/Context.cs @@ -0,0 +1,173 @@ +using System.Collections.Generic; +using Ductus.FluentDocker.Common; +using Ductus.FluentDocker.Executors; +using Ductus.FluentDocker.Executors.Parsers; +using Ductus.FluentDocker.Extensions; +using Ductus.FluentDocker.Model.Common; +using Ductus.FluentDocker.Model.Containers; +using Ductus.FluentDocker.Model.Stacks; + +namespace Ductus.FluentDocker.Commands +{ + public interface IContextEndpoint + { + /// + /// Copy the context (docker or kubernetes) from the following named context. + /// + /// Name of the docker / kubernetes context. + string From { get; set; } + } + public sealed class DockerEndpoint : IContextEndpoint + { + /// + /// Copy the context from a named docker context. + /// + /// The name to copy the docker context config from + /// + /// If this set to null or empty string (default) it will use + /// the current docker context. + /// + public string From { get; set; } + /// + /// The URI to the remote host e.g. ssh://nisse@remotemachine.com + /// + /// The URI to the remote host. + public DockerUri Host { get; set; } + /// + /// Paths to certificates if remote host is protected using TLS. + /// + /// + public CertificatePaths Certificates { get; set; } + /// + /// Set this to true to skip TLS verify. + /// + /// True if remote TLS certificate shall not be verified. + public bool SkipTLSVerify { get; set; } + + public override string ToString() + { + string s = ""; + if (null != Host) + { + s = $"host={Host}"; + } + if (!string.IsNullOrEmpty(From)) + { + s.CommaAdd($"from={From}"); + } + if (SkipTLSVerify) + { + s.CommaAdd($"skip-tls-verify=true"); + } + if (null != Certificates?.CaCertificate) + { + s.CommaAdd($"ca={Certificates.CaCertificate}"); + } + if (null != Certificates?.ClientCertificate) + { + s.CommaAdd($"cert={Certificates.ClientCertificate}"); + } + if (null != Certificates?.ClientKey) + { + s.CommaAdd($"key={Certificates.ClientKey}"); + } + return s; + } + } + + public sealed class KubernetesEndpoint : IContextEndpoint + { + /// + /// Copy the context from a named kubernetes configuration. + /// + /// The name to copy the kubernetes config from + public string From { get; set; } + /// + /// The kubernetes configuration file path. + /// + /// Path to the kubernetes configuration file + public string Config { get; set; } + /// + /// Overrides the context set in the kubernetes config file. + /// + /// + public string ContextOverride { get; set; } + /// + /// Overrides the namespace set in the kubernetes config file + /// + /// + public string NamespaceOverride { get; set; } + + public override string ToString() + { + string s = ""; + if (!string.IsNullOrEmpty(From)) + { + s = $"from={From}"; + } + if (!string.IsNullOrEmpty(Config)) + { + s.CommaAdd($"config-file={Config}"); + } + if (!string.IsNullOrEmpty(ContextOverride)) + { + s.CommaAdd($"context-override={ContextOverride}"); + } + if (!string.IsNullOrEmpty(NamespaceOverride)) + { + s.CommaAdd($"namespace-override={NamespaceOverride}"); + } + return s; + } + } + public static class Context + { + /// + /// Creates a new docker or kubernetes based context. + /// + /// The endpoint to create + /// Name of this new context. + /// Optional description of the context. + /// Optional a name of a existing context to clone from. + /// Optional a orchestrator to use. + /// The output of the creation of the context. + public static CommandResponse> CreateContext(this IContextEndpoint ep, + string name, + string description = null, + string from = null, + Orchestrator orchestrator = Orchestrator.None) + { + string opts = ""; + if (!string.IsNullOrWhiteSpace(description)) + { + opts = $" --description \"{description}\""; + } + if (!string.IsNullOrWhiteSpace(from)) + { + opts += $" --from {from}"; + } + if (orchestrator != Orchestrator.None) + { + opts += $" --default-stack-orchestrator {orchestrator.ToString().ToLower()}"; + } + + if (ep is KubernetesEndpoint) + { + opts += " " + ep.ToString(); + } + else if (ep is DockerEndpoint) + { + opts += " " + ep.ToString(); + } + else + { + throw new FluentDockerException($"The IContextEndpoint is neither docker or kubernetes"); + } + + return + new ProcessExecutor>( + "docker".ResolveBinary(), + $"context create {name}{opts}").Execute(); + } + } +} \ No newline at end of file diff --git a/Ductus.FluentDocker/Extensions/CommandExtensions.cs b/Ductus.FluentDocker/Extensions/CommandExtensions.cs index b38ed0be..da4b3280 100644 --- a/Ductus.FluentDocker/Extensions/CommandExtensions.cs +++ b/Ductus.FluentDocker/Extensions/CommandExtensions.cs @@ -191,6 +191,13 @@ public static IPAddress EmulatedNativeAdress(bool useCache = true) return _cachedDockerIpAddress; } + internal static string CommaAdd(this string s, string value) + { + if (string.IsNullOrEmpty(s)) { + return value; + } + return $"{s},{value}"; + } internal static string RenderBaseArgs(this Uri host, ICertificatePaths certificates = null) { var args = string.Empty; diff --git a/Ductus.FluentDocker/Model/Stacks/Orchestrator.cs b/Ductus.FluentDocker/Model/Stacks/Orchestrator.cs index 85a21cd4..0f921fe7 100644 --- a/Ductus.FluentDocker/Model/Stacks/Orchestrator.cs +++ b/Ductus.FluentDocker/Model/Stacks/Orchestrator.cs @@ -13,6 +13,10 @@ public enum Orchestrator /// /// Kubernetes /// - Kubernetes + Kubernetes, + /// + /// No orchestrator. + /// + None } }