diff --git a/glanceclient/shell.py b/glanceclient/shell.py index 80d2f9a..c41e867 100644 --- a/glanceclient/shell.py +++ b/glanceclient/shell.py @@ -21,6 +21,7 @@ from __future__ import print_function import argparse import copy +import getpass import json import logging import os @@ -448,10 +449,21 @@ class OpenStackImagesShell(object): "env[OS_USERNAME]")) if not args.os_password: - raise exc.CommandError( - _("You must provide a password via" - " either --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: + args.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 args.os_password: + raise exc.CommandError( + _("You must provide a password via " + "either --os-password, " + "env[OS_PASSWORD], " + "or prompted response")) # Validate password flow auth project_info = (args.os_tenant_name or diff --git a/tests/test_shell.py b/tests/test_shell.py index 02e05d0..1a6e5ba 100644 --- a/tests/test_shell.py +++ b/tests/test_shell.py @@ -18,6 +18,7 @@ import argparse import os import sys +import fixtures import mock import six @@ -67,6 +68,11 @@ class ShellTest(utils.TestCase): # expected auth plugin to invoke auth_plugin = 'keystoneclient.auth.identity.v2.Password' + # Patch os.environ to avoid required auth info + def make_env(self, exclude=None, fake_env=FAKE_V2_ENV): + env = dict((k, v) for k, v in fake_env.items() if k != exclude) + self.useFixture(fixtures.MonkeyPatch('os.environ', env)) + def setUp(self): super(ShellTest, self).setUp() global _old_env @@ -246,6 +252,27 @@ class ShellTest(utils.TestCase): glance_shell.main(args.split()) self._assert_auth_plugin_args(mock_auth_plugin) + @mock.patch('sys.stdin', side_effect=mock.MagicMock) + @mock.patch('getpass.getpass', return_value='password') + def test_password_prompted_with_v2(self, mock_getpass, mock_stdin): + glance_shell = openstack_shell.OpenStackImagesShell() + self.make_env(exclude='OS_PASSWORD') + # We will get a Connection Refused because there is no keystone. + self.assertRaises(ks_exc.ConnectionRefused, + glance_shell.main, ['image-list']) + # Make sure we are actually prompted. + mock_getpass.assert_called_with('OS Password: ') + + @mock.patch('sys.stdin', side_effect=mock.MagicMock) + @mock.patch('getpass.getpass', side_effect=EOFError) + def test_password_prompted_ctrlD_with_v2(self, mock_getpass, mock_stdin): + glance_shell = openstack_shell.OpenStackImagesShell() + self.make_env(exclude='OS_PASSWORD') + # We should get Command Error because we mock Ctl-D. + self.assertRaises(exc.CommandError, glance_shell.main, ['image-list']) + # Make sure we are actually prompted. + mock_getpass.assert_called_with('OS Password: ') + class ShellTestWithKeystoneV3Auth(ShellTest): # auth environment to use