Skip to content

Commit

Permalink
Merge pull request #105 from cmurphy/projects-filtering
Browse files Browse the repository at this point in the history
Add projectsornamespaces query parameter
  • Loading branch information
cmurphy authored May 12, 2023
2 parents 3279919 + 84dedac commit 1dfd3c7
Show file tree
Hide file tree
Showing 9 changed files with 985 additions and 38 deletions.
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()
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
}
Loading

0 comments on commit 1dfd3c7

Please sign in to comment.