mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Support utf8_strict_mode: false in Mimir (#90092)
This commit is contained in:
@@ -72,10 +72,10 @@ describe('formAmRouteToAmRoute', () => {
|
|||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(amRoute.matchers).toStrictEqual([
|
expect(amRoute.matchers).toStrictEqual([
|
||||||
'"foo"="bar"',
|
'foo="bar"',
|
||||||
'"foo"="bar\\"baz"',
|
'foo="bar\\"baz"',
|
||||||
'"foo"="bar\\\\baz"',
|
'foo="bar\\\\baz"',
|
||||||
'"foo"="\\\\bar\\\\baz\\"\\\\"',
|
'foo="\\\\bar\\\\baz\\"\\\\"',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ describe('formAmRouteToAmRoute', () => {
|
|||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(amRoute.matchers).toStrictEqual([
|
expect(amRoute.matchers).toStrictEqual([
|
||||||
'"foo"="bar"',
|
'foo="bar"',
|
||||||
'"foo with spaces"="bar"',
|
'"foo with spaces"="bar"',
|
||||||
'"foo\\\\slash"="bar"',
|
'"foo\\\\slash"="bar"',
|
||||||
'"foo\\"quote"="bar"',
|
'"foo\\"quote"="bar"',
|
||||||
@@ -116,7 +116,7 @@ describe('formAmRouteToAmRoute', () => {
|
|||||||
const amRoute = formAmRouteToAmRoute('mimir-am', route, { id: 'root' });
|
const amRoute = formAmRouteToAmRoute('mimir-am', route, { id: 'root' });
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(amRoute.matchers).toStrictEqual(['"foo"=""']);
|
expect(amRoute.matchers).toStrictEqual(['foo=""']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow matchers with empty values for Grafana AM', () => {
|
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 { matcherToMatcherField } from './alertmanager';
|
||||||
import { GRAFANA_RULES_SOURCE_NAME } from './datasource';
|
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 { findExistingRoute } from './routeTree';
|
||||||
import { isValidPrometheusDuration, safeParsePrometheusDuration } from './time';
|
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
|
// 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
|
// does not exist in upstream AlertManager
|
||||||
if (alertManagerSourceName !== GRAFANA_RULES_SOURCE_NAME) {
|
if (alertManagerSourceName !== GRAFANA_RULES_SOURCE_NAME) {
|
||||||
amRoute.matchers = formAmRoute.object_matchers?.map(
|
// to support UTF-8 characters we must wrap label keys and values with double quotes if they contain reserved characters.
|
||||||
({ name, operator, value }) => `${quoteWithEscape(name)}${operator}${quoteWithEscape(value)}`
|
amRoute.matchers = formAmRoute.object_matchers?.map(encodeMatcher);
|
||||||
);
|
|
||||||
amRoute.object_matchers = undefined;
|
amRoute.object_matchers = undefined;
|
||||||
} else {
|
} else {
|
||||||
amRoute.object_matchers = normalizeMatchers(amRoute);
|
amRoute.object_matchers = normalizeMatchers(amRoute);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { MatcherOperator, Route } from '../../../../plugins/datasource/alertmanager/types';
|
import { MatcherOperator, Route } from '../../../../plugins/datasource/alertmanager/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
encodeMatcher,
|
||||||
getMatcherQueryParams,
|
getMatcherQueryParams,
|
||||||
isPromQLStyleMatcher,
|
isPromQLStyleMatcher,
|
||||||
matcherToObjectMatcher,
|
matcherToObjectMatcher,
|
||||||
@@ -9,6 +10,7 @@ import {
|
|||||||
parsePromQLStyleMatcher,
|
parsePromQLStyleMatcher,
|
||||||
parseQueryParamMatchers,
|
parseQueryParamMatchers,
|
||||||
quoteWithEscape,
|
quoteWithEscape,
|
||||||
|
quoteWithEscapeIfRequired,
|
||||||
unquoteWithUnescape,
|
unquoteWithUnescape,
|
||||||
} from './matchers';
|
} from './matchers';
|
||||||
|
|
||||||
@@ -175,4 +177,19 @@ describe('parsePromQLStyleMatcher', () => {
|
|||||||
it('should throw when not using correct syntax', () => {
|
it('should throw when not using correct syntax', () => {
|
||||||
expect(() => parsePromQLStyleMatcher('foo="bar"')).toThrow();
|
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 { Matcher, MatcherOperator, ObjectMatcher, Route } from 'app/plugins/datasource/alertmanager/types';
|
||||||
|
|
||||||
import { Labels } from '../../../../types/unified-alerting-dto';
|
import { Labels } from '../../../../types/unified-alerting-dto';
|
||||||
|
import { MatcherFieldValue } from '../types/silence-form';
|
||||||
|
|
||||||
import { isPrivateLabelKey } from './labels';
|
import { isPrivateLabelKey } from './labels';
|
||||||
|
|
||||||
@@ -144,6 +145,27 @@ export function quoteWithEscape(input: string) {
|
|||||||
return `"${escaped}"`;
|
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**
|
* Unquotes and unescapes a string **if it has been quoted**
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user