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