fix: allow first-party store service subdomains and registry images
Unit Tests / test (push) Successful in 11m25s
Unit Tests / test (push) Successful in 11m25s
Two manifest validation bugs blocked all store service installs: 1. service_store_manager.RESERVED_SUBDOMAINS included 'mail', which prevented the email service from using its required subdomain. Removed mail/calendar/files/webmail — they belong to official PIC store services and must be claimable by them. 2. manifest_validator required @sha256 digest pins on ALL images, including first-party git.pic.ngo/roof/* images that the PIC team builds and controls. service_store_manager._validate_manifest already only warned for first-party images; the secondary validator was stricter than intended, causing a hard reject on :latest tags. Aligned to warn-not-reject for first-party; malformed digests (when provided) are still a hard error. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,9 +5,12 @@ Both ServiceComposer and ServiceStoreManager import from here so validation logi
|
||||
lives in exactly one place and cannot be bypassed by taking either code path.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import re
|
||||
import yaml
|
||||
|
||||
logger = logging.getLogger('picell')
|
||||
|
||||
_SUBDOMAIN_RE = re.compile(r'^[a-z][a-z0-9-]{0,30}$')
|
||||
_BACKEND_RE = re.compile(r'^[A-Za-z0-9._-]+:\d{1,5}$')
|
||||
_CAP_ALLOWLIST = frozenset({
|
||||
@@ -73,13 +76,23 @@ def validate_manifest(manifest: dict) -> tuple:
|
||||
f'id must match ^[a-z][a-z0-9_-]{{0,30}}$, got: {manifest_id!r}'
|
||||
)
|
||||
|
||||
# image must be digest-pinned from git.pic.ngo/roof/*
|
||||
# image must come from git.pic.ngo/roof/*; if a digest IS provided it must be
|
||||
# valid; first-party images without a digest pin are allowed with a warning.
|
||||
image = manifest.get('image')
|
||||
if image is not None:
|
||||
if not isinstance(image, str) or not _IMAGE_DIGEST_RE.match(image):
|
||||
if not isinstance(image, str):
|
||||
errors.append(f'image must be a string, got: {image!r}')
|
||||
elif not image.startswith('git.pic.ngo/roof/'):
|
||||
errors.append(
|
||||
f'image must match git.pic.ngo/roof/*@sha256:<64-hex>, got: {image!r}'
|
||||
f'image must be from git.pic.ngo/roof/*, got: {image!r}'
|
||||
)
|
||||
elif '@sha256:' in image:
|
||||
if not _IMAGE_DIGEST_RE.match(image):
|
||||
errors.append(
|
||||
f'image digest must match @sha256:<64-hex>, got: {image!r}'
|
||||
)
|
||||
else:
|
||||
logger.warning('manifest image %s has no digest pin', image)
|
||||
|
||||
# container_name structural check
|
||||
cname = manifest.get('container_name')
|
||||
|
||||
Reference in New Issue
Block a user