diff --git a/lib/client/api.go b/lib/client/api.go index 1e7cf563d477f..c00f90b6058b7 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -375,6 +375,16 @@ type Config struct { // MockHeadlessLogin is used in tests for mocking the Headless login response. MockHeadlessLogin SSHLoginFunc + // OverrideMySQLOptionFilePath overrides the MySQL option file path to use. + // Useful in parallel tests so they don't all use the default path in the + // user home dir. + OverrideMySQLOptionFilePath string + + // OverridePostgresServiceFilePath overrides the Postgres service file path. + // Useful in parallel tests so they don't all use the default path in the + // user home dir. + OverridePostgresServiceFilePath string + // HomePath is where tsh stores profiles HomePath string diff --git a/lib/client/db/dbcmd/dbcmd.go b/lib/client/db/dbcmd/dbcmd.go index 0a9ad359941c0..9825a57ba1a39 100644 --- a/lib/client/db/dbcmd/dbcmd.go +++ b/lib/client/db/dbcmd/dbcmd.go @@ -330,7 +330,7 @@ func (c *CLICommandBuilder) getMySQLOracleCommand() (*exec.Cmd, error) { // We save configuration to ~/.my.cnf, but on Windows that file is not read, // see tables 4.1 and 4.2 on https://dev.mysql.com/doc/refman/8.0/en/option-files.html. // We instruct mysql client to use use that file with --defaults-extra-file. - configPath, err := mysql.DefaultConfigPath(c.tc.HomePath) + configPath, err := c.getMySQLOptionFilePath() if err != nil { return nil, trace.Wrap(err) } @@ -346,6 +346,15 @@ func (c *CLICommandBuilder) getMySQLOracleCommand() (*exec.Cmd, error) { return exec.Command(mysqlBin, args...), nil } +// getMySQLOptionFilePath gets the filepath to .my.cnf from the default location +// in ~/.my.cnf, unless overridden by config. +func (c *CLICommandBuilder) getMySQLOptionFilePath() (string, error) { + if c.tc.OverrideMySQLOptionFilePath != "" { + return c.tc.OverrideMySQLOptionFilePath, nil + } + return mysql.DefaultConfigPath() +} + // getMySQLCommand returns mariadb command if the binary is on the path. Otherwise, // mysql command is returned. Both mysql versions (MariaDB and Oracle) are supported. func (c *CLICommandBuilder) getMySQLCommand() (*exec.Cmd, error) { diff --git a/lib/client/db/mysql/optionfile.go b/lib/client/db/mysql/optionfile.go index e7ee7344d4159..14499e8e02a8e 100644 --- a/lib/client/db/mysql/optionfile.go +++ b/lib/client/db/mysql/optionfile.go @@ -46,10 +46,7 @@ type OptionFile struct { // DefaultConfigPath returns the default config path, which is .my.cnf file in // the user's home directory. Home dir is determined by environment if not // supplied as an argument. -func DefaultConfigPath(home string) (string, error) { - if home != "" { - return filepath.Join(home, mysqlOptionFile), nil - } +func DefaultConfigPath() (string, error) { home, err := os.UserHomeDir() if err != nil || home == "" { usr, err := utils.CurrentUser() @@ -63,8 +60,8 @@ func DefaultConfigPath(home string) (string, error) { } // Load loads MySQL option file from the default location. -func Load(home string) (*OptionFile, error) { - cnfPath, err := DefaultConfigPath(home) +func Load() (*OptionFile, error) { + cnfPath, err := DefaultConfigPath() if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/client/db/postgres/servicefile.go b/lib/client/db/postgres/servicefile.go index c9de6c392226c..ff95be5bde878 100644 --- a/lib/client/db/postgres/servicefile.go +++ b/lib/client/db/postgres/servicefile.go @@ -44,14 +44,10 @@ type ServiceFile struct { } // DefaultConfigPath returns the default config path, which is .pg_service.conf -// file in the user's home directory. Home dir is determined by environment if -// not supplied as an argument. -func defaultConfigPath(home string) (string, error) { +// file in the user's home directory. +func defaultConfigPath() (string, error) { // Default location is .pg_service.conf file in the user's home directory. // TODO(r0mant): Check PGSERVICEFILE and PGSYSCONFDIR env vars as well. - if home != "" { - return filepath.Join(home, pgServiceFile), nil - } home, err := os.UserHomeDir() if err != nil || home == "" { usr, err := utils.CurrentUser() @@ -65,8 +61,8 @@ func defaultConfigPath(home string) (string, error) { } // Load loads Postgres connection service file from the default location. -func Load(home string) (*ServiceFile, error) { - cnfPath, err := defaultConfigPath(home) +func Load() (*ServiceFile, error) { + cnfPath, err := defaultConfigPath() if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/client/db/profile.go b/lib/client/db/profile.go index d37221cea9881..36c2d31ae273b 100644 --- a/lib/client/db/profile.go +++ b/lib/client/db/profile.go @@ -141,9 +141,15 @@ func IsSupported(db tlsca.RouteToDatabase) bool { func load(tc *client.TeleportClient, db tlsca.RouteToDatabase) (profile.ConnectProfileFile, error) { switch db.Protocol { case defaults.ProtocolPostgres: - return postgres.Load(tc.HomePath) + if tc.OverridePostgresServiceFilePath != "" { + return postgres.LoadFromPath(tc.OverridePostgresServiceFilePath) + } + return postgres.Load() case defaults.ProtocolMySQL: - return mysql.Load(tc.HomePath) + if tc.OverrideMySQLOptionFilePath != "" { + return mysql.LoadFromPath(tc.OverrideMySQLOptionFilePath) + } + return mysql.Load() } return nil, trace.BadParameter("unsupported database protocol %q", db.Protocol) diff --git a/tool/tsh/common/db_test.go b/tool/tsh/common/db_test.go index c03dab6d973d7..0b7ba228be1ef 100644 --- a/tool/tsh/common/db_test.go +++ b/tool/tsh/common/db_test.go @@ -242,11 +242,23 @@ func testDatabaseLogin(t *testing.T) { if len(selectors) == 0 { selectors = []string{test.databaseName} } + + // override the mysql/postgres config file paths to avoid parallel + // updates to the default location in the user home dir. + mySqlCnfPath := filepath.Join(tmpHomePath, ".my.cnf") + pgCnfPath := filepath.Join(tmpHomePath, ".pg_service.conf") + // all subsequent tsh commands need these options. + cliOpts := []CliOption{ + // set .tsh location to the temp dir for this test. + setHomePath(tmpHomePath), + setOverrideMySQLConfigPath(mySqlCnfPath), + setOverridePostgresConfigPath(pgCnfPath), + } args := append([]string{ // default --db-user and --db-name are selected from roles. "db", "login", }, selectors...) - err := Run(context.Background(), args, setHomePath(tmpHomePath)) + err := Run(context.Background(), args, cliOpts...) require.NoError(t, err) // Fetch the active profile. @@ -270,7 +282,7 @@ func testDatabaseLogin(t *testing.T) { args := append([]string{ "db", "config", }, selectors...) - err := Run(context.Background(), args, setHomePath(tmpHomePath)) + err := Run(context.Background(), args, cliOpts...) if test.expectErrForConfigCmd { require.Error(t, err) @@ -284,7 +296,7 @@ func testDatabaseLogin(t *testing.T) { args := append([]string{ "db", "env", }, selectors...) - err := Run(context.Background(), args, setHomePath(tmpHomePath)) + err := Run(context.Background(), args, cliOpts...) if test.expectErrForEnvCmd { require.Error(t, err) @@ -299,7 +311,7 @@ func testDatabaseLogin(t *testing.T) { args := append([]string{ "db", "logout", }, selectors...) - err := Run(context.Background(), args, setHomePath(tmpHomePath)) + err := Run(context.Background(), args, cliOpts...) require.NoError(t, err) }) }) diff --git a/tool/tsh/common/tsh.go b/tool/tsh/common/tsh.go index 11bb168a3c74b..0f62b29d25927 100644 --- a/tool/tsh/common/tsh.go +++ b/tool/tsh/common/tsh.go @@ -340,6 +340,16 @@ type CLIConf struct { // MockHeadlessLogin used in tests to override Headless login handler in teleport client. MockHeadlessLogin client.SSHLoginFunc + // overrideMySQLOptionFilePath overrides the MySQL option file path to use. + // Useful in parallel tests so they don't all use the default path in the + // user home dir. + overrideMySQLOptionFilePath string + + // overridePostgresServiceFilePath overrides the Postgres service file path. + // Useful in parallel tests so they don't all use the default path in the + // user home dir. + overridePostgresServiceFilePath string + // HomePath is where tsh stores profiles HomePath string @@ -3701,6 +3711,10 @@ func loadClientConfigFromCLIConf(cf *CLIConf, proxy string) (*client.Config, err c.MockHeadlessLogin = cf.MockHeadlessLogin c.DTAuthnRunCeremony = cf.DTAuthnRunCeremony + // pass along MySQL/Postgres path overrides (only used in tests). + c.OverrideMySQLOptionFilePath = cf.overrideMySQLOptionFilePath + c.OverridePostgresServiceFilePath = cf.overridePostgresServiceFilePath + // Set tsh home directory c.HomePath = cf.HomePath diff --git a/tool/tsh/common/tsh_test.go b/tool/tsh/common/tsh_test.go index 72550b1140cce..5ac1a2558e660 100644 --- a/tool/tsh/common/tsh_test.go +++ b/tool/tsh/common/tsh_test.go @@ -3367,6 +3367,20 @@ func setHomePath(path string) CliOption { } } +func setOverrideMySQLConfigPath(path string) CliOption { + return func(cf *CLIConf) error { + cf.overrideMySQLOptionFilePath = path + return nil + } +} + +func setOverridePostgresConfigPath(path string) CliOption { + return func(cf *CLIConf) error { + cf.overridePostgresServiceFilePath = path + return nil + } +} + func setKubeConfigPath(path string) CliOption { return func(cf *CLIConf) error { cf.KubeConfigPath = path