xref: /qemu/tests/docker/docker.py (revision 2461d80e6c36dfefdcde1ec8735c317c31895c6b)
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
46432d8ad5SAlex Bennée
47438d1168SPhilippe Mathieu-Daudédef _file_checksum(filename):
48438d1168SPhilippe Mathieu-Daudé    return _text_checksum(open(filename, 'rb').read())
49438d1168SPhilippe Mathieu-Daudé
50432d8ad5SAlex Bennée
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])
64432d8ad5SAlex Bennée    raise Exception("Cannot find working docker command. Tried:\n%s" %
654485b04bSFam Zheng                    commands_txt)
664485b04bSFam Zheng
67432d8ad5SAlex Bennée
682499ee9fSPhilippe Mathieu-Daudédef _copy_with_mkdir(src, root_dir, sub_path='.'):
69504ca3c2SAlex Bennée    """Copy src into root_dir, creating sub_path as needed."""
70504ca3c2SAlex Bennée    dest_dir = os.path.normpath("%s/%s" % (root_dir, sub_path))
71504ca3c2SAlex Bennée    try:
72504ca3c2SAlex Bennée        os.makedirs(dest_dir)
73504ca3c2SAlex Bennée    except OSError:
74504ca3c2SAlex Bennée        # we can safely ignore already created directories
75504ca3c2SAlex Bennée        pass
76504ca3c2SAlex Bennée
77504ca3c2SAlex Bennée    dest_file = "%s/%s" % (dest_dir, os.path.basename(src))
78504ca3c2SAlex Bennée    copy(src, dest_file)
79504ca3c2SAlex Bennée
80504ca3c2SAlex Bennée
81504ca3c2SAlex Bennéedef _get_so_libs(executable):
82504ca3c2SAlex Bennée    """Return a list of libraries associated with an executable.
83504ca3c2SAlex Bennée
84504ca3c2SAlex Bennée    The paths may be symbolic links which would need to be resolved to
85504ca3c2SAlex Bennée    ensure theright data is copied."""
86504ca3c2SAlex Bennée
87504ca3c2SAlex Bennée    libs = []
88504ca3c2SAlex Bennée    ldd_re = re.compile(r"(/.*/)(\S*)")
89504ca3c2SAlex Bennée    try:
90504ca3c2SAlex Bennée        ldd_output = subprocess.check_output(["ldd", executable])
91504ca3c2SAlex Bennée        for line in ldd_output.split("\n"):
92504ca3c2SAlex Bennée            search = ldd_re.search(line)
93504ca3c2SAlex Bennée            if search and len(search.groups()) == 2:
94504ca3c2SAlex Bennée                so_path = search.groups()[0]
95504ca3c2SAlex Bennée                so_lib = search.groups()[1]
96504ca3c2SAlex Bennée                libs.append("%s/%s" % (so_path, so_lib))
97504ca3c2SAlex Bennée    except subprocess.CalledProcessError:
98f03868bdSEduardo Habkost        print("%s had no associated libraries (static build?)" % (executable))
99504ca3c2SAlex Bennée
100504ca3c2SAlex Bennée    return libs
101504ca3c2SAlex Bennée
102432d8ad5SAlex Bennée
103d10404b1SAlex Bennéedef _copy_binary_with_libs(src, bin_dest, dest_dir):
104d10404b1SAlex Bennée    """Maybe copy a binary and all its dependent libraries.
105d10404b1SAlex Bennée
106d10404b1SAlex Bennée    If bin_dest isn't set we only copy the support libraries because
107d10404b1SAlex Bennée    we don't need qemu in the docker path to run (due to persistent
108d10404b1SAlex Bennée    mapping). Indeed users may get confused if we aren't running what
109d10404b1SAlex Bennée    is in the image.
110504ca3c2SAlex Bennée
111504ca3c2SAlex Bennée    This does rely on the host file-system being fairly multi-arch
112d10404b1SAlex Bennée    aware so the file don't clash with the guests layout.
113d10404b1SAlex Bennée    """
114504ca3c2SAlex Bennée
115d10404b1SAlex Bennée    if bin_dest:
116d10404b1SAlex Bennée        _copy_with_mkdir(src, dest_dir, os.path.dirname(bin_dest))
117d10404b1SAlex Bennée    else:
118d10404b1SAlex Bennée        print("only copying support libraries for %s" % (src))
119504ca3c2SAlex Bennée
120504ca3c2SAlex Bennée    libs = _get_so_libs(src)
121504ca3c2SAlex Bennée    if libs:
122504ca3c2SAlex Bennée        for l in libs:
123504ca3c2SAlex Bennée            so_path = os.path.dirname(l)
124504ca3c2SAlex Bennée            _copy_with_mkdir(l, dest_dir, so_path)
125504ca3c2SAlex Bennée
12615352decSAlex Bennée
12715352decSAlex Bennéedef _check_binfmt_misc(executable):
12815352decSAlex Bennée    """Check binfmt_misc has entry for executable in the right place.
12915352decSAlex Bennée
13015352decSAlex Bennée    The details of setting up binfmt_misc are outside the scope of
13115352decSAlex Bennée    this script but we should at least fail early with a useful
132d10404b1SAlex Bennée    message if it won't work.
133d10404b1SAlex Bennée
134d10404b1SAlex Bennée    Returns the configured binfmt path and a valid flag. For
135d10404b1SAlex Bennée    persistent configurations we will still want to copy and dependent
136d10404b1SAlex Bennée    libraries.
137d10404b1SAlex Bennée    """
13815352decSAlex Bennée
13915352decSAlex Bennée    binary = os.path.basename(executable)
14015352decSAlex Bennée    binfmt_entry = "/proc/sys/fs/binfmt_misc/%s" % (binary)
14115352decSAlex Bennée
14215352decSAlex Bennée    if not os.path.exists(binfmt_entry):
14315352decSAlex Bennée        print ("No binfmt_misc entry for %s" % (binary))
144d10404b1SAlex Bennée        return None, False
14515352decSAlex Bennée
14615352decSAlex Bennée    with open(binfmt_entry) as x: entry = x.read()
14715352decSAlex Bennée
14843c898b7SAlex Bennée    if re.search("flags:.*F.*\n", entry):
149432d8ad5SAlex Bennée        print("binfmt_misc for %s uses persistent(F) mapping to host binary" %
15043c898b7SAlex Bennée              (binary))
151d10404b1SAlex Bennée        return None, True
15243c898b7SAlex Bennée
1537e81d198SAlex Bennée    m = re.search("interpreter (\S+)\n", entry)
1547e81d198SAlex Bennée    interp = m.group(1)
1557e81d198SAlex Bennée    if interp and interp != executable:
1567e81d198SAlex Bennée        print("binfmt_misc for %s does not point to %s, using %s" %
1577e81d198SAlex Bennée              (binary, executable, interp))
15815352decSAlex Bennée
159d10404b1SAlex Bennée    return interp, True
160d10404b1SAlex Bennée
16115352decSAlex Bennée
162c1958e9dSFam Zhengdef _read_qemu_dockerfile(img_name):
163547cb45eSAlex Bennée    # special case for Debian linux-user images
164547cb45eSAlex Bennée    if img_name.startswith("debian") and img_name.endswith("user"):
165547cb45eSAlex Bennée        img_name = "debian-bootstrap"
166547cb45eSAlex Bennée
167c1958e9dSFam Zheng    df = os.path.join(os.path.dirname(__file__), "dockerfiles",
168c1958e9dSFam Zheng                      img_name + ".docker")
169c1958e9dSFam Zheng    return open(df, "r").read()
170c1958e9dSFam Zheng
171432d8ad5SAlex Bennée
172c1958e9dSFam Zhengdef _dockerfile_preprocess(df):
173c1958e9dSFam Zheng    out = ""
174c1958e9dSFam Zheng    for l in df.splitlines():
175c1958e9dSFam Zheng        if len(l.strip()) == 0 or l.startswith("#"):
176c1958e9dSFam Zheng            continue
177c1958e9dSFam Zheng        from_pref = "FROM qemu:"
178c1958e9dSFam Zheng        if l.startswith(from_pref):
179c1958e9dSFam Zheng            # TODO: Alternatively we could replace this line with "FROM $ID"
180c1958e9dSFam Zheng            # where $ID is the image's hex id obtained with
181c1958e9dSFam Zheng            #    $ docker images $IMAGE --format="{{.Id}}"
182c1958e9dSFam Zheng            # but unfortunately that's not supported by RHEL 7.
183c1958e9dSFam Zheng            inlining = _read_qemu_dockerfile(l[len(from_pref):])
184c1958e9dSFam Zheng            out += _dockerfile_preprocess(inlining)
185c1958e9dSFam Zheng            continue
186c1958e9dSFam Zheng        out += l + "\n"
187c1958e9dSFam Zheng    return out
188c1958e9dSFam Zheng
189432d8ad5SAlex Bennée
1904485b04bSFam Zhengclass Docker(object):
1914485b04bSFam Zheng    """ Running Docker commands """
1924485b04bSFam Zheng    def __init__(self):
1934485b04bSFam Zheng        self._command = _guess_docker_command()
1944485b04bSFam Zheng        self._instances = []
1954485b04bSFam Zheng        atexit.register(self._kill_instances)
19697cba1a1SFam Zheng        signal.signal(signal.SIGTERM, self._kill_instances)
19797cba1a1SFam Zheng        signal.signal(signal.SIGHUP, self._kill_instances)
1984485b04bSFam Zheng
19958bf7b6dSFam Zheng    def _do(self, cmd, quiet=True, **kwargs):
2004485b04bSFam Zheng        if quiet:
201c9772570SSascha Silbe            kwargs["stdout"] = DEVNULL
2024485b04bSFam Zheng        return subprocess.call(self._command + cmd, **kwargs)
2034485b04bSFam Zheng
2040b95ff72SFam Zheng    def _do_check(self, cmd, quiet=True, **kwargs):
2050b95ff72SFam Zheng        if quiet:
2060b95ff72SFam Zheng            kwargs["stdout"] = DEVNULL
2070b95ff72SFam Zheng        return subprocess.check_call(self._command + cmd, **kwargs)
2080b95ff72SFam Zheng
2094485b04bSFam Zheng    def _do_kill_instances(self, only_known, only_active=True):
2104485b04bSFam Zheng        cmd = ["ps", "-q"]
2114485b04bSFam Zheng        if not only_active:
2124485b04bSFam Zheng            cmd.append("-a")
2134485b04bSFam Zheng        for i in self._output(cmd).split():
2144485b04bSFam Zheng            resp = self._output(["inspect", i])
2154485b04bSFam Zheng            labels = json.loads(resp)[0]["Config"]["Labels"]
2164485b04bSFam Zheng            active = json.loads(resp)[0]["State"]["Running"]
2174485b04bSFam Zheng            if not labels:
2184485b04bSFam Zheng                continue
2194485b04bSFam Zheng            instance_uuid = labels.get("com.qemu.instance.uuid", None)
2204485b04bSFam Zheng            if not instance_uuid:
2214485b04bSFam Zheng                continue
2224485b04bSFam Zheng            if only_known and instance_uuid not in self._instances:
2234485b04bSFam Zheng                continue
224f03868bdSEduardo Habkost            print("Terminating", i)
2254485b04bSFam Zheng            if active:
2264485b04bSFam Zheng                self._do(["kill", i])
2274485b04bSFam Zheng            self._do(["rm", i])
2284485b04bSFam Zheng
2294485b04bSFam Zheng    def clean(self):
2304485b04bSFam Zheng        self._do_kill_instances(False, False)
2314485b04bSFam Zheng        return 0
2324485b04bSFam Zheng
23397cba1a1SFam Zheng    def _kill_instances(self, *args, **kwargs):
2344485b04bSFam Zheng        return self._do_kill_instances(True)
2354485b04bSFam Zheng
2364485b04bSFam Zheng    def _output(self, cmd, **kwargs):
2374485b04bSFam Zheng        return subprocess.check_output(self._command + cmd,
2384485b04bSFam Zheng                                       stderr=subprocess.STDOUT,
2394485b04bSFam Zheng                                       **kwargs)
2404485b04bSFam Zheng
241f97da1f7SAlex Bennée    def inspect_tag(self, tag):
242f97da1f7SAlex Bennée        try:
243f97da1f7SAlex Bennée            return self._output(["inspect", tag])
244f97da1f7SAlex Bennée        except subprocess.CalledProcessError:
245f97da1f7SAlex Bennée            return None
246f97da1f7SAlex Bennée
2477b882245SAlex Bennée    def get_image_creation_time(self, info):
2487b882245SAlex Bennée        return json.loads(info)[0]["Created"]
2497b882245SAlex Bennée
2504485b04bSFam Zheng    def get_image_dockerfile_checksum(self, tag):
251f97da1f7SAlex Bennée        resp = self.inspect_tag(tag)
2524485b04bSFam Zheng        labels = json.loads(resp)[0]["Config"].get("Labels", {})
2534485b04bSFam Zheng        return labels.get("com.qemu.dockerfile-checksum", "")
2544485b04bSFam Zheng
255414a8ce5SAlex Bennée    def build_image(self, tag, docker_dir, dockerfile,
256438d1168SPhilippe Mathieu-Daudé                    quiet=True, user=False, argv=None, extra_files_cksum=[]):
257432d8ad5SAlex Bennée        if argv is None:
2584485b04bSFam Zheng            argv = []
2594485b04bSFam Zheng
260a9f8d038SAlex Bennée        tmp_df = tempfile.NamedTemporaryFile(dir=docker_dir, suffix=".docker")
2614485b04bSFam Zheng        tmp_df.write(dockerfile)
2624485b04bSFam Zheng
263414a8ce5SAlex Bennée        if user:
264414a8ce5SAlex Bennée            uid = os.getuid()
265414a8ce5SAlex Bennée            uname = getpwuid(uid).pw_name
266414a8ce5SAlex Bennée            tmp_df.write("\n")
267414a8ce5SAlex Bennée            tmp_df.write("RUN id %s 2>/dev/null || useradd -u %d -U %s" %
268414a8ce5SAlex Bennée                         (uname, uid, uname))
269414a8ce5SAlex Bennée
2704485b04bSFam Zheng        tmp_df.write("\n")
2714485b04bSFam Zheng        tmp_df.write("LABEL com.qemu.dockerfile-checksum=%s" %
272f9172822SAlex Bennée                     _text_checksum(_dockerfile_preprocess(dockerfile)))
273f9172822SAlex Bennée        for f, c in extra_files_cksum:
274f9172822SAlex Bennée            tmp_df.write("LABEL com.qemu.%s-checksum=%s" % (f, c))
275f9172822SAlex Bennée
2764485b04bSFam Zheng        tmp_df.flush()
277a9f8d038SAlex Bennée
278432d8ad5SAlex Bennée        self._do_check(["build", "-t", tag, "-f", tmp_df.name] + argv +
279a9f8d038SAlex Bennée                       [docker_dir],
2804485b04bSFam Zheng                       quiet=quiet)
2814485b04bSFam Zheng
2826e733da6SAlex Bennée    def update_image(self, tag, tarball, quiet=True):
2836e733da6SAlex Bennée        "Update a tagged image using "
2846e733da6SAlex Bennée
2850b95ff72SFam Zheng        self._do_check(["build", "-t", tag, "-"], quiet=quiet, stdin=tarball)
2866e733da6SAlex Bennée
2874485b04bSFam Zheng    def image_matches_dockerfile(self, tag, dockerfile):
2884485b04bSFam Zheng        try:
2894485b04bSFam Zheng            checksum = self.get_image_dockerfile_checksum(tag)
2904485b04bSFam Zheng        except Exception:
2914485b04bSFam Zheng            return False
292c1958e9dSFam Zheng        return checksum == _text_checksum(_dockerfile_preprocess(dockerfile))
2934485b04bSFam Zheng
2944485b04bSFam Zheng    def run(self, cmd, keep, quiet):
2954485b04bSFam Zheng        label = uuid.uuid1().hex
2964485b04bSFam Zheng        if not keep:
2974485b04bSFam Zheng            self._instances.append(label)
2980b95ff72SFam Zheng        ret = self._do_check(["run", "--label",
2994485b04bSFam Zheng                             "com.qemu.instance.uuid=" + label] + cmd,
3004485b04bSFam Zheng                             quiet=quiet)
3014485b04bSFam Zheng        if not keep:
3024485b04bSFam Zheng            self._instances.remove(label)
3034485b04bSFam Zheng        return ret
3044485b04bSFam Zheng
3054b08af60SFam Zheng    def command(self, cmd, argv, quiet):
3064b08af60SFam Zheng        return self._do([cmd] + argv, quiet=quiet)
3074b08af60SFam Zheng
308432d8ad5SAlex Bennée
3094485b04bSFam Zhengclass SubCommand(object):
3104485b04bSFam Zheng    """A SubCommand template base class"""
3114485b04bSFam Zheng    name = None  # Subcommand name
312432d8ad5SAlex Bennée
3134485b04bSFam Zheng    def shared_args(self, parser):
3144485b04bSFam Zheng        parser.add_argument("--quiet", action="store_true",
315e50a6121SStefan Weil                            help="Run quietly unless an error occurred")
3164485b04bSFam Zheng
3174485b04bSFam Zheng    def args(self, parser):
3184485b04bSFam Zheng        """Setup argument parser"""
3194485b04bSFam Zheng        pass
320432d8ad5SAlex Bennée
3214485b04bSFam Zheng    def run(self, args, argv):
3224485b04bSFam Zheng        """Run command.
3234485b04bSFam Zheng        args: parsed argument by argument parser.
3244485b04bSFam Zheng        argv: remaining arguments from sys.argv.
3254485b04bSFam Zheng        """
3264485b04bSFam Zheng        pass
3274485b04bSFam Zheng
328432d8ad5SAlex Bennée
3294485b04bSFam Zhengclass RunCommand(SubCommand):
3304485b04bSFam Zheng    """Invoke docker run and take care of cleaning up"""
3314485b04bSFam Zheng    name = "run"
332432d8ad5SAlex Bennée
3334485b04bSFam Zheng    def args(self, parser):
3344485b04bSFam Zheng        parser.add_argument("--keep", action="store_true",
3354485b04bSFam Zheng                            help="Don't remove image when command completes")
336*2461d80eSMarc-André Lureau        parser.add_argument("--run-as-current-user", action="store_true",
337*2461d80eSMarc-André Lureau                            help="Run container using the current user's uid")
338432d8ad5SAlex Bennée
3394485b04bSFam Zheng    def run(self, args, argv):
340*2461d80eSMarc-André Lureau        if args.run_as_current_user:
341*2461d80eSMarc-André Lureau            uid = os.getuid()
342*2461d80eSMarc-André Lureau            argv = [ "-u", str(uid) ] + argv
3434485b04bSFam Zheng        return Docker().run(argv, args.keep, quiet=args.quiet)
3444485b04bSFam Zheng
345432d8ad5SAlex Bennée
3464485b04bSFam Zhengclass BuildCommand(SubCommand):
347432d8ad5SAlex Bennée    """ Build docker image out of a dockerfile. Arg: <tag> <dockerfile>"""
3484485b04bSFam Zheng    name = "build"
349432d8ad5SAlex Bennée
3504485b04bSFam Zheng    def args(self, parser):
351504ca3c2SAlex Bennée        parser.add_argument("--include-executable", "-e",
352504ca3c2SAlex Bennée                            help="""Specify a binary that will be copied to the
353504ca3c2SAlex Bennée                            container together with all its dependent
354504ca3c2SAlex Bennée                            libraries""")
3554c84f662SPhilippe Mathieu-Daudé        parser.add_argument("--extra-files", "-f", nargs='*',
3564c84f662SPhilippe Mathieu-Daudé                            help="""Specify files that will be copied in the
3574c84f662SPhilippe Mathieu-Daudé                            Docker image, fulfilling the ADD directive from the
3584c84f662SPhilippe Mathieu-Daudé                            Dockerfile""")
359414a8ce5SAlex Bennée        parser.add_argument("--add-current-user", "-u", dest="user",
360414a8ce5SAlex Bennée                            action="store_true",
361414a8ce5SAlex Bennée                            help="Add the current user to image's passwd")
3624485b04bSFam Zheng        parser.add_argument("tag",
3634485b04bSFam Zheng                            help="Image Tag")
3644485b04bSFam Zheng        parser.add_argument("dockerfile",
3654485b04bSFam Zheng                            help="Dockerfile name")
3664485b04bSFam Zheng
3674485b04bSFam Zheng    def run(self, args, argv):
3684485b04bSFam Zheng        dockerfile = open(args.dockerfile, "rb").read()
3694485b04bSFam Zheng        tag = args.tag
3704485b04bSFam Zheng
3714485b04bSFam Zheng        dkr = Docker()
3726fe3ae3fSAlex Bennée        if "--no-cache" not in argv and \
3736fe3ae3fSAlex Bennée           dkr.image_matches_dockerfile(tag, dockerfile):
3744485b04bSFam Zheng            if not args.quiet:
375f03868bdSEduardo Habkost                print("Image is up to date.")
376a9f8d038SAlex Bennée        else:
377a9f8d038SAlex Bennée            # Create a docker context directory for the build
378a9f8d038SAlex Bennée            docker_dir = tempfile.mkdtemp(prefix="docker_build")
3794485b04bSFam Zheng
38015352decSAlex Bennée            # Validate binfmt_misc will work
38115352decSAlex Bennée            if args.include_executable:
382d10404b1SAlex Bennée                qpath, enabled = _check_binfmt_misc(args.include_executable)
383d10404b1SAlex Bennée                if not enabled:
38415352decSAlex Bennée                    return 1
38515352decSAlex Bennée
386920776eaSAlex Bennée            # Is there a .pre file to run in the build context?
387920776eaSAlex Bennée            docker_pre = os.path.splitext(args.dockerfile)[0]+".pre"
388920776eaSAlex Bennée            if os.path.exists(docker_pre):
389f8042deaSSascha Silbe                stdout = DEVNULL if args.quiet else None
390920776eaSAlex Bennée                rc = subprocess.call(os.path.realpath(docker_pre),
391f8042deaSSascha Silbe                                     cwd=docker_dir, stdout=stdout)
392920776eaSAlex Bennée                if rc == 3:
393f03868bdSEduardo Habkost                    print("Skip")
394920776eaSAlex Bennée                    return 0
395920776eaSAlex Bennée                elif rc != 0:
396f03868bdSEduardo Habkost                    print("%s exited with code %d" % (docker_pre, rc))
397920776eaSAlex Bennée                    return 1
398920776eaSAlex Bennée
3994c84f662SPhilippe Mathieu-Daudé            # Copy any extra files into the Docker context. These can be
4004c84f662SPhilippe Mathieu-Daudé            # included by the use of the ADD directive in the Dockerfile.
401438d1168SPhilippe Mathieu-Daudé            cksum = []
402504ca3c2SAlex Bennée            if args.include_executable:
403438d1168SPhilippe Mathieu-Daudé                # FIXME: there is no checksum of this executable and the linked
404438d1168SPhilippe Mathieu-Daudé                # libraries, once the image built any change of this executable
405438d1168SPhilippe Mathieu-Daudé                # or any library won't trigger another build.
406d10404b1SAlex Bennée                _copy_binary_with_libs(args.include_executable,
407d10404b1SAlex Bennée                                       qpath, docker_dir)
408d10404b1SAlex Bennée
4094c84f662SPhilippe Mathieu-Daudé            for filename in args.extra_files or []:
4104c84f662SPhilippe Mathieu-Daudé                _copy_with_mkdir(filename, docker_dir)
411f9172822SAlex Bennée                cksum += [(filename, _file_checksum(filename))]
412504ca3c2SAlex Bennée
41306cc3551SPhilippe Mathieu-Daudé            argv += ["--build-arg=" + k.lower() + "=" + v
41406cc3551SPhilippe Mathieu-Daudé                     for k, v in os.environ.iteritems()
41506cc3551SPhilippe Mathieu-Daudé                     if k.lower() in FILTERED_ENV_NAMES]
416a9f8d038SAlex Bennée            dkr.build_image(tag, docker_dir, dockerfile,
417438d1168SPhilippe Mathieu-Daudé                            quiet=args.quiet, user=args.user, argv=argv,
418438d1168SPhilippe Mathieu-Daudé                            extra_files_cksum=cksum)
419a9f8d038SAlex Bennée
420a9f8d038SAlex Bennée            rmtree(docker_dir)
421a9f8d038SAlex Bennée
4224485b04bSFam Zheng        return 0
4234485b04bSFam Zheng
424432d8ad5SAlex Bennée
4256e733da6SAlex Bennéeclass UpdateCommand(SubCommand):
426432d8ad5SAlex Bennée    """ Update a docker image with new executables. Args: <tag> <executable>"""
4276e733da6SAlex Bennée    name = "update"
428432d8ad5SAlex Bennée
4296e733da6SAlex Bennée    def args(self, parser):
4306e733da6SAlex Bennée        parser.add_argument("tag",
4316e733da6SAlex Bennée                            help="Image Tag")
4326e733da6SAlex Bennée        parser.add_argument("executable",
4336e733da6SAlex Bennée                            help="Executable to copy")
4346e733da6SAlex Bennée
4356e733da6SAlex Bennée    def run(self, args, argv):
4366e733da6SAlex Bennée        # Create a temporary tarball with our whole build context and
4376e733da6SAlex Bennée        # dockerfile for the update
4386e733da6SAlex Bennée        tmp = tempfile.NamedTemporaryFile(suffix="dckr.tar.gz")
4396e733da6SAlex Bennée        tmp_tar = TarFile(fileobj=tmp, mode='w')
4406e733da6SAlex Bennée
4417e81d198SAlex Bennée        # Add the executable to the tarball, using the current
442d10404b1SAlex Bennée        # configured binfmt_misc path. If we don't get a path then we
443d10404b1SAlex Bennée        # only need the support libraries copied
444d10404b1SAlex Bennée        ff, enabled = _check_binfmt_misc(args.executable)
4457e81d198SAlex Bennée
446d10404b1SAlex Bennée        if not enabled:
447d10404b1SAlex Bennée            print("binfmt_misc not enabled, update disabled")
448d10404b1SAlex Bennée            return 1
449d10404b1SAlex Bennée
450d10404b1SAlex Bennée        if ff:
4516e733da6SAlex Bennée            tmp_tar.add(args.executable, arcname=ff)
4526e733da6SAlex Bennée
4536e733da6SAlex Bennée        # Add any associated libraries
4546e733da6SAlex Bennée        libs = _get_so_libs(args.executable)
4556e733da6SAlex Bennée        if libs:
4566e733da6SAlex Bennée            for l in libs:
4576e733da6SAlex Bennée                tmp_tar.add(os.path.realpath(l), arcname=l)
4586e733da6SAlex Bennée
4596e733da6SAlex Bennée        # Create a Docker buildfile
4606e733da6SAlex Bennée        df = StringIO()
4616e733da6SAlex Bennée        df.write("FROM %s\n" % args.tag)
4626e733da6SAlex Bennée        df.write("ADD . /\n")
4636e733da6SAlex Bennée        df.seek(0)
4646e733da6SAlex Bennée
4656e733da6SAlex Bennée        df_tar = TarInfo(name="Dockerfile")
4666e733da6SAlex Bennée        df_tar.size = len(df.buf)
4676e733da6SAlex Bennée        tmp_tar.addfile(df_tar, fileobj=df)
4686e733da6SAlex Bennée
4696e733da6SAlex Bennée        tmp_tar.close()
4706e733da6SAlex Bennée
4716e733da6SAlex Bennée        # reset the file pointers
4726e733da6SAlex Bennée        tmp.flush()
4736e733da6SAlex Bennée        tmp.seek(0)
4746e733da6SAlex Bennée
4756e733da6SAlex Bennée        # Run the build with our tarball context
4766e733da6SAlex Bennée        dkr = Docker()
4776e733da6SAlex Bennée        dkr.update_image(args.tag, tmp, quiet=args.quiet)
4786e733da6SAlex Bennée
4796e733da6SAlex Bennée        return 0
4806e733da6SAlex Bennée
481432d8ad5SAlex Bennée
4824485b04bSFam Zhengclass CleanCommand(SubCommand):
4834485b04bSFam Zheng    """Clean up docker instances"""
4844485b04bSFam Zheng    name = "clean"
485432d8ad5SAlex Bennée
4864485b04bSFam Zheng    def run(self, args, argv):
4874485b04bSFam Zheng        Docker().clean()
4884485b04bSFam Zheng        return 0
4894485b04bSFam Zheng
490432d8ad5SAlex Bennée
4914b08af60SFam Zhengclass ImagesCommand(SubCommand):
4924b08af60SFam Zheng    """Run "docker images" command"""
4934b08af60SFam Zheng    name = "images"
494432d8ad5SAlex Bennée
4954b08af60SFam Zheng    def run(self, args, argv):
4964b08af60SFam Zheng        return Docker().command("images", argv, args.quiet)
4974b08af60SFam Zheng
49815df9d37SAlex Bennée
49915df9d37SAlex Bennéeclass ProbeCommand(SubCommand):
50015df9d37SAlex Bennée    """Probe if we can run docker automatically"""
50115df9d37SAlex Bennée    name = "probe"
50215df9d37SAlex Bennée
50315df9d37SAlex Bennée    def run(self, args, argv):
50415df9d37SAlex Bennée        try:
50515df9d37SAlex Bennée            docker = Docker()
50615df9d37SAlex Bennée            if docker._command[0] == "docker":
507f03868bdSEduardo Habkost                print("yes")
50815df9d37SAlex Bennée            elif docker._command[0] == "sudo":
509f03868bdSEduardo Habkost                print("sudo")
51015df9d37SAlex Bennée        except Exception:
511f03868bdSEduardo Habkost            print("no")
51215df9d37SAlex Bennée
51315df9d37SAlex Bennée        return
51415df9d37SAlex Bennée
51515df9d37SAlex Bennée
5165e03c2d8SAlex Bennéeclass CcCommand(SubCommand):
5175e03c2d8SAlex Bennée    """Compile sources with cc in images"""
5185e03c2d8SAlex Bennée    name = "cc"
5195e03c2d8SAlex Bennée
5205e03c2d8SAlex Bennée    def args(self, parser):
5215e03c2d8SAlex Bennée        parser.add_argument("--image", "-i", required=True,
5225e03c2d8SAlex Bennée                            help="The docker image in which to run cc")
52399cfdb86SAlex Bennée        parser.add_argument("--cc", default="cc",
52499cfdb86SAlex Bennée                            help="The compiler executable to call")
52550b72738SAlex Bennée        parser.add_argument("--user",
52650b72738SAlex Bennée                            help="The user-id to run under")
5275e03c2d8SAlex Bennée        parser.add_argument("--source-path", "-s", nargs="*", dest="paths",
5285e03c2d8SAlex Bennée                            help="""Extra paths to (ro) mount into container for
5295e03c2d8SAlex Bennée                            reading sources""")
5305e03c2d8SAlex Bennée
5315e03c2d8SAlex Bennée    def run(self, args, argv):
5325e03c2d8SAlex Bennée        if argv and argv[0] == "--":
5335e03c2d8SAlex Bennée            argv = argv[1:]
5345e03c2d8SAlex Bennée        cwd = os.getcwd()
5355e03c2d8SAlex Bennée        cmd = ["--rm", "-w", cwd,
5365e03c2d8SAlex Bennée               "-v", "%s:%s:rw" % (cwd, cwd)]
5375e03c2d8SAlex Bennée        if args.paths:
5385e03c2d8SAlex Bennée            for p in args.paths:
5395e03c2d8SAlex Bennée                cmd += ["-v", "%s:%s:ro,z" % (p, p)]
54050b72738SAlex Bennée        if args.user:
54150b72738SAlex Bennée            cmd += ["-u", args.user]
54299cfdb86SAlex Bennée        cmd += [args.image, args.cc]
5435e03c2d8SAlex Bennée        cmd += argv
5445e03c2d8SAlex Bennée        return Docker().command("run", cmd, args.quiet)
5455e03c2d8SAlex Bennée
5465e03c2d8SAlex Bennée
547f97da1f7SAlex Bennéeclass CheckCommand(SubCommand):
548f97da1f7SAlex Bennée    """Check if we need to re-build a docker image out of a dockerfile.
549f97da1f7SAlex Bennée    Arguments: <tag> <dockerfile>"""
550f97da1f7SAlex Bennée    name = "check"
551f97da1f7SAlex Bennée
552f97da1f7SAlex Bennée    def args(self, parser):
553f97da1f7SAlex Bennée        parser.add_argument("tag",
554f97da1f7SAlex Bennée                            help="Image Tag")
5557b882245SAlex Bennée        parser.add_argument("dockerfile", default=None,
5567b882245SAlex Bennée                            help="Dockerfile name", nargs='?')
5577b882245SAlex Bennée        parser.add_argument("--checktype", choices=["checksum", "age"],
5587b882245SAlex Bennée                            default="checksum", help="check type")
5597b882245SAlex Bennée        parser.add_argument("--olderthan", default=60, type=int,
5607b882245SAlex Bennée                            help="number of minutes")
561f97da1f7SAlex Bennée
562f97da1f7SAlex Bennée    def run(self, args, argv):
563f97da1f7SAlex Bennée        tag = args.tag
564f97da1f7SAlex Bennée
56543e1b2ffSAlex Bennée        try:
566f97da1f7SAlex Bennée            dkr = Docker()
567432d8ad5SAlex Bennée        except subprocess.CalledProcessError:
56843e1b2ffSAlex Bennée            print("Docker not set up")
56943e1b2ffSAlex Bennée            return 1
57043e1b2ffSAlex Bennée
571f97da1f7SAlex Bennée        info = dkr.inspect_tag(tag)
572f97da1f7SAlex Bennée        if info is None:
573f97da1f7SAlex Bennée            print("Image does not exist")
574f97da1f7SAlex Bennée            return 1
575f97da1f7SAlex Bennée
5767b882245SAlex Bennée        if args.checktype == "checksum":
5777b882245SAlex Bennée            if not args.dockerfile:
5787b882245SAlex Bennée                print("Need a dockerfile for tag:%s" % (tag))
5797b882245SAlex Bennée                return 1
5807b882245SAlex Bennée
5817b882245SAlex Bennée            dockerfile = open(args.dockerfile, "rb").read()
5827b882245SAlex Bennée
583f97da1f7SAlex Bennée            if dkr.image_matches_dockerfile(tag, dockerfile):
584f97da1f7SAlex Bennée                if not args.quiet:
585f97da1f7SAlex Bennée                    print("Image is up to date")
586f97da1f7SAlex Bennée                return 0
587f97da1f7SAlex Bennée            else:
588f97da1f7SAlex Bennée                print("Image needs updating")
589f97da1f7SAlex Bennée                return 1
5907b882245SAlex Bennée        elif args.checktype == "age":
5917b882245SAlex Bennée            timestr = dkr.get_image_creation_time(info).split(".")[0]
5927b882245SAlex Bennée            created = datetime.strptime(timestr, "%Y-%m-%dT%H:%M:%S")
5937b882245SAlex Bennée            past = datetime.now() - timedelta(minutes=args.olderthan)
5947b882245SAlex Bennée            if created < past:
5957b882245SAlex Bennée                print ("Image created @ %s more than %d minutes old" %
5967b882245SAlex Bennée                       (timestr, args.olderthan))
5977b882245SAlex Bennée                return 1
5987b882245SAlex Bennée            else:
5997b882245SAlex Bennée                if not args.quiet:
6007b882245SAlex Bennée                    print ("Image less than %d minutes old" % (args.olderthan))
6017b882245SAlex Bennée                return 0
602f97da1f7SAlex Bennée
603f97da1f7SAlex Bennée
6044485b04bSFam Zhengdef main():
6054485b04bSFam Zheng    parser = argparse.ArgumentParser(description="A Docker helper",
606432d8ad5SAlex Bennée                                     usage="%s <subcommand> ..." %
607432d8ad5SAlex Bennée                                     os.path.basename(sys.argv[0]))
6084485b04bSFam Zheng    subparsers = parser.add_subparsers(title="subcommands", help=None)
6094485b04bSFam Zheng    for cls in SubCommand.__subclasses__():
6104485b04bSFam Zheng        cmd = cls()
6114485b04bSFam Zheng        subp = subparsers.add_parser(cmd.name, help=cmd.__doc__)
6124485b04bSFam Zheng        cmd.shared_args(subp)
6134485b04bSFam Zheng        cmd.args(subp)
6144485b04bSFam Zheng        subp.set_defaults(cmdobj=cmd)
6154485b04bSFam Zheng    args, argv = parser.parse_known_args()
6164485b04bSFam Zheng    return args.cmdobj.run(args, argv)
6174485b04bSFam Zheng
618432d8ad5SAlex Bennée
6194485b04bSFam Zhengif __name__ == "__main__":
6204485b04bSFam Zheng    sys.exit(main())
621