1
0
mirror of https://github.com/rancher/steve.git synced 2025-09-08 18:59:58 +00:00

#50914 - Changed ParseQuery projectornamespace Handling (#718)

* changed ParseQuery projectornamespace handling

* addressed comments from eric/chad and tom

* fixing lint error

* added resourceVersion to list

* updated mocks

* updated tests

* Update tests after rebase

---------

Co-authored-by: Tom Lebreux <tom.lebreux@suse.com>
This commit is contained in:
Felipe Gehrke
2025-07-17 14:51:13 -03:00
committed by GitHub
parent 0c2c554c8c
commit b556256ed3
8 changed files with 67 additions and 20 deletions

View File

@@ -48,6 +48,7 @@ type WatchFilter struct {
type ByOptionsLister interface {
ListByOptions(ctx context.Context, lo *sqltypes.ListOptions, partitions []partition.Partition, namespace string) (*unstructured.UnstructuredList, int, string, error)
Watch(ctx context.Context, options WatchOptions, eventsCh chan<- watch.Event) error
GetLatestResourceVersion() []string
}
// this is set to a var so that it can be overridden by test code for mocking purposes

View File

@@ -44,6 +44,20 @@ func (m *MockByOptionsLister) EXPECT() *MockByOptionsListerMockRecorder {
return m.recorder
}
// GetLatestResourceVersion mocks base method.
func (m *MockByOptionsLister) GetLatestResourceVersion() []string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetLatestResourceVersion")
ret0, _ := ret[0].([]string)
return ret0
}
// GetLatestResourceVersion indicates an expected call of GetLatestResourceVersion.
func (mr *MockByOptionsListerMockRecorder) GetLatestResourceVersion() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLatestResourceVersion", reflect.TypeOf((*MockByOptionsLister)(nil).GetLatestResourceVersion))
}
// ListByOptions mocks base method.
func (m *MockByOptionsLister) ListByOptions(ctx context.Context, lo *sqltypes.ListOptions, partitions []partition.Partition, namespace string) (*unstructured.UnstructuredList, int, string, error) {
m.ctrl.T.Helper()

View File

@@ -285,6 +285,16 @@ func NewListOptionIndexer(ctx context.Context, s Store, opts ListOptionIndexerOp
return l, nil
}
func (l *ListOptionIndexer) GetLatestResourceVersion() []string {
var latestRV []string
l.latestRVLock.RLock()
latestRV = []string{l.latestRV}
l.latestRVLock.RUnlock()
return latestRV
}
func (l *ListOptionIndexer) Watch(ctx context.Context, opts WatchOptions, eventsCh chan<- watch.Event) error {
l.latestRVLock.RLock()
latestRV := l.latestRV

View File

@@ -4,6 +4,7 @@ package listprocessor
import (
"context"
"fmt"
"net/http"
"regexp"
"strconv"
"strings"
@@ -152,8 +153,9 @@ func ParseQuery(apiOp *types.APIRequest, namespaceCache Cache) (sqltypes.ListOpt
if err != nil {
return opts, err
}
if projOrNSFilters == nil {
return opts, apierror.NewAPIError(validation.NotFound, fmt.Sprintf("could not find any namespaces named [%s] or namespaces belonging to project named [%s]", projectsOrNamespaces, projectsOrNamespaces))
if len(projOrNSFilters) == 0 {
return opts, apierror.NewAPIError(validation.ErrorCode{Code: "No Data", Status: http.StatusNoContent},
fmt.Sprintf("could not find any namespaces named [%s] or namespaces belonging to project named [%s]", projectsOrNamespaces, projectsOrNamespaces))
}
if op == sqltypes.NotEq {
for _, filter := range projOrNSFilters {
@@ -182,7 +184,7 @@ func splitQuery(query string) []string {
}
func parseNamespaceOrProjectFilters(ctx context.Context, projOrNS string, op sqltypes.Op, namespaceInformer Cache) ([]sqltypes.Filter, error) {
var filters []sqltypes.Filter
filters := []sqltypes.Filter{}
for _, pn := range strings.Split(projOrNS, ",") {
uList, _, _, err := namespaceInformer.ListByOptions(ctx, &sqltypes.ListOptions{
Filters: []sqltypes.OrFilter{

View File

@@ -152,25 +152,14 @@ func TestParseQuery(t *testing.T) {
})
tests = append(tests, testCase{
description: "ParseQuery() with no errors returned should returned no errors. If projectsornamespaces is not empty" +
" and nsc does not return namespaces, an error should be returned.",
" and nsc does not return namespaces, it should return an empty filter array",
req: &types.APIRequest{
Request: &http.Request{
URL: &url.URL{RawQuery: "projectsornamespaces=somethin"},
},
},
expectedLO: sqltypes.ListOptions{
Filters: []sqltypes.OrFilter{
{
Filters: []sqltypes.Filter{
{
Field: []string{"metadata", "namespace"},
Matches: []string{"ns1"},
Op: sqltypes.Eq,
Partial: false,
},
},
},
},
Filters: []sqltypes.OrFilter{},
Pagination: sqltypes.Pagination{
Page: 1,
},

View File

@@ -781,10 +781,6 @@ func (s *Store) Delete(apiOp *types.APIRequest, schema *types.APISchema, id stri
// - a continue token, if there are more pages after the returned one
// - an error instead of all of the above if anything went wrong
func (s *Store) ListByPartitions(apiOp *types.APIRequest, apiSchema *types.APISchema, partitions []partition.Partition) (*unstructured.UnstructuredList, int, string, error) {
opts, err := listprocessor.ParseQuery(apiOp, s.namespaceCache)
if err != nil {
return nil, 0, "", err
}
// warnings from inside the informer are discarded
buffer := WarningBuffer{}
client, err := s.clientGetter.TableAdminClient(apiOp, apiSchema, "", &buffer)
@@ -803,6 +799,23 @@ func (s *Store) ListByPartitions(apiOp *types.APIRequest, apiSchema *types.APISc
if err != nil {
return nil, 0, "", fmt.Errorf("cachefor %v: %w", gvk, err)
}
opts, err := listprocessor.ParseQuery(apiOp, s.namespaceCache)
if err != nil {
var apiError *apierror.APIError
if errors.As(err, &apiError) {
if apiError.Code.Status == http.StatusNoContent {
list := &unstructured.UnstructuredList{}
resourceVersion := inf.ByOptionsLister.GetLatestResourceVersion()
if len(resourceVersion) > 0 {
list.SetResourceVersion(resourceVersion[0])
}
return list, 0, "", nil
}
}
return nil, 0, "", err
}
if gvk.Group == "ext.cattle.io" && (gvk.Kind == "Token" || gvk.Kind == "Kubeconfig") {
accessSet := accesscontrol.AccessSetFromAPIRequest(apiOp)
// See https://github.com/rancher/rancher/blob/7266e5e624f0d610c76ab0af33e30f5b72e11f61/pkg/ext/stores/tokens/tokens.go#L1186C2-L1195C3

View File

@@ -260,6 +260,7 @@ func TestListByPartitions(t *testing.T) {
cg := NewMockClientGetter(gomock.NewController(t))
cf := NewMockCacheFactory(gomock.NewController(t))
tb := NewMockTransformBuilder(gomock.NewController(t))
ri := NewMockResourceInterface(gomock.NewController(t))
s := &Store{
ctx: context.Background(),
@@ -313,6 +314,9 @@ func TestListByPartitions(t *testing.T) {
copy(listToReturn.Items, expectedItems)
nsi.EXPECT().ListByOptions(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, 0, "", fmt.Errorf("error")).Times(2)
cg.EXPECT().TableAdminClient(req, schema, "", &WarningBuffer{}).Return(ri, nil)
tb.EXPECT().GetTransformFunc(attributes.GVK(schema), gomock.Any(), false).Return(func(obj interface{}) (interface{}, error) { return obj, nil })
cf.EXPECT().CacheFor(context.Background(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), &tablelistconvert.Client{ResourceInterface: ri}, attributes.GVK(schema), attributes.Namespaced(schema), true)
_, err := listprocessor.ParseQuery(req, nsi)
assert.NotNil(t, err)

View File

@@ -45,6 +45,20 @@ func (m *MockByOptionsLister) EXPECT() *MockByOptionsListerMockRecorder {
return m.recorder
}
// GetLatestResourceVersion mocks base method.
func (m *MockByOptionsLister) GetLatestResourceVersion() []string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetLatestResourceVersion")
ret0, _ := ret[0].([]string)
return ret0
}
// GetLatestResourceVersion indicates an expected call of GetLatestResourceVersion.
func (mr *MockByOptionsListerMockRecorder) GetLatestResourceVersion() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLatestResourceVersion", reflect.TypeOf((*MockByOptionsLister)(nil).GetLatestResourceVersion))
}
// ListByOptions mocks base method.
func (m *MockByOptionsLister) ListByOptions(ctx context.Context, lo *sqltypes.ListOptions, partitions []partition.Partition, namespace string) (*unstructured.UnstructuredList, int, string, error) {
m.ctrl.T.Helper()