Select named imports before source admission

This commit is contained in:
2026-10-19 20:07:54 +00:00
parent b750b0fec6
commit 5f33e1e90c
3 changed files with 390 additions and 0 deletions

View File

@@ -0,0 +1,130 @@
package engine
import (
"errors"
"io/fs"
"path"
"path/filepath"
"sort"
)
type importSelectionOutcome uint8
const (
importSelectionNone importSelectionOutcome = iota
importSelectionHasCandidates
)
type selectedImportCandidate struct {
path string
accessPath string
site importFieldSite
}
// importSelection contains lexical candidates only; admission assigns source identity later.
type importSelection struct {
candidates []selectedImportCandidate
outcome importSelectionOutcome
}
func selectNamedImportCandidates(access *filesystemAccess, basePath string, spec importDeclarationSpec, policy resolvedImportPolicy) (importSelection, []Diagnostic) {
normalized := make([]selectedImportCandidate, 0, len(spec.paths))
seen := make(map[string]struct{}, len(spec.paths))
for index, relative := range spec.paths {
accessPath := access.join(basePath, relative)
candidatePath := accessPath
if access.native == nil {
candidatePath = filepath.ToSlash(filepath.Clean(accessPath))
}
if _, exists := seen[candidatePath]; exists {
continue
}
seen[candidatePath] = struct{}{}
var site importFieldSite
if index < len(spec.pathSites) {
site = spec.pathSites[index]
} else {
site = spec.fieldSites["path"]
}
normalized = append(normalized, selectedImportCandidate{path: candidatePath, accessPath: accessPath, site: site})
}
sort.Slice(normalized, func(left, right int) bool {
return normalized[left].path < normalized[right].path
})
candidates := make([]selectedImportCandidate, 0, len(normalized))
for _, candidate := range normalized {
missing, diagnostic := validateNamedImportTarget(access, candidate, policy.required)
if diagnostic != nil {
return importSelection{}, []Diagnostic{*diagnostic}
}
if missing {
continue
}
if excludedBasename(path.Base(candidate.path), policy.exclude) {
continue
}
candidates = append(candidates, candidate)
}
selection := importSelection{candidates: candidates}
if len(candidates) != 0 {
selection.outcome = importSelectionHasCandidates
}
return selection, nil
}
func validateNamedImportTarget(access *filesystemAccess, candidate selectedImportCandidate, required bool) (bool, *Diagnostic) {
if !access.contains(candidate.accessPath) {
return false, importSelectionDiagnostic(candidate.site, CodeImportOutsideRoot, "import target is outside the configuration root")
}
canonical, err := access.canonical(candidate.accessPath)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
if required {
return false, importSelectionDiagnostic(candidate.site, CodeImportMissing, "required import target does not exist")
}
return true, nil
}
if errors.Is(err, fs.ErrPermission) {
return false, importSelectionDiagnostic(candidate.site, CodeImportInvalid, "import target cannot be accessed")
}
return false, importSelectionDiagnostic(candidate.site, CodeImportInvalid, "import target could not be canonicalized")
}
if !access.contains(canonical) {
return false, importSelectionDiagnostic(candidate.site, CodeImportOutsideRoot, "import target resolves outside the configuration root")
}
info, err := access.stat(canonical)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
if required {
return false, importSelectionDiagnostic(candidate.site, CodeImportMissing, "required import target does not exist")
}
return true, nil
}
if errors.Is(err, fs.ErrPermission) {
return false, importSelectionDiagnostic(candidate.site, CodeImportInvalid, "import target cannot be accessed")
}
return false, importSelectionDiagnostic(candidate.site, CodeImportInvalid, "import target could not be inspected")
}
if !info.Mode().IsRegular() {
return false, importSelectionDiagnostic(candidate.site, CodeSourceNotRegular, "source is not a regular file")
}
return false, nil
}
func excludedBasename(name string, patterns []string) bool {
for _, pattern := range patterns {
matched, err := path.Match(pattern, name)
if err == nil && matched {
return true
}
}
return false
}
func importSelectionDiagnostic(site importFieldSite, code Code, message string) *Diagnostic {
location := site.location
return &Diagnostic{
Code: code, Severity: SeverityError, Path: site.path.clone(),
Message: message, Primary: &location,
}
}

View File

@@ -0,0 +1,257 @@
package engine
import (
"io/fs"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
func TestSelectNamedImportsHandlesPartialAndEmptySelections(t *testing.T) {
t.Parallel()
root := t.TempDir()
if err := os.WriteFile(filepath.Join(root, "present.yaml"), []byte("value: present\n"), 0o600); err != nil {
t.Fatal(err)
}
access, err := openHostAccess(root)
if err != nil {
t.Fatal(err)
}
defer access.close()
specs, diagnostics := parseImportSpecsYAML(t, " - path: [missing.yaml, present.yaml]\n")
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
selection, diagnostics := selectNamedImportCandidates(access, root, specs[0], resolvedImportPolicy{})
if len(diagnostics) != 0 {
t.Fatalf("optional partial selection diagnostics = %#v", diagnostics)
}
if selection.outcome != importSelectionHasCandidates || len(selection.candidates) != 1 || filepath.Base(selection.candidates[0].path) != "present.yaml" {
t.Fatalf("partial selection = %#v", selection)
}
requiredSpecs, diagnostics := parseImportSpecsYAML(t, " - path: [missing.yaml, present.yaml]\n required: true\n")
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
_, diagnostics = selectNamedImportCandidates(access, root, requiredSpecs[0], resolvedImportPolicy{required: true})
if len(diagnostics) != 1 || diagnostics[0].Code != CodeImportMissing {
t.Fatalf("required partial selection diagnostics = %#v", diagnostics)
}
emptySpecs, diagnostics := parseImportSpecsYAML(t, " - path: []\n required: true\n")
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
empty, diagnostics := selectNamedImportCandidates(access, root, emptySpecs[0], resolvedImportPolicy{required: true})
if len(diagnostics) != 0 || empty.outcome != importSelectionNone || len(empty.candidates) != 0 {
t.Fatalf("required empty path selection, diagnostics = %#v, %#v", empty, diagnostics)
}
}
func TestSelectNamedImportsChecksRequiredTargetsBeforeExclusions(t *testing.T) {
t.Parallel()
root := t.TempDir()
access, err := openHostAccess(root)
if err != nil {
t.Fatal(err)
}
defer access.close()
specs, diagnostics := parseImportSpecsYAML(t, " - path: missing.yaml\n required: true\n exclude: '*.yaml'\n")
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
policy, diagnostics := resolveImportPolicy(specs[0], importDefaults{})
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
_, diagnostics = selectNamedImportCandidates(access, root, specs[0], policy)
if len(diagnostics) != 1 || diagnostics[0].Code != CodeImportMissing || diagnostics[0].Path.String() != "/configkit/imports/0/path" {
t.Fatalf("required excluded missing target diagnostics = %#v", diagnostics)
}
}
func TestSelectNamedImportsDeduplicatesLexicalPathsButRetainsAliases(t *testing.T) {
t.Parallel()
root := t.TempDir()
if err := os.WriteFile(filepath.Join(root, "real.yaml"), []byte("value: real\n"), 0o600); err != nil {
t.Fatal(err)
}
if err := os.Symlink("real.yaml", filepath.Join(root, "alias.yaml")); err != nil {
t.Skipf("symlinks are unavailable: %v", err)
}
access, err := openHostAccess(root)
if err != nil {
t.Fatal(err)
}
defer access.close()
specs, diagnostics := parseImportSpecsYAML(t, " - path: [real.yaml, nested/../real.yaml, alias.yaml]\n")
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
selection, diagnostics := selectNamedImportCandidates(access, root, specs[0], resolvedImportPolicy{})
if len(diagnostics) != 0 {
t.Fatalf("selection diagnostics = %#v", diagnostics)
}
if len(selection.candidates) != 2 {
t.Fatalf("candidates = %#v, want two alias spellings", selection.candidates)
}
if filepath.Base(selection.candidates[0].path) != "alias.yaml" || filepath.Base(selection.candidates[1].path) != "real.yaml" {
t.Fatalf("candidate order = %#v", selection.candidates)
}
}
func TestSelectNamedImportsDoesNotOpenExcludedContentOrApplySearchControls(t *testing.T) {
t.Parallel()
filesystem := &statOnlyImportFilesystem{files: map[string]bool{"secret.yaml": true, ".private.yaml": true}}
access := newNativeAccess(filesystem, ".", &namespaceToken{})
specs, diagnostics := parseImportSpecsYAML(t, " - path: secret.yaml\n required: true\n exclude: '*.yaml'\n")
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
policy, diagnostics := resolveImportPolicy(specs[0], importDefaults{})
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
selection, diagnostics := selectNamedImportCandidates(access, ".", specs[0], policy)
if len(diagnostics) != 0 || selection.outcome != importSelectionNone {
t.Fatalf("excluded selection, diagnostics = %#v, %#v", selection, diagnostics)
}
if filesystem.openCalls != 0 || filesystem.statCalls == 0 {
t.Fatalf("excluded target access = %d opens, %d stats", filesystem.openCalls, filesystem.statCalls)
}
private, diagnostics := parseImportSpecsYAML(t, " - path: .private.yaml\n")
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
hidden := false
privatePolicy, policyDiagnostics := resolveImportPolicy(private[0], importDefaults{hidden: &hidden})
if len(policyDiagnostics) != 0 || !privatePolicy.hidden {
t.Fatalf("path selector accepted search-only defaults: %#v, %#v", privatePolicy, policyDiagnostics)
}
selection, diagnostics = selectNamedImportCandidates(access, ".", private[0], privatePolicy)
if len(diagnostics) != 0 || len(selection.candidates) != 1 || selection.candidates[0].path != ".private.yaml" {
t.Fatalf("explicit hidden path was filtered: %#v, %#v", selection, diagnostics)
}
if filesystem.openCalls != 0 {
t.Fatalf("named selection opened content %d times", filesystem.openCalls)
}
}
func TestSelectNamedImportsChecksNormalizedPathsLexically(t *testing.T) {
t.Parallel()
filesystem := &statOnlyImportFilesystem{files: map[string]bool{"a.yaml": true, "z.yaml": true}}
access := newNativeAccess(filesystem, ".", &namespaceToken{})
specs, diagnostics := parseImportSpecsYAML(t, " - path: [z.yaml, a.yaml]\n")
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
selection, diagnostics := selectNamedImportCandidates(access, ".", specs[0], resolvedImportPolicy{})
if len(diagnostics) != 0 || len(selection.candidates) != 2 {
t.Fatalf("selection, diagnostics = %#v, %#v", selection, diagnostics)
}
if strings.Join(filesystem.statPaths, ",") != "a.yaml,a.yaml,z.yaml,z.yaml" {
t.Fatalf("target stat order = %#v", filesystem.statPaths)
}
}
func TestSelectNamedImportsDoesNotTreatInvalidTargetsAsAbsence(t *testing.T) {
t.Parallel()
root := t.TempDir()
if err := os.Mkdir(filepath.Join(root, "directory"), 0o700); err != nil {
t.Fatal(err)
}
outside := filepath.Join(t.TempDir(), "outside.yaml")
if err := os.WriteFile(outside, []byte("value: outside\n"), 0o600); err != nil {
t.Fatal(err)
}
if err := os.Symlink(outside, filepath.Join(root, "escape.yaml")); err != nil {
t.Skipf("symlinks are unavailable: %v", err)
}
access, err := openHostAccess(root)
if err != nil {
t.Fatal(err)
}
defer access.close()
for _, test := range []struct {
name, value string
code Code
}{
{name: "nonregular target", value: "directory", code: CodeSourceNotRegular},
{name: "outside symlink", value: "escape.yaml", code: CodeImportOutsideRoot},
} {
t.Run(test.name, func(t *testing.T) {
specs, diagnostics := parseImportSpecsYAML(t, " - path: "+test.value+"\n")
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
_, diagnostics = selectNamedImportCandidates(access, root, specs[0], resolvedImportPolicy{})
if len(diagnostics) != 1 || diagnostics[0].Code != test.code {
t.Fatalf("diagnostics = %#v, want %s", diagnostics, test.code)
}
})
}
}
func TestSelectNamedImportsDoesNotTreatPermissionErrorsAsAbsence(t *testing.T) {
t.Parallel()
filesystem := &statOnlyImportFilesystem{statErrors: map[string]error{"denied.yaml": fs.ErrPermission}}
access := newNativeAccess(filesystem, ".", &namespaceToken{})
specs, diagnostics := parseImportSpecsYAML(t, " - path: denied.yaml\n")
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
_, diagnostics = selectNamedImportCandidates(access, ".", specs[0], resolvedImportPolicy{})
if len(diagnostics) != 1 || diagnostics[0].Code != CodeImportInvalid {
t.Fatalf("optional permission failure diagnostics = %#v", diagnostics)
}
}
type statOnlyImportFilesystem struct {
files map[string]bool
statErrors map[string]error
openCalls int
statCalls int
statPaths []string
}
func (filesystem *statOnlyImportFilesystem) Open(name string) (fs.File, error) {
filesystem.openCalls++
return nil, fs.ErrPermission
}
func (filesystem *statOnlyImportFilesystem) Stat(name string) (fs.FileInfo, error) {
filesystem.statCalls++
filesystem.statPaths = append(filesystem.statPaths, name)
if err := filesystem.statErrors[name]; err != nil {
return nil, err
}
if !filesystem.files[name] {
return nil, fs.ErrNotExist
}
return importSelectionFileInfo{name: pathBaseForTest(name)}, nil
}
type importSelectionFileInfo struct{ name string }
func (info importSelectionFileInfo) Name() string { return info.name }
func (importSelectionFileInfo) Size() int64 { return 1 }
func (importSelectionFileInfo) Mode() fs.FileMode { return 0o600 }
func (importSelectionFileInfo) ModTime() time.Time { return time.Time{} }
func (importSelectionFileInfo) IsDir() bool { return false }
func (importSelectionFileInfo) Sys() any { return nil }
func pathBaseForTest(name string) string { return strings.TrimPrefix(name, "./") }

View File

@@ -25,6 +25,7 @@ type importDeclarationSpec struct {
location Location location Location
selector importSelector selector importSelector
paths []string paths []string
pathSites []importFieldSite
glob string glob string
globSegments []string globSegments []string
required *bool required *bool
@@ -285,6 +286,8 @@ func parseImportDeclarationSpec(node *document.Node, declarationPath Path) (impo
badPath, badNode := listMemberSite(selectorEntry.Value, selectorPath, origins[index]) badPath, badNode := listMemberSite(selectorEntry.Value, selectorPath, origins[index])
return importDeclarationSpec{}, []Diagnostic{nodeImportDiagnostic(CodeImportInvalid, badPath, selectorEntry.Key+" contains an invalid source path", badNode)} return importDeclarationSpec{}, []Diagnostic{nodeImportDiagnostic(CodeImportInvalid, badPath, selectorEntry.Key+" contains an invalid source path", badNode)}
} }
badPath, badNode := listMemberSite(selectorEntry.Value, selectorPath, origins[index])
spec.pathSites = append(spec.pathSites, importFieldSite{path: badPath.clone(), location: publicLocation(badNode.Location)})
} }
spec.paths = values spec.paths = values
case importSelectorGlob: case importSelectorGlob: