summaryrefslogtreecommitdiffstats
path: root/classes/image-oci.bbclass
diff options
context:
space:
mode:
authorBruce Ashfield <bruce.ashfield@gmail.com>2026-01-14 04:45:53 +0000
committerBruce Ashfield <bruce.ashfield@gmail.com>2026-02-09 03:32:52 +0000
commitb02a9d60012bc4ec556238738ffe5285001e0c59 (patch)
treed564422985e500bc860d2a344270d533da57c8a6 /classes/image-oci.bbclass
parent68320b2c0a6751bf54ae9376d6e1e1dab30c0376 (diff)
downloadmeta-virtualization-b02a9d60012bc4ec556238738ffe5285001e0c59.tar.gz
image-oci: add multi-layer OCI support and CMD default
Add support for multi-layer OCI images, enabling base + app layer builds: Multi-layer support: - Add OCI_BASE_IMAGE variable to specify base layer (recipe name or path) - Add OCI_BASE_IMAGE_TAG for selecting base image tag (default: latest) - Resolve base image type (recipe/path/remote) at parse time - Copy base OCI layout before adding new layer via umoci repack - Fix merged-usr whiteout ordering issue for non-merged-usr base images (replaces problematic whiteouts with filtered entries to avoid Docker pull failures when layering merged-usr on traditional layout) CMD/ENTRYPOINT behavior change: - Add OCI_IMAGE_CMD variable (default: "/bin/sh") - Change OCI_IMAGE_ENTRYPOINT default to empty string - This makes `docker run image /bin/sh` work as expected (like Docker Hub images) - OCI_IMAGE_ENTRYPOINT_ARGS still works for legacy compatibility - Fix shlex.split() for proper shell quoting in CMD/ENTRYPOINT values The multi-layer feature requires umoci backend (default). The sloci backend only supports single-layer images and will error if OCI_BASE_IMAGE is set. Example usage: OCI_BASE_IMAGE = "container-base" IMAGE_INSTALL = "myapp" OCI_IMAGE_CMD = "/usr/bin/myapp" Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
Diffstat (limited to 'classes/image-oci.bbclass')
-rw-r--r--classes/image-oci.bbclass105
1 files changed, 104 insertions, 1 deletions
diff --git a/classes/image-oci.bbclass b/classes/image-oci.bbclass
index 70f32bf1..6f8011ca 100644
--- a/classes/image-oci.bbclass
+++ b/classes/image-oci.bbclass
@@ -42,6 +42,8 @@ IMAGE_TYPEDEP:oci = "container tar.bz2"
42# OCI_IMAGE_BACKEND ?= "sloci-image" 42# OCI_IMAGE_BACKEND ?= "sloci-image"
43OCI_IMAGE_BACKEND ?= "umoci" 43OCI_IMAGE_BACKEND ?= "umoci"
44do_image_oci[depends] += "${OCI_IMAGE_BACKEND}-native:do_populate_sysroot" 44do_image_oci[depends] += "${OCI_IMAGE_BACKEND}-native:do_populate_sysroot"
45# jq-native is needed for the merged-usr whiteout fix
46do_image_oci[depends] += "jq-native:do_populate_sysroot"
45 47
46# 48#
47# image type configuration block 49# image type configuration block
@@ -55,8 +57,12 @@ OCI_IMAGE_RUNTIME_UID ?= ""
55OCI_IMAGE_ARCH ?= "${@oe.go.map_arch(d.getVar('TARGET_ARCH'))}" 57OCI_IMAGE_ARCH ?= "${@oe.go.map_arch(d.getVar('TARGET_ARCH'))}"
56OCI_IMAGE_SUBARCH ?= "${@oci_map_subarch(d.getVar('TARGET_ARCH'), d.getVar('TUNE_FEATURES'), d)}" 58OCI_IMAGE_SUBARCH ?= "${@oci_map_subarch(d.getVar('TARGET_ARCH'), d.getVar('TUNE_FEATURES'), d)}"
57 59
58OCI_IMAGE_ENTRYPOINT ?= "sh" 60# OCI_IMAGE_ENTRYPOINT: If set, this command always runs (args appended).
61# OCI_IMAGE_CMD: Default command (replaced when user passes arguments).
62# Most base images use CMD only for flexibility. Use ENTRYPOINT for wrapper scripts.
63OCI_IMAGE_ENTRYPOINT ?= ""
59OCI_IMAGE_ENTRYPOINT_ARGS ?= "" 64OCI_IMAGE_ENTRYPOINT_ARGS ?= ""
65OCI_IMAGE_CMD ?= "/bin/sh"
60OCI_IMAGE_WORKINGDIR ?= "" 66OCI_IMAGE_WORKINGDIR ?= ""
61OCI_IMAGE_STOPSIGNAL ?= "" 67OCI_IMAGE_STOPSIGNAL ?= ""
62 68
@@ -112,6 +118,27 @@ OCI_IMAGE_BUILD_DATE ?= ""
112# Enable/disable auto-detection of git metadata (set to "0" to disable) 118# Enable/disable auto-detection of git metadata (set to "0" to disable)
113OCI_IMAGE_AUTO_LABELS ?= "1" 119OCI_IMAGE_AUTO_LABELS ?= "1"
114 120
121# =============================================================================
122# Multi-Layer OCI Support
123# =============================================================================
124#
125# OCI_BASE_IMAGE: Base image to build on top of
126# - Recipe name: "container-base" (uses local recipe's OCI output)
127# - Path: "/path/to/oci-dir" (uses existing OCI layout)
128# - Registry URL: "docker.io/library/alpine:3.19" (fetches external image)
129#
130# OCI_LAYER_MODE: How to create layers
131# - "single" (default): Single layer with complete rootfs (backward compatible)
132# - "multi": Multiple layers from OCI_LAYERS definitions
133#
134# When OCI_BASE_IMAGE is set:
135# - Base image layers are preserved
136# - New content from IMAGE_ROOTFS is added as additional layer(s)
137#
138OCI_BASE_IMAGE ?= ""
139OCI_BASE_IMAGE_TAG ?= "latest"
140OCI_LAYER_MODE ?= "single"
141
115# whether the oci image dir should be left as a directory, or 142# whether the oci image dir should be left as a directory, or
116# bundled into a tarball. 143# bundled into a tarball.
117OCI_IMAGE_TAR_OUTPUT ?= "true" 144OCI_IMAGE_TAR_OUTPUT ?= "true"
@@ -131,6 +158,82 @@ def oci_map_subarch(a, f, d):
131 return '' 158 return ''
132 return '' 159 return ''
133 160
161# =============================================================================
162# Base Image Resolution and Dependency Setup
163# =============================================================================
164
165def oci_resolve_base_image(d):
166 """Resolve OCI_BASE_IMAGE to determine its type.
167
168 Returns dict with 'type' key:
169 - {'type': 'recipe', 'name': 'container-base'}
170 - {'type': 'path', 'path': '/path/to/oci-dir'}
171 - {'type': 'remote', 'url': 'docker.io/library/alpine:3.19'}
172 - None if no base image
173 """
174 base = d.getVar('OCI_BASE_IMAGE') or ''
175 if not base:
176 return None
177
178 # Check if it's a path (starts with /)
179 if base.startswith('/'):
180 return {'type': 'path', 'path': base}
181
182 # Check if it looks like a registry URL (contains / or has registry prefix)
183 if '/' in base or '.' in base.split(':')[0]:
184 return {'type': 'remote', 'url': base}
185
186 # Assume it's a recipe name
187 return {'type': 'recipe', 'name': base}
188
189python __anonymous() {
190 import os
191
192 backend = d.getVar('OCI_IMAGE_BACKEND') or 'umoci'
193 base_image = d.getVar('OCI_BASE_IMAGE') or ''
194 layer_mode = d.getVar('OCI_LAYER_MODE') or 'single'
195
196 # sloci doesn't support multi-layer
197 if backend == 'sloci-image':
198 if layer_mode != 'single' or base_image:
199 bb.fatal("Multi-layer OCI requires umoci backend. "
200 "Set OCI_IMAGE_BACKEND = 'umoci' or remove OCI_BASE_IMAGE")
201
202 # Resolve base image and set up dependencies
203 if base_image:
204 resolved = oci_resolve_base_image(d)
205 if resolved:
206 if resolved['type'] == 'recipe':
207 # Add dependency on base recipe's OCI output
208 # Use do_build as it works for both image recipes and oci-fetch recipes
209 base_recipe = resolved['name']
210 d.setVar('_OCI_BASE_RECIPE', base_recipe)
211 d.appendVarFlag('do_image_oci', 'depends',
212 f" {base_recipe}:do_build rsync-native:do_populate_sysroot")
213 bb.debug(1, f"OCI: Using base image from recipe: {base_recipe}")
214
215 elif resolved['type'] == 'path':
216 d.setVar('_OCI_BASE_PATH', resolved['path'])
217 d.appendVarFlag('do_image_oci', 'depends',
218 " rsync-native:do_populate_sysroot")
219 bb.debug(1, f"OCI: Using base image from path: {resolved['path']}")
220
221 elif resolved['type'] == 'remote':
222 # Remote URLs are not supported directly - use a container-bundle recipe
223 remote_url = resolved['url']
224 # Create sanitized key for CONTAINER_DIGESTS varflag
225 sanitized_key = remote_url.replace('/', '_').replace(':', '_')
226 bb.fatal(f"Remote base images cannot be used directly: {remote_url}\n\n"
227 f"Create a container-bundle recipe to fetch the external image:\n\n"
228 f" # recipes-containers/oci-base-images/my-base.bb\n"
229 f" inherit container-bundle\n"
230 f" CONTAINER_BUNDLES = \"{remote_url}\"\n"
231 f" CONTAINER_DIGESTS[{sanitized_key}] = \"sha256:...\"\n"
232 f" CONTAINER_BUNDLE_DEPLOY = \"1\"\n\n"
233 f"Get digest with: skopeo inspect docker://{remote_url} | jq -r '.Digest'\n\n"
234 f"Then use: OCI_BASE_IMAGE = \"my-base\"")
235}
236
134# the IMAGE_CMD:oci comes from the .inc 237# the IMAGE_CMD:oci comes from the .inc
135OCI_IMAGE_BACKEND_INC ?= "${@"image-oci-" + "${OCI_IMAGE_BACKEND}" + ".inc"}" 238OCI_IMAGE_BACKEND_INC ?= "${@"image-oci-" + "${OCI_IMAGE_BACKEND}" + ".inc"}"
136include ${OCI_IMAGE_BACKEND_INC} 239include ${OCI_IMAGE_BACKEND_INC}