Commit fc37682
Eric Bower
·
2026-08-11 11:33:42 -0400 EDT
parent 1569539
fix: pici runner should not create a published.json artifact
2 files changed,
+88,
-27
M
main.go
M
main.go
+37,
-27
| ... | ... | @@ -1343,21 +1343,9 @@ func monitorTick(cfg *Cfg, log *slog.Logger, output io.Writer, jobStates map[str | |
| 1343 | 1343 | log.Error("publish final status", "err", err) | |
| 1344 | 1344 | } | |
| 1345 | 1345 | } | |
| 1346 | - | // Write published.json, then sync to include it in the rsync. | |
| 1347 | - | // This is the only sync for completed jobs. In-progress jobs are | |
| 1348 | - | // synced every tick in the else branch below. | |
| 1349 | - | published := map[string]interface{}{ | |
| 1350 | - | "status": status, | |
| 1351 | - | "exit_code": exitCode, | |
| 1352 | - | "job_id": jobID, | |
| 1353 | - | "finished_at": time.Now().UTC().Format(time.RFC3339), | |
| 1354 | - | } | |
| 1355 | - | publishedJSON, _ := json.Marshal(published) | |
| 1356 | - | log.Info("writing sentinel", "path", sentinel) | |
| 1357 | - | if err := os.MkdirAll(filepath.Dir(sentinel), 0755); err == nil { | |
| 1358 | - | if err := os.WriteFile(sentinel, publishedJSON, 0644); err != nil { | |
| 1359 | - | log.Error("write published sentinel", "err", err) | |
| 1360 | - | } | |
| 1346 | + | log.Info("writing sentinel", "repo", name, "job_id", jobID) | |
| 1347 | + | if err := writePublishedSentinel(cfg.ArtifactDir, name, jobID, status, exitCode); err != nil { | |
| 1348 | + | log.Error("write published sentinel", "err", err) | |
| 1361 | 1349 | } | |
| 1362 | 1350 | // Regenerate index.html now that published.json exists, so the artifact list includes it. | |
| 1363 | 1351 | indexHTML, indexTXT := generateJobIndex(cfg.ArtifactDir, name, jobID, group) |
| ... | ... | @@ -2403,9 +2391,43 @@ func runLocal(cfg *Cfg, dest string) error { | |
| 2403 | 2391 | fmt.Fprintf(os.Stdout, "✅ artifacts rsynced to %s\n", eventData.ArtifactDest) //nolint:errcheck | |
| 2404 | 2392 | } | |
| 2405 | 2393 | ||
| 2394 | + | // Write published sentinel for local run (`pici`) | |
| 2395 | + | domain := getDomain(eventData.Type) | |
| 2396 | + | prefix := fmt.Sprintf("%s.%s.%s.", domain, eventData.Name, jobID) | |
| 2397 | + | if listOutput, err := exec.Command("zmx", "list").CombinedOutput(); err == nil { | |
| 2398 | + | sessions := parseZMXList(string(listOutput)) | |
| 2399 | + | var jobSessions []SessionInfo | |
| 2400 | + | for _, s := range sessions { | |
| 2401 | + | if strings.HasPrefix(s.Name, prefix) { | |
| 2402 | + | s.Short = cleanSessionShort(s.Name, prefix, eventData.Name, jobID) | |
| 2403 | + | jobSessions = append(jobSessions, s) | |
| 2404 | + | } | |
| 2405 | + | } | |
| 2406 | + | exitCode, status := resolveJobExitCode(jobSessions) | |
| 2407 | + | _ = writePublishedSentinel(cfg.ArtifactDir, eventData.Name, jobID, status, exitCode) | |
| 2408 | + | } | |
| 2409 | + | ||
| 2406 | 2410 | return nil | |
| 2407 | 2411 | } | |
| 2408 | 2412 | ||
| 2413 | + | func writePublishedSentinel(artifactDir, repoName, jobID, status string, exitCode int) error { | |
| 2414 | + | sentinel := filepath.Join(artifactDir, repoName, jobID, "artifacts", "published.json") | |
| 2415 | + | published := map[string]interface{}{ | |
| 2416 | + | "status": status, | |
| 2417 | + | "exit_code": exitCode, | |
| 2418 | + | "job_id": jobID, | |
| 2419 | + | "finished_at": time.Now().UTC().Format(time.RFC3339), | |
| 2420 | + | } | |
| 2421 | + | publishedJSON, err := json.Marshal(published) | |
| 2422 | + | if err != nil { | |
| 2423 | + | return err | |
| 2424 | + | } | |
| 2425 | + | if err := os.MkdirAll(filepath.Dir(sentinel), 0755); err != nil { | |
| 2426 | + | return err | |
| 2427 | + | } | |
| 2428 | + | return os.WriteFile(sentinel, publishedJSON, 0644) | |
| 2429 | + | } | |
| 2430 | + | ||
| 2409 | 2431 | func stageLocalArtifacts(cfg *Cfg, log *slog.Logger, repoName, jobID, eventType string) error { | |
| 2410 | 2432 | domain := getDomain(eventType) | |
| 2411 | 2433 | prefix := fmt.Sprintf("%s.%s.%s.", domain, repoName, jobID) |
| ... | ... | @@ -2449,18 +2471,6 @@ func stageLocalArtifacts(cfg *Cfg, log *slog.Logger, repoName, jobID, eventType | |
| 2449 | 2471 | } | |
| 2450 | 2472 | } | |
| 2451 | 2473 | ||
| 2452 | - | // Write published sentinel | |
| 2453 | - | exitCode, status := resolveJobExitCode(jobSessions) | |
| 2454 | - | sentinel := filepath.Join(cfg.ArtifactDir, repoName, jobID, "artifacts", "published.json") | |
| 2455 | - | published := map[string]interface{}{ | |
| 2456 | - | "status": status, | |
| 2457 | - | "exit_code": exitCode, | |
| 2458 | - | "job_id": jobID, | |
| 2459 | - | "finished_at": time.Now().UTC().Format(time.RFC3339), | |
| 2460 | - | } | |
| 2461 | - | publishedJSON, _ := json.Marshal(published) | |
| 2462 | - | _ = os.WriteFile(sentinel, publishedJSON, 0644) | |
| 2463 | - | ||
| 2464 | 2474 | indexHTML, indexTXT := generateJobIndex(cfg.ArtifactDir, repoName, jobID, jobSessions) | |
| 2465 | 2475 | _ = stageArtifact(cfg.ArtifactDir, repoName, jobID, "index", indexHTML, ".html") | |
| 2466 | 2476 | _ = stageArtifact(cfg.ArtifactDir, repoName, jobID, "index", indexTXT, ".txt") |
+51,
-0
| ... | ... | @@ -86,6 +86,12 @@ zmx run step2 echo "hello from step2" | |
| 86 | 86 | t.Fatal("timeout waiting for runner to complete") | |
| 87 | 87 | } | |
| 88 | 88 | ||
| 89 | + | // Verify that RunRunner does NOT create published.json (monitor's job) | |
| 90 | + | sentinel := filepath.Join(artifactDir, "test-repo", testJobID, "artifacts", "published.json") | |
| 91 | + | if _, err := os.Stat(sentinel); err == nil { | |
| 92 | + | t.Fatalf("expected published.json NOT to exist after RunRunner, but it exists at %s", sentinel) | |
| 93 | + | } | |
| 94 | + | ||
| 89 | 95 | // 4. Run the monitor and poll for incremental artifacts | |
| 90 | 96 | monitorDone := make(chan error, 1) | |
| 91 | 97 | go func() { |
| ... | ... | @@ -972,3 +978,48 @@ func TestWorkspaceRsync_ExcludesGitAndJJ(t *testing.T) { | |
| 972 | 978 | t.Errorf("expected checksum to start with 'sha256:', got %q", checksum) | |
| 973 | 979 | } | |
| 974 | 980 | } | |
| 981 | + | ||
| 982 | + | func TestRunnerDoesNotCreatePublishedSentinel(t *testing.T) { | |
| 983 | + | tmpDir := t.TempDir() | |
| 984 | + | artifactDir := filepath.Join(tmpDir, "artifacts") | |
| 985 | + | wkDir := filepath.Join(tmpDir, "workspace") | |
| 986 | + | ||
| 987 | + | if err := os.MkdirAll(wkDir, 0755); err != nil { | |
| 988 | + | t.Fatalf("failed to create workspace dir: %v", err) | |
| 989 | + | } | |
| 990 | + | ||
| 991 | + | picoScript := `#!/usr/bin/env bash | |
| 992 | + | set -euo pipefail | |
| 993 | + | zmx run step1 -d echo "step1" | |
| 994 | + | zmx wait "*" | |
| 995 | + | ` | |
| 996 | + | if err := os.WriteFile(filepath.Join(wkDir, "pico.sh"), []byte(picoScript), 0755); err != nil { | |
| 997 | + | t.Fatalf("failed to write pico.sh: %v", err) | |
| 998 | + | } | |
| 999 | + | ||
| 1000 | + | jobID := "j-no-published-test" | |
| 1001 | + | event := Event{ | |
| 1002 | + | Type: "git.push", | |
| 1003 | + | Name: "no-published-repo", | |
| 1004 | + | JobID: jobID, | |
| 1005 | + | Workspace: wkDir, | |
| 1006 | + | } | |
| 1007 | + | eventJSON, _ := json.Marshal(event) | |
| 1008 | + | ||
| 1009 | + | cfg := &Cfg{ | |
| 1010 | + | Logger: slog.New(slog.NewTextHandler(io.Discard, nil)), | |
| 1011 | + | ArtifactDir: artifactDir, | |
| 1012 | + | EventSource: io.NopCloser(bytes.NewReader(append(eventJSON, '\n'))), | |
| 1013 | + | NewWorkspace: defaultWorkspaceFactory, | |
| 1014 | + | SessionPrefix: "test.", | |
| 1015 | + | } | |
| 1016 | + | ||
| 1017 | + | if err := RunRunner(cfg); err != nil { | |
| 1018 | + | t.Fatalf("RunRunner failed: %v", err) | |
| 1019 | + | } | |
| 1020 | + | ||
| 1021 | + | sentinel := filepath.Join(artifactDir, "no-published-repo", jobID, "artifacts", "published.json") | |
| 1022 | + | if _, err := os.Stat(sentinel); err == nil { | |
| 1023 | + | t.Errorf("expected published.json NOT to exist after RunRunner, but it exists at %s", sentinel) | |
| 1024 | + | } | |
| 1025 | + | } |