mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 02:44:59 +00:00
Add viper (for env variable) support to CLI (#2251)
* Add viper (for env variable) support to CLI * Move the "featurebase cli" sub-command to its own "fbsql" command I don't know if this is the final name, but putting it here as a placeholder for now. * Handle single `--command` flags. This also adds a printer interface so we can opt NOT to print setup information in non-interactive mode. * Add support for multiple `--command` flags in the same call * Add support for multiple `--file` flags * Move members related to Config into a separate struct * Make sure non-interactive mode can connect to a database * comment fix * support control-C on readline * Prevent connection message from printing in non-interactive mode * Return errors (instead of printing them) in non-interactive mode
This commit is contained in:
parent
4172976e6a
commit
393721c0ce
11 changed files with 414 additions and 135 deletions
5
Makefile
5
Makefile
|
|
@ -144,7 +144,7 @@ authclustertests: vendor
|
|||
CLUSTERTESTS_FB_ARGS=$(AUTH_ARGS) $(DOCKER_COMPOSE) -f internal/clustertests/docker-compose.yml down
|
||||
|
||||
# Install FeatureBase and IDK
|
||||
install: install-featurebase install-idk
|
||||
install: install-featurebase install-idk install-fbsql
|
||||
|
||||
install-featurebase:
|
||||
$(GO) install -tags='$(BUILD_TAGS)' -ldflags $(LDFLAGS) $(FLAGS) ./cmd/featurebase
|
||||
|
|
@ -152,6 +152,9 @@ install-featurebase:
|
|||
install-idk:
|
||||
$(MAKE) -C ./idk install
|
||||
|
||||
install-fbsql:
|
||||
$(GO) install ./cmd/fbsql
|
||||
|
||||
# Build the lattice assets
|
||||
build-lattice:
|
||||
docker build -t lattice:build ./lattice
|
||||
|
|
|
|||
326
cli/cli.go
326
cli/cli.go
|
|
@ -14,8 +14,8 @@ import (
|
|||
"github.com/chzyer/readline"
|
||||
featurebase "github.com/featurebasedb/featurebase/v3"
|
||||
"github.com/featurebasedb/featurebase/v3/cli/fbcloud"
|
||||
"github.com/featurebasedb/featurebase/v3/errors"
|
||||
"github.com/featurebasedb/featurebase/v3/logger"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -38,23 +38,19 @@ Type "\q" to quit.
|
|||
`, featurebase.Version)
|
||||
)
|
||||
|
||||
type CLICommand struct {
|
||||
Host string `json:"host"`
|
||||
Port string `json:"port"`
|
||||
HistoryPath string `json:"history-path"`
|
||||
// Ensure type implments interfaces.
|
||||
var _ printer = (*Command)(nil)
|
||||
|
||||
// Cloud Auth
|
||||
ClientID string `json:"client-id"`
|
||||
Region string `json:"region"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
type Command struct {
|
||||
host string
|
||||
port string
|
||||
|
||||
splitter *splitter
|
||||
buffer *buffer
|
||||
workingDir *workingDir
|
||||
|
||||
OrganizationID string `json:"org-id"`
|
||||
Database string `json:"db"`
|
||||
organizationID string
|
||||
database string
|
||||
databaseID string
|
||||
databaseName string
|
||||
|
||||
|
|
@ -69,17 +65,47 @@ type CLICommand struct {
|
|||
output io.Writer `json:"-"`
|
||||
writeOptions *writeOptions
|
||||
|
||||
Config *Config `json:"config"`
|
||||
|
||||
historyPath string
|
||||
|
||||
// Commands contains optional commands provided via one or more `-c` (or
|
||||
// `--command`) flags. If this is non-empty, the cli will run in
|
||||
// non-interactive mode; i.e. it will quit after the command is complete.
|
||||
Commands []string `json:"commands"`
|
||||
|
||||
// Files contains optional files provided via one or more `-f` (or `--file`)
|
||||
// flags. If this is non-empty, the cli will run in non-interactive mode;
|
||||
// i.e. it will quit after the command is complete.
|
||||
Files []string `json:"files"`
|
||||
|
||||
// nonInteractiveMode is set to true when fbsql is running in
|
||||
// non-ineracative mode. And example of this is when the user has provided a
|
||||
// `-c` flag in the command line.
|
||||
nonInteractiveMode bool
|
||||
|
||||
// quit gets closed when Run should stop listening for input.
|
||||
quit chan struct{}
|
||||
}
|
||||
|
||||
func NewCLICommand(logdest logger.Logger) *CLICommand {
|
||||
return &CLICommand{
|
||||
Host: defaultHost,
|
||||
HistoryPath: "",
|
||||
func NewCommand(logdest logger.Logger) *Command {
|
||||
return &Command{
|
||||
Config: &Config{
|
||||
Host: defaultHost,
|
||||
Port: "",
|
||||
|
||||
OrganizationID: "",
|
||||
Database: "",
|
||||
OrganizationID: "",
|
||||
Database: "",
|
||||
|
||||
CloudAuth: CloudAuthConfig{
|
||||
ClientID: "",
|
||||
Region: "",
|
||||
Email: "",
|
||||
Password: "",
|
||||
},
|
||||
|
||||
HistoryPath: "",
|
||||
},
|
||||
|
||||
splitter: newSplitter(),
|
||||
buffer: newBuffer(),
|
||||
|
|
@ -96,9 +122,40 @@ func NewCLICommand(logdest logger.Logger) *CLICommand {
|
|||
}
|
||||
}
|
||||
|
||||
// Run is the main entry-point to the CLI. Currently it handles the interaction
|
||||
// with a user, as opposed to calling `featurebase cli` in a script.
|
||||
func (cmd *CLICommand) Run(ctx context.Context) error {
|
||||
// Run is the main entry-point to the CLI.
|
||||
func (cmd *Command) Run(ctx context.Context) error {
|
||||
cmd.setupConfig()
|
||||
|
||||
// Check to see if Command needs to run in non-interactive mode.
|
||||
if len(cmd.Commands) > 0 || len(cmd.Files) > 0 {
|
||||
cmd.nonInteractiveMode = true
|
||||
|
||||
if err := cmd.setupClient(); err != nil {
|
||||
return errors.Wrap(err, "setting up client")
|
||||
}
|
||||
if err := cmd.connectToDatabase(cmd.database); err != nil {
|
||||
cmd.Errorf(errors.Wrap(err, "connecting to database").Error() + "\n")
|
||||
}
|
||||
|
||||
// Run Commands.
|
||||
for _, line := range cmd.Commands {
|
||||
if err := cmd.handleLine(line); err != nil {
|
||||
cmd.Errorf(err.Error())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Run Files.
|
||||
for _, fname := range cmd.Files {
|
||||
if _, err := executeFile(cmd, fname); err != nil {
|
||||
cmd.Errorf(err.Error())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Print the splash message.
|
||||
cmd.Printf(splash)
|
||||
cmd.setupHistory()
|
||||
|
|
@ -106,13 +163,13 @@ func (cmd *CLICommand) Run(ctx context.Context) error {
|
|||
return errors.Wrap(err, "setting up client")
|
||||
}
|
||||
cmd.printConnInfo()
|
||||
if err := cmd.connectToDatabase(cmd.Database); err != nil {
|
||||
if err := cmd.connectToDatabase(cmd.database); err != nil {
|
||||
cmd.Errorf(errors.Wrap(err, "connecting to database").Error() + "\n")
|
||||
}
|
||||
|
||||
rl, err := readline.NewEx(&readline.Config{
|
||||
Prompt: promptBegin,
|
||||
HistoryFile: cmd.HistoryPath,
|
||||
HistoryFile: cmd.historyPath,
|
||||
HistoryLimit: 100000,
|
||||
DisableAutoSaveHistory: true,
|
||||
|
||||
|
|
@ -138,7 +195,11 @@ func (cmd *CLICommand) Run(ctx context.Context) error {
|
|||
|
||||
// Read user provided input.
|
||||
line, err := rl.Readline()
|
||||
if err != nil {
|
||||
if err == readline.ErrInterrupt {
|
||||
inMidCommand = false
|
||||
cmd.buffer.reset()
|
||||
continue
|
||||
} else if err != nil {
|
||||
return errors.Wrap(err, "reading line")
|
||||
}
|
||||
|
||||
|
|
@ -236,11 +297,27 @@ func (cmd *CLICommand) Run(ctx context.Context) error {
|
|||
|
||||
// close is called upon quitting. It should close any remaining open file
|
||||
// handles used by the CLICommand.
|
||||
func (cmd *CLICommand) close() error {
|
||||
func (cmd *Command) close() error {
|
||||
return cmd.closeOutput()
|
||||
}
|
||||
|
||||
func (cmd *CLICommand) executeAndWriteQuery(qry query) error {
|
||||
// setupConfig sets up private struct members based on values provided via the
|
||||
// configuration flags.
|
||||
func (cmd *Command) setupConfig() {
|
||||
if cmd.Config == nil {
|
||||
return
|
||||
}
|
||||
|
||||
cmd.host = cmd.Config.Host
|
||||
cmd.port = cmd.Config.Port
|
||||
|
||||
cmd.organizationID = cmd.Config.OrganizationID
|
||||
cmd.database = cmd.Config.Database
|
||||
|
||||
cmd.historyPath = cmd.Config.HistoryPath
|
||||
}
|
||||
|
||||
func (cmd *Command) executeAndWriteQuery(qry query) error {
|
||||
queryResponse, err := cmd.executeQuery(qry)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "making query")
|
||||
|
|
@ -253,32 +330,63 @@ func (cmd *CLICommand) executeAndWriteQuery(qry query) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (cmd *CLICommand) executeQuery(qry query) (*featurebase.WireQueryResponse, error) {
|
||||
return cmd.Queryer.Query(cmd.OrganizationID, cmd.databaseID, qry.Reader())
|
||||
func (cmd *Command) executeQuery(qry query) (*featurebase.WireQueryResponse, error) {
|
||||
wqr, err := cmd.Queryer.Query(cmd.organizationID, cmd.databaseID, qry.Reader())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "executing query")
|
||||
}
|
||||
|
||||
// If we're running in non-interactive mode, we need to check the error that
|
||||
// comes back in the WireQueryResponse. If there's an error, we want to
|
||||
// return it now (rather than just printing it later) so that we immediately
|
||||
// stop any further execution of commands.
|
||||
if cmd.nonInteractiveMode && wqr.Error != "" {
|
||||
return nil, errors.Errorf(wqr.Error)
|
||||
}
|
||||
|
||||
return wqr, nil
|
||||
}
|
||||
|
||||
// printer is an interface which encapsulates the methods used to print output
|
||||
// to the various io.Writers.
|
||||
type printer interface {
|
||||
Printf(format string, a ...any)
|
||||
Outputf(format string, a ...any)
|
||||
Errorf(format string, a ...any)
|
||||
}
|
||||
|
||||
type nopPrinter struct{}
|
||||
|
||||
func newNopPrinter() *nopPrinter {
|
||||
return &nopPrinter{}
|
||||
}
|
||||
|
||||
func (n *nopPrinter) Printf(format string, a ...any) {}
|
||||
func (n *nopPrinter) Outputf(format string, a ...any) {}
|
||||
func (n *nopPrinter) Errorf(format string, a ...any) {}
|
||||
|
||||
// Printf is a helper method which sends the given payload to stdout.
|
||||
func (cmd *CLICommand) Printf(format string, a ...any) {
|
||||
func (cmd *Command) Printf(format string, a ...any) {
|
||||
out := fmt.Sprintf(format, a...)
|
||||
cmd.Stdout.Write([]byte(out))
|
||||
}
|
||||
|
||||
// Outputf is a helper method which sends the given payload to output.
|
||||
func (cmd *CLICommand) Outputf(format string, a ...any) {
|
||||
func (cmd *Command) Outputf(format string, a ...any) {
|
||||
out := fmt.Sprintf(format, a...)
|
||||
cmd.output.Write([]byte(out))
|
||||
}
|
||||
|
||||
// Errorf is a helper method which sends the given payload to stderr.
|
||||
func (cmd *CLICommand) Errorf(format string, a ...any) {
|
||||
func (cmd *Command) Errorf(format string, a ...any) {
|
||||
out := fmt.Sprintf(format, a...)
|
||||
cmd.Stderr.Write([]byte(out))
|
||||
}
|
||||
|
||||
func (cmd *CLICommand) setupHistory() {
|
||||
func (cmd *Command) setupHistory() {
|
||||
// If HistoryPath has already been configured (i.e. with a command flag),
|
||||
// don't bother setting up the default in the home directory.
|
||||
if cmd.HistoryPath != "" {
|
||||
if cmd.historyPath != "" {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -294,20 +402,25 @@ func (cmd *CLICommand) setupHistory() {
|
|||
historyPath = filepath.Join(historyDir, "cli_history")
|
||||
}
|
||||
}
|
||||
cmd.HistoryPath = historyPath
|
||||
cmd.historyPath = historyPath
|
||||
}
|
||||
|
||||
// printConnInfo displays the currently set host.
|
||||
// TODO(tlt): extend this to be the output of the /conninfo meta-command.
|
||||
func (cmd *CLICommand) printConnInfo() {
|
||||
cmd.Printf("Host: %s\n", hostPort(cmd.Host, cmd.Port))
|
||||
func (cmd *Command) printConnInfo() {
|
||||
cmd.Printf("Host: %s\n", hostPort(cmd.host, cmd.port))
|
||||
}
|
||||
|
||||
func (cmd *CLICommand) connectToDatabase(dbName string) error {
|
||||
func (cmd *Command) connectToDatabase(dbName string) error {
|
||||
var p printer = cmd
|
||||
if cmd.nonInteractiveMode {
|
||||
p = newNopPrinter()
|
||||
}
|
||||
|
||||
if dbName == "" {
|
||||
cmd.databaseID = ""
|
||||
cmd.databaseName = ""
|
||||
cmd.Printf(cmd.connectionMessage())
|
||||
p.Printf(cmd.connectionMessage())
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -327,40 +440,45 @@ func (cmd *CLICommand) connectToDatabase(dbName string) error {
|
|||
if db[1] == dbName {
|
||||
cmd.databaseName = dbName
|
||||
cmd.databaseID = db[0].(string)
|
||||
cmd.Printf(cmd.connectionMessage())
|
||||
p.Printf(cmd.connectionMessage())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.Errorf("invalid database: %s", dbName)
|
||||
}
|
||||
|
||||
func (cmd *CLICommand) orgMessage() string {
|
||||
if cmd.OrganizationID == "" {
|
||||
func (cmd *Command) orgMessage() string {
|
||||
if cmd.organizationID == "" {
|
||||
return "You have not set an organization.\n"
|
||||
}
|
||||
return fmt.Sprintf("You have set organization \"%s\".\n", cmd.OrganizationID)
|
||||
return fmt.Sprintf("You have set organization \"%s\".\n", cmd.organizationID)
|
||||
}
|
||||
|
||||
func (cmd *CLICommand) connectionMessage() string {
|
||||
func (cmd *Command) connectionMessage() string {
|
||||
if cmd.databaseName == "" {
|
||||
return "You are not connected to a database.\n"
|
||||
}
|
||||
return fmt.Sprintf("You are now connected to database \"%s\" (%s) as user \"???\".\n", cmd.databaseName, cmd.databaseID)
|
||||
}
|
||||
|
||||
func (cmd *CLICommand) setupClient() error {
|
||||
func (cmd *Command) setupClient() error {
|
||||
// If the Queryer has already been set (in tests for example), don't bother
|
||||
// trying to detect it.
|
||||
if cmd.Queryer != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if strings.TrimSpace(cmd.Host) == "" {
|
||||
var p printer = cmd
|
||||
if cmd.nonInteractiveMode {
|
||||
p = newNopPrinter()
|
||||
}
|
||||
|
||||
if strings.TrimSpace(cmd.host) == "" {
|
||||
return errors.Errorf("no host provided\n")
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(cmd.Host, "http") {
|
||||
cmd.Host = "http://" + cmd.Host
|
||||
if !strings.HasPrefix(cmd.host, "http") {
|
||||
cmd.host = "http://" + cmd.host
|
||||
}
|
||||
|
||||
typ, err := cmd.detectFBType()
|
||||
|
|
@ -370,39 +488,40 @@ func (cmd *CLICommand) setupClient() error {
|
|||
|
||||
switch typ {
|
||||
case featurebaseTypeOnPremClassic:
|
||||
cmd.Printf("Detected on-prem, classic deployment.\n")
|
||||
p.Printf("Detected on-prem, classic deployment.\n")
|
||||
cmd.Queryer = &standardQueryer{
|
||||
Host: cmd.Host,
|
||||
Port: cmd.Port,
|
||||
Host: cmd.host,
|
||||
Port: cmd.port,
|
||||
}
|
||||
case featurebaseTypeOnPremServerless:
|
||||
cmd.Printf("Detected on-prem, serverless deployment.\n")
|
||||
p.Printf("Detected on-prem, serverless deployment.\n")
|
||||
cmd.Queryer = &serverlessQueryer{
|
||||
Host: cmd.Host,
|
||||
Port: cmd.Port,
|
||||
Host: cmd.host,
|
||||
Port: cmd.port,
|
||||
}
|
||||
case featurebaseTypeCloud:
|
||||
cmd.Printf("Detected cloud deployment.\n")
|
||||
cmd.Queryer = &fbcloud.Queryer{
|
||||
Host: hostPort(cmd.Host, cmd.Port),
|
||||
p.Printf("Detected cloud deployment.\n")
|
||||
|
||||
ClientID: cmd.ClientID,
|
||||
Region: cmd.Region,
|
||||
Email: cmd.Email,
|
||||
Password: cmd.Password,
|
||||
cmd.Queryer = &fbcloud.Queryer{
|
||||
Host: hostPort(cmd.host, cmd.port),
|
||||
|
||||
ClientID: cmd.Config.CloudAuth.ClientID,
|
||||
Region: cmd.Config.CloudAuth.Region,
|
||||
Email: cmd.Config.CloudAuth.Email,
|
||||
Password: cmd.Config.CloudAuth.Password,
|
||||
}
|
||||
case featurebaseTypeUnknown:
|
||||
cmd.Printf("Could not detect deployment\n")
|
||||
p.Printf("Could not detect deployment\n")
|
||||
// cmd.Queryer = &nopQueryer{}
|
||||
// Instead of using a no-op queryer when the type can't be detected, we
|
||||
// default to using a cloud queryer.
|
||||
cmd.Queryer = &fbcloud.Queryer{
|
||||
Host: hostPort(cmd.Host, cmd.Port),
|
||||
Host: hostPort(cmd.host, cmd.port),
|
||||
|
||||
ClientID: cmd.ClientID,
|
||||
Region: cmd.Region,
|
||||
Email: cmd.Email,
|
||||
Password: cmd.Password,
|
||||
ClientID: cmd.Config.CloudAuth.ClientID,
|
||||
Region: cmd.Config.CloudAuth.Region,
|
||||
Email: cmd.Config.CloudAuth.Email,
|
||||
Password: cmd.Config.CloudAuth.Password,
|
||||
}
|
||||
default:
|
||||
return errors.Errorf("unknown type: %s", typ)
|
||||
|
|
@ -428,7 +547,7 @@ func hostPort(host, port string) string {
|
|||
|
||||
// detectFBType determines if we're talking to standalone FeatureBase
|
||||
// or FeatureBase Cloud
|
||||
func (cmd *CLICommand) detectFBType() (featurebaseType, error) {
|
||||
func (cmd *Command) detectFBType() (featurebaseType, error) {
|
||||
type trial struct {
|
||||
port string
|
||||
health string
|
||||
|
|
@ -440,23 +559,23 @@ func (cmd *CLICommand) detectFBType() (featurebaseType, error) {
|
|||
trials := []trial{}
|
||||
|
||||
var clientTimeout time.Duration
|
||||
if cmd.Port != "" {
|
||||
if cmd.port != "" {
|
||||
clientTimeout = 100 * time.Millisecond
|
||||
trials = append(trials,
|
||||
// on-prem, serverless
|
||||
trial{
|
||||
port: cmd.Port,
|
||||
port: cmd.port,
|
||||
health: "/queryer/health",
|
||||
typ: featurebaseTypeOnPremServerless,
|
||||
},
|
||||
// on-prem, classic
|
||||
trial{
|
||||
port: cmd.Port,
|
||||
port: cmd.port,
|
||||
health: "/status",
|
||||
typ: featurebaseTypeOnPremClassic,
|
||||
},
|
||||
)
|
||||
} else if strings.HasPrefix(cmd.Host, "https") {
|
||||
} else if strings.HasPrefix(cmd.host, "https") {
|
||||
// https suggesting we might be connecting to a cloud host
|
||||
clientTimeout = 1 * time.Second
|
||||
trials = append(trials,
|
||||
|
|
@ -490,11 +609,11 @@ func (cmd *CLICommand) detectFBType() (featurebaseType, error) {
|
|||
Timeout: clientTimeout,
|
||||
}
|
||||
for _, trial := range trials {
|
||||
url := hostPort(cmd.Host, trial.port) + trial.health
|
||||
url := hostPort(cmd.host, trial.port) + trial.health
|
||||
if resp, err := client.Get(url); err != nil {
|
||||
continue
|
||||
} else if resp.StatusCode/100 == 2 {
|
||||
cmd.Port = trial.port
|
||||
cmd.port = trial.port
|
||||
return trial.typ, nil
|
||||
}
|
||||
}
|
||||
|
|
@ -502,7 +621,7 @@ func (cmd *CLICommand) detectFBType() (featurebaseType, error) {
|
|||
return featurebaseTypeUnknown, nil
|
||||
}
|
||||
|
||||
func (cmd *CLICommand) closeOutput() error {
|
||||
func (cmd *Command) closeOutput() error {
|
||||
if cmd.output == nil {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -513,3 +632,62 @@ func (cmd *CLICommand) closeOutput() error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd *Command) handleLine(line string) error {
|
||||
// For single-line command handling, we handle either a meta-command, or
|
||||
// query parts, but not both. The logic is that any line which begins with
|
||||
// "\" will be handled as a meta-command, otherwise it will be handled as a
|
||||
// query.
|
||||
if len(line) == 0 {
|
||||
return nil
|
||||
} else if line[0] == byte('\\') {
|
||||
return cmd.handleLineAsMetaCommand(line)
|
||||
} else {
|
||||
return cmd.handleLineAsQueryParts(line)
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Command) handleLineAsMetaCommand(line string) error {
|
||||
_, mcs, err := cmd.splitter.split(line)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "splitting line")
|
||||
}
|
||||
|
||||
for i := range mcs {
|
||||
_, err := mcs[i].execute(cmd)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "executing meta command")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd *Command) handleLineAsQueryParts(line string) error {
|
||||
qps, mcs, err := cmd.splitter.split(line)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "splitting line")
|
||||
} else if len(mcs) > 0 {
|
||||
return errors.Errorf("--command does not support meta-commands")
|
||||
}
|
||||
|
||||
// Add a termintor part to the end of []queryPart. We do this because the
|
||||
// command is coming in from the --command flag, it may not end with a
|
||||
// semi-colon, but we still want to execute it.
|
||||
if len(qps) > 0 {
|
||||
if _, ok := qps[len(qps)-1].(*partTerminator); !ok {
|
||||
qps = append(qps, newPartTerminator())
|
||||
}
|
||||
}
|
||||
|
||||
for i := range qps {
|
||||
if qry, err := cmd.buffer.addPart(qps[i]); err != nil {
|
||||
return errors.Wrap(err, "adding part to buffer")
|
||||
} else if qry != nil {
|
||||
if err := cmd.executeAndWriteQuery(qry); err != nil {
|
||||
return errors.Wrap(err, "executing query")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ func TestCLI(t *testing.T) {
|
|||
|
||||
capture := newCapture(t)
|
||||
|
||||
cli := cli.NewCLICommand(logger.StderrLogger)
|
||||
cli := cli.NewCommand(logger.StderrLogger)
|
||||
cli.Stdin = capture
|
||||
cli.Stdout = capture
|
||||
cli.Queryer = capture
|
||||
|
|
|
|||
22
cli/config.go
Normal file
22
cli/config.go
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
package cli
|
||||
|
||||
// Config represents the configuration for the command.
|
||||
type Config struct {
|
||||
Host string `json:"host"`
|
||||
Port string `json:"port"`
|
||||
|
||||
OrganizationID string `json:"org-id"`
|
||||
Database string `json:"db"`
|
||||
|
||||
// CloudAuth
|
||||
CloudAuth CloudAuthConfig `json:"cloud-auth"`
|
||||
|
||||
HistoryPath string `json:"history-path"`
|
||||
}
|
||||
|
||||
type CloudAuthConfig struct {
|
||||
ClientID string `json:"client-id"`
|
||||
Region string `json:"region"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
68
cli/meta.go
68
cli/meta.go
|
|
@ -29,7 +29,7 @@ const (
|
|||
|
||||
// metaCommand is the interface for any type responding to a "\" meta-command.
|
||||
type metaCommand interface {
|
||||
execute(cmd *CLICommand) (action, error)
|
||||
execute(cmd *Command) (action, error)
|
||||
}
|
||||
|
||||
// Ensure type implements interface.
|
||||
|
|
@ -71,7 +71,7 @@ func newMetaBang(args []string) *metaBang {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *metaBang) execute(cmd *CLICommand) (action, error) {
|
||||
func (m *metaBang) execute(cmd *Command) (action, error) {
|
||||
if len(m.args) == 0 {
|
||||
return actionNone, errors.Errorf("meta command '!' requires at least one argument")
|
||||
}
|
||||
|
|
@ -95,7 +95,7 @@ func newMetaBorder(args []string) *metaBorder {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *metaBorder) execute(cmd *CLICommand) (action, error) {
|
||||
func (m *metaBorder) execute(cmd *Command) (action, error) {
|
||||
switch len(m.args) {
|
||||
case 0:
|
||||
// pass
|
||||
|
|
@ -129,7 +129,7 @@ func newMetaChangeDirectory(args []string) *metaChangeDirectory {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *metaChangeDirectory) execute(cmd *CLICommand) (action, error) {
|
||||
func (m *metaChangeDirectory) execute(cmd *Command) (action, error) {
|
||||
if len(m.args) != 1 {
|
||||
return actionNone, errors.Errorf("meta command 'cd' requires exactly one argument")
|
||||
}
|
||||
|
|
@ -150,7 +150,7 @@ func newMetaConnect(args []string) *metaConnect {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *metaConnect) execute(cmd *CLICommand) (action, error) {
|
||||
func (m *metaConnect) execute(cmd *Command) (action, error) {
|
||||
switch len(m.args) {
|
||||
case 0:
|
||||
cmd.Printf(cmd.connectionMessage())
|
||||
|
|
@ -177,7 +177,7 @@ func newMetaEcho(args []string) *metaEcho {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *metaEcho) execute(cmd *CLICommand) (action, error) {
|
||||
func (m *metaEcho) execute(cmd *Command) (action, error) {
|
||||
return echo(m.args, cmd.Stdout)
|
||||
}
|
||||
|
||||
|
|
@ -218,7 +218,7 @@ func newMetaExpanded(args []string) *metaExpanded {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *metaExpanded) execute(cmd *CLICommand) (action, error) {
|
||||
func (m *metaExpanded) execute(cmd *Command) (action, error) {
|
||||
switch len(m.args) {
|
||||
case 0:
|
||||
cmd.writeOptions.expanded = !cmd.writeOptions.expanded
|
||||
|
|
@ -257,7 +257,7 @@ func newMetaFile(args []string) *metaFile {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *metaFile) execute(cmd *CLICommand) (action, error) {
|
||||
func (m *metaFile) execute(cmd *Command) (action, error) {
|
||||
if len(m.args) != 1 {
|
||||
return actionNone, errors.Errorf("meta command 'file' requires exactly one argument")
|
||||
}
|
||||
|
|
@ -292,7 +292,7 @@ func newMetaHelp(args []string) *metaHelp {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *metaHelp) execute(cmd *CLICommand) (action, error) {
|
||||
func (m *metaHelp) execute(cmd *Command) (action, error) {
|
||||
helpText := `General
|
||||
\q[uit] quit psql
|
||||
\watch [SEC] execute query every SEC seconds
|
||||
|
|
@ -323,7 +323,7 @@ Formatting
|
|||
\pset [NAME [VALUE]] set table output option
|
||||
(border|expanded|tuples_only)
|
||||
\t [on|off] show only rows
|
||||
\x [on|off|auto] toggle expanded output
|
||||
\x [on|off] toggle expanded output
|
||||
|
||||
Connection
|
||||
\c[onnect] [DBNAME] connect to new database
|
||||
|
|
@ -352,14 +352,18 @@ func newMetaInclude(args []string) *metaInclude {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *metaInclude) execute(cmd *CLICommand) (action, error) {
|
||||
func (m *metaInclude) execute(cmd *Command) (action, error) {
|
||||
if len(m.args) != 1 {
|
||||
return actionNone, errors.Errorf("meta command 'include' requires exactly one argument")
|
||||
}
|
||||
|
||||
file, err := os.Open(m.args[0])
|
||||
return executeFile(cmd, m.args[0])
|
||||
}
|
||||
|
||||
func executeFile(cmd *Command, fileName string) (action, error) {
|
||||
file, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return actionNone, errors.Wrapf(err, "opening file: %s", m.args[0])
|
||||
return actionNone, errors.Wrapf(err, "opening file: %s", fileName)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
|
|
@ -376,7 +380,7 @@ func (m *metaInclude) execute(cmd *CLICommand) (action, error) {
|
|||
if err != nil {
|
||||
return actionNone, errors.Wrapf(err, "splitting lines")
|
||||
} else if len(mcs) > 0 {
|
||||
return actionNone, errors.Errorf("include does not support meta-commands")
|
||||
return actionNone, errors.Errorf("include does not support meta-commands")
|
||||
}
|
||||
|
||||
for i := range qps {
|
||||
|
|
@ -390,7 +394,7 @@ func (m *metaInclude) execute(cmd *CLICommand) (action, error) {
|
|||
}
|
||||
}
|
||||
if err := sc.Err(); err != nil {
|
||||
return actionNone, errors.Wrapf(err, "scanning file: %s", m.args[0])
|
||||
return actionNone, errors.Wrapf(err, "scanning file: %s", fileName)
|
||||
}
|
||||
|
||||
return actionReset, nil
|
||||
|
|
@ -405,7 +409,7 @@ func newMetaListDatabases() *metaListDatabases {
|
|||
return &metaListDatabases{}
|
||||
}
|
||||
|
||||
func (m *metaListDatabases) execute(cmd *CLICommand) (action, error) {
|
||||
func (m *metaListDatabases) execute(cmd *Command) (action, error) {
|
||||
qry := []queryPart{
|
||||
newPartRaw("SHOW DATABASES"),
|
||||
}
|
||||
|
|
@ -426,7 +430,7 @@ func newMetaListTables() *metaListTables {
|
|||
return &metaListTables{}
|
||||
}
|
||||
|
||||
func (m *metaListTables) execute(cmd *CLICommand) (action, error) {
|
||||
func (m *metaListTables) execute(cmd *Command) (action, error) {
|
||||
qry := []queryPart{
|
||||
newPartRaw("SHOW TABLES"),
|
||||
}
|
||||
|
|
@ -451,14 +455,14 @@ func newMetaOrg(args []string) *metaOrg {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *metaOrg) execute(cmd *CLICommand) (action, error) {
|
||||
func (m *metaOrg) execute(cmd *Command) (action, error) {
|
||||
switch len(m.args) {
|
||||
|
||||
case 0:
|
||||
cmd.Printf(cmd.orgMessage())
|
||||
return actionNone, nil
|
||||
case 1:
|
||||
cmd.OrganizationID = m.args[0]
|
||||
cmd.organizationID = m.args[0]
|
||||
cmd.Printf(cmd.orgMessage())
|
||||
return actionNone, nil
|
||||
|
||||
|
|
@ -480,7 +484,7 @@ func newMetaOutput(args []string) *metaOutput {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *metaOutput) execute(cmd *CLICommand) (action, error) {
|
||||
func (m *metaOutput) execute(cmd *Command) (action, error) {
|
||||
switch len(m.args) {
|
||||
case 0:
|
||||
// Close cmd.output (if closable).
|
||||
|
|
@ -522,7 +526,7 @@ func newMetaPrint() *metaPrint {
|
|||
return &metaPrint{}
|
||||
}
|
||||
|
||||
func (m *metaPrint) execute(cmd *CLICommand) (action, error) {
|
||||
func (m *metaPrint) execute(cmd *Command) (action, error) {
|
||||
cmd.Printf(cmd.buffer.print() + "\n")
|
||||
return actionNone, nil
|
||||
}
|
||||
|
|
@ -540,7 +544,7 @@ func newMetaPSet(args []string) *metaPSet {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *metaPSet) print(cmd *CLICommand) {
|
||||
func (m *metaPSet) print(cmd *Command) {
|
||||
onOff := func(b bool) string {
|
||||
if b {
|
||||
return "on"
|
||||
|
|
@ -561,7 +565,7 @@ tuples_only %s
|
|||
|
||||
}
|
||||
|
||||
func (m *metaPSet) execute(cmd *CLICommand) (action, error) {
|
||||
func (m *metaPSet) execute(cmd *Command) (action, error) {
|
||||
switch len(m.args) {
|
||||
case 0:
|
||||
m.print(cmd)
|
||||
|
|
@ -598,7 +602,7 @@ func newMetaQEcho(args []string) *metaQEcho {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *metaQEcho) execute(cmd *CLICommand) (action, error) {
|
||||
func (m *metaQEcho) execute(cmd *Command) (action, error) {
|
||||
return echo(m.args, cmd.output)
|
||||
}
|
||||
|
||||
|
|
@ -611,7 +615,7 @@ func newMetaQuit() *metaQuit {
|
|||
return &metaQuit{}
|
||||
}
|
||||
|
||||
func (m *metaQuit) execute(cmd *CLICommand) (action, error) {
|
||||
func (m *metaQuit) execute(cmd *Command) (action, error) {
|
||||
return actionQuit, nil
|
||||
}
|
||||
|
||||
|
|
@ -624,7 +628,7 @@ func newMetaReset() *metaReset {
|
|||
return &metaReset{}
|
||||
}
|
||||
|
||||
func (m *metaReset) execute(cmd *CLICommand) (action, error) {
|
||||
func (m *metaReset) execute(cmd *Command) (action, error) {
|
||||
cmd.Printf(cmd.buffer.reset())
|
||||
return actionReset, nil
|
||||
}
|
||||
|
|
@ -642,7 +646,7 @@ func newMetaSet(args []string) *metaSet {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *metaSet) execute(cmd *CLICommand) (action, error) {
|
||||
func (m *metaSet) execute(cmd *Command) (action, error) {
|
||||
// TODO: set the variable (or clear it, etc)
|
||||
return actionNone, nil
|
||||
}
|
||||
|
|
@ -660,7 +664,7 @@ func newMetaTiming(args []string) *metaTiming {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *metaTiming) execute(cmd *CLICommand) (action, error) {
|
||||
func (m *metaTiming) execute(cmd *Command) (action, error) {
|
||||
switch len(m.args) {
|
||||
case 0:
|
||||
cmd.writeOptions.timing = !cmd.writeOptions.timing
|
||||
|
|
@ -699,7 +703,7 @@ func newMetaTuplesOnly(args []string) *metaTuplesOnly {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *metaTuplesOnly) execute(cmd *CLICommand) (action, error) {
|
||||
func (m *metaTuplesOnly) execute(cmd *Command) (action, error) {
|
||||
switch len(m.args) {
|
||||
case 0:
|
||||
cmd.writeOptions.tuplesOnly = !cmd.writeOptions.tuplesOnly
|
||||
|
|
@ -738,7 +742,7 @@ func newMetaWarn(args []string) *metaWarn {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *metaWarn) execute(cmd *CLICommand) (action, error) {
|
||||
func (m *metaWarn) execute(cmd *Command) (action, error) {
|
||||
return echo(m.args, cmd.Stderr)
|
||||
}
|
||||
|
||||
|
|
@ -755,7 +759,7 @@ func newMetaWatch(args []string) *metaWatch {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *metaWatch) execute(cmd *CLICommand) (action, error) {
|
||||
func (m *metaWatch) execute(cmd *Command) (action, error) {
|
||||
period := 2 * time.Second
|
||||
|
||||
qry := cmd.buffer.lastQuery
|
||||
|
|
@ -809,7 +813,7 @@ func newMetaWrite(args []string) *metaWrite {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *metaWrite) execute(cmd *CLICommand) (action, error) {
|
||||
func (m *metaWrite) execute(cmd *Command) (action, error) {
|
||||
switch len(m.args) {
|
||||
case 0:
|
||||
cmd.Errorf(`\w: missing required argument` + "\n")
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ func defaultWriteOptions() *writeOptions {
|
|||
return &writeOptions{
|
||||
border: 1,
|
||||
expanded: false,
|
||||
timing: true,
|
||||
timing: false,
|
||||
tuplesOnly: false,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,25 @@ func TestWriter(t *testing.T) {
|
|||
" 3 | Cindy | 28 ",
|
||||
"",
|
||||
),
|
||||
expOut: "",
|
||||
expErr: "",
|
||||
},
|
||||
{
|
||||
// timing on
|
||||
format: &writeOptions{
|
||||
border: 1,
|
||||
expanded: false,
|
||||
timing: true,
|
||||
tuplesOnly: false,
|
||||
},
|
||||
expQOut: stringOfLines(
|
||||
" _id | name | age ",
|
||||
"-----+-------+-----",
|
||||
" 1 | Amy | 44 ",
|
||||
" 2 | Bob | 32 ",
|
||||
" 3 | Cindy | 28 ",
|
||||
"",
|
||||
),
|
||||
expOut: "Execution time: 0μs\n",
|
||||
expErr: "",
|
||||
},
|
||||
|
|
|
|||
34
cmd/cli.go
34
cmd/cli.go
|
|
@ -2,34 +2,34 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/featurebasedb/featurebase/v3/cli"
|
||||
"github.com/featurebasedb/featurebase/v3/ctl"
|
||||
"github.com/featurebasedb/featurebase/v3/logger"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var cliCmd *cli.CLICommand
|
||||
var cliCmd *cli.Command
|
||||
|
||||
// newCLICommand runs the FeatureBase CLI subcommand for ingesting bulk data.
|
||||
func newCLICommand(logdest logger.Logger) *cobra.Command {
|
||||
cliCmd = cli.NewCLICommand(logdest)
|
||||
// NewCLICommand runs the FeatureBase CLI subcommand.
|
||||
func NewCLICommand(stderr io.Writer) *cobra.Command {
|
||||
logdest := logger.NewStandardLogger(stderr)
|
||||
cliCmd = cli.NewCommand(logdest)
|
||||
cobraCmd := &cobra.Command{
|
||||
Use: "cli",
|
||||
Use: "fbsql",
|
||||
Short: "Query FeatureBase with SQL from the command line",
|
||||
Long: ``,
|
||||
RunE: usageErrorWrapper(cliCmd),
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
v := viper.New()
|
||||
return setAllConfig(v, cmd.Flags(), "FBSQL")
|
||||
},
|
||||
SilenceErrors: true,
|
||||
}
|
||||
|
||||
flags := cobraCmd.Flags()
|
||||
flags.StringVarP(&cliCmd.Host, "host", "", cliCmd.Host, "hostname of FeatureBase.")
|
||||
flags.StringVarP(&cliCmd.Port, "port", "", cliCmd.Port, "port of FeatureBase.")
|
||||
flags.StringVar(&cliCmd.HistoryPath, "history-path", cliCmd.HistoryPath, "path for history files.")
|
||||
flags.StringVar(&cliCmd.OrganizationID, "org-id", cliCmd.OrganizationID, "OrganizationID.")
|
||||
flags.StringVar(&cliCmd.Database, "db", cliCmd.Database, "Name of the database to connect to.")
|
||||
|
||||
flags.StringVar(&cliCmd.ClientID, "client-id", cliCmd.ClientID, "Cognito Client ID for FeatureBase Cloud access.")
|
||||
flags.StringVar(&cliCmd.Region, "region", cliCmd.Region, "Cloud region for FeatureBase Cloud access (e.g. us-east-2).")
|
||||
flags.StringVar(&cliCmd.Email, "email", cliCmd.Email, "Email address for FeatureBase Cloud access.")
|
||||
flags.StringVar(&cliCmd.Password, "password", cliCmd.Password, "Password for FeatureBase Cloud access.")
|
||||
|
||||
// Attach flags to the command.
|
||||
ctl.BuildCLIFlags(cobraCmd, cliCmd)
|
||||
return cobraCmd
|
||||
}
|
||||
|
|
|
|||
13
cmd/fbsql/main.go
Normal file
13
cmd/fbsql/main.go
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/featurebasedb/featurebase/v3/cmd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
command := cmd.NewCLICommand(os.Stderr)
|
||||
command.Execute()
|
||||
}
|
||||
19
cmd/root.go
19
cmd/root.go
|
|
@ -63,11 +63,12 @@ at https://docs.featurebase.com/.
|
|||
` + pilosa.VersionInfo(true) + "\n",
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
v := viper.New()
|
||||
if cmd.Use == "dax" {
|
||||
|
||||
switch cmd.Use {
|
||||
case "dax":
|
||||
v.Set("future.rename", true) // always use FEATUREBASE env for dax
|
||||
}
|
||||
err := setAllConfig(v, cmd.Flags())
|
||||
if err != nil {
|
||||
if err := setAllConfig(v, cmd.Flags(), ""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -104,7 +105,6 @@ at https://docs.featurebase.com/.
|
|||
rc.AddCommand(newServeCmd(stderr))
|
||||
rc.AddCommand(newHolderCmd(stderr))
|
||||
rc.AddCommand(newKeygenCommand(logdest))
|
||||
rc.AddCommand(newCLICommand(logdest))
|
||||
rc.AddCommand(newDAXCommand(stderr))
|
||||
rc.AddCommand(newDataframeCsvLoaderCommand(logdest))
|
||||
rc.AddCommand(newPreSortCommand(logdest))
|
||||
|
|
@ -124,17 +124,18 @@ at https://docs.featurebase.com/.
|
|||
// setAllConfig looks for environment variables which are capitalized versions
|
||||
// of the flag names with dashes replaced by underscores, and prefixed with
|
||||
// envPrefix plus an underscore.
|
||||
func setAllConfig(v *viper.Viper, flags *pflag.FlagSet) error { // nolint: unparam
|
||||
func setAllConfig(v *viper.Viper, flags *pflag.FlagSet, envPrefix string) error { // nolint: unparam
|
||||
// add cmd line flag def to viper
|
||||
err := v.BindPFlags(flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
envPrefix := "PILOSA"
|
||||
rename := v.GetBool("future.rename")
|
||||
if rename {
|
||||
envPrefix = "FEATUREBASE"
|
||||
if envPrefix == "" {
|
||||
envPrefix = "PILOSA"
|
||||
if v.GetBool("future.rename") {
|
||||
envPrefix = "FEATUREBASE"
|
||||
}
|
||||
}
|
||||
|
||||
// add env to viper
|
||||
|
|
|
|||
39
ctl/cli.go
Normal file
39
ctl/cli.go
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package ctl
|
||||
|
||||
import (
|
||||
"github.com/featurebasedb/featurebase/v3/cli"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// BuildCLIFlags attaches a set of flags to the command for a cli instance.
|
||||
func BuildCLIFlags(cmd *cobra.Command, cliCmd *cli.Command) {
|
||||
flags := cmd.Flags()
|
||||
|
||||
// Base struct flags.
|
||||
flags.StringSliceVarP(&cliCmd.Commands, "command", "c", cliCmd.Commands, "Command to run in non-interactive mode. Provide multiple flags to execute more than one command. All `--command` flags run before all `--file` flags.")
|
||||
flags.StringSliceVarP(&cliCmd.Files, "file", "f", cliCmd.Files, "File to run in non-interactive mode. Provide multiple flags to execute more than one file. All `--command` flags run before all `--file` flags.")
|
||||
|
||||
// Config flags.
|
||||
flags.AddFlagSet(cliConfigFlagSet(cliCmd.Config))
|
||||
}
|
||||
|
||||
// cliConfigFlagSet returns a pflag.FlagSet for the CLI Config struct.
|
||||
func cliConfigFlagSet(cfg *cli.Config) *pflag.FlagSet {
|
||||
flags := pflag.NewFlagSet("cli", pflag.ExitOnError)
|
||||
|
||||
flags.StringVarP(&cfg.Host, "host", "", cfg.Host, "hostname of FeatureBase.")
|
||||
flags.StringVarP(&cfg.Port, "port", "", cfg.Port, "port of FeatureBase.")
|
||||
flags.StringVar(&cfg.HistoryPath, "history-path", cfg.HistoryPath, "path for history files.")
|
||||
flags.StringVar(&cfg.OrganizationID, "org-id", cfg.OrganizationID, "OrganizationID.")
|
||||
flags.StringVar(&cfg.Database, "db", cfg.Database, "Name of the database to connect to.")
|
||||
|
||||
flags.StringVar(&cfg.CloudAuth.ClientID, "client-id", cfg.CloudAuth.ClientID, "Cognito Client ID for FeatureBase Cloud access.")
|
||||
flags.StringVar(&cfg.CloudAuth.Region, "region", cfg.CloudAuth.Region, "Cloud region for FeatureBase Cloud access (e.g. us-east-2).")
|
||||
flags.StringVar(&cfg.CloudAuth.Email, "email", cfg.CloudAuth.Email, "Email address for FeatureBase Cloud access.")
|
||||
flags.StringVar(&cfg.CloudAuth.Password, "password", cfg.CloudAuth.Password, "Password for FeatureBase Cloud access.")
|
||||
|
||||
flags.String("config", "", "Configuration file to read from.")
|
||||
|
||||
return flags
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue