diff --git a/internal/stage/archive.go b/internal/stage/archive.go index ffbedcb..67814e0 100644 --- a/internal/stage/archive.go +++ b/internal/stage/archive.go @@ -18,6 +18,10 @@ import ( ) type archiveStage struct{} +type archiveUploadFile struct { + RelativePath string + LocalPath string +} var archivePrerequisiteStages = []string{ "prepare", @@ -112,7 +116,12 @@ func (archiveStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*S return nil, fmt.Errorf("archive: run id is required") } - runFiles, err := collectArchiveRunFiles(workDir) + manifestSource, err := resolveArchiveManifestSource(env, m, workDir) + if err != nil { + return nil, fmt.Errorf("archive: resolve manifest source: %w", err) + } + + runFiles, err := collectArchiveRunFiles(workDir, manifestSource) if err != nil { return nil, fmt.Errorf("archive: collect run files: %w", err) } @@ -120,21 +129,13 @@ func (archiveStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*S if err != nil { return nil, fmt.Errorf("archive: resolve promotion rules: %w", err) } - currentManifestSource := filepath.Join(workDir, "manifest.json") - if info, err := os.Stat(currentManifestSource); err != nil { - return nil, fmt.Errorf("archive: current manifest source %q: %w", currentManifestSource, err) - } else if info.IsDir() { - return nil, fmt.Errorf("archive: current manifest source %q is a directory", currentManifestSource) - } - runUploaded := make([]string, 0, len(runFiles)) - for _, rel := range runFiles { - localPath := filepath.Join(workDir, filepath.FromSlash(rel)) - key := artifacts.S3RunRelativeDestinationKey(runPrefix, rel) - if _, err := env.ObjectStore.Upload(ctx, localPath, key, storage.UploadOptions{}); err != nil { - return nil, fmt.Errorf("archive: upload run file %q to %q: %w", rel, key, err) + for _, file := range runFiles { + key := artifacts.S3RunRelativeDestinationKey(runPrefix, file.RelativePath) + if _, err := env.ObjectStore.Upload(ctx, file.LocalPath, key, storage.UploadOptions{}); err != nil { + return nil, fmt.Errorf("archive: upload run file %q to %q: %w", file.RelativePath, key, err) } - runUploaded = append(runUploaded, rel) + runUploaded = append(runUploaded, file.RelativePath) } promotedUploaded := make([]string, 0, len(promotions)) @@ -375,8 +376,8 @@ func resolveWorkDirRelativePath(workDir, rel string) (string, error) { return cleanedFull, nil } -func collectArchiveRunFiles(workDir string) ([]string, error) { - files := make([]string, 0, 64) +func collectArchiveRunFiles(workDir, manifestPath string) ([]archiveUploadFile, error) { + files := make([]archiveUploadFile, 0, 64) for _, dirName := range archiveRunUploadDirs { fullDir := filepath.Join(workDir, dirName) @@ -403,30 +404,72 @@ func collectArchiveRunFiles(workDir string) ([]string, error) { return fmt.Errorf("relative path from %q to %q: %w", workDir, path, err) } rel = filepath.ToSlash(rel) - files = append(files, rel) + files = append(files, archiveUploadFile{ + RelativePath: rel, + LocalPath: path, + }) return nil }); err != nil { return nil, fmt.Errorf("walk %q: %w", fullDir, err) } } - manifestPath := filepath.Join(workDir, "manifest.json") manifestInfo, err := os.Stat(manifestPath) if err != nil { if os.IsNotExist(err) { - return nil, fmt.Errorf("manifest.json not found in workdir %q", workDir) + return nil, fmt.Errorf("manifest.json not found (checked path %q)", manifestPath) } return nil, fmt.Errorf("stat %q: %w", manifestPath, err) } if manifestInfo.IsDir() { return nil, fmt.Errorf("manifest path %q is a directory", manifestPath) } - files = append(files, "manifest.json") + files = append(files, archiveUploadFile{ + RelativePath: "manifest.json", + LocalPath: manifestPath, + }) - sort.Strings(files) + sort.Slice(files, func(i, j int) bool { + return files[i].RelativePath < files[j].RelativePath + }) return files, nil } +func resolveArchiveManifestSource(env *Env, m *manifest.Manifest, workDir string) (string, error) { + candidates := make([]string, 0, 3) + candidates = append(candidates, filepath.Join(workDir, "manifest.json")) + + sessionID := strings.TrimSpace(env.Config.Session.SessionID) + if sessionID == "" && m != nil { + sessionID = strings.TrimSpace(m.SessionID) + } + if sessionID != "" { + sessionManifest := filepath.Join(artifacts.SessionWorkDir(env.Config.Pipeline.Workspace.Root, sessionID), "manifest.json") + candidates = append(candidates, sessionManifest) + } + + seen := make(map[string]struct{}, len(candidates)) + for _, candidate := range candidates { + clean := filepath.Clean(candidate) + if _, ok := seen[clean]; ok { + continue + } + seen[clean] = struct{}{} + info, err := os.Stat(clean) + if err != nil { + if os.IsNotExist(err) { + continue + } + return "", fmt.Errorf("stat %q: %w", clean, err) + } + if info.IsDir() { + continue + } + return clean, nil + } + return "", fmt.Errorf("manifest.json not found in run workdir or session workdir") +} + func writeCurrentManifestSnapshot(m *manifest.Manifest, archiveMetadata map[string]any) (string, error) { if m == nil { return "", fmt.Errorf("manifest is required")