From 751e064761e878a10007541dfec00def9037c5e9 Mon Sep 17 00:00:00 2001 From: John Trowbridge Date: Fri, 15 Aug 2014 17:04:27 -0400 Subject: [PATCH] Adds tty password entry for glanceclient Added functionality from keystoneclient to fallback to the tty for password entry if no password is given via the environment or the --os-password option. Change-Id: I096e81b61d7f499cbda300abdc14430928d18491 Closes-Bug: 1357549 --- glanceclient/shell.py | 20 ++++++++++++++++---- tests/test_shell.py | 27 +++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/glanceclient/shell.py b/glanceclient/shell.py index 92de22ea..01743e2c 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 @@ -439,10 +440,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 da1ca131..8753c4b5 100644 --- a/tests/test_shell.py +++ b/tests/test_shell.py @@ -17,6 +17,7 @@ import argparse import os +import fixtures import mock from glanceclient import exc @@ -65,6 +66,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 @@ -224,6 +230,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