@@ -122,7 +122,7 @@ func addControllers(
|
||||
client.KubeSphere(), devopsClient,
|
||||
informerFactory.KubernetesSharedInformerFactory().Core().V1().Namespaces(),
|
||||
informerFactory.KubeSphereSharedInformerFactory().Devops().V1alpha3().DevOpsProjects(),
|
||||
)
|
||||
informerFactory.KubeSphereSharedInformerFactory().Tenant().V1alpha1().Workspaces())
|
||||
|
||||
devopsPipelineController = pipeline.NewController(client.Kubernetes(),
|
||||
client.KubeSphere(),
|
||||
|
||||
@@ -51,6 +51,9 @@ type Attributes interface {
|
||||
// The namespace of the object, if a request is for a REST object.
|
||||
GetNamespace() string
|
||||
|
||||
// The devops project of the object, if a request is for a REST object.
|
||||
GetDevOps() string
|
||||
|
||||
// The kind of object, if a request is for a REST object.
|
||||
GetResource() string
|
||||
|
||||
@@ -109,6 +112,7 @@ type AttributesRecord struct {
|
||||
Cluster string
|
||||
Workspace string
|
||||
Namespace string
|
||||
DevOps string
|
||||
APIGroup string
|
||||
APIVersion string
|
||||
Resource string
|
||||
@@ -144,6 +148,10 @@ func (a AttributesRecord) GetNamespace() string {
|
||||
return a.Namespace
|
||||
}
|
||||
|
||||
func (a AttributesRecord) GetDevOps() string {
|
||||
return a.DevOps
|
||||
}
|
||||
|
||||
func (a AttributesRecord) GetResource() string {
|
||||
return a.Resource
|
||||
}
|
||||
|
||||
@@ -234,11 +234,21 @@ func (r *RBACAuthorizer) visitRulesFor(requestAttributes authorizer.Attributes,
|
||||
}
|
||||
}
|
||||
|
||||
if requestAttributes.GetResourceScope() == request.WorkspaceScope || requestAttributes.GetResourceScope() == request.NamespaceScope {
|
||||
if requestAttributes.GetResourceScope() == request.WorkspaceScope ||
|
||||
requestAttributes.GetResourceScope() == request.NamespaceScope ||
|
||||
requestAttributes.GetResourceScope() == request.DevOpsScope {
|
||||
|
||||
var workspace string
|
||||
var err error
|
||||
// all of resource under namespace and devops belong to workspace
|
||||
if requestAttributes.GetResourceScope() == request.NamespaceScope {
|
||||
if workspace, err = r.am.GetControlledWorkspace(requestAttributes.GetNamespace()); err != nil {
|
||||
if workspace, err = r.am.GetNamespaceControlledWorkspace(requestAttributes.GetNamespace()); err != nil {
|
||||
if !visitor(nil, "", nil, err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else if requestAttributes.GetResourceScope() == request.DevOpsScope {
|
||||
if workspace, err = r.am.GetDevOpsControlledWorkspace(requestAttributes.GetDevOps()); err != nil {
|
||||
if !visitor(nil, "", nil, err) {
|
||||
return
|
||||
}
|
||||
@@ -279,19 +289,33 @@ func (r *RBACAuthorizer) visitRulesFor(requestAttributes authorizer.Attributes,
|
||||
}
|
||||
}
|
||||
|
||||
if requestAttributes.GetResourceScope() == request.NamespaceScope {
|
||||
if roleBindings, err := r.am.ListRoleBindings("", requestAttributes.GetNamespace()); err != nil {
|
||||
if requestAttributes.GetResourceScope() == request.NamespaceScope ||
|
||||
requestAttributes.GetResourceScope() == request.DevOpsScope {
|
||||
|
||||
namespace := requestAttributes.GetNamespace()
|
||||
// list devops role binding
|
||||
if requestAttributes.GetResourceScope() == request.DevOpsScope {
|
||||
if relatedNamespace, err := r.am.GetDevOpsRelatedNamespace(requestAttributes.GetDevOps()); err != nil {
|
||||
if !visitor(nil, "", nil, err) {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
namespace = relatedNamespace
|
||||
}
|
||||
}
|
||||
|
||||
if roleBindings, err := r.am.ListRoleBindings("", namespace); err != nil {
|
||||
if !visitor(nil, "", nil, err) {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
sourceDescriber := &roleBindingDescriber{}
|
||||
for _, roleBinding := range roleBindings {
|
||||
subjectIndex, applies := appliesTo(requestAttributes.GetUser(), roleBinding.Subjects, requestAttributes.GetNamespace())
|
||||
subjectIndex, applies := appliesTo(requestAttributes.GetUser(), roleBinding.Subjects, namespace)
|
||||
if !applies {
|
||||
continue
|
||||
}
|
||||
regoPolicy, rules, err := r.am.GetRoleReferenceRules(roleBinding.RoleRef, requestAttributes.GetNamespace())
|
||||
regoPolicy, rules, err := r.am.GetRoleReferenceRules(roleBinding.RoleRef, namespace)
|
||||
if err != nil {
|
||||
visitor(nil, "", nil, err)
|
||||
continue
|
||||
|
||||
@@ -82,12 +82,12 @@ func getAuthorizerAttributes(ctx context.Context) (authorizer.Attributes, error)
|
||||
attribs.Cluster = requestInfo.Cluster
|
||||
attribs.Workspace = requestInfo.Workspace
|
||||
attribs.KubernetesRequest = requestInfo.IsKubernetesRequest
|
||||
|
||||
attribs.APIGroup = requestInfo.APIGroup
|
||||
attribs.APIVersion = requestInfo.APIVersion
|
||||
attribs.Resource = requestInfo.Resource
|
||||
attribs.Subresource = requestInfo.Subresource
|
||||
attribs.Namespace = requestInfo.Namespace
|
||||
attribs.DevOps = requestInfo.DevOps
|
||||
attribs.Name = requestInfo.Name
|
||||
|
||||
return &attribs, nil
|
||||
|
||||
@@ -69,6 +69,9 @@ type RequestInfo struct {
|
||||
// Cluster of requested resource, this is empty in single-cluster environment
|
||||
Cluster string
|
||||
|
||||
// DevOps project of requested resource
|
||||
DevOps string
|
||||
|
||||
// Scope of requested resource.
|
||||
ResourceScope string
|
||||
}
|
||||
@@ -195,15 +198,6 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
|
||||
}
|
||||
}
|
||||
|
||||
selector := req.URL.Query().Get("labelSelector")
|
||||
// URL forms: /api/v1/watch/namespaces?labelSelector=kubesphere.io/workspace=system-workspace
|
||||
if strings.HasPrefix(selector, workspaceSelectorPrefix) {
|
||||
workspace := strings.TrimPrefix(selector, workspaceSelectorPrefix)
|
||||
// URL forms: /api/v1/watch/namespaces?labelSelector=kubesphere.io/workspace==system-workspace
|
||||
workspace = strings.TrimPrefix(workspace, "=")
|
||||
requestInfo.Workspace = workspace
|
||||
}
|
||||
|
||||
// URL forms: /namespaces/{namespace}/{kind}/*, where parts are adjusted to be relative to kind
|
||||
if currentParts[0] == "namespaces" {
|
||||
if len(currentParts) > 1 {
|
||||
@@ -215,8 +209,19 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
|
||||
currentParts = currentParts[2:]
|
||||
}
|
||||
}
|
||||
} else if currentParts[0] == "devops" {
|
||||
if len(currentParts) > 1 {
|
||||
requestInfo.DevOps = currentParts[1]
|
||||
|
||||
// if there is another step after the devops name
|
||||
// move currentParts to include it as a resource in its own right
|
||||
if len(currentParts) > 2 {
|
||||
currentParts = currentParts[2:]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
requestInfo.Namespace = metav1.NamespaceNone
|
||||
requestInfo.DevOps = metav1.NamespaceNone
|
||||
}
|
||||
|
||||
// parsing successful, so we now know the proper value for .Parts
|
||||
@@ -260,6 +265,15 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
|
||||
requestInfo.Verb = "list"
|
||||
}
|
||||
|
||||
// URL forms: /api/v1/watch/namespaces?labelSelector=kubesphere.io/workspace=system-workspace
|
||||
if requestInfo.Verb == "watch" {
|
||||
selector := req.URL.Query().Get("labelSelector")
|
||||
if strings.HasPrefix(selector, workspaceSelectorPrefix) {
|
||||
workspace := strings.TrimPrefix(selector, workspaceSelectorPrefix)
|
||||
requestInfo.Workspace = workspace
|
||||
}
|
||||
}
|
||||
|
||||
if opts.FieldSelector != nil {
|
||||
if name, ok := opts.FieldSelector.RequiresExactMatch("metadata.name"); ok {
|
||||
if len(path.IsValidPathSegmentName(name)) == 0 {
|
||||
@@ -306,6 +320,7 @@ const (
|
||||
ClusterScope = "Cluster"
|
||||
WorkspaceScope = "Workspace"
|
||||
NamespaceScope = "Namespace"
|
||||
DevOpsScope = "DevOps"
|
||||
workspaceSelectorPrefix = constants.WorkspaceLabelKey + "="
|
||||
)
|
||||
|
||||
@@ -318,6 +333,10 @@ func (r *RequestInfoFactory) resolveResourceScope(request RequestInfo) string {
|
||||
return NamespaceScope
|
||||
}
|
||||
|
||||
if request.DevOps != "" {
|
||||
return DevOpsScope
|
||||
}
|
||||
|
||||
if request.Workspace != "" {
|
||||
return WorkspaceScope
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ import (
|
||||
"k8s.io/klog"
|
||||
devopsv1alpha3 "kubesphere.io/kubesphere/pkg/apis/devops/v1alpha3"
|
||||
"kubesphere.io/kubesphere/pkg/client/clientset/versioned/scheme"
|
||||
tenantv1alpha1informers "kubesphere.io/kubesphere/pkg/client/informers/externalversions/tenant/v1alpha1"
|
||||
tenantv1alpha1listers "kubesphere.io/kubesphere/pkg/client/listers/tenant/v1alpha1"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
devopsClient "kubesphere.io/kubesphere/pkg/simple/client/devops"
|
||||
"kubesphere.io/kubesphere/pkg/utils/k8sutil"
|
||||
@@ -48,6 +50,9 @@ type Controller struct {
|
||||
namespaceLister corev1lister.NamespaceLister
|
||||
namespaceSynced cache.InformerSynced
|
||||
|
||||
workspaceLister tenantv1alpha1listers.WorkspaceLister
|
||||
workspaceSynced cache.InformerSynced
|
||||
|
||||
workqueue workqueue.RateLimitingInterface
|
||||
|
||||
workerLoopPeriod time.Duration
|
||||
@@ -59,7 +64,8 @@ func NewController(client clientset.Interface,
|
||||
kubesphereClient kubesphereclient.Interface,
|
||||
devopsClinet devopsClient.Interface,
|
||||
namespaceInformer corev1informer.NamespaceInformer,
|
||||
devopsInformer devopsinformers.DevOpsProjectInformer) *Controller {
|
||||
devopsInformer devopsinformers.DevOpsProjectInformer,
|
||||
workspaceInformer tenantv1alpha1informers.WorkspaceInformer) *Controller {
|
||||
|
||||
broadcaster := record.NewBroadcaster()
|
||||
broadcaster.StartLogging(func(format string, args ...interface{}) {
|
||||
@@ -77,6 +83,8 @@ func NewController(client clientset.Interface,
|
||||
devOpsProjectSynced: devopsInformer.Informer().HasSynced,
|
||||
namespaceLister: namespaceInformer.Lister(),
|
||||
namespaceSynced: namespaceInformer.Informer().HasSynced,
|
||||
workspaceLister: workspaceInformer.Lister(),
|
||||
workspaceSynced: workspaceInformer.Informer().HasSynced,
|
||||
workerLoopPeriod: time.Second,
|
||||
}
|
||||
|
||||
@@ -163,7 +171,7 @@ func (c *Controller) Run(workers int, stopCh <-chan struct{}) error {
|
||||
klog.Info("starting devops project controller")
|
||||
defer klog.Info("shutting down devops project controller")
|
||||
|
||||
if !cache.WaitForCacheSync(stopCh, c.devOpsProjectSynced) {
|
||||
if !cache.WaitForCacheSync(stopCh, c.devOpsProjectSynced, c.devOpsProjectSynced, c.workspaceSynced) {
|
||||
return fmt.Errorf("failed to wait for caches to sync")
|
||||
}
|
||||
|
||||
@@ -273,12 +281,18 @@ func (c *Controller) syncHandler(key string) error {
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(copyProject, project) {
|
||||
_, err := c.kubesphereClient.DevopsV1alpha3().DevOpsProjects().Update(copyProject)
|
||||
copyProject, err = c.kubesphereClient.DevopsV1alpha3().DevOpsProjects().Update(copyProject)
|
||||
if err != nil {
|
||||
klog.V(8).Info(err, fmt.Sprintf("failed to update ns %s ", key))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if copyProject, err = c.bindWorkspace(copyProject); err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Check project exists, otherwise we will create it.
|
||||
_, err := c.devopsClient.GetDevOpsProject(copyProject.Status.AdminNamespace)
|
||||
if err != nil {
|
||||
@@ -312,6 +326,38 @@ func (c *Controller) syncHandler(key string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Controller) bindWorkspace(project *devopsv1alpha3.DevOpsProject) (*devopsv1alpha3.DevOpsProject, error) {
|
||||
|
||||
workspaceName := project.Labels[constants.WorkspaceLabelKey]
|
||||
|
||||
if workspaceName == "" {
|
||||
return project, nil
|
||||
}
|
||||
|
||||
workspace, err := c.workspaceLister.Get(workspaceName)
|
||||
|
||||
if err != nil {
|
||||
// skip if workspace not found
|
||||
if errors.IsNotFound(err) {
|
||||
return project, nil
|
||||
}
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !metav1.IsControlledBy(project, workspace) {
|
||||
project.OwnerReferences = nil
|
||||
if err := controllerutil.SetControllerReference(workspace, project, scheme.Scheme); err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.kubesphereClient.DevopsV1alpha3().DevOpsProjects().Update(project)
|
||||
}
|
||||
|
||||
return project, nil
|
||||
}
|
||||
|
||||
func (c *Controller) deleteDevOpsProjectInDevOps(project *devopsv1alpha3.DevOpsProject) error {
|
||||
|
||||
err := c.devopsClient.DeleteDevOpsProject(project.Status.AdminNamespace)
|
||||
@@ -330,9 +376,16 @@ func (c *Controller) generateNewNamespace(project *devopsv1alpha3.DevOpsProject)
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: project.Name,
|
||||
Labels: map[string]string{constants.DevOpsProjectLabelKey: project.Name},
|
||||
Labels: map[string]string{
|
||||
constants.DevOpsProjectLabelKey: project.Name,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if creator := project.Annotations[constants.CreatorAnnotationKey]; creator != "" {
|
||||
ns.Annotations = map[string]string{constants.CreatorAnnotationKey: creator}
|
||||
}
|
||||
|
||||
controllerutil.SetControllerReference(project, ns, scheme.Scheme)
|
||||
return ns
|
||||
}
|
||||
|
||||
@@ -120,8 +120,10 @@ func (f *fixture) newController() (*Controller, informers.SharedInformerFactory,
|
||||
k8sI := kubeinformers.NewSharedInformerFactory(f.kubeclient, noResyncPeriodFunc())
|
||||
dI := fakeDevOps.New(f.initDevOpsProject...)
|
||||
|
||||
c := NewController(f.kubeclient, f.client, dI, k8sI.Core().V1().Namespaces(),
|
||||
i.Devops().V1alpha3().DevOpsProjects())
|
||||
c := NewController(f.kubeclient, f.client, dI,
|
||||
k8sI.Core().V1().Namespaces(),
|
||||
i.Devops().V1alpha3().DevOpsProjects(),
|
||||
i.Tenant().V1alpha1().Workspaces())
|
||||
|
||||
c.devOpsProjectSynced = alwaysReady
|
||||
c.eventRecorder = &record.FakeRecorder{}
|
||||
@@ -251,7 +253,9 @@ func filterInformerActions(actions []core.Action) []core.Action {
|
||||
(action.Matches("list", devopsprojects.ResourcePluralDevOpsProject) ||
|
||||
action.Matches("watch", devopsprojects.ResourcePluralDevOpsProject) ||
|
||||
action.Matches("list", "namespaces") ||
|
||||
action.Matches("watch", "namespaces")) {
|
||||
action.Matches("watch", "namespaces") ||
|
||||
action.Matches("watch", "workspaces") ||
|
||||
action.Matches("list", "workspaces")) {
|
||||
continue
|
||||
}
|
||||
ret = append(ret, action)
|
||||
|
||||
@@ -229,17 +229,16 @@ func (c *Controller) reconcile(key string) error {
|
||||
}
|
||||
|
||||
if globalRoleBinding.RoleRef.Name == iamv1alpha2.PlatformAdmin {
|
||||
if err := c.relateToClusterAdmin(globalRoleBinding); err != nil {
|
||||
if err := c.assignClusterAdminRole(globalRoleBinding); err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
if c.devopsClient != nil {
|
||||
username := findExpectUsername(globalRoleBinding)
|
||||
err = c.devopsClient.AssignGlobalRole(modeldevops.JenkinsAdminRoleName, username)
|
||||
if err != nil {
|
||||
klog.Errorf("%+v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if c.devopsClient != nil {
|
||||
if err := c.assignDevOpsAdminRole(globalRoleBinding); err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,11 +298,9 @@ func (c *Controller) multiClusterSync(globalRoleBinding *iamv1alpha2.GlobalRoleB
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Controller) relateToClusterAdmin(globalRoleBinding *iamv1alpha2.GlobalRoleBinding) error {
|
||||
func (c *Controller) assignClusterAdminRole(globalRoleBinding *iamv1alpha2.GlobalRoleBinding) error {
|
||||
|
||||
username := findExpectUsername(globalRoleBinding)
|
||||
|
||||
// unexpected
|
||||
if username == "" {
|
||||
return nil
|
||||
}
|
||||
@@ -436,6 +433,16 @@ func (c *Controller) ensureNotControlledByKubefed(globalRoleBinding *iamv1alpha2
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Controller) assignDevOpsAdminRole(globalRoleBinding *iamv1alpha2.GlobalRoleBinding) error {
|
||||
if username := findExpectUsername(globalRoleBinding); username != "" {
|
||||
if err := c.devopsClient.AssignGlobalRole(modeldevops.JenkinsAdminRoleName, username); err != nil {
|
||||
klog.Errorf("%+v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureSubjectAPIVersionIsValid(subjects []rbacv1.Subject) []rbacv1.Subject {
|
||||
validSubjects := make([]rbacv1.Subject, 0)
|
||||
for _, subject := range subjects {
|
||||
|
||||
@@ -5,8 +5,6 @@ import (
|
||||
"github.com/emicklei/go-restful"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/api"
|
||||
@@ -1079,7 +1077,7 @@ func (h *iamHandler) resolveNamespace(namespace string, devops string) (string,
|
||||
if devops == "" {
|
||||
return namespace, nil
|
||||
}
|
||||
return h.am.GetControlledNamespace(devops)
|
||||
return h.am.GetDevOpsRelatedNamespace(devops)
|
||||
}
|
||||
|
||||
func (h *iamHandler) PatchWorkspaceRole(request *restful.Request, response *restful.Response) {
|
||||
@@ -1213,18 +1211,7 @@ func (h *iamHandler) updateGlobalRoleBinding(operator user.Info, user *iamv1alph
|
||||
func (h *iamHandler) ListUserLoginRecords(request *restful.Request, response *restful.Response) {
|
||||
username := request.PathParameter("user")
|
||||
queryParam := query.ParseQueryParameter(request)
|
||||
selector, _ := labels.Parse(queryParam.LabelSelector)
|
||||
if selector == nil {
|
||||
selector = labels.NewSelector()
|
||||
}
|
||||
requirement, err := labels.NewRequirement(iamv1alpha2.UserReferenceLabel, selection.Equals, []string{username})
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
handleError(request, response, err)
|
||||
return
|
||||
}
|
||||
selector.Add(*requirement)
|
||||
queryParam.LabelSelector = selector.String()
|
||||
queryParam.Filters[query.FieldLabel] = query.Value(fmt.Sprintf("%s=%s", iamv1alpha2.UserReferenceLabel, username))
|
||||
result, err := h.im.ListLoginRecords(queryParam)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
|
||||
@@ -290,6 +290,7 @@ func (h *handler) passwordGrant(username string, password string, req *restful.R
|
||||
return
|
||||
default:
|
||||
response.WriteError(http.StatusInternalServerError, apierrors.NewInternalError(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -101,6 +101,29 @@ func (h *tenantHandler) ListNamespaces(req *restful.Request, resp *restful.Respo
|
||||
resp.WriteEntity(result)
|
||||
}
|
||||
|
||||
func (h *tenantHandler) ListDevOpsProjects(req *restful.Request, resp *restful.Response) {
|
||||
user, ok := request.UserFrom(req.Request.Context())
|
||||
queryParam := query.ParseQueryParameter(req)
|
||||
|
||||
if !ok {
|
||||
err := fmt.Errorf("cannot obtain user info")
|
||||
klog.Errorln(err)
|
||||
api.HandleForbidden(resp, nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
workspace := req.PathParameter("workspace")
|
||||
|
||||
result, err := h.tenant.ListDevOpsProjects(user, workspace, queryParam)
|
||||
|
||||
if err != nil {
|
||||
api.HandleInternalError(resp, nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp.WriteEntity(result)
|
||||
}
|
||||
|
||||
func (h *tenantHandler) CreateNamespace(request *restful.Request, response *restful.Response) {
|
||||
workspace := request.PathParameter("workspace")
|
||||
var namespace corev1.Namespace
|
||||
|
||||
@@ -27,7 +27,6 @@ import (
|
||||
eventsv1alpha1 "kubesphere.io/kubesphere/pkg/api/events/v1alpha1"
|
||||
loggingv1alpha2 "kubesphere.io/kubesphere/pkg/api/logging/v1alpha2"
|
||||
tenantv1alpha2 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha2"
|
||||
typesv1beta1 "kubesphere.io/kubesphere/pkg/apis/types/v1beta1"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/runtime"
|
||||
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
@@ -105,31 +104,37 @@ func AddToContainer(c *restful.Container, factory informers.InformerFactory, k8s
|
||||
To(handler.ListNamespaces).
|
||||
Param(ws.PathParameter("workspace", "workspace name")).
|
||||
Doc("List the namespaces for the current user").
|
||||
Returns(http.StatusOK, api.StatusOK, []corev1.Namespace{}).
|
||||
Returns(http.StatusOK, api.StatusOK, api.ListResult{}).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
|
||||
ws.Route(ws.GET("/federatednamespaces").
|
||||
To(handler.ListFederatedNamespaces).
|
||||
Param(ws.PathParameter("workspace", "workspace name")).
|
||||
Doc("List the federated namespaces for the current user").
|
||||
Returns(http.StatusOK, api.StatusOK, []typesv1beta1.FederatedNamespace{}).
|
||||
Returns(http.StatusOK, api.StatusOK, api.ListResult{}).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
|
||||
ws.Route(ws.GET("/workspaces/{workspace}/federatednamespaces").
|
||||
To(handler.ListFederatedNamespaces).
|
||||
Param(ws.PathParameter("workspace", "workspace name")).
|
||||
Doc("List the federated namespaces of the specified workspace for the current user").
|
||||
Returns(http.StatusOK, api.StatusOK, []typesv1beta1.FederatedNamespace{}).
|
||||
Returns(http.StatusOK, api.StatusOK, api.ListResult{}).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
|
||||
ws.Route(ws.GET("/workspaces/{workspace}/namespaces").
|
||||
To(handler.ListNamespaces).
|
||||
Param(ws.PathParameter("workspace", "workspace name")).
|
||||
Doc("List the namespaces of the specified workspace for the current user").
|
||||
Returns(http.StatusOK, api.StatusOK, []corev1.Namespace{}).
|
||||
Returns(http.StatusOK, api.StatusOK, api.ListResult{}).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
|
||||
ws.Route(ws.GET("/workspaces/{workspace}/devops").
|
||||
To(handler.ListDevOpsProjects).
|
||||
Param(ws.PathParameter("workspace", "workspace name")).
|
||||
Doc("List the devops projects of the specified workspace for the current user").
|
||||
Returns(http.StatusOK, api.StatusOK, api.ListResult{}).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
|
||||
ws.Route(ws.GET("/workspaces/{workspace}/namespaces/{namespace}").
|
||||
To(handler.DescribeNamespace).
|
||||
Param(ws.PathParameter("workspace", "workspace name")).
|
||||
Doc("Retrieve namespace details.").
|
||||
Returns(http.StatusOK, api.StatusOK, []corev1.Namespace{}).
|
||||
Returns(http.StatusOK, api.StatusOK, corev1.Namespace{}).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
|
||||
ws.Route(ws.DELETE("/workspaces/{workspace}/namespaces/{namespace}").
|
||||
To(handler.DeleteNamespace).
|
||||
@@ -142,20 +147,20 @@ func AddToContainer(c *restful.Container, factory informers.InformerFactory, k8s
|
||||
Param(ws.PathParameter("workspace", "workspace name")).
|
||||
Doc("List the namespaces of the specified workspace for the current user").
|
||||
Reads(corev1.Namespace{}).
|
||||
Returns(http.StatusOK, api.StatusOK, []corev1.Namespace{}).
|
||||
Returns(http.StatusOK, api.StatusOK, corev1.Namespace{}).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
|
||||
ws.Route(ws.PUT("/workspaces/{workspace}/namespaces/{namespace}").
|
||||
To(handler.UpdateNamespace).
|
||||
Param(ws.PathParameter("workspace", "workspace name")).
|
||||
Reads(corev1.Namespace{}).
|
||||
Returns(http.StatusOK, api.StatusOK, []corev1.Namespace{}).
|
||||
Returns(http.StatusOK, api.StatusOK, corev1.Namespace{}).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
|
||||
ws.Route(ws.PATCH("/workspaces/{workspace}/namespaces/{namespace}").
|
||||
To(handler.PatchNamespace).
|
||||
Consumes(mimePatch...).
|
||||
Param(ws.PathParameter("workspace", "workspace name")).
|
||||
Reads(corev1.Namespace{}).
|
||||
Returns(http.StatusOK, api.StatusOK, []corev1.Namespace{}).
|
||||
Returns(http.StatusOK, api.StatusOK, corev1.Namespace{}).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
|
||||
|
||||
ws.Route(ws.GET("/events").
|
||||
|
||||
@@ -70,8 +70,9 @@ type AccessManagementInterface interface {
|
||||
RemoveUserFromNamespace(username string, namespace string) error
|
||||
CreateClusterRoleBinding(username string, role string) error
|
||||
RemoveUserFromCluster(username string) error
|
||||
GetControlledNamespace(devops string) (string, error)
|
||||
GetControlledWorkspace(namespace string) (string, error)
|
||||
GetDevOpsRelatedNamespace(devops string) (string, error)
|
||||
GetNamespaceControlledWorkspace(namespace string) (string, error)
|
||||
GetDevOpsControlledWorkspace(devops string) (string, error)
|
||||
PatchNamespaceRole(namespace string, role *rbacv1.Role) (*rbacv1.Role, error)
|
||||
PatchClusterRole(clusterRole *rbacv1.ClusterRole) (*rbacv1.ClusterRole, error)
|
||||
}
|
||||
@@ -279,23 +280,19 @@ func (am *amOperator) ListGlobalRoleBindings(username string) ([]*iamv1alpha2.Gl
|
||||
}
|
||||
|
||||
func (am *amOperator) ListRoleBindings(username, namespace string) ([]*rbacv1.RoleBinding, error) {
|
||||
|
||||
roleBindings, err := am.resourceGetter.List(iamv1alpha2.ResourcesPluralRoleBinding, namespace, query.New())
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]*rbacv1.RoleBinding, 0)
|
||||
|
||||
for _, obj := range roleBindings.Items {
|
||||
roleBinding := obj.(*rbacv1.RoleBinding)
|
||||
if contains(roleBinding.Subjects, username) {
|
||||
result = append(result, roleBinding)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -964,7 +961,7 @@ func (am *amOperator) GetClusterRole(name string) (*rbacv1.ClusterRole, error) {
|
||||
}
|
||||
return obj.(*rbacv1.ClusterRole), nil
|
||||
}
|
||||
func (am *amOperator) GetControlledNamespace(devops string) (string, error) {
|
||||
func (am *amOperator) GetDevOpsRelatedNamespace(devops string) (string, error) {
|
||||
obj, err := am.resourceGetter.Get(devopsv1alpha3.ResourcePluralDevOpsProject, "", devops)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
@@ -975,7 +972,17 @@ func (am *amOperator) GetControlledNamespace(devops string) (string, error) {
|
||||
return devopsProject.Status.AdminNamespace, nil
|
||||
}
|
||||
|
||||
func (am *amOperator) GetControlledWorkspace(namespace string) (string, error) {
|
||||
func (am *amOperator) GetDevOpsControlledWorkspace(devops string) (string, error) {
|
||||
obj, err := am.resourceGetter.Get(devopsv1alpha3.ResourcePluralDevOpsProject, "", devops)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return "", err
|
||||
}
|
||||
devopsProject := obj.(*devopsv1alpha3.DevOpsProject)
|
||||
return devopsProject.Labels[tenantv1alpha1.WorkspaceLabel], nil
|
||||
}
|
||||
|
||||
func (am *amOperator) GetNamespaceControlledWorkspace(namespace string) (string, error) {
|
||||
obj, err := am.resourceGetter.Get("namespaces", "", namespace)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
|
||||
@@ -17,26 +17,109 @@ limitations under the License.
|
||||
package tenant
|
||||
|
||||
import (
|
||||
"kubesphere.io/kubesphere/pkg/models"
|
||||
"kubesphere.io/kubesphere/pkg/server/params"
|
||||
dsClient "kubesphere.io/kubesphere/pkg/simple/client/devops"
|
||||
"fmt"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/api"
|
||||
devopsv1alpha3 "kubesphere.io/kubesphere/pkg/apis/devops/v1alpha3"
|
||||
tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/query"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/request"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
resources "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3"
|
||||
)
|
||||
|
||||
type DevOpsProjectLister interface {
|
||||
ListDevOpsProjects(workspace string, conditions *params.Conditions, orderBy string, reverse bool, limit int, offset int) (*models.PageableResponse, error)
|
||||
}
|
||||
|
||||
type devopsProjectLister struct {
|
||||
dsProject dsClient.ProjectOperator
|
||||
}
|
||||
|
||||
func newProjectLister(client dsClient.ProjectOperator) DevOpsProjectLister {
|
||||
return &devopsProjectLister{
|
||||
dsProject: client,
|
||||
func (t *tenantOperator) ListDevOpsProjects(user user.Info, workspace string, queryParam *query.Query) (*api.ListResult, error) {
|
||||
scope := request.ClusterScope
|
||||
if workspace != "" {
|
||||
scope = request.WorkspaceScope
|
||||
}
|
||||
}
|
||||
|
||||
func (o *devopsProjectLister) ListDevOpsProjects(workspace string, conditions *params.Conditions, orderBy string, reverse bool, limit int, offset int) (*models.PageableResponse, error) {
|
||||
//TODO: @runzexia use informer to impl it
|
||||
return nil, nil
|
||||
listDevOps := authorizer.AttributesRecord{
|
||||
User: user,
|
||||
Verb: "list",
|
||||
Workspace: workspace,
|
||||
Resource: "devops",
|
||||
ResourceRequest: true,
|
||||
ResourceScope: scope,
|
||||
}
|
||||
|
||||
decision, _, err := t.authorizer.Authorize(listDevOps)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// allowed list devops in the specified scope
|
||||
if decision == authorizer.DecisionAllow {
|
||||
// filter by workspace
|
||||
if workspace != "" {
|
||||
queryParam.Filters[query.FieldLabel] = query.Value(fmt.Sprintf("%s=%s", tenantv1alpha1.WorkspaceLabel, workspace))
|
||||
}
|
||||
result, err := t.resourceGetter.List(devopsv1alpha3.ResourcePluralDevOpsProject, "", queryParam)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
roleBindings, err := t.am.ListRoleBindings(user.GetName(), "")
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
devopsProjects := make([]runtime.Object, 0)
|
||||
for _, roleBinding := range roleBindings {
|
||||
obj, err := t.resourceGetter.Get("namespaces", "", roleBinding.Namespace)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
namespace := obj.(*corev1.Namespace)
|
||||
controlledDevOpsProject := namespace.Labels[constants.DevOpsProjectLabelKey]
|
||||
// skip if not controlled by devops project
|
||||
if controlledDevOpsProject == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
devopsProject, err := t.resourceGetter.Get(devopsv1alpha3.ResourcePluralDevOpsProject, "", controlledDevOpsProject)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
klog.Warning("orphan devops project found ", devopsProject)
|
||||
continue
|
||||
}
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// skip if not controlled by the specified workspace
|
||||
if workspace != "" &&
|
||||
devopsProject.(*devopsv1alpha3.DevOpsProject).Labels[tenantv1alpha1.WorkspaceLabel] != workspace {
|
||||
continue
|
||||
}
|
||||
|
||||
if !contains(devopsProjects, devopsProject) {
|
||||
devopsProjects = append(devopsProjects, namespace)
|
||||
}
|
||||
}
|
||||
|
||||
result := resources.DefaultList(devopsProjects, queryParam, func(left runtime.Object, right runtime.Object, field query.Field) bool {
|
||||
return resources.DefaultObjectMetaCompare(left.(*devopsv1alpha3.DevOpsProject).ObjectMeta, right.(*devopsv1alpha3.DevOpsProject).ObjectMeta, field)
|
||||
}, func(object runtime.Object, filter query.Filter) bool {
|
||||
devopsProject := object.(*devopsv1alpha3.DevOpsProject).ObjectMeta
|
||||
if workspace != "" {
|
||||
if workspaceLabel, ok := devopsProject.Labels[tenantv1alpha1.WorkspaceLabel]; !ok || workspaceLabel != workspace {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return resources.DefaultObjectMetaFilter(devopsProject, filter)
|
||||
})
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ import (
|
||||
type Interface interface {
|
||||
ListWorkspaces(user user.Info, query *query.Query) (*api.ListResult, error)
|
||||
ListNamespaces(user user.Info, workspace string, query *query.Query) (*api.ListResult, error)
|
||||
ListDevOpsProjects(user user.Info, workspace string, query *query.Query) (*api.ListResult, error)
|
||||
ListFederatedNamespaces(info user.Info, workspace string, param *query.Query) (*api.ListResult, error)
|
||||
CreateNamespace(workspace string, namespace *corev1.Namespace) (*corev1.Namespace, error)
|
||||
CreateWorkspace(workspace *tenantv1alpha2.WorkspaceTemplate) (*tenantv1alpha2.WorkspaceTemplate, error)
|
||||
|
||||
Reference in New Issue
Block a user