Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add projectsornamespaces query parameter #105

Merged
merged 1 commit into from
May 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,36 @@ item is included in the list.
/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`

Only applicable to list requests (`/v1/{type}` and `/v1/{type}/{namespace}`).
Expand Down
6 changes: 4 additions & 2 deletions pkg/resources/common/formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/rancher/steve/pkg/stores/proxy"
"github.com/rancher/steve/pkg/summarycache"
"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/summary"
"k8s.io/apimachinery/pkg/api/meta"
Expand All @@ -21,9 +22,10 @@ import (

func DefaultTemplate(clientGetter proxy.ClientGetter,
summaryCache *summarycache.SummaryCache,
asl accesscontrol.AccessSetLookup) schema.Template {
asl accesscontrol.AccessSetLookup,
namespaceCache corecontrollers.NamespaceCache) 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),
}
}
Expand Down
6 changes: 4 additions & 2 deletions pkg/resources/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
steveschema "github.com/rancher/steve/pkg/schema"
"github.com/rancher/steve/pkg/stores/proxy"
"github.com/rancher/steve/pkg/summarycache"
corecontrollers "github.com/rancher/wrangler/pkg/generated/controllers/core/v1"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/client-go/discovery"
)
Expand Down Expand Up @@ -46,9 +47,10 @@ func DefaultSchemaTemplates(cf *client.Factory,
baseSchemas *types.APISchemas,
summaryCache *summarycache.SummaryCache,
lookup accesscontrol.AccessSetLookup,
discovery discovery.DiscoveryInterface) []schema.Template {
discovery discovery.DiscoveryInterface,
namespaceCache corecontrollers.NamespaceCache) []schema.Template {
return []schema.Template{
common.DefaultTemplate(cf, summaryCache, lookup),
common.DefaultTemplate(cf, summaryCache, lookup, namespaceCache),
apigroups.Template(discovery),
{
ID: "configmap",
Expand Down
2 changes: 1 addition & 1 deletion pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ func setup(ctx context.Context, server *Server) error {
summaryCache := summarycache.New(sf, ccache)
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)
}

Expand Down
108 changes: 85 additions & 23 deletions pkg/stores/partition/listprocessor/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,24 @@ import (
"github.com/rancher/apiserver/pkg/types"
"github.com/rancher/wrangler/pkg/data"
"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"
)

const (
defaultLimit = 100000
continueParam = "continue"
limitParam = "limit"
filterParam = "filter"
sortParam = "sort"
pageSizeParam = "pagesize"
pageParam = "page"
revisionParam = "revision"
orOp = ","
defaultLimit = 100000
continueParam = "continue"
limitParam = "limit"
filterParam = "filter"
sortParam = "sort"
pageSizeParam = "pagesize"
pageParam = "page"
revisionParam = "revision"
projectsOrNamespacesVar = "projectsornamespaces"
projectIDFieldLabel = "field.cattle.io/projectId"

orOp = ","
notOp = "!"
)

var opReg = regexp.MustCompile(`[!]?=`)
Expand All @@ -36,12 +41,13 @@ const (

// ListOptions represents the query parameters that may be included in a list request.
type ListOptions struct {
ChunkSize int
Resume string
Filters []OrFilter
Sort Sort
Pagination Pagination
Revision string
ChunkSize int
Resume string
Filters []OrFilter
Sort Sort
Pagination Pagination
Revision string
ProjectsOrNamespaces ProjectsOrNamespacesFilter
}

// Filter represents a field to filter by.
Expand Down Expand Up @@ -127,11 +133,21 @@ func (p Pagination) PageSize() int {
return p.pageSize
}

type ProjectsOrNamespacesFilter struct {
filter map[string]struct{}
op op
}

// ParseQuery parses the query params of a request and returns a ListOptions.
func ParseQuery(apiOp *types.APIRequest) *ListOptions {
chunkSize := getLimit(apiOp)
opts := ListOptions{}

opts.ChunkSize = getLimit(apiOp)

q := apiOp.Request.URL.Query()
cont := q.Get(continueParam)
opts.Resume = cont

filterParams := q[filterParam]
filterOpts := []OrFilter{}
for _, filters := range filterParams {
Expand Down Expand Up @@ -168,6 +184,8 @@ func ParseQuery(apiOp *types.APIRequest) *ListOptions {
}
return fieldI.String() < fieldJ.String()
})
opts.Filters = filterOpts

sortOpts := Sort{}
sortKeys := q.Get(sortParam)
if sortKeys != "" {
Expand All @@ -191,6 +209,8 @@ func ParseQuery(apiOp *types.APIRequest) *ListOptions {
}
}
}
opts.Sort = sortOpts

var err error
pagination := Pagination{}
pagination.pageSize, err = strconv.Atoi(q.Get(pageSizeParam))
Expand All @@ -201,15 +221,29 @@ func ParseQuery(apiOp *types.APIRequest) *ListOptions {
if err != nil {
pagination.page = 1
}
opts.Pagination = pagination

revision := q.Get(revisionParam)
return &ListOptions{
ChunkSize: chunkSize,
Resume: cont,
Filters: filterOpts,
Sort: sortOpts,
Pagination: pagination,
Revision: revision,
opts.Revision = revision

projectsOptions := ProjectsOrNamespacesFilter{}
var op op
projectsOrNamespaces := q.Get(projectsOrNamespacesVar)
if projectsOrNamespaces == "" {
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.
Expand Down Expand Up @@ -360,3 +394,31 @@ func PaginateList(list []unstructured.Unstructured, p Pagination) ([]unstructure
}
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()
cmurphy marked this conversation as resolved.
Show resolved Hide resolved
if namespaceName == "" {
continue
}
namespace, err := namespaceCache.Get(namespaceName)
if namespace == nil || err != nil {
continue
}
projectLabel, _ := namespace.GetLabels()[projectIDFieldLabel]
MbolotSuse marked this conversation as resolved.
Show resolved Hide resolved
_, 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
}
Loading