Skip to content
Allex edited this page Dec 10, 2024 · 1 revision

ArgoCD keeps your kubernetes cluster in sync with git.

Get PR diffs from ArgoCD

As small changes in configuration files, for example a helm chart version update, can result in big changes to the deployed manifests the code-diff does not reflect the actual impact of the PR.

ArgoCD includes a cli with a app diff command that we can run from our CI system and use the resulting output to add a commend with the diff to the PR.

As GitHub has a limit to the size of comments it's a good idea to split the application diff along some axis. I have not run into issues when splitting on project level but this might not be good enough if a large set of apps is in the same project.

Steps:

  • List projects
  • Per project, list apps
  • Per app, call app diff

Authentication

To use the CLI without an argocd login is using the --auth-token option. For this, create a local user with ApiKey capabilities. When the user is created an admin can generate a new ApiKey for that user using the GUI.

This user should also get the required RBAC rules to access the resources we need.

config:
  cm:
    accounts.automation: apiKey
  rbac:
    policy.csv: |
        p, role:automation, applications, get, *, allow
        p, role:automation, projects, get, *, allow

        g, automation, role:automation

Bonus: Cloudflare tunnel

If ArgoCD is deployed behind a cloudflare tunnel which requires additional login we'll have to include a cloudflare Service Token with the correct permissions in the headers. ArgoCD allows adding arbitrary headers to all requests done using the -H flag.

Once you have the Service Token, in cloudflare, add a Policy to the ArgoCD Application with the "Service Auth" Action. Then in the "Additional Rules", add an "Include" rule with Selector "Service Token" and Value the created service token name.

Workflow

These two scripts and workflow run a diff for all apps the argocd API key has access to, grouped by project. Note that it is assumed the $REVISION of all the applications is the same, so they are all sources from the same repository. If some applications have a different source, they will report a Failed to checkout revision message.

All diffs for a single project are written to a single file.

#!/usr/bin/env bash

PROJECT_LIST=$(argocd \
    -H "CF-Access-Client-Id: $CLOUDFLARE_SA_CLIENT_ID" \
    -H "CF-Access-Client-Secret: $CLOUDFLARE_SA_CLIENT_SECRET" \
    --auth-token="$ARGOCD_AUTH_TOKEN" \
    --server="$ARGOCD_SERVER" \
    --grpc-web \
    -o name \
    proj list)

echo "projects=$(jq -ncR '[inputs]' <<< "$PROJECT_LIST")" >> "$GITHUB_OUTPUT"
#!/usr/bin/env bash

# https://github.com/argoproj/argo-cd/issues/9773
export KUBECTL_EXTERNAL_DIFF="diff -u -N -I 'helm\.sh/chart: .*$' -I 'argocd.argoproj.io/tracking-id: .*$'"

EXIT_CODE=0
HAS_DIFF=false

APP_LIST=$(argocd \
    -H "CF-Access-Client-Id: $CLOUDFLARE_SA_CLIENT_ID" \
    -H "CF-Access-Client-Secret: $CLOUDFLARE_SA_CLIENT_SECRET" \
    --auth-token="$ARGOCD_AUTH_TOKEN" \
    --server="$ARGOCD_SERVER" \
    --grpc-web \
    -o name \
    -p "$PROJECT" \
    -l argcd.chain-stock.com/pr-diff!=false \
    app list)

FILE="/tmp/$PROJECT.diff"

if [ -f "$FILE" ]; then
    rm "$FILE"
fi

for APP in $APP_LIST
do
    echo "::group::$APP"
    DIFF=$(argocd \
        -H "CF-Access-Client-Id: $CLOUDFLARE_SA_CLIENT_ID" \
        -H "CF-Access-Client-Secret: $CLOUDFLARE_SA_CLIENT_SECRET" \
        --auth-token="$ARGOCD_AUTH_TOKEN" \
        --server="$ARGOCD_SERVER" \
        --grpc-web \
        --revision="$REVISION" \
        app diff "$APP" 2>&1)
    if [ $? -gt 1 ]
    then
        # argocd diff returns 1 on diff, 0 on no diff and 2 or higher on error.
        # Continue with the diffs but ensure the script as a whole exits with an error.
        EXIT_CODE=1
    fi
    echo "$DIFF"
    echo "::endgroup::"

    if [ "$DIFF" != "" ]
    then
        HAS_DIFF=true
        printf "### $APP\n" >> $FILE
        printf "<details>\n" >> $FILE
        printf "\n\`\`\`diff\n" >> $FILE
        printf "$DIFF" >> $FILE
        printf "\n\`\`\`\n\n" >> $FILE
        printf "</details>\n\n" >> $FILE
    fi
done


# printf "$DIFF" >> /tmp/diff.txt
echo "diff-file=$FILE" >> "$GITHUB_OUTPUT"
echo "has-diff=$HAS_DIFF" >> "$GITHUB_OUTPUT"
exit $EXIT_CODE
name: ArgoCD diff

on:
    pull_request:
        branches:
            - main

concurrency:
    # Cancel existing runs when pushing to the same branch of a PR
    group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
    cancel-in-progress: true

env:
    ARGOCD_SERVER: ${{ secrets.ARGOCD_SERVER }}
    ARGOCD_AUTH_TOKEN: ${{ secrets.ARGOCD_AUTH_TOKEN }}
    CLOUDFLARE_SA_CLIENT_ID: ${{ secrets.CLOUDFLARE_SA_CLIENT_ID }}
    CLOUDFLARE_SA_CLIENT_SECRET: ${{ secrets.CLOUDFLARE_SA_CLIENT_SECRET }}
    REVISION: ${{ github.sha }}

jobs:

    list-projects:
      runs-on: ubuntu-latest
      outputs:
        projects: ${{ steps.list-projects.outputs.projects }}

      steps:
        - name: Install argocd
          shell: bash
          run: |
            curl -sSL -o argocd-linux-amd64 https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
            sudo install -m 555 argocd-linux-amd64 /usr/local/bin/argocd

        - uses: actions/checkout@v3

        - name: List Projects
          shell: bash
          id: list-projects
          run: ./.github/scripts/argocd-proj-list.sh

    diff-apps:
      runs-on: ubuntu-latest
      needs: [list-projects]

      strategy:
        matrix:
          project: ${{fromJson(needs.list-projects.outputs.projects)}}

      steps:
          - name: Install argocd
            shell: bash
            run: |
              curl -sSL -o argocd-linux-amd64 https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
              sudo install -m 555 argocd-linux-amd64 /usr/local/bin/argocd

          - uses: actions/checkout@v3

          # To exlude an app from PR diff, add the argcd.chain-stock.com/pr-diff=false label
          - name: Diff apps
            shell: bash
            continue-on-error: true
            id: app-diff
            env:
              PROJECT: ${{ matrix.project }}
            run: ./.github/scripts/argocd-diff.sh

          - uses: mshick/add-pr-comment@v2
            if: steps.app-diff.outputs.has-diff == 'true'
            with:
              message-id: ${{ matrix.project }}
              message-path: ${{ steps.app-diff.outputs.diff-file }}

Clone this wiki locally