-
Notifications
You must be signed in to change notification settings - Fork 219
Expand file tree
/
Copy pathJustfile
More file actions
564 lines (498 loc) · 22.6 KB
/
Copy pathJustfile
File metadata and controls
564 lines (498 loc) · 22.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
# The default entrypoint to working on this project.
# Run `just --list` to see available targets organized by group.
#
# See also `Makefile` and `xtask.rs`. Commands which end in `-local`
# skip containerization or virtualization (and typically just proxy `make`).
#
# By default the layering is:
# Github Actions -> Justfile -> podman -> make -> rustc
# -> podman -> package manager
# -> cargo xtask
# --------------------------------------------------------------------
mod bcvk 'bcvk.just'
# Configuration variables (override via environment or command line)
# Example: BOOTC_base=quay.io/fedora/fedora-bootc:42 just build
#
# Composefs backend quick-start (use env vars so settings persist across targets):
# export BOOTC_variant=composefs
# export BOOTC_bootloader=systemd # needed for UKI
# just build && just test-tmt readonly # both use composefs+systemd
#
# just build-sealed # shortcut: sealed UKI image
# just test-composefs systemd ext4 uki sealed
#
# Constraints:
# sealed → requires boot_type=uki and filesystem with fsverity (ext4/btrfs)
# uki → requires bootloader=systemd
# Output image name
base_img := "localhost/bootc"
# Synthetic upgrade image for testing
upgrade_img := base_img + "-upgrade"
# Base image with tmt dependencies added, used as the boot source for upgrade tests
upgrade_source_img := base_img + "-upgrade-source"
# Build variant: ostree (default) or composefs
variant := env("BOOTC_variant", "ostree")
bootloader := env("BOOTC_bootloader", "grub")
# Only used for composefs tests
filesystem := env("BOOTC_filesystem", "ext4")
# Only used for composefs tests
boot_type := env("BOOTC_boot_type", "bls")
# Only used for composefs tests
seal_state := env("BOOTC_seal_state", "unsealed")
# Baseconfigs to inject into the image for testing (e.g. "etc-transient" or "root-transient")
baseconfigs := env("BOOTC_baseconfigs", "")
# Base container image to build from
base := env("BOOTC_base", "quay.io/centos-bootc/centos-bootc:stream10")
# Buildroot base image
buildroot_base := env("BOOTC_buildroot_base", "quay.io/centos/centos:stream10")
# Optional: path to extra source (e.g. composefs-rs) for local development
# DEPRECATED: Use [patch] sections in Cargo.toml instead, which are auto-detected
extra_src := env("BOOTC_extra_src", "")
# Set to "1" to disable auto-detection of local Rust dependencies
no_auto_local_deps := env("BOOTC_no_auto_local_deps", "")
# Internal variables
nocache := env("BOOTC_nocache", "")
_nocache_arg := if nocache != "" { "--no-cache" } else { "" }
_baseconfigs_env := if baseconfigs != "" { "--env=BOOTC_baseconfigs=" + baseconfigs } else { "" }
testimage_label := "bootc.testimage=1"
lbi_images := "quay.io/curl/curl:latest quay.io/curl/curl-base:latest registry.access.redhat.com/ubi9/podman:latest"
fedora-coreos := "quay.io/fedora/fedora-coreos:testing-devel"
generic_buildargs := ""
_extra_src_args := if extra_src != "" { "-v " + extra_src + ":/run/extra-src:ro --security-opt=label=disable" } else { "" }
# filesystem arg: required for bootc container ukify to allow missing fsverity
# CARGO_INCREMENTAL is passed through as-is (CI sets it to 0); empty is a no-op,
# leaving cargo's own profile defaults in effect in the Dockerfile.
base_buildargs := generic_buildargs + " " + _extra_src_args \
+ " --build-arg=CARGO_INCREMENTAL=" + env("CARGO_INCREMENTAL", "") \
+ " --build-arg=base=" + base \
+ " --build-arg=variant=" + variant \
+ " --build-arg=bootloader=" + bootloader \
+ " --build-arg=boot_type=" + boot_type \
+ " --build-arg=seal_state=" + seal_state \
+ " --build-arg=filesystem=" + filesystem \
+ " --build-arg=baseconfigs=" + baseconfigs
buildargs := base_buildargs \
+ " --cap-add=all --security-opt=label=type:container_runtime_t --device /dev/fuse" \
+ " --secret=id=secureboot_key,src=target/test-secureboot/db.key --secret=id=secureboot_cert,src=target/test-secureboot/db.crt"
# ============================================================================
# Core workflows - the main targets most developers will use
# ============================================================================
# Build container image from current sources (default target)
[group('core')]
build: package _keygen && _pull-lbi-images
#!/bin/bash
set -xeuo pipefail
test -d target/packages
pkg_path=$(realpath target/packages)
eval $(just _git-build-vars)
podman build {{_nocache_arg}} --build-arg=image_version=${VERSION} --build-context "packages=${pkg_path}" -t {{base_img}} {{buildargs}} .
# Fetch all external dependencies with a retry loop.
#
# This runs `podman build --target=fetch` for both the main image and the
# upgrade-source image, retrying on transient network failures (Koji 503s,
# Copr outages, quay.io blips, etc.). In CI this runs as its own step
# before `just build` / `just test-upgrade` so that flakes don't require
# re-queueing the entire PR.
#
# The retry parameters can be overridden via environment variables:
# BOOTC_CI_RETRIES=10 BOOTC_CI_DELAY=60 just build-fetch
[group('core')]
build-fetch: _keygen package
#!/bin/bash
set -euo pipefail
retries=${BOOTC_CI_RETRIES:-3}
delay=${BOOTC_CI_DELAY:-30}
retry() {
local attempt
for attempt in $(seq 1 "$retries"); do
echo "--- Attempt ${attempt}/${retries}: $*"
if "$@"; then
return 0
fi
if [ "$attempt" -lt "$retries" ]; then
echo "--- Attempt ${attempt} failed, retrying in ${delay}s..."
sleep "$delay"
fi
done
echo "--- All ${retries} attempts failed: $*" >&2
return 1
}
# Pull the base images explicitly so failures are retried cleanly
# before we even start the container build.
retry podman pull -q {{base}}
retry podman pull -q {{buildroot_base}}
# Pull LBI images (also fetched later by _pull-lbi-images, but doing it
# here means a failure is retried rather than aborting the full build).
for img in {{lbi_images}}; do
retry podman pull -q "$img"
done
pkg_path=$(realpath target/packages)
# Build the network-heavy fetch stage of the main image. If this
# succeeds, `just build` will get a cache hit on the fetch layer and
# run entirely offline.
# Note: buildargs (not base_buildargs) is needed here because the
# target-base stage requires --cap-add/--security-opt for bwrap.
retry podman build {{_nocache_arg}} --build-context "packages=${pkg_path}" --target=fetch {{buildargs}} .
# Same for the upgrade-source image used by test-upgrade.
retry podman build {{_nocache_arg}} --build-arg=base={{base}} \
--target=fetch -f tmt/tests/Dockerfile.upgrade-source .
# Show available build variants and current configuration
[group('core')]
list-variants:
#!/bin/bash
cat <<'EOF'
Build Variants (set via BOOTC_variant= or variant=)
====================================================
ostree (default)
Standard bootc image using ostree backend.
This is the traditional, production-ready configuration.
composefs (bootloader, filesystem, boot_type, seal_state)
Build Composefs image with:
- The specified bootloader (grub/systemd)
- The specified filesystem (ext4,btrfs,xfs)
- The specified boot type (BLS/UKI)
- The specified seal state (sealed/unsealed) determining whether we sign the UKI and
use secure boot or not
Use `just build-sealed` as shortcut to build a sealed composefs image with systemd-boot as the bootloader
Current Configuration
=====================
EOF
echo " BOOTC_variant={{variant}}"
echo " BOOTC_base={{base}}"
echo " BOOTC_extra_src={{extra_src}}"
echo ""
# Build a sealed composefs image (alias for variant=composefs-sealeduki-sdboot)
[group('core')]
build-sealed:
@just --justfile {{justfile()}} variant=composefs bootloader=systemd boot_type=uki seal_state=sealed build
# Run tmt integration tests in VMs (e.g. `just test-tmt readonly`)
[group('core')]
test-tmt *ARGS: build
@just _build-upgrade-image
@just test-tmt-nobuild {{ARGS}}
# Split out from `test-container` because, unlike the container integration tests,
# unit tests don't depend on variant/filesystem/bootloader/boot_type/seal_state, so
# CI runs this once per OS (in the `package` job) instead of once per test-integration
# matrix leg.
# Run the unit test suite in a container (needs e.g. ostree/composefs shared libs)
[group('core')]
unit-tests: build-units
podman run --rm --read-only localhost/bootc-units /usr/bin/bootc-units
# Used by CI's test-integration matrix, where unit tests already ran once per OS.
# Run the container integration tests only (no unit tests)
[group('core')]
test-container-integration: build
podman run --rm --env=BOOTC_variant={{variant}} --env=BOOTC_base={{base}} --env=BOOTC_boot_type={{boot_type}} --mount=type=image,source={{base_img}},target=/run/target {{base_img}} bootc-integration-tests container
# Run containerized unit and integration tests
[group('core')]
test-container: unit-tests test-container-integration
[group('core')]
test-composefs bootloader filesystem boot_type seal_state *ARGS:
@if [ "{{seal_state}}" = "sealed" ] && [ "{{filesystem}}" = "xfs" ]; then \
echo "Invalid combination: sealed requires filesystem that supports fs-verity (ext4, btrfs)"; \
exit 1; \
fi
@if [ "{{seal_state}}" = "sealed" ] && [ "{{boot_type}}" != "uki" ]; then \
echo "Invalid combination: sealed requires boot_type=uki"; \
exit 1; \
fi
just variant=composefs \
bootloader={{bootloader}} \
filesystem={{filesystem}} \
boot_type={{boot_type}} \
seal_state={{seal_state}} \
test-tmt --composefs-backend \
--bootloader={{bootloader}} \
--filesystem={{filesystem}} \
--seal-state={{seal_state}} \
--boot-type={{boot_type}} \
{{ARGS}} \
$(if [ "{{boot_type}}" = "uki" ] && [ "{{seal_state}}" = "sealed" ]; then echo "readonly image-upgrade-reboot"; else echo "integration"; fi)
# Run upgrade test: boot VM from published base image (with tmt deps added),
# upgrade to locally-built image, reboot, then run readonly tests to verify.
# The --upgrade-image flag triggers --bind-storage-ro in bcvk, making the
# locally-built image available inside the VM via containers-storage transport.
[group('core')]
test-upgrade *ARGS: build _build-upgrade-source-image
#!/bin/bash
set -xeuo pipefail
composefs_args=()
if [[ "{{variant}}" = composefs ]]; then
composefs_args=(--composefs-backend \
--bootloader={{bootloader}} \
--filesystem={{filesystem}} \
--seal-state={{seal_state}} \
--boot-type={{boot_type}} \
--karg=enforcing=0)
fi
cargo xtask run-tmt --env=BOOTC_variant={{variant}} \
--env=BOOTC_test_upgrade_image={{base_img}} \
--upgrade-image={{base_img}} \
"${composefs_args[@]}" \
{{upgrade_source_img}} {{ARGS}} readonly
# Run all validation checks: tmt plan staleness (local), then fmt/clippy/man/schema (container)
[group('core')]
validate:
cargo xtask update-generated direct --check
podman build {{base_buildargs}} --target validate-post-build .
# Test container export via Anaconda liveimg install in a QEMU VM
[group('testing')]
test-container-export: build
#!/bin/bash
set -xeuo pipefail
iso=target/anaconda-test/boot.iso
if [ ! -f "$iso" ]; then
# Determine the ISO download URL from the base image's os-release
eval $(podman run --rm {{base_img}} bash -c '. /etc/os-release && echo "ID=$ID VERSION_ID=$VERSION_ID"')
case "${ID}-${VERSION_ID}" in
centos-10)
url="https://mirror.stream.centos.org/10-stream/BaseOS/x86_64/iso/CentOS-Stream-10-latest-x86_64-boot.iso" ;;
fedora-*)
url="https://download.fedoraproject.org/pub/fedora/linux/releases/${VERSION_ID}/Everything/x86_64/iso/Fedora-Everything-netinst-x86_64-${VERSION_ID}-1.1.iso" ;;
*)
echo "Unsupported OS: ${ID}-${VERSION_ID}" >&2; exit 1 ;;
esac
mkdir -p target/anaconda-test
curl -L --retry 3 --progress-bar -o "$iso" "$url"
fi
cargo run -p tests-integration -- anaconda-test --iso "$iso" {{base_img}}
# ============================================================================
# Testing variants and utilities
# ============================================================================
# Run tmt tests without rebuilding (for fast iteration)
[group('testing')]
test-tmt-nobuild *ARGS:
cargo xtask run-tmt --env=BOOTC_variant={{variant}} {{_baseconfigs_env}} --upgrade-image={{upgrade_img}} {{base_img}} {{ARGS}}
# Run readonly tests with a baseconfig baked into the image at build time.
# Requires composefs variant. Example: just variant=composefs test-tmt-baseconfig root-transient
[group('testing')]
test-tmt-baseconfig baseconfig *ARGS:
just variant=composefs baseconfigs={{baseconfig}} build
just variant=composefs baseconfigs={{baseconfig}} _build-upgrade-image
cargo xtask run-tmt \
--env=BOOTC_variant=composefs \
--env=BOOTC_baseconfigs={{baseconfig}} \
--upgrade-image={{upgrade_img}} \
--composefs-backend \
--bootloader={{bootloader}} \
--filesystem={{filesystem}} \
--boot-type={{boot_type}} \
--seal-state={{seal_state}} \
{{base_img}} readonly {{ARGS}}
# Run readonly tests for all standard baseconfigs
[group('testing')]
test-baseconfigs *ARGS:
just test-tmt-baseconfig etc-transient {{ARGS}}
just test-tmt-baseconfig root-transient {{ARGS}}
just test-tmt-baseconfig var-volatile {{ARGS}}
# Run tmt tests on Fedora CoreOS
[group('testing')]
test-tmt-on-coreos *ARGS:
cargo xtask run-tmt --env=BOOTC_variant={{variant}} --env=BOOTC_target={{base_img}}-coreos:latest {{fedora-coreos}} {{ARGS}}
# Run external container tests against localhost/bootc
[group('testing')]
run-container-external-tests:
./tests/container/run {{base_img}}
# Remove all test VMs created by tmt tests
[group('testing')]
tmt-vm-cleanup:
bcvk libvirt rm --stop --force --label bootc.test=1
# Build test image for Fedora CoreOS testing
[group('testing')]
build-testimage-coreos PATH: _keygen
#!/bin/bash
set -xeuo pipefail
pkg_path=$(realpath "{{PATH}}")
podman build --build-context "packages=${pkg_path}" \
--build-arg SKIP_CONFIGS=1 \
-t {{base_img}}-coreos {{buildargs}} .
# Build test image for install tests (used by CI)
[group('testing')]
build-install-test-image: build
cd hack && podman build {{base_buildargs}} -t {{base_img}}-install -f Containerfile.drop-lbis
# ============================================================================
# Documentation
# ============================================================================
# Serve docs locally (prints URL)
[group('docs')]
mdbook-serve: build-mdbook
#!/bin/bash
set -xeuo pipefail
podman run --init --replace -d --name bootc-mdbook --rm --publish 127.0.0.1::8000 localhost/bootc-mdbook
echo http://$(podman port bootc-mdbook 8000/tcp)
# Build the documentation (mdbook)
[group('docs')]
build-mdbook:
#!/bin/bash
set -xeuo pipefail
secret_arg=""
if test -n "${GH_TOKEN:-}"; then
secret_arg="--secret=id=GH_TOKEN,env=GH_TOKEN"
fi
podman build {{generic_buildargs}} ${secret_arg} -t localhost/bootc-mdbook -f docs/Dockerfile.mdbook .
# Build docs and extract to DIR
[group('docs')]
build-mdbook-to DIR: build-mdbook
#!/bin/bash
set -xeuo pipefail
container_id=$(podman create localhost/bootc-mdbook)
podman cp ${container_id}:/src/docs/book {{DIR}}
podman rm -f ${container_id}
# ============================================================================
# Debugging and validation
# ============================================================================
# Validate composefs digests match between build and install views
[group('debugging')]
validate-composefs-digest:
cargo xtask validate-composefs-digest {{base_img}}
# Verify reproducible builds (runs package twice, compares output)
[group('debugging')]
check-buildsys:
cargo run -p xtask check-buildsys
# Get container image pullspec for a given OS (e.g. `pullspec-for-os base fedora-42`)
[group('debugging')]
pullspec-for-os TYPE NAME:
@jq -r --arg v "{{NAME}}" '."{{TYPE}}"[$v]' < hack/os-image-map.json
# ============================================================================
# Maintenance
# ============================================================================
# Update generated files (man pages, JSON schemas)
# tmt plans are updated directly; man pages + JSON schemas are regenerated
# inside a container (so ostree is available) and written back via --output.
[group('maintenance')]
update-generated:
cargo xtask update-generated direct
podman build {{base_buildargs}} --target update-generated-from-code-output --output type=local,dest=. .
# Remove all locally-built test container images
[group('maintenance')]
clean-local-images:
podman images --filter "label={{testimage_label}}"
podman images --filter "label={{testimage_label}}" --format "{{{{.ID}}" | xargs -r podman rmi -f
podman image prune -f
podman rmi {{fedora-coreos}} -f
# Build packages (RPM) into target/packages/
[group('maintenance')]
package:
#!/bin/bash
set -xeuo pipefail
packages=target/packages
if test -n "${BOOTC_SKIP_PACKAGE:-}"; then
if test '!' -d "${packages}"; then
echo "BOOTC_SKIP_PACKAGE is set, but missing ${packages}" 1>&2; exit 1
fi
exit 0
fi
eval $(just _git-build-vars)
echo "Building RPM with version: ${VERSION}"
# Auto-detect local Rust path dependencies (e.g., from [patch] sections)
local_deps_args=""
if [[ -z "{{no_auto_local_deps}}" ]]; then
local_deps_args=$(cargo xtask local-rust-deps)
fi
podman build {{base_buildargs}} --build-arg=SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH} --build-arg=pkgversion=${VERSION} -t localhost/bootc-pkg --target=build $local_deps_args .
mkdir -p "${packages}"
rm -vf "${packages}"/*.rpm
podman run --rm localhost/bootc-pkg tar -C /out/ -cf - . | tar -C "${packages}"/ -xvf -
chmod a+rx target "${packages}"
chmod a+r "${packages}"/*.rpm
# Build unit tests into a container image
[group('maintenance')]
build-units:
#!/bin/bash
set -xeuo pipefail
eval $(just _git-build-vars)
podman build {{base_buildargs}} --build-arg=SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH} --build-arg=pkgversion=${VERSION} --target units -t localhost/bootc-units .
# ============================================================================
# Development VM workflow (sysext-based)
# ============================================================================
# Build a systemd-sysext via the container build (binary only, for fast iteration)
[group('dev')]
sysext:
contrib/packaging/build-container-stage sysext target/sysext \
{{base_buildargs}} $(just _local-deps-args)
# ============================================================================
# Internal helpers (prefixed with _)
# ============================================================================
_pull-lbi-images:
podman pull -q --retry 5 --retry-delay 5s {{lbi_images}}
_git-build-vars:
#!/bin/bash
set -euo pipefail
SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
if VERSION=$(git describe --tags --exact-match 2>/dev/null); then
VERSION="${VERSION#v}"
VERSION="${VERSION//-/.}"
else
COMMIT=$(git rev-parse HEAD | cut -c1-10)
COMMIT_TS=$(git show -s --format=%ct)
TIMESTAMP=$(date -u -d @${COMMIT_TS} +%Y%m%d%H%M)
VERSION="${TIMESTAMP}.g${COMMIT}"
fi
echo "SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH}"
echo "VERSION=${VERSION}"
_local-deps-args:
#!/bin/bash
set -euo pipefail
if [[ -z "{{no_auto_local_deps}}" ]]; then
cargo xtask local-rust-deps
fi
_keygen:
./hack/generate-secureboot-keys
_build-upgrade-image:
#!/bin/bash
set -xeuo pipefail
# Secrets are always available (test-tmt depends on build which runs _keygen).
# Extra capabilities are only needed for UKI builds (composefs + fuse).
extra_args=()
if [ "{{boot_type}}" = "uki" ]; then
extra_args+=(--cap-add=all --security-opt=label=type:container_runtime_t --device /dev/fuse)
fi
podman build \
--build-arg "boot_type={{boot_type}}" \
--build-arg "seal_state={{seal_state}}" \
--build-arg "filesystem={{filesystem}}" \
--secret=id=secureboot_key,src=target/test-secureboot/db.key \
--secret=id=secureboot_cert,src=target/test-secureboot/db.crt \
"${extra_args[@]}" \
-t {{upgrade_img}} \
-f tmt/tests/Dockerfile.upgrade \
.
# Build the upgrade source image: base image + tmt dependencies (rsync, nu, cloud-init)
_build-upgrade-source-image:
podman build --build-arg=base={{base}} --build-arg=variant={{variant}} -t {{upgrade_source_img}} -f tmt/tests/Dockerfile.upgrade-source .
# Copy an image from user podman storage to root's podman storage
# This allows building as regular user then running privileged tests
[group('testing')]
copy-to-rootful $image:
#!/bin/bash
set -euxo pipefail
# If already running as root, nothing to do
if [[ "${UID}" -eq "0" ]]; then
echo "Already root, no need to copy image"
exit 0
fi
# Check if the image exists in user storage
if ! podman image exists "${image}"; then
echo "Image ${image} not found in user podman storage" >&2
exit 1
fi
# Get the image ID from user storage
USER_IMG_ID=$(podman images --filter reference="${image}" --format '{{{{.ID}}')
# Check if the same image ID exists in root storage
ROOT_IMG_ID=$(sudo podman images --filter reference="${image}" --format '{{{{.ID}}' 2>/dev/null || true)
if [[ "${USER_IMG_ID}" == "${ROOT_IMG_ID}" ]] && [[ -n "${ROOT_IMG_ID}" ]]; then
echo "Image ${image} already exists in root storage with same ID"
exit 0
fi
# Copy the image from user to root storage
# Use podman save/load via pipe (works on systems without machinectl)
podman save "${image}" | sudo podman load
echo "Copied ${image} to root podman storage"
# Copy all LBI (bound) images to root's podman storage
[group('testing')]
copy-lbi-to-rootful:
#!/bin/bash
set -euxo pipefail
for img in {{lbi_images}}; do
just copy-to-rootful "$img"
done