From 70065d430de021edd4a9f1942148e18fee4c0502 Mon Sep 17 00:00:00 2001 From: hongming Date: Thu, 25 Oct 2018 14:55:16 +0800 Subject: [PATCH] refactor workspace api --- pkg/apis/v1alpha/iam/iam_handler.go | 6 +- .../v1alpha/monitoring/monitor_handler.go | 60 +- .../v1alpha/registries/registries_handler.go | 41 + pkg/apis/v1alpha/workspaces/workspaces.go | 85 +- pkg/client/prometheusclient.go | 33 +- .../controllers/clusterrole_bindings.go | 62 + pkg/models/controllers/clusterroles.go | 14 +- pkg/models/controllers/jobs.go | 114 +- pkg/models/controllers/namespaces.go | 6 +- pkg/models/controllers/pvcs.go | 6 +- pkg/models/controllers/role_bindings.go | 64 + pkg/models/controllers/roles.go | 4 +- pkg/models/controllers/run.go | 12 +- pkg/models/controllers/services.go | 2 +- pkg/models/controllers/types.go | 19 +- pkg/models/iam/iam.go | 386 ++++-- pkg/models/iam/policy.go | 1068 ++++++++--------- pkg/models/image_registries.go | 296 ++++- pkg/models/metrics/containers.go | 0 pkg/models/metrics/metrics_collector.go | 184 --- pkg/models/metrics/metrics_rule_tmpl.go | 147 --- pkg/models/metrics/metricscollector.go | 539 +++++++++ pkg/models/metrics/metricsconst.go | 236 ++++ .../{metrics_rule.go => metricsrule.go} | 9 +- .../{metrics_struct.go => metricsstruct.go} | 0 pkg/models/metrics/nodes.go | 0 pkg/models/metrics/pods.go | 0 pkg/models/resources.go | 26 +- pkg/models/workspaces/types.go | 5 +- pkg/models/workspaces/workspaces.go | 630 ++++++++-- 30 files changed, 2805 insertions(+), 1249 deletions(-) mode change 100644 => 100755 pkg/apis/v1alpha/monitoring/monitor_handler.go create mode 100644 pkg/models/controllers/clusterrole_bindings.go create mode 100644 pkg/models/controllers/role_bindings.go mode change 100644 => 100755 pkg/models/metrics/containers.go delete mode 100644 pkg/models/metrics/metrics_collector.go delete mode 100644 pkg/models/metrics/metrics_rule_tmpl.go create mode 100755 pkg/models/metrics/metricscollector.go create mode 100755 pkg/models/metrics/metricsconst.go rename pkg/models/metrics/{metrics_rule.go => metricsrule.go} (94%) mode change 100644 => 100755 rename pkg/models/metrics/{metrics_struct.go => metricsstruct.go} (100%) mode change 100644 => 100755 mode change 100644 => 100755 pkg/models/metrics/nodes.go mode change 100644 => 100755 pkg/models/metrics/pods.go diff --git a/pkg/apis/v1alpha/iam/iam_handler.go b/pkg/apis/v1alpha/iam/iam_handler.go index 38ca6c0ac..38a9e7aba 100644 --- a/pkg/apis/v1alpha/iam/iam_handler.go +++ b/pkg/apis/v1alpha/iam/iam_handler.go @@ -63,7 +63,7 @@ func userRolesHandler(req *restful.Request, resp *restful.Response) { username := req.PathParameter("username") - roles, err := iam.GetRoles(username) + roles, err := iam.GetRoles("", username) if err != nil { resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) @@ -206,7 +206,7 @@ func clusterRoleRulesHandler(req *restful.Request, resp *restful.Response) { var rules []iam.Rule if name == "" { - rules = iam.ClusterRoleRuleGroup + rules = iam.ClusterRoleRuleMapping } else { var err error rules, err = iam.GetClusterRoleRules(name) @@ -227,7 +227,7 @@ func roleRulesHandler(req *restful.Request, resp *restful.Response) { var rules []iam.Rule if namespace == "" && name == "" { - rules = iam.RoleRuleGroup + rules = iam.RoleRuleMapping } else { var err error rules, err = iam.GetRoleRules(namespace, name) diff --git a/pkg/apis/v1alpha/monitoring/monitor_handler.go b/pkg/apis/v1alpha/monitoring/monitor_handler.go old mode 100644 new mode 100755 index 6c08836e4..59725ee39 --- a/pkg/apis/v1alpha/monitoring/monitor_handler.go +++ b/pkg/apis/v1alpha/monitoring/monitor_handler.go @@ -46,6 +46,31 @@ func (u MonitorResource) monitorContainer(request *restful.Request, response *re } func (u MonitorResource) monitorWorkload(request *restful.Request, response *restful.Response) { + wlKind := request.PathParameter("workload_kind") + if strings.Trim(wlKind, " ") == "" { + // count all workloads figure + //metricName := "workload_count" + res := metrics.MonitorWorkloadCount(request) + response.WriteAsJson(res) + } else { + res := metrics.MonitorAllMetrics(request) + response.WriteAsJson(res) + } +} + +// merge multiple metric: all-devops, all-roles, all-projects...this api is designed for admin +func (u MonitorResource) monitorWorkspaceUserInfo(request *restful.Request, response *restful.Response) { + res := metrics.MonitorWorkspaceUserInfo(request) + response.WriteAsJson(res) +} + +// merge multiple metric: devops, roles, projects... +func (u MonitorResource) monitorWorkspaceResourceLevelMetrics(request *restful.Request, response *restful.Response) { + res := metrics.MonitorWorkspaceResourceLevelMetrics(request) + response.WriteAsJson(res) +} + +func (u MonitorResource) monitorWorkspacePodLevelMetrics(request *restful.Request, response *restful.Response) { res := metrics.MonitorAllMetrics(request) response.WriteAsJson(res) } @@ -196,10 +221,43 @@ func Register(ws *restful.WebService, subPath string) { Doc("monitor specific workload level metrics"). Param(ws.PathParameter("ns_name", "namespace").DataType("string").Required(true).DefaultValue("kube-system")). Param(ws.QueryParameter("metrics_filter", "metrics name cpu memory...").DataType("string").Required(false)). - Param(ws.PathParameter("workload_kind", "workload kind").DataType("string").Required(true).DefaultValue("daemonset")). + Param(ws.PathParameter("workload_kind", "workload kind").DataType("string").Required(false).DefaultValue("daemonset")). Param(ws.QueryParameter("workload_name", "workload name").DataType("string").Required(true).DefaultValue("")). Metadata(restfulspec.KeyOpenAPITags, tags)). Consumes(restful.MIME_JSON, restful.MIME_XML). Produces(restful.MIME_JSON) + ws.Route(ws.GET(subPath+"/namespaces/{ns_name}/workloads").To(u.monitorWorkload). + Filter(route.RouteLogging). + Doc("monitor all workload level metrics"). + Param(ws.PathParameter("ns_name", "namespace").DataType("string").Required(true).DefaultValue("kube-system")). + Param(ws.QueryParameter("metrics_filter", "metrics name cpu memory...").DataType("string").Required(false)). + Metadata(restfulspec.KeyOpenAPITags, tags)). + Consumes(restful.MIME_JSON, restful.MIME_XML). + Produces(restful.MIME_JSON) + + ws.Route(ws.GET(subPath+"/workspaces/{workspace_name}/pods").To(u.monitorWorkspacePodLevelMetrics). + Filter(route.RouteLogging). + Doc("monitor specific workspace level metrics"). + Param(ws.PathParameter("workspace_name", "workspace name").DataType("string").Required(true)). + Param(ws.QueryParameter("namespaces_filter", "namespaces filter").DataType("string").Required(false).DefaultValue("k.*")). + Param(ws.QueryParameter("metrics_filter", "metrics name cpu memory...").DataType("string").Required(false).DefaultValue("tenant_memory_utilisation_wo_cache")). + Metadata(restfulspec.KeyOpenAPITags, tags)). + Consumes(restful.MIME_JSON, restful.MIME_XML). + Produces(restful.MIME_JSON) + + ws.Route(ws.GET(subPath+"/workspaces/{workspace_name}").To(u.monitorWorkspaceResourceLevelMetrics). + Filter(route.RouteLogging). + Doc("monitor specific workspace level metrics"). + Param(ws.PathParameter("workspace_name", "workspace name").DataType("string").Required(true)). + Metadata(restfulspec.KeyOpenAPITags, tags)). + Consumes(restful.MIME_JSON, restful.MIME_XML). + Produces(restful.MIME_JSON) + + ws.Route(ws.GET(subPath+"/workspaces").To(u.monitorWorkspaceUserInfo). + Filter(route.RouteLogging). + Doc("monitor specific workspace level metrics"). + Metadata(restfulspec.KeyOpenAPITags, tags)). + Consumes(restful.MIME_JSON, restful.MIME_XML). + Produces(restful.MIME_JSON) } diff --git a/pkg/apis/v1alpha/registries/registries_handler.go b/pkg/apis/v1alpha/registries/registries_handler.go index c67026a67..5126290f8 100644 --- a/pkg/apis/v1alpha/registries/registries_handler.go +++ b/pkg/apis/v1alpha/registries/registries_handler.go @@ -55,6 +55,23 @@ func Register(ws *restful.WebService, subPath string) { Consumes(restful.MIME_JSON). Produces(restful.MIME_JSON) + ws.Route(ws.GET(subPath + "/{name}/namespaces/{namespace}/searchwords/{searchWord}"). + Param(ws.PathParameter("namespace", "registry secret's namespace")). + Param(ws.PathParameter("name", "registry secret's name")). + Param(ws.PathParameter("searchWord", "keyword use to search image")). + To(handlerImageSearch). + Filter(route.RouteLogging)). + Consumes(restful.MIME_JSON). + Produces(restful.MIME_JSON) + ws.Route(ws.GET(subPath + "/{name}/namespaces/{namespace}/tags"). + Param(ws.QueryParameter("image", "imageName")). + Param(ws.PathParameter("namespace", "registry secret's namespace")). + Param(ws.PathParameter("name", "registry secret's name")). + To(handlerGetImageTags). + Filter(route.RouteLogging)). + Consumes(restful.MIME_JSON). + Produces(restful.MIME_JSON) + } func handlerRegistryValidation(request *restful.Request, response *restful.Response) { @@ -77,6 +94,30 @@ func handlerRegistryValidation(request *restful.Request, response *restful.Respo } +func handlerImageSearch(request *restful.Request, response *restful.Response) { + + registry := request.PathParameter("name") + searchWord := request.PathParameter("searchWord") + namespace := request.PathParameter("namespace") + + res := models.ImageSearch(namespace, registry, searchWord) + + response.WriteEntity(res) + +} + +func handlerGetImageTags(request *restful.Request, response *restful.Response) { + + registry := request.PathParameter("name") + image := request.QueryParameter("image") + namespace := request.PathParameter("namespace") + + res := models.GetImageTags(namespace, registry, image) + + response.WriteEntity(res) + +} + func handleCreateRegistries(request *restful.Request, response *restful.Response) { registries := models.Registries{} diff --git a/pkg/apis/v1alpha/workspaces/workspaces.go b/pkg/apis/v1alpha/workspaces/workspaces.go index 7be20b7e4..24ccec2f7 100644 --- a/pkg/apis/v1alpha/workspaces/workspaces.go +++ b/pkg/apis/v1alpha/workspaces/workspaces.go @@ -16,18 +16,22 @@ import ( "kubesphere.io/kubesphere/pkg/models/workspaces" ) +const UserNameHeader = "X-Token-Username" + func Register(ws *restful.WebService, subPath string) { - ws.Route(ws.GET(subPath).To(WorkspaceListHandler)) + + ws.Route(ws.GET(subPath).To(UserWorkspaceListHandler)) ws.Route(ws.POST(subPath).To(WorkspaceCreateHandler)) ws.Route(ws.DELETE(subPath + "/{name}").To(DeleteWorkspaceHandler)) ws.Route(ws.GET(subPath + "/{name}").To(WorkspaceDetailHandler)) ws.Route(ws.PUT(subPath + "/{name}").To(WorkspaceEditHandler)) - ws.Route(ws.GET(subPath + "/{name}/namespaces").To(NamespaceHandler)) + ws.Route(ws.GET(subPath + "/{workspace}/namespaces").To(UserNamespaceListHandler)) ws.Route(ws.POST(subPath + "/{name}/namespaces").To(NamespaceCreateHandler)) ws.Route(ws.DELETE(subPath + "/{name}/namespaces/{namespace}").To(NamespaceDeleteHandler)) ws.Route(ws.GET(subPath + "/{name}/devops").To(DevOpsProjectHandler)) ws.Route(ws.POST(subPath + "/{name}/devops").To(DevOpsProjectCreateHandler)) ws.Route(ws.DELETE(subPath + "/{name}/devops/{id}").To(DevOpsProjectDeleteHandler)) + ws.Route(ws.GET(subPath + "/{name}/members").To(MembersHandler)) ws.Route(ws.GET(subPath + "/{name}/members/{member}").To(MemberHandler)) ws.Route(ws.GET(subPath + "/{name}/roles").To(RolesHandler)) @@ -107,6 +111,7 @@ func MemberHandler(req *restful.Request, resp *restful.Response) { } namespaces, err := workspaces.Namespaces(workspace) + if err != nil { resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) return @@ -169,14 +174,9 @@ func MembersRemoveHandler(req *restful.Request, resp *restful.Response) { func NamespaceDeleteHandler(req *restful.Request, resp *restful.Response) { namespace := req.PathParameter("namespace") workspace := req.PathParameter("name") - force := req.QueryParameter("force") - err := workspaces.UnBindNamespace(workspace, namespace) + //force := req.QueryParameter("force") - if err != nil && force != "true" { - resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) - return - } - err = workspaces.DeleteNamespace(namespace) + err := workspaces.DeleteNamespace(workspace, namespace) if err != nil { resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) @@ -190,7 +190,7 @@ func DevOpsProjectDeleteHandler(req *restful.Request, resp *restful.Response) { devops := req.PathParameter("id") workspace := req.PathParameter("name") force := req.QueryParameter("force") - username := req.HeaderParameter("X-Token-Username") + username := req.HeaderParameter(UserNameHeader) err := workspaces.UnBindDevopsProject(workspace, devops) @@ -212,7 +212,7 @@ func DevOpsProjectDeleteHandler(req *restful.Request, resp *restful.Response) { func DevOpsProjectCreateHandler(req *restful.Request, resp *restful.Response) { workspace := req.PathParameter("name") - username := req.HeaderParameter("X-Token-Username") + username := req.HeaderParameter(UserNameHeader) var devops workspaces.DevopsProject @@ -248,7 +248,7 @@ func DevOpsProjectCreateHandler(req *restful.Request, resp *restful.Response) { func NamespaceCreateHandler(req *restful.Request, resp *restful.Response) { workspace := req.PathParameter("name") - username := req.HeaderParameter("X-Token-Username") + username := req.HeaderParameter(UserNameHeader) namespace := &v1.Namespace{} @@ -266,6 +266,12 @@ func NamespaceCreateHandler(req *restful.Request, resp *restful.Response) { namespace.Annotations["creator"] = username namespace.Annotations["workspace"] = workspace + if namespace.Labels == nil { + namespace.Labels = make(map[string]string, 0) + } + + namespace.Labels["kubesphere.io/workspace"] = workspace + namespace, err = workspaces.CreateNamespace(namespace) if err != nil { @@ -273,14 +279,6 @@ func NamespaceCreateHandler(req *restful.Request, resp *restful.Response) { return } - err = workspaces.BindingNamespace(workspace, namespace.Name) - - if err != nil { - workspaces.DeleteNamespace(namespace.Name) - resp.WriteHeaderAndEntity(http.StatusBadRequest, constants.MessageResponse{Message: err.Error()}) - return - } - resp.WriteEntity(namespace) } @@ -298,22 +296,9 @@ func DevOpsProjectHandler(req *restful.Request, resp *restful.Response) { resp.WriteEntity(devOpsProjects) } -func NamespaceHandler(req *restful.Request, resp *restful.Response) { - - workspace := req.PathParameter("name") - - namespaces, err := workspaces.Namespaces(workspace) - - if err != nil { - resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) - return - } - - resp.WriteEntity(namespaces) -} func WorkspaceCreateHandler(req *restful.Request, resp *restful.Response) { var workspace workspaces.Workspace - username := req.HeaderParameter("X-Token-Username") + username := req.HeaderParameter(UserNameHeader) err := req.ReadEntity(&workspace) if err != nil { resp.WriteHeaderAndEntity(http.StatusBadRequest, constants.MessageResponse{Message: err.Error()}) @@ -327,7 +312,11 @@ func WorkspaceCreateHandler(req *restful.Request, resp *restful.Response) { workspace.Path = workspace.Name workspace.Members = nil - workspace.Creator = username + if workspace.Admin != "" { + workspace.Creator = workspace.Admin + } else { + workspace.Creator = username + } created, err := workspaces.Create(&workspace) @@ -411,15 +400,12 @@ func WorkspaceDetailHandler(req *restful.Request, resp *restful.Response) { resp.WriteEntity(workspace) } -func WorkspaceListHandler(req *restful.Request, resp *restful.Response) { +// List all workspaces for the current user +func UserWorkspaceListHandler(req *restful.Request, resp *restful.Response) { - var names []string + username := req.HeaderParameter(UserNameHeader) - if query := req.QueryParameter("name"); query != "" { - names = strings.Split(query, ",") - } - - list, err := workspaces.List(names) + list, err := workspaces.ListByUser(username) if err != nil { resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) @@ -428,3 +414,18 @@ func WorkspaceListHandler(req *restful.Request, resp *restful.Response) { resp.WriteEntity(list) } + +func UserNamespaceListHandler(req *restful.Request, resp *restful.Response) { + + username := req.HeaderParameter(UserNameHeader) + workspaceName := req.PathParameter("workspace") + + namespaces, err := workspaces.ListNamespaceByUser(workspaceName, username) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + resp.WriteEntity(namespaces) +} diff --git a/pkg/client/prometheusclient.go b/pkg/client/prometheusclient.go index 9bfc1b056..95992936f 100644 --- a/pkg/client/prometheusclient.go +++ b/pkg/client/prometheusclient.go @@ -17,6 +17,9 @@ import ( "net/http" "net/url" + "strconv" + "time" + "github.com/emicklei/go-restful" "github.com/golang/glog" "github.com/pkg/errors" @@ -28,6 +31,8 @@ const ( DefaultPrometheusPort = "9090" PrometheusApiPath = "/api/v1/" PrometheusEndpointUrl = DefaultScheme + "://" + DefaultPrometheusService + ":" + DefaultPrometheusPort + PrometheusApiPath + DefaultQueryStep = "10m" + DefaultQueryTimeout = "30s" ) var client = &http.Client{} @@ -79,14 +84,18 @@ func ParseRequestHeader(request *restful.Request) (url.Values, bool, error) { end := request.QueryParameter("end") step := request.QueryParameter("step") timeout := request.QueryParameter("timeout") + if timeout == "" { - timeout = "30s" + timeout = DefaultQueryTimeout + } + if step == "" { + step = DefaultQueryStep } // Whether query or query_range request u := url.Values{} - if start != "" && end != "" && step != "" { - u.Set("start", start) - u.Set("end", end) + if start != "" && end != "" { + u.Set("start", convertTimeGranularity(start)) + u.Set("end", convertTimeGranularity(end)) u.Set("step", step) u.Set("timeout", timeout) return u, true, nil @@ -101,6 +110,18 @@ func ParseRequestHeader(request *restful.Request) (url.Values, bool, error) { return u, false, nil } - glog.Error("Parse request failed", u) - return u, false, errors.Errorf("Parse request failed") + glog.Errorln("Parse request %s failed", u) + return u, false, errors.Errorf("Parse request time range %s failed", u) +} + +func convertTimeGranularity(ts string) string { + timeFloat, err := strconv.ParseFloat(ts, 64) + if err != nil { + glog.Errorf("convert second timestamp %s to minute timestamp failed", ts) + return strconv.FormatInt(int64(time.Now().Unix()), 10) + } + timeInt := int64(timeFloat) + // convert second timestamp to minute timestamp + secondTime := time.Unix(timeInt, 0).Truncate(time.Minute).Unix() + return strconv.FormatInt(secondTime, 10) } diff --git a/pkg/models/controllers/clusterrole_bindings.go b/pkg/models/controllers/clusterrole_bindings.go new file mode 100644 index 000000000..89bfd789f --- /dev/null +++ b/pkg/models/controllers/clusterrole_bindings.go @@ -0,0 +1,62 @@ +/* +Copyright 2018 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 controllers + +import ( + "time" + + "github.com/golang/glog" + "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/informers" +) + +func (ctl *ClusterRoleBindingCtl) Name() string { + return ctl.CommonAttribute.Name +} + +func (ctl *ClusterRoleBindingCtl) sync(stopChan chan struct{}) { + ctl.initListerAndInformer() + ctl.informer.Run(stopChan) +} + +func (ctl *ClusterRoleBindingCtl) total() int { + list, err := ctl.lister.List(labels.Everything()) + if err != nil { + glog.Errorf("count %s falied, reason:%s", err, ctl.Name()) + return 0 + } + return len(list) +} + +func (ctl *ClusterRoleBindingCtl) initListerAndInformer() { + informerFactory := informers.NewSharedInformerFactory(ctl.K8sClient, time.Second*resyncCircle) + ctl.lister = informerFactory.Rbac().V1().ClusterRoleBindings().Lister() + ctl.informer = informerFactory.Rbac().V1().ClusterRoleBindings().Informer() +} + +func (ctl *ClusterRoleBindingCtl) CountWithConditions(conditions string) int { + return 0 +} + +func (ctl *ClusterRoleBindingCtl) ListWithConditions(conditions string, paging *Paging, order string) (int, interface{}, error) { + return 0, nil, errors.New("not implement") +} + +func (ctl *ClusterRoleBindingCtl) Lister() interface{} { + return ctl.lister +} diff --git a/pkg/models/controllers/clusterroles.go b/pkg/models/controllers/clusterroles.go index bd74540ef..6e742637c 100644 --- a/pkg/models/controllers/clusterroles.go +++ b/pkg/models/controllers/clusterroles.go @@ -36,7 +36,7 @@ func (ctl *ClusterRoleCtl) generateObject(item v1.ClusterRole) *ClusterRole { } name := item.Name - if strings.HasPrefix(name, systemPrefix) { + if strings.HasPrefix(name, systemPrefix) || item.Annotations == nil || len(item.Annotations[creator]) == 0 { return nil } @@ -74,7 +74,9 @@ func (ctl *ClusterRoleCtl) sync(stopChan chan struct{}) { for _, item := range list { obj := ctl.generateObject(*item) if obj != nil { - db.Create(obj) + if err := db.Create(obj).Error; err != nil { + glog.Error("cluster roles sync error", err) + } } } @@ -111,14 +113,18 @@ func (ctl *ClusterRoleCtl) initListerAndInformer() { object := obj.(*v1.ClusterRole) mysqlObject := ctl.generateObject(*object) if mysqlObject != nil { - db.Create(mysqlObject) + if err := db.Create(mysqlObject).Error; err != nil { + glog.Error("cluster roles sync error", err) + } } }, UpdateFunc: func(old, new interface{}) { object := new.(*v1.ClusterRole) mysqlObject := ctl.generateObject(*object) if mysqlObject != nil { - db.Save(mysqlObject) + if err := db.Save(mysqlObject).Error; err != nil { + glog.Error("cluster roles update error", err) + } } }, DeleteFunc: func(obj interface{}) { diff --git a/pkg/models/controllers/jobs.go b/pkg/models/controllers/jobs.go index 9e93a1e2a..cb3f95a0d 100644 --- a/pkg/models/controllers/jobs.go +++ b/pkg/models/controllers/jobs.go @@ -29,11 +29,16 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/cache" + "reflect" + "strings" + "kubesphere.io/kubesphere/pkg/client" ) var k8sClient *kubernetes.Clientset +const retryTimes = 3 + func (ctl *JobCtl) generateObject(item v1.Job) *Job { var status, displayName string @@ -134,11 +139,13 @@ func (ctl *JobCtl) initListerAndInformer() { object := obj.(*v1.Job) mysqlObject := ctl.generateObject(*object) + ctl.makeRevision(object) db.Create(mysqlObject) }, UpdateFunc: func(old, new interface{}) { object := new.(*v1.Job) mysqlObject := ctl.generateObject(*object) + ctl.makeRevision(object) db.Save(mysqlObject) }, DeleteFunc: func(obj interface{}) { @@ -186,41 +193,41 @@ func getRevisions(job v1.Job) (JobRevisions, error) { err := json.Unmarshal([]byte(revisionsStr), &revisions) if err != nil { - glog.Errorf("failed to rerun job %s, reason: %s", err, err) - return nil, fmt.Errorf("failed to rerun job %s", job.Name) + return nil, fmt.Errorf("failed to get job %s's revisions, reason: %s", job.Name, err) } } return revisions, nil } -func getStatus(item *v1.Job) JobStatus { - var status JobStatus +func getCurrentRevision(item *v1.Job) JobRevision { + var revision JobRevision for _, condition := range item.Status.Conditions { if condition.Type == "Failed" && condition.Status == "True" { - status.Status = Failed - status.Reasons = append(status.Reasons, condition.Reason) - status.Messages = append(status.Messages, condition.Message) + revision.Status = Failed + revision.Reasons = append(revision.Reasons, condition.Reason) + revision.Messages = append(revision.Messages, condition.Message) } if condition.Type == "Complete" && condition.Status == "True" { - status.Status = Completed + revision.Status = Completed } } - if len(status.Status) == 0 { - status.Status = Unfinished + if len(revision.Status) == 0 { + revision.Status = Running } - status.DesirePodNum = *item.Spec.Completions - status.Succeed = item.Status.Succeeded - status.Failed = item.Status.Failed - status.StartTime = item.Status.StartTime.Time + revision.DesirePodNum = *item.Spec.Completions + revision.Succeed = item.Status.Succeeded + revision.Failed = item.Status.Failed + revision.StartTime = item.CreationTimestamp.Time + revision.Uid = string(item.UID) if item.Status.CompletionTime != nil { - status.CompletionTime = item.Status.CompletionTime.Time + revision.CompletionTime = item.Status.CompletionTime.Time } - return status + return revision } func deleteJob(namespace, job string) error { @@ -229,46 +236,81 @@ func deleteJob(namespace, job string) error { return err } +func (ctl *JobCtl) makeRevision(job *v1.Job) { + revisionIndex := -1 + revisions, err := getRevisions(*job) + + if err != nil { + glog.Error(err) + return + } + + uid := job.UID + for index, revision := range revisions { + if revision.Uid == string(uid) { + currentRevision := getCurrentRevision(job) + if reflect.DeepEqual(currentRevision, revision) { + return + } else { + revisionIndex = index + break + } + } + } + + if revisionIndex == -1 { + revisionIndex = len(revisions) + 1 + } + + revisions[revisionIndex] = getCurrentRevision(job) + + revisionsByte, err := json.Marshal(revisions) + if err != nil { + glog.Error(err) + } + + if job.Annotations == nil { + job.Annotations = make(map[string]string) + } + + job.Annotations["revisions"] = string(revisionsByte) + ctl.K8sClient.BatchV1().Jobs(job.Namespace).Update(job) + +} + func JobReRun(namespace, jobName string) (string, error) { k8sClient = client.NewK8sClient() job, err := k8sClient.BatchV1().Jobs(namespace).Get(jobName, metav1.GetOptions{}) if err != nil { return "", err } + newJob := *job newJob.ResourceVersion = "" newJob.Status = v1.JobStatus{} newJob.ObjectMeta.UID = "" + newJob.Annotations["revisions"] = strings.Replace(job.Annotations["revisions"], Running, Unfinished, -1) + delete(newJob.Spec.Selector.MatchLabels, "controller-uid") delete(newJob.Spec.Template.ObjectMeta.Labels, "controller-uid") - revisions, err := getRevisions(*job) - + err = deleteJob(namespace, jobName) if err != nil { - return "", err - } - - index := len(revisions) + 1 - value := getStatus(job) - revisions[index] = value - - revisionsByte, err := json.Marshal(revisions) - if err != nil { - glog.Errorf("failed to rerun job %s, reason: %s", err, err) + glog.Errorf("failed to rerun job %s, reason: %s", jobName, err) return "", fmt.Errorf("failed to rerun job %s", jobName) } - newJob.Annotations["revisions"] = string(revisionsByte) - - err = deleteJob(job.Namespace, job.Name) - if err != nil { - glog.Errorf("failed to rerun job %s, reason: %s", err, err) - return "", fmt.Errorf("failed to rerun job %s", jobName) + for i := 0; i < retryTimes; i++ { + _, err = k8sClient.BatchV1().Jobs(namespace).Create(&newJob) + if err != nil { + time.Sleep(time.Second) + continue + } + break } - _, err = k8sClient.BatchV1().Jobs(namespace).Create(&newJob) if err != nil { - glog.Errorf("failed to rerun job %s, reason: %s", err, err) + glog.Errorf("failed to rerun job %s, reason: %s", jobName, err) return "", fmt.Errorf("failed to rerun job %s", jobName) } diff --git a/pkg/models/controllers/namespaces.go b/pkg/models/controllers/namespaces.go index a3ecf902c..88355264b 100644 --- a/pkg/models/controllers/namespaces.go +++ b/pkg/models/controllers/namespaces.go @@ -147,9 +147,9 @@ func (ctl *NamespaceCtl) createDefaultRoleBinding(ns, user string) error { } func (ctl *NamespaceCtl) createDefaultRole(ns string) error { - adminRole := &rbac.Role{ObjectMeta: metaV1.ObjectMeta{Name: admin, Namespace: ns}, Rules: adminRules} - editorRole := &rbac.Role{ObjectMeta: metaV1.ObjectMeta{Name: editor, Namespace: ns}, Rules: editorRules} - viewerRole := &rbac.Role{ObjectMeta: metaV1.ObjectMeta{Name: viewer, Namespace: ns}, Rules: viewerRules} + adminRole := &rbac.Role{ObjectMeta: metaV1.ObjectMeta{Name: admin, Namespace: ns, Annotations: map[string]string{"creator": "system"}}, Rules: adminRules} + editorRole := &rbac.Role{ObjectMeta: metaV1.ObjectMeta{Name: editor, Namespace: ns, Annotations: map[string]string{"creator": "system"}}, Rules: editorRules} + viewerRole := &rbac.Role{ObjectMeta: metaV1.ObjectMeta{Name: viewer, Namespace: ns, Annotations: map[string]string{"creator": "system"}}, Rules: viewerRules} _, err := ctl.K8sClient.RbacV1().Roles(ns).Create(adminRole) diff --git a/pkg/models/controllers/pvcs.go b/pkg/models/controllers/pvcs.go index 38a20dfc8..09e794888 100644 --- a/pkg/models/controllers/pvcs.go +++ b/pkg/models/controllers/pvcs.go @@ -38,8 +38,12 @@ func (ctl *PvcCtl) generateObject(item *v1.PersistentVolumeClaim) *Pvc { name := item.Name namespace := item.Namespace - status := fmt.Sprintf("%s", item.Status.Phase) createTime := item.CreationTimestamp.Time + status := fmt.Sprintf("%s", item.Status.Phase) + if item.DeletionTimestamp != nil { + status = "Terminating" + } + var capacity, storageClass, accessModeStr string if createTime.IsZero() { diff --git a/pkg/models/controllers/role_bindings.go b/pkg/models/controllers/role_bindings.go new file mode 100644 index 000000000..3de5dafda --- /dev/null +++ b/pkg/models/controllers/role_bindings.go @@ -0,0 +1,64 @@ +/* +Copyright 2018 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 controllers + +import ( + "time" + + "github.com/golang/glog" + "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/informers" +) + +func (ctl *RoleBindingCtl) Name() string { + return ctl.CommonAttribute.Name +} + +func (ctl *RoleBindingCtl) sync(stopChan chan struct{}) { + ctl.initListerAndInformer() + ctl.informer.Run(stopChan) +} + +func (ctl *RoleBindingCtl) total() int { + list, err := ctl.lister.List(labels.Everything()) + if err != nil { + glog.Errorf("count %s falied, reason:%s", err, ctl.Name()) + return 0 + } + return len(list) +} + +func (ctl *RoleBindingCtl) initListerAndInformer() { + + informerFactory := informers.NewSharedInformerFactory(ctl.K8sClient, time.Second*resyncCircle) + + ctl.lister = informerFactory.Rbac().V1().RoleBindings().Lister() + ctl.informer = informerFactory.Rbac().V1().RoleBindings().Informer() +} + +func (ctl *RoleBindingCtl) CountWithConditions(conditions string) int { + return 0 +} + +func (ctl *RoleBindingCtl) ListWithConditions(conditions string, paging *Paging, order string) (int, interface{}, error) { + return 0, nil, errors.New("not implement") +} + +func (ctl *RoleBindingCtl) Lister() interface{} { + return ctl.lister +} diff --git a/pkg/models/controllers/roles.go b/pkg/models/controllers/roles.go index 8116ae5f4..bf294648f 100644 --- a/pkg/models/controllers/roles.go +++ b/pkg/models/controllers/roles.go @@ -30,12 +30,12 @@ import ( func (ctl *RoleCtl) generateObject(item v1.Role) *Role { var displayName string - if item.Annotations != nil && len(item.Annotations[DisplayName]) > 0 { + if item.Annotations != nil && len(item.Annotations[DisplayName]) == 0 { displayName = item.Annotations[DisplayName] } name := item.Name - if strings.HasPrefix(name, systemPrefix) { + if strings.HasPrefix(name, systemPrefix) || item.Annotations == nil || len(item.Annotations[creator]) == 0 { return nil } namespace := item.Namespace diff --git a/pkg/models/controllers/run.go b/pkg/models/controllers/run.go index 9659d65af..f65d8e950 100644 --- a/pkg/models/controllers/run.go +++ b/pkg/models/controllers/run.go @@ -37,7 +37,7 @@ type resourceControllers struct { var ResourceControllers resourceControllers -func (rec *resourceControllers) runContoller(name string, stopChan chan struct{}, wg *sync.WaitGroup) { +func (rec *resourceControllers) runController(name string, stopChan chan struct{}, wg *sync.WaitGroup) { var ctl Controller attr := CommonAttribute{DB: client.NewDBClient(), K8sClient: rec.k8sClient, stopChan: stopChan, aliveChan: make(chan struct{}), Name: name} @@ -78,6 +78,10 @@ func (rec *resourceControllers) runContoller(name string, stopChan chan struct{} ctl = &ConfigMapCtl{CommonAttribute: attr} case Secrets: ctl = &SecretCtl{CommonAttribute: attr} + case ClusterRoleBindings: + ctl = &ClusterRoleBindingCtl{CommonAttribute: attr} + case RoleBindings: + ctl = &RoleBindingCtl{CommonAttribute: attr} default: return } @@ -116,9 +120,9 @@ func Run(stopChan chan struct{}, wg *sync.WaitGroup) { ResourceControllers = resourceControllers{k8sClient: k8sClient, Controllers: make(map[string]Controller)} for _, item := range []string{Deployments, Statefulsets, Daemonsets, PersistentVolumeClaim, Pods, Services, - Ingresses, Roles, ClusterRoles, Namespaces, StorageClasses, Jobs, Cronjobs, Nodes, Replicasets, + Ingresses, Roles, RoleBindings, ClusterRoles, ClusterRoleBindings, Namespaces, StorageClasses, Jobs, Cronjobs, Nodes, Replicasets, ControllerRevisions, ConfigMaps, Secrets} { - ResourceControllers.runContoller(item, stopChan, wg) + ResourceControllers.runController(item, stopChan, wg) } go dbHealthCheck(client.NewDBClient()) @@ -131,7 +135,7 @@ func Run(stopChan chan struct{}, wg *sync.WaitGroup) { case _, isClose := <-controller.chanAlive(): if !isClose { glog.Errorf("controller %s have stopped, restart it", ctlName) - ResourceControllers.runContoller(ctlName, stopChan, wg) + ResourceControllers.runController(ctlName, stopChan, wg) } default: time.Sleep(3 * time.Second) diff --git a/pkg/models/controllers/services.go b/pkg/models/controllers/services.go index c07755f4d..33ec7261d 100644 --- a/pkg/models/controllers/services.go +++ b/pkg/models/controllers/services.go @@ -95,7 +95,7 @@ func generateSvcObject(item v1.Service) *Service { createTime = time.Now() } - if len(item.Spec.ClusterIP) == 0 { + if len(item.Spec.ClusterIP) == 0 || item.Spec.ClusterIP == "None" { if len(item.Spec.Selector) == 0 { serviceType = "Headless(Selector)" } diff --git a/pkg/models/controllers/types.go b/pkg/models/controllers/types.go index 35ef692a2..03f1fd5d5 100644 --- a/pkg/models/controllers/types.go +++ b/pkg/models/controllers/types.go @@ -49,6 +49,7 @@ const ( Warning = "warning" Error = "error" DisplayName = "displayName" + creator = "creator" Pods = "pods" Deployments = "deployments" @@ -58,7 +59,9 @@ const ( Ingresses = "ingresses" PersistentVolumeClaim = "persistent-volume-claims" Roles = "roles" + RoleBindings = "role-bindings" ClusterRoles = "cluster-roles" + ClusterRoleBindings = "cluster-role-bindings" Services = "services" StorageClasses = "storage-classes" Applications = "applications" @@ -284,15 +287,16 @@ type StorageClass struct { Provisioner string `json:"provisioner"` } -type JobRevisions map[int]JobStatus +type JobRevisions map[int]JobRevision -type JobStatus struct { +type JobRevision struct { Status string `json:"status"` Reasons []string `json:"reasons"` Messages []string `json:"messages"` Succeed int32 `json:"succeed"` DesirePodNum int32 `json:"desire"` Failed int32 `json:"failed"` + Uid string `json:"uid"` StartTime time.Time `json:"start-time"` CompletionTime time.Time `json:"completion-time"` } @@ -462,6 +466,17 @@ type ClusterRoleCtl struct { CommonAttribute } +type ClusterRoleBindingCtl struct { + lister rbacV1.ClusterRoleBindingLister + informer cache.SharedIndexInformer + CommonAttribute +} +type RoleBindingCtl struct { + lister rbacV1.RoleBindingLister + informer cache.SharedIndexInformer + CommonAttribute +} + type JobCtl struct { lister batchv1.JobLister informer cache.SharedIndexInformer diff --git a/pkg/models/iam/iam.go b/pkg/models/iam/iam.go index d0c6b7603..e14235374 100644 --- a/pkg/models/iam/iam.go +++ b/pkg/models/iam/iam.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "io/ioutil" - "log" "net/http" "strings" @@ -12,10 +11,12 @@ import ( "k8s.io/api/rbac/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/util/slice" + "k8s.io/apimachinery/pkg/labels" + v12 "k8s.io/client-go/listers/rbac/v1" "kubesphere.io/kubesphere/pkg/client" "kubesphere.io/kubesphere/pkg/constants" + "kubesphere.io/kubesphere/pkg/models/controllers" ksErr "kubesphere.io/kubesphere/pkg/util/errors" ) @@ -133,7 +134,7 @@ func WorkspaceRoleRules(workspace string, roleName string) (*v1.ClusterRole, []R rule := Rule{Name: WorkspaceRoleRuleMapping[i].Name} rule.Actions = make([]Action, 0) for j := 0; j < len(WorkspaceRoleRuleMapping[i].Actions); j++ { - if actionValidate(role.Rules, WorkspaceRoleRuleMapping[i].Actions[j]) { + if rulesMatchesAction(role.Rules, WorkspaceRoleRuleMapping[i].Actions[j]) { rule.Actions = append(rule.Actions, WorkspaceRoleRuleMapping[i].Actions[j]) } } @@ -161,18 +162,22 @@ func GetUserNamespaces(username string, requiredRule v1.PolicyRule) (allNamespac } if requiredRule.Size() == 0 { - if ruleValidate(clusterRules, v1.PolicyRule{ - Verbs: []string{"get", "list"}, - APIGroups: []string{""}, - Resources: []string{"namespaces"}, + if RulesMatchesRequired(clusterRules, v1.PolicyRule{ + Verbs: []string{"get"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"workspaces/namespaces"}, }) { return true, nil, nil } - } else if ruleValidate(clusterRules, requiredRule) { - return true, nil, nil + } else { + + if RulesMatchesRequired(clusterRules, requiredRule) { + return true, nil, nil + } + } - roles, err := GetRoles(username) + roles, err := GetRoles("", username) if err != nil { return false, nil, err @@ -192,7 +197,7 @@ func GetUserNamespaces(username string, requiredRule v1.PolicyRule) (allNamespac namespaces = make([]string, 0) for namespace, rules := range rulesMapping { - if requiredRule.Size() == 0 || ruleValidate(rules, requiredRule) { + if requiredRule.Size() == 0 || RulesMatchesRequired(rules, requiredRule) { namespaces = append(namespaces, namespace) } } @@ -309,22 +314,24 @@ func GetClusterRole(name string) (*v1.ClusterRole, error) { return role, nil } -func GetRoles(username string) ([]v1.Role, error) { - k8s := client.NewK8sClient() +func GetRoles(namespace string, username string) ([]v1.Role, error) { + roleBindingLister := controllers.ResourceControllers.Controllers[controllers.RoleBindings].Lister().(v12.RoleBindingLister) + roleLister := controllers.ResourceControllers.Controllers[controllers.Roles].Lister().(v12.RoleLister) + clusterRoleLister := controllers.ResourceControllers.Controllers[controllers.ClusterRoles].Lister().(v12.ClusterRoleLister) - roleBindings, err := k8s.RbacV1().RoleBindings("").List(meta_v1.ListOptions{}) + roleBindings, err := roleBindingLister.RoleBindings(namespace).List(labels.Everything()) if err != nil { return nil, err } roles := make([]v1.Role, 0) - for _, roleBinding := range roleBindings.Items { + for _, roleBinding := range roleBindings { for _, subject := range roleBinding.Subjects { if subject.Kind == v1.UserKind && subject.Name == username { if roleBinding.RoleRef.Kind == ClusterRoleKind { - clusterRole, err := k8s.RbacV1().ClusterRoles().Get(roleBinding.RoleRef.Name, meta_v1.GetOptions{}) + clusterRole, err := clusterRoleLister.Get(roleBinding.RoleRef.Name) if err == nil { var role = v1.Role{TypeMeta: (*clusterRole).TypeMeta, ObjectMeta: (*clusterRole).ObjectMeta, Rules: (*clusterRole).Rules} role.Namespace = roleBinding.Namespace @@ -339,7 +346,7 @@ func GetRoles(username string) ([]v1.Role, error) { } else { if subject.Kind == v1.UserKind && subject.Name == username { - rule, err := k8s.RbacV1().Roles(roleBinding.Namespace).Get(roleBinding.RoleRef.Name, meta_v1.GetOptions{}) + rule, err := roleLister.Roles(roleBinding.Namespace).Get(roleBinding.RoleRef.Name) if err == nil { roles = append(roles, *rule) break @@ -361,10 +368,12 @@ func GetRoles(username string) ([]v1.Role, error) { return roles, nil } +// Get cluster roles by username func GetClusterRoles(username string) ([]v1.ClusterRole, error) { - k8s := client.NewK8sClient() - - clusterRoleBindings, err := k8s.RbacV1().ClusterRoleBindings().List(meta_v1.ListOptions{}) + //TODO fix NPE + clusterRoleBindingLister := controllers.ResourceControllers.Controllers[controllers.ClusterRoleBindings].Lister().(v12.ClusterRoleBindingLister) + clusterRoleLister := controllers.ResourceControllers.Controllers[controllers.ClusterRoles].Lister().(v12.ClusterRoleLister) + clusterRoleBindings, err := clusterRoleBindingLister.List(labels.Everything()) if err != nil { return nil, err @@ -372,27 +381,24 @@ func GetClusterRoles(username string) ([]v1.ClusterRole, error) { roles := make([]v1.ClusterRole, 0) - for _, roleBinding := range clusterRoleBindings.Items { + for _, roleBinding := range clusterRoleBindings { for _, subject := range roleBinding.Subjects { if subject.Kind == v1.UserKind && subject.Name == username { if roleBinding.RoleRef.Kind == ClusterRoleKind { - role, err := k8s.RbacV1().ClusterRoles().Get(roleBinding.RoleRef.Name, meta_v1.GetOptions{}) + role, err := clusterRoleLister.Get(roleBinding.RoleRef.Name) if err == nil { if role.Annotations == nil { role.Annotations = make(map[string]string, 0) } - role.Annotations["rbac.authorization.k8s.io/clusterrolebinding"] = roleBinding.Name - if roleBinding.Annotations != nil && roleBinding.Annotations["rbac.authorization.k8s.io/clusterrole"] == roleBinding.RoleRef.Name { role.Annotations["rbac.authorization.k8s.io/clusterrole"] = "true" } - roles = append(roles, *role) break } else if apierrors.IsNotFound(err) { - log.Println(err) + glog.Warning(err) break } else { return nil, err @@ -405,80 +411,80 @@ func GetClusterRoles(username string) ([]v1.ClusterRole, error) { return roles, nil } -func ruleValidate(rules []v1.PolicyRule, rule v1.PolicyRule) bool { +//func RuleValidate(rules []v1.PolicyRule, rule v1.PolicyRule) bool { +// +// for _, apiGroup := range rule.APIGroups { +// if len(rule.NonResourceURLs) == 0 { +// for _, resource := range rule.Resources { +// +// //if len(Rule.ResourceNames) == 0 { +// +// for _, verb := range rule.Verbs { +// if !verbValidate(rules, apiGroup, "", resource, "", verb) { +// return false +// } +// } +// +// //} else { +// // for _, resourceName := range Rule.ResourceNames { +// // for _, verb := range Rule.Verbs { +// // if !verbValidate(rules, apiGroup, "", resource, resourceName, verb) { +// // return false +// // } +// // } +// // } +// //} +// } +// } else { +// for _, nonResourceURL := range rule.NonResourceURLs { +// for _, verb := range rule.Verbs { +// if !verbValidate(rules, apiGroup, nonResourceURL, "", "", verb) { +// return false +// } +// } +// } +// } +// } +// return true +//} - for _, apiGroup := range rule.APIGroups { - if len(rule.NonResourceURLs) == 0 { - for _, resource := range rule.Resources { - - //if len(Rule.ResourceNames) == 0 { - - for _, verb := range rule.Verbs { - if !verbValidate(rules, apiGroup, "", resource, "", verb) { - return false - } - } - - //} else { - // for _, resourceName := range Rule.ResourceNames { - // for _, verb := range Rule.Verbs { - // if !verbValidate(rules, apiGroup, "", resource, resourceName, verb) { - // return false - // } - // } - // } - //} - } - } else { - for _, nonResourceURL := range rule.NonResourceURLs { - for _, verb := range rule.Verbs { - if !verbValidate(rules, apiGroup, nonResourceURL, "", "", verb) { - return false - } - } - } - } - } - return true -} - -func verbValidate(rules []v1.PolicyRule, apiGroup string, nonResourceURL string, resource string, resourceName string, verb string) bool { - for _, rule := range rules { - - if nonResourceURL == "" { - if slice.ContainsString(rule.APIGroups, apiGroup, nil) || - slice.ContainsString(rule.APIGroups, v1.APIGroupAll, nil) { - if slice.ContainsString(rule.Verbs, verb, nil) || - slice.ContainsString(rule.Verbs, v1.VerbAll, nil) { - if slice.ContainsString(rule.Resources, v1.ResourceAll, nil) { - return true - } else if slice.ContainsString(rule.Resources, resource, nil) { - if len(rule.ResourceNames) > 0 { - if slice.ContainsString(rule.ResourceNames, resourceName, nil) { - return true - } - } else if resourceName == "" { - return true - } - } - } - } - - } else if slice.ContainsString(rule.NonResourceURLs, nonResourceURL, nil) || - slice.ContainsString(rule.NonResourceURLs, v1.NonResourceAll, nil) { - if slice.ContainsString(rule.Verbs, verb, nil) || - slice.ContainsString(rule.Verbs, v1.VerbAll, nil) { - return true - } - } - } - return false -} +//func verbValidate(rules []v1.PolicyRule, apiGroup string, nonResourceURL string, resource string, resourceName string, verb string) bool { +// for _, rule := range rules { +// +// if nonResourceURL == "" { +// if slice.ContainsString(rule.APIGroups, apiGroup, nil) || +// slice.ContainsString(rule.APIGroups, v1.APIGroupAll, nil) { +// if slice.ContainsString(rule.Verbs, verb, nil) || +// slice.ContainsString(rule.Verbs, v1.VerbAll, nil) { +// if slice.ContainsString(rule.Resources, v1.ResourceAll, nil) { +// return true +// } else if slice.ContainsString(rule.Resources, resource, nil) { +// if len(rule.ResourceNames) > 0 { +// if slice.ContainsString(rule.ResourceNames, resourceName, nil) { +// return true +// } +// } else if resourceName == "" { +// return true +// } +// } +// } +// } +// +// } else if slice.ContainsString(rule.NonResourceURLs, nonResourceURL, nil) || +// slice.ContainsString(rule.NonResourceURLs, v1.NonResourceAll, nil) { +// if slice.ContainsString(rule.Verbs, verb, nil) || +// slice.ContainsString(rule.Verbs, v1.VerbAll, nil) { +// return true +// } +// } +// } +// return false +//} func GetUserRules(username string) (map[string][]Rule, error) { items := make(map[string][]Rule, 0) - userRoles, err := GetRoles(username) + userRoles, err := GetRoles("", username) if err != nil { return nil, err @@ -508,12 +514,12 @@ func GetUserRules(username string) (map[string][]Rule, error) { func convertToRules(policyRules []v1.PolicyRule) []Rule { rules := make([]Rule, 0) - for i := 0; i < (len(RoleRuleGroup)); i++ { - rule := Rule{Name: RoleRuleGroup[i].Name} + for i := 0; i < (len(RoleRuleMapping)); i++ { + rule := Rule{Name: RoleRuleMapping[i].Name} rule.Actions = make([]Action, 0) - for j := 0; j < (len(RoleRuleGroup[i].Actions)); j++ { - if actionValidate(policyRules, RoleRuleGroup[i].Actions[j]) { - rule.Actions = append(rule.Actions, RoleRuleGroup[i].Actions[j]) + for j := 0; j < (len(RoleRuleMapping[i].Actions)); j++ { + if rulesMatchesAction(policyRules, RoleRuleMapping[i].Actions[j]) { + rule.Actions = append(rule.Actions, RoleRuleMapping[i].Actions[j]) } } @@ -541,12 +547,12 @@ func GetUserClusterRules(username string) ([]Rule, error) { clusterRules = append(clusterRules, role.Rules...) } - for i := 0; i < (len(ClusterRoleRuleGroup)); i++ { - rule := Rule{Name: ClusterRoleRuleGroup[i].Name} + for i := 0; i < (len(ClusterRoleRuleMapping)); i++ { + rule := Rule{Name: ClusterRoleRuleMapping[i].Name} rule.Actions = make([]Action, 0) - for j := 0; j < (len(ClusterRoleRuleGroup[i].Actions)); j++ { - if actionValidate(clusterRules, ClusterRoleRuleGroup[i].Actions[j]) { - rule.Actions = append(rule.Actions, ClusterRoleRuleGroup[i].Actions[j]) + for j := 0; j < (len(ClusterRoleRuleMapping[i].Actions)); j++ { + if rulesMatchesAction(clusterRules, ClusterRoleRuleMapping[i].Actions[j]) { + rule.Actions = append(rule.Actions, ClusterRoleRuleMapping[i].Actions[j]) } } if len(rule.Actions) > 0 { @@ -567,12 +573,12 @@ func GetClusterRoleRules(name string) ([]Rule, error) { rules := make([]Rule, 0) - for i := 0; i < len(ClusterRoleRuleGroup); i++ { - rule := Rule{Name: ClusterRoleRuleGroup[i].Name} + for i := 0; i < len(ClusterRoleRuleMapping); i++ { + rule := Rule{Name: ClusterRoleRuleMapping[i].Name} rule.Actions = make([]Action, 0) - for j := 0; j < (len(ClusterRoleRuleGroup[i].Actions)); j++ { - if actionValidate(clusterRole.Rules, ClusterRoleRuleGroup[i].Actions[j]) { - rule.Actions = append(rule.Actions, ClusterRoleRuleGroup[i].Actions[j]) + for j := 0; j < (len(ClusterRoleRuleMapping[i].Actions)); j++ { + if rulesMatchesAction(clusterRole.Rules, ClusterRoleRuleMapping[i].Actions[j]) { + rule.Actions = append(rule.Actions, ClusterRoleRuleMapping[i].Actions[j]) } } if len(rule.Actions) > 0 { @@ -590,12 +596,12 @@ func GetRoleRules(namespace string, name string) ([]Rule, error) { } rules := make([]Rule, 0) - for i := 0; i < len(RoleRuleGroup); i++ { - rule := Rule{Name: RoleRuleGroup[i].Name} + for i := 0; i < len(RoleRuleMapping); i++ { + rule := Rule{Name: RoleRuleMapping[i].Name} rule.Actions = make([]Action, 0) - for j := 0; j < len(RoleRuleGroup[i].Actions); j++ { - if actionValidate(role.Rules, RoleRuleGroup[i].Actions[j]) { - rule.Actions = append(rule.Actions, RoleRuleGroup[i].Actions[j]) + for j := 0; j < len(RoleRuleMapping[i].Actions); j++ { + if rulesMatchesAction(role.Rules, RoleRuleMapping[i].Actions[j]) { + rule.Actions = append(rule.Actions, RoleRuleMapping[i].Actions[j]) } } if len(rule.Actions) > 0 { @@ -605,11 +611,157 @@ func GetRoleRules(namespace string, name string) ([]Rule, error) { return rules, nil } -func actionValidate(rules []v1.PolicyRule, action Action) bool { +func rulesMatchesAction(rules []v1.PolicyRule, action Action) bool { + for _, rule := range action.Rules { - if !ruleValidate(rules, rule) { + if !RulesMatchesRequired(rules, rule) { return false } } return true } + +func RulesMatchesRequired(rules []v1.PolicyRule, required v1.PolicyRule) bool { + for _, rule := range rules { + if ruleMatchesRequired(rule, required) { + return true + } + } + return false +} + +func ruleMatchesRequired(rule v1.PolicyRule, required v1.PolicyRule) bool { + + if len(required.NonResourceURLs) == 0 { + for _, apiGroup := range required.APIGroups { + for _, resource := range required.Resources { + resources := strings.Split(resource, "/") + resource = resources[0] + var subsource string + if len(resources) > 1 { + subsource = resources[1] + } + + if len(required.ResourceNames) == 0 { + for _, verb := range required.Verbs { + if !ruleMatchesRequest(rule, apiGroup, "", resource, subsource, "", verb) { + return false + } + } + } else { + for _, resourceName := range required.ResourceNames { + for _, verb := range required.Verbs { + if !ruleMatchesRequest(rule, apiGroup, "", resource, subsource, resourceName, verb) { + return false + } + } + } + } + } + } + } else { + for _, apiGroup := range required.APIGroups { + for _, nonResourceURL := range required.NonResourceURLs { + for _, verb := range required.Verbs { + if !ruleMatchesRequest(rule, apiGroup, nonResourceURL, "", "", "", verb) { + return false + } + } + } + } + } + return true +} + +func ruleMatchesResources(rule v1.PolicyRule, apiGroup string, resource string, subresource string, resourceName string) bool { + + if resource == "" { + return false + } + + if !hasString(rule.APIGroups, apiGroup) && !hasString(rule.APIGroups, v1.ResourceAll) { + return false + } + + if len(rule.ResourceNames) > 0 && !hasString(rule.ResourceNames, resourceName) { + return false + } + + combinedResource := resource + + if subresource != "" { + combinedResource = combinedResource + "/" + subresource + } + + for _, res := range rule.Resources { + + // match "*" + if res == v1.ResourceAll || res == combinedResource { + return true + } + + // match "*/subresource" + if len(subresource) > 0 && strings.HasPrefix(res, "*/") && subresource == strings.TrimLeft(res, "*/") { + return true + } + // match "resource/*" + if strings.HasSuffix(res, "/*") && resource == strings.TrimRight(res, "/*") { + return true + } + } + + return false +} + +func ruleMatchesRequest(rule v1.PolicyRule, apiGroup string, nonResourceURL string, resource string, subresource string, resourceName string, verb string) bool { + + if !hasString(rule.Verbs, verb) && !hasString(rule.Verbs, v1.VerbAll) { + return false + } + + if nonResourceURL == "" { + return ruleMatchesResources(rule, apiGroup, resource, subresource, resourceName) + } else { + return ruleMatchesNonResource(rule, nonResourceURL) + } +} + +func ruleMatchesNonResource(rule v1.PolicyRule, nonResourceURL string) bool { + + if nonResourceURL == "" { + return false + } + + for _, spec := range rule.NonResourceURLs { + if pathMatches(nonResourceURL, spec) { + return true + } + } + + return false +} + +func pathMatches(path, spec string) bool { + // Allow wildcard match + if spec == "*" { + return true + } + // Allow exact match + if spec == path { + return true + } + // Allow a trailing * subpath match + if strings.HasSuffix(spec, "*") && strings.HasPrefix(path, strings.TrimRight(spec, "*")) { + return true + } + return false +} + +func hasString(slice []string, value string) bool { + for _, s := range slice { + if s == value { + return true + } + } + return false +} diff --git a/pkg/models/iam/policy.go b/pkg/models/iam/policy.go index 8ab352664..7557134c4 100644 --- a/pkg/models/iam/policy.go +++ b/pkg/models/iam/policy.go @@ -31,11 +31,12 @@ const ( func init() { rulesConfig, err := ioutil.ReadFile(rulesConfigPath) + if err == nil { config := &[]Rule{} json.Unmarshal(rulesConfig, config) if len(*config) > 0 { - RoleRuleGroup = *config + RoleRuleMapping = *config } } @@ -45,7 +46,7 @@ func init() { config := &[]Rule{} json.Unmarshal(clusterRulesConfig, config) if len(*config) > 0 { - ClusterRoleRuleGroup = *config + ClusterRoleRuleMapping = *config } } } @@ -55,6 +56,7 @@ var ( { Name: "workspaces", Actions: []Action{ + {Name: "edit", Rules: []v1.PolicyRule{ { @@ -110,6 +112,24 @@ var ( }, }, }, + {Name: "edit", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"update", "patch"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"workspaces/devops"}, + }, + }, + }, + {Name: "delete", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"delete", "deletecollection"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"workspaces/devops"}, + }, + }, + }, }, }, { @@ -133,6 +153,24 @@ var ( }, }, }, + {Name: "edit", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"update", "patch"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"workspaces/namespaces"}, + }, + }, + }, + {Name: "delete", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"delete", "deletecollection"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"workspaces/namespaces"}, + }, + }, + }, }, }, { @@ -144,240 +182,144 @@ var ( {Name: "delete"}, }, }, + { + Name: "organizations", + Actions: []Action{ + {Name: "view"}, + {Name: "create"}, + {Name: "edit"}, + {Name: "delete"}, + }, + }, + { + Name: "roles", + Actions: []Action{ + {Name: "view"}, + {Name: "create"}, + {Name: "edit"}, + {Name: "delete"}, + }, + }, } - ClusterRoleRuleGroup = []Rule{{ - Name: "projects", - Actions: []Action{ - {Name: "view", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"get", "watch", "list"}, - APIGroups: []string{""}, - Resources: []string{"namespaces"}, - }, - }, - }, - {Name: "create", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"create"}, - APIGroups: []string{""}, - Resources: []string{"namespaces"}, - }, - }, - }, - {Name: "edit", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"update", "patch"}, - APIGroups: []string{""}, - Resources: []string{"namespaces"}, - }, - }, - }, - {Name: "delete", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"delete", "deletecollection"}, - APIGroups: []string{""}, - Resources: []string{"namespaces"}, - }, - }, - }, - {Name: "members", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"get", "watch", "list", "create", "delete"}, - APIGroups: []string{"rbac.authorization.k8s.io"}, - Resources: []string{"rolebindings"}, - }, - }, - }, - {Name: "member_roles", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"get", "watch", "list", "create", "delete", "patch", "update"}, - APIGroups: []string{"rbac.authorization.k8s.io"}, - Resources: []string{"roles"}, - }, - }, - }, - }, - }, { - Name: "users", - Actions: []Action{ - {Name: "view", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"get", "watch", "list"}, - APIGroups: []string{"kubesphere.io"}, - Resources: []string{"users"}, - }, - { - Verbs: []string{"get", "watch", "list"}, - APIGroups: []string{"rbac.authorization.k8s.io"}, - Resources: []string{"clusterrolebindings"}, - }, - }, - }, - {Name: "create", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"create"}, - APIGroups: []string{"kubesphere.io"}, - Resources: []string{"users"}, - }, - { - Verbs: []string{"create", "delete", "deletecollection"}, - APIGroups: []string{"rbac.authorization.k8s.io"}, - Resources: []string{"clusterrolebindings"}, - }, - }, - }, - {Name: "edit", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"update", "patch"}, - APIGroups: []string{"kubesphere.io"}, - Resources: []string{"users"}, - }, - { - Verbs: []string{"create", "delete", "deletecollection"}, - APIGroups: []string{"rbac.authorization.k8s.io"}, - Resources: []string{"clusterrolebindings"}, - }, - }, - }, - {Name: "delete", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"delete", "deletecollection"}, - APIGroups: []string{"kubesphere.io"}, - Resources: []string{"users"}, - }, - }, - }, - }, - }, { - Name: "roles", - Actions: []Action{ - {Name: "view", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"get", "watch", "list"}, - APIGroups: []string{"rbac.authorization.k8s.io"}, - Resources: []string{"clusterroles"}, - }, - }, - }, - - {Name: "create", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"create"}, - APIGroups: []string{"rbac.authorization.k8s.io"}, - Resources: []string{"clusterroles"}, - }, - }, - }, - {Name: "edit", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"update", "patch"}, - APIGroups: []string{"rbac.authorization.k8s.io"}, - Resources: []string{"clusterroles"}, - }, - }, - }, - {Name: "delete", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"delete", "deletecollection"}, - APIGroups: []string{"rbac.authorization.k8s.io"}, - Resources: []string{"clusterroles"}, - }, - }, - }, - }, - }, { - - Name: "images", - Actions: []Action{ - {Name: "view", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"get", "watch", "list"}, - APIGroups: []string{""}, - Resources: []string{ - "secrets", - }, - }, - { - Verbs: []string{"list"}, - APIGroups: []string{""}, - Resources: []string{"namespaces"}, - }, - }, - }, - {Name: "create", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"create"}, - APIGroups: []string{""}, - Resources: []string{ - "secrets", + ClusterRoleRuleMapping = []Rule{ + {Name: "workspaces", + Actions: []Action{ + { + Name: "view", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"get", "watch", "list"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"workspaces"}, }, }, }, - }, - {Name: "edit", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"update", "patch"}, - APIGroups: []string{""}, - Resources: []string{ - "secrets", + { + Name: "create", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"create"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"workspaces"}, }, }, }, - }, - {Name: "delete", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"delete", "deletecollection"}, - APIGroups: []string{""}, - Resources: []string{ - "secrets", + {Name: "edit", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"get", "watch", "list", "create", "delete", "patch", "update"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"workspaces", "workspaces/namespaces", "workspaces/roles", "workspaces/devops", "workspaces/members"}, + }, + { + Verbs: []string{"get"}, + APIGroups: []string{"kubesphere.io"}, + ResourceNames: []string{"workspaces"}, + Resources: []string{"monitoring/*"}, }, }, }, }, }, - }, + //{ + // Name: "projects", + // Actions: []Action{ + // {Name: "view", + // Rules: []v1.PolicyRule{ + // { + // Verbs: []string{"get", "watch", "list"}, + // APIGroups: []string{""}, + // Resources: []string{"namespaces"}, + // }, + // }, + // }, + // {Name: "create", + // Rules: []v1.PolicyRule{ + // { + // Verbs: []string{"create"}, + // APIGroups: []string{""}, + // Resources: []string{"namespaces"}, + // }, + // }, + // }, + // {Name: "edit", + // Rules: []v1.PolicyRule{ + // { + // Verbs: []string{"update", "patch"}, + // APIGroups: []string{""}, + // Resources: []string{"namespaces"}, + // }, + // }, + // }, + // {Name: "delete", + // Rules: []v1.PolicyRule{ + // { + // Verbs: []string{"delete", "deletecollection"}, + // APIGroups: []string{""}, + // Resources: []string{"namespaces"}, + // }, + // }, + // }, + // {Name: "members", + // Rules: []v1.PolicyRule{ + // { + // Verbs: []string{"get", "watch", "list", "create", "delete", "patch", "update"}, + // APIGroups: []string{"rbac.authorization.k8s.io"}, + // Resources: []string{"rolebindings", "roles"}, + // }, + // }, + // }, + // }, + //}, { - Name: "volumes", + Name: "accounts", Actions: []Action{ {Name: "view", Rules: []v1.PolicyRule{ { Verbs: []string{"get", "watch", "list"}, - APIGroups: []string{""}, - Resources: []string{"persistentvolumes"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"accounts"}, }, { - Verbs: []string{"list"}, - APIGroups: []string{""}, - Resources: []string{"namespaces"}, + Verbs: []string{"get", "watch", "list"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"clusterrolebindings"}, }, }, }, {Name: "create", Rules: []v1.PolicyRule{ { - Verbs: []string{"get", "watch", "list"}, - APIGroups: []string{""}, - Resources: []string{"persistentvolumes"}, + Verbs: []string{"create"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"accounts"}, + }, + { + Verbs: []string{"create", "delete", "deletecollection"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"clusterrolebindings"}, }, }, }, @@ -385,17 +327,63 @@ var ( Rules: []v1.PolicyRule{ { Verbs: []string{"update", "patch"}, - APIGroups: []string{""}, - Resources: []string{"persistentvolumes"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"accounts"}, + }, + { + Verbs: []string{"create", "delete", "deletecollection"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"clusterrolebindings"}, }, }, }, {Name: "delete", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"delete", "deletecollection"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"accounts"}, + }, + }, + }, + }, + }, { + Name: "roles", + Actions: []Action{ + {Name: "view", Rules: []v1.PolicyRule{ { Verbs: []string{"get", "watch", "list"}, - APIGroups: []string{""}, - Resources: []string{"persistentvolumes"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"clusterroles"}, + }, + }, + }, + + {Name: "create", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"create"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"clusterroles"}, + }, + }, + }, + {Name: "edit", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"update", "patch"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"clusterroles"}, + }, + }, + }, + {Name: "delete", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"delete", "deletecollection"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"clusterroles"}, }, }, }, @@ -447,7 +435,7 @@ var ( Rules: []v1.PolicyRule{ { Verbs: []string{"get", "watch", "list"}, - APIGroups: []string{""}, + APIGroups: []string{"kubesphere.io"}, Resources: []string{"nodes"}, }, }, @@ -461,25 +449,31 @@ var ( }, }, }, - {Name: "drain", + {Name: "cordon", Rules: []v1.PolicyRule{ { - Verbs: []string{"*"}, - APIGroups: []string{"kubesphere.io"}, + Verbs: []string{"update", "patch"}, + APIGroups: []string{""}, Resources: []string{"nodes"}, }, + }}, + {Name: "taint", Rules: []v1.PolicyRule{ + { + Verbs: []string{"update", "patch"}, + APIGroups: []string{""}, + Resources: []string{"nodes"}, }, - }, + }}, }, }, { - Name: "app_catalog", + Name: "repos", Actions: []Action{ {Name: "view", Rules: []v1.PolicyRule{ { Verbs: []string{"get", "watch", "list"}, APIGroups: []string{"openpitrix.io"}, - Resources: []string{"appcatalog"}, + Resources: []string{"repos"}, }, }, }, @@ -488,7 +482,7 @@ var ( { Verbs: []string{"create"}, APIGroups: []string{"openpitrix.io"}, - Resources: []string{"appcatalog"}, + Resources: []string{"repos"}, }, }, }, @@ -497,7 +491,7 @@ var ( { Verbs: []string{"update", "patch"}, APIGroups: []string{"openpitrix.io"}, - Resources: []string{"appcatalog"}, + Resources: []string{"repos"}, }, }, }, @@ -506,7 +500,7 @@ var ( { Verbs: []string{"delete", "deletecollection"}, APIGroups: []string{"openpitrix.io"}, - Resources: []string{"appcatalog"}, + Resources: []string{"repos"}, }, }, }, @@ -519,7 +513,7 @@ var ( { Verbs: []string{"get", "watch", "list"}, APIGroups: []string{"openpitrix.io"}, - Resources: []string{"apps"}, + Resources: []string{"apps", "repos"}, }, }, }, @@ -537,7 +531,96 @@ var ( }, }, }, - }, { + }} + + RoleRuleMapping = []Rule{{ + Name: "projects", + Actions: []Action{ + // limit range + router + {Name: "edit", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"update", "patch", "get"}, + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + }, + { + Verbs: []string{"list"}, + APIGroups: []string{""}, + Resources: []string{"events"}, + }, + }, + }, + {Name: "delete", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"delete"}, + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + }, + { + Verbs: []string{"get"}, + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + }, + { + Verbs: []string{"list"}, + APIGroups: []string{""}, + Resources: []string{"events"}, + }, + }, + }, + }, + }, + { + Name: "members", + Actions: []Action{ + {Name: "view", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"get", "watch", "list", "create", "delete"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"rolebindings"}, + }, + { + Verbs: []string{"get"}, + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + }, + { + Verbs: []string{"list"}, + APIGroups: []string{""}, + Resources: []string{"events"}, + }, + }, + }, + }, + }, + { + Name: "roles", + Actions: []Action{ + {Name: "view", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"get", "watch", "list", "create", "delete", "patch", "update"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles"}, + }, + { + Verbs: []string{"get"}, + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + }, + { + Verbs: []string{"list"}, + APIGroups: []string{""}, + Resources: []string{"events"}, + }, + }, + }, + }, + }, + { Name: "deployments", Actions: []Action{ {Name: "view", @@ -548,10 +631,15 @@ var ( Resources: []string{"deployments", "deployments/scale"}, }, { - Verbs: []string{"list"}, + Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"namespaces"}, }, + { + Verbs: []string{"list"}, + APIGroups: []string{""}, + Resources: []string{"events"}, + }, { Verbs: []string{"get", "watch", "list"}, APIGroups: []string{""}, @@ -609,10 +697,15 @@ var ( Resources: []string{"statefulsets"}, }, { - Verbs: []string{"list"}, + Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"namespaces"}, }, + { + Verbs: []string{"list"}, + APIGroups: []string{""}, + Resources: []string{"events"}, + }, { Verbs: []string{"get", "watch", "list"}, APIGroups: []string{""}, @@ -668,10 +761,15 @@ var ( Resources: []string{"daemonsets"}, }, { - Verbs: []string{"list"}, + Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"namespaces"}, }, + { + Verbs: []string{"list"}, + APIGroups: []string{""}, + Resources: []string{"events"}, + }, { Verbs: []string{"get", "watch", "list"}, APIGroups: []string{""}, @@ -720,373 +818,7 @@ var ( }, }, }, - }, { - Name: "services", - Actions: []Action{ - {Name: "view", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"list"}, - APIGroups: []string{""}, - Resources: []string{"services"}, - }, - { - Verbs: []string{"list"}, - APIGroups: []string{""}, - Resources: []string{"namespaces"}, - }, - }, - }, - {Name: "create", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"create"}, - APIGroups: []string{""}, - Resources: []string{"services"}, - }, - }, - }, - - {Name: "edit", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"update", "patch"}, - APIGroups: []string{""}, - Resources: []string{"services"}, - }, - }, - }, - {Name: "delete", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"delete", "deletecollection"}, - APIGroups: []string{""}, - Resources: []string{"services"}, - }, - }, - }, - }, - }, { - Name: "routes", - Actions: []Action{ - {Name: "view", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"get", "watch", "list"}, - APIGroups: []string{"extensions"}, - Resources: []string{"ingresses"}, - }, - { - Verbs: []string{"list"}, - APIGroups: []string{""}, - Resources: []string{"namespaces"}, - }, - }, - }, - {Name: "create", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"create"}, - APIGroups: []string{"extensions"}, - Resources: []string{"ingresses"}, - }, - }, - }, - {Name: "edit", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"update", "patch"}, - APIGroups: []string{"extensions"}, - Resources: []string{"ingresses"}, - }, - }, - }, - {Name: "delete", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"delete", "deletecollection"}, - APIGroups: []string{"extensions"}, - Resources: []string{"ingresses"}, - }, - }, - }, - }, - }} - - RoleRuleGroup = []Rule{{ - Name: "projects", - Actions: []Action{ - {Name: "members", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"get", "watch", "list", "create", "delete"}, - APIGroups: []string{"rbac.authorization.k8s.io"}, - Resources: []string{"rolebindings"}, - }, - { - Verbs: []string{"get"}, - APIGroups: []string{""}, - Resources: []string{"namespaces"}, - }, - { - Verbs: []string{"list"}, - APIGroups: []string{""}, - Resources: []string{"events"}, - }, - }, - }, - {Name: "member_roles", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"get", "watch", "list", "create", "delete", "patch", "update"}, - APIGroups: []string{"rbac.authorization.k8s.io"}, - Resources: []string{"roles"}, - }, - { - Verbs: []string{"get"}, - APIGroups: []string{""}, - Resources: []string{"namespaces"}, - }, - { - Verbs: []string{"list"}, - APIGroups: []string{""}, - Resources: []string{"events"}, - }, - }, - }, - {Name: "edit", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"update", "patch", "get"}, - APIGroups: []string{""}, - Resources: []string{"namespaces"}, - }, - { - Verbs: []string{"list"}, - APIGroups: []string{""}, - Resources: []string{"events"}, - }, - }, - }, - {Name: "delete", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"delete"}, - APIGroups: []string{""}, - Resources: []string{"namespaces"}, - }, - { - Verbs: []string{"get"}, - APIGroups: []string{""}, - Resources: []string{"namespaces"}, - }, - { - Verbs: []string{"list"}, - APIGroups: []string{""}, - Resources: []string{"events"}, - }, - }, - }, }, - }, { - Name: "deployments", - Actions: []Action{ - {Name: "view", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"get", "watch", "list"}, - APIGroups: []string{"apps", "extensions"}, - Resources: []string{"deployments", "deployments/scale"}, - }, - { - Verbs: []string{"get"}, - APIGroups: []string{""}, - Resources: []string{"namespaces"}, - }, - { - Verbs: []string{"list"}, - APIGroups: []string{""}, - Resources: []string{"events"}, - }, - { - Verbs: []string{"get", "watch", "list"}, - APIGroups: []string{""}, - Resources: []string{"pods", "pods/log", "pods/status"}, - }, - }, - }, - {Name: "create", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"create"}, - APIGroups: []string{"apps", "extensions"}, - Resources: []string{"deployments"}, - }, - }, - }, - - {Name: "edit", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"update", "patch"}, - APIGroups: []string{"apps", "extensions"}, - Resources: []string{"deployments", "deployments/rollback"}, - }, - }, - }, - - {Name: "delete", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"delete", "deletecollection"}, - APIGroups: []string{"apps", "extensions"}, - Resources: []string{"deployments"}, - }, - }, - }, - {Name: "scale", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"create", "update", "patch", "delete"}, - APIGroups: []string{"apps", "extensions"}, - Resources: []string{"deployments/scale"}, - }, - }, - }, - }, - }, { - Name: "statefulsets", - Actions: []Action{ - {Name: "view", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"get", "watch", "list"}, - APIGroups: []string{"apps"}, - Resources: []string{"statefulsets"}, - }, - { - Verbs: []string{"get"}, - APIGroups: []string{""}, - Resources: []string{"namespaces"}, - }, - { - Verbs: []string{"list"}, - APIGroups: []string{""}, - Resources: []string{"events"}, - }, - { - Verbs: []string{"get", "watch", "list"}, - APIGroups: []string{""}, - Resources: []string{"pods", "pods/log", "pods/status"}, - }, - }, - }, - {Name: "create", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"create"}, - APIGroups: []string{"apps"}, - Resources: []string{"statefulsets"}, - }, - }, - }, - {Name: "edit", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"update", "patch"}, - APIGroups: []string{"apps"}, - Resources: []string{"statefulsets"}, - }, - }, - }, - {Name: "delete", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"delete", "deletecollection"}, - APIGroups: []string{"apps"}, - Resources: []string{"statefulsets"}, - }, - }, - }, - {Name: "scale", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"patch"}, - APIGroups: []string{"apps"}, - Resources: []string{"statefulsets"}, - }, - }, - }, - }, - }, { - Name: "daemonsets", - Actions: []Action{ - {Name: "view", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"get", "watch", "list"}, - APIGroups: []string{"apps", "extensions"}, - Resources: []string{"daemonsets"}, - }, - { - Verbs: []string{"get"}, - APIGroups: []string{""}, - Resources: []string{"namespaces"}, - }, - { - Verbs: []string{"list"}, - APIGroups: []string{""}, - Resources: []string{"events"}, - }, - { - Verbs: []string{"get", "watch", "list"}, - APIGroups: []string{""}, - Resources: []string{"pods", "pods/log", "pods/status"}, - }, - }, - }, - {Name: "create", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"create"}, - APIGroups: []string{"apps", "extensions"}, - Resources: []string{"daemonsets"}, - }, - }, - }, - {Name: "edit", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"update", "patch"}, - APIGroups: []string{"apps", "extensions"}, - Resources: []string{"daemonsets"}, - }, - }, - }, - {Name: "delete", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"delete", "deletecollection"}, - APIGroups: []string{"apps", "extensions"}, - Resources: []string{"daemonsets"}, - }, - }, - }, - }, - }, { - Name: "pods", - Actions: []Action{ - {Name: "terminal", - Rules: []v1.PolicyRule{ - { - Verbs: []string{"*"}, - APIGroups: []string{"kubesphere.io"}, - Resources: []string{"terminal"}, - }, - }, - }, - }, - }, { Name: "services", Actions: []Action{ @@ -1238,5 +970,151 @@ var ( }, }, }, - }} + }, { + Name: "applications", + Actions: []Action{ + {Name: "view", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"list"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"applications"}, + }, + }, + }, + }, + }, + { + Name: "jobs", + Actions: []Action{ + {Name: "view", Rules: []v1.PolicyRule{ + { + Verbs: []string{"view", "list"}, + APIGroups: []string{"batch"}, + Resources: []string{"jobs"}, + }, + }}, + {Name: "create", Rules: []v1.PolicyRule{ + { + Verbs: []string{"create"}, + APIGroups: []string{"batch"}, + Resources: []string{"jobs"}, + }, + }}, + {Name: "edit", Rules: []v1.PolicyRule{ + { + Verbs: []string{"update", "patch"}, + APIGroups: []string{"batch"}, + Resources: []string{"jobs"}, + }, + }}, + {Name: "delete", Rules: []v1.PolicyRule{ + { + Verbs: []string{"delete", "deletecollection"}, + APIGroups: []string{"batch"}, + Resources: []string{"jobs"}, + }, + }}, + }, + }, + { + Name: "cronjobs", + Actions: []Action{ + {Name: "view", Rules: []v1.PolicyRule{ + { + Verbs: []string{"view", "list"}, + APIGroups: []string{"batch"}, + Resources: []string{"cronjobs"}, + }, + }}, + {Name: "create", Rules: []v1.PolicyRule{ + { + Verbs: []string{"create"}, + APIGroups: []string{"batch"}, + Resources: []string{"cronjobs"}, + }, + }}, + {Name: "edit", Rules: []v1.PolicyRule{ + { + Verbs: []string{"update", "patch"}, + APIGroups: []string{"batch"}, + Resources: []string{"cronjobs"}, + }, + }}, + {Name: "delete", Rules: []v1.PolicyRule{ + { + Verbs: []string{"delete", "deletecollection"}, + APIGroups: []string{"batch"}, + Resources: []string{"cronjobs"}, + }, + }}, + }, + }, + { + Name: "secrets", + Actions: []Action{ + {Name: "view", Rules: []v1.PolicyRule{ + { + Verbs: []string{"view", "list"}, + APIGroups: []string{""}, + Resources: []string{"secrets"}, + }, + }}, + {Name: "create", Rules: []v1.PolicyRule{ + { + Verbs: []string{"create"}, + APIGroups: []string{""}, + Resources: []string{"secrets"}, + }, + }}, + {Name: "edit", Rules: []v1.PolicyRule{ + { + Verbs: []string{"update", "patch"}, + APIGroups: []string{""}, + Resources: []string{"secrets"}, + }, + }}, + {Name: "delete", Rules: []v1.PolicyRule{ + { + Verbs: []string{"delete", "deletecollection"}, + APIGroups: []string{""}, + Resources: []string{"secrets"}, + }, + }}, + }, + }, + { + Name: "configmaps", + Actions: []Action{ + {Name: "view", Rules: []v1.PolicyRule{ + { + Verbs: []string{"view", "list"}, + APIGroups: []string{""}, + Resources: []string{"configmaps"}, + }, + }}, + {Name: "create", Rules: []v1.PolicyRule{ + { + Verbs: []string{"create"}, + APIGroups: []string{""}, + Resources: []string{"configmaps"}, + }, + }}, + {Name: "edit", Rules: []v1.PolicyRule{ + { + Verbs: []string{"update", "patch"}, + APIGroups: []string{""}, + Resources: []string{"configmaps"}, + }, + }}, + {Name: "delete", Rules: []v1.PolicyRule{ + { + Verbs: []string{"delete", "deletecollection"}, + APIGroups: []string{""}, + Resources: []string{"configmaps"}, + }, + }}, + }, + }, + } ) diff --git a/pkg/models/image_registries.go b/pkg/models/image_registries.go index 6271b55d3..f031c22dc 100644 --- a/pkg/models/image_registries.go +++ b/pkg/models/image_registries.go @@ -29,15 +29,23 @@ import ( "k8s.io/api/core/v1" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "crypto/tls" + "io/ioutil" + "net/http" + "time" + kubeclient "kubesphere.io/kubesphere/pkg/client" "kubesphere.io/kubesphere/pkg/constants" ) -const TYPE = "kubernetes.io/dockerconfigjson" - -const SECRET = "Secret" - -const APIVERSION = "v1" +const ( + TYPE = "kubernetes.io/dockerconfigjson" + SECRET = "Secret" + APIVERSION = "v1" + TYPEHARBOR = "harbor" + TYPEDOCKERHUB = "dockerhub" + TYPEDOCKERREGISTRY = "docker-registry" +) type AuthInfo struct { Username string `json:"username"` @@ -45,6 +53,50 @@ type AuthInfo struct { ServerHost string `json:"serverhost"` } +type DockerConfigEntry struct { + Username string `json:"username"` + Password string `json:"password"` + Auth string `json:"auth"` +} + +type RegistryInfo struct { + user, password, registryType, url string +} + +type dockerConfig map[string]map[string]DockerConfigEntry + +type harborRepo struct { + RepoName string `json:"repository_name"` +} + +type harborRepos struct { + Repos []harborRepo `json:"repository"` +} + +type registryRepos struct { + Repositories []string +} + +type registryTags struct { + Name string `json:"name"` + Tags []string `json:"tags"` +} + +type dockerhubRepo struct { + RepoName string `json:"repo_name"` +} +type dockerhubRepos struct { + Repositories []dockerhubRepo `json:"results"` +} + +type dockerhubTag struct { + TagName string `json:"name"` +} + +type dockerhubTags struct { + Tags []dockerhubTag `json:"results"` +} + func NewAuthInfo(para Registries) *AuthInfo { return &AuthInfo{ @@ -440,3 +492,237 @@ func GetReisgtries(name string) (Registries, error) { return reg, nil } + +// by image secret to get registry'info, like username, password, registry url ... +func getRegistryInfo(namespace, registryName string) *RegistryInfo { + + var registry RegistryInfo + k8sClient := kubeclient.NewK8sClient() + secret, err := k8sClient.CoreV1().Secrets(namespace).Get(registryName, meta_v1.GetOptions{}) + if err != nil { + glog.Error(err) + return nil + } + + registry.registryType = secret.Annotations["type"] + + data := secret.Data[v1.DockerConfigJsonKey] + + authsMap := make(dockerConfig) + err = json.Unmarshal(data, &authsMap) + if err != nil { + glog.Error(err) + return nil + } + + for url, config := range authsMap["auths"] { + registry.url = url + registry.user = config.Username + registry.password = config.Password + break + } + + return ®istry +} + +func ImageSearch(namespace, registryName, searchWord string) []string { + registry := getRegistryInfo(namespace, registryName) + if registry == nil { + return nil + } + + switch registry.registryType { + case TYPEDOCKERHUB: + return searchDockerHub(registry.url, searchWord) + case TYPEDOCKERREGISTRY: + return searchDockerRegistry(registry.url, searchWord) + case TYPEHARBOR: + return searchHarbor(registry.url, registry.user, registry.password, searchWord) + } + + return nil +} + +func GetImageTags(namespace, registryName, imageName string) []string { + registry := getRegistryInfo(namespace, registryName) + if registry == nil { + return nil + } + + switch registry.registryType { + case TYPEDOCKERHUB: + return getTagInDockerHub(registry.url, imageName) + case TYPEDOCKERREGISTRY: + return getTagInDockerRegistry(registry.url, imageName) + case TYPEHARBOR: + return getTagInHarbor(registry.url, registry.user, registry.password, imageName) + } + + return nil +} + +func httpGet(url, username, password string, insecure bool) ([]byte, error) { + var httpClient *http.Client + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + + if insecure { + httpClient = &http.Client{} + } else { + req.SetBasicAuth(username, password) + tr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} + httpClient = &http.Client{Timeout: 20 * time.Second, Transport: tr} + } + + resp, err := httpClient.Do(req) + + if err != nil { + err := fmt.Errorf("Request to %s failed reason: %s ", url, err) + return nil, err + } + + body, err := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + if resp.StatusCode >= http.StatusBadRequest || err != nil { + return nil, err + } + + return body, nil +} + +func searchHarbor(url, username, password, searchWord string) []string { + url = strings.TrimSuffix(url, "/") + fmt.Sprintf("/api/search?q=%s", searchWord) + + body, err := httpGet(url, username, password, false) + if err != nil || len(body) == 0 { + glog.Error(err) + return nil + } + + var repos harborRepos + repoList := make([]string, 0, 100) + err = json.Unmarshal(body, &repos) + if err != nil { + glog.Error(err) + return nil + } + + for _, repo := range repos.Repos { + repoList = append(repoList, repo.RepoName) + } + + return repoList +} + +func searchDockerRegistry(url, searchword string) []string { + url = strings.TrimSuffix(url, "/") + "/v2/_catalog" + body, err := httpGet(url, "", "", true) + if err != nil || len(body) == 0 { + glog.Error(err) + return nil + } + + var repos registryRepos + err = json.Unmarshal(body, &repos) + if err != nil { + glog.Error(err) + return nil + } + + repoList := make([]string, 0, 100) + for _, repo := range repos.Repositories { + if strings.HasPrefix(repo, searchword) { + repoList = append(repoList, repo) + } + } + + return repoList +} + +func searchDockerHub(url, searchWord string) []string { + url = fmt.Sprintf("https://hub.docker.com/v2/search/repositories/?page=1&query=%s&page_size=50", searchWord) + body, err := httpGet(url, "", "", true) + if err != nil || len(body) == 0 { + glog.Error(err) + return nil + } + + var repos dockerhubRepos + err = json.Unmarshal(body, &repos) + if err != nil { + glog.Error(err) + return nil + } + + repoList := make([]string, 0, 50) + for _, repo := range repos.Repositories { + repoList = append(repoList, repo.RepoName) + } + + return repoList +} + +func getTagInHarbor(url, username, password, imageName string) []string { + url = strings.TrimSuffix(url, "/") + fmt.Sprintf("/api/repositories/%s/tags", imageName) + body, err := httpGet(url, username, password, false) + if err != nil || len(body) == 0 { + glog.Error(err) + return nil + } + + var tagList []string + err = json.Unmarshal(body, &tagList) + if err != nil { + glog.Error(err) + return nil + } + + return tagList +} + +func getTagInDockerRegistry(url, imageName string) []string { + url = strings.TrimSuffix(url, "/") + fmt.Sprintf("/v2/%s/tags/list", imageName) + body, err := httpGet(url, "", "", true) + if err != nil || len(body) == 0 { + glog.Error(err) + return nil + } + + var tags registryTags + err = json.Unmarshal(body, &tags) + if err != nil { + glog.Error(err) + return nil + } + + return tags.Tags +} + +func getTagInDockerHub(url, imageName string) []string { + if !strings.Contains(imageName, "/") { + imageName = fmt.Sprintf("library/%s", imageName) + } + url = fmt.Sprintf("https://hub.docker.com/v2/repositories/%s/tags/?page=1&page_size=200", imageName) + + body, err := httpGet(url, "", "", true) + if err != nil || len(body) == 0 { + glog.Error(err) + return nil + } + + var tags dockerhubTags + err = json.Unmarshal(body, &tags) + if err != nil { + glog.Error(err) + return nil + } + + tagList := make([]string, 0, 200) + for _, tag := range tags.Tags { + tagList = append(tagList, tag.TagName) + } + + return tagList +} diff --git a/pkg/models/metrics/containers.go b/pkg/models/metrics/containers.go old mode 100644 new mode 100755 diff --git a/pkg/models/metrics/metrics_collector.go b/pkg/models/metrics/metrics_collector.go deleted file mode 100644 index 8f5762cdc..000000000 --- a/pkg/models/metrics/metrics_collector.go +++ /dev/null @@ -1,184 +0,0 @@ -/* -Copyright 2018 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 metrics - -import ( - "encoding/json" - "regexp" - "strings" - - "github.com/emicklei/go-restful" - "github.com/golang/glog" - - "kubesphere.io/kubesphere/pkg/client" -) - -func getPodNameRegexInWorkload(request *restful.Request) string { - promql := MakeWorkloadRule(request) - res := client.SendPrometheusRequest(request, promql) - data := []byte(res) - var dat CommonMetricsResult - jsonErr := json.Unmarshal(data, &dat) - if jsonErr != nil { - glog.Errorln("json parse failed", jsonErr) - } - var podNames []string - for _, x := range dat.Data.Result { - podName := x.KubePodMetric.Pod - podNames = append(podNames, podName) - } - podNamesFilter := "^(" + strings.Join(podNames, "|") + ")$" - return podNamesFilter -} - -func MonitorWorkloadSingleMetric(request *restful.Request, metricsName string) *FormatedMetric { - nsName := strings.Trim(request.PathParameter("ns_name"), " ") - podNamesFilter := getPodNameRegexInWorkload(request) - newPromql := MakePodPromQL(request, []string{metricsName, nsName, "", "", podNamesFilter}) - podMetrics := client.SendPrometheusRequest(request, newPromql) - cleanedJson := ReformatJson(podMetrics, metricsName) - return cleanedJson -} - -func MonitorPodSingleMetric(request *restful.Request, metricsName string) *FormatedMetric { - nsName := strings.Trim(request.PathParameter("ns_name"), " ") - nodeID := strings.Trim(request.PathParameter("node_id"), " ") - podName := strings.Trim(request.PathParameter("pod_name"), " ") - podFilter := strings.Trim(request.QueryParameter("pods_filter"), " ") - params := []string{metricsName, nsName, nodeID, podName, podFilter} - promql := MakePodPromQL(request, params) - if promql != "" { - res := client.SendPrometheusRequest(request, promql) - cleanedJson := ReformatJson(res, metricsName) - return cleanedJson - } - return nil -} - -func MonitorNamespaceSingleMetric(request *restful.Request, metricsName string) *FormatedMetric { - recordingRule := MakeNamespacePromQL(request, metricsName) - res := client.SendPrometheusRequest(request, recordingRule) - cleanedJson := ReformatJson(res, metricsName) - return cleanedJson -} - -func ReformatJson(metric string, metricsName string) *FormatedMetric { - var formatMetric FormatedMetric - err := json.Unmarshal([]byte(metric), &formatMetric) - if err != nil { - glog.Errorln("Unmarshal metric json failed", err) - } - if formatMetric.MetricName == "" { - formatMetric.MetricName = metricsName - } - // retrive metrics success - if formatMetric.Status == "success" { - result := formatMetric.Data.Result - for _, res := range result { - metric, ok := res["metric"] - me := metric.(map[string]interface{}) - if ok { - delete(me, "__name__") - } - } - } - return &formatMetric -} - -func collectNodeorClusterMetrics(request *restful.Request, metricsName string, ch chan<- *FormatedMetric) { - metric := MonitorNodeorClusterSingleMetric(request, metricsName) - ch <- metric -} - -func collectNamespaceMetrics(request *restful.Request, metricsName string, ch chan<- *FormatedMetric) { - metric := MonitorNamespaceSingleMetric(request, metricsName) - ch <- metric -} - -func collectWorkloadMetrics(request *restful.Request, metricsName string, ch chan<- *FormatedMetric) { - metricsName = strings.TrimLeft(metricsName, "workload_") - metric := MonitorWorkloadSingleMetric(request, metricsName) - ch <- metric -} - -func collectPodMetrics(request *restful.Request, metricsName string, ch chan<- *FormatedMetric) { - metric := MonitorPodSingleMetric(request, metricsName) - ch <- metric -} - -func MonitorAllMetrics(request *restful.Request) FormatedLevelMetric { - metricsName := strings.Trim(request.QueryParameter("metrics_filter"), " ") - if metricsName == "" { - metricsName = ".*" - } - path := request.SelectedRoutePath() - sourceType := path[strings.LastIndex(path, "/")+1 : len(path)-1] - if strings.Contains(path, "workload") { - sourceType = "workload" - } - var ch = make(chan *FormatedMetric, 10) - for _, k := range MetricsNames { - bol, err := regexp.MatchString(metricsName, k) - if !bol { - continue - } - if err != nil { - glog.Errorln("regex match failed", err) - continue - } - if strings.HasPrefix(k, sourceType) { - if sourceType == "node" || sourceType == "cluster" { - go collectNodeorClusterMetrics(request, k, ch) - } else if sourceType == "namespace" { - go collectNamespaceMetrics(request, k, ch) - } else if sourceType == "pod" { - go collectPodMetrics(request, k, ch) - } else if sourceType == "workload" { - go collectWorkloadMetrics(request, k, ch) - } - } - } - var metricsArray []FormatedMetric - var tempJson *FormatedMetric - for _, k := range MetricsNames { - bol, err := regexp.MatchString(metricsName, k) - if !bol { - continue - } - if err != nil { - glog.Errorln("regex match failed") - continue - } - if strings.HasPrefix(k, sourceType) { - tempJson = <-ch - if tempJson != nil { - metricsArray = append(metricsArray, *tempJson) - } - } - } - return FormatedLevelMetric{ - MetricsLevel: sourceType, - Results: metricsArray, - } -} - -func MonitorNodeorClusterSingleMetric(request *restful.Request, metricsName string) *FormatedMetric { - recordingRule := MakeNodeorClusterRule(request, metricsName) - res := client.SendPrometheusRequest(request, recordingRule) - cleanedJson := ReformatJson(res, metricsName) - return cleanedJson -} diff --git a/pkg/models/metrics/metrics_rule_tmpl.go b/pkg/models/metrics/metrics_rule_tmpl.go deleted file mode 100644 index eae10835b..000000000 --- a/pkg/models/metrics/metrics_rule_tmpl.go +++ /dev/null @@ -1,147 +0,0 @@ -/* -Copyright 2018 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 metrics - -type MetricMap map[string]string - -var MetricsNames = []string{ - "cluster_cpu_utilisation", - "cluster_memory_utilisation", - "cluster_net_utilisation", - "cluster_pod_count", - - "node_cpu_utilisation", - "node_memory_utilisation", - "node_memory_available", - "node_memory_total", - "node_net_utilisation", - "node_net_bytes_transmitted", - "node_net_bytes_received", - "node_disk_read_iops", - "node_disk_write_iops", - "node_disk_read_throughput", - "node_disk_write_throughput", - "node_disk_capacity", - "node_disk_available", - "node_disk_utilization", - - "namespace_cpu_utilisation", - "namespace_memory_utilisation", - "namespace_memory_utilisation_wo_cache", - "namespace_net_bytes_transmitted", - "namespace_net_bytes_received", - "namespace_pod_count", - - "pod_cpu_utilisation", - "pod_memory_utilisation", - "pod_memory_utilisation_wo_cache", - "pod_net_bytes_transmitted", - "pod_net_bytes_received", - - "workload_pod_cpu_utilisation", - "workload_pod_memory_utilisation", - "workload_pod_memory_utilisation_wo_cache", - "workload_pod_net_bytes_transmitted", - "workload_pod_net_bytes_received", - //"container_cpu_utilisation", - //"container_memory_utilisation_wo_cache", - //"container_memory_utilisation", - - "tenant_cpu_utilisation", - "tenant_memory_utilisation", - "tenant_memory_utilisation_wo_cache", - "tenant_net_bytes_transmitted", - "tenant_net_bytes_received", - "tenant_pod_count", -} - -var RulePromQLTmplMap = MetricMap{ - //cluster - "cluster_cpu_utilisation": ":node_cpu_utilisation:avg1m", - "cluster_memory_utilisation": ":node_memory_utilisation:", - // Cluster network utilisation (bytes received + bytes transmitted per second) - "cluster_net_utilisation": ":node_net_utilisation:sum_irate", - "cluster_pod_count": `count(kube_pod_info{job="kube-state-metrics"})`, - - //node - "node_cpu_utilisation": "node:node_cpu_utilisation:avg1m", - "node_memory_utilisation": "node:node_memory_utilisation:", - "node_memory_available": "node:node_memory_bytes_available:sum", - "node_memory_total": "node:node_memory_bytes_total:sum", - // Node network utilisation (bytes received + bytes transmitted per second) - "node_net_utilisation": "node:node_net_utilisation:sum_irate", - // Node network bytes transmitted per second - "node_net_bytes_transmitted": "node:node_net_bytes_transmitted:sum_irate", - // Node network bytes received per second - "node_net_bytes_received": "node:node_net_bytes_received:sum_irate", - - // node:data_volume_iops_reads:sum{node=~"i-5xcldxos|i-6soe9zl1"} - "node_disk_read_iops": "node:data_volume_iops_reads:sum", - // node:data_volume_iops_writes:sum{node=~"i-5xcldxos|i-6soe9zl1"} - "node_disk_write_iops": "node:data_volume_iops_writes:sum", - // node:data_volume_throughput_bytes_read:sum{node=~"i-5xcldxos|i-6soe9zl1"} - "node_disk_read_throughput": "node:data_volume_throughput_bytes_read:sum", - // node:data_volume_throughput_bytes_written:sum{node=~"i-5xcldxos|i-6soe9zl1"} - "node_disk_write_throughput": "node:data_volume_throughput_bytes_written:sum", - - "node_disk_capacity": `sum by (node) ((node_filesystem_avail{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:$1)`, - "node_disk_available": `sum by (node) ((node_filesystem_avail{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:$1)`, - "node_disk_utilization": `sum by (node) (((node_filesystem_size{mountpoint="/", job="node-exporter"} - node_filesystem_avail{mountpoint="/", job="node-exporter"}) / node_filesystem_size{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:$1)`, - - //namespace - "namespace_cpu_utilisation": `namespace:container_cpu_usage_seconds_total:sum_rate{namespace=~"$1"}`, - "namespace_memory_utilisation": `namespace:container_memory_usage_bytes:sum{namespace=~"$1"}`, - "namespace_memory_utilisation_wo_cache": `namespace:container_memory_usage_bytes_wo_cache:sum{namespace=~"$1"}`, - "namespace_net_bytes_transmitted": `sum by (namespace) (irate(container_network_transmit_bytes_total{namespace=~"$1", pod_name!="", interface="eth0", job="kubelet"}[2m]))`, - "namespace_net_bytes_received": `sum by (namespace) (irate(container_network_receive_bytes_total{namespace=~"$1", pod_name!="", interface="eth0", job="kubelet"}[2m]))`, - // count(kube_pod_info) by (namespace) namespace=~"monitoring|default|kube-system" - "namespace_pod_count": `count(kube_pod_info{job="kube-state-metrics", namespace=~"$1"}) by (namespace)`, - - // pod - "pod_cpu_utilisation": `sum(irate(container_cpu_usage_seconds_total{job="kubelet", namespace="$1", pod_name="$2", image!=""}[5m])) by (namespace, pod_name)`, - "pod_memory_utilisation": `sum(container_memory_usage_bytes{job="kubelet", namespace="$1", pod_name="$2", image!=""}) by (namespace, pod_name)`, - "pod_memory_utilisation_wo_cache": `sum(container_memory_usage_bytes{job="kubelet", namespace="$1", pod_name="$2", image!=""} - container_memory_cache{job="kubelet", namespace="$1", pod_name="$2",image!=""}) by (namespace, pod_name)`, - "pod_net_bytes_transmitted": `sum by (namespace, pod_name) (irate(container_network_transmit_bytes_total{namespace="$1", pod_name!="", pod_name="$2", interface="eth0", job="kubelet"}[2m]))`, - "pod_net_bytes_received": `sum by (namespace, pod_name) (irate(container_network_receive_bytes_total{namespace="$1", pod_name!="", pod_name="$2", interface="eth0", job="kubelet"}[2m]))`, - - "pod_cpu_utilisation_all": `sum(irate(container_cpu_usage_seconds_total{job="kubelet", namespace="$1", pod_name=~"$2", image!=""}[5m])) by (namespace, pod_name)`, - "pod_memory_utilisation_all": `sum(container_memory_usage_bytes{job="kubelet", namespace="$1", pod_name=~"$2", image!=""}) by (namespace, pod_name)`, - "pod_memory_utilisation_wo_cache_all": `sum(container_memory_usage_bytes{job="kubelet", namespace="$1", pod_name=~"$2", image!=""} - container_memory_cache{job="kubelet", namespace="$1", pod_name=~"$2", image!=""}) by (namespace, pod_name)`, - "pod_net_bytes_transmitted_all": `sum by (namespace, pod_name) (irate(container_network_transmit_bytes_total{namespace="$1", pod_name!="", pod_name=~"$2", interface="eth0", job="kubelet"}[2m]))`, - "pod_net_bytes_received_all": `sum by (namespace, pod_name) (irate(container_network_receive_bytes_total{namespace="$1", pod_name!="", pod_name=~"$2", interface="eth0", job="kubelet"}[2m]))`, - - //"pod_cpu_utilisation_node": `sum by (node, pod) (label_join(irate(container_cpu_usage_seconds_total{job="kubelet", image!=""}[5m]), "pod", " ", "pod_name") * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:{node=~"$3"})`, - "pod_cpu_utilisation_node": `sum by (node, pod) (label_join(irate(container_cpu_usage_seconds_total{job="kubelet",pod_name=~"$2", image!=""}[5m]), "pod", " ", "pod_name") * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:{node=~"$3"})`, - "pod_memory_utilisation_node": `sum by (node, pod) (label_join(container_memory_usage_bytes{job="kubelet",pod_name=~"$2", image!=""}, "pod", " ", "pod_name") * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:{node=~"$3"})`, - "pod_memory_utilisation_wo_cache_node": `sum by (node, pod) ((label_join(container_memory_usage_bytes{job="kubelet",pod_name=~"$2", image!=""}, "pod", " ", "pod_name") - label_join(container_memory_cache{job="kubelet",pod_name=~"$2", image!=""}, "pod", " ", "pod_name")) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:{node=~"$3"})`, - - // container - "container_cpu_utilisation": `sum(irate(container_cpu_usage_seconds_total{namespace="$1", pod_name="$2", container_name="$3"}[5m])) by (namespace, pod_name, container_name)`, - //"container_cpu_utilisation_wo_podname": `sum(irate(container_cpu_usage_seconds_total{namespace="$1", container_name=~"$3"}[5m])) by (namespace, pod_name, container_name)`, - "container_cpu_utilisation_all": `sum(irate(container_cpu_usage_seconds_total{namespace="$1", pod_name="$2", container_name=~"$3", container_name!="POD"}[5m])) by (namespace, pod_name, container_name)`, - //"container_cpu_utilisation_all_wo_podname": `sum(irate(container_cpu_usage_seconds_total{namespace="$1", container_name!="POD"}[5m])) by (namespace, pod_name, container_name)`, - - "container_memory_utilisation_wo_cache": `container_memory_usage_bytes{namespace="$1", pod_name="$2", container_name="$3"} - ignoring(id, image, endpoint, instance, job, name, service) container_memory_cache{namespace="$1", pod_name="$2", container_name="$3"}`, - "container_memory_utilisation_wo_cache_all": `container_memory_usage_bytes{namespace="$1", pod_name="$2", container_name=~"$3", container_name!="POD"} - ignoring(id, image, endpoint, instance, job, name, service) container_memory_cache{namespace="$1", pod_name="$2", container_name=~"$3", container_name!="POD"}`, - "container_memory_utilisation": `container_memory_usage_bytes{namespace="$1", pod_name="$2", container_name="$3"}`, - "container_memory_utilisation_all": `container_memory_usage_bytes{namespace="$1", pod_name="$2", container_name=~"$3", container_name!="POD"}`, - - // tenant - "tenant_cpu_utilisation": `sum(namespace:container_cpu_usage_seconds_total:sum_rate{namespace =~"$1"})`, - "tenant_memory_utilisation": `sum(namespace:container_memory_usage_bytes:sum{namespace =~"$1"})`, - "tenant_memory_utilisation_wo_cache": `sum(namespace:container_memory_usage_bytes_wo_cache:sum{namespace =~"$1"})`, - "tenant_net_bytes_transmitted": `sum(sum by (namespace) (irate(container_network_transmit_bytes_total{namespace=~"$1", pod_name!="", interface="eth0", job="kubelet"}[2m])))`, - "tenant_net_bytes_received": `sum(sum by (namespace) (irate(container_network_receive_bytes_total{namespace=~"$1", pod_name!="", interface="eth0", job="kubelet"}[2m])))`, - "tenant_pod_count": `sum(count(kube_pod_info{job="kube-state-metrics", namespace=~"$1"}) by (namespace))`, -} diff --git a/pkg/models/metrics/metricscollector.go b/pkg/models/metrics/metricscollector.go new file mode 100755 index 000000000..d534135dc --- /dev/null +++ b/pkg/models/metrics/metricscollector.go @@ -0,0 +1,539 @@ +/* +Copyright 2018 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 metrics + +import ( + "encoding/json" + "regexp" + "strings" + + "github.com/emicklei/go-restful" + "github.com/golang/glog" + + "time" + + "github.com/pkg/errors" + "k8s.io/api/core/v1" + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "sync" + + "k8s.io/apimachinery/pkg/labels" + v12 "k8s.io/client-go/listers/core/v1" + + "kubesphere.io/kubesphere/pkg/client" + "kubesphere.io/kubesphere/pkg/models" + "kubesphere.io/kubesphere/pkg/models/controllers" + "kubesphere.io/kubesphere/pkg/models/workspaces" +) + +func getPodNameRegexInWorkload(request *restful.Request) string { + promql := MakeWorkloadRule(request) + res := client.SendPrometheusRequest(request, promql) + data := []byte(res) + var dat CommonMetricsResult + jsonErr := json.Unmarshal(data, &dat) + if jsonErr != nil { + glog.Errorln("json parse failed", jsonErr) + } + var podNames []string + for _, x := range dat.Data.Result { + podName := x.KubePodMetric.Pod + podNames = append(podNames, podName) + } + podNamesFilter := "^(" + strings.Join(podNames, "|") + ")$" + return podNamesFilter +} + +func MonitorWorkloadSingleMetric(request *restful.Request, metricsName string) *FormatedMetric { + nsName := strings.Trim(request.PathParameter("ns_name"), " ") + podNamesFilter := getPodNameRegexInWorkload(request) + newPromql := MakePodPromQL(request, []string{metricsName, nsName, "", "", podNamesFilter}) + podMetrics := client.SendPrometheusRequest(request, newPromql) + cleanedJson := ReformatJson(podMetrics, metricsName) + return cleanedJson +} + +func MonitorPodSingleMetric(request *restful.Request, metricsName string) *FormatedMetric { + nsName := strings.Trim(request.PathParameter("ns_name"), " ") + nodeID := strings.Trim(request.PathParameter("node_id"), " ") + podName := strings.Trim(request.PathParameter("pod_name"), " ") + podFilter := strings.Trim(request.QueryParameter("pods_filter"), " ") + params := []string{metricsName, nsName, nodeID, podName, podFilter} + promql := MakePodPromQL(request, params) + if promql != "" { + res := client.SendPrometheusRequest(request, promql) + cleanedJson := ReformatJson(res, metricsName) + return cleanedJson + } + return nil +} + +func MonitorNamespaceSingleMetric(request *restful.Request, metricsName string) *FormatedMetric { + recordingRule := MakeNamespacePromQL(request, metricsName) + res := client.SendPrometheusRequest(request, recordingRule) + cleanedJson := ReformatJson(res, metricsName) + return cleanedJson +} + +// maybe this function is time consuming +func ReformatJson(metric string, metricsName string) *FormatedMetric { + var formatMetric FormatedMetric + err := json.Unmarshal([]byte(metric), &formatMetric) + if err != nil { + glog.Errorln("Unmarshal metric json failed", err) + } + if formatMetric.MetricName == "" { + formatMetric.MetricName = metricsName + } + // retrive metrics success + if formatMetric.Status == MetricStatusSuccess { + result := formatMetric.Data.Result + for _, res := range result { + metric, ok := res[ResultItemMetric] + me := metric.(map[string]interface{}) + if ok { + delete(me, "__name__") + } + } + } + return &formatMetric +} + +func collectNodeorClusterMetrics(request *restful.Request, metricsName string, ch chan<- *FormatedMetric) { + metric := MonitorNodeorClusterSingleMetric(request, metricsName) + ch <- metric +} + +func collectNamespaceMetrics(request *restful.Request, metricsName string, ch chan<- *FormatedMetric) { + metric := MonitorNamespaceSingleMetric(request, metricsName) + ch <- metric +} + +func collectWorkloadMetrics(request *restful.Request, metricsName string, ch chan<- *FormatedMetric) { + metricsName = strings.TrimLeft(metricsName, "workload_") + metric := MonitorWorkloadSingleMetric(request, metricsName) + ch <- metric +} + +func collectWorkspaceMetrics(request *restful.Request, metricsName string, namespaceList []string, ch chan<- *FormatedMetric) { + mertic := monitorWorkspaceSingleMertic(request, metricsName, namespaceList) + ch <- mertic +} + +func collectPodMetrics(request *restful.Request, metricsName string, ch chan<- *FormatedMetric) { + metric := MonitorPodSingleMetric(request, metricsName) + ch <- metric +} + +func monitorWorkspaceSingleMertic(request *restful.Request, metricsName string, namespaceList []string) *FormatedMetric { + namespaceRe2 := "^(" + strings.Join(namespaceList, "|") + ")$" + newpromql := MakeWorkspacePromQL(metricsName, namespaceRe2) + podMetrics := client.SendPrometheusRequest(request, newpromql) + cleanedJson := ReformatJson(podMetrics, metricsName) + return cleanedJson +} + +func filterNamespace(request *restful.Request, namespaceList []string) []string { + var newNSlist []string + nsFilter := strings.Trim(request.QueryParameter("namespaces_filter"), " ") + if nsFilter == "" { + nsFilter = ".*" + } + for _, ns := range namespaceList { + bol, _ := regexp.MatchString(nsFilter, ns) + if bol { + newNSlist = append(newNSlist, ns) + } + } + return newNSlist +} + +func MonitorAllMetrics(request *restful.Request) FormatedLevelMetric { + metricsName := strings.Trim(request.QueryParameter("metrics_filter"), " ") + if metricsName == "" { + metricsName = ".*" + } + path := request.SelectedRoutePath() + sourceType := path[strings.LastIndex(path, "/")+1 : len(path)-1] + if strings.Contains(path, MetricLevelWorkload) { + sourceType = MetricLevelWorkload + } else if strings.Contains(path, MetricLevelWorkspace) { + sourceType = MetricLevelWorkspace + } + var ch = make(chan *FormatedMetric, 10) + for _, metricName := range MetricsNames { + bol, err := regexp.MatchString(metricsName, metricName) + if !bol { + continue + } + if err != nil { + glog.Errorln("regex match failed", err) + continue + } + if strings.HasPrefix(metricName, sourceType) { + if sourceType == MetricLevelCluster || sourceType == MetricLevelNode { + go collectNodeorClusterMetrics(request, metricName, ch) + } else if sourceType == MetricLevelNamespace { + go collectNamespaceMetrics(request, metricName, ch) + } else if sourceType == MetricLevelPod { + go collectPodMetrics(request, metricName, ch) + } else if sourceType == MetricLevelWorkload { + go collectWorkloadMetrics(request, metricName, ch) + } else if sourceType == MetricLevelWorkspace { + name := request.PathParameter("workspace_name") + namespaces, err := workspaces.WorkspaceNamespaces(name) + + if err != nil { + glog.Errorln(err) + } + namespaces = filterNamespace(request, namespaces) + go collectWorkspaceMetrics(request, metricName, namespaces, ch) + } + } + } + var metricsArray []FormatedMetric + var tempJson *FormatedMetric + for _, k := range MetricsNames { + bol, err := regexp.MatchString(metricsName, k) + if !bol { + continue + } + if err != nil { + glog.Errorln("regex match failed") + continue + } + if strings.HasPrefix(k, sourceType) { + tempJson = <-ch + if tempJson != nil { + metricsArray = append(metricsArray, *tempJson) + } + } + } + return FormatedLevelMetric{ + MetricsLevel: sourceType, + Results: metricsArray, + } +} + +func MonitorWorkspaceUserInfo(req *restful.Request) FormatedLevelMetric { + + var metricsArray []FormatedMetric + timestamp := time.Now().Unix() + + wg := sync.WaitGroup{} + var orgResultItem FormatedMetric + var dvpResultItem FormatedMetric + var projResultItem FormatedMetric + var actResultItem FormatedMetric + + wg.Add(4) + + go func() { + orgNums, errOrg := workspaces.GetAllOrgNums() + orgResultItem = getSpecificMetricItem(timestamp, MetricNameWorkspaceAllOrganizationCount, WorkspaceResourceKindOrganization, orgNums, errOrg) + wg.Done() + }() + go func() { + devOpsProjectNums, errDevops := workspaces.GetAllDevOpsProjectsNums() + dvpResultItem = getSpecificMetricItem(timestamp, MetricNameWorkspaceAllDevopsCount, WorkspaceResourceKindDevops, devOpsProjectNums, errDevops) + wg.Done() + }() + go func() { + projNums, errProj := workspaces.GetAllProjectNums() + projResultItem = getSpecificMetricItem(timestamp, MetricNameWorkspaceAllProjectCount, WorkspaceResourceKindNamespace, projNums, errProj) + wg.Done() + }() + go func() { + actNums, errAct := workspaces.GetAllAccountNums() + actResultItem = getSpecificMetricItem(timestamp, MetricNameWorkspaceAllAccountCount, WorkspaceResourceKindAccount, actNums, errAct) + wg.Done() + }() + + wg.Wait() + metricsArray = append(metricsArray, orgResultItem, dvpResultItem, projResultItem, actResultItem) + + return FormatedLevelMetric{ + MetricsLevel: MetricLevelWorkspace, + Results: metricsArray, + } +} + +//func getWorkspaceMetricItem(timestamp int64, namespaceNums int64, resourceName string, err error) FormatedMetric { +// var fMetric FormatedMetric +// fMetric.Data.ResultType = ResultTypeVector +// fMetric.MetricName = MetricNameWorkspaceInfoCount +// fMetric.Status = MetricStatusSuccess +// if err != nil { +// fMetric.Status = MetricStatusError +// } +// resultItem := make(map[string]interface{}) +// tmp := make(map[string]string) +// tmp[ResultItemMetricResource] = resourceName +// resultItem[ResultItemMetric] = tmp +// resultItem[ResultItemValue] = []interface{}{timestamp, strconv.FormatInt(namespaceNums, 10)} +// return fMetric +//} + +func MonitorWorkspaceResourceLevelMetrics(request *restful.Request) FormatedLevelMetric { + wsName := request.PathParameter("workspace_name") + namspaces, errNs := workspaces.WorkspaceNamespaces(wsName) + + devOpsProjects, errDevOps := workspaces.GetDevOpsProjects(wsName) + members, errMemb := workspaces.GetOrgMembers(wsName) + roles, errRole := workspaces.GetOrgRoles(wsName) + + var fMetricsArray []FormatedMetric + timestamp := int64(time.Now().Unix()) + namespaces, noneExistentNs := getExistingNamespace(namspaces) + if len(noneExistentNs) != 0 { + nsStr := strings.Join(noneExistentNs, "|") + errStr := "the namespaces " + nsStr + " do not exist" + if errNs == nil { + errNs = errors.New(errStr) + } else { + errNs = errors.New(errNs.Error() + "\t" + errStr) + } + } + + // add namespaces(project) metric + nsMetrics := getSpecificMetricItem(timestamp, MetricNameWorkspaceNamespaceCount, WorkspaceResourceKindNamespace, len(namespaces), errNs) + // add devops metric + devopsMetrics := getSpecificMetricItem(timestamp, MetricNameWorkspaceDevopsCount, WorkspaceResourceKindDevops, len(devOpsProjects), errDevOps) + // add member metric + memberMetrics := getSpecificMetricItem(timestamp, MetricNameWorkspaceMemberCount, WorkspaceResourceKindMember, len(members), errMemb) + // add role metric + roleMetrics := getSpecificMetricItem(timestamp, MetricNameWorkspaceRoleCount, WorkspaceResourceKindRole, len(roles), errRole) + // add workloads count metric + wlMetrics := getWorkspaceWorkloadCountMetrics(namespaces) + // add pods count metric + podsCountMetrics := getWorkspacePodsCountMetrics(request, namespaces) + fMetricsArray = append(fMetricsArray, nsMetrics, devopsMetrics, memberMetrics, roleMetrics, wlMetrics, *podsCountMetrics) + + return FormatedLevelMetric{ + MetricsLevel: MetricLevelWorkspace, + Results: fMetricsArray, + } +} + +func getWorkspacePodsCountMetrics(request *restful.Request, namespaces []string) *FormatedMetric { + metricName := MetricNameNamespacePodCount + var recordingRule = RulePromQLTmplMap[metricName] + nsFilter := "^(" + strings.Join(namespaces, "|") + ")$" + recordingRule = strings.Replace(recordingRule, "$1", nsFilter, -1) + res := client.SendPrometheusRequest(request, recordingRule) + cleanedJson := ReformatJson(res, metricName) + return cleanedJson +} + +func getWorkspaceWorkloadCountMetrics(namespaces []string) FormatedMetric { + var wlQuotaMetrics models.ResourceQuota + wlQuotaMetrics.NameSpace = strings.Join(namespaces, "|") + wlQuotaMetrics.Data.Used = make(v1.ResourceList, 1) + wlQuotaMetrics.Data.Hard = make(v1.ResourceList, 1) + for _, ns := range namespaces { + quotaMetric, err := models.GetNamespaceQuota(ns) + if err != nil { + glog.Errorln(err) + continue + } + // sum all resources used along namespaces + quotaUsed := quotaMetric.Data.Used + for resourceName, quantity := range quotaUsed { + if _, ok := wlQuotaMetrics.Data.Used[resourceName]; ok { + tmpQuantity := wlQuotaMetrics.Data.Used[v1.ResourceName(resourceName)] + tmpQuantity.Add(quantity) + wlQuotaMetrics.Data.Used[v1.ResourceName(resourceName)] = tmpQuantity + } else { + wlQuotaMetrics.Data.Used[v1.ResourceName(resourceName)] = quantity.DeepCopy() + } + } + + // sum all resources hard along namespaces + quotaHard := quotaMetric.Data.Hard + for resourceName, quantity := range quotaHard { + if _, ok := wlQuotaMetrics.Data.Hard[resourceName]; ok { + tmpQuantity := wlQuotaMetrics.Data.Hard[v1.ResourceName(resourceName)] + tmpQuantity.Add(quantity) + wlQuotaMetrics.Data.Hard[v1.ResourceName(resourceName)] = tmpQuantity + } else { + wlQuotaMetrics.Data.Hard[v1.ResourceName(resourceName)] = quantity.DeepCopy() + } + } + } + wlMetrics := convertQuota2MetricStruct(&wlQuotaMetrics) + return wlMetrics +} + +func getSpecificMetricItem(timestamp int64, metricName string, kind string, count int, err error) FormatedMetric { + var nsMetrics FormatedMetric + nsMetrics.MetricName = metricName + nsMetrics.Data.ResultType = ResultTypeVector + resultItem := make(map[string]interface{}) + tmp := make(map[string]string) + tmp[ResultItemMetricResource] = kind + if err == nil { + nsMetrics.Status = MetricStatusSuccess + } else { + nsMetrics.Status = MetricStatusError + resultItem["errorinfo"] = err.Error() + } + + resultItem[ResultItemMetric] = tmp + resultItem[ResultItemValue] = []interface{}{timestamp, count} + nsMetrics.Data.Result = make([]map[string]interface{}, 1) + nsMetrics.Data.Result[0] = resultItem + return nsMetrics +} + +func MonitorNodeorClusterSingleMetric(request *restful.Request, metricsName string) *FormatedMetric { + // support cluster node statistic, include healthy nodes and unhealthy nodes + var res string + var fMetric FormatedMetric + timestamp := int64(time.Now().Unix()) + + if metricsName == "cluster_node_online" { + onlineNodes, _ := getNodeHealthyConditionMetric() + fMetric = getSpecificMetricItem(timestamp, MetricNameClusterHealthyNodeCount, "node_count", len(onlineNodes), nil) + } else if metricsName == "cluster_node_offline" { + _, offlineNodes := getNodeHealthyConditionMetric() + fMetric = getSpecificMetricItem(timestamp, MetricNameClusterUnhealthyNodeCount, "node_count", len(offlineNodes), nil) + } else if metricsName == "cluster_node_total" { + onlineNodes, offlineNodes := getNodeHealthyConditionMetric() + fMetric = getSpecificMetricItem(timestamp, MetricNameClusterNodeCount, "node_count", len(onlineNodes)+len(offlineNodes), nil) + } else { + recordingRule := MakeNodeorClusterRule(request, metricsName) + res = client.SendPrometheusRequest(request, recordingRule) + fMetric = *ReformatJson(res, metricsName) + } + return &fMetric +} + +func getNodeHealthyConditionMetric() ([]string, []string) { + nodeList, err := client.NewK8sClient().CoreV1().Nodes().List(metaV1.ListOptions{}) + if err != nil { + glog.Errorln(err) + return nil, nil + } + var onlineNodes []string + var offlineNodes []string + for _, node := range nodeList.Items { + nodeName := node.Labels["kubernetes.io/hostname"] + nodeRole := node.Labels["role"] + bol := true + for _, cond := range node.Status.Conditions { + if cond.Type == "Ready" && cond.Status == "Unknown" { + bol = false + break + } + } + if nodeRole != "log" { + if bol { + // reachable node + onlineNodes = append(onlineNodes, nodeName) + } else { + // unreachable node + offlineNodes = append(offlineNodes, nodeName) + } + } + } + return onlineNodes, offlineNodes +} + +func getExistingNamespace(namespaces []string) ([]string, []string) { + namespaceMap, err := getAllNamespace() + var existedNs []string + var noneExistedNs []string + if err != nil { + return namespaces, nil + } + for _, ns := range namespaces { + if _, ok := namespaceMap[ns]; ok { + existedNs = append(existedNs, ns) + } else { + noneExistedNs = append(noneExistedNs, ns) + } + } + return existedNs, noneExistedNs +} + +func getAllNamespace() (map[string]int, error) { + lister := controllers.ResourceControllers.Controllers[controllers.Namespaces].Lister().(v12.NamespaceLister) + nsList, err := lister.List(labels.Everything()) + if err != nil { + glog.Errorln(err) + return nil, err + } + namespaceMap := make(map[string]int) + for _, item := range nsList { + namespaceMap[item.Name] = 0 + } + return namespaceMap, nil +} + +func MonitorWorkloadCount(request *restful.Request) FormatedMetric { + namespace := strings.Trim(request.PathParameter("ns_name"), " ") + + quotaMetric, err := models.GetNamespaceQuota(namespace) + fMetric := convertQuota2MetricStruct(quotaMetric) + + // whether the namespace in request parameters exists? + namespaceMap, e := getAllNamespace() + _, ok := namespaceMap[namespace] + if e != nil { + ok = true + } + if !ok || err != nil { + fMetric.Status = MetricStatusError + fMetric.Data.ResultType = "" + errInfo := make(map[string]interface{}) + if err != nil { + errInfo["errormsg"] = err.Error() + } else { + errInfo["errormsg"] = "namespace " + namespace + " does not exist" + } + fMetric.Data.Result = []map[string]interface{}{errInfo} + } + + return fMetric +} + +func convertQuota2MetricStruct(quotaMetric *models.ResourceQuota) FormatedMetric { + var fMetric FormatedMetric + fMetric.MetricName = MetricNameWorkloadCount + fMetric.Status = MetricStatusSuccess + fMetric.Data.ResultType = ResultTypeVector + timestamp := int64(time.Now().Unix()) + var resultItems []map[string]interface{} + + hardMap := make(map[string]string) + for resourceName, v := range quotaMetric.Data.Hard { + hardMap[resourceName.String()] = v.String() + } + + for resourceName, v := range quotaMetric.Data.Used { + resultItem := make(map[string]interface{}) + tmp := make(map[string]string) + tmp[ResultItemMetricResource] = resourceName.String() + resultItem[ResultItemMetric] = tmp + resultItem[ResultItemValue] = []interface{}{timestamp, hardMap[resourceName.String()], v.String()} + resultItems = append(resultItems, resultItem) + } + + fMetric.Data.Result = resultItems + return fMetric +} diff --git a/pkg/models/metrics/metricsconst.go b/pkg/models/metrics/metricsconst.go new file mode 100755 index 000000000..a43cd52b5 --- /dev/null +++ b/pkg/models/metrics/metricsconst.go @@ -0,0 +1,236 @@ +/* +Copyright 2018 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 metrics + +const ( + ResultTypeVector = "vector" + ResultTypeMatrix = "matrix" + MetricStatusError = "error" + MetricStatusSuccess = "success" + ResultItemMetric = "metric" + ResultItemMetricResource = "resource" + ResultItemValue = "value" +) + +const ( + MetricNameWorkloadCount = "workload_count" + MetricNameNamespacePodCount = "namespace_pod_count" + + MetricNameWorkspaceAllOrganizationCount = "workspace_all_organization_count" + MetricNameWorkspaceAllAccountCount = "workspace_all_account_count" + MetricNameWorkspaceAllProjectCount = "workspace_all_project_count" + MetricNameWorkspaceAllDevopsCount = "workspace_all_devops_project_count" + + MetricNameWorkspaceNamespaceCount = "workspace_namespace_count" + MetricNameWorkspaceDevopsCount = "workspace_devops_project_count" + MetricNameWorkspaceMemberCount = "workspace_member_count" + MetricNameWorkspaceRoleCount = "workspace_role_count" + + MetricNameClusterHealthyNodeCount = "cluster_node_online" + MetricNameClusterUnhealthyNodeCount = "cluster_node_offline" + MetricNameClusterNodeCount = "cluster_node_total" +) + +const ( + WorkspaceResourceKindOrganization = "organization" + WorkspaceResourceKindAccount = "account" + WorkspaceResourceKindNamespace = "namespace" + WorkspaceResourceKindDevops = "devops" + WorkspaceResourceKindMember = "member" + WorkspaceResourceKindRole = "role" +) + +const ( + MetricLevelCluster = "cluster" + MetricLevelNode = "node" + MetricLevelWorkspace = "workspace" + MetricLevelNamespace = "namespace" + MetricLevelPod = "pod" + MetricLevelContainer = "container" + MetricLevelWorkload = "workload" +) + +type MetricMap map[string]string + +var MetricsNames = []string{ + "cluster_cpu_utilisation", + "cluster_cpu_usage", + "cluster_cpu_total", + "cluster_memory_utilisation", + "cluster_pod_count", + "cluster_memory_bytes_available", + "cluster_memory_bytes_total", + "cluster_memory_bytes_usage", + "cluster_net_utilisation", + "cluster_net_bytes_transmitted", + "cluster_net_bytes_received", + "cluster_disk_read_iops", + "cluster_disk_write_iops", + "cluster_disk_read_throughput", + "cluster_disk_write_throughput", + "cluster_disk_size_usage", + "cluster_disk_size_utilisation", + "cluster_disk_size_capacity", + "cluster_disk_size_available", + "cluster_node_online", + "cluster_node_offline", + "cluster_node_total", + + "node_cpu_utilisation", + "node_cpu_total", + "node_cpu_usage", + "node_memory_utilisation", + "node_memory_bytes_usage", + "node_memory_bytes_available", + "node_memory_bytes_total", + "node_net_utilisation", + "node_net_bytes_transmitted", + "node_net_bytes_received", + "node_disk_read_iops", + "node_disk_write_iops", + "node_disk_read_throughput", + "node_disk_write_throughput", + "node_disk_size_capacity", + "node_disk_size_available", + "node_disk_size_usage", + "node_disk_size_utilisation", + "node_pod_count", + "node_pod_quota", + + "namespace_cpu_usage", + "namespace_memory_usage", + "namespace_memory_usage_wo_cache", + "namespace_net_bytes_transmitted", + "namespace_net_bytes_received", + "namespace_pod_count", + + "pod_cpu_usage", + "pod_memory_usage", + "pod_memory_usage_wo_cache", + "pod_net_bytes_transmitted", + "pod_net_bytes_received", + + "workload_pod_cpu_usage", + "workload_pod_memory_usage", + "workload_pod_memory_usage_wo_cache", + "workload_pod_net_bytes_transmitted", + "workload_pod_net_bytes_received", + //"container_cpu_usage", + //"container_memory_usage_wo_cache", + //"container_memory_usage", + + "workspace_cpu_usage", + "workspace_memory_usage", + "workspace_memory_usage_wo_cache", + "workspace_net_bytes_transmitted", + "workspace_net_bytes_received", + "workspace_pod_count", +} + +var RulePromQLTmplMap = MetricMap{ + //cluster + "cluster_cpu_utilisation": ":node_cpu_utilisation:avg1m", + "cluster_cpu_usage": `sum (irate(container_cpu_usage_seconds_total{job="kubelet", image!=""}[5m]))`, + "cluster_cpu_total": "sum(node:node_num_cpu:sum)", + "cluster_memory_utilisation": ":node_memory_utilisation:", + "cluster_pod_count": `count(kube_pod_info unless on(pod) kube_pod_completion_time unless on(node) kube_node_labels{label_role="log"})`, + "cluster_memory_bytes_available": "sum(node:node_memory_bytes_available:sum)", + "cluster_memory_bytes_total": "sum(node:node_memory_bytes_total:sum)", + "cluster_memory_bytes_usage": "sum(node:node_memory_bytes_total:sum) - sum(node:node_memory_bytes_available:sum)", + "cluster_net_utilisation": ":node_net_utilisation:sum_irate", + "cluster_net_bytes_transmitted": "sum(node:node_net_bytes_transmitted:sum_irate)", + "cluster_net_bytes_received": "sum(node:node_net_bytes_received:sum_irate)", + "cluster_disk_read_iops": "sum(node:data_volume_iops_reads:sum)", + "cluster_disk_write_iops": "sum(node:data_volume_iops_writes:sum)", + "cluster_disk_read_throughput": "sum(node:data_volume_throughput_bytes_read:sum)", + "cluster_disk_write_throughput": "sum(node:data_volume_throughput_bytes_written:sum)", + "cluster_disk_size_usage": `sum(sum by (node) ((node_filesystem_size{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:)) - sum(sum by (node) ((node_filesystem_avail{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:))`, + "cluster_disk_size_utilisation": `(sum(sum by (node) ((node_filesystem_size{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:)) - sum(sum by (node) ((node_filesystem_avail{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:))) / sum(sum by (node) ((node_filesystem_size{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:))`, + "cluster_disk_size_capacity": `sum(sum by (node) ((node_filesystem_size{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:))`, + "cluster_disk_size_available": `sum(sum by (node) ((node_filesystem_avail{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:))`, + + //node + "node_cpu_utilisation": "node:node_cpu_utilisation:avg1m", + "node_cpu_total": "node:node_num_cpu:sum", + "node_memory_utilisation": "node:node_memory_utilisation:", + "node_memory_bytes_available": "node:node_memory_bytes_available:sum", + "node_memory_bytes_total": "node:node_memory_bytes_total:sum", + // Node network utilisation (bytes received + bytes transmitted per second) + "node_net_utilisation": "node:node_net_utilisation:sum_irate", + // Node network bytes transmitted per second + "node_net_bytes_transmitted": "node:node_net_bytes_transmitted:sum_irate", + // Node network bytes received per second + "node_net_bytes_received": "node:node_net_bytes_received:sum_irate", + + // node:data_volume_iops_reads:sum{node=~"i-5xcldxos|i-6soe9zl1"} + "node_disk_read_iops": "node:data_volume_iops_reads:sum", + // node:data_volume_iops_writes:sum{node=~"i-5xcldxos|i-6soe9zl1"} + "node_disk_write_iops": "node:data_volume_iops_writes:sum", + // node:data_volume_throughput_bytes_read:sum{node=~"i-5xcldxos|i-6soe9zl1"} + "node_disk_read_throughput": "node:data_volume_throughput_bytes_read:sum", + // node:data_volume_throughput_bytes_written:sum{node=~"i-5xcldxos|i-6soe9zl1"} + "node_disk_write_throughput": "node:data_volume_throughput_bytes_written:sum", + + "node_disk_size_capacity": `sum by (node) ((node_filesystem_size{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:$1)`, + "node_disk_size_available": `sum by (node) ((node_filesystem_avail{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:$1)`, + "node_disk_size_usage": `sum by (node) ((node_filesystem_size{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:$1) -sum by (node) ((node_filesystem_avail{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:$1)`, + "node_disk_size_utilisation": `sum by (node) (((node_filesystem_size{mountpoint="/", job="node-exporter"} - node_filesystem_avail{mountpoint="/", job="node-exporter"}) / node_filesystem_size{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:$1)`, + "node_pod_count": `count(kube_pod_info$1 unless on(pod) kube_pod_completion_time) by (node)`, + // without log node: unless on(node) kube_node_labels{label_role="log"} + "node_pod_quota": `sum(kube_node_status_capacity_pods$1) by (node)`, + "node_cpu_usage": `sum by (node) (label_join(irate(container_cpu_usage_seconds_total{job="kubelet", image!=""}[5m]), "pod", " ", "pod_name") * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:$1)`, + "node_memory_bytes_usage": "node:node_memory_bytes_total:sum$1 - node:node_memory_bytes_available:sum$1", + + //namespace + "namespace_cpu_usage": `namespace:container_cpu_usage_seconds_total:sum_rate{namespace=~"$1"}`, + "namespace_memory_usage": `namespace:container_memory_usage_bytes:sum{namespace=~"$1"}`, + "namespace_memory_usage_wo_cache": `namespace:container_memory_usage_bytes_wo_cache:sum{namespace=~"$1"}`, + "namespace_net_bytes_transmitted": `sum by (namespace) (irate(container_network_transmit_bytes_total{namespace=~"$1", pod_name!="", interface="eth0", job="kubelet"}[5m]))`, + "namespace_net_bytes_received": `sum by (namespace) (irate(container_network_receive_bytes_total{namespace=~"$1", pod_name!="", interface="eth0", job="kubelet"}[5m]))`, + "namespace_pod_count": `count(kube_pod_info{namespace=~"$1"} unless on(pod) kube_pod_completion_time) by (namespace)`, + + // pod + "pod_cpu_usage": `sum(irate(container_cpu_usage_seconds_total{job="kubelet", namespace="$1", pod_name="$2", image!=""}[5m])) by (namespace, pod_name)`, + "pod_memory_usage": `sum(container_memory_usage_bytes{job="kubelet", namespace="$1", pod_name="$2", image!=""}) by (namespace, pod_name)`, + "pod_memory_usage_wo_cache": `sum(container_memory_usage_bytes{job="kubelet", namespace="$1", pod_name="$2", image!=""} - container_memory_cache{job="kubelet", namespace="$1", pod_name="$2",image!=""}) by (namespace, pod_name)`, + "pod_net_bytes_transmitted": `sum by (namespace, pod_name) (irate(container_network_transmit_bytes_total{namespace="$1", pod_name!="", pod_name="$2", interface="eth0", job="kubelet"}[5m]))`, + "pod_net_bytes_received": `sum by (namespace, pod_name) (irate(container_network_receive_bytes_total{namespace="$1", pod_name!="", pod_name="$2", interface="eth0", job="kubelet"}[5m]))`, + + "pod_cpu_usage_all": `sum(irate(container_cpu_usage_seconds_total{job="kubelet", namespace="$1", pod_name=~"$2", image!=""}[5m])) by (namespace, pod_name)`, + "pod_memory_usage_all": `sum(container_memory_usage_bytes{job="kubelet", namespace="$1", pod_name=~"$2", image!=""}) by (namespace, pod_name)`, + "pod_memory_usage_wo_cache_all": `sum(container_memory_usage_bytes{job="kubelet", namespace="$1", pod_name=~"$2", image!=""} - container_memory_cache{job="kubelet", namespace="$1", pod_name=~"$2", image!=""}) by (namespace, pod_name)`, + "pod_net_bytes_transmitted_all": `sum by (namespace, pod_name) (irate(container_network_transmit_bytes_total{namespace="$1", pod_name!="", pod_name=~"$2", interface="eth0", job="kubelet"}[5m]))`, + "pod_net_bytes_received_all": `sum by (namespace, pod_name) (irate(container_network_receive_bytes_total{namespace="$1", pod_name!="", pod_name=~"$2", interface="eth0", job="kubelet"}[5m]))`, + + "pod_cpu_usage_node": `sum by (node, pod) (label_join(irate(container_cpu_usage_seconds_total{job="kubelet",pod_name=~"$2", image!=""}[5m]), "pod", " ", "pod_name") * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:{node=~"$3"})`, + "pod_memory_usage_node": `sum by (node, pod) (label_join(container_memory_usage_bytes{job="kubelet",pod_name=~"$2", image!=""}, "pod", " ", "pod_name") * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:{node=~"$3"})`, + "pod_memory_usage_wo_cache_node": `sum by (node, pod) ((label_join(container_memory_usage_bytes{job="kubelet",pod_name=~"$2", image!=""}, "pod", " ", "pod_name") - label_join(container_memory_cache{job="kubelet",pod_name=~"$2", image!=""}, "pod", " ", "pod_name")) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:{node=~"$3"})`, + + // container + "container_cpu_usage": `sum(irate(container_cpu_usage_seconds_total{namespace="$1", pod_name="$2", container_name="$3"}[5m])) by (namespace, pod_name, container_name)`, + "container_cpu_usage_all": `sum(irate(container_cpu_usage_seconds_total{namespace="$1", pod_name="$2", container_name=~"$3", container_name!="POD"}[5m])) by (namespace, pod_name, container_name)`, + + "container_memory_usage_wo_cache": `container_memory_usage_bytes{namespace="$1", pod_name="$2", container_name="$3"} - ignoring(id, image, endpoint, instance, job, name, service) container_memory_cache{namespace="$1", pod_name="$2", container_name="$3"}`, + "container_memory_usage_wo_cache_all": `container_memory_usage_bytes{namespace="$1", pod_name="$2", container_name=~"$3", container_name!="POD"} - ignoring(id, image, endpoint, instance, job, name, service) container_memory_cache{namespace="$1", pod_name="$2", container_name=~"$3", container_name!="POD"}`, + "container_memory_usage": `container_memory_usage_bytes{namespace="$1", pod_name="$2", container_name="$3"}`, + "container_memory_usage_all": `container_memory_usage_bytes{namespace="$1", pod_name="$2", container_name=~"$3", container_name!="POD"}`, + + // enterprise + "workspace_cpu_usage": `sum(namespace:container_cpu_usage_seconds_total:sum_rate{namespace =~"$1"})`, + "workspace_memory_usage": `sum(namespace:container_memory_usage_bytes:sum{namespace =~"$1"})`, + "workspace_memory_usage_wo_cache": `sum(namespace:container_memory_usage_bytes_wo_cache:sum{namespace =~"$1"})`, + "workspace_net_bytes_transmitted": `sum(sum by (namespace) (irate(container_network_transmit_bytes_total{namespace=~"$1", pod_name!="", interface="eth0", job="kubelet"}[5m])))`, + "workspace_net_bytes_received": `sum(sum by (namespace) (irate(container_network_receive_bytes_total{namespace=~"$1", pod_name!="", interface="eth0", job="kubelet"}[5m])))`, + "workspace_pod_count": `sum(count(kube_pod_info{namespace=~"$1"} unless on(pod) kube_pod_completion_time) by (namespace))`, +} diff --git a/pkg/models/metrics/metrics_rule.go b/pkg/models/metrics/metricsrule.go old mode 100644 new mode 100755 similarity index 94% rename from pkg/models/metrics/metrics_rule.go rename to pkg/models/metrics/metricsrule.go index 14be69488..ee3ee3b70 --- a/pkg/models/metrics/metrics_rule.go +++ b/pkg/models/metrics/metricsrule.go @@ -67,6 +67,12 @@ func MakeWorkloadRule(request *restful.Request) string { return rule } +func MakeWorkspacePromQL(metricsName string, namespaceRe2 string) string { + promql := RulePromQLTmplMap[metricsName] + promql = strings.Replace(promql, "$1", namespaceRe2, -1) + return promql +} + func MakeContainerPromQL(request *restful.Request) string { nsName := strings.Trim(request.PathParameter("ns_name"), " ") poName := strings.Trim(request.PathParameter("pod_name"), " ") @@ -169,9 +175,8 @@ func MakeNodeorClusterRule(request *restful.Request, metricsName string) string if nodesFilter == "" { nodesFilter = ".*" } - if strings.Contains(metricsName, "disk") && (!(strings.Contains(metricsName, "read") || strings.Contains(metricsName, "write"))) { + if strings.Contains(metricsName, "disk_size") || strings.Contains(metricsName, "pod") || strings.Contains(metricsName, "usage") { // disk size promql - nodesFilter := "" if nodeID != "" { nodesFilter = "{" + "node" + "=" + "\"" + nodeID + "\"" + "}" } else { diff --git a/pkg/models/metrics/metrics_struct.go b/pkg/models/metrics/metricsstruct.go old mode 100644 new mode 100755 similarity index 100% rename from pkg/models/metrics/metrics_struct.go rename to pkg/models/metrics/metricsstruct.go diff --git a/pkg/models/metrics/nodes.go b/pkg/models/metrics/nodes.go old mode 100644 new mode 100755 diff --git a/pkg/models/metrics/pods.go b/pkg/models/metrics/pods.go old mode 100644 new mode 100755 diff --git a/pkg/models/resources.go b/pkg/models/resources.go index ed9e1377c..8ace3ba28 100644 --- a/pkg/models/resources.go +++ b/pkg/models/resources.go @@ -220,6 +220,8 @@ func ListResource(resourceName, conditonSrt, pagingStr, order string) (*Resource } func generateConditionStr(conditions *searchConditions) string { + shouldUseAnd := false + shouldUseBrackets := false conditionStr := "" if conditions == nil { @@ -242,11 +244,21 @@ func generateConditionStr(conditions *searchConditions) string { } } + if len(conditionStr) > 0 { + shouldUseAnd = true + } + for k, v := range conditions.matchOr { if len(conditionStr) == 0 { conditionStr = fmt.Sprintf("%s = \"%s\" ", k, v) } else { - conditionStr = fmt.Sprintf("%s OR %s = \"%s\" ", conditionStr, k, v) + if shouldUseAnd { + conditionStr = fmt.Sprintf("%s And (%s = \"%s\" ", conditionStr, k, v) + shouldUseBrackets = true + shouldUseAnd = false + } else { + conditionStr = fmt.Sprintf("%s OR %s = \"%s\" ", conditionStr, k, v) + } } } @@ -254,10 +266,20 @@ func generateConditionStr(conditions *searchConditions) string { if len(conditionStr) == 0 { conditionStr = fmt.Sprintf("%s like '%%%s%%' ", k, v) } else { - conditionStr = fmt.Sprintf("%s OR %s like '%%%s%%' ", conditionStr, k, v) + if shouldUseAnd { + conditionStr = fmt.Sprintf("%s And (%s like '%%%s%%' ", conditionStr, k, v) + shouldUseAnd = false + shouldUseBrackets = true + } else { + conditionStr = fmt.Sprintf("%s OR %s like '%%%s%%' ", conditionStr, k, v) + } } } + if shouldUseBrackets { + conditionStr = fmt.Sprintf("%s )", conditionStr) + } + return conditionStr } diff --git a/pkg/models/workspaces/types.go b/pkg/models/workspaces/types.go index 3147dbd18..edc8176c4 100644 --- a/pkg/models/workspaces/types.go +++ b/pkg/models/workspaces/types.go @@ -4,8 +4,9 @@ import "time" type Workspace struct { Group `json:",inline"` - Namespaces []string `json:"namespaces,omitempty"` - DevopsProjects []string `json:"devops_projects,omitempty"` + Admin string `json:"admin,omitempty"` + Namespaces []string `json:"namespaces"` + DevopsProjects []string `json:"devops_projects"` } type UserInvite struct { diff --git a/pkg/models/workspaces/workspaces.go b/pkg/models/workspaces/workspaces.go index 6547517f1..a840c3fc8 100644 --- a/pkg/models/workspaces/workspaces.go +++ b/pkg/models/workspaces/workspaces.go @@ -7,19 +7,21 @@ import ( "io/ioutil" "net/http" - "github.com/jinzhu/gorm" - core "k8s.io/api/core/v1" - k8sErr "k8s.io/apimachinery/pkg/api/errors" - "log" "strings" + "github.com/jinzhu/gorm" + core "k8s.io/api/core/v1" + + "errors" + "regexp" + "github.com/emicklei/go-restful" "k8s.io/api/rbac/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - v13 "k8s.io/client-go/listers/rbac/v1" + clientV1 "k8s.io/client-go/listers/core/v1" "k8s.io/kubernetes/pkg/util/slice" "kubesphere.io/kubesphere/pkg/client" @@ -31,12 +33,6 @@ import ( var WorkSpaceRoles = []string{"admin", "operator", "viewer"} -func UnBindNamespace(workspace string, namespace string) error { - db := client.NewSharedDBClient() - defer db.Close() - return db.Delete(&WorkspaceNSBinding{Workspace: workspace, Namespace: namespace}).Error -} - func UnBindDevopsProject(workspace string, devops string) error { db := client.NewSharedDBClient() defer db.Close() @@ -100,44 +96,85 @@ func CreateDevopsProject(username string, devops DevopsProject) (*DevopsProject, return &project, nil } -func Namespaces(workspace string) ([]*core.Namespace, error) { - db := client.NewSharedDBClient() - defer db.Close() +func ListNamespaceByUser(workspaceName string, username string) ([]*core.Namespace, error) { - var workspaceNSBindings []WorkspaceNSBinding + namespaces, err := Namespaces(workspaceName) - if err := db.Where("workspace = ?", workspace).Find(&workspaceNSBindings).Error; err != nil { + if err != nil { return nil, err } - namespaces := make([]*core.Namespace, 0) + clusterRoles, err := iam.GetClusterRoles(username) - for _, workspaceNSBinding := range workspaceNSBindings { - namespace, err := client.NewK8sClient().CoreV1().Namespaces().Get(workspaceNSBinding.Namespace, meta_v1.GetOptions{}) - if err != nil { - if k8sErr.IsNotFound(err) { - db.Delete(&WorkspaceNSBinding{Workspace: workspace, Namespace: workspaceNSBinding.Namespace}) - } else { + if err != nil { + return nil, err + } + + rules := make([]v1.PolicyRule, 0) + + for _, clusterRole := range clusterRoles { + rules = append(rules, clusterRole.Rules...) + } + + namespacesManager := v1.PolicyRule{APIGroups: []string{"kubesphere.io"}, ResourceNames: []string{workspaceName}, Verbs: []string{"get"}, Resources: []string{"workspaces/namespaces"}} + + if iam.RulesMatchesRequired(rules, namespacesManager) { + return namespaces, nil + } else { + for i := 0; i < len(namespaces); i++ { + roles, err := iam.GetRoles(namespaces[i].Name, username) + if err != nil { return nil, err } - } else { - namespaces = append(namespaces, namespace) + rules := make([]v1.PolicyRule, 0) + for _, role := range roles { + rules = append(rules, role.Rules...) + } + if !iam.RulesMatchesRequired(rules, v1.PolicyRule{APIGroups: []string{""}, ResourceNames: []string{namespaces[i].Name}, Verbs: []string{"get"}, Resources: []string{"namespaces"}}) { + namespaces = append(namespaces[:i], namespaces[i+1:]...) + i-- + } } } return namespaces, nil } +func Namespaces(workspaceName string) ([]*core.Namespace, error) { + + lister := controllers.ResourceControllers.Controllers[controllers.Namespaces].Lister().(clientV1.NamespaceLister) + + namespaces, err := lister.List(labels.SelectorFromSet(labels.Set{"kubesphere.io/workspace": workspaceName})) + + if err != nil { + return nil, err + } + + if namespaces == nil { + return make([]*core.Namespace, 0), nil + } + + return namespaces, nil +} + func BindingDevopsProject(workspace string, devops string) error { db := client.NewSharedDBClient() defer db.Close() return db.Create(&WorkspaceDPBinding{Workspace: workspace, DevOpsProject: devops}).Error } -func DeleteNamespace(namespace string) error { - deletePolicy := meta_v1.DeletePropagationBackground - err := client.NewK8sClient().CoreV1().Namespaces().Delete(namespace, &meta_v1.DeleteOptions{PropagationPolicy: &deletePolicy}) - return err +func DeleteNamespace(workspace string, namespaceName string) error { + namespace, err := client.NewK8sClient().CoreV1().Namespaces().Get(namespaceName, meta_v1.GetOptions{}) + if err != nil { + return err + } + if namespace.Labels != nil && namespace.Labels["kubesphere.io/workspace"] == workspace { + deletePolicy := meta_v1.DeletePropagationBackground + return client.NewK8sClient().CoreV1().Namespaces().Delete(namespaceName, &meta_v1.DeleteOptions{PropagationPolicy: &deletePolicy}) + } else { + return errors.New("resource not found") + } + } func Delete(workspace *Workspace) error { @@ -174,7 +211,7 @@ func Delete(workspace *Workspace) error { func release(workspace *Workspace) error { for _, namespace := range workspace.Namespaces { - err := DeleteNamespace(namespace) + err := DeleteNamespace(workspace.Name, namespace) if err != nil && !apierrors.IsNotFound(err) { return err } @@ -335,7 +372,44 @@ func Detail(name string) (*Workspace, error) { return workspace, nil } -func List(names []string) ([]*Workspace, error) { +// List all workspaces for the current user +func ListByUser(username string) ([]*Workspace, error) { + + clusterRoles, err := iam.GetClusterRoles(username) + + if err != nil { + return nil, err + } + + rules := make([]v1.PolicyRule, 0) + + for _, clusterRole := range clusterRoles { + rules = append(rules, clusterRole.Rules...) + } + + workspacesManager := v1.PolicyRule{APIGroups: []string{"kubesphere.io"}, Verbs: []string{"list", "get"}, Resources: []string{"workspaces"}} + + if iam.RulesMatchesRequired(rules, workspacesManager) { + return fetch(nil) + } else { + workspaceNames := make([]string, 0) + + for _, clusterRole := range clusterRoles { + if regexp.MustCompile("^system:\\w+:(admin|operator|viewer)$").MatchString(clusterRole.Name) { + arr := strings.Split(clusterRole.Name, ":") + workspaceNames = append(workspaceNames, arr[1]) + } + } + + if len(workspaceNames) == 0 { + return make([]*Workspace, 0), nil + } + + return fetch(workspaceNames) + } +} + +func fetch(names []string) ([]*Workspace, error) { url := fmt.Sprintf("http://%s/apis/account.kubesphere.io/v1alpha1/groups", constants.AccountAPIServer) @@ -379,6 +453,7 @@ func List(names []string) ([]*Workspace, error) { } workspaces = append(workspaces, workspace) } + return workspaces, nil } @@ -434,16 +509,16 @@ func DevopsProjects(workspace string) ([]DevopsProject, error) { } func convertGroupToWorkspace(db *gorm.DB, group Group) (*Workspace, error) { - var workspaceNSBindings []WorkspaceNSBinding + namespaces, err := Namespaces(group.Name) - if err := db.Where("workspace = ?", group.Name).Find(&workspaceNSBindings).Error; err != nil { + if err != nil { return nil, err } - namespaces := make([]string, 0) + namespacesNames := make([]string, 0) - for _, workspaceNSBinding := range workspaceNSBindings { - namespaces = append(namespaces, workspaceNSBinding.Namespace) + for _, namespace := range namespaces { + namespacesNames = append(namespacesNames, namespace.Name) } var workspaceDOPBindings []WorkspaceDPBinding @@ -459,7 +534,7 @@ func convertGroupToWorkspace(db *gorm.DB, group Group) (*Workspace, error) { } workspace := Workspace{Group: group} - workspace.Namespaces = namespaces + workspace.Namespaces = namespacesNames workspace.DevopsProjects = devOpsProjects return &workspace, nil } @@ -468,12 +543,6 @@ func CreateNamespace(namespace *core.Namespace) (*core.Namespace, error) { return client.NewK8sClient().CoreV1().Namespaces().Create(namespace) } -func BindingNamespace(workspace string, namespace string) error { - db := client.NewSharedDBClient() - defer db.Close() - return db.Create(&WorkspaceNSBinding{Workspace: workspace, Namespace: namespace}).Error -} - func Invite(workspaceName string, users []UserInvite) error { for _, user := range users { if !slice.ContainsString(WorkSpaceRoles, user.Role, nil) { @@ -523,38 +592,22 @@ func RemoveMembers(workspaceName string, users []string) error { return err } + for i := 0; i < len(workspace.Members); i++ { + if slice.ContainsString(users, workspace.Members[i], nil) { + workspace.Members = append(workspace.Members[:i], workspace.Members[i+1:]...) + i-- + } + } + + workspace, err = Edit(workspace) + + if err != nil { + return err + } + return nil } -//func checkUserExist(username string) (bool, error) { -// result, err := http.Get(fmt.Sprintf("http://%s/apis/account.kubesphere.io/v1alpha1/users?check=%s", constants.AccountAPIServer, username)) -// -// if err != nil { -// return false, err -// } -// -// data, err := ioutil.ReadAll(result.Body) -// -// if err != nil { -// return false, err -// } -// -// if result.StatusCode > 200 { -// return false, ksErr.Wrap(data) -// } -// -// var r map[string]bool -// -// err = json.Unmarshal(data, &r) -// -// if err != nil { -// return false, err -// } -// -// return r["exist"], nil -// -//} - func Roles(workspace *Workspace) ([]*v1.ClusterRole, error) { roles := make([]*v1.ClusterRole, 0) @@ -614,11 +667,138 @@ func WorkspaceRoleInit(workspace *Workspace) error { admin.Name = fmt.Sprintf("system:%s:admin", workspace.Name) admin.Kind = iam.ClusterRoleKind admin.Rules = []v1.PolicyRule{ + // apis/kubesphere.io/v1alpha1/workspaces/sample + // apis/kubesphere.io/v1alpha1/workspaces/sample/namespaces + // apis/kubesphere.io/v1alpha1/workspaces/sample/devops + // apis/kubesphere.io/v1alpha1/workspaces/sample/roles + // apis/kubesphere.io/v1alpha1/workspaces/sample/members + // apis/kubesphere.io/v1alpha1/workspaces/sample/members/admin + { Verbs: []string{"*"}, + APIGroups: []string{"kubesphere.io", "account.kubesphere.io"}, + ResourceNames: []string{workspace.Name}, + Resources: []string{"workspaces", "workspaces/*"}, + }, + + // post apis/kubesphere.io/v1alpha1/workspaces/sample/namespaces + + { + Verbs: []string{"create"}, APIGroups: []string{"kubesphere.io"}, ResourceNames: []string{workspace.Name}, - Resources: []string{"workspaces", "workspaces/namespaces", "workspaces/members", "workspaces/devops", "workspaces/registries"}, + Resources: []string{"workspaces/namespaces"}, + }, + + // post apis/kubesphere.io/v1alpha1/workspaces/sample/members + + { + Verbs: []string{"create"}, + APIGroups: []string{"kubesphere.io"}, + ResourceNames: []string{workspace.Name}, + Resources: []string{"workspaces/members"}, + }, + + // post apis/kubesphere.io/v1alpha1/workspaces/sample/devops + { + Verbs: []string{"create"}, + APIGroups: []string{"kubesphere.io"}, + ResourceNames: []string{workspace.Name}, + Resources: []string{"workspaces/devops"}, + }, + // TODO have risks + // get apis/apps/v1/namespaces/proj1/deployments/?labelSelector + // post api/v1/namespaces/project-0vya57/limitranges + { + Verbs: []string{"*"}, + APIGroups: []string{"", "apps", "extensions", "batch"}, + Resources: []string{"limitranges", "deployments", "configmaps", "secrets", "jobs", "cronjobs", "persistentvolumes", "statefulsets", "daemonsets", "ingresses", "services", "pods/*", "pods", "events", "deployments/scale"}, + }, + // get apis/kubesphere.io/v1alpha1/quota/namespaces/proj1 + { + Verbs: []string{"get"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"quota/*"}, + }, + // get api/v1/namespaces/proj1 + { + Verbs: []string{"get"}, + APIGroups: []string{""}, + Resources: []string{"namespaces", "serviceaccounts", "configmaps"}, + }, + // get api/v1/namespaces/proj1/serviceaccounts + // get api/v1/namespaces/proj1/configmaps + // get api/v1/namespaces/proj1/secrets + + { + Verbs: []string{"list"}, + APIGroups: []string{""}, + Resources: []string{"serviceaccounts", "configmaps", "secrets"}, + }, + + // get apis/kubesphere.io/v1alpha1/status/namespaces/proj1 + { + Verbs: []string{"get"}, + APIGroups: []string{"kubesphere.io"}, + ResourceNames: []string{"namespaces"}, + Resources: []string{"status/*"}, + }, + // apis/kubesphere.io/v1alpha1/namespaces/proj1/router + { + Verbs: []string{"list"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"router"}, + }, + // get apis/kubesphere.io/v1alpha1/registries/proj1 + { + Verbs: []string{"get"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"registries"}, + }, + + // get apis/kubesphere.io/v1alpha1/monitoring/namespaces/proj1 + + { + Verbs: []string{"get"}, + APIGroups: []string{"kubesphere.io"}, + ResourceNames: []string{"namespaces"}, + Resources: []string{"monitoring/*"}, + }, + + // get apis/kubesphere.io/v1alpha1/resources/persistent-volume-claims + // get apis/kubesphere.io/v1alpha1/resources/deployments + // get apis/kubesphere.io/v1alpha1/resources/statefulsets + // get apis/kubesphere.io/v1alpha1/resources/daemonsets + // get apis/kubesphere.io/v1alpha1/resources/jobs + // get apis/kubesphere.io/v1alpha1/resources/cronjobs + // get apis/kubesphere.io/v1alpha1/resources/persistent-volume-claims + // get apis/kubesphere.io/v1alpha1/resources/services + // get apis/kubesphere.io/v1alpha1/resources/ingresses + // get apis/kubesphere.io/v1alpha1/resources/secrets + // get apis/kubesphere.io/v1alpha1/resources/configmaps + // get apis/kubesphere.io/v1alpha1/resources/roles + + { + Verbs: []string{"get"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"resources"}, + }, + + // apis/account.kubesphere.io/v1alpha1/users + // apis/account.kubesphere.io/v1alpha1/namespaces/proj1/users + { + Verbs: []string{"list"}, + APIGroups: []string{"account.kubesphere.io"}, + Resources: []string{"users"}, + }, + + // apis/kubesphere.io/v1alpha1/monitoring/workspaces/sample?metrics_filter= + // apis/kubesphere.io/v1alpha1/monitoring/workspaces/sample/pods?step=30m + { + Verbs: []string{"get"}, + APIGroups: []string{"kubesphere.io"}, + ResourceNames: []string{"workspaces"}, + Resources: []string{"monitoring/" + workspace.Name}, }, } @@ -634,35 +814,159 @@ func WorkspaceRoleInit(workspace *Workspace) error { Resources: []string{"workspaces"}, ResourceNames: []string{workspace.Name}, }, { - Verbs: []string{"list", "create"}, + Verbs: []string{"create", "get"}, APIGroups: []string{"kubesphere.io"}, Resources: []string{"workspaces/namespaces", "workspaces/devops"}, ResourceNames: []string{workspace.Name}, - }, { - Verbs: []string{"list"}, - APIGroups: []string{"kubesphere.io"}, - Resources: []string{"workspaces/members", "workspaces/registries"}, - ResourceNames: []string{workspace.Name}, + }, + { + Verbs: []string{"get"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"registries"}, }, } + operator.Labels = map[string]string{"creator": "system"} viewer := new(v1.ClusterRole) viewer.Name = fmt.Sprintf("system:%s:viewer", workspace.Name) viewer.Kind = iam.ClusterRoleKind viewer.Rules = []v1.PolicyRule{ + // apis/kubesphere.io/v1alpha1/workspaces/sample + // apis/kubesphere.io/v1alpha1/workspaces/sample/namespaces + // apis/kubesphere.io/v1alpha1/workspaces/sample/devops + // apis/kubesphere.io/v1alpha1/workspaces/sample/roles + // apis/kubesphere.io/v1alpha1/workspaces/sample/members + // apis/kubesphere.io/v1alpha1/workspaces/sample/members/admin + + { + Verbs: []string{"get"}, + APIGroups: []string{"kubesphere.io", "account.kubesphere.io"}, + ResourceNames: []string{workspace.Name}, + Resources: []string{"workspaces", "workspaces/*"}, + }, + + // post apis/kubesphere.io/v1alpha1/workspaces/sample/namespaces + + //{ + // Verbs: []string{"create"}, + // APIGroups: []string{"kubesphere.io"}, + // ResourceNames: []string{workspace.Name}, + // Resources: []string{"workspaces/namespaces"}, + //}, + + // post apis/kubesphere.io/v1alpha1/workspaces/sample/members + + //{ + // Verbs: []string{"create"}, + // APIGroups: []string{"kubesphere.io"}, + // ResourceNames: []string{workspace.Name}, + // Resources: []string{"workspaces/members"}, + //}, + + // post apis/kubesphere.io/v1alpha1/workspaces/sample/devops + //{ + // Verbs: []string{"create"}, + // APIGroups: []string{"kubesphere.io"}, + // ResourceNames: []string{workspace.Name}, + // Resources: []string{"workspaces/devops"}, + //}, + // TODO have risks + // get apis/apps/v1/namespaces/proj1/deployments/?labelSelector + // post api/v1/namespaces/project-0vya57/limitranges + { + Verbs: []string{"get", "list"}, + APIGroups: []string{"", "apps", "extensions", "batch"}, + Resources: []string{"limitranges", "deployments", "configmaps", "secrets", "jobs", "cronjobs", "persistentvolumes", "statefulsets", "daemonsets", "ingresses", "services", "pods/*", "pods", "events", "deployments/scale"}, + }, + // get apis/kubesphere.io/v1alpha1/quota/namespaces/proj1 + { + Verbs: []string{"get"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"quota/*"}, + }, + // get api/v1/namespaces/proj1 + { + Verbs: []string{"get"}, + APIGroups: []string{""}, + Resources: []string{"namespaces", "serviceaccounts", "configmaps"}, + }, + // get api/v1/namespaces/proj1/serviceaccounts + // get api/v1/namespaces/proj1/configmaps + // get api/v1/namespaces/proj1/secrets + + { + Verbs: []string{"list"}, + APIGroups: []string{""}, + Resources: []string{"serviceaccounts", "configmaps", "secrets"}, + }, + + // get apis/kubesphere.io/v1alpha1/status/namespaces/proj1 { Verbs: []string{"get"}, APIGroups: []string{"kubesphere.io"}, - Resources: []string{"workspaces"}, - ResourceNames: []string{workspace.Name}, - }, { - Verbs: []string{"list"}, + ResourceNames: []string{"namespaces"}, + Resources: []string{"status/*"}, + }, + // apis/kubesphere.io/v1alpha1/namespaces/proj1/router + { + Verbs: []string{"list"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"router"}, + }, + // get apis/kubesphere.io/v1alpha1/registries/proj1 + { + Verbs: []string{"get"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"registries"}, + }, + + // get apis/kubesphere.io/v1alpha1/monitoring/namespaces/proj1 + + { + Verbs: []string{"get"}, APIGroups: []string{"kubesphere.io"}, - Resources: []string{"workspaces/namespaces", "workspaces/members", "workspaces/devops", "workspaces/registries"}, - ResourceNames: []string{workspace.Name}, + ResourceNames: []string{"namespaces"}, + Resources: []string{"monitoring/*"}, + }, + + // get apis/kubesphere.io/v1alpha1/resources/persistent-volume-claims + // get apis/kubesphere.io/v1alpha1/resources/deployments + // get apis/kubesphere.io/v1alpha1/resources/statefulsets + // get apis/kubesphere.io/v1alpha1/resources/daemonsets + // get apis/kubesphere.io/v1alpha1/resources/jobs + // get apis/kubesphere.io/v1alpha1/resources/cronjobs + // get apis/kubesphere.io/v1alpha1/resources/persistent-volume-claims + // get apis/kubesphere.io/v1alpha1/resources/services + // get apis/kubesphere.io/v1alpha1/resources/ingresses + // get apis/kubesphere.io/v1alpha1/resources/secrets + // get apis/kubesphere.io/v1alpha1/resources/configmaps + // get apis/kubesphere.io/v1alpha1/resources/roles + + { + Verbs: []string{"get"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"resources"}, + }, + + // apis/account.kubesphere.io/v1alpha1/users + // apis/account.kubesphere.io/v1alpha1/namespaces/proj1/users + { + Verbs: []string{"list"}, + APIGroups: []string{"account.kubesphere.io"}, + Resources: []string{"users"}, + }, + + // apis/kubesphere.io/v1alpha1/monitoring/workspaces/sample?metrics_filter= + // apis/kubesphere.io/v1alpha1/monitoring/workspaces/sample/pods?step=30m + { + Verbs: []string{"get"}, + APIGroups: []string{"kubesphere.io"}, + ResourceNames: []string{"workspaces"}, + Resources: []string{"monitoring/" + workspace.Name}, }, } + viewer.Labels = map[string]string{"creator": "system"} _, err := k8sClient.RbacV1().ClusterRoles().Create(admin) @@ -677,6 +981,7 @@ func WorkspaceRoleInit(workspace *Workspace) error { adminRoleBinding.Name = admin.Name adminRoleBinding.RoleRef = v1.RoleRef{Kind: "ClusterRole", Name: admin.Name} adminRoleBinding.Subjects = []v1.Subject{{Kind: v1.UserKind, Name: workspace.Creator}} + _, err = k8sClient.RbacV1().ClusterRoleBindings().Create(adminRoleBinding) if err != nil { if !apierrors.IsAlreadyExists(err) { @@ -761,17 +1066,15 @@ func unbindWorkspaceRole(workspace string, users []string) error { func unbindNamespacesRole(namespaces []string, users []string) error { - lister := controllers.ResourceControllers.Controllers[controllers.Namespaces].Lister().(v13.RoleBindingLister) - k8sClient := client.NewK8sClient() for _, namespace := range namespaces { - roleBindings, err := lister.RoleBindings(namespace).List(labels.Everything()) + roleBindings, err := k8sClient.RbacV1().RoleBindings(namespace).List(meta_v1.ListOptions{}) if err != nil { return err } - for _, roleBinding := range roleBindings { + for _, roleBinding := range roleBindings.Items { modify := false for i := 0; i < len(roleBinding.Subjects); i++ { @@ -781,7 +1084,7 @@ func unbindNamespacesRole(namespaces []string, users []string) error { } } if modify { - _, err := k8sClient.RbacV1().RoleBindings(namespace).Update(roleBinding) + _, err := k8sClient.RbacV1().RoleBindings(namespace).Update(&roleBinding) if err != nil { return err } @@ -857,3 +1160,150 @@ func CreateWorkspaceRoleBinding(workspace *Workspace, username string, role stri return nil } + +func GetDevOpsProjects(name string) ([]string, error) { + + db := client.NewSharedDBClient() + defer db.Close() + + var workspaceDOPBindings []WorkspaceDPBinding + + if err := db.Where("workspace = ?", name).Find(&workspaceDOPBindings).Error; err != nil { + return nil, err + } + + devOpsProjects := make([]string, 0) + + for _, workspaceDOPBinding := range workspaceDOPBindings { + devOpsProjects = append(devOpsProjects, workspaceDOPBinding.DevOpsProject) + } + return devOpsProjects, nil +} + +func GetOrgMembers(workspace string) ([]string, error) { + ws, err := Detail(workspace) + if err != nil { + return nil, err + } + return ws.Members, nil +} + +func GetOrgRoles(name string) ([]string, error) { + return []string{"admin", "operator", "user"}, nil +} + +func WorkspaceNamespaces(workspaceName string) ([]string, error) { + ns, err := Namespaces(workspaceName) + + namespaces := make([]string, 0) + + if err != nil { + return namespaces, err + } + + for i := 0; i < len(ns); i++ { + namespaces = append(namespaces, ns[i].Name) + } + + return namespaces, nil +} + +func CountAll() (int, error) { + result, err := http.Get(fmt.Sprintf("http://%s/apis/account.kubesphere.io/v1alpha1/groups/count", constants.AccountAPIServer)) + + if err != nil { + return 0, err + } + + data, err := ioutil.ReadAll(result.Body) + if err != nil { + return 0, err + } + if result.StatusCode > 200 { + return 0, ksErr.Wrap(data) + } + var count map[string]interface{} + + err = json.Unmarshal(data, &count) + + if err != nil { + return 0, err + } + val, ok := count["total"] + + if !ok { + return 0, errors.New("not found") + } + + switch val.(type) { + case int: + return val.(int), nil + case float32: + return int(val.(float32)), nil + case float64: + return int(val.(float64)), nil + } + + return 0, errors.New("not found") +} + +func GetAllOrgNums() (int, error) { + count, err := CountAll() + if err != nil { + return 0, err + } + return count, nil +} + +func GetAllProjectNums() (int, error) { + return controllers.ResourceControllers.Controllers[controllers.Namespaces].CountWithConditions(""), nil +} + +func GetAllDevOpsProjectsNums() (int, error) { + db := client.NewSharedDBClient() + defer db.Close() + + var count int + if err := db.Find(&WorkspaceDPBinding{}).Count(&count).Error; err != nil { + return 0, err + } + return count, nil +} + +func GetAllAccountNums() (int, error) { + result, err := http.Get(fmt.Sprintf("http://%s/apis/account.kubesphere.io/v1alpha1/users", constants.AccountAPIServer)) + + if err != nil { + return 0, err + } + + data, err := ioutil.ReadAll(result.Body) + if err != nil { + return 0, err + } + if result.StatusCode > 200 { + return 0, ksErr.Wrap(data) + } + var count map[string]interface{} + + err = json.Unmarshal(data, &count) + + if err != nil { + return 0, err + } + val, ok := count["total"] + + if !ok { + return 0, errors.New("not found") + } + + switch val.(type) { + case int: + return val.(int), nil + case float32: + return int(val.(float32)), nil + case float64: + return int(val.(float64)), nil + } + return 0, errors.New("not found") +}