Resolve native imports beneath nested roots
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Import Discovery and Scheduling Review Findings
|
||||
|
||||
Status: in progress; issues 1–2 complete, four findings remain.
|
||||
Status: in progress; issues 1–3 complete, three findings remain.
|
||||
|
||||
Reviewed implementation: `aa93aed..15943e2`, ending at
|
||||
`15943e233ca3cd1286c03c0e7085871db344cd4b`, against the accepted
|
||||
@@ -92,7 +92,7 @@ items, underlays, and selected main items, and failed before the change. The
|
||||
full everyday check passed, including existing default/enabled source-version
|
||||
and assigned-index ownership regressions.
|
||||
|
||||
## 3. [ ] P2 — Accept valid multi-component native filesystem roots
|
||||
## 3. [x] P2 — Accept valid multi-component native filesystem roots
|
||||
|
||||
Owner: [resolveNativeImportPath](../../internal/engine/filesystem_links.go),
|
||||
especially lines 170–181.
|
||||
@@ -114,6 +114,12 @@ resolved targets. Test directory and glob discovery under multi-component roots,
|
||||
with ordinary entries and supported symlinks. Do not require consumers to flatten
|
||||
their roots or remove optional link-inspection capability.
|
||||
|
||||
Completed: native link resolution allows ancestor prefixes while resolving a
|
||||
contained multi-component path; selected paths and resolved link targets still
|
||||
undergo their existing containment checks. Public directory/glob regressions
|
||||
cover ordinary and aliased roots plus escaping links. All four valid cases
|
||||
failed before the fix. The full everyday check passed.
|
||||
|
||||
## 4. [ ] P2 — Distinguish finite symlink reuse from actual cycles
|
||||
|
||||
Owners: [resolveHostImportPath and resolveNativeImportPath](../../internal/engine/filesystem_links.go),
|
||||
|
||||
52
facade_import_root_test.go
Normal file
52
facade_import_root_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package configkit_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
configkit "gitea.maximumdirect.net/eric/ConfigKit"
|
||||
)
|
||||
|
||||
func TestFacadeNativeDiscoveryWithNestedRoot(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
parts := filepath.Join(root, "nested/root/parts")
|
||||
if err := os.MkdirAll(parts, 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(parts, "value.yml"), []byte("value: 1\n"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Symlink("parts", filepath.Join(root, "nested/root/linked")); err != nil {
|
||||
t.Skipf("symlinks unavailable: %v", err)
|
||||
}
|
||||
filesystem := configkit.NewFilesystem("nested", os.DirFS(root))
|
||||
context := &configkit.FilesystemContext{FS: &filesystem, Root: "nested/root", BaseDir: "nested/root"}
|
||||
for _, selector := range []string{
|
||||
"dir: parts", "glob: 'parts/*.yml'", "dir: linked", "glob: 'lin*/*.yml'",
|
||||
} {
|
||||
t.Run(selector, func(t *testing.T) {
|
||||
result, err := configkit.Load(configkit.Bytes("main", []byte("configkit:\n imports:\n - "+selector+"\n")), configkit.Options[map[string]any]{Imports: true, Filesystem: context})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result.Config["value"] != int64(1) {
|
||||
t.Fatalf("configuration = %#v", result.Config)
|
||||
}
|
||||
})
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(root, "nested/outside.yml"), []byte("value: outside\n"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Symlink("../../outside.yml", filepath.Join(parts, "escape.yml")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, selector := range []string{"dir: parts", "glob: 'parts/*.yml'"} {
|
||||
_, err := configkit.Load(configkit.Bytes("main", []byte("configkit:\n imports:\n - "+selector+"\n")), configkit.Options[map[string]any]{Imports: true, Filesystem: context})
|
||||
var loadError *configkit.LoadError
|
||||
if !errors.As(err, &loadError) || len(loadError.Diagnostics()) != 1 || loadError.Diagnostics()[0].Code != configkit.CodeImportOutsideRoot {
|
||||
t.Fatalf("escaping %s: %v", selector, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -177,7 +177,9 @@ func resolveNativeImportPath(access *filesystemAccess, filesystem fs.ReadLinkFS,
|
||||
if candidate == "" {
|
||||
candidate = "."
|
||||
}
|
||||
if !access.contains(candidate) {
|
||||
// Ancestors of a multi-component root are resolution prefixes, not
|
||||
// selected targets. Complete paths and link targets remain contained.
|
||||
if !access.contains(candidate) && !strings.HasPrefix(access.rootPath, candidate+"/") {
|
||||
return "", errImportPathOutsideRoot
|
||||
}
|
||||
info, err := filesystem.Lstat(candidate)
|
||||
|
||||
Reference in New Issue
Block a user