mirror of
https://github.com/actions-runner-controller/actions-runner-controller.git
synced 2026-10-09 12:46:00 +02:00
Track runner spec updates with an actionable revision
When the AutoscalingRunnerSet's runner spec changes, the EphemeralRunnerSet has to delete its idle and pending runners so they are rebuilt from the new spec. That was detected by hashing Spec.EphemeralRunnerSpec into the actions.github.com/integrity-hash annotation and comparing the annotation against a freshly computed hash. Replace it with a monotonic revision counter split across spec and status. The AutoscalingRunnerSet controller bumps Spec.ActionableRevision when it patches a new runner spec across; the EphemeralRunnerSet controller advances Status.AppliedActionableRevision only after the cleanup has actually succeeded. Because the applied marker lives in status and is written last, a controller that crashes part-way through the cleanup comes back with the applied revision still behind the spec revision and redoes the work, instead of skipping runners that are still running the old spec. The drift check uses apiequality.Semantic.DeepEqual rather than cmp.Equal or reflect.DeepEqual. Most PodSpec collection fields carry omitempty, so a template containing an explicitly empty value (`env: []`) is dropped when the EphemeralRunnerSet is written and reads back as nil. A strict comparison would report drift on every reconcile, bump the revision each time, and delete every idle and pending runner forever. helpers_drift_test.go pins that behaviour with a round-trip-through-JSON test. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot App
parent
b830b658d6
commit
ec793dfe9f
@@ -1,10 +1,44 @@
|
||||
package actionsgithubcom
|
||||
|
||||
import (
|
||||
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
)
|
||||
|
||||
// ephemeralRunnerSetActionableSpecChanged reports whether the runner spec the
|
||||
// EphemeralRunnerSet is running differs from the one derived from the
|
||||
// AutoscalingRunnerSet, in a way that requires re-applying it to the runners.
|
||||
//
|
||||
// Semantic.DeepEqual is used rather than cmp.Equal or reflect.DeepEqual because
|
||||
// it treats a nil slice/map as equal to an empty one. That matters here: most
|
||||
// PodSpec collection fields carry omitempty, so a template containing an
|
||||
// explicitly empty value (`env: []`) is dropped when the EphemeralRunnerSet is
|
||||
// written and reads back as nil. A strict comparison would report drift on every
|
||||
// single reconcile, bumping ActionableRevision each time and making the
|
||||
// EphemeralRunnerSet controller delete every idle and pending runner, forever.
|
||||
// Semantic also knows how to compare resource.Quantity, and unlike cmp.Equal it
|
||||
// cannot panic on types with unexported fields.
|
||||
func ephemeralRunnerSetActionableSpecChanged(current, desired *v1alpha1.EphemeralRunnerSet) bool {
|
||||
if current == nil || desired == nil {
|
||||
return current != desired
|
||||
}
|
||||
|
||||
return !apiequality.Semantic.DeepEqual(current.Spec.EphemeralRunnerSpec, desired.Spec.EphemeralRunnerSpec)
|
||||
}
|
||||
|
||||
func nextActionableRevision(current *v1alpha1.EphemeralRunnerSet) int64 {
|
||||
if current == nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
if current.Spec.ActionableRevision > current.Status.AppliedActionableRevision {
|
||||
return current.Spec.ActionableRevision + 1
|
||||
}
|
||||
|
||||
return current.Status.AppliedActionableRevision + 1
|
||||
}
|
||||
|
||||
// listenerPodSpecRequiresRecreation reports whether the live listener pod must be
|
||||
// deleted and rebuilt to match the desired spec.
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user