mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 18:51:59 +00:00
[sg] Watch config files (#61669)
* watch config * removed toy commands * updated go.mod * addressed comments
This commit is contained in:
parent
24e8505019
commit
08ffd2d658
@ -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",
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
1
go.mod
@ -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
2
go.sum
@ -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=
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user