workflows.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Guillaume Tucker <gtucker@gtucker.io>
To: "Nathan Chancellor" <nathan@kernel.org>,
	"Miguel Ojeda" <ojeda@kernel.org>,
	"David Gow" <davidgow@google.com>,
	"Onur Özkan" <work@onurozkan.dev>
Cc: Guillaume Tucker <gtucker@gtucker.io>,
	Arnd Bergmann <arnd@arndb.de>,
	linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org,
	linux-kbuild@vger.kernel.org,
	automated-testing@lists.yoctoproject.org,
	workflows@vger.kernel.org, llvm@lists.linux.dev
Subject: [PATCH v2 1/2] scripts: add tool to run containerized builds
Date: Thu, 18 Dec 2025 13:49:52 +0100	[thread overview]
Message-ID: <35b951506304b141047812f516fa946a4f1549a1.1766061692.git.gtucker@gtucker.io> (raw)
In-Reply-To: <cover.1766061692.git.gtucker@gtucker.io>

Add a 'scripts/container' tool written in Python to run any command in
the source tree from within a container.  This can typically be used
to call 'make' with a compiler toolchain image to run reproducible
builds but any arbitrary command can be run too.  Only Docker and
Podman are supported for this initial version.

Cc: Nathan Chancellor <nathan@kernel.org>
Cc: Miguel Ojeda <ojeda@kernel.org>
Cc: David Gow <davidgow@google.com>
Cc: "Onur Özkan" <work@onurozkan.dev>
Link: https://lore.kernel.org/all/affb7aff-dc9b-4263-bbd4-a7965c19ac4e@gtucker.io/
Signed-off-by: Guillaume Tucker <gtucker@gtucker.io>
---
 scripts/container | 194 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 194 insertions(+)
 create mode 100755 scripts/container

diff --git a/scripts/container b/scripts/container
new file mode 100755
index 000000000000..2d0143c7d43e
--- /dev/null
+++ b/scripts/container
@@ -0,0 +1,194 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-only
+# Copyright (C) 2025 Guillaume Tucker
+
+"""Containerized builds"""
+
+import abc
+import argparse
+import logging
+import os
+import shutil
+import subprocess
+import sys
+import uuid
+
+
+class ContainerRuntime(abc.ABC):
+    """Base class for a container runtime implementation"""
+
+    name = None  # Property defined in each implementation class
+
+    def __init__(self, args, logger):
+        self._uid = args.uid or os.getuid()
+        self._gid = args.gid or args.uid or os.getgid()
+        self._env_file = args.env_file
+        self._logger = logger
+
+    @classmethod
+    def is_present(cls):
+        """Determine whether the runtime is present on the system"""
+        return shutil.which(cls.name) is not None
+
+    @abc.abstractmethod
+    def _do_run(self, image, cmd, container_name):
+        """Runtime-specific handler to run a command in a container"""
+
+    @abc.abstractmethod
+    def _do_abort(self, container_name):
+        """Runtime-specific handler to abort a command in running container"""
+
+    def run(self, image, cmd):
+        """Run a command in a runtime container"""
+        container_name = str(uuid.uuid4())
+        self._logger.debug("container: %s", container_name)
+        try:
+            return self._do_run(image, cmd, container_name)
+        except KeyboardInterrupt:
+            self._logger.error("user aborted")
+            self._do_abort(container_name)
+            return 1
+
+
+class DockerRuntime(ContainerRuntime):
+    """Run a command in a Docker container"""
+
+    name = 'docker'
+
+    def _do_run(self, image, cmd, container_name):
+        cmdline = [
+            'docker', 'run',
+            '--name', container_name,
+            '--rm',
+            '--tty',
+            '--volume', f'{os.getcwd()}:/src',
+            '--workdir', '/src',
+            '--user', f'{self._uid}:{self._gid}'
+        ]
+        if self._env_file:
+            cmdline += ['--env-file', self._env_file]
+        cmdline.append(image)
+        cmdline += cmd
+        return subprocess.call(cmdline)
+
+    def _do_abort(self, container_name):
+        subprocess.call(['docker', 'kill', container_name])
+
+
+class PodmanRuntime(ContainerRuntime):
+    """Run a command in a Podman container"""
+
+    name = 'podman'
+
+    def _do_run(self, image, cmd, container_name):
+        cmdline = [
+            'podman', 'run',
+            '--name', container_name,
+            '--rm',
+            '--tty',
+            '--interactive',
+            '--volume', f'{os.getcwd()}:/src',
+            '--workdir', '/src',
+            '--userns', f'keep-id:uid={self._uid},gid={self._gid}',
+        ]
+        if self._env_file:
+            cmdline += ['--env-file', self._env_file]
+        cmdline.append(image)
+        cmdline += cmd
+        return subprocess.call(cmdline)
+
+    def _do_abort(self, container_name):
+        pass  # Signals are handled by Podman in interactive mode
+
+
+class Runtimes:
+    """List of all supported runtimes"""
+
+    runtimes = [DockerRuntime, PodmanRuntime]
+
+    @classmethod
+    def get_names(cls):
+        """Get a list of all the runtime names"""
+        return list(runtime.name for runtime in cls.runtimes)
+
+    @classmethod
+    def get(cls, name):
+        """Get a single runtime class matching the given name"""
+        for runtime in cls.runtimes:
+            if runtime.name == name:
+                if not runtime.is_present():
+                    raise ValueError(f"runtime not found: {name}")
+                return runtime
+        raise ValueError(f"unknown runtime: {runtime}")
+
+    @classmethod
+    def find(cls):
+        """Find the first runtime present on the system"""
+        for runtime in cls.runtimes:
+            if runtime.is_present():
+                return runtime
+        raise ValueError("no runtime found")
+
+
+def _get_logger(verbose):
+    """Set up a logger with the appropriate level"""
+    logger = logging.getLogger('container')
+    handler = logging.StreamHandler()
+    handler.setFormatter(logging.Formatter(
+        fmt='[container {levelname}] {message}', style='{'
+    ))
+    logger.addHandler(handler)
+    logger.setLevel(logging.DEBUG if verbose is True else logging.INFO)
+    return logger
+
+
+def main(args):
+    """Main entry point for the container tool"""
+    logger = _get_logger(args.verbose)
+    try:
+        cls = Runtimes.get(args.runtime) if args.runtime else Runtimes.find()
+    except ValueError as ex:
+        logger.error(ex)
+        return 1
+    logger.debug("runtime: %s", cls.name)
+    logger.debug("image: %s", args.image)
+    return cls(args, logger).run(args.image, args.cmd)
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(
+        'container',
+        description="Containerized builds.  See the dev-tools/container "
+        "kernel documentation section for more details."
+    )
+    parser.add_argument(
+        '-e', '--env-file',
+        help="Path to an environment file to load in the container."
+    )
+    parser.add_argument(
+        '-g', '--gid',
+        help="Group ID to use inside the container."
+    )
+    parser.add_argument(
+        '-i', '--image', required=True,
+        help="Container image name."
+    )
+    parser.add_argument(
+        '-r', '--runtime', choices=Runtimes.get_names(),
+        help="Container runtime name.  If not specified, the first one found "
+        "on the system will be used i.e. Docker if present, otherwise Podman."
+    )
+    parser.add_argument(
+        '-u', '--uid',
+        help="User ID to use inside the container.  If the -g option is not "
+        "specified, the user ID will also be set as the group ID."
+    )
+    parser.add_argument(
+        '-v', '--verbose', action='store_true',
+        help="Enable verbose output."
+    )
+    parser.add_argument(
+        'cmd', nargs='+',
+        help="Command to run in the container"
+    )
+    sys.exit(main(parser.parse_args(sys.argv[1:])))
-- 
2.47.3


  reply	other threads:[~2025-12-18 12:50 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-12-18 12:49 [PATCH v2 0/2] scripts: introduce " Guillaume Tucker
2025-12-18 12:49 ` Guillaume Tucker [this message]
2025-12-19 21:27   ` [PATCH v2 1/2] scripts: add tool to run " Nathan Chancellor
2025-12-21 20:09     ` Guillaume Tucker
2025-12-30 20:23       ` Nathan Chancellor
2025-12-21 20:19   ` Guillaume Tucker
2025-12-22  3:30     ` Miguel Ojeda
2025-12-22  9:11       ` Guillaume Tucker
2025-12-22 16:12         ` Konstantin Ryabitsev
2025-12-18 12:49 ` [PATCH v2 2/2] Documentation: dev-tools: add container.rst page Guillaume Tucker
2025-12-21 20:13   ` Guillaume Tucker
2025-12-30 20:16     ` Nathan Chancellor

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=35b951506304b141047812f516fa946a4f1549a1.1766061692.git.gtucker@gtucker.io \
    --to=gtucker@gtucker.io \
    --cc=arnd@arndb.de \
    --cc=automated-testing@lists.yoctoproject.org \
    --cc=davidgow@google.com \
    --cc=linux-kbuild@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=llvm@lists.linux.dev \
    --cc=nathan@kernel.org \
    --cc=ojeda@kernel.org \
    --cc=rust-for-linux@vger.kernel.org \
    --cc=work@onurozkan.dev \
    --cc=workflows@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox