diff --git a/src/app/backend/api/types.go b/src/app/backend/api/types.go index 9f7b69b57814..cffe5a809e28 100644 --- a/src/app/backend/api/types.go +++ b/src/app/backend/api/types.go @@ -137,6 +137,7 @@ const ( ResourceKindStatefulSet = "statefulset" ResourceKindStorageClass = "storageclass" ResourceKindClusterRole = "clusterrole" + ResourceKindRole = "role" ResourceKindPlugin = "plugin" ResourceKindEndpoint = "endpoint" ) diff --git a/src/app/backend/handler/apihandler.go b/src/app/backend/handler/apihandler.go index 431d82f06559..4c537d8233b2 100644 --- a/src/app/backend/handler/apihandler.go +++ b/src/app/backend/handler/apihandler.go @@ -53,6 +53,7 @@ import ( "github.com/kubernetes/dashboard/src/app/backend/resource/pod" "github.com/kubernetes/dashboard/src/app/backend/resource/replicaset" "github.com/kubernetes/dashboard/src/app/backend/resource/replicationcontroller" + "github.com/kubernetes/dashboard/src/app/backend/resource/role" "github.com/kubernetes/dashboard/src/app/backend/resource/secret" resourceService "github.com/kubernetes/dashboard/src/app/backend/resource/service" "github.com/kubernetes/dashboard/src/app/backend/resource/statefulset" @@ -516,6 +517,15 @@ func CreateHTTPAPIHandler(iManager integration.IntegrationManager, cManager clie To(apiHandler.handleGetClusterRoleDetail). Writes(clusterrole.ClusterRoleDetail{})) + apiV1Ws.Route( + apiV1Ws.GET("/role/{namespace}"). + To(apiHandler.handleGetRoleList). + Writes(role.RoleList{})) + apiV1Ws.Route( + apiV1Ws.GET("/role/{namespace}/{name}"). + To(apiHandler.handleGetRoleDetail). + Writes(role.RoleDetail{})) + apiV1Ws.Route( apiV1Ws.GET("/persistentvolume"). To(apiHandler.handleGetPersistentVolumeList). @@ -634,6 +644,40 @@ func (apiHandler *APIHandler) handleGetClusterRoleDetail(request *restful.Reques response.WriteHeaderAndEntity(http.StatusOK, result) } +func (apiHandler *APIHandler) handleGetRoleList(request *restful.Request, response *restful.Response) { + k8sClient, err := apiHandler.cManager.Client(request) + if err != nil { + errors.HandleInternalError(response, err) + return + } + + namespace := parseNamespacePathParameter(request) + dataSelect := parser.ParseDataSelectPathParameter(request) + result, err := role.GetRoleList(k8sClient, namespace, dataSelect) + if err != nil { + errors.HandleInternalError(response, err) + return + } + response.WriteHeaderAndEntity(http.StatusOK, result) +} + +func (apiHandler *APIHandler) handleGetRoleDetail(request *restful.Request, response *restful.Response) { + k8sClient, err := apiHandler.cManager.Client(request) + if err != nil { + errors.HandleInternalError(response, err) + return + } + + namespace := request.PathParameter("namespace") + name := request.PathParameter("name") + result, err := role.GetRoleDetail(k8sClient, namespace, name) + if err != nil { + errors.HandleInternalError(response, err) + return + } + response.WriteHeaderAndEntity(http.StatusOK, result) +} + func (apiHandler *APIHandler) handleGetCsrfToken(request *restful.Request, response *restful.Response) { action := request.PathParameter("action") token := xsrftoken.Generate(apiHandler.cManager.CSRFKey(), "none", action) diff --git a/src/app/backend/resource/common/resourcechannels.go b/src/app/backend/resource/common/resourcechannels.go index 99663c853ef9..ec7209cce7d2 100644 --- a/src/app/backend/resource/common/resourcechannels.go +++ b/src/app/backend/resource/common/resourcechannels.go @@ -690,14 +690,14 @@ type RoleListChannel struct { // GetRoleListChannel returns a pair of channels to a Role list for a namespace and errors that // both must be read numReads times. -func GetRoleListChannel(client client.Interface, numReads int) RoleListChannel { +func GetRoleListChannel(client client.Interface, nsQuery *NamespaceQuery, numReads int) RoleListChannel { channel := RoleListChannel{ List: make(chan *rbac.RoleList, numReads), Error: make(chan error, numReads), } go func() { - list, err := client.RbacV1().Roles("").List(api.ListEverything) + list, err := client.RbacV1().Roles(nsQuery.ToRequestParam()).List(api.ListEverything) for i := 0; i < numReads; i++ { channel.List <- list channel.Error <- err diff --git a/src/app/backend/resource/role/common.go b/src/app/backend/resource/role/common.go new file mode 100644 index 000000000000..631ddd655e70 --- /dev/null +++ b/src/app/backend/resource/role/common.go @@ -0,0 +1,53 @@ +// Copyright 2017 The Kubernetes 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 role + +import ( + "github.com/kubernetes/dashboard/src/app/backend/resource/dataselect" +) + +// The code below allows to perform complex data section on []Role + +type RoleCell Role + +func (self RoleCell) GetProperty(name dataselect.PropertyName) dataselect.ComparableValue { + switch name { + case dataselect.NameProperty: + return dataselect.StdComparableString(self.ObjectMeta.Name) + case dataselect.CreationTimestampProperty: + return dataselect.StdComparableTime(self.ObjectMeta.CreationTimestamp.Time) + case dataselect.NamespaceProperty: + return dataselect.StdComparableString(self.ObjectMeta.Namespace) + default: + // if name is not supported then just return a constant dummy value, sort will have no effect. + return nil + } +} + +func toCells(std []Role) []dataselect.DataCell { + cells := make([]dataselect.DataCell, len(std)) + for i := range std { + cells[i] = RoleCell(std[i]) + } + return cells +} + +func fromCells(cells []dataselect.DataCell) []Role { + std := make([]Role, len(cells)) + for i := range std { + std[i] = Role(cells[i].(RoleCell)) + } + return std +} diff --git a/src/app/backend/resource/role/detail.go b/src/app/backend/resource/role/detail.go new file mode 100644 index 000000000000..69433bb7b7e3 --- /dev/null +++ b/src/app/backend/resource/role/detail.go @@ -0,0 +1,51 @@ +// Copyright 2017 The Kubernetes 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 role + +import ( + rbac "k8s.io/api/rbac/v1" + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" + k8sClient "k8s.io/client-go/kubernetes" +) + +// RoleDetail contains Cron Job details. +type RoleDetail struct { + // Extends list item structure. + Role `json:",inline"` + + Rules []rbac.PolicyRule `json:"rules"` + + // List of non-critical errors, that occurred during resource retrieval. + Errors []error `json:"errors"` +} + +// GetRoleDetail gets Role details. +func GetRoleDetail(client k8sClient.Interface, namespace, name string) (*RoleDetail, error) { + rawObject, err := client.RbacV1().Roles(namespace).Get(name, metaV1.GetOptions{}) + if err != nil { + return nil, err + } + + cr := toRoleDetail(*rawObject) + return &cr, nil +} + +func toRoleDetail(cr rbac.Role) RoleDetail { + return RoleDetail{ + Role: toRole(cr), + Rules: cr.Rules, + Errors: []error{}, + } +} diff --git a/src/app/backend/resource/role/list.go b/src/app/backend/resource/role/list.go new file mode 100644 index 000000000000..e98e7f5f6b5c --- /dev/null +++ b/src/app/backend/resource/role/list.go @@ -0,0 +1,89 @@ +// Copyright 2017 The Kubernetes 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 role + +import ( + "log" + + "github.com/kubernetes/dashboard/src/app/backend/api" + "github.com/kubernetes/dashboard/src/app/backend/errors" + "github.com/kubernetes/dashboard/src/app/backend/resource/common" + "github.com/kubernetes/dashboard/src/app/backend/resource/dataselect" + rbac "k8s.io/api/rbac/v1" + "k8s.io/client-go/kubernetes" +) + +// RoleList contains a list of role in the cluster. +type RoleList struct { + ListMeta api.ListMeta `json:"listMeta"` + Items []Role `json:"items"` + + // List of non-critical errors, that occurred during resource retrieval. + Errors []error `json:"errors"` +} + +// Role is a presentation layer view of Kubernetes role. This means it is role plus additional +// augmented data we can get from other sources. +type Role struct { + ObjectMeta api.ObjectMeta `json:"objectMeta"` + TypeMeta api.TypeMeta `json:"typeMeta"` +} + +// GetRoleList returns a list of all Roles in the cluster. +func GetRoleList(client kubernetes.Interface, nsQuery *common.NamespaceQuery, dsQuery *dataselect.DataSelectQuery) (*RoleList, error) { + log.Print("Getting list of all roles in the cluster") + channels := &common.ResourceChannels{ + RoleList: common.GetRoleListChannel(client, nsQuery, 1), + } + + return GetRoleListFromChannels(channels, dsQuery) +} + +// GetRoleListFromChannels returns a list of all Roles in the cluster +// reading required resource list once from the channels. +func GetRoleListFromChannels(channels *common.ResourceChannels, dsQuery *dataselect.DataSelectQuery) (*RoleList, error) { + roles := <-channels.RoleList.List + err := <-channels.RoleList.Error + nonCriticalErrors, criticalError := errors.HandleError(err) + if criticalError != nil { + return nil, criticalError + } + deploymentList := toRoleList(roles.Items, nonCriticalErrors, dsQuery) + return deploymentList, nil +} + +func toRole(role rbac.Role) Role { + return Role{ + ObjectMeta: api.NewObjectMeta(role.ObjectMeta), + TypeMeta: api.NewTypeMeta(api.ResourceKindRole), + } +} + +func toRoleList(roles []rbac.Role, nonCriticalErrors []error, dsQuery *dataselect.DataSelectQuery) *RoleList { + result := &RoleList{ + ListMeta: api.ListMeta{TotalItems: len(roles)}, + Errors: nonCriticalErrors, + } + + items := make([]Role, 0) + for _, item := range roles { + items = append(items, toRole(item)) + } + + roleCells, filteredTotal := dataselect.GenericDataSelectWithFilter(toCells(items), dsQuery) + result.ListMeta = api.ListMeta{TotalItems: filteredTotal} + result.Items = fromCells(roleCells) + return result +} diff --git a/src/app/backend/resource/role/list_test.go b/src/app/backend/resource/role/list_test.go new file mode 100644 index 000000000000..196aff619c10 --- /dev/null +++ b/src/app/backend/resource/role/list_test.go @@ -0,0 +1,59 @@ +// Copyright 2017 The Kubernetes 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 role + +import ( + "reflect" + "testing" + + "github.com/kubernetes/dashboard/src/app/backend/api" + "github.com/kubernetes/dashboard/src/app/backend/resource/dataselect" + rbac "k8s.io/api/rbac/v1" + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestToRbacRoleLists(t *testing.T) { + cases := []struct { + Roles []rbac.Role + expected *RoleList + }{ + {nil, &RoleList{Items: []Role{}}}, + { + []rbac.Role{ + { + ObjectMeta: metaV1.ObjectMeta{Name: "role"}, + Rules: []rbac.PolicyRule{{ + Verbs: []string{"post", "put"}, + Resources: []string{"pods", "deployments"}, + }}, + }, + }, + &RoleList{ + ListMeta: api.ListMeta{TotalItems: 1}, + Items: []Role{{ + ObjectMeta: api.ObjectMeta{Name: "role", Namespace: ""}, + TypeMeta: api.TypeMeta{Kind: api.ResourceKindRole}, + }}, + }, + }, + } + for _, c := range cases { + actual := toRoleList(c.Roles, nil, dataselect.NoDataSelect) + if !reflect.DeepEqual(actual, c.expected) { + t.Errorf("toRbacRoleLists(%#v) == \n%#v\nexpected \n%#v\n", + c.Roles, actual, c.expected) + } + } +}