1
0
mirror of https://github.com/rancher/steve.git synced 2025-09-23 12:29:09 +00:00

Further fixes to the projectsornamespaces param, with new tests. (#772)

* Further fixes to the projectsornamespaces param, with new tests.

* Simplify projectsornamespaces on namespace kind

Move the processing of this special case from the SQL code generator
to the parser, and map it to simple `metadata.name=` and `metadata.labels[...]`
tests.

* Fix `projectsornamespace!=` code gen.

These should generate (f.namespace != X AND (negative label test).

The code was generating an `OR` giving too many false positives.

* Fix the projORns not error in the SQL
This commit is contained in:
Eric Promislow
2025-08-18 21:53:50 -07:00
committed by GitHub
parent 3119b3d8fa
commit 0de8a57d39
6 changed files with 288 additions and 58 deletions

View File

@@ -14,6 +14,7 @@ import (
"github.com/rancher/steve/pkg/stores/queryhelper"
"github.com/rancher/steve/pkg/stores/sqlpartition/queryparser"
"github.com/rancher/steve/pkg/stores/sqlpartition/selection"
"github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
@@ -77,7 +78,7 @@ func k8sRequirementToOrFilter(requirement queryparser.Requirement) (sqltypes.Fil
}
// ParseQuery parses the query params of a request and returns a ListOptions.
func ParseQuery(apiOp *types.APIRequest) (sqltypes.ListOptions, error) {
func ParseQuery(apiOp *types.APIRequest, gvKind string) (sqltypes.ListOptions, error) {
opts := sqltypes.ListOptions{}
q := apiOp.Request.URL.Query()
@@ -146,7 +147,23 @@ func ParseQuery(apiOp *types.APIRequest) (sqltypes.ListOptions, error) {
}
}
if projectsOrNamespaces != "" {
opts.ProjectsOrNamespaces = parseNamespaceOrProjectFilters(projectsOrNamespaces, op)
if gvKind == "Namespace" {
projNSFilter := parseNamespaceOrProjectFilters(projectsOrNamespaces, op)
if len(projNSFilter.Filters) == 2 {
if op == sqltypes.In {
opts.Filters = append(opts.Filters, projNSFilter)
} else {
opts.Filters = append(opts.Filters, sqltypes.OrFilter{Filters: []sqltypes.Filter{projNSFilter.Filters[0]}})
opts.Filters = append(opts.Filters, sqltypes.OrFilter{Filters: []sqltypes.Filter{projNSFilter.Filters[1]}})
}
} else if len(projNSFilter.Filters) == 0 {
// do nothing
} else {
logrus.Infof("Ignoring unexpected filter for query %q: parseNamespaceOrProjectFilters returned %d filters, expecting 2", q, len(projNSFilter.Filters))
}
} else {
opts.ProjectsOrNamespaces = parseNamespaceOrProjectFilters(projectsOrNamespaces, op)
}
}
return opts, nil

View File

@@ -16,6 +16,7 @@ func TestParseQuery(t *testing.T) {
type testCase struct {
description string
req *types.APIRequest
gvKind string // This is to distinguish Namespace projectornamespaces from others
expectedLO sqltypes.ListOptions
errExpected bool
errorText string
@@ -63,6 +64,118 @@ func TestParseQuery(t *testing.T) {
},
},
})
tests = append(tests, testCase{
description: "ParseQuery() with only projectsornamespaces on a namespace GVK should return a standard filter.",
req: &types.APIRequest{
Request: &http.Request{
URL: &url.URL{RawQuery: "projectsornamespaces=elm&filter=metadata.name~beech"},
},
},
gvKind: "Namespace",
expectedLO: sqltypes.ListOptions{
Filters: []sqltypes.OrFilter{
{
Filters: []sqltypes.Filter{
{
Field: []string{"metadata", "name"},
Matches: []string{"beech"},
Op: sqltypes.Eq,
Partial: true,
},
},
},
{
Filters: []sqltypes.Filter{
{
Field: []string{"metadata", "name"},
Matches: []string{"elm"},
Op: sqltypes.In,
},
{
Field: []string{"metadata", "labels", "field.cattle.io/projectId"},
Matches: []string{"elm"},
Op: sqltypes.In,
},
},
},
},
Pagination: sqltypes.Pagination{
Page: 1,
},
},
})
tests = append(tests, testCase{
description: "ParseQuery() with only a negative projectsornamespaces should return a project/ns filter.",
req: &types.APIRequest{
Request: &http.Request{
URL: &url.URL{RawQuery: "projectsornamespaces!=somethin"},
},
},
expectedLO: sqltypes.ListOptions{
Filters: []sqltypes.OrFilter{},
ProjectsOrNamespaces: sqltypes.OrFilter{
Filters: []sqltypes.Filter{
{
Field: []string{"metadata", "name"},
Matches: []string{"somethin"},
Op: sqltypes.NotIn,
},
{
Field: []string{"metadata", "labels", "field.cattle.io/projectId"},
Matches: []string{"somethin"},
Op: sqltypes.NotIn,
},
},
},
Pagination: sqltypes.Pagination{
Page: 1,
},
},
})
tests = append(tests, testCase{
description: "ParseQuery() with only negative projectsornamespaces on a namespace GVK should return a standard filter.",
req: &types.APIRequest{
Request: &http.Request{
URL: &url.URL{RawQuery: "projectsornamespaces!=elm&filter=metadata.name~beech"},
},
},
gvKind: "Namespace",
expectedLO: sqltypes.ListOptions{
Filters: []sqltypes.OrFilter{
{
Filters: []sqltypes.Filter{
{
Field: []string{"metadata", "name"},
Matches: []string{"beech"},
Op: sqltypes.Eq,
Partial: true,
},
},
},
{
Filters: []sqltypes.Filter{
{
Field: []string{"metadata", "name"},
Matches: []string{"elm"},
Op: sqltypes.NotIn,
},
},
},
{
Filters: []sqltypes.Filter{
{
Field: []string{"metadata", "labels", "field.cattle.io/projectId"},
Matches: []string{"elm"},
Op: sqltypes.NotIn,
},
},
},
},
Pagination: sqltypes.Pagination{
Page: 1,
},
},
})
tests = append(tests, testCase{
description: "ParseQuery() with filter param set should include filter with partial set to true in list options.",
req: &types.APIRequest{
@@ -758,7 +871,7 @@ func TestParseQuery(t *testing.T) {
//if test.description == "ParseQuery() with no errors: if projectsornamespaces is not empty, it should return an empty filter array" {
// fmt.Println("stop here")
//}
lo, err := ParseQuery(test.req)
lo, err := ParseQuery(test.req, test.gvKind)
if test.errExpected {
assert.NotNil(t, err)
if test.errorText != "" {