Demonstrate source-aware path normalization
This commit is contained in:
@@ -4,6 +4,7 @@ package consumers
|
||||
|
||||
import (
|
||||
"math"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -146,6 +147,69 @@ func NotariusWindow(configuration NotariusConfig, binding int) (int, bool) {
|
||||
return int(value), true
|
||||
}
|
||||
|
||||
// NotariusPathConfig demonstrates application-owned interpretation of a
|
||||
// path-valued configuration field. ConfigKit supplies provenance; this example
|
||||
// selects its own relative-path bases.
|
||||
type NotariusPathConfig struct {
|
||||
Reference string `yaml:"reference"`
|
||||
Sensitive string `yaml:"sensitive"`
|
||||
}
|
||||
|
||||
// LoadNotariusPaths loads the path-bearing Notarius-shaped example. InvocationDir
|
||||
// is the application-supplied base for relative CLI overrides; it is never read
|
||||
// from the process working directory.
|
||||
func LoadNotariusPaths(path, invocationDir string, overrides ...configkit.Override) (*configkit.Result[NotariusPathConfig], error) {
|
||||
return configkit.Load(configkit.File(path), configkit.Options[NotariusPathConfig]{
|
||||
Imports: true, Overrides: overrides,
|
||||
Definition: configkit.Definition[NotariusPathConfig]{
|
||||
Defaults: func() NotariusPathConfig { return NotariusPathConfig{Reference: "defaults/reference.txt"} },
|
||||
Resolve: func(configuration *NotariusPathConfig, resolution *configkit.Resolution) error {
|
||||
resolveNotariusReference(configuration, resolution, invocationDir)
|
||||
return nil
|
||||
},
|
||||
Secret: func(path configkit.Path) bool { return path.String() == "/sensitive" },
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func resolveNotariusReference(configuration *NotariusPathConfig, resolution *configkit.Resolution, invocationDir string) {
|
||||
path := configkit.Keys("reference")
|
||||
if configuration.Reference == "" || filepath.IsAbs(configuration.Reference) {
|
||||
return
|
||||
}
|
||||
info, found := resolution.InputInfo(path)
|
||||
if !found {
|
||||
resolution.Warning(path, "relative reference has no entry provenance")
|
||||
return
|
||||
}
|
||||
|
||||
base := ""
|
||||
if info.Origin.Layer == configkit.LayerCLI {
|
||||
base = invocationDir
|
||||
if base == "" {
|
||||
resolution.Warning(path, "relative CLI reference has no invocation directory")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
sourceID := "source-1" // ConfigKit's primary-source identity convention.
|
||||
if info.Origin.Layer != configkit.LayerDefault {
|
||||
if len(info.Origin.Locations) != 1 {
|
||||
resolution.Warning(path, "relative reference has no unambiguous declaring source")
|
||||
return
|
||||
}
|
||||
sourceID = info.Origin.Locations[0].SourceID
|
||||
}
|
||||
source, exists := resolution.Source(sourceID)
|
||||
if !exists || source.Path == "" {
|
||||
resolution.Warning(path, "relative reference has no declaring file")
|
||||
return
|
||||
}
|
||||
base = filepath.Dir(source.Path)
|
||||
}
|
||||
configkit.Normalize(resolution, path, &configuration.Reference,
|
||||
filepath.Join(base, configuration.Reference), "resolved relative reference from entry source")
|
||||
}
|
||||
|
||||
// ModuleOptions is a WeatherReporter-shaped union of known module option fields.
|
||||
type ModuleOptions struct {
|
||||
WindowUnits *int `yaml:"window_units"`
|
||||
|
||||
@@ -29,6 +29,12 @@ func Example_notarius() {
|
||||
// Output: 4 summary 12 true <nil>
|
||||
}
|
||||
|
||||
func Example_notariusPaths() {
|
||||
result, err := consumers.LoadNotariusPaths(fixture("notarius-paths", "main.yml"), "/invocation")
|
||||
fmt.Println(filepath.Base(result.Config.Reference), err)
|
||||
// Output: imported.txt <nil>
|
||||
}
|
||||
|
||||
func Example_weatherReporter() {
|
||||
defaults, defaultsErr := consumers.LoadWeather(configkit.NoSource())
|
||||
result, err := consumers.LoadWeather(configkit.File(fixture("weather", "config.yml")))
|
||||
@@ -240,12 +246,89 @@ func TestWeatherLoadedAndConstructedRulesAgreeWithoutMutation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotariusPathExampleUsesEntryProvenance(t *testing.T) {
|
||||
main := fixture("notarius-paths", "main.yml")
|
||||
declared := fixture("notarius-paths", "declarations", "path.yml")
|
||||
declaredCanonical := canonicalFixture(t, declared)
|
||||
wantImported := filepath.Join(filepath.Dir(declaredCanonical), "..", "assets", "imported.txt")
|
||||
result, err := consumers.LoadNotariusPaths(main, filepath.Join(t.TempDir(), "invocation"))
|
||||
if err != nil || result.Config.Reference != wantImported {
|
||||
t.Fatalf("imported reference = %#v, %v", result, err)
|
||||
}
|
||||
var effective bytes.Buffer
|
||||
if err := result.WriteEffective(&effective); err != nil || !strings.Contains(effective.String(), wantImported) ||
|
||||
strings.Contains(effective.String(), "PATH_REFERENCE_SECRET_CANARY") {
|
||||
t.Fatalf("effective output = %q, %v", effective.String(), err)
|
||||
}
|
||||
explanation, found := result.Explain(configkit.Keys("reference"))
|
||||
if !found || len(explanation.History) == 0 || explanation.History[len(explanation.History)-1].Before.YAML != "../assets/imported.txt\n" ||
|
||||
len(explanation.History[len(explanation.History)-1].Reasons) != 1 || explanation.History[len(explanation.History)-1].Reasons[0] != "resolved relative reference from entry source" {
|
||||
t.Fatalf("reference explanation = %#v, %v", explanation, found)
|
||||
}
|
||||
sensitive, found := result.Explain(configkit.Keys("sensitive"))
|
||||
var rendered bytes.Buffer
|
||||
writeErr := result.WriteExplanation(&rendered, configkit.Keys("sensitive"))
|
||||
if !found || !sensitive.Effective.Redacted || writeErr != nil ||
|
||||
strings.Contains(fmt.Sprintf("%#v %s", sensitive, rendered.String()), "PATH_REFERENCE_SECRET_CANARY") {
|
||||
t.Fatalf("sensitive inspection = %#v, %q, %v", sensitive, rendered.String(), writeErr)
|
||||
}
|
||||
|
||||
defaults := fixture("notarius-paths", "defaults.yml")
|
||||
defaultCanonical := canonicalFixture(t, defaults)
|
||||
defaultResult, err := consumers.LoadNotariusPaths(defaults, filepath.Join(t.TempDir(), "invocation"))
|
||||
wantDefault := filepath.Join(filepath.Dir(defaultCanonical), "defaults", "reference.txt")
|
||||
if err != nil || defaultResult.Config.Reference != wantDefault {
|
||||
t.Fatalf("default reference = %#v, %v", defaultResult, err)
|
||||
}
|
||||
|
||||
invocation := filepath.Join(t.TempDir(), "caller")
|
||||
cliResult, err := consumers.LoadNotariusPaths(main, invocation,
|
||||
configkit.Override{Path: configkit.Keys("reference"), Value: "cli/reference.txt", Flag: "--reference"})
|
||||
wantCLI := filepath.Join(invocation, "cli", "reference.txt")
|
||||
if err != nil || cliResult.Config.Reference != wantCLI {
|
||||
t.Fatalf("CLI reference = %#v, %v", cliResult, err)
|
||||
}
|
||||
missingBase, err := consumers.LoadNotariusPaths(main, "",
|
||||
configkit.Override{Path: configkit.Keys("reference"), Value: "cli/reference.txt", Flag: "--reference"})
|
||||
if err != nil || missingBase.Config.Reference != "cli/reference.txt" || len(missingBase.Diagnostics()) != 1 ||
|
||||
missingBase.Diagnostics()[0].Code != configkit.CodeResolveWarning {
|
||||
t.Fatalf("missing CLI base = %#v, %v", missingBase, err)
|
||||
}
|
||||
absolute := filepath.Join(t.TempDir(), "absolute.txt")
|
||||
absoluteResult, err := consumers.LoadNotariusPaths(main, invocation,
|
||||
configkit.Override{Path: configkit.Keys("reference"), Value: absolute, Flag: "--reference"})
|
||||
if err != nil || absoluteResult.Config.Reference != absolute {
|
||||
t.Fatalf("absolute reference = %#v, %v", absoluteResult, err)
|
||||
}
|
||||
|
||||
target := fixture("notarius-paths", "symlink-target.yml")
|
||||
link := filepath.Join(t.TempDir(), "linked-config.yml")
|
||||
if err := os.Symlink(target, link); err != nil {
|
||||
t.Skipf("symlink fixture unavailable: %v", err)
|
||||
}
|
||||
symlinkResult, err := consumers.LoadNotariusPaths(link, invocation)
|
||||
targetCanonical := canonicalFixture(t, target)
|
||||
wantSymlink := filepath.Join(filepath.Dir(targetCanonical), "..", "assets", "target.txt")
|
||||
if err != nil || symlinkResult.Config.Reference != wantSymlink {
|
||||
t.Fatalf("symlink reference = %#v, %v", symlinkResult, err)
|
||||
}
|
||||
}
|
||||
|
||||
func fixture(parts ...string) string {
|
||||
_, file, _, _ := runtime.Caller(0)
|
||||
values := append([]string{filepath.Dir(file), "testdata"}, parts...)
|
||||
return filepath.Join(values...)
|
||||
}
|
||||
|
||||
func canonicalFixture(t *testing.T, path string) string {
|
||||
t.Helper()
|
||||
canonical, err := filepath.EvalSymlinks(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return canonical
|
||||
}
|
||||
|
||||
func preserveEnvironment(name string) func() {
|
||||
value, present := os.LookupEnv(name)
|
||||
return func() {
|
||||
|
||||
1
examples/consumers/testdata/notarius-paths/declarations/path.yml
vendored
Normal file
1
examples/consumers/testdata/notarius-paths/declarations/path.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
reference: ../assets/imported.txt
|
||||
1
examples/consumers/testdata/notarius-paths/defaults.yml
vendored
Normal file
1
examples/consumers/testdata/notarius-paths/defaults.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
sensitive: PATH_REFERENCE_SECRET_CANARY
|
||||
4
examples/consumers/testdata/notarius-paths/main.yml
vendored
Normal file
4
examples/consumers/testdata/notarius-paths/main.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
configkit:
|
||||
imports:
|
||||
- path: declarations/path.yml
|
||||
sensitive: PATH_REFERENCE_SECRET_CANARY
|
||||
2
examples/consumers/testdata/notarius-paths/symlink-target.yml
vendored
Normal file
2
examples/consumers/testdata/notarius-paths/symlink-target.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
reference: ../assets/target.txt
|
||||
sensitive: PATH_REFERENCE_SECRET_CANARY
|
||||
Reference in New Issue
Block a user