Some checks failed
Use-case regression gate / usecase-regressions (push) Has been cancelled
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a18e2d-f513-7742-b8e7-12af0b744ff3
66 lines
6.8 KiB
Python
66 lines
6.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Prepare a fixed email-connect sender candidate using reads and server dry-runs.
|
|
|
|
No create/apply/patch is executed without --dry-run=server. This does not admit
|
|
OpenBao access, install a resource, scan a mailbox or send an email.
|
|
"""
|
|
import argparse, os, subprocess,shlex,json,hashlib
|
|
from pathlib import Path
|
|
from datetime import datetime,timezone
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('--output', type=Path, required=True)
|
|
args = parser.parse_args()
|
|
if not __debug__:
|
|
raise SystemExit('Assertions must be enabled')
|
|
if args.output.exists():
|
|
raise SystemExit('Choose a new candidate output; existing evidence is retained')
|
|
code=r'''
|
|
import subprocess,json,hashlib
|
|
from datetime import datetime,timezone
|
|
K=['k3s','kubectl']
|
|
def get(kind,name):
|
|
return json.loads(subprocess.check_output(K+['get',kind,name,'-n','email-connect','-o','json'],timeout=20))
|
|
d=get('deployment','email-connect');cm=get('configmap','email-connect-config')
|
|
assert d['metadata']['uid']=='2e98e52a-a2d1-4c52-9ac8-6efbc0a18e72'
|
|
assert d['status']['observedGeneration']==d['metadata']['generation'] and d['status']['readyReplicas']==1
|
|
c=d['spec']['template']['spec']['containers'][0]
|
|
assert c['name']=='email-connect' and len(d['spec']['template']['spec']['containers'])==1
|
|
assert c['image']=='forgejo.coulomb.social/coulomb/email-connect@sha256:5d79fd1fec15d3a279383820f170d263de3a55f5308df01a0a34732ebaea6d3d'
|
|
assert c['envFrom']==[{'configMapRef':{'name':'email-connect-config'}},{'secretRef':{'name':'email-connect-runtime','optional':False}}]
|
|
assert cm['data']['EMAIL_CONNECT_SENDER']=='forgejo@coulomb.social'
|
|
assert all(e['name'] not in {'EMAIL_CONNECT_SMTP_USERNAME','EMAIL_CONNECT_SMTP_PASSWORD','EMAIL_CONNECT_SENDER'} for e in c['env'])
|
|
assert c['env']==[{'name':'EMAIL_CONNECT_CALLERS_FILE','value':'/etc/email-connect-callers/callers.json'}], 'unexpected environment; refuse to copy unknown values'
|
|
role='email-connect-platform-smtp-eso';store='openbao-email-connect-platform-smtp';secret='email-connect-platform-smtp'
|
|
def obj(api,kind,name,**extra):return {'apiVersion':api,'kind':kind,'metadata':{'name':name,'namespace':'email-connect'},**extra}
|
|
resources=[obj('v1','ServiceAccount',role,automountServiceAccountToken=False),obj('external-secrets.io/v1','SecretStore',store,spec={'provider':{'vault':{'server':'http://openbao.openbao.svc:8200','path':'platform','version':'v2','auth':{'kubernetes':{'mountPath':'kubernetes','role':role,'serviceAccountRef':{'name':role,'audiences':['openbao']}}}}}}),obj('external-secrets.io/v1','ExternalSecret',secret,spec={'refreshInterval':'1h','secretStoreRef':{'kind':'SecretStore','name':store},'target':{'name':secret,'creationPolicy':'Owner','deletionPolicy':'Retain','template':{'type':'Opaque'}},'data':[{'secretKey':'password','remoteRef':{'key':'workloads/railiance-telemetry/smtp','property':'SMTP_PASSWORD'}}]})]
|
|
for x in resources:
|
|
out=subprocess.check_output(K+['get',x['kind'],x['metadata']['name'],'-n','email-connect','--ignore-not-found','-o','name'],timeout=20)
|
|
assert not out.strip(),'candidate resource already exists'
|
|
base='/spec/template/spec/containers/0'
|
|
dpatch=[{'op':'test','path':'/metadata/uid','value':d['metadata']['uid']},{'op':'test','path':'/metadata/resourceVersion','value':d['metadata']['resourceVersion']},{'op':'test','path':base+'/image','value':c['image']},{'op':'test','path':base+'/envFrom','value':c['envFrom']},{'op':'test','path':base+'/env','value':c['env']},{'op':'replace','path':base+'/envFrom','value':[{'configMapRef':{'name':'email-connect-config'}}]},{'op':'replace','path':base+'/env','value':c['env']+[{'name':'EMAIL_CONNECT_SMTP_USERNAME','value':'platform@coulomb.social'},{'name':'EMAIL_CONNECT_SMTP_PASSWORD','valueFrom':{'secretKeyRef':{'name':secret,'key':'password','optional':False}}}]}]
|
|
cmpatch=[{'op':'test','path':'/metadata/uid','value':cm['metadata']['uid']},{'op':'test','path':'/data/EMAIL_CONNECT_SENDER','value':'forgejo@coulomb.social'},{'op':'replace','path':'/data/EMAIL_CONNECT_SENDER','value':'platform@coulomb.social'}]
|
|
def patchdry(kind,name,patch):
|
|
return json.loads(subprocess.check_output(K+['patch',kind,name,'-n','email-connect','--type=json','--dry-run=server','-p',json.dumps(patch),'-o','json'],timeout=30))
|
|
checks=[]
|
|
for x in resources:
|
|
p=subprocess.run(K+['create','--dry-run=server','-f','-','-o','json'],input=json.dumps(x).encode(),capture_output=True,timeout=30)
|
|
if p.returncode:raise RuntimeError('resource dry-run failed: '+x['kind'])
|
|
y=json.loads(p.stdout);checks.append({'kind':y['kind'],'name':y['metadata']['name'],'accepted':True})
|
|
da=patchdry('deployment','email-connect',dpatch);ca=patchdry('configmap','email-connect-config',cmpatch)
|
|
expected=json.loads(json.dumps(d['spec']));expected['template']['spec']['containers'][0]['envFrom']=dpatch[-2]['value'];expected['template']['spec']['containers'][0]['env']=dpatch[-1]['value']
|
|
assert da['spec']==expected,'unexpected deployment spec change'
|
|
expectedcm=dict(cm['data'],EMAIL_CONNECT_SENDER='platform@coulomb.social');assert ca['data']==expectedcm
|
|
checks.extend([{'kind':'Deployment','name':'email-connect','accepted':True,'only_envFrom_and_env_changed':True},{'kind':'ConfigMap','name':'email-connect-config','accepted':True,'only_sender_changed':True}])
|
|
volumes=d['spec']['template']['spec']['volumes']; policy_name=next(v['configMap']['name'] for v in volumes if v['name']=='caller-policy')
|
|
pol=get('configmap',policy_name)['data']['callers.json'];parsed=json.loads(pol)
|
|
publiccallers=[{k:x.get(k) for k in ('caller_id','operations','templates')} for x in parsed['callers']]
|
|
print(json.dumps({'schema':'coulomb.platform-mail-sender-candidate.v1','observed_at':datetime.now(timezone.utc).isoformat(),'deployment_uid':d['metadata']['uid'],'deployment_generation':d['metadata']['generation'],'image':c['image'],'new_resources':resources,'deployment_json_patch':dpatch,'configmap_json_patch':cmpatch,'native_server_dry_run':checks,'live_caller_policy':{'name':policy_name,'sha256':hashlib.sha256(pol.encode()).hexdigest(),'callers':publiccallers},'platform_mailbox_admission_executed':False,'resources_installed':False,'mail_sent':False,'secrets_read':False,'execution_ready':False}))
|
|
'''
|
|
r=subprocess.run(['ssh','-o','BatchMode=yes','-o','ConnectTimeout=10','railiance01','sudo -n python3 -c '+shlex.quote(code)],capture_output=True,text=True,timeout=180)
|
|
if r.returncode:raise SystemExit('read/dry-run preparation failed: '+r.stderr[-1500:])
|
|
j=json.loads(r.stdout)
|
|
j['preparer_sha256']=hashlib.sha256(Path(__file__).read_bytes()).hexdigest()
|
|
fd=os.open(args.output,os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600)
|
|
with os.fdopen(fd,'w') as target:
|
|
json.dump(j,target,indent=2);target.write('\n');target.flush();os.fsync(target.fileno())
|
|
print(json.dumps({k:j[k] for k in ('observed_at','deployment_generation','native_server_dry_run','live_caller_policy','execution_ready')},indent=2))
|