Files
archy/scripts/package-d09-c01.py

68 lines
3.0 KiB
Python

#!/usr/bin/env python3
"""Reproducible source owner package; no installation or admission authority.
Fixed runtime destinations only. Native ELF/signature and external policy inputs
are separately pinned release inputs. Refuses overwriting an existing artifact.
"""
import hashlib
import ast
import io
import json
from pathlib import Path
import sys
import tarfile
ROOT=Path(__file__).resolve().parents[1]
sys.path.insert(0,str(ROOT/'scripts'))
def modules(root):
# Read the provisioner's declaration without executing privileged modules.
tree=ast.parse((root/'scripts/provision-d09-lifecycle.py').read_text())
declared=None
for node in tree.body:
if isinstance(node,ast.Assign) and any(isinstance(t,ast.Name) and t.id=='MODULES' for t in node.targets):
declared=ast.literal_eval(node.value)
if not declared:
raise ValueError('missing provisioner module declaration')
names=set(declared)
for pattern in ('c01_*.py','d09_*.py','host_*.py'):
names.update(p.name for p in (root/'scripts').glob(pattern))
return sorted(names)
def package(destination):
files={}
for name in modules(ROOT):
files['usr/libexec/'+name]=(ROOT/'scripts'/name,0o644)
if name.startswith(('c01_','host_')) or name=='runtime_owner.py':
files['usr/lib/archipelago/'+name]=(ROOT/'scripts'/name,0o644)
for source,target in {'d09-lifecycle-owner.py':'archipelago-d09-owner',
'd09-lifecycle-client.py':'archipelago-d09ctl','provision-d09-lifecycle.py':'archipelago-d09-provision',
'dashboard-public-guard.py':'archipelago-d09-public-guard'}.items():
files['usr/libexec/'+target]=(ROOT/'scripts'/source,0o755)
for source,target in {'prepare-terminal-workspace.sh':'archipelago-d09-prepare-workspace',
'60-terminal-workspace.conf':'archipelago-d09-terminal-workspace.conf'}.items():
files['usr/libexec/'+target]=(ROOT/'image-recipe/configs'/source,0o755 if source.endswith('.sh') else 0o644)
# Pre-read all inputs so an absent helper cannot leave a partial artifact.
payload={}
for target,(source,mode) in files.items():
if source.is_symlink() or not source.is_file():
raise ValueError(f'not a regular package input: {source}')
payload[target]=(source.read_bytes(),mode)
manifest={}
with tarfile.open(destination,'x') as archive:
for target,(raw,mode) in sorted(payload.items()):
manifest[target]=hashlib.sha256(raw).hexdigest()
info=tarfile.TarInfo(target);info.mode=mode;info.uid=info.gid=info.mtime=0;info.size=len(raw)
archive.addfile(info,io.BytesIO(raw))
raw=json.dumps(manifest,sort_keys=True,separators=(',',':')).encode()
info=tarfile.TarInfo('D09-C01-SOURCE-SHA256.json');info.mode=0o444;info.size=len(raw)
archive.addfile(info,io.BytesIO(raw))
return manifest
if __name__=='__main__':
if len(sys.argv)!=2: raise SystemExit('one new artifact pathname required')
package(Path(sys.argv[1]))