diff --git a/web/src/__tests__/attribute-filters.spec.ts b/web/src/__tests__/attribute-filters.spec.ts index b98e055c6..8ae9a95cf 100644 --- a/web/src/__tests__/attribute-filters.spec.ts +++ b/web/src/__tests__/attribute-filters.spec.ts @@ -1,7 +1,7 @@ import { filtersFromQuery, getContentPipelineStage, - getMatcherFromSet, + getK8sMatcherFromSet, getMatchersFromFilters, getNamespaceMatcher, getSeverityFilterPipelineStage, @@ -140,7 +140,7 @@ describe('Attribute filters', () => { expected: { label: 'test', operator: '=~', value: '"value-1|value-2"' }, }, ].forEach(({ set, label, expected }) => { - const selectors = getMatcherFromSet(label, set); + const selectors = getK8sMatcherFromSet(label, set); expect(selectors).toEqual(expected); }); }); @@ -363,6 +363,60 @@ describe('Attribute filters', () => { test('query from filters', () => { [ + { + initialQuery: '{ kubernetes_pod_name=~"a-pod|b-pod" }', + // If a regex is provided as a value, the operator must be =~ + filters: { + pod: new Set(['.*etcd.*']), + }, + expectedQuery: '{ kubernetes_pod_name=~".*etcd.*" }', + }, + { + initialQuery: '{ kubernetes_pod_name=~"a-pod|b-pod" }', + // Single dots are not considered regex as they are valid DNS names: + // https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + filters: { + pod: new Set(['openshift.pod']), + }, + expectedQuery: '{ kubernetes_pod_name="openshift.pod" }', + }, + { + initialQuery: '{ kubernetes_pod_name=~"a-pod|b-pod" }', + // A regex including a dot must have a quantifier (* or + or ?) + // as regex match in LogQL is fully anchored + // https://grafana.com/docs/loki/latest/query/log_queries/#log-stream-selector + filters: { + pod: new Set(['openshift.*pod']), + }, + expectedQuery: '{ kubernetes_pod_name=~"openshift.*pod" }', + }, + { + initialQuery: '{ kubernetes_pod_name=~"a-pod|b-pod" }', + // A regex including a dot must have a quantifier (* or + or ?) + // as regex match in LogQL is fully anchored + // https://grafana.com/docs/loki/latest/query/log_queries/#log-stream-selector + filters: { + pod: new Set(['openshift.+pod']), + }, + expectedQuery: '{ kubernetes_pod_name=~"openshift.+pod" }', + }, + { + initialQuery: '{ kubernetes_pod_name=~"a-pod|b-pod" }', + // A regex including a dot must have a quantifier (* or + or ?) + // as regex match in LogQL is fully anchored + // https://grafana.com/docs/loki/latest/query/log_queries/#log-stream-selector + filters: { + pod: new Set(['openshift.?pod']), + }, + expectedQuery: '{ kubernetes_pod_name=~"openshift.?pod" }', + }, + { + initialQuery: '{ kubernetes_pod_name=~"a-pod|b-pod" }', + filters: { + pod: new Set(['ns-1']), + }, + expectedQuery: '{ kubernetes_pod_name="ns-1" }', + }, { initialQuery: '{ kubernetes_pod_name=~"a-pod|b-pod", kubernetes_namespace_name=~"ns-1|ns-2", label="test", kubernetes_container_name="container-1" } |="some line content" | other="filter" | level="err|eror" or level="unknown" or level=""', diff --git a/web/src/attribute-filters.tsx b/web/src/attribute-filters.tsx index 5c1d1536b..e7440c321 100644 --- a/web/src/attribute-filters.tsx +++ b/web/src/attribute-filters.tsx @@ -273,6 +273,12 @@ export const getNamespaceMatcher = (namespace?: string): LabelMatcher | undefine }; }; +const isK8sValueARegex = (value: string) => { + // Regex to test if a string contains a regex matching a k8s name value + const testRegex = /[{}()[\]^$*+?|/\\]|\.[*+?]/; + return testRegex.test(value); +}; + export const queryWithNamespace = ({ query, namespace }: { query: string; namespace?: string }) => { const logQLQuery = new LogQLQuery(query ?? ''); @@ -281,17 +287,22 @@ export const queryWithNamespace = ({ query, namespace }: { query: string; namesp return logQLQuery.toString(); }; -export const getMatcherFromSet = (label: string, values: Set): LabelMatcher | undefined => { +export const getK8sMatcherFromSet = ( + label: string, + values: Set, +): LabelMatcher | undefined => { const valuesArray = Array.from(values); if (valuesArray.length === 0) { return undefined; } if (valuesArray.length === 1) { + const value = valuesArray[0]; + return { label, - operator: '=', - value: `"${valuesArray[0]}"`, + operator: isK8sValueARegex(value) ? '=~' : '=', + value: `"${value}"`, }; } @@ -314,13 +325,13 @@ export const getMatchersFromFilters = (filters?: Filters): Array = if (value) { switch (key) { case 'namespace': - matchers.push(getMatcherFromSet('kubernetes_namespace_name', value)); + matchers.push(getK8sMatcherFromSet('kubernetes_namespace_name', value)); break; case 'pod': - matchers.push(getMatcherFromSet('kubernetes_pod_name', value)); + matchers.push(getK8sMatcherFromSet('kubernetes_pod_name', value)); break; case 'container': - matchers.push(getMatcherFromSet('kubernetes_container_name', value)); + matchers.push(getK8sMatcherFromSet('kubernetes_container_name', value)); break; } }