Skip to content
Merged
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
2 changes: 1 addition & 1 deletion mage/download/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
)

const (
RunnerVersion = "2.333.1"
RunnerVersion = "2.335.1"
CNIVersion = "1.6.2"
ContainerdVersion = "2.2.2"
RuncVersion = "1.3.4"
Expand Down
66 changes: 62 additions & 4 deletions pkg/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"os"
"path/filepath"
goruntime "runtime"
"strconv"
"strings"
)

Expand Down Expand Up @@ -113,7 +114,16 @@ func (m *Manager) Extract() error {
return nil
}

// findTarball locates the runner archive in the embedded filesystem for the current OS.
// findTarball locates the runner archive in the embedded filesystem for the
// current OS, preferring the HIGHEST embedded version.
//
// Selecting by version (rather than "first prefix match") is critical: the
// embed directory can legitimately hold more than one version — a RunnerVersion
// bump downloads the new tarball but leaves the old one behind — and ReadDir
// returns them sorted, so a naive first-match ships the OLDER runner. GitHub
// deprecates old runner versions and refuses their connections, which silently
// fails every job ("Runner version vX is deprecated and cannot receive
// messages"). Always pick the newest so a leftover tarball can't regress us.
func (m *Manager) findTarball() (string, error) {
// Runner archives are named: actions-runner-{os}-{arch}-{version}.{ext}
// os: linux, win, osx
Expand All @@ -132,12 +142,21 @@ func (m *Manager) findTarball() (string, error) {
return "", fmt.Errorf("reading embedded files: %w", err)
}

var best string
var bestVer []int
for _, e := range entries {
name := e.Name()
if strings.HasPrefix(name, osPrefix) {
return "embed/" + name, nil
if !strings.HasPrefix(name, osPrefix) {
continue
}
ver := parseRunnerVersion(name)
if best == "" || compareRunnerVersions(ver, bestVer) > 0 {
best, bestVer = name, ver
}
}
if best != "" {
return "embed/" + best, nil
}

// Fall back to first non-gitkeep file if no OS match
for _, e := range entries {
Expand All @@ -148,7 +167,46 @@ func (m *Manager) findTarball() (string, error) {
return "embed/" + name, nil
}

return "", fmt.Errorf("no runner archive found in embedded files (did you run 'make download-runner'?)")
return "", fmt.Errorf("no runner archive found in embedded files (did you run 'mage download'?)")
}

// parseRunnerVersion extracts the numeric version components from a runner
// archive name like "actions-runner-osx-arm64-2.335.1.tar.gz" -> [2 335 1].
// Names it can't parse return nil, which sorts lowest.
func parseRunnerVersion(name string) []int {
base := strings.TrimSuffix(strings.TrimSuffix(name, ".tar.gz"), ".zip")
idx := strings.LastIndex(base, "-")
if idx < 0 {
return nil
}
parts := strings.Split(base[idx+1:], ".")
nums := make([]int, 0, len(parts))
for _, p := range parts {
n, err := strconv.Atoi(p)
if err != nil {
return nil
}
nums = append(nums, n)
}
return nums
}

// compareRunnerVersions returns >0 if a>b, <0 if a<b, 0 if equal, comparing
// numeric components left to right (a missing component counts as 0).
func compareRunnerVersions(a, b []int) int {
for i := 0; i < len(a) || i < len(b); i++ {
var ai, bi int
if i < len(a) {
ai = a[i]
}
if i < len(b) {
bi = b[i]
}
if ai != bi {
return ai - bi
}
}
return 0
}

func extractZipFromReader(r io.Reader, dest string) error {
Expand Down
82 changes: 82 additions & 0 deletions pkg/runner/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,3 +383,85 @@ func TestFindTarball_MatchesPlatform(t *testing.T) {
}
}
}

// --- version-aware tarball selection (regression: shipping a deprecated runner) ---

func TestParseRunnerVersion(t *testing.T) {
cases := []struct {
name string
want []int
}{
{"actions-runner-osx-arm64-2.335.1.tar.gz", []int{2, 335, 1}},
{"actions-runner-linux-arm64-2.333.1.tar.gz", []int{2, 333, 1}},
{"actions-runner-win-x64-2.340.0.zip", []int{2, 340, 0}},
{".gitkeep", nil},
{"garbage", nil},
}
for _, c := range cases {
got := parseRunnerVersion(c.name)
if len(got) != len(c.want) {
t.Errorf("parseRunnerVersion(%q) = %v, want %v", c.name, got, c.want)
continue
}
for i := range got {
if got[i] != c.want[i] {
t.Errorf("parseRunnerVersion(%q) = %v, want %v", c.name, got, c.want)
break
}
}
}
}

func TestCompareRunnerVersions(t *testing.T) {
cases := []struct {
a, b []int
want int // sign
}{
{[]int{2, 335, 1}, []int{2, 333, 1}, +1}, // the bug: 335 must beat 333
{[]int{2, 333, 1}, []int{2, 335, 1}, -1},
{[]int{2, 335, 1}, []int{2, 335, 1}, 0},
{[]int{2, 335}, []int{2, 335, 1}, -1}, // missing component counts as 0
{[]int{2, 340, 0}, []int{2, 335, 9}, +1},
{nil, []int{2, 335, 1}, -1}, // unparseable sorts lowest
}
for _, c := range cases {
got := compareRunnerVersions(c.a, c.b)
if (got > 0) != (c.want > 0) || (got < 0) != (c.want < 0) || (got == 0) != (c.want == 0) {
t.Errorf("compareRunnerVersions(%v,%v) sign = %d, want sign %d", c.a, c.b, got, c.want)
}
}
}

// TestFindTarball_PicksHighestVersion guards the real embedded FS: whatever
// versions are present, findTarball must return the newest one for this OS so a
// leftover older tarball can never ship a GitHub-deprecated runner.
func TestFindTarball_PicksHighestVersion(t *testing.T) {
m := &Manager{}
got, err := m.findTarball()
if err != nil {
t.Fatalf("findTarball: %v", err)
}
chosen := parseRunnerVersion(got)

entries, err := runnerFS.ReadDir("embed")
if err != nil {
t.Fatalf("ReadDir: %v", err)
}
var osPrefix string
switch goruntime.GOOS {
case "windows":
osPrefix = "actions-runner-win-"
case "darwin":
osPrefix = "actions-runner-osx-"
default:
osPrefix = "actions-runner-linux-"
}
for _, e := range entries {
if !strings.HasPrefix(e.Name(), osPrefix) {
continue
}
if compareRunnerVersions(parseRunnerVersion(e.Name()), chosen) > 0 {
t.Errorf("findTarball chose %q but a newer tarball %q is embedded", got, e.Name())
}
}
}