diff options
| author | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-01-12 20:14:29 +0000 |
|---|---|---|
| committer | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-02-09 03:32:52 +0000 |
| commit | 1d8968199aa812d5c9bdc8089e0eb53da25cd877 (patch) | |
| tree | c4d4d71a383bd0d430ffbf2cfed0485a3f16b4a0 /recipes-containers | |
| parent | 45a4f2aa1a69bbf2f084d8d56a4c2812aea26b51 (diff) | |
| download | meta-virtualization-1d8968199aa812d5c9bdc8089e0eb53da25cd877.tar.gz | |
container-registry: add industry-standard tag strategies
Add comprehensive tag support for registry push operations:
Tag strategies (CONTAINER_REGISTRY_TAG_STRATEGY):
- sha/git: short git commit hash for traceability
- branch: git branch name (sanitized) for dev workflows
- semver: nested SemVer tags (1.2.3 -> 1.2.3, 1.2, 1)
- timestamp: YYYYMMDD-HHMMSS format
- version: single version tag from PV
- latest: the "latest" tag
- arch: append architecture suffix
Helper script enhancements:
- push --tag <tag>: explicit tags (repeatable)
- push --strategy <strategies>: override tag strategy
- push --version <ver>: version for semver strategy
- Baked-in defaults from bitbake variables
- Environment variable overrides supported
This aligns with industry practices:
- Git SHA for CI/CD traceability
- SemVer nested tags for release management
- Branch tags for feature development
Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
Diffstat (limited to 'recipes-containers')
| -rw-r--r-- | recipes-containers/container-registry/README.md | 54 | ||||
| -rw-r--r-- | recipes-containers/container-registry/container-registry-index.bb | 182 |
2 files changed, 215 insertions, 21 deletions
diff --git a/recipes-containers/container-registry/README.md b/recipes-containers/container-registry/README.md index 11db39bb..82932706 100644 --- a/recipes-containers/container-registry/README.md +++ b/recipes-containers/container-registry/README.md | |||
| @@ -34,12 +34,61 @@ Script location: `${TOPDIR}/container-registry/container-registry.sh` (outside t | |||
| 34 | | `start` | Start the container registry server | | 34 | | `start` | Start the container registry server | |
| 35 | | `stop` | Stop the container registry server | | 35 | | `stop` | Stop the container registry server | |
| 36 | | `status` | Check if registry is running | | 36 | | `status` | Check if registry is running | |
| 37 | | `push` | Push all OCI images from deploy/ to registry | | 37 | | `push [options]` | Push all OCI images from deploy/ to registry | |
| 38 | | `import <image> [name]` | Import 3rd party image to registry | | 38 | | `import <image> [name]` | Import 3rd party image to registry | |
| 39 | | `list` | List all images with their tags | | 39 | | `list` | List all images with their tags | |
| 40 | | `tags <image>` | List tags for a specific image | | 40 | | `tags <image>` | List tags for a specific image | |
| 41 | | `catalog` | Raw API catalog output | | 41 | | `catalog` | Raw API catalog output | |
| 42 | 42 | ||
| 43 | ### Push Options | ||
| 44 | |||
| 45 | ```bash | ||
| 46 | # Explicit tags | ||
| 47 | container-registry.sh push --tag v1.0.0 | ||
| 48 | container-registry.sh push --tag latest --tag v1.0.0 | ||
| 49 | |||
| 50 | # Strategy-based (see Tag Strategies below) | ||
| 51 | container-registry.sh push --strategy "sha branch latest" | ||
| 52 | container-registry.sh push --strategy semver --version 1.2.3 | ||
| 53 | |||
| 54 | # Environment variable override | ||
| 55 | CONTAINER_REGISTRY_TAG_STRATEGY="sha latest" container-registry.sh push | ||
| 56 | ``` | ||
| 57 | |||
| 58 | ## Tag Strategies | ||
| 59 | |||
| 60 | Configure tag generation via `CONTAINER_REGISTRY_TAG_STRATEGY` (space-separated): | ||
| 61 | |||
| 62 | | Strategy | Output | Description | | ||
| 63 | |----------|--------|-------------| | ||
| 64 | | `timestamp` | `20260112-143022` | Build timestamp | | ||
| 65 | | `sha` / `git` | `8a3f2b1` | Short git commit hash | | ||
| 66 | | `branch` | `main`, `feature-login` | Git branch name (sanitized) | | ||
| 67 | | `semver` | `1.2.3`, `1.2`, `1` | Nested SemVer from PV | | ||
| 68 | | `version` | `1.2.3` | Single version tag | | ||
| 69 | | `latest` | `latest` | The "latest" tag | | ||
| 70 | | `arch` | `*-x86_64` | Append architecture suffix | | ||
| 71 | |||
| 72 | ### Example Workflows | ||
| 73 | |||
| 74 | **Development builds** (track code changes): | ||
| 75 | ```bitbake | ||
| 76 | CONTAINER_REGISTRY_TAG_STRATEGY = "sha branch latest" | ||
| 77 | ``` | ||
| 78 | Result: `my-app:8a3f2b1`, `my-app:feature-login`, `my-app:latest` | ||
| 79 | |||
| 80 | **Release builds** (semantic versioning): | ||
| 81 | ```bitbake | ||
| 82 | CONTAINER_REGISTRY_TAG_STRATEGY = "semver latest" | ||
| 83 | PV = "1.2.3" | ||
| 84 | ``` | ||
| 85 | Result: `my-app:1.2.3`, `my-app:1.2`, `my-app:1`, `my-app:latest` | ||
| 86 | |||
| 87 | **CI/CD** (traceability): | ||
| 88 | ```bash | ||
| 89 | IMAGE_VERSION=1.2.3 container-registry.sh push --strategy "semver sha latest" | ||
| 90 | ``` | ||
| 91 | |||
| 43 | ## Configuration (local.conf) | 92 | ## Configuration (local.conf) |
| 44 | 93 | ||
| 45 | ```bitbake | 94 | ```bitbake |
| @@ -52,6 +101,9 @@ CONTAINER_REGISTRY_NAMESPACE = "yocto" | |||
| 52 | # Mark as insecure (HTTP) | 101 | # Mark as insecure (HTTP) |
| 53 | CONTAINER_REGISTRY_INSECURE = "1" | 102 | CONTAINER_REGISTRY_INSECURE = "1" |
| 54 | 103 | ||
| 104 | # Tag strategy (default: "timestamp latest") | ||
| 105 | CONTAINER_REGISTRY_TAG_STRATEGY = "sha branch latest" | ||
| 106 | |||
| 55 | # For Docker targets | 107 | # For Docker targets |
| 56 | DOCKER_REGISTRY_INSECURE = "localhost:5000" | 108 | DOCKER_REGISTRY_INSECURE = "localhost:5000" |
| 57 | 109 | ||
diff --git a/recipes-containers/container-registry/container-registry-index.bb b/recipes-containers/container-registry/container-registry-index.bb index c3a48e94..f865bfaa 100644 --- a/recipes-containers/container-registry/container-registry-index.bb +++ b/recipes-containers/container-registry/container-registry-index.bb | |||
| @@ -112,6 +112,8 @@ python do_generate_registry_script() { | |||
| 112 | registry_url = d.getVar('CONTAINER_REGISTRY_URL') | 112 | registry_url = d.getVar('CONTAINER_REGISTRY_URL') |
| 113 | registry_namespace = d.getVar('CONTAINER_REGISTRY_NAMESPACE') | 113 | registry_namespace = d.getVar('CONTAINER_REGISTRY_NAMESPACE') |
| 114 | registry_storage = d.getVar('CONTAINER_REGISTRY_STORAGE') | 114 | registry_storage = d.getVar('CONTAINER_REGISTRY_STORAGE') |
| 115 | tag_strategy = d.getVar('CONTAINER_REGISTRY_TAG_STRATEGY') or 'latest' | ||
| 116 | target_arch = d.getVar('TARGET_ARCH') or '' | ||
| 115 | 117 | ||
| 116 | os.makedirs(deploy_dir, exist_ok=True) | 118 | os.makedirs(deploy_dir, exist_ok=True) |
| 117 | 119 | ||
| @@ -122,14 +124,19 @@ python do_generate_registry_script() { | |||
| 122 | # This script has all paths pre-configured for your build. | 124 | # This script has all paths pre-configured for your build. |
| 123 | # | 125 | # |
| 124 | # Usage: | 126 | # Usage: |
| 125 | # {script_path} start # Start registry server | 127 | # {script_path} start # Start registry server |
| 126 | # {script_path} stop # Stop registry server | 128 | # {script_path} stop # Stop registry server |
| 127 | # {script_path} status # Check if running | 129 | # {script_path} status # Check if running |
| 128 | # {script_path} push # Push OCI images to registry | 130 | # {script_path} push [options] # Push OCI images to registry |
| 129 | # {script_path} import <image> # Import 3rd party image | 131 | # {script_path} import <image> # Import 3rd party image |
| 130 | # {script_path} list # List all images with tags | 132 | # {script_path} list # List all images with tags |
| 131 | # {script_path} tags <image> # List tags for an image | 133 | # {script_path} tags <image> # List tags for an image |
| 132 | # {script_path} catalog # List image names (raw API) | 134 | # {script_path} catalog # List image names (raw API) |
| 135 | # | ||
| 136 | # Push options: | ||
| 137 | # --tag <tag> Explicit tag (can be repeated) | ||
| 138 | # --strategy <strats> Tag strategy: timestamp, sha, branch, semver, latest, arch | ||
| 139 | # --version <ver> Version for semver strategy (e.g., 1.2.3) | ||
| 133 | 140 | ||
| 134 | set -e | 141 | set -e |
| 135 | 142 | ||
| @@ -142,9 +149,77 @@ REGISTRY_URL="{registry_url}" | |||
| 142 | REGISTRY_NAMESPACE="{registry_namespace}" | 149 | REGISTRY_NAMESPACE="{registry_namespace}" |
| 143 | DEPLOY_DIR_IMAGE="{deploy_dir_image}" | 150 | DEPLOY_DIR_IMAGE="{deploy_dir_image}" |
| 144 | 151 | ||
| 152 | # Baked-in defaults from bitbake (can be overridden by CLI or env vars) | ||
| 153 | DEFAULT_TAG_STRATEGY="{tag_strategy}" | ||
| 154 | DEFAULT_TARGET_ARCH="{target_arch}" | ||
| 155 | |||
| 145 | PID_FILE="/tmp/container-registry.pid" | 156 | PID_FILE="/tmp/container-registry.pid" |
| 146 | LOG_FILE="/tmp/container-registry.log" | 157 | LOG_FILE="/tmp/container-registry.log" |
| 147 | 158 | ||
| 159 | # Generate tags based on strategy | ||
| 160 | # Usage: generate_tags "strategy1 strategy2 ..." | ||
| 161 | # Strategies: timestamp, sha/git, branch, semver, version, latest, arch | ||
| 162 | generate_tags() {{ | ||
| 163 | local strategy="${{1:-latest}}" | ||
| 164 | local version="${{IMAGE_VERSION:-}}" | ||
| 165 | local arch="${{TARGET_ARCH:-$DEFAULT_TARGET_ARCH}}" | ||
| 166 | local tags="" | ||
| 167 | |||
| 168 | for strat in $strategy; do | ||
| 169 | case "$strat" in | ||
| 170 | timestamp) | ||
| 171 | tags="$tags $(date +%Y%m%d-%H%M%S)" | ||
| 172 | ;; | ||
| 173 | sha|git) | ||
| 174 | local sha=$(git rev-parse --short HEAD 2>/dev/null || true) | ||
| 175 | [ -n "$sha" ] && tags="$tags $sha" | ||
| 176 | ;; | ||
| 177 | branch) | ||
| 178 | local branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true) | ||
| 179 | if [ -n "$branch" ] && [ "$branch" != "HEAD" ]; then | ||
| 180 | # Sanitize: feature/login -> feature-login | ||
| 181 | tags="$tags $(echo $branch | tr '/_' '--')" | ||
| 182 | fi | ||
| 183 | ;; | ||
| 184 | semver) | ||
| 185 | if [ -n "$version" ]; then | ||
| 186 | local v="$version" | ||
| 187 | # Strip any suffix like +gitAUTOINC | ||
| 188 | v=$(echo "$v" | cut -d'+' -f1) | ||
| 189 | local major=$(echo "$v" | cut -d. -f1) | ||
| 190 | local minor=$(echo "$v" | cut -d. -f2) | ||
| 191 | local patch=$(echo "$v" | cut -d. -f3) | ||
| 192 | [ -n "$patch" ] && tags="$tags $major.$minor.$patch" | ||
| 193 | [ -n "$minor" ] && tags="$tags $major.$minor" | ||
| 194 | [ -n "$major" ] && [ "$major" != "$v" ] && tags="$tags $major" | ||
| 195 | fi | ||
| 196 | ;; | ||
| 197 | version) | ||
| 198 | if [ -n "$version" ]; then | ||
| 199 | local v=$(echo "$version" | cut -d'+' -f1) | ||
| 200 | tags="$tags $v" | ||
| 201 | fi | ||
| 202 | ;; | ||
| 203 | latest) | ||
| 204 | tags="$tags latest" | ||
| 205 | ;; | ||
| 206 | arch) | ||
| 207 | if [ -n "$arch" ]; then | ||
| 208 | local arch_tags="" | ||
| 209 | for t in $tags; do | ||
| 210 | [ "$t" != "latest" ] && arch_tags="$arch_tags ${{t}}-${{arch}}" | ||
| 211 | done | ||
| 212 | tags="$tags $arch_tags" | ||
| 213 | fi | ||
| 214 | ;; | ||
| 215 | esac | ||
| 216 | done | ||
| 217 | |||
| 218 | # Ensure at least one tag | ||
| 219 | [ -z "$tags" ] && tags="latest" | ||
| 220 | echo $tags | ||
| 221 | }} | ||
| 222 | |||
| 148 | cmd_start() {{ | 223 | cmd_start() {{ |
| 149 | if [ -f "$PID_FILE" ] && kill -0 "$(cat $PID_FILE)" 2>/dev/null; then | 224 | if [ -f "$PID_FILE" ] && kill -0 "$(cat $PID_FILE)" 2>/dev/null; then |
| 150 | echo "Registry already running (PID: $(cat $PID_FILE))" | 225 | echo "Registry already running (PID: $(cat $PID_FILE))" |
| @@ -213,14 +288,53 @@ cmd_status() {{ | |||
| 213 | }} | 288 | }} |
| 214 | 289 | ||
| 215 | cmd_push() {{ | 290 | cmd_push() {{ |
| 291 | shift # Remove 'push' from args | ||
| 292 | |||
| 293 | # Parse options | ||
| 294 | local explicit_tags="" | ||
| 295 | local strategy="${{CONTAINER_REGISTRY_TAG_STRATEGY:-$DEFAULT_TAG_STRATEGY}}" | ||
| 296 | local version="${{IMAGE_VERSION:-}}" | ||
| 297 | |||
| 298 | while [ $# -gt 0 ]; do | ||
| 299 | case "$1" in | ||
| 300 | --tag|-t) | ||
| 301 | explicit_tags="$explicit_tags $2" | ||
| 302 | shift 2 | ||
| 303 | ;; | ||
| 304 | --strategy|-s) | ||
| 305 | strategy="$2" | ||
| 306 | shift 2 | ||
| 307 | ;; | ||
| 308 | --version|-v) | ||
| 309 | version="$2" | ||
| 310 | shift 2 | ||
| 311 | ;; | ||
| 312 | *) | ||
| 313 | shift | ||
| 314 | ;; | ||
| 315 | esac | ||
| 316 | done | ||
| 317 | |||
| 318 | # Export version for generate_tags | ||
| 319 | export IMAGE_VERSION="$version" | ||
| 320 | |||
| 216 | if ! curl -s "http://$REGISTRY_URL/v2/" >/dev/null 2>&1; then | 321 | if ! curl -s "http://$REGISTRY_URL/v2/" >/dev/null 2>&1; then |
| 217 | echo "Registry not responding at http://$REGISTRY_URL" | 322 | echo "Registry not responding at http://$REGISTRY_URL" |
| 218 | echo "Start it first: $0 start" | 323 | echo "Start it first: $0 start" |
| 219 | return 1 | 324 | return 1 |
| 220 | fi | 325 | fi |
| 221 | 326 | ||
| 327 | # Determine tags to use | ||
| 328 | local tags | ||
| 329 | if [ -n "$explicit_tags" ]; then | ||
| 330 | tags="$explicit_tags" | ||
| 331 | else | ||
| 332 | tags=$(generate_tags "$strategy") | ||
| 333 | fi | ||
| 334 | |||
| 222 | echo "Pushing OCI images from $DEPLOY_DIR_IMAGE" | 335 | echo "Pushing OCI images from $DEPLOY_DIR_IMAGE" |
| 223 | echo "To registry: $REGISTRY_URL/$REGISTRY_NAMESPACE/" | 336 | echo "To registry: $REGISTRY_URL/$REGISTRY_NAMESPACE/" |
| 337 | echo "Tags: $tags" | ||
| 224 | echo "" | 338 | echo "" |
| 225 | 339 | ||
| 226 | for oci_dir in "$DEPLOY_DIR_IMAGE"/*-oci; do | 340 | for oci_dir in "$DEPLOY_DIR_IMAGE"/*-oci; do |
| @@ -234,9 +348,12 @@ cmd_push() {{ | |||
| 234 | name=$(echo "$name" | sed 's/\\.rootfs-[0-9]*//') | 348 | name=$(echo "$name" | sed 's/\\.rootfs-[0-9]*//') |
| 235 | 349 | ||
| 236 | echo "Pushing: $name" | 350 | echo "Pushing: $name" |
| 237 | "$SKOPEO_BIN" copy --dest-tls-verify=false \\ | 351 | for tag in $tags; do |
| 238 | "oci:$oci_dir" \\ | 352 | echo " -> $REGISTRY_URL/$REGISTRY_NAMESPACE/$name:$tag" |
| 239 | "docker://$REGISTRY_URL/$REGISTRY_NAMESPACE/$name:latest" | 353 | "$SKOPEO_BIN" copy --dest-tls-verify=false \\ |
| 354 | "oci:$oci_dir" \\ | ||
| 355 | "docker://$REGISTRY_URL/$REGISTRY_NAMESPACE/$name:$tag" | ||
| 356 | done | ||
| 240 | done | 357 | done |
| 241 | 358 | ||
| 242 | echo "" | 359 | echo "" |
| @@ -365,39 +482,64 @@ cmd_import() {{ | |||
| 365 | }} | 482 | }} |
| 366 | 483 | ||
| 367 | cmd_help() {{ | 484 | cmd_help() {{ |
| 368 | echo "Usage: $0 <command>" | 485 | echo "Usage: $0 <command> [options]" |
| 369 | echo "" | 486 | echo "" |
| 370 | echo "Commands:" | 487 | echo "Commands:" |
| 371 | echo " start Start the container registry server" | 488 | echo " start Start the container registry server" |
| 372 | echo " stop Stop the container registry server" | 489 | echo " stop Stop the container registry server" |
| 373 | echo " status Check if registry is running" | 490 | echo " status Check if registry is running" |
| 374 | echo " push Push all OCI images to registry" | 491 | echo " push [options] Push all OCI images to registry" |
| 375 | echo " import <image> [name] Import 3rd party image to registry" | 492 | echo " import <image> [name] Import 3rd party image to registry" |
| 376 | echo " list List all images with tags" | 493 | echo " list List all images with tags" |
| 377 | echo " tags <image> List tags for an image" | 494 | echo " tags <image> List tags for an image" |
| 378 | echo " catalog List image names (raw API)" | 495 | echo " catalog List image names (raw API)" |
| 379 | echo " help Show this help" | 496 | echo " help Show this help" |
| 380 | echo "" | 497 | echo "" |
| 498 | echo "Push options:" | ||
| 499 | echo " --tag, -t <tag> Explicit tag (can be repeated for multiple tags)" | ||
| 500 | echo " --strategy, -s <str> Tag strategy (default: $DEFAULT_TAG_STRATEGY)" | ||
| 501 | echo " --version, -v <ver> Version for semver strategy (e.g., 1.2.3)" | ||
| 502 | echo "" | ||
| 503 | echo "Tag strategies (can combine: 'sha branch latest'):" | ||
| 504 | echo " timestamp YYYYMMDD-HHMMSS format" | ||
| 505 | echo " sha, git Short git commit hash" | ||
| 506 | echo " branch Git branch name (sanitized)" | ||
| 507 | echo " semver Nested SemVer (1.2.3 -> 1.2.3, 1.2, 1)" | ||
| 508 | echo " version Single version tag from --version" | ||
| 509 | echo " latest The 'latest' tag" | ||
| 510 | echo " arch Append architecture suffix to other tags" | ||
| 511 | echo "" | ||
| 381 | echo "Examples:" | 512 | echo "Examples:" |
| 382 | echo " $0 start" | 513 | echo " $0 start" |
| 383 | echo " $0 push" | 514 | echo " $0 push # Uses default strategy" |
| 515 | echo " $0 push --tag v1.0.0 # Explicit tag" | ||
| 516 | echo " $0 push --tag latest --tag v1.0.0 # Multiple tags" | ||
| 517 | echo " $0 push --strategy 'sha branch latest' # Strategy-based" | ||
| 518 | echo " $0 push --strategy semver --version 1.2.3 # SemVer tags" | ||
| 384 | echo " $0 import docker.io/library/alpine:latest" | 519 | echo " $0 import docker.io/library/alpine:latest" |
| 385 | echo " $0 import docker.io/library/busybox:latest my-busybox" | 520 | echo " $0 import docker.io/library/busybox:latest my-busybox" |
| 386 | echo " $0 list" | 521 | echo " $0 list" |
| 387 | echo " $0 tags container-base" | 522 | echo " $0 tags container-base" |
| 388 | echo "" | 523 | echo "" |
| 389 | echo "Configuration:" | 524 | echo "Environment variables:" |
| 390 | echo " Registry URL: $REGISTRY_URL" | 525 | echo " CONTAINER_REGISTRY_TAG_STRATEGY Override default tag strategy" |
| 391 | echo " Namespace: $REGISTRY_NAMESPACE" | 526 | echo " IMAGE_VERSION Version for semver/version strategies" |
| 392 | echo " Storage: $REGISTRY_STORAGE" | 527 | echo " TARGET_ARCH Architecture for arch strategy" |
| 393 | echo " Deploy images: $DEPLOY_DIR_IMAGE" | 528 | echo "" |
| 529 | echo "Configuration (baked from bitbake):" | ||
| 530 | echo " Registry URL: $REGISTRY_URL" | ||
| 531 | echo " Namespace: $REGISTRY_NAMESPACE" | ||
| 532 | echo " Tag strategy: $DEFAULT_TAG_STRATEGY" | ||
| 533 | echo " Target arch: $DEFAULT_TARGET_ARCH" | ||
| 534 | echo " Storage: $REGISTRY_STORAGE" | ||
| 535 | echo " Deploy images: $DEPLOY_DIR_IMAGE" | ||
| 394 | }} | 536 | }} |
| 395 | 537 | ||
| 396 | case "${{1:-help}}" in | 538 | case "${{1:-help}}" in |
| 397 | start) cmd_start ;; | 539 | start) cmd_start ;; |
| 398 | stop) cmd_stop ;; | 540 | stop) cmd_stop ;; |
| 399 | status) cmd_status ;; | 541 | status) cmd_status ;; |
| 400 | push) cmd_push ;; | 542 | push) cmd_push "$@" ;; |
| 401 | import) cmd_import "$@" ;; | 543 | import) cmd_import "$@" ;; |
| 402 | list) cmd_list ;; | 544 | list) cmd_list ;; |
| 403 | tags) cmd_tags "$@" ;; | 545 | tags) cmd_tags "$@" ;; |
