Render label and annotation values as strings (#4637)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Nikola Jokic
2026-09-10 10:22:05 +02:00
committed by GitHub
co-authored by Copilot App Copilot Autofix powered by AI
parent 5e540b6c71
commit b0e69a37bd
13 changed files with 190 additions and 40 deletions
@@ -32,11 +32,11 @@ Render a ResourceMeta block for AutoscalingRunnerSet spec fields.
{{- define "autoscaling-runner-set.spec-resource-metadata" -}}
{{- with .labels }}
labels:
{{- toYaml . | nindent 2 }}
{{- include "string-map" . | nindent 2 }}
{{- end }}
{{- with .annotations }}
annotations:
{{- toYaml . | nindent 2 }}
{{- include "string-map" . | nindent 2 }}
{{- end }}
{{- end }}
@@ -1,3 +1,32 @@
{{/*
Render a single label or annotation value as a string.
Values from a values file arrive as float64, so "%v" would turn large integers into
scientific notation (12345678901234 -> 1.2345678901234e+13) and silently write a value the
user never asked for. Integral floats are therefore formatted without an exponent.
*/}}
{{- define "metadata-value" -}}
{{- if eq . nil -}}
{{- "" -}}
{{- else if and (kindIs "float64" .) (eq . (floor .)) -}}
{{- printf "%.0f" . -}}
{{- else -}}
{{- printf "%v" . -}}
{{- end -}}
{{- end }}
{{/*
Render a labels or annotations map with all values coerced to strings.
Kubernetes only accepts string values, so scalars such as `true` or `1` must not be
rendered as YAML booleans or numbers.
*/}}
{{- define "string-map" -}}
{{- $out := dict -}}
{{- range $k, $v := . -}}
{{- $_ := set $out $k (include "metadata-value" $v) -}}
{{- end -}}
{{- toYaml $out -}}
{{- end }}
{{/*
Create the labels for the GitHub auth secret.
*/}}
@@ -53,14 +82,16 @@ Reserved annotations are excluded from both levels.
{{/*
Takes a map of user labels and removes the ones with "actions.github.com/" prefix
Takes a map of user labels and removes the ones with "actions.github.com/" prefix.
Values are rendered as strings so that scalars such as `true` or `1.0` do not become
non-string YAML values, which Kubernetes rejects for labels and annotations.
*/}}
{{- define "apply-non-reserved-gha-labels-and-annotations" -}}
{{- $userLabels := . -}}
{{- $processed := dict -}}
{{- range $key, $value := $userLabels -}}
{{- if not (hasPrefix "actions.github.com/" $key) -}}
{{- $_ := set $processed $key $value -}}
{{- $_ := set $processed $key (include "metadata-value" $value) -}}
{{- end -}}
{{- end -}}
{{- if not (empty $processed) -}}
@@ -54,17 +54,46 @@ app.kubernetes.io/name: {{ include "gha-runner-scale-set.scale-set-name" . }}
app.kubernetes.io/instance: {{ include "gha-runner-scale-set.scale-set-name" . }}
{{- end }}
{{/*
Render a single label or annotation value as a string.
Values from a values file arrive as float64, so "%v" would turn large integers into
scientific notation (12345678901234 -> 1.2345678901234e+13) and silently write a value the
user never asked for. Integral floats are therefore formatted without an exponent.
*/}}
{{- define "gha-runner-scale-set.metadataValue" -}}
{{- if eq . nil -}}
{{- "" -}}
{{- else if and (kindIs "float64" .) (eq . (floor .)) -}}
{{- printf "%.0f" . -}}
{{- else -}}
{{- printf "%v" . -}}
{{- end -}}
{{- end }}
{{/*
Render a labels or annotations map with all values coerced to strings.
Kubernetes only accepts string values, so scalars such as `true` or `1` must not be
rendered as YAML booleans or numbers.
*/}}
{{- define "gha-runner-scale-set.stringMap" -}}
{{- $out := dict -}}
{{- range $k, $v := . -}}
{{- $_ := set $out $k (include "gha-runner-scale-set.metadataValue" $v) -}}
{{- end -}}
{{- toYaml $out -}}
{{- end }}
{{/*
Render a ResourceMeta block for AutoscalingRunnerSet spec fields.
*/}}
{{- define "gha-runner-scale-set.resourceMetaSpec" -}}
{{- with .labels }}
labels:
{{- toYaml . | nindent 2 }}
{{- include "gha-runner-scale-set.stringMap" . | nindent 2 }}
{{- end }}
{{- with .annotations }}
annotations:
{{- toYaml . | nindent 2 }}
{{- include "gha-runner-scale-set.stringMap" . | nindent 2 }}
{{- end }}
{{- end }}
@@ -19,7 +19,7 @@ metadata:
{{- with .Values.labels }}
{{- range $k, $v := . }}
{{- if not (or (hasKey $reserved $k) (hasPrefix "actions.github.com/" $k)) }}
{{ $k }}: {{ $v | quote }}
{{ $k }}: {{ include "gha-runner-scale-set.metadataValue" $v | quote }}
{{- end }}
{{- end }}
{{- end }}
@@ -27,7 +27,7 @@ metadata:
{{- with .Values.resourceMeta.autoscalingRunnerSet.labels }}
{{- range $k, $v := . }}
{{- if not (or (hasKey $reserved $k) (hasPrefix "actions.github.com/" $k)) }}
{{ $k }}: {{ $v | quote }}
{{ $k }}: {{ include "gha-runner-scale-set.metadataValue" $v | quote }}
{{- end }}
{{- end }}
{{- end }}
@@ -38,7 +38,7 @@ metadata:
{{- with .Values.annotations }}
{{- range $k, $v := . }}
{{- if not (or (hasPrefix "actions.github.com/cleanup-" $k) (eq $k "actions.github.com/values-hash")) }}
{{ $k }}: {{ $v | quote }}
{{ $k }}: {{ include "gha-runner-scale-set.metadataValue" $v | quote }}
{{- end }}
{{- end }}
{{- end }}
@@ -46,7 +46,7 @@ metadata:
{{- with .Values.resourceMeta.autoscalingRunnerSet.annotations }}
{{- range $k, $v := . }}
{{- if not (or (hasPrefix "actions.github.com/cleanup-" $k) (eq $k "actions.github.com/values-hash")) }}
{{ $k }}: {{ $v | quote }}
{{ $k }}: {{ include "gha-runner-scale-set.metadataValue" $v | quote }}
{{- end }}
{{- end }}
{{- end }}
@@ -218,11 +218,11 @@ spec:
metadata:
{{- with .labels }}
labels:
{{- toYaml . | nindent 8 }}
{{- include "gha-runner-scale-set.stringMap" . | nindent 8 }}
{{- end }}
{{- with .annotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- include "gha-runner-scale-set.stringMap" . | nindent 8 }}
{{- end }}
{{- end }}
spec:
@@ -12,23 +12,23 @@ metadata:
{{- with .Values.labels }}
{{- range $k, $v := . }}
{{- if not (or (hasKey $reserved $k) (hasPrefix "actions.github.com/" $k)) }}
{{ $k }}: {{ $v | quote }}
{{ $k }}: {{ include "gha-runner-scale-set.metadataValue" $v | quote }}
{{- end }}
{{- end }}
{{- end }}
{{- if $hasCustomResourceMeta }}
{{- with .Values.resourceMeta.githubConfigSecret.labels }}
{{- toYaml . | nindent 4 }}
{{- include "gha-runner-scale-set.stringMap" . | nindent 4 }}
{{- end }}
{{- end }}
{{- include "gha-runner-scale-set.labels" . | nindent 4 }}
annotations:
{{- with .Values.annotations }}
{{- toYaml . | nindent 4 }}
{{- include "gha-runner-scale-set.stringMap" . | nindent 4 }}
{{- end }}
{{- if $hasCustomResourceMeta }}
{{- with .Values.resourceMeta.githubConfigSecret.annotations }}
{{- toYaml . | nindent 4 }}
{{- include "gha-runner-scale-set.stringMap" . | nindent 4 }}
{{- end }}
{{- end }}
finalizers:
@@ -14,23 +14,23 @@ metadata:
{{- with .Values.labels }}
{{- range $k, $v := . }}
{{- if not (or (hasKey $reserved $k) (hasPrefix "actions.github.com/" $k)) }}
{{ $k }}: {{ $v | quote }}
{{ $k }}: {{ include "gha-runner-scale-set.metadataValue" $v | quote }}
{{- end }}
{{- end }}
{{- end }}
{{- if $hasCustomResourceMeta }}
{{- with .Values.resourceMeta.kubernetesModeRole.labels }}
{{- toYaml . | nindent 4 }}
{{- include "gha-runner-scale-set.stringMap" . | nindent 4 }}
{{- end }}
{{- end }}
{{- include "gha-runner-scale-set.labels" . | nindent 4 }}
annotations:
{{- with .Values.annotations }}
{{- toYaml . | nindent 4 }}
{{- include "gha-runner-scale-set.stringMap" . | nindent 4 }}
{{- end }}
{{- if $hasCustomResourceMeta }}
{{- with .Values.resourceMeta.kubernetesModeRole.annotations }}
{{- toYaml . | nindent 4 }}
{{- include "gha-runner-scale-set.stringMap" . | nindent 4 }}
{{- end }}
{{- end }}
finalizers:
@@ -13,24 +13,24 @@ metadata:
{{- with .Values.labels }}
{{- range $k, $v := . }}
{{- if not (or (hasKey $reserved $k) (hasPrefix "actions.github.com/" $k)) }}
{{ $k }}: {{ $v | quote }}
{{ $k }}: {{ include "gha-runner-scale-set.metadataValue" $v | quote }}
{{- end }}
{{- end }}
{{- end }}
{{- if $hasCustomResourceMeta }}
{{- with .Values.resourceMeta.kubernetesModeRoleBinding.labels }}
{{- toYaml . | nindent 4 }}
{{- include "gha-runner-scale-set.stringMap" . | nindent 4 }}
{{- end }}
{{- end }}
{{- include "gha-runner-scale-set.labels" . | nindent 4 }}
annotations:
{{- with .Values.annotations }}
{{- toYaml . | nindent 4 }}
{{- include "gha-runner-scale-set.stringMap" . | nindent 4 }}
{{- end }}
{{- if $hasCustomResourceMeta }}
{{- with .Values.resourceMeta.kubernetesModeRoleBinding.annotations }}
{{- toYaml . | nindent 4 }}
{{- include "gha-runner-scale-set.stringMap" . | nindent 4 }}
{{- end }}
{{- end }}
finalizers:
@@ -9,11 +9,11 @@ metadata:
{{- if or .Values.annotations $hasCustomResourceMeta }}
annotations:
{{- with .Values.annotations }}
{{- toYaml . | nindent 4 }}
{{- include "gha-runner-scale-set.stringMap" . | nindent 4 }}
{{- end }}
{{- if $hasCustomResourceMeta }}
{{- with .Values.resourceMeta.kubernetesModeServiceAccount.annotations }}
{{- toYaml . | nindent 4 }}
{{- include "gha-runner-scale-set.stringMap" . | nindent 4 }}
{{- end }}
{{- end }}
{{- end }}
@@ -24,13 +24,13 @@ metadata:
{{- with .Values.labels }}
{{- range $k, $v := . }}
{{- if not (or (hasKey $reserved $k) (hasPrefix "actions.github.com/" $k)) }}
{{ $k }}: {{ $v | quote }}
{{ $k }}: {{ include "gha-runner-scale-set.metadataValue" $v | quote }}
{{- end }}
{{- end }}
{{- end }}
{{- if $hasCustomResourceMeta }}
{{- with .Values.resourceMeta.kubernetesModeServiceAccount.labels }}
{{- toYaml . | nindent 4 }}
{{- include "gha-runner-scale-set.stringMap" . | nindent 4 }}
{{- end }}
{{- end }}
{{- include "gha-runner-scale-set.labels" . | nindent 4 }}
@@ -11,24 +11,24 @@ metadata:
{{- with .Values.labels }}
{{- range $k, $v := . }}
{{- if not (or (hasKey $reserved $k) (hasPrefix "actions.github.com/" $k)) }}
{{ $k }}: {{ $v | quote }}
{{ $k }}: {{ include "gha-runner-scale-set.metadataValue" $v | quote }}
{{- end }}
{{- end }}
{{- end }}
{{- if $hasCustomResourceMeta }}
{{- with .Values.resourceMeta.managerRole.labels }}
{{- toYaml . | nindent 4 }}
{{- include "gha-runner-scale-set.stringMap" . | nindent 4 }}
{{- end }}
{{- end }}
{{- include "gha-runner-scale-set.labels" . | nindent 4 }}
app.kubernetes.io/component: manager-role
annotations:
{{- with .Values.annotations }}
{{- toYaml . | nindent 4 }}
{{- include "gha-runner-scale-set.stringMap" . | nindent 4 }}
{{- end }}
{{- if $hasCustomResourceMeta }}
{{- with .Values.resourceMeta.managerRole.annotations }}
{{- toYaml . | nindent 4 }}
{{- include "gha-runner-scale-set.stringMap" . | nindent 4 }}
{{- end }}
{{- end }}
finalizers:
@@ -11,24 +11,24 @@ metadata:
{{- with .Values.labels }}
{{- range $k, $v := . }}
{{- if not (or (hasKey $reserved $k) (hasPrefix "actions.github.com/" $k)) }}
{{ $k }}: {{ $v | quote }}
{{ $k }}: {{ include "gha-runner-scale-set.metadataValue" $v | quote }}
{{- end }}
{{- end }}
{{- end }}
{{- if $hasCustomResourceMeta }}
{{- with .Values.resourceMeta.managerRoleBinding.labels }}
{{- toYaml . | nindent 4 }}
{{- include "gha-runner-scale-set.stringMap" . | nindent 4 }}
{{- end }}
{{- end }}
{{- include "gha-runner-scale-set.labels" . | nindent 4 }}
app.kubernetes.io/component: manager-role-binding
annotations:
{{- with .Values.annotations }}
{{- toYaml . | nindent 4 }}
{{- include "gha-runner-scale-set.stringMap" . | nindent 4 }}
{{- end }}
{{- if $hasCustomResourceMeta }}
{{- with .Values.resourceMeta.managerRoleBinding.annotations }}
{{- toYaml . | nindent 4 }}
{{- include "gha-runner-scale-set.stringMap" . | nindent 4 }}
{{- end }}
{{- end }}
finalizers:
@@ -13,23 +13,23 @@ metadata:
{{- with .Values.labels }}
{{- range $k, $v := . }}
{{- if not (or (hasKey $reserved $k) (hasPrefix "actions.github.com/" $k)) }}
{{ $k }}: {{ $v | quote }}
{{ $k }}: {{ include "gha-runner-scale-set.metadataValue" $v | quote }}
{{- end }}
{{- end }}
{{- end }}
{{- if $hasCustomResourceMeta }}
{{- with .Values.resourceMeta.noPermissionServiceAccount.labels }}
{{- toYaml . | nindent 4 }}
{{- include "gha-runner-scale-set.stringMap" . | nindent 4 }}
{{- end }}
{{- end }}
{{- include "gha-runner-scale-set.labels" . | nindent 4 }}
annotations:
{{- with .Values.annotations }}
{{- toYaml . | nindent 4 }}
{{- include "gha-runner-scale-set.stringMap" . | nindent 4 }}
{{- end }}
{{- if $hasCustomResourceMeta }}
{{- with .Values.resourceMeta.noPermissionServiceAccount.annotations }}
{{- toYaml . | nindent 4 }}
{{- include "gha-runner-scale-set.stringMap" . | nindent 4 }}
{{- end }}
{{- end }}
finalizers:
@@ -3152,3 +3152,54 @@ func TestAutoscalingRunnerSetCustomAnnotationsAndLabelsApplied(t *testing.T) {
assert.NotEqual(t, "not-propagated", autoscalingRunnerSet.Annotations["actions.github.com/cleanup-manager-role-name"])
assert.NotEqual(t, "not-propagated", autoscalingRunnerSet.Labels["app.kubernetes.io/component"])
}
func TestTemplateRenderedAutoScalingRunnerSet_ScalarMetadataValuesAreRenderedAsStrings(t *testing.T) {
t.Parallel()
helmChartPath, err := filepath.Abs("../../gha-runner-scale-set")
require.NoError(t, err)
testValuesPath, err := filepath.Abs("../tests/values_scalar_metadata.yaml")
require.NoError(t, err)
releaseName := "test-runners"
namespaceName := "test-" + strings.ToLower(random.UniqueID())
options := &helm.Options{
Logger: logger.Discard,
ValuesFiles: []string{testValuesPath},
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
}
// UnmarshalK8SYaml fails outright if a value decodes as a bool or number rather than a
// string, so a successful decode is itself part of the assertion.
output := helm.RenderTemplateContext(t, t.Context(), options, helmChartPath, releaseName, []string{"templates/autoscalingrunnerset.yaml"})
var autoscalingRunnerSet v1alpha1.AutoscalingRunnerSet
helm.UnmarshalK8SYaml(t, output, &autoscalingRunnerSet)
assert.Equal(t, "true", autoscalingRunnerSet.Labels["chart-bool"])
assert.Equal(t, "1", autoscalingRunnerSet.Labels["chart-int"])
assert.Equal(t, "false", autoscalingRunnerSet.Annotations["chart-bool-annotation"])
assert.Equal(t, "1.5", autoscalingRunnerSet.Annotations["chart-float-annotation"])
assert.Equal(t, "12345678901234", autoscalingRunnerSet.Annotations["chart-big-int-annotation"])
assert.Equal(t, "true", autoscalingRunnerSet.Spec.Template.Labels["pod-bool"])
assert.Equal(t, "42", autoscalingRunnerSet.Spec.Template.Labels["pod-int"])
assert.Equal(t, "true", autoscalingRunnerSet.Spec.Template.Annotations["pod-bool-annotation"])
assert.Equal(t, "7", autoscalingRunnerSet.Spec.Template.Annotations["pod-int-annotation"])
require.NotNil(t, autoscalingRunnerSet.Spec.EphemeralRunnerMetadata)
assert.Equal(t, "false", autoscalingRunnerSet.Spec.EphemeralRunnerMetadata.Labels["runner-bool"])
assert.Equal(t, "3", autoscalingRunnerSet.Spec.EphemeralRunnerMetadata.Labels["runner-int"])
assert.Equal(t, "9", autoscalingRunnerSet.Spec.EphemeralRunnerMetadata.Annotations["runner-int-annotation"])
output = helm.RenderTemplateContext(t, t.Context(), options, helmChartPath, releaseName, []string{"templates/githubsecret.yaml"})
var githubSecret corev1.Secret
helm.UnmarshalK8SYaml(t, output, &githubSecret)
assert.Equal(t, "5", githubSecret.Labels["secret-int"])
assert.Equal(t, "true", githubSecret.Labels["chart-bool"])
assert.Equal(t, "1.5", githubSecret.Annotations["chart-float-annotation"])
}
@@ -0,0 +1,39 @@
githubConfigUrl: https://github.com/actions
githubConfigSecret:
github_token: gh_token12345
controllerServiceAccount:
name: arc
namespace: arc-system
# Every value below is an unquoted YAML scalar, so it parses as a bool, int or float
# rather than a string. Kubernetes only accepts string label and annotation values, so
# the chart has to coerce these when rendering.
labels:
chart-bool: true
chart-int: 1
annotations:
chart-bool-annotation: false
chart-float-annotation: 1.5
# Large integers must not be rendered in scientific notation. Values loaded from a file
# arrive as float64, so a naive "%v" would produce "1.2345678901234e+13".
chart-big-int-annotation: 12345678901234
template:
metadata:
labels:
pod-bool: true
pod-int: 42
annotations:
pod-bool-annotation: true
pod-int-annotation: 7
resourceMeta:
ephemeralRunner:
labels:
runner-bool: false
runner-int: 3
annotations:
runner-int-annotation: 9
githubConfigSecret:
labels:
secret-int: 5