Skip to content
Closed
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
4 changes: 4 additions & 0 deletions cli/alert_add.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ be assumed to be the value of the alertname pair.

amtool alert add foo node=bar

To specify a label name that contains any UTF-8 character, enclose it in double quotes:

amtool alert add foo '"==label.name=="=bar'

One or more annotations can be added using the --annotation flag:

amtool alert add foo node=bar \
Expand Down
7 changes: 6 additions & 1 deletion cli/alert_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,19 @@ amtool alert query alertname=foo node=bar

amtool alert query foo node=bar

If label name contains any UTF-8 symbols, enclose it in double quotes

amtool alert query '"==label.name=="=bar'

If alertname is omitted and the first argument does not contain a '=' or a
'=~' then it will be assumed to be the value of the alertname pair.

amtool alert query 'alertname=~foo.*'

As well as direct equality, regex matching is also supported. The '=~' syntax
(similar to prometheus) is used to represent a regex match. Regex matching
can be used in combination with a direct match.
can be used in combination with a direct match.


Amtool supports several flags for filtering the returned alerts by state
(inhibited, silenced, active, unprocessed). If none of these flags is given,
Expand Down
4 changes: 4 additions & 0 deletions cli/silence_add.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ const silenceAddHelp = `Add a new alertmanager silence
As well as direct equality, regex matching is also supported. The '=~' syntax
(similar to Prometheus) is used to represent a regex match. Regex matching
can be used in combination with a direct match.

amtool silence add '"path.to.label"=~foo

To use any UTF-8 character in label name enclose it in double quotes.
`

func configureSilenceAddCmd(cc *kingpin.CmdClause) {
Expand Down
4 changes: 4 additions & 0 deletions cli/silence_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ amtool silence query alertname=foo node=bar

amtool silence query foo node=bar

If label name contains any UTF-8 symbols, enclose it in double quotes

amtool silence query '"==label.name=="=bar'

If alertname is omitted and the first argument does not contain a '=' or a
'=~' then it will be assumed to be the value of the alertname pair.

Expand Down
2 changes: 1 addition & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -802,7 +802,7 @@ func (r *Route) UnmarshalYAML(unmarshal func(interface{}) error) error {
r.GroupByAll = true
} else {
labelName := model.LabelName(l)
if !labelName.IsValid() {
if !labels.IsValidName(labelName) {
return fmt.Errorf("invalid label name %q in group_by list", l)
}
r.GroupBy = append(r.GroupBy, labelName)
Expand Down
17 changes: 15 additions & 2 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,14 +309,14 @@ receivers:
func TestGroupByInvalidLabel(t *testing.T) {
in := `
route:
group_by: ['-invalid-']
group_by: [' ']
receiver: team-X-mails
receivers:
- name: 'team-X-mails'
`
_, err := Load(in)

expected := "invalid label name \"-invalid-\" in group_by list"
expected := `invalid label name " " in group_by list`

if err == nil {
t.Fatalf("no error returned, expected:\n%q", expected)
Expand All @@ -326,6 +326,19 @@ receivers:
}
}

func TestGroupByUtf8Label(t *testing.T) {
in := `
route:
group_by: ['👍.any.label']
receiver: team-X-mails
receivers:
- name: 'team-X-mails'
`
_, err := Load(in)

require.NoError(t, err)
}

func TestRootRouteExists(t *testing.T) {
in := `
receivers:
Expand Down
24 changes: 14 additions & 10 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ value is set to the specified default.
Generic placeholders are defined as follows:

* `<duration>`: a duration matching the regular expression `((([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?|0)`, e.g. `1d`, `1h30m`, `5m`, `10s`
* `<labelname>`: a string matching the regular expression `[a-zA-Z_][a-zA-Z0-9_]*`
* `<prom_labelname>`: a string matching the regular expression `[a-zA-Z_][a-zA-Z0-9_]*`
* `<labelname>`: a string of unicode characters
* `<labelvalue>`: a string of unicode characters
* `<filepath>`: a valid path in the current working directory
* `<boolean>`: a boolean that can take the values `true` or `false`
Expand Down Expand Up @@ -171,12 +172,12 @@ current node.
# DEPRECATED: Use matchers below.
# A set of equality matchers an alert has to fulfill to match the node.
match:
[ <labelname>: <labelvalue>, ... ]
[ <prom_labelname>: <labelvalue>, ... ]

# DEPRECATED: Use matchers below.
# A set of regex-matchers an alert has to fulfill to match the node.
match_re:
[ <labelname>: <regex>, ... ]
[ <prom_labelname>: <regex>, ... ]

# A list of matchers that an alert has to fulfill to match the node.
matchers:
Expand Down Expand Up @@ -382,10 +383,10 @@ to reason about and does not trigger this special case.
# DEPRECATED: Use target_matchers below.
# Matchers that have to be fulfilled in the alerts to be muted.
target_match:
[ <labelname>: <labelvalue>, ... ]
[ <prom_labelname>: <labelvalue>, ... ]
# DEPRECATED: Use target_matchers below.
target_match_re:
[ <labelname>: <regex>, ... ]
[ <prom_labelname>: <regex>, ... ]

# A list of matchers that have to be fulfilled by the target
# alerts to be muted.
Expand All @@ -396,10 +397,10 @@ target_matchers:
# Matchers for which one or more alerts have to exist for the
# inhibition to take effect.
source_match:
[ <labelname>: <labelvalue>, ... ]
[ <prom_labelname>: <labelvalue>, ... ]
# DEPRECATED: Use source_matchers below.
source_match_re:
[ <labelname>: <regex>, ... ]
[ <prom_labelname>: <regex>, ... ]

# A list of matchers for which one or more alerts have
# to exist for the inhibition to take effect.
Expand All @@ -420,13 +421,15 @@ Label matchers are used both in routes and inhibition rules to match certain ale

A matcher is a string with a syntax inspired by PromQL and OpenMetrics. The syntax of a matcher consists of three tokens:

- A valid Prometheus label name.
- A UTF-8 string enclosed in double quotes that contains at least one non-whitespace character. If string is a valid Prometheus label name then double quotes can be omitted.

- One of `=`, `!=`, `=~`, or `!~`. `=` means equals, `!=` means that the strings are not equal, `=~` is used for equality of regex expressions and `!~` is used for un-equality of regex expressions. They have the same meaning as known from PromQL selectors.

- A UTF-8 string, which may be enclosed in double quotes. Before or after each token, there may be any amount of whitespace.
- A UTF-8 string, which may be enclosed in double quotes. Can be an empty string.

The 3rd token may be the empty string. Within the 3rd token, OpenMetrics escaping rules apply: `\"` for a double-quote, `\n` for a line feed, `\\` for a literal backslash. Unescaped `"` must not occur inside the 3rd token (only as the 1st or last character). However, literal line feed characters are tolerated, as are single `\` characters not followed by `\`, `n`, or `"`. They act as a literal backslash in that case.
Before or after each token, there may be any amount of whitespace.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was moved to a newline, just wanted to check if that was intentional?


Within the 1st and 3rd token, OpenMetrics escaping rules apply: `\"` for a double-quote, `\n` for a line feed, `\\` for a literal backslash. Unescaped `"` must not occur inside the token (only as the 1st or last character). However, literal line feed characters are tolerated, as are single `\` characters not followed by `\`, `n`, or `"`. They act as a literal backslash in that case.

Matchers are ANDed together, meaning that all matchers must evaluate to "true" when tested against the labels on a given alert. For example, an alert with these labels:

Expand All @@ -452,6 +455,7 @@ Here are some examples of valid string matchers:
matchers:
- foo = bar
- dings !=bums
- "utf-8.string" = value
```

2. Similar to example 1, shown below are two equality matchers combined in a short form YAML list.
Expand Down
48 changes: 48 additions & 0 deletions pkg/labels/labels.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2017 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package labels

import (
"fmt"
"unicode"
"unicode/utf8"

"github.com/prometheus/common/model"
)

// IsValidName validates that model.LabelName is not a whitespace string and contains only valid UTF-8 symbols
func IsValidName(ln model.LabelName) bool {
lns := string(ln)
allSpaces := true
for _, i := range lns {
if !unicode.IsSpace(i) {
allSpaces = false
break
}
}
return !allSpaces && utf8.ValidString(lns)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need utf8.ValidString if its also checked on Line 138 of parse.go?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is used in many other places to validate incoming labels: API calls, config parsing\validation. parse.go only parses matchers.

}

// IsValidSet validates that model.LabelSet keys and values are are valid
func IsValidSet(ls model.LabelSet) error {
for ln, lv := range ls {
if !IsValidName(ln) {
return fmt.Errorf("invalid name %q", ln)
}
if !lv.IsValid() {
return fmt.Errorf("invalid value %q", lv)
}
}
return nil
}
71 changes: 71 additions & 0 deletions pkg/labels/labels_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright 2017 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package labels

import (
"strings"
"testing"
"unicode"

"github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
)

func TestIsValidName(t *testing.T) {
testCases := []struct {
name string
labelName model.LabelName
valid bool
}{
{
name: "invalid: empty string",
labelName: "",
valid: false,
},
{
name: "invalid: all spaces",
labelName: " ",
valid: false,
},
{
name: "invalid: only whitespaces",
labelName: func() model.LabelName {
whiteSpaceBuilder := strings.Builder{}
for _, r16 := range unicode.White_Space.R16 {
for sym := r16.Lo; sym <= r16.Hi; sym += r16.Stride {
whiteSpaceBuilder.WriteRune(rune(sym))
}
}
return model.LabelName(whiteSpaceBuilder.String())
}(),
valid: false,
},
{
name: "valid: Prometheus label",
labelName: "TEST_label_name_12345",
valid: true,
},
{
name: "valid: any UTF-8 character",
labelName: "\nlabel:test data.爱!🙂\t",
valid: true,
},
}

for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
require.Equal(t, testCase.valid, IsValidName(testCase.labelName))
})
}
}
3 changes: 3 additions & 0 deletions pkg/labels/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ func NewMatcher(t MatchType, n, v string) (*Matcher, error) {
}

func (m *Matcher) String() string {
if !model.LabelName(m.Name).IsValid() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand what this does, would it be possible to explain it in a comment?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure! The idea is to check whether the name is Prometheus-compatible and return it without quotes (same as it does now), and wrap it in double quotes otherwise.

return fmt.Sprintf(`"%s"%s"%s"`, openMetricsEscape(m.Name), m.Type, openMetricsEscape(m.Value))
}
return fmt.Sprintf(`%s%s"%s"`, m.Name, m.Type, openMetricsEscape(m.Value))
}

Expand Down
6 changes: 6 additions & 0 deletions pkg/labels/matcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,12 @@ line`,
value: `tab stop`,
want: `foo="tab stop"`,
},
{
name: `utf-8.name\😉`,
op: MatchEqual,
value: `tab stop`,
want: `"utf-8.name\\😉"="tab stop"`,
},
}

for _, test := range tests {
Expand Down
Loading