xref: /qemu/tests/docker/docker.py (revision f9172822e7a07db346e21d461e2a61ffcee7c77e)
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
17c2d31896SStefan Hajnoczisys.path.append(os.path.join(os.path.dirname(__file__),
18c2d31896SStefan Hajnoczi                             '..', '..', 'scripts'))
19c2d31896SStefan Hajnocziimport argparse
204485b04bSFam Zhengimport subprocess
214485b04bSFam Zhengimport json
224485b04bSFam Zhengimport hashlib
234485b04bSFam Zhengimport atexit
244485b04bSFam Zhengimport uuid
254485b04bSFam Zhengimport tempfile
26504ca3c2SAlex Bennéeimport re
2797cba1a1SFam Zhengimport signal
286e733da6SAlex Bennéefrom tarfile import TarFile, TarInfo
297a5d936bSAlex Bennéetry:
306e733da6SAlex Bennée    from StringIO import StringIO
317a5d936bSAlex Bennéeexcept ImportError:
327a5d936bSAlex Bennée    from io import StringIO
33a9f8d038SAlex Bennéefrom shutil import copy, rmtree
34414a8ce5SAlex Bennéefrom pwd import getpwuid
354485b04bSFam Zheng
36c9772570SSascha Silbe
3706cc3551SPhilippe Mathieu-DaudéFILTERED_ENV_NAMES = ['ftp_proxy', 'http_proxy', 'https_proxy']
3806cc3551SPhilippe Mathieu-Daudé
3906cc3551SPhilippe Mathieu-Daudé
40c9772570SSascha SilbeDEVNULL = open(os.devnull, 'wb')
41c9772570SSascha Silbe
42c9772570SSascha Silbe
434485b04bSFam Zhengdef _text_checksum(text):
444485b04bSFam Zheng    """Calculate a digest string unique to the text content"""
454485b04bSFam Zheng    return hashlib.sha1(text).hexdigest()
464485b04bSFam Zheng
47438d1168SPhilippe Mathieu-Daudédef _file_checksum(filename):
48438d1168SPhilippe Mathieu-Daudé    return _text_checksum(open(filename, 'rb').read())
49438d1168SPhilippe Mathieu-Daudé
504485b04bSFam Zhengdef _guess_docker_command():
514485b04bSFam Zheng    """ Guess a working docker command or raise exception if not found"""
524485b04bSFam Zheng    commands = [["docker"], ["sudo", "-n", "docker"]]
534485b04bSFam Zheng    for cmd in commands:
540679f98bSEduardo Habkost        try:
5583405c45SAlex Bennée            # docker version will return the client details in stdout
5683405c45SAlex Bennée            # but still report a status of 1 if it can't contact the daemon
5783405c45SAlex Bennée            if subprocess.call(cmd + ["version"],
58c9772570SSascha Silbe                               stdout=DEVNULL, stderr=DEVNULL) == 0:
594485b04bSFam Zheng                return cmd
600679f98bSEduardo Habkost        except OSError:
610679f98bSEduardo Habkost            pass
624485b04bSFam Zheng    commands_txt = "\n".join(["  " + " ".join(x) for x in commands])
634485b04bSFam Zheng    raise Exception("Cannot find working docker command. Tried:\n%s" % \
644485b04bSFam Zheng                    commands_txt)
654485b04bSFam Zheng
662499ee9fSPhilippe Mathieu-Daudédef _copy_with_mkdir(src, root_dir, sub_path='.'):
67504ca3c2SAlex Bennée    """Copy src into root_dir, creating sub_path as needed."""
68504ca3c2SAlex Bennée    dest_dir = os.path.normpath("%s/%s" % (root_dir, sub_path))
69504ca3c2SAlex Bennée    try:
70504ca3c2SAlex Bennée        os.makedirs(dest_dir)
71504ca3c2SAlex Bennée    except OSError:
72504ca3c2SAlex Bennée        # we can safely ignore already created directories
73504ca3c2SAlex Bennée        pass
74504ca3c2SAlex Bennée
75504ca3c2SAlex Bennée    dest_file = "%s/%s" % (dest_dir, os.path.basename(src))
76504ca3c2SAlex Bennée    copy(src, dest_file)
77504ca3c2SAlex Bennée
78504ca3c2SAlex Bennée
79504ca3c2SAlex Bennéedef _get_so_libs(executable):
80504ca3c2SAlex Bennée    """Return a list of libraries associated with an executable.
81504ca3c2SAlex Bennée
82504ca3c2SAlex Bennée    The paths may be symbolic links which would need to be resolved to
83504ca3c2SAlex Bennée    ensure theright data is copied."""
84504ca3c2SAlex Bennée
85504ca3c2SAlex Bennée    libs = []
86504ca3c2SAlex Bennée    ldd_re = re.compile(r"(/.*/)(\S*)")
87504ca3c2SAlex Bennée    try:
88504ca3c2SAlex Bennée        ldd_output = subprocess.check_output(["ldd", executable])
89504ca3c2SAlex Bennée        for line in ldd_output.split("\n"):
90504ca3c2SAlex Bennée            search = ldd_re.search(line)
91504ca3c2SAlex Bennée            if search and len(search.groups()) == 2:
92504ca3c2SAlex Bennée                so_path = search.groups()[0]
93504ca3c2SAlex Bennée                so_lib = search.groups()[1]
94504ca3c2SAlex Bennée                libs.append("%s/%s" % (so_path, so_lib))
95504ca3c2SAlex Bennée    except subprocess.CalledProcessError:
96f03868bdSEduardo Habkost        print("%s had no associated libraries (static build?)" % (executable))
97504ca3c2SAlex Bennée
98504ca3c2SAlex Bennée    return libs
99504ca3c2SAlex Bennée
100504ca3c2SAlex Bennéedef _copy_binary_with_libs(src, dest_dir):
101504ca3c2SAlex Bennée    """Copy a binary executable and all its dependant libraries.
102504ca3c2SAlex Bennée
103504ca3c2SAlex Bennée    This does rely on the host file-system being fairly multi-arch
104504ca3c2SAlex Bennée    aware so the file don't clash with the guests layout."""
105504ca3c2SAlex Bennée
106504ca3c2SAlex Bennée    _copy_with_mkdir(src, dest_dir, "/usr/bin")
107504ca3c2SAlex Bennée
108504ca3c2SAlex Bennée    libs = _get_so_libs(src)
109504ca3c2SAlex Bennée    if libs:
110504ca3c2SAlex Bennée        for l in libs:
111504ca3c2SAlex Bennée            so_path = os.path.dirname(l)
112504ca3c2SAlex Bennée            _copy_with_mkdir(l , dest_dir, so_path)
113504ca3c2SAlex Bennée
114c1958e9dSFam Zhengdef _read_qemu_dockerfile(img_name):
115c1958e9dSFam Zheng    df = os.path.join(os.path.dirname(__file__), "dockerfiles",
116c1958e9dSFam Zheng                      img_name + ".docker")
117c1958e9dSFam Zheng    return open(df, "r").read()
118c1958e9dSFam Zheng
119c1958e9dSFam Zhengdef _dockerfile_preprocess(df):
120c1958e9dSFam Zheng    out = ""
121c1958e9dSFam Zheng    for l in df.splitlines():
122c1958e9dSFam Zheng        if len(l.strip()) == 0 or l.startswith("#"):
123c1958e9dSFam Zheng            continue
124c1958e9dSFam Zheng        from_pref = "FROM qemu:"
125c1958e9dSFam Zheng        if l.startswith(from_pref):
126c1958e9dSFam Zheng            # TODO: Alternatively we could replace this line with "FROM $ID"
127c1958e9dSFam Zheng            # where $ID is the image's hex id obtained with
128c1958e9dSFam Zheng            #    $ docker images $IMAGE --format="{{.Id}}"
129c1958e9dSFam Zheng            # but unfortunately that's not supported by RHEL 7.
130c1958e9dSFam Zheng            inlining = _read_qemu_dockerfile(l[len(from_pref):])
131c1958e9dSFam Zheng            out += _dockerfile_preprocess(inlining)
132c1958e9dSFam Zheng            continue
133c1958e9dSFam Zheng        out += l + "\n"
134c1958e9dSFam Zheng    return out
135c1958e9dSFam Zheng
1364485b04bSFam Zhengclass Docker(object):
1374485b04bSFam Zheng    """ Running Docker commands """
1384485b04bSFam Zheng    def __init__(self):
1394485b04bSFam Zheng        self._command = _guess_docker_command()
1404485b04bSFam Zheng        self._instances = []
1414485b04bSFam Zheng        atexit.register(self._kill_instances)
14297cba1a1SFam Zheng        signal.signal(signal.SIGTERM, self._kill_instances)
14397cba1a1SFam Zheng        signal.signal(signal.SIGHUP, self._kill_instances)
1444485b04bSFam Zheng
14558bf7b6dSFam Zheng    def _do(self, cmd, quiet=True, **kwargs):
1464485b04bSFam Zheng        if quiet:
147c9772570SSascha Silbe            kwargs["stdout"] = DEVNULL
1484485b04bSFam Zheng        return subprocess.call(self._command + cmd, **kwargs)
1494485b04bSFam Zheng
1500b95ff72SFam Zheng    def _do_check(self, cmd, quiet=True, **kwargs):
1510b95ff72SFam Zheng        if quiet:
1520b95ff72SFam Zheng            kwargs["stdout"] = DEVNULL
1530b95ff72SFam Zheng        return subprocess.check_call(self._command + cmd, **kwargs)
1540b95ff72SFam Zheng
1554485b04bSFam Zheng    def _do_kill_instances(self, only_known, only_active=True):
1564485b04bSFam Zheng        cmd = ["ps", "-q"]
1574485b04bSFam Zheng        if not only_active:
1584485b04bSFam Zheng            cmd.append("-a")
1594485b04bSFam Zheng        for i in self._output(cmd).split():
1604485b04bSFam Zheng            resp = self._output(["inspect", i])
1614485b04bSFam Zheng            labels = json.loads(resp)[0]["Config"]["Labels"]
1624485b04bSFam Zheng            active = json.loads(resp)[0]["State"]["Running"]
1634485b04bSFam Zheng            if not labels:
1644485b04bSFam Zheng                continue
1654485b04bSFam Zheng            instance_uuid = labels.get("com.qemu.instance.uuid", None)
1664485b04bSFam Zheng            if not instance_uuid:
1674485b04bSFam Zheng                continue
1684485b04bSFam Zheng            if only_known and instance_uuid not in self._instances:
1694485b04bSFam Zheng                continue
170f03868bdSEduardo Habkost            print("Terminating", i)
1714485b04bSFam Zheng            if active:
1724485b04bSFam Zheng                self._do(["kill", i])
1734485b04bSFam Zheng            self._do(["rm", i])
1744485b04bSFam Zheng
1754485b04bSFam Zheng    def clean(self):
1764485b04bSFam Zheng        self._do_kill_instances(False, False)
1774485b04bSFam Zheng        return 0
1784485b04bSFam Zheng
17997cba1a1SFam Zheng    def _kill_instances(self, *args, **kwargs):
1804485b04bSFam Zheng        return self._do_kill_instances(True)
1814485b04bSFam Zheng
1824485b04bSFam Zheng    def _output(self, cmd, **kwargs):
1834485b04bSFam Zheng        return subprocess.check_output(self._command + cmd,
1844485b04bSFam Zheng                                       stderr=subprocess.STDOUT,
1854485b04bSFam Zheng                                       **kwargs)
1864485b04bSFam Zheng
1874485b04bSFam Zheng    def get_image_dockerfile_checksum(self, tag):
1884485b04bSFam Zheng        resp = self._output(["inspect", tag])
1894485b04bSFam Zheng        labels = json.loads(resp)[0]["Config"].get("Labels", {})
1904485b04bSFam Zheng        return labels.get("com.qemu.dockerfile-checksum", "")
1914485b04bSFam Zheng
192414a8ce5SAlex Bennée    def build_image(self, tag, docker_dir, dockerfile,
193438d1168SPhilippe Mathieu-Daudé                    quiet=True, user=False, argv=None, extra_files_cksum=[]):
1944485b04bSFam Zheng        if argv == None:
1954485b04bSFam Zheng            argv = []
1964485b04bSFam Zheng
197a9f8d038SAlex Bennée        tmp_df = tempfile.NamedTemporaryFile(dir=docker_dir, suffix=".docker")
1984485b04bSFam Zheng        tmp_df.write(dockerfile)
1994485b04bSFam Zheng
200414a8ce5SAlex Bennée        if user:
201414a8ce5SAlex Bennée            uid = os.getuid()
202414a8ce5SAlex Bennée            uname = getpwuid(uid).pw_name
203414a8ce5SAlex Bennée            tmp_df.write("\n")
204414a8ce5SAlex Bennée            tmp_df.write("RUN id %s 2>/dev/null || useradd -u %d -U %s" %
205414a8ce5SAlex Bennée                         (uname, uid, uname))
206414a8ce5SAlex Bennée
2074485b04bSFam Zheng        tmp_df.write("\n")
2084485b04bSFam Zheng        tmp_df.write("LABEL com.qemu.dockerfile-checksum=%s" %
209*f9172822SAlex Bennée                     _text_checksum(_dockerfile_preprocess(dockerfile)))
210*f9172822SAlex Bennée        for f, c in extra_files_cksum:
211*f9172822SAlex Bennée            tmp_df.write("LABEL com.qemu.%s-checksum=%s" % (f, c))
212*f9172822SAlex Bennée
2134485b04bSFam Zheng        tmp_df.flush()
214a9f8d038SAlex Bennée
2150b95ff72SFam Zheng        self._do_check(["build", "-t", tag, "-f", tmp_df.name] + argv + \
216a9f8d038SAlex Bennée                       [docker_dir],
2174485b04bSFam Zheng                       quiet=quiet)
2184485b04bSFam Zheng
2196e733da6SAlex Bennée    def update_image(self, tag, tarball, quiet=True):
2206e733da6SAlex Bennée        "Update a tagged image using "
2216e733da6SAlex Bennée
2220b95ff72SFam Zheng        self._do_check(["build", "-t", tag, "-"], quiet=quiet, stdin=tarball)
2236e733da6SAlex Bennée
2244485b04bSFam Zheng    def image_matches_dockerfile(self, tag, dockerfile):
2254485b04bSFam Zheng        try:
2264485b04bSFam Zheng            checksum = self.get_image_dockerfile_checksum(tag)
2274485b04bSFam Zheng        except Exception:
2284485b04bSFam Zheng            return False
229c1958e9dSFam Zheng        return checksum == _text_checksum(_dockerfile_preprocess(dockerfile))
2304485b04bSFam Zheng
2314485b04bSFam Zheng    def run(self, cmd, keep, quiet):
2324485b04bSFam Zheng        label = uuid.uuid1().hex
2334485b04bSFam Zheng        if not keep:
2344485b04bSFam Zheng            self._instances.append(label)
2350b95ff72SFam Zheng        ret = self._do_check(["run", "--label",
2364485b04bSFam Zheng                             "com.qemu.instance.uuid=" + label] + cmd,
2374485b04bSFam Zheng                             quiet=quiet)
2384485b04bSFam Zheng        if not keep:
2394485b04bSFam Zheng            self._instances.remove(label)
2404485b04bSFam Zheng        return ret
2414485b04bSFam Zheng
2424b08af60SFam Zheng    def command(self, cmd, argv, quiet):
2434b08af60SFam Zheng        return self._do([cmd] + argv, quiet=quiet)
2444b08af60SFam Zheng
2454485b04bSFam Zhengclass SubCommand(object):
2464485b04bSFam Zheng    """A SubCommand template base class"""
2474485b04bSFam Zheng    name = None # Subcommand name
2484485b04bSFam Zheng    def shared_args(self, parser):
2494485b04bSFam Zheng        parser.add_argument("--quiet", action="store_true",
2504485b04bSFam Zheng                            help="Run quietly unless an error occured")
2514485b04bSFam Zheng
2524485b04bSFam Zheng    def args(self, parser):
2534485b04bSFam Zheng        """Setup argument parser"""
2544485b04bSFam Zheng        pass
2554485b04bSFam Zheng    def run(self, args, argv):
2564485b04bSFam Zheng        """Run command.
2574485b04bSFam Zheng        args: parsed argument by argument parser.
2584485b04bSFam Zheng        argv: remaining arguments from sys.argv.
2594485b04bSFam Zheng        """
2604485b04bSFam Zheng        pass
2614485b04bSFam Zheng
2624485b04bSFam Zhengclass RunCommand(SubCommand):
2634485b04bSFam Zheng    """Invoke docker run and take care of cleaning up"""
2644485b04bSFam Zheng    name = "run"
2654485b04bSFam Zheng    def args(self, parser):
2664485b04bSFam Zheng        parser.add_argument("--keep", action="store_true",
2674485b04bSFam Zheng                            help="Don't remove image when command completes")
2684485b04bSFam Zheng    def run(self, args, argv):
2694485b04bSFam Zheng        return Docker().run(argv, args.keep, quiet=args.quiet)
2704485b04bSFam Zheng
2714485b04bSFam Zhengclass BuildCommand(SubCommand):
2724485b04bSFam Zheng    """ Build docker image out of a dockerfile. Arguments: <tag> <dockerfile>"""
2734485b04bSFam Zheng    name = "build"
2744485b04bSFam Zheng    def args(self, parser):
275504ca3c2SAlex Bennée        parser.add_argument("--include-executable", "-e",
276504ca3c2SAlex Bennée                            help="""Specify a binary that will be copied to the
277504ca3c2SAlex Bennée                            container together with all its dependent
278504ca3c2SAlex Bennée                            libraries""")
2794c84f662SPhilippe Mathieu-Daudé        parser.add_argument("--extra-files", "-f", nargs='*',
2804c84f662SPhilippe Mathieu-Daudé                            help="""Specify files that will be copied in the
2814c84f662SPhilippe Mathieu-Daudé                            Docker image, fulfilling the ADD directive from the
2824c84f662SPhilippe Mathieu-Daudé                            Dockerfile""")
283414a8ce5SAlex Bennée        parser.add_argument("--add-current-user", "-u", dest="user",
284414a8ce5SAlex Bennée                            action="store_true",
285414a8ce5SAlex Bennée                            help="Add the current user to image's passwd")
2864485b04bSFam Zheng        parser.add_argument("tag",
2874485b04bSFam Zheng                            help="Image Tag")
2884485b04bSFam Zheng        parser.add_argument("dockerfile",
2894485b04bSFam Zheng                            help="Dockerfile name")
2904485b04bSFam Zheng
2914485b04bSFam Zheng    def run(self, args, argv):
2924485b04bSFam Zheng        dockerfile = open(args.dockerfile, "rb").read()
2934485b04bSFam Zheng        tag = args.tag
2944485b04bSFam Zheng
2954485b04bSFam Zheng        dkr = Docker()
2966fe3ae3fSAlex Bennée        if "--no-cache" not in argv and \
2976fe3ae3fSAlex Bennée           dkr.image_matches_dockerfile(tag, dockerfile):
2984485b04bSFam Zheng            if not args.quiet:
299f03868bdSEduardo Habkost                print("Image is up to date.")
300a9f8d038SAlex Bennée        else:
301a9f8d038SAlex Bennée            # Create a docker context directory for the build
302a9f8d038SAlex Bennée            docker_dir = tempfile.mkdtemp(prefix="docker_build")
3034485b04bSFam Zheng
304920776eaSAlex Bennée            # Is there a .pre file to run in the build context?
305920776eaSAlex Bennée            docker_pre = os.path.splitext(args.dockerfile)[0]+".pre"
306920776eaSAlex Bennée            if os.path.exists(docker_pre):
307f8042deaSSascha Silbe                stdout = DEVNULL if args.quiet else None
308920776eaSAlex Bennée                rc = subprocess.call(os.path.realpath(docker_pre),
309f8042deaSSascha Silbe                                     cwd=docker_dir, stdout=stdout)
310920776eaSAlex Bennée                if rc == 3:
311f03868bdSEduardo Habkost                    print("Skip")
312920776eaSAlex Bennée                    return 0
313920776eaSAlex Bennée                elif rc != 0:
314f03868bdSEduardo Habkost                    print("%s exited with code %d" % (docker_pre, rc))
315920776eaSAlex Bennée                    return 1
316920776eaSAlex Bennée
3174c84f662SPhilippe Mathieu-Daudé            # Copy any extra files into the Docker context. These can be
3184c84f662SPhilippe Mathieu-Daudé            # included by the use of the ADD directive in the Dockerfile.
319438d1168SPhilippe Mathieu-Daudé            cksum = []
320504ca3c2SAlex Bennée            if args.include_executable:
321438d1168SPhilippe Mathieu-Daudé                # FIXME: there is no checksum of this executable and the linked
322438d1168SPhilippe Mathieu-Daudé                # libraries, once the image built any change of this executable
323438d1168SPhilippe Mathieu-Daudé                # or any library won't trigger another build.
3244c84f662SPhilippe Mathieu-Daudé                _copy_binary_with_libs(args.include_executable, docker_dir)
3254c84f662SPhilippe Mathieu-Daudé            for filename in args.extra_files or []:
3264c84f662SPhilippe Mathieu-Daudé                _copy_with_mkdir(filename, docker_dir)
327*f9172822SAlex Bennée                cksum += [(filename, _file_checksum(filename))]
328504ca3c2SAlex Bennée
32906cc3551SPhilippe Mathieu-Daudé            argv += ["--build-arg=" + k.lower() + "=" + v
33006cc3551SPhilippe Mathieu-Daudé                        for k, v in os.environ.iteritems()
33106cc3551SPhilippe Mathieu-Daudé                        if k.lower() in FILTERED_ENV_NAMES]
332a9f8d038SAlex Bennée            dkr.build_image(tag, docker_dir, dockerfile,
333438d1168SPhilippe Mathieu-Daudé                            quiet=args.quiet, user=args.user, argv=argv,
334438d1168SPhilippe Mathieu-Daudé                            extra_files_cksum=cksum)
335a9f8d038SAlex Bennée
336a9f8d038SAlex Bennée            rmtree(docker_dir)
337a9f8d038SAlex Bennée
3384485b04bSFam Zheng        return 0
3394485b04bSFam Zheng
3406e733da6SAlex Bennéeclass UpdateCommand(SubCommand):
3416e733da6SAlex Bennée    """ Update a docker image with new executables. Arguments: <tag> <executable>"""
3426e733da6SAlex Bennée    name = "update"
3436e733da6SAlex Bennée    def args(self, parser):
3446e733da6SAlex Bennée        parser.add_argument("tag",
3456e733da6SAlex Bennée                            help="Image Tag")
3466e733da6SAlex Bennée        parser.add_argument("executable",
3476e733da6SAlex Bennée                            help="Executable to copy")
3486e733da6SAlex Bennée
3496e733da6SAlex Bennée    def run(self, args, argv):
3506e733da6SAlex Bennée        # Create a temporary tarball with our whole build context and
3516e733da6SAlex Bennée        # dockerfile for the update
3526e733da6SAlex Bennée        tmp = tempfile.NamedTemporaryFile(suffix="dckr.tar.gz")
3536e733da6SAlex Bennée        tmp_tar = TarFile(fileobj=tmp, mode='w')
3546e733da6SAlex Bennée
3556e733da6SAlex Bennée        # Add the executable to the tarball
3566e733da6SAlex Bennée        bn = os.path.basename(args.executable)
3576e733da6SAlex Bennée        ff = "/usr/bin/%s" % bn
3586e733da6SAlex Bennée        tmp_tar.add(args.executable, arcname=ff)
3596e733da6SAlex Bennée
3606e733da6SAlex Bennée        # Add any associated libraries
3616e733da6SAlex Bennée        libs = _get_so_libs(args.executable)
3626e733da6SAlex Bennée        if libs:
3636e733da6SAlex Bennée            for l in libs:
3646e733da6SAlex Bennée                tmp_tar.add(os.path.realpath(l), arcname=l)
3656e733da6SAlex Bennée
3666e733da6SAlex Bennée        # Create a Docker buildfile
3676e733da6SAlex Bennée        df = StringIO()
3686e733da6SAlex Bennée        df.write("FROM %s\n" % args.tag)
3696e733da6SAlex Bennée        df.write("ADD . /\n")
3706e733da6SAlex Bennée        df.seek(0)
3716e733da6SAlex Bennée
3726e733da6SAlex Bennée        df_tar = TarInfo(name="Dockerfile")
3736e733da6SAlex Bennée        df_tar.size = len(df.buf)
3746e733da6SAlex Bennée        tmp_tar.addfile(df_tar, fileobj=df)
3756e733da6SAlex Bennée
3766e733da6SAlex Bennée        tmp_tar.close()
3776e733da6SAlex Bennée
3786e733da6SAlex Bennée        # reset the file pointers
3796e733da6SAlex Bennée        tmp.flush()
3806e733da6SAlex Bennée        tmp.seek(0)
3816e733da6SAlex Bennée
3826e733da6SAlex Bennée        # Run the build with our tarball context
3836e733da6SAlex Bennée        dkr = Docker()
3846e733da6SAlex Bennée        dkr.update_image(args.tag, tmp, quiet=args.quiet)
3856e733da6SAlex Bennée
3866e733da6SAlex Bennée        return 0
3876e733da6SAlex Bennée
3884485b04bSFam Zhengclass CleanCommand(SubCommand):
3894485b04bSFam Zheng    """Clean up docker instances"""
3904485b04bSFam Zheng    name = "clean"
3914485b04bSFam Zheng    def run(self, args, argv):
3924485b04bSFam Zheng        Docker().clean()
3934485b04bSFam Zheng        return 0
3944485b04bSFam Zheng
3954b08af60SFam Zhengclass ImagesCommand(SubCommand):
3964b08af60SFam Zheng    """Run "docker images" command"""
3974b08af60SFam Zheng    name = "images"
3984b08af60SFam Zheng    def run(self, args, argv):
3994b08af60SFam Zheng        return Docker().command("images", argv, args.quiet)
4004b08af60SFam Zheng
40115df9d37SAlex Bennée
40215df9d37SAlex Bennéeclass ProbeCommand(SubCommand):
40315df9d37SAlex Bennée    """Probe if we can run docker automatically"""
40415df9d37SAlex Bennée    name = "probe"
40515df9d37SAlex Bennée
40615df9d37SAlex Bennée    def run(self, args, argv):
40715df9d37SAlex Bennée        try:
40815df9d37SAlex Bennée            docker = Docker()
40915df9d37SAlex Bennée            if docker._command[0] == "docker":
410f03868bdSEduardo Habkost                print("yes")
41115df9d37SAlex Bennée            elif docker._command[0] == "sudo":
412f03868bdSEduardo Habkost                print("sudo")
41315df9d37SAlex Bennée        except Exception:
414f03868bdSEduardo Habkost            print("no")
41515df9d37SAlex Bennée
41615df9d37SAlex Bennée        return
41715df9d37SAlex Bennée
41815df9d37SAlex Bennée
4195e03c2d8SAlex Bennéeclass CcCommand(SubCommand):
4205e03c2d8SAlex Bennée    """Compile sources with cc in images"""
4215e03c2d8SAlex Bennée    name = "cc"
4225e03c2d8SAlex Bennée
4235e03c2d8SAlex Bennée    def args(self, parser):
4245e03c2d8SAlex Bennée        parser.add_argument("--image", "-i", required=True,
4255e03c2d8SAlex Bennée                            help="The docker image in which to run cc")
42699cfdb86SAlex Bennée        parser.add_argument("--cc", default="cc",
42799cfdb86SAlex Bennée                            help="The compiler executable to call")
42850b72738SAlex Bennée        parser.add_argument("--user",
42950b72738SAlex Bennée                            help="The user-id to run under")
4305e03c2d8SAlex Bennée        parser.add_argument("--source-path", "-s", nargs="*", dest="paths",
4315e03c2d8SAlex Bennée                            help="""Extra paths to (ro) mount into container for
4325e03c2d8SAlex Bennée                            reading sources""")
4335e03c2d8SAlex Bennée
4345e03c2d8SAlex Bennée    def run(self, args, argv):
4355e03c2d8SAlex Bennée        if argv and argv[0] == "--":
4365e03c2d8SAlex Bennée            argv = argv[1:]
4375e03c2d8SAlex Bennée        cwd = os.getcwd()
4385e03c2d8SAlex Bennée        cmd = ["--rm", "-w", cwd,
4395e03c2d8SAlex Bennée               "-v", "%s:%s:rw" % (cwd, cwd)]
4405e03c2d8SAlex Bennée        if args.paths:
4415e03c2d8SAlex Bennée            for p in args.paths:
4425e03c2d8SAlex Bennée                cmd += ["-v", "%s:%s:ro,z" % (p, p)]
44350b72738SAlex Bennée        if args.user:
44450b72738SAlex Bennée            cmd += ["-u", args.user]
44599cfdb86SAlex Bennée        cmd += [args.image, args.cc]
4465e03c2d8SAlex Bennée        cmd += argv
4475e03c2d8SAlex Bennée        return Docker().command("run", cmd, args.quiet)
4485e03c2d8SAlex Bennée
4495e03c2d8SAlex Bennée
4504485b04bSFam Zhengdef main():
4514485b04bSFam Zheng    parser = argparse.ArgumentParser(description="A Docker helper",
4524485b04bSFam Zheng            usage="%s <subcommand> ..." % os.path.basename(sys.argv[0]))
4534485b04bSFam Zheng    subparsers = parser.add_subparsers(title="subcommands", help=None)
4544485b04bSFam Zheng    for cls in SubCommand.__subclasses__():
4554485b04bSFam Zheng        cmd = cls()
4564485b04bSFam Zheng        subp = subparsers.add_parser(cmd.name, help=cmd.__doc__)
4574485b04bSFam Zheng        cmd.shared_args(subp)
4584485b04bSFam Zheng        cmd.args(subp)
4594485b04bSFam Zheng        subp.set_defaults(cmdobj=cmd)
4604485b04bSFam Zheng    args, argv = parser.parse_known_args()
4614485b04bSFam Zheng    return args.cmdobj.run(args, argv)
4624485b04bSFam Zheng
4634485b04bSFam Zhengif __name__ == "__main__":
4644485b04bSFam Zheng    sys.exit(main())
465