99 "strings"
1010
1111 "prometheus-cli/internal/completion"
12+ "prometheus-cli/internal/config"
1213 "prometheus-cli/internal/display"
1314 "prometheus-cli/internal/prometheus"
1415
@@ -17,58 +18,83 @@ import (
1718 "github.com/prometheus/common/version"
1819)
1920
20- // Command-line flags for configuring the application behavior.
21- var (
22- // Prometheus Connection Flags
23- url = kingpin .Flag ("url" , "Prometheus server URL." ).Default ("http://localhost:9090" ).String ()
24- username = kingpin .Flag ("username" , "Username for basic authentication." ).Envar ("PROM_USERNAME" ).String ()
25- password = kingpin .Flag ("password" , "Password for basic authentication." ).Envar ("PROM_PASSWORD" ).String ()
26- passwordFile = kingpin .Flag ("password-file" , "Path to file containing password for basic authentication." ).String ()
27- insecure = kingpin .Flag ("insecure" , "Skip TLS certificate verification." ).Bool ()
28-
29- // Autocompletion Flags
30- enableLabelValues = kingpin .Flag ("enable-label-values" , "Enable autocompletion for label values." ).Default ("true" ).Bool ()
31-
32- // History Flags
33- historyFile = kingpin .Flag ("history-file" , "Path to the command history file." ).String ()
34- persistHistory = kingpin .Flag ("persist-history" , "Do not delete the history file on exit." ).Bool ()
35-
36- // Display and Utility Flags
37- debug = kingpin .Flag ("debug" , "Enable verbose error output for debugging." ).Bool ()
38- tips = kingpin .Flag ("tips" , "Display detailed feature and usage tips on startup." ).Bool ()
39- )
40-
4121// main is the entry point of the Prometheus CLI application.
4222// It initializes the Prometheus client, sets up autocompletion, and runs the interactive query loop.
4323func main () {
44- // Configure command-line argument parsing
45- kingpin .Version (version .Print ("prom-cli" ))
46- kingpin .HelpFlag .Short ('h' )
47- kingpin .Parse ()
24+ // 1. Determine config file path (Priority: Flag --config > Home Dir > Default None)
25+ configPath := findConfigPath ()
26+
27+ // 2. Load configuration (start with defaults, overwrite with file if exists)
28+ cfg := config .NewConfig ()
29+ if configPath != "" {
30+ loadedCfg , err := config .LoadFromFile (configPath )
31+ if err == nil {
32+ cfg = loadedCfg
33+ } else if isExplicitConfigFlag () {
34+ // Only fail if user explicitly asked for a config file that fails to load
35+ fmt .Fprintf (os .Stderr , "Error loading config file %s: %v\n " , configPath , err )
36+ os .Exit (1 )
37+ }
38+ }
39+
40+ // 3. Define Flags (using Config values as Defaults)
41+ // Kingpin priority: Flag > Envar > Default (which is now Config)
42+ app := kingpin .New ("prom-cli" , "A powerful command-line tool for querying Prometheus metrics." )
43+ app .Version (version .Print ("prom-cli" ))
44+ app .HelpFlag .Short ('h' )
45+
46+ var (
47+ cfgFile = app .Flag ("config" , "Path to configuration file." ).Default (configPath ).String ()
48+
49+ // Prometheus Connection Flags
50+ url = app .Flag ("url" , "Prometheus server URL." ).Default (cfg .URL ).String ()
51+ username = app .Flag ("username" , "Username for basic authentication." ).Envar ("PROM_USERNAME" ).Default (cfg .Username ).String ()
52+ password = app .Flag ("password" , "Password for basic authentication." ).Envar ("PROM_PASSWORD" ).Default (cfg .Password ).String ()
53+ passwordFile = app .Flag ("password-file" , "Path to file containing password for basic authentication." ).Default (cfg .PasswordFile ).String ()
54+ insecure = app .Flag ("insecure" , "Skip TLS certificate verification." ).Default (fmt .Sprintf ("%v" , cfg .Insecure )).Bool ()
55+
56+ // Autocompletion Flags
57+ enableLabelValues = app .Flag ("enable-label-values" , "Enable autocompletion for label values." ).Default (fmt .Sprintf ("%v" , cfg .EnableLabelValues )).Bool ()
58+
59+ // History Flags
60+ historyFile = app .Flag ("history-file" , "Path to the command history file." ).Default (cfg .HistoryFile ).String ()
61+ persistHistory = app .Flag ("persist-history" , "Do not delete the history file on exit." ).Default (fmt .Sprintf ("%v" , cfg .PersistHistory )).Bool ()
62+
63+ // Display and Utility Flags
64+ debug = app .Flag ("debug" , "Enable verbose error output for debugging." ).Default (fmt .Sprintf ("%v" , cfg .Debug )).Bool ()
65+ tips = app .Flag ("tips" , "Display detailed feature and usage tips on startup." ).Default (fmt .Sprintf ("%v" , cfg .Tips )).Bool ()
66+ )
67+
68+ kingpin .MustParse (app .Parse (os .Args [1 :]))
4869
4970 // Handle password file if provided
5071 if * passwordFile != "" {
5172 if * password != "" {
52- kingpin .FatalUsage ("Cannot use both --password and --password-file" )
73+ app .FatalUsage ("Cannot use both --password and --password-file" )
5374 }
5475 content , err := os .ReadFile (* passwordFile )
5576 if err != nil {
56- kingpin .Fatalf ("Error reading password file: %v" , err )
77+ app .Fatalf ("Error reading password file: %v" , err )
5778 }
5879 * password = strings .TrimSpace (string (content ))
5980 }
6081
6182 // Display welcome message and feature information if tips are enabled
6283 if * tips {
63- printWelcomeMessage ()
84+ printWelcomeMessage (* tips )
6485 } else {
6586 fmt .Println ("Enter Prometheus queries. Press Ctrl+C to exit." )
6687 }
6788
6889 // Initialize Prometheus client with user-provided configuration
6990 if * debug {
91+ if configPath != "" && * cfgFile == configPath {
92+ fmt .Printf ("Debug: Loaded configuration from %s\n " , configPath )
93+ }
7094 fmt .Printf ("Debug: Setting Prometheus URL to %s/api/v1\n " , * url )
71- fmt .Printf ("Debug: Setting Basic Auth with username: %s\n " , * username )
95+ if * username != "" {
96+ fmt .Printf ("Debug: Setting Basic Auth with username: %s\n " , * username )
97+ }
7298 fmt .Printf ("Debug: Setting TLS InsecureSkipVerify to %t\n " , * insecure )
7399 }
74100 prometheus .SetPrometheusURL (* url + "/api/v1" )
@@ -146,13 +172,10 @@ func main() {
146172 }
147173
148174 // Schedule the history file to be removed if persistence is not requested.
149- if * debug {
150- fmt .Printf ("Debug: shouldRemoveHistoryFile is %t before defer registration.\n " , shouldRemoveHistoryFile )
151- }
152175 if shouldRemoveHistoryFile {
153176 defer func () {
154177 if * debug {
155- fmt .Printf ("Debug: Inside defer. shouldRemoveHistoryFile is %t. Attempting to remove history file: %s\n " , shouldRemoveHistoryFile , historyFilePath )
178+ fmt .Printf ("Debug: Removing history file: %s\n " , historyFilePath )
156179 }
157180 if err := os .Remove (historyFilePath ); err != nil {
158181 fmt .Fprintf (os .Stderr , "Warning: could not remove history file %s: %v\n " , historyFilePath , err )
@@ -179,14 +202,52 @@ func main() {
179202 }()
180203
181204 // Run the main interactive query loop
182- runQueryLoop (l )
205+ runQueryLoop (l , * debug )
206+ }
207+
208+ // findConfigPath looks for a configuration file.
209+ // Priority:
210+ // 1. --config flag in os.Args
211+ // 2. $HOME/.prom-cli.yaml
212+ func findConfigPath () string {
213+ // 1. Check args
214+ for i , arg := range os .Args {
215+ if arg == "--config" && i + 1 < len (os .Args ) {
216+ return os .Args [i + 1 ]
217+ }
218+ if strings .HasPrefix (arg , "--config=" ) {
219+ return strings .TrimPrefix (arg , "--config=" )
220+ }
221+ }
222+
223+ // 2. Check Home Directory
224+ home , err := os .UserHomeDir ()
225+ if err == nil {
226+ defaultPath := filepath .Join (home , ".prom-cli.yaml" )
227+ if _ , err := os .Stat (defaultPath ); err == nil {
228+ return defaultPath
229+ }
230+ }
231+
232+ return ""
233+ }
234+
235+ // isExplicitConfigFlag checks if the user explicitly provided the --config flag.
236+ // This is used to decide whether to error out if the file is missing.
237+ func isExplicitConfigFlag () bool {
238+ for _ , arg := range os .Args {
239+ if strings .HasPrefix (arg , "--config" ) {
240+ return true
241+ }
242+ }
243+ return false
183244}
184245
185246// printWelcomeMessage displays the welcome message and available features.
186- func printWelcomeMessage () {
247+ func printWelcomeMessage (showTips bool ) {
187248 fmt .Println ("Enter Prometheus queries. Press Ctrl+C to exit." )
188249
189- if * tips {
250+ if showTips {
190251 fmt .Print (`
191252✨ Features:
192253 - Metric Names: Smart autocompletion for all available Prometheus metrics
@@ -206,7 +267,7 @@ func printWelcomeMessage() {
206267}
207268
208269// runQueryLoop runs the main interactive loop for processing user queries.
209- func runQueryLoop (l * readline.Instance ) {
270+ func runQueryLoop (l * readline.Instance , debugMode bool ) {
210271 for {
211272 line , err := l .Readline ()
212273 if err == readline .ErrInterrupt {
@@ -224,7 +285,7 @@ func runQueryLoop(l *readline.Instance) {
224285 // Execute the Prometheus query and display results
225286 results , err := prometheus .QueryPrometheus (query )
226287 if err != nil {
227- if * debug {
288+ if debugMode {
228289 fmt .Printf ("Error executing query: %v\n " , err )
229290 } else {
230291 fmt .Printf ("Error executing query. Use --debug for more details.\n " )
0 commit comments