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 46*432d8ad5SAlex Bennée 47438d1168SPhilippe Mathieu-Daudédef _file_checksum(filename): 48438d1168SPhilippe Mathieu-Daudé return _text_checksum(open(filename, 'rb').read()) 49438d1168SPhilippe Mathieu-Daudé 50*432d8ad5SAlex 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]) 64*432d8ad5SAlex Bennée raise Exception("Cannot find working docker command. Tried:\n%s" % 654485b04bSFam Zheng commands_txt) 664485b04bSFam Zheng 67*432d8ad5SAlex 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 102*432d8ad5SAlex 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): 149*432d8ad5SAlex 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 171*432d8ad5SAlex 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 189*432d8ad5SAlex 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=[]): 257*432d8ad5SAlex 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 278*432d8ad5SAlex 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 308*432d8ad5SAlex Bennée 3094485b04bSFam Zhengclass SubCommand(object): 3104485b04bSFam Zheng """A SubCommand template base class""" 3114485b04bSFam Zheng name = None # Subcommand name 312*432d8ad5SAlex 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 320*432d8ad5SAlex 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 328*432d8ad5SAlex Bennée 3294485b04bSFam Zhengclass RunCommand(SubCommand): 3304485b04bSFam Zheng """Invoke docker run and take care of cleaning up""" 3314485b04bSFam Zheng name = "run" 332*432d8ad5SAlex 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*432d8ad5SAlex Bennée 3374485b04bSFam Zheng def run(self, args, argv): 3384485b04bSFam Zheng return Docker().run(argv, args.keep, quiet=args.quiet) 3394485b04bSFam Zheng 340*432d8ad5SAlex Bennée 3414485b04bSFam Zhengclass BuildCommand(SubCommand): 342*432d8ad5SAlex Bennée """ Build docker image out of a dockerfile. Arg: <tag> <dockerfile>""" 3434485b04bSFam Zheng name = "build" 344*432d8ad5SAlex Bennée 3454485b04bSFam Zheng def args(self, parser): 346504ca3c2SAlex Bennée parser.add_argument("--include-executable", "-e", 347504ca3c2SAlex Bennée help="""Specify a binary that will be copied to the 348504ca3c2SAlex Bennée container together with all its dependent 349504ca3c2SAlex Bennée libraries""") 3504c84f662SPhilippe Mathieu-Daudé parser.add_argument("--extra-files", "-f", nargs='*', 3514c84f662SPhilippe Mathieu-Daudé help="""Specify files that will be copied in the 3524c84f662SPhilippe Mathieu-Daudé Docker image, fulfilling the ADD directive from the 3534c84f662SPhilippe Mathieu-Daudé Dockerfile""") 354414a8ce5SAlex Bennée parser.add_argument("--add-current-user", "-u", dest="user", 355414a8ce5SAlex Bennée action="store_true", 356414a8ce5SAlex Bennée help="Add the current user to image's passwd") 3574485b04bSFam Zheng parser.add_argument("tag", 3584485b04bSFam Zheng help="Image Tag") 3594485b04bSFam Zheng parser.add_argument("dockerfile", 3604485b04bSFam Zheng help="Dockerfile name") 3614485b04bSFam Zheng 3624485b04bSFam Zheng def run(self, args, argv): 3634485b04bSFam Zheng dockerfile = open(args.dockerfile, "rb").read() 3644485b04bSFam Zheng tag = args.tag 3654485b04bSFam Zheng 3664485b04bSFam Zheng dkr = Docker() 3676fe3ae3fSAlex Bennée if "--no-cache" not in argv and \ 3686fe3ae3fSAlex Bennée dkr.image_matches_dockerfile(tag, dockerfile): 3694485b04bSFam Zheng if not args.quiet: 370f03868bdSEduardo Habkost print("Image is up to date.") 371a9f8d038SAlex Bennée else: 372a9f8d038SAlex Bennée # Create a docker context directory for the build 373a9f8d038SAlex Bennée docker_dir = tempfile.mkdtemp(prefix="docker_build") 3744485b04bSFam Zheng 37515352decSAlex Bennée # Validate binfmt_misc will work 37615352decSAlex Bennée if args.include_executable: 377d10404b1SAlex Bennée qpath, enabled = _check_binfmt_misc(args.include_executable) 378d10404b1SAlex Bennée if not enabled: 37915352decSAlex Bennée return 1 38015352decSAlex Bennée 381920776eaSAlex Bennée # Is there a .pre file to run in the build context? 382920776eaSAlex Bennée docker_pre = os.path.splitext(args.dockerfile)[0]+".pre" 383920776eaSAlex Bennée if os.path.exists(docker_pre): 384f8042deaSSascha Silbe stdout = DEVNULL if args.quiet else None 385920776eaSAlex Bennée rc = subprocess.call(os.path.realpath(docker_pre), 386f8042deaSSascha Silbe cwd=docker_dir, stdout=stdout) 387920776eaSAlex Bennée if rc == 3: 388f03868bdSEduardo Habkost print("Skip") 389920776eaSAlex Bennée return 0 390920776eaSAlex Bennée elif rc != 0: 391f03868bdSEduardo Habkost print("%s exited with code %d" % (docker_pre, rc)) 392920776eaSAlex Bennée return 1 393920776eaSAlex Bennée 3944c84f662SPhilippe Mathieu-Daudé # Copy any extra files into the Docker context. These can be 3954c84f662SPhilippe Mathieu-Daudé # included by the use of the ADD directive in the Dockerfile. 396438d1168SPhilippe Mathieu-Daudé cksum = [] 397504ca3c2SAlex Bennée if args.include_executable: 398438d1168SPhilippe Mathieu-Daudé # FIXME: there is no checksum of this executable and the linked 399438d1168SPhilippe Mathieu-Daudé # libraries, once the image built any change of this executable 400438d1168SPhilippe Mathieu-Daudé # or any library won't trigger another build. 401d10404b1SAlex Bennée _copy_binary_with_libs(args.include_executable, 402d10404b1SAlex Bennée qpath, docker_dir) 403d10404b1SAlex Bennée 4044c84f662SPhilippe Mathieu-Daudé for filename in args.extra_files or []: 4054c84f662SPhilippe Mathieu-Daudé _copy_with_mkdir(filename, docker_dir) 406f9172822SAlex Bennée cksum += [(filename, _file_checksum(filename))] 407504ca3c2SAlex Bennée 40806cc3551SPhilippe Mathieu-Daudé argv += ["--build-arg=" + k.lower() + "=" + v 40906cc3551SPhilippe Mathieu-Daudé for k, v in os.environ.iteritems() 41006cc3551SPhilippe Mathieu-Daudé if k.lower() in FILTERED_ENV_NAMES] 411a9f8d038SAlex Bennée dkr.build_image(tag, docker_dir, dockerfile, 412438d1168SPhilippe Mathieu-Daudé quiet=args.quiet, user=args.user, argv=argv, 413438d1168SPhilippe Mathieu-Daudé extra_files_cksum=cksum) 414a9f8d038SAlex Bennée 415a9f8d038SAlex Bennée rmtree(docker_dir) 416a9f8d038SAlex Bennée 4174485b04bSFam Zheng return 0 4184485b04bSFam Zheng 419*432d8ad5SAlex Bennée 4206e733da6SAlex Bennéeclass UpdateCommand(SubCommand): 421*432d8ad5SAlex Bennée """ Update a docker image with new executables. Args: <tag> <executable>""" 4226e733da6SAlex Bennée name = "update" 423*432d8ad5SAlex Bennée 4246e733da6SAlex Bennée def args(self, parser): 4256e733da6SAlex Bennée parser.add_argument("tag", 4266e733da6SAlex Bennée help="Image Tag") 4276e733da6SAlex Bennée parser.add_argument("executable", 4286e733da6SAlex Bennée help="Executable to copy") 4296e733da6SAlex Bennée 4306e733da6SAlex Bennée def run(self, args, argv): 4316e733da6SAlex Bennée # Create a temporary tarball with our whole build context and 4326e733da6SAlex Bennée # dockerfile for the update 4336e733da6SAlex Bennée tmp = tempfile.NamedTemporaryFile(suffix="dckr.tar.gz") 4346e733da6SAlex Bennée tmp_tar = TarFile(fileobj=tmp, mode='w') 4356e733da6SAlex Bennée 4367e81d198SAlex Bennée # Add the executable to the tarball, using the current 437d10404b1SAlex Bennée # configured binfmt_misc path. If we don't get a path then we 438d10404b1SAlex Bennée # only need the support libraries copied 439d10404b1SAlex Bennée ff, enabled = _check_binfmt_misc(args.executable) 4407e81d198SAlex Bennée 441d10404b1SAlex Bennée if not enabled: 442d10404b1SAlex Bennée print("binfmt_misc not enabled, update disabled") 443d10404b1SAlex Bennée return 1 444d10404b1SAlex Bennée 445d10404b1SAlex Bennée if ff: 4466e733da6SAlex Bennée tmp_tar.add(args.executable, arcname=ff) 4476e733da6SAlex Bennée 4486e733da6SAlex Bennée # Add any associated libraries 4496e733da6SAlex Bennée libs = _get_so_libs(args.executable) 4506e733da6SAlex Bennée if libs: 4516e733da6SAlex Bennée for l in libs: 4526e733da6SAlex Bennée tmp_tar.add(os.path.realpath(l), arcname=l) 4536e733da6SAlex Bennée 4546e733da6SAlex Bennée # Create a Docker buildfile 4556e733da6SAlex Bennée df = StringIO() 4566e733da6SAlex Bennée df.write("FROM %s\n" % args.tag) 4576e733da6SAlex Bennée df.write("ADD . /\n") 4586e733da6SAlex Bennée df.seek(0) 4596e733da6SAlex Bennée 4606e733da6SAlex Bennée df_tar = TarInfo(name="Dockerfile") 4616e733da6SAlex Bennée df_tar.size = len(df.buf) 4626e733da6SAlex Bennée tmp_tar.addfile(df_tar, fileobj=df) 4636e733da6SAlex Bennée 4646e733da6SAlex Bennée tmp_tar.close() 4656e733da6SAlex Bennée 4666e733da6SAlex Bennée # reset the file pointers 4676e733da6SAlex Bennée tmp.flush() 4686e733da6SAlex Bennée tmp.seek(0) 4696e733da6SAlex Bennée 4706e733da6SAlex Bennée # Run the build with our tarball context 4716e733da6SAlex Bennée dkr = Docker() 4726e733da6SAlex Bennée dkr.update_image(args.tag, tmp, quiet=args.quiet) 4736e733da6SAlex Bennée 4746e733da6SAlex Bennée return 0 4756e733da6SAlex Bennée 476*432d8ad5SAlex Bennée 4774485b04bSFam Zhengclass CleanCommand(SubCommand): 4784485b04bSFam Zheng """Clean up docker instances""" 4794485b04bSFam Zheng name = "clean" 480*432d8ad5SAlex Bennée 4814485b04bSFam Zheng def run(self, args, argv): 4824485b04bSFam Zheng Docker().clean() 4834485b04bSFam Zheng return 0 4844485b04bSFam Zheng 485*432d8ad5SAlex Bennée 4864b08af60SFam Zhengclass ImagesCommand(SubCommand): 4874b08af60SFam Zheng """Run "docker images" command""" 4884b08af60SFam Zheng name = "images" 489*432d8ad5SAlex Bennée 4904b08af60SFam Zheng def run(self, args, argv): 4914b08af60SFam Zheng return Docker().command("images", argv, args.quiet) 4924b08af60SFam Zheng 49315df9d37SAlex Bennée 49415df9d37SAlex Bennéeclass ProbeCommand(SubCommand): 49515df9d37SAlex Bennée """Probe if we can run docker automatically""" 49615df9d37SAlex Bennée name = "probe" 49715df9d37SAlex Bennée 49815df9d37SAlex Bennée def run(self, args, argv): 49915df9d37SAlex Bennée try: 50015df9d37SAlex Bennée docker = Docker() 50115df9d37SAlex Bennée if docker._command[0] == "docker": 502f03868bdSEduardo Habkost print("yes") 50315df9d37SAlex Bennée elif docker._command[0] == "sudo": 504f03868bdSEduardo Habkost print("sudo") 50515df9d37SAlex Bennée except Exception: 506f03868bdSEduardo Habkost print("no") 50715df9d37SAlex Bennée 50815df9d37SAlex Bennée return 50915df9d37SAlex Bennée 51015df9d37SAlex Bennée 5115e03c2d8SAlex Bennéeclass CcCommand(SubCommand): 5125e03c2d8SAlex Bennée """Compile sources with cc in images""" 5135e03c2d8SAlex Bennée name = "cc" 5145e03c2d8SAlex Bennée 5155e03c2d8SAlex Bennée def args(self, parser): 5165e03c2d8SAlex Bennée parser.add_argument("--image", "-i", required=True, 5175e03c2d8SAlex Bennée help="The docker image in which to run cc") 51899cfdb86SAlex Bennée parser.add_argument("--cc", default="cc", 51999cfdb86SAlex Bennée help="The compiler executable to call") 52050b72738SAlex Bennée parser.add_argument("--user", 52150b72738SAlex Bennée help="The user-id to run under") 5225e03c2d8SAlex Bennée parser.add_argument("--source-path", "-s", nargs="*", dest="paths", 5235e03c2d8SAlex Bennée help="""Extra paths to (ro) mount into container for 5245e03c2d8SAlex Bennée reading sources""") 5255e03c2d8SAlex Bennée 5265e03c2d8SAlex Bennée def run(self, args, argv): 5275e03c2d8SAlex Bennée if argv and argv[0] == "--": 5285e03c2d8SAlex Bennée argv = argv[1:] 5295e03c2d8SAlex Bennée cwd = os.getcwd() 5305e03c2d8SAlex Bennée cmd = ["--rm", "-w", cwd, 5315e03c2d8SAlex Bennée "-v", "%s:%s:rw" % (cwd, cwd)] 5325e03c2d8SAlex Bennée if args.paths: 5335e03c2d8SAlex Bennée for p in args.paths: 5345e03c2d8SAlex Bennée cmd += ["-v", "%s:%s:ro,z" % (p, p)] 53550b72738SAlex Bennée if args.user: 53650b72738SAlex Bennée cmd += ["-u", args.user] 53799cfdb86SAlex Bennée cmd += [args.image, args.cc] 5385e03c2d8SAlex Bennée cmd += argv 5395e03c2d8SAlex Bennée return Docker().command("run", cmd, args.quiet) 5405e03c2d8SAlex Bennée 5415e03c2d8SAlex Bennée 542f97da1f7SAlex Bennéeclass CheckCommand(SubCommand): 543f97da1f7SAlex Bennée """Check if we need to re-build a docker image out of a dockerfile. 544f97da1f7SAlex Bennée Arguments: <tag> <dockerfile>""" 545f97da1f7SAlex Bennée name = "check" 546f97da1f7SAlex Bennée 547f97da1f7SAlex Bennée def args(self, parser): 548f97da1f7SAlex Bennée parser.add_argument("tag", 549f97da1f7SAlex Bennée help="Image Tag") 5507b882245SAlex Bennée parser.add_argument("dockerfile", default=None, 5517b882245SAlex Bennée help="Dockerfile name", nargs='?') 5527b882245SAlex Bennée parser.add_argument("--checktype", choices=["checksum", "age"], 5537b882245SAlex Bennée default="checksum", help="check type") 5547b882245SAlex Bennée parser.add_argument("--olderthan", default=60, type=int, 5557b882245SAlex Bennée help="number of minutes") 556f97da1f7SAlex Bennée 557f97da1f7SAlex Bennée def run(self, args, argv): 558f97da1f7SAlex Bennée tag = args.tag 559f97da1f7SAlex Bennée 56043e1b2ffSAlex Bennée try: 561f97da1f7SAlex Bennée dkr = Docker() 562*432d8ad5SAlex Bennée except subprocess.CalledProcessError: 56343e1b2ffSAlex Bennée print("Docker not set up") 56443e1b2ffSAlex Bennée return 1 56543e1b2ffSAlex Bennée 566f97da1f7SAlex Bennée info = dkr.inspect_tag(tag) 567f97da1f7SAlex Bennée if info is None: 568f97da1f7SAlex Bennée print("Image does not exist") 569f97da1f7SAlex Bennée return 1 570f97da1f7SAlex Bennée 5717b882245SAlex Bennée if args.checktype == "checksum": 5727b882245SAlex Bennée if not args.dockerfile: 5737b882245SAlex Bennée print("Need a dockerfile for tag:%s" % (tag)) 5747b882245SAlex Bennée return 1 5757b882245SAlex Bennée 5767b882245SAlex Bennée dockerfile = open(args.dockerfile, "rb").read() 5777b882245SAlex Bennée 578f97da1f7SAlex Bennée if dkr.image_matches_dockerfile(tag, dockerfile): 579f97da1f7SAlex Bennée if not args.quiet: 580f97da1f7SAlex Bennée print("Image is up to date") 581f97da1f7SAlex Bennée return 0 582f97da1f7SAlex Bennée else: 583f97da1f7SAlex Bennée print("Image needs updating") 584f97da1f7SAlex Bennée return 1 5857b882245SAlex Bennée elif args.checktype == "age": 5867b882245SAlex Bennée timestr = dkr.get_image_creation_time(info).split(".")[0] 5877b882245SAlex Bennée created = datetime.strptime(timestr, "%Y-%m-%dT%H:%M:%S") 5887b882245SAlex Bennée past = datetime.now() - timedelta(minutes=args.olderthan) 5897b882245SAlex Bennée if created < past: 5907b882245SAlex Bennée print ("Image created @ %s more than %d minutes old" % 5917b882245SAlex Bennée (timestr, args.olderthan)) 5927b882245SAlex Bennée return 1 5937b882245SAlex Bennée else: 5947b882245SAlex Bennée if not args.quiet: 5957b882245SAlex Bennée print ("Image less than %d minutes old" % (args.olderthan)) 5967b882245SAlex Bennée return 0 597f97da1f7SAlex Bennée 598f97da1f7SAlex Bennée 5994485b04bSFam Zhengdef main(): 6004485b04bSFam Zheng parser = argparse.ArgumentParser(description="A Docker helper", 601*432d8ad5SAlex Bennée usage="%s <subcommand> ..." % 602*432d8ad5SAlex Bennée os.path.basename(sys.argv[0])) 6034485b04bSFam Zheng subparsers = parser.add_subparsers(title="subcommands", help=None) 6044485b04bSFam Zheng for cls in SubCommand.__subclasses__(): 6054485b04bSFam Zheng cmd = cls() 6064485b04bSFam Zheng subp = subparsers.add_parser(cmd.name, help=cmd.__doc__) 6074485b04bSFam Zheng cmd.shared_args(subp) 6084485b04bSFam Zheng cmd.args(subp) 6094485b04bSFam Zheng subp.set_defaults(cmdobj=cmd) 6104485b04bSFam Zheng args, argv = parser.parse_known_args() 6114485b04bSFam Zheng return args.cmdobj.run(args, argv) 6124485b04bSFam Zheng 613*432d8ad5SAlex Bennée 6144485b04bSFam Zhengif __name__ == "__main__": 6154485b04bSFam Zheng sys.exit(main()) 616