{{- /*
Step 01 — gitea-mirror.

Mirrors the upstream openova-io public repo into the local Sovereign
Gitea so subsequent Flux GitRepository pivots resolve against in-cluster
DNS. This step is the canonical seam: catalyst-api stamps a Job from
this PodSpec exactly once per cutover.

Consumer contract (catalyst-api cutover endpoint, issue #792):
  - Discovered by labels app.kubernetes.io/part-of=self-sovereign-cutover
    + app.kubernetes.io/component=cutover-step.
  - Order = bp.openova.io/cutover-order label (integer, ascending).
  - Mode = bp.openova.io/cutover-mode label ("job" here).
  - Required data keys:
      stepName  short slug used in status ConfigMap + Job naming
      podSpec   YAML of corev1.PodSpec; catalyst-api wraps it in
                PodTemplateSpec + batchv1.Job.

Image phase: pre — runs BEFORE registries.yaml v2 is active.
*/ -}}
apiVersion: v1
kind: ConfigMap
metadata:
  name: cutover-step-01-gitea-mirror
  namespace: {{ .Release.Namespace }}
  labels:
    {{- include "bp-self-sovereign-cutover.labels" . | nindent 4 }}
    app.kubernetes.io/component: cutover-step
    bp.openova.io/cutover-order: "1"
    bp.openova.io/cutover-mode: "job"
data:
  stepName: gitea-mirror
  podSpec: |
    serviceAccountName: {{ include "bp-self-sovereign-cutover.serviceAccountName" . }}
    restartPolicy: Never
    activeDeadlineSeconds: {{ .Values.stepTimeouts.giteaMirrorSeconds }}
    containers:
      - name: gitea-mirror
        image: {{ include "bp-self-sovereign-cutover.image" (dict "repository" .Values.images.git.repository "tag" .Values.images.git.tag "cutoverPhase" "pre" "Values" .Values) }}
        imagePullPolicy: IfNotPresent
        env:
          - name: UPSTREAM_REPO_URL
            value: {{ .Values.upstream.repoURL | quote }}
          - name: UPSTREAM_BRANCH
            value: {{ .Values.upstream.branch | quote }}
          - name: GITEA_INTERNAL_URL
            value: {{ .Values.sovereign.giteaInternalURL | quote }}
          - name: GITEA_ORG
            value: {{ .Values.gitea.org | quote }}
          - name: GITEA_REPO
            value: {{ .Values.gitea.repo | quote }}
          - name: MIRROR_INTERVAL
            value: {{ .Values.upstream.mirrorInterval | quote }}
          - name: GITEA_USERNAME
            valueFrom:
              secretKeyRef:
                name: {{ .Values.gitea.adminSecretRef.name }}
                key: {{ .Values.gitea.adminSecretRef.usernameKey }}
          - name: GITEA_PASSWORD
            valueFrom:
              secretKeyRef:
                name: {{ .Values.gitea.adminSecretRef.name }}
                key: {{ .Values.gitea.adminSecretRef.passwordKey }}
        command: ["/bin/sh", "-c"]
        args:
          - |
            set -eu
            # Credential hygiene: never echo $GITEA_PASSWORD. We feed it
            # to git via URL embedding only inside the redirected http
            # call below; stdout shows just the redacted form. The HTTP
            # API calls use a base64'd Authorization: Basic header (the
            # alpine/git image's BusyBox wget does NOT support --user/
            # --password flags — caught live on otech103 2026-05-04).
            redacted_url="${GITEA_INTERNAL_URL}/${GITEA_ORG}/${GITEA_REPO}.git"
            echo "[gitea-mirror] upstream=${UPSTREAM_REPO_URL} branch=${UPSTREAM_BRANCH}"
            echo "[gitea-mirror] target=${redacted_url}"
            echo "[gitea-mirror] mirror_interval=${MIRROR_INTERVAL}"

            # Build BusyBox-wget-compatible Basic auth header. printf -n
            # avoids the trailing newline that would otherwise corrupt
            # the base64 encoding (and thus the credential).
            basic_auth=$(printf '%s' "${GITEA_USERNAME}:${GITEA_PASSWORD}" | base64 -w0 2>/dev/null || printf '%s' "${GITEA_USERNAME}:${GITEA_PASSWORD}" | base64 | tr -d '\n')

            # 1. Ensure the org exists in Gitea (idempotent — POST /orgs
            #    returns 422 if a same-named user exists, 422 if org
            #    exists, 201 on create). We swallow non-201 because the
            #    "already exists" surface is the steady-state path.
            api_url="${GITEA_INTERNAL_URL}/api/v1"
            org_check=$(wget -q -O - \
              --header="Authorization: Basic ${basic_auth}" \
              --header="Content-Type: application/json" \
              "${api_url}/orgs/${GITEA_ORG}" 2>/dev/null || echo "missing")
            if [ "${org_check}" = "missing" ]; then
              echo "[gitea-mirror] creating org ${GITEA_ORG}"
              wget -q -O /dev/null --post-data="{\"username\":\"${GITEA_ORG}\",\"visibility\":\"public\"}" \
                --header="Authorization: Basic ${basic_auth}" \
                --header="Content-Type: application/json" \
                "${api_url}/orgs" 2>/dev/null || true
            else
              echo "[gitea-mirror] org already present"
            fi

            # 2. Check whether the local repo already exists. Two cases:
            #    - exists  → ensure mirror_interval is set + trigger sync
            #    - missing → create via /repos/migrate with mirror=true
            #                so Gitea owns the recurring upstream sync
            #                going forward (10m default; tunable via
            #                .Values.upstream.mirrorInterval).
            #
            #    Why migrate (mirror=true) over the legacy create+push:
            #    Gitea's native pull-mirror polls upstream on the
            #    configured interval and replicates branches+tags. The
            #    previous one-shot push left local Gitea drifting from
            #    upstream main indefinitely after Day-2 cutover, requiring
            #    operator-driven `git fetch` for every chart rollout
            #    (hit twice on otech103 2026-05-04). Mirror mode makes
            #    drift impossible. Per Gitea migrate API, we explicitly
            #    opt out of issues/pull-requests/wiki — the Sovereign
            #    only needs branches+tags for Flux GitRepository.
            repo_check=$(wget -q -O - \
              --header="Authorization: Basic ${basic_auth}" \
              --header="Content-Type: application/json" \
              "${api_url}/repos/${GITEA_ORG}/${GITEA_REPO}" 2>/dev/null || echo "missing")

            if [ "${repo_check}" = "missing" ]; then
              echo "[gitea-mirror] creating mirror ${GITEA_ORG}/${GITEA_REPO} via /repos/migrate"
              # POST /api/v1/repos/migrate — single-shot create-as-mirror.
              # service=git is the generic git source (works for github
              # without forcing the github-specific importer, which would
              # require a GitHub PAT we don't have).
              #
              # Build payload via printf rather than a heredoc — the YAML
              # block-scalar wrapper indents every shell line, and POSIX
              # `<<MARKER` heredocs require the closing MARKER token to
              # appear unindented in column 0 (only `<<-MARKER` strips
              # leading tabs, not spaces). Using printf side-steps that
              # whole class of shell-quoting bugs and keeps the JSON on a
              # single line, which is also easier on stdout when set -x
              # debugging is on.
              migrate_payload=$(printf '{"clone_addr":"%s","repo_name":"%s","repo_owner":"%s","service":"git","mirror":true,"mirror_interval":"%s","private":false,"wiki":false,"issues":false,"pull_requests":false,"labels":false,"milestones":false,"releases":false,"description":"Pull-mirror of %s (auto-sync every %s)"}' \
                "${UPSTREAM_REPO_URL}" "${GITEA_REPO}" "${GITEA_ORG}" "${MIRROR_INTERVAL}" "${UPSTREAM_REPO_URL}" "${MIRROR_INTERVAL}")
              # Capture status code so we can fail loud if migrate refuses.
              # wget -S writes status to stderr; grep for "HTTP/1.1 2".
              migrate_log=$(mktemp)
              wget -q -O "${migrate_log}" -S \
                --post-data="${migrate_payload}" \
                --header="Authorization: Basic ${basic_auth}" \
                --header="Content-Type: application/json" \
                "${api_url}/repos/migrate" 2>&1 || true
              if ! wget -q -O - \
                --header="Authorization: Basic ${basic_auth}" \
                --header="Content-Type: application/json" \
                "${api_url}/repos/${GITEA_ORG}/${GITEA_REPO}" >/dev/null 2>&1; then
                echo "[gitea-mirror] FATAL: migrate failed — repo not visible after POST" >&2
                cat "${migrate_log}" >&2 || true
                exit 1
              fi
              echo "[gitea-mirror] migrate complete — repo now mirroring from upstream"
            else
              # Repo already present (re-run / prior cutover attempt).
              # PATCH the mirror_interval to the desired value. Note:
              # PATCH only succeeds in setting interval if the repo was
              # already created with mirror=true. Older Sovereigns seeded
              # by chart < 0.1.14 (legacy create+push) lack the mirror
              # bit and PATCH alone won't convert them — operator action
              # to delete+re-migrate would be required. New provisions
              # always go through the migrate path above and are fine.
              echo "[gitea-mirror] repo already present — patching mirror_interval=${MIRROR_INTERVAL}"
              patch_payload="{\"mirror_interval\":\"${MIRROR_INTERVAL}\"}"
              # BusyBox wget supports --method via newer alpine builds,
              # but to stay portable across alpine/git image variants we
              # fall back to a raw-style PATCH using the X-HTTP-Method-
              # Override header which Gitea's middleware honours.
              wget -q -O /dev/null \
                --post-data="${patch_payload}" \
                --header="Authorization: Basic ${basic_auth}" \
                --header="Content-Type: application/json" \
                --header="X-HTTP-Method-Override: PATCH" \
                "${api_url}/repos/${GITEA_ORG}/${GITEA_REPO}" 2>/dev/null || \
                echo "[gitea-mirror] note: mirror_interval PATCH failed — repo may be a legacy non-mirror; continuing"

              # Trigger an immediate sync so the local repo catches up
              # with whatever has accumulated upstream since last interval.
              # POST /repos/{owner}/{repo}/mirror-sync — fire-and-forget.
              wget -q -O /dev/null \
                --post-data='' \
                --header="Authorization: Basic ${basic_auth}" \
                "${api_url}/repos/${GITEA_ORG}/${GITEA_REPO}/mirror-sync" 2>/dev/null || true
            fi

            # 3. Verify the mirror config landed. Read back the repo
            #    metadata and confirm mirror=true + mirror_interval set.
            #    This is the "no manual git fetch ever again" assertion.
            repo_meta=$(wget -q -O - \
              --header="Authorization: Basic ${basic_auth}" \
              "${api_url}/repos/${GITEA_ORG}/${GITEA_REPO}" 2>/dev/null || echo "{}")
            # Lightweight JSON probe — no jq in alpine/git. We only need
            # to confirm `"mirror":true` and the interval string land.
            if printf '%s' "${repo_meta}" | grep -q '"mirror":true'; then
              echo "[gitea-mirror] verified: mirror=true on ${GITEA_ORG}/${GITEA_REPO}"
            else
              echo "[gitea-mirror] WARN: mirror flag not visible in repo metadata"
              echo "[gitea-mirror] (legacy chart < 0.1.14 seed; auto-sync will not engage)"
            fi
            if printf '%s' "${repo_meta}" | grep -q "\"mirror_interval\":\"${MIRROR_INTERVAL}\""; then
              echo "[gitea-mirror] verified: mirror_interval=${MIRROR_INTERVAL}"
            fi
            echo "[gitea-mirror] step complete"
        resources:
          requests: { cpu: 100m, memory: 256Mi }
          limits:   { memory: 1Gi }
        securityContext:
          runAsNonRoot: true
          runAsUser: 1000
          allowPrivilegeEscalation: false
          readOnlyRootFilesystem: false
          capabilities:
            drop: ["ALL"]
