feat(influxd): Migrate influxd binary to cobra Command package

This commit consists of several improvements or changes:

* migrate the influxd binary to cobra.Command
* introduce a default run sub-command to start the server
* register the run sub-command flags with viper
  to maintain compatibility with the existing behavior of automatic
  binding of flags to environment variables.

Closes #12602
pull/12615/head
Stuart Carnie 2019-03-13 16:06:50 -07:00
parent f5c54a00b0
commit e8045ae187
No known key found for this signature in database
GPG Key ID: 848D9C9718D78B4F
4 changed files with 220 additions and 161 deletions

View File

@ -14,8 +14,11 @@ import (
"github.com/influxdata/flux/control"
"github.com/influxdata/flux/execute"
"github.com/influxdata/influxdb/kit/signals"
"github.com/influxdata/influxdb/telemetry"
"github.com/opentracing/opentracing-go"
"github.com/prometheus/client_golang/prometheus"
"github.com/spf13/cobra"
jaegerconfig "github.com/uber/jaeger-client-go/config"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
@ -65,6 +68,128 @@ const (
JaegerTracing = "jaeger"
)
func NewCommand() *cobra.Command {
l := NewLauncher()
cmd := &cobra.Command{
Use: "run",
Short: "Start the influxd server (default)",
Run: func(cmd *cobra.Command, args []string) {
// exit with SIGINT and SIGTERM
ctx := context.Background()
ctx = signals.WithStandardSignals(ctx)
// m.SetBuild(version, commit, date)
if err := l.run(ctx); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
} else if !l.Running() {
os.Exit(1)
}
var wg sync.WaitGroup
if !l.ReportingDisabled() {
reporter := telemetry.NewReporter(l.Registry())
reporter.Interval = 8 * time.Hour
reporter.Logger = l.Logger()
wg.Add(1)
go func() {
defer wg.Done()
reporter.Report(ctx)
}()
}
<-ctx.Done()
// Attempt clean shutdown.
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
l.Shutdown(ctx)
wg.Wait()
},
}
buildLauncherCommand(l, cmd)
return cmd
}
func buildLauncherCommand(l *Launcher, cmd *cobra.Command) {
dir, err := fs.InfluxDir()
if err != nil {
panic(fmt.Errorf("failed to determine influx directory: %v", err))
}
opts := []cli.Opt{
{
DestP: &l.logLevel,
Flag: "log-level",
Default: "info",
Desc: "supported log levels are debug, info, and error",
},
{
DestP: &l.tracingType,
Flag: "tracing-type",
Default: "",
Desc: fmt.Sprintf("supported tracing types are %s, %s", LogTracing, JaegerTracing),
},
{
DestP: &l.httpBindAddress,
Flag: "http-bind-address",
Default: ":9999",
Desc: "bind address for the REST HTTP API",
},
{
DestP: &l.boltPath,
Flag: "bolt-path",
Default: filepath.Join(dir, "influxd.bolt"),
Desc: "path to boltdb database",
},
{
DestP: &l.assetsPath,
Flag: "assets-path",
Desc: "override default assets by serving from a specific directory (developer mode)",
},
{
DestP: &l.storeType,
Flag: "store",
Default: "bolt",
Desc: "backing store for REST resources (bolt or memory)",
},
{
DestP: &l.testing,
Flag: "e2e-testing",
Default: false,
Desc: "add /debug/flush endpoint to clear stores; used for end-to-end tests",
},
{
DestP: &l.enginePath,
Flag: "engine-path",
Default: filepath.Join(dir, "engine"),
Desc: "path to persistent engine files",
},
{
DestP: &l.secretStore,
Flag: "secret-store",
Default: "bolt",
Desc: "data store for secrets (bolt or vault)",
},
{
DestP: &l.protosPath,
Flag: "protos-path",
Default: filepath.Join(dir, "protos"),
Desc: "path to protos on the filesystem",
},
{
DestP: &l.reportingDisabled,
Flag: "reporting-disabled",
Default: false,
Desc: "disable sending telemetry data to https://telemetry.influxdata.com every 8 hours",
},
}
cli.BindOptions(cmd, opts)
}
// Launcher represents the main program execution.
type Launcher struct {
wg sync.WaitGroup
@ -103,9 +228,6 @@ type Launcher struct {
logger *zap.Logger
reg *prom.Registry
// BuildInfo contains commit, version and such of influxdb.
BuildInfo platform.BuildInfo
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
@ -141,13 +263,6 @@ func (m *Launcher) Logger() *zap.Logger {
return m.logger
}
// SetBuild adds version, commit, and date to prometheus metrics.
func (m *Launcher) SetBuild(version, commit, date string) {
m.BuildInfo.Version = version
m.BuildInfo.Commit = commit
m.BuildInfo.Date = date
}
// URL returns the URL to connect to the HTTP server.
func (m *Launcher) URL() string {
return fmt.Sprintf("http://127.0.0.1:%d", m.httpPort)
@ -200,84 +315,16 @@ func (m *Launcher) Cancel() { m.cancel() }
// Run executes the program with the given CLI arguments.
func (m *Launcher) Run(ctx context.Context, args ...string) error {
dir, err := fs.InfluxDir()
if err != nil {
return fmt.Errorf("failed to determine influx directory: %v", err)
}
prog := &cli.Program{
Name: "influxd",
Run: func() error { return m.run(ctx) },
Opts: []cli.Opt{
{
DestP: &m.logLevel,
Flag: "log-level",
Default: "info",
Desc: "supported log levels are debug, info, and error",
},
{
DestP: &m.tracingType,
Flag: "tracing-type",
Default: "",
Desc: fmt.Sprintf("supported tracing types are %s, %s", LogTracing, JaegerTracing),
},
{
DestP: &m.httpBindAddress,
Flag: "http-bind-address",
Default: ":9999",
Desc: "bind address for the REST HTTP API",
},
{
DestP: &m.boltPath,
Flag: "bolt-path",
Default: filepath.Join(dir, "influxd.bolt"),
Desc: "path to boltdb database",
},
{
DestP: &m.assetsPath,
Flag: "assets-path",
Desc: "override default assets by serving from a specific directory (developer mode)",
},
{
DestP: &m.storeType,
Flag: "store",
Default: "bolt",
Desc: "backing store for REST resources (bolt or memory)",
},
{
DestP: &m.testing,
Flag: "e2e-testing",
Default: false,
Desc: "add /debug/flush endpoint to clear stores; used for end-to-end tests",
},
{
DestP: &m.enginePath,
Flag: "engine-path",
Default: filepath.Join(dir, "engine"),
Desc: "path to persistent engine files",
},
{
DestP: &m.secretStore,
Flag: "secret-store",
Default: "bolt",
Desc: "data store for secrets (bolt or vault)",
},
{
DestP: &m.protosPath,
Flag: "protos-path",
Default: filepath.Join(dir, "protos"),
Desc: "path to protos on the filesystem",
},
{
DestP: &m.reportingDisabled,
Flag: "reporting-disabled",
Default: false,
Desc: "disable sending telemetry data to https://telemetry.influxdata.com every 8 hours",
},
cmd := &cobra.Command{
Use: "run",
Short: "Start the influxd server (default)",
RunE: func(cmd *cobra.Command, args []string) error {
return m.run(ctx)
},
}
cmd := cli.NewCommand(prog)
buildLauncherCommand(m, cmd)
cmd.SetArgs(args)
return cmd.Execute()
}
@ -304,10 +351,11 @@ func (m *Launcher) run(ctx context.Context) (err error) {
return err
}
info := platform.GetBuildInfo()
m.logger.Info("Welcome to InfluxDB",
zap.String("version", m.BuildInfo.Version),
zap.String("commit", m.BuildInfo.Commit),
zap.String("build_date", m.BuildInfo.Date),
zap.String("version", info.Version),
zap.String("commit", info.Commit),
zap.String("build_date", info.Date),
)
switch m.tracingType {
@ -373,7 +421,7 @@ func (m *Launcher) run(ctx context.Context) (err error) {
m.reg = prom.NewRegistry()
m.reg.MustRegister(
prometheus.NewGoCollector(),
infprom.NewInfluxCollector(m.boltClient, m.BuildInfo),
infprom.NewInfluxCollector(m.boltClient, info),
)
m.reg.WithLogger(m.logger)
m.reg.MustRegister(m.boltClient)

View File

@ -1,19 +1,17 @@
package main
import (
"context"
"fmt"
_ "net/http/pprof"
"os"
"sync"
"time"
"strings"
"github.com/influxdata/influxdb"
"github.com/influxdata/influxdb/cmd/influxd/launcher"
"github.com/influxdata/influxdb/kit/signals"
_ "github.com/influxdata/influxdb/query/builtin"
"github.com/influxdata/influxdb/telemetry"
_ "github.com/influxdata/influxdb/tsdb/tsi1"
_ "github.com/influxdata/influxdb/tsdb/tsm1"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var (
@ -22,37 +20,36 @@ var (
date = "unknown"
)
func main() {
// exit with SIGINT and SIGTERM
ctx := context.Background()
ctx = signals.WithStandardSignals(ctx)
m := launcher.NewLauncher()
m.SetBuild(version, commit, date)
if err := m.Run(ctx, os.Args[1:]...); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
} else if !m.Running() {
os.Exit(1)
}
var wg sync.WaitGroup
if !m.ReportingDisabled() {
reporter := telemetry.NewReporter(m.Registry())
reporter.Interval = 8 * time.Hour
reporter.Logger = m.Logger()
wg.Add(1)
go func() {
defer wg.Done()
reporter.Report(ctx)
}()
}
<-ctx.Done()
// Attempt clean shutdown.
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
m.Shutdown(ctx)
wg.Wait()
var rootCmd = &cobra.Command{
Use: "influxd",
Short: "Influx Server",
}
func init() {
influxdb.SetBuildInfo(version, commit, date)
viper.SetEnvPrefix("INFLUXD")
viper.AutomaticEnv()
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
rootCmd.InitDefaultHelpCmd()
rootCmd.AddCommand(launcher.NewCommand())
}
// find determines the default behavior when running influxd.
// Specifically, find will return the influxd run command if no sub-command
// was specified.
func find(args []string) *cobra.Command {
cmd, _, err := rootCmd.Find(args)
if err == nil && cmd == rootCmd {
// Execute the run command if no sub-command is specified
return launcher.NewCommand()
}
return rootCmd
}
func main() {
cmd := find(os.Args[1:])
if err := cmd.Execute(); err != nil {
os.Exit(1)
}
}

3
go.sum
View File

@ -157,9 +157,6 @@ github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/goreleaser/goreleaser v0.94.0/go.mod h1:OjbYR2NhOI6AEUWCowMSBzo9nP1aRif3sYtx+rhp+Zo=
github.com/goreleaser/goreleaser v0.97.0 h1:2/GZKg0cLk5HgIiiSaW/lbDfj0T/GMgF6qEFexy0Vfs=
github.com/goreleaser/goreleaser v0.97.0/go.mod h1:MnjA0e0Uq6ISqjG1WxxMAl+3VS1QYjILSWVnMYDxasE=
github.com/goreleaser/nfpm v0.9.7 h1:h8RQMDztu6cW7b0/s4PGbdeMYykAbJG0UMXaWG5uBMI=
github.com/goreleaser/nfpm v0.9.7/go.mod h1:F2yzin6cBAL9gb+mSiReuXdsfTrOQwDMsuSpULof+y4=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=

View File

@ -3,10 +3,10 @@ package cli
import (
"fmt"
"strings"
"time"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"time"
)
// Opt is a single command-line option
@ -57,49 +57,66 @@ func NewCommand(p *Program) *cobra.Command {
// This normalizes "-" to an underscore in env names.
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
for _, o := range p.Opts {
switch o.DestP.(type) {
BindOptions(cmd, p.Opts)
return cmd
}
// BindOptions adds opts to the specified command and automatically
// registers those options with viper.
func BindOptions(cmd *cobra.Command, opts []Opt) {
for _, o := range opts {
switch destP := o.DestP.(type) {
case *string:
if o.Default == nil {
o.Default = ""
var d string
if o.Default != nil {
d = o.Default.(string)
}
cmd.Flags().StringVar(o.DestP.(*string), o.Flag, o.Default.(string), o.Desc)
viper.BindPFlag(o.Flag, cmd.Flags().Lookup(o.Flag))
*o.DestP.(*string) = viper.GetString(o.Flag)
cmd.Flags().StringVar(destP, o.Flag, d, o.Desc)
mustBindPFlag(o.Flag, cmd)
*destP = viper.GetString(o.Flag)
case *int:
if o.Default == nil {
o.Default = 0
var d int
if o.Default != nil {
d = o.Default.(int)
}
cmd.Flags().IntVar(o.DestP.(*int), o.Flag, o.Default.(int), o.Desc)
viper.BindPFlag(o.Flag, cmd.Flags().Lookup(o.Flag))
*o.DestP.(*int) = viper.GetInt(o.Flag)
cmd.Flags().IntVar(destP, o.Flag, d, o.Desc)
mustBindPFlag(o.Flag, cmd)
*destP = viper.GetInt(o.Flag)
case *bool:
if o.Default == nil {
o.Default = false
var d bool
if o.Default != nil {
d = o.Default.(bool)
}
cmd.Flags().BoolVar(o.DestP.(*bool), o.Flag, o.Default.(bool), o.Desc)
viper.BindPFlag(o.Flag, cmd.Flags().Lookup(o.Flag))
*o.DestP.(*bool) = viper.GetBool(o.Flag)
cmd.Flags().BoolVar(destP, o.Flag, d, o.Desc)
mustBindPFlag(o.Flag, cmd)
*destP = viper.GetBool(o.Flag)
case *time.Duration:
if o.Default == nil {
o.Default = time.Duration(0)
var d time.Duration
if o.Default != nil {
d = o.Default.(time.Duration)
}
cmd.Flags().DurationVar(o.DestP.(*time.Duration), o.Flag, o.Default.(time.Duration), o.Desc)
viper.BindPFlag(o.Flag, cmd.Flags().Lookup(o.Flag))
*o.DestP.(*time.Duration) = viper.GetDuration(o.Flag)
cmd.Flags().DurationVar(destP, o.Flag, d, o.Desc)
mustBindPFlag(o.Flag, cmd)
*destP = viper.GetDuration(o.Flag)
case *[]string:
if o.Default == nil {
o.Default = []string{}
var d []string
if o.Default != nil {
d = o.Default.([]string)
}
cmd.Flags().StringSliceVar(o.DestP.(*[]string), o.Flag, o.Default.([]string), o.Desc)
viper.BindPFlag(o.Flag, cmd.Flags().Lookup(o.Flag))
*o.DestP.(*[]string) = viper.GetStringSlice(o.Flag)
cmd.Flags().StringSliceVar(destP, o.Flag, d, o.Desc)
mustBindPFlag(o.Flag, cmd)
*destP = viper.GetStringSlice(o.Flag)
default:
// if you get a panic here, sorry about that!
// anyway, go ahead and make a PR and add another type.
panic(fmt.Errorf("unknown destination type %t", o.DestP))
}
}
return cmd
}
func mustBindPFlag(key string, cmd *cobra.Command) {
if err := viper.BindPFlag(key, cmd.Flags().Lookup(key)); err != nil {
panic(err)
}
}