2025-06-18 01:11:22 +00:00
|
|
|
|
// Copyright The Prometheus Authors
|
2021-05-29 18:45:16 +00:00
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
|
|
//
|
|
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
//
|
|
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
2015-04-05 16:20:17 +00:00
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2025-05-07 02:22:12 +00:00
|
|
|
|
"context"
|
2021-07-26 14:58:38 +00:00
|
|
|
|
"fmt"
|
2024-10-17 16:03:17 +00:00
|
|
|
|
"io"
|
2015-04-05 16:20:17 +00:00
|
|
|
|
"net/http"
|
2025-05-07 02:25:07 +00:00
|
|
|
|
_ "net/http/pprof"
|
2017-06-28 15:01:24 +00:00
|
|
|
|
"net/url"
|
|
|
|
|
|
"os"
|
2018-11-12 15:31:58 +00:00
|
|
|
|
"os/signal"
|
2024-10-17 16:03:17 +00:00
|
|
|
|
"strings"
|
2018-11-09 16:55:29 +00:00
|
|
|
|
"time"
|
|
|
|
|
|
|
2023-03-23 08:51:33 +00:00
|
|
|
|
"github.com/alecthomas/kingpin/v2"
|
2017-06-06 13:06:51 +00:00
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
2024-10-17 16:03:17 +00:00
|
|
|
|
versioncollector "github.com/prometheus/client_golang/prometheus/collectors/version"
|
2021-05-31 07:59:09 +00:00
|
|
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
2024-10-17 16:03:17 +00:00
|
|
|
|
"github.com/prometheus/common/promslog"
|
|
|
|
|
|
"github.com/prometheus/common/promslog/flag"
|
2017-10-30 09:19:16 +00:00
|
|
|
|
"github.com/prometheus/common/version"
|
2022-08-25 12:47:13 +00:00
|
|
|
|
"github.com/prometheus/exporter-toolkit/web"
|
|
|
|
|
|
webflag "github.com/prometheus/exporter-toolkit/web/kingpinflag"
|
2025-05-07 02:22:12 +00:00
|
|
|
|
|
|
|
|
|
|
"github.com/prometheus-community/elasticsearch_exporter/collector"
|
2025-08-19 01:43:53 +00:00
|
|
|
|
"github.com/prometheus-community/elasticsearch_exporter/config"
|
2025-05-07 02:22:12 +00:00
|
|
|
|
"github.com/prometheus-community/elasticsearch_exporter/pkg/clusterinfo"
|
|
|
|
|
|
"github.com/prometheus-community/elasticsearch_exporter/pkg/roundtripper"
|
2015-04-05 16:20:17 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
2021-08-17 17:38:06 +00:00
|
|
|
|
const name = "elasticsearch_exporter"
|
|
|
|
|
|
|
2022-01-19 14:39:54 +00:00
|
|
|
|
type transportWithAPIKey struct {
|
2021-07-24 19:11:20 +00:00
|
|
|
|
underlyingTransport http.RoundTripper
|
2021-07-26 14:58:38 +00:00
|
|
|
|
apiKey string
|
2021-07-24 19:11:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-01-19 14:39:54 +00:00
|
|
|
|
func (t *transportWithAPIKey) RoundTrip(req *http.Request) (*http.Response, error) {
|
2021-07-26 14:58:38 +00:00
|
|
|
|
req.Header.Add("Authorization", fmt.Sprintf("ApiKey %s", t.apiKey))
|
2021-07-24 19:11:20 +00:00
|
|
|
|
return t.underlyingTransport.RoundTrip(req)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-04-05 16:20:17 +00:00
|
|
|
|
func main() {
|
|
|
|
|
|
var (
|
2018-11-15 10:31:41 +00:00
|
|
|
|
metricsPath = kingpin.Flag("web.telemetry-path",
|
|
|
|
|
|
"Path under which to expose metrics.").
|
2022-01-19 17:36:15 +00:00
|
|
|
|
Default("/metrics").String()
|
2023-03-23 08:51:33 +00:00
|
|
|
|
toolkitFlags = webflag.AddFlags(kingpin.CommandLine, ":9114")
|
|
|
|
|
|
esURI = kingpin.Flag("es.uri",
|
2018-11-15 10:31:41 +00:00
|
|
|
|
"HTTP API address of an Elasticsearch node.").
|
2025-08-19 01:43:53 +00:00
|
|
|
|
Default("").String()
|
2018-11-15 10:31:41 +00:00
|
|
|
|
esTimeout = kingpin.Flag("es.timeout",
|
|
|
|
|
|
"Timeout for trying to get stats from Elasticsearch.").
|
2022-01-19 17:36:15 +00:00
|
|
|
|
Default("5s").Duration()
|
2018-11-15 10:31:41 +00:00
|
|
|
|
esAllNodes = kingpin.Flag("es.all",
|
|
|
|
|
|
"Export stats for all nodes in the cluster. If used, this flag will override the flag es.node.").
|
2022-01-19 17:36:15 +00:00
|
|
|
|
Default("false").Bool()
|
2018-11-15 10:31:41 +00:00
|
|
|
|
esNode = kingpin.Flag("es.node",
|
|
|
|
|
|
"Node's name of which metrics should be exposed.").
|
2022-01-19 17:36:15 +00:00
|
|
|
|
Default("_local").String()
|
2018-11-15 10:31:41 +00:00
|
|
|
|
esExportIndices = kingpin.Flag("es.indices",
|
|
|
|
|
|
"Export stats for indices in the cluster.").
|
2022-01-19 17:36:15 +00:00
|
|
|
|
Default("false").Bool()
|
2018-11-16 11:34:55 +00:00
|
|
|
|
esExportIndicesSettings = kingpin.Flag("es.indices_settings",
|
2018-11-15 10:31:41 +00:00
|
|
|
|
"Export stats for settings of all indices of the cluster.").
|
2022-01-19 17:36:15 +00:00
|
|
|
|
Default("false").Bool()
|
2021-06-04 12:56:50 +00:00
|
|
|
|
esExportIndicesMappings = kingpin.Flag("es.indices_mappings",
|
|
|
|
|
|
"Export stats for mappings of all indices of the cluster.").
|
2022-01-19 17:36:15 +00:00
|
|
|
|
Default("false").Bool()
|
2022-05-19 13:22:54 +00:00
|
|
|
|
esExportIndexAliases = kingpin.Flag("es.aliases",
|
|
|
|
|
|
"Export informational alias metrics.").
|
|
|
|
|
|
Default("true").Bool()
|
2018-11-15 10:31:41 +00:00
|
|
|
|
esExportShards = kingpin.Flag("es.shards",
|
|
|
|
|
|
"Export stats for shards in the cluster (implies --es.indices).").
|
2022-01-19 17:36:15 +00:00
|
|
|
|
Default("false").Bool()
|
2018-08-21 16:52:01 +00:00
|
|
|
|
esClusterInfoInterval = kingpin.Flag("es.clusterinfo.interval",
|
|
|
|
|
|
"Cluster info update interval for the cluster label").
|
2022-01-19 17:36:15 +00:00
|
|
|
|
Default("5m").Duration()
|
2018-11-15 10:31:41 +00:00
|
|
|
|
esCA = kingpin.Flag("es.ca",
|
|
|
|
|
|
"Path to PEM file that contains trusted Certificate Authorities for the Elasticsearch connection.").
|
2022-01-19 17:36:15 +00:00
|
|
|
|
Default("").String()
|
2018-11-15 10:31:41 +00:00
|
|
|
|
esClientPrivateKey = kingpin.Flag("es.client-private-key",
|
|
|
|
|
|
"Path to PEM file that contains the private key for client auth when connecting to Elasticsearch.").
|
2022-01-19 17:36:15 +00:00
|
|
|
|
Default("").String()
|
2018-11-15 10:31:41 +00:00
|
|
|
|
esClientCert = kingpin.Flag("es.client-cert",
|
|
|
|
|
|
"Path to PEM file that contains the corresponding cert for the private key to connect to Elasticsearch.").
|
2022-01-19 17:36:15 +00:00
|
|
|
|
Default("").String()
|
2018-11-15 10:31:41 +00:00
|
|
|
|
esInsecureSkipVerify = kingpin.Flag("es.ssl-skip-verify",
|
|
|
|
|
|
"Skip SSL verification when connecting to Elasticsearch.").
|
2022-01-19 17:36:15 +00:00
|
|
|
|
Default("false").Bool()
|
2018-11-15 10:31:41 +00:00
|
|
|
|
logOutput = kingpin.Flag("log.output",
|
|
|
|
|
|
"Sets the log output. Valid outputs are stdout and stderr").
|
2022-01-19 17:36:15 +00:00
|
|
|
|
Default("stdout").String()
|
2022-07-19 23:21:38 +00:00
|
|
|
|
awsRegion = kingpin.Flag("aws.region",
|
|
|
|
|
|
"Region for AWS elasticsearch").
|
|
|
|
|
|
Default("").String()
|
2023-01-24 14:28:38 +00:00
|
|
|
|
awsRoleArn = kingpin.Flag("aws.role-arn",
|
|
|
|
|
|
"Role ARN of an IAM role to assume.").
|
|
|
|
|
|
Default("").String()
|
2025-08-19 01:43:53 +00:00
|
|
|
|
configFile = kingpin.Flag("config.file", "Path to YAML configuration file.").Default("").String()
|
2015-04-05 16:20:17 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
2024-10-17 16:03:17 +00:00
|
|
|
|
promslogConfig := &promslog.Config{}
|
|
|
|
|
|
flag.AddFlags(kingpin.CommandLine, promslogConfig)
|
2021-08-17 17:38:06 +00:00
|
|
|
|
kingpin.Version(version.Print(name))
|
2019-02-26 10:11:41 +00:00
|
|
|
|
kingpin.CommandLine.HelpFlag.Short('h')
|
|
|
|
|
|
kingpin.Parse()
|
2017-10-30 09:19:16 +00:00
|
|
|
|
|
2025-08-19 01:43:53 +00:00
|
|
|
|
// Load optional YAML config
|
|
|
|
|
|
var cfg *config.Config
|
|
|
|
|
|
if *configFile != "" {
|
|
|
|
|
|
var cfgErr error
|
|
|
|
|
|
cfg, cfgErr = config.LoadConfig(*configFile)
|
|
|
|
|
|
if cfgErr != nil {
|
|
|
|
|
|
// At this stage logger not yet created; fallback to stderr
|
|
|
|
|
|
fmt.Fprintf(os.Stderr, "failed to load config file: %v\n", cfgErr)
|
|
|
|
|
|
os.Exit(1)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-17 16:03:17 +00:00
|
|
|
|
var w io.Writer
|
|
|
|
|
|
switch strings.ToLower(*logOutput) {
|
|
|
|
|
|
case "stderr":
|
|
|
|
|
|
w = os.Stderr
|
|
|
|
|
|
case "stdout":
|
|
|
|
|
|
w = os.Stdout
|
|
|
|
|
|
default:
|
|
|
|
|
|
w = os.Stdout
|
|
|
|
|
|
}
|
|
|
|
|
|
promslogConfig.Writer = w
|
|
|
|
|
|
logger := promslog.New(promslogConfig)
|
2017-06-28 15:01:24 +00:00
|
|
|
|
|
2025-08-19 01:43:53 +00:00
|
|
|
|
// version metric
|
|
|
|
|
|
prometheus.MustRegister(versioncollector.NewCollector(name))
|
2018-08-21 16:52:01 +00:00
|
|
|
|
|
2025-08-19 01:43:53 +00:00
|
|
|
|
// Create a context that is cancelled on SIGKILL or SIGINT.
|
|
|
|
|
|
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
|
|
|
|
|
|
defer cancel()
|
2021-07-27 11:19:59 +00:00
|
|
|
|
|
2025-08-19 01:43:53 +00:00
|
|
|
|
if *esURI != "" {
|
|
|
|
|
|
esURL, err := url.Parse(*esURI)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
logger.Error("failed to parse es.uri", "err", err)
|
|
|
|
|
|
os.Exit(1)
|
|
|
|
|
|
}
|
2021-07-27 11:19:59 +00:00
|
|
|
|
|
2025-08-19 01:43:53 +00:00
|
|
|
|
esUsername := os.Getenv("ES_USERNAME")
|
|
|
|
|
|
esPassword := os.Getenv("ES_PASSWORD")
|
2017-06-28 15:01:24 +00:00
|
|
|
|
|
2025-08-19 01:43:53 +00:00
|
|
|
|
if esUsername != "" && esPassword != "" {
|
|
|
|
|
|
esURL.User = url.UserPassword(esUsername, esPassword)
|
|
|
|
|
|
}
|
2021-07-27 06:45:44 +00:00
|
|
|
|
|
2025-08-19 01:43:53 +00:00
|
|
|
|
// returns nil if not provided and falls back to simple TCP.
|
|
|
|
|
|
tlsConfig := createTLSConfig(*esCA, *esClientCert, *esClientPrivateKey, *esInsecureSkipVerify)
|
2021-07-24 19:11:20 +00:00
|
|
|
|
|
2025-08-19 01:43:53 +00:00
|
|
|
|
var httpTransport http.RoundTripper
|
2021-07-27 06:45:44 +00:00
|
|
|
|
|
2025-08-19 01:43:53 +00:00
|
|
|
|
httpTransport = &http.Transport{
|
|
|
|
|
|
TLSClientConfig: tlsConfig,
|
|
|
|
|
|
Proxy: http.ProxyFromEnvironment,
|
2021-07-24 19:11:20 +00:00
|
|
|
|
}
|
2017-06-28 15:01:24 +00:00
|
|
|
|
|
2025-08-19 01:43:53 +00:00
|
|
|
|
esAPIKey := os.Getenv("ES_API_KEY")
|
2021-07-27 06:45:44 +00:00
|
|
|
|
|
2025-08-19 01:43:53 +00:00
|
|
|
|
if esAPIKey != "" {
|
|
|
|
|
|
httpTransport = &transportWithAPIKey{
|
|
|
|
|
|
underlyingTransport: httpTransport,
|
|
|
|
|
|
apiKey: esAPIKey,
|
|
|
|
|
|
}
|
2022-07-19 23:21:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-19 01:43:53 +00:00
|
|
|
|
httpClient := &http.Client{
|
|
|
|
|
|
Timeout: *esTimeout,
|
|
|
|
|
|
Transport: httpTransport,
|
|
|
|
|
|
}
|
2018-08-21 16:52:01 +00:00
|
|
|
|
|
2025-08-19 01:43:53 +00:00
|
|
|
|
if *awsRegion != "" {
|
|
|
|
|
|
var err error
|
|
|
|
|
|
httpClient.Transport, err = roundtripper.NewAWSSigningTransport(httpTransport, *awsRegion, *awsRoleArn, logger)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
logger.Error("failed to create AWS transport", "err", err)
|
|
|
|
|
|
os.Exit(1)
|
|
|
|
|
|
}
|
2018-08-21 16:52:01 +00:00
|
|
|
|
}
|
2025-08-19 01:43:53 +00:00
|
|
|
|
|
|
|
|
|
|
// create the exporter
|
|
|
|
|
|
exporter, err := collector.NewElasticsearchCollector(
|
|
|
|
|
|
logger,
|
|
|
|
|
|
[]string{},
|
|
|
|
|
|
collector.WithElasticsearchURL(esURL),
|
|
|
|
|
|
collector.WithHTTPClient(httpClient),
|
|
|
|
|
|
)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
logger.Error("failed to create Elasticsearch collector", "err", err)
|
2023-12-12 18:05:44 +00:00
|
|
|
|
os.Exit(1)
|
|
|
|
|
|
}
|
2025-08-19 01:43:53 +00:00
|
|
|
|
prometheus.MustRegister(exporter)
|
|
|
|
|
|
|
|
|
|
|
|
// TODO(@sysadmind): Remove this when we have a better way to get the cluster name to down stream collectors.
|
|
|
|
|
|
// cluster info retriever
|
|
|
|
|
|
clusterInfoRetriever := clusterinfo.New(logger, httpClient, esURL, *esClusterInfoInterval)
|
|
|
|
|
|
|
|
|
|
|
|
prometheus.MustRegister(collector.NewClusterHealth(logger, httpClient, esURL))
|
|
|
|
|
|
prometheus.MustRegister(collector.NewNodes(logger, httpClient, esURL, *esAllNodes, *esNode))
|
|
|
|
|
|
|
|
|
|
|
|
if *esExportIndices || *esExportShards {
|
|
|
|
|
|
sC := collector.NewShards(logger, httpClient, esURL)
|
|
|
|
|
|
prometheus.MustRegister(sC)
|
|
|
|
|
|
iC := collector.NewIndices(logger, httpClient, esURL, *esExportShards, *esExportIndexAliases)
|
|
|
|
|
|
prometheus.MustRegister(iC)
|
|
|
|
|
|
if registerErr := clusterInfoRetriever.RegisterConsumer(iC); registerErr != nil {
|
|
|
|
|
|
logger.Error("failed to register indices collector in cluster info")
|
|
|
|
|
|
os.Exit(1)
|
|
|
|
|
|
}
|
|
|
|
|
|
if registerErr := clusterInfoRetriever.RegisterConsumer(sC); registerErr != nil {
|
|
|
|
|
|
logger.Error("failed to register shards collector in cluster info")
|
|
|
|
|
|
os.Exit(1)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2018-08-21 16:52:01 +00:00
|
|
|
|
|
2025-08-19 01:43:53 +00:00
|
|
|
|
if *esExportIndicesSettings {
|
|
|
|
|
|
prometheus.MustRegister(collector.NewIndicesSettings(logger, httpClient, esURL))
|
|
|
|
|
|
}
|
2018-08-21 16:52:01 +00:00
|
|
|
|
|
2025-08-19 01:43:53 +00:00
|
|
|
|
if *esExportIndicesMappings {
|
|
|
|
|
|
prometheus.MustRegister(collector.NewIndicesMappings(logger, httpClient, esURL))
|
|
|
|
|
|
}
|
2021-06-04 12:56:50 +00:00
|
|
|
|
|
2025-08-19 01:43:53 +00:00
|
|
|
|
// start the cluster info retriever
|
|
|
|
|
|
switch runErr := clusterInfoRetriever.Run(ctx); runErr {
|
|
|
|
|
|
case nil:
|
|
|
|
|
|
logger.Info("started cluster info retriever", "interval", (*esClusterInfoInterval).String())
|
|
|
|
|
|
case clusterinfo.ErrInitialCallTimeout:
|
|
|
|
|
|
logger.Info("initial cluster info call timed out")
|
|
|
|
|
|
default:
|
|
|
|
|
|
logger.Error("failed to run cluster info retriever", "err", runErr)
|
|
|
|
|
|
os.Exit(1)
|
|
|
|
|
|
}
|
2018-11-12 15:11:03 +00:00
|
|
|
|
|
2025-08-19 01:43:53 +00:00
|
|
|
|
// register cluster info retriever as prometheus collector
|
|
|
|
|
|
prometheus.MustRegister(clusterInfoRetriever)
|
2018-08-21 16:52:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-19 01:43:53 +00:00
|
|
|
|
http.HandleFunc(*metricsPath, func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
|
// /metrics endpoint is reserved for single-target mode only.
|
|
|
|
|
|
// For per-scrape overrides use the dedicated /probe endpoint.
|
|
|
|
|
|
promhttp.Handler().ServeHTTP(w, r)
|
|
|
|
|
|
})
|
2018-08-21 16:52:01 +00:00
|
|
|
|
|
2023-03-23 08:51:33 +00:00
|
|
|
|
if *metricsPath != "/" && *metricsPath != "" {
|
|
|
|
|
|
landingConfig := web.LandingConfig{
|
|
|
|
|
|
Name: "Elasticsearch Exporter",
|
|
|
|
|
|
Description: "Prometheus Exporter for Elasticsearch servers",
|
|
|
|
|
|
Version: version.Info(),
|
|
|
|
|
|
Links: []web.LandingLinks{
|
|
|
|
|
|
{
|
|
|
|
|
|
Address: *metricsPath,
|
|
|
|
|
|
Text: "Metrics",
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
landingPage, err := web.NewLandingPage(landingConfig)
|
2018-08-08 18:46:07 +00:00
|
|
|
|
if err != nil {
|
2024-10-17 16:03:17 +00:00
|
|
|
|
logger.Error("error creating landing page", "err", err)
|
2023-03-23 08:51:33 +00:00
|
|
|
|
os.Exit(1)
|
2018-08-08 18:46:07 +00:00
|
|
|
|
}
|
2023-03-23 08:51:33 +00:00
|
|
|
|
http.Handle("/", landingPage)
|
|
|
|
|
|
}
|
2017-05-15 14:22:45 +00:00
|
|
|
|
|
2019-08-06 15:10:25 +00:00
|
|
|
|
// health endpoint
|
2023-03-23 08:51:33 +00:00
|
|
|
|
http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
|
2019-08-06 15:10:25 +00:00
|
|
|
|
http.Error(w, http.StatusText(http.StatusOK), http.StatusOK)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-08-19 01:43:53 +00:00
|
|
|
|
// probe endpoint
|
|
|
|
|
|
http.HandleFunc("/probe", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
|
origQuery := r.URL.Query()
|
|
|
|
|
|
targetStr, am, valErr := validateProbeParams(cfg, origQuery)
|
|
|
|
|
|
if valErr != nil {
|
|
|
|
|
|
http.Error(w, valErr.Error(), http.StatusBadRequest)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
targetURL, _ := url.Parse(targetStr)
|
|
|
|
|
|
if am != nil {
|
|
|
|
|
|
// Apply userpass credentials only if the module type is explicitly set to userpass.
|
|
|
|
|
|
if strings.EqualFold(am.Type, "userpass") && am.UserPass != nil {
|
|
|
|
|
|
targetURL.User = url.UserPassword(am.UserPass.Username, am.UserPass.Password)
|
|
|
|
|
|
}
|
|
|
|
|
|
if len(am.Options) > 0 {
|
|
|
|
|
|
q := targetURL.Query()
|
|
|
|
|
|
for k, v := range am.Options {
|
|
|
|
|
|
q.Set(k, v)
|
|
|
|
|
|
}
|
|
|
|
|
|
targetURL.RawQuery = q.Encode()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Build a dedicated HTTP client for this probe request (reuse TLS opts, timeout, etc.).
|
|
|
|
|
|
pemCA := *esCA
|
|
|
|
|
|
pemCert := *esClientCert
|
|
|
|
|
|
pemKey := *esClientPrivateKey
|
|
|
|
|
|
insecure := *esInsecureSkipVerify
|
|
|
|
|
|
|
|
|
|
|
|
// Apply TLS configuration from auth module if provided (for transport security)
|
|
|
|
|
|
// This matches single-target behavior where TLS settings are always applied
|
|
|
|
|
|
if am != nil && am.TLS != nil {
|
|
|
|
|
|
// Override with module-specific TLS settings
|
|
|
|
|
|
if am.TLS.CAFile != "" {
|
|
|
|
|
|
pemCA = am.TLS.CAFile
|
|
|
|
|
|
}
|
|
|
|
|
|
if am.TLS.CertFile != "" {
|
|
|
|
|
|
pemCert = am.TLS.CertFile
|
|
|
|
|
|
}
|
|
|
|
|
|
if am.TLS.KeyFile != "" {
|
|
|
|
|
|
pemKey = am.TLS.KeyFile
|
|
|
|
|
|
}
|
|
|
|
|
|
if am.TLS.InsecureSkipVerify {
|
|
|
|
|
|
insecure = true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
tlsCfg := createTLSConfig(pemCA, pemCert, pemKey, insecure)
|
|
|
|
|
|
var transport http.RoundTripper = &http.Transport{
|
|
|
|
|
|
TLSClientConfig: tlsCfg,
|
|
|
|
|
|
Proxy: http.ProxyFromEnvironment,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// inject authentication based on auth_module type
|
|
|
|
|
|
if am != nil {
|
|
|
|
|
|
switch strings.ToLower(am.Type) {
|
|
|
|
|
|
case "apikey":
|
|
|
|
|
|
if am.APIKey != "" {
|
|
|
|
|
|
transport = &transportWithAPIKey{
|
|
|
|
|
|
underlyingTransport: transport,
|
|
|
|
|
|
apiKey: am.APIKey,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
case "aws":
|
|
|
|
|
|
var region string
|
|
|
|
|
|
if am.AWS.Region != "" {
|
|
|
|
|
|
region = am.AWS.Region
|
|
|
|
|
|
}
|
|
|
|
|
|
var err error
|
|
|
|
|
|
transport, err = roundtripper.NewAWSSigningTransport(transport, region, am.AWS.RoleARN, logger)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
http.Error(w, "failed to create AWS signing transport", http.StatusInternalServerError)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
case "tls":
|
|
|
|
|
|
// No additional auth wrapper needed - client certificates in TLS config handle authentication
|
|
|
|
|
|
case "userpass":
|
|
|
|
|
|
// Already handled above by setting targetURL.User
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
probeClient := &http.Client{
|
|
|
|
|
|
Timeout: *esTimeout,
|
|
|
|
|
|
Transport: transport,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
reg := prometheus.NewRegistry()
|
|
|
|
|
|
|
|
|
|
|
|
// version metric
|
|
|
|
|
|
reg.MustRegister(versioncollector.NewCollector(name))
|
|
|
|
|
|
|
|
|
|
|
|
// Core exporter collector
|
|
|
|
|
|
exp, err := collector.NewElasticsearchCollector(
|
|
|
|
|
|
logger,
|
|
|
|
|
|
[]string{},
|
|
|
|
|
|
collector.WithElasticsearchURL(targetURL),
|
|
|
|
|
|
collector.WithHTTPClient(probeClient),
|
|
|
|
|
|
)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
http.Error(w, "failed to create exporter", http.StatusInternalServerError)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
reg.MustRegister(exp)
|
|
|
|
|
|
// Basic additional collectors – reuse global CLI flags
|
|
|
|
|
|
reg.MustRegister(collector.NewClusterHealth(logger, probeClient, targetURL))
|
|
|
|
|
|
reg.MustRegister(collector.NewNodes(logger, probeClient, targetURL, *esAllNodes, *esNode))
|
|
|
|
|
|
if *esExportIndices || *esExportShards {
|
|
|
|
|
|
shardsC := collector.NewShards(logger, probeClient, targetURL)
|
|
|
|
|
|
indicesC := collector.NewIndices(logger, probeClient, targetURL, *esExportShards, *esExportIndexAliases)
|
|
|
|
|
|
reg.MustRegister(shardsC)
|
|
|
|
|
|
reg.MustRegister(indicesC)
|
|
|
|
|
|
}
|
|
|
|
|
|
if *esExportIndicesSettings {
|
|
|
|
|
|
reg.MustRegister(collector.NewIndicesSettings(logger, probeClient, targetURL))
|
|
|
|
|
|
}
|
|
|
|
|
|
if *esExportIndicesMappings {
|
|
|
|
|
|
reg.MustRegister(collector.NewIndicesMappings(logger, probeClient, targetURL))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
promhttp.HandlerFor(reg, promhttp.HandlerOpts{}).ServeHTTP(w, r)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2023-03-23 08:51:33 +00:00
|
|
|
|
server := &http.Server{}
|
2018-11-12 15:11:03 +00:00
|
|
|
|
go func() {
|
2025-08-19 01:43:53 +00:00
|
|
|
|
if err := web.ListenAndServe(server, toolkitFlags, logger); err != nil {
|
2024-10-17 16:03:17 +00:00
|
|
|
|
logger.Error("http server quit", "err", err)
|
2019-05-16 08:34:23 +00:00
|
|
|
|
os.Exit(1)
|
2018-11-12 15:11:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
}()
|
|
|
|
|
|
|
2021-08-17 17:38:06 +00:00
|
|
|
|
<-ctx.Done()
|
2024-10-17 16:03:17 +00:00
|
|
|
|
logger.Info("shutting down")
|
2018-11-12 15:11:03 +00:00
|
|
|
|
// create a context for graceful http server shutdown
|
|
|
|
|
|
srvCtx, srvCancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
|
|
|
|
defer srvCancel()
|
2018-11-12 15:31:58 +00:00
|
|
|
|
_ = server.Shutdown(srvCtx)
|
2017-05-15 14:22:45 +00:00
|
|
|
|
}
|