mirror of
https://github.com/grafana/grafana.git
synced 2025-01-02 12:17:01 -06:00
Alerting: Support utf8_strict_mode: false
in Mimir (#90092)
This commit is contained in:
parent
f88bf474bd
commit
650616a404
@ -72,10 +72,10 @@ describe('formAmRouteToAmRoute', () => {
|
||||
|
||||
// Assert
|
||||
expect(amRoute.matchers).toStrictEqual([
|
||||
'"foo"="bar"',
|
||||
'"foo"="bar\\"baz"',
|
||||
'"foo"="bar\\\\baz"',
|
||||
'"foo"="\\\\bar\\\\baz\\"\\\\"',
|
||||
'foo="bar"',
|
||||
'foo="bar\\"baz"',
|
||||
'foo="bar\\\\baz"',
|
||||
'foo="\\\\bar\\\\baz\\"\\\\"',
|
||||
]);
|
||||
});
|
||||
|
||||
@ -97,7 +97,7 @@ describe('formAmRouteToAmRoute', () => {
|
||||
|
||||
// Assert
|
||||
expect(amRoute.matchers).toStrictEqual([
|
||||
'"foo"="bar"',
|
||||
'foo="bar"',
|
||||
'"foo with spaces"="bar"',
|
||||
'"foo\\\\slash"="bar"',
|
||||
'"foo\\"quote"="bar"',
|
||||
@ -116,7 +116,7 @@ describe('formAmRouteToAmRoute', () => {
|
||||
const amRoute = formAmRouteToAmRoute('mimir-am', route, { id: 'root' });
|
||||
|
||||
// Assert
|
||||
expect(amRoute.matchers).toStrictEqual(['"foo"=""']);
|
||||
expect(amRoute.matchers).toStrictEqual(['foo=""']);
|
||||
});
|
||||
|
||||
it('should allow matchers with empty values for Grafana AM', () => {
|
||||
|
@ -8,7 +8,7 @@ import { MatcherFieldValue } from '../types/silence-form';
|
||||
|
||||
import { matcherToMatcherField } from './alertmanager';
|
||||
import { GRAFANA_RULES_SOURCE_NAME } from './datasource';
|
||||
import { normalizeMatchers, parseMatcherToArray, quoteWithEscape, unquoteWithUnescape } from './matchers';
|
||||
import { encodeMatcher, normalizeMatchers, parseMatcherToArray, unquoteWithUnescape } from './matchers';
|
||||
import { findExistingRoute } from './routeTree';
|
||||
import { isValidPrometheusDuration, safeParsePrometheusDuration } from './time';
|
||||
|
||||
@ -189,9 +189,8 @@ export const formAmRouteToAmRoute = (
|
||||
// Grafana maintains a fork of AM to support all utf-8 characters in the "object_matchers" property values but this
|
||||
// does not exist in upstream AlertManager
|
||||
if (alertManagerSourceName !== GRAFANA_RULES_SOURCE_NAME) {
|
||||
amRoute.matchers = formAmRoute.object_matchers?.map(
|
||||
({ name, operator, value }) => `${quoteWithEscape(name)}${operator}${quoteWithEscape(value)}`
|
||||
);
|
||||
// to support UTF-8 characters we must wrap label keys and values with double quotes if they contain reserved characters.
|
||||
amRoute.matchers = formAmRoute.object_matchers?.map(encodeMatcher);
|
||||
amRoute.object_matchers = undefined;
|
||||
} else {
|
||||
amRoute.object_matchers = normalizeMatchers(amRoute);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { MatcherOperator, Route } from '../../../../plugins/datasource/alertmanager/types';
|
||||
|
||||
import {
|
||||
encodeMatcher,
|
||||
getMatcherQueryParams,
|
||||
isPromQLStyleMatcher,
|
||||
matcherToObjectMatcher,
|
||||
@ -9,6 +10,7 @@ import {
|
||||
parsePromQLStyleMatcher,
|
||||
parseQueryParamMatchers,
|
||||
quoteWithEscape,
|
||||
quoteWithEscapeIfRequired,
|
||||
unquoteWithUnescape,
|
||||
} from './matchers';
|
||||
|
||||
@ -175,4 +177,19 @@ describe('parsePromQLStyleMatcher', () => {
|
||||
it('should throw when not using correct syntax', () => {
|
||||
expect(() => parsePromQLStyleMatcher('foo="bar"')).toThrow();
|
||||
});
|
||||
|
||||
it('should only encode matchers if the label key contains reserved characters', () => {
|
||||
expect(quoteWithEscapeIfRequired('foo')).toBe('foo');
|
||||
expect(quoteWithEscapeIfRequired('foo bar')).toBe('"foo bar"');
|
||||
expect(quoteWithEscapeIfRequired('foo{}bar')).toBe('"foo{}bar"');
|
||||
expect(quoteWithEscapeIfRequired('foo\\bar')).toBe('"foo\\\\bar"');
|
||||
});
|
||||
|
||||
it('should properly encode a matcher field', () => {
|
||||
expect(encodeMatcher({ name: 'foo', operator: MatcherOperator.equal, value: 'baz' })).toBe('foo="baz"');
|
||||
expect(encodeMatcher({ name: 'foo bar', operator: MatcherOperator.equal, value: 'baz' })).toBe('"foo bar"="baz"');
|
||||
expect(encodeMatcher({ name: 'foo{}bar', operator: MatcherOperator.equal, value: 'baz qux' })).toBe(
|
||||
'"foo{}bar"="baz qux"'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -10,6 +10,7 @@ import { compact, uniqBy } from 'lodash';
|
||||
import { Matcher, MatcherOperator, ObjectMatcher, Route } from 'app/plugins/datasource/alertmanager/types';
|
||||
|
||||
import { Labels } from '../../../../types/unified-alerting-dto';
|
||||
import { MatcherFieldValue } from '../types/silence-form';
|
||||
|
||||
import { isPrivateLabelKey } from './labels';
|
||||
|
||||
@ -144,6 +145,27 @@ export function quoteWithEscape(input: string) {
|
||||
return `"${escaped}"`;
|
||||
}
|
||||
|
||||
// The list of reserved characters that indicate we should be escaping the label key / value are
|
||||
// { } ! = ~ , \ " ' ` and any whitespace (\s), encoded in the regular expression below
|
||||
//
|
||||
// See Alertmanager PR: https://github.com/prometheus/alertmanager/pull/3453
|
||||
const RESERVED_CHARACTERS = /[\{\}\!\=\~\,\\\"\'\`\s]+/;
|
||||
|
||||
/**
|
||||
* Quotes string only when reserved characters are used
|
||||
*/
|
||||
export function quoteWithEscapeIfRequired(input: string) {
|
||||
const shouldQuote = RESERVED_CHARACTERS.test(input);
|
||||
return shouldQuote ? quoteWithEscape(input) : input;
|
||||
}
|
||||
|
||||
export const encodeMatcher = ({ name, operator, value }: MatcherFieldValue) => {
|
||||
const encodedLabelName = quoteWithEscapeIfRequired(name);
|
||||
const encodedLabelValue = quoteWithEscape(value);
|
||||
|
||||
return `${encodedLabelName}${operator}${encodedLabelValue}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unquotes and unescapes a string **if it has been quoted**
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user