diff --git a/docs/README.md b/docs/README.md
index 617c28f39e..6e5e48e074 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -65,6 +65,7 @@ See each file for specific documentation about the exposed metrics:
- [ClusterRole Metrics](clusterrole-metrics.md)
- [ClusterRoleBinding Metrics](clusterrolebinding-metrics.md)
+- [IngressClass Metrics](ingressclass-metrics.md)
- [Role Metrics](role-metrics.md)
- [RoleBinding Metrics](rolebinding-metrics.md)
- [ServiceAccount Metrics](serviceaccount-metrics.md)
diff --git a/docs/ingressclass-metrics.md b/docs/ingressclass-metrics.md
new file mode 100644
index 0000000000..1be511d350
--- /dev/null
+++ b/docs/ingressclass-metrics.md
@@ -0,0 +1,8 @@
+# IngressClass Metrics
+
+| Metric name| Metric type | Labels/tags | Status |
+| ---------- | ----------- | ----------- | ----------- |
+| kube_ingressclass_annotations | Gauge | `ingressclass`=<ingressclass-name>
`annotation_INGRESSCLASS_ANNOTATION`=<INGRESSCLASS_ANNOTATION> | EXPERIMENTAL |
+| kube_ingressclass_info | Gauge | `ingressclass`=<ingressclass-name>
`controller`=<ingress-controller-name>
| STABLE |
+| kube_ingressclass_labels | Gauge | `ingressclass`=<ingressclass-name>
`label_INGRESSCLASS_LABEL`=<INGRESSCLASS_LABEL> | STABLE |
+| kube_ingressclass_created | Gauge | `ingressclass`=<ingressclass-name> | STABLE |
diff --git a/examples/autosharding/cluster-role.yaml b/examples/autosharding/cluster-role.yaml
index 2a54e60129..235ed7f570 100644
--- a/examples/autosharding/cluster-role.yaml
+++ b/examples/autosharding/cluster-role.yaml
@@ -97,6 +97,7 @@ rules:
- networking.k8s.io
resources:
- networkpolicies
+ - ingressclasses
- ingresses
verbs:
- list
diff --git a/examples/standard/cluster-role.yaml b/examples/standard/cluster-role.yaml
index 2a54e60129..235ed7f570 100644
--- a/examples/standard/cluster-role.yaml
+++ b/examples/standard/cluster-role.yaml
@@ -97,6 +97,7 @@ rules:
- networking.k8s.io
resources:
- networkpolicies
+ - ingressclasses
- ingresses
verbs:
- list
diff --git a/internal/store/builder.go b/internal/store/builder.go
index 11dbca14ba..35786cdaf5 100644
--- a/internal/store/builder.go
+++ b/internal/store/builder.go
@@ -295,6 +295,7 @@ var availableStores = map[string]func(f *Builder) []cache.Store{
"endpoints": func(b *Builder) []cache.Store { return b.buildEndpointsStores() },
"horizontalpodautoscalers": func(b *Builder) []cache.Store { return b.buildHPAStores() },
"ingresses": func(b *Builder) []cache.Store { return b.buildIngressStores() },
+ "ingressclasses": func(b *Builder) []cache.Store { return b.buildIngressClassStores() },
"jobs": func(b *Builder) []cache.Store { return b.buildJobStores() },
"leases": func(b *Builder) []cache.Store { return b.buildLeasesStores() },
"limitranges": func(b *Builder) []cache.Store { return b.buildLimitRangeStores() },
@@ -470,6 +471,10 @@ func (b *Builder) buildRoleBindingStores() []cache.Store {
return b.buildStoresFunc(roleBindingMetricFamilies(b.allowAnnotationsList["rolebindings"], b.allowLabelsList["rolebindings"]), &rbacv1.RoleBinding{}, createRoleBindingListWatch, b.useAPIServerCache)
}
+func (b *Builder) buildIngressClassStores() []cache.Store {
+ return b.buildStoresFunc(ingressClassMetricFamilies(b.allowAnnotationsList["ingressclass"], b.allowLabelsList["ingressclass"]), &networkingv1.IngressClass{}, createIngressClassListWatch, b.useAPIServerCache)
+}
+
func (b *Builder) buildStores(
metricFamilies []generator.FamilyGenerator,
expectedType interface{},
diff --git a/internal/store/ingressclass.go b/internal/store/ingressclass.go
new file mode 100644
index 0000000000..f86a3180d3
--- /dev/null
+++ b/internal/store/ingressclass.go
@@ -0,0 +1,134 @@
+/*
+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"
+
+ "k8s.io/kube-state-metrics/v2/pkg/metric"
+ generator "k8s.io/kube-state-metrics/v2/pkg/metric_generator"
+
+ networkingv1 "k8s.io/api/networking/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"
+)
+
+var (
+ descIngressClassAnnotationsName = "kube_ingressclass_annotations"
+ descIngressClassAnnotationsHelp = "Kubernetes annotations converted to Prometheus labels."
+ descIngressClassLabelsName = "kube_ingressclass_labels"
+ descIngressClassLabelsHelp = "Kubernetes labels converted to Prometheus labels."
+ descIngressClassLabelsDefaultLabels = []string{"ingressclass"}
+)
+
+func ingressClassMetricFamilies(allowAnnotationsList, allowLabelsList []string) []generator.FamilyGenerator {
+ return []generator.FamilyGenerator{
+ *generator.NewFamilyGenerator(
+ "kube_ingressclass_info",
+ "Information about ingressclass.",
+ metric.Gauge,
+ "",
+ wrapIngressClassFunc(func(s *networkingv1.IngressClass) *metric.Family {
+
+ m := metric.Metric{
+ LabelKeys: []string{"controller"},
+ LabelValues: []string{s.Spec.Controller},
+ Value: 1,
+ }
+ return &metric.Family{Metrics: []*metric.Metric{&m}}
+ }),
+ ),
+ *generator.NewFamilyGenerator(
+ "kube_ingressclass_created",
+ "Unix creation timestamp",
+ metric.Gauge,
+ "",
+ wrapIngressClassFunc(func(s *networkingv1.IngressClass) *metric.Family {
+ ms := []*metric.Metric{}
+ if !s.CreationTimestamp.IsZero() {
+ ms = append(ms, &metric.Metric{
+ Value: float64(s.CreationTimestamp.Unix()),
+ })
+ }
+ return &metric.Family{
+ Metrics: ms,
+ }
+ }),
+ ),
+ *generator.NewFamilyGenerator(
+ descIngressClassAnnotationsName,
+ descIngressClassAnnotationsHelp,
+ metric.Gauge,
+ "",
+ wrapIngressClassFunc(func(s *networkingv1.IngressClass) *metric.Family {
+ annotationKeys, annotationValues := createPrometheusLabelKeysValues("annotation", s.Annotations, allowAnnotationsList)
+ return &metric.Family{
+ Metrics: []*metric.Metric{
+ {
+ LabelKeys: annotationKeys,
+ LabelValues: annotationValues,
+ Value: 1,
+ },
+ },
+ }
+ }),
+ ),
+ *generator.NewFamilyGenerator(
+ descIngressClassLabelsName,
+ descIngressClassLabelsHelp,
+ metric.Gauge,
+ "",
+ wrapIngressClassFunc(func(s *networkingv1.IngressClass) *metric.Family {
+ labelKeys, labelValues := createPrometheusLabelKeysValues("label", s.Labels, allowLabelsList)
+ return &metric.Family{
+ Metrics: []*metric.Metric{
+ {
+ LabelKeys: labelKeys,
+ LabelValues: labelValues,
+ Value: 1,
+ },
+ },
+ }
+ }),
+ ),
+ }
+}
+
+func wrapIngressClassFunc(f func(*networkingv1.IngressClass) *metric.Family) func(interface{}) *metric.Family {
+ return func(obj interface{}) *metric.Family {
+ ingressClass := obj.(*networkingv1.IngressClass)
+
+ metricFamily := f(ingressClass)
+
+ for _, m := range metricFamily.Metrics {
+ m.LabelKeys, m.LabelValues = mergeKeyValues(descIngressClassLabelsDefaultLabels, []string{ingressClass.Name}, m.LabelKeys, m.LabelValues)
+ }
+
+ return metricFamily
+ }
+}
+
+func createIngressClassListWatch(kubeClient clientset.Interface, ns string, fieldSelector string) cache.ListerWatcher {
+ return &cache.ListWatch{
+ ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) {
+ return kubeClient.NetworkingV1().IngressClasses().List(context.TODO(), opts)
+ },
+ WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) {
+ return kubeClient.NetworkingV1().IngressClasses().Watch(context.TODO(), opts)
+ },
+ }
+}
diff --git a/internal/store/ingressclass_test.go b/internal/store/ingressclass_test.go
new file mode 100644
index 0000000000..50312d0ca3
--- /dev/null
+++ b/internal/store/ingressclass_test.go
@@ -0,0 +1,105 @@
+/*
+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"
+
+ networkingv1 "k8s.io/api/networking/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+ generator "k8s.io/kube-state-metrics/v2/pkg/metric_generator"
+)
+
+func TestIngressClassStore(t *testing.T) {
+ startTime := 1501569018
+ metav1StartTime := metav1.Unix(int64(startTime), 0)
+
+ cases := []generateMetricsTestCase{
+ {
+ Obj: &networkingv1.IngressClass{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test_ingressclass-info",
+ },
+ Spec: networkingv1.IngressClassSpec{
+ Controller: "controller",
+ },
+ },
+ Want: `
+ # HELP kube_ingressclass_info Information about ingressclass.
+ # TYPE kube_ingressclass_info gauge
+ kube_ingressclass_info{ingressclass="test_ingressclass-info",controller="controller"} 1
+ `,
+ MetricNames: []string{
+ "kube_ingressclass_info",
+ },
+ },
+ {
+ Obj: &networkingv1.IngressClass{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test_kube_ingressclass-created",
+ CreationTimestamp: metav1StartTime,
+ },
+ Spec: networkingv1.IngressClassSpec{
+ Controller: "controller",
+ },
+ },
+ Want: `
+ # HELP kube_ingressclass_created Unix creation timestamp
+ # TYPE kube_ingressclass_created gauge
+ kube_ingressclass_created{ingressclass="test_kube_ingressclass-created"} 1.501569018e+09
+ `,
+ MetricNames: []string{
+ "kube_ingressclass_created",
+ },
+ },
+ {
+ AllowAnnotationsList: []string{
+ "ingressclass.kubernetes.io/is-default-class",
+ },
+ Obj: &networkingv1.IngressClass{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test_ingressclass-labels",
+ Annotations: map[string]string{
+ "ingressclass.kubernetes.io/is-default-class": "true",
+ },
+ Labels: map[string]string{
+ "foo": "bar",
+ },
+ },
+ Spec: networkingv1.IngressClassSpec{
+ Controller: "controller",
+ },
+ },
+ Want: `
+ # HELP kube_ingressclass_annotations Kubernetes annotations converted to Prometheus labels.
+ # HELP kube_ingressclass_labels Kubernetes labels converted to Prometheus labels.
+ # TYPE kube_ingressclass_annotations gauge
+ # TYPE kube_ingressclass_labels gauge
+ kube_ingressclass_annotations{ingressclass="test_ingressclass-labels",annotation_ingressclass_kubernetes_io_is_default_class="true"} 1
+ kube_ingressclass_labels{ingressclass="test_ingressclass-labels"} 1
+ `,
+ MetricNames: []string{
+ "kube_ingressclass_annotations", "kube_ingressclass_labels",
+ },
+ },
+ }
+ for i, c := range cases {
+ c.Func = generator.ComposeMetricGenFuncs(ingressClassMetricFamilies(c.AllowAnnotationsList, nil))
+ c.Headers = generator.ExtractMetricFamilyHeaders(ingressClassMetricFamilies(c.AllowAnnotationsList, nil))
+ 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 44ff393ebc..2275c7b584 100644
--- a/jsonnet/kube-state-metrics/kube-state-metrics.libsonnet
+++ b/jsonnet/kube-state-metrics/kube-state-metrics.libsonnet
@@ -134,6 +134,7 @@
apiGroups: ['networking.k8s.io'],
resources: [
'networkpolicies',
+ 'ingressclasses',
'ingresses',
],
verbs: ['list', 'watch'],
diff --git a/tests/manifests/ingress.yaml b/tests/manifests/ingress.yaml
index 05428277e1..7e35f7e343 100644
--- a/tests/manifests/ingress.yaml
+++ b/tests/manifests/ingress.yaml
@@ -1,4 +1,4 @@
-apiVersion: networking.k8s.io/v1
+apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
diff --git a/tests/manifests/ingressclass.yaml b/tests/manifests/ingressclass.yaml
new file mode 100644
index 0000000000..95a5aa1278
--- /dev/null
+++ b/tests/manifests/ingressclass.yaml
@@ -0,0 +1,8 @@
+apiVersion: networking.k8s.io/v1
+kind: IngressClass
+metadata:
+ name: example-ingressclass
+ annotations:
+ ingressclass.kubernetes.io/is-default-class: "true"
+spec:
+ controller: example-ingress/controller