package app import ( "context" "fmt" "io" "strings" "gitea.maximumdirect.net/eric/narratio/internal/adapters/storage" "gitea.maximumdirect.net/eric/narratio/internal/artifactpolicy" "gitea.maximumdirect.net/eric/narratio/internal/artifacts" "gitea.maximumdirect.net/eric/narratio/internal/config" ) func buildHelperArtifactCatalog(cfg *config.Config) (*artifacts.ArtifactCatalog, error) { catalog := artifacts.NewArtifactCatalog() if err := catalog.RegisterBuiltIns(); err != nil { return nil, err } configured := map[string]artifacts.ConfiguredArtifactDefinition{} if cfg.Pipeline.Scriptorium != nil { for key, item := range cfg.Pipeline.Scriptorium.Artifacts { configured[key] = artifacts.ConfiguredArtifactDefinition{Enabled: item.Enabled, OutputPath: item.OutputPath} } } if err := catalog.RegisterConfiguredArtifacts(configured, nil); err != nil { return nil, err } return catalog, nil } func writeArtifactList(out io.Writer, cfg *config.Config, catalog *artifacts.ArtifactCatalog, locks *effectiveLocks, publishedRemoteState map[string]string) { lockSet := lockSourceSet(locks.All) fmt.Fprintln(out, "Built-in:") for _, id := range []string{ artifacts.ArtifactTranscriptBase, artifacts.ArtifactTranscriptPolished, artifacts.ArtifactTranscriptFinal, artifacts.ArtifactTranscriptFinalTrimmed, artifacts.ArtifactBoundsSession, } { writeArtifactLine(out, id, lockSet) } fmt.Fprintln(out, "Configured:") for _, entry := range catalog.ListConfigured() { writeArtifactLine(out, entry.SourceID, lockSet) } fmt.Fprintln(out, "Previous-session:") for _, req := range artifacts.CollectPreviousArtifactRequirements(configuredScriptoriumArtifacts(cfg)) { fmt.Fprintf(out, "- %s required=%t\n", artifactpolicy.PreviousSessionSourceID(req.Name), req.Required) } fmt.Fprintln(out, "Published:") for _, rule := range cfg.Pipeline.Publish.Outputs { writePublishedOutputLine(out, rule, catalog, lockSet, publishedRemoteState) } } func writeArtifactLine(out io.Writer, source string, lockSet map[string]config.PublishLockRule) { parts := []string{source} if _, ok := lockSet[source]; ok { parts = append(parts, "locked") } fmt.Fprintf(out, "- %s\n", strings.Join(parts, " ")) } func writePublishedOutputLine(out io.Writer, rule config.PublishOutputRule, catalog *artifacts.ArtifactCatalog, lockSet map[string]config.PublishLockRule, remoteState map[string]string) { source := strings.TrimSpace(rule.Source) parts := []string{source} if _, ok := lockSet[source]; ok { parts = append(parts, "locked") } dest, showDest, err := helperPublishedOutputDest(rule, catalog) if err != nil { parts = append(parts, "remote=error") fmt.Fprintf(out, "- %s\n", strings.Join(parts, " ")) return } if showDest { parts = append(parts, "dest="+dest) } if state := remoteState[publishedOutputRemoteStateKey(source, dest)]; state != "" { parts = append(parts, state) } fmt.Fprintf(out, "- %s\n", strings.Join(parts, " ")) } func remotePublishedOutputAvailability(ctx context.Context, cfg *config.Config, store storage.ObjectStore, catalog *artifacts.ArtifactCatalog) map[string]string { out := map[string]string{} sessionPrefix := artifacts.S3SessionPrefix(cfg.Pipeline.Storage.S3.RootPrefix, cfg.Session.Campaign, cfg.Session.SessionID) for _, rule := range cfg.Pipeline.Publish.Outputs { source := strings.TrimSpace(rule.Source) dest, _, err := helperPublishedOutputDest(rule, catalog) if err != nil { out[publishedOutputRemoteStateKey(source, "")] = "remote=error" continue } key := artifacts.S3PublishedOutputKey(sessionPrefix, dest) if exists, err := store.Exists(ctx, key); err == nil && exists { out[publishedOutputRemoteStateKey(source, dest)] = "remote=published" } else if err != nil { out[publishedOutputRemoteStateKey(source, dest)] = "remote=error" } else { out[publishedOutputRemoteStateKey(source, dest)] = "remote=missing" } } return out } func helperPublishedOutputDest(rule config.PublishOutputRule, catalog *artifacts.ArtifactCatalog) (string, bool, error) { source := strings.TrimSpace(rule.Source) normalized, err := artifactpolicy.ResolvePublishedDestination(source, rule.Dest, helperConfiguredOutputPathMap(catalog)) if err != nil { return "", false, err } entry, ok := catalog.Lookup(source) showDest := !ok || strings.TrimSpace(entry.CanonicalRelPath) != normalized return normalized, showDest, nil } func helperConfiguredOutputPathMap(catalog *artifacts.ArtifactCatalog) map[string]string { out := map[string]string{} if catalog == nil { return out } for _, entry := range catalog.ListConfigured() { if strings.TrimSpace(entry.ConfiguredKey) == "" { continue } out[entry.ConfiguredKey] = strings.TrimSpace(entry.CanonicalRelPath) } return out } func publishedOutputRemoteStateKey(source, dest string) string { return strings.TrimSpace(source) + "\x00" + strings.TrimSpace(dest) }