Alerting: Support spaces in alert names for creating silence links (#71280)

This commit is contained in:
Gilles De Mey 2023-07-13 14:24:25 +02:00 committed by GitHub
parent 98f4bbf7fa
commit bd9b0d6d82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 38 additions and 12 deletions

View File

@ -10,6 +10,7 @@ import {
} from '../../../../plugins/datasource/alertmanager/types';
import { matcherToOperator } from '../utils/alertmanager';
import { getDatasourceAPIUid, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
import { wrapWithQuotes } from '../utils/misc';
import { alertingApi } from './alertingApi';
@ -39,7 +40,7 @@ export const alertmanagerApi = alertingApi.injectEndpoints({
// TODO Add support for active, silenced, inhibited, unprocessed filters
const filterMatchers = filter?.matchers
?.filter((matcher) => matcher.name && matcher.value)
.map((matcher) => `${matcher.name}${matcherToOperator(matcher)}${matcher.value}`);
.map((matcher) => `${matcher.name}${matcherToOperator(matcher)}${wrapWithQuotes(matcher.value)}`);
const { silenced, inhibited, unprocessed, active } = filter || {};

View File

@ -27,7 +27,7 @@ describe('Alertmanager utils', () => {
});
expect(parseMatcher('foo!~ bar')).toEqual<Matcher>({
name: 'foo',
value: 'bar',
value: ' bar',
isRegex: true,
isEqual: false,
});

View File

@ -12,23 +12,22 @@ const matcherOperators = [
];
export function parseMatcher(matcher: string): Matcher {
const trimmed = matcher.trim();
if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
throw new Error(`PromQL matchers not supported yet, sorry! PromQL matcher found: ${trimmed}`);
if (matcher.startsWith('{') && matcher.endsWith('}')) {
throw new Error(`PromQL matchers not supported yet, sorry! PromQL matcher found: ${matcher}`);
}
const operatorsFound = matcherOperators
.map((op): [MatcherOperator, number] => [op, trimmed.indexOf(op)])
.map((op): [MatcherOperator, number] => [op, matcher.indexOf(op)])
.filter(([_, idx]) => idx > -1)
.sort((a, b) => a[1] - b[1]);
if (!operatorsFound.length) {
throw new Error(`Invalid matcher: ${trimmed}`);
throw new Error(`Invalid matcher: ${matcher}`);
}
const [operator, idx] = operatorsFound[0];
const name = trimmed.slice(0, idx).trim();
const value = trimmed.slice(idx + operator.length).trim();
const name = matcher.slice(0, idx).trim();
const value = matcher.slice(idx + operator.length);
if (!name) {
throw new Error(`Invalid matcher: ${trimmed}`);
throw new Error(`Invalid matcher: ${matcher}`);
}
return {
@ -41,7 +40,7 @@ export function parseMatcher(matcher: string): Matcher {
// Parses a list of entries like like "['foo=bar', 'baz=~bad*']" into SilenceMatcher[]
export function parseQueryParamMatchers(matcherPairs: string[]): Matcher[] {
const parsedMatchers = matcherPairs.filter((x) => !!x.trim()).map((x) => parseMatcher(x.trim()));
const parsedMatchers = matcherPairs.filter((x) => !!x.trim()).map((x) => parseMatcher(x));
// Due to migration, old alert rules might have a duplicated alertname label
// To handle that case want to filter out duplicates and make sure there are only unique labels

View File

@ -1,4 +1,4 @@
import { sortAlerts } from 'app/features/alerting/unified/utils/misc';
import { sortAlerts, wrapWithQuotes, escapeQuotes } from 'app/features/alerting/unified/utils/misc';
import { SortOrder } from 'app/plugins/panel/alertlist/types';
import { Alert } from 'app/types/unified-alerting';
import { GrafanaAlertState } from 'app/types/unified-alerting-dto';
@ -33,6 +33,24 @@ function permute(inputArray: any[]): any[] {
}, []);
}
describe('wrapWithQuotes', () => {
it('should work as expected', () => {
expect(wrapWithQuotes('"hello, world!"')).toBe('\\"hello, world!\\"');
expect(wrapWithQuotes('hello, world!')).toBe('"hello, world!"');
expect(wrapWithQuotes('hello, "world"!')).toBe('"hello, \\"world\\"!"');
expect(wrapWithQuotes('"hello""')).toBe('\\"hello\\"\\"');
});
});
describe('escapeQuotes', () => {
it('should escape all quotes', () => {
expect(escapeQuotes('"hello, world!"')).toBe('\\"hello, world!\\"');
expect(escapeQuotes('hello, world!')).toBe('hello, world!');
expect(escapeQuotes('hello, "world"!')).toBe('hello, \\"world\\"!');
expect(escapeQuotes('hello"')).toBe('hello\\"');
});
});
describe('Unified Altering misc', () => {
describe('sortAlerts', () => {
describe('when using any sortOrder with a list of alert instances', () => {

View File

@ -102,7 +102,15 @@ export function makeAMLink(path: string, alertManagerName?: string, options?: UR
return `${path}?${search.toString()}`;
}
export const escapeQuotes = (input: string) => input.replace(/\"/g, '\\"');
export function wrapWithQuotes(input: string) {
const alreadyWrapped = input.startsWith('"') && input.endsWith('"');
return alreadyWrapped ? escapeQuotes(input) : `"${escapeQuotes(input)}"`;
}
export function makeRuleBasedSilenceLink(alertManagerSourceName: string, rule: CombinedRule) {
// we wrap the name of the alert with quotes since it might contain starting and trailing spaces
const labels: Labels = {
alertname: rule.name,
...rule.labels,