From 35932791f54ff9c0a676a77b74f6155e21a6515e Mon Sep 17 00:00:00 2001 From: Christian Schlotter Date: Fri, 19 Aug 2022 17:28:08 +0200 Subject: [PATCH] Allow defining custom resource flags multiple times --- docs/customresourcestate-metrics.md | 10 ++++++---- main.go | 19 ++++++++++--------- pkg/customresourcestate/config.go | 25 +++++++++++++++++++++++-- pkg/options/options.go | 8 ++++---- 4 files changed, 43 insertions(+), 19 deletions(-) diff --git a/docs/customresourcestate-metrics.md b/docs/customresourcestate-metrics.md index 501db66add..bb9262a4ef 100644 --- a/docs/customresourcestate-metrics.md +++ b/docs/customresourcestate-metrics.md @@ -9,12 +9,14 @@ A YAML configuration file described below is required to define your custom reso Two flags can be used: - * `--custom-resource-state-config "inline yaml (see example)"` or - * `--custom-resource-state-config-file /path/to/config.yaml` +* `--custom-resource-state-config "inline yaml (see example)"` or +* `--custom-resource-state-config-file /path/to/config.yaml` -If both flags are provided, the inline configuration will take precedence. +Both flags can be provided multiple times to load multiple configurations. +When multiple entries for the same resource exist, kube-state-metrics will refuse to start. +This includes configuration which refers to a different API version. -In addition to specifying one of `--custom-resource-state-config*` flags, you should also add the custom resource *Kind*s in plural form to the list of exposed resources in the `--resources` flag. If you don't specify `--resources`, then all known custom resources configured in `--custom-resource-state-config-*` and all available default kubernetes objects will be taken into account by kube-state-metrics. +In addition to specifying one of `--custom-resource-state-config*` flags, you should also add the custom resource *Kind*s in plural form to the list of exposed resources in the `--resources` flag. If you don't specify `--resources`, then all known custom resources configured in `--custom-resource-state-config*` and all available default kubernetes objects will be taken into account by kube-state-metrics. ```yaml apiVersion: apps/v1 diff --git a/main.go b/main.go index a0e6c97a45..1e37c19a91 100644 --- a/main.go +++ b/main.go @@ -53,8 +53,8 @@ func main() { } var factories []customresource.RegistryFactory - if config, set := resolveCustomResourceConfig(opts); set { - crf, err := customresourcestate.FromConfig(config) + if configs := resolveCustomResourceConfigs(opts); len(configs) > 0 { + crf, err := customresourcestate.FromConfigs(configs) if err != nil { klog.ErrorS(err, "Parsing from Custom Resource State Metrics file failed") klog.FlushAndExit(klog.ExitFlushTimeout, 1) @@ -69,17 +69,18 @@ func main() { } } -func resolveCustomResourceConfig(opts *options.Options) (customresourcestate.ConfigDecoder, bool) { - if s := opts.CustomResourceConfig; s != "" { - return yaml.NewDecoder(strings.NewReader(s)), true +func resolveCustomResourceConfigs(opts *options.Options) []customresourcestate.ConfigDecoder { + decoders := []customresourcestate.ConfigDecoder{} + for _, s := range opts.CustomResourceConfigs { + decoders = append(decoders, yaml.NewDecoder(strings.NewReader(s))) } - if file := opts.CustomResourceConfigFile; file != "" { + for _, file := range opts.CustomResourceConfigFiles { f, err := os.Open(file) if err != nil { - klog.ErrorS(err, "Custom Resource State Metrics file could not be opened") + klog.ErrorS(err, "Custom Resource State Metrics file could not be opened", "file", file) klog.FlushAndExit(klog.ExitFlushTimeout, 1) } - return yaml.NewDecoder(f), true + decoders = append(decoders, yaml.NewDecoder(f)) } - return nil, false + return decoders } diff --git a/pkg/customresourcestate/config.go b/pkg/customresourcestate/config.go index 01dfd8fa8f..fc9a6db191 100644 --- a/pkg/customresourcestate/config.go +++ b/pkg/customresourcestate/config.go @@ -161,9 +161,30 @@ type ConfigDecoder interface { Decode(v interface{}) (err error) } -// FromConfig decodes a configuration source into a slice of customresource.RegistryFactory that are ready to use. -func FromConfig(decoder ConfigDecoder) (factories []customresource.RegistryFactory, err error) { +// FromConfigs decodes a configuration source into a slice of customresource.RegistryFactory that are ready to use. +func FromConfigs(decoders []ConfigDecoder) ([]customresource.RegistryFactory, error) { + factoriesIndex := map[string]bool{} + var factories []customresource.RegistryFactory + for _, decoder := range decoders { + fs, err := fromConfig(decoder) + if err != nil { + return nil, err + } + for _, f := range fs { + if _, ok := factoriesIndex[f.Name()]; ok { + return nil, fmt.Errorf("found multiple custom resource configurations for the same resource %s", f.Name()) + } + factoriesIndex[f.Name()] = true + factories = append(factories, f) + } + } + + return factories, nil +} + +func fromConfig(decoder ConfigDecoder) ([]customresource.RegistryFactory, error) { var crconfig Metrics + var factories []customresource.RegistryFactory if err := decoder.Decode(&crconfig); err != nil { return nil, fmt.Errorf("failed to parse Custom Resource State metrics: %w", err) } diff --git a/pkg/options/options.go b/pkg/options/options.go index f87b459c0c..20c43d47b7 100644 --- a/pkg/options/options.go +++ b/pkg/options/options.go @@ -54,8 +54,8 @@ type Options struct { UseAPIServerCache bool - CustomResourceConfig string - CustomResourceConfigFile string + CustomResourceConfigs []string + CustomResourceConfigFiles []string flags *pflag.FlagSet } @@ -115,8 +115,8 @@ func (o *Options) AddFlags() { o.flags.BoolVarP(&o.Version, "version", "", false, "kube-state-metrics build version information") o.flags.BoolVar(&o.EnableGZIPEncoding, "enable-gzip-encoding", false, "Gzip responses when requested by clients via 'Accept-Encoding: gzip' header.") - o.flags.StringVar(&o.CustomResourceConfig, "custom-resource-state-config", "", "Inline Custom Resource State Metrics config YAML (experimental)") - o.flags.StringVar(&o.CustomResourceConfigFile, "custom-resource-state-config-file", "", "Path to a Custom Resource State Metrics config file (experimental)") + o.flags.StringArrayVar(&o.CustomResourceConfigs, "custom-resource-state-config", []string{}, "Inline Custom Resource State Metrics config YAML (experimental)") + o.flags.StringArrayVar(&o.CustomResourceConfigFiles, "custom-resource-state-config-file", []string{}, "Path to a Custom Resource State Metrics config file (experimental)") } // Parse parses the flag definitions from the argument list.