From 8a1c453412a8914661d4850586f64fe0c730261e Mon Sep 17 00:00:00 2001 From: "Roland.Ma" Date: Mon, 15 Mar 2021 02:54:31 +0000 Subject: [PATCH] refine group e2e test Signed-off-by: Roland.Ma --- go.mod | 2 +- go.sum | 2 + staging/src/kubesphere.io/client-go/go.mod | 1 + staging/src/kubesphere.io/client-go/go.sum | 7 ++ .../client-go/restclient/restclient.go | 44 +++++++ .../versioned/iam/v1alpha2/group.go | 65 ++++++++++ .../versioned/iam/v1alpha2/iam_client.go | 55 +++++++++ .../versioned/iam/v1alpha2/rolebindings.go | 98 ++++++++++++++++ test/e2e/{ => constant}/constants.go | 5 +- test/e2e/framework/framework.go | 51 ++++++-- test/e2e/framework/iam/fixtures.go | 2 +- test/e2e/framework/iam/utils.go | 70 +++++++++++ test/e2e/framework/resource/fixtures.go | 35 ++++++ test/e2e/framework/test_context.go | 17 +++ test/e2e/groups.go | 111 +++++++++++++++--- 15 files changed, 536 insertions(+), 29 deletions(-) create mode 100644 staging/src/kubesphere.io/client-go/restclient/restclient.go create mode 100644 staging/src/kubesphere.io/client-go/restclient/versioned/iam/v1alpha2/group.go create mode 100644 staging/src/kubesphere.io/client-go/restclient/versioned/iam/v1alpha2/iam_client.go create mode 100644 staging/src/kubesphere.io/client-go/restclient/versioned/iam/v1alpha2/rolebindings.go rename test/e2e/{ => constant}/constants.go (80%) create mode 100644 test/e2e/framework/iam/utils.go create mode 100644 test/e2e/framework/resource/fixtures.go diff --git a/go.mod b/go.mod index 4fd18bad15..b2847c0a62 100644 --- a/go.mod +++ b/go.mod @@ -72,7 +72,7 @@ require ( github.com/stretchr/testify v1.6.1 github.com/xanzy/ssh-agent v0.2.1 // indirect golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de - golang.org/x/net v0.0.0-20200707034311-ab3426394381 + golang.org/x/net v0.0.0-20201224014010-6772e930b67b golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d google.golang.org/grpc v1.30.0 gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect diff --git a/go.sum b/go.sum index 3d874ad6de..9167f29276 100644 --- a/go.sum +++ b/go.sum @@ -289,6 +289,8 @@ github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3yg github.com/go-playground/universal-translator v0.0.0-20170327191703-71201497bace/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4= github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-resty/resty/v2 v2.5.0 h1:WFb5bD49/85PO7WgAjZ+/TJQ+Ty1XOcWEfD1zIFCM1c= +github.com/go-resty/resty/v2 v2.5.0/go.mod h1:B88+xCTEwvfD94NOuE6GS1wMlnoKNY8eEiNizfNwOwA= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= diff --git a/staging/src/kubesphere.io/client-go/go.mod b/staging/src/kubesphere.io/client-go/go.mod index bd995a6444..4e000fe5dc 100644 --- a/staging/src/kubesphere.io/client-go/go.mod +++ b/staging/src/kubesphere.io/client-go/go.mod @@ -7,6 +7,7 @@ module kubesphere.io/client-go go 1.13 require ( + github.com/go-resty/resty/v2 v2.5.0 k8s.io/apimachinery v0.18.6 k8s.io/client-go v0.18.6 sigs.k8s.io/controller-runtime v0.6.4 diff --git a/staging/src/kubesphere.io/client-go/go.sum b/staging/src/kubesphere.io/client-go/go.sum index 3b3f17c538..a241a436a8 100644 --- a/staging/src/kubesphere.io/client-go/go.sum +++ b/staging/src/kubesphere.io/client-go/go.sum @@ -117,6 +117,8 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= +github.com/go-resty/resty/v2 v2.5.0 h1:WFb5bD49/85PO7WgAjZ+/TJQ+Ty1XOcWEfD1zIFCM1c= +github.com/go-resty/resty/v2 v2.5.0/go.mod h1:B88+xCTEwvfD94NOuE6GS1wMlnoKNY8eEiNizfNwOwA= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= @@ -324,6 +326,8 @@ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= @@ -355,6 +359,9 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/staging/src/kubesphere.io/client-go/restclient/restclient.go b/staging/src/kubesphere.io/client-go/restclient/restclient.go new file mode 100644 index 0000000000..7c495cc4d5 --- /dev/null +++ b/staging/src/kubesphere.io/client-go/restclient/restclient.go @@ -0,0 +1,44 @@ +/* +Copyright 2021 The KubeSphere Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package restclient + +import ( + rest "k8s.io/client-go/rest" + iamv1alpha2 "kubesphere.io/client-go/restclient/versioned/iam/v1alpha2" +) + +// NewForConfig returns a new Client using the provided config and Options. +func NewForConfig(c *rest.Config) (*RestClient, error) { + var rc RestClient + var err error + rc.iamV1alpha2, err = iamv1alpha2.NewForConfig(c) + if err != nil { + return nil, err + } + return &rc, nil +} + +// RestClient is a set of restful API clients that doesn't compatible with +// Kube API machinery. +type RestClient struct { + iamV1alpha2 *iamv1alpha2.IamV1alpha2Client +} + +// IamV1alpha2 retrieves the IamV1alpha2Client +func (c *RestClient) IamV1alpha2() iamv1alpha2.IamV1alpha2Interface { + return c.iamV1alpha2 +} diff --git a/staging/src/kubesphere.io/client-go/restclient/versioned/iam/v1alpha2/group.go b/staging/src/kubesphere.io/client-go/restclient/versioned/iam/v1alpha2/group.go new file mode 100644 index 0000000000..315559f446 --- /dev/null +++ b/staging/src/kubesphere.io/client-go/restclient/versioned/iam/v1alpha2/group.go @@ -0,0 +1,65 @@ +/* +Copyright 2021 The KubeSphere Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha2 + +import ( + "context" + + resty "github.com/go-resty/resty/v2" +) + +type GroupsGetter interface { + Groups() GroupInterface +} + +type GroupInterface interface { + CreateBinding(ctx context.Context, workspace, group, user string) (string, error) +} + +type groups struct { + client *resty.Client +} + +func newGroups(c *IamV1alpha2Client) *groups { + return &groups{ + client: c.client, + } +} + +//TODO: to be remoted once we move kubesphere.io/apis out of kubesphere package +type groupMember struct { + UserName string `json:"userName"` + GroupName string `json:"groupName"` +} + +// Create takes the representation of a group and creates it. Returns the server's representation of the group, and an error, if there is any. +func (c *groups) CreateBinding(ctx context.Context, workspace, group, user string) (result string, err error) { + + members := []groupMember{{ + UserName: user, + GroupName: group, + }} + + resp, err := c.client.R(). + SetHeader("Content-Type", "application/json"). + SetBody(members). + SetPathParams(map[string]string{ + "workspace": workspace, + }). + Post("/kapis/iam.kubesphere.io/v1alpha2/workspaces/{workspace}/groupbindings") + return resp.String(), err +} diff --git a/staging/src/kubesphere.io/client-go/restclient/versioned/iam/v1alpha2/iam_client.go b/staging/src/kubesphere.io/client-go/restclient/versioned/iam/v1alpha2/iam_client.go new file mode 100644 index 0000000000..bef0bdc86c --- /dev/null +++ b/staging/src/kubesphere.io/client-go/restclient/versioned/iam/v1alpha2/iam_client.go @@ -0,0 +1,55 @@ +/* +Copyright 2020 The KubeSphere Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha2 + +import ( + resty "github.com/go-resty/resty/v2" + rest "k8s.io/client-go/rest" +) + +type IamV1alpha2Interface interface { + GroupsGetter + RoleBindingsGetter +} +type IamV1alpha2Client struct { + client *resty.Client +} + +func (c *IamV1alpha2Client) Groups() GroupInterface { + return newGroups(c) +} + +func (c *IamV1alpha2Client) RoleBindings() RoleBindingInterface { + return newRoleBindings(c) +} + +// NewForConfig creates a new IamV1alpha2Client for the given config. +func NewForConfig(c *rest.Config) (*IamV1alpha2Client, error) { + + client := resty.New() + + client.SetHostURL(c.Host) + if c.BearerToken != "" { + client.SetAuthToken(c.BearerToken) + } + + if c.Username != "" { + client.SetBasicAuth(c.Username, c.Password) + } + + return &IamV1alpha2Client{client}, nil +} diff --git a/staging/src/kubesphere.io/client-go/restclient/versioned/iam/v1alpha2/rolebindings.go b/staging/src/kubesphere.io/client-go/restclient/versioned/iam/v1alpha2/rolebindings.go new file mode 100644 index 0000000000..beb6a94c85 --- /dev/null +++ b/staging/src/kubesphere.io/client-go/restclient/versioned/iam/v1alpha2/rolebindings.go @@ -0,0 +1,98 @@ +/* +Copyright 2020 The KubeSphere Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha2 + +import ( + "context" + + resty "github.com/go-resty/resty/v2" +) + +type RoleBindingsGetter interface { + RoleBindings() RoleBindingInterface +} + +type RoleBindingInterface interface { + CreateRoleBinding(ctx context.Context, namespace, role, group string) (string, error) + CreateWorkspaceRoleBinding(ctx context.Context, namespace, role, group string) (string, error) +} + +type rolebindings struct { + client *resty.Client +} + +func newRoleBindings(c *IamV1alpha2Client) *rolebindings { + return &rolebindings{ + client: c.client, + } +} + +// CreateRoleBinding assembling of a rolebinding object and creates it. Returns the server's response and an error, if there is any. +func (c *rolebindings) CreateRoleBinding(ctx context.Context, namespace, role, group string) (result string, err error) { + + roles := []map[string]interface{}{{ + "subjects": []map[string]interface{}{ + { + "kind": "Group", + "apiGroup": "rbac.authorization.k8s.io", + "name": group, + }, + }, + "roleRef": map[string]interface{}{ + "apiGroup": "rbac.authorization.k8s.io", + "kind": "Role", + "name": role, + }, + }} + + resp, err := c.client.R(). + SetHeader("Content-Type", "application/json"). + SetBody(roles). + SetPathParams(map[string]string{ + "namespace": namespace, + }). + Post("/kapis/iam.kubesphere.io/v1alpha2/namespaces/{namespace}/rolebindings") + return resp.String(), err +} + +// CreateWorkspaceRoleBinding assembling of a workspacerolebinding object and creates it. Returns the server's response, and an error, if there is any. +func (c *rolebindings) CreateWorkspaceRoleBinding(ctx context.Context, workspace, role, group string) (result string, err error) { + + roles := []map[string]interface{}{{ + "subjects": []map[string]interface{}{ + { + "kind": "Group", + "apiGroup": "rbac.authorization.k8s.io", + "name": group, + }, + }, + "roleRef": map[string]interface{}{ + "apiGroup": "iam.kubesphere.io/v1alpha2", + "kind": "WorkspaceRoleBinding", + "name": role, + }, + }} + + resp, err := c.client.R(). + SetHeader("Content-Type", "application/json"). + SetBody(roles). + SetPathParams(map[string]string{ + "workspace": workspace, + }). + Post("/kapis/iam.kubesphere.io/v1alpha2/workspaces/{workspace}/workspacerolebindings/") + return resp.String(), err +} diff --git a/test/e2e/constants.go b/test/e2e/constant/constants.go similarity index 80% rename from test/e2e/constants.go rename to test/e2e/constant/constants.go index 5437ef9009..911770e44f 100644 --- a/test/e2e/constants.go +++ b/test/e2e/constant/constants.go @@ -14,8 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -package e2e +package constant const ( DefaultWorkspaceRoleAdmin = "%v-admin" + DefaultAdminUser = "admin" + DefaultPassword = "P@88w0rd" + LocalAPIServer = "http://127.0.0.1:9090" ) diff --git a/test/e2e/framework/framework.go b/test/e2e/framework/framework.go index b1938a00d7..cfcf75bbee 100644 --- a/test/e2e/framework/framework.go +++ b/test/e2e/framework/framework.go @@ -19,6 +19,7 @@ package framework import ( "context" "fmt" + "time" "github.com/onsi/ginkgo" //nolint:stylecheck "github.com/onsi/gomega" @@ -29,11 +30,21 @@ import ( "k8s.io/client-go/rest" "kubesphere.io/client-go/client" "kubesphere.io/client-go/client/generic" + "kubesphere.io/client-go/restclient" "kubesphere.io/kubesphere/pkg/apis" "kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/test/e2e/framework/workspace" ) +const ( + // Using the same interval as integration should be fine given the + // minimal load that the apiserver is likely to be under. + PollInterval = 50 * time.Millisecond + // How long to try single API calls (like 'get' or 'list'). Used to prevent + // transient failures from failing tests. + DefaultSingleCallTimeout = 30 * time.Second +) + type Framework struct { BaseName string Workspace string @@ -45,6 +56,7 @@ type Framework struct { // that the implementation can vary without affecting tests. type KubeSphereFramework interface { GenericClient(userAgent string) client.Client + RestClient(userAgent string) *restclient.RestClient KubeSphereSystemNamespace() string // Name of the workspace for the current test to target @@ -54,6 +66,8 @@ type KubeSphereFramework interface { CreateNamespace(name string) string // Get Names of the namespaces for the current test to target GetNamespaceNames() []string + + GetScheme() *runtime.Scheme } func NewKubeSphereFramework(baseName string) KubeSphereFramework { @@ -65,6 +79,7 @@ func NewKubeSphereFramework(baseName string) KubeSphereFramework { if err := scheme.AddToScheme(sch); err != nil { Failf("unable add Kubernetes APIs to scheme: %v", err) } + f := &Framework{ BaseName: baseName, Scheme: sch, @@ -97,7 +112,7 @@ func CreateTestWorkSpace(client client.Client, baseName string) string { wspt.GenerateName = fmt.Sprintf("e2e-tests-%v-", baseName) wspt, err := workspace.CreateWorkspace(client, wspt) gomega.Expect(err).NotTo(gomega.HaveOccurred()) - ginkgo.By(fmt.Sprintf("Created test namespace %s", wspt.Name)) + ginkgo.By(fmt.Sprintf("Created test workspace %s", wspt.Name)) return wspt.Name } @@ -106,6 +121,7 @@ func (f *Framework) GetNamespaceNames() []string { } func (f *Framework) CreateNamespace(name string) string { + name = fmt.Sprintf("%s-%s", f.Workspace, name) ns := &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: name, @@ -133,19 +149,32 @@ func (f *Framework) KubeSphereSystemNamespace() string { func (f *Framework) GenericClient(userAgent string) client.Client { ctx := TestContext + config := &rest.Config{ - Host: "127.0.0.1:9090", - Username: "admin", - Password: "P@88w0rd", - } - if ctx.Host != "" { - config = &rest.Config{ - Host: ctx.Host, - Username: ctx.Username, - Password: ctx.Password, - } + Host: ctx.Host, + Username: ctx.Username, + Password: ctx.Password, } + rest.AddUserAgent(config, userAgent) return generic.NewForConfigOrDie(config, client.Options{Scheme: f.Scheme}) } + +func (f *Framework) RestClient(userAgent string) *restclient.RestClient { + ctx := TestContext + config := &rest.Config{ + Host: ctx.Host, + Username: ctx.Username, + Password: ctx.Password, + } + c, err := restclient.NewForConfig(config) + if err != nil { + panic(err) + } + return c +} + +func (f *Framework) GetScheme() *runtime.Scheme { + return f.Scheme +} diff --git a/test/e2e/framework/iam/fixtures.go b/test/e2e/framework/iam/fixtures.go index 1e9e52d53b..9e5d34fe97 100644 --- a/test/e2e/framework/iam/fixtures.go +++ b/test/e2e/framework/iam/fixtures.go @@ -41,7 +41,7 @@ func NewUser(name, globelRole string) *iamv1alpha2.User { }, }, Spec: iamv1alpha2.UserSpec{ - Email: fmt.Sprint("%v@kubesphere.io", name), + Email: fmt.Sprintf("%s@kubesphere.io", name), EncryptedPassword: "P@88w0rd", }, } diff --git a/test/e2e/framework/iam/utils.go b/test/e2e/framework/iam/utils.go new file mode 100644 index 0000000000..a1940b5504 --- /dev/null +++ b/test/e2e/framework/iam/utils.go @@ -0,0 +1,70 @@ +/* +Copyright 2021 KubeSphere Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package iam + +import ( + "context" + "fmt" + + "golang.org/x/oauth2" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/rest" + "kubesphere.io/client-go/client" + "kubesphere.io/client-go/client/generic" + "kubesphere.io/client-go/restclient" + "kubesphere.io/kubesphere/test/e2e/framework" +) + +//NewClient creates a new client with user authencation +func NewClient(s *runtime.Scheme, user, passsword string) (client.Client, error) { + + ctx := framework.TestContext + token, err := getToken(ctx.Host, user, passsword) + if err != nil { + return nil, err + } + config := &rest.Config{ + Host: ctx.Host, + BearerToken: token.AccessToken, + } + + return generic.New(config, client.Options{Scheme: s}) +} + +func NewRestClient(user, passsword string) (*restclient.RestClient, error) { + ctx := framework.TestContext + token, err := getToken(ctx.Host, user, passsword) + if err != nil { + return nil, err + } + config := &rest.Config{ + Host: ctx.Host, + BearerToken: token.AccessToken, + } + + return restclient.NewForConfig(config) +} + +func getToken(host, user, password string) (*oauth2.Token, error) { + config := &oauth2.Config{ + Endpoint: oauth2.Endpoint{ + TokenURL: fmt.Sprintf("%s/oauth/token", host), + AuthStyle: oauth2.AuthStyleInParams, + }, + } + return config.PasswordCredentialsToken(context.TODO(), user, password) +} diff --git a/test/e2e/framework/resource/fixtures.go b/test/e2e/framework/resource/fixtures.go new file mode 100644 index 0000000000..6d70ab3cbf --- /dev/null +++ b/test/e2e/framework/resource/fixtures.go @@ -0,0 +1,35 @@ +/* +Copyright 2021 KubeSphere Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resource + +import ( + "context" + + v1 "k8s.io/api/core/v1" + + "kubesphere.io/client-go/client" +) + +// ListPods gets the Pods by namespace. If the returned error is nil, the returned PodList is valid. +func ListPods(c client.Client, namespace string) (*v1.PodList, error) { + pods := &v1.PodList{} + err := c.List(context.TODO(), pods, &client.ListOptions{Namespace: namespace}) + if err != nil { + return nil, err + } + return pods, nil +} diff --git a/test/e2e/framework/test_context.go b/test/e2e/framework/test_context.go index 395f2dbb96..86b1a36b74 100644 --- a/test/e2e/framework/test_context.go +++ b/test/e2e/framework/test_context.go @@ -19,6 +19,8 @@ package framework import ( "flag" "os" + + "kubesphere.io/kubesphere/test/e2e/constant" ) type TestContextType struct { @@ -41,7 +43,22 @@ func registerFlags(t *TestContextType) { var TestContext *TestContextType = &TestContextType{} +func setDefaultValue(t *TestContextType) { + + if t.Host == "" { + t.Host = constant.LocalAPIServer + } + if t.Username == "" { + t.Username = constant.DefaultAdminUser + } + + if t.Password == "" { + t.Password = constant.DefaultPassword + } +} + func ParseFlags() { registerFlags(TestContext) flag.Parse() + setDefaultValue(TestContext) } diff --git a/test/e2e/groups.go b/test/e2e/groups.go index cdd8bd66d1..6c8839808d 100644 --- a/test/e2e/groups.go +++ b/test/e2e/groups.go @@ -17,34 +17,115 @@ limitations under the License. package e2e import ( + "context" + "fmt" + . "github.com/onsi/ginkgo" //nolint:stylecheck . "github.com/onsi/gomega" //nolint:stylecheck + "k8s.io/apimachinery/pkg/util/wait" "kubesphere.io/client-go/client" + "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2" + "kubesphere.io/kubesphere/test/e2e/constant" "kubesphere.io/kubesphere/test/e2e/framework" "kubesphere.io/kubesphere/test/e2e/framework/iam" + "kubesphere.io/kubesphere/test/e2e/framework/resource" + + apierrors "k8s.io/apimachinery/pkg/api/errors" ) -var _ = Describe("Worksspace", func() { +const ( + GroupName = "test-group" + UserName = "tester" +) + +var _ = Describe("Groups", func() { f := framework.NewKubeSphereFramework("group") - var wsName = "" - var gclient client.Client + var workspace = "" + var namespace = "" + var group = "" + var userClient client.Client + + adminClient := f.GenericClient("group") + restClient := f.RestClient("group") + + Context("Grant Permissions by assign user to group", func() { + It("Should create group and assign members successfully", func() { + By("Expecting to create test workspace for Group tests") + workspace = f.TestWorkSpaceName() + namespace = f.CreateNamespace("group") + g, err := iam.CreateGroup(adminClient, iam.NewGroup(GroupName, workspace), workspace) + framework.ExpectNoError(err) + group = g.Name + Eventually(func() bool { + expGroup, err := iam.GetGroup(adminClient, group, workspace) + return err == nil && expGroup.Name == group + }, timeout, interval).Should(BeTrue()) + + By("Create user and wait until active") + + u, err := createUserWithWait(f, adminClient, UserName) + framework.ExpectNoError(err) + + By("Assign user to Group") + _, err = restClient.IamV1alpha2().Groups().CreateBinding(context.TODO(), workspace, group, UserName) + framework.ExpectNoError(err) + + By("Creating a new client with user authentication") + userClient, err = iam.NewClient(f.GetScheme(), u.Name, constant.DefaultPassword) + framework.ExpectNoError(err) + }) + + It(fmt.Sprintf("%s has no permissions to access namespace: %s", UserName, namespace), func() { + err := CheckNamespaceAccess(f, userClient, namespace) + Expect(apierrors.IsForbidden(err)).To(BeTrue()) + + }) + + It(fmt.Sprintf("%s should has full access namespace: %s", UserName, namespace), func() { + + rolename := fmt.Sprintf("%s-regular", workspace) + By("Grant namespace permission by bind admin role to group") + _, err := restClient.IamV1alpha2().RoleBindings().CreateRoleBinding(context.TODO(), namespace, "admin", group) + framework.ExpectNoError(err) + _, err = restClient.IamV1alpha2().RoleBindings().CreateWorkspaceRoleBinding(context.TODO(), workspace, rolename, group) + framework.ExpectNoError(err) + + err = CheckNamespaceAccess(f, userClient, namespace) + framework.ExpectNoError(err) + }) - BeforeEach(func() { - gclient = f.GenericClient("group") }) +}) - It("Should create group successfully", func() { +// Todo: The can-i API should be a better option, but ks-apiserver doesn't support it yet. +// So we will try to list objects in the namespace. +func CheckNamespaceAccess(f framework.KubeSphereFramework, c client.Client, namespace string) error { + _, err := resource.ListPods(c, namespace) + return err +} - By("Expecting to create workspace thronght workspace template") - wsName = f.TestWorkSpaceName() +// Create a user and wait until the user became active status. +func createUserWithWait(f framework.KubeSphereFramework, c client.Client, username string) (*v1alpha2.User, error) { + u := iam.NewUser(username, "platform-regular") + if _, err := iam.CreateUser(c, u); err != nil { + return nil, err + } - group, err := iam.CreateGroup(gclient, iam.NewGroup("group1", wsName), wsName) - framework.ExpectNoError(err) - Eventually(func() bool { - expGroup, err := iam.GetGroup(gclient, group.Name, wsName) - return err == nil && expGroup.Name == group.Name - }, timeout, interval).Should(BeTrue()) + err := wait.PollImmediate(framework.PollInterval, framework.DefaultSingleCallTimeout, func() (bool, error) { + u, err := iam.GetUser(c, username) + if err != nil { + framework.Failf("Cannot retrieve User %q: %v", username, err) + return false, err + } + if u == nil || u.Status.State == nil { + return false, nil + } + return *u.Status.State == v1alpha2.UserActive, nil }) -}) + if err != nil { + return nil, err + } + return u, nil +}