Files
narratio/internal/config/artifact_catalog.go

145 lines
4.1 KiB
Go

package config
import (
"fmt"
"sort"
"strings"
configkit "gitea.maximumdirect.net/eric/ConfigKit"
"gitea.maximumdirect.net/eric/narratio/internal/artifactpolicy"
)
// EffectiveArtifactRecord describes one concrete artifact after configuration
// resolution and family expansion.
type EffectiveArtifactRecord struct {
Key string
SourceID string
DefinitionSources []string
Enabled bool
OutputPath string
Dependencies []string
FamilyKey string
CharacterID string
FamilySource string
PartySource string
}
// EffectiveArtifactCatalog returns the deterministic concrete artifact catalog
// consumed by runtime planning.
func EffectiveArtifactCatalog(cfg *PipelineConfig, party ResolvedParty) ([]EffectiveArtifactRecord, error) {
if cfg == nil {
return nil, fmt.Errorf("pipeline config is required")
}
if cfg.Scriptorium == nil || len(cfg.Scriptorium.Artifacts) == 0 {
return []EffectiveArtifactRecord{}, nil
}
keys := make([]string, 0, len(cfg.Scriptorium.Artifacts))
for key := range cfg.Scriptorium.Artifacts {
keys = append(keys, key)
}
sort.Strings(keys)
records := make([]EffectiveArtifactRecord, 0, len(keys))
for _, key := range keys {
artifact := cfg.Scriptorium.Artifacts[key]
record := EffectiveArtifactRecord{
Key: key,
SourceID: artifactpolicy.ConfiguredSourceID(key),
Enabled: artifact.Enabled,
OutputPath: artifact.OutputPath,
Dependencies: append([]string(nil), artifact.DependsOn...),
}
if member, ok := cfg.familyCatalog.Members[key]; ok {
record.DefinitionSources = artifactFamilyDefinitionSources(cfg, member.Family)
record.FamilyKey = member.Family
record.CharacterID = member.CharacterID
record.FamilySource = member.Source
record.PartySource = party.Source.Path
} else {
record.DefinitionSources = artifactDefinitionSources(cfg, key)
}
record.Dependencies = nonEmptySortedStrings(record.Dependencies)
records = append(records, record)
}
return records, nil
}
func artifactDefinitionSources(cfg *PipelineConfig, key string) []string {
return declarationDefinitionSources(cfg,
configkit.Keys("scriptorium", "artifacts", key),
"scriptorium.artifacts."+key,
)
}
func artifactFamilyDefinitionSources(cfg *PipelineConfig, key string) []string {
return declarationDefinitionSources(cfg,
configkit.Keys("scriptorium", "artifact_families", key),
"scriptorium.artifact_families."+key,
)
}
func declarationDefinitionSources(cfg *PipelineConfig, declaration configkit.Path, prefix string) []string {
if cfg == nil || cfg.resolution == nil {
return nil
}
pathsByID := make(map[string]string, len(cfg.resolution.configkitSources))
for _, source := range cfg.resolution.configkitSources {
path := source.Path
if path == "" {
path = source.Label
}
pathsByID[source.ID] = path
}
seen := map[string]struct{}{}
for _, change := range cfg.resolution.configkitMetadata.History(declaration) {
if change.Origin.Layer != configkit.LayerBase && change.Origin.Layer != configkit.LayerOverlay {
continue
}
for _, location := range change.Origin.Locations {
source := pathsByID[location.SourceID]
if source == "" {
source = location.SourceID
}
seen[source] = struct{}{}
}
}
if len(seen) == 0 {
for _, ownership := range cfg.resolution.ownership {
if ownership.path != prefix && !strings.HasPrefix(ownership.path, prefix+".") {
continue
}
for _, source := range ownership.sources {
if source != pipelineDefaultOwnershipSource && source != "resolution" {
seen[source] = struct{}{}
}
}
}
}
if len(seen) == 0 && cfg.resolution.rootPath != "" {
seen[cfg.resolution.rootPath] = struct{}{}
}
result := make([]string, 0, len(seen))
for source := range seen {
result = append(result, source)
}
sort.Strings(result)
return result
}
func nonEmptySortedStrings(values []string) []string {
seen := map[string]struct{}{}
for _, value := range values {
value = strings.TrimSpace(value)
if value != "" {
seen[value] = struct{}{}
}
}
result := make([]string, 0, len(seen))
for value := range seen {
result = append(result, value)
}
sort.Strings(result)
return result
}