diff --git a/promql/parse.go b/promql/parse.go index afd38904d..19a4cc193 100644 --- a/promql/parse.go +++ b/promql/parse.go @@ -883,6 +883,25 @@ func (p *parser) vectorSelector(name string) *VectorSelector { if len(matchers) == 0 { p.errorf("vector selector must contain label matchers or metric name") } + // A vector selector must contain at least one non-empty matcher to prevent + // implicit selection of all metrics (e.g. by a typo). + notEmpty := false + for _, lm := range matchers { + // Matching changes the inner state of the regex and causes reflect.DeepEqual + // to return false, which break tests. + // Thus, we create a new label matcher for this testing. + lm, err := metric.NewLabelMatcher(lm.Type, lm.Name, lm.Value) + if err != nil { + p.error(err) + } + if !lm.Match("") { + notEmpty = true + break + } + } + if !notEmpty { + p.errorf("vector selector must contain at least one non-empty matcher") + } var err error var offset time.Duration diff --git a/promql/parse_test.go b/promql/parse_test.go index 1e8bb7159..8721d0858 100644 --- a/promql/parse_test.go +++ b/promql/parse_test.go @@ -587,6 +587,22 @@ var testExpr = []struct { input: `{}`, fail: true, errMsg: "vector selector must contain label matchers or metric name", + }, { + input: `{x=""}`, + fail: true, + errMsg: "vector selector must contain at least one non-empty matcher", + }, { + input: `{x=~".*"}`, + fail: true, + errMsg: "vector selector must contain at least one non-empty matcher", + }, { + input: `{x!~".+"}`, + fail: true, + errMsg: "vector selector must contain at least one non-empty matcher", + }, { + input: `{x!="a"}`, + fail: true, + errMsg: "vector selector must contain at least one non-empty matcher", }, { input: `foo{__name__="bar"}`, fail: true, diff --git a/promql/testdata/legacy.test b/promql/testdata/legacy.test index 63e077b66..86861ab3c 100644 --- a/promql/testdata/legacy.test +++ b/promql/testdata/legacy.test @@ -378,7 +378,7 @@ eval instant at 50m drop_common_labels(http_requests{group="production",job="api http_requests{instance="0"} 100 http_requests{instance="1"} 200 -eval instant at 50m {__name__=~".*"} +eval instant at 50m {__name__=~".+"} http_requests{group="canary", instance="0", job="api-server"} 300 http_requests{group="canary", instance="0", job="app-server"} 700 http_requests{group="canary", instance="1", job="api-server"} 400 diff --git a/web/api/v1/api_test.go b/web/api/v1/api_test.go index efc114087..e9ee61e5e 100644 --- a/web/api/v1/api_test.go +++ b/web/api/v1/api_test.go @@ -277,7 +277,7 @@ func TestEndpoints(t *testing.T) { }, { endpoint: api.dropSeries, query: url.Values{ - "match[]": []string{`{__name__=~".*"}`}, + "match[]": []string{`{__name__=~".+"}`}, }, response: struct { NumDeleted int `json:"numDeleted"`