Flagarize
Flagarize your Go struct to initialize your even complex struct from flags! 🚀
Install / Use
/learn @bwplotka/FlagarizeREADME
flagarize
Flagarize your Go struct to initialize your even complex CLI config struct from flags!
NOTE: This project is in maintainance mode. This means that until new maintainer will be willing to support it, no additional features will be added. The project was fun to create and is fully functional, but benefits are not strong enough to justify lack of type safety her (thus lack of autocomplection).
Goals
- Allow flag parsing for any struct field using Go struct tags.
- Minimal dependencies: Only
"gopkg.in/alecthomas/kingpin.v2". - Extensible with custom types and custom flagarizing.
- Native supports for all kingpin flag types and more like
regexp,pathorcontent,timeorduration.
Requirements:
- Go 1.3+
gopkg.in/alecthomas/kingpin.v2
Usage
For each field of the configuration struct that you want to register as a flag, add flagarize:"<key=value,>" struct tag.
Flagarize tags
Flagarize struct tag expected value to be map where key=values are separated by | (can be configured via WithElemSep function.)
Available keys:
name: Name of the flag. If empty field name will be used and parsed to different case (e.gFooBarfield will befoo-bar)help: Usage description for the flag. If empty, value from string<FieldName>FlagarizeHelpfield in the same struct will be used.hidden: Optional. iftrueflag will be hidden.required: Optional. iftrueflag will be required.default: Optional. Value will be used as a value if the flag is not specified. Otherwise default value for type will be used.envvar: Optional. Name of environment variable if needed next to the flag.short: Optional. Short single character for a flag name alternative.placeholderOptional. Flag placeholder for expected type.
Short tag example:
type config struct {
Field1 string `flagarize:"name=case9|help=help|hidden=true|required=true|default=some|envvar=LOL|short=z|placeholder=<something2>"`
}
Supported types
Without extensions flagarize supports all kingpin supported types plus few more. For current supported types it's best to
see TestFlagarize_OK unit test here.
Example
See below example for usage:
func main() {
// Create new kingpin app as usual.
a := kingpin.New(filepath.Base(os.Args[0]), "<Your CLI description>")
// Define you own config.
type ComponentAOptions struct {
Field1 []string `flagarize:"name=a.flag1|help=Help for field 1 in nested struct for component A."`
}
type ConfigForCLI struct {
Field1 string `flagarize:"name=flag1|help=Help for field 1.|default=something"`
Field2 *url.URL `flagarize:"name=flag2|help=Help for field 2.|placeholder=<URL>"`
Field3 int `flagarize:"name=flag3|help=Help for field 3.|default=2144"`
Field4 flagarize.TimeOrDuration `flagarize:"name=flag4|help=Help for field 4.|default=1m|placeholder=<time or duration>"`
ComponentA ComponentAOptions
NotFromFlags int
}
// You can define some fields as usual as well.
var notInConfigField time.Duration
a.Flag("some-field10", "Help for some help which is defined outside of ConfigForCLI struct.").
DurationVar(¬InConfigField)
// Create new config.
cfg := &ConfigForCLI{}
// Flagarize your config! (Register flags from config). Parse the flags afterwards.
if err := flagarize.Flagarize(a, cfg); err != nil {
log.Fatal(err)
}
if _, err := a.Parse(os.Args[1:]); err != nil {
log.Fatal(err)
}
// Config is filled with values from flags!
_ = cfg.Field1
// Run your command...
}
But Bartek, such Go projects already exist!
Yes, but not as simple, not focused on kingpin, and they does not allow custom flagarazing! 🤗
But Bartek, normal flag registration is enough, don't overengineer!
Well, depends. It might get quite weird. Here is how it could look with and without flagarize:
Without
cfg := struct {
configFile string
localStoragePath string
notifier notifier.Options
notifierTimeout model.Duration
forGracePeriod model.Duration
outageTolerance model.Duration
resendDelay model.Duration
web web.Options
tsdb tsdbOptions
lookbackDelta model.Duration
webTimeout model.Duration
queryTimeout model.Duration
queryConcurrency int
queryMaxSamples int
RemoteFlushDeadline model.Duration
prometheusURL string
corsRegexString string
promlogConfig promlog.Config
}{}
a := kingpin.New(filepath.Base(os.Args[0]), "The Prometheus monitoring server")
a.Version(version.Print("prometheus"))
a.HelpFlag.Short('h')
a.Flag("config.file", "Prometheus configuration file path.").
Default("prometheus.yml").StringVar(&cfg.configFile)
a.Flag("web.listen-address", "Address to listen on for UI, API, and telemetry.").
Default("0.0.0.0:9090").StringVar(&cfg.web.ListenAddress)
a.Flag("web.read-timeout",
"Maximum duration before timing out read of the request, and closing idle connections.").
Default("5m").SetValue(&cfg.webTimeout)
a.Flag("web.max-connections", "Maximum number of simultaneous connections.").
Default("512").IntVar(&cfg.web.MaxConnections)
a.Flag("web.external-url",
"The URL under which Prometheus is externally reachable (for example, if Prometheus is served via a reverse proxy). Used for generating relative and absolute links back to Prometheus itself. If the URL has a path portion, it will be used to prefix all HTTP endpoints served by Prometheus. If omitted, relevant URL components will be derived automatically.").
PlaceHolder("<URL>").StringVar(&cfg.prometheusURL)
a.Flag("web.route-prefix",
"Prefix for the internal routes of web endpoints. Defaults to path of --web.external-url.").
PlaceHolder("<path>").StringVar(&cfg.web.RoutePrefix)
a.Flag("web.user-assets", "Path to static asset directory, available at /user.").
PlaceHolder("<path>").StringVar(&cfg.web.UserAssetsPath)
a.Flag("web.enable-lifecycle", "Enable shutdown and reload via HTTP request.").
Default("false").BoolVar(&cfg.web.EnableLifecycle)
a.Flag("web.enable-admin-api", "Enable API endpoints for admin control actions.").
Default("false").BoolVar(&cfg.web.EnableAdminAPI)
a.Flag("web.console.templates", "Path to the console template directory, available at /consoles.").
Default("consoles").StringVar(&cfg.web.ConsoleTemplatesPath)
//... Thousands more of that.
if _, err := a.Parse(os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, errors.Wrapf(err, "Error parsing commandline arguments"))
a.Usage(os.Args[1:])
os.Exit(2)
}
With flagarize
cfg := struct {
ConfigFile string `flagarize:"name=config.file|help=Prometheus configuration file path.|default=prometheus.yml"`
ExternalURL string `flagarize:"name=web.external-url|help=The URL under which Prometheus is externally reachable (for example, if Prometheus is served via a reverse proxy). Used for generating relative and absolute links back to Prometheus itself. If the URL has a path portion, it will be used to prefix all HTTP endpoints served by Prometheus. If omitted, relevant URL components will be derived automatically.|placeholder=<URL>"`
StoragePath string `flagarize:"name=storage.tsdb.path|help=Base path for metrics storage.|default=data/"`
RemoteFlushDeadline model.Duration `flagarize:"name=storage.remote.flush-deadline|help=How long to wait flushing sample on shutdown or config reload.|default=1m|placeholder=<duration>"`
RulesOutageTolerance model.Duration `flagarize:"name=rules.alert.for-outage-tolerance|help=Max time to tolerate prometheus outage for restoring \"for\" state of alert.|default=1h"`
RulesForGracePeriod model.Duration `flagarize:"name=rules.alert.for-grace-period|help=Minimum duration between alert and restored \"for\" state. This is maintained only for alerts with configured \"for\" time greater than grace period.|default=10m"`
RulesResendDelay model.Duration `flagarize:"name=rules.alert.resend-delay|help=Minimum amount of time to wait before resending an alert to Alertmanager.|default=1m"`
LookbackDelta model.Duration `flagarize:"name=query.lookback-delta|help=The maximum lookback duration for retrieving metrics during expression evaluations and federation.|default=5m"`
QueryTimeout model.Duration `flagarize:"name=query.timeout|help=Maximum time a query may take before being aborted.|default=2m"`
QueryConcurrency int `flagarize:"name=query.max-concurrency|help=Maximum number of queries executed concurrently.|default=20"`
QueryMaxSamples int `flagarize:"name=query.max-samples|help=Maximum number of samples a single query can load into memory. Note that queries will fail if they try to load more samples than this into memory, so this also limits the number of samples a query can return.|default=50000000"`
Web web.Options
Notifier notifier.Options
TSDB tsdbOptions
PromLog promlog.Config
}{}
a := kingpin.New(filep
