56 lines
3.2 KiB
Python
56 lines
3.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Build a versioned flat operations bundle from committed files, without touching the worktree/index."""
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import re
|
|
import subprocess
|
|
import tempfile
|
|
|
|
FILES=('stage.php','promote.php','operations.php','rollback.php','readiness.php',
|
|
'verify-node.py','panel-acceptance.php','install-node.sh','renew-node.sh','dpl-node.service')
|
|
|
|
|
|
def build(repo,source,base,branch,bundle):
|
|
if not all(re.fullmatch('[0-9a-f]{40}',r) for r in (source,base)) or not re.fullmatch('codex/[A-Za-z0-9_/-]+',branch) or '..' in branch:
|
|
raise ValueError('Explicit full commits and a new codex branch required')
|
|
destination=Path(bundle)
|
|
if not destination.is_absolute() or destination.exists() or destination.is_symlink():raise ValueError('New absolute bundle path required')
|
|
def git(*args,input=None,env=None):
|
|
r=subprocess.run(['git','-C',str(repo),*args],input=input,stdout=subprocess.PIPE,stderr=subprocess.DEVNULL,env=env)
|
|
if r.returncode:raise ValueError('Git source/base/ref check failed')
|
|
return r.stdout
|
|
git('cat-file','-e',source+'^{commit}');git('cat-file','-e',base+'^{commit}');git('check-ref-format','refs/heads/'+branch)
|
|
exists=subprocess.run(['git','-C',str(repo),'show-ref','--verify','--quiet','refs/heads/'+branch]).returncode
|
|
if exists!=1:raise ValueError('Branch exists or outcome unknown; inspect it, do not overwrite')
|
|
with tempfile.TemporaryDirectory(prefix='dpl-ops-index-',dir=destination.parent) as temp:
|
|
env=dict(os.environ,GIT_INDEX_FILE=str(Path(temp)/'index'))
|
|
git('read-tree',base,env=env)
|
|
for name in FILES:
|
|
entry=git('ls-tree',source,'--','scripts/node-onboarding/'+name).decode().strip()
|
|
if not entry:raise ValueError('Required committed operations file missing')
|
|
mode,kind,rest=entry.split(' ',2);sha,_=rest.split('\t',1)
|
|
if kind!='blob' or mode not in ('100644','100755'):raise ValueError('Regular committed operations file required')
|
|
git('update-index','--add','--cacheinfo',mode+','+sha+','+name,env=env)
|
|
tree=git('write-tree',env=env).decode().strip()
|
|
commit=git('commit-tree',tree,'-p',base,input=('DPL operations snapshot\n\nSource commit: '+source+'\n').encode()).decode().strip()
|
|
git('update-ref','refs/heads/'+branch,commit,'0'*40)
|
|
git('bundle','create',str(destination),'refs/heads/'+branch)
|
|
destination.chmod(0o600)
|
|
return {'commit':commit,'source_commit':source,'branch':branch,'bundle_sha256':hashlib.sha256(destination.read_bytes()).hexdigest(),'files':len(FILES),'network':False}
|
|
|
|
|
|
def main():
|
|
p=argparse.ArgumentParser(description=__doc__)
|
|
p.add_argument('--repo',default=str(Path(__file__).resolve().parents[2]))
|
|
for k in ('source','base','branch','bundle'):p.add_argument('--'+k,required=True)
|
|
a=p.parse_args()
|
|
try:
|
|
print(json.dumps(build(a.repo,a.source,a.base,a.branch,a.bundle)));return 0
|
|
except Exception:
|
|
print(json.dumps({'state':'blocked','reason':'Inspect source/base, branch and bundle outcome before retry; no network performed'}));return 2
|
|
|
|
if __name__=='__main__':raise SystemExit(main())
|