mirror of
https://github.com/niusmallnan/steve.git
synced 2025-06-01 19:15:10 +00:00
Merge pull request #105 from cmurphy/projects-filtering
Add projectsornamespaces query parameter
This commit is contained in:
commit
1dfd3c711f
30
README.md
30
README.md
@ -117,6 +117,36 @@ item is included in the list.
|
|||||||
/v1/{type}?filter=spec.containers.image=alpine
|
/v1/{type}?filter=spec.containers.image=alpine
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### `projectsornamespaces`
|
||||||
|
|
||||||
|
Resources can also be filtered by the Rancher projects their namespaces belong
|
||||||
|
to. Since a project isn't an intrinsic part of the resource itself, the filter
|
||||||
|
parameter for filtering by projects is separate from the main `filter`
|
||||||
|
parameter. This query parameter is only applicable when steve is runnning in
|
||||||
|
concert with Rancher.
|
||||||
|
|
||||||
|
The list can be filtered by either projects or namespaces or both.
|
||||||
|
|
||||||
|
Filtering by a single project or a single namespace:
|
||||||
|
|
||||||
|
```
|
||||||
|
/v1/{type}?projectsornamespaces=p1
|
||||||
|
```
|
||||||
|
|
||||||
|
Filtering by multiple projects or namespaces is done with a comma separated
|
||||||
|
list. A resource matching any project or namespace in the list is included in
|
||||||
|
the result:
|
||||||
|
|
||||||
|
```
|
||||||
|
/v1/{type}?projectsornamespaces=p1,n1,n2
|
||||||
|
```
|
||||||
|
|
||||||
|
The list can be negated to exclude results:
|
||||||
|
|
||||||
|
```
|
||||||
|
/v1/{type}?projectsornamespaces!=p1,n1,n2
|
||||||
|
```
|
||||||
|
|
||||||
#### `sort`
|
#### `sort`
|
||||||
|
|
||||||
Only applicable to list requests (`/v1/{type}` and `/v1/{type}/{namespace}`).
|
Only applicable to list requests (`/v1/{type}` and `/v1/{type}/{namespace}`).
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/rancher/steve/pkg/stores/proxy"
|
"github.com/rancher/steve/pkg/stores/proxy"
|
||||||
"github.com/rancher/steve/pkg/summarycache"
|
"github.com/rancher/steve/pkg/summarycache"
|
||||||
"github.com/rancher/wrangler/pkg/data"
|
"github.com/rancher/wrangler/pkg/data"
|
||||||
|
corecontrollers "github.com/rancher/wrangler/pkg/generated/controllers/core/v1"
|
||||||
"github.com/rancher/wrangler/pkg/slice"
|
"github.com/rancher/wrangler/pkg/slice"
|
||||||
"github.com/rancher/wrangler/pkg/summary"
|
"github.com/rancher/wrangler/pkg/summary"
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
@ -21,9 +22,10 @@ import (
|
|||||||
|
|
||||||
func DefaultTemplate(clientGetter proxy.ClientGetter,
|
func DefaultTemplate(clientGetter proxy.ClientGetter,
|
||||||
summaryCache *summarycache.SummaryCache,
|
summaryCache *summarycache.SummaryCache,
|
||||||
asl accesscontrol.AccessSetLookup) schema.Template {
|
asl accesscontrol.AccessSetLookup,
|
||||||
|
namespaceCache corecontrollers.NamespaceCache) schema.Template {
|
||||||
return schema.Template{
|
return schema.Template{
|
||||||
Store: metricsStore.NewMetricsStore(proxy.NewProxyStore(clientGetter, summaryCache, asl)),
|
Store: metricsStore.NewMetricsStore(proxy.NewProxyStore(clientGetter, summaryCache, asl, namespaceCache)),
|
||||||
Formatter: formatter(summaryCache),
|
Formatter: formatter(summaryCache),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
steveschema "github.com/rancher/steve/pkg/schema"
|
steveschema "github.com/rancher/steve/pkg/schema"
|
||||||
"github.com/rancher/steve/pkg/stores/proxy"
|
"github.com/rancher/steve/pkg/stores/proxy"
|
||||||
"github.com/rancher/steve/pkg/summarycache"
|
"github.com/rancher/steve/pkg/summarycache"
|
||||||
|
corecontrollers "github.com/rancher/wrangler/pkg/generated/controllers/core/v1"
|
||||||
"k8s.io/apiserver/pkg/endpoints/request"
|
"k8s.io/apiserver/pkg/endpoints/request"
|
||||||
"k8s.io/client-go/discovery"
|
"k8s.io/client-go/discovery"
|
||||||
)
|
)
|
||||||
@ -46,9 +47,10 @@ func DefaultSchemaTemplates(cf *client.Factory,
|
|||||||
baseSchemas *types.APISchemas,
|
baseSchemas *types.APISchemas,
|
||||||
summaryCache *summarycache.SummaryCache,
|
summaryCache *summarycache.SummaryCache,
|
||||||
lookup accesscontrol.AccessSetLookup,
|
lookup accesscontrol.AccessSetLookup,
|
||||||
discovery discovery.DiscoveryInterface) []schema.Template {
|
discovery discovery.DiscoveryInterface,
|
||||||
|
namespaceCache corecontrollers.NamespaceCache) []schema.Template {
|
||||||
return []schema.Template{
|
return []schema.Template{
|
||||||
common.DefaultTemplate(cf, summaryCache, lookup),
|
common.DefaultTemplate(cf, summaryCache, lookup, namespaceCache),
|
||||||
apigroups.Template(discovery),
|
apigroups.Template(discovery),
|
||||||
{
|
{
|
||||||
ID: "configmap",
|
ID: "configmap",
|
||||||
|
@ -145,7 +145,7 @@ func setup(ctx context.Context, server *Server) error {
|
|||||||
summaryCache := summarycache.New(sf, ccache)
|
summaryCache := summarycache.New(sf, ccache)
|
||||||
summaryCache.Start(ctx)
|
summaryCache.Start(ctx)
|
||||||
|
|
||||||
for _, template := range resources.DefaultSchemaTemplates(cf, server.BaseSchemas, summaryCache, asl, server.controllers.K8s.Discovery()) {
|
for _, template := range resources.DefaultSchemaTemplates(cf, server.BaseSchemas, summaryCache, asl, server.controllers.K8s.Discovery(), server.controllers.Core.Namespace().Cache()) {
|
||||||
sf.AddTemplate(template)
|
sf.AddTemplate(template)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/rancher/apiserver/pkg/types"
|
"github.com/rancher/apiserver/pkg/types"
|
||||||
"github.com/rancher/wrangler/pkg/data"
|
"github.com/rancher/wrangler/pkg/data"
|
||||||
"github.com/rancher/wrangler/pkg/data/convert"
|
"github.com/rancher/wrangler/pkg/data/convert"
|
||||||
|
corecontrollers "github.com/rancher/wrangler/pkg/generated/controllers/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,7 +23,11 @@ const (
|
|||||||
pageSizeParam = "pagesize"
|
pageSizeParam = "pagesize"
|
||||||
pageParam = "page"
|
pageParam = "page"
|
||||||
revisionParam = "revision"
|
revisionParam = "revision"
|
||||||
|
projectsOrNamespacesVar = "projectsornamespaces"
|
||||||
|
projectIDFieldLabel = "field.cattle.io/projectId"
|
||||||
|
|
||||||
orOp = ","
|
orOp = ","
|
||||||
|
notOp = "!"
|
||||||
)
|
)
|
||||||
|
|
||||||
var opReg = regexp.MustCompile(`[!]?=`)
|
var opReg = regexp.MustCompile(`[!]?=`)
|
||||||
@ -42,6 +47,7 @@ type ListOptions struct {
|
|||||||
Sort Sort
|
Sort Sort
|
||||||
Pagination Pagination
|
Pagination Pagination
|
||||||
Revision string
|
Revision string
|
||||||
|
ProjectsOrNamespaces ProjectsOrNamespacesFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter represents a field to filter by.
|
// Filter represents a field to filter by.
|
||||||
@ -127,11 +133,21 @@ func (p Pagination) PageSize() int {
|
|||||||
return p.pageSize
|
return p.pageSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ProjectsOrNamespacesFilter struct {
|
||||||
|
filter map[string]struct{}
|
||||||
|
op op
|
||||||
|
}
|
||||||
|
|
||||||
// ParseQuery parses the query params of a request and returns a ListOptions.
|
// ParseQuery parses the query params of a request and returns a ListOptions.
|
||||||
func ParseQuery(apiOp *types.APIRequest) *ListOptions {
|
func ParseQuery(apiOp *types.APIRequest) *ListOptions {
|
||||||
chunkSize := getLimit(apiOp)
|
opts := ListOptions{}
|
||||||
|
|
||||||
|
opts.ChunkSize = getLimit(apiOp)
|
||||||
|
|
||||||
q := apiOp.Request.URL.Query()
|
q := apiOp.Request.URL.Query()
|
||||||
cont := q.Get(continueParam)
|
cont := q.Get(continueParam)
|
||||||
|
opts.Resume = cont
|
||||||
|
|
||||||
filterParams := q[filterParam]
|
filterParams := q[filterParam]
|
||||||
filterOpts := []OrFilter{}
|
filterOpts := []OrFilter{}
|
||||||
for _, filters := range filterParams {
|
for _, filters := range filterParams {
|
||||||
@ -168,6 +184,8 @@ func ParseQuery(apiOp *types.APIRequest) *ListOptions {
|
|||||||
}
|
}
|
||||||
return fieldI.String() < fieldJ.String()
|
return fieldI.String() < fieldJ.String()
|
||||||
})
|
})
|
||||||
|
opts.Filters = filterOpts
|
||||||
|
|
||||||
sortOpts := Sort{}
|
sortOpts := Sort{}
|
||||||
sortKeys := q.Get(sortParam)
|
sortKeys := q.Get(sortParam)
|
||||||
if sortKeys != "" {
|
if sortKeys != "" {
|
||||||
@ -191,6 +209,8 @@ func ParseQuery(apiOp *types.APIRequest) *ListOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
opts.Sort = sortOpts
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
pagination := Pagination{}
|
pagination := Pagination{}
|
||||||
pagination.pageSize, err = strconv.Atoi(q.Get(pageSizeParam))
|
pagination.pageSize, err = strconv.Atoi(q.Get(pageSizeParam))
|
||||||
@ -201,16 +221,30 @@ func ParseQuery(apiOp *types.APIRequest) *ListOptions {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
pagination.page = 1
|
pagination.page = 1
|
||||||
}
|
}
|
||||||
|
opts.Pagination = pagination
|
||||||
|
|
||||||
revision := q.Get(revisionParam)
|
revision := q.Get(revisionParam)
|
||||||
return &ListOptions{
|
opts.Revision = revision
|
||||||
ChunkSize: chunkSize,
|
|
||||||
Resume: cont,
|
projectsOptions := ProjectsOrNamespacesFilter{}
|
||||||
Filters: filterOpts,
|
var op op
|
||||||
Sort: sortOpts,
|
projectsOrNamespaces := q.Get(projectsOrNamespacesVar)
|
||||||
Pagination: pagination,
|
if projectsOrNamespaces == "" {
|
||||||
Revision: revision,
|
projectsOrNamespaces = q.Get(projectsOrNamespacesVar + notOp)
|
||||||
|
if projectsOrNamespaces != "" {
|
||||||
|
op = notEq
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if projectsOrNamespaces != "" {
|
||||||
|
projectsOptions.filter = make(map[string]struct{})
|
||||||
|
for _, pn := range strings.Split(projectsOrNamespaces, ",") {
|
||||||
|
projectsOptions.filter[pn] = struct{}{}
|
||||||
|
}
|
||||||
|
projectsOptions.op = op
|
||||||
|
opts.ProjectsOrNamespaces = projectsOptions
|
||||||
|
}
|
||||||
|
return &opts
|
||||||
|
}
|
||||||
|
|
||||||
// getLimit extracts the limit parameter from the request or sets a default of 100000.
|
// getLimit extracts the limit parameter from the request or sets a default of 100000.
|
||||||
// The default limit can be explicitly disabled by setting it to zero or negative.
|
// The default limit can be explicitly disabled by setting it to zero or negative.
|
||||||
@ -360,3 +394,31 @@ func PaginateList(list []unstructured.Unstructured, p Pagination) ([]unstructure
|
|||||||
}
|
}
|
||||||
return list[offset : offset+p.pageSize], pages
|
return list[offset : offset+p.pageSize], pages
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FilterByProjectsAndNamespaces(list []unstructured.Unstructured, projectsOrNamespaces ProjectsOrNamespacesFilter, namespaceCache corecontrollers.NamespaceCache) []unstructured.Unstructured {
|
||||||
|
if len(projectsOrNamespaces.filter) == 0 {
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
result := []unstructured.Unstructured{}
|
||||||
|
for _, obj := range list {
|
||||||
|
namespaceName := obj.GetNamespace()
|
||||||
|
if namespaceName == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
namespace, err := namespaceCache.Get(namespaceName)
|
||||||
|
if namespace == nil || err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
projectLabel, _ := namespace.GetLabels()[projectIDFieldLabel]
|
||||||
|
_, matchesProject := projectsOrNamespaces.filter[projectLabel]
|
||||||
|
_, matchesNamespace := projectsOrNamespaces.filter[namespaceName]
|
||||||
|
matches := matchesProject || matchesNamespace
|
||||||
|
if projectsOrNamespaces.op == eq && matches {
|
||||||
|
result = append(result, obj)
|
||||||
|
}
|
||||||
|
if projectsOrNamespaces.op == notEq && !matches {
|
||||||
|
result = append(result, obj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
@ -3,8 +3,12 @@ package listprocessor
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
corecontrollers "github.com/rancher/wrangler/pkg/generated/controllers/core/v1"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFilterList(t *testing.T) {
|
func TestFilterList(t *testing.T) {
|
||||||
@ -2567,3 +2571,669 @@ func TestPaginateList(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFilterByProjectsAndNamespaces(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
objects []unstructured.Unstructured
|
||||||
|
filter ProjectsOrNamespacesFilter
|
||||||
|
want []unstructured.Unstructured
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "filter by one project",
|
||||||
|
objects: []unstructured.Unstructured{
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "fuji",
|
||||||
|
"namespace": "n1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "granny-smith",
|
||||||
|
"namespace": "n2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
filter: ProjectsOrNamespacesFilter{
|
||||||
|
filter: map[string]struct{}{
|
||||||
|
"p-abcde": struct{}{},
|
||||||
|
},
|
||||||
|
op: eq,
|
||||||
|
},
|
||||||
|
want: []unstructured.Unstructured{
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "fuji",
|
||||||
|
"namespace": "n1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "filter by multiple projects",
|
||||||
|
objects: []unstructured.Unstructured{
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "fuji",
|
||||||
|
"namespace": "n1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "honeycrisp",
|
||||||
|
"namespace": "n2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "granny-smith",
|
||||||
|
"namespace": "n3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
filter: ProjectsOrNamespacesFilter{
|
||||||
|
filter: map[string]struct{}{
|
||||||
|
"p-abcde": struct{}{},
|
||||||
|
"p-fghij": struct{}{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: []unstructured.Unstructured{
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "fuji",
|
||||||
|
"namespace": "n1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "honeycrisp",
|
||||||
|
"namespace": "n2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "filter by one namespace",
|
||||||
|
objects: []unstructured.Unstructured{
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "fuji",
|
||||||
|
"namespace": "n1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "granny-smith",
|
||||||
|
"namespace": "n2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
filter: ProjectsOrNamespacesFilter{
|
||||||
|
filter: map[string]struct{}{
|
||||||
|
"n1": struct{}{},
|
||||||
|
},
|
||||||
|
op: eq,
|
||||||
|
},
|
||||||
|
want: []unstructured.Unstructured{
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "fuji",
|
||||||
|
"namespace": "n1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "filter by multiple namespaces",
|
||||||
|
objects: []unstructured.Unstructured{
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "fuji",
|
||||||
|
"namespace": "n1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "honeycrisp",
|
||||||
|
"namespace": "n2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "granny-smith",
|
||||||
|
"namespace": "n3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
filter: ProjectsOrNamespacesFilter{
|
||||||
|
filter: map[string]struct{}{
|
||||||
|
"n1": struct{}{},
|
||||||
|
"n2": struct{}{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: []unstructured.Unstructured{
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "fuji",
|
||||||
|
"namespace": "n1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "honeycrisp",
|
||||||
|
"namespace": "n2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "filter by namespaces and projects",
|
||||||
|
objects: []unstructured.Unstructured{
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "fuji",
|
||||||
|
"namespace": "n1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "honeycrisp",
|
||||||
|
"namespace": "n2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "granny-smith",
|
||||||
|
"namespace": "n3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
filter: ProjectsOrNamespacesFilter{
|
||||||
|
filter: map[string]struct{}{
|
||||||
|
"n1": struct{}{},
|
||||||
|
"p-fghij": struct{}{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: []unstructured.Unstructured{
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "fuji",
|
||||||
|
"namespace": "n1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "honeycrisp",
|
||||||
|
"namespace": "n2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no matches",
|
||||||
|
objects: []unstructured.Unstructured{
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "fuji",
|
||||||
|
"namespace": "n1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "honeycrisp",
|
||||||
|
"namespace": "n2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "granny-smith",
|
||||||
|
"namespace": "n3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
filter: ProjectsOrNamespacesFilter{
|
||||||
|
filter: map[string]struct{}{
|
||||||
|
"foobar": struct{}{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: []unstructured.Unstructured{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no filters",
|
||||||
|
objects: []unstructured.Unstructured{
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "fuji",
|
||||||
|
"namespace": "n1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "honeycrisp",
|
||||||
|
"namespace": "n2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "granny-smith",
|
||||||
|
"namespace": "n3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
filter: ProjectsOrNamespacesFilter{},
|
||||||
|
want: []unstructured.Unstructured{
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "fuji",
|
||||||
|
"namespace": "n1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "honeycrisp",
|
||||||
|
"namespace": "n2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "granny-smith",
|
||||||
|
"namespace": "n3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "filter by one project negated",
|
||||||
|
objects: []unstructured.Unstructured{
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "fuji",
|
||||||
|
"namespace": "n1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "honeycrisp",
|
||||||
|
"namespace": "n2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "granny-smith",
|
||||||
|
"namespace": "n3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
filter: ProjectsOrNamespacesFilter{
|
||||||
|
filter: map[string]struct{}{
|
||||||
|
"p-abcde": struct{}{},
|
||||||
|
},
|
||||||
|
op: notEq,
|
||||||
|
},
|
||||||
|
want: []unstructured.Unstructured{
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "honeycrisp",
|
||||||
|
"namespace": "n2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "granny-smith",
|
||||||
|
"namespace": "n3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "filter by multiple projects negated",
|
||||||
|
objects: []unstructured.Unstructured{
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "fuji",
|
||||||
|
"namespace": "n1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "honeycrisp",
|
||||||
|
"namespace": "n2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "granny-smith",
|
||||||
|
"namespace": "n3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
filter: ProjectsOrNamespacesFilter{
|
||||||
|
filter: map[string]struct{}{
|
||||||
|
"p-abcde": struct{}{},
|
||||||
|
"p-fghij": struct{}{},
|
||||||
|
},
|
||||||
|
op: notEq,
|
||||||
|
},
|
||||||
|
want: []unstructured.Unstructured{
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "granny-smith",
|
||||||
|
"namespace": "n3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "filter by one namespace negated",
|
||||||
|
objects: []unstructured.Unstructured{
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "fuji",
|
||||||
|
"namespace": "n1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "granny-smith",
|
||||||
|
"namespace": "n2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
filter: ProjectsOrNamespacesFilter{
|
||||||
|
filter: map[string]struct{}{
|
||||||
|
"n1": struct{}{},
|
||||||
|
},
|
||||||
|
op: notEq,
|
||||||
|
},
|
||||||
|
want: []unstructured.Unstructured{
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "granny-smith",
|
||||||
|
"namespace": "n2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "filter by multiple namespaces negated",
|
||||||
|
objects: []unstructured.Unstructured{
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "fuji",
|
||||||
|
"namespace": "n1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "honeycrisp",
|
||||||
|
"namespace": "n2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "granny-smith",
|
||||||
|
"namespace": "n3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
filter: ProjectsOrNamespacesFilter{
|
||||||
|
filter: map[string]struct{}{
|
||||||
|
"n1": struct{}{},
|
||||||
|
"n2": struct{}{},
|
||||||
|
},
|
||||||
|
op: notEq,
|
||||||
|
},
|
||||||
|
want: []unstructured.Unstructured{
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "granny-smith",
|
||||||
|
"namespace": "n3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "filter by namespaces and projects negated",
|
||||||
|
objects: []unstructured.Unstructured{
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "fuji",
|
||||||
|
"namespace": "n1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "honeycrisp",
|
||||||
|
"namespace": "n2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "granny-smith",
|
||||||
|
"namespace": "n3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
filter: ProjectsOrNamespacesFilter{
|
||||||
|
filter: map[string]struct{}{
|
||||||
|
"n1": struct{}{},
|
||||||
|
"p-fghij": struct{}{},
|
||||||
|
},
|
||||||
|
op: notEq,
|
||||||
|
},
|
||||||
|
want: []unstructured.Unstructured{
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "apple",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "granny-smith",
|
||||||
|
"namespace": "n3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
got := FilterByProjectsAndNamespaces(test.objects, test.filter, mockNamespaceCache{})
|
||||||
|
assert.Equal(t, test.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var namespaces = map[string]*corev1.Namespace{
|
||||||
|
"n1": &corev1.Namespace{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "n1",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"field.cattle.io/projectId": "p-abcde",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"n2": &corev1.Namespace{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "n2",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"field.cattle.io/projectId": "p-fghij",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"n3": &corev1.Namespace{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "n3",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"field.cattle.io/projectId": "p-klmno",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"n4": &corev1.Namespace{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "n4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockNamespaceCache struct{}
|
||||||
|
|
||||||
|
func (m mockNamespaceCache) Get(name string) (*corev1.Namespace, error) {
|
||||||
|
return namespaces[name], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockNamespaceCache) List(selector labels.Selector) ([]*corev1.Namespace, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
func (m mockNamespaceCache) AddIndexer(indexName string, indexer corecontrollers.NamespaceIndexer) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
func (m mockNamespaceCache) GetByIndex(indexName, key string) ([]*corev1.Namespace, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/rancher/apiserver/pkg/types"
|
"github.com/rancher/apiserver/pkg/types"
|
||||||
"github.com/rancher/steve/pkg/accesscontrol"
|
"github.com/rancher/steve/pkg/accesscontrol"
|
||||||
"github.com/rancher/steve/pkg/stores/partition/listprocessor"
|
"github.com/rancher/steve/pkg/stores/partition/listprocessor"
|
||||||
|
corecontrollers "github.com/rancher/wrangler/pkg/generated/controllers/core/v1"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
@ -45,10 +46,11 @@ type Store struct {
|
|||||||
Partitioner Partitioner
|
Partitioner Partitioner
|
||||||
listCache *cache.LRUExpireCache
|
listCache *cache.LRUExpireCache
|
||||||
asl accesscontrol.AccessSetLookup
|
asl accesscontrol.AccessSetLookup
|
||||||
|
namespaceCache corecontrollers.NamespaceCache
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStore creates a types.Store implementation with a partitioner and an LRU expiring cache for list responses.
|
// NewStore creates a types.Store implementation with a partitioner and an LRU expiring cache for list responses.
|
||||||
func NewStore(partitioner Partitioner, asl accesscontrol.AccessSetLookup) *Store {
|
func NewStore(partitioner Partitioner, asl accesscontrol.AccessSetLookup, namespaceCache corecontrollers.NamespaceCache) *Store {
|
||||||
cacheSize := defaultCacheSize
|
cacheSize := defaultCacheSize
|
||||||
if v := os.Getenv(cacheSizeEnv); v != "" {
|
if v := os.Getenv(cacheSizeEnv); v != "" {
|
||||||
sizeInt, err := strconv.Atoi(v)
|
sizeInt, err := strconv.Atoi(v)
|
||||||
@ -59,6 +61,7 @@ func NewStore(partitioner Partitioner, asl accesscontrol.AccessSetLookup) *Store
|
|||||||
s := &Store{
|
s := &Store{
|
||||||
Partitioner: partitioner,
|
Partitioner: partitioner,
|
||||||
asl: asl,
|
asl: asl,
|
||||||
|
namespaceCache: namespaceCache,
|
||||||
}
|
}
|
||||||
if v := os.Getenv(cacheDisableEnv); v == "false" {
|
if v := os.Getenv(cacheDisableEnv); v == "false" {
|
||||||
s.listCache = cache.NewLRUExpireCache(cacheSize)
|
s.listCache = cache.NewLRUExpireCache(cacheSize)
|
||||||
@ -203,6 +206,7 @@ func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.AP
|
|||||||
listToCache := &unstructured.UnstructuredList{
|
listToCache := &unstructured.UnstructuredList{
|
||||||
Items: list,
|
Items: list,
|
||||||
}
|
}
|
||||||
|
list = listprocessor.FilterByProjectsAndNamespaces(list, opts.ProjectsOrNamespaces, s.namespaceCache)
|
||||||
c := lister.Continue()
|
c := lister.Continue()
|
||||||
if c != "" {
|
if c != "" {
|
||||||
listToCache.SetContinue(c)
|
listToCache.SetContinue(c)
|
||||||
|
@ -12,9 +12,13 @@ import (
|
|||||||
|
|
||||||
"github.com/rancher/apiserver/pkg/types"
|
"github.com/rancher/apiserver/pkg/types"
|
||||||
"github.com/rancher/steve/pkg/accesscontrol"
|
"github.com/rancher/steve/pkg/accesscontrol"
|
||||||
|
corecontrollers "github.com/rancher/wrangler/pkg/generated/controllers/core/v1"
|
||||||
"github.com/rancher/wrangler/pkg/schemas"
|
"github.com/rancher/wrangler/pkg/schemas"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apimachinery/pkg/watch"
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
"k8s.io/apiserver/pkg/authentication/user"
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
"k8s.io/apiserver/pkg/endpoints/request"
|
"k8s.io/apiserver/pkg/endpoints/request"
|
||||||
@ -1928,6 +1932,118 @@ func TestList(t *testing.T) {
|
|||||||
{"all": 1},
|
{"all": 1},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "with project filters",
|
||||||
|
apiOps: []*types.APIRequest{
|
||||||
|
newRequest("projectsornamespaces=p-abcde", "user1"),
|
||||||
|
newRequest("projectsornamespaces=p-abcde,p-fghij", "user1"),
|
||||||
|
newRequest("projectsornamespaces=p-abcde,n2", "user1"),
|
||||||
|
newRequest("projectsornamespaces!=p-abcde", "user1"),
|
||||||
|
newRequest("projectsornamespaces!=p-abcde,p-fghij", "user1"),
|
||||||
|
newRequest("projectsornamespaces!=p-abcde,n2", "user1"),
|
||||||
|
newRequest("projectsornamespaces=foobar", "user1"),
|
||||||
|
newRequest("projectsornamespaces!=foobar", "user1"),
|
||||||
|
},
|
||||||
|
access: []map[string]string{
|
||||||
|
{
|
||||||
|
"user1": "roleA",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user1": "roleA",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user1": "roleA",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user1": "roleA",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user1": "roleA",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user1": "roleA",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user1": "roleA",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user1": "roleA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
partitions: map[string][]Partition{
|
||||||
|
"user1": {
|
||||||
|
mockPartition{
|
||||||
|
name: "all",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
objects: map[string]*unstructured.UnstructuredList{
|
||||||
|
"all": {
|
||||||
|
Items: []unstructured.Unstructured{
|
||||||
|
newApple("fuji").withNamespace("n1").Unstructured,
|
||||||
|
newApple("granny-smith").withNamespace("n1").Unstructured,
|
||||||
|
newApple("bramley").withNamespace("n2").Unstructured,
|
||||||
|
newApple("crispin").withNamespace("n3").Unstructured,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: []types.APIObjectList{
|
||||||
|
{
|
||||||
|
Count: 2,
|
||||||
|
Objects: []types.APIObject{
|
||||||
|
newApple("fuji").withNamespace("n1").toObj(),
|
||||||
|
newApple("granny-smith").withNamespace("n1").toObj(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Count: 3,
|
||||||
|
Objects: []types.APIObject{
|
||||||
|
newApple("fuji").withNamespace("n1").toObj(),
|
||||||
|
newApple("granny-smith").withNamespace("n1").toObj(),
|
||||||
|
newApple("bramley").withNamespace("n2").toObj(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Count: 3,
|
||||||
|
Objects: []types.APIObject{
|
||||||
|
newApple("fuji").withNamespace("n1").toObj(),
|
||||||
|
newApple("granny-smith").withNamespace("n1").toObj(),
|
||||||
|
newApple("bramley").withNamespace("n2").toObj(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Count: 2,
|
||||||
|
Objects: []types.APIObject{
|
||||||
|
newApple("bramley").withNamespace("n2").toObj(),
|
||||||
|
newApple("crispin").withNamespace("n3").toObj(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Count: 1,
|
||||||
|
Objects: []types.APIObject{
|
||||||
|
newApple("crispin").withNamespace("n3").toObj(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Count: 1,
|
||||||
|
Objects: []types.APIObject{
|
||||||
|
newApple("crispin").withNamespace("n3").toObj(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Count: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Count: 4,
|
||||||
|
Objects: []types.APIObject{
|
||||||
|
newApple("fuji").withNamespace("n1").toObj(),
|
||||||
|
newApple("granny-smith").withNamespace("n1").toObj(),
|
||||||
|
newApple("bramley").withNamespace("n2").toObj(),
|
||||||
|
newApple("crispin").withNamespace("n3").toObj(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
@ -1947,7 +2063,7 @@ func TestList(t *testing.T) {
|
|||||||
store := NewStore(mockPartitioner{
|
store := NewStore(mockPartitioner{
|
||||||
stores: stores,
|
stores: stores,
|
||||||
partitions: test.partitions,
|
partitions: test.partitions,
|
||||||
}, asl)
|
}, asl, mockNamespaceCache{})
|
||||||
for i, req := range test.apiOps {
|
for i, req := range test.apiOps {
|
||||||
got, gotErr := store.List(req, schema)
|
got, gotErr := store.List(req, schema)
|
||||||
assert.Nil(t, gotErr)
|
assert.Nil(t, gotErr)
|
||||||
@ -2022,7 +2138,7 @@ func TestListByRevision(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, asl)
|
}, asl, mockNamespaceCache{})
|
||||||
req := newRequest("", "user1")
|
req := newRequest("", "user1")
|
||||||
|
|
||||||
got, gotErr := store.List(req, schema)
|
got, gotErr := store.List(req, schema)
|
||||||
@ -2214,9 +2330,15 @@ func newApple(name string) apple {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a apple) toObj() types.APIObject {
|
func (a apple) toObj() types.APIObject {
|
||||||
|
meta := a.Object["metadata"].(map[string]interface{})
|
||||||
|
id := meta["name"].(string)
|
||||||
|
ns, ok := meta["namespace"]
|
||||||
|
if ok {
|
||||||
|
id = ns.(string) + "/" + id
|
||||||
|
}
|
||||||
return types.APIObject{
|
return types.APIObject{
|
||||||
Type: "apple",
|
Type: "apple",
|
||||||
ID: a.Object["metadata"].(map[string]interface{})["name"].(string),
|
ID: id,
|
||||||
Object: &a.Unstructured,
|
Object: &a.Unstructured,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2228,6 +2350,11 @@ func (a apple) with(data map[string]string) apple {
|
|||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a apple) withNamespace(namespace string) apple {
|
||||||
|
a.Object["metadata"].(map[string]interface{})["namespace"] = namespace
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
type mockAccessSetLookup struct {
|
type mockAccessSetLookup struct {
|
||||||
accessID string
|
accessID string
|
||||||
userRoles []map[string]string
|
userRoles []map[string]string
|
||||||
@ -2250,3 +2377,51 @@ func getAccessID(user, role string) string {
|
|||||||
h := sha256.Sum256([]byte(user + role))
|
h := sha256.Sum256([]byte(user + role))
|
||||||
return string(h[:])
|
return string(h[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var namespaces = map[string]*corev1.Namespace{
|
||||||
|
"n1": &corev1.Namespace{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "n1",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"field.cattle.io/projectId": "p-abcde",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"n2": &corev1.Namespace{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "n2",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"field.cattle.io/projectId": "p-fghij",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"n3": &corev1.Namespace{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "n3",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"field.cattle.io/projectId": "p-klmno",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"n4": &corev1.Namespace{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "n4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockNamespaceCache struct{}
|
||||||
|
|
||||||
|
func (m mockNamespaceCache) Get(name string) (*corev1.Namespace, error) {
|
||||||
|
return namespaces[name], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockNamespaceCache) List(selector labels.Selector) ([]*corev1.Namespace, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
func (m mockNamespaceCache) AddIndexer(indexName string, indexer corecontrollers.NamespaceIndexer) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
func (m mockNamespaceCache) GetByIndex(indexName, key string) ([]*corev1.Namespace, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
metricsStore "github.com/rancher/steve/pkg/stores/metrics"
|
metricsStore "github.com/rancher/steve/pkg/stores/metrics"
|
||||||
"github.com/rancher/steve/pkg/stores/partition"
|
"github.com/rancher/steve/pkg/stores/partition"
|
||||||
"github.com/rancher/wrangler/pkg/data"
|
"github.com/rancher/wrangler/pkg/data"
|
||||||
|
corecontrollers "github.com/rancher/wrangler/pkg/generated/controllers/core/v1"
|
||||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||||
"github.com/rancher/wrangler/pkg/summary"
|
"github.com/rancher/wrangler/pkg/summary"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
@ -85,7 +86,7 @@ type Store struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewProxyStore returns a wrapped types.Store.
|
// NewProxyStore returns a wrapped types.Store.
|
||||||
func NewProxyStore(clientGetter ClientGetter, notifier RelationshipNotifier, lookup accesscontrol.AccessSetLookup) types.Store {
|
func NewProxyStore(clientGetter ClientGetter, notifier RelationshipNotifier, lookup accesscontrol.AccessSetLookup, namespaceCache corecontrollers.NamespaceCache) types.Store {
|
||||||
return &errorStore{
|
return &errorStore{
|
||||||
Store: &WatchRefresh{
|
Store: &WatchRefresh{
|
||||||
Store: partition.NewStore(
|
Store: partition.NewStore(
|
||||||
@ -96,6 +97,7 @@ func NewProxyStore(clientGetter ClientGetter, notifier RelationshipNotifier, loo
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
lookup,
|
lookup,
|
||||||
|
namespaceCache,
|
||||||
),
|
),
|
||||||
asl: lookup,
|
asl: lookup,
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user