Start implementing sg doctor command (#20796)

This adds a `sg doctor` command that executes the checks defined in `sg.config.yaml`.
This commit is contained in:
Thorsten Ball 2021-05-14 17:39:48 +02:00 committed by GitHub
parent 66433efe17
commit 61a832f77e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 98 additions and 12 deletions

View File

@ -35,6 +35,11 @@ func ParseConfigFile(name string) (*Config, error) {
conf.Tests[name] = cmd
}
for name, check := range conf.Checks {
check.Name = name
conf.Checks[name] = check
}
return &conf, nil
}
@ -107,11 +112,18 @@ func equal(a, b []string) bool {
return true
}
type Check struct {
Name string `yaml:"-"`
Cmd string `yaml:"cmd"`
FailMessage string `yaml:"failMessage"`
}
type Config struct {
Env map[string]string `yaml:"env"`
Commands map[string]Command `yaml:"commands"`
Commandsets map[string][]string `yaml:"commandsets"`
Tests map[string]Command `yaml:"tests"`
Checks map[string]Check `yaml:"checks"`
}
// Merges merges the top-level entries of two Config objects, with the receiver

View File

@ -22,7 +22,7 @@ var (
ShortHelp: "Run the given command.",
FlagSet: runFlagSet,
Exec: runExec,
UsageFunc: runUsage,
UsageFunc: printRunUsage,
}
runSetFlagSet = flag.NewFlagSet("sg run-set", flag.ExitOnError)
@ -32,7 +32,7 @@ var (
ShortHelp: "Run the given command set.",
FlagSet: runSetFlagSet,
Exec: runSetExec,
UsageFunc: runSetUsage,
UsageFunc: printRunSetUsage,
}
startFlagSet = flag.NewFlagSet("sg start", flag.ExitOnError)
@ -42,7 +42,7 @@ var (
ShortHelp: "Runs the commandset with the name 'start'.",
FlagSet: startFlagSet,
Exec: startExec,
UsageFunc: startUsage,
UsageFunc: printStartUsage,
}
testFlagSet = flag.NewFlagSet("sg test", flag.ExitOnError)
@ -52,7 +52,17 @@ var (
ShortHelp: "Run the given test suite.",
FlagSet: testFlagSet,
Exec: testExec,
UsageFunc: testUsage,
UsageFunc: printTestUsage,
}
doctorFlagSet = flag.NewFlagSet("sg doctor", flag.ExitOnError)
doctorCommand = &ffcli.Command{
Name: "doctor",
ShortUsage: "sg doctor",
ShortHelp: "Run the checks defined in the config file to make sure your system is healthy.",
FlagSet: doctorFlagSet,
Exec: doctorExec,
UsageFunc: printDoctorUsage,
}
)
@ -67,9 +77,8 @@ var (
overwriteConfigFlag = rootFlagSet.String("overwrite", defaultConfigOverwriteFile, "configuration overwrites file that is gitignored and can be used to, for example, add credentials")
rootCommand = &ffcli.Command{
ShortUsage: "sg [flags] <subcommand>",
FlagSet: rootFlagSet,
Subcommands: []*ffcli.Command{runCommand, runSetCommand, startCommand, testCommand},
ShortUsage: "sg [flags] <subcommand>",
FlagSet: rootFlagSet,
Exec: func(ctx context.Context, args []string) error {
return flag.ErrHelp
},
@ -91,6 +100,7 @@ var (
return out.String()
},
Subcommands: []*ffcli.Command{runCommand, runSetCommand, startCommand, testCommand, doctorCommand},
}
)
@ -227,7 +237,11 @@ func runExec(ctx context.Context, args []string) error {
return run(ctx, cmd)
}
func runUsage(c *ffcli.Command) string {
func doctorExec(ctx context.Context, args []string) error {
return runChecks(ctx, conf.Checks)
}
func printRunUsage(c *ffcli.Command) string {
var out strings.Builder
fmt.Fprintf(&out, "USAGE\n")
@ -249,7 +263,7 @@ func runUsage(c *ffcli.Command) string {
return out.String()
}
func testUsage(c *ffcli.Command) string {
func printTestUsage(c *ffcli.Command) string {
var out strings.Builder
fmt.Fprintf(&out, "USAGE\n")
@ -271,7 +285,7 @@ func testUsage(c *ffcli.Command) string {
return out.String()
}
func runSetUsage(c *ffcli.Command) string {
func printRunSetUsage(c *ffcli.Command) string {
var out strings.Builder
fmt.Fprintf(&out, "USAGE\n")
@ -292,7 +306,7 @@ func runSetUsage(c *ffcli.Command) string {
return out.String()
}
func startUsage(c *ffcli.Command) string {
func printStartUsage(c *ffcli.Command) string {
var out strings.Builder
fmt.Fprintf(&out, "USAGE\n")
@ -301,6 +315,15 @@ func startUsage(c *ffcli.Command) string {
return out.String()
}
func printDoctorUsage(c *ffcli.Command) string {
var out strings.Builder
fmt.Fprintf(&out, "USAGE\n")
fmt.Fprintf(&out, " sg doctor\n")
return out.String()
}
func fileExists(path string) (bool, error) {
_, err := os.Stat(path)
if err != nil {

View File

@ -382,3 +382,41 @@ func runTest(ctx context.Context, cmd Command, args []string) error {
return c.Run()
}
func runChecks(ctx context.Context, checks map[string]Check) error {
root, err := root.RepositoryRoot()
if err != nil {
return err
}
for _, check := range checks {
commandCtx, cancel := context.WithCancel(ctx)
defer cancel()
c := exec.CommandContext(commandCtx, "bash", "-c", check.Cmd)
c.Dir = root
c.Env = makeEnv(conf.Env)
p := out.Pending(output.Linef(output.EmojiLightbulb, output.StylePending, "Running check %q...", check.Name))
if cmdOut, err := c.CombinedOutput(); err != nil {
p.Complete(output.Linef(output.EmojiFailure, output.StyleWarning, "Check %q failed: %s", check.Name, err))
out.WriteLine(output.Linef("", output.StyleWarning, "%s", check.FailMessage))
if len(cmdOut) != 0 {
out.WriteLine(output.Linef("", output.StyleWarning, "Check produced the following output:"))
separator := strings.Repeat("-", 80)
line := output.Linef(
"", output.StyleWarning,
"%s\n%s%s%s%s%s",
separator, output.StyleReset, cmdOut, output.StyleWarning, separator, output.StyleReset,
)
out.WriteLine(line)
}
} else {
p.Complete(output.Linef(output.EmojiSuccess, output.StyleSuccess, "Check %q success!", check.Name))
}
}
return nil
}

View File

@ -6,7 +6,7 @@ import (
"sync"
"time"
"github.com/sourcegraph/src-cli/internal/output"
"github.com/sourcegraph/sourcegraph/lib/output"
)
var (

View File

@ -315,6 +315,19 @@ commands:
GOGC: 50
PATH: .bin:$PATH
checks:
docker:
cmd: docker -v
failMessage: "Failed to run 'docker -v'. Please make sure Docker is running."
redis:
cmd: echo "PING" | nc localhost 6379
failMessage: "Failed to connect to Redis on port 6379. Please make sure Redis is running."
postgres:
cmd: psql -c 'SELECT 1;'
failMessage: "Failed to connect to Postgres database. Make sure environment variables are setup correctly so that psql can connect."
commandsets:
default:
- frontend