From aee1e4b1fd1a6bae3b4c6d0c256bd6960b66d97c Mon Sep 17 00:00:00 2001 From: Calle Pettersson Date: Sat, 12 Aug 2017 07:44:59 +0100 Subject: [PATCH 1/5] Change to kingpin for flags --- collector/logical_disk.go | 12 +++++++++--- collector/net.go | 12 +++++++++--- collector/process.go | 7 +++++-- collector/service.go | 7 +++++-- exporter.go | 41 ++++++++++++++++++++------------------- 5 files changed, 49 insertions(+), 30 deletions(-) diff --git a/collector/logical_disk.go b/collector/logical_disk.go index 1e678bcb..d3fc3a26 100644 --- a/collector/logical_disk.go +++ b/collector/logical_disk.go @@ -5,13 +5,13 @@ package collector import ( - "flag" "fmt" "log" "regexp" "github.com/StackExchange/wmi" "github.com/prometheus/client_golang/prometheus" + "gopkg.in/alecthomas/kingpin.v2" ) func init() { @@ -19,8 +19,14 @@ func init() { } var ( - volumeWhitelist = flag.String("collector.logical_disk.volume-whitelist", ".+", "Regexp of volumes to whitelist. Volume name must both match whitelist and not match blacklist to be included.") - volumeBlacklist = flag.String("collector.logical_disk.volume-blacklist", "", "Regexp of volumes to blacklist. Volume name must both match whitelist and not match blacklist to be included.") + volumeWhitelist = kingpin.Flag( + "collector.logical_disk.volume-whitelist", + "Regexp of volumes to whitelist. Volume name must both match whitelist and not match blacklist to be included.", + ).Default(".+").String() + volumeBlacklist = kingpin.Flag( + "collector.logical_disk.volume-blacklist", + "Regexp of volumes to blacklist. Volume name must both match whitelist and not match blacklist to be included.", + ).Default("").String() ) // A LogicalDiskCollector is a Prometheus collector for WMI Win32_PerfRawData_PerfDisk_LogicalDisk metrics diff --git a/collector/net.go b/collector/net.go index b44ea510..51080875 100644 --- a/collector/net.go +++ b/collector/net.go @@ -7,13 +7,13 @@ package collector import ( - "flag" "fmt" "log" "regexp" "github.com/StackExchange/wmi" "github.com/prometheus/client_golang/prometheus" + "gopkg.in/alecthomas/kingpin.v2" ) func init() { @@ -21,8 +21,14 @@ func init() { } var ( - nicWhitelist = flag.String("collector.net.nic-whitelist", ".+", "Regexp of NIC:s to whitelist. NIC name must both match whitelist and not match blacklist to be included.") - nicBlacklist = flag.String("collector.net.nic-blacklist", "", "Regexp of NIC:s to blacklist. NIC name must both match whitelist and not match blacklist to be included.") + nicWhitelist = kingpin.Flag( + "collector.net.nic-whitelist", + "Regexp of NIC:s to whitelist. NIC name must both match whitelist and not match blacklist to be included.", + ).Default(".+").String() + nicBlacklist = kingpin.Flag( + "collector.net.nic-blacklist", + "Regexp of NIC:s to blacklist. NIC name must both match whitelist and not match blacklist to be included.", + ).Default("").String() nicNameToUnderscore = regexp.MustCompile("[^a-zA-Z0-9]") ) diff --git a/collector/process.go b/collector/process.go index 773d2233..e2e363c3 100644 --- a/collector/process.go +++ b/collector/process.go @@ -4,13 +4,13 @@ package collector import ( "bytes" - "flag" "log" "strconv" "strings" "github.com/StackExchange/wmi" "github.com/prometheus/client_golang/prometheus" + "gopkg.in/alecthomas/kingpin.v2" ) func init() { @@ -18,7 +18,10 @@ func init() { } var ( - processWhereClause = flag.String("collector.process.processes-where", "", "WQL 'where' clause to use in WMI metrics query. Limits the response to the processes you specify and reduces the size of the response.") + processWhereClause = kingpin.Flag( + "collector.process.processes-where", + "WQL 'where' clause to use in WMI metrics query. Limits the response to the processes you specify and reduces the size of the response.", + ).Default("").String() ) // A ProcessCollector is a Prometheus collector for WMI Win32_PerfRawData_PerfProc_Process metrics diff --git a/collector/service.go b/collector/service.go index 64c9b91a..fbc91c89 100644 --- a/collector/service.go +++ b/collector/service.go @@ -4,12 +4,12 @@ package collector import ( "bytes" - "flag" "log" "strings" "github.com/StackExchange/wmi" "github.com/prometheus/client_golang/prometheus" + "gopkg.in/alecthomas/kingpin.v2" ) func init() { @@ -17,7 +17,10 @@ func init() { } var ( - serviceWhereClause = flag.String("collector.service.services-where", "", "WQL 'where' clause to use in WMI metrics query. Limits the response to the services you specify and reduces the size of the response.") + serviceWhereClause = kingpin.Flag( + "collector.service.services-where", + "WQL 'where' clause to use in WMI metrics query. Limits the response to the services you specify and reduces the size of the response.", + ).Default("").String() ) // A serviceCollector is a Prometheus collector for WMI Win32_Service metrics diff --git a/exporter.go b/exporter.go index ee97403f..5909c36c 100644 --- a/exporter.go +++ b/exporter.go @@ -1,11 +1,9 @@ package main import ( - "flag" "fmt" "io" "net/http" - "os" "sort" "strings" "sync" @@ -19,6 +17,7 @@ import ( "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/common/log" "github.com/prometheus/common/version" + "gopkg.in/alecthomas/kingpin.v2" ) // WmiCollector implements the prometheus.Collector interface. @@ -157,28 +156,30 @@ func initWbem() { wmi.DefaultClient.SWbemServicesClient = s } -func usage() { - fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) - flag.PrintDefaults() - fmt.Fprintf(os.Stderr, "\nNote: If executing from Powershell, the flags need to quoted. For example:\n%s\n", - "\twmi_exporter \"-collectors.enabled\" iis") -} - func main() { var ( - showVersion = flag.Bool("version", false, "Print version information.") - listenAddress = flag.String("telemetry.addr", ":9182", "host:port for WMI exporter.") - metricsPath = flag.String("telemetry.path", "/metrics", "URL path for surfacing collected metrics.") - enabledCollectors = flag.String("collectors.enabled", filterAvailableCollectors(defaultCollectors), "Comma-separated list of collectors to use. Use '[defaults]' as a placeholder for all the collectors enabled by default") - printCollectors = flag.Bool("collectors.print", false, "If true, print available collectors and exit.") + listenAddress = kingpin.Flag( + "telemetry.addr", + "host:port for WMI exporter.", + ).Default(":9182").String() + metricsPath = kingpin.Flag( + "telemetry.path", + "URL path for surfacing collected metrics.", + ).Default("/metrics").String() + enabledCollectors = kingpin.Flag( + "collectors.enabled", + "Comma-separated list of collectors to use. Use '[default]' as a placeholder for all the collectors enabled by default."). + Default(filterAvailableCollectors(defaultCollectors)).String() + printCollectors = kingpin.Flag( + "collectors.print", + "If true, print available collectors and exit.", + ).Bool() ) - flag.Usage = usage - flag.Parse() - if *showVersion { - fmt.Fprintln(os.Stdout, version.Print("wmi_exporter")) - os.Exit(0) - } + log.AddFlags(kingpin.CommandLine) + kingpin.Version(version.Print("wmi_exporter")) + kingpin.HelpFlag.Short('h') + kingpin.Parse() if *printCollectors { collectorNames := make(sort.StringSlice, 0, len(collector.Factories)) From caa46799f8fd5747afe636439d9ba4d15a15fed6 Mon Sep 17 00:00:00 2001 From: Calle Pettersson Date: Sat, 12 Aug 2017 07:49:18 +0100 Subject: [PATCH 2/5] Update installer with new flags --- installer/wmi_exporter.wxs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/installer/wmi_exporter.wxs b/installer/wmi_exporter.wxs index ca2e8307..29e1dde3 100644 --- a/installer/wmi_exporter.wxs +++ b/installer/wmi_exporter.wxs @@ -16,14 +16,14 @@ - ENABLED_COLLECTORS + ENABLED_COLLECTORS - LISTEN_ADDR OR LISTEN_PORT + LISTEN_ADDR OR LISTEN_PORT - METRICS_PATH + METRICS_PATH @@ -36,7 +36,7 @@ - + From 5ecdfe9498c767a9741b0c458aa96d49a421bfa1 Mon Sep 17 00:00:00 2001 From: Calle Pettersson Date: Sat, 12 Aug 2017 07:56:09 +0100 Subject: [PATCH 3/5] Update README with new flags --- README.md | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index e90752d0..e6aff4be 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ If the installer is run without any parameters, the exporter will run with defau Name | Description -----|------------ -`ENABLED_COLLECTORS` | As the `-collectors.enabled` flag, provide a comma-separated list of enabled collectors +`ENABLED_COLLECTORS` | As the `--collectors.enabled` flag, provide a comma-separated list of enabled collectors `LISTEN_ADDR` | The IP address to bind to. Defaults to 0.0.0.0 `LISTEN_PORT` | The port to bind to. Defaults to 9182. `METRICS_PATH` | The path at which to serve metrics. Defaults to `/metrics` @@ -64,20 +64,15 @@ The prometheus metrics will be exposed on [localhost:9182](http://localhost:9182 ## Examples -Please note: The quotes in the parameter names are required because of how Powershell parses command line arguments. - ### Enable only service collector and specify a custom query - .\wmi_exporter.exe "-collectors.enabled" "service" "-collector.service.services-where" "Name='wmi_exporter'" - - -## Examples - -Please note: The quotes in the parameter names are required because of how Powershell parses command line arguments. + .\wmi_exporter.exe --collectors.enabled "service" --collector.service.services-where "Name='wmi_exporter'" ### Enable only process collector and specify a custom query - .\wmi_exporter.exe "-collectors.enabled" "process" "-collector.process.processes-where" "Name='firefox'" + .\wmi_exporter.exe --collectors.enabled "process" --collector.process.processes-where "Name LIKE 'firefox%'" + +When there are multiple processes with the same name, WMI represents those after the first instance as `process-name#index`. So to get them all, rather than just the first one, the query needs to be a wildcard search. ## License From 33c5e99e0f9f0c356bea414ca5c96dc7f7b4e934 Mon Sep 17 00:00:00 2001 From: Calle Pettersson Date: Sat, 12 Aug 2017 08:02:04 +0100 Subject: [PATCH 4/5] Add missing vendor --- .../github.com/alecthomas/template/README.md | 25 + vendor/github.com/alecthomas/units/README.md | 11 + .../gopkg.in/alecthomas/kingpin.v2/README.md | 674 ++++++++++++++++++ .../alecthomas/kingpin.v2/values.json | 25 + 4 files changed, 735 insertions(+) create mode 100644 vendor/github.com/alecthomas/template/README.md create mode 100644 vendor/github.com/alecthomas/units/README.md create mode 100644 vendor/gopkg.in/alecthomas/kingpin.v2/README.md create mode 100644 vendor/gopkg.in/alecthomas/kingpin.v2/values.json diff --git a/vendor/github.com/alecthomas/template/README.md b/vendor/github.com/alecthomas/template/README.md new file mode 100644 index 00000000..ef6a8ee3 --- /dev/null +++ b/vendor/github.com/alecthomas/template/README.md @@ -0,0 +1,25 @@ +# Go's `text/template` package with newline elision + +This is a fork of Go 1.4's [text/template](http://golang.org/pkg/text/template/) package with one addition: a backslash immediately after a closing delimiter will delete all subsequent newlines until a non-newline. + +eg. + +``` +{{if true}}\ +hello +{{end}}\ +``` + +Will result in: + +``` +hello\n +``` + +Rather than: + +``` +\n +hello\n +\n +``` diff --git a/vendor/github.com/alecthomas/units/README.md b/vendor/github.com/alecthomas/units/README.md new file mode 100644 index 00000000..bee884e3 --- /dev/null +++ b/vendor/github.com/alecthomas/units/README.md @@ -0,0 +1,11 @@ +# Units - Helpful unit multipliers and functions for Go + +The goal of this package is to have functionality similar to the [time](http://golang.org/pkg/time/) package. + +It allows for code like this: + +```go +n, err := ParseBase2Bytes("1KB") +// n == 1024 +n = units.Mebibyte * 512 +``` diff --git a/vendor/gopkg.in/alecthomas/kingpin.v2/README.md b/vendor/gopkg.in/alecthomas/kingpin.v2/README.md new file mode 100644 index 00000000..cd4edeb6 --- /dev/null +++ b/vendor/gopkg.in/alecthomas/kingpin.v2/README.md @@ -0,0 +1,674 @@ +# Kingpin - A Go (golang) command line and flag parser +[![](https://godoc.org/github.com/alecthomas/kingpin?status.svg)](http://godoc.org/github.com/alecthomas/kingpin) [![Build Status](https://travis-ci.org/alecthomas/kingpin.svg?branch=master)](https://travis-ci.org/alecthomas/kingpin) [![Gitter chat](https://badges.gitter.im/alecthomas.png)](https://gitter.im/alecthomas/Lobby) + + + + + +- [Overview](#overview) +- [Features](#features) +- [User-visible changes between v1 and v2](#user-visible-changes-between-v1-and-v2) + - [Flags can be used at any point after their definition.](#flags-can-be-used-at-any-point-after-their-definition) + - [Short flags can be combined with their parameters](#short-flags-can-be-combined-with-their-parameters) +- [API changes between v1 and v2](#api-changes-between-v1-and-v2) +- [Versions](#versions) + - [V2 is the current stable version](#v2-is-the-current-stable-version) + - [V1 is the OLD stable version](#v1-is-the-old-stable-version) +- [Change History](#change-history) +- [Examples](#examples) + - [Simple Example](#simple-example) + - [Complex Example](#complex-example) +- [Reference Documentation](#reference-documentation) + - [Displaying errors and usage information](#displaying-errors-and-usage-information) + - [Sub-commands](#sub-commands) + - [Custom Parsers](#custom-parsers) + - [Repeatable flags](#repeatable-flags) + - [Boolean Values](#boolean-values) + - [Default Values](#default-values) + - [Place-holders in Help](#place-holders-in-help) + - [Consuming all remaining arguments](#consuming-all-remaining-arguments) + - [Bash/ZSH Shell Completion](#bashzsh-shell-completion) + - [Supporting -h for help](#supporting--h-for-help) + - [Custom help](#custom-help) + + + +## Overview + +Kingpin is a [fluent-style](http://en.wikipedia.org/wiki/Fluent_interface), +type-safe command-line parser. It supports flags, nested commands, and +positional arguments. + +Install it with: + + $ go get gopkg.in/alecthomas/kingpin.v2 + +It looks like this: + +```go +var ( + verbose = kingpin.Flag("verbose", "Verbose mode.").Short('v').Bool() + name = kingpin.Arg("name", "Name of user.").Required().String() +) + +func main() { + kingpin.Parse() + fmt.Printf("%v, %s\n", *verbose, *name) +} +``` + +More [examples](https://github.com/alecthomas/kingpin/tree/master/_examples) are available. + +Second to parsing, providing the user with useful help is probably the most +important thing a command-line parser does. Kingpin tries to provide detailed +contextual help if `--help` is encountered at any point in the command line +(excluding after `--`). + +## Features + +- Help output that isn't as ugly as sin. +- Fully [customisable help](#custom-help), via Go templates. +- Parsed, type-safe flags (`kingpin.Flag("f", "help").Int()`) +- Parsed, type-safe positional arguments (`kingpin.Arg("a", "help").Int()`). +- Parsed, type-safe, arbitrarily deep commands (`kingpin.Command("c", "help")`). +- Support for required flags and required positional arguments (`kingpin.Flag("f", "").Required().Int()`). +- Support for arbitrarily nested default commands (`command.Default()`). +- Callbacks per command, flag and argument (`kingpin.Command("c", "").Action(myAction)`). +- POSIX-style short flag combining (`-a -b` -> `-ab`). +- Short-flag+parameter combining (`-a parm` -> `-aparm`). +- Read command-line from files (`@`). +- Automatically generate man pages (`--help-man`). + +## User-visible changes between v1 and v2 + +### Flags can be used at any point after their definition. + +Flags can be specified at any point after their definition, not just +*immediately after their associated command*. From the chat example below, the +following used to be required: + +``` +$ chat --server=chat.server.com:8080 post --image=~/Downloads/owls.jpg pics +``` + +But the following will now work: + +``` +$ chat post --server=chat.server.com:8080 --image=~/Downloads/owls.jpg pics +``` + +### Short flags can be combined with their parameters + +Previously, if a short flag was used, any argument to that flag would have to +be separated by a space. That is no longer the case. + +## API changes between v1 and v2 + +- `ParseWithFileExpansion()` is gone. The new parser directly supports expanding `@`. +- Added `FatalUsage()` and `FatalUsageContext()` for displaying an error + usage and terminating. +- `Dispatch()` renamed to `Action()`. +- Added `ParseContext()` for parsing a command line into its intermediate context form without executing. +- Added `Terminate()` function to override the termination function. +- Added `UsageForContextWithTemplate()` for printing usage via a custom template. +- Added `UsageTemplate()` for overriding the default template to use. Two templates are included: + 1. `DefaultUsageTemplate` - default template. + 2. `CompactUsageTemplate` - compact command template for larger applications. + +## Versions + +Kingpin uses [gopkg.in](https://gopkg.in/alecthomas/kingpin) for versioning. + +The current stable version is [gopkg.in/alecthomas/kingpin.v2](https://gopkg.in/alecthomas/kingpin.v2). The previous version, [gopkg.in/alecthomas/kingpin.v1](https://gopkg.in/alecthomas/kingpin.v1), is deprecated and in maintenance mode. + +### [V2](https://gopkg.in/alecthomas/kingpin.v2) is the current stable version + +Installation: + +```sh +$ go get gopkg.in/alecthomas/kingpin.v2 +``` + +### [V1](https://gopkg.in/alecthomas/kingpin.v1) is the OLD stable version + +Installation: + +```sh +$ go get gopkg.in/alecthomas/kingpin.v1 +``` + +## Change History + +- *2015-09-19* -- Stable v2.1.0 release. + - Added `command.Default()` to specify a default command to use if no other + command matches. This allows for convenient user shortcuts. + - Exposed `HelpFlag` and `VersionFlag` for further customisation. + - `Action()` and `PreAction()` added and both now support an arbitrary + number of callbacks. + - `kingpin.SeparateOptionalFlagsUsageTemplate`. + - `--help-long` and `--help-man` (hidden by default) flags. + - Flags are "interspersed" by default, but can be disabled with `app.Interspersed(false)`. + - Added flags for all simple builtin types (int8, uint16, etc.) and slice variants. + - Use `app.Writer(os.Writer)` to specify the default writer for all output functions. + - Dropped `os.Writer` prefix from all printf-like functions. + +- *2015-05-22* -- Stable v2.0.0 release. + - Initial stable release of v2.0.0. + - Fully supports interspersed flags, commands and arguments. + - Flags can be present at any point after their logical definition. + - Application.Parse() terminates if commands are present and a command is not parsed. + - Dispatch() -> Action(). + - Actions are dispatched after all values are populated. + - Override termination function (defaults to os.Exit). + - Override output stream (defaults to os.Stderr). + - Templatised usage help, with default and compact templates. + - Make error/usage functions more consistent. + - Support argument expansion from files by default (with @). + - Fully public data model is available via .Model(). + - Parser has been completely refactored. + - Parsing and execution has been split into distinct stages. + - Use `go generate` to generate repeated flags. + - Support combined short-flag+argument: -fARG. + +- *2015-01-23* -- Stable v1.3.4 release. + - Support "--" for separating flags from positional arguments. + - Support loading flags from files (ParseWithFileExpansion()). Use @FILE as an argument. + - Add post-app and post-cmd validation hooks. This allows arbitrary validation to be added. + - A bunch of improvements to help usage and formatting. + - Support arbitrarily nested sub-commands. + +- *2014-07-08* -- Stable v1.2.0 release. + - Pass any value through to `Strings()` when final argument. + Allows for values that look like flags to be processed. + - Allow `--help` to be used with commands. + - Support `Hidden()` flags. + - Parser for [units.Base2Bytes](https://github.com/alecthomas/units) + type. Allows for flags like `--ram=512MB` or `--ram=1GB`. + - Add an `Enum()` value, allowing only one of a set of values + to be selected. eg. `Flag(...).Enum("debug", "info", "warning")`. + +- *2014-06-27* -- Stable v1.1.0 release. + - Bug fixes. + - Always return an error (rather than panicing) when misconfigured. + - `OpenFile(flag, perm)` value type added, for finer control over opening files. + - Significantly improved usage formatting. + +- *2014-06-19* -- Stable v1.0.0 release. + - Support [cumulative positional](#consuming-all-remaining-arguments) arguments. + - Return error rather than panic when there are fatal errors not caught by + the type system. eg. when a default value is invalid. + - Use gokpg.in. + +- *2014-06-10* -- Place-holder streamlining. + - Renamed `MetaVar` to `PlaceHolder`. + - Removed `MetaVarFromDefault`. Kingpin now uses [heuristics](#place-holders-in-help) + to determine what to display. + +## Examples + +### Simple Example + +Kingpin can be used for simple flag+arg applications like so: + +``` +$ ping --help +usage: ping [] [] + +Flags: + --debug Enable debug mode. + --help Show help. + -t, --timeout=5s Timeout waiting for ping. + +Args: + IP address to ping. + [] Number of packets to send +$ ping 1.2.3.4 5 +Would ping: 1.2.3.4 with timeout 5s and count 0 +``` + +From the following source: + +```go +package main + +import ( + "fmt" + + "gopkg.in/alecthomas/kingpin.v2" +) + +var ( + debug = kingpin.Flag("debug", "Enable debug mode.").Bool() + timeout = kingpin.Flag("timeout", "Timeout waiting for ping.").Default("5s").OverrideDefaultFromEnvar("PING_TIMEOUT").Short('t').Duration() + ip = kingpin.Arg("ip", "IP address to ping.").Required().IP() + count = kingpin.Arg("count", "Number of packets to send").Int() +) + +func main() { + kingpin.Version("0.0.1") + kingpin.Parse() + fmt.Printf("Would ping: %s with timeout %s and count %d\n", *ip, *timeout, *count) +} +``` + +### Complex Example + +Kingpin can also produce complex command-line applications with global flags, +subcommands, and per-subcommand flags, like this: + +``` +$ chat --help +usage: chat [] [] [ ...] + +A command-line chat application. + +Flags: + --help Show help. + --debug Enable debug mode. + --server=127.0.0.1 Server address. + +Commands: + help [] + Show help for a command. + + register + Register a new user. + + post [] [] + Post a message to a channel. + +$ chat help post +usage: chat [] post [] [] + +Post a message to a channel. + +Flags: + --image=IMAGE Image to post. + +Args: + Channel to post to. + [] Text to post. + +$ chat post --image=~/Downloads/owls.jpg pics +... +``` + +From this code: + +```go +package main + +import ( + "os" + "strings" + "gopkg.in/alecthomas/kingpin.v2" +) + +var ( + app = kingpin.New("chat", "A command-line chat application.") + debug = app.Flag("debug", "Enable debug mode.").Bool() + serverIP = app.Flag("server", "Server address.").Default("127.0.0.1").IP() + + register = app.Command("register", "Register a new user.") + registerNick = register.Arg("nick", "Nickname for user.").Required().String() + registerName = register.Arg("name", "Name of user.").Required().String() + + post = app.Command("post", "Post a message to a channel.") + postImage = post.Flag("image", "Image to post.").File() + postChannel = post.Arg("channel", "Channel to post to.").Required().String() + postText = post.Arg("text", "Text to post.").Strings() +) + +func main() { + switch kingpin.MustParse(app.Parse(os.Args[1:])) { + // Register user + case register.FullCommand(): + println(*registerNick) + + // Post message + case post.FullCommand(): + if *postImage != nil { + } + text := strings.Join(*postText, " ") + println("Post:", text) + } +} +``` + +## Reference Documentation + +### Displaying errors and usage information + +Kingpin exports a set of functions to provide consistent errors and usage +information to the user. + +Error messages look something like this: + + : error: + +The functions on `Application` are: + +Function | Purpose +---------|-------------- +`Errorf(format, args)` | Display a printf formatted error to the user. +`Fatalf(format, args)` | As with Errorf, but also call the termination handler. +`FatalUsage(format, args)` | As with Fatalf, but also print contextual usage information. +`FatalUsageContext(context, format, args)` | As with Fatalf, but also print contextual usage information from a `ParseContext`. +`FatalIfError(err, format, args)` | Conditionally print an error prefixed with format+args, then call the termination handler + +There are equivalent global functions in the kingpin namespace for the default +`kingpin.CommandLine` instance. + +### Sub-commands + +Kingpin supports nested sub-commands, with separate flag and positional +arguments per sub-command. Note that positional arguments may only occur after +sub-commands. + +For example: + +```go +var ( + deleteCommand = kingpin.Command("delete", "Delete an object.") + deleteUserCommand = deleteCommand.Command("user", "Delete a user.") + deleteUserUIDFlag = deleteUserCommand.Flag("uid", "Delete user by UID rather than username.") + deleteUserUsername = deleteUserCommand.Arg("username", "Username to delete.") + deletePostCommand = deleteCommand.Command("post", "Delete a post.") +) + +func main() { + switch kingpin.Parse() { + case "delete user": + case "delete post": + } +} +``` + +### Custom Parsers + +Kingpin supports both flag and positional argument parsers for converting to +Go types. For example, some included parsers are `Int()`, `Float()`, +`Duration()` and `ExistingFile()` (see [parsers.go](./parsers.go) for a complete list of included parsers). + +Parsers conform to Go's [`flag.Value`](http://godoc.org/flag#Value) +interface, so any existing implementations will work. + +For example, a parser for accumulating HTTP header values might look like this: + +```go +type HTTPHeaderValue http.Header + +func (h *HTTPHeaderValue) Set(value string) error { + parts := strings.SplitN(value, ":", 2) + if len(parts) != 2 { + return fmt.Errorf("expected HEADER:VALUE got '%s'", value) + } + (*http.Header)(h).Add(parts[0], parts[1]) + return nil +} + +func (h *HTTPHeaderValue) String() string { + return "" +} +``` + +As a convenience, I would recommend something like this: + +```go +func HTTPHeader(s Settings) (target *http.Header) { + target = &http.Header{} + s.SetValue((*HTTPHeaderValue)(target)) + return +} +``` + +You would use it like so: + +```go +headers = HTTPHeader(kingpin.Flag("header", "Add a HTTP header to the request.").Short('H')) +``` + +### Repeatable flags + +Depending on the `Value` they hold, some flags may be repeated. The +`IsCumulative() bool` function on `Value` tells if it's safe to call `Set()` +multiple times or if an error should be raised if several values are passed. + +The built-in `Value`s returning slices and maps, as well as `Counter` are +examples of `Value`s that make a flag repeatable. + +### Boolean values + +Boolean values are uniquely managed by Kingpin. Each boolean flag will have a negative complement: +`--` and `--no-`. + +### Default Values + +The default value is the zero value for a type. This can be overridden with +the `Default(value...)` function on flags and arguments. This function accepts +one or several strings, which are parsed by the value itself, so they *must* +be compliant with the format expected. + +### Place-holders in Help + +The place-holder value for a flag is the value used in the help to describe +the value of a non-boolean flag. + +The value provided to PlaceHolder() is used if provided, then the value +provided by Default() if provided, then finally the capitalised flag name is +used. + +Here are some examples of flags with various permutations: + + --name=NAME // Flag(...).String() + --name="Harry" // Flag(...).Default("Harry").String() + --name=FULL-NAME // flag(...).PlaceHolder("FULL-NAME").Default("Harry").String() + +### Consuming all remaining arguments + +A common command-line idiom is to use all remaining arguments for some +purpose. eg. The following command accepts an arbitrary number of +IP addresses as positional arguments: + + ./cmd ping 10.1.1.1 192.168.1.1 + +Such arguments are similar to [repeatable flags](#repeatable-flags), but for +arguments. Therefore they use the same `IsCumulative() bool` function on the +underlying `Value`, so the built-in `Value`s for which the `Set()` function +can be called several times will consume multiple arguments. + +To implement the above example with a custom `Value`, we might do something +like this: + +```go +type ipList []net.IP + +func (i *ipList) Set(value string) error { + if ip := net.ParseIP(value); ip == nil { + return fmt.Errorf("'%s' is not an IP address", value) + } else { + *i = append(*i, ip) + return nil + } +} + +func (i *ipList) String() string { + return "" +} + +func (i *ipList) IsCumulative() bool { + return true +} + +func IPList(s Settings) (target *[]net.IP) { + target = new([]net.IP) + s.SetValue((*ipList)(target)) + return +} +``` + +And use it like so: + +```go +ips := IPList(kingpin.Arg("ips", "IP addresses to ping.")) +``` + +### Bash/ZSH Shell Completion + +By default, all flags and commands/subcommands generate completions +internally. + +Out of the box, CLI tools using kingpin should be able to take advantage +of completion hinting for flags and commands. By specifying +`--completion-bash` as the first argument, your CLI tool will show +possible subcommands. By ending your argv with `--`, hints for flags +will be shown. + +To allow your end users to take advantage you must package a +`/etc/bash_completion.d` script with your distribution (or the equivalent +for your target platform/shell). An alternative is to instruct your end +user to source a script from their `bash_profile` (or equivalent). + +Fortunately Kingpin makes it easy to generate or source a script for use +with end users shells. `./yourtool --completion-script-bash` and +`./yourtool --completion-script-zsh` will generate these scripts for you. + +**Installation by Package** + +For the best user experience, you should bundle your pre-created +completion script with your CLI tool and install it inside +`/etc/bash_completion.d` (or equivalent). A good suggestion is to add +this as an automated step to your build pipeline, in the implementation +is improved for bug fixed. + +**Installation by `bash_profile`** + +Alternatively, instruct your users to add an additional statement to +their `bash_profile` (or equivalent): + +``` +eval "$(your-cli-tool --completion-script-bash)" +``` + +Or for ZSH + +``` +eval "$(your-cli-tool --completion-script-zsh)" +``` + +#### Additional API +To provide more flexibility, a completion option API has been +exposed for flags to allow user defined completion options, to extend +completions further than just EnumVar/Enum. + + +**Provide Static Options** + +When using an `Enum` or `EnumVar`, users are limited to only the options +given. Maybe we wish to hint possible options to the user, but also +allow them to provide their own custom option. `HintOptions` gives +this functionality to flags. + +``` +app := kingpin.New("completion", "My application with bash completion.") +app.Flag("port", "Provide a port to connect to"). + Required(). + HintOptions("80", "443", "8080"). + IntVar(&c.port) +``` + +**Provide Dynamic Options** +Consider the case that you needed to read a local database or a file to +provide suggestions. You can dynamically generate the options + +``` +func listHosts() []string { + // Provide a dynamic list of hosts from a hosts file or otherwise + // for bash completion. In this example we simply return static slice. + + // You could use this functionality to reach into a hosts file to provide + // completion for a list of known hosts. + return []string{"sshhost.example", "webhost.example", "ftphost.example"} +} + +app := kingpin.New("completion", "My application with bash completion.") +app.Flag("flag-1", "").HintAction(listHosts).String() +``` + +**EnumVar/Enum** +When using `Enum` or `EnumVar`, any provided options will be automatically +used for bash autocompletion. However, if you wish to provide a subset or +different options, you can use `HintOptions` or `HintAction` which will override +the default completion options for `Enum`/`EnumVar`. + + +**Examples** +You can see an in depth example of the completion API within +`examples/completion/main.go` + + +### Supporting -h for help + +`kingpin.CommandLine.HelpFlag.Short('h')` + +### Custom help + +Kingpin v2 supports templatised help using the text/template library (actually, [a fork](https://github.com/alecthomas/template)). + +You can specify the template to use with the [Application.UsageTemplate()](http://godoc.org/gopkg.in/alecthomas/kingpin.v2#Application.UsageTemplate) function. + +There are four included templates: `kingpin.DefaultUsageTemplate` is the default, +`kingpin.CompactUsageTemplate` provides a more compact representation for more complex command-line structures, +`kingpin.SeparateOptionalFlagsUsageTemplate` looks like the default template, but splits required +and optional command flags into separate lists, and `kingpin.ManPageTemplate` is used to generate man pages. + +See the above templates for examples of usage, and the the function [UsageForContextWithTemplate()](https://github.com/alecthomas/kingpin/blob/master/usage.go#L198) method for details on the context. + +#### Default help template + +``` +$ go run ./examples/curl/curl.go --help +usage: curl [] [ ...] + +An example implementation of curl. + +Flags: + --help Show help. + -t, --timeout=5s Set connection timeout. + -H, --headers=HEADER=VALUE + Add HTTP headers to the request. + +Commands: + help [...] + Show help. + + get url + Retrieve a URL. + + get file + Retrieve a file. + + post [] + POST a resource. +``` + +#### Compact help template + +``` +$ go run ./examples/curl/curl.go --help +usage: curl [] [ ...] + +An example implementation of curl. + +Flags: + --help Show help. + -t, --timeout=5s Set connection timeout. + -H, --headers=HEADER=VALUE + Add HTTP headers to the request. + +Commands: + help [...] + get [] + url + file + post [] +``` diff --git a/vendor/gopkg.in/alecthomas/kingpin.v2/values.json b/vendor/gopkg.in/alecthomas/kingpin.v2/values.json new file mode 100644 index 00000000..23c67448 --- /dev/null +++ b/vendor/gopkg.in/alecthomas/kingpin.v2/values.json @@ -0,0 +1,25 @@ +[ + {"type": "bool", "parser": "strconv.ParseBool(s)"}, + {"type": "string", "parser": "s, error(nil)", "format": "string(*f.v)", "plural": "Strings"}, + {"type": "uint", "parser": "strconv.ParseUint(s, 0, 64)", "plural": "Uints"}, + {"type": "uint8", "parser": "strconv.ParseUint(s, 0, 8)"}, + {"type": "uint16", "parser": "strconv.ParseUint(s, 0, 16)"}, + {"type": "uint32", "parser": "strconv.ParseUint(s, 0, 32)"}, + {"type": "uint64", "parser": "strconv.ParseUint(s, 0, 64)"}, + {"type": "int", "parser": "strconv.ParseFloat(s, 64)", "plural": "Ints"}, + {"type": "int8", "parser": "strconv.ParseInt(s, 0, 8)"}, + {"type": "int16", "parser": "strconv.ParseInt(s, 0, 16)"}, + {"type": "int32", "parser": "strconv.ParseInt(s, 0, 32)"}, + {"type": "int64", "parser": "strconv.ParseInt(s, 0, 64)"}, + {"type": "float64", "parser": "strconv.ParseFloat(s, 64)"}, + {"type": "float32", "parser": "strconv.ParseFloat(s, 32)"}, + {"name": "Duration", "type": "time.Duration", "no_value_parser": true}, + {"name": "IP", "type": "net.IP", "no_value_parser": true}, + {"name": "TCPAddr", "Type": "*net.TCPAddr", "plural": "TCPList", "no_value_parser": true}, + {"name": "ExistingFile", "Type": "string", "plural": "ExistingFiles", "no_value_parser": true}, + {"name": "ExistingDir", "Type": "string", "plural": "ExistingDirs", "no_value_parser": true}, + {"name": "ExistingFileOrDir", "Type": "string", "plural": "ExistingFilesOrDirs", "no_value_parser": true}, + {"name": "Regexp", "Type": "*regexp.Regexp", "parser": "regexp.Compile(s)"}, + {"name": "ResolvedIP", "Type": "net.IP", "parser": "resolveHost(s)", "help": "Resolve a hostname or IP to an IP."}, + {"name": "HexBytes", "Type": "[]byte", "parser": "hex.DecodeString(s)", "help": "Bytes as a hex string."} +] From 2461407277aa2d4de86852228633d3ef7ed42ca2 Mon Sep 17 00:00:00 2001 From: Calle Pettersson Date: Sun, 25 Mar 2018 09:50:37 +0200 Subject: [PATCH 5/5] Use kingpin for iis and msmq --- collector/iis.go | 10 +++++----- collector/msmq.go | 7 ++++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/collector/iis.go b/collector/iis.go index 70102e8f..136cb2f0 100644 --- a/collector/iis.go +++ b/collector/iis.go @@ -7,7 +7,6 @@ package collector import ( - "flag" "fmt" "log" "regexp" @@ -16,6 +15,7 @@ import ( "github.com/StackExchange/wmi" "github.com/prometheus/client_golang/prometheus" + "gopkg.in/alecthomas/kingpin.v2" ) func init() { @@ -56,10 +56,10 @@ func getIISVersion() simple_version { } var ( - siteWhitelist = flag.String("collector.iis.site-whitelist", ".+", "Regexp of sites to whitelist. Site name must both match whitelist and not match blacklist to be included.") - siteBlacklist = flag.String("collector.iis.site-blacklist", "", "Regexp of sites to blacklist. Site name must both match whitelist and not match blacklist to be included.") - appWhitelist = flag.String("collector.iis.app-whitelist", ".+", "Regexp of apps to whitelist. App name must both match whitelist and not match blacklist to be included.") - appBlacklist = flag.String("collector.iis.app-blacklist", "", "Regexp of apps to blacklist. App name must both match whitelist and not match blacklist to be included.") + siteWhitelist = kingpin.Flag("collector.iis.site-whitelist", "Regexp of sites to whitelist. Site name must both match whitelist and not match blacklist to be included.").Default(".+").String() + siteBlacklist = kingpin.Flag("collector.iis.site-blacklist", "Regexp of sites to blacklist. Site name must both match whitelist and not match blacklist to be included.").String() + appWhitelist = kingpin.Flag("collector.iis.app-whitelist", "Regexp of apps to whitelist. App name must both match whitelist and not match blacklist to be included.").Default(".+").String() + appBlacklist = kingpin.Flag("collector.iis.app-blacklist", "Regexp of apps to blacklist. App name must both match whitelist and not match blacklist to be included.").String() iis_version = simple_version{} ) diff --git a/collector/msmq.go b/collector/msmq.go index 31b32abb..2ad133f4 100644 --- a/collector/msmq.go +++ b/collector/msmq.go @@ -3,12 +3,13 @@ package collector import ( - "log" - "flag" "bytes" + "log" "strings" + "github.com/StackExchange/wmi" "github.com/prometheus/client_golang/prometheus" + "gopkg.in/alecthomas/kingpin.v2" ) func init() { @@ -16,7 +17,7 @@ func init() { } var ( - msmqWhereClause = flag.String("collector.msmq.msmq-where", "", "WQL 'where' clause to use in WMI metrics query. Limits the response to the msmqs you specify and reduces the size of the response.") + msmqWhereClause = kingpin.Flag("collector.msmq.msmq-where", "WQL 'where' clause to use in WMI metrics query. Limits the response to the msmqs you specify and reduces the size of the response.").String() ) // A Win32_PerfRawData_MSMQ_MSMQQueueCollector is a Prometheus collector for WMI Win32_PerfRawData_MSMQ_MSMQQueue metrics