diff --git a/charts/gha-runner-scale-set/templates/_helpers.tpl b/charts/gha-runner-scale-set/templates/_helpers.tpl index 4ad4bfef9f..35d50588f9 100644 --- a/charts/gha-runner-scale-set/templates/_helpers.tpl +++ b/charts/gha-runner-scale-set/templates/_helpers.tpl @@ -96,6 +96,22 @@ annotations: {{- include "gha-runner-scale-set.fullname" . | replace "_" "-" }}-kube-mode {{- end }} +{{/* +Default image/command for the "runner" container, mirroring values.yaml's +template.spec.containers default. Helm replaces list values wholesale rather +than merging list items, so a user-supplied template.spec.containers entry +for "runner" (e.g. to set only resources) otherwise loses image/command +entirely -- these are used as a fallback in each mode's runner-container +render logic when the user's override doesn't set that key. +*/}} +{{- define "gha-runner-scale-set.defaultRunnerImage" -}} +ghcr.io/actions/actions-runner:latest +{{- end -}} + +{{- define "gha-runner-scale-set.defaultRunnerCommand" -}} +["/home/runner/run.sh"] +{{- end -}} + {{- define "gha-runner-scale-set.dind-init-container" -}} {{- range $i, $val := .Values.template.spec.containers }} {{- if eq $val.name "runner" }} @@ -220,6 +236,13 @@ volumeMounts: {{ $key }}: {{ $val | toYaml | nindent 2 }} {{- end }} {{- end }} + {{- $usingDefaultRunnerImage := not (hasKey $container "image") }} + {{- if $usingDefaultRunnerImage }} +image: {{ include "gha-runner-scale-set.defaultRunnerImage" . }} + {{- end }} + {{- if and $usingDefaultRunnerImage (not (hasKey $container "command")) }} +command: {{ include "gha-runner-scale-set.defaultRunnerCommand" . }} + {{- end }} {{- $setDockerHost := 1 }} {{- $setRunnerWaitDocker := 1 }} {{- $setNodeExtraCaCerts := 0 }} @@ -309,6 +332,13 @@ volumeMounts: {{ $key }}: {{ $val | toYaml | nindent 2 }} {{- end }} {{- end }} + {{- $usingDefaultRunnerImage := not (hasKey $container "image") }} + {{- if $usingDefaultRunnerImage }} +image: {{ include "gha-runner-scale-set.defaultRunnerImage" . }} + {{- end }} + {{- if and $usingDefaultRunnerImage (not (hasKey $container "command")) }} +command: {{ include "gha-runner-scale-set.defaultRunnerCommand" . }} + {{- end }} {{- $setContainerHooks := 1 }} {{- $setPodName := 1 }} {{- $setRequireJobContainer := 1 }} @@ -404,6 +434,14 @@ volumeMounts: {{ $key }}: {{ $val | toYaml | nindent 2 }} {{- end }} {{- end }} + {{- $usingDefaultRunnerImage := not $setRunnerImage }} + {{- if $usingDefaultRunnerImage }} + {{- $setRunnerImage = include "gha-runner-scale-set.defaultRunnerImage" . }} +image: {{ $setRunnerImage }} + {{- end }} + {{- if and $usingDefaultRunnerImage (not (hasKey $container "command")) }} +command: {{ include "gha-runner-scale-set.defaultRunnerCommand" . }} + {{- end }} {{- $setContainerHooks := 1 }} {{- $setPodName := 1 }} {{- $setRequireJobContainer := 1 }} @@ -502,6 +540,13 @@ volumeMounts: [] {{ $key }}: {{ $val | toYaml | nindent 4 }} {{- end }} {{- end }} + {{- $usingDefaultRunnerImage := not (hasKey $container "image") }} + {{- if $usingDefaultRunnerImage }} + image: {{ include "gha-runner-scale-set.defaultRunnerImage" . }} + {{- end }} + {{- if and $usingDefaultRunnerImage (not (hasKey $container "command")) }} + command: {{ include "gha-runner-scale-set.defaultRunnerCommand" . }} + {{- end }} {{- $setNodeExtraCaCerts := 0 }} {{- $setRunnerUpdateCaCerts := 0 }} {{- if $tlsConfig.runnerMountPath }} diff --git a/charts/gha-runner-scale-set/tests/template_test.go b/charts/gha-runner-scale-set/tests/template_test.go index e9aa4c9bcb..5a6d766e11 100644 --- a/charts/gha-runner-scale-set/tests/template_test.go +++ b/charts/gha-runner-scale-set/tests/template_test.go @@ -2099,6 +2099,101 @@ func TestTemplateRenderedAutoScalingRunnerSet_ExtraContainers(t *testing.T) { assert.Equal(t, "192.0.2.1", ars.Spec.Template.Spec.DNSConfig.Nameservers[0], "DNS Nameserver should be set") } +// Overriding template.spec.containers to set only e.g. resources on the +// "runner" container must not drop the chart's default image/command -- +// Helm replaces list values wholesale rather than merging list items, so +// without a template-side fallback the resulting container has no image, +// and the pod fails admission entirely. +func TestTemplateRenderedAutoScalingRunnerSet_RunnerResourcesOnlyKeepsDefaultImageAndCommand(t *testing.T) { + t.Parallel() + + helmChartPath, err := filepath.Abs("../../gha-runner-scale-set") + require.NoError(t, err) + + testCases := []struct { + name string + valuesFile string + containerIndex int + }{ + {name: "default mode", valuesFile: "values_runner_resources_only.yaml", containerIndex: 0}, + {name: "dind mode", valuesFile: "values_runner_resources_only_dind.yaml", containerIndex: 0}, + {name: "kubernetes mode", valuesFile: "values_runner_resources_only_kubernetes.yaml", containerIndex: 0}, + {name: "kubernetes-novolume mode", valuesFile: "values_runner_resources_only_kubernetes_novolume.yaml", containerIndex: 0}, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + testValuesPath, err := filepath.Abs("../tests/" + tc.valuesFile) + require.NoError(t, err) + + releaseName := "test-runners" + namespaceName := "test-" + strings.ToLower(random.UniqueID()) + + options := &helm.Options{ + Logger: logger.Discard, + SetValues: map[string]string{ + "controllerServiceAccount.name": "arc", + "controllerServiceAccount.namespace": "arc-system", + }, + ValuesFiles: []string{testValuesPath}, + KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), + } + + output := helm.RenderTemplateContext(t, t.Context(), options, helmChartPath, releaseName, []string{"templates/autoscalingrunnerset.yaml"}, "--debug") + + var ars v1alpha1.AutoscalingRunnerSet + helm.UnmarshalK8SYaml(t, output, &ars) + + runner := ars.Spec.Template.Spec.Containers[tc.containerIndex] + assert.Equal(t, "runner", runner.Name) + assert.Equal(t, "ghcr.io/actions/actions-runner:latest", runner.Image, "runner container should keep the chart's default image") + assert.Equal(t, []string{"/home/runner/run.sh"}, runner.Command, "runner container should keep the chart's default command") + assert.Equal(t, "50m", runner.Resources.Requests.Cpu().String(), "user-supplied resources should still be applied") + assert.Equal(t, "50Mi", runner.Resources.Requests.Memory().String(), "user-supplied resources should still be applied") + assert.Equal(t, "2Gi", runner.Resources.Limits.Memory().String(), "user-supplied resources should still be applied") + }) + } +} + +// A user-supplied custom image should never be paired with a forced default +// command -- if they haven't set command either, leave it unset rather than +// assuming their custom image expects the stock runner's entrypoint. +func TestTemplateRenderedAutoScalingRunnerSet_CustomImageWithoutCommandIsNotForced(t *testing.T) { + t.Parallel() + + helmChartPath, err := filepath.Abs("../../gha-runner-scale-set") + require.NoError(t, err) + + testValuesPath, err := filepath.Abs("../tests/values_runner_custom_image_no_command.yaml") + require.NoError(t, err) + + releaseName := "test-runners" + namespaceName := "test-" + strings.ToLower(random.UniqueID()) + + options := &helm.Options{ + Logger: logger.Discard, + SetValues: map[string]string{ + "controllerServiceAccount.name": "arc", + "controllerServiceAccount.namespace": "arc-system", + }, + ValuesFiles: []string{testValuesPath}, + KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), + } + + output := helm.RenderTemplateContext(t, t.Context(), options, helmChartPath, releaseName, []string{"templates/autoscalingrunnerset.yaml"}, "--debug") + + var ars v1alpha1.AutoscalingRunnerSet + helm.UnmarshalK8SYaml(t, output, &ars) + + runner := ars.Spec.Template.Spec.Containers[0] + assert.Equal(t, "runner", runner.Name) + assert.Equal(t, "my-custom-runner-image:latest", runner.Image, "user-supplied custom image should be respected") + assert.Empty(t, runner.Command, "command should not be forced onto a custom image") +} + func TestTemplateRenderedAutoScalingRunnerSet_RestartPolicy(t *testing.T) { t.Parallel() diff --git a/charts/gha-runner-scale-set/tests/values_runner_custom_image_no_command.yaml b/charts/gha-runner-scale-set/tests/values_runner_custom_image_no_command.yaml new file mode 100644 index 0000000000..3d71431cfa --- /dev/null +++ b/charts/gha-runner-scale-set/tests/values_runner_custom_image_no_command.yaml @@ -0,0 +1,14 @@ +githubConfigUrl: https://github.com/actions/actions-runner-controller +githubConfigSecret: + github_token: test +template: + spec: + containers: + - name: runner + image: my-custom-runner-image:latest + resources: + limits: + memory: "2Gi" + requests: + cpu: "50m" + memory: "50Mi" diff --git a/charts/gha-runner-scale-set/tests/values_runner_resources_only.yaml b/charts/gha-runner-scale-set/tests/values_runner_resources_only.yaml new file mode 100644 index 0000000000..6d0e2fb31c --- /dev/null +++ b/charts/gha-runner-scale-set/tests/values_runner_resources_only.yaml @@ -0,0 +1,13 @@ +githubConfigUrl: https://github.com/actions/actions-runner-controller +githubConfigSecret: + github_token: test +template: + spec: + containers: + - name: runner + resources: + limits: + memory: "2Gi" + requests: + cpu: "50m" + memory: "50Mi" diff --git a/charts/gha-runner-scale-set/tests/values_runner_resources_only_dind.yaml b/charts/gha-runner-scale-set/tests/values_runner_resources_only_dind.yaml new file mode 100644 index 0000000000..6e27fefcd0 --- /dev/null +++ b/charts/gha-runner-scale-set/tests/values_runner_resources_only_dind.yaml @@ -0,0 +1,15 @@ +githubConfigUrl: https://github.com/actions/actions-runner-controller +githubConfigSecret: + github_token: test +containerMode: + type: dind +template: + spec: + containers: + - name: runner + resources: + limits: + memory: "2Gi" + requests: + cpu: "50m" + memory: "50Mi" diff --git a/charts/gha-runner-scale-set/tests/values_runner_resources_only_kubernetes.yaml b/charts/gha-runner-scale-set/tests/values_runner_resources_only_kubernetes.yaml new file mode 100644 index 0000000000..635210c7f7 --- /dev/null +++ b/charts/gha-runner-scale-set/tests/values_runner_resources_only_kubernetes.yaml @@ -0,0 +1,15 @@ +githubConfigUrl: https://github.com/actions/actions-runner-controller +githubConfigSecret: + github_token: test +containerMode: + type: kubernetes +template: + spec: + containers: + - name: runner + resources: + limits: + memory: "2Gi" + requests: + cpu: "50m" + memory: "50Mi" diff --git a/charts/gha-runner-scale-set/tests/values_runner_resources_only_kubernetes_novolume.yaml b/charts/gha-runner-scale-set/tests/values_runner_resources_only_kubernetes_novolume.yaml new file mode 100644 index 0000000000..c8b937b61d --- /dev/null +++ b/charts/gha-runner-scale-set/tests/values_runner_resources_only_kubernetes_novolume.yaml @@ -0,0 +1,15 @@ +githubConfigUrl: https://github.com/actions/actions-runner-controller +githubConfigSecret: + github_token: test +containerMode: + type: kubernetes-novolume +template: + spec: + containers: + - name: runner + resources: + limits: + memory: "2Gi" + requests: + cpu: "50m" + memory: "50Mi"