Add effective artifact catalog command
This commit is contained in:
182
internal/app/config_artifacts_test.go
Normal file
182
internal/app/config_artifacts_test.go
Normal file
@@ -0,0 +1,182 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const artifactCatalogHeader = "key\tsource\tdefinitions\tenabled\toutput\tdependencies\tfamily\tcharacter\tfamily_source\tparty_source\n"
|
||||
|
||||
func TestConfigArtifactsReportsOrdinaryCatalogWithoutCampaign(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
pipelinePath := filepath.Join(dir, "pipeline.yml")
|
||||
mustWriteTestFile(t, pipelinePath, `workspace:
|
||||
root: `+filepath.Join(dir, "workspace")+`
|
||||
whisperx:
|
||||
transcribe_url: https://transcription.example.com/transcribe
|
||||
scriptorium:
|
||||
binary: scriptorium
|
||||
artifacts:
|
||||
zeta:
|
||||
enabled: false
|
||||
output_path: artifacts/zeta.md
|
||||
alpha:
|
||||
enabled: false
|
||||
output_path: artifacts/alpha.md
|
||||
inputs:
|
||||
upstream: {source: narratio.artifact.zeta, required: true}
|
||||
`)
|
||||
|
||||
var out bytes.Buffer
|
||||
if err := ConfigArtifacts(context.Background(), []string{"--config", pipelinePath}, &out); err != nil {
|
||||
t.Fatalf("ConfigArtifacts() error = %v", err)
|
||||
}
|
||||
lines := strings.Split(strings.TrimSuffix(out.String(), "\n"), "\n")
|
||||
if len(lines) != 3 || !strings.HasPrefix(lines[1], "alpha\t") || !strings.HasPrefix(lines[2], "zeta\t") {
|
||||
t.Fatalf("catalog order/output = %q", out.String())
|
||||
}
|
||||
alpha := strings.Split(lines[1], "\t")
|
||||
if len(alpha) != 10 {
|
||||
t.Fatalf("alpha fields = %#v", alpha)
|
||||
}
|
||||
if alpha[1] != "narratio.artifact.alpha" || alpha[2] != pipelinePath || alpha[3] != "false" || alpha[4] != "artifacts/alpha.md" || alpha[5] != "zeta" {
|
||||
t.Fatalf("alpha record = %#v", alpha)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigArtifactsEmptyCatalogWritesOnlyHeader(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
pipelinePath := filepath.Join(dir, "pipeline.yml")
|
||||
mustWriteTestFile(t, pipelinePath, "workspace:\n root: "+filepath.Join(dir, "workspace")+"\nwhisperx:\n transcribe_url: https://transcription.example.com/transcribe\n")
|
||||
var out bytes.Buffer
|
||||
if err := ConfigArtifacts(context.Background(), []string{"--config", pipelinePath}, &out); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if out.String() != artifactCatalogHeader {
|
||||
t.Fatalf("empty catalog = %q, want header only", out.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigArtifactsReportsGeneratedFamilyProvenance(t *testing.T) {
|
||||
pipelinePath, campaignPath := writeInspectionFamilyConfig(t)
|
||||
var out bytes.Buffer
|
||||
if err := ConfigArtifacts(context.Background(), []string{"--config", pipelinePath, "--campaign-file", campaignPath}, &out); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
records := catalogRecordsByKey(t, out.String())
|
||||
meta := records["character_meta_arannis"]
|
||||
note := records["character_note_arannis"]
|
||||
partyPath := filepath.Join(filepath.Dir(campaignPath), "party.yml")
|
||||
if len(meta) != 10 || meta[6] != "character_meta" || meta[7] != "arannis" || meta[8] != pipelinePath || meta[9] != partyPath {
|
||||
t.Fatalf("generated meta record = %#v", meta)
|
||||
}
|
||||
if len(note) != 10 || note[5] != "character_meta_arannis" || note[6] != "character_note" {
|
||||
t.Fatalf("generated note record = %#v", note)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigArtifactsReportsBaseAndProfileContributors(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
pipelinePath := filepath.Join(dir, "pipeline.yml")
|
||||
profilePath := filepath.Join(dir, "profiles", "testing.yml")
|
||||
mustWriteTestFile(t, pipelinePath, `workspace:
|
||||
root: /srv/narratio
|
||||
whisperx:
|
||||
transcribe_url: https://transcription.example.com/transcribe
|
||||
scriptorium:
|
||||
binary: scriptorium
|
||||
artifacts:
|
||||
notes:
|
||||
enabled: false
|
||||
output_path: artifacts/notes.md
|
||||
`)
|
||||
mustWriteTestFile(t, profilePath, `scriptorium:
|
||||
artifacts:
|
||||
notes:
|
||||
enabled: true
|
||||
prompt_id: notes
|
||||
profile_only:
|
||||
enabled: false
|
||||
output_path: artifacts/profile-only.md
|
||||
`)
|
||||
|
||||
var out bytes.Buffer
|
||||
if err := ConfigArtifacts(context.Background(), []string{"--config", pipelinePath, "--profile", "testing"}, &out); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
records := catalogRecordsByKey(t, out.String())
|
||||
if got := strings.Split(records["notes"][2], ","); !reflect.DeepEqual(got, []string{pipelinePath, profilePath}) {
|
||||
t.Fatalf("notes definitions = %#v", got)
|
||||
}
|
||||
if got := records["profile_only"][2]; got != profilePath {
|
||||
t.Fatalf("profile-only definition = %q, want %q", got, profilePath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigArtifactsIdentityDoesNotDependOnDefinitionFilename(t *testing.T) {
|
||||
inlineDir := t.TempDir()
|
||||
inlinePath := filepath.Join(inlineDir, "pipeline.yml")
|
||||
mustWriteTestFile(t, inlinePath, artifactIdentityPipeline("", "recap"))
|
||||
importDir := t.TempDir()
|
||||
importPath := filepath.Join(importDir, "pipeline.yml")
|
||||
mustWriteTestFile(t, importPath, artifactIdentityPipeline("artifacts.yml", ""))
|
||||
mustWriteTestFile(t, filepath.Join(importDir, "artifacts.yml"), "scriptorium:\n artifacts:\n recap:\n enabled: false\n output_path: artifacts/recap.md\n")
|
||||
|
||||
for _, path := range []string{inlinePath, importPath} {
|
||||
var out bytes.Buffer
|
||||
if err := ConfigArtifacts(context.Background(), []string{"--config", path}, &out); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := catalogRecordsByKey(t, out.String())["recap"][1]; got != "narratio.artifact.recap" {
|
||||
t.Fatalf("source identity for %s = %q", path, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigArtifactsPropagatesWriterErrorsAndEscapesFields(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
pipelinePath := filepath.Join(dir, "pipeline.yml")
|
||||
mustWriteTestFile(t, pipelinePath, "workspace:\n root: /srv/narratio\nwhisperx:\n transcribe_url: https://transcription.example.com/transcribe\n")
|
||||
want := errors.New("write rejected")
|
||||
err := ConfigArtifacts(context.Background(), []string{"--config", pipelinePath}, rejectingCatalogWriter{err: want})
|
||||
if !errors.Is(err, want) {
|
||||
t.Fatalf("ConfigArtifacts() error = %v, want writer error", err)
|
||||
}
|
||||
if got := escapeCatalogField("a\\b\tc\nd\r"); got != `a\\b\tc\nd\r` {
|
||||
t.Fatalf("escaped field = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func catalogRecordsByKey(t *testing.T, output string) map[string][]string {
|
||||
t.Helper()
|
||||
lines := strings.Split(strings.TrimSuffix(output, "\n"), "\n")
|
||||
if len(lines) == 0 || lines[0]+"\n" != artifactCatalogHeader {
|
||||
t.Fatalf("catalog header = %q", output)
|
||||
}
|
||||
records := make(map[string][]string, len(lines)-1)
|
||||
for _, line := range lines[1:] {
|
||||
fields := strings.Split(line, "\t")
|
||||
records[fields[0]] = fields
|
||||
}
|
||||
return records
|
||||
}
|
||||
|
||||
func artifactIdentityPipeline(importPath, inlineKey string) string {
|
||||
document := "workspace:\n root: /srv/narratio\nwhisperx:\n transcribe_url: https://transcription.example.com/transcribe\nscriptorium:\n binary: scriptorium\n"
|
||||
if importPath != "" {
|
||||
document = "configkit:\n imports:\n - path: " + importPath + "\n" + document
|
||||
}
|
||||
if inlineKey != "" {
|
||||
document += " artifacts:\n " + inlineKey + ":\n enabled: false\n output_path: artifacts/recap.md\n"
|
||||
}
|
||||
return document
|
||||
}
|
||||
|
||||
type rejectingCatalogWriter struct{ err error }
|
||||
|
||||
func (w rejectingCatalogWriter) Write([]byte) (int, error) { return 0, w.err }
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/config"
|
||||
@@ -36,7 +37,7 @@ func Config(ctx context.Context, args []string, out io.Writer) error {
|
||||
func configWithDependencies(ctx context.Context, args []string, out io.Writer, deps commandDependencies) error {
|
||||
deps = normalizeCommandDependencies(deps)
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("config: expected subcommand: validate|show|sources|diff")
|
||||
return fmt.Errorf("config: expected subcommand: validate|show|sources|artifacts|diff")
|
||||
}
|
||||
if args[0] == "--help" || args[0] == "-h" {
|
||||
writeConfigCommandUsage(out)
|
||||
@@ -49,6 +50,8 @@ func configWithDependencies(ctx context.Context, args []string, out io.Writer, d
|
||||
return configShowWithDependencies(ctx, args[1:], out, deps)
|
||||
case "sources":
|
||||
return configSourcesWithDependencies(ctx, args[1:], out, deps)
|
||||
case "artifacts":
|
||||
return configArtifactsWithDependencies(ctx, args[1:], out, deps)
|
||||
case "diff":
|
||||
return configDiffWithDependencies(ctx, args[1:], out, deps)
|
||||
default:
|
||||
@@ -140,6 +143,70 @@ func configSourcesWithDependencies(_ context.Context, args []string, out io.Writ
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConfigArtifacts validates and reports the effective concrete artifact catalog
|
||||
// without loading session state or runtime collaborators.
|
||||
func ConfigArtifacts(ctx context.Context, args []string, out io.Writer) error {
|
||||
return configArtifactsWithDependencies(ctx, args, out, commandDependencies{})
|
||||
}
|
||||
|
||||
func configArtifactsWithDependencies(_ context.Context, args []string, out io.Writer, deps commandDependencies) error {
|
||||
flags, err := parseInspectionFlags("config artifacts", args, out)
|
||||
if err != nil {
|
||||
if errors.Is(err, flag.ErrHelp) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
resolved, err := resolveInspectionConfigWithDependencies(flags, deps)
|
||||
if err != nil {
|
||||
return fmt.Errorf("config artifacts: %w", err)
|
||||
}
|
||||
records, err := config.EffectiveArtifactCatalog(resolved.Pipeline, resolved.Party)
|
||||
if err != nil {
|
||||
return fmt.Errorf("config artifacts: %w", err)
|
||||
}
|
||||
if _, err := fmt.Fprintln(out, "key\tsource\tdefinitions\tenabled\toutput\tdependencies\tfamily\tcharacter\tfamily_source\tparty_source"); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, record := range records {
|
||||
fields := []string{
|
||||
record.Key,
|
||||
record.SourceID,
|
||||
strings.Join(normalizedCatalogPaths(record.DefinitionSources), ","),
|
||||
strconv.FormatBool(record.Enabled),
|
||||
record.OutputPath,
|
||||
strings.Join(record.Dependencies, ","),
|
||||
record.FamilyKey,
|
||||
record.CharacterID,
|
||||
config.NormalizedConfigurationPath(record.FamilySource),
|
||||
config.NormalizedConfigurationPath(record.PartySource),
|
||||
}
|
||||
for index := range fields {
|
||||
fields[index] = escapeCatalogField(fields[index])
|
||||
}
|
||||
if _, err := fmt.Fprintln(out, strings.Join(fields, "\t")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizedCatalogPaths(paths []string) []string {
|
||||
result := make([]string, len(paths))
|
||||
for index, path := range paths {
|
||||
result[index] = config.NormalizedConfigurationPath(path)
|
||||
}
|
||||
sort.Strings(result)
|
||||
return result
|
||||
}
|
||||
|
||||
func escapeCatalogField(value string) string {
|
||||
value = strings.ReplaceAll(value, "\\", "\\\\")
|
||||
value = strings.ReplaceAll(value, "\t", "\\t")
|
||||
value = strings.ReplaceAll(value, "\r", "\\r")
|
||||
return strings.ReplaceAll(value, "\n", "\\n")
|
||||
}
|
||||
|
||||
func parseInspectionFlags(command string, args []string, out io.Writer) (inspectionFlags, error) {
|
||||
fs := flag.NewFlagSet(command, flag.ContinueOnError)
|
||||
fs.SetOutput(out)
|
||||
@@ -256,9 +323,9 @@ func inspectionProfile(pipeline *config.PipelineConfig) string {
|
||||
}
|
||||
|
||||
func writeConfigCommandUsage(out io.Writer) {
|
||||
fmt.Fprintln(out, "Usage: narratio config <validate|show|sources|diff>")
|
||||
fmt.Fprintln(out, "Usage: narratio config <validate|show|sources|artifacts|diff>")
|
||||
fmt.Fprintln(out)
|
||||
fmt.Fprintln(out, "Use config validate to check an effective pipeline, config show to print normalized YAML, config sources to report ownership, or config diff to compare two profiles.")
|
||||
fmt.Fprintln(out, "Use config validate to check an effective pipeline, config show to print normalized YAML, config sources to report ownership, config artifacts to list concrete artifacts, or config diff to compare two profiles.")
|
||||
}
|
||||
|
||||
func writeInspectionSourceHeader(out io.Writer, resolved *inspectionConfig) error {
|
||||
|
||||
@@ -22,13 +22,14 @@ func TestConfigCommandHelpAndDispatch(t *testing.T) {
|
||||
want string
|
||||
code int
|
||||
}{
|
||||
{name: "top level help", args: []string{"config", "--help"}, want: "Usage: narratio config <validate|show|sources|diff>"},
|
||||
{name: "top level help", args: []string{"config", "--help"}, want: "Usage: narratio config <validate|show|sources|artifacts|diff>"},
|
||||
{name: "validate help", args: []string{"config", "validate", "--help"}, want: "Usage: narratio config validate"},
|
||||
{name: "show help", args: []string{"config", "show", "--help"}, want: "Usage: narratio config show"},
|
||||
{name: "sources help", args: []string{"config", "sources", "--help"}, want: "Usage: narratio config sources"},
|
||||
{name: "artifacts help", args: []string{"config", "artifacts", "--help"}, want: "Usage: narratio config artifacts"},
|
||||
{name: "diff help", args: []string{"config", "diff", "--help"}, want: "Usage: narratio config diff"},
|
||||
{name: "unknown", args: []string{"config", "unknown"}, want: `config: unknown subcommand "unknown"`, code: 1},
|
||||
{name: "missing", args: []string{"config"}, want: "config: expected subcommand: validate|show|sources|diff", code: 1},
|
||||
{name: "missing", args: []string{"config"}, want: "config: expected subcommand: validate|show|sources|artifacts|diff", code: 1},
|
||||
{name: "session flag", args: []string{"config", "validate", "--session", "session.yml"}, want: "flag provided but not defined", code: 1},
|
||||
{name: "stage flag", args: []string{"config", "show", "--from", "prepare"}, want: "flag provided but not defined", code: 1},
|
||||
{name: "force flag", args: []string{"config", "show", "--force"}, want: "flag provided but not defined", code: 1},
|
||||
@@ -53,7 +54,7 @@ func TestExecuteConfigUsesInvocationPipelineLoader(t *testing.T) {
|
||||
pipelinePath := filepath.Join(dir, "pipeline.yml")
|
||||
mustWriteTestFile(t, pipelinePath, "workspace:\n root: "+filepath.Join(dir, "workspace")+"\nwhisperx:\n transcribe_url: https://transcription.example.com/transcribe\n")
|
||||
|
||||
for _, subcommand := range []string{"validate", "show", "sources"} {
|
||||
for _, subcommand := range []string{"validate", "show", "sources", "artifacts"} {
|
||||
t.Run(subcommand, func(t *testing.T) {
|
||||
calls := 0
|
||||
deps := commandDependencies{
|
||||
@@ -336,11 +337,15 @@ func TestConfigInspectionOutputsNeverExposeResolvedCredentialValues(t *testing.T
|
||||
if err := ConfigSources(context.Background(), []string{"--config", pipelinePath, "--profile", "production"}, &sources); err != nil {
|
||||
t.Fatalf("config sources: %v", err)
|
||||
}
|
||||
var catalog bytes.Buffer
|
||||
if err := ConfigArtifacts(context.Background(), []string{"--config", pipelinePath, "--profile", "production"}, &catalog); err != nil {
|
||||
t.Fatalf("config artifacts: %v", err)
|
||||
}
|
||||
var diff bytes.Buffer
|
||||
if err := ConfigDiff(context.Background(), []string{"production", "testing", "--config", pipelinePath}, &diff); err != nil {
|
||||
t.Fatalf("config diff: %v", err)
|
||||
}
|
||||
for label, output := range map[string]string{"show": show.String(), "sources": sources.String(), "diff": diff.String()} {
|
||||
for label, output := range map[string]string{"show": show.String(), "sources": sources.String(), "artifacts": catalog.String(), "diff": diff.String()} {
|
||||
if strings.Contains(output, firstCanary) || strings.Contains(output, secondCanary) {
|
||||
t.Fatalf("%s output exposed credential value: %q", label, output)
|
||||
}
|
||||
@@ -364,7 +369,7 @@ func TestConfigCommandsInspectMaintainedSplitBundleWithoutRuntimeState(t *testin
|
||||
t.Fatalf("example workspace stat = %v, want absent", err)
|
||||
}
|
||||
|
||||
for _, command := range []func(context.Context, []string, io.Writer) error{ConfigValidate, ConfigShow, ConfigSources} {
|
||||
for _, command := range []func(context.Context, []string, io.Writer) error{ConfigValidate, ConfigShow, ConfigSources, ConfigArtifacts} {
|
||||
if err := command(context.Background(), []string{"--config", pipelinePath, "--campaign-file", campaignPath, "--profile", "production"}, io.Discard); err != nil {
|
||||
t.Fatalf("inspection command %T error = %v", command, err)
|
||||
}
|
||||
@@ -423,7 +428,7 @@ func TestConfigCommandsSelectProfilesAndCampaigns(t *testing.T) {
|
||||
func TestConfigCommandsExpandFamiliesAndRequireCampaign(t *testing.T) {
|
||||
pipelinePath, campaignPath := writeInspectionFamilyConfig(t)
|
||||
|
||||
for _, command := range []func(context.Context, []string, io.Writer) error{ConfigValidate, ConfigShow} {
|
||||
for _, command := range []func(context.Context, []string, io.Writer) error{ConfigValidate, ConfigShow, ConfigArtifacts} {
|
||||
var out bytes.Buffer
|
||||
err := command(context.Background(), []string{"--config", pipelinePath}, &out)
|
||||
if err == nil || !strings.Contains(err.Error(), "artifact_families requires a campaign") {
|
||||
|
||||
132
internal/config/artifact_catalog.go
Normal file
132
internal/config/artifact_catalog.go
Normal file
@@ -0,0 +1,132 @@
|
||||
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 = nonEmptySortedStrings([]string{member.Source})
|
||||
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 {
|
||||
if 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{}{}
|
||||
declaration := configkit.Keys("scriptorium", "artifacts", key)
|
||||
for _, change := range cfg.resolution.configkitMetadata.History(declaration) {
|
||||
if change.Origin.Layer != configkit.LayerBase && change.Origin.Layer != configkit.LayerProfile {
|
||||
continue
|
||||
}
|
||||
for _, location := range change.Origin.Locations {
|
||||
source := pathsByID[location.SourceID]
|
||||
if source == "" {
|
||||
source = location.SourceID
|
||||
}
|
||||
seen[source] = struct{}{}
|
||||
}
|
||||
}
|
||||
if len(seen) == 0 {
|
||||
prefix := "scriptorium.artifacts." + key
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user