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): 100*e50a6121SStefan 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)) 12615352decSAlex Bennée return False 12715352decSAlex Bennée 12815352decSAlex Bennée with open(binfmt_entry) as x: entry = x.read() 12915352decSAlex Bennée 13015352decSAlex Bennée qpath = "/usr/bin/%s" % (binary) 13115352decSAlex Bennée if not re.search("interpreter %s\n" % (qpath), entry): 13215352decSAlex Bennée print ("binfmt_misc for %s does not point to %s" % (binary, qpath)) 13315352decSAlex Bennée return False 13415352decSAlex Bennée 13515352decSAlex Bennée return True 13615352decSAlex Bennée 13715352decSAlex Bennée 138c1958e9dSFam Zhengdef _read_qemu_dockerfile(img_name): 139547cb45eSAlex Bennée # special case for Debian linux-user images 140547cb45eSAlex Bennée if img_name.startswith("debian") and img_name.endswith("user"): 141547cb45eSAlex Bennée img_name = "debian-bootstrap" 142547cb45eSAlex Bennée 143c1958e9dSFam Zheng df = os.path.join(os.path.dirname(__file__), "dockerfiles", 144c1958e9dSFam Zheng img_name + ".docker") 145c1958e9dSFam Zheng return open(df, "r").read() 146c1958e9dSFam Zheng 147c1958e9dSFam Zhengdef _dockerfile_preprocess(df): 148c1958e9dSFam Zheng out = "" 149c1958e9dSFam Zheng for l in df.splitlines(): 150c1958e9dSFam Zheng if len(l.strip()) == 0 or l.startswith("#"): 151c1958e9dSFam Zheng continue 152c1958e9dSFam Zheng from_pref = "FROM qemu:" 153c1958e9dSFam Zheng if l.startswith(from_pref): 154c1958e9dSFam Zheng # TODO: Alternatively we could replace this line with "FROM $ID" 155c1958e9dSFam Zheng # where $ID is the image's hex id obtained with 156c1958e9dSFam Zheng # $ docker images $IMAGE --format="{{.Id}}" 157c1958e9dSFam Zheng # but unfortunately that's not supported by RHEL 7. 158c1958e9dSFam Zheng inlining = _read_qemu_dockerfile(l[len(from_pref):]) 159c1958e9dSFam Zheng out += _dockerfile_preprocess(inlining) 160c1958e9dSFam Zheng continue 161c1958e9dSFam Zheng out += l + "\n" 162c1958e9dSFam Zheng return out 163c1958e9dSFam Zheng 1644485b04bSFam Zhengclass Docker(object): 1654485b04bSFam Zheng """ Running Docker commands """ 1664485b04bSFam Zheng def __init__(self): 1674485b04bSFam Zheng self._command = _guess_docker_command() 1684485b04bSFam Zheng self._instances = [] 1694485b04bSFam Zheng atexit.register(self._kill_instances) 17097cba1a1SFam Zheng signal.signal(signal.SIGTERM, self._kill_instances) 17197cba1a1SFam Zheng signal.signal(signal.SIGHUP, self._kill_instances) 1724485b04bSFam Zheng 17358bf7b6dSFam Zheng def _do(self, cmd, quiet=True, **kwargs): 1744485b04bSFam Zheng if quiet: 175c9772570SSascha Silbe kwargs["stdout"] = DEVNULL 1764485b04bSFam Zheng return subprocess.call(self._command + cmd, **kwargs) 1774485b04bSFam Zheng 1780b95ff72SFam Zheng def _do_check(self, cmd, quiet=True, **kwargs): 1790b95ff72SFam Zheng if quiet: 1800b95ff72SFam Zheng kwargs["stdout"] = DEVNULL 1810b95ff72SFam Zheng return subprocess.check_call(self._command + cmd, **kwargs) 1820b95ff72SFam Zheng 1834485b04bSFam Zheng def _do_kill_instances(self, only_known, only_active=True): 1844485b04bSFam Zheng cmd = ["ps", "-q"] 1854485b04bSFam Zheng if not only_active: 1864485b04bSFam Zheng cmd.append("-a") 1874485b04bSFam Zheng for i in self._output(cmd).split(): 1884485b04bSFam Zheng resp = self._output(["inspect", i]) 1894485b04bSFam Zheng labels = json.loads(resp)[0]["Config"]["Labels"] 1904485b04bSFam Zheng active = json.loads(resp)[0]["State"]["Running"] 1914485b04bSFam Zheng if not labels: 1924485b04bSFam Zheng continue 1934485b04bSFam Zheng instance_uuid = labels.get("com.qemu.instance.uuid", None) 1944485b04bSFam Zheng if not instance_uuid: 1954485b04bSFam Zheng continue 1964485b04bSFam Zheng if only_known and instance_uuid not in self._instances: 1974485b04bSFam Zheng continue 198f03868bdSEduardo Habkost print("Terminating", i) 1994485b04bSFam Zheng if active: 2004485b04bSFam Zheng self._do(["kill", i]) 2014485b04bSFam Zheng self._do(["rm", i]) 2024485b04bSFam Zheng 2034485b04bSFam Zheng def clean(self): 2044485b04bSFam Zheng self._do_kill_instances(False, False) 2054485b04bSFam Zheng return 0 2064485b04bSFam Zheng 20797cba1a1SFam Zheng def _kill_instances(self, *args, **kwargs): 2084485b04bSFam Zheng return self._do_kill_instances(True) 2094485b04bSFam Zheng 2104485b04bSFam Zheng def _output(self, cmd, **kwargs): 2114485b04bSFam Zheng return subprocess.check_output(self._command + cmd, 2124485b04bSFam Zheng stderr=subprocess.STDOUT, 2134485b04bSFam Zheng **kwargs) 2144485b04bSFam Zheng 215f97da1f7SAlex Bennée def inspect_tag(self, tag): 216f97da1f7SAlex Bennée try: 217f97da1f7SAlex Bennée return self._output(["inspect", tag]) 218f97da1f7SAlex Bennée except subprocess.CalledProcessError: 219f97da1f7SAlex Bennée return None 220f97da1f7SAlex Bennée 2217b882245SAlex Bennée def get_image_creation_time(self, info): 2227b882245SAlex Bennée return json.loads(info)[0]["Created"] 2237b882245SAlex Bennée 2244485b04bSFam Zheng def get_image_dockerfile_checksum(self, tag): 225f97da1f7SAlex Bennée resp = self.inspect_tag(tag) 2264485b04bSFam Zheng labels = json.loads(resp)[0]["Config"].get("Labels", {}) 2274485b04bSFam Zheng return labels.get("com.qemu.dockerfile-checksum", "") 2284485b04bSFam Zheng 229414a8ce5SAlex Bennée def build_image(self, tag, docker_dir, dockerfile, 230438d1168SPhilippe Mathieu-Daudé quiet=True, user=False, argv=None, extra_files_cksum=[]): 2314485b04bSFam Zheng if argv == None: 2324485b04bSFam Zheng argv = [] 2334485b04bSFam Zheng 234a9f8d038SAlex Bennée tmp_df = tempfile.NamedTemporaryFile(dir=docker_dir, suffix=".docker") 2354485b04bSFam Zheng tmp_df.write(dockerfile) 2364485b04bSFam Zheng 237414a8ce5SAlex Bennée if user: 238414a8ce5SAlex Bennée uid = os.getuid() 239414a8ce5SAlex Bennée uname = getpwuid(uid).pw_name 240414a8ce5SAlex Bennée tmp_df.write("\n") 241414a8ce5SAlex Bennée tmp_df.write("RUN id %s 2>/dev/null || useradd -u %d -U %s" % 242414a8ce5SAlex Bennée (uname, uid, uname)) 243414a8ce5SAlex Bennée 2444485b04bSFam Zheng tmp_df.write("\n") 2454485b04bSFam Zheng tmp_df.write("LABEL com.qemu.dockerfile-checksum=%s" % 246f9172822SAlex Bennée _text_checksum(_dockerfile_preprocess(dockerfile))) 247f9172822SAlex Bennée for f, c in extra_files_cksum: 248f9172822SAlex Bennée tmp_df.write("LABEL com.qemu.%s-checksum=%s" % (f, c)) 249f9172822SAlex Bennée 2504485b04bSFam Zheng tmp_df.flush() 251a9f8d038SAlex Bennée 2520b95ff72SFam Zheng self._do_check(["build", "-t", tag, "-f", tmp_df.name] + argv + \ 253a9f8d038SAlex Bennée [docker_dir], 2544485b04bSFam Zheng quiet=quiet) 2554485b04bSFam Zheng 2566e733da6SAlex Bennée def update_image(self, tag, tarball, quiet=True): 2576e733da6SAlex Bennée "Update a tagged image using " 2586e733da6SAlex Bennée 2590b95ff72SFam Zheng self._do_check(["build", "-t", tag, "-"], quiet=quiet, stdin=tarball) 2606e733da6SAlex Bennée 2614485b04bSFam Zheng def image_matches_dockerfile(self, tag, dockerfile): 2624485b04bSFam Zheng try: 2634485b04bSFam Zheng checksum = self.get_image_dockerfile_checksum(tag) 2644485b04bSFam Zheng except Exception: 2654485b04bSFam Zheng return False 266c1958e9dSFam Zheng return checksum == _text_checksum(_dockerfile_preprocess(dockerfile)) 2674485b04bSFam Zheng 2684485b04bSFam Zheng def run(self, cmd, keep, quiet): 2694485b04bSFam Zheng label = uuid.uuid1().hex 2704485b04bSFam Zheng if not keep: 2714485b04bSFam Zheng self._instances.append(label) 2720b95ff72SFam Zheng ret = self._do_check(["run", "--label", 2734485b04bSFam Zheng "com.qemu.instance.uuid=" + label] + cmd, 2744485b04bSFam Zheng quiet=quiet) 2754485b04bSFam Zheng if not keep: 2764485b04bSFam Zheng self._instances.remove(label) 2774485b04bSFam Zheng return ret 2784485b04bSFam Zheng 2794b08af60SFam Zheng def command(self, cmd, argv, quiet): 2804b08af60SFam Zheng return self._do([cmd] + argv, quiet=quiet) 2814b08af60SFam Zheng 2824485b04bSFam Zhengclass SubCommand(object): 2834485b04bSFam Zheng """A SubCommand template base class""" 2844485b04bSFam Zheng name = None # Subcommand name 2854485b04bSFam Zheng def shared_args(self, parser): 2864485b04bSFam Zheng parser.add_argument("--quiet", action="store_true", 287*e50a6121SStefan Weil help="Run quietly unless an error occurred") 2884485b04bSFam Zheng 2894485b04bSFam Zheng def args(self, parser): 2904485b04bSFam Zheng """Setup argument parser""" 2914485b04bSFam Zheng pass 2924485b04bSFam Zheng def run(self, args, argv): 2934485b04bSFam Zheng """Run command. 2944485b04bSFam Zheng args: parsed argument by argument parser. 2954485b04bSFam Zheng argv: remaining arguments from sys.argv. 2964485b04bSFam Zheng """ 2974485b04bSFam Zheng pass 2984485b04bSFam Zheng 2994485b04bSFam Zhengclass RunCommand(SubCommand): 3004485b04bSFam Zheng """Invoke docker run and take care of cleaning up""" 3014485b04bSFam Zheng name = "run" 3024485b04bSFam Zheng def args(self, parser): 3034485b04bSFam Zheng parser.add_argument("--keep", action="store_true", 3044485b04bSFam Zheng help="Don't remove image when command completes") 3054485b04bSFam Zheng def run(self, args, argv): 3064485b04bSFam Zheng return Docker().run(argv, args.keep, quiet=args.quiet) 3074485b04bSFam Zheng 3084485b04bSFam Zhengclass BuildCommand(SubCommand): 3094485b04bSFam Zheng """ Build docker image out of a dockerfile. Arguments: <tag> <dockerfile>""" 3104485b04bSFam Zheng name = "build" 3114485b04bSFam Zheng def args(self, parser): 312504ca3c2SAlex Bennée parser.add_argument("--include-executable", "-e", 313504ca3c2SAlex Bennée help="""Specify a binary that will be copied to the 314504ca3c2SAlex Bennée container together with all its dependent 315504ca3c2SAlex Bennée libraries""") 3164c84f662SPhilippe Mathieu-Daudé parser.add_argument("--extra-files", "-f", nargs='*', 3174c84f662SPhilippe Mathieu-Daudé help="""Specify files that will be copied in the 3184c84f662SPhilippe Mathieu-Daudé Docker image, fulfilling the ADD directive from the 3194c84f662SPhilippe Mathieu-Daudé Dockerfile""") 320414a8ce5SAlex Bennée parser.add_argument("--add-current-user", "-u", dest="user", 321414a8ce5SAlex Bennée action="store_true", 322414a8ce5SAlex Bennée help="Add the current user to image's passwd") 3234485b04bSFam Zheng parser.add_argument("tag", 3244485b04bSFam Zheng help="Image Tag") 3254485b04bSFam Zheng parser.add_argument("dockerfile", 3264485b04bSFam Zheng help="Dockerfile name") 3274485b04bSFam Zheng 3284485b04bSFam Zheng def run(self, args, argv): 3294485b04bSFam Zheng dockerfile = open(args.dockerfile, "rb").read() 3304485b04bSFam Zheng tag = args.tag 3314485b04bSFam Zheng 3324485b04bSFam Zheng dkr = Docker() 3336fe3ae3fSAlex Bennée if "--no-cache" not in argv and \ 3346fe3ae3fSAlex Bennée dkr.image_matches_dockerfile(tag, dockerfile): 3354485b04bSFam Zheng if not args.quiet: 336f03868bdSEduardo Habkost print("Image is up to date.") 337a9f8d038SAlex Bennée else: 338a9f8d038SAlex Bennée # Create a docker context directory for the build 339a9f8d038SAlex Bennée docker_dir = tempfile.mkdtemp(prefix="docker_build") 3404485b04bSFam Zheng 34115352decSAlex Bennée # Validate binfmt_misc will work 34215352decSAlex Bennée if args.include_executable: 34315352decSAlex Bennée if not _check_binfmt_misc(args.include_executable): 34415352decSAlex Bennée return 1 34515352decSAlex Bennée 346920776eaSAlex Bennée # Is there a .pre file to run in the build context? 347920776eaSAlex Bennée docker_pre = os.path.splitext(args.dockerfile)[0]+".pre" 348920776eaSAlex Bennée if os.path.exists(docker_pre): 349f8042deaSSascha Silbe stdout = DEVNULL if args.quiet else None 350920776eaSAlex Bennée rc = subprocess.call(os.path.realpath(docker_pre), 351f8042deaSSascha Silbe cwd=docker_dir, stdout=stdout) 352920776eaSAlex Bennée if rc == 3: 353f03868bdSEduardo Habkost print("Skip") 354920776eaSAlex Bennée return 0 355920776eaSAlex Bennée elif rc != 0: 356f03868bdSEduardo Habkost print("%s exited with code %d" % (docker_pre, rc)) 357920776eaSAlex Bennée return 1 358920776eaSAlex Bennée 3594c84f662SPhilippe Mathieu-Daudé # Copy any extra files into the Docker context. These can be 3604c84f662SPhilippe Mathieu-Daudé # included by the use of the ADD directive in the Dockerfile. 361438d1168SPhilippe Mathieu-Daudé cksum = [] 362504ca3c2SAlex Bennée if args.include_executable: 363438d1168SPhilippe Mathieu-Daudé # FIXME: there is no checksum of this executable and the linked 364438d1168SPhilippe Mathieu-Daudé # libraries, once the image built any change of this executable 365438d1168SPhilippe Mathieu-Daudé # or any library won't trigger another build. 3664c84f662SPhilippe Mathieu-Daudé _copy_binary_with_libs(args.include_executable, docker_dir) 3674c84f662SPhilippe Mathieu-Daudé for filename in args.extra_files or []: 3684c84f662SPhilippe Mathieu-Daudé _copy_with_mkdir(filename, docker_dir) 369f9172822SAlex Bennée cksum += [(filename, _file_checksum(filename))] 370504ca3c2SAlex Bennée 37106cc3551SPhilippe Mathieu-Daudé argv += ["--build-arg=" + k.lower() + "=" + v 37206cc3551SPhilippe Mathieu-Daudé for k, v in os.environ.iteritems() 37306cc3551SPhilippe Mathieu-Daudé if k.lower() in FILTERED_ENV_NAMES] 374a9f8d038SAlex Bennée dkr.build_image(tag, docker_dir, dockerfile, 375438d1168SPhilippe Mathieu-Daudé quiet=args.quiet, user=args.user, argv=argv, 376438d1168SPhilippe Mathieu-Daudé extra_files_cksum=cksum) 377a9f8d038SAlex Bennée 378a9f8d038SAlex Bennée rmtree(docker_dir) 379a9f8d038SAlex Bennée 3804485b04bSFam Zheng return 0 3814485b04bSFam Zheng 3826e733da6SAlex Bennéeclass UpdateCommand(SubCommand): 3836e733da6SAlex Bennée """ Update a docker image with new executables. Arguments: <tag> <executable>""" 3846e733da6SAlex Bennée name = "update" 3856e733da6SAlex Bennée def args(self, parser): 3866e733da6SAlex Bennée parser.add_argument("tag", 3876e733da6SAlex Bennée help="Image Tag") 3886e733da6SAlex Bennée parser.add_argument("executable", 3896e733da6SAlex Bennée help="Executable to copy") 3906e733da6SAlex Bennée 3916e733da6SAlex Bennée def run(self, args, argv): 3926e733da6SAlex Bennée # Create a temporary tarball with our whole build context and 3936e733da6SAlex Bennée # dockerfile for the update 3946e733da6SAlex Bennée tmp = tempfile.NamedTemporaryFile(suffix="dckr.tar.gz") 3956e733da6SAlex Bennée tmp_tar = TarFile(fileobj=tmp, mode='w') 3966e733da6SAlex Bennée 3976e733da6SAlex Bennée # Add the executable to the tarball 3986e733da6SAlex Bennée bn = os.path.basename(args.executable) 3996e733da6SAlex Bennée ff = "/usr/bin/%s" % bn 4006e733da6SAlex Bennée tmp_tar.add(args.executable, arcname=ff) 4016e733da6SAlex Bennée 4026e733da6SAlex Bennée # Add any associated libraries 4036e733da6SAlex Bennée libs = _get_so_libs(args.executable) 4046e733da6SAlex Bennée if libs: 4056e733da6SAlex Bennée for l in libs: 4066e733da6SAlex Bennée tmp_tar.add(os.path.realpath(l), arcname=l) 4076e733da6SAlex Bennée 4086e733da6SAlex Bennée # Create a Docker buildfile 4096e733da6SAlex Bennée df = StringIO() 4106e733da6SAlex Bennée df.write("FROM %s\n" % args.tag) 4116e733da6SAlex Bennée df.write("ADD . /\n") 4126e733da6SAlex Bennée df.seek(0) 4136e733da6SAlex Bennée 4146e733da6SAlex Bennée df_tar = TarInfo(name="Dockerfile") 4156e733da6SAlex Bennée df_tar.size = len(df.buf) 4166e733da6SAlex Bennée tmp_tar.addfile(df_tar, fileobj=df) 4176e733da6SAlex Bennée 4186e733da6SAlex Bennée tmp_tar.close() 4196e733da6SAlex Bennée 4206e733da6SAlex Bennée # reset the file pointers 4216e733da6SAlex Bennée tmp.flush() 4226e733da6SAlex Bennée tmp.seek(0) 4236e733da6SAlex Bennée 4246e733da6SAlex Bennée # Run the build with our tarball context 4256e733da6SAlex Bennée dkr = Docker() 4266e733da6SAlex Bennée dkr.update_image(args.tag, tmp, quiet=args.quiet) 4276e733da6SAlex Bennée 4286e733da6SAlex Bennée return 0 4296e733da6SAlex Bennée 4304485b04bSFam Zhengclass CleanCommand(SubCommand): 4314485b04bSFam Zheng """Clean up docker instances""" 4324485b04bSFam Zheng name = "clean" 4334485b04bSFam Zheng def run(self, args, argv): 4344485b04bSFam Zheng Docker().clean() 4354485b04bSFam Zheng return 0 4364485b04bSFam Zheng 4374b08af60SFam Zhengclass ImagesCommand(SubCommand): 4384b08af60SFam Zheng """Run "docker images" command""" 4394b08af60SFam Zheng name = "images" 4404b08af60SFam Zheng def run(self, args, argv): 4414b08af60SFam Zheng return Docker().command("images", argv, args.quiet) 4424b08af60SFam Zheng 44315df9d37SAlex Bennée 44415df9d37SAlex Bennéeclass ProbeCommand(SubCommand): 44515df9d37SAlex Bennée """Probe if we can run docker automatically""" 44615df9d37SAlex Bennée name = "probe" 44715df9d37SAlex Bennée 44815df9d37SAlex Bennée def run(self, args, argv): 44915df9d37SAlex Bennée try: 45015df9d37SAlex Bennée docker = Docker() 45115df9d37SAlex Bennée if docker._command[0] == "docker": 452f03868bdSEduardo Habkost print("yes") 45315df9d37SAlex Bennée elif docker._command[0] == "sudo": 454f03868bdSEduardo Habkost print("sudo") 45515df9d37SAlex Bennée except Exception: 456f03868bdSEduardo Habkost print("no") 45715df9d37SAlex Bennée 45815df9d37SAlex Bennée return 45915df9d37SAlex Bennée 46015df9d37SAlex Bennée 4615e03c2d8SAlex Bennéeclass CcCommand(SubCommand): 4625e03c2d8SAlex Bennée """Compile sources with cc in images""" 4635e03c2d8SAlex Bennée name = "cc" 4645e03c2d8SAlex Bennée 4655e03c2d8SAlex Bennée def args(self, parser): 4665e03c2d8SAlex Bennée parser.add_argument("--image", "-i", required=True, 4675e03c2d8SAlex Bennée help="The docker image in which to run cc") 46899cfdb86SAlex Bennée parser.add_argument("--cc", default="cc", 46999cfdb86SAlex Bennée help="The compiler executable to call") 47050b72738SAlex Bennée parser.add_argument("--user", 47150b72738SAlex Bennée help="The user-id to run under") 4725e03c2d8SAlex Bennée parser.add_argument("--source-path", "-s", nargs="*", dest="paths", 4735e03c2d8SAlex Bennée help="""Extra paths to (ro) mount into container for 4745e03c2d8SAlex Bennée reading sources""") 4755e03c2d8SAlex Bennée 4765e03c2d8SAlex Bennée def run(self, args, argv): 4775e03c2d8SAlex Bennée if argv and argv[0] == "--": 4785e03c2d8SAlex Bennée argv = argv[1:] 4795e03c2d8SAlex Bennée cwd = os.getcwd() 4805e03c2d8SAlex Bennée cmd = ["--rm", "-w", cwd, 4815e03c2d8SAlex Bennée "-v", "%s:%s:rw" % (cwd, cwd)] 4825e03c2d8SAlex Bennée if args.paths: 4835e03c2d8SAlex Bennée for p in args.paths: 4845e03c2d8SAlex Bennée cmd += ["-v", "%s:%s:ro,z" % (p, p)] 48550b72738SAlex Bennée if args.user: 48650b72738SAlex Bennée cmd += ["-u", args.user] 48799cfdb86SAlex Bennée cmd += [args.image, args.cc] 4885e03c2d8SAlex Bennée cmd += argv 4895e03c2d8SAlex Bennée return Docker().command("run", cmd, args.quiet) 4905e03c2d8SAlex Bennée 4915e03c2d8SAlex Bennée 492f97da1f7SAlex Bennéeclass CheckCommand(SubCommand): 493f97da1f7SAlex Bennée """Check if we need to re-build a docker image out of a dockerfile. 494f97da1f7SAlex Bennée Arguments: <tag> <dockerfile>""" 495f97da1f7SAlex Bennée name = "check" 496f97da1f7SAlex Bennée 497f97da1f7SAlex Bennée def args(self, parser): 498f97da1f7SAlex Bennée parser.add_argument("tag", 499f97da1f7SAlex Bennée help="Image Tag") 5007b882245SAlex Bennée parser.add_argument("dockerfile", default=None, 5017b882245SAlex Bennée help="Dockerfile name", nargs='?') 5027b882245SAlex Bennée parser.add_argument("--checktype", choices=["checksum", "age"], 5037b882245SAlex Bennée default="checksum", help="check type") 5047b882245SAlex Bennée parser.add_argument("--olderthan", default=60, type=int, 5057b882245SAlex Bennée help="number of minutes") 506f97da1f7SAlex Bennée 507f97da1f7SAlex Bennée def run(self, args, argv): 508f97da1f7SAlex Bennée tag = args.tag 509f97da1f7SAlex Bennée 51043e1b2ffSAlex Bennée try: 511f97da1f7SAlex Bennée dkr = Docker() 51243e1b2ffSAlex Bennée except: 51343e1b2ffSAlex Bennée print("Docker not set up") 51443e1b2ffSAlex Bennée return 1 51543e1b2ffSAlex Bennée 516f97da1f7SAlex Bennée info = dkr.inspect_tag(tag) 517f97da1f7SAlex Bennée if info is None: 518f97da1f7SAlex Bennée print("Image does not exist") 519f97da1f7SAlex Bennée return 1 520f97da1f7SAlex Bennée 5217b882245SAlex Bennée if args.checktype == "checksum": 5227b882245SAlex Bennée if not args.dockerfile: 5237b882245SAlex Bennée print("Need a dockerfile for tag:%s" % (tag)) 5247b882245SAlex Bennée return 1 5257b882245SAlex Bennée 5267b882245SAlex Bennée dockerfile = open(args.dockerfile, "rb").read() 5277b882245SAlex Bennée 528f97da1f7SAlex Bennée if dkr.image_matches_dockerfile(tag, dockerfile): 529f97da1f7SAlex Bennée if not args.quiet: 530f97da1f7SAlex Bennée print("Image is up to date") 531f97da1f7SAlex Bennée return 0 532f97da1f7SAlex Bennée else: 533f97da1f7SAlex Bennée print("Image needs updating") 534f97da1f7SAlex Bennée return 1 5357b882245SAlex Bennée elif args.checktype == "age": 5367b882245SAlex Bennée timestr = dkr.get_image_creation_time(info).split(".")[0] 5377b882245SAlex Bennée created = datetime.strptime(timestr, "%Y-%m-%dT%H:%M:%S") 5387b882245SAlex Bennée past = datetime.now() - timedelta(minutes=args.olderthan) 5397b882245SAlex Bennée if created < past: 5407b882245SAlex Bennée print ("Image created @ %s more than %d minutes old" % 5417b882245SAlex Bennée (timestr, args.olderthan)) 5427b882245SAlex Bennée return 1 5437b882245SAlex Bennée else: 5447b882245SAlex Bennée if not args.quiet: 5457b882245SAlex Bennée print ("Image less than %d minutes old" % (args.olderthan)) 5467b882245SAlex Bennée return 0 547f97da1f7SAlex Bennée 548f97da1f7SAlex Bennée 5494485b04bSFam Zhengdef main(): 5504485b04bSFam Zheng parser = argparse.ArgumentParser(description="A Docker helper", 5514485b04bSFam Zheng usage="%s <subcommand> ..." % os.path.basename(sys.argv[0])) 5524485b04bSFam Zheng subparsers = parser.add_subparsers(title="subcommands", help=None) 5534485b04bSFam Zheng for cls in SubCommand.__subclasses__(): 5544485b04bSFam Zheng cmd = cls() 5554485b04bSFam Zheng subp = subparsers.add_parser(cmd.name, help=cmd.__doc__) 5564485b04bSFam Zheng cmd.shared_args(subp) 5574485b04bSFam Zheng cmd.args(subp) 5584485b04bSFam Zheng subp.set_defaults(cmdobj=cmd) 5594485b04bSFam Zheng args, argv = parser.parse_known_args() 5604485b04bSFam Zheng return args.cmdobj.run(args, argv) 5614485b04bSFam Zheng 5624485b04bSFam Zhengif __name__ == "__main__": 5634485b04bSFam Zheng sys.exit(main()) 564