diff --git a/cinderclient/shell.py b/cinderclient/shell.py index df5f62c6e..7bfa3e6d0 100644 --- a/cinderclient/shell.py +++ b/cinderclient/shell.py @@ -21,6 +21,7 @@ Command-line interface to the OpenStack Cinder API. from __future__ import print_function import argparse +import getpass import glob import imp import itertools @@ -504,7 +505,6 @@ class OpenStackCinderShell(object): ks_logger.setLevel(logging.DEBUG) def main(self, argv): - # Parse args once to find version and debug settings parser = self.get_base_parser() (options, args) = parser.parse_known_args(argv) @@ -554,7 +554,6 @@ class OpenStackCinderShell(object): args.service_type, args.service_name, args.volume_service_name, args.os_cacert, args.os_auth_system) - if os_auth_system and os_auth_system != "keystone": auth_plugin = cinderclient.auth_plugin.load_plugin(os_auth_system) else: @@ -581,9 +580,20 @@ class OpenStackCinderShell(object): "env[OS_USERNAME].") if not os_password: - raise exc.CommandError("You must provide a password " - "through --os-password or " - "env[OS_PASSWORD].") + # No password, If we've got a tty, try prompting for it + if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty(): + # Check for Ctl-D + try: + os_password = getpass.getpass('OS Password: ') + except EOFError: + pass + # No password because we didn't have a tty or the + # user Ctl-D when prompted. + if not os_password: + raise exc.CommandError("You must provide a password " + "through --os-password, " + "env[OS_PASSWORD] " + "or, prompted response.") if not (os_tenant_name or os_tenant_id): raise exc.CommandError("You must provide a tenant ID " diff --git a/cinderclient/tests/test_shell.py b/cinderclient/tests/test_shell.py index 27e4f69e6..63fa1c665 100644 --- a/cinderclient/tests/test_shell.py +++ b/cinderclient/tests/test_shell.py @@ -16,6 +16,7 @@ import re import sys import fixtures +import mock import requests_mock from six import moves from testtools import matchers @@ -24,6 +25,7 @@ from cinderclient import exceptions from cinderclient import shell from cinderclient.tests import utils from cinderclient.tests.fixture_data import keystone_client +import keystoneclient.exceptions as ks_exc from keystoneclient.exceptions import DiscoveryFailure @@ -33,10 +35,14 @@ class ShellTest(utils.TestCase): 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_TENANT_NAME': 'tenant_name', - 'OS_AUTH_URL': 'http://no.where', + 'OS_AUTH_URL': 'http://no.where/v2.0', } # Patch os.environ to avoid required auth info. + def make_env(self, exclude=None): + env = dict((k, v) for k, v in self.FAKE_ENV.items() if k != exclude) + self.useFixture(fixtures.MonkeyPatch('os.environ', env)) + def setUp(self): super(ShellTest, self).setUp() for var in self.FAKE_ENV: @@ -110,6 +116,15 @@ class ShellTest(utils.TestCase): self.assertEqual(v3_url, os_auth_url, "Expected v3 url") self.assertEqual(v2_url, None, "Expected no v2 url") + @mock.patch('sys.stdin', side_effect=mock.MagicMock) + @mock.patch('getpass.getpass', return_value='password') + def test_password_prompted(self, mock_getpass, mock_stdin): + self.make_env(exclude='OS_PASSWORD') + # We should get a Connection Refused because there is no keystone. + self.assertRaises(ks_exc.ConnectionRefused, self.shell, 'list') + # Make sure we are actually prompted. + mock_getpass.assert_called_with('OS Password: ') + class CinderClientArgumentParserTest(utils.TestCase):