669 lines
29 KiB
Go
669 lines
29 KiB
Go
// Copyright 2019 Dolthub, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package sqlserver
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/dolthub/go-mysql-server/sql"
|
|
"github.com/fatih/color"
|
|
"github.com/sirupsen/logrus"
|
|
|
|
"github.com/dolthub/dolt/go/cmd/dolt/cli"
|
|
"github.com/dolthub/dolt/go/cmd/dolt/commands"
|
|
"github.com/dolthub/dolt/go/libraries/doltcore/env"
|
|
"github.com/dolthub/dolt/go/libraries/doltcore/servercfg"
|
|
"github.com/dolthub/dolt/go/libraries/utils/argparser"
|
|
"github.com/dolthub/dolt/go/libraries/utils/filesys"
|
|
"github.com/dolthub/dolt/go/libraries/utils/svcs"
|
|
eventsapi "github.com/dolthub/eventsapi_schema/dolt/services/eventsapi/v1alpha1"
|
|
)
|
|
|
|
const (
|
|
hostFlag = "host"
|
|
portFlag = "port"
|
|
skipRootUserInitialization = "skip-root-user-initialization"
|
|
timeoutFlag = "timeout"
|
|
readonlyFlag = "readonly"
|
|
logLevelFlag = "loglevel"
|
|
logFormatFlag = "logformat"
|
|
noAutoCommitFlag = "no-auto-commit"
|
|
configFileFlag = "config"
|
|
queryParallelismFlag = "query-parallelism"
|
|
maxConnectionsFlag = "max-connections"
|
|
maxWaitConnectionsFlag = "back-log"
|
|
maxWaitConsTimeoutFlag = "max-connections-timeout"
|
|
allowCleartextPasswordsFlag = "allow-cleartext-passwords"
|
|
socketFlag = "socket"
|
|
remotesapiPortFlag = "remotesapi-port"
|
|
remotesapiReadOnlyFlag = "remotesapi-readonly"
|
|
goldenMysqlConn = "golden"
|
|
eventSchedulerStatus = "event-scheduler"
|
|
mcpPortFlag = "mcp-port"
|
|
mcpUserFlag = "mcp-user"
|
|
mcpPasswordFlag = "mcp-password"
|
|
mcpDatabaseFlag = "mcp-database"
|
|
)
|
|
|
|
func indentLines(s string) string {
|
|
sb := strings.Builder{}
|
|
lines := strings.Split(s, "\n")
|
|
for _, line := range lines {
|
|
sb.WriteRune('\t')
|
|
sb.WriteString(line)
|
|
sb.WriteRune('\n')
|
|
}
|
|
return sb.String()
|
|
}
|
|
|
|
var sqlServerDocs = cli.CommandDocumentationContent{
|
|
ShortDesc: "Start a MySQL-compatible server.",
|
|
LongDesc: "By default, starts a MySQL-compatible server on the dolt database in the current directory. " +
|
|
"Databases are named after the directories they appear in." +
|
|
"Parameters can be specified using a yaml configuration file passed to the server via " +
|
|
"{{.EmphasisLeft}}--config <file>{{.EmphasisRight}}, or by using the supported switches and flags to configure " +
|
|
"the server directly on the command line. If {{.EmphasisLeft}}--config <file>{{.EmphasisRight}} is provided all" +
|
|
" other command line arguments are ignored.\n\nThis is an example yaml configuration file showing all supported" +
|
|
" items and their default values:\n\n" +
|
|
indentLines(servercfg.ServerConfigAsYAMLConfig(DefaultCommandLineServerConfig()).String()) + "\n\n" + `
|
|
ENVIRONMENT VARIABLE INTERPOLATION:
|
|
|
|
SQL server yaml configs support environment variable interpolation:
|
|
|
|
${VAR} Expands to the value of VAR (error if VAR is unset or empty)
|
|
$$ Escapes to a literal '$'
|
|
|
|
Notes:
|
|
- Interpolation happens before YAML parsing.
|
|
- Quote values for string fields when needed (e.g. values containing ':'), but do not quote placeholders intended for numeric/bool fields.
|
|
|
|
SUPPORTED CONFIG FILE FIELDS:
|
|
|
|
{{.EmphasisLeft}}data_dir{{.EmphasisRight}}: A directory where the server will load dolt databases to serve, and create new ones. Defaults to the current directory.
|
|
|
|
{{.EmphasisLeft}}cfg_dir{{.EmphasisRight}}: A directory where the server will load and store non-database configuration data, such as permission information. Defaults {{.EmphasisLeft}}$data_dir/.doltcfg{{.EmphasisRight}}.
|
|
|
|
{{.EmphasisLeft}}log_level{{.EmphasisRight}}: Level of logging provided. Options are: {{.EmphasisLeft}}trace{{.EmphasisRight}}, {{.EmphasisLeft}}debug{{.EmphasisRight}}, {{.EmphasisLeft}}info{{.EmphasisRight}}, {{.EmphasisLeft}}warning{{.EmphasisRight}}, {{.EmphasisLeft}}error{{.EmphasisRight}}, and {{.EmphasisLeft}}fatal{{.EmphasisRight}}.
|
|
|
|
{{.EmphasisLeft}}log_format{{.EmphasisRight}}: Format of logging provided. Options are: {{.EmphasisLeft}}text{{.EmphasisRight}}, {{.EmphasisLeft}}json{{.EmphasisRight}}.
|
|
|
|
{{.EmphasisLeft}}privilege_file{{.EmphasisRight}}: "Path to a file to load and store users and grants. Defaults to {{.EmphasisLeft}}$doltcfg-dir/privileges.db{{.EmphasisRight}}. Will be created automatically if it doesn't exist.
|
|
|
|
{{.EmphasisLeft}}branch_control_file{{.EmphasisRight}}: Path to a file to load and store branch control permissions. Defaults to {{.EmphasisLeft}}$doltcfg-dir/branch_control.db{{.EmphasisRight}}. Will be created as needed.
|
|
|
|
{{.EmphasisLeft}}max_logged_query_len{{.EmphasisRight}}: If greater than zero, truncates query strings in logging to the number of characters given.
|
|
|
|
{{.EmphasisLeft}}behavior.read_only{{.EmphasisRight}}: If true database modification is disabled. Defaults to false.
|
|
|
|
{{.EmphasisLeft}}behavior.autocommit{{.EmphasisRight}}: If true every statement is committed automatically. Defaults to true. @@autocommit can also be specified in each session.
|
|
|
|
{{.EmphasisLeft}}behavior.dolt_transaction_commit{{.EmphasisRight}}: If true all SQL transaction commits will automatically create a Dolt commit, with a generated commit message. This is useful when a system working with Dolt wants to create versioned data, but doesn't want to directly use Dolt features such as dolt_commit().
|
|
|
|
{{.EmphasisLeft}}behavior.auto_gc_behavior.enabled{{.EmphasisRight}}: If true, garbage collection will run automatically in the background.
|
|
|
|
{{.EmphasisLeft}}listener.host{{.EmphasisRight}}: The host address that the server will run on. This may be {{.EmphasisLeft}}localhost{{.EmphasisRight}} or an IPv4 or IPv6 address
|
|
|
|
{{.EmphasisLeft}}listener.port{{.EmphasisRight}}: The port that the server should listen on
|
|
|
|
{{.EmphasisLeft}}listener.max_connections{{.EmphasisRight}}: The number of simultaneous connections that the server will accept
|
|
|
|
{{.EmphasisLeft}}listener.back_log{{.EmphasisRight}}: The number of simultaneous connections that the server will allow to block waiting for a connection before new connections result in immediate rejection. Default 50.
|
|
|
|
{{.EmphasisLeft}}listener.max_wait_connections_timeout{{.EmphasisRight}}: The maximum amount of time that a connection will block waiting for a connection before being rejected.
|
|
|
|
{{.EmphasisLeft}}listener.read_timeout_millis{{.EmphasisRight}}: The number of milliseconds that the server will wait for a read operation.
|
|
|
|
{{.EmphasisLeft}}listener.write_timeout_millis{{.EmphasisRight}}: The number of milliseconds that the server will wait for a write operation.
|
|
|
|
{{.EmphasisLeft}}listener.require_secure_transport{{.EmphasisRight}}: Boolean flag to turn on TLS/SSL transport.
|
|
|
|
{{.EmphasisLeft}}listener.require_client_cert{{.EmphasisRight}}: Boolean flag to require all connections present a certificate. This implies that all connections must be over TLS, so listener.tls_key and listener.tls_cert must also be set.
|
|
|
|
{{.EmphasisLeft}}listener.ca_cert{{.EmphasisRight}}: The path to a Certificate Authority (CA) certificate used to validate client certificates.
|
|
|
|
{{.EmphasisLeft}}listener.tls_cert{{.EmphasisRight}}: The path to the TLS certificate used for secure transport.
|
|
|
|
{{.EmphasisLeft}}listener.tls_key{{.EmphasisRight}}: The path to the TLS key used for secure transport.
|
|
|
|
{{.EmphasisLeft}}remotesapi.port{{.EmphasisRight}}: A port to listen for remote API operations on. If set to a positive integer, this server will accept connections from clients to clone, pull, etc. databases being served.
|
|
|
|
{{.EmphasisLeft}}remotesapi.read_only{{.EmphasisRight}}: Boolean flag which disables the ability to perform pushes against the server.
|
|
|
|
{{.EmphasisLeft}}system_variables{{.EmphasisRight}}: A map of system variable name to desired value for all system variable values to override.
|
|
|
|
{{.EmphasisLeft}}user_session_vars{{.EmphasisRight}}: A map of user name to a map of session variables to set on connection for each session.
|
|
|
|
{{.EmphasisLeft}}cluster{{.EmphasisRight}}: Settings related to running this server in a replicated cluster. For information on setting these values, see https://dolthub.com/docs/sql-reference/server/replication
|
|
|
|
If a config file is not provided many of these settings may be configured on the command line.`,
|
|
Synopsis: []string{
|
|
"--config {{.LessThan}}file{{.GreaterThan}}",
|
|
"[-H {{.LessThan}}host{{.GreaterThan}}] [-P {{.LessThan}}port{{.GreaterThan}}] [-t {{.LessThan}}timeout{{.GreaterThan}}] [-l {{.LessThan}}loglevel{{.GreaterThan}}] [--data-dir {{.LessThan}}directory{{.GreaterThan}}] [-r]",
|
|
},
|
|
}
|
|
|
|
type SqlServerCmd struct {
|
|
VersionStr string
|
|
}
|
|
|
|
// Name is returns the name of the Dolt cli command. This is what is used on the command line to invoke the command
|
|
func (cmd SqlServerCmd) Name() string {
|
|
return "sql-server"
|
|
}
|
|
|
|
// Description returns a description of the command
|
|
func (cmd SqlServerCmd) Description() string {
|
|
return sqlServerDocs.ShortDesc
|
|
}
|
|
|
|
func (cmd SqlServerCmd) Docs() *cli.CommandDocumentation {
|
|
ap := cmd.ArgParser()
|
|
return cli.NewCommandDocumentation(sqlServerDocs, ap)
|
|
}
|
|
|
|
func (cmd SqlServerCmd) ArgParser() *argparser.ArgParser {
|
|
return cmd.ArgParserWithName(cmd.Name())
|
|
}
|
|
|
|
func (cmd SqlServerCmd) ArgParserWithName(name string) *argparser.ArgParser {
|
|
serverConfig := DefaultCommandLineServerConfig()
|
|
|
|
ap := argparser.NewArgParserWithVariableArgs(name)
|
|
ap.SupportsString(configFileFlag, "", "file", "When provided configuration is taken from the yaml config file and all command line parameters are ignored.")
|
|
ap.SupportsString(hostFlag, "H", "host address", fmt.Sprintf("Defines the host address that the server will run on. Defaults to `%v`.", serverConfig.Host()))
|
|
ap.SupportsUint(portFlag, "P", "port", fmt.Sprintf("Defines the port that the server will run on. Defaults to `%v`.", serverConfig.Port()))
|
|
// TODO: After December 2025, remove the deprecated user/password arguments completely from the command line
|
|
// and config file options for dolt sql-server.
|
|
ap.SupportsString(commands.UserFlag, "u", "user", "This option is no longer supported. Instead, you can create users using CREATE USER and GRANT SQL statements.")
|
|
ap.SupportsFlag(skipRootUserInitialization, "", "Skips the automatic creation of a default root super user on the first launch of a SQL server.")
|
|
ap.SupportsString("password", "p", "password", "This option is no longer supported. Instead, you can create users using CREATE USER and GRANT SQL statements.")
|
|
ap.SupportsInt(timeoutFlag, "t", "connection timeout", fmt.Sprintf("Defines the timeout, in seconds, used for connections\nA value of `0` represents an infinite timeout. Defaults to `%v`.", serverConfig.ReadTimeout()))
|
|
ap.SupportsFlag(readonlyFlag, "r", "Disable modification of the database.")
|
|
ap.SupportsString(logLevelFlag, "l", "log level", fmt.Sprintf("Defines the level of logging provided\nOptions are: `trace`, `debug`, `info`, `warning`, `error`, `fatal`. Defaults to `%v`.", serverConfig.LogLevel()))
|
|
ap.SupportsString(logFormatFlag, "f", "log format", fmt.Sprintf("Defines the output format of the server log\nOptions are: `text`, `json`. Defaults to `%v`.", serverConfig.LogFormat()))
|
|
ap.SupportsString(commands.DataDirFlag, "", "directory", "Defines a directory to find databases to serve. Defaults to the current directory.")
|
|
ap.SupportsString(commands.MultiDBDirFlag, "", "directory", "Deprecated, use `--data-dir` instead.")
|
|
ap.SupportsString(commands.CfgDirFlag, "", "directory", "Defines a directory that contains non-database storage for dolt. Defaults to `$data-dir/.doltcfg`. Will be created automatically as needed.")
|
|
ap.SupportsFlag(noAutoCommitFlag, "", "Set @@autocommit = off for the server.")
|
|
ap.SupportsInt(queryParallelismFlag, "", "num-go-routines", "Deprecated, no effect in current versions of Dolt")
|
|
ap.SupportsInt(maxConnectionsFlag, "", "max-connections", fmt.Sprintf("Set the number of connections handled by the server. Defaults to `%d`.", serverConfig.MaxConnections()))
|
|
ap.SupportsInt(maxWaitConnectionsFlag, "", "back-log", fmt.Sprintf("Set the number of connections that can block waiting for a connection before new connections are rejected. Defaults to `%d`.", serverConfig.MaxWaitConnections()))
|
|
ap.SupportsString(maxWaitConsTimeoutFlag, "", "max-connections-timeout", fmt.Sprintf("Set the maximum duration that a connection will block waiting for a connection before being rejected. Defaults to `%v`.", serverConfig.MaxWaitConnectionsTimeout()))
|
|
ap.SupportsString(commands.PrivsFilePathFlag, "", "privilege file", "Path to a file to load and store users and grants. Defaults to `$doltcfg-dir/privileges.db`. Will be created as needed.")
|
|
ap.SupportsString(commands.BranchCtrlPathFlag, "", "branch control file", "Path to a file to load and store branch control permissions. Defaults to `$doltcfg-dir/branch_control.db`. Will be created as needed.")
|
|
ap.SupportsString(allowCleartextPasswordsFlag, "", "allow-cleartext-passwords", "Allows use of cleartext passwords. Defaults to false.")
|
|
ap.SupportsOptionalString(socketFlag, "", "socket file", "Path for the unix socket file. Defaults to '/tmp/mysql.sock'.")
|
|
ap.SupportsUint(remotesapiPortFlag, "", "remotesapi port", "Sets the port for a server which can expose the databases in this sql-server over remotesapi, so that clients can clone or pull from this server.")
|
|
ap.SupportsFlag(remotesapiReadOnlyFlag, "", "Disable writes to the sql-server via the push operations. SQL writes are unaffected by this setting.")
|
|
ap.SupportsString(goldenMysqlConn, "", "mysql connection string", "Provides a connection string to a MySQL instance to be used to validate query results")
|
|
ap.SupportsString(eventSchedulerStatus, "", "status", "Determines whether the Event Scheduler is enabled and running on the server. It has one of the following values: 'ON', 'OFF' or 'DISABLED'.")
|
|
// Start an MCP HTTP server connected to this sql-server on the given port
|
|
ap.SupportsUint(mcpPortFlag, "", "port", "If provided, runs a Dolt MCP HTTP server on this port alongside the sql-server.")
|
|
// MCP SQL credentials (user required when MCP enabled; password optional)
|
|
ap.SupportsString(mcpUserFlag, "", "user", "SQL user for MCP to connect as (required when --mcp-port is set).")
|
|
ap.SupportsString(mcpPasswordFlag, "", "password", "Optional SQL password for MCP to connect with (requires --mcp-user).")
|
|
ap.SupportsString(mcpDatabaseFlag, "", "database", "Optional SQL database name MCP should connect to (requires --mcp-port and --mcp-user).")
|
|
return ap
|
|
}
|
|
|
|
// EventType returns the type of the event to log
|
|
func (cmd SqlServerCmd) EventType() eventsapi.ClientEventType {
|
|
return eventsapi.ClientEventType_SQL_SERVER
|
|
}
|
|
|
|
// RequiresRepo indicates that this command does not have to be run from within a dolt data repository directory.
|
|
// In this case it is because this command supports the dataDirFlag which can pass in a directory. In the event that
|
|
// that parameter is not provided there is additional error handling within this command to make sure that this was in
|
|
// fact run from within a dolt data repository directory.
|
|
func (cmd SqlServerCmd) RequiresRepo() bool {
|
|
return false
|
|
}
|
|
|
|
// Exec executes the command
|
|
func (cmd SqlServerCmd) Exec(ctx context.Context, commandStr string, args []string, dEnv *env.DoltEnv, cliCtx cli.CliContext) int {
|
|
controller := svcs.NewController()
|
|
newCtx, cancelF := context.WithCancel(context.Background())
|
|
go func() {
|
|
// Here we only forward along the SIGINT if the server starts
|
|
// up successfully. If the service does not start up
|
|
// successfully, or if WaitForStart() blocks indefinitely, then
|
|
// startServer() should have returned an error and we do not
|
|
// need to Stop the running server or deal with our canceled
|
|
// parent context.
|
|
if controller.WaitForStart() == nil {
|
|
<-ctx.Done()
|
|
controller.Stop()
|
|
cancelF()
|
|
}
|
|
}()
|
|
|
|
err := StartServer(newCtx, cmd.VersionStr, commandStr, args, dEnv, cliCtx.WorkingDir(), controller)
|
|
if err != nil {
|
|
cli.Println(color.RedString(err.Error()))
|
|
return 1
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
func validateSqlServerArgs(apr *argparser.ArgParseResults) error {
|
|
if apr.NArg() > 0 {
|
|
args := strings.Join(apr.Args, ", ")
|
|
return fmt.Errorf("error: sql-server does not take positional arguments, but found %d: %s", apr.NArg(), args)
|
|
}
|
|
_, multiDbDir := apr.GetValue(commands.MultiDBDirFlag)
|
|
if multiDbDir {
|
|
cli.PrintErrln("WARNING: --multi-db-dir is deprecated, use --data-dir instead")
|
|
}
|
|
_, userSpecified := apr.GetValue(commands.UserFlag)
|
|
if userSpecified {
|
|
return fmt.Errorf("ERROR: --user and --password have been removed from the sql-server command. " +
|
|
"Create users explicitly with CREATE USER and GRANT statements instead.")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// StartServer starts the sql server with the controller provided and blocks until the server is stopped.
|
|
func StartServer(ctx context.Context, versionStr, commandStr string, args []string, dEnv *env.DoltEnv, cwd filesys.Filesys, controller *svcs.Controller) error {
|
|
ap := SqlServerCmd{}.ArgParser()
|
|
help, _ := cli.HelpAndUsagePrinters(cli.CommandDocsForCommandString(commandStr, sqlServerDocs, ap))
|
|
apr := cli.ParseArgsOrDie(ap, args, help)
|
|
serverConfig, err := ServerConfigFromArgs(apr, dEnv, cwd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Optional MCP HTTP port
|
|
var mcpPortPtr *int
|
|
if mp, ok := apr.GetInt(mcpPortFlag); ok {
|
|
mcpPort := mp
|
|
mcpPortPtr = &mcpPort
|
|
}
|
|
|
|
// Optional MCP SQL user
|
|
var mcpUserPtr *string
|
|
if mu, ok := apr.GetValue(mcpUserFlag); ok {
|
|
user := mu
|
|
mcpUserPtr = &user
|
|
}
|
|
// Optional MCP SQL password
|
|
var mcpPasswordPtr *string
|
|
if mpw, ok := apr.GetValue(mcpPasswordFlag); ok {
|
|
pw := mpw
|
|
mcpPasswordPtr = &pw
|
|
}
|
|
// Optional MCP SQL database
|
|
var mcpDatabasePtr *string
|
|
if mdb, ok := apr.GetValue(mcpDatabaseFlag); ok {
|
|
db := mdb
|
|
mcpDatabasePtr = &db
|
|
}
|
|
|
|
// Validate and prepare MCP options in dedicated helper
|
|
// Fill MCP options from YAML config (if present) for any unset CLI values.
|
|
// CLI always has precedence over config file.
|
|
|
|
// Prefer CLI values; fall back to ServerConfig interface (e.g., YAML config)
|
|
if mcpPortPtr == nil {
|
|
mcpPortPtr = serverConfig.MCPPort()
|
|
}
|
|
if mcpUserPtr == nil {
|
|
mcpUserPtr = serverConfig.MCPUser()
|
|
}
|
|
if mcpPasswordPtr == nil {
|
|
mcpPasswordPtr = serverConfig.MCPPassword()
|
|
}
|
|
if mcpDatabasePtr == nil {
|
|
mcpDatabasePtr = serverConfig.MCPDatabase()
|
|
}
|
|
if err := validateAndPrepareMCP(serverConfig, mcpPortPtr, mcpUserPtr, mcpPasswordPtr, mcpDatabasePtr); err != nil {
|
|
return err
|
|
}
|
|
|
|
err = generateYamlConfigIfNone(ap, help, args, dEnv, serverConfig)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = servercfg.ApplySystemVariables(nil, serverConfig, sql.SystemVariables)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cli.Printf("Starting server with Config %v\n", servercfg.ConfigInfo(serverConfig))
|
|
|
|
// Build MCP config if any MCP-related options are present
|
|
var mcpCfg *MCPConfig
|
|
if mcpPortPtr != nil || (mcpUserPtr != nil && *mcpUserPtr != "") || (mcpPasswordPtr != nil && *mcpPasswordPtr != "") || (mcpDatabasePtr != nil && *mcpDatabasePtr != "") {
|
|
mcpCfg = &MCPConfig{
|
|
Port: mcpPortPtr,
|
|
User: mcpUserPtr,
|
|
Password: mcpPasswordPtr,
|
|
Database: mcpDatabasePtr,
|
|
}
|
|
}
|
|
|
|
skipRootUserInitialization := apr.Contains(skipRootUserInitialization)
|
|
|
|
startError, closeError := Serve(ctx, &Config{
|
|
Version: versionStr,
|
|
ServerConfig: serverConfig,
|
|
Controller: controller,
|
|
DoltEnv: dEnv,
|
|
SkipRootUserInit: skipRootUserInitialization,
|
|
MCP: mcpCfg,
|
|
})
|
|
if startError != nil {
|
|
return startError
|
|
}
|
|
if closeError != nil {
|
|
return closeError
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetDataDirPreStart returns the data dir to use for the process. This is called early in the bootstrapping of the process
|
|
// to ensure that we know the data dir early. This function first parses the args for the --data-dir flag,
|
|
// then attempts to find it in the server's yaml config file if it was specified.
|
|
//
|
|
// The returned value is non-empty only if we found a data dir. The string will be an absolute path to the data dir. An
|
|
// empty string indicates that there was no data dir specified, and the caller should determine the data dir.
|
|
//
|
|
// If the --data-dir flag is specified in the command line, and the config file, an error is returned.
|
|
func GetDataDirPreStart(fs filesys.Filesys, args []string) (string, error) {
|
|
ap := SqlServerCmd{}.ArgParser()
|
|
apr, err := cli.ParseArgs(ap, args, nil)
|
|
if err != nil {
|
|
// Parse failure at this stage is ignored. We'll handle it during command execution, to be more consistent with
|
|
// other commands.
|
|
return "", nil
|
|
}
|
|
|
|
cliDataDir, hasDataDirCliArg := apr.GetValue(commands.DataDirFlag)
|
|
if hasDataDirCliArg {
|
|
cliDataDir, err = fs.Abs(cliDataDir)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
var cfgDataDir string
|
|
confArg, hasConfArg := apr.GetValue(configFileFlag)
|
|
if hasConfArg {
|
|
reader := DoltServerConfigReader{}
|
|
cfg, err := reader.ReadConfigFile(fs, confArg)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if cfg.DataDir() != "" {
|
|
cfgDataDir, err = fs.Abs(cfg.DataDir())
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
}
|
|
|
|
if cfgDataDir != "" && cliDataDir != "" {
|
|
return "", errors.New("--data-dir specified in both config file and command line. Please specify only one.")
|
|
}
|
|
if cfgDataDir != "" {
|
|
return cfgDataDir, nil
|
|
}
|
|
if cliDataDir != "" {
|
|
return cliDataDir, nil
|
|
}
|
|
return "", nil
|
|
}
|
|
|
|
// ServerConfigFromArgs returns a ServerConfig from the given args
|
|
func ServerConfigFromArgs(apr *argparser.ArgParseResults, dEnv *env.DoltEnv, cwd filesys.Filesys) (servercfg.ServerConfig, error) {
|
|
return ServerConfigFromArgsWithReader(apr, dEnv, cwd, DoltServerConfigReader{})
|
|
}
|
|
|
|
// ServerConfigFromArgsWithReader returns a ServerConfig from the given args, using the provided ServerConfigReader
|
|
func ServerConfigFromArgsWithReader(
|
|
apr *argparser.ArgParseResults,
|
|
dEnv *env.DoltEnv,
|
|
cwd filesys.Filesys,
|
|
reader ServerConfigReader,
|
|
) (servercfg.ServerConfig, error) {
|
|
if err := validateSqlServerArgs(apr); err != nil {
|
|
cli.PrintErrln(color.RedString(err.Error()))
|
|
return nil, err
|
|
}
|
|
|
|
dataDir, err := dEnv.FS.Abs("")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
serverConfig, err := getServerConfig(cwd, apr, dataDir, reader)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("bad configuration: %w", err)
|
|
}
|
|
|
|
if err = setupDoltConfig(dEnv, cwd, apr, serverConfig); err != nil {
|
|
return nil, fmt.Errorf("bad configuration: %w", err)
|
|
}
|
|
|
|
return serverConfig, nil
|
|
}
|
|
|
|
// getServerConfig returns ServerConfig that is set either from yaml file if given, if not it is set with values defined
|
|
// on command line. Server config variables not defined are set to default values.
|
|
func getServerConfig(cwdFS filesys.Filesys, apr *argparser.ArgParseResults, dataDirOverride string, reader ServerConfigReader) (servercfg.ServerConfig, error) {
|
|
cfgFile, ok := apr.GetValue(configFileFlag)
|
|
if !ok {
|
|
return reader.ReadConfigArgs(apr, dataDirOverride)
|
|
}
|
|
|
|
cfg, err := reader.ReadConfigFile(cwdFS, cfgFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if connStr, ok := apr.GetValue(goldenMysqlConn); ok {
|
|
if yamlCfg, ok := cfg.(servercfg.YAMLConfig); ok {
|
|
cli.Println(connStr)
|
|
yamlCfg.GoldenMysqlConn = &connStr
|
|
}
|
|
}
|
|
|
|
if cfg.UserIsSpecified() {
|
|
logrus.Warn("user and password are no longer supported in sql-server configuration files." +
|
|
"Use CREATE USER and GRANT statements to manage user accounts.")
|
|
}
|
|
|
|
return cfg, nil
|
|
}
|
|
|
|
// GetClientConfig returns configuration which is suitable for a client to use. The fact that it returns a ServerConfig
|
|
// is a little confusing, but it is because the client and server use the same configuration struct. The main difference
|
|
// between this method and getServerConfig is that this method required a cli.UserPassword argument. It is created by
|
|
// prompting the user, and we don't want the server to follow that code path.
|
|
func GetClientConfig(cwdFS filesys.Filesys, creds *cli.UserPassword, apr *argparser.ArgParseResults) (servercfg.ServerConfig, error) {
|
|
cfgFile, hasCfgFile := apr.GetValue(configFileFlag)
|
|
|
|
if !hasCfgFile {
|
|
return NewCommandLineConfig(creds, apr, "")
|
|
}
|
|
|
|
var yamlCfg servercfg.YAMLConfig
|
|
cfg, err := servercfg.YamlConfigFromFile(cwdFS, cfgFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
yamlCfg = cfg.(servercfg.YAMLConfig)
|
|
|
|
// if command line user argument was given, replace yaml's user and password
|
|
if creds.Specified {
|
|
yamlCfg.UserConfig.Name = &creds.Username
|
|
yamlCfg.UserConfig.Password = &creds.Password
|
|
}
|
|
|
|
if connStr, ok := apr.GetValue(goldenMysqlConn); ok {
|
|
cli.Println(connStr)
|
|
yamlCfg.GoldenMysqlConn = &connStr
|
|
}
|
|
|
|
return yamlCfg, nil
|
|
}
|
|
|
|
// setupDoltConfig updates the given server config with where to create .doltcfg directory
|
|
func setupDoltConfig(dEnv *env.DoltEnv, cwd filesys.Filesys, apr *argparser.ArgParseResults, config servercfg.ServerConfig) error {
|
|
if _, ok := apr.GetValue(configFileFlag); ok {
|
|
return nil
|
|
}
|
|
|
|
serverConfig := config.(*commandLineServerConfig)
|
|
|
|
_, dataDirFlag1 := apr.GetValue(commands.MultiDBDirFlag)
|
|
_, dataDirFlag2 := apr.GetValue(commands.DataDirFlag)
|
|
dataDirSpecified := dataDirFlag1 || dataDirFlag2
|
|
|
|
var cfgDirPath string
|
|
dataDir := serverConfig.DataDir()
|
|
cfgDir, cfgDirSpecified := apr.GetValue(commands.CfgDirFlag)
|
|
if cfgDirSpecified {
|
|
if !filepath.IsAbs(cfgDir) {
|
|
var err error
|
|
cfgDir, err = cwd.Abs(cfgDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
cfgDirPath = cfgDir
|
|
} else if dataDirSpecified {
|
|
cfgDirPath = filepath.Join(dataDir, commands.DefaultCfgDirName)
|
|
} else {
|
|
// Look in parent directory for doltcfg
|
|
parentDirCfg := filepath.Join("..", commands.DefaultCfgDirName)
|
|
parentExists, isDir := dEnv.FS.Exists(parentDirCfg)
|
|
parentDirExists := parentExists && isDir
|
|
|
|
// Look in data directory (which is necessarily current directory) for doltcfg
|
|
currDirCfg := filepath.Join(dataDir, commands.DefaultCfgDirName)
|
|
currExists, isDir := dEnv.FS.Exists(currDirCfg)
|
|
currDirExists := currExists && isDir
|
|
|
|
// Error if both current and parent exist
|
|
if currDirExists && parentDirExists {
|
|
p1, err := dEnv.FS.Abs(cfgDirPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p2, err := dEnv.FS.Abs(parentDirCfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return commands.ErrMultipleDoltCfgDirs.New(p1, p2)
|
|
}
|
|
|
|
// Assign the one that exists, defaults to current if neither exist
|
|
if parentDirExists {
|
|
cfgDirPath = parentDirCfg
|
|
} else {
|
|
cfgDirPath = currDirCfg
|
|
}
|
|
}
|
|
serverConfig.withCfgDir(cfgDirPath)
|
|
|
|
if cfgDirSpecified {
|
|
serverConfig.valuesSet[servercfg.CfgDirKey] = struct{}{}
|
|
}
|
|
|
|
if dataDirSpecified {
|
|
serverConfig.valuesSet[servercfg.DataDirKey] = struct{}{}
|
|
}
|
|
|
|
if privsFp, ok := apr.GetValue(commands.PrivsFilePathFlag); ok {
|
|
serverConfig.withPrivilegeFilePath(privsFp)
|
|
serverConfig.valuesSet[servercfg.PrivilegeFilePathKey] = struct{}{}
|
|
} else {
|
|
path, err := dEnv.FS.Abs(filepath.Join(cfgDirPath, commands.DefaultPrivsName))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
serverConfig.withPrivilegeFilePath(path)
|
|
}
|
|
|
|
if branchControlFilePath, ok := apr.GetValue(commands.BranchCtrlPathFlag); ok {
|
|
serverConfig.withBranchControlFilePath(branchControlFilePath)
|
|
serverConfig.valuesSet[servercfg.BranchControlFilePathKey] = struct{}{}
|
|
} else {
|
|
path, err := dEnv.FS.Abs(filepath.Join(cfgDirPath, commands.DefaultBranchCtrlName))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
serverConfig.withBranchControlFilePath(path)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// generateYamlConfigIfNone creates a YAML config file in the database directory if one is not specified in the args
|
|
// and one doesn't already exist in the database directory. The fields of the YAML file are generated using the values
|
|
// in serverConfig that were explicitly set by the command line args.
|
|
func generateYamlConfigIfNone(
|
|
ap *argparser.ArgParser,
|
|
help cli.UsagePrinter,
|
|
args []string,
|
|
dEnv *env.DoltEnv,
|
|
serverConfig servercfg.ServerConfig) error {
|
|
const yamlConfigName = "config.yaml"
|
|
|
|
apr := cli.ParseArgsOrDie(ap, args, help)
|
|
|
|
if apr.Contains(configFileFlag) {
|
|
return nil
|
|
}
|
|
|
|
path := filepath.Join(serverConfig.DataDir(), yamlConfigName)
|
|
exists, _ := dEnv.FS.Exists(path)
|
|
if exists {
|
|
return nil
|
|
}
|
|
|
|
generatedYaml := generateYamlConfig(serverConfig)
|
|
|
|
err := dEnv.FS.WriteFile(path, []byte(generatedYaml), os.ModePerm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// generateYamlConfig returns a YAML string containing the fields in serverConfig that
|
|
// were explicitly set by the command line args, along with commented-out placeholders for any
|
|
// fields that were not explicitly set by the command line args.
|
|
func generateYamlConfig(serverConfig servercfg.ServerConfig) string {
|
|
yamlConfig := servercfg.ServerConfigSetValuesAsYAMLConfig(serverConfig)
|
|
|
|
return `# Dolt SQL server configuration
|
|
#
|
|
# Uncomment and edit lines as necessary to modify your configuration.
|
|
# Full documentation: https://dolthub.com/docs/sql-reference/server/configuration
|
|
#` + "\n\n" + yamlConfig.VerboseString()
|
|
}
|