* [yocto-autobuilder2][PATCH v3 0/6] Implement 'containers' jobs
@ 2026-06-06 2:51 tim.orling
2026-06-06 2:51 ` [yocto-autobuilder2][PATCH v3 1/6] scripts: add vcontainer-tarball setup, integration, and publishing tim.orling
` (6 more replies)
0 siblings, 7 replies; 19+ messages in thread
From: tim.orling @ 2026-06-06 2:51 UTC (permalink / raw)
To: yocto-patches
From: Tim Orling <tim.orling@konsulko.com>
This series builds on top of the patches already merged from v2.
This series contains the 'yocto-autobuilder-helper' patches to enable:
* building, testing and publishing of 'vcontainer-tarball'
- vcontainer-tests job
* building and pushing containers from:
- meta-virtualization
* containers are pushed when the push_containers boolean is set in the
AutoBuilder UI
- regstries are set with CONTAINER_REGISTRIES variable
* authentication uses local user credentials (e.g. ~/.docker/config.json
or .../auth.json for podman)
* containers are tagged with PV_MAJOR, PV_MAJOR.PV_MINOR, DISTRO_CODENAME
and yocto-x.y release (yocto-x.y.z for release builds).
Additional features, such as container signing and attaching SLSA provenance
attestations will come in a future series, once this initial functionality
is agreed upon an stable.
Concerns about the possible infinite loop around the tarball extraction
"while True, try, expect OSError" code [1] are addressed separately [2],
since the original patch has been merged.
This series relies on the resolution of [3] and [4].
[1] https://lore.kernel.org/yocto-patches/02f0c35f16a8a51bbe0e89145353008f67180a84.camel@pbarker.dev/
[2] https://lore.kernel.org/yocto-patches/20260606011918.61582-1-tim.orling@konsulko.com/
[3] https://lore.kernel.org/yocto-meta-virtualization/aiJDIinJWZhO4Jid@gmail.com/T/#t
[4] https://lore.kernel.org/yocto-meta-virtualization/20260604001931.96550-1-tim.orling@konsulko.com/T/#u
Changes in v2:
* Simplify by merging vdkr-tests and vpdmn-tests into vcontainer-tests
* Simplify by building containers from meta-virtualization:
- Depends on resolution of: https://lists.yoctoproject.org/g/meta-virtualization/message/9826
* Workaround for recent (since Friday May 29, 2026) errors:
Error: reading blob sha256:<hash>: file integrity checksum failed for "<file>"
- This is probably related to either sstate changes or recent changes in vcontainer-common...
Changes in v3:
* vcontainer-tarball is installed in jobs that set "vcontainer" similar to "extratools", instead
of the more global "buildtools" behavior.
* The (rather large amount of) code for pushing containers in run-config is broken out into
a dedicated run-push-containers script.
* Workaround for pushing containers is moved to run-push-containers. The most recent
behavior (on top of meta-virt master-next) showed a different error pattern:
Error: reading blob sha256:<hash>: EOF
The workaround simply removes all container images from the VM's container-registry before
importing or pushing new container images. The commit message was reworded accordingly.
* Rather than installing latest versions of pip, setuptools and wheel, silence the pip
warning with PIP_DISABLE_PIP_VERSION_CHECK=1
* Rather than installing the latest versions of pytest, pytest-timeout and pexpect, install
from meta-virtualization/tests/requirements.txt (pending).
* Refactor run-vcontainer-tests script to use optargs instead of a mix of positional args
and env vars.
The following changes since commit 07887d0ace65c873aa929ef31cae52c4c1a8e5c5:
script/utils: Remove prefix from sha256 parameter if present (2026-06-05 23:41:35 +0100)
are available in the Git repository at:
https://git.yoctoproject.org/yocto-autobuilder-helper contrib/timo/containers-upstream-v3
https://git.yoctoproject.org/yocto-autobuilder-helper/log/?h=contrib/timo/containers-upstream-v3
for you to fetch changes up to c21fe441965ef9fd801338f017109592dba8b9e0:
scripts/run-push-containers: remove all images before push (2026-06-05 18:20:07 -0700)
----------------------------------------------------------------
Tim Orling (6):
scripts: add vcontainer-tarball setup, integration, and publishing
config.json: add vcontainer-tarball build target
scripts: add run-vcontainer-tests for meta-virtualization
scripts: add container registry push, auth, tagging, runtime selection
config.json: add 'containers-library' build job
scripts/run-push-containers: remove all images before push
config.json | 118 +++++++++++++++++++
scripts/publish-artefacts | 5 +
scripts/run-config | 23 ++++
scripts/run-push-containers | 167 +++++++++++++++++++++++++++
scripts/run-vcontainer-tests | 212 +++++++++++++++++++++++++++++++++++
scripts/utils.py | 20 +++-
6 files changed, 541 insertions(+), 4 deletions(-)
create mode 100755 scripts/run-push-containers
create mode 100755 scripts/run-vcontainer-tests
--
2.43.0
^ permalink raw reply [flat|nested] 19+ messages in thread
* [yocto-autobuilder2][PATCH v3 1/6] scripts: add vcontainer-tarball setup, integration, and publishing
2026-06-06 2:51 [yocto-autobuilder2][PATCH v3 0/6] Implement 'containers' jobs tim.orling
@ 2026-06-06 2:51 ` tim.orling
2026-06-06 2:51 ` [yocto-autobuilder2][PATCH v3 2/6] config.json: add vcontainer-tarball build target tim.orling
` (5 subsequent siblings)
6 siblings, 0 replies; 19+ messages in thread
From: tim.orling @ 2026-06-06 2:51 UTC (permalink / raw)
To: yocto-patches
From: Tim Orling <tim.orling@konsulko.com>
Introduce the vcontainer-tarball SDK plumbing used by container build
jobs. The vcontainer-tarball is a meta-virtualization-derived SDK
(modelled after buildtools-tarball) that ships the container build
toolchain so worker jobs do not need to rebuild it for every step.
* scripts/utils.py: add setup_vcontainer_tarball(), and add an
env_glob keyword argument to setup_tools_tarball() and
enable_tools_tarball() so the vcontainer-tarball can source its
specific environment-setup-ci file rather than the universal
glob.
* scripts/run-config: source the vcontainer-tarball environment
for build-targets when "vcontainer" parameter is passed in,
similar to "extratools" behavior.
* scripts/publish-artefacts: publish the vcontainer-tarball
artefact so downstream test jobs can fetch a stable SDK.
AI-Generated: Claude Cowork Opus 4.8
Signed-off-by: Tim Orling <tim.orling@konsulko.com>
---
scripts/publish-artefacts | 5 +++++
scripts/run-config | 11 +++++++++++
scripts/utils.py | 20 ++++++++++++++++----
3 files changed, 32 insertions(+), 4 deletions(-)
diff --git a/scripts/publish-artefacts b/scripts/publish-artefacts
index e56e131..0e820e9 100755
--- a/scripts/publish-artefacts
+++ b/scripts/publish-artefacts
@@ -146,5 +146,10 @@ case "$target" in
sha256sums $TMPDIR/deploy/images/qemux86-64
cp -R --no-dereference --preserve=links $TMPDIR/deploy/images/qemux86-64/*qemux86* $DEST/patchtest
;;
+ "vcontainer-tarball")
+ mkdir -p $DEST/vcontainer-tarball
+ sha256sums $TMPDIR/deploy/sdk
+ cp -R --no-dereference --preserve=links $TMPDIR/deploy/sdk/*vcontainer* $DEST/vcontainer-tarball
+ ;;
esac
diff --git a/scripts/run-config b/scripts/run-config
index e896234..90a5996 100755
--- a/scripts/run-config
+++ b/scripts/run-config
@@ -153,6 +153,17 @@ else:
if args.phase == "init" and args.stepname == "buildtools":
sys.exit(0)
+vcontainer = utils.getconfigvar("vcontainer", ourconfig, args.target)
+if jcfg:
+ if vcontainer:
+ addentry("vcontainer", "Setup vcontainer tarball", "init")
+elif vcontainer:
+ # vcontainer is opt-in per target via the "vcontainer" config variable,
+ # so this is a no-op for targets which don't set it
+ utils.setup_vcontainer_tarball(ourconfig, args.target, args.builddir + "/../vcontainer-tarball")
+ if args.phase == "init" and args.stepname == "vcontainer":
+ sys.exit(0)
+
extratools = utils.getconfigvar("extratools", ourconfig, args.target)
if jcfg:
if extratools:
diff --git a/scripts/utils.py b/scripts/utils.py
index a4dd12e..bddc715 100644
--- a/scripts/utils.py
+++ b/scripts/utils.py
@@ -456,8 +456,8 @@ def sha256_file(filename):
pass
return method.hexdigest()
-def enable_tools_tarball(btdir, name):
- btenv = glob.glob(btdir + "/environment-setup*")
+def enable_tools_tarball(btdir, name, env_glob="/environment-setup*"):
+ btenv = glob.glob(btdir + env_glob)
print("Using %s %s" % (name, btenv))
# We either parse or wrap all our execution calls, rock and a hard place :(
with open(btenv[0], "r") as f:
@@ -474,6 +474,18 @@ def enable_tools_tarball(btdir, name):
if line in os.environ:
del os.environ[line]
+# Unlike buildtools (a host/worker property, keyed by worker name globs),
+# the vcontainer-tarball is only needed by specific jobs (e.g.
+# containers-library), so it is keyed off the target/builder via a
+# per-target "vcontainer" config variable, following the extratools pattern.
+def setup_vcontainer_tarball(ourconfig, target, vcdir, checkonly=False):
+ vctarball = getconfigvar("vcontainer", ourconfig, target) or None
+
+ if checkonly:
+ return vctarball
+
+ setup_tools_tarball(ourconfig, vcdir, vctarball, name="vcontainer-tarball", env_glob="/environment-setup-ci")
+
def setup_buildtools_tarball(ourconfig, workername, btdir, checkonly=False):
bttarball = None
if "buildtools" in ourconfig and workername:
@@ -488,7 +500,7 @@ def setup_buildtools_tarball(ourconfig, workername, btdir, checkonly=False):
setup_tools_tarball(ourconfig, btdir, bttarball)
-def setup_tools_tarball(ourconfig, btdir, bttarball, name="buildtools"):
+def setup_tools_tarball(ourconfig, btdir, bttarball, name="buildtools", env_glob="/environment-setup*"):
btenv = None
if bttarball:
@@ -557,7 +569,7 @@ def setup_tools_tarball(ourconfig, btdir, bttarball, name="buildtools"):
if not os.path.exists(btdir):
print("Extracting %s %s" % (name, bttarball))
subprocess.check_call(["bash", btdlpath, "-d", btdir, "-y"])
- enable_tools_tarball(btdir, name)
+ enable_tools_tarball(btdir, name, env_glob)
def get_string_from_version(version, milestone=None, rc=None):
""" Point releases finishing by 0 (e.g 4.0.0, 4.1.0) do no exists,
--
2.43.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [yocto-autobuilder2][PATCH v3 2/6] config.json: add vcontainer-tarball build target
2026-06-06 2:51 [yocto-autobuilder2][PATCH v3 0/6] Implement 'containers' jobs tim.orling
2026-06-06 2:51 ` [yocto-autobuilder2][PATCH v3 1/6] scripts: add vcontainer-tarball setup, integration, and publishing tim.orling
@ 2026-06-06 2:51 ` tim.orling
2026-06-06 2:51 ` [yocto-autobuilder2][PATCH v3 3/6] scripts: add run-vcontainer-tests for meta-virtualization tim.orling
` (4 subsequent siblings)
6 siblings, 0 replies; 19+ messages in thread
From: tim.orling @ 2026-06-06 2:51 UTC (permalink / raw)
To: yocto-patches
From: Tim Orling <tim.orling@konsulko.com>
Add the vcontainer-tarball build target which produces the
meta-virtualization SDK used by downstream container build
jobs. Modelled after the buildtools-tarball target.
The target uses EXTRACMDS to reset BBMULTICONFIG for the worker
shell, places the extravars in the per-step config, and includes
a publish-artefacts step so the resulting SDK tarball is staged
for reuse by container-tests and other consumers.
Since meta-virtualization is the first layer removed by
'remove-layers', our BBMULTICONFIGs become invalid immediately
after layer removal and throw an error.
In scripts/run-config, EXTRACMDS runs after BBTARGETS, but
before 'remove-layers'. Use 'sed' to reset BBMULTICONFIG to ''.
The error in the 'remove-layers' step can be summarized as:
bb.parse.ParseError: ParseError at /home/pokybuild/yocto-worker/vcontainer-tarball/build/layers/openembedded-core/meta/conf/bitbake.conf:824: Could not include required file conf/multiconfig/vruntime-aarch64.conf
Signed-off-by: Tim Orling <tim.orling@konsulko.com>
---
config.json | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/config.json b/config.json
index 4904439..2971fe9 100644
--- a/config.json
+++ b/config.json
@@ -1827,6 +1827,32 @@
},
"toaster" : {
"EXTRACMDS" : ["${SCRIPTSDIR}/run-toaster-tests ${HELPERBUILDDIR} ${HELPERBUILDDIR}/../layers/bitbake"]
+ },
+ "vcontainer-tarball": {
+ "NEEDREPOS" : ["bitbake", "meta-openembedded", "meta-virtualization"],
+ "ADDLAYER" : [
+ "${BUILDDIR}/../meta-openembedded/meta-oe",
+ "${BUILDDIR}/../meta-openembedded/meta-python",
+ "${BUILDDIR}/../meta-openembedded/meta-networking",
+ "${BUILDDIR}/../meta-openembedded/meta-filesystems",
+ "${BUILDDIR}/../meta-virtualization"
+ ],
+ "step1" : {
+ "shortname" : "Build vcontainer-tarballs",
+ "BBTARGETS" : "vcontainer-tarball",
+ "extravars" : [
+ "DISTRO_FEATURES:append = ' virtualization vcontainer'",
+ "BBMULTICONFIG = 'vruntime-aarch64 vruntime-x86-64'",
+ "INIT_MANAGER = 'systemd'"
+ ],
+ "EXTRACMDS" : ["sed -i '/vruntime-aarch64 vruntime-x86-64/d' ${HELPERBUILDDIR}/conf/auto.conf"]
+ },
+ "step2" : {
+ "shortname" : "Publish vcontainer SDK for test reuse",
+ "EXTRACMDS" : [
+ "install -d ${BASE_SHAREDDIR}/pub/vcontainer-tarball-latest && install -m 0755 ${BUILDDIR}/tmp/deploy/sdk/vcontainer-standalone.sh ${BASE_SHAREDDIR}/pub/vcontainer-tarball-latest/vcontainer-standalone.sh.new && mv -f ${BASE_SHAREDDIR}/pub/vcontainer-tarball-latest/vcontainer-standalone.sh.new ${BASE_SHAREDDIR}/pub/vcontainer-tarball-latest/vcontainer-standalone.sh"
+ ]
+ }
}
},
"repo-defaults" : {
--
2.43.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [yocto-autobuilder2][PATCH v3 3/6] scripts: add run-vcontainer-tests for meta-virtualization
2026-06-06 2:51 [yocto-autobuilder2][PATCH v3 0/6] Implement 'containers' jobs tim.orling
2026-06-06 2:51 ` [yocto-autobuilder2][PATCH v3 1/6] scripts: add vcontainer-tarball setup, integration, and publishing tim.orling
2026-06-06 2:51 ` [yocto-autobuilder2][PATCH v3 2/6] config.json: add vcontainer-tarball build target tim.orling
@ 2026-06-06 2:51 ` tim.orling
2026-06-06 2:51 ` [yocto-autobuilder2][PATCH v3 4/6] scripts: add container registry push, auth, tagging, runtime selection tim.orling
` (3 subsequent siblings)
6 siblings, 0 replies; 19+ messages in thread
From: tim.orling @ 2026-06-06 2:51 UTC (permalink / raw)
To: yocto-patches
From: Tim Orling <tim.orling@konsulko.com>
Add scripts/run-vcontainer-tests, the test runner used by the
vcontainers-test job. It sources the vcontainer-tarball SDK,
discovers the meta-virtualization pytest suite, and runs a
configurable set of suites (vdkr, vpdmn, memres) against the
checked-out layers. Suites can be selected per-step so the
top-level 'vcontainer-tests' job runs the container engine
agnostic tests:
- tests/test_container_cross_install.py
- tests/test_container_registry_script.py
- tests/test_vcontainer_auth_config.py
- tests/test_multiarch_oci.py
- tests/test_multilayer_oci.py
The 'vdkr-tests' and 'vpdmn-tests' jobs run only their respective
suites (including memres for each container engine):
- tests/test_vdkr.py
- tests/test_vdkr_registry.py
and
- tests/test_vpdmn.py
The purpose of this script is to test the just built vcontainer-tarball,
so that it can be considered "known-good" and be published for use by
container building jobs that need the vcontainer-tarball installed.
AI-Generated: Claude Cowork Opus 4.7
Signed-off-by: Tim Orling <tim.orling@konsulko.com>
---
config.json | 31 +++++
scripts/run-vcontainer-tests | 212 +++++++++++++++++++++++++++++++++++
2 files changed, 243 insertions(+)
create mode 100755 scripts/run-vcontainer-tests
diff --git a/config.json b/config.json
index 2971fe9..3fccd83 100644
--- a/config.json
+++ b/config.json
@@ -1853,6 +1853,37 @@
"install -d ${BASE_SHAREDDIR}/pub/vcontainer-tarball-latest && install -m 0755 ${BUILDDIR}/tmp/deploy/sdk/vcontainer-standalone.sh ${BASE_SHAREDDIR}/pub/vcontainer-tarball-latest/vcontainer-standalone.sh.new && mv -f ${BASE_SHAREDDIR}/pub/vcontainer-tarball-latest/vcontainer-standalone.sh.new ${BASE_SHAREDDIR}/pub/vcontainer-tarball-latest/vcontainer-standalone.sh"
]
}
+ },
+ "vcontainer-tests": {
+ "NEEDREPOS" : ["bitbake", "meta-openembedded", "meta-virtualization"],
+ "ADDLAYER" : [
+ "${BUILDDIR}/../meta-openembedded/meta-oe",
+ "${BUILDDIR}/../meta-openembedded/meta-python",
+ "${BUILDDIR}/../meta-openembedded/meta-networking",
+ "${BUILDDIR}/../meta-openembedded/meta-filesystems",
+ "${BUILDDIR}/../meta-virtualization"
+ ],
+ "step1" : {
+ "shortname" : "Run vcontainer pytest suite",
+ "NOBUILDTOOLS" : 1,
+ "EXTRACMDS" : [
+ "${SCRIPTSDIR}/run-vcontainer-tests -s vcontainer -b ${BUILDDIR} -m ${BUILDDIR}/../meta-virtualization -S ${BASE_SHAREDDIR}/pub/vcontainer-tarball-latest/vcontainer-standalone.sh -r ${HELPERRESULTSDIR}"
+ ]
+ },
+ "step2" : {
+ "shortname" : "Run vdkr pytest suite",
+ "NOBUILDTOOLS" : 1,
+ "EXTRACMDS" : [
+ "${SCRIPTSDIR}/run-vcontainer-tests -s vdkr -b ${BUILDDIR} -m ${BUILDDIR}/../meta-virtualization -S ${BASE_SHAREDDIR}/pub/vcontainer-tarball-latest/vcontainer-standalone.sh -r ${HELPERRESULTSDIR}"
+ ]
+ },
+ "step3" : {
+ "shortname" : "Run vpdmn pytest suite",
+ "NOBUILDTOOLS" : 1,
+ "EXTRACMDS" : [
+ "${SCRIPTSDIR}/run-vcontainer-tests -s vpdmn -b ${BUILDDIR} -m ${BUILDDIR}/../meta-virtualization -S ${BASE_SHAREDDIR}/pub/vcontainer-tarball-latest/vcontainer-standalone.sh -r ${HELPERRESULTSDIR}"
+ ]
+ }
}
},
"repo-defaults" : {
diff --git a/scripts/run-vcontainer-tests b/scripts/run-vcontainer-tests
new file mode 100755
index 0000000..a7dbab3
--- /dev/null
+++ b/scripts/run-vcontainer-tests
@@ -0,0 +1,212 @@
+#!/bin/bash
+#
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Run meta-virtualization pytest test suites against the vcontainer
+# standalone SDK (vdkr/vpdmn) that was built in a previous bitbake
+# step.
+#
+# Usage:
+# run-vcontainer-tests -s <suite> -b <builddir> -m <metavirtdir> [options]
+#
+# Required:
+# -s <suite> suite name: one of "vcontainer", "vdkr", "vpdmn"
+# -b <builddir> bitbake build directory (${BUILDDIR})
+# -m <metavirtdir> path to the meta-virtualization layer
+#
+# Optional:
+# -S <sdk> path to the vcontainer standalone SDK installer. The
+# autobuilder -tests jobs share the SDK produced by the
+# separate vcontainer-tarball builder. When unset, falls
+# back to looking in <builddir>/tmp/deploy/sdk.
+# -e <extractdir> where to extract the standalone SDK tarball
+# (default: <builddir>/vcontainer-test-extracted)
+# -i <oci_image> path to an OCI image directory (enables vdkr/vpdmn
+# import tests)
+# -a <arch> target architecture for vdkr/vpdmn tests (default: x86_64)
+# -r <resultsdir> directory to copy pytest artefacts (junit xml / log) to
+# -k <markers> pytest marker filter (default excludes long running /
+# infrastructure dependent tests)
+# -h show this help and exit
+#
+# The script is intentionally conservative: any pytest tests that cannot run
+# in the CI environment (those marked "slow", "network", "boot") are skipped
+# so that the autobuilder step completes without needing network access. Those
+# can be re-enabled via -k before invocation.
+#
+# It is assumed that /dev/kvm is writable by the CI user running the tests,
+# since the performance is significantly faster with 'memres'.
+#
+
+set -e
+set -u
+set -o pipefail
+set -x
+
+usage() {
+ cat >&2 <<'EOF'
+Usage: run-vcontainer-tests -s <suite> -b <builddir> -m <metavirtdir> [options]
+
+Required:
+ -s <suite> vcontainer | vdkr | vpdmn
+ -b <builddir> bitbake build directory
+ -m <metavirtdir> path to the meta-virtualization layer
+
+Optional:
+ -S <sdk> path to the vcontainer standalone SDK installer
+ -e <extractdir> where to extract the standalone SDK tarball
+ -i <oci_image> path to an OCI image directory
+ -a <arch> target architecture (default: x86_64)
+ -r <resultsdir> directory to copy pytest artefacts to
+ -k <markers> pytest marker filter
+ -h show this help and exit
+EOF
+}
+
+suite=""
+builddir=""
+metavirtdir=""
+sdk_tarball=""
+extract_dir=""
+oci_image=""
+arch=""
+results_dir=""
+marker_filter="not slow and not network and not boot and not incus and not k3s"
+
+while getopts ":s:b:m:S:e:i:a:r:k:h" opt; do
+ case "$opt" in
+ s) suite="$OPTARG" ;;
+ b) builddir="$OPTARG" ;;
+ m) metavirtdir="$OPTARG" ;;
+ S) sdk_tarball="$OPTARG" ;;
+ e) extract_dir="$OPTARG" ;;
+ i) oci_image="$OPTARG" ;;
+ a) arch="$OPTARG" ;;
+ r) results_dir="$OPTARG" ;;
+ k) marker_filter="$OPTARG" ;;
+ h) usage; exit 0 ;;
+ :) echo "ERROR: option -$OPTARG requires an argument" >&2; usage; exit 2 ;;
+ \?) echo "ERROR: unknown option -$OPTARG" >&2; usage; exit 2 ;;
+ esac
+done
+
+if [ -z "$suite" ] || [ -z "$builddir" ] || [ -z "$metavirtdir" ]; then
+ echo "ERROR: -s, -b and -m are required" >&2
+ usage
+ exit 2
+fi
+
+builddir=$(realpath "$builddir")
+metavirtdir=$(realpath "$metavirtdir")
+
+if [ ! -d "$metavirtdir/tests" ]; then
+ echo "ERROR: meta-virtualization tests directory not found at $metavirtdir/tests" >&2
+ exit 1
+fi
+
+# Locate the vcontainer standalone SDK tarball. Prefer an explicitly-provided
+# SDK (-S), and fall back to looking in the local build's
+# deploy/sdk directory when running stand-alone.
+if [ -n "$sdk_tarball" ]; then
+ if [ ! -f "$sdk_tarball" ]; then
+ echo "ERROR: SDK installer '$sdk_tarball' is set but not a file" >&2
+ exit 1
+ fi
+else
+ sdk_tarball="$builddir/tmp/deploy/sdk/vcontainer-standalone.sh"
+ if [ ! -f "$sdk_tarball" ]; then
+ # Try to find any matching tarball in case naming changed (e.g. versioned)
+ alt=$(ls -1 "$builddir"/tmp/deploy/sdk/vcontainer-*.sh 2>/dev/null | head -n1 || true)
+ if [ -n "$alt" ]; then
+ sdk_tarball="$alt"
+ else
+ echo "ERROR: vcontainer standalone SDK not found." >&2
+ echo " Pass -S with an existing SDK installer, or" >&2
+ echo " build vcontainer-tarball so $builddir/tmp/deploy/sdk/vcontainer-standalone.sh exists." >&2
+ exit 1
+ fi
+ fi
+fi
+
+extract_dir="${extract_dir:-$builddir/vcontainer-test-extracted}"
+rm -rf "$extract_dir"
+mkdir -p "$(dirname "$extract_dir")"
+
+# Self-extracting installer (silent, -y agrees to license, -d picks dir)
+"$sdk_tarball" -d "$extract_dir" -y
+
+# Prepare a Python venv so we don't pollute the worker's system packages.
+python3 -m venv "$builddir/meta-virt-test-venv"
+# shellcheck disable=SC1091
+source "$builddir/meta-virt-test-venv/bin/activate"
+# Avoid warnings by upgrading pip; install pytest/pexpect into the venv via pip.
+export PIP_DISABLE_PIP_VERSION_CHECK=1
+python3 -m pip install --quiet -r "$metavirtdir/tests/requirements.txt"
+
+# Per-suite test file selection. Uses -k/-m for fine-grained filtering and
+# keeps the CLI small for logging clarity.
+case "$suite" in
+ vdkr)
+ test_files=(
+ "tests/test_vdkr.py"
+ "tests/test_vdkr_registry.py"
+ )
+ ;;
+ vpdmn)
+ test_files=(
+ "tests/test_vpdmn.py"
+ )
+ ;;
+ vcontainer)
+ # Broad vcontainer/bbclass/tooling coverage that doesn't require the
+ # vdkr/vpdmn CLI harness to be running.
+ test_files=(
+ "tests/test_container_cross_install.py"
+ "tests/test_container_registry_script.py"
+ "tests/test_vcontainer_auth_config.py"
+ "tests/test_multiarch_oci.py"
+ "tests/test_multilayer_oci.py"
+ )
+ ;;
+ *)
+ echo "ERROR: unknown suite '$suite' (expected vcontainer|vdkr|vpdmn)" >&2
+ exit 2
+ ;;
+esac
+
+pytest_args=(
+ -v
+ --tb=short
+ -m "$marker_filter"
+ --vdkr-dir "$extract_dir"
+ --junitxml="$builddir/pytest-$suite-results.xml"
+)
+
+# Allow tests that consume an OCI image (import/save/load) to find one.
+if [ -n "$oci_image" ] && [ -d "$oci_image" ]; then
+ pytest_args+=(--oci-image "$oci_image")
+fi
+
+# Pass architecture through when set (default is x86_64).
+if [ -n "$arch" ]; then
+ pytest_args+=(--arch "$arch")
+fi
+
+cd "$metavirtdir"
+# Don't let a single failing test kill the whole step - collect the junit
+# report, then surface the exit code via the junit file + exit status.
+set +e
+python3 -m pytest "${pytest_args[@]}" "${test_files[@]}"
+rc=$?
+set -e
+
+# Copy artefacts to the results dir if one was provided.
+if [ -n "$results_dir" ]; then
+ mkdir -p "$results_dir"
+ cp -f "$builddir/pytest-$suite-results.xml" "$results_dir/" 2>/dev/null || true
+ if [ -f /tmp/pytest-vcontainer.log ]; then
+ cp -f /tmp/pytest-vcontainer.log "$results_dir/pytest-$suite.log" || true
+ fi
+fi
+
+exit $rc
--
2.43.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [yocto-autobuilder2][PATCH v3 4/6] scripts: add container registry push, auth, tagging, runtime selection
2026-06-06 2:51 [yocto-autobuilder2][PATCH v3 0/6] Implement 'containers' jobs tim.orling
` (2 preceding siblings ...)
2026-06-06 2:51 ` [yocto-autobuilder2][PATCH v3 3/6] scripts: add run-vcontainer-tests for meta-virtualization tim.orling
@ 2026-06-06 2:51 ` tim.orling
2026-06-06 2:51 ` [yocto-autobuilder2][PATCH v3 5/6] config.json: add 'containers-library' build job tim.orling
` (2 subsequent siblings)
6 siblings, 0 replies; 19+ messages in thread
From: tim.orling @ 2026-06-06 2:51 UTC (permalink / raw)
To: yocto-patches
From: Tim Orling <tim.orling@konsulko.com>
Add the push-containers infrastructure that drives the
post-build steps for the 'containers-library' job. After
each build step the runtime container store is harvested
and pushed to one or more registries with derived per-step
tags.
* config.json: add CONTAINER_REGISTRIES, CONTAINER_AUTH_CONFIG,
CONTAINER_RUNTIME, CONTAINER_TAG_CMDS, CONTAINER_VERSION_RECIPE
and CONTAINER_IMAGE_MAP configuration knobs.
* scripts/run-config: drive push-containers as a post-step
action (by running scripts/run-push-containers).
* scripts/run-push-containers: Tags are generated from recipe and
distro metadata (yocto- tag uses major.minor on snapshots and full
PV on releases) with CONTAINER_VERSION_RECIPE allowing a step to
source PV from a different recipe than the image itself.
* Registry auth is staged via .../config.json or podman
.../auth.json using CONTAINER_AUTH_CONFIG, replacing an
interactive login that could hang. CONTAINER_RUNTIME picks
between vdkr (Docker-compatible) and vpdmn (Podman) runtimes.
* Robustness: skip gracefully when no registries are configured,
fix the OCI directory path, handle memres already running,
and avoid hanging when memres has not yet come up.
AI-Generated: Claude Cowork Opus 4.8
Signed-off-by: Tim Orling <tim.orling@konsulko.com>
---
config.json | 5 ++
scripts/run-config | 12 +++
scripts/run-push-containers | 165 ++++++++++++++++++++++++++++++++++++
3 files changed, 182 insertions(+)
create mode 100755 scripts/run-push-containers
diff --git a/config.json b/config.json
index 3fccd83..e1ea01a 100644
--- a/config.json
+++ b/config.json
@@ -43,6 +43,11 @@
"BUILDINFOVARS" : ["INHERIT += 'image-buildinfo'", "IMAGE_BUILDINFO_VARS:append = ' IMAGE_BASENAME IMAGE_NAME'"],
"WRITECONFIG" : true,
"SENDERRORS" : true,
+ "CONTAINER_RUNTIME" : "vpdmn",
+ "CONTAINER_REGISTRIES" : [],
+ "CONTAINER_TAGS" : ["latest"],
+ "CONTAINER_TAG_CMDS" : [],
+ "CONTAINER_IMAGE_MAP" : {},
"extravars" : [
"SANITY_TESTED_DISTROS = ''",
"BB_HASHSERVE = '${AUTOBUILDER_HASHSERV}'",
diff --git a/scripts/run-config b/scripts/run-config
index 90a5996..31db212 100755
--- a/scripts/run-config
+++ b/scripts/run-config
@@ -195,6 +195,8 @@ utils.mkdir(errordir)
errorlogs = set()
+push_containers = properties.get("push_containers", False)
+
def log_file_contents(filename, builddir, stepnum, stepname):
logfile = logname(builddir, stepnum, stepname)
with open(logfile, "a") as outf, open(filename, "r") as f:
@@ -313,6 +315,16 @@ def handle_stepnum(stepnum):
hp.printheader("Step %s/%s: Running bitbake %s" % (stepnum, maxsteps, sanitytargets))
bitbakecmd(args.builddir, "bitbake %s -k" % (sanitytargets), report, stepnum, args.stepname)
+ # Push container images to registries when push_containers is enabled.
+ # The push logic itself lives in scripts/run-push-containers.
+ container_images = utils.getconfigdict("CONTAINER_IMAGE_MAP", ourconfig, args.target, stepnum)
+ if container_images and push_containers:
+ if jcfg:
+ addstepentry("push-containers", "Push containers", shortdesc, desc, str(container_images), str(stepnum))
+ elif args.stepname == "push-containers":
+ hp.printheader("Step %s/%s: Pushing container images %s" % (stepnum, maxsteps, list(container_images.keys())))
+ bitbakecmd(args.builddir, "%s/run-push-containers %s %s" % (scriptsdir, args.target, stepnum), report, stepnum, args.stepname)
+
# Run any extra commands specified
cmds = utils.getconfiglist("EXTRACMDS", ourconfig, args.target, stepnum)
if jcfg:
diff --git a/scripts/run-push-containers b/scripts/run-push-containers
new file mode 100755
index 0000000..cdef497
--- /dev/null
+++ b/scripts/run-push-containers
@@ -0,0 +1,165 @@
+#!/usr/bin/env python3
+#
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Push container images built by a target step to the configured registries.
+#
+# Invoked by run-config for 'push-containers' steps with the OE build
+# environment already sourced (bitbake and the vcontainer runtime wrappers
+# must be on PATH). All configuration comes from the target/step config:
+#
+# CONTAINER_IMAGE_MAP - dict of image recipe -> registry image name
+# CONTAINER_RUNTIME - vpdmn (default) or vdkr
+# CONTAINER_REGISTRIES - list of registries to push to
+# CONTAINER_TAGS - list of static tags (e.g. latest)
+# CONTAINER_TAG_CMDS - extra shell to populate _EXTRA_TAGS
+# CONTAINER_VERSION_RECIPE - recipe whose PV provides the version tag
+# CONTAINER_AUTH_CONFIG - registry auth file staged into the guest
+#
+
+import subprocess
+import sys
+
+import utils
+
+parser = utils.ArgParser(description='Pushes container images for a target step to the configured registries.')
+
+parser.add_argument('target',
+ help="The target build name")
+parser.add_argument('stepnum',
+ type=int,
+ help="The step number within the target")
+
+args = parser.parse_args()
+
+ourconfig = utils.loadconfig()
+
+container_images = utils.getconfigdict("CONTAINER_IMAGE_MAP", ourconfig, args.target, args.stepnum)
+if not container_images:
+ print("No CONTAINER_IMAGE_MAP for %s step %s, nothing to push" % (args.target, args.stepnum))
+ sys.exit(0)
+
+registries = utils.getconfiglist("CONTAINER_REGISTRIES", ourconfig, args.target, args.stepnum)
+if not registries:
+ utils.printheader("push-containers skipped — CONTAINER_REGISTRIES is empty, no containers pushed")
+ sys.exit(0)
+
+runtime = utils.getconfigvar("CONTAINER_RUNTIME", ourconfig, args.target, args.stepnum) or "vpdmn"
+static_tags = utils.getconfiglist("CONTAINER_TAGS", ourconfig, args.target, args.stepnum)
+auth_config = utils.getconfigvar("CONTAINER_AUTH_CONFIG", ourconfig, args.target, args.stepnum)
+if not auth_config:
+ if runtime == "vpdmn":
+ auth_config = "${HOME}/.config/containers/auth.json"
+ else:
+ auth_config = "${HOME}/.docker/config.json"
+
+utils.printheader("Pushing container images %s" % list(container_images.keys()))
+
+script = [
+ "set -e",
+ "test -w /dev/kvm || { echo 'ERROR: /dev/kvm is not writable, cannot push containers'; exit 1; }",
+ # Always bring up a fresh memres VM in the foreground.
+ #
+ # 'memres status' only checks that the QEMU PID in daemon.pid
+ # is alive (see daemon_is_running()/daemon_status() in
+ # meta-virtualization's vrunner.sh); it returns 0 as soon as
+ # QEMU forks, so a hung/partially-booted VM from a previous
+ # run — or a VM in mid-boot — is reported as healthy. The
+ # subsequent 'login'/'vimport'/'push' commands then hang on
+ # the unresponsive daemon socket.
+ #
+ # 'memres restart' is synchronous: it does stop+start and
+ # runs a PING/PONG readiness probe against the daemon socket
+ # (120s timeout), exiting non-zero if the VM never answers.
+ # Running it in the foreground gives us a trustworthy ready
+ # signal via its exit code, so we can drop the status-poll
+ # loop entirely.
+ #
+ # Install an EXIT trap first so we always tear the daemon
+ # down, even if bitbake -e / vimport / push fails mid-step
+ # under 'set -e'. The trap is armed before the restart so
+ # a restart failure also triggers cleanup.
+ #
+ # Registry auth is staged into the guest at VM boot via
+ # the global '--config' flag — vrunner.sh's setup_auth_share()
+ # copies $AUTH_CONFIG onto a read-only 9p share, and
+ # vdkr-init.sh / vpdmn-init.sh's install_auth_config()
+ # installs it at /root/.docker/config.json (vdkr) or
+ # /run/containers/0/auth.json (vpdmn) inside the guest.
+ # Subsequent 'push' calls use those creds directly, so no
+ # explicit 'login' step is needed. Calling 'login' would
+ # actually hang under the autobuilder (no PTY): when the
+ # memres daemon is running, vcontainer-common.sh dispatches
+ # login via '--daemon-interactive' and blocks reading the
+ # password from stdin (see login case in vcontainer-common.sh).
+ "trap '%s-$(arch) memres stop 2>/dev/null || true' EXIT" % runtime,
+ "%s-$(arch) --config %s memres restart </dev/null" % (runtime, auth_config),
+]
+tag_cmds = utils.getconfiglist("CONTAINER_TAG_CMDS", ourconfig, args.target, args.stepnum)
+version_recipe = utils.getconfigvar("CONTAINER_VERSION_RECIPE", ourconfig, args.target, args.stepnum)
+for recipe, image in container_images.items():
+ # Extract version metadata from the recipe and distro via
+ # bitbake -e. Steps that need additional derived tags (e.g.
+ # major, major.minor) populate _EXTRA_TAGS via
+ # CONTAINER_TAG_CMDS in their step config.
+ #
+ # PV is sanitized with 'sed s/+.*//' to drop Yocto's
+ # '+git<sha>' suffix on AUTOREV/dev recipes — Docker
+ # reference format does not allow '+' in tags, and the
+ # base PV is what consumers expect.
+ #
+ # DISTRO_VERSION needs context-sensitive handling. Poky's
+ # DISTRO_VERSION resolves to '${PV}+snapshot-${METADATA_REVISION}'
+ # off a tag and just '${PV}' on a release tag. The '+' in
+ # the snapshot form is illegal in a Docker tag, but more
+ # importantly the patch level on a snapshot build (e.g.
+ # '6.0.99' between 6.0 and 6.1) is a moving target that
+ # doesn't correspond to any real release — only the
+ # major.minor line is meaningful. So:
+ # - snapshot build (DISTRO_VERSION contains '+') → tag
+ # with major.minor only, e.g. 'yocto-6.0'.
+ # - release-tag build (no '+') → tag with the full
+ # version, e.g. 'yocto-5.0.5' from the yocto-5.0.5 tag.
+ script += [
+ "_BBENV=$(bitbake -e %s 2>/dev/null) || true" % recipe,
+ "_PV=$(echo \"$_BBENV\" | awk -F'\"' '/^PV=/{ print $2; exit }' | sed 's/+.*//')",
+ "_DISTRO_CODENAME=$(echo \"$_BBENV\" | awk -F'\"' '/^DISTRO_CODENAME=/{ print $2; exit }')",
+ "_DISTRO_VERSION_RAW=$(echo \"$_BBENV\" | awk -F'\"' '/^DISTRO_VERSION=/{ print $2; exit }')",
+ "case \"$_DISTRO_VERSION_RAW\" in",
+ " *+*) _DISTRO_VERSION=$(echo \"${_DISTRO_VERSION_RAW%%+*}\" | cut -d. -f1,2) ;;",
+ " *) _DISTRO_VERSION=\"$_DISTRO_VERSION_RAW\" ;;",
+ "esac",
+ "_DEPLOY_DIR_IMAGE=$(echo \"$_BBENV\" | awk -F'\"' '/^DEPLOY_DIR_IMAGE=/{ print $2; exit }')",
+ "_EXTRA_TAGS=\"\"",
+ ]
+ if version_recipe:
+ # When the image recipe's PV is a wrapper-style
+ # placeholder (e.g. app-container-python_1.0.0.bb,
+ # whose 1.0.0 is meaningless to a downstream user),
+ # CONTAINER_VERSION_RECIPE points at the recipe whose
+ # PV is actually meaningful for the resulting tag —
+ # typically the language runtime or app being packaged
+ # (e.g. python3 -> 3.14.x). Override _PV from that
+ # recipe; image-recipe state still drives
+ # DEPLOY_DIR_IMAGE and DISTRO_* since those are
+ # environment-wide.
+ script += [
+ "_VBBENV=$(bitbake -e %s 2>/dev/null) || true" % version_recipe,
+ "_PV=$(echo \"$_VBBENV\" | awk -F'\"' '/^PV=/{ print $2; exit }' | sed 's/+.*//')",
+ ]
+ script += tag_cmds
+ script.append(
+ "_TAGS=\"%s $_PV $_DISTRO_CODENAME yocto-$_DISTRO_VERSION $_EXTRA_TAGS\"" % " ".join(static_tags)
+ )
+ for registry in registries:
+ # No per-registry 'login': credentials were staged into
+ # the guest by '--config' on 'memres restart' above.
+ script += [
+ "for _tag in $_TAGS; do",
+ " %s-$(arch) vimport ${_DEPLOY_DIR_IMAGE}/%s-latest-oci %s/%s:${_tag}" % (runtime, recipe, registry, image),
+ " %s-$(arch) push %s/%s:${_tag}" % (runtime, registry, image),
+ "done",
+ ]
+# Tear-down is handled by the EXIT trap installed above.
+utils.flush()
+sys.exit(subprocess.call(["/bin/bash", "-c", "\n".join(script)]))
--
2.43.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [yocto-autobuilder2][PATCH v3 5/6] config.json: add 'containers-library' build job
2026-06-06 2:51 [yocto-autobuilder2][PATCH v3 0/6] Implement 'containers' jobs tim.orling
` (3 preceding siblings ...)
2026-06-06 2:51 ` [yocto-autobuilder2][PATCH v3 4/6] scripts: add container registry push, auth, tagging, runtime selection tim.orling
@ 2026-06-06 2:51 ` tim.orling
2026-06-06 2:51 ` [yocto-autobuilder2][PATCH v3 6/6] scripts/run-push-containers: remove all images before push tim.orling
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 00/11] Implement 'containers' jobs Tim Orling
6 siblings, 0 replies; 19+ messages in thread
From: tim.orling @ 2026-06-06 2:51 UTC (permalink / raw)
To: yocto-patches
From: Tim Orling <tim.orling@konsulko.com>
Add 'containers-library' build job that builds container images on top
of the vcontainer-tarball SDK:
* original container images from the intial "container-cross-install"
branch of meta-virtualization
- container-base
- app-container-curl
* additional images modelled after docker.io/library/*
- app-container-python
- app-container-mosquitto
- app-container-valkey
- app-container-nginx
* Tag containers with versions based on the recipe to which they are
mapped, e.g. python:3, python:3.14, python:3.14.5. Also tag with
DISTRO_CODENAME and DISTRO_VERSION, e.g. wrynose and yocto-6.0.
Signed-off-by: Tim Orling <tim.orling@konsulko.com>
---
config.json | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 56 insertions(+)
diff --git a/config.json b/config.json
index e1ea01a..18a2b6b 100644
--- a/config.json
+++ b/config.json
@@ -1889,6 +1889,62 @@
"${SCRIPTSDIR}/run-vcontainer-tests -s vpdmn -b ${BUILDDIR} -m ${BUILDDIR}/../meta-virtualization -S ${BASE_SHAREDDIR}/pub/vcontainer-tarball-latest/vcontainer-standalone.sh -r ${HELPERRESULTSDIR}"
]
}
+ },
+ "containers-library": {
+ "NEEDREPOS" : ["bitbake", "meta-openembedded", "meta-virtualization"],
+ "ADDLAYER" : [
+ "${BUILDDIR}/../meta-openembedded/meta-oe",
+ "${BUILDDIR}/../meta-openembedded/meta-python",
+ "${BUILDDIR}/../meta-openembedded/meta-networking",
+ "${BUILDDIR}/../meta-openembedded/meta-filesystems",
+ "${BUILDDIR}/../meta-openembedded/meta-webserver",
+ "${BUILDDIR}/../meta-virtualization"
+ ],
+ "extravars" : [
+ "DISTRO_FEATURES:append = ' virtualization vcontainer'"
+ ],
+ "vcontainer" : "${VCONTAINER_TARBALL_URL}",
+ "CONTAINER_TAG_CMDS" : [
+ "_PV_MAJOR=$(echo $_PV | cut -d. -f1)",
+ "_PV_MAJOR_MINOR=$(echo $_PV | cut -d. -f1,2)",
+ "_EXTRA_TAGS=\"$_PV_MAJOR $_PV_MAJOR_MINOR\""
+ ],
+ "step1" : {
+ "shortname" : "Build 'base' container",
+ "BBTARGETS" : "container-base",
+ "CONTAINER_IMAGE_MAP" : {"container-base": "base"},
+ "CONTAINER_VERSION_RECIPE" : "base-files"
+ },
+ "step2" : {
+ "shortname" : "Build 'curl' container",
+ "BBTARGETS" : "app-container-curl",
+ "CONTAINER_IMAGE_MAP" : {"app-container-curl": "curl"},
+ "CONTAINER_VERSION_RECIPE" : "curl"
+ },
+ "step3" : {
+ "shortname" : "Build 'python' container",
+ "BBTARGETS" : "app-container-python",
+ "CONTAINER_IMAGE_MAP" : {"app-container-python": "python"},
+ "CONTAINER_VERSION_RECIPE" : "python3"
+ },
+ "step4" : {
+ "shortname" : "Build 'mosquitto' container",
+ "BBTARGETS" : "app-container-mosquitto",
+ "CONTAINER_IMAGE_MAP" : {"app-container-mosquitto": "mosquitto"},
+ "CONTAINER_VERSION_RECIPE" : "mosquitto"
+ },
+ "step5" : {
+ "shortname" : "Build 'valkey' container",
+ "BBTARGETS" : "app-container-valkey",
+ "CONTAINER_IMAGE_MAP" : {"app-container-valkey": "valkey"},
+ "CONTAINER_VERSION_RECIPE" : "valkey"
+ },
+ "step6" : {
+ "shortname" : "Build 'nginx' container",
+ "BBTARGETS" : "app-container-nginx",
+ "CONTAINER_IMAGE_MAP" : {"app-container-nginx": "nginx"},
+ "CONTAINER_VERSION_RECIPE" : "nginx"
+ }
}
},
"repo-defaults" : {
--
2.43.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [yocto-autobuilder2][PATCH v3 6/6] scripts/run-push-containers: remove all images before push
2026-06-06 2:51 [yocto-autobuilder2][PATCH v3 0/6] Implement 'containers' jobs tim.orling
` (4 preceding siblings ...)
2026-06-06 2:51 ` [yocto-autobuilder2][PATCH v3 5/6] config.json: add 'containers-library' build job tim.orling
@ 2026-06-06 2:51 ` tim.orling
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 00/11] Implement 'containers' jobs Tim Orling
6 siblings, 0 replies; 19+ messages in thread
From: tim.orling @ 2026-06-06 2:51 UTC (permalink / raw)
To: yocto-patches
From: Tim Orling <tim.orling@konsulko.com>
Workaround for errors like:
Error: reading blob sha256:<hash>: file integrity checksum failed for "<file>"
Error: reading blob sha256:<hash>: EOF
The first time the blob (e.g. bin/bash.bash) is copied, it succeeds. All subsequent
tries fail. Might possibly be an issue with sstate or a recent change to vcontainer-common
to allow for multiarch containers.
Signed-off-by: Tim Orling <tim.orling@konsulko.com>
---
scripts/run-push-containers | 2 ++
1 file changed, 2 insertions(+)
diff --git a/scripts/run-push-containers b/scripts/run-push-containers
index cdef497..4f1ac53 100755
--- a/scripts/run-push-containers
+++ b/scripts/run-push-containers
@@ -94,6 +94,8 @@ script = [
# password from stdin (see login case in vcontainer-common.sh).
"trap '%s-$(arch) memres stop 2>/dev/null || true' EXIT" % runtime,
"%s-$(arch) --config %s memres restart </dev/null" % (runtime, auth_config),
+ # workaround for 'Error: reading blob sha256:<hash>: file integrity checksum failed for "<file>"'
+ "%s-$(arch) image rm --all 2>/dev/null" % (runtime),
]
tag_cmds = utils.getconfiglist("CONTAINER_TAG_CMDS", ourconfig, args.target, args.stepnum)
version_recipe = utils.getconfigvar("CONTAINER_VERSION_RECIPE", ourconfig, args.target, args.stepnum)
--
2.43.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [yocto-autobuilder-helper][PATCH v4 00/11] Implement 'containers' jobs
2026-06-06 2:51 [yocto-autobuilder2][PATCH v3 0/6] Implement 'containers' jobs tim.orling
` (5 preceding siblings ...)
2026-06-06 2:51 ` [yocto-autobuilder2][PATCH v3 6/6] scripts/run-push-containers: remove all images before push tim.orling
@ 2026-07-06 22:05 ` Tim Orling
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 01/11] scripts: add vcontainer-tarball setup, integration, and publishing Tim Orling
` (10 more replies)
6 siblings, 11 replies; 19+ messages in thread
From: Tim Orling @ 2026-07-06 22:05 UTC (permalink / raw)
To: yocto-patches
This series builds on top of the patches already merged from v2 and
previously submitted as v3.
This series contains the 'yocto-autobuilder-helper' patches to enable:
* building, testing and publishing of 'vcontainer-tarball'
- vcontainer-tests job
* building and pushing containers from:
- meta-virtualization
* containers are pushed when the push_containers boolean is set in the
AutoBuilder UI
- regstries are set with CONTAINER_REGISTRIES variable
* authentication uses local user credentials (e.g. ~/.docker/config.json
or .../auth.json for podman)
* containers are tagged with PV_MAJOR, PV_MAJOR.PV_MINOR, DISTRO_CODENAME
and yocto-x.y release (yocto-x.y.z for release builds).
* multiarch containers built for x86-64 and aarch64
* signing of containers
* attaching SPDX 3.0 SBOM attestation (and signing it) for pushed
containers
* 'dev' mode containers which run as 'root' user and in some cases add a
shell. These are tagged as above but with a '-dev' suffix.
Additional features, such as attaching SLSA provenance attestations will
come in a future series.
Concerns about the possible infinite loop around the tarball extraction
"while True, try, expect OSError" code [1] are addressed separately [2],
since the original patch has been merged.
This series relies on the resolution of [3].
The 'vcontainer-tests' job is currently experiencing some failures [4],
which will have to be resolved separately.
[1] https://lore.kernel.org/yocto-patches/02f0c35f16a8a51bbe0e89145353008f67180a84.camel@pbarker.dev/
[2] https://lore.kernel.org/yocto-patches/20260606011918.61582-1-tim.orling@konsulko.com/
[3] https://lore.kernel.org/yocto-meta-virtualization/cover.1783356922.git.tim.orling@konsulko.com/
[4] https://autobuilder.yoctoproject.org/valkyrie/?#/builders/118/builds/4
Changes in v2:
* Simplify by merging vdkr-tests and vpdmn-tests into vcontainer-tests
* Simplify by building containers from meta-virtualization:
- Depends on resolution of: https://lists.yoctoproject.org/g/meta-virtualization/message/9826
* Workaround for recent (since Friday May 29, 2026) errors:
Error: reading blob sha256:<hash>: file integrity checksum failed for
"<file>"
- This is probably related to either sstate changes or recent changes
in vcontainer-common...
Changes in v3:
* vcontainer-tarball is installed in jobs that set "vcontainer" similar
to "extratools", instead of the more global "buildtools" behavior.
* The (rather large amount of) code for pushing containers in run-config
is broken out into a dedicated run-push-containers script.
* Workaround for pushing containers is moved to run-push-containers. The
most recent behavior (on top of meta-virt master-next) showed a
different error pattern:
Error: reading blob sha256:<hash>: EOF
The workaround simply removes all container images from the VM's
container-registry before importing or pushing new container images.
The commit message was reworded accordingly.
* Rather than installing latest versions of pip, setuptools and wheel,
silence the pip warning with PIP_DISABLE_PIP_VERSION_CHECK=1
* Rather than installing the latest versions of pytest, pytest-timeout
and pexpect, install from meta-virtualization/tests/requirements.txt.
* Refactor run-vcontainer-tests script to use optargs instead of a mix of
positional args and env vars.
Changes in v4:
* Adjusted the 'vcontainer' installation of the vcontainer-tarball to
only apply to 'push-containers' step, as we currently only need the
functionality to 'vimport' and push single-arch containers.
* Added app-container-alpine as a single-arch container example
* Switched other container builds to multi-arch, building both x86-64 and
aarch64 flavors
* Added conditional signing of containers in the run-push-containers
script, this is gated by CONTAINER_COSIGN_KEY being set to the path to
a 'cosign' private key.
* Added conditional attachment of SPDX 3.0 SBOM attestation (when
CONTAINER_COSIGN_KEY is defined).
* Added conditional verification of the SPDX 3.0 SBOM attestation, which
is gated by CONTAINER_COSIGN_PUB being set to the path to a 'cosign'
public key.
* Added building of '-dev' mode containers, with PACKAGECONFIG 'dev',
which run as 'root' user instead of the 'nonroot' user and in some
cases conditionally add a shell (depends on the container image recipe).
* Added pushing of '-dev'containers adding a '-dev' suffix to the tags.
Tim Orling (11):
scripts: add vcontainer-tarball setup, integration, and publishing
config.json: add vcontainer-tarball build target
scripts: add run-vcontainer-tests for meta-virtualization
scripts: add container registry push, auth, tagging, runtime selection
config.json: add 'containers-library' build job
scripts/run-push-containers: push multiarch containers with
skopeo-native
containers-library: switch to multiarch; add alpine single-arch
run-push-containers: conditionally sign pushed containers with cosign
scripts/run-push-containers: add SPDX SBOM attestation
config.json: add -dev container builds to containers-library
run-push-containers: optionally push -dev tagged containers
config.json | 168 +++++++++++++
scripts/publish-artefacts | 5 +
scripts/run-config | 19 ++
scripts/run-push-containers | 471 +++++++++++++++++++++++++++++++++++
scripts/run-vcontainer-tests | 212 ++++++++++++++++
scripts/utils.py | 20 +-
6 files changed, 891 insertions(+), 4 deletions(-)
create mode 100755 scripts/run-push-containers
create mode 100755 scripts/run-vcontainer-tests
These changes can be found:
https://git.yoctoproject.org/yocto-autobuilder-helper/log/?h=contrib/timo/containers-upstream-v4
These changes had a successful build of containers-library job:
https://autobuilder.yoctoproject.org/valkyrie/#/builders/117/builds/25
(with one additional commit to set the path to VCONTAINER_TARBALL_URL)
--
2.54.0
^ permalink raw reply [flat|nested] 19+ messages in thread
* [yocto-autobuilder-helper][PATCH v4 01/11] scripts: add vcontainer-tarball setup, integration, and publishing
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 00/11] Implement 'containers' jobs Tim Orling
@ 2026-07-06 22:05 ` Tim Orling
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 02/11] config.json: add vcontainer-tarball build target Tim Orling
` (9 subsequent siblings)
10 siblings, 0 replies; 19+ messages in thread
From: Tim Orling @ 2026-07-06 22:05 UTC (permalink / raw)
To: yocto-patches
Introduce the vcontainer-tarball SDK plumbing used by container build
jobs. The vcontainer-tarball is a meta-virtualization-derived SDK
(modelled after buildtools-tarball) that ships the container build
toolchain so worker jobs do not need to rebuild it for every step.
* scripts/utils.py: add setup_vcontainer_tarball(), and add an
env_glob keyword argument to setup_tools_tarball() and
enable_tools_tarball() so the vcontainer-tarball can source its
specific environment-setup-ci file rather than the universal
glob.
* scripts/run-config: source the vcontainer-tarball environment
for build-targets when "vcontainer" parameter is passed in,
similar to "extratools" behavior.
* scripts/publish-artefacts: publish the vcontainer-tarball
artefact so downstream test jobs can fetch a stable SDK.
AI-Generated: Claude Cowork Opus 4.8
Signed-off-by: Tim Orling <tim.orling@konsulko.com>
---
scripts/publish-artefacts | 5 +++++
scripts/run-config | 11 +++++++++++
scripts/utils.py | 20 ++++++++++++++++----
3 files changed, 32 insertions(+), 4 deletions(-)
diff --git a/scripts/publish-artefacts b/scripts/publish-artefacts
index e56e131..0e820e9 100755
--- a/scripts/publish-artefacts
+++ b/scripts/publish-artefacts
@@ -146,5 +146,10 @@ case "$target" in
sha256sums $TMPDIR/deploy/images/qemux86-64
cp -R --no-dereference --preserve=links $TMPDIR/deploy/images/qemux86-64/*qemux86* $DEST/patchtest
;;
+ "vcontainer-tarball")
+ mkdir -p $DEST/vcontainer-tarball
+ sha256sums $TMPDIR/deploy/sdk
+ cp -R --no-dereference --preserve=links $TMPDIR/deploy/sdk/*vcontainer* $DEST/vcontainer-tarball
+ ;;
esac
diff --git a/scripts/run-config b/scripts/run-config
index e896234..90a5996 100755
--- a/scripts/run-config
+++ b/scripts/run-config
@@ -153,6 +153,17 @@ else:
if args.phase == "init" and args.stepname == "buildtools":
sys.exit(0)
+vcontainer = utils.getconfigvar("vcontainer", ourconfig, args.target)
+if jcfg:
+ if vcontainer:
+ addentry("vcontainer", "Setup vcontainer tarball", "init")
+elif vcontainer:
+ # vcontainer is opt-in per target via the "vcontainer" config variable,
+ # so this is a no-op for targets which don't set it
+ utils.setup_vcontainer_tarball(ourconfig, args.target, args.builddir + "/../vcontainer-tarball")
+ if args.phase == "init" and args.stepname == "vcontainer":
+ sys.exit(0)
+
extratools = utils.getconfigvar("extratools", ourconfig, args.target)
if jcfg:
if extratools:
diff --git a/scripts/utils.py b/scripts/utils.py
index a4dd12e..bddc715 100644
--- a/scripts/utils.py
+++ b/scripts/utils.py
@@ -456,8 +456,8 @@ def sha256_file(filename):
pass
return method.hexdigest()
-def enable_tools_tarball(btdir, name):
- btenv = glob.glob(btdir + "/environment-setup*")
+def enable_tools_tarball(btdir, name, env_glob="/environment-setup*"):
+ btenv = glob.glob(btdir + env_glob)
print("Using %s %s" % (name, btenv))
# We either parse or wrap all our execution calls, rock and a hard place :(
with open(btenv[0], "r") as f:
@@ -474,6 +474,18 @@ def enable_tools_tarball(btdir, name):
if line in os.environ:
del os.environ[line]
+# Unlike buildtools (a host/worker property, keyed by worker name globs),
+# the vcontainer-tarball is only needed by specific jobs (e.g.
+# containers-library), so it is keyed off the target/builder via a
+# per-target "vcontainer" config variable, following the extratools pattern.
+def setup_vcontainer_tarball(ourconfig, target, vcdir, checkonly=False):
+ vctarball = getconfigvar("vcontainer", ourconfig, target) or None
+
+ if checkonly:
+ return vctarball
+
+ setup_tools_tarball(ourconfig, vcdir, vctarball, name="vcontainer-tarball", env_glob="/environment-setup-ci")
+
def setup_buildtools_tarball(ourconfig, workername, btdir, checkonly=False):
bttarball = None
if "buildtools" in ourconfig and workername:
@@ -488,7 +500,7 @@ def setup_buildtools_tarball(ourconfig, workername, btdir, checkonly=False):
setup_tools_tarball(ourconfig, btdir, bttarball)
-def setup_tools_tarball(ourconfig, btdir, bttarball, name="buildtools"):
+def setup_tools_tarball(ourconfig, btdir, bttarball, name="buildtools", env_glob="/environment-setup*"):
btenv = None
if bttarball:
@@ -557,7 +569,7 @@ def setup_tools_tarball(ourconfig, btdir, bttarball, name="buildtools"):
if not os.path.exists(btdir):
print("Extracting %s %s" % (name, bttarball))
subprocess.check_call(["bash", btdlpath, "-d", btdir, "-y"])
- enable_tools_tarball(btdir, name)
+ enable_tools_tarball(btdir, name, env_glob)
def get_string_from_version(version, milestone=None, rc=None):
""" Point releases finishing by 0 (e.g 4.0.0, 4.1.0) do no exists,
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [yocto-autobuilder-helper][PATCH v4 02/11] config.json: add vcontainer-tarball build target
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 00/11] Implement 'containers' jobs Tim Orling
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 01/11] scripts: add vcontainer-tarball setup, integration, and publishing Tim Orling
@ 2026-07-06 22:05 ` Tim Orling
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 03/11] scripts: add run-vcontainer-tests for meta-virtualization Tim Orling
` (8 subsequent siblings)
10 siblings, 0 replies; 19+ messages in thread
From: Tim Orling @ 2026-07-06 22:05 UTC (permalink / raw)
To: yocto-patches
Add the vcontainer-tarball build target which produces the
meta-virtualization SDK used by downstream container build/test
jobs. Modelled after the buildtools-tarball target.
Following meta-virtualization/docs/build-profiles.md add
"require conf/distro/include/meta-virt-host.conf" to extravars,
which appends required DISTRO_FEATURES and sets appropriate
BBMULTICONFIG.
The target places the extravars in the per-step config, and includes
a publish-artefacts step so the resulting SDK tarball is staged
for reuse by container-tests and other consumers.
Since meta-virtualization is the first layer removed by
'remove-layers', our BBMULTICONFIGs become invalid immediately
after layer removal and throw an error.
In scripts/run-config, EXTRACMDS runs after BBTARGETS, but
before 'remove-layers'. Use 'sed' to remove the "require
...meta-virt-host.conf" line.
The error in the 'remove-layers' step can be summarized as:
bb.parse.ParseError: ParseError at /home/pokybuild/yocto-worker/vcontainer-tarball/build/layers/openembedded-core/meta/conf/bitbake.conf:824: Could not include required file conf/multiconfig/vruntime-aarch64.conf
Signed-off-by: Tim Orling <tim.orling@konsulko.com>
---
config.json | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/config.json b/config.json
index f4ead18..972682d 100644
--- a/config.json
+++ b/config.json
@@ -1830,6 +1830,31 @@
},
"toaster" : {
"EXTRACMDS" : ["${SCRIPTSDIR}/run-toaster-tests ${HELPERBUILDDIR} ${HELPERBUILDDIR}/../layers/bitbake"]
+ },
+ "vcontainer-tarball": {
+ "NEEDREPOS" : ["bitbake", "meta-openembedded", "meta-virtualization"],
+ "ADDLAYER" : [
+ "${BUILDDIR}/../meta-openembedded/meta-oe",
+ "${BUILDDIR}/../meta-openembedded/meta-python",
+ "${BUILDDIR}/../meta-openembedded/meta-networking",
+ "${BUILDDIR}/../meta-openembedded/meta-filesystems",
+ "${BUILDDIR}/../meta-virtualization"
+ ],
+ "step1" : {
+ "shortname" : "Build vcontainer-tarballs",
+ "BBTARGETS" : "vcontainer-tarball",
+ "extravars" : [
+ "require conf/distro/include/meta-virt-host.conf",
+ "INIT_MANAGER = 'systemd'"
+ ],
+ "EXTRACMDS" : ["sed -i '/meta-virt-host.conf/d' ${HELPERBUILDDIR}/conf/auto.conf"]
+ },
+ "step2" : {
+ "shortname" : "Publish vcontainer SDK for test reuse",
+ "EXTRACMDS" : [
+ "install -d ${BASE_SHAREDDIR}/pub/vcontainer-tarball-latest && install -m 0755 ${BUILDDIR}/tmp/deploy/sdk/vcontainer-standalone.sh ${BASE_SHAREDDIR}/pub/vcontainer-tarball-latest/vcontainer-standalone.sh.new && mv -f ${BASE_SHAREDDIR}/pub/vcontainer-tarball-latest/vcontainer-standalone.sh.new ${BASE_SHAREDDIR}/pub/vcontainer-tarball-latest/vcontainer-standalone.sh"
+ ]
+ }
}
},
"repo-defaults" : {
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [yocto-autobuilder-helper][PATCH v4 03/11] scripts: add run-vcontainer-tests for meta-virtualization
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 00/11] Implement 'containers' jobs Tim Orling
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 01/11] scripts: add vcontainer-tarball setup, integration, and publishing Tim Orling
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 02/11] config.json: add vcontainer-tarball build target Tim Orling
@ 2026-07-06 22:05 ` Tim Orling
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 04/11] scripts: add container registry push, auth, tagging, runtime selection Tim Orling
` (7 subsequent siblings)
10 siblings, 0 replies; 19+ messages in thread
From: Tim Orling @ 2026-07-06 22:05 UTC (permalink / raw)
To: yocto-patches
Add scripts/run-vcontainer-tests, the test runner used by the
vcontainers-test job. It sources the vcontainer-tarball SDK,
discovers the meta-virtualization pytest suite, and runs a
configurable set of suites (vdkr, vpdmn, memres) against the
checked-out layers. Suites can be selected per-step so the
top-level 'vcontainer-tests' job runs the container engine
agnostic tests:
- tests/test_container_cross_install.py
- tests/test_container_registry_script.py
- tests/test_vcontainer_auth_config.py
- tests/test_multiarch_oci.py
- tests/test_multilayer_oci.py
The 'vdkr-tests' and 'vpdmn-tests' jobs run only their respective
suites (including memres for each container engine):
- tests/test_vdkr.py
- tests/test_vdkr_registry.py
and
- tests/test_vpdmn.py
The purpose of this script is to test the just built vcontainer-tarball,
so that it can be considered "known-good" and be published for use by
container building jobs that need the vcontainer-tarball installed.
AI-Generated: Claude Cowork Opus 4.7
Signed-off-by: Tim Orling <tim.orling@konsulko.com>
---
config.json | 31 +++++
scripts/run-vcontainer-tests | 212 +++++++++++++++++++++++++++++++++++
2 files changed, 243 insertions(+)
create mode 100755 scripts/run-vcontainer-tests
diff --git a/config.json b/config.json
index 972682d..ae13c5b 100644
--- a/config.json
+++ b/config.json
@@ -1855,6 +1855,37 @@
"install -d ${BASE_SHAREDDIR}/pub/vcontainer-tarball-latest && install -m 0755 ${BUILDDIR}/tmp/deploy/sdk/vcontainer-standalone.sh ${BASE_SHAREDDIR}/pub/vcontainer-tarball-latest/vcontainer-standalone.sh.new && mv -f ${BASE_SHAREDDIR}/pub/vcontainer-tarball-latest/vcontainer-standalone.sh.new ${BASE_SHAREDDIR}/pub/vcontainer-tarball-latest/vcontainer-standalone.sh"
]
}
+ },
+ "vcontainer-tests": {
+ "NEEDREPOS" : ["bitbake", "meta-openembedded", "meta-virtualization"],
+ "ADDLAYER" : [
+ "${BUILDDIR}/../meta-openembedded/meta-oe",
+ "${BUILDDIR}/../meta-openembedded/meta-python",
+ "${BUILDDIR}/../meta-openembedded/meta-networking",
+ "${BUILDDIR}/../meta-openembedded/meta-filesystems",
+ "${BUILDDIR}/../meta-virtualization"
+ ],
+ "step1" : {
+ "shortname" : "Run vcontainer pytest suite",
+ "NOBUILDTOOLS" : 1,
+ "EXTRACMDS" : [
+ "${SCRIPTSDIR}/run-vcontainer-tests -s vcontainer -b ${BUILDDIR} -m ${BUILDDIR}/../meta-virtualization -S ${BASE_SHAREDDIR}/pub/vcontainer-tarball-latest/vcontainer-standalone.sh -r ${HELPERRESULTSDIR}"
+ ]
+ },
+ "step2" : {
+ "shortname" : "Run vdkr pytest suite",
+ "NOBUILDTOOLS" : 1,
+ "EXTRACMDS" : [
+ "${SCRIPTSDIR}/run-vcontainer-tests -s vdkr -b ${BUILDDIR} -m ${BUILDDIR}/../meta-virtualization -S ${BASE_SHAREDDIR}/pub/vcontainer-tarball-latest/vcontainer-standalone.sh -r ${HELPERRESULTSDIR}"
+ ]
+ },
+ "step3" : {
+ "shortname" : "Run vpdmn pytest suite",
+ "NOBUILDTOOLS" : 1,
+ "EXTRACMDS" : [
+ "${SCRIPTSDIR}/run-vcontainer-tests -s vpdmn -b ${BUILDDIR} -m ${BUILDDIR}/../meta-virtualization -S ${BASE_SHAREDDIR}/pub/vcontainer-tarball-latest/vcontainer-standalone.sh -r ${HELPERRESULTSDIR}"
+ ]
+ }
}
},
"repo-defaults" : {
diff --git a/scripts/run-vcontainer-tests b/scripts/run-vcontainer-tests
new file mode 100755
index 0000000..a7dbab3
--- /dev/null
+++ b/scripts/run-vcontainer-tests
@@ -0,0 +1,212 @@
+#!/bin/bash
+#
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Run meta-virtualization pytest test suites against the vcontainer
+# standalone SDK (vdkr/vpdmn) that was built in a previous bitbake
+# step.
+#
+# Usage:
+# run-vcontainer-tests -s <suite> -b <builddir> -m <metavirtdir> [options]
+#
+# Required:
+# -s <suite> suite name: one of "vcontainer", "vdkr", "vpdmn"
+# -b <builddir> bitbake build directory (${BUILDDIR})
+# -m <metavirtdir> path to the meta-virtualization layer
+#
+# Optional:
+# -S <sdk> path to the vcontainer standalone SDK installer. The
+# autobuilder -tests jobs share the SDK produced by the
+# separate vcontainer-tarball builder. When unset, falls
+# back to looking in <builddir>/tmp/deploy/sdk.
+# -e <extractdir> where to extract the standalone SDK tarball
+# (default: <builddir>/vcontainer-test-extracted)
+# -i <oci_image> path to an OCI image directory (enables vdkr/vpdmn
+# import tests)
+# -a <arch> target architecture for vdkr/vpdmn tests (default: x86_64)
+# -r <resultsdir> directory to copy pytest artefacts (junit xml / log) to
+# -k <markers> pytest marker filter (default excludes long running /
+# infrastructure dependent tests)
+# -h show this help and exit
+#
+# The script is intentionally conservative: any pytest tests that cannot run
+# in the CI environment (those marked "slow", "network", "boot") are skipped
+# so that the autobuilder step completes without needing network access. Those
+# can be re-enabled via -k before invocation.
+#
+# It is assumed that /dev/kvm is writable by the CI user running the tests,
+# since the performance is significantly faster with 'memres'.
+#
+
+set -e
+set -u
+set -o pipefail
+set -x
+
+usage() {
+ cat >&2 <<'EOF'
+Usage: run-vcontainer-tests -s <suite> -b <builddir> -m <metavirtdir> [options]
+
+Required:
+ -s <suite> vcontainer | vdkr | vpdmn
+ -b <builddir> bitbake build directory
+ -m <metavirtdir> path to the meta-virtualization layer
+
+Optional:
+ -S <sdk> path to the vcontainer standalone SDK installer
+ -e <extractdir> where to extract the standalone SDK tarball
+ -i <oci_image> path to an OCI image directory
+ -a <arch> target architecture (default: x86_64)
+ -r <resultsdir> directory to copy pytest artefacts to
+ -k <markers> pytest marker filter
+ -h show this help and exit
+EOF
+}
+
+suite=""
+builddir=""
+metavirtdir=""
+sdk_tarball=""
+extract_dir=""
+oci_image=""
+arch=""
+results_dir=""
+marker_filter="not slow and not network and not boot and not incus and not k3s"
+
+while getopts ":s:b:m:S:e:i:a:r:k:h" opt; do
+ case "$opt" in
+ s) suite="$OPTARG" ;;
+ b) builddir="$OPTARG" ;;
+ m) metavirtdir="$OPTARG" ;;
+ S) sdk_tarball="$OPTARG" ;;
+ e) extract_dir="$OPTARG" ;;
+ i) oci_image="$OPTARG" ;;
+ a) arch="$OPTARG" ;;
+ r) results_dir="$OPTARG" ;;
+ k) marker_filter="$OPTARG" ;;
+ h) usage; exit 0 ;;
+ :) echo "ERROR: option -$OPTARG requires an argument" >&2; usage; exit 2 ;;
+ \?) echo "ERROR: unknown option -$OPTARG" >&2; usage; exit 2 ;;
+ esac
+done
+
+if [ -z "$suite" ] || [ -z "$builddir" ] || [ -z "$metavirtdir" ]; then
+ echo "ERROR: -s, -b and -m are required" >&2
+ usage
+ exit 2
+fi
+
+builddir=$(realpath "$builddir")
+metavirtdir=$(realpath "$metavirtdir")
+
+if [ ! -d "$metavirtdir/tests" ]; then
+ echo "ERROR: meta-virtualization tests directory not found at $metavirtdir/tests" >&2
+ exit 1
+fi
+
+# Locate the vcontainer standalone SDK tarball. Prefer an explicitly-provided
+# SDK (-S), and fall back to looking in the local build's
+# deploy/sdk directory when running stand-alone.
+if [ -n "$sdk_tarball" ]; then
+ if [ ! -f "$sdk_tarball" ]; then
+ echo "ERROR: SDK installer '$sdk_tarball' is set but not a file" >&2
+ exit 1
+ fi
+else
+ sdk_tarball="$builddir/tmp/deploy/sdk/vcontainer-standalone.sh"
+ if [ ! -f "$sdk_tarball" ]; then
+ # Try to find any matching tarball in case naming changed (e.g. versioned)
+ alt=$(ls -1 "$builddir"/tmp/deploy/sdk/vcontainer-*.sh 2>/dev/null | head -n1 || true)
+ if [ -n "$alt" ]; then
+ sdk_tarball="$alt"
+ else
+ echo "ERROR: vcontainer standalone SDK not found." >&2
+ echo " Pass -S with an existing SDK installer, or" >&2
+ echo " build vcontainer-tarball so $builddir/tmp/deploy/sdk/vcontainer-standalone.sh exists." >&2
+ exit 1
+ fi
+ fi
+fi
+
+extract_dir="${extract_dir:-$builddir/vcontainer-test-extracted}"
+rm -rf "$extract_dir"
+mkdir -p "$(dirname "$extract_dir")"
+
+# Self-extracting installer (silent, -y agrees to license, -d picks dir)
+"$sdk_tarball" -d "$extract_dir" -y
+
+# Prepare a Python venv so we don't pollute the worker's system packages.
+python3 -m venv "$builddir/meta-virt-test-venv"
+# shellcheck disable=SC1091
+source "$builddir/meta-virt-test-venv/bin/activate"
+# Avoid warnings by upgrading pip; install pytest/pexpect into the venv via pip.
+export PIP_DISABLE_PIP_VERSION_CHECK=1
+python3 -m pip install --quiet -r "$metavirtdir/tests/requirements.txt"
+
+# Per-suite test file selection. Uses -k/-m for fine-grained filtering and
+# keeps the CLI small for logging clarity.
+case "$suite" in
+ vdkr)
+ test_files=(
+ "tests/test_vdkr.py"
+ "tests/test_vdkr_registry.py"
+ )
+ ;;
+ vpdmn)
+ test_files=(
+ "tests/test_vpdmn.py"
+ )
+ ;;
+ vcontainer)
+ # Broad vcontainer/bbclass/tooling coverage that doesn't require the
+ # vdkr/vpdmn CLI harness to be running.
+ test_files=(
+ "tests/test_container_cross_install.py"
+ "tests/test_container_registry_script.py"
+ "tests/test_vcontainer_auth_config.py"
+ "tests/test_multiarch_oci.py"
+ "tests/test_multilayer_oci.py"
+ )
+ ;;
+ *)
+ echo "ERROR: unknown suite '$suite' (expected vcontainer|vdkr|vpdmn)" >&2
+ exit 2
+ ;;
+esac
+
+pytest_args=(
+ -v
+ --tb=short
+ -m "$marker_filter"
+ --vdkr-dir "$extract_dir"
+ --junitxml="$builddir/pytest-$suite-results.xml"
+)
+
+# Allow tests that consume an OCI image (import/save/load) to find one.
+if [ -n "$oci_image" ] && [ -d "$oci_image" ]; then
+ pytest_args+=(--oci-image "$oci_image")
+fi
+
+# Pass architecture through when set (default is x86_64).
+if [ -n "$arch" ]; then
+ pytest_args+=(--arch "$arch")
+fi
+
+cd "$metavirtdir"
+# Don't let a single failing test kill the whole step - collect the junit
+# report, then surface the exit code via the junit file + exit status.
+set +e
+python3 -m pytest "${pytest_args[@]}" "${test_files[@]}"
+rc=$?
+set -e
+
+# Copy artefacts to the results dir if one was provided.
+if [ -n "$results_dir" ]; then
+ mkdir -p "$results_dir"
+ cp -f "$builddir/pytest-$suite-results.xml" "$results_dir/" 2>/dev/null || true
+ if [ -f /tmp/pytest-vcontainer.log ]; then
+ cp -f /tmp/pytest-vcontainer.log "$results_dir/pytest-$suite.log" || true
+ fi
+fi
+
+exit $rc
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [yocto-autobuilder-helper][PATCH v4 04/11] scripts: add container registry push, auth, tagging, runtime selection
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 00/11] Implement 'containers' jobs Tim Orling
` (2 preceding siblings ...)
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 03/11] scripts: add run-vcontainer-tests for meta-virtualization Tim Orling
@ 2026-07-06 22:05 ` Tim Orling
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 05/11] config.json: add 'containers-library' build job Tim Orling
` (6 subsequent siblings)
10 siblings, 0 replies; 19+ messages in thread
From: Tim Orling @ 2026-07-06 22:05 UTC (permalink / raw)
To: yocto-patches
Add the push-containers infrastructure that drives the
post-build steps for the 'containers-library' job. After
each build step the runtime container store is harvested
and pushed to one or more registries with derived per-step
tags.
* config.json: add CONTAINER_REGISTRIES, CONTAINER_AUTH_CONFIG,
CONTAINER_RUNTIME, CONTAINER_TAG_CMDS, CONTAINER_VERSION_RECIPE
and CONTAINER_IMAGE_MAP configuration knobs.
* scripts/run-config: drive push-containers as a post-step
action (by running scripts/run-push-containers).
* scripts/run-push-containers: Tags are generated from recipe and
distro metadata (yocto- tag uses major.minor on snapshots and full
PV on releases) with CONTAINER_VERSION_RECIPE allowing a step to
source PV from a different recipe than the image itself.
* Skip pushing when building the container image failed
* Registry auth is staged via .../config.json or podman
.../auth.json using CONTAINER_AUTH_CONFIG, replacing an
interactive login that could hang. CONTAINER_RUNTIME picks
between vdkr (Docker-compatible) and vpdmn (Podman) runtimes.
* Robustness: skip gracefully when no registries are configured,
fix the OCI directory path, handle memres already running,
and avoid hanging when memres has not yet come up.
* The vcontainer-tarball bundles its own python3/site-packages;
sourcing its environment-setup-ci puts them on PATH and shadows the
buildbot-venv (e.g. its websockets<10 breaks bitbake's hashserv).
It must never touch the build steps' PATH, so there is no job-wide
"vcontainer" init step: the tarball is set up (downloaded/extracted
on demand, cached) and its env sourced only in the synthesized steps
that actually use its runtime wrappers -- push-containers (vpdmn/vdkr
'vimport' for single-arch; multiarch uses skopeo-native from the
build).
AI-Generated: Claude Cowork Opus 4.8
Signed-off-by: Tim Orling <tim.orling@konsulko.com>
---
config.json | 5 +
scripts/run-config | 24 +++--
scripts/run-push-containers | 180 ++++++++++++++++++++++++++++++++++++
3 files changed, 201 insertions(+), 8 deletions(-)
create mode 100755 scripts/run-push-containers
diff --git a/config.json b/config.json
index ae13c5b..c6ee372 100644
--- a/config.json
+++ b/config.json
@@ -43,6 +43,11 @@
"BUILDINFOVARS" : ["INHERIT += 'image-buildinfo'", "IMAGE_BUILDINFO_VARS:append = ' IMAGE_BASENAME IMAGE_NAME'"],
"WRITECONFIG" : true,
"SENDERRORS" : true,
+ "CONTAINER_RUNTIME" : "vpdmn",
+ "CONTAINER_REGISTRIES" : [],
+ "CONTAINER_TAGS" : ["latest"],
+ "CONTAINER_TAG_CMDS" : [],
+ "CONTAINER_IMAGE_MAP" : {},
"extravars" : [
"SANITY_TESTED_DISTROS = ''",
"BB_HASHSERVE = '${AUTOBUILDER_HASHSERV}'",
diff --git a/scripts/run-config b/scripts/run-config
index 90a5996..391db3e 100755
--- a/scripts/run-config
+++ b/scripts/run-config
@@ -154,15 +154,11 @@ else:
sys.exit(0)
vcontainer = utils.getconfigvar("vcontainer", ourconfig, args.target)
-if jcfg:
- if vcontainer:
- addentry("vcontainer", "Setup vcontainer tarball", "init")
-elif vcontainer:
- # vcontainer is opt-in per target via the "vcontainer" config variable,
- # so this is a no-op for targets which don't set it
+# The vcontainer-tarball bundles its own python3/site-packages; avoid
+# shadowing the buildbot-venv, except in steps that require 'vpdmn' or
+# 'vkdr'. This avoids build failures on 'buildtools' workers.
+if not jcfg and vcontainer and args.stepname in ("push-containers"):
utils.setup_vcontainer_tarball(ourconfig, args.target, args.builddir + "/../vcontainer-tarball")
- if args.phase == "init" and args.stepname == "vcontainer":
- sys.exit(0)
extratools = utils.getconfigvar("extratools", ourconfig, args.target)
if jcfg:
@@ -195,6 +191,8 @@ utils.mkdir(errordir)
errorlogs = set()
+push_containers = properties.get("push_containers", False)
+
def log_file_contents(filename, builddir, stepnum, stepname):
logfile = logname(builddir, stepnum, stepname)
with open(logfile, "a") as outf, open(filename, "r") as f:
@@ -313,6 +311,16 @@ def handle_stepnum(stepnum):
hp.printheader("Step %s/%s: Running bitbake %s" % (stepnum, maxsteps, sanitytargets))
bitbakecmd(args.builddir, "bitbake %s -k" % (sanitytargets), report, stepnum, args.stepname)
+ # Push container images to registries when push_containers is enabled.
+ # The push logic itself lives in scripts/run-push-containers.
+ container_images = utils.getconfigdict("CONTAINER_IMAGE_MAP", ourconfig, args.target, stepnum)
+ if container_images and push_containers:
+ if jcfg:
+ addstepentry("push-containers", "Push containers", shortdesc, desc, str(container_images), str(stepnum))
+ elif args.stepname == "push-containers":
+ hp.printheader("Step %s/%s: Pushing container images %s" % (stepnum, maxsteps, list(container_images.keys())))
+ bitbakecmd(args.builddir, "%s/run-push-containers %s %s" % (scriptsdir, args.target, stepnum), report, stepnum, args.stepname)
+
# Run any extra commands specified
cmds = utils.getconfiglist("EXTRACMDS", ourconfig, args.target, stepnum)
if jcfg:
diff --git a/scripts/run-push-containers b/scripts/run-push-containers
new file mode 100755
index 0000000..00d87ed
--- /dev/null
+++ b/scripts/run-push-containers
@@ -0,0 +1,180 @@
+#!/usr/bin/env python3
+#
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Push container images built by a target step to the configured registries.
+#
+# Invoked by run-config for 'push-containers' steps with the OE build
+# environment already sourced (bitbake and the vcontainer runtime wrappers
+# must be on PATH). All configuration comes from the target/step config:
+#
+# CONTAINER_IMAGE_MAP - dict of image recipe -> registry image name
+# CONTAINER_RUNTIME - vpdmn (default) or vdkr
+# CONTAINER_REGISTRIES - list of registries to push to
+# CONTAINER_TAGS - list of static tags (e.g. latest)
+# CONTAINER_TAG_CMDS - extra shell to populate _EXTRA_TAGS
+# CONTAINER_VERSION_RECIPE - recipe whose PV provides the version tag
+# CONTAINER_AUTH_CONFIG - registry auth file staged into the guest
+#
+
+import subprocess
+import sys
+
+import utils
+
+parser = utils.ArgParser(description='Pushes container images for a target step to the configured registries.')
+
+parser.add_argument('target',
+ help="The target build name")
+parser.add_argument('stepnum',
+ type=int,
+ help="The step number within the target")
+
+args = parser.parse_args()
+
+ourconfig = utils.loadconfig()
+
+container_images = utils.getconfigdict("CONTAINER_IMAGE_MAP", ourconfig, args.target, args.stepnum)
+if not container_images:
+ print("No CONTAINER_IMAGE_MAP for %s step %s, nothing to push" % (args.target, args.stepnum))
+ sys.exit(0)
+
+registries = utils.getconfiglist("CONTAINER_REGISTRIES", ourconfig, args.target, args.stepnum)
+if not registries:
+ utils.printheader("push-containers skipped — CONTAINER_REGISTRIES is empty, no containers pushed")
+ sys.exit(0)
+
+runtime = utils.getconfigvar("CONTAINER_RUNTIME", ourconfig, args.target, args.stepnum) or "vpdmn"
+static_tags = utils.getconfiglist("CONTAINER_TAGS", ourconfig, args.target, args.stepnum)
+auth_config = utils.getconfigvar("CONTAINER_AUTH_CONFIG", ourconfig, args.target, args.stepnum)
+if not auth_config:
+ if runtime == "vpdmn":
+ auth_config = "${HOME}/.config/containers/auth.json"
+ else:
+ auth_config = "${HOME}/.docker/config.json"
+
+utils.printheader("Pushing container images %s" % list(container_images.keys()))
+
+script = [
+ "set -e",
+ "test -w /dev/kvm || { echo 'ERROR: /dev/kvm is not writable, cannot push containers'; exit 1; }",
+ # Always bring up a fresh memres VM in the foreground.
+ #
+ # 'memres status' only checks that the QEMU PID in daemon.pid
+ # is alive (see daemon_is_running()/daemon_status() in
+ # meta-virtualization's vrunner.sh); it returns 0 as soon as
+ # QEMU forks, so a hung/partially-booted VM from a previous
+ # run — or a VM in mid-boot — is reported as healthy. The
+ # subsequent 'login'/'vimport'/'push' commands then hang on
+ # the unresponsive daemon socket.
+ #
+ # 'memres restart' is synchronous: it does stop+start and
+ # runs a PING/PONG readiness probe against the daemon socket
+ # (120s timeout), exiting non-zero if the VM never answers.
+ # Running it in the foreground gives us a trustworthy ready
+ # signal via its exit code, so we can drop the status-poll
+ # loop entirely.
+ #
+ # Install an EXIT trap first so we always tear the daemon
+ # down, even if bitbake -e / vimport / push fails mid-step
+ # under 'set -e'. The trap is armed before the restart so
+ # a restart failure also triggers cleanup.
+ #
+ # Registry auth is staged into the guest at VM boot via
+ # the global '--config' flag — vrunner.sh's setup_auth_share()
+ # copies $AUTH_CONFIG onto a read-only 9p share, and
+ # vdkr-init.sh / vpdmn-init.sh's install_auth_config()
+ # installs it at /root/.docker/config.json (vdkr) or
+ # /run/containers/0/auth.json (vpdmn) inside the guest.
+ # Subsequent 'push' calls use those creds directly, so no
+ # explicit 'login' step is needed. Calling 'login' would
+ # actually hang under the autobuilder (no PTY): when the
+ # memres daemon is running, vcontainer-common.sh dispatches
+ # login via '--daemon-interactive' and blocks reading the
+ # password from stdin (see login case in vcontainer-common.sh).
+ "trap '%s-$(arch) memres stop 2>/dev/null || true' EXIT" % runtime,
+ "%s-$(arch) --config %s memres restart </dev/null" % (runtime, auth_config),
+]
+tag_cmds = utils.getconfiglist("CONTAINER_TAG_CMDS", ourconfig, args.target, args.stepnum)
+version_recipe = utils.getconfigvar("CONTAINER_VERSION_RECIPE", ourconfig, args.target, args.stepnum)
+for recipe, image in container_images.items():
+ # Extract version metadata from the recipe and distro via
+ # bitbake -e. Steps that need additional derived tags (e.g.
+ # major, major.minor) populate _EXTRA_TAGS via
+ # CONTAINER_TAG_CMDS in their step config.
+ #
+ # PV is sanitized with 'sed s/+.*//' to drop Yocto's
+ # '+git<sha>' suffix on AUTOREV/dev recipes — Docker
+ # reference format does not allow '+' in tags, and the
+ # base PV is what consumers expect.
+ #
+ # DISTRO_VERSION needs context-sensitive handling. Poky's
+ # DISTRO_VERSION resolves to '${PV}+snapshot-${METADATA_REVISION}'
+ # off a tag and just '${PV}' on a release tag. The '+' in
+ # the snapshot form is illegal in a Docker tag, but more
+ # importantly the patch level on a snapshot build (e.g.
+ # '6.0.99' between 6.0 and 6.1) is a moving target that
+ # doesn't correspond to any real release — only the
+ # major.minor line is meaningful. So:
+ # - snapshot build (DISTRO_VERSION contains '+') → tag
+ # with major.minor only, e.g. 'yocto-6.0'.
+ # - release-tag build (no '+') → tag with the full
+ # version, e.g. 'yocto-5.0.5' from the yocto-5.0.5 tag.
+ script += [
+ "_BBENV=$(bitbake -e %s 2>/dev/null) || true" % recipe,
+ "_PV=$(echo \"$_BBENV\" | awk -F'\"' '/^PV=/{ print $2; exit }' | sed 's/+.*//')",
+ "_DISTRO_CODENAME=$(echo \"$_BBENV\" | awk -F'\"' '/^DISTRO_CODENAME=/{ print $2; exit }')",
+ "_DISTRO_VERSION_RAW=$(echo \"$_BBENV\" | awk -F'\"' '/^DISTRO_VERSION=/{ print $2; exit }')",
+ "case \"$_DISTRO_VERSION_RAW\" in",
+ " *+*) _DISTRO_VERSION=$(echo \"${_DISTRO_VERSION_RAW%%+*}\" | cut -d. -f1,2) ;;",
+ " *) _DISTRO_VERSION=\"$_DISTRO_VERSION_RAW\" ;;",
+ "esac",
+ "_DEPLOY_DIR_IMAGE=$(echo \"$_BBENV\" | awk -F'\"' '/^DEPLOY_DIR_IMAGE=/{ print $2; exit }')",
+ "_EXTRA_TAGS=\"\"",
+ ]
+ if version_recipe:
+ # When the image recipe's PV is a wrapper-style
+ # placeholder (e.g. app-container-python_1.0.0.bb,
+ # whose 1.0.0 is meaningless to a downstream user),
+ # CONTAINER_VERSION_RECIPE points at the recipe whose
+ # PV is actually meaningful for the resulting tag —
+ # typically the language runtime or app being packaged
+ # (e.g. python3 -> 3.14.x). Override _PV from that
+ # recipe; image-recipe state still drives
+ # DEPLOY_DIR_IMAGE and DISTRO_* since those are
+ # environment-wide.
+ script += [
+ "_VBBENV=$(bitbake -e %s 2>/dev/null) || true" % version_recipe,
+ "_PV=$(echo \"$_VBBENV\" | awk -F'\"' '/^PV=/{ print $2; exit }' | sed 's/+.*//')",
+ ]
+ script += tag_cmds
+ script.append(
+ "_TAGS=\"%s $_PV $_DISTRO_CODENAME yocto-$_DISTRO_VERSION $_EXTRA_TAGS\"" % " ".join(static_tags)
+ )
+ # Only push an image whose build actually produced an OCI artefact.
+ # build-targets runs 'bitbake ... -k', so a failed image build does
+ # not abort the build step or the other images, and the failure is
+ # already reported there. If we let the push proceed, 'vimport' of
+ # the missing ${recipe}-latest-oci would fail under 'set -e' and
+ # abort the whole push step — taking down the images that *did*
+ # build with it. So skip a missing image here (warn, don't fail)
+ # and let the successfully-built ones publish.
+ script += [
+ "_OCI_IMAGE=${_DEPLOY_DIR_IMAGE}/%s-latest-oci" % recipe,
+ "if [ ! -e \"$_OCI_IMAGE\" ]; then",
+ " echo \"WARNING: %s did not build (no OCI image at $_OCI_IMAGE), skipping push\"" % recipe,
+ "else",
+ ]
+ for registry in registries:
+ # No per-registry 'login': credentials were staged into
+ # the guest by '--config' on 'memres restart' above.
+ script += [
+ " for _tag in $_TAGS; do",
+ " %s-$(arch) vimport ${_DEPLOY_DIR_IMAGE}/%s-latest-oci %s/%s:${_tag}" % (runtime, recipe, registry, image),
+ " %s-$(arch) push %s/%s:${_tag}" % (runtime, registry, image),
+ " done",
+ ]
+ script.append("fi")
+# Tear-down is handled by the EXIT trap installed above.
+utils.flush()
+sys.exit(subprocess.call(["/bin/bash", "-c", "\n".join(script)]))
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [yocto-autobuilder-helper][PATCH v4 05/11] config.json: add 'containers-library' build job
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 00/11] Implement 'containers' jobs Tim Orling
` (3 preceding siblings ...)
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 04/11] scripts: add container registry push, auth, tagging, runtime selection Tim Orling
@ 2026-07-06 22:05 ` Tim Orling
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 06/11] scripts/run-push-containers: push multiarch containers with skopeo-native Tim Orling
` (5 subsequent siblings)
10 siblings, 0 replies; 19+ messages in thread
From: Tim Orling @ 2026-07-06 22:05 UTC (permalink / raw)
To: yocto-patches
Add 'containers-library' build job that build container images on top
of the vcontainer-tarball SDK:
* original container images from the intial "container-cross-install"
branch of meta-virtualization
- container-base
- app-container-curl
* additional images modelled after docker.io/library/*
- app-container-python
- app-container-mosquitto
- app-container-valkey
- app-container-nginx
* Tag containers with versions based on the recipe to which they are
mapped, e.g. python:3, python:3.14, python:3.14.5. Also tag with
DISTRO_CODENAME and DISTRO_VERSION, e.g. wrynose and yocto-6.0.
* Similar to vcontainer-tarball job, follow the guidance in
meta-virtualization/docs/build-profiles.md and add
"require conf/distro/include/meta-virt-host.conf" to
extravars. This adds the required DISTRO_FEATURES and BBMULTICONFIG
(vruntime-*, container-*). It also fixes warnings due to BBMASKing,
by setting the appropriate BBFILE_PATTERN_IGNORE_EMPTY on layers
which are intentionally masked. Use 'sed' in EXTRACMDS to remove the
added 'require' line from conf/auto.conf or else remove-layers step
throws a file not found error.
* Append 'usrmerge' to DISTRO_FEATURES and set INIT_MANAGER to systemd
Signed-off-by: Tim Orling <tim.orling@konsulko.com>
---
config.json | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 59 insertions(+)
diff --git a/config.json b/config.json
index c6ee372..adabd57 100644
--- a/config.json
+++ b/config.json
@@ -1891,6 +1891,65 @@
"${SCRIPTSDIR}/run-vcontainer-tests -s vpdmn -b ${BUILDDIR} -m ${BUILDDIR}/../meta-virtualization -S ${BASE_SHAREDDIR}/pub/vcontainer-tarball-latest/vcontainer-standalone.sh -r ${HELPERRESULTSDIR}"
]
}
+ },
+ "containers-library": {
+ "NEEDREPOS" : ["bitbake", "meta-openembedded", "meta-virtualization"],
+ "ADDLAYER" : [
+ "${BUILDDIR}/../meta-openembedded/meta-oe",
+ "${BUILDDIR}/../meta-openembedded/meta-python",
+ "${BUILDDIR}/../meta-openembedded/meta-networking",
+ "${BUILDDIR}/../meta-openembedded/meta-filesystems",
+ "${BUILDDIR}/../meta-openembedded/meta-webserver",
+ "${BUILDDIR}/../meta-virtualization"
+ ],
+ "extravars" : [
+ "require conf/distro/include/meta-virt-host.conf",
+ "DISTRO_FEATURES:append = ' usrmerge'",
+ "INIT_MANAGER = 'systemd'"
+ ],
+ "EXTRACMDS" : ["sed -i '/meta-virt-host.conf/d' ${HELPERBUILDDIR}/conf/auto.conf"],
+ "vcontainer" : "${VCONTAINER_TARBALL_URL}",
+ "CONTAINER_TAG_CMDS" : [
+ "_PV_MAJOR=$(echo $_PV | cut -d. -f1)",
+ "_PV_MAJOR_MINOR=$(echo $_PV | cut -d. -f1,2)",
+ "_EXTRA_TAGS=\"$_PV_MAJOR $_PV_MAJOR_MINOR\""
+ ],
+ "step1" : {
+ "shortname" : "Build 'base' container",
+ "BBTARGETS" : "container-base",
+ "CONTAINER_IMAGE_MAP" : {"container-base": "base"},
+ "CONTAINER_VERSION_RECIPE" : "base-files"
+ },
+ "step2" : {
+ "shortname" : "Build 'curl' container",
+ "BBTARGETS" : "app-container-curl",
+ "CONTAINER_IMAGE_MAP" : {"app-container-curl": "curl"},
+ "CONTAINER_VERSION_RECIPE" : "curl"
+ },
+ "step3" : {
+ "shortname" : "Build 'python' container",
+ "BBTARGETS" : "app-container-python",
+ "CONTAINER_IMAGE_MAP" : {"app-container-python": "python"},
+ "CONTAINER_VERSION_RECIPE" : "python3"
+ },
+ "step4" : {
+ "shortname" : "Build 'mosquitto' container",
+ "BBTARGETS" : "app-container-mosquitto",
+ "CONTAINER_IMAGE_MAP" : {"app-container-mosquitto": "mosquitto"},
+ "CONTAINER_VERSION_RECIPE" : "mosquitto"
+ },
+ "step5" : {
+ "shortname" : "Build 'valkey' container",
+ "BBTARGETS" : "app-container-valkey",
+ "CONTAINER_IMAGE_MAP" : {"app-container-valkey": "valkey"},
+ "CONTAINER_VERSION_RECIPE" : "valkey"
+ },
+ "step6" : {
+ "shortname" : "Build 'nginx' container",
+ "BBTARGETS" : "app-container-nginx",
+ "CONTAINER_IMAGE_MAP" : {"app-container-nginx": "nginx"},
+ "CONTAINER_VERSION_RECIPE" : "nginx"
+ }
}
},
"repo-defaults" : {
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [yocto-autobuilder-helper][PATCH v4 06/11] scripts/run-push-containers: push multiarch containers with skopeo-native
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 00/11] Implement 'containers' jobs Tim Orling
` (4 preceding siblings ...)
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 05/11] config.json: add 'containers-library' build job Tim Orling
@ 2026-07-06 22:05 ` Tim Orling
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 07/11] containers-library: switch to multiarch; add alpine single-arch Tim Orling
` (4 subsequent siblings)
10 siblings, 0 replies; 19+ messages in thread
From: Tim Orling @ 2026-07-06 22:05 UTC (permalink / raw)
To: yocto-patches
Support pushing multi-architecture container images (recipes
inheriting meta-virtualization's oci-multiarch class) from the
'containers-library' job. These produce an OCI Image Index on
disk rather than a single-arch image in a runtime store, so
they are pushed with skopeo instead of vdkr/vpdmn (which only
vimport one architecture).
* scripts/run-push-containers: detect multiarch recipes per-recipe
at runtime: 'bitbake -e' only contains OCI_MULTIARCH_OUTPUT when
the recipe inherits oci-multiarch, and its value is the exact
OCI layout path to push. Multiarch images are pushed with
'oe-run-native skopeo-native skopeo copy --all' direct from
the OCI layout, after populating the skopeo-native recipe
sysroot via 'bitbake skopeo-native -c addto_recipe_sysroot'
(once per step, prepare_skopeo). The same tag set and
registries are used for both paths; auth is passed to skopeo
via --dest-authfile from CONTAINER_AUTH_CONFIG.
* scripts/run-push-containers: defer the memres VM bring-up (kvm
check, EXIT trap, restart, image rm) into a lazy start_vm() that
only runs when the first single-arch recipe is pushed, so multiarch
-only steps need neither /dev/kvm nor a VM boot.
AI-Generated: Claude Cowork Opus 4.8
Signed-off-by: Tim Orling <tim.orling@konsulko.com>
---
scripts/run-push-containers | 100 +++++++++++++++++++++++++++---------
1 file changed, 77 insertions(+), 23 deletions(-)
diff --git a/scripts/run-push-containers b/scripts/run-push-containers
index 00d87ed..c13f493 100755
--- a/scripts/run-push-containers
+++ b/scripts/run-push-containers
@@ -58,7 +58,24 @@ utils.printheader("Pushing container images %s" % list(container_images.keys()))
script = [
"set -e",
"test -w /dev/kvm || { echo 'ERROR: /dev/kvm is not writable, cannot push containers'; exit 1; }",
- # Always bring up a fresh memres VM in the foreground.
+ # Two push paths share this script:
+ #
+ # - Single-arch images (the common case) are imported into
+ # a vdkr/vpdmn memres VM and pushed from inside it. The
+ # VM bring-up is expensive and needs /dev/kvm, so it is
+ # wrapped in start_vm() and only runs when the first
+ # single-arch recipe is pushed.
+ #
+ # - Multi-arch images (recipes inheriting oci-multiarch)
+ # are pushed with skopeo-native straight from the OCI
+ # Image Index layout on disk — no VM, no /dev/kvm.
+ # skopeo's recipe sysroot is populated on first use by
+ # prepare_skopeo(). Detection is per-recipe at runtime:
+ # 'bitbake -e <recipe>' only contains OCI_MULTIARCH_OUTPUT
+ # when the recipe inherits oci-multiarch, and its value is
+ # the exact OCI layout path to push.
+ #
+ # Notes on the memres VM bring-up in start_vm():
#
# 'memres status' only checks that the QEMU PID in daemon.pid
# is alive (see daemon_is_running()/daemon_status() in
@@ -92,8 +109,20 @@ script = [
# memres daemon is running, vcontainer-common.sh dispatches
# login via '--daemon-interactive' and blocks reading the
# password from stdin (see login case in vcontainer-common.sh).
- "trap '%s-$(arch) memres stop 2>/dev/null || true' EXIT" % runtime,
- "%s-$(arch) --config %s memres restart </dev/null" % (runtime, auth_config),
+ "_VM_STARTED=0",
+ "start_vm() {",
+ " if [ \"$_VM_STARTED\" = 1 ]; then return 0; fi",
+ " test -w /dev/kvm || { echo 'ERROR: /dev/kvm is not writable, cannot push containers'; exit 1; }",
+ " trap '%s-$(arch) memres stop 2>/dev/null || true' EXIT" % runtime,
+ " %s-$(arch) --config %s memres restart </dev/null" % (runtime, auth_config),
+ " _VM_STARTED=1",
+ "}",
+ "_SKOPEO_READY=0",
+ "prepare_skopeo() {",
+ " if [ \"$_SKOPEO_READY\" = 1 ]; then return 0; fi",
+ " bitbake skopeo-native -c addto_recipe_sysroot",
+ " _SKOPEO_READY=1",
+ "}",
]
tag_cmds = utils.getconfiglist("CONTAINER_TAG_CMDS", ourconfig, args.target, args.stepnum)
version_recipe = utils.getconfigvar("CONTAINER_VERSION_RECIPE", ourconfig, args.target, args.stepnum)
@@ -130,6 +159,10 @@ for recipe, image in container_images.items():
" *) _DISTRO_VERSION=\"$_DISTRO_VERSION_RAW\" ;;",
"esac",
"_DEPLOY_DIR_IMAGE=$(echo \"$_BBENV\" | awk -F'\"' '/^DEPLOY_DIR_IMAGE=/{ print $2; exit }')",
+ # Only set (non-empty) when the recipe inherits
+ # oci-multiarch; doubles as the multiarch marker and
+ # the OCI layout path for skopeo below.
+ "_OCI_MULTIARCH_OUTPUT=$(echo \"$_BBENV\" | awk -F'\"' '/^OCI_MULTIARCH_OUTPUT=/{ print $2; exit }')",
"_EXTRA_TAGS=\"\"",
]
if version_recipe:
@@ -151,29 +184,50 @@ for recipe, image in container_images.items():
script.append(
"_TAGS=\"%s $_PV $_DISTRO_CODENAME yocto-$_DISTRO_VERSION $_EXTRA_TAGS\"" % " ".join(static_tags)
)
- # Only push an image whose build actually produced an OCI artefact.
- # build-targets runs 'bitbake ... -k', so a failed image build does
- # not abort the build step or the other images, and the failure is
- # already reported there. If we let the push proceed, 'vimport' of
- # the missing ${recipe}-latest-oci would fail under 'set -e' and
- # abort the whole push step — taking down the images that *did*
- # build with it. So skip a missing image here (warn, don't fail)
- # and let the successfully-built ones publish.
- script += [
- "_OCI_IMAGE=${_DEPLOY_DIR_IMAGE}/%s-latest-oci" % recipe,
- "if [ ! -e \"$_OCI_IMAGE\" ]; then",
- " echo \"WARNING: %s did not build (no OCI image at $_OCI_IMAGE), skipping push\"" % recipe,
- "else",
- ]
+ # Detect multiarch vs single-arch per recipe and push accordingly,
+ # but only for an image whose build actually produced its OCI artefact.
+ #
+ # build-targets runs 'bitbake ... -k', so a failed image build does not
+ # abort the build step or the other images, and that failure is already
+ # reported there. If we pushed regardless, the missing artefact would make
+ # 'skopeo copy'/'vimport' fail under 'set -e' and abort the whole push
+ # step — taking the images that *did* build down with it. So skip an image
+ # whose artefact is missing (warn, don't fail) and let the
+ # successfully-built ones publish.
+ #
+ # No per-registry 'login': for the VM path, credentials are staged into
+ # the guest by '--config' on 'memres restart' (see start_vm); for the
+ # skopeo path, the same auth file is passed via --dest-authfile.
+ script.append("if [ -n \"$_OCI_MULTIARCH_OUTPUT\" ]; then")
+ # Multi-arch: push the OCI Image Index layout at $_OCI_MULTIARCH_OUTPUT
+ # straight from disk with skopeo-native (no VM, no /dev/kvm).
+ script.append(" if [ ! -e \"$_OCI_MULTIARCH_OUTPUT\" ]; then")
+ script.append(" echo \"WARNING: %s did not build (no OCI image at $_OCI_MULTIARCH_OUTPUT), skipping push\"" % recipe)
+ script.append(" else")
+ script.append(" prepare_skopeo")
+ for registry in registries:
+ script += [
+ " for _tag in $_TAGS; do",
+ " oe-run-native skopeo-native skopeo copy --all --dest-authfile %s oci:${_OCI_MULTIARCH_OUTPUT} docker://%s/%s:${_tag}" % (auth_config, registry, image),
+ " done",
+ ]
+ script.append(" fi")
+ script.append("else")
+ # Single-arch: import the ${recipe}-latest-oci artefact into the memres VM
+ # and push it from inside the guest.
+ script.append(" _OCI_IMAGE=${_DEPLOY_DIR_IMAGE}/%s-latest-oci" % recipe)
+ script.append(" if [ ! -e \"$_OCI_IMAGE\" ]; then")
+ script.append(" echo \"WARNING: %s did not build (no OCI image at $_OCI_IMAGE), skipping push\"" % recipe)
+ script.append(" else")
+ script.append(" start_vm")
for registry in registries:
- # No per-registry 'login': credentials were staged into
- # the guest by '--config' on 'memres restart' above.
script += [
- " for _tag in $_TAGS; do",
- " %s-$(arch) vimport ${_DEPLOY_DIR_IMAGE}/%s-latest-oci %s/%s:${_tag}" % (runtime, recipe, registry, image),
- " %s-$(arch) push %s/%s:${_tag}" % (runtime, registry, image),
- " done",
+ " for _tag in $_TAGS; do",
+ " %s-$(arch) vimport ${_DEPLOY_DIR_IMAGE}/%s-latest-oci %s/%s:${_tag}" % (runtime, recipe, registry, image),
+ " %s-$(arch) push %s/%s:${_tag}" % (runtime, registry, image),
+ " done",
]
+ script.append(" fi")
script.append("fi")
# Tear-down is handled by the EXIT trap installed above.
utils.flush()
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [yocto-autobuilder-helper][PATCH v4 07/11] containers-library: switch to multiarch; add alpine single-arch
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 00/11] Implement 'containers' jobs Tim Orling
` (5 preceding siblings ...)
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 06/11] scripts/run-push-containers: push multiarch containers with skopeo-native Tim Orling
@ 2026-07-06 22:05 ` Tim Orling
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 08/11] run-push-containers: conditionally sign pushed containers with cosign Tim Orling
` (3 subsequent siblings)
10 siblings, 0 replies; 19+ messages in thread
From: Tim Orling @ 2026-07-06 22:05 UTC (permalink / raw)
To: yocto-patches
Utilize the oci-multiarch detection in run-push-containers script,
which uses 'skopeo-native copy' to 'push' the oci/ directory to the
container registries.
Add app-container-alpine as a single-arch container which demonstrates
alpine-oci-base and ensures we are exercising the single-arch path
in run-push-containers.
Signed-off-by: Tim Orling <tim.orling@konsulko.com>
---
config.json | 44 +++++++++++++++++++++++++-------------------
1 file changed, 25 insertions(+), 19 deletions(-)
diff --git a/config.json b/config.json
index adabd57..878cca0 100644
--- a/config.json
+++ b/config.json
@@ -1915,39 +1915,45 @@
"_EXTRA_TAGS=\"$_PV_MAJOR $_PV_MAJOR_MINOR\""
],
"step1" : {
- "shortname" : "Build 'base' container",
- "BBTARGETS" : "container-base",
- "CONTAINER_IMAGE_MAP" : {"container-base": "base"},
- "CONTAINER_VERSION_RECIPE" : "base-files"
+ "shortname" : "Build 'alpine' single-arch container",
+ "BBTARGETS" : "app-container-alpine",
+ "CONTAINER_IMAGE_MAP" : {"app-container-alpine": "alpine"},
+ "CONTAINER_VERSION_RECIPE" : "alpine-oci-base"
},
"step2" : {
- "shortname" : "Build 'curl' container",
- "BBTARGETS" : "app-container-curl",
- "CONTAINER_IMAGE_MAP" : {"app-container-curl": "curl"},
- "CONTAINER_VERSION_RECIPE" : "curl"
+ "shortname" : "Build 'base' multiarch container",
+ "BBTARGETS" : "container-image-multiarch-container-base",
+ "CONTAINER_IMAGE_MAP" : {"container-image-multiarch-container-base": "base"},
+ "CONTAINER_VERSION_RECIPE" : "base-files"
},
"step3" : {
+ "shortname" : "Build 'curl' multiarch container",
+ "BBTARGETS" : "container-image-multiarch-app-container-curl",
+ "CONTAINER_IMAGE_MAP" : {"container-image-multiarch-app-container-curl": "curl"},
+ "CONTAINER_VERSION_RECIPE" : "curl"
+ },
+ "step4" : {
"shortname" : "Build 'python' container",
- "BBTARGETS" : "app-container-python",
- "CONTAINER_IMAGE_MAP" : {"app-container-python": "python"},
+ "BBTARGETS" : "container-image-multiarch-app-container-python",
+ "CONTAINER_IMAGE_MAP" : {"container-image-multiarch-app-container-python": "python"},
"CONTAINER_VERSION_RECIPE" : "python3"
},
- "step4" : {
+ "step5" : {
"shortname" : "Build 'mosquitto' container",
- "BBTARGETS" : "app-container-mosquitto",
- "CONTAINER_IMAGE_MAP" : {"app-container-mosquitto": "mosquitto"},
+ "BBTARGETS" : "container-image-multiarch-app-container-mosquitto",
+ "CONTAINER_IMAGE_MAP" : {"container-image-multiarch-app-container-mosquitto": "mosquitto"},
"CONTAINER_VERSION_RECIPE" : "mosquitto"
},
- "step5" : {
+ "step6" : {
"shortname" : "Build 'valkey' container",
- "BBTARGETS" : "app-container-valkey",
- "CONTAINER_IMAGE_MAP" : {"app-container-valkey": "valkey"},
+ "BBTARGETS" : "container-image-multiarch-app-container-valkey",
+ "CONTAINER_IMAGE_MAP" : {"container-image-multiarch-app-container-valkey": "valkey"},
"CONTAINER_VERSION_RECIPE" : "valkey"
},
- "step6" : {
+ "step7" : {
"shortname" : "Build 'nginx' container",
- "BBTARGETS" : "app-container-nginx",
- "CONTAINER_IMAGE_MAP" : {"app-container-nginx": "nginx"},
+ "BBTARGETS" : "container-image-multiarch-app-container-nginx",
+ "CONTAINER_IMAGE_MAP" : {"container-image-multiarch-app-container-nginx": "nginx"},
"CONTAINER_VERSION_RECIPE" : "nginx"
}
}
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [yocto-autobuilder-helper][PATCH v4 08/11] run-push-containers: conditionally sign pushed containers with cosign
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 00/11] Implement 'containers' jobs Tim Orling
` (6 preceding siblings ...)
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 07/11] containers-library: switch to multiarch; add alpine single-arch Tim Orling
@ 2026-07-06 22:05 ` Tim Orling
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 09/11] scripts/run-push-containers: add SPDX SBOM attestation Tim Orling
` (2 subsequent siblings)
10 siblings, 0 replies; 19+ messages in thread
From: Tim Orling @ 2026-07-06 22:05 UTC (permalink / raw)
To: yocto-patches
Introduce CONTAINER_COSIGN_KEY, a cosign private key path that gates
signing. When unset (the default) no cosign sysroot is prepared and
signing is skipped entirely.
When set, every image that is successfully pushed is signed in place:
- prepare_cosign() lazily populates the cosign-native sysroot
(bitbake cosign-native -c addto_recipe_sysroot) and logs cosign in
to each target registry. CONTAINER_REGISTRIES entries carry a
namespace path (e.g. quay.io/ticotimo), but the auth file is keyed
by the bare registry host and 'cosign login' only accepts a host
authority, so each entry is stripped to its host ('${reg%%/*}') for
both the credential lookup and the login: auths.<host>.auth is
base64-decoded into user:password and the password is fed on stdin
(--password-stdin).
- sign_image() signs the image by digest with 'cosign sign --recursive
--key' (which also covers every child manifest of a multi-arch index)
so cosign does not warn about signing a mutable tag. The digest must
be the one the tag resolves to: the multi-arch index digest is taken
from 'skopeo copy --all --digestfile' at push time, and the
single-arch manifest digest from 'skopeo inspect' (correct there, as
there is no list). Signing 'skopeo inspect' on a multi-arch tag is
avoided because without --raw it returns the host platform's child
digest, which would sign one arch and leave the index unsigned.
Signing is deduped per digest within the run, and a transparency-log
conflict ('entry already exists' / HTTP 409, which also recurs on
rebuilds that reproduce the same digest) is treated as success so
signing stays idempotent rather than aborting the step.
Signing runs in both push paths (multiarch skopeo-native and single-arch
memres VM) and only after a successful push, so a missing/failed-build
artefact is still skipped rather than signed. COSIGN_PASSWORD is exported
(defaulting to empty) so cosign reads the passphrase from the environment
instead of falling through to an interactive prompt (which dies with
"inappropriate ioctl for device" under the autobuilder); an encrypted key
requires the real value to be present in the build environment.
Signed-off-by: Tim Orling <tim.orling@konsulko.com>
---
config.json | 1 +
scripts/run-push-containers | 115 +++++++++++++++++++++++++++++++++++-
2 files changed, 113 insertions(+), 3 deletions(-)
diff --git a/config.json b/config.json
index 878cca0..a1eee57 100644
--- a/config.json
+++ b/config.json
@@ -48,6 +48,7 @@
"CONTAINER_TAGS" : ["latest"],
"CONTAINER_TAG_CMDS" : [],
"CONTAINER_IMAGE_MAP" : {},
+ "CONTAINER_COSIGN_KEY" : "",
"extravars" : [
"SANITY_TESTED_DISTROS = ''",
"BB_HASHSERVE = '${AUTOBUILDER_HASHSERV}'",
diff --git a/scripts/run-push-containers b/scripts/run-push-containers
index c13f493..9f6f2fa 100755
--- a/scripts/run-push-containers
+++ b/scripts/run-push-containers
@@ -15,6 +15,9 @@
# CONTAINER_TAG_CMDS - extra shell to populate _EXTRA_TAGS
# CONTAINER_VERSION_RECIPE - recipe whose PV provides the version tag
# CONTAINER_AUTH_CONFIG - registry auth file staged into the guest
+# CONTAINER_COSIGN_KEY - cosign private key path; when set, each
+# successfully pushed image is signed with
+# cosign (otherwise signing is skipped)
#
import subprocess
@@ -53,6 +56,10 @@ if not auth_config:
else:
auth_config = "${HOME}/.docker/config.json"
+# Signing is conditional: only when CONTAINER_COSIGN_KEY points at a cosign
+# private key. When unset, no cosign sysroot is prepared and no signing runs.
+cosign_key = utils.getconfigvar("CONTAINER_COSIGN_KEY", ourconfig, args.target, args.stepnum)
+
utils.printheader("Pushing container images %s" % list(container_images.keys()))
script = [
@@ -124,6 +131,99 @@ script = [
" _SKOPEO_READY=1",
"}",
]
+
+# Cosign signing helpers, only emitted when a signing key is configured.
+#
+# prepare_cosign() is lazy like prepare_skopeo()/start_vm(): it populates the
+# cosign-native sysroot once and logs cosign in to every target registry. The
+# push paths authenticate via the auth file directly (--dest-authfile /
+# --config), but cosign authenticates through its own credential store, so we
+# must 'cosign login' explicitly. CONTAINER_REGISTRIES entries carry a
+# namespace path (e.g. quay.io/ticotimo), but the auth file is keyed by the
+# bare registry host and 'cosign login' only accepts a host authority (a path
+# fails with "registries must be valid RFC 3986 URI authorities"). So we strip
+# each entry to its host ('${reg%%/*}') for both the credential lookup and the
+# login: read auths.<host>.auth from the auth file, base64-decode it into
+# 'user:password', and feed the password on stdin (--password-stdin) so it
+# never appears in the process table.
+#
+# COSIGN_PASSWORD is exported (defaulting to empty) so cosign reads the
+# passphrase from the environment and never falls through to an interactive
+# prompt under the autobuilder (no PTY) — without it cosign tries to read the
+# passphrase from the terminal and dies with "inappropriate ioctl for device".
+# The real value, if the key is encrypted, must be present in the build
+# environment.
+#
+# sign_image() signs by digest ($2) so cosign does not warn about signing a
+# mutable tag, while still pinning the index a consumer verifies. The digest
+# MUST be the digest the tag resolves to:
+# - multi-arch: the manifest-list/index digest, captured at push time from
+# 'skopeo copy --all --digestfile' (the digest of the list it pushed).
+# - single-arch: the lone manifest digest, which 'skopeo inspect' returns
+# correctly (no list, so no platform ambiguity) — used when $2 is empty.
+# 'cosign sign --recursive' then signs that digest and every child manifest.
+#
+# IMPORTANT: do NOT feed 'skopeo inspect' a multi-arch tag for the sign digest.
+# Without --raw it resolves to the host platform's CHILD manifest, so signing
+# image@<that-digest> signs one architecture and leaves the index unsigned —
+# why signed multi-arch images can still show as "Unsigned". Hence the index
+# digest comes from --digestfile, not inspect.
+if cosign_key:
+ login_block = """ for _reg in %s; do
+ _host=${_reg%%%%/*}
+ _creds=$(python3 -c 'import base64,json,os,sys
+a=json.load(open(os.path.expandvars(sys.argv[1])))
+e=a.get("auths",{}).get(sys.argv[2]) or {}
+t=e.get("auth")
+sys.stdout.write(base64.b64decode(t).decode() if t else "")' "%s" "$_host")
+ if [ -z "$_creds" ]; then
+ echo "WARNING: no credentials for $_host in auth file, skipping cosign login"
+ continue
+ fi
+ _cuser=${_creds%%%%:*}
+ _cpass=${_creds#*:}
+ printf '%%s' "$_cpass" | oe-run-native cosign-native cosign login "$_host" -u "$_cuser" --password-stdin
+ done""" % (" ".join(registries), auth_config)
+ script += [
+ "export COSIGN_PASSWORD=\"${COSIGN_PASSWORD:-}\"",
+ "_COSIGN_READY=0",
+ "prepare_cosign() {",
+ " if [ \"$_COSIGN_READY\" = 1 ]; then return 0; fi",
+ " bitbake cosign-native -c addto_recipe_sysroot",
+ login_block,
+ " _COSIGN_READY=1",
+ "}",
+ # Track digests already signed in this run so the same artefact is
+ # signed only once (see sign_image).
+ "_SIGNED_REFS=\"\"",
+ # $1 = <registry>/<image>:<tag> just pushed; $2 = its digest (the index
+ # digest from 'skopeo copy --digestfile' on the multi-arch path). When
+ # $2 is empty (single-arch), resolve the lone manifest digest with
+ # 'skopeo inspect' — correct there since there is no list. oe-run-native
+ # prints 'Getting sysroot...' to stdout, so grep the digest out.
+ "sign_image() {",
+ " prepare_skopeo",
+ " prepare_cosign",
+ " _SIG_DIGEST=\"$2\"",
+ " if [ -z \"$_SIG_DIGEST\" ]; then _SIG_DIGEST=$(oe-run-native skopeo-native skopeo inspect --authfile %s --format '{{.Digest}}' docker://$1 | grep -oE 'sha256:[0-9a-f]{64}' | tail -n1); fi" % auth_config,
+ " if [ -z \"$_SIG_DIGEST\" ]; then echo \"WARNING: could not resolve digest for $1, skipping cosign sign\"; return 0; fi",
+ " _SIG_REF=\"${1%:*}@${_SIG_DIGEST}\"",
+ " case \" $_SIGNED_REFS \" in *\" $_SIG_REF \"*) return 0 ;; esac",
+ " _SIGNED_REFS=\"$_SIGNED_REFS $_SIG_REF\"",
+ # Sign by digest; --recursive also signs each child manifest. Treat a
+ # transparency-log conflict ('already exists' / HTTP 409, which also
+ # recurs on rebuilds that reproduce the same digest) as success so
+ # signing stays idempotent instead of aborting the step.
+ " _sign_out=$(oe-run-native cosign-native cosign sign --recursive --key %s \"$_SIG_REF\" 2>&1) || {" % cosign_key,
+ " case \"$_sign_out\" in",
+ " *\"already exists\"*|*createLogEntryConflict*) echo \"cosign: $_SIG_REF already in transparency log, treating as signed\" ;;",
+ " *) echo \"$_sign_out\" >&2; return 1 ;;",
+ " esac",
+ " }",
+ " echo \"$_sign_out\"",
+ "}",
+ ]
+
tag_cmds = utils.getconfiglist("CONTAINER_TAG_CMDS", ourconfig, args.target, args.stepnum)
version_recipe = utils.getconfigvar("CONTAINER_VERSION_RECIPE", ourconfig, args.target, args.stepnum)
for recipe, image in container_images.items():
@@ -205,12 +305,19 @@ for recipe, image in container_images.items():
script.append(" echo \"WARNING: %s did not build (no OCI image at $_OCI_MULTIARCH_OUTPUT), skipping push\"" % recipe)
script.append(" else")
script.append(" prepare_skopeo")
+ # --digestfile records the index digest skopeo pushed, so signing can pin
+ # it directly (see sign_image); only needed when signing is enabled.
+ digestfile = ' --digestfile "$_DGSTFILE"' if cosign_key else ""
+ if cosign_key:
+ script.append(" _DGSTFILE=$(mktemp)") # tiny tmp file, assumes autobuilder workdir is ephemeral
for registry in registries:
script += [
" for _tag in $_TAGS; do",
- " oe-run-native skopeo-native skopeo copy --all --dest-authfile %s oci:${_OCI_MULTIARCH_OUTPUT} docker://%s/%s:${_tag}" % (auth_config, registry, image),
- " done",
+ " oe-run-native skopeo-native skopeo copy --all%s --dest-authfile %s oci:${_OCI_MULTIARCH_OUTPUT} docker://%s/%s:${_tag}" % (digestfile, auth_config, registry, image),
]
+ if cosign_key:
+ script.append(" sign_image %s/%s:${_tag} \"$(cat \"$_DGSTFILE\")\"" % (registry, image))
+ script.append(" done")
script.append(" fi")
script.append("else")
# Single-arch: import the ${recipe}-latest-oci artefact into the memres VM
@@ -225,8 +332,10 @@ for recipe, image in container_images.items():
" for _tag in $_TAGS; do",
" %s-$(arch) vimport ${_DEPLOY_DIR_IMAGE}/%s-latest-oci %s/%s:${_tag}" % (runtime, recipe, registry, image),
" %s-$(arch) push %s/%s:${_tag}" % (runtime, registry, image),
- " done",
]
+ if cosign_key:
+ script.append(" sign_image %s/%s:${_tag}" % (registry, image))
+ script.append(" done")
script.append(" fi")
script.append("fi")
# Tear-down is handled by the EXIT trap installed above.
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [yocto-autobuilder-helper][PATCH v4 09/11] scripts/run-push-containers: add SPDX SBOM attestation
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 00/11] Implement 'containers' jobs Tim Orling
` (7 preceding siblings ...)
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 08/11] run-push-containers: conditionally sign pushed containers with cosign Tim Orling
@ 2026-07-06 22:05 ` Tim Orling
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 10/11] config.json: add -dev container builds to containers-library Tim Orling
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 11/11] run-push-containers: optionally push -dev tagged containers Tim Orling
10 siblings, 0 replies; 19+ messages in thread
From: Tim Orling @ 2026-07-06 22:05 UTC (permalink / raw)
To: yocto-patches
Attach a *.rootfs.spdx.json as an in-toto attestation to either
the top-level container if it is single-arch or to each arch
container image if it is multi-arch.
Introduce CONTAINER_COSIGN_PUB which is the cosign public key path;
when set, each attestation is verified with 'cosign verify-attestation'
after it is attached
Signed-off-by: Tim Orling <tim.orling@konsulko.com>
---
scripts/run-push-containers | 106 +++++++++++++++++++++++++++++++++++-
1 file changed, 104 insertions(+), 2 deletions(-)
diff --git a/scripts/run-push-containers b/scripts/run-push-containers
index 9f6f2fa..debf83f 100755
--- a/scripts/run-push-containers
+++ b/scripts/run-push-containers
@@ -17,7 +17,14 @@
# CONTAINER_AUTH_CONFIG - registry auth file staged into the guest
# CONTAINER_COSIGN_KEY - cosign private key path; when set, each
# successfully pushed image is signed with
-# cosign (otherwise signing is skipped)
+# cosign (otherwise signing is skipped). Also
+# gates SPDX SBOM attestation: each pushed image
+# gets a 'cosign attest --type spdxjson'
+# attestation of its SPDX SBOM (per-arch on the
+# multi-arch path, top manifest on single-arch).
+# CONTAINER_COSIGN_PUB - cosign public key path; when set, each
+# attestation is verified with
+# 'cosign verify-attestation' after it is made
#
import subprocess
@@ -59,6 +66,9 @@ if not auth_config:
# Signing is conditional: only when CONTAINER_COSIGN_KEY points at a cosign
# private key. When unset, no cosign sysroot is prepared and no signing runs.
cosign_key = utils.getconfigvar("CONTAINER_COSIGN_KEY", ourconfig, args.target, args.stepnum)
+# Public key for verifying attestations; when unset, attestations are made but
+# not verified.
+cosign_pub = utils.getconfigvar("CONTAINER_COSIGN_PUB", ourconfig, args.target, args.stepnum)
utils.printheader("Pushing container images %s" % list(container_images.keys()))
@@ -193,6 +203,13 @@ sys.stdout.write(base64.b64decode(t).decode() if t else "")' "%s" "$_host")
login_block,
" _COSIGN_READY=1",
"}",
+ # Resolve the lone manifest digest of a single-arch reference. Correct
+ # only when there is no manifest list (single-arch / child manifest);
+ # the multi-arch path passes explicit digests instead. oe-run-native
+ # prints 'Getting sysroot...' to stdout, so grep the digest out.
+ "resolve_digest() {",
+ " oe-run-native skopeo-native skopeo inspect --authfile %s --format '{{.Digest}}' docker://$1 | grep -oE 'sha256:[0-9a-f]{64}' | tail -n1" % auth_config,
+ "}",
# Track digests already signed in this run so the same artefact is
# signed only once (see sign_image).
"_SIGNED_REFS=\"\"",
@@ -205,7 +222,7 @@ sys.stdout.write(base64.b64decode(t).decode() if t else "")' "%s" "$_host")
" prepare_skopeo",
" prepare_cosign",
" _SIG_DIGEST=\"$2\"",
- " if [ -z \"$_SIG_DIGEST\" ]; then _SIG_DIGEST=$(oe-run-native skopeo-native skopeo inspect --authfile %s --format '{{.Digest}}' docker://$1 | grep -oE 'sha256:[0-9a-f]{64}' | tail -n1); fi" % auth_config,
+ " if [ -z \"$_SIG_DIGEST\" ]; then _SIG_DIGEST=$(resolve_digest $1); fi",
" if [ -z \"$_SIG_DIGEST\" ]; then echo \"WARNING: could not resolve digest for $1, skipping cosign sign\"; return 0; fi",
" _SIG_REF=\"${1%:*}@${_SIG_DIGEST}\"",
" case \" $_SIGNED_REFS \" in *\" $_SIG_REF \"*) return 0 ;; esac",
@@ -222,6 +239,46 @@ sys.stdout.write(base64.b64decode(t).decode() if t else "")' "%s" "$_host")
" }",
" echo \"$_sign_out\"",
"}",
+ # Make sure an image's SPDX SBOM exists before we attest it. $1 is the
+ # expected spdx.json path, $2 the bitbake target that produces it
+ # (mc:<mc>:<recipe> on the multi-arch path, plain <recipe> single-arch).
+ # create_image_sbom_spdx runs by default for single-arch builds, so the
+ # file is normally already there; the multi-arch index recipe doesn't
+ # pull in the per-arch SBOM tasks, so we generate them on demand.
+ "ensure_spdx() {",
+ " if [ ! -e \"$1\" ]; then",
+ " echo \"SPDX SBOM $1 missing, generating: bitbake -c create_image_sbom_spdx $2\"",
+ " bitbake -c create_image_sbom_spdx $2 || true",
+ " fi",
+ " if [ ! -e \"$1\" ]; then echo \"WARNING: SPDX SBOM $1 still missing, skipping attestation\"; return 1; fi",
+ "}",
+ # Track refs already attested so the same digest is attested once.
+ "_ATTESTED_REFS=\"\"",
+ # Attest an SPDX SBOM against a digest-pinned image ref. $1 =
+ # <registry>/<image>@sha256:...; $2 = spdx.json path. Idempotent: a
+ # transparency-log conflict on rebuilds is treated as success. When a
+ # public key is configured the attestation is verified afterwards.
+ "attest_image() {",
+ " prepare_skopeo",
+ " prepare_cosign",
+ " case \" $_ATTESTED_REFS \" in *\" $1 \"*) return 0 ;; esac",
+ " _ATTESTED_REFS=\"$_ATTESTED_REFS $1\"",
+ " _att_out=$(oe-run-native cosign-native cosign attest --key %s --type spdxjson --predicate \"$2\" \"$1\" 2>&1) || {" % cosign_key,
+ " case \"$_att_out\" in",
+ " *\"already exists\"*|*createLogEntryConflict*) echo \"cosign: attestation for $1 already in transparency log\" ;;",
+ " *) echo \"$_att_out\" >&2; return 1 ;;",
+ " esac",
+ " }",
+ " echo \"$_att_out\"",
+ ]
+ if cosign_pub:
+ script += [
+ " oe-run-native cosign-native cosign verify-attestation --key %s --type spdxjson \"$1\" >/dev/null \\" % cosign_pub,
+ " && echo \"cosign: verified attestation for $1\" \\",
+ " || { echo \"ERROR: attestation verification failed for $1\" >&2; return 1; }",
+ ]
+ script += [
+ "}",
]
tag_cmds = utils.getconfiglist("CONTAINER_TAG_CMDS", ourconfig, args.target, args.stepnum)
@@ -259,6 +316,13 @@ for recipe, image in container_images.items():
" *) _DISTRO_VERSION=\"$_DISTRO_VERSION_RAW\" ;;",
"esac",
"_DEPLOY_DIR_IMAGE=$(echo \"$_BBENV\" | awk -F'\"' '/^DEPLOY_DIR_IMAGE=/{ print $2; exit }')",
+ # MACHINE locates the single-arch SPDX SBOM (<recipe>-<MACHINE>.rootfs.spdx.json).
+ "_MACHINE=$(echo \"$_BBENV\" | awk -F'\"' '/^MACHINE=/{ print $2; exit }')",
+ # TOPDIR and the oci-multiarch plain vars drive per-arch SBOM
+ # attestation below; the latter two are empty for non-multiarch recipes.
+ "_TOPDIR=$(echo \"$_BBENV\" | awk -F'\"' '/^TOPDIR=/{ print $2; exit }')",
+ "_OCI_MULTIARCH_RECIPE=$(echo \"$_BBENV\" | awk -F'\"' '/^OCI_MULTIARCH_RECIPE=/{ print $2; exit }')",
+ "_OCI_MULTIARCH_PLATFORMS=$(echo \"$_BBENV\" | awk -F'\"' '/^OCI_MULTIARCH_PLATFORMS=/{ print $2; exit }')",
# Only set (non-empty) when the recipe inherits
# oci-multiarch; doubles as the multiarch marker and
# the OCI layout path for skopeo below.
@@ -318,6 +382,34 @@ for recipe, image in container_images.items():
if cosign_key:
script.append(" sign_image %s/%s:${_tag} \"$(cat \"$_DGSTFILE\")\"" % (registry, image))
script.append(" done")
+ if cosign_key:
+ # Attest each child manifest with that arch's SPDX SBOM. The arch ->
+ # (multiconfig, machine) mapping lives in oci-multiarch.bbclass flags
+ # (OCI_MULTIARCH_MC/MACHINE[<arch>]); query them per platform with
+ # bitbake-getvar so an overridden mapping is still honoured. The child
+ # manifest digest is the per-arch source image's manifest digest, which
+ # the class copies verbatim into the index and skopeo copy --all pushes
+ # unchanged — so we attest <image>@<child-digest>.
+ script.append(" for _plat in $_OCI_MULTIARCH_PLATFORMS; do")
+ script.append(" _mc=$(bitbake-getvar -q -r %s --value -f $_plat OCI_MULTIARCH_MC)" % recipe)
+ script.append(" _machine=$(bitbake-getvar -q -r %s --value -f $_plat OCI_MULTIARCH_MACHINE)" % recipe)
+ script.append(" if [ -z \"$_mc\" ] || [ -z \"$_machine\" ]; then echo \"WARNING: no mc/machine for platform $_plat, skipping attestation\"; continue; fi")
+ script.append(" _pdir=${_TOPDIR}/tmp-${_mc}/deploy/images/${_machine}")
+ script.append(" _mspdx=${_pdir}/${_OCI_MULTIARCH_RECIPE}-${_machine}.rootfs.spdx.json")
+ # Locate the per-arch source OCI layout (same name patterns the class
+ # searches) and read its single manifest digest = the child digest.
+ script.append(" _mdig=\"\"")
+ script.append(" for _oci in ${_OCI_MULTIARCH_RECIPE}-latest-oci ${_OCI_MULTIARCH_RECIPE}-${_machine}-latest-oci ${_OCI_MULTIARCH_RECIPE}-oci; do")
+ script.append(" if [ -e \"${_pdir}/${_oci}/index.json\" ]; then")
+ script.append(" _mdig=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))[\"manifests\"][0][\"digest\"])' \"${_pdir}/${_oci}/index.json\")")
+ script.append(" break")
+ script.append(" fi")
+ script.append(" done")
+ script.append(" if [ -z \"$_mdig\" ]; then echo \"WARNING: no source OCI index for $_plat under $_pdir, skipping attestation\"; continue; fi")
+ script.append(" ensure_spdx \"$_mspdx\" \"mc:${_mc}:${_OCI_MULTIARCH_RECIPE}\" || continue")
+ for registry in registries:
+ script.append(" attest_image %s/%s@${_mdig} \"$_mspdx\"" % (registry, image))
+ script.append(" done")
script.append(" fi")
script.append("else")
# Single-arch: import the ${recipe}-latest-oci artefact into the memres VM
@@ -327,6 +419,12 @@ for recipe, image in container_images.items():
script.append(" echo \"WARNING: %s did not build (no OCI image at $_OCI_IMAGE), skipping push\"" % recipe)
script.append(" else")
script.append(" start_vm")
+ if cosign_key:
+ # create_image_sbom_spdx runs by default for single-arch image builds,
+ # so the SBOM is normally already deployed; ensure_spdx regenerates it
+ # only if absent. Single manifest -> attest the digest the tag resolves
+ # to (resolve_digest, also used by sign_image).
+ script.append(" _SPDX=${_DEPLOY_DIR_IMAGE}/%s-${_MACHINE}.rootfs.spdx.json" % recipe)
for registry in registries:
script += [
" for _tag in $_TAGS; do",
@@ -335,6 +433,10 @@ for recipe, image in container_images.items():
]
if cosign_key:
script.append(" sign_image %s/%s:${_tag}" % (registry, image))
+ script.append(" if ensure_spdx \"$_SPDX\" \"%s\"; then" % recipe)
+ script.append(" _ADIG=$(resolve_digest %s/%s:${_tag})" % (registry, image))
+ script.append(" [ -n \"$_ADIG\" ] && attest_image %s/%s@${_ADIG} \"$_SPDX\"" % (registry, image))
+ script.append(" fi")
script.append(" done")
script.append(" fi")
script.append("fi")
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [yocto-autobuilder-helper][PATCH v4 10/11] config.json: add -dev container builds to containers-library
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 00/11] Implement 'containers' jobs Tim Orling
` (8 preceding siblings ...)
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 09/11] scripts/run-push-containers: add SPDX SBOM attestation Tim Orling
@ 2026-07-06 22:05 ` Tim Orling
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 11/11] run-push-containers: optionally push -dev tagged containers Tim Orling
10 siblings, 0 replies; 19+ messages in thread
From: Tim Orling @ 2026-07-06 22:05 UTC (permalink / raw)
To: yocto-patches
For some of the containers, we want to provide a -dev version,
denoted by '-dev' appended to the tag(s). This runs the
container as UID '0' root user and adds a shell.
Inspired by dhi.io (Docker Hardened Images).
Signed-off-by: Tim Orling <tim.orling@konsulko.com>
---
config.json | 59 +++++++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 50 insertions(+), 9 deletions(-)
diff --git a/config.json b/config.json
index a1eee57..118401a 100644
--- a/config.json
+++ b/config.json
@@ -1925,37 +1925,78 @@
"shortname" : "Build 'base' multiarch container",
"BBTARGETS" : "container-image-multiarch-container-base",
"CONTAINER_IMAGE_MAP" : {"container-image-multiarch-container-base": "base"},
- "CONTAINER_VERSION_RECIPE" : "base-files"
+ "CONTAINER_VERSION_RECIPE" : "base-files",
+ "extravars" : ["PACKAGE_EXTRA_ARCHS:append = ' container-dummy-provides'"]
},
"step3" : {
"shortname" : "Build 'curl' multiarch container",
"BBTARGETS" : "container-image-multiarch-app-container-curl",
"CONTAINER_IMAGE_MAP" : {"container-image-multiarch-app-container-curl": "curl"},
- "CONTAINER_VERSION_RECIPE" : "curl"
+ "CONTAINER_VERSION_RECIPE" : "curl",
+ "extravars" : ["PACKAGE_EXTRA_ARCHS:append = ' container-dummy-provides'"]
},
"step4" : {
+ "shortname" : "Build 'curl' multiarch container (dev)",
+ "BBTARGETS" : "container-image-multiarch-app-container-curl",
+ "CONTAINER_IMAGE_MAP" : {"container-image-multiarch-app-container-curl": "curl"},
+ "CONTAINER_VERSION_RECIPE" : "curl",
+ "extravars" : ["PACKAGECONFIG:pn-app-container-curl = 'dev'"]
+ },
+ "step5" : {
"shortname" : "Build 'python' container",
"BBTARGETS" : "container-image-multiarch-app-container-python",
"CONTAINER_IMAGE_MAP" : {"container-image-multiarch-app-container-python": "python"},
- "CONTAINER_VERSION_RECIPE" : "python3"
+ "CONTAINER_VERSION_RECIPE" : "python3",
+ "extravars" : ["PACKAGE_EXTRA_ARCHS:append = ' container-dummy-provides'"]
},
- "step5" : {
+ "step6" : {
+ "shortname" : "Build 'python' container (dev)",
+ "BBTARGETS" : "container-image-multiarch-app-container-python",
+ "CONTAINER_IMAGE_MAP" : {"container-image-multiarch-app-container-python": "python"},
+ "CONTAINER_VERSION_RECIPE" : "python3",
+ "extravars" : ["PACKAGECONFIG:pn-app-container-python = 'dev'"]
+ },
+ "step7" : {
"shortname" : "Build 'mosquitto' container",
"BBTARGETS" : "container-image-multiarch-app-container-mosquitto",
"CONTAINER_IMAGE_MAP" : {"container-image-multiarch-app-container-mosquitto": "mosquitto"},
- "CONTAINER_VERSION_RECIPE" : "mosquitto"
+ "CONTAINER_VERSION_RECIPE" : "mosquitto",
+ "extravars" : ["PACKAGE_EXTRA_ARCHS:append = ' container-dummy-provides'"]
},
- "step6" : {
+ "step8" : {
+ "shortname" : "Build 'mosquitto' container (dev)",
+ "BBTARGETS" : "container-image-multiarch-app-container-mosquitto",
+ "CONTAINER_IMAGE_MAP" : {"container-image-multiarch-app-container-mosquitto": "mosquitto"},
+ "CONTAINER_VERSION_RECIPE" : "mosquitto",
+ "extravars" : ["PACKAGECONFIG:pn-app-container-mosquitto = 'dev'"]
+ },
+ "step9" : {
"shortname" : "Build 'valkey' container",
"BBTARGETS" : "container-image-multiarch-app-container-valkey",
"CONTAINER_IMAGE_MAP" : {"container-image-multiarch-app-container-valkey": "valkey"},
- "CONTAINER_VERSION_RECIPE" : "valkey"
+ "CONTAINER_VERSION_RECIPE" : "valkey",
+ "extravars" : ["PACKAGE_EXTRA_ARCHS:append = ' container-dummy-provides'"]
},
- "step7" : {
+ "step10" : {
+ "shortname" : "Build 'valkey' container (dev)",
+ "BBTARGETS" : "container-image-multiarch-app-container-valkey",
+ "CONTAINER_IMAGE_MAP" : {"container-image-multiarch-app-container-valkey": "valkey"},
+ "CONTAINER_VERSION_RECIPE" : "valkey",
+ "extravars" : ["PACKAGECONFIG:pn-app-container-valkey = 'dev'"]
+ },
+ "step11" : {
"shortname" : "Build 'nginx' container",
"BBTARGETS" : "container-image-multiarch-app-container-nginx",
"CONTAINER_IMAGE_MAP" : {"container-image-multiarch-app-container-nginx": "nginx"},
- "CONTAINER_VERSION_RECIPE" : "nginx"
+ "CONTAINER_VERSION_RECIPE" : "nginx",
+ "extravars" : ["PACKAGE_EXTRA_ARCHS:append = ' container-dummy-provides'"]
+ },
+ "step12" : {
+ "shortname" : "Build 'nginx' container (dev)",
+ "BBTARGETS" : "container-image-multiarch-app-container-nginx",
+ "CONTAINER_IMAGE_MAP" : {"container-image-multiarch-app-container-nginx": "nginx"},
+ "CONTAINER_VERSION_RECIPE" : "nginx",
+ "extravars" : ["PACKAGECONFIG:pn-app-container-nginx = 'dev'"]
}
}
},
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [yocto-autobuilder-helper][PATCH v4 11/11] run-push-containers: optionally push -dev tagged containers
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 00/11] Implement 'containers' jobs Tim Orling
` (9 preceding siblings ...)
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 10/11] config.json: add -dev container builds to containers-library Tim Orling
@ 2026-07-06 22:05 ` Tim Orling
10 siblings, 0 replies; 19+ messages in thread
From: Tim Orling @ 2026-07-06 22:05 UTC (permalink / raw)
To: yocto-patches
For containers which set PACKAGECONFIG 'dev', push additional
container images with '-dev' suffix to the tags.
Inspired by dhi.io (Docker Hardened Images).
Signed-off-by: Tim Orling <tim.orling@konsulko.com>
---
scripts/run-push-containers | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/scripts/run-push-containers b/scripts/run-push-containers
index debf83f..5dd35af 100755
--- a/scripts/run-push-containers
+++ b/scripts/run-push-containers
@@ -327,6 +327,20 @@ for recipe, image in container_images.items():
# oci-multiarch; doubles as the multiarch marker and
# the OCI layout path for skopeo below.
"_OCI_MULTIARCH_OUTPUT=$(echo \"$_BBENV\" | awk -F'\"' '/^OCI_MULTIARCH_OUTPUT=/{ print $2; exit }')",
+ # Drives the '-dev' tag suffix below (dhi.io convention: same image
+ # name, tag suffixed) for recipes built with PACKAGECONFIG:pn-<recipe> = "dev".
+ #
+ # $recipe here is the BBTARGET from CONTAINER_IMAGE_MAP, which on the
+ # multiarch path is the container-image-multiarch wrapper (e.g.
+ # container-image-multiarch-app-container-nginx) — a different PN
+ # than app-container-nginx, so 'PACKAGECONFIG:pn-app-container-nginx'
+ # never touches its PACKAGECONFIG. OCI_MULTIARCH_RECIPE (already
+ # extracted above) is the underlying app recipe name and is what
+ # PACKAGECONFIG:pn-<recipe> actually targets; fall back to $recipe
+ # itself for single-arch recipes (OCI_MULTIARCH_RECIPE empty there).
+ "_PC_RECIPE=\"${_OCI_MULTIARCH_RECIPE:-%s}\"" % recipe,
+ "_PC_BBENV=$(bitbake -e \"$_PC_RECIPE\" 2>/dev/null) || true",
+ "_PACKAGECONFIG=$(echo \"$_PC_BBENV\" | awk -F'\"' '/^PACKAGECONFIG=/{ print $2; exit }')",
"_EXTRA_TAGS=\"\"",
]
if version_recipe:
@@ -348,6 +362,18 @@ for recipe, image in container_images.items():
script.append(
"_TAGS=\"%s $_PV $_DISTRO_CODENAME yocto-$_DISTRO_VERSION $_EXTRA_TAGS\"" % " ".join(static_tags)
)
+ # 'dev' builds (PACKAGECONFIG:pn-<recipe> = "dev") push under the same
+ # image name as production, with every tag suffixed '-dev' (dhi.io
+ # convention), instead of a separate image name/repo.
+ script += [
+ "case \" $_PACKAGECONFIG \" in",
+ " *\" dev \"*)",
+ " _DEVTAGS=\"\"",
+ " for _t in $_TAGS; do _DEVTAGS=\"$_DEVTAGS ${_t}-dev\"; done",
+ " _TAGS=\"$_DEVTAGS\"",
+ " ;;",
+ "esac",
+ ]
# Detect multiarch vs single-arch per recipe and push accordingly,
# but only for an image whose build actually produced its OCI artefact.
#
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
end of thread, other threads:[~2026-07-06 22:07 UTC | newest]
Thread overview: 19+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-06 2:51 [yocto-autobuilder2][PATCH v3 0/6] Implement 'containers' jobs tim.orling
2026-06-06 2:51 ` [yocto-autobuilder2][PATCH v3 1/6] scripts: add vcontainer-tarball setup, integration, and publishing tim.orling
2026-06-06 2:51 ` [yocto-autobuilder2][PATCH v3 2/6] config.json: add vcontainer-tarball build target tim.orling
2026-06-06 2:51 ` [yocto-autobuilder2][PATCH v3 3/6] scripts: add run-vcontainer-tests for meta-virtualization tim.orling
2026-06-06 2:51 ` [yocto-autobuilder2][PATCH v3 4/6] scripts: add container registry push, auth, tagging, runtime selection tim.orling
2026-06-06 2:51 ` [yocto-autobuilder2][PATCH v3 5/6] config.json: add 'containers-library' build job tim.orling
2026-06-06 2:51 ` [yocto-autobuilder2][PATCH v3 6/6] scripts/run-push-containers: remove all images before push tim.orling
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 00/11] Implement 'containers' jobs Tim Orling
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 01/11] scripts: add vcontainer-tarball setup, integration, and publishing Tim Orling
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 02/11] config.json: add vcontainer-tarball build target Tim Orling
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 03/11] scripts: add run-vcontainer-tests for meta-virtualization Tim Orling
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 04/11] scripts: add container registry push, auth, tagging, runtime selection Tim Orling
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 05/11] config.json: add 'containers-library' build job Tim Orling
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 06/11] scripts/run-push-containers: push multiarch containers with skopeo-native Tim Orling
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 07/11] containers-library: switch to multiarch; add alpine single-arch Tim Orling
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 08/11] run-push-containers: conditionally sign pushed containers with cosign Tim Orling
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 09/11] scripts/run-push-containers: add SPDX SBOM attestation Tim Orling
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 10/11] config.json: add -dev container builds to containers-library Tim Orling
2026-07-06 22:05 ` [yocto-autobuilder-helper][PATCH v4 11/11] run-push-containers: optionally push -dev tagged containers Tim Orling
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.