diff --git a/README.md b/README.md index 5d3b940..33fbd9c 100644 --- a/README.md +++ b/README.md @@ -114,23 +114,29 @@ There are no configuration files. **CLI flags** ``` -usage: exporter [] +🚀 Collect prometheus metrics from a running Fail2Ban instance Flags: - -h, --help Show context-sensitive help (also try --help-long and --help-man). - -v, --version show version info and exit - --collector.f2b.socket="/var/run/fail2ban/fail2ban.sock" - path to the fail2ban server socket - --collector.textfile.directory="" - directory to read text files with metrics from - --web.listen-address=":9191" - address to use for the metrics server - --web.basic-auth.username="" - username to use to protect endpoints with basic auth - --web.basic-auth.password="" - password to use to protect endpoints with basic auth - --collector.f2b.exit-on-socket-connection-error - when set to true the exporter will immediately exit on a fail2ban socket connection error + -h, --help Show context-sensitive help. + -v, --version Show version info and exit + --web.listen-address=":9191" Address to use for the metrics server + ($F2B_WEB_LISTEN_ADDRESS) + --collector.f2b.socket="/var/run/fail2ban/fail2ban.sock" + Path to the fail2ban server socket + ($F2B_COLLECTOR_SOCKET) + --collector.f2b.exit-on-socket-connection-error + When set to true the exporter will immediately + exit on a fail2ban socket connection error + ($F2B_EXIT_ON_SOCKET_CONN_ERROR) + --collector.textfile.directory=STRING + Directory to read text files with metrics from + ($F2B_COLLECTOR_TEXT_PATH) + --web.basic-auth.username=STRING + Username to use to protect endpoints with basic auth + ($F2B_WEB_BASICAUTH_USER) + --web.basic-auth.password=STRING + Password to use to protect endpoints with basic auth + ($F2B_WEB_BASICAUTH_PASS) ``` **Environment variables** diff --git a/cfg/cfg.go b/cfg/cfg.go index f1b3b35..cee729e 100644 --- a/cfg/cfg.go +++ b/cfg/cfg.go @@ -2,108 +2,68 @@ package cfg import ( "fmt" - "github.com/alecthomas/kingpin/v2" "os" + + "github.com/alecthomas/kong" ) -const ( - socketEnvName = "F2B_COLLECTOR_SOCKET" - fileCollectorPathEnvName = "F2B_COLLECTOR_TEXT_PATH" - addressEnvName = "F2B_WEB_LISTEN_ADDRESS" - basicAuthUserEnvName = "F2B_WEB_BASICAUTH_USER" - basicAuthPassEnvName = "F2B_WEB_BASICAUTH_PASS" - exitOnSocketConnErrorEnvName = "F2B_EXIT_ON_SOCKET_CONN_ERROR" -) - -type AppSettings struct { - VersionMode bool - MetricsAddress string - Fail2BanSocketPath string - FileCollectorPath string - BasicAuthProvider *hashedBasicAuth - ExitOnSocketConnError bool -} - -func init() { - kingpin.HelpFlag.Short('h') +var cliStruct struct { + VersionMode bool `name:"version" short:"v" help:"Show version info and exit"` + ServerAddress string `name:"web.listen-address" env:"F2B_WEB_LISTEN_ADDRESS" help:"Address to use for the metrics server" default:"${default_address}"` + F2bSocketPath string `name:"collector.f2b.socket" env:"F2B_COLLECTOR_SOCKET" help:"Path to the fail2ban server socket" default:"${default_socket}"` + ExitOnSocketError bool `name:"collector.f2b.exit-on-socket-connection-error" env:"F2B_EXIT_ON_SOCKET_CONN_ERROR" help:"When set to true the exporter will immediately exit on a fail2ban socket connection error"` + TextFileExporterPath string `name:"collector.textfile.directory" env:"F2B_COLLECTOR_TEXT_PATH" help:"Directory to read text files with metrics from"` + BasicAuthUser string `name:"web.basic-auth.username" env:"F2B_WEB_BASICAUTH_USER" help:"Username to use to protect endpoints with basic auth"` + BasicAuthPass string `name:"web.basic-auth.password" env:"F2B_WEB_BASICAUTH_PASS" help:"Password to use to protect endpoints with basic auth"` } func Parse() *AppSettings { - settings := &AppSettings{} - readParamsFromCli(settings) - settings.validateFlags() + ctx := kong.Parse( + &cliStruct, + kong.Vars{ + "default_socket": "/var/run/fail2ban/fail2ban.sock", + "default_address": ":9191", + }, + kong.Name("fail2ban_exporter"), + kong.Description("🚀 Export prometheus metrics from a running Fail2Ban instance"), + kong.UsageOnError(), + ) + + validateFlags(ctx) + settings := &AppSettings{ + VersionMode: cliStruct.VersionMode, + MetricsAddress: cliStruct.ServerAddress, + Fail2BanSocketPath: cliStruct.F2bSocketPath, + FileCollectorPath: cliStruct.TextFileExporterPath, + ExitOnSocketConnError: cliStruct.ExitOnSocketError, + BasicAuthProvider: newHashedBasicAuth(cliStruct.BasicAuthUser, cliStruct.BasicAuthPass), + } return settings } -func readParamsFromCli(settings *AppSettings) { - versionMode := kingpin. - Flag("version", "show version info and exit"). - Short('v'). - Default("false"). - Bool() - socketPath := kingpin. - Flag("collector.f2b.socket", "path to the fail2ban server socket"). - Default("/var/run/fail2ban/fail2ban.sock"). - Envar(socketEnvName). - String() - fileCollectorPath := kingpin. - Flag("collector.textfile.directory", "directory to read text files with metrics from"). - Default(""). - Envar(fileCollectorPathEnvName). - String() - address := kingpin. - Flag("web.listen-address", "address to use for the metrics server"). - Default(":9191"). - Envar(addressEnvName). - String() - rawBasicAuthUsername := kingpin. - Flag("web.basic-auth.username", "username to use to protect endpoints with basic auth"). - Default(""). - Envar(basicAuthUserEnvName). - String() - rawBasicAuthPassword := kingpin. - Flag("web.basic-auth.password", "password to use to protect endpoints with basic auth"). - Default(""). - Envar(basicAuthPassEnvName). - String() - rawExitOnSocketConnError := kingpin. - Flag("collector.f2b.exit-on-socket-connection-error", "when set to true the exporter will immediately exit on a fail2ban socket connection error"). - Default("false"). - Envar(exitOnSocketConnErrorEnvName). - Bool() - - kingpin.Parse() - - settings.VersionMode = *versionMode - settings.MetricsAddress = *address - settings.Fail2BanSocketPath = *socketPath - settings.FileCollectorPath = *fileCollectorPath - settings.setBasicAuthValues(*rawBasicAuthUsername, *rawBasicAuthPassword) - settings.ExitOnSocketConnError = *rawExitOnSocketConnError -} - -func (settings *AppSettings) setBasicAuthValues(rawUsername, rawPassword string) { - settings.BasicAuthProvider = newHashedBasicAuth(rawUsername, rawPassword) -} - -func (settings *AppSettings) validateFlags() { +func validateFlags(cliCtx *kong.Context) { var flagsValid = true - if !settings.VersionMode { - if settings.Fail2BanSocketPath == "" { - fmt.Println("error: fail2ban socket path must not be blank") + var messages = []string{} + if !cliStruct.VersionMode { + if cliStruct.F2bSocketPath == "" { + messages = append(messages, "error: fail2ban socket path must not be blank") flagsValid = false } - if settings.MetricsAddress == "" { - fmt.Println("error: invalid server address, must not be blank") + if cliStruct.ServerAddress == "" { + messages = append(messages, "error: invalid server address, must not be blank") flagsValid = false } - if (len(settings.BasicAuthProvider.username) > 0) != (len(settings.BasicAuthProvider.password) > 0) { - fmt.Println("error: to enable basic auth both the username and the password must be provided") + if (len(cliStruct.BasicAuthUser) > 0) != (len(cliStruct.BasicAuthPass) > 0) { + messages = append(messages, "error: to enable basic auth both the username and the password must be provided") flagsValid = false } } if !flagsValid { - kingpin.Usage() + cliCtx.PrintUsage(false) + fmt.Println() + for i := 0; i < len(messages); i++ { + fmt.Println(messages[i]) + } os.Exit(1) } } diff --git a/cfg/settings.go b/cfg/settings.go new file mode 100644 index 0000000..160234e --- /dev/null +++ b/cfg/settings.go @@ -0,0 +1,10 @@ +package cfg + +type AppSettings struct { + VersionMode bool + MetricsAddress string + Fail2BanSocketPath string + FileCollectorPath string + BasicAuthProvider *hashedBasicAuth + ExitOnSocketConnError bool +} diff --git a/go.mod b/go.mod index 4ca102c..b3a4c92 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( ) require ( + github.com/alecthomas/kong v0.7.1 // indirect github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect diff --git a/go.sum b/go.sum index b63c166..e4d22f5 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/alecthomas/kingpin/v2 v2.3.2 h1:H0aULhgmSzN8xQ3nX1uxtdlTHYoPLu5AhHxWrKI6ocU= github.com/alecthomas/kingpin/v2 v2.3.2/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= +github.com/alecthomas/kong v0.7.1 h1:azoTh0IOfwlAX3qN9sHWTxACE2oV8Bg2gAwBsMwDQY4= +github.com/alecthomas/kong v0.7.1/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -8,16 +10,24 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/og-rek v1.2.0 h1:CTvDIin+YnetsSQAYbe+QNAxXU3B50C5hseEz8xEoJw= github.com/kisielk/og-rek v1.2.0/go.mod h1:6ihsOSzSAxR/65S3Bn9zNihoEqRquhDQZ2c6I2+MG3c= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nlpodyssey/gopickle v0.2.0 h1:4naD2DVylYJupQLbCQFdwo6yiXEmPyp+0xf5MVlrBDY= github.com/nlpodyssey/gopickle v0.2.0/go.mod h1:YIUwjJ2O7+vnBsxUN+MHAAI3N+adqEGiw+nDpwW95bY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -45,4 +55,5 @@ google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cn google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=