Argo CD Diff Preview is a tool that renders the diff between two branches in a Git repository. It is designed to render manifests generated by Argo CD, providing a clear and concise view of the changes between two branches. It operates similarly to Atlantis for Terraform, creating a plan that outlines the proposed changes.
argocd-diff-preview
will be presented at ArgoCon 2024 in Utah, US. The talk will cover the current tools and methods for visualizing code changes in GitOps workflows and introduce this new approach, which uses ephemeral clusters to render accurate diffs directly on your pull requests.
In the Kubernetes world, we often use templating tools like Kustomize and Helm to generate our Kubernetes manifests. These tools make maintaining and streamlining configuration easier across applications and environments. However, they also make it harder to visualize the application's actual configuration in the cluster.
Mentally parsing Helm templates and Kustomize patches is hard without rendering the actual output. Thus, making mistakes while modifying an application's configuration is relatively easy.
In the field of GitOps and infrastructure as code, all configurations are checked into Git and modified through PRs. The code changes in the PR are reviewed by a human, who needs to understand the changes made to the configuration. This is hard when the configuration is generated through templating tools like Kustomize and Helm.
The safest way to make changes to you Helm Charts and Kustomize Overlays in your GitOps repository is to let Argo CD render them for you. This can be done by spinning up an ephemeral cluster in your automated pipelines. Since the diff is rendered by Argo CD itself, it is as accurate as possible.
The implementation is actually quite simple. It just follows the steps below:
targetRevision
to the Pull Request branchsyncPolicy
from the applications (to avoid the applications syncing locally)[!TIP]
Try demo locally with 3 simple commands!
First, make sure Docker is running. E.g., run
docker ps
to see if it's running.Second, run the following 3 commands:
git clone https://github.com/dag-andersen/argocd-diff-preview base-branch --depth 1 -q git clone https://github.com/dag-andersen/argocd-diff-preview target-branch --depth 1 -q -b helm-example-3 docker run \ --network host \ -v /var/run/docker.sock:/var/run/docker.sock \ -v $(pwd)/output:/output \ -v $(pwd)/base-branch:/base-branch \ -v $(pwd)/target-branch:/target-branch \ -e TARGET_BRANCH=helm-example-3 \ -e REPO=dag-andersen/argocd-diff-preview \ dagandersen/argocd-diff-preview:v0.0.18
and the output would be something like this:
... 🚀 Creating cluster... 🦑 Installing Argo CD... ... 🌚 Getting resources for base-branch 🌚 Getting resources for target-branch ... 🔮 Generating diff between main and helm-example-3 🙏 Please check the ./output/diff.md file for differences
Finally, you can view the diff by running
cat ./output/diff.md
. The diff should look something like this
Pre-requisites:
docker run \
--network host \ # This is required so the container can access the local cluster on the host's docker daemon.
-v /var/run/docker.sock:/var/run/docker.sock \ # This is required to access the host's docker daemon.
-v <path-to-main-branch>:/base-branch \
-v <path-to-pr-branch>:/target-branch \
-v $(pwd)/output:/output \
-e BASE_BRANCH=main \
-e TARGET_BRANCH=<name-of-the-target-branch> \
-e REPO=<owner/repo-name> \
dagandersen/argocd-diff-preview:v0.0.18
Example on how to use it: "Try demo locally with 3 simple commands!"
Pre-requisites:
Check the releases and find the correct binary for your operating system.
Example for downloading and running on macOS:
curl -LJO https://github.com/dag-andersen/argocd-diff-preview/releases/download/v0.0.18/argocd-diff-preview-Darwin-x86_64.tar.gz
tar -xvf argocd-diff-preview-Darwin-x86_64.tar.gz
sudo mv argocd-diff-preview /usr/local/bin
argocd-diff-preview --help
Pre-requisites:
git clone https://github.com/dag-andersen/argocd-diff-preview
cd argocd-diff-preview
cargo run -- --help
name: Argo CD Diff Preview
on:
pull_request:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
path: pull-request
- uses: actions/checkout@v4
with:
ref: main
path: main
- name: Generate Diff
run: |
docker run \
--network=host \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $(pwd)/main:/base-branch \
-v $(pwd)/pull-request:/target-branch \
-v $(pwd)/output:/output \
-e TARGET_BRANCH=${{ github.head_ref }} \
-e REPO=${{ github.repository }} \
dagandersen/argocd-diff-preview:v0.0.18
- name: Post diff as comment
run: |
gh pr comment ${{ github.event.number }} --repo ${{ github.repository }} --body-file output/diff.md --edit-last || \
gh pr comment ${{ github.event.number }} --repo ${{ github.repository }} --body-file output/diff.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
In the simple code examples above, we do not provide the cluster with any credentials, which only works if the image/Helm Chart registry and the Git repository are public. Since your repository might not be public you need to provide the tool with the necessary read-access credentials for the repository. This can be done by placing the Argo CD repo secrets in folder mounted at /secrets
. When the tool starts, it will simply run kubectl apply -f /secrets
to apply every resource to the cluster, before starting the rendering process.
Example of accessing a private repository with a GitHub token:
jobs:
build:
...
steps:
...
- name: Prepare secrets
run: |
mkdir secrets
cat > secrets/secret.yaml << "EOF"
apiVersion: v1
kind: Secret
metadata:
name: private-repo
namespace: argocd
labels:
argocd.argoproj.io/secret-type: repo-creds
stringData:
url: https://github.com/${{ github.repository }}
password: ${{ secrets.GITHUB_TOKEN }} ⬅️ Short-lived GitHub Token
username: not-used
EOF
- name: Generate Diff
run: |
docker run \
--network=host \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $(pwd)/main:/base-branch \
-v $(pwd)/pull-request:/target-branch \
-v $(pwd)/output:/output \
-v $(pwd)/secrets:/secrets \ ⬅️ Mount the secrets folder
-e TARGET_BRANCH=${{ github.head_ref }} \
-e REPO=${{ github.repository }} \
dagandersen/argocd-diff-preview:v0.0.18
For more info, see the Argo CD docs
argocd-diff-preview
will only look for YAML files in the repository with kind: Application
or kind: ApplicationSet
. If your applications are generated from a Helm chart or Kustomize template, you will have to add a step in the pipeline that renders the chart/template.
Helm and Kustomize examples:
jobs:
build:
...
steps:
...
- uses: actions/checkout@v4
with:
path: pull-request
- name: Generate with helm chart
run: helm template pull-request/some/path/my-chart > pull-request/rendered-apps.yaml
- name: Generate with kustomize
run: kustomize build pull-request/some/path/my-kustomize > pull-request/rendered-apps.yaml
- name: Generate Diff
run: |
docker run \
--network=host \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $(pwd)/main:/base-branch \
...
This will place the rendered manifests inside the pull-request
folder, and the tool will pick them up.
Rendering the manifests generated by all applications in the repository on each pull request can be slow. Limiting the number of applications rendered can speed up the rendering process significantly. By default, argocd-diff-preview
will render all applications in the repository.
Here are three ways to limit which applications are rendered:
Run the tool with the --selector
option to filter applications based on labels. The option supports =
, ==
, and !=
.
Example:
argocd-diff-preview --selector "team=a"
This command :arrow_up: will target the following application :arrow_down: and ignore all applications that do not have the label team: a
.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
labels:
team: a
spec:
...
You can exclude specific applications from rendering by adding the annotation argocd-diff-preview/ignore: "true"
to their manifest. This is useful for skipping applications that don’t require a diff.
Example:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
annotations:
argocd-diff-preview/ignore: "true"
spec:
...
Alternatively, use the --file-regex
option to limit rendering to manifests whose file paths match a regular expression. This is helpful when rendering changes from specific teams or directories.
Example:
If someone in your organization from Team A changes to one of their applications, the tool can be run with:
argocd-diff-preview --file-regex="/Team-A/"
This ensures only applications in folders matching */Team-A/*
are rendered.
Argo CD is installed using a Helm Chart. You can specify the Chart version with the --argocd-chart-version
option. It defaults to the latest version.
You can modify the Argo CD Helm Chart installation by providing the tool with a values.yaml
file and mounting it in the argocd-config
folder within the container. Check out all the available values in the Argo CD Helm Chart.
Example:
Here we set configs.cm."kustomize.buildOptions"
in the Chart.
jobs:
build:
...
steps:
...
- name: Set ArgoCD Custom Values
run: |
cat > values.yaml << "EOF"
# set whatever helm values you want
configs:
cm:
kustomize.buildOptions: --load-restrictor LoadRestrictionsNone --enable-helm
EOF
- name: Generate Diff
run: |
docker run \
--network=host \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $(pwd)/main:/base-branch \
-v $(pwd)/pull-request:/target-branch \
-v $(pwd)/values.yaml:/argocd-config/values.yaml \ ⬅️ Mount values.yaml
...
You can install any Argo CD Config Management Plugin that is supported through the Argo CD Helm Chart. However, there is no guarantee that the plugin will work with the tool, as this depends on the plugin and its specific implementation
USAGE:
argocd-diff-preview [FLAGS] [OPTIONS] --repo <repo> --target-branch <target-branch>
FLAGS:
-d, --debug Activate debug mode
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
--argocd-chart-version <version> Argo CD Helm Chart version [env: ARGOCD_CHART_VERSION=]
-b, --base-branch <base-branch> Base branch name [env: BASE_BRANCH=] [default: main]
--base-branch-folder <folder> Base branch folder [env: BASE_BRANCH_FOLDER=] [default: base-branch]
-i, --diff-ignore <diff-ignore> Ignore lines in diff. Example: use 'v[1,9]+.[1,9]+.[1,9]+' for ignoring changes caused by version changes following semver [env: DIFF_IGNORE=]
-r, --file-regex <file-regex> Regex to filter files. Example: "/apps_.*\.yaml" [env: FILE_REGEX=]
-c, --line-count <line-count> Generate diffs with <n> lines above and below the highlighted changes in the diff. [env: LINE_COUNT=] [Default: 10]
--local-cluster-tool <tool> Local cluster tool. Options: kind, minikube [env: LOCAL_CLUSTER_TOOL=] [default: auto]
--max-diff-length <length> Max diff message character count. [env: MAX_DIFF_LENGTH=] [Default: 65536] (GitHub comment limit)
-o, --output-folder <output-folder> Output folder where the diff will be saved [env: OUTPUT_FOLDER=] [default: ./output]
--repo <repo> Git Repository. Format: OWNER/REPO [env: REPO=]
-s, --secrets-folder <secrets-folder> Secrets folder where the secrets are read from [env: SECRETS_FOLDER=] [default: ./secrets]
-l, --selector <selector> Label selector to filter on, supports '=', '==', and '!='. (e.g. -l key1=value1,key2=value2) [env: SELECTOR=]
-t, --target-branch <target-branch> Target branch name [env: TARGET_BRANCH=]
--target-branch-folder <folder> Target branch folder [env: TARGET_BRANCH_FOLDER=] [default: target-branch]
--timeout <timeout> Set timeout [env: TIMEOUT=] [default: 180]
[!IMPORTANT]
Questions, issues, or suggestions
If you experience issues or have any questions or suggestions, please open an issue in this repository! 🚀