restrictor/query_restrictor.go (84 lines of code) (raw):
/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
package restrictor
import (
	"fmt"
	"strings"
	"github.com/prometheus/prometheus/pkg/labels"
	"github.com/prometheus/prometheus/promql/parser"
)
// QueryRestrictor provides functionality to add restrictor labels to a
// Prometheus query
type QueryRestrictor struct {
	matchers []labels.Matcher
	Opts
}
// Opts contains optional configurations for the QueryRestrictor
type Opts struct {
	ReplaceExistingLabel bool
}
var DefaultOpts = Opts{ReplaceExistingLabel: true}
// NewQueryRestrictor returns a new QueryRestrictor to be built upon
func NewQueryRestrictor(opts Opts) *QueryRestrictor {
	return &QueryRestrictor{
		matchers: []labels.Matcher{},
		Opts:     opts,
	}
}
// AddMatcher takes a key and an arbitrary number of values. If only one value
// is provided, an Equal matcher will be added to the restrictor. Otherwise a
// Regex matcher with an OR of the values will be added. e.g. {label=~"value1|value2"}
// If values is empty the label will be matched to the empty string e.g. {label=""}
// effectively, this matches which do not contain this label
func (q *QueryRestrictor) AddMatcher(key string, values ...string) *QueryRestrictor {
	if len(values) < 1 {
		q.matchers = append(q.matchers, labels.Matcher{Type: labels.MatchEqual, Name: key})
		return q
	}
	if len(values) == 1 {
		q.matchers = append(q.matchers, labels.Matcher{Type: labels.MatchEqual, Name: key, Value: values[0]})
		return q
	}
	q.matchers = append(q.matchers, labels.Matcher{Type: labels.MatchRegexp, Name: key, Value: strings.Join(values, "|")})
	return q
}
// RestrictQuery appends a label selector to each metric in a given query so
// that only metrics with those labels are returned from the query.
func (q *QueryRestrictor) RestrictQuery(query string) (string, error) {
	if query == "" {
		return "", fmt.Errorf("empty query string")
	}
	promQuery, err := parser.ParseExpr(query)
	if err != nil {
		return "", fmt.Errorf("error parsing query: %v", err)
	}
	parser.Inspect(promQuery, q.addRestrictorLabels())
	return promQuery.String(), nil
}
// Matchers returns the list of label matchers for the restrictor
func (q *QueryRestrictor) Matchers() []labels.Matcher {
	return q.matchers
}
func (q *QueryRestrictor) addRestrictorLabels() func(n parser.Node, path []parser.Node) error {
	return func(n parser.Node, path []parser.Node) error {
		if n == nil {
			return nil
		}
		for _, matcher := range q.matchers {
			switch n := n.(type) {
			case *parser.VectorSelector:
				n.LabelMatchers = appendOrReplaceMatcher(n.LabelMatchers, matcher, q.ReplaceExistingLabel)
			case *parser.MatrixSelector:
				n.VectorSelector.(*parser.VectorSelector).LabelMatchers = appendOrReplaceMatcher(n.VectorSelector.(*parser.VectorSelector).LabelMatchers, matcher, q.ReplaceExistingLabel)
			}
		}
		return nil
	}
}
func appendOrReplaceMatcher(matchers []*labels.Matcher, newMatcher labels.Matcher, replaceExistingLabel bool) []*labels.Matcher {
	if replaceExistingLabel && getMatcherIndex(matchers, newMatcher.Name) >= 0 {
		return replaceLabelValue(matchers, newMatcher.Name, newMatcher.Value)
	}
	return append(matchers, &newMatcher)
}
func getMatcherIndex(matchers []*labels.Matcher, name string) int {
	for idx, match := range matchers {
		if match.Name == name {
			return idx
		}
	}
	return -1
}
func replaceLabelValue(matchers []*labels.Matcher, name, value string) []*labels.Matcher {
	idx := getMatcherIndex(matchers, name)
	if idx >= -1 {
		matchers[idx].Value = value
	}
	return matchers
}