diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 5c603d04da..76f5ffd836 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -4461,26 +4461,30 @@ class SshServer(command.Command): metavar='', help=_('Server (name or ID)'), ) + # Deprecated during the Yoga cycle parser.add_argument( '--login', '-l', metavar='', - help=_('Login name (ssh -l option)'), + help=argparse.SUPPRESS, ) + # Deprecated during the Yoga cycle parser.add_argument( '--port', '-p', metavar='', type=int, - help=_('Destination port (ssh -p option)'), + help=argparse.SUPPRESS, ) + # Deprecated during the Yoga cycle parser.add_argument( '--identity', '-i', metavar='', - help=_('Private key file (ssh -i option)'), + help=argparse.SUPPRESS, ) + # Deprecated during the Yoga cycle parser.add_argument( '--option', '-o', metavar='', - help=_('Options in ssh_config(5) format (ssh -o option)'), + help=argparse.SUPPRESS, ) ip_group = parser.add_mutually_exclusive_group() ip_group.add_argument( @@ -4521,6 +4525,7 @@ class SshServer(command.Command): default='public', help=_('Use other IP address (public, private, etc)'), ) + # Deprecated during the Yoga cycle parser.add_argument( '-v', dest='verbose', @@ -4528,46 +4533,77 @@ class SshServer(command.Command): default=False, help=argparse.SUPPRESS, ) + parser.add_argument( + 'ssh_args', + nargs='*', + metavar='-- ', + help=( + 'Any argument or option that ssh allows. ' + 'Use -- once between openstackclient args and SSH args.' + ), + ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute + server = utils.find_resource( compute_client.servers, parsed_args.server, ) - # Build the command - cmd = "ssh" + # first, handle the deprecated options + if any(( + parsed_args.port, + parsed_args.identity, + parsed_args.option, + parsed_args.login, + parsed_args.verbose, + )): + msg = _( + 'The ssh options have been deprecated. The ssh equivalent ' + 'options can be used instead as arguments after "--" on ' + 'the command line.' + ) + self.log.warning(msg) ip_address_family = [4, 6] if parsed_args.ipv4: ip_address_family = [4] - cmd += " -4" if parsed_args.ipv6: ip_address_family = [6] - cmd += " -6" + + args = parsed_args.ssh_args[:] if parsed_args.port: - cmd += " -p %d" % parsed_args.port + args.extend(['-p', str(parsed_args.port)]) + if parsed_args.identity: - cmd += " -i %s" % parsed_args.identity + args.extend(['-i', parsed_args.identity]) + if parsed_args.option: - cmd += " -o %s" % parsed_args.option + args.extend(['-o', parsed_args.option]) + if parsed_args.login: login = parsed_args.login - else: + args.extend(['-l', login]) + elif '-l' not in args: login = self.app.client_manager.auth_ref.username - if parsed_args.verbose: - cmd += " -v" + args.extend(['-l', login]) - cmd += " %s@%s" - ip_address = _get_ip_address(server.addresses, - parsed_args.address_type, - ip_address_family) - LOG.debug("ssh command: %s", (cmd % (login, ip_address))) - os.system(cmd % (login, ip_address)) + if parsed_args.verbose: + args.append('-v') + + ip_address = _get_ip_address( + server.addresses, + parsed_args.address_type, + ip_address_family, + ) + + cmd = ' '.join(['ssh', ip_address] + args) + LOG.debug("ssh command: {cmd}".format(cmd=cmd)) + os.system(cmd) class StartServer(command.Command): diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index acff61e6ec..3f7ad04ac5 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -8307,15 +8307,48 @@ class TestServerSsh(TestServer): ('ipv6', False), ('address_type', 'public'), ('verbose', False), + ('ssh_args', []), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) + with mock.patch.object(self.cmd.log, 'warning') as mock_warning: + result = self.cmd.take_action(parsed_args) self.assertIsNone(result) - mock_exec.assert_called_once_with('ssh cloud@192.168.1.30') + mock_exec.assert_called_once_with('ssh 192.168.1.30 -l cloud') + mock_warning.assert_not_called() - def test_server_ssh_opts(self, mock_exec): + def test_server_ssh_passthrough_opts(self, mock_exec): + arglist = [ + self.server.name, + '--', + '-l', 'username', + '-p', '2222', + ] + verifylist = [ + ('server', self.server.name), + ('login', None), + ('port', None), + ('identity', None), + ('option', None), + ('ipv4', False), + ('ipv6', False), + ('address_type', 'public'), + ('verbose', False), + ('ssh_args', ['-l', 'username', '-p', '2222']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object(self.cmd.log, 'warning') as mock_warning: + result = self.cmd.take_action(parsed_args) + + self.assertIsNone(result) + mock_exec.assert_called_once_with( + 'ssh 192.168.1.30 -l username -p 2222' + ) + mock_warning.assert_not_called() + + def test_server_ssh_deprecated_opts(self, mock_exec): arglist = [ self.server.name, '-l', 'username', @@ -8331,14 +8364,21 @@ class TestServerSsh(TestServer): ('ipv6', False), ('address_type', 'public'), ('verbose', False), + ('ssh_args', []), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) + with mock.patch.object(self.cmd.log, 'warning') as mock_warning: + result = self.cmd.take_action(parsed_args) self.assertIsNone(result) mock_exec.assert_called_once_with( - 'ssh -p 2222 username@192.168.1.30' + 'ssh 192.168.1.30 -p 2222 -l username' + ) + mock_warning.assert_called_once() + self.assertIn( + 'The ssh options have been deprecated.', + mock_warning.call_args[0][0], ) diff --git a/releasenotes/notes/pass_ssh_args-cf26a2ce26ccddaf.yaml b/releasenotes/notes/pass_ssh_args-cf26a2ce26ccddaf.yaml new file mode 100644 index 0000000000..fe81de94a5 --- /dev/null +++ b/releasenotes/notes/pass_ssh_args-cf26a2ce26ccddaf.yaml @@ -0,0 +1,14 @@ +--- +features: + - | + Added the ability to pass arguments through to the ``ssh`` command When + using ``openstack server ssh``. This allows the user to use any ``ssh`` + option without needing to add that option to the openstack client. + Existing openstackclient options that mirror SSH options are now + deprecated. +deprecations: + - | + ``openstack server ssh`` options that mirror ``ssh`` options are now + deprecated (``--login, -l, --port, --identity, --option, -o, -vz``). + The ``ssh`` equivalent of each deprecated option should be used instead. + For example ``openstack server ssh instance -- -l user -i key``