Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 20 additions & 18 deletions receiver/prometheusreceiver/internal/targetallocator/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,24 +47,18 @@ func (cfg *Config) Validate() error {
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))
return unmarshalConf(componentParser, func(m map[string]any) {
// we have to set it as else marshaling will fail
m["url"] = "http://placeholder"
}, (*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))
return unmarshalConf(componentParser, nil, (*commonconfig.HTTPClientConfig)(cfg))
}

func (cfg *PromHTTPClientConfig) Validate() error {
Expand Down Expand Up @@ -109,13 +103,21 @@ func checkTLSConfig(tlsConfig commonconfig.TLSConfig) error {
return nil
}

func unmarshalYAML(in map[string]any, out any) error {
yamlOut, err := yaml.MarshalWithOptions(
in,
yaml.CustomMarshaler[commonconfig.Secret](func(s commonconfig.Secret) ([]byte, error) {
return []byte(s), nil
}),
)
// unmarshalConf unmarshals conf to out.
// YAML is used as an intermediary format, because confmap.Conf.Unmarshal is incompatible with Prometheus types,
// as the former uses mapstructure tags .
// If cb is not nil, it's called to mutate the map representation of conf.
func unmarshalConf(conf *confmap.Conf, cb func(map[string]any), out any) error {
in := conf.ToStringMap()
if len(in) == 0 {
return nil
}

if cb != nil {
cb(in)
}

yamlOut, err := yaml.Marshal(in)
if err != nil {
return fmt.Errorf("prometheus receiver: failed to marshal config to yaml: %w", err)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,39 @@ func TestComponentConfigStruct(t *testing.T) {
}

func TestLoadTargetAllocatorConfig(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)
cfg := &Config{}
t.Run("basic", func(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)
cfg := &Config{}

sub, err := cm.Sub("target_allocator")
require.NoError(t, err)
require.NoError(t, sub.Unmarshal(cfg))
require.NoError(t, xconfmap.Validate(cfg))
sub, err := cm.Sub("target_allocator")
require.NoError(t, err)
require.NoError(t, sub.Unmarshal(cfg))
require.NoError(t, xconfmap.Validate(cfg))

assert.Equal(t, "http://localhost:8080", cfg.Endpoint)
assert.Equal(t, 5*time.Second, cfg.Timeout)
assert.Equal(t, "client.crt", cfg.TLS.CertFile)
assert.Equal(t, 30*time.Second, cfg.Interval)
assert.Equal(t, "collector-1", cfg.CollectorID)
})

t.Run("special characters in password", func(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config_with_special_chars.yaml"))
require.NoError(t, err)

assert.Equal(t, "http://localhost:8080", cfg.Endpoint)
assert.Equal(t, 5*time.Second, cfg.Timeout)
assert.Equal(t, "client.crt", cfg.TLS.CertFile)
assert.Equal(t, 30*time.Second, cfg.Interval)
assert.Equal(t, "collector-1", cfg.CollectorID)
var cfg Config
sub, err := cm.Sub("target_allocator")
require.NoError(t, err)
require.NoError(t, sub.Unmarshal(&cfg))

require.NotNil(t, cfg.HTTPSDConfig, "http_sd_config should be present")
require.NotNil(t, cfg.HTTPScrapeConfig, "http_scrape_config should be present")
require.NotNil(t, cfg.HTTPScrapeConfig.BasicAuth, "basic_auth should be present")
assert.Equal(t, "testuser", cfg.HTTPScrapeConfig.BasicAuth.Username)
assert.Equal(t, "%password-with-percent", string(cfg.HTTPScrapeConfig.BasicAuth.Password),
"password with special YAML characters should be preserved")
})
}

func TestPromHTTPClientConfigValidateAuthorization(t *testing.T) {
Expand Down Expand Up @@ -488,3 +507,50 @@ func TestConfigureSDHTTPClientConfigFromTA_Errors(t *testing.T) {
})
}
}

func TestUnmarshalConf(t *testing.T) {
t.Run("empty config", func(t *testing.T) {
var cfg promConfig.HTTPClientConfig
err := unmarshalConf(confmap.NewFromStringMap(map[string]any{}), nil, &cfg)
require.NoError(t, err)
assert.Zero(t, cfg)
})

t.Run("special YAML characters preserved", func(t *testing.T) {
var cfg promConfig.HTTPClientConfig
input := map[string]any{
"basic_auth": map[string]any{
"username": "user",
"password": "%password-with-percent",
},
}
err := unmarshalConf(confmap.NewFromStringMap(input), nil, &cfg)
require.NoError(t, err)
require.NotNil(t, cfg.BasicAuth)
assert.Equal(t, "%password-with-percent", string(cfg.BasicAuth.Password))
})

t.Run("callback mutates config", func(t *testing.T) {
var cfg promConfig.HTTPClientConfig
input := map[string]any{
"basic_auth": map[string]any{
"username": "original",
},
}
cb := func(m map[string]any) {
m["basic_auth"].(map[string]any)["username"] = "mutated"
}
require.NoError(t, unmarshalConf(confmap.NewFromStringMap(input), cb, &cfg))
require.NotNil(t, cfg.BasicAuth)
assert.Equal(t, "mutated", cfg.BasicAuth.Username)
})

t.Run("marshal error", func(t *testing.T) {
var cfg promConfig.HTTPClientConfig
input := map[string]any{
"invalid": make(chan int), // channels can't be marshaled to YAML
}
err := unmarshalConf(confmap.NewFromStringMap(input), nil, &cfg)
require.ErrorContains(t, err, "failed to marshal")
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
target_allocator:
endpoint: http://localhost:8080
interval: 30s
collector_id: collector-1
http_sd_config:
refresh_interval: 60s
http_scrape_config:
basic_auth:
username: testuser
password: "%password-with-percent"