217 lines
7.5 KiB
Go
217 lines
7.5 KiB
Go
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"
|
|
"gitea.maximumdirect.net/eric/narratio/internal/manifest"
|
|
)
|
|
|
|
func buildHelperArtifactCatalog(cfg *config.Config, m *manifest.Manifest) (*artifacts.ArtifactCatalog, error) {
|
|
configured := artifacts.ConfiguredArtifactDefinitions(nil)
|
|
if cfg.Pipeline.Scriptorium != nil {
|
|
configured = artifacts.ConfiguredArtifactDefinitions(cfg.Pipeline.Scriptorium.Artifacts)
|
|
}
|
|
extractionDefinitions := artifacts.ExtractionDefinitionsFromConfig(cfg.Pipeline.Notarius)
|
|
effective, err := artifacts.ResolveEffectiveArtifactSet(configured, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
catalog, err := artifacts.BootstrapRuntimeCatalog(configured, effective, extractionDefinitions)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
paths := artifacts.NewLocalStore(cfg.Pipeline.Workspace.Root).SessionPathsFor(cfg.Session.Campaign, cfg.Session.SessionID)
|
|
if cfg.Pipeline.Notarius != nil && cfg.Pipeline.Notarius.Enabled {
|
|
catalog.HydrateExtractionArtifacts(paths, m, extractionDefinitions)
|
|
}
|
|
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 _, transcript := range artifacts.RuntimeTranscriptArtifacts() {
|
|
writeArtifactLine(out, transcript.SourceID, lockSet)
|
|
}
|
|
writeArtifactLine(out, artifacts.ArtifactBoundsSession, lockSet)
|
|
fmt.Fprintln(out, "Configured:")
|
|
for _, entry := range catalog.ListConfigured() {
|
|
writeArtifactLine(out, entry.SourceID, lockSet)
|
|
}
|
|
fmt.Fprintln(out, "Extraction:")
|
|
for _, entry := range catalog.ListExtraction() {
|
|
state := "unavailable"
|
|
if entry.Available {
|
|
state = "available"
|
|
}
|
|
writeExtractionArtifactLine(out, entry.SourceID, state, entry.Provenance, lockSet)
|
|
}
|
|
fmt.Fprintln(out, "Previous-session:")
|
|
if effective, err := resolveEffectiveArtifacts(cfg, nil); err == nil {
|
|
for _, req := range artifacts.CollectPreviousArtifactRequirements(configuredScriptoriumArtifacts(cfg), effective) {
|
|
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 writeExtractionArtifactLine(out io.Writer, source, state, provenance string, lockSet map[string]config.PublishLockRule) {
|
|
parts := []string{source, "planned", state}
|
|
if strings.TrimSpace(provenance) != "" {
|
|
parts = append(parts, "provenance="+strings.TrimSpace(provenance))
|
|
}
|
|
if _, ok := lockSet[source]; ok {
|
|
parts = append(parts, "locked")
|
|
}
|
|
fmt.Fprintf(out, "- %s\n", strings.Join(parts, " "))
|
|
}
|
|
|
|
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 remotePublishedOutputAvailabilityForCurrent(
|
|
ctx context.Context,
|
|
cfg *config.Config,
|
|
store storage.ObjectStore,
|
|
catalog *artifacts.ArtifactCatalog,
|
|
current *RemoteCurrentState,
|
|
) map[string]string {
|
|
if current == nil || current.Commit == nil {
|
|
return remotePublishedOutputAvailability(ctx, cfg, store, catalog)
|
|
}
|
|
out := map[string]string{}
|
|
runPrefix := artifacts.S3RunPrefix(current.SessionPrefix, current.RunID)
|
|
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.S3RunRelativeDestinationKey(runPrefix, dest)
|
|
if committedPublishedOutput(current.Commit, key) {
|
|
out[publishedOutputRemoteStateKey(source, dest)] = "remote=published"
|
|
} else {
|
|
out[publishedOutputRemoteStateKey(source, dest)] = "remote=missing"
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func committedPublishedOutput(commit *artifacts.RemoteCommitManifest, key string) bool {
|
|
if commit == nil {
|
|
return false
|
|
}
|
|
for _, artifact := range commit.Artifacts {
|
|
if artifact.Type == artifacts.RemoteArtifactTypePublishedOutput && artifact.DestinationKey == key {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func helperPublishedOutputDest(rule config.PublishOutputRule, catalog *artifacts.ArtifactCatalog) (string, bool, error) {
|
|
source := strings.TrimSpace(rule.Source)
|
|
normalized, err := artifactpolicy.ResolvePublishedDestinationWithExtractions(
|
|
source,
|
|
rule.Dest,
|
|
helperConfiguredOutputPathMap(catalog),
|
|
helperExtractionOutputSet(catalog),
|
|
)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
entry, ok := catalog.Lookup(source)
|
|
showDest := !ok || strings.TrimSpace(entry.CanonicalRelPath) != normalized
|
|
return normalized, showDest, nil
|
|
}
|
|
|
|
func helperExtractionOutputSet(catalog *artifacts.ArtifactCatalog) map[string]struct{} {
|
|
out := map[string]struct{}{}
|
|
if catalog == nil {
|
|
return out
|
|
}
|
|
for _, entry := range catalog.ListExtraction() {
|
|
if strings.TrimSpace(entry.ExtractionKey) != "" {
|
|
out[entry.ExtractionKey] = struct{}{}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
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)
|
|
}
|