Merge pull request #1989 from wansir/dev

add iam crd
This commit is contained in:
KubeSphere CI Bot
2020-04-12 21:21:21 +08:00
committed by GitHub
908 changed files with 8121 additions and 140563 deletions

View File

@@ -144,7 +144,10 @@ func (s *APIServer) installKubeSphereAPIs() {
urlruntime.Must(resourcesv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.InformerFactory))
//urlruntime.Must(tenantv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.InformerFactory, s.DBClient.Database()))
urlruntime.Must(terminalv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.KubernetesClient.Config()))
urlruntime.Must(iamv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.InformerFactory, s.LdapClient, s.CacheClient, s.Config.AuthenticationOptions))
urlruntime.Must(iamv1alpha2.AddToContainer(s.container, im.NewOperator(s.KubernetesClient.KubeSphere(),
s.InformerFactory.KubeSphereSharedInformerFactory()),
am.NewAMOperator(s.KubernetesClient.KubeSphere(), s.InformerFactory.KubeSphereSharedInformerFactory()),
s.Config.AuthenticationOptions))
urlruntime.Must(oauth.AddToContainer(s.container, token.NewJwtTokenIssuer(token.DefaultIssuerName, s.Config.AuthenticationOptions, s.CacheClient), s.Config.AuthenticationOptions))
urlruntime.Must(servicemeshv1alpha2.AddToContainer(s.container))
}
@@ -190,12 +193,12 @@ func (s *APIServer) buildHandlerChain() {
pathAuthorizer, _ := path.NewAuthorizer(excludedPaths)
// union authorizers are ordered, don't change the order here
authorizers := unionauthorizer.New(pathAuthorizer, authorizerfactory.NewOPAAuthorizer(am.NewFakeAMOperator()))
authorizers := unionauthorizer.New(pathAuthorizer, authorizerfactory.NewOPAAuthorizer(am.NewAMOperator(s.KubernetesClient.KubeSphere(), s.InformerFactory.KubeSphereSharedInformerFactory())))
handler = filters.WithAuthorization(handler, authorizers)
// authenticators are unordered
authn := unionauth.New(anonymous.NewAuthenticator(),
basictoken.New(basic.NewBasicAuthenticator(im.NewFakeOperator())),
basictoken.New(basic.NewBasicAuthenticator(im.NewOperator(s.KubernetesClient.KubeSphere(), s.InformerFactory.KubeSphereSharedInformerFactory()))),
bearertoken.New(jwttoken.NewTokenAuthenticator(token.NewJwtTokenIssuer(token.DefaultIssuerName, s.Config.AuthenticationOptions, s.CacheClient))))
handler = filters.WithAuthentication(handler, authn)
handler = filters.WithRequestInfo(handler, requestInfoResolver)
@@ -275,6 +278,10 @@ func (s *APIServer) waitForResourceSync(stopCh <-chan struct{}) error {
ksGVRs := []schema.GroupVersionResource{
{Group: "tenant.kubesphere.io", Version: "v1alpha1", Resource: "workspaces"},
{Group: "iam.kubesphere.io", Version: "v1alpha2", Resource: "users"},
{Group: "iam.kubesphere.io", Version: "v1alpha2", Resource: "roles"},
{Group: "iam.kubesphere.io", Version: "v1alpha2", Resource: "rolebindings"},
{Group: "iam.kubesphere.io", Version: "v1alpha2", Resource: "policyrules"},
{Group: "tower.kubesphere.io", Version: "v1alpha1", Resource: "agents"},
}

View File

@@ -51,7 +51,7 @@ func (t *basicAuthenticator) AuthenticatePassword(ctx context.Context, username,
return &authenticator.Response{
User: &user.DefaultInfo{
Name: providedUser.GetName(),
UID: providedUser.GetUID(),
UID: string(providedUser.GetUID()),
Groups: []string{user.AllAuthenticated},
},
}, true, nil

View File

@@ -21,7 +21,7 @@ package token
import (
"fmt"
"github.com/dgrijalva/jwt-go"
"kubesphere.io/kubesphere/pkg/api/iam"
"k8s.io/apiserver/pkg/authentication/user"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/server/errors"
"kubesphere.io/kubesphere/pkg/simple/client/cache"
@@ -69,7 +69,7 @@ func (s *jwtTokenIssuer) Verify(tokenString string) (User, error) {
return nil, err
}
return &iam.User{Name: clm.Username, UID: clm.UID}, nil
return &user.DefaultInfo{Name: clm.Username, UID: clm.UID}, nil
}
func (s *jwtTokenIssuer) IssueTo(user User, expiresIn time.Duration) (string, error) {

View File

@@ -20,7 +20,7 @@ package token
import (
"github.com/google/go-cmp/cmp"
"kubesphere.io/kubesphere/pkg/api/iam"
"k8s.io/apiserver/pkg/authentication/user"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/simple/client/cache"
"testing"
@@ -48,7 +48,7 @@ func TestJwtTokenIssuer(t *testing.T) {
}
for _, testCase := range testCases {
user := &iam.User{
user := &user.DefaultInfo{
Name: testCase.name,
UID: testCase.uid,
}

View File

@@ -21,6 +21,9 @@ package authorizerfactory
import (
"context"
"github.com/open-policy-agent/opa/rego"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/klog"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
"kubesphere.io/kubesphere/pkg/models/iam/am"
)
@@ -29,115 +32,126 @@ type opaAuthorizer struct {
am am.AccessManagementInterface
}
const (
permissionUndefined = "permission undefined"
defaultRegoQuery = "data.authz.allow"
)
// Make decision by request attributes
func (o *opaAuthorizer) Authorize(attr authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
// Make decisions based on the authorization policy of different levels of roles
platformRole, err := o.am.GetPlatformRole(attr.GetUser().GetName())
// Error returned when an internal error occurs
// Reason must be returned when access is denied
globalRole, err := o.am.GetRoleOfUserInTargetScope(iamv1alpha2.GlobalScope, "", attr.GetUser().GetName())
if err != nil {
if errors.IsNotFound(err) {
return authorizer.DecisionDeny, err.Error(), nil
}
return authorizer.DecisionDeny, "", err
}
// check platform role policy rules
if authorized, reason, err = makeDecision(platformRole, attr); authorized == authorizer.DecisionAllow {
return authorized, reason, err
if authorized, reason, err = o.makeDecision(globalRole, attr); authorized == authorizer.DecisionAllow {
return authorized, reason, nil
}
// it's not in cluster resource, permission denied
if attr.GetCluster() == "" {
return authorizer.DecisionDeny, "permission undefined", nil
return authorizer.DecisionDeny, permissionUndefined, nil
}
clusterRole, err := o.am.GetClusterRole(attr.GetCluster(), attr.GetUser().GetName())
clusterRole, err := o.am.GetRoleOfUserInTargetScope(iamv1alpha2.ClusterScope, attr.GetCluster(), attr.GetUser().GetName())
if err != nil {
if errors.IsNotFound(err) {
return authorizer.DecisionDeny, err.Error(), nil
}
return authorizer.DecisionDeny, "", err
}
// check cluster role policy rules
if a, r, e := makeDecision(clusterRole, attr); a == authorizer.DecisionAllow {
return a, r, e
if authorized, reason, err := o.makeDecision(clusterRole, attr); authorized == authorizer.DecisionAllow {
return authorized, reason, nil
} else if err != nil {
return authorizer.DecisionDeny, "", err
}
// it's not in cluster resource, permission denied
if attr.GetWorkspace() == "" && attr.GetNamespace() == "" {
return authorizer.DecisionDeny, "permission undefined", nil
return authorizer.DecisionDeny, permissionUndefined, nil
}
workspaceRole, err := o.am.GetWorkspaceRole(attr.GetWorkspace(), attr.GetUser().GetName())
workspaceRole, err := o.am.GetRoleOfUserInTargetScope(iamv1alpha2.WorkspaceScope, attr.GetWorkspace(), attr.GetUser().GetName())
if err != nil {
if errors.IsNotFound(err) {
return authorizer.DecisionDeny, err.Error(), nil
}
return authorizer.DecisionDeny, "", err
}
// check workspace role policy rules
if a, r, e := makeDecision(workspaceRole, attr); a == authorizer.DecisionAllow {
return a, r, e
if authorized, reason, err := o.makeDecision(workspaceRole, attr); authorized == authorizer.DecisionAllow {
return authorized, reason, err
} else if err != nil {
return authorizer.DecisionDeny, "", err
}
// it's not in workspace resource, permission denied
if attr.GetNamespace() == "" {
return authorizer.DecisionDeny, "permission undefined", nil
return authorizer.DecisionDeny, permissionUndefined, nil
}
if attr.GetNamespace() != "" {
namespaceRole, err := o.am.GetNamespaceRole(attr.GetCluster(), attr.GetNamespace(), attr.GetUser().GetName())
if err != nil {
return authorizer.DecisionDeny, "", err
}
// check namespace role policy rules
if a, r, e := makeDecision(namespaceRole, attr); a == authorizer.DecisionAllow {
return a, r, e
namespaceRole, err := o.am.GetRoleOfUserInTargetScope(iamv1alpha2.NamespaceScope, attr.GetNamespace(), attr.GetUser().GetName())
if err != nil {
if errors.IsNotFound(err) {
return authorizer.DecisionDeny, err.Error(), nil
}
return authorizer.DecisionDeny, "", err
}
// check namespace role policy rules
if authorized, reason, err := o.makeDecision(namespaceRole, attr); authorized == authorizer.DecisionAllow {
return authorized, reason, err
} else if err != nil {
return authorizer.DecisionDeny, "", err
}
return authorizer.DecisionDeny, "", nil
return authorizer.DecisionDeny, permissionUndefined, nil
}
// Make decision base on role
func makeDecision(role am.Role, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
func (o *opaAuthorizer) makeDecision(role *iamv1alpha2.Role, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
// Call the rego.New function to create an object that can be prepared or evaluated
// After constructing a new rego.Rego object you can call PrepareForEval() to obtain an executable query
query, err := rego.New(rego.Query("data.authz.allow"), rego.Module("authz.rego", role.GetRego())).PrepareForEval(context.Background())
for _, ruleRef := range role.Rules {
rule, err := o.am.GetPolicyRule(ruleRef.Name)
if err != nil {
if errors.IsNotFound(err) {
continue
}
return authorizer.DecisionDeny, "", err
}
// Call the rego.New function to create an object that can be prepared or evaluated
// After constructing a new rego.Rego object you can call PrepareForEval() to obtain an executable query
query, err := rego.New(rego.Query(defaultRegoQuery), rego.Module("authz.rego", rule.Rego)).PrepareForEval(context.Background())
if err != nil {
return authorizer.DecisionDeny, "", err
if err != nil {
klog.Errorf("rule syntax error:%s", err)
continue
}
// The policy decision is contained in the results returned by the Eval() call. You can inspect the decision and handle it accordingly.
results, err := query.Eval(context.Background(), rego.EvalInput(a))
if err != nil {
klog.Errorf("rule syntax error:%s", err)
continue
}
if len(results) > 0 && results[0].Expressions[0].Value == true {
return authorizer.DecisionAllow, "", nil
}
}
// data example
//{
// "User": {
// "Name": "admin",
// "UID": "0",
// "Groups": [
// "admin"
// ],
// "Extra": null
// },
// "Verb": "list",
// "Cluster": "cluster1",
// "Workspace": "",
// "Namespace": "",
// "APIGroup": "",
// "APIVersion": "v1",
// "Resource": "nodes",
// "Subresource": "",
// "Name": "",
// "KubernetesRequest": true,
// "ResourceRequest": true,
// "Path": "/api/v1/nodes"
//}
// The policy decision is contained in the results returned by the Eval() call. You can inspect the decision and handle it accordingly.
results, err := query.Eval(context.Background(), rego.EvalInput(a))
if err != nil {
return authorizer.DecisionDeny, "", err
}
if len(results) > 0 && results[0].Expressions[0].Value == true {
return authorizer.DecisionAllow, "", nil
}
return authorizer.DecisionDeny, "permission undefined", nil
return authorizer.DecisionDeny, permissionUndefined, nil
}
func NewOPAAuthorizer(am am.AccessManagementInterface) *opaAuthorizer {

View File

@@ -19,22 +19,44 @@
package authorizerfactory
import (
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/authentication/user"
iamvealpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
"kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake"
"kubesphere.io/kubesphere/pkg/client/informers/externalversions"
"kubesphere.io/kubesphere/pkg/models/iam/am"
"testing"
)
func TestPlatformRole(t *testing.T) {
platformRoles := map[string]am.FakeRole{"admin": {
Name: "admin",
Rego: "package authz\ndefault allow = true",
}, "anonymous": {
Name: "anonymous",
Rego: "package authz\ndefault allow = false",
}, "tom": {
Name: "tom",
Rego: `package authz
func prepare() (am.AccessManagementInterface, error) {
rules := []*iamvealpha2.PolicyRule{
{
TypeMeta: metav1.TypeMeta{
Kind: iamvealpha2.PolicyRuleKind,
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: "always-allow",
},
Rego: "package authz\ndefault allow = true",
}, {
TypeMeta: metav1.TypeMeta{
Kind: iamvealpha2.PolicyRuleKind,
APIVersion: iamvealpha2.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: "always-deny",
},
Rego: "package authz\ndefault allow = false",
}, {
TypeMeta: metav1.TypeMeta{
Kind: iamvealpha2.PolicyRuleKind,
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: "manage-cluster1-resources",
},
Rego: `package authz
default allow = false
allow {
resources_in_cluster1
@@ -42,11 +64,168 @@ allow {
resources_in_cluster1 {
input.Cluster == "cluster1"
}`,
},
},
}
operator := am.NewFakeAMOperator()
operator.Prepare(platformRoles, nil, nil, nil)
roles := []*iamvealpha2.Role{
{
TypeMeta: metav1.TypeMeta{
Kind: iamvealpha2.RoleKind,
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: "global-admin",
},
Target: iamvealpha2.Target{
Scope: iamvealpha2.GlobalScope,
Name: "",
},
Rules: []iamvealpha2.RuleRef{
{
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
Kind: iamvealpha2.PolicyRuleKind,
Name: "always-allow",
},
},
},
{
TypeMeta: metav1.TypeMeta{
Kind: iamvealpha2.RoleKind,
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: "anonymous",
},
Target: iamvealpha2.Target{
Scope: iamvealpha2.GlobalScope,
Name: "",
},
Rules: []iamvealpha2.RuleRef{
{
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
Kind: iamvealpha2.PolicyRuleKind,
Name: "always-deny",
},
},
},
{
TypeMeta: metav1.TypeMeta{
Kind: iamvealpha2.RoleKind,
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: "cluster1-admin",
},
Target: iamvealpha2.Target{
Scope: iamvealpha2.GlobalScope,
Name: "",
},
Rules: []iamvealpha2.RuleRef{
{
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
Kind: iamvealpha2.PolicyRuleKind,
Name: "manage-cluster1-resources",
},
},
},
}
roleBindings := []*iamvealpha2.RoleBinding{
{
TypeMeta: metav1.TypeMeta{
Kind: iamvealpha2.RoleBindingKind,
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: "global-admin",
},
Scope: iamvealpha2.GlobalScope,
RoleRef: iamvealpha2.RoleRef{
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
Kind: iamvealpha2.RoleKind,
Name: "global-admin",
},
Subjects: []iamvealpha2.Subject{
{
Kind: iamvealpha2.UserKind,
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
Name: "admin",
},
},
},
{
TypeMeta: metav1.TypeMeta{
Kind: iamvealpha2.RoleBindingKind,
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: "anonymous",
},
Scope: iamvealpha2.GlobalScope,
RoleRef: iamvealpha2.RoleRef{
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
Kind: iamvealpha2.RoleKind,
Name: "anonymous",
},
Subjects: []iamvealpha2.Subject{
{
Kind: iamvealpha2.UserKind,
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
Name: user.Anonymous,
},
},
},
{
TypeMeta: metav1.TypeMeta{
Kind: iamvealpha2.RoleBindingKind,
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: "cluster1-admin",
},
Scope: iamvealpha2.GlobalScope,
RoleRef: iamvealpha2.RoleRef{
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
Kind: iamvealpha2.RoleKind,
Name: "cluster1-admin",
},
Subjects: []iamvealpha2.Subject{
{
Kind: iamvealpha2.UserKind,
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
Name: "tom",
},
},
},
}
ksClient := fake.NewSimpleClientset()
informerFactory := externalversions.NewSharedInformerFactory(ksClient, 0)
for _, rule := range rules {
err := informerFactory.Iam().V1alpha2().PolicyRules().Informer().GetIndexer().Add(rule)
if err != nil {
return nil, fmt.Errorf("add rule:%s", err)
}
}
for _, role := range roles {
err := informerFactory.Iam().V1alpha2().Roles().Informer().GetIndexer().Add(role)
if err != nil {
return nil, fmt.Errorf("add role:%s", err)
}
}
for _, roleBinding := range roleBindings {
err := informerFactory.Iam().V1alpha2().RoleBindings().Informer().GetIndexer().Add(roleBinding)
if err != nil {
return nil, fmt.Errorf("add role binding:%s", err)
}
}
operator := am.NewAMOperator(ksClient, informerFactory)
return operator, nil
}
func TestGlobalRole(t *testing.T) {
operator, err := prepare()
if err != nil {
t.Fatal(err)
}
opa := NewOPAAuthorizer(operator)

View File

@@ -1,16 +1,18 @@
package query
type Field string
type Value string
const (
FieldName = "name"
FieldUID = "uid"
FieldCreationTimeStamp = "creationTimestamp"
FieldLastUpdateTimestamp = "lastUpdateTimestamp"
FieldLabel = "label"
FieldAnnotation = "annotation"
FieldNamespace = "namespace"
FieldStatus = "status"
FieldApplication = "application"
FieldOwner = "owner"
FieldOwnerReference = "ownerReference"
FieldOwnerKind = "ownerKind"
)
@@ -23,9 +25,11 @@ var SortableFields = []Field{
// Field contains all the query field that can be compared
var ComparableFields = []Field{
FieldName,
FieldUID,
FieldLabel,
FieldAnnotation,
FieldNamespace,
FieldStatus,
FieldApplication,
FieldOwner,
FieldOwnerReference,
FieldOwnerKind,
}

View File

@@ -3,7 +3,6 @@ package query
import (
"github.com/emicklei/go-restful"
"strconv"
"strings"
)
const (
@@ -16,24 +15,6 @@ const (
ParameterAscending = "ascending"
)
type Comparable interface {
Compare(Comparable) int
Contains(Comparable) bool
}
type ComparableString string
func (c ComparableString) Compare(comparable Comparable) int {
other := comparable.(ComparableString)
return strings.Compare(string(c), string(other))
}
func (c ComparableString) Contains(comparable Comparable) bool {
other := comparable.(ComparableString)
return strings.Contains(string(c), string(other))
}
// Query represents api search terms
type Query struct {
Pagination *Pagination
@@ -52,29 +33,30 @@ type Pagination struct {
// items per page
Limit int
// page number
Page int
// offset
Offset int
}
var NoPagination = newPagination(-1, -1)
var NoPagination = newPagination(-1, 0)
func newPagination(limit int, page int) *Pagination {
// make sure that pagination is valid
func newPagination(limit int, offset int) *Pagination {
return &Pagination{
Limit: limit,
Page: page,
Limit: limit,
Offset: offset,
}
}
func (p *Pagination) IsValidPagintaion() bool {
return p.Limit >= 0 && p.Page >= 0
}
func (p *Pagination) GetValidPagination(total int) (startIndex, endIndex int) {
if p.Limit == NoPagination.Limit {
return 0, total
}
func (p *Pagination) IsPageAvailable(total, startIndex int) bool {
return total > startIndex && p.Limit > 0
}
if p.Limit < 0 || p.Offset < 0 {
return 0, 0
}
func (p *Pagination) GetPaginationSettings(total int) (startIndex, endIndex int) {
startIndex = p.Limit * p.Page
startIndex = p.Limit * p.Offset
endIndex = startIndex + p.Limit
if endIndex > total {
@@ -86,33 +68,33 @@ func (p *Pagination) GetPaginationSettings(total int) (startIndex, endIndex int)
func New() *Query {
return &Query{
Pagination: &Pagination{
Limit: -1,
Page: -1,
},
SortBy: "",
Ascending: false,
Filters: []Filter{},
Pagination: NoPagination,
SortBy: "",
Ascending: false,
Filters: []Filter{},
}
}
type Filter struct {
Field Field
Value Comparable
Value Value
}
func ParseQueryParameter(request *restful.Request) *Query {
query := New()
limit, err := strconv.ParseInt(request.QueryParameter("limit"), 10, 0)
limit, err := strconv.Atoi(request.QueryParameter("limit"))
// equivalent to undefined, use the default value
if err != nil {
query.Pagination = NoPagination
limit = -1
}
page, err := strconv.Atoi(request.QueryParameter("page"))
// equivalent to undefined, use the default value
if err != nil {
page = 1
}
page, err := strconv.ParseInt(request.QueryParameter("page"), 10, 0)
if err == nil {
query.Pagination = newPagination(int(limit), int(page-1))
}
query.Pagination = newPagination(limit, page-1)
query.SortBy = Field(defaultString(request.QueryParameter("sortBy"), FieldCreationTimeStamp))
@@ -128,13 +110,12 @@ func ParseQueryParameter(request *restful.Request) *Query {
if len(f) != 0 {
query.Filters = append(query.Filters, Filter{
Field: field,
Value: ComparableString(f),
Value: Value(f),
})
}
}
return query
}
func defaultString(value, defaultValue string) string {

View File

@@ -16,7 +16,7 @@ func TestParseQueryParameter(t *testing.T) {
}{
{
"test normal case",
"name=foo&status=Running&application=book&page=1&limit=10&ascending=true",
"label=app.kubernetes.io/name:book&name=foo&status=Running&page=1&limit=10&ascending=true",
&Query{
Pagination: newPagination(10, 0),
SortBy: FieldCreationTimeStamp,
@@ -24,15 +24,15 @@ func TestParseQueryParameter(t *testing.T) {
Filters: []Filter{
{
FieldName,
ComparableString("foo"),
Value("foo"),
},
{
FieldLabel,
Value("app.kubernetes.io/name:book"),
},
{
FieldStatus,
ComparableString("Running"),
},
{
FieldApplication,
ComparableString("book"),
Value("Running"),
},
},
},
@@ -41,13 +41,10 @@ func TestParseQueryParameter(t *testing.T) {
"test bad case",
"xxxx=xxxx&dsfsw=xxxx&page=abc&limit=add&ascending=ssss",
&Query{
Pagination: &Pagination{
Limit: -1,
Page: -1,
},
SortBy: FieldCreationTimeStamp,
Ascending: false,
Filters: []Filter{},
Pagination: NoPagination,
SortBy: FieldCreationTimeStamp,
Ascending: false,
Filters: []Filter{},
},
},
}