the-custodian/docs/changes/2026-11-01-scanner-json-digest/native-r4/test_controller_image.py

71 lines
4.2 KiB
Python
Raw Normal View History

"""Run the actual controller migration/stream framing in pinned Forgejo userspace."""
from pathlib import Path
import importlib.util
import json
import subprocess
HERE=Path(__file__).resolve().parent
IMAGE='code.forgejo.org/forgejo/forgejo@sha256:691657c7e64af7730a2e885e6d022cf16ae60724931c5392bd61838afadcaeba'
spec=importlib.util.spec_from_file_location('r4',HERE/'rollout.py');r=importlib.util.module_from_spec(spec);spec.loader.exec_module(r)
m=r.support();C=json.loads((r.ORIGINAL/'inputs.json').read_bytes())
def main():
argv=['docker','run','-d','--rm','--network=none','--user=1000:1000','--read-only','--cap-drop=ALL','--security-opt=no-new-privileges:true',
'--tmpfs','/tmp:rw,size=64m,uid=1000,gid=1000','--tmpfs','/data:rw,size=64m,uid=1000,gid=1000',
'--mount','type=bind,src='+str(r.ORIGINAL)+',dst=/original,readonly','--entrypoint=/bin/sh',IMAGE,'-c','tail -f /dev/null']
created=subprocess.run(argv,capture_output=True,timeout=30)
if created.returncode:raise RuntimeError('fixture-container-start-failed')
cid=created.stdout.decode().strip();events=[];phase='setup'
def shell(script,args=(),data=None,timeout=180):
q=subprocess.run(['docker','exec','-i',cid,'/bin/sh','-c',script,'sh',*args],input=data,capture_output=True,timeout=timeout)
if q.returncode:raise RuntimeError('fixture-command-failed:'+phase)
return q.stdout
try:
script=r'''set -eu
n=0
while test "$n" -lt 171; do
name=$(printf test%03d "$n")
repo=/data/git/gitea-repositories/coulomb/$name.git
for hook in pre-receive post-receive update pre-receive.d/gitea post-receive.d/gitea update.d/gitea pre-receive.d/20-authorize post-receive.d/20-notify; do
path="$repo/hooks/$hook";mkdir -p "${path%/*}"
printf 'unchanged generated hook\n' > "$path";chmod 775 "$path"
done
file="$repo/hooks/pre-receive.d/10-custodian-secret-scan"
cp /original/before-hook "$file";chmod 775 "$file"
if test "$n" -lt 3; then chmod 755 "$file"; fi
if test "$n" -lt 16; then chmod 755 "$repo/hooks/pre-receive"; fi
n=$((n+1))
done
'''
shell(script)
class TestRunner(r.make_runner(m)):
def shell(self,pod,script,args=(),data=None,timeout=90):return shell(script,args,data,max(timeout,180))
def event(self,name,**fields):events.append({'event':name,**fields})
runner=TestRunner(C);before=runner.inventory('fixture',{C['old_hook_sha256']});runner.receipt['before_hooks']=before
phase='initial-migration';runner.migrate('fixture','apply')
phase='simulate-fsgroup';shell("find /data -type f -exec chmod g+w {} +")
phase='post-recreate-migration';runner.migrate('fixture','apply')
assert events[-1]['group_write_additions_reversed']==19
phase='simulate-second-fsgroup';shell("find /data -type f -exec chmod g+w {} +")
phase='rollback-migration';runner.migrate('fixture','rollback')
after=runner.inventory('fixture',{C['old_hook_sha256']});assert after==before
# Unknown protected data must stop before any scanner rewrite.
phase='protected-drift';shell("printf 'owner content drift\\n' > /data/git/gitea-repositories/coulomb/test000.git/hooks/pre-receive")
try:runner.migrate('fixture','apply')
except RuntimeError as exc:assert str(exc)=='protected-hook-content-drift'
else:raise AssertionError('protected-drift-not-refused')
still=runner.inventory('fixture',{C['old_hook_sha256']});assert len(still)==171
result={'result':'passed','image':IMAGE,'repositories':171,'hooks':sum(len(x) for x in before.values()),
'cases':['initial-migration','post-recreate-mode-restoration','rollback-mode-restoration','protected-drift-before-write'],
'group_write_additions_reversed':19,'events':events,'native_mutation':False}
(HERE/'controller-image-tests.json').write_text(json.dumps(result,indent=2)+'\n');print(json.dumps(result))
except Exception as exc:
(HERE/'controller-image-tests-failed.json').write_text(json.dumps({'result':'unverified','phase':phase,'error':type(exc).__name__,'image':IMAGE,'native_mutation':False},indent=2)+'\n')
raise
finally:
subprocess.run(['docker','stop','--time=0',cid],capture_output=True,timeout=30)
if __name__=='__main__':main()