diff --git a/helm/.gitignore b/helm/.gitignore new file mode 100644 index 0000000000..ee3892e879 --- /dev/null +++ b/helm/.gitignore @@ -0,0 +1 @@ +charts/ diff --git a/helm/Makefile b/helm/Makefile index 4010cf42d6..467d4e6e36 100644 --- a/helm/Makefile +++ b/helm/Makefile @@ -17,9 +17,11 @@ lint/helm: lint/helm/coder lint/helm/provisioner .PHONY: lint/helm lint/helm/coder: + helm dependency update --skip-refresh coder/ helm lint --strict --set coder.image.tag=v0.0.1 coder/ .PHONY: lint/helm/coder lint/helm/provisioner: + helm dependency update --skip-refresh provisioner/ helm lint --strict --set coder.image.tag=v0.0.1 provisioner/ .PHONY: lint/helm/provisioner diff --git a/helm/coder/charts/libcoder-0.1.0.tgz b/helm/coder/charts/libcoder-0.1.0.tgz deleted file mode 100644 index baae560bb8..0000000000 Binary files a/helm/coder/charts/libcoder-0.1.0.tgz and /dev/null differ diff --git a/helm/coder/tests/chart_test.go b/helm/coder/tests/chart_test.go index 8a57cdec25..d9bf4fee0c 100644 --- a/helm/coder/tests/chart_test.go +++ b/helm/coder/tests/chart_test.go @@ -84,6 +84,10 @@ var testCases = []testCase{ name: "prometheus", expectedError: "", }, + { + name: "sa_extra_rules", + expectedError: "", + }, } type testCase struct { @@ -113,6 +117,9 @@ func TestRenderChart(t *testing.T) { // Ensure that Helm is available in $PATH helmPath := lookupHelm(t) + err := updateHelmDependencies(t, helmPath, "..") + require.NoError(t, err, "failed to build Helm dependencies") + for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { @@ -154,6 +161,9 @@ func TestUpdateGoldenFiles(t *testing.T) { } helmPath := lookupHelm(t) + err := updateHelmDependencies(t, helmPath, "..") + require.NoError(t, err, "failed to build Helm dependencies") + for _, tc := range testCases { if tc.expectedError != "" { t.Logf("skipping test case %q with render error", tc.name) @@ -175,6 +185,26 @@ func TestUpdateGoldenFiles(t *testing.T) { t.Log("Golden files updated. Please review the changes and commit them.") } +// updateHelmDependencies runs `helm dependency update .` on the given chartDir. +func updateHelmDependencies(t testing.TB, helmPath, chartDir string) error { + // Remove charts/ from chartDir if it exists. + err := os.RemoveAll(filepath.Join(chartDir, "charts")) + if err != nil { + return xerrors.Errorf("failed to remove charts/ directory: %w", err) + } + + // Regenerate the chart dependencies. + cmd := exec.Command(helmPath, "dependency", "update", "--skip-refresh", ".") + cmd.Dir = chartDir + t.Logf("exec command: %v", cmd.Args) + out, err := cmd.CombinedOutput() + if err != nil { + return xerrors.Errorf("failed to run `helm dependency build`: %w\noutput: %s", err, out) + } + + return nil +} + // runHelmTemplate runs helm template on the given chart with the given values and // returns the raw output. func runHelmTemplate(t testing.TB, helmPath, chartDir, valuesFilePath string) (string, error) { diff --git a/helm/coder/tests/testdata/sa_extra_rules.golden b/helm/coder/tests/testdata/sa_extra_rules.golden new file mode 100644 index 0000000000..5766f45c6c --- /dev/null +++ b/helm/coder/tests/testdata/sa_extra_rules.golden @@ -0,0 +1,204 @@ +--- +# Source: coder/templates/coder.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: {} + labels: + app.kubernetes.io/instance: release-name + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: coder + app.kubernetes.io/part-of: coder + app.kubernetes.io/version: 0.1.0 + helm.sh/chart: coder-0.1.0 + name: coder +--- +# Source: coder/templates/rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: coder-workspace-perms +rules: + - apiGroups: [""] + resources: ["pods"] + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch + - apiGroups: [""] + resources: ["persistentvolumeclaims"] + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch + - apiGroups: + - apps + resources: + - deployments + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch + + - apiGroups: + - "" + resources: + - services + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +--- +# Source: coder/templates/rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: "coder" +subjects: + - kind: ServiceAccount + name: "coder" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: coder-workspace-perms +--- +# Source: coder/templates/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: coder + labels: + helm.sh/chart: coder-0.1.0 + app.kubernetes.io/name: coder + app.kubernetes.io/instance: release-name + app.kubernetes.io/part-of: coder + app.kubernetes.io/version: "0.1.0" + app.kubernetes.io/managed-by: Helm + annotations: + {} +spec: + type: LoadBalancer + sessionAffinity: None + ports: + - name: "http" + port: 80 + targetPort: "http" + protocol: TCP + + externalTrafficPolicy: "Cluster" + selector: + app.kubernetes.io/name: coder + app.kubernetes.io/instance: release-name +--- +# Source: coder/templates/coder.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: {} + labels: + app.kubernetes.io/instance: release-name + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: coder + app.kubernetes.io/part-of: coder + app.kubernetes.io/version: 0.1.0 + helm.sh/chart: coder-0.1.0 + name: coder +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/instance: release-name + app.kubernetes.io/name: coder + template: + metadata: + annotations: {} + labels: + app.kubernetes.io/instance: release-name + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: coder + app.kubernetes.io/part-of: coder + app.kubernetes.io/version: 0.1.0 + helm.sh/chart: coder-0.1.0 + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app.kubernetes.io/instance + operator: In + values: + - coder + topologyKey: kubernetes.io/hostname + weight: 1 + containers: + - args: + - server + command: + - /opt/coder + env: + - name: CODER_HTTP_ADDRESS + value: 0.0.0.0:8080 + - name: CODER_PROMETHEUS_ADDRESS + value: 0.0.0.0:2112 + - name: CODER_ACCESS_URL + value: http://coder.default.svc.cluster.local + - name: KUBE_POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: CODER_DERP_SERVER_RELAY_URL + value: http://$(KUBE_POD_IP):8080 + image: ghcr.io/coder/coder:latest + imagePullPolicy: IfNotPresent + lifecycle: {} + livenessProbe: + httpGet: + path: /healthz + port: http + scheme: HTTP + name: coder + ports: + - containerPort: 8080 + name: http + protocol: TCP + readinessProbe: + httpGet: + path: /healthz + port: http + scheme: HTTP + resources: {} + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: null + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + seccompProfile: + type: RuntimeDefault + volumeMounts: [] + restartPolicy: Always + serviceAccountName: coder + terminationGracePeriodSeconds: 60 + volumes: [] diff --git a/helm/coder/tests/testdata/sa_extra_rules.yaml b/helm/coder/tests/testdata/sa_extra_rules.yaml new file mode 100644 index 0000000000..22d6fe81d8 --- /dev/null +++ b/helm/coder/tests/testdata/sa_extra_rules.yaml @@ -0,0 +1,17 @@ +coder: + image: + tag: latest + + serviceAccount: + extraRules: + - apiGroups: [""] + resources: ["services"] + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch diff --git a/helm/coder/values.yaml b/helm/coder/values.yaml index 153d638037..fae239de7f 100644 --- a/helm/coder/values.yaml +++ b/helm/coder/values.yaml @@ -91,9 +91,24 @@ coder: # It is recommended to keep this on if you are using Kubernetes templates # within Coder. workspacePerms: true - # coder.serviceAccount.enableDeployments -- Provides the service account permission - # to manage Kubernetes deployments. + # coder.serviceAccount.enableDeployments -- Provides the service account + # permission to manage Kubernetes deployments. Depends on workspacePerms. enableDeployments: true + # coder.serviceAccount.extraRules -- Additional permissions added to the SA + # role. Depends on workspacePerms. + extraRules: [] + # - apiGroups: [""] + # resources: ["services"] + # verbs: + # - create + # - delete + # - deletecollection + # - get + # - list + # - patch + # - update + # - watch + # coder.serviceAccount.annotations -- The Coder service account annotations. annotations: {} # coder.serviceAccount.name -- The service account name diff --git a/helm/libcoder/templates/_rbac.yaml b/helm/libcoder/templates/_rbac.yaml index c60357ad2a..1320c652c8 100644 --- a/helm/libcoder/templates/_rbac.yaml +++ b/helm/libcoder/templates/_rbac.yaml @@ -43,6 +43,9 @@ rules: - update - watch {{- end }} +{{- with .Values.coder.serviceAccount.extraRules }} +{{ toYaml . | nindent 2 }} +{{- end }} --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding diff --git a/helm/provisioner/charts/libcoder-0.1.0.tgz b/helm/provisioner/charts/libcoder-0.1.0.tgz index 094e3f6420..136a99f3a0 100644 Binary files a/helm/provisioner/charts/libcoder-0.1.0.tgz and b/helm/provisioner/charts/libcoder-0.1.0.tgz differ