summaryrefslogtreecommitdiffstats
path: root/recipes-containers/vcontainer/files
diff options
context:
space:
mode:
authorBruce Ashfield <bruce.ashfield@gmail.com>2026-01-15 21:50:38 +0000
committerBruce Ashfield <bruce.ashfield@gmail.com>2026-01-21 18:00:26 -0500
commit02bce5b72e8725ba58d82627c780e376ac59a84b (patch)
tree8e01635166b3d475fbf4fef34150ccde7ccda21a /recipes-containers/vcontainer/files
parent640fc9278435c49b8c59d78c18d024c66b3d6e6a (diff)
downloadmeta-virtualization-02bce5b72e8725ba58d82627c780e376ac59a84b.tar.gz
vcontainer: add multi-arch OCI support
Add functions to detect and handle multi-architecture OCI Image Index format with automatic platform selection during import. Also add oci-multiarch.bbclass for build-time multi-arch OCI creation. Runtime support (vcontainer-common.sh): - is_oci_image_index() - detect multi-arch OCI images - get_oci_platforms() - list available platforms - select_platform_manifest() - select manifest for target architecture - extract_platform_oci() - extract single platform to new OCI dir - normalize_arch_to_oci/from_oci() - architecture name mapping - Update vimport to auto-select platform from multi-arch images Build-time support (oci-multiarch.bbclass): - Create OCI Image Index from multiconfig builds - Collect images from vruntime-aarch64, vruntime-x86-64 - Combine blobs and create unified manifest list Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
Diffstat (limited to 'recipes-containers/vcontainer/files')
-rwxr-xr-xrecipes-containers/vcontainer/files/vcontainer-common.sh243
1 files changed, 239 insertions, 4 deletions
diff --git a/recipes-containers/vcontainer/files/vcontainer-common.sh b/recipes-containers/vcontainer/files/vcontainer-common.sh
index ddcb674f..39e57b13 100755
--- a/recipes-containers/vcontainer/files/vcontainer-common.sh
+++ b/recipes-containers/vcontainer/files/vcontainer-common.sh
@@ -296,6 +296,196 @@ check_oci_arch() {
296 return 0 296 return 0
297} 297}
298 298
299# ============================================================================
300# Multi-Architecture OCI Support
301# ============================================================================
302# OCI Image Index (manifest list) format allows multiple platform-specific
303# images under a single tag. These functions detect and handle multi-arch OCI.
304# ============================================================================
305
306# Normalize architecture name to OCI convention
307# Usage: normalize_arch_to_oci <arch>
308# Returns: OCI-format architecture (arm64, amd64, etc.)
309normalize_arch_to_oci() {
310 local arch="$1"
311 case "$arch" in
312 aarch64) echo "arm64" ;;
313 x86_64) echo "amd64" ;;
314 *) echo "$arch" ;;
315 esac
316}
317
318# Normalize OCI architecture to Yocto/Linux convention
319# Usage: normalize_arch_from_oci <arch>
320# Returns: Linux-format architecture (aarch64, x86_64, etc.)
321normalize_arch_from_oci() {
322 local arch="$1"
323 case "$arch" in
324 arm64) echo "aarch64" ;;
325 amd64) echo "x86_64" ;;
326 *) echo "$arch" ;;
327 esac
328}
329
330# Check if OCI directory contains a multi-architecture Image Index
331# Usage: is_oci_image_index <oci_dir>
332# Returns: 0 if multi-arch, 1 if single-arch or not OCI
333is_oci_image_index() {
334 local oci_dir="$1"
335
336 [ -f "$oci_dir/index.json" ] || return 1
337
338 # Check if index.json has manifests with platform info
339 # Multi-arch images have "platform" object in manifest entries
340 if grep -q '"platform"' "$oci_dir/index.json" 2>/dev/null; then
341 # Also verify there are multiple manifests
342 local manifest_count=$(grep -c '"digest"' "$oci_dir/index.json" 2>/dev/null || echo "0")
343 [ "$manifest_count" -gt 1 ] && return 0
344
345 # Single manifest with platform info is also a valid Image Index
346 # (could be a multi-arch image built with only one arch so far)
347 return 0
348 fi
349
350 return 1
351}
352
353# Get list of available platforms in a multi-arch OCI Image Index
354# Usage: get_oci_platforms <oci_dir>
355# Returns: Space-separated list of architectures (e.g., "arm64 amd64")
356get_oci_platforms() {
357 local oci_dir="$1"
358
359 [ -f "$oci_dir/index.json" ] || return 1
360
361 # Extract architecture values from platform objects
362 # Format: "platform": { "architecture": "arm64", "os": "linux" }
363 grep -o '"architecture"[[:space:]]*:[[:space:]]*"[^"]*"' "$oci_dir/index.json" 2>/dev/null | \
364 sed 's/.*"\([^"]*\)"$/\1/' | \
365 tr '\n' ' ' | sed 's/ $//'
366}
367
368# Select manifest digest for a specific platform from OCI Image Index
369# Usage: select_platform_manifest <oci_dir> <target_arch>
370# Returns: sha256 digest of the matching manifest (without "sha256:" prefix)
371# Sets OCI_SELECTED_PLATFORM to the matched platform for informational purposes
372select_platform_manifest() {
373 local oci_dir="$1"
374 local target_arch="$2"
375
376 [ -f "$oci_dir/index.json" ] || return 1
377
378 # Normalize target arch to OCI convention
379 local oci_arch=$(normalize_arch_to_oci "$target_arch")
380
381 # Parse index.json to find manifest with matching platform
382 # This is done without jq using grep/sed for portability
383 local in_manifest=0
384 local current_digest=""
385 local current_arch=""
386 local matched_digest=""
387
388 # Read index.json line by line
389 while IFS= read -r line; do
390 # Track when we're inside a manifest entry
391 if echo "$line" | grep -q '"manifests"'; then
392 in_manifest=1
393 continue
394 fi
395
396 if [ "$in_manifest" = "1" ]; then
397 # Extract digest
398 if echo "$line" | grep -q '"digest"'; then
399 current_digest=$(echo "$line" | sed 's/.*"sha256:\([a-f0-9]*\)".*/\1/')
400 fi
401
402 # Extract architecture from platform
403 # Handle both formats: "architecture": "arm64" or {"architecture": "arm64", ...}
404 if echo "$line" | grep -q '"architecture"'; then
405 current_arch=$(echo "$line" | sed 's/.*"architecture"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
406
407 # Check if this matches our target
408 if [ "$current_arch" = "$oci_arch" ]; then
409 matched_digest="$current_digest"
410 OCI_SELECTED_PLATFORM="$current_arch"
411 break
412 fi
413 fi
414
415 # Reset on closing brace (end of manifest entry)
416 if echo "$line" | grep -q '^[[:space:]]*}'; then
417 current_digest=""
418 current_arch=""
419 fi
420 fi
421 done < "$oci_dir/index.json"
422
423 if [ -n "$matched_digest" ]; then
424 echo "$matched_digest"
425 return 0
426 fi
427
428 return 1
429}
430
431# Extract a single platform from multi-arch OCI to a new OCI directory
432# Usage: extract_platform_oci <src_oci_dir> <dest_oci_dir> <manifest_digest>
433# This creates a single-arch OCI directory that skopeo can import
434extract_platform_oci() {
435 local src_dir="$1"
436 local dest_dir="$2"
437 local manifest_digest="$3"
438
439 mkdir -p "$dest_dir/blobs/sha256"
440
441 # Copy the manifest blob
442 cp "$src_dir/blobs/sha256/$manifest_digest" "$dest_dir/blobs/sha256/"
443
444 # Read the manifest to get config and layer digests
445 local manifest_file="$src_dir/blobs/sha256/$manifest_digest"
446
447 # Extract and copy config blob
448 local config_digest=$(grep -o '"config"[[:space:]]*:[[:space:]]*{[^}]*"digest"[[:space:]]*:[[:space:]]*"sha256:[a-f0-9]*"' "$manifest_file" | \
449 sed 's/.*sha256:\([a-f0-9]*\)".*/\1/')
450 if [ -n "$config_digest" ] && [ -f "$src_dir/blobs/sha256/$config_digest" ]; then
451 cp "$src_dir/blobs/sha256/$config_digest" "$dest_dir/blobs/sha256/"
452 fi
453
454 # Extract and copy layer blobs
455 grep -o '"digest"[[:space:]]*:[[:space:]]*"sha256:[a-f0-9]*"' "$manifest_file" | \
456 sed 's/.*sha256:\([a-f0-9]*\)".*/\1/' | while read -r layer_digest; do
457 if [ -f "$src_dir/blobs/sha256/$layer_digest" ]; then
458 cp "$src_dir/blobs/sha256/$layer_digest" "$dest_dir/blobs/sha256/"
459 fi
460 done
461
462 # Get manifest size
463 local manifest_size=$(stat -c%s "$manifest_file" 2>/dev/null || stat -f%z "$manifest_file" 2>/dev/null)
464
465 # Create new index.json pointing to just this manifest
466 cat > "$dest_dir/index.json" << EOF
467{
468 "schemaVersion": 2,
469 "manifests": [
470 {
471 "mediaType": "application/vnd.oci.image.manifest.v1+json",
472 "digest": "sha256:$manifest_digest",
473 "size": $manifest_size
474 }
475 ]
476}
477EOF
478
479 # Copy oci-layout
480 if [ -f "$src_dir/oci-layout" ]; then
481 cp "$src_dir/oci-layout" "$dest_dir/"
482 else
483 echo '{"imageLayoutVersion": "1.0.0"}' > "$dest_dir/oci-layout"
484 fi
485
486 return 0
487}
488
299show_usage() { 489show_usage() {
300 local PROG_NAME=$(basename "$0") 490 local PROG_NAME=$(basename "$0")
301 local RUNTIME_UPPER=$(echo "$VCONTAINER_RUNTIME_CMD" | sed 's/./\U&/') 491 local RUNTIME_UPPER=$(echo "$VCONTAINER_RUNTIME_CMD" | sed 's/./\U&/')
@@ -348,6 +538,7 @@ ${BOLD}${RUNTIME_UPPER}-COMPATIBLE COMMANDS:${NC}
348 538
349${BOLD}EXTENDED COMMANDS (${VCONTAINER_RUNTIME_NAME}-specific):${NC} 539${BOLD}EXTENDED COMMANDS (${VCONTAINER_RUNTIME_NAME}-specific):${NC}
350 ${CYAN}vimport${NC} <path> [name:tag] Import from OCI dir, tarball, or directory (auto-detect) 540 ${CYAN}vimport${NC} <path> [name:tag] Import from OCI dir, tarball, or directory (auto-detect)
541 Multi-arch OCI Image Index supported (auto-selects platform)
351 ${CYAN}vrun${NC} [opts] <image> [cmd] Run command, clearing entrypoint (see RUN vs VRUN below) 542 ${CYAN}vrun${NC} [opts] <image> [cmd] Run command, clearing entrypoint (see RUN vs VRUN below)
352 ${CYAN}vstorage${NC} List all storage directories (alias: vstorage list) 543 ${CYAN}vstorage${NC} List all storage directories (alias: vstorage list)
353 ${CYAN}vstorage list${NC} List all storage directories with details 544 ${CYAN}vstorage list${NC} List all storage directories with details
@@ -425,6 +616,7 @@ ${BOLD}GLOBAL OPTIONS:${NC}
425 --no-kvm Disable KVM acceleration (use TCG emulation) 616 --no-kvm Disable KVM acceleration (use TCG emulation)
426 --no-daemon Run in ephemeral mode (don't auto-start/use daemon) 617 --no-daemon Run in ephemeral mode (don't auto-start/use daemon)
427 --registry <url> Default registry for unqualified images (e.g., 10.0.2.2:5000/yocto) 618 --registry <url> Default registry for unqualified images (e.g., 10.0.2.2:5000/yocto)
619 --no-registry Disable baked-in default registry (use images as-is)
428 --insecure-registry <host:port> Mark registry as insecure (HTTP). Can repeat. 620 --insecure-registry <host:port> Mark registry as insecure (HTTP). Can repeat.
429 --verbose, -v Enable verbose output 621 --verbose, -v Enable verbose output
430 --help, -h Show this help 622 --help, -h Show this help
@@ -635,6 +827,11 @@ while [ $# -gt 0 ]; do
635 REGISTRY="$2" 827 REGISTRY="$2"
636 shift 2 828 shift 2
637 ;; 829 ;;
830 --no-registry)
831 # Explicitly disable baked-in registry (passes docker_registry=none to init)
832 REGISTRY="none"
833 shift
834 ;;
638 --insecure-registry) 835 --insecure-registry)
639 INSECURE_REGISTRIES+=("$2") 836 INSECURE_REGISTRIES+=("$2")
640 shift 2 837 shift 2
@@ -1322,15 +1519,53 @@ case "$COMMAND" in
1322 if [ -d "$INPUT_PATH" ]; then 1519 if [ -d "$INPUT_PATH" ]; then
1323 if [ -f "$INPUT_PATH/index.json" ] || [ -f "$INPUT_PATH/oci-layout" ]; then 1520 if [ -f "$INPUT_PATH/index.json" ] || [ -f "$INPUT_PATH/oci-layout" ]; then
1324 INPUT_TYPE="oci" 1521 INPUT_TYPE="oci"
1522 ACTUAL_OCI_PATH="$INPUT_PATH"
1523
1524 # Check for multi-architecture OCI Image Index
1525 if is_oci_image_index "$INPUT_PATH"; then
1526 local available_platforms=$(get_oci_platforms "$INPUT_PATH")
1527 [ "$VERBOSE" = "true" ] && echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Multi-arch OCI detected. Available: $available_platforms" >&2
1528
1529 # Select manifest for target architecture
1530 local manifest_digest=$(select_platform_manifest "$INPUT_PATH" "$TARGET_ARCH")
1531 if [ -z "$manifest_digest" ]; then
1532 echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Architecture $TARGET_ARCH not found in multi-arch image" >&2
1533 echo -e "${YELLOW}[$VCONTAINER_RUNTIME_NAME]${NC} Available platforms: $available_platforms" >&2
1534 echo -e "${YELLOW}[$VCONTAINER_RUNTIME_NAME]${NC} Use --arch <arch> to select a different platform" >&2
1535 exit 1
1536 fi
1325 1537
1326 # Check architecture before importing 1538 echo -e "${GREEN}[$VCONTAINER_RUNTIME_NAME]${NC} Selected platform: $OCI_SELECTED_PLATFORM (from multi-arch image)" >&2
1327 if ! check_oci_arch "$INPUT_PATH" "$TARGET_ARCH"; then 1539
1328 exit 1 1540 # Extract single-platform OCI to temp directory
1541 TEMP_OCI_DIR=$(mktemp -d)
1542 trap "rm -rf '$TEMP_OCI_DIR'" EXIT
1543
1544 if ! extract_platform_oci "$INPUT_PATH" "$TEMP_OCI_DIR" "$manifest_digest"; then
1545 echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Failed to extract platform from multi-arch image" >&2
1546 exit 1
1547 fi
1548
1549 ACTUAL_OCI_PATH="$TEMP_OCI_DIR"
1550 [ "$VERBOSE" = "true" ] && echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Extracted to: $TEMP_OCI_DIR" >&2
1551 else
1552 # Single-arch OCI - check architecture before importing
1553 if ! check_oci_arch "$INPUT_PATH" "$TARGET_ARCH"; then
1554 exit 1
1555 fi
1329 fi 1556 fi
1330 1557
1331 # Use skopeo to properly import OCI image with full metadata (entrypoint, cmd, etc.) 1558 # Use skopeo to properly import OCI image with full metadata (entrypoint, cmd, etc.)
1332 # This preserves the container config unlike raw import 1559 # This preserves the container config unlike raw import
1333 RUNTIME_CMD="skopeo copy oci:{INPUT} ${VCONTAINER_IMPORT_TARGET}$IMAGE_NAME && $VCONTAINER_RUNTIME_CMD images" 1560 # For multi-arch, we import from the extracted temp directory
1561 if [ "$ACTUAL_OCI_PATH" = "$INPUT_PATH" ]; then
1562 RUNTIME_CMD="skopeo copy oci:{INPUT} ${VCONTAINER_IMPORT_TARGET}$IMAGE_NAME && $VCONTAINER_RUNTIME_CMD images"
1563 else
1564 # Multi-arch: copy extracted OCI to share dir for import
1565 # We need to handle this specially since INPUT_PATH differs from actual OCI
1566 INPUT_PATH="$ACTUAL_OCI_PATH"
1567 RUNTIME_CMD="skopeo copy oci:{INPUT} ${VCONTAINER_IMPORT_TARGET}$IMAGE_NAME && $VCONTAINER_RUNTIME_CMD images"
1568 fi
1334 else 1569 else
1335 # Directory but not OCI - check if it looks like a deploy/images dir 1570 # Directory but not OCI - check if it looks like a deploy/images dir
1336 # and provide a helpful hint 1571 # and provide a helpful hint