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
1@@ -1343,21 +1343,9 @@ func monitorTick(cfg *Cfg, log *slog.Logger, output io.Writer, jobStates map[str
2 log.Error("publish final status", "err", err)
3 }
4 }
5- // Write published.json, then sync to include it in the rsync.
6- // This is the only sync for completed jobs. In-progress jobs are
7- // synced every tick in the else branch below.
8- published := map[string]interface{}{
9- "status": status,
10- "exit_code": exitCode,
11- "job_id": jobID,
12- "finished_at": time.Now().UTC().Format(time.RFC3339),
13- }
14- publishedJSON, _ := json.Marshal(published)
15- log.Info("writing sentinel", "path", sentinel)
16- if err := os.MkdirAll(filepath.Dir(sentinel), 0755); err == nil {
17- if err := os.WriteFile(sentinel, publishedJSON, 0644); err != nil {
18- log.Error("write published sentinel", "err", err)
19- }
20+ log.Info("writing sentinel", "repo", name, "job_id", jobID)
21+ if err := writePublishedSentinel(cfg.ArtifactDir, name, jobID, status, exitCode); err != nil {
22+ log.Error("write published sentinel", "err", err)
23 }
24 // Regenerate index.html now that published.json exists, so the artifact list includes it.
25 indexHTML, indexTXT := generateJobIndex(cfg.ArtifactDir, name, jobID, group)
26@@ -2403,9 +2391,43 @@ func runLocal(cfg *Cfg, dest string) error {
27 fmt.Fprintf(os.Stdout, "✅ artifacts rsynced to %s\n", eventData.ArtifactDest) //nolint:errcheck
28 }
29
30+ // Write published sentinel for local run (`pici`)
31+ domain := getDomain(eventData.Type)
32+ prefix := fmt.Sprintf("%s.%s.%s.", domain, eventData.Name, jobID)
33+ if listOutput, err := exec.Command("zmx", "list").CombinedOutput(); err == nil {
34+ sessions := parseZMXList(string(listOutput))
35+ var jobSessions []SessionInfo
36+ for _, s := range sessions {
37+ if strings.HasPrefix(s.Name, prefix) {
38+ s.Short = cleanSessionShort(s.Name, prefix, eventData.Name, jobID)
39+ jobSessions = append(jobSessions, s)
40+ }
41+ }
42+ exitCode, status := resolveJobExitCode(jobSessions)
43+ _ = writePublishedSentinel(cfg.ArtifactDir, eventData.Name, jobID, status, exitCode)
44+ }
45+
46 return nil
47 }
48
49+func writePublishedSentinel(artifactDir, repoName, jobID, status string, exitCode int) error {
50+ sentinel := filepath.Join(artifactDir, repoName, jobID, "artifacts", "published.json")
51+ published := map[string]interface{}{
52+ "status": status,
53+ "exit_code": exitCode,
54+ "job_id": jobID,
55+ "finished_at": time.Now().UTC().Format(time.RFC3339),
56+ }
57+ publishedJSON, err := json.Marshal(published)
58+ if err != nil {
59+ return err
60+ }
61+ if err := os.MkdirAll(filepath.Dir(sentinel), 0755); err != nil {
62+ return err
63+ }
64+ return os.WriteFile(sentinel, publishedJSON, 0644)
65+}
66+
67 func stageLocalArtifacts(cfg *Cfg, log *slog.Logger, repoName, jobID, eventType string) error {
68 domain := getDomain(eventType)
69 prefix := fmt.Sprintf("%s.%s.%s.", domain, repoName, jobID)
70@@ -2449,18 +2471,6 @@ func stageLocalArtifacts(cfg *Cfg, log *slog.Logger, repoName, jobID, eventType
71 }
72 }
73
74- // Write published sentinel
75- exitCode, status := resolveJobExitCode(jobSessions)
76- sentinel := filepath.Join(cfg.ArtifactDir, repoName, jobID, "artifacts", "published.json")
77- published := map[string]interface{}{
78- "status": status,
79- "exit_code": exitCode,
80- "job_id": jobID,
81- "finished_at": time.Now().UTC().Format(time.RFC3339),
82- }
83- publishedJSON, _ := json.Marshal(published)
84- _ = os.WriteFile(sentinel, publishedJSON, 0644)
85-
86 indexHTML, indexTXT := generateJobIndex(cfg.ArtifactDir, repoName, jobID, jobSessions)
87 _ = stageArtifact(cfg.ArtifactDir, repoName, jobID, "index", indexHTML, ".html")
88 _ = stageArtifact(cfg.ArtifactDir, repoName, jobID, "index", indexTXT, ".txt")
+51,
-0
1@@ -86,6 +86,12 @@ zmx run step2 echo "hello from step2"
2 t.Fatal("timeout waiting for runner to complete")
3 }
4
5+ // Verify that RunRunner does NOT create published.json (monitor's job)
6+ sentinel := filepath.Join(artifactDir, "test-repo", testJobID, "artifacts", "published.json")
7+ if _, err := os.Stat(sentinel); err == nil {
8+ t.Fatalf("expected published.json NOT to exist after RunRunner, but it exists at %s", sentinel)
9+ }
10+
11 // 4. Run the monitor and poll for incremental artifacts
12 monitorDone := make(chan error, 1)
13 go func() {
14@@ -972,3 +978,48 @@ func TestWorkspaceRsync_ExcludesGitAndJJ(t *testing.T) {
15 t.Errorf("expected checksum to start with 'sha256:', got %q", checksum)
16 }
17 }
18+
19+func TestRunnerDoesNotCreatePublishedSentinel(t *testing.T) {
20+ tmpDir := t.TempDir()
21+ artifactDir := filepath.Join(tmpDir, "artifacts")
22+ wkDir := filepath.Join(tmpDir, "workspace")
23+
24+ if err := os.MkdirAll(wkDir, 0755); err != nil {
25+ t.Fatalf("failed to create workspace dir: %v", err)
26+ }
27+
28+ picoScript := `#!/usr/bin/env bash
29+set -euo pipefail
30+zmx run step1 -d echo "step1"
31+zmx wait "*"
32+`
33+ if err := os.WriteFile(filepath.Join(wkDir, "pico.sh"), []byte(picoScript), 0755); err != nil {
34+ t.Fatalf("failed to write pico.sh: %v", err)
35+ }
36+
37+ jobID := "j-no-published-test"
38+ event := Event{
39+ Type: "git.push",
40+ Name: "no-published-repo",
41+ JobID: jobID,
42+ Workspace: wkDir,
43+ }
44+ eventJSON, _ := json.Marshal(event)
45+
46+ cfg := &Cfg{
47+ Logger: slog.New(slog.NewTextHandler(io.Discard, nil)),
48+ ArtifactDir: artifactDir,
49+ EventSource: io.NopCloser(bytes.NewReader(append(eventJSON, '\n'))),
50+ NewWorkspace: defaultWorkspaceFactory,
51+ SessionPrefix: "test.",
52+ }
53+
54+ if err := RunRunner(cfg); err != nil {
55+ t.Fatalf("RunRunner failed: %v", err)
56+ }
57+
58+ sentinel := filepath.Join(artifactDir, "no-published-repo", jobID, "artifacts", "published.json")
59+ if _, err := os.Stat(sentinel); err == nil {
60+ t.Errorf("expected published.json NOT to exist after RunRunner, but it exists at %s", sentinel)
61+ }
62+}