From 7074281aba3894a3d298eabd256c63724d95f84a Mon Sep 17 00:00:00 2001 From: Kochetkov S Date: Wed, 5 Aug 2026 12:50:26 +0300 Subject: [PATCH] ++ add secrets-contract-probe test app for brusnika-stage acceptance test --- test-app/Dockerfile | 11 ++++++ test-app/app.py | 78 ++++++++++++++++++++++++++++++++++++++++ test-app/deployment.yaml | 48 +++++++++++++++++++++++++ 3 files changed, 137 insertions(+) create mode 100644 test-app/Dockerfile create mode 100644 test-app/app.py create mode 100644 test-app/deployment.yaml diff --git a/test-app/Dockerfile b/test-app/Dockerfile new file mode 100644 index 0000000..b95247f --- /dev/null +++ b/test-app/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3.12-slim + +WORKDIR /app + +RUN pip install --no-cache-dir pika==1.3.2 + +COPY app.py . + +USER 65534:65534 + +CMD ["python", "app.py"] diff --git a/test-app/app.py b/test-app/app.py new file mode 100644 index 0000000..496eeb5 --- /dev/null +++ b/test-app/app.py @@ -0,0 +1,78 @@ +"""Secrets-contract acceptance probe. + +Connects to RabbitMQ using credentials read from a Kubernetes Secret produced +by the v2 secrets contract (schema=rabbitmq.v1) and, every PROBE_INTERVAL_SECONDS, +publishes a message to PROBE_QUEUE and immediately consumes it back. Success +or failure is logged; message bodies never contain secret values. +""" +import json +import logging +import os +import time + +import pika + +logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") +log = logging.getLogger("secrets-contract-probe") + +QUEUE = os.environ.get("PROBE_QUEUE", "test-terraform-probe") +INTERVAL = int(os.environ.get("PROBE_INTERVAL_SECONDS", "15")) + + +def env(name: str) -> str: + value = os.environ.get(name, "") + if not value: + raise RuntimeError(f"missing required env var {name}") + return value + + +def connect() -> pika.BlockingConnection: + params = pika.ConnectionParameters( + host=env("RABBITMQ_HOST"), + port=int(env("RABBITMQ_PORT")), + virtual_host=env("RABBITMQ_VHOST"), + credentials=pika.PlainCredentials(env("RABBITMQ_USERNAME"), env("RABBITMQ_PASSWORD")), + heartbeat=30, + blocked_connection_timeout=10, + ) + return pika.BlockingConnection(params) + + +def main() -> None: + log.info("starting probe: host=%s vhost=%s queue=%s interval=%ss", + os.environ.get("RABBITMQ_HOST"), os.environ.get("RABBITMQ_VHOST"), QUEUE, INTERVAL) + + conn = connect() + channel = conn.channel() + channel.queue_declare(queue=QUEUE, durable=True) + log.info("connected to rabbitmq, entering probe loop") + + seq = 0 + while True: + seq += 1 + try: + payload = json.dumps({"seq": seq, "ts": time.time()}) + channel.basic_publish(exchange="", routing_key=QUEUE, body=payload) + log.info("published probe seq=%d", seq) + + method, _props, _body = channel.basic_get(queue=QUEUE, auto_ack=False) + if method is None: + log.error("probe seq=%d FAILED: nothing to consume right after publish", seq) + else: + channel.basic_ack(method.delivery_tag) + log.info("consumed+acked probe seq=%d OK", seq) + except Exception: # noqa: BLE001 - keep the loop alive, log and retry + log.exception("probe seq=%d FAILED", seq) + try: + conn = connect() + channel = conn.channel() + channel.queue_declare(queue=QUEUE, durable=True) + log.info("reconnected to rabbitmq") + except Exception: # noqa: BLE001 + log.exception("reconnect failed, will retry next cycle") + + time.sleep(INTERVAL) + + +if __name__ == "__main__": + main() diff --git a/test-app/deployment.yaml b/test-app/deployment.yaml new file mode 100644 index 0000000..30309e3 --- /dev/null +++ b/test-app/deployment.yaml @@ -0,0 +1,48 @@ +# Acceptance-test deployment for the v2 secrets contract (SECRETS-CONTRACT-PLAN.md). +# Applied manually with kubectl after `live/namespace`, `live/rabbitmq` and +# `live/secrets` are applied for INFRA_ENV=brusnika-stage - not part of any +# terragrunt stack, this is a throwaway probe, not managed infrastructure. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: secrets-contract-probe + namespace: test-terraform + labels: + app: secrets-contract-probe +spec: + replicas: 1 + selector: + matchLabels: + app: secrets-contract-probe + template: + metadata: + labels: + app: secrets-contract-probe + spec: + imagePullSecrets: + - name: regcred + containers: + - name: probe + image: cr.yandex/crp3ccidau046kdj8g9q/secrets-contract-probe:v1 + env: + - name: RABBITMQ_HOST + valueFrom: { secretKeyRef: { name: rabbitmq, key: host } } + - name: RABBITMQ_PORT + valueFrom: { secretKeyRef: { name: rabbitmq, key: port } } + - name: RABBITMQ_VHOST + valueFrom: { secretKeyRef: { name: rabbitmq, key: vhost } } + - name: RABBITMQ_USERNAME + valueFrom: { secretKeyRef: { name: rabbitmq, key: username } } + - name: RABBITMQ_PASSWORD + valueFrom: { secretKeyRef: { name: rabbitmq, key: password } } + - name: PROBE_QUEUE + value: test-terraform-probe + - name: PROBE_INTERVAL_SECONDS + value: "15" + resources: + requests: + cpu: 10m + memory: 32Mi + limits: + cpu: 100m + memory: 64Mi