45 lines
2.3 KiB
Python
45 lines
2.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Freeze a reusable SSH-site plan locally. No network, credentials or ledger writes."""
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import importlib
|
|
import sys
|
|
from pathlib import Path
|
|
from batch import validate,atomic_json
|
|
from site_adapter import SiteAdapter
|
|
|
|
|
|
def configure(plan,site):
|
|
allowed={'ops_root','node_root','batch_backup_root','node_backup_root','qa_root','provider_snapshot','exit_registry','ssh_config','preserved_service','formal_socks_port','gateway_containers','client_sha256'}
|
|
if set(site)-allowed:raise ValueError('Unexpected site configuration fields')
|
|
library=importlib.import_module('paramiko')
|
|
if library.__version__!='5.0.0':raise ValueError('Use the reviewed Paramiko 5.0.0 environment')
|
|
plan=dict(plan,context={**plan['context'],**site})
|
|
validate(plan)
|
|
adapter=SiteAdapter(plan,Path('/private/tmp/dpl-validation-only')) # Creates no directory or connections.
|
|
adapter.pool.close()
|
|
base=Path(__file__).resolve().parent
|
|
plan.pop('command_profile',None)
|
|
plan['adapter']=[str(Path(sys.executable).resolve()),str(base/'command-adapter.py')]
|
|
paths=[base/x for x in ('batch.py','site_adapter.py','ssh_pool.py','acceptance.py')]+[Path(site[k]) for k in ('ssh_config','exit_registry')]
|
|
plan['runtime']={'kind':'ssh-site-v1','python':str(Path(sys.executable).resolve()),'paramiko_version':library.__version__,'files':{str(p):hashlib.sha256(p.read_bytes()).hexdigest() for p in paths}}
|
|
return plan
|
|
|
|
|
|
def main():
|
|
p=argparse.ArgumentParser(description=__doc__)
|
|
for k in ('plan','site','output'):p.add_argument('--'+k,required=True)
|
|
a=p.parse_args()
|
|
try:
|
|
output=Path(a.output)
|
|
if not output.is_absolute() or output.exists() or output.is_symlink():raise ValueError('New absolute output file required; never mutate a frozen plan')
|
|
plan=configure(json.loads(Path(a.plan).read_text()),json.loads(Path(a.site).read_text()))
|
|
atomic_json(output,plan)
|
|
print(json.dumps({'state':'configured','nodes':len(plan['nodes']),'network':False,'ledger_writes':False}))
|
|
return 0
|
|
except Exception:
|
|
print(json.dumps({'state':'blocked','reason':'Inspect explicit site paths, registry and plan; no network performed'}));return 2
|
|
|
|
if __name__=='__main__':raise SystemExit(main())
|