1#!/usr/bin/env python3 2# Copyright 2021 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5# 6# Usage: 7# 8# To get an interactive shell for development: 9# ./tools/dev_container 10# 11# To run a command in the container, e.g. to run presubmits: 12# ./tools/dev_container ./tools/presubmit 13# 14# The state of the container (including build artifacts) are preserved between 15# calls. To stop the container call: 16# ./tools/dev_container --stop 17# 18# The dev container can also be called with a fresh container for each call that 19# is cleaned up afterwards (e.g. when run by Kokoro): 20# 21# ./tools/dev_container --hermetic CMD 22 23import argparse 24import getpass 25from argh import arg 26from impl.common import CROSVM_ROOT, run_main, cmd, chdir, quoted 27import sys 28import os 29 30CONTAINER_NAME = f"crosvm_dev_{getpass.getuser()}" 31IMAGE_VERSION = (CROSVM_ROOT / "tools/impl/dev_container/version").read_text().strip() 32 33try: 34 docker = cmd(os.environ.get("DOCKER", "docker")) 35except ValueError: 36 docker = cmd("podman") 37 38is_podman = docker.executable.name == "podman" 39 40# Enable interactive mode when running in an interactive terminal. 41TTY_ARGS = "--interactive --tty" if sys.stdin.isatty() else None 42 43 44DOCKER_ARGS = [ 45 TTY_ARGS, 46 # Podman will not share devices when `--privileged` is specified 47 "--privileged" if not is_podman else None, 48 # Share crosvm source 49 f"--volume {quoted(CROSVM_ROOT)}:/workspace:rw", 50 # Share devices and syslog 51 "--device /dev/kvm", 52 "--volume /dev/log:/dev/log", 53 "--device /dev/net/tun", 54 "--device /dev/vhost-net", 55 "--device /dev/vhost-vsock", 56 # Use tmpfs in the container for faster performance. 57 "--mount type=tmpfs,destination=/tmp", 58 # For plugin process jail 59 "--mount type=tmpfs,destination=/var/empty", 60 f"gcr.io/crosvm-packages/crosvm_dev:{IMAGE_VERSION}", 61] 62 63 64def container_revision(container_id: str): 65 image = docker("container inspect -f {{.Config.Image}}", container_id).stdout() 66 parts = image.split(":") 67 assert len(parts) == 2, f"Invalid image name {image}" 68 return parts[1] 69 70 71@arg("command", nargs=argparse.REMAINDER) 72def main(command: tuple[str, ...], stop: bool = False, hermetic: bool = False): 73 chdir(CROSVM_ROOT) 74 container_id = docker(f"ps -q -f name={CONTAINER_NAME}").stdout() 75 76 # Start an interactive shell by default 77 if not command: 78 command = ("/bin/bash",) 79 80 command = list(map(quoted, command)) 81 82 if stop: 83 if container_id: 84 print(f"Stopping dev-container {container_id}.") 85 docker("rm -f", container_id).fg(quiet=True) 86 else: 87 print(f"Dev-container is not running.") 88 return 89 90 if hermetic: 91 docker(f"run --rm", *DOCKER_ARGS, *command).fg() 92 else: 93 if container_id and container_revision(container_id) != IMAGE_VERSION: 94 print(f"New image is available. Stopping old container ({container_id}).") 95 docker("rm -f", container_id).fg(quiet=True) 96 container_id = None 97 98 if not container_id: 99 container_id = docker(f"run --detach --name {CONTAINER_NAME}", *DOCKER_ARGS).stdout() 100 print(f"Started dev-container ({container_id}).") 101 else: 102 print(f"Using existing dev-container instance ({container_id}).") 103 104 docker("exec", TTY_ARGS, container_id, *command).fg() 105 106 107run_main(main) 108