From 51df8fe763553bf2bdd6f1bcc9ff3e32991a9cbd Mon Sep 17 00:00:00 2001 From: Kaito Ii Date: Sun, 7 Aug 2022 00:25:53 +0900 Subject: [PATCH] add rolebinding metrics --- docs/README.md | 2 + docs/cli-arguments.md | 2 +- docs/clusterrolebinding-metrics.md | 9 + docs/rolebinding-metrics.md | 9 + examples/autosharding/cluster-role.yaml | 2 + examples/standard/cluster-role.yaml | 2 + internal/store/builder.go | 10 ++ internal/store/clusterrolebinding.go | 156 ++++++++++++++++++ internal/store/clusterrolebinding_test.go | 113 +++++++++++++ internal/store/rolebinding.go | 156 ++++++++++++++++++ internal/store/rolebinding_test.go | 115 +++++++++++++ .../kube-state-metrics.libsonnet | 2 + pkg/options/resource.go | 2 + tests/manifests/clusterolebinding.yaml | 12 ++ tests/manifests/rolebinding.yaml | 11 ++ 15 files changed, 602 insertions(+), 1 deletion(-) create mode 100644 docs/clusterrolebinding-metrics.md create mode 100644 docs/rolebinding-metrics.md create mode 100644 internal/store/clusterrolebinding.go create mode 100644 internal/store/clusterrolebinding_test.go create mode 100644 internal/store/rolebinding.go create mode 100644 internal/store/rolebinding_test.go create mode 100644 tests/manifests/clusterolebinding.yaml create mode 100644 tests/manifests/rolebinding.yaml diff --git a/docs/README.md b/docs/README.md index ddf69681bf..9ae52d803e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -31,6 +31,7 @@ Per group of metrics there is one file for each metrics. See each file for speci - [CertificateSigningRequest Metrics](certificatesigningrequest-metrics.md) - [ClusterRole Metrics](clusterrole-metrics.md) +- [ClusterRoleBinding Metrics](clusterrolebinding-metrics.md) - [ConfigMap Metrics](configmap-metrics.md) - [CronJob Metrics](cronjob-metrics.md) - [DaemonSet Metrics](daemonset-metrics.md) @@ -53,6 +54,7 @@ Per group of metrics there is one file for each metrics. See each file for speci - [ReplicationController Metrics](replicationcontroller-metrics.md) - [ResourceQuota Metrics](resourcequota-metrics.md) - [Role Metrics](role-metrics.md) +- [RoleBinding Metrics](rolebinding-metrics.md) - [Secret Metrics](secret-metrics.md) - [Service Metrics](service-metrics.md) - [ServiceAccount Metrics](serviceaccount-metrics.md) diff --git a/docs/cli-arguments.md b/docs/cli-arguments.md index f5cf56ded7..299f17e796 100644 --- a/docs/cli-arguments.md +++ b/docs/cli-arguments.md @@ -50,7 +50,7 @@ Usage of ./kube-state-metrics: --pod string Name of the pod that contains the kube-state-metrics container. When set, it is expected that --pod and --pod-namespace are both set. Most likely this should be passed via the downward API. This is used for auto-detecting sharding. If set, this has preference over statically configured sharding. This is experimental, it may be removed without notice. --pod-namespace string Name of the namespace of the pod specified by --pod. When set, it is expected that --pod and --pod-namespace are both set. Most likely this should be passed via the downward API. This is used for auto-detecting sharding. If set, this has preference over statically configured sharding. This is experimental, it may be removed without notice. --port int Port to expose metrics on. (default 8080) - --resources string Comma-separated list of Resources to be enabled. Defaults to "certificatesigningrequests,clusterroles,configmaps,cronjobs,daemonsets,deployments,endpoints,horizontalpodautoscalers,ingresses,jobs,leases,limitranges,mutatingwebhookconfigurations,namespaces,networkpolicies,nodes,persistentvolumeclaims,persistentvolumes,poddisruptionbudgets,pods,replicasets,replicationcontrollers,resourcequotas,roles,secrets,serviceaccounts,services,statefulsets,storageclasses,validatingwebhookconfigurations,volumeattachments" + --resources string Comma-separated list of Resources to be enabled. Defaults to "certificatesigningrequests,clusterrolebindings,clusterroles,configmaps,cronjobs,daemonsets,deployments,endpoints,horizontalpodautoscalers,ingresses,jobs,leases,limitranges,mutatingwebhookconfigurations,namespaces,networkpolicies,nodes,persistentvolumeclaims,persistentvolumes,poddisruptionbudgets,pods,replicasets,replicationcontrollers,resourcequotas,rolebindings,roles,secrets,serviceaccounts,services,statefulsets,storageclasses,validatingwebhookconfigurations,volumeattachments" --shard int32 The instances shard nominal (zero indexed) within the total number of shards. (default 0) --skip_headers If true, avoid header prefixes in the log messages --skip_log_headers If true, avoid headers when opening log files diff --git a/docs/clusterrolebinding-metrics.md b/docs/clusterrolebinding-metrics.md new file mode 100644 index 0000000000..e5aeab7415 --- /dev/null +++ b/docs/clusterrolebinding-metrics.md @@ -0,0 +1,9 @@ +# ClusterRoleBinding Metrics + +| Metric name| Metric type | Labels/tags | Status | +| ---------- | ----------- | ----------- | ----------- | +| kube_clusterrolebinding_annotations | Gauge | `clusterrolebinding`=<clusterrolebinding-name> | EXPERIMENTAL +| kube_clusterrolebinding_labels | Gauge | `clusterrolebinding`=<clusterrolebinding-name> | EXPERIMENTAL +| kube_clusterrolebinding_info | Gauge | `clusterrolebinding`=<clusterrolebinding-name>
`roleref-kind`=<roleref-kind>
`roleref-name`=<roleref-name> | EXPERIMENTAL +| kube_clusterrolebinding_created | Gauge | `clusterrolebinding`=<clusterrolebinding-name> | EXPERIMENTAL | +| kube_clusterrolebinding_metadata_resource_version | Gauge | `clusterrolebinding`=<clusterrolebinding-name> | EXPERIMENTAL | diff --git a/docs/rolebinding-metrics.md b/docs/rolebinding-metrics.md new file mode 100644 index 0000000000..11752ba62c --- /dev/null +++ b/docs/rolebinding-metrics.md @@ -0,0 +1,9 @@ +# RoleBinding Metrics + +| Metric name| Metric type | Labels/tags | Status | +| ---------- | ----------- | ----------- | ----------- | +| kube_rolebinding_annotations | Gauge | `rolebinding`=<rolebinding-name>
`namespace`=<rolebinding-namespace> | EXPERIMENTAL +| kube_rolebinding_labels | Gauge | `rolebinding`=<rolebinding-name>
`namespace`=<rolebinding-namespace> | EXPERIMENTAL +| kube_rolebinding_info | Gauge | `rolebinding`=<rolebinding-name>
`namespace`=<rolebinding-namespace>
`roleref-kind`=<roleref-kind>
`roleref-name`=<roleref-name>| EXPERIMENTAL +| kube_rolebinding_created | Gauge | `rolebinding`=<rolebinding-name>
`namespace`=<rolebinding-namespace> | EXPERIMENTAL | +| kube_rolebinding_metadata_resource_version | Gauge | `rolebinding`=<rolebinding-name>
`namespace`=<rolebinding-namespace> | EXPERIMENTAL | diff --git a/examples/autosharding/cluster-role.yaml b/examples/autosharding/cluster-role.yaml index 574452e9e1..a3f1625d28 100644 --- a/examples/autosharding/cluster-role.yaml +++ b/examples/autosharding/cluster-role.yaml @@ -112,7 +112,9 @@ rules: - rbac.authorization.k8s.io resources: - clusterroles + - clusterrolebindings - roles + - rolesbindings verbs: - list - watch diff --git a/examples/standard/cluster-role.yaml b/examples/standard/cluster-role.yaml index 574452e9e1..a3f1625d28 100644 --- a/examples/standard/cluster-role.yaml +++ b/examples/standard/cluster-role.yaml @@ -112,7 +112,9 @@ rules: - rbac.authorization.k8s.io resources: - clusterroles + - clusterrolebindings - roles + - rolesbindings verbs: - list - watch diff --git a/internal/store/builder.go b/internal/store/builder.go index 783cfeea87..64c60fa8f1 100644 --- a/internal/store/builder.go +++ b/internal/store/builder.go @@ -264,6 +264,7 @@ var availableStores = map[string]func(f *Builder) []cache.Store{ "certificatesigningrequests": func(b *Builder) []cache.Store { return b.buildCsrStores() }, "clusterroles": func(b *Builder) []cache.Store { return b.buildClusterRoleStores() }, "configmaps": func(b *Builder) []cache.Store { return b.buildConfigMapStores() }, + "clusterrolebindings": func(b *Builder) []cache.Store { return b.buildClusterRoleBindingStores() }, "cronjobs": func(b *Builder) []cache.Store { return b.buildCronJobStores() }, "daemonsets": func(b *Builder) []cache.Store { return b.buildDaemonSetStores() }, "deployments": func(b *Builder) []cache.Store { return b.buildDeploymentStores() }, @@ -285,6 +286,7 @@ var availableStores = map[string]func(f *Builder) []cache.Store{ "replicationcontrollers": func(b *Builder) []cache.Store { return b.buildReplicationControllerStores() }, "resourcequotas": func(b *Builder) []cache.Store { return b.buildResourceQuotaStores() }, "roles": func(b *Builder) []cache.Store { return b.buildRoleStores() }, + "rolebindings": func(b *Builder) []cache.Store { return b.buildRoleBindingStores() }, "secrets": func(b *Builder) []cache.Store { return b.buildSecretStores() }, "serviceaccounts": func(b *Builder) []cache.Store { return b.buildServiceAccountStores() }, "services": func(b *Builder) []cache.Store { return b.buildServiceStores() }, @@ -436,6 +438,14 @@ func (b *Builder) buildRoleStores() []cache.Store { return b.buildStoresFunc(roleMetricFamilies(b.allowAnnotationsList["roles"], b.allowLabelsList["roles"]), &rbacv1.Role{}, createRoleListWatch, b.useAPIServerCache) } +func (b *Builder) buildClusterRoleBindingStores() []cache.Store { + return b.buildStoresFunc(clusterRoleBindingMetricFamilies(b.allowAnnotationsList["clusterrolebindings"], b.allowLabelsList["clusterrolebindings"]), &rbacv1.RoleBinding{}, createClusterRoleBindingListWatch, b.useAPIServerCache) +} + +func (b *Builder) buildRoleBindingStores() []cache.Store { + return b.buildStoresFunc(roleBindingMetricFamilies(b.allowAnnotationsList["rolebindings"], b.allowLabelsList["rolebindings"]), &rbacv1.RoleBinding{}, createRoleBindingListWatch, b.useAPIServerCache) +} + func (b *Builder) buildStores( metricFamilies []generator.FamilyGenerator, expectedType interface{}, diff --git a/internal/store/clusterrolebinding.go b/internal/store/clusterrolebinding.go new file mode 100644 index 0000000000..fb90a83396 --- /dev/null +++ b/internal/store/clusterrolebinding.go @@ -0,0 +1,156 @@ +/* +Copyright 2022 The Kubernetes Authors All rights reserved. + +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 store + +import ( + "context" + + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + clientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/cache" + + "k8s.io/kube-state-metrics/v2/pkg/metric" + generator "k8s.io/kube-state-metrics/v2/pkg/metric_generator" +) + +var ( + descClusterRoleBindingAnnotationsName = "kube_clusterrolebinding_annotations" + descClusterRoleBindingAnnotationsHelp = "Kubernetes annotations converted to Prometheus labels." + descClusterRoleBindingLabelsName = "kube_clusterrolebinding_labels" + descClusterRoleBindingLabelsHelp = "Kubernetes labels converted to Prometheus labels." + descClusterRoleBindingLabelsDefaultLabels = []string{"clusterrolebinding"} +) + +func clusterRoleBindingMetricFamilies(allowAnnotationsList, allowLabelsList []string) []generator.FamilyGenerator { + return []generator.FamilyGenerator{ + *generator.NewFamilyGenerator( + descClusterRoleBindingAnnotationsName, + descClusterRoleBindingAnnotationsHelp, + metric.Gauge, + "", + wrapClusterRoleBindingFunc(func(r *rbacv1.ClusterRoleBinding) *metric.Family { + annotationKeys, annotationValues := createPrometheusLabelKeysValues("annotation", r.Annotations, allowAnnotationsList) + return &metric.Family{ + Metrics: []*metric.Metric{ + { + LabelKeys: annotationKeys, + LabelValues: annotationValues, + Value: 1, + }, + }, + } + }), + ), + *generator.NewFamilyGenerator( + descClusterRoleBindingLabelsName, + descClusterRoleBindingLabelsHelp, + metric.Gauge, + "", + wrapClusterRoleBindingFunc(func(r *rbacv1.ClusterRoleBinding) *metric.Family { + labelKeys, labelValues := createPrometheusLabelKeysValues("label", r.Labels, allowLabelsList) + return &metric.Family{ + Metrics: []*metric.Metric{ + { + LabelKeys: labelKeys, + LabelValues: labelValues, + Value: 1, + }, + }, + } + }), + ), + *generator.NewFamilyGenerator( + "kube_clusterrolebinding_info", + "Information about clusterrolebinding.", + metric.Gauge, + "", + wrapClusterRoleBindingFunc(func(r *rbacv1.ClusterRoleBinding) *metric.Family { + labelKeys := []string{"roleref_kind", "roleref_name"} + labelValues := []string{r.RoleRef.Kind, r.RoleRef.Name} + return &metric.Family{ + Metrics: []*metric.Metric{{ + LabelKeys: labelKeys, + LabelValues: labelValues, + Value: 1, + }}, + } + }), + ), + *generator.NewFamilyGenerator( + "kube_clusterrolebinding_created", + "Unix creation timestamp", + metric.Gauge, + "", + wrapClusterRoleBindingFunc(func(r *rbacv1.ClusterRoleBinding) *metric.Family { + ms := []*metric.Metric{} + + if !r.CreationTimestamp.IsZero() { + ms = append(ms, &metric.Metric{ + LabelKeys: []string{}, + LabelValues: []string{}, + Value: float64(r.CreationTimestamp.Unix()), + }) + } + + return &metric.Family{ + Metrics: ms, + } + }), + ), + *generator.NewFamilyGenerator( + "kube_clusterrolebinding_metadata_resource_version", + "Resource version representing a specific version of the clusterrolebinding.", + metric.Gauge, + "", + wrapClusterRoleBindingFunc(func(r *rbacv1.ClusterRoleBinding) *metric.Family { + return &metric.Family{ + Metrics: resourceVersionMetric(r.ObjectMeta.ResourceVersion), + } + }), + ), + } +} + +func createClusterRoleBindingListWatch(kubeClient clientset.Interface, ns string, fieldSelector string) cache.ListerWatcher { + return &cache.ListWatch{ + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { + opts.FieldSelector = fieldSelector + return kubeClient.RbacV1().ClusterRoleBindings().List(context.TODO(), opts) + }, + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { + opts.FieldSelector = fieldSelector + return kubeClient.RbacV1().ClusterRoleBindings().Watch(context.TODO(), opts) + }, + } +} + +func wrapClusterRoleBindingFunc(f func(*rbacv1.ClusterRoleBinding) *metric.Family) func(interface{}) *metric.Family { + return func(obj interface{}) *metric.Family { + clusterrolebinding := obj.(*rbacv1.ClusterRoleBinding) + + metricFamily := f(clusterrolebinding) + + for _, m := range metricFamily.Metrics { + m.LabelKeys, m.LabelValues = mergeKeyValues(descClusterRoleBindingLabelsDefaultLabels, []string{clusterrolebinding.Name}, m.LabelKeys, m.LabelValues) + } + + return metricFamily + } +} diff --git a/internal/store/clusterrolebinding_test.go b/internal/store/clusterrolebinding_test.go new file mode 100644 index 0000000000..e367425853 --- /dev/null +++ b/internal/store/clusterrolebinding_test.go @@ -0,0 +1,113 @@ +/* +Copyright 2022 The Kubernetes Authors All rights reserved. + +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 store + +import ( + "testing" + + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + generator "k8s.io/kube-state-metrics/v2/pkg/metric_generator" +) + +func TestClusterRoleBindingStore(t *testing.T) { + startTime := 1501569018 + metav1StartTime := metav1.Unix(int64(startTime), 0) + + cases := []generateMetricsTestCase{ + { + AllowAnnotationsList: []string{ + "app.k8s.io/owner", + }, + AllowLabelsList: []string{ + "app", + }, + Obj: &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "clusterrolebinding1", + ResourceVersion: "BBBBB", + Annotations: map[string]string{ + "app": "mysql-server", + "app.k8s.io/owner": "@foo", + }, + Labels: map[string]string{ + "excluded": "me", + "app": "mysql-server", + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: "role", + }, + }, + Want: ` + # HELP kube_clusterrolebinding_annotations Kubernetes annotations converted to Prometheus labels. + # HELP kube_clusterrolebinding_labels Kubernetes labels converted to Prometheus labels. + # HELP kube_clusterrolebinding_info Information about clusterrolebinding. + # HELP kube_clusterrolebinding_metadata_resource_version Resource version representing a specific version of the clusterrolebinding. + # TYPE kube_clusterrolebinding_annotations gauge + # TYPE kube_clusterrolebinding_labels gauge + # TYPE kube_clusterrolebinding_info gauge + # TYPE kube_clusterrolebinding_metadata_resource_version gauge + kube_clusterrolebinding_annotations{annotation_app_k8s_io_owner="@foo",clusterrolebinding="clusterrolebinding1"} 1 + kube_clusterrolebinding_labels{clusterrolebinding="clusterrolebinding1",label_app="mysql-server"} 1 + kube_clusterrolebinding_info{clusterrolebinding="clusterrolebinding1",roleref_kind="Role",roleref_name="role"} 1 +`, + MetricNames: []string{ + "kube_clusterrolebinding_annotations", + "kube_clusterrolebinding_labels", + "kube_clusterrolebinding_info", + "kube_clusterrolebinding_metadata_resource_version", + }, + }, + { + Obj: &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "clusterrolebinding2", + CreationTimestamp: metav1StartTime, + ResourceVersion: "10596", + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: "role", + }, + }, + Want: ` + # HELP kube_clusterrolebinding_created Unix creation timestamp + # HELP kube_clusterrolebinding_info Information about clusterrolebinding. + # HELP kube_clusterrolebinding_metadata_resource_version Resource version representing a specific version of the clusterrolebinding. + # TYPE kube_clusterrolebinding_created gauge + # TYPE kube_clusterrolebinding_info gauge + # TYPE kube_clusterrolebinding_metadata_resource_version gauge + kube_clusterrolebinding_info{clusterrolebinding="clusterrolebinding2",roleref_kind="Role",roleref_name="role"} 1 + kube_clusterrolebinding_created{clusterrolebinding="clusterrolebinding2"} 1.501569018e+09 + kube_clusterrolebinding_metadata_resource_version{clusterrolebinding="clusterrolebinding2"} 10596 + `, + MetricNames: []string{"kube_clusterrolebinding_info", "kube_clusterrolebinding_created", "kube_clusterrolebinding_metadata_resource_version"}, + }, + } + for i, c := range cases { + c.Func = generator.ComposeMetricGenFuncs(clusterRoleBindingMetricFamilies(c.AllowAnnotationsList, c.AllowLabelsList)) + c.Headers = generator.ExtractMetricFamilyHeaders(clusterRoleBindingMetricFamilies(c.AllowAnnotationsList, c.AllowLabelsList)) + if err := c.run(); err != nil { + t.Errorf("unexpected collecting result in %vth run:\n%s", i, err) + } + } +} diff --git a/internal/store/rolebinding.go b/internal/store/rolebinding.go new file mode 100644 index 0000000000..cdf95b9e04 --- /dev/null +++ b/internal/store/rolebinding.go @@ -0,0 +1,156 @@ +/* +Copyright 2022 The Kubernetes Authors All rights reserved. + +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 store + +import ( + "context" + + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + clientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/cache" + + "k8s.io/kube-state-metrics/v2/pkg/metric" + generator "k8s.io/kube-state-metrics/v2/pkg/metric_generator" +) + +var ( + descRoleBindingAnnotationsName = "kube_rolebinding_annotations" + descRoleBindingAnnotationsHelp = "Kubernetes annotations converted to Prometheus labels." + descRoleBindingLabelsName = "kube_rolebinding_labels" + descRoleBindingLabelsHelp = "Kubernetes labels converted to Prometheus labels." + descRoleBindingLabelsDefaultLabels = []string{"namespace", "rolebinding"} +) + +func roleBindingMetricFamilies(allowAnnotationsList, allowLabelsList []string) []generator.FamilyGenerator { + return []generator.FamilyGenerator{ + *generator.NewFamilyGenerator( + descRoleBindingAnnotationsName, + descRoleBindingAnnotationsHelp, + metric.Gauge, + "", + wrapRoleBindingFunc(func(r *rbacv1.RoleBinding) *metric.Family { + annotationKeys, annotationValues := createPrometheusLabelKeysValues("annotation", r.Annotations, allowAnnotationsList) + return &metric.Family{ + Metrics: []*metric.Metric{ + { + LabelKeys: annotationKeys, + LabelValues: annotationValues, + Value: 1, + }, + }, + } + }), + ), + *generator.NewFamilyGenerator( + descRoleBindingLabelsName, + descRoleBindingLabelsHelp, + metric.Gauge, + "", + wrapRoleBindingFunc(func(r *rbacv1.RoleBinding) *metric.Family { + labelKeys, labelValues := createPrometheusLabelKeysValues("label", r.Labels, allowLabelsList) + return &metric.Family{ + Metrics: []*metric.Metric{ + { + LabelKeys: labelKeys, + LabelValues: labelValues, + Value: 1, + }, + }, + } + }), + ), + *generator.NewFamilyGenerator( + "kube_rolebinding_info", + "Information about rolebinding.", + metric.Gauge, + "", + wrapRoleBindingFunc(func(r *rbacv1.RoleBinding) *metric.Family { + labelKeys := []string{"roleref_kind", "roleref_name"} + labelValues := []string{r.RoleRef.Kind, r.RoleRef.Name} + return &metric.Family{ + Metrics: []*metric.Metric{{ + LabelKeys: labelKeys, + LabelValues: labelValues, + Value: 1, + }}, + } + }), + ), + *generator.NewFamilyGenerator( + "kube_rolebinding_created", + "Unix creation timestamp", + metric.Gauge, + "", + wrapRoleBindingFunc(func(r *rbacv1.RoleBinding) *metric.Family { + ms := []*metric.Metric{} + + if !r.CreationTimestamp.IsZero() { + ms = append(ms, &metric.Metric{ + LabelKeys: []string{}, + LabelValues: []string{}, + Value: float64(r.CreationTimestamp.Unix()), + }) + } + + return &metric.Family{ + Metrics: ms, + } + }), + ), + *generator.NewFamilyGenerator( + "kube_rolebinding_metadata_resource_version", + "Resource version representing a specific version of the rolebinding.", + metric.Gauge, + "", + wrapRoleBindingFunc(func(r *rbacv1.RoleBinding) *metric.Family { + return &metric.Family{ + Metrics: resourceVersionMetric(r.ObjectMeta.ResourceVersion), + } + }), + ), + } +} + +func createRoleBindingListWatch(kubeClient clientset.Interface, ns string, fieldSelector string) cache.ListerWatcher { + return &cache.ListWatch{ + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { + opts.FieldSelector = fieldSelector + return kubeClient.RbacV1().RoleBindings(ns).List(context.TODO(), opts) + }, + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { + opts.FieldSelector = fieldSelector + return kubeClient.RbacV1().RoleBindings(ns).Watch(context.TODO(), opts) + }, + } +} + +func wrapRoleBindingFunc(f func(*rbacv1.RoleBinding) *metric.Family) func(interface{}) *metric.Family { + return func(obj interface{}) *metric.Family { + rolebinding := obj.(*rbacv1.RoleBinding) + + metricFamily := f(rolebinding) + + for _, m := range metricFamily.Metrics { + m.LabelKeys, m.LabelValues = mergeKeyValues(descRoleBindingLabelsDefaultLabels, []string{rolebinding.Namespace, rolebinding.Name}, m.LabelKeys, m.LabelValues) + } + + return metricFamily + } +} diff --git a/internal/store/rolebinding_test.go b/internal/store/rolebinding_test.go new file mode 100644 index 0000000000..17ce4848cd --- /dev/null +++ b/internal/store/rolebinding_test.go @@ -0,0 +1,115 @@ +/* +Copyright 2022 The Kubernetes Authors All rights reserved. + +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 store + +import ( + "testing" + + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + generator "k8s.io/kube-state-metrics/v2/pkg/metric_generator" +) + +func TestRoleBindingStore(t *testing.T) { + startTime := 1501569018 + metav1StartTime := metav1.Unix(int64(startTime), 0) + + cases := []generateMetricsTestCase{ + { + AllowAnnotationsList: []string{ + "app.k8s.io/owner", + }, + AllowLabelsList: []string{ + "app", + }, + Obj: &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rolebinding1", + Namespace: "ns1", + ResourceVersion: "BBBBB", + Annotations: map[string]string{ + "app": "mysql-server", + "app.k8s.io/owner": "@foo", + }, + Labels: map[string]string{ + "excluded": "me", + "app": "mysql-server", + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: "role", + }, + }, + Want: ` + # HELP kube_rolebinding_annotations Kubernetes annotations converted to Prometheus labels. + # HELP kube_rolebinding_labels Kubernetes labels converted to Prometheus labels. + # HELP kube_rolebinding_info Information about rolebinding. + # HELP kube_rolebinding_metadata_resource_version Resource version representing a specific version of the rolebinding. + # TYPE kube_rolebinding_annotations gauge + # TYPE kube_rolebinding_labels gauge + # TYPE kube_rolebinding_info gauge + # TYPE kube_rolebinding_metadata_resource_version gauge + kube_rolebinding_annotations{annotation_app_k8s_io_owner="@foo",rolebinding="rolebinding1",namespace="ns1"} 1 + kube_rolebinding_labels{rolebinding="rolebinding1",label_app="mysql-server",namespace="ns1"} 1 + kube_rolebinding_info{rolebinding="rolebinding1",namespace="ns1",roleref_kind="Role",roleref_name="role"} 1 +`, + MetricNames: []string{ + "kube_rolebinding_annotations", + "kube_rolebinding_labels", + "kube_rolebinding_info", + "kube_rolebinding_metadata_resource_version", + }, + }, + { + Obj: &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rolebinding2", + Namespace: "ns2", + CreationTimestamp: metav1StartTime, + ResourceVersion: "10596", + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: "role", + }, + }, + Want: ` + # HELP kube_rolebinding_created Unix creation timestamp + # HELP kube_rolebinding_info Information about rolebinding. + # HELP kube_rolebinding_metadata_resource_version Resource version representing a specific version of the rolebinding. + # TYPE kube_rolebinding_created gauge + # TYPE kube_rolebinding_info gauge + # TYPE kube_rolebinding_metadata_resource_version gauge + kube_rolebinding_info{rolebinding="rolebinding2",namespace="ns2",roleref_kind="Role",roleref_name="role"} 1 + kube_rolebinding_created{rolebinding="rolebinding2",namespace="ns2"} 1.501569018e+09 + kube_rolebinding_metadata_resource_version{rolebinding="rolebinding2",namespace="ns2"} 10596 + `, + MetricNames: []string{"kube_rolebinding_info", "kube_rolebinding_created", "kube_rolebinding_metadata_resource_version"}, + }, + } + for i, c := range cases { + c.Func = generator.ComposeMetricGenFuncs(roleBindingMetricFamilies(c.AllowAnnotationsList, c.AllowLabelsList)) + c.Headers = generator.ExtractMetricFamilyHeaders(roleBindingMetricFamilies(c.AllowAnnotationsList, c.AllowLabelsList)) + if err := c.run(); err != nil { + t.Errorf("unexpected collecting result in %vth run:\n%s", i, err) + } + } +} diff --git a/jsonnet/kube-state-metrics/kube-state-metrics.libsonnet b/jsonnet/kube-state-metrics/kube-state-metrics.libsonnet index 80e259dc51..22287cce83 100644 --- a/jsonnet/kube-state-metrics/kube-state-metrics.libsonnet +++ b/jsonnet/kube-state-metrics/kube-state-metrics.libsonnet @@ -149,7 +149,9 @@ apiGroups: ['rbac.authorization.k8s.io'], resources: [ 'clusterroles', + 'clusterrolebindings', 'roles', + 'rolesbindings', ], verbs: ['list', 'watch'], }, diff --git a/pkg/options/resource.go b/pkg/options/resource.go index 3ca7cd5fd2..5fa4ea7088 100644 --- a/pkg/options/resource.go +++ b/pkg/options/resource.go @@ -29,6 +29,7 @@ var ( "certificatesigningrequests": struct{}{}, "clusterroles": struct{}{}, "configmaps": struct{}{}, + "clusterrolebindings": struct{}{}, "cronjobs": struct{}{}, "daemonsets": struct{}{}, "deployments": struct{}{}, @@ -50,6 +51,7 @@ var ( "replicationcontrollers": struct{}{}, "resourcequotas": struct{}{}, "roles": struct{}{}, + "rolebindings": struct{}{}, "secrets": struct{}{}, "serviceaccounts": struct{}{}, "services": struct{}{}, diff --git a/tests/manifests/clusterolebinding.yaml b/tests/manifests/clusterolebinding.yaml new file mode 100644 index 0000000000..fc8c3a58d9 --- /dev/null +++ b/tests/manifests/clusterolebinding.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: clusterrolebinding +subjects: +- kind: ServiceAccount + name: test-service-account + namespace: default +roleRef: + kind: ClusterRole + name: clusterrole + apiGroup: rbac.authorization.k8s.io diff --git a/tests/manifests/rolebinding.yaml b/tests/manifests/rolebinding.yaml new file mode 100644 index 0000000000..7b01c65bea --- /dev/null +++ b/tests/manifests/rolebinding.yaml @@ -0,0 +1,11 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: rolebinding +subjects: +- kind: ServiceAccount + name: test-service-account +roleRef: + kind: Role + name: role + apiGroup: rbac.authorization.k8s.io