Using Kustomize to post render a Helm Chart
- - 5 min read
Lately I came across several issues where a given Helm Chart must be modified after it has been rendered by Argo CD. Argo CD does a helm template to render a Chart. Sometimes, especially when you work with Subcharts or when a specific setting is not yet supported by the Chart, you need to modify it later … you need to post-render the Chart.
In this very short article, I would like to demonstrate this on a real-live example I had to do. I would like to inject annotations to a Route objects, so that the certificate can be injected. This is done by the cert-utils operator. For the post-rendering the Argo CD repo pod will be extended with a sidecar container, that is watching for the repos and patches them if required.
Everything below is using OpenShift Gitops Operator. This is based on Argo CD, but instead of directly modifying the repo Deployment, we will modify the Argo CD Custom Resource. |
In the future it will be easier to inject certificates into a Route, by defining a Secret. This as currently a TechPreview feature (OpenShift 4.17). Creating a route with externally managed certificate |
The Route Object
Imagine we have the following Route object, rendered via Helm template:
---
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: my-route
namespace: my-namespace
spec:
host: my.route.apps.cluster.name
port:
targetPort: http
tls:
insecureEdgeTerminationPolicy: Redirect
termination: edge
to:
kind: Service
name: my-service
weight: 100
wildcardPolicy: None
The cert-manager Operator requested a certificate which can be found in the Secret "my-certificate ". To let the cert-utils Operator inject the data from the certificate automatically, we need to add annotations to that Route object.
This injection is usually a good idea, since we do not want to define certificate and (private) key directly in the Route object using our Chart.
apiVersion: route.openshift.io/v1
kind: Route
metadata:
annotations: (1)
cert-manager.io/cluster-issuer: my-issuer
cert-utils-operator.redhat-cop.io/certs-from-secret: my-certificate
1 | Two annotations shall be added to the Route object. |
In this example certificates have to be ordered. No wildcard certificate is available. |
Post-Rendering
To modify the output after it has been rendered by Argo CD we will use Kustomize patch feature. This means, after the template has been rendered, we send it to Kustomize and let it patch it.
Let’s go through the steps one-by-one:
Create a kustomization.yaml Place the following file next to your Chart.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: my-namespace
resources:
- ./all.yaml (1)
patches:
- patch: |
- op: add (2)
path: /metadata/annotations
value:
cert-manager.io/cluster-issuer: my-issuer
cert-utils-operator.redhat-cop.io/certs-from-secret: my-certificate
target:
kind: Route
name: my-route (3)
1 | The all.yaml file will be created by the helm template command. |
2 | Add the annotations to the Route object. |
3 | The name of the Route object. |
This will patch the Route object. You can test this locally by execute the command: helm template . > all.yaml && kustomize build && rm all.yaml
Create an empty file called my-cmp-plugin into the folder next to the Chart.yaml I will explain in a bit why I chose to use this approach.
Create the following ConfigMap in the OpenShift GitOps namespace (for example openshift-gitops)
kind: ConfigMap
apiVersion: v1
metadata:
name: my-cmp-plugin
namespace: openshift-gitops
data:
plugin.yaml: |-
apiVersion: argoproj.io/v1alpha1
kind: ConfigManagementPlugin
metadata:
name: my-cmp-plugin (1)
spec:
version: v1.0
init: (2)
command: [sh, -c, 'echo "Initializing my-plugin-cmp..."', 'helm dependency build || true']
generate: (3)
command: [sh, -c, "helm template . --name-template $ARGOCD_APP_NAME --namespace $ARGOCD_APP_NAMESPACE --include-crds > all.yaml && kustomize build"]
discover: (4)
find:
glob: "**/my-cmp-plugin"
1 | The name of the plugin. |
2 | The init command will be executed once, when the plugin is loaded. |
3 | The generate command will be executed every time the plugin is called. |
4 | The discovery command will be executed to find the plugin. |
This will execute the command to generate a helm template, pipe the output into all.yaml and let Kustomize patch the output. The "discovery" part is looking for a specific file in the repository. I thought this might be useful to pin down this plugin to specific repositories only. However, there are other ways to implement this. You could omit this part and define the name of the plugin inside the Argo CD Application too for example.
Patching Argo CD Repo server
Now it is time to patch our repo server specification of the Argo CD custom resource. The following should do it:
As image for the sidecar container, I am using Gerald Nunn’s tool image. You can use your own image, as long as Helm and Kustomize are available. |
apiVersion: argoproj.io/v1alpha1
kind: ArgoCD
metadata:
name: openshift-gitops
namespace: openshift-gitops
spec:
[...]
repo:
- configMap: (1)
name: my-cmp-plugin
name: my-cmp-plugin
sidecarContainers: (2)
- name: my-cmp-plugin
command: [/var/run/argocd/argocd-cmp-server]
env:
- name: APP_ENV
value: prod
image: quay.io/gnunn/tools:latest (3)
imagePullPolicy: Always
securityContext:
runAsNonRoot: true
volumeMounts: (4)
- mountPath: /var/run/argocd
name: var-files
- mountPath: /home/argocd/cmp-server/plugins
name: plugins
- mountPath: /tmp
name: tmp
- mountPath: /home/argocd/cmp-server/config/plugin.yaml
subPath: plugin.yaml
name: my-cmp-plugin
volumes: (5)
- configMap:
name: cloudbees-cmp-plugin
name: cloudbees-cmp-plugin
1 | The name of the ConfigMap that was created in step 2. |
2 | The sidecar container specification. |
3 | The image that is used for the sidecar container. |
4 | The volume mounts for the sidecar container. |
5 | The volumes for the sidecar container. |
As soon as the repo Pod has been patched a 2nd container inside the Pod will be started as a sidecar. This will take the ConfigMap that was created in step 2 and mount it. As soon as a repo is found where this patch shall be executed, Argo CD will perform the actions defined in the ConfigMap, resulting in the output of the helm template and the patched output of Kustomize.
---
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: my-route
namespace: my-namespace
annotations: (1)
cert-manager.io/cluster-issuer: my-issuer
cert-utils-operator.redhat-cop.io/certs-from-secret: my-certificate
spec:
host: my.route.apps.cluster.name
port:
targetPort: http
tls:
insecureEdgeTerminationPolicy: Redirect
termination: edge
to:
kind: Service
name: my-service
weight: 100
wildcardPolicy: None
1 | The annotations that are added to the Route. |
This is it; this will patch our resource. Such post-renderer can be used for other patches as well. For example, to remove certain items from an object.
2nd Example
In my real-live example I had the problem that the path was empty in the Helm Chart and OpenShift automatically removed that, which was shown as out-of-sync in Argo CD.
So I am using the patch to remove the path.
Only do this if you are sure the element is really empty! |
I extended the kustomization.yaml with
- op: remove
path: /spec/path
so it looks like:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: my-namespace
resources:
- ./all.yaml
patches:
- patch: |
- op: add
path: /metadata/annotations
value:
cert-manager.io/cluster-issuer: my-issuer
cert-utils-operator.redhat-cop.io/certs-from-secret: my-certificate
- op: remove (1)
path: /spec/path
target:
kind: Route
name: my-route
1 | The patch that removes the path. |
This 2nd patch will completely remove the /spec/path from the Route object named my-route.
Further information:
Example, which was the base of my patch: Plugin Sidecar
G.Nunn’s tools image (Thanks for everything): https://quay.io/repository/gnunn/tools
Copyright © 2020 - 2024 Toni Schmidbauer & Thomas Jungbauer