Deliver validated backup capture to worker via immutable runtime bundle
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a06ecb-456a-71c2-b41e-0755d336e883
This commit is contained in:
parent
480d181549
commit
34b9385bee
1 changed files with 46 additions and 0 deletions
46
scripts/deploy_verified_backup_bundle.py
Normal file
46
scripts/deploy_verified_backup_bundle.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#!/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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue