@@ -19,6 +19,7 @@ package cmd
1919import (
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,55 @@ 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 (
115+ cmd .Context (),
116+ pr .namespaceFilter ,
117+ inputFile ,
118+ pr .providers ,
119+ pr .getProviderSpecificFlags (),
120+ )
121+
122+ for _ , table := range notificationTablesMap {
123+ fmt .Fprintln (os .Stderr , table )
124+ }
125+
126+ if err != nil {
127+ fmt .Fprintf (os .Stdout , "# Error: %v\n " , err )
128+ } else {
129+ pr .outputResult (gatewayResources )
130+ }
131+
132+ fmt .Fprintf (os .Stdout , "=== End: %s ===\n \n " , inputFile )
133+ }
134+ return nil
135+ }
136+
137+ gatewayResources , notificationTablesMap , err := i2gw .ToGatewayAPIResources (
138+ cmd .Context (),
139+ pr .namespaceFilter ,
140+ "" ,
141+ pr .providers ,
142+ pr .getProviderSpecificFlags (),
143+ )
92144 if err != nil {
93145 return err
94146 }
@@ -281,16 +333,18 @@ func (pr *PrintRunner) initializeResourcePrinter() error {
281333// 3. If namespace is specified, it filters resources based on that namespace.
282334// 4. If no namespace is specified and reading from the cluster, it attempts to get the namespace from the cluster; if unsuccessful, initialization fails.
283335func (pr * PrintRunner ) initializeNamespaceFilter () error {
336+ hasFileInput := len (pr .inputPaths ) > 0
337+
284338 // When we should use all namespaces, empty string is used as the filter.
285- if pr .allNamespaces || (pr . inputFile != "" && pr .namespace == "" ) {
339+ if pr .allNamespaces || (hasFileInput && pr .namespace == "" ) {
286340 pr .namespaceFilter = ""
287341 return nil
288342 }
289343
290344 // If namespace flag is not specified, try to use the default namespace from the cluster
291345 if pr .namespace == "" {
292346 ns , err := getNamespaceInCurrentContext ()
293- if err != nil && pr .inputFile == "" {
347+ if err != nil && len ( pr .inputPaths ) == 0 {
294348 // When asked to read from the cluster, but getting the current namespace
295349 // failed for whatever reason - do not process the request.
296350 return err
@@ -326,8 +380,11 @@ func newPrintCommand() *cobra.Command {
326380 cmd .Flags ().StringVarP (& pr .outputFormat , "output" , "o" , "yaml" ,
327381 "Output format. One of: (yaml, json, kyaml)." )
328382
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.` )
383+ cmd .Flags ().StringSliceVar (& pr .inputPaths , "input-file" , []string {},
384+ `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.` )
385+
386+ cmd .Flags ().BoolVar (& pr .recursive , "recursive" , false ,
387+ `When used with --input-file, recursively explore nested directories.` )
331388
332389 cmd .Flags ().StringVarP (& pr .namespace , "namespace" , "n" , "" ,
333390 `If present, the namespace scope for this CLI request.` )
@@ -397,3 +454,33 @@ func PrintUnstructuredAsYaml(obj *unstructured.Unstructured) error {
397454
398455 return nil
399456}
457+
458+ // discoverManifestFiles finds all manifest files in a directory
459+ func discoverManifestFiles (dir string , recursive bool ) ([]string , error ) {
460+ var files []string
461+
462+ err := filepath .Walk (dir , func (path string , info os.FileInfo , err error ) error {
463+ if err != nil {
464+ return err
465+ }
466+
467+ // Skip other directories if not in recursive mode
468+ if info .IsDir () && ! recursive && path != dir {
469+ return filepath .SkipDir
470+ }
471+
472+ if ! info .IsDir () && isManifestFile (path ) {
473+ files = append (files , path )
474+ }
475+
476+ return nil
477+ })
478+
479+ return files , err
480+ }
481+
482+ // isManifestFile checks if a file is a YAML or JSON manifest
483+ func isManifestFile (path string ) bool {
484+ ext := strings .ToLower (filepath .Ext (path ))
485+ return ext == ".yaml" || ext == ".yml" || ext == ".json"
486+ }
0 commit comments