otelcollector/prometheusreceiver/targetallocator/config.go (155 lines of code) (raw):
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package targetallocator // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/prometheusreceiver/targetallocator"
import (
"encoding/base64"
"errors"
"fmt"
"net/url"
"os"
"strings"
"time"
commonconfig "github.com/prometheus/common/config"
promHTTP "github.com/prometheus/prometheus/discovery/http"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/confmap"
"gopkg.in/yaml.v2"
)
type Config struct {
confighttp.ClientConfig `mapstructure:",squash"`
Interval time.Duration `mapstructure:"interval"`
CollectorID string `mapstructure:"collector_id"`
HTTPSDConfig *PromHTTPSDConfig `mapstructure:"http_sd_config"`
HTTPScrapeConfig *PromHTTPClientConfig `mapstructure:"http_scrape_config"`
}
// PromHTTPSDConfig is a redeclaration of promHTTP.SDConfig because we need custom unmarshaling
// as prometheus "config" uses `yaml` tags.
type PromHTTPSDConfig promHTTP.SDConfig
func (cfg *Config) Validate() error {
// ensure valid endpoint
if _, err := url.ParseRequestURI(cfg.Endpoint); err != nil {
return fmt.Errorf("TargetAllocator endpoint is not valid: %s", cfg.Endpoint)
}
// ensure valid collectorID without variables
if cfg.CollectorID == "" || strings.Contains(cfg.CollectorID, "${") {
return errors.New("CollectorID is not a valid ID")
}
return nil
}
var _ confmap.Unmarshaler = (*PromHTTPSDConfig)(nil)
func (cfg *PromHTTPSDConfig) Unmarshal(componentParser *confmap.Conf) error {
cfgMap := componentParser.ToStringMap()
if len(cfgMap) == 0 {
return nil
}
cfgMap["url"] = "http://placeholder" // we have to set it as else marshaling will fail
return unmarshalYAML(cfgMap, (*promHTTP.SDConfig)(cfg))
}
type PromHTTPClientConfig commonconfig.HTTPClientConfig
var _ confmap.Unmarshaler = (*PromHTTPClientConfig)(nil)
func (cfg *PromHTTPClientConfig) Unmarshal(componentParser *confmap.Conf) error {
cfgMap := componentParser.ToStringMap()
if len(cfgMap) == 0 {
return nil
}
return unmarshalYAML(cfgMap, (*commonconfig.HTTPClientConfig)(cfg))
}
func (cfg *PromHTTPClientConfig) Validate() error {
httpCfg := (*commonconfig.HTTPClientConfig)(cfg)
if err := validateHTTPClientConfig(httpCfg); err != nil {
return err
}
// Prometheus UnmarshalYaml implementation by default calls Validate,
// but it is safer to do it here as well.
return httpCfg.Validate()
}
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
}
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
}
// convertTLSVersion converts a string TLS version to the corresponding config.TLSVersion value in prometheus common.
func convertTLSVersion(version string) (commonconfig.TLSVersion, error) {
normalizedVersion := "TLS" + strings.ReplaceAll(version, ".", "")
if tlsVersion, exists := commonconfig.TLSVersions[normalizedVersion]; exists {
return tlsVersion, nil
}
return 0, fmt.Errorf("unsupported TLS version: %s", version)
}
// configureSDHTTPClientConfigFromTA configures the http client for the service discovery manager
// based on the provided TargetAllocator configuration.
func configureSDHTTPClientConfigFromTA(httpSD *promHTTP.SDConfig, allocConf *Config) error {
httpSD.HTTPClientConfig.FollowRedirects = false
httpSD.HTTPClientConfig.TLSConfig = commonconfig.TLSConfig{
InsecureSkipVerify: allocConf.TLSSetting.InsecureSkipVerify,
ServerName: allocConf.TLSSetting.ServerName,
CAFile: allocConf.TLSSetting.CAFile,
CertFile: allocConf.TLSSetting.CertFile,
KeyFile: allocConf.TLSSetting.KeyFile,
}
if allocConf.TLSSetting.CAPem != "" {
decodedCA, err := base64.StdEncoding.DecodeString(string(allocConf.TLSSetting.CAPem))
if err != nil {
return fmt.Errorf("failed to decode CA: %w", err)
}
httpSD.HTTPClientConfig.TLSConfig.CA = string(decodedCA)
}
if allocConf.TLSSetting.CertPem != "" {
decodedCert, err := base64.StdEncoding.DecodeString(string(allocConf.TLSSetting.CertPem))
if err != nil {
return fmt.Errorf("failed to decode Cert: %w", err)
}
httpSD.HTTPClientConfig.TLSConfig.Cert = string(decodedCert)
}
if allocConf.TLSSetting.KeyPem != "" {
decodedKey, err := base64.StdEncoding.DecodeString(string(allocConf.TLSSetting.KeyPem))
if err != nil {
return fmt.Errorf("failed to decode Key: %w", err)
}
httpSD.HTTPClientConfig.TLSConfig.Key = commonconfig.Secret(decodedKey)
}
if allocConf.TLSSetting.MinVersion != "" {
minVersion, err := convertTLSVersion(allocConf.TLSSetting.MinVersion)
if err != nil {
return err
}
httpSD.HTTPClientConfig.TLSConfig.MinVersion = minVersion
}
if allocConf.TLSSetting.MaxVersion != "" {
maxVersion, err := convertTLSVersion(allocConf.TLSSetting.MaxVersion)
if err != nil {
return err
}
httpSD.HTTPClientConfig.TLSConfig.MaxVersion = maxVersion
}
if allocConf.ProxyURL != "" {
proxyURL, err := url.Parse(allocConf.ProxyURL)
if err != nil {
return err
}
httpSD.HTTPClientConfig.ProxyURL = commonconfig.URL{URL: proxyURL}
}
return nil
}