* prometheus: handle label-values with special characters
* added comment
(cherry picked from commit d363c36853)
Co-authored-by: Gábor Farkas <gabor.farkas@gmail.com>
This commit is contained in:
co-authored by
Gábor Farkas
parent
5d2fb57dd8
commit
ea030e8bbd
+168
-216
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@ import {
|
||||
BrowserLabel as PromLabel,
|
||||
} from '@grafana/ui';
|
||||
import PromQlLanguageProvider from '../language_provider';
|
||||
import { escapeLabelValueInExactSelector, escapeLabelValueInRegexSelector } from '../language_utils';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import store from 'app/core/store';
|
||||
import { FixedSizeList } from 'react-window';
|
||||
@@ -65,12 +66,12 @@ export function buildSelector(labels: SelectableLabel[]): string {
|
||||
if ((label.name === METRIC_LABEL || label.selected) && label.values && label.values.length > 0) {
|
||||
const selectedValues = label.values.filter((value) => value.selected).map((value) => value.name);
|
||||
if (selectedValues.length > 1) {
|
||||
selectedLabels.push(`${label.name}=~"${selectedValues.join('|')}"`);
|
||||
selectedLabels.push(`${label.name}=~"${selectedValues.map(escapeLabelValueInRegexSelector).join('|')}"`);
|
||||
} else if (selectedValues.length === 1) {
|
||||
if (label.name === METRIC_LABEL) {
|
||||
singleMetric = selectedValues[0];
|
||||
} else {
|
||||
selectedLabels.push(`${label.name}="${selectedValues[0]}"`);
|
||||
selectedLabels.push(`${label.name}="${escapeLabelValueInExactSelector(selectedValues[0])}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -864,6 +864,9 @@ export function extractRuleMappingFromGroups(groups: any[]) {
|
||||
);
|
||||
}
|
||||
|
||||
// NOTE: these two functions are very similar to the escapeLabelValueIn* functions
|
||||
// in language_utils.ts, but they are not exactly the same algorithm, and we found
|
||||
// no way to reuse one in the another or vice versa.
|
||||
export function prometheusRegularEscape(value: any) {
|
||||
return typeof value === 'string' ? value.replace(/\\/g, '\\\\').replace(/'/g, "\\\\'") : value;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { expandRecordingRules, fixSummariesMetadata, parseSelector } from './language_utils';
|
||||
import {
|
||||
escapeLabelValueInExactSelector,
|
||||
escapeLabelValueInRegexSelector,
|
||||
expandRecordingRules,
|
||||
fixSummariesMetadata,
|
||||
parseSelector,
|
||||
} from './language_utils';
|
||||
|
||||
describe('parseSelector()', () => {
|
||||
let parsed;
|
||||
@@ -168,3 +174,45 @@ describe('expandRecordingRules()', () => {
|
||||
).toBe('rate(fooA{label1="value1",label2="value2"}[])/ rate(fooB{label3="value3"}[])');
|
||||
});
|
||||
});
|
||||
|
||||
describe('escapeLabelValueInExactSelector()', () => {
|
||||
it('handles newline characters', () => {
|
||||
expect(escapeLabelValueInExactSelector('t\nes\nt')).toBe('t\\nes\\nt');
|
||||
});
|
||||
|
||||
it('handles backslash characters', () => {
|
||||
expect(escapeLabelValueInExactSelector('t\\es\\t')).toBe('t\\\\es\\\\t');
|
||||
});
|
||||
|
||||
it('handles double-quote characters', () => {
|
||||
expect(escapeLabelValueInExactSelector('t"es"t')).toBe('t\\"es\\"t');
|
||||
});
|
||||
|
||||
it('handles all together', () => {
|
||||
expect(escapeLabelValueInExactSelector('t\\e"st\nl\nab"e\\l')).toBe('t\\\\e\\"st\\nl\\nab\\"e\\\\l');
|
||||
});
|
||||
});
|
||||
|
||||
describe('escapeLabelValueInRegexSelector()', () => {
|
||||
it('handles newline characters', () => {
|
||||
expect(escapeLabelValueInRegexSelector('t\nes\nt')).toBe('t\\nes\\nt');
|
||||
});
|
||||
|
||||
it('handles backslash characters', () => {
|
||||
expect(escapeLabelValueInRegexSelector('t\\es\\t')).toBe('t\\\\\\\\es\\\\\\\\t');
|
||||
});
|
||||
|
||||
it('handles double-quote characters', () => {
|
||||
expect(escapeLabelValueInRegexSelector('t"es"t')).toBe('t\\"es\\"t');
|
||||
});
|
||||
|
||||
it('handles regex-meaningful characters', () => {
|
||||
expect(escapeLabelValueInRegexSelector('t+es$t')).toBe('t\\\\+es\\\\$t');
|
||||
});
|
||||
|
||||
it('handles all together', () => {
|
||||
expect(escapeLabelValueInRegexSelector('t\\e"s+t\nl\n$ab"e\\l')).toBe(
|
||||
't\\\\\\\\e\\"s\\\\+t\\nl\\n\\\\$ab\\"e\\\\\\\\l'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -239,3 +239,28 @@ export function limitSuggestions(items: string[]) {
|
||||
export function addLimitInfo(items: any[] | undefined): string {
|
||||
return items && items.length >= SUGGESTIONS_LIMIT ? `, limited to the first ${SUGGESTIONS_LIMIT} received items` : '';
|
||||
}
|
||||
|
||||
// NOTE: the following 2 exported functions are very similar to the prometheus*Escape
|
||||
// functions in datasource.ts, but they are not exactly the same algorithm, and we found
|
||||
// no way to reuse one in the another or vice versa.
|
||||
|
||||
// Prometheus regular-expressions use the RE2 syntax (https://github.com/google/re2/wiki/Syntax),
|
||||
// so every character that matches something in that list has to be escaped.
|
||||
// the list of metacharacters is: *+?()|\.[]{}^$
|
||||
// we make a javascript regular expression that matches those characters:
|
||||
const RE2_METACHARACTERS = /[*+?()|\\.\[\]{}^$]/g;
|
||||
function escapePrometheusRegexp(value: string): string {
|
||||
return value.replace(RE2_METACHARACTERS, '\\$&');
|
||||
}
|
||||
|
||||
// based on the openmetrics-documentation, the 3 symbols we have to handle are:
|
||||
// - \n ... the newline character
|
||||
// - \ ... the backslash character
|
||||
// - " ... the double-quote character
|
||||
export function escapeLabelValueInExactSelector(labelValue: string): string {
|
||||
return labelValue.replace(/\\/g, '\\\\').replace(/\n/g, '\\n').replace(/"/g, '\\"');
|
||||
}
|
||||
|
||||
export function escapeLabelValueInRegexSelector(labelValue: string): string {
|
||||
return escapeLabelValueInExactSelector(escapePrometheusRegexp(labelValue));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
load('scripts/drone/vault.star', 'from_secret', 'github_token', 'pull_secret', 'drone_token')
|
||||
|
||||
grabpl_version = '2.4.6'
|
||||
build_image = 'grafana/build-container:1.4.2'
|
||||
build_image = 'grafana/build-container:1.4.3'
|
||||
publish_image = 'grafana/grafana-ci-deploy:1.3.1'
|
||||
grafana_docker_image = 'grafana/drone-grafana-docker:0.3.2'
|
||||
deploy_docker_image = 'us.gcr.io/kubernetes-dev/drone/plugins/deploy-image'
|
||||
@@ -63,8 +63,6 @@ def initialize_step(edition, platform, ver_mode, is_downstream=False, install_de
|
||||
'curl -fLO https://github.com/jwilder/dockerize/releases/download/v$${DOCKERIZE_VERSION}/dockerize-linux-amd64-v$${DOCKERIZE_VERSION}.tar.gz',
|
||||
'tar -C bin -xzvf dockerize-linux-amd64-v$${DOCKERIZE_VERSION}.tar.gz',
|
||||
'rm dockerize-linux-amd64-v$${DOCKERIZE_VERSION}.tar.gz',
|
||||
'mv /etc/apt/sources.list.d/nodesource.list /etc/apt/sources.list.d/nodesource.list.disabled; apt-get update; apt-get -y upgrade; apt-get install -y ca-certificates libgnutls30',
|
||||
'mv /etc/apt/sources.list.d/nodesource.list.disabled /etc/apt/sources.list.d/nodesource.list',
|
||||
'yarn install --frozen-lockfile --no-progress',
|
||||
])
|
||||
if edition in ('enterprise', 'enterprise2'):
|
||||
@@ -698,8 +696,6 @@ def postgres_integration_tests_step():
|
||||
'POSTGRES_HOST': 'postgres',
|
||||
},
|
||||
'commands': [
|
||||
'mv /etc/apt/sources.list.d/nodesource.list /etc/apt/sources.list.d/nodesource.list.disabled; apt-get update; apt-get -y upgrade; apt-get install -y ca-certificates libgnutls30',
|
||||
'mv /etc/apt/sources.list.d/nodesource.list.disabled /etc/apt/sources.list.d/nodesource.list',
|
||||
'apt-get install -yq postgresql-client',
|
||||
'./bin/dockerize -wait tcp://postgres:5432 -timeout 120s',
|
||||
'psql -p 5432 -h postgres -U grafanatest -d grafanatest -f ' +
|
||||
@@ -723,8 +719,6 @@ def mysql_integration_tests_step():
|
||||
'MYSQL_HOST': 'mysql',
|
||||
},
|
||||
'commands': [
|
||||
'mv /etc/apt/sources.list.d/nodesource.list /etc/apt/sources.list.d/nodesource.list.disabled; apt-get update; apt-get -y upgrade; apt-get install -y ca-certificates libgnutls30',
|
||||
'mv /etc/apt/sources.list.d/nodesource.list.disabled /etc/apt/sources.list.d/nodesource.list',
|
||||
'apt-get install -yq default-mysql-client',
|
||||
'./bin/dockerize -wait tcp://mysql:3306 -timeout 120s',
|
||||
'cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root -prootpass',
|
||||
|
||||
Reference in New Issue
Block a user