diff options
| author | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-05-10 13:58:27 +0000 |
|---|---|---|
| committer | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-05-10 13:58:27 +0000 |
| commit | af92db59a7d2367528bc86cb37d969e87ef36659 (patch) | |
| tree | 605b5ae55979b6b3c3c89ed83802d01cad5e2870 | |
| parent | 3d431848b32caae67b9dcbf7fe04f08ddcd448b5 (diff) | |
| download | meta-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-x | recipes-containers/vcontainer/files/vcontainer-common.sh | 71 |
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 |
| 333 | is_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 | ||
| 365 | is_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") |
| 356 | get_oci_platforms() { | 372 | get_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) |
