Some checks failed
Use-case regression gate / usecase-regressions (push) Failing after 2m27s
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a18e2d-f513-7742-b8e7-12af0b744ff3
306 lines
9.3 KiB
Python
306 lines
9.3 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
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import shlex
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
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,
|
|
)
|
|
)
|