Rename selected configuration profiles to overlays

This commit is contained in:
2026-09-23 18:27:29 +00:00
parent 66e080ed34
commit ae91f57bbe
32 changed files with 306 additions and 283 deletions

View File

@@ -181,24 +181,24 @@ func nodeImportDiagnostic(code Code, path Path, message string, node *document.N
return Diagnostic{Code: code, Severity: SeverityError, Path: path, Message: message, Primary: &location}
}
func profileDirectiveDiagnostics(node *document.Node, current Path) []Diagnostic {
func overlayDirectiveDiagnostics(node *document.Node, current Path) []Diagnostic {
if node.Kind == document.Mapping {
for _, entry := range node.Mapping {
childPath := current.Key(entry.Key)
if entry.Key == directiveKey {
location := publicLocation(entry.KeyLocation)
return []Diagnostic{{
Code: CodeProfileDirective, Severity: SeverityError, Path: childPath,
Message: "profiles cannot contain configuration directives", Primary: &location,
Code: CodeOverlayDirective, Severity: SeverityError, Path: childPath,
Message: "overlays cannot contain configuration directives", Primary: &location,
}}
}
if diagnostics := profileDirectiveDiagnostics(entry.Value, childPath); len(diagnostics) != 0 {
if diagnostics := overlayDirectiveDiagnostics(entry.Value, childPath); len(diagnostics) != 0 {
return diagnostics
}
}
} else if node.Kind == document.Sequence {
for index, child := range node.Sequence {
if diagnostics := profileDirectiveDiagnostics(child, current.Index(index)); len(diagnostics) != 0 {
if diagnostics := overlayDirectiveDiagnostics(child, current.Index(index)); len(diagnostics) != 0 {
return diagnostics
}
}

View File

@@ -38,7 +38,7 @@ func (r *Inspection) WriteExplanation(output io.Writer, path Path) error {
rendered.WriteString(" - representation: ")
if change.Origin.Operation == ChangeDecoding {
rendered.WriteString("input to canonical typed\n")
} else if change.Origin.Layer == LayerBase || change.Origin.Layer == LayerProfile || change.Origin.Layer == LayerCLI {
} else if change.Origin.Layer == LayerBase || change.Origin.Layer == LayerOverlay || change.Origin.Layer == LayerCLI {
rendered.WriteString("policy-validated input\n")
} else {
rendered.WriteString("canonical typed\n")

View File

@@ -21,13 +21,13 @@ type explanationConcurrency struct {
} `yaml:"transcription"`
}
func TestExplainShowsDefaultBaseAndEffectiveProfileValue(t *testing.T) {
func TestExplainShowsDefaultBaseAndEffectiveOverlayValue(t *testing.T) {
directory := t.TempDir()
mainPath := writeImportFile(t, directory, "config.yml", "configkit:\n imports: [{path: transcription.yml}]\n")
writeImportFile(t, directory, "transcription.yml", "transcription:\n concurrency: 8\n")
writeImportFile(t, directory, filepath.Join("profiles", "testing.yml"), "transcription:\n concurrency: 1\n")
writeImportFile(t, directory, filepath.Join("overlays", "testing.yml"), "transcription:\n concurrency: 1\n")
result, err := testLoad[explanationConcurrency](File(mainPath), Options[explanationConcurrency]{
Imports: true, Profile: "testing",
Imports: true, Overlay: "testing",
Definition: Definition[explanationConcurrency]{Defaults: func() explanationConcurrency {
value := explanationConcurrency{}
value.Transcription.Concurrency = 4
@@ -44,7 +44,7 @@ func TestExplainShowsDefaultBaseAndEffectiveProfileValue(t *testing.T) {
if got := explanationValues(explanation); !reflect.DeepEqual(got, []string{"4\n", "8\n", "1\n"}) {
t.Fatalf("history values = %#v", got)
}
if explanation.Effective.YAML != "1\n" || explanation.Origin.Layer != LayerProfile ||
if explanation.Effective.YAML != "1\n" || explanation.Origin.Layer != LayerOverlay ||
len(explanation.Origin.Locations) != 1 || explanation.Origin.Locations[0].SourceID != "source-3" ||
explanation.Origin.Locations[0].Line != 2 {
t.Fatalf("effective explanation = %#v", explanation)
@@ -70,18 +70,18 @@ func TestExplainReplaysCLIResolutionContainersAndSafeHistoricalValues(t *testing
directory := t.TempDir()
mainPath := writeImportFile(t, directory, "config.yml", `concurrency: 8
equal: same
limit: wrong-before-profile
limit: wrong-before-overlay
items: [base, removed]
labels: {base: retained}
dynamic: {retired: HISTORICAL_EXPLAIN_CANARY, public: visible}
credential: APP_SECRET
classified: APP_SECRET
`)
writeImportFile(t, directory, filepath.Join("profiles", "testing.yml"), `concurrency: 1
writeImportFile(t, directory, filepath.Join("overlays", "testing.yml"), `concurrency: 1
equal: same
limit: 7
items: [profile]
labels: {profile: retained}
items: [overlay]
labels: {overlay: retained}
dynamic: selected
`)
result, err := testLoadWithSecretLookup(File(mainPath), func(name string) (string, bool) {
@@ -90,7 +90,7 @@ dynamic: selected
}
return "RESOLVED_EXPLAIN_CANARY", true
}, Options[explanationConfig]{
Profile: "testing",
Overlay: "testing",
Overrides: []Override{
{Path: Keys("concurrency"), Value: 2, Flag: "--concurrency"},
{Path: Keys("equal"), Value: "same", Flag: "--equal"},
@@ -130,7 +130,7 @@ dynamic: selected
if got := explanationValues(concurrency); !reflect.DeepEqual(got, []string{"4\n", "8\n", "1\n", "2\n", "3\n"}) {
t.Fatalf("concurrency history = %#v", got)
}
if got := historyValueLayers(concurrency); !reflect.DeepEqual(got, []Layer{LayerDefault, LayerBase, LayerProfile, LayerCLI, LayerResolution}) {
if got := historyValueLayers(concurrency); !reflect.DeepEqual(got, []Layer{LayerDefault, LayerBase, LayerOverlay, LayerCLI, LayerResolution}) {
t.Fatalf("concurrency layers = %#v", got)
}
if concurrency.History[4].Before.YAML != "2\n" || concurrency.History[4].Reasons[0] != "normalized\nconcurrency" ||
@@ -149,7 +149,7 @@ dynamic: selected
}
limit, _ := result.Explain(Keys("limit"))
if got := explanationValues(limit); !reflect.DeepEqual(got, []string{"0\n", "wrong-before-profile\n", "7\n"}) {
if got := explanationValues(limit); !reflect.DeepEqual(got, []string{"0\n", "wrong-before-overlay\n", "7\n"}) {
t.Fatalf("overridden invalid input was not retained: %#v", got)
}
removed, ok := result.Explain(Keys("dynamic", "retired"))
@@ -182,7 +182,7 @@ dynamic: selected
}
labels, _ := result.Explain(Keys("labels"))
if len(labels.History) != 4 || !strings.Contains(labels.Effective.YAML, "default: retained") ||
!strings.Contains(labels.Effective.YAML, "profile: retained") || !strings.Contains(labels.Effective.YAML, "cli: retained") {
!strings.Contains(labels.Effective.YAML, "overlay: retained") || !strings.Contains(labels.Effective.YAML, "cli: retained") {
t.Fatalf("composite mapping history = %#v", labels)
}
if labels.Origin.Layer != LayerCLI || len(labels.Origin.Locations) != 3 {
@@ -258,7 +258,7 @@ dynamic: selected
var invalidInput bytes.Buffer
if err := result.WriteExplanation(&invalidInput, Keys("limit")); err != nil ||
!strings.Contains(invalidInput.String(), "policy-validated input") ||
strings.Contains(invalidInput.String(), "wrong-before-profile\n source: layer=\"base\"\n before (canonical typed)") {
strings.Contains(invalidInput.String(), "wrong-before-overlay\n source: layer=\"base\"\n before (canonical typed)") {
t.Fatalf("historical representation rendering = %v\n%s", err, invalidInput.String())
}
}
@@ -340,8 +340,8 @@ func TestExplanationReconcilesDefaultsAfterInputReplacement(t *testing.T) {
if cli {
opts.Overrides = []Override{{Path: Keys("m"), Value: map[string]any{"b": 2}, Flag: "--m"}}
} else {
writeImportFile(t, root, "profiles/p.yml", "m: {b: 2}\n")
opts.Profile = "p"
writeImportFile(t, root, "overlays/p.yml", "m: {b: 2}\n")
opts.Overlay = "p"
}
result, err := testLoad(File(main), opts)
if err != nil {

View File

@@ -229,11 +229,11 @@ func TestGlobTraversalBoundsNonmatchesAndAcceptedCandidates(t *testing.T) {
}
}
func TestFilesystemContextIsValidatedWithoutImportsOrActiveProfile(t *testing.T) {
func TestFilesystemContextIsValidatedWithoutImportsOrActiveOverlay(t *testing.T) {
directory := t.TempDir()
mainPath := writeImportFile(t, directory, "main.yml", "items: {root: root}\n")
result, err := testLoad[importTestConfig](File(mainPath), Options[importTestConfig]{
Profile: "default", Filesystem: &FilesystemContext{Root: directory},
Overlay: "default", Filesystem: &FilesystemContext{Root: directory},
})
if err != nil || result.Config.Items["root"] != "root" {
t.Fatalf("file filesystem context = %#v, %v", result, err)
@@ -247,9 +247,9 @@ func TestFilesystemContextIsValidatedWithoutImportsOrActiveProfile(t *testing.T)
t.Fatalf("byte filesystem context = %#v, %v", result, err)
}
requireLoadCode(t, Bytes("memory.yml", []byte("{}\n")), Options[importTestConfig]{Filesystem: &FilesystemContext{BaseDir: directory}}, CodeOptionsInvalid)
defaultsOnly, err := testLoad[importTestConfig](NoSource(), Options[importTestConfig]{Profile: "default"})
defaultsOnly, err := testLoad[importTestConfig](NoSource(), Options[importTestConfig]{Overlay: "default"})
if err != nil || defaultsOnly == nil {
t.Fatalf("default profile with NoSource = %#v, %v", defaultsOnly, err)
t.Fatalf("default overlay with NoSource = %#v, %v", defaultsOnly, err)
}
if runtime.GOOS == "linux" || runtime.GOOS == "darwin" {

View File

@@ -19,17 +19,17 @@ type Definition[T any] struct {
}
// Options contains the application definition and per-load input selections.
// Imports enables explicit path and glob imports; Profile selects one fixed profile
// overlay; Overrides contains only values explicitly supplied by an application CLI.
// Imports enables explicit path and glob imports; Overlay selects one fixed overlay;
// Overrides contains only values explicitly supplied by an application CLI.
type Options[T any] struct {
Definition Definition[T]
Imports bool
Profile string
Overlay string
Filesystem *FilesystemContext
Overrides []Override
}
// FilesystemContext supplies filesystem roots for imports and profiles.
// FilesystemContext supplies filesystem roots for imports and overlays.
type FilesystemContext struct {
BaseDir string
Root string
@@ -113,7 +113,7 @@ func (r *Inspection) Diagnostics() []Diagnostic {
}
// Load parses and strictly decodes zero or one selected source, explicitly enabled
// imports, and one selected profile. It accepts at most one Options value and
// imports, and one selected overlay. It accepts at most one Options value and
// returns no result when any error is reported.
func Load[T any](source Source, secrets SecretBinding, supplied ...Options[T]) (*Loaded[T], error) {
return loadWithSecretLookup(source, secrets, os.LookupEnv, supplied...)

View File

@@ -25,7 +25,7 @@ func assembleBaseInput[T any](source Source, options Options[T]) (assembledInput
input.document = input.composition.root
} else {
input.sources = sourceInfos(parsed.source)
if options.Filesystem != nil && !activeProfile(options.Profile) {
if options.Filesystem != nil && !activeOverlay(options.Overlay) {
if diagnostic := validateFilesystemContext(source, options.Filesystem, parsed); diagnostic != nil {
return input, []Diagnostic{*diagnostic}
}
@@ -40,17 +40,17 @@ func assembleBaseInput[T any](source Source, options Options[T]) (assembledInput
}
func applyInputLayers[T any](source Source, options Options[T], input assembledInput) (assembledInput, []Diagnostic) {
if !activeProfile(options.Profile) && len(options.Overrides) == 0 {
if !activeOverlay(options.Overlay) && len(options.Overrides) == 0 {
return input, nil
}
composition, diagnostics := compositionForInput(input)
if len(diagnostics) != 0 {
return input, diagnostics
}
if activeProfile(options.Profile) {
composition, input.sources, diagnostics = loadProfileDocument(
if activeOverlay(options.Overlay) {
composition, input.sources, diagnostics = loadSelectedOverlayDocument(
source, options.Filesystem, input.parsed, composition, input.sources,
options.Profile, options.Definition.Version, defaultImportLimits(),
options.Overlay, options.Definition.Version, defaultImportLimits(),
)
if len(diagnostics) != 0 {
return input, diagnostics
@@ -69,7 +69,7 @@ func applyInputLayers[T any](source Source, options Options[T], input assembledI
return input, nil
}
// compositionForInput initializes one handoff for profiles and CLI, including
// compositionForInput initializes one handoff for overlays and CLI, including
// defaults-only CLI input. Raw source bytes and mapping keys retain their budgets.
func compositionForInput(input assembledInput) (*composedDocument, []Diagnostic) {
if input.composition != nil {

View File

@@ -60,8 +60,8 @@ func TestHistoricalPathsExposeHiddenBaseOwnershipWithoutResolvingBaseSecrets(t *
root := writeImportFile(t, dir, "main.yml", "configkit:\n imports: [{path: first.yml}, {path: second.yml}]\ncredential: CONFIGKIT_HISTORY_UNUSED_PROD_KEY\n")
writeImportFile(t, dir, "first.yml", "definitions: {recap: {prompt: recap}}\n")
writeImportFile(t, dir, "second.yml", "definitions: {recap: {output: recap.md}}\n")
writeImportFile(t, dir, "profiles/testing.yml", "definitions: null\ncredential: "+credential+"\n")
result, err := testLoad[config](File(root), Options[config]{Imports: true, Profile: "testing"})
writeImportFile(t, dir, "overlays/testing.yml", "definitions: null\ncredential: "+credential+"\n")
result, err := testLoad[config](File(root), Options[config]{Imports: true, Overlay: "testing"})
if err != nil {
t.Fatal(err)
}

View File

@@ -10,8 +10,8 @@ func selectOptions[T any](source Source, supplied []Options[T]) (Options[T], []D
}
var diagnostics []Diagnostic
definition := options.Definition
if !validProfileName(options.Profile) {
diagnostics = append(diagnostics, optionDiagnostic(CodeOptionsInvalid, "profile name must contain only ASCII letters, digits, underscore, or hyphen"))
if !validOverlayName(options.Overlay) {
diagnostics = append(diagnostics, optionDiagnostic(CodeOptionsInvalid, "overlay name must contain only ASCII letters, digits, underscore, or hyphen"))
}
if options.Filesystem != nil {
diagnostics = append(diagnostics, filesystemOptionDiagnostics(source, options.Filesystem)...)
@@ -19,11 +19,11 @@ func selectOptions[T any](source Source, supplied []Options[T]) (Options[T], []D
if source.kind == sourceNone && options.Imports {
diagnostics = append(diagnostics, optionDiagnostic(CodeOptionsInvalid, "NoSource cannot enable imports"))
}
if source.kind == sourceNone && options.Profile != "" && options.Profile != "default" {
diagnostics = append(diagnostics, optionDiagnostic(CodeOptionsInvalid, "NoSource cannot select an active profile"))
if source.kind == sourceNone && options.Overlay != "" && options.Overlay != "default" {
diagnostics = append(diagnostics, optionDiagnostic(CodeOptionsInvalid, "NoSource cannot select an active overlay"))
}
if options.Filesystem == nil && (source.kind == sourceBytes || source.kind == sourceReader) && (options.Imports || activeProfile(options.Profile)) {
diagnostics = append(diagnostics, optionDiagnostic(CodeOptionsInvalid, "byte and reader imports or profiles require filesystem context"))
if options.Filesystem == nil && (source.kind == sourceBytes || source.kind == sourceReader) && (options.Imports || activeOverlay(options.Overlay)) {
diagnostics = append(diagnostics, optionDiagnostic(CodeOptionsInvalid, "byte and reader imports or overlays require filesystem context"))
}
if definition.Version != nil {
if !definition.Version.Path.valid() {

View File

@@ -20,14 +20,14 @@ type displacedDefinition struct {
func overlayDocuments(base *composedDocument, overlay *document.Node) (*composedDocument, []Diagnostic) {
if base == nil || base.root == nil || base.root.Kind != document.Mapping || overlay == nil || overlay.Kind != document.Mapping {
return nil, []Diagnostic{{
Code: CodeProfileInvalid, Severity: SeverityError,
Message: "profile overlay requires mapping document roots",
Code: CodeOverlayInvalid, Severity: SeverityError,
Message: "overlay overlay requires mapping document roots",
}}
}
result := cloneComposedDocument(base)
mergeOverlayOrigin(result.origins, Path{}, overlay.Location, LayerProfile)
mergeOverlayMappings(result, result.root, overlay, Path{}, LayerProfile, true)
result.applications = append(result.applications, historyApplication{layer: LayerProfile, value: overlay})
mergeOverlayOrigin(result.origins, Path{}, overlay.Location, LayerOverlay)
mergeOverlayMappings(result, result.root, overlay, Path{}, LayerOverlay, true)
result.applications = append(result.applications, historyApplication{layer: LayerOverlay, value: overlay})
return result, nil
}

View File

@@ -22,7 +22,7 @@ to_scalar: {child: old}
}
overlay := parseCompositionDocument(t, "source-2", `shared:
changed: new
added: profile
added: overlay
sequence: []
nullable: null
to_map: {child: new}
@@ -42,7 +42,7 @@ to_scalar: scalar
}{
{path: Keys("shared", "kept"), kind: document.String, text: "base"},
{path: Keys("shared", "changed"), kind: document.String, text: "new"},
{path: Keys("shared", "added"), kind: document.String, text: "profile"},
{path: Keys("shared", "added"), kind: document.String, text: "overlay"},
{path: Keys("sequence"), kind: document.Sequence},
{path: Keys("nullable"), kind: document.Null},
{path: Keys("to_map"), kind: document.Mapping},
@@ -62,7 +62,7 @@ to_scalar: scalar
t.Fatal("parent replacement retained a stale child origin")
}
shared := result.origins["/shared"]
if shared.Layer != LayerProfile || len(shared.Locations) != 2 {
if shared.Layer != LayerOverlay || len(shared.Locations) != 2 {
t.Fatalf("shared mapping origin = %#v", shared)
}
if len(result.overlays) != 5 {

View File

@@ -41,9 +41,9 @@ func TestOverridesRunAtHighestInputPrecedenceBeforeResolutionAndValidation(t *te
labels: {base: retained}
api_key: BASE_SECRET
`)
writeImportFile(t, directory, filepath.Join("profiles", "testing.yml"), `settings:
target: profile
labels: {profile: retained}
writeImportFile(t, directory, filepath.Join("overlays", "testing.yml"), `settings:
target: overlay
labels: {overlay: retained}
`)
validated := false
overrides := []Override{
@@ -62,7 +62,7 @@ func TestOverridesRunAtHighestInputPrecedenceBeforeResolutionAndValidation(t *te
}
return "credential-value", true
}, Options[overrideLifecycleConfig]{
Imports: true, Profile: "testing", Overrides: overrides,
Imports: true, Overlay: "testing", Overrides: overrides,
Definition: Definition[overrideLifecycleConfig]{
Defaults: func() overrideLifecycleConfig {
return overrideLifecycleConfig{Settings: overrideLifecycleSettings{Target: "default", Count: 9, Text: "default", Items: []string{"default"}}}
@@ -86,7 +86,7 @@ func TestOverridesRunAtHighestInputPrecedenceBeforeResolutionAndValidation(t *te
t.Fatalf("testLoadWithSecretLookup() error = %v", err)
}
want := overrideLifecycleSettings{
Target: "cli", Labels: map[string]string{"base": "retained", "profile": "retained", "cli": "selected"},
Target: "cli", Labels: map[string]string{"base": "retained", "overlay": "retained", "cli": "selected"},
APIKey: result.Config.Settings.APIKey,
}
if !validated || result.Config.Settings.Count != 0 || result.Config.Settings.Enabled || result.Config.Settings.Text != "" ||
@@ -95,7 +95,7 @@ func TestOverridesRunAtHighestInputPrecedenceBeforeResolutionAndValidation(t *te
t.Fatalf("effective settings = %#v", result.Config.Settings)
}
sources := result.Sources()
if len(sources) != 3+len(overrides) || sources[0].Layer != LayerBase || sources[1].Layer != LayerBase || sources[2].Layer != LayerProfile {
if len(sources) != 3+len(overrides) || sources[0].Layer != LayerBase || sources[1].Layer != LayerBase || sources[2].Layer != LayerOverlay {
t.Fatalf("sources = %#v", sources)
}
for index, override := range overrides {
@@ -104,7 +104,7 @@ func TestOverridesRunAtHighestInputPrecedenceBeforeResolutionAndValidation(t *te
}
}
history := result.Metadata().History(Keys("settings", "target"))
wantLayers := []Layer{LayerDefault, LayerBase, LayerProfile, LayerCLI, LayerResolution}
wantLayers := []Layer{LayerDefault, LayerBase, LayerOverlay, LayerCLI, LayerResolution}
if got := historyLayers(history); !reflect.DeepEqual(got, wantLayers) {
t.Fatalf("target history layers = %#v, want %#v; history %#v", got, wantLayers, history)
}

View File

@@ -16,7 +16,7 @@ type layeredConfig struct {
Shared struct {
Equal string `yaml:"equal"`
Imported string `yaml:"imported"`
Profile string `yaml:"profile"`
Overlay string `yaml:"overlay"`
} `yaml:"shared"`
Values []int `yaml:"values"`
Items map[string]layeredItem `yaml:"items"`
@@ -40,7 +40,7 @@ nullable: base
empty: {}
`)
importPath := writeImportFile(t, directory, "imported.yml", "shared: {imported: base}\n")
profilePath := writeImportFile(t, directory, "profiles/testing.yml", `shared: {profile: selected}
overlayPath := writeImportFile(t, directory, "overlays/testing.yml", `shared: {overlay: selected}
values: [3, 4]
value: selected
nullable: null
@@ -71,23 +71,23 @@ empty: {}
}
result, err := testLoad[layeredConfig](File(mainPath), Options[layeredConfig]{
Imports: true, Profile: "testing", Definition: definition,
Imports: true, Overlay: "testing", Definition: definition,
})
if err != nil {
var loadError *LoadError
_ = errors.As(err, &loadError)
t.Fatalf("%v: %#v", err, loadError.Diagnostics())
}
wantSources := []string{canonicalTestPath(t, mainPath), canonicalTestPath(t, importPath), canonicalTestPath(t, profilePath)}
wantSources := []string{canonicalTestPath(t, mainPath), canonicalTestPath(t, importPath), canonicalTestPath(t, overlayPath)}
sources := result.Sources()
gotSources := make([]string, len(sources))
for index, source := range sources {
gotSources[index] = source.Path
}
if !reflect.DeepEqual(gotSources, wantSources) || sources[2].Layer != LayerProfile {
if !reflect.DeepEqual(gotSources, wantSources) || sources[2].Layer != LayerOverlay {
t.Fatalf("sources = %#v", sources)
}
if result.Config.Shared.Equal != "same" || result.Config.Shared.Imported != "base" || result.Config.Shared.Profile != "selected" ||
if result.Config.Shared.Equal != "same" || result.Config.Shared.Imported != "base" || result.Config.Shared.Overlay != "selected" ||
!reflect.DeepEqual(result.Config.Values, []int{5}) || result.Config.Nullable != nil || result.Config.Derived != 7 ||
result.Config.Empty["retained"] != "default" {
t.Fatalf("configuration = %#v", result.Config)
@@ -100,13 +100,13 @@ empty: {}
empty, _ := metadata.Lookup(Keys("empty"))
firstValue, _ := metadata.Lookup(Keys("values").Index(0))
if !equal.Explicit || equal.Origin.Layer != LayerBase || derived.Explicit || derived.Origin.Operation != ChangeDerivedDefault ||
!nullable.Explicit || !nullable.Null || nullable.Origin.Layer != LayerProfile ||
!empty.Explicit || empty.Origin.Layer != LayerProfile || len(empty.Origin.Locations) != 2 ||
!nullable.Explicit || !nullable.Null || nullable.Origin.Layer != LayerOverlay ||
!empty.Explicit || empty.Origin.Layer != LayerOverlay || len(empty.Origin.Locations) != 2 ||
!firstValue.Explicit || firstValue.Origin.Operation != ChangeNormalization {
t.Fatalf("metadata = equal %#v derived %#v nullable %#v empty %#v value %#v", equal, derived, nullable, empty, firstValue)
}
if _, ok := metadata.Lookup(Keys("value", "child")); ok {
t.Fatal("profile-removed child remains effective")
t.Fatal("overlay-removed child remains effective")
}
if _, ok := metadata.Lookup(Keys("values").Index(1)); ok {
t.Fatal("resolution-removed sequence index remains effective")
@@ -116,10 +116,10 @@ empty: {}
}
assertHistoryLayersAndPaths(t, metadata.History(Keys("values").Index(1)),
[]Layer{LayerDefault, LayerBase, LayerProfile, LayerResolution},
[]Layer{LayerDefault, LayerBase, LayerOverlay, LayerResolution},
[]string{"/values/1", "/values/1", "/values", "/values"})
assertHistoryLayersAndPaths(t, metadata.History(Keys("value", "child")),
[]Layer{LayerDefault, LayerBase, LayerProfile},
[]Layer{LayerDefault, LayerBase, LayerOverlay},
[]string{"/value/child", "/value/child", "/value"})
removedHistory := metadata.History(Keys("items", "remove", "value"))
assertHistoryLayersAndPaths(t, removedHistory,
@@ -159,11 +159,11 @@ empty: {}
}
}
func TestSelectedProfileLoadsRemainIndependentConcurrently(t *testing.T) {
func TestSelectedOverlayLoadsRemainIndependentConcurrently(t *testing.T) {
directory := t.TempDir()
mainPath := writeImportFile(t, directory, "main.yml", "values: [1, 2]\n")
writeImportFile(t, directory, "profiles/testing.yml", "values: [3, 4]\n")
options := Options[layeredConfig]{Profile: "testing"}
writeImportFile(t, directory, "overlays/testing.yml", "values: [3, 4]\n")
options := Options[layeredConfig]{Overlay: "testing"}
const loads = 16
results := make(chan *testResult[layeredConfig], loads)
@@ -200,7 +200,7 @@ func TestSelectedProfileLoadsRemainIndependentConcurrently(t *testing.T) {
if !reflect.DeepEqual(result.Config.Values, []int{3, 4}) {
t.Fatalf("concurrent result contaminated = %#v", result.Config.Values)
}
if info, ok := result.Metadata().Lookup(Keys("values")); !ok || info.Origin.Layer != LayerProfile {
if info, ok := result.Metadata().Lookup(Keys("values")); !ok || info.Origin.Layer != LayerOverlay {
t.Fatalf("concurrent metadata contaminated = %#v, %v", info, ok)
}
}
@@ -220,7 +220,7 @@ sequence: [first, second]
composite: {first: first}
`)
secondPath := writeImportFile(t, directory, "second.yml", "composite: {second: second}\n")
profilePath := writeImportFile(t, directory, "profiles/testing.yml", "equal: same\nprofile: profile\n")
overlayPath := writeImportFile(t, directory, "overlays/testing.yml", "equal: same\noverlay: overlay\n")
compositeSources := map[string]bool{
canonicalTestPath(t, mainPath): true,
canonicalTestPath(t, firstPath): true,
@@ -232,12 +232,12 @@ composite: {first: first}
Sequence []string `yaml:"sequence"`
Composite map[string]string `yaml:"composite"`
Equal string `yaml:"equal"`
Profile string `yaml:"profile"`
Overlay string `yaml:"overlay"`
CLIEqual string `yaml:"cli_equal"`
}
var callbackErr error
result, err := testLoad[config](File(mainPath), Options[config]{
Imports: true, Profile: "testing", Overrides: []Override{{Path: Keys("cli_equal"), Value: "same", Flag: "--cli-equal"}},
Imports: true, Overlay: "testing", Overrides: []Override{{Path: Keys("cli_equal"), Value: "same", Flag: "--cli-equal"}},
Definition: Definition[config]{Resolve: func(_ *config, resolution *Resolution) error {
check := func(ok bool, message string) {
if !ok && callbackErr == nil {
@@ -257,9 +257,9 @@ composite: {first: first}
fileOwner(Keys("main"), canonicalTestPath(t, mainPath))
fileOwner(Keys("imported"), canonicalTestPath(t, firstPath))
fileOwner(Keys("sequence").Index(1), canonicalTestPath(t, firstPath))
profile := fileOwner(Keys("equal"), canonicalTestPath(t, profilePath))
fileOwner(Keys("profile"), canonicalTestPath(t, profilePath))
check(profile.Origin.Layer == LayerProfile, "profile replacement retained base ownership")
overlay := fileOwner(Keys("equal"), canonicalTestPath(t, overlayPath))
fileOwner(Keys("overlay"), canonicalTestPath(t, overlayPath))
check(overlay.Origin.Layer == LayerOverlay, "overlay replacement retained base ownership")
cli, found := resolution.InputInfo(Keys("cli_equal"))
check(found && cli.Origin.Layer == LayerCLI && len(cli.Origin.Locations) == 1, "CLI replacement lost ownership")
if found && len(cli.Origin.Locations) == 1 {
@@ -298,7 +298,7 @@ composite: {first: first}
t.Fatal(err)
}
if result.Config.Main != "main" || result.Config.Imported != "imported" || !reflect.DeepEqual(result.Config.Sequence, []string{"first", "second"}) ||
result.Config.Equal != "same" || result.Config.Profile != "profile" || result.Config.CLIEqual != "same" {
result.Config.Equal != "same" || result.Config.Overlay != "overlay" || result.Config.CLIEqual != "same" {
t.Fatalf("configuration = %#v", result.Config)
}

View File

@@ -6,7 +6,7 @@ type Layer string
const (
LayerDefault Layer = "default"
LayerBase Layer = "base"
LayerProfile Layer = "profile"
LayerOverlay Layer = "overlay"
LayerCLI Layer = "cli"
LayerResolution Layer = "resolution"
)
@@ -90,13 +90,13 @@ const (
CodeImportLimitExceeded Code = "import.limit_exceeded"
CodeImportVersion Code = "import.version"
CodeProfileInvalid Code = "profile.invalid"
CodeProfileMissing Code = "profile.missing"
CodeProfileOutsideRoot Code = "profile.outside_root"
CodeProfileRepeated Code = "profile.repeated"
CodeProfileDirective Code = "profile.directive"
CodeProfileVersion Code = "profile.version"
CodeProfileLimitExceeded Code = "profile.limit_exceeded"
CodeOverlayInvalid Code = "overlay.invalid"
CodeOverlayMissing Code = "overlay.missing"
CodeOverlayOutsideRoot Code = "overlay.outside_root"
CodeOverlayRepeated Code = "overlay.repeated"
CodeOverlayDirective Code = "overlay.directive"
CodeOverlayVersion Code = "overlay.version"
CodeOverlayLimitExceeded Code = "overlay.limit_exceeded"
CodeComposeInvalid Code = "compose.invalid"
CodeComposeConflict Code = "compose.conflict"

View File

@@ -71,7 +71,7 @@ empty_sequence: []
labels: {hidden: DYNAMIC_CANARY_6a12, public: visible}
sequence: [second, first]
`)
writeImportFile(t, directory, "profiles/testing.yml", "dynamic: selected\n")
writeImportFile(t, directory, "overlays/testing.yml", "dynamic: selected\n")
definition := Definition[renderConfig]{
Secret: func(path Path) bool {
return path.String() == "/labels/hidden" || path.String() == "/dynamic/retired"
@@ -85,7 +85,7 @@ sequence: [second, first]
return nil
},
}
result, err := testLoad[renderConfig](File(mainPath), Options[renderConfig]{Definition: definition, Profile: "testing"})
result, err := testLoad[renderConfig](File(mainPath), Options[renderConfig]{Definition: definition, Overlay: "testing"})
if err != nil {
t.Fatal(err)
}

View File

@@ -12,12 +12,12 @@ import (
yamladapter "gitea.maximumdirect.net/eric/ConfigKit/internal/yaml"
)
func activeProfile(name string) bool {
func activeOverlay(name string) bool {
return name != "" && name != "default"
}
func validProfileName(name string) bool {
if !activeProfile(name) {
func validOverlayName(name string) bool {
if !activeOverlay(name) {
return true
}
for _, character := range name {
@@ -31,38 +31,38 @@ func validProfileName(name string) bool {
return true
}
func loadProfileDocument(source Source, context *FilesystemContext, main parsedSource, base *composedDocument, sources []SourceInfo, name string, rule *VersionRule, limits importLimits) (*composedDocument, []SourceInfo, []Diagnostic) {
func loadSelectedOverlayDocument(source Source, context *FilesystemContext, main parsedSource, base *composedDocument, sources []SourceInfo, name string, rule *VersionRule, limits importLimits) (*composedDocument, []SourceInfo, []Diagnostic) {
rootPath, _, _, diagnostic := importFilesystem(source, context, main)
if diagnostic != nil {
return nil, cloneSourceInfos(sources), []Diagnostic{*diagnostic}
}
profilePath := filepath.Join(rootPath, "profiles", name+".yml")
canonical, err := canonicalExistingPath(profilePath)
overlayPath := filepath.Join(rootPath, "overlays", name+".yml")
canonical, err := canonicalExistingPath(overlayPath)
if err != nil {
code := CodeProfileInvalid
message := "selected profile could not be resolved"
code := CodeOverlayInvalid
message := "selected overlay could not be resolved"
if errors.Is(err, fs.ErrNotExist) {
code, message = CodeProfileMissing, "selected profile does not exist"
code, message = CodeOverlayMissing, "selected overlay does not exist"
} else if errors.Is(err, fs.ErrPermission) {
code, message = CodeImportPermission, "selected profile cannot be accessed"
code, message = CodeImportPermission, "selected overlay cannot be accessed"
}
return nil, cloneSourceInfos(sources), []Diagnostic{importDiagnostic(code, message, nil)}
}
if !containedPath(rootPath, canonical) {
return nil, cloneSourceInfos(sources), []Diagnostic{importDiagnostic(CodeProfileOutsideRoot, "selected profile resolves outside the configuration root", nil)}
return nil, cloneSourceInfos(sources), []Diagnostic{importDiagnostic(CodeOverlayOutsideRoot, "selected overlay resolves outside the configuration root", nil)}
}
for _, existing := range sources {
if existing.Path != "" && existing.Path == canonical {
return nil, cloneSourceInfos(sources), []Diagnostic{importDiagnostic(CodeProfileRepeated, "selected profile is already present in the base source graph", nil)}
return nil, cloneSourceInfos(sources), []Diagnostic{importDiagnostic(CodeOverlayRepeated, "selected overlay is already present in the base source graph", nil)}
}
}
if len(sources)+1 > limits.sources {
return nil, cloneSourceInfos(sources), []Diagnostic{importDiagnostic(CodeProfileLimitExceeded, "selected profile exceeds the source-count limit", nil)}
return nil, cloneSourceInfos(sources), []Diagnostic{importDiagnostic(CodeOverlayLimitExceeded, "selected overlay exceeds the source-count limit", nil)}
}
root, err := os.OpenRoot(rootPath)
if err != nil {
value := globFilesystemDiagnostic(err, "configuration root could not be opened for the selected profile", Location{})
value := globFilesystemDiagnostic(err, "configuration root could not be opened for the selected overlay", Location{})
value.Primary = nil
return nil, cloneSourceInfos(sources), []Diagnostic{value}
}
@@ -74,57 +74,57 @@ func loadProfileDocument(source Source, context *FilesystemContext, main parsedS
}()
relative, err := filepath.Rel(rootPath, canonical)
if err != nil || filepath.IsAbs(relative) || relative == ".." || strings.HasPrefix(relative, ".."+string(filepath.Separator)) {
return nil, cloneSourceInfos(sources), []Diagnostic{importDiagnostic(CodeProfileOutsideRoot, "selected profile could not be addressed inside the configuration root", nil)}
return nil, cloneSourceInfos(sources), []Diagnostic{importDiagnostic(CodeOverlayOutsideRoot, "selected overlay could not be addressed inside the configuration root", nil)}
}
sourceID := "source-" + strconv.Itoa(len(sources)+1)
profileLocation := &Location{SourceID: sourceID}
profileSource := SourceInfo{ID: sourceID, Label: filepath.ToSlash(filepath.Join("profiles", name+".yml")), Path: canonical, Layer: LayerProfile}
sources = append(cloneSourceInfos(sources), profileSource)
data, diagnostic := readProfileFile(root, relative, sourceID, limits.bytes-base.inputBytes)
overlayLocation := &Location{SourceID: sourceID}
overlaySource := SourceInfo{ID: sourceID, Label: filepath.ToSlash(filepath.Join("overlays", name+".yml")), Path: canonical, Layer: LayerOverlay}
sources = append(cloneSourceInfos(sources), overlaySource)
data, diagnostic := readOverlayFile(root, relative, sourceID, limits.bytes-base.inputBytes)
if diagnostic != nil {
return nil, sources, []Diagnostic{*diagnostic}
}
profileRoot, issues := yamladapter.Parse(bytes.NewReader(data), sourceID, yamladapter.Limits{Depth: maxDocumentDepth, Nodes: maxDocumentNodes})
overlayRoot, issues := yamladapter.Parse(bytes.NewReader(data), sourceID, yamladapter.Limits{Depth: maxDocumentDepth, Nodes: maxDocumentNodes})
if len(issues) != 0 {
return nil, sources, parserDiagnostics(sourceID, issues)
}
nodeCount := countInputNodes(profileRoot)
nodeCount := countInputNodes(overlayRoot)
if base.inputNodes+nodeCount > limits.nodes {
return nil, sources, []Diagnostic{importDiagnostic(CodeProfileLimitExceeded, "selected profile exceeds the aggregate node limit", profileLocation)}
return nil, sources, []Diagnostic{importDiagnostic(CodeOverlayLimitExceeded, "selected overlay exceeds the aggregate node limit", overlayLocation)}
}
if diagnostics := profileDirectiveDiagnostics(profileRoot, Path{}); len(diagnostics) != 0 {
if diagnostics := overlayDirectiveDiagnostics(overlayRoot, Path{}); len(diagnostics) != 0 {
return nil, sources, diagnostics
}
if versionNode := fragmentVersionClaim(profileRoot, rule); versionNode != nil {
if versionNode := fragmentVersionClaim(overlayRoot, rule); versionNode != nil {
location := publicLocation(versionNode.Location)
return nil, sources, []Diagnostic{{
Code: CodeProfileVersion, Severity: SeverityError, Path: rule.Path.clone(),
Message: "profiles cannot contribute to or replace the schema-version path", Primary: &location,
Code: CodeOverlayVersion, Severity: SeverityError, Path: rule.Path.clone(),
Message: "overlays cannot contribute to or replace the schema-version path", Primary: &location,
}}
}
closeErr := root.Close()
rootClosed = true
if closeErr != nil {
return nil, sources, []Diagnostic{importDiagnostic(CodeImportRead, "configuration root could not be closed after reading the selected profile", nil)}
return nil, sources, []Diagnostic{importDiagnostic(CodeImportRead, "configuration root could not be closed after reading the selected overlay", nil)}
}
profiled, diagnostics := overlayDocuments(base, profileRoot)
if profiled != nil {
profiled.inputBytes = base.inputBytes + int64(len(data))
profiled.inputNodes = base.inputNodes + nodeCount
overlayd, diagnostics := overlayDocuments(base, overlayRoot)
if overlayd != nil {
overlayd.inputBytes = base.inputBytes + int64(len(data))
overlayd.inputNodes = base.inputNodes + nodeCount
}
return profiled, sources, diagnostics
return overlayd, sources, diagnostics
}
func readProfileFile(root *os.Root, relative, sourceID string, remaining int64) ([]byte, *Diagnostic) {
func readOverlayFile(root *os.Root, relative, sourceID string, remaining int64) ([]byte, *Diagnostic) {
limit := min(int64(maxSourceBytes), remaining)
if limit < 0 {
limit = 0
}
data, failure := readRegularFile(relative, root.Stat, root.Open, limit)
if failure != nil {
value := sourceReadDiagnostic(failure, sourceID, CodeProfileLimitExceeded, "selected profile exceeds a source or aggregate byte limit")
value := sourceReadDiagnostic(failure, sourceID, CodeOverlayLimitExceeded, "selected overlay exceeds a source or aggregate byte limit")
return nil, &value
}
return data, nil

View File

@@ -9,7 +9,7 @@ import (
"testing"
)
type profileTestConfig struct {
type overlayTestConfig struct {
Settings struct {
Kept string `yaml:"kept"`
Changed string `yaml:"changed"`
@@ -21,7 +21,7 @@ type profileTestConfig struct {
Schema map[string]string `yaml:"schema"`
}
func TestLoadAppliesSelectedProfileAfterBaseAndBeforeDefaults(t *testing.T) {
func TestLoadAppliesSelectedOverlayAfterBaseAndBeforeDefaults(t *testing.T) {
directory := t.TempDir()
mainPath := writeImportFile(t, directory, "main.yml", `configkit:
imports: [{path: base.yml}]
@@ -33,19 +33,19 @@ nullable: base
value: {child: base}
`)
basePath := writeImportFile(t, directory, "base.yml", "settings: {added: imported}\n")
profilePath := writeImportFile(t, directory, "profiles/testing.yml", `settings:
changed: profile
values: [profile]
overlayPath := writeImportFile(t, directory, "overlays/testing.yml", `settings:
changed: overlay
values: [overlay]
nullable: null
value: scalar
`)
result, err := testLoad[profileTestConfig](File(mainPath), Options[profileTestConfig]{
result, err := testLoad[overlayTestConfig](File(mainPath), Options[overlayTestConfig]{
Imports: true,
Profile: "testing",
Definition: Definition[profileTestConfig]{Defaults: func() profileTestConfig {
Overlay: "testing",
Definition: Definition[overlayTestConfig]{Defaults: func() overlayTestConfig {
defaultValue := "default"
return profileTestConfig{
return overlayTestConfig{
Values: []string{"default"}, Nullable: &defaultValue,
Settings: struct {
Kept string `yaml:"kept"`
@@ -58,31 +58,31 @@ value: scalar
if err != nil {
t.Fatal(err)
}
if result.Config.Settings.Kept != "main" || result.Config.Settings.Changed != "profile" || result.Config.Settings.Added != "imported" {
if result.Config.Settings.Kept != "main" || result.Config.Settings.Changed != "overlay" || result.Config.Settings.Added != "imported" {
t.Fatalf("settings = %#v", result.Config.Settings)
}
if !reflect.DeepEqual(result.Config.Values, []string{"profile"}) || result.Config.Nullable != nil || result.Config.Value != "scalar" {
t.Fatalf("profile values = values %#v nullable %#v value %#v", result.Config.Values, result.Config.Nullable, result.Config.Value)
if !reflect.DeepEqual(result.Config.Values, []string{"overlay"}) || result.Config.Nullable != nil || result.Config.Value != "scalar" {
t.Fatalf("overlay values = values %#v nullable %#v value %#v", result.Config.Values, result.Config.Nullable, result.Config.Value)
}
sources := result.Sources()
wantPaths := []string{canonicalTestPath(t, mainPath), canonicalTestPath(t, basePath), canonicalTestPath(t, profilePath)}
wantPaths := []string{canonicalTestPath(t, mainPath), canonicalTestPath(t, basePath), canonicalTestPath(t, overlayPath)}
gotPaths := make([]string, len(sources))
for index, source := range sources {
gotPaths[index] = source.Path
}
if !reflect.DeepEqual(gotPaths, wantPaths) || sources[2].Layer != LayerProfile {
if !reflect.DeepEqual(gotPaths, wantPaths) || sources[2].Layer != LayerOverlay {
t.Fatalf("sources = %#v", sources)
}
changed, _ := result.Metadata().Lookup(Keys("settings", "changed"))
kept, _ := result.Metadata().Lookup(Keys("settings", "kept"))
if changed.Origin.Layer != LayerProfile || changed.Origin.Locations[0].SourceID != "source-3" || kept.Origin.Layer != LayerBase {
if changed.Origin.Layer != LayerOverlay || changed.Origin.Locations[0].SourceID != "source-3" || kept.Origin.Layer != LayerBase {
t.Fatalf("origins = changed %#v kept %#v", changed, kept)
}
if _, ok := result.Metadata().Lookup(Keys("value", "child")); ok {
t.Fatal("removed child remains effective")
}
childHistory := result.Metadata().History(Keys("value", "child"))
if len(childHistory) < 2 || childHistory[len(childHistory)-1].Path.String() != "/value" || childHistory[len(childHistory)-1].Origin.Layer != LayerProfile {
if len(childHistory) < 2 || childHistory[len(childHistory)-1].Path.String() != "/value" || childHistory[len(childHistory)-1].Origin.Layer != LayerOverlay {
t.Fatalf("removed child history = %#v", childHistory)
}
if _, ok := result.Metadata().Lookup(Keys("values").Index(1)); ok {
@@ -94,34 +94,34 @@ value: scalar
}
}
func TestLoadProfileSelectionUsesOneFixedYAMLFile(t *testing.T) {
func TestLoadOverlaySelectionUsesOneFixedYAMLFile(t *testing.T) {
directory := t.TempDir()
mainPath := writeImportFile(t, directory, "main.yml", "settings: {kept: base}\n")
writeImportFile(t, directory, "profiles/testing.yml", "settings: {changed: selected}\n")
writeImportFile(t, directory, "profiles/default.yml", "settings: {changed: forbidden}\n")
writeImportFile(t, directory, "overlays/testing.yml", "settings: {changed: selected}\n")
writeImportFile(t, directory, "overlays/default.yml", "settings: {changed: forbidden}\n")
for _, name := range []string{"", "default"} {
result, err := testLoad[profileTestConfig](File(mainPath), Options[profileTestConfig]{Profile: name})
result, err := testLoad[overlayTestConfig](File(mainPath), Options[overlayTestConfig]{Overlay: name})
if err != nil || result.Config.Settings.Changed != "" || len(result.Sources()) != 1 {
t.Fatalf("inactive profile %q = %#v, %v", name, result, err)
t.Fatalf("inactive overlay %q = %#v, %v", name, result, err)
}
}
selected, err := testLoad[profileTestConfig](File(mainPath), Options[profileTestConfig]{Profile: "testing"})
selected, err := testLoad[overlayTestConfig](File(mainPath), Options[overlayTestConfig]{Overlay: "testing"})
if err != nil || selected.Config.Settings.Changed != "selected" || len(selected.Sources()) != 2 {
t.Fatalf("selected profile = %#v, %v", selected, err)
t.Fatalf("selected overlay = %#v, %v", selected, err)
}
for _, name := range []string{"../testing", "testing.yml", "one,two", "with space", "é", "bad\x00name"} {
t.Run(name, func(t *testing.T) {
requireProfileLoadCode(t, File(mainPath), Options[profileTestConfig]{Profile: name}, CodeOptionsInvalid)
requireOverlayLoadCode(t, File(mainPath), Options[overlayTestConfig]{Overlay: name}, CodeOptionsInvalid)
})
}
requireProfileLoadCode(t, File(mainPath), Options[profileTestConfig]{Profile: "missing"}, CodeProfileMissing)
writeImportFile(t, directory, "profiles/fallback.yaml", "settings: {changed: fallback}\n")
requireProfileLoadCode(t, File(mainPath), Options[profileTestConfig]{Profile: "fallback"}, CodeProfileMissing)
requireOverlayLoadCode(t, File(mainPath), Options[overlayTestConfig]{Overlay: "missing"}, CodeOverlayMissing)
writeImportFile(t, directory, "overlays/fallback.yaml", "settings: {changed: fallback}\n")
requireOverlayLoadCode(t, File(mainPath), Options[overlayTestConfig]{Overlay: "fallback"}, CodeOverlayMissing)
}
func TestLoadProfileRejectsDirectivesVersionsAndBaseAliases(t *testing.T) {
func TestLoadOverlayRejectsDirectivesVersionsAndBaseAliases(t *testing.T) {
versionRule := &VersionRule{Path: Keys("schema", "version"), Check: func(version Version, _ *Validation) error {
if !version.Present || version.Text != "4" {
return errors.New("unexpected version")
@@ -130,55 +130,55 @@ func TestLoadProfileRejectsDirectivesVersionsAndBaseAliases(t *testing.T) {
}}
for _, test := range []struct {
name string
profile string
overlay string
code Code
}{
{name: "root directive", profile: "configkit: {}\n", code: CodeProfileDirective},
{name: "nested directive", profile: "settings: {configkit: {}}\n", code: CodeProfileDirective},
{name: "equal integer version", profile: "schema: {version: 4}\n", code: CodeProfileVersion},
{name: "ancestor scalar", profile: "schema: null\n", code: CodeProfileVersion},
{name: "root directive", overlay: "configkit: {}\n", code: CodeOverlayDirective},
{name: "nested directive", overlay: "settings: {configkit: {}}\n", code: CodeOverlayDirective},
{name: "equal integer version", overlay: "schema: {version: 4}\n", code: CodeOverlayVersion},
{name: "ancestor scalar", overlay: "schema: null\n", code: CodeOverlayVersion},
} {
t.Run(test.name, func(t *testing.T) {
directory := t.TempDir()
mainPath := writeImportFile(t, directory, "main.yml", "schema: {version: 4}\n")
writeImportFile(t, directory, "profiles/testing.yml", test.profile)
requireProfileLoadCode(t, File(mainPath), Options[profileTestConfig]{Profile: "testing", Definition: Definition[profileTestConfig]{Version: versionRule}}, test.code)
writeImportFile(t, directory, "overlays/testing.yml", test.overlay)
requireOverlayLoadCode(t, File(mainPath), Options[overlayTestConfig]{Overlay: "testing", Definition: Definition[overlayTestConfig]{Version: versionRule}}, test.code)
})
}
t.Run("mapping sibling is allowed", func(t *testing.T) {
directory := t.TempDir()
mainPath := writeImportFile(t, directory, "main.yml", "schema: {version: 4}\n")
writeImportFile(t, directory, "profiles/testing.yml", "schema: {other: allowed}\n")
result, err := testLoad[profileTestConfig](File(mainPath), Options[profileTestConfig]{Profile: "testing", Definition: Definition[profileTestConfig]{Version: versionRule}})
writeImportFile(t, directory, "overlays/testing.yml", "schema: {other: allowed}\n")
result, err := testLoad[overlayTestConfig](File(mainPath), Options[overlayTestConfig]{Overlay: "testing", Definition: Definition[overlayTestConfig]{Version: versionRule}})
if err != nil || result.Config.Schema["other"] != "allowed" {
t.Fatalf("mapping sibling = %#v, %v", result, err)
}
})
t.Run("profile aliases imported base", func(t *testing.T) {
t.Run("overlay aliases imported base", func(t *testing.T) {
directory := t.TempDir()
mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: profiles/testing.yml}]}\n")
writeImportFile(t, directory, "profiles/testing.yml", "settings: {changed: value}\n")
requireProfileLoadCode(t, File(mainPath), Options[profileTestConfig]{Imports: true, Profile: "testing"}, CodeProfileRepeated)
mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: overlays/testing.yml}]}\n")
writeImportFile(t, directory, "overlays/testing.yml", "settings: {changed: value}\n")
requireOverlayLoadCode(t, File(mainPath), Options[overlayTestConfig]{Imports: true, Overlay: "testing"}, CodeOverlayRepeated)
})
}
func TestLoadProfileKeepsBaseFailuresAndFilesystemContainment(t *testing.T) {
t.Run("base parse precedes profile", func(t *testing.T) {
func TestLoadOverlayKeepsBaseFailuresAndFilesystemContainment(t *testing.T) {
t.Run("base parse precedes overlay", func(t *testing.T) {
directory := t.TempDir()
mainPath := writeImportFile(t, directory, "main.yml", "settings: [\n")
writeImportFile(t, directory, "profiles/testing.yml", "settings: {changed: profile}\n")
requireProfileLoadCode(t, File(mainPath), Options[profileTestConfig]{Profile: "testing"}, CodeYAMLInvalid)
writeImportFile(t, directory, "overlays/testing.yml", "settings: {changed: overlay}\n")
requireOverlayLoadCode(t, File(mainPath), Options[overlayTestConfig]{Overlay: "testing"}, CodeYAMLInvalid)
})
t.Run("profile parse", func(t *testing.T) {
t.Run("overlay parse", func(t *testing.T) {
directory := t.TempDir()
mainPath := writeImportFile(t, directory, "main.yml", "{}\n")
writeImportFile(t, directory, "profiles/testing.yml", "settings: [\n")
loadError := requireProfileLoadCode(t, File(mainPath), Options[profileTestConfig]{Profile: "testing"}, CodeYAMLInvalid)
writeImportFile(t, directory, "overlays/testing.yml", "settings: [\n")
loadError := requireOverlayLoadCode(t, File(mainPath), Options[overlayTestConfig]{Overlay: "testing"}, CodeYAMLInvalid)
if loadError.Diagnostics()[0].Primary.SourceID != "source-2" || len(loadError.Sources()) != 2 {
t.Fatalf("profile parse attribution = %#v / %#v", loadError.Diagnostics(), loadError.Sources())
t.Fatalf("overlay parse attribution = %#v / %#v", loadError.Diagnostics(), loadError.Sources())
}
})
@@ -186,32 +186,32 @@ func TestLoadProfileKeepsBaseFailuresAndFilesystemContainment(t *testing.T) {
t.Run("symlink escape", func(t *testing.T) {
root := t.TempDir()
outside := t.TempDir()
outsideProfile := writeImportFile(t, outside, "testing.yml", "settings: {changed: outside}\n")
if err := os.Mkdir(filepath.Join(root, "profiles"), 0o755); err != nil {
outsideOverlay := writeImportFile(t, outside, "testing.yml", "settings: {changed: outside}\n")
if err := os.Mkdir(filepath.Join(root, "overlays"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.Symlink(outsideProfile, filepath.Join(root, "profiles/testing.yml")); err != nil {
if err := os.Symlink(outsideOverlay, filepath.Join(root, "overlays/testing.yml")); err != nil {
t.Skipf("symlinks unavailable: %v", err)
}
mainPath := writeImportFile(t, root, "main.yml", "{}\n")
requireProfileLoadCode(t, File(mainPath), Options[profileTestConfig]{Profile: "testing"}, CodeProfileOutsideRoot)
requireOverlayLoadCode(t, File(mainPath), Options[overlayTestConfig]{Overlay: "testing"}, CodeOverlayOutsideRoot)
})
}
t.Run("byte source context", func(t *testing.T) {
directory := t.TempDir()
writeImportFile(t, directory, "profiles/testing.yml", "settings: {changed: selected}\n")
result, err := testLoad[profileTestConfig](Bytes("s3://bucket/main.yml", []byte("{}\n")), Options[profileTestConfig]{
Profile: "testing", Filesystem: &FilesystemContext{BaseDir: directory, Root: directory},
writeImportFile(t, directory, "overlays/testing.yml", "settings: {changed: selected}\n")
result, err := testLoad[overlayTestConfig](Bytes("s3://bucket/main.yml", []byte("{}\n")), Options[overlayTestConfig]{
Overlay: "testing", Filesystem: &FilesystemContext{BaseDir: directory, Root: directory},
})
if err != nil || result.Config.Settings.Changed != "selected" {
t.Fatalf("byte profile = %#v, %v", result, err)
t.Fatalf("byte overlay = %#v, %v", result, err)
}
requireProfileLoadCode(t, Bytes("memory.yml", []byte("{}\n")), Options[profileTestConfig]{Profile: "testing"}, CodeOptionsInvalid)
requireOverlayLoadCode(t, Bytes("memory.yml", []byte("{}\n")), Options[overlayTestConfig]{Overlay: "testing"}, CodeOptionsInvalid)
})
}
func TestLoadProfileCountsAgainstAggregateBudgets(t *testing.T) {
func TestLoadOverlayCountsAgainstAggregateBudgets(t *testing.T) {
for _, test := range []struct {
name string
limits func(parsedSource, int) importLimits
@@ -219,8 +219,8 @@ func TestLoadProfileCountsAgainstAggregateBudgets(t *testing.T) {
{name: "source count", limits: func(_ parsedSource, _ int) importLimits {
return importLimits{sources: 1, bytes: maxTotalBytes, nodes: maxDocumentNodes}
}},
{name: "aggregate bytes", limits: func(main parsedSource, profileBytes int) importLimits {
return importLimits{sources: 2, bytes: main.bytes + int64(profileBytes) - 1, nodes: maxDocumentNodes}
{name: "aggregate bytes", limits: func(main parsedSource, overlayBytes int) importLimits {
return importLimits{sources: 2, bytes: main.bytes + int64(overlayBytes) - 1, nodes: maxDocumentNodes}
}},
{name: "aggregate nodes", limits: func(main parsedSource, _ int) importLimits {
return importLimits{sources: 2, bytes: maxTotalBytes, nodes: countInputNodes(main.document) + 1}
@@ -229,8 +229,8 @@ func TestLoadProfileCountsAgainstAggregateBudgets(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
directory := t.TempDir()
mainPath := writeImportFile(t, directory, "main.yml", "{}\n")
profileData := "settings: {changed: selected}\n"
writeImportFile(t, directory, "profiles/testing.yml", profileData)
overlayData := "settings: {changed: selected}\n"
writeImportFile(t, directory, "overlays/testing.yml", overlayData)
main, diagnostics := parseSource(File(mainPath), "source-1", defaultParseLimits())
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
@@ -241,17 +241,17 @@ func TestLoadProfileCountsAgainstAggregateBudgets(t *testing.T) {
}
base.inputBytes = main.bytes
base.inputNodes = countInputNodes(main.document)
_, _, diagnostics = loadProfileDocument(File(mainPath), nil, main, base, sourceInfos(main.source), "testing", nil, test.limits(main, len(profileData)))
if len(diagnostics) == 0 || diagnostics[0].Code != CodeProfileLimitExceeded {
_, _, diagnostics = loadSelectedOverlayDocument(File(mainPath), nil, main, base, sourceInfos(main.source), "testing", nil, test.limits(main, len(overlayData)))
if len(diagnostics) == 0 || diagnostics[0].Code != CodeOverlayLimitExceeded {
t.Fatalf("diagnostics = %#v", diagnostics)
}
})
}
}
func requireProfileLoadCode(t *testing.T, source Source, options Options[profileTestConfig], code Code) *LoadError {
func requireOverlayLoadCode(t *testing.T, source Source, options Options[overlayTestConfig], code Code) *LoadError {
t.Helper()
result, err := testLoad[profileTestConfig](source, options)
result, err := testLoad[overlayTestConfig](source, options)
if result != nil || err == nil {
t.Fatalf("result, error = %#v, %v", result, err)
}

View File

@@ -23,8 +23,8 @@ func TestNonregularSourcesDoNotBlock(t *testing.T) {
source = File(filepath.Join(root, "pipe.yml"))
case "import":
opts.Imports = true
case "profile":
opts.Profile = "fifo"
case "overlay":
opts.Overlay = "fifo"
}
_, err := testLoad(source, opts)
var loadErr *LoadError
@@ -38,7 +38,7 @@ func TestNonregularSourcesDoNotBlock(t *testing.T) {
}
t.Fatalf("expected nonregular classification, got %v", loadErr.Diagnostics())
}
for _, mode := range []string{"file", "import", "profile"} {
for _, mode := range []string{"file", "import", "overlay"} {
t.Run(mode, func(t *testing.T) {
root := t.TempDir()
input := "value: 1\n"
@@ -48,12 +48,12 @@ func TestNonregularSourcesDoNotBlock(t *testing.T) {
if err := os.WriteFile(filepath.Join(root, "config.yml"), []byte(input), 0600); err != nil {
t.Fatal(err)
}
if err := os.Mkdir(filepath.Join(root, "profiles"), 0700); err != nil {
if err := os.Mkdir(filepath.Join(root, "overlays"), 0700); err != nil {
t.Fatal(err)
}
fifo := filepath.Join(root, "pipe.yml")
if mode == "profile" {
fifo = filepath.Join(root, "profiles", "fifo.yml")
if mode == "overlay" {
fifo = filepath.Join(root, "overlays", "fifo.yml")
}
if err := syscall.Mkfifo(fifo, 0600); err != nil {
t.Fatal(err)