Commit a45593d

Eric Bower  ·  2026-08-12 10:05:47 -0400 EDT
parent 07ea8af
feat: inject artifacts dir
2 files changed,  +81, -11
+35, -11
......@@ -238,6 +238,8 @@ ENVIRONMENT VARIABLES: pici injects these vars into pico.sh:
238238 PICI_BRANCH Git branch name (auto-detected)
239239 PICI_COMMIT Git commit SHA (auto-detected)
240240 PICI_TAG Git tag name (set for tag events)
241+ PICI_ARTIFACTS_DIR Path to job artifacts directory for staging assets
242+ PICI_ARTIFACT_DIR Alias for PICI_ARTIFACTS_DIR
241243 ZMX_SESSION_PREFIX Internal session prefix managed by pici
242244
243245 Pass custom env vars or overrides with -e / -env (e.g. pici -e CGO_ENABLED=0).
......@@ -415,11 +417,12 @@ EXAMPLE
415417
416418 ENV VARS IN pico.sh
417419 The runner exports these environment variables for your pico.sh script:
418- PICI_JOB Unique job identifier (e.g. "a3f2b8c1")
420+ PICI_JOB Unique job identifier (e.g. "a3f2b8c1")
419421 PICI_REPO Repository name (from event "name" field)
420422 PICI_EVENT Event type (e.g. "git.push", "git.tag")
421423 PICI_TAG Git tag name (set only for git.tag events)
422424 PICI_BRANCH Git branch name (set only for git.push events)
425+ PICI_ARTIFACTS_DIR Path to job artifacts directory for staging assets
423426
424427 Note: pico.sh runs with the workspace as its working directory, so
425428 $(pwd) gives you the workspace path directly.
......@@ -750,7 +753,12 @@ func (eng *JobEngine) Run(manifest string) error {
750753 // outer zmx run call — it would be prepended to the runner's own name.
751754 // The prefix is only for child sessions spawned inside pico.sh (via the
752755 // exported env var).
753- cmdEnv := make([]string, 0, len(os.Environ())+5)
756+ artifactsDir := filepath.Join(eng.Cfg.ArtifactDir, eng.Ev.Name, eng.JobID, "artifacts")
757+ if err := os.MkdirAll(artifactsDir, 0755); err != nil {
758+ log.Error("create artifacts dir", "err", err)
759+ }
760+
761+ cmdEnv := make([]string, 0, len(os.Environ())+7)
754762 for _, e := range os.Environ() {
755763 if !strings.HasPrefix(e, "ZMX_SESSION_PREFIX=") {
756764 cmdEnv = append(cmdEnv, e)
......@@ -761,6 +769,8 @@ func (eng *JobEngine) Run(manifest string) error {
761769 fmt.Sprintf("PICI_REPO=%s", eng.Ev.Name),
762770 fmt.Sprintf("PICI_EVENT=%s", eng.Ev.Type),
763771 fmt.Sprintf("PICI_BRANCH=%s", eng.Ev.Branch),
772+ fmt.Sprintf("PICI_ARTIFACTS_DIR=%s", artifactsDir),
773+ fmt.Sprintf("PICI_ARTIFACT_DIR=%s", artifactsDir),
764774 )
765775 if eng.Ev.Tag != "" {
766776 cmdEnv = append(cmdEnv, fmt.Sprintf("PICI_TAG=%s", eng.Ev.Tag))
......@@ -2200,21 +2210,29 @@ func generateJobIndex(artifactDir, name, jobID string, sessions []SessionInfo) (
22002210 var artifacts []artifactRow
22012211 artifactsDir := filepath.Join(artifactDir, name, jobID, "artifacts")
22022212
2203- if entries, err := os.ReadDir(artifactsDir); err == nil {
2204- for _, entry := range entries {
2205- if entry.IsDir() {
2206- continue
2213+ if _, err := os.Stat(artifactsDir); err == nil {
2214+ _ = filepath.WalkDir(artifactsDir, func(path string, d os.DirEntry, err error) error {
2215+ if err != nil || d.IsDir() {
2216+ return nil
22072217 }
2208- info, err := entry.Info()
2218+ rel, err := filepath.Rel(artifactsDir, path)
22092219 if err != nil {
2210- continue
2220+ return nil
2221+ }
2222+ if strings.HasPrefix(d.Name(), ".") {
2223+ return nil
2224+ }
2225+ info, err := d.Info()
2226+ if err != nil {
2227+ return nil
22112228 }
22122229 artifacts = append(artifacts, artifactRow{
2213- Name: entry.Name(),
2230+ Name: rel,
22142231 Size: formatFileSize(info.Size()),
22152232 ModTime: formatTimestamp(fmt.Sprintf("%d", info.ModTime().Unix())),
22162233 })
2217- }
2234+ return nil
2235+ })
22182236 }
22192237
22202238 // Resolve overall job status
......@@ -2262,6 +2280,12 @@ func generateJobIndex(artifactDir, name, jobID string, sessions []SessionInfo) (
22622280 fmt.Fprintf(&buf, " %s %-20s exit: %-5s duration: %-8s ended: %s\n",
22632281 rIcon, r.Short, r.ExitCode, r.Duration, r.Ended)
22642282 }
2283+ if len(artifacts) > 0 {
2284+ fmt.Fprintln(&buf, "Artifacts:")
2285+ for _, a := range artifacts {
2286+ fmt.Fprintf(&buf, " %-30s %-10s %s\n", a.Name, a.Size, a.ModTime)
2287+ }
2288+ }
22652289 fmt.Fprintln(&buf, strings.Repeat("-", 60))
22662290 txtContent = buf.String()
22672291
......@@ -2387,10 +2411,10 @@ func runLocal(cfg *Cfg, dest string) error {
23872411 }()
23882412
23892413 fmt.Fprintf(os.Stdout, "🚀 starting local job local.%s.%s\n", eventData.Name, jobID) //nolint:errcheck
2390- fmt.Fprintln(os.Stdout, "📦 syncing workspace to temp directory...") //nolint:errcheck
23912414 if err := eng.Setup(); err != nil {
23922415 return fmt.Errorf("workspace setup: %w", err)
23932416 }
2417+ fmt.Fprintf(os.Stdout, "📦 syncing workspace to temp directory %s\n", eng.Wk.GetDir()) //nolint:errcheck
23942418 log.Debug("workspace directory", "dir", eng.Wk.GetDir())
23952419
23962420 // Store event.json in artifact dir
+46, -0
......@@ -1036,3 +1036,49 @@ func (w *mockLocalWorkspace) Setup() error { return nil }
10361036 func (w *mockLocalWorkspace) Cleanup() error { return nil }
10371037 func (w *mockLocalWorkspace) GetDir() string { return w.dir }
10381038 func (w *mockLocalWorkspace) Checksum() string { return "" }
1039+
1040+func TestJobEngine_ExportsArtifactsDir(t *testing.T) {
1041+ tmpDir := t.TempDir()
1042+ eng := &JobEngine{
1043+ Logger: slog.New(slog.NewTextHandler(io.Discard, nil)),
1044+ Cfg: &Cfg{
1045+ ArtifactDir: tmpDir,
1046+ },
1047+ Ev: &Event{
1048+ Name: "myrepo",
1049+ Type: "local",
1050+ },
1051+ JobID: "job123",
1052+ }
1053+
1054+ expectedArtifactsDir := filepath.Join(tmpDir, "myrepo", "job123", "artifacts")
1055+
1056+ // Verify that artifacts directory path matches expected layout
1057+ artifactsDir := filepath.Join(eng.Cfg.ArtifactDir, eng.Ev.Name, eng.JobID, "artifacts")
1058+ if artifactsDir != expectedArtifactsDir {
1059+ t.Errorf("expected artifactsDir %s, got %s", expectedArtifactsDir, artifactsDir)
1060+ }
1061+}
1062+
1063+func TestGenerateJobIndex_WalksSubdirectories(t *testing.T) {
1064+ tmpDir := t.TempDir()
1065+ repoName := "testrepo"
1066+ jobID := "j999"
1067+ artifactsDir := filepath.Join(tmpDir, repoName, jobID, "artifacts")
1068+
1069+ if err := os.MkdirAll(filepath.Join(artifactsDir, "build"), 0755); err != nil {
1070+ t.Fatalf("mkdir failed: %v", err)
1071+ }
1072+ if err := os.WriteFile(filepath.Join(artifactsDir, "build", "output.json"), []byte("{}"), 0644); err != nil {
1073+ t.Fatalf("write file failed: %v", err)
1074+ }
1075+
1076+ htmlContent, txtContent := generateJobIndex(tmpDir, repoName, jobID, nil)
1077+ if !strings.Contains(htmlContent, "build/output.json") {
1078+ t.Errorf("html index missing nested artifact build/output.json: %s", htmlContent)
1079+ }
1080+ if !strings.Contains(txtContent, "build/output.json") {
1081+ t.Errorf("txt index missing nested artifact build/output.json: %s", txtContent)
1082+ }
1083+}
1084+