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>',
|
metavar='<server>',
|
||||||
help=_('Server (name or ID)'),
|
help=_('Server (name or ID)'),
|
||||||
)
|
)
|
||||||
|
# Deprecated during the Yoga cycle
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--login', '-l',
|
'--login', '-l',
|
||||||
metavar='<login-name>',
|
metavar='<login-name>',
|
||||||
help=_('Login name (ssh -l option)'),
|
help=argparse.SUPPRESS,
|
||||||
)
|
)
|
||||||
|
# Deprecated during the Yoga cycle
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--port', '-p',
|
'--port', '-p',
|
||||||
metavar='<port>',
|
metavar='<port>',
|
||||||
type=int,
|
type=int,
|
||||||
help=_('Destination port (ssh -p option)'),
|
help=argparse.SUPPRESS,
|
||||||
)
|
)
|
||||||
|
# Deprecated during the Yoga cycle
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--identity', '-i',
|
'--identity', '-i',
|
||||||
metavar='<keyfile>',
|
metavar='<keyfile>',
|
||||||
help=_('Private key file (ssh -i option)'),
|
help=argparse.SUPPRESS,
|
||||||
)
|
)
|
||||||
|
# Deprecated during the Yoga cycle
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--option', '-o',
|
'--option', '-o',
|
||||||
metavar='<config-options>',
|
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 = parser.add_mutually_exclusive_group()
|
||||||
ip_group.add_argument(
|
ip_group.add_argument(
|
||||||
@ -4521,6 +4525,7 @@ class SshServer(command.Command):
|
|||||||
default='public',
|
default='public',
|
||||||
help=_('Use other IP address (public, private, etc)'),
|
help=_('Use other IP address (public, private, etc)'),
|
||||||
)
|
)
|
||||||
|
# Deprecated during the Yoga cycle
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-v',
|
'-v',
|
||||||
dest='verbose',
|
dest='verbose',
|
||||||
@ -4528,46 +4533,77 @@ class SshServer(command.Command):
|
|||||||
default=False,
|
default=False,
|
||||||
help=argparse.SUPPRESS,
|
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
|
return parser
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
|
|
||||||
compute_client = self.app.client_manager.compute
|
compute_client = self.app.client_manager.compute
|
||||||
|
|
||||||
server = utils.find_resource(
|
server = utils.find_resource(
|
||||||
compute_client.servers,
|
compute_client.servers,
|
||||||
parsed_args.server,
|
parsed_args.server,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Build the command
|
# first, handle the deprecated options
|
||||||
cmd = "ssh"
|
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]
|
ip_address_family = [4, 6]
|
||||||
if parsed_args.ipv4:
|
if parsed_args.ipv4:
|
||||||
ip_address_family = [4]
|
ip_address_family = [4]
|
||||||
cmd += " -4"
|
|
||||||
if parsed_args.ipv6:
|
if parsed_args.ipv6:
|
||||||
ip_address_family = [6]
|
ip_address_family = [6]
|
||||||
cmd += " -6"
|
|
||||||
|
args = parsed_args.ssh_args[:]
|
||||||
|
|
||||||
if parsed_args.port:
|
if parsed_args.port:
|
||||||
cmd += " -p %d" % parsed_args.port
|
args.extend(['-p', str(parsed_args.port)])
|
||||||
|
|
||||||
if parsed_args.identity:
|
if parsed_args.identity:
|
||||||
cmd += " -i %s" % parsed_args.identity
|
args.extend(['-i', parsed_args.identity])
|
||||||
|
|
||||||
if parsed_args.option:
|
if parsed_args.option:
|
||||||
cmd += " -o %s" % parsed_args.option
|
args.extend(['-o', parsed_args.option])
|
||||||
|
|
||||||
if parsed_args.login:
|
if parsed_args.login:
|
||||||
login = 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
|
login = self.app.client_manager.auth_ref.username
|
||||||
if parsed_args.verbose:
|
args.extend(['-l', login])
|
||||||
cmd += " -v"
|
|
||||||
|
|
||||||
cmd += " %s@%s"
|
if parsed_args.verbose:
|
||||||
ip_address = _get_ip_address(server.addresses,
|
args.append('-v')
|
||||||
parsed_args.address_type,
|
|
||||||
ip_address_family)
|
ip_address = _get_ip_address(
|
||||||
LOG.debug("ssh command: %s", (cmd % (login, ip_address)))
|
server.addresses,
|
||||||
os.system(cmd % (login, ip_address))
|
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):
|
class StartServer(command.Command):
|
||||||
|
@ -8307,15 +8307,48 @@ class TestServerSsh(TestServer):
|
|||||||
('ipv6', False),
|
('ipv6', False),
|
||||||
('address_type', 'public'),
|
('address_type', 'public'),
|
||||||
('verbose', False),
|
('verbose', False),
|
||||||
|
('ssh_args', []),
|
||||||
]
|
]
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
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)
|
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 = [
|
arglist = [
|
||||||
self.server.name,
|
self.server.name,
|
||||||
'-l', 'username',
|
'-l', 'username',
|
||||||
@ -8331,14 +8364,21 @@ class TestServerSsh(TestServer):
|
|||||||
('ipv6', False),
|
('ipv6', False),
|
||||||
('address_type', 'public'),
|
('address_type', 'public'),
|
||||||
('verbose', False),
|
('verbose', False),
|
||||||
|
('ssh_args', []),
|
||||||
]
|
]
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
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)
|
self.assertIsNone(result)
|
||||||
mock_exec.assert_called_once_with(
|
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