# application-controller Deployment.
#
# Single replica with leader election so the Lease handles HA when the
# replica count is bumped. The controller is K8s-native and stateless
# beyond its watch cache — re-running a reconcile after a restart
# converges on the same desired state.
#
# Per docs/INVIOLABLE-PRINCIPLES.md the Deployment ships:
#   - SHA-pinned image (`<sha>` placeholder; CI deploys overwrite)
#   - non-root, read-only-root-fs, drop ALL caps, runtime-default
#     seccomp profile
#   - resources/requests + resources/limits set (no unbounded pods)
#
# Configuration via env-var per Inviolable Principle #4 (never hardcode):
#   GITEA_API_URL          in-cluster Gitea endpoint
#   GITEA_TOKEN            secret with the per-Sovereign Gitea PAT
#   GITEA_PUBLIC_URL       externally-visible Gitea URL stamped on
#                          Application.status.giteaRepo
#   SOURCE_NAMESPACE       Flux source CR namespace inside vCluster
#   HELMRELEASE_INTERVAL   per-HelmRelease reconcile interval seconds
#
# The image reference is left as a placeholder for the GitHub Actions
# pipeline to substitute via `sed -i`. Per principle #4a, never edit
# this file by hand to roll an image — let CI do it.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: application-controller
  namespace: catalyst-system
  labels:
    app.kubernetes.io/name: application-controller
    app.kubernetes.io/component: controller
    app.kubernetes.io/managed-by: flux
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: application-controller
  template:
    metadata:
      labels:
        app.kubernetes.io/name: application-controller
        app.kubernetes.io/component: controller
        app.kubernetes.io/managed-by: flux
    spec:
      serviceAccountName: application-controller
      automountServiceAccountToken: true
      securityContext:
        runAsNonRoot: true
        runAsUser: 65534
        runAsGroup: 65534
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: controller
          image: ghcr.io/openova-io/openova/application-controller:placeholder
          imagePullPolicy: IfNotPresent
          args:
            - --metrics-bind-address=:8080
            - --health-probe-bind-address=:8081
          env:
            - name: LEADER_ELECT_NS
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
            - name: GITEA_API_URL
              value: http://gitea-http.gitea.svc.cluster.local:3000
            - name: GITEA_TOKEN
              valueFrom:
                secretKeyRef:
                  name: catalyst-gitea-token
                  key: token
            - name: GITEA_PUBLIC_URL
              valueFrom:
                configMapKeyRef:
                  name: catalyst-runtime-config
                  key: gitea-public-url
                  optional: true
            - name: SOURCE_NAMESPACE
              value: flux-system
            - name: CATALOG_SOURCE_REF
              value: openova-catalog
            - name: HELMRELEASE_INTERVAL
              value: "600"
            - name: REQUEUE_AFTER_SECONDS
              value: "300"
          ports:
            - name: metrics
              containerPort: 8080
            - name: health
              containerPort: 8081
          livenessProbe:
            httpGet:
              path: /healthz
              port: health
            initialDelaySeconds: 15
            periodSeconds: 20
          readinessProbe:
            httpGet:
              path: /readyz
              port: health
            initialDelaySeconds: 5
            periodSeconds: 10
          resources:
            requests:
              cpu: 25m
              memory: 64Mi
            limits:
              cpu: 250m
              memory: 256Mi
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              drop: ["ALL"]
            readOnlyRootFilesystem: true
      terminationGracePeriodSeconds: 15
