From 8a8d82cfa704827df764dc169334c240a08ecc77 Mon Sep 17 00:00:00 2001 From: Pranshu Srivastava Date: Sat, 8 Oct 2022 00:45:00 +0530 Subject: [PATCH] Allow optional VK in CR metrics Allow optional VK in CR metrics, while only requiring the group to be fixed. This would allow users to define custom metrics for: * a specific API version: version is fixed, kind varies. * all the API versions of a resource: version varies, kind is fixed. * all the APIs part of an API group: version varies, kind varies. Signed-off-by: Pranshu Srivastava --- main.go | 2 +- pkg/customresourcestate/config.go | 72 ++++++++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index a0e6c97a45..fb3b82db55 100644 --- a/main.go +++ b/main.go @@ -54,7 +54,7 @@ func main() { var factories []customresource.RegistryFactory if config, set := resolveCustomResourceConfig(opts); set { - crf, err := customresourcestate.FromConfig(config) + crf, err := customresourcestate.FromConfig(config, opts) if err != nil { klog.ErrorS(err, "Parsing from Custom Resource State Metrics file failed") klog.FlushAndExit(klog.ExitFlushTimeout, 1) diff --git a/pkg/customresourcestate/config.go b/pkg/customresourcestate/config.go index 76e7786763..f33e90fef4 100644 --- a/pkg/customresourcestate/config.go +++ b/pkg/customresourcestate/config.go @@ -21,9 +21,12 @@ import ( "strings" "github.com/gobuffalo/flect" + "k8s.io/client-go/discovery" + "k8s.io/client-go/tools/clientcmd" "k8s.io/klog/v2" "k8s.io/kube-state-metrics/v2/pkg/customresource" + "k8s.io/kube-state-metrics/v2/pkg/options" ) // Metrics is the top level configuration object. @@ -161,14 +164,81 @@ type ConfigDecoder interface { Decode(v interface{}) (err error) } +// generateResources generates the set of possible resources from the varying V and, or K of the original config. +func generateResources(resources []Resource, opts *options.Options) { + d := getDiscoveryClient(opts) + _, resourceLists, err := d.ServerGroupsAndResources() + if err != nil { + klog.Fatalf("Failed to get resource lists: %v", err) + } + r := map[GroupVersionKind]bool{} + var out []Resource + for resourceIndex := range resources { + configGVK := resources[resourceIndex].GroupVersionKind + originalConfigGVK := configGVK + if configGVK.Version == "" || configGVK.Kind == "" { + // resourceLists is a list of APIGroupResources, which is a list of APIResources. + // These use the GV as the primary key. + for groupVersionListIndex := range resourceLists { + var discoveredGroup string + if strings.Contains(resourceLists[groupVersionListIndex].GroupVersion, "/") { + discoveredGroup = strings.Split(resourceLists[groupVersionListIndex].GroupVersion, "/")[0] + } else { + discoveredGroup = "" + } + if discoveredGroup == configGVK.Group { + for discoveredResourceIndex := range resourceLists[groupVersionListIndex].APIResources { + discoveredResource := resourceLists[groupVersionListIndex].APIResources[discoveredResourceIndex] + // - groupVersionKind: + // group: "apps" + if configGVK.Version == "" && configGVK.Kind == "" { + configGVK.Version = discoveredResource.Version + configGVK.Kind = discoveredResource.Kind + } + // - groupVersionKind: + // group: "apps" + // kind: "Deployment" + if configGVK.Version == "" && configGVK.Kind != "" { + configGVK.Version = discoveredResource.Version + } + // - groupVersionKind: + // group: "apps" + // version: "v1" + if configGVK.Version != "" && configGVK.Kind == "" { + configGVK.Kind = discoveredResource.Kind + } + // Collect all possible GVKs for the current config. + r[configGVK] = true + configGVK = originalConfigGVK + } + } + } + } + for gvk := range r { + resources[resourceIndex].GroupVersionKind = gvk + out = append(out, resources[resourceIndex]) + } + } + resources = out +} + +func getDiscoveryClient(opts *options.Options) *discovery.DiscoveryClient { + config, err := clientcmd.BuildConfigFromFlags(opts.Apiserver, opts.Kubeconfig) + if err != nil { + klog.Fatalf("Error building kubeconfig: %v", err) + } + return discovery.NewDiscoveryClientForConfigOrDie(config) +} + // FromConfig decodes a configuration source into a slice of customresource.RegistryFactory that are ready to use. -func FromConfig(decoder ConfigDecoder) ([]customresource.RegistryFactory, error) { +func FromConfig(decoder ConfigDecoder, opts *options.Options) ([]customresource.RegistryFactory, error) { var crconfig Metrics var factories []customresource.RegistryFactory factoriesIndex := map[string]bool{} if err := decoder.Decode(&crconfig); err != nil { return nil, fmt.Errorf("failed to parse Custom Resource State metrics: %w", err) } + generateResources(crconfig.Spec.Resources, opts) for _, resource := range crconfig.Spec.Resources { factory, err := NewCustomResourceMetrics(resource) if err != nil {