Skip to content

Commit 2b4bcf5

Browse files
authored
script to validate framework imports (#2337)
Signed-off-by: Etai Lev Ran <elevran@gmail.com>
1 parent 2c2fe3b commit 2b4bcf5

1 file changed

Lines changed: 170 additions & 0 deletions

File tree

hack/verify-framework-imports.go

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
//go:build ignore
2+
// +build ignore
3+
4+
/*
5+
Copyright 2026 The Kubernetes Authors.
6+
7+
Licensed under the Apache License, Version 2.0 (the "License");
8+
you may not use this file except in compliance with the License.
9+
You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing, software
14+
distributed under the License is distributed on an "AS IS" BASIS,
15+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
See the License for the specific language governing permissions and
17+
limitations under the License.
18+
*/
19+
20+
// validate-framework-imports validates that files under pkg/epp/framework
21+
// only import other files from within pkg/epp/framework (or external
22+
// dependencies).
23+
package main
24+
25+
import (
26+
"errors"
27+
"fmt"
28+
"go/parser"
29+
"go/token"
30+
"os"
31+
"path/filepath"
32+
"strings"
33+
34+
"github.com/spf13/pflag"
35+
)
36+
37+
const (
38+
frameworkPath = "./pkg/epp/framework"
39+
repoModule = "sigs.k8s.io/gateway-api-inference-extension"
40+
)
41+
42+
// allowedExceptions lists additional import paths that are allowed
43+
// for framework files beyond pkg/epp/framework itself.
44+
// Add paths here that the framework is permitted to import from other
45+
// parts of the repository.
46+
var allowedExceptions = []string{
47+
"pkg/common/observability/logging",
48+
// "pkg/epp/util/error",
49+
}
50+
51+
var (
52+
additionalAllowed []string
53+
)
54+
55+
func main() {
56+
pflag.StringSliceVar(&additionalAllowed, "allow", []string{}, "Additional allowed import paths via command line (can be specified multiple times)")
57+
pflag.Parse()
58+
59+
if err := run(); err != nil {
60+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
61+
os.Exit(1)
62+
}
63+
}
64+
65+
// isAllowedImport checks if the given import path starts with any allowed path
66+
func isAllowedImport(importPath string, allowedPaths map[string]struct{}) bool {
67+
for allowedPath := range allowedPaths {
68+
if strings.HasPrefix(importPath, allowedPath) {
69+
return true
70+
}
71+
}
72+
return false
73+
}
74+
75+
func run() error {
76+
violations := []string{}
77+
78+
// build the list of allowed paths with deduplication
79+
allowedPathsMap := make(map[string]struct{})
80+
allowedPathsMap["pkg/epp/framework"] = struct{}{} // always allowed
81+
82+
// add built-in exceptions
83+
for _, path := range allowedExceptions {
84+
allowedPathsMap[path] = struct{}{}
85+
}
86+
87+
// add exceptions from command-line
88+
for _, path := range additionalAllowed {
89+
allowedPathsMap[path] = struct{}{}
90+
}
91+
92+
// Convert map keys to slice for display purposes only
93+
allowedPathsList := make([]string, 0, len(allowedPathsMap))
94+
for path := range allowedPathsMap {
95+
allowedPathsList = append(allowedPathsList, path)
96+
}
97+
98+
fmt.Printf("Validating imports in %s\n", frameworkPath)
99+
if len(allowedExceptions) > 0 {
100+
fmt.Printf("Allowed exceptions (hardcoded): %v\n", allowedExceptions)
101+
}
102+
if len(additionalAllowed) > 0 {
103+
fmt.Printf("Additional allowed paths (via flags): %v\n", additionalAllowed)
104+
}
105+
if len(allowedPathsList) > 1 {
106+
fmt.Printf("Total unique allowed paths: %v\n", allowedPathsList)
107+
}
108+
fmt.Println()
109+
110+
// walk through all Go files in pkg/epp/framework
111+
err := filepath.Walk(frameworkPath, func(path string, info os.FileInfo, err error) error {
112+
if err != nil {
113+
return err
114+
}
115+
116+
if info.IsDir() || !strings.HasSuffix(path, ".go") {
117+
return nil
118+
}
119+
120+
// skip test files if desired (currently we check them too)
121+
// if strings.HasSuffix(path, "_test.go") {
122+
// return nil
123+
// }
124+
125+
// parse the Go file
126+
fset := token.NewFileSet()
127+
node, err := parser.ParseFile(fset, path, nil, parser.ImportsOnly)
128+
if err != nil {
129+
return fmt.Errorf("failed to parse %s: %w", path, err)
130+
}
131+
132+
// check imports
133+
for _, imp := range node.Imports {
134+
importPath := strings.Trim(imp.Path.Value, `"`)
135+
136+
if strings.HasPrefix(importPath, repoModule) {
137+
relPath := strings.TrimPrefix(importPath, repoModule+"/")
138+
139+
// Check if the import path starts with any allowed path
140+
allowed := isAllowedImport(relPath, allowedPathsMap)
141+
142+
if !allowed {
143+
violations = append(violations, fmt.Sprintf(
144+
"%s: imports %s (not in allowed paths)",
145+
path, importPath,
146+
))
147+
}
148+
}
149+
}
150+
151+
return nil
152+
})
153+
154+
if err != nil {
155+
return fmt.Errorf("failed to walk directory: %w", err)
156+
}
157+
158+
if len(violations) > 0 { // report results
159+
fmt.Printf("\nAllowed paths: %v\n", allowedPathsList)
160+
fmt.Printf("❌ Found %d import violations:\n", len(violations))
161+
fmt.Println()
162+
for _, v := range violations {
163+
fmt.Println(" " + v)
164+
}
165+
return errors.New("import validation failed")
166+
}
167+
168+
fmt.Printf("✅ All %s imports are valid!\n", frameworkPath)
169+
return nil
170+
}

0 commit comments

Comments
 (0)