From 3ebe4b48db5b7c8a197e440097c04e94b51b9803 Mon Sep 17 00:00:00 2001 From: Martin Chodur Date: Tue, 25 Oct 2022 01:12:30 +0200 Subject: [PATCH] feat: add promtool http config support Signed-off-by: Martin Chodur --- cmd/promtool/main.go | 67 ++++++++++++++++++++++++++++----------- cmd/promtool/main_test.go | 6 ++-- 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/cmd/promtool/main.go b/cmd/promtool/main.go index 2295e314ff1..3dfbfb414d9 100644 --- a/cmd/promtool/main.go +++ b/cmd/promtool/main.go @@ -45,6 +45,7 @@ import ( "gopkg.in/yaml.v2" dto "github.com/prometheus/client_model/go" + promconfig "github.com/prometheus/common/config" "github.com/prometheus/common/expfmt" "github.com/prometheus/prometheus/config" @@ -74,6 +75,12 @@ const ( var lintOptions = []string{lintOptionAll, lintOptionDuplicateRules, lintOptionNone} func main() { + var ( + httpRoundTripper = api.DefaultRoundTripper + serverURL *url.URL + httpConfigFilePath string + ) + app := kingpin.New(filepath.Base(os.Args[0]), "Tooling for the Prometheus monitoring system.").UsageWriter(os.Stdout) app.Version(version.Print("promtool")) app.HelpFlag.Short('h') @@ -124,14 +131,15 @@ func main() { queryCmd := app.Command("query", "Run query against a Prometheus server.") queryCmdFmt := queryCmd.Flag("format", "Output format of the query.").Short('o').Default("promql").Enum("promql", "json") + queryCmd.Flag("http.config.file", "HTTP client configuration file for promtool to connect to Prometheus.").PlaceHolder("").ExistingFileVar(&httpConfigFilePath) queryInstantCmd := queryCmd.Command("instant", "Run instant query.") - queryInstantServer := queryInstantCmd.Arg("server", "Prometheus server to query.").Required().URL() + queryInstantCmd.Arg("server", "Prometheus server to query.").Required().URLVar(&serverURL) queryInstantExpr := queryInstantCmd.Arg("expr", "PromQL query expression.").Required().String() queryInstantTime := queryInstantCmd.Flag("time", "Query evaluation time (RFC3339 or Unix timestamp).").String() queryRangeCmd := queryCmd.Command("range", "Run range query.") - queryRangeServer := queryRangeCmd.Arg("server", "Prometheus server to query.").Required().URL() + queryRangeCmd.Arg("server", "Prometheus server to query.").Required().URLVar(&serverURL) queryRangeExpr := queryRangeCmd.Arg("expr", "PromQL query expression.").Required().String() queryRangeHeaders := queryRangeCmd.Flag("header", "Extra headers to send to server.").StringMap() queryRangeBegin := queryRangeCmd.Flag("start", "Query range start time (RFC3339 or Unix timestamp).").String() @@ -139,7 +147,7 @@ func main() { queryRangeStep := queryRangeCmd.Flag("step", "Query step size (duration).").Duration() querySeriesCmd := queryCmd.Command("series", "Run series query.") - querySeriesServer := querySeriesCmd.Arg("server", "Prometheus server to query.").Required().URL() + querySeriesCmd.Arg("server", "Prometheus server to query.").Required().URLVar(&serverURL) querySeriesMatch := querySeriesCmd.Flag("match", "Series selector. Can be specified multiple times.").Required().Strings() querySeriesBegin := querySeriesCmd.Flag("start", "Start time (RFC3339 or Unix timestamp).").String() querySeriesEnd := querySeriesCmd.Flag("end", "End time (RFC3339 or Unix timestamp).").String() @@ -153,7 +161,7 @@ func main() { debugAllServer := debugAllCmd.Arg("server", "Prometheus server to get all debug information from.").Required().String() queryLabelsCmd := queryCmd.Command("labels", "Run labels query.") - queryLabelsServer := queryLabelsCmd.Arg("server", "Prometheus server to query.").Required().URL() + queryLabelsCmd.Arg("server", "Prometheus server to query.").Required().URLVar(&serverURL) queryLabelsName := queryLabelsCmd.Arg("name", "Label name to provide label values for.").Required().String() queryLabelsBegin := queryLabelsCmd.Flag("start", "Start time (RFC3339 or Unix timestamp).").String() queryLabelsEnd := queryLabelsCmd.Flag("end", "End time (RFC3339 or Unix timestamp).").String() @@ -200,7 +208,8 @@ func main() { importFilePath := openMetricsImportCmd.Arg("input file", "OpenMetrics file to read samples from.").Required().String() importDBPath := openMetricsImportCmd.Arg("output directory", "Output directory for generated blocks.").Default(defaultDBPath).String() importRulesCmd := importCmd.Command("rules", "Create blocks of data for new recording rules.") - importRulesURL := importRulesCmd.Flag("url", "The URL for the Prometheus API with the data where the rule will be backfilled from.").Default("http://localhost:9090").URL() + importRulesCmd.Flag("http.config.file", "HTTP client configuration file for promtool to connect to Prometheus.").PlaceHolder("").ExistingFileVar(&httpConfigFilePath) + importRulesCmd.Flag("url", "The URL for the Prometheus API with the data where the rule will be backfilled from.").Default("http://localhost:9090").URLVar(&serverURL) importRulesStart := importRulesCmd.Flag("start", "The time to start backfilling the new rule from. Must be a RFC3339 formatted date or Unix timestamp. Required."). Required().String() importRulesEnd := importRulesCmd.Flag("end", "If an end time is provided, all recording rules in the rule files provided will be backfilled to the end time. Default will backfill up to 3 hours ago. Must be a RFC3339 formatted date or Unix timestamp.").String() @@ -224,6 +233,22 @@ func main() { p = &promqlPrinter{} } + if httpConfigFilePath != "" { + if serverURL != nil && serverURL.User.Username() != "" { + kingpin.Fatalf("Cannot set base auth in the server URL and use a http.config.file at the same time") + } + var err error + httpConfig, _, err := config_util.LoadHTTPConfigFile(httpConfigFilePath) + if err != nil { + kingpin.Fatalf("Failed to load HTTP config file: %v", err) + } + + httpRoundTripper, err = promconfig.NewRoundTripperFromConfig(*httpConfig, "promtool", config_util.WithUserAgent("promtool/"+version.Version)) + if err != nil { + kingpin.Fatalf("Failed to create a new HTTP round tripper: %v", err) + } + } + var noDefaultScrapePort bool for _, f := range *featureList { opts := strings.Split(f, ",") @@ -258,13 +283,13 @@ func main() { os.Exit(CheckMetrics(*checkMetricsExtended)) case queryInstantCmd.FullCommand(): - os.Exit(QueryInstant(*queryInstantServer, *queryInstantExpr, *queryInstantTime, p)) + os.Exit(QueryInstant(serverURL, httpRoundTripper, *queryInstantExpr, *queryInstantTime, p)) case queryRangeCmd.FullCommand(): - os.Exit(QueryRange(*queryRangeServer, *queryRangeHeaders, *queryRangeExpr, *queryRangeBegin, *queryRangeEnd, *queryRangeStep, p)) + os.Exit(QueryRange(serverURL, httpRoundTripper, *queryRangeHeaders, *queryRangeExpr, *queryRangeBegin, *queryRangeEnd, *queryRangeStep, p)) case querySeriesCmd.FullCommand(): - os.Exit(QuerySeries(*querySeriesServer, *querySeriesMatch, *querySeriesBegin, *querySeriesEnd, p)) + os.Exit(QuerySeries(serverURL, httpRoundTripper, *querySeriesMatch, *querySeriesBegin, *querySeriesEnd, p)) case debugPprofCmd.FullCommand(): os.Exit(debugPprof(*debugPprofServer)) @@ -276,7 +301,7 @@ func main() { os.Exit(debugAll(*debugAllServer)) case queryLabelsCmd.FullCommand(): - os.Exit(QueryLabels(*queryLabelsServer, *queryLabelsMatch, *queryLabelsName, *queryLabelsBegin, *queryLabelsEnd, p)) + os.Exit(QueryLabels(serverURL, httpRoundTripper, *queryLabelsMatch, *queryLabelsName, *queryLabelsBegin, *queryLabelsEnd, p)) case testRulesCmd.FullCommand(): os.Exit(RulesUnitTest( @@ -303,7 +328,7 @@ func main() { os.Exit(backfillOpenMetrics(*importFilePath, *importDBPath, *importHumanReadable, *importQuiet, *maxBlockDuration)) case importRulesCmd.FullCommand(): - os.Exit(checkErr(importRules(*importRulesURL, *importRulesStart, *importRulesEnd, *importRulesOutputDir, *importRulesEvalInterval, *maxBlockDuration, *importRulesFiles...))) + os.Exit(checkErr(importRules(serverURL, *importRulesStart, *importRulesEnd, *importRulesOutputDir, *importRulesEvalInterval, *maxBlockDuration, *importRulesFiles...))) } } @@ -795,12 +820,13 @@ func checkMetricsExtended(r io.Reader) ([]metricStat, int, error) { } // QueryInstant performs an instant query against a Prometheus server. -func QueryInstant(url *url.URL, query, evalTime string, p printer) int { +func QueryInstant(url *url.URL, roundTripper http.RoundTripper, query, evalTime string, p printer) int { if url.Scheme == "" { url.Scheme = "http" } config := api.Config{ - Address: url.String(), + Address: url.String(), + RoundTripper: roundTripper, } // Create new client. @@ -835,12 +861,13 @@ func QueryInstant(url *url.URL, query, evalTime string, p printer) int { } // QueryRange performs a range query against a Prometheus server. -func QueryRange(url *url.URL, headers map[string]string, query, start, end string, step time.Duration, p printer) int { +func QueryRange(url *url.URL, roundTripper http.RoundTripper, headers map[string]string, query, start, end string, step time.Duration, p printer) int { if url.Scheme == "" { url.Scheme = "http" } config := api.Config{ - Address: url.String(), + Address: url.String(), + RoundTripper: roundTripper, } if len(headers) > 0 { @@ -848,7 +875,7 @@ func QueryRange(url *url.URL, headers map[string]string, query, start, end strin for key, value := range headers { req.Header.Add(key, value) } - return http.DefaultTransport.RoundTrip(req) + return roundTripper.RoundTrip(req) }) } @@ -908,12 +935,13 @@ func QueryRange(url *url.URL, headers map[string]string, query, start, end strin } // QuerySeries queries for a series against a Prometheus server. -func QuerySeries(url *url.URL, matchers []string, start, end string, p printer) int { +func QuerySeries(url *url.URL, roundTripper http.RoundTripper, matchers []string, start, end string, p printer) int { if url.Scheme == "" { url.Scheme = "http" } config := api.Config{ - Address: url.String(), + Address: url.String(), + RoundTripper: roundTripper, } // Create new client. @@ -944,12 +972,13 @@ func QuerySeries(url *url.URL, matchers []string, start, end string, p printer) } // QueryLabels queries for label values against a Prometheus server. -func QueryLabels(url *url.URL, matchers []string, name, start, end string, p printer) int { +func QueryLabels(url *url.URL, roundTripper http.RoundTripper, matchers []string, name, start, end string, p printer) int { if url.Scheme == "" { url.Scheme = "http" } config := api.Config{ - Address: url.String(), + Address: url.String(), + RoundTripper: roundTripper, } // Create new client. diff --git a/cmd/promtool/main_test.go b/cmd/promtool/main_test.go index 40df3e5248e..f8254bdbb7f 100644 --- a/cmd/promtool/main_test.go +++ b/cmd/promtool/main_test.go @@ -56,14 +56,14 @@ func TestQueryRange(t *testing.T) { require.Equal(t, nil, err) p := &promqlPrinter{} - exitCode := QueryRange(urlObject, map[string]string{}, "up", "0", "300", 0, p) + exitCode := QueryRange(urlObject, http.DefaultTransport, map[string]string{}, "up", "0", "300", 0, p) require.Equal(t, "/api/v1/query_range", getRequest().URL.Path) form := getRequest().Form require.Equal(t, "up", form.Get("query")) require.Equal(t, "1", form.Get("step")) require.Equal(t, 0, exitCode) - exitCode = QueryRange(urlObject, map[string]string{}, "up", "0", "300", 10*time.Millisecond, p) + exitCode = QueryRange(urlObject, http.DefaultTransport, map[string]string{}, "up", "0", "300", 10*time.Millisecond, p) require.Equal(t, "/api/v1/query_range", getRequest().URL.Path) form = getRequest().Form require.Equal(t, "up", form.Get("query")) @@ -79,7 +79,7 @@ func TestQueryInstant(t *testing.T) { require.Equal(t, nil, err) p := &promqlPrinter{} - exitCode := QueryInstant(urlObject, "up", "300", p) + exitCode := QueryInstant(urlObject, http.DefaultTransport, "up", "300", p) require.Equal(t, "/api/v1/query", getRequest().URL.Path) form := getRequest().Form require.Equal(t, "up", form.Get("query"))