Preserve inherited reference path origins
This commit is contained in:
@@ -80,10 +80,12 @@ umask, setgid inheritance, and default ACLs; see [Operations](operations.md#cach
|
|||||||
|
|
||||||
Every nonempty relative configured destination or reference path resolves from
|
Every nonempty relative configured destination or reference path resolves from
|
||||||
the canonical directory of the file that supplies its surviving declaration.
|
the canonical directory of the file that supplies its surviving declaration.
|
||||||
This includes imported fragments and selected configuration overlays. Built-in
|
This includes imported fragments, selected configuration overlays, and values
|
||||||
relative destinations use the selected main configuration file's directory;
|
copied through inheritance: the copied value keeps its original declaring
|
||||||
explicit `--output-dir` and `--debug-dir` values use the invocation working
|
file. A child-supplied replacement resolves from the child's declaring file.
|
||||||
directory. Absolute values remain unchanged.
|
Built-in relative destinations use the selected main configuration file's
|
||||||
|
directory; explicit `--output-dir` and `--debug-dir` values use the invocation
|
||||||
|
working directory. Absolute values remain unchanged.
|
||||||
|
|
||||||
The root file can use ConfigKit imports to compose disjoint definitions. Imports
|
The root file can use ConfigKit imports to compose disjoint definitions. Imports
|
||||||
and selected overlays remain under the configuration root; they do not merge or
|
and selected overlays remain under the configuration root; they do not merge or
|
||||||
|
|||||||
@@ -28,10 +28,13 @@ at their respective [CLI](cli.md), [pipeline](pipeline.md), and
|
|||||||
|
|
||||||
## Loading And Validation
|
## Loading And Validation
|
||||||
|
|
||||||
ConfigKit acquires the selected root, imports, optional selected overlay, and
|
ConfigKit composes the selected root, imports, optional selected overlay, and
|
||||||
explicit CLI overrides in precedence order. Its document definition supplies
|
explicit CLI overrides in precedence order, then expands native sibling
|
||||||
typed defaults, source-aware path normalization, and structural validation
|
inheritance before typed decoding. The document definition supplies typed
|
||||||
before conversion to the independently owned runtime **Config**. Only
|
defaults and structural validation; Notarius's recorded resolution changes
|
||||||
|
normalize configured paths after decode. Path normalization follows the source
|
||||||
|
of the surviving value, including a value copied through inheritance, rather
|
||||||
|
than the inheritance directive's location. Only
|
||||||
`NOTARIUS_CONFIG` participates in discovery; retired field-level environment
|
`NOTARIUS_CONFIG` participates in discovery; retired field-level environment
|
||||||
variables do not participate in loading. Version 5 decoding rejects scalar
|
variables do not participate in loading. Version 5 decoding rejects scalar
|
||||||
module bindings and scalar external references. ConfigKit retains redacted,
|
module bindings and scalar external references. ConfigKit retains redacted,
|
||||||
|
|||||||
@@ -227,6 +227,8 @@ not change defaults or add CLI flags for arbitrary configuration overrides.
|
|||||||
|
|
||||||
## Stage 4 — Verify Declaration-Relative Paths Through Inheritance
|
## Stage 4 — Verify Declaration-Relative Paths Through Inheritance
|
||||||
|
|
||||||
|
> Completion: complete.
|
||||||
|
|
||||||
Goal: keep inherited resource paths anchored to the original declaration.
|
Goal: keep inherited resource paths anchored to the original declaration.
|
||||||
|
|
||||||
Inspect `document_paths.go`, `document_paths_test.go`, the original-path map in
|
Inspect `document_paths.go`, `document_paths_test.go`, the original-path map in
|
||||||
|
|||||||
@@ -331,3 +331,105 @@ pipelines:
|
|||||||
t.Fatalf("override-inherited repair budget = %d, want 3", got)
|
t.Fatalf("override-inherited repair budget = %d, want 3", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInheritedReferencePathsKeepDeclaringSource(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
root := t.TempDir()
|
||||||
|
parentDirectory := filepath.Join(root, "parents")
|
||||||
|
childDirectory := filepath.Join(root, "children")
|
||||||
|
overlayDirectory := filepath.Join(root, "overlays")
|
||||||
|
fixtureDirectory := filepath.Join(root, "fixtures")
|
||||||
|
for _, directory := range []string{parentDirectory, childDirectory, overlayDirectory, fixtureDirectory} {
|
||||||
|
if err := os.MkdirAll(directory, 0o700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, name := range []string{"shared.txt", "parent.txt", "child.txt", "step.txt", "overlay.txt"} {
|
||||||
|
if err := os.WriteFile(filepath.Join(fixtureDirectory, name), []byte("fixture\n"), 0o600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parent := filepath.Join(parentDirectory, "pipelines.yml")
|
||||||
|
if err := os.WriteFile(parent, []byte(`pipelines:
|
||||||
|
" parent ":
|
||||||
|
input: {module: input}
|
||||||
|
references:
|
||||||
|
" shared ": {path: ../fixtures/shared.txt}
|
||||||
|
nested:
|
||||||
|
extends: " shared "
|
||||||
|
source: {path: ../fixtures/parent.txt}
|
||||||
|
steps:
|
||||||
|
- id: first
|
||||||
|
references:
|
||||||
|
step-source: {path: ../fixtures/step.txt}
|
||||||
|
artifacts:
|
||||||
|
summary:
|
||||||
|
extract: {module: extract}
|
||||||
|
`), 0o600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
child := filepath.Join(childDirectory, "pipelines.yml")
|
||||||
|
if err := os.WriteFile(child, []byte(`pipelines:
|
||||||
|
child:
|
||||||
|
extends: " parent "
|
||||||
|
references:
|
||||||
|
source: {path: ../fixtures/child.txt}
|
||||||
|
`), 0o600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
main := filepath.Join(root, "notarius.yml")
|
||||||
|
if err := os.WriteFile(main, []byte(`version: 5
|
||||||
|
configkit:
|
||||||
|
imports:
|
||||||
|
- path: parents/pipelines.yml
|
||||||
|
- path: children/pipelines.yml
|
||||||
|
`), 0o600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(overlayDirectory, "development.yml"), []byte(`pipelines:
|
||||||
|
" parent ":
|
||||||
|
references:
|
||||||
|
" shared ": {path: ../fixtures/overlay.txt}
|
||||||
|
`), 0o600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
loaded, err := loadConfig(main, LoadOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("loadConfig() error = %v", err)
|
||||||
|
}
|
||||||
|
assertReferencePath := func(pipelineID, referenceID, want string) {
|
||||||
|
t.Helper()
|
||||||
|
if got := loaded.Config.Pipelines[pipelineID].References[referenceID].Path; got != want {
|
||||||
|
t.Errorf("%s reference %s path = %q, want %q", pipelineID, referenceID, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertReferencePath("parent", "shared", filepath.Join(fixtureDirectory, "shared.txt"))
|
||||||
|
assertReferencePath("child", "shared", filepath.Join(fixtureDirectory, "shared.txt"))
|
||||||
|
assertReferencePath("child", "nested", filepath.Join(fixtureDirectory, "shared.txt"))
|
||||||
|
assertReferencePath("parent", "source", filepath.Join(fixtureDirectory, "parent.txt"))
|
||||||
|
assertReferencePath("child", "source", filepath.Join(fixtureDirectory, "child.txt"))
|
||||||
|
if got := loaded.Config.Pipelines["child"].Steps[0].References["step-source"].Path; got != filepath.Join(fixtureDirectory, "step.txt") {
|
||||||
|
t.Errorf("inherited step reference path = %q", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
overlaid, err := loadConfig(main, LoadOptions{Overlay: "development"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("loadConfig() with overlay error = %v", err)
|
||||||
|
}
|
||||||
|
for _, pipelineID := range []string{"parent", "child"} {
|
||||||
|
for _, referenceID := range []string{"shared", "nested"} {
|
||||||
|
if got, want := overlaid.Config.Pipelines[pipelineID].References[referenceID].Path, filepath.Join(fixtureDirectory, "overlay.txt"); got != want {
|
||||||
|
t.Errorf("overlaid %s reference %s path = %q, want %q", pipelineID, referenceID, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if got, want := overlaid.Config.Pipelines["child"].References["source"].Path, filepath.Join(fixtureDirectory, "child.txt"); got != want {
|
||||||
|
t.Errorf("child replacement path = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
if got, want := overlaid.Config.Pipelines["child"].Steps[0].References["step-source"].Path, filepath.Join(fixtureDirectory, "step.txt"); got != want {
|
||||||
|
t.Errorf("overlaid inherited step reference path = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user