compute: Pass through args to ssh
Why limit a user to preset ssh arguments? Capture them all and send them along to ssh to deal with. This allows users to use the full range of ssh arguments, including specifying a command to run on the instance. For example: openstack server ssh -4 upg -- -l cirros -i ~/id_rsa_upg "date; uptime" SSH arguments that openstackclient currently mirrors are deprecated except for -4 and -6, as they are useful for retrieving the correct instance IP. Change-Id: Ia50786d5eee52688e180550fe16aeb8af610154b Co-authored-by: Stephen Finucane <stephen@that.guru>
This commit is contained in:
parent
ba69870d86
commit
3a929611c0
@ -4461,26 +4461,30 @@ class SshServer(command.Command):
|
||||
metavar='<server>',
|
||||
help=_('Server (name or ID)'),
|
||||
)
|
||||
# Deprecated during the Yoga cycle
|
||||
parser.add_argument(
|
||||
'--login', '-l',
|
||||
metavar='<login-name>',
|
||||
help=_('Login name (ssh -l option)'),
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
# Deprecated during the Yoga cycle
|
||||
parser.add_argument(
|
||||
'--port', '-p',
|
||||
metavar='<port>',
|
||||
type=int,
|
||||
help=_('Destination port (ssh -p option)'),
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
# Deprecated during the Yoga cycle
|
||||
parser.add_argument(
|
||||
'--identity', '-i',
|
||||
metavar='<keyfile>',
|
||||
help=_('Private key file (ssh -i option)'),
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
# Deprecated during the Yoga cycle
|
||||
parser.add_argument(
|
||||
'--option', '-o',
|
||||
metavar='<config-options>',
|
||||
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='-- <standard ssh args>',
|
||||
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):
|
||||
|
@ -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],
|
||||
)
|
||||
|
||||
|
||||
|
14
releasenotes/notes/pass_ssh_args-cf26a2ce26ccddaf.yaml
Normal file
14
releasenotes/notes/pass_ssh_args-cf26a2ce26ccddaf.yaml
Normal file
@ -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``
|
Loading…
Reference in New Issue
Block a user