53 lines
3.2 KiB
Python
53 lines
3.2 KiB
Python
|
|
#!/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()
|