Create the annia enqueuer #89
Labels
No labels
blocked
bug
duplicate
effort
high
effort
low
effort
medium
effort
minimal
enhancement
help wanted
invalid
pr
feedback
pr
reviewing
question
status
backlog
status
document
status
done
status
implement
status
pending
status
planning
status
reviewing
waiting
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
tepichord/milner#89
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
tepichord referenced this issue2026-09-22 14:12:03 -07:00
Naina:
Overview
Create annia's cluster-side enqueuer: the
annianamespace, its two Vault credentials, the RabbitMQ Messaging Topology Operator and the topology CRs that are the cross-repo contract withannia-infra#308, and theannia-enqueuerService (port 80,/api/hook) plus the Deployment that pins the enqueuer image. The enqueuer's application source and image areannia-infra's (#308/#310) — this issue owns the deployment contract and the broker topology. Theannianamespace is created here; #25 and #87 must not create it again.Technical Approach
infrastructure/namespaces/annia.yaml, and list it ininfrastructure/kustomization.yamlafternamespaces/rabbitmq.yaml.infrastructure/rabbitmq-operator/values.yaml(it isenabled: falsetoday5, andtest_rabbitmq_knobs_land_where_they_are_readpins that6). The chart generates its own webhook certs (useCertManager: false)4, so no cert-manager.data/annia/rabbitmq.yaml(all CRsnamespace: annia,rabbitmqClusterReference: {name: rabbitmq, namespace: rabbitmq}10). The DLX andx-dead-letter-routing-keyare deliberate: without the latter, dead-lettering reuses the publisher's routing key and nothing catches it1213.Queue.spec.durablemust be written explicitly — the operator defaults it tofalse11 (the chart installs only a validating webhook, so the upstream mutating defaulter that would set it totruenever runs).Wire the data layer —
data/annia/kustomization.yaml(namespace: annia, resourcerabbitmq.yaml) and add- anniatodata/kustomization.yaml. NohealthCheckExprschange: kstatus reads these CRs' ownReadycondition, and before the broker exists they have none (→ Current)89, so the expression list stays["Cluster", "DatabaseRole", "Database"].Add the Vault policy and role —
docs/vault.md§4c-ter: addanniato thefor ns in …loop and this policy above it.secrets/vaultauth-annia.yaml(nsannia, roleannia, SAdefault,vaultConnectionRef: vault-secrets-operator/vault-connection)14 andsecrets/annia-secrets.yaml(thekv-v2/refreshAfter: 1h/create: true/overwrite: trueshape matches the existingVaultStaticSecrets15).Both
kv-v2,refreshAfter: 1h,destination.create: true,destination.overwrite: true. List both files insecrets/kustomization.yaml.apps/annia/{service.yaml,deployment.yaml,kustomization.yaml}(namespace: annia), and- anniainapps/kustomization.yaml. TCP probes, so the app needs no/healthz.tests/test_gitops.py(the VSO count tripwire is(7, 11)today7, and7 + 1 = 8,11 + 2 = 13):Add
test_annia_topology_matches_the_cross_repo_contractasserting the queue'stype/durable/arguments, the DLQ binding key, theannia.events → annia.dispatchbinding, thePermission, and the Service's name/port.docs/rabbitmq.md§16e (vhost, user, topology, DLX inspection, the imperative user creation) and anOPERATIONS.md§1 row. Then comment onannia-infra#308:annia.events(topic) →annia.dispatch(quorum,x-delivery-limit: 2, DLXannia.dlx→annia.dispatch.dead); publish routing key = repo short name (milner), binding key*; persistent (delivery_mode=2) with publisher confirms, 2xx only after the confirm; HMAC-SHA256 over the raw body,X-Forgejo-Signature(bare hex) orX-Hub-Signature-256: sha256=<hex>16.Implementation Details
Files to create:
gitops/clusters/milner/infrastructure/namespaces/annia.yamlbaselinegitops/clusters/milner/data/annia/rabbitmq.yamlgitops/clusters/milner/data/annia/kustomization.yamlnamespace: anniagitops/clusters/milner/secrets/vaultauth-annia.yamldefaultinanniagitops/clusters/milner/secrets/annia-secrets.yamlgitops/clusters/milner/apps/annia/{service,deployment,kustomization}.yamlFiles to modify:
infrastructure/kustomization.yaml,infrastructure/rabbitmq-operator/values.yaml,data/kustomization.yaml,secrets/kustomization.yaml,apps/kustomization.yaml,tests/test_gitops.py(3 places),docs/vault.md§4c-ter,docs/rabbitmq.md(§16e),OPERATIONS.md§1.Vault keys the manifests depend on (hand-run, docs/rabbitmq.md §16e):
secret/annia/forgejo-webhook→hmac-secret;secret/annia/rabbitmq→username,password. Broker useranniais created imperatively with that password (rabbitmqctl add_user), because no CR can carry a Vault-held password17 unverified — the operator'sUserCR does exposespec.importCredentialsSecret, which importsusername/passwordfrom a Secret, so the premise that no CR can carry a Vault-held password needs re-checking.Constraints: layer ordering is
infrastructure(operator) →secrets→data(topology) →apps(workload)12; the topology operator's webhooks arefailurePolicy: Fail3, so a broken operator surfaces as adata-layer failure; policy/k8s.rego needs cpu+memory limits on the Deployment container.Coordination / out-of-scope notes:
infrastructure/namespaces/annia.yaml.annia-infra's (#308 source, #310/#311 pipeline), per #89. Until #310 publishes the pinned tag the pod isImagePullBackOffand theappslayer reports not-Ready — a stated cross-repo dependency, not a misconfiguration. #308's plan comment claims the source is Milner's; confirm which holds before implementing step 7.ImageRepository/ImagePolicy/ImageUpdateAutomation) does not exist in this cluster; the tag is a literal pin, as #25 records. Adding image automation is #25's, not this issue's.annia.vando.dev/api/hookis #88.Acceptance Criteria
kustomize buildsucceeds for theinfrastructure,data, andappslayers;python3 -m pytest tests/ -k gitopsis green.data/annia/rabbitmq.yamldeclares vhostannia, exchangesannia.events/annia.dlx(topic, durable), quorum+durable queuesannia.dispatch(x-delivery-limit: 2, DLXannia.dlx, DLX routing keyannia.dead) andannia.dispatch.dead, both bindings, and annia'sPermission; a test asserts these arguments.msgTopologyOperator.enabled: truewith pinnednodeSelector/resources, and thetest_rabbitmq_knobs_land_where_they_are_readassertion flipped toTrue.secrets/vaultauth-annia.yaml+ two VaultStaticSecrets exist, the VSO count tripwire is(8, 13), anddocs/vault.md§4c-ter carriesannia-readandanniain the role loop.annia-enqueuerService (port 80 →http) and its Deployment (image pinned, both VSO Secrets wired, resources set) exist inapps/annia/and are listed inapps/kustomization.yaml.docs/rabbitmq.md§16e + theOPERATIONS.md§1 row document annia's vhost, user, topology, and the hand-run broker user creation.annia-infra#308.Testing Plan
python3 -m pytest tests/ -k gitops— render, kubeconform and the new contract test.kustomize build gitops/clusters/milner/{infrastructure,data,apps}— the three touched layers.conftest test --policy policy/on the renderedappslayer — Deployment limits, no:latest.basic.nackwith requeue, or a crashing consumer) → it lands inannia.dispatch.deadwithx-deathreason: delivery_limit.Related Issues/PRs
http://annia-enqueuer.annia.svc.cluster.local:80/api/hook)./nixclaim, ScaledJob, and Flux image automation for this tag; owns the otherapps/annia/workloads.annia.dispatch); #87 — Tailscale egress (also creates namespaces).annia-infra#308 — the pointer contract this topology is the other half of; #310/#311 — the image build pipeline.Research Findings
X-Forgejo-Signature(bare hex),X-Hub-Signature-256(sha256=<hex>), plusX-Gitea/X-Gogs-Signature; there is no timestamp or replay protection. (codeberg.org/forgejo/forgejoservices/webhook/shared/payloader.go; forgejo.org/docs/next/user/webhooks)useCertManager: falsepath generates its own 365-day webhook cert Secret viagenCA/genSignedCert, so flippingmsgTopologyOperator.enabled: trueneeds no cert-manager. (github.com/CloudPirates-io/helm-chartsrabbitmq-cluster-operator)Readycondition, but when theRabbitmqClusteris absent the operator writes no status, so kstatus reports thedatalayer Current rather than deadlocking. (rabbitmq/messaging-topology-operatortopology_controller.go; kubernetes-sigs/cli-utilspkg/kstatus)x-delivery-limitis a quorum-queue argument; on RabbitMQ 4.3 it counts genuine failures, and withoutx-dead-letter-routing-keya dead-lettered message keeps its original routing key — hence theannia.deadkey above. (rabbitmq.com/docs/quorum-queues, /docs/dlx)Pre-Mortem
annia-infra#308 drifts — the image tag pinned in step 7 or the pointer schema (exchange/queue/routing key/{repo, kind, number, event_id}) does not match what the enqueuer actually publishes or what #310 builds, leaving the pod inImagePullBackOffor theappslayer not-Ready. The plan itself flags that #308 claims the source is Milner's, so the two repos can land different contracts unless step 9's comment is reconciled first.msgTopologyOperatorwith fail-closed webhooks breaks thedatalayer — the operator's validating webhooks usefailurePolicy: Fail, and if its pod/webhook service is not Ready whendata/annia/rabbitmq.yamlis applied, every topology CR is rejected and the layer never reconciles. TheuseCertManager: falseself-signed-cert premise is asserted from chart templates rather than verified in-cluster, so a missing cert path would compound this.secret/annia/rabbitmqpassword must be applied out-of-band withrabbitmqctl add_userbefore the operator'sPermissionCR and the enqueuer can authenticate, and the plan's claim that no CR can carry a Vault-held password was flagged unverified. If that hand-run step is missed or the password changes, thePermissionCR fails and the enqueuer crash-loops on AMQP auth errors that the static tests cannot catch.