From a4579e56d4989a0edfee343c76b5a27508270dd2 Mon Sep 17 00:00:00 2001 From: CSTDev Date: Fri, 7 Mar 2025 22:26:50 +0000 Subject: [PATCH 1/3] Make exporter publicly accessible --- cmd/x509-certificate-exporter/main.go | 14 ++-- {internal => pkg/exporter}/certificate.go | 2 +- {internal => pkg/exporter}/collector.go | 48 ++++++------- {internal => pkg/exporter}/exporter.go | 67 ++++++++++++++++++- {internal => pkg/exporter}/exporter_test.go | 2 +- {internal => pkg/exporter}/kubernetes.go | 2 +- {internal => pkg/exporter}/kubernetes_test.go | 2 +- {internal => pkg/exporter}/utility.go | 2 +- {internal => pkg/exporter}/version.go | 2 +- 9 files changed, 101 insertions(+), 40 deletions(-) rename {internal => pkg/exporter}/certificate.go (99%) rename {internal => pkg/exporter}/collector.go (78%) rename {internal => pkg/exporter}/exporter.go (85%) rename {internal => pkg/exporter}/exporter_test.go (99%) rename {internal => pkg/exporter}/kubernetes.go (99%) rename {internal => pkg/exporter}/kubernetes_test.go (99%) rename {internal => pkg/exporter}/utility.go (94%) rename {internal => pkg/exporter}/version.go (87%) diff --git a/cmd/x509-certificate-exporter/main.go b/cmd/x509-certificate-exporter/main.go index b4b1656f..6ceb17ba 100644 --- a/cmd/x509-certificate-exporter/main.go +++ b/cmd/x509-certificate-exporter/main.go @@ -16,7 +16,7 @@ import ( "go.uber.org/automaxprocs/maxprocs" "k8s.io/client-go/util/flowcontrol" - "github.com/enix/x509-certificate-exporter/v3/internal" + "github.com/enix/x509-certificate-exporter/v3/pkg/exporter" getopt "github.com/pborman/getopt/v2" ) @@ -89,7 +89,7 @@ func main() { } if *version { - fmt.Fprintf(os.Stderr, "version %s\n", internal.Version) + fmt.Fprintf(os.Stderr, "version %s\n", exporter.Version) return } @@ -99,7 +99,7 @@ func main() { } slog.SetDefault(logger) - slog.Info("Starting exporter", "version", internal.Version, "revision", internal.Revision, "build_time", internal.BuildDateTime) + slog.Info("Starting exporter", "version", exporter.Version, "revision", exporter.Revision, "build_time", exporter.BuildDateTime) if *profile { go func() { @@ -124,23 +124,23 @@ func main() { slog.Error("Cannot set GOMEMLIMIT with automemlimit", "reason", err.Error()) } - kubeSecretTypes := make([]internal.KubeSecretType, 0) + kubeSecretTypes := make([]exporter.KubeSecretType, 0) for _, pattern := range kubeSecretTypePatterns { - kst, err := internal.ParseSecretType(pattern) + kst, err := exporter.ParseSecretType(pattern) if err != nil { log.Fatal("failed to parse --secret-type argument: ", err) } kubeSecretTypes = append(kubeSecretTypes, kst) } - exporter := internal.Exporter{ + exporter := exporter.Exporter{ ListenAddress: *listenAddress, SystemdSocket: *systemdSocket, ConfigFile: *configFile, Files: files, Directories: directories, YAMLs: yamls, - YAMLPaths: internal.DefaultYamlPaths, + YAMLPaths: exporter.DefaultYamlPaths, TrimPathComponents: *trimPathComponents, MaxCacheDuration: time.Duration(maxCacheDuration), ExposeRelativeMetrics: *exposeRelativeMetrics, diff --git a/internal/certificate.go b/pkg/exporter/certificate.go similarity index 99% rename from internal/certificate.go rename to pkg/exporter/certificate.go index 0a0c855d..508a5148 100644 --- a/internal/certificate.go +++ b/pkg/exporter/certificate.go @@ -1,4 +1,4 @@ -package internal +package exporter import ( "crypto/x509" diff --git a/internal/collector.go b/pkg/exporter/collector.go similarity index 78% rename from internal/collector.go rename to pkg/exporter/collector.go index e03cb0bc..4a1aff12 100644 --- a/internal/collector.go +++ b/pkg/exporter/collector.go @@ -1,15 +1,15 @@ -package internal +package exporter import ( + "log/slog" "runtime" "time" - "log/slog" "github.com/prometheus/client_golang/prometheus" ) -type collector struct { - exporter *Exporter +type Collector struct { + Exporter *Exporter } var ( @@ -51,28 +51,28 @@ var ( "goos": runtime.GOOS, "goarch": runtime.GOARCH, } - infoDesc = prometheus.NewDesc(infoMetric, infoHelp, nil, infoConstLabels) + //infoDesc = prometheus.NewDesc(infoMetric, infoHelp, nil, infoConstLabels) ) -func (collector *collector) Describe(ch chan<- *prometheus.Desc) { +func (collector Collector) Describe(ch chan<- *prometheus.Desc) { ch <- certExpiredDesc ch <- certNotBeforeDesc ch <- certNotAfterDesc ch <- certErrorsDesc - ch <- infoDesc + //ch <- infoDesc - if collector.exporter.ExposeRelativeMetrics { + if collector.Exporter.ExposeRelativeMetrics { ch <- certExpiresInDesc ch <- certValidSinceDesc } - if collector.exporter.ExposeErrorMetrics { + if collector.Exporter.ExposeErrorMetrics { ch <- certErrorDesc } } -func (collector *collector) Collect(ch chan<- prometheus.Metric) { - certRefs, certErrors := collector.exporter.parseAllCertificates() +func (collector Collector) Collect(ch chan<- prometheus.Metric) { + certRefs, certErrors := collector.Exporter.parseAllCertificates() for _, certRef := range certRefs { for _, cert := range certRef.certificates { @@ -82,8 +82,8 @@ func (collector *collector) Collect(ch chan<- prometheus.Metric) { } } - if collector.exporter.ExposeErrorMetrics && len(certRef.certificates) > 0 { - labelKeys, labelValues := collector.exporter.unzipLabels(collector.exporter.getBaseLabels(certRef)) + if collector.Exporter.ExposeErrorMetrics && len(certRef.certificates) > 0 { + labelKeys, labelValues := collector.Exporter.unzipLabels(collector.Exporter.getBaseLabels(certRef)) ch <- prometheus.MustNewConstMetric( prometheus.NewDesc(certErrorMetric, certErrorHelp, labelKeys, nil), @@ -99,8 +99,8 @@ func (collector *collector) Collect(ch chan<- prometheus.Metric) { slog.Debug("Collect read error", "reason", err, "index", index) } - if collector.exporter.ExposeErrorMetrics && err.ref != nil { - labelKeys, labelValues := collector.exporter.unzipLabels(collector.exporter.getBaseLabels(err.ref)) + if collector.Exporter.ExposeErrorMetrics && err.ref != nil { + labelKeys, labelValues := collector.Exporter.unzipLabels(collector.Exporter.getBaseLabels(err.ref)) ch <- prometheus.MustNewConstMetric( prometheus.NewDesc(certErrorMetric, certErrorHelp, labelKeys, nil), @@ -117,22 +117,22 @@ func (collector *collector) Collect(ch chan<- prometheus.Metric) { float64(len(certErrors)), ) - ch <- prometheus.MustNewConstMetric( - infoDesc, - prometheus.GaugeValue, - float64(1), - ) + // ch <- prometheus.MustNewConstMetric( + // infoDesc, + // prometheus.GaugeValue, + // float64(1), + // ) } -func (collector *collector) getMetricsForCertificate(certData *parsedCertificate, ref *certificateRef) []prometheus.Metric { - labels := collector.exporter.getLabels(certData, ref) +func (collector Collector) getMetricsForCertificate(certData *parsedCertificate, ref *certificateRef) []prometheus.Metric { + labels := collector.Exporter.getLabels(certData, ref) expired := 0. if time.Now().After(certData.cert.NotAfter) { expired = 1. } - labelKeys, labelValues := collector.exporter.unzipLabels(labels) + labelKeys, labelValues := collector.Exporter.unzipLabels(labels) metrics := []prometheus.Metric{ prometheus.MustNewConstMetric( prometheus.NewDesc(certExpiredMetric, certExpiredHelp, labelKeys, nil), @@ -154,7 +154,7 @@ func (collector *collector) getMetricsForCertificate(certData *parsedCertificate ), } - if collector.exporter.ExposeRelativeMetrics { + if collector.Exporter.ExposeRelativeMetrics { metrics = append(metrics, prometheus.MustNewConstMetric( prometheus.NewDesc(certExpiresInMetric, certExpiresInHelp, labelKeys, nil), prometheus.GaugeValue, diff --git a/internal/exporter.go b/pkg/exporter/exporter.go similarity index 85% rename from internal/exporter.go rename to pkg/exporter/exporter.go index b603d402..c7f976c5 100644 --- a/internal/exporter.go +++ b/pkg/exporter/exporter.go @@ -1,4 +1,4 @@ -package internal +package exporter import ( "context" @@ -24,6 +24,7 @@ import ( "github.com/dimiro1/health" "github.com/prometheus/exporter-toolkit/web" "k8s.io/client-go/kubernetes" + "k8s.io/client-go/util/flowcontrol" ) // Exporter : Configuration (from command-line) @@ -95,13 +96,73 @@ func (exporter *Exporter) ListenAndServe() error { return exporter.Serve() } +type Options struct { + Directories []string + Files []string + YAMLs []string + YAMLPaths []YAMLCertRef + TrimPathComponents int + MaxCacheDuration time.Duration + ExposeRelativeMetrics bool + ExposeErrorMetrics bool + ExposeLabels []string + ConfigMapKeys []string + KubeEnabled bool + KubeConfigPath string + KubeSecretTypes []KubeSecretType + KubeIncludeNamespaces []string + KubeExcludeNamespaces []string + KubeIncludeNamespaceLabels []string + KubeExcludeNamespaceLabels []string + KubeIncludeLabels []string + KubeExcludeLabels []string +} + +func New(options Options) (Exporter, error) { + + exporter := Exporter{ + Directories: options.Directories, + Files: options.Files, + YAMLs: options.YAMLs, + YAMLPaths: options.YAMLPaths, + TrimPathComponents: options.TrimPathComponents, + MaxCacheDuration: options.MaxCacheDuration, + ExposeRelativeMetrics: options.ExposeRelativeMetrics, + ExposeErrorMetrics: options.ExposeErrorMetrics, + ExposeLabels: options.ExposeLabels, + ConfigMapKeys: options.ConfigMapKeys, + KubeSecretTypes: options.KubeSecretTypes, + KubeIncludeNamespaces: options.KubeIncludeNamespaces, + KubeExcludeNamespaces: options.KubeExcludeNamespaces, + KubeIncludeNamespaceLabels: options.KubeIncludeNamespaceLabels, + KubeExcludeNamespaceLabels: options.KubeExcludeNamespaceLabels, + KubeIncludeLabels: options.KubeIncludeLabels, + KubeExcludeLabels: options.KubeExcludeLabels, + } + + exporter.DiscoverCertificates() + + if options.KubeEnabled { + // TODO Set rate limiter only if both QPS and burst are set - copy from main.go + var rateLimiter flowcontrol.RateLimiter + + err := exporter.ConnectToKubernetesCluster(options.KubeConfigPath, rateLimiter) + if err != nil { + slog.Error("Failed to connect to Kubernetes API", "reason", err) + return exporter, err + } + } + + return exporter, nil +} + // Listen : Listen for requests func (exporter *Exporter) Listen() error { - err := prometheus.Register(&collector{exporter: exporter}) + err := prometheus.Register(&Collector{Exporter: exporter}) if err != nil { if registered, ok := err.(prometheus.AlreadyRegisteredError); ok { prometheus.Unregister(registered.ExistingCollector) - prometheus.MustRegister(&collector{exporter: exporter}) + prometheus.MustRegister(&Collector{Exporter: exporter}) } else { return err } diff --git a/internal/exporter_test.go b/pkg/exporter/exporter_test.go similarity index 99% rename from internal/exporter_test.go rename to pkg/exporter/exporter_test.go index 0f307fec..6928340a 100644 --- a/internal/exporter_test.go +++ b/pkg/exporter/exporter_test.go @@ -1,4 +1,4 @@ -package internal +package exporter import ( "bytes" diff --git a/internal/kubernetes.go b/pkg/exporter/kubernetes.go similarity index 99% rename from internal/kubernetes.go rename to pkg/exporter/kubernetes.go index 7be1245b..8e117457 100644 --- a/internal/kubernetes.go +++ b/pkg/exporter/kubernetes.go @@ -1,4 +1,4 @@ -package internal +package exporter import ( "context" diff --git a/internal/kubernetes_test.go b/pkg/exporter/kubernetes_test.go similarity index 99% rename from internal/kubernetes_test.go rename to pkg/exporter/kubernetes_test.go index b386e803..b9b547be 100644 --- a/internal/kubernetes_test.go +++ b/pkg/exporter/kubernetes_test.go @@ -1,4 +1,4 @@ -package internal +package exporter import ( "context" diff --git a/internal/utility.go b/pkg/exporter/utility.go similarity index 94% rename from internal/utility.go rename to pkg/exporter/utility.go index 39cfabfd..b96fec0a 100644 --- a/internal/utility.go +++ b/pkg/exporter/utility.go @@ -1,4 +1,4 @@ -package internal +package exporter func unique(data []*certificateRef) []*certificateRef { output := []*certificateRef{} diff --git a/internal/version.go b/pkg/exporter/version.go similarity index 87% rename from internal/version.go rename to pkg/exporter/version.go index 007de0ba..1da01c9a 100644 --- a/internal/version.go +++ b/pkg/exporter/version.go @@ -1,4 +1,4 @@ -package internal +package exporter // Version and build informations set at link time var Version = "0.0.0" From 2e1c79d6ab625d941dd8b64fbfeba331954a7b6a Mon Sep 17 00:00:00 2001 From: CSTDev Date: Fri, 12 Sep 2025 14:25:06 +0100 Subject: [PATCH 2/3] Add kubernetes rate limiting config to exporter --- pkg/exporter/exporter.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/exporter/exporter.go b/pkg/exporter/exporter.go index c7f976c5..fdac825b 100644 --- a/pkg/exporter/exporter.go +++ b/pkg/exporter/exporter.go @@ -116,6 +116,8 @@ type Options struct { KubeExcludeNamespaceLabels []string KubeIncludeLabels []string KubeExcludeLabels []string + RateLimitQPS int + RateLimitBurst int } func New(options Options) (Exporter, error) { @@ -143,8 +145,12 @@ func New(options Options) (Exporter, error) { exporter.DiscoverCertificates() if options.KubeEnabled { - // TODO Set rate limiter only if both QPS and burst are set - copy from main.go + // Set rate limiter only if both QPS and burst are set var rateLimiter flowcontrol.RateLimiter + if options.RateLimitQPS > 0 && options.RateLimitBurst > 0 { + slog.Info("Setting Kubernetes API rate limiter", "qps", options.RateLimitQPS, "burst", options.RateLimitBurst) + rateLimiter = flowcontrol.NewTokenBucketRateLimiter(float32(options.RateLimitQPS), options.RateLimitBurst) + } err := exporter.ConnectToKubernetesCluster(options.KubeConfigPath, rateLimiter) if err != nil { From faae4a0dff3d4be234b0d6046b8de29fcaf6bdd4 Mon Sep 17 00:00:00 2001 From: CSTDev Date: Fri, 12 Sep 2025 14:46:52 +0100 Subject: [PATCH 3/3] Re-organise struct --- pkg/exporter/exporter.go | 48 ++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/pkg/exporter/exporter.go b/pkg/exporter/exporter.go index fdac825b..6a7c85a0 100644 --- a/pkg/exporter/exporter.go +++ b/pkg/exporter/exporter.go @@ -58,6 +58,30 @@ type Exporter struct { configMapsCache *cache.Cache } +type Options struct { + Directories []string + Files []string + YAMLs []string + YAMLPaths []YAMLCertRef + TrimPathComponents int + MaxCacheDuration time.Duration + ExposeRelativeMetrics bool + ExposeErrorMetrics bool + ExposeLabels []string + ConfigMapKeys []string + KubeEnabled bool + KubeConfigPath string + KubeSecretTypes []KubeSecretType + KubeIncludeNamespaces []string + KubeExcludeNamespaces []string + KubeIncludeNamespaceLabels []string + KubeExcludeNamespaceLabels []string + KubeIncludeLabels []string + KubeExcludeLabels []string + RateLimitQPS int + RateLimitBurst int +} + type KubeSecretType struct { Type string Regexp *regexp.Regexp @@ -96,30 +120,6 @@ func (exporter *Exporter) ListenAndServe() error { return exporter.Serve() } -type Options struct { - Directories []string - Files []string - YAMLs []string - YAMLPaths []YAMLCertRef - TrimPathComponents int - MaxCacheDuration time.Duration - ExposeRelativeMetrics bool - ExposeErrorMetrics bool - ExposeLabels []string - ConfigMapKeys []string - KubeEnabled bool - KubeConfigPath string - KubeSecretTypes []KubeSecretType - KubeIncludeNamespaces []string - KubeExcludeNamespaces []string - KubeIncludeNamespaceLabels []string - KubeExcludeNamespaceLabels []string - KubeIncludeLabels []string - KubeExcludeLabels []string - RateLimitQPS int - RateLimitBurst int -} - func New(options Options) (Exporter, error) { exporter := Exporter{