From 368aae9274c9b446ca94a8ee8f6cc229aa776d01 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Jul 17 2017 10:21:30 +0000 Subject: inventory: Add an inventory file that launches a docker container This is one that pulls a docker container from a registry, and launches it. We have to install python2 and friends for Ansible to work inside the container. Uses ansible_connection=docker --- diff --git a/inventory/standard-inventory-docker b/inventory/standard-inventory-docker new file mode 100755 index 0000000..8f3b39d --- /dev/null +++ b/inventory/standard-inventory-docker @@ -0,0 +1,138 @@ +#!/usr/bin/env python + +import argparse +import json +import os +import shutil +import shlex +import signal +import socket +import subprocess +import sys +import tempfile +import time +import traceback + +def main(argv): + parser = argparse.ArgumentParser(description="Inventory for a container image in a registry") + parser.add_argument("--list", action="store_true", help="Verbose output") + parser.add_argument('--host', help="Get host variables") + parser.add_argument("subjects", nargs="*", default=shlex.split(os.environ.get("TEST_SUBJECTS", ""))) + opts = parser.parse_args() + + try: + if opts.host: + name, data = host(opts.host) + else: + data = list(opts.subjects) + sys.stdout.write(json.dumps(data, indent=4, separators=(',', ': '))) + except RuntimeError, ex: + sys.stderr.write("{0}: {1}\n".format(os.path.basename(sys.argv[0]), str(ex))) + return 1 + + return 0 + +def list(subjects): + hosts = [ ] + variables = { } + for subject in subjects: + if subject.startswith("docker:"): + image = subject[7:] + name, vars = host(image) + if vars: + hosts.append(name) + variables[name] = vars + return { "localhost": { "hosts": hosts, "vars": { } }, "subjects": { "hosts": hosts, "vars": { } }, "_meta": { "hostvars": variables } } + +def host(image): + null = open(os.devnull, 'w') + + try: + tty = os.open("/dev/tty", os.O_WRONLY) + os.dup2(tty, 2) + except OSError: + tty = None + pass + + directory = tempfile.mkdtemp(prefix="inventory-docker") + cidfile = os.path.join(directory, "cid") + + sys.stderr.write("Launching Docker container for {0}\n".format(image)) + + # And launch the actual container + cmd = [ + "/usr/bin/docker", "run", "--rm", "--detach", "--cidfile={0}".format(cidfile), + "--entrypoint=/bin/sh", image, "-c", "sleep 1000000" + ] + + try: + subprocess.check_call(cmd, stdout=sys.stderr.fileno()) + except subprocess.CalledProcessError, ex: + raise RuntimeError("Could not start container image: {0}".format(image)) + + # Read out the container environment variable + for x in range(1, 90): + if os.path.exists(cidfile): + break + time.sleep(1) + else: + raise RuntimeError("Could not find container file for launched container") + + with open(cidfile, "r") as f: + name = f.read().strip() + + # Now install the necessary stuff in the container :S + install = [ + "/usr/bin/docker", "exec", "--user=root", name, "/usr/bin/yum", "-y", "install", + "python2", "python2-dnf", "libselinux-python" + ] + try: + subprocess.check_call(install, stdout=sys.stderr.fileno()) + except subprocess.CalledProcessError, ex: + raise RuntimeError("Could not install Ansible dependencies in launched container") + + # The variables + variables = { + "ansible_connection": "docker", + } + + # Process of our parent + ppid = os.getppid() + + child = os.fork() + if child: + return name, variables + + # Daemonize and watch the processes + os.chdir("/") + os.setsid() + os.umask(0) + + if tty is None: + tty = null.fileno() + + # Duplicate standard input to standard output and standard error. + os.dup2(null.fileno(), 0) + os.dup2(tty, 1) + os.dup2(tty, 2) + + # Now wait for the parent process to go away, then kill the VM + while True: + time.sleep(3) + + try: + os.kill(ppid, 0) + except OSError: + break # Either of the processes no longer exist + + # Kill the container + try: + subprocess.call(["/usr/bin/docker", "kill", name ]) + except OSError: + pass + + shutil.rmtree(directory) + sys.exit(0) + +if __name__ == '__main__': + sys.exit(main(sys.argv))