#!/usr/bin/env python3 """Project only names, image paths and GitOps source fields; never query Secrets.""" import argparse import json import subprocess from urllib.parse import urlsplit, urlunsplit def get(resource): r=subprocess.run(['kubectl','get',resource,'-A','-o','json'],capture_output=True,text=True) if r.returncode: raise RuntimeError('Read failed for '+resource) return json.loads(r.stdout)['items'] def safe_url(value): p=urlsplit(value) return urlunsplit((p.scheme,p.hostname or '',p.path,'','')) if p.scheme else value.split('?')[0] def main(): ap=argparse.ArgumentParser(description=__doc__) ap.add_argument('--output',required=True) args=ap.parse_args() nodes=get('nodes') addresses={a['address'] for n in nodes for a in n.get('status',{}).get('addresses',[])} if '92.205.62.239' not in addresses: raise RuntimeError('Expected railiance01 node IP not found; refusing wrong-cluster capture') rows=[] for resource in ['deployments','statefulsets','daemonsets','jobs','cronjobs','services.serving.knative.dev','clusters.postgresql.cnpg.io']: for item in get(resource): spec=item.get('spec',{}) pod=spec.get('jobTemplate',{}).get('spec',{}).get('template',{}).get('spec',{}) if item['kind']=='CronJob' else spec.get('template',{}).get('spec',{}) images=sorted({c['image'] for key in ['containers','initContainers'] for c in pod.get(key,[]) if c.get('image')}) if spec.get('imageName'): images.append(spec['imageName']) rows.append({'kind':item['kind'],'namespace':item['metadata'].get('namespace'),'name':item['metadata']['name'], 'images':images,'image_pull_secret_names':sorted(x['name'] for x in pod.get('imagePullSecrets',[])), 'owners':[{'kind':o['kind'],'name':o['name']} for o in item['metadata'].get('ownerReferences',[])]}) apps=[] for i in get('applications.argoproj.io'): s=i['spec']; sources=s.get('sources',[])+([s['source']] if s.get('source') else []) apps.append({'name':i['metadata']['name'],'namespace':i['metadata']['namespace'],'project':s.get('project'), 'sources':[{k:safe_url(v) if k=='repoURL' else v for k,v in src.items() if k in ['repoURL','path','chart','targetRevision']} for src in sources]}) projects=[{'name':i['metadata']['name'],'namespace':i['metadata']['namespace'], 'sourceRepos':[safe_url(x) for x in i['spec'].get('sourceRepos',[])]} for i in get('appprojects.argoproj.io')] result={'cluster':'railiance01','node_ip_verified':'92.205.62.239','coverage':'All namespaces; workload controller units, not pod/replicaset duplicates. Jobs may be historical; owners retained. Secrets never queried.', 'workloads':sorted(rows,key=lambda r:(r['namespace'] or '',r['kind'],r['name'])), 'applications':sorted(apps,key=lambda r:r['name']),'projects':sorted(projects,key=lambda r:r['name'])} with open(args.output,'w') as f: json.dump(result,f,indent=2,sort_keys=True);f.write('\n') print('Captured',len(rows),'controllers;',len(apps),'Applications;',len(projects),'AppProjects') if __name__=='__main__': main()