mirror of
https://github.com/SigNoz/signoz.git
synced 2026-10-28 20:10:43 +00:00
Compare commits
11 Commits
ns/trace-s
...
keystone-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18a52a39a2 | ||
|
|
b8951defad | ||
|
|
c3fb588e6f | ||
|
|
b0c3eae3b8 | ||
|
|
4fe7b0ad81 | ||
|
|
3725772d46 | ||
|
|
17b78a98a2 | ||
|
|
6e5d295545 | ||
|
|
42beea849c | ||
|
|
08d69b1f26 | ||
|
|
3f51075634 |
@@ -3,6 +3,7 @@ package querybuilder
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
@@ -14,7 +15,10 @@ import (
|
||||
)
|
||||
|
||||
func TelemetrySelector(_ context.Context, resource coretypes.Resource, id string, _ valuer.UUID) ([]coretypes.Selector, error) {
|
||||
values := telemetrytypes.NewTelemetryGrantSelectors(id)
|
||||
values, err := telemetrySelectorValues(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
selectors := make([]coretypes.Selector, 0, len(values))
|
||||
for _, value := range values {
|
||||
@@ -182,27 +186,103 @@ func builderQuerySelectors(queryType, expression string, variables map[string]qb
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ids := make([]string, 0)
|
||||
atoms := make([]WhereClauseCondition, 0)
|
||||
for _, condition := range normalized.Conditions {
|
||||
if !condition.TopLevel {
|
||||
if !condition.TopLevel || len(condition.Values) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
key, ok := telemetrytypes.NewTelemetryGrantKey(condition.Key)
|
||||
if !ok {
|
||||
if condition.Operator != "=" && condition.Operator != "IN" {
|
||||
continue
|
||||
}
|
||||
|
||||
if condition.Operator == "=" || condition.Operator == "IN" {
|
||||
for _, value := range condition.Values {
|
||||
ids = append(ids, queryType+"/"+key+"/"+value)
|
||||
}
|
||||
if _, ok := telemetrytypes.NewTelemetryGrantKey(condition.Key); !ok {
|
||||
continue
|
||||
}
|
||||
atoms = append(atoms, condition)
|
||||
}
|
||||
if len(atoms) == 0 {
|
||||
return []string{typeWildcard}, nil
|
||||
}
|
||||
|
||||
if len(ids) == 0 {
|
||||
return []string{typeWildcard}, nil
|
||||
listIndex := -1
|
||||
for index, atom := range atoms {
|
||||
if atom.Operator != "IN" || len(atom.Values) < 2 {
|
||||
continue
|
||||
}
|
||||
if listIndex >= 0 {
|
||||
return []string{queryType + "/" + telemetryAtomsFilter(atoms)}, nil
|
||||
}
|
||||
listIndex = index
|
||||
}
|
||||
if listIndex < 0 {
|
||||
return []string{queryType + "/" + telemetryAtomsFilter(atoms)}, nil
|
||||
}
|
||||
|
||||
list := atoms[listIndex]
|
||||
ids := make([]string, 0, len(list.Values))
|
||||
for _, value := range list.Values {
|
||||
atoms[listIndex] = WhereClauseCondition{Key: list.Key, Operator: "=", Values: []string{value}}
|
||||
ids = append(ids, queryType+"/"+telemetryAtomsFilter(atoms))
|
||||
}
|
||||
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func telemetryAtomsFilter(atoms []WhereClauseCondition) string {
|
||||
parts := make([]string, 0, len(atoms))
|
||||
for _, atom := range atoms {
|
||||
literals := make([]string, 0, len(atom.Values))
|
||||
for _, value := range atom.Values {
|
||||
literals = append(literals, FilterStringLiteral(value))
|
||||
}
|
||||
if len(literals) == 1 {
|
||||
parts = append(parts, atom.Key+" = "+literals[0])
|
||||
continue
|
||||
}
|
||||
parts = append(parts, atom.Key+" IN ("+strings.Join(literals, ", ")+")")
|
||||
}
|
||||
slices.Sort(parts)
|
||||
|
||||
return strings.Join(slices.Compact(parts), " AND ")
|
||||
}
|
||||
|
||||
func telemetrySelectorValues(id string) ([]string, error) {
|
||||
queryType, filter, found := strings.Cut(id, "/")
|
||||
if !found || filter == coretypes.WildCardSelectorString {
|
||||
return telemetrytypes.NewTelemetryGrantSelectors(id), nil
|
||||
}
|
||||
|
||||
normalized, err := NormalizeWhereClause(filter, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
values := make([]string, 0)
|
||||
for _, condition := range normalized.Conditions {
|
||||
if !condition.TopLevel {
|
||||
continue
|
||||
}
|
||||
key, ok := telemetrytypes.NewTelemetryGrantKey(condition.Key)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
case condition.Operator == "=", condition.Operator == "IN" && len(condition.Values) == 1:
|
||||
values = append(values, queryType+"/"+key+"/"+condition.Values[0], queryType+"/"+key+"/"+coretypes.WildCardSelectorString)
|
||||
case condition.Operator == "IN":
|
||||
values = append(values, queryType+"/"+key+"/"+coretypes.WildCardSelectorString)
|
||||
}
|
||||
}
|
||||
values = append(values, queryType+"/"+coretypes.WildCardSelectorString, coretypes.WildCardSelectorString)
|
||||
|
||||
seen := make(map[string]struct{}, len(values))
|
||||
unique := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
if _, ok := seen[value]; ok {
|
||||
continue
|
||||
}
|
||||
seen[value] = struct{}{}
|
||||
unique = append(unique, value)
|
||||
}
|
||||
|
||||
return unique, nil
|
||||
}
|
||||
|
||||
@@ -21,100 +21,172 @@ func TestQueryRangeResources(t *testing.T) {
|
||||
expected []coretypes.ResourceWithID
|
||||
}{
|
||||
{
|
||||
name: "top level key equality",
|
||||
name: "TopLevelEquality_OneResource",
|
||||
body: builderQueryBody("logs", "signoz.workspace.key.id = 'checkout' AND status = 500"),
|
||||
expected: []coretypes.ResourceWithID{
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/signoz.workspace.key.id/checkout"},
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/signoz.workspace.key.id = 'checkout'"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "resource prefixed key",
|
||||
name: "ResourcePrefixedKey_KeptInID",
|
||||
body: builderQueryBody("traces", "resource.signoz.workspace.key.id = 'checkout'"),
|
||||
expected: []coretypes.ResourceWithID{
|
||||
{Resource: coretypes.ResourceTelemetryResourceTraces, ID: "builder_query/signoz.workspace.key.id/checkout"},
|
||||
{Resource: coretypes.ResourceTelemetryResourceTraces, ID: "builder_query/resource.signoz.workspace.key.id = 'checkout'"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "in atom requires every value",
|
||||
name: "SingleList_OneResourcePerValue",
|
||||
body: builderQueryBody("logs", "signoz.workspace.key.id IN ('b', 'a')"),
|
||||
expected: []coretypes.ResourceWithID{
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/signoz.workspace.key.id/a"},
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/signoz.workspace.key.id/b"},
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/signoz.workspace.key.id = 'a'"},
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/signoz.workspace.key.id = 'b'"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple equality atoms each require a grant",
|
||||
name: "SameKeyEqualities_OneResource",
|
||||
body: builderQueryBody("logs", "signoz.workspace.key.id = 'b' AND signoz.workspace.key.id = 'a'"),
|
||||
expected: []coretypes.ResourceWithID{
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/signoz.workspace.key.id/a"},
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/signoz.workspace.key.id/b"},
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/signoz.workspace.key.id = 'a' AND signoz.workspace.key.id = 'b'"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no filter expression",
|
||||
name: "DifferentKeyEqualities_OneResource",
|
||||
body: builderQueryBody("logs", "resource.service.name = 'checkout' AND resource.deployment.environment.name = 'prod'"),
|
||||
expected: []coretypes.ResourceWithID{
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/resource.deployment.environment.name = 'prod' AND resource.service.name = 'checkout'"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SingleListWithEquality_ExpandsPerValue",
|
||||
body: builderQueryBody("logs", "resource.service.name IN ('b', 'a') AND signoz.workspace.key.id = 'k'"),
|
||||
expected: []coretypes.ResourceWithID{
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/resource.service.name = 'a' AND signoz.workspace.key.id = 'k'"},
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/resource.service.name = 'b' AND signoz.workspace.key.id = 'k'"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "TwoLists_OneResource",
|
||||
body: builderQueryBody("logs", "resource.service.name IN ('a', 'b') AND signoz.workspace.key.id IN ('y', 'z')"),
|
||||
expected: []coretypes.ResourceWithID{
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/resource.service.name IN ('a', 'b') AND signoz.workspace.key.id IN ('y', 'z')"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SingleValueListBesideList_Pinned",
|
||||
body: builderQueryBody("logs", "resource.service.name IN ('a', 'b') AND signoz.workspace.key.id IN ('k')"),
|
||||
expected: []coretypes.ResourceWithID{
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/resource.service.name = 'a' AND signoz.workspace.key.id = 'k'"},
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/resource.service.name = 'b' AND signoz.workspace.key.id = 'k'"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SingleValueListsOnly_Pinned",
|
||||
body: builderQueryBody("logs", "resource.service.name IN ('a') AND signoz.workspace.key.id IN ('k')"),
|
||||
expected: []coretypes.ResourceWithID{
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/resource.service.name = 'a' AND signoz.workspace.key.id = 'k'"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "RepeatedList_ExpandsOnce",
|
||||
body: builderQueryBody("logs", "resource.service.name IN ('checkout') AND resource.service.name IN ('checkout')"),
|
||||
expected: []coretypes.ResourceWithID{
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/resource.service.name = 'checkout'"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "TwoListsOneKey_OneResource",
|
||||
body: builderQueryBody("logs", "signoz.workspace.key.id IN ('a', 'b') AND resource.signoz.workspace.key.id IN ('b', 'c')"),
|
||||
expected: []coretypes.ResourceWithID{
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/resource.signoz.workspace.key.id IN ('b', 'c') AND signoz.workspace.key.id IN ('a', 'b')"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OldEnvironmentSpelling_NotAnAtom",
|
||||
body: builderQueryBody("traces", "resource.deployment.environment = 'prod'"),
|
||||
expected: []coretypes.ResourceWithID{
|
||||
{Resource: coretypes.ResourceTelemetryResourceTraces, ID: "builder_query/*"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ServiceList_OneResourcePerValue",
|
||||
body: builderQueryBody("logs", "resource.service.name IN ('frontend', 'checkout')"),
|
||||
expected: []coretypes.ResourceWithID{
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/resource.service.name = 'checkout'"},
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/resource.service.name = 'frontend'"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "BareCustomerKey_NotAnAtom",
|
||||
body: builderQueryBody("logs", "service.name = 'checkout'"),
|
||||
expected: []coretypes.ResourceWithID{
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/*"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "NoFilter_QueryTypeWildcard",
|
||||
body: `{"compositeQuery":{"queries":[{"type":"builder_query","spec":{"signal":"logs"}}]}}`,
|
||||
expected: []coretypes.ResourceWithID{
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/*"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "key atom under or does not qualify",
|
||||
name: "TopLevelOr_NoAtoms",
|
||||
body: builderQueryBody("logs", "signoz.workspace.key.id = 'a' OR status = 500"),
|
||||
expected: []coretypes.ResourceWithID{
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/*"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "negated key atom does not qualify",
|
||||
body: builderQueryBody("logs", "NOT signoz.workspace.key.id = 'a'"),
|
||||
name: "ListUnderOr_NoAtoms",
|
||||
body: builderQueryBody("logs", "signoz.workspace.key.id IN ('a', 'b') OR status = 500"),
|
||||
expected: []coretypes.ResourceWithID{
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/*"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "key inequality does not qualify",
|
||||
body: builderQueryBody("logs", "signoz.workspace.key.id != 'a'"),
|
||||
name: "NestedNegationUnion_NoAtoms",
|
||||
body: builderQueryBody("logs", "NOT (NOT resource.service.name = 'checkout' AND NOT resource.service.name = 'frontend')"),
|
||||
expected: []coretypes.ResourceWithID{
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/*"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "value with spaces and slashes stays plaintext in the id",
|
||||
name: "ValueWithSpacesAndSlashes_Plaintext",
|
||||
body: builderQueryBody("logs", "signoz.workspace.key.id = 'check out/2'"),
|
||||
expected: []coretypes.ResourceWithID{
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/signoz.workspace.key.id/check out/2"},
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/signoz.workspace.key.id = 'check out/2'"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "audit source maps to audit logs resource",
|
||||
name: "AuditSource_AuditLogsResource",
|
||||
body: `{"compositeQuery":{"queries":[{"type":"builder_query","spec":{"signal":"logs","source":"audit","filter":{"expression":"signoz.workspace.key.id = 'a'"}}}]}}`,
|
||||
expected: []coretypes.ResourceWithID{
|
||||
{Resource: coretypes.ResourceTelemetryResourceAuditLogs, ID: "builder_query/signoz.workspace.key.id/a"},
|
||||
{Resource: coretypes.ResourceTelemetryResourceAuditLogs, ID: "builder_query/signoz.workspace.key.id = 'a'"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ai builder query maps to traces resource without a signal",
|
||||
name: "AIBuilderQuery_TracesResource",
|
||||
body: `{"compositeQuery":{"queries":[{"type":"builder_ai_query","spec":{"filter":{"expression":"signoz.workspace.key.id = 'checkout'"}}}]}}`,
|
||||
expected: []coretypes.ResourceWithID{
|
||||
{Resource: coretypes.ResourceTelemetryResourceTraces, ID: "builder_ai_query/signoz.workspace.key.id/checkout"},
|
||||
{Resource: coretypes.ResourceTelemetryResourceTraces, ID: "builder_ai_query/signoz.workspace.key.id = 'checkout'"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ai builder query without filter is wildcard",
|
||||
name: "AIBuilderQueryNoFilter_Wildcard",
|
||||
body: `{"compositeQuery":{"queries":[{"type":"builder_ai_query","spec":{}}]}}`,
|
||||
expected: []coretypes.ResourceWithID{
|
||||
{Resource: coretypes.ResourceTelemetryResourceTraces, ID: "builder_ai_query/*"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "promql is wildcard only",
|
||||
name: "PromQL_WildcardOnly",
|
||||
body: `{"compositeQuery":{"queries":[{"type":"promql","spec":{"query":"up"}}]}}`,
|
||||
expected: []coretypes.ResourceWithID{
|
||||
{Resource: coretypes.ResourceTelemetryResourceMetrics, ID: "promql/*"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "clickhouse sql covers all signals",
|
||||
name: "ClickHouseSQL_AllSignals",
|
||||
body: `{"compositeQuery":{"queries":[{"type":"clickhouse_sql","spec":{"query":"SELECT 1"}}]}}`,
|
||||
expected: []coretypes.ResourceWithID{
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "clickhouse_sql/*"},
|
||||
@@ -124,29 +196,29 @@ func TestQueryRangeResources(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "formula produces no resources",
|
||||
name: "Formula_NoResources",
|
||||
body: `{"compositeQuery":{"queries":[{"type":"builder_formula","spec":{"expression":"A/B"}}]}}`,
|
||||
expected: []coretypes.ResourceWithID{},
|
||||
},
|
||||
{
|
||||
name: "trace operator rides on its referenced queries",
|
||||
name: "TraceOperator_ReferencedQueries",
|
||||
body: `{"compositeQuery":{"queries":[{"type":"builder_query","spec":{"name":"A","signal":"traces","disabled":true,"filter":{"expression":"signoz.workspace.key.id = 'checkout'"}}},{"type":"builder_query","spec":{"name":"B","signal":"traces","disabled":true,"filter":{"expression":"signoz.workspace.key.id = 'checkout' AND has_error = true"}}},{"type":"builder_trace_operator","spec":{"name":"T1","expression":"A => B","returnSpansFrom":"A"}}]}}`,
|
||||
expected: []coretypes.ResourceWithID{
|
||||
{Resource: coretypes.ResourceTelemetryResourceTraces, ID: "builder_query/signoz.workspace.key.id/checkout"},
|
||||
{Resource: coretypes.ResourceTelemetryResourceTraces, ID: "builder_query/signoz.workspace.key.id = 'checkout'"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "variable substitution qualifies",
|
||||
name: "VariableSubstitution_Qualifies",
|
||||
body: `{"variables":{"key":{"value":"checkout"}},"compositeQuery":{"queries":[{"type":"builder_query","spec":{"signal":"logs","filter":{"expression":"signoz.workspace.key.id = $key"}}}]}}`,
|
||||
expected: []coretypes.ResourceWithID{
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/signoz.workspace.key.id/checkout"},
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/signoz.workspace.key.id = 'checkout'"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "duplicate queries dedupe",
|
||||
name: "DuplicateQueries_Deduped",
|
||||
body: `{"compositeQuery":{"queries":[{"type":"builder_query","spec":{"signal":"logs","filter":{"expression":"signoz.workspace.key.id = 'a'"}}},{"type":"builder_query","spec":{"signal":"logs","filter":{"expression":"signoz.workspace.key.id='a'"}}}]}}`,
|
||||
expected: []coretypes.ResourceWithID{
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/signoz.workspace.key.id/a"},
|
||||
{Resource: coretypes.ResourceTelemetryResourceLogs, ID: "builder_query/signoz.workspace.key.id = 'a'"},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -188,12 +260,28 @@ func TestTelemetrySelector(t *testing.T) {
|
||||
return values
|
||||
}
|
||||
|
||||
assert.Equal(t, []string{"builder_query/signoz.workspace.key.id/a", "builder_query/signoz.workspace.key.id/*", "builder_query/*", "*"}, selectorValues("builder_query/signoz.workspace.key.id/a"))
|
||||
assert.Equal(t, []string{"builder_query/signoz.workspace.key.id/a", "builder_query/signoz.workspace.key.id/*", "builder_query/*", "*"}, selectorValues("builder_query/signoz.workspace.key.id = 'a'"))
|
||||
assert.Equal(t, []string{"builder_query/signoz.workspace.key.id/a", "builder_query/signoz.workspace.key.id/*", "builder_query/*", "*"}, selectorValues("builder_query/resource.signoz.workspace.key.id = 'a' AND status = 500"))
|
||||
assert.Equal(t, []string{
|
||||
"builder_query/resource.service.name/x",
|
||||
"builder_query/resource.service.name/*",
|
||||
"builder_query/signoz.workspace.key.id/z",
|
||||
"builder_query/signoz.workspace.key.id/*",
|
||||
"builder_query/*",
|
||||
"*",
|
||||
}, selectorValues("builder_query/resource.service.name = 'x' AND signoz.workspace.key.id = 'z'"))
|
||||
assert.Equal(t, []string{"builder_query/signoz.workspace.key.id/*", "builder_query/*", "*"}, selectorValues("builder_query/signoz.workspace.key.id IN ('a', 'b')"))
|
||||
assert.Equal(t, []string{"builder_query/signoz.workspace.key.id/a", "builder_query/signoz.workspace.key.id/*", "builder_query/*", "*"}, selectorValues("builder_query/signoz.workspace.key.id IN ('a')"))
|
||||
assert.Equal(t, []string{"builder_query/*", "*"}, selectorValues("builder_query/signoz.workspace.key.id = 'a' OR status = 500"))
|
||||
assert.Equal(t, []string{"builder_query/*", "*"}, selectorValues("builder_query/service.name = 'x'"))
|
||||
assert.Equal(t, []string{"builder_query/signoz.workspace.key.id/a/b", "builder_query/signoz.workspace.key.id/*", "builder_query/*", "*"}, selectorValues("builder_query/signoz.workspace.key.id = 'a/b'"))
|
||||
assert.Equal(t, []string{"builder_query/*", "*"}, selectorValues("builder_query/*"))
|
||||
assert.Equal(t, []string{"builder_query/*", "*"}, selectorValues("builder_query"))
|
||||
assert.Equal(t, []string{"promql/*", "*"}, selectorValues("promql"))
|
||||
assert.Equal(t, []string{"promql/*", "*"}, selectorValues("promql/*"))
|
||||
assert.Equal(t, []string{"*"}, selectorValues("*"))
|
||||
// a value containing "/" stays one logical segment (SplitN 3).
|
||||
assert.Equal(t, []string{"builder_query/signoz.workspace.key.id/a/b", "builder_query/signoz.workspace.key.id/*", "builder_query/*", "*"}, selectorValues("builder_query/signoz.workspace.key.id/a/b"))
|
||||
|
||||
_, err := TelemetrySelector(context.Background(), coretypes.ResourceTelemetryResourceLogs, "builder_query/signoz.workspace.key.id = ", orgID)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestBuilderQueryWithFilterExpressionResources(t *testing.T) {
|
||||
@@ -205,7 +293,7 @@ func TestBuilderQueryWithFilterExpressionResources(t *testing.T) {
|
||||
{
|
||||
name: "FilterPresent_ScopedID",
|
||||
body: `{"start":1,"end":2,"filter":{"expression":"signoz.workspace.key.id = 'key-a'"}}`,
|
||||
expected: []coretypes.ResourceWithID{{Resource: coretypes.ResourceTelemetryResourceTraces, ID: "builder_query/signoz.workspace.key.id/key-a"}},
|
||||
expected: []coretypes.ResourceWithID{{Resource: coretypes.ResourceTelemetryResourceTraces, ID: "builder_query/signoz.workspace.key.id = 'key-a'"}},
|
||||
},
|
||||
{
|
||||
name: "FilterAbsent_Wildcard",
|
||||
|
||||
@@ -2,6 +2,7 @@ package querybuilder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -39,6 +40,7 @@ const (
|
||||
type normalizedPart struct {
|
||||
text string
|
||||
join joinKind
|
||||
negated bool
|
||||
skipped bool
|
||||
}
|
||||
|
||||
@@ -48,11 +50,12 @@ type normalizedValue struct {
|
||||
}
|
||||
|
||||
type whereClauseNormalizer struct {
|
||||
variables map[string]qbtypes.VariableItem
|
||||
conditions []WhereClauseCondition
|
||||
negated bool
|
||||
orDepth int
|
||||
errors []string
|
||||
variables map[string]qbtypes.VariableItem
|
||||
conditions []WhereClauseCondition
|
||||
negated bool
|
||||
orDepth int
|
||||
negationDepth int
|
||||
errors []string
|
||||
}
|
||||
|
||||
func NormalizeWhereClause(expression string, variables map[string]qbtypes.VariableItem) (*NormalizedWhereClause, error) {
|
||||
@@ -113,8 +116,11 @@ func NormalizeWhereClause(expression string, variables map[string]qbtypes.Variab
|
||||
sort.Slice(visitor.conditions, func(i, j int) bool {
|
||||
return visitor.conditions[i].sortKey() < visitor.conditions[j].sortKey()
|
||||
})
|
||||
conditions := slices.CompactFunc(visitor.conditions, func(left, right WhereClauseCondition) bool {
|
||||
return left.sortKey() == right.sortKey()
|
||||
})
|
||||
|
||||
return &NormalizedWhereClause{Expression: part.text, Conditions: visitor.conditions}, nil
|
||||
return &NormalizedWhereClause{Expression: part.text, Conditions: conditions}, nil
|
||||
}
|
||||
|
||||
func (condition WhereClauseCondition) sortKey() string {
|
||||
@@ -197,20 +203,22 @@ func (visitor *whereClauseNormalizer) visitUnaryExpression(ctx grammar.IUnaryExp
|
||||
negated := ctx.NOT() != nil
|
||||
if negated {
|
||||
visitor.negated = !visitor.negated
|
||||
visitor.negationDepth++
|
||||
}
|
||||
|
||||
part := visitor.visitPrimary(ctx.Primary())
|
||||
|
||||
if negated {
|
||||
visitor.negated = !visitor.negated
|
||||
visitor.negationDepth--
|
||||
|
||||
if part.skipped {
|
||||
return part
|
||||
}
|
||||
if part.join != joinKindNone {
|
||||
return normalizedPart{text: "NOT (" + part.text + ")", join: joinKindNone}
|
||||
if part.join != joinKindNone || part.negated {
|
||||
return normalizedPart{text: "NOT (" + part.text + ")", negated: true}
|
||||
}
|
||||
return normalizedPart{text: "NOT " + part.text, join: joinKindNone}
|
||||
return normalizedPart{text: "NOT " + part.text, negated: true}
|
||||
}
|
||||
|
||||
return part
|
||||
@@ -439,7 +447,7 @@ func (visitor *whereClauseNormalizer) appendCondition(key, operator string, valu
|
||||
Operator: operator,
|
||||
Values: values,
|
||||
Negated: visitor.negated,
|
||||
TopLevel: visitor.orDepth == 0 && !visitor.negated,
|
||||
TopLevel: visitor.orDepth == 0 && visitor.negationDepth == 0,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,15 @@ func TestNormalizeWhereClauseEquivalenceClasses(t *testing.T) {
|
||||
},
|
||||
expected: "service.name = 'frontend' AND status = 200",
|
||||
},
|
||||
{
|
||||
name: "nested not keeps its parentheses",
|
||||
expressions: []string{
|
||||
"NOT (NOT a = 1)",
|
||||
"NOT(NOT a=1)",
|
||||
"not (not a = 1)",
|
||||
},
|
||||
expected: "NOT (NOT a = 1)",
|
||||
},
|
||||
{
|
||||
name: "operand order",
|
||||
expressions: []string{
|
||||
@@ -275,15 +284,25 @@ func TestNormalizeWhereClauseTopLevel(t *testing.T) {
|
||||
expected: map[string]bool{"service.name": false, "status": true},
|
||||
},
|
||||
{
|
||||
name: "double negation restores top level",
|
||||
name: "any negation disqualifies even when it cancels",
|
||||
expression: "NOT (NOT (service.name = 'a'))",
|
||||
expected: map[string]bool{"service.name": true},
|
||||
expected: map[string]bool{"service.name": false},
|
||||
},
|
||||
{
|
||||
name: "in condition under and is top level",
|
||||
expression: "service.name IN ('a', 'b') AND x = 1",
|
||||
expected: map[string]bool{"service.name": true, "x": true},
|
||||
},
|
||||
{
|
||||
name: "nested negation over an and is a disjunction",
|
||||
expression: "NOT (NOT service.name = 'a' AND NOT status = 500)",
|
||||
expected: map[string]bool{"service.name": false, "status": false},
|
||||
},
|
||||
{
|
||||
name: "negation over an or is a conjunction of negations",
|
||||
expression: "NOT (service.name = 'a' OR status = 500)",
|
||||
expected: map[string]bool{"service.name": false, "status": false},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
@@ -327,6 +346,8 @@ func TestNormalizeWhereClauseIdempotence(t *testing.T) {
|
||||
"a IN [3, 1, 2] AND hasAll(tags, ['a', 'b']) AND body CONTAINS 'x'",
|
||||
"duration BETWEEN 1 AND 10 OR duration > 100",
|
||||
`msg = 'with \'escapes\' and "quotes"'`,
|
||||
"NOT (NOT resource.region = 'us-east')",
|
||||
"NOT (NOT (a = 1 OR b = 2)) AND NOT c EXISTS",
|
||||
}
|
||||
|
||||
for _, expression := range expressions {
|
||||
@@ -439,3 +460,13 @@ func TestFilterStringLiteral(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeWhereClauseDedupesRepeatedConditions(t *testing.T) {
|
||||
normalized, err := NormalizeWhereClause("service.name IN ('checkout') AND service.name IN ('checkout') AND status = 500 AND status = 500", nil)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "service.name IN ('checkout') AND status = 500", normalized.Expression)
|
||||
assert.Equal(t, []WhereClauseCondition{
|
||||
{Key: "service.name", Operator: "IN", Values: []string{"checkout"}, TopLevel: true},
|
||||
{Key: "status", Operator: "=", Values: []string{"500"}, TopLevel: true},
|
||||
}, normalized.Conditions)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package telemetrytypes
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
@@ -16,8 +17,12 @@ var telemetryGrantQueryTypes = map[string]bool{
|
||||
"clickhouse_sql": false,
|
||||
}
|
||||
|
||||
var telemetryGrantKeys = map[string]struct{}{
|
||||
"signoz.workspace.key.id": {},
|
||||
// signoz.workspace.key.id is stamped on the resource by ingestion and never arrives as an
|
||||
// attribute, so its bare spelling is unambiguous; customer-controlled keys must say resource.
|
||||
var telemetryGrantKeys = map[string]string{
|
||||
"signoz.workspace.key.id": "signoz.workspace.key.id",
|
||||
"service.name": "resource.service.name",
|
||||
"deployment.environment.name": "resource.deployment.environment.name",
|
||||
}
|
||||
|
||||
func NewTelemetryGrantKey(keyText string) (string, bool) {
|
||||
@@ -26,11 +31,16 @@ func NewTelemetryGrantKey(keyText string) (string, bool) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
if _, ok := telemetryGrantKeys[fieldKey.Name]; !ok {
|
||||
canonical, ok := telemetryGrantKeys[fieldKey.Name]
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
|
||||
return fieldKey.Name, true
|
||||
if fieldKey.FieldContext == FieldContextUnspecified && canonical != fieldKey.Name {
|
||||
return "", false
|
||||
}
|
||||
|
||||
return canonical, true
|
||||
}
|
||||
|
||||
func NewTelemetryGrantSelector(input string) (string, error) {
|
||||
@@ -104,8 +114,9 @@ func NewTelemetryGrantSelectors(selector string) []string {
|
||||
|
||||
func telemetryGrantKeyNames() []string {
|
||||
names := make([]string, 0, len(telemetryGrantKeys))
|
||||
for name := range telemetryGrantKeys {
|
||||
names = append(names, name)
|
||||
for _, canonical := range telemetryGrantKeys {
|
||||
names = append(names, canonical)
|
||||
}
|
||||
slices.Sort(names)
|
||||
return names
|
||||
}
|
||||
|
||||
@@ -14,11 +14,14 @@ func TestNewTelemetryGrantSelector(t *testing.T) {
|
||||
"builder_query/*": "builder_query/*",
|
||||
"promql": "promql/*",
|
||||
"clickhouse_sql": "clickhouse_sql/*",
|
||||
"builder_query/signoz.workspace.key.id/*": "builder_query/signoz.workspace.key.id/*",
|
||||
"builder_query/signoz.workspace.key.id/key-a": "builder_query/signoz.workspace.key.id/key-a",
|
||||
"builder_query/resource.signoz.workspace.key.id/key-a": "builder_query/signoz.workspace.key.id/key-a",
|
||||
"builder_query/signoz.workspace.key.id/key a": "builder_query/signoz.workspace.key.id/key a",
|
||||
"builder_query/signoz.workspace.key.id/a/b": "builder_query/signoz.workspace.key.id/a/b",
|
||||
"builder_query/signoz.workspace.key.id/*": "builder_query/signoz.workspace.key.id/*",
|
||||
"builder_query/signoz.workspace.key.id/key-a": "builder_query/signoz.workspace.key.id/key-a",
|
||||
"builder_query/resource.signoz.workspace.key.id/key-a": "builder_query/signoz.workspace.key.id/key-a",
|
||||
"builder_query/signoz.workspace.key.id/key a": "builder_query/signoz.workspace.key.id/key a",
|
||||
"builder_query/signoz.workspace.key.id/a/b": "builder_query/signoz.workspace.key.id/a/b",
|
||||
"builder_query/resource.service.name/frontend": "builder_query/resource.service.name/frontend",
|
||||
"builder_query/resource.service.name/*": "builder_query/resource.service.name/*",
|
||||
"builder_query/resource.deployment.environment.name/prod": "builder_query/resource.deployment.environment.name/prod",
|
||||
}
|
||||
for input, expected := range valid {
|
||||
canonical, err := NewTelemetryGrantSelector(input)
|
||||
@@ -32,6 +35,10 @@ func TestNewTelemetryGrantSelector(t *testing.T) {
|
||||
"signoz.workspace.key.id = 'key-a'",
|
||||
"builder_trace_operator/signoz.workspace.key.id/key-a",
|
||||
"builder_query/service.name/frontend",
|
||||
"builder_query/deployment.environment.name/prod",
|
||||
"builder_query/resource.deployment.environment/prod",
|
||||
"builder_query/attribute.service.name/frontend",
|
||||
"builder_query/host.name/frontend",
|
||||
"builder_query/signoz.workspace.key.id/",
|
||||
"builder_query/signoz.workspace.key.id/$svc",
|
||||
"*/signoz.workspace.key.id/key-a",
|
||||
@@ -48,8 +55,10 @@ func TestNewTelemetryGrantSelector(t *testing.T) {
|
||||
|
||||
func TestNewTelemetryGrantKey(t *testing.T) {
|
||||
valid := map[string]string{
|
||||
"signoz.workspace.key.id": "signoz.workspace.key.id",
|
||||
"resource.signoz.workspace.key.id": "signoz.workspace.key.id",
|
||||
"signoz.workspace.key.id": "signoz.workspace.key.id",
|
||||
"resource.signoz.workspace.key.id": "signoz.workspace.key.id",
|
||||
"resource.service.name": "resource.service.name",
|
||||
"resource.deployment.environment.name": "resource.deployment.environment.name",
|
||||
}
|
||||
for keyText, expected := range valid {
|
||||
key, ok := NewTelemetryGrantKey(keyText)
|
||||
@@ -57,7 +66,7 @@ func TestNewTelemetryGrantKey(t *testing.T) {
|
||||
assert.Equal(t, expected, key, keyText)
|
||||
}
|
||||
|
||||
for _, keyText := range []string{"service.name", "attribute.signoz.workspace.key.id", "body.signoz.workspace.key.id"} {
|
||||
for _, keyText := range []string{"service.name", "deployment.environment.name", "resource.deployment.environment", "host.name", "attribute.signoz.workspace.key.id", "attribute.service.name", "body.signoz.workspace.key.id"} {
|
||||
_, ok := NewTelemetryGrantKey(keyText)
|
||||
assert.False(t, ok, keyText)
|
||||
}
|
||||
|
||||
@@ -250,7 +250,7 @@ def test_filtered_routes_allow_granted_key(
|
||||
timeout=10,
|
||||
)
|
||||
assert denied.status_code == HTTPStatus.FORBIDDEN, denied.text
|
||||
assert "builder_query/signoz.workspace.key.id/key-b" in denied.text
|
||||
assert "builder_query/signoz.workspace.key.id = 'key-b'" in denied.text
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
@@ -50,7 +50,9 @@ def test_setup(
|
||||
[
|
||||
"signoz.workspace.key.id = 'key-a'", # expression form, not the wire form
|
||||
"unknown_query_type/signoz.workspace.key.id/key-a", # unsupported query type
|
||||
"builder_query/service.name/frontend", # service.name is not a supported grant key
|
||||
"builder_query/service.name/frontend", # customer keys must carry the resource prefix
|
||||
"builder_query/attribute.service.name/frontend", # grant keys are resource attributes only
|
||||
"builder_query/host.name/frontend", # host.name is not a supported grant key
|
||||
"*/signoz.workspace.key.id/key-a", # non-prefix wildcard
|
||||
"builder_query/signoz.workspace.key.id/", # empty value
|
||||
"builder_query/signoz.workspace.key.id", # missing value, not a wildcard
|
||||
@@ -153,7 +155,7 @@ def test_denied_message_names_resource(
|
||||
request_type="raw",
|
||||
)
|
||||
assert response.status_code == HTTPStatus.FORBIDDEN, response.text
|
||||
assert "builder_query/signoz.workspace.key.id/key-b" in response.text
|
||||
assert "builder_query/signoz.workspace.key.id = 'key-b'" in response.text
|
||||
|
||||
|
||||
def test_variables_resolve_into_gate(
|
||||
|
||||
135
tests/integration/tests/querierauthz/06_multi_key.py
Normal file
135
tests/integration/tests/querierauthz/06_multi_key.py
Normal file
@@ -0,0 +1,135 @@
|
||||
from collections.abc import Callable
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from http import HTTPStatus
|
||||
|
||||
import pytest
|
||||
|
||||
from fixtures import types
|
||||
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD, change_user_role, create_active_user
|
||||
from fixtures.logs import Logs
|
||||
from fixtures.querier import build_raw_query, make_query_request
|
||||
from fixtures.role import transaction_group
|
||||
|
||||
user_password = "password123Z$"
|
||||
service_role = "telemetry-scope-service"
|
||||
service_email = "scope-service@telemetry.test"
|
||||
env_role = "telemetry-scope-env"
|
||||
env_email = "scope-env@telemetry.test"
|
||||
two_services_role = "telemetry-scope-two-services"
|
||||
two_services_email = "scope-two-services@telemetry.test"
|
||||
two_keys_role = "telemetry-scope-two-keys"
|
||||
two_keys_email = "scope-two-keys@telemetry.test"
|
||||
mixed_role = "telemetry-scope-mixed"
|
||||
mixed_email = "scope-mixed@telemetry.test"
|
||||
key_wild_role = "telemetry-scope-key-wild"
|
||||
key_wild_email = "scope-key-wild@telemetry.test"
|
||||
|
||||
two_lists = "resource.service.name IN ('checkout', 'frontend') AND signoz.workspace.key.id IN ('k1', 'k2')"
|
||||
|
||||
|
||||
def test_setup(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
create_role: Callable[..., str],
|
||||
) -> None:
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
for role, email, selectors in [
|
||||
(service_role, service_email, ["builder_query/resource.service.name/checkout"]),
|
||||
(env_role, env_email, ["builder_query/resource.deployment.environment.name/prod"]),
|
||||
(two_services_role, two_services_email, ["builder_query/resource.service.name/checkout", "builder_query/resource.service.name/frontend"]),
|
||||
(two_keys_role, two_keys_email, ["builder_query/signoz.workspace.key.id/k1", "builder_query/signoz.workspace.key.id/k2"]),
|
||||
(mixed_role, mixed_email, ["builder_query/resource.service.name/checkout", "builder_query/signoz.workspace.key.id/k2"]),
|
||||
(key_wild_role, key_wild_email, ["builder_query/signoz.workspace.key.id/*"]),
|
||||
]:
|
||||
create_role(admin_token, role, [transaction_group("read", "telemetryresource", "logs", selectors)])
|
||||
user_id = create_active_user(signoz, admin_token, email=email, role="signoz-viewer", password=user_password)
|
||||
change_user_role(signoz, admin_token, user_id, "signoz-viewer", role)
|
||||
|
||||
|
||||
@pytest.fixture(name="seed_logs")
|
||||
def seed_logs_fixture(insert_logs: Callable[[list[Logs]], None]) -> datetime:
|
||||
now = datetime.now(tz=UTC)
|
||||
insert_logs(
|
||||
[
|
||||
Logs(timestamp=now - timedelta(seconds=1), resources={"service.name": "checkout", "signoz.workspace.key.id": "k1", "deployment.environment.name": "prod"}, body="checkout-k1"),
|
||||
Logs(timestamp=now - timedelta(seconds=2), resources={"service.name": "frontend", "signoz.workspace.key.id": "k2", "deployment.environment.name": "prod"}, body="frontend-k2"),
|
||||
]
|
||||
)
|
||||
return now
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("email", "expression"),
|
||||
[
|
||||
(service_email, "resource.service.name = 'checkout'"),
|
||||
(service_email, "resource.service.name IN ('checkout')"),
|
||||
(service_email, "resource.service.name = 'checkout' AND severity_text = 'ERROR'"),
|
||||
# any granted atom selects the query; the other atoms only narrow it
|
||||
(service_email, "resource.service.name = 'checkout' AND resource.deployment.environment.name = 'prod'"),
|
||||
(env_email, "resource.service.name = 'checkout' AND resource.deployment.environment.name = 'prod'"),
|
||||
(service_email, "resource.service.name IN ('checkout') AND signoz.workspace.key.id IN ('k1', 'k2')"),
|
||||
# a single list is a union: every value, or another granted atom, covers it
|
||||
(two_services_email, "resource.service.name IN ('checkout', 'frontend') AND signoz.workspace.key.id IN ('k1')"),
|
||||
(two_keys_email, "resource.service.name IN ('checkout') AND signoz.workspace.key.id IN ('k1', 'k2')"),
|
||||
# two lists are not expanded: only a key wildcard covers them
|
||||
(key_wild_email, two_lists),
|
||||
],
|
||||
)
|
||||
def test_allowed(
|
||||
signoz: types.SigNoz,
|
||||
get_token: Callable[[str, str], str],
|
||||
seed_logs: datetime,
|
||||
email: str,
|
||||
expression: str,
|
||||
) -> None:
|
||||
response = make_query_request(
|
||||
signoz,
|
||||
get_token(email, user_password),
|
||||
int((seed_logs - timedelta(minutes=10)).timestamp() * 1000),
|
||||
int(seed_logs.timestamp() * 1000),
|
||||
[build_raw_query("A", "logs", limit=50, filter_expression=expression)],
|
||||
request_type="raw",
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("email", "expression", "denied_resource"),
|
||||
[
|
||||
(service_email, "service.name = 'checkout'", "builder_query/*"), # bare customer key never selects
|
||||
# NOT(NOT a AND NOT b) == a OR b: nested negations must not mark either value as a required atom
|
||||
(service_email, "NOT (NOT resource.service.name = 'checkout' AND NOT resource.service.name = 'frontend')", "builder_query/*"),
|
||||
(service_email, "resource.deployment.environment.name = 'prod'", "builder_query/resource.deployment.environment.name = 'prod'"),
|
||||
(service_email, "resource.service.name = 'frontend'", "builder_query/resource.service.name = 'frontend'"),
|
||||
(env_email, "resource.service.name = 'checkout'", "builder_query/resource.service.name = 'checkout'"),
|
||||
(
|
||||
service_email,
|
||||
"resource.service.name IN ('checkout', 'frontend') AND resource.deployment.environment.name = 'prod'",
|
||||
"builder_query/resource.deployment.environment.name = 'prod' AND resource.service.name = 'frontend'",
|
||||
),
|
||||
# two lists are not expanded, so value grants never cover them
|
||||
(two_services_email, two_lists, "builder_query/resource.service.name IN ('checkout', 'frontend') AND signoz.workspace.key.id IN ('k1', 'k2')"),
|
||||
(two_keys_email, two_lists, "builder_query/resource.service.name IN ('checkout', 'frontend') AND signoz.workspace.key.id IN ('k1', 'k2')"),
|
||||
(mixed_email, two_lists, "builder_query/resource.service.name IN ('checkout', 'frontend') AND signoz.workspace.key.id IN ('k1', 'k2')"),
|
||||
],
|
||||
)
|
||||
def test_denied(
|
||||
signoz: types.SigNoz,
|
||||
get_token: Callable[[str, str], str],
|
||||
seed_logs: datetime,
|
||||
email: str,
|
||||
expression: str,
|
||||
denied_resource: str,
|
||||
) -> None:
|
||||
response = make_query_request(
|
||||
signoz,
|
||||
get_token(email, user_password),
|
||||
int((seed_logs - timedelta(minutes=10)).timestamp() * 1000),
|
||||
int(seed_logs.timestamp() * 1000),
|
||||
[build_raw_query("A", "logs", limit=50, filter_expression=expression)],
|
||||
request_type="raw",
|
||||
)
|
||||
assert response.status_code == HTTPStatus.FORBIDDEN, response.text
|
||||
assert denied_resource in response.text
|
||||
Reference in New Issue
Block a user