From 9f290fd57f743bd498ea9a29bf881ac3230a513b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Jeanneret?= Date: Thu, 23 Jul 2026 14:03:59 +0200 Subject: [PATCH 1/3] Enable automated sync with retry policy by default Child ArgoCD Applications now deploy with automated sync and a retry policy (limit 5, 30s-3m exponential backoff) out of the box, removing the need for every override file to add a full syncPolicy. Documentation warns users to pin targetRevision to a tag or commit hash to avoid unintended deployments from branch pushes. Co-Authored-By: Claude Opus 4.6 --- README.md | 5 ++ charts/all/rhoso-gitops/README.md | 49 ++++++++--- .../rhoso-gitops/tests/application_test.yaml | 37 ++++++++ charts/all/rhoso-gitops/values.schema.json | 3 +- charts/all/rhoso-gitops/values.yaml | 84 +++++++++++++++---- overrides/values-rhoso-gitops.yaml | 84 +++++++++++++++---- 6 files changed, 222 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 1a00249..5860583 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,11 @@ flowchart LR To change the upstream Git revision, edit `targetRevision` in `overrides/values-rhoso-gitops.yaml`. +> **Note:** Child applications use automated sync by default. Pin +> `targetRevision` to a tag or commit hash in +> `overrides/values-rhoso-gitops.yaml` — see +> [chart README](charts/all/rhoso-gitops/README.md#automated-sync-and-revision-stability). + ## Chart layout - `charts/all/rhoso-gitops/` -- pattern meta-chart (see diff --git a/charts/all/rhoso-gitops/README.md b/charts/all/rhoso-gitops/README.md index 05efb50..63e5f67 100644 --- a/charts/all/rhoso-gitops/README.md +++ b/charts/all/rhoso-gitops/README.md @@ -111,10 +111,39 @@ render that `Application`; `enabled: false` skips it. | `kustomize` | map | `spec.source.kustomize` ([Argo CD Kustomize][argo-kustomize]). | | `finalizers` | list | `metadata.finalizers` (`background` or `foreground`). | | `project` | string | Argo CD AppProject (default `default`). | -| `syncPolicy` | map | Full `spec.syncPolicy`; when set, top-level `syncOptions` are ignored. | +| `syncPolicy` | map | Full `spec.syncPolicy`. Defaults to automated sync with retry and `Prune=true`; set explicitly to override (e.g. remove `automated` for manual sync). When set, top-level `syncOptions` are ignored. | Schema validation: `values.schema.json` (used by `helm lint` in CI). +### Automated sync and revision stability + +> **Warning:** With automated sync enabled (the default), Argo CD reconciles +> child applications whenever the upstream Git repository changes. If +> `targetRevision` is set to a branch name like `main` or `HEAD`, any push +> to that branch triggers an automatic deployment. This can cause unexpected +> changes in production. +> +> **Always pin `targetRevision` to a tag or commit hash** for stability: +> +> ```yaml +> applications: +> openstack-operator: +> targetRevision: "v0.3.4" # tag — recommended +> openstack-controlplane: +> targetRevision: "abc123def456" # commit hash — also safe +> ``` +> +> To disable automated sync for a specific application, override its +> `syncPolicy` without the `automated` key: +> +> ```yaml +> applications: +> openstack-dataplane: +> syncPolicy: +> syncOptions: +> - Prune=true +> ``` + ## Default applications Enabled by default in chart `values.yaml` except `openstack-secrets` @@ -122,15 +151,15 @@ Enabled by default in chart `values.yaml` except `openstack-secrets` `overrides/values-rhoso-gitops.yaml` pin paths to the upstream `example/*` directories at tag `v0.1.0`. -| Application | Purpose (summary) | Default `syncWave` | -| --- | --- | --- | -| `operator-dependencies` | Infra + optional VSO/ESO via `kustomize.components` | `-20` | -| `openstack-operator` | OpenStack operator | `-20` | -| `openstack-operator-cr` | Main `OpenStack` CR | `-15` | -| `openstack-secrets` | Secure-backend sync (disabled until configured) | `-10` | -| `openstack-networks` | Networks | `0` | -| `openstack-controlplane` | `OpenStackControlPlane` | `10` | -| `openstack-dataplane` | Data plane | `20` | +| Application | Purpose (summary) | Default `syncWave` | Sync | +| --- | --- | --- | --- | +| `operator-dependencies` | Infra + optional VSO/ESO via `kustomize.components` | `-20` | automated | +| `openstack-operator` | OpenStack operator | `-20` | automated | +| `openstack-operator-cr` | Main `OpenStack` CR | `-15` | automated | +| `openstack-secrets` | Secure-backend sync (disabled until configured) | `-10` | automated | +| `openstack-networks` | Networks | `0` | automated | +| `openstack-controlplane` | `OpenStackControlPlane` | `10` | automated | +| `openstack-dataplane` | Data plane | `20` | automated | ### Sync wave ordering diff --git a/charts/all/rhoso-gitops/tests/application_test.yaml b/charts/all/rhoso-gitops/tests/application_test.yaml index 271c8c6..0fd0ed3 100644 --- a/charts/all/rhoso-gitops/tests/application_test.yaml +++ b/charts/all/rhoso-gitops/tests/application_test.yaml @@ -48,6 +48,20 @@ tests: - equal: path: spec.syncPolicy.syncOptions[0] value: Prune=true + - exists: + path: spec.syncPolicy.automated + - equal: + path: spec.syncPolicy.retry.limit + value: 5 + - equal: + path: spec.syncPolicy.retry.backoff.duration + value: 30s + - equal: + path: spec.syncPolicy.retry.backoff.factor + value: 2 + - equal: + path: spec.syncPolicy.retry.backoff.maxDuration + value: 3m - it: renders operator-dependencies with default background finalizer values: @@ -112,6 +126,29 @@ tests: - notExists: path: spec.syncPolicy.automated + - it: allows disabling automated sync via syncPolicy override + set: + applicationNamespace: openshift-gitops + destinationServer: https://kubernetes.default.svc + applications: + manual-test: + enabled: true + repoURL: https://github.com/example/repo + path: "." + targetRevision: v1.0.0 + syncPolicy: + syncOptions: + - Prune=true + documentSelector: + path: metadata.name + value: manual-test + asserts: + - notExists: + path: spec.syncPolicy.automated + - equal: + path: spec.syncPolicy.syncOptions[0] + value: Prune=true + - it: defaults syncWave to 0 when omitted set: applicationNamespace: openshift-gitops diff --git a/charts/all/rhoso-gitops/values.schema.json b/charts/all/rhoso-gitops/values.schema.json index a3e7d55..70107e8 100644 --- a/charts/all/rhoso-gitops/values.schema.json +++ b/charts/all/rhoso-gitops/values.schema.json @@ -41,8 +41,7 @@ "enabled", "repoURL", "path", - "targetRevision", - "syncOptions" + "targetRevision" ], "additionalProperties": false, "properties": { diff --git a/charts/all/rhoso-gitops/values.yaml b/charts/all/rhoso-gitops/values.yaml index 7c7ccab..61be887 100644 --- a/charts/all/rhoso-gitops/values.yaml +++ b/charts/all/rhoso-gitops/values.yaml @@ -11,8 +11,16 @@ applications: enabled: true path: "example/dependencies" repoURL: https://github.com/openstack-k8s-operators/gitops - syncOptions: - - Prune=true + syncPolicy: + automated: {} + retry: + limit: 5 + backoff: + duration: 30s + factor: 2 + maxDuration: 3m + syncOptions: + - Prune=true syncWave: "-20" targetRevision: v0.1.0 # Deploy openstack-operator. @@ -22,8 +30,16 @@ applications: enabled: true path: "example/openstack-operator" repoURL: https://github.com/openstack-k8s-operators/gitops - syncOptions: - - Prune=true + syncPolicy: + automated: {} + retry: + limit: 5 + backoff: + duration: 30s + factor: 2 + maxDuration: 3m + syncOptions: + - Prune=true syncWave: "-20" targetRevision: v0.1.0 # Create the main OpenStack Custom Resource. @@ -33,8 +49,16 @@ applications: enabled: true path: "example/openstack-operator-cr" repoURL: https://github.com/openstack-k8s-operators/gitops - syncOptions: - - Prune=true + syncPolicy: + automated: {} + retry: + limit: 5 + backoff: + duration: 30s + factor: 2 + maxDuration: 3m + syncOptions: + - Prune=true syncWave: "-15" targetRevision: v0.1.0 # Create cluster secrets by syncing from the secure backend (placeholder path). @@ -45,8 +69,16 @@ applications: enabled: false path: "TODO" repoURL: https://github.com/openstack-k8s-operators/gitops - syncOptions: - - Prune=true + syncPolicy: + automated: {} + retry: + limit: 5 + backoff: + duration: 30s + factor: 2 + maxDuration: 3m + syncOptions: + - Prune=true syncWave: "-10" targetRevision: v0.1.0 # Create underlying networks for controlplane and dataplane. @@ -56,8 +88,16 @@ applications: enabled: true path: "example/openstack-networks" repoURL: https://github.com/openstack-k8s-operators/gitops - syncOptions: - - Prune=true + syncPolicy: + automated: {} + retry: + limit: 5 + backoff: + duration: 30s + factor: 2 + maxDuration: 3m + syncOptions: + - Prune=true syncWave: "0" targetRevision: v0.1.0 # Deploy and configure OpenStackControlPlane resource. @@ -69,8 +109,16 @@ applications: repoURL: https://github.com/openstack-k8s-operators/gitops finalizers: - resources-finalizer.argocd.argoproj.io/foreground - syncOptions: - - Prune=true + syncPolicy: + automated: {} + retry: + limit: 5 + backoff: + duration: 30s + factor: 2 + maxDuration: 3m + syncOptions: + - Prune=true syncWave: "10" targetRevision: v0.1.0 # Deploy and configure OpenStackDataPlaneNodeSet and OpenStackDataPlaneDeployment resources. @@ -80,7 +128,15 @@ applications: enabled: true path: "example/openstack-dataplane" repoURL: https://github.com/openstack-k8s-operators/gitops - syncOptions: - - Prune=true + syncPolicy: + automated: {} + retry: + limit: 5 + backoff: + duration: 30s + factor: 2 + maxDuration: 3m + syncOptions: + - Prune=true syncWave: "20" targetRevision: v0.1.0 diff --git a/overrides/values-rhoso-gitops.yaml b/overrides/values-rhoso-gitops.yaml index 5c8ac06..7ce0dd1 100644 --- a/overrides/values-rhoso-gitops.yaml +++ b/overrides/values-rhoso-gitops.yaml @@ -8,40 +8,80 @@ applications: repoURL: https://github.com/openstack-k8s-operators/gitops path: example/dependencies targetRevision: v0.1.0 - syncOptions: - - Prune=true + syncPolicy: + automated: {} + retry: + limit: 5 + backoff: + duration: 30s + factor: 2 + maxDuration: 3m + syncOptions: + - Prune=true syncWave: "-20" openstack-operator: enabled: true repoURL: https://github.com/openstack-k8s-operators/gitops path: example/openstack-operator targetRevision: v0.1.0 - syncOptions: - - Prune=true + syncPolicy: + automated: {} + retry: + limit: 5 + backoff: + duration: 30s + factor: 2 + maxDuration: 3m + syncOptions: + - Prune=true syncWave: "-20" openstack-operator-cr: enabled: true repoURL: https://github.com/openstack-k8s-operators/gitops path: example/openstack-operator-cr targetRevision: v0.1.0 - syncOptions: - - Prune=true + syncPolicy: + automated: {} + retry: + limit: 5 + backoff: + duration: 30s + factor: 2 + maxDuration: 3m + syncOptions: + - Prune=true syncWave: "-15" openstack-secrets: enabled: false repoURL: https://github.com/openstack-k8s-operators/gitops path: TODO targetRevision: v0.1.0 - syncOptions: - - Prune=true + syncPolicy: + automated: {} + retry: + limit: 5 + backoff: + duration: 30s + factor: 2 + maxDuration: 3m + syncOptions: + - Prune=true syncWave: "-10" openstack-networks: enabled: true repoURL: https://github.com/openstack-k8s-operators/gitops path: example/openstack-networks targetRevision: v0.1.0 - syncOptions: - - Prune=true + syncPolicy: + automated: {} + retry: + limit: 5 + backoff: + duration: 30s + factor: 2 + maxDuration: 3m + syncOptions: + - Prune=true syncWave: "0" openstack-controlplane: enabled: true @@ -50,14 +90,30 @@ applications: targetRevision: v0.1.0 finalizers: - resources-finalizer.argocd.argoproj.io/foreground - syncOptions: - - Prune=true + syncPolicy: + automated: {} + retry: + limit: 5 + backoff: + duration: 30s + factor: 2 + maxDuration: 3m + syncOptions: + - Prune=true syncWave: "10" openstack-dataplane: enabled: true repoURL: https://github.com/openstack-k8s-operators/gitops path: example/openstack-dataplane targetRevision: v0.1.0 - syncOptions: - - Prune=true + syncPolicy: + automated: {} + retry: + limit: 5 + backoff: + duration: 30s + factor: 2 + maxDuration: 3m + syncOptions: + - Prune=true syncWave: "20" From 9b4f54dff9fd7f3a7ebf68f72e7dd18cdf963690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Jeanneret?= Date: Thu, 23 Jul 2026 14:13:05 +0200 Subject: [PATCH 2/3] Deprecate standalone syncOptions parameter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the syncOptions fallback from the template helper — syncPolicy is now the sole source for sync configuration. Upgrade the JSON Schema from draft-07 to 2019-09 so syncOptions can be marked deprecated: true, giving tooling a formal signal while still accepting existing values files without a hard break. Co-Authored-By: Claude Opus 4.6 --- charts/all/rhoso-gitops/README.md | 3 +- .../all/rhoso-gitops/templates/_helpers.tpl | 16 ++--- .../rhoso-gitops/tests/application_test.yaml | 71 +++---------------- charts/all/rhoso-gitops/values.schema.json | 15 ++-- 4 files changed, 22 insertions(+), 83 deletions(-) diff --git a/charts/all/rhoso-gitops/README.md b/charts/all/rhoso-gitops/README.md index 63e5f67..8d0d963 100644 --- a/charts/all/rhoso-gitops/README.md +++ b/charts/all/rhoso-gitops/README.md @@ -107,11 +107,10 @@ render that `Application`; `enabled: false` skips it. | `path` | string | Directory in the repository (empty → `"."`). | | `targetRevision` | string | Branch, tag, or commit (empty → `"HEAD"`). | | `syncWave` | string | `argocd.argoproj.io/sync-wave` annotation. | -| `syncOptions` | list | Used when `syncPolicy` is empty; default includes `Prune=true`. | +| `syncPolicy` | map | Full `spec.syncPolicy`. Defaults to automated sync with retry and `Prune=true`; set explicitly to override (e.g. remove `automated` for manual sync). Put `syncOptions` inside this map. | | `kustomize` | map | `spec.source.kustomize` ([Argo CD Kustomize][argo-kustomize]). | | `finalizers` | list | `metadata.finalizers` (`background` or `foreground`). | | `project` | string | Argo CD AppProject (default `default`). | -| `syncPolicy` | map | Full `spec.syncPolicy`. Defaults to automated sync with retry and `Prune=true`; set explicitly to override (e.g. remove `automated` for manual sync). When set, top-level `syncOptions` are ignored. | Schema validation: `values.schema.json` (used by `helm lint` in CI). diff --git a/charts/all/rhoso-gitops/templates/_helpers.tpl b/charts/all/rhoso-gitops/templates/_helpers.tpl index 0c53490..9051980 100644 --- a/charts/all/rhoso-gitops/templates/_helpers.tpl +++ b/charts/all/rhoso-gitops/templates/_helpers.tpl @@ -66,23 +66,17 @@ Pass dict with key "app" (per-application values map). Omitted if unset, non-map {{/* Build spec.syncPolicy; emit block or nothing. -If syncPolicy is a non-empty map, it is the sole source (top-level syncOptions is ignored). -If syncPolicy is absent or empty, top-level syncOptions is merged in as spec.syncPolicy.syncOptions. Pass dict with key "app" (per-application values map). */}} {{- define "rhoso-gitops.syncPolicySpec" -}} {{- $app := .app -}} -{{- $merged := $app.syncPolicy | default dict }} -{{- if not (kindIs "map" $merged) }} -{{- $merged = dict }} +{{- $sp := $app.syncPolicy | default dict }} +{{- if not (kindIs "map" $sp) }} +{{- $sp = dict }} {{- end }} -{{- if not (empty $merged) }} +{{- if not (empty $sp) }} syncPolicy: -{{ toYaml $merged | indent 4 }} -{{- else if and $app.syncOptions (not (empty $app.syncOptions)) }} -{{- $only := dict "syncOptions" $app.syncOptions }} - syncPolicy: -{{ toYaml $only | indent 4 }} +{{ toYaml $sp | indent 4 }} {{- end }} {{- end }} diff --git a/charts/all/rhoso-gitops/tests/application_test.yaml b/charts/all/rhoso-gitops/tests/application_test.yaml index 0fd0ed3..acab2e5 100644 --- a/charts/all/rhoso-gitops/tests/application_test.yaml +++ b/charts/all/rhoso-gitops/tests/application_test.yaml @@ -74,58 +74,6 @@ tests: path: metadata.finalizers[0] value: resources-finalizer.argocd.argoproj.io/background - - it: uses syncPolicy and ignores top-level syncOptions when both are set - set: - applicationNamespace: openshift-gitops - destinationServer: https://kubernetes.default.svc - applications: - policy-merge-test: - enabled: true - repoURL: https://github.com/example/repo - path: "." - targetRevision: HEAD - syncWave: "0" - syncPolicy: - automated: - prune: true - syncOptions: - - FromNestedPolicy=true - syncOptions: - - FromTopLevel=true - documentSelector: - path: metadata.name - value: policy-merge-test - asserts: - - lengthEqual: - path: spec.syncPolicy.syncOptions - count: 1 - - equal: - path: spec.syncPolicy.syncOptions[0] - value: FromNestedPolicy=true - - - it: uses top-level syncOptions when syncPolicy is unset - set: - applicationNamespace: openshift-gitops - destinationServer: https://kubernetes.default.svc - applications: - syncopt-only: - enabled: true - repoURL: https://github.com/example/repo - path: "." - targetRevision: main - syncWave: "0" - syncOptions: - - OnlyFromTopLevel=true - documentSelector: - path: metadata.name - value: syncopt-only - asserts: - - equal: - path: spec.syncPolicy.syncOptions[0] - value: OnlyFromTopLevel=true - - notExists: - path: spec.syncPolicy.automated - - it: allows disabling automated sync via syncPolicy override set: applicationNamespace: openshift-gitops @@ -159,8 +107,9 @@ tests: repoURL: https://github.com/example/repo path: "." targetRevision: main - syncOptions: - - Prune=true + syncPolicy: + syncOptions: + - Prune=true documentSelector: path: metadata.name value: no-wave @@ -190,8 +139,9 @@ tests: path: examples/foo targetRevision: main syncWave: "0" - syncOptions: - - Prune=true + syncPolicy: + syncOptions: + - Prune=true kustomize: namePrefix: dev- components: @@ -217,7 +167,6 @@ tests: path: "." targetRevision: main syncWave: "0" - syncOptions: [] asserts: - failedTemplate: errorPattern: repoURL @@ -233,7 +182,6 @@ tests: path: "." targetRevision: main syncWave: "0" - syncOptions: [] asserts: - failedTemplate: errorPattern: destinationServer @@ -258,8 +206,9 @@ tests: repoURL: https://github.com/example/repo path: "." targetRevision: main - syncOptions: - - RespectIgnoreDifferences=true + syncPolicy: + syncOptions: + - RespectIgnoreDifferences=true ignoreDifferences: - group: core.openstack.org kind: OpenStackControlPlane @@ -290,7 +239,6 @@ tests: repoURL: https://github.com/example/repo path: "." targetRevision: main - syncOptions: [] ignoreDifferences: - group: dataplane.openstack.org kind: OpenStackDataPlaneNodeSet @@ -315,7 +263,6 @@ tests: path: "." targetRevision: main syncWave: "0" - syncOptions: [] finalizers: - resources-finalizer.argocd.argoproj.io/invalid asserts: diff --git a/charts/all/rhoso-gitops/values.schema.json b/charts/all/rhoso-gitops/values.schema.json index 70107e8..2b98774 100644 --- a/charts/all/rhoso-gitops/values.schema.json +++ b/charts/all/rhoso-gitops/values.schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://github.com/validatedpatterns-sandbox/rhoso-gitops/charts/all/rhoso-gitops/values.schema.json", "title": "rhoso-gitops Helm chart values", "description": "Validated against templates/ and the documentation block in values.yaml.", @@ -29,11 +29,11 @@ "pattern": "^[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?$" }, "additionalProperties": { - "$ref": "#/definitions/application" + "$ref": "#/$defs/application" } } }, - "definitions": { + "$defs": { "application": { "type": "object", "description": "Per-application values under applications..", @@ -70,11 +70,10 @@ }, "syncOptions": { "type": "array", - "description": "Argo CD sync option strings. Used for spec.syncPolicy.syncOptions when syncPolicy is not set or is an empty object; ignored when syncPolicy is a non-empty object (use syncPolicy.syncOptions).", - "minItems": 0, + "deprecated": true, + "description": "Deprecated: ignored at runtime. Use syncPolicy.syncOptions instead.", "items": { - "type": "string", - "minLength": 1 + "type": "string" } }, "project": { @@ -83,7 +82,7 @@ }, "syncPolicy": { "type": "object", - "description": "When non-empty, the map is rendered as spec.syncPolicy in full; top-level syncOptions is not merged in. For syncOptions only, omit syncPolicy or set it to {} and set top-level syncOptions.", + "description": "Full spec.syncPolicy map. Defaults to automated sync with retry and Prune=true. Set explicitly to override (e.g. omit automated for manual sync). Include syncOptions inside this map.", "additionalProperties": true }, "kustomize": { From c0a72a62e512fc87f097dd9a864de941de2d5a4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Jeanneret?= Date: Thu, 23 Jul 2026 14:39:10 +0200 Subject: [PATCH 3/3] Address biome format issues --- charts/all/rhoso-gitops/values.schema.json | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/charts/all/rhoso-gitops/values.schema.json b/charts/all/rhoso-gitops/values.schema.json index 2b98774..33ed54c 100644 --- a/charts/all/rhoso-gitops/values.schema.json +++ b/charts/all/rhoso-gitops/values.schema.json @@ -37,12 +37,7 @@ "application": { "type": "object", "description": "Per-application values under applications..", - "required": [ - "enabled", - "repoURL", - "path", - "targetRevision" - ], + "required": ["enabled", "repoURL", "path", "targetRevision"], "additionalProperties": false, "properties": { "enabled": {