Resolve native imports beneath nested roots
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# Import Discovery and Scheduling Review Findings
|
# 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
|
Reviewed implementation: `aa93aed..15943e2`, ending at
|
||||||
`15943e233ca3cd1286c03c0e7085871db344cd4b`, against the accepted
|
`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
|
full everyday check passed, including existing default/enabled source-version
|
||||||
and assigned-index ownership regressions.
|
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),
|
Owner: [resolveNativeImportPath](../../internal/engine/filesystem_links.go),
|
||||||
especially lines 170–181.
|
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
|
with ordinary entries and supported symlinks. Do not require consumers to flatten
|
||||||
their roots or remove optional link-inspection capability.
|
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
|
## 4. [ ] P2 — Distinguish finite symlink reuse from actual cycles
|
||||||
|
|
||||||
Owners: [resolveHostImportPath and resolveNativeImportPath](../../internal/engine/filesystem_links.go),
|
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 == "" {
|
if candidate == "" {
|
||||||
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
|
return "", errImportPathOutsideRoot
|
||||||
}
|
}
|
||||||
info, err := filesystem.Lstat(candidate)
|
info, err := filesystem.Lstat(candidate)
|
||||||
|
|||||||
Reference in New Issue
Block a user