xref: /qemu/tests/docker/docker.py (revision 15352decf8fd834627d05fb619dd523f50101977)
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
357b882245SAlex Bennéefrom datetime import datetime,timedelta
364485b04bSFam Zheng
37c9772570SSascha Silbe
3806cc3551SPhilippe Mathieu-DaudéFILTERED_ENV_NAMES = ['ftp_proxy', 'http_proxy', 'https_proxy']
3906cc3551SPhilippe Mathieu-Daudé
4006cc3551SPhilippe Mathieu-Daudé
41c9772570SSascha SilbeDEVNULL = open(os.devnull, 'wb')
42c9772570SSascha Silbe
43c9772570SSascha Silbe
444485b04bSFam Zhengdef _text_checksum(text):
454485b04bSFam Zheng    """Calculate a digest string unique to the text content"""
464485b04bSFam Zheng    return hashlib.sha1(text).hexdigest()
474485b04bSFam Zheng
48438d1168SPhilippe Mathieu-Daudédef _file_checksum(filename):
49438d1168SPhilippe Mathieu-Daudé    return _text_checksum(open(filename, 'rb').read())
50438d1168SPhilippe Mathieu-Daudé
514485b04bSFam Zhengdef _guess_docker_command():
524485b04bSFam Zheng    """ Guess a working docker command or raise exception if not found"""
534485b04bSFam Zheng    commands = [["docker"], ["sudo", "-n", "docker"]]
544485b04bSFam Zheng    for cmd in commands:
550679f98bSEduardo Habkost        try:
5683405c45SAlex Bennée            # docker version will return the client details in stdout
5783405c45SAlex Bennée            # but still report a status of 1 if it can't contact the daemon
5883405c45SAlex Bennée            if subprocess.call(cmd + ["version"],
59c9772570SSascha Silbe                               stdout=DEVNULL, stderr=DEVNULL) == 0:
604485b04bSFam Zheng                return cmd
610679f98bSEduardo Habkost        except OSError:
620679f98bSEduardo Habkost            pass
634485b04bSFam Zheng    commands_txt = "\n".join(["  " + " ".join(x) for x in commands])
644485b04bSFam Zheng    raise Exception("Cannot find working docker command. Tried:\n%s" % \
654485b04bSFam Zheng                    commands_txt)
664485b04bSFam Zheng
672499ee9fSPhilippe Mathieu-Daudédef _copy_with_mkdir(src, root_dir, sub_path='.'):
68504ca3c2SAlex Bennée    """Copy src into root_dir, creating sub_path as needed."""
69504ca3c2SAlex Bennée    dest_dir = os.path.normpath("%s/%s" % (root_dir, sub_path))
70504ca3c2SAlex Bennée    try:
71504ca3c2SAlex Bennée        os.makedirs(dest_dir)
72504ca3c2SAlex Bennée    except OSError:
73504ca3c2SAlex Bennée        # we can safely ignore already created directories
74504ca3c2SAlex Bennée        pass
75504ca3c2SAlex Bennée
76504ca3c2SAlex Bennée    dest_file = "%s/%s" % (dest_dir, os.path.basename(src))
77504ca3c2SAlex Bennée    copy(src, dest_file)
78504ca3c2SAlex Bennée
79504ca3c2SAlex Bennée
80504ca3c2SAlex Bennéedef _get_so_libs(executable):
81504ca3c2SAlex Bennée    """Return a list of libraries associated with an executable.
82504ca3c2SAlex Bennée
83504ca3c2SAlex Bennée    The paths may be symbolic links which would need to be resolved to
84504ca3c2SAlex Bennée    ensure theright data is copied."""
85504ca3c2SAlex Bennée
86504ca3c2SAlex Bennée    libs = []
87504ca3c2SAlex Bennée    ldd_re = re.compile(r"(/.*/)(\S*)")
88504ca3c2SAlex Bennée    try:
89504ca3c2SAlex Bennée        ldd_output = subprocess.check_output(["ldd", executable])
90504ca3c2SAlex Bennée        for line in ldd_output.split("\n"):
91504ca3c2SAlex Bennée            search = ldd_re.search(line)
92504ca3c2SAlex Bennée            if search and len(search.groups()) == 2:
93504ca3c2SAlex Bennée                so_path = search.groups()[0]
94504ca3c2SAlex Bennée                so_lib = search.groups()[1]
95504ca3c2SAlex Bennée                libs.append("%s/%s" % (so_path, so_lib))
96504ca3c2SAlex Bennée    except subprocess.CalledProcessError:
97f03868bdSEduardo Habkost        print("%s had no associated libraries (static build?)" % (executable))
98504ca3c2SAlex Bennée
99504ca3c2SAlex Bennée    return libs
100504ca3c2SAlex Bennée
101504ca3c2SAlex Bennéedef _copy_binary_with_libs(src, dest_dir):
102504ca3c2SAlex Bennée    """Copy a binary executable and all its dependant libraries.
103504ca3c2SAlex Bennée
104504ca3c2SAlex Bennée    This does rely on the host file-system being fairly multi-arch
105504ca3c2SAlex Bennée    aware so the file don't clash with the guests layout."""
106504ca3c2SAlex Bennée
107504ca3c2SAlex Bennée    _copy_with_mkdir(src, dest_dir, "/usr/bin")
108504ca3c2SAlex Bennée
109504ca3c2SAlex Bennée    libs = _get_so_libs(src)
110504ca3c2SAlex Bennée    if libs:
111504ca3c2SAlex Bennée        for l in libs:
112504ca3c2SAlex Bennée            so_path = os.path.dirname(l)
113504ca3c2SAlex Bennée            _copy_with_mkdir(l , dest_dir, so_path)
114504ca3c2SAlex Bennée
115*15352decSAlex Bennée
116*15352decSAlex Bennéedef _check_binfmt_misc(executable):
117*15352decSAlex Bennée    """Check binfmt_misc has entry for executable in the right place.
118*15352decSAlex Bennée
119*15352decSAlex Bennée    The details of setting up binfmt_misc are outside the scope of
120*15352decSAlex Bennée    this script but we should at least fail early with a useful
121*15352decSAlex Bennée    message if it won't work."""
122*15352decSAlex Bennée
123*15352decSAlex Bennée    binary = os.path.basename(executable)
124*15352decSAlex Bennée    binfmt_entry = "/proc/sys/fs/binfmt_misc/%s" % (binary)
125*15352decSAlex Bennée
126*15352decSAlex Bennée    if not os.path.exists(binfmt_entry):
127*15352decSAlex Bennée        print ("No binfmt_misc entry for %s" % (binary))
128*15352decSAlex Bennée        return False
129*15352decSAlex Bennée
130*15352decSAlex Bennée    with open(binfmt_entry) as x: entry = x.read()
131*15352decSAlex Bennée
132*15352decSAlex Bennée    qpath = "/usr/bin/%s" % (binary)
133*15352decSAlex Bennée    if not re.search("interpreter %s\n" % (qpath), entry):
134*15352decSAlex Bennée        print ("binfmt_misc for %s does not point to %s" % (binary, qpath))
135*15352decSAlex Bennée        return False
136*15352decSAlex Bennée
137*15352decSAlex Bennée    return True
138*15352decSAlex Bennée
139*15352decSAlex Bennée
140c1958e9dSFam Zhengdef _read_qemu_dockerfile(img_name):
141547cb45eSAlex Bennée    # special case for Debian linux-user images
142547cb45eSAlex Bennée    if img_name.startswith("debian") and img_name.endswith("user"):
143547cb45eSAlex Bennée        img_name = "debian-bootstrap"
144547cb45eSAlex Bennée
145c1958e9dSFam Zheng    df = os.path.join(os.path.dirname(__file__), "dockerfiles",
146c1958e9dSFam Zheng                      img_name + ".docker")
147c1958e9dSFam Zheng    return open(df, "r").read()
148c1958e9dSFam Zheng
149c1958e9dSFam Zhengdef _dockerfile_preprocess(df):
150c1958e9dSFam Zheng    out = ""
151c1958e9dSFam Zheng    for l in df.splitlines():
152c1958e9dSFam Zheng        if len(l.strip()) == 0 or l.startswith("#"):
153c1958e9dSFam Zheng            continue
154c1958e9dSFam Zheng        from_pref = "FROM qemu:"
155c1958e9dSFam Zheng        if l.startswith(from_pref):
156c1958e9dSFam Zheng            # TODO: Alternatively we could replace this line with "FROM $ID"
157c1958e9dSFam Zheng            # where $ID is the image's hex id obtained with
158c1958e9dSFam Zheng            #    $ docker images $IMAGE --format="{{.Id}}"
159c1958e9dSFam Zheng            # but unfortunately that's not supported by RHEL 7.
160c1958e9dSFam Zheng            inlining = _read_qemu_dockerfile(l[len(from_pref):])
161c1958e9dSFam Zheng            out += _dockerfile_preprocess(inlining)
162c1958e9dSFam Zheng            continue
163c1958e9dSFam Zheng        out += l + "\n"
164c1958e9dSFam Zheng    return out
165c1958e9dSFam Zheng
1664485b04bSFam Zhengclass Docker(object):
1674485b04bSFam Zheng    """ Running Docker commands """
1684485b04bSFam Zheng    def __init__(self):
1694485b04bSFam Zheng        self._command = _guess_docker_command()
1704485b04bSFam Zheng        self._instances = []
1714485b04bSFam Zheng        atexit.register(self._kill_instances)
17297cba1a1SFam Zheng        signal.signal(signal.SIGTERM, self._kill_instances)
17397cba1a1SFam Zheng        signal.signal(signal.SIGHUP, self._kill_instances)
1744485b04bSFam Zheng
17558bf7b6dSFam Zheng    def _do(self, cmd, quiet=True, **kwargs):
1764485b04bSFam Zheng        if quiet:
177c9772570SSascha Silbe            kwargs["stdout"] = DEVNULL
1784485b04bSFam Zheng        return subprocess.call(self._command + cmd, **kwargs)
1794485b04bSFam Zheng
1800b95ff72SFam Zheng    def _do_check(self, cmd, quiet=True, **kwargs):
1810b95ff72SFam Zheng        if quiet:
1820b95ff72SFam Zheng            kwargs["stdout"] = DEVNULL
1830b95ff72SFam Zheng        return subprocess.check_call(self._command + cmd, **kwargs)
1840b95ff72SFam Zheng
1854485b04bSFam Zheng    def _do_kill_instances(self, only_known, only_active=True):
1864485b04bSFam Zheng        cmd = ["ps", "-q"]
1874485b04bSFam Zheng        if not only_active:
1884485b04bSFam Zheng            cmd.append("-a")
1894485b04bSFam Zheng        for i in self._output(cmd).split():
1904485b04bSFam Zheng            resp = self._output(["inspect", i])
1914485b04bSFam Zheng            labels = json.loads(resp)[0]["Config"]["Labels"]
1924485b04bSFam Zheng            active = json.loads(resp)[0]["State"]["Running"]
1934485b04bSFam Zheng            if not labels:
1944485b04bSFam Zheng                continue
1954485b04bSFam Zheng            instance_uuid = labels.get("com.qemu.instance.uuid", None)
1964485b04bSFam Zheng            if not instance_uuid:
1974485b04bSFam Zheng                continue
1984485b04bSFam Zheng            if only_known and instance_uuid not in self._instances:
1994485b04bSFam Zheng                continue
200f03868bdSEduardo Habkost            print("Terminating", i)
2014485b04bSFam Zheng            if active:
2024485b04bSFam Zheng                self._do(["kill", i])
2034485b04bSFam Zheng            self._do(["rm", i])
2044485b04bSFam Zheng
2054485b04bSFam Zheng    def clean(self):
2064485b04bSFam Zheng        self._do_kill_instances(False, False)
2074485b04bSFam Zheng        return 0
2084485b04bSFam Zheng
20997cba1a1SFam Zheng    def _kill_instances(self, *args, **kwargs):
2104485b04bSFam Zheng        return self._do_kill_instances(True)
2114485b04bSFam Zheng
2124485b04bSFam Zheng    def _output(self, cmd, **kwargs):
2134485b04bSFam Zheng        return subprocess.check_output(self._command + cmd,
2144485b04bSFam Zheng                                       stderr=subprocess.STDOUT,
2154485b04bSFam Zheng                                       **kwargs)
2164485b04bSFam Zheng
217f97da1f7SAlex Bennée    def inspect_tag(self, tag):
218f97da1f7SAlex Bennée        try:
219f97da1f7SAlex Bennée            return self._output(["inspect", tag])
220f97da1f7SAlex Bennée        except subprocess.CalledProcessError:
221f97da1f7SAlex Bennée            return None
222f97da1f7SAlex Bennée
2237b882245SAlex Bennée    def get_image_creation_time(self, info):
2247b882245SAlex Bennée        return json.loads(info)[0]["Created"]
2257b882245SAlex Bennée
2264485b04bSFam Zheng    def get_image_dockerfile_checksum(self, tag):
227f97da1f7SAlex Bennée        resp = self.inspect_tag(tag)
2284485b04bSFam Zheng        labels = json.loads(resp)[0]["Config"].get("Labels", {})
2294485b04bSFam Zheng        return labels.get("com.qemu.dockerfile-checksum", "")
2304485b04bSFam Zheng
231414a8ce5SAlex Bennée    def build_image(self, tag, docker_dir, dockerfile,
232438d1168SPhilippe Mathieu-Daudé                    quiet=True, user=False, argv=None, extra_files_cksum=[]):
2334485b04bSFam Zheng        if argv == None:
2344485b04bSFam Zheng            argv = []
2354485b04bSFam Zheng
236a9f8d038SAlex Bennée        tmp_df = tempfile.NamedTemporaryFile(dir=docker_dir, suffix=".docker")
2374485b04bSFam Zheng        tmp_df.write(dockerfile)
2384485b04bSFam Zheng
239414a8ce5SAlex Bennée        if user:
240414a8ce5SAlex Bennée            uid = os.getuid()
241414a8ce5SAlex Bennée            uname = getpwuid(uid).pw_name
242414a8ce5SAlex Bennée            tmp_df.write("\n")
243414a8ce5SAlex Bennée            tmp_df.write("RUN id %s 2>/dev/null || useradd -u %d -U %s" %
244414a8ce5SAlex Bennée                         (uname, uid, uname))
245414a8ce5SAlex Bennée
2464485b04bSFam Zheng        tmp_df.write("\n")
2474485b04bSFam Zheng        tmp_df.write("LABEL com.qemu.dockerfile-checksum=%s" %
248f9172822SAlex Bennée                     _text_checksum(_dockerfile_preprocess(dockerfile)))
249f9172822SAlex Bennée        for f, c in extra_files_cksum:
250f9172822SAlex Bennée            tmp_df.write("LABEL com.qemu.%s-checksum=%s" % (f, c))
251f9172822SAlex Bennée
2524485b04bSFam Zheng        tmp_df.flush()
253a9f8d038SAlex Bennée
2540b95ff72SFam Zheng        self._do_check(["build", "-t", tag, "-f", tmp_df.name] + argv + \
255a9f8d038SAlex Bennée                       [docker_dir],
2564485b04bSFam Zheng                       quiet=quiet)
2574485b04bSFam Zheng
2586e733da6SAlex Bennée    def update_image(self, tag, tarball, quiet=True):
2596e733da6SAlex Bennée        "Update a tagged image using "
2606e733da6SAlex Bennée
2610b95ff72SFam Zheng        self._do_check(["build", "-t", tag, "-"], quiet=quiet, stdin=tarball)
2626e733da6SAlex Bennée
2634485b04bSFam Zheng    def image_matches_dockerfile(self, tag, dockerfile):
2644485b04bSFam Zheng        try:
2654485b04bSFam Zheng            checksum = self.get_image_dockerfile_checksum(tag)
2664485b04bSFam Zheng        except Exception:
2674485b04bSFam Zheng            return False
268c1958e9dSFam Zheng        return checksum == _text_checksum(_dockerfile_preprocess(dockerfile))
2694485b04bSFam Zheng
2704485b04bSFam Zheng    def run(self, cmd, keep, quiet):
2714485b04bSFam Zheng        label = uuid.uuid1().hex
2724485b04bSFam Zheng        if not keep:
2734485b04bSFam Zheng            self._instances.append(label)
2740b95ff72SFam Zheng        ret = self._do_check(["run", "--label",
2754485b04bSFam Zheng                             "com.qemu.instance.uuid=" + label] + cmd,
2764485b04bSFam Zheng                             quiet=quiet)
2774485b04bSFam Zheng        if not keep:
2784485b04bSFam Zheng            self._instances.remove(label)
2794485b04bSFam Zheng        return ret
2804485b04bSFam Zheng
2814b08af60SFam Zheng    def command(self, cmd, argv, quiet):
2824b08af60SFam Zheng        return self._do([cmd] + argv, quiet=quiet)
2834b08af60SFam Zheng
2844485b04bSFam Zhengclass SubCommand(object):
2854485b04bSFam Zheng    """A SubCommand template base class"""
2864485b04bSFam Zheng    name = None # Subcommand name
2874485b04bSFam Zheng    def shared_args(self, parser):
2884485b04bSFam Zheng        parser.add_argument("--quiet", action="store_true",
2894485b04bSFam Zheng                            help="Run quietly unless an error occured")
2904485b04bSFam Zheng
2914485b04bSFam Zheng    def args(self, parser):
2924485b04bSFam Zheng        """Setup argument parser"""
2934485b04bSFam Zheng        pass
2944485b04bSFam Zheng    def run(self, args, argv):
2954485b04bSFam Zheng        """Run command.
2964485b04bSFam Zheng        args: parsed argument by argument parser.
2974485b04bSFam Zheng        argv: remaining arguments from sys.argv.
2984485b04bSFam Zheng        """
2994485b04bSFam Zheng        pass
3004485b04bSFam Zheng
3014485b04bSFam Zhengclass RunCommand(SubCommand):
3024485b04bSFam Zheng    """Invoke docker run and take care of cleaning up"""
3034485b04bSFam Zheng    name = "run"
3044485b04bSFam Zheng    def args(self, parser):
3054485b04bSFam Zheng        parser.add_argument("--keep", action="store_true",
3064485b04bSFam Zheng                            help="Don't remove image when command completes")
3074485b04bSFam Zheng    def run(self, args, argv):
3084485b04bSFam Zheng        return Docker().run(argv, args.keep, quiet=args.quiet)
3094485b04bSFam Zheng
3104485b04bSFam Zhengclass BuildCommand(SubCommand):
3114485b04bSFam Zheng    """ Build docker image out of a dockerfile. Arguments: <tag> <dockerfile>"""
3124485b04bSFam Zheng    name = "build"
3134485b04bSFam Zheng    def args(self, parser):
314504ca3c2SAlex Bennée        parser.add_argument("--include-executable", "-e",
315504ca3c2SAlex Bennée                            help="""Specify a binary that will be copied to the
316504ca3c2SAlex Bennée                            container together with all its dependent
317504ca3c2SAlex Bennée                            libraries""")
3184c84f662SPhilippe Mathieu-Daudé        parser.add_argument("--extra-files", "-f", nargs='*',
3194c84f662SPhilippe Mathieu-Daudé                            help="""Specify files that will be copied in the
3204c84f662SPhilippe Mathieu-Daudé                            Docker image, fulfilling the ADD directive from the
3214c84f662SPhilippe Mathieu-Daudé                            Dockerfile""")
322414a8ce5SAlex Bennée        parser.add_argument("--add-current-user", "-u", dest="user",
323414a8ce5SAlex Bennée                            action="store_true",
324414a8ce5SAlex Bennée                            help="Add the current user to image's passwd")
3254485b04bSFam Zheng        parser.add_argument("tag",
3264485b04bSFam Zheng                            help="Image Tag")
3274485b04bSFam Zheng        parser.add_argument("dockerfile",
3284485b04bSFam Zheng                            help="Dockerfile name")
3294485b04bSFam Zheng
3304485b04bSFam Zheng    def run(self, args, argv):
3314485b04bSFam Zheng        dockerfile = open(args.dockerfile, "rb").read()
3324485b04bSFam Zheng        tag = args.tag
3334485b04bSFam Zheng
3344485b04bSFam Zheng        dkr = Docker()
3356fe3ae3fSAlex Bennée        if "--no-cache" not in argv and \
3366fe3ae3fSAlex Bennée           dkr.image_matches_dockerfile(tag, dockerfile):
3374485b04bSFam Zheng            if not args.quiet:
338f03868bdSEduardo Habkost                print("Image is up to date.")
339a9f8d038SAlex Bennée        else:
340a9f8d038SAlex Bennée            # Create a docker context directory for the build
341a9f8d038SAlex Bennée            docker_dir = tempfile.mkdtemp(prefix="docker_build")
3424485b04bSFam Zheng
343*15352decSAlex Bennée            # Validate binfmt_misc will work
344*15352decSAlex Bennée            if args.include_executable:
345*15352decSAlex Bennée                if not _check_binfmt_misc(args.include_executable):
346*15352decSAlex Bennée                    return 1
347*15352decSAlex Bennée
348920776eaSAlex Bennée            # Is there a .pre file to run in the build context?
349920776eaSAlex Bennée            docker_pre = os.path.splitext(args.dockerfile)[0]+".pre"
350920776eaSAlex Bennée            if os.path.exists(docker_pre):
351f8042deaSSascha Silbe                stdout = DEVNULL if args.quiet else None
352920776eaSAlex Bennée                rc = subprocess.call(os.path.realpath(docker_pre),
353f8042deaSSascha Silbe                                     cwd=docker_dir, stdout=stdout)
354920776eaSAlex Bennée                if rc == 3:
355f03868bdSEduardo Habkost                    print("Skip")
356920776eaSAlex Bennée                    return 0
357920776eaSAlex Bennée                elif rc != 0:
358f03868bdSEduardo Habkost                    print("%s exited with code %d" % (docker_pre, rc))
359920776eaSAlex Bennée                    return 1
360920776eaSAlex Bennée
3614c84f662SPhilippe Mathieu-Daudé            # Copy any extra files into the Docker context. These can be
3624c84f662SPhilippe Mathieu-Daudé            # included by the use of the ADD directive in the Dockerfile.
363438d1168SPhilippe Mathieu-Daudé            cksum = []
364504ca3c2SAlex Bennée            if args.include_executable:
365438d1168SPhilippe Mathieu-Daudé                # FIXME: there is no checksum of this executable and the linked
366438d1168SPhilippe Mathieu-Daudé                # libraries, once the image built any change of this executable
367438d1168SPhilippe Mathieu-Daudé                # or any library won't trigger another build.
3684c84f662SPhilippe Mathieu-Daudé                _copy_binary_with_libs(args.include_executable, docker_dir)
3694c84f662SPhilippe Mathieu-Daudé            for filename in args.extra_files or []:
3704c84f662SPhilippe Mathieu-Daudé                _copy_with_mkdir(filename, docker_dir)
371f9172822SAlex Bennée                cksum += [(filename, _file_checksum(filename))]
372504ca3c2SAlex Bennée
37306cc3551SPhilippe Mathieu-Daudé            argv += ["--build-arg=" + k.lower() + "=" + v
37406cc3551SPhilippe Mathieu-Daudé                        for k, v in os.environ.iteritems()
37506cc3551SPhilippe Mathieu-Daudé                        if k.lower() in FILTERED_ENV_NAMES]
376a9f8d038SAlex Bennée            dkr.build_image(tag, docker_dir, dockerfile,
377438d1168SPhilippe Mathieu-Daudé                            quiet=args.quiet, user=args.user, argv=argv,
378438d1168SPhilippe Mathieu-Daudé                            extra_files_cksum=cksum)
379a9f8d038SAlex Bennée
380a9f8d038SAlex Bennée            rmtree(docker_dir)
381a9f8d038SAlex Bennée
3824485b04bSFam Zheng        return 0
3834485b04bSFam Zheng
3846e733da6SAlex Bennéeclass UpdateCommand(SubCommand):
3856e733da6SAlex Bennée    """ Update a docker image with new executables. Arguments: <tag> <executable>"""
3866e733da6SAlex Bennée    name = "update"
3876e733da6SAlex Bennée    def args(self, parser):
3886e733da6SAlex Bennée        parser.add_argument("tag",
3896e733da6SAlex Bennée                            help="Image Tag")
3906e733da6SAlex Bennée        parser.add_argument("executable",
3916e733da6SAlex Bennée                            help="Executable to copy")
3926e733da6SAlex Bennée
3936e733da6SAlex Bennée    def run(self, args, argv):
3946e733da6SAlex Bennée        # Create a temporary tarball with our whole build context and
3956e733da6SAlex Bennée        # dockerfile for the update
3966e733da6SAlex Bennée        tmp = tempfile.NamedTemporaryFile(suffix="dckr.tar.gz")
3976e733da6SAlex Bennée        tmp_tar = TarFile(fileobj=tmp, mode='w')
3986e733da6SAlex Bennée
3996e733da6SAlex Bennée        # Add the executable to the tarball
4006e733da6SAlex Bennée        bn = os.path.basename(args.executable)
4016e733da6SAlex Bennée        ff = "/usr/bin/%s" % bn
4026e733da6SAlex Bennée        tmp_tar.add(args.executable, arcname=ff)
4036e733da6SAlex Bennée
4046e733da6SAlex Bennée        # Add any associated libraries
4056e733da6SAlex Bennée        libs = _get_so_libs(args.executable)
4066e733da6SAlex Bennée        if libs:
4076e733da6SAlex Bennée            for l in libs:
4086e733da6SAlex Bennée                tmp_tar.add(os.path.realpath(l), arcname=l)
4096e733da6SAlex Bennée
4106e733da6SAlex Bennée        # Create a Docker buildfile
4116e733da6SAlex Bennée        df = StringIO()
4126e733da6SAlex Bennée        df.write("FROM %s\n" % args.tag)
4136e733da6SAlex Bennée        df.write("ADD . /\n")
4146e733da6SAlex Bennée        df.seek(0)
4156e733da6SAlex Bennée
4166e733da6SAlex Bennée        df_tar = TarInfo(name="Dockerfile")
4176e733da6SAlex Bennée        df_tar.size = len(df.buf)
4186e733da6SAlex Bennée        tmp_tar.addfile(df_tar, fileobj=df)
4196e733da6SAlex Bennée
4206e733da6SAlex Bennée        tmp_tar.close()
4216e733da6SAlex Bennée
4226e733da6SAlex Bennée        # reset the file pointers
4236e733da6SAlex Bennée        tmp.flush()
4246e733da6SAlex Bennée        tmp.seek(0)
4256e733da6SAlex Bennée
4266e733da6SAlex Bennée        # Run the build with our tarball context
4276e733da6SAlex Bennée        dkr = Docker()
4286e733da6SAlex Bennée        dkr.update_image(args.tag, tmp, quiet=args.quiet)
4296e733da6SAlex Bennée
4306e733da6SAlex Bennée        return 0
4316e733da6SAlex Bennée
4324485b04bSFam Zhengclass CleanCommand(SubCommand):
4334485b04bSFam Zheng    """Clean up docker instances"""
4344485b04bSFam Zheng    name = "clean"
4354485b04bSFam Zheng    def run(self, args, argv):
4364485b04bSFam Zheng        Docker().clean()
4374485b04bSFam Zheng        return 0
4384485b04bSFam Zheng
4394b08af60SFam Zhengclass ImagesCommand(SubCommand):
4404b08af60SFam Zheng    """Run "docker images" command"""
4414b08af60SFam Zheng    name = "images"
4424b08af60SFam Zheng    def run(self, args, argv):
4434b08af60SFam Zheng        return Docker().command("images", argv, args.quiet)
4444b08af60SFam Zheng
44515df9d37SAlex Bennée
44615df9d37SAlex Bennéeclass ProbeCommand(SubCommand):
44715df9d37SAlex Bennée    """Probe if we can run docker automatically"""
44815df9d37SAlex Bennée    name = "probe"
44915df9d37SAlex Bennée
45015df9d37SAlex Bennée    def run(self, args, argv):
45115df9d37SAlex Bennée        try:
45215df9d37SAlex Bennée            docker = Docker()
45315df9d37SAlex Bennée            if docker._command[0] == "docker":
454f03868bdSEduardo Habkost                print("yes")
45515df9d37SAlex Bennée            elif docker._command[0] == "sudo":
456f03868bdSEduardo Habkost                print("sudo")
45715df9d37SAlex Bennée        except Exception:
458f03868bdSEduardo Habkost            print("no")
45915df9d37SAlex Bennée
46015df9d37SAlex Bennée        return
46115df9d37SAlex Bennée
46215df9d37SAlex Bennée
4635e03c2d8SAlex Bennéeclass CcCommand(SubCommand):
4645e03c2d8SAlex Bennée    """Compile sources with cc in images"""
4655e03c2d8SAlex Bennée    name = "cc"
4665e03c2d8SAlex Bennée
4675e03c2d8SAlex Bennée    def args(self, parser):
4685e03c2d8SAlex Bennée        parser.add_argument("--image", "-i", required=True,
4695e03c2d8SAlex Bennée                            help="The docker image in which to run cc")
47099cfdb86SAlex Bennée        parser.add_argument("--cc", default="cc",
47199cfdb86SAlex Bennée                            help="The compiler executable to call")
47250b72738SAlex Bennée        parser.add_argument("--user",
47350b72738SAlex Bennée                            help="The user-id to run under")
4745e03c2d8SAlex Bennée        parser.add_argument("--source-path", "-s", nargs="*", dest="paths",
4755e03c2d8SAlex Bennée                            help="""Extra paths to (ro) mount into container for
4765e03c2d8SAlex Bennée                            reading sources""")
4775e03c2d8SAlex Bennée
4785e03c2d8SAlex Bennée    def run(self, args, argv):
4795e03c2d8SAlex Bennée        if argv and argv[0] == "--":
4805e03c2d8SAlex Bennée            argv = argv[1:]
4815e03c2d8SAlex Bennée        cwd = os.getcwd()
4825e03c2d8SAlex Bennée        cmd = ["--rm", "-w", cwd,
4835e03c2d8SAlex Bennée               "-v", "%s:%s:rw" % (cwd, cwd)]
4845e03c2d8SAlex Bennée        if args.paths:
4855e03c2d8SAlex Bennée            for p in args.paths:
4865e03c2d8SAlex Bennée                cmd += ["-v", "%s:%s:ro,z" % (p, p)]
48750b72738SAlex Bennée        if args.user:
48850b72738SAlex Bennée            cmd += ["-u", args.user]
48999cfdb86SAlex Bennée        cmd += [args.image, args.cc]
4905e03c2d8SAlex Bennée        cmd += argv
4915e03c2d8SAlex Bennée        return Docker().command("run", cmd, args.quiet)
4925e03c2d8SAlex Bennée
4935e03c2d8SAlex Bennée
494f97da1f7SAlex Bennéeclass CheckCommand(SubCommand):
495f97da1f7SAlex Bennée    """Check if we need to re-build a docker image out of a dockerfile.
496f97da1f7SAlex Bennée    Arguments: <tag> <dockerfile>"""
497f97da1f7SAlex Bennée    name = "check"
498f97da1f7SAlex Bennée
499f97da1f7SAlex Bennée    def args(self, parser):
500f97da1f7SAlex Bennée        parser.add_argument("tag",
501f97da1f7SAlex Bennée                            help="Image Tag")
5027b882245SAlex Bennée        parser.add_argument("dockerfile", default=None,
5037b882245SAlex Bennée                            help="Dockerfile name", nargs='?')
5047b882245SAlex Bennée        parser.add_argument("--checktype", choices=["checksum", "age"],
5057b882245SAlex Bennée                            default="checksum", help="check type")
5067b882245SAlex Bennée        parser.add_argument("--olderthan", default=60, type=int,
5077b882245SAlex Bennée                            help="number of minutes")
508f97da1f7SAlex Bennée
509f97da1f7SAlex Bennée    def run(self, args, argv):
510f97da1f7SAlex Bennée        tag = args.tag
511f97da1f7SAlex Bennée
51243e1b2ffSAlex Bennée        try:
513f97da1f7SAlex Bennée            dkr = Docker()
51443e1b2ffSAlex Bennée        except:
51543e1b2ffSAlex Bennée            print("Docker not set up")
51643e1b2ffSAlex Bennée            return 1
51743e1b2ffSAlex Bennée
518f97da1f7SAlex Bennée        info = dkr.inspect_tag(tag)
519f97da1f7SAlex Bennée        if info is None:
520f97da1f7SAlex Bennée            print("Image does not exist")
521f97da1f7SAlex Bennée            return 1
522f97da1f7SAlex Bennée
5237b882245SAlex Bennée        if args.checktype == "checksum":
5247b882245SAlex Bennée            if not args.dockerfile:
5257b882245SAlex Bennée                print("Need a dockerfile for tag:%s" % (tag))
5267b882245SAlex Bennée                return 1
5277b882245SAlex Bennée
5287b882245SAlex Bennée            dockerfile = open(args.dockerfile, "rb").read()
5297b882245SAlex Bennée
530f97da1f7SAlex Bennée            if dkr.image_matches_dockerfile(tag, dockerfile):
531f97da1f7SAlex Bennée                if not args.quiet:
532f97da1f7SAlex Bennée                    print("Image is up to date")
533f97da1f7SAlex Bennée                return 0
534f97da1f7SAlex Bennée            else:
535f97da1f7SAlex Bennée                print("Image needs updating")
536f97da1f7SAlex Bennée                return 1
5377b882245SAlex Bennée        elif args.checktype == "age":
5387b882245SAlex Bennée            timestr = dkr.get_image_creation_time(info).split(".")[0]
5397b882245SAlex Bennée            created = datetime.strptime(timestr, "%Y-%m-%dT%H:%M:%S")
5407b882245SAlex Bennée            past = datetime.now() - timedelta(minutes=args.olderthan)
5417b882245SAlex Bennée            if created < past:
5427b882245SAlex Bennée                print ("Image created @ %s more than %d minutes old" %
5437b882245SAlex Bennée                       (timestr, args.olderthan))
5447b882245SAlex Bennée                return 1
5457b882245SAlex Bennée            else:
5467b882245SAlex Bennée                if not args.quiet:
5477b882245SAlex Bennée                    print ("Image less than %d minutes old" % (args.olderthan))
5487b882245SAlex Bennée                return 0
549f97da1f7SAlex Bennée
550f97da1f7SAlex Bennée
5514485b04bSFam Zhengdef main():
5524485b04bSFam Zheng    parser = argparse.ArgumentParser(description="A Docker helper",
5534485b04bSFam Zheng            usage="%s <subcommand> ..." % os.path.basename(sys.argv[0]))
5544485b04bSFam Zheng    subparsers = parser.add_subparsers(title="subcommands", help=None)
5554485b04bSFam Zheng    for cls in SubCommand.__subclasses__():
5564485b04bSFam Zheng        cmd = cls()
5574485b04bSFam Zheng        subp = subparsers.add_parser(cmd.name, help=cmd.__doc__)
5584485b04bSFam Zheng        cmd.shared_args(subp)
5594485b04bSFam Zheng        cmd.args(subp)
5604485b04bSFam Zheng        subp.set_defaults(cmdobj=cmd)
5614485b04bSFam Zheng    args, argv = parser.parse_known_args()
5624485b04bSFam Zheng    return args.cmdobj.run(args, argv)
5634485b04bSFam Zheng
5644485b04bSFam Zhengif __name__ == "__main__":
5654485b04bSFam Zheng    sys.exit(main())
566