summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBruce Ashfield <bruce.ashfield@gmail.com>2026-02-19 19:07:31 +0000
committerBruce Ashfield <bruce.ashfield@gmail.com>2026-02-26 01:05:01 +0000
commit05b30f9e0a8edf59a258a891cf987287b6acb889 (patch)
treefdc1ac630a0cbcf8e1c6800881acd9aec8691b55
parentc118bfe7af02d22efd4f43b19dcb40fbb348d616 (diff)
downloadmeta-virtualization-05b30f9e0a8edf59a258a891cf987287b6acb889.tar.gz
vcontainer: add bundle command for OCI runtime bundle creation
Add 'bundle' command to the vcontainer CLI for creating OCI runtime bundles from container images. Pulls the image via skopeo, extracts layers into rootfs/, resolves entrypoint/cmd/env from OCI config, and generates config.json. Supports command override via -- separator. Only available on the Xen (vxn) backend. Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
-rwxr-xr-xrecipes-containers/vcontainer/files/vcontainer-common.sh118
1 files changed, 118 insertions, 0 deletions
diff --git a/recipes-containers/vcontainer/files/vcontainer-common.sh b/recipes-containers/vcontainer/files/vcontainer-common.sh
index 7f3b8945..ea78b265 100755
--- a/recipes-containers/vcontainer/files/vcontainer-common.sh
+++ b/recipes-containers/vcontainer/files/vcontainer-common.sh
@@ -539,6 +539,7 @@ ${BOLD}${RUNTIME_UPPER}-COMPATIBLE COMMANDS:${NC}
539${BOLD}EXTENDED COMMANDS (${VCONTAINER_RUNTIME_NAME}-specific):${NC} 539${BOLD}EXTENDED COMMANDS (${VCONTAINER_RUNTIME_NAME}-specific):${NC}
540 ${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) 541 Multi-arch OCI Image Index supported (auto-selects platform)
542 ${CYAN}bundle${NC} <image> <dir> [-- cmd] Create OCI bundle from image (for vxn-oci-runtime)
542 ${CYAN}vrun${NC} [opts] <image> [cmd] Run command, clearing entrypoint (see RUN vs VRUN below) 543 ${CYAN}vrun${NC} [opts] <image> [cmd] Run command, clearing entrypoint (see RUN vs VRUN below)
543 ${CYAN}vstorage${NC} List all storage directories (alias: vstorage list) 544 ${CYAN}vstorage${NC} List all storage directories (alias: vstorage list)
544 ${CYAN}vstorage list${NC} List all storage directories with details 545 ${CYAN}vstorage list${NC} List all storage directories with details
@@ -3083,6 +3084,123 @@ case "$COMMAND" in
3083 esac 3084 esac
3084 ;; 3085 ;;
3085 3086
3087 bundle)
3088 # Create an OCI runtime bundle from a container image.
3089 # Usage: <tool> bundle <image> <output-dir> [-- <cmd> ...]
3090 #
3091 # Pulls the image via skopeo, extracts layers into rootfs/,
3092 # and generates config.json from the OCI image config.
3093 # Optional command after -- overrides the image's default entrypoint.
3094 # The resulting bundle can be passed to vxn-oci-runtime create --bundle.
3095 [ "${VCONTAINER_HYPERVISOR:-}" = "xen" ] || {
3096 echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} bundle is only supported for Xen (vxn)" >&2
3097 exit 1
3098 }
3099
3100 if [ ${#COMMAND_ARGS[@]} -lt 2 ]; then
3101 echo "Usage: $VCONTAINER_RUNTIME_NAME bundle <image> <output-dir> [-- <cmd> ...]" >&2
3102 echo "" >&2
3103 echo "Creates an OCI runtime bundle from a container image." >&2
3104 echo "The bundle can then be used with vxn-oci-runtime:" >&2
3105 echo "" >&2
3106 echo " $VCONTAINER_RUNTIME_NAME bundle alpine /tmp/test-bundle -- /bin/echo hello" >&2
3107 echo " vxn-oci-runtime create --bundle /tmp/test-bundle --pid-file /tmp/t.pid test1" >&2
3108 echo " vxn-oci-runtime start test1" >&2
3109 echo " vxn-oci-runtime state test1" >&2
3110 echo " vxn-oci-runtime delete test1" >&2
3111 exit 1
3112 fi
3113
3114 BUNDLE_IMAGE="${COMMAND_ARGS[0]}"
3115 BUNDLE_DIR="${COMMAND_ARGS[1]}"
3116
3117 # Parse optional command override after --
3118 BUNDLE_CMD_OVERRIDE=()
3119 _bundle_found_sep=false
3120 for _ba in "${COMMAND_ARGS[@]:2}"; do
3121 if [ "$_bundle_found_sep" = "true" ]; then
3122 BUNDLE_CMD_OVERRIDE+=("$_ba")
3123 elif [ "$_ba" = "--" ]; then
3124 _bundle_found_sep=true
3125 fi
3126 done
3127
3128 # Check prerequisites
3129 command -v skopeo >/dev/null 2>&1 || {
3130 echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} skopeo not found (needed for image pull)" >&2
3131 exit 1
3132 }
3133 command -v jq >/dev/null 2>&1 || {
3134 echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} jq not found (needed for OCI config parsing)" >&2
3135 exit 1
3136 }
3137
3138 # Pull image via skopeo
3139 BUNDLE_TMP=$(mktemp -d)
3140 trap 'rm -rf "$BUNDLE_TMP"' EXIT
3141 BUNDLE_OCI="$BUNDLE_TMP/oci"
3142
3143 echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Pulling $BUNDLE_IMAGE..."
3144 if ! skopeo copy "docker://$BUNDLE_IMAGE" "oci:$BUNDLE_OCI:latest" 2>&1; then
3145 echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Failed to pull image: $BUNDLE_IMAGE" >&2
3146 exit 1
3147 fi
3148
3149 # Extract layers into rootfs/
3150 mkdir -p "$BUNDLE_DIR/rootfs"
3151
3152 BUNDLE_MANIFEST_DIGEST=$(jq -r '.manifests[0].digest' "$BUNDLE_OCI/index.json")
3153 BUNDLE_MANIFEST="$BUNDLE_OCI/blobs/${BUNDLE_MANIFEST_DIGEST/://}"
3154 BUNDLE_CONFIG_DIGEST=$(jq -r '.config.digest' "$BUNDLE_MANIFEST")
3155 BUNDLE_CONFIG="$BUNDLE_OCI/blobs/${BUNDLE_CONFIG_DIGEST/://}"
3156
3157 echo -e "${CYAN}[$VCONTAINER_RUNTIME_NAME]${NC} Extracting layers..."
3158 for layer_digest in $(jq -r '.layers[].digest' "$BUNDLE_MANIFEST"); do
3159 layer_file="$BUNDLE_OCI/blobs/${layer_digest/://}"
3160 [ -f "$layer_file" ] && tar -xf "$layer_file" -C "$BUNDLE_DIR/rootfs" 2>/dev/null || true
3161 done
3162
3163 # Parse OCI config
3164 BUNDLE_ENTRYPOINT=$(jq -r '(.config.Entrypoint // [])' "$BUNDLE_CONFIG")
3165 BUNDLE_CMD=$(jq -r '(.config.Cmd // [])' "$BUNDLE_CONFIG")
3166 BUNDLE_ENV=$(jq -r '(.config.Env // [])' "$BUNDLE_CONFIG")
3167 BUNDLE_CWD=$(jq -r '.config.WorkingDir // "/"' "$BUNDLE_CONFIG")
3168 [ -z "$BUNDLE_CWD" ] && BUNDLE_CWD="/"
3169
3170 # Merge Entrypoint + Cmd into process.args (or use override)
3171 if [ ${#BUNDLE_CMD_OVERRIDE[@]} -gt 0 ]; then
3172 BUNDLE_ARGS=$(printf '%s\n' "${BUNDLE_CMD_OVERRIDE[@]}" | jq -R . | jq -s .)
3173 else
3174 BUNDLE_ARGS=$(jq -n \
3175 --argjson ep "$BUNDLE_ENTRYPOINT" \
3176 --argjson cmd "$BUNDLE_CMD" \
3177 '$ep + $cmd')
3178 fi
3179
3180 # Generate config.json
3181 jq -n \
3182 --argjson args "$BUNDLE_ARGS" \
3183 --argjson env "$BUNDLE_ENV" \
3184 --arg cwd "$BUNDLE_CWD" \
3185 '{
3186 ociVersion: "1.0.2",
3187 process: {
3188 args: $args,
3189 env: $env,
3190 cwd: $cwd
3191 },
3192 root: { path: "rootfs" }
3193 }' > "$BUNDLE_DIR/config.json"
3194
3195 rm -rf "$BUNDLE_TMP"
3196 trap - EXIT
3197
3198 BUNDLE_ARGS_DISPLAY=$(jq -r 'join(" ")' <<< "$BUNDLE_ARGS")
3199 echo -e "${GREEN}[$VCONTAINER_RUNTIME_NAME]${NC} Bundle created: $BUNDLE_DIR"
3200 echo -e " entrypoint: $BUNDLE_ARGS_DISPLAY"
3201 echo -e " cwd: $BUNDLE_CWD"
3202 ;;
3203
3086 *) 3204 *)
3087 echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Unknown command: $COMMAND" >&2 3205 echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Unknown command: $COMMAND" >&2
3088 echo "Run '$VCONTAINER_RUNTIME_NAME --help' for usage" >&2 3206 echo "Run '$VCONTAINER_RUNTIME_NAME --help' for usage" >&2