Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions charts/gha-runner-scale-set/tests/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2797,3 +2797,40 @@ 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 TestTemplateRenderedRunnerSetWithTemplateMetadata(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",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
"template.metadata.labels.my-label": "my-value",
},
// Use SetStrValues (--set-string) to ensure boolean-like values are treated as strings
SetStrValues: map[string]string{
"template.metadata.annotations.karpenter\\.sh/do-not-disrupt": "true",
},
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, "true", ars.Spec.Template.ObjectMeta.Annotations["karpenter.sh/do-not-disrupt"],
"karpenter do-not-disrupt annotation should be set on runner pod template")
assert.Equal(t, "my-value", ars.Spec.Template.ObjectMeta.Labels["my-label"],
"custom label should be set on runner pod template")
}
15 changes: 15 additions & 0 deletions charts/gha-runner-scale-set/tests/values_template_metadata.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
githubConfigUrl: "https://github.com/actions"
githubConfigSecret:
github_token: "gh_token12345"

template:
metadata:
annotations:
karpenter.sh/do-not-disrupt: "true"
labels:
my-label: "my-value"
spec:
containers:
- name: runner
image: ghcr.io/actions/actions-runner:latest
command: ["/home/runner/run.sh"]
11 changes: 11 additions & 0 deletions charts/gha-runner-scale-set/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,17 @@ githubConfigSecret:
## template is the PodSpec for each runner Pod
## For reference: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodSpec
template:
## template.metadata allows setting custom labels and annotations on runner pods.
## Useful for integrations with tools like Karpenter, cost attribution, Dependabot, etc.
## Labels and annotations set here will be propagated to each EphemeralRunner pod.
##
## Example - prevent Karpenter from evicting runner pods mid-job:
# metadata:
# annotations:
# karpenter.sh/do-not-disrupt: "true"
# labels:
# my-label: "my-value"
##
## template.spec will be modified if you change the container mode
## with containerMode.type=dind, we will populate the template.spec with following pod spec
## template:
Expand Down
20 changes: 17 additions & 3 deletions controllers/actions.github.com/resourcebuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ func (b *ResourceBuilder) newAutoScalingListener(autoscalingRunnerSet *v1alpha1.
annotationKeyRunnerSpecHash: autoscalingRunnerSet.ListenerSpecHash(),
annotationKeyValuesHash: autoscalingRunnerSet.Annotations[annotationKeyValuesHash],
}
// Propagate custom annotations from AutoscalingRunnerSet, skipping reserved ones
for k, v := range autoscalingRunnerSet.Annotations {
if !strings.HasPrefix(k, "actions.github.com/") {
annotations[k] = v
}
}

if err := applyGitHubURLLabels(autoscalingRunnerSet.Spec.GitHubConfigUrl, labels); err != nil {
return nil, fmt.Errorf("failed to apply GitHub URL labels: %v", err)
Expand Down Expand Up @@ -283,15 +289,23 @@ func (b *ResourceBuilder) newScaleSetListenerPod(autoscalingListener *v1alpha1.A
labels := make(map[string]string, len(autoscalingListener.Labels))
maps.Copy(labels, autoscalingListener.Labels)

annotations := make(map[string]string)
for k, v := range autoscalingListener.Annotations {
if !strings.HasPrefix(k, "actions.github.com/") {
annotations[k] = v
}
}

newRunnerScaleSetListenerPod := &corev1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: autoscalingListener.Name,
Namespace: autoscalingListener.Namespace,
Labels: labels,
Name: autoscalingListener.Name,
Namespace: autoscalingListener.Namespace,
Labels: labels,
Annotations: annotations,
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: autoscalingListener.GetObjectKind().GroupVersionKind().GroupVersion().String(),
Expand Down
12 changes: 12 additions & 0 deletions controllers/actions.github.com/resourcebuilder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ func TestLabelPropagation(t *testing.T) {
runnerScaleSetIDAnnotationKey: "1",
AnnotationKeyGitHubRunnerGroupName: "test-group",
AnnotationKeyGitHubRunnerScaleSetName: "test-scale-set",
"karpenter.sh/do-not-disrupt": "true",
"my-company.io/team": "platform",
},
},
Spec: v1alpha1.AutoscalingRunnerSetSpec{
Expand Down Expand Up @@ -76,6 +78,13 @@ func TestLabelPropagation(t *testing.T) {
assert.NotContains(t, listener.Labels, "directly.excluded.org/label")
assert.Equal(t, "not-excluded-value", listener.Labels["directly.excluded.org/arbitrary"])

// Custom annotations should be propagated to the listener
assert.Equal(t, "true", listener.Annotations["karpenter.sh/do-not-disrupt"])
assert.Equal(t, "platform", listener.Annotations["my-company.io/team"])
// Reserved actions.github.com/ annotations must not be propagated
assert.NotContains(t, listener.Annotations, AnnotationKeyGitHubRunnerGroupName)
assert.NotContains(t, listener.Annotations, AnnotationKeyGitHubRunnerScaleSetName)

listenerServiceAccount := &corev1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Expand All @@ -84,6 +93,9 @@ func TestLabelPropagation(t *testing.T) {
listenerPod, err := b.newScaleSetListenerPod(listener, &corev1.Secret{}, listenerServiceAccount, nil)
require.NoError(t, err)
assert.Equal(t, listenerPod.Labels, listener.Labels)
// Custom annotations must also reach the listener pod itself
assert.Equal(t, "true", listenerPod.Annotations["karpenter.sh/do-not-disrupt"])
assert.Equal(t, "platform", listenerPod.Annotations["my-company.io/team"])

ephemeralRunner := b.newEphemeralRunner(ephemeralRunnerSet)
require.NoError(t, err)
Expand Down