1*4112aff7SAlex Bennée#!/usr/bin/env python3 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 144485b04bSFam Zhengimport os 154485b04bSFam Zhengimport sys 164485b04bSFam Zhengimport subprocess 174485b04bSFam Zhengimport json 184485b04bSFam Zhengimport hashlib 194485b04bSFam Zhengimport atexit 204485b04bSFam Zhengimport uuid 21ae68fdabSEduardo Habkostimport argparse 229459f754SMarc-André Lureauimport enum 234485b04bSFam Zhengimport tempfile 24504ca3c2SAlex Bennéeimport re 2597cba1a1SFam Zhengimport signal 266e733da6SAlex Bennéefrom tarfile import TarFile, TarInfo 277a5d936bSAlex Bennéefrom io import StringIO 28a9f8d038SAlex Bennéefrom shutil import copy, rmtree 29414a8ce5SAlex Bennéefrom pwd import getpwuid 307b882245SAlex Bennéefrom datetime import datetime, timedelta 314485b04bSFam Zheng 32c9772570SSascha Silbe 3306cc3551SPhilippe Mathieu-DaudéFILTERED_ENV_NAMES = ['ftp_proxy', 'http_proxy', 'https_proxy'] 3406cc3551SPhilippe Mathieu-Daudé 3506cc3551SPhilippe Mathieu-Daudé 36c9772570SSascha SilbeDEVNULL = open(os.devnull, 'wb') 37c9772570SSascha Silbe 389459f754SMarc-André Lureauclass EngineEnum(enum.IntEnum): 399459f754SMarc-André Lureau AUTO = 1 409459f754SMarc-André Lureau DOCKER = 2 419459f754SMarc-André Lureau PODMAN = 3 429459f754SMarc-André Lureau 439459f754SMarc-André Lureau def __str__(self): 449459f754SMarc-André Lureau return self.name.lower() 459459f754SMarc-André Lureau 469459f754SMarc-André Lureau def __repr__(self): 479459f754SMarc-André Lureau return str(self) 489459f754SMarc-André Lureau 499459f754SMarc-André Lureau @staticmethod 509459f754SMarc-André Lureau def argparse(s): 519459f754SMarc-André Lureau try: 529459f754SMarc-André Lureau return EngineEnum[s.upper()] 539459f754SMarc-André Lureau except KeyError: 549459f754SMarc-André Lureau return s 559459f754SMarc-André Lureau 569459f754SMarc-André Lureau 579459f754SMarc-André LureauUSE_ENGINE = EngineEnum.AUTO 58c9772570SSascha Silbe 594485b04bSFam Zhengdef _text_checksum(text): 604485b04bSFam Zheng """Calculate a digest string unique to the text content""" 61*4112aff7SAlex Bennée return hashlib.sha1(text.encode('utf-8')).hexdigest() 624485b04bSFam Zheng 63*4112aff7SAlex Bennéedef _read_dockerfile(path): 64*4112aff7SAlex Bennée return open(path, 'rt', encoding='utf-8').read() 65432d8ad5SAlex Bennée 66438d1168SPhilippe Mathieu-Daudédef _file_checksum(filename): 67*4112aff7SAlex Bennée return _text_checksum(_read_dockerfile(filename)) 68438d1168SPhilippe Mathieu-Daudé 69432d8ad5SAlex Bennée 709459f754SMarc-André Lureaudef _guess_engine_command(): 719459f754SMarc-André Lureau """ Guess a working engine command or raise exception if not found""" 729459f754SMarc-André Lureau commands = [] 739459f754SMarc-André Lureau 749459f754SMarc-André Lureau if USE_ENGINE in [EngineEnum.AUTO, EngineEnum.PODMAN]: 759459f754SMarc-André Lureau commands += [["podman"]] 769459f754SMarc-André Lureau if USE_ENGINE in [EngineEnum.AUTO, EngineEnum.DOCKER]: 779459f754SMarc-André Lureau commands += [["docker"], ["sudo", "-n", "docker"]] 784485b04bSFam Zheng for cmd in commands: 790679f98bSEduardo Habkost try: 8083405c45SAlex Bennée # docker version will return the client details in stdout 8183405c45SAlex Bennée # but still report a status of 1 if it can't contact the daemon 8283405c45SAlex Bennée if subprocess.call(cmd + ["version"], 83c9772570SSascha Silbe stdout=DEVNULL, stderr=DEVNULL) == 0: 844485b04bSFam Zheng return cmd 850679f98bSEduardo Habkost except OSError: 860679f98bSEduardo Habkost pass 874485b04bSFam Zheng commands_txt = "\n".join([" " + " ".join(x) for x in commands]) 889459f754SMarc-André Lureau raise Exception("Cannot find working engine command. Tried:\n%s" % 894485b04bSFam Zheng commands_txt) 904485b04bSFam Zheng 91432d8ad5SAlex Bennée 922499ee9fSPhilippe Mathieu-Daudédef _copy_with_mkdir(src, root_dir, sub_path='.'): 93504ca3c2SAlex Bennée """Copy src into root_dir, creating sub_path as needed.""" 94504ca3c2SAlex Bennée dest_dir = os.path.normpath("%s/%s" % (root_dir, sub_path)) 95504ca3c2SAlex Bennée try: 96504ca3c2SAlex Bennée os.makedirs(dest_dir) 97504ca3c2SAlex Bennée except OSError: 98504ca3c2SAlex Bennée # we can safely ignore already created directories 99504ca3c2SAlex Bennée pass 100504ca3c2SAlex Bennée 101504ca3c2SAlex Bennée dest_file = "%s/%s" % (dest_dir, os.path.basename(src)) 102504ca3c2SAlex Bennée copy(src, dest_file) 103504ca3c2SAlex Bennée 104504ca3c2SAlex Bennée 105504ca3c2SAlex Bennéedef _get_so_libs(executable): 106504ca3c2SAlex Bennée """Return a list of libraries associated with an executable. 107504ca3c2SAlex Bennée 108504ca3c2SAlex Bennée The paths may be symbolic links which would need to be resolved to 109504ca3c2SAlex Bennée ensure theright data is copied.""" 110504ca3c2SAlex Bennée 111504ca3c2SAlex Bennée libs = [] 112504ca3c2SAlex Bennée ldd_re = re.compile(r"(/.*/)(\S*)") 113504ca3c2SAlex Bennée try: 114504ca3c2SAlex Bennée ldd_output = subprocess.check_output(["ldd", executable]) 115504ca3c2SAlex Bennée for line in ldd_output.split("\n"): 116504ca3c2SAlex Bennée search = ldd_re.search(line) 117504ca3c2SAlex Bennée if search and len(search.groups()) == 2: 118504ca3c2SAlex Bennée so_path = search.groups()[0] 119504ca3c2SAlex Bennée so_lib = search.groups()[1] 120504ca3c2SAlex Bennée libs.append("%s/%s" % (so_path, so_lib)) 121504ca3c2SAlex Bennée except subprocess.CalledProcessError: 122f03868bdSEduardo Habkost print("%s had no associated libraries (static build?)" % (executable)) 123504ca3c2SAlex Bennée 124504ca3c2SAlex Bennée return libs 125504ca3c2SAlex Bennée 126432d8ad5SAlex Bennée 127d10404b1SAlex Bennéedef _copy_binary_with_libs(src, bin_dest, dest_dir): 128d10404b1SAlex Bennée """Maybe copy a binary and all its dependent libraries. 129d10404b1SAlex Bennée 130d10404b1SAlex Bennée If bin_dest isn't set we only copy the support libraries because 131d10404b1SAlex Bennée we don't need qemu in the docker path to run (due to persistent 132d10404b1SAlex Bennée mapping). Indeed users may get confused if we aren't running what 133d10404b1SAlex Bennée is in the image. 134504ca3c2SAlex Bennée 135504ca3c2SAlex Bennée This does rely on the host file-system being fairly multi-arch 136d10404b1SAlex Bennée aware so the file don't clash with the guests layout. 137d10404b1SAlex Bennée """ 138504ca3c2SAlex Bennée 139d10404b1SAlex Bennée if bin_dest: 140d10404b1SAlex Bennée _copy_with_mkdir(src, dest_dir, os.path.dirname(bin_dest)) 141d10404b1SAlex Bennée else: 142d10404b1SAlex Bennée print("only copying support libraries for %s" % (src)) 143504ca3c2SAlex Bennée 144504ca3c2SAlex Bennée libs = _get_so_libs(src) 145504ca3c2SAlex Bennée if libs: 146504ca3c2SAlex Bennée for l in libs: 147504ca3c2SAlex Bennée so_path = os.path.dirname(l) 148504ca3c2SAlex Bennée _copy_with_mkdir(l, dest_dir, so_path) 149504ca3c2SAlex Bennée 15015352decSAlex Bennée 15115352decSAlex Bennéedef _check_binfmt_misc(executable): 15215352decSAlex Bennée """Check binfmt_misc has entry for executable in the right place. 15315352decSAlex Bennée 15415352decSAlex Bennée The details of setting up binfmt_misc are outside the scope of 15515352decSAlex Bennée this script but we should at least fail early with a useful 156d10404b1SAlex Bennée message if it won't work. 157d10404b1SAlex Bennée 158d10404b1SAlex Bennée Returns the configured binfmt path and a valid flag. For 159d10404b1SAlex Bennée persistent configurations we will still want to copy and dependent 160d10404b1SAlex Bennée libraries. 161d10404b1SAlex Bennée """ 16215352decSAlex Bennée 16315352decSAlex Bennée binary = os.path.basename(executable) 16415352decSAlex Bennée binfmt_entry = "/proc/sys/fs/binfmt_misc/%s" % (binary) 16515352decSAlex Bennée 16615352decSAlex Bennée if not os.path.exists(binfmt_entry): 16715352decSAlex Bennée print ("No binfmt_misc entry for %s" % (binary)) 168d10404b1SAlex Bennée return None, False 16915352decSAlex Bennée 17015352decSAlex Bennée with open(binfmt_entry) as x: entry = x.read() 17115352decSAlex Bennée 17243c898b7SAlex Bennée if re.search("flags:.*F.*\n", entry): 173432d8ad5SAlex Bennée print("binfmt_misc for %s uses persistent(F) mapping to host binary" % 17443c898b7SAlex Bennée (binary)) 175d10404b1SAlex Bennée return None, True 17643c898b7SAlex Bennée 1777e81d198SAlex Bennée m = re.search("interpreter (\S+)\n", entry) 1787e81d198SAlex Bennée interp = m.group(1) 1797e81d198SAlex Bennée if interp and interp != executable: 1807e81d198SAlex Bennée print("binfmt_misc for %s does not point to %s, using %s" % 1817e81d198SAlex Bennée (binary, executable, interp)) 18215352decSAlex Bennée 183d10404b1SAlex Bennée return interp, True 184d10404b1SAlex Bennée 18515352decSAlex Bennée 186c1958e9dSFam Zhengdef _read_qemu_dockerfile(img_name): 187547cb45eSAlex Bennée # special case for Debian linux-user images 188547cb45eSAlex Bennée if img_name.startswith("debian") and img_name.endswith("user"): 189547cb45eSAlex Bennée img_name = "debian-bootstrap" 190547cb45eSAlex Bennée 191c1958e9dSFam Zheng df = os.path.join(os.path.dirname(__file__), "dockerfiles", 192c1958e9dSFam Zheng img_name + ".docker") 193*4112aff7SAlex Bennée return _read_dockerfile(df) 194c1958e9dSFam Zheng 195432d8ad5SAlex Bennée 196c1958e9dSFam Zhengdef _dockerfile_preprocess(df): 197c1958e9dSFam Zheng out = "" 198c1958e9dSFam Zheng for l in df.splitlines(): 199c1958e9dSFam Zheng if len(l.strip()) == 0 or l.startswith("#"): 200c1958e9dSFam Zheng continue 201c1958e9dSFam Zheng from_pref = "FROM qemu:" 202c1958e9dSFam Zheng if l.startswith(from_pref): 203c1958e9dSFam Zheng # TODO: Alternatively we could replace this line with "FROM $ID" 204c1958e9dSFam Zheng # where $ID is the image's hex id obtained with 205c1958e9dSFam Zheng # $ docker images $IMAGE --format="{{.Id}}" 206c1958e9dSFam Zheng # but unfortunately that's not supported by RHEL 7. 207c1958e9dSFam Zheng inlining = _read_qemu_dockerfile(l[len(from_pref):]) 208c1958e9dSFam Zheng out += _dockerfile_preprocess(inlining) 209c1958e9dSFam Zheng continue 210c1958e9dSFam Zheng out += l + "\n" 211c1958e9dSFam Zheng return out 212c1958e9dSFam Zheng 213432d8ad5SAlex Bennée 2144485b04bSFam Zhengclass Docker(object): 2154485b04bSFam Zheng """ Running Docker commands """ 2164485b04bSFam Zheng def __init__(self): 2179459f754SMarc-André Lureau self._command = _guess_engine_command() 2184485b04bSFam Zheng self._instances = [] 2194485b04bSFam Zheng atexit.register(self._kill_instances) 22097cba1a1SFam Zheng signal.signal(signal.SIGTERM, self._kill_instances) 22197cba1a1SFam Zheng signal.signal(signal.SIGHUP, self._kill_instances) 2224485b04bSFam Zheng 22358bf7b6dSFam Zheng def _do(self, cmd, quiet=True, **kwargs): 2244485b04bSFam Zheng if quiet: 225c9772570SSascha Silbe kwargs["stdout"] = DEVNULL 2264485b04bSFam Zheng return subprocess.call(self._command + cmd, **kwargs) 2274485b04bSFam Zheng 2280b95ff72SFam Zheng def _do_check(self, cmd, quiet=True, **kwargs): 2290b95ff72SFam Zheng if quiet: 2300b95ff72SFam Zheng kwargs["stdout"] = DEVNULL 2310b95ff72SFam Zheng return subprocess.check_call(self._command + cmd, **kwargs) 2320b95ff72SFam Zheng 2334485b04bSFam Zheng def _do_kill_instances(self, only_known, only_active=True): 2344485b04bSFam Zheng cmd = ["ps", "-q"] 2354485b04bSFam Zheng if not only_active: 2364485b04bSFam Zheng cmd.append("-a") 2374485b04bSFam Zheng for i in self._output(cmd).split(): 2384485b04bSFam Zheng resp = self._output(["inspect", i]) 2394485b04bSFam Zheng labels = json.loads(resp)[0]["Config"]["Labels"] 2404485b04bSFam Zheng active = json.loads(resp)[0]["State"]["Running"] 2414485b04bSFam Zheng if not labels: 2424485b04bSFam Zheng continue 2434485b04bSFam Zheng instance_uuid = labels.get("com.qemu.instance.uuid", None) 2444485b04bSFam Zheng if not instance_uuid: 2454485b04bSFam Zheng continue 2464485b04bSFam Zheng if only_known and instance_uuid not in self._instances: 2474485b04bSFam Zheng continue 248f03868bdSEduardo Habkost print("Terminating", i) 2494485b04bSFam Zheng if active: 2504485b04bSFam Zheng self._do(["kill", i]) 2514485b04bSFam Zheng self._do(["rm", i]) 2524485b04bSFam Zheng 2534485b04bSFam Zheng def clean(self): 2544485b04bSFam Zheng self._do_kill_instances(False, False) 2554485b04bSFam Zheng return 0 2564485b04bSFam Zheng 25797cba1a1SFam Zheng def _kill_instances(self, *args, **kwargs): 2584485b04bSFam Zheng return self._do_kill_instances(True) 2594485b04bSFam Zheng 2604485b04bSFam Zheng def _output(self, cmd, **kwargs): 2614485b04bSFam Zheng return subprocess.check_output(self._command + cmd, 2624485b04bSFam Zheng stderr=subprocess.STDOUT, 263*4112aff7SAlex Bennée encoding='utf-8', 2644485b04bSFam Zheng **kwargs) 2654485b04bSFam Zheng 266f97da1f7SAlex Bennée def inspect_tag(self, tag): 267f97da1f7SAlex Bennée try: 268f97da1f7SAlex Bennée return self._output(["inspect", tag]) 269f97da1f7SAlex Bennée except subprocess.CalledProcessError: 270f97da1f7SAlex Bennée return None 271f97da1f7SAlex Bennée 2727b882245SAlex Bennée def get_image_creation_time(self, info): 2737b882245SAlex Bennée return json.loads(info)[0]["Created"] 2747b882245SAlex Bennée 2754485b04bSFam Zheng def get_image_dockerfile_checksum(self, tag): 276f97da1f7SAlex Bennée resp = self.inspect_tag(tag) 2774485b04bSFam Zheng labels = json.loads(resp)[0]["Config"].get("Labels", {}) 2784485b04bSFam Zheng return labels.get("com.qemu.dockerfile-checksum", "") 2794485b04bSFam Zheng 280414a8ce5SAlex Bennée def build_image(self, tag, docker_dir, dockerfile, 281438d1168SPhilippe Mathieu-Daudé quiet=True, user=False, argv=None, extra_files_cksum=[]): 282432d8ad5SAlex Bennée if argv is None: 2834485b04bSFam Zheng argv = [] 2844485b04bSFam Zheng 285*4112aff7SAlex Bennée tmp_df = tempfile.NamedTemporaryFile(mode="w+t", 286*4112aff7SAlex Bennée encoding='utf-8', 287*4112aff7SAlex Bennée dir=docker_dir, suffix=".docker") 2884485b04bSFam Zheng tmp_df.write(dockerfile) 2894485b04bSFam Zheng 290414a8ce5SAlex Bennée if user: 291414a8ce5SAlex Bennée uid = os.getuid() 292414a8ce5SAlex Bennée uname = getpwuid(uid).pw_name 293414a8ce5SAlex Bennée tmp_df.write("\n") 294414a8ce5SAlex Bennée tmp_df.write("RUN id %s 2>/dev/null || useradd -u %d -U %s" % 295414a8ce5SAlex Bennée (uname, uid, uname)) 296414a8ce5SAlex Bennée 2974485b04bSFam Zheng tmp_df.write("\n") 2984485b04bSFam Zheng tmp_df.write("LABEL com.qemu.dockerfile-checksum=%s" % 299f9172822SAlex Bennée _text_checksum(_dockerfile_preprocess(dockerfile))) 300f9172822SAlex Bennée for f, c in extra_files_cksum: 301f9172822SAlex Bennée tmp_df.write("LABEL com.qemu.%s-checksum=%s" % (f, c)) 302f9172822SAlex Bennée 3034485b04bSFam Zheng tmp_df.flush() 304a9f8d038SAlex Bennée 305432d8ad5SAlex Bennée self._do_check(["build", "-t", tag, "-f", tmp_df.name] + argv + 306a9f8d038SAlex Bennée [docker_dir], 3074485b04bSFam Zheng quiet=quiet) 3084485b04bSFam Zheng 3096e733da6SAlex Bennée def update_image(self, tag, tarball, quiet=True): 3106e733da6SAlex Bennée "Update a tagged image using " 3116e733da6SAlex Bennée 3120b95ff72SFam Zheng self._do_check(["build", "-t", tag, "-"], quiet=quiet, stdin=tarball) 3136e733da6SAlex Bennée 3144485b04bSFam Zheng def image_matches_dockerfile(self, tag, dockerfile): 3154485b04bSFam Zheng try: 3164485b04bSFam Zheng checksum = self.get_image_dockerfile_checksum(tag) 3174485b04bSFam Zheng except Exception: 3184485b04bSFam Zheng return False 319c1958e9dSFam Zheng return checksum == _text_checksum(_dockerfile_preprocess(dockerfile)) 3204485b04bSFam Zheng 3214485b04bSFam Zheng def run(self, cmd, keep, quiet): 3224485b04bSFam Zheng label = uuid.uuid1().hex 3234485b04bSFam Zheng if not keep: 3244485b04bSFam Zheng self._instances.append(label) 3250b95ff72SFam Zheng ret = self._do_check(["run", "--label", 3264485b04bSFam Zheng "com.qemu.instance.uuid=" + label] + cmd, 3274485b04bSFam Zheng quiet=quiet) 3284485b04bSFam Zheng if not keep: 3294485b04bSFam Zheng self._instances.remove(label) 3304485b04bSFam Zheng return ret 3314485b04bSFam Zheng 3324b08af60SFam Zheng def command(self, cmd, argv, quiet): 3334b08af60SFam Zheng return self._do([cmd] + argv, quiet=quiet) 3344b08af60SFam Zheng 335432d8ad5SAlex Bennée 3364485b04bSFam Zhengclass SubCommand(object): 3374485b04bSFam Zheng """A SubCommand template base class""" 3384485b04bSFam Zheng name = None # Subcommand name 339432d8ad5SAlex Bennée 3404485b04bSFam Zheng def shared_args(self, parser): 3414485b04bSFam Zheng parser.add_argument("--quiet", action="store_true", 342e50a6121SStefan Weil help="Run quietly unless an error occurred") 3434485b04bSFam Zheng 3444485b04bSFam Zheng def args(self, parser): 3454485b04bSFam Zheng """Setup argument parser""" 3464485b04bSFam Zheng pass 347432d8ad5SAlex Bennée 3484485b04bSFam Zheng def run(self, args, argv): 3494485b04bSFam Zheng """Run command. 3504485b04bSFam Zheng args: parsed argument by argument parser. 3514485b04bSFam Zheng argv: remaining arguments from sys.argv. 3524485b04bSFam Zheng """ 3534485b04bSFam Zheng pass 3544485b04bSFam Zheng 355432d8ad5SAlex Bennée 3564485b04bSFam Zhengclass RunCommand(SubCommand): 3574485b04bSFam Zheng """Invoke docker run and take care of cleaning up""" 3584485b04bSFam Zheng name = "run" 359432d8ad5SAlex Bennée 3604485b04bSFam Zheng def args(self, parser): 3614485b04bSFam Zheng parser.add_argument("--keep", action="store_true", 3624485b04bSFam Zheng help="Don't remove image when command completes") 3632461d80eSMarc-André Lureau parser.add_argument("--run-as-current-user", action="store_true", 3642461d80eSMarc-André Lureau help="Run container using the current user's uid") 365432d8ad5SAlex Bennée 3664485b04bSFam Zheng def run(self, args, argv): 3672461d80eSMarc-André Lureau if args.run_as_current_user: 3682461d80eSMarc-André Lureau uid = os.getuid() 3692461d80eSMarc-André Lureau argv = [ "-u", str(uid) ] + argv 3709459f754SMarc-André Lureau docker = Docker() 3719459f754SMarc-André Lureau if docker._command[0] == "podman": 3729459f754SMarc-André Lureau argv = [ "--uidmap", "%d:0:1" % uid, 3739459f754SMarc-André Lureau "--uidmap", "0:1:%d" % uid, 3749459f754SMarc-André Lureau "--uidmap", "%d:%d:64536" % (uid + 1, uid + 1)] + argv 3754485b04bSFam Zheng return Docker().run(argv, args.keep, quiet=args.quiet) 3764485b04bSFam Zheng 377432d8ad5SAlex Bennée 3784485b04bSFam Zhengclass BuildCommand(SubCommand): 379432d8ad5SAlex Bennée """ Build docker image out of a dockerfile. Arg: <tag> <dockerfile>""" 3804485b04bSFam Zheng name = "build" 381432d8ad5SAlex Bennée 3824485b04bSFam Zheng def args(self, parser): 383504ca3c2SAlex Bennée parser.add_argument("--include-executable", "-e", 384504ca3c2SAlex Bennée help="""Specify a binary that will be copied to the 385504ca3c2SAlex Bennée container together with all its dependent 386504ca3c2SAlex Bennée libraries""") 3874c84f662SPhilippe Mathieu-Daudé parser.add_argument("--extra-files", "-f", nargs='*', 3884c84f662SPhilippe Mathieu-Daudé help="""Specify files that will be copied in the 3894c84f662SPhilippe Mathieu-Daudé Docker image, fulfilling the ADD directive from the 3904c84f662SPhilippe Mathieu-Daudé Dockerfile""") 391414a8ce5SAlex Bennée parser.add_argument("--add-current-user", "-u", dest="user", 392414a8ce5SAlex Bennée action="store_true", 393414a8ce5SAlex Bennée help="Add the current user to image's passwd") 3944485b04bSFam Zheng parser.add_argument("tag", 3954485b04bSFam Zheng help="Image Tag") 3964485b04bSFam Zheng parser.add_argument("dockerfile", 3974485b04bSFam Zheng help="Dockerfile name") 3984485b04bSFam Zheng 3994485b04bSFam Zheng def run(self, args, argv): 400*4112aff7SAlex Bennée dockerfile = _read_dockerfile(args.dockerfile) 4014485b04bSFam Zheng tag = args.tag 4024485b04bSFam Zheng 4034485b04bSFam Zheng dkr = Docker() 4046fe3ae3fSAlex Bennée if "--no-cache" not in argv and \ 4056fe3ae3fSAlex Bennée dkr.image_matches_dockerfile(tag, dockerfile): 4064485b04bSFam Zheng if not args.quiet: 407f03868bdSEduardo Habkost print("Image is up to date.") 408a9f8d038SAlex Bennée else: 409a9f8d038SAlex Bennée # Create a docker context directory for the build 410a9f8d038SAlex Bennée docker_dir = tempfile.mkdtemp(prefix="docker_build") 4114485b04bSFam Zheng 41215352decSAlex Bennée # Validate binfmt_misc will work 41315352decSAlex Bennée if args.include_executable: 414d10404b1SAlex Bennée qpath, enabled = _check_binfmt_misc(args.include_executable) 415d10404b1SAlex Bennée if not enabled: 41615352decSAlex Bennée return 1 41715352decSAlex Bennée 418920776eaSAlex Bennée # Is there a .pre file to run in the build context? 419920776eaSAlex Bennée docker_pre = os.path.splitext(args.dockerfile)[0]+".pre" 420920776eaSAlex Bennée if os.path.exists(docker_pre): 421f8042deaSSascha Silbe stdout = DEVNULL if args.quiet else None 422920776eaSAlex Bennée rc = subprocess.call(os.path.realpath(docker_pre), 423f8042deaSSascha Silbe cwd=docker_dir, stdout=stdout) 424920776eaSAlex Bennée if rc == 3: 425f03868bdSEduardo Habkost print("Skip") 426920776eaSAlex Bennée return 0 427920776eaSAlex Bennée elif rc != 0: 428f03868bdSEduardo Habkost print("%s exited with code %d" % (docker_pre, rc)) 429920776eaSAlex Bennée return 1 430920776eaSAlex Bennée 4314c84f662SPhilippe Mathieu-Daudé # Copy any extra files into the Docker context. These can be 4324c84f662SPhilippe Mathieu-Daudé # included by the use of the ADD directive in the Dockerfile. 433438d1168SPhilippe Mathieu-Daudé cksum = [] 434504ca3c2SAlex Bennée if args.include_executable: 435438d1168SPhilippe Mathieu-Daudé # FIXME: there is no checksum of this executable and the linked 436438d1168SPhilippe Mathieu-Daudé # libraries, once the image built any change of this executable 437438d1168SPhilippe Mathieu-Daudé # or any library won't trigger another build. 438d10404b1SAlex Bennée _copy_binary_with_libs(args.include_executable, 439d10404b1SAlex Bennée qpath, docker_dir) 440d10404b1SAlex Bennée 4414c84f662SPhilippe Mathieu-Daudé for filename in args.extra_files or []: 4424c84f662SPhilippe Mathieu-Daudé _copy_with_mkdir(filename, docker_dir) 443f9172822SAlex Bennée cksum += [(filename, _file_checksum(filename))] 444504ca3c2SAlex Bennée 44506cc3551SPhilippe Mathieu-Daudé argv += ["--build-arg=" + k.lower() + "=" + v 446*4112aff7SAlex Bennée for k, v in os.environ.items() 44706cc3551SPhilippe Mathieu-Daudé if k.lower() in FILTERED_ENV_NAMES] 448a9f8d038SAlex Bennée dkr.build_image(tag, docker_dir, dockerfile, 449438d1168SPhilippe Mathieu-Daudé quiet=args.quiet, user=args.user, argv=argv, 450438d1168SPhilippe Mathieu-Daudé extra_files_cksum=cksum) 451a9f8d038SAlex Bennée 452a9f8d038SAlex Bennée rmtree(docker_dir) 453a9f8d038SAlex Bennée 4544485b04bSFam Zheng return 0 4554485b04bSFam Zheng 456432d8ad5SAlex Bennée 4576e733da6SAlex Bennéeclass UpdateCommand(SubCommand): 458432d8ad5SAlex Bennée """ Update a docker image with new executables. Args: <tag> <executable>""" 4596e733da6SAlex Bennée name = "update" 460432d8ad5SAlex Bennée 4616e733da6SAlex Bennée def args(self, parser): 4626e733da6SAlex Bennée parser.add_argument("tag", 4636e733da6SAlex Bennée help="Image Tag") 4646e733da6SAlex Bennée parser.add_argument("executable", 4656e733da6SAlex Bennée help="Executable to copy") 4666e733da6SAlex Bennée 4676e733da6SAlex Bennée def run(self, args, argv): 4686e733da6SAlex Bennée # Create a temporary tarball with our whole build context and 4696e733da6SAlex Bennée # dockerfile for the update 4706e733da6SAlex Bennée tmp = tempfile.NamedTemporaryFile(suffix="dckr.tar.gz") 4716e733da6SAlex Bennée tmp_tar = TarFile(fileobj=tmp, mode='w') 4726e733da6SAlex Bennée 4737e81d198SAlex Bennée # Add the executable to the tarball, using the current 474d10404b1SAlex Bennée # configured binfmt_misc path. If we don't get a path then we 475d10404b1SAlex Bennée # only need the support libraries copied 476d10404b1SAlex Bennée ff, enabled = _check_binfmt_misc(args.executable) 4777e81d198SAlex Bennée 478d10404b1SAlex Bennée if not enabled: 479d10404b1SAlex Bennée print("binfmt_misc not enabled, update disabled") 480d10404b1SAlex Bennée return 1 481d10404b1SAlex Bennée 482d10404b1SAlex Bennée if ff: 4836e733da6SAlex Bennée tmp_tar.add(args.executable, arcname=ff) 4846e733da6SAlex Bennée 4856e733da6SAlex Bennée # Add any associated libraries 4866e733da6SAlex Bennée libs = _get_so_libs(args.executable) 4876e733da6SAlex Bennée if libs: 4886e733da6SAlex Bennée for l in libs: 4896e733da6SAlex Bennée tmp_tar.add(os.path.realpath(l), arcname=l) 4906e733da6SAlex Bennée 4916e733da6SAlex Bennée # Create a Docker buildfile 4926e733da6SAlex Bennée df = StringIO() 4936e733da6SAlex Bennée df.write("FROM %s\n" % args.tag) 4946e733da6SAlex Bennée df.write("ADD . /\n") 4956e733da6SAlex Bennée df.seek(0) 4966e733da6SAlex Bennée 4976e733da6SAlex Bennée df_tar = TarInfo(name="Dockerfile") 4986e733da6SAlex Bennée df_tar.size = len(df.buf) 4996e733da6SAlex Bennée tmp_tar.addfile(df_tar, fileobj=df) 5006e733da6SAlex Bennée 5016e733da6SAlex Bennée tmp_tar.close() 5026e733da6SAlex Bennée 5036e733da6SAlex Bennée # reset the file pointers 5046e733da6SAlex Bennée tmp.flush() 5056e733da6SAlex Bennée tmp.seek(0) 5066e733da6SAlex Bennée 5076e733da6SAlex Bennée # Run the build with our tarball context 5086e733da6SAlex Bennée dkr = Docker() 5096e733da6SAlex Bennée dkr.update_image(args.tag, tmp, quiet=args.quiet) 5106e733da6SAlex Bennée 5116e733da6SAlex Bennée return 0 5126e733da6SAlex Bennée 513432d8ad5SAlex Bennée 5144485b04bSFam Zhengclass CleanCommand(SubCommand): 5154485b04bSFam Zheng """Clean up docker instances""" 5164485b04bSFam Zheng name = "clean" 517432d8ad5SAlex Bennée 5184485b04bSFam Zheng def run(self, args, argv): 5194485b04bSFam Zheng Docker().clean() 5204485b04bSFam Zheng return 0 5214485b04bSFam Zheng 522432d8ad5SAlex Bennée 5234b08af60SFam Zhengclass ImagesCommand(SubCommand): 5244b08af60SFam Zheng """Run "docker images" command""" 5254b08af60SFam Zheng name = "images" 526432d8ad5SAlex Bennée 5274b08af60SFam Zheng def run(self, args, argv): 5284b08af60SFam Zheng return Docker().command("images", argv, args.quiet) 5294b08af60SFam Zheng 53015df9d37SAlex Bennée 53115df9d37SAlex Bennéeclass ProbeCommand(SubCommand): 53215df9d37SAlex Bennée """Probe if we can run docker automatically""" 53315df9d37SAlex Bennée name = "probe" 53415df9d37SAlex Bennée 53515df9d37SAlex Bennée def run(self, args, argv): 53615df9d37SAlex Bennée try: 53715df9d37SAlex Bennée docker = Docker() 53815df9d37SAlex Bennée if docker._command[0] == "docker": 539f03868bdSEduardo Habkost print("yes") 54015df9d37SAlex Bennée elif docker._command[0] == "sudo": 541f03868bdSEduardo Habkost print("sudo") 5429459f754SMarc-André Lureau elif docker._command[0] == "podman": 5439459f754SMarc-André Lureau print("podman") 54415df9d37SAlex Bennée except Exception: 545f03868bdSEduardo Habkost print("no") 54615df9d37SAlex Bennée 54715df9d37SAlex Bennée return 54815df9d37SAlex Bennée 54915df9d37SAlex Bennée 5505e03c2d8SAlex Bennéeclass CcCommand(SubCommand): 5515e03c2d8SAlex Bennée """Compile sources with cc in images""" 5525e03c2d8SAlex Bennée name = "cc" 5535e03c2d8SAlex Bennée 5545e03c2d8SAlex Bennée def args(self, parser): 5555e03c2d8SAlex Bennée parser.add_argument("--image", "-i", required=True, 5565e03c2d8SAlex Bennée help="The docker image in which to run cc") 55799cfdb86SAlex Bennée parser.add_argument("--cc", default="cc", 55899cfdb86SAlex Bennée help="The compiler executable to call") 55950b72738SAlex Bennée parser.add_argument("--user", 56050b72738SAlex Bennée help="The user-id to run under") 5615e03c2d8SAlex Bennée parser.add_argument("--source-path", "-s", nargs="*", dest="paths", 5625e03c2d8SAlex Bennée help="""Extra paths to (ro) mount into container for 5635e03c2d8SAlex Bennée reading sources""") 5645e03c2d8SAlex Bennée 5655e03c2d8SAlex Bennée def run(self, args, argv): 5665e03c2d8SAlex Bennée if argv and argv[0] == "--": 5675e03c2d8SAlex Bennée argv = argv[1:] 5685e03c2d8SAlex Bennée cwd = os.getcwd() 5695e03c2d8SAlex Bennée cmd = ["--rm", "-w", cwd, 5705e03c2d8SAlex Bennée "-v", "%s:%s:rw" % (cwd, cwd)] 5715e03c2d8SAlex Bennée if args.paths: 5725e03c2d8SAlex Bennée for p in args.paths: 5735e03c2d8SAlex Bennée cmd += ["-v", "%s:%s:ro,z" % (p, p)] 57450b72738SAlex Bennée if args.user: 57550b72738SAlex Bennée cmd += ["-u", args.user] 57699cfdb86SAlex Bennée cmd += [args.image, args.cc] 5775e03c2d8SAlex Bennée cmd += argv 5785e03c2d8SAlex Bennée return Docker().command("run", cmd, args.quiet) 5795e03c2d8SAlex Bennée 5805e03c2d8SAlex Bennée 581f97da1f7SAlex Bennéeclass CheckCommand(SubCommand): 582f97da1f7SAlex Bennée """Check if we need to re-build a docker image out of a dockerfile. 583f97da1f7SAlex Bennée Arguments: <tag> <dockerfile>""" 584f97da1f7SAlex Bennée name = "check" 585f97da1f7SAlex Bennée 586f97da1f7SAlex Bennée def args(self, parser): 587f97da1f7SAlex Bennée parser.add_argument("tag", 588f97da1f7SAlex Bennée help="Image Tag") 5897b882245SAlex Bennée parser.add_argument("dockerfile", default=None, 5907b882245SAlex Bennée help="Dockerfile name", nargs='?') 5917b882245SAlex Bennée parser.add_argument("--checktype", choices=["checksum", "age"], 5927b882245SAlex Bennée default="checksum", help="check type") 5937b882245SAlex Bennée parser.add_argument("--olderthan", default=60, type=int, 5947b882245SAlex Bennée help="number of minutes") 595f97da1f7SAlex Bennée 596f97da1f7SAlex Bennée def run(self, args, argv): 597f97da1f7SAlex Bennée tag = args.tag 598f97da1f7SAlex Bennée 59943e1b2ffSAlex Bennée try: 600f97da1f7SAlex Bennée dkr = Docker() 601432d8ad5SAlex Bennée except subprocess.CalledProcessError: 60243e1b2ffSAlex Bennée print("Docker not set up") 60343e1b2ffSAlex Bennée return 1 60443e1b2ffSAlex Bennée 605f97da1f7SAlex Bennée info = dkr.inspect_tag(tag) 606f97da1f7SAlex Bennée if info is None: 607f97da1f7SAlex Bennée print("Image does not exist") 608f97da1f7SAlex Bennée return 1 609f97da1f7SAlex Bennée 6107b882245SAlex Bennée if args.checktype == "checksum": 6117b882245SAlex Bennée if not args.dockerfile: 6127b882245SAlex Bennée print("Need a dockerfile for tag:%s" % (tag)) 6137b882245SAlex Bennée return 1 6147b882245SAlex Bennée 615*4112aff7SAlex Bennée dockerfile = _read_dockerfile(args.dockerfile) 6167b882245SAlex Bennée 617f97da1f7SAlex Bennée if dkr.image_matches_dockerfile(tag, dockerfile): 618f97da1f7SAlex Bennée if not args.quiet: 619f97da1f7SAlex Bennée print("Image is up to date") 620f97da1f7SAlex Bennée return 0 621f97da1f7SAlex Bennée else: 622f97da1f7SAlex Bennée print("Image needs updating") 623f97da1f7SAlex Bennée return 1 6247b882245SAlex Bennée elif args.checktype == "age": 6257b882245SAlex Bennée timestr = dkr.get_image_creation_time(info).split(".")[0] 6267b882245SAlex Bennée created = datetime.strptime(timestr, "%Y-%m-%dT%H:%M:%S") 6277b882245SAlex Bennée past = datetime.now() - timedelta(minutes=args.olderthan) 6287b882245SAlex Bennée if created < past: 6297b882245SAlex Bennée print ("Image created @ %s more than %d minutes old" % 6307b882245SAlex Bennée (timestr, args.olderthan)) 6317b882245SAlex Bennée return 1 6327b882245SAlex Bennée else: 6337b882245SAlex Bennée if not args.quiet: 6347b882245SAlex Bennée print ("Image less than %d minutes old" % (args.olderthan)) 6357b882245SAlex Bennée return 0 636f97da1f7SAlex Bennée 637f97da1f7SAlex Bennée 6384485b04bSFam Zhengdef main(): 6399459f754SMarc-André Lureau global USE_ENGINE 6409459f754SMarc-André Lureau 6414485b04bSFam Zheng parser = argparse.ArgumentParser(description="A Docker helper", 642432d8ad5SAlex Bennée usage="%s <subcommand> ..." % 643432d8ad5SAlex Bennée os.path.basename(sys.argv[0])) 6449459f754SMarc-André Lureau parser.add_argument("--engine", type=EngineEnum.argparse, choices=list(EngineEnum), 6459459f754SMarc-André Lureau help="specify which container engine to use") 6464485b04bSFam Zheng subparsers = parser.add_subparsers(title="subcommands", help=None) 6474485b04bSFam Zheng for cls in SubCommand.__subclasses__(): 6484485b04bSFam Zheng cmd = cls() 6494485b04bSFam Zheng subp = subparsers.add_parser(cmd.name, help=cmd.__doc__) 6504485b04bSFam Zheng cmd.shared_args(subp) 6514485b04bSFam Zheng cmd.args(subp) 6524485b04bSFam Zheng subp.set_defaults(cmdobj=cmd) 6534485b04bSFam Zheng args, argv = parser.parse_known_args() 6549459f754SMarc-André Lureau USE_ENGINE = args.engine 6554485b04bSFam Zheng return args.cmdobj.run(args, argv) 6564485b04bSFam Zheng 657432d8ad5SAlex Bennée 6584485b04bSFam Zhengif __name__ == "__main__": 6594485b04bSFam Zheng sys.exit(main()) 660