Argo ApplicationSet override template per environment
ApplicationSet is an elegant feature for handing out manual work to the Argo. It enables you to generate Argo Applications programmatically.
In this article, I will demonstrate how I tackled the problem of overriding ApplicationSet template.spec
of the applications itself per environment.
ApplicationSet reference example is given below:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: my-service
namespace: argo
spec:
generators:
- list:
elements:
- cluster: my-cluster-1
environment: "c1"
url: https://kubernetes-cluster-ip:443
- cluster: my-cluster-2
environment: "c2"
url: https://kubernetes-cluster-ip:443
- cluster: my-cluster-3
environment: "c3"
url: https://kubernetes-cluster-ip:443
template:
metadata:
name: '{{`{{cluster}}`}}-service'
labels:
type: kubernetes
spec:
project: 'default'
source:
chart: "ChartNamel"
repoURL: https://ChartURL
targetRevision: "1.0.0"
helm:
parameters:
- name: env
value: "{{`{{environment}}`}}"
releaseName: service
valueFiles:
- "values-{{`{{ cluster }}`}}.yaml"
destination:
server: '{{`{{url}}`}}'
namespace: default
Example above handles 3 clusters. It will create an application using generator capability.
There are many generators (See more). In this example generator list was used. What it does is next:
- Define the list of the custom parameters for use in templating the
template.spec
itself.
You can define ApplicationSet as plain YAML or under the Helm chart. If the plain YAML is used then next notation for the templating is used:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: my-service
namespace: argo
spec:
generators:
- list:
elements:
- cluster: my-cluster-1
environment: "c1"
url: https://kubernetes-cluster-ip:443
- cluster: my-cluster-2
environment: "c2"
url: https://kubernetes-cluster-ip:443
- cluster: my-cluster-3
environment: "c3"
url: https://kubernetes-cluster-ip:443
template:
metadata:
name: '{{cluster}}-service'
labels:
type: kubernetes
spec:
project: 'default'
source:
chart: "ChartNamel"
repoURL: https://ChartURL
targetRevision: "1.0.0"
helm:
parameters:
- name: env
value: "{{environment}}"
releaseName: service
valueFiles:
- "values-{{cluster}}.yaml"
destination:
server: '{{url}}'
namespace: default
On the other hand if defining under the Helm chart it's like the first example.
The difference is in the backticks eg. server: '{{ `{{url}}` }}'
- this tells Helm to parse it as literal.
This is good for templating any values that are not maps or lists.
For example, what you cannot do is next (using helm):
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: my-service
namespace: argo
spec:
generators:
- list:
elements:
- cluster: my-cluster-1
environment: "c1"
url: https://kubernetes-cluster-ip:443
values: |-
foo: bar
- cluster: my-cluster-2
environment: "c2"
url: https://kubernetes-cluster-ip:443
values: |-
foo: baz
- cluster: my-cluster-3
environment: "c3"
url: https://kubernetes-cluster-ip:443
values: |-
foo: bax
template:
metadata:
name: '{{`{{cluster}}`}}-service'
labels:
type: kubernetes
spec:
project: 'default'
source:
chart: "ChartNamel"
repoURL: https://ChartURL
targetRevision: "1.0.0"
helm:
parameters:
- name: env
value: "{{`{{environment}}`}}"
releaseName: service
value: {{`{{url}}` | toYaml | nindent 8 }}
destination:
server: '{{`{{url}}`}}'
namespace: default
So what you cannot do is to template the template.spec with any value that is map or list.
So how could I tackle this issue?
I found out that a generator can have multiple lists in itself and every list generator can override template.spec
lists values. ~ Thank you contributors to this great feature.
You will see in the example what I am talking about:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: my-service
namespace: argo
spec:
generators:
- list:
elements:
- cluster: my-cluster-1
environment: "c1"
url: https://kubernetes-cluster-ip:443
template:
metadata: {}
spec:
project: 'default'
source:
chart: "ChartName"
repoURL: https://ChartURL
targetRevision: "1.0.0"
helm:
parameters:
- name: env
value: "{{`{{environment}}`}}"
releaseName: service
value:
overriding: true
destination: {}
- list:
elements:
- cluster: my-cluster-1
environment: "c1"
url: https://kubernetes-cluster-ip:443
template:
metadata: {}
spec:
project: 'default'
source:
chart: "ChartName"
repoURL: https://ChartURL
targetRevision: "1.0.0"
helm:
parameters:
- name: env
value: "{{`{{environment}}`}}"
releaseName: service
value:
overriding: false
destination: {}
template:
metadata:
name: '{{`{{cluster}}`}}-service'
labels:
type: kubernetes
spec:
project: 'default'
source:
chart: "ChartNamel"
repoURL: https://ChartURL
targetRevision: "1.0.0"
helm:
parameters:
- name: env
value: "{{`{{environment}}`}}"
releaseName: service
destination:
server: '{{`{{url}}`}}'
namespace: default
This way baseline of the template.spec
can be overridden via template.spec
under the specific list generator. Which is an elegant way to override the options of the Application itself where a map or list is needed.
The template.spec
under the list will take precedence over the baseline version of the template.spec
. Fields with {}
value will be ignored and the one from the baseline template.spec
will be used.
This way you can only modify the fields you want.