diff --git a/docs/customresourcestate-metrics.md b/docs/customresourcestate-metrics.md index 5296091e..d21fe37b 100644 --- a/docs/customresourcestate-metrics.md +++ b/docs/customresourcestate-metrics.md @@ -49,7 +49,7 @@ spec: - --resources=certificatesigningrequests,configmaps,cronjobs,daemonsets,deployments,endpoints,foos,horizontalpodautoscalers,ingresses,jobs,limitranges,mutatingwebhookconfigurations,namespaces,networkpolicies,nodes,persistentvolumeclaims,persistentvolumes,poddisruptionbudgets,pods,replicasets,replicationcontrollers,resourcequotas,secrets,services,statefulsets,storageclasses,validatingwebhookconfigurations,volumeattachments,verticalpodautoscalers ``` -NOTE: The `group`, `version`, and `kind` common labels are reserved, and will be overwritten by the values from the `groupVersionKind` field. +NOTE: The `customresource_group`, `customresource_version`, and `customresource_kind` common labels are reserved, and will be overwritten by the values from the `groupVersionKind` field. ### Examples diff --git a/pkg/customresourcestate/config.go b/pkg/customresourcestate/config.go index 288c1e90..4ec21f05 100644 --- a/pkg/customresourcestate/config.go +++ b/pkg/customresourcestate/config.go @@ -26,6 +26,10 @@ import ( "k8s.io/kube-state-metrics/v2/pkg/customresource" ) +// customResourceState is used to prefix the auto-generated GVK labels as well as an appendix for the metric itself +// if no custom metric name is defined +const customResourceState string = "customresource" + // Metrics is the top level configuration object. type Metrics struct { Spec MetricsSpec `yaml:"spec" json:"spec"` @@ -64,7 +68,7 @@ type Resource struct { func (r Resource) GetMetricNamePrefix() string { p := r.MetricNamePrefix if p == nil { - return "kube_crd" + return "kube_" + customResourceState } return *p } diff --git a/pkg/customresourcestate/custom_resource_metrics_test.go b/pkg/customresourcestate/custom_resource_metrics_test.go index 9bd1a5c5..e62e333c 100644 --- a/pkg/customresourcestate/custom_resource_metrics_test.go +++ b/pkg/customresourcestate/custom_resource_metrics_test.go @@ -17,18 +17,25 @@ limitations under the License. package customresourcestate import ( + "encoding/json" + "reflect" "testing" + + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/utils/pointer" ) func TestNewCustomResourceMetrics(t *testing.T) { + tests := []struct { - r Resource - wantErr bool - name string + r Resource + wantErr bool + wantResult *customResourceMetrics + name string }{ { // https://github.com/kubernetes/kube-state-metrics/issues/1886 - name: "dynamic metric type (not just hardcoded to gauge)", + name: "cr metric with dynamic metric type", r: Resource{ GroupVersionKind: GroupVersionKind{ Group: "apps", @@ -39,6 +46,9 @@ func TestNewCustomResourceMetrics(t *testing.T) { LabelsFromPath: map[string][]string{ "name": {"metadata", "name"}, }, + CommonLabels: map[string]string{ + "hello": "world", + }, }, Metrics: []Generator{ { @@ -60,16 +70,178 @@ func TestNewCustomResourceMetrics(t *testing.T) { }, }, wantErr: false, + wantResult: &customResourceMetrics{ + MetricNamePrefix: "kube_customresource", + GroupVersionKind: schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "Deployment", + }, + ResourceName: "deployments", + Families: []compiledFamily{ + { + Name: "kube_customresource_test_metrics", + Help: "metrics for testing", + Each: &compiledInfo{}, + Labels: map[string]string{ + "customresource_group": "apps", + "customresource_kind": "Deployment", + "customresource_version": "v1", + "hello": "world", + }, + LabelFromPath: map[string]valuePath{ + "name": mustCompilePath(t, "metadata", "name"), + }, + }, + }, + }, + }, + { + name: "cr metric with custom prefix", + r: Resource{ + GroupVersionKind: GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "Deployment", + }, + Labels: Labels{ + LabelsFromPath: map[string][]string{ + "name": {"metadata", "name"}, + }, + CommonLabels: map[string]string{ + "hello": "world", + }, + }, + Metrics: []Generator{ + { + Name: "test_metrics", + Help: "metrics for testing", + Each: Metric{ + Type: MetricTypeInfo, + Info: &MetricInfo{ + MetricMeta: MetricMeta{ + Path: []string{ + "metadata", + "annotations", + }, + }, + LabelFromKey: "test", + }, + }, + }, + }, + MetricNamePrefix: pointer.String("apps_deployment"), + }, + wantErr: false, + wantResult: &customResourceMetrics{ + MetricNamePrefix: "apps_deployment", + GroupVersionKind: schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "Deployment", + }, + ResourceName: "deployments", + Families: []compiledFamily{ + { + Name: "apps_deployment_test_metrics", + Help: "metrics for testing", + Each: &compiledInfo{}, + Labels: map[string]string{ + "customresource_group": "apps", + "customresource_kind": "Deployment", + "customresource_version": "v1", + "hello": "world", + }, + LabelFromPath: map[string]valuePath{ + "name": mustCompilePath(t, "metadata", "name"), + }, + }, + }, + }, + }, + { + name: "cr metric with custom prefix - expect error", + r: Resource{ + GroupVersionKind: GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "Deployment", + }, + Labels: Labels{ + LabelsFromPath: map[string][]string{ + "name": {"metadata", "name"}, + }, + CommonLabels: map[string]string{ + "hello": "world", + }, + }, + Metrics: []Generator{ + { + Name: "test_metrics", + Help: "metrics for testing", + Each: Metric{ + Type: MetricTypeInfo, + Info: &MetricInfo{ + MetricMeta: MetricMeta{ + Path: []string{ + "metadata", + "annotations", + }, + }, + LabelFromKey: "test", + }, + }, + }, + }, + MetricNamePrefix: pointer.String("apps_deployment"), + }, + wantErr: true, + wantResult: &customResourceMetrics{ + GroupVersionKind: schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "Deployment", + }, + ResourceName: "deployments", + Families: []compiledFamily{ + { + Name: "apps_deployment_test_metrics", + Help: "metrics for testing", + Each: &compiledInfo{}, + Labels: map[string]string{ + "customresource_group": "apps", + "customresource_kind": "Deployment", + "customresource_version": "v1", + "hello": "world", + }, + LabelFromPath: map[string]valuePath{ + "name": mustCompilePath(t, "metadata", "name"), + }, + }, + }, + }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { v, err := NewCustomResourceMetrics(tt.r) - expectedError := v.(*customResourceMetrics).Families[0].Each.Type() != "info" - if (err != nil) != tt.wantErr || expectedError { - t.Errorf("NewCustomResourceMetrics() error = %v, wantErr %v", err, tt.wantErr) - return + if err != nil { + t.Errorf(err.Error()) + } + + // convert to JSON for easier nil comparison + ttWantJSON, _ := json.Marshal(tt.wantResult) + customResourceMetricJSON, _ := json.Marshal(v.(*customResourceMetrics)) + + if !tt.wantErr && !reflect.DeepEqual(ttWantJSON, customResourceMetricJSON) { + t.Errorf("NewCustomResourceMetrics: error expected %v", tt.wantErr) + t.Errorf("NewCustomResourceMetrics:\n %#v\n is not deep equal\n %#v", v, tt.wantResult) + } + + if tt.wantErr && reflect.DeepEqual(ttWantJSON, customResourceMetricJSON) { + t.Errorf("NewCustomResourceMetrics: error expected %v", tt.wantErr) + t.Errorf("NewCustomResourceMetrics:\n %#v\n is not deep equal\n %#v", v, tt.wantResult) } }) } diff --git a/pkg/customresourcestate/registry_factory.go b/pkg/customresourcestate/registry_factory.go index e027cae8..7e9eca53 100644 --- a/pkg/customresourcestate/registry_factory.go +++ b/pkg/customresourcestate/registry_factory.go @@ -38,9 +38,9 @@ func compile(resource Resource) ([]compiledFamily, error) { if resource.CommonLabels == nil { resource.CommonLabels = map[string]string{} } - resource.CommonLabels["group"] = resource.GroupVersionKind.Group - resource.CommonLabels["version"] = resource.GroupVersionKind.Version - resource.CommonLabels["kind"] = resource.GroupVersionKind.Kind + resource.CommonLabels[customResourceState+"_group"] = resource.GroupVersionKind.Group + resource.CommonLabels[customResourceState+"_version"] = resource.GroupVersionKind.Version + resource.CommonLabels[customResourceState+"_kind"] = resource.GroupVersionKind.Kind for _, f := range resource.Metrics { family, err := compileFamily(f, resource) if err != nil { diff --git a/pkg/customresourcestate/registry_factory_test.go b/pkg/customresourcestate/registry_factory_test.go index e4da7d99..1b42a570 100644 --- a/pkg/customresourcestate/registry_factory_test.go +++ b/pkg/customresourcestate/registry_factory_test.go @@ -333,7 +333,7 @@ func Test_fullName(t *testing.T) { resource: r(nil), f: count, }, - want: "kube_crd_count", + want: "kube_customresource_count", }, { name: "no prefix",