summaryrefslogtreecommitdiffstats
path: root/recipes-containers/container-registry/container-registry-index.bb
diff options
context:
space:
mode:
Diffstat (limited to 'recipes-containers/container-registry/container-registry-index.bb')
-rw-r--r--recipes-containers/container-registry/container-registry-index.bb433
1 files changed, 433 insertions, 0 deletions
diff --git a/recipes-containers/container-registry/container-registry-index.bb b/recipes-containers/container-registry/container-registry-index.bb
new file mode 100644
index 00000000..c3a48e94
--- /dev/null
+++ b/recipes-containers/container-registry/container-registry-index.bb
@@ -0,0 +1,433 @@
1# SPDX-FileCopyrightText: Copyright (C) 2025 Bruce Ashfield
2#
3# SPDX-License-Identifier: MIT
4#
5# container-registry-index.bb
6# ===========================================================================
7# Push OCI container images to a registry (like package-index for containers)
8# ===========================================================================
9#
10# This is the container equivalent of meta/recipes-core/meta/package-index.bb
11# It discovers OCI images in DEPLOY_DIR_IMAGE and pushes them to a registry.
12#
13# Usage:
14# # Start registry first (separate terminal):
15# oe-run-native docker-distribution-native registry serve config.yml
16#
17# # Push all container images to registry:
18# bitbake container-registry-index
19#
20# # Or use the helper script:
21# oe-run-native container-registry-index
22#
23# Configuration (in local.conf):
24# CONTAINER_REGISTRY_URL = "localhost:5000"
25# CONTAINER_REGISTRY_NAMESPACE = "yocto"
26# CONTAINER_REGISTRY_IMAGES = "container-base container-app" # optional filter
27#
28# ===========================================================================
29
30SUMMARY = "Populate container registry with OCI images"
31LICENSE = "MIT"
32
33INHIBIT_DEFAULT_DEPS = "1"
34PACKAGES = ""
35
36inherit nopackages container-registry
37
38deltask do_fetch
39deltask do_unpack
40deltask do_patch
41deltask do_configure
42deltask do_compile
43deltask do_install
44deltask do_populate_lic
45deltask do_populate_sysroot
46
47do_container_registry_index[nostamp] = "1"
48do_container_registry_index[network] = "1"
49do_container_registry_index[depends] += "skopeo-native:do_populate_sysroot"
50
51python do_container_registry_index() {
52 import os
53
54 registry = d.getVar('CONTAINER_REGISTRY_URL')
55 namespace = d.getVar('CONTAINER_REGISTRY_NAMESPACE')
56 specific_images = (d.getVar('CONTAINER_REGISTRY_IMAGES') or '').split()
57
58 bb.plain(f"Container Registry Index: {registry}/{namespace}/")
59
60 # Discover OCI images
61 all_images = container_registry_discover_oci_images(d)
62
63 if not all_images:
64 bb.warn("No OCI images found in deploy directory")
65 bb.plain(f"Deploy directory: {d.getVar('DEPLOY_DIR_IMAGE')}")
66 bb.plain("Build container images first: bitbake container-base")
67 return
68
69 bb.plain(f"Found {len(all_images)} OCI images")
70
71 # Filter if specific images requested
72 if specific_images:
73 images = [(path, name) for path, name in all_images if name in specific_images]
74 else:
75 images = all_images
76
77 # Push each image
78 pushed_refs = []
79 for oci_path, image_name in images:
80 bb.plain(f"Pushing: {image_name}")
81 refs = container_registry_push(d, oci_path, image_name)
82 pushed_refs.extend(refs)
83
84 bb.plain(f"Pushed {len(pushed_refs)} image references to {registry}")
85}
86
87addtask do_container_registry_index before do_build
88
89# Generate a helper script with paths baked in
90# Script is placed alongside registry storage (outside tmp/) so it persists
91CONTAINER_REGISTRY_SCRIPT = "${CONTAINER_REGISTRY_STORAGE}/container-registry.sh"
92
93python do_generate_registry_script() {
94 import os
95 import stat
96
97 script_path = d.getVar('CONTAINER_REGISTRY_SCRIPT')
98 deploy_dir = d.getVar('DEPLOY_DIR')
99 deploy_dir_image = d.getVar('DEPLOY_DIR_IMAGE')
100
101 # Find registry binary path
102 native_sysroot = d.getVar('STAGING_DIR_NATIVE') or ''
103 registry_bin = os.path.join(native_sysroot, 'usr', 'sbin', 'registry')
104
105 # Find skopeo binary path
106 skopeo_bin = os.path.join(d.getVar('STAGING_SBINDIR_NATIVE') or '', 'skopeo')
107
108 # Config file path
109 config_file = os.path.join(d.getVar('THISDIR'), 'files', 'container-registry-dev.yml')
110
111 # Registry settings
112 registry_url = d.getVar('CONTAINER_REGISTRY_URL')
113 registry_namespace = d.getVar('CONTAINER_REGISTRY_NAMESPACE')
114 registry_storage = d.getVar('CONTAINER_REGISTRY_STORAGE')
115
116 os.makedirs(deploy_dir, exist_ok=True)
117
118 script = f'''#!/bin/bash
119# Container Registry Helper Script
120# Generated by: bitbake container-registry-index -c generate_registry_script
121#
122# This script has all paths pre-configured for your build.
123#
124# Usage:
125# {script_path} start # Start registry server
126# {script_path} stop # Stop registry server
127# {script_path} status # Check if running
128# {script_path} push # Push OCI images to registry
129# {script_path} import <image> # Import 3rd party image
130# {script_path} list # List all images with tags
131# {script_path} tags <image> # List tags for an image
132# {script_path} catalog # List image names (raw API)
133
134set -e
135
136# Pre-configured paths from bitbake
137REGISTRY_BIN="{registry_bin}"
138SKOPEO_BIN="{skopeo_bin}"
139REGISTRY_CONFIG="{config_file}"
140REGISTRY_STORAGE="{registry_storage}"
141REGISTRY_URL="{registry_url}"
142REGISTRY_NAMESPACE="{registry_namespace}"
143DEPLOY_DIR_IMAGE="{deploy_dir_image}"
144
145PID_FILE="/tmp/container-registry.pid"
146LOG_FILE="/tmp/container-registry.log"
147
148cmd_start() {{
149 if [ -f "$PID_FILE" ] && kill -0 "$(cat $PID_FILE)" 2>/dev/null; then
150 echo "Registry already running (PID: $(cat $PID_FILE))"
151 return 0
152 fi
153
154 if [ ! -x "$REGISTRY_BIN" ]; then
155 echo "Error: Registry binary not found at $REGISTRY_BIN"
156 echo "Build it with: bitbake docker-distribution-native"
157 return 1
158 fi
159
160 mkdir -p "$REGISTRY_STORAGE"
161 export REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY="$REGISTRY_STORAGE"
162
163 echo "Starting container registry..."
164 echo " URL: http://$REGISTRY_URL"
165 echo " Storage: $REGISTRY_STORAGE"
166 echo " Config: $REGISTRY_CONFIG"
167
168 nohup "$REGISTRY_BIN" serve "$REGISTRY_CONFIG" > "$LOG_FILE" 2>&1 &
169 echo $! > "$PID_FILE"
170 sleep 2
171
172 if kill -0 "$(cat $PID_FILE)" 2>/dev/null; then
173 echo "Registry started (PID: $(cat $PID_FILE))"
174 echo "Logs: $LOG_FILE"
175 else
176 echo "Failed to start registry. Check $LOG_FILE"
177 cat "$LOG_FILE"
178 return 1
179 fi
180}}
181
182cmd_stop() {{
183 if [ ! -f "$PID_FILE" ]; then
184 echo "Registry not running"
185 return 0
186 fi
187
188 local pid=$(cat "$PID_FILE")
189 if kill -0 "$pid" 2>/dev/null; then
190 echo "Stopping registry (PID: $pid)..."
191 kill "$pid"
192 rm -f "$PID_FILE"
193 echo "Registry stopped"
194 else
195 rm -f "$PID_FILE"
196 echo "Registry not running (stale PID file removed)"
197 fi
198}}
199
200cmd_status() {{
201 if [ -f "$PID_FILE" ] && kill -0 "$(cat $PID_FILE)" 2>/dev/null; then
202 echo "Registry running (PID: $(cat $PID_FILE))"
203 echo "URL: http://$REGISTRY_URL"
204 if curl -s "http://$REGISTRY_URL/v2/" >/dev/null 2>&1; then
205 echo "Status: healthy"
206 else
207 echo "Status: not responding"
208 fi
209 else
210 echo "Registry not running"
211 return 1
212 fi
213}}
214
215cmd_push() {{
216 if ! curl -s "http://$REGISTRY_URL/v2/" >/dev/null 2>&1; then
217 echo "Registry not responding at http://$REGISTRY_URL"
218 echo "Start it first: $0 start"
219 return 1
220 fi
221
222 echo "Pushing OCI images from $DEPLOY_DIR_IMAGE"
223 echo "To registry: $REGISTRY_URL/$REGISTRY_NAMESPACE/"
224 echo ""
225
226 for oci_dir in "$DEPLOY_DIR_IMAGE"/*-oci; do
227 [ -d "$oci_dir" ] || continue
228 [ -f "$oci_dir/index.json" ] || continue
229
230 name=$(basename "$oci_dir" | sed 's/-latest-oci$//' | sed 's/-oci$//')
231 # Remove machine suffix
232 name=$(echo "$name" | sed 's/-qemux86-64//' | sed 's/-qemuarm64//')
233 # Remove rootfs timestamp
234 name=$(echo "$name" | sed 's/\\.rootfs-[0-9]*//')
235
236 echo "Pushing: $name"
237 "$SKOPEO_BIN" copy --dest-tls-verify=false \\
238 "oci:$oci_dir" \\
239 "docker://$REGISTRY_URL/$REGISTRY_NAMESPACE/$name:latest"
240 done
241
242 echo ""
243 echo "Done. Catalog:"
244 cmd_catalog
245}}
246
247cmd_catalog() {{
248 curl -s "http://$REGISTRY_URL/v2/_catalog" | python3 -m json.tool 2>/dev/null || \\
249 curl -s "http://$REGISTRY_URL/v2/_catalog"
250}}
251
252cmd_tags() {{
253 local image="${{2:-}}"
254
255 if [ -z "$image" ]; then
256 echo "Usage: $0 tags <image>"
257 echo ""
258 echo "Examples:"
259 echo " $0 tags alpine"
260 echo " $0 tags yocto/container-base"
261 return 1
262 fi
263
264 # Add namespace if not already qualified
265 if ! echo "$image" | grep -q '/'; then
266 image="$REGISTRY_NAMESPACE/$image"
267 fi
268
269 local result=$(curl -s "http://$REGISTRY_URL/v2/$image/tags/list")
270
271 # Check for errors or empty result
272 if [ -z "$result" ]; then
273 echo "Image not found: $image"
274 return 1
275 fi
276
277 if echo "$result" | grep -qE '"errors"|NAME_UNKNOWN|MANIFEST_UNKNOWN'; then
278 echo "Image not found: $image"
279 return 1
280 fi
281
282 # Check if tags array is null or empty
283 if echo "$result" | python3 -c "import sys,json; d=json.load(sys.stdin); exit(0 if d.get('tags') else 1)" 2>/dev/null; then
284 echo "$result" | python3 -m json.tool 2>/dev/null || echo "$result"
285 else
286 echo "Image not found: $image"
287 return 1
288 fi
289}}
290
291cmd_list() {{
292 if ! curl -s "http://$REGISTRY_URL/v2/" >/dev/null 2>&1; then
293 echo "Registry not responding at http://$REGISTRY_URL"
294 return 1
295 fi
296
297 echo "Images in $REGISTRY_URL:"
298 echo ""
299
300 local repos=$(curl -s "http://$REGISTRY_URL/v2/_catalog" | python3 -c "import sys,json; print('\\n'.join(json.load(sys.stdin).get('repositories',[])))" 2>/dev/null)
301
302 if [ -z "$repos" ]; then
303 echo " (none)"
304 return 0
305 fi
306
307 for repo in $repos; do
308 local tags=$(curl -s "http://$REGISTRY_URL/v2/$repo/tags/list" | python3 -c "import sys,json; print(' '.join(json.load(sys.stdin).get('tags',[])))" 2>/dev/null)
309 if [ -n "$tags" ]; then
310 echo " $repo: $tags"
311 else
312 echo " $repo: (no tags)"
313 fi
314 done
315}}
316
317cmd_import() {{
318 local source="${{2:-}}"
319 local dest_name="${{3:-}}"
320
321 if [ -z "$source" ]; then
322 echo "Usage: $0 import <source-image> [local-name]"
323 echo ""
324 echo "Examples:"
325 echo " $0 import docker.io/library/alpine:latest"
326 echo " $0 import docker.io/library/alpine:latest my-alpine"
327 echo " $0 import quay.io/podman/hello:latest hello"
328 echo " $0 import ghcr.io/owner/image:tag"
329 return 1
330 fi
331
332 if ! curl -s "http://$REGISTRY_URL/v2/" >/dev/null 2>&1; then
333 echo "Registry not responding at http://$REGISTRY_URL"
334 echo "Start it first: $0 start"
335 return 1
336 fi
337
338 # Extract image name if not provided
339 if [ -z "$dest_name" ]; then
340 # docker.io/library/alpine:latest -> alpine
341 # quay.io/podman/hello:latest -> hello
342 dest_name=$(echo "$source" | rev | cut -d'/' -f1 | rev | cut -d':' -f1)
343 fi
344
345 # Extract tag from source, default to latest
346 local tag="latest"
347 if echo "$source" | grep -q ':'; then
348 tag=$(echo "$source" | rev | cut -d':' -f1 | rev)
349 fi
350
351 echo "Importing: $source"
352 echo " To: $REGISTRY_URL/$REGISTRY_NAMESPACE/$dest_name:$tag"
353 echo ""
354
355 "$SKOPEO_BIN" copy \\
356 --dest-tls-verify=false \\
357 "docker://$source" \\
358 "docker://$REGISTRY_URL/$REGISTRY_NAMESPACE/$dest_name:$tag"
359
360 echo ""
361 echo "Import complete. Pull with:"
362 echo " vdkr --registry $REGISTRY_URL/$REGISTRY_NAMESPACE pull $dest_name"
363 echo " # or configure: vdkr vconfig registry $REGISTRY_URL/$REGISTRY_NAMESPACE"
364 echo " # then: vdkr pull $dest_name"
365}}
366
367cmd_help() {{
368 echo "Usage: $0 <command>"
369 echo ""
370 echo "Commands:"
371 echo " start Start the container registry server"
372 echo " stop Stop the container registry server"
373 echo " status Check if registry is running"
374 echo " push Push all OCI images to registry"
375 echo " import <image> [name] Import 3rd party image to registry"
376 echo " list List all images with tags"
377 echo " tags <image> List tags for an image"
378 echo " catalog List image names (raw API)"
379 echo " help Show this help"
380 echo ""
381 echo "Examples:"
382 echo " $0 start"
383 echo " $0 push"
384 echo " $0 import docker.io/library/alpine:latest"
385 echo " $0 import docker.io/library/busybox:latest my-busybox"
386 echo " $0 list"
387 echo " $0 tags container-base"
388 echo ""
389 echo "Configuration:"
390 echo " Registry URL: $REGISTRY_URL"
391 echo " Namespace: $REGISTRY_NAMESPACE"
392 echo " Storage: $REGISTRY_STORAGE"
393 echo " Deploy images: $DEPLOY_DIR_IMAGE"
394}}
395
396case "${{1:-help}}" in
397 start) cmd_start ;;
398 stop) cmd_stop ;;
399 status) cmd_status ;;
400 push) cmd_push ;;
401 import) cmd_import "$@" ;;
402 list) cmd_list ;;
403 tags) cmd_tags "$@" ;;
404 catalog) cmd_catalog ;;
405 help|--help|-h) cmd_help ;;
406 *) echo "Unknown command: $1"; cmd_help; exit 1 ;;
407esac
408'''
409
410 with open(script_path, 'w') as f:
411 f.write(script)
412
413 # Make executable
414 os.chmod(script_path, os.stat(script_path).st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
415
416 bb.plain("")
417 bb.plain("=" * 70)
418 bb.plain("Generated container registry helper script:")
419 bb.plain(f" {script_path}")
420 bb.plain("")
421 bb.plain("Usage:")
422 bb.plain(f" {script_path} start # Start registry server")
423 bb.plain(f" {script_path} push # Push OCI images to registry")
424 bb.plain(f" {script_path} catalog # List images in registry")
425 bb.plain(f" {script_path} stop # Stop registry server")
426 bb.plain("=" * 70)
427 bb.plain("")
428}
429
430do_generate_registry_script[depends] += "docker-distribution-native:do_populate_sysroot skopeo-native:do_populate_sysroot"
431addtask do_generate_registry_script
432
433EXCLUDE_FROM_WORLD = "1"