Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix container's deploy when used with keys. #300

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions nixops/backends/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -353,15 +357,15 @@ 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]
return self._logged_exec(cmdline)

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]
Expand Down
62 changes: 58 additions & 4 deletions nixops/backends/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down Expand Up @@ -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

Expand All @@ -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 []

Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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:
Expand Down