mirror of
https://github.com/actions-runner-controller/actions-runner-controller.git
synced 2026-09-30 03:53:12 +02:00
139 lines
5.5 KiB
Go
139 lines
5.5 KiB
Go
package actionsgithubcom
|
|
|
|
import (
|
|
"maps"
|
|
"testing"
|
|
|
|
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
)
|
|
|
|
// legacyIntegrityHashAnnotation is the annotation this package used to stamp on
|
|
// the resources it builds. It is deliberately spelled out rather than
|
|
// referenced, because the constant it mirrors has been deleted; these tests
|
|
// pin the upgrade behaviour for objects that were created while it still
|
|
// existed.
|
|
const legacyIntegrityHashAnnotation = "actions.github.com/integrity-hash"
|
|
|
|
func newLegacyAnnotationTestAutoscalingRunnerSet() *v1alpha1.AutoscalingRunnerSet {
|
|
return &v1alpha1.AutoscalingRunnerSet{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-scale-set",
|
|
Namespace: "test-ns",
|
|
Labels: map[string]string{
|
|
LabelKeyKubernetesPartOf: labelValueKubernetesPartOf,
|
|
LabelKeyKubernetesVersion: "0.2.0",
|
|
},
|
|
Annotations: map[string]string{
|
|
runnerScaleSetIDAnnotationKey: "1",
|
|
AnnotationKeyGitHubRunnerGroupName: "test-group",
|
|
AnnotationKeyGitHubRunnerScaleSetName: "test-scale-set",
|
|
},
|
|
},
|
|
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
|
GitHubConfigUrl: "https://github.com/org/repo",
|
|
},
|
|
}
|
|
}
|
|
|
|
// TestLegacyIntegrityHashAnnotationCausesOneTimeListenerRecreation documents an
|
|
// upgrade consequence of no longer stamping the integrity hash.
|
|
//
|
|
// AutoscalingRunnerSetReconciler compares the live listener's annotations
|
|
// against the desired ones with maps.Equal and deletes the listener when they
|
|
// differ. A listener created by an older controller carries the legacy
|
|
// annotation, the desired listener no longer does, so the first reconcile after
|
|
// an upgrade recreates it.
|
|
//
|
|
// This is a one-time rollout, not a reconcile loop: the replacement is built by
|
|
// the same code path and carries no annotation, so the next comparison matches.
|
|
// In practice the rollout is not additional either, since the listener runs the
|
|
// manager's own image, and a controller upgrade changes that image and so
|
|
// already forces the same recreation through the Spec comparison.
|
|
func TestLegacyIntegrityHashAnnotationCausesOneTimeListenerRecreation(t *testing.T) {
|
|
autoscalingRunnerSet := newLegacyAnnotationTestAutoscalingRunnerSet()
|
|
|
|
cache := NewResourceCache()
|
|
b := ResourceBuilder{ResourceCache: &cache}
|
|
|
|
ephemeralRunnerSet, err := b.newEphemeralRunnerSet(autoscalingRunnerSet)
|
|
require.NoError(t, err)
|
|
|
|
desired, err := b.newAutoscalingListener(autoscalingRunnerSet, ephemeralRunnerSet, "controller-ns", "test:latest", nil)
|
|
require.NoError(t, err)
|
|
assert.NotContains(t, desired.Annotations, legacyIntegrityHashAnnotation)
|
|
|
|
// A listener as an older controller would have left it behind.
|
|
live := desired.DeepCopy()
|
|
live.Annotations = map[string]string{legacyIntegrityHashAnnotation: "some-stale-hash"}
|
|
|
|
assert.False(
|
|
t,
|
|
maps.Equal(live.Annotations, desired.Annotations),
|
|
"a listener carrying the legacy annotation must not compare equal to the desired listener, otherwise it would never be replaced",
|
|
)
|
|
|
|
// The replacement converges, so the recreation happens once rather than on
|
|
// every reconcile.
|
|
replacement, err := b.newAutoscalingListener(autoscalingRunnerSet, ephemeralRunnerSet, "controller-ns", "test:latest", nil)
|
|
require.NoError(t, err)
|
|
assert.NotContains(t, replacement.Annotations, legacyIntegrityHashAnnotation)
|
|
assert.True(
|
|
t,
|
|
maps.Equal(replacement.Annotations, desired.Annotations),
|
|
"the recreated listener must match the desired one, otherwise the controller would rebuild it forever",
|
|
)
|
|
}
|
|
|
|
// TestLegacyIntegrityHashAnnotationRetainedOnExistingObjects pins the scope of
|
|
// this removal: it stops the annotation being written, it does not strip it
|
|
// from objects that already carry it.
|
|
//
|
|
// Every reconciler that updates an object it does not replace merges the live
|
|
// annotations under the desired ones, so a key that is only absent from the
|
|
// desired set survives. Nothing reads the annotation any more, so it is inert
|
|
// metadata rather than a behavioural leftover; removing it from existing
|
|
// objects would mean patching every managed object on upgrade and is a separate
|
|
// decision from this change.
|
|
func TestLegacyIntegrityHashAnnotationRetainedOnExistingObjects(t *testing.T) {
|
|
ephemeralRunnerSet := &v1alpha1.EphemeralRunnerSet{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-scale-set",
|
|
Namespace: "test-ns",
|
|
Labels: map[string]string{
|
|
LabelKeyGitHubScaleSetName: "test-scale-set",
|
|
LabelKeyGitHubScaleSetNamespace: "test-ns",
|
|
},
|
|
},
|
|
}
|
|
|
|
var b ResourceBuilder
|
|
desired, err := b.newEphemeralRunnerSetProxySecret(ephemeralRunnerSet, map[string][]byte{
|
|
"http_proxy": []byte("http://proxy.example.com"),
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotContains(t, desired.Annotations, legacyIntegrityHashAnnotation)
|
|
|
|
// A proxy secret as an older controller would have left it behind.
|
|
live := &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: desired.Name,
|
|
Namespace: desired.Namespace,
|
|
Annotations: map[string]string{
|
|
legacyIntegrityHashAnnotation: "some-stale-hash",
|
|
"example.com/user-annotation": "user-value",
|
|
},
|
|
},
|
|
}
|
|
|
|
merged := b.mergeAnnotations(live.Annotations, desired.Annotations)
|
|
|
|
assert.Contains(t, merged, legacyIntegrityHashAnnotation,
|
|
"the legacy annotation survives on existing objects; this change stops it being written, it does not migrate it away")
|
|
assert.Equal(t, "user-value", merged["example.com/user-annotation"],
|
|
"unrelated annotations must be preserved")
|
|
}
|