receiver/prometheusreceiver/config.go (149 lines of code) (raw):

// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package prometheusreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/prometheusreceiver" import ( "errors" "fmt" "os" "sort" "strings" commonconfig "github.com/prometheus/common/config" promconfig "github.com/prometheus/prometheus/config" "github.com/prometheus/prometheus/discovery/kubernetes" "go.opentelemetry.io/collector/config/confighttp" "go.opentelemetry.io/collector/confmap" "gopkg.in/yaml.v2" "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/prometheusreceiver/targetallocator" ) // Config defines configuration for Prometheus receiver. type Config struct { PrometheusConfig *PromConfig `mapstructure:"config"` TrimMetricSuffixes bool `mapstructure:"trim_metric_suffixes"` // UseStartTimeMetric enables retrieving the start time of all counter metrics // from the process_start_time_seconds metric. This is only correct if all counters on that endpoint // started after the process start time, and the process is the only actor exporting the metric after // the process started. It should not be used in "exporters" which export counters that may have // started before the process itself. Use only if you know what you are doing, as this may result // in incorrect rate calculations. UseStartTimeMetric bool `mapstructure:"use_start_time_metric"` StartTimeMetricRegex string `mapstructure:"start_time_metric_regex"` // ReportExtraScrapeMetrics - enables reporting of additional metrics for Prometheus client like scrape_body_size_bytes ReportExtraScrapeMetrics bool `mapstructure:"report_extra_scrape_metrics"` TargetAllocator *targetallocator.Config `mapstructure:"target_allocator"` // APIServer has the settings to enable the receiver to host the Prometheus API // server in agent mode. This allows the user to call the endpoint to get // the config, service discovery, and targets for debugging purposes. APIServer *APIServer `mapstructure:"api_server"` } // Validate checks the receiver configuration is valid. func (cfg *Config) Validate() error { if !containsScrapeConfig(cfg) && cfg.TargetAllocator == nil { return errors.New("no Prometheus scrape_configs or target_allocator set") } if cfg.APIServer != nil { if err := cfg.APIServer.Validate(); err != nil { return fmt.Errorf("invalid API server configuration settings: %w", err) } } return nil } func containsScrapeConfig(cfg *Config) bool { if cfg.PrometheusConfig == nil { return false } scrapeConfigs, err := (*promconfig.Config)(cfg.PrometheusConfig).GetScrapeConfigs() if err != nil { return false } return len(scrapeConfigs) > 0 } // PromConfig is a redeclaration of promconfig.Config because we need custom unmarshaling // as prometheus "config" uses `yaml` tags. type PromConfig promconfig.Config var _ confmap.Unmarshaler = (*PromConfig)(nil) func (cfg *PromConfig) Unmarshal(componentParser *confmap.Conf) error { cfgMap := componentParser.ToStringMap() if len(cfgMap) == 0 { return nil } return unmarshalYAML(cfgMap, (*promconfig.Config)(cfg)) } func (cfg *PromConfig) Validate() error { // Reject features that Prometheus supports but that the receiver doesn't support: // See: // * https://github.com/open-telemetry/opentelemetry-collector/issues/3863 // * https://github.com/open-telemetry/wg-prometheus/issues/3 unsupportedFeatures := make([]string, 0, 4) if len(cfg.RemoteWriteConfigs) != 0 { unsupportedFeatures = append(unsupportedFeatures, "remote_write") } if len(cfg.RemoteReadConfigs) != 0 { unsupportedFeatures = append(unsupportedFeatures, "remote_read") } if len(cfg.RuleFiles) != 0 { unsupportedFeatures = append(unsupportedFeatures, "rule_files") } if len(cfg.AlertingConfig.AlertRelabelConfigs) != 0 { unsupportedFeatures = append(unsupportedFeatures, "alert_config.relabel_configs") } if len(cfg.AlertingConfig.AlertmanagerConfigs) != 0 { unsupportedFeatures = append(unsupportedFeatures, "alert_config.alertmanagers") } if len(unsupportedFeatures) != 0 { // Sort the values for deterministic error messages. sort.Strings(unsupportedFeatures) return fmt.Errorf("unsupported features:\n\t%s", strings.Join(unsupportedFeatures, "\n\t")) } scrapeConfigs, err := (*promconfig.Config)(cfg).GetScrapeConfigs() if err != nil { return err } // Since Prometheus 3.0, the scrape manager started to fail scrapes that don't have proper // Content-Type headers, but they provided an extra configuration option to fallback to the // previous behavior. We need to make sure that this option is set for all scrape configs // to avoid introducing a breaking change. for _, sc := range scrapeConfigs { if sc.ScrapeFallbackProtocol == "" { sc.ScrapeFallbackProtocol = promconfig.PrometheusText0_0_4 } } for _, sc := range scrapeConfigs { if err := validateHTTPClientConfig(&sc.HTTPClientConfig); err != nil { return err } for _, c := range sc.ServiceDiscoveryConfigs { if c, ok := c.(*kubernetes.SDConfig); ok { if err := validateHTTPClientConfig(&c.HTTPClientConfig); err != nil { return err } } } } return nil } func unmarshalYAML(in map[string]any, out any) error { yamlOut, err := yaml.Marshal(in) if err != nil { return fmt.Errorf("prometheus receiver: failed to marshal config to yaml: %w", err) } err = yaml.UnmarshalStrict(yamlOut, out) if err != nil { return fmt.Errorf("prometheus receiver: failed to unmarshal yaml to prometheus config object: %w", err) } return nil } func validateHTTPClientConfig(cfg *commonconfig.HTTPClientConfig) error { if cfg.Authorization != nil { if err := checkFile(cfg.Authorization.CredentialsFile); err != nil { return fmt.Errorf("error checking authorization credentials file %q: %w", cfg.Authorization.CredentialsFile, err) } } if err := checkTLSConfig(cfg.TLSConfig); err != nil { return err } return nil } func checkFile(fn string) error { // Nothing set, nothing to error on. if fn == "" { return nil } _, err := os.Stat(fn) return err } func checkTLSConfig(tlsConfig commonconfig.TLSConfig) error { if err := checkFile(tlsConfig.CertFile); err != nil { return fmt.Errorf("error checking client cert file %q: %w", tlsConfig.CertFile, err) } if err := checkFile(tlsConfig.KeyFile); err != nil { return fmt.Errorf("error checking client key file %q: %w", tlsConfig.KeyFile, err) } return nil } type APIServer struct { Enabled bool `mapstructure:"enabled"` ServerConfig confighttp.ServerConfig `mapstructure:"server_config"` } func (cfg *APIServer) Validate() error { if !cfg.Enabled { return nil } if cfg.ServerConfig.Endpoint == "" { return fmt.Errorf("if api_server is enabled, it requires a non-empty server_config endpoint") } return nil }