Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a06ecb-456a-71c2-b41e-0755d336e883
46 lines
3.5 KiB
Python
46 lines
3.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Deploy the reviewed backup entry point to activity-core without editing host checkouts."""
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import subprocess
|
|
from state_hub_preflight_lane import assert_cluster
|
|
ROOT=Path(__file__).resolve().parents[1]
|
|
FILES=['tools/cmd/forgejo-backup','scripts/capture_forgejo_archive.py','lib/railiance-backup-common.sh','lib/railiance-print.sh']
|
|
|
|
def bundle():
|
|
values={p.replace('/','__'):(ROOT/p).read_text() for p in FILES}
|
|
values['entrypoint']='#!/bin/sh\nexec /opt/railiance-backup-verified/tools/cmd/forgejo-backup "$@"\n'
|
|
digest=hashlib.sha256(json.dumps(values,sort_keys=True).encode()).hexdigest()
|
|
name='backup-verified-'+digest[:12]
|
|
cm={'apiVersion':'v1','kind':'ConfigMap','metadata':{'name':name,'namespace':'activity-core','labels':{'railiance.io/workplan':'RPF-WP-0029'}},'immutable':True,'data':values}
|
|
patch={'spec':{'template':{'spec':{'volumes':[{'name':'backup-verified','configMap':{'name':name,'defaultMode':365,'items':[{'key':p.replace('/','__'),'path':p} for p in FILES]+[{'key':'entrypoint','path':'entrypoint'}]}}], 'containers':[{'name':'worker','volumeMounts':[{'name':'backup-verified','mountPath':'/opt/railiance-backup-verified','readOnly':True},{'name':'backup-verified','mountPath':'/opt/railiance-platform/tools/cmd/forgejo-backup','subPath':'entrypoint','readOnly':True}]}]}}}}
|
|
return cm,patch
|
|
|
|
def main():
|
|
p=argparse.ArgumentParser(description=__doc__);p.add_argument('--kubeconfig',required=True);p.add_argument('--receipt',required=True);a=p.parse_args()
|
|
k=['kubectl','--kubeconfig',a.kubeconfig]
|
|
assert_cluster(k)
|
|
cm,patch=bundle()
|
|
def cmd(args,payload=None):
|
|
r=subprocess.run(k+args,input=None if payload is None else json.dumps(payload).encode(),capture_output=True,timeout=180)
|
|
if r.returncode: raise RuntimeError('backup bundle operation failed')
|
|
return r.stdout
|
|
deployment=json.loads(cmd(['-n','activity-core','get','deployment','actcore-worker','-o','json']))
|
|
# Select the existing worker container by its declared backup hostPath mount.
|
|
selected=[c['name'] for c in deployment['spec']['template']['spec']['containers'] if any(m['mountPath']=='/opt/railiance-platform' for m in c.get('volumeMounts',[]))]
|
|
if len(selected)!=1: raise RuntimeError('ambiguous worker container')
|
|
patch['spec']['template']['spec']['containers'][0]['name']=selected[0]
|
|
cmd(['apply','--dry-run=server','-f','-'],cm)
|
|
cmd(['-n','activity-core','patch','deployment','actcore-worker','--type=strategic','--dry-run=server','--patch-file=/dev/stdin'],patch)
|
|
cmd(['apply','-f','-'],cm)
|
|
cmd(['-n','activity-core','patch','deployment','actcore-worker','--type=strategic','--patch-file=/dev/stdin'],patch)
|
|
cmd(['-n','activity-core','rollout','status','deployment/actcore-worker','--timeout=150s'])
|
|
for file in FILES:
|
|
actual=cmd(['-n','activity-core','exec','deployment/actcore-worker','-c',selected[0],'--','sha256sum','/opt/railiance-backup-verified/'+file]).split()[0].decode()
|
|
if actual!=hashlib.sha256((ROOT/file).read_bytes()).hexdigest(): raise RuntimeError('worker source mismatch')
|
|
receipt={'status':'verified','configmap':cm['metadata']['name'],'worker_ready':True,'bundle_files_match':True,'host_checkout_edited':False,'rollback':'Restore deployment from its prior ReplicaSet with kubectl rollout undo; retain immutable ConfigMap until no revision references it.'}
|
|
Path(a.receipt).write_text(json.dumps(receipt,indent=2)+'\n')
|
|
print(json.dumps(receipt))
|
|
if __name__=='__main__': main()
|