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))