From d817cae8452e580c6f947d370f9eeac40c23efe6 Mon Sep 17 00:00:00 2001 From: ydlr Date: Sat, 21 Oct 2017 13:38:23 -0500 Subject: [PATCH] add forwardPorts option to container backend --- nix/container.nix | 47 ++++++++++++++++++++++++++++++------ nixops/backends/container.py | 47 ++++++++++++++++++++++++++++++++---- 2 files changed, 81 insertions(+), 13 deletions(-) diff --git a/nix/container.nix b/nix/container.nix index b6de2cc42..bb228ef69 100644 --- a/nix/container.nix +++ b/nix/container.nix @@ -18,15 +18,46 @@ in options = { - deployment.container.host = mkOption { - type = types.either types.str machine; - apply = x: if builtins.isString x then x else "__machine-" + x._name; - default = "localhost"; - description = '' - The NixOS machine on which this container is to be instantiated. - ''; + deployment.container = { + + host = mkOption { + type = types.either types.str machine; + apply = x: if builtins.isString x then x else "__machine-" + x._name; + default = "localhost"; + description = '' + The NixOS machine on which this container is to be instantiated. + ''; + }; + + forwardPorts = mkOption { + type = types.listOf (types.submodule { + options = { + protocol = mkOption { + type = types.str; + default = "tcp"; + description = "The protocol specifier for port forwarding between host and container"; + }; + hostPort = mkOption { + type = types.int; + description = "Source port of the external interface of host"; + }; + containerPort = mkOption { + type = types.nullOr types.int; + default = null; + description = "Target port of container"; + }; + }; + }); + default = []; + example = [ { protocol = "tcp"; hostPort = 8080; containerPort = 80; } ]; + description = '' + List of forwarded ports from host to container. Each forwarded port + is specified by protocol, hostPort and containerPort. By default, + protocol is tcp and hostPort and containerPort are assumed to be + the same if containerPort is not explicitly given. + ''; + }; }; - }; config = mkIf (config.deployment.targetEnv == "container") { diff --git a/nixops/backends/container.py b/nixops/backends/container.py index 3c4e1e6de..2e8f20fb5 100644 --- a/nixops/backends/container.py +++ b/nixops/backends/container.py @@ -4,6 +4,7 @@ import nixops.util import nixops.ssh_util import subprocess +import re class ContainerDefinition(MachineDefinition): """Definition of a NixOS container.""" @@ -28,6 +29,7 @@ def get_type(cls): state = nixops.util.attr_property("state", MachineState.MISSING, int) # override private_ipv4 = nixops.util.attr_property("privateIpv4", None) host = nixops.util.attr_property("container.host", None) + forward_ports = nixops.util.attr_property( "container.forwardPorts", [], 'json' ) client_private_key = nixops.util.attr_property("container.clientPrivateKey", None) client_public_key = nixops.util.attr_property("container.clientPublicKey", None) public_host_key = nixops.util.attr_property("container.publicHostKey", None) @@ -98,7 +100,21 @@ def get_host_ssh_flags(self): def wait_for_ssh(self, check=False): return True - + + def port_str(self, port): + p_str = port['protocol'] + ":" + str(port['hostPort']) + ":" + if 'containerPort' in port: + p_str += str(port['containerPort']) + else: + p_str += str(port['hostPort']) + return p_str + + def port_flag(self, ports): + if not ports: + return '' + port_strs = map(self.port_str, ports) + return "--port " + ",".join(port_strs) + # Run a command in the container via ‘nixos-container run’. Since # this uses ‘nsenter’, we don't need SSH in the container. def run_command(self, command, **kwargs): @@ -143,12 +159,33 @@ def create(self, defn, check, allow_reboot, allow_recreate): self.log("creating container...") self.host = defn.host + self.forward_ports = defn.config["container"]["forwardPorts"] self.copy_closure_to(path) + self.vm_id = self.host_ssh.run_command( - "nixos-container create {0} --ensure-unique-name --system-path '{1}'" - .format(self.name[:7], path), capture_stdout=True).rstrip() + "nixos-container create {0} --ensure-unique-name --system-path '{1}' {2}" + .format(self.name[:7], path, self.port_flag(self.forward_ports)), capture_stdout=True).rstrip() self.state = self.STOPPED - + + if defn.config["container"]["forwardPorts"] or self.forward_ports: + + if defn.config["container"]["forwardPorts"]: + defn_port_str = self.port_flag(defn.config["container"]["forwardPorts"]).split('--port ')[1] + else: + defn_port_str = '' + + if self.forward_ports: + port_str = self.port_flag(self.forward_ports).split('--port ')[1] + else: + port_str = '' + + if defn_port_str != port_str: + container_conf = self.host_ssh.run_command("cat /etc/containers/{0}.conf".format(self.vm_id), capture_stdout=True).rstrip() + container_conf = re.sub(r'HOST_PORT=.*', 'HOST_PORT=' + defn_port_str, container_conf, re.M) + self.host_ssh.run_command("echo '{0}' > /etc/containers/{1}.conf".format(container_conf, self.vm_id)) + self.forward_ports = defn.config["container"]["forwardPorts"] + self.stop() + if self.state == self.STOPPED: self.host_ssh.run_command("nixos-container start {0}".format(self.vm_id)) self.state = self.UP @@ -160,7 +197,7 @@ def create(self, defn, check, allow_reboot, allow_recreate): if self.public_host_key is None: self.public_host_key = self.host_ssh.run_command("nixos-container show-host-key {0}".format(self.vm_id), capture_stdout=True).rstrip() nixops.known_hosts.add(self.get_ssh_name(), self.public_host_key) - + def destroy(self, wipe=False): if not self.vm_id: return True