sourcegraph/dev/sg/main.go
Thorsten Ball 01575ee603
sg: add ability to define checks per commandset (#23020)
This allows us to check whether services are running before we boot up
the environment. This should reduce the number of "why is service X failing?".
At least that's the hope.

The changes to the config file format are backwards compatible. See the
`UnmarshalYAML` method on `Commandset`.

It also updates the checks to make sure that they work for users of the
docker-compose setup.
2021-07-21 17:37:24 +02:00

873 lines
28 KiB
Go

package main
import (
"context"
"flag"
"fmt"
"io"
"net/url"
"os"
"path/filepath"
"strings"
"syscall"
"github.com/Masterminds/semver"
"github.com/cockroachdb/errors"
"github.com/peterbourgon/ff/v3/ffcli"
"github.com/sourcegraph/sourcegraph/dev/sg/internal/db"
"github.com/sourcegraph/sourcegraph/dev/sg/internal/migration"
"github.com/sourcegraph/sourcegraph/dev/sg/internal/squash"
"github.com/sourcegraph/sourcegraph/dev/sg/internal/stdout"
"github.com/sourcegraph/sourcegraph/dev/sg/root"
"github.com/sourcegraph/sourcegraph/lib/output"
)
var out *output.Output = stdout.Out
var (
runFlagSet = flag.NewFlagSet("sg run", flag.ExitOnError)
runCommand = &ffcli.Command{
Name: "run",
ShortUsage: "sg run <command>",
ShortHelp: "Run the given command.",
FlagSet: runFlagSet,
Exec: runExec,
UsageFunc: printRunUsage,
}
runSetFlagSet = flag.NewFlagSet("sg run-set", flag.ExitOnError)
runSetCommand = &ffcli.Command{
Name: "run-set",
ShortUsage: "sg run-set <commandset>",
ShortHelp: "Run the given command set.",
FlagSet: runSetFlagSet,
Exec: runSetExec,
UsageFunc: printRunSetUsage,
}
startFlagSet = flag.NewFlagSet("sg start", flag.ExitOnError)
startCommand = &ffcli.Command{
Name: "start",
ShortUsage: "sg start",
ShortHelp: "Runs the commandset with the name 'start'.",
FlagSet: startFlagSet,
Exec: startExec,
UsageFunc: printStartUsage,
}
testFlagSet = flag.NewFlagSet("sg test", flag.ExitOnError)
testCommand = &ffcli.Command{
Name: "test",
ShortUsage: "sg test <testsuite>",
ShortHelp: "Run the given test suite.",
FlagSet: testFlagSet,
Exec: testExec,
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,
}
liveFlagSet = flag.NewFlagSet("sg live", flag.ExitOnError)
liveCommand = &ffcli.Command{
Name: "live",
ShortUsage: "sg live <environment>",
ShortHelp: "Reports which version of Sourcegraph is currently live in the given environment",
FlagSet: liveFlagSet,
Exec: liveExec,
UsageFunc: printLiveUsage,
}
migrationAddFlagSet = flag.NewFlagSet("sg migration add", flag.ExitOnError)
migrationAddDatabaseNameFlag = migrationAddFlagSet.String("db", db.DefaultDatabase.Name, "The target database instance.")
migrationAddCommand = &ffcli.Command{
Name: "add",
ShortUsage: fmt.Sprintf("sg migration add [-db=%s] <name>", db.DefaultDatabase.Name),
ShortHelp: "Add a new migration file",
FlagSet: migrationAddFlagSet,
Exec: migrationAddExec,
UsageFunc: printMigrationAddUsage,
}
migrationUpFlagSet = flag.NewFlagSet("sg migration up", flag.ExitOnError)
migrationUpDatabaseNameFlag = migrationUpFlagSet.String("db", db.DefaultDatabase.Name, "The target database instance.")
migrationUpNFlag = migrationUpFlagSet.Int("n", 1, "How many migrations to apply.")
migrationUpCommand = &ffcli.Command{
Name: "up",
ShortUsage: fmt.Sprintf("sg migration up [-db=%s] [-n]", db.DefaultDatabase.Name),
ShortHelp: "Run up migration files",
FlagSet: migrationUpFlagSet,
Exec: migrationUpExec,
UsageFunc: printMigrationUpUsage,
}
migrationDownFlagSet = flag.NewFlagSet("sg migration down", flag.ExitOnError)
migrationDownDatabaseNameFlag = migrationDownFlagSet.String("db", db.DefaultDatabase.Name, "The target database instance.")
migrationDownNFlag = migrationDownFlagSet.Int("n", 1, "How many migrations to apply.")
migrationDownCommand = &ffcli.Command{
Name: "down",
ShortUsage: fmt.Sprintf("sg migration down [-db=%s] [-n=1]", db.DefaultDatabase.Name),
ShortHelp: "Run down migration files",
FlagSet: migrationDownFlagSet,
Exec: migrationDownExec,
UsageFunc: printMigrationDownUsage,
}
migrationSquashFlagSet = flag.NewFlagSet("sg migration squash", flag.ExitOnError)
migrationSquashDatabaseNameFlag = migrationSquashFlagSet.String("db", db.DefaultDatabase.Name, "The target database instance")
migrationSquashCommand = &ffcli.Command{
Name: "squash",
ShortUsage: fmt.Sprintf("sg migration squash [-db=%s] <current-release>", db.DefaultDatabase.Name),
ShortHelp: "Collapse migration files from historic releases together",
FlagSet: migrationSquashFlagSet,
Exec: migrationSquashExec,
UsageFunc: printMigrationSquashUsage,
}
migrationFixupFlagSet = flag.NewFlagSet("sg migration fixup", flag.ExitOnError)
migrationFixupDatabaseNameFlag = migrationFixupFlagSet.String("db", "all", "The target database instance (or 'all' for all databases)")
migrationFixupMainNameFlag = migrationFixupFlagSet.String("main", "main", "The branch/revision to compare with")
migrationFixupRunFlag = migrationFixupFlagSet.Bool("run", true, "Run the migrations in your local database")
migrationFixupCommand = &ffcli.Command{
Name: "fixup",
ShortUsage: fmt.Sprintf("sg migration fixup [-db=%s] [-main=%s] [-run=true]", "all", "main"),
ShortHelp: "Find and fix any conflicting migration names from rebasing on main. Also properly migrates your local database",
FlagSet: migrationFixupFlagSet,
Exec: migrationFixupExec,
UsageFunc: printMigrationFixupUsage,
}
migrationFlagSet = flag.NewFlagSet("sg migration", flag.ExitOnError)
migrationCommand = &ffcli.Command{
Name: "migration",
ShortUsage: "sg migration <command>",
ShortHelp: "Modifies and runs database migrations",
FlagSet: migrationFlagSet,
Exec: func(ctx context.Context, args []string) error {
return flag.ErrHelp
},
UsageFunc: printMigrationUsage,
Subcommands: []*ffcli.Command{
migrationAddCommand,
migrationUpCommand,
migrationDownCommand,
migrationSquashCommand,
migrationFixupCommand,
},
}
rfcFlagSet = flag.NewFlagSet("sg rfc", flag.ExitOnError)
rfcCommand = &ffcli.Command{
Name: "rfc",
ShortUsage: "sg rfc [list|search|open]",
ShortHelp: "Run the given RFC command to manage RFCs.",
FlagSet: rfcFlagSet,
Exec: rfcExec,
UsageFunc: printRFCUsage,
}
)
const (
defaultConfigFile = "sg.config.yaml"
defaultConfigOverwriteFile = "sg.config.overwrite.yaml"
)
var (
rootFlagSet = flag.NewFlagSet("sg", flag.ExitOnError)
configFlag = rootFlagSet.String("config", defaultConfigFile, "configuration file")
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,
Exec: func(ctx context.Context, args []string) error {
return flag.ErrHelp
},
UsageFunc: func(c *ffcli.Command) string {
var out strings.Builder
printLogo(&out)
fmt.Fprintf(&out, "USAGE\n")
fmt.Fprintf(&out, " sg <subcommand>\n")
fmt.Fprintf(&out, "\n")
fmt.Fprintf(&out, "AVAILABLE COMMANDS\n")
for _, sub := range c.Subcommands {
fmt.Fprintf(&out, " %s\n", sub.Name)
}
fmt.Fprintf(&out, "\nRun 'sg <subcommand> -help' to get help output for each subcommand\n")
return out.String()
},
Subcommands: []*ffcli.Command{
runCommand,
runSetCommand,
startCommand,
testCommand,
doctorCommand,
liveCommand,
migrationCommand,
rfcCommand,
},
}
)
func setMaxOpenFiles() error {
const maxOpenFiles = 10000
var rLimit syscall.Rlimit
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
return err
}
if rLimit.Cur < maxOpenFiles {
rLimit.Cur = maxOpenFiles
// This may not succeed, see https://github.com/golang/go/issues/30401
return syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
}
return nil
}
func main() {
if err := rootCommand.Parse(os.Args[1:]); err != nil {
os.Exit(1)
}
// We always try to set this, since we often want to watch files, start commands, etc.
if err := setMaxOpenFiles(); err != nil {
fmt.Printf("failed to set max open files: %s\n", err)
os.Exit(1)
}
if err := rootCommand.Run(context.Background()); err != nil {
fmt.Printf("error: %s\n", err)
os.Exit(1)
}
}
// globalConf is the global config. If a command needs to access it, it *must* call
// `parseConf` before.
var globalConf *Config
// parseConf parses the config file and the optional overwrite file.
// Iear the conf has already been parsed it's a noop.
func parseConf(confFile, overwriteFile string) (bool, output.FancyLine) {
if globalConf != nil {
return true, output.FancyLine{}
}
// Try to determine root of repository, so we can look for config there
repoRoot, err := root.RepositoryRoot()
if err != nil {
return false, output.Linef("", output.StyleWarning, "Failed to determine repository root location: %s", err)
}
// If the configFlag/overwriteConfigFlag flags have their default value, we
// take the value as relative to the root of the repository.
if confFile == defaultConfigFile {
confFile = filepath.Join(repoRoot, confFile)
}
if overwriteFile == defaultConfigOverwriteFile {
overwriteFile = filepath.Join(repoRoot, overwriteFile)
}
globalConf, err = ParseConfigFile(confFile)
if err != nil {
return false, output.Linef("", output.StyleWarning, "Failed to parse %s%s%s%s as configuration file:%s\n%s\n", output.StyleBold, confFile, output.StyleReset, output.StyleWarning, output.StyleReset, err)
}
if ok, _ := fileExists(overwriteFile); ok {
overwriteConf, err := ParseConfigFile(overwriteFile)
if err != nil {
return false, output.Linef("", output.StyleWarning, "Failed to parse %s%s%s%s as overwrites configuration file:%s\n%s\n", output.StyleBold, overwriteFile, output.StyleReset, output.StyleWarning, output.StyleReset, err)
}
globalConf.Merge(overwriteConf)
}
return true, output.FancyLine{}
}
func runSetExec(ctx context.Context, args []string) error {
ok, errLine := parseConf(*configFlag, *overwriteConfigFlag)
if !ok {
out.WriteLine(errLine)
os.Exit(1)
}
if len(args) == 0 {
out.WriteLine(output.Linef("", output.StyleWarning, "No commandset specified\n"))
return flag.ErrHelp
}
if len(args) != 1 {
out.WriteLine(output.Linef("", output.StyleWarning, "ERROR: too many arguments\n"))
return flag.ErrHelp
}
set, ok := globalConf.Commandsets[args[0]]
if !ok {
out.WriteLine(output.Linef("", output.StyleWarning, "ERROR: commandset %q not found :(\n", args[0]))
return flag.ErrHelp
}
var checks []Check
for _, name := range set.Checks {
check, ok := globalConf.Checks[name]
if !ok {
out.WriteLine(output.Linef("", output.StyleWarning, "WARNING: check %s not found in config\n", name))
continue
}
checks = append(checks, check)
}
ok, err := runChecks(ctx, checks...)
if err != nil {
out.WriteLine(output.Linef("", output.StyleWarning, "ERROR: checks could not be run: %s\n", err))
}
if !ok {
out.WriteLine(output.Linef("", output.StyleWarning, "ERROR: checks did not pass, aborting start of commandset %s\n", set.Name))
return nil
}
cmds := make([]Command, 0, len(set.Commands))
for _, name := range set.Commands {
cmd, ok := globalConf.Commands[name]
if !ok {
return errors.Errorf("command %q not found in commandset %q", name, args[0])
}
cmds = append(cmds, cmd)
}
return run(ctx, cmds...)
}
func testExec(ctx context.Context, args []string) error {
ok, errLine := parseConf(*configFlag, *overwriteConfigFlag)
if !ok {
out.WriteLine(errLine)
os.Exit(1)
}
if len(args) == 0 {
out.WriteLine(output.Linef("", output.StyleWarning, "No test suite specified\n"))
return flag.ErrHelp
}
cmd, ok := globalConf.Tests[args[0]]
if !ok {
out.WriteLine(output.Linef("", output.StyleWarning, "ERROR: test suite %q not found :(\n", args[0]))
return flag.ErrHelp
}
return runTest(ctx, cmd, args[1:])
}
func startExec(ctx context.Context, args []string) error {
ok, errLine := parseConf(*configFlag, *overwriteConfigFlag)
if !ok {
out.WriteLine(errLine)
os.Exit(1)
}
if len(args) != 0 {
out.WriteLine(output.Linef("", output.StyleWarning, "ERROR: too many arguments\n"))
return flag.ErrHelp
}
return runSetExec(ctx, []string{"default"})
}
func runExec(ctx context.Context, args []string) error {
ok, errLine := parseConf(*configFlag, *overwriteConfigFlag)
if !ok {
out.WriteLine(errLine)
os.Exit(1)
}
if len(args) == 0 {
out.WriteLine(output.Linef("", output.StyleWarning, "No command specified\n"))
return flag.ErrHelp
}
if len(args) != 1 {
out.WriteLine(output.Linef("", output.StyleWarning, "ERROR: too many arguments\n"))
return flag.ErrHelp
}
cmd, ok := globalConf.Commands[args[0]]
if !ok {
out.WriteLine(output.Linef("", output.StyleWarning, "ERROR: command %q not found :(\n", args[0]))
return flag.ErrHelp
}
return run(ctx, cmd)
}
func doctorExec(ctx context.Context, args []string) error {
ok, errLine := parseConf(*configFlag, *overwriteConfigFlag)
if !ok {
out.WriteLine(errLine)
os.Exit(1)
}
var checks []Check
for _, c := range globalConf.Checks {
checks = append(checks, c)
}
_, err := runChecks(ctx, checks...)
return err
}
func liveExec(ctx context.Context, args []string) error {
if len(args) == 0 {
out.WriteLine(output.Linef("", output.StyleWarning, "No environment specified\n"))
return flag.ErrHelp
}
if len(args) != 1 {
out.WriteLine(output.Linef("", output.StyleWarning, "ERROR: too many arguments\n"))
return flag.ErrHelp
}
e, ok := getEnvironment(args[0])
if !ok {
if customURL, err := url.Parse(args[0]); err == nil {
e = environment{Name: customURL.Host, URL: customURL.String()}
} else {
out.WriteLine(output.Linef("", output.StyleWarning, "ERROR: environment %q not found, or is not a valid URL :(\n", args[0]))
return flag.ErrHelp
}
}
return printDeployedVersion(e)
}
func migrationAddExec(ctx context.Context, args []string) error {
if len(args) == 0 {
out.WriteLine(output.Linef("", output.StyleWarning, "No migration name specified\n"))
return flag.ErrHelp
}
if len(args) != 1 {
out.WriteLine(output.Linef("", output.StyleWarning, "ERROR: too many arguments\n"))
return flag.ErrHelp
}
var (
databaseName = *migrationAddDatabaseNameFlag
migrationName = args[0]
database, ok = db.DatabaseByName(databaseName)
)
if !ok {
out.WriteLine(output.Linef("", output.StyleWarning, "ERROR: database %q not found :(\n", databaseName))
return flag.ErrHelp
}
upFile, downFile, err := migration.RunAdd(database, migrationName)
if err != nil {
return err
}
block := out.Block(output.Linef("", output.StyleBold, "Migration files created"))
block.Writef("Up migration: %s", upFile)
block.Writef("Down migration: %s", downFile)
block.Close()
return nil
}
func migrationUpExec(ctx context.Context, args []string) error {
if len(args) != 0 {
out.WriteLine(output.Linef("", output.StyleWarning, "ERROR: too many arguments\n"))
return flag.ErrHelp
}
var (
databaseName = *migrationUpDatabaseNameFlag
database, ok = db.DatabaseByName(databaseName)
)
if !ok {
out.WriteLine(output.Linef("", output.StyleWarning, "ERROR: database %q not found :(\n", databaseName))
return flag.ErrHelp
}
var n *int
migrationUpFlagSet.Visit(func(f *flag.Flag) {
if f.Name == "n" {
n = migrationUpNFlag
}
})
// Only pass the value of n here if the user actually set it
// We have to do the dance above because the flags package
// requires you to define a default value for each flag.
return migration.RunUp(database, n)
}
func migrationDownExec(ctx context.Context, args []string) error {
if len(args) != 0 {
out.WriteLine(output.Linef("", output.StyleWarning, "ERROR: too many arguments\n"))
return flag.ErrHelp
}
var (
databaseName = *migrationDownDatabaseNameFlag
database, ok = db.DatabaseByName(databaseName)
)
if !ok {
out.WriteLine(output.Linef("", output.StyleWarning, "ERROR: database %q not found :(\n", databaseName))
return flag.ErrHelp
}
return migration.RunDown(database, migrationDownNFlag)
}
// minimumMigrationSquashDistance is the minimum number of releases a migration is guaranteed to exist
// as a non-squashed file.
//
// A squash distance of 1 will allow one minor downgrade.
// A squash distance of 2 will allow two minor downgrades.
// etc
const minimumMigrationSquashDistance = 2
func migrationSquashExec(ctx context.Context, args []string) (err error) {
if len(args) == 0 {
out.WriteLine(output.Linef("", output.StyleWarning, "No current-version specified\n"))
return flag.ErrHelp
}
if len(args) != 1 {
out.WriteLine(output.Linef("", output.StyleWarning, "ERROR: too many arguments\n"))
return flag.ErrHelp
}
var (
databaseName = *migrationSquashDatabaseNameFlag
migrationName = args[0]
database, ok = db.DatabaseByName(databaseName)
)
if !ok {
out.WriteLine(output.Linef("", output.StyleWarning, "ERROR: database %q not found :(\n", databaseName))
return flag.ErrHelp
}
currentVersion, err := semver.NewVersion(migrationName)
if err != nil {
return err
}
// Get the last migration that existed in the version _before_ `minimumMigrationSquashDistance` releases ago
commit := fmt.Sprintf("v%d.%d.0", currentVersion.Major(), currentVersion.Minor()-minimumMigrationSquashDistance-1)
out.Writef("Squashing migration files defined up through %s", commit)
return squash.Run(database, commit)
}
func printRunUsage(c *ffcli.Command) string {
var out strings.Builder
fmt.Fprintf(&out, "USAGE\n")
fmt.Fprintf(&out, " sg %s <command>\n", c.Name)
// Attempt to parse config to list available commands, but don't fail on
// error, because we should never error when the user wants --help output.
_, _ = parseConf(*configFlag, *overwriteConfigFlag)
if globalConf != nil {
fmt.Fprintf(&out, "\n")
fmt.Fprintf(&out, "AVAILABLE COMMANDS IN %s%s%s\n", output.StyleBold, *configFlag, output.StyleReset)
for name := range globalConf.Commands {
fmt.Fprintf(&out, " %s\n", name)
}
}
return out.String()
}
func migrationFixupExec(ctx context.Context, args []string) (err error) {
if len(args) != 0 {
out.WriteLine(output.Linef("", output.StyleWarning, "ERROR: too many arguments\n"))
return flag.ErrHelp
}
branchName := *migrationFixupMainNameFlag
if branchName == "" {
branchName = "main"
}
databaseName := *migrationFixupDatabaseNameFlag
if databaseName == "all" {
for _, databaseName := range db.DatabaseNames() {
database, _ := db.DatabaseByName(databaseName)
if err := migration.RunFixup(database, branchName, *migrationFixupRunFlag); err != nil {
return err
}
}
return nil
} else {
database, ok := db.DatabaseByName(databaseName)
if !ok {
out.WriteLine(output.Linef("", output.StyleWarning, "ERROR: database %q not found :(\n", databaseName))
return flag.ErrHelp
}
return migration.RunFixup(database, branchName, *migrationFixupRunFlag)
}
}
func printTestUsage(c *ffcli.Command) string {
var out strings.Builder
fmt.Fprintf(&out, "USAGE\n")
fmt.Fprintf(&out, " sg %s <test suite>\n", c.Name)
// Attempt to parse config so we can list test suites, but don't fail on
// error, because we should never error when the user wants --help output.
_, _ = parseConf(*configFlag, *overwriteConfigFlag)
if globalConf != nil {
fmt.Fprintf(&out, "\n")
fmt.Fprintf(&out, "AVAILABLE TESTSUITES IN %s%s%s\n", output.StyleBold, *configFlag, output.StyleReset)
for name := range globalConf.Tests {
fmt.Fprintf(&out, " %s\n", name)
}
}
return out.String()
}
func printRunSetUsage(c *ffcli.Command) string {
var out strings.Builder
fmt.Fprintf(&out, "USAGE\n")
fmt.Fprintf(&out, " sg %s <commandset>\n", c.Name)
// Attempt to parse config so we can list available sets, but don't fail on
// error, because we should never error when the user wants --help output.
_, _ = parseConf(*configFlag, *overwriteConfigFlag)
if globalConf != nil {
fmt.Fprintf(&out, "\n")
fmt.Fprintf(&out, "AVAILABLE COMMANDSETS IN %s%s%s\n", output.StyleBold, *configFlag, output.StyleReset)
for name := range globalConf.Commandsets {
fmt.Fprintf(&out, " %s\n", name)
}
}
return out.String()
}
func printStartUsage(c *ffcli.Command) string {
var out strings.Builder
fmt.Fprintf(&out, "USAGE\n")
fmt.Fprintln(&out, " sg start")
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 printLiveUsage(c *ffcli.Command) string {
var out strings.Builder
fmt.Fprintf(&out, "USAGE\n")
fmt.Fprintf(&out, " sg live <environment|url>\n")
fmt.Fprintf(&out, "\n")
fmt.Fprintf(&out, "AVAILABLE PRESET ENVIRONMENTS\n")
for _, name := range environmentNames() {
fmt.Fprintf(&out, " %s\n", name)
}
return out.String()
}
func printMigrationUsage(c *ffcli.Command) string {
var out strings.Builder
printLogo(&out)
fmt.Fprintf(&out, "USAGE\n")
fmt.Fprintf(&out, " sg migration <subcommand>\n")
fmt.Fprintf(&out, "\n")
fmt.Fprintf(&out, "AVAILABLE COMMANDS\n")
for _, sub := range c.Subcommands {
fmt.Fprintf(&out, " %s\n", sub.Name)
}
fmt.Fprintf(&out, "\nRun 'sg migration <subcommand> -help' to get help output for each subcommand\n")
return out.String()
}
func printMigrationAddUsage(c *ffcli.Command) string {
var out strings.Builder
fmt.Fprintf(&out, "USAGE\n")
fmt.Fprintf(&out, " %s", c.ShortUsage)
fmt.Fprintf(&out, "\n")
fmt.Fprintf(&out, "AVAILABLE DATABASES\n")
for _, name := range db.DatabaseNames() {
fmt.Fprintf(&out, " %s\n", name)
}
return out.String()
}
func printMigrationUpUsage(c *ffcli.Command) string {
var out strings.Builder
fmt.Fprintf(&out, "USAGE\n")
fmt.Fprintf(&out, " sg migration up [-db=%s] [-n]\n", db.DefaultDatabase.Name)
fmt.Fprintf(&out, "\n")
fmt.Fprintf(&out, "AVAILABLE DATABASES\n")
for _, name := range db.DatabaseNames() {
fmt.Fprintf(&out, " %s\n", name)
}
return out.String()
}
func printMigrationDownUsage(c *ffcli.Command) string {
var out strings.Builder
fmt.Fprintf(&out, "USAGE\n")
fmt.Fprintf(&out, " sg migration down [-db=%s] [-n=1]\n", db.DefaultDatabase.Name)
fmt.Fprintf(&out, "\n")
fmt.Fprintf(&out, "AVAILABLE DATABASES\n")
for _, name := range db.DatabaseNames() {
fmt.Fprintf(&out, " %s\n", name)
}
return out.String()
}
func printMigrationSquashUsage(c *ffcli.Command) string {
var out strings.Builder
fmt.Fprintf(&out, "USAGE\n")
fmt.Fprintf(&out, " sg migration squash [-db=%s] <current-release>\n", db.DefaultDatabase.Name)
fmt.Fprintf(&out, "\n")
fmt.Fprintf(&out, "AVAILABLE DATABASES\n")
for _, name := range db.DatabaseNames() {
fmt.Fprintf(&out, " %s\n", name)
}
return out.String()
}
func printMigrationFixupUsage(c *ffcli.Command) string {
var out strings.Builder
fmt.Fprintf(&out, "USAGE\n")
fmt.Fprintf(&out, " sg migration fixup [-db=%s] [-main=%s] [-run=true]\n", db.DefaultDatabase.Name, "main")
fmt.Fprintf(&out, "\n")
fmt.Fprintf(&out, "AVAILABLE DATABASES\n")
for _, name := range db.DatabaseNames() {
fmt.Fprintf(&out, " %s\n", name)
}
return out.String()
}
func printRFCUsage(c *ffcli.Command) string {
var out strings.Builder
fmt.Fprintf(&out, "USAGE\n")
fmt.Fprintf(&out, " sg %s <command>\n", c.Name)
fmt.Fprintf(&out, "\n")
fmt.Fprintf(&out, "COMMANDS:\n")
fmt.Fprintf(&out, " list - list all RFCs\n")
fmt.Fprintf(&out, " search <query> - search for RFCs matching the query\n")
fmt.Fprintf(&out, " open <number> - Open the specified RFC\n")
return out.String()
}
func fileExists(path string) (bool, error) {
_, err := os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
return true, nil
}
var styleOrange = output.Fg256Color(202)
func printLogo(out io.Writer) {
fmt.Fprintf(out, "%s", output.StyleLogo)
fmt.Fprintln(out, ` _____ _____`)
fmt.Fprintln(out, ` /\ \ /\ \`)
fmt.Fprintf(out, ` /%s::%s\ \ /%s::%s\ \`, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo)
fmt.Fprintln(out)
fmt.Fprintf(out, ` /%s::::%s\ \ /%s::::%s\ \`, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo)
fmt.Fprintln(out)
fmt.Fprintf(out, ` /%s::::::%s\ \ /%s::::::%s\ \`, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo)
fmt.Fprintln(out)
fmt.Fprintf(out, ` /%s:::%s/\%s:::%s\ \ /%s:::%s/\%s:::%s\ \`, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo)
fmt.Fprintln(out)
fmt.Fprintf(out, ` /%s:::%s/__\%s:::%s\ \ /%s:::%s/ \%s:::%s\ \`, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo)
fmt.Fprintln(out)
fmt.Fprintf(out, ` \%s:::%s\ \%s:::%s\ \ /%s:::%s/ \%s:::%s\ \`, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo)
fmt.Fprintln(out)
fmt.Fprintf(out, ` ___\%s:::%s\ \%s:::%s\ \ /%s:::%s/ / \%s:::%s\ \`, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo)
fmt.Fprintln(out)
fmt.Fprintf(out, ` /\ \%s:::%s\ \%s:::%s\ \ /%s:::%s/ / \%s:::%s\ ___\`, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo)
fmt.Fprintln(out)
fmt.Fprintf(out, `/%s::%s\ \%s:::%s\ \%s:::%s\____\/%s:::%s/____/ ___\%s:::%s| |`, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo)
fmt.Fprintln(out)
fmt.Fprintf(out, `\%s:::%s\ \%s:::%s\ \%s::%s/ /\%s:::%s\ \ /\ /%s:::%s|____|`, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo)
fmt.Fprintln(out)
fmt.Fprintf(out, ` \%s:::%s\ \%s:::%s\ \/____/ \%s:::%s\ /%s::%s\ \%s::%s/ /`, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo)
fmt.Fprintln(out)
fmt.Fprintf(out, ` \%s:::%s\ \%s:::%s\ \ \%s:::%s\ \%s:::%s\ \/____/`, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo)
fmt.Fprintln(out)
fmt.Fprintf(out, ` \%s:::%s\ \%s:::%s\____\ \%s:::%s\ \%s:::%s\____\`, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo)
fmt.Fprintln(out)
fmt.Fprintf(out, ` \%s:::%s\ /%s:::%s/ / \%s:::%s\ /%s:::%s/ /`, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo)
fmt.Fprintln(out)
fmt.Fprintf(out, ` \%s:::%s\/%s:::%s/ / \%s:::%s\/%s:::%s/ /`, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo)
fmt.Fprintln(out)
fmt.Fprintf(out, ` \%s::::::%s/ / \%s::::::%s/ /`, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo)
fmt.Fprintln(out)
fmt.Fprintf(out, ` \%s::::%s/ / \%s::::%s/ /`, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo)
fmt.Fprintln(out)
fmt.Fprintf(out, ` \%s::%s/ / \%s::%s/____/`, styleOrange, output.StyleLogo, styleOrange, output.StyleLogo)
fmt.Fprintln(out)
fmt.Fprintln(out, ` \/____/`)
fmt.Fprintf(out, "%s", output.StyleReset)
}