14112aff7SAlex 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 59*af509738SPaolo Bonzinidef _bytes_checksum(bytes): 60*af509738SPaolo Bonzini """Calculate a digest string unique to the text content""" 61*af509738SPaolo Bonzini return hashlib.sha1(bytes).hexdigest() 62*af509738SPaolo Bonzini 634485b04bSFam Zhengdef _text_checksum(text): 644485b04bSFam Zheng """Calculate a digest string unique to the text content""" 65*af509738SPaolo Bonzini return _bytes_checksum(text.encode('utf-8')) 664485b04bSFam Zheng 674112aff7SAlex Bennéedef _read_dockerfile(path): 684112aff7SAlex Bennée return open(path, 'rt', encoding='utf-8').read() 69432d8ad5SAlex Bennée 70438d1168SPhilippe Mathieu-Daudédef _file_checksum(filename): 71*af509738SPaolo Bonzini return _bytes_checksum(open(filename, 'rb').read()) 72438d1168SPhilippe Mathieu-Daudé 73432d8ad5SAlex Bennée 749459f754SMarc-André Lureaudef _guess_engine_command(): 759459f754SMarc-André Lureau """ Guess a working engine command or raise exception if not found""" 769459f754SMarc-André Lureau commands = [] 779459f754SMarc-André Lureau 789459f754SMarc-André Lureau if USE_ENGINE in [EngineEnum.AUTO, EngineEnum.PODMAN]: 799459f754SMarc-André Lureau commands += [["podman"]] 809459f754SMarc-André Lureau if USE_ENGINE in [EngineEnum.AUTO, EngineEnum.DOCKER]: 819459f754SMarc-André Lureau commands += [["docker"], ["sudo", "-n", "docker"]] 824485b04bSFam Zheng for cmd in commands: 830679f98bSEduardo Habkost try: 8483405c45SAlex Bennée # docker version will return the client details in stdout 8583405c45SAlex Bennée # but still report a status of 1 if it can't contact the daemon 8683405c45SAlex Bennée if subprocess.call(cmd + ["version"], 87c9772570SSascha Silbe stdout=DEVNULL, stderr=DEVNULL) == 0: 884485b04bSFam Zheng return cmd 890679f98bSEduardo Habkost except OSError: 900679f98bSEduardo Habkost pass 914485b04bSFam Zheng commands_txt = "\n".join([" " + " ".join(x) for x in commands]) 929459f754SMarc-André Lureau raise Exception("Cannot find working engine command. Tried:\n%s" % 934485b04bSFam Zheng commands_txt) 944485b04bSFam Zheng 95432d8ad5SAlex Bennée 962499ee9fSPhilippe Mathieu-Daudédef _copy_with_mkdir(src, root_dir, sub_path='.'): 97504ca3c2SAlex Bennée """Copy src into root_dir, creating sub_path as needed.""" 98504ca3c2SAlex Bennée dest_dir = os.path.normpath("%s/%s" % (root_dir, sub_path)) 99504ca3c2SAlex Bennée try: 100504ca3c2SAlex Bennée os.makedirs(dest_dir) 101504ca3c2SAlex Bennée except OSError: 102504ca3c2SAlex Bennée # we can safely ignore already created directories 103504ca3c2SAlex Bennée pass 104504ca3c2SAlex Bennée 105504ca3c2SAlex Bennée dest_file = "%s/%s" % (dest_dir, os.path.basename(src)) 106504ca3c2SAlex Bennée copy(src, dest_file) 107504ca3c2SAlex Bennée 108504ca3c2SAlex Bennée 109504ca3c2SAlex Bennéedef _get_so_libs(executable): 110504ca3c2SAlex Bennée """Return a list of libraries associated with an executable. 111504ca3c2SAlex Bennée 112504ca3c2SAlex Bennée The paths may be symbolic links which would need to be resolved to 113504ca3c2SAlex Bennée ensure the right data is copied.""" 114504ca3c2SAlex Bennée 115504ca3c2SAlex Bennée libs = [] 1165e33f7feSAlex Bennée ldd_re = re.compile(r"(?:\S+ => )?(\S*) \(:?0x[0-9a-f]+\)") 117504ca3c2SAlex Bennée try: 118eea2153eSAlex Bennée ldd_output = subprocess.check_output(["ldd", executable]).decode('utf-8') 119504ca3c2SAlex Bennée for line in ldd_output.split("\n"): 120504ca3c2SAlex Bennée search = ldd_re.search(line) 1215e33f7feSAlex Bennée if search: 1225e33f7feSAlex Bennée try: 1235e33f7feSAlex Bennée libs.append(s.group(1)) 1245e33f7feSAlex Bennée except IndexError: 1255e33f7feSAlex Bennée pass 126504ca3c2SAlex Bennée except subprocess.CalledProcessError: 127f03868bdSEduardo Habkost print("%s had no associated libraries (static build?)" % (executable)) 128504ca3c2SAlex Bennée 129504ca3c2SAlex Bennée return libs 130504ca3c2SAlex Bennée 131432d8ad5SAlex Bennée 132d10404b1SAlex Bennéedef _copy_binary_with_libs(src, bin_dest, dest_dir): 133d10404b1SAlex Bennée """Maybe copy a binary and all its dependent libraries. 134d10404b1SAlex Bennée 135d10404b1SAlex Bennée If bin_dest isn't set we only copy the support libraries because 136d10404b1SAlex Bennée we don't need qemu in the docker path to run (due to persistent 137d10404b1SAlex Bennée mapping). Indeed users may get confused if we aren't running what 138d10404b1SAlex Bennée is in the image. 139504ca3c2SAlex Bennée 140504ca3c2SAlex Bennée This does rely on the host file-system being fairly multi-arch 141d10404b1SAlex Bennée aware so the file don't clash with the guests layout. 142d10404b1SAlex Bennée """ 143504ca3c2SAlex Bennée 144d10404b1SAlex Bennée if bin_dest: 145d10404b1SAlex Bennée _copy_with_mkdir(src, dest_dir, os.path.dirname(bin_dest)) 146d10404b1SAlex Bennée else: 147d10404b1SAlex Bennée print("only copying support libraries for %s" % (src)) 148504ca3c2SAlex Bennée 149504ca3c2SAlex Bennée libs = _get_so_libs(src) 150504ca3c2SAlex Bennée if libs: 151504ca3c2SAlex Bennée for l in libs: 152504ca3c2SAlex Bennée so_path = os.path.dirname(l) 1535e33f7feSAlex Bennée real_l = os.path.realpath(l) 1545e33f7feSAlex Bennée _copy_with_mkdir(real_l, dest_dir, so_path) 155504ca3c2SAlex Bennée 15615352decSAlex Bennée 15715352decSAlex Bennéedef _check_binfmt_misc(executable): 15815352decSAlex Bennée """Check binfmt_misc has entry for executable in the right place. 15915352decSAlex Bennée 16015352decSAlex Bennée The details of setting up binfmt_misc are outside the scope of 16115352decSAlex Bennée this script but we should at least fail early with a useful 162d10404b1SAlex Bennée message if it won't work. 163d10404b1SAlex Bennée 164d10404b1SAlex Bennée Returns the configured binfmt path and a valid flag. For 165d10404b1SAlex Bennée persistent configurations we will still want to copy and dependent 166d10404b1SAlex Bennée libraries. 167d10404b1SAlex Bennée """ 16815352decSAlex Bennée 16915352decSAlex Bennée binary = os.path.basename(executable) 17015352decSAlex Bennée binfmt_entry = "/proc/sys/fs/binfmt_misc/%s" % (binary) 17115352decSAlex Bennée 17215352decSAlex Bennée if not os.path.exists(binfmt_entry): 17315352decSAlex Bennée print ("No binfmt_misc entry for %s" % (binary)) 174d10404b1SAlex Bennée return None, False 17515352decSAlex Bennée 17615352decSAlex Bennée with open(binfmt_entry) as x: entry = x.read() 17715352decSAlex Bennée 17843c898b7SAlex Bennée if re.search("flags:.*F.*\n", entry): 179432d8ad5SAlex Bennée print("binfmt_misc for %s uses persistent(F) mapping to host binary" % 18043c898b7SAlex Bennée (binary)) 181d10404b1SAlex Bennée return None, True 18243c898b7SAlex Bennée 1837e81d198SAlex Bennée m = re.search("interpreter (\S+)\n", entry) 1847e81d198SAlex Bennée interp = m.group(1) 1857e81d198SAlex Bennée if interp and interp != executable: 1867e81d198SAlex Bennée print("binfmt_misc for %s does not point to %s, using %s" % 1877e81d198SAlex Bennée (binary, executable, interp)) 18815352decSAlex Bennée 189d10404b1SAlex Bennée return interp, True 190d10404b1SAlex Bennée 19115352decSAlex Bennée 192c1958e9dSFam Zhengdef _read_qemu_dockerfile(img_name): 193547cb45eSAlex Bennée # special case for Debian linux-user images 194547cb45eSAlex Bennée if img_name.startswith("debian") and img_name.endswith("user"): 195547cb45eSAlex Bennée img_name = "debian-bootstrap" 196547cb45eSAlex Bennée 197c1958e9dSFam Zheng df = os.path.join(os.path.dirname(__file__), "dockerfiles", 198c1958e9dSFam Zheng img_name + ".docker") 1994112aff7SAlex Bennée return _read_dockerfile(df) 200c1958e9dSFam Zheng 201432d8ad5SAlex Bennée 202c1958e9dSFam Zhengdef _dockerfile_preprocess(df): 203c1958e9dSFam Zheng out = "" 204c1958e9dSFam Zheng for l in df.splitlines(): 205c1958e9dSFam Zheng if len(l.strip()) == 0 or l.startswith("#"): 206c1958e9dSFam Zheng continue 207c1958e9dSFam Zheng from_pref = "FROM qemu:" 208c1958e9dSFam Zheng if l.startswith(from_pref): 209c1958e9dSFam Zheng # TODO: Alternatively we could replace this line with "FROM $ID" 210c1958e9dSFam Zheng # where $ID is the image's hex id obtained with 211c1958e9dSFam Zheng # $ docker images $IMAGE --format="{{.Id}}" 212c1958e9dSFam Zheng # but unfortunately that's not supported by RHEL 7. 213c1958e9dSFam Zheng inlining = _read_qemu_dockerfile(l[len(from_pref):]) 214c1958e9dSFam Zheng out += _dockerfile_preprocess(inlining) 215c1958e9dSFam Zheng continue 216c1958e9dSFam Zheng out += l + "\n" 217c1958e9dSFam Zheng return out 218c1958e9dSFam Zheng 219432d8ad5SAlex Bennée 2204485b04bSFam Zhengclass Docker(object): 2214485b04bSFam Zheng """ Running Docker commands """ 2224485b04bSFam Zheng def __init__(self): 2239459f754SMarc-André Lureau self._command = _guess_engine_command() 224529994e2SAlex Bennée self._instance = None 2254485b04bSFam Zheng atexit.register(self._kill_instances) 22697cba1a1SFam Zheng signal.signal(signal.SIGTERM, self._kill_instances) 22797cba1a1SFam Zheng signal.signal(signal.SIGHUP, self._kill_instances) 2284485b04bSFam Zheng 22958bf7b6dSFam Zheng def _do(self, cmd, quiet=True, **kwargs): 2304485b04bSFam Zheng if quiet: 231c9772570SSascha Silbe kwargs["stdout"] = DEVNULL 2324485b04bSFam Zheng return subprocess.call(self._command + cmd, **kwargs) 2334485b04bSFam Zheng 2340b95ff72SFam Zheng def _do_check(self, cmd, quiet=True, **kwargs): 2350b95ff72SFam Zheng if quiet: 2360b95ff72SFam Zheng kwargs["stdout"] = DEVNULL 2370b95ff72SFam Zheng return subprocess.check_call(self._command + cmd, **kwargs) 2380b95ff72SFam Zheng 2394485b04bSFam Zheng def _do_kill_instances(self, only_known, only_active=True): 2404485b04bSFam Zheng cmd = ["ps", "-q"] 2414485b04bSFam Zheng if not only_active: 2424485b04bSFam Zheng cmd.append("-a") 243529994e2SAlex Bennée 244529994e2SAlex Bennée filter = "--filter=label=com.qemu.instance.uuid" 245529994e2SAlex Bennée if only_known: 246529994e2SAlex Bennée if self._instance: 247529994e2SAlex Bennée filter += "=%s" % (self._instance) 248529994e2SAlex Bennée else: 249529994e2SAlex Bennée # no point trying to kill, we finished 250529994e2SAlex Bennée return 251529994e2SAlex Bennée 252529994e2SAlex Bennée print("filter=%s" % (filter)) 253529994e2SAlex Bennée cmd.append(filter) 2544485b04bSFam Zheng for i in self._output(cmd).split(): 255529994e2SAlex Bennée self._do(["rm", "-f", i]) 2564485b04bSFam Zheng 2574485b04bSFam Zheng def clean(self): 2584485b04bSFam Zheng self._do_kill_instances(False, False) 2594485b04bSFam Zheng return 0 2604485b04bSFam Zheng 26197cba1a1SFam Zheng def _kill_instances(self, *args, **kwargs): 2624485b04bSFam Zheng return self._do_kill_instances(True) 2634485b04bSFam Zheng 2644485b04bSFam Zheng def _output(self, cmd, **kwargs): 2652d110c11SJohn Snow try: 2664485b04bSFam Zheng return subprocess.check_output(self._command + cmd, 2674485b04bSFam Zheng stderr=subprocess.STDOUT, 2684112aff7SAlex Bennée encoding='utf-8', 2694485b04bSFam Zheng **kwargs) 2702d110c11SJohn Snow except TypeError: 2712d110c11SJohn Snow # 'encoding' argument was added in 3.6+ 272884fcafcSAlex Bennée return subprocess.check_output(self._command + cmd, 273884fcafcSAlex Bennée stderr=subprocess.STDOUT, 274884fcafcSAlex Bennée **kwargs).decode('utf-8') 275884fcafcSAlex Bennée 2764485b04bSFam Zheng 277f97da1f7SAlex Bennée def inspect_tag(self, tag): 278f97da1f7SAlex Bennée try: 279f97da1f7SAlex Bennée return self._output(["inspect", tag]) 280f97da1f7SAlex Bennée except subprocess.CalledProcessError: 281f97da1f7SAlex Bennée return None 282f97da1f7SAlex Bennée 2837b882245SAlex Bennée def get_image_creation_time(self, info): 2847b882245SAlex Bennée return json.loads(info)[0]["Created"] 2857b882245SAlex Bennée 2864485b04bSFam Zheng def get_image_dockerfile_checksum(self, tag): 287f97da1f7SAlex Bennée resp = self.inspect_tag(tag) 2884485b04bSFam Zheng labels = json.loads(resp)[0]["Config"].get("Labels", {}) 2894485b04bSFam Zheng return labels.get("com.qemu.dockerfile-checksum", "") 2904485b04bSFam Zheng 291414a8ce5SAlex Bennée def build_image(self, tag, docker_dir, dockerfile, 292438d1168SPhilippe Mathieu-Daudé quiet=True, user=False, argv=None, extra_files_cksum=[]): 293432d8ad5SAlex Bennée if argv is None: 2944485b04bSFam Zheng argv = [] 2954485b04bSFam Zheng 2964112aff7SAlex Bennée tmp_df = tempfile.NamedTemporaryFile(mode="w+t", 2974112aff7SAlex Bennée encoding='utf-8', 2984112aff7SAlex Bennée dir=docker_dir, suffix=".docker") 2994485b04bSFam Zheng tmp_df.write(dockerfile) 3004485b04bSFam Zheng 301414a8ce5SAlex Bennée if user: 302414a8ce5SAlex Bennée uid = os.getuid() 303414a8ce5SAlex Bennée uname = getpwuid(uid).pw_name 304414a8ce5SAlex Bennée tmp_df.write("\n") 305414a8ce5SAlex Bennée tmp_df.write("RUN id %s 2>/dev/null || useradd -u %d -U %s" % 306414a8ce5SAlex Bennée (uname, uid, uname)) 307414a8ce5SAlex Bennée 3084485b04bSFam Zheng tmp_df.write("\n") 3094485b04bSFam Zheng tmp_df.write("LABEL com.qemu.dockerfile-checksum=%s" % 310f9172822SAlex Bennée _text_checksum(_dockerfile_preprocess(dockerfile))) 311f9172822SAlex Bennée for f, c in extra_files_cksum: 312f9172822SAlex Bennée tmp_df.write("LABEL com.qemu.%s-checksum=%s" % (f, c)) 313f9172822SAlex Bennée 3144485b04bSFam Zheng tmp_df.flush() 315a9f8d038SAlex Bennée 316432d8ad5SAlex Bennée self._do_check(["build", "-t", tag, "-f", tmp_df.name] + argv + 317a9f8d038SAlex Bennée [docker_dir], 3184485b04bSFam Zheng quiet=quiet) 3194485b04bSFam Zheng 3206e733da6SAlex Bennée def update_image(self, tag, tarball, quiet=True): 3216e733da6SAlex Bennée "Update a tagged image using " 3226e733da6SAlex Bennée 3230b95ff72SFam Zheng self._do_check(["build", "-t", tag, "-"], quiet=quiet, stdin=tarball) 3246e733da6SAlex Bennée 3254485b04bSFam Zheng def image_matches_dockerfile(self, tag, dockerfile): 3264485b04bSFam Zheng try: 3274485b04bSFam Zheng checksum = self.get_image_dockerfile_checksum(tag) 3284485b04bSFam Zheng except Exception: 3294485b04bSFam Zheng return False 330c1958e9dSFam Zheng return checksum == _text_checksum(_dockerfile_preprocess(dockerfile)) 3314485b04bSFam Zheng 33271ebbe09SAlex Bennée def run(self, cmd, keep, quiet, as_user=False): 333529994e2SAlex Bennée label = uuid.uuid4().hex 3344485b04bSFam Zheng if not keep: 335529994e2SAlex Bennée self._instance = label 33671ebbe09SAlex Bennée 33771ebbe09SAlex Bennée if as_user: 33871ebbe09SAlex Bennée uid = os.getuid() 33971ebbe09SAlex Bennée cmd = [ "-u", str(uid) ] + cmd 34071ebbe09SAlex Bennée # podman requires a bit more fiddling 34171ebbe09SAlex Bennée if self._command[0] == "podman": 342b3a790beSJohn Snow cmd.insert(0, '--userns=keep-id') 34371ebbe09SAlex Bennée 3440b95ff72SFam Zheng ret = self._do_check(["run", "--label", 3454485b04bSFam Zheng "com.qemu.instance.uuid=" + label] + cmd, 3464485b04bSFam Zheng quiet=quiet) 3474485b04bSFam Zheng if not keep: 348529994e2SAlex Bennée self._instance = None 3494485b04bSFam Zheng return ret 3504485b04bSFam Zheng 3514b08af60SFam Zheng def command(self, cmd, argv, quiet): 3524b08af60SFam Zheng return self._do([cmd] + argv, quiet=quiet) 3534b08af60SFam Zheng 354432d8ad5SAlex Bennée 3554485b04bSFam Zhengclass SubCommand(object): 3564485b04bSFam Zheng """A SubCommand template base class""" 3574485b04bSFam Zheng name = None # Subcommand name 358432d8ad5SAlex Bennée 3594485b04bSFam Zheng def shared_args(self, parser): 3604485b04bSFam Zheng parser.add_argument("--quiet", action="store_true", 361e50a6121SStefan Weil help="Run quietly unless an error occurred") 3624485b04bSFam Zheng 3634485b04bSFam Zheng def args(self, parser): 3644485b04bSFam Zheng """Setup argument parser""" 3654485b04bSFam Zheng pass 366432d8ad5SAlex Bennée 3674485b04bSFam Zheng def run(self, args, argv): 3684485b04bSFam Zheng """Run command. 3694485b04bSFam Zheng args: parsed argument by argument parser. 3704485b04bSFam Zheng argv: remaining arguments from sys.argv. 3714485b04bSFam Zheng """ 3724485b04bSFam Zheng pass 3734485b04bSFam Zheng 374432d8ad5SAlex Bennée 3754485b04bSFam Zhengclass RunCommand(SubCommand): 3764485b04bSFam Zheng """Invoke docker run and take care of cleaning up""" 3774485b04bSFam Zheng name = "run" 378432d8ad5SAlex Bennée 3794485b04bSFam Zheng def args(self, parser): 3804485b04bSFam Zheng parser.add_argument("--keep", action="store_true", 3814485b04bSFam Zheng help="Don't remove image when command completes") 3822461d80eSMarc-André Lureau parser.add_argument("--run-as-current-user", action="store_true", 3832461d80eSMarc-André Lureau help="Run container using the current user's uid") 384432d8ad5SAlex Bennée 3854485b04bSFam Zheng def run(self, args, argv): 38671ebbe09SAlex Bennée return Docker().run(argv, args.keep, quiet=args.quiet, 38771ebbe09SAlex Bennée as_user=args.run_as_current_user) 3884485b04bSFam Zheng 389432d8ad5SAlex Bennée 3904485b04bSFam Zhengclass BuildCommand(SubCommand): 391432d8ad5SAlex Bennée """ Build docker image out of a dockerfile. Arg: <tag> <dockerfile>""" 3924485b04bSFam Zheng name = "build" 393432d8ad5SAlex Bennée 3944485b04bSFam Zheng def args(self, parser): 395504ca3c2SAlex Bennée parser.add_argument("--include-executable", "-e", 396504ca3c2SAlex Bennée help="""Specify a binary that will be copied to the 397504ca3c2SAlex Bennée container together with all its dependent 398504ca3c2SAlex Bennée libraries""") 399dfae6284SPaolo Bonzini parser.add_argument("--extra-files", nargs='*', 4004c84f662SPhilippe Mathieu-Daudé help="""Specify files that will be copied in the 4014c84f662SPhilippe Mathieu-Daudé Docker image, fulfilling the ADD directive from the 4024c84f662SPhilippe Mathieu-Daudé Dockerfile""") 403414a8ce5SAlex Bennée parser.add_argument("--add-current-user", "-u", dest="user", 404414a8ce5SAlex Bennée action="store_true", 405414a8ce5SAlex Bennée help="Add the current user to image's passwd") 406dfae6284SPaolo Bonzini parser.add_argument("-t", dest="tag", 4074485b04bSFam Zheng help="Image Tag") 408dfae6284SPaolo Bonzini parser.add_argument("-f", dest="dockerfile", 4094485b04bSFam Zheng help="Dockerfile name") 4104485b04bSFam Zheng 4114485b04bSFam Zheng def run(self, args, argv): 4124112aff7SAlex Bennée dockerfile = _read_dockerfile(args.dockerfile) 4134485b04bSFam Zheng tag = args.tag 4144485b04bSFam Zheng 4154485b04bSFam Zheng dkr = Docker() 4166fe3ae3fSAlex Bennée if "--no-cache" not in argv and \ 4176fe3ae3fSAlex Bennée dkr.image_matches_dockerfile(tag, dockerfile): 4184485b04bSFam Zheng if not args.quiet: 419f03868bdSEduardo Habkost print("Image is up to date.") 420a9f8d038SAlex Bennée else: 421a9f8d038SAlex Bennée # Create a docker context directory for the build 422a9f8d038SAlex Bennée docker_dir = tempfile.mkdtemp(prefix="docker_build") 4234485b04bSFam Zheng 42415352decSAlex Bennée # Validate binfmt_misc will work 42515352decSAlex Bennée if args.include_executable: 426d10404b1SAlex Bennée qpath, enabled = _check_binfmt_misc(args.include_executable) 427d10404b1SAlex Bennée if not enabled: 42815352decSAlex Bennée return 1 42915352decSAlex Bennée 430920776eaSAlex Bennée # Is there a .pre file to run in the build context? 431920776eaSAlex Bennée docker_pre = os.path.splitext(args.dockerfile)[0]+".pre" 432920776eaSAlex Bennée if os.path.exists(docker_pre): 433f8042deaSSascha Silbe stdout = DEVNULL if args.quiet else None 434920776eaSAlex Bennée rc = subprocess.call(os.path.realpath(docker_pre), 435f8042deaSSascha Silbe cwd=docker_dir, stdout=stdout) 436920776eaSAlex Bennée if rc == 3: 437f03868bdSEduardo Habkost print("Skip") 438920776eaSAlex Bennée return 0 439920776eaSAlex Bennée elif rc != 0: 440f03868bdSEduardo Habkost print("%s exited with code %d" % (docker_pre, rc)) 441920776eaSAlex Bennée return 1 442920776eaSAlex Bennée 4434c84f662SPhilippe Mathieu-Daudé # Copy any extra files into the Docker context. These can be 4444c84f662SPhilippe Mathieu-Daudé # included by the use of the ADD directive in the Dockerfile. 445438d1168SPhilippe Mathieu-Daudé cksum = [] 446504ca3c2SAlex Bennée if args.include_executable: 447438d1168SPhilippe Mathieu-Daudé # FIXME: there is no checksum of this executable and the linked 448438d1168SPhilippe Mathieu-Daudé # libraries, once the image built any change of this executable 449438d1168SPhilippe Mathieu-Daudé # or any library won't trigger another build. 450d10404b1SAlex Bennée _copy_binary_with_libs(args.include_executable, 451d10404b1SAlex Bennée qpath, docker_dir) 452d10404b1SAlex Bennée 4534c84f662SPhilippe Mathieu-Daudé for filename in args.extra_files or []: 4544c84f662SPhilippe Mathieu-Daudé _copy_with_mkdir(filename, docker_dir) 455f9172822SAlex Bennée cksum += [(filename, _file_checksum(filename))] 456504ca3c2SAlex Bennée 45706cc3551SPhilippe Mathieu-Daudé argv += ["--build-arg=" + k.lower() + "=" + v 4584112aff7SAlex Bennée for k, v in os.environ.items() 45906cc3551SPhilippe Mathieu-Daudé if k.lower() in FILTERED_ENV_NAMES] 460a9f8d038SAlex Bennée dkr.build_image(tag, docker_dir, dockerfile, 461438d1168SPhilippe Mathieu-Daudé quiet=args.quiet, user=args.user, argv=argv, 462438d1168SPhilippe Mathieu-Daudé extra_files_cksum=cksum) 463a9f8d038SAlex Bennée 464a9f8d038SAlex Bennée rmtree(docker_dir) 465a9f8d038SAlex Bennée 4664485b04bSFam Zheng return 0 4674485b04bSFam Zheng 468432d8ad5SAlex Bennée 4696e733da6SAlex Bennéeclass UpdateCommand(SubCommand): 470432d8ad5SAlex Bennée """ Update a docker image with new executables. Args: <tag> <executable>""" 4716e733da6SAlex Bennée name = "update" 472432d8ad5SAlex Bennée 4736e733da6SAlex Bennée def args(self, parser): 4746e733da6SAlex Bennée parser.add_argument("tag", 4756e733da6SAlex Bennée help="Image Tag") 4766e733da6SAlex Bennée parser.add_argument("executable", 4776e733da6SAlex Bennée help="Executable to copy") 4786e733da6SAlex Bennée 4796e733da6SAlex Bennée def run(self, args, argv): 4806e733da6SAlex Bennée # Create a temporary tarball with our whole build context and 4816e733da6SAlex Bennée # dockerfile for the update 4826e733da6SAlex Bennée tmp = tempfile.NamedTemporaryFile(suffix="dckr.tar.gz") 4836e733da6SAlex Bennée tmp_tar = TarFile(fileobj=tmp, mode='w') 4846e733da6SAlex Bennée 4857e81d198SAlex Bennée # Add the executable to the tarball, using the current 486d10404b1SAlex Bennée # configured binfmt_misc path. If we don't get a path then we 487d10404b1SAlex Bennée # only need the support libraries copied 488d10404b1SAlex Bennée ff, enabled = _check_binfmt_misc(args.executable) 4897e81d198SAlex Bennée 490d10404b1SAlex Bennée if not enabled: 491d10404b1SAlex Bennée print("binfmt_misc not enabled, update disabled") 492d10404b1SAlex Bennée return 1 493d10404b1SAlex Bennée 494d10404b1SAlex Bennée if ff: 4956e733da6SAlex Bennée tmp_tar.add(args.executable, arcname=ff) 4966e733da6SAlex Bennée 4976e733da6SAlex Bennée # Add any associated libraries 4986e733da6SAlex Bennée libs = _get_so_libs(args.executable) 4996e733da6SAlex Bennée if libs: 5006e733da6SAlex Bennée for l in libs: 5016e733da6SAlex Bennée tmp_tar.add(os.path.realpath(l), arcname=l) 5026e733da6SAlex Bennée 5036e733da6SAlex Bennée # Create a Docker buildfile 5046e733da6SAlex Bennée df = StringIO() 5056e733da6SAlex Bennée df.write("FROM %s\n" % args.tag) 5066e733da6SAlex Bennée df.write("ADD . /\n") 5076e733da6SAlex Bennée df.seek(0) 5086e733da6SAlex Bennée 5096e733da6SAlex Bennée df_tar = TarInfo(name="Dockerfile") 5106e733da6SAlex Bennée df_tar.size = len(df.buf) 5116e733da6SAlex Bennée tmp_tar.addfile(df_tar, fileobj=df) 5126e733da6SAlex Bennée 5136e733da6SAlex Bennée tmp_tar.close() 5146e733da6SAlex Bennée 5156e733da6SAlex Bennée # reset the file pointers 5166e733da6SAlex Bennée tmp.flush() 5176e733da6SAlex Bennée tmp.seek(0) 5186e733da6SAlex Bennée 5196e733da6SAlex Bennée # Run the build with our tarball context 5206e733da6SAlex Bennée dkr = Docker() 5216e733da6SAlex Bennée dkr.update_image(args.tag, tmp, quiet=args.quiet) 5226e733da6SAlex Bennée 5236e733da6SAlex Bennée return 0 5246e733da6SAlex Bennée 525432d8ad5SAlex Bennée 5264485b04bSFam Zhengclass CleanCommand(SubCommand): 5274485b04bSFam Zheng """Clean up docker instances""" 5284485b04bSFam Zheng name = "clean" 529432d8ad5SAlex Bennée 5304485b04bSFam Zheng def run(self, args, argv): 5314485b04bSFam Zheng Docker().clean() 5324485b04bSFam Zheng return 0 5334485b04bSFam Zheng 534432d8ad5SAlex Bennée 5354b08af60SFam Zhengclass ImagesCommand(SubCommand): 5364b08af60SFam Zheng """Run "docker images" command""" 5374b08af60SFam Zheng name = "images" 538432d8ad5SAlex Bennée 5394b08af60SFam Zheng def run(self, args, argv): 5404b08af60SFam Zheng return Docker().command("images", argv, args.quiet) 5414b08af60SFam Zheng 54215df9d37SAlex Bennée 54315df9d37SAlex Bennéeclass ProbeCommand(SubCommand): 54415df9d37SAlex Bennée """Probe if we can run docker automatically""" 54515df9d37SAlex Bennée name = "probe" 54615df9d37SAlex Bennée 54715df9d37SAlex Bennée def run(self, args, argv): 54815df9d37SAlex Bennée try: 54915df9d37SAlex Bennée docker = Docker() 55015df9d37SAlex Bennée if docker._command[0] == "docker": 5518480517dSAlex Bennée print("docker") 55215df9d37SAlex Bennée elif docker._command[0] == "sudo": 5538480517dSAlex Bennée print("sudo docker") 5549459f754SMarc-André Lureau elif docker._command[0] == "podman": 5559459f754SMarc-André Lureau print("podman") 55615df9d37SAlex Bennée except Exception: 557f03868bdSEduardo Habkost print("no") 55815df9d37SAlex Bennée 55915df9d37SAlex Bennée return 56015df9d37SAlex Bennée 56115df9d37SAlex Bennée 5625e03c2d8SAlex Bennéeclass CcCommand(SubCommand): 5635e03c2d8SAlex Bennée """Compile sources with cc in images""" 5645e03c2d8SAlex Bennée name = "cc" 5655e03c2d8SAlex Bennée 5665e03c2d8SAlex Bennée def args(self, parser): 5675e03c2d8SAlex Bennée parser.add_argument("--image", "-i", required=True, 5685e03c2d8SAlex Bennée help="The docker image in which to run cc") 56999cfdb86SAlex Bennée parser.add_argument("--cc", default="cc", 57099cfdb86SAlex Bennée help="The compiler executable to call") 5715e03c2d8SAlex Bennée parser.add_argument("--source-path", "-s", nargs="*", dest="paths", 5725e03c2d8SAlex Bennée help="""Extra paths to (ro) mount into container for 5735e03c2d8SAlex Bennée reading sources""") 5745e03c2d8SAlex Bennée 5755e03c2d8SAlex Bennée def run(self, args, argv): 5765e03c2d8SAlex Bennée if argv and argv[0] == "--": 5775e03c2d8SAlex Bennée argv = argv[1:] 5785e03c2d8SAlex Bennée cwd = os.getcwd() 5795e03c2d8SAlex Bennée cmd = ["--rm", "-w", cwd, 5805e03c2d8SAlex Bennée "-v", "%s:%s:rw" % (cwd, cwd)] 5815e03c2d8SAlex Bennée if args.paths: 5825e03c2d8SAlex Bennée for p in args.paths: 5835e03c2d8SAlex Bennée cmd += ["-v", "%s:%s:ro,z" % (p, p)] 58499cfdb86SAlex Bennée cmd += [args.image, args.cc] 5855e03c2d8SAlex Bennée cmd += argv 58671ebbe09SAlex Bennée return Docker().run(cmd, False, quiet=args.quiet, 58771ebbe09SAlex Bennée as_user=True) 5885e03c2d8SAlex Bennée 5895e03c2d8SAlex Bennée 590f97da1f7SAlex Bennéeclass CheckCommand(SubCommand): 591f97da1f7SAlex Bennée """Check if we need to re-build a docker image out of a dockerfile. 592f97da1f7SAlex Bennée Arguments: <tag> <dockerfile>""" 593f97da1f7SAlex Bennée name = "check" 594f97da1f7SAlex Bennée 595f97da1f7SAlex Bennée def args(self, parser): 596f97da1f7SAlex Bennée parser.add_argument("tag", 597f97da1f7SAlex Bennée help="Image Tag") 5987b882245SAlex Bennée parser.add_argument("dockerfile", default=None, 5997b882245SAlex Bennée help="Dockerfile name", nargs='?') 6007b882245SAlex Bennée parser.add_argument("--checktype", choices=["checksum", "age"], 6017b882245SAlex Bennée default="checksum", help="check type") 6027b882245SAlex Bennée parser.add_argument("--olderthan", default=60, type=int, 6037b882245SAlex Bennée help="number of minutes") 604f97da1f7SAlex Bennée 605f97da1f7SAlex Bennée def run(self, args, argv): 606f97da1f7SAlex Bennée tag = args.tag 607f97da1f7SAlex Bennée 60843e1b2ffSAlex Bennée try: 609f97da1f7SAlex Bennée dkr = Docker() 610432d8ad5SAlex Bennée except subprocess.CalledProcessError: 61143e1b2ffSAlex Bennée print("Docker not set up") 61243e1b2ffSAlex Bennée return 1 61343e1b2ffSAlex Bennée 614f97da1f7SAlex Bennée info = dkr.inspect_tag(tag) 615f97da1f7SAlex Bennée if info is None: 616f97da1f7SAlex Bennée print("Image does not exist") 617f97da1f7SAlex Bennée return 1 618f97da1f7SAlex Bennée 6197b882245SAlex Bennée if args.checktype == "checksum": 6207b882245SAlex Bennée if not args.dockerfile: 6217b882245SAlex Bennée print("Need a dockerfile for tag:%s" % (tag)) 6227b882245SAlex Bennée return 1 6237b882245SAlex Bennée 6244112aff7SAlex Bennée dockerfile = _read_dockerfile(args.dockerfile) 6257b882245SAlex Bennée 626f97da1f7SAlex Bennée if dkr.image_matches_dockerfile(tag, dockerfile): 627f97da1f7SAlex Bennée if not args.quiet: 628f97da1f7SAlex Bennée print("Image is up to date") 629f97da1f7SAlex Bennée return 0 630f97da1f7SAlex Bennée else: 631f97da1f7SAlex Bennée print("Image needs updating") 632f97da1f7SAlex Bennée return 1 6337b882245SAlex Bennée elif args.checktype == "age": 6347b882245SAlex Bennée timestr = dkr.get_image_creation_time(info).split(".")[0] 6357b882245SAlex Bennée created = datetime.strptime(timestr, "%Y-%m-%dT%H:%M:%S") 6367b882245SAlex Bennée past = datetime.now() - timedelta(minutes=args.olderthan) 6377b882245SAlex Bennée if created < past: 6387b882245SAlex Bennée print ("Image created @ %s more than %d minutes old" % 6397b882245SAlex Bennée (timestr, args.olderthan)) 6407b882245SAlex Bennée return 1 6417b882245SAlex Bennée else: 6427b882245SAlex Bennée if not args.quiet: 6437b882245SAlex Bennée print ("Image less than %d minutes old" % (args.olderthan)) 6447b882245SAlex Bennée return 0 645f97da1f7SAlex Bennée 646f97da1f7SAlex Bennée 6474485b04bSFam Zhengdef main(): 6489459f754SMarc-André Lureau global USE_ENGINE 6499459f754SMarc-André Lureau 6504485b04bSFam Zheng parser = argparse.ArgumentParser(description="A Docker helper", 651432d8ad5SAlex Bennée usage="%s <subcommand> ..." % 652432d8ad5SAlex Bennée os.path.basename(sys.argv[0])) 6539459f754SMarc-André Lureau parser.add_argument("--engine", type=EngineEnum.argparse, choices=list(EngineEnum), 6549459f754SMarc-André Lureau help="specify which container engine to use") 6554485b04bSFam Zheng subparsers = parser.add_subparsers(title="subcommands", help=None) 6564485b04bSFam Zheng for cls in SubCommand.__subclasses__(): 6574485b04bSFam Zheng cmd = cls() 6584485b04bSFam Zheng subp = subparsers.add_parser(cmd.name, help=cmd.__doc__) 6594485b04bSFam Zheng cmd.shared_args(subp) 6604485b04bSFam Zheng cmd.args(subp) 6614485b04bSFam Zheng subp.set_defaults(cmdobj=cmd) 6624485b04bSFam Zheng args, argv = parser.parse_known_args() 6638480517dSAlex Bennée if args.engine: 6649459f754SMarc-André Lureau USE_ENGINE = args.engine 6654485b04bSFam Zheng return args.cmdobj.run(args, argv) 6664485b04bSFam Zheng 667432d8ad5SAlex Bennée 6684485b04bSFam Zhengif __name__ == "__main__": 6694485b04bSFam Zheng sys.exit(main()) 670