# Config Drift Detection CronJob - Phase 25 P2
# 每小時掃描 awoooi-prod namespace 的配置漂移
#
# 建立時間: 2026-04-04 (台北時區)
# 建立者: Claude Code (Phase 25 P2)
# 關聯設計: docs/superpowers/specs/2026-04-04-nemotron-active-defense-design.md 方向三
# 關聯 ADR: 待起草 ADR-057
#
# 部署: 由 ArgoCD 套用 k8s/awoooi-prod/kustomization.yaml
# 手動觸發: kubectl create job --from=cronjob/drift-scanner drift-scan-manual -n awoooi-prod
# 查看 log: kubectl logs -l job-name=drift-scanner -n awoooi-prod

apiVersion: batch/v1
kind: CronJob
metadata:
  name: drift-scanner
  namespace: awoooi-prod
  labels:
    app: awoooi
    component: drift-scanner
    phase: "25"
  annotations:
    # 2026-04-04 ogt: Phase 25 P2 — Config Drift Detection
    description: "每小時掃描 K8s 配置漂移，由 Nemotron 做意圖分析"
spec:
  # 每小時整點執行（台北時間 = UTC+8，schedule 用 UTC）
  schedule: "0 * * * *"
  concurrencyPolicy: Forbid      # 禁止並發：上次未完成則跳過
  successfulJobsHistoryLimit: 3
  # 2026-05-24 Codex: failed Job objects make ArgoCD Application health stay
  # Degraded long after the next scans have recovered. Failure evidence must
  # live in AwoooP/alert logs, not as retained Kubernetes Job noise.
  failedJobsHistoryLimit: 0
  startingDeadlineSeconds: 60    # 錯過時間窗口超過 60s 則跳過
  jobTemplate:
    spec:
      backoffLimit: 0            # 失敗不重試（漂移掃描冪等，下次 cron 自動補掃）
      activeDeadlineSeconds: 300 # 最長 5 分鐘
      template:
        metadata:
          labels:
            app: awoooi
            component: drift-scanner
            # 2026-04-14 Claude Sonnet 4.6: 補 system=awoooi — allow-required-egress 以此篩選
            # 缺此 label 導致 default-deny-all 封鎖所有 egress（系統沉默 9.4h 根因）
            system: awoooi
        spec:
          restartPolicy: Never
          # 2026-04-09 Claude Sonnet 4.6: awoooi-api SA 不存在，改用 default（只需呼叫內部 API，不需 K8s 權限）
          serviceAccountName: default
          containers:
            - name: drift-scanner
              # 使用 awoooi-api 鏡像（含 kubectl + Python 環境）
              # 2026-05-05 Codex: keep the API image placeholder so CD
              # injects the same immutable tag used by API/worker. Leaving
              # this CronJob on a fixed old tag made the schedule drift after
              # reboot even when Deployments were healthy.
              image: 192.168.0.110:5000/library/api:IMAGE_TAG_PLACEHOLDER
              imagePullPolicy: Always
              command:
                - python
                - -c
                - |
                  import asyncio, httpx, os
                  API_URL = os.environ.get("INTERNAL_API_URL", "http://awoooi-api:8000")
                  async def run():
                      async with httpx.AsyncClient(timeout=240) as c:
                          r = await c.post(f"{API_URL}/api/v1/drift/internal/scan")
                          print(f"status={r.status_code} body={r.text[:200]}")
                  asyncio.run(run())
              env:
                # 2026-05-05 Codex: call the in-cluster Service instead of a
                # fixed worker NodePort. After reboot, 121 can be unavailable
                # while the Service and VIP are already healthy.
                - name: INTERNAL_API_URL
                  value: "http://awoooi-api-svc.awoooi-prod.svc.cluster.local:8000"
                - name: DRIFT_SCAN_NAMESPACES
                  value: "awoooi-prod"
              resources:
                requests:
                  cpu: "50m"
                  memory: "64Mi"
                limits:
                  cpu: "200m"
                  memory: "256Mi"
