the-custodian/docs/changes/CUST-WP-0080/observe_forge_inventory.py
codex eb93eeedfd
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Reject generated-only commits / generated-commit-guard (push) Successful in 7s
security: preserve verified Forgejo scanner activation and reconcile recovery
2026-10-13 11:31:06 +02:00

34 lines
3.5 KiB
Python

import json, subprocess, re, datetime
def command(args, payload=None):
r=subprocess.run(['kubectl','--request-timeout=15s',*args],input=payload,capture_output=True,text=True,timeout=35)
if r.returncode: raise SystemExit('Metadata observation failed; raw output suppressed.')
return r.stdout
def ready(p):
return p.get('status',{}).get('phase')=='Running' and any(c.get('type')=='Ready' and c.get('status')=='True' for c in p.get('status',{}).get('conditions',[]))
nodes=json.loads(command(['get','nodes','-o','json']))['items']
if len(nodes)!=1 or not any(a['address']=='92.205.62.239' for a in nodes[0]['status']['addresses']): raise SystemExit('Unexpected target host.')
pods=json.loads(command(['-n','forgejo','get','pods','-o','json']))['items']
apps=[p for p in pods if ready(p) and any(c['name']=='gitea' for c in p['spec']['containers'])]
if len(apps)!=1: raise SystemExit('Ambiguous forge pod.')
app=apps[0]
cluster=json.loads(command(['-n','databases','get','cluster','forgejo-db','-o','json']))
primary=cluster.get('status',{}).get('currentPrimary')
if not primary or not re.fullmatch(r'forgejo-db-[0-9]+',primary): raise SystemExit('No unambiguous database primary.')
query='BEGIN READ ONLY;\nSET LOCAL statement_timeout = \'5s\';\nSELECT json_build_object(\n \'repositories\',(SELECT json_agg(row_to_json(x) ORDER BY x.id) FROM\n (SELECT r.id,u.lower_name AS owner,r.lower_name AS name,r.is_private,r.is_empty,r.is_archived,r.is_mirror,r.is_fork,r.object_format_name,r.git_size,r.lfs_size,\n (SELECT count(*) FROM protected_branch b WHERE b.repo_id=r.id) AS protected_branch_rules,\n (SELECT json_agg(ru.type ORDER BY ru.type) FROM repo_unit ru WHERE ru.repo_id=r.id) AS unit_types\n FROM repository r JOIN "user" u ON u.id=r.owner_id) x)\n);\nROLLBACK;'
raw=command(['-n','databases','exec','-i',primary,'-c','postgres','--','psql','-X','-U','postgres','-d','forgejo','-Atq','-v','ON_ERROR_STOP=1'],query)
metadata=json.loads(raw)
versions=command(['-n','forgejo','exec',app['metadata']['name'],'-c','gitea','--','/usr/bin/git','--version']).strip()
directories=command(['-n','forgejo','exec',app['metadata']['name'],'-c','gitea','--','find','/data/git/gitea-repositories','-mindepth','2','-maxdepth','2','-type','d','-name','*.git']).splitlines()
relative=[]
for directory in directories:
path=directory.removeprefix('/data/git/gitea-repositories/')
if not re.fullmatch(r'[a-z0-9_.-]+/[a-z0-9_.-]+\.git',path): raise SystemExit('Unrecognized repository path; output suppressed.')
relative.append(path)
for repo in metadata['repositories']:
if not re.fullmatch(r'[a-z0-9_.-]+',repo['owner']) or not re.fullmatch(r'[a-z0-9_.-]+',repo['name']): raise SystemExit('Unrecognized repository identity; output suppressed.')
repo['main_directory_present']=repo['owner']+'/'+repo['name']+'.git' in relative
repo['wiki_directory_present']=repo['owner']+'/'+repo['name']+'.wiki.git' in relative
result={'observed_at':datetime.datetime.now(datetime.timezone.utc).isoformat(),'host':'railiance01','forge_pod':app['metadata']['name'],'forge_pod_uid':app['metadata']['uid'],'containers':[{'name':c['name'],'image':c['image'],'resources':c.get('resources',{})} for c in app['spec']['containers']],'database_primary':primary,'git_version':versions,'repositories':metadata['repositories'],'repository_directories':sorted(relative),'scope':'Read-only metadata: DB identifiers/flags/sizes/unit types/protected-rule counts and repository-directory presence. No descriptions, URLs, credentials, repository contents or logs read.'}
print(json.dumps(result,indent=2))