diff --git a/nixops/backends/__init__.py b/nixops/backends/__init__.py index dc8bd31d6..010924e04 100644 --- a/nixops/backends/__init__.py +++ b/nixops/backends/__init__.py @@ -39,7 +39,7 @@ class MachineState(nixops.resources.ResourceState): ssh_pinged = nixops.util.attr_property("sshPinged", False, bool) ssh_port = nixops.util.attr_property("targetPort", 22, int) public_vpn_key = nixops.util.attr_property("publicVpnKey", None) - store_keys_on_machine = nixops.util.attr_property("storeKeysOnMachine", True, bool) + store_keys_on_machine = nixops.util.attr_property("storeKeysOnMachine", False, bool) keys = nixops.util.attr_property("keys", {}, 'json') owners = nixops.util.attr_property("owners", [], 'json') @@ -236,6 +236,10 @@ def get_ssh_flags(self, scp=False): else: return ["-p", str(self.ssh_port)] + def adapt_ssh_flag_for_use_with_scp(self, flags): + # For scp, the "-P" flag is used to specify the port. "-p" + # attempt to preserve file attributes. + return (map (lambda x: '-P' if x == '-p' else x, flags)) def get_ssh_password(self): return None @@ -353,7 +357,7 @@ def generate_vpn_key(self, check=False): def upload_file(self, source, target, recursive=False): master = self.ssh.get_master() - cmdline = ["scp"] + self.get_ssh_flags(True) + master.opts + cmdline = ["scp"] + self.get_ssh_flags(True) + self.adapt_ssh_flag_for_use_with_scp(master.opts) if recursive: cmdline += ['-r'] cmdline += [source, "root@" + self.get_ssh_name() + ":" + target] @@ -361,7 +365,7 @@ def upload_file(self, source, target, recursive=False): def download_file(self, source, target, recursive=False): master = self.ssh.get_master() - cmdline = ["scp"] + self.get_ssh_flags(True) + master.opts + cmdline = ["scp"] + self.get_ssh_flags(True) + self.adapt_ssh_flag_for_use_with_scp(master.opts) if recursive: cmdline += ['-r'] cmdline += ["root@" + self.get_ssh_name() + ":" + source, target] diff --git a/nixops/backends/container.py b/nixops/backends/container.py index 62431fbad..600618029 100644 --- a/nixops/backends/container.py +++ b/nixops/backends/container.py @@ -4,6 +4,9 @@ import nixops.util import nixops.ssh_util import subprocess +import time +import threading +import sys class ContainerDefinition(MachineDefinition): """Definition of a NixOS container.""" @@ -63,9 +66,9 @@ def get_ssh_flags(self, *args, **kwargs): flags = super(ContainerState, self).get_ssh_flags(*args, **kwargs) flags += ["-i", self.get_ssh_private_key_file()] if self.host == "localhost": - flags.extend(MachineState.get_ssh_flags(self)) + flags.extend(MachineState.get_ssh_flags(self, *args, **kwargs)) else: - cmd = "ssh -x -a root@{0} {1} nc -c {2} {3}".format(self.get_host_ssh(), " ".join(self.get_host_ssh_flags()), self.private_ipv4, self.ssh_port) + cmd = "ssh -x -a root@{0} {1} nc -c {2} {3}".format(self.get_host_ssh(), " ".join(self.get_host_ssh_flags(*args, **kwargs)), self.private_ipv4, self.ssh_port) flags.extend(["-o", "ProxyCommand=" + cmd]) return flags @@ -87,12 +90,12 @@ def get_host_ssh(self): else: return self.host - def get_host_ssh_flags(self): + def get_host_ssh_flags(self, *args, **kwargs): if self.host.startswith("__machine-"): m = self.depl.get_machine(self.host[10:]) if not m.started: raise Exception("host machine ‘{0}’ of container ‘{1}’ is not up".format(m.name, self.name)) - return m.get_ssh_flags() + return m.get_ssh_flags(*args, **kwargs) else: return [] @@ -120,6 +123,8 @@ def create_after(self, resources, defn): def create(self, defn, check, allow_reboot, allow_recreate): assert isinstance(defn, ContainerDefinition) + self.set_common_state(defn) + if not self.client_private_key: (self.client_private_key, self.client_public_key) = nixops.util.create_key_pair() @@ -179,11 +184,60 @@ def stop(self): self.host_ssh.run_command("nixos-container stop {0}".format(self.vm_id)) self.state = self.STOPPED + + def get_container_status(self): + try: + status = self.host_ssh.run_command("nixos-container status {0}".format(self.vm_id), capture_stdout=True).rstrip() + except nixops.ssh_util.SSHConnectionFailed: + status = "unknown_ssh_connection_failed" + except nixops.ssh_util.SSHCommandFailed: + status = "unknown_ssh_command_failed" + return status + + def wait_container_available(self): + # For some reason, it seems to work best if I wait before sending the command to + # check the status of the container instead of the reverse. + time.sleep(1) + while True: + status = self.get_container_status() + self.log("waiting for container... Current status: {0}".format(status)) + if status in {"up", "down"}: + break + time.sleep(1) + + def send_key_task(self): + # Do not attempt anything when there are no keys so as to + # avoid breaking setups with no keys to send. + if not self.get_keys().items(): + return + + # For some reason, it seems that when there are keys listed in the + # container deployment, it becomes impossible to use the start command + # after a stop. The instance simply hangs and it is impossible to even + # ping it. Once we solve this mystery, the following code should become + # useful. + + # When performing the command `nixos-container status` on the container, + # I get: the following: + # `Failed to start container@webserv-18.service: Interactive authentication required.` + # which may give us some hint as to the source of the problem. + + self.wait_container_available() + self.log("sending keys...") + self.send_keys() + def start(self): if not self.vm_id: return True self.log("starting container...") + # As the nixos-container is blocking, we need to lauch a + # thread so as to send the keys. Otherwise, the deployment + # would block on a service that depends on those keys and as such + # we would never get a chance to send them. + send_key_thread = threading.Thread(target=self.send_key_task) + send_key_thread.start() self.host_ssh.run_command("nixos-container start {0}".format(self.vm_id)) self.state = self.STARTING + send_key_thread.join() def _check(self, res): if not self.vm_id: