Files
atelier/examples/good/gitops-pr.md
T
Jon Chery 9ebc9c8868 docs(milestone): complete v0.3 — GitOps+Operators/AI-ML/i18n/Compliance
---ci---
project: atelier
phase: 6
milestone: v0.3
status: complete
requirements:
  covered: [ATELIER-60..91]
  partial: []
---/ci---
2026-08-05 03:45:38 +00:00

7.4 KiB

Good Example: GitOps Pull Request

A pull request that changes ArgoCD Application manifests following Atelier's GitOps + Operators principles. Each aspect cites the principle it satisfies.

The PR

A PR titled promote payments-api 1.2.3 to prod opened against the GitOps repo platform/gitops. It changes the targetRevision of the payments-api Application from 1.2.2 to 1.2.3, adds a sync-wave annotation to a new migration Job, and tightens the AppProject's destination allow-list. CI runs plan/diff; nothing pushes to the cluster.

The Commit

# manifests/prod/payments-api.yaml — the only file changed
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: payments-api
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  source:
    repoURL: https://git.example.com/platform/payments
    targetRevision: 1.2.3          # was 1.2.2 — pinned, not latest
    path: manifests/prod
  destination:
    server: https://kubernetes.default.svc
    namespace: payments
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=false
      - PrunePropagationPolicy=foreground
# manifests/prod/payments-db-migration.yaml — new file, wave-ordered
apiVersion: batch/v1
kind: Job
metadata:
  name: payments-db-migrate-1.2.3
  namespace: payments
  annotations:
    argocd.argoproj.io/sync-wave: "-1"   # PreSync: run before the app
spec:
  backoffLimit: 0
  ttlSecondsAfterFinished: 86400
  template:
    spec:
      restartPolicy: OnFailure
      serviceAccountName: payments-migrator
      containers:
        - name: migrate
          image: registry.example.com/payments/migrate:1.2.3
          args: ["up", "--to", "1.2.3"]
# projects/payments.yaml — AppProject tightened in the same PR
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: payments
  namespace: argocd
spec:
  sourceRepos:
    - https://git.example.com/platform/payments
  destinations:
    - server: https://kubernetes.default.svc
      namespace: payments            # prod-app can only deploy to payments ns
  clusterResourceWhitelist:
    - group: ""
      kind: Namespace                # allowed to create its own namespace
  roles:
    - name: payments-team
      policies:
        - p, proj:payments:payments-team, applications, sync, payments/*, allow

The CI Pipeline (runs on the PR, before merge)

# .github/workflows/gitops-plan.yml (illustrative steps)
- name: validate manifests
  run: argocd app manifests manifests/prod/ | kubeconform -strict

- name: diff against live cluster (read-only, no apply)
  run: argocd app diff payments-api --server $ARGOCD_SERVER --auth-token $READ_ONLY_TOKEN
  # CI holds a READ-ONLY ArgoCD token. It never holds kubectl rights.
  # A non-empty diff is the PR's proposed change, rendered for review.

- name: opa gate (admission policy pre-check)
  run: opa eval -i manifests/prod/ -d policies/ "data.k8s.admission.deny"
  # Policy violations fail the PR before merge, not after deploy.

What Makes It Good

Git is the Source of Truth (GitOps P1, C1 Correctness)

  • The promotion is a commit. The cluster's desired state is a derivative of this repo; the repo is the authority. If the change is wrong, git revert is the rollback — the recovery path is the history.
  • See domains/gitops-operators/first-principles.md P1 and domains/gitops-operators/argocd.md (Application CRD).

Pull, Don't Push (GitOps P3, C4 Locality)

  • CI holds a read-only ArgoCD token for app diff. It holds no kubectl rights against the production cluster. The cluster's ArgoCD controller pulls the merged commit; nothing pushes to the cluster. A compromised CI token can read, not deploy.
  • See domains/gitops-operators/argocd.md (RBAC and SSO) and domains/gitops-operators/flux.md for the same pull boundary from the Flux side.

State is Immutable and Versioned (GitOps P5, C5 Reversibility)

  • targetRevision: 1.2.3 — the Application pins a specific chart revision, not latest. The commit that changed it is a permanent record; git revert restores 1.2.2 and ArgoCD's selfHeal converges the cluster back. No force-push; history is the audit trail.
  • See domains/gitops-operators/first-principles.md P5 and domains/infrastructure-as-code/state.md (State is Truth).

Sync Waves Order Correctness (GitOps P4, C1)

  • The migration Job carries argocd.argoproj.io/sync-wave: "-1" so it runs in PreSync before the payments-api Deployment that depends on the new schema. Wave ordering is a correctness mechanism, not performance — the app starting before its migration is a correctness bug.
  • See domains/gitops-operators/argocd.md (Sync Waves and Hooks).

Reconcile, Don't Mutate by Hand (GitOps P8)

  • selfHeal: true + prune: true means a hand-edited drift on a managed resource is overwritten on the next loop. The fix for drift is a new commit, not kubectl edit. The PR author does not SSH into the cluster to "fix" anything.
  • See domains/gitops-operators/argocd.md (Diff and Drift) and domains/gitops-operators/first-principles.md P8.

Least Privilege Reconciliation (GitOps P10, C8 Economy)

  • The AppProject payments restricts the Application to the payments namespace and the payments repo. The controller's ServiceAccount (not shown) is bound to a namespace-scoped Role, not cluster-admin. The PR tightens the allow-list — least privilege is a direction, not a one-time setting.
  • See domains/gitops-operators/argocd.md (RBAC and SSO) and domains/kubernetes/rbac.md.
  • The opa eval step runs the admission policy against the proposed manifests before merge. A violation fails the PR; the non-compliant state is never realized. Detection is not enforcement; this is enforcement.
  • See domains/compliance/policy-as-code.md and domains/devops/ci-cd.md.

Failure is Observable (GitOps P9)

  • A sync failure or health degradation on payments-api emits ArgoCD status (Degraded / OutOfSync) and a notification. Silent drift is the bug; this PR does not disable notifications.
  • See domains/gitops-operators/argocd.md (Health and Status) and domains/observability/metrics.md.

What This PR Does NOT Do (And Why That's Good)

  • Does not run kubectl apply from CI — that is the push pattern, a P3 violation (see examples/bad/ for the anti-pattern).
  • Does not use argocd app set as the steady state — the change is in git, not in an imperative command's history.
  • Does not store raw Secrets in the GitOps repo — secrets arrive via Sealed Secrets / SOPS / External Secrets, encrypted in git.
  • Does not float targetRevision: latest — the Application pins a version; "latest" is an unknown model of the system.
  • domains/gitops-operators/argocd.md — the Application CRD, sync waves, RBAC/AppProjects, and the pull model.
  • domains/gitops-operators/flux.md — the same PR pattern from the Flux side (Kustomization CRD, per-cluster autonomy).
  • domains/kubernetes/workloads.md — the Deployment/Job the Application reconciles.
  • domains/kubernetes/rbac.md — the ServiceAccount + Role the controller and the migration Job run as.
  • domains/compliance/policy-as-code.md — the OPA gate is a compliance-as-a-gate enforcement point.
  • domains/devops/P4 Rollback Firstgit revert is the rollback; selfHeal is the convergence.