Skip to content

Commit b92cee2

Browse files
committed
Read resources from multiple files
1 parent abe3637 commit b92cee2

File tree

2 files changed

+157
-8
lines changed

2 files changed

+157
-8
lines changed

cmd/print.go

Lines changed: 82 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package cmd
1919
import (
2020
"fmt"
2121
"os"
22+
"path/filepath"
2223
"slices"
2324
"strings"
2425

@@ -49,8 +50,11 @@ type PrintRunner struct {
4950
// Defaults to YAML.
5051
outputFormat string
5152

52-
// The path to the input yaml config file. Value assigned via --input-file flag
53-
inputFile string
53+
// inputFiles contains the paths to YAML manifest files to process. Value assigned via --input-files flag.
54+
inputPaths []string
55+
56+
// recursive enables recursive directory traversal when using --input-dir.
57+
recursive bool
5458

5559
// The namespace used to query Gateway API objects. Value assigned via
5660
// --namespace/-n flag.
@@ -88,7 +92,43 @@ func (pr *PrintRunner) PrintGatewayAPIObjects(cmd *cobra.Command, _ []string) er
8892
return fmt.Errorf("failed to initialize namespace filter: %w", err)
8993
}
9094

91-
gatewayResources, notificationTablesMap, err := i2gw.ToGatewayAPIResources(cmd.Context(), pr.namespaceFilter, pr.inputFile, pr.providers, pr.getProviderSpecificFlags())
95+
allFiles := []string{}
96+
97+
for _, path := range pr.inputPaths {
98+
// Check if path is a directory
99+
if info, err := os.Stat(path); err == nil && info.IsDir() {
100+
// Discover manifest files in directory
101+
dirFiles, err := discoverManifestFiles(path, pr.recursive)
102+
if err != nil {
103+
return fmt.Errorf("error reading directory %s: %w", path, err)
104+
}
105+
allFiles = append(allFiles, dirFiles...)
106+
} else {
107+
allFiles = append(allFiles, path)
108+
}
109+
}
110+
111+
if len(allFiles) > 0 {
112+
for _, inputFile := range allFiles {
113+
fmt.Fprintf(os.Stdout, "=== File: %s ===\n", inputFile)
114+
gatewayResources, notificationTablesMap, err := i2gw.ToGatewayAPIResources(cmd.Context(), pr.namespaceFilter, inputFile, pr.providers, pr.getProviderSpecificFlags())
115+
116+
for _, table := range notificationTablesMap {
117+
fmt.Fprintln(os.Stderr, table)
118+
}
119+
120+
if err != nil {
121+
fmt.Fprintf(os.Stdout, "# Error: %v\n", err)
122+
} else {
123+
pr.outputResult(gatewayResources)
124+
}
125+
126+
fmt.Fprintf(os.Stdout, "=== End: %s ===\n\n", inputFile)
127+
}
128+
return nil
129+
}
130+
131+
gatewayResources, notificationTablesMap, err := i2gw.ToGatewayAPIResources(cmd.Context(), pr.namespaceFilter, "", pr.providers, pr.getProviderSpecificFlags())
92132
if err != nil {
93133
return err
94134
}
@@ -281,16 +321,18 @@ func (pr *PrintRunner) initializeResourcePrinter() error {
281321
// 3. If namespace is specified, it filters resources based on that namespace.
282322
// 4. If no namespace is specified and reading from the cluster, it attempts to get the namespace from the cluster; if unsuccessful, initialization fails.
283323
func (pr *PrintRunner) initializeNamespaceFilter() error {
324+
hasFileInput := len(pr.inputPaths) > 0
325+
284326
// When we should use all namespaces, empty string is used as the filter.
285-
if pr.allNamespaces || (pr.inputFile != "" && pr.namespace == "") {
327+
if pr.allNamespaces || (hasFileInput && pr.namespace == "") {
286328
pr.namespaceFilter = ""
287329
return nil
288330
}
289331

290332
// If namespace flag is not specified, try to use the default namespace from the cluster
291333
if pr.namespace == "" {
292334
ns, err := getNamespaceInCurrentContext()
293-
if err != nil && pr.inputFile == "" {
335+
if err != nil && len(pr.inputPaths) == 0 {
294336
// When asked to read from the cluster, but getting the current namespace
295337
// failed for whatever reason - do not process the request.
296338
return err
@@ -326,8 +368,11 @@ func newPrintCommand() *cobra.Command {
326368
cmd.Flags().StringVarP(&pr.outputFormat, "output", "o", "yaml",
327369
"Output format. One of: (yaml, json, kyaml).")
328370

329-
cmd.Flags().StringVar(&pr.inputFile, "input-file", "",
330-
`Path to the manifest file. When set, the tool will read ingresses from the file instead of reading from the cluster. Supported files are yaml and json.`)
371+
cmd.Flags().StringSliceVar(&pr.inputPaths, "input-file", []string{},
372+
`Path to manifest file(s) or directory(s). When set, the tool will read ingresses from the specified files or all YAML files in directories. Supported files are yaml and json.`)
373+
374+
cmd.Flags().BoolVar(&pr.recursive, "recursive", false,
375+
`When used with --input-file, recursively explore nested directories.`)
331376

332377
cmd.Flags().StringVarP(&pr.namespace, "namespace", "n", "",
333378
`If present, the namespace scope for this CLI request.`)
@@ -397,3 +442,33 @@ func PrintUnstructuredAsYaml(obj *unstructured.Unstructured) error {
397442

398443
return nil
399444
}
445+
446+
// discoverManifestFiles finds all manifest files in a directory
447+
func discoverManifestFiles(dir string, recursive bool) ([]string, error) {
448+
var files []string
449+
450+
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
451+
if err != nil {
452+
return err
453+
}
454+
455+
// Skip other directories if not in recursive mode
456+
if info.IsDir() && !recursive && path != dir {
457+
return filepath.SkipDir
458+
}
459+
460+
if !info.IsDir() && isManifestFile(path) {
461+
files = append(files, path)
462+
}
463+
464+
return nil
465+
})
466+
467+
return files, err
468+
}
469+
470+
// isManifestFile checks if a file is a YAML or JSON manifest
471+
func isManifestFile(path string) bool {
472+
ext := strings.ToLower(filepath.Ext(path))
473+
return ext == ".yaml" || ext == ".yml" || ext == ".json"
474+
}

cmd/print_test.go

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,14 @@ func Test_getNamespaceFilter(t *testing.T) {
149149

150150
for _, tc := range testCases {
151151
t.Run(tc.name, func(t *testing.T) {
152+
var inputPaths []string
153+
if tc.inputfile != "" {
154+
inputPaths = []string{tc.inputfile}
155+
}
152156
pr := PrintRunner{
153157
namespace: tc.namespace,
154158
allNamespaces: tc.allNamespaces,
155-
inputFile: tc.inputfile,
159+
inputPaths: inputPaths,
156160
}
157161
err = pr.initializeNamespaceFilter()
158162

@@ -320,3 +324,73 @@ func Test_getProviderSpecificFlags(t *testing.T) {
320324
})
321325
}
322326
}
327+
328+
func TestDiscoverManifestFiles(t *testing.T) {
329+
tmpDir := t.TempDir()
330+
subDir := filepath.Join(tmpDir, "subdir")
331+
nestedDir := filepath.Join(subDir, "nested")
332+
os.MkdirAll(nestedDir, 0755)
333+
files := map[string]string{
334+
"test1.yaml": "test",
335+
"test2.yml": "test",
336+
"test3.json": "test",
337+
//.txt should be ignored
338+
"test4.txt": "test",
339+
//to test --recursive
340+
"subdir/sub.yaml": "test",
341+
"subdir/nested/deep.yml": "test",
342+
"subdir/nested/config.json": "test",
343+
}
344+
345+
for path, content := range files {
346+
fullPath := filepath.Join(tmpDir, path)
347+
os.WriteFile(fullPath, []byte(content), 0644)
348+
}
349+
350+
tests := []struct {
351+
name string
352+
recursive bool
353+
wantCount int
354+
}{
355+
{"non-recursive finds only top level", false, 3},
356+
{"recursive finds all manifest files", true, 6},
357+
}
358+
359+
for _, tt := range tests {
360+
t.Run(tt.name, func(t *testing.T) {
361+
filesFound, err := discoverManifestFiles(tmpDir, tt.recursive)
362+
if err != nil {
363+
t.Fatalf("Unexpected error: %v", err)
364+
}
365+
if len(filesFound) != tt.wantCount {
366+
t.Errorf("Expected %d files, got %d", tt.wantCount, len(filesFound))
367+
}
368+
})
369+
}
370+
371+
// To test non-existing directory
372+
_, err := discoverManifestFiles("/non/existent/path", false)
373+
if err == nil {
374+
t.Error("Expected error for non-existent directory")
375+
}
376+
}
377+
378+
func TestIsManifestFile(t *testing.T) {
379+
tests := []struct {
380+
path string
381+
want bool
382+
}{
383+
{"test.yaml", true},
384+
{"test.yml", true},
385+
{"test.json", true},
386+
{"test.txt", false},
387+
{"test", false},
388+
}
389+
390+
for _, tt := range tests {
391+
got := isManifestFile(tt.path)
392+
if got != tt.want {
393+
t.Errorf("isManifestFile(%q) = %v, want %v", tt.path, got, tt.want)
394+
}
395+
}
396+
}

0 commit comments

Comments
 (0)