Argo CD ApplicationSet with templatePatch
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:
- Helm charts with a values file.
- Helm charts with a values file and additional manifests.
- 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.
Related Reads
- https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/
- https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/Template/
- https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/Security/