Verify healthcheck in image archives before and after Podman import
Build and Publish TVJ EPG Image / build (push) Successful in 33s

This commit is contained in:
Lyncoln
2026-09-10 15:53:22 -04:00
parent 59f5a2614d
commit 6fc15d29d0
3 changed files with 54 additions and 11 deletions
+12 -2
View File
@@ -73,9 +73,18 @@ jobs:
-t "$FULL_IMAGE:latest" \ -t "$FULL_IMAGE:latest" \
-t "$FULL_IMAGE:v$VERSION" \ -t "$FULL_IMAGE:v$VERSION" \
-t "$FULL_IMAGE:sha-$SHORT_SHA" \ -t "$FULL_IMAGE:sha-$SHORT_SHA" \
--load \ --output "type=docker,dest=$RUNNER_TEMP/tvj-epg-built.tar" \
. .
- name: Verify BuildKit export and load image
run: |
set -euo pipefail
python3 scripts/verify_image_healthcheck.py --archive "$RUNNER_TEMP/tvj-epg-built.tar"
docker load --input "$RUNNER_TEMP/tvj-epg-built.tar"
- name: Report container engine versions
run: docker version
- name: Verify healthcheck and publish - name: Verify healthcheck and publish
env: env:
REGISTRY: ${{ vars.REGISTRY }} REGISTRY: ${{ vars.REGISTRY }}
@@ -84,7 +93,8 @@ jobs:
set -euo pipefail set -euo pipefail
SHORT_SHA="${GITHUB_SHA::7}" SHORT_SHA="${GITHUB_SHA::7}"
FULL_IMAGE="$REGISTRY/david/tvj-epg" FULL_IMAGE="$REGISTRY/david/tvj-epg"
docker image inspect "$FULL_IMAGE:latest" | python3 scripts/verify_image_healthcheck.py docker image save --output "$RUNNER_TEMP/tvj-epg-loaded.tar" "$FULL_IMAGE:latest"
python3 scripts/verify_image_healthcheck.py --archive "$RUNNER_TEMP/tvj-epg-loaded.tar"
docker push "$FULL_IMAGE:latest" docker push "$FULL_IMAGE:latest"
docker push "$FULL_IMAGE:v$VERSION" docker push "$FULL_IMAGE:v$VERSION"
docker push "$FULL_IMAGE:sha-$SHORT_SHA" docker push "$FULL_IMAGE:sha-$SHORT_SHA"
+4 -2
View File
@@ -131,8 +131,10 @@ podman inspect --format '{{.State.Health.Status}}' tvj-epg
No health environment variable or Quadlet `HealthCmd` is required. New containers No health environment variable or Quadlet `HealthCmd` is required. New containers
inherit the image healthcheck unless deployment settings explicitly disable it. inherit the image healthcheck unless deployment settings explicitly disable it.
Build with `podman build --format docker` to preserve healthcheck metadata; OCI Build with `podman build --format docker` to preserve healthcheck metadata; OCI
builds can omit it. The Gitea publishing workflow verifies the image healthcheck before pushing builds can omit it. The Gitea publishing workflow verifies the healthcheck in the Docker archive
tags. It loads and verifies the image before publishing. exported by BuildKit, then loads it through the Podman socket and verifies a
second archive exported from the loaded image before pushing tags. This checks
the actual image configuration rather than the compatibility API inspect view.
After an image update, pull the new image and recreate the container through its After an image update, pull the new image and recreate the container through its
Quadlet service to acquire the metadata. An existing container does not acquire Quadlet service to acquire the metadata. An existing container does not acquire
+38 -7
View File
@@ -1,10 +1,41 @@
"""Fail publication if the built image lost its automatic healthcheck.""" """Verify health metadata in inspect JSON or an exported Docker image archive."""
import argparse
import json import json
import sys import sys
import tarfile
images = json.load(sys.stdin)
healthcheck = images[0].get("Config", {}).get("Healthcheck") or {} def archive_config(path):
expected = ["CMD", "python", "-m", "epg.healthcheck"] with tarfile.open(path) as archive:
if healthcheck.get("Test") != expected: manifest_file = archive.extractfile('manifest.json')
sys.exit("Image healthcheck missing or incorrect; refusing publication") if manifest_file is None:
print("Image healthcheck verified") raise ValueError('Archive has no Docker manifest')
manifest = json.load(manifest_file)
if not manifest:
raise ValueError('Archive manifest is empty')
config_file = archive.extractfile(manifest[0]['Config'])
if config_file is None:
raise ValueError('Archive has no image config')
return json.load(config_file).get('config', {})
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--archive', help='Docker image tar archive to verify')
args = parser.parse_args()
try:
config = archive_config(args.archive) if args.archive else json.load(sys.stdin)[0].get('Config', {})
healthcheck = config.get('Healthcheck') or {}
print('Image config Healthcheck:', json.dumps(healthcheck), flush=True)
expected = ['CMD', 'python', '-m', 'epg.healthcheck']
if healthcheck.get('Test') != expected:
raise ValueError('Image config lacks the expected healthcheck; refusing publication')
except (OSError, ValueError, KeyError, IndexError, tarfile.TarError) as error:
print(str(error), file=sys.stderr)
return 1
print('Image healthcheck verified')
return 0
if __name__ == '__main__':
sys.exit(main())