Alerting: Allow notification policy filters to match quoted matchers (#98525)

This commit is contained in:
Gilles De Mey 2025-01-07 16:59:58 +01:00 committed by GitHub
parent 7c596bb4ed
commit f3cc1f7700
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 75 additions and 3 deletions

View File

@ -0,0 +1,47 @@
import { MatcherOperator, ObjectMatcher } from 'app/plugins/datasource/alertmanager/types';
import { findRoutesByMatchers } from './Filters';
describe('findRoutesByMatchers', () => {
it('should match even if keys or values are quoted', () => {
const routes = [
{ id: '0', matchers: ['foo=bar'] },
{ id: '0', matchers: ['foo="bar"'] },
{ id: '0', matchers: ['"foo"=bar'] },
{ id: '0', matchers: ['"foo"="bar"'] },
];
const matchers: ObjectMatcher[] = [
['foo', MatcherOperator.equal, 'bar'],
['foo', MatcherOperator.equal, '"bar"'],
['"foo"', MatcherOperator.equal, 'bar'],
['"foo"', MatcherOperator.equal, '"bar"'],
];
routes.forEach((route) => {
matchers.forEach((matcher) => {
expect(findRoutesByMatchers(route, [matcher])).toBe(true);
});
});
});
it('should match even if keys or values are quoted with special characters', () => {
const routes = [
{ id: '0', matchers: ['foo="bar baz"'] },
{ id: '0', matchers: ['"foo"="bar baz"'] },
];
const matchers: ObjectMatcher[] = [
['foo', MatcherOperator.equal, 'bar baz'],
['foo', MatcherOperator.equal, '"bar baz"'],
['"foo"', MatcherOperator.equal, 'bar baz'],
['"foo"', MatcherOperator.equal, '"bar baz"'],
];
matchers.forEach((matcher) => {
routes.forEach((route) => {
expect(findRoutesByMatchers(route, [matcher])).toBe(true);
});
});
});
});

View File

@ -14,6 +14,7 @@ import {
normalizeMatchers,
parsePromQLStyleMatcherLoose,
parsePromQLStyleMatcherLooseSafe,
unquoteIfRequired,
} from '../../utils/matchers';
interface NotificationPoliciesFilterProps {
@ -174,11 +175,20 @@ export function findRoutesMatchingPredicate(
}
export function findRoutesByMatchers(route: RouteWithID, labelMatchersFilter: ObjectMatcher[]): boolean {
const routeMatchers = normalizeMatchers(route);
return labelMatchersFilter.every((filter) => routeMatchers.some((matcher) => isEqual(filter, matcher)));
const filters = labelMatchersFilter.map(unquoteMatchersIfRequired);
const routeMatchers = normalizeMatchers(route).map(unquoteMatchersIfRequired);
return filters.every((filter) => routeMatchers.some((matcher) => isEqual(filter, matcher)));
}
/**
* This function is mostly used for decoding matchers like "test"="test" into test=test to remove quotes when they're not needed.
* This mimicks the behaviour in Alertmanager where it decodes the label matchers in the same way and makes searching for policies
* easier in case the label keys or values are quoted when they shouldn't really be.
*/
const unquoteMatchersIfRequired = ([key, operator, value]: ObjectMatcher): ObjectMatcher => {
return [unquoteIfRequired(key), operator, unquoteIfRequired(value)];
};
const getNotificationPoliciesFilters = (searchParams: URLSearchParams) => ({
queryString: searchParams.get('queryString') ?? undefined,
contactPoint: searchParams.get('contactPoint') ?? undefined,

View File

@ -13,6 +13,7 @@ import {
parseQueryParamMatchers,
quoteWithEscape,
quoteWithEscapeIfRequired,
unquoteIfRequired,
unquoteWithUnescape,
} from './matchers';
@ -129,6 +130,16 @@ describe('unquoteWithUnescape', () => {
});
});
describe('unquoteIfRequired', () => {
it('should unquote strings with no special character', () => {
expect(unquoteIfRequired('"test"')).toBe('test');
});
it('should not unquote strings with special character', () => {
expect(unquoteIfRequired('"test this"')).toBe('"test this"');
});
});
describe('isPromQLStyleMatcher', () => {
it('should detect promQL style matcher', () => {
expect(isPromQLStyleMatcher('{ foo=bar }')).toBe(true);

View File

@ -183,6 +183,10 @@ export function quoteWithEscapeIfRequired(input: string) {
return shouldQuote ? quoteWithEscape(input) : input;
}
export function unquoteIfRequired(input: string) {
return quoteWithEscapeIfRequired(unquoteWithUnescape(input));
}
export const encodeMatcher = ({ name, operator, value }: MatcherFieldValue) => {
const encodedLabelName = quoteWithEscapeIfRequired(name);
const encodedLabelValue = quoteWithEscape(value);