summaryrefslogtreecommitdiffstats
path: root/classes
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 /classes
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 'classes')
-rw-r--r--classes/oci-multiarch.bbclass243
1 files changed, 243 insertions, 0 deletions
diff --git a/classes/oci-multiarch.bbclass b/classes/oci-multiarch.bbclass
new file mode 100644
index 00000000..9960b7cc
--- /dev/null
+++ b/classes/oci-multiarch.bbclass
@@ -0,0 +1,243 @@
1# SPDX-FileCopyrightText: Copyright (C) 2025 Bruce Ashfield
2#
3# SPDX-License-Identifier: MIT
4#
5# oci-multiarch.bbclass
6# ===========================================================================
7# Build multi-architecture OCI container images locally
8# ===========================================================================
9#
10# This class creates OCI Image Index (manifest list) from multiple
11# single-architecture OCI images built via multiconfig.
12#
13# USAGE:
14# # In your recipe (e.g., myapp-container-multiarch.bb):
15# inherit oci-multiarch
16#
17# OCI_MULTIARCH_RECIPE = "myapp-container"
18# OCI_MULTIARCH_PLATFORMS = "aarch64 x86_64"
19#
20# # Optional: custom multiconfig mapping
21# OCI_MULTIARCH_MC[aarch64] = "vruntime-aarch64"
22# OCI_MULTIARCH_MC[x86_64] = "vruntime-x86-64"
23#
24# OUTPUT:
25# ${DEPLOY_DIR_IMAGE}/${PN}-multiarch-oci/
26# index.json - OCI Image Index with platform entries
27# oci-layout - OCI layout marker
28# blobs/sha256/ - Combined blobs from all architectures
29#
30# REQUIREMENTS:
31# - Multiconfig must be enabled in local.conf:
32# BBMULTICONFIG = "vruntime-aarch64 vruntime-x86-64"
33# - OCI_MULTIARCH_RECIPE must inherit image-oci or produce OCI output
34#
35# ===========================================================================
36
37inherit nopackages
38
39INHIBIT_DEFAULT_DEPS = "1"
40
41# Required variables
42OCI_MULTIARCH_RECIPE ?= ""
43OCI_MULTIARCH_PLATFORMS ?= ""
44
45# Default multiconfig mapping (uses existing vruntime-* configs)
46OCI_MULTIARCH_MC[aarch64] ?= "vruntime-aarch64"
47OCI_MULTIARCH_MC[x86_64] ?= "vruntime-x86-64"
48
49# Machine mapping for deploy directory paths
50OCI_MULTIARCH_MACHINE[aarch64] ?= "qemuarm64"
51OCI_MULTIARCH_MACHINE[x86_64] ?= "qemux86-64"
52
53# Architecture to OCI platform name mapping
54OCI_ARCH_TO_PLATFORM[aarch64] = "arm64"
55OCI_ARCH_TO_PLATFORM[x86_64] = "amd64"
56
57# Output directory
58OCI_MULTIARCH_OUTPUT = "${DEPLOY_DIR_IMAGE}/${PN}-multiarch-oci"
59
60# Delete standard tasks we don't need
61deltask do_fetch
62deltask do_unpack
63deltask do_patch
64deltask do_configure
65deltask do_compile
66deltask do_install
67deltask do_populate_lic
68deltask do_populate_sysroot
69deltask do_package
70deltask do_package_qa
71deltask do_packagedata
72
73# Generate mcdepends at parse time
74python __anonymous() {
75 recipe = d.getVar('OCI_MULTIARCH_RECIPE')
76 platforms = d.getVar('OCI_MULTIARCH_PLATFORMS')
77
78 if not recipe:
79 bb.fatal("OCI_MULTIARCH_RECIPE must be set")
80
81 if not platforms:
82 bb.fatal("OCI_MULTIARCH_PLATFORMS must be set (e.g., 'aarch64 x86_64')")
83
84 # Build mcdepends string for each platform
85 mcdepends = []
86 for platform in platforms.split():
87 mc = d.getVarFlag('OCI_MULTIARCH_MC', platform)
88 if not mc:
89 bb.fatal(f"No multiconfig defined for platform '{platform}'. Set OCI_MULTIARCH_MC[{platform}]")
90 mcdepends.append(f"mc::{mc}:{recipe}:do_image_oci")
91
92 # Set the mcdepends for our main task
93 d.setVarFlag('do_create_multiarch_index', 'mcdepends', ' '.join(mcdepends))
94
95 bb.note(f"OCI multi-arch: building {recipe} for platforms: {platforms}")
96}
97
98python do_create_multiarch_index() {
99 import os
100 import json
101 import shutil
102 import hashlib
103
104 recipe = d.getVar('OCI_MULTIARCH_RECIPE')
105 platforms = d.getVar('OCI_MULTIARCH_PLATFORMS').split()
106 topdir = d.getVar('TOPDIR')
107 output_dir = d.getVar('OCI_MULTIARCH_OUTPUT')
108
109 bb.plain(f"Creating multi-arch OCI Image Index for {recipe}")
110 bb.plain(f"Platforms: {' '.join(platforms)}")
111
112 # Clean output directory
113 if os.path.exists(output_dir):
114 shutil.rmtree(output_dir)
115 os.makedirs(os.path.join(output_dir, 'blobs', 'sha256'))
116
117 # Collect manifests from each platform
118 index_manifests = []
119
120 for platform in platforms:
121 mc = d.getVarFlag('OCI_MULTIARCH_MC', platform)
122 machine = d.getVarFlag('OCI_MULTIARCH_MACHINE', platform)
123 oci_platform = d.getVarFlag('OCI_ARCH_TO_PLATFORM', platform) or platform
124
125 if not mc or not machine:
126 bb.fatal(f"Missing configuration for platform {platform}")
127
128 # Find the OCI image in the multiconfig's deploy directory
129 # Pattern: tmp-<mc>/deploy/images/<machine>/<recipe>-latest-oci/
130 mc_deploy_base = os.path.join(topdir, f'tmp-{mc}', 'deploy', 'images', machine)
131
132 # Try different naming patterns
133 oci_patterns = [
134 f"{recipe}-latest-oci",
135 f"{recipe}-{machine}-latest-oci",
136 f"{recipe}-oci",
137 ]
138
139 oci_dir = None
140 for pattern in oci_patterns:
141 candidate = os.path.join(mc_deploy_base, pattern)
142 if os.path.isdir(candidate) and os.path.exists(os.path.join(candidate, 'index.json')):
143 oci_dir = candidate
144 break
145
146 if not oci_dir:
147 bb.fatal(f"OCI image not found for {platform} ({mc}:{recipe})")
148 bb.fatal(f"Looked in: {mc_deploy_base}")
149 continue
150
151 bb.plain(f" Found {platform} OCI: {oci_dir}")
152
153 # Read the source index.json
154 with open(os.path.join(oci_dir, 'index.json'), 'r') as f:
155 src_index = json.load(f)
156
157 # Get the manifest entry (should be first/only one for single-arch)
158 if not src_index.get('manifests'):
159 bb.warn(f"No manifests found in {oci_dir}/index.json")
160 continue
161
162 src_manifest_entry = src_index['manifests'][0]
163 manifest_digest = src_manifest_entry['digest']
164 manifest_size = src_manifest_entry['size']
165
166 # Copy all blobs from source to output
167 src_blobs = os.path.join(oci_dir, 'blobs', 'sha256')
168 dst_blobs = os.path.join(output_dir, 'blobs', 'sha256')
169
170 if os.path.isdir(src_blobs):
171 for blob in os.listdir(src_blobs):
172 src_blob = os.path.join(src_blobs, blob)
173 dst_blob = os.path.join(dst_blobs, blob)
174 if not os.path.exists(dst_blob):
175 shutil.copy2(src_blob, dst_blob)
176 bb.note(f" Copied blob: {blob[:12]}...")
177
178 # Create manifest entry with platform info
179 manifest_entry = {
180 'mediaType': 'application/vnd.oci.image.manifest.v1+json',
181 'digest': manifest_digest,
182 'size': manifest_size,
183 'platform': {
184 'architecture': oci_platform,
185 'os': 'linux'
186 }
187 }
188 index_manifests.append(manifest_entry)
189 bb.plain(f" Added {oci_platform}/linux manifest: {manifest_digest[:19]}...")
190
191 if not index_manifests:
192 bb.fatal("No manifests collected - cannot create multi-arch index")
193
194 # Create the OCI Image Index
195 image_index = {
196 'schemaVersion': 2,
197 'mediaType': 'application/vnd.oci.image.index.v1+json',
198 'manifests': index_manifests
199 }
200
201 # Write index.json
202 index_path = os.path.join(output_dir, 'index.json')
203 with open(index_path, 'w') as f:
204 json.dump(image_index, f, indent=2)
205
206 # Write oci-layout
207 layout_path = os.path.join(output_dir, 'oci-layout')
208 with open(layout_path, 'w') as f:
209 json.dump({'imageLayoutVersion': '1.0.0'}, f)
210
211 bb.plain("")
212 bb.plain(f"Created multi-arch OCI Image Index:")
213 bb.plain(f" {output_dir}")
214 bb.plain(f" Platforms: {', '.join(d.getVarFlag('OCI_ARCH_TO_PLATFORM', p) or p for p in platforms)}")
215 bb.plain("")
216 bb.plain("To import into vdkr (will auto-select platform):")
217 bb.plain(f" vdkr vimport {output_dir} {recipe}:latest")
218}
219
220addtask do_create_multiarch_index before do_build
221
222# Stamp includes platforms to rebuild when platforms change
223do_create_multiarch_index[stamp-extra-info] = "${OCI_MULTIARCH_PLATFORMS}"
224
225# Deploy the multi-arch OCI
226python do_deploy() {
227 import os
228 import shutil
229
230 output_dir = d.getVar('OCI_MULTIARCH_OUTPUT')
231 deploy_dir = d.getVar('DEPLOY_DIR_IMAGE')
232
233 # Already deployed in place (output_dir is in deploy_dir)
234 # Just verify it exists
235 if not os.path.exists(os.path.join(output_dir, 'index.json')):
236 bb.fatal(f"Multi-arch OCI not found: {output_dir}")
237
238 bb.plain(f"Multi-arch OCI available at: {output_dir}")
239}
240
241addtask do_deploy after do_create_multiarch_index before do_build
242
243EXCLUDE_FROM_WORLD = "1"