diff options
22 files changed, 310 insertions, 38 deletions
diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 94ea2225..7cd877ef 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -15,6 +15,7 @@ """Common client utilities""" +import getpass import logging import os import six @@ -229,3 +230,18 @@ def get_effective_log_level(): for handler in root_log.handlers: min_log_lvl = min(min_log_lvl, handler.level) return min_log_lvl + + +def get_password(stdin): + if hasattr(stdin, 'isatty') and stdin.isatty(): + try: + while True: + first_pass = getpass.getpass("User password: ") + second_pass = getpass.getpass("Repeat user password: ") + if first_pass == second_pass: + return first_pass + print("The passwords entered were not the same") + except EOFError: # Ctl-D + raise exceptions.CommandError("Error reading password.") + raise exceptions.CommandError("There was a request to be prompted for a" + " password and a terminal was not detected.") diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py index b79ebbe7..c8fb6ccc 100644 --- a/openstackclient/compute/v2/agent.py +++ b/openstackclient/compute/v2/agent.py @@ -136,7 +136,7 @@ class SetAgent(show.ShowOne): parser.add_argument( "id", metavar="<id>", - help="ID of the agent build") + help="ID of the agent") parser.add_argument( "version", metavar="<version>", diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 87f5f689..808741fd 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -483,40 +483,40 @@ class ListServer(lister.Lister): parser.add_argument( '--reservation-id', metavar='<reservation-id>', - help='only return instances that match the reservation') + help='Only return instances that match the reservation') parser.add_argument( '--ip', metavar='<ip-address-regex>', - help='regular expression to match IP address') + help='Regular expression to match IP addresses') parser.add_argument( '--ip6', metavar='<ip-address-regex>', - help='regular expression to match IPv6 address') + help='Regular expression to match IPv6 addresses') parser.add_argument( '--name', metavar='<name>', - help='regular expression to match name') + help='Regular expression to match names') parser.add_argument( '--status', metavar='<status>', # FIXME(dhellmann): Add choices? - help='search by server status') + help='Search by server status') parser.add_argument( '--flavor', metavar='<flavor>', - help='search by flavor ID') + help='Search by flavor ID') parser.add_argument( '--image', metavar='<image>', - help='search by image ID') + help='Search by image ID') parser.add_argument( '--host', metavar='<hostname>', - help='search by hostname') + help='Search by hostname') parser.add_argument( '--instance-name', metavar='<server-name>', - help='regular expression to match instance name (admin only)') + help='Regular expression to match instance name (admin only)') parser.add_argument( '--all-projects', action='store_true', @@ -526,7 +526,7 @@ class ListServer(lister.Lister): '--long', action='store_true', default=False, - help='Additional fields are listed in output') + help='List additional fields in output') return parser def take_action(self, parsed_args): @@ -1181,14 +1181,14 @@ class SshServer(command.Command): dest='ipv4', action='store_true', default=False, - help='Use only IPv4 addresses only', + help='Use only IPv4 addresses', ) ip_group.add_argument( '-6', dest='ipv6', action='store_true', default=False, - help='Use only IPv6 addresses only', + help='Use only IPv6 addresses', ) type_group = parser.add_mutually_exclusive_group() type_group.add_argument( diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py index 30835769..1dfe8c0a 100644 --- a/openstackclient/compute/v2/usage.py +++ b/openstackclient/compute/v2/usage.py @@ -35,7 +35,7 @@ class ListUsage(lister.Lister): "--start", metavar="<start>", default=None, - help="Usage range start date ex 2012-01-20" + help="Usage range start date, ex 2012-01-20" " (default: 4 weeks ago)." ) parser.add_argument( diff --git a/openstackclient/identity/client.py b/openstackclient/identity/client.py index a71dfc7a..b58098e9 100644 --- a/openstackclient/identity/client.py +++ b/openstackclient/identity/client.py @@ -40,7 +40,10 @@ def make_client(instance): LOG.debug('instantiating identity client: token flow') client = identity_client( endpoint=instance._url, - token=instance._token) + token=instance._token, + cacert=instance._cacert, + insecure=instance._insecure, + ) else: LOG.debug('instantiating identity client: password flow') client = identity_client( diff --git a/openstackclient/identity/v2_0/endpoint.py b/openstackclient/identity/v2_0/endpoint.py index 5a050fa1..0319c268 100644 --- a/openstackclient/identity/v2_0/endpoint.py +++ b/openstackclient/identity/v2_0/endpoint.py @@ -106,7 +106,7 @@ class ListEndpoint(lister.Lister): '--long', action='store_true', default=False, - help='Additional fields are listed in output') + help='List additional fields in output') return parser def take_action(self, parsed_args): diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index 92d1e099..ea45f634 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -104,7 +104,7 @@ class ListService(lister.Lister): '--long', action='store_true', default=False, - help='Additional fields are listed in output') + help='List additional fields in output') return parser def take_action(self, parsed_args): diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index 371c45a9..628be4b8 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -43,6 +43,12 @@ class CreateUser(show.ShowOne): help='New user password', ) parser.add_argument( + '--password-prompt', + dest="password_prompt", + action="store_true", + help='Prompt interactively for password', + ) + parser.add_argument( '--email', metavar='<user-email>', help='New user email address', @@ -80,6 +86,8 @@ class CreateUser(show.ShowOne): enabled = True if parsed_args.disable: enabled = False + if parsed_args.password_prompt: + parsed_args.password = utils.get_password(self.app.stdin) user = identity_client.users.create( parsed_args.name, @@ -143,7 +151,7 @@ class ListUser(lister.Lister): '--long', action='store_true', default=False, - help='Additional fields are listed in output') + help='List additional fields in output') return parser def take_action(self, parsed_args): @@ -225,6 +233,12 @@ class SetUser(command.Command): help='New user password', ) parser.add_argument( + '--password-prompt', + dest="password_prompt", + action="store_true", + help='Prompt interactively for password', + ) + parser.add_argument( '--email', metavar='<user-email>', help='New user email address', @@ -251,6 +265,9 @@ class SetUser(command.Command): self.log.debug('take_action(%s)' % parsed_args) identity_client = self.app.client_manager.identity + if parsed_args.password_prompt: + parsed_args.password = utils.get_password(self.app.stdin) + if (not parsed_args.name and not parsed_args.name and not parsed_args.password diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 43da07aa..e0b1f1a3 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -120,7 +120,7 @@ class ListEndpoint(lister.Lister): '--long', action='store_true', default=False, - help='Additional fields are listed in output') + help='List additional fields in output') return parser def take_action(self, parsed_args): diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index b5d24ef5..6c059b5d 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -197,7 +197,7 @@ class ListGroup(lister.Lister): '--long', action='store_true', default=False, - help='Additional fields are listed in output', + help='List additional fields in output', ) return parser diff --git a/openstackclient/identity/v3/policy.py b/openstackclient/identity/v3/policy.py index cdbb1cf2..a760d8cd 100644 --- a/openstackclient/identity/v3/policy.py +++ b/openstackclient/identity/v3/policy.py @@ -37,7 +37,7 @@ class CreatePolicy(show.ShowOne): '--type', metavar='<policy-type>', default="application/json", - help='New MIME Type of the policy blob - i.e.: application/json', + help='New MIME type of the policy blob - i.e.: application/json', ) parser.add_argument( 'blob_file', diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py index 68f9ffef..3cc78cd7 100644 --- a/openstackclient/identity/v3/token.py +++ b/openstackclient/identity/v3/token.py @@ -213,12 +213,12 @@ class DeleteAccessToken(command.Command): parser.add_argument( 'user', metavar='<user>', - help='Name or Id of user', + help='Name or ID of user', ) parser.add_argument( 'access_key', metavar='<access-key>', - help='Access Token to be deleted', + help='Access token to be deleted', ) return parser @@ -243,7 +243,7 @@ class ListAccessToken(lister.Lister): parser.add_argument( 'user', metavar='<user>', - help='Name or Id of user', + help='Name or ID of user', ) return parser diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 54ffe561..060eeca7 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -44,6 +44,12 @@ class CreateUser(show.ShowOne): help='New user password', ) parser.add_argument( + '--password-prompt', + dest="password_prompt", + action="store_true", + help='Prompt interactively for password', + ) + parser.add_argument( '--email', metavar='<user-email>', help='New user email address', @@ -97,6 +103,8 @@ class CreateUser(show.ShowOne): enabled = True if parsed_args.disable: enabled = False + if parsed_args.password_prompt: + parsed_args.password = utils.get_password(self.app.stdin) user = identity_client.users.create( parsed_args.name, @@ -174,7 +182,7 @@ class ListUser(lister.Lister): '--long', action='store_true', default=False, - help='Additional fields are listed in output', + help='List additional fields in output', ) return parser @@ -274,6 +282,12 @@ class SetUser(command.Command): help='New user password', ) parser.add_argument( + '--password-prompt', + dest="password_prompt", + action="store_true", + help='Prompt interactively for password', + ) + parser.add_argument( '--email', metavar='<user-email>', help='New user email address', @@ -310,6 +324,9 @@ class SetUser(command.Command): self.log.debug('take_action(%s)' % parsed_args) identity_client = self.app.client_manager.identity + if parsed_args.password_prompt: + parsed_args.password = utils.get_password(self.app.stdin) + if (not parsed_args.name and not parsed_args.name and not parsed_args.password diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 76cc3c6a..49307992 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -117,7 +117,7 @@ class OpenStackShell(app.App): action='store_true', dest='deferred_help', default=False, - help="show this help message and exit", + help="Show this help message and exit", ) def run(self, argv): @@ -147,21 +147,21 @@ class OpenStackShell(app.App): '--os-domain-name', metavar='<auth-domain-name>', default=env('OS_DOMAIN_NAME'), - help='Domain name of the requested domain-level' + help='Domain name of the requested domain-level ' 'authorization scope (Env: OS_DOMAIN_NAME)', ) parser.add_argument( '--os-domain-id', metavar='<auth-domain-id>', default=env('OS_DOMAIN_ID'), - help='Domain ID of the requested domain-level' + help='Domain ID of the requested domain-level ' 'authorization scope (Env: OS_DOMAIN_ID)', ) parser.add_argument( '--os-project-name', metavar='<auth-project-name>', default=env('OS_PROJECT_NAME', default=env('OS_TENANT_NAME')), - help='Project name of the requested project-level' + help='Project name of the requested project-level ' 'authorization scope (Env: OS_PROJECT_NAME)', ) parser.add_argument( @@ -174,7 +174,7 @@ class OpenStackShell(app.App): '--os-project-id', metavar='<auth-project-id>', default=env('OS_PROJECT_ID', default=env('OS_TENANT_ID')), - help='Project ID of the requested project-level' + help='Project ID of the requested project-level ' 'authorization scope (Env: OS_PROJECT_ID)', ) parser.add_argument( diff --git a/openstackclient/tests/common/test_utils.py b/openstackclient/tests/common/test_utils.py new file mode 100644 index 00000000..0ad4ca9b --- /dev/null +++ b/openstackclient/tests/common/test_utils.py @@ -0,0 +1,59 @@ +# Copyright 2012-2013 OpenStack, LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import mock + +from openstackclient.common import exceptions +from openstackclient.common import utils +from openstackclient.tests import utils as test_utils + +PASSWORD = "Pa$$w0rd" +WASSPORD = "Wa$$p0rd" +DROWSSAP = "dr0w$$aP" + + +class TestUtils(test_utils.TestCase): + + def test_get_password_good(self): + with mock.patch("getpass.getpass", return_value=PASSWORD): + mock_stdin = mock.Mock() + mock_stdin.isatty = mock.Mock() + mock_stdin.isatty.return_value = True + self.assertEqual(utils.get_password(mock_stdin), PASSWORD) + + def test_get_password_bad_once(self): + answers = [PASSWORD, WASSPORD, DROWSSAP, DROWSSAP] + with mock.patch("getpass.getpass", side_effect=answers): + mock_stdin = mock.Mock() + mock_stdin.isatty = mock.Mock() + mock_stdin.isatty.return_value = True + self.assertEqual(utils.get_password(mock_stdin), DROWSSAP) + + def test_get_password_no_tty(self): + mock_stdin = mock.Mock() + mock_stdin.isatty = mock.Mock() + mock_stdin.isatty.return_value = False + self.assertRaises(exceptions.CommandError, + utils.get_password, + mock_stdin) + + def test_get_password_cntrl_d(self): + with mock.patch("getpass.getpass", side_effect=EOFError()): + mock_stdin = mock.Mock() + mock_stdin.isatty = mock.Mock() + mock_stdin.isatty.return_value = True + self.assertRaises(exceptions.CommandError, + utils.get_password, + mock_stdin) diff --git a/openstackclient/tests/identity/v2_0/test_user.py b/openstackclient/tests/identity/v2_0/test_user.py index a18d13d8..dfb358ff 100644 --- a/openstackclient/tests/identity/v2_0/test_user.py +++ b/openstackclient/tests/identity/v2_0/test_user.py @@ -14,6 +14,7 @@ # import copy +import mock from openstackclient.identity.v2_0 import user from openstackclient.tests import fakes @@ -99,6 +100,7 @@ class TestUserCreate(TestUser): ] verifylist = [ ('name', identity_fakes.user_name), + ('password_prompt', False), ('password', 'secret') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -130,6 +132,47 @@ class TestUserCreate(TestUser): ) self.assertEqual(data, datalist) + def test_user_create_password_prompt(self): + arglist = [ + '--password-prompt', + identity_fakes.user_name, + ] + verifylist = [ + ('name', identity_fakes.user_name), + ('password_prompt', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + mocker = mock.Mock() + mocker.return_value = 'abc123' + with mock.patch("openstackclient.common.utils.get_password", mocker): + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'enabled': True, + 'tenant_id': None, + } + # UserManager.create(name, password, email, tenant_id=, enabled=) + self.users_mock.create.assert_called_with( + identity_fakes.user_name, + 'abc123', + None, + **kwargs + ) + + collist = ('email', 'enabled', 'id', 'name', 'project_id') + self.assertEqual(columns, collist) + datalist = ( + identity_fakes.user_email, + True, + identity_fakes.user_id, + identity_fakes.user_name, + identity_fakes.project_id, + ) + self.assertEqual(data, datalist) + def test_user_create_email(self): arglist = [ '--email', 'barney@example.com', @@ -498,6 +541,7 @@ class TestUserSet(TestUser): verifylist = [ ('name', None), ('password', 'secret'), + ('password_prompt', False), ('email', None), ('project', None), ('enable', False), @@ -515,6 +559,35 @@ class TestUserSet(TestUser): 'secret', ) + def test_user_set_password_prompt(self): + arglist = [ + '--password-prompt', + identity_fakes.user_name, + ] + verifylist = [ + ('name', None), + ('password', None), + ('password_prompt', True), + ('email', None), + ('project', None), + ('enable', False), + ('disable', False), + ('user', identity_fakes.user_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + mocker = mock.Mock() + mocker.return_value = 'abc123' + with mock.patch("openstackclient.common.utils.get_password", mocker): + self.cmd.take_action(parsed_args) + + # UserManager.update_password(user, password) + self.users_mock.update_password.assert_called_with( + identity_fakes.user_id, + 'abc123', + ) + def test_user_set_email(self): arglist = [ '--email', 'barney@example.com', diff --git a/openstackclient/tests/identity/v3/test_user.py b/openstackclient/tests/identity/v3/test_user.py index 4321b047..af7b2f70 100644 --- a/openstackclient/tests/identity/v3/test_user.py +++ b/openstackclient/tests/identity/v3/test_user.py @@ -14,6 +14,7 @@ # import copy +import mock from openstackclient.identity.v3 import user from openstackclient.tests import fakes @@ -118,6 +119,7 @@ class TestUserCreate(TestUser): ] verifylist = [ ('password', 'secret'), + ('password_prompt', False), ('enable', False), ('disable', False), ('name', identity_fakes.user_name), @@ -155,6 +157,54 @@ class TestUserCreate(TestUser): ) self.assertEqual(data, datalist) + def test_user_create_password_prompt(self): + arglist = [ + '--password-prompt', + identity_fakes.user_name, + ] + verifylist = [ + ('password', None), + ('password_prompt', True), + ('enable', False), + ('disable', False), + ('name', identity_fakes.user_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + mocker = mock.Mock() + mocker.return_value = 'abc123' + with mock.patch("openstackclient.common.utils.get_password", mocker): + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'default_project': None, + 'description': None, + 'domain': None, + 'email': None, + 'enabled': True, + 'password': 'abc123', + } + # UserManager.create(name, domain=, project=, password=, email=, + # description=, enabled=, default_project=) + self.users_mock.create.assert_called_with( + identity_fakes.user_name, + **kwargs + ) + + collist = ('domain_id', 'email', 'enabled', 'id', 'name', 'project_id') + self.assertEqual(columns, collist) + datalist = ( + identity_fakes.domain_id, + identity_fakes.user_email, + True, + identity_fakes.user_id, + identity_fakes.user_name, + identity_fakes.project_id, + ) + self.assertEqual(data, datalist) + def test_user_create_email(self): arglist = [ '--email', 'barney@example.com', @@ -761,6 +811,7 @@ class TestUserSet(TestUser): verifylist = [ ('name', None), ('password', 'secret'), + ('password_prompt', False), ('email', None), ('domain', None), ('project', None), @@ -785,6 +836,42 @@ class TestUserSet(TestUser): **kwargs ) + def test_user_set_password_prompt(self): + arglist = [ + '--password-prompt', + identity_fakes.user_name, + ] + verifylist = [ + ('name', None), + ('password', None), + ('password_prompt', True), + ('email', None), + ('domain', None), + ('project', None), + ('enable', False), + ('disable', False), + ('user', identity_fakes.user_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + mocker = mock.Mock() + mocker.return_value = 'abc123' + with mock.patch("openstackclient.common.utils.get_password", mocker): + self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'enabled': True, + 'password': 'abc123', + } + # UserManager.update(user, name=, domain=, project=, password=, + # email=, description=, enabled=, default_project=) + self.users_mock.update.assert_called_with( + identity_fakes.user_id, + **kwargs + ) + def test_user_set_email(self): arglist = [ '--email', 'barney@example.com', diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py index dbf6eb73..ac34749b 100644 --- a/openstackclient/volume/v1/backup.py +++ b/openstackclient/volume/v1/backup.py @@ -41,7 +41,7 @@ class CreateBackup(show.ShowOne): '--container', metavar='<container>', required=False, - help='Optional Backup container name.', + help='Optional backup container name.', ) parser.add_argument( '--name', diff --git a/openstackclient/volume/v1/type.py b/openstackclient/volume/v1/type.py index ecf22781..edacb397 100644 --- a/openstackclient/volume/v1/type.py +++ b/openstackclient/volume/v1/type.py @@ -100,7 +100,7 @@ class ListVolumeType(lister.Lister): '--long', action='store_true', default=False, - help='Additional fields are listed in output') + help='List additional fields in output') return parser def take_action(self, parsed_args): diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 0253bc1d..928ed76b 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -68,7 +68,7 @@ class CreateVolume(show.ShowOne): parser.add_argument( '--project', metavar='<project>', - help='Specify a diffeent project (admin only)', + help='Specify a different project (admin only)', ) parser.add_argument( '--availability-zone', diff --git a/requirements.txt b/requirements.txt index cfbc598b..3bf92e09 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,9 @@ -pbr>=0.5.21,<1.0 +pbr>=0.6,<1.0 cliff>=1.4.3 keyring>=1.6.1,<2.0,>=2.1 pycrypto>=2.6 python-glanceclient>=0.9.0 -python-keystoneclient>=0.4.2 +python-keystoneclient>=0.6.0 python-novaclient>=2.15.0 python-cinderclient>=1.0.6 requests>=1.1 diff --git a/test-requirements.txt b/test-requirements.txt index 34beccae..7e1fa06b 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -5,8 +5,8 @@ discover fixtures>=0.3.14 mock>=1.0 sphinx>=1.1.2,<1.2 -testrepository>=0.0.17 -testtools>=0.9.32 -WebOb>=1.2.3,<1.3 +testrepository>=0.0.18 +testtools>=0.9.34 +WebOb>=1.2.3 Babel>=1.3 |
