Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ func run(cfg *Config) error {
FilesChanged: filesChanged,
IgnoreInvalidWatchPattern: cfg.IgnoreInvalidWatchPattern,
WatchIfNoWatchPatternFound: cfg.WatchIfNoWatchPatternFound,
InferAppDependencies: cfg.InferAppDependencies,
RepoSelector: cfg.RepoSelector,
}

// Get applications for both branches
Expand Down
9 changes: 9 additions & 0 deletions cmd/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ var (
DefaultDryRun = false
DefaultAutoDetectFilesChanged = true
DefaultWatchIfNoWatchPatternFound = true
DefaultInferAppDependencies = false
DefaultIgnoreInvalidWatchPattern = false
DefaultHideDeletedAppDiff = false
DefaultIgnoreResourceRules = ""
Expand Down Expand Up @@ -112,6 +113,7 @@ type RawOptions struct {
FilesChanged string `mapstructure:"files-changed"`
IgnoreInvalidWatchPattern bool `mapstructure:"ignore-invalid-watch-pattern"`
WatchIfNoWatchPatternFound bool `mapstructure:"watch-if-no-watch-pattern-found"`
InferAppDependencies bool `mapstructure:"infer-app-dependencies"`
AutoDetectFilesChanged bool `mapstructure:"auto-detect-files-changed"`
KeepClusterAlive bool `mapstructure:"keep-cluster-alive"`
ArgocdNamespace string `mapstructure:"argocd-namespace"`
Expand Down Expand Up @@ -159,6 +161,7 @@ type Config struct {
MaxDiffLength uint
IgnoreInvalidWatchPattern bool
WatchIfNoWatchPatternFound bool
InferAppDependencies bool
AutoDetectFilesChanged bool
KeepClusterAlive bool
ArgocdNamespace string
Expand Down Expand Up @@ -252,6 +255,7 @@ func Parse() *Config {
viper.SetDefault("secrets-folder", DefaultSecretsFolder)
viper.SetDefault("create-cluster", DefaultCreateCluster)
viper.SetDefault("watch-if-no-watch-pattern-found", DefaultWatchIfNoWatchPatternFound)
viper.SetDefault("infer-app-dependencies", DefaultInferAppDependencies)
viper.SetDefault("ignore-invalid-watch-pattern", DefaultIgnoreInvalidWatchPattern)
viper.SetDefault("keep-cluster-alive", DefaultKeepClusterAlive)
viper.SetDefault("cluster", DefaultCluster)
Expand Down Expand Up @@ -330,6 +334,7 @@ func Parse() *Config {
rootCmd.Flags().Bool("auto-detect-files-changed", DefaultAutoDetectFilesChanged, "Auto detect files changed between branches")
rootCmd.Flags().Bool("ignore-invalid-watch-pattern", DefaultIgnoreInvalidWatchPattern, "Ignore invalid watch pattern Regex on Applications")
rootCmd.Flags().Bool("watch-if-no-watch-pattern-found", DefaultWatchIfNoWatchPatternFound, "Render applications without watch pattern")
rootCmd.Flags().Bool("infer-app-dependencies", DefaultInferAppDependencies, "Infer each application's local-repo file dependencies (spec.source.path and helm.valueFiles) and render it only when those files change. Combine with --watch-if-no-watch-pattern-found=false to render only affected applications")
rootCmd.Flags().String("redirect-target-revisions", "", "Comma-separated source targetRevision values to redirect to the target branch. Example: main,HEAD. By default, every targetRevision in matching repositories is redirected")
rootCmd.Flags().String("title", DefaultTitle, "Custom title for the markdown output")
rootCmd.Flags().Bool("hide-deleted-app-diff", DefaultHideDeletedAppDiff, "Hide diff content for fully deleted applications (only show deletion header)")
Expand Down Expand Up @@ -406,6 +411,7 @@ func (o *RawOptions) ToConfig() (*Config, error) {
MaxDiffLength: o.MaxDiffLength,
IgnoreInvalidWatchPattern: o.IgnoreInvalidWatchPattern,
WatchIfNoWatchPatternFound: o.WatchIfNoWatchPatternFound,
InferAppDependencies: o.InferAppDependencies,
AutoDetectFilesChanged: o.AutoDetectFilesChanged,
KeepClusterAlive: o.KeepClusterAlive,
ArgocdNamespace: o.ArgocdNamespace,
Expand Down Expand Up @@ -717,6 +723,9 @@ func (o *Config) LogConfig() {
} else {
log.Info().Msgf("✨ --- Skip applications with no watch-pattern annotation")
}
if o.InferAppDependencies {
log.Info().Msgf("✨ --- Infer application dependencies from spec.source.path and helm.valueFiles")
}
}
if len(o.Selectors) > 0 {
selectorStrings := make([]string, len(o.Selectors))
Expand Down
22 changes: 22 additions & 0 deletions docs/application-selection.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,28 @@ spec:

For more details on this annotation, see the [Argo CD documentation](https://argo-cd.readthedocs.io/en/stable/operator-manual/high_availability/#manifest-paths-annotation).

### Option C: Automatic Dependency Inference

Both options above require you to declare each application's dependencies via annotations. The `--infer-app-dependencies` flag removes that requirement: instead of reading annotations, the tool inspects each application's own spec and derives the repository files it depends on, then renders the application only when one of those files changes.

**What is inferred (for sources that live in the repository being diffed):**

- `spec.source.path` / `spec.sources[*].path` — the application is rendered when any changed file falls under that directory. This is source-type agnostic: it works for Helm, Kustomize, plain manifest directories, jsonnet, etc.
- `helm.valueFiles` — individual values files, resolved relative to the source path. References to another source via `$ref/...` (for example `$values/env/prod.yaml`) are resolved against that ref source, so a **remote Helm chart whose values come from a local source** is still rendered when the local values file changes.

**What is not inferred:** files that do not live in this repository (remote chart contents, cross-repo sources) cannot be tracked — the tool only has this repository checked out. Such applications fall back to the `--watch-if-no-watch-pattern-found` setting. Kustomize bases referenced via `../` outside `source.path` are also not traced.

Inferred dependencies are combined with any `watch-pattern` / `manifest-generate-paths` annotations (union — an application renders if either matches), and the application is always rendered if its own manifest file changes.

**Usage** — combine with `--watch-if-no-watch-pattern-found=false` so applications with no changed dependencies are skipped:

```bash
argocd-diff-preview --infer-app-dependencies --watch-if-no-watch-pattern-found=false
```

!!! note "ApplicationSets"
When `--infer-app-dependencies` is enabled, ApplicationSets are always generated (they are not pre-filtered by changed files), and the resulting Applications are then filtered by their inferred dependencies. This means you no longer need `watch-pattern` annotations on the ApplicationSet template — but every non-excluded ApplicationSet is generated on each run (generation is cheap relative to rendering). `render: "never"`, the `ignore` annotation, and label selectors still apply to ApplicationSets as usual.

### Implementing Changed File Detection in CI/CD

Once you've added watch-pattern annotations to your applications, configure your CI/CD pipeline to detect changed files and use them for filtering. Here are two approaches:
Expand Down
1 change: 1 addition & 0 deletions docs/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ argocd-diff-preview [FLAGS] [OPTIONS] (--repo <repo> | --repo-regex <regex>) --t
| `--disable-client-throttling` | `DISABLE_CLIENT_THROTTLING` | `true` | Disable client-side throttling (rely on API Priority and Fairness instead) |
| `--auto-detect-files-changed` | `AUTO_DETECT_FILES_CHANGED` | `true` | Auto detect files changed between branches. Skipped if `--files-changed` is provided |
| `--watch-if-no-watch-pattern-found` | `WATCH_IF_NO_WATCH_PATTERN_FOUND` | `true` | Render applications without watch-pattern annotation |
| `--infer-app-dependencies` | `INFER_APP_DEPENDENCIES` | `false` | Infer each application's local-repo file dependencies (`spec.source.path` and `helm.valueFiles`) and render it only when those files change. Combine with `--watch-if-no-watch-pattern-found=false` |
| `--debug`, `-d` | `DEBUG` | `false` | Activate debug mode |
| `--dry-run` | `DRY_RUN` | `false` | Show which applications would be processed without creating a cluster or generating a diff |
| `--hide-deleted-app-diff` | `HIDE_DELETED_APP_DIFF` | `false` | Hide diff content for deleted applications (only show deletion header) |
Expand Down
Loading