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 46438d1168SPhilippe Mathieu-Daudédef _file_checksum(filename): 47438d1168SPhilippe Mathieu-Daudé return _text_checksum(open(filename, 'rb').read()) 48438d1168SPhilippe Mathieu-Daudé 494485b04bSFam Zhengdef _guess_docker_command(): 504485b04bSFam Zheng """ Guess a working docker command or raise exception if not found""" 514485b04bSFam Zheng commands = [["docker"], ["sudo", "-n", "docker"]] 524485b04bSFam Zheng for cmd in commands: 530679f98bSEduardo Habkost try: 5483405c45SAlex Bennée # docker version will return the client details in stdout 5583405c45SAlex Bennée # but still report a status of 1 if it can't contact the daemon 5683405c45SAlex Bennée if subprocess.call(cmd + ["version"], 57c9772570SSascha Silbe stdout=DEVNULL, stderr=DEVNULL) == 0: 584485b04bSFam Zheng return cmd 590679f98bSEduardo Habkost except OSError: 600679f98bSEduardo Habkost pass 614485b04bSFam Zheng commands_txt = "\n".join([" " + " ".join(x) for x in commands]) 624485b04bSFam Zheng raise Exception("Cannot find working docker command. Tried:\n%s" % \ 634485b04bSFam Zheng commands_txt) 644485b04bSFam Zheng 652499ee9fSPhilippe Mathieu-Daudédef _copy_with_mkdir(src, root_dir, sub_path='.'): 66504ca3c2SAlex Bennée """Copy src into root_dir, creating sub_path as needed.""" 67504ca3c2SAlex Bennée dest_dir = os.path.normpath("%s/%s" % (root_dir, sub_path)) 68504ca3c2SAlex Bennée try: 69504ca3c2SAlex Bennée os.makedirs(dest_dir) 70504ca3c2SAlex Bennée except OSError: 71504ca3c2SAlex Bennée # we can safely ignore already created directories 72504ca3c2SAlex Bennée pass 73504ca3c2SAlex Bennée 74504ca3c2SAlex Bennée dest_file = "%s/%s" % (dest_dir, os.path.basename(src)) 75504ca3c2SAlex Bennée copy(src, dest_file) 76504ca3c2SAlex Bennée 77504ca3c2SAlex Bennée 78504ca3c2SAlex Bennéedef _get_so_libs(executable): 79504ca3c2SAlex Bennée """Return a list of libraries associated with an executable. 80504ca3c2SAlex Bennée 81504ca3c2SAlex Bennée The paths may be symbolic links which would need to be resolved to 82504ca3c2SAlex Bennée ensure theright data is copied.""" 83504ca3c2SAlex Bennée 84504ca3c2SAlex Bennée libs = [] 85504ca3c2SAlex Bennée ldd_re = re.compile(r"(/.*/)(\S*)") 86504ca3c2SAlex Bennée try: 87504ca3c2SAlex Bennée ldd_output = subprocess.check_output(["ldd", executable]) 88504ca3c2SAlex Bennée for line in ldd_output.split("\n"): 89504ca3c2SAlex Bennée search = ldd_re.search(line) 90504ca3c2SAlex Bennée if search and len(search.groups()) == 2: 91504ca3c2SAlex Bennée so_path = search.groups()[0] 92504ca3c2SAlex Bennée so_lib = search.groups()[1] 93504ca3c2SAlex Bennée libs.append("%s/%s" % (so_path, so_lib)) 94504ca3c2SAlex Bennée except subprocess.CalledProcessError: 95f03868bdSEduardo Habkost print("%s had no associated libraries (static build?)" % (executable)) 96504ca3c2SAlex Bennée 97504ca3c2SAlex Bennée return libs 98504ca3c2SAlex Bennée 99*d10404b1SAlex Bennéedef _copy_binary_with_libs(src, bin_dest, dest_dir): 100*d10404b1SAlex Bennée """Maybe copy a binary and all its dependent libraries. 101*d10404b1SAlex Bennée 102*d10404b1SAlex Bennée If bin_dest isn't set we only copy the support libraries because 103*d10404b1SAlex Bennée we don't need qemu in the docker path to run (due to persistent 104*d10404b1SAlex Bennée mapping). Indeed users may get confused if we aren't running what 105*d10404b1SAlex Bennée is in the image. 106504ca3c2SAlex Bennée 107504ca3c2SAlex Bennée This does rely on the host file-system being fairly multi-arch 108*d10404b1SAlex Bennée aware so the file don't clash with the guests layout. 109*d10404b1SAlex Bennée """ 110504ca3c2SAlex Bennée 111*d10404b1SAlex Bennée if bin_dest: 112*d10404b1SAlex Bennée _copy_with_mkdir(src, dest_dir, os.path.dirname(bin_dest)) 113*d10404b1SAlex Bennée else: 114*d10404b1SAlex Bennée print("only copying support libraries for %s" % (src)) 115504ca3c2SAlex Bennée 116504ca3c2SAlex Bennée libs = _get_so_libs(src) 117504ca3c2SAlex Bennée if libs: 118504ca3c2SAlex Bennée for l in libs: 119504ca3c2SAlex Bennée so_path = os.path.dirname(l) 120504ca3c2SAlex Bennée _copy_with_mkdir(l , dest_dir, so_path) 121504ca3c2SAlex Bennée 12215352decSAlex Bennée 12315352decSAlex Bennéedef _check_binfmt_misc(executable): 12415352decSAlex Bennée """Check binfmt_misc has entry for executable in the right place. 12515352decSAlex Bennée 12615352decSAlex Bennée The details of setting up binfmt_misc are outside the scope of 12715352decSAlex Bennée this script but we should at least fail early with a useful 128*d10404b1SAlex Bennée message if it won't work. 129*d10404b1SAlex Bennée 130*d10404b1SAlex Bennée Returns the configured binfmt path and a valid flag. For 131*d10404b1SAlex Bennée persistent configurations we will still want to copy and dependent 132*d10404b1SAlex Bennée libraries. 133*d10404b1SAlex Bennée """ 13415352decSAlex Bennée 13515352decSAlex Bennée binary = os.path.basename(executable) 13615352decSAlex Bennée binfmt_entry = "/proc/sys/fs/binfmt_misc/%s" % (binary) 13715352decSAlex Bennée 13815352decSAlex Bennée if not os.path.exists(binfmt_entry): 13915352decSAlex Bennée print ("No binfmt_misc entry for %s" % (binary)) 140*d10404b1SAlex Bennée return None, False 14115352decSAlex Bennée 14215352decSAlex Bennée with open(binfmt_entry) as x: entry = x.read() 14315352decSAlex Bennée 14443c898b7SAlex Bennée if re.search("flags:.*F.*\n", entry): 14543c898b7SAlex Bennée print("binfmt_misc for %s uses persistent(F) mapping to host binary\n" % 14643c898b7SAlex Bennée (binary)) 147*d10404b1SAlex Bennée return None, True 14843c898b7SAlex Bennée 1497e81d198SAlex Bennée m = re.search("interpreter (\S+)\n", entry) 1507e81d198SAlex Bennée interp = m.group(1) 1517e81d198SAlex Bennée if interp and interp != executable: 1527e81d198SAlex Bennée print("binfmt_misc for %s does not point to %s, using %s" % 1537e81d198SAlex Bennée (binary, executable, interp)) 15415352decSAlex Bennée 155*d10404b1SAlex Bennée return interp, True 156*d10404b1SAlex Bennée 15715352decSAlex Bennée 158c1958e9dSFam Zhengdef _read_qemu_dockerfile(img_name): 159547cb45eSAlex Bennée # special case for Debian linux-user images 160547cb45eSAlex Bennée if img_name.startswith("debian") and img_name.endswith("user"): 161547cb45eSAlex Bennée img_name = "debian-bootstrap" 162547cb45eSAlex Bennée 163c1958e9dSFam Zheng df = os.path.join(os.path.dirname(__file__), "dockerfiles", 164c1958e9dSFam Zheng img_name + ".docker") 165c1958e9dSFam Zheng return open(df, "r").read() 166c1958e9dSFam Zheng 167c1958e9dSFam Zhengdef _dockerfile_preprocess(df): 168c1958e9dSFam Zheng out = "" 169c1958e9dSFam Zheng for l in df.splitlines(): 170c1958e9dSFam Zheng if len(l.strip()) == 0 or l.startswith("#"): 171c1958e9dSFam Zheng continue 172c1958e9dSFam Zheng from_pref = "FROM qemu:" 173c1958e9dSFam Zheng if l.startswith(from_pref): 174c1958e9dSFam Zheng # TODO: Alternatively we could replace this line with "FROM $ID" 175c1958e9dSFam Zheng # where $ID is the image's hex id obtained with 176c1958e9dSFam Zheng # $ docker images $IMAGE --format="{{.Id}}" 177c1958e9dSFam Zheng # but unfortunately that's not supported by RHEL 7. 178c1958e9dSFam Zheng inlining = _read_qemu_dockerfile(l[len(from_pref):]) 179c1958e9dSFam Zheng out += _dockerfile_preprocess(inlining) 180c1958e9dSFam Zheng continue 181c1958e9dSFam Zheng out += l + "\n" 182c1958e9dSFam Zheng return out 183c1958e9dSFam Zheng 1844485b04bSFam Zhengclass Docker(object): 1854485b04bSFam Zheng """ Running Docker commands """ 1864485b04bSFam Zheng def __init__(self): 1874485b04bSFam Zheng self._command = _guess_docker_command() 1884485b04bSFam Zheng self._instances = [] 1894485b04bSFam Zheng atexit.register(self._kill_instances) 19097cba1a1SFam Zheng signal.signal(signal.SIGTERM, self._kill_instances) 19197cba1a1SFam Zheng signal.signal(signal.SIGHUP, self._kill_instances) 1924485b04bSFam Zheng 19358bf7b6dSFam Zheng def _do(self, cmd, quiet=True, **kwargs): 1944485b04bSFam Zheng if quiet: 195c9772570SSascha Silbe kwargs["stdout"] = DEVNULL 1964485b04bSFam Zheng return subprocess.call(self._command + cmd, **kwargs) 1974485b04bSFam Zheng 1980b95ff72SFam Zheng def _do_check(self, cmd, quiet=True, **kwargs): 1990b95ff72SFam Zheng if quiet: 2000b95ff72SFam Zheng kwargs["stdout"] = DEVNULL 2010b95ff72SFam Zheng return subprocess.check_call(self._command + cmd, **kwargs) 2020b95ff72SFam Zheng 2034485b04bSFam Zheng def _do_kill_instances(self, only_known, only_active=True): 2044485b04bSFam Zheng cmd = ["ps", "-q"] 2054485b04bSFam Zheng if not only_active: 2064485b04bSFam Zheng cmd.append("-a") 2074485b04bSFam Zheng for i in self._output(cmd).split(): 2084485b04bSFam Zheng resp = self._output(["inspect", i]) 2094485b04bSFam Zheng labels = json.loads(resp)[0]["Config"]["Labels"] 2104485b04bSFam Zheng active = json.loads(resp)[0]["State"]["Running"] 2114485b04bSFam Zheng if not labels: 2124485b04bSFam Zheng continue 2134485b04bSFam Zheng instance_uuid = labels.get("com.qemu.instance.uuid", None) 2144485b04bSFam Zheng if not instance_uuid: 2154485b04bSFam Zheng continue 2164485b04bSFam Zheng if only_known and instance_uuid not in self._instances: 2174485b04bSFam Zheng continue 218f03868bdSEduardo Habkost print("Terminating", i) 2194485b04bSFam Zheng if active: 2204485b04bSFam Zheng self._do(["kill", i]) 2214485b04bSFam Zheng self._do(["rm", i]) 2224485b04bSFam Zheng 2234485b04bSFam Zheng def clean(self): 2244485b04bSFam Zheng self._do_kill_instances(False, False) 2254485b04bSFam Zheng return 0 2264485b04bSFam Zheng 22797cba1a1SFam Zheng def _kill_instances(self, *args, **kwargs): 2284485b04bSFam Zheng return self._do_kill_instances(True) 2294485b04bSFam Zheng 2304485b04bSFam Zheng def _output(self, cmd, **kwargs): 2314485b04bSFam Zheng return subprocess.check_output(self._command + cmd, 2324485b04bSFam Zheng stderr=subprocess.STDOUT, 2334485b04bSFam Zheng **kwargs) 2344485b04bSFam Zheng 235f97da1f7SAlex Bennée def inspect_tag(self, tag): 236f97da1f7SAlex Bennée try: 237f97da1f7SAlex Bennée return self._output(["inspect", tag]) 238f97da1f7SAlex Bennée except subprocess.CalledProcessError: 239f97da1f7SAlex Bennée return None 240f97da1f7SAlex Bennée 2417b882245SAlex Bennée def get_image_creation_time(self, info): 2427b882245SAlex Bennée return json.loads(info)[0]["Created"] 2437b882245SAlex Bennée 2444485b04bSFam Zheng def get_image_dockerfile_checksum(self, tag): 245f97da1f7SAlex Bennée resp = self.inspect_tag(tag) 2464485b04bSFam Zheng labels = json.loads(resp)[0]["Config"].get("Labels", {}) 2474485b04bSFam Zheng return labels.get("com.qemu.dockerfile-checksum", "") 2484485b04bSFam Zheng 249414a8ce5SAlex Bennée def build_image(self, tag, docker_dir, dockerfile, 250438d1168SPhilippe Mathieu-Daudé quiet=True, user=False, argv=None, extra_files_cksum=[]): 2514485b04bSFam Zheng if argv == None: 2524485b04bSFam Zheng argv = [] 2534485b04bSFam Zheng 254a9f8d038SAlex Bennée tmp_df = tempfile.NamedTemporaryFile(dir=docker_dir, suffix=".docker") 2554485b04bSFam Zheng tmp_df.write(dockerfile) 2564485b04bSFam Zheng 257414a8ce5SAlex Bennée if user: 258414a8ce5SAlex Bennée uid = os.getuid() 259414a8ce5SAlex Bennée uname = getpwuid(uid).pw_name 260414a8ce5SAlex Bennée tmp_df.write("\n") 261414a8ce5SAlex Bennée tmp_df.write("RUN id %s 2>/dev/null || useradd -u %d -U %s" % 262414a8ce5SAlex Bennée (uname, uid, uname)) 263414a8ce5SAlex Bennée 2644485b04bSFam Zheng tmp_df.write("\n") 2654485b04bSFam Zheng tmp_df.write("LABEL com.qemu.dockerfile-checksum=%s" % 266f9172822SAlex Bennée _text_checksum(_dockerfile_preprocess(dockerfile))) 267f9172822SAlex Bennée for f, c in extra_files_cksum: 268f9172822SAlex Bennée tmp_df.write("LABEL com.qemu.%s-checksum=%s" % (f, c)) 269f9172822SAlex Bennée 2704485b04bSFam Zheng tmp_df.flush() 271a9f8d038SAlex Bennée 2720b95ff72SFam Zheng self._do_check(["build", "-t", tag, "-f", tmp_df.name] + argv + \ 273a9f8d038SAlex Bennée [docker_dir], 2744485b04bSFam Zheng quiet=quiet) 2754485b04bSFam Zheng 2766e733da6SAlex Bennée def update_image(self, tag, tarball, quiet=True): 2776e733da6SAlex Bennée "Update a tagged image using " 2786e733da6SAlex Bennée 2790b95ff72SFam Zheng self._do_check(["build", "-t", tag, "-"], quiet=quiet, stdin=tarball) 2806e733da6SAlex Bennée 2814485b04bSFam Zheng def image_matches_dockerfile(self, tag, dockerfile): 2824485b04bSFam Zheng try: 2834485b04bSFam Zheng checksum = self.get_image_dockerfile_checksum(tag) 2844485b04bSFam Zheng except Exception: 2854485b04bSFam Zheng return False 286c1958e9dSFam Zheng return checksum == _text_checksum(_dockerfile_preprocess(dockerfile)) 2874485b04bSFam Zheng 2884485b04bSFam Zheng def run(self, cmd, keep, quiet): 2894485b04bSFam Zheng label = uuid.uuid1().hex 2904485b04bSFam Zheng if not keep: 2914485b04bSFam Zheng self._instances.append(label) 2920b95ff72SFam Zheng ret = self._do_check(["run", "--label", 2934485b04bSFam Zheng "com.qemu.instance.uuid=" + label] + cmd, 2944485b04bSFam Zheng quiet=quiet) 2954485b04bSFam Zheng if not keep: 2964485b04bSFam Zheng self._instances.remove(label) 2974485b04bSFam Zheng return ret 2984485b04bSFam Zheng 2994b08af60SFam Zheng def command(self, cmd, argv, quiet): 3004b08af60SFam Zheng return self._do([cmd] + argv, quiet=quiet) 3014b08af60SFam Zheng 3024485b04bSFam Zhengclass SubCommand(object): 3034485b04bSFam Zheng """A SubCommand template base class""" 3044485b04bSFam Zheng name = None # Subcommand name 3054485b04bSFam Zheng def shared_args(self, parser): 3064485b04bSFam Zheng parser.add_argument("--quiet", action="store_true", 307e50a6121SStefan Weil help="Run quietly unless an error occurred") 3084485b04bSFam Zheng 3094485b04bSFam Zheng def args(self, parser): 3104485b04bSFam Zheng """Setup argument parser""" 3114485b04bSFam Zheng pass 3124485b04bSFam Zheng def run(self, args, argv): 3134485b04bSFam Zheng """Run command. 3144485b04bSFam Zheng args: parsed argument by argument parser. 3154485b04bSFam Zheng argv: remaining arguments from sys.argv. 3164485b04bSFam Zheng """ 3174485b04bSFam Zheng pass 3184485b04bSFam Zheng 3194485b04bSFam Zhengclass RunCommand(SubCommand): 3204485b04bSFam Zheng """Invoke docker run and take care of cleaning up""" 3214485b04bSFam Zheng name = "run" 3224485b04bSFam Zheng def args(self, parser): 3234485b04bSFam Zheng parser.add_argument("--keep", action="store_true", 3244485b04bSFam Zheng help="Don't remove image when command completes") 3254485b04bSFam Zheng def run(self, args, argv): 3264485b04bSFam Zheng return Docker().run(argv, args.keep, quiet=args.quiet) 3274485b04bSFam Zheng 3284485b04bSFam Zhengclass BuildCommand(SubCommand): 3294485b04bSFam Zheng """ Build docker image out of a dockerfile. Arguments: <tag> <dockerfile>""" 3304485b04bSFam Zheng name = "build" 3314485b04bSFam Zheng def args(self, parser): 332504ca3c2SAlex Bennée parser.add_argument("--include-executable", "-e", 333504ca3c2SAlex Bennée help="""Specify a binary that will be copied to the 334504ca3c2SAlex Bennée container together with all its dependent 335504ca3c2SAlex Bennée libraries""") 3364c84f662SPhilippe Mathieu-Daudé parser.add_argument("--extra-files", "-f", nargs='*', 3374c84f662SPhilippe Mathieu-Daudé help="""Specify files that will be copied in the 3384c84f662SPhilippe Mathieu-Daudé Docker image, fulfilling the ADD directive from the 3394c84f662SPhilippe Mathieu-Daudé Dockerfile""") 340414a8ce5SAlex Bennée parser.add_argument("--add-current-user", "-u", dest="user", 341414a8ce5SAlex Bennée action="store_true", 342414a8ce5SAlex Bennée help="Add the current user to image's passwd") 3434485b04bSFam Zheng parser.add_argument("tag", 3444485b04bSFam Zheng help="Image Tag") 3454485b04bSFam Zheng parser.add_argument("dockerfile", 3464485b04bSFam Zheng help="Dockerfile name") 3474485b04bSFam Zheng 3484485b04bSFam Zheng def run(self, args, argv): 3494485b04bSFam Zheng dockerfile = open(args.dockerfile, "rb").read() 3504485b04bSFam Zheng tag = args.tag 3514485b04bSFam Zheng 3524485b04bSFam Zheng dkr = Docker() 3536fe3ae3fSAlex Bennée if "--no-cache" not in argv and \ 3546fe3ae3fSAlex Bennée dkr.image_matches_dockerfile(tag, dockerfile): 3554485b04bSFam Zheng if not args.quiet: 356f03868bdSEduardo Habkost print("Image is up to date.") 357a9f8d038SAlex Bennée else: 358a9f8d038SAlex Bennée # Create a docker context directory for the build 359a9f8d038SAlex Bennée docker_dir = tempfile.mkdtemp(prefix="docker_build") 3604485b04bSFam Zheng 36115352decSAlex Bennée # Validate binfmt_misc will work 36215352decSAlex Bennée if args.include_executable: 363*d10404b1SAlex Bennée qpath, enabled = _check_binfmt_misc(args.include_executable) 364*d10404b1SAlex Bennée if not enabled: 36515352decSAlex Bennée return 1 36615352decSAlex Bennée 367920776eaSAlex Bennée # Is there a .pre file to run in the build context? 368920776eaSAlex Bennée docker_pre = os.path.splitext(args.dockerfile)[0]+".pre" 369920776eaSAlex Bennée if os.path.exists(docker_pre): 370f8042deaSSascha Silbe stdout = DEVNULL if args.quiet else None 371920776eaSAlex Bennée rc = subprocess.call(os.path.realpath(docker_pre), 372f8042deaSSascha Silbe cwd=docker_dir, stdout=stdout) 373920776eaSAlex Bennée if rc == 3: 374f03868bdSEduardo Habkost print("Skip") 375920776eaSAlex Bennée return 0 376920776eaSAlex Bennée elif rc != 0: 377f03868bdSEduardo Habkost print("%s exited with code %d" % (docker_pre, rc)) 378920776eaSAlex Bennée return 1 379920776eaSAlex Bennée 3804c84f662SPhilippe Mathieu-Daudé # Copy any extra files into the Docker context. These can be 3814c84f662SPhilippe Mathieu-Daudé # included by the use of the ADD directive in the Dockerfile. 382438d1168SPhilippe Mathieu-Daudé cksum = [] 383504ca3c2SAlex Bennée if args.include_executable: 384438d1168SPhilippe Mathieu-Daudé # FIXME: there is no checksum of this executable and the linked 385438d1168SPhilippe Mathieu-Daudé # libraries, once the image built any change of this executable 386438d1168SPhilippe Mathieu-Daudé # or any library won't trigger another build. 387*d10404b1SAlex Bennée _copy_binary_with_libs(args.include_executable, 388*d10404b1SAlex Bennée qpath, docker_dir) 389*d10404b1SAlex Bennée 3904c84f662SPhilippe Mathieu-Daudé for filename in args.extra_files or []: 3914c84f662SPhilippe Mathieu-Daudé _copy_with_mkdir(filename, docker_dir) 392f9172822SAlex Bennée cksum += [(filename, _file_checksum(filename))] 393504ca3c2SAlex Bennée 39406cc3551SPhilippe Mathieu-Daudé argv += ["--build-arg=" + k.lower() + "=" + v 39506cc3551SPhilippe Mathieu-Daudé for k, v in os.environ.iteritems() 39606cc3551SPhilippe Mathieu-Daudé if k.lower() in FILTERED_ENV_NAMES] 397a9f8d038SAlex Bennée dkr.build_image(tag, docker_dir, dockerfile, 398438d1168SPhilippe Mathieu-Daudé quiet=args.quiet, user=args.user, argv=argv, 399438d1168SPhilippe Mathieu-Daudé extra_files_cksum=cksum) 400a9f8d038SAlex Bennée 401a9f8d038SAlex Bennée rmtree(docker_dir) 402a9f8d038SAlex Bennée 4034485b04bSFam Zheng return 0 4044485b04bSFam Zheng 4056e733da6SAlex Bennéeclass UpdateCommand(SubCommand): 4066e733da6SAlex Bennée """ Update a docker image with new executables. Arguments: <tag> <executable>""" 4076e733da6SAlex Bennée name = "update" 4086e733da6SAlex Bennée def args(self, parser): 4096e733da6SAlex Bennée parser.add_argument("tag", 4106e733da6SAlex Bennée help="Image Tag") 4116e733da6SAlex Bennée parser.add_argument("executable", 4126e733da6SAlex Bennée help="Executable to copy") 4136e733da6SAlex Bennée 4146e733da6SAlex Bennée def run(self, args, argv): 4156e733da6SAlex Bennée # Create a temporary tarball with our whole build context and 4166e733da6SAlex Bennée # dockerfile for the update 4176e733da6SAlex Bennée tmp = tempfile.NamedTemporaryFile(suffix="dckr.tar.gz") 4186e733da6SAlex Bennée tmp_tar = TarFile(fileobj=tmp, mode='w') 4196e733da6SAlex Bennée 4207e81d198SAlex Bennée # Add the executable to the tarball, using the current 421*d10404b1SAlex Bennée # configured binfmt_misc path. If we don't get a path then we 422*d10404b1SAlex Bennée # only need the support libraries copied 423*d10404b1SAlex Bennée ff, enabled = _check_binfmt_misc(args.executable) 4247e81d198SAlex Bennée 425*d10404b1SAlex Bennée if not enabled: 426*d10404b1SAlex Bennée print("binfmt_misc not enabled, update disabled") 427*d10404b1SAlex Bennée return 1 428*d10404b1SAlex Bennée 429*d10404b1SAlex Bennée if ff: 4306e733da6SAlex Bennée tmp_tar.add(args.executable, arcname=ff) 4316e733da6SAlex Bennée 4326e733da6SAlex Bennée # Add any associated libraries 4336e733da6SAlex Bennée libs = _get_so_libs(args.executable) 4346e733da6SAlex Bennée if libs: 4356e733da6SAlex Bennée for l in libs: 4366e733da6SAlex Bennée tmp_tar.add(os.path.realpath(l), arcname=l) 4376e733da6SAlex Bennée 4386e733da6SAlex Bennée # Create a Docker buildfile 4396e733da6SAlex Bennée df = StringIO() 4406e733da6SAlex Bennée df.write("FROM %s\n" % args.tag) 4416e733da6SAlex Bennée df.write("ADD . /\n") 4426e733da6SAlex Bennée df.seek(0) 4436e733da6SAlex Bennée 4446e733da6SAlex Bennée df_tar = TarInfo(name="Dockerfile") 4456e733da6SAlex Bennée df_tar.size = len(df.buf) 4466e733da6SAlex Bennée tmp_tar.addfile(df_tar, fileobj=df) 4476e733da6SAlex Bennée 4486e733da6SAlex Bennée tmp_tar.close() 4496e733da6SAlex Bennée 4506e733da6SAlex Bennée # reset the file pointers 4516e733da6SAlex Bennée tmp.flush() 4526e733da6SAlex Bennée tmp.seek(0) 4536e733da6SAlex Bennée 4546e733da6SAlex Bennée # Run the build with our tarball context 4556e733da6SAlex Bennée dkr = Docker() 4566e733da6SAlex Bennée dkr.update_image(args.tag, tmp, quiet=args.quiet) 4576e733da6SAlex Bennée 4586e733da6SAlex Bennée return 0 4596e733da6SAlex Bennée 4604485b04bSFam Zhengclass CleanCommand(SubCommand): 4614485b04bSFam Zheng """Clean up docker instances""" 4624485b04bSFam Zheng name = "clean" 4634485b04bSFam Zheng def run(self, args, argv): 4644485b04bSFam Zheng Docker().clean() 4654485b04bSFam Zheng return 0 4664485b04bSFam Zheng 4674b08af60SFam Zhengclass ImagesCommand(SubCommand): 4684b08af60SFam Zheng """Run "docker images" command""" 4694b08af60SFam Zheng name = "images" 4704b08af60SFam Zheng def run(self, args, argv): 4714b08af60SFam Zheng return Docker().command("images", argv, args.quiet) 4724b08af60SFam Zheng 47315df9d37SAlex Bennée 47415df9d37SAlex Bennéeclass ProbeCommand(SubCommand): 47515df9d37SAlex Bennée """Probe if we can run docker automatically""" 47615df9d37SAlex Bennée name = "probe" 47715df9d37SAlex Bennée 47815df9d37SAlex Bennée def run(self, args, argv): 47915df9d37SAlex Bennée try: 48015df9d37SAlex Bennée docker = Docker() 48115df9d37SAlex Bennée if docker._command[0] == "docker": 482f03868bdSEduardo Habkost print("yes") 48315df9d37SAlex Bennée elif docker._command[0] == "sudo": 484f03868bdSEduardo Habkost print("sudo") 48515df9d37SAlex Bennée except Exception: 486f03868bdSEduardo Habkost print("no") 48715df9d37SAlex Bennée 48815df9d37SAlex Bennée return 48915df9d37SAlex Bennée 49015df9d37SAlex Bennée 4915e03c2d8SAlex Bennéeclass CcCommand(SubCommand): 4925e03c2d8SAlex Bennée """Compile sources with cc in images""" 4935e03c2d8SAlex Bennée name = "cc" 4945e03c2d8SAlex Bennée 4955e03c2d8SAlex Bennée def args(self, parser): 4965e03c2d8SAlex Bennée parser.add_argument("--image", "-i", required=True, 4975e03c2d8SAlex Bennée help="The docker image in which to run cc") 49899cfdb86SAlex Bennée parser.add_argument("--cc", default="cc", 49999cfdb86SAlex Bennée help="The compiler executable to call") 50050b72738SAlex Bennée parser.add_argument("--user", 50150b72738SAlex Bennée help="The user-id to run under") 5025e03c2d8SAlex Bennée parser.add_argument("--source-path", "-s", nargs="*", dest="paths", 5035e03c2d8SAlex Bennée help="""Extra paths to (ro) mount into container for 5045e03c2d8SAlex Bennée reading sources""") 5055e03c2d8SAlex Bennée 5065e03c2d8SAlex Bennée def run(self, args, argv): 5075e03c2d8SAlex Bennée if argv and argv[0] == "--": 5085e03c2d8SAlex Bennée argv = argv[1:] 5095e03c2d8SAlex Bennée cwd = os.getcwd() 5105e03c2d8SAlex Bennée cmd = ["--rm", "-w", cwd, 5115e03c2d8SAlex Bennée "-v", "%s:%s:rw" % (cwd, cwd)] 5125e03c2d8SAlex Bennée if args.paths: 5135e03c2d8SAlex Bennée for p in args.paths: 5145e03c2d8SAlex Bennée cmd += ["-v", "%s:%s:ro,z" % (p, p)] 51550b72738SAlex Bennée if args.user: 51650b72738SAlex Bennée cmd += ["-u", args.user] 51799cfdb86SAlex Bennée cmd += [args.image, args.cc] 5185e03c2d8SAlex Bennée cmd += argv 5195e03c2d8SAlex Bennée return Docker().command("run", cmd, args.quiet) 5205e03c2d8SAlex Bennée 5215e03c2d8SAlex Bennée 522f97da1f7SAlex Bennéeclass CheckCommand(SubCommand): 523f97da1f7SAlex Bennée """Check if we need to re-build a docker image out of a dockerfile. 524f97da1f7SAlex Bennée Arguments: <tag> <dockerfile>""" 525f97da1f7SAlex Bennée name = "check" 526f97da1f7SAlex Bennée 527f97da1f7SAlex Bennée def args(self, parser): 528f97da1f7SAlex Bennée parser.add_argument("tag", 529f97da1f7SAlex Bennée help="Image Tag") 5307b882245SAlex Bennée parser.add_argument("dockerfile", default=None, 5317b882245SAlex Bennée help="Dockerfile name", nargs='?') 5327b882245SAlex Bennée parser.add_argument("--checktype", choices=["checksum", "age"], 5337b882245SAlex Bennée default="checksum", help="check type") 5347b882245SAlex Bennée parser.add_argument("--olderthan", default=60, type=int, 5357b882245SAlex Bennée help="number of minutes") 536f97da1f7SAlex Bennée 537f97da1f7SAlex Bennée def run(self, args, argv): 538f97da1f7SAlex Bennée tag = args.tag 539f97da1f7SAlex Bennée 54043e1b2ffSAlex Bennée try: 541f97da1f7SAlex Bennée dkr = Docker() 54243e1b2ffSAlex Bennée except: 54343e1b2ffSAlex Bennée print("Docker not set up") 54443e1b2ffSAlex Bennée return 1 54543e1b2ffSAlex Bennée 546f97da1f7SAlex Bennée info = dkr.inspect_tag(tag) 547f97da1f7SAlex Bennée if info is None: 548f97da1f7SAlex Bennée print("Image does not exist") 549f97da1f7SAlex Bennée return 1 550f97da1f7SAlex Bennée 5517b882245SAlex Bennée if args.checktype == "checksum": 5527b882245SAlex Bennée if not args.dockerfile: 5537b882245SAlex Bennée print("Need a dockerfile for tag:%s" % (tag)) 5547b882245SAlex Bennée return 1 5557b882245SAlex Bennée 5567b882245SAlex Bennée dockerfile = open(args.dockerfile, "rb").read() 5577b882245SAlex Bennée 558f97da1f7SAlex Bennée if dkr.image_matches_dockerfile(tag, dockerfile): 559f97da1f7SAlex Bennée if not args.quiet: 560f97da1f7SAlex Bennée print("Image is up to date") 561f97da1f7SAlex Bennée return 0 562f97da1f7SAlex Bennée else: 563f97da1f7SAlex Bennée print("Image needs updating") 564f97da1f7SAlex Bennée return 1 5657b882245SAlex Bennée elif args.checktype == "age": 5667b882245SAlex Bennée timestr = dkr.get_image_creation_time(info).split(".")[0] 5677b882245SAlex Bennée created = datetime.strptime(timestr, "%Y-%m-%dT%H:%M:%S") 5687b882245SAlex Bennée past = datetime.now() - timedelta(minutes=args.olderthan) 5697b882245SAlex Bennée if created < past: 5707b882245SAlex Bennée print ("Image created @ %s more than %d minutes old" % 5717b882245SAlex Bennée (timestr, args.olderthan)) 5727b882245SAlex Bennée return 1 5737b882245SAlex Bennée else: 5747b882245SAlex Bennée if not args.quiet: 5757b882245SAlex Bennée print ("Image less than %d minutes old" % (args.olderthan)) 5767b882245SAlex Bennée return 0 577f97da1f7SAlex Bennée 578f97da1f7SAlex Bennée 5794485b04bSFam Zhengdef main(): 5804485b04bSFam Zheng parser = argparse.ArgumentParser(description="A Docker helper", 5814485b04bSFam Zheng usage="%s <subcommand> ..." % os.path.basename(sys.argv[0])) 5824485b04bSFam Zheng subparsers = parser.add_subparsers(title="subcommands", help=None) 5834485b04bSFam Zheng for cls in SubCommand.__subclasses__(): 5844485b04bSFam Zheng cmd = cls() 5854485b04bSFam Zheng subp = subparsers.add_parser(cmd.name, help=cmd.__doc__) 5864485b04bSFam Zheng cmd.shared_args(subp) 5874485b04bSFam Zheng cmd.args(subp) 5884485b04bSFam Zheng subp.set_defaults(cmdobj=cmd) 5894485b04bSFam Zheng args, argv = parser.parse_known_args() 5904485b04bSFam Zheng return args.cmdobj.run(args, argv) 5914485b04bSFam Zheng 5924485b04bSFam Zhengif __name__ == "__main__": 5934485b04bSFam Zheng sys.exit(main()) 594