InfluxDB: Fix tag interpolation when varable used within a regex pattern (#82785)

* fix tag interpolation

* remove redundant variables
This commit is contained in:
ismail simsek 2024-02-16 10:20:09 +01:00 committed by GitHub
parent 86c618a6d6
commit 94f544c9f6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 28 additions and 71 deletions

View File

@ -14,9 +14,7 @@ import (
var (
regexpOperatorPattern = regexp.MustCompile(`^\/.*\/$`)
regexpMeasurementPattern = regexp.MustCompile(`^\/.*\/$`)
regexMatcherSimple = regexp.MustCompile(`^/(.*)/$`)
regexMatcherWithStartEndPattern = regexp.MustCompile(`^/\^(.*)\$/$`)
mustEscapeCharsMatcher = regexp.MustCompile(`[\\^$*+?.()|[\]{}\/]`)
)
func (query *Query) Build(queryContext *backend.QueryDataRequest) (string, error) {
@ -112,7 +110,7 @@ func (query *Query) renderTags() []string {
var textValue string
switch tag.Operator {
case "=~", "!~", "":
textValue = escape(tag.Value)
textValue = tag.Value
case "<", ">", ">=", "<=":
textValue = removeRegexWrappers(tag.Value, `'`)
case "Is", "Is Not":
@ -258,44 +256,3 @@ func removeRegexWrappers(wrappedValue string, wrapper string) string {
return value
}
func escape(unescapedValue string) string {
pipe := `|`
beginning := `/^`
ending := `$/`
value := unescapedValue
substitute := `\$0`
fullMatch := false
// get the value only in between /^...$/
matches := regexMatcherWithStartEndPattern.FindStringSubmatch(unescapedValue)
if len(matches) > 1 {
// full match. the value is like /^value$/
value = matches[1]
fullMatch = true
}
if !fullMatch {
// get the value only in between /.../
matches = regexMatcherSimple.FindStringSubmatch(unescapedValue)
if len(matches) > 1 {
value = matches[1]
beginning = `/`
ending = `/`
}
}
// split them with pipe |
parts := strings.Split(value, pipe)
for i, v := range parts {
// escape each item
parts[i] = mustEscapeCharsMatcher.ReplaceAllString(v, substitute)
}
// stitch them to each other
escaped := make([]byte, 0, 64)
escaped = append(escaped, beginning...)
escaped = append(escaped, strings.Join(parts, pipe)...)
escaped = append(escaped, ending...)
return string(escaped)
}

View File

@ -304,30 +304,6 @@ func TestInfluxdbQueryBuilder(t *testing.T) {
require.Equal(t, query.renderMeasurement(), ` FROM "policy"./apa/`)
})
t.Run("can render regexp tags", func(t *testing.T) {
query := &Query{Tags: []*Tag{{Operator: "=~", Value: `/etc/hosts|/etc/hostname`, Key: "key"}}}
require.Equal(t, `"key" =~ /^\/etc\/hosts|\/etc\/hostname$/`, strings.Join(query.renderTags(), ""))
})
t.Run("can render regexp tags 2", func(t *testing.T) {
query := &Query{Tags: []*Tag{{Operator: "=~", Value: `/^/etc/hosts$/`, Key: "key"}}}
require.Equal(t, `"key" =~ /^\/etc\/hosts$/`, strings.Join(query.renderTags(), ""))
})
t.Run("can render regexp tags 3", func(t *testing.T) {
query := &Query{Tags: []*Tag{{Operator: "=~", Value: `/etc/hosts`, Key: "key"}}}
require.Equal(t, `"key" =~ /^\/etc\/hosts$/`, strings.Join(query.renderTags(), ""))
})
t.Run("can render regexp tags with dots in values", func(t *testing.T) {
query := &Query{Tags: []*Tag{{Operator: "=~", Value: `/etc/resolv.conf`, Key: "key"}}}
require.Equal(t, `"key" =~ /^\/etc\/resolv\.conf$/`, strings.Join(query.renderTags(), ""))
})
t.Run("can render single quoted tag value when regexed value has been sent", func(t *testing.T) {
query := &Query{Tags: []*Tag{{Operator: ">", Value: `/^12.2$/`, Key: "key"}}}

View File

@ -257,7 +257,12 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
return {
...tag,
key: this.templateSrv.replace(tag.key, scopedVars),
value: this.templateSrv.replace(tag.value, scopedVars, 'pipe'),
value: this.templateSrv.replace(
tag.value ?? '',
scopedVars,
(value: string | string[] = [], variable: QueryVariableModel) =>
this.interpolateQueryExpr(value, variable, tag.value)
),
};
});
}
@ -286,7 +291,6 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
};
}
// this should only be used for rawQueries.
interpolateQueryExpr(value: string | string[] = [], variable: QueryVariableModel, query?: string) {
// If there is no query just return the value directly
if (!query) {
@ -297,7 +301,11 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
// we always want to deal with special chars.
if (variable.multi) {
if (typeof value === 'string') {
return escapeRegex(value);
// Check the value is a number. If not run to escape special characters
if (isNaN(parseFloat(value))) {
return escapeRegex(value);
}
return value;
}
// If the value is a string array first escape them then join them with pipe

View File

@ -293,6 +293,22 @@ describe('InfluxDataSource Backend Mode', () => {
const qData = fetchMock.mock.calls[0][0].data.queries[0].query;
expect(qData).toBe(qe);
});
it('should interpolate variable inside a regex pattern', () => {
const query: InfluxQuery = {
refId: 'A',
tags: [
{
key: 'key',
operator: '=~',
value: '/^.*-$var1$/',
},
],
};
const res = ds.applyVariables(query, {});
const expected = `/^.*-var1$/`;
expect(res.tags?.[0].value).toEqual(expected);
});
});
describe('metric find query', () => {