22

I am trying to insert multiline json string into helm template for base64 encoding required for Kubernetes secret.

Goals:

  • helm value is injected into json string
  • multi-line json string must be base64 encoded using b64enc

myfile1.json does not work but myfile2.json works. I prefer not to put entire json file in values.yaml.

apiVersion: v1
kind: Secret
metadata:
  name: {{ template "mychart.fullname" . }}
  labels:
    app: {{ template "mychart.name" . }}
    chart: {{ template "mychart.chart" . }}
    release: {{ .Release.Name }}
    heritage: {{ .Release.Service }}
type: Opaque
data:
  myfile.json: {{ |-
    {
      "item1": {
          "name": "{{ .Values.item1.name }}"
      },
      "item2": {
      }
    } | b64enc }}
  myfile2.json: {{ .Values.myfile2 | b64enc }}
Steve
  • 863
  • 3
  • 9
  • 21

3 Answers3

39

You actually don't need to base64-encode the secret in the helm chart. If you use the stringData field instead of data field, Kubernetes knows that it needs to base64 encode the data upon the secret's deployment.

From the docs (Source):

The Secret contains two maps: data and stringData. The data field is used to store arbitrary data, encoded using base64. The stringData field is provided for convenience, and allows you to provide secret data as unencoded strings.

So we can rewrite your secret using stringData instead of data and keep multiline json strings in templates like so:

apiVersion: "v1"
kind: "Secret"
metadata:
  name: {{ template "mychart.fullname" . }}
  labels:
    app: {{ template "mychart.name" . }}
    chart: {{ template "mychart.chart" . }}
    release: {{ .Release.Name }}
    heritage: {{ .Release.Service }}
type: "Opaque"
stringData:
  myfile.json: |-
    {
      "item1": {
          "name": "{{ .Values.item1.name }}"
      },
      "item2": {
      }
    }
  myfile2.json: {{ .Values.myfile2 }}

Note that this does not mean you suddenly need to worry about having unencoded secrets. stringData will ultimately be base64-encoded and converted to data when it is installed, so it will behave exactly the same once it's loaded into Kubernetes.

Again, from the docs (emphasis mine) (Source):

stringData allows specifying non-binary secret data in string form. It is provided as a write-only convenience method. All keys and values are merged into the data field on write, overwriting any existing values. It is never output when reading from the API.

Technetium
  • 5,902
  • 2
  • 43
  • 54
8

I found a solution. You can use tpl function on json file to render template.

apiVersion: v1
kind: Secret
metadata:
  name: {{ template "mychart.fullname" . }}
  labels:
    app: {{ template "mychart.name" . }}
    chart: {{ template "mychart.chart" . }}
    release: {{ .Release.Name }}
    heritage: {{ .Release.Service }}
type: Opaque
data:
  myfile.json: {{ tpl(.Files.Get "myfile.json") . | b64enc }}

myfile.json

{
  "item1": {
    "name": "{{ .Values.item1.name }}"
  },
  "item2": {
  }
}
Steve
  • 863
  • 3
  • 9
  • 21
  • 1
    While using `stringData` as mentioned in the highest upvoted answer is good enough for most use-cases, I hope this will be voted higher because that solution is not the correct answer for every case. This solution is great for my case, as I already have a base64 encoded string I wanted to use directly. The string is stored in a file that's accessible to my CI jobs, so this solution allows me to read the file in directly to the `data` field without it becoming encoded a second time, and without having to decode the file to read it in. – Speeddymon Jun 23 '23 at 18:33
6

My impression (and others seem to have hit it too) is you have to compromise either on it being multi-line or on not putting it in a file. I think the problem is that you have to use a yaml instruction (the |-) to get multiple lines and that is part of the template itself so you can't get an 'output' from it in a way that you can then feed into b64enc.

If this were a ConfigMap you wouldn't need to feed into b64enc so it would be as simple as:

  myfile.json: |
    {
      "item1": {
          "name": "{{ .Values.item1.name }}"
      },
      "item2": {
      }
    }

Or if you were to compromise on single-line approach then that could be:

myfile.json: {{ tpl ("{ 'item1': { 'name': '{{ .Values.item1.name }}' }, 'item2': { } }") . | toJson | b64enc }}

If it were coming from a file then you could use {{ tpl (.Files.Get "files/myfile.json") . | b64enc | quote }}

Another option would be to put the whole json in the values file

Or you could have a myfile entry in your values file like:

myfile:
  item1:
    name: "bob"
  item2:
    name: "fred"

And then use it with myfile.json: {{ .Values.myfile | toJson | b64enc }}

Ryan Dawson
  • 11,832
  • 5
  • 38
  • 61