Add label value replacers to the config and query

This commit is contained in:
Carson Anderson 2021-03-05 16:37:40 -07:00
parent 7e11fe30ee
commit 0536a5d481
5 changed files with 35 additions and 15 deletions

View file

@ -38,6 +38,16 @@ type DiscoveryRule struct {
// `.GroupBy` is the comma-separated expected group-by label names. The delimeters // `.GroupBy` is the comma-separated expected group-by label names. The delimeters
// are `<<` and `>>`. // are `<<` and `>>`.
MetricsQuery string `json:"metricsQuery,omitempty" yaml:"metricsQuery,omitempty"` MetricsQuery string `json:"metricsQuery,omitempty" yaml:"metricsQuery,omitempty"`
// LabelValueReplacements is a list of find/replace strings that will be run against
// all label values before they are sent to Prometheus. This is sometimes required
// when prometheus labels have values that cannot be represented in Kubernetes.
// for example: "#" is valid as prometheus label value but not for kubernetes
LabelValueReplacements []LabelValueReplacement `json:"labelValueReplacements,omitempty" yaml:"labelValueReplacements,omitempty"`
}
type LabelValueReplacement struct {
Find string `json:"find,omitempty" yaml:"find,omitempty"`
Replace string `json:"replace,omitempty" yaml:"replace,omitempty"`
} }
// RegexFilter is a filter that matches positively or negatively against a regex. // RegexFilter is a filter that matches positively or negatively against a regex.

View file

@ -97,11 +97,12 @@ func (m *ReMatcher) Matches(val string) bool {
} }
type metricNamer struct { type metricNamer struct {
seriesQuery prom.Selector seriesQuery prom.Selector
metricsQuery MetricsQuery metricsQuery MetricsQuery
nameMatches *regexp.Regexp nameMatches *regexp.Regexp
nameAs string nameAs string
seriesMatchers []*ReMatcher seriesMatchers []*ReMatcher
labelReplacements []config.LabelValueReplacement
ResourceConverter ResourceConverter
} }
@ -155,7 +156,7 @@ func NamersFromConfig(cfg []config.DiscoveryRule, mapper apimeta.RESTMapper) ([]
return nil, err return nil, err
} }
metricsQuery, err := NewMetricsQuery(rule.MetricsQuery, resConv) metricsQuery, err := NewMetricsQuery(rule.MetricsQuery, resConv, rule.LabelValueReplacements)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to construct metrics query associated with series query %q: %v", rule.SeriesQuery, err) return nil, fmt.Errorf("unable to construct metrics query associated with series query %q: %v", rule.SeriesQuery, err)
} }

View file

@ -28,6 +28,7 @@ import (
"k8s.io/apimachinery/pkg/selection" "k8s.io/apimachinery/pkg/selection"
prom "github.com/kubernetes-sigs/prometheus-adapter/pkg/client" prom "github.com/kubernetes-sigs/prometheus-adapter/pkg/client"
"github.com/kubernetes-sigs/prometheus-adapter/pkg/config"
) )
// MetricsQuery represents a compiled metrics query for some set of // MetricsQuery represents a compiled metrics query for some set of
@ -50,15 +51,16 @@ type MetricsQuery interface {
// - LabelMatchersByName: the raw map-form of the above matchers // - LabelMatchersByName: the raw map-form of the above matchers
// - GroupBy: the group-by clause to use for the resources in the query (stringified) // - GroupBy: the group-by clause to use for the resources in the query (stringified)
// - GroupBySlice: the raw slice form of the above group-by clause // - GroupBySlice: the raw slice form of the above group-by clause
func NewMetricsQuery(queryTemplate string, resourceConverter ResourceConverter) (MetricsQuery, error) { func NewMetricsQuery(queryTemplate string, resourceConverter ResourceConverter, labelValueReplacements []config.LabelValueReplacement) (MetricsQuery, error) {
templ, err := template.New("metrics-query").Delims("<<", ">>").Parse(queryTemplate) templ, err := template.New("metrics-query").Delims("<<", ">>").Parse(queryTemplate)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to parse metrics query template %q: %v", queryTemplate, err) return nil, fmt.Errorf("unable to parse metrics query template %q: %v", queryTemplate, err)
} }
return &metricsQuery{ return &metricsQuery{
resConverter: resourceConverter, resConverter: resourceConverter,
template: templ, template: templ,
labelValueReplacements: labelValueReplacements,
}, nil }, nil
} }
@ -66,8 +68,9 @@ func NewMetricsQuery(queryTemplate string, resourceConverter ResourceConverter)
// with the delimiters as `<<` and `>>`, and the arguments found in // with the delimiters as `<<` and `>>`, and the arguments found in
// queryTemplateArgs. // queryTemplateArgs.
type metricsQuery struct { type metricsQuery struct {
resConverter ResourceConverter resConverter ResourceConverter
template *template.Template template *template.Template
labelValueReplacements []config.LabelValueReplacement
} }
// queryTemplateArgs contains the arguments for the template used in metricsQuery. // queryTemplateArgs contains the arguments for the template used in metricsQuery.
@ -170,6 +173,12 @@ func (q *metricsQuery) BuildExternal(seriesName string, namespace string, groupB
return "", err return "", err
} }
for _, repl := range q.labelValueReplacements {
for key, value := range valuesByName {
valuesByName[key] = strings.ReplaceAll(value, repl.Find, repl.Replace)
}
}
args := queryTemplateArgs{ args := queryTemplateArgs{
Series: seriesName, Series: seriesName,
LabelMatchers: strings.Join(exprs, ","), LabelMatchers: strings.Join(exprs, ","),

View file

@ -76,7 +76,7 @@ func checks(cs ...checkFunc) checkFunc {
func TestBuildSelector(t *testing.T) { func TestBuildSelector(t *testing.T) {
mustNewQuery := func(queryTemplate string, namespaced bool) MetricsQuery { mustNewQuery := func(queryTemplate string, namespaced bool) MetricsQuery {
mq, err := NewMetricsQuery(queryTemplate, &resourceConverterMock{namespaced}) mq, err := NewMetricsQuery(queryTemplate, &resourceConverterMock{namespaced}, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -272,7 +272,7 @@ func TestBuildSelector(t *testing.T) {
func TestBuildExternalSelector(t *testing.T) { func TestBuildExternalSelector(t *testing.T) {
mustNewQuery := func(queryTemplate string) MetricsQuery { mustNewQuery := func(queryTemplate string) MetricsQuery {
mq, err := NewMetricsQuery(queryTemplate, &resourceConverterMock{true}) mq, err := NewMetricsQuery(queryTemplate, &resourceConverterMock{true}, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -54,11 +54,11 @@ func newResourceQuery(cfg config.ResourceRule, mapper apimeta.RESTMapper) (resou
return resourceQuery{}, fmt.Errorf("unable to construct label-resource converter: %v", err) return resourceQuery{}, fmt.Errorf("unable to construct label-resource converter: %v", err)
} }
contQuery, err := naming.NewMetricsQuery(cfg.ContainerQuery, converter) contQuery, err := naming.NewMetricsQuery(cfg.ContainerQuery, converter, nil)
if err != nil { if err != nil {
return resourceQuery{}, fmt.Errorf("unable to construct container metrics query: %v", err) return resourceQuery{}, fmt.Errorf("unable to construct container metrics query: %v", err)
} }
nodeQuery, err := naming.NewMetricsQuery(cfg.NodeQuery, converter) nodeQuery, err := naming.NewMetricsQuery(cfg.NodeQuery, converter, nil)
if err != nil { if err != nil {
return resourceQuery{}, fmt.Errorf("unable to construct node metrics query: %v", err) return resourceQuery{}, fmt.Errorf("unable to construct node metrics query: %v", err)
} }