Skip to content

Commit

Permalink
Merge pull request #7152 from dolthub/macneale4/sql-server-push
Browse files Browse the repository at this point in the history
This change alters the remoteapi server behavior running in a sql-server to allow people to push to it, with some caveats:

- User authenticates using the DOLT_REMOTE_PASSWORD environment variable, and --user passed as a stored procedure or CLI flag
- User has SuperUser privileges, due to the nature of the root update
- The update of the server will be performed only if there are no working set changes and the commit being pushed is a fast forward.
- The --force option will overwrite any changes on the target, including the working set changes

Breaking Changes:
 - The shortened -u option was dropped from the fetch and pull commands and stored procedures. This was done to remove naming conflicts in commands as push has a -u option already which matches git.
  • Loading branch information
macneale4 authored Dec 22, 2023
2 parents 0c76ff6 + ed01719 commit 0ef3bc8
Show file tree
Hide file tree
Showing 26 changed files with 851 additions and 261 deletions.
5 changes: 3 additions & 2 deletions go/cmd/dolt/cli/arg_parser_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ func CreateMergeArgParser() *argparser.ArgParser {

func CreatePushArgParser() *argparser.ArgParser {
ap := argparser.NewArgParserWithVariableArgs("push")
ap.SupportsString(UserFlag, "", "user", "User name to use when authenticating with the remote. Gets password from the environment variable {{.EmphasisLeft}}DOLT_REMOTE_PASSWORD{{.EmphasisRight}}.")
ap.SupportsFlag(SetUpstreamFlag, "u", "For every branch that is up to date or successfully pushed, add upstream (tracking) reference, used by argument-less {{.EmphasisLeft}}dolt pull{{.EmphasisRight}} and other commands.")
ap.SupportsFlag(ForceFlag, "f", "Update the remote with local history, overwriting any conflicting history in the remote.")
ap.SupportsFlag(AllFlag, "", "Push all branches.")
Expand Down Expand Up @@ -167,7 +168,7 @@ func CreateCherryPickArgParser() *argparser.ArgParser {

func CreateFetchArgParser() *argparser.ArgParser {
ap := argparser.NewArgParserWithVariableArgs("fetch")
ap.SupportsString(UserFlag, "u", "user", "User name to use when authenticating with the remote. Gets password from the environment variable {{.EmphasisLeft}}DOLT_REMOTE_PASSWORD{{.EmphasisRight}}.")
ap.SupportsString(UserFlag, "", "user", "User name to use when authenticating with the remote. Gets password from the environment variable {{.EmphasisLeft}}DOLT_REMOTE_PASSWORD{{.EmphasisRight}}.")
ap.SupportsFlag(PruneFlag, "p", "After fetching, remove any remote-tracking references that don't exist on the remote.")
ap.SupportsFlag(SilentFlag, "", "Suppress progress information.")
return ap
Expand All @@ -192,7 +193,7 @@ func CreatePullArgParser() *argparser.ArgParser {
ap.SupportsFlag(CommitFlag, "", "Perform the merge and commit the result. This is the default option, but can be overridden with the --no-commit flag. Note that this option does not affect fast-forward merges, which don't create a new merge commit, and if any merge conflicts or constraint violations are detected, no commit will be attempted.")
ap.SupportsFlag(NoCommitFlag, "", "Perform the merge and stop just before creating a merge commit. Note this will not prevent a fast-forward merge; use the --no-ff arg together with the --no-commit arg to prevent both fast-forwards and merge commits.")
ap.SupportsFlag(NoEditFlag, "", "Use an auto-generated commit message when creating a merge commit. The default for interactive CLI sessions is to open an editor.")
ap.SupportsString(UserFlag, "u", "user", "User name to use when authenticating with the remote. Gets password from the environment variable {{.EmphasisLeft}}DOLT_REMOTE_PASSWORD{{.EmphasisRight}}.")
ap.SupportsString(UserFlag, "", "user", "User name to use when authenticating with the remote. Gets password from the environment variable {{.EmphasisLeft}}DOLT_REMOTE_PASSWORD{{.EmphasisRight}}.")
ap.SupportsFlag(SilentFlag, "", "Suppress progress information.")
return ap
}
Expand Down
6 changes: 6 additions & 0 deletions go/cmd/dolt/commands/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@ func constructInterpolatedDoltPushQuery(apr *argparser.ArgParseResults) (string,
var params []interface{}
var args []string

if user, hasUser := apr.GetValue(cli.UserFlag); hasUser {
args = append(args, "'--user'")
args = append(args, "?")
params = append(params, user)
}

if setUpstream := apr.Contains(cli.SetUpstreamFlag); setUpstream {
args = append(args, "'--set-upstream'")
}
Expand Down
19 changes: 16 additions & 3 deletions go/cmd/dolt/commands/sqlserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,10 +392,16 @@ func ConfigureServices(
remoteSrv.state.Swap(svcs.ServiceState_Init)

port := *serverConfig.RemotesapiPort()

apiReadOnly := false
if serverConfig.RemotesapiReadOnly() != nil {
apiReadOnly = *serverConfig.RemotesapiReadOnly()
}

listenaddr := fmt.Sprintf(":%d", port)
args, err := sqle.RemoteSrvServerArgs(sqlEngine.NewDefaultContext, remotesrv.ServerArgs{
Logger: logrus.NewEntry(lgr),
ReadOnly: true,
ReadOnly: apiReadOnly || serverConfig.ReadOnly(),
HttpListenAddr: listenaddr,
GrpcListenAddr: listenaddr,
})
Expand Down Expand Up @@ -710,19 +716,26 @@ func (r *remotesapiAuth) ApiAuthenticate(ctx context.Context) (context.Context,
return updatedCtx, nil
}

func (r *remotesapiAuth) ApiAuthorize(ctx context.Context) (bool, error) {
func (r *remotesapiAuth) ApiAuthorize(ctx context.Context, superUserRequired bool) (bool, error) {
sqlCtx, ok := ctx.Value(ApiSqleContextKey).(*sql.Context)
if !ok {
return false, fmt.Errorf("Runtime error: could not get SQL context from context")
}

privOp := sql.NewDynamicPrivilegedOperation(plan.DynamicPrivilege_CloneAdmin)
if superUserRequired {
database := sqlCtx.GetCurrentDatabase()
subject := sql.PrivilegeCheckSubject{Database: database}
privOp = sql.NewPrivilegedOperation(subject, sql.PrivilegeType_Super)
}

authorized := r.rawDb.UserHasPrivileges(sqlCtx, privOp)

if !authorized {
if superUserRequired {
return false, fmt.Errorf("API Authorization Failure: %s has not been granted SuperUser access", sqlCtx.Session.Client().User)
}
return false, fmt.Errorf("API Authorization Failure: %s has not been granted CLONE_ADMIN access", sqlCtx.Session.Client().User)

}
return true, nil
}
Expand Down
12 changes: 12 additions & 0 deletions go/cmd/dolt/commands/sqlserver/serverconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@ type ServerConfig interface {
// as a dolt remote for things like `clone`, `fetch` and read
// replication.
RemotesapiPort() *int
// RemotesapiReadOnly is true if the remotesapi interface should be read only.
RemotesapiReadOnly() *bool
// ClusterConfig is the configuration for clustering in this sql-server.
ClusterConfig() cluster.Config
// EventSchedulerStatus is the configuration for enabling or disabling the event scheduler in this server.
Expand Down Expand Up @@ -201,6 +203,7 @@ type commandLineServerConfig struct {
allowCleartextPasswords bool
socket string
remotesapiPort *int
remotesapiReadOnly *bool
goldenMysqlConn string
eventSchedulerStatus string
}
Expand Down Expand Up @@ -326,6 +329,10 @@ func (cfg *commandLineServerConfig) RemotesapiPort() *int {
return cfg.remotesapiPort
}

func (cfg *commandLineServerConfig) RemotesapiReadOnly() *bool {
return cfg.remotesapiReadOnly
}

func (cfg *commandLineServerConfig) ClusterConfig() cluster.Config {
return nil
}
Expand Down Expand Up @@ -475,6 +482,11 @@ func (cfg *commandLineServerConfig) WithRemotesapiPort(port *int) *commandLineSe
return cfg
}

func (cfs *commandLineServerConfig) WithRemotesapiReadOnly(readonly *bool) *commandLineServerConfig {
cfs.remotesapiReadOnly = readonly
return cfs
}

func (cfg *commandLineServerConfig) goldenMysqlConnectionString() string {
return cfg.goldenMysqlConn
}
Expand Down
10 changes: 10 additions & 0 deletions go/cmd/dolt/commands/sqlserver/sqlserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const (
allowCleartextPasswordsFlag = "allow-cleartext-passwords"
socketFlag = "socket"
remotesapiPortFlag = "remotesapi-port"
remotesapiReadOnlyFlag = "remotesapi-readonly"
goldenMysqlConn = "golden"
eventSchedulerStatus = "event-scheduler"
)
Expand Down Expand Up @@ -109,6 +110,8 @@ SUPPORTED CONFIG FILE FIELDS:
{{.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}}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://docs.dolthub.com/sql-reference/server/replication
Expand Down Expand Up @@ -167,6 +170,7 @@ func (cmd SqlServerCmd) ArgParserWithName(name string) *argparser.ArgParser {
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'.")
return ap
Expand Down Expand Up @@ -444,6 +448,10 @@ func getCommandLineConfig(creds *cli.UserPassword, apr *argparser.ArgParseResult
if port, ok := apr.GetInt(remotesapiPortFlag); ok {
config.WithRemotesapiPort(&port)
}
if apr.Contains(remotesapiReadOnlyFlag) {
val := true
config.WithRemotesapiReadOnly(&val)
}

if persistenceBehavior, ok := apr.GetValue(persistenceBehaviorFlag); ok {
config.withPersistenceBehavior(persistenceBehavior)
Expand All @@ -470,6 +478,8 @@ func getCommandLineConfig(creds *cli.UserPassword, apr *argparser.ArgParseResult

if _, ok := apr.GetValue(readonlyFlag); ok {
config.withReadOnly(true)
val := true
config.WithRemotesapiReadOnly(&val)
}

if logLevel, ok := apr.GetValue(logLevelFlag); ok {
Expand Down
14 changes: 12 additions & 2 deletions go/cmd/dolt/commands/sqlserver/yaml_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,18 @@ type MetricsYAMLConfig struct {
}

type RemotesapiYAMLConfig struct {
Port_ *int `yaml:"port"`
Port_ *int `yaml:"port"`
ReadOnly_ *bool `yaml:"read_only" minver:"1.30.0"`
}

func (r RemotesapiYAMLConfig) Port() int {
return *r.Port_
}

func (r RemotesapiYAMLConfig) ReadOnly() bool {
return *r.ReadOnly_
}

type UserSessionVars struct {
Name string `yaml:"name"`
Vars map[string]string `yaml:"vars"`
Expand Down Expand Up @@ -214,7 +219,8 @@ func ServerConfigAsYAMLConfig(cfg ServerConfig) YAMLConfig {
Port: intPtr(cfg.MetricsPort()),
},
RemotesapiConfig: RemotesapiYAMLConfig{
Port_: cfg.RemotesapiPort(),
Port_: cfg.RemotesapiPort(),
ReadOnly_: cfg.RemotesapiReadOnly(),
},
ClusterCfg: clusterConfigAsYAMLConfig(cfg.ClusterConfig()),
PrivilegeFile: strPtr(cfg.PrivilegeFilePath()),
Expand Down Expand Up @@ -413,6 +419,10 @@ func (cfg YAMLConfig) RemotesapiPort() *int {
return cfg.RemotesapiConfig.Port_
}

func (cfg YAMLConfig) RemotesapiReadOnly() *bool {
return cfg.RemotesapiConfig.ReadOnly_
}

// PrivilegeFilePath returns the path to the file which contains all needed privilege information in the form of a
// JSON string.
func (cfg YAMLConfig) PrivilegeFilePath() string {
Expand Down
4 changes: 2 additions & 2 deletions go/libraries/doltcore/doltdb/commit_hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func (ph *PushOnWriteHook) Execute(ctx context.Context, ds datas.Dataset, db dat
func pushDataset(ctx context.Context, destDB, srcDB datas.Database, ds datas.Dataset, tmpDir string) error {
addr, ok := ds.MaybeHeadAddr()
if !ok {
_, err := destDB.Delete(ctx, ds)
_, err := destDB.Delete(ctx, ds, "")
return err
}

Expand All @@ -75,7 +75,7 @@ func pushDataset(ctx context.Context, destDB, srcDB datas.Database, ds datas.Dat
return err
}

_, err = destDB.SetHead(ctx, ds, addr)
_, err = destDB.SetHead(ctx, ds, addr, "")
return err
}

Expand Down
Loading

0 comments on commit 0ef3bc8

Please sign in to comment.