From e3ed1ba2260eb3c3dcf699ca8631d2339aef7859 Mon Sep 17 00:00:00 2001 From: Nikola Jokic Date: Fri, 3 Oct 2025 12:03:38 +0200 Subject: [PATCH] Introduce new kubernetes-novolume mode (#4250) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../templates/_helpers.tpl | 95 +++++ .../templates/autoscalingrunnerset.yaml | 12 +- .../templates/kube_mode_role.yaml | 4 +- .../templates/kube_mode_role_binding.yaml | 2 +- .../templates/kube_mode_serviceaccount.yaml | 2 +- .../tests/template_test.go | 361 ++++++++++++++++-- charts/gha-runner-scale-set/values.yaml | 21 +- 7 files changed, 453 insertions(+), 44 deletions(-) diff --git a/charts/gha-runner-scale-set/templates/_helpers.tpl b/charts/gha-runner-scale-set/templates/_helpers.tpl index 8428c29a..9dfb615f 100644 --- a/charts/gha-runner-scale-set/templates/_helpers.tpl +++ b/charts/gha-runner-scale-set/templates/_helpers.tpl @@ -377,6 +377,101 @@ volumeMounts: {{- end }} {{- end }} +{{- define "gha-runner-scale-set.kubernetes-novolume-mode-runner-container" -}} +{{- $tlsConfig := (default (dict) .Values.githubServerTLS) }} +{{- range $i, $container := .Values.template.spec.containers }} + {{- if eq $container.name "runner" }} + {{- $setRunnerImage := "" }} + {{- range $key, $val := $container }} + {{- if and (ne $key "env") (ne $key "volumeMounts") (ne $key "name") }} + {{- if eq $key "image" }} + {{- $setRunnerImage = $val }} + {{- end }} +{{ $key }}: {{ $val | toYaml | nindent 2 }} + {{- end }} + {{- end }} + {{- $setContainerHooks := 1 }} + {{- $setPodName := 1 }} + {{- $setRequireJobContainer := 1 }} + {{- $setActionsRunnerImage := 1 }} + {{- $setNodeExtraCaCerts := 0 }} + {{- $setRunnerUpdateCaCerts := 0 }} + {{- if $tlsConfig.runnerMountPath }} + {{- $setNodeExtraCaCerts = 1 }} + {{- $setRunnerUpdateCaCerts = 1 }} + {{- end }} +env: + {{- with $container.env }} + {{- range $i, $env := . }} + {{- if eq $env.name "ACTIONS_RUNNER_CONTAINER_HOOKS" }} + {{- $setContainerHooks = 0 }} + {{- end }} + {{- if eq $env.name "ACTIONS_RUNNER_IMAGE" }} + {{- $setActionsRunnerImage = 0 }} + {{- end }} + {{- if eq $env.name "ACTIONS_RUNNER_POD_NAME" }} + {{- $setPodName = 0 }} + {{- end }} + {{- if eq $env.name "ACTIONS_RUNNER_REQUIRE_JOB_CONTAINER" }} + {{- $setRequireJobContainer = 0 }} + {{- end }} + {{- if eq $env.name "NODE_EXTRA_CA_CERTS" }} + {{- $setNodeExtraCaCerts = 0 }} + {{- end }} + {{- if eq $env.name "RUNNER_UPDATE_CA_CERTS" }} + {{- $setRunnerUpdateCaCerts = 0 }} + {{- end }} + - {{ $env | toYaml | nindent 4 }} + {{- end }} + {{- end }} + {{- if $setContainerHooks }} + - name: ACTIONS_RUNNER_CONTAINER_HOOKS + value: /home/runner/k8s-novolume/index.js + {{- end }} + {{- if $setPodName }} + - name: ACTIONS_RUNNER_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + {{- end }} + {{- if $setRequireJobContainer }} + - name: ACTIONS_RUNNER_REQUIRE_JOB_CONTAINER + value: "true" + {{- end }} + {{- if $setActionsRunnerImage }} + - name: ACTIONS_RUNNER_IMAGE + value: "{{- $setRunnerImage -}}" + {{- end }} + {{- if $setNodeExtraCaCerts }} + - name: NODE_EXTRA_CA_CERTS + value: {{ clean (print $tlsConfig.runnerMountPath "/" $tlsConfig.certificateFrom.configMapKeyRef.key) }} + {{- end }} + {{- if $setRunnerUpdateCaCerts }} + - name: RUNNER_UPDATE_CA_CERTS + value: "1" + {{- end }} + {{- $mountGitHubServerTLS := 0 }} + {{- if $tlsConfig.runnerMountPath }} + {{- $mountGitHubServerTLS = 1 }} + {{- end }} +volumeMounts: + {{- with $container.volumeMounts }} + {{- range $i, $volMount := . }} + {{- if eq $volMount.name "github-server-tls-cert" }} + {{- $mountGitHubServerTLS = 0 }} + {{- end }} + - {{ $volMount | toYaml | nindent 4 }} + {{- end }} + {{- end }} + {{- if $mountGitHubServerTLS }} + - name: github-server-tls-cert + mountPath: {{ clean (print $tlsConfig.runnerMountPath "/" $tlsConfig.certificateFrom.configMapKeyRef.key) }} + subPath: {{ $tlsConfig.certificateFrom.configMapKeyRef.key }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} + {{- define "gha-runner-scale-set.default-mode-runner-containers" -}} {{- $tlsConfig := (default (dict) .Values.githubServerTLS) }} {{- range $i, $container := .Values.template.spec.containers }} diff --git a/charts/gha-runner-scale-set/templates/autoscalingrunnerset.yaml b/charts/gha-runner-scale-set/templates/autoscalingrunnerset.yaml index 715e7ad4..73ec15d8 100644 --- a/charts/gha-runner-scale-set/templates/autoscalingrunnerset.yaml +++ b/charts/gha-runner-scale-set/templates/autoscalingrunnerset.yaml @@ -37,12 +37,12 @@ metadata: {{- end }} actions.github.com/cleanup-manager-role-binding: {{ include "gha-runner-scale-set.managerRoleBindingName" . }} actions.github.com/cleanup-manager-role-name: {{ include "gha-runner-scale-set.managerRoleName" . }} - {{- if and $containerMode (eq $containerMode.type "kubernetes") (not .Values.template.spec.serviceAccountName) }} + {{- if and (or (eq $containerMode.type "kubernetes") (eq $containerMode.type "kubernetes-novolume")) (not .Values.template.spec.serviceAccountName) }} actions.github.com/cleanup-kubernetes-mode-role-binding-name: {{ include "gha-runner-scale-set.kubeModeRoleBindingName" . }} actions.github.com/cleanup-kubernetes-mode-role-name: {{ include "gha-runner-scale-set.kubeModeRoleName" . }} actions.github.com/cleanup-kubernetes-mode-service-account-name: {{ include "gha-runner-scale-set.kubeModeServiceAccountName" . }} {{- end }} - {{- if and (ne $containerMode.type "kubernetes") (not .Values.template.spec.serviceAccountName) }} + {{- if and (ne $containerMode.type "kubernetes") (ne $containerMode.type "kubernetes-novolume") (not .Values.template.spec.serviceAccountName) }} actions.github.com/cleanup-no-permission-service-account-name: {{ include "gha-runner-scale-set.noPermissionServiceAccountName" . }} {{- end }} @@ -157,7 +157,7 @@ spec: restartPolicy: Never {{- end }} {{- $containerMode := .Values.containerMode }} - {{- if eq $containerMode.type "kubernetes" }} + {{- if or (eq $containerMode.type "kubernetes") (eq $containerMode.type "kubernetes-novolume") }} serviceAccountName: {{ default (include "gha-runner-scale-set.kubeModeServiceAccountName" .) .Values.template.spec.serviceAccountName }} {{- else }} serviceAccountName: {{ default (include "gha-runner-scale-set.noPermissionServiceAccountName" .) .Values.template.spec.serviceAccountName }} @@ -189,11 +189,15 @@ spec: - name: runner {{- include "gha-runner-scale-set.kubernetes-mode-runner-container" . | nindent 8 }} {{- include "gha-runner-scale-set.non-runner-containers" . | nindent 6 }} + {{- else if eq $containerMode.type "kubernetes-novolume" }} + - name: runner + {{- include "gha-runner-scale-set.kubernetes-novolume-mode-runner-container" . | nindent 8 }} + {{- include "gha-runner-scale-set.non-runner-containers" . | nindent 6 }} {{- else }} {{- include "gha-runner-scale-set.default-mode-runner-containers" . | nindent 6 }} {{- end }} {{- $tlsConfig := (default (dict) .Values.githubServerTLS) }} - {{- if or .Values.template.spec.volumes (eq $containerMode.type "dind") (eq $containerMode.type "kubernetes") $tlsConfig.runnerMountPath }} + {{- if or .Values.template.spec.volumes (eq $containerMode.type "dind") (eq $containerMode.type "kubernetes") (eq $containerMode.type "kubernetes-novolume") $tlsConfig.runnerMountPath }} volumes: {{- if $tlsConfig.runnerMountPath }} {{- include "gha-runner-scale-set.tls-volume" $tlsConfig | nindent 6 }} diff --git a/charts/gha-runner-scale-set/templates/kube_mode_role.yaml b/charts/gha-runner-scale-set/templates/kube_mode_role.yaml index 038307c1..b795d67d 100644 --- a/charts/gha-runner-scale-set/templates/kube_mode_role.yaml +++ b/charts/gha-runner-scale-set/templates/kube_mode_role.yaml @@ -1,6 +1,6 @@ {{- $containerMode := .Values.containerMode }} {{- $hasCustomResourceMeta := (and .Values.resourceMeta .Values.resourceMeta.kubernetesModeRole) }} -{{- if and (eq $containerMode.type "kubernetes") (not .Values.template.spec.serviceAccountName) }} +{{- if and (or (eq $containerMode.type "kubernetes") (eq $containerMode.type "kubernetes-novolume")) (not .Values.template.spec.serviceAccountName) }} # default permission for runner pod service account in kubernetes mode (container hook) apiVersion: rbac.authorization.k8s.io/v1 kind: Role @@ -38,9 +38,11 @@ rules: - apiGroups: [""] resources: ["pods/log"] verbs: ["get", "list", "watch",] +{{- if ne $containerMode.type "kubernetes-novolume" }} - apiGroups: ["batch"] resources: ["jobs"] verbs: ["get", "list", "create", "delete"] +{{- end }} - apiGroups: [""] resources: ["secrets"] verbs: ["get", "list", "create", "delete"] diff --git a/charts/gha-runner-scale-set/templates/kube_mode_role_binding.yaml b/charts/gha-runner-scale-set/templates/kube_mode_role_binding.yaml index a4416890..a8ea8d60 100644 --- a/charts/gha-runner-scale-set/templates/kube_mode_role_binding.yaml +++ b/charts/gha-runner-scale-set/templates/kube_mode_role_binding.yaml @@ -1,6 +1,6 @@ {{- $containerMode := .Values.containerMode }} {{- $hasCustomResourceMeta := (and .Values.resourceMeta .Values.resourceMeta.kubernetesModeRoleBinding) }} -{{- if and (eq $containerMode.type "kubernetes") (not .Values.template.spec.serviceAccountName) }} +{{- if and (or (eq $containerMode.type "kubernetes") (eq $containerMode.type "kubernetes-novolume")) (not .Values.template.spec.serviceAccountName) }} apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: diff --git a/charts/gha-runner-scale-set/templates/kube_mode_serviceaccount.yaml b/charts/gha-runner-scale-set/templates/kube_mode_serviceaccount.yaml index 5286ff50..2ae4f712 100644 --- a/charts/gha-runner-scale-set/templates/kube_mode_serviceaccount.yaml +++ b/charts/gha-runner-scale-set/templates/kube_mode_serviceaccount.yaml @@ -1,6 +1,6 @@ {{- $containerMode := .Values.containerMode }} {{- $hasCustomResourceMeta := (and .Values.resourceMeta .Values.resourceMeta.kubernetesModeServiceAccount) }} -{{- if and (eq $containerMode.type "kubernetes") (not .Values.template.spec.serviceAccountName) }} +{{- if and (or (eq $containerMode.type "kubernetes") (eq $containerMode.type "kubernetes-novolume")) (not .Values.template.spec.serviceAccountName) }} apiVersion: v1 kind: ServiceAccount metadata: diff --git a/charts/gha-runner-scale-set/tests/template_test.go b/charts/gha-runner-scale-set/tests/template_test.go index c08f9d3e..7a657475 100644 --- a/charts/gha-runner-scale-set/tests/template_test.go +++ b/charts/gha-runner-scale-set/tests/template_test.go @@ -204,7 +204,6 @@ func TestTemplateRenderedSetServiceAccountToNoPermission(t *testing.T) { func TestTemplateRenderedSetServiceAccountToKubeMode(t *testing.T) { t.Parallel() - // Path to the helm chart we will test helmChartPath, err := filepath.Abs("../../gha-runner-scale-set") require.NoError(t, err) @@ -270,6 +269,72 @@ func TestTemplateRenderedSetServiceAccountToKubeMode(t *testing.T) { assert.Equal(t, expectedServiceAccountName, ars.Annotations[actionsgithubcom.AnnotationKeyKubernetesModeServiceAccountName]) } +func TestTemplateRenderedSetServiceAccountToKubeNoVolumeMode(t *testing.T) { + t.Parallel() + // Path to the helm chart we will test + helmChartPath, err := filepath.Abs("../../gha-runner-scale-set") + require.NoError(t, err) + + releaseName := "test-runners" + namespaceName := "test-" + strings.ToLower(random.UniqueId()) + + options := &helm.Options{ + Logger: logger.Discard, + SetValues: map[string]string{ + "githubConfigUrl": "https://github.com/actions", + "githubConfigSecret.github_token": "gh_token12345", + "containerMode.type": "kubernetes-novolume", + "controllerServiceAccount.name": "arc", + "controllerServiceAccount.namespace": "arc-system", + }, + KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), + } + + output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/kube_mode_serviceaccount.yaml"}) + var serviceAccount corev1.ServiceAccount + helm.UnmarshalK8SYaml(t, output, &serviceAccount) + + assert.Equal(t, namespaceName, serviceAccount.Namespace) + assert.Equal(t, "test-runners-gha-rs-kube-mode", serviceAccount.Name) + assert.Equal(t, "actions.github.com/cleanup-protection", serviceAccount.Finalizers[0]) + + output = helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/kube_mode_role.yaml"}) + var role rbacv1.Role + helm.UnmarshalK8SYaml(t, output, &role) + + assert.Equal(t, namespaceName, role.Namespace) + assert.Equal(t, "test-runners-gha-rs-kube-mode", role.Name) + + assert.Equal(t, "actions.github.com/cleanup-protection", role.Finalizers[0]) + + assert.Len(t, role.Rules, 4, "kube mode role should have 4 rules") + assert.Equal(t, "pods", role.Rules[0].Resources[0]) + assert.Equal(t, "pods/exec", role.Rules[1].Resources[0]) + assert.Equal(t, "pods/log", role.Rules[2].Resources[0]) + assert.Equal(t, "secrets", role.Rules[3].Resources[0]) + + output = helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/kube_mode_role_binding.yaml"}) + var roleBinding rbacv1.RoleBinding + helm.UnmarshalK8SYaml(t, output, &roleBinding) + + assert.Equal(t, namespaceName, roleBinding.Namespace) + assert.Equal(t, "test-runners-gha-rs-kube-mode", roleBinding.Name) + assert.Len(t, roleBinding.Subjects, 1) + assert.Equal(t, "test-runners-gha-rs-kube-mode", roleBinding.Subjects[0].Name) + assert.Equal(t, namespaceName, roleBinding.Subjects[0].Namespace) + assert.Equal(t, "test-runners-gha-rs-kube-mode", roleBinding.RoleRef.Name) + assert.Equal(t, "Role", roleBinding.RoleRef.Kind) + assert.Equal(t, "actions.github.com/cleanup-protection", serviceAccount.Finalizers[0]) + + output = helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/autoscalingrunnerset.yaml"}) + var ars v1alpha1.AutoscalingRunnerSet + helm.UnmarshalK8SYaml(t, output, &ars) + + expectedServiceAccountName := "test-runners-gha-rs-kube-mode" + assert.Equal(t, expectedServiceAccountName, ars.Spec.Template.Spec.ServiceAccountName) + assert.Equal(t, expectedServiceAccountName, ars.Annotations[actionsgithubcom.AnnotationKeyKubernetesModeServiceAccountName]) +} + func TestTemplateRenderedUserProvideSetServiceAccount(t *testing.T) { t.Parallel() @@ -961,6 +1026,65 @@ func TestTemplateRenderedAutoScalingRunnerSet_EnableKubernetesMode(t *testing.T) assert.NotNil(t, ars.Spec.Template.Spec.Volumes[0].Ephemeral, "Template.Spec should have 1 ephemeral volume") } +func TestTemplateRenderedAutoScalingRunnerSet_EnableKubernetesModeNoVolume(t *testing.T) { + t.Parallel() + + // Path to the helm chart we will test + helmChartPath, err := filepath.Abs("../../gha-runner-scale-set") + require.NoError(t, err) + + releaseName := "test-runners" + namespaceName := "test-" + strings.ToLower(random.UniqueId()) + + options := &helm.Options{ + Logger: logger.Discard, + SetValues: map[string]string{ + "githubConfigUrl": "https://github.com/actions", + "githubConfigSecret.github_token": "gh_token12345", + "containerMode.type": "kubernetes-novolume", + "controllerServiceAccount.name": "arc", + "controllerServiceAccount.namespace": "arc-system", + }, + KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), + } + + output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/autoscalingrunnerset.yaml"}) + + var ars v1alpha1.AutoscalingRunnerSet + helm.UnmarshalK8SYaml(t, output, &ars) + + assert.Equal(t, namespaceName, ars.Namespace) + assert.Equal(t, "test-runners", ars.Name) + + assert.Equal(t, "test-runners", ars.Labels["app.kubernetes.io/name"]) + assert.Equal(t, "test-runners", ars.Labels["app.kubernetes.io/instance"]) + assert.Equal(t, "https://github.com/actions", ars.Spec.GitHubConfigUrl) + assert.Equal(t, "test-runners-gha-rs-github-secret", ars.Spec.GitHubConfigSecret) + + assert.Empty(t, ars.Spec.RunnerGroup, "RunnerGroup should be empty") + assert.Nil(t, ars.Spec.MinRunners, "MinRunners should be nil") + assert.Nil(t, ars.Spec.MaxRunners, "MaxRunners should be nil") + assert.Nil(t, ars.Spec.Proxy, "Proxy should be nil") + assert.Nil(t, ars.Spec.GitHubServerTLS, "GitHubServerTLS should be nil") + + assert.NotNil(t, ars.Spec.Template.Spec, "Template.Spec should not be nil") + + assert.Len(t, ars.Spec.Template.Spec.Containers, 1, "Template.Spec should have 1 container") + assert.Equal(t, "runner", ars.Spec.Template.Spec.Containers[0].Name) + assert.Equal(t, "ghcr.io/actions/actions-runner:latest", ars.Spec.Template.Spec.Containers[0].Image) + + require.Len(t, ars.Spec.Template.Spec.Containers[0].Env, 4, "The runner container should have 4 env vars") + assert.Equal(t, "ACTIONS_RUNNER_CONTAINER_HOOKS", ars.Spec.Template.Spec.Containers[0].Env[0].Name) + assert.Equal(t, "/home/runner/k8s-novolume/index.js", ars.Spec.Template.Spec.Containers[0].Env[0].Value) + assert.Equal(t, "ACTIONS_RUNNER_POD_NAME", ars.Spec.Template.Spec.Containers[0].Env[1].Name) + assert.Equal(t, "ACTIONS_RUNNER_REQUIRE_JOB_CONTAINER", ars.Spec.Template.Spec.Containers[0].Env[2].Name) + assert.Equal(t, "true", ars.Spec.Template.Spec.Containers[0].Env[2].Value) + assert.Equal(t, "ACTIONS_RUNNER_IMAGE", ars.Spec.Template.Spec.Containers[0].Env[3].Name) + assert.Equal(t, ars.Spec.Template.Spec.Containers[0].Image, ars.Spec.Template.Spec.Containers[0].Env[3].Value) + + assert.Len(t, ars.Spec.Template.Spec.Volumes, 0, "Template.Spec should have 0 volumes") +} + func TestTemplateRenderedAutoscalingRunnerSet_ListenerPodTemplate(t *testing.T) { t.Parallel() @@ -1140,7 +1264,7 @@ func TestTemplateRenderedWithTLS(t *testing.T) { } t.Run("providing githubServerTLS.runnerMountPath", func(t *testing.T) { - t.Run("mode: default", func(t *testing.T) { + t.Run("mode default", func(t *testing.T) { options := &helm.Options{ Logger: logger.Discard, SetValues: map[string]string{ @@ -1199,7 +1323,7 @@ func TestTemplateRenderedWithTLS(t *testing.T) { }) }) - t.Run("mode: dind", func(t *testing.T) { + t.Run("mode dind", func(t *testing.T) { options := &helm.Options{ Logger: logger.Discard, SetValues: map[string]string{ @@ -1259,7 +1383,7 @@ func TestTemplateRenderedWithTLS(t *testing.T) { }) }) - t.Run("mode: kubernetes", func(t *testing.T) { + t.Run("mode kubernetes", func(t *testing.T) { options := &helm.Options{ Logger: logger.Discard, SetValues: map[string]string{ @@ -1318,10 +1442,70 @@ func TestTemplateRenderedWithTLS(t *testing.T) { Value: "1", }) }) + + t.Run("mode kubernetes-novolume", func(t *testing.T) { + options := &helm.Options{ + Logger: logger.Discard, + SetValues: map[string]string{ + "githubConfigUrl": "https://github.com/actions", + "githubConfigSecret": "pre-defined-secrets", + "githubServerTLS.certificateFrom.configMapKeyRef.name": "certs-configmap", + "githubServerTLS.certificateFrom.configMapKeyRef.key": "cert.pem", + "githubServerTLS.runnerMountPath": "/runner/mount/path", + "containerMode.type": "kubernetes-novolume", + "controllerServiceAccount.name": "arc", + "controllerServiceAccount.namespace": "arc-system", + }, + KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), + } + + ars := render(t, options) + + require.NotNil(t, ars.Spec.GitHubServerTLS) + expected := &v1alpha1.TLSConfig{ + CertificateFrom: &v1alpha1.TLSCertificateSource{ + ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "certs-configmap", + }, + Key: "cert.pem", + }, + }, + } + assert.Equal(t, expected, ars.Spec.GitHubServerTLS) + + var volume *corev1.Volume + for _, v := range ars.Spec.Template.Spec.Volumes { + if v.Name == "github-server-tls-cert" { + volume = &v + break + } + } + require.NotNil(t, volume) + assert.Equal(t, "certs-configmap", volume.ConfigMap.Name) + assert.Equal(t, "cert.pem", volume.ConfigMap.Items[0].Key) + assert.Equal(t, "cert.pem", volume.ConfigMap.Items[0].Path) + + assert.Contains(t, ars.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{ + Name: "github-server-tls-cert", + MountPath: "/runner/mount/path/cert.pem", + SubPath: "cert.pem", + }) + + assert.Contains(t, ars.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{ + Name: "NODE_EXTRA_CA_CERTS", + Value: "/runner/mount/path/cert.pem", + }) + + assert.Contains(t, ars.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{ + Name: "RUNNER_UPDATE_CA_CERTS", + Value: "1", + }) + }) }) t.Run("without providing githubServerTLS.runnerMountPath", func(t *testing.T) { - t.Run("mode: default", func(t *testing.T) { + t.Run("mode default", func(t *testing.T) { options := &helm.Options{ Logger: logger.Discard, SetValues: map[string]string{ @@ -1376,7 +1560,7 @@ func TestTemplateRenderedWithTLS(t *testing.T) { }) }) - t.Run("mode: dind", func(t *testing.T) { + t.Run("mode dind", func(t *testing.T) { options := &helm.Options{ Logger: logger.Discard, SetValues: map[string]string{ @@ -1432,7 +1616,7 @@ func TestTemplateRenderedWithTLS(t *testing.T) { }) }) - t.Run("mode: kubernetes", func(t *testing.T) { + t.Run("mode kubernetes", func(t *testing.T) { options := &helm.Options{ Logger: logger.Discard, SetValues: map[string]string{ @@ -1487,6 +1671,62 @@ func TestTemplateRenderedWithTLS(t *testing.T) { Value: "1", }) }) + + t.Run("mode kubernetes-novolume", func(t *testing.T) { + options := &helm.Options{ + Logger: logger.Discard, + SetValues: map[string]string{ + "githubConfigUrl": "https://github.com/actions", + "githubConfigSecret": "pre-defined-secrets", + "githubServerTLS.certificateFrom.configMapKeyRef.name": "certs-configmap", + "githubServerTLS.certificateFrom.configMapKeyRef.key": "cert.pem", + "containerMode.type": "kubernetes-novolume", + "controllerServiceAccount.name": "arc", + "controllerServiceAccount.namespace": "arc-system", + }, + KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), + } + + ars := render(t, options) + + require.NotNil(t, ars.Spec.GitHubServerTLS) + expected := &v1alpha1.TLSConfig{ + CertificateFrom: &v1alpha1.TLSCertificateSource{ + ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "certs-configmap", + }, + Key: "cert.pem", + }, + }, + } + assert.Equal(t, expected, ars.Spec.GitHubServerTLS) + + var volume *corev1.Volume + for _, v := range ars.Spec.Template.Spec.Volumes { + if v.Name == "github-server-tls-cert" { + volume = &v + break + } + } + assert.Nil(t, volume) + + assert.NotContains(t, ars.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{ + Name: "github-server-tls-cert", + MountPath: "/runner/mount/path/cert.pem", + SubPath: "cert.pem", + }) + + assert.NotContains(t, ars.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{ + Name: "NODE_EXTRA_CA_CERTS", + Value: "/runner/mount/path/cert.pem", + }) + + assert.NotContains(t, ars.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{ + Name: "RUNNER_UPDATE_CA_CERTS", + Value: "1", + }) + }) }) } @@ -1951,40 +2191,44 @@ func TestTemplateRenderedAutoscalingRunnerSetAnnotation_GitHubSecret(t *testing. func TestTemplateRenderedAutoscalingRunnerSetAnnotation_KubernetesModeCleanup(t *testing.T) { t.Parallel() - // Path to the helm chart we will test - helmChartPath, err := filepath.Abs("../../gha-runner-scale-set") - require.NoError(t, err) + for _, mode := range []string{"kubernetes", "kubernetes-novolume"} { + t.Run("containerMode "+mode, func(t *testing.T) { + // Path to the helm chart we will test + helmChartPath, err := filepath.Abs("../../gha-runner-scale-set") + require.NoError(t, err) - releaseName := "test-runners" - namespaceName := "test-" + strings.ToLower(random.UniqueId()) + releaseName := "test-runners" + namespaceName := "test-" + strings.ToLower(random.UniqueId()) - options := &helm.Options{ - Logger: logger.Discard, - SetValues: map[string]string{ - "githubConfigUrl": "https://github.com/actions", - "githubConfigSecret.github_token": "gh_token12345", - "controllerServiceAccount.name": "arc", - "controllerServiceAccount.namespace": "arc-system", - "containerMode.type": "kubernetes", - }, - KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), - } + options := &helm.Options{ + Logger: logger.Discard, + SetValues: map[string]string{ + "githubConfigUrl": "https://github.com/actions", + "githubConfigSecret.github_token": "gh_token12345", + "controllerServiceAccount.name": "arc", + "controllerServiceAccount.namespace": "arc-system", + "containerMode.type": mode, + }, + KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), + } - output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/autoscalingrunnerset.yaml"}) - var autoscalingRunnerSet v1alpha1.AutoscalingRunnerSet - helm.UnmarshalK8SYaml(t, output, &autoscalingRunnerSet) + output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/autoscalingrunnerset.yaml"}) + var autoscalingRunnerSet v1alpha1.AutoscalingRunnerSet + helm.UnmarshalK8SYaml(t, output, &autoscalingRunnerSet) - annotationValues := map[string]string{ - actionsgithubcom.AnnotationKeyGitHubSecretName: "test-runners-gha-rs-github-secret", - actionsgithubcom.AnnotationKeyManagerRoleName: "test-runners-gha-rs-manager", - actionsgithubcom.AnnotationKeyManagerRoleBindingName: "test-runners-gha-rs-manager", - actionsgithubcom.AnnotationKeyKubernetesModeServiceAccountName: "test-runners-gha-rs-kube-mode", - actionsgithubcom.AnnotationKeyKubernetesModeRoleName: "test-runners-gha-rs-kube-mode", - actionsgithubcom.AnnotationKeyKubernetesModeRoleBindingName: "test-runners-gha-rs-kube-mode", - } + annotationValues := map[string]string{ + actionsgithubcom.AnnotationKeyGitHubSecretName: "test-runners-gha-rs-github-secret", + actionsgithubcom.AnnotationKeyManagerRoleName: "test-runners-gha-rs-manager", + actionsgithubcom.AnnotationKeyManagerRoleBindingName: "test-runners-gha-rs-manager", + actionsgithubcom.AnnotationKeyKubernetesModeServiceAccountName: "test-runners-gha-rs-kube-mode", + actionsgithubcom.AnnotationKeyKubernetesModeRoleName: "test-runners-gha-rs-kube-mode", + actionsgithubcom.AnnotationKeyKubernetesModeRoleBindingName: "test-runners-gha-rs-kube-mode", + } - for annotation, value := range annotationValues { - assert.Equal(t, value, autoscalingRunnerSet.Annotations[annotation], fmt.Sprintf("Annotation %q does not match the expected value", annotation)) + for annotation, value := range annotationValues { + assert.Equal(t, value, autoscalingRunnerSet.Annotations[annotation], fmt.Sprintf("Annotation %q does not match the expected value", annotation)) + } + }) } } @@ -2416,6 +2660,21 @@ func TestNamespaceOverride(t *testing.T) { KubectlOptions: k8s.NewKubectlOptions("", "", releaseNamespace), }, }, + "kube_novolume_mode_role": { + file: "kube_mode_role.yaml", + options: &helm.Options{ + Logger: logger.Discard, + SetValues: map[string]string{ + "namespaceOverride": namespaceOverride, + "containerMode.type": "kubernetes-novolume", + "controllerServiceAccount.name": "foo", + "controllerServiceAccount.namespace": "bar", + "githubConfigSecret.github_token": "gh_token12345", + "githubConfigUrl": "https://github.com", + }, + KubectlOptions: k8s.NewKubectlOptions("", "", releaseNamespace), + }, + }, "kube_mode_role_binding": { file: "kube_mode_role_binding.yaml", options: &helm.Options{ @@ -2431,6 +2690,21 @@ func TestNamespaceOverride(t *testing.T) { KubectlOptions: k8s.NewKubectlOptions("", "", releaseNamespace), }, }, + "kube_novolume_mode_role_binding": { + file: "kube_mode_role_binding.yaml", + options: &helm.Options{ + Logger: logger.Discard, + SetValues: map[string]string{ + "namespaceOverride": namespaceOverride, + "containerMode.type": "kubernetes-novolume", + "controllerServiceAccount.name": "foo", + "controllerServiceAccount.namespace": "bar", + "githubConfigSecret.github_token": "gh_token12345", + "githubConfigUrl": "https://github.com", + }, + KubectlOptions: k8s.NewKubectlOptions("", "", releaseNamespace), + }, + }, "kube_mode_serviceaccount": { file: "kube_mode_serviceaccount.yaml", options: &helm.Options{ @@ -2446,6 +2720,21 @@ func TestNamespaceOverride(t *testing.T) { KubectlOptions: k8s.NewKubectlOptions("", "", releaseNamespace), }, }, + "kube_novolume_mode_serviceaccount": { + file: "kube_mode_serviceaccount.yaml", + options: &helm.Options{ + Logger: logger.Discard, + SetValues: map[string]string{ + "namespaceOverride": namespaceOverride, + "containerMode.type": "kubernetes-novolume", + "controllerServiceAccount.name": "foo", + "controllerServiceAccount.namespace": "bar", + "githubConfigSecret.github_token": "gh_token12345", + "githubConfigUrl": "https://github.com", + }, + KubectlOptions: k8s.NewKubectlOptions("", "", releaseNamespace), + }, + }, } for name, tc := range tt { diff --git a/charts/gha-runner-scale-set/values.yaml b/charts/gha-runner-scale-set/values.yaml index 794f71df..5a0f4ca3 100644 --- a/charts/gha-runner-scale-set/values.yaml +++ b/charts/gha-runner-scale-set/values.yaml @@ -115,7 +115,7 @@ githubConfigSecret: ## If any customization is required for dind or kubernetes mode, containerMode should remain ## empty, and configuration should be applied to the template. # containerMode: -# type: "dind" ## type can be set to dind or kubernetes +# type: "dind" ## type can be set to "dind", "kubernetes", or "kubernetes-novolume" # ## the following is required when containerMode.type=kubernetes # kubernetesModeWorkVolumeClaim: # accessModes: ["ReadWriteOnce"] @@ -391,6 +391,25 @@ template: ## resources: ## requests: ## storage: 1Gi + ###################################################################################################### + ## with containerMode.type=kubernetes-novolume, we will populate the template.spec with following pod spec + ## template: + ## spec: + ## containers: + ## - name: runner + ## image: ghcr.io/actions/actions-runner:latest + ## command: ["/home/runner/run.sh"] + ## env: + ## - name: ACTIONS_RUNNER_CONTAINER_HOOKS + ## value: /home/runner/k8s-novolume/index.js + ## - name: ACTIONS_RUNNER_POD_NAME + ## valueFrom: + ## fieldRef: + ## fieldPath: metadata.name + ## - name: ACTIONS_RUNNER_IMAGE + ## value: ghcr.io/actions/actions-runner:latest # should match the runnerimage + ## - name: ACTIONS_RUNNER_REQUIRE_JOB_CONTAINER + ## value: "true" spec: containers: - name: runner