[sg] Watch config files (#61669)

* watch config

* removed toy commands

* updated go.mod

* addressed comments
This commit is contained in:
James McNamara 2024-04-22 09:32:21 -07:00 committed by GitHub
parent 24e8505019
commit 08ffd2d658
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 354 additions and 200 deletions

View File

@ -110,6 +110,7 @@ go_library(
"@com_github_jackc_pgx_v4//:pgx",
"@com_github_keegancsmith_sqlf//:sqlf",
"@com_github_masterminds_semver//:semver",
"@com_github_mitchellh_hashstructure_v2//:hashstructure",
"@com_github_opsgenie_opsgenie_go_sdk_v2//alert",
"@com_github_opsgenie_opsgenie_go_sdk_v2//client",
"@com_github_slack_go_slack//:slack",

View File

@ -23,6 +23,7 @@ var checks = map[string]check.CheckFunc{
"docker": check.Docker,
"ibazel": check.WrapErrMessage(check.InPath("ibazel"), "brew install ibazel"),
"bazelisk": check.Bazelisk,
"dev-private": check.DevPrivate,
}
func runChecksWithName(ctx context.Context, names []string) error {

View File

@ -23,6 +23,7 @@ go_library(
"//dev/sg/root",
"//internal/database/postgresdsn",
"//internal/limiter",
"//lib/cliutil/exit",
"//lib/errors",
"//lib/output",
"@com_github_gomodule_redigo//redis",

View File

@ -20,9 +20,12 @@ import (
"github.com/sourcegraph/run"
"github.com/sourcegraph/sourcegraph/dev/sg/internal/sgconf"
"github.com/sourcegraph/sourcegraph/dev/sg/internal/std"
"github.com/sourcegraph/sourcegraph/dev/sg/root"
"github.com/sourcegraph/sourcegraph/internal/database/postgresdsn"
"github.com/sourcegraph/sourcegraph/lib/cliutil/exit"
"github.com/sourcegraph/sourcegraph/lib/errors"
"github.com/sourcegraph/sourcegraph/lib/output"
)
// checkPostgresConnection succeeds connecting to the default user database works, regardless
@ -351,6 +354,69 @@ func Caddy(_ context.Context) error {
return errors.New("doesn't look like certificate is trusted")
}
const devPrivateDefaultBranch = "master"
var NoDevPrivateCheck = false
// TODO: use or merge this check with the `dev-private` check in `sg setup`
func DevPrivate(ctx context.Context) error {
if NoDevPrivateCheck {
return nil
}
repoRoot, err := root.RepositoryRoot()
if err != nil {
std.Out.WriteLine(output.Styledf(output.StyleWarning, "Failed to determine repository root location: %s", err))
return exit.NewEmptyExitErr(1)
}
devPrivatePath := filepath.Join(repoRoot, "..", "dev-private")
exists, err := pathExists(devPrivatePath)
if err != nil {
std.Out.WriteLine(output.Styledf(output.StyleWarning, "Failed to check whether dev-private repository exists: %s", err))
return exit.NewEmptyExitErr(1)
}
if !exists {
std.Out.WriteLine(output.Styled(output.StyleWarning, "ERROR: dev-private repository not found!"))
std.Out.WriteLine(output.Styledf(output.StyleWarning, "It's expected to exist at: %s", devPrivatePath))
std.Out.WriteLine(output.Styled(output.StyleWarning, "See the documentation for how to get set up: https://sourcegraph.com/docs/dev/setup/quickstart#run-sg-setup"))
std.Out.Write("")
return exit.NewEmptyExitErr(1)
}
// dev-private exists, let's see if there are any changes
update := std.Out.Pending(output.Styled(output.StylePending, "Checking for dev-private changes..."))
shouldUpdate, err := shouldUpdateDevPrivate(ctx, devPrivatePath, devPrivateDefaultBranch)
if shouldUpdate {
update.WriteLine(output.Line(output.EmojiInfo, output.StyleSuggestion, "We found some changes in dev-private that you're missing out on! If you want the new changes, 'cd ../dev-private' and then do a 'git stash' and a 'git pull'!"))
}
if err != nil {
update.Close()
std.Out.WriteWarningf("WARNING: Encountered some trouble while checking if there are remote changes in dev-private!")
std.Out.Write("")
std.Out.Write(err.Error())
std.Out.Write("")
} else {
update.Complete(output.Line(output.EmojiSuccess, output.StyleSuccess, "Done checking dev-private changes"))
}
return nil
}
func shouldUpdateDevPrivate(ctx context.Context, path, branch string) (bool, error) {
// git fetch so that we check whether there are any remote changes
if err := run.Bash(ctx, fmt.Sprintf("git fetch origin %s", branch)).Dir(path).Run().Wait(); err != nil {
return false, err
}
// Now we check if there are any changes. If the output is empty, we're not missing out on anything.
outputStr, err := run.Bash(ctx, fmt.Sprintf("git diff --shortstat origin/%s", branch)).Dir(path).Run().String()
if err != nil {
return false, err
}
return len(outputStr) > 0, err
}
func pathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {

View File

@ -76,7 +76,9 @@ func initLogsDir() (string, error) {
}
logsdir := path.Join(sghomedir, "sg_start/logs")
os.RemoveAll(logsdir)
if err := os.RemoveAll(logsdir); err != nil {
return "", err
}
if err := os.MkdirAll(logsdir, 0744); err != nil && !os.IsExist(err) {
return "", err
}
@ -223,7 +225,9 @@ func newIBazelEventHandler(filename string) *iBazelEventHandler {
// This is a blocking function
func (h *iBazelEventHandler) watch(ctx context.Context) {
_, cancel := context.WithCancelCause(ctx)
tail, err := tail.TailFile(h.filename, tail.Config{Follow: true, Logger: tail.DiscardingLogger})
// I have anecdotal evidence that the default inotify events fail when the logfile is recreated and dumped to
// by a restarting iBazel instance. So switching to Poll
tail, err := tail.TailFile(h.filename, tail.Config{Follow: true, Poll: true, Logger: tail.DiscardingLogger})
if err != nil {
cancel(err)
}

View File

@ -83,10 +83,6 @@ type Commandset struct {
DockerCommands []string `yaml:"dockerCommands"`
Checks []string `yaml:"checks"`
Env map[string]string `yaml:"env"`
// If this is set to true, then the commandset requires the dev-private
// repository to be cloned at the same level as the sourcegraph repository.
RequiresDevPrivate bool `yaml:"requiresDevPrivate"`
}
// UnmarshalYAML implements the Unmarshaler interface.
@ -136,8 +132,6 @@ func (c *Commandset) Merge(other *Commandset) *Commandset {
merged.Env[k] = v
}
merged.RequiresDevPrivate = other.RequiresDevPrivate
return merged
}

View File

@ -57,6 +57,16 @@ func GetWithoutOverwrites(confFile string) (*Config, error) {
return globalConf, globalConfErr
}
// GetUnbuffered retrieves the global config files and merges them into a single sg config.
// Unlike Get, it doesn't cache the result, and will evaluate every time. This is to allow file watching.
//
// It must not be called before flag initalization, i.e. when confFile or overwriteFile is
// not set, or it will panic. This means that it can only be used in (*cli).Action,
// (*cli).Before/(*cli).After, and postInitHooks
func GetUnbuffered(confFile, overwriteFile string, disableOverwrite bool) (*Config, error) {
return parseConf(confFile, overwriteFile, false)
}
func parseConf(confFile, overwriteFile string, noOverwrite bool) (*Config, error) {
// Try to determine root of repository, so we can look for config there
repoRoot, err := root.RepositoryRoot()

View File

@ -8,6 +8,7 @@ import (
"path/filepath"
"time"
hashstructure "github.com/mitchellh/hashstructure/v2"
"github.com/urfave/cli/v2"
"github.com/sourcegraph/log"
@ -15,7 +16,9 @@ import (
"github.com/sourcegraph/sourcegraph/dev/sg/ci"
"github.com/sourcegraph/sourcegraph/dev/sg/internal/analytics"
"github.com/sourcegraph/sourcegraph/dev/sg/internal/background"
"github.com/sourcegraph/sourcegraph/dev/sg/internal/check"
"github.com/sourcegraph/sourcegraph/dev/sg/internal/release"
"github.com/sourcegraph/sourcegraph/dev/sg/internal/run"
"github.com/sourcegraph/sourcegraph/dev/sg/internal/secrets"
"github.com/sourcegraph/sourcegraph/dev/sg/internal/sgconf"
"github.com/sourcegraph/sourcegraph/dev/sg/internal/std"
@ -51,8 +54,6 @@ func main() {
var (
BuildCommit = "dev"
NoDevPrivateCheck = false
// configFile is the path to use with sgconf.Get - it must not be used before flag
// initialization.
configFile string
@ -145,7 +146,7 @@ var sg = &cli.App{
Usage: "disable checking for dev-private - only useful for automation or ci",
EnvVars: []string{"SG_NO_DEV_PRIVATE"},
Value: false,
Destination: &NoDevPrivateCheck,
Destination: &check.NoDevPrivateCheck,
},
},
Before: func(cmd *cli.Context) (err error) {
@ -353,3 +354,62 @@ func getConfig() (*sgconf.Config, error) {
}
return sgconf.Get(configFile, configOverwriteFile)
}
// watchConfig starts a file watcher for the sg configuration files. It returns a channel
// that will receive the updated configuration whenever the file changes to a valid configuration,
// distinct from the last parsed value (invalid or unparseable updates will be dropped). The
// initial configuration is read and sent on the channel before the function returns so it
// can be read immediately.
func watchConfig(ctx context.Context) (<-chan *sgconf.Config, error) {
conf, err := sgconf.GetUnbuffered(configFile, configOverwriteFile, disableOverwrite)
if err != nil {
return nil, err
}
// Create a hash to compare future reads against
hash, err := hashstructure.Hash(conf, hashstructure.FormatV2, nil)
if err != nil {
return nil, err
}
output := make(chan *sgconf.Config, 1)
output <- conf
// start file watcher on configuration files
paths := []string{configFile}
if !disableOverwrite {
paths = append(paths, configOverwriteFile)
}
updates, err := run.WatchPaths(ctx, paths)
if err != nil {
return nil, err
}
// watch for configuration updates
go func() {
for {
select {
case <-ctx.Done():
close(output)
return
case <-updates:
conf, err = sgconf.GetUnbuffered(configFile, configOverwriteFile, disableOverwrite)
if err != nil {
std.Out.WriteWarningf("Failed to reload configuration: %s", err)
continue
}
newHash, err := hashstructure.Hash(conf, hashstructure.FormatV2, nil)
if err != nil {
std.Out.WriteWarningf("Failed to hash configuration: %s", err)
continue
}
// if this is a true update, send it on the channel and remember its hash
if newHash != hash {
hash = newHash
output <- conf
}
}
}
}()
return output, err
}

View File

@ -7,7 +7,6 @@ import (
"strings"
"github.com/urfave/cli/v2"
"gopkg.in/yaml.v3"
"github.com/sourcegraph/sourcegraph/dev/sg/internal/category"
"github.com/sourcegraph/sourcegraph/dev/sg/internal/std"
@ -73,30 +72,11 @@ sg run -describe jaeger
}
func runExec(ctx *cli.Context) error {
config, err := getConfig()
if err != nil {
return err
args := StartArgs{
Describe: ctx.Bool("describe"),
Commands: ctx.Args().Slice(),
}
cmds, err := listToCommands(config, ctx.Args().Slice())
if err != nil {
return err
}
if ctx.Bool("describe") {
for _, cmd := range cmds.commands {
out, err := yaml.Marshal(cmd)
if err != nil {
return err
}
if err = std.Out.WriteMarkdown(fmt.Sprintf("# %s\n\n```yaml\n%s\n```\n\n", cmd.GetConfig().Name, string(out))); err != nil {
return err
}
}
return nil
}
return cmds.start(ctx.Context)
return start(ctx.Context, args)
}
func constructRunCmdLongHelp() string {

View File

@ -7,13 +7,12 @@ import (
"net/http"
_ "net/http/pprof"
"os"
"path/filepath"
"slices"
"sort"
"strings"
"time"
sgrun "github.com/sourcegraph/run"
hashstructure "github.com/mitchellh/hashstructure/v2"
"github.com/urfave/cli/v2"
"gopkg.in/yaml.v3"
@ -22,9 +21,7 @@ import (
"github.com/sourcegraph/sourcegraph/dev/sg/internal/sgconf"
"github.com/sourcegraph/sourcegraph/dev/sg/internal/std"
"github.com/sourcegraph/sourcegraph/dev/sg/interrupt"
"github.com/sourcegraph/sourcegraph/dev/sg/root"
"github.com/sourcegraph/sourcegraph/lib/cliutil/completions"
"github.com/sourcegraph/sourcegraph/lib/cliutil/exit"
"github.com/sourcegraph/sourcegraph/lib/errors"
"github.com/sourcegraph/sourcegraph/lib/output"
)
@ -47,8 +44,6 @@ func init() {
)
}
const devPrivateDefaultBranch = "master"
var (
debugStartServices cli.StringSlice
infoStartServices cli.StringSlice
@ -191,11 +186,6 @@ func constructStartCmdLongHelp() string {
}
func startExec(ctx *cli.Context) error {
config, err := getConfig()
if err != nil {
return err
}
pid, exists, err := run.PidExistsWithArgs(os.Args[1:])
if err != nil {
std.Out.WriteAlertf("Could not check if 'sg %s' is already running with the same arguments. Process: %d", strings.Join(os.Args[1:], " "), pid)
@ -212,80 +202,6 @@ func startExec(ctx *cli.Context) error {
}
}
// If the commands flag is passed, we just extract the command line arguments as
// a list of commands to run.
if ctx.Bool("commands") {
cmds, err := listToCommands(config, ctx.Args().Slice())
if err != nil {
return err
}
return cmds.start(ctx.Context)
}
set, err := getCommandSet(config, ctx.Args().Slice())
if err != nil {
std.Out.WriteLine(output.Styledf(output.StyleWarning, "ERROR: extracting commandset failed %q :(", err))
return flag.ErrHelp
}
if ctx.Bool("describe") {
out, err := yaml.Marshal(set)
if err != nil {
return err
}
return std.Out.WriteMarkdown(fmt.Sprintf("# %s\n\n```yaml\n%s\n```\n\n", set.Name, string(out)))
}
// If the commandset requires the dev-private repository to be cloned, we
// check that it's at the right location here.
if set.RequiresDevPrivate && !NoDevPrivateCheck {
repoRoot, err := root.RepositoryRoot()
if err != nil {
std.Out.WriteLine(output.Styledf(output.StyleWarning, "Failed to determine repository root location: %s", err))
return exit.NewEmptyExitErr(1)
}
devPrivatePath := filepath.Join(repoRoot, "..", "dev-private")
exists, err := pathExists(devPrivatePath)
if err != nil {
std.Out.WriteLine(output.Styledf(output.StyleWarning, "Failed to check whether dev-private repository exists: %s", err))
return exit.NewEmptyExitErr(1)
}
if !exists {
std.Out.WriteLine(output.Styled(output.StyleWarning, "ERROR: dev-private repository not found!"))
std.Out.WriteLine(output.Styledf(output.StyleWarning, "It's expected to exist at: %s", devPrivatePath))
std.Out.WriteLine(output.Styled(output.StyleWarning, "See the documentation for how to get set up: https://sourcegraph.com/docs/dev/setup/quickstart#run-sg-setup"))
std.Out.Write("")
overwritePath := filepath.Join(repoRoot, "sg.config.overwrite.yaml")
std.Out.WriteLine(output.Styledf(output.StylePending, "If you know what you're doing and want disable the check, add the following to %s:", overwritePath))
std.Out.Write("")
std.Out.Write(fmt.Sprintf(` commandsets:
%s:
requiresDevPrivate: false
`, set.Name))
std.Out.Write("")
return exit.NewEmptyExitErr(1)
}
// dev-private exists, let's see if there are any changes
update := std.Out.Pending(output.Styled(output.StylePending, "Checking for dev-private changes..."))
shouldUpdate, err := shouldUpdateDevPrivate(ctx.Context, devPrivatePath, devPrivateDefaultBranch)
if shouldUpdate {
update.WriteLine(output.Line(output.EmojiInfo, output.StyleSuggestion, "We found some changes in dev-private that you're missing out on! If you want the new changes, 'cd ../dev-private' and then do a 'git stash' and a 'git pull'!"))
}
if err != nil {
update.Close()
std.Out.WriteWarningf("WARNING: Encountered some trouble while checking if there are remote changes in dev-private!")
std.Out.Write("")
std.Out.Write(err.Error())
std.Out.Write("")
} else {
update.Complete(output.Line(output.EmojiSuccess, output.StyleSuccess, "Done checking dev-private changes"))
}
}
if ctx.Bool("profile") {
// start a pprof server
go func() {
@ -308,68 +224,150 @@ go tool pprof -help
`)
}
return startCommandSet(ctx.Context, set, config)
args := StartArgs{
Describe: ctx.Bool("describe"),
}
if ctx.Bool("commands") {
args.Commands = ctx.Args().Slice()
} else {
commandsets := ctx.Args().Slice()
switch length := len(commandsets); {
case length > 1:
std.Out.WriteLine(output.Styled(output.StyleWarning, "ERROR: too many arguments"))
return flag.ErrHelp
case length == 1:
args.CommandSet = commandsets[0]
}
}
return start(ctx.Context, args)
}
func startCommandSet(ctx context.Context, set *sgconf.Commandset, conf *sgconf.Config) error {
commands, err := commandSetToCommands(conf, set)
func start(ctx context.Context, args StartArgs) error {
// Start the config watcher
configs, err := watchConfig(ctx)
if err != nil {
return err
}
return commands.start(ctx)
var (
childCtx context.Context
cancel func()
errs = make(chan error)
hash uint64
)
for {
select {
case <-ctx.Done():
return nil
case err := <-errs:
if err != nil {
return err
}
case conf := <-configs:
// Construct the new commands definition and only restart if the changes
// to the config file are relevant to the commands we're running
cmds, err := args.toCommands(conf)
if err != nil {
return err
}
newHash, err := hashstructure.Hash(cmds, hashstructure.FormatV2, nil)
if err != nil {
return err
}
if hash == newHash {
continue
} else {
hash = newHash
}
// Cancel current context if exists, wait for it to close then create a new one
if cancel != nil {
cancel()
// Wait for the context to close and make sure it's a context cancellation error.
// In the case where all watched commands have already exited with 0 status,
// there won't be an error so we can just continue
select {
case err := <-errs:
if !errors.Is(err, context.Canceled) {
return err
}
case <-time.After(500 * time.Millisecond):
}
}
// Create a new child context and restart the process
childCtx, cancel = context.WithCancel(ctx)
defer cancel()
std.Out.ClearScreen()
go func() {
if args.Describe {
errs <- cmds.describe(conf)
} else {
errs <- cmds.start(childCtx)
}
}()
}
}
}
func shouldUpdateDevPrivate(ctx context.Context, path, branch string) (bool, error) {
// git fetch so that we check whether there are any remote changes
if err := sgrun.Bash(ctx, fmt.Sprintf("git fetch origin %s", branch)).Dir(path).Run().Wait(); err != nil {
return false, err
}
// Now we check if there are any changes. If the output is empty, we're not missing out on anything.
outputStr, err := sgrun.Bash(ctx, fmt.Sprintf("git diff --shortstat origin/%s", branch)).Dir(path).Run().String()
if err != nil {
return false, err
}
return len(outputStr) > 0, err
type StartArgs struct {
Describe bool
Commands []string
CommandSet string
}
func getCommandSet(config *sgconf.Config, args []string) (*sgconf.Commandset, error) {
switch length := len(args); {
case length > 1:
std.Out.WriteLine(output.Styled(output.StyleWarning, "ERROR: too many arguments"))
func (args StartArgs) toCommands(conf *sgconf.Config) (*Commands, error) {
if conf == nil {
return nil, errors.New("config is nil")
}
// If the commands flag is passed, we just extract the command line arguments as
// a list of commands to run. Else we extract the commandset and parse out its individual commands
if len(args.Commands) > 0 {
return listToCommands(conf, args.Commands)
} else {
set, err := getCommandSet(conf, args.CommandSet)
if err != nil {
std.Out.WriteLine(output.Styledf(output.StyleWarning, "ERROR: extracting commandset failed %q :(", err))
return nil, flag.ErrHelp
}
return commandSetToCommands(conf, set)
}
}
func getCommandSet(config *sgconf.Config, name string) (*sgconf.Commandset, error) {
if name == "" {
name = config.DefaultCommandset
}
if set, ok := config.Commandsets[name]; ok {
return set, nil
} else {
std.Out.WriteLine(output.Styledf(output.StyleWarning, "ERROR: commandset %q not found :(", name))
return nil, flag.ErrHelp
case length == 1:
if set, ok := config.Commandsets[args[0]]; ok {
return set, nil
} else {
std.Out.WriteLine(output.Styledf(output.StyleWarning, "ERROR: commandset %q not found :(", args[0]))
return nil, flag.ErrHelp
}
default:
if set, ok := config.Commandsets[config.DefaultCommandset]; ok {
return set, nil
} else {
std.Out.WriteLine(output.Styled(output.StyleWarning, "ERROR: No commandset specified and no 'defaultCommandset' specified in sg.config.yaml\n"))
return nil, flag.ErrHelp
}
}
}
// Public keys are considered part of hash calculation
type Commands struct {
checks []string
commands []run.SGConfigCommand
env map[string]string
Name string
Checks []string
Commands []run.SGConfigCommand
Env map[string]string
ibazel *run.IBazel
}
func (cmds *Commands) add(cmd ...run.SGConfigCommand) {
cmds.commands = append(cmds.commands, cmd...)
cmds.Commands = append(cmds.Commands, cmd...)
}
func (cmds *Commands) getBazelTargets() (targets []string) {
for _, cmd := range cmds.commands {
for _, cmd := range cmds.Commands {
target := cmd.GetBazelTarget()
if target != "" && !slices.Contains(targets, target) {
targets = append(targets, target)
@ -380,7 +378,7 @@ func (cmds *Commands) getBazelTargets() (targets []string) {
}
func (cmds *Commands) getInstallers() (installers []run.Installer, err error) {
for _, cmd := range cmds.commands {
for _, cmd := range cmds.Commands {
if installer, ok := cmd.(run.Installer); ok {
installers = append(installers, installer)
}
@ -396,11 +394,11 @@ func (cmds *Commands) getInstallers() (installers []run.Installer, err error) {
}
func (cmds *Commands) start(ctx context.Context) error {
if err := runChecksWithName(ctx, cmds.checks); err != nil {
if err := runChecksWithName(ctx, cmds.Checks); err != nil {
return err
}
if len(cmds.commands) == 0 {
if len(cmds.Commands) == 0 {
std.Out.WriteLine(output.Styled(output.StyleWarning, "WARNING: no commands to run"))
return nil
}
@ -410,7 +408,7 @@ func (cmds *Commands) start(ctx context.Context) error {
return err
}
if err := run.Install(ctx, cmds.env, verbose, installers); err != nil {
if err := run.Install(ctx, cmds.Env, verbose, installers); err != nil {
return err
}
@ -419,10 +417,14 @@ func (cmds *Commands) start(ctx context.Context) error {
defer cmds.ibazel.Close()
}
return run.Commands(ctx, cmds.env, verbose, cmds.commands)
return run.Commands(ctx, cmds.Env, verbose, cmds.Commands)
}
func listToCommands(config *sgconf.Config, names []string) (*Commands, error) {
if len(names) == 0 {
std.Out.WriteLine(output.Styled(output.StyleWarning, "ERROR: no commands passed"))
return nil, flag.ErrHelp
}
var cmds Commands
for _, arg := range names {
if cmd, ok := getCommand(config, arg); ok {
@ -432,13 +434,15 @@ func listToCommands(config *sgconf.Config, names []string) (*Commands, error) {
return nil, flag.ErrHelp
}
}
cmds.env = config.Env
cmds.Env = config.Env
return &cmds, nil
}
func commandSetToCommands(config *sgconf.Config, set *sgconf.Commandset) (*Commands, error) {
cmds := Commands{}
cmds := Commands{
Name: set.Name,
}
if ccmds, err := getCommands(set.Commands, set, config.Commands); err != nil {
return nil, err
} else {
@ -457,16 +461,18 @@ func commandSetToCommands(config *sgconf.Config, set *sgconf.Commandset) (*Comma
cmds.add(dcmds...)
}
cmds.env = config.Env
cmds.Env = config.Env
for k, v := range set.Env {
cmds.env[k] = v
cmds.Env[k] = v
}
addLogLevel := createLogLevelAdder(logLevelOverrides())
for i, cmd := range cmds.commands {
cmds.commands[i] = cmd.UpdateConfig(addLogLevel)
for i, cmd := range cmds.Commands {
cmds.Commands[i] = cmd.UpdateConfig(addLogLevel)
}
cmds.Checks = set.Checks
return &cmds, nil
}
@ -524,6 +530,33 @@ func getCommands[T run.SGConfigCommand](commands []string, set *sgconf.Commandse
return cmds, nil
}
func (cmds *Commands) describe(config *sgconf.Config) error {
if cmds.Name == "" {
for _, cmd := range cmds.Commands {
out, err := yaml.Marshal(cmd)
if err != nil {
return err
}
if err = std.Out.WriteMarkdown(fmt.Sprintf("# %s\n\n```yaml\n%s\n```\n\n", cmd.GetConfig().Name, string(out))); err != nil {
return err
}
}
return nil
} else {
set, err := getCommandSet(config, cmds.Name)
if err != nil {
return nil
}
out, err := yaml.Marshal(set)
if err != nil {
return err
}
return std.Out.WriteMarkdown(fmt.Sprintf("# %s\n\n```yaml\n%s\n```\n\n", set.Name, string(out)))
}
}
// logLevelOverrides builds a map of commands -> log level that should be overridden in the environment.
func logLevelOverrides() map[string]string {
levelServices := make(map[string][]string)
@ -558,14 +591,3 @@ func createLogLevelAdder(overrides map[string]string) func(*run.SGConfigCommandO
}
}
}
func pathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}

View File

@ -34,11 +34,15 @@ func TestStartCommandSet(t *testing.T) {
Commandsets: map[string]*sgconf.Commandset{"test-set": commandSet},
}
if err := startCommandSet(ctx, commandSet, testConf); err != nil {
args := StartArgs{
CommandSet: "test-set",
}
cmds, _ := args.toCommands(testConf)
if err := cmds.start(ctx); err != nil {
t.Errorf("failed to start: %s", err)
}
println(strings.Join(buf.Lines(), "\n"))
expectOutput(t, buf, []string{
"",
"💡 Installing 1 commands...",
@ -76,7 +80,15 @@ func TestStartCommandSet_InstallError(t *testing.T) {
Commandsets: map[string]*sgconf.Commandset{"test-set": commandSet},
}
err := startCommandSet(ctx, commandSet, testConf)
args := StartArgs{
CommandSet: "test-set",
}
cmds, err := args.toCommands(testConf)
if err != nil {
t.Errorf("unexected error constructing commands: %s", err)
}
err = cmds.start(ctx)
if err == nil {
t.Fatalf("err is nil unexpectedly")
}

1
go.mod
View File

@ -268,6 +268,7 @@ require (
github.com/hashicorp/terraform-cdk-go/cdktf v0.17.3
github.com/invopop/jsonschema v0.12.0
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/mroth/weightedrand/v2 v2.0.1
github.com/nxadm/tail v1.4.11
github.com/oschwald/maxminddb-golang v1.12.0

2
go.sum
View File

@ -1366,6 +1366,8 @@ github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTS
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0=
github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v0.0.0-20170523030023-d0303fe80992/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=

View File

@ -1244,13 +1244,13 @@ dockerCommands:
defaultCommandset: enterprise
commandsets:
enterprise-bazel: &enterprise_bazel_set
requiresDevPrivate: true
checks:
- redis
- postgres
- git
- bazelisk
- ibazel
- dev-private
bazelCommands:
- blobstore
- docsite
@ -1272,12 +1272,12 @@ commandsets:
# If you modify this command set, please consider also updating the dotcom runset.
enterprise: &enterprise_set
requiresDevPrivate: true
checks:
- docker
- redis
- postgres
- git
- dev-private
commands:
- frontend
- worker
@ -1310,12 +1310,12 @@ commandsets:
dotcom:
# This is 95% the enterprise runset, with the addition of Cody Gateway.
requiresDevPrivate: true
checks:
- docker
- redis
- postgres
- git
- dev-private
commands:
- frontend
- worker
@ -1339,7 +1339,6 @@ commandsets:
SOURCEGRAPHDOTCOM_MODE: true
codeintel-bazel: &codeintel_bazel_set
requiresDevPrivate: true
checks:
- docker
- redis
@ -1347,6 +1346,7 @@ commandsets:
- git
- bazelisk
- ibazel
- dev-private
bazelCommands:
- blobstore
- frontend
@ -1372,12 +1372,12 @@ commandsets:
- prometheus
codeintel-syntactic:
requiresDevPrivate: true
checks:
- docker
- redis
- postgres
- git
- dev-private
commands:
- frontend
- web
@ -1390,12 +1390,12 @@ commandsets:
- syntactic-code-intel-worker-1
codeintel:
requiresDevPrivate: true
checks:
- docker
- redis
- postgres
- git
- dev-private
commands:
- frontend
- worker
@ -1421,12 +1421,12 @@ commandsets:
- prometheus
codeintel-kubernetes:
requiresDevPrivate: true
checks:
- docker
- redis
- postgres
- git
- dev-private
commands:
- frontend
- worker
@ -1452,12 +1452,12 @@ commandsets:
- prometheus
enterprise-codeintel:
requiresDevPrivate: true
checks:
- docker
- redis
- postgres
- git
- dev-private
commands:
- frontend
- worker
@ -1482,12 +1482,12 @@ commandsets:
- grafana
- prometheus
enterprise-codeintel-multi-queue-executor:
requiresDevPrivate: true
checks:
- docker
- redis
- postgres
- git
- dev-private
commands:
- frontend
- worker
@ -1516,12 +1516,12 @@ commandsets:
<<: *codeintel_bazel_set
enterprise-codeinsights:
requiresDevPrivate: true
checks:
- docker
- redis
- postgres
- git
- dev-private
commands:
- frontend
- worker
@ -1544,12 +1544,12 @@ commandsets:
DISABLE_CODE_INSIGHTS: false
api-only:
requiresDevPrivate: true
checks:
- docker
- redis
- postgres
- git
- dev-private
commands:
- frontend
- worker
@ -1565,12 +1565,12 @@ commandsets:
- blobstore
batches:
requiresDevPrivate: true
checks:
- docker
- redis
- postgres
- git
- dev-private
commands:
- frontend
- worker
@ -1592,12 +1592,12 @@ commandsets:
- batcheshelper-builder
batches-kubernetes:
requiresDevPrivate: true
checks:
- docker
- redis
- postgres
- git
- dev-private
commands:
- frontend
- worker
@ -1619,12 +1619,12 @@ commandsets:
- batcheshelper-builder
iam:
requiresDevPrivate: true
checks:
- docker
- redis
- postgres
- git
- dev-private
commands:
- frontend
- repo-updater
@ -1695,9 +1695,9 @@ commandsets:
- jaeger
single-program:
requiresDevPrivate: true
checks:
- git
- dev-private
commands:
- sourcegraph
- web