55 "context"
66 "encoding/json"
77 "errors"
8- "flag"
98 "fmt"
109 "log/slog"
1110 "os"
@@ -22,6 +21,7 @@ import (
2221 "github.com/joschi/java-metadata/internal/output"
2322 "github.com/joschi/java-metadata/internal/providers"
2423 "github.com/joschi/java-metadata/internal/providers/allproviders"
24+ "github.com/spf13/cobra"
2525
2626 "go.opentelemetry.io/otel"
2727 "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp"
@@ -36,83 +36,10 @@ import (
3636)
3737
3838func main () {
39- // Global flags
40- logLevel := flag .String ("log-level" , "info" , "Log level (debug, info, warn, error)" )
41- verbose := flag .Bool ("verbose" , false , "Enable verbose output (same as --log-level=debug)" )
42- quiet := flag .Bool ("quiet" , false , "Quiet mode (same as --log-level=error)" )
43-
44- updateCmd := flag .NewFlagSet ("update" , flag .ExitOnError )
45- metadataDir := updateCmd .String ("metadata-dir" , "./docs/metadata" , "Output directory for metadata" )
46- checksumDir := updateCmd .String ("checksum-dir" , "./docs/checksums" , "Output directory for checksums" )
47- concurrency := updateCmd .Int ("concurrency" , 4 , "Number of concurrent provider fetches" )
48- downloadConcurrency := updateCmd .Int ("download-concurrency" , 3 , "Number of concurrent downloads" )
49- maxRetries := updateCmd .Int ("max-retries" , 3 , "Maximum number of retry attempts for downloads" )
50- providerTimeout := updateCmd .Duration ("provider-timeout" , 5 * time .Minute , "Per-provider timeout (e.g. 2m, 30s)" )
51-
52- validateCmd := flag .NewFlagSet ("validate" , flag .ExitOnError )
53- validateMetadataDir := validateCmd .String ("metadata-dir" , "./docs/metadata" , "Directory containing metadata files" )
54- validateConcurrency := validateCmd .Int ("concurrency" , 10 , "Number of concurrent URL checks" )
55- validateDelete := validateCmd .Bool ("delete" , false , "Delete files that fail validation" )
56-
57- if len (os .Args ) < 2 {
58- fmt .Println ("Usage: java-metadata [global-options] <command> [options]" )
59- fmt .Println ("\n Global Options:" )
60- flag .PrintDefaults ()
61- fmt .Println ("\n Commands:" )
62- fmt .Println (" update Fetch and update metadata for all vendors" )
63- fmt .Println (" validate Validate URLs in metadata files" )
64- os .Exit (1 )
65- }
66-
67- // Parse global flags
68- flag .Parse ()
69-
70- // Configure logging
71- level := logger .ParseLevel (* logLevel )
72- if * verbose {
73- level = logger .LevelDebug
74- }
75- if * quiet {
76- level = logger .LevelError
77- }
78- logger .SetLevel (level )
79-
39+ // Root context and signal handling
8040 rootCtx := context .Background ()
81-
82- // Initialize OpenTelemetry with standard environment variable configuration
83- res , err := initResource (rootCtx )
84- if err != nil {
85- logger .Error (rootCtx , "failed to initialize resource" , "error" , err )
86- os .Exit (1 )
87- }
88- tp , err := initTracer (rootCtx , res )
89- if err != nil {
90- logger .Error (rootCtx , "failed to initialize tracer" , "error" , err )
91- // Continue without tracing rather than failing
92- } else if tp != nil {
93- defer func () {
94- if err := tp .Shutdown (rootCtx ); err != nil {
95- logger .Error (rootCtx , "error shutting down tracer provider" , "error" , err )
96- }
97- }()
98- }
99- lp , err := initLogger (rootCtx , res )
100- if err != nil {
101- logger .Error (rootCtx , "failed to initialize logger" , "error" , err )
102- // Continue without logging rather than failing
103- } else if lp != nil {
104- defer func () {
105- if err := lp .Shutdown (rootCtx ); err != nil {
106- logger .Error (rootCtx , "error shutting down logger provider" , "error" , err )
107- }
108- }()
109- }
110-
111- // Root context to allow coordinated cancellation (e.g., future signal handling)
11241 ctx , cancel := context .WithCancel (rootCtx )
11342 defer cancel ()
114-
115- // Handle SIGINT/SIGTERM to cancel in-flight work
11643 sigs := make (chan os.Signal , 1 )
11744 signal .Notify (sigs , syscall .SIGINT , syscall .SIGTERM )
11845 go func () {
@@ -121,21 +48,108 @@ func main() {
12148 cancel ()
12249 }()
12350
124- switch os .Args [1 ] {
125- case "update" :
126- updateCmd .Parse (os .Args [2 :])
127- if err := runUpdate (ctx , * metadataDir , * checksumDir , * concurrency , * downloadConcurrency , * maxRetries , * providerTimeout ); err != nil {
128- logger .Error (ctx , "update failed" , "error" , err )
129- os .Exit (1 )
130- }
131- case "validate" :
132- validateCmd .Parse (os .Args [2 :])
133- if err := runValidate (ctx , * validateMetadataDir , * validateConcurrency , * validateDelete ); err != nil {
134- logger .Error (ctx , "validation failed" , "error" , err )
135- os .Exit (1 )
136- }
137- default :
138- fmt .Printf ("Unknown command: %s\n " , os .Args [1 ])
51+ // Global options
52+ var (
53+ optLogLevel string
54+ optVerbose bool
55+ optQuiet bool
56+ )
57+
58+ rootCmd := & cobra.Command {
59+ Use : "java-metadata" ,
60+ Short : "Collect and validate Java JDK/JRE metadata" ,
61+ PersistentPreRunE : func (cmd * cobra.Command , args []string ) error {
62+ // Configure logging from persistent flags
63+ level := logger .ParseLevel (optLogLevel )
64+ if optVerbose {
65+ level = logger .LevelDebug
66+ }
67+ if optQuiet {
68+ level = logger .LevelError
69+ }
70+ logger .SetLevel (level )
71+
72+ // Initialize OpenTelemetry
73+ res , err := initResource (cmd .Context ())
74+ if err != nil {
75+ logger .Error (cmd .Context (), "failed to initialize resource" , "error" , err )
76+ return nil // continue without resource
77+ }
78+ tp , err := initTracer (cmd .Context (), res )
79+ if err == nil && tp != nil {
80+ // Ensure shutdown on exit
81+ defer func () {
82+ if err := tp .Shutdown (rootCtx ); err != nil {
83+ logger .Error (rootCtx , "error shutting down tracer provider" , "error" , err )
84+ }
85+ }()
86+ }
87+ lp , err := initLogger (cmd .Context (), res )
88+ if err == nil && lp != nil {
89+ defer func () {
90+ if err := lp .Shutdown (rootCtx ); err != nil {
91+ logger .Error (rootCtx , "error shutting down logger provider" , "error" , err )
92+ }
93+ }()
94+ }
95+ return nil
96+ },
97+ Run : func (cmd * cobra.Command , args []string ) {
98+ // Show help when no subcommand is provided
99+ _ = cmd .Help ()
100+ },
101+ }
102+
103+ // Persistent flags
104+ rootCmd .PersistentFlags ().StringVar (& optLogLevel , "log-level" , "info" , "Log level (debug, info, warn, error)" )
105+ rootCmd .PersistentFlags ().BoolVar (& optVerbose , "verbose" , false , "Enable verbose output (same as --log-level=debug)" )
106+ rootCmd .PersistentFlags ().BoolVar (& optQuiet , "quiet" , false , "Quiet mode (same as --log-level=error)" )
107+
108+ // Update command flags
109+ var (
110+ metadataDir string
111+ checksumDir string
112+ concurrency int
113+ downloadConcurrency int
114+ maxRetries int
115+ providerTimeout time.Duration
116+ )
117+ updateCmd := & cobra.Command {
118+ Use : "update" ,
119+ Short : "Fetch and update metadata for all vendors" ,
120+ RunE : func (cmd * cobra.Command , args []string ) error {
121+ return runUpdate (cmd .Context (), metadataDir , checksumDir , concurrency , downloadConcurrency , maxRetries , providerTimeout )
122+ },
123+ }
124+ updateCmd .Flags ().StringVar (& metadataDir , "metadata-dir" , "./docs/metadata" , "Output directory for metadata" )
125+ updateCmd .Flags ().StringVar (& checksumDir , "checksum-dir" , "./docs/checksums" , "Output directory for checksums" )
126+ updateCmd .Flags ().IntVar (& concurrency , "concurrency" , 4 , "Number of concurrent provider fetches" )
127+ updateCmd .Flags ().IntVar (& downloadConcurrency , "download-concurrency" , 3 , "Number of concurrent downloads" )
128+ updateCmd .Flags ().IntVar (& maxRetries , "max-retries" , 3 , "Maximum number of retry attempts for downloads" )
129+ updateCmd .Flags ().DurationVar (& providerTimeout , "provider-timeout" , 5 * time .Minute , "Per-provider timeout (e.g. 2m, 30s)" )
130+
131+ // Validate command flags
132+ var (
133+ validateMetadataDir string
134+ validateConcurrency int
135+ validateDelete bool
136+ )
137+ validateCmd := & cobra.Command {
138+ Use : "validate" ,
139+ Short : "Validate URLs in metadata files" ,
140+ RunE : func (cmd * cobra.Command , args []string ) error {
141+ return runValidate (cmd .Context (), validateMetadataDir , validateConcurrency , validateDelete )
142+ },
143+ }
144+ validateCmd .Flags ().StringVar (& validateMetadataDir , "metadata-dir" , "./docs/metadata" , "Directory containing metadata files" )
145+ validateCmd .Flags ().IntVar (& validateConcurrency , "concurrency" , 10 , "Number of concurrent URL checks" )
146+ validateCmd .Flags ().BoolVar (& validateDelete , "delete" , false , "Delete files that fail validation" )
147+
148+ rootCmd .AddCommand (updateCmd , validateCmd )
149+ rootCmd .SetContext (ctx )
150+
151+ if err := rootCmd .Execute (); err != nil {
152+ logger .Error (ctx , "command failed" , "error" , err )
139153 os .Exit (1 )
140154 }
141155}
0 commit comments