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 99504ca3c2SAlex Bennéedef _copy_binary_with_libs(src, dest_dir): 100e50a6121SStefan Weil """Copy a binary executable and all its dependent libraries. 101504ca3c2SAlex Bennée 102504ca3c2SAlex Bennée This does rely on the host file-system being fairly multi-arch 103504ca3c2SAlex Bennée aware so the file don't clash with the guests layout.""" 104504ca3c2SAlex Bennée 105504ca3c2SAlex Bennée _copy_with_mkdir(src, dest_dir, "/usr/bin") 106504ca3c2SAlex Bennée 107504ca3c2SAlex Bennée libs = _get_so_libs(src) 108504ca3c2SAlex Bennée if libs: 109504ca3c2SAlex Bennée for l in libs: 110504ca3c2SAlex Bennée so_path = os.path.dirname(l) 111504ca3c2SAlex Bennée _copy_with_mkdir(l , dest_dir, so_path) 112504ca3c2SAlex Bennée 11315352decSAlex Bennée 11415352decSAlex Bennéedef _check_binfmt_misc(executable): 11515352decSAlex Bennée """Check binfmt_misc has entry for executable in the right place. 11615352decSAlex Bennée 11715352decSAlex Bennée The details of setting up binfmt_misc are outside the scope of 11815352decSAlex Bennée this script but we should at least fail early with a useful 11915352decSAlex Bennée message if it won't work.""" 12015352decSAlex Bennée 12115352decSAlex Bennée binary = os.path.basename(executable) 12215352decSAlex Bennée binfmt_entry = "/proc/sys/fs/binfmt_misc/%s" % (binary) 12315352decSAlex Bennée 12415352decSAlex Bennée if not os.path.exists(binfmt_entry): 12515352decSAlex Bennée print ("No binfmt_misc entry for %s" % (binary)) 1267e81d198SAlex Bennée return None 12715352decSAlex Bennée 12815352decSAlex Bennée with open(binfmt_entry) as x: entry = x.read() 12915352decSAlex Bennée 130*43c898b7SAlex Bennée if re.search("flags:.*F.*\n", entry): 131*43c898b7SAlex Bennée print("binfmt_misc for %s uses persistent(F) mapping to host binary\n" % 132*43c898b7SAlex Bennée (binary)) 133*43c898b7SAlex Bennée return None 134*43c898b7SAlex Bennée 1357e81d198SAlex Bennée m = re.search("interpreter (\S+)\n", entry) 1367e81d198SAlex Bennée interp = m.group(1) 1377e81d198SAlex Bennée if interp and interp != executable: 1387e81d198SAlex Bennée print("binfmt_misc for %s does not point to %s, using %s" % 1397e81d198SAlex Bennée (binary, executable, interp)) 14015352decSAlex Bennée 1417e81d198SAlex Bennée return interp 14215352decSAlex Bennée 143c1958e9dSFam Zhengdef _read_qemu_dockerfile(img_name): 144547cb45eSAlex Bennée # special case for Debian linux-user images 145547cb45eSAlex Bennée if img_name.startswith("debian") and img_name.endswith("user"): 146547cb45eSAlex Bennée img_name = "debian-bootstrap" 147547cb45eSAlex Bennée 148c1958e9dSFam Zheng df = os.path.join(os.path.dirname(__file__), "dockerfiles", 149c1958e9dSFam Zheng img_name + ".docker") 150c1958e9dSFam Zheng return open(df, "r").read() 151c1958e9dSFam Zheng 152c1958e9dSFam Zhengdef _dockerfile_preprocess(df): 153c1958e9dSFam Zheng out = "" 154c1958e9dSFam Zheng for l in df.splitlines(): 155c1958e9dSFam Zheng if len(l.strip()) == 0 or l.startswith("#"): 156c1958e9dSFam Zheng continue 157c1958e9dSFam Zheng from_pref = "FROM qemu:" 158c1958e9dSFam Zheng if l.startswith(from_pref): 159c1958e9dSFam Zheng # TODO: Alternatively we could replace this line with "FROM $ID" 160c1958e9dSFam Zheng # where $ID is the image's hex id obtained with 161c1958e9dSFam Zheng # $ docker images $IMAGE --format="{{.Id}}" 162c1958e9dSFam Zheng # but unfortunately that's not supported by RHEL 7. 163c1958e9dSFam Zheng inlining = _read_qemu_dockerfile(l[len(from_pref):]) 164c1958e9dSFam Zheng out += _dockerfile_preprocess(inlining) 165c1958e9dSFam Zheng continue 166c1958e9dSFam Zheng out += l + "\n" 167c1958e9dSFam Zheng return out 168c1958e9dSFam Zheng 1694485b04bSFam Zhengclass Docker(object): 1704485b04bSFam Zheng """ Running Docker commands """ 1714485b04bSFam Zheng def __init__(self): 1724485b04bSFam Zheng self._command = _guess_docker_command() 1734485b04bSFam Zheng self._instances = [] 1744485b04bSFam Zheng atexit.register(self._kill_instances) 17597cba1a1SFam Zheng signal.signal(signal.SIGTERM, self._kill_instances) 17697cba1a1SFam Zheng signal.signal(signal.SIGHUP, self._kill_instances) 1774485b04bSFam Zheng 17858bf7b6dSFam Zheng def _do(self, cmd, quiet=True, **kwargs): 1794485b04bSFam Zheng if quiet: 180c9772570SSascha Silbe kwargs["stdout"] = DEVNULL 1814485b04bSFam Zheng return subprocess.call(self._command + cmd, **kwargs) 1824485b04bSFam Zheng 1830b95ff72SFam Zheng def _do_check(self, cmd, quiet=True, **kwargs): 1840b95ff72SFam Zheng if quiet: 1850b95ff72SFam Zheng kwargs["stdout"] = DEVNULL 1860b95ff72SFam Zheng return subprocess.check_call(self._command + cmd, **kwargs) 1870b95ff72SFam Zheng 1884485b04bSFam Zheng def _do_kill_instances(self, only_known, only_active=True): 1894485b04bSFam Zheng cmd = ["ps", "-q"] 1904485b04bSFam Zheng if not only_active: 1914485b04bSFam Zheng cmd.append("-a") 1924485b04bSFam Zheng for i in self._output(cmd).split(): 1934485b04bSFam Zheng resp = self._output(["inspect", i]) 1944485b04bSFam Zheng labels = json.loads(resp)[0]["Config"]["Labels"] 1954485b04bSFam Zheng active = json.loads(resp)[0]["State"]["Running"] 1964485b04bSFam Zheng if not labels: 1974485b04bSFam Zheng continue 1984485b04bSFam Zheng instance_uuid = labels.get("com.qemu.instance.uuid", None) 1994485b04bSFam Zheng if not instance_uuid: 2004485b04bSFam Zheng continue 2014485b04bSFam Zheng if only_known and instance_uuid not in self._instances: 2024485b04bSFam Zheng continue 203f03868bdSEduardo Habkost print("Terminating", i) 2044485b04bSFam Zheng if active: 2054485b04bSFam Zheng self._do(["kill", i]) 2064485b04bSFam Zheng self._do(["rm", i]) 2074485b04bSFam Zheng 2084485b04bSFam Zheng def clean(self): 2094485b04bSFam Zheng self._do_kill_instances(False, False) 2104485b04bSFam Zheng return 0 2114485b04bSFam Zheng 21297cba1a1SFam Zheng def _kill_instances(self, *args, **kwargs): 2134485b04bSFam Zheng return self._do_kill_instances(True) 2144485b04bSFam Zheng 2154485b04bSFam Zheng def _output(self, cmd, **kwargs): 2164485b04bSFam Zheng return subprocess.check_output(self._command + cmd, 2174485b04bSFam Zheng stderr=subprocess.STDOUT, 2184485b04bSFam Zheng **kwargs) 2194485b04bSFam Zheng 220f97da1f7SAlex Bennée def inspect_tag(self, tag): 221f97da1f7SAlex Bennée try: 222f97da1f7SAlex Bennée return self._output(["inspect", tag]) 223f97da1f7SAlex Bennée except subprocess.CalledProcessError: 224f97da1f7SAlex Bennée return None 225f97da1f7SAlex Bennée 2267b882245SAlex Bennée def get_image_creation_time(self, info): 2277b882245SAlex Bennée return json.loads(info)[0]["Created"] 2287b882245SAlex Bennée 2294485b04bSFam Zheng def get_image_dockerfile_checksum(self, tag): 230f97da1f7SAlex Bennée resp = self.inspect_tag(tag) 2314485b04bSFam Zheng labels = json.loads(resp)[0]["Config"].get("Labels", {}) 2324485b04bSFam Zheng return labels.get("com.qemu.dockerfile-checksum", "") 2334485b04bSFam Zheng 234414a8ce5SAlex Bennée def build_image(self, tag, docker_dir, dockerfile, 235438d1168SPhilippe Mathieu-Daudé quiet=True, user=False, argv=None, extra_files_cksum=[]): 2364485b04bSFam Zheng if argv == None: 2374485b04bSFam Zheng argv = [] 2384485b04bSFam Zheng 239a9f8d038SAlex Bennée tmp_df = tempfile.NamedTemporaryFile(dir=docker_dir, suffix=".docker") 2404485b04bSFam Zheng tmp_df.write(dockerfile) 2414485b04bSFam Zheng 242414a8ce5SAlex Bennée if user: 243414a8ce5SAlex Bennée uid = os.getuid() 244414a8ce5SAlex Bennée uname = getpwuid(uid).pw_name 245414a8ce5SAlex Bennée tmp_df.write("\n") 246414a8ce5SAlex Bennée tmp_df.write("RUN id %s 2>/dev/null || useradd -u %d -U %s" % 247414a8ce5SAlex Bennée (uname, uid, uname)) 248414a8ce5SAlex Bennée 2494485b04bSFam Zheng tmp_df.write("\n") 2504485b04bSFam Zheng tmp_df.write("LABEL com.qemu.dockerfile-checksum=%s" % 251f9172822SAlex Bennée _text_checksum(_dockerfile_preprocess(dockerfile))) 252f9172822SAlex Bennée for f, c in extra_files_cksum: 253f9172822SAlex Bennée tmp_df.write("LABEL com.qemu.%s-checksum=%s" % (f, c)) 254f9172822SAlex Bennée 2554485b04bSFam Zheng tmp_df.flush() 256a9f8d038SAlex Bennée 2570b95ff72SFam Zheng self._do_check(["build", "-t", tag, "-f", tmp_df.name] + argv + \ 258a9f8d038SAlex Bennée [docker_dir], 2594485b04bSFam Zheng quiet=quiet) 2604485b04bSFam Zheng 2616e733da6SAlex Bennée def update_image(self, tag, tarball, quiet=True): 2626e733da6SAlex Bennée "Update a tagged image using " 2636e733da6SAlex Bennée 2640b95ff72SFam Zheng self._do_check(["build", "-t", tag, "-"], quiet=quiet, stdin=tarball) 2656e733da6SAlex Bennée 2664485b04bSFam Zheng def image_matches_dockerfile(self, tag, dockerfile): 2674485b04bSFam Zheng try: 2684485b04bSFam Zheng checksum = self.get_image_dockerfile_checksum(tag) 2694485b04bSFam Zheng except Exception: 2704485b04bSFam Zheng return False 271c1958e9dSFam Zheng return checksum == _text_checksum(_dockerfile_preprocess(dockerfile)) 2724485b04bSFam Zheng 2734485b04bSFam Zheng def run(self, cmd, keep, quiet): 2744485b04bSFam Zheng label = uuid.uuid1().hex 2754485b04bSFam Zheng if not keep: 2764485b04bSFam Zheng self._instances.append(label) 2770b95ff72SFam Zheng ret = self._do_check(["run", "--label", 2784485b04bSFam Zheng "com.qemu.instance.uuid=" + label] + cmd, 2794485b04bSFam Zheng quiet=quiet) 2804485b04bSFam Zheng if not keep: 2814485b04bSFam Zheng self._instances.remove(label) 2824485b04bSFam Zheng return ret 2834485b04bSFam Zheng 2844b08af60SFam Zheng def command(self, cmd, argv, quiet): 2854b08af60SFam Zheng return self._do([cmd] + argv, quiet=quiet) 2864b08af60SFam Zheng 2874485b04bSFam Zhengclass SubCommand(object): 2884485b04bSFam Zheng """A SubCommand template base class""" 2894485b04bSFam Zheng name = None # Subcommand name 2904485b04bSFam Zheng def shared_args(self, parser): 2914485b04bSFam Zheng parser.add_argument("--quiet", action="store_true", 292e50a6121SStefan Weil help="Run quietly unless an error occurred") 2934485b04bSFam Zheng 2944485b04bSFam Zheng def args(self, parser): 2954485b04bSFam Zheng """Setup argument parser""" 2964485b04bSFam Zheng pass 2974485b04bSFam Zheng def run(self, args, argv): 2984485b04bSFam Zheng """Run command. 2994485b04bSFam Zheng args: parsed argument by argument parser. 3004485b04bSFam Zheng argv: remaining arguments from sys.argv. 3014485b04bSFam Zheng """ 3024485b04bSFam Zheng pass 3034485b04bSFam Zheng 3044485b04bSFam Zhengclass RunCommand(SubCommand): 3054485b04bSFam Zheng """Invoke docker run and take care of cleaning up""" 3064485b04bSFam Zheng name = "run" 3074485b04bSFam Zheng def args(self, parser): 3084485b04bSFam Zheng parser.add_argument("--keep", action="store_true", 3094485b04bSFam Zheng help="Don't remove image when command completes") 3104485b04bSFam Zheng def run(self, args, argv): 3114485b04bSFam Zheng return Docker().run(argv, args.keep, quiet=args.quiet) 3124485b04bSFam Zheng 3134485b04bSFam Zhengclass BuildCommand(SubCommand): 3144485b04bSFam Zheng """ Build docker image out of a dockerfile. Arguments: <tag> <dockerfile>""" 3154485b04bSFam Zheng name = "build" 3164485b04bSFam Zheng def args(self, parser): 317504ca3c2SAlex Bennée parser.add_argument("--include-executable", "-e", 318504ca3c2SAlex Bennée help="""Specify a binary that will be copied to the 319504ca3c2SAlex Bennée container together with all its dependent 320504ca3c2SAlex Bennée libraries""") 3214c84f662SPhilippe Mathieu-Daudé parser.add_argument("--extra-files", "-f", nargs='*', 3224c84f662SPhilippe Mathieu-Daudé help="""Specify files that will be copied in the 3234c84f662SPhilippe Mathieu-Daudé Docker image, fulfilling the ADD directive from the 3244c84f662SPhilippe Mathieu-Daudé Dockerfile""") 325414a8ce5SAlex Bennée parser.add_argument("--add-current-user", "-u", dest="user", 326414a8ce5SAlex Bennée action="store_true", 327414a8ce5SAlex Bennée help="Add the current user to image's passwd") 3284485b04bSFam Zheng parser.add_argument("tag", 3294485b04bSFam Zheng help="Image Tag") 3304485b04bSFam Zheng parser.add_argument("dockerfile", 3314485b04bSFam Zheng help="Dockerfile name") 3324485b04bSFam Zheng 3334485b04bSFam Zheng def run(self, args, argv): 3344485b04bSFam Zheng dockerfile = open(args.dockerfile, "rb").read() 3354485b04bSFam Zheng tag = args.tag 3364485b04bSFam Zheng 3374485b04bSFam Zheng dkr = Docker() 3386fe3ae3fSAlex Bennée if "--no-cache" not in argv and \ 3396fe3ae3fSAlex Bennée dkr.image_matches_dockerfile(tag, dockerfile): 3404485b04bSFam Zheng if not args.quiet: 341f03868bdSEduardo Habkost print("Image is up to date.") 342a9f8d038SAlex Bennée else: 343a9f8d038SAlex Bennée # Create a docker context directory for the build 344a9f8d038SAlex Bennée docker_dir = tempfile.mkdtemp(prefix="docker_build") 3454485b04bSFam Zheng 34615352decSAlex Bennée # Validate binfmt_misc will work 34715352decSAlex Bennée if args.include_executable: 34815352decSAlex Bennée if not _check_binfmt_misc(args.include_executable): 34915352decSAlex Bennée return 1 35015352decSAlex Bennée 351920776eaSAlex Bennée # Is there a .pre file to run in the build context? 352920776eaSAlex Bennée docker_pre = os.path.splitext(args.dockerfile)[0]+".pre" 353920776eaSAlex Bennée if os.path.exists(docker_pre): 354f8042deaSSascha Silbe stdout = DEVNULL if args.quiet else None 355920776eaSAlex Bennée rc = subprocess.call(os.path.realpath(docker_pre), 356f8042deaSSascha Silbe cwd=docker_dir, stdout=stdout) 357920776eaSAlex Bennée if rc == 3: 358f03868bdSEduardo Habkost print("Skip") 359920776eaSAlex Bennée return 0 360920776eaSAlex Bennée elif rc != 0: 361f03868bdSEduardo Habkost print("%s exited with code %d" % (docker_pre, rc)) 362920776eaSAlex Bennée return 1 363920776eaSAlex Bennée 3644c84f662SPhilippe Mathieu-Daudé # Copy any extra files into the Docker context. These can be 3654c84f662SPhilippe Mathieu-Daudé # included by the use of the ADD directive in the Dockerfile. 366438d1168SPhilippe Mathieu-Daudé cksum = [] 367504ca3c2SAlex Bennée if args.include_executable: 368438d1168SPhilippe Mathieu-Daudé # FIXME: there is no checksum of this executable and the linked 369438d1168SPhilippe Mathieu-Daudé # libraries, once the image built any change of this executable 370438d1168SPhilippe Mathieu-Daudé # or any library won't trigger another build. 3714c84f662SPhilippe Mathieu-Daudé _copy_binary_with_libs(args.include_executable, docker_dir) 3724c84f662SPhilippe Mathieu-Daudé for filename in args.extra_files or []: 3734c84f662SPhilippe Mathieu-Daudé _copy_with_mkdir(filename, docker_dir) 374f9172822SAlex Bennée cksum += [(filename, _file_checksum(filename))] 375504ca3c2SAlex Bennée 37606cc3551SPhilippe Mathieu-Daudé argv += ["--build-arg=" + k.lower() + "=" + v 37706cc3551SPhilippe Mathieu-Daudé for k, v in os.environ.iteritems() 37806cc3551SPhilippe Mathieu-Daudé if k.lower() in FILTERED_ENV_NAMES] 379a9f8d038SAlex Bennée dkr.build_image(tag, docker_dir, dockerfile, 380438d1168SPhilippe Mathieu-Daudé quiet=args.quiet, user=args.user, argv=argv, 381438d1168SPhilippe Mathieu-Daudé extra_files_cksum=cksum) 382a9f8d038SAlex Bennée 383a9f8d038SAlex Bennée rmtree(docker_dir) 384a9f8d038SAlex Bennée 3854485b04bSFam Zheng return 0 3864485b04bSFam Zheng 3876e733da6SAlex Bennéeclass UpdateCommand(SubCommand): 3886e733da6SAlex Bennée """ Update a docker image with new executables. Arguments: <tag> <executable>""" 3896e733da6SAlex Bennée name = "update" 3906e733da6SAlex Bennée def args(self, parser): 3916e733da6SAlex Bennée parser.add_argument("tag", 3926e733da6SAlex Bennée help="Image Tag") 3936e733da6SAlex Bennée parser.add_argument("executable", 3946e733da6SAlex Bennée help="Executable to copy") 3956e733da6SAlex Bennée 3966e733da6SAlex Bennée def run(self, args, argv): 3976e733da6SAlex Bennée # Create a temporary tarball with our whole build context and 3986e733da6SAlex Bennée # dockerfile for the update 3996e733da6SAlex Bennée tmp = tempfile.NamedTemporaryFile(suffix="dckr.tar.gz") 4006e733da6SAlex Bennée tmp_tar = TarFile(fileobj=tmp, mode='w') 4016e733da6SAlex Bennée 4027e81d198SAlex Bennée # Add the executable to the tarball, using the current 4037e81d198SAlex Bennée # configured binfmt_misc path. 4047e81d198SAlex Bennée ff = _check_binfmt_misc(args.executable) 4057e81d198SAlex Bennée if not ff: 4066e733da6SAlex Bennée bn = os.path.basename(args.executable) 4076e733da6SAlex Bennée ff = "/usr/bin/%s" % bn 4087e81d198SAlex Bennée print ("No binfmt_misc configured: copied to %s" % (ff)) 4097e81d198SAlex Bennée 4106e733da6SAlex Bennée tmp_tar.add(args.executable, arcname=ff) 4116e733da6SAlex Bennée 4126e733da6SAlex Bennée # Add any associated libraries 4136e733da6SAlex Bennée libs = _get_so_libs(args.executable) 4146e733da6SAlex Bennée if libs: 4156e733da6SAlex Bennée for l in libs: 4166e733da6SAlex Bennée tmp_tar.add(os.path.realpath(l), arcname=l) 4176e733da6SAlex Bennée 4186e733da6SAlex Bennée # Create a Docker buildfile 4196e733da6SAlex Bennée df = StringIO() 4206e733da6SAlex Bennée df.write("FROM %s\n" % args.tag) 4216e733da6SAlex Bennée df.write("ADD . /\n") 4226e733da6SAlex Bennée df.seek(0) 4236e733da6SAlex Bennée 4246e733da6SAlex Bennée df_tar = TarInfo(name="Dockerfile") 4256e733da6SAlex Bennée df_tar.size = len(df.buf) 4266e733da6SAlex Bennée tmp_tar.addfile(df_tar, fileobj=df) 4276e733da6SAlex Bennée 4286e733da6SAlex Bennée tmp_tar.close() 4296e733da6SAlex Bennée 4306e733da6SAlex Bennée # reset the file pointers 4316e733da6SAlex Bennée tmp.flush() 4326e733da6SAlex Bennée tmp.seek(0) 4336e733da6SAlex Bennée 4346e733da6SAlex Bennée # Run the build with our tarball context 4356e733da6SAlex Bennée dkr = Docker() 4366e733da6SAlex Bennée dkr.update_image(args.tag, tmp, quiet=args.quiet) 4376e733da6SAlex Bennée 4386e733da6SAlex Bennée return 0 4396e733da6SAlex Bennée 4404485b04bSFam Zhengclass CleanCommand(SubCommand): 4414485b04bSFam Zheng """Clean up docker instances""" 4424485b04bSFam Zheng name = "clean" 4434485b04bSFam Zheng def run(self, args, argv): 4444485b04bSFam Zheng Docker().clean() 4454485b04bSFam Zheng return 0 4464485b04bSFam Zheng 4474b08af60SFam Zhengclass ImagesCommand(SubCommand): 4484b08af60SFam Zheng """Run "docker images" command""" 4494b08af60SFam Zheng name = "images" 4504b08af60SFam Zheng def run(self, args, argv): 4514b08af60SFam Zheng return Docker().command("images", argv, args.quiet) 4524b08af60SFam Zheng 45315df9d37SAlex Bennée 45415df9d37SAlex Bennéeclass ProbeCommand(SubCommand): 45515df9d37SAlex Bennée """Probe if we can run docker automatically""" 45615df9d37SAlex Bennée name = "probe" 45715df9d37SAlex Bennée 45815df9d37SAlex Bennée def run(self, args, argv): 45915df9d37SAlex Bennée try: 46015df9d37SAlex Bennée docker = Docker() 46115df9d37SAlex Bennée if docker._command[0] == "docker": 462f03868bdSEduardo Habkost print("yes") 46315df9d37SAlex Bennée elif docker._command[0] == "sudo": 464f03868bdSEduardo Habkost print("sudo") 46515df9d37SAlex Bennée except Exception: 466f03868bdSEduardo Habkost print("no") 46715df9d37SAlex Bennée 46815df9d37SAlex Bennée return 46915df9d37SAlex Bennée 47015df9d37SAlex Bennée 4715e03c2d8SAlex Bennéeclass CcCommand(SubCommand): 4725e03c2d8SAlex Bennée """Compile sources with cc in images""" 4735e03c2d8SAlex Bennée name = "cc" 4745e03c2d8SAlex Bennée 4755e03c2d8SAlex Bennée def args(self, parser): 4765e03c2d8SAlex Bennée parser.add_argument("--image", "-i", required=True, 4775e03c2d8SAlex Bennée help="The docker image in which to run cc") 47899cfdb86SAlex Bennée parser.add_argument("--cc", default="cc", 47999cfdb86SAlex Bennée help="The compiler executable to call") 48050b72738SAlex Bennée parser.add_argument("--user", 48150b72738SAlex Bennée help="The user-id to run under") 4825e03c2d8SAlex Bennée parser.add_argument("--source-path", "-s", nargs="*", dest="paths", 4835e03c2d8SAlex Bennée help="""Extra paths to (ro) mount into container for 4845e03c2d8SAlex Bennée reading sources""") 4855e03c2d8SAlex Bennée 4865e03c2d8SAlex Bennée def run(self, args, argv): 4875e03c2d8SAlex Bennée if argv and argv[0] == "--": 4885e03c2d8SAlex Bennée argv = argv[1:] 4895e03c2d8SAlex Bennée cwd = os.getcwd() 4905e03c2d8SAlex Bennée cmd = ["--rm", "-w", cwd, 4915e03c2d8SAlex Bennée "-v", "%s:%s:rw" % (cwd, cwd)] 4925e03c2d8SAlex Bennée if args.paths: 4935e03c2d8SAlex Bennée for p in args.paths: 4945e03c2d8SAlex Bennée cmd += ["-v", "%s:%s:ro,z" % (p, p)] 49550b72738SAlex Bennée if args.user: 49650b72738SAlex Bennée cmd += ["-u", args.user] 49799cfdb86SAlex Bennée cmd += [args.image, args.cc] 4985e03c2d8SAlex Bennée cmd += argv 4995e03c2d8SAlex Bennée return Docker().command("run", cmd, args.quiet) 5005e03c2d8SAlex Bennée 5015e03c2d8SAlex Bennée 502f97da1f7SAlex Bennéeclass CheckCommand(SubCommand): 503f97da1f7SAlex Bennée """Check if we need to re-build a docker image out of a dockerfile. 504f97da1f7SAlex Bennée Arguments: <tag> <dockerfile>""" 505f97da1f7SAlex Bennée name = "check" 506f97da1f7SAlex Bennée 507f97da1f7SAlex Bennée def args(self, parser): 508f97da1f7SAlex Bennée parser.add_argument("tag", 509f97da1f7SAlex Bennée help="Image Tag") 5107b882245SAlex Bennée parser.add_argument("dockerfile", default=None, 5117b882245SAlex Bennée help="Dockerfile name", nargs='?') 5127b882245SAlex Bennée parser.add_argument("--checktype", choices=["checksum", "age"], 5137b882245SAlex Bennée default="checksum", help="check type") 5147b882245SAlex Bennée parser.add_argument("--olderthan", default=60, type=int, 5157b882245SAlex Bennée help="number of minutes") 516f97da1f7SAlex Bennée 517f97da1f7SAlex Bennée def run(self, args, argv): 518f97da1f7SAlex Bennée tag = args.tag 519f97da1f7SAlex Bennée 52043e1b2ffSAlex Bennée try: 521f97da1f7SAlex Bennée dkr = Docker() 52243e1b2ffSAlex Bennée except: 52343e1b2ffSAlex Bennée print("Docker not set up") 52443e1b2ffSAlex Bennée return 1 52543e1b2ffSAlex Bennée 526f97da1f7SAlex Bennée info = dkr.inspect_tag(tag) 527f97da1f7SAlex Bennée if info is None: 528f97da1f7SAlex Bennée print("Image does not exist") 529f97da1f7SAlex Bennée return 1 530f97da1f7SAlex Bennée 5317b882245SAlex Bennée if args.checktype == "checksum": 5327b882245SAlex Bennée if not args.dockerfile: 5337b882245SAlex Bennée print("Need a dockerfile for tag:%s" % (tag)) 5347b882245SAlex Bennée return 1 5357b882245SAlex Bennée 5367b882245SAlex Bennée dockerfile = open(args.dockerfile, "rb").read() 5377b882245SAlex Bennée 538f97da1f7SAlex Bennée if dkr.image_matches_dockerfile(tag, dockerfile): 539f97da1f7SAlex Bennée if not args.quiet: 540f97da1f7SAlex Bennée print("Image is up to date") 541f97da1f7SAlex Bennée return 0 542f97da1f7SAlex Bennée else: 543f97da1f7SAlex Bennée print("Image needs updating") 544f97da1f7SAlex Bennée return 1 5457b882245SAlex Bennée elif args.checktype == "age": 5467b882245SAlex Bennée timestr = dkr.get_image_creation_time(info).split(".")[0] 5477b882245SAlex Bennée created = datetime.strptime(timestr, "%Y-%m-%dT%H:%M:%S") 5487b882245SAlex Bennée past = datetime.now() - timedelta(minutes=args.olderthan) 5497b882245SAlex Bennée if created < past: 5507b882245SAlex Bennée print ("Image created @ %s more than %d minutes old" % 5517b882245SAlex Bennée (timestr, args.olderthan)) 5527b882245SAlex Bennée return 1 5537b882245SAlex Bennée else: 5547b882245SAlex Bennée if not args.quiet: 5557b882245SAlex Bennée print ("Image less than %d minutes old" % (args.olderthan)) 5567b882245SAlex Bennée return 0 557f97da1f7SAlex Bennée 558f97da1f7SAlex Bennée 5594485b04bSFam Zhengdef main(): 5604485b04bSFam Zheng parser = argparse.ArgumentParser(description="A Docker helper", 5614485b04bSFam Zheng usage="%s <subcommand> ..." % os.path.basename(sys.argv[0])) 5624485b04bSFam Zheng subparsers = parser.add_subparsers(title="subcommands", help=None) 5634485b04bSFam Zheng for cls in SubCommand.__subclasses__(): 5644485b04bSFam Zheng cmd = cls() 5654485b04bSFam Zheng subp = subparsers.add_parser(cmd.name, help=cmd.__doc__) 5664485b04bSFam Zheng cmd.shared_args(subp) 5674485b04bSFam Zheng cmd.args(subp) 5684485b04bSFam Zheng subp.set_defaults(cmdobj=cmd) 5694485b04bSFam Zheng args, argv = parser.parse_known_args() 5704485b04bSFam Zheng return args.cmdobj.run(args, argv) 5714485b04bSFam Zheng 5724485b04bSFam Zhengif __name__ == "__main__": 5734485b04bSFam Zheng sys.exit(main()) 574