From 9e54fd749e088c602563cdf8e74e1b2488865b9f Mon Sep 17 00:00:00 2001
From: Dean Sheather <dean@deansheather.com>
Date: Wed, 6 Apr 2022 01:46:32 +1000
Subject: [PATCH] feat(helm): allow arbitrary environment vars on coderd (#236)

(cherry picked from commit 0ea854764bb2eed86da57b065e707c6cb897c43b)
---
 README.md             |  3 ++-
 templates/coderd.yaml |  6 +++++
 tests/envs_test.go    | 62 +++++++++++++++++++++++++++++++++++++++++++
 tests/values.go       |  1 +
 values.yaml           |  9 +++++++
 5 files changed, 80 insertions(+), 1 deletion(-)
 create mode 100644 tests/envs_test.go

diff --git a/README.md b/README.md
index 1fbb8796..019a4898 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ View [our docs](https://coder.com/docs/setup/installation) for detailed installa
 | certs | object | Certificate that will be mounted inside Coder services. | `{"secret":{"key":"","name":""}}` |
 | certs.secret.key | string | Key pointing to a certificate in the secret. | `""` |
 | certs.secret.name | string | Name of the secret. | `""` |
-| coderd | object | Primary service responsible for all things Coder! | `{"affinity":{"podAntiAffinity":{"preferredDuringSchedulingIgnoredDuringExecution":[{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app.kubernetes.io/name","operator":"In","values":["coderd"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":1}]}},"alternateHostnames":[],"annotations":{},"builtinProviderServiceAccount":{"annotations":{},"labels":{}},"clientTLS":{"secretName":""},"devurlsHost":"","extraLabels":{},"image":"","networkPolicy":{"enable":true},"oidc":{"enableRefresh":false,"redirectOptions":{}},"podSecurityContext":{"runAsGroup":1000,"runAsNonRoot":true,"runAsUser":1000,"seccompProfile":{"type":"RuntimeDefault"}},"proxy":{"exempt":"cluster.local","http":"","https":""},"replicas":1,"resources":{"limits":{"cpu":"250m","memory":"512Mi"},"requests":{"cpu":"250m","memory":"512Mi"}},"reverseProxy":{"headers":[],"trustedOrigins":[]},"satellite":{"accessURL":"","enable":false,"primaryURL":""},"securityContext":{"allowPrivilegeEscalation":false,"readOnlyRootFilesystem":true,"runAsGroup":1000,"runAsNonRoot":true,"runAsUser":1000,"seccompProfile":{"type":"RuntimeDefault"}},"serviceAnnotations":{},"serviceNodePorts":{"http":null,"https":null},"serviceSpec":{"externalTrafficPolicy":"Local","loadBalancerIP":"","loadBalancerSourceRanges":[],"type":"LoadBalancer"},"superAdmin":{"passwordSecret":{"key":"password","name":""}},"tls":{"devurlsHostSecretName":"","hostSecretName":""},"trustProxyIP":false}` |
+| coderd | object | Primary service responsible for all things Coder! | `{"affinity":{"podAntiAffinity":{"preferredDuringSchedulingIgnoredDuringExecution":[{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app.kubernetes.io/name","operator":"In","values":["coderd"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":1}]}},"alternateHostnames":[],"annotations":{},"builtinProviderServiceAccount":{"annotations":{},"labels":{}},"clientTLS":{"secretName":""},"devurlsHost":"","extraEnvs":[],"extraLabels":{},"image":"","networkPolicy":{"enable":true},"oidc":{"enableRefresh":false,"redirectOptions":{}},"podSecurityContext":{"runAsGroup":1000,"runAsNonRoot":true,"runAsUser":1000,"seccompProfile":{"type":"RuntimeDefault"}},"proxy":{"exempt":"cluster.local","http":"","https":""},"replicas":1,"resources":{"limits":{"cpu":"250m","memory":"512Mi"},"requests":{"cpu":"250m","memory":"512Mi"}},"reverseProxy":{"headers":[],"trustedOrigins":[]},"satellite":{"accessURL":"","enable":false,"primaryURL":""},"securityContext":{"allowPrivilegeEscalation":false,"readOnlyRootFilesystem":true,"runAsGroup":1000,"runAsNonRoot":true,"runAsUser":1000,"seccompProfile":{"type":"RuntimeDefault"}},"serviceAnnotations":{},"serviceNodePorts":{"http":null,"https":null},"serviceSpec":{"externalTrafficPolicy":"Local","loadBalancerIP":"","loadBalancerSourceRanges":[],"type":"LoadBalancer"},"superAdmin":{"passwordSecret":{"key":"password","name":""}},"tls":{"devurlsHostSecretName":"","hostSecretName":""},"trustProxyIP":false}` |
 | coderd.affinity | object | Allows specifying an affinity rule for the `coderd` deployment. The default rule prefers to schedule coderd pods on different nodes, which is only applicable if coderd.replicas is greater than 1. | `{"podAntiAffinity":{"preferredDuringSchedulingIgnoredDuringExecution":[{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app.kubernetes.io/name","operator":"In","values":["coderd"]}]},"topologyKey":"kubernetes.io/hostname"},"weight":1}]}}` |
 | coderd.alternateHostnames | list | A list of hostnames that coderd (including satellites) will allow for OIDC. If this list is not set, all OIDC traffic will go to the configured access URL in the admin settings on the dashboard (or the satellite's primary URL as configured by Helm). | `[]` |
 | coderd.annotations | object | Apply annotations to the coderd deployment. https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ | `{}` |
@@ -35,6 +35,7 @@ View [our docs](https://coder.com/docs/setup/installation) for detailed installa
 | coderd.clientTLS | object | Client-side TLS configuration for coderd. | `{"secretName":""}` |
 | coderd.clientTLS.secretName | string | Secret containing a PEM encoded cert file. | `""` |
 | coderd.devurlsHost | string | Wildcard hostname to allow matching against custom-created dev URLs. Leaving as an empty string results in DevURLs being disabled. | `""` |
+| coderd.extraEnvs | list | Add additional environment variables to the coderd deployment containers. Overriding any environment variables that the Helm chart sets automatically is unsupported and will result in undefined behavior. You can find a list of the environment variables we set by default by inspecting the helm template files or by running `kubectl describe` against your existing coderd deployment. https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/ | `[]` |
 | coderd.extraLabels | object | Allows specifying additional labels to pods in the `coderd` deployment (.spec.template.metadata.labels). | `{}` |
 | coderd.image | string | Injected by Coder during release. | `""` |
 | coderd.networkPolicy | object | Configure the network policy to apply to coderd. | `{"enable":true}` |
diff --git a/templates/coderd.yaml b/templates/coderd.yaml
index c0bfeae6..0c4dd852 100644
--- a/templates/coderd.yaml
+++ b/templates/coderd.yaml
@@ -85,6 +85,9 @@ spec:
               value: {{ .Values.coderd.proxy.exempt | quote }}
             {{- include "coder.postgres.env" . | indent 12 }}
             {{- include "coder.workspaces.configMapEnv" . | indent 12 }}
+            {{- with .Values.coderd.extraEnvs -}}
+            {{ toYaml . | nindent 12 }}
+            {{- end }}
           command:
             - coderd
             - migrate
@@ -201,6 +204,9 @@ spec:
               value: {{ join "," .Values.coderd.alternateHostnames | quote }}
             {{- include "coder.workspaces.configMapEnv" . | indent 12 }}
             {{- include "coder.postgres.env" . | indent 12 }}
+            {{- with .Values.coderd.extraEnvs -}}
+            {{ toYaml . | nindent 12 }}
+            {{- end }}
           command:
             - coderd
             {{- if not .Values.coderd.satellite.enable }}
diff --git a/tests/envs_test.go b/tests/envs_test.go
new file mode 100644
index 00000000..82903dee
--- /dev/null
+++ b/tests/envs_test.go
@@ -0,0 +1,62 @@
+package tests
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	corev1 "k8s.io/api/core/v1"
+)
+
+func TestExtraEnvs(t *testing.T) {
+	t.Parallel()
+
+	cases := []struct {
+		name string
+		envs []corev1.EnvVar
+	}{
+		{
+			name: "Empty",
+			envs: nil,
+		},
+		{
+			name: "One",
+			envs: []corev1.EnvVar{
+				{
+					Name:  "DEAN_WAS_HERE",
+					Value: "true",
+				},
+			},
+		},
+		{
+			name: "Many",
+			envs: []corev1.EnvVar{
+				{
+					Name:  "DEAN_WAS_HERE",
+					Value: "true",
+				},
+				{
+					Name:  "COLIN_WAS_HERE",
+					Value: "false",
+				},
+			},
+		},
+	}
+
+	for _, c := range cases {
+		c := c
+		t.Run(c.name, func(t *testing.T) {
+			chart := LoadChart(t)
+			objs := chart.MustRender(t, func(cv *CoderValues) {
+				cv.Coderd.ExtraEnvs = c.envs
+			})
+
+			depl := MustFindDeployment(t, objs, "coderd")
+			for _, container := range depl.Spec.Template.Spec.InitContainers {
+				assert.Subsetf(t, container.Env, c.envs, "init container %q", container.Name)
+			}
+			for _, container := range depl.Spec.Template.Spec.Containers {
+				assert.Subsetf(t, container.Env, c.envs, "container %q", container.Name)
+			}
+		})
+	}
+}
diff --git a/tests/values.go b/tests/values.go
index 55538641..a6386f3b 100644
--- a/tests/values.go
+++ b/tests/values.go
@@ -93,6 +93,7 @@ type CoderdValues struct {
 	SuperAdmin                    *CoderdSuperAdminValues                    `json:"superAdmin" yaml:"superAdmin"`
 	Affinity                      *corev1.Affinity                           `json:"affinity" yaml:"affinity"`
 	ExtraLabels                   map[string]string                          `json:"extraLabels" yaml:"extraLabels"`
+	ExtraEnvs                     []corev1.EnvVar                            `json:"extraEnvs" yaml:"extraEnvs"`
 	Proxy                         *CoderdProxyValues                         `json:"proxy" yaml:"proxy"`
 	ReverseProxy                  *CoderdReverseProxyValues                  `json:"reverseProxy" yaml:"reverseProxy"`
 	NetworkPolicy                 *CoderdNetworkPolicyValues                 `json:"networkPolicy" yaml:"networkPolicy"`
diff --git a/values.yaml b/values.yaml
index 33417756..a7e9ee02 100644
--- a/values.yaml
+++ b/values.yaml
@@ -243,6 +243,15 @@ coderd:
   # `coderd` deployment (.spec.template.metadata.labels).
   extraLabels: {}
 
+  # coderd.extraEnvs -- Add additional environment variables to the coderd
+  # deployment containers. Overriding any environment variables that the Helm
+  # chart sets automatically is unsupported and will result in undefined
+  # behavior. You can find a list of the environment variables we set by default
+  # by inspecting the helm template files or by running `kubectl describe`
+  # against your existing coderd deployment.
+  # https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/
+  extraEnvs: []
+
   # coderd.affinity -- Allows specifying an affinity rule for the `coderd`
   # deployment. The default rule prefers to schedule coderd pods on different
   # nodes, which is only applicable if coderd.replicas is greater than 1.
