Argo CD ApplicationSet with templatePatch

[GunnarGrop] - - 4 mins read

Abstract

Deploying applications with Argo CD can be done different ways for several different reasons. For me, it differ quite a bit between the deployments I maintain professionally, versus the stuff I deploy in my homelab. In my homelab I don’t care as much about stability or maintainability, and I care more about testing interesting things, quickly, and easily.

Therefore I’ve been trying to utilize Argo CD’s ApplicationSet resource to deploy applications in my homelab as easily and quickly as possible. Preferably I’d only need a single ApplicationSet to deploy everything. Something i didn’t think possible before I realized that the templatePatch field can be used instead of template for basically everything.

Writing ApplicationSets

Normally, you’d write an ApplicationSet something like this:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: example
spec:
  goTemplate: true
  goTemplateOptions: ["missingkey=error"]
  generators:
    - git:
        repoURL: https://git.example.com/argocd
        revision: HEAD
        files:
          - path: "**/values.yaml"
  template:
    metadata:
      name: '{{ .path.basename }}'
    spec:
      project: default
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{ .path.basename }}'
      source:
        chart: example-cahrt
        repoURL: https://example.github.io/charts/
        targetRevision: 1.0.0
        helm:
          valueFiles:
            - '{{ .path.path }}/values.yaml'

In this case the ApplicationSet uses a git generator to look through a specified git repository and generates an Application resource for every file it finds called values.yaml. Furthermore, the name of the generated Applications will be the name of the directory where values.yaml lives.

This ApplicationSet will work great if your’re planning on running several instances/deployments of the same Helm chart! An example git directory structure would look something like this:

.
├── app1
│   └── values.yaml
├── app2
│   └── values.yaml
└── app3
    └── values.yaml

But that’s not quite what I’m after. In my homelab I run many different charts, with different requirements. Effectively, I have three distinct types of deployments:

  1. Helm charts with a values file.
  2. Helm charts with a values file and additional manifests.
  3. Just Kubernetes manifests.

So, with the help of templatePatch I came up with this design:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: meta-applicationset-applications
spec:
  goTemplate: true
  goTemplateOptions: ["missingkey=error"]
  generators:
    - git:
        repoURL: https://git.sr.ht/~gunnargrop/argocd-env-mars.internal
        revision: HEAD
        files:
          - path: "apps/**/meta.yaml"
  templatePatch: |
    spec:
      sources:
        {{- if .sources.chart.enabled }}
        - repoURL: "{{ .sources.chart.repoURL }}"
          chart: "{{ .sources.chart.chart }}"
          targetRevision: "{{ .sources.chart.targetRevision }}"
          helm:
            valueFiles:
              - "$values/{{ .path.path }}/values.yaml"
        {{- end }}
        {{- if .sources.values.enabled }}
        - repoURL: https://git.sr.ht/~gunnargrop/argocd-env-mars.internal
          targetRevision: "{{ .sources.values.targetRevision }}"
          ref: values
        {{- end }}
        {{- if .sources.manifests.enabled }}
        - repoURL: https://git.sr.ht/~gunnargrop/argocd-env-mars.internal
          targetRevision: "{{ .sources.manifests.targetRevision }}"
          path: "{{ .path.path }}/manifests"
        {{- end }}
      {{- with .syncPolicy }}
      syncPolicy:
        {{- toYaml . | nindent 6 }}
      {{- end }}
  template:
    metadata:
      name: "{{ .path.basename }}"
    spec:
      project: "{{ .project }}"
      destination:
        server: https://kubernetes.default.svc
        namespace: "{{ .namespace }}"

The biggest difference here is that spec.source and spec.syncPolicy is defined entirely inside of the templatePatch field. What this allows for is much more powerful gotmpl templating, where we can have if statements and other handy constructs.

It might be a bit dense to read and understand from a glance, so I’ll point out how it works by showing you what the git repository structure looks like.

.
└── apps
    ├── app1
    │   ├── meta.yaml
    │   └── values.yaml
    ├── app2
    │   ├── manifests
    │   │   └── secret.yaml
    │   ├── meta.yaml
    │   └── values.yaml
    └── app3
        ├── manifests
        │   ├── deployment.yaml
        │   ├── ingress.yaml
        │   └── service.yaml
        └── meta.yaml

As you can see this ApplicationSet takes care of deploying all of the different deployment types I have in my homelab. Importantly every “app” in the git repo have a special meta.yaml file. This file is just used to specify a bunch of variables that Argo CD will read and use in the Application templating.

Here’s one example of a meta.yaml that just deploys regular manifests:

project: default
namespace: default

sources:
  chart:
    enabled: false

  values:
    enabled: false

  manifests:
    enabled: true
    targetRevision: HEAD

syncPolicy:
  automated:
    prune: true

And here is an example of a meta.yaml that deploys a Helm chart:

project: default
namespace: default

sources:
  chart:
    enabled: true
    repoURL: registry-1.docker.io/example
    chart: example
    targetRevision: 1.0.0

  values:
    enabled: true
    targetRevision: HEAD

  manifests:
    enabled: false

syncPolicy:
  automated:
    prune: true

  syncOptions:
    - CreateNamespace=true

  managedNamespaceMetadata:
    labels:
      trust: enabled

Both of these files will be picked up by the ApplicationSet, which will create one Application per file.

This is pretty much all I want! Now I no longer have to write any Application/ApplicationSet resources manually, since I can just create a simple meta.yaml file in a directory in the specified git repository, and the one ApplicationSet will deploy all of my applications.

Conclusion

As you can see, ApplicationSets are even more flexible than at first glance. Of course, your needs/wants will probably differ from mine. Maybe you want a different directory structure, or to supply several values files to a Helm deployment? Argo CD and it’s ApplicationSets can probably handle it.

Hopefully this little article has provided you a starting point for how to templatePatch.