Search code examples
kuberneteskustomize

Kubernetes kustomize, patching, merging arrays instead of replacing


I'm trying to append some items to an array in a kustomization.

kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- api.yaml
patches:
- path: patch.yaml

api.yaml

apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: my-api
  namespace: ns
spec:
  values:
    pod:
      env:
      - name: Elastic__AuthenticationType
        value: BasicAuth

patch.yaml

apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: my-api
  namespace: ns
spec:
  values:
    pod:
      env:
      - name: MetaPod__AllowedClients
        value: Test

expected:

apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: my-api
  namespace: ns
spec:
  values:
    pod:
      env:
      - name: Elastic__AuthenticationType
        value: BasicAuth
      - name: MetaPod__AllowedClients
        value: Test

actual:

apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: my-api
  namespace: ns
spec:
  values:
    pod:
      env:
      - name: MetaPod__AllowedClients
        value: Test

I could achieve this via json patching (rather than strategic merge), but for reasons, I have to use strategic merge.

I've read the documentation about $patch: merge, and that doesn't seem to have any affect.

apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: my-api
  namespace: ns
spec:
  values:
    pod:
      env:
      - $patch: merge
        name: MetaPod__AllowedClients
        value: Test
> kustomize version  
v5.4.2

Solution

  • The fundamental problem here is that Kustomize doesn't know how to merge a list in a HelmRelease object. For standard types (Pods, Deployments, Services, etc), Kustomize has built-in information about appropriate merge keys for lists. That is, given a Pod like this:

    apiVersion: v1
    kind: Pod
    metadata:
      name: "example"
    spec:
      containers:
      - name: example
        env:
        - name: WIDGET_COLOR
          value: red
    

    And a kustomization.yaml like this:

    apiVersion: kustomize.config.k8s.io/v1beta1
    kind: Kustomization
    resources:
    - pod.yaml
    
    patches:
      - patch: |
          apiVersion: v1
          kind: Pod
          metadata:
            name: "example"
          spec:
            containers:
            - name: example
              env:
              - name: WIDGET_SIZE
                value: large
    

    Kustomize knows the the name attribute is the appropriate merge key for spec.containers.env. If the patch introduces a new value for an existing name, the value will be updated. If the patch introduces a new name, that will be added to the list.

    For non-standard types, it is often possible to provide this information to Kustomize explicitly using the openapi option, which is documented here with examples here. You can fetch the schema from your kubernetes server using kustomize openapi fetch > openapi.json.

    Unfortunately, this isn't going to help you, because there is no standard schema for HelmRelase.spec.values: the structure of the values is entirely up to the template designer, which is why the HelmRelease schema includes:

            "spec": {
              "x-kubernetes-preserve-unknown-fields": true
            },
    

    That means, "we don't care about or validate any attributes of the HelmRelease.spec".

    You could create a custom schema that applies to your particular configuration, but you probably don't want to do that: it would apply to all HelmRelease objects, which means that as soon as you have more than one it wouldn't be useful.

    Ultimately, I think your best bet is to use JSONPatch patches to update fields in HelmRelease.spec.values.