From 6782d2c4c1efe097bfc7434ddcf11acefe633cbf Mon Sep 17 00:00:00 2001 From: SuperQ Date: Sat, 1 Apr 2023 09:44:00 +0200 Subject: [PATCH] Add toolkit package Implement a top level package to make it easy to bootstrap an exporter. * Move the flag package to the top level. * Add support for `--web.telemetry-path` flag. Defaults to`/metrics`. * Add a self-check function to the web FlagConfig. Signed-off-by: SuperQ --- web/kingpinflag/flag.go => flag.go | 6 ++- toolkit.go | 66 ++++++++++++++++++++++++++++++ web/tls_config.go | 21 +++++++++- 3 files changed, 90 insertions(+), 3 deletions(-) rename web/kingpinflag/flag.go => flag.go (92%) create mode 100644 toolkit.go diff --git a/web/kingpinflag/flag.go b/flag.go similarity index 92% rename from web/kingpinflag/flag.go rename to flag.go index ce6850ff..81e0fcdf 100644 --- a/web/kingpinflag/flag.go +++ b/flag.go @@ -10,7 +10,7 @@ // 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 kingpinflag +package toolkit import ( "runtime" @@ -31,6 +31,10 @@ func AddFlags(a *kingpin.Application, defaultAddress string) *web.FlagConfig { ).Bool() } flags := web.FlagConfig{ + MetricsPath: a.Flag( + "web.telemetry-path", + "Path under which to expose metrics.", + ).Default("/metrics").String(), WebListenAddresses: a.Flag( "web.listen-address", "Addresses on which to expose metrics and web interface. Repeatable for multiple addresses.", diff --git a/toolkit.go b/toolkit.go new file mode 100644 index 00000000..a766cd78 --- /dev/null +++ b/toolkit.go @@ -0,0 +1,66 @@ +// Copyright 2023 The Prometheus Authors +// 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 toolkit + +import ( + "errors" + "net/http" + "os" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" + "github.com/prometheus/exporter-toolkit/web" +) + +var ErrNoFlagConfig = errors.New("Missing FlagConfig") + +type Config struct { + MetricsHandler http.Handler + MetricsHandlerFunc *func(http.ResponseWriter, *http.Request) + FlagConfig *web.FlagConfig + LandingConfig web.LandingConfig + Logger log.Logger +} + +func Run(c Config) error { + if c.FlagConfig == nil { + return ErrNoFlagConfig + } + err := c.FlagConfig.CheckFlags() + if err != nil { + return err + } + if *c.FlagConfig.MetricsPath != "" && c.MetricsHandler != nil { + http.Handle(*c.FlagConfig.MetricsPath, c.MetricsHandler) + } + if *c.FlagConfig.MetricsPath != "" && c.MetricsHandlerFunc != nil { + http.HandleFunc(*c.FlagConfig.MetricsPath, *c.MetricsHandlerFunc) + } + if *c.FlagConfig.MetricsPath != "/" && *c.FlagConfig.MetricsPath != "" { + c.LandingConfig.Links = append(c.LandingConfig.Links, + web.LandingLinks{ + Address: *c.FlagConfig.MetricsPath, + Text: "Metrics", + }, + ) + landingPage, err := web.NewLandingPage(c.LandingConfig) + if err != nil { + level.Error(c.Logger).Log("err", err) + os.Exit(1) + } + http.Handle("/", landingPage) + } + + srv := &http.Server{} + return web.ListenAndServe(srv, c.FlagConfig, c.Logger) +} diff --git a/web/tls_config.go b/web/tls_config.go index b20fed52..c561fbed 100644 --- a/web/tls_config.go +++ b/web/tls_config.go @@ -34,6 +34,7 @@ import ( var ( errNoTLSConfig = errors.New("TLS config is not present") ErrNoListeners = errors.New("no web listen address or systemd socket flag specified") + ErrMissingFlag = errors.New("Flag config is empty") ) type Config struct { @@ -55,11 +56,26 @@ type TLSConfig struct { } type FlagConfig struct { + MetricsPath *string WebListenAddresses *[]string WebSystemdSocket *bool WebConfigFile *string } +// CheckFlags validates that the FlagConfig has all required values set and has at least one listener. +func (c *FlagConfig) CheckFlags() error { + if c.MetricsPath == nil { + return ErrMissingFlag + } + if c.WebSystemdSocket == nil && (c.WebListenAddresses == nil || len(*c.WebListenAddresses) == 0) { + return ErrNoListeners + } + if c.WebConfigFile == nil { + return ErrMissingFlag + } + return nil +} + // SetDirectory joins any relative file paths with dir. func (t *TLSConfig) SetDirectory(dir string) { t.TLSCertPath = config_util.JoinDir(dir, t.TLSCertPath) @@ -204,8 +220,9 @@ func ServeMultiple(listeners []net.Listener, server *http.Server, flags *FlagCon // WebSystemdSocket in the FlagConfig is true. The FlagConfig is also passed on // to ServeMultiple. func ListenAndServe(server *http.Server, flags *FlagConfig, logger log.Logger) error { - if flags.WebSystemdSocket == nil && (flags.WebListenAddresses == nil || len(*flags.WebListenAddresses) == 0) { - return ErrNoListeners + err := flags.CheckFlags() + if err != nil { + return err } if flags.WebSystemdSocket != nil && *flags.WebSystemdSocket {