diff --git a/CHANGELOG.md b/CHANGELOG.md index baa5f4130..1f7b4e07b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,8 @@ stdout/stderr and `tt` logs go to `tt.log` file. - tt completion: added luarocks completions. - tarantool-ee: search and install development builds. +- ``tt play``: ability to pass username and password via flags and environment + variables. ### Fixed - ``tt rocks``: broken ``--verbose`` option. diff --git a/cli/checkpoint/lua/play.lua b/cli/checkpoint/lua/play.lua index 37e203983..18cc4de4d 100644 --- a/cli/checkpoint/lua/play.lua +++ b/cli/checkpoint/lua/play.lua @@ -45,14 +45,14 @@ local function filter_xlog(gen, param, state, opts, cb) end end -local function play(positional_arguments, keyword_arguments) - local opts = keyword_arguments +local function play(positional_arguments, keyword_arguments, opts) + local filter_opts = keyword_arguments local uri = table.remove(positional_arguments, 1) if uri == nil then log.error('Internal error: empty URI is provided') os.exit(1) end - local remote = netbox.new(uri) + local remote = netbox.new(uri, opts) if not remote:wait_connected() then log.error('Fatal error: no connection to the host "%s"', uri) os.exit(1) @@ -61,7 +61,7 @@ local function play(positional_arguments, keyword_arguments) print(string.format('• Play is processing file "%s" •', file)) io.stdout:flush() local gen, param, state = xlog.pairs(file) - filter_xlog(gen, param, state, opts, function(record) + filter_xlog(gen, param, state, filter_opts, function(record) local sid = record.BODY and record.BODY.space_id if sid ~= nil then local args, so = {}, remote.space[sid] @@ -137,7 +137,11 @@ local function main() end end - play(positional_arguments, keyword_arguments) + local opts = { + user = os.getenv('TT_CLI_PLAY_USERNAME'), + password = os.getenv('TT_CLI_PLAY_PASSWORD'), + } + play(positional_arguments, keyword_arguments, opts) end main() diff --git a/cli/cmd/play.go b/cli/cmd/play.go index a7814b280..fb0876145 100644 --- a/cli/cmd/play.go +++ b/cli/cmd/play.go @@ -2,6 +2,7 @@ package cmd import ( "encoding/json" + "errors" "fmt" "math" "os" @@ -11,6 +12,7 @@ import ( "github.com/spf13/cobra" "github.com/tarantool/tt/cli/checkpoint" "github.com/tarantool/tt/cli/cmdcontext" + "github.com/tarantool/tt/cli/connect" "github.com/tarantool/tt/cli/modules" "github.com/tarantool/tt/cli/util" "github.com/tarantool/tt/cli/version" @@ -26,6 +28,13 @@ var playFlags = checkpoint.Opts{ ShowSystem: false, } +var ( + // playUsername contains username flag. + playUsername string + // playPassword contains password flag. + playPassword string +) + // NewPlayCmd creates a new play command. func NewPlayCmd() *cobra.Command { var playCmd = &cobra.Command{ @@ -39,6 +48,8 @@ func NewPlayCmd() *cobra.Command { }, } + playCmd.Flags().StringVarP(&playUsername, "username", "u", "", "username") + playCmd.Flags().StringVarP(&playPassword, "password", "p", "", "password") playCmd.Flags().Uint64Var(&playFlags.To, "to", playFlags.To, "Show operations ending with the given lsn") playCmd.Flags().Uint64Var(&playFlags.From, "from", playFlags.From, @@ -68,7 +79,27 @@ func internalPlayModule(cmdCtx *cmdcontext.CmdCtx, args []string) error { ) } + if connect.IsCredentialsURI(args[0]) { + if playUsername != "" || playPassword != "" { + return errors.New("username and password are specified with" + + " flags and a URI") + } + } else { + if playUsername == "" { + playUsername = os.Getenv(connect.TarantoolUsernameEnv) + } + if playPassword == "" { + playPassword = os.Getenv(connect.TarantoolPasswordEnv) + } + } + os.Setenv("TT_CLI_PLAY_FILES_AND_URI", string(filesAndUriJson)) + if playUsername != "" { + os.Setenv("TT_CLI_PLAY_USERNAME", playUsername) + } + if playPassword != "" { + os.Setenv("TT_CLI_PLAY_PASSWORD", playPassword) + } os.Setenv("TT_CLI_PLAY_SHOW_SYS", strconv.FormatBool(playFlags.ShowSystem)) // List of spaces is passed to lua play script via environment variable in json format. diff --git a/test/integration/play/test_file/remote_instance_cfg.lua b/test/integration/play/test_file/remote_instance_cfg.lua index 94c4a0d43..0cdd86e14 100644 --- a/test/integration/play/test_file/remote_instance_cfg.lua +++ b/test/integration/play/test_file/remote_instance_cfg.lua @@ -21,6 +21,8 @@ local function configure_instance() ) tester:create_index('primary', {type = 'tree', parts = {'id'}}) box.schema.user.grant('guest', 'read,write', 'space', 'tester') + box.schema.user.create('test_user', { password = 'secret' }) + box.schema.user.grant('test_user', 'super') end configure_instance() diff --git a/test/integration/play/test_play.py b/test/integration/play/test_play.py index d7d5bdf1a..6557233e2 100644 --- a/test/integration/play/test_play.py +++ b/test/integration/play/test_play.py @@ -81,3 +81,75 @@ def test_play_test_remote_instance(tt_cmd, tmpdir): assert re.search(r"[1, 'Roxette', 1986]", output) assert re.search(r"[2, 'Scorpions', 2015]", output) assert re.search(r"[3, 'Ace of Base', 1993]", output) + + +@pytest.mark.parametrize("opts", [ + pytest.param({"flags": ["--username=test_user", "--password=4"]}), + pytest.param({"flags": ["--username=fry"]}), + pytest.param({"env": {"TT_CLI_USERNAME": "test_user", "TT_CLI_PASSWORD": "4"}}), + pytest.param({"env": {"TT_CLI_USERNAME": "fry"}}), + pytest.param({"uri": "test_user:4"}), +]) +def test_play_wrong_creds(tt_cmd, tmpdir, opts): + # Testing play using remote instance. + test_app_path = os.path.join(os.path.dirname(__file__), "test_file") + # Copy the .xlog file to the "run" directory. + shutil.copy(test_app_path + "/test.xlog", tmpdir) + + # Create tarantool instance for testing and start it. + path_to_lua_utils = os.path.join(os.path.dirname(__file__), "test_file/../../../") + test_instance = TarantoolTestInstance(INSTANCE_NAME, test_app_path, path_to_lua_utils, tmpdir) + test_instance.start() + + # Play .xlog file to the remote instance. + uri = "127.0.0.1:" + test_instance.port + if "uri" in opts: + uri = opts["uri"] + "@" + uri + if "env" in opts: + env = opts["env"] + else: + env = None + cmd = [tt_cmd, "play", uri, "test.xlog", "--space=999"] + if "flags" in opts: + cmd.extend(opts["flags"]) + + rc, output = run_command_and_get_output(cmd, cwd=tmpdir, env=env) + test_instance.stop() + assert rc != 0 + + +@pytest.mark.parametrize("opts", [ + pytest.param({"flags": ["--username=test_user", "--password=secret"]}), + pytest.param({"env": { + "TT_CLI_USERNAME": "test_user", + "TT_CLI_PASSWORD": "secret", + "PATH": os.getenv("PATH"), + }}), + pytest.param({"uri": "test_user:secret"}), +]) +def test_play_creds(tt_cmd, tmpdir, opts): + # Testing play using remote instance. + test_app_path = os.path.join(os.path.dirname(__file__), "test_file") + # Copy the .xlog file to the "run" directory. + shutil.copy(test_app_path + "/test.xlog", tmpdir) + + # Create tarantool instance for testing and start it. + path_to_lua_utils = os.path.join(os.path.dirname(__file__), "test_file/../../../") + test_instance = TarantoolTestInstance(INSTANCE_NAME, test_app_path, path_to_lua_utils, tmpdir) + test_instance.start() + + # Play .xlog file to the remote instance. + uri = "127.0.0.1:" + test_instance.port + if "uri" in opts: + uri = opts["uri"] + "@" + uri + if "env" in opts: + env = opts["env"] + else: + env = None + cmd = [tt_cmd, "play", uri, "test.xlog", "--space=999"] + if "flags" in opts: + cmd.extend(opts["flags"]) + + rc, output = run_command_and_get_output(cmd, cwd=tmpdir, env=env) + test_instance.stop() + assert rc == 0