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 594485b04bSFam Zhengdef _text_checksum(text): 604485b04bSFam Zheng """Calculate a digest string unique to the text content""" 614112aff7SAlex Bennée return hashlib.sha1(text.encode('utf-8')).hexdigest() 624485b04bSFam Zheng 634112aff7SAlex Bennéedef _read_dockerfile(path): 644112aff7SAlex Bennée return open(path, 'rt', encoding='utf-8').read() 65432d8ad5SAlex Bennée 66438d1168SPhilippe Mathieu-Daudédef _file_checksum(filename): 674112aff7SAlex 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 the right data is copied.""" 110504ca3c2SAlex Bennée 111504ca3c2SAlex Bennée libs = [] 1125e33f7feSAlex Bennée ldd_re = re.compile(r"(?:\S+ => )?(\S*) \(:?0x[0-9a-f]+\)") 113504ca3c2SAlex Bennée try: 114eea2153eSAlex Bennée ldd_output = subprocess.check_output(["ldd", executable]).decode('utf-8') 115504ca3c2SAlex Bennée for line in ldd_output.split("\n"): 116504ca3c2SAlex Bennée search = ldd_re.search(line) 1175e33f7feSAlex Bennée if search: 1185e33f7feSAlex Bennée try: 1195e33f7feSAlex Bennée libs.append(s.group(1)) 1205e33f7feSAlex Bennée except IndexError: 1215e33f7feSAlex Bennée pass 122504ca3c2SAlex Bennée except subprocess.CalledProcessError: 123f03868bdSEduardo Habkost print("%s had no associated libraries (static build?)" % (executable)) 124504ca3c2SAlex Bennée 125504ca3c2SAlex Bennée return libs 126504ca3c2SAlex Bennée 127432d8ad5SAlex Bennée 128d10404b1SAlex Bennéedef _copy_binary_with_libs(src, bin_dest, dest_dir): 129d10404b1SAlex Bennée """Maybe copy a binary and all its dependent libraries. 130d10404b1SAlex Bennée 131d10404b1SAlex Bennée If bin_dest isn't set we only copy the support libraries because 132d10404b1SAlex Bennée we don't need qemu in the docker path to run (due to persistent 133d10404b1SAlex Bennée mapping). Indeed users may get confused if we aren't running what 134d10404b1SAlex Bennée is in the image. 135504ca3c2SAlex Bennée 136504ca3c2SAlex Bennée This does rely on the host file-system being fairly multi-arch 137d10404b1SAlex Bennée aware so the file don't clash with the guests layout. 138d10404b1SAlex Bennée """ 139504ca3c2SAlex Bennée 140d10404b1SAlex Bennée if bin_dest: 141d10404b1SAlex Bennée _copy_with_mkdir(src, dest_dir, os.path.dirname(bin_dest)) 142d10404b1SAlex Bennée else: 143d10404b1SAlex Bennée print("only copying support libraries for %s" % (src)) 144504ca3c2SAlex Bennée 145504ca3c2SAlex Bennée libs = _get_so_libs(src) 146504ca3c2SAlex Bennée if libs: 147504ca3c2SAlex Bennée for l in libs: 148504ca3c2SAlex Bennée so_path = os.path.dirname(l) 1495e33f7feSAlex Bennée real_l = os.path.realpath(l) 1505e33f7feSAlex Bennée _copy_with_mkdir(real_l, dest_dir, so_path) 151504ca3c2SAlex Bennée 15215352decSAlex Bennée 15315352decSAlex Bennéedef _check_binfmt_misc(executable): 15415352decSAlex Bennée """Check binfmt_misc has entry for executable in the right place. 15515352decSAlex Bennée 15615352decSAlex Bennée The details of setting up binfmt_misc are outside the scope of 15715352decSAlex Bennée this script but we should at least fail early with a useful 158d10404b1SAlex Bennée message if it won't work. 159d10404b1SAlex Bennée 160d10404b1SAlex Bennée Returns the configured binfmt path and a valid flag. For 161d10404b1SAlex Bennée persistent configurations we will still want to copy and dependent 162d10404b1SAlex Bennée libraries. 163d10404b1SAlex Bennée """ 16415352decSAlex Bennée 16515352decSAlex Bennée binary = os.path.basename(executable) 16615352decSAlex Bennée binfmt_entry = "/proc/sys/fs/binfmt_misc/%s" % (binary) 16715352decSAlex Bennée 16815352decSAlex Bennée if not os.path.exists(binfmt_entry): 16915352decSAlex Bennée print ("No binfmt_misc entry for %s" % (binary)) 170d10404b1SAlex Bennée return None, False 17115352decSAlex Bennée 17215352decSAlex Bennée with open(binfmt_entry) as x: entry = x.read() 17315352decSAlex Bennée 17443c898b7SAlex Bennée if re.search("flags:.*F.*\n", entry): 175432d8ad5SAlex Bennée print("binfmt_misc for %s uses persistent(F) mapping to host binary" % 17643c898b7SAlex Bennée (binary)) 177d10404b1SAlex Bennée return None, True 17843c898b7SAlex Bennée 1797e81d198SAlex Bennée m = re.search("interpreter (\S+)\n", entry) 1807e81d198SAlex Bennée interp = m.group(1) 1817e81d198SAlex Bennée if interp and interp != executable: 1827e81d198SAlex Bennée print("binfmt_misc for %s does not point to %s, using %s" % 1837e81d198SAlex Bennée (binary, executable, interp)) 18415352decSAlex Bennée 185d10404b1SAlex Bennée return interp, True 186d10404b1SAlex Bennée 18715352decSAlex Bennée 188c1958e9dSFam Zhengdef _read_qemu_dockerfile(img_name): 189547cb45eSAlex Bennée # special case for Debian linux-user images 190547cb45eSAlex Bennée if img_name.startswith("debian") and img_name.endswith("user"): 191547cb45eSAlex Bennée img_name = "debian-bootstrap" 192547cb45eSAlex Bennée 193c1958e9dSFam Zheng df = os.path.join(os.path.dirname(__file__), "dockerfiles", 194c1958e9dSFam Zheng img_name + ".docker") 1954112aff7SAlex Bennée return _read_dockerfile(df) 196c1958e9dSFam Zheng 197432d8ad5SAlex Bennée 198c1958e9dSFam Zhengdef _dockerfile_preprocess(df): 199c1958e9dSFam Zheng out = "" 200c1958e9dSFam Zheng for l in df.splitlines(): 201c1958e9dSFam Zheng if len(l.strip()) == 0 or l.startswith("#"): 202c1958e9dSFam Zheng continue 203c1958e9dSFam Zheng from_pref = "FROM qemu:" 204c1958e9dSFam Zheng if l.startswith(from_pref): 205c1958e9dSFam Zheng # TODO: Alternatively we could replace this line with "FROM $ID" 206c1958e9dSFam Zheng # where $ID is the image's hex id obtained with 207c1958e9dSFam Zheng # $ docker images $IMAGE --format="{{.Id}}" 208c1958e9dSFam Zheng # but unfortunately that's not supported by RHEL 7. 209c1958e9dSFam Zheng inlining = _read_qemu_dockerfile(l[len(from_pref):]) 210c1958e9dSFam Zheng out += _dockerfile_preprocess(inlining) 211c1958e9dSFam Zheng continue 212c1958e9dSFam Zheng out += l + "\n" 213c1958e9dSFam Zheng return out 214c1958e9dSFam Zheng 215432d8ad5SAlex Bennée 2164485b04bSFam Zhengclass Docker(object): 2174485b04bSFam Zheng """ Running Docker commands """ 2184485b04bSFam Zheng def __init__(self): 2199459f754SMarc-André Lureau self._command = _guess_engine_command() 220529994e2SAlex Bennée self._instance = None 2214485b04bSFam Zheng atexit.register(self._kill_instances) 22297cba1a1SFam Zheng signal.signal(signal.SIGTERM, self._kill_instances) 22397cba1a1SFam Zheng signal.signal(signal.SIGHUP, self._kill_instances) 2244485b04bSFam Zheng 22558bf7b6dSFam Zheng def _do(self, cmd, quiet=True, **kwargs): 2264485b04bSFam Zheng if quiet: 227c9772570SSascha Silbe kwargs["stdout"] = DEVNULL 2284485b04bSFam Zheng return subprocess.call(self._command + cmd, **kwargs) 2294485b04bSFam Zheng 2300b95ff72SFam Zheng def _do_check(self, cmd, quiet=True, **kwargs): 2310b95ff72SFam Zheng if quiet: 2320b95ff72SFam Zheng kwargs["stdout"] = DEVNULL 2330b95ff72SFam Zheng return subprocess.check_call(self._command + cmd, **kwargs) 2340b95ff72SFam Zheng 2354485b04bSFam Zheng def _do_kill_instances(self, only_known, only_active=True): 2364485b04bSFam Zheng cmd = ["ps", "-q"] 2374485b04bSFam Zheng if not only_active: 2384485b04bSFam Zheng cmd.append("-a") 239529994e2SAlex Bennée 240529994e2SAlex Bennée filter = "--filter=label=com.qemu.instance.uuid" 241529994e2SAlex Bennée if only_known: 242529994e2SAlex Bennée if self._instance: 243529994e2SAlex Bennée filter += "=%s" % (self._instance) 244529994e2SAlex Bennée else: 245529994e2SAlex Bennée # no point trying to kill, we finished 246529994e2SAlex Bennée return 247529994e2SAlex Bennée 248529994e2SAlex Bennée print("filter=%s" % (filter)) 249529994e2SAlex Bennée cmd.append(filter) 2504485b04bSFam Zheng for i in self._output(cmd).split(): 251529994e2SAlex Bennée self._do(["rm", "-f", 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): 2612d110c11SJohn Snow try: 2624485b04bSFam Zheng return subprocess.check_output(self._command + cmd, 2634485b04bSFam Zheng stderr=subprocess.STDOUT, 2644112aff7SAlex Bennée encoding='utf-8', 2654485b04bSFam Zheng **kwargs) 2662d110c11SJohn Snow except TypeError: 2672d110c11SJohn Snow # 'encoding' argument was added in 3.6+ 268884fcafcSAlex Bennée return subprocess.check_output(self._command + cmd, 269884fcafcSAlex Bennée stderr=subprocess.STDOUT, 270884fcafcSAlex Bennée **kwargs).decode('utf-8') 271884fcafcSAlex Bennée 2724485b04bSFam Zheng 273f97da1f7SAlex Bennée def inspect_tag(self, tag): 274f97da1f7SAlex Bennée try: 275f97da1f7SAlex Bennée return self._output(["inspect", tag]) 276f97da1f7SAlex Bennée except subprocess.CalledProcessError: 277f97da1f7SAlex Bennée return None 278f97da1f7SAlex Bennée 2797b882245SAlex Bennée def get_image_creation_time(self, info): 2807b882245SAlex Bennée return json.loads(info)[0]["Created"] 2817b882245SAlex Bennée 2824485b04bSFam Zheng def get_image_dockerfile_checksum(self, tag): 283f97da1f7SAlex Bennée resp = self.inspect_tag(tag) 2844485b04bSFam Zheng labels = json.loads(resp)[0]["Config"].get("Labels", {}) 2854485b04bSFam Zheng return labels.get("com.qemu.dockerfile-checksum", "") 2864485b04bSFam Zheng 287414a8ce5SAlex Bennée def build_image(self, tag, docker_dir, dockerfile, 288438d1168SPhilippe Mathieu-Daudé quiet=True, user=False, argv=None, extra_files_cksum=[]): 289432d8ad5SAlex Bennée if argv is None: 2904485b04bSFam Zheng argv = [] 2914485b04bSFam Zheng 2924112aff7SAlex Bennée tmp_df = tempfile.NamedTemporaryFile(mode="w+t", 2934112aff7SAlex Bennée encoding='utf-8', 2944112aff7SAlex Bennée dir=docker_dir, suffix=".docker") 2954485b04bSFam Zheng tmp_df.write(dockerfile) 2964485b04bSFam Zheng 297414a8ce5SAlex Bennée if user: 298414a8ce5SAlex Bennée uid = os.getuid() 299414a8ce5SAlex Bennée uname = getpwuid(uid).pw_name 300414a8ce5SAlex Bennée tmp_df.write("\n") 301414a8ce5SAlex Bennée tmp_df.write("RUN id %s 2>/dev/null || useradd -u %d -U %s" % 302414a8ce5SAlex Bennée (uname, uid, uname)) 303414a8ce5SAlex Bennée 3044485b04bSFam Zheng tmp_df.write("\n") 3054485b04bSFam Zheng tmp_df.write("LABEL com.qemu.dockerfile-checksum=%s" % 306f9172822SAlex Bennée _text_checksum(_dockerfile_preprocess(dockerfile))) 307f9172822SAlex Bennée for f, c in extra_files_cksum: 308f9172822SAlex Bennée tmp_df.write("LABEL com.qemu.%s-checksum=%s" % (f, c)) 309f9172822SAlex Bennée 3104485b04bSFam Zheng tmp_df.flush() 311a9f8d038SAlex Bennée 312432d8ad5SAlex Bennée self._do_check(["build", "-t", tag, "-f", tmp_df.name] + argv + 313a9f8d038SAlex Bennée [docker_dir], 3144485b04bSFam Zheng quiet=quiet) 3154485b04bSFam Zheng 3166e733da6SAlex Bennée def update_image(self, tag, tarball, quiet=True): 3176e733da6SAlex Bennée "Update a tagged image using " 3186e733da6SAlex Bennée 3190b95ff72SFam Zheng self._do_check(["build", "-t", tag, "-"], quiet=quiet, stdin=tarball) 3206e733da6SAlex Bennée 3214485b04bSFam Zheng def image_matches_dockerfile(self, tag, dockerfile): 3224485b04bSFam Zheng try: 3234485b04bSFam Zheng checksum = self.get_image_dockerfile_checksum(tag) 3244485b04bSFam Zheng except Exception: 3254485b04bSFam Zheng return False 326c1958e9dSFam Zheng return checksum == _text_checksum(_dockerfile_preprocess(dockerfile)) 3274485b04bSFam Zheng 32871ebbe09SAlex Bennée def run(self, cmd, keep, quiet, as_user=False): 329529994e2SAlex Bennée label = uuid.uuid4().hex 3304485b04bSFam Zheng if not keep: 331529994e2SAlex Bennée self._instance = label 33271ebbe09SAlex Bennée 33371ebbe09SAlex Bennée if as_user: 33471ebbe09SAlex Bennée uid = os.getuid() 33571ebbe09SAlex Bennée cmd = [ "-u", str(uid) ] + cmd 33671ebbe09SAlex Bennée # podman requires a bit more fiddling 33771ebbe09SAlex Bennée if self._command[0] == "podman": 338b3a790beSJohn Snow cmd.insert(0, '--userns=keep-id') 33971ebbe09SAlex Bennée 3400b95ff72SFam Zheng ret = self._do_check(["run", "--label", 3414485b04bSFam Zheng "com.qemu.instance.uuid=" + label] + cmd, 3424485b04bSFam Zheng quiet=quiet) 3434485b04bSFam Zheng if not keep: 344529994e2SAlex Bennée self._instance = None 3454485b04bSFam Zheng return ret 3464485b04bSFam Zheng 3474b08af60SFam Zheng def command(self, cmd, argv, quiet): 3484b08af60SFam Zheng return self._do([cmd] + argv, quiet=quiet) 3494b08af60SFam Zheng 350432d8ad5SAlex Bennée 3514485b04bSFam Zhengclass SubCommand(object): 3524485b04bSFam Zheng """A SubCommand template base class""" 3534485b04bSFam Zheng name = None # Subcommand name 354432d8ad5SAlex Bennée 3554485b04bSFam Zheng def shared_args(self, parser): 3564485b04bSFam Zheng parser.add_argument("--quiet", action="store_true", 357e50a6121SStefan Weil help="Run quietly unless an error occurred") 3584485b04bSFam Zheng 3594485b04bSFam Zheng def args(self, parser): 3604485b04bSFam Zheng """Setup argument parser""" 3614485b04bSFam Zheng pass 362432d8ad5SAlex Bennée 3634485b04bSFam Zheng def run(self, args, argv): 3644485b04bSFam Zheng """Run command. 3654485b04bSFam Zheng args: parsed argument by argument parser. 3664485b04bSFam Zheng argv: remaining arguments from sys.argv. 3674485b04bSFam Zheng """ 3684485b04bSFam Zheng pass 3694485b04bSFam Zheng 370432d8ad5SAlex Bennée 3714485b04bSFam Zhengclass RunCommand(SubCommand): 3724485b04bSFam Zheng """Invoke docker run and take care of cleaning up""" 3734485b04bSFam Zheng name = "run" 374432d8ad5SAlex Bennée 3754485b04bSFam Zheng def args(self, parser): 3764485b04bSFam Zheng parser.add_argument("--keep", action="store_true", 3774485b04bSFam Zheng help="Don't remove image when command completes") 3782461d80eSMarc-André Lureau parser.add_argument("--run-as-current-user", action="store_true", 3792461d80eSMarc-André Lureau help="Run container using the current user's uid") 380432d8ad5SAlex Bennée 3814485b04bSFam Zheng def run(self, args, argv): 38271ebbe09SAlex Bennée return Docker().run(argv, args.keep, quiet=args.quiet, 38371ebbe09SAlex Bennée as_user=args.run_as_current_user) 3844485b04bSFam Zheng 385432d8ad5SAlex Bennée 3864485b04bSFam Zhengclass BuildCommand(SubCommand): 387432d8ad5SAlex Bennée """ Build docker image out of a dockerfile. Arg: <tag> <dockerfile>""" 3884485b04bSFam Zheng name = "build" 389432d8ad5SAlex Bennée 3904485b04bSFam Zheng def args(self, parser): 391504ca3c2SAlex Bennée parser.add_argument("--include-executable", "-e", 392504ca3c2SAlex Bennée help="""Specify a binary that will be copied to the 393504ca3c2SAlex Bennée container together with all its dependent 394504ca3c2SAlex Bennée libraries""") 395*dfae6284SPaolo Bonzini parser.add_argument("--extra-files", nargs='*', 3964c84f662SPhilippe Mathieu-Daudé help="""Specify files that will be copied in the 3974c84f662SPhilippe Mathieu-Daudé Docker image, fulfilling the ADD directive from the 3984c84f662SPhilippe Mathieu-Daudé Dockerfile""") 399414a8ce5SAlex Bennée parser.add_argument("--add-current-user", "-u", dest="user", 400414a8ce5SAlex Bennée action="store_true", 401414a8ce5SAlex Bennée help="Add the current user to image's passwd") 402*dfae6284SPaolo Bonzini parser.add_argument("-t", dest="tag", 4034485b04bSFam Zheng help="Image Tag") 404*dfae6284SPaolo Bonzini parser.add_argument("-f", dest="dockerfile", 4054485b04bSFam Zheng help="Dockerfile name") 4064485b04bSFam Zheng 4074485b04bSFam Zheng def run(self, args, argv): 4084112aff7SAlex Bennée dockerfile = _read_dockerfile(args.dockerfile) 4094485b04bSFam Zheng tag = args.tag 4104485b04bSFam Zheng 4114485b04bSFam Zheng dkr = Docker() 4126fe3ae3fSAlex Bennée if "--no-cache" not in argv and \ 4136fe3ae3fSAlex Bennée dkr.image_matches_dockerfile(tag, dockerfile): 4144485b04bSFam Zheng if not args.quiet: 415f03868bdSEduardo Habkost print("Image is up to date.") 416a9f8d038SAlex Bennée else: 417a9f8d038SAlex Bennée # Create a docker context directory for the build 418a9f8d038SAlex Bennée docker_dir = tempfile.mkdtemp(prefix="docker_build") 4194485b04bSFam Zheng 42015352decSAlex Bennée # Validate binfmt_misc will work 42115352decSAlex Bennée if args.include_executable: 422d10404b1SAlex Bennée qpath, enabled = _check_binfmt_misc(args.include_executable) 423d10404b1SAlex Bennée if not enabled: 42415352decSAlex Bennée return 1 42515352decSAlex Bennée 426920776eaSAlex Bennée # Is there a .pre file to run in the build context? 427920776eaSAlex Bennée docker_pre = os.path.splitext(args.dockerfile)[0]+".pre" 428920776eaSAlex Bennée if os.path.exists(docker_pre): 429f8042deaSSascha Silbe stdout = DEVNULL if args.quiet else None 430920776eaSAlex Bennée rc = subprocess.call(os.path.realpath(docker_pre), 431f8042deaSSascha Silbe cwd=docker_dir, stdout=stdout) 432920776eaSAlex Bennée if rc == 3: 433f03868bdSEduardo Habkost print("Skip") 434920776eaSAlex Bennée return 0 435920776eaSAlex Bennée elif rc != 0: 436f03868bdSEduardo Habkost print("%s exited with code %d" % (docker_pre, rc)) 437920776eaSAlex Bennée return 1 438920776eaSAlex Bennée 4394c84f662SPhilippe Mathieu-Daudé # Copy any extra files into the Docker context. These can be 4404c84f662SPhilippe Mathieu-Daudé # included by the use of the ADD directive in the Dockerfile. 441438d1168SPhilippe Mathieu-Daudé cksum = [] 442504ca3c2SAlex Bennée if args.include_executable: 443438d1168SPhilippe Mathieu-Daudé # FIXME: there is no checksum of this executable and the linked 444438d1168SPhilippe Mathieu-Daudé # libraries, once the image built any change of this executable 445438d1168SPhilippe Mathieu-Daudé # or any library won't trigger another build. 446d10404b1SAlex Bennée _copy_binary_with_libs(args.include_executable, 447d10404b1SAlex Bennée qpath, docker_dir) 448d10404b1SAlex Bennée 4494c84f662SPhilippe Mathieu-Daudé for filename in args.extra_files or []: 4504c84f662SPhilippe Mathieu-Daudé _copy_with_mkdir(filename, docker_dir) 451f9172822SAlex Bennée cksum += [(filename, _file_checksum(filename))] 452504ca3c2SAlex Bennée 45306cc3551SPhilippe Mathieu-Daudé argv += ["--build-arg=" + k.lower() + "=" + v 4544112aff7SAlex Bennée for k, v in os.environ.items() 45506cc3551SPhilippe Mathieu-Daudé if k.lower() in FILTERED_ENV_NAMES] 456a9f8d038SAlex Bennée dkr.build_image(tag, docker_dir, dockerfile, 457438d1168SPhilippe Mathieu-Daudé quiet=args.quiet, user=args.user, argv=argv, 458438d1168SPhilippe Mathieu-Daudé extra_files_cksum=cksum) 459a9f8d038SAlex Bennée 460a9f8d038SAlex Bennée rmtree(docker_dir) 461a9f8d038SAlex Bennée 4624485b04bSFam Zheng return 0 4634485b04bSFam Zheng 464432d8ad5SAlex Bennée 4656e733da6SAlex Bennéeclass UpdateCommand(SubCommand): 466432d8ad5SAlex Bennée """ Update a docker image with new executables. Args: <tag> <executable>""" 4676e733da6SAlex Bennée name = "update" 468432d8ad5SAlex Bennée 4696e733da6SAlex Bennée def args(self, parser): 4706e733da6SAlex Bennée parser.add_argument("tag", 4716e733da6SAlex Bennée help="Image Tag") 4726e733da6SAlex Bennée parser.add_argument("executable", 4736e733da6SAlex Bennée help="Executable to copy") 4746e733da6SAlex Bennée 4756e733da6SAlex Bennée def run(self, args, argv): 4766e733da6SAlex Bennée # Create a temporary tarball with our whole build context and 4776e733da6SAlex Bennée # dockerfile for the update 4786e733da6SAlex Bennée tmp = tempfile.NamedTemporaryFile(suffix="dckr.tar.gz") 4796e733da6SAlex Bennée tmp_tar = TarFile(fileobj=tmp, mode='w') 4806e733da6SAlex Bennée 4817e81d198SAlex Bennée # Add the executable to the tarball, using the current 482d10404b1SAlex Bennée # configured binfmt_misc path. If we don't get a path then we 483d10404b1SAlex Bennée # only need the support libraries copied 484d10404b1SAlex Bennée ff, enabled = _check_binfmt_misc(args.executable) 4857e81d198SAlex Bennée 486d10404b1SAlex Bennée if not enabled: 487d10404b1SAlex Bennée print("binfmt_misc not enabled, update disabled") 488d10404b1SAlex Bennée return 1 489d10404b1SAlex Bennée 490d10404b1SAlex Bennée if ff: 4916e733da6SAlex Bennée tmp_tar.add(args.executable, arcname=ff) 4926e733da6SAlex Bennée 4936e733da6SAlex Bennée # Add any associated libraries 4946e733da6SAlex Bennée libs = _get_so_libs(args.executable) 4956e733da6SAlex Bennée if libs: 4966e733da6SAlex Bennée for l in libs: 4976e733da6SAlex Bennée tmp_tar.add(os.path.realpath(l), arcname=l) 4986e733da6SAlex Bennée 4996e733da6SAlex Bennée # Create a Docker buildfile 5006e733da6SAlex Bennée df = StringIO() 5016e733da6SAlex Bennée df.write("FROM %s\n" % args.tag) 5026e733da6SAlex Bennée df.write("ADD . /\n") 5036e733da6SAlex Bennée df.seek(0) 5046e733da6SAlex Bennée 5056e733da6SAlex Bennée df_tar = TarInfo(name="Dockerfile") 5066e733da6SAlex Bennée df_tar.size = len(df.buf) 5076e733da6SAlex Bennée tmp_tar.addfile(df_tar, fileobj=df) 5086e733da6SAlex Bennée 5096e733da6SAlex Bennée tmp_tar.close() 5106e733da6SAlex Bennée 5116e733da6SAlex Bennée # reset the file pointers 5126e733da6SAlex Bennée tmp.flush() 5136e733da6SAlex Bennée tmp.seek(0) 5146e733da6SAlex Bennée 5156e733da6SAlex Bennée # Run the build with our tarball context 5166e733da6SAlex Bennée dkr = Docker() 5176e733da6SAlex Bennée dkr.update_image(args.tag, tmp, quiet=args.quiet) 5186e733da6SAlex Bennée 5196e733da6SAlex Bennée return 0 5206e733da6SAlex Bennée 521432d8ad5SAlex Bennée 5224485b04bSFam Zhengclass CleanCommand(SubCommand): 5234485b04bSFam Zheng """Clean up docker instances""" 5244485b04bSFam Zheng name = "clean" 525432d8ad5SAlex Bennée 5264485b04bSFam Zheng def run(self, args, argv): 5274485b04bSFam Zheng Docker().clean() 5284485b04bSFam Zheng return 0 5294485b04bSFam Zheng 530432d8ad5SAlex Bennée 5314b08af60SFam Zhengclass ImagesCommand(SubCommand): 5324b08af60SFam Zheng """Run "docker images" command""" 5334b08af60SFam Zheng name = "images" 534432d8ad5SAlex Bennée 5354b08af60SFam Zheng def run(self, args, argv): 5364b08af60SFam Zheng return Docker().command("images", argv, args.quiet) 5374b08af60SFam Zheng 53815df9d37SAlex Bennée 53915df9d37SAlex Bennéeclass ProbeCommand(SubCommand): 54015df9d37SAlex Bennée """Probe if we can run docker automatically""" 54115df9d37SAlex Bennée name = "probe" 54215df9d37SAlex Bennée 54315df9d37SAlex Bennée def run(self, args, argv): 54415df9d37SAlex Bennée try: 54515df9d37SAlex Bennée docker = Docker() 54615df9d37SAlex Bennée if docker._command[0] == "docker": 5478480517dSAlex Bennée print("docker") 54815df9d37SAlex Bennée elif docker._command[0] == "sudo": 5498480517dSAlex Bennée print("sudo docker") 5509459f754SMarc-André Lureau elif docker._command[0] == "podman": 5519459f754SMarc-André Lureau print("podman") 55215df9d37SAlex Bennée except Exception: 553f03868bdSEduardo Habkost print("no") 55415df9d37SAlex Bennée 55515df9d37SAlex Bennée return 55615df9d37SAlex Bennée 55715df9d37SAlex Bennée 5585e03c2d8SAlex Bennéeclass CcCommand(SubCommand): 5595e03c2d8SAlex Bennée """Compile sources with cc in images""" 5605e03c2d8SAlex Bennée name = "cc" 5615e03c2d8SAlex Bennée 5625e03c2d8SAlex Bennée def args(self, parser): 5635e03c2d8SAlex Bennée parser.add_argument("--image", "-i", required=True, 5645e03c2d8SAlex Bennée help="The docker image in which to run cc") 56599cfdb86SAlex Bennée parser.add_argument("--cc", default="cc", 56699cfdb86SAlex Bennée help="The compiler executable to call") 5675e03c2d8SAlex Bennée parser.add_argument("--source-path", "-s", nargs="*", dest="paths", 5685e03c2d8SAlex Bennée help="""Extra paths to (ro) mount into container for 5695e03c2d8SAlex Bennée reading sources""") 5705e03c2d8SAlex Bennée 5715e03c2d8SAlex Bennée def run(self, args, argv): 5725e03c2d8SAlex Bennée if argv and argv[0] == "--": 5735e03c2d8SAlex Bennée argv = argv[1:] 5745e03c2d8SAlex Bennée cwd = os.getcwd() 5755e03c2d8SAlex Bennée cmd = ["--rm", "-w", cwd, 5765e03c2d8SAlex Bennée "-v", "%s:%s:rw" % (cwd, cwd)] 5775e03c2d8SAlex Bennée if args.paths: 5785e03c2d8SAlex Bennée for p in args.paths: 5795e03c2d8SAlex Bennée cmd += ["-v", "%s:%s:ro,z" % (p, p)] 58099cfdb86SAlex Bennée cmd += [args.image, args.cc] 5815e03c2d8SAlex Bennée cmd += argv 58271ebbe09SAlex Bennée return Docker().run(cmd, False, quiet=args.quiet, 58371ebbe09SAlex Bennée as_user=True) 5845e03c2d8SAlex Bennée 5855e03c2d8SAlex Bennée 586f97da1f7SAlex Bennéeclass CheckCommand(SubCommand): 587f97da1f7SAlex Bennée """Check if we need to re-build a docker image out of a dockerfile. 588f97da1f7SAlex Bennée Arguments: <tag> <dockerfile>""" 589f97da1f7SAlex Bennée name = "check" 590f97da1f7SAlex Bennée 591f97da1f7SAlex Bennée def args(self, parser): 592f97da1f7SAlex Bennée parser.add_argument("tag", 593f97da1f7SAlex Bennée help="Image Tag") 5947b882245SAlex Bennée parser.add_argument("dockerfile", default=None, 5957b882245SAlex Bennée help="Dockerfile name", nargs='?') 5967b882245SAlex Bennée parser.add_argument("--checktype", choices=["checksum", "age"], 5977b882245SAlex Bennée default="checksum", help="check type") 5987b882245SAlex Bennée parser.add_argument("--olderthan", default=60, type=int, 5997b882245SAlex Bennée help="number of minutes") 600f97da1f7SAlex Bennée 601f97da1f7SAlex Bennée def run(self, args, argv): 602f97da1f7SAlex Bennée tag = args.tag 603f97da1f7SAlex Bennée 60443e1b2ffSAlex Bennée try: 605f97da1f7SAlex Bennée dkr = Docker() 606432d8ad5SAlex Bennée except subprocess.CalledProcessError: 60743e1b2ffSAlex Bennée print("Docker not set up") 60843e1b2ffSAlex Bennée return 1 60943e1b2ffSAlex Bennée 610f97da1f7SAlex Bennée info = dkr.inspect_tag(tag) 611f97da1f7SAlex Bennée if info is None: 612f97da1f7SAlex Bennée print("Image does not exist") 613f97da1f7SAlex Bennée return 1 614f97da1f7SAlex Bennée 6157b882245SAlex Bennée if args.checktype == "checksum": 6167b882245SAlex Bennée if not args.dockerfile: 6177b882245SAlex Bennée print("Need a dockerfile for tag:%s" % (tag)) 6187b882245SAlex Bennée return 1 6197b882245SAlex Bennée 6204112aff7SAlex Bennée dockerfile = _read_dockerfile(args.dockerfile) 6217b882245SAlex Bennée 622f97da1f7SAlex Bennée if dkr.image_matches_dockerfile(tag, dockerfile): 623f97da1f7SAlex Bennée if not args.quiet: 624f97da1f7SAlex Bennée print("Image is up to date") 625f97da1f7SAlex Bennée return 0 626f97da1f7SAlex Bennée else: 627f97da1f7SAlex Bennée print("Image needs updating") 628f97da1f7SAlex Bennée return 1 6297b882245SAlex Bennée elif args.checktype == "age": 6307b882245SAlex Bennée timestr = dkr.get_image_creation_time(info).split(".")[0] 6317b882245SAlex Bennée created = datetime.strptime(timestr, "%Y-%m-%dT%H:%M:%S") 6327b882245SAlex Bennée past = datetime.now() - timedelta(minutes=args.olderthan) 6337b882245SAlex Bennée if created < past: 6347b882245SAlex Bennée print ("Image created @ %s more than %d minutes old" % 6357b882245SAlex Bennée (timestr, args.olderthan)) 6367b882245SAlex Bennée return 1 6377b882245SAlex Bennée else: 6387b882245SAlex Bennée if not args.quiet: 6397b882245SAlex Bennée print ("Image less than %d minutes old" % (args.olderthan)) 6407b882245SAlex Bennée return 0 641f97da1f7SAlex Bennée 642f97da1f7SAlex Bennée 6434485b04bSFam Zhengdef main(): 6449459f754SMarc-André Lureau global USE_ENGINE 6459459f754SMarc-André Lureau 6464485b04bSFam Zheng parser = argparse.ArgumentParser(description="A Docker helper", 647432d8ad5SAlex Bennée usage="%s <subcommand> ..." % 648432d8ad5SAlex Bennée os.path.basename(sys.argv[0])) 6499459f754SMarc-André Lureau parser.add_argument("--engine", type=EngineEnum.argparse, choices=list(EngineEnum), 6509459f754SMarc-André Lureau help="specify which container engine to use") 6514485b04bSFam Zheng subparsers = parser.add_subparsers(title="subcommands", help=None) 6524485b04bSFam Zheng for cls in SubCommand.__subclasses__(): 6534485b04bSFam Zheng cmd = cls() 6544485b04bSFam Zheng subp = subparsers.add_parser(cmd.name, help=cmd.__doc__) 6554485b04bSFam Zheng cmd.shared_args(subp) 6564485b04bSFam Zheng cmd.args(subp) 6574485b04bSFam Zheng subp.set_defaults(cmdobj=cmd) 6584485b04bSFam Zheng args, argv = parser.parse_known_args() 6598480517dSAlex Bennée if args.engine: 6609459f754SMarc-André Lureau USE_ENGINE = args.engine 6614485b04bSFam Zheng return args.cmdobj.run(args, argv) 6624485b04bSFam Zheng 663432d8ad5SAlex Bennée 6644485b04bSFam Zhengif __name__ == "__main__": 6654485b04bSFam Zheng sys.exit(main()) 666