summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBruce Ashfield <bruce.ashfield@gmail.com>2026-05-10 13:58:27 +0000
committerBruce Ashfield <bruce.ashfield@gmail.com>2026-05-10 13:58:27 +0000
commitaf92db59a7d2367528bc86cb37d969e87ef36659 (patch)
tree605b5ae55979b6b3c3c89ed83802d01cad5e2870
parent3d431848b32caae67b9dcbf7fe04f08ddcd448b5 (diff)
downloadmeta-virtualization-af92db59a7d2367528bc86cb37d969e87ef36659.tar.gz
vcontainer-common: support nested OCI layout and fix vimport shell errors
The multi-arch OCI functions (is_oci_image_index, get_oci_platforms, select_platform_manifest) only checked index.json directly for platform information. With the skopeo-compatible nested OCI layout — where index.json references a single image index blob that in turn contains the per-platform manifests — the functions failed to detect multi-arch images because index.json no longer contains platform entries. Add _resolve_oci_platform_file() helper that handles both layouts: - Flat: platform info directly in index.json (legacy/simple case) - Nested: index.json → image index blob → platform manifests All three multi-arch functions now use this single helper, eliminating the layout resolution logic that would otherwise be duplicated in each. Also fixes two issues in the vimport case block: - 'local' keyword used outside a function (bash error on line 1879). The vimport handler is in a case statement in the main script body, not inside a function, so 'local' is invalid. The original multi-arch code was written assuming it would be inside a function. - OCI_SELECTED_PLATFORM was blank in output because select_platform_manifest sets it inside a $() subshell, where variable assignments are lost. Use normalize_arch_to_oci directly for the display message instead. Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
-rwxr-xr-xrecipes-containers/vcontainer/files/vcontainer-common.sh71
1 files changed, 40 insertions, 31 deletions
diff --git a/recipes-containers/vcontainer/files/vcontainer-common.sh b/recipes-containers/vcontainer/files/vcontainer-common.sh
index 8adec77d..f389941c 100755
--- a/recipes-containers/vcontainer/files/vcontainer-common.sh
+++ b/recipes-containers/vcontainer/files/vcontainer-common.sh
@@ -327,40 +327,54 @@ normalize_arch_from_oci() {
327 esac 327 esac
328} 328}
329 329
330# Check if OCI directory contains a multi-architecture Image Index 330# Resolve the file containing platform manifests in an OCI directory.
331# Usage: is_oci_image_index <oci_dir> 331# Handles two layouts:
332# Returns: 0 if multi-arch, 1 if single-arch or not OCI 332# Flat: index.json directly contains manifests with platform info
333is_oci_image_index() { 333# Nested: index.json → single image index blob → platform manifests
334# (skopeo-compatible format)
335# Usage: _resolve_oci_platform_file <oci_dir>
336# Prints: path to the file containing platform manifests, or empty
337_resolve_oci_platform_file() {
334 local oci_dir="$1" 338 local oci_dir="$1"
335 339
336 [ -f "$oci_dir/index.json" ] || return 1 340 [ -f "$oci_dir/index.json" ] || return 1
337 341
338 # Check if index.json has manifests with platform info 342 # Flat layout: platform info directly in index.json
339 # Multi-arch images have "platform" object in manifest entries
340 if grep -q '"platform"' "$oci_dir/index.json" 2>/dev/null; then 343 if grep -q '"platform"' "$oci_dir/index.json" 2>/dev/null; then
341 # Also verify there are multiple manifests 344 echo "$oci_dir/index.json"
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 345 return 0
348 fi 346 fi
349 347
348 # Nested layout: index.json has a single entry with image.index mediaType
349 if grep -q 'image\.index' "$oci_dir/index.json" 2>/dev/null; then
350 local index_digest=$(grep -o '"sha256:[a-f0-9]*"' "$oci_dir/index.json" 2>/dev/null | head -1 | tr -d '"' | sed 's/sha256://')
351 if [ -n "$index_digest" ] && [ -f "$oci_dir/blobs/sha256/$index_digest" ]; then
352 if grep -q '"platform"' "$oci_dir/blobs/sha256/$index_digest" 2>/dev/null; then
353 echo "$oci_dir/blobs/sha256/$index_digest"
354 return 0
355 fi
356 fi
357 fi
358
350 return 1 359 return 1
351} 360}
352 361
362# Check if OCI directory contains a multi-architecture Image Index
363# Usage: is_oci_image_index <oci_dir>
364# Returns: 0 if multi-arch, 1 if single-arch or not OCI
365is_oci_image_index() {
366 _resolve_oci_platform_file "$1" >/dev/null 2>&1
367}
368
353# Get list of available platforms in a multi-arch OCI Image Index 369# Get list of available platforms in a multi-arch OCI Image Index
354# Usage: get_oci_platforms <oci_dir> 370# Usage: get_oci_platforms <oci_dir>
355# Returns: Space-separated list of architectures (e.g., "arm64 amd64") 371# Returns: Space-separated list of architectures (e.g., "arm64 amd64")
356get_oci_platforms() { 372get_oci_platforms() {
357 local oci_dir="$1" 373 local oci_dir="$1"
374 local platform_file
375 platform_file=$(_resolve_oci_platform_file "$oci_dir") || return 1
358 376
359 [ -f "$oci_dir/index.json" ] || return 1 377 grep -o '"architecture"[[:space:]]*:[[:space:]]*"[^"]*"' "$platform_file" 2>/dev/null | \
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/' | \ 378 sed 's/.*"\([^"]*\)"$/\1/' | \
365 tr '\n' ' ' | sed 's/ $//' 379 tr '\n' ' ' | sed 's/ $//'
366} 380}
@@ -373,38 +387,34 @@ select_platform_manifest() {
373 local oci_dir="$1" 387 local oci_dir="$1"
374 local target_arch="$2" 388 local target_arch="$2"
375 389
376 [ -f "$oci_dir/index.json" ] || return 1
377
378 # Normalize target arch to OCI convention 390 # Normalize target arch to OCI convention
379 local oci_arch=$(normalize_arch_to_oci "$target_arch") 391 local oci_arch=$(normalize_arch_to_oci "$target_arch")
380 392
381 # Parse index.json to find manifest with matching platform 393 # Resolve the file containing platform manifests (flat or nested)
394 local manifest_index
395 manifest_index=$(_resolve_oci_platform_file "$oci_dir") || return 1
396
397 # Parse the manifest index to find manifest with matching platform
382 # This is done without jq using grep/sed for portability 398 # This is done without jq using grep/sed for portability
383 local in_manifest=0 399 local in_manifest=0
384 local current_digest="" 400 local current_digest=""
385 local current_arch="" 401 local current_arch=""
386 local matched_digest="" 402 local matched_digest=""
387 403
388 # Read index.json line by line
389 while IFS= read -r line; do 404 while IFS= read -r line; do
390 # Track when we're inside a manifest entry
391 if echo "$line" | grep -q '"manifests"'; then 405 if echo "$line" | grep -q '"manifests"'; then
392 in_manifest=1 406 in_manifest=1
393 continue 407 continue
394 fi 408 fi
395 409
396 if [ "$in_manifest" = "1" ]; then 410 if [ "$in_manifest" = "1" ]; then
397 # Extract digest
398 if echo "$line" | grep -q '"digest"'; then 411 if echo "$line" | grep -q '"digest"'; then
399 current_digest=$(echo "$line" | sed 's/.*"sha256:\([a-f0-9]*\)".*/\1/') 412 current_digest=$(echo "$line" | sed 's/.*"sha256:\([a-f0-9]*\)".*/\1/')
400 fi 413 fi
401 414
402 # Extract architecture from platform
403 # Handle both formats: "architecture": "arm64" or {"architecture": "arm64", ...}
404 if echo "$line" | grep -q '"architecture"'; then 415 if echo "$line" | grep -q '"architecture"'; then
405 current_arch=$(echo "$line" | sed 's/.*"architecture"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/') 416 current_arch=$(echo "$line" | sed 's/.*"architecture"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
406 417
407 # Check if this matches our target
408 if [ "$current_arch" = "$oci_arch" ]; then 418 if [ "$current_arch" = "$oci_arch" ]; then
409 matched_digest="$current_digest" 419 matched_digest="$current_digest"
410 OCI_SELECTED_PLATFORM="$current_arch" 420 OCI_SELECTED_PLATFORM="$current_arch"
@@ -412,13 +422,12 @@ select_platform_manifest() {
412 fi 422 fi
413 fi 423 fi
414 424
415 # Reset on closing brace (end of manifest entry)
416 if echo "$line" | grep -q '^[[:space:]]*}'; then 425 if echo "$line" | grep -q '^[[:space:]]*}'; then
417 current_digest="" 426 current_digest=""
418 current_arch="" 427 current_arch=""
419 fi 428 fi
420 fi 429 fi
421 done < "$oci_dir/index.json" 430 done < "$manifest_index"
422 431
423 if [ -n "$matched_digest" ]; then 432 if [ -n "$matched_digest" ]; then
424 echo "$matched_digest" 433 echo "$matched_digest"
@@ -1876,11 +1885,11 @@ case "$COMMAND" in
1876 1885
1877 # Check for multi-architecture OCI Image Index 1886 # Check for multi-architecture OCI Image Index
1878 if is_oci_image_index "$INPUT_PATH"; then 1887 if is_oci_image_index "$INPUT_PATH"; then
1879 local available_platforms=$(get_oci_platforms "$INPUT_PATH") 1888 available_platforms=$(get_oci_platforms "$INPUT_PATH")
1880 [ "$VERBOSE" = "true" ] && echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Multi-arch OCI detected. Available: $available_platforms" >&2 1889 [ "$VERBOSE" = "true" ] && echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Multi-arch OCI detected. Available: $available_platforms" >&2
1881 1890
1882 # Select manifest for target architecture 1891 # Select manifest for target architecture
1883 local manifest_digest=$(select_platform_manifest "$INPUT_PATH" "$TARGET_ARCH") 1892 manifest_digest=$(select_platform_manifest "$INPUT_PATH" "$TARGET_ARCH")
1884 if [ -z "$manifest_digest" ]; then 1893 if [ -z "$manifest_digest" ]; then
1885 echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Architecture $TARGET_ARCH not found in multi-arch image" >&2 1894 echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Architecture $TARGET_ARCH not found in multi-arch image" >&2
1886 echo -e "${YELLOW}[$VCONTAINER_RUNTIME_NAME]${NC} Available platforms: $available_platforms" >&2 1895 echo -e "${YELLOW}[$VCONTAINER_RUNTIME_NAME]${NC} Available platforms: $available_platforms" >&2
@@ -1888,7 +1897,7 @@ case "$COMMAND" in
1888 exit 1 1897 exit 1
1889 fi 1898 fi
1890 1899
1891 echo -e "${GREEN}[$VCONTAINER_RUNTIME_NAME]${NC} Selected platform: $OCI_SELECTED_PLATFORM (from multi-arch image)" >&2 1900 echo -e "${GREEN}[$VCONTAINER_RUNTIME_NAME]${NC} Selected platform: $(normalize_arch_to_oci "$TARGET_ARCH")/linux (from multi-arch image)" >&2
1892 1901
1893 # Extract single-platform OCI to temp directory 1902 # Extract single-platform OCI to temp directory
1894 TEMP_OCI_DIR=$(mktemp -d) 1903 TEMP_OCI_DIR=$(mktemp -d)