|
| 1 | +# App of Apps |
| 2 | + |
| 3 | +!!! warning "🧪 Experimental" |
| 4 | + App of Apps support is an experimental feature. The behaviour and flags described on this page may change in future releases without a deprecation notice. |
| 5 | + |
| 6 | +The [App of Apps pattern](https://argo-cd.readthedocs.io/en/stable/operator-manual/cluster-bootstrapping/) is a common Argo CD pattern where a parent Application renders child Application manifests. The parent application points to a directory of Application YAML files, and Argo CD creates those child applications automatically. |
| 7 | + |
| 8 | +Without App of Apps support, `argocd-diff-preview` renders only the applications it discovers directly in your repository files. Child applications that are *generated* by a parent - and therefore never exist as files in the repo - are invisible to the tool. |
| 9 | + |
| 10 | +With the `--traverse-app-of-apps` flag, `argocd-diff-preview` can discover and render those child applications automatically. |
| 11 | + |
| 12 | +--- |
| 13 | + |
| 14 | +## Consider alternatives first |
| 15 | + |
| 16 | +!!! tip "Prefer simpler alternatives when possible" |
| 17 | + The `--traverse-app-of-apps` feature is **slower** and **more limited** than the standard rendering flow. Before enabling it, consider whether one of the alternatives below covers your use case. |
| 18 | + |
| 19 | +**Pre-render your Application manifests** |
| 20 | + |
| 21 | +If your applications do not exist as plain manifests inside the repo, but are instead generated from Helm or Kustomize, you can pre-render them in your CI pipeline and place the output in the branch folder. `argocd-diff-preview` will then pick them up as regular files. See [Helm/Kustomize generated Argo CD applications](./generated-applications.md) for details and examples. |
| 22 | + |
| 23 | +Only use `--traverse-app-of-apps` when the child Applications are *not* committed as plain manifests to the repository AND can *not* be pre-rendered easily. |
| 24 | + |
| 25 | +--- |
| 26 | + |
| 27 | +## How it works |
| 28 | + |
| 29 | +When `--traverse-app-of-apps` is enabled, the tool performs a breadth-first expansion: |
| 30 | + |
| 31 | +1. **Render a parent application** - exactly as it normally would. |
| 32 | +2. **Scan the rendered manifests** for any resources of `kind: Application`. |
| 33 | +3. **Enqueue child applications** - each discovered child is added to the render queue as if it were a top-level application. |
| 34 | +4. **Repeat** - until no new child applications are found or the maximum depth is reached. |
| 35 | + |
| 36 | +--- |
| 37 | + |
| 38 | +## Requirements |
| 39 | + |
| 40 | +- **Render method:** `--traverse-app-of-apps` requires `--render-method=repo-server-api`. The flag will cause an error if used with any other render method. |
| 41 | + |
| 42 | +--- |
| 43 | + |
| 44 | +## Usage |
| 45 | + |
| 46 | +```bash |
| 47 | +argocd-diff-preview \ |
| 48 | + --render-method=repo-server-api \ |
| 49 | + --traverse-app-of-apps |
| 50 | +``` |
| 51 | + |
| 52 | +Or via environment variables: |
| 53 | + |
| 54 | +```bash |
| 55 | +RENDER_METHOD=repo-server-api \ |
| 56 | +TRAVERSE_APP_OF_APPS=true \ |
| 57 | +argocd-diff-preview |
| 58 | +``` |
| 59 | + |
| 60 | +--- |
| 61 | + |
| 62 | +## Application selection |
| 63 | + |
| 64 | +Child applications discovered through the App of Apps expansion are subject to the same [application selection](application-selection.md) filters as top-level applications: |
| 65 | + |
| 66 | +| Filter | Applied to child apps? | |
| 67 | +|---|---| |
| 68 | +| Watch-pattern annotations (`--files-changed`) | ✅ Yes - the child app's own annotations are evaluated | |
| 69 | +| Label selectors (`--selector`) | ✅ Yes | |
| 70 | +| `--watch-if-no-watch-pattern-found` | ✅ Yes | |
| 71 | +| File path regex (`--file-regex`) | ❌ No - child apps have no physical file path | |
| 72 | + |
| 73 | +!!! warning "Filters apply at every level of the tree" |
| 74 | + A child application is only discovered if its **parent is rendered**. If a parent application is excluded by a selector, watch-pattern, or any other filter, the tool never renders it - and therefore never sees its children. This means changes further down the tree can go undetected. |
| 75 | + |
| 76 | + For example, if you use `--selector "team=frontend"` and your root app does not have the label `team: frontend`, none of its children will be processed - even if a child app *does* carry that label. |
| 77 | + |
| 78 | + When using application selection filters together with `--traverse-app-of-apps`, make sure your **root and intermediate applications pass the filters**, not just the leaf applications you care about. |
| 79 | + |
| 80 | +!!! tip "Watch patterns on child apps" |
| 81 | + You can add `argocd-diff-preview/watch-pattern` or `argocd.argoproj.io/manifest-generate-paths` annotations directly to your child Application manifests. These annotations are evaluated against the PR's changed files, just like they are for top-level applications. |
| 82 | + |
| 83 | +### Recommended: use `--file-regex` to select only root applications |
| 84 | + |
| 85 | +If you follow the App of Apps pattern, a practical approach is to use `--file-regex` to select only the root application files and let the tree traversal take care of the rest. This way the root apps are always rendered, and all children are discovered automatically. |
| 86 | + |
| 87 | +For example, if your root application is defined in `apps/root.yaml`: |
| 88 | + |
| 89 | +```bash |
| 90 | +argocd-diff-preview \ |
| 91 | + --render-method=repo-server-api \ |
| 92 | + --traverse-app-of-apps \ |
| 93 | + --file-regex="^apps/root\.yaml$" |
| 94 | +``` |
| 95 | + |
| 96 | +This avoids the problem described above where filters accidentally exclude a parent and silently hide changes in its children. |
| 97 | + |
| 98 | +--- |
| 99 | + |
| 100 | +## Cycle and diamond protection |
| 101 | + |
| 102 | +The expansion engine tracks every `(app-id, branch)` pair it has already rendered. This means: |
| 103 | + |
| 104 | +- **Cycles** (A → B → A) are detected and broken automatically. |
| 105 | +- **Diamond dependencies** (A → C and B → C) cause C to be rendered only once. |
| 106 | + |
| 107 | +--- |
| 108 | + |
| 109 | +## Depth limit |
| 110 | + |
| 111 | +The expansion stops after a maximum depth of **10 levels** to guard against runaway trees. If your App of Apps hierarchy is deeper than 10 levels, applications beyond that depth will not be rendered and a warning will be logged. |
| 112 | + |
| 113 | +--- |
| 114 | + |
| 115 | +## Output |
| 116 | + |
| 117 | +Diff output for child applications looks identical to that of top-level applications. The application name in the diff header includes a breadcrumb showing which parent generated it, making it easy to trace the app-of-apps tree. |
| 118 | + |
| 119 | +For example, a diff generated with a two-level app-of-apps hierarchy might look like this: |
| 120 | + |
| 121 | +``` |
| 122 | +<details> |
| 123 | +<summary>child-app-1 (parent: my-root-app)</summary> |
| 124 | +<br> |
| 125 | +
|
| 126 | +#### ConfigMap: default/some-config |
| 127 | +... |
| 128 | +``` |
0 commit comments