mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Add escape and quote support to the matcher name (#83601)
Add escape and quote support to the matcher name
This commit is contained in:
parent
36a19bfa83
commit
11d341d2bb
@ -72,10 +72,36 @@ 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\\"\\\\"',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should quote and escape matcher names', () => {
|
||||||
|
// Arrange
|
||||||
|
const route: FormAmRoute = buildFormAmRoute({
|
||||||
|
id: '1',
|
||||||
|
object_matchers: [
|
||||||
|
{ name: 'foo', operator: MatcherOperator.equal, value: 'bar' },
|
||||||
|
{ name: 'foo with spaces', operator: MatcherOperator.equal, value: 'bar' },
|
||||||
|
{ name: 'foo\\slash', operator: MatcherOperator.equal, value: 'bar' },
|
||||||
|
{ name: 'foo"quote', operator: MatcherOperator.equal, value: 'bar' },
|
||||||
|
{ name: 'fo\\o', operator: MatcherOperator.equal, value: 'ba\\r' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const amRoute = formAmRouteToAmRoute('mimir-am', route, { id: 'root' });
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(amRoute.matchers).toStrictEqual([
|
||||||
|
'"foo"="bar"',
|
||||||
|
'"foo with spaces"="bar"',
|
||||||
|
'"foo\\\\slash"="bar"',
|
||||||
|
'"foo\\"quote"="bar"',
|
||||||
|
'"fo\\\\o"="ba\\\\r"',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -90,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', () => {
|
||||||
@ -173,4 +199,23 @@ describe('amRouteToFormAmRoute', () => {
|
|||||||
{ name: 'foo', operator: MatcherOperator.equal, value: '\\bar\\baz"\\' },
|
{ name: 'foo', operator: MatcherOperator.equal, value: '\\bar\\baz"\\' },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should unquote and unescape matcher names', () => {
|
||||||
|
// Arrange
|
||||||
|
const amRoute = buildAmRoute({
|
||||||
|
matchers: ['"foo"=bar', '"foo with spaces"=bar', '"foo\\\\slash"=bar', '"foo"quote"=bar', '"fo\\\\o"="ba\\\\r"'],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const formRoute = amRouteToFormAmRoute(amRoute);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(formRoute.object_matchers).toStrictEqual([
|
||||||
|
{ name: 'foo', operator: MatcherOperator.equal, value: 'bar' },
|
||||||
|
{ name: 'foo with spaces', operator: MatcherOperator.equal, value: 'bar' },
|
||||||
|
{ name: 'foo\\slash', operator: MatcherOperator.equal, value: 'bar' },
|
||||||
|
{ name: 'foo"quote', operator: MatcherOperator.equal, value: 'bar' },
|
||||||
|
{ name: 'fo\\o', operator: MatcherOperator.equal, value: 'ba\\r' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -98,7 +98,7 @@ export const amRouteToFormAmRoute = (route: RouteWithID | Route | undefined): Fo
|
|||||||
route.matchers
|
route.matchers
|
||||||
?.map((matcher) => matcherToMatcherField(parseMatcher(matcher)))
|
?.map((matcher) => matcherToMatcherField(parseMatcher(matcher)))
|
||||||
.map(({ name, operator, value }) => ({
|
.map(({ name, operator, value }) => ({
|
||||||
name,
|
name: unquoteWithUnescape(name),
|
||||||
operator,
|
operator,
|
||||||
value: unquoteWithUnescape(value),
|
value: unquoteWithUnescape(value),
|
||||||
})) ?? [];
|
})) ?? [];
|
||||||
@ -186,7 +186,7 @@ export const formAmRouteToAmRoute = (
|
|||||||
// 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(
|
amRoute.matchers = formAmRoute.object_matchers?.map(
|
||||||
({ name, operator, value }) => `${name}${operator}${quoteWithEscape(value)}`
|
({ name, operator, value }) => `${quoteWithEscape(name)}${operator}${quoteWithEscape(value)}`
|
||||||
);
|
);
|
||||||
amRoute.object_matchers = undefined;
|
amRoute.object_matchers = undefined;
|
||||||
} else {
|
} else {
|
||||||
|
@ -137,9 +137,10 @@ export const matcherFormatter = {
|
|||||||
return `${name} ${operator} ${formattedValue}`;
|
return `${name} ${operator} ${formattedValue}`;
|
||||||
},
|
},
|
||||||
unquote: ([name, operator, value]: ObjectMatcher): string => {
|
unquote: ([name, operator, value]: ObjectMatcher): string => {
|
||||||
|
const unquotedName = unquoteWithUnescape(name);
|
||||||
// Unquoted value can be an empty string which we want to display as ""
|
// Unquoted value can be an empty string which we want to display as ""
|
||||||
const unquotedValue = unquoteWithUnescape(value) || '""';
|
const unquotedValue = unquoteWithUnescape(value) || '""';
|
||||||
return `${name} ${operator} ${unquotedValue}`;
|
return `${unquotedName} ${operator} ${unquotedValue}`;
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
getInheritedProperties,
|
getInheritedProperties,
|
||||||
matchLabels,
|
matchLabels,
|
||||||
normalizeRoute,
|
normalizeRoute,
|
||||||
|
unquoteRouteMatchers,
|
||||||
} from './notification-policies';
|
} from './notification-policies';
|
||||||
|
|
||||||
import 'core-js/stable/structured-clone';
|
import 'core-js/stable/structured-clone';
|
||||||
@ -476,3 +477,39 @@ describe('matchLabels', () => {
|
|||||||
expect(result.labelsMatch).toMatchSnapshot();
|
expect(result.labelsMatch).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('unquoteRouteMatchers', () => {
|
||||||
|
it('should unquote and unescape matchers values', () => {
|
||||||
|
const route: RouteWithID = {
|
||||||
|
id: '1',
|
||||||
|
object_matchers: [
|
||||||
|
['foo', MatcherOperator.equal, 'bar'],
|
||||||
|
['foo', MatcherOperator.equal, '"bar"'],
|
||||||
|
['foo', MatcherOperator.equal, '"b\\\\ar b\\"az"'],
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const unwrapped = unquoteRouteMatchers(route);
|
||||||
|
|
||||||
|
expect(unwrapped.object_matchers).toHaveLength(3);
|
||||||
|
expect(unwrapped.object_matchers).toContainEqual(['foo', MatcherOperator.equal, 'bar']);
|
||||||
|
expect(unwrapped.object_matchers).toContainEqual(['foo', MatcherOperator.equal, 'bar']);
|
||||||
|
expect(unwrapped.object_matchers).toContainEqual(['foo', MatcherOperator.equal, 'b\\ar b"az']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should unquote and unescape matcher names', () => {
|
||||||
|
const route: RouteWithID = {
|
||||||
|
id: '1',
|
||||||
|
object_matchers: [
|
||||||
|
['"f\\"oo with quote"', MatcherOperator.equal, 'bar'],
|
||||||
|
['"f\\\\oo with slash"', MatcherOperator.equal, 'bar'],
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const unwrapped = unquoteRouteMatchers(route);
|
||||||
|
|
||||||
|
expect(unwrapped.object_matchers).toHaveLength(2);
|
||||||
|
expect(unwrapped.object_matchers).toContainEqual(['f"oo with quote', MatcherOperator.equal, 'bar']);
|
||||||
|
expect(unwrapped.object_matchers).toContainEqual(['f\\oo with slash', MatcherOperator.equal, 'bar']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -127,7 +127,7 @@ export function normalizeRoute(rootRoute: RouteWithID): RouteWithID {
|
|||||||
export function unquoteRouteMatchers(route: RouteWithID): RouteWithID {
|
export function unquoteRouteMatchers(route: RouteWithID): RouteWithID {
|
||||||
function unquoteRoute(route: RouteWithID) {
|
function unquoteRoute(route: RouteWithID) {
|
||||||
route.object_matchers = route.object_matchers?.map(([name, operator, value]) => {
|
route.object_matchers = route.object_matchers?.map(([name, operator, value]) => {
|
||||||
return [name, operator, unquoteWithUnescape(value)];
|
return [unquoteWithUnescape(name), operator, unquoteWithUnescape(value)];
|
||||||
});
|
});
|
||||||
route.routes?.forEach(unquoteRoute);
|
route.routes?.forEach(unquoteRoute);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user