diff --git a/cmd/github-actions-runner-job/main_test.go b/cmd/github-actions-runner-job/main_test.go index 189584d..22ca13c 100644 --- a/cmd/github-actions-runner-job/main_test.go +++ b/cmd/github-actions-runner-job/main_test.go @@ -34,7 +34,7 @@ func TestT915CommandRunsDynamicProviderEnvelopeThroughSidecarAndRunner(t *testin if body.Ref != "main" { t.Fatalf("dispatch ref: got %q", body.Ref) } - wantLabels := `["self-hosted","linux","wfc-stg-ghp-linux-01234567-abcdef98","wfc-ghp-stg","wfc-ghp-ephemeral"]` + wantLabels := `["self-hosted","linux","wfc-stg-ghp-linux-abcdef987249-543210f71ee4","wfc-ghp-stg","wfc-ghp-ephemeral"]` for key, want := range map[string]string{ "runner_profile": "provider", "allow_github_hosted_fallback": "false", @@ -103,9 +103,9 @@ func TestT915CommandRunsDynamicProviderEnvelopeThroughSidecarAndRunner(t *testin for _, want := range []string{ "--url\nhttps://github.com/GoCodeAlone", "--token\nrunner-registration-token", - "--name\nwfc-stg-ghp-linux-01234567-abcdef98", + "--name\nwfc-stg-ghp-linux-abcdef987249-543210f71ee4", "--runnergroup\nephemeral", - "--labels\nself-hosted,linux,wfc-stg-ghp-linux-01234567-abcdef98,wfc-ghp-stg,wfc-ghp-ephemeral", + "--labels\nself-hosted,linux,wfc-stg-ghp-linux-abcdef987249-543210f71ee4,wfc-ghp-stg,wfc-ghp-ephemeral", "--ephemeral", } { if !strings.Contains(configArgs, want) { @@ -125,7 +125,7 @@ func TestT915CommandRunsDynamicProviderEnvelopeThroughSidecarAndRunner(t *testin t.Fatalf("artifacts = %#v", result.Artifacts) } proof := readFile(t, filepath.Join(workspace, "github-runner-proof.json")) - for _, want := range []string{"wfc-stg-ghp-linux-01234567-abcdef98", "task-abcdef9876543210", "removed"} { + for _, want := range []string{"wfc-stg-ghp-linux-abcdef987249-543210f71ee4", "task-abcdef9876543210", "removed"} { if !strings.Contains(proof, want) { t.Fatalf("proof missing %q:\n%s", want, proof) } diff --git a/internal/ephemeral_runner_job.go b/internal/ephemeral_runner_job.go index 3b91a8c..8ec1cdb 100644 --- a/internal/ephemeral_runner_job.go +++ b/internal/ephemeral_runner_job.go @@ -2,6 +2,8 @@ package internal import ( "context" + "crypto/sha256" + "encoding/hex" "errors" "fmt" "strings" @@ -180,11 +182,28 @@ func shortEphemeralID(value string) string { if value == "" { return "" } - if _, suffix, ok := strings.Cut(value, "-"); ok && suffix != "" { - value = suffix + canonical := strings.ToLower(value) + var safe strings.Builder + for _, r := range canonical { + switch { + case r >= 'a' && r <= 'z': + safe.WriteRune(r) + case r >= '0' && r <= '9': + safe.WriteRune(r) + } + } + token := safe.String() + if token == "" { + return "" + } + if len(token) <= 8 && token == canonical { + return token } - if len(value) > 8 { - return value[:8] + sum := sha256.Sum256([]byte(canonical)) + hash := hex.EncodeToString(sum[:])[:6] + tail := token + if len(tail) > 6 { + tail = tail[len(tail)-6:] } - return value + return tail + hash } diff --git a/internal/ephemeral_runner_job_test.go b/internal/ephemeral_runner_job_test.go index 2192b3d..55fb580 100644 --- a/internal/ephemeral_runner_job_test.go +++ b/internal/ephemeral_runner_job_test.go @@ -21,13 +21,13 @@ func TestT594EphemeralRunnerJobBuildsDeterministicNameAndLabels(t *testing.T) { t.Fatalf("build spec: %v", err) } - if spec.RunnerName != "wfc-stg-ghp-linux-01234567-abcdef98" { + if spec.RunnerName != "wfc-stg-ghp-linux-abcdef987249-543210f71ee4" { t.Fatalf("runner name: got %q", spec.RunnerName) } wantLabels := []string{ "self-hosted", "linux", - "wfc-stg-ghp-linux-01234567-abcdef98", + "wfc-stg-ghp-linux-abcdef987249-543210f71ee4", "wfc-ghp-stg", "wfc-ghp-ephemeral", } @@ -39,14 +39,62 @@ func TestT594EphemeralRunnerJobBuildsDeterministicNameAndLabels(t *testing.T) { } } +func TestT594EphemeralRunnerJobSpecKeepsTimestampedDogfoodTasksUnique(t *testing.T) { + base := EphemeralRunnerJobRequest{ + Environment: "stg", + OS: "linux", + WorkerID: "github-runner-linux-stg-am5-2-20260629", + Organization: "GoCodeAlone", + RunnerGroup: "ephemeral", + } + first := base + first.TaskID = "github-provider-dogfood-linux-20260630061427" + second := base + second.TaskID = "github-provider-dogfood-linux-20260630064133" + + firstSpec, err := BuildEphemeralRunnerJobSpec(first) + if err != nil { + t.Fatalf("build first spec: %v", err) + } + secondSpec, err := BuildEphemeralRunnerJobSpec(second) + if err != nil { + t.Fatalf("build second spec: %v", err) + } + + if firstSpec.RunnerName == secondSpec.RunnerName { + t.Fatalf("runner names must be unique across timestamped dogfood tasks, both got %q", firstSpec.RunnerName) + } + if firstSpec.Labels[2] == secondSpec.Labels[2] { + t.Fatalf("runner dispatch labels must be unique across timestamped dogfood tasks, both got %q", firstSpec.Labels[2]) + } + if firstSpec.RunnerName != "wfc-stg-ghp-linux-260629fabb6f-061427fb2c0e" { + t.Fatalf("first runner name: got %q", firstSpec.RunnerName) + } + if secondSpec.RunnerName != "wfc-stg-ghp-linux-260629fabb6f-064133cf86b4" { + t.Fatalf("second runner name: got %q", secondSpec.RunnerName) + } +} + +func TestT594ShortEphemeralIDKeepsSanitizedShortIDsUnique(t *testing.T) { + if got := shortEphemeralID("abcd"); got != "abcd" { + t.Fatalf("unchanged short ID: got %q", got) + } + if got := shortEphemeralID("ab-cd"); got != "abcd5db3d8" { + t.Fatalf("sanitized short ID: got %q", got) + } + if shortEphemeralID("abcd") == shortEphemeralID("ab-cd") { + t.Fatal("sanitized short IDs must not collide with already-safe short IDs") + } +} + func TestT594EphemeralRunnerJobUsesExactLabelsForDispatchAndAttachModes(t *testing.T) { for _, mode := range []EphemeralRunnerJobMode{EphemeralRunnerJobModeDispatchThenWait, EphemeralRunnerJobModeAttachToQueued} { t.Run(string(mode), func(t *testing.T) { driver := &fakeEphemeralRunnerDriver{ result: EphemeralRunnerJobResult{ RunnerID: 42, - RunnerName: "wfc-stg-ghp-linux-01234567-abcdef98", - Labels: []string{"self-hosted", "linux", "wfc-stg-ghp-linux-01234567-abcdef98", "wfc-ghp-stg", "wfc-ghp-ephemeral"}, + RunnerName: "wfc-stg-ghp-linux-abcdef987249-543210f71ee4", + Labels: []string{"self-hosted", "linux", "wfc-stg-ghp-linux-abcdef987249-543210f71ee4", "wfc-ghp-stg", "wfc-ghp-ephemeral"}, WorkflowRunID: 1001, WorkflowJobID: 2002, CleanupStatus: "removed", @@ -81,7 +129,7 @@ func TestT594EphemeralRunnerJobCleansUpRunnerOnTimeout(t *testing.T) { driver := &fakeEphemeralRunnerDriver{ result: EphemeralRunnerJobResult{ RunnerID: 42, - RunnerName: "wfc-stg-ghp-linux-01234567-abcdef98", + RunnerName: "wfc-stg-ghp-linux-abcdef987249-543210f71ee4", CleanupStatus: "pending", }, blockUntilDone: true, @@ -117,8 +165,8 @@ func TestT594EphemeralRunnerJobProofIncludesAssignmentCleanupAndArtifacts(t *tes driver := &fakeEphemeralRunnerDriver{ result: EphemeralRunnerJobResult{ RunnerID: 42, - RunnerName: "wfc-stg-ghp-linux-01234567-abcdef98", - Labels: []string{"self-hosted", "linux", "wfc-stg-ghp-linux-01234567-abcdef98", "wfc-ghp-stg", "wfc-ghp-ephemeral"}, + RunnerName: "wfc-stg-ghp-linux-abcdef987249-543210f71ee4", + Labels: []string{"self-hosted", "linux", "wfc-stg-ghp-linux-abcdef987249-543210f71ee4", "wfc-ghp-stg", "wfc-ghp-ephemeral"}, WorkflowRunID: 1001, WorkflowJobID: 2002, WorkerID: "worker-0123456789abcdef", @@ -197,7 +245,7 @@ func TestT594RunnerProviderInvokesEphemeralRunnerJobSpec(t *testing.T) { if err != nil { t.Fatalf("invoke ephemeral_runner_job: %v", err) } - if result["runner_name"] != "wfc-stg-ghp-linux-01234567-abcdef98" { + if result["runner_name"] != "wfc-stg-ghp-linux-abcdef987249-543210f71ee4" { t.Fatalf("runner name: got %+v", result) } if labels, ok := result["labels"].([]string); !ok || len(labels) != 5 || labels[3] != "wfc-ghp-stg" {