Resolve runtime dependency platform digests
This commit is contained in:
@@ -195,18 +195,33 @@ jobs:
|
|||||||
WEB_DIGEST="sha256:$(sha256sum runtime-output/web-index.json | cut -d' ' -f1)"
|
WEB_DIGEST="sha256:$(sha256sum runtime-output/web-index.json | cut -d' ' -f1)"
|
||||||
python tools/release/resolve-oci-platforms.py --repository git.add-ideas.de/govoplan/runtime-api --index-digest "$API_DIGEST" --index runtime-output/api-index.json --output runtime-output/api-metadata.json
|
python tools/release/resolve-oci-platforms.py --repository git.add-ideas.de/govoplan/runtime-api --index-digest "$API_DIGEST" --index runtime-output/api-index.json --output runtime-output/api-metadata.json
|
||||||
python tools/release/resolve-oci-platforms.py --repository git.add-ideas.de/govoplan/runtime-web --index-digest "$WEB_DIGEST" --index runtime-output/web-index.json --output runtime-output/web-metadata.json
|
python tools/release/resolve-oci-platforms.py --repository git.add-ideas.de/govoplan/runtime-web --index-digest "$WEB_DIGEST" --index runtime-output/web-index.json --output runtime-output/web-metadata.json
|
||||||
- name: Exercise amd64 and arm64 runtime images
|
- name: Resolve managed dependency platform images
|
||||||
working-directory: govoplan
|
working-directory: govoplan
|
||||||
env:
|
env:
|
||||||
POSTGRES_IMAGE: ${{ inputs.postgres_image }}
|
POSTGRES_IMAGE: ${{ inputs.postgres_image }}
|
||||||
REDIS_IMAGE: ${{ inputs.redis_image }}
|
REDIS_IMAGE: ${{ inputs.redis_image }}
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools inspect "$POSTGRES_IMAGE" --raw > runtime-output/postgres-index.json
|
||||||
|
docker buildx imagetools inspect "$REDIS_IMAGE" --raw > runtime-output/redis-index.json
|
||||||
|
python tools/release/resolve-oci-platforms.py \
|
||||||
|
--repository "${POSTGRES_IMAGE%@*}" \
|
||||||
|
--index-digest "${POSTGRES_IMAGE##*@}" \
|
||||||
|
--index runtime-output/postgres-index.json \
|
||||||
|
--output runtime-output/postgres-metadata.json
|
||||||
|
python tools/release/resolve-oci-platforms.py \
|
||||||
|
--repository "${REDIS_IMAGE%@*}" \
|
||||||
|
--index-digest "${REDIS_IMAGE##*@}" \
|
||||||
|
--index runtime-output/redis-index.json \
|
||||||
|
--output runtime-output/redis-metadata.json
|
||||||
|
- name: Exercise amd64 and arm64 runtime images
|
||||||
|
working-directory: govoplan
|
||||||
run: |
|
run: |
|
||||||
for ARCH in amd64 arm64; do
|
for ARCH in amd64 arm64; do
|
||||||
.runtime-build/bin/python tools/checks/runtime-image-smoke.py \
|
.runtime-build/bin/python tools/checks/runtime-image-smoke.py \
|
||||||
--api-metadata runtime-output/api-metadata.json \
|
--api-metadata runtime-output/api-metadata.json \
|
||||||
--web-metadata runtime-output/web-metadata.json \
|
--web-metadata runtime-output/web-metadata.json \
|
||||||
--postgres-image "$POSTGRES_IMAGE" \
|
--postgres-metadata runtime-output/postgres-metadata.json \
|
||||||
--redis-image "$REDIS_IMAGE" \
|
--redis-metadata runtime-output/redis-metadata.json \
|
||||||
--platform "linux/$ARCH" \
|
--platform "linux/$ARCH" \
|
||||||
--output "runtime-output/evidence/runtime-smoke-$ARCH.json"
|
--output "runtime-output/evidence/runtime-smoke-$ARCH.json"
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -157,6 +157,9 @@ migrations against the pinned PostgreSQL image, reach API and WebUI readiness
|
|||||||
as non-root/read-only processes, and complete a task through the pinned Redis
|
as non-root/read-only processes, and complete a task through the pinned Redis
|
||||||
image and packaged worker. Sanitized per-platform smoke receipts are retained
|
image and packaged worker. Sanitized per-platform smoke receipts are retained
|
||||||
as immutable release assets.
|
as immutable release assets.
|
||||||
|
PostgreSQL and Redis indexes are resolved to untagged platform-child digests
|
||||||
|
before each smoke run. This keeps the evidence architecture-specific and
|
||||||
|
avoids retargeting one local Docker tag between incompatible platforms.
|
||||||
The smoke also proves a bounded post-migration table contract and aborts as
|
The smoke also proves a bounded post-migration table contract and aborts as
|
||||||
soon as a required container exits, rather than allowing a dead process to
|
soon as a required container exits, rather than allowing a dead process to
|
||||||
consume the full readiness timeout.
|
consume the full readiness timeout.
|
||||||
|
|||||||
@@ -145,6 +145,9 @@ class RuntimeDistributionBuildTests(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertIn('for ARCH in amd64 arm64; do', workflow)
|
self.assertIn('for ARCH in amd64 arm64; do', workflow)
|
||||||
self.assertIn("tools/checks/runtime-image-smoke.py", workflow)
|
self.assertIn("tools/checks/runtime-image-smoke.py", workflow)
|
||||||
|
self.assertIn("Resolve managed dependency platform images", workflow)
|
||||||
|
self.assertIn("--postgres-metadata", workflow)
|
||||||
|
self.assertIn("--redis-metadata", workflow)
|
||||||
self.assertIn("runtime-smoke-amd64.json", workflow)
|
self.assertIn("runtime-smoke-amd64.json", workflow)
|
||||||
self.assertIn("runtime-smoke-arm64.json", workflow)
|
self.assertIn("runtime-smoke-arm64.json", workflow)
|
||||||
|
|
||||||
@@ -244,6 +247,15 @@ class RuntimeDistributionBuildTests(unittest.TestCase):
|
|||||||
"registry.example/govoplan/api@sha256:" + "1" * 64,
|
"registry.example/govoplan/api@sha256:" + "1" * 64,
|
||||||
metadata["platforms"]["linux/amd64"],
|
metadata["platforms"]["linux/amd64"],
|
||||||
)
|
)
|
||||||
|
dependency_metadata = OCI.resolve_platforms(
|
||||||
|
index,
|
||||||
|
repository="registry.example:5000/library/postgres:16-alpine",
|
||||||
|
index_digest="sha256:" + "a" * 64,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
"registry.example:5000/library/postgres@sha256:" + "2" * 64,
|
||||||
|
dependency_metadata["platforms"]["linux/arm64"],
|
||||||
|
)
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory(prefix="govoplan-runtime-finalize-") as value:
|
with tempfile.TemporaryDirectory(prefix="govoplan-runtime-finalize-") as value:
|
||||||
root = Path(value)
|
root = Path(value)
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ def build_parser() -> argparse.ArgumentParser:
|
|||||||
parser = argparse.ArgumentParser(description=__doc__)
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
parser.add_argument("--api-metadata", type=Path, required=True)
|
parser.add_argument("--api-metadata", type=Path, required=True)
|
||||||
parser.add_argument("--web-metadata", type=Path, required=True)
|
parser.add_argument("--web-metadata", type=Path, required=True)
|
||||||
parser.add_argument("--postgres-image", required=True)
|
parser.add_argument("--postgres-metadata", type=Path, required=True)
|
||||||
parser.add_argument("--redis-image", required=True)
|
parser.add_argument("--redis-metadata", type=Path, required=True)
|
||||||
parser.add_argument("--platform", choices=sorted(PLATFORMS), required=True)
|
parser.add_argument("--platform", choices=sorted(PLATFORMS), required=True)
|
||||||
parser.add_argument("--output", type=Path, required=True)
|
parser.add_argument("--output", type=Path, required=True)
|
||||||
parser.add_argument("--timeout-seconds", type=float, default=600.0)
|
parser.add_argument("--timeout-seconds", type=float, default=600.0)
|
||||||
@@ -625,11 +625,17 @@ def main() -> int:
|
|||||||
try:
|
try:
|
||||||
api_image = platform_image(args.api_metadata, args.platform, "API")
|
api_image = platform_image(args.api_metadata, args.platform, "API")
|
||||||
web_image = platform_image(args.web_metadata, args.platform, "Web")
|
web_image = platform_image(args.web_metadata, args.platform, "Web")
|
||||||
|
postgres_image = platform_image(
|
||||||
|
args.postgres_metadata,
|
||||||
|
args.platform,
|
||||||
|
"PostgreSQL",
|
||||||
|
)
|
||||||
|
redis_image = platform_image(args.redis_metadata, args.platform, "Redis")
|
||||||
evidence = run_smoke(
|
evidence = run_smoke(
|
||||||
api_image=api_image,
|
api_image=api_image,
|
||||||
web_image=web_image,
|
web_image=web_image,
|
||||||
postgres_image=args.postgres_image,
|
postgres_image=postgres_image,
|
||||||
redis_image=args.redis_image,
|
redis_image=redis_image,
|
||||||
platform=args.platform,
|
platform=args.platform,
|
||||||
timeout=args.timeout_seconds,
|
timeout=args.timeout_seconds,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -21,6 +21,12 @@ def resolve_platforms(
|
|||||||
) -> dict[str, object]:
|
) -> dict[str, object]:
|
||||||
if not repository or "@" in repository or any(value.isspace() for value in repository):
|
if not repository or "@" in repository or any(value.isspace() for value in repository):
|
||||||
raise ValueError("repository must be an unpinned OCI repository name")
|
raise ValueError("repository must be an unpinned OCI repository name")
|
||||||
|
last_slash = repository.rfind("/")
|
||||||
|
last_colon = repository.rfind(":")
|
||||||
|
if last_colon > last_slash:
|
||||||
|
repository = repository[:last_colon]
|
||||||
|
if not repository:
|
||||||
|
raise ValueError("repository must be an unpinned OCI repository name")
|
||||||
if DIGEST.fullmatch(index_digest) is None:
|
if DIGEST.fullmatch(index_digest) is None:
|
||||||
raise ValueError("index digest must be sha256:<hex>")
|
raise ValueError("index digest must be sha256:<hex>")
|
||||||
if not isinstance(payload, dict) or not isinstance(payload.get("manifests"), list):
|
if not isinstance(payload, dict) or not isinstance(payload.get("manifests"), list):
|
||||||
|
|||||||
Reference in New Issue
Block a user