xref: /qemu/tests/docker/docker.py (revision 43c898b75a3bb92b113f9a366a06cac013d055a8)
14485b04bSFam Zheng#!/usr/bin/env python2
24485b04bSFam Zheng#
34485b04bSFam Zheng# Docker controlling module
44485b04bSFam Zheng#
54485b04bSFam Zheng# Copyright (c) 2016 Red Hat Inc.
64485b04bSFam Zheng#
74485b04bSFam Zheng# Authors:
84485b04bSFam Zheng#  Fam Zheng <famz@redhat.com>
94485b04bSFam Zheng#
104485b04bSFam Zheng# This work is licensed under the terms of the GNU GPL, version 2
114485b04bSFam Zheng# or (at your option) any later version. See the COPYING file in
124485b04bSFam Zheng# the top-level directory.
134485b04bSFam Zheng
14f03868bdSEduardo Habkostfrom __future__ import print_function
154485b04bSFam Zhengimport os
164485b04bSFam Zhengimport sys
174485b04bSFam Zhengimport subprocess
184485b04bSFam Zhengimport json
194485b04bSFam Zhengimport hashlib
204485b04bSFam Zhengimport atexit
214485b04bSFam Zhengimport uuid
22ae68fdabSEduardo Habkostimport argparse
234485b04bSFam Zhengimport tempfile
24504ca3c2SAlex Bennéeimport re
2597cba1a1SFam Zhengimport signal
266e733da6SAlex Bennéefrom tarfile import TarFile, TarInfo
277a5d936bSAlex Bennéetry:
286e733da6SAlex Bennée    from StringIO import StringIO
297a5d936bSAlex Bennéeexcept ImportError:
307a5d936bSAlex Bennée    from io import StringIO
31a9f8d038SAlex Bennéefrom shutil import copy, rmtree
32414a8ce5SAlex Bennéefrom pwd import getpwuid
337b882245SAlex Bennéefrom datetime import datetime,timedelta
344485b04bSFam Zheng
35c9772570SSascha Silbe
3606cc3551SPhilippe Mathieu-DaudéFILTERED_ENV_NAMES = ['ftp_proxy', 'http_proxy', 'https_proxy']
3706cc3551SPhilippe Mathieu-Daudé
3806cc3551SPhilippe Mathieu-Daudé
39c9772570SSascha SilbeDEVNULL = open(os.devnull, 'wb')
40c9772570SSascha Silbe
41c9772570SSascha Silbe
424485b04bSFam Zhengdef _text_checksum(text):
434485b04bSFam Zheng    """Calculate a digest string unique to the text content"""
444485b04bSFam Zheng    return hashlib.sha1(text).hexdigest()
454485b04bSFam Zheng
46438d1168SPhilippe Mathieu-Daudédef _file_checksum(filename):
47438d1168SPhilippe Mathieu-Daudé    return _text_checksum(open(filename, 'rb').read())
48438d1168SPhilippe Mathieu-Daudé
494485b04bSFam Zhengdef _guess_docker_command():
504485b04bSFam Zheng    """ Guess a working docker command or raise exception if not found"""
514485b04bSFam Zheng    commands = [["docker"], ["sudo", "-n", "docker"]]
524485b04bSFam Zheng    for cmd in commands:
530679f98bSEduardo Habkost        try:
5483405c45SAlex Bennée            # docker version will return the client details in stdout
5583405c45SAlex Bennée            # but still report a status of 1 if it can't contact the daemon
5683405c45SAlex Bennée            if subprocess.call(cmd + ["version"],
57c9772570SSascha Silbe                               stdout=DEVNULL, stderr=DEVNULL) == 0:
584485b04bSFam Zheng                return cmd
590679f98bSEduardo Habkost        except OSError:
600679f98bSEduardo Habkost            pass
614485b04bSFam Zheng    commands_txt = "\n".join(["  " + " ".join(x) for x in commands])
624485b04bSFam Zheng    raise Exception("Cannot find working docker command. Tried:\n%s" % \
634485b04bSFam Zheng                    commands_txt)
644485b04bSFam Zheng
652499ee9fSPhilippe Mathieu-Daudédef _copy_with_mkdir(src, root_dir, sub_path='.'):
66504ca3c2SAlex Bennée    """Copy src into root_dir, creating sub_path as needed."""
67504ca3c2SAlex Bennée    dest_dir = os.path.normpath("%s/%s" % (root_dir, sub_path))
68504ca3c2SAlex Bennée    try:
69504ca3c2SAlex Bennée        os.makedirs(dest_dir)
70504ca3c2SAlex Bennée    except OSError:
71504ca3c2SAlex Bennée        # we can safely ignore already created directories
72504ca3c2SAlex Bennée        pass
73504ca3c2SAlex Bennée
74504ca3c2SAlex Bennée    dest_file = "%s/%s" % (dest_dir, os.path.basename(src))
75504ca3c2SAlex Bennée    copy(src, dest_file)
76504ca3c2SAlex Bennée
77504ca3c2SAlex Bennée
78504ca3c2SAlex Bennéedef _get_so_libs(executable):
79504ca3c2SAlex Bennée    """Return a list of libraries associated with an executable.
80504ca3c2SAlex Bennée
81504ca3c2SAlex Bennée    The paths may be symbolic links which would need to be resolved to
82504ca3c2SAlex Bennée    ensure theright data is copied."""
83504ca3c2SAlex Bennée
84504ca3c2SAlex Bennée    libs = []
85504ca3c2SAlex Bennée    ldd_re = re.compile(r"(/.*/)(\S*)")
86504ca3c2SAlex Bennée    try:
87504ca3c2SAlex Bennée        ldd_output = subprocess.check_output(["ldd", executable])
88504ca3c2SAlex Bennée        for line in ldd_output.split("\n"):
89504ca3c2SAlex Bennée            search = ldd_re.search(line)
90504ca3c2SAlex Bennée            if search and len(search.groups()) == 2:
91504ca3c2SAlex Bennée                so_path = search.groups()[0]
92504ca3c2SAlex Bennée                so_lib = search.groups()[1]
93504ca3c2SAlex Bennée                libs.append("%s/%s" % (so_path, so_lib))
94504ca3c2SAlex Bennée    except subprocess.CalledProcessError:
95f03868bdSEduardo Habkost        print("%s had no associated libraries (static build?)" % (executable))
96504ca3c2SAlex Bennée
97504ca3c2SAlex Bennée    return libs
98504ca3c2SAlex Bennée
99504ca3c2SAlex Bennéedef _copy_binary_with_libs(src, dest_dir):
100e50a6121SStefan Weil    """Copy a binary executable and all its dependent libraries.
101504ca3c2SAlex Bennée
102504ca3c2SAlex Bennée    This does rely on the host file-system being fairly multi-arch
103504ca3c2SAlex Bennée    aware so the file don't clash with the guests layout."""
104504ca3c2SAlex Bennée
105504ca3c2SAlex Bennée    _copy_with_mkdir(src, dest_dir, "/usr/bin")
106504ca3c2SAlex Bennée
107504ca3c2SAlex Bennée    libs = _get_so_libs(src)
108504ca3c2SAlex Bennée    if libs:
109504ca3c2SAlex Bennée        for l in libs:
110504ca3c2SAlex Bennée            so_path = os.path.dirname(l)
111504ca3c2SAlex Bennée            _copy_with_mkdir(l , dest_dir, so_path)
112504ca3c2SAlex Bennée
11315352decSAlex Bennée
11415352decSAlex Bennéedef _check_binfmt_misc(executable):
11515352decSAlex Bennée    """Check binfmt_misc has entry for executable in the right place.
11615352decSAlex Bennée
11715352decSAlex Bennée    The details of setting up binfmt_misc are outside the scope of
11815352decSAlex Bennée    this script but we should at least fail early with a useful
11915352decSAlex Bennée    message if it won't work."""
12015352decSAlex Bennée
12115352decSAlex Bennée    binary = os.path.basename(executable)
12215352decSAlex Bennée    binfmt_entry = "/proc/sys/fs/binfmt_misc/%s" % (binary)
12315352decSAlex Bennée
12415352decSAlex Bennée    if not os.path.exists(binfmt_entry):
12515352decSAlex Bennée        print ("No binfmt_misc entry for %s" % (binary))
1267e81d198SAlex Bennée        return None
12715352decSAlex Bennée
12815352decSAlex Bennée    with open(binfmt_entry) as x: entry = x.read()
12915352decSAlex Bennée
130*43c898b7SAlex Bennée    if re.search("flags:.*F.*\n", entry):
131*43c898b7SAlex Bennée        print("binfmt_misc for %s uses persistent(F) mapping to host binary\n" %
132*43c898b7SAlex Bennée              (binary))
133*43c898b7SAlex Bennée        return None
134*43c898b7SAlex Bennée
1357e81d198SAlex Bennée    m = re.search("interpreter (\S+)\n", entry)
1367e81d198SAlex Bennée    interp = m.group(1)
1377e81d198SAlex Bennée    if interp and interp != executable:
1387e81d198SAlex Bennée        print("binfmt_misc for %s does not point to %s, using %s" %
1397e81d198SAlex Bennée              (binary, executable, interp))
14015352decSAlex Bennée
1417e81d198SAlex Bennée    return interp
14215352decSAlex Bennée
143c1958e9dSFam Zhengdef _read_qemu_dockerfile(img_name):
144547cb45eSAlex Bennée    # special case for Debian linux-user images
145547cb45eSAlex Bennée    if img_name.startswith("debian") and img_name.endswith("user"):
146547cb45eSAlex Bennée        img_name = "debian-bootstrap"
147547cb45eSAlex Bennée
148c1958e9dSFam Zheng    df = os.path.join(os.path.dirname(__file__), "dockerfiles",
149c1958e9dSFam Zheng                      img_name + ".docker")
150c1958e9dSFam Zheng    return open(df, "r").read()
151c1958e9dSFam Zheng
152c1958e9dSFam Zhengdef _dockerfile_preprocess(df):
153c1958e9dSFam Zheng    out = ""
154c1958e9dSFam Zheng    for l in df.splitlines():
155c1958e9dSFam Zheng        if len(l.strip()) == 0 or l.startswith("#"):
156c1958e9dSFam Zheng            continue
157c1958e9dSFam Zheng        from_pref = "FROM qemu:"
158c1958e9dSFam Zheng        if l.startswith(from_pref):
159c1958e9dSFam Zheng            # TODO: Alternatively we could replace this line with "FROM $ID"
160c1958e9dSFam Zheng            # where $ID is the image's hex id obtained with
161c1958e9dSFam Zheng            #    $ docker images $IMAGE --format="{{.Id}}"
162c1958e9dSFam Zheng            # but unfortunately that's not supported by RHEL 7.
163c1958e9dSFam Zheng            inlining = _read_qemu_dockerfile(l[len(from_pref):])
164c1958e9dSFam Zheng            out += _dockerfile_preprocess(inlining)
165c1958e9dSFam Zheng            continue
166c1958e9dSFam Zheng        out += l + "\n"
167c1958e9dSFam Zheng    return out
168c1958e9dSFam Zheng
1694485b04bSFam Zhengclass Docker(object):
1704485b04bSFam Zheng    """ Running Docker commands """
1714485b04bSFam Zheng    def __init__(self):
1724485b04bSFam Zheng        self._command = _guess_docker_command()
1734485b04bSFam Zheng        self._instances = []
1744485b04bSFam Zheng        atexit.register(self._kill_instances)
17597cba1a1SFam Zheng        signal.signal(signal.SIGTERM, self._kill_instances)
17697cba1a1SFam Zheng        signal.signal(signal.SIGHUP, self._kill_instances)
1774485b04bSFam Zheng
17858bf7b6dSFam Zheng    def _do(self, cmd, quiet=True, **kwargs):
1794485b04bSFam Zheng        if quiet:
180c9772570SSascha Silbe            kwargs["stdout"] = DEVNULL
1814485b04bSFam Zheng        return subprocess.call(self._command + cmd, **kwargs)
1824485b04bSFam Zheng
1830b95ff72SFam Zheng    def _do_check(self, cmd, quiet=True, **kwargs):
1840b95ff72SFam Zheng        if quiet:
1850b95ff72SFam Zheng            kwargs["stdout"] = DEVNULL
1860b95ff72SFam Zheng        return subprocess.check_call(self._command + cmd, **kwargs)
1870b95ff72SFam Zheng
1884485b04bSFam Zheng    def _do_kill_instances(self, only_known, only_active=True):
1894485b04bSFam Zheng        cmd = ["ps", "-q"]
1904485b04bSFam Zheng        if not only_active:
1914485b04bSFam Zheng            cmd.append("-a")
1924485b04bSFam Zheng        for i in self._output(cmd).split():
1934485b04bSFam Zheng            resp = self._output(["inspect", i])
1944485b04bSFam Zheng            labels = json.loads(resp)[0]["Config"]["Labels"]
1954485b04bSFam Zheng            active = json.loads(resp)[0]["State"]["Running"]
1964485b04bSFam Zheng            if not labels:
1974485b04bSFam Zheng                continue
1984485b04bSFam Zheng            instance_uuid = labels.get("com.qemu.instance.uuid", None)
1994485b04bSFam Zheng            if not instance_uuid:
2004485b04bSFam Zheng                continue
2014485b04bSFam Zheng            if only_known and instance_uuid not in self._instances:
2024485b04bSFam Zheng                continue
203f03868bdSEduardo Habkost            print("Terminating", i)
2044485b04bSFam Zheng            if active:
2054485b04bSFam Zheng                self._do(["kill", i])
2064485b04bSFam Zheng            self._do(["rm", i])
2074485b04bSFam Zheng
2084485b04bSFam Zheng    def clean(self):
2094485b04bSFam Zheng        self._do_kill_instances(False, False)
2104485b04bSFam Zheng        return 0
2114485b04bSFam Zheng
21297cba1a1SFam Zheng    def _kill_instances(self, *args, **kwargs):
2134485b04bSFam Zheng        return self._do_kill_instances(True)
2144485b04bSFam Zheng
2154485b04bSFam Zheng    def _output(self, cmd, **kwargs):
2164485b04bSFam Zheng        return subprocess.check_output(self._command + cmd,
2174485b04bSFam Zheng                                       stderr=subprocess.STDOUT,
2184485b04bSFam Zheng                                       **kwargs)
2194485b04bSFam Zheng
220f97da1f7SAlex Bennée    def inspect_tag(self, tag):
221f97da1f7SAlex Bennée        try:
222f97da1f7SAlex Bennée            return self._output(["inspect", tag])
223f97da1f7SAlex Bennée        except subprocess.CalledProcessError:
224f97da1f7SAlex Bennée            return None
225f97da1f7SAlex Bennée
2267b882245SAlex Bennée    def get_image_creation_time(self, info):
2277b882245SAlex Bennée        return json.loads(info)[0]["Created"]
2287b882245SAlex Bennée
2294485b04bSFam Zheng    def get_image_dockerfile_checksum(self, tag):
230f97da1f7SAlex Bennée        resp = self.inspect_tag(tag)
2314485b04bSFam Zheng        labels = json.loads(resp)[0]["Config"].get("Labels", {})
2324485b04bSFam Zheng        return labels.get("com.qemu.dockerfile-checksum", "")
2334485b04bSFam Zheng
234414a8ce5SAlex Bennée    def build_image(self, tag, docker_dir, dockerfile,
235438d1168SPhilippe Mathieu-Daudé                    quiet=True, user=False, argv=None, extra_files_cksum=[]):
2364485b04bSFam Zheng        if argv == None:
2374485b04bSFam Zheng            argv = []
2384485b04bSFam Zheng
239a9f8d038SAlex Bennée        tmp_df = tempfile.NamedTemporaryFile(dir=docker_dir, suffix=".docker")
2404485b04bSFam Zheng        tmp_df.write(dockerfile)
2414485b04bSFam Zheng
242414a8ce5SAlex Bennée        if user:
243414a8ce5SAlex Bennée            uid = os.getuid()
244414a8ce5SAlex Bennée            uname = getpwuid(uid).pw_name
245414a8ce5SAlex Bennée            tmp_df.write("\n")
246414a8ce5SAlex Bennée            tmp_df.write("RUN id %s 2>/dev/null || useradd -u %d -U %s" %
247414a8ce5SAlex Bennée                         (uname, uid, uname))
248414a8ce5SAlex Bennée
2494485b04bSFam Zheng        tmp_df.write("\n")
2504485b04bSFam Zheng        tmp_df.write("LABEL com.qemu.dockerfile-checksum=%s" %
251f9172822SAlex Bennée                     _text_checksum(_dockerfile_preprocess(dockerfile)))
252f9172822SAlex Bennée        for f, c in extra_files_cksum:
253f9172822SAlex Bennée            tmp_df.write("LABEL com.qemu.%s-checksum=%s" % (f, c))
254f9172822SAlex Bennée
2554485b04bSFam Zheng        tmp_df.flush()
256a9f8d038SAlex Bennée
2570b95ff72SFam Zheng        self._do_check(["build", "-t", tag, "-f", tmp_df.name] + argv + \
258a9f8d038SAlex Bennée                       [docker_dir],
2594485b04bSFam Zheng                       quiet=quiet)
2604485b04bSFam Zheng
2616e733da6SAlex Bennée    def update_image(self, tag, tarball, quiet=True):
2626e733da6SAlex Bennée        "Update a tagged image using "
2636e733da6SAlex Bennée
2640b95ff72SFam Zheng        self._do_check(["build", "-t", tag, "-"], quiet=quiet, stdin=tarball)
2656e733da6SAlex Bennée
2664485b04bSFam Zheng    def image_matches_dockerfile(self, tag, dockerfile):
2674485b04bSFam Zheng        try:
2684485b04bSFam Zheng            checksum = self.get_image_dockerfile_checksum(tag)
2694485b04bSFam Zheng        except Exception:
2704485b04bSFam Zheng            return False
271c1958e9dSFam Zheng        return checksum == _text_checksum(_dockerfile_preprocess(dockerfile))
2724485b04bSFam Zheng
2734485b04bSFam Zheng    def run(self, cmd, keep, quiet):
2744485b04bSFam Zheng        label = uuid.uuid1().hex
2754485b04bSFam Zheng        if not keep:
2764485b04bSFam Zheng            self._instances.append(label)
2770b95ff72SFam Zheng        ret = self._do_check(["run", "--label",
2784485b04bSFam Zheng                             "com.qemu.instance.uuid=" + label] + cmd,
2794485b04bSFam Zheng                             quiet=quiet)
2804485b04bSFam Zheng        if not keep:
2814485b04bSFam Zheng            self._instances.remove(label)
2824485b04bSFam Zheng        return ret
2834485b04bSFam Zheng
2844b08af60SFam Zheng    def command(self, cmd, argv, quiet):
2854b08af60SFam Zheng        return self._do([cmd] + argv, quiet=quiet)
2864b08af60SFam Zheng
2874485b04bSFam Zhengclass SubCommand(object):
2884485b04bSFam Zheng    """A SubCommand template base class"""
2894485b04bSFam Zheng    name = None # Subcommand name
2904485b04bSFam Zheng    def shared_args(self, parser):
2914485b04bSFam Zheng        parser.add_argument("--quiet", action="store_true",
292e50a6121SStefan Weil                            help="Run quietly unless an error occurred")
2934485b04bSFam Zheng
2944485b04bSFam Zheng    def args(self, parser):
2954485b04bSFam Zheng        """Setup argument parser"""
2964485b04bSFam Zheng        pass
2974485b04bSFam Zheng    def run(self, args, argv):
2984485b04bSFam Zheng        """Run command.
2994485b04bSFam Zheng        args: parsed argument by argument parser.
3004485b04bSFam Zheng        argv: remaining arguments from sys.argv.
3014485b04bSFam Zheng        """
3024485b04bSFam Zheng        pass
3034485b04bSFam Zheng
3044485b04bSFam Zhengclass RunCommand(SubCommand):
3054485b04bSFam Zheng    """Invoke docker run and take care of cleaning up"""
3064485b04bSFam Zheng    name = "run"
3074485b04bSFam Zheng    def args(self, parser):
3084485b04bSFam Zheng        parser.add_argument("--keep", action="store_true",
3094485b04bSFam Zheng                            help="Don't remove image when command completes")
3104485b04bSFam Zheng    def run(self, args, argv):
3114485b04bSFam Zheng        return Docker().run(argv, args.keep, quiet=args.quiet)
3124485b04bSFam Zheng
3134485b04bSFam Zhengclass BuildCommand(SubCommand):
3144485b04bSFam Zheng    """ Build docker image out of a dockerfile. Arguments: <tag> <dockerfile>"""
3154485b04bSFam Zheng    name = "build"
3164485b04bSFam Zheng    def args(self, parser):
317504ca3c2SAlex Bennée        parser.add_argument("--include-executable", "-e",
318504ca3c2SAlex Bennée                            help="""Specify a binary that will be copied to the
319504ca3c2SAlex Bennée                            container together with all its dependent
320504ca3c2SAlex Bennée                            libraries""")
3214c84f662SPhilippe Mathieu-Daudé        parser.add_argument("--extra-files", "-f", nargs='*',
3224c84f662SPhilippe Mathieu-Daudé                            help="""Specify files that will be copied in the
3234c84f662SPhilippe Mathieu-Daudé                            Docker image, fulfilling the ADD directive from the
3244c84f662SPhilippe Mathieu-Daudé                            Dockerfile""")
325414a8ce5SAlex Bennée        parser.add_argument("--add-current-user", "-u", dest="user",
326414a8ce5SAlex Bennée                            action="store_true",
327414a8ce5SAlex Bennée                            help="Add the current user to image's passwd")
3284485b04bSFam Zheng        parser.add_argument("tag",
3294485b04bSFam Zheng                            help="Image Tag")
3304485b04bSFam Zheng        parser.add_argument("dockerfile",
3314485b04bSFam Zheng                            help="Dockerfile name")
3324485b04bSFam Zheng
3334485b04bSFam Zheng    def run(self, args, argv):
3344485b04bSFam Zheng        dockerfile = open(args.dockerfile, "rb").read()
3354485b04bSFam Zheng        tag = args.tag
3364485b04bSFam Zheng
3374485b04bSFam Zheng        dkr = Docker()
3386fe3ae3fSAlex Bennée        if "--no-cache" not in argv and \
3396fe3ae3fSAlex Bennée           dkr.image_matches_dockerfile(tag, dockerfile):
3404485b04bSFam Zheng            if not args.quiet:
341f03868bdSEduardo Habkost                print("Image is up to date.")
342a9f8d038SAlex Bennée        else:
343a9f8d038SAlex Bennée            # Create a docker context directory for the build
344a9f8d038SAlex Bennée            docker_dir = tempfile.mkdtemp(prefix="docker_build")
3454485b04bSFam Zheng
34615352decSAlex Bennée            # Validate binfmt_misc will work
34715352decSAlex Bennée            if args.include_executable:
34815352decSAlex Bennée                if not _check_binfmt_misc(args.include_executable):
34915352decSAlex Bennée                    return 1
35015352decSAlex Bennée
351920776eaSAlex Bennée            # Is there a .pre file to run in the build context?
352920776eaSAlex Bennée            docker_pre = os.path.splitext(args.dockerfile)[0]+".pre"
353920776eaSAlex Bennée            if os.path.exists(docker_pre):
354f8042deaSSascha Silbe                stdout = DEVNULL if args.quiet else None
355920776eaSAlex Bennée                rc = subprocess.call(os.path.realpath(docker_pre),
356f8042deaSSascha Silbe                                     cwd=docker_dir, stdout=stdout)
357920776eaSAlex Bennée                if rc == 3:
358f03868bdSEduardo Habkost                    print("Skip")
359920776eaSAlex Bennée                    return 0
360920776eaSAlex Bennée                elif rc != 0:
361f03868bdSEduardo Habkost                    print("%s exited with code %d" % (docker_pre, rc))
362920776eaSAlex Bennée                    return 1
363920776eaSAlex Bennée
3644c84f662SPhilippe Mathieu-Daudé            # Copy any extra files into the Docker context. These can be
3654c84f662SPhilippe Mathieu-Daudé            # included by the use of the ADD directive in the Dockerfile.
366438d1168SPhilippe Mathieu-Daudé            cksum = []
367504ca3c2SAlex Bennée            if args.include_executable:
368438d1168SPhilippe Mathieu-Daudé                # FIXME: there is no checksum of this executable and the linked
369438d1168SPhilippe Mathieu-Daudé                # libraries, once the image built any change of this executable
370438d1168SPhilippe Mathieu-Daudé                # or any library won't trigger another build.
3714c84f662SPhilippe Mathieu-Daudé                _copy_binary_with_libs(args.include_executable, docker_dir)
3724c84f662SPhilippe Mathieu-Daudé            for filename in args.extra_files or []:
3734c84f662SPhilippe Mathieu-Daudé                _copy_with_mkdir(filename, docker_dir)
374f9172822SAlex Bennée                cksum += [(filename, _file_checksum(filename))]
375504ca3c2SAlex Bennée
37606cc3551SPhilippe Mathieu-Daudé            argv += ["--build-arg=" + k.lower() + "=" + v
37706cc3551SPhilippe Mathieu-Daudé                        for k, v in os.environ.iteritems()
37806cc3551SPhilippe Mathieu-Daudé                        if k.lower() in FILTERED_ENV_NAMES]
379a9f8d038SAlex Bennée            dkr.build_image(tag, docker_dir, dockerfile,
380438d1168SPhilippe Mathieu-Daudé                            quiet=args.quiet, user=args.user, argv=argv,
381438d1168SPhilippe Mathieu-Daudé                            extra_files_cksum=cksum)
382a9f8d038SAlex Bennée
383a9f8d038SAlex Bennée            rmtree(docker_dir)
384a9f8d038SAlex Bennée
3854485b04bSFam Zheng        return 0
3864485b04bSFam Zheng
3876e733da6SAlex Bennéeclass UpdateCommand(SubCommand):
3886e733da6SAlex Bennée    """ Update a docker image with new executables. Arguments: <tag> <executable>"""
3896e733da6SAlex Bennée    name = "update"
3906e733da6SAlex Bennée    def args(self, parser):
3916e733da6SAlex Bennée        parser.add_argument("tag",
3926e733da6SAlex Bennée                            help="Image Tag")
3936e733da6SAlex Bennée        parser.add_argument("executable",
3946e733da6SAlex Bennée                            help="Executable to copy")
3956e733da6SAlex Bennée
3966e733da6SAlex Bennée    def run(self, args, argv):
3976e733da6SAlex Bennée        # Create a temporary tarball with our whole build context and
3986e733da6SAlex Bennée        # dockerfile for the update
3996e733da6SAlex Bennée        tmp = tempfile.NamedTemporaryFile(suffix="dckr.tar.gz")
4006e733da6SAlex Bennée        tmp_tar = TarFile(fileobj=tmp, mode='w')
4016e733da6SAlex Bennée
4027e81d198SAlex Bennée        # Add the executable to the tarball, using the current
4037e81d198SAlex Bennée        # configured binfmt_misc path.
4047e81d198SAlex Bennée        ff = _check_binfmt_misc(args.executable)
4057e81d198SAlex Bennée        if not ff:
4066e733da6SAlex Bennée            bn = os.path.basename(args.executable)
4076e733da6SAlex Bennée            ff = "/usr/bin/%s" % bn
4087e81d198SAlex Bennée            print ("No binfmt_misc configured: copied to %s" % (ff))
4097e81d198SAlex Bennée
4106e733da6SAlex Bennée        tmp_tar.add(args.executable, arcname=ff)
4116e733da6SAlex Bennée
4126e733da6SAlex Bennée        # Add any associated libraries
4136e733da6SAlex Bennée        libs = _get_so_libs(args.executable)
4146e733da6SAlex Bennée        if libs:
4156e733da6SAlex Bennée            for l in libs:
4166e733da6SAlex Bennée                tmp_tar.add(os.path.realpath(l), arcname=l)
4176e733da6SAlex Bennée
4186e733da6SAlex Bennée        # Create a Docker buildfile
4196e733da6SAlex Bennée        df = StringIO()
4206e733da6SAlex Bennée        df.write("FROM %s\n" % args.tag)
4216e733da6SAlex Bennée        df.write("ADD . /\n")
4226e733da6SAlex Bennée        df.seek(0)
4236e733da6SAlex Bennée
4246e733da6SAlex Bennée        df_tar = TarInfo(name="Dockerfile")
4256e733da6SAlex Bennée        df_tar.size = len(df.buf)
4266e733da6SAlex Bennée        tmp_tar.addfile(df_tar, fileobj=df)
4276e733da6SAlex Bennée
4286e733da6SAlex Bennée        tmp_tar.close()
4296e733da6SAlex Bennée
4306e733da6SAlex Bennée        # reset the file pointers
4316e733da6SAlex Bennée        tmp.flush()
4326e733da6SAlex Bennée        tmp.seek(0)
4336e733da6SAlex Bennée
4346e733da6SAlex Bennée        # Run the build with our tarball context
4356e733da6SAlex Bennée        dkr = Docker()
4366e733da6SAlex Bennée        dkr.update_image(args.tag, tmp, quiet=args.quiet)
4376e733da6SAlex Bennée
4386e733da6SAlex Bennée        return 0
4396e733da6SAlex Bennée
4404485b04bSFam Zhengclass CleanCommand(SubCommand):
4414485b04bSFam Zheng    """Clean up docker instances"""
4424485b04bSFam Zheng    name = "clean"
4434485b04bSFam Zheng    def run(self, args, argv):
4444485b04bSFam Zheng        Docker().clean()
4454485b04bSFam Zheng        return 0
4464485b04bSFam Zheng
4474b08af60SFam Zhengclass ImagesCommand(SubCommand):
4484b08af60SFam Zheng    """Run "docker images" command"""
4494b08af60SFam Zheng    name = "images"
4504b08af60SFam Zheng    def run(self, args, argv):
4514b08af60SFam Zheng        return Docker().command("images", argv, args.quiet)
4524b08af60SFam Zheng
45315df9d37SAlex Bennée
45415df9d37SAlex Bennéeclass ProbeCommand(SubCommand):
45515df9d37SAlex Bennée    """Probe if we can run docker automatically"""
45615df9d37SAlex Bennée    name = "probe"
45715df9d37SAlex Bennée
45815df9d37SAlex Bennée    def run(self, args, argv):
45915df9d37SAlex Bennée        try:
46015df9d37SAlex Bennée            docker = Docker()
46115df9d37SAlex Bennée            if docker._command[0] == "docker":
462f03868bdSEduardo Habkost                print("yes")
46315df9d37SAlex Bennée            elif docker._command[0] == "sudo":
464f03868bdSEduardo Habkost                print("sudo")
46515df9d37SAlex Bennée        except Exception:
466f03868bdSEduardo Habkost            print("no")
46715df9d37SAlex Bennée
46815df9d37SAlex Bennée        return
46915df9d37SAlex Bennée
47015df9d37SAlex Bennée
4715e03c2d8SAlex Bennéeclass CcCommand(SubCommand):
4725e03c2d8SAlex Bennée    """Compile sources with cc in images"""
4735e03c2d8SAlex Bennée    name = "cc"
4745e03c2d8SAlex Bennée
4755e03c2d8SAlex Bennée    def args(self, parser):
4765e03c2d8SAlex Bennée        parser.add_argument("--image", "-i", required=True,
4775e03c2d8SAlex Bennée                            help="The docker image in which to run cc")
47899cfdb86SAlex Bennée        parser.add_argument("--cc", default="cc",
47999cfdb86SAlex Bennée                            help="The compiler executable to call")
48050b72738SAlex Bennée        parser.add_argument("--user",
48150b72738SAlex Bennée                            help="The user-id to run under")
4825e03c2d8SAlex Bennée        parser.add_argument("--source-path", "-s", nargs="*", dest="paths",
4835e03c2d8SAlex Bennée                            help="""Extra paths to (ro) mount into container for
4845e03c2d8SAlex Bennée                            reading sources""")
4855e03c2d8SAlex Bennée
4865e03c2d8SAlex Bennée    def run(self, args, argv):
4875e03c2d8SAlex Bennée        if argv and argv[0] == "--":
4885e03c2d8SAlex Bennée            argv = argv[1:]
4895e03c2d8SAlex Bennée        cwd = os.getcwd()
4905e03c2d8SAlex Bennée        cmd = ["--rm", "-w", cwd,
4915e03c2d8SAlex Bennée               "-v", "%s:%s:rw" % (cwd, cwd)]
4925e03c2d8SAlex Bennée        if args.paths:
4935e03c2d8SAlex Bennée            for p in args.paths:
4945e03c2d8SAlex Bennée                cmd += ["-v", "%s:%s:ro,z" % (p, p)]
49550b72738SAlex Bennée        if args.user:
49650b72738SAlex Bennée            cmd += ["-u", args.user]
49799cfdb86SAlex Bennée        cmd += [args.image, args.cc]
4985e03c2d8SAlex Bennée        cmd += argv
4995e03c2d8SAlex Bennée        return Docker().command("run", cmd, args.quiet)
5005e03c2d8SAlex Bennée
5015e03c2d8SAlex Bennée
502f97da1f7SAlex Bennéeclass CheckCommand(SubCommand):
503f97da1f7SAlex Bennée    """Check if we need to re-build a docker image out of a dockerfile.
504f97da1f7SAlex Bennée    Arguments: <tag> <dockerfile>"""
505f97da1f7SAlex Bennée    name = "check"
506f97da1f7SAlex Bennée
507f97da1f7SAlex Bennée    def args(self, parser):
508f97da1f7SAlex Bennée        parser.add_argument("tag",
509f97da1f7SAlex Bennée                            help="Image Tag")
5107b882245SAlex Bennée        parser.add_argument("dockerfile", default=None,
5117b882245SAlex Bennée                            help="Dockerfile name", nargs='?')
5127b882245SAlex Bennée        parser.add_argument("--checktype", choices=["checksum", "age"],
5137b882245SAlex Bennée                            default="checksum", help="check type")
5147b882245SAlex Bennée        parser.add_argument("--olderthan", default=60, type=int,
5157b882245SAlex Bennée                            help="number of minutes")
516f97da1f7SAlex Bennée
517f97da1f7SAlex Bennée    def run(self, args, argv):
518f97da1f7SAlex Bennée        tag = args.tag
519f97da1f7SAlex Bennée
52043e1b2ffSAlex Bennée        try:
521f97da1f7SAlex Bennée            dkr = Docker()
52243e1b2ffSAlex Bennée        except:
52343e1b2ffSAlex Bennée            print("Docker not set up")
52443e1b2ffSAlex Bennée            return 1
52543e1b2ffSAlex Bennée
526f97da1f7SAlex Bennée        info = dkr.inspect_tag(tag)
527f97da1f7SAlex Bennée        if info is None:
528f97da1f7SAlex Bennée            print("Image does not exist")
529f97da1f7SAlex Bennée            return 1
530f97da1f7SAlex Bennée
5317b882245SAlex Bennée        if args.checktype == "checksum":
5327b882245SAlex Bennée            if not args.dockerfile:
5337b882245SAlex Bennée                print("Need a dockerfile for tag:%s" % (tag))
5347b882245SAlex Bennée                return 1
5357b882245SAlex Bennée
5367b882245SAlex Bennée            dockerfile = open(args.dockerfile, "rb").read()
5377b882245SAlex Bennée
538f97da1f7SAlex Bennée            if dkr.image_matches_dockerfile(tag, dockerfile):
539f97da1f7SAlex Bennée                if not args.quiet:
540f97da1f7SAlex Bennée                    print("Image is up to date")
541f97da1f7SAlex Bennée                return 0
542f97da1f7SAlex Bennée            else:
543f97da1f7SAlex Bennée                print("Image needs updating")
544f97da1f7SAlex Bennée                return 1
5457b882245SAlex Bennée        elif args.checktype == "age":
5467b882245SAlex Bennée            timestr = dkr.get_image_creation_time(info).split(".")[0]
5477b882245SAlex Bennée            created = datetime.strptime(timestr, "%Y-%m-%dT%H:%M:%S")
5487b882245SAlex Bennée            past = datetime.now() - timedelta(minutes=args.olderthan)
5497b882245SAlex Bennée            if created < past:
5507b882245SAlex Bennée                print ("Image created @ %s more than %d minutes old" %
5517b882245SAlex Bennée                       (timestr, args.olderthan))
5527b882245SAlex Bennée                return 1
5537b882245SAlex Bennée            else:
5547b882245SAlex Bennée                if not args.quiet:
5557b882245SAlex Bennée                    print ("Image less than %d minutes old" % (args.olderthan))
5567b882245SAlex Bennée                return 0
557f97da1f7SAlex Bennée
558f97da1f7SAlex Bennée
5594485b04bSFam Zhengdef main():
5604485b04bSFam Zheng    parser = argparse.ArgumentParser(description="A Docker helper",
5614485b04bSFam Zheng            usage="%s <subcommand> ..." % os.path.basename(sys.argv[0]))
5624485b04bSFam Zheng    subparsers = parser.add_subparsers(title="subcommands", help=None)
5634485b04bSFam Zheng    for cls in SubCommand.__subclasses__():
5644485b04bSFam Zheng        cmd = cls()
5654485b04bSFam Zheng        subp = subparsers.add_parser(cmd.name, help=cmd.__doc__)
5664485b04bSFam Zheng        cmd.shared_args(subp)
5674485b04bSFam Zheng        cmd.args(subp)
5684485b04bSFam Zheng        subp.set_defaults(cmdobj=cmd)
5694485b04bSFam Zheng    args, argv = parser.parse_known_args()
5704485b04bSFam Zheng    return args.cmdobj.run(args, argv)
5714485b04bSFam Zheng
5724485b04bSFam Zhengif __name__ == "__main__":
5734485b04bSFam Zheng    sys.exit(main())
574