From 389d842a30ecd50bd6b052f18e9e89fda43991ed Mon Sep 17 00:00:00 2001 From: Nikola Jokic Date: Wed, 14 May 2025 21:38:16 +0200 Subject: [PATCH] Relax version requirements to allow patch version mismatch (#4080) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- apis/actions.github.com/v1alpha1/version.go | 72 +++++++++++++++++++ .../v1alpha1/version_test.go | 60 ++++++++++++++++ .../autoscalingrunnerset_controller.go | 2 +- 3 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 apis/actions.github.com/v1alpha1/version.go create mode 100644 apis/actions.github.com/v1alpha1/version_test.go diff --git a/apis/actions.github.com/v1alpha1/version.go b/apis/actions.github.com/v1alpha1/version.go new file mode 100644 index 00000000..731c6011 --- /dev/null +++ b/apis/actions.github.com/v1alpha1/version.go @@ -0,0 +1,72 @@ +package v1alpha1 + +import "strings" + +func IsVersionAllowed(resourceVersion, buildVersion string) bool { + if buildVersion == "dev" || resourceVersion == buildVersion || strings.HasPrefix(buildVersion, "canary-") { + return true + } + + rv, ok := parseSemver(resourceVersion) + if !ok { + return false + } + bv, ok := parseSemver(buildVersion) + if !ok { + return false + } + return rv.major == bv.major && rv.minor == bv.minor +} + +type semver struct { + major string + minor string +} + +func parseSemver(v string) (p semver, ok bool) { + if v == "" { + return + } + p.major, v, ok = parseInt(v) + if !ok { + return p, false + } + if v == "" { + p.minor = "0" + return p, true + } + if v[0] != '.' { + return p, false + } + p.minor, v, ok = parseInt(v[1:]) + if !ok { + return p, false + } + if v == "" { + return p, true + } + if v[0] != '.' { + return p, false + } + if _, _, ok = parseInt(v[1:]); !ok { + return p, false + } + return p, true +} + +func parseInt(v string) (t, rest string, ok bool) { + if v == "" { + return + } + if v[0] < '0' || '9' < v[0] { + return + } + i := 1 + for i < len(v) && '0' <= v[i] && v[i] <= '9' { + i++ + } + if v[0] == '0' && i != 1 { + return + } + return v[:i], v[i:], true +} diff --git a/apis/actions.github.com/v1alpha1/version_test.go b/apis/actions.github.com/v1alpha1/version_test.go new file mode 100644 index 00000000..8b4e8025 --- /dev/null +++ b/apis/actions.github.com/v1alpha1/version_test.go @@ -0,0 +1,60 @@ +package v1alpha1_test + +import ( + "testing" + + "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1" + "github.com/stretchr/testify/assert" +) + +func TestIsVersionAllowed(t *testing.T) { + t.Parallel() + tt := map[string]struct { + resourceVersion string + buildVersion string + want bool + }{ + "dev should always be allowed": { + resourceVersion: "0.11.0", + buildVersion: "dev", + want: true, + }, + "resourceVersion is not semver": { + resourceVersion: "dev", + buildVersion: "0.11.0", + want: false, + }, + "buildVersion is not semver": { + resourceVersion: "0.11.0", + buildVersion: "NA", + want: false, + }, + "major version mismatch": { + resourceVersion: "0.11.0", + buildVersion: "1.11.0", + want: false, + }, + "minor version mismatch": { + resourceVersion: "0.11.0", + buildVersion: "0.10.0", + want: false, + }, + "patch version mismatch": { + resourceVersion: "0.11.1", + buildVersion: "0.11.0", + want: true, + }, + "arbitrary version match": { + resourceVersion: "abc", + buildVersion: "abc", + want: true, + }, + } + + for name, tc := range tt { + t.Run(name, func(t *testing.T) { + got := v1alpha1.IsVersionAllowed(tc.resourceVersion, tc.buildVersion) + assert.Equal(t, tc.want, got) + }) + } +} diff --git a/controllers/actions.github.com/autoscalingrunnerset_controller.go b/controllers/actions.github.com/autoscalingrunnerset_controller.go index ad4a0514..21740ff6 100644 --- a/controllers/actions.github.com/autoscalingrunnerset_controller.go +++ b/controllers/actions.github.com/autoscalingrunnerset_controller.go @@ -151,7 +151,7 @@ func (r *AutoscalingRunnerSetReconciler) Reconcile(ctx context.Context, req ctrl return ctrl.Result{}, nil } - if autoscalingRunnerSet.Labels[LabelKeyKubernetesVersion] != build.Version { + if !v1alpha1.IsVersionAllowed(autoscalingRunnerSet.Labels[LabelKeyKubernetesVersion], build.Version) { if err := r.Delete(ctx, autoscalingRunnerSet); err != nil { log.Error(err, "Failed to delete autoscaling runner set on version mismatch", "buildVersion", build.Version,