xref: /aosp_15_r20/external/crosvm/tools/dev_container (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1*bb4ee6a4SAndroid Build Coastguard Worker#!/usr/bin/env python3
2*bb4ee6a4SAndroid Build Coastguard Worker# Copyright 2021 The ChromiumOS Authors
3*bb4ee6a4SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be
4*bb4ee6a4SAndroid Build Coastguard Worker# found in the LICENSE file.
5*bb4ee6a4SAndroid Build Coastguard Worker
6*bb4ee6a4SAndroid Build Coastguard Worker# Usage:
7*bb4ee6a4SAndroid Build Coastguard Worker#
8*bb4ee6a4SAndroid Build Coastguard Worker# To get an interactive shell for development:
9*bb4ee6a4SAndroid Build Coastguard Worker#   ./tools/dev_container
10*bb4ee6a4SAndroid Build Coastguard Worker#
11*bb4ee6a4SAndroid Build Coastguard Worker# To run a command in the container, e.g. to run presubmits:
12*bb4ee6a4SAndroid Build Coastguard Worker#   ./tools/dev_container ./tools/presubmit
13*bb4ee6a4SAndroid Build Coastguard Worker#
14*bb4ee6a4SAndroid Build Coastguard Worker# The state of the container (including build artifacts) are preserved between
15*bb4ee6a4SAndroid Build Coastguard Worker# calls. To stop the container call:
16*bb4ee6a4SAndroid Build Coastguard Worker#   ./tools/dev_container --stop
17*bb4ee6a4SAndroid Build Coastguard Worker#
18*bb4ee6a4SAndroid Build Coastguard Worker# The dev container can also be called with a fresh container for each call that
19*bb4ee6a4SAndroid Build Coastguard Worker# is cleaned up afterwards (e.g. when run by Kokoro):
20*bb4ee6a4SAndroid Build Coastguard Worker#
21*bb4ee6a4SAndroid Build Coastguard Worker#   ./tools/dev_container --hermetic CMD
22*bb4ee6a4SAndroid Build Coastguard Worker#
23*bb4ee6a4SAndroid Build Coastguard Worker# There's an alternative container which can be used to test crosvm in crOS tree.
24*bb4ee6a4SAndroid Build Coastguard Worker# It can be launched with:
25*bb4ee6a4SAndroid Build Coastguard Worker#   ./tools/dev_container --cros
26*bb4ee6a4SAndroid Build Coastguard Worker
27*bb4ee6a4SAndroid Build Coastguard Workerimport argparse
28*bb4ee6a4SAndroid Build Coastguard Workerfrom pathlib import Path
29*bb4ee6a4SAndroid Build Coastguard Workerimport shutil
30*bb4ee6a4SAndroid Build Coastguard Workerfrom impl.util import (
31*bb4ee6a4SAndroid Build Coastguard Worker    add_common_args,
32*bb4ee6a4SAndroid Build Coastguard Worker    confirm,
33*bb4ee6a4SAndroid Build Coastguard Worker    cros_repo_root,
34*bb4ee6a4SAndroid Build Coastguard Worker    CROSVM_ROOT,
35*bb4ee6a4SAndroid Build Coastguard Worker    is_cros_repo,
36*bb4ee6a4SAndroid Build Coastguard Worker    is_kiwi_repo,
37*bb4ee6a4SAndroid Build Coastguard Worker    kiwi_repo_root,
38*bb4ee6a4SAndroid Build Coastguard Worker    is_aosp_repo,
39*bb4ee6a4SAndroid Build Coastguard Worker    aosp_repo_root,
40*bb4ee6a4SAndroid Build Coastguard Worker)
41*bb4ee6a4SAndroid Build Coastguard Workerfrom impl.command import (
42*bb4ee6a4SAndroid Build Coastguard Worker    chdir,
43*bb4ee6a4SAndroid Build Coastguard Worker    cmd,
44*bb4ee6a4SAndroid Build Coastguard Worker    quoted,
45*bb4ee6a4SAndroid Build Coastguard Worker)
46*bb4ee6a4SAndroid Build Coastguard Workerfrom typing import Optional, List
47*bb4ee6a4SAndroid Build Coastguard Workerimport getpass
48*bb4ee6a4SAndroid Build Coastguard Workerimport sys
49*bb4ee6a4SAndroid Build Coastguard Workerimport unittest
50*bb4ee6a4SAndroid Build Coastguard Workerimport os
51*bb4ee6a4SAndroid Build Coastguard Workerimport zlib
52*bb4ee6a4SAndroid Build Coastguard Worker
53*bb4ee6a4SAndroid Build Coastguard WorkerDEV_CONTAINER_NAME = (
54*bb4ee6a4SAndroid Build Coastguard Worker    f"crosvm_dev_{getpass.getuser()}_{zlib.crc32(os.path.realpath(__file__).encode('utf-8')):x}"
55*bb4ee6a4SAndroid Build Coastguard Worker)
56*bb4ee6a4SAndroid Build Coastguard WorkerCROS_CONTAINER_NAME = (
57*bb4ee6a4SAndroid Build Coastguard Worker    f"crosvm_cros_{getpass.getuser()}_{zlib.crc32(os.path.realpath(__file__).encode('utf-8')):x}"
58*bb4ee6a4SAndroid Build Coastguard Worker)
59*bb4ee6a4SAndroid Build Coastguard Worker
60*bb4ee6a4SAndroid Build Coastguard WorkerDEV_IMAGE_NAME = "gcr.io/crosvm-infra/crosvm_dev"
61*bb4ee6a4SAndroid Build Coastguard WorkerCROS_IMAGE_NAME = "gcr.io/crosvm-infra/crosvm_cros_cloudbuild"
62*bb4ee6a4SAndroid Build Coastguard WorkerDEV_IMAGE_VERSION = (CROSVM_ROOT / "tools/impl/dev_container/version").read_text().strip()
63*bb4ee6a4SAndroid Build Coastguard Worker
64*bb4ee6a4SAndroid Build Coastguard WorkerCACHE_DIR = os.environ.get("CROSVM_CONTAINER_CACHE", None)
65*bb4ee6a4SAndroid Build Coastguard Worker
66*bb4ee6a4SAndroid Build Coastguard WorkerCOMMON_ARGS = [
67*bb4ee6a4SAndroid Build Coastguard Worker    # Share cache dir
68*bb4ee6a4SAndroid Build Coastguard Worker    f"--volume {CACHE_DIR}:/cache:rw" if CACHE_DIR else None,
69*bb4ee6a4SAndroid Build Coastguard Worker    # Use tmpfs in the container for faster performance.
70*bb4ee6a4SAndroid Build Coastguard Worker    "--mount type=tmpfs,destination=/tmp",
71*bb4ee6a4SAndroid Build Coastguard Worker    # KVM is required to run a VM for testing.
72*bb4ee6a4SAndroid Build Coastguard Worker    "--device /dev/kvm" if Path("/dev/kvm").is_char_device() else None,
73*bb4ee6a4SAndroid Build Coastguard Worker    # Enable terminal colors
74*bb4ee6a4SAndroid Build Coastguard Worker    f"--env TERM={os.environ.get('TERM', 'xterm-256color')}",
75*bb4ee6a4SAndroid Build Coastguard Worker]
76*bb4ee6a4SAndroid Build Coastguard Worker
77*bb4ee6a4SAndroid Build Coastguard WorkerDOCKER_ARGS = [
78*bb4ee6a4SAndroid Build Coastguard Worker    *COMMON_ARGS,
79*bb4ee6a4SAndroid Build Coastguard Worker]
80*bb4ee6a4SAndroid Build Coastguard Worker
81*bb4ee6a4SAndroid Build Coastguard WorkerPODMAN_ARGS = [
82*bb4ee6a4SAndroid Build Coastguard Worker    *COMMON_ARGS,
83*bb4ee6a4SAndroid Build Coastguard Worker    # Allow access to group permissions of the user (e.g. for kvm access).
84*bb4ee6a4SAndroid Build Coastguard Worker    "--group-add keep-groups" if os.name == "posix" else None,
85*bb4ee6a4SAndroid Build Coastguard Worker    # Increase number of PIDs the container can spawn (we run a lot of test processes in parallel)
86*bb4ee6a4SAndroid Build Coastguard Worker    "--pids-limit=4096" if os.name == "posix" else None,
87*bb4ee6a4SAndroid Build Coastguard Worker]
88*bb4ee6a4SAndroid Build Coastguard Worker
89*bb4ee6a4SAndroid Build Coastguard Worker# Environment variables to pass through to the container if they are specified.
90*bb4ee6a4SAndroid Build Coastguard WorkerENV_PASSTHROUGH = [
91*bb4ee6a4SAndroid Build Coastguard Worker    "NEXTEST_PROFILE",
92*bb4ee6a4SAndroid Build Coastguard Worker    "http_proxy",
93*bb4ee6a4SAndroid Build Coastguard Worker    "https_proxy",
94*bb4ee6a4SAndroid Build Coastguard Worker]
95*bb4ee6a4SAndroid Build Coastguard Worker
96*bb4ee6a4SAndroid Build Coastguard Worker
97*bb4ee6a4SAndroid Build Coastguard Workerdef machine_is_running(docker: cmd):
98*bb4ee6a4SAndroid Build Coastguard Worker    machine_state = docker("machine info").stdout()
99*bb4ee6a4SAndroid Build Coastguard Worker    return "MachineState: Running" in machine_state
100*bb4ee6a4SAndroid Build Coastguard Worker
101*bb4ee6a4SAndroid Build Coastguard Worker
102*bb4ee6a4SAndroid Build Coastguard Workerdef container_name(cros: bool):
103*bb4ee6a4SAndroid Build Coastguard Worker    if cros:
104*bb4ee6a4SAndroid Build Coastguard Worker        return CROS_CONTAINER_NAME
105*bb4ee6a4SAndroid Build Coastguard Worker    else:
106*bb4ee6a4SAndroid Build Coastguard Worker        return DEV_CONTAINER_NAME
107*bb4ee6a4SAndroid Build Coastguard Worker
108*bb4ee6a4SAndroid Build Coastguard Worker
109*bb4ee6a4SAndroid Build Coastguard Workerdef container_revision(docker: cmd, container_id: str):
110*bb4ee6a4SAndroid Build Coastguard Worker    image = docker("container inspect -f {{.Config.Image}}", container_id).stdout()
111*bb4ee6a4SAndroid Build Coastguard Worker    parts = image.split(":")
112*bb4ee6a4SAndroid Build Coastguard Worker    assert len(parts) == 2, f"Invalid image name {image}"
113*bb4ee6a4SAndroid Build Coastguard Worker    return parts[1]
114*bb4ee6a4SAndroid Build Coastguard Worker
115*bb4ee6a4SAndroid Build Coastguard Worker
116*bb4ee6a4SAndroid Build Coastguard Workerdef container_id(docker: cmd, cros: bool):
117*bb4ee6a4SAndroid Build Coastguard Worker    return docker(f"ps -a -q -f name={container_name(cros)}").stdout()
118*bb4ee6a4SAndroid Build Coastguard Worker
119*bb4ee6a4SAndroid Build Coastguard Worker
120*bb4ee6a4SAndroid Build Coastguard Workerdef container_is_running(docker: cmd, cros: bool):
121*bb4ee6a4SAndroid Build Coastguard Worker    return bool(docker(f"ps -q -f name={container_name(cros)}").stdout())
122*bb4ee6a4SAndroid Build Coastguard Worker
123*bb4ee6a4SAndroid Build Coastguard Worker
124*bb4ee6a4SAndroid Build Coastguard Workerdef delete_container(docker: cmd, cros: bool):
125*bb4ee6a4SAndroid Build Coastguard Worker    cid = container_id(docker, cros)
126*bb4ee6a4SAndroid Build Coastguard Worker    if cid:
127*bb4ee6a4SAndroid Build Coastguard Worker        print(f"Deleting dev-container {cid}.")
128*bb4ee6a4SAndroid Build Coastguard Worker        docker("rm -f", cid).fg(quiet=True)
129*bb4ee6a4SAndroid Build Coastguard Worker        return True
130*bb4ee6a4SAndroid Build Coastguard Worker    return False
131*bb4ee6a4SAndroid Build Coastguard Worker
132*bb4ee6a4SAndroid Build Coastguard Worker
133*bb4ee6a4SAndroid Build Coastguard Workerdef workspace_mount_args(cros: bool):
134*bb4ee6a4SAndroid Build Coastguard Worker    """
135*bb4ee6a4SAndroid Build Coastguard Worker    Returns arguments for mounting the crosvm sources to /workspace.
136*bb4ee6a4SAndroid Build Coastguard Worker
137*bb4ee6a4SAndroid Build Coastguard Worker    In ChromeOS checkouts the crosvm repo uses a symlink or worktree checkout, which links to a
138*bb4ee6a4SAndroid Build Coastguard Worker    different folder in the ChromeOS checkout. So we need to mount the whole CrOS checkout.
139*bb4ee6a4SAndroid Build Coastguard Worker    """
140*bb4ee6a4SAndroid Build Coastguard Worker    if cros:
141*bb4ee6a4SAndroid Build Coastguard Worker        return ["--workdir /home/crosvmdev/chromiumos/src/platform/crosvm"]
142*bb4ee6a4SAndroid Build Coastguard Worker    elif is_cros_repo():
143*bb4ee6a4SAndroid Build Coastguard Worker        return [
144*bb4ee6a4SAndroid Build Coastguard Worker            f"--volume {quoted(cros_repo_root())}:/workspace:rw",
145*bb4ee6a4SAndroid Build Coastguard Worker            "--workdir /workspace/src/platform/crosvm",
146*bb4ee6a4SAndroid Build Coastguard Worker        ]
147*bb4ee6a4SAndroid Build Coastguard Worker    elif is_kiwi_repo():
148*bb4ee6a4SAndroid Build Coastguard Worker        return [
149*bb4ee6a4SAndroid Build Coastguard Worker            f"--volume {quoted(kiwi_repo_root())}:/workspace:rw",
150*bb4ee6a4SAndroid Build Coastguard Worker            # We override /scratch because we run out of memory if we use memory to back the
151*bb4ee6a4SAndroid Build Coastguard Worker            # `/scratch` mount point.
152*bb4ee6a4SAndroid Build Coastguard Worker            f"--volume {quoted(kiwi_repo_root())}/scratch:/scratch/cargo_target:rw",
153*bb4ee6a4SAndroid Build Coastguard Worker            "--workdir /workspace/platform/crosvm",
154*bb4ee6a4SAndroid Build Coastguard Worker        ]
155*bb4ee6a4SAndroid Build Coastguard Worker    elif is_aosp_repo():
156*bb4ee6a4SAndroid Build Coastguard Worker        return [
157*bb4ee6a4SAndroid Build Coastguard Worker            f"--volume {quoted(aosp_repo_root())}:/workspace:rw",
158*bb4ee6a4SAndroid Build Coastguard Worker            "--workdir /workspace/external/crosvm",
159*bb4ee6a4SAndroid Build Coastguard Worker        ]
160*bb4ee6a4SAndroid Build Coastguard Worker    else:
161*bb4ee6a4SAndroid Build Coastguard Worker        return [
162*bb4ee6a4SAndroid Build Coastguard Worker            f"--volume {quoted(CROSVM_ROOT)}:/workspace:rw",
163*bb4ee6a4SAndroid Build Coastguard Worker        ]
164*bb4ee6a4SAndroid Build Coastguard Worker
165*bb4ee6a4SAndroid Build Coastguard Worker
166*bb4ee6a4SAndroid Build Coastguard Workerdef ensure_container_is_alive(docker: cmd, docker_args: List[Optional[str]], cros: bool):
167*bb4ee6a4SAndroid Build Coastguard Worker    cid = container_id(docker, cros)
168*bb4ee6a4SAndroid Build Coastguard Worker    if cid and not container_is_running(docker, cros):
169*bb4ee6a4SAndroid Build Coastguard Worker        print("Existing container is not running.")
170*bb4ee6a4SAndroid Build Coastguard Worker        delete_container(docker, cros)
171*bb4ee6a4SAndroid Build Coastguard Worker    elif cid and not cros and container_revision(docker, cid) != DEV_IMAGE_VERSION:
172*bb4ee6a4SAndroid Build Coastguard Worker        print(f"New image is available.")
173*bb4ee6a4SAndroid Build Coastguard Worker        delete_container(docker, cros)
174*bb4ee6a4SAndroid Build Coastguard Worker
175*bb4ee6a4SAndroid Build Coastguard Worker    if not container_is_running(docker, cros):
176*bb4ee6a4SAndroid Build Coastguard Worker        # Run neverending sleep to keep container alive while we 'docker exec' commands.
177*bb4ee6a4SAndroid Build Coastguard Worker        print(f"Starting container...")
178*bb4ee6a4SAndroid Build Coastguard Worker        docker(
179*bb4ee6a4SAndroid Build Coastguard Worker            f"run --detach --name {container_name(cros)}",
180*bb4ee6a4SAndroid Build Coastguard Worker            *docker_args,
181*bb4ee6a4SAndroid Build Coastguard Worker            "sleep infinity",
182*bb4ee6a4SAndroid Build Coastguard Worker        ).fg(quiet=False)
183*bb4ee6a4SAndroid Build Coastguard Worker        cid = container_id(docker, cros)
184*bb4ee6a4SAndroid Build Coastguard Worker    else:
185*bb4ee6a4SAndroid Build Coastguard Worker        cid = container_id(docker, cros)
186*bb4ee6a4SAndroid Build Coastguard Worker        print(f"Using existing container ({cid}).")
187*bb4ee6a4SAndroid Build Coastguard Worker    return cid
188*bb4ee6a4SAndroid Build Coastguard Worker
189*bb4ee6a4SAndroid Build Coastguard Worker
190*bb4ee6a4SAndroid Build Coastguard Workerdef validate_podman(podman: cmd):
191*bb4ee6a4SAndroid Build Coastguard Worker    graph_driver_name = podman("info --format={{.Store.GraphDriverName}}").stdout()
192*bb4ee6a4SAndroid Build Coastguard Worker    config_file_name = podman("info --format={{.Store.ConfigFile}}").stdout()
193*bb4ee6a4SAndroid Build Coastguard Worker    if graph_driver_name == "vfs":
194*bb4ee6a4SAndroid Build Coastguard Worker        print("You are using vfs as a storage driver. This will be extremely slow.")
195*bb4ee6a4SAndroid Build Coastguard Worker        print("Using the overlay driver is strongly recommended.")
196*bb4ee6a4SAndroid Build Coastguard Worker        print("Note: This will delete all existing podman images and containers.")
197*bb4ee6a4SAndroid Build Coastguard Worker        if confirm(f"Do you want me to update your config in {config_file_name}?"):
198*bb4ee6a4SAndroid Build Coastguard Worker            podman("system reset -f").fg()
199*bb4ee6a4SAndroid Build Coastguard Worker            with open(config_file_name, "a") as config_file:
200*bb4ee6a4SAndroid Build Coastguard Worker                print("[storage]", file=config_file)
201*bb4ee6a4SAndroid Build Coastguard Worker                print('driver = "overlay"', file=config_file)
202*bb4ee6a4SAndroid Build Coastguard Worker
203*bb4ee6a4SAndroid Build Coastguard Worker    if os.name == "posix":
204*bb4ee6a4SAndroid Build Coastguard Worker        username = os.environ["USER"]
205*bb4ee6a4SAndroid Build Coastguard Worker        subuids = Path("/etc/subuid").read_text()
206*bb4ee6a4SAndroid Build Coastguard Worker        if not username in subuids:
207*bb4ee6a4SAndroid Build Coastguard Worker            print("Rootless podman requires subuid's to be set up for your user.")
208*bb4ee6a4SAndroid Build Coastguard Worker            usermod = cmd(
209*bb4ee6a4SAndroid Build Coastguard Worker                "sudo usermod --add-subuids 900000-965535 --add-subgids 900000-965535", username
210*bb4ee6a4SAndroid Build Coastguard Worker            )
211*bb4ee6a4SAndroid Build Coastguard Worker            print("I can fix that by running:", usermod)
212*bb4ee6a4SAndroid Build Coastguard Worker            if confirm("Ok?"):
213*bb4ee6a4SAndroid Build Coastguard Worker                usermod.fg()
214*bb4ee6a4SAndroid Build Coastguard Worker                podman("system migrate").fg()
215*bb4ee6a4SAndroid Build Coastguard Worker
216*bb4ee6a4SAndroid Build Coastguard Worker
217*bb4ee6a4SAndroid Build Coastguard Workerdef main(argv: List[str]):
218*bb4ee6a4SAndroid Build Coastguard Worker    parser = argparse.ArgumentParser()
219*bb4ee6a4SAndroid Build Coastguard Worker    add_common_args(parser)
220*bb4ee6a4SAndroid Build Coastguard Worker    parser.add_argument("--stop", action="store_true")
221*bb4ee6a4SAndroid Build Coastguard Worker    parser.add_argument("--clean", action="store_true")
222*bb4ee6a4SAndroid Build Coastguard Worker    parser.add_argument("--hermetic", action="store_true")
223*bb4ee6a4SAndroid Build Coastguard Worker    parser.add_argument("--no-interactive", action="store_true")
224*bb4ee6a4SAndroid Build Coastguard Worker    parser.add_argument("--use-docker", action="store_true")
225*bb4ee6a4SAndroid Build Coastguard Worker    parser.add_argument("--self-test", action="store_true")
226*bb4ee6a4SAndroid Build Coastguard Worker    parser.add_argument("--pull", action="store_true")
227*bb4ee6a4SAndroid Build Coastguard Worker    parser.add_argument("--cros", action="store_true")
228*bb4ee6a4SAndroid Build Coastguard Worker    parser.add_argument("command", nargs=argparse.REMAINDER)
229*bb4ee6a4SAndroid Build Coastguard Worker
230*bb4ee6a4SAndroid Build Coastguard Worker    args = parser.parse_args(argv)
231*bb4ee6a4SAndroid Build Coastguard Worker
232*bb4ee6a4SAndroid Build Coastguard Worker    chdir(CROSVM_ROOT)
233*bb4ee6a4SAndroid Build Coastguard Worker
234*bb4ee6a4SAndroid Build Coastguard Worker    if CACHE_DIR:
235*bb4ee6a4SAndroid Build Coastguard Worker        Path(CACHE_DIR).mkdir(exist_ok=True)
236*bb4ee6a4SAndroid Build Coastguard Worker
237*bb4ee6a4SAndroid Build Coastguard Worker    has_docker = shutil.which("docker") != None
238*bb4ee6a4SAndroid Build Coastguard Worker    has_podman = shutil.which("podman") != None
239*bb4ee6a4SAndroid Build Coastguard Worker    if not has_podman and not has_docker:
240*bb4ee6a4SAndroid Build Coastguard Worker        raise Exception("Please install podman (or docker) to use the dev container.")
241*bb4ee6a4SAndroid Build Coastguard Worker
242*bb4ee6a4SAndroid Build Coastguard Worker    use_docker = args.use_docker
243*bb4ee6a4SAndroid Build Coastguard Worker    if has_docker and not has_podman:
244*bb4ee6a4SAndroid Build Coastguard Worker        use_docker = True
245*bb4ee6a4SAndroid Build Coastguard Worker
246*bb4ee6a4SAndroid Build Coastguard Worker    # cros container only works in docker
247*bb4ee6a4SAndroid Build Coastguard Worker    if args.cros:
248*bb4ee6a4SAndroid Build Coastguard Worker        use_docker = True
249*bb4ee6a4SAndroid Build Coastguard Worker
250*bb4ee6a4SAndroid Build Coastguard Worker    if use_docker:
251*bb4ee6a4SAndroid Build Coastguard Worker        print(
252*bb4ee6a4SAndroid Build Coastguard Worker            "WARNING: Running dev_container with docker may cause root-owned files to be created."
253*bb4ee6a4SAndroid Build Coastguard Worker        )
254*bb4ee6a4SAndroid Build Coastguard Worker        print("Use podman to prevent this.")
255*bb4ee6a4SAndroid Build Coastguard Worker        print()
256*bb4ee6a4SAndroid Build Coastguard Worker        docker = cmd("docker")
257*bb4ee6a4SAndroid Build Coastguard Worker        docker_args = [
258*bb4ee6a4SAndroid Build Coastguard Worker            *DOCKER_ARGS,
259*bb4ee6a4SAndroid Build Coastguard Worker            *workspace_mount_args(args.cros),
260*bb4ee6a4SAndroid Build Coastguard Worker        ]
261*bb4ee6a4SAndroid Build Coastguard Worker    else:
262*bb4ee6a4SAndroid Build Coastguard Worker        docker = cmd("podman")
263*bb4ee6a4SAndroid Build Coastguard Worker
264*bb4ee6a4SAndroid Build Coastguard Worker        # On windows, podman uses wsl vm. start the default podman vm for the rest of the script
265*bb4ee6a4SAndroid Build Coastguard Worker        # to work properly.
266*bb4ee6a4SAndroid Build Coastguard Worker        if os.name == "nt" and not machine_is_running(docker):
267*bb4ee6a4SAndroid Build Coastguard Worker            print("Starting podman default machine.")
268*bb4ee6a4SAndroid Build Coastguard Worker            docker("machine start").fg(quiet=True)
269*bb4ee6a4SAndroid Build Coastguard Worker        docker_args = [
270*bb4ee6a4SAndroid Build Coastguard Worker            *PODMAN_ARGS,
271*bb4ee6a4SAndroid Build Coastguard Worker            *workspace_mount_args(args.cros),
272*bb4ee6a4SAndroid Build Coastguard Worker        ]
273*bb4ee6a4SAndroid Build Coastguard Worker        validate_podman(docker)
274*bb4ee6a4SAndroid Build Coastguard Worker
275*bb4ee6a4SAndroid Build Coastguard Worker    if args.cros:
276*bb4ee6a4SAndroid Build Coastguard Worker        docker_args.append("--privileged")  # cros container requires privileged container
277*bb4ee6a4SAndroid Build Coastguard Worker        docker_args.append(CROS_IMAGE_NAME)
278*bb4ee6a4SAndroid Build Coastguard Worker    else:
279*bb4ee6a4SAndroid Build Coastguard Worker        docker_args.append(DEV_IMAGE_NAME + ":" + DEV_IMAGE_VERSION)
280*bb4ee6a4SAndroid Build Coastguard Worker
281*bb4ee6a4SAndroid Build Coastguard Worker    # Add environment variables to command line
282*bb4ee6a4SAndroid Build Coastguard Worker    exec_args: List[str] = []
283*bb4ee6a4SAndroid Build Coastguard Worker    for key in ENV_PASSTHROUGH:
284*bb4ee6a4SAndroid Build Coastguard Worker        value = os.environ.get(key)
285*bb4ee6a4SAndroid Build Coastguard Worker        if value is not None:
286*bb4ee6a4SAndroid Build Coastguard Worker            exec_args.append("--env")
287*bb4ee6a4SAndroid Build Coastguard Worker            exec_args.append(f"{key}={quoted(value)}")
288*bb4ee6a4SAndroid Build Coastguard Worker
289*bb4ee6a4SAndroid Build Coastguard Worker    if args.self_test:
290*bb4ee6a4SAndroid Build Coastguard Worker        TestDevContainer.docker = docker
291*bb4ee6a4SAndroid Build Coastguard Worker        suite = unittest.defaultTestLoader.loadTestsFromTestCase(TestDevContainer)
292*bb4ee6a4SAndroid Build Coastguard Worker        unittest.TextTestRunner().run(suite)
293*bb4ee6a4SAndroid Build Coastguard Worker        return
294*bb4ee6a4SAndroid Build Coastguard Worker
295*bb4ee6a4SAndroid Build Coastguard Worker    if args.stop:
296*bb4ee6a4SAndroid Build Coastguard Worker        if not delete_container(docker, args.cros):
297*bb4ee6a4SAndroid Build Coastguard Worker            print(f"container is not running.")
298*bb4ee6a4SAndroid Build Coastguard Worker        return
299*bb4ee6a4SAndroid Build Coastguard Worker
300*bb4ee6a4SAndroid Build Coastguard Worker    if args.clean:
301*bb4ee6a4SAndroid Build Coastguard Worker        delete_container(docker, args.cros)
302*bb4ee6a4SAndroid Build Coastguard Worker
303*bb4ee6a4SAndroid Build Coastguard Worker    if args.pull:
304*bb4ee6a4SAndroid Build Coastguard Worker        if args.cros:
305*bb4ee6a4SAndroid Build Coastguard Worker            docker("pull", CROS_IMAGE_NAME).fg()
306*bb4ee6a4SAndroid Build Coastguard Worker        else:
307*bb4ee6a4SAndroid Build Coastguard Worker            docker("pull", f"gcr.io/crosvm-infra/crosvm_dev:{DEV_IMAGE_VERSION}").fg()
308*bb4ee6a4SAndroid Build Coastguard Worker        return
309*bb4ee6a4SAndroid Build Coastguard Worker
310*bb4ee6a4SAndroid Build Coastguard Worker    command = args.command
311*bb4ee6a4SAndroid Build Coastguard Worker
312*bb4ee6a4SAndroid Build Coastguard Worker    # Default to interactive mode if a tty is present.
313*bb4ee6a4SAndroid Build Coastguard Worker    tty_args: List[str] = []
314*bb4ee6a4SAndroid Build Coastguard Worker    if sys.stdin.isatty():
315*bb4ee6a4SAndroid Build Coastguard Worker        tty_args += ["--tty"]
316*bb4ee6a4SAndroid Build Coastguard Worker        if not args.no_interactive:
317*bb4ee6a4SAndroid Build Coastguard Worker            tty_args += ["--interactive"]
318*bb4ee6a4SAndroid Build Coastguard Worker
319*bb4ee6a4SAndroid Build Coastguard Worker    # Start an interactive shell by default
320*bb4ee6a4SAndroid Build Coastguard Worker    if args.hermetic:
321*bb4ee6a4SAndroid Build Coastguard Worker        # cmd is passed to entrypoint
322*bb4ee6a4SAndroid Build Coastguard Worker        quoted_cmd = list(map(quoted, command))
323*bb4ee6a4SAndroid Build Coastguard Worker        docker(f"run --rm", *tty_args, *docker_args, *exec_args, *quoted_cmd).fg()
324*bb4ee6a4SAndroid Build Coastguard Worker    else:
325*bb4ee6a4SAndroid Build Coastguard Worker        # cmd is executed directly
326*bb4ee6a4SAndroid Build Coastguard Worker        cid = ensure_container_is_alive(docker, docker_args, args.cros)
327*bb4ee6a4SAndroid Build Coastguard Worker        if not command:
328*bb4ee6a4SAndroid Build Coastguard Worker            command = ("/bin/bash",)
329*bb4ee6a4SAndroid Build Coastguard Worker        quoted_cmd = list(map(quoted, command))
330*bb4ee6a4SAndroid Build Coastguard Worker        docker("exec", *tty_args, *exec_args, cid, *quoted_cmd).fg()
331*bb4ee6a4SAndroid Build Coastguard Worker
332*bb4ee6a4SAndroid Build Coastguard Worker
333*bb4ee6a4SAndroid Build Coastguard Workerclass TestDevContainer(unittest.TestCase):
334*bb4ee6a4SAndroid Build Coastguard Worker    """
335*bb4ee6a4SAndroid Build Coastguard Worker    Runs live tests using the docker service.
336*bb4ee6a4SAndroid Build Coastguard Worker
337*bb4ee6a4SAndroid Build Coastguard Worker    Note: This test is not run by health-check since it cannot be run inside the
338*bb4ee6a4SAndroid Build Coastguard Worker    container. It is run by infra/recipes/health_check.py before running health checks.
339*bb4ee6a4SAndroid Build Coastguard Worker    """
340*bb4ee6a4SAndroid Build Coastguard Worker
341*bb4ee6a4SAndroid Build Coastguard Worker    docker: cmd
342*bb4ee6a4SAndroid Build Coastguard Worker    docker_args = [
343*bb4ee6a4SAndroid Build Coastguard Worker        *workspace_mount_args(cros=False),
344*bb4ee6a4SAndroid Build Coastguard Worker        *DOCKER_ARGS,
345*bb4ee6a4SAndroid Build Coastguard Worker    ]
346*bb4ee6a4SAndroid Build Coastguard Worker
347*bb4ee6a4SAndroid Build Coastguard Worker    def setUp(self):
348*bb4ee6a4SAndroid Build Coastguard Worker        # Start with a stopped container for each test.
349*bb4ee6a4SAndroid Build Coastguard Worker        delete_container(self.docker, cros=False)
350*bb4ee6a4SAndroid Build Coastguard Worker
351*bb4ee6a4SAndroid Build Coastguard Worker    def test_stopped_container(self):
352*bb4ee6a4SAndroid Build Coastguard Worker        # Create but do not run a new container.
353*bb4ee6a4SAndroid Build Coastguard Worker        self.docker(
354*bb4ee6a4SAndroid Build Coastguard Worker            f"create --name {DEV_CONTAINER_NAME}", *self.docker_args, "sleep infinity"
355*bb4ee6a4SAndroid Build Coastguard Worker        ).stdout()
356*bb4ee6a4SAndroid Build Coastguard Worker        self.assertTrue(container_id(self.docker, cros=False))
357*bb4ee6a4SAndroid Build Coastguard Worker        self.assertFalse(container_is_running(self.docker, cros=False))
358*bb4ee6a4SAndroid Build Coastguard Worker
359*bb4ee6a4SAndroid Build Coastguard Worker    def test_container_reuse(self):
360*bb4ee6a4SAndroid Build Coastguard Worker        cid = ensure_container_is_alive(self.docker, self.docker_args, cros=False)
361*bb4ee6a4SAndroid Build Coastguard Worker        cid2 = ensure_container_is_alive(self.docker, self.docker_args, cros=False)
362*bb4ee6a4SAndroid Build Coastguard Worker        self.assertEqual(cid, cid2)
363*bb4ee6a4SAndroid Build Coastguard Worker
364*bb4ee6a4SAndroid Build Coastguard Worker    def test_handling_of_stopped_container(self):
365*bb4ee6a4SAndroid Build Coastguard Worker        cid = ensure_container_is_alive(self.docker, self.docker_args, cros=False)
366*bb4ee6a4SAndroid Build Coastguard Worker        self.docker("kill", cid).fg()
367*bb4ee6a4SAndroid Build Coastguard Worker
368*bb4ee6a4SAndroid Build Coastguard Worker        # Make sure we can get back into a good state and execute commands.
369*bb4ee6a4SAndroid Build Coastguard Worker        ensure_container_is_alive(self.docker, self.docker_args, cros=False)
370*bb4ee6a4SAndroid Build Coastguard Worker        self.assertTrue(container_is_running(self.docker, cros=False))
371*bb4ee6a4SAndroid Build Coastguard Worker        main(["true"])
372*bb4ee6a4SAndroid Build Coastguard Worker
373*bb4ee6a4SAndroid Build Coastguard Worker
374*bb4ee6a4SAndroid Build Coastguard Workerif __name__ == "__main__":
375*bb4ee6a4SAndroid Build Coastguard Worker    main(sys.argv[1:])
376