58 lines
2.7 KiB
Python
58 lines
2.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Read-only release metadata check against an explicitly reviewed source ref.
|
|
|
|
This does not sign, generate, publish, build or validate artifact bytes. Signature,
|
|
artifact provenance, mirror parity and operator acceptance gates remain mandatory.
|
|
"""
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
import re
|
|
import subprocess
|
|
import tomllib
|
|
from urllib.parse import unquote, urlsplit
|
|
|
|
|
|
def validate(manifest, commit, backend_version, frontend_version):
|
|
version = manifest.get('version')
|
|
if not isinstance(version, str) or not re.fullmatch(r'\d+\.\d+\.\d+(?:-[A-Za-z0-9.]+)?', version):
|
|
raise ValueError('invalid release version')
|
|
if version != backend_version or version != frontend_version:
|
|
raise ValueError('manifest/backend/frontend versions differ from reviewed source')
|
|
if manifest.get('source_commit') != commit:
|
|
raise ValueError('manifest source_commit differs from reviewed source')
|
|
components = manifest.get('components')
|
|
if not isinstance(components, list) or not components:
|
|
raise ValueError('release has no components')
|
|
for component in components:
|
|
if component.get('new_version') != version:
|
|
raise ValueError('component version differs from reviewed release')
|
|
url = urlsplit(component.get('download_url', ''))
|
|
parts = unquote(url.path).split('/')
|
|
if (url.scheme != 'https' or not url.netloc or url.query or url.fragment
|
|
or len(parts) < 5 or parts[-4:-1] != ['releases', 'download', 'v' + version]
|
|
or not parts[-1] or parts[-1] in ('.', '..')):
|
|
raise ValueError('component URL does not bind the reviewed release version over HTTPS')
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('manifest', type=Path)
|
|
parser.add_argument('--source-ref', required=True, help='explicit reviewed full commit or tag')
|
|
args = parser.parse_args()
|
|
root = Path(__file__).resolve().parents[1]
|
|
def git(*arguments):
|
|
return subprocess.check_output(['git', '-C', str(root), *arguments], text=True).strip()
|
|
commit = git('rev-parse', '--verify', args.source_ref + '^{commit}')
|
|
backend = tomllib.loads(git('show', commit + ':core/archipelago/Cargo.toml'))['package']['version']
|
|
frontend = json.loads(git('show', commit + ':neode-ui/package.json'))['version']
|
|
validate(json.loads(args.manifest.read_text()), commit, backend, frontend)
|
|
print('Release metadata matches reviewed source ' + commit + '; artifact/signature acceptance remains separate')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
try:
|
|
main()
|
|
except (ValueError, KeyError, subprocess.CalledProcessError) as error:
|
|
raise SystemExit('REFUSED: ' + str(error))
|