Verify healthcheck in image archives before and after Podman import
Build and Publish TVJ EPG Image / build (push) Successful in 33s
Build and Publish TVJ EPG Image / build (push) Successful in 33s
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
Reference in New Issue
Block a user