Switch grafana server command to use urfave/cli/v2 (#60684)

* switch grafana server to use urfave/cli/v2

* autocomplete support

* lint fix
This commit is contained in:
Dan Cech 2022-12-24 22:33:18 -05:00 committed by GitHub
parent 9bd6e471e4
commit 9c4051bfa1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 168 additions and 118 deletions

View File

@ -0,0 +1,18 @@
#! /bin/bash
_cli_bash_autocomplete() {
if [[ "${COMP_WORDS[0]}" != "source" ]]; then
local cur opts base
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
if [[ "$cur" == "-"* ]]; then
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion )
else
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
fi
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
}
complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete grafana

View File

@ -0,0 +1,8 @@
$name = "grafana"
Register-ArgumentCompleter -Native -CommandName $name -ScriptBlock {
param($commandName, $wordToComplete, $cursorPosition)
$other = "$wordToComplete --generate-bash-completion"
Invoke-Expression $other | ForEach-Object {
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
}
}

View File

@ -0,0 +1,20 @@
#compdef grafana
_cli_zsh_autocomplete() {
local -a opts
local cur
cur=${words[-1]}
if [[ "$cur" == "-"* ]]; then
opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}")
else
opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-bash-completion)}")
fi
if [[ "${opts[1]}" != "" ]]; then
_describe 'values' opts
else
_files
fi
}
compdef _cli_zsh_autocomplete grafana

View File

@ -16,6 +16,23 @@ func CLICommand(version string) *cli.Command {
Name: "cli", Name: "cli",
Usage: "run the grafana cli", Usage: "run the grafana cli",
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{
Name: "config",
Usage: "Path to config file",
},
&cli.StringFlag{
Name: "homepath",
Usage: "Path to Grafana install/home path, defaults to working directory",
},
&cli.StringFlag{
Name: "configOverrides",
Usage: "Configuration options to override defaults as a string. e.g. cfg:default.paths.log=/dev/null",
},
cli.VersionFlag,
&cli.BoolFlag{
Name: "debug, d",
Usage: "Enable debug logging",
},
&cli.StringFlag{ &cli.StringFlag{
Name: "pluginsDir", Name: "pluginsDir",
Usage: "Path to the Grafana plugin directory", Usage: "Path to the Grafana plugin directory",
@ -38,23 +55,6 @@ func CLICommand(version string) *cli.Command {
Name: "insecure", Name: "insecure",
Usage: "Skip TLS verification (insecure)", Usage: "Skip TLS verification (insecure)",
}, },
&cli.BoolFlag{
Name: "debug, d",
Usage: "Enable debug logging",
},
&cli.StringFlag{
Name: "configOverrides",
Usage: "Configuration options to override defaults as a string. e.g. cfg:default.paths.log=/dev/null",
},
&cli.StringFlag{
Name: "homepath",
Usage: "Path to Grafana install/home path, defaults to working directory",
},
&cli.StringFlag{
Name: "config",
Usage: "Path to config file",
},
cli.VersionFlag,
}, },
Subcommands: Commands, Subcommands: Commands,
Before: func(c *cli.Context) error { Before: func(c *cli.Context) error {

View File

@ -2,8 +2,6 @@ package commands
import ( import (
"context" "context"
"errors"
"flag"
"fmt" "fmt"
"net/http" "net/http"
_ "net/http/pprof" _ "net/http/pprof"
@ -26,6 +24,7 @@ import (
_ "github.com/grafana/grafana/pkg/services/alerting/conditions" _ "github.com/grafana/grafana/pkg/services/alerting/conditions"
_ "github.com/grafana/grafana/pkg/services/alerting/notifiers" _ "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/urfave/cli/v2"
) )
type ServerOptions struct { type ServerOptions struct {
@ -33,48 +32,99 @@ type ServerOptions struct {
Commit string Commit string
BuildBranch string BuildBranch string
BuildStamp string BuildStamp string
Args []string Context *cli.Context
} }
type exitWithCode struct { func ServerCommand(version, commit, buildBranch, buildstamp string) *cli.Command {
reason string return &cli.Command{
code int Name: "server",
Usage: "run the grafana server",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "config",
Usage: "Path to config file",
},
&cli.StringFlag{
Name: "homepath",
Usage: "Path to Grafana install/home path, defaults to working directory",
},
&cli.StringFlag{
Name: "pidfile",
Usage: "Path to Grafana pid file",
},
&cli.StringFlag{
Name: "packaging",
Value: "unknown",
Usage: "describes the way Grafana was installed",
},
&cli.StringFlag{
Name: "configOverrides",
Usage: "Configuration options to override defaults as a string. e.g. cfg:default.paths.log=/dev/null",
},
cli.VersionFlag,
&cli.BoolFlag{
Name: "vv",
Usage: "prints current version, all dependencies and exits",
},
&cli.BoolFlag{
Name: "profile",
Value: false,
Usage: "Turn on pprof profiling",
},
&cli.StringFlag{
Name: "profile-addr",
Value: "localhost",
Usage: "Define custom address for profiling",
},
&cli.Uint64Flag{
Name: "profile-port",
Value: 6060,
Usage: "Define custom port for profiling",
},
&cli.BoolFlag{
Name: "tracing",
Value: false,
Usage: "Turn on tracing",
},
&cli.StringFlag{
Name: "tracing-file",
Value: "trace.out",
Usage: "Define tracing output file",
},
},
Action: func(context *cli.Context) error {
return RunServer(ServerOptions{
Version: version,
Commit: commit,
BuildBranch: buildBranch,
BuildStamp: buildstamp,
Context: context,
})
},
}
} }
var serverFs = flag.NewFlagSet("server", flag.ContinueOnError) func RunServer(opt ServerOptions) error {
var clilog = log.New("cli")
func (e exitWithCode) Error() string {
return e.reason
}
func RunServer(opt ServerOptions) int {
var ( var (
configFile = serverFs.String("config", "", "path to config file") configFile = opt.Context.String("config")
homePath = serverFs.String("homepath", "", "path to grafana install/home path, defaults to working directory") homePath = opt.Context.String("homepath")
pidFile = serverFs.String("pidfile", "", "path to pid file") pidFile = opt.Context.String("pidfile")
packaging = serverFs.String("packaging", "unknown", "describes the way Grafana was installed") packaging = opt.Context.String("packaging")
configOverrides = serverFs.String("configOverrides", "", "Configuration options to override defaults as a string. e.g. cfg:default.paths.log=/dev/null") configOverrides = opt.Context.String("configOverrides")
v = serverFs.Bool("v", false, "prints current version and exits") v = opt.Context.Bool("version")
vv = serverFs.Bool("vv", false, "prints current version, all dependencies and exits") vv = opt.Context.Bool("vv")
profile = serverFs.Bool("profile", false, "Turn on pprof profiling") profile = opt.Context.Bool("profile")
profileAddr = serverFs.String("profile-addr", "localhost", "Define custom address for profiling") profileAddr = opt.Context.String("profile-addr")
profilePort = serverFs.Uint64("profile-port", 6060, "Define custom port for profiling") profilePort = opt.Context.Uint64("profile-port")
tracing = serverFs.Bool("tracing", false, "Turn on tracing") tracing = opt.Context.Bool("tracing")
tracingFile = serverFs.String("tracing-file", "trace.out", "Define tracing output file") tracingFile = opt.Context.String("tracing-file")
) )
if err := serverFs.Parse(opt.Args); err != nil { if v || vv {
fmt.Fprintln(os.Stderr, err.Error())
return 1
}
if *v || *vv {
fmt.Printf("Version %s (commit: %s, branch: %s)\n", opt.Version, opt.Commit, opt.BuildBranch) fmt.Printf("Version %s (commit: %s, branch: %s)\n", opt.Version, opt.Commit, opt.BuildBranch)
if *vv { if vv {
fmt.Println("Dependencies:") fmt.Println("Dependencies:")
if info, ok := debug.ReadBuildInfo(); ok { if info, ok := debug.ReadBuildInfo(); ok {
for _, dep := range info.Deps { for _, dep := range info.Deps {
@ -82,19 +132,17 @@ func RunServer(opt ServerOptions) int {
} }
} }
} }
return 0 return nil
} }
profileDiagnostics := newProfilingDiagnostics(*profile, *profileAddr, *profilePort) profileDiagnostics := newProfilingDiagnostics(profile, profileAddr, profilePort)
if err := profileDiagnostics.overrideWithEnv(); err != nil { if err := profileDiagnostics.overrideWithEnv(); err != nil {
fmt.Fprintln(os.Stderr, err.Error()) return err
return 1
} }
traceDiagnostics := newTracingDiagnostics(*tracing, *tracingFile) traceDiagnostics := newTracingDiagnostics(tracing, tracingFile)
if err := traceDiagnostics.overrideWithEnv(); err != nil { if err := traceDiagnostics.overrideWithEnv(); err != nil {
fmt.Fprintln(os.Stderr, err.Error()) return err
return 1
} }
if profileDiagnostics.enabled { if profileDiagnostics.enabled {
@ -112,29 +160,14 @@ func RunServer(opt ServerOptions) int {
}() }()
} }
if err := executeServer(*configFile, *homePath, *pidFile, *packaging, *configOverrides, traceDiagnostics, opt); err != nil {
code := 1
var ewc exitWithCode
if errors.As(err, &ewc) {
code = ewc.code
}
if code != 0 {
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
}
return code
}
return 0
}
func executeServer(configFile, homePath, pidFile, packaging, configOverrides string, traceDiagnostics *tracingDiagnostics, opt ServerOptions) error {
defer func() { defer func() {
if err := log.Close(); err != nil { if err := log.Close(); err != nil {
fmt.Fprintf(os.Stderr, "Failed to close log: %s\n", err) fmt.Fprintf(os.Stderr, "Failed to close log: %s\n", err)
} }
}() }()
clilog := log.New("cli")
defer func() { defer func() {
// If we've managed to initialize them, this is the last place // If we've managed to initialize them, this is the last place
// where we're able to log anything that'll end up in Grafana's // where we're able to log anything that'll end up in Grafana's
@ -196,7 +229,7 @@ func executeServer(configFile, homePath, pidFile, packaging, configOverrides str
Config: configFile, Config: configFile,
HomePath: homePath, HomePath: homePath,
// tailing arguments have precedence over the options string // tailing arguments have precedence over the options string
Args: append(configOptions, serverFs.Args()...), Args: append(configOptions, opt.Context.Args().Slice()...),
}, },
server.Options{ server.Options{
PidFile: pidFile, PidFile: pidFile,
@ -207,7 +240,6 @@ func executeServer(configFile, homePath, pidFile, packaging, configOverrides str
api.ServerOptions{}, api.ServerOptions{},
) )
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Failed to start grafana. error: %s\n", err.Error())
return err return err
} }
@ -215,15 +247,7 @@ func executeServer(configFile, homePath, pidFile, packaging, configOverrides str
go listenToSystemSignals(ctx, s) go listenToSystemSignals(ctx, s)
if err := s.Run(); err != nil { return s.Run()
code := s.ExitCode(err)
return exitWithCode{
reason: err.Error(),
code: code,
}
}
return nil
} }
func validPackaging(packaging string) string { func validPackaging(packaging string) string {

View File

@ -29,23 +29,10 @@ func main() {
Version: version, Version: version,
Commands: []*cli.Command{ Commands: []*cli.Command{
gcli.CLICommand(version), gcli.CLICommand(version),
{ gsrv.ServerCommand(version, commit, buildBranch, buildstamp),
Name: "server",
Usage: "server <server options>",
Action: func(context *cli.Context) error {
os.Exit(gsrv.RunServer(gsrv.ServerOptions{
Version: version,
Commit: commit,
BuildBranch: buildBranch,
BuildStamp: buildstamp,
Args: context.Args().Slice(),
}))
return nil
},
SkipFlagParsing: true,
},
}, },
CommandNotFound: cmdNotFound, CommandNotFound: cmdNotFound,
EnableBashCompletion: true,
} }
if err := app.Run(os.Args); err != nil { if err := app.Run(os.Args); err != nil {

View File

@ -121,7 +121,10 @@ func (s *Server) init() error {
} }
s.isInitialized = true s.isInitialized = true
s.writePIDFile() if err := s.writePIDFile(); err != nil {
return err
}
if err := metrics.SetEnvironmentInformation(s.cfg.MetricsGrafanaEnvironmentInfo); err != nil { if err := metrics.SetEnvironmentInformation(s.cfg.MetricsGrafanaEnvironmentInfo); err != nil {
return err return err
} }
@ -203,36 +206,28 @@ func (s *Server) Shutdown(ctx context.Context, reason string) error {
return err return err
} }
// ExitCode returns an exit code for a given error.
func (s *Server) ExitCode(runError error) int {
if runError != nil {
s.log.Error("Server shutdown", "error", runError)
return 1
}
return 0
}
// writePIDFile retrieves the current process ID and writes it to file. // writePIDFile retrieves the current process ID and writes it to file.
func (s *Server) writePIDFile() { func (s *Server) writePIDFile() error {
if s.pidFile == "" { if s.pidFile == "" {
return return nil
} }
// Ensure the required directory structure exists. // Ensure the required directory structure exists.
err := os.MkdirAll(filepath.Dir(s.pidFile), 0700) err := os.MkdirAll(filepath.Dir(s.pidFile), 0700)
if err != nil { if err != nil {
s.log.Error("Failed to verify pid directory", "error", err) s.log.Error("Failed to verify pid directory", "error", err)
os.Exit(1) return fmt.Errorf("failed to verify pid directory: %s", err)
} }
// Retrieve the PID and write it to file. // Retrieve the PID and write it to file.
pid := strconv.Itoa(os.Getpid()) pid := strconv.Itoa(os.Getpid())
if err := os.WriteFile(s.pidFile, []byte(pid), 0644); err != nil { if err := os.WriteFile(s.pidFile, []byte(pid), 0644); err != nil {
s.log.Error("Failed to write pidfile", "error", err) s.log.Error("Failed to write pidfile", "error", err)
os.Exit(1) return fmt.Errorf("failed to write pidfile: %s", err)
} }
s.log.Info("Writing PID file", "path", s.pidFile, "pid", pid) s.log.Info("Writing PID file", "path", s.pidFile, "pid", pid)
return nil
} }
// notifySystemd sends state notifications to systemd. // notifySystemd sends state notifications to systemd.

View File

@ -61,7 +61,6 @@ func TestServer_Run_Error(t *testing.T) {
s := testServer(t, newTestService(nil, false), newTestService(testErr, false)) s := testServer(t, newTestService(nil, false), newTestService(testErr, false))
err := s.Run() err := s.Run()
require.ErrorIs(t, err, testErr) require.ErrorIs(t, err, testErr)
require.NotZero(t, s.ExitCode(err))
} }
func TestServer_Shutdown(t *testing.T) { func TestServer_Shutdown(t *testing.T) {
@ -87,7 +86,6 @@ func TestServer_Shutdown(t *testing.T) {
}() }()
err := s.Run() err := s.Run()
require.NoError(t, err) require.NoError(t, err)
require.Zero(t, s.ExitCode(err))
err = <-ch err = <-ch
require.NoError(t, err) require.NoError(t, err)