summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.rst69
-rw-r--r--debian/CHANGELOG1299
-rw-r--r--debian/changelog12
-rw-r--r--debian/control15
-rw-r--r--debian/patches/series1
-rw-r--r--debian/python-openstackclient.doc-base9
-rw-r--r--debian/python-openstackclient.docs1
-rwxr-xr-xdebian/rules10
-rw-r--r--doc/source/_static/basic.css416
-rw-r--r--doc/source/_static/default.css230
-rw-r--r--doc/source/_static/header-line.gifbin0 -> 48 bytes
-rw-r--r--doc/source/_static/header_bg.jpgbin0 -> 3738 bytes
-rw-r--r--doc/source/_static/jquery.tweet.js154
-rw-r--r--doc/source/_static/nature.css245
-rw-r--r--doc/source/_static/openstack_logo.pngbin0 -> 3670 bytes
-rw-r--r--doc/source/_static/tweaks.css94
-rw-r--r--doc/source/_theme/layout.html83
-rw-r--r--doc/source/_theme/theme.conf4
-rw-r--r--doc/source/commands.rst54
-rw-r--r--doc/source/conf.py3
-rw-r--r--doc/source/index.rst8
-rw-r--r--doc/source/man/openstack.rst116
-rw-r--r--doc/source/plugins.rst4
-rw-r--r--doc/source/releases.rst60
-rw-r--r--openstack-common.conf2
-rw-r--r--openstackclient/common/__init__.py14
-rw-r--r--openstackclient/common/clientmanager.py18
-rw-r--r--openstackclient/common/commandmanager.py1
-rw-r--r--openstackclient/common/extension.py78
-rw-r--r--openstackclient/common/limits.py2
-rw-r--r--openstackclient/common/module.py2
-rw-r--r--openstackclient/common/openstackkeyring.py12
-rw-r--r--openstackclient/common/quota.py4
-rw-r--r--openstackclient/common/utils.py57
-rw-r--r--openstackclient/compute/__init__.py14
-rw-r--r--openstackclient/compute/client.py9
-rw-r--r--openstackclient/compute/v2/__init__.py14
-rw-r--r--openstackclient/compute/v2/agent.py18
-rw-r--r--openstackclient/compute/v2/aggregate.py14
-rw-r--r--openstackclient/compute/v2/console.py14
-rw-r--r--openstackclient/compute/v2/fixedip.py4
-rw-r--r--openstackclient/compute/v2/flavor.py8
-rw-r--r--openstackclient/compute/v2/floatingip.py12
-rw-r--r--openstackclient/compute/v2/floatingippool.py2
-rw-r--r--openstackclient/compute/v2/host.py4
-rw-r--r--openstackclient/compute/v2/hypervisor.py4
-rw-r--r--openstackclient/compute/v2/keypair.py10
-rw-r--r--openstackclient/compute/v2/security_group.py38
-rw-r--r--openstackclient/compute/v2/server.py90
-rw-r--r--openstackclient/compute/v2/service.py4
-rw-r--r--openstackclient/compute/v2/usage.py13
-rw-r--r--openstackclient/identity/__init__.py14
-rw-r--r--openstackclient/identity/client.py13
-rw-r--r--openstackclient/identity/common.py38
-rw-r--r--openstackclient/identity/v2_0/__init__.py14
-rw-r--r--openstackclient/identity/v2_0/ec2creds.py14
-rw-r--r--openstackclient/identity/v2_0/endpoint.py107
-rw-r--r--openstackclient/identity/v2_0/project.py34
-rw-r--r--openstackclient/identity/v2_0/role.py14
-rw-r--r--openstackclient/identity/v2_0/service.py38
-rw-r--r--openstackclient/identity/v2_0/token.py60
-rw-r--r--openstackclient/identity/v2_0/user.py48
-rw-r--r--openstackclient/identity/v3/__init__.py14
-rw-r--r--openstackclient/identity/v3/consumer.py33
-rw-r--r--openstackclient/identity/v3/credential.py16
-rw-r--r--openstackclient/identity/v3/domain.py14
-rw-r--r--openstackclient/identity/v3/endpoint.py59
-rw-r--r--openstackclient/identity/v3/group.py139
-rw-r--r--openstackclient/identity/v3/identity_provider.py180
-rw-r--r--openstackclient/identity/v3/policy.py24
-rw-r--r--openstackclient/identity/v3/project.py52
-rw-r--r--openstackclient/identity/v3/role.py118
-rw-r--r--openstackclient/identity/v3/role_assignment.py156
-rw-r--r--openstackclient/identity/v3/service.py32
-rw-r--r--openstackclient/identity/v3/token.py150
-rw-r--r--openstackclient/identity/v3/user.py154
-rw-r--r--openstackclient/image/__init__.py14
-rw-r--r--openstackclient/image/client.py2
-rw-r--r--openstackclient/image/v1/__init__.py14
-rw-r--r--openstackclient/image/v1/image.py314
-rw-r--r--openstackclient/image/v2/__init__.py14
-rw-r--r--openstackclient/image/v2/image.py8
-rw-r--r--openstackclient/object/__init__.py12
-rw-r--r--openstackclient/object/v1/__init__.py12
-rw-r--r--openstackclient/object/v1/container.py4
-rw-r--r--openstackclient/object/v1/lib/__init__.py12
-rw-r--r--openstackclient/object/v1/lib/object.py1
-rw-r--r--openstackclient/object/v1/object.py4
-rw-r--r--openstackclient/openstack/common/cfg.py1731
-rw-r--r--openstackclient/openstack/common/gettextutils.py497
-rw-r--r--openstackclient/openstack/common/strutils.py104
-rw-r--r--openstackclient/shell.py136
-rw-r--r--openstackclient/tests/__init__.py14
-rw-r--r--openstackclient/tests/common/__init__.py14
-rw-r--r--openstackclient/tests/common/test_extension.py128
-rw-r--r--openstackclient/tests/common/test_parseractions.py2
-rw-r--r--openstackclient/tests/common/test_restapi.py5
-rw-r--r--openstackclient/tests/common/test_utils.py132
-rw-r--r--openstackclient/tests/compute/__init__.py14
-rw-r--r--openstackclient/tests/compute/v2/__init__.py14
-rw-r--r--openstackclient/tests/compute/v2/fakes.py21
-rw-r--r--openstackclient/tests/compute/v2/test_server.py6
-rw-r--r--openstackclient/tests/fakes.py3
-rw-r--r--openstackclient/tests/identity/__init__.py14
-rw-r--r--openstackclient/tests/identity/v2_0/__init__.py14
-rw-r--r--openstackclient/tests/identity/v2_0/fakes.py57
-rw-r--r--openstackclient/tests/identity/v2_0/test_endpoint.py266
-rw-r--r--openstackclient/tests/identity/v2_0/test_project.py65
-rw-r--r--openstackclient/tests/identity/v2_0/test_token.py77
-rw-r--r--openstackclient/tests/identity/v2_0/test_user.py85
-rw-r--r--openstackclient/tests/identity/v3/__init__.py14
-rw-r--r--openstackclient/tests/identity/v3/fakes.py133
-rw-r--r--openstackclient/tests/identity/v3/test_consumer.py200
-rw-r--r--openstackclient/tests/identity/v3/test_group.py193
-rw-r--r--openstackclient/tests/identity/v3/test_identity_provider.py368
-rw-r--r--openstackclient/tests/identity/v3/test_oauth.py152
-rw-r--r--openstackclient/tests/identity/v3/test_project.py132
-rw-r--r--openstackclient/tests/identity/v3/test_role.py196
-rw-r--r--openstackclient/tests/identity/v3/test_role_assignment.py388
-rw-r--r--openstackclient/tests/identity/v3/test_service.py24
-rw-r--r--openstackclient/tests/identity/v3/test_token.py79
-rw-r--r--openstackclient/tests/identity/v3/test_user.py296
-rw-r--r--openstackclient/tests/image/__init__.py14
-rw-r--r--openstackclient/tests/image/v1/__init__.py14
-rw-r--r--openstackclient/tests/image/v1/fakes.py28
-rw-r--r--openstackclient/tests/image/v1/test_image.py385
-rw-r--r--openstackclient/tests/image/v2/__init__.py14
-rw-r--r--openstackclient/tests/object/__init__.py12
-rw-r--r--openstackclient/tests/object/v1/__init__.py12
-rw-r--r--openstackclient/tests/object/v1/lib/__init__.py12
-rw-r--r--openstackclient/tests/test_shell.py182
-rw-r--r--openstackclient/tests/volume/__init__.py14
-rw-r--r--openstackclient/tests/volume/test_find_resource.py71
-rw-r--r--openstackclient/tests/volume/v1/__init__.py14
-rw-r--r--openstackclient/tests/volume/v1/fakes.py35
-rw-r--r--openstackclient/tests/volume/v1/test_volume.py157
-rw-r--r--openstackclient/volume/__init__.py14
-rw-r--r--openstackclient/volume/client.py22
-rw-r--r--openstackclient/volume/v1/__init__.py14
-rw-r--r--openstackclient/volume/v1/backup.py12
-rw-r--r--openstackclient/volume/v1/snapshot.py10
-rw-r--r--openstackclient/volume/v1/type.py25
-rw-r--r--openstackclient/volume/v1/volume.py28
-rw-r--r--requirements.txt10
-rw-r--r--setup.cfg24
-rw-r--r--setup.py8
-rw-r--r--test-requirements.txt10
-rw-r--r--tools/install_venv.py1
-rw-r--r--tools/install_venv_common.py2
-rw-r--r--tox.ini9
150 files changed, 8455 insertions, 3567 deletions
diff --git a/README.rst b/README.rst
index b0f3b6b..6da3d6a 100644
--- a/README.rst
+++ b/README.rst
@@ -2,59 +2,71 @@
OpenStack Client
================
-python-openstackclient is a unified command-line client for the OpenStack APIs.
-It is a thin wrapper to the stock python-*client modules that implement the
+OpenStackclient (aka ``python-openstackclient``) is a command-line client for
+the OpenStack APIs.
+It is primarily a wrapper to the stock python-*client modules that implement the
actual REST API client actions.
This is an implementation of the design goals shown in
-http://wiki.openstack.org/UnifiedCLI. The primary goal is to provide
+`OpenStack Client Wiki`_. The primary goal is to provide
a unified shell command structure and a common language to describe
-operations in OpenStack.
+operations in OpenStack. The master repository is on GitHub_.
-python-openstackclient is designed to add support for API extensions via a
-plugin mechanism.
+.. _OpenStack Client Wiki: https://wiki.openstack.org/wiki/OpenStackClient
+.. _GitHub: https://github.com/openstack/python-openstackclient
-For release management::
+OpenStackclient has a plugin mechanism to add support for API extensions.
- * https://launchpad.net/python-openstackclient
-
-For blueprints and feature specifications::
-
- * https://blueprints.launchpad.net/python-openstackclient
-
-For issue tracking::
-
- * https://bugs.launchpad.net/python-openstackclient
+* `Release management`_
+* `Blueprints and feature specifications`_
+* `Issue tracking`_
+* `PyPi`_
+* `Developer Docs`_
+.. _release management: https://launchpad.net/python-openstackclient
+.. _Blueprints and feature specifications: https://blueprints.launchpad.net/python-openstackclient
+.. _Issue tracking: https://bugs.launchpad.net/python-openstackclient
+.. _PyPi: https://pypi.python.org/pypi/python-openstackclient
+.. _Developer Docs: http://docs.openstack.org/developer/python-openstackclient/
+.. _install virtualenv: tools/install_venv.py
Note
====
-OpenStackClient is considered to be alpha release quality as of the 0.2 release;
+OpenStackClient is considered to be beta release quality as of the 0.3 release;
no assurances are made at this point for ongoing compatibility in command forms
or output. We do not, however, expect any major changes at this point.
Getting Started
===============
-We recommend using a virtualenv to install the client. This description
-uses the `install_venv.py`_ script to create the virtualenv::
+OpenStackclient can be installed from PyPI using pip::
+
+ pip install python-openstackclient
+
+Developers can use the `install virtualenv`_ script to create the virtualenv::
python tools/install_venv.py
source .venv/bin/activate
python setup.py develop
-Unit tests can be ran simply by running::
-
- run_tests.sh
+Unit tests are now run using tox. The ``run_test.sh`` script provides compatibility
+but is generally considered deprecated.
The client can be called interactively by simply typing::
openstack
-Alternatively command line parameters can be called non-interactively::
+There are a few variants on getting help. A list of global options and supported
+commands is shown with ``--help``::
openstack --help
+There is also a ``help`` command that can be used to get help text for a specific
+command::
+
+ openstack help
+ openstack help server create
+
Configuration
=============
@@ -82,7 +94,7 @@ prompted to provide one securely. If keyring is enabled, the password entered
in the prompt is stored in keyring. From next time, the password is read from
keyring, if it is not provided above (in plaintext).
-The token flow variation for authentication uses an already-aquired token
+The token flow variation for authentication uses an already-acquired token
and a URL pointing directly to the service API that presumably was acquired
from the Service Catalog::
@@ -113,10 +125,9 @@ The source is maintained in the ``doc/source`` folder using
.. _reStructuredText: http://docutils.sourceforge.net/rst.html
.. _Sphinx: http://sphinx.pocoo.org/
-* Building Manually::
+Building Manually::
- $ export DJANGO_SETTINGS_MODULE=local.local_settings
- $ python doc/generate_autodoc_index.py
- $ sphinx-build -b html doc/source build/sphinx/html
+ cd doc
+ make html
-Results are in the `build/sphinx/html` directory.
+Results are in the ``build/html`` directory.
diff --git a/debian/CHANGELOG b/debian/CHANGELOG
index 49e6f51..455cade 100644
--- a/debian/CHANGELOG
+++ b/debian/CHANGELOG
@@ -1,3 +1,1302 @@
+commit a732bfae491f8189f234e1bd5c2cbbff3daab4bc
+Merge: 23f64a4 3fa5fa5
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Fri Jun 20 20:05:58 2014 +0000
+
+ Merge "Update docs and release notes for 0.4.0"
+
+commit 3fa5fa5ba745cec0ee96f1031fadff0d5e7820f0
+Author: Dean Troyer <dtroyer@gmail.com>
+Date: Thu May 15 09:05:37 2014 -0500
+
+ Update docs and release notes for 0.4.0
+
+ Change-Id: Iad6cfe5dee63adb9e60a0ea9811217b3175eb99c
+
+commit 23f64a469a5ba77b2d06e10dc4e6c7d318f11835
+Merge: 3019f11 9dd3a53
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Fri Jun 20 15:46:39 2014 +0000
+
+ Merge "Complete Identity v3 list command filters"
+
+commit 3019f11032459c10efea152186bd630479867f37
+Author: OpenStack Proposal Bot <openstack-infra@lists.openstack.org>
+Date: Fri Jun 20 03:38:56 2014 +0000
+
+ Updated from global requirements
+
+ Change-Id: I708fe9d2f10e53d61e67130ff41b0f92cddef64d
+
+commit 9dd3a5326c0fe04df3b4224754fd02351a4623c9
+Author: Dean Troyer <dtroyer@gmail.com>
+Date: Thu Jun 19 12:38:30 2014 -0500
+
+ Complete Identity v3 list command filters
+
+ Complete the 'group list' and 'user list' filter options following
+ the refactor in https://review.openstack.org/69878
+
+ Change-Id: Ib4af417c56d4f7da4b88852f191af615cc7fa2ec
+
+commit c6cc1d72d50404b5921b9d451afcb513ad98ff9a
+Merge: 0a491b2 f78a3f1
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Thu Jun 19 17:14:03 2014 +0000
+
+ Merge "Refactor role list subcommand for identity v3 api"
+
+commit 0a491b2ac82553d5bec10e782c582fca3a88c148
+Merge: 982b418 25d6955
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Wed Jun 18 22:44:24 2014 +0000
+
+ Merge "Change the token verb to issue/revoke"
+
+commit 25d6955bb477241af0926ca90969b9e0c1ec5647
+Author: Terry Howe <terrylhowe@gmail.com>
+Date: Wed Jun 18 11:19:31 2014 -0600
+
+ Change the token verb to issue/revoke
+
+ Change the token verb to issue/revoke as documented in:
+
+ https://wiki.openstack.org/wiki/OpenStackClient/Commands#token
+ https://wiki.openstack.org/wiki/OpenStackClient/Commands#Actions
+
+ Change-Id: I44f77f98ad3269c4f2149301c204804dcb75ac81
+
+commit 982b418da78e5d729ba0c4f3113bb691eb78b7c7
+Merge: ddcc668 deaff72
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Wed Jun 18 15:40:04 2014 +0000
+
+ Merge "Update docs template"
+
+commit ddcc66839372506f607d8f65b9a085b181291f2b
+Merge: 1f2189d 89cbdd1
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Wed Jun 18 15:38:49 2014 +0000
+
+ Merge "Add a docs job to tox.ini"
+
+commit 1f2189d6ded9a13637608cf13b7e82591c39b99b
+Merge: 0b2987f d6321c0
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Wed Jun 18 15:37:40 2014 +0000
+
+ Merge "Add token delete command for identity v2"
+
+commit deaff7274e4d0782a7ddedb5c7d371fe5a9bb141
+Author: Steve Martinelli <stevemar@ca.ibm.com>
+Date: Tue Jun 17 23:42:25 2014 -0400
+
+ Update docs template
+
+ To make things more consistent across all openstack projects, the
+ developer docs should be upgraded to the newer template used by
+ keystone and keystoneclient (and other projects).
+
+ I dropped in the necessary static files and themes, and updated
+ the config file to make the changes at build time.
+
+ Change-Id: I5a268cff3cd5af29ad712705d540b9d1d6667d56
+ Partial-Bug: #1331304
+
+commit 89cbdd1ae1f0ed2e55ffdc43fbed07e417751455
+Author: Steve Martinelli <stevemar@ca.ibm.com>
+Date: Tue Jun 17 23:24:55 2014 -0400
+
+ Add a docs job to tox.ini
+
+ A noticed that there wasn't a docs option when running tox.
+ Thought it would be a good idea to add one.
+
+ Partial-Bug: #1331304
+ Change-Id: I5af9ff5d5986ad546146c0fa73eb98256fd00c5f
+
+commit 0b2987fef389603b95b2ba7b788492b8baa56745
+Author: Terry Howe <terrylhowe@gmail.com>
+Date: Thu May 22 17:38:41 2014 -0600
+
+ Fix find_resource for keystone and cinder
+
+ The find_resource method had two hacks in in to support cinder
+ and keystone and I have removed those in favor of a monkey patch
+ for cinder.
+
+ The find_resource method used to attempt to UUID parse the id, but
+ it would do a manager.get anyway. I changed it to skip the UUID
+ parsing. This will make things run minorly faster and it supports
+ LDAP for keystone.
+
+ The find_resource used to attempt to use display_name=name_or_id
+ when finding. This was a hack for cinder support, but it breaks
+ keystone because keystone totally messes up with the bogus filter
+ and keystone refuses to fix it.
+
+ Change-Id: I66e45a6341f704900f1d5321a0e70eac3d051665
+ Closes-Bug: #1306699
+
+commit 6380b8b95918a42cee63c21b90f7d8d55854d0c8
+Author: Dean Troyer <dtroyer@gmail.com>
+Date: Thu May 8 11:09:22 2014 -0500
+
+ Image create and set command updates and tests
+
+ Refactor image create and set commands to properly handle properties.
+ This is particularly tricky with exclusive booleans as in this case leaving
+ both choices off the command line should NOT assume a default value but
+ leave the existing value unchanged.
+
+ Properties were not being updated correctly in the 'image set' command.
+ Refactor it to use the same pattern as in other SetXxx commands.
+ Add tests for arg handling.
+
+ Change-Id: I123a64c9b4feecab25a3e2013cc047f55b1c9967
+
+commit aba1fb2268b3e5a2c785742861d7ae086b18e9a7
+Merge: fbb9e7a d5aaba9
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Tue Jun 17 01:23:25 2014 +0000
+
+ Merge "Refactor oauth1 code for updates"
+
+commit fbb9e7aefa3838f6c03b1d17111011c76ece7706
+Merge: 5c2d9f3 67354f6
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Mon Jun 16 21:32:31 2014 +0000
+
+ Merge "Clean up logging levels"
+
+commit 5c2d9f386b99c04b2d9683d658d952473885b9a8
+Merge: 0da5bfe 5805596
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Sun Jun 15 00:17:45 2014 +0000
+
+ Merge "Updated from global requirements"
+
+commit d5aaba9d8284ea1cafe137b367ef9c9297b31e75
+Author: Steve Martinelli <stevemar@ca.ibm.com>
+Date: Mon Dec 9 17:50:07 2013 -0600
+
+ Refactor oauth1 code for updates
+
+ The keystoneclient code for oauth1 support has changed.
+ As such, we should remove the delete, list and authenticate
+ functions, since they are not in keystoneclient.
+
+ Also, we must now pass in the project id when creating a
+ request token. Additionally we must now pass in roles
+ when authorizing a request token.
+
+ Added functional tests to ensure output and input args
+ are the same.
+
+ bp add-oauth-support
+ Change-Id: I559c18a73ad95a0c8b7a6a95f463b78334186f61
+
+commit 0da5bfe42875457a26a0c5040fcc3b0e1ffb5d2a
+Merge: 99e7660 0059f04
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Sat Jun 14 18:37:12 2014 +0000
+
+ Merge "Ignore most of the new hacking 0.9.2 rules"
+
+commit 99e7660d56431d5480999c2674b1aa12254732b2
+Merge: 497a389 4ae4dc3
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Sat Jun 14 06:25:38 2014 +0000
+
+ Merge "Add support for extension list"
+
+commit 5805596013435835842f9b050485f554e8758a02
+Author: OpenStack Proposal Bot <openstack-infra@lists.openstack.org>
+Date: Fri Jun 13 22:57:28 2014 +0000
+
+ Updated from global requirements
+
+ Change-Id: I44f13a22528824a8b24ffb3b0e3075e870e5ee58
+
+commit 67354f651b064c7c67ad749bf75196d59b851d18
+Author: Dean Troyer <dtroyer@gmail.com>
+Date: Wed May 7 10:54:38 2014 -0500
+
+ Clean up logging levels
+
+ The following logging levels are set according to the combination of
+ --verbose, --quiet and --debug options:
+
+ verbose_level logging level options
+ 0 --quiet ERROR
+ 1 (none) WARNING
+ 2 --verbose INFO
+ 3+ --verbose --verbose DEBUG
+ or --debug
+
+ Logging levels for the requests and iso8601 modules are forced to ERROR.
+
+ This is the first step in bp use-logging-not-print
+
+ The difference between '--debug' and '--verbose --verbose' is --debug triggers
+ cliff's exception handling and traceback display.
+
+ Change-Id: Ide2233b3316471d279260fb1e7255a6ca2072023
+
+commit 0059f045a9958b1748fcec51799d8cffc0831440
+Author: Dean Troyer <dtroyer@gmail.com>
+Date: Fri Jun 13 16:56:32 2014 -0500
+
+ Ignore most of the new hacking 0.9.2 rules
+
+ So we can update requriements.txt. But fix a couple of easy ones:
+ * Fix E251 (1 occurrance)
+ * Fix E131 (1 occurrance)
+
+ Change-Id: I62aaa423aa6da9e9f0ca026ec586b51cc6a6df03
+
+commit 497a38903c4a282975b2f197bc4af22c15d8ab21
+Merge: 7f6a901 58f80e4
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Fri Jun 13 20:56:27 2014 +0000
+
+ Merge "Add role assignments list support to identity v3"
+
+commit f78a3f1653c634e40071fb96750b9ca64865c058
+Author: Qiu Yu <qiuyu@ebaysf.com>
+Date: Wed Jan 29 22:54:01 2014 +0800
+
+ Refactor role list subcommand for identity v3 api
+
+ Currently parts of user list and group list command are actually
+ functioning as role listing, which is quite counter intuitive and
+ misleading.
+
+ This refactor change move role related logic to a single place of role
+ list command. It now allows role grants listing for user/group +
+ domain/project combinations.
+
+ If no user or group specified, it will list all roles in the system,
+ which is the default behaviour.
+
+ Change-Id: I4ced6df4b76f018d01000d28b4281ad9f252ffcc
+
+commit 4ae4dc35bda42a972c1d1480e89cda67bf39636d
+Author: Matt Fischer <matt@mattfischer.com>
+Date: Tue May 13 13:04:02 2014 -0400
+
+ Add support for extension list
+
+ - Add support in the common section for extension list. This only
+ supports Identity for now. Once the APIs for volume and compute
+ are supported in the respective APIs, they will be added. Once
+ network is added to this client, it will be added (the API already
+ supports it).
+ - Include extension fakes for volume and compute for pre-enablement.
+
+ Change-Id: Iebb0156a779887d2ab06488a2a27b70b56369376
+ Closes-Bug: #1319115
+
+commit 7f6a901d011ae366a5dea6fa7e532ab941125e9e
+Merge: 3b925e9 dc44ec1
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Tue Jun 10 02:27:55 2014 +0000
+
+ Merge "Add tests for identity endpoints"
+
+commit 3b925e9806cb942c6333d4038ebdad65cd9390df
+Merge: fb65652 3b485de
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Tue Jun 10 02:27:54 2014 +0000
+
+ Merge "replace string format arguments with function parameters"
+
+commit 58f80e4c7544c0ee064c1629b3f128a628fc71d8
+Author: henriquetruta <henrique@lsd.ufcg.edu.br>
+Date: Wed May 28 11:09:46 2014 -0300
+
+ Add role assignments list support to identity v3
+
+ The assignments manager and its test class were created.
+ Some fake stubs were also added on the fakes.py module.
+
+ The "openstack role assignment list" command was created.
+
+ Change-Id: Iae94f4fee608ea3e09ff38961ad22edc38efb89c
+ Implements: blueprint roles-assignment-list
+ Closes-Bug: 1246310
+
+commit fb656527530444c116dce493abd85cc69e8902fb
+Merge: 7ceff0e a8087a6
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Wed May 28 23:05:54 2014 +0000
+
+ Merge "Fixed several typos throughout the codebase"
+
+commit 7ceff0eafc01c038ac5264d91701ee6384ff0b31
+Merge: 870e7dd da5e31d
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Wed May 28 22:05:53 2014 +0000
+
+ Merge "Fix server image create"
+
+commit d6321c0893d529af1548da79a985f337bce7069f
+Author: Terry Howe <terrylhowe@gmail.com>
+Date: Fri May 23 10:17:42 2014 -0600
+
+ Add token delete command for identity v2
+
+ Identity v2 has undocumented support for token delete and
+ keystoneclient also has support.
+
+ Change-Id: Ib98d17958ceb88f7b63471691dee71fdb884ce2e
+ Closes-Bug: #1318442
+
+commit a8087a6c8b5946ecf25f019e183b26579c3475a8
+Author: Alex Gaynor <alex.gaynor@gmail.com>
+Date: Wed May 21 07:47:29 2014 -0700
+
+ Fixed several typos throughout the codebase
+
+ Change-Id: I048ee857fc1215fea7f60978364894e1b5abdf66
+
+commit 3b485de6b0495d1e9f6d659463a187d73a783594
+Author: Christian Berendt <berendt@b1-systems.de>
+Date: Tue May 20 13:11:19 2014 +0200
+
+ replace string format arguments with function parameters
+
+ There are files containing string format arguments inside
+ logging messages. Using logging function parameters should
+ be preferred.
+
+ Change-Id: Ic749ac9eb55564ed631d57055a5a4dfc3aebd169
+
+commit dc44ec1ddf09177430d79f7c91c9ba36c3a44a9d
+Author: Matt Fischer <matt@mattfischer.com>
+Date: Thu May 15 12:20:30 2014 -0400
+
+ Add tests for identity endpoints
+
+ Change-Id: If15cc74fafbbe52fa86aa353f2598aa31daf0695
+ Closes-Bug: #1319450
+
+commit 870e7ddbcce5d5f1b1bd36bb3ad95665565b3ced
+Author: Dean Troyer <dtroyer@gmail.com>
+Date: Fri May 9 09:28:53 2014 -0500
+
+ Change volume create --volume-type to --type
+
+ This makes it consistent with the other --type options in OSC. Also
+ add a few more volume_create tests.
+
+ Change-Id: I50ef927932cabf157ecdfd6c4faa1914b4fdf413
+
+commit a93851d6af899e18638039aee06465c025e1e037
+Merge: 03b9a72 ef9496a
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Fri May 9 21:38:20 2014 +0000
+
+ Merge "Implement CRUD operations for Identity Providers"
+
+commit da5e31dbb629c26f54e476ca3587455c3a17cdcb
+Author: Dean Troyer <dtroyer@gmail.com>
+Date: Thu May 8 10:58:17 2014 -0500
+
+ Fix server image create
+
+ The final find_resource() call errored because servers.create_image()
+ returns an image ID rather than an Image resource. Reset expectations
+ and arguments.
+
+ Change-Id: I1b9132f66091f9df76198724156acb7a6fb2f6fe
+
+commit 03b9a7282ba94eeb901c758d825ba1616006050c
+Merge: 3b57117 f7f8fe4
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Wed May 7 16:40:41 2014 +0000
+
+ Merge "Fix help message for `ip floating delete`"
+
+commit 3b57117f8efc0c945f730c5e8a598768ac774114
+Author: Yejia Xu <yejia@unitedstack.com>
+Date: Wed May 7 01:49:15 2014 +0000
+
+ Display all server log when --lines option is None
+
+ Without --lines option, `console log show xxx`
+ cmd will break.
+
+ Change-Id: I4027aacac245e6916c1808fd9f878fb708c8a5f0
+ Closes-Bug: #1316870
+
+commit f7f8fe4e7c4229562bed04faf21a1cf6e7d3a56b
+Author: Yejia Xu <yejia@unitedstack.com>
+Date: Wed May 7 02:13:03 2014 +0000
+
+ Fix help message for `ip floating delete`
+
+ Previously, the help message incorrectly had the
+ string "IP address to add to server". This should
+ probably read "IP address to delete".
+
+ Change-Id: If592b736448199f84c30e0cbc8110a0a76e2c140
+ Closes-Bug: #1316877
+
+commit 834867b8bfaab1dcbad5f1e88e10f8a10c48f626
+Merge: 2cc3a2f 37231b5
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Tue May 6 06:27:28 2014 +0000
+
+ Merge "volume type create should display properties"
+
+commit 37231b5801162c9fcbd2a704a6660021dda6327d
+Author: Terry Howe <terrylhowe@gmail.com>
+Date: Tue Apr 22 09:08:52 2014 -0600
+
+ volume type create should display properties
+
+ The volume type create command should properly output the
+ properties. The code was doing a create on the volume type and
+ then setting the properties, but it was printing out the volume
+ object from the create.
+
+ Change-Id: I23c8a0182e77bb71903ad87c1b01ba2b62405f3b
+ Closes-Bug: #1303978
+
+commit 2cc3a2fdbddb10cc26ffb49e4a7cfa114a1e9e53
+Author: Yejia Xu <yejia@unitedstack.com>
+Date: Mon May 5 00:11:17 2014 +0000
+
+ Skip auth in cinderclient
+
+ cinderclient can't work well with keystone v3 auth
+ info. We should do it in openstackclient just like
+ compute extension.
+
+ Closes-Bug: #1315963
+ Change-Id: I46f794c5315f6a9fe1d9a0e5dc7b84f067d7f792
+
+commit 6c5f2e39e23a11236986b119974b90bf15f73878
+Author: OpenStack Proposal Bot <openstack-infra@lists.openstack.org>
+Date: Thu May 1 13:50:49 2014 +0000
+
+ Updated from global requirements
+
+ Change-Id: Idde32a0bdcee8843c09a968dff69b246b5e784f7
+
+commit ef9496a4fc9b8600dac88666b7159119e663642c
+Author: Marek Denis <marek.denis@cern.ch>
+Date: Wed Apr 9 19:05:36 2014 +0200
+
+ Implement CRUD operations for Identity Providers
+
+ Operations for:
+ * adding Identity Provider
+ * listing Identity Providers
+ * showing Identity Provider
+ * updating Identity Provider
+ * deleting Identity Provider
+
+ Change-Id: I4557168309f93e4670116b5c3c0e29252ff0c40f
+ Implements: bp/add-openstackclient-federation-crud
+
+commit c744abdbdaf2e3dbce028b5535cb6da0632f3284
+Merge: 2643df7 dde02b8
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Wed Apr 23 23:11:50 2014 +0000
+
+ Merge "Updated from global requirements"
+
+commit 2643df740514376c66b38032fb64a30a73eabe0b
+Merge: 845f670 bea6e6a
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Wed Apr 23 23:11:49 2014 +0000
+
+ Merge "Make endpoint commands more consistent"
+
+commit 845f6705bf65725830b1a7bba22870f177668d86
+Merge: 8ec78a1 e72072a
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Wed Apr 23 23:11:48 2014 +0000
+
+ Merge "Fix the project option to user list so it filters"
+
+commit dde02b8a38daa46e3680bd512ea549d265faa00c
+Author: OpenStack Proposal Bot <openstack-infra@lists.openstack.org>
+Date: Sun Apr 20 09:59:03 2014 +0000
+
+ Updated from global requirements
+
+ Change-Id: Ib4416938530bd1037cc4b7e84bf81475d91e6b16
+
+commit 8ec78a10f8ee35b607c4a7c0260de31178cdf5b0
+Merge: 3c3f212 022b6d9
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Thu Apr 17 02:40:36 2014 +0000
+
+ Merge "Pass arguments to v3 keystoneclient by kwarg"
+
+commit 3c3f212d8b3646e4cd05c4ac6051636fa3a8a671
+Merge: 398dc6c 211cd31
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Thu Apr 17 02:06:11 2014 +0000
+
+ Merge "Make bash comple command best effort to authorize"
+
+commit 398dc6c0bdb1ceb3211a748e0c076930fb81a7d1
+Merge: 01db6d9 e9015b9
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Thu Apr 17 02:04:18 2014 +0000
+
+ Merge "In anticipation of network agents, rename compute"
+
+commit 01db6d977f518d6a348a89549ca566c4aa780e79
+Author: Steve Martinelli <stevemar@ca.ibm.com>
+Date: Tue Apr 1 10:30:03 2014 -0500
+
+ move read_blob_file_contents to utils
+
+ Thinking ahead, a few other upcoming keystone features could
+ benefit from reading contents from a file. Thus, moving the
+ function from policy to utils.
+
+ Change-Id: I713ab0e5a00c949ad996daf83b775a7c19044888
+
+commit 022b6d95d167405fa6534680c8a7fe449b35ce77
+Author: Jamie Lennox <jamielennox@redhat.com>
+Date: Fri Apr 4 08:09:43 2014 +1000
+
+ Pass arguments to v3 keystoneclient by kwarg
+
+ Keystoneclient has added the positional decorator which emits a warning
+ if arguments aren't passed by keyword. This means we are getting
+ warnings in certain places in openstackclient.
+
+ Change-Id: Ic5446cd6f122cbb56fce543011386d53bc31fe18
+ Closes-Bug: #1302199
+
+commit e72072adc3b62b5ef8e3076169fed19dea9995f7
+Author: Terry Howe <terrylhowe@gmail.com>
+Date: Mon Mar 31 14:48:42 2014 -0600
+
+ Fix the project option to user list so it filters
+
+ The --project option to the user list command was not implemented
+ * Allow users to be filted by project
+ * Support id or name of project with the find_resource command
+ * Make sure the report does not contain duplicates
+
+ Change-Id: Ic0e10cccd7749d38a7d4b80bbdc68e61a660084b
+ Closes-Bug: #1177255
+
+commit bea6e6ac23e893a85f4b0a49bab52934aca86726
+Author: Terry Howe <terrylhowe@gmail.com>
+Date: Wed Mar 12 11:51:17 2014 -0600
+
+ Make endpoint commands more consistent
+
+ Make endpoints more consistent across create, show, etc
+ * Make the name option required for create
+ * Use a common function to fetch services by id, name or type
+ * Have show work by endpoint id or by service id, type or name
+ * Have show display all the fields by default
+ * Remove capability to filter queries by attribute value pairs
+
+ Change-Id: Idaa4b8d930ba859fd62de777e44a10b1ed58c79b
+ Partial-Bug: #1184012
+
+commit ee22070473dab8bfa3e89d47f1f5a77918c2b026
+Merge: 4900c64 8117256
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Sun Mar 23 18:43:24 2014 +0000
+
+ Merge "Correct display of project/tenant id on display of credentials"
+
+commit 8117256d7dc1464e86382db5c480d33b01131854
+Author: Chris Johnson <wchrisjohnson@gmail.com>
+Date: Fri Mar 21 11:16:25 2014 -0400
+
+ Correct display of project/tenant id on display of credentials
+
+ This change corrects the display of ec2 credentails within the
+ ListEC2Creds method. Added explicit headers and corrected data listt o
+ specify tenant_id instead of project id.
+
+ Change-Id: I2ea579082bee800d774f202bdc38e2d546e57e77
+ Closes-Bug: #1292337
+
+commit 4900c64d091ffe4ec9050cd71845b12db49bd293
+Author: Terry Howe <terrylhowe@gmail.com>
+Date: Mon Mar 17 17:32:41 2014 -0600
+
+ Produce a useful error message for NoUniqueMatch
+
+ Most of the CLIs use a NoUniqueMatch, so produce a useful
+ error message if that happens. Added some tests for
+ find_resource as well.
+
+ Change-Id: I85ba61d5f6d1be5bd336a1cc4b02501492905f33
+ Closes-Bug: #1293846
+
+commit 0c0803d363aeae9e8a6eb2cdaf4b5baedee416dc
+Author: Steven Hardy <shardy@redhat.com>
+Date: Fri Mar 7 18:25:41 2014 +0000
+
+ identity v3 allow project list filtering by domain
+
+ The underlying keystoneclient interface allows filtering by domain,
+ so support it in the cli interface because it makes project list
+ much nicer to use in a multi-domain deployment.
+
+ Change-Id: If3f5cf1205c1e9cf314f8286a3ae81bda4456b8f
+ Closes-Bug: #1289513
+
+commit ce63fa01219cf4a6e1fab8c3be7e5d20d35f08e1
+Merge: 65f094e 70e6333
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Thu Mar 13 06:33:24 2014 +0000
+
+ Merge "Add ability to set key value pairs in projects"
+
+commit 65f094e73802380b967c75c126c0938281973707
+Author: OpenStack Jenkins <jenkins@openstack.org>
+Date: Mon Mar 10 21:37:10 2014 +0000
+
+ Updated from global requirements
+
+ Change-Id: I73f40a5fd569d0a5e341aabbece1885a7478d7f5
+
+commit cf8506d2bdbde02a731afb16a839e0660b71a89b
+Merge: 4632238 27ebdeb
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Mon Mar 10 16:40:50 2014 +0000
+
+ Merge "Fix 'keypair show' command output"
+
+commit 46322387b6e8a6957c50655dffb227452b36c628
+Merge: 3293138 00188f0
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Sat Mar 8 03:50:55 2014 +0000
+
+ Merge "Fixed spelling error, compatability to compatibility"
+
+commit 3293138121c0ad1b91d63eae219c9aae41d758c3
+Merge: b03680a e6e0dbf
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Sat Mar 8 03:50:54 2014 +0000
+
+ Merge "Add --volume option to image create command"
+
+commit b03680aea5a3759e38d8b46b0c99df65738f98d7
+Merge: b0c0b48 9766707
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Sat Mar 8 00:33:11 2014 +0000
+
+ Merge "Fixed Spelling errors - compatability to compatibility"
+
+commit b0c0b48fdc1a26196b5838db33e3bc0683566b3d
+Merge: 3435f18 14548aa
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Sat Mar 8 00:33:10 2014 +0000
+
+ Merge "Fixed spelling errors - occurance to occurence"
+
+commit 27ebdeb57dd78564770ef92b95bc659b25e10a69
+Author: Dean Troyer <dtroyer@gmail.com>
+Date: Fri Mar 7 15:34:28 2014 -0600
+
+ Fix 'keypair show' command output
+
+ The attempt to get the data dict out of the keypair resource object uses a
+ key 'keypair. This is incorrect, no key is required.
+
+ Closes-Bug: 1289594
+ Change-Id: I7887119c1d800d389cb6f63ea7847bea1e25bb52
+
+commit 3435f188a400b21891981c4024c26bcefc7be861
+Author: Dolph Mathews <dolph.mathews@gmail.com>
+Date: Wed Feb 19 14:44:03 2014 -0600
+
+ add interface and url to endpoint list
+
+ endpoint list is not terribly useful without these details
+
+ Change-Id: I65b0bdf7667d73ceaad5856171678cabcde003f3
+
+commit 00188f092c76fd5bfaf0f6a67cd0b52ed473313b
+Author: Alex Holden <alex@alexjonasholden.com>
+Date: Fri Mar 7 09:40:29 2014 -0800
+
+ Fixed spelling error, compatability to compatibility
+
+ Change-Id: I72c1254666a741ffe1070cf8275af889fa323f52
+
+commit 97667079dc286f4b32c7ce4f0440bb9109758772
+Author: Alex Holden <alex@alexjonasholden.com>
+Date: Fri Mar 7 09:39:02 2014 -0800
+
+ Fixed Spelling errors - compatability to compatibility
+
+ Change-Id: I9da380cef8b798e21fd35882763bd05f5cf1e33e
+
+commit 14548aa69a25f6a57c0d5bbccae2d5243a99dfef
+Author: Alex Holden <alex@alexjonasholden.com>
+Date: Fri Mar 7 09:37:59 2014 -0800
+
+ Fixed spelling errors - occurance to occurence
+
+ Change-Id: Ie0550a1168448d85043d9b4943edd732b1f10307
+
+commit 211cd31d7ac2c7768cc871ac7c9228f7713b8dfc
+Author: Terry Howe <terrylhowe@gmail.com>
+Date: Thu Mar 6 13:56:10 2014 -0700
+
+ Make bash comple command best effort to authorize
+
+ If authorization fails for the complete command, generate the bash complete anyway.
+ * Added best_effort flag to command
+ * Attempts to authorize for bash complete, but if it fails, it tries anyway
+
+ Change-Id: I796258f8044f42abc6a51164d920a26f73397962
+ Partial-Bug: #1283550
+
+commit 70e6333e7d4b3fa87496ff3a888527693875273b
+Author: Terry Howe <terrylhowe@gmail.com>
+Date: Thu Mar 6 12:50:37 2014 -0700
+
+ Add ability to set key value pairs in projects
+
+ Add supporto of extra key value pairs for projects (aka tenants)
+ * Added option --property key=value to create and set commands
+ * Support for versions v2 and v3
+
+ Change-Id: I84064b8b308579d1b66c83b1ed3d1a37614ec087
+ Closes-Bug: #1220280
+
+commit 64a33b0aa59a1d22fadc41bffebfab04dd6c2cc7
+Author: OpenStack Jenkins <jenkins@openstack.org>
+Date: Wed Mar 5 19:30:08 2014 +0000
+
+ Updated from global requirements
+
+ Change-Id: Ic90d9682a9c15795928c0c5b64c41bd06d74243a
+
+commit e6e0dbf754c4dbc631e5c797b50d8032481a1a27
+Author: Terry Howe <terrylhowe@gmail.com>
+Date: Wed Feb 26 12:13:01 2014 -0700
+
+ Add --volume option to image create command
+
+ Add ability to create an image from a volume.
+ * Added --volume command to image create
+ * Added --force option to image create
+ * Added block to access volume manager in image create
+ * Tests added for the volume option
+
+ Change-Id: I3910a2b5e04acd0d15dd230747ba6ebca07aa316
+ Closes-Bug: #1207615
+
+commit 808fa652109e8d0141f88b258df05874e6676d98
+Merge: 9ccaacb 038269c
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Fri Feb 28 23:14:51 2014 +0000
+
+ Merge "Update release notes for 0.3.1"
+
+commit 9ccaacbe2760012ee916b517035a24160fcf7b91
+Merge: b7f673c ada9d35
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Fri Feb 28 01:44:48 2014 +0000
+
+ Merge "Fix format errors in nova security group rule list"
+
+commit 038269cf7fbbfad9ccc9d85a762e2ff4f7d97d11
+Author: Dean Troyer <dtroyer@gmail.com>
+Date: Fri Feb 21 12:46:07 2014 -0600
+
+ Update release notes for 0.3.1
+
+ * update README
+ * update man page
+ * fix doc errors
+
+ Change-Id: I5682654bf482289879c8ba9016e348f2b2782971
+
+commit e9015b94f91d3ff6972109fb21a2b5e7fd3b9a79
+Author: Terry Howe <terrylhowe@gmail.com>
+Date: Thu Feb 27 10:55:37 2014 -0700
+
+ In anticipation of network agents, rename compute
+
+ Rename the compute agents in anticipation of network agents
+
+ Change-Id: I201121915638d89dfbe46a7e0026aa4c2777e590
+ Closes-Bug: #1285800
+
+commit b7f673cb81f1a6539739dc6d12eaef7455bf63fd
+Merge: 8be3e24 eddab62
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Wed Feb 26 23:18:34 2014 +0000
+
+ Merge "Fix volume commands with multiple regions"
+
+commit 8be3e249b6bb63188e8f31874827bb9c4b45ff40
+Author: Jamie Lennox <jamielennox@redhat.com>
+Date: Wed Feb 26 13:26:45 2014 +1000
+
+ Use cacert values when creating identity client
+
+ These were ignored when the client was created with a username and
+ password.
+
+ Change-Id: Id7557a5b07a41c7f79ab1a05ede385da31889940
+ Closes-Bug: #1284957
+
+commit 125393210bd40d70d77a4667922a4a33f569a7ef
+Author: OpenStack Jenkins <jenkins@openstack.org>
+Date: Sun Feb 23 09:31:55 2014 +0000
+
+ Updated from global requirements
+
+ Change-Id: Icad0fcf4125b4aefac7ed8f0df5bef2c6ee64594
+
+commit 8fe50fc75d4fa26557662af0759c44cb22c5fb79
+Merge: 034a9d1 5043293
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Sun Feb 23 06:17:49 2014 +0000
+
+ Merge "Fix some help strings"
+
+commit 034a9d158f27bb9bd1a7430c09d1826196215093
+Merge: 8b494f0 033f27f
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Sat Feb 22 06:00:18 2014 +0000
+
+ Merge "Add ability to prompt for passwords for user create and set"
+
+commit eddab621094e5da9f9c6bf2ae27a872625c8e135
+Author: Terry Howe <terrylhowe@gmail.com>
+Date: Fri Feb 21 17:22:26 2014 -0700
+
+ Fix volume commands with multiple regions
+
+ The region_name was not passed into the the client causing volume
+ commands to fail if there were multiple regions.
+
+ Change-Id: I066dbbc4852f412e017daeeb16a3f186d3f91d2f
+ Closes-Bug: #1241177
+
+commit 033f27fe4dc4455c2f07978a273fd65faa653b67
+Author: Terry Howe <terrylhowe@gmail.com>
+Date: Wed Feb 19 19:30:56 2014 -0700
+
+ Add ability to prompt for passwords for user create and set
+
+ * Add get_password method to the utilities
+ * Add --password-prompt option
+ * Call the get_password method if a prompt is requested
+ * Various tests
+
+ Change-Id: I1786ad531e2a2fbcc21b8bc86aac0ccd7985995a
+ Closes-Bug: 1100116
+
+commit 50432931562ca697a2b88db2bb2f0b1da91fb28a
+Author: Andreas Jaeger <aj@suse.de>
+Date: Fri Feb 21 19:22:32 2014 +0100
+
+ Fix some help strings
+
+ This fixes some errors and inconsistencies I found reviewing the
+ help strings:
+ * Capitalize help strings
+ * Add missing space between words (in multi-line strings)
+ * Improve wording
+
+ Change-Id: I2fb31ab4191c330146e31c1a9651115a6657769a
+
+commit 8b494f08b2d60b487b37de6603349db66496b955
+Merge: 2cae50c 99cea54
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Fri Feb 21 18:27:51 2014 +0000
+
+ Merge "Update oslo incubator bits"
+
+commit 2cae50c442337826216d68832a1cb387f3cc1341
+Merge: 2958421 d8bdd2b
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Fri Feb 21 01:36:26 2014 +0000
+
+ Merge "Rename Openstack to OpenStack"
+
+commit ada9d35cbe477d7225d217c6c1926b5fb8aa0a92
+Author: Terry Howe <terrylhowe@gmail.com>
+Date: Thu Feb 20 09:29:38 2014 -0700
+
+ Fix format errors in nova security group rule list
+
+ * port range was throwing exception for None to/from ports
+ * ip_range didn't always have cidr causing error
+ * ip_protocol None at times and looked bad
+
+ Closes-Bug #1256935
+
+ Change-Id: I451a0f038a3e9646bca3f278c5d6f6d7e3097a83
+
+commit 99cea54741fa9364bd1790cdc0bc85e1ba19ef75
+Author: Dean Troyer <dtroyer@gmail.com>
+Date: Wed Jan 8 10:42:49 2014 -0600
+
+ Update oslo incubator bits
+
+ * update gettextutils.py, strutils.py, install_venv_common.py
+ * remove cfg.py, openstackkeyring
+
+ oslo-incubator commit 630d3959b9d001ca18bd2ed1cf757f2eb44a336f
+
+ Change-Id: I0ae9b9dc72ec88ed64a8c353b9c51734ee2cd24c
+
+commit 295842175540df01ea4a2ab5de08f79398e6e12b
+Merge: 5f9e7d0 ecc4fb3
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Thu Feb 20 20:00:59 2014 +0000
+
+ Merge "Glance client no longer isa http client"
+
+commit 5f9e7d09cb0c6efd01816253611092c1bbc51495
+Author: Cyril Roelandt <cyril.roelandt@enovance.com>
+Date: Tue Feb 18 00:49:10 2014 +0100
+
+ Python 3: the content of a FakeResponse must be bytes
+
+ Encode '_content' if necessary.
+
+ Change-Id: I25c1e1cd5330f0519bf062be840045d0ef520b28
+
+commit d8bdd2b5ed2c890df84718be6f977a8d93eddb86
+Author: tanlin <lin.tan@intel.com>
+Date: Thu Feb 13 16:42:04 2014 +0800
+
+ Rename Openstack to OpenStack
+
+ Change-Id: I9e5b245141290a4b642900fbc46b98bd4f44c321
+
+commit 2f5e8232aa07a9031231ad7b4678b7c52c1effc4
+Merge: e068cd0 eaa4c3e
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Tue Feb 11 21:36:46 2014 +0000
+
+ Merge "Python 3: fix a syntax error"
+
+commit e068cd0d715711e39ee0e564aca7e1dca819fb4a
+Merge: c8354b8 9dc3eb5
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Tue Feb 11 21:35:55 2014 +0000
+
+ Merge "FakeResponse: use a default status code"
+
+commit c8354b8d8310dce9b0296597e41f9a355d7067c0
+Merge: 9aa207e 8aa0b07
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Tue Feb 11 18:44:30 2014 +0000
+
+ Merge "Fix misspellings in python openstackclient"
+
+commit 9dc3eb5b18612831cba7f44b592481056635a476
+Author: Cyril Roelandt <cyril.roelandt@enovance.com>
+Date: Tue Feb 11 17:31:37 2014 +0100
+
+ FakeResponse: use a default status code
+
+ When running some tests from test_restapi.py, the following error happens:
+
+ TypeError: unorderable types: NoneType() < int()
+
+ In Python 2, comparing NoneType and integers is possible:
+
+ >>> None < 2
+ True
+
+ But in Python 3, it's not allowed. Fix this by using a default status code.
+
+ Change-Id: Ic0fad5c68f3bf2dd8a2b98423549903f982192c9
+
+commit 9aa207eb9d66ae3240606a9b497b253b37566c95
+Merge: 380d78c 3a5abf7
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Tue Feb 11 15:41:40 2014 +0000
+
+ Merge "Use six.iteritems() rather than dict.iteritems()"
+
+commit eaa4c3e1a6a4d2fd3139695db88a9f28d64a506f
+Author: Cyril Roelandt <cyril.roelandt@enovance.com>
+Date: Tue Feb 11 15:44:54 2014 +0100
+
+ Python 3: fix a syntax error
+
+ "raise AttributeError, name" is invalid in Python 3.
+
+ Change-Id: Id61bd3747f49c2bd810cbfeae56506e7ed9d2bd0
+
+commit 3a5abf743c66f36faf45f710720fe28546b04ea2
+Author: Cyril Roelandt <cyril.roelandt@enovance.com>
+Date: Tue Feb 11 02:22:20 2014 +0100
+
+ Use six.iteritems() rather than dict.iteritems()
+
+ This is compatible with both Python 2 and 3.
+
+ Change-Id: I6fe3e9bf9ece699badbdb9933118af90642a91e9
+
+commit 380d78c8564ca81c60d46176af622fd92a7b1582
+Author: Jeremy Stanley <fungi@yuggoth.org>
+Date: Mon Feb 10 03:10:52 2014 +0000
+
+ Remove tox locale overrides
+
+ * tox.ini: The LANG, LANGUAGE and LC_ALL environment overrides were
+ introduced originally during the testr migration in an attempt to be
+ conservative about the possibility that locale settings in the
+ calling environment could cause consistency problems for test runs.
+ In actuality, this should be unnecessary and any place where it does
+ cause issues ought to be considered an actual bug. Also, having
+ these in the configuration actively causes older pip to have
+ problems with non-ASCII content in some package metadata files under
+ Python 3, so drop it now.
+
+ Change-Id: I89ff5c22be053f09defb04b3ec589d74bffcae9d
+ Closes-Bug: #1277495
+
+commit ecc4fb330dea487b7ac86050eb971d97cea895d6
+Author: Terry Howe <terrylhowe@gmail.com>
+Date: Tue Jan 21 10:45:52 2014 -0700
+
+ Glance client no longer isa http client
+
+ If the client has-a http_client, then is must not be an is-a. This has been tested with the current version of glanceclient and the master branch.
+
+ Closes-Bug: #1269821
+ Change-Id: I14d67eb094bfb4c2dbc07106343488298b6a9409
+
+commit 8aa0b07fbc4dd3cd2c7d99513407b652eaa39d85
+Author: Shane Wang <shane.wang@intel.com>
+Date: Fri Feb 7 13:25:43 2014 +0800
+
+ Fix misspellings in python openstackclient
+
+ Fix misspellings detected by:
+ * pip install misspellings
+ * git ls-files | grep -v locale | misspellings -f -
+
+ Change-Id: Ic0d3efa26eb9a05ce16a8319c142f5bd1ce23821
+ Closes-Bug: #1257295
+
+commit 632363328b6bcdfb562e2822cd033967adcf4b6c
+Merge: 0aeb357 a8d828f
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Fri Jan 31 17:28:02 2014 +0000
+
+ Merge "Add token create subcommand for identity v3 api"
+
+commit a8d828f330119502fc18107c264f2944548a7fb9
+Author: Qiu Yu <qiuyu@ebaysf.com>
+Date: Wed Jan 29 15:57:18 2014 +0800
+
+ Add token create subcommand for identity v3 api
+
+ Implements token create subcommand which is an equivalent of keystone
+ token-get command. Original "wrap" parameter for keystone token-get is
+ not implemented yet due to cliff Bug #1269299
+
+ This is a part of: blueprint add-identity-token-support
+
+ Change-Id: I2255021c9d1f10f757686583b1ebe40b5f3a9ecb
+
+commit 0aeb357fc24b312c6ba8632cc8019f7ea3ec32fd
+Author: OpenStack Jenkins <jenkins@openstack.org>
+Date: Fri Jan 24 22:40:56 2014 +0000
+
+ Updated from global requirements
+
+ Change-Id: I98929876d5a21a990009398d9a8259c54d893e7e
+
+commit 0076f694acd7bc02ce1fb26ec0f1974ba68e0cf3
+Merge: cb2fd0a bc2395e
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Thu Jan 23 17:38:15 2014 +0000
+
+ Merge "Fix keyring issue where there were name space problems"
+
+commit cb2fd0af8301648e88f30c8cb449333be5ba1cb0
+Merge: 9e9bbad ad43678
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Thu Jan 23 17:38:15 2014 +0000
+
+ Merge "Remove copyright from empty files"
+
+commit bc2395eb473a203d11df52d48968b6ab61e2c95e
+Author: Terry Howe <thowe@hp.com>
+Date: Thu Jan 23 09:33:06 2014 -0700
+
+ Fix keyring issue where there were name space problems
+
+ The import of keyring conflicted with a string named keyring
+
+ Change-Id: I7416ea1cf453a126dd03dba8bc2900cad35ed2da
+ Closes-bug: #1271987
+
+commit 9e9bbad120e16d2c490b7c65de35e15cb233411a
+Merge: ebfa669 350718f
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Thu Jan 23 04:09:53 2014 +0000
+
+ Merge "Remove remaining print statements"
+
+commit 350718f3bbad1e3b5da45bbb4e61e4af5a11e944
+Author: Dean Troyer <dtroyer@gmail.com>
+Date: Wed Dec 4 18:37:50 2013 -0600
+
+ Remove remaining print statements
+
+ I think these are the last two stragglers, including debugging lines
+
+ Change-Id: Ic3dd98480211d0f7d3cc951bec5cd54f902a101f
+
+commit ebfa6698a1a714c437cbedfc99348d501e68efa1
+Merge: 81d33a5 4848d3c
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Wed Jan 22 08:13:42 2014 +0000
+
+ Merge "Add token create subcommand for identity v2 api"
+
+commit ad4367839f53f1d00cde80bfcb396cfc3d8f9c7d
+Author: Alexander Ignatov <aignatov@mirantis.com>
+Date: Mon Jan 20 17:27:02 2014 +0400
+
+ Remove copyright from empty files
+
+ According to policy change in HACKING:
+ http://docs.openstack.org/developer/hacking/#openstack-licensing
+ empty files should no longer contain copyright notices.
+
+ Change-Id: Iba09a00f24dfbd1cd03c1c9f70ea216788e64d93
+ Closes-Bug: #1262424
+
+commit 4848d3ca3aff4d148fb1b0aa0aa54e1eb3f3600f
+Author: Qiu Yu <qiuyu@ebaysf.com>
+Date: Wed Jan 15 17:03:59 2014 +0800
+
+ Add token create subcommand for identity v2 api
+
+ Implements token create subcommand which is an equivalent of keystone
+ token-get command. Original "wrap" parameter for keystone token-get is
+ not implemented yet due to cliff Bug #1269299
+
+ This is a part of: blueprint add-identity-token-support
+
+ Change-Id: I9e4de93306f2f5959717b5219621da03961524d8
+
+commit 81d33a524dd53f233bf72ea1eae4bea1058ceeaf
+Author: Sascha Peilicke <saschpe@gmx.de>
+Date: Thu Jan 16 09:21:39 2014 +0100
+
+ Sync with global requirements
+
+ Change-Id: Ie47804617ab9a11a91efd96c7989f0207e47e120
+
+commit a53ed797b7778e05e6dfc21305d632fe66618dbf
+Merge: b00bbaa 7b999d7
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Fri Jan 10 17:27:50 2014 +0000
+
+ Merge "Fix image set properties error"
+
+commit b00bbaa062e90124f4c6542e522d96b7da1d6468
+Merge: 6ad0511 9e31f8e
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Fri Jan 10 17:27:48 2014 +0000
+
+ Merge "Fix errant underscores"
+
+commit 6ad05112f47cee2f2f5e8310dc9e05de33263b0c
+Merge: e821a6b a5e087e
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Fri Jan 10 17:07:12 2014 +0000
+
+ Merge "Displaying curl commands for nova and cinder calls"
+
+commit 9e31f8ea147cc51e2a8b22658f50e769e9866fdb
+Author: Dean Troyer <dtroyer@gmail.com>
+Date: Thu Jan 9 16:54:40 2014 -0600
+
+ Fix errant underscores
+
+ Change-Id: I71b8c8df14b85e3042220e3593a9732ee6cefe15
+
+commit e821a6b97fcc5484ceae3f228f851be30a26ed76
+Merge: 202c3e3 420b10e
+Author: Jenkins <jenkins@review.openstack.org>
+Date: Wed Jan 8 16:40:34 2014 +0000
+
+ Merge "Add support for specifying custom domains"
+
+commit 420b10ee6dc8c40a7936001381080e4b6628e900
+Author: Paul Belanger <paul.belanger@polybeacon.com>
+Date: Mon Dec 9 20:01:04 2013 -0500
+
+ Add support for specifying custom domains
+
+ Add the ability to pass user_domain_id / user_domain_name, domain_id
+ / domain_name, and project_domain_id / project_domain_name to keystone.
+ These parameters are the first step needed to getting multi-domain
+ support working via the CLI.
+
+ Closes-Bug: #1198171
+ Change-Id: I81a8534913978ff1cce01ec02741ae477e8c5fa4
+ Signed-off-by: Paul Belanger <paul.belanger@polybeacon.com>
+ Signed-off-by: Bo Tang <btang@cs.utsa.edu>
+
+commit 7b999d786c827922ea66b61a41c89123417ed407
+Author: Dean Troyer <dtroyer@gmail.com>
+Date: Mon Jan 6 09:42:40 2014 -0600
+
+ Fix image set properties error
+
+ Change-Id: Ia290935c8a040221caf1a46ca29a7bb2e5df1ce6
+
+commit a5e087e7a9b88e2ce698ddc32d89e1462509fbb5
+Author: Florent Flament <florent.flament-ext@cloudwatt.com>
+Date: Wed Dec 18 15:07:03 2013 +0000
+
+ Displaying curl commands for nova and cinder calls
+
+ When using the -v option, displays curl equivalent commands and http
+ messages exchanged with the nova and cinder API servers. Displays the
+ same messages as those displayed with the --debug option of
+ python-novaclient and python-cinderclient.
+
+ Implements: blueprint curl-commands-in-debugging-messages for nova and
+ cinder related calls
+
+ Change-Id: Ibc8ef79d874334585b81d652b9c7df9e874fffa9
+
+commit 202c3e375bf3ac528a5e8e1a1d1d83d45b57b9bf
+Author: Terry Howe <terrylhowe@gmail.com>
+Date: Wed Dec 25 09:30:05 2013 -0700
+
+ Closes-Bug: #1262322 Make links clickable
+
+ Change-Id: I61302ff5274cdaa09801cb9b0dc9bfd353ac687f
+
+commit 12f31eed2f9f12e7fd60c238a338bb45eaee3516
+Author: Terry Howe <terrylhowe@gmail.com>
+Date: Thu Dec 19 20:06:54 2013 -0700
+
+ Closes-Bug: #1262321
+ Remove the unimplemented post_process method call
+
+ Change-Id: Iaed526cc25a651008a66ad7f0050070ab2b4c595
+
commit de27c1b455624b6123b26bfdd022d2763c541a25
Author: Dean Troyer <dtroyer@gmail.com>
Date: Tue Dec 17 09:22:17 2013 -0600
diff --git a/debian/changelog b/debian/changelog
index 19981bd..6ebf515 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,15 @@
+python-openstackclient (0.4.0-1) unstable; urgency=medium
+
+ * New upstream release.
+
+ -- gustavo panizzo <gfa@zumbi.com.ar> Mon, 30 Jun 2014 00:55:59 -0300
+
+python-openstackclient (0.3.1-1) unstable; urgency=medium
+
+ * New upstream release.
+
+ -- gustavo panizzo <gfa@zumbi.com.ar> Fri, 25 Apr 2014 16:17:10 -0300
+
python-openstackclient (0.3.0-1) unstable; urgency=low
* Initial release. (Closes: #733557)
diff --git a/debian/control b/debian/control
index 017bca1..30b4ac6 100644
--- a/debian/control
+++ b/debian/control
@@ -6,7 +6,7 @@ Uploaders: gustavo panizzo <gfa@zumbi.com.ar>, Thomas Goirand <zigo@debian.org>
Build-Depends: debhelper (>= 9),
openstack-pkg-tools,
python-all (>= 2.6.6-3~),
- python-pbr (>= 0.5.21),
+ python-pbr (>= 0.6),
python-setuptools
Build-Depends-Indep: python-babel (>= 1.3),
python-cinderclient (>= 1:1.0.6),
@@ -16,12 +16,12 @@ Build-Depends-Indep: python-babel (>= 1.3),
python-fixtures (>= 0.3.14),
python-glanceclient (>= 1:0.9.0),
python-hacking,
- python-keyring (>= 1.6.1),
- python-keystoneclient (>= 1:0.4.1),
+ python-keyring (>= 2.1),
+ python-keystoneclient (>= 1:0.9.0),
python-mock (>= 1.0),
- python-novaclient (>= 2:2.15.0),
+ python-novaclient (>= 2:2.17.0),
python-requests (>= 1.1),
- python-six (>= 1.4.1),
+ python-six (>= 1.7.0),
python-sphinx (>= 1.1.2),
python-testtools (>= 0.9.32),
python-webob (>= 1.2.3),
@@ -41,9 +41,10 @@ Depends: python-cinderclient (>= 1:1.0.6),
python-keystoneclient (>= 1:0.4.1),
python-novaclient (>= 2:2.15.0),
python-requests (>= 1.1),
- python-six (>= 1.4.1),
+ python-six (>= 1.7.0),
${misc:Depends},
- ${python:Depends}
+ ${python:Depends},
+ ${sphinxdoc:Depends}
Recommends: ${python:Recommends}
Description: OpenStack Command-line Client
python-openstackclient is a unified command-line client for the OpenStack APIs.
diff --git a/debian/patches/series b/debian/patches/series
index fcd7aa0..d88172d 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1,2 +1 @@
001-disable-intersphinx.patch
-002-python-keyring-requirements.patch
diff --git a/debian/python-openstackclient.doc-base b/debian/python-openstackclient.doc-base
new file mode 100644
index 0000000..605add3
--- /dev/null
+++ b/debian/python-openstackclient.doc-base
@@ -0,0 +1,9 @@
+Document: python-openstackclient
+Title: OpenStackClient Documentation
+Author: OpenStack LLC.
+Abstract: Sphinx documentation for OpenStackClient
+Section: Network/Remote Access
+
+Format: HTML
+Index: /usr/share/doc/python-openstackclient/html/index.html
+Files: /usr/share/doc/python-openstackclient/html/*
diff --git a/debian/python-openstackclient.docs b/debian/python-openstackclient.docs
new file mode 100644
index 0000000..213f66e
--- /dev/null
+++ b/debian/python-openstackclient.docs
@@ -0,0 +1 @@
+usr/share/doc/python-openstackclient/html/*
diff --git a/debian/rules b/debian/rules
index 0e95de0..bccbd4c 100755
--- a/debian/rules
+++ b/debian/rules
@@ -6,16 +6,22 @@ include /usr/share/openstack-pkg-tools/pkgos.make
export OSLO_PACKAGE_VERSION=$(VERSION)
%:
- dh $@ --with python2
+ dh $@ --with python2,sphinxdoc
override_dh_auto_clean:
rm -rf doc/build build python_openstackclient.egg-info .testrepository
override_dh_auto_build:
dh_auto_build
- make -C doc man
ifeq (,$(findstring nocheck, $(DEB_BUILD_OPTIONS)))
override_dh_auto_test:
python setup.py testr || true
endif
+
+override_dh_sphinxdoc:
+ifeq (,$(findstring nodocs, $(DEB_BUILD_OPTIONS)))
+ sphinx-build -b man doc/source doc/build/man
+ PYTHONPATH=. sphinx-build -b html doc/source $(CURDIR)/debian/python-openstackclient/usr/share/doc/python-openstackclient/html
+ dh_sphinxdoc -O--buildsystem=python2
+endif
diff --git a/doc/source/_static/basic.css b/doc/source/_static/basic.css
new file mode 100644
index 0000000..d909ce3
--- /dev/null
+++ b/doc/source/_static/basic.css
@@ -0,0 +1,416 @@
+/**
+ * Sphinx stylesheet -- basic theme
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+/* -- main layout ----------------------------------------------------------- */
+
+div.clearer {
+ clear: both;
+}
+
+/* -- relbar ---------------------------------------------------------------- */
+
+div.related {
+ width: 100%;
+ font-size: 90%;
+}
+
+div.related h3 {
+ display: none;
+}
+
+div.related ul {
+ margin: 0;
+ padding: 0 0 0 10px;
+ list-style: none;
+}
+
+div.related li {
+ display: inline;
+}
+
+div.related li.right {
+ float: right;
+ margin-right: 5px;
+}
+
+/* -- sidebar --------------------------------------------------------------- */
+
+div.sphinxsidebarwrapper {
+ padding: 10px 5px 0 10px;
+}
+
+div.sphinxsidebar {
+ float: left;
+ width: 230px;
+ margin-left: -100%;
+ font-size: 90%;
+}
+
+div.sphinxsidebar ul {
+ list-style: none;
+}
+
+div.sphinxsidebar ul ul,
+div.sphinxsidebar ul.want-points {
+ margin-left: 20px;
+ list-style: square;
+}
+
+div.sphinxsidebar ul ul {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+div.sphinxsidebar form {
+ margin-top: 10px;
+}
+
+div.sphinxsidebar input {
+ border: 1px solid #98dbcc;
+ font-family: sans-serif;
+ font-size: 1em;
+}
+
+img {
+ border: 0;
+}
+
+/* -- search page ----------------------------------------------------------- */
+
+ul.search {
+ margin: 10px 0 0 20px;
+ padding: 0;
+}
+
+ul.search li {
+ padding: 5px 0 5px 20px;
+ background-image: url(file.png);
+ background-repeat: no-repeat;
+ background-position: 0 7px;
+}
+
+ul.search li a {
+ font-weight: bold;
+}
+
+ul.search li div.context {
+ color: #888;
+ margin: 2px 0 0 30px;
+ text-align: left;
+}
+
+ul.keywordmatches li.goodmatch a {
+ font-weight: bold;
+}
+
+/* -- index page ------------------------------------------------------------ */
+
+table.contentstable {
+ width: 90%;
+}
+
+table.contentstable p.biglink {
+ line-height: 150%;
+}
+
+a.biglink {
+ font-size: 1.3em;
+}
+
+span.linkdescr {
+ font-style: italic;
+ padding-top: 5px;
+ font-size: 90%;
+}
+
+/* -- general index --------------------------------------------------------- */
+
+table.indextable td {
+ text-align: left;
+ vertical-align: top;
+}
+
+table.indextable dl, table.indextable dd {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+table.indextable tr.pcap {
+ height: 10px;
+}
+
+table.indextable tr.cap {
+ margin-top: 10px;
+ background-color: #f2f2f2;
+}
+
+img.toggler {
+ margin-right: 3px;
+ margin-top: 3px;
+ cursor: pointer;
+}
+
+/* -- general body styles --------------------------------------------------- */
+
+a.headerlink {
+ visibility: hidden;
+}
+
+h1:hover > a.headerlink,
+h2:hover > a.headerlink,
+h3:hover > a.headerlink,
+h4:hover > a.headerlink,
+h5:hover > a.headerlink,
+h6:hover > a.headerlink,
+dt:hover > a.headerlink {
+ visibility: visible;
+}
+
+div.body p.caption {
+ text-align: inherit;
+}
+
+div.body td {
+ text-align: left;
+}
+
+.field-list ul {
+ padding-left: 1em;
+}
+
+.first {
+}
+
+p.rubric {
+ margin-top: 30px;
+ font-weight: bold;
+}
+
+/* -- sidebars -------------------------------------------------------------- */
+
+div.sidebar {
+ margin: 0 0 0.5em 1em;
+ border: 1px solid #ddb;
+ padding: 7px 7px 0 7px;
+ background-color: #ffe;
+ width: 40%;
+ float: right;
+}
+
+p.sidebar-title {
+ font-weight: bold;
+}
+
+/* -- topics ---------------------------------------------------------------- */
+
+div.topic {
+ border: 1px solid #ccc;
+ padding: 7px 7px 0 7px;
+ margin: 10px 0 10px 0;
+}
+
+p.topic-title {
+ font-size: 1.1em;
+ font-weight: bold;
+ margin-top: 10px;
+}
+
+/* -- admonitions ----------------------------------------------------------- */
+
+div.admonition {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ padding: 7px;
+}
+
+div.admonition dt {
+ font-weight: bold;
+}
+
+div.admonition dl {
+ margin-bottom: 0;
+}
+
+p.admonition-title {
+ margin: 0px 10px 5px 0px;
+ font-weight: bold;
+}
+
+div.body p.centered {
+ text-align: center;
+ margin-top: 25px;
+}
+
+/* -- tables ---------------------------------------------------------------- */
+
+table.docutils {
+ border: 0;
+ border-collapse: collapse;
+}
+
+table.docutils td, table.docutils th {
+ padding: 1px 8px 1px 0;
+ border-top: 0;
+ border-left: 0;
+ border-right: 0;
+ border-bottom: 1px solid #aaa;
+}
+
+table.field-list td, table.field-list th {
+ border: 0 !important;
+}
+
+table.footnote td, table.footnote th {
+ border: 0 !important;
+}
+
+th {
+ text-align: left;
+ padding-right: 5px;
+}
+
+/* -- other body styles ----------------------------------------------------- */
+
+dl {
+ margin-bottom: 15px;
+}
+
+dd p {
+ margin-top: 0px;
+}
+
+dd ul, dd table {
+ margin-bottom: 10px;
+}
+
+dd {
+ margin-top: 3px;
+ margin-bottom: 10px;
+ margin-left: 30px;
+}
+
+dt:target, .highlight {
+ background-color: #fbe54e;
+}
+
+dl.glossary dt {
+ font-weight: bold;
+ font-size: 1.1em;
+}
+
+.field-list ul {
+ margin: 0;
+ padding-left: 1em;
+}
+
+.field-list p {
+ margin: 0;
+}
+
+.refcount {
+ color: #060;
+}
+
+.optional {
+ font-size: 1.3em;
+}
+
+.versionmodified {
+ font-style: italic;
+}
+
+.system-message {
+ background-color: #fda;
+ padding: 5px;
+ border: 3px solid red;
+}
+
+.footnote:target {
+ background-color: #ffa
+}
+
+.line-block {
+ display: block;
+ margin-top: 1em;
+ margin-bottom: 1em;
+}
+
+.line-block .line-block {
+ margin-top: 0;
+ margin-bottom: 0;
+ margin-left: 1.5em;
+}
+
+/* -- code displays --------------------------------------------------------- */
+
+pre {
+ overflow: auto;
+}
+
+td.linenos pre {
+ padding: 5px 0px;
+ border: 0;
+ background-color: transparent;
+ color: #aaa;
+}
+
+table.highlighttable {
+ margin-left: 0.5em;
+}
+
+table.highlighttable td {
+ padding: 0 0.5em 0 0.5em;
+}
+
+tt.descname {
+ background-color: transparent;
+ font-weight: bold;
+ font-size: 1.2em;
+}
+
+tt.descclassname {
+ background-color: transparent;
+}
+
+tt.xref, a tt {
+ background-color: transparent;
+ font-weight: bold;
+}
+
+h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt {
+ background-color: transparent;
+}
+
+/* -- math display ---------------------------------------------------------- */
+
+img.math {
+ vertical-align: middle;
+}
+
+div.body div.math p {
+ text-align: center;
+}
+
+span.eqno {
+ float: right;
+}
+
+/* -- printout stylesheet --------------------------------------------------- */
+
+@media print {
+ div.document,
+ div.documentwrapper,
+ div.bodywrapper {
+ margin: 0 !important;
+ width: 100%;
+ }
+
+ div.sphinxsidebar,
+ div.related,
+ div.footer,
+ #top-link {
+ display: none;
+ }
+}
diff --git a/doc/source/_static/default.css b/doc/source/_static/default.css
new file mode 100644
index 0000000..c8091ec
--- /dev/null
+++ b/doc/source/_static/default.css
@@ -0,0 +1,230 @@
+/**
+ * Sphinx stylesheet -- default theme
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+@import url("basic.css");
+
+/* -- page layout ----------------------------------------------------------- */
+
+body {
+ font-family: sans-serif;
+ font-size: 100%;
+ background-color: #11303d;
+ color: #000;
+ margin: 0;
+ padding: 0;
+}
+
+div.document {
+ background-color: #1c4e63;
+}
+
+div.documentwrapper {
+ float: left;
+ width: 100%;
+}
+
+div.bodywrapper {
+ margin: 0 0 0 230px;
+}
+
+div.body {
+ background-color: #ffffff;
+ color: #000000;
+ padding: 0 20px 30px 20px;
+}
+
+div.footer {
+ color: #ffffff;
+ width: 100%;
+ padding: 9px 0 9px 0;
+ text-align: center;
+ font-size: 75%;
+}
+
+div.footer a {
+ color: #ffffff;
+ text-decoration: underline;
+}
+
+div.related {
+ background-color: #133f52;
+ line-height: 30px;
+ color: #ffffff;
+}
+
+div.related a {
+ color: #ffffff;
+}
+
+div.sphinxsidebar {
+}
+
+div.sphinxsidebar h3 {
+ font-family: 'Trebuchet MS', sans-serif;
+ color: #ffffff;
+ font-size: 1.4em;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+
+div.sphinxsidebar h3 a {
+ color: #ffffff;
+}
+
+div.sphinxsidebar h4 {
+ font-family: 'Trebuchet MS', sans-serif;
+ color: #ffffff;
+ font-size: 1.3em;
+ font-weight: normal;
+ margin: 5px 0 0 0;
+ padding: 0;
+}
+
+div.sphinxsidebar p {
+ color: #ffffff;
+}
+
+div.sphinxsidebar p.topless {
+ margin: 5px 10px 10px 10px;
+}
+
+div.sphinxsidebar ul {
+ margin: 10px;
+ padding: 0;
+ color: #ffffff;
+}
+
+div.sphinxsidebar a {
+ color: #98dbcc;
+}
+
+div.sphinxsidebar input {
+ border: 1px solid #98dbcc;
+ font-family: sans-serif;
+ font-size: 1em;
+}
+
+/* -- body styles ----------------------------------------------------------- */
+
+a {
+ color: #355f7c;
+ text-decoration: none;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+div.body p, div.body dd, div.body li {
+ text-align: left;
+ line-height: 130%;
+}
+
+div.body h1,
+div.body h2,
+div.body h3,
+div.body h4,
+div.body h5,
+div.body h6 {
+ font-family: 'Trebuchet MS', sans-serif;
+ background-color: #f2f2f2;
+ font-weight: normal;
+ color: #20435c;
+ border-bottom: 1px solid #ccc;
+ margin: 20px -20px 10px -20px;
+ padding: 3px 0 3px 10px;
+}
+
+div.body h1 { margin-top: 0; font-size: 200%; }
+div.body h2 { font-size: 160%; }
+div.body h3 { font-size: 140%; }
+div.body h4 { font-size: 120%; }
+div.body h5 { font-size: 110%; }
+div.body h6 { font-size: 100%; }
+
+a.headerlink {
+ color: #c60f0f;
+ font-size: 0.8em;
+ padding: 0 4px 0 4px;
+ text-decoration: none;
+}
+
+a.headerlink:hover {
+ background-color: #c60f0f;
+ color: white;
+}
+
+div.body p, div.body dd, div.body li {
+ text-align: left;
+ line-height: 130%;
+}
+
+div.admonition p.admonition-title + p {
+ display: inline;
+}
+
+div.admonition p {
+ margin-bottom: 5px;
+}
+
+div.admonition pre {
+ margin-bottom: 5px;
+}
+
+div.admonition ul, div.admonition ol {
+ margin-bottom: 5px;
+}
+
+div.note {
+ background-color: #eee;
+ border: 1px solid #ccc;
+}
+
+div.seealso {
+ background-color: #ffc;
+ border: 1px solid #ff6;
+}
+
+div.topic {
+ background-color: #eee;
+}
+
+div.warning {
+ background-color: #ffe4e4;
+ border: 1px solid #f66;
+}
+
+p.admonition-title {
+ display: inline;
+}
+
+p.admonition-title:after {
+ content: ":";
+}
+
+pre {
+ padding: 5px;
+ background-color: #eeffcc;
+ color: #333333;
+ line-height: 120%;
+ border: 1px solid #ac9;
+ border-left: none;
+ border-right: none;
+}
+
+tt {
+ background-color: #ecf0f3;
+ padding: 0 1px 0 1px;
+ font-size: 0.95em;
+}
+
+.warning tt {
+ background: #efc2c2;
+}
+
+.note tt {
+ background: #d6d6d6;
+}
diff --git a/doc/source/_static/header-line.gif b/doc/source/_static/header-line.gif
new file mode 100644
index 0000000..3601730
--- /dev/null
+++ b/doc/source/_static/header-line.gif
Binary files differ
diff --git a/doc/source/_static/header_bg.jpg b/doc/source/_static/header_bg.jpg
new file mode 100644
index 0000000..f788c41
--- /dev/null
+++ b/doc/source/_static/header_bg.jpg
Binary files differ
diff --git a/doc/source/_static/jquery.tweet.js b/doc/source/_static/jquery.tweet.js
new file mode 100644
index 0000000..79bf0bd
--- /dev/null
+++ b/doc/source/_static/jquery.tweet.js
@@ -0,0 +1,154 @@
+(function($) {
+
+ $.fn.tweet = function(o){
+ var s = {
+ username: ["seaofclouds"], // [string] required, unless you want to display our tweets. :) it can be an array, just do ["username1","username2","etc"]
+ list: null, //[string] optional name of list belonging to username
+ avatar_size: null, // [integer] height and width of avatar if displayed (48px max)
+ count: 3, // [integer] how many tweets to display?
+ intro_text: null, // [string] do you want text BEFORE your your tweets?
+ outro_text: null, // [string] do you want text AFTER your tweets?
+ join_text: null, // [string] optional text in between date and tweet, try setting to "auto"
+ auto_join_text_default: "i said,", // [string] auto text for non verb: "i said" bullocks
+ auto_join_text_ed: "i", // [string] auto text for past tense: "i" surfed
+ auto_join_text_ing: "i am", // [string] auto tense for present tense: "i was" surfing
+ auto_join_text_reply: "i replied to", // [string] auto tense for replies: "i replied to" @someone "with"
+ auto_join_text_url: "i was looking at", // [string] auto tense for urls: "i was looking at" http:...
+ loading_text: null, // [string] optional loading text, displayed while tweets load
+ query: null // [string] optional search query
+ };
+
+ if(o) $.extend(s, o);
+
+ $.fn.extend({
+ linkUrl: function() {
+ var returning = [];
+ var regexp = /((ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?)/gi;
+ this.each(function() {
+ returning.push(this.replace(regexp,"<a href=\"$1\">$1</a>"));
+ });
+ return $(returning);
+ },
+ linkUser: function() {
+ var returning = [];
+ var regexp = /[\@]+([A-Za-z0-9-_]+)/gi;
+ this.each(function() {
+ returning.push(this.replace(regexp,"<a href=\"http://twitter.com/$1\">@$1</a>"));
+ });
+ return $(returning);
+ },
+ linkHash: function() {
+ var returning = [];
+ var regexp = / [\#]+([A-Za-z0-9-_]+)/gi;
+ this.each(function() {
+ returning.push(this.replace(regexp, ' <a href="http://search.twitter.com/search?q=&tag=$1&lang=all&from='+s.username.join("%2BOR%2B")+'">#$1</a>'));
+ });
+ return $(returning);
+ },
+ capAwesome: function() {
+ var returning = [];
+ this.each(function() {
+ returning.push(this.replace(/\b(awesome)\b/gi, '<span class="awesome">$1</span>'));
+ });
+ return $(returning);
+ },
+ capEpic: function() {
+ var returning = [];
+ this.each(function() {
+ returning.push(this.replace(/\b(epic)\b/gi, '<span class="epic">$1</span>'));
+ });
+ return $(returning);
+ },
+ makeHeart: function() {
+ var returning = [];
+ this.each(function() {
+ returning.push(this.replace(/(&lt;)+[3]/gi, "<tt class='heart'>&#x2665;</tt>"));
+ });
+ return $(returning);
+ }
+ });
+
+ function relative_time(time_value) {
+ var parsed_date = Date.parse(time_value);
+ var relative_to = (arguments.length > 1) ? arguments[1] : new Date();
+ var delta = parseInt((relative_to.getTime() - parsed_date) / 1000);
+ var pluralize = function (singular, n) {
+ return '' + n + ' ' + singular + (n == 1 ? '' : 's');
+ };
+ if(delta < 60) {
+ return 'less than a minute ago';
+ } else if(delta < (45*60)) {
+ return 'about ' + pluralize("minute", parseInt(delta / 60)) + ' ago';
+ } else if(delta < (24*60*60)) {
+ return 'about ' + pluralize("hour", parseInt(delta / 3600)) + ' ago';
+ } else {
+ return 'about ' + pluralize("day", parseInt(delta / 86400)) + ' ago';
+ }
+ }
+
+ function build_url() {
+ var proto = ('https:' == document.location.protocol ? 'https:' : 'http:');
+ if (s.list) {
+ return proto+"//api.twitter.com/1/"+s.username[0]+"/lists/"+s.list+"/statuses.json?per_page="+s.count+"&callback=?";
+ } else if (s.query == null && s.username.length == 1) {
+ return proto+'//twitter.com/status/user_timeline/'+s.username[0]+'.json?count='+s.count+'&callback=?';
+ } else {
+ var query = (s.query || 'from:'+s.username.join('%20OR%20from:'));
+ return proto+'//search.twitter.com/search.json?&q='+query+'&rpp='+s.count+'&callback=?';
+ }
+ }
+
+ return this.each(function(){
+ var list = $('<ul class="tweet_list">').appendTo(this);
+ var intro = '<p class="tweet_intro">'+s.intro_text+'</p>';
+ var outro = '<p class="tweet_outro">'+s.outro_text+'</p>';
+ var loading = $('<p class="loading">'+s.loading_text+'</p>');
+
+ if(typeof(s.username) == "string"){
+ s.username = [s.username];
+ }
+
+ if (s.loading_text) $(this).append(loading);
+ $.getJSON(build_url(), function(data){
+ if (s.loading_text) loading.remove();
+ if (s.intro_text) list.before(intro);
+ $.each((data.results || data), function(i,item){
+ // auto join text based on verb tense and content
+ if (s.join_text == "auto") {
+ if (item.text.match(/^(@([A-Za-z0-9-_]+)) .*/i)) {
+ var join_text = s.auto_join_text_reply;
+ } else if (item.text.match(/(^\w+:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&\?\/.=]+) .*/i)) {
+ var join_text = s.auto_join_text_url;
+ } else if (item.text.match(/^((\w+ed)|just) .*/im)) {
+ var join_text = s.auto_join_text_ed;
+ } else if (item.text.match(/^(\w*ing) .*/i)) {
+ var join_text = s.auto_join_text_ing;
+ } else {
+ var join_text = s.auto_join_text_default;
+ }
+ } else {
+ var join_text = s.join_text;
+ };
+
+ var from_user = item.from_user || item.user.screen_name;
+ var profile_image_url = item.profile_image_url || item.user.profile_image_url;
+ var join_template = '<span class="tweet_join"> '+join_text+' </span>';
+ var join = ((s.join_text) ? join_template : ' ');
+ var avatar_template = '<a class="tweet_avatar" href="http://twitter.com/'+from_user+'"><img src="'+profile_image_url+'" height="'+s.avatar_size+'" width="'+s.avatar_size+'" alt="'+from_user+'\'s avatar" title="'+from_user+'\'s avatar" border="0"/></a>';
+ var avatar = (s.avatar_size ? avatar_template : '');
+ var date = '<a href="http://twitter.com/'+from_user+'/statuses/'+item.id+'" title="view tweet on twitter">'+relative_time(item.created_at)+'</a>';
+ var text = '<span class="tweet_text">' +$([item.text]).linkUrl().linkUser().linkHash().makeHeart().capAwesome().capEpic()[0]+ '</span>';
+
+ // until we create a template option, arrange the items below to alter a tweet's display.
+ list.append('<li>' + avatar + date + join + text + '</li>');
+
+ list.children('li:first').addClass('tweet_first');
+ list.children('li:odd').addClass('tweet_even');
+ list.children('li:even').addClass('tweet_odd');
+ });
+ if (s.outro_text) list.after(outro);
+ });
+
+ });
+ };
+})(jQuery); \ No newline at end of file
diff --git a/doc/source/_static/nature.css b/doc/source/_static/nature.css
new file mode 100644
index 0000000..a98bd42
--- /dev/null
+++ b/doc/source/_static/nature.css
@@ -0,0 +1,245 @@
+/*
+ * nature.css_t
+ * ~~~~~~~~~~~~
+ *
+ * Sphinx stylesheet -- nature theme.
+ *
+ * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+
+@import url("basic.css");
+
+/* -- page layout ----------------------------------------------------------- */
+
+body {
+ font-family: Arial, sans-serif;
+ font-size: 100%;
+ background-color: #111;
+ color: #555;
+ margin: 0;
+ padding: 0;
+}
+
+div.documentwrapper {
+ float: left;
+ width: 100%;
+}
+
+div.bodywrapper {
+ margin: 0 0 0 {{ theme_sidebarwidth|toint }}px;
+}
+
+hr {
+ border: 1px solid #B1B4B6;
+}
+
+div.document {
+ background-color: #eee;
+}
+
+div.body {
+ background-color: #ffffff;
+ color: #3E4349;
+ padding: 0 30px 30px 30px;
+ font-size: 0.9em;
+}
+
+div.footer {
+ color: #555;
+ width: 100%;
+ padding: 13px 0;
+ text-align: center;
+ font-size: 75%;
+}
+
+div.footer a {
+ color: #444;
+ text-decoration: underline;
+}
+
+div.related {
+ background-color: #6BA81E;
+ line-height: 32px;
+ color: #fff;
+ text-shadow: 0px 1px 0 #444;
+ font-size: 0.9em;
+}
+
+div.related a {
+ color: #E2F3CC;
+}
+
+div.sphinxsidebar {
+ font-size: 0.75em;
+ line-height: 1.5em;
+}
+
+div.sphinxsidebarwrapper{
+ padding: 20px 0;
+}
+
+div.sphinxsidebar h3,
+div.sphinxsidebar h4 {
+ font-family: Arial, sans-serif;
+ color: #222;
+ font-size: 1.2em;
+ font-weight: normal;
+ margin: 0;
+ padding: 5px 10px;
+ background-color: #ddd;
+ text-shadow: 1px 1px 0 white
+}
+
+div.sphinxsidebar h4{
+ font-size: 1.1em;
+}
+
+div.sphinxsidebar h3 a {
+ color: #444;
+}
+
+
+div.sphinxsidebar p {
+ color: #888;
+ padding: 5px 20px;
+}
+
+div.sphinxsidebar p.topless {
+}
+
+div.sphinxsidebar ul {
+ margin: 10px 20px;
+ padding: 0;
+ color: #000;
+}
+
+div.sphinxsidebar a {
+ color: #444;
+}
+
+div.sphinxsidebar input {
+ border: 1px solid #ccc;
+ font-family: sans-serif;
+ font-size: 1em;
+}
+
+div.sphinxsidebar input[type=text]{
+ margin-left: 20px;
+}
+
+/* -- body styles ----------------------------------------------------------- */
+
+a {
+ color: #005B81;
+ text-decoration: none;
+}
+
+a:hover {
+ color: #E32E00;
+ text-decoration: underline;
+}
+
+div.body h1,
+div.body h2,
+div.body h3,
+div.body h4,
+div.body h5,
+div.body h6 {
+ font-family: Arial, sans-serif;
+ background-color: #BED4EB;
+ font-weight: normal;
+ color: #212224;
+ margin: 30px 0px 10px 0px;
+ padding: 5px 0 5px 10px;
+ text-shadow: 0px 1px 0 white
+}
+
+div.body h1 { border-top: 20px solid white; margin-top: 0; font-size: 200%; }
+div.body h2 { font-size: 150%; background-color: #C8D5E3; }
+div.body h3 { font-size: 120%; background-color: #D8DEE3; }
+div.body h4 { font-size: 110%; background-color: #D8DEE3; }
+div.body h5 { font-size: 100%; background-color: #D8DEE3; }
+div.body h6 { font-size: 100%; background-color: #D8DEE3; }
+
+a.headerlink {
+ color: #c60f0f;
+ font-size: 0.8em;
+ padding: 0 4px 0 4px;
+ text-decoration: none;
+}
+
+a.headerlink:hover {
+ background-color: #c60f0f;
+ color: white;
+}
+
+div.body p, div.body dd, div.body li {
+ line-height: 1.5em;
+}
+
+div.admonition p.admonition-title + p {
+ display: inline;
+}
+
+div.highlight{
+ background-color: white;
+}
+
+div.note {
+ background-color: #eee;
+ border: 1px solid #ccc;
+}
+
+div.seealso {
+ background-color: #ffc;
+ border: 1px solid #ff6;
+}
+
+div.topic {
+ background-color: #eee;
+}
+
+div.warning {
+ background-color: #ffe4e4;
+ border: 1px solid #f66;
+}
+
+p.admonition-title {
+ display: inline;
+}
+
+p.admonition-title:after {
+ content: ":";
+}
+
+pre {
+ padding: 10px;
+ background-color: White;
+ color: #222;
+ line-height: 1.2em;
+ border: 1px solid #C6C9CB;
+ font-size: 1.1em;
+ margin: 1.5em 0 1.5em 0;
+ -webkit-box-shadow: 1px 1px 1px #d8d8d8;
+ -moz-box-shadow: 1px 1px 1px #d8d8d8;
+}
+
+tt {
+ background-color: #ecf0f3;
+ color: #222;
+ /* padding: 1px 2px; */
+ font-size: 1.1em;
+ font-family: monospace;
+}
+
+.viewcode-back {
+ font-family: Arial, sans-serif;
+}
+
+div.viewcode-block:target {
+ background-color: #f4debf;
+ border-top: 1px solid #ac9;
+ border-bottom: 1px solid #ac9;
+}
diff --git a/doc/source/_static/openstack_logo.png b/doc/source/_static/openstack_logo.png
new file mode 100644
index 0000000..146faec
--- /dev/null
+++ b/doc/source/_static/openstack_logo.png
Binary files differ
diff --git a/doc/source/_static/tweaks.css b/doc/source/_static/tweaks.css
new file mode 100644
index 0000000..3f3fb3f
--- /dev/null
+++ b/doc/source/_static/tweaks.css
@@ -0,0 +1,94 @@
+body {
+ background: #fff url(../_static/header_bg.jpg) top left no-repeat;
+}
+
+#header {
+ width: 950px;
+ margin: 0 auto;
+ height: 102px;
+}
+
+#header h1#logo {
+ background: url(../_static/openstack_logo.png) top left no-repeat;
+ display: block;
+ float: left;
+ text-indent: -9999px;
+ width: 175px;
+ height: 55px;
+}
+
+#navigation {
+ background: url(../_static/header-line.gif) repeat-x 0 bottom;
+ display: block;
+ float: left;
+ margin: 27px 0 0 25px;
+ padding: 0;
+}
+
+#navigation li{
+ float: left;
+ display: block;
+ margin-right: 25px;
+}
+
+#navigation li a {
+ display: block;
+ font-weight: normal;
+ text-decoration: none;
+ background-position: 50% 0;
+ padding: 20px 0 5px;
+ color: #353535;
+ font-size: 14px;
+}
+
+#navigation li a.current, #navigation li a.section {
+ border-bottom: 3px solid #cf2f19;
+ color: #cf2f19;
+}
+
+div.related {
+ background-color: #cde2f8;
+ border: 1px solid #b0d3f8;
+}
+
+div.related a {
+ color: #4078ba;
+ text-shadow: none;
+}
+
+div.sphinxsidebarwrapper {
+ padding-top: 0;
+}
+
+pre {
+ color: #555;
+}
+
+div.documentwrapper h1, div.documentwrapper h2, div.documentwrapper h3, div.documentwrapper h4, div.documentwrapper h5, div.documentwrapper h6 {
+ font-family: 'PT Sans', sans-serif !important;
+ color: #264D69;
+ border-bottom: 1px dotted #C5E2EA;
+ padding: 0;
+ background: none;
+ padding-bottom: 5px;
+}
+
+div.documentwrapper h3 {
+ color: #CF2F19;
+}
+
+a.headerlink {
+ color: #fff !important;
+ margin-left: 5px;
+ background: #CF2F19 !important;
+}
+
+div.body {
+ margin-top: -25px;
+ margin-left: 230px;
+}
+
+div.document {
+ width: 960px;
+ margin: 0 auto;
+} \ No newline at end of file
diff --git a/doc/source/_theme/layout.html b/doc/source/_theme/layout.html
new file mode 100644
index 0000000..750b782
--- /dev/null
+++ b/doc/source/_theme/layout.html
@@ -0,0 +1,83 @@
+{% extends "basic/layout.html" %}
+{% set css_files = css_files + ['_static/tweaks.css'] %}
+{% set script_files = script_files + ['_static/jquery.tweet.js'] %}
+
+{%- macro sidebar() %}
+ {%- if not embedded %}{% if not theme_nosidebar|tobool %}
+ <div class="sphinxsidebar">
+ <div class="sphinxsidebarwrapper">
+ {%- block sidebarlogo %}
+ {%- if logo %}
+ <p class="logo"><a href="{{ pathto(master_doc) }}">
+ <img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
+ </a></p>
+ {%- endif %}
+ {%- endblock %}
+ {%- block sidebartoc %}
+ {%- if display_toc %}
+ <h3><a href="{{ pathto(master_doc) }}">{{ _('Table Of Contents') }}</a></h3>
+ {{ toc }}
+ {%- endif %}
+ {%- endblock %}
+ {%- block sidebarrel %}
+ {%- if prev %}
+ <h4>{{ _('Previous topic') }}</h4>
+ <p class="topless"><a href="{{ prev.link|e }}"
+ title="{{ _('previous chapter') }}">{{ prev.title }}</a></p>
+ {%- endif %}
+ {%- if next %}
+ <h4>{{ _('Next topic') }}</h4>
+ <p class="topless"><a href="{{ next.link|e }}"
+ title="{{ _('next chapter') }}">{{ next.title }}</a></p>
+ {%- endif %}
+ {%- endblock %}
+ {%- block sidebarsourcelink %}
+ {%- if show_source and has_source and sourcename %}
+ <h3>{{ _('This Page') }}</h3>
+ <ul class="this-page-menu">
+ <li><a href="{{ pathto('_sources/' + sourcename, true)|e }}"
+ rel="nofollow">{{ _('Show Source') }}</a></li>
+ </ul>
+ {%- endif %}
+ {%- endblock %}
+ {%- if customsidebar %}
+ {% include customsidebar %}
+ {%- endif %}
+ {%- block sidebarsearch %}
+ {%- if pagename != "search" %}
+ <div id="searchbox" style="display: none">
+ <h3>{{ _('Quick search') }}</h3>
+ <form class="search" action="{{ pathto('search') }}" method="get">
+ <input type="text" name="q" size="18" />
+ <input type="submit" value="{{ _('Go') }}" />
+ <input type="hidden" name="check_keywords" value="yes" />
+ <input type="hidden" name="area" value="default" />
+ </form>
+ <p class="searchtip" style="font-size: 90%">
+ {{ _('Enter search terms or a module, class or function name.') }}
+ </p>
+ </div>
+ <script type="text/javascript">$('#searchbox').show(0);</script>
+ {%- endif %}
+ {%- endblock %}
+ </div>
+ </div>
+ {%- endif %}{% endif %}
+{%- endmacro %}
+
+{% block relbar1 %}{% endblock relbar1 %}
+
+{% block header %}
+ <div id="header">
+ <h1 id="logo"><a href="http://www.openstack.org/">OpenStack</a></h1>
+ <ul id="navigation">
+ <li><a href="http://www.openstack.org/" title="Go to the Home page" class="link">Home</a></li>
+ <li><a href="http://www.openstack.org/projects/" title="Go to the OpenStack Projects page">Projects</a></li>
+ <li><a href="http://www.openstack.org/user-stories/" title="Go to the User Stories page" class="link">User Stories</a></li>
+ <li><a href="http://www.openstack.org/community/" title="Go to the Community page" class="link">Community</a></li>
+ <li><a href="http://www.openstack.org/blog/" title="Go to the OpenStack Blog">Blog</a></li>
+ <li><a href="http://wiki.openstack.org/" title="Go to the OpenStack Wiki">Wiki</a></li>
+ <li><a href="http://docs.openstack.org/" title="Go to OpenStack Documentation" class="current">Documentation</a></li>
+ </ul>
+ </div>
+{% endblock %} \ No newline at end of file
diff --git a/doc/source/_theme/theme.conf b/doc/source/_theme/theme.conf
new file mode 100644
index 0000000..1cc4004
--- /dev/null
+++ b/doc/source/_theme/theme.conf
@@ -0,0 +1,4 @@
+[theme]
+inherit = basic
+stylesheet = nature.css
+pygments_style = tango
diff --git a/doc/source/commands.rst b/doc/source/commands.rst
index 0b93c64..a9d10c9 100644
--- a/doc/source/commands.rst
+++ b/doc/source/commands.rst
@@ -12,9 +12,9 @@ Commands take the form::
openstack [<global-options>] <object-1> <action> [<object-2>] [<command-arguments>]
-* All long options names begin with two dashes ('--') and use a single dash
- ('-') internally between words (--like-this). Underscores ('_') are not used
- in option names.
+* All long options names begin with two dashes (``--``) and use a single dash
+ (``-``) internally between words (``--like-this``). Underscores (``_``) are
+ not used in option names.
Global Options
@@ -25,11 +25,12 @@ invocation regardless of action to be performed. They include authentication
credentials and API version selection. Most global options have a corresponding
environment variable that may also be used to set the value. If both are
present, the command-line option takes priority. The environment variable
-names are derived from the option name by dropping the leading dashes ('--'),
-converting each embedded dash ('-') to an underscore ('_'), and converting
+names are derived from the option name by dropping the leading dashes (``--``),
+converting each embedded dash (``-``) to an underscore (``_``), and converting
to upper case.
-For example, ``--os-username`` can be set from the environment via ``OS_USERNAME``.
+For example, the default value of ``--os-username`` can be set by defining
+the environment variable ``OS_USERNAME``.
Command Object(s) and Action
@@ -61,12 +62,49 @@ They follow the same style as the global options and always appear between
the command and any positional arguments the command requires.
+Actions
+-------
+
+The actions used by OpenStackClient are defined below to provide a consistent
+meaning to each action. Many of them have logical opposite actions.
+Those actions with an opposite action are noted in parens if applicable.
+
+* ``authorize`` - authorize a token (used in OAuth)
+* ``add`` (``remove``) - add some object to a container object; the command
+ is built in the order of ``container add object <container> <object>``,
+ the positional arguments appear in the same order
+* ``create`` (``delete``) - create a new occurrence of the specified object
+* ``delete`` (``create``) - delete a specific occurrence of the specified object
+* ``issue`` (``revoke``) - issue a token
+* ``list`` - display summary information about multiple objects
+* ``lock`` (``unlock``)
+* ``migrate`` - move a server to a different host; ``--live`` performs a
+ live migration if possible
+* ``pause`` (``unpause``) - stop a server and leave it in memory
+* ``reboot`` - forcibly reboot a server
+* ``rebuild`` - rebuild a server using (most of) the same arguments as in the original create
+* ``remove`` (``add``) - remove an object from a group of objects
+* ``rescue`` (``unrescue``) - reboot a server in a special rescue mode allowing access to the original disks
+* ``resize`` - change a server's flavor
+* ``resume`` (``suspend``) - return a suspended server to running state
+* ``revoke`` (``issue``) - revoke a token
+* ``save`` - download an object locally
+* ``set`` (``unset``) - set a property on the object, formerly called metadata
+* ``show`` - display detailed information about the specific object
+* ``suspend`` (``resume``) - stop a server and save to disk freeing memory
+* ``unlock`` (``lock``)
+* ``unpause`` (``pause``) - return a paused server to running state
+* ``unrescue`` (``rescue``) - return a server to normal boot mode
+* ``unset`` (``set``) - remove an attribute of the object
+
+
Implementation
==============
The command structure is designed to support seamless addition of plugin
command modules via ``setuptools`` entry points. The plugin commands must
-be subclasses of Cliff's command.Command object.
+be subclasses of Cliff's command.Command object. See :doc:`plugins` for
+more information.
Command Entry Points
@@ -81,7 +119,7 @@ entry points have the form::
verb_object = fully.qualified.module.vXX.object:VerbObject
-For example, the 'list user' command fir the Identity API is identified in
+For example, the ``list user`` command for the Identity API is identified in
``setup.cfg`` with::
openstack.identity.v3 =
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 47025b6..fcce1da 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -101,7 +101,8 @@ pygments_style = 'sphinx'
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
-html_theme = 'default'
+html_theme_path = ["."]
+html_theme = '_theme'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
diff --git a/doc/source/index.rst b/doc/source/index.rst
index f42ea1b..2bb8f38 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -1,7 +1,9 @@
-OpenStack Client
-================
+===============
+OpenStackClient
+===============
-This is a command-line client for OpenStack that unifies the operation
+OpenStackClient (aka OSC) is a command-line client for OpenStack that
+unifies the operation
of python-keystoneclient, python-novaclient, python-glanceclient and
python-cinderclient in a single shell with a uniform command structure.
diff --git a/doc/source/man/openstack.rst b/doc/source/man/openstack.rst
index 3a780e2..16f0bc4 100644
--- a/doc/source/man/openstack.rst
+++ b/doc/source/man/openstack.rst
@@ -18,7 +18,7 @@ DESCRIPTION
===========
:program:`openstack` provides a common command-line interface to OpenStack APIs. It is generally
-equivalent to the CLIs provided by the OpenStack project client librariess, but with
+equivalent to the CLIs provided by the OpenStack project client libraries, but with
a distinct and consistent command structure.
:program:`openstack` uses a similar authentication scheme as the OpenStack project CLIs, with
@@ -37,49 +37,51 @@ command line. The primary difference is the use of 'project' in the name of the
OPTIONS
=======
+:program:`openstack` takes global options that control overall behaviour and command-specific options that control the command operation. Most global options have a corresponding environment variable that may also be used to set the value. If both are present, the command-line option takes priority. The environment variable names are derived from the option name by dropping the leading dashes ('--'), converting each embedded dash ('-') to an underscore ('_'), and converting to upper case.
+
:program:`openstack` recognizes the following global topions:
-:option:`--os-auth-url <auth-url>`
+:option:`--os-auth-url` <auth-url>
Authentication URL
-:option:`--os-project-name <auth-project-name>`
- Authentication project name (only one of :option:`--os-project-name` or :option:`--os-project-id` need be supplied)
+:option:`--os-domain-name` <auth-domain-name> | :option:`--os-domain-id` <auth-domain-id>
+ Domain-level authorization scope (name or ID)
+
+:option:`--os-project-name` <auth-project-name> | :option:`--os-project-id` <auth-project-id>
+ Project-level authentication scope (name or ID)
-:option:`--os-project-id <auth-project-id>`
- Authentication tenant ID (only one of :option:`--os-project-name` or :option:`--os-project-id` need be supplied)
+:option:`--os-project-domain-name` <auth-project-domain-name> | :option:`--os-project-domain-id` <auth-project-domain-id>
+ Domain name or id containing project
-:option:`--os-username <auth-username>`
+:option:`--os-username` <auth-username>
Authentication username
-:option:`--os-password <auth-password>`
+:option:`--os-user-domain-name` <auth-user-domain-name> | :option:`--os-user-domain-id` <auth-user-domain-id>
+ Domain name or id containing user
+
+:option:`--os-password` <auth-password>
Authentication password
-:option:`--os-region-name <auth-region-name>`
+:option:`--os-region-name` <auth-region-name>
Authentication region name
-:option:`--os-default-domain <auth-domain>`
+:option:`--os-default-domain` <auth-domain>
Default domain ID (Default: 'default')
-:options:`--os-use-keyring`
+:option:`--os-use-keyring`
Use keyring to store password (default: False)
-:option:`--os-cacert <ca-bundle-file>`
+:option:`--os-cacert` <ca-bundle-file>
CA certificate bundle file
-:option:`--verify|--insecure`
+:option:`--verify` | :option:`--insecure`
Verify or ignore server certificate (default: verify)
-:option:`--os-identity-api-version <identity-api-version>`
+:option:`--os-identity-api-version` <identity-api-version>
Identity API version (Default: 2.0)
-:option:`--os-XXXX-api-version <XXXX-api-version>`
- Additional API version options will be presend depending on the installed API libraries.
-
-
-NOTES
-=====
-
-[This section intentionally left blank. So there.]
+:option:`--os-XXXX-api-version` <XXXX-api-version>
+ Additional API version options will be available depending on the installed API libraries.
COMMANDS
@@ -87,7 +89,7 @@ COMMANDS
To get a list of the available commands::
- openstack -h
+ openstack --help
To get a description of a specific command::
@@ -101,12 +103,23 @@ To get a description of a specific command::
Print help for an individual command
+NOTES
+=====
+
+The command list displayed in help output reflects the API versions selected. For
+example, to see Identity v3 commands ``OS_IDENTITY_API_VERSION`` must be set to ``3``.
+
+
EXAMPLES
========
Show the detailed information for server ``appweb01``::
- openstack --os-tenant-name ExampleCo --os-username demo --os-password secrete --os-auth-url http://localhost:5000:/v2.0 server show appweb01
+ openstack \
+ --os-project-name ExampleCo \
+ --os-username demo --os-password secrete \
+ --os-auth-url http://localhost:5000:/v2.0 \
+ server show appweb01
The same command if the auth environment variables (:envvar:`OS_AUTH_URL`, :envvar:`OS_PROJECT_NAME`,
:envvar:`OS_USERNAME`, :envvar:`OS_PASSWORD`) are set::
@@ -126,25 +139,62 @@ Create a new image::
FILES
=====
- :file:`~/.openstack`
+:file:`~/.openstack`
+ Placeholder for future local state directory. This directory is intended to be shared among multiple OpenStack-related applications; contents are namespaced with an identifier for the app that owns it. Shared contents (such as :file:`~/.openstack/cache`) have no prefix and the contents must be portable.
ENVIRONMENT VARIABLES
=====================
-The following environment variables can be set to alter the behaviour of :program:`openstack`
+The following environment variables can be set to alter the behaviour of :program:`openstack`. Most of them have corresponding command-line options that take precedence if set.
+
+:envvar:`OS_AUTH_URL`
+ Authentication URL
+
+:envvar:`OS_DOMAIN_NAME`
+ Domain-level authorization scope (name or ID)
+
+:envvar:`OS_PROJECT_NAME`
+ Project-level authentication scope (name or ID)
+
+:envvar:`OS_PROJECT_DOMAIN_NAME`
+ Domain name or id containing project
:envvar:`OS_USERNAME`
- Set the username
+ Authentication username
+
+:envvar:`OS_USER_DOMAIN_NAME`
+ Domain name or id containing user
:envvar:`OS_PASSWORD`
- Set the password
+ Authentication password
-:envvar:`OS_PROJECT_NAME`
- Set the project name
+:envvar:`OS_REGION_NAME`
+ Authentication region name
-:envvar:`OS_AUTH_URL`
- Set the authentication URL
+:envvar:`OS_DEFAULT_DOMAIN`
+ Default domain ID (Default: ‘default’)
+
+:envvar:`OS_USE_KEYRING`
+ Use keyring to store password (default: False)
+
+:envvar:`OS_CACERT`
+ CA certificate bundle file
+
+:envvar:`OS_COMPUTE_API_VERISON`
+ Compute API version (Default: 2)
+
+:envvar:`OS_IDENTITY_API_VERISON`
+ Identity API version (Default: 2.0)
+
+:envvar:`OS_IMAGE_API_VERISON`
+ Image API version (Default: 1)
+
+:envvar:`OS_VOLUME_API_VERISON`
+ Volume API version (Default: 1)
+
+:envvar:`OS_XXXX_API_VERISON`
+ Additional API version options will be available depending on the installed API libraries.
BUGS
@@ -163,7 +213,7 @@ Please refer to the AUTHORS file distributed with OpenStackClient.
COPYRIGHT
=========
-Copyright 2011-2013 OpenStack Foundation and the authors listed in the AUTHORS file.
+Copyright 2011-2014 OpenStack Foundation and the authors listed in the AUTHORS file.
LICENSE
diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst
index 5cea16c..690c126 100644
--- a/doc/source/plugins.rst
+++ b/doc/source/plugins.rst
@@ -11,7 +11,7 @@ Implementation
--------------
Plugins are discovered by enumerating the entry points
-found under ``openstack.cli.extension`` and initializing the specified
+found under :py:mod:`openstack.cli.extension` and initializing the specified
client module.
::
@@ -42,5 +42,5 @@ defined for the API version:
plugin_list = oscplugin.v1.plugin:ListPlugin
plugin_show = oscplugin.v1.plugin:ShowPlugin
-Note that OSC defines the group name as ``openstack.<api-name>.v<version>``
+Note that OSC defines the group name as :py:mod:`openstack.<api-name>.v<version>`
so the version should not contain the leading 'v' character.
diff --git a/doc/source/releases.rst b/doc/source/releases.rst
index 7d74318..9551db2 100644
--- a/doc/source/releases.rst
+++ b/doc/source/releases.rst
@@ -2,6 +2,62 @@
Release Notes
=============
+0.4.0 (20 Jun 2014)
+===================
+
+* Bug 1184012_: fix Identity v2 endpoint command name/id handling
+* Bug 1207615_: add ``--volume`` and ``--force`` to ``image create`` command
+* Bug 1220280_: add ``--property`` to project create and set commands
+* Bug 1246310_: add ``role assignments list`` command
+* Bug 1285800_: rename ``agent`` to ``compute agent``
+* Bug 1289513_: add ``--domain`` to project list
+* Bug 1289594_: fix keypair show output
+* Bug 1292337_: fix ec2 credentials project ID output
+* Bug 1303978_: fix output of ``volume type create`` command
+* Bug 1316870_: display all output when ``--lines`` omitted from ``console log show`` command
+* add 'interface' and 'url' columns to endpoint list command
+* add identity provider create/delete/list/set/show commands
+* change ``volume create --volume-type`` option to ``--type``
+* fix ``server image create`` command output
+* configure appropriate logging levels for ``--verbose``, ``--quiet`` and ``--debug``
+* properly handle properties in Image v1 ``create`` and ``set`` commands
+* rename Identity v2 ``token create`` to ``token issue``
+* add Identity v2 ``token revoke`` command
+* refactor the ``group|user|role list`` command filters so that each command
+ only lists rows of that type of object, ie ``user list`` always lists users, etc.
+* add ``role assignment list`` command
+* add ``extension list`` command
+
+.. _1184012: https://launchpad.net/bugs/1184012
+.. _1207615: https://launchpad.net/bugs/1207615
+.. _1220280: https://launchpad.net/bugs/1220280
+.. _1246310: https://launchpad.net/bugs/1246310
+.. _1285800: https://launchpad.net/bugs/1285800
+.. _1289513: https://launchpad.net/bugs/1289513
+.. _1289594: https://launchpad.net/bugs/1289594
+.. _1292337: https://launchpad.net/bugs/1292337
+.. _1303978: https://launchpad.net/bugs/1303978
+.. _1316870: https://launchpad.net/bugs/1316870
+
+0.3.1 (28 Feb 2014)
+===================
+
+* add ``token create`` command
+* internal changes for Python 3.3 compatibility
+* Bug 1100116_: Prompt interactive user for passwords in ``user create`` and ``user set``
+* Bug 1198171_: add domain support options for Identity v3
+* Bug 1241177_: Fix region handling in volume commands
+* Bug 1256935_: Clean up ``security group rule list`` output format
+* Bug 1269821_: Fix for unreleased Glance client change in internal class structure
+* Bug 1284957_: Correctly pass ``--cacert`` and ``--insecure`` to Identity client in token flow auth
+
+.. _1100116: https://bugs.launchpad.net/ubuntu/+source/python-keystoneclient/+bug/1100116
+.. _1198171: https://bugs.launchpad.net/keystone/+bug/1198171
+.. _1241177: https://bugs.launchpad.net/python-openstackclient/+bug/1241177
+.. _1256935: https://bugs.launchpad.net/python-openstackclient/+bug/1256935
+.. _1269821: https://bugs.launchpad.net/python-openstackclient/+bug/1269821
+.. _1284957: https://bugs.launchpad.net/python-openstackclient/+bug/1284957
+
0.3.0 (17 Dec 2013)
===================
@@ -10,10 +66,6 @@ Release Notes
* add options to support TLS cetificate verification
* add object-store show commands for container and object
-.. commented to save format of bug fix
-.. * 1254168_: OS_REGION_NAME is not used
-.. _1254168: https://bugs.launchpad.net/python-openstackclient/+bug/1254168
-
0.2.2 (20 Sep 2013)
===================
diff --git a/openstack-common.conf b/openstack-common.conf
index 5e55d58..2b26cf2 100644
--- a/openstack-common.conf
+++ b/openstack-common.conf
@@ -1,10 +1,8 @@
[DEFAULT]
# The list of modules to copy from openstack-common
-module=cfg
module=iniparser
module=install_venv_common
-module=openstackkeyring
module=strutils
# The base module to hold the copy of openstack.common
diff --git a/openstackclient/common/__init__.py b/openstackclient/common/__init__.py
index 85ac250..e69de29 100644
--- a/openstackclient/common/__init__.py
+++ b/openstackclient/common/__init__.py
@@ -1,14 +0,0 @@
-# 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.
-#
diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py
index a022406..353a0a1 100644
--- a/openstackclient/common/clientmanager.py
+++ b/openstackclient/common/clientmanager.py
@@ -42,16 +42,26 @@ class ClientManager(object):
"""Manages access to API clients, including authentication."""
identity = ClientCache(identity_client.make_client)
- def __init__(self, token=None, url=None, auth_url=None, project_name=None,
- project_id=None, username=None, password=None,
- region_name=None, verify=True, api_version=None):
+ def __init__(self, token=None, url=None, auth_url=None,
+ domain_id=None, domain_name=None,
+ project_name=None, project_id=None,
+ username=None, password=None,
+ user_domain_id=None, user_domain_name=None,
+ project_domain_id=None, project_domain_name=None,
+ region_name=None, api_version=None, verify=True):
self._token = token
self._url = url
self._auth_url = auth_url
+ self._domain_id = domain_id
+ self._domain_name = domain_name
self._project_name = project_name
self._project_id = project_id
self._username = username
self._password = password
+ self._user_domain_id = user_domain_id
+ self._user_domain_name = user_domain_name
+ self._project_domain_id = project_domain_id
+ self._project_domain_name = project_domain_name
self._region_name = region_name
self._api_version = api_version
self._service_catalog = None
@@ -93,7 +103,7 @@ def get_extension_modules(group):
"""Add extension clients"""
mod_list = []
for ep in pkg_resources.iter_entry_points(group):
- LOG.debug('found extension %r' % ep.name)
+ LOG.debug('found extension %r', ep.name)
__import__(ep.module_name)
module = sys.modules[ep.module_name]
diff --git a/openstackclient/common/commandmanager.py b/openstackclient/common/commandmanager.py
index 553bc92..aa238a2 100644
--- a/openstackclient/common/commandmanager.py
+++ b/openstackclient/common/commandmanager.py
@@ -37,7 +37,6 @@ class CommandManager(cliff.commandmanager.CommandManager):
group = self.namespace
self.group_list.append(group)
for ep in pkg_resources.iter_entry_points(group):
- LOG.debug('found command %r' % ep.name)
cmd_name = (
ep.name.replace('_', ' ')
if self.convert_underscores
diff --git a/openstackclient/common/extension.py b/openstackclient/common/extension.py
new file mode 100644
index 0000000..a8b1a6b
--- /dev/null
+++ b/openstackclient/common/extension.py
@@ -0,0 +1,78 @@
+# Copyright 2012-2013 OpenStack Foundation
+#
+# 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.
+#
+
+"""Extension action implementations"""
+
+import logging
+
+from cliff import lister
+
+from openstackclient.common import exceptions as exc
+from openstackclient.common import utils
+
+
+class ListExtension(lister.Lister):
+ """List extension command"""
+
+ # TODO(mfisch): add support for volume and compute
+ # when the underlying APIs support it. Add support
+ # for network when it's added to openstackclient.
+
+ log = logging.getLogger(__name__ + '.ListExtension')
+
+ def get_parser(self, prog_name):
+ parser = super(ListExtension, self).get_parser(prog_name)
+ parser.add_argument(
+ '--long',
+ action='store_true',
+ default=False,
+ help='List additional fields in output')
+ parser.add_argument(
+ '--identity',
+ action='store_true',
+ default=False,
+ help='List extensions for the Identity API')
+ return parser
+
+ def take_action(self, parsed_args):
+ self.log.debug('take_action(%s)' % parsed_args)
+
+ if parsed_args.long:
+ columns = ('Name', 'Namespace', 'Description',
+ 'Alias', 'Updated', 'Links')
+ else:
+ columns = ('Name', 'Alias', 'Description')
+
+ data = []
+
+ # by default we want to show everything, unless the
+ # user specifies one or more of the APIs to show
+ # for now, only identity is supported
+ show_all = (not parsed_args.identity)
+
+ if parsed_args.identity or show_all:
+ identity_client = self.app.client_manager.identity
+ try:
+ data += identity_client.extensions.list()
+ except Exception:
+ raise exc.CommandError(
+ "Extensions list not supported by"
+ " identity API")
+
+ return (columns,
+ (utils.get_item_properties(
+ s, columns,
+ formatters={},
+ ) for s in data))
diff --git a/openstackclient/common/limits.py b/openstackclient/common/limits.py
index bbc1522..9c9458a 100644
--- a/openstackclient/common/limits.py
+++ b/openstackclient/common/limits.py
@@ -52,7 +52,7 @@ class ShowLimits(lister.Lister):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
volume_client = self.app.client_manager.volume
diff --git a/openstackclient/common/module.py b/openstackclient/common/module.py
index 4a7f062..7f9c52d 100644
--- a/openstackclient/common/module.py
+++ b/openstackclient/common/module.py
@@ -39,7 +39,7 @@ class ListModule(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
data = {}
# Get module versions
diff --git a/openstackclient/common/openstackkeyring.py b/openstackclient/common/openstackkeyring.py
index 34c994b..30450e8 100644
--- a/openstackclient/common/openstackkeyring.py
+++ b/openstackclient/common/openstackkeyring.py
@@ -13,7 +13,7 @@
# under the License.
#
-"""Keyring backend for Openstack, to store encrypted password in a file."""
+"""Keyring backend for OpenStack, to store encrypted password in a file."""
from Crypto.Cipher import AES
@@ -24,8 +24,8 @@ import os
KEYRING_FILE = os.path.join(os.path.expanduser('~'), '.openstack-keyring.cfg')
-class OpenstackKeyring(keyring.backends.file.BaseKeyring):
- """Openstack Keyring to store encrypted password."""
+class OpenStackKeyring(keyring.backends.file.BaseKeyring):
+ """OpenStack Keyring to store encrypted password."""
filename = KEYRING_FILE
def supported(self):
@@ -37,7 +37,7 @@ class OpenstackKeyring(keyring.backends.file.BaseKeyring):
block_size = 32
padding = '0'
- # init the cipher with the class name, upto block_size
+ # init the cipher with the class name, up to block_size
password = __name__[block_size:]
password = password + (block_size - len(password) %
block_size) * padding
@@ -56,5 +56,5 @@ class OpenstackKeyring(keyring.backends.file.BaseKeyring):
def os_keyring():
"""Initialize the openstack keyring."""
- keyring = 'openstackclient.common.openstackkeyring.OpenstackKeyring'
- return keyring.core.load_keyring(None, keyring)
+ ring = 'openstackclient.common.openstackkeyring.OpenStackKeyring'
+ return keyring.core.load_keyring(None, ring)
diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py
index fd482da..e011fd3 100644
--- a/openstackclient/common/quota.py
+++ b/openstackclient/common/quota.py
@@ -79,7 +79,7 @@ class SetQuota(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
volume_client = self.app.client_manager.volume
@@ -148,7 +148,7 @@ class ShowQuota(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
volume_client = self.app.client_manager.volume
diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py
index 91a2089..a420dd5 100644
--- a/openstackclient/common/utils.py
+++ b/openstackclient/common/utils.py
@@ -15,11 +15,12 @@
"""Common client utilities"""
+import getpass
+import logging
import os
import six
import sys
import time
-import uuid
from openstackclient.common import exceptions
from openstackclient.openstack.common import strutils
@@ -35,13 +36,6 @@ def find_resource(manager, name_or_id):
except exceptions.NotFound:
pass
- # Try to get entity as uuid
- try:
- uuid.UUID(str(name_or_id))
- return manager.get(name_or_id)
- except (ValueError, exceptions.NotFound):
- pass
-
# Try directly using the passed value
try:
return manager.get(name_or_id)
@@ -63,15 +57,14 @@ def find_resource(manager, name_or_id):
# Eventually this should be pulled from a common set
# of client exceptions.
except Exception as ex:
- try:
- return manager.find(display_name=name_or_id)
- except Exception:
- pass
-
if type(ex).__name__ == 'NotFound':
msg = "No %s with a name or ID of '%s' exists." % \
(manager.resource_class.__name__.lower(), name_or_id)
raise exceptions.CommandError(msg)
+ if type(ex).__name__ == 'NoUniqueMatch':
+ msg = "More than one %s exists with the name '%s'." % \
+ (manager.resource_class.__name__.lower(), name_or_id)
+ raise exceptions.CommandError(msg)
else:
raise
@@ -215,3 +208,41 @@ def wait_for_status(status_f,
callback(progress)
time.sleep(sleep_time)
return retval
+
+
+def get_effective_log_level():
+ """Returns the lowest logging level considered by logging handlers
+
+ Retrieve an return the smallest log level set among the root
+ logger's handlers (in case of multiple handlers).
+ """
+ root_log = logging.getLogger()
+ min_log_lvl = logging.CRITICAL
+ 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.")
+
+
+def read_blob_file_contents(blob_file):
+ try:
+ with open(blob_file) as file:
+ blob = file.read().strip()
+ return blob
+ except IOError:
+ msg = "Error occurred trying to read from file %s"
+ raise exceptions.CommandError(msg % blob_file)
diff --git a/openstackclient/compute/__init__.py b/openstackclient/compute/__init__.py
index 85ac250..e69de29 100644
--- a/openstackclient/compute/__init__.py
+++ b/openstackclient/compute/__init__.py
@@ -1,14 +0,0 @@
-# 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.
-#
diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py
index 4ccb2f6..3dacee8 100644
--- a/openstackclient/compute/client.py
+++ b/openstackclient/compute/client.py
@@ -34,7 +34,11 @@ def make_client(instance):
API_NAME,
instance._api_version[API_NAME],
API_VERSIONS)
- LOG.debug('instantiating compute client: %s' % compute_client)
+ LOG.debug('instantiating compute client: %s', compute_client)
+
+ # Set client http_log_debug to True if verbosity level is high enough
+ http_log_debug = utils.get_effective_log_level() <= logging.DEBUG
+
client = compute_client(
username=instance._username,
api_key=instance._password,
@@ -49,7 +53,8 @@ def make_client(instance):
extensions=[],
service_type=API_NAME,
# FIXME(dhellmann): what is service_name?
- service_name='')
+ service_name='',
+ http_log_debug=http_log_debug)
# Populate the Nova client to skip another auth query to Identity
if instance._url:
diff --git a/openstackclient/compute/v2/__init__.py b/openstackclient/compute/v2/__init__.py
index 85ac250..e69de29 100644
--- a/openstackclient/compute/v2/__init__.py
+++ b/openstackclient/compute/v2/__init__.py
@@ -1,14 +0,0 @@
-# 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.
-#
diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py
index b79ebbe..14c4b2c 100644
--- a/openstackclient/compute/v2/agent.py
+++ b/openstackclient/compute/v2/agent.py
@@ -26,7 +26,7 @@ from openstackclient.common import utils
class CreateAgent(show.ShowOne):
- """Create agent command"""
+ """Create compute agent command"""
log = logging.getLogger(__name__ + ".CreateAgent")
@@ -60,7 +60,7 @@ class CreateAgent(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
args = (
parsed_args.os,
@@ -75,7 +75,7 @@ class CreateAgent(show.ShowOne):
class DeleteAgent(command.Command):
- """Delete agent command"""
+ """Delete compute agent command"""
log = logging.getLogger(__name__ + ".DeleteAgent")
@@ -88,14 +88,14 @@ class DeleteAgent(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
compute_client.agents.delete(parsed_args.id)
return
class ListAgent(lister.Lister):
- """List agent command"""
+ """List compute agent command"""
log = logging.getLogger(__name__ + ".ListAgent")
@@ -108,7 +108,7 @@ class ListAgent(lister.Lister):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
columns = (
"Agent ID",
@@ -127,7 +127,7 @@ class ListAgent(lister.Lister):
class SetAgent(show.ShowOne):
- """Set agent command"""
+ """Set compute agent command"""
log = logging.getLogger(__name__ + ".SetAgent")
@@ -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>",
@@ -152,7 +152,7 @@ class SetAgent(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
args = (
parsed_args.id,
diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py
index d786d7e..8fff4e6 100644
--- a/openstackclient/compute/v2/aggregate.py
+++ b/openstackclient/compute/v2/aggregate.py
@@ -47,7 +47,7 @@ class AddAggregateHost(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
@@ -89,7 +89,7 @@ class CreateAggregate(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
@@ -124,7 +124,7 @@ class DeleteAggregate(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
data = utils.find_resource(
@@ -150,7 +150,7 @@ class ListAggregate(lister.Lister):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
@@ -207,7 +207,7 @@ class RemoveAggregateHost(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
@@ -257,7 +257,7 @@ class SetAggregate(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
aggregate = utils.find_resource(
@@ -304,7 +304,7 @@ class ShowAggregate(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
data = utils.find_resource(
diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py
index 8f49c51..e1f84e2 100644
--- a/openstackclient/compute/v2/console.py
+++ b/openstackclient/compute/v2/console.py
@@ -48,16 +48,20 @@ class ShowConsoleLog(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
server = utils.find_resource(
compute_client.servers,
parsed_args.server,
)
- # NOTE(dtroyer): get_console_output() appears to shortchange the
- # output by one line
- data = server.get_console_output(length=parsed_args.lines + 1)
+ length = parsed_args.lines
+ if length:
+ # NOTE(dtroyer): get_console_output() appears to shortchange the
+ # output by one line
+ length += 1
+
+ data = server.get_console_output(length=length)
sys.stdout.write(data)
return
@@ -100,7 +104,7 @@ class ShowConsoleURL(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
server = utils.find_resource(
compute_client.servers,
diff --git a/openstackclient/compute/v2/fixedip.py b/openstackclient/compute/v2/fixedip.py
index c41fed4..d105e39 100644
--- a/openstackclient/compute/v2/fixedip.py
+++ b/openstackclient/compute/v2/fixedip.py
@@ -42,7 +42,7 @@ class AddFixedIP(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
network = utils.find_resource(
@@ -75,7 +75,7 @@ class RemoveFixedIP(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
server = utils.find_resource(
diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py
index d1d08d8..7342979 100644
--- a/openstackclient/compute/v2/flavor.py
+++ b/openstackclient/compute/v2/flavor.py
@@ -93,7 +93,7 @@ class CreateFlavor(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
args = (
@@ -128,7 +128,7 @@ class DeleteFlavor(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
flavor = utils.find_resource(compute_client.flavors,
parsed_args.flavor)
@@ -142,7 +142,7 @@ class ListFlavor(lister.Lister):
log = logging.getLogger(__name__ + ".ListFlavor")
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
columns = (
"ID",
@@ -177,7 +177,7 @@ class ShowFlavor(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
flavor = utils.find_resource(compute_client.flavors,
parsed_args.flavor)._info.copy()
diff --git a/openstackclient/compute/v2/floatingip.py b/openstackclient/compute/v2/floatingip.py
index 7ed847f..72b19c6 100644
--- a/openstackclient/compute/v2/floatingip.py
+++ b/openstackclient/compute/v2/floatingip.py
@@ -45,7 +45,7 @@ class AddFloatingIP(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
server = utils.find_resource(
@@ -70,7 +70,7 @@ class CreateFloatingIP(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
floating_ip = compute_client.floating_ips.create(parsed_args.pool)
@@ -89,12 +89,12 @@ class DeleteFloatingIP(command.Command):
parser.add_argument(
"ip_address",
metavar="<ip-address>",
- help="IP address to add to server",
+ help="IP address to delete",
)
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
floating_ip = utils.find_resource(
@@ -112,7 +112,7 @@ class ListFloatingIP(lister.Lister):
log = logging.getLogger(__name__ + '.ListFloatingIP')
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
columns = ('ID', 'Pool', 'IP', 'Fixed IP', 'Instance ID')
@@ -146,7 +146,7 @@ class RemoveFloatingIP(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
server = utils.find_resource(
diff --git a/openstackclient/compute/v2/floatingippool.py b/openstackclient/compute/v2/floatingippool.py
index e1da97c..d5e8d0d 100644
--- a/openstackclient/compute/v2/floatingippool.py
+++ b/openstackclient/compute/v2/floatingippool.py
@@ -28,7 +28,7 @@ class ListFloatingIPPool(lister.Lister):
log = logging.getLogger(__name__ + '.ListFloatingIPPool')
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
columns = ('Name',)
diff --git a/openstackclient/compute/v2/host.py b/openstackclient/compute/v2/host.py
index 44f457d..4f72738 100644
--- a/openstackclient/compute/v2/host.py
+++ b/openstackclient/compute/v2/host.py
@@ -36,7 +36,7 @@ class ListHost(lister.Lister):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
columns = (
"Host Name",
@@ -64,7 +64,7 @@ class ShowHost(lister.Lister):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
columns = (
"Host",
diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py
index 535062e..334987e 100644
--- a/openstackclient/compute/v2/hypervisor.py
+++ b/openstackclient/compute/v2/hypervisor.py
@@ -39,7 +39,7 @@ class ListHypervisor(lister.Lister):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
columns = (
"ID",
@@ -71,7 +71,7 @@ class ShowHypervisor(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
hypervisor = utils.find_resource(compute_client.hypervisors,
parsed_args.id)._info.copy()
diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py
index d68dae0..972443a 100644
--- a/openstackclient/compute/v2/keypair.py
+++ b/openstackclient/compute/v2/keypair.py
@@ -48,7 +48,7 @@ class CreateKeypair(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
public_key = parsed_args.public_key
@@ -93,7 +93,7 @@ class DeleteKeypair(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
compute_client.keypairs.delete(parsed_args.name)
return
@@ -105,7 +105,7 @@ class ListKeypair(lister.Lister):
log = logging.getLogger(__name__ + ".ListKeypair")
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
columns = (
"Name",
@@ -140,13 +140,13 @@ class ShowKeypair(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
keypair = utils.find_resource(compute_client.keypairs,
parsed_args.name)
info = {}
- info.update(keypair._info['keypair'])
+ info.update(keypair._info)
if not parsed_args.public_key:
del info['public_key']
return zip(*sorted(six.iteritems(info)))
diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py
index a1dc786..0ba55c9 100644
--- a/openstackclient/compute/v2/security_group.py
+++ b/openstackclient/compute/v2/security_group.py
@@ -31,15 +31,23 @@ from openstackclient.common import utils
def _xform_security_group_rule(sgroup):
info = {}
info.update(sgroup)
- info.update(
- {'port_range': "%u:%u" % (
- info.pop('from_port'),
- info.pop('to_port'),
- )}
- )
- info['ip_range'] = info['ip_range']['cidr']
+ from_port = info.pop('from_port')
+ to_port = info.pop('to_port')
+ if isinstance(from_port, int) and isinstance(to_port, int):
+ port_range = {'port_range': "%u:%u" % (from_port, to_port)}
+ elif from_port is None and to_port is None:
+ port_range = {'port_range': ""}
+ else:
+ port_range = {'port_range': "%s:%s" % (from_port, to_port)}
+ info.update(port_range)
+ if 'cidr' in info['ip_range']:
+ info['ip_range'] = info['ip_range']['cidr']
+ else:
+ info['ip_range'] = ''
if info['ip_protocol'] == 'icmp':
info['port_range'] = ''
+ elif info['ip_protocol'] is None:
+ info['ip_protocol'] = ''
return info
@@ -63,7 +71,7 @@ class CreateSecurityGroup(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
@@ -92,7 +100,7 @@ class DeleteSecurityGroup(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
data = utils.find_resource(
@@ -126,7 +134,7 @@ class ListSecurityGroup(lister.Lister):
except KeyError:
return project_id
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
columns = (
@@ -179,7 +187,7 @@ class SetSecurityGroup(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
data = utils.find_resource(
@@ -220,7 +228,7 @@ class ShowSecurityGroup(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
info = {}
@@ -278,7 +286,7 @@ class CreateSecurityGroupRule(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
group = utils.find_resource(
@@ -332,7 +340,7 @@ class DeleteSecurityGroupRule(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
group = utils.find_resource(
@@ -366,7 +374,7 @@ class ListSecurityGroupRule(lister.Lister):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
group = utils.find_resource(
diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py
index 87f5f68..f75444f 100644
--- a/openstackclient/compute/v2/server.py
+++ b/openstackclient/compute/v2/server.py
@@ -121,7 +121,7 @@ class AddServerVolume(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
volume_client = self.app.client_manager.volume
@@ -162,7 +162,7 @@ class AddServerSecurityGroup(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
@@ -278,7 +278,7 @@ class CreateServer(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
# Lookup parsed_args.image
@@ -363,8 +363,8 @@ class CreateServer(show.ShowOne):
scheduler_hints=hints,
config_drive=config_drive)
- self.log.debug('boot_args: %s' % boot_args)
- self.log.debug('boot_kwargs: %s' % boot_kwargs)
+ self.log.debug('boot_args: %s', boot_args)
+ self.log.debug('boot_kwargs: %s', boot_kwargs)
server = compute_client.servers.create(*boot_args, **boot_kwargs)
if parsed_args.wait:
@@ -375,7 +375,7 @@ class CreateServer(show.ShowOne):
):
sys.stdout.write('\n')
else:
- self.log.error('Error creating server: %s' %
+ self.log.error('Error creating server: %s',
parsed_args.server_name)
sys.stdout.write('\nError creating server')
raise SystemExit
@@ -393,7 +393,7 @@ class CreateServerImage(show.ShowOne):
parser = super(CreateServerImage, self).get_parser(prog_name)
parser.add_argument(
'server',
- metavar='<server',
+ metavar='<server>',
help='Server (name or ID)',
)
parser.add_argument(
@@ -409,7 +409,7 @@ class CreateServerImage(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
image_client = self.app.client_manager.image
server = utils.find_resource(
@@ -421,7 +421,7 @@ class CreateServerImage(show.ShowOne):
else:
name = server.name
- image = compute_client.servers.create_image(
+ image_id = compute_client.servers.create_image(
server,
name,
)
@@ -429,13 +429,13 @@ class CreateServerImage(show.ShowOne):
if parsed_args.wait:
if utils.wait_for_status(
image_client.images.get,
- image,
+ image_id,
callback=_show_progress,
):
sys.stdout.write('\n')
else:
self.log.error(
- 'Error creating server snapshot: %s' %
+ 'Error creating server snapshot: %s',
parsed_args.image_name,
)
sys.stdout.write('\nError creating server snapshot')
@@ -443,7 +443,7 @@ class CreateServerImage(show.ShowOne):
image = utils.find_resource(
image_client.images,
- image.id,
+ image_id,
)
info = {}
@@ -465,7 +465,7 @@ class DeleteServer(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
server = utils.find_resource(
compute_client.servers, parsed_args.server)
@@ -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,11 +526,11 @@ 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):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
search_opts = {
'reservation_id': parsed_args.reservation_id,
@@ -600,7 +600,7 @@ class LockServer(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
utils.find_resource(
@@ -672,7 +672,7 @@ class MigrateServer(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
@@ -716,7 +716,7 @@ class PauseServer(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
utils.find_resource(
@@ -762,7 +762,7 @@ class RebootServer(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
server = utils.find_resource(
compute_client.servers, parsed_args.server)
@@ -811,7 +811,7 @@ class RebuildServer(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
# Lookup parsed_args.image
@@ -856,7 +856,7 @@ class RemoveServerSecurityGroup(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
@@ -892,7 +892,7 @@ class RemoveServerVolume(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
volume_client = self.app.client_manager.volume
@@ -927,7 +927,7 @@ class RescueServer(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
server = utils.find_resource(
@@ -968,7 +968,7 @@ class ResizeServer(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
server = utils.find_resource(
@@ -1013,7 +1013,7 @@ class ResumeServer(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
utils.find_resource(
@@ -1054,7 +1054,7 @@ class SetServer(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
server = utils.find_resource(
@@ -1102,7 +1102,7 @@ class ShowServer(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
server = utils.find_resource(compute_client.servers,
parsed_args.server)
@@ -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(
@@ -1224,7 +1224,7 @@ class SshServer(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
server = utils.find_resource(
@@ -1272,7 +1272,7 @@ class SshServer(command.Command):
cmd += " -v"
cmd += " %s@%s"
- self.log.debug("ssh command: %s" % (cmd % (login, ip_address)))
+ self.log.debug("ssh command: %s", (cmd % (login, ip_address)))
os.system(cmd % (login, ip_address))
@@ -1291,7 +1291,7 @@ class SuspendServer(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
utils.find_resource(
@@ -1315,7 +1315,7 @@ class UnlockServer(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
utils.find_resource(
@@ -1339,7 +1339,7 @@ class UnpauseServer(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
utils.find_resource(
@@ -1363,7 +1363,7 @@ class UnrescueServer(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
utils.find_resource(
@@ -1395,7 +1395,7 @@ class UnsetServer(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
compute_client = self.app.client_manager.compute
server = utils.find_resource(
compute_client.servers,
diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py
index 5e57e0a..4b2ebac 100644
--- a/openstackclient/compute/v2/service.py
+++ b/openstackclient/compute/v2/service.py
@@ -40,7 +40,7 @@ class ListService(lister.Lister):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
columns = (
"Binary",
@@ -88,7 +88,7 @@ class SetService(lister.Lister):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
columns = (
"Host",
diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py
index f60c210..ed98af2 100644
--- a/openstackclient/compute/v2/usage.py
+++ b/openstackclient/compute/v2/usage.py
@@ -17,6 +17,7 @@
import datetime
import logging
+import sys
from cliff import lister
@@ -34,8 +35,8 @@ class ListUsage(lister.Lister):
"--start",
metavar="<start>",
default=None,
- help="Usage range start date ex 2012-01-20"
- " (default: 4 weeks ago)."
+ help="Usage range start date, ex 2012-01-20"
+ " (default: 4 weeks ago)."
)
parser.add_argument(
"--end",
@@ -46,7 +47,7 @@ class ListUsage(lister.Lister):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
def _format_project(project):
if not project:
@@ -95,8 +96,10 @@ class ListUsage(lister.Lister):
pass
if len(usage_list) > 0:
- print("Usage from %s to %s:" % (start.strftime(dateformat),
- end.strftime(dateformat)))
+ sys.stdout.write("Usage from %s to %s:" % (
+ start.strftime(dateformat),
+ end.strftime(dateformat),
+ ))
return (column_headers,
(utils.get_item_properties(
diff --git a/openstackclient/identity/__init__.py b/openstackclient/identity/__init__.py
index 85ac250..e69de29 100644
--- a/openstackclient/identity/__init__.py
+++ b/openstackclient/identity/__init__.py
@@ -1,14 +0,0 @@
-# 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.
-#
diff --git a/openstackclient/identity/client.py b/openstackclient/identity/client.py
index 305d4cc..b58098e 100644
--- a/openstackclient/identity/client.py
+++ b/openstackclient/identity/client.py
@@ -40,12 +40,21 @@ 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(
username=instance._username,
password=instance._password,
+ user_domain_id=instance._user_domain_id,
+ user_domain_name=instance._user_domain_name,
+ project_domain_id=instance._project_domain_id,
+ project_domain_name=instance._project_domain_name,
+ domain_id=instance._domain_id,
+ domain_name=instance._domain_name,
tenant_name=instance._project_name,
tenant_id=instance._project_id,
auth_url=instance._auth_url,
@@ -64,4 +73,4 @@ class IdentityClientv2_0(identity_client_v2_0.Client):
if name == "projects":
return self.tenants
else:
- raise AttributeError, name
+ raise AttributeError(name)
diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py
new file mode 100644
index 0000000..6aeaa3c
--- /dev/null
+++ b/openstackclient/identity/common.py
@@ -0,0 +1,38 @@
+# Copyright 2012-2013 OpenStack Foundation
+#
+# 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.
+#
+
+"""Common identity code"""
+
+from keystoneclient import exceptions as identity_exc
+from openstackclient.common import exceptions
+from openstackclient.common import utils
+
+
+def find_service(identity_client, name_type_or_id):
+ """Find a service by id, name or type."""
+
+ try:
+ # search for the usual ID or name
+ return utils.find_resource(identity_client.services, name_type_or_id)
+ except exceptions.CommandError:
+ try:
+ # search for service type
+ return identity_client.services.find(type=name_type_or_id)
+ # FIXME(dtroyer): This exception should eventually come from
+ # common client exceptions
+ except identity_exc.NotFound:
+ msg = ("No service with a type, name or ID of '%s' exists."
+ % name_type_or_id)
+ raise exceptions.CommandError(msg)
diff --git a/openstackclient/identity/v2_0/__init__.py b/openstackclient/identity/v2_0/__init__.py
index 85ac250..e69de29 100644
--- a/openstackclient/identity/v2_0/__init__.py
+++ b/openstackclient/identity/v2_0/__init__.py
@@ -1,14 +0,0 @@
-# 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.
-#
diff --git a/openstackclient/identity/v2_0/ec2creds.py b/openstackclient/identity/v2_0/ec2creds.py
index cb3a516..74c9d5e 100644
--- a/openstackclient/identity/v2_0/ec2creds.py
+++ b/openstackclient/identity/v2_0/ec2creds.py
@@ -46,7 +46,7 @@ class CreateEC2Creds(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
if parsed_args.project:
@@ -93,7 +93,7 @@ class DeleteEC2Creds(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
if parsed_args.user:
@@ -123,7 +123,7 @@ class ListEC2Creds(lister.Lister):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
if parsed_args.user:
@@ -135,9 +135,11 @@ class ListEC2Creds(lister.Lister):
# Get the user from the current auth
user = identity_client.auth_user_id
- columns = ('Access', 'Secret', 'Project ID', 'User ID')
+ columns = ('access', 'secret', 'tenant_id', 'user_id')
+ column_headers = ('Access', 'Secret', 'Project ID', 'User ID')
data = identity_client.ec2.list(user)
- return (columns,
+
+ return (column_headers,
(utils.get_item_properties(
s, columns,
formatters={},
@@ -164,7 +166,7 @@ class ShowEC2Creds(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
if parsed_args.user:
diff --git a/openstackclient/identity/v2_0/endpoint.py b/openstackclient/identity/v2_0/endpoint.py
index 5a050fa..36f52ca 100644
--- a/openstackclient/identity/v2_0/endpoint.py
+++ b/openstackclient/identity/v2_0/endpoint.py
@@ -22,9 +22,8 @@ from cliff import command
from cliff import lister
from cliff import show
-from keystoneclient import exceptions as identity_exc
-from openstackclient.common import exceptions
from openstackclient.common import utils
+from openstackclient.identity import common
class CreateEndpoint(show.ShowOne):
@@ -45,6 +44,7 @@ class CreateEndpoint(show.ShowOne):
parser.add_argument(
'--publicurl',
metavar='<public-url>',
+ required=True,
help='New endpoint public URL')
parser.add_argument(
'--adminurl',
@@ -57,10 +57,9 @@ class CreateEndpoint(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
- service = utils.find_resource(identity_client.services,
- parsed_args.service)
+ service = common.find_service(identity_client, parsed_args.service)
endpoint = identity_client.endpoints.create(
parsed_args.region,
service.id,
@@ -89,7 +88,7 @@ class DeleteEndpoint(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
identity_client.endpoints.delete(parsed_args.endpoint)
return
@@ -106,11 +105,11 @@ 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):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
if parsed_args.long:
columns = ('ID', 'Region', 'Service Name', 'Service Type',
@@ -120,8 +119,7 @@ class ListEndpoint(lister.Lister):
data = identity_client.endpoints.list()
for ep in data:
- service = utils.find_resource(
- identity_client.services, ep.service_id)
+ service = common.find_service(identity_client, ep.service_id)
ep.service_name = service.name
ep.service_type = service.type
return (columns,
@@ -139,77 +137,30 @@ class ShowEndpoint(show.ShowOne):
def get_parser(self, prog_name):
parser = super(ShowEndpoint, self).get_parser(prog_name)
parser.add_argument(
- 'service',
- metavar='<service>',
- help='Name or ID of service endpoint to display')
- parser.add_argument(
- '--type',
- metavar='<endpoint-type>',
- default='publicURL',
- help='Endpoint type: publicURL, internalURL, adminURL ' +
- '(default publicURL)')
- parser.add_argument(
- '--attr',
- metavar='<endpoint-attribute>',
- help='Endpoint attribute to use for selection')
- parser.add_argument(
- '--value',
- metavar='<endpoint-value>',
- help='Value of endpoint attribute to use for selection')
- parser.add_argument(
- '--all',
- action='store_true',
- default=False,
- help='Show all endpoints for this service')
+ 'endpoint_or_service',
+ metavar='<endpoint_or_service>',
+ help='Endpoint ID or name, type or ID of service to display')
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
-
- if not parsed_args.all:
- # Find endpoint filtered by a specific attribute or service type
- kwargs = {
- 'service_type': parsed_args.service,
- 'endpoint_type': parsed_args.type,
- }
- if parsed_args.attr and parsed_args.value:
- kwargs.update({
- 'attr': parsed_args.attr,
- 'filter_value': parsed_args.value,
- })
- elif parsed_args.attr or parsed_args.value:
- msg = 'Both --attr and --value required'
- raise exceptions.CommandError(msg)
-
- url = identity_client.service_catalog.url_for(**kwargs)
- info = {'%s.%s' % (parsed_args.service, parsed_args.type): url}
- return zip(*sorted(six.iteritems(info)))
- else:
- # The Identity 2.0 API doesn't support retrieving a single
- # endpoint so we have to do this ourselves
- try:
- service = utils.find_resource(identity_client.services,
- parsed_args.service)
- except exceptions.CommandError:
- try:
- # search for service type
- service = identity_client.services.find(
- type=parsed_args.service)
- # FIXME(dtroyer): This exception should eventually come from
- # common client exceptions
- except identity_exc.NotFound:
- msg = "No service with a type, name or ID of '%s' exists" \
- % parsed_args.service
- raise exceptions.CommandError(msg)
-
- data = identity_client.endpoints.list()
+ data = identity_client.endpoints.list()
+ match = None
+ for ep in data:
+ if ep.id == parsed_args.endpoint_or_service:
+ match = ep
+ service = common.find_service(identity_client, ep.service_id)
+ if match is None:
+ service = common.find_service(identity_client,
+ parsed_args.endpoint_or_service)
for ep in data:
if ep.service_id == service.id:
- info = {}
- info.update(ep._info)
- service = utils.find_resource(identity_client.services,
- ep.service_id)
- info['service_name'] = service.name
- info['service_type'] = service.type
- return zip(*sorted(six.iteritems(info)))
+ match = ep
+ if match is None:
+ return None
+ info = {}
+ info.update(match._info)
+ info['service_name'] = service.name
+ info['service_type'] = service.type
+ return zip(*sorted(six.iteritems(info)))
diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py
index 2d0acb8..7e19d5a 100644
--- a/openstackclient/identity/v2_0/project.py
+++ b/openstackclient/identity/v2_0/project.py
@@ -22,6 +22,7 @@ from cliff import command
from cliff import lister
from cliff import show
+from openstackclient.common import parseractions
from openstackclient.common import utils
@@ -53,20 +54,31 @@ class CreateProject(show.ShowOne):
action='store_true',
help='Disable project',
)
+ parser.add_argument(
+ '--property',
+ metavar='<key=value>',
+ action=parseractions.KeyValueAction,
+ help='Property to add for this project '
+ '(repeat option to set multiple properties)',
+ )
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
enabled = True
if parsed_args.disable:
enabled = False
+ kwargs = {}
+ if parsed_args.property:
+ kwargs = parsed_args.property.copy()
project = identity_client.tenants.create(
parsed_args.name,
description=parsed_args.description,
enabled=enabled,
+ **kwargs
)
info = {}
@@ -89,7 +101,7 @@ class DeleteProject(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
project = utils.find_resource(
@@ -117,7 +129,7 @@ class ListProject(lister.Lister):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
if parsed_args.long:
columns = ('ID', 'Name', 'Description', 'Enabled')
else:
@@ -163,15 +175,23 @@ class SetProject(command.Command):
action='store_true',
help='Disable project',
)
+ parser.add_argument(
+ '--property',
+ metavar='<key=value>',
+ action=parseractions.KeyValueAction,
+ help='Property to add for this project '
+ '(repeat option to set multiple properties)',
+ )
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
if (not parsed_args.name
and not parsed_args.description
and not parsed_args.enable
+ and not parsed_args.property
and not parsed_args.disable):
return
@@ -189,10 +209,12 @@ class SetProject(command.Command):
kwargs['enabled'] = True
if parsed_args.disable:
kwargs['enabled'] = False
+ if parsed_args.property:
+ kwargs.update(parsed_args.property)
if 'id' in kwargs:
del kwargs['id']
if 'name' in kwargs:
- # Hack around borken Identity API arg names
+ # Hack around broken Identity API arg names
kwargs['tenant_name'] = kwargs['name']
del kwargs['name']
@@ -214,7 +236,7 @@ class ShowProject(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
project = utils.find_resource(
identity_client.tenants,
diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py
index fdf2110..faf48ed 100644
--- a/openstackclient/identity/v2_0/role.py
+++ b/openstackclient/identity/v2_0/role.py
@@ -51,7 +51,7 @@ class AddRole(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
role = utils.find_resource(identity_client.roles, parsed_args.role)
project = utils.find_resource(
@@ -84,7 +84,7 @@ class CreateRole(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
role = identity_client.roles.create(parsed_args.role_name)
@@ -108,7 +108,7 @@ class DeleteRole(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
role = utils.find_resource(
@@ -126,7 +126,7 @@ class ListRole(lister.Lister):
log = logging.getLogger(__name__ + '.ListRole')
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
columns = ('ID', 'Name')
data = self.app.client_manager.identity.roles.list()
return (columns,
@@ -156,7 +156,7 @@ class ListUserRole(lister.Lister):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
auth_ref = self.app.client_manager.auth_ref
@@ -228,7 +228,7 @@ class RemoveRole(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
role = utils.find_resource(identity_client.roles, parsed_args.role)
project = utils.find_resource(
@@ -256,7 +256,7 @@ class ShowRole(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
role = utils.find_resource(identity_client.roles, parsed_args.role)
diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py
index 92d1e09..138ed3b 100644
--- a/openstackclient/identity/v2_0/service.py
+++ b/openstackclient/identity/v2_0/service.py
@@ -22,9 +22,9 @@ from cliff import command
from cliff import lister
from cliff import show
-from keystoneclient import exceptions as identity_exc
from openstackclient.common import exceptions
from openstackclient.common import utils
+from openstackclient.identity import common
class CreateService(show.ShowOne):
@@ -53,7 +53,7 @@ class CreateService(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
service = identity_client.services.create(
@@ -81,14 +81,9 @@ class DeleteService(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
-
- service = utils.find_resource(
- identity_client.services,
- parsed_args.service,
- )
-
+ service = common.find_service(identity_client, parsed_args.service)
identity_client.services.delete(service.id)
return
@@ -104,11 +99,11 @@ 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):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
if parsed_args.long:
columns = ('ID', 'Name', 'Type', 'Description')
@@ -143,7 +138,7 @@ class ShowService(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
if parsed_args.catalog:
@@ -159,24 +154,7 @@ class ShowService(show.ShowOne):
"exists." % (parsed_args.service))
raise exceptions.CommandError(msg)
else:
- try:
- # search for the usual ID or name
- service = utils.find_resource(
- identity_client.services,
- parsed_args.service,
- )
- except exceptions.CommandError:
- try:
- # search for service type
- service = identity_client.services.find(
- type=parsed_args.service)
- # FIXME(dtroyer): This exception should eventually come from
- # common client exceptions
- except identity_exc.NotFound:
- msg = ("No service with a type, name or ID of '%s' exists."
- % parsed_args.service)
- raise exceptions.CommandError(msg)
-
+ service = common.find_service(identity_client, parsed_args.service)
info = {}
info.update(service._info)
return zip(*sorted(six.iteritems(info)))
diff --git a/openstackclient/identity/v2_0/token.py b/openstackclient/identity/v2_0/token.py
new file mode 100644
index 0000000..fe1557a
--- /dev/null
+++ b/openstackclient/identity/v2_0/token.py
@@ -0,0 +1,60 @@
+# Copyright 2014 eBay Inc.
+#
+# 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.
+#
+
+"""Identity v2 Token action implementations"""
+
+import logging
+import six
+
+from cliff import command
+from cliff import show
+
+
+class CreateToken(show.ShowOne):
+ """Issue token command"""
+
+ log = logging.getLogger(__name__ + '.CreateToken')
+
+ def get_parser(self, prog_name):
+ parser = super(CreateToken, self).get_parser(prog_name)
+ return parser
+
+ def take_action(self, parsed_args):
+ self.log.debug('take_action(%s)', parsed_args)
+ identity_client = self.app.client_manager.identity
+ token = identity_client.service_catalog.get_token()
+ token['project_id'] = token.pop('tenant_id')
+ return zip(*sorted(six.iteritems(token)))
+
+
+class DeleteToken(command.Command):
+ """Revoke token command"""
+
+ log = logging.getLogger(__name__ + '.DeleteToken')
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteToken, self).get_parser(prog_name)
+ parser.add_argument(
+ 'token',
+ metavar='<token>',
+ help='Token to be deleted',
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ self.log.debug('take_action(%s)' % parsed_args)
+ identity_client = self.app.client_manager.identity
+ identity_client.tokens.delete(parsed_args.token)
+ return
diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py
index 371c45a..60af6dd 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',
@@ -66,7 +72,7 @@ class CreateUser(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
if parsed_args.project:
@@ -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,
@@ -115,7 +123,7 @@ class DeleteUser(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
user = utils.find_resource(
@@ -143,11 +151,12 @@ 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):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
+ identity_client = self.app.client_manager.identity
def _format_project(project):
if not project:
@@ -157,6 +166,14 @@ class ListUser(lister.Lister):
else:
return project
+ project = None
+ if parsed_args.project:
+ project = utils.find_resource(
+ identity_client.tenants,
+ parsed_args.project,
+ )
+ project = project.id
+
if parsed_args.long:
columns = (
'ID',
@@ -175,14 +192,20 @@ class ListUser(lister.Lister):
# Cache the project list
project_cache = {}
try:
- for p in self.app.client_manager.identity.tenants.list():
+ for p in identity_client.tenants.list():
project_cache[p.id] = p
except Exception:
# Just forget it if there's any trouble
pass
else:
columns = column_headers = ('ID', 'Name')
- data = self.app.client_manager.identity.users.list()
+ data = identity_client.users.list(tenant_id=project)
+
+ if parsed_args.project:
+ d = {}
+ for s in data:
+ d[s.id] = s
+ data = d.values()
if parsed_args.long:
# FIXME(dtroyer): Sometimes user objects have 'tenant_id' instead
@@ -225,6 +248,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',
@@ -248,9 +277,12 @@ class SetUser(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ 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
@@ -311,7 +343,7 @@ class ShowUser(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
user = utils.find_resource(
diff --git a/openstackclient/identity/v3/__init__.py b/openstackclient/identity/v3/__init__.py
index 85ac250..e69de29 100644
--- a/openstackclient/identity/v3/__init__.py
+++ b/openstackclient/identity/v3/__init__.py
@@ -1,14 +0,0 @@
-# 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.
-#
diff --git a/openstackclient/identity/v3/consumer.py b/openstackclient/identity/v3/consumer.py
index ddeae61..7f54603 100644
--- a/openstackclient/identity/v3/consumer.py
+++ b/openstackclient/identity/v3/consumer.py
@@ -41,9 +41,9 @@ class CreateConsumer(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
- consumer = identity_client.consumers.create_consumer(
+ consumer = identity_client.oauth1.consumers.create(
parsed_args.description
)
info = {}
@@ -66,11 +66,11 @@ class DeleteConsumer(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
consumer = utils.find_resource(
- identity_client.consumers, parsed_args.consumer)
- identity_client.consumers.delete_consumer(consumer.id)
+ identity_client.oauth1.consumers, parsed_args.consumer)
+ identity_client.oauth1.consumers.delete(consumer.id)
return
@@ -80,9 +80,9 @@ class ListConsumer(lister.Lister):
log = logging.getLogger(__name__ + '.ListConsumer')
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
columns = ('ID', 'Description')
- data = self.app.client_manager.identity.consumers.list_consumers()
+ data = self.app.client_manager.identity.oauth1.consumers.list()
return (columns,
(utils.get_item_properties(
s, columns,
@@ -110,10 +110,10 @@ class SetConsumer(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
consumer = utils.find_resource(
- identity_client.consumers, parsed_args.consumer)
+ identity_client.oauth1.consumers, parsed_args.consumer)
kwargs = {}
if parsed_args.description:
kwargs['description'] = parsed_args.description
@@ -122,14 +122,9 @@ class SetConsumer(command.Command):
sys.stdout.write("Consumer not updated, no arguments present")
return
- consumer = identity_client.consumers.update_consumer(
- consumer.id,
- **kwargs
- )
-
- info = {}
- info.update(consumer._info)
- return zip(*sorted(six.iteritems(info)))
+ consumer = identity_client.oauth1.consumers.update(
+ consumer.id, **kwargs)
+ return
class ShowConsumer(show.ShowOne):
@@ -147,10 +142,10 @@ class ShowConsumer(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
consumer = utils.find_resource(
- identity_client.consumers, parsed_args.consumer)
+ identity_client.oauth1.consumers, parsed_args.consumer)
info = {}
info.update(consumer._info)
diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py
index b82825f..43d16c2 100644
--- a/openstackclient/identity/v3/credential.py
+++ b/openstackclient/identity/v3/credential.py
@@ -58,7 +58,7 @@ class CreateCredential(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
user_id = utils.find_resource(identity_client.users,
parsed_args.user).id
@@ -68,9 +68,9 @@ class CreateCredential(show.ShowOne):
else:
project = None
credential = identity_client.credentials.create(
- user_id,
- parsed_args.type,
- parsed_args.data,
+ user=user_id,
+ type=parsed_args.type,
+ blob=parsed_args.data,
project=project)
return zip(*sorted(six.iteritems(credential._info)))
@@ -91,7 +91,7 @@ class DeleteCredential(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
identity_client.credentials.delete(parsed_args.credential)
return
@@ -103,7 +103,7 @@ class ListCredential(lister.Lister):
log = logging.getLogger(__name__ + '.ListCredential')
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
columns = ('ID', 'Type', 'User ID', 'Data', 'Project ID')
data = self.app.client_manager.identity.credentials.list()
return (columns,
@@ -149,7 +149,7 @@ class SetCredential(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
user_id = utils.find_resource(identity_client.users,
parsed_args.user).id
@@ -187,7 +187,7 @@ class ShowCredential(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
credential = utils.find_resource(identity_client.credentials,
parsed_args.credential)
diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py
index 1e9a4a2..f976384 100644
--- a/openstackclient/identity/v3/domain.py
+++ b/openstackclient/identity/v3/domain.py
@@ -58,11 +58,11 @@ class CreateDomain(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
domain = identity_client.domains.create(
- parsed_args.name,
- parsed_args.description,
+ name=parsed_args.name,
+ description=parsed_args.description,
enabled=parsed_args.enabled,
)
@@ -84,7 +84,7 @@ class DeleteDomain(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
domain = utils.find_resource(identity_client.domains,
parsed_args.domain)
@@ -98,7 +98,7 @@ class ListDomain(lister.Lister):
log = logging.getLogger(__name__ + '.ListDomain')
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
columns = ('ID', 'Name', 'Enabled', 'Description')
data = self.app.client_manager.identity.domains.list()
return (columns,
@@ -147,7 +147,7 @@ class SetDomain(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
domain = utils.find_resource(identity_client.domains,
parsed_args.domain)
@@ -181,7 +181,7 @@ class ShowDomain(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
domain = utils.find_resource(identity_client.domains,
parsed_args.domain)
diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py
index 43da07a..93d77be 100644
--- a/openstackclient/identity/v3/endpoint.py
+++ b/openstackclient/identity/v3/endpoint.py
@@ -24,6 +24,7 @@ from cliff import lister
from cliff import show
from openstackclient.common import utils
+from openstackclient.identity import common
class CreateEndpoint(show.ShowOne):
@@ -67,17 +68,16 @@ class CreateEndpoint(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
- service = utils.find_resource(identity_client.services,
- parsed_args.service)
+ service = common.find_service(identity_client, parsed_args.service)
endpoint = identity_client.endpoints.create(
- service.id,
- parsed_args.url,
- parsed_args.interface,
- parsed_args.region,
- parsed_args.enabled
+ service=service.id,
+ url=parsed_args.url,
+ interface=parsed_args.interface,
+ region=parsed_args.region,
+ enabled=parsed_args.enabled
)
info = {}
@@ -101,7 +101,7 @@ class DeleteEndpoint(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
endpoint_id = utils.find_resource(identity_client.endpoints,
parsed_args.endpoint).id
@@ -114,28 +114,15 @@ class ListEndpoint(lister.Lister):
log = logging.getLogger(__name__ + '.ListEndpoint')
- def get_parser(self, prog_name):
- parser = super(ListEndpoint, self).get_parser(prog_name)
- parser.add_argument(
- '--long',
- action='store_true',
- default=False,
- help='Additional fields are listed in output')
- return parser
-
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
- if parsed_args.long:
- columns = ('ID', 'Region', 'Service Name', 'Service Type',
- 'Enabled', 'Interface', 'URL')
- else:
- columns = ('ID', 'Region', 'Service Name', 'Enabled')
+ columns = ('ID', 'Region', 'Service Name', 'Service Type',
+ 'Enabled', 'Interface', 'URL')
data = identity_client.endpoints.list()
for ep in data:
- service = utils.find_resource(
- identity_client.services, ep.service_id)
+ service = common.find_service(identity_client, ep.service_id)
ep.service_name = service.name
ep.service_type = service.type
return (columns,
@@ -190,12 +177,11 @@ class SetEndpoint(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
endpoint = utils.find_resource(identity_client.endpoints,
parsed_args.endpoint)
- service = utils.find_resource(identity_client.services,
- parsed_args.service)
+ service = common.find_service(identity_client, parsed_args.service)
if (not parsed_args.interface and not parsed_args.url
and not parsed_args.service and not parsed_args.region):
@@ -204,11 +190,11 @@ class SetEndpoint(command.Command):
identity_client.endpoints.update(
endpoint.id,
- service.id,
- parsed_args.url,
- parsed_args.interface,
- parsed_args.region,
- parsed_args.enabled
+ service=service.id,
+ url=parsed_args.url,
+ interface=parsed_args.interface,
+ region=parsed_args.region,
+ enabled=parsed_args.enabled
)
return
@@ -228,13 +214,12 @@ class ShowEndpoint(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
endpoint = utils.find_resource(identity_client.endpoints,
parsed_args.endpoint)
- service = utils.find_resource(identity_client.services,
- endpoint.service_id)
+ service = common.find_service(identity_client, endpoint.service_id)
info = {}
info.update(endpoint._info)
diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py
index b5d24ef..4eb1448 100644
--- a/openstackclient/identity/v3/group.py
+++ b/openstackclient/identity/v3/group.py
@@ -46,7 +46,7 @@ class AddUserToGroup(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
user_id = utils.find_resource(identity_client.users,
@@ -84,7 +84,7 @@ class CheckUserInGroup(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
user_id = utils.find_resource(identity_client.users,
@@ -125,7 +125,7 @@ class CreateGroup(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
if parsed_args.domain:
domain = utils.find_resource(identity_client.domains,
@@ -133,7 +133,7 @@ class CreateGroup(show.ShowOne):
else:
domain = None
group = identity_client.groups.create(
- parsed_args.name,
+ name=parsed_args.name,
domain=domain,
description=parsed_args.description)
@@ -156,7 +156,7 @@ class DeleteGroup(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
group = utils.find_resource(identity_client.groups, parsed_args.group)
identity_client.groups.delete(group.id)
@@ -164,112 +164,67 @@ class DeleteGroup(command.Command):
class ListGroup(lister.Lister):
- """List groups and optionally roles assigned to groups"""
+ """List groups"""
log = logging.getLogger(__name__ + '.ListGroup')
def get_parser(self, prog_name):
parser = super(ListGroup, self).get_parser(prog_name)
parser.add_argument(
- 'group',
- metavar='<group>',
- nargs='?',
- help='Name or ID of group to list [required with --role]',
- )
- parser.add_argument(
- '--role',
- action='store_true',
- default=False,
- help='List the roles assigned to <group>',
- )
- domain_or_project = parser.add_mutually_exclusive_group()
- domain_or_project.add_argument(
'--domain',
metavar='<domain>',
- help='Filter list by <domain> [Only valid with --role]',
+ help='Filter group list by <domain>',
)
- domain_or_project.add_argument(
- '--project',
- metavar='<project>',
- help='Filter list by <project> [Only valid with --role]',
+ parser.add_argument(
+ '--user',
+ metavar='<user>',
+ help='List group memberships for <user> (name or ID)',
)
parser.add_argument(
'--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):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
- if parsed_args.role:
- # List roles belonging to group
-
- # Group is required here, bail if it is not supplied
- if not parsed_args.group:
- sys.stderr.write('Error: Group must be specified')
- # TODO(dtroyer): This lists the commands...I want it to
- # show the help for _this_ command.
- self.app.DeferredHelpAction(
- self.app.parser,
- self.app.parser,
- None,
- None,
- )
- return ([], [])
-
- group = utils.find_resource(
- identity_client.groups,
- parsed_args.group,
- )
-
- if parsed_args.domain:
- columns = ('ID', 'Name', 'Domain', 'Group')
- domain = utils.find_resource(
- identity_client.domains,
- parsed_args.domain,
- )
- data = identity_client.roles.list(
- group=group,
- domain=domain,
- )
- for group_role in data:
- group_role.group = group.name
- group_role.domain = domain.name
- elif parsed_args.project:
- columns = ('ID', 'Name', 'Project', 'Group')
- project = utils.find_resource(
- identity_client.projects,
- parsed_args.project,
- )
- data = identity_client.roles.list(
- group=group,
- project=project,
- )
- for group_role in data:
- group_role.group = group.name
- group_role.project = project.name
- else:
- # TODO(dtroyer): raise exception here, this really is an error
- sys.stderr.write("Error: Must specify --domain or --project "
- "with --role\n")
- return ([], [])
+ if parsed_args.domain:
+ domain = utils.find_resource(
+ identity_client.domains,
+ parsed_args.domain,
+ ).id
else:
- # List groups
- if parsed_args.long:
- columns = ('ID', 'Name', 'Domain ID', 'Description')
- else:
- columns = ('ID', 'Name')
- data = identity_client.groups.list()
+ domain = None
- return (columns,
- (utils.get_item_properties(
- s, columns,
- formatters={},
- ) for s in data))
+ if parsed_args.user:
+ user = utils.find_resource(
+ identity_client.users,
+ parsed_args.user,
+ ).id
+ else:
+ user = None
+
+ # List groups
+ if parsed_args.long:
+ columns = ('ID', 'Name', 'Domain ID', 'Description')
+ else:
+ columns = ('ID', 'Name')
+ data = identity_client.groups.list(
+ domain=domain,
+ user=user,
+ )
+
+ return (
+ columns,
+ (utils.get_item_properties(
+ s, columns,
+ formatters={},
+ ) for s in data)
+ )
class RemoveUserFromGroup(command.Command):
@@ -292,7 +247,7 @@ class RemoveUserFromGroup(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
user_id = utils.find_resource(identity_client.users,
@@ -336,7 +291,7 @@ class SetGroup(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
group = utils.find_resource(identity_client.groups, parsed_args.group)
kwargs = {}
@@ -370,7 +325,7 @@ class ShowGroup(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
group = utils.find_resource(identity_client.groups, parsed_args.group)
diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py
new file mode 100644
index 0000000..b60678b
--- /dev/null
+++ b/openstackclient/identity/v3/identity_provider.py
@@ -0,0 +1,180 @@
+# 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.
+#
+
+"""Identity v3 IdentityProvider action implementations"""
+
+import logging
+import six
+import sys
+
+from cliff import command
+from cliff import lister
+from cliff import show
+
+from openstackclient.common import utils
+
+
+class CreateIdentityProvider(show.ShowOne):
+ """Create identity_provider command"""
+
+ log = logging.getLogger(__name__ + '.CreateIdentityProvider')
+
+ def get_parser(self, prog_name):
+ parser = super(CreateIdentityProvider, self).get_parser(prog_name)
+ parser.add_argument(
+ 'identity_provider_id',
+ metavar='<identity_provider_id>',
+ help='New identity provider ID (must be unique)'
+ )
+ parser.add_argument(
+ '--description',
+ metavar='<description>',
+ help='New identity provider description',
+ )
+
+ enable_identity_provider = parser.add_mutually_exclusive_group()
+ enable_identity_provider.add_argument(
+ '--enable',
+ dest='enabled',
+ action='store_true',
+ default=True,
+ help='Enable identity provider',
+ )
+ enable_identity_provider.add_argument(
+ '--disable',
+ dest='enabled',
+ action='store_false',
+ help='Disable the identity provider',
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ self.log.debug('take_action(%s)', parsed_args)
+ identity_client = self.app.client_manager.identity
+ idp = identity_client.identity_providers.create(
+ parsed_args.identity_provider_id,
+ description=parsed_args.description,
+ enabled=parsed_args.enabled)
+ info = {}
+ info.update(idp._info)
+ return zip(*sorted(six.iteritems(info)))
+
+
+class DeleteIdentityProvider(command.Command):
+ """Delete identity provider"""
+
+ log = logging.getLogger(__name__ + '.DeleteIdentityProvider')
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteIdentityProvider, self).get_parser(prog_name)
+ parser.add_argument(
+ 'identity_provider',
+ metavar='<identity_provider>',
+ help='ID of the identity provider to be deleted',
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ self.log.debug('take_action(%s)', parsed_args)
+ identity_client = self.app.client_manager.identity
+ identity_client.identity_providers.delete(
+ parsed_args.identity_provider)
+ return
+
+
+class ListIdentityProvider(lister.Lister):
+ """List identity providers"""
+
+ log = logging.getLogger(__name__ + '.ListIdentityProvider')
+
+ def take_action(self, parsed_args):
+ self.log.debug('take_action(%s)', parsed_args)
+ columns = ('ID', 'Enabled', 'Description')
+ data = self.app.client_manager.identity.identity_providers.list()
+ return (columns,
+ (utils.get_item_properties(
+ s, columns,
+ formatters={},
+ ) for s in data))
+
+
+class SetIdentityProvider(command.Command):
+ """Set identity provider"""
+
+ log = logging.getLogger(__name__ + '.SetIdentityProvider')
+
+ def get_parser(self, prog_name):
+ parser = super(SetIdentityProvider, self).get_parser(prog_name)
+ parser.add_argument(
+ 'identity_provider',
+ metavar='<identity_provider>',
+ help='ID of the identity provider to be changed',
+ )
+
+ enable_identity_provider = parser.add_mutually_exclusive_group()
+ enable_identity_provider.add_argument(
+ '--enable',
+ action='store_true',
+ help='Enable the identity provider',
+ )
+ enable_identity_provider.add_argument(
+ '--disable',
+ action='store_true',
+ help='Disable the identity provider',
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ self.log.debug('take_action(%s)', parsed_args)
+ identity_client = self.app.client_manager.identity
+
+ if parsed_args.enable is True:
+ enabled = True
+ elif parsed_args.disable is True:
+ enabled = False
+ else:
+ sys.stdout.write("Identity Provider not updated, "
+ "no arguments present")
+ return (None, None)
+
+ identity_provider = identity_client.identity_providers.update(
+ parsed_args.identity_provider, enabled=enabled)
+ info = {}
+ info.update(identity_provider._info)
+ return zip(*sorted(six.iteritems(info)))
+
+
+class ShowIdentityProvider(show.ShowOne):
+ """Show identity provider"""
+
+ log = logging.getLogger(__name__ + '.ShowIdentityProvider')
+
+ def get_parser(self, prog_name):
+ parser = super(ShowIdentityProvider, self).get_parser(prog_name)
+ parser.add_argument(
+ 'identity_provider',
+ metavar='<identity_provider>',
+ help='ID of the identity provider to be displayed',
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ self.log.debug('take_action(%s)', parsed_args)
+ identity_client = self.app.client_manager.identity
+ identity_provider = utils.find_resource(
+ identity_client.identity_providers,
+ parsed_args.identity_provider)
+
+ info = {}
+ info.update(identity_provider._info)
+ return zip(*sorted(six.iteritems(info)))
diff --git a/openstackclient/identity/v3/policy.py b/openstackclient/identity/v3/policy.py
index cdbb1cf..87f3cbe 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',
@@ -47,12 +47,12 @@ class CreatePolicy(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
- blob = _read_blob_file_contents(parsed_args.blob_file)
+ self.log.debug('take_action(%s)', parsed_args)
+ blob = utils.read_blob_file_contents(parsed_args.blob_file)
identity_client = self.app.client_manager.identity
policy = identity_client.policies.create(
- blob, type=parsed_args.type
+ blob=blob, type=parsed_args.type
)
return zip(*sorted(six.iteritems(policy._info)))
@@ -73,7 +73,7 @@ class DeletePolicy(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
identity_client.policies.delete(parsed_args.policy)
return
@@ -95,7 +95,7 @@ class ListPolicy(lister.Lister):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
if parsed_args.include_blob:
columns = ('ID', 'Type', 'Blob')
else:
@@ -133,12 +133,12 @@ class SetPolicy(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
blob = None
if parsed_args.blob_file:
- blob = _read_blob_file_contents(parsed_args.blob_file)
+ blob = utils.read_blob_file_contents(parsed_args.blob_file)
kwargs = {}
if blob:
@@ -168,15 +168,9 @@ class ShowPolicy(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
policy = utils.find_resource(identity_client.policies,
parsed_args.policy)
return zip(*sorted(six.iteritems(policy._info)))
-
-
-def _read_blob_file_contents(blob_file):
- with open(blob_file) as file:
- blob = file.read().strip()
- return blob
diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py
index f245a88..00a98d1 100644
--- a/openstackclient/identity/v3/project.py
+++ b/openstackclient/identity/v3/project.py
@@ -22,6 +22,7 @@ from cliff import command
from cliff import lister
from cliff import show
+from openstackclient.common import parseractions
from openstackclient.common import utils
@@ -58,10 +59,17 @@ class CreateProject(show.ShowOne):
action='store_true',
help='Disable project',
)
+ parser.add_argument(
+ '--property',
+ metavar='<key=value>',
+ action=parseractions.KeyValueAction,
+ help='Property to add for this project '
+ '(repeat option to set multiple properties)',
+ )
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
if parsed_args.domain:
@@ -75,12 +83,16 @@ class CreateProject(show.ShowOne):
enabled = True
if parsed_args.disable:
enabled = False
+ kwargs = {}
+ if parsed_args.property:
+ kwargs = parsed_args.property.copy()
project = identity_client.projects.create(
- parsed_args.name,
- domain,
+ name=parsed_args.name,
+ domain=domain,
description=parsed_args.description,
enabled=enabled,
+ **kwargs
)
info = {}
@@ -103,7 +115,7 @@ class DeleteProject(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
project = utils.find_resource(
@@ -128,15 +140,27 @@ class ListProject(lister.Lister):
default=False,
help='List additional fields in output',
)
+ parser.add_argument(
+ '--domain',
+ metavar='<project-domain>',
+ help='Filter by a specific domain',
+ )
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
+ identity_client = self.app.client_manager.identity
if parsed_args.long:
columns = ('ID', 'Name', 'Domain ID', 'Description', 'Enabled')
else:
columns = ('ID', 'Name')
- data = self.app.client_manager.identity.projects.list()
+ kwargs = {}
+ if parsed_args.domain:
+ kwargs['domain'] = utils.find_resource(
+ identity_client.domains,
+ parsed_args.domain,
+ ).id
+ data = identity_client.projects.list(**kwargs)
return (columns,
(utils.get_item_properties(
s, columns,
@@ -182,16 +206,24 @@ class SetProject(command.Command):
action='store_true',
help='Disable project',
)
+ parser.add_argument(
+ '--property',
+ metavar='<key=value>',
+ action=parseractions.KeyValueAction,
+ help='Property to add for this project '
+ '(repeat option to set multiple properties)',
+ )
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
if (not parsed_args.name
and not parsed_args.description
and not parsed_args.domain
and not parsed_args.enable
+ and not parsed_args.property
and not parsed_args.disable):
return
@@ -214,10 +246,12 @@ class SetProject(command.Command):
kwargs['enabled'] = True
if parsed_args.disable:
kwargs['enabled'] = False
+ if parsed_args.property:
+ kwargs.update(parsed_args.property)
if 'id' in kwargs:
del kwargs['id']
if 'domain_id' in kwargs:
- # Hack around borken Identity API arg names
+ # Hack around broken Identity API arg names
kwargs.update(
{'domain': kwargs.pop('domain_id')}
)
@@ -240,7 +274,7 @@ class ShowProject(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
project = utils.find_resource(identity_client.projects,
parsed_args.project)
diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py
index 05bdbbf..2aabc00 100644
--- a/openstackclient/identity/v3/role.py
+++ b/openstackclient/identity/v3/role.py
@@ -63,7 +63,7 @@ class AddRole(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
if (not parsed_args.user and not parsed_args.domain
@@ -152,10 +152,10 @@ class CreateRole(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
- role = identity_client.roles.create(parsed_args.name)
+ role = identity_client.roles.create(name=parsed_args.name)
return zip(*sorted(six.iteritems(role._info)))
@@ -175,7 +175,7 @@ class DeleteRole(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
role = utils.find_resource(
@@ -192,10 +192,110 @@ class ListRole(lister.Lister):
log = logging.getLogger(__name__ + '.ListRole')
+ def get_parser(self, prog_name):
+ parser = super(ListRole, self).get_parser(prog_name)
+ domain_or_project = parser.add_mutually_exclusive_group()
+ domain_or_project.add_argument(
+ '--domain',
+ metavar='<domain>',
+ help='Filter role list by <domain>',
+ )
+ domain_or_project.add_argument(
+ '--project',
+ metavar='<project>',
+ help='Filter role list by <project>',
+ )
+ user_or_group = parser.add_mutually_exclusive_group()
+ user_or_group.add_argument(
+ '--user',
+ metavar='<user>',
+ help='Name or ID of user to list roles asssigned to',
+ )
+ user_or_group.add_argument(
+ '--group',
+ metavar='<group>',
+ help='Name or ID of group to list roles asssigned to',
+ )
+ return parser
+
def take_action(self, parsed_args):
self.log.debug('take_action(%s)' % parsed_args)
- columns = ('ID', 'Name')
- data = self.app.client_manager.identity.roles.list()
+ identity_client = self.app.client_manager.identity
+
+ if parsed_args.user:
+ user = utils.find_resource(
+ identity_client.users,
+ parsed_args.user,
+ )
+ elif parsed_args.group:
+ group = utils.find_resource(
+ identity_client.groups,
+ parsed_args.group,
+ )
+
+ if parsed_args.domain:
+ domain = utils.find_resource(
+ identity_client.domains,
+ parsed_args.domain,
+ )
+ elif parsed_args.project:
+ project = utils.find_resource(
+ identity_client.projects,
+ parsed_args.project,
+ )
+
+ # no user or group specified, list all roles in the system
+ if not parsed_args.user and not parsed_args.group:
+ columns = ('ID', 'Name')
+ data = identity_client.roles.list()
+ elif parsed_args.user and parsed_args.domain:
+ columns = ('ID', 'Name', 'Domain', 'User')
+ data = identity_client.roles.list(
+ user=user,
+ domain=domain,
+ )
+ for user_role in data:
+ user_role.user = user.name
+ user_role.domain = domain.name
+ elif parsed_args.user and parsed_args.project:
+ columns = ('ID', 'Name', 'Project', 'User')
+ data = identity_client.roles.list(
+ user=user,
+ project=project,
+ )
+ for user_role in data:
+ user_role.user = user.name
+ user_role.project = project.name
+ elif parsed_args.user:
+ columns = ('ID', 'Name')
+ data = identity_client.roles.list(
+ user=user,
+ domain='default',
+ )
+ elif parsed_args.group and parsed_args.domain:
+ columns = ('ID', 'Name', 'Domain', 'Group')
+ data = identity_client.roles.list(
+ group=group,
+ domain=domain,
+ )
+ for group_role in data:
+ group_role.group = group.name
+ group_role.domain = domain.name
+ elif parsed_args.group and parsed_args.project:
+ columns = ('ID', 'Name', 'Project', 'Group')
+ data = identity_client.roles.list(
+ group=group,
+ project=project,
+ )
+ for group_role in data:
+ group_role.group = group.name
+ group_role.project = project.name
+ else:
+ sys.stderr.write("Error: If a user or group is specified, either "
+ "--domain or --project must also be specified to "
+ "list role grants.\n")
+ return ([], [])
+
return (columns,
(utils.get_item_properties(
s, columns,
@@ -240,7 +340,7 @@ class RemoveRole(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
if (not parsed_args.user and not parsed_args.domain
@@ -334,7 +434,7 @@ class SetRole(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
if not parsed_args.name:
@@ -364,7 +464,7 @@ class ShowRole(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
role = utils.find_resource(
diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py
new file mode 100644
index 0000000..5cc97e8
--- /dev/null
+++ b/openstackclient/identity/v3/role_assignment.py
@@ -0,0 +1,156 @@
+# 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.
+#
+
+"""Identity v3 Assignment action implementations """
+
+import logging
+
+from cliff import lister
+
+from openstackclient.common import utils
+
+
+class ListRoleAssignment(lister.Lister):
+ """Lists role assignments according to the given filters"""
+
+ log = logging.getLogger(__name__ + '.ListRoleAssignment')
+
+ def get_parser(self, prog_name):
+ parser = super(ListRoleAssignment, self).get_parser(prog_name)
+ parser.add_argument(
+ '--effective',
+ action="store_true",
+ default=False,
+ help='Returns only effective role assignments',
+ )
+ parser.add_argument(
+ '--role',
+ metavar='<role>',
+ help='Name or ID of role to filter',
+ )
+ user_or_group = parser.add_mutually_exclusive_group()
+ user_or_group.add_argument(
+ '--user',
+ metavar='<user>',
+ help='Name or ID of user to filter',
+ )
+ user_or_group.add_argument(
+ '--group',
+ metavar='<group>',
+ help='Name or ID of group to filter',
+ )
+ domain_or_project = parser.add_mutually_exclusive_group()
+ domain_or_project.add_argument(
+ '--domain',
+ metavar='<domain>',
+ help='Name or ID of domain to filter',
+ )
+ domain_or_project.add_argument(
+ '--project',
+ metavar='<project>',
+ help='Name or ID of project to filter',
+ )
+
+ return parser
+
+ def _as_tuple(self, assignment):
+ return (assignment.role, assignment.user, assignment.group,
+ assignment.project, assignment.domain)
+
+ def take_action(self, parsed_args):
+ self.log.debug('take_action(%s)' % parsed_args)
+ identity_client = self.app.client_manager.identity
+
+ role = None
+ if parsed_args.role:
+ role = utils.find_resource(
+ identity_client.roles,
+ parsed_args.role,
+ )
+
+ user = None
+ if parsed_args.user:
+ user = utils.find_resource(
+ identity_client.users,
+ parsed_args.user,
+ )
+
+ domain = None
+ if parsed_args.domain:
+ domain = utils.find_resource(
+ identity_client.domains,
+ parsed_args.domain,
+ )
+
+ project = None
+ if parsed_args.project:
+ project = utils.find_resource(
+ identity_client.projects,
+ parsed_args.project,
+ )
+
+ group = None
+ if parsed_args.group:
+ group = utils.find_resource(
+ identity_client.groups,
+ parsed_args.group,
+ )
+
+ effective = True if parsed_args.effective else False
+ self.log.debug('take_action(%s)' % parsed_args)
+ columns = ('Role', 'User', 'Group', 'Project', 'Domain')
+ data = identity_client.role_assignments.list(
+ domain=domain,
+ user=user,
+ group=group,
+ project=project,
+ role=role,
+ effective=effective)
+
+ data_parsed = []
+ for assignment in data:
+ # Removing the extra "scope" layer in the assignment json
+ scope = assignment.scope
+ if 'project' in scope:
+ setattr(assignment, 'project', scope['project']['id'])
+ assignment.domain = ''
+ elif 'domain' in scope:
+ setattr(assignment, 'domain', scope['domain']['id'])
+ assignment.project = ''
+
+ else:
+ assignment.domain = ''
+ assignment.project = ''
+
+ del assignment.scope
+
+ if hasattr(assignment, 'user'):
+ setattr(assignment, 'user', assignment.user['id'])
+ assignment.group = ''
+ elif hasattr(assignment, 'group'):
+ setattr(assignment, 'group', assignment.group['id'])
+ assignment.user = ''
+ else:
+ assignment.user = ''
+ assignment.group = ''
+
+ if hasattr(assignment, 'role'):
+ setattr(assignment, 'role', assignment.role['id'])
+ else:
+ assignment.role = ''
+
+ # Creating a tuple from data object fields
+ # (including the blank ones)
+ data_parsed.append(self._as_tuple(assignment))
+
+ return columns, tuple(data_parsed)
diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py
index 7e3bfc6..88301ed 100644
--- a/openstackclient/identity/v3/service.py
+++ b/openstackclient/identity/v3/service.py
@@ -23,6 +23,7 @@ from cliff import lister
from cliff import show
from openstackclient.common import utils
+from openstackclient.identity import common
class CreateService(show.ShowOne):
@@ -56,7 +57,7 @@ class CreateService(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
enabled = True
@@ -64,9 +65,9 @@ class CreateService(show.ShowOne):
enabled = False
service = identity_client.services.create(
- parsed_args.name,
- parsed_args.type,
- enabled,
+ name=parsed_args.name,
+ type=parsed_args.type,
+ enabled=enabled,
)
return zip(*sorted(six.iteritems(service._info)))
@@ -87,13 +88,10 @@ class DeleteService(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
- service = utils.find_resource(
- identity_client.services,
- parsed_args.service,
- )
+ service = common.find_service(identity_client, parsed_args.service)
identity_client.services.delete(service.id)
return
@@ -105,7 +103,7 @@ class ListService(lister.Lister):
log = logging.getLogger(__name__ + '.ListService')
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
columns = ('ID', 'Name', 'Type', 'Enabled')
data = self.app.client_manager.identity.services.list()
@@ -152,7 +150,7 @@ class SetService(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
if (not parsed_args.name
@@ -161,10 +159,7 @@ class SetService(command.Command):
and not parsed_args.disable):
return
- service = utils.find_resource(
- identity_client.services,
- parsed_args.service,
- )
+ service = common.find_service(identity_client, parsed_args.service)
kwargs = service._info
if parsed_args.type:
@@ -200,12 +195,9 @@ class ShowService(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
- service = utils.find_resource(
- identity_client.services,
- parsed_args.service,
- )
+ service = common.find_service(identity_client, parsed_args.service)
return zip(*sorted(six.iteritems(service._info)))
diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py
index ba667be..cd73d03 100644
--- a/openstackclient/identity/v3/token.py
+++ b/openstackclient/identity/v3/token.py
@@ -18,55 +18,8 @@
import logging
import six
-from cliff import command
-from cliff import lister
from cliff import show
-from openstackclient.common import utils
-
-
-class AuthenticateAccessToken(show.ShowOne):
- """Authenticate access token to receive keystone token"""
-
- api = 'identity'
- log = logging.getLogger(__name__ + '.AuthenticateAccessToken')
-
- def get_parser(self, prog_name):
- parser = super(AuthenticateAccessToken, self).get_parser(prog_name)
- parser.add_argument(
- '--consumer-key',
- metavar='<consumer-key>',
- help='Consumer key',
- required=True
- )
- parser.add_argument(
- '--consumer-secret',
- metavar='<consumer-secret>',
- help='Consumer secret',
- required=True
- )
- parser.add_argument(
- '--access-key',
- metavar='<access-key>',
- help='Access token key',
- required=True
- )
- parser.add_argument(
- '--access-secret',
- metavar='<access-secret>',
- help='Access token secret',
- required=True
- )
- return parser
-
- def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
- token_client = self.app.client_manager.identity.tokens
- keystone_token = token_client.authenticate_access_token(
- parsed_args.consumer_key, parsed_args.consumer_secret,
- parsed_args.access_key, parsed_args.access_secret)
- return zip(*sorted(six.iteritems(keystone_token)))
-
class AuthorizeRequestToken(show.ShowOne):
"""Authorize request token command"""
@@ -78,17 +31,28 @@ class AuthorizeRequestToken(show.ShowOne):
parser.add_argument(
'--request-key',
metavar='<request-key>',
- help='Consumer key',
+ help='Request token key',
+ required=True
+ )
+ parser.add_argument(
+ '--role-ids',
+ metavar='<role-ids>',
+ help='Requested role IDs',
required=True
)
return parser
def take_action(self, parsed_args):
self.log.debug('take_action(%s)' % parsed_args)
- token_client = self.app.client_manager.identity.tokens
+ identity_client = self.app.client_manager.identity
+
+ roles = []
+ for r_id in parsed_args.role_ids.split():
+ roles.append(r_id)
- verifier_pin = token_client.authorize_request_token(
- parsed_args.request_key)
+ verifier_pin = identity_client.oauth1.request_tokens.authorize(
+ parsed_args.request_key,
+ roles)
info = {}
info.update(verifier_pin._info)
return zip(*sorted(six.iteritems(info)))
@@ -135,12 +99,14 @@ class CreateAccessToken(show.ShowOne):
def take_action(self, parsed_args):
self.log.debug('take_action(%s)' % parsed_args)
- token_client = self.app.client_manager.identity.tokens
- access_token = token_client.create_access_token(
+ token_client = self.app.client_manager.identity.oauth1.access_tokens
+ access_token = token_client.create(
parsed_args.consumer_key, parsed_args.consumer_secret,
parsed_args.request_key, parsed_args.request_secret,
parsed_args.verifier)
- return zip(*sorted(six.iteritems(access_token)))
+ info = {}
+ info.update(access_token._info)
+ return zip(*sorted(six.iteritems(info)))
class CreateRequestToken(show.ShowOne):
@@ -163,84 +129,38 @@ class CreateRequestToken(show.ShowOne):
required=True
)
parser.add_argument(
- '--role-ids',
- metavar='<role-ids>',
- help='Requested role IDs',
- )
- parser.add_argument(
'--project-id',
metavar='<project-id>',
help='Requested project ID',
+ required=True
)
return parser
def take_action(self, parsed_args):
self.log.debug('take_action(%s)' % parsed_args)
- token_client = self.app.client_manager.identity.tokens
- request_token = token_client.create_request_token(
+ token_client = self.app.client_manager.identity.oauth1.request_tokens
+ request_token = token_client.create(
parsed_args.consumer_key,
parsed_args.consumer_secret,
- parsed_args.role_ids,
parsed_args.project_id)
- return zip(*sorted(six.iteritems(request_token)))
-
-
-class DeleteAccessToken(command.Command):
- """Delete access token command"""
-
- log = logging.getLogger(__name__ + '.DeleteAccessToken')
-
- def get_parser(self, prog_name):
- parser = super(DeleteAccessToken, self).get_parser(prog_name)
- parser.add_argument(
- 'user',
- metavar='<user>',
- help='Name or Id of user',
- )
- parser.add_argument(
- 'access_key',
- metavar='<access-key>',
- help='Access Token to be deleted',
- )
- return parser
-
- def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
-
- identity_client = self.app.client_manager.identity
- user = utils.find_resource(
- identity_client.users, parsed_args.user).id
- identity_client.tokens.delete_access_token(user,
- parsed_args.access_key)
- return
+ info = {}
+ info.update(request_token._info)
+ return zip(*sorted(six.iteritems(info)))
-class ListAccessToken(lister.Lister):
- """List access tokens command"""
+class CreateToken(show.ShowOne):
+ """Issue token command"""
- log = logging.getLogger(__name__ + '.ListAccessToken')
+ log = logging.getLogger(__name__ + '.CreateToken')
def get_parser(self, prog_name):
- parser = super(ListAccessToken, self).get_parser(prog_name)
- parser.add_argument(
- 'user',
- metavar='<user>',
- help='Name or Id of user',
- )
+ parser = super(CreateToken, self).get_parser(prog_name)
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
-
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
- user = utils.find_resource(
- identity_client.users, parsed_args.user).id
-
- columns = ('ID', 'Consumer ID', 'Expires At',
- 'Project Id', 'Authorizing User Id')
- data = identity_client.tokens.list_access_tokens(user)
- return (columns,
- (utils.get_item_properties(
- s, columns,
- formatters={},
- ) for s in data))
+ token = identity_client.service_catalog.get_token()
+ if 'tenant_id' in token:
+ token['project_id'] = token.pop('tenant_id')
+ return zip(*sorted(six.iteritems(token)))
diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py
index 54ffe56..38c3497 100644
--- a/openstackclient/identity/v3/user.py
+++ b/openstackclient/identity/v3/user.py
@@ -17,7 +17,6 @@
import logging
import six
-import sys
from cliff import command
from cliff import lister
@@ -44,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',
@@ -77,7 +82,7 @@ class CreateUser(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
if parsed_args.project:
@@ -97,9 +102,11 @@ 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,
+ name=parsed_args.name,
domain=domain_id,
default_project=project_id,
password=parsed_args.password,
@@ -128,7 +135,7 @@ class DeleteUser(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
user = utils.find_resource(
@@ -141,114 +148,68 @@ class DeleteUser(command.Command):
class ListUser(lister.Lister):
- """List users and optionally roles assigned to users"""
+ """List users"""
log = logging.getLogger(__name__ + '.ListUser')
def get_parser(self, prog_name):
parser = super(ListUser, self).get_parser(prog_name)
parser.add_argument(
- 'user',
- metavar='<user>',
- nargs='?',
- help='Name or ID of user to list [required with --role]',
- )
- parser.add_argument(
- '--role',
- action='store_true',
- default=False,
- help='List the roles assigned to <user>',
- )
- domain_or_project = parser.add_mutually_exclusive_group()
- domain_or_project.add_argument(
'--domain',
metavar='<domain>',
- help='Filter list by <domain> [Only valid with --role]',
+ help='Filter group list by <domain>',
)
- domain_or_project.add_argument(
- '--project',
- metavar='<project>',
- help='Filter list by <project> [Only valid with --role]',
+ parser.add_argument(
+ '--group',
+ metavar='<group>',
+ help='List memberships of <group> (name or ID)',
)
parser.add_argument(
'--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):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
- if parsed_args.role:
- # List roles belonging to user
-
- # User is required here, bail if it is not supplied
- if not parsed_args.user:
- sys.stderr.write('Error: User must be specified')
- return ([], [])
-
- user = utils.find_resource(
- identity_client.users,
- parsed_args.user,
- )
-
- # List a user's roles
- if not parsed_args.domain and not parsed_args.project:
- columns = ('ID', 'Name')
- data = identity_client.roles.list(
- user=user,
- domain='default',
- )
- # List a user's roles on a domain
- elif parsed_args.user and parsed_args.domain:
- columns = ('ID', 'Name', 'Domain', 'User')
- domain = utils.find_resource(
- identity_client.domains,
- parsed_args.domain,
- )
- data = identity_client.roles.list(
- user=user,
- domain=domain,
- )
- for user_role in data:
- user_role.user = user.name
- user_role.domain = domain.name
- # List a user's roles on a project
- elif parsed_args.user and parsed_args.project:
- columns = ('ID', 'Name', 'Project', 'User')
- project = utils.find_resource(
- identity_client.projects,
- parsed_args.project,
- )
- data = identity_client.roles.list(
- user=user,
- project=project,
- )
- for user_role in data:
- user_role.user = user.name
- user_role.project = project.name
- else:
- # TODO(dtroyer): raise exception here, this really is an error
- sys.stderr.write("Error: Must specify --domain or --project "
- "with --role\n")
- return ([], [])
+ if parsed_args.domain:
+ domain = utils.find_resource(
+ identity_client.domains,
+ parsed_args.domain,
+ ).id
+ else:
+ domain = None
+
+ if parsed_args.group:
+ group = utils.find_resource(
+ identity_client.groups,
+ parsed_args.group,
+ ).id
+ else:
+ group = None
+
+ # List users
+ if parsed_args.long:
+ columns = ('ID', 'Name', 'Project Id', 'Domain Id',
+ 'Description', 'Email', 'Enabled')
else:
- # List users
- if parsed_args.long:
- columns = ('ID', 'Name', 'Project Id', 'Domain Id',
- 'Description', 'Email', 'Enabled')
- else:
- columns = ('ID', 'Name')
- data = self.app.client_manager.identity.users.list()
+ columns = ('ID', 'Name')
+ data = identity_client.users.list(
+ domain=domain,
+ group=group,
+ )
- return (columns,
- (utils.get_item_properties(
- s, columns,
- formatters={},
- ) for s in data))
+ return (
+ columns,
+ (utils.get_item_properties(
+ s, columns,
+ formatters={},
+ ) for s in data)
+ )
class SetUser(command.Command):
@@ -274,6 +235,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',
@@ -307,9 +274,12 @@ class SetUser(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ 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
@@ -368,7 +338,7 @@ class ShowUser(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
user = utils.find_resource(
diff --git a/openstackclient/image/__init__.py b/openstackclient/image/__init__.py
index 85ac250..e69de29 100644
--- a/openstackclient/image/__init__.py
+++ b/openstackclient/image/__init__.py
@@ -1,14 +0,0 @@
-# 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.
-#
diff --git a/openstackclient/image/client.py b/openstackclient/image/client.py
index 9edffde..ba48a0e 100644
--- a/openstackclient/image/client.py
+++ b/openstackclient/image/client.py
@@ -73,7 +73,7 @@ class Client_v1(gc_v1_client.Client):
def __init__(self, *args, **kwargs):
super(Client_v1, self).__init__(*args, **kwargs)
- self.images = ImageManager_v1(self)
+ self.images = ImageManager_v1(getattr(self, 'http_client', self))
class ImageManager_v1(gc_v1_images.ImageManager):
diff --git a/openstackclient/image/v1/__init__.py b/openstackclient/image/v1/__init__.py
index ebf59b3..e69de29 100644
--- a/openstackclient/image/v1/__init__.py
+++ b/openstackclient/image/v1/__init__.py
@@ -1,14 +0,0 @@
-# Copyright 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.
-#
diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py
index 40f9fce..92d0995 100644
--- a/openstackclient/image/v1/image.py
+++ b/openstackclient/image/v1/image.py
@@ -35,6 +35,10 @@ from openstackclient.common import parseractions
from openstackclient.common import utils
+DEFAULT_CONTAINER_FORMAT = 'bare'
+DEFAULT_DISK_FORMAT = 'raw'
+
+
class CreateImage(show.ShowOne):
"""Create/upload an image"""
@@ -45,146 +49,205 @@ class CreateImage(show.ShowOne):
parser.add_argument(
"name",
metavar="<name>",
- help="Name of image",
- )
- parser.add_argument(
- "--disk_format",
- default="raw",
- metavar="<disk_format>",
- help="Disk format of image",
+ help="New image name",
)
parser.add_argument(
"--id",
metavar="<id>",
- help="ID of image to reserve",
+ help="Image ID to reserve",
)
parser.add_argument(
"--store",
metavar="<store>",
- help="Store to upload image to",
+ help="Upload image to this store",
)
parser.add_argument(
"--container-format",
- default="bare",
- metavar="<container_format>",
- help="Container format of image",
+ default=DEFAULT_CONTAINER_FORMAT,
+ metavar="<container-format>",
+ help="Image container format "
+ "(default: %s)" % DEFAULT_CONTAINER_FORMAT,
+ )
+ parser.add_argument(
+ "--disk-format",
+ default=DEFAULT_DISK_FORMAT,
+ metavar="<disk-format>",
+ help="Image disk format "
+ "(default: %s)" % DEFAULT_DISK_FORMAT,
)
parser.add_argument(
"--owner",
metavar="<project>",
- help="Image owner (project name or ID)",
+ help="Image owner project name or ID",
)
parser.add_argument(
"--size",
metavar="<size>",
- help="Size of image in bytes. Only used with --location and"
- " --copy-from",
+ help="Image size, in bytes (only used with --location and"
+ " --copy-from)",
)
parser.add_argument(
"--min-disk",
- metavar="<disk_gb>",
- help="Minimum size of disk needed to boot image in gigabytes",
+ metavar="<disk-gb>",
+ type=int,
+ help="Minimum disk size needed to boot image, in gigabytes",
)
parser.add_argument(
"--min-ram",
- metavar="<disk_ram>",
- help="Minimum amount of ram needed to boot image in megabytes",
+ metavar="<ram-mb>",
+ type=int,
+ help="Minimum RAM size needed to boot image, in megabytes",
)
parser.add_argument(
"--location",
- metavar="<image_url>",
- help="URL where the data for this image already resides",
+ metavar="<image-url>",
+ help="Download image from an existing URL",
+ )
+ parser.add_argument(
+ "--copy-from",
+ metavar="<image-url>",
+ help="Copy image from the data store (similar to --location)",
)
parser.add_argument(
"--file",
metavar="<file>",
- help="Local file that contains disk image",
+ help="Upload image from local file",
)
parser.add_argument(
- "--checksum",
- metavar="<checksum>",
- help="Hash of image data used for verification",
+ "--volume",
+ metavar="<volume>",
+ help="Create image from a volume",
)
parser.add_argument(
- "--copy-from",
- metavar="<image_url>",
- help="Similar to --location, but this indicates that the image"
- " should immediately be copied from the data store",
+ "--force",
+ dest='force',
+ action='store_true',
+ default=False,
+ help="Force image creation if volume is in use "
+ "(only meaningful with --volume)",
)
parser.add_argument(
- "--property",
- dest="properties",
- metavar="<key=value>",
- action=parseractions.KeyValueAction,
- help="Set property on this image "
- '(repeat option to set multiple properties)',
+ "--checksum",
+ metavar="<checksum>",
+ help="Image hash used for verification",
)
protected_group = parser.add_mutually_exclusive_group()
protected_group.add_argument(
"--protected",
- dest="protected",
action="store_true",
- help="Prevent image from being deleted (default: False)",
+ help="Prevent image from being deleted",
)
protected_group.add_argument(
"--unprotected",
- dest="protected",
- action="store_false",
- default=False,
- help="Allow images to be deleted (default: True)",
+ action="store_true",
+ help="Allow image to be deleted (default)",
)
public_group = parser.add_mutually_exclusive_group()
public_group.add_argument(
"--public",
- dest="is_public",
action="store_true",
- default=True,
- help="Image is accessible to the public (default)",
+ help="Image is accessible to the public",
)
public_group.add_argument(
"--private",
- dest="is_public",
- action="store_false",
- help="Image is inaccessible to the public",
+ action="store_true",
+ help="Image is inaccessible to the public (default)",
+ )
+ parser.add_argument(
+ "--property",
+ dest="properties",
+ metavar="<key=value>",
+ action=parseractions.KeyValueAction,
+ help="Set an image property "
+ "(repeat option to set multiple properties)",
)
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
-
- # NOTE(jk0): Since create() takes kwargs, it's easiest to just make a
- # copy of parsed_args and remove what we don't need.
- args = vars(parsed_args)
- args = dict(filter(lambda x: x[1] is not None, args.items()))
- args.pop("columns")
- args.pop("formatter")
- args.pop("prefix")
- args.pop("variables")
-
- if "location" not in args and "copy_from" not in args:
- if "file" in args:
- args["data"] = open(args.pop("file"), "rb")
+ self.log.debug("take_action(%s)", parsed_args)
+ image_client = self.app.client_manager.image
+
+ # Build an attribute dict from the parsed args, only include
+ # attributes that were actually set on the command line
+ kwargs = {}
+ copy_attrs = ('name', 'id', 'store', 'container_format',
+ 'disk_format', 'owner', 'size', 'min_disk', 'min_ram',
+ 'localtion', 'copy_from', 'volume', 'force',
+ 'checksum', 'properties')
+ for attr in copy_attrs:
+ if attr in parsed_args:
+ val = getattr(parsed_args, attr, None)
+ if val:
+ # Only include a value in kwargs for attributes that are
+ # actually present on the command line
+ kwargs[attr] = val
+ # Handle exclusive booleans with care
+ # Avoid including attributes in kwargs if an option is not
+ # present on the command line. These exclusive booleans are not
+ # a single value for the pair of options because the default must be
+ # to do nothing when no options are present as opposed to always
+ # setting a default.
+ if parsed_args.protected:
+ kwargs['protected'] = True
+ if parsed_args.unprotected:
+ kwargs['protected'] = False
+ if parsed_args.public:
+ kwargs['is_public'] = True
+ if parsed_args.private:
+ kwargs['is_public'] = False
+
+ if not parsed_args.location and not parsed_args.copy_from:
+ if parsed_args.volume:
+ volume_client = self.app.client_manager.volume
+ source_volume = utils.find_resource(
+ volume_client.volumes,
+ parsed_args.volume,
+ )
+ response, body = volume_client.volumes.upload_to_image(
+ source_volume.id,
+ parsed_args.force,
+ parsed_args.name,
+ parsed_args.container_format,
+ parsed_args.disk_format,
+ )
+ info = body['os-volume_upload_image']
+ elif parsed_args.file:
+ # Send an open file handle to glanceclient so it will
+ # do a chunked transfer
+ kwargs["data"] = open(parsed_args.file, "rb")
else:
- args["data"] = None
+ # Read file from stdin
+ kwargs["data"] = None
if sys.stdin.isatty() is not True:
if msvcrt:
msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
- args["data"] = sys.stdin
+ # Send an open file handle to glanceclient so it will
+ # do a chunked transfer
+ kwargs["data"] = sys.stdin
- image_client = self.app.client_manager.image
try:
image = utils.find_resource(
image_client.images,
parsed_args.name,
)
+
+ # Preserve previous properties if any are being set now
+ if image.properties:
+ if parsed_args.properties:
+ image.properties.update(kwargs['properties'])
+ kwargs['properties'] = image.properties
+
except exceptions.CommandError:
- # This is normal for a create or reserve (create w/o an image)
- image = image_client.images.create(**args)
+ if not parsed_args.volume:
+ # This is normal for a create or reserve (create w/o an image)
+ # But skip for create from volume
+ image = image_client.images.create(**kwargs)
else:
- # It must be an update
- # If an image is specified via --file, --location or --copy-from
- # let the API handle it
- image = image_client.images.update(image, **args)
+ # Update an existing reservation
+
+ # If an image is specified via --file, --location or
+ # --copy-from let the API handle it
+ image = image_client.images.update(image.id, **kwargs)
info = {}
info.update(image._info)
@@ -206,7 +269,7 @@ class DeleteImage(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
image_client = self.app.client_manager.image
image = utils.find_resource(
@@ -231,7 +294,7 @@ class ListImage(lister.Lister):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
image_client = self.app.client_manager.image
@@ -265,7 +328,7 @@ class SaveImage(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
image_client = self.app.client_manager.image
image = utils.find_resource(
@@ -287,87 +350,104 @@ class SetImage(show.ShowOne):
parser.add_argument(
"image",
metavar="<image>",
- help="Name or ID of image to change",
+ help="Image name or ID to change",
)
parser.add_argument(
"--name",
metavar="<name>",
- help="Name of image",
+ help="New image name",
)
parser.add_argument(
"--owner",
metavar="<project>",
- help="Image owner (project name or ID)",
+ help="New image owner project name or ID",
)
parser.add_argument(
"--min-disk",
- metavar="<disk_gb>",
- help="Minimum size of disk needed to boot image in gigabytes",
+ metavar="<disk-gb>",
+ type=int,
+ help="Minimum disk size needed to boot image, in gigabytes",
)
parser.add_argument(
"--min-ram",
- metavar="<disk_ram>",
- help="Minimum amount of ram needed to boot image in megabytes",
- )
- parser.add_argument(
- "--property",
- dest="properties",
- metavar="<key=value>",
- action=parseractions.KeyValueAction,
- help="Set property on this image "
- '(repeat option to set multiple properties)',
+ metavar="<disk-ram>",
+ type=int,
+ help="Minimum RAM size needed to boot image, in megabytes",
)
protected_group = parser.add_mutually_exclusive_group()
protected_group.add_argument(
"--protected",
- dest="protected",
action="store_true",
- help="Prevent image from being deleted (default: False)",
+ help="Prevent image from being deleted",
)
protected_group.add_argument(
"--unprotected",
- dest="protected",
- action="store_false",
- default=False,
- help="Allow images to be deleted (default: True)",
+ action="store_true",
+ help="Allow image to be deleted (default)",
)
public_group = parser.add_mutually_exclusive_group()
public_group.add_argument(
"--public",
- dest="is_public",
action="store_true",
- default=True,
- help="Image is accessible to the public (default)",
+ help="Image is accessible to the public",
)
public_group.add_argument(
"--private",
- dest="is_public",
- action="store_false",
- help="Image is inaccessible to the public",
+ action="store_true",
+ help="Image is inaccessible to the public (default)",
+ )
+ parser.add_argument(
+ "--property",
+ dest="properties",
+ metavar="<key=value>",
+ action=parseractions.KeyValueAction,
+ help="Set an image property "
+ "(repeat option to set multiple properties)",
)
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
-
- # NOTE(jk0): Since create() takes kwargs, it's easiest to just make a
- # copy of parsed_args and remove what we don't need.
- args = vars(parsed_args)
- args = dict(filter(lambda x: x[1] is not None, args.items()))
- args.pop("columns")
- args.pop("formatter")
- args.pop("prefix")
- args.pop("variables")
- image_arg = args.pop("image")
-
+ self.log.debug("take_action(%s)", parsed_args)
image_client = self.app.client_manager.image
+
+ kwargs = {}
+ copy_attrs = ('name', 'owner', 'min_disk', 'min_ram', 'properties')
+ for attr in copy_attrs:
+ if attr in parsed_args:
+ val = getattr(parsed_args, attr, None)
+ if val:
+ # Only include a value in kwargs for attributes that are
+ # actually present on the command line
+ kwargs[attr] = val
+ # Handle exclusive booleans with care
+ # Avoid including attributes in kwargs if an option is not
+ # present on the command line. These exclusive booleans are not
+ # a single value for the pair of options because the default must be
+ # to do nothing when no options are present as opposed to always
+ # setting a default.
+ if parsed_args.protected:
+ kwargs['protected'] = True
+ if parsed_args.unprotected:
+ kwargs['protected'] = False
+ if parsed_args.public:
+ kwargs['is_public'] = True
+ if parsed_args.private:
+ kwargs['is_public'] = False
+
+ if not kwargs:
+ self.log.warning('no arguments specified')
+ return {}, {}
+
image = utils.find_resource(
image_client.images,
- image_arg,
+ parsed_args.image,
)
- # Merge properties
- args["properties"].update(image.properties)
- image = image_client.images.update(image, **args)
+
+ if image.properties and parsed_args.properties:
+ image.properties.update(kwargs['properties'])
+ kwargs['properties'] = image.properties
+
+ image = image_client.images.update(image.id, **kwargs)
info = {}
info.update(image._info)
@@ -389,7 +469,7 @@ class ShowImage(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
image_client = self.app.client_manager.image
image = utils.find_resource(
diff --git a/openstackclient/image/v2/__init__.py b/openstackclient/image/v2/__init__.py
index 85ac250..e69de29 100644
--- a/openstackclient/image/v2/__init__.py
+++ b/openstackclient/image/v2/__init__.py
@@ -1,14 +0,0 @@
-# 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.
-#
diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py
index e84e0d0..08897b2 100644
--- a/openstackclient/image/v2/image.py
+++ b/openstackclient/image/v2/image.py
@@ -41,7 +41,7 @@ class DeleteImage(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
image_client = self.app.client_manager.image
image = utils.find_resource(
@@ -66,7 +66,7 @@ class ListImage(lister.Lister):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
image_client = self.app.client_manager.image
@@ -100,7 +100,7 @@ class SaveImage(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
image_client = self.app.client_manager.image
image = utils.find_resource(
@@ -127,7 +127,7 @@ class ShowImage(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug("take_action(%s)" % parsed_args)
+ self.log.debug("take_action(%s)", parsed_args)
image_client = self.app.client_manager.image
image = utils.find_resource(
diff --git a/openstackclient/object/__init__.py b/openstackclient/object/__init__.py
index 02be10c..e69de29 100644
--- a/openstackclient/object/__init__.py
+++ b/openstackclient/object/__init__.py
@@ -1,12 +0,0 @@
-# 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.
-#
diff --git a/openstackclient/object/v1/__init__.py b/openstackclient/object/v1/__init__.py
index 02be10c..e69de29 100644
--- a/openstackclient/object/v1/__init__.py
+++ b/openstackclient/object/v1/__init__.py
@@ -1,12 +0,0 @@
-# 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.
-#
diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py
index fcfbd78..1e252aa 100644
--- a/openstackclient/object/v1/container.py
+++ b/openstackclient/object/v1/container.py
@@ -69,7 +69,7 @@ class ListContainer(lister.Lister):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
if parsed_args.long:
columns = ('Name', 'Bytes', 'Count')
@@ -116,7 +116,7 @@ class ShowContainer(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
data = lib_container.show_container(
self.app.restapi,
diff --git a/openstackclient/object/v1/lib/__init__.py b/openstackclient/object/v1/lib/__init__.py
index 02be10c..e69de29 100644
--- a/openstackclient/object/v1/lib/__init__.py
+++ b/openstackclient/object/v1/lib/__init__.py
@@ -1,12 +0,0 @@
-# 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.
-#
diff --git a/openstackclient/object/v1/lib/object.py b/openstackclient/object/v1/lib/object.py
index 6f9c9d6..646737b 100644
--- a/openstackclient/object/v1/lib/object.py
+++ b/openstackclient/object/v1/lib/object.py
@@ -126,7 +126,6 @@ def show_object(
'container': container,
'object': obj,
}
- #print "data: %s" % data
data['content-type'] = response.headers.get('content-type', None)
if 'content-length' in response.headers:
data['content-length'] = response.headers.get('content-length', None)
diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py
index f6a7703..ee30c84 100644
--- a/openstackclient/object/v1/object.py
+++ b/openstackclient/object/v1/object.py
@@ -79,7 +79,7 @@ class ListObject(lister.Lister):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
if parsed_args.long:
columns = (
@@ -140,7 +140,7 @@ class ShowObject(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
data = lib_object.show_object(
self.app.restapi,
diff --git a/openstackclient/openstack/common/cfg.py b/openstackclient/openstack/common/cfg.py
deleted file mode 100644
index cba3145..0000000
--- a/openstackclient/openstack/common/cfg.py
+++ /dev/null
@@ -1,1731 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2012 Red Hat, Inc.
-#
-# 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.
-
-r"""
-Configuration options which may be set on the command line or in config files.
-
-The schema for each option is defined using the Opt sub-classes, e.g.:
-
-::
-
- common_opts = [
- cfg.StrOpt('bind_host',
- default='0.0.0.0',
- help='IP address to listen on'),
- cfg.IntOpt('bind_port',
- default=9292,
- help='Port number to listen on')
- ]
-
-Options can be strings, integers, floats, booleans, lists or 'multi strings'::
-
- enabled_apis_opt = cfg.ListOpt('enabled_apis',
- default=['ec2', 'osapi_compute'],
- help='List of APIs to enable by default')
-
- DEFAULT_EXTENSIONS = [
- 'nova.api.openstack.compute.contrib.standard_extensions'
- ]
- osapi_compute_extension_opt = cfg.MultiStrOpt('osapi_compute_extension',
- default=DEFAULT_EXTENSIONS)
-
-Option schemas are registered with the config manager at runtime, but before
-the option is referenced::
-
- class ExtensionManager(object):
-
- enabled_apis_opt = cfg.ListOpt(...)
-
- def __init__(self, conf):
- self.conf = conf
- self.conf.register_opt(enabled_apis_opt)
- ...
-
- def _load_extensions(self):
- for ext_factory in self.conf.osapi_compute_extension:
- ....
-
-A common usage pattern is for each option schema to be defined in the module or
-class which uses the option::
-
- opts = ...
-
- def add_common_opts(conf):
- conf.register_opts(opts)
-
- def get_bind_host(conf):
- return conf.bind_host
-
- def get_bind_port(conf):
- return conf.bind_port
-
-An option may optionally be made available via the command line. Such options
-must registered with the config manager before the command line is parsed (for
-the purposes of --help and CLI arg validation)::
-
- cli_opts = [
- cfg.BoolOpt('verbose',
- short='v',
- default=False,
- help='Print more verbose output'),
- cfg.BoolOpt('debug',
- short='d',
- default=False,
- help='Print debugging output'),
- ]
-
- def add_common_opts(conf):
- conf.register_cli_opts(cli_opts)
-
-The config manager has two CLI options defined by default, --config-file
-and --config-dir::
-
- class ConfigOpts(object):
-
- def __call__(self, ...):
-
- opts = [
- MultiStrOpt('config-file',
- ...),
- StrOpt('config-dir',
- ...),
- ]
-
- self.register_cli_opts(opts)
-
-Option values are parsed from any supplied config files using
-openstack.common.iniparser. If none are specified, a default set is used
-e.g. glance-api.conf and glance-common.conf::
-
- glance-api.conf:
- [DEFAULT]
- bind_port = 9292
-
- glance-common.conf:
- [DEFAULT]
- bind_host = 0.0.0.0
-
-Option values in config files override those on the command line. Config files
-are parsed in order, with values in later files overriding those in earlier
-files.
-
-The parsing of CLI args and config files is initiated by invoking the config
-manager e.g.::
-
- conf = ConfigOpts()
- conf.register_opt(BoolOpt('verbose', ...))
- conf(sys.argv[1:])
- if conf.verbose:
- ...
-
-Options can be registered as belonging to a group::
-
- rabbit_group = cfg.OptGroup(name='rabbit',
- title='RabbitMQ options')
-
- rabbit_host_opt = cfg.StrOpt('host',
- default='localhost',
- help='IP/hostname to listen on'),
- rabbit_port_opt = cfg.IntOpt('port',
- default=5672,
- help='Port number to listen on')
-
- def register_rabbit_opts(conf):
- conf.register_group(rabbit_group)
- # options can be registered under a group in either of these ways:
- conf.register_opt(rabbit_host_opt, group=rabbit_group)
- conf.register_opt(rabbit_port_opt, group='rabbit')
-
-If it no group attributes are required other than the group name, the group
-need not be explicitly registered e.g.
-
- def register_rabbit_opts(conf):
- # The group will automatically be created, equivalent calling::
- # conf.register_group(OptGroup(name='rabbit'))
- conf.register_opt(rabbit_port_opt, group='rabbit')
-
-If no group is specified, options belong to the 'DEFAULT' section of config
-files::
-
- glance-api.conf:
- [DEFAULT]
- bind_port = 9292
- ...
-
- [rabbit]
- host = localhost
- port = 5672
- use_ssl = False
- userid = guest
- password = guest
- virtual_host = /
-
-Command-line options in a group are automatically prefixed with the
-group name::
-
- --rabbit-host localhost --rabbit-port 9999
-
-Option values in the default group are referenced as attributes/properties on
-the config manager; groups are also attributes on the config manager, with
-attributes for each of the options associated with the group::
-
- server.start(app, conf.bind_port, conf.bind_host, conf)
-
- self.connection = kombu.connection.BrokerConnection(
- hostname=conf.rabbit.host,
- port=conf.rabbit.port,
- ...)
-
-Option values may reference other values using PEP 292 string substitution::
-
- opts = [
- cfg.StrOpt('state_path',
- default=os.path.join(os.path.dirname(__file__), '../'),
- help='Top-level directory for maintaining nova state'),
- cfg.StrOpt('sqlite_db',
- default='nova.sqlite',
- help='file name for sqlite'),
- cfg.StrOpt('sql_connection',
- default='sqlite:///$state_path/$sqlite_db',
- help='connection string for sql database'),
- ]
-
-Note that interpolation can be avoided by using '$$'.
-
-Options may be declared as required so that an error is raised if the user
-does not supply a value for the option.
-
-Options may be declared as secret so that their values are not leaked into
-log files::
-
- opts = [
- cfg.StrOpt('s3_store_access_key', secret=True),
- cfg.StrOpt('s3_store_secret_key', secret=True),
- ...
- ]
-
-This module also contains a global instance of the ConfigOpts class
-in order to support a common usage pattern in OpenStack::
-
- from openstackclient.openstack.common import cfg
-
- opts = [
- cfg.StrOpt('bind_host', default='0.0.0.0'),
- cfg.IntOpt('bind_port', default=9292),
- ]
-
- CONF = cfg.CONF
- CONF.register_opts(opts)
-
- def start(server, app):
- server.start(app, CONF.bind_port, CONF.bind_host)
-
-Positional command line arguments are supported via a 'positional' Opt
-constructor argument::
-
- >>> conf = ConfigOpts()
- >>> conf.register_cli_opt(MultiStrOpt('bar', positional=True))
- True
- >>> conf(['a', 'b'])
- >>> conf.bar
- ['a', 'b']
-
-It is also possible to use argparse "sub-parsers" to parse additional
-command line arguments using the SubCommandOpt class:
-
- >>> def add_parsers(subparsers):
- ... list_action = subparsers.add_parser('list')
- ... list_action.add_argument('id')
- ...
- >>> conf = ConfigOpts()
- >>> conf.register_cli_opt(SubCommandOpt('action', handler=add_parsers))
- True
- >>> conf(args=['list', '10'])
- >>> conf.action.name, conf.action.id
- ('list', '10')
-
-"""
-
-import argparse
-import collections
-import copy
-import functools
-import glob
-import os
-import string
-import sys
-
-from openstackclient.openstack.common import iniparser
-
-
-class Error(Exception):
- """Base class for cfg exceptions."""
-
- def __init__(self, msg=None):
- self.msg = msg
-
- def __str__(self):
- return self.msg
-
-
-class ArgsAlreadyParsedError(Error):
- """Raised if a CLI opt is registered after parsing."""
-
- def __str__(self):
- ret = "arguments already parsed"
- if self.msg:
- ret += ": " + self.msg
- return ret
-
-
-class NoSuchOptError(Error, AttributeError):
- """Raised if an opt which doesn't exist is referenced."""
-
- def __init__(self, opt_name, group=None):
- self.opt_name = opt_name
- self.group = group
-
- def __str__(self):
- if self.group is None:
- return "no such option: %s" % self.opt_name
- else:
- return "no such option in group %s: %s" % (self.group.name,
- self.opt_name)
-
-
-class NoSuchGroupError(Error):
- """Raised if a group which doesn't exist is referenced."""
-
- def __init__(self, group_name):
- self.group_name = group_name
-
- def __str__(self):
- return "no such group: %s" % self.group_name
-
-
-class DuplicateOptError(Error):
- """Raised if multiple opts with the same name are registered."""
-
- def __init__(self, opt_name):
- self.opt_name = opt_name
-
- def __str__(self):
- return "duplicate option: %s" % self.opt_name
-
-
-class RequiredOptError(Error):
- """Raised if an option is required but no value is supplied by the user."""
-
- def __init__(self, opt_name, group=None):
- self.opt_name = opt_name
- self.group = group
-
- def __str__(self):
- if self.group is None:
- return "value required for option: %s" % self.opt_name
- else:
- return "value required for option: %s.%s" % (self.group.name,
- self.opt_name)
-
-
-class TemplateSubstitutionError(Error):
- """Raised if an error occurs substituting a variable in an opt value."""
-
- def __str__(self):
- return "template substitution error: %s" % self.msg
-
-
-class ConfigFilesNotFoundError(Error):
- """Raised if one or more config files are not found."""
-
- def __init__(self, config_files):
- self.config_files = config_files
-
- def __str__(self):
- return ('Failed to read some config files: %s' %
- string.join(self.config_files, ','))
-
-
-class ConfigFileParseError(Error):
- """Raised if there is an error parsing a config file."""
-
- def __init__(self, config_file, msg):
- self.config_file = config_file
- self.msg = msg
-
- def __str__(self):
- return 'Failed to parse %s: %s' % (self.config_file, self.msg)
-
-
-class ConfigFileValueError(Error):
- """Raised if a config file value does not match its opt type."""
- pass
-
-
-def _fixpath(p):
- """Apply tilde expansion and absolutization to a path."""
- return os.path.abspath(os.path.expanduser(p))
-
-
-def _get_config_dirs(project=None):
- """Return a list of directors where config files may be located.
-
- :param project: an optional project name
-
- If a project is specified, following directories are returned::
-
- ~/.${project}/
- ~/
- /etc/${project}/
- /etc/
-
- Otherwise, these directories::
-
- ~/
- /etc/
- """
- cfg_dirs = [
- _fixpath(os.path.join('~', '.' + project)) if project else None,
- _fixpath('~'),
- os.path.join('/etc', project) if project else None,
- '/etc'
- ]
-
- return filter(bool, cfg_dirs)
-
-
-def _search_dirs(dirs, basename, extension=""):
- """Search a list of directories for a given filename.
-
- Iterator over the supplied directories, returning the first file
- found with the supplied name and extension.
-
- :param dirs: a list of directories
- :param basename: the filename, e.g. 'glance-api'
- :param extension: the file extension, e.g. '.conf'
- :returns: the path to a matching file, or None
- """
- for d in dirs:
- path = os.path.join(d, '%s%s' % (basename, extension))
- if os.path.exists(path):
- return path
-
-
-def find_config_files(project=None, prog=None, extension='.conf'):
- """Return a list of default configuration files.
-
- :param project: an optional project name
- :param prog: the program name, defaulting to the basename of sys.argv[0]
- :param extension: the type of the config file
-
- We default to two config files: [${project}.conf, ${prog}.conf]
-
- And we look for those config files in the following directories::
-
- ~/.${project}/
- ~/
- /etc/${project}/
- /etc/
-
- We return an absolute path for (at most) one of each the default config
- files, for the topmost directory it exists in.
-
- For example, if project=foo, prog=bar and /etc/foo/foo.conf, /etc/bar.conf
- and ~/.foo/bar.conf all exist, then we return ['/etc/foo/foo.conf',
- '~/.foo/bar.conf']
-
- If no project name is supplied, we only look for ${prog.conf}.
- """
- if prog is None:
- prog = os.path.basename(sys.argv[0])
-
- cfg_dirs = _get_config_dirs(project)
-
- config_files = []
- if project:
- config_files.append(_search_dirs(cfg_dirs, project, extension))
- config_files.append(_search_dirs(cfg_dirs, prog, extension))
-
- return filter(bool, config_files)
-
-
-def _is_opt_registered(opts, opt):
- """Check whether an opt with the same name is already registered.
-
- The same opt may be registered multiple times, with only the first
- registration having any effect. However, it is an error to attempt
- to register a different opt with the same name.
-
- :param opts: the set of opts already registered
- :param opt: the opt to be registered
- :returns: True if the opt was previously registered, False otherwise
- :raises: DuplicateOptError if a naming conflict is detected
- """
- if opt.dest in opts:
- if opts[opt.dest]['opt'] != opt:
- raise DuplicateOptError(opt.name)
- return True
- else:
- return False
-
-
-def set_defaults(opts, **kwargs):
- for opt in opts:
- if opt.dest in kwargs:
- opt.default = kwargs[opt.dest]
- break
-
-
-class Opt(object):
-
- """Base class for all configuration options.
-
- An Opt object has no public methods, but has a number of public string
- properties:
-
- name:
- the name of the option, which may include hyphens
- dest:
- the (hyphen-less) ConfigOpts property which contains the option value
- short:
- a single character CLI option name
- default:
- the default value of the option
- positional:
- True if the option is a positional CLI argument
- metavar:
- the name shown as the argument to a CLI option in --help output
- help:
- an string explaining how the options value is used
- """
- multi = False
-
- def __init__(self, name, dest=None, short=None, default=None,
- positional=False, metavar=None, help=None,
- secret=False, required=False, deprecated_name=None):
- """Construct an Opt object.
-
- The only required parameter is the option's name. However, it is
- common to also supply a default and help string for all options.
-
- :param name: the option's name
- :param dest: the name of the corresponding ConfigOpts property
- :param short: a single character CLI option name
- :param default: the default value of the option
- :param positional: True if the option is a positional CLI argument
- :param metavar: the option argument to show in --help
- :param help: an explanation of how the option is used
- :param secret: true iff the value should be obfuscated in log output
- :param required: true iff a value must be supplied for this option
- :param deprecated_name: deprecated name option. Acts like an alias
- """
- self.name = name
- if dest is None:
- self.dest = self.name.replace('-', '_')
- else:
- self.dest = dest
- self.short = short
- self.default = default
- self.positional = positional
- self.metavar = metavar
- self.help = help
- self.secret = secret
- self.required = required
- if deprecated_name is not None:
- self.deprecated_name = deprecated_name.replace('-', '_')
- else:
- self.deprecated_name = None
-
- def __ne__(self, another):
- return vars(self) != vars(another)
-
- def _get_from_config_parser(self, cparser, section):
- """Retrieves the option value from a MultiConfigParser object.
-
- This is the method ConfigOpts uses to look up the option value from
- config files. Most opt types override this method in order to perform
- type appropriate conversion of the returned value.
-
- :param cparser: a ConfigParser object
- :param section: a section name
- """
- return self._cparser_get_with_deprecated(cparser, section)
-
- def _cparser_get_with_deprecated(self, cparser, section):
- """If cannot find option as dest try deprecated_name alias."""
- if self.deprecated_name is not None:
- return cparser.get(section, [self.dest, self.deprecated_name])
- return cparser.get(section, [self.dest])
-
- def _add_to_cli(self, parser, group=None):
- """Makes the option available in the command line interface.
-
- This is the method ConfigOpts uses to add the opt to the CLI interface
- as appropriate for the opt type. Some opt types may extend this method,
- others may just extend the helper methods it uses.
-
- :param parser: the CLI option parser
- :param group: an optional OptGroup object
- """
- container = self._get_argparse_container(parser, group)
- kwargs = self._get_argparse_kwargs(group)
- prefix = self._get_argparse_prefix('', group)
- self._add_to_argparse(container, self.name, self.short, kwargs, prefix,
- self.positional, self.deprecated_name)
-
- def _add_to_argparse(self, container, name, short, kwargs, prefix='',
- positional=False, deprecated_name=None):
- """Add an option to an argparse parser or group.
-
- :param container: an argparse._ArgumentGroup object
- :param name: the opt name
- :param short: the short opt name
- :param kwargs: the keyword arguments for add_argument()
- :param prefix: an optional prefix to prepend to the opt name
- :param position: whether the optional is a positional CLI argument
- :raises: DuplicateOptError if a naming confict is detected
- """
- def hyphen(arg):
- return arg if not positional else ''
-
- args = [hyphen('--') + prefix + name]
- if short:
- args.append(hyphen('-') + short)
- if deprecated_name:
- args.append(hyphen('--') + prefix + deprecated_name)
-
- try:
- container.add_argument(*args, **kwargs)
- except argparse.ArgumentError as e:
- raise DuplicateOptError(e)
-
- def _get_argparse_container(self, parser, group):
- """Returns an argparse._ArgumentGroup.
-
- :param parser: an argparse.ArgumentParser
- :param group: an (optional) OptGroup object
- :returns: an argparse._ArgumentGroup if group is given, else parser
- """
- if group is not None:
- return group._get_argparse_group(parser)
- else:
- return parser
-
- def _get_argparse_kwargs(self, group, **kwargs):
- """Build a dict of keyword arguments for argparse's add_argument().
-
- Most opt types extend this method to customize the behaviour of the
- options added to argparse.
-
- :param group: an optional group
- :param kwargs: optional keyword arguments to add to
- :returns: a dict of keyword arguments
- """
- if not self.positional:
- dest = self.dest
- if group is not None:
- dest = group.name + '_' + dest
- kwargs['dest'] = dest
- else:
- kwargs['nargs'] = '?'
- kwargs.update({'default': None,
- 'metavar': self.metavar,
- 'help': self.help, })
- return kwargs
-
- def _get_argparse_prefix(self, prefix, group):
- """Build a prefix for the CLI option name, if required.
-
- CLI options in a group are prefixed with the group's name in order
- to avoid conflicts between similarly named options in different
- groups.
-
- :param prefix: an existing prefix to append to (e.g. 'no' or '')
- :param group: an optional OptGroup object
- :returns: a CLI option prefix including the group name, if appropriate
- """
- if group is not None:
- return group.name + '-' + prefix
- else:
- return prefix
-
-
-class StrOpt(Opt):
- """
- String opts do not have their values transformed and are returned as
- str objects.
- """
- pass
-
-
-class BoolOpt(Opt):
-
- """
- Bool opts are set to True or False on the command line using --optname or
- --noopttname respectively.
-
- In config files, boolean values are case insensitive and can be set using
- 1/0, yes/no, true/false or on/off.
- """
-
- _boolean_states = {'1': True, 'yes': True, 'true': True, 'on': True,
- '0': False, 'no': False, 'false': False, 'off': False}
-
- def __init__(self, *args, **kwargs):
- if 'positional' in kwargs:
- raise ValueError('positional boolean args not supported')
- super(BoolOpt, self).__init__(*args, **kwargs)
-
- def _get_from_config_parser(self, cparser, section):
- """Retrieve the opt value as a boolean from ConfigParser."""
- def convert_bool(v):
- value = self._boolean_states.get(v.lower())
- if value is None:
- raise ValueError('Unexpected boolean value %r' % v)
-
- return value
-
- return [convert_bool(v) for v in
- self._cparser_get_with_deprecated(cparser, section)]
-
- def _add_to_cli(self, parser, group=None):
- """Extends the base class method to add the --nooptname option."""
- super(BoolOpt, self)._add_to_cli(parser, group)
- self._add_inverse_to_argparse(parser, group)
-
- def _add_inverse_to_argparse(self, parser, group):
- """Add the --nooptname option to the option parser."""
- container = self._get_argparse_container(parser, group)
- kwargs = self._get_argparse_kwargs(group, action='store_false')
- prefix = self._get_argparse_prefix('no', group)
- kwargs["help"] = "The inverse of --" + self.name
- self._add_to_argparse(container, self.name, None, kwargs, prefix,
- self.positional, self.deprecated_name)
-
- def _get_argparse_kwargs(self, group, action='store_true', **kwargs):
- """Extends the base argparse keyword dict for boolean options."""
-
- kwargs = super(BoolOpt, self)._get_argparse_kwargs(group, **kwargs)
-
- # metavar has no effect for BoolOpt
- if 'metavar' in kwargs:
- del kwargs['metavar']
-
- if action != 'store_true':
- action = 'store_false'
-
- kwargs['action'] = action
-
- return kwargs
-
-
-class IntOpt(Opt):
-
- """Int opt values are converted to integers using the int() builtin."""
-
- def _get_from_config_parser(self, cparser, section):
- """Retrieve the opt value as a integer from ConfigParser."""
- return [int(v) for v in self._cparser_get_with_deprecated(cparser,
- section)]
-
- def _get_argparse_kwargs(self, group, **kwargs):
- """Extends the base argparse keyword dict for integer options."""
- return super(IntOpt,
- self)._get_argparse_kwargs(group, type=int, **kwargs)
-
-
-class FloatOpt(Opt):
-
- """Float opt values are converted to floats using the float() builtin."""
-
- def _get_from_config_parser(self, cparser, section):
- """Retrieve the opt value as a float from ConfigParser."""
- return [float(v) for v in
- self._cparser_get_with_deprecated(cparser, section)]
-
- def _get_argparse_kwargs(self, group, **kwargs):
- """Extends the base argparse keyword dict for float options."""
- return super(FloatOpt, self)._get_argparse_kwargs(group,
- type=float, **kwargs)
-
-
-class ListOpt(Opt):
-
- """
- List opt values are simple string values separated by commas. The opt value
- is a list containing these strings.
- """
-
- class _StoreListAction(argparse.Action):
- """
- An argparse action for parsing an option value into a list.
- """
- def __call__(self, parser, namespace, values, option_string=None):
- if values is not None:
- values = [a.strip() for a in values.split(',')]
- setattr(namespace, self.dest, values)
-
- def _get_from_config_parser(self, cparser, section):
- """Retrieve the opt value as a list from ConfigParser."""
- return [[a.strip() for a in v.split(',')] for v in
- self._cparser_get_with_deprecated(cparser, section)]
-
- def _get_argparse_kwargs(self, group, **kwargs):
- """Extends the base argparse keyword dict for list options."""
- return Opt._get_argparse_kwargs(self,
- group,
- action=ListOpt._StoreListAction,
- **kwargs)
-
-
-class MultiStrOpt(Opt):
-
- """
- Multistr opt values are string opts which may be specified multiple times.
- The opt value is a list containing all the string values specified.
- """
- multi = True
-
- def _get_argparse_kwargs(self, group, **kwargs):
- """Extends the base argparse keyword dict for multi str options."""
- kwargs = super(MultiStrOpt, self)._get_argparse_kwargs(group)
- if not self.positional:
- kwargs['action'] = 'append'
- else:
- kwargs['nargs'] = '*'
- return kwargs
-
- def _cparser_get_with_deprecated(self, cparser, section):
- """If cannot find option as dest try deprecated_name alias."""
- if self.deprecated_name is not None:
- return cparser.get(section, [self.dest, self.deprecated_name],
- multi=True)
- return cparser.get(section, [self.dest], multi=True)
-
-
-class SubCommandOpt(Opt):
-
- """
- Sub-command options allow argparse sub-parsers to be used to parse
- additional command line arguments.
-
- The handler argument to the SubCommandOpt contructor is a callable
- which is supplied an argparse subparsers object. Use this handler
- callable to add sub-parsers.
-
- The opt value is SubCommandAttr object with the name of the chosen
- sub-parser stored in the 'name' attribute and the values of other
- sub-parser arguments available as additional attributes.
- """
-
- def __init__(self, name, dest=None, handler=None,
- title=None, description=None, help=None):
- """Construct an sub-command parsing option.
-
- This behaves similarly to other Opt sub-classes but adds a
- 'handler' argument. The handler is a callable which is supplied
- an subparsers object when invoked. The add_parser() method on
- this subparsers object can be used to register parsers for
- sub-commands.
-
- :param name: the option's name
- :param dest: the name of the corresponding ConfigOpts property
- :param title: title of the sub-commands group in help output
- :param description: description of the group in help output
- :param help: a help string giving an overview of available sub-commands
- """
- super(SubCommandOpt, self).__init__(name, dest=dest, help=help)
- self.handler = handler
- self.title = title
- self.description = description
-
- def _add_to_cli(self, parser, group=None):
- """Add argparse sub-parsers and invoke the handler method."""
- dest = self.dest
- if group is not None:
- dest = group.name + '_' + dest
-
- subparsers = parser.add_subparsers(dest=dest,
- title=self.title,
- description=self.description,
- help=self.help)
-
- if self.handler is not None:
- self.handler(subparsers)
-
-
-class OptGroup(object):
-
- """
- Represents a group of opts.
-
- CLI opts in the group are automatically prefixed with the group name.
-
- Each group corresponds to a section in config files.
-
- An OptGroup object has no public methods, but has a number of public string
- properties:
-
- name:
- the name of the group
- title:
- the group title as displayed in --help
- help:
- the group description as displayed in --help
- """
-
- def __init__(self, name, title=None, help=None):
- """Constructs an OptGroup object.
-
- :param name: the group name
- :param title: the group title for --help
- :param help: the group description for --help
- """
- self.name = name
- if title is None:
- self.title = "%s options" % title
- else:
- self.title = title
- self.help = help
-
- self._opts = {} # dict of dicts of (opt:, override:, default:)
- self._argparse_group = None
-
- def _register_opt(self, opt, cli=False):
- """Add an opt to this group.
-
- :param opt: an Opt object
- :param cli: whether this is a CLI option
- :returns: False if previously registered, True otherwise
- :raises: DuplicateOptError if a naming conflict is detected
- """
- if _is_opt_registered(self._opts, opt):
- return False
-
- self._opts[opt.dest] = {'opt': opt, 'cli': cli}
-
- return True
-
- def _unregister_opt(self, opt):
- """Remove an opt from this group.
-
- :param opt: an Opt object
- """
- if opt.dest in self._opts:
- del self._opts[opt.dest]
-
- def _get_argparse_group(self, parser):
- if self._argparse_group is None:
- """Build an argparse._ArgumentGroup for this group."""
- self._argparse_group = parser.add_argument_group(self.title,
- self.help)
- return self._argparse_group
-
- def _clear(self):
- """Clear this group's option parsing state."""
- self._argparse_group = None
-
-
-class ParseError(iniparser.ParseError):
- def __init__(self, msg, lineno, line, filename):
- super(ParseError, self).__init__(msg, lineno, line)
- self.filename = filename
-
- def __str__(self):
- return 'at %s:%d, %s: %r' % (self.filename, self.lineno,
- self.msg, self.line)
-
-
-class ConfigParser(iniparser.BaseParser):
- def __init__(self, filename, sections):
- super(ConfigParser, self).__init__()
- self.filename = filename
- self.sections = sections
- self.section = None
-
- def parse(self):
- with open(self.filename) as f:
- return super(ConfigParser, self).parse(f)
-
- def new_section(self, section):
- self.section = section
- self.sections.setdefault(self.section, {})
-
- def assignment(self, key, value):
- if not self.section:
- raise self.error_no_section()
-
- self.sections[self.section].setdefault(key, [])
- self.sections[self.section][key].append('\n'.join(value))
-
- def parse_exc(self, msg, lineno, line=None):
- return ParseError(msg, lineno, line, self.filename)
-
- def error_no_section(self):
- return self.parse_exc('Section must be started before assignment',
- self.lineno)
-
-
-class MultiConfigParser(object):
- def __init__(self):
- self.parsed = []
-
- def read(self, config_files):
- read_ok = []
-
- for filename in config_files:
- sections = {}
- parser = ConfigParser(filename, sections)
-
- try:
- parser.parse()
- except IOError:
- continue
- self.parsed.insert(0, sections)
- read_ok.append(filename)
-
- return read_ok
-
- def get(self, section, names, multi=False):
- rvalue = []
- for sections in self.parsed:
- if section not in sections:
- continue
- for name in names:
- if name in sections[section]:
- if multi:
- rvalue = sections[section][name] + rvalue
- else:
- return sections[section][name]
- if multi and rvalue != []:
- return rvalue
- raise KeyError
-
-
-class ConfigOpts(collections.Mapping):
-
- """
- Config options which may be set on the command line or in config files.
-
- ConfigOpts is a configuration option manager with APIs for registering
- option schemas, grouping options, parsing option values and retrieving
- the values of options.
- """
-
- def __init__(self):
- """Construct a ConfigOpts object."""
- self._opts = {} # dict of dicts of (opt:, override:, default:)
- self._groups = {}
-
- self._args = None
-
- self._oparser = None
- self._cparser = None
- self._cli_values = {}
- self.__cache = {}
- self._config_opts = []
-
- def _pre_setup(self, project, prog, version, usage, default_config_files):
- """Initialize a ConfigCliParser object for option parsing."""
-
- if prog is None:
- prog = os.path.basename(sys.argv[0])
-
- if default_config_files is None:
- default_config_files = find_config_files(project, prog)
-
- self._oparser = argparse.ArgumentParser(prog=prog, usage=usage)
- self._oparser.add_argument('--version',
- action='version',
- version=version)
-
- return prog, default_config_files
-
- def _setup(self, project, prog, version, usage, default_config_files):
- """Initialize a ConfigOpts object for option parsing."""
-
- self._config_opts = [
- MultiStrOpt('config-file',
- default=default_config_files,
- metavar='PATH',
- help='Path to a config file to use. Multiple config '
- 'files can be specified, with values in later '
- 'files taking precedence. The default files '
- ' used are: %s' % (default_config_files, )),
- StrOpt('config-dir',
- metavar='DIR',
- help='Path to a config directory to pull *.conf '
- 'files from. This file set is sorted, so as to '
- 'provide a predictable parse order if individual '
- 'options are over-ridden. The set is parsed after '
- 'the file(s), if any, specified via --config-file, '
- 'hence over-ridden options in the directory take '
- 'precedence.'),
- ]
- self.register_cli_opts(self._config_opts)
-
- self.project = project
- self.prog = prog
- self.version = version
- self.usage = usage
- self.default_config_files = default_config_files
-
- def __clear_cache(f):
- @functools.wraps(f)
- def __inner(self, *args, **kwargs):
- if kwargs.pop('clear_cache', True):
- self.__cache.clear()
- return f(self, *args, **kwargs)
-
- return __inner
-
- def __call__(self,
- args=None,
- project=None,
- prog=None,
- version=None,
- usage=None,
- default_config_files=None):
- """Parse command line arguments and config files.
-
- Calling a ConfigOpts object causes the supplied command line arguments
- and config files to be parsed, causing opt values to be made available
- as attributes of the object.
-
- The object may be called multiple times, each time causing the previous
- set of values to be overwritten.
-
- Automatically registers the --config-file option with either a supplied
- list of default config files, or a list from find_config_files().
-
- If the --config-dir option is set, any *.conf files from this
- directory are pulled in, after all the file(s) specified by the
- --config-file option.
-
- :param args: command line arguments (defaults to sys.argv[1:])
- :param project: the toplevel project name, used to locate config files
- :param prog: the name of the program (defaults to sys.argv[0] basename)
- :param version: the program version (for --version)
- :param usage: a usage string (%prog will be expanded)
- :param default_config_files: config files to use by default
- :returns: the list of arguments left over after parsing options
- :raises: SystemExit, ConfigFilesNotFoundError, ConfigFileParseError,
- RequiredOptError, DuplicateOptError
- """
-
- self.clear()
-
- prog, default_config_files = self._pre_setup(project,
- prog,
- version,
- usage,
- default_config_files)
-
- self._setup(project, prog, version, usage, default_config_files)
-
- self._cli_values = self._parse_cli_opts(args)
-
- self._parse_config_files()
-
- self._check_required_opts()
-
- def __getattr__(self, name):
- """Look up an option value and perform string substitution.
-
- :param name: the opt name (or 'dest', more precisely)
- :returns: the option value (after string subsititution) or a GroupAttr
- :raises: NoSuchOptError,ConfigFileValueError,TemplateSubstitutionError
- """
- return self._get(name)
-
- def __getitem__(self, key):
- """Look up an option value and perform string substitution."""
- return self.__getattr__(key)
-
- def __contains__(self, key):
- """Return True if key is the name of a registered opt or group."""
- return key in self._opts or key in self._groups
-
- def __iter__(self):
- """Iterate over all registered opt and group names."""
- for key in self._opts.keys() + self._groups.keys():
- yield key
-
- def __len__(self):
- """Return the number of options and option groups."""
- return len(self._opts) + len(self._groups)
-
- def reset(self):
- """Clear the object state and unset overrides and defaults."""
- self._unset_defaults_and_overrides()
- self.clear()
-
- @__clear_cache
- def clear(self):
- """Clear the state of the object to before it was called.
-
- Any subparsers added using the add_cli_subparsers() will also be
- removed as a side-effect of this method.
- """
- self._args = None
- self._cli_values.clear()
- self._oparser = argparse.ArgumentParser()
- self._cparser = None
- self.unregister_opts(self._config_opts)
- for group in self._groups.values():
- group._clear()
-
- @__clear_cache
- def register_opt(self, opt, group=None, cli=False):
- """Register an option schema.
-
- Registering an option schema makes any option value which is previously
- or subsequently parsed from the command line or config files available
- as an attribute of this object.
-
- :param opt: an instance of an Opt sub-class
- :param cli: whether this is a CLI option
- :param group: an optional OptGroup object or group name
- :return: False if the opt was already register, True otherwise
- :raises: DuplicateOptError
- """
- if group is not None:
- group = self._get_group(group, autocreate=True)
- return group._register_opt(opt, cli)
-
- if _is_opt_registered(self._opts, opt):
- return False
-
- self._opts[opt.dest] = {'opt': opt, 'cli': cli}
-
- return True
-
- @__clear_cache
- def register_opts(self, opts, group=None):
- """Register multiple option schemas at once."""
- for opt in opts:
- self.register_opt(opt, group, clear_cache=False)
-
- @__clear_cache
- def register_cli_opt(self, opt, group=None):
- """Register a CLI option schema.
-
- CLI option schemas must be registered before the command line and
- config files are parsed. This is to ensure that all CLI options are
- show in --help and option validation works as expected.
-
- :param opt: an instance of an Opt sub-class
- :param group: an optional OptGroup object or group name
- :return: False if the opt was already register, True otherwise
- :raises: DuplicateOptError, ArgsAlreadyParsedError
- """
- if self._args is not None:
- raise ArgsAlreadyParsedError("cannot register CLI option")
-
- return self.register_opt(opt, group, cli=True, clear_cache=False)
-
- @__clear_cache
- def register_cli_opts(self, opts, group=None):
- """Register multiple CLI option schemas at once."""
- for opt in opts:
- self.register_cli_opt(opt, group, clear_cache=False)
-
- def register_group(self, group):
- """Register an option group.
-
- An option group must be registered before options can be registered
- with the group.
-
- :param group: an OptGroup object
- """
- if group.name in self._groups:
- return
-
- self._groups[group.name] = copy.copy(group)
-
- @__clear_cache
- def unregister_opt(self, opt, group=None):
- """Unregister an option.
-
- :param opt: an Opt object
- :param group: an optional OptGroup object or group name
- :raises: ArgsAlreadyParsedError, NoSuchGroupError
- """
- if self._args is not None:
- raise ArgsAlreadyParsedError("reset before unregistering options")
-
- if group is not None:
- self._get_group(group)._unregister_opt(opt)
- elif opt.dest in self._opts:
- del self._opts[opt.dest]
-
- @__clear_cache
- def unregister_opts(self, opts, group=None):
- """Unregister multiple CLI option schemas at once."""
- for opt in opts:
- self.unregister_opt(opt, group, clear_cache=False)
-
- def import_opt(self, name, module_str, group=None):
- """Import an option definition from a module.
-
- Import a module and check that a given option is registered.
-
- This is intended for use with global configuration objects
- like cfg.CONF where modules commonly register options with
- CONF at module load time. If one module requires an option
- defined by another module it can use this method to explicitly
- declare the dependency.
-
- :param name: the name/dest of the opt
- :param module_str: the name of a module to import
- :param group: an option OptGroup object or group name
- :raises: NoSuchOptError, NoSuchGroupError
- """
- __import__(module_str)
- self._get_opt_info(name, group)
-
- @__clear_cache
- def set_override(self, name, override, group=None):
- """Override an opt value.
-
- Override the command line, config file and default values of a
- given option.
-
- :param name: the name/dest of the opt
- :param override: the override value
- :param group: an option OptGroup object or group name
- :raises: NoSuchOptError, NoSuchGroupError
- """
- opt_info = self._get_opt_info(name, group)
- opt_info['override'] = override
-
- @__clear_cache
- def set_default(self, name, default, group=None):
- """Override an opt's default value.
-
- Override the default value of given option. A command line or
- config file value will still take precedence over this default.
-
- :param name: the name/dest of the opt
- :param default: the default value
- :param group: an option OptGroup object or group name
- :raises: NoSuchOptError, NoSuchGroupError
- """
- opt_info = self._get_opt_info(name, group)
- opt_info['default'] = default
-
- @__clear_cache
- def clear_override(self, name, group=None):
- """Clear an override an opt value.
-
- Clear a previously set override of the command line, config file
- and default values of a given option.
-
- :param name: the name/dest of the opt
- :param group: an option OptGroup object or group name
- :raises: NoSuchOptError, NoSuchGroupError
- """
- opt_info = self._get_opt_info(name, group)
- opt_info.pop('override', None)
-
- @__clear_cache
- def clear_default(self, name, group=None):
- """Clear an override an opt's default value.
-
- Clear a previously set override of the default value of given option.
-
- :param name: the name/dest of the opt
- :param group: an option OptGroup object or group name
- :raises: NoSuchOptError, NoSuchGroupError
- """
- opt_info = self._get_opt_info(name, group)
- opt_info.pop('default', None)
-
- def _all_opt_infos(self):
- """A generator function for iteration opt infos."""
- for info in self._opts.values():
- yield info, None
- for group in self._groups.values():
- for info in group._opts.values():
- yield info, group
-
- def _all_cli_opts(self):
- """A generator function for iterating CLI opts."""
- for info, group in self._all_opt_infos():
- if info['cli']:
- yield info['opt'], group
-
- def _unset_defaults_and_overrides(self):
- """Unset any default or override on all options."""
- for info, group in self._all_opt_infos():
- info.pop('default', None)
- info.pop('override', None)
-
- def find_file(self, name):
- """Locate a file located alongside the config files.
-
- Search for a file with the supplied basename in the directories
- which we have already loaded config files from and other known
- configuration directories.
-
- The directory, if any, supplied by the config_dir option is
- searched first. Then the config_file option is iterated over
- and each of the base directories of the config_files values
- are searched. Failing both of these, the standard directories
- searched by the module level find_config_files() function is
- used. The first matching file is returned.
-
- :param basename: the filename, e.g. 'policy.json'
- :returns: the path to a matching file, or None
- """
- dirs = []
- if self.config_dir:
- dirs.append(_fixpath(self.config_dir))
-
- for cf in reversed(self.config_file):
- dirs.append(os.path.dirname(_fixpath(cf)))
-
- dirs.extend(_get_config_dirs(self.project))
-
- return _search_dirs(dirs, name)
-
- def log_opt_values(self, logger, lvl):
- """Log the value of all registered opts.
-
- It's often useful for an app to log its configuration to a log file at
- startup for debugging. This method dumps to the entire config state to
- the supplied logger at a given log level.
-
- :param logger: a logging.Logger object
- :param lvl: the log level (e.g. logging.DEBUG) arg to logger.log()
- """
- logger.log(lvl, "*" * 80)
- logger.log(lvl, "Configuration options gathered from:")
- logger.log(lvl, "command line args: %s", self._args)
- logger.log(lvl, "config files: %s", self.config_file)
- logger.log(lvl, "=" * 80)
-
- def _sanitize(opt, value):
- """Obfuscate values of options declared secret"""
- return value if not opt.secret else '*' * len(str(value))
-
- for opt_name in sorted(self._opts):
- opt = self._get_opt_info(opt_name)['opt']
- logger.log(lvl, "%-30s = %s", opt_name,
- _sanitize(opt, getattr(self, opt_name)))
-
- for group_name in self._groups:
- group_attr = self.GroupAttr(self, self._get_group(group_name))
- for opt_name in sorted(self._groups[group_name]._opts):
- opt = self._get_opt_info(opt_name, group_name)['opt']
- logger.log(lvl, "%-30s = %s",
- "%s.%s" % (group_name, opt_name),
- _sanitize(opt, getattr(group_attr, opt_name)))
-
- logger.log(lvl, "*" * 80)
-
- def print_usage(self, file=None):
- """Print the usage message for the current program."""
- self._oparser.print_usage(file)
-
- def print_help(self, file=None):
- """Print the help message for the current program."""
- self._oparser.print_help(file)
-
- def _get(self, name, group=None):
- if isinstance(group, OptGroup):
- key = (group.name, name)
- else:
- key = (group, name)
- try:
- return self.__cache[key]
- except KeyError:
- value = self._substitute(self._do_get(name, group))
- self.__cache[key] = value
- return value
-
- def _do_get(self, name, group=None):
- """Look up an option value.
-
- :param name: the opt name (or 'dest', more precisely)
- :param group: an OptGroup
- :returns: the option value, or a GroupAttr object
- :raises: NoSuchOptError, NoSuchGroupError, ConfigFileValueError,
- TemplateSubstitutionError
- """
- if group is None and name in self._groups:
- return self.GroupAttr(self, self._get_group(name))
-
- info = self._get_opt_info(name, group)
- opt = info['opt']
-
- if isinstance(opt, SubCommandOpt):
- return self.SubCommandAttr(self, group, opt.dest)
-
- if 'override' in info:
- return info['override']
-
- values = []
- if self._cparser is not None:
- section = group.name if group is not None else 'DEFAULT'
- try:
- value = opt._get_from_config_parser(self._cparser, section)
- except KeyError:
- pass
- except ValueError as ve:
- raise ConfigFileValueError(str(ve))
- else:
- if not opt.multi:
- # No need to continue since the last value wins
- return value[-1]
- values.extend(value)
-
- name = name if group is None else group.name + '_' + name
- value = self._cli_values.get(name)
- if value is not None:
- if not opt.multi:
- return value
-
- # argparse ignores default=None for nargs='*'
- if opt.positional and not value:
- value = opt.default
-
- return value + values
-
- if values:
- return values
-
- if 'default' in info:
- return info['default']
-
- return opt.default
-
- def _substitute(self, value):
- """Perform string template substitution.
-
- Substitute any template variables (e.g. $foo, ${bar}) in the supplied
- string value(s) with opt values.
-
- :param value: the string value, or list of string values
- :returns: the substituted string(s)
- """
- if isinstance(value, list):
- return [self._substitute(i) for i in value]
- elif isinstance(value, str):
- tmpl = string.Template(value)
- return tmpl.safe_substitute(self.StrSubWrapper(self))
- else:
- return value
-
- def _get_group(self, group_or_name, autocreate=False):
- """Looks up a OptGroup object.
-
- Helper function to return an OptGroup given a parameter which can
- either be the group's name or an OptGroup object.
-
- The OptGroup object returned is from the internal dict of OptGroup
- objects, which will be a copy of any OptGroup object that users of
- the API have access to.
-
- :param group_or_name: the group's name or the OptGroup object itself
- :param autocreate: whether to auto-create the group if it's not found
- :raises: NoSuchGroupError
- """
- group = group_or_name if isinstance(group_or_name, OptGroup) else None
- group_name = group.name if group else group_or_name
-
- if group_name not in self._groups:
- if group is not None or not autocreate:
- raise NoSuchGroupError(group_name)
-
- self.register_group(OptGroup(name=group_name))
-
- return self._groups[group_name]
-
- def _get_opt_info(self, opt_name, group=None):
- """Return the (opt, override, default) dict for an opt.
-
- :param opt_name: an opt name/dest
- :param group: an optional group name or OptGroup object
- :raises: NoSuchOptError, NoSuchGroupError
- """
- if group is None:
- opts = self._opts
- else:
- group = self._get_group(group)
- opts = group._opts
-
- if opt_name not in opts:
- raise NoSuchOptError(opt_name, group)
-
- return opts[opt_name]
-
- def _parse_config_files(self):
- """Parse the config files from --config-file and --config-dir.
-
- :raises: ConfigFilesNotFoundError, ConfigFileParseError
- """
- config_files = list(self.config_file)
-
- if self.config_dir:
- config_dir_glob = os.path.join(self.config_dir, '*.conf')
- config_files += sorted(glob.glob(config_dir_glob))
-
- config_files = [_fixpath(p) for p in config_files]
-
- self._cparser = MultiConfigParser()
-
- try:
- read_ok = self._cparser.read(config_files)
- except iniparser.ParseError as pe:
- raise ConfigFileParseError(pe.filename, str(pe))
-
- if read_ok != config_files:
- not_read_ok = filter(lambda f: f not in read_ok, config_files)
- raise ConfigFilesNotFoundError(not_read_ok)
-
- def _check_required_opts(self):
- """Check that all opts marked as required have values specified.
-
- :raises: RequiredOptError
- """
- for info, group in self._all_opt_infos():
- opt = info['opt']
-
- if opt.required:
- if 'default' in info or 'override' in info:
- continue
-
- if self._get(opt.dest, group) is None:
- raise RequiredOptError(opt.name, group)
-
- def _parse_cli_opts(self, args):
- """Parse command line options.
-
- Initializes the command line option parser and parses the supplied
- command line arguments.
-
- :param args: the command line arguments
- :returns: a dict of parsed option values
- :raises: SystemExit, DuplicateOptError
-
- """
- self._args = args
-
- for opt, group in self._all_cli_opts():
- opt._add_to_cli(self._oparser, group)
-
- return vars(self._oparser.parse_args(args))
-
- class GroupAttr(collections.Mapping):
-
- """
- A helper class representing the option values of a group as a mapping
- and attributes.
- """
-
- def __init__(self, conf, group):
- """Construct a GroupAttr object.
-
- :param conf: a ConfigOpts object
- :param group: an OptGroup object
- """
- self._conf = conf
- self._group = group
-
- def __getattr__(self, name):
- """Look up an option value and perform template substitution."""
- return self._conf._get(name, self._group)
-
- def __getitem__(self, key):
- """Look up an option value and perform string substitution."""
- return self.__getattr__(key)
-
- def __contains__(self, key):
- """Return True if key is the name of a registered opt or group."""
- return key in self._group._opts
-
- def __iter__(self):
- """Iterate over all registered opt and group names."""
- for key in self._group._opts.keys():
- yield key
-
- def __len__(self):
- """Return the number of options and option groups."""
- return len(self._group._opts)
-
- class SubCommandAttr(object):
-
- """
- A helper class representing the name and arguments of an argparse
- sub-parser.
- """
-
- def __init__(self, conf, group, dest):
- """Construct a SubCommandAttr object.
-
- :param conf: a ConfigOpts object
- :param group: an OptGroup object
- :param dest: the name of the sub-parser
- """
- self._conf = conf
- self._group = group
- self._dest = dest
-
- def __getattr__(self, name):
- """Look up a sub-parser name or argument value."""
- if name == 'name':
- name = self._dest
- if self._group is not None:
- name = self._group.name + '_' + name
- return self._conf._cli_values[name]
-
- if name in self._conf:
- raise DuplicateOptError(name)
-
- try:
- return self._conf._cli_values[name]
- except KeyError:
- raise NoSuchOptError(name)
-
- class StrSubWrapper(object):
-
- """
- A helper class exposing opt values as a dict for string substitution.
- """
-
- def __init__(self, conf):
- """Construct a StrSubWrapper object.
-
- :param conf: a ConfigOpts object
- """
- self.conf = conf
-
- def __getitem__(self, key):
- """Look up an opt value from the ConfigOpts object.
-
- :param key: an opt name
- :returns: an opt value
- :raises: TemplateSubstitutionError if attribute is a group
- """
- value = getattr(self.conf, key)
- if isinstance(value, self.conf.GroupAttr):
- raise TemplateSubstitutionError(
- 'substituting group %s not supported' % key)
- return value
-
-
-CONF = ConfigOpts()
diff --git a/openstackclient/openstack/common/gettextutils.py b/openstackclient/openstack/common/gettextutils.py
index d4c93f4..2b15854 100644
--- a/openstackclient/openstack/common/gettextutils.py
+++ b/openstackclient/openstack/common/gettextutils.py
@@ -1,5 +1,3 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
# Copyright 2012 Red Hat, Inc.
# Copyright 2013 IBM Corp.
# All Rights Reserved.
@@ -25,14 +23,12 @@ Usual usage in an openstack.common module:
"""
import copy
+import functools
import gettext
-import logging
+import locale
+from logging import handlers
import os
import re
-try:
- import UserString as _userString
-except ImportError:
- import collections as _userString
from babel import localedata
import six
@@ -40,6 +36,17 @@ import six
_localedir = os.environ.get('openstackclient'.upper() + '_LOCALEDIR')
_t = gettext.translation('openstackclient', localedir=_localedir, fallback=True)
+# We use separate translation catalogs for each log level, so set up a
+# mapping between the log level name and the translator. The domain
+# for the log level is project_name + "-log-" + log_level so messages
+# for each level end up in their own catalog.
+_t_log_levels = dict(
+ (level, gettext.translation('openstackclient' + '-log-' + level,
+ localedir=_localedir,
+ fallback=True))
+ for level in ['info', 'warning', 'error', 'critical']
+)
+
_AVAILABLE_LANGUAGES = {}
USE_LAZY = False
@@ -58,13 +65,35 @@ def enable_lazy():
def _(msg):
if USE_LAZY:
- return Message(msg, 'openstackclient')
+ return Message(msg, domain='openstackclient')
else:
if six.PY3:
return _t.gettext(msg)
return _t.ugettext(msg)
+def _log_translation(msg, level):
+ """Build a single translation of a log message
+ """
+ if USE_LAZY:
+ return Message(msg, domain='openstackclient' + '-log-' + level)
+ else:
+ translator = _t_log_levels[level]
+ if six.PY3:
+ return translator.gettext(msg)
+ return translator.ugettext(msg)
+
+# Translators for log levels.
+#
+# The abbreviated names are meant to reflect the usual use of a short
+# name like '_'. The "L" is for "log" and the other letter comes from
+# the level.
+_LI = functools.partial(_log_translation, level='info')
+_LW = functools.partial(_log_translation, level='warning')
+_LE = functools.partial(_log_translation, level='error')
+_LC = functools.partial(_log_translation, level='critical')
+
+
def install(domain, lazy=False):
"""Install a _() function using the given translation domain.
@@ -90,11 +119,6 @@ def install(domain, lazy=False):
# messages in OpenStack. We override the standard _() function
# and % (format string) operation to build Message objects that can
# later be translated when we have more information.
- #
- # Also included below is an example LocaleHandler that translates
- # Messages to an associated locale, effectively allowing many logs,
- # each with their own locale.
-
def _lazy_gettext(msg):
"""Create and return a Message object.
@@ -105,7 +129,7 @@ def install(domain, lazy=False):
Message encapsulates a string so that we can translate
it later when needed.
"""
- return Message(msg, domain)
+ return Message(msg, domain=domain)
from six import moves
moves.builtins.__dict__['_'] = _lazy_gettext
@@ -120,182 +144,169 @@ def install(domain, lazy=False):
unicode=True)
-class Message(_userString.UserString, object):
- """Class used to encapsulate translatable messages."""
- def __init__(self, msg, domain):
- # _msg is the gettext msgid and should never change
- self._msg = msg
- self._left_extra_msg = ''
- self._right_extra_msg = ''
- self._locale = None
- self.params = None
- self.domain = domain
-
- @property
- def data(self):
- # NOTE(mrodden): this should always resolve to a unicode string
- # that best represents the state of the message currently
-
- localedir = os.environ.get(self.domain.upper() + '_LOCALEDIR')
- if self.locale:
- lang = gettext.translation(self.domain,
- localedir=localedir,
- languages=[self.locale],
- fallback=True)
- else:
- # use system locale for translations
- lang = gettext.translation(self.domain,
- localedir=localedir,
- fallback=True)
+class Message(six.text_type):
+ """A Message object is a unicode object that can be translated.
+
+ Translation of Message is done explicitly using the translate() method.
+ For all non-translation intents and purposes, a Message is simply unicode,
+ and can be treated as such.
+ """
+ def __new__(cls, msgid, msgtext=None, params=None,
+ domain='openstackclient', *args):
+ """Create a new Message object.
+
+ In order for translation to work gettext requires a message ID, this
+ msgid will be used as the base unicode text. It is also possible
+ for the msgid and the base unicode text to be different by passing
+ the msgtext parameter.
+ """
+ # If the base msgtext is not given, we use the default translation
+ # of the msgid (which is in English) just in case the system locale is
+ # not English, so that the base text will be in that locale by default.
+ if not msgtext:
+ msgtext = Message._translate_msgid(msgid, domain)
+ # We want to initialize the parent unicode with the actual object that
+ # would have been plain unicode if 'Message' was not enabled.
+ msg = super(Message, cls).__new__(cls, msgtext)
+ msg.msgid = msgid
+ msg.domain = domain
+ msg.params = params
+ return msg
+
+ def translate(self, desired_locale=None):
+ """Translate this message to the desired locale.
+
+ :param desired_locale: The desired locale to translate the message to,
+ if no locale is provided the message will be
+ translated to the system's default locale.
+
+ :returns: the translated message in unicode
+ """
+
+ translated_message = Message._translate_msgid(self.msgid,
+ self.domain,
+ desired_locale)
+ if self.params is None:
+ # No need for more translation
+ return translated_message
+
+ # This Message object may have been formatted with one or more
+ # Message objects as substitution arguments, given either as a single
+ # argument, part of a tuple, or as one or more values in a dictionary.
+ # When translating this Message we need to translate those Messages too
+ translated_params = _translate_args(self.params, desired_locale)
+
+ translated_message = translated_message % translated_params
+
+ return translated_message
+
+ @staticmethod
+ def _translate_msgid(msgid, domain, desired_locale=None):
+ if not desired_locale:
+ system_locale = locale.getdefaultlocale()
+ # If the system locale is not available to the runtime use English
+ if not system_locale[0]:
+ desired_locale = 'en_US'
+ else:
+ desired_locale = system_locale[0]
+
+ locale_dir = os.environ.get(domain.upper() + '_LOCALEDIR')
+ lang = gettext.translation(domain,
+ localedir=locale_dir,
+ languages=[desired_locale],
+ fallback=True)
if six.PY3:
- ugettext = lang.gettext
+ translator = lang.gettext
else:
- ugettext = lang.ugettext
-
- full_msg = (self._left_extra_msg +
- ugettext(self._msg) +
- self._right_extra_msg)
-
- if self.params is not None:
- full_msg = full_msg % self.params
-
- return six.text_type(full_msg)
-
- @property
- def locale(self):
- return self._locale
-
- @locale.setter
- def locale(self, value):
- self._locale = value
- if not self.params:
- return
-
- # This Message object may have been constructed with one or more
- # Message objects as substitution parameters, given as a single
- # Message, or a tuple or Map containing some, so when setting the
- # locale for this Message we need to set it for those Messages too.
- if isinstance(self.params, Message):
- self.params.locale = value
- return
- if isinstance(self.params, tuple):
- for param in self.params:
- if isinstance(param, Message):
- param.locale = value
- return
- if isinstance(self.params, dict):
- for param in self.params.values():
- if isinstance(param, Message):
- param.locale = value
-
- def _save_dictionary_parameter(self, dict_param):
- full_msg = self.data
- # look for %(blah) fields in string;
- # ignore %% and deal with the
- # case where % is first character on the line
- keys = re.findall('(?:[^%]|^)?%\((\w*)\)[a-z]', full_msg)
-
- # if we don't find any %(blah) blocks but have a %s
- if not keys and re.findall('(?:[^%]|^)%[a-z]', full_msg):
- # apparently the full dictionary is the parameter
- params = copy.deepcopy(dict_param)
- else:
- params = {}
- for key in keys:
- try:
- params[key] = copy.deepcopy(dict_param[key])
- except TypeError:
- # cast uncopyable thing to unicode string
- params[key] = six.text_type(dict_param[key])
+ translator = lang.ugettext
- return params
+ translated_message = translator(msgid)
+ return translated_message
- def _save_parameters(self, other):
- # we check for None later to see if
- # we actually have parameters to inject,
- # so encapsulate if our parameter is actually None
+ def __mod__(self, other):
+ # When we mod a Message we want the actual operation to be performed
+ # by the parent class (i.e. unicode()), the only thing we do here is
+ # save the original msgid and the parameters in case of a translation
+ params = self._sanitize_mod_params(other)
+ unicode_mod = super(Message, self).__mod__(params)
+ modded = Message(self.msgid,
+ msgtext=unicode_mod,
+ params=params,
+ domain=self.domain)
+ return modded
+
+ def _sanitize_mod_params(self, other):
+ """Sanitize the object being modded with this Message.
+
+ - Add support for modding 'None' so translation supports it
+ - Trim the modded object, which can be a large dictionary, to only
+ those keys that would actually be used in a translation
+ - Snapshot the object being modded, in case the message is
+ translated, it will be used as it was when the Message was created
+ """
if other is None:
- self.params = (other, )
+ params = (other,)
elif isinstance(other, dict):
- self.params = self._save_dictionary_parameter(other)
+ params = self._trim_dictionary_parameters(other)
else:
- # fallback to casting to unicode,
- # this will handle the problematic python code-like
- # objects that cannot be deep-copied
- try:
- self.params = copy.deepcopy(other)
- except TypeError:
- self.params = six.text_type(other)
-
- return self
-
- # overrides to be more string-like
- def __unicode__(self):
- return self.data
-
- def __str__(self):
- if six.PY3:
- return self.__unicode__()
- return self.data.encode('utf-8')
+ params = self._copy_param(other)
+ return params
- def __getstate__(self):
- to_copy = ['_msg', '_right_extra_msg', '_left_extra_msg',
- 'domain', 'params', '_locale']
- new_dict = self.__dict__.fromkeys(to_copy)
- for attr in to_copy:
- new_dict[attr] = copy.deepcopy(self.__dict__[attr])
+ def _trim_dictionary_parameters(self, dict_param):
+ """Return a dict that only has matching entries in the msgid."""
+ # NOTE(luisg): Here we trim down the dictionary passed as parameters
+ # to avoid carrying a lot of unnecessary weight around in the message
+ # object, for example if someone passes in Message() % locals() but
+ # only some params are used, and additionally we prevent errors for
+ # non-deepcopyable objects by unicoding() them.
+
+ # Look for %(param) keys in msgid;
+ # Skip %% and deal with the case where % is first character on the line
+ keys = re.findall('(?:[^%]|^)?%\((\w*)\)[a-z]', self.msgid)
+
+ # If we don't find any %(param) keys but have a %s
+ if not keys and re.findall('(?:[^%]|^)%[a-z]', self.msgid):
+ # Apparently the full dictionary is the parameter
+ params = self._copy_param(dict_param)
+ else:
+ params = {}
+ # Save our existing parameters as defaults to protect
+ # ourselves from losing values if we are called through an
+ # (erroneous) chain that builds a valid Message with
+ # arguments, and then does something like "msg % kwds"
+ # where kwds is an empty dictionary.
+ src = {}
+ if isinstance(self.params, dict):
+ src.update(self.params)
+ src.update(dict_param)
+ for key in keys:
+ params[key] = self._copy_param(src[key])
- return new_dict
+ return params
- def __setstate__(self, state):
- for (k, v) in state.items():
- setattr(self, k, v)
+ def _copy_param(self, param):
+ try:
+ return copy.deepcopy(param)
+ except TypeError:
+ # Fallback to casting to unicode this will handle the
+ # python code-like objects that can't be deep-copied
+ return six.text_type(param)
- # operator overloads
def __add__(self, other):
- copied = copy.deepcopy(self)
- copied._right_extra_msg += other.__str__()
- return copied
+ msg = _('Message objects do not support addition.')
+ raise TypeError(msg)
def __radd__(self, other):
- copied = copy.deepcopy(self)
- copied._left_extra_msg += other.__str__()
- return copied
+ return self.__add__(other)
- def __mod__(self, other):
- # do a format string to catch and raise
- # any possible KeyErrors from missing parameters
- self.data % other
- copied = copy.deepcopy(self)
- return copied._save_parameters(other)
-
- def __mul__(self, other):
- return self.data * other
-
- def __rmul__(self, other):
- return other * self.data
-
- def __getitem__(self, key):
- return self.data[key]
-
- def __getslice__(self, start, end):
- return self.data.__getslice__(start, end)
-
- def __getattribute__(self, name):
- # NOTE(mrodden): handle lossy operations that we can't deal with yet
- # These override the UserString implementation, since UserString
- # uses our __class__ attribute to try and build a new message
- # after running the inner data string through the operation.
- # At that point, we have lost the gettext message id and can just
- # safely resolve to a string instead.
- ops = ['capitalize', 'center', 'decode', 'encode',
- 'expandtabs', 'ljust', 'lstrip', 'replace', 'rjust', 'rstrip',
- 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
- if name in ops:
- return getattr(self.data, name)
- else:
- return _userString.UserString.__getattribute__(self, name)
+ def __str__(self):
+ # NOTE(luisg): Logging in python 2.6 tries to str() log records,
+ # and it expects specifically a UnicodeError in order to proceed.
+ msg = _('Message objects do not support str() because they may '
+ 'contain non-ascii characters. '
+ 'Please use unicode() or translate() instead.')
+ raise UnicodeError(msg)
def get_available_languages(domain):
@@ -317,49 +328,147 @@ def get_available_languages(domain):
# NOTE(luisg): Babel <1.0 used a function called list(), which was
# renamed to locale_identifiers() in >=1.0, the requirements master list
# requires >=0.9.6, uncapped, so defensively work with both. We can remove
- # this check when the master list updates to >=1.0, and all projects udpate
+ # this check when the master list updates to >=1.0, and update all projects
list_identifiers = (getattr(localedata, 'list', None) or
getattr(localedata, 'locale_identifiers'))
locale_identifiers = list_identifiers()
+
for i in locale_identifiers:
if find(i) is not None:
language_list.append(i)
+
+ # NOTE(luisg): Babel>=1.0,<1.3 has a bug where some OpenStack supported
+ # locales (e.g. 'zh_CN', and 'zh_TW') aren't supported even though they
+ # are perfectly legitimate locales:
+ # https://github.com/mitsuhiko/babel/issues/37
+ # In Babel 1.3 they fixed the bug and they support these locales, but
+ # they are still not explicitly "listed" by locale_identifiers().
+ # That is why we add the locales here explicitly if necessary so that
+ # they are listed as supported.
+ aliases = {'zh': 'zh_CN',
+ 'zh_Hant_HK': 'zh_HK',
+ 'zh_Hant': 'zh_TW',
+ 'fil': 'tl_PH'}
+ for (locale, alias) in six.iteritems(aliases):
+ if locale in language_list and alias not in language_list:
+ language_list.append(alias)
+
_AVAILABLE_LANGUAGES[domain] = language_list
return copy.copy(language_list)
-def get_localized_message(message, user_locale):
- """Gets a localized version of the given message in the given locale."""
+def translate(obj, desired_locale=None):
+ """Gets the translated unicode representation of the given object.
+
+ If the object is not translatable it is returned as-is.
+ If the locale is None the object is translated to the system locale.
+
+ :param obj: the object to translate
+ :param desired_locale: the locale to translate the message to, if None the
+ default system locale will be used
+ :returns: the translated object in unicode, or the original object if
+ it could not be translated
+ """
+ message = obj
+ if not isinstance(message, Message):
+ # If the object to translate is not already translatable,
+ # let's first get its unicode representation
+ message = six.text_type(obj)
if isinstance(message, Message):
- if user_locale:
- message.locale = user_locale
- return six.text_type(message)
- else:
- return message
+ # Even after unicoding() we still need to check if we are
+ # running with translatable unicode before translating
+ return message.translate(desired_locale)
+ return obj
+
+
+def _translate_args(args, desired_locale=None):
+ """Translates all the translatable elements of the given arguments object.
+
+ This method is used for translating the translatable values in method
+ arguments which include values of tuples or dictionaries.
+ If the object is not a tuple or a dictionary the object itself is
+ translated if it is translatable.
+
+ If the locale is None the object is translated to the system locale.
+
+ :param args: the args to translate
+ :param desired_locale: the locale to translate the args to, if None the
+ default system locale will be used
+ :returns: a new args object with the translated contents of the original
+ """
+ if isinstance(args, tuple):
+ return tuple(translate(v, desired_locale) for v in args)
+ if isinstance(args, dict):
+ translated_dict = {}
+ for (k, v) in six.iteritems(args):
+ translated_v = translate(v, desired_locale)
+ translated_dict[k] = translated_v
+ return translated_dict
+ return translate(args, desired_locale)
+
+
+class TranslationHandler(handlers.MemoryHandler):
+ """Handler that translates records before logging them.
+
+ The TranslationHandler takes a locale and a target logging.Handler object
+ to forward LogRecord objects to after translating them. This handler
+ depends on Message objects being logged, instead of regular strings.
+ The handler can be configured declaratively in the logging.conf as follows:
-class LocaleHandler(logging.Handler):
- """Handler that can have a locale associated to translate Messages.
+ [handlers]
+ keys = translatedlog, translator
- A quick example of how to utilize the Message class above.
- LocaleHandler takes a locale and a target logging.Handler object
- to forward LogRecord objects to after translating the internal Message.
+ [handler_translatedlog]
+ class = handlers.WatchedFileHandler
+ args = ('/var/log/api-localized.log',)
+ formatter = context
+
+ [handler_translator]
+ class = openstack.common.log.TranslationHandler
+ target = translatedlog
+ args = ('zh_CN',)
+
+ If the specified locale is not available in the system, the handler will
+ log in the default locale.
"""
- def __init__(self, locale, target):
- """Initialize a LocaleHandler
+ def __init__(self, locale=None, target=None):
+ """Initialize a TranslationHandler
:param locale: locale to use for translating messages
:param target: logging.Handler object to forward
LogRecord objects to after translation
"""
- logging.Handler.__init__(self)
+ # NOTE(luisg): In order to allow this handler to be a wrapper for
+ # other handlers, such as a FileHandler, and still be able to
+ # configure it using logging.conf, this handler has to extend
+ # MemoryHandler because only the MemoryHandlers' logging.conf
+ # parsing is implemented such that it accepts a target handler.
+ handlers.MemoryHandler.__init__(self, capacity=0, target=target)
self.locale = locale
- self.target = target
+
+ def setFormatter(self, fmt):
+ self.target.setFormatter(fmt)
def emit(self, record):
- if isinstance(record.msg, Message):
- # set the locale and resolve to a string
- record.msg.locale = self.locale
+ # We save the message from the original record to restore it
+ # after translation, so other handlers are not affected by this
+ original_msg = record.msg
+ original_args = record.args
+
+ try:
+ self._translate_and_log_record(record)
+ finally:
+ record.msg = original_msg
+ record.args = original_args
+
+ def _translate_and_log_record(self, record):
+ record.msg = translate(record.msg, self.locale)
+
+ # In addition to translating the message, we also need to translate
+ # arguments that were passed to the log method that were not part
+ # of the main message e.g., log.info(_('Some message %s'), this_one))
+ record.args = _translate_args(record.args, self.locale)
self.target.emit(record)
diff --git a/openstackclient/openstack/common/strutils.py b/openstackclient/openstack/common/strutils.py
index e3f26a7..33fca54 100644
--- a/openstackclient/openstack/common/strutils.py
+++ b/openstackclient/openstack/common/strutils.py
@@ -1,5 +1,3 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
# Copyright 2011 OpenStack Foundation.
# All Rights Reserved.
#
@@ -19,25 +17,31 @@
System-level utilities and helper functions.
"""
+import math
import re
import sys
import unicodedata
import six
-from openstackclient.openstack.common.gettextutils import _ # noqa
+from openstackclient.openstack.common.gettextutils import _
-# Used for looking up extensions of text
-# to their 'multiplied' byte amount
-BYTE_MULTIPLIERS = {
- '': 1,
- 't': 1024 ** 4,
- 'g': 1024 ** 3,
- 'm': 1024 ** 2,
- 'k': 1024,
+UNIT_PREFIX_EXPONENT = {
+ 'k': 1,
+ 'K': 1,
+ 'Ki': 1,
+ 'M': 2,
+ 'Mi': 2,
+ 'G': 3,
+ 'Gi': 3,
+ 'T': 4,
+ 'Ti': 4,
+}
+UNIT_SYSTEM_INFO = {
+ 'IEC': (1024, re.compile(r'(^[-+]?\d*\.?\d+)([KMGT]i?)?(b|bit|B)$')),
+ 'SI': (1000, re.compile(r'(^[-+]?\d*\.?\d+)([kMGT])?(b|bit|B)$')),
}
-BYTE_REGEX = re.compile(r'(^-?\d+)(\D*)')
TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes')
FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no')
@@ -60,12 +64,12 @@ def int_from_bool_as_string(subject):
return bool_from_string(subject) and 1 or 0
-def bool_from_string(subject, strict=False):
+def bool_from_string(subject, strict=False, default=False):
"""Interpret a string as a boolean.
A case-insensitive match is performed such that strings matching 't',
'true', 'on', 'y', 'yes', or '1' are considered True and, when
- `strict=False`, anything else is considered False.
+ `strict=False`, anything else returns the value specified by 'default'.
Useful for JSON-decoded stuff and config file parsing.
@@ -90,7 +94,7 @@ def bool_from_string(subject, strict=False):
'acceptable': acceptable}
raise ValueError(msg)
else:
- return False
+ return default
def safe_decode(text, incoming=None, errors='strict'):
@@ -101,7 +105,7 @@ def safe_decode(text, incoming=None, errors='strict'):
values http://docs.python.org/2/library/codecs.html
:returns: text or a unicode `incoming` encoded
representation of it.
- :raises TypeError: If text is not an isntance of str
+ :raises TypeError: If text is not an instance of str
"""
if not isinstance(text, six.string_types):
raise TypeError("%s can't be decoded" % type(text))
@@ -144,7 +148,7 @@ def safe_encode(text, incoming=None,
values http://docs.python.org/2/library/codecs.html
:returns: text or a bytestring `encoding` encoded
representation of it.
- :raises TypeError: If text is not an isntance of str
+ :raises TypeError: If text is not an instance of str
"""
if not isinstance(text, six.string_types):
raise TypeError("%s can't be encoded" % type(text))
@@ -154,43 +158,65 @@ def safe_encode(text, incoming=None,
sys.getdefaultencoding())
if isinstance(text, six.text_type):
- return text.encode(encoding, errors)
+ if six.PY3:
+ return text.encode(encoding, errors).decode(incoming)
+ else:
+ return text.encode(encoding, errors)
elif text and encoding != incoming:
# Decode text before encoding it with `encoding`
text = safe_decode(text, incoming, errors)
- return text.encode(encoding, errors)
+ if six.PY3:
+ return text.encode(encoding, errors).decode(incoming)
+ else:
+ return text.encode(encoding, errors)
return text
-def to_bytes(text, default=0):
- """Converts a string into an integer of bytes.
+def string_to_bytes(text, unit_system='IEC', return_int=False):
+ """Converts a string into an float representation of bytes.
+
+ The units supported for IEC ::
+
+ Kb(it), Kib(it), Mb(it), Mib(it), Gb(it), Gib(it), Tb(it), Tib(it)
+ KB, KiB, MB, MiB, GB, GiB, TB, TiB
- Looks at the last characters of the text to determine
- what conversion is needed to turn the input text into a byte number.
- Supports "B, K(B), M(B), G(B), and T(B)". (case insensitive)
+ The units supported for SI ::
+
+ kb(it), Mb(it), Gb(it), Tb(it)
+ kB, MB, GB, TB
+
+ Note that the SI unit system does not support capital letter 'K'
:param text: String input for bytes size conversion.
- :param default: Default return value when text is blank.
+ :param unit_system: Unit system for byte size conversion.
+ :param return_int: If True, returns integer representation of text
+ in bytes. (default: decimal)
+ :returns: Numerical representation of text in bytes.
+ :raises ValueError: If text has an invalid value.
"""
- match = BYTE_REGEX.search(text)
+ try:
+ base, reg_ex = UNIT_SYSTEM_INFO[unit_system]
+ except KeyError:
+ msg = _('Invalid unit system: "%s"') % unit_system
+ raise ValueError(msg)
+ match = reg_ex.match(text)
if match:
- magnitude = int(match.group(1))
- mult_key_org = match.group(2)
- if not mult_key_org:
- return magnitude
- elif text:
+ magnitude = float(match.group(1))
+ unit_prefix = match.group(2)
+ if match.group(3) in ['b', 'bit']:
+ magnitude /= 8
+ else:
msg = _('Invalid string format: %s') % text
- raise TypeError(msg)
+ raise ValueError(msg)
+ if not unit_prefix:
+ res = magnitude
else:
- return default
- mult_key = mult_key_org.lower().replace('b', '', 1)
- multiplier = BYTE_MULTIPLIERS.get(mult_key)
- if multiplier is None:
- msg = _('Unknown byte multiplier: %s') % mult_key_org
- raise TypeError(msg)
- return magnitude * multiplier
+ res = magnitude * pow(base, UNIT_PREFIX_EXPONENT[unit_prefix])
+ if return_int:
+ return int(math.ceil(res))
+ return res
def to_slug(value, incoming=None, errors="strict"):
diff --git a/openstackclient/shell.py b/openstackclient/shell.py
index 73766a6..67eaca5 100644
--- a/openstackclient/shell.py
+++ b/openstackclient/shell.py
@@ -24,6 +24,7 @@ import traceback
from cliff import app
from cliff import command
+from cliff import complete
from cliff import help
import openstackclient
@@ -64,8 +65,10 @@ class OpenStackShell(app.App):
def __init__(self):
# Patch command.Command to add a default auth_required = True
command.Command.auth_required = True
+ command.Command.best_effort = False
# But not help
help.HelpCommand.auth_required = False
+ complete.CompleteCommand.best_effort = True
super(OpenStackShell, self).__init__(
description=__doc__.strip(),
@@ -117,9 +120,53 @@ 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 configure_logging(self):
+ """Configure logging for the app
+
+ Cliff sets some defaults we don't want so re-work it a bit
+ """
+
+ if self.options.debug:
+ # --debug forces verbose_level 3
+ # Set this here so cliff.app.configure_logging() can work
+ self.options.verbose_level = 3
+
+ super(OpenStackShell, self).configure_logging()
+ root_logger = logging.getLogger('')
+
+ # Requests logs some stuff at INFO that we don't want
+ # unless we have DEBUG
+ requests_log = logging.getLogger("requests")
+ requests_log.setLevel(logging.ERROR)
+
+ # Other modules we don't want DEBUG output for so
+ # don't reset them below
+ iso8601_log = logging.getLogger("iso8601")
+ iso8601_log.setLevel(logging.ERROR)
+
+ # Set logging to the requested level
+ self.dump_stack_trace = False
+ if self.options.verbose_level == 0:
+ # --quiet
+ root_logger.setLevel(logging.ERROR)
+ elif self.options.verbose_level == 1:
+ # This is the default case, no --debug, --verbose or --quiet
+ root_logger.setLevel(logging.WARNING)
+ elif self.options.verbose_level == 2:
+ # One --verbose
+ root_logger.setLevel(logging.INFO)
+ elif self.options.verbose_level >= 3:
+ # Two or more --verbose
+ root_logger.setLevel(logging.DEBUG)
+ requests_log.setLevel(logging.DEBUG)
+
+ if self.options.debug:
+ # --debug forces traceback
+ self.dump_stack_trace = True
+
def run(self, argv):
try:
return super(OpenStackShell, self).run(argv)
@@ -144,10 +191,25 @@ class OpenStackShell(app.App):
default=env('OS_AUTH_URL'),
help='Authentication URL (Env: OS_AUTH_URL)')
parser.add_argument(
+ '--os-domain-name',
+ metavar='<auth-domain-name>',
+ default=env('OS_DOMAIN_NAME'),
+ 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 '
+ '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='Authentication project name (Env: OS_PROJECT_NAME)',
+ help='Project name of the requested project-level '
+ 'authorization scope (Env: OS_PROJECT_NAME)',
)
parser.add_argument(
'--os-tenant-name',
@@ -159,7 +221,8 @@ class OpenStackShell(app.App):
'--os-project-id',
metavar='<auth-project-id>',
default=env('OS_PROJECT_ID', default=env('OS_TENANT_ID')),
- help='Authentication project ID (Env: OS_PROJECT_ID)',
+ help='Project ID of the requested project-level '
+ 'authorization scope (Env: OS_PROJECT_ID)',
)
parser.add_argument(
'--os-tenant-id',
@@ -178,6 +241,30 @@ class OpenStackShell(app.App):
default=utils.env('OS_PASSWORD'),
help='Authentication password (Env: OS_PASSWORD)')
parser.add_argument(
+ '--os-user-domain-name',
+ metavar='<auth-user-domain-name>',
+ default=utils.env('OS_USER_DOMAIN_NAME'),
+ help='Domain name of the user (Env: OS_USER_DOMAIN_NAME)')
+ parser.add_argument(
+ '--os-user-domain-id',
+ metavar='<auth-user-domain-id>',
+ default=utils.env('OS_USER_DOMAIN_ID'),
+ help='Domain ID of the user (Env: OS_USER_DOMAIN_ID)')
+ parser.add_argument(
+ '--os-project-domain-name',
+ metavar='<auth-project-domain-name>',
+ default=utils.env('OS_PROJECT_DOMAIN_NAME'),
+ help='Domain name of the project which is the requested '
+ 'project-level authorization scope '
+ '(Env: OS_PROJECT_DOMAIN_NAME)')
+ parser.add_argument(
+ '--os-project-domain-id',
+ metavar='<auth-project-domain-id>',
+ default=utils.env('OS_PROJECT_DOMAIN_ID'),
+ help='Domain ID of the project which is the requested '
+ 'project-level authorization scope '
+ '(Env: OS_PROJECT_DOMAIN_ID)')
+ parser.add_argument(
'--os-region-name',
metavar='<auth-region-name>',
default=env('OS_REGION_NAME'),
@@ -284,11 +371,16 @@ class OpenStackShell(app.App):
" either --os-password, or env[OS_PASSWORD], "
" or prompted response")
- if not (self.options.os_project_id
- or self.options.os_project_name):
+ if not ((self.options.os_project_id
+ or self.options.os_project_name) or
+ (self.options.os_domain_id
+ or self.options.os_domain_name)):
raise exc.CommandError(
- "You must provide a project id via"
- " either --os-project-id or via env[OS_PROJECT_ID]")
+ "You must provide authentication scope as a project "
+ "or a domain via --os-project-id or env[OS_PROJECT_ID], "
+ "--os-project-name or env[OS_PROJECT_NAME], "
+ "--os-domain-id or env[OS_DOMAIN_ID], or"
+ "--os-domain-name or env[OS_DOMAIN_NAME].")
if not self.options.os_auth_url:
raise exc.CommandError(
@@ -299,8 +391,14 @@ class OpenStackShell(app.App):
token=self.options.os_token,
url=self.options.os_url,
auth_url=self.options.os_auth_url,
+ domain_id=self.options.os_domain_id,
+ domain_name=self.options.os_domain_name,
project_name=self.options.os_project_name,
project_id=self.options.os_project_id,
+ user_domain_id=self.options.os_user_domain_id,
+ user_domain_name=self.options.os_user_domain_name,
+ project_domain_id=self.options.os_project_domain_id,
+ project_domain_name=self.options.os_project_domain_name,
username=self.options.os_username,
password=self.options.os_password,
region_name=self.options.os_region_name,
@@ -347,15 +445,6 @@ class OpenStackShell(app.App):
super(OpenStackShell, self).initialize_app(argv)
- # Set requests logging to a useful level
- requests_log = logging.getLogger("requests")
- if self.options.debug:
- requests_log.setLevel(logging.DEBUG)
- self.dump_stack_trace = True
- else:
- requests_log.setLevel(logging.WARNING)
- self.dump_stack_trace = False
-
# Save default domain
self.default_domain = self.options.os_default_domain
@@ -368,13 +457,14 @@ class OpenStackShell(app.App):
ver = getattr(self.options, mod.API_VERSION_OPTION, None)
if ver:
self.api_version[mod.API_NAME] = ver
- self.log.debug('%s API version %s' % (mod.API_NAME, ver))
+ self.log.debug('%(name)s API version %(version)s',
+ {'name': mod.API_NAME, 'version': ver})
# Add the API version-specific commands
for api in self.api_version.keys():
version = '.v' + self.api_version[api].replace('.', '_')
cmd_group = 'openstack.' + api.replace('-', '_') + version
- self.log.debug('command group %s' % cmd_group)
+ self.log.debug('command group %s', cmd_group)
self.command_manager.add_command_group(cmd_group)
# Commands that span multiple APIs
@@ -414,7 +504,15 @@ class OpenStackShell(app.App):
"""Set up auth and API versions"""
self.log.debug('prepare_to_run_command %s', cmd.__class__.__name__)
- if cmd.auth_required:
+ if not cmd.auth_required:
+ return
+ if cmd.best_effort:
+ try:
+ self.authenticate_user()
+ self.restapi.set_auth(self.client_manager.identity.auth_token)
+ except Exception:
+ pass
+ else:
self.authenticate_user()
self.restapi.set_auth(self.client_manager.identity.auth_token)
return
diff --git a/openstackclient/tests/__init__.py b/openstackclient/tests/__init__.py
index 85ac250..e69de29 100644
--- a/openstackclient/tests/__init__.py
+++ b/openstackclient/tests/__init__.py
@@ -1,14 +0,0 @@
-# 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.
-#
diff --git a/openstackclient/tests/common/__init__.py b/openstackclient/tests/common/__init__.py
index c534c01..e69de29 100644
--- a/openstackclient/tests/common/__init__.py
+++ b/openstackclient/tests/common/__init__.py
@@ -1,14 +0,0 @@
-# Copyright 2013 OpenStack Foundation
-#
-# 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.
-#
diff --git a/openstackclient/tests/common/test_extension.py b/openstackclient/tests/common/test_extension.py
new file mode 100644
index 0000000..2e6e705
--- /dev/null
+++ b/openstackclient/tests/common/test_extension.py
@@ -0,0 +1,128 @@
+# 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 copy
+
+from openstackclient.common import extension
+from openstackclient.tests import fakes
+from openstackclient.tests import utils
+
+from openstackclient.tests.identity.v2_0 import fakes as identity_fakes
+
+
+class TestExtension(utils.TestCommand):
+
+ def setUp(self):
+ super(TestExtension, self).setUp()
+
+ self.app.client_manager.identity = identity_fakes.FakeIdentityv2Client(
+ endpoint=fakes.AUTH_URL,
+ token=fakes.AUTH_TOKEN,
+ )
+
+ # Get shortcuts to the ExtensionManager Mocks
+ self.identity_extensions_mock = (
+ self.app.client_manager.identity.extensions)
+ self.identity_extensions_mock.reset_mock()
+
+
+class TestExtensionList(TestExtension):
+
+ def setUp(self):
+ super(TestExtensionList, self).setUp()
+
+ self.identity_extensions_mock.list.return_value = [
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.EXTENSION),
+ loaded=True,
+ ),
+ ]
+
+ # Get the command object to test
+ self.cmd = extension.ListExtension(self.app, None)
+
+ def test_extension_list_no_options(self):
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # no args should output from all services
+ self.identity_extensions_mock.list.assert_called_with()
+
+ collist = ('Name', 'Alias', 'Description')
+ self.assertEqual(columns, collist)
+ datalist = (
+ (
+ identity_fakes.extension_name,
+ identity_fakes.extension_alias,
+ identity_fakes.extension_description,
+ ),
+ )
+ self.assertEqual(tuple(data), datalist)
+
+ def test_extension_list_long(self):
+ arglist = [
+ '--long',
+ ]
+ verifylist = [
+ ('long', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # no args should output from all services
+ self.identity_extensions_mock.list.assert_called_with()
+
+ collist = ('Name', 'Namespace', 'Description', 'Alias', 'Updated',
+ 'Links')
+ self.assertEqual(columns, collist)
+ datalist = (
+ (
+ identity_fakes.extension_name,
+ identity_fakes.extension_namespace,
+ identity_fakes.extension_description,
+ identity_fakes.extension_alias,
+ identity_fakes.extension_updated,
+ identity_fakes.extension_links,
+ ),
+ )
+ self.assertEqual(tuple(data), datalist)
+
+ def test_extension_list_identity(self):
+ arglist = [
+ '--identity',
+ ]
+ verifylist = [
+ ('identity', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.identity_extensions_mock.list.assert_called_with()
+
+ collist = ('Name', 'Alias', 'Description')
+ self.assertEqual(columns, collist)
+ datalist = ((
+ identity_fakes.extension_name,
+ identity_fakes.extension_alias,
+ identity_fakes.extension_description,
+ ), )
+ self.assertEqual(tuple(data), datalist)
diff --git a/openstackclient/tests/common/test_parseractions.py b/openstackclient/tests/common/test_parseractions.py
index 705e7e9..f26f289 100644
--- a/openstackclient/tests/common/test_parseractions.py
+++ b/openstackclient/tests/common/test_parseractions.py
@@ -88,7 +88,7 @@ class TestKeyValueAction(utils.TestCase):
failhere = None
actual = getattr(results, 'property', {})
- # Verify non-existant red key
+ # Verify non-existent red key
try:
failhere = actual['red']
except Exception as e:
diff --git a/openstackclient/tests/common/test_restapi.py b/openstackclient/tests/common/test_restapi.py
index c1e02fc..291818c 100644
--- a/openstackclient/tests/common/test_restapi.py
+++ b/openstackclient/tests/common/test_restapi.py
@@ -19,6 +19,7 @@ import json
import mock
import requests
+import six
from openstackclient.common import restapi
from openstackclient.tests import utils
@@ -55,13 +56,15 @@ fake_headers = {
class FakeResponse(requests.Response):
- def __init__(self, headers={}, status_code=None, data=None, encoding=None):
+ def __init__(self, headers={}, status_code=200, data=None, encoding=None):
super(FakeResponse, self).__init__()
self.status_code = status_code
self.headers.update(headers)
self._content = json.dumps(data)
+ if not isinstance(self._content, six.binary_type):
+ self._content = self._content.encode()
@mock.patch('openstackclient.common.restapi.requests.Session')
diff --git a/openstackclient/tests/common/test_utils.py b/openstackclient/tests/common/test_utils.py
new file mode 100644
index 0000000..6d75a9b
--- /dev/null
+++ b/openstackclient/tests/common/test_utils.py
@@ -0,0 +1,132 @@
+# 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)
+
+
+class NoUniqueMatch(Exception):
+ pass
+
+
+class TestFindResource(test_utils.TestCase):
+ def setUp(self):
+ super(TestFindResource, self).setUp()
+ self.name = 'legos'
+ self.expected = mock.Mock()
+ self.manager = mock.Mock()
+ self.manager.resource_class = mock.Mock()
+ self.manager.resource_class.__name__ = 'lego'
+
+ def test_find_resource_get_int(self):
+ self.manager.get = mock.Mock(return_value=self.expected)
+ result = utils.find_resource(self.manager, 1)
+ self.assertEqual(self.expected, result)
+ self.manager.get.assert_called_with(1)
+
+ def test_find_resource_get_int_string(self):
+ self.manager.get = mock.Mock(return_value=self.expected)
+ result = utils.find_resource(self.manager, "2")
+ self.assertEqual(self.expected, result)
+ self.manager.get.assert_called_with(2)
+
+ def test_find_resource_get_uuid(self):
+ uuid = '9a0dc2a0-ad0d-11e3-a5e2-0800200c9a66'
+ self.manager.get = mock.Mock(return_value=self.expected)
+ result = utils.find_resource(self.manager, uuid)
+ self.assertEqual(self.expected, result)
+ self.manager.get.assert_called_with(uuid)
+
+ def test_find_resource_get_whatever(self):
+ self.manager.get = mock.Mock(return_value=self.expected)
+ result = utils.find_resource(self.manager, 'whatever')
+ self.assertEqual(self.expected, result)
+ self.manager.get.assert_called_with('whatever')
+
+ def test_find_resource_find(self):
+ self.manager.get = mock.Mock(side_effect=Exception('Boom!'))
+ self.manager.find = mock.Mock(return_value=self.expected)
+ result = utils.find_resource(self.manager, self.name)
+ self.assertEqual(self.expected, result)
+ self.manager.get.assert_called_with(self.name)
+ self.manager.find.assert_called_with(name=self.name)
+
+ def test_find_resource_find_not_found(self):
+ self.manager.get = mock.Mock(side_effect=Exception('Boom!'))
+ self.manager.find = mock.Mock(
+ side_effect=exceptions.NotFound(404, "2")
+ )
+ result = self.assertRaises(exceptions.CommandError,
+ utils.find_resource,
+ self.manager,
+ self.name)
+ self.assertEqual("No lego with a name or ID of 'legos' exists.",
+ str(result))
+ self.manager.get.assert_called_with(self.name)
+ self.manager.find.assert_called_with(name=self.name)
+
+ def test_find_resource_find_no_unique(self):
+ self.manager.get = mock.Mock(side_effect=Exception('Boom!'))
+ self.manager.find = mock.Mock(side_effect=NoUniqueMatch())
+ result = self.assertRaises(exceptions.CommandError,
+ utils.find_resource,
+ self.manager,
+ self.name)
+ self.assertEqual("More than one lego exists with the name 'legos'.",
+ str(result))
+ self.manager.get.assert_called_with(self.name)
+ self.manager.find.assert_called_with(name=self.name)
diff --git a/openstackclient/tests/compute/__init__.py b/openstackclient/tests/compute/__init__.py
index ebf59b3..e69de29 100644
--- a/openstackclient/tests/compute/__init__.py
+++ b/openstackclient/tests/compute/__init__.py
@@ -1,14 +0,0 @@
-# Copyright 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.
-#
diff --git a/openstackclient/tests/compute/v2/__init__.py b/openstackclient/tests/compute/v2/__init__.py
index c534c01..e69de29 100644
--- a/openstackclient/tests/compute/v2/__init__.py
+++ b/openstackclient/tests/compute/v2/__init__.py
@@ -1,14 +0,0 @@
-# Copyright 2013 OpenStack Foundation
-#
-# 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.
-#
diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py
index 03ebd67..cef5ee9 100644
--- a/openstackclient/tests/compute/v2/fakes.py
+++ b/openstackclient/tests/compute/v2/fakes.py
@@ -28,6 +28,25 @@ SERVER = {
'name': server_name,
}
+extension_name = 'Multinic'
+extension_namespace = 'http://docs.openstack.org/compute/ext/'\
+ 'multinic/api/v1.1'
+extension_description = 'Multiple network support'
+extension_updated = '2014-01-07T12:00:0-00:00'
+extension_alias = 'NMN'
+extension_links = '[{"href":'\
+ '"https://github.com/openstack/compute-api", "type":'\
+ ' "text/html", "rel": "describedby"}]'
+
+EXTENSION = {
+ 'name': extension_name,
+ 'namespace': extension_namespace,
+ 'description': extension_description,
+ 'updated': extension_updated,
+ 'alias': extension_alias,
+ 'links': extension_links,
+}
+
class FakeComputev2Client(object):
def __init__(self, **kwargs):
@@ -35,6 +54,8 @@ class FakeComputev2Client(object):
self.images.resource_class = fakes.FakeResource(None, {})
self.servers = mock.Mock()
self.servers.resource_class = fakes.FakeResource(None, {})
+ self.extensions = mock.Mock()
+ self.extensions.resource_class = fakes.FakeResource(None, {})
self.auth_token = kwargs['token']
self.management_url = kwargs['endpoint']
diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py
index 4cd294c..efe4c58 100644
--- a/openstackclient/tests/compute/v2/test_server.py
+++ b/openstackclient/tests/compute/v2/test_server.py
@@ -80,11 +80,7 @@ class TestServerImageCreate(TestServer):
loaded=True,
)
- self.servers_mock.create_image.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(image_fakes.IMAGE),
- loaded=True,
- )
+ self.servers_mock.create_image.return_value = image_fakes.image_id
self.images_mock.get.return_value = fakes.FakeResource(
None,
diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py
index 0121424..4c50c0b 100644
--- a/openstackclient/tests/fakes.py
+++ b/openstackclient/tests/fakes.py
@@ -13,6 +13,7 @@
# under the License.
#
+import six
import sys
@@ -68,7 +69,7 @@ class FakeResource(object):
self._loaded = loaded
def _add_details(self, info):
- for (k, v) in info.iteritems():
+ for (k, v) in six.iteritems(info):
setattr(self, k, v)
def __repr__(self):
diff --git a/openstackclient/tests/identity/__init__.py b/openstackclient/tests/identity/__init__.py
index c534c01..e69de29 100644
--- a/openstackclient/tests/identity/__init__.py
+++ b/openstackclient/tests/identity/__init__.py
@@ -1,14 +0,0 @@
-# Copyright 2013 OpenStack Foundation
-#
-# 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.
-#
diff --git a/openstackclient/tests/identity/v2_0/__init__.py b/openstackclient/tests/identity/v2_0/__init__.py
index c534c01..e69de29 100644
--- a/openstackclient/tests/identity/v2_0/__init__.py
+++ b/openstackclient/tests/identity/v2_0/__init__.py
@@ -1,14 +0,0 @@
-# Copyright 2013 OpenStack Foundation
-#
-# 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.
-#
diff --git a/openstackclient/tests/identity/v2_0/fakes.py b/openstackclient/tests/identity/v2_0/fakes.py
index 80febd2..a8438e9 100644
--- a/openstackclient/tests/identity/v2_0/fakes.py
+++ b/openstackclient/tests/identity/v2_0/fakes.py
@@ -70,19 +70,76 @@ USER = {
'enabled': True,
}
+token_expires = '2014-01-01T00:00:00Z'
+token_id = 'tttttttt-tttt-tttt-tttt-tttttttttttt'
+
+TOKEN = {
+ 'expires': token_expires,
+ 'id': token_id,
+ 'tenant_id': project_id,
+ 'user_id': user_id,
+}
+
+endpoint_name = service_name
+endpoint_adminurl = 'https://admin.example.com/v2/UUID'
+endpoint_region = 'RegionOne'
+endpoint_internalurl = 'https://internal.example.com/v2/UUID'
+endpoint_type = service_type
+endpoint_id = '11b41ee1b00841128b7333d4bf1a6140'
+endpoint_publicurl = 'https://public.example.com/v2/UUID'
+endpoint_service_id = service_id
+
+ENDPOINT = {
+ 'service_name': endpoint_name,
+ 'adminurl': endpoint_adminurl,
+ 'region': endpoint_region,
+ 'internalurl': endpoint_internalurl,
+ 'service_type': endpoint_type,
+ 'id': endpoint_id,
+ 'publicurl': endpoint_publicurl,
+ 'service_id': endpoint_service_id,
+}
+
+extension_name = 'OpenStack Keystone User CRUD'
+extension_namespace = 'http://docs.openstack.org/identity/'\
+ 'api/ext/OS-KSCRUD/v1.0'
+extension_description = 'OpenStack extensions to Keystone v2.0 API'\
+ ' enabling User Operations.'
+extension_updated = '2013-07-07T12:00:0-00:00'
+extension_alias = 'OS-KSCRUD'
+extension_links = '[{"href":'\
+ '"https://github.com/openstack/identity-api", "type":'\
+ ' "text/html", "rel": "describedby"}]'
+
+EXTENSION = {
+ 'name': extension_name,
+ 'namespace': extension_namespace,
+ 'description': extension_description,
+ 'updated': extension_updated,
+ 'alias': extension_alias,
+ 'links': extension_links,
+}
+
class FakeIdentityv2Client(object):
def __init__(self, **kwargs):
self.roles = mock.Mock()
self.roles.resource_class = fakes.FakeResource(None, {})
+ self.service_catalog = mock.Mock()
self.services = mock.Mock()
self.services.resource_class = fakes.FakeResource(None, {})
self.tenants = mock.Mock()
self.tenants.resource_class = fakes.FakeResource(None, {})
+ self.tokens = mock.Mock()
+ self.tokens.resource_class = fakes.FakeResource(None, {})
self.users = mock.Mock()
self.users.resource_class = fakes.FakeResource(None, {})
self.ec2 = mock.Mock()
self.ec2.resource_class = fakes.FakeResource(None, {})
+ self.endpoints = mock.Mock()
+ self.endpoints.resource_class = fakes.FakeResource(None, {})
+ self.extensions = mock.Mock()
+ self.extensions.resource_class = fakes.FakeResource(None, {})
self.auth_token = kwargs['token']
self.management_url = kwargs['endpoint']
diff --git a/openstackclient/tests/identity/v2_0/test_endpoint.py b/openstackclient/tests/identity/v2_0/test_endpoint.py
new file mode 100644
index 0000000..0d7db0a
--- /dev/null
+++ b/openstackclient/tests/identity/v2_0/test_endpoint.py
@@ -0,0 +1,266 @@
+# 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 copy
+
+from openstackclient.identity.v2_0 import endpoint
+from openstackclient.tests import fakes
+from openstackclient.tests.identity.v2_0 import fakes as identity_fakes
+
+
+class TestEndpoint(identity_fakes.TestIdentityv2):
+
+ def setUp(self):
+ super(TestEndpoint, self).setUp()
+
+ # Get a shortcut to the EndpointManager Mock
+ self.endpoints_mock = self.app.client_manager.identity.endpoints
+ self.endpoints_mock.reset_mock()
+
+ # Get a shortcut to the ServiceManager Mock
+ self.services_mock = self.app.client_manager.identity.services
+ self.services_mock.reset_mock()
+
+
+class TestEndpointCreate(TestEndpoint):
+
+ def setUp(self):
+ super(TestEndpointCreate, self).setUp()
+
+ self.endpoints_mock.create.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.ENDPOINT),
+ loaded=True,
+ )
+
+ self.services_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.SERVICE),
+ loaded=True,
+ )
+
+ # Get the command object to test
+ self.cmd = endpoint.CreateEndpoint(self.app, None)
+
+ def test_endpoint_create(self):
+ arglist = [
+ '--publicurl', identity_fakes.endpoint_publicurl,
+ '--internalurl', identity_fakes.endpoint_internalurl,
+ '--adminurl', identity_fakes.endpoint_adminurl,
+ '--region', identity_fakes.endpoint_region,
+ identity_fakes.endpoint_name,
+ ]
+ verifylist = [
+ ('adminurl', identity_fakes.endpoint_adminurl),
+ ('internalurl', identity_fakes.endpoint_internalurl),
+ ('publicurl', identity_fakes.endpoint_publicurl),
+ ('region', identity_fakes.endpoint_region),
+ ('service', identity_fakes.service_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # EndpointManager.create(region, service_id, publicurl, adminurl,
+ # internalurl)
+ self.endpoints_mock.create.assert_called_with(
+ identity_fakes.endpoint_region,
+ identity_fakes.service_id,
+ identity_fakes.endpoint_publicurl,
+ identity_fakes.endpoint_adminurl,
+ identity_fakes.endpoint_internalurl,
+ )
+
+ collist = ('adminurl', 'id', 'internalurl', 'publicurl',
+ 'region', 'service_id', 'service_name', 'service_type')
+ self.assertEqual(columns, collist)
+ datalist = (
+ identity_fakes.endpoint_adminurl,
+ identity_fakes.endpoint_id,
+ identity_fakes.endpoint_internalurl,
+ identity_fakes.endpoint_publicurl,
+ identity_fakes.endpoint_region,
+ identity_fakes.service_id,
+ identity_fakes.service_name,
+ identity_fakes.service_type,
+ )
+
+ self.assertEqual(data, datalist)
+
+
+class TestEndpointDelete(TestEndpoint):
+
+ def setUp(self):
+ super(TestEndpointDelete, self).setUp()
+
+ self.endpoints_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.ENDPOINT),
+ loaded=True,
+ )
+
+ self.services_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.SERVICE),
+ loaded=True,
+ )
+
+ self.endpoints_mock.delete.return_value = None
+
+ # Get the command object to test
+ self.cmd = endpoint.DeleteEndpoint(self.app, None)
+
+ def test_endpoint_delete_no_options(self):
+ arglist = [
+ identity_fakes.endpoint_id,
+ ]
+ verifylist = [
+ ('endpoint', identity_fakes.endpoint_id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ self.cmd.take_action(parsed_args)
+
+ self.endpoints_mock.delete.assert_called_with(
+ identity_fakes.endpoint_id,
+ )
+
+
+class TestEndpointList(TestEndpoint):
+
+ def setUp(self):
+ super(TestEndpointList, self).setUp()
+
+ self.endpoints_mock.list.return_value = [
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.ENDPOINT),
+ loaded=True,
+ ),
+ ]
+
+ self.services_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.SERVICE),
+ loaded=True,
+ )
+
+ # Get the command object to test
+ self.cmd = endpoint.ListEndpoint(self.app, None)
+
+ def test_endpoint_list_no_options(self):
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.endpoints_mock.list.assert_called_with()
+
+ collist = ('ID', 'Region', 'Service Name', 'Service Type')
+ self.assertEqual(columns, collist)
+ datalist = ((
+ identity_fakes.endpoint_id,
+ identity_fakes.endpoint_region,
+ identity_fakes.service_name,
+ identity_fakes.service_type,
+ ), )
+ self.assertEqual(tuple(data), datalist)
+
+ def test_endpoint_list_long(self):
+ arglist = [
+ '--long',
+ ]
+ verifylist = [
+ ('long', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.endpoints_mock.list.assert_called_with()
+
+ collist = ('ID', 'Region', 'Service Name', 'Service Type',
+ 'PublicURL', 'AdminURL', 'InternalURL')
+ self.assertEqual(columns, collist)
+ datalist = ((
+ identity_fakes.endpoint_id,
+ identity_fakes.endpoint_region,
+ identity_fakes.service_name,
+ identity_fakes.service_type,
+ identity_fakes.endpoint_publicurl,
+ identity_fakes.endpoint_adminurl,
+ identity_fakes.endpoint_internalurl,
+ ), )
+ self.assertEqual(tuple(data), datalist)
+
+
+class TestEndpointShow(TestEndpoint):
+
+ def setUp(self):
+ super(TestEndpointShow, self).setUp()
+
+ self.endpoints_mock.list.return_value = [
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.ENDPOINT),
+ loaded=True,
+ ),
+ ]
+
+ self.services_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.SERVICE),
+ loaded=True,
+ )
+
+ # Get the command object to test
+ self.cmd = endpoint.ShowEndpoint(self.app, None)
+
+ def test_endpoint_show(self):
+ arglist = [
+ identity_fakes.endpoint_name,
+ ]
+ verifylist = [
+ ('endpoint_or_service', identity_fakes.endpoint_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # EndpointManager.list()
+ self.endpoints_mock.list.assert_called_with()
+ # ServiceManager.get(name)
+ self.services_mock.get.assert_called_with(
+ identity_fakes.service_name,
+ )
+
+ collist = ('adminurl', 'id', 'internalurl', 'publicurl',
+ 'region', 'service_id', 'service_name', 'service_type')
+ self.assertEqual(columns, collist)
+ datalist = (
+ identity_fakes.endpoint_adminurl,
+ identity_fakes.endpoint_id,
+ identity_fakes.endpoint_internalurl,
+ identity_fakes.endpoint_publicurl,
+ identity_fakes.endpoint_region,
+ identity_fakes.service_id,
+ identity_fakes.service_name,
+ identity_fakes.service_type,
+ )
+ self.assertEqual(data, datalist)
diff --git a/openstackclient/tests/identity/v2_0/test_project.py b/openstackclient/tests/identity/v2_0/test_project.py
index 30f4278..d046cd4 100644
--- a/openstackclient/tests/identity/v2_0/test_project.py
+++ b/openstackclient/tests/identity/v2_0/test_project.py
@@ -182,6 +182,43 @@ class TestProjectCreate(TestProject):
)
self.assertEqual(data, datalist)
+ def test_project_create_property(self):
+ arglist = [
+ '--property', 'fee=fi',
+ '--property', 'fo=fum',
+ identity_fakes.project_name,
+ ]
+ verifylist = [
+ ('property', {'fee': 'fi', 'fo': 'fum'}),
+ ('name', identity_fakes.project_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'description': None,
+ 'enabled': True,
+ 'fee': 'fi',
+ 'fo': 'fum',
+ }
+ self.projects_mock.create.assert_called_with(
+ identity_fakes.project_name,
+ **kwargs
+ )
+
+ collist = ('description', 'enabled', 'id', 'name')
+ self.assertEqual(columns, collist)
+ datalist = (
+ identity_fakes.project_description,
+ True,
+ identity_fakes.project_id,
+ identity_fakes.project_name,
+ )
+ self.assertEqual(data, datalist)
+
class TestProjectDelete(TestProject):
@@ -412,6 +449,34 @@ class TestProjectSet(TestProject):
**kwargs
)
+ def test_project_set_property(self):
+ arglist = [
+ '--property', 'fee=fi',
+ '--property', 'fo=fum',
+ identity_fakes.project_name,
+ ]
+ verifylist = [
+ ('property', {'fee': 'fi', 'fo': 'fum'}),
+ ('project', identity_fakes.project_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.run(parsed_args)
+ self.assertEqual(result, 0)
+
+ # Set expected values
+ kwargs = {
+ 'description': identity_fakes.project_description,
+ 'enabled': True,
+ 'tenant_name': identity_fakes.project_name,
+ 'fee': 'fi',
+ 'fo': 'fum',
+ }
+ self.projects_mock.update.assert_called_with(
+ identity_fakes.project_id,
+ **kwargs
+ )
+
class TestProjectShow(TestProject):
diff --git a/openstackclient/tests/identity/v2_0/test_token.py b/openstackclient/tests/identity/v2_0/test_token.py
new file mode 100644
index 0000000..e196753
--- /dev/null
+++ b/openstackclient/tests/identity/v2_0/test_token.py
@@ -0,0 +1,77 @@
+# Copyright 2014 eBay Inc.
+#
+# 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.
+#
+
+from openstackclient.identity.v2_0 import token
+from openstackclient.tests.identity.v2_0 import fakes as identity_fakes
+
+
+class TestToken(identity_fakes.TestIdentityv2):
+
+ def setUp(self):
+ super(TestToken, self).setUp()
+
+ # Get a shortcut to the Service Catalog Mock
+ self.sc_mock = self.app.client_manager.identity.service_catalog
+ self.sc_mock.reset_mock()
+
+
+class TestTokenCreate(TestToken):
+
+ def setUp(self):
+ super(TestTokenCreate, self).setUp()
+
+ self.sc_mock.get_token.return_value = identity_fakes.TOKEN
+ self.cmd = token.CreateToken(self.app, None)
+
+ def test_token_create(self):
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.sc_mock.get_token.assert_called_with()
+
+ collist = ('expires', 'id', 'project_id', 'user_id')
+ self.assertEqual(columns, collist)
+ datalist = (
+ identity_fakes.token_expires,
+ identity_fakes.token_id,
+ identity_fakes.project_id,
+ identity_fakes.user_id,
+ )
+ self.assertEqual(data, datalist)
+
+
+class TestTokenDelete(TestToken):
+
+ TOKEN = 'fob'
+
+ def setUp(self):
+ super(TestTokenDelete, self).setUp()
+ self.tokens_mock = self.app.client_manager.identity.tokens
+ self.tokens_mock.reset_mock()
+ self.tokens_mock.delete.return_value = True
+ self.cmd = token.DeleteToken(self.app, None)
+
+ def test_token_create(self):
+ arglist = [self.TOKEN]
+ verifylist = [('token', self.TOKEN)]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+
+ self.tokens_mock.delete.assert_called_with(self.TOKEN)
diff --git a/openstackclient/tests/identity/v2_0/test_user.py b/openstackclient/tests/identity/v2_0/test_user.py
index a18d13d..e191431 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',
@@ -338,6 +381,11 @@ class TestUserList(TestUser):
def setUp(self):
super(TestUserList, self).setUp()
+ self.projects_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.PROJECT_2),
+ loaded=True,
+ )
self.projects_mock.list.return_value = [
fakes.FakeResource(
None,
@@ -365,7 +413,7 @@ class TestUserList(TestUser):
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
- self.users_mock.list.assert_called_with()
+ self.users_mock.list.assert_called_with(tenant_id=None)
collist = ('ID', 'Name')
self.assertEqual(columns, collist)
@@ -383,11 +431,12 @@ class TestUserList(TestUser):
('project', identity_fakes.project_id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ project_id = identity_fakes.PROJECT_2['id']
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
- self.users_mock.list.assert_called_with()
+ self.users_mock.list.assert_called_with(tenant_id=project_id)
collist = ('ID', 'Name')
self.assertEqual(columns, collist)
@@ -409,7 +458,7 @@ class TestUserList(TestUser):
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
- self.users_mock.list.assert_called_with()
+ self.users_mock.list.assert_called_with(tenant_id=None)
collist = ('ID', 'Name', 'Project', 'Email', 'Enabled')
self.assertEqual(columns, collist)
@@ -498,6 +547,7 @@ class TestUserSet(TestUser):
verifylist = [
('name', None),
('password', 'secret'),
+ ('password_prompt', False),
('email', None),
('project', None),
('enable', False),
@@ -515,6 +565,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/__init__.py b/openstackclient/tests/identity/v3/__init__.py
index c534c01..e69de29 100644
--- a/openstackclient/tests/identity/v3/__init__.py
+++ b/openstackclient/tests/identity/v3/__init__.py
@@ -1,14 +0,0 @@
-# Copyright 2013 OpenStack Foundation
-#
-# 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.
-#
diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py
index 9d40d9d..711a423 100644
--- a/openstackclient/tests/identity/v3/fakes.py
+++ b/openstackclient/tests/identity/v3/fakes.py
@@ -88,6 +88,94 @@ USER = {
'domain_id': domain_id,
}
+token_expires = '2014-01-01T00:00:00Z'
+token_id = 'tttttttt-tttt-tttt-tttt-tttttttttttt'
+
+TOKEN_WITH_TENANT_ID = {
+ 'expires': token_expires,
+ 'id': token_id,
+ 'tenant_id': project_id,
+ 'user_id': user_id,
+}
+
+TOKEN_WITH_DOMAIN_ID = {
+ 'expires': token_expires,
+ 'id': token_id,
+ 'domain_id': domain_id,
+ 'user_id': user_id,
+}
+
+idp_id = 'test_idp'
+idp_description = 'super exciting IdP description'
+
+IDENTITY_PROVIDER = {
+ 'id': idp_id,
+ 'enabled': True,
+ 'description': idp_description
+}
+
+#Assignments
+ASSIGNMENT_WITH_PROJECT_ID_AND_USER_ID = {
+ 'scope': {'project': {'id': project_id}},
+ 'user': {'id': user_id},
+ 'role': {'id': role_id},
+}
+
+ASSIGNMENT_WITH_PROJECT_ID_AND_GROUP_ID = {
+ 'scope': {'project': {'id': project_id}},
+ 'group': {'id': group_id},
+ 'role': {'id': role_id},
+}
+
+ASSIGNMENT_WITH_DOMAIN_ID_AND_USER_ID = {
+ 'scope': {'domain': {'id': domain_id}},
+ 'user': {'id': user_id},
+ 'role': {'id': role_id},
+}
+
+ASSIGNMENT_WITH_DOMAIN_ID_AND_GROUP_ID = {
+ 'scope': {'domain': {'id': domain_id}},
+ 'group': {'id': group_id},
+ 'role': {'id': role_id},
+}
+
+consumer_id = 'test consumer id'
+consumer_description = 'someone we trust'
+consumer_secret = 'test consumer secret'
+
+OAUTH_CONSUMER = {
+ 'id': consumer_id,
+ 'secret': consumer_secret,
+ 'description': consumer_description
+}
+
+access_token_id = 'test access token id'
+access_token_secret = 'test access token secret'
+access_token_expires = '2014-05-18T03:13:18.152071Z'
+
+OAUTH_ACCESS_TOKEN = {
+ 'id': access_token_id,
+ 'expires': access_token_expires,
+ 'key': access_token_id,
+ 'secret': access_token_secret
+}
+
+request_token_id = 'test request token id'
+request_token_secret = 'test request token secret'
+request_token_expires = '2014-05-17T11:10:51.511336Z'
+
+OAUTH_REQUEST_TOKEN = {
+ 'id': request_token_id,
+ 'expires': request_token_expires,
+ 'key': request_token_id,
+ 'secret': request_token_secret
+}
+
+oauth_verifier_pin = '6d74XaDS'
+OAUTH_VERIFIER = {
+ 'oauth_verifier': oauth_verifier_pin
+}
+
class FakeIdentityv3Client(object):
def __init__(self, **kwargs):
@@ -95,18 +183,43 @@ class FakeIdentityv3Client(object):
self.domains.resource_class = fakes.FakeResource(None, {})
self.groups = mock.Mock()
self.groups.resource_class = fakes.FakeResource(None, {})
+ self.oauth1 = mock.Mock()
+ self.oauth1.resource_class = fakes.FakeResource(None, {})
self.projects = mock.Mock()
self.projects.resource_class = fakes.FakeResource(None, {})
self.roles = mock.Mock()
self.roles.resource_class = fakes.FakeResource(None, {})
self.services = mock.Mock()
self.services.resource_class = fakes.FakeResource(None, {})
+ self.service_catalog = mock.Mock()
self.users = mock.Mock()
self.users.resource_class = fakes.FakeResource(None, {})
+ self.role_assignments = mock.Mock()
+ self.role_assignments.resource_class = fakes.FakeResource(None, {})
self.auth_token = kwargs['token']
self.management_url = kwargs['endpoint']
+class FakeFederatedClient(FakeIdentityv3Client):
+ def __init__(self, **kwargs):
+ super(FakeFederatedClient, self).__init__(**kwargs)
+
+ self.identity_providers = mock.Mock()
+ self.identity_providers.resource_class = fakes.FakeResource(None, {})
+
+
+class FakeOAuth1Client(FakeIdentityv3Client):
+ def __init__(self, **kwargs):
+ super(FakeOAuth1Client, self).__init__(**kwargs)
+
+ self.access_tokens = mock.Mock()
+ self.access_tokens.resource_class = fakes.FakeResource(None, {})
+ self.consumers = mock.Mock()
+ self.consumers.resource_class = fakes.FakeResource(None, {})
+ self.request_tokens = mock.Mock()
+ self.request_tokens.resource_class = fakes.FakeResource(None, {})
+
+
class TestIdentityv3(utils.TestCommand):
def setUp(self):
super(TestIdentityv3, self).setUp()
@@ -115,3 +228,23 @@ class TestIdentityv3(utils.TestCommand):
endpoint=fakes.AUTH_URL,
token=fakes.AUTH_TOKEN,
)
+
+
+class TestFederatedIdentity(utils.TestCommand):
+ def setUp(self):
+ super(TestFederatedIdentity, self).setUp()
+
+ self.app.client_manager.identity = FakeFederatedClient(
+ endpoint=fakes.AUTH_URL,
+ token=fakes.AUTH_TOKEN
+ )
+
+
+class TestOAuth1(utils.TestCommand):
+ def setUp(self):
+ super(TestOAuth1, self).setUp()
+
+ self.app.client_manager.identity = FakeOAuth1Client(
+ endpoint=fakes.AUTH_URL,
+ token=fakes.AUTH_TOKEN
+ )
diff --git a/openstackclient/tests/identity/v3/test_consumer.py b/openstackclient/tests/identity/v3/test_consumer.py
new file mode 100644
index 0000000..a109570
--- /dev/null
+++ b/openstackclient/tests/identity/v3/test_consumer.py
@@ -0,0 +1,200 @@
+# 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 copy
+
+from openstackclient.identity.v3 import consumer
+from openstackclient.tests import fakes
+from openstackclient.tests.identity.v3 import fakes as identity_fakes
+
+
+class TestOAuth1(identity_fakes.TestOAuth1):
+
+ def setUp(self):
+ super(TestOAuth1, self).setUp()
+ identity_client = self.app.client_manager.identity
+ self.consumers_mock = identity_client.oauth1.consumers
+ self.consumers_mock.reset_mock()
+
+
+class TestConsumerCreate(TestOAuth1):
+
+ def setUp(self):
+ super(TestConsumerCreate, self).setUp()
+
+ self.consumers_mock.create.return_value = fakes.FakeResource(
+ None, copy.deepcopy(identity_fakes.OAUTH_CONSUMER),
+ loaded=True)
+
+ self.cmd = consumer.CreateConsumer(self.app, None)
+
+ def test_create_consumer(self):
+ arglist = [
+ '--description', identity_fakes.consumer_description
+ ]
+ verifylist = [
+ ('description', identity_fakes.consumer_description)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.consumers_mock.create.assert_called_with(
+ identity_fakes.consumer_description)
+
+ collist = ('description', 'id', 'secret')
+ self.assertEqual(columns, collist)
+ datalist = (
+ identity_fakes.consumer_description,
+ identity_fakes.consumer_id,
+ identity_fakes.consumer_secret
+ )
+ self.assertEqual(data, datalist)
+
+
+class TestConsumerDelete(TestOAuth1):
+
+ def setUp(self):
+ super(TestConsumerDelete, self).setUp()
+
+ # This is the return value for utils.find_resource()
+ self.consumers_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.OAUTH_CONSUMER),
+ loaded=True)
+
+ self.consumers_mock.delete.return_value = None
+ self.cmd = consumer.DeleteConsumer(self.app, None)
+
+ def test_delete_consumer(self):
+ arglist = [
+ identity_fakes.consumer_id
+ ]
+ verifylist = [
+ ('consumer', identity_fakes.consumer_id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.run(parsed_args)
+ self.assertEqual(result, 0)
+
+ self.consumers_mock.delete.assert_called_with(
+ identity_fakes.consumer_id,
+ )
+
+
+class TestConsumerList(TestOAuth1):
+
+ def setUp(self):
+ super(TestConsumerList, self).setUp()
+
+ self.consumers_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.OAUTH_CONSUMER),
+ loaded=True,
+ )
+ self.consumers_mock.list.return_value = [
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.OAUTH_CONSUMER),
+ loaded=True,
+ ),
+ ]
+
+ # Get the command object to test
+ self.cmd = consumer.ListConsumer(self.app, None)
+
+ def test_consumer_list(self):
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+ self.consumers_mock.list.assert_called_with()
+
+ collist = ('ID', 'Description')
+ self.assertEqual(columns, collist)
+ datalist = ((
+ identity_fakes.consumer_id,
+ identity_fakes.consumer_description
+ ), )
+ self.assertEqual(tuple(data), datalist)
+
+
+class TestConsumerShow(TestOAuth1):
+
+ def setUp(self):
+ super(TestConsumerShow, self).setUp()
+
+ consumer_no_secret = copy.deepcopy(identity_fakes.OAUTH_CONSUMER)
+ del consumer_no_secret['secret']
+ self.consumers_mock.get.return_value = fakes.FakeResource(
+ None, consumer_no_secret, loaded=True)
+
+ # Get the command object to test
+ self.cmd = consumer.ShowConsumer(self.app, None)
+
+ def test_consumer_show(self):
+ arglist = [
+ identity_fakes.consumer_id
+ ]
+ verifylist = [
+ ('consumer', identity_fakes.consumer_id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.consumers_mock.get.assert_called_with(
+ identity_fakes.consumer_id)
+
+ collist = ('description', 'id' )
+ self.assertEqual(columns, collist)
+ datalist = (
+ identity_fakes.consumer_description,
+ identity_fakes.consumer_id
+ )
+ self.assertEqual(data, datalist)
+
+
+class TestConsumerSet(TestOAuth1):
+
+ def setUp(self):
+ super(TestConsumerSet, self).setUp()
+
+ self.consumers_mock.get.return_value = fakes.FakeResource(
+ None, copy.deepcopy(identity_fakes.OAUTH_CONSUMER), loaded=True)
+
+ consumer_updated = copy.deepcopy(identity_fakes.OAUTH_CONSUMER)
+ consumer_updated['description'] = "consumer new description"
+ self.consumers_mock.update.return_value = fakes.FakeResource(
+ None, consumer_updated, loaded=True)
+
+ self.cmd = consumer.SetConsumer(self.app, None)
+
+ def test_consumer_update(self):
+ new_description = "consumer new description"
+
+ arglist = [
+ '--description', new_description,
+ identity_fakes.consumer_id
+ ]
+ verifylist = [
+ ('description', new_description),
+ ('consumer', identity_fakes.consumer_id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.run(parsed_args)
+ self.assertEqual(result, 0)
+
+ kwargs = {'description': new_description}
+ self.consumers_mock.update.assert_called_with(
+ identity_fakes.consumer_id, **kwargs)
diff --git a/openstackclient/tests/identity/v3/test_group.py b/openstackclient/tests/identity/v3/test_group.py
new file mode 100644
index 0000000..dce5636
--- /dev/null
+++ b/openstackclient/tests/identity/v3/test_group.py
@@ -0,0 +1,193 @@
+# 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 copy
+
+from openstackclient.identity.v3 import group
+from openstackclient.tests import fakes
+from openstackclient.tests.identity.v3 import fakes as identity_fakes
+
+
+class TestGroup(identity_fakes.TestIdentityv3):
+
+ def setUp(self):
+ super(TestGroup, self).setUp()
+
+ # Get a shortcut to the DomainManager Mock
+ self.domains_mock = self.app.client_manager.identity.domains
+ self.domains_mock.reset_mock()
+
+ # Get a shortcut to the GroupManager Mock
+ self.groups_mock = self.app.client_manager.identity.groups
+ self.groups_mock.reset_mock()
+
+ # Get a shortcut to the UserManager Mock
+ self.users_mock = self.app.client_manager.identity.users
+ self.users_mock.reset_mock()
+
+
+class TestGroupList(TestGroup):
+
+ def setUp(self):
+ super(TestGroupList, self).setUp()
+
+ self.groups_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.GROUP),
+ loaded=True,
+ )
+ self.groups_mock.list.return_value = [
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.GROUP),
+ loaded=True,
+ ),
+ ]
+
+ self.domains_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.DOMAIN),
+ loaded=True,
+ )
+
+ self.users_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.USER),
+ loaded=True,
+ )
+
+ # Get the command object to test
+ self.cmd = group.ListGroup(self.app, None)
+
+ def test_group_list_no_options(self):
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'domain': None,
+ 'user': None,
+ }
+
+ self.groups_mock.list.assert_called_with(
+ **kwargs
+ )
+
+ collist = ('ID', 'Name')
+ self.assertEqual(columns, collist)
+ datalist = ((
+ identity_fakes.group_id,
+ identity_fakes.group_name,
+ ), )
+ self.assertEqual(datalist, tuple(data))
+
+ def test_group_list_domain(self):
+ arglist = [
+ '--domain', identity_fakes.domain_id,
+ ]
+ verifylist = [
+ ('domain', identity_fakes.domain_id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'domain': identity_fakes.domain_id,
+ 'user': None,
+ }
+
+ self.groups_mock.list.assert_called_with(
+ **kwargs
+ )
+
+ collist = ('ID', 'Name')
+ self.assertEqual(columns, collist)
+ datalist = ((
+ identity_fakes.group_id,
+ identity_fakes.group_name,
+ ), )
+ self.assertEqual(datalist, tuple(data))
+
+ def test_group_list_user(self):
+ arglist = [
+ '--user', identity_fakes.user_name,
+ ]
+ verifylist = [
+ ('user', identity_fakes.user_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'domain': None,
+ 'user': identity_fakes.user_id,
+ }
+
+ self.groups_mock.list.assert_called_with(
+ **kwargs
+ )
+
+ collist = ('ID', 'Name')
+ self.assertEqual(columns, collist)
+ datalist = ((
+ identity_fakes.group_id,
+ identity_fakes.group_name,
+ ), )
+ self.assertEqual(datalist, tuple(data))
+
+ def test_group_list_long(self):
+ arglist = [
+ '--long',
+ ]
+ verifylist = [
+ ('long', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'domain': None,
+ 'user': None,
+ }
+
+ self.groups_mock.list.assert_called_with(
+ **kwargs
+ )
+
+ collist = (
+ 'ID',
+ 'Name',
+ 'Domain ID',
+ 'Description',
+ )
+ self.assertEqual(columns, collist)
+ datalist = ((
+ identity_fakes.group_id,
+ identity_fakes.group_name,
+ '',
+ '',
+ ), )
+ self.assertEqual(datalist, tuple(data))
diff --git a/openstackclient/tests/identity/v3/test_identity_provider.py b/openstackclient/tests/identity/v3/test_identity_provider.py
new file mode 100644
index 0000000..41015b6
--- /dev/null
+++ b/openstackclient/tests/identity/v3/test_identity_provider.py
@@ -0,0 +1,368 @@
+# Copyright 2014 CERN.
+#
+# 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 copy
+
+from openstackclient.identity.v3 import identity_provider
+from openstackclient.tests import fakes
+from openstackclient.tests.identity.v3 import fakes as identity_fakes
+
+
+class TestIdentityProvider(identity_fakes.TestFederatedIdentity):
+
+ def setUp(self):
+ super(TestIdentityProvider, self).setUp()
+
+ self.identity_providers_mock = self.app.client_manager.\
+ identity.identity_providers
+
+ self.identity_providers_mock.reset_mock()
+
+
+class TestIdentityProviderCreate(TestIdentityProvider):
+
+ def setUp(self):
+ super(TestIdentityProviderCreate, self).setUp()
+
+ self.identity_providers_mock.create.return_value = \
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.IDENTITY_PROVIDER),
+ loaded=True
+ )
+
+ self.cmd = identity_provider.CreateIdentityProvider(
+ self.app, None)
+
+ def test_create_identity_provider_no_options(self):
+ arglist = [
+ identity_fakes.idp_id
+ ]
+ verifylist = [
+ ('identity_provider_id', identity_fakes.idp_id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'enabled': True,
+ 'description': None,
+ }
+
+ self.identity_providers_mock.create.assert_called_with(
+ identity_fakes.idp_id, **kwargs)
+
+ collist = ('description', 'enabled', 'id')
+ self.assertEqual(columns, collist)
+ datalist = (
+ identity_fakes.idp_description,
+ True,
+ identity_fakes.idp_id,
+ )
+ self.assertEqual(data, datalist)
+
+ def test_create_identity_provider_description(self):
+ arglist = ['--description', identity_fakes.idp_description,
+ identity_fakes.idp_id]
+ verifylist = [
+ ('identity_provider_id', identity_fakes.idp_id),
+ ('description', identity_fakes.idp_description)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'description': identity_fakes.idp_description,
+ 'enabled': True,
+ }
+
+ self.identity_providers_mock.create.assert_called_with(
+ identity_fakes.idp_id, **kwargs)
+
+ collist = ('description', 'enabled', 'id')
+ self.assertEqual(columns, collist)
+ datalist = (
+ identity_fakes.idp_description, True, identity_fakes.idp_id,
+ )
+ self.assertEqual(data, datalist)
+
+ def test_create_identity_provider_disabled(self):
+
+ # Prepare FakeResource object
+ IDENTITY_PROVIDER = copy.deepcopy(identity_fakes.IDENTITY_PROVIDER)
+ IDENTITY_PROVIDER['enabled'] = False
+ IDENTITY_PROVIDER['description'] = None
+
+ self.identity_providers_mock.create.return_value = \
+ fakes.FakeResource(
+ None,
+ IDENTITY_PROVIDER,
+ loaded=True
+ )
+ arglist = ['--disable',
+ identity_fakes.idp_id]
+ verifylist = [
+ ('identity_provider_id', identity_fakes.idp_id),
+
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'enabled': False,
+ 'description': None
+ }
+
+ self.identity_providers_mock.create.assert_called_with(
+ identity_fakes.idp_id, **kwargs)
+
+ collist = ('description', 'enabled', 'id')
+ self.assertEqual(columns, collist)
+ datalist = (
+ None,
+ False,
+ identity_fakes.idp_id,
+ )
+ self.assertEqual(data, datalist)
+
+
+class TestIdentityProviderDelete(TestIdentityProvider):
+
+ def setUp(self):
+ super(TestIdentityProviderDelete, self).setUp()
+
+ # This is the return value for utils.find_resource()
+ self.identity_providers_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.IDENTITY_PROVIDER),
+ loaded=True)
+
+ self.identity_providers_mock.delete.return_value = None
+ self.cmd = identity_provider.DeleteIdentityProvider(
+ self.app, None)
+
+ def test_delete_identity_provider(self):
+ arglist = [
+ identity_fakes.idp_id
+ ]
+ verifylist = [
+ ('identity_provider', identity_fakes.idp_id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.cmd.take_action(parsed_args)
+ self.identity_providers_mock.delete.assert_called_with(
+ identity_fakes.idp_id,
+ )
+
+
+class TestIdentityProviderList(TestIdentityProvider):
+
+ def setUp(self):
+ super(TestIdentityProviderList, self).setUp()
+
+ self.identity_providers_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.IDENTITY_PROVIDER),
+ loaded=True,
+ )
+ self.identity_providers_mock.list.return_value = [
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.IDENTITY_PROVIDER),
+ loaded=True,
+ ),
+ ]
+
+ # Get the command object to test
+ self.cmd = identity_provider.ListIdentityProvider(self.app, None)
+
+ def test_identity_provider_list_no_options(self):
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.identity_providers_mock.list.assert_called_with()
+
+ collist = ('ID', 'Enabled', 'Description')
+ self.assertEqual(columns, collist)
+ datalist = ((
+ identity_fakes.idp_id,
+ True,
+ identity_fakes.idp_description
+
+ ), )
+ self.assertEqual(tuple(data), datalist)
+
+
+class TestIdentityProviderShow(TestIdentityProvider):
+
+ def setUp(self):
+ super(TestIdentityProviderShow, self).setUp()
+
+ self.identity_providers_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.IDENTITY_PROVIDER),
+ loaded=True
+ )
+
+ # Get the command object to test
+ self.cmd = identity_provider.ShowIdentityProvider(self.app, None)
+
+ def test_identity_provider_show(self):
+ arglist = [
+ identity_fakes.idp_id
+ ]
+ verifylist = [
+ ('identity_provider', identity_fakes.idp_id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.identity_providers_mock.get.assert_called_with(
+ identity_fakes.idp_id)
+
+ collist = ('description', 'enabled', 'id' )
+ self.assertEqual(columns, collist)
+ datalist = (
+ identity_fakes.idp_description,
+ True,
+ identity_fakes.idp_id
+ )
+ self.assertEqual(data, datalist)
+
+
+class TestIdentityProviderSet(TestIdentityProvider):
+
+ def setUp(self):
+ super(TestIdentityProviderSet, self).setUp()
+ self.cmd = identity_provider.SetIdentityProvider(self.app, None)
+
+ def test_identity_provider_disable(self):
+ """Disable Identity Provider
+
+ Set Identity Provider's ``enabled`` attribute to False.
+
+ """
+ def prepare(self):
+ """Prepare fake return objects before the test is executed"""
+ updated_idp = copy.deepcopy(identity_fakes.IDENTITY_PROVIDER)
+ updated_idp['enabled'] = False
+ resources = fakes.FakeResource(
+ None,
+ updated_idp,
+ loaded=True
+ )
+ self.identity_providers_mock.update.return_value = resources
+
+ prepare(self)
+ arglist = [
+ '--disable', identity_fakes.idp_id
+ ]
+ verifylist = [
+ ('identity_provider', identity_fakes.idp_id),
+ ('enable', False),
+ ('disable', True)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+ self.identity_providers_mock.update.assert_called_with(
+ identity_fakes.idp_id, enabled=False)
+ collist = ('description', 'enabled', 'id' )
+ self.assertEqual(columns, collist)
+ datalist = (
+ identity_fakes.idp_description,
+ False,
+ identity_fakes.idp_id
+ )
+ self.assertEqual(datalist, data)
+
+ def test_identity_provider_enable(self):
+ """Enable Identity Provider.
+
+ Set Identity Provider's ``enabled`` attribute to True.
+
+ """
+ def prepare(self):
+ """Prepare fake return objects before the test is executed"""
+ resources = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.IDENTITY_PROVIDER),
+ loaded=True
+ )
+ self.identity_providers_mock.update.return_value = resources
+
+ prepare(self)
+ arglist = [
+ '--enable', identity_fakes.idp_id
+ ]
+ verifylist = [
+ ('identity_provider', identity_fakes.idp_id),
+ ('enable', True),
+ ('disable', False)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ self.identity_providers_mock.update.assert_called_with(
+ identity_fakes.idp_id, enabled=True)
+ collist = ('description', 'enabled', 'id' )
+ self.assertEqual(columns, collist)
+ datalist = (
+ identity_fakes.idp_description,
+ True,
+ identity_fakes.idp_id
+ )
+ self.assertEqual(data, datalist)
+
+ def test_identity_provider_no_options(self):
+ def prepare(self):
+ """Prepare fake return objects before the test is executed"""
+ resources = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.IDENTITY_PROVIDER),
+ loaded=True
+ )
+ self.identity_providers_mock.get.return_value = resources
+
+ resources = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.IDENTITY_PROVIDER),
+ loaded=True
+ )
+ self.identity_providers_mock.update.return_value = resources
+
+ prepare(self)
+ arglist = [
+ identity_fakes.idp_id
+ ]
+ verifylist = [
+ ('identity_provider', identity_fakes.idp_id),
+ ('enable', False),
+ ('disable', False)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # expect take_action() to return (None, None) as
+ # neither --enable nor --disable was specified
+ self.assertEqual(columns, None)
+ self.assertEqual(data, None)
diff --git a/openstackclient/tests/identity/v3/test_oauth.py b/openstackclient/tests/identity/v3/test_oauth.py
new file mode 100644
index 0000000..5a52864
--- /dev/null
+++ b/openstackclient/tests/identity/v3/test_oauth.py
@@ -0,0 +1,152 @@
+# 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 copy
+
+from openstackclient.identity.v3 import token
+from openstackclient.tests import fakes
+from openstackclient.tests.identity.v3 import fakes as identity_fakes
+
+
+class TestOAuth1(identity_fakes.TestOAuth1):
+
+ def setUp(self):
+ super(TestOAuth1, self).setUp()
+ identity_client = self.app.client_manager.identity
+ self.access_tokens_mock = identity_client.oauth1.access_tokens
+ self.access_tokens_mock.reset_mock()
+ self.request_tokens_mock = identity_client.oauth1.request_tokens
+ self.request_tokens_mock.reset_mock()
+
+
+class TestRequestTokenCreate(TestOAuth1):
+
+ def setUp(self):
+ super(TestRequestTokenCreate, self).setUp()
+
+ self.request_tokens_mock.create.return_value = fakes.FakeResource(
+ None, copy.deepcopy(identity_fakes.OAUTH_REQUEST_TOKEN),
+ loaded=True)
+
+ self.cmd = token.CreateRequestToken(self.app, None)
+
+ def test_create_request_tokens(self):
+ arglist = [
+ '--consumer-key', identity_fakes.consumer_id,
+ '--consumer-secret', identity_fakes.consumer_secret,
+ '--project-id', identity_fakes.project_id
+ ]
+ verifylist = [
+ ('consumer_key', identity_fakes.consumer_id),
+ ('consumer_secret', identity_fakes.consumer_secret),
+ ('project_id', identity_fakes.project_id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.request_tokens_mock.create.assert_called_with(
+ identity_fakes.consumer_id,
+ identity_fakes.consumer_secret,
+ identity_fakes.project_id)
+
+ collist = ('expires', 'id', 'key', 'secret')
+ self.assertEqual(columns, collist)
+ datalist = (
+ identity_fakes.request_token_expires,
+ identity_fakes.request_token_id,
+ identity_fakes.request_token_id,
+ identity_fakes.request_token_secret
+ )
+ self.assertEqual(data, datalist)
+
+
+class TestRequestTokenAuthorize(TestOAuth1):
+
+ def setUp(self):
+ super(TestRequestTokenAuthorize, self).setUp()
+
+ self.request_tokens_mock.authorize.return_value = \
+ fakes.FakeResource(
+ None, copy.deepcopy(identity_fakes.OAUTH_VERIFIER),
+ loaded=True)
+
+ self.cmd = token.AuthorizeRequestToken(self.app, None)
+
+ def test_authorize_request_tokens(self):
+ arglist = [
+ '--request-key', identity_fakes.request_token_id,
+ '--role-ids', identity_fakes.role_id
+ ]
+ verifylist = [
+ ('request_key', identity_fakes.request_token_id),
+ ('role_ids', identity_fakes.role_id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.request_tokens_mock.authorize.assert_called_with(
+ identity_fakes.request_token_id,
+ [identity_fakes.role_id])
+
+ collist = ('oauth_verifier',)
+ self.assertEqual(columns, collist)
+ datalist = (
+ identity_fakes.oauth_verifier_pin,
+ )
+ self.assertEqual(data, datalist)
+
+
+class TestAccessTokenCreate(TestOAuth1):
+
+ def setUp(self):
+ super(TestAccessTokenCreate, self).setUp()
+
+ self.access_tokens_mock.create.return_value = fakes.FakeResource(
+ None, copy.deepcopy(identity_fakes.OAUTH_ACCESS_TOKEN),
+ loaded=True)
+
+ self.cmd = token.CreateAccessToken(self.app, None)
+
+ def test_create_access_tokens(self):
+ arglist = [
+ '--consumer-key', identity_fakes.consumer_id,
+ '--consumer-secret', identity_fakes.consumer_secret,
+ '--request-key', identity_fakes.request_token_id,
+ '--request-secret', identity_fakes.request_token_secret,
+ '--verifier', identity_fakes.oauth_verifier_pin
+ ]
+ verifylist = [
+ ('consumer_key', identity_fakes.consumer_id),
+ ('consumer_secret', identity_fakes.consumer_secret),
+ ('request_key', identity_fakes.request_token_id),
+ ('request_secret', identity_fakes.request_token_secret),
+ ('verifier', identity_fakes.oauth_verifier_pin)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.access_tokens_mock.create.assert_called_with(
+ identity_fakes.consumer_id,
+ identity_fakes.consumer_secret,
+ identity_fakes.request_token_id,
+ identity_fakes.request_token_secret,
+ identity_fakes.oauth_verifier_pin)
+
+ collist = ('expires', 'id', 'key', 'secret')
+ self.assertEqual(columns, collist)
+ datalist = (
+ identity_fakes.access_token_expires,
+ identity_fakes.access_token_id,
+ identity_fakes.access_token_id,
+ identity_fakes.access_token_secret
+ )
+ self.assertEqual(data, datalist)
diff --git a/openstackclient/tests/identity/v3/test_project.py b/openstackclient/tests/identity/v3/test_project.py
index 02cb41b..e0420a1 100644
--- a/openstackclient/tests/identity/v3/test_project.py
+++ b/openstackclient/tests/identity/v3/test_project.py
@@ -70,13 +70,14 @@ class TestProjectCreate(TestProject):
# Set expected values
kwargs = {
+ 'name': identity_fakes.project_name,
+ 'domain': None,
'description': None,
'enabled': True,
}
- # ProjectManager.create(name, domain, description=, enabled=, **kwargs)
+ # ProjectManager.create(name=, domain=, description=,
+ # enabled=, **kwargs)
self.projects_mock.create.assert_called_with(
- identity_fakes.project_name,
- None,
**kwargs
)
@@ -109,13 +110,14 @@ class TestProjectCreate(TestProject):
# Set expected values
kwargs = {
+ 'name': identity_fakes.project_name,
+ 'domain': None,
'description': 'new desc',
'enabled': True,
}
- # ProjectManager.create(name, domain, description=, enabled=, **kwargs)
+ # ProjectManager.create(name=, domain=, description=,
+ # enabled=, **kwargs)
self.projects_mock.create.assert_called_with(
- identity_fakes.project_name,
- None,
**kwargs
)
@@ -148,13 +150,14 @@ class TestProjectCreate(TestProject):
# Set expected values
kwargs = {
+ 'name': identity_fakes.project_name,
+ 'domain': identity_fakes.domain_id,
'description': None,
'enabled': True,
}
- # ProjectManager.create(name, domain, description=, enabled=, **kwargs)
+ # ProjectManager.create(name=, domain=, description=,
+ # enabled=, **kwargs)
self.projects_mock.create.assert_called_with(
- identity_fakes.project_name,
- identity_fakes.domain_id,
**kwargs
)
@@ -186,13 +189,14 @@ class TestProjectCreate(TestProject):
# Set expected values
kwargs = {
+ 'name': identity_fakes.project_name,
+ 'domain': None,
'description': None,
'enabled': True,
}
- # ProjectManager.create(name, domain, description=, enabled=, **kwargs)
+ # ProjectManager.create(name=, domain=, description=,
+ # enabled=, **kwargs)
self.projects_mock.create.assert_called_with(
- identity_fakes.project_name,
- None,
**kwargs
)
@@ -224,13 +228,55 @@ class TestProjectCreate(TestProject):
# Set expected values
kwargs = {
+ 'name': identity_fakes.project_name,
+ 'domain': None,
'description': None,
'enabled': False,
}
- # ProjectManager.create(name, domain, description=, enabled=, **kwargs)
+ # ProjectManager.create(name=, domain=,
+ # description=, enabled=, **kwargs)
self.projects_mock.create.assert_called_with(
+ **kwargs
+ )
+
+ collist = ('description', 'domain_id', 'enabled', 'id', 'name')
+ self.assertEqual(columns, collist)
+ datalist = (
+ identity_fakes.project_description,
+ identity_fakes.domain_id,
+ True,
+ identity_fakes.project_id,
identity_fakes.project_name,
- None,
+ )
+ self.assertEqual(data, datalist)
+
+ def test_project_create_property(self):
+ arglist = [
+ '--property', 'fee=fi',
+ '--property', 'fo=fum',
+ identity_fakes.project_name,
+ ]
+ verifylist = [
+ ('property', {'fee': 'fi', 'fo': 'fum'}),
+ ('name', identity_fakes.project_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'name': identity_fakes.project_name,
+ 'domain': None,
+ 'description': None,
+ 'enabled': True,
+ 'fee': 'fi',
+ 'fo': 'fum',
+ }
+ # ProjectManager.create(name=, domain=, description=,
+ # enabled=, **kwargs)
+ self.projects_mock.create.assert_called_with(
**kwargs
)
@@ -336,6 +382,35 @@ class TestProjectList(TestProject):
), )
self.assertEqual(tuple(data), datalist)
+ def test_project_list_domain(self):
+ arglist = [
+ '--domain', identity_fakes.domain_name,
+ ]
+ verifylist = [
+ ('domain', identity_fakes.domain_name),
+ ]
+
+ self.domains_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.DOMAIN),
+ loaded=True,
+ )
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+ self.projects_mock.list.assert_called_with(
+ domain=identity_fakes.domain_id)
+
+ collist = ('ID', 'Name')
+ self.assertEqual(columns, collist)
+ datalist = ((
+ identity_fakes.project_id,
+ identity_fakes.project_name,
+ ), )
+ self.assertEqual(tuple(data), datalist)
+
class TestProjectSet(TestProject):
@@ -488,6 +563,35 @@ class TestProjectSet(TestProject):
**kwargs
)
+ def test_project_set_property(self):
+ arglist = [
+ '--property', 'fee=fi',
+ '--property', 'fo=fum',
+ identity_fakes.project_name,
+ ]
+ verifylist = [
+ ('property', {'fee': 'fi', 'fo': 'fum'}),
+ ('project', identity_fakes.project_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.run(parsed_args)
+ self.assertEqual(result, 0)
+
+ # Set expected values
+ kwargs = {
+ 'description': identity_fakes.project_description,
+ 'domain': identity_fakes.domain_id,
+ 'enabled': True,
+ 'name': identity_fakes.project_name,
+ 'fee': 'fi',
+ 'fo': 'fum',
+ }
+ self.projects_mock.update.assert_called_with(
+ identity_fakes.project_id,
+ **kwargs
+ )
+
class TestProjectShow(TestProject):
diff --git a/openstackclient/tests/identity/v3/test_role.py b/openstackclient/tests/identity/v3/test_role.py
index 040c39d..fa02ecb 100644
--- a/openstackclient/tests/identity/v3/test_role.py
+++ b/openstackclient/tests/identity/v3/test_role.py
@@ -232,9 +232,14 @@ class TestRoleCreate(TestRole):
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
- # RoleManager.create(name)
+ # Set expected values
+ kwargs = {
+ 'name': identity_fakes.role_name,
+ }
+
+ # RoleManager.create(name=)
self.roles_mock.create.assert_called_with(
- identity_fakes.role_name,
+ **kwargs
)
collist = ('id', 'name')
@@ -291,6 +296,27 @@ class TestRoleList(TestRole):
),
]
+ self.domains_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.DOMAIN),
+ loaded=True,
+ )
+ self.projects_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.PROJECT),
+ loaded=True,
+ )
+ self.users_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.USER),
+ loaded=True,
+ )
+ self.groups_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.GROUP),
+ loaded=True,
+ )
+
# Get the command object to test
self.cmd = role.ListRole(self.app, None)
@@ -312,6 +338,172 @@ class TestRoleList(TestRole):
), )
self.assertEqual(tuple(data), datalist)
+ def test_user_list_user(self):
+ arglist = [
+ '--user', identity_fakes.user_id,
+ ]
+ verifylist = [
+ ('user', identity_fakes.user_id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'domain': 'default',
+ 'user': self.users_mock.get(),
+ }
+ # RoleManager.list(user=, group=, domain=, project=, **kwargs)
+ self.roles_mock.list.assert_called_with(
+ **kwargs
+ )
+
+ collist = ('ID', 'Name')
+ self.assertEqual(columns, collist)
+ datalist = ((
+ identity_fakes.role_id,
+ identity_fakes.role_name,
+ ), )
+ self.assertEqual(tuple(data), datalist)
+
+ def test_role_list_domain_user(self):
+ arglist = [
+ '--domain', identity_fakes.domain_name,
+ '--user', identity_fakes.user_id,
+ ]
+ verifylist = [
+ ('domain', identity_fakes.domain_name),
+ ('user', identity_fakes.user_id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'domain': self.domains_mock.get(),
+ 'user': self.users_mock.get(),
+ }
+ # RoleManager.list(user=, group=, domain=, project=, **kwargs)
+ self.roles_mock.list.assert_called_with(
+ **kwargs
+ )
+
+ collist = ('ID', 'Name', 'Domain', 'User')
+ self.assertEqual(columns, collist)
+ datalist = ((
+ identity_fakes.role_id,
+ identity_fakes.role_name,
+ identity_fakes.domain_name,
+ identity_fakes.user_name,
+ ), )
+ self.assertEqual(tuple(data), datalist)
+
+ def test_role_list_domain_group(self):
+ arglist = [
+ '--domain', identity_fakes.domain_name,
+ '--group', identity_fakes.group_id,
+ ]
+ verifylist = [
+ ('domain', identity_fakes.domain_name),
+ ('group', identity_fakes.group_id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'domain': self.domains_mock.get(),
+ 'group': self.groups_mock.get(),
+ }
+ # RoleManager.list(user=, group=, domain=, project=, **kwargs)
+ self.roles_mock.list.assert_called_with(
+ **kwargs
+ )
+
+ collist = ('ID', 'Name', 'Domain', 'Group')
+ self.assertEqual(columns, collist)
+ datalist = ((
+ identity_fakes.role_id,
+ identity_fakes.role_name,
+ identity_fakes.domain_name,
+ identity_fakes.group_name,
+ ), )
+ self.assertEqual(tuple(data), datalist)
+
+ def test_role_list_project_user(self):
+ arglist = [
+ '--project', identity_fakes.project_name,
+ '--user', identity_fakes.user_id,
+ ]
+ verifylist = [
+ ('project', identity_fakes.project_name),
+ ('user', identity_fakes.user_id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'project': self.projects_mock.get(),
+ 'user': self.users_mock.get(),
+ }
+ # RoleManager.list(user=, group=, domain=, project=, **kwargs)
+ self.roles_mock.list.assert_called_with(
+ **kwargs
+ )
+
+ collist = ('ID', 'Name', 'Project', 'User')
+ self.assertEqual(columns, collist)
+ datalist = ((
+ identity_fakes.role_id,
+ identity_fakes.role_name,
+ identity_fakes.project_name,
+ identity_fakes.user_name,
+ ), )
+ self.assertEqual(tuple(data), datalist)
+
+ def test_role_list_project_group(self):
+ arglist = [
+ '--project', identity_fakes.project_name,
+ '--group', identity_fakes.group_id,
+ ]
+ verifylist = [
+ ('project', identity_fakes.project_name),
+ ('group', identity_fakes.group_id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'project': self.projects_mock.get(),
+ 'group': self.groups_mock.get(),
+ }
+ # RoleManager.list(user=, group=, domain=, project=, **kwargs)
+ self.roles_mock.list.assert_called_with(
+ **kwargs
+ )
+
+ collist = ('ID', 'Name', 'Project', 'Group')
+ self.assertEqual(columns, collist)
+ datalist = ((
+ identity_fakes.role_id,
+ identity_fakes.role_name,
+ identity_fakes.project_name,
+ identity_fakes.group_name,
+ ), )
+ self.assertEqual(tuple(data), datalist)
+
class TestRoleRemove(TestRole):
diff --git a/openstackclient/tests/identity/v3/test_role_assignment.py b/openstackclient/tests/identity/v3/test_role_assignment.py
new file mode 100644
index 0000000..6497ca8
--- /dev/null
+++ b/openstackclient/tests/identity/v3/test_role_assignment.py
@@ -0,0 +1,388 @@
+# 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 copy
+
+from openstackclient.identity.v3 import role_assignment
+from openstackclient.tests import fakes
+from openstackclient.tests.identity.v3 import fakes as identity_fakes
+
+
+class TestRoleAssignment(identity_fakes.TestIdentityv3):
+
+ def setUp(self):
+ super(TestRoleAssignment, self).setUp()
+
+
+class TestRoleAssignmentList(TestRoleAssignment):
+
+ def setUp(self):
+ super(TestRoleAssignment, self).setUp()
+
+ # Get a shortcut to the UserManager Mock
+ self.users_mock = self.app.client_manager.identity.users
+ self.users_mock.reset_mock()
+
+ # Get a shortcut to the GroupManager Mock
+ self.groups_mock = self.app.client_manager.identity.groups
+ self.groups_mock.reset_mock()
+
+ # Get a shortcut to the DomainManager Mock
+ self.domains_mock = self.app.client_manager.identity.domains
+ self.domains_mock.reset_mock()
+
+ # Get a shortcut to the ProjectManager Mock
+ self.projects_mock = self.app.client_manager.identity.projects
+ self.projects_mock.reset_mock()
+
+ # Get a shortcut to the RoleManager Mock
+ self.roles_mock = self.app.client_manager.identity.roles
+ self.roles_mock.reset_mock()
+
+ self.role_assignments_mock = self.app.client_manager.identity.\
+ role_assignments
+ self.role_assignments_mock.reset_mock()
+
+ # Get the command object to test
+ self.cmd = role_assignment.ListRoleAssignment(self.app, None)
+
+ def test_role_assignment_list_no_filters(self):
+
+ self.role_assignments_mock.list.return_value = [
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(
+ identity_fakes.ASSIGNMENT_WITH_PROJECT_ID_AND_USER_ID),
+ loaded=True,
+ ),
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(
+ identity_fakes.ASSIGNMENT_WITH_PROJECT_ID_AND_GROUP_ID),
+ loaded=True,
+ ),
+ ]
+
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.role_assignments_mock.list.assert_called_with(
+ domain=None,
+ group=None,
+ effective=False,
+ role=None,
+ user=None,
+ project=None)
+
+ collist = ('Role', 'User', 'Group', 'Project', 'Domain')
+ self.assertEqual(columns, collist)
+ datalist = ((
+ identity_fakes.role_id,
+ identity_fakes.user_id,
+ '',
+ identity_fakes.project_id,
+ ''
+ ), (identity_fakes.role_id,
+ '',
+ identity_fakes.group_id,
+ identity_fakes.project_id,
+ ''
+ ),)
+ self.assertEqual(tuple(data), datalist)
+
+ def test_role_assignment_list_user(self):
+
+ self.role_assignments_mock.list.return_value = [
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(
+ identity_fakes.ASSIGNMENT_WITH_DOMAIN_ID_AND_USER_ID),
+ loaded=True,
+ ),
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(
+ identity_fakes.ASSIGNMENT_WITH_PROJECT_ID_AND_USER_ID),
+ loaded=True,
+ ),
+ ]
+
+ arglist = [
+ '--user', identity_fakes.user_name
+ ]
+ verifylist = [
+ ('user', identity_fakes.user_name),
+ ('group', None),
+ ('domain', None),
+ ('project', None),
+ ('role', None),
+ ('effective', False),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.role_assignments_mock.list.assert_called_with(
+ domain=None,
+ user=self.users_mock.get(),
+ group=None,
+ project=None,
+ role=None,
+ effective=False)
+
+ collist = ('Role', 'User', 'Group', 'Project', 'Domain')
+ self.assertEqual(columns, collist)
+ datalist = ((
+ identity_fakes.role_id,
+ identity_fakes.user_id,
+ '',
+ '',
+ identity_fakes.domain_id
+ ), (identity_fakes.role_id,
+ identity_fakes.user_id,
+ '',
+ identity_fakes.project_id,
+ ''
+ ),)
+ self.assertEqual(tuple(data), datalist)
+
+ def test_role_assignment_list_group(self):
+
+ self.role_assignments_mock.list.return_value = [
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(
+ identity_fakes.ASSIGNMENT_WITH_DOMAIN_ID_AND_GROUP_ID),
+ loaded=True,
+ ),
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(
+ identity_fakes.ASSIGNMENT_WITH_PROJECT_ID_AND_GROUP_ID),
+ loaded=True,
+ ),
+ ]
+
+ arglist = [
+ '--group', identity_fakes.group_name
+ ]
+ verifylist = [
+ ('user', None),
+ ('group', identity_fakes.group_name),
+ ('domain', None),
+ ('project', None),
+ ('role', None),
+ ('effective', False),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.role_assignments_mock.list.assert_called_with(
+ domain=None,
+ group=self.groups_mock.get(),
+ effective=False,
+ project=None,
+ role=None,
+ user=None)
+
+ collist = ('Role', 'User', 'Group', 'Project', 'Domain')
+ self.assertEqual(columns, collist)
+ datalist = ((
+ identity_fakes.role_id,
+ '',
+ identity_fakes.group_id,
+ '',
+ identity_fakes.domain_id
+ ), (identity_fakes.role_id,
+ '',
+ identity_fakes.group_id,
+ identity_fakes.project_id,
+ ''
+ ),)
+ self.assertEqual(tuple(data), datalist)
+
+ def test_role_assignment_list_domain(self):
+
+ self.role_assignments_mock.list.return_value = [
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(
+ identity_fakes.ASSIGNMENT_WITH_DOMAIN_ID_AND_USER_ID),
+ loaded=True,
+ ),
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(
+ identity_fakes.ASSIGNMENT_WITH_DOMAIN_ID_AND_GROUP_ID),
+ loaded=True,
+ ),
+ ]
+
+ arglist = [
+ '--domain', identity_fakes.domain_name
+ ]
+ verifylist = [
+ ('user', None),
+ ('group', None),
+ ('domain', identity_fakes.domain_name),
+ ('project', None),
+ ('role', None),
+ ('effective', False),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.role_assignments_mock.list.assert_called_with(
+ domain=self.domains_mock.get(),
+ group=None,
+ effective=False,
+ project=None,
+ role=None,
+ user=None)
+
+ collist = ('Role', 'User', 'Group', 'Project', 'Domain')
+ self.assertEqual(columns, collist)
+ datalist = ((
+ identity_fakes.role_id,
+ identity_fakes.user_id,
+ '',
+ '',
+ identity_fakes.domain_id
+ ), (identity_fakes.role_id,
+ '',
+ identity_fakes.group_id,
+ '',
+ identity_fakes.domain_id
+ ),)
+ self.assertEqual(tuple(data), datalist)
+
+ def test_role_assignment_list_project(self):
+
+ self.role_assignments_mock.list.return_value = [
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(
+ identity_fakes.ASSIGNMENT_WITH_PROJECT_ID_AND_USER_ID),
+ loaded=True,
+ ),
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(
+ identity_fakes.ASSIGNMENT_WITH_PROJECT_ID_AND_GROUP_ID),
+ loaded=True,
+ ),
+ ]
+
+ arglist = [
+ '--project', identity_fakes.project_name
+ ]
+ verifylist = [
+ ('user', None),
+ ('group', None),
+ ('domain', None),
+ ('project', identity_fakes.project_name),
+ ('role', None),
+ ('effective', False),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.role_assignments_mock.list.assert_called_with(
+ domain=None,
+ group=None,
+ effective=False,
+ project=self.projects_mock.get(),
+ role=None,
+ user=None)
+
+ collist = ('Role', 'User', 'Group', 'Project', 'Domain')
+ self.assertEqual(columns, collist)
+ datalist = ((
+ identity_fakes.role_id,
+ identity_fakes.user_id,
+ '',
+ identity_fakes.project_id,
+ ''
+ ), (identity_fakes.role_id,
+ '',
+ identity_fakes.group_id,
+ identity_fakes.project_id,
+ ''
+ ),)
+ self.assertEqual(tuple(data), datalist)
+
+ def test_role_assignment_list_effective(self):
+
+ self.role_assignments_mock.list.return_value = [
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(
+ identity_fakes.ASSIGNMENT_WITH_PROJECT_ID_AND_USER_ID),
+ loaded=True,
+ ),
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(
+ identity_fakes.ASSIGNMENT_WITH_DOMAIN_ID_AND_USER_ID),
+ loaded=True,
+ ),
+ ]
+
+ arglist = ['--effective']
+ verifylist = [
+ ('user', None),
+ ('group', None),
+ ('domain', None),
+ ('project', None),
+ ('role', None),
+ ('effective', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.role_assignments_mock.list.assert_called_with(
+ domain=None,
+ group=None,
+ effective=True,
+ project=None,
+ role=None,
+ user=None)
+
+ collist = ('Role', 'User', 'Group', 'Project', 'Domain')
+ self.assertEqual(columns, collist)
+ datalist = ((
+ identity_fakes.role_id,
+ identity_fakes.user_id,
+ '',
+ identity_fakes.project_id,
+ ''
+ ), (identity_fakes.role_id,
+ identity_fakes.user_id,
+ '',
+ '',
+ identity_fakes.domain_id,
+ ),)
+ self.assertEqual(tuple(data), datalist)
diff --git a/openstackclient/tests/identity/v3/test_service.py b/openstackclient/tests/identity/v3/test_service.py
index 10d249c..6733f7f 100644
--- a/openstackclient/tests/identity/v3/test_service.py
+++ b/openstackclient/tests/identity/v3/test_service.py
@@ -60,11 +60,11 @@ class TestServiceCreate(TestService):
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
- # ServiceManager.create(name, type, enabled=, **kwargs)
+ # ServiceManager.create(name=, type=, enabled=, **kwargs)
self.services_mock.create.assert_called_with(
- identity_fakes.service_name,
- identity_fakes.service_type,
- True,
+ name=identity_fakes.service_name,
+ type=identity_fakes.service_type,
+ enabled=True,
)
collist = ('enabled', 'id', 'name', 'type')
@@ -93,11 +93,11 @@ class TestServiceCreate(TestService):
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
- # ServiceManager.create(name, type, enabled=, **kwargs)
+ # ServiceManager.create(name=, type=, enabled=, **kwargs)
self.services_mock.create.assert_called_with(
- None,
- identity_fakes.service_type,
- True,
+ name=None,
+ type=identity_fakes.service_type,
+ enabled=True,
)
collist = ('enabled', 'id', 'name', 'type')
@@ -126,11 +126,11 @@ class TestServiceCreate(TestService):
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
- # ServiceManager.create(name, type, enabled=, **kwargs)
+ # ServiceManager.create(name=, type=, enabled=, **kwargs)
self.services_mock.create.assert_called_with(
- None,
- identity_fakes.service_type,
- False,
+ name=None,
+ type=identity_fakes.service_type,
+ enabled=False,
)
collist = ('enabled', 'id', 'name', 'type')
diff --git a/openstackclient/tests/identity/v3/test_token.py b/openstackclient/tests/identity/v3/test_token.py
new file mode 100644
index 0000000..7e1d166
--- /dev/null
+++ b/openstackclient/tests/identity/v3/test_token.py
@@ -0,0 +1,79 @@
+# Copyright 2014 eBay Inc.
+#
+# 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.
+#
+
+from openstackclient.identity.v3 import token
+from openstackclient.tests.identity.v3 import fakes as identity_fakes
+
+
+class TestToken(identity_fakes.TestIdentityv3):
+
+ def setUp(self):
+ super(TestToken, self).setUp()
+
+ # Get a shortcut to the Service Catalog Mock
+ self.sc_mock = self.app.client_manager.identity.service_catalog
+ self.sc_mock.reset_mock()
+
+
+class TestTokenCreate(TestToken):
+
+ def setUp(self):
+ super(TestTokenCreate, self).setUp()
+
+ self.cmd = token.CreateToken(self.app, None)
+
+ def test_token_create_with_project_id(self):
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.sc_mock.get_token.return_value = \
+ identity_fakes.TOKEN_WITH_TENANT_ID
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.sc_mock.get_token.assert_called_with()
+
+ collist = ('expires', 'id', 'project_id', 'user_id')
+ self.assertEqual(columns, collist)
+ datalist = (
+ identity_fakes.token_expires,
+ identity_fakes.token_id,
+ identity_fakes.project_id,
+ identity_fakes.user_id,
+ )
+ self.assertEqual(data, datalist)
+
+ def test_token_create_with_domain_id(self):
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.sc_mock.get_token.return_value = \
+ identity_fakes.TOKEN_WITH_DOMAIN_ID
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.sc_mock.get_token.assert_called_with()
+
+ collist = ('domain_id', 'expires', 'id', 'user_id')
+ self.assertEqual(columns, collist)
+ datalist = (
+ identity_fakes.domain_id,
+ identity_fakes.token_expires,
+ identity_fakes.token_id,
+ identity_fakes.user_id,
+ )
+ self.assertEqual(data, datalist)
diff --git a/openstackclient/tests/identity/v3/test_user.py b/openstackclient/tests/identity/v3/test_user.py
index 4321b04..569d914 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
@@ -33,9 +34,9 @@ class TestUser(identity_fakes.TestIdentityv3):
self.projects_mock = self.app.client_manager.identity.projects
self.projects_mock.reset_mock()
- # Get a shortcut to the RoleManager Mock
- self.roles_mock = self.app.client_manager.identity.roles
- self.roles_mock.reset_mock()
+ # Get a shortcut to the GroupManager Mock
+ self.groups_mock = self.app.client_manager.identity.groups
+ self.groups_mock.reset_mock()
# Get a shortcut to the UserManager Mock
self.users_mock = self.app.client_manager.identity.users
@@ -84,6 +85,7 @@ class TestUserCreate(TestUser):
# Set expected values
kwargs = {
+ 'name': identity_fakes.user_name,
'default_project': None,
'description': None,
'domain': None,
@@ -92,10 +94,9 @@ class TestUserCreate(TestUser):
'password': None,
}
- # UserManager.create(name, domain=, project=, password=, email=,
+ # UserManager.create(name=, domain=, project=, password=, email=,
# description=, enabled=, default_project=)
self.users_mock.create.assert_called_with(
- identity_fakes.user_name,
**kwargs
)
@@ -118,6 +119,7 @@ class TestUserCreate(TestUser):
]
verifylist = [
('password', 'secret'),
+ ('password_prompt', False),
('enable', False),
('disable', False),
('name', identity_fakes.user_name),
@@ -129,6 +131,7 @@ class TestUserCreate(TestUser):
# Set expected values
kwargs = {
+ 'name': identity_fakes.user_name,
'default_project': None,
'description': None,
'domain': None,
@@ -136,10 +139,57 @@ class TestUserCreate(TestUser):
'enabled': True,
'password': 'secret',
}
- # UserManager.create(name, domain=, project=, password=, email=,
+ # UserManager.create(name=, domain=, project=, password=, email=,
# description=, enabled=, default_project=)
self.users_mock.create.assert_called_with(
+ **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_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 = {
+ 'name': identity_fakes.user_name,
+ '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(
**kwargs
)
@@ -173,6 +223,7 @@ class TestUserCreate(TestUser):
# Set expected values
kwargs = {
+ 'name': identity_fakes.user_name,
'default_project': None,
'description': None,
'domain': None,
@@ -180,10 +231,9 @@ class TestUserCreate(TestUser):
'enabled': True,
'password': None,
}
- # UserManager.create(name, domain=, project=, password=, email=,
+ # UserManager.create(name=, domain=, project=, password=, email=,
# description=, enabled=, default_project=)
self.users_mock.create.assert_called_with(
- identity_fakes.user_name,
**kwargs
)
@@ -232,6 +282,7 @@ class TestUserCreate(TestUser):
# Set expected values
kwargs = {
+ 'name': identity_fakes.user_name,
'default_project': identity_fakes.PROJECT_2['id'],
'description': None,
'domain': None,
@@ -239,10 +290,9 @@ class TestUserCreate(TestUser):
'enabled': True,
'password': None,
}
- # UserManager.create(name, domain=, project=, password=, email=,
+ # UserManager.create(name=, domain=, project=, password=, email=,
# description=, enabled=, default_project=)
self.users_mock.create.assert_called_with(
- identity_fakes.user_name,
**kwargs
)
@@ -276,6 +326,7 @@ class TestUserCreate(TestUser):
# Set expected values
kwargs = {
+ 'name': identity_fakes.user_name,
'default_project': None,
'description': None,
'domain': identity_fakes.domain_id,
@@ -283,10 +334,9 @@ class TestUserCreate(TestUser):
'enabled': True,
'password': None,
}
- # UserManager.create(name, domain=, project=, password=, email=,
+ # UserManager.create(name=, domain=, project=, password=, email=,
# description=, enabled=, default_project=)
self.users_mock.create.assert_called_with(
- identity_fakes.user_name,
**kwargs
)
@@ -319,6 +369,7 @@ class TestUserCreate(TestUser):
# Set expected values
kwargs = {
+ 'name': identity_fakes.user_name,
'default_project': None,
'description': None,
'domain': None,
@@ -326,10 +377,9 @@ class TestUserCreate(TestUser):
'enabled': True,
'password': None,
}
- # UserManager.create(name, domain=, project=, password=, email=,
+ # UserManager.create(name=, domain=, project=, password=, email=,
# description=, enabled=, default_project=)
self.users_mock.create.assert_called_with(
- identity_fakes.user_name,
**kwargs
)
@@ -362,6 +412,7 @@ class TestUserCreate(TestUser):
# Set expected values
kwargs = {
+ 'name': identity_fakes.user_name,
'default_project': None,
'description': None,
'domain': None,
@@ -369,9 +420,8 @@ class TestUserCreate(TestUser):
'enabled': False,
'password': None,
}
- # users.create(name, password, email, tenant_id=None, enabled=True)
+ # users.create(name=, password, email, tenant_id=None, enabled=True)
self.users_mock.create.assert_called_with(
- identity_fakes.user_name,
**kwargs
)
@@ -426,45 +476,30 @@ class TestUserList(TestUser):
def setUp(self):
super(TestUserList, self).setUp()
- self.domains_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.DOMAIN),
- loaded=True,
- )
-
- self.projects_mock.get.return_value = fakes.FakeResource(
+ self.users_mock.get.return_value = fakes.FakeResource(
None,
- copy.deepcopy(identity_fakes.PROJECT),
+ copy.deepcopy(identity_fakes.USER),
loaded=True,
)
- self.projects_mock.list.return_value = [
+ self.users_mock.list.return_value = [
fakes.FakeResource(
None,
- copy.deepcopy(identity_fakes.PROJECT),
+ copy.deepcopy(identity_fakes.USER),
loaded=True,
),
]
- self.roles_mock.list.return_value = [
- fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.ROLE),
- loaded=True,
- ),
- ]
+ self.domains_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.DOMAIN),
+ loaded=True,
+ )
- self.users_mock.get.return_value = fakes.FakeResource(
+ self.groups_mock.get.return_value = fakes.FakeResource(
None,
- copy.deepcopy(identity_fakes.USER),
+ copy.deepcopy(identity_fakes.GROUP),
loaded=True,
)
- self.users_mock.list.return_value = [
- fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.USER),
- loaded=True,
- ),
- ]
# Get the command object to test
self.cmd = user.ListUser(self.app, None)
@@ -477,29 +512,15 @@ class TestUserList(TestUser):
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
- self.users_mock.list.assert_called_with()
-
- collist = ('ID', 'Name')
- self.assertEqual(columns, collist)
- datalist = ((
- identity_fakes.user_id,
- identity_fakes.user_name,
- ), )
- self.assertEqual(tuple(data), datalist)
-
- def test_user_list_project(self):
- arglist = [
- '--project', identity_fakes.project_id,
- ]
- verifylist = [
- ('project', identity_fakes.project_id),
- ]
- parsed_args = self.check_parser(self.cmd, arglist, verifylist)
-
- # DisplayCommandBase.take_action() returns two tuples
- columns, data = self.cmd.take_action(parsed_args)
+ # Set expected values
+ kwargs = {
+ 'domain': None,
+ 'group': None,
+ }
- self.users_mock.list.assert_called_with()
+ self.users_mock.list.assert_called_with(
+ **kwargs
+ )
collist = ('ID', 'Name')
self.assertEqual(columns, collist)
@@ -507,7 +528,7 @@ class TestUserList(TestUser):
identity_fakes.user_id,
identity_fakes.user_name,
), )
- self.assertEqual(tuple(data), datalist)
+ self.assertEqual(datalist, tuple(data))
def test_user_list_domain(self):
arglist = [
@@ -521,94 +542,30 @@ class TestUserList(TestUser):
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
- self.users_mock.list.assert_called_with()
-
- collist = ('ID', 'Name')
- self.assertEqual(columns, collist)
- datalist = ((
- identity_fakes.user_id,
- identity_fakes.user_name,
- ), )
- self.assertEqual(tuple(data), datalist)
-
- def test_user_list_role_user(self):
- arglist = [
- '--role',
- identity_fakes.user_id,
- ]
- verifylist = [
- ('role', True),
- ('user', identity_fakes.user_id),
- ]
- parsed_args = self.check_parser(self.cmd, arglist, verifylist)
-
- # DisplayCommandBase.take_action() returns two tuples
- columns, data = self.cmd.take_action(parsed_args)
-
# Set expected values
kwargs = {
- 'domain': 'default',
- 'user': self.users_mock.get(),
+ 'domain': identity_fakes.domain_id,
+ 'group': None,
}
- # RoleManager.list(user=, group=, domain=, project=, **kwargs)
- self.roles_mock.list.assert_called_with(
+
+ self.users_mock.list.assert_called_with(
**kwargs
)
collist = ('ID', 'Name')
self.assertEqual(columns, collist)
datalist = ((
- identity_fakes.role_id,
- identity_fakes.role_name,
- ), )
- self.assertEqual(tuple(data), datalist)
-
- def test_user_list_role_domain(self):
- arglist = [
- '--domain', identity_fakes.domain_name,
- '--role',
identity_fakes.user_id,
- ]
- verifylist = [
- ('domain', identity_fakes.domain_name),
- ('role', True),
- ('user', identity_fakes.user_id),
- ]
- parsed_args = self.check_parser(self.cmd, arglist, verifylist)
-
- # DisplayCommandBase.take_action() returns two tuples
- columns, data = self.cmd.take_action(parsed_args)
-
- # Set expected values
- kwargs = {
- 'domain': self.domains_mock.get(),
- 'user': self.users_mock.get(),
- }
- # RoleManager.list(user=, group=, domain=, project=, **kwargs)
- self.roles_mock.list.assert_called_with(
- **kwargs
- )
-
- collist = ('ID', 'Name', 'Domain', 'User')
- self.assertEqual(columns, collist)
- datalist = ((
- identity_fakes.role_id,
- identity_fakes.role_name,
- identity_fakes.domain_name,
identity_fakes.user_name,
), )
- self.assertEqual(tuple(data), datalist)
+ self.assertEqual(datalist, tuple(data))
- def test_user_list_role_project(self):
+ def test_user_list_group(self):
arglist = [
- '--project', identity_fakes.project_name,
- '--role',
- identity_fakes.user_id,
+ '--group', identity_fakes.group_name,
]
verifylist = [
- ('project', identity_fakes.project_name),
- ('role', True),
- ('user', identity_fakes.user_id),
+ ('group', identity_fakes.group_name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -617,23 +574,21 @@ class TestUserList(TestUser):
# Set expected values
kwargs = {
- 'project': self.projects_mock.get(),
- 'user': self.users_mock.get(),
+ 'domain': None,
+ 'group': identity_fakes.group_id,
}
- # RoleManager.list(user=, group=, domain=, project=, **kwargs)
- self.roles_mock.list.assert_called_with(
+
+ self.users_mock.list.assert_called_with(
**kwargs
)
- collist = ('ID', 'Name', 'Project', 'User')
+ collist = ('ID', 'Name')
self.assertEqual(columns, collist)
datalist = ((
- identity_fakes.role_id,
- identity_fakes.role_name,
- identity_fakes.project_name,
+ identity_fakes.user_id,
identity_fakes.user_name,
), )
- self.assertEqual(tuple(data), datalist)
+ self.assertEqual(datalist, tuple(data))
def test_user_list_long(self):
arglist = [
@@ -647,7 +602,15 @@ class TestUserList(TestUser):
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
- self.users_mock.list.assert_called_with()
+ # Set expected values
+ kwargs = {
+ 'domain': None,
+ 'group': None,
+ }
+
+ self.users_mock.list.assert_called_with(
+ **kwargs
+ )
collist = (
'ID',
@@ -668,7 +631,7 @@ class TestUserList(TestUser):
identity_fakes.user_email,
True,
), )
- self.assertEqual(tuple(data), datalist)
+ self.assertEqual(datalist, tuple(data))
class TestUserSet(TestUser):
@@ -761,6 +724,7 @@ class TestUserSet(TestUser):
verifylist = [
('name', None),
('password', 'secret'),
+ ('password_prompt', False),
('email', None),
('domain', None),
('project', None),
@@ -785,6 +749,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/tests/image/__init__.py b/openstackclient/tests/image/__init__.py
index ebf59b3..e69de29 100644
--- a/openstackclient/tests/image/__init__.py
+++ b/openstackclient/tests/image/__init__.py
@@ -1,14 +0,0 @@
-# Copyright 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.
-#
diff --git a/openstackclient/tests/image/v1/__init__.py b/openstackclient/tests/image/v1/__init__.py
index ebf59b3..e69de29 100644
--- a/openstackclient/tests/image/v1/__init__.py
+++ b/openstackclient/tests/image/v1/__init__.py
@@ -1,14 +0,0 @@
-# Copyright 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.
-#
diff --git a/openstackclient/tests/image/v1/fakes.py b/openstackclient/tests/image/v1/fakes.py
index ea2af84..972e641 100644
--- a/openstackclient/tests/image/v1/fakes.py
+++ b/openstackclient/tests/image/v1/fakes.py
@@ -17,16 +17,38 @@ import mock
from openstackclient.tests import fakes
from openstackclient.tests import utils
+from openstackclient.tests.volume.v1 import fakes as volume_fakes
image_id = 'im1'
image_name = 'graven'
+image_owner = 'baal'
+image_protected = False
+image_public = True
+image_properties = {
+ 'Alpha': 'a',
+ 'Beta': 'b',
+ 'Gamma': 'g',
+}
+image_properties_str = "{'Alpha': 'a', 'Beta': 'b', 'Gamma': 'g'}"
+image_data = 'line 1\nline 2\n'
IMAGE = {
'id': image_id,
- 'name': image_name
+ 'name': image_name,
+ 'container_format': '',
+ 'disk_format': '',
+ 'owner': image_owner,
+ 'min_disk': 0,
+ 'min_ram': 0,
+ 'is_public': image_public,
+ 'protected': image_protected,
+ 'properties': image_properties,
}
+IMAGE_columns = tuple(sorted(IMAGE))
+IMAGE_data = tuple((IMAGE[x] for x in sorted(IMAGE)))
+
class FakeImagev1Client(object):
def __init__(self, **kwargs):
@@ -44,3 +66,7 @@ class TestImagev1(utils.TestCommand):
endpoint=fakes.AUTH_URL,
token=fakes.AUTH_TOKEN,
)
+ self.app.client_manager.volume = volume_fakes.FakeVolumev1Client(
+ endpoint=fakes.AUTH_URL,
+ token=fakes.AUTH_TOKEN,
+ )
diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py
index a410674..b746a53 100644
--- a/openstackclient/tests/image/v1/test_image.py
+++ b/openstackclient/tests/image/v1/test_image.py
@@ -14,7 +14,9 @@
#
import copy
+import mock
+from openstackclient.common import exceptions
from openstackclient.image.v1 import image
from openstackclient.tests import fakes
from openstackclient.tests.image.v1 import fakes as image_fakes
@@ -30,6 +32,234 @@ class TestImage(image_fakes.TestImagev1):
self.images_mock.reset_mock()
+class TestImageCreate(TestImage):
+
+ def setUp(self):
+ super(TestImageCreate, self).setUp()
+
+ self.images_mock.create.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(image_fakes.IMAGE),
+ loaded=True,
+ )
+ # This is the return value for utils.find_resource()
+ self.images_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(image_fakes.IMAGE),
+ loaded=True,
+ )
+ self.images_mock.update.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(image_fakes.IMAGE),
+ loaded=True,
+ )
+
+ # Get the command object to test
+ self.cmd = image.CreateImage(self.app, None)
+
+ def test_image_reserve_no_options(self):
+ mock_exception = {
+ 'find.side_effect': exceptions.CommandError('x'),
+ 'get.side_effect': exceptions.CommandError('x'),
+ }
+ self.images_mock.configure_mock(**mock_exception)
+ arglist = [
+ image_fakes.image_name,
+ ]
+ verifylist = [
+ ('container_format', image.DEFAULT_CONTAINER_FORMAT),
+ ('disk_format', image.DEFAULT_DISK_FORMAT),
+ ('name', image_fakes.image_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # ImageManager.create(name=, **)
+ self.images_mock.create.assert_called_with(
+ name=image_fakes.image_name,
+ container_format=image.DEFAULT_CONTAINER_FORMAT,
+ disk_format=image.DEFAULT_DISK_FORMAT,
+ data=mock.ANY,
+ )
+
+ # Verify update() was not called, if it was show the args
+ self.assertEqual(self.images_mock.update.call_args_list, [])
+
+ self.assertEqual(image_fakes.IMAGE_columns, columns)
+ self.assertEqual(image_fakes.IMAGE_data, data)
+
+ def test_image_reserve_options(self):
+ mock_exception = {
+ 'find.side_effect': exceptions.CommandError('x'),
+ 'get.side_effect': exceptions.CommandError('x'),
+ }
+ self.images_mock.configure_mock(**mock_exception)
+ arglist = [
+ '--container-format', 'ovf',
+ '--disk-format', 'fs',
+ '--min-disk', '10',
+ '--min-ram', '4',
+ '--protected',
+ '--private',
+ image_fakes.image_name,
+ ]
+ verifylist = [
+ ('container_format', 'ovf'),
+ ('disk_format', 'fs'),
+ ('min_disk', 10),
+ ('min_ram', 4),
+ ('protected', True),
+ ('unprotected', False),
+ ('public', False),
+ ('private', True),
+ ('name', image_fakes.image_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # ImageManager.create(name=, **)
+ self.images_mock.create.assert_called_with(
+ name=image_fakes.image_name,
+ container_format='ovf',
+ disk_format='fs',
+ min_disk=10,
+ min_ram=4,
+ protected=True,
+ is_public=False,
+ data=mock.ANY,
+ )
+
+ # Verify update() was not called, if it was show the args
+ self.assertEqual(self.images_mock.update.call_args_list, [])
+
+ self.assertEqual(image_fakes.IMAGE_columns, columns)
+ self.assertEqual(image_fakes.IMAGE_data, data)
+
+ @mock.patch('__builtin__.open')
+ def test_image_create_file(self, open_mock):
+ mock_exception = {
+ 'find.side_effect': exceptions.CommandError('x'),
+ 'get.side_effect': exceptions.CommandError('x'),
+ }
+ self.images_mock.configure_mock(**mock_exception)
+ open_mock.return_value = image_fakes.image_data
+ arglist = [
+ '--file', 'filer',
+ '--unprotected',
+ '--public',
+ '--property', 'Alpha=1',
+ '--property', 'Beta=2',
+ image_fakes.image_name,
+ ]
+ verifylist = [
+ ('file', 'filer'),
+ ('protected', False),
+ ('unprotected', True),
+ ('public', True),
+ ('private', False),
+ ('properties', {'Alpha': '1', 'Beta': '2'}),
+ ('name', image_fakes.image_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ open_mock.assert_called_with('filer', 'rb')
+
+ # ImageManager.get(name)
+ self.images_mock.get.assert_called_with(image_fakes.image_name)
+
+ # ImageManager.create(name=, **)
+ self.images_mock.create.assert_called_with(
+ name=image_fakes.image_name,
+ container_format=image.DEFAULT_CONTAINER_FORMAT,
+ disk_format=image.DEFAULT_DISK_FORMAT,
+ protected=False,
+ is_public=True,
+ properties={
+ 'Alpha': '1',
+ 'Beta': '2',
+ },
+ data=image_fakes.image_data,
+ )
+
+ # Verify update() was not called, if it was show the args
+ self.assertEqual(self.images_mock.update.call_args_list, [])
+
+ self.assertEqual(image_fakes.IMAGE_columns, columns)
+ self.assertEqual(image_fakes.IMAGE_data, data)
+
+ def test_image_create_volume(self):
+ # Set up VolumeManager Mock
+ volumes_mock = self.app.client_manager.volume.volumes
+ volumes_mock.reset_mock()
+ volumes_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy({'id': 'vol1', 'name': 'volly'}),
+ loaded=True,
+ )
+ response = {
+ "id": 'volume_id',
+ "updated_at": 'updated_at',
+ "status": 'uploading',
+ "display_description": 'desc',
+ "size": 'size',
+ "volume_type": 'volume_type',
+ "image_id": 'image1',
+ "container_format": image.DEFAULT_CONTAINER_FORMAT,
+ "disk_format": image.DEFAULT_DISK_FORMAT,
+ "image_name": image_fakes.image_name,
+ }
+ full_response = {"os-volume_upload_image": response}
+ volumes_mock.upload_to_image.return_value = (201, full_response)
+
+ arglist = [
+ '--volume', 'volly',
+ image_fakes.image_name,
+ ]
+ verifylist = [
+ ('private', False),
+ ('protected', False),
+ ('public', False),
+ ('unprotected', False),
+ ('volume', 'volly'),
+ ('force', False),
+ ('name', image_fakes.image_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # VolumeManager.upload_to_image(volume, force, image_name,
+ # container_format, disk_format)
+ volumes_mock.upload_to_image.assert_called_with(
+ 'vol1',
+ False,
+ image_fakes.image_name,
+ 'bare',
+ 'raw',
+ )
+
+ # ImageManager.update(image_id, remove_props=, **)
+ self.images_mock.update.assert_called_with(
+ image_fakes.image_id,
+ name=image_fakes.image_name,
+ container_format=image.DEFAULT_CONTAINER_FORMAT,
+ disk_format=image.DEFAULT_DISK_FORMAT,
+ properties=image_fakes.image_properties,
+ volume='volly',
+ )
+
+ self.assertEqual(image_fakes.IMAGE_columns, columns)
+ self.assertEqual(image_fakes.IMAGE_data, data)
+
+
class TestImageDelete(TestImage):
def setUp(self):
@@ -61,3 +291,158 @@ class TestImageDelete(TestImage):
self.images_mock.delete.assert_called_with(
image_fakes.image_id,
)
+
+
+class TestImageSet(TestImage):
+
+ def setUp(self):
+ super(TestImageSet, self).setUp()
+
+ # This is the return value for utils.find_resource()
+ self.images_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(image_fakes.IMAGE),
+ loaded=True,
+ )
+ self.images_mock.update.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(image_fakes.IMAGE),
+ loaded=True,
+ )
+
+ # Get the command object to test
+ self.cmd = image.SetImage(self.app, None)
+
+ def test_image_set_no_options(self):
+ arglist = [
+ image_fakes.image_name,
+ ]
+ verifylist = [
+ ('image', image_fakes.image_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ self.cmd.take_action(parsed_args)
+
+ # Verify update() was not called, if it was show the args
+ self.assertEqual(self.images_mock.update.call_args_list, [])
+
+ def test_image_set_options(self):
+ arglist = [
+ '--name', 'new-name',
+ '--owner', 'new-owner',
+ '--min-disk', '2',
+ '--min-ram', '4',
+ image_fakes.image_name,
+ ]
+ verifylist = [
+ ('name', 'new-name'),
+ ('owner', 'new-owner'),
+ ('min_disk', 2),
+ ('min_ram', 4),
+ ('image', image_fakes.image_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ kwargs = {
+ 'name': 'new-name',
+ 'owner': 'new-owner',
+ 'min_disk': 2,
+ 'min_ram': 4,
+ }
+ # ImageManager.update(image, **kwargs)
+ self.images_mock.update.assert_called_with(
+ image_fakes.image_id,
+ **kwargs
+ )
+
+ self.assertEqual(image_fakes.IMAGE_columns, columns)
+ self.assertEqual(image_fakes.IMAGE_data, data)
+
+ def test_image_set_bools1(self):
+ arglist = [
+ '--protected',
+ '--private',
+ image_fakes.image_name,
+ ]
+ verifylist = [
+ ('protected', True),
+ ('unprotected', False),
+ ('public', False),
+ ('private', True),
+ ('image', image_fakes.image_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ self.cmd.take_action(parsed_args)
+
+ kwargs = {
+ 'protected': True,
+ 'is_public': False,
+ }
+ # ImageManager.update(image, **kwargs)
+ self.images_mock.update.assert_called_with(
+ image_fakes.image_id,
+ **kwargs
+ )
+
+ def test_image_set_bools2(self):
+ arglist = [
+ '--unprotected',
+ '--public',
+ image_fakes.image_name,
+ ]
+ verifylist = [
+ ('protected', False),
+ ('unprotected', True),
+ ('public', True),
+ ('private', False),
+ ('image', image_fakes.image_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ self.cmd.take_action(parsed_args)
+
+ kwargs = {
+ 'protected': False,
+ 'is_public': True,
+ }
+ # ImageManager.update(image, **kwargs)
+ self.images_mock.update.assert_called_with(
+ image_fakes.image_id,
+ **kwargs
+ )
+
+ def test_image_set_properties(self):
+ arglist = [
+ '--property', 'Alpha=1',
+ '--property', 'Beta=2',
+ image_fakes.image_name,
+ ]
+ verifylist = [
+ ('properties', {'Alpha': '1', 'Beta': '2'}),
+ ('image', image_fakes.image_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ self.cmd.take_action(parsed_args)
+
+ kwargs = {
+ 'properties': {
+ 'Alpha': '1',
+ 'Beta': '2',
+ 'Gamma': 'g',
+ },
+ }
+ # ImageManager.update(image, **kwargs)
+ self.images_mock.update.assert_called_with(
+ image_fakes.image_id,
+ **kwargs
+ )
diff --git a/openstackclient/tests/image/v2/__init__.py b/openstackclient/tests/image/v2/__init__.py
index ebf59b3..e69de29 100644
--- a/openstackclient/tests/image/v2/__init__.py
+++ b/openstackclient/tests/image/v2/__init__.py
@@ -1,14 +0,0 @@
-# Copyright 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.
-#
diff --git a/openstackclient/tests/object/__init__.py b/openstackclient/tests/object/__init__.py
index 02be10c..e69de29 100644
--- a/openstackclient/tests/object/__init__.py
+++ b/openstackclient/tests/object/__init__.py
@@ -1,12 +0,0 @@
-# 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.
-#
diff --git a/openstackclient/tests/object/v1/__init__.py b/openstackclient/tests/object/v1/__init__.py
index 02be10c..e69de29 100644
--- a/openstackclient/tests/object/v1/__init__.py
+++ b/openstackclient/tests/object/v1/__init__.py
@@ -1,12 +0,0 @@
-# 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.
-#
diff --git a/openstackclient/tests/object/v1/lib/__init__.py b/openstackclient/tests/object/v1/lib/__init__.py
index 02be10c..e69de29 100644
--- a/openstackclient/tests/object/v1/lib/__init__.py
+++ b/openstackclient/tests/object/v1/lib/__init__.py
@@ -1,12 +0,0 @@
-# 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.
-#
diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py
index be9c5d4..9253f70 100644
--- a/openstackclient/tests/test_shell.py
+++ b/openstackclient/tests/test_shell.py
@@ -20,13 +20,19 @@ from openstackclient import shell
from openstackclient.tests import utils
-DEFAULT_USERNAME = "username"
-DEFAULT_PASSWORD = "password"
+DEFAULT_AUTH_URL = "http://127.0.0.1:5000/v2.0/"
DEFAULT_PROJECT_ID = "xxxx-yyyy-zzzz"
DEFAULT_PROJECT_NAME = "project"
-DEFAULT_TOKEN = "token"
+DEFAULT_DOMAIN_ID = "aaaa-bbbb-cccc"
+DEFAULT_DOMAIN_NAME = "domain"
+DEFAULT_USER_DOMAIN_ID = "aaaa-bbbb-cccc"
+DEFAULT_USER_DOMAIN_NAME = "domain"
+DEFAULT_PROJECT_DOMAIN_ID = "aaaa-bbbb-cccc"
+DEFAULT_PROJECT_DOMAIN_NAME = "domain"
+DEFAULT_USERNAME = "username"
+DEFAULT_PASSWORD = "password"
DEFAULT_REGION_NAME = "ZZ9_Plural_Z_Alpha"
-DEFAULT_AUTH_URL = "http://127.0.0.1:5000/v2.0/"
+DEFAULT_TOKEN = "token"
DEFAULT_SERVICE_URL = "http://127.0.0.1:8771/v3.0/"
DEFAULT_COMPUTE_API_VERSION = "2"
@@ -78,6 +84,18 @@ class TestShell(utils.TestCase):
default_args["project_id"])
self.assertEqual(_shell.options.os_project_name,
default_args["project_name"])
+ self.assertEqual(_shell.options.os_domain_id,
+ default_args["domain_id"])
+ self.assertEqual(_shell.options.os_domain_name,
+ default_args["domain_name"])
+ self.assertEqual(_shell.options.os_user_domain_id,
+ default_args["user_domain_id"])
+ self.assertEqual(_shell.options.os_user_domain_name,
+ default_args["user_domain_name"])
+ self.assertEqual(_shell.options.os_project_domain_id,
+ default_args["project_domain_id"])
+ self.assertEqual(_shell.options.os_project_domain_name,
+ default_args["project_domain_name"])
self.assertEqual(_shell.options.os_username,
default_args["username"])
self.assertEqual(_shell.options.os_password,
@@ -151,6 +169,12 @@ class TestShellPasswordAuth(TestShell):
"auth_url": DEFAULT_AUTH_URL,
"project_id": "",
"project_name": "",
+ "domain_id": "",
+ "domain_name": "",
+ "user_domain_id": "",
+ "user_domain_name": "",
+ "project_domain_id": "",
+ "project_domain_name": "",
"username": "",
"password": "",
"region_name": ""
@@ -163,6 +187,12 @@ class TestShellPasswordAuth(TestShell):
"auth_url": "",
"project_id": DEFAULT_PROJECT_ID,
"project_name": "",
+ "domain_id": "",
+ "domain_name": "",
+ "user_domain_id": "",
+ "user_domain_name": "",
+ "project_domain_id": "",
+ "project_domain_name": "",
"username": "",
"password": "",
"region_name": ""
@@ -175,6 +205,12 @@ class TestShellPasswordAuth(TestShell):
"auth_url": "",
"project_id": "",
"project_name": DEFAULT_PROJECT_NAME,
+ "domain_id": "",
+ "domain_name": "",
+ "user_domain_id": "",
+ "user_domain_name": "",
+ "project_domain_id": "",
+ "project_domain_name": "",
"username": "",
"password": "",
"region_name": ""
@@ -187,6 +223,12 @@ class TestShellPasswordAuth(TestShell):
"auth_url": "",
"project_id": DEFAULT_PROJECT_ID,
"project_name": "",
+ "domain_id": "",
+ "domain_name": "",
+ "user_domain_id": "",
+ "user_domain_name": "",
+ "project_domain_id": "",
+ "project_domain_name": "",
"username": "",
"password": "",
"region_name": ""
@@ -199,6 +241,120 @@ class TestShellPasswordAuth(TestShell):
"auth_url": "",
"project_id": "",
"project_name": DEFAULT_PROJECT_NAME,
+ "domain_id": "",
+ "domain_name": "",
+ "user_domain_id": "",
+ "user_domain_name": "",
+ "project_domain_id": "",
+ "project_domain_name": "",
+ "username": "",
+ "password": "",
+ "region_name": ""
+ }
+ self._assert_password_auth(flag, kwargs)
+
+ def test_only_domain_id_flow(self):
+ flag = "--os-domain-id " + DEFAULT_DOMAIN_ID
+ kwargs = {
+ "auth_url": "",
+ "project_id": "",
+ "project_name": "",
+ "domain_id": DEFAULT_DOMAIN_ID,
+ "domain_name": "",
+ "user_domain_id": "",
+ "user_domain_name": "",
+ "project_domain_id": "",
+ "project_domain_name": "",
+ "username": "",
+ "password": "",
+ "region_name": ""
+ }
+ self._assert_password_auth(flag, kwargs)
+
+ def test_only_domain_name_flow(self):
+ flag = "--os-domain-name " + DEFAULT_DOMAIN_NAME
+ kwargs = {
+ "auth_url": "",
+ "project_id": "",
+ "project_name": "",
+ "domain_id": "",
+ "domain_name": DEFAULT_DOMAIN_NAME,
+ "user_domain_id": "",
+ "user_domain_name": "",
+ "project_domain_id": "",
+ "project_domain_name": "",
+ "username": "",
+ "password": "",
+ "region_name": ""
+ }
+ self._assert_password_auth(flag, kwargs)
+
+ def test_only_user_domain_id_flow(self):
+ flag = "--os-user-domain-id " + DEFAULT_USER_DOMAIN_ID
+ kwargs = {
+ "auth_url": "",
+ "project_id": "",
+ "project_name": "",
+ "domain_id": "",
+ "domain_name": "",
+ "user_domain_id": DEFAULT_USER_DOMAIN_ID,
+ "user_domain_name": "",
+ "project_domain_id": "",
+ "project_domain_name": "",
+ "username": "",
+ "password": "",
+ "region_name": ""
+ }
+ self._assert_password_auth(flag, kwargs)
+
+ def test_only_user_domain_name_flow(self):
+ flag = "--os-user-domain-name " + DEFAULT_USER_DOMAIN_NAME
+ kwargs = {
+ "auth_url": "",
+ "project_id": "",
+ "project_name": "",
+ "domain_id": "",
+ "domain_name": "",
+ "user_domain_id": "",
+ "user_domain_name": DEFAULT_USER_DOMAIN_NAME,
+ "project_domain_id": "",
+ "project_domain_name": "",
+ "username": "",
+ "password": "",
+ "region_name": ""
+ }
+ self._assert_password_auth(flag, kwargs)
+
+ def test_only_project_domain_id_flow(self):
+ flag = "--os-project-domain-id " + DEFAULT_PROJECT_DOMAIN_ID
+ kwargs = {
+ "auth_url": "",
+ "project_id": "",
+ "project_name": "",
+ "domain_id": "",
+ "domain_name": "",
+ "user_domain_id": "",
+ "user_domain_name": "",
+ "project_domain_id": DEFAULT_PROJECT_DOMAIN_ID,
+ "project_domain_name": "",
+ "username": "",
+ "password": "",
+ "region_name": ""
+ }
+ self._assert_password_auth(flag, kwargs)
+
+ def test_only_project_domain_name_flow(self):
+ flag = "--os-project-domain-name " + DEFAULT_PROJECT_DOMAIN_NAME
+ kwargs = {
+ "auth_url": "",
+ "project_id": "",
+ "project_name": "",
+ "domain_id": "",
+ "domain_name": "",
+ "user_domain_id": "",
+ "user_domain_name": "",
+ "project_domain_id": "",
+ "project_domain_name": DEFAULT_PROJECT_DOMAIN_NAME,
"username": "",
"password": "",
"region_name": ""
@@ -211,6 +367,12 @@ class TestShellPasswordAuth(TestShell):
"auth_url": "",
"project_id": "",
"project_name": "",
+ "domain_id": "",
+ "domain_name": "",
+ "user_domain_id": "",
+ "user_domain_name": "",
+ "project_domain_id": "",
+ "project_domain_name": "",
"username": DEFAULT_USERNAME,
"password": "",
"region_name": ""
@@ -223,6 +385,12 @@ class TestShellPasswordAuth(TestShell):
"auth_url": "",
"project_id": "",
"project_name": "",
+ "domain_id": "",
+ "domain_name": "",
+ "user_domain_id": "",
+ "user_domain_name": "",
+ "project_domain_id": "",
+ "project_domain_name": "",
"username": "",
"password": DEFAULT_PASSWORD,
"region_name": ""
@@ -235,6 +403,12 @@ class TestShellPasswordAuth(TestShell):
"auth_url": "",
"project_id": "",
"project_name": "",
+ "domain_id": "",
+ "domain_name": "",
+ "user_domain_id": "",
+ "user_domain_name": "",
+ "project_domain_id": "",
+ "project_domain_name": "",
"username": "",
"password": "",
"region_name": DEFAULT_REGION_NAME
diff --git a/openstackclient/tests/volume/__init__.py b/openstackclient/tests/volume/__init__.py
index ebf59b3..e69de29 100644
--- a/openstackclient/tests/volume/__init__.py
+++ b/openstackclient/tests/volume/__init__.py
@@ -1,14 +0,0 @@
-# Copyright 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.
-#
diff --git a/openstackclient/tests/volume/test_find_resource.py b/openstackclient/tests/volume/test_find_resource.py
new file mode 100644
index 0000000..8539070
--- /dev/null
+++ b/openstackclient/tests/volume/test_find_resource.py
@@ -0,0 +1,71 @@
+# Copyright 2013 Nebula Inc.
+#
+# 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 cinderclient.v1 import volume_snapshots
+from cinderclient.v1 import volumes
+
+from openstackclient.common import exceptions
+from openstackclient.common import utils
+from openstackclient.tests import utils as test_utils
+
+
+ID = '1after909'
+NAME = 'PhilSpector'
+
+
+class TestFindResourceVolumes(test_utils.TestCase):
+
+ def setUp(self):
+ super(TestFindResourceVolumes, self).setUp()
+ api = mock.Mock()
+ api.client = mock.Mock()
+ api.client.get = mock.Mock()
+ resp = mock.Mock()
+ body = {"volumes": [{"id": ID, 'display_name': NAME}]}
+ api.client.get.side_effect = [Exception("Not found"), (resp, body)]
+ self.manager = volumes.VolumeManager(api)
+
+ def test_find(self):
+ result = utils.find_resource(self.manager, NAME)
+ self.assertEqual(ID, result.id)
+ self.assertEqual(NAME, result.display_name)
+
+ def test_not_find(self):
+ self.assertRaises(exceptions.CommandError, utils.find_resource,
+ self.manager, 'GeorgeMartin')
+
+
+class TestFindResourceVolumeSnapshots(test_utils.TestCase):
+
+ def setUp(self):
+ super(TestFindResourceVolumeSnapshots, self).setUp()
+ api = mock.Mock()
+ api.client = mock.Mock()
+ api.client.get = mock.Mock()
+ resp = mock.Mock()
+ body = {"snapshots": [{"id": ID, 'display_name': NAME}]}
+ api.client.get.side_effect = [Exception("Not found"), (resp, body)]
+ self.manager = volume_snapshots.SnapshotManager(api)
+
+ def test_find(self):
+ result = utils.find_resource(self.manager, NAME)
+ self.assertEqual(ID, result.id)
+ self.assertEqual(NAME, result.display_name)
+
+ def test_not_find(self):
+ self.assertRaises(exceptions.CommandError, utils.find_resource,
+ self.manager, 'GeorgeMartin')
diff --git a/openstackclient/tests/volume/v1/__init__.py b/openstackclient/tests/volume/v1/__init__.py
index c534c01..e69de29 100644
--- a/openstackclient/tests/volume/v1/__init__.py
+++ b/openstackclient/tests/volume/v1/__init__.py
@@ -1,14 +0,0 @@
-# Copyright 2013 OpenStack Foundation
-#
-# 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.
-#
diff --git a/openstackclient/tests/volume/v1/fakes.py b/openstackclient/tests/volume/v1/fakes.py
index b25dfaf..c0ffbd3 100644
--- a/openstackclient/tests/volume/v1/fakes.py
+++ b/openstackclient/tests/volume/v1/fakes.py
@@ -24,7 +24,14 @@ volume_id = 'vvvvvvvv-vvvv-vvvv-vvvvvvvv'
volume_name = 'nigel'
volume_description = 'Nigel Tufnel'
volume_size = 120
-volume_metadata = {}
+volume_type = 'to-eleven'
+volume_zone = 'stonehenge'
+volume_metadata = {
+ 'Alpha': 'a',
+ 'Beta': 'b',
+ 'Gamma': 'g',
+}
+volume_metadata_str = "Alpha='a', Beta='b', Gamma='g'"
VOLUME = {
'id': volume_id,
@@ -32,10 +39,32 @@ VOLUME = {
'display_description': volume_description,
'size': volume_size,
'status': '',
- 'attach_status': 'detatched',
+ 'attach_status': 'detached',
+ 'availability_zone': volume_zone,
+ 'volume_type': volume_type,
'metadata': volume_metadata,
}
+extension_name = 'SchedulerHints'
+extension_namespace = 'http://docs.openstack.org/'\
+ 'block-service/ext/scheduler-hints/api/v2'
+extension_description = 'Pass arbitrary key/value'\
+ 'pairs to the scheduler.'
+extension_updated = '2014-02-07T12:00:0-00:00'
+extension_alias = 'OS-SCH-HNT'
+extension_links = '[{"href":'\
+ '"https://github.com/openstack/block-api", "type":'\
+ ' "text/html", "rel": "describedby"}]'
+
+EXTENSION = {
+ 'name': extension_name,
+ 'namespace': extension_namespace,
+ 'description': extension_description,
+ 'updated': extension_updated,
+ 'alias': extension_alias,
+ 'links': extension_links,
+}
+
class FakeVolumev1Client(object):
def __init__(self, **kwargs):
@@ -43,6 +72,8 @@ class FakeVolumev1Client(object):
self.volumes.resource_class = fakes.FakeResource(None, {})
self.services = mock.Mock()
self.services.resource_class = fakes.FakeResource(None, {})
+ self.extensions = mock.Mock()
+ self.extensions.resource_class = fakes.FakeResource(None, {})
self.auth_token = kwargs['token']
self.management_url = kwargs['endpoint']
diff --git a/openstackclient/tests/volume/v1/test_volume.py b/openstackclient/tests/volume/v1/test_volume.py
index 4e033df..d881598 100644
--- a/openstackclient/tests/volume/v1/test_volume.py
+++ b/openstackclient/tests/volume/v1/test_volume.py
@@ -71,10 +71,6 @@ class TestVolumeCreate(TestVolume):
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
- # Set expected values
- #kwargs = {
- # 'metadata': volume_fakes.volume_metadata,
- #}
# VolumeManager.create(size, snapshot_id=, source_volid=,
# display_name=, display_description=,
# volume_type=, user_id=,
@@ -96,22 +92,90 @@ class TestVolumeCreate(TestVolume):
collist = (
'attach_status',
+ 'availability_zone',
'display_description',
'display_name',
'id',
'properties',
'size',
'status',
+ 'type',
)
self.assertEqual(columns, collist)
datalist = (
- 'detatched',
+ 'detached',
+ volume_fakes.volume_zone,
volume_fakes.volume_description,
volume_fakes.volume_name,
volume_fakes.volume_id,
+ volume_fakes.volume_metadata_str,
+ volume_fakes.volume_size,
'',
+ volume_fakes.volume_type,
+ )
+ self.assertEqual(data, datalist)
+
+ def test_volume_create_options(self):
+ arglist = [
+ '--size', str(volume_fakes.volume_size),
+ '--description', volume_fakes.volume_description,
+ '--type', volume_fakes.volume_type,
+ '--availability-zone', volume_fakes.volume_zone,
+ volume_fakes.volume_name,
+ ]
+ verifylist = [
+ ('size', volume_fakes.volume_size),
+ ('description', volume_fakes.volume_description),
+ ('type', volume_fakes.volume_type),
+ ('availability_zone', volume_fakes.volume_zone),
+ ('name', volume_fakes.volume_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # VolumeManager.create(size, snapshot_id=, source_volid=,
+ # display_name=, display_description=,
+ # volume_type=, user_id=,
+ # project_id=, availability_zone=,
+ # metadata=, imageRef=)
+ self.volumes_mock.create.assert_called_with(
+ volume_fakes.volume_size,
+ None,
+ None,
+ volume_fakes.volume_name,
+ volume_fakes.volume_description,
+ volume_fakes.volume_type,
+ None,
+ None,
+ volume_fakes.volume_zone,
+ None,
+ None,
+ )
+
+ collist = (
+ 'attach_status',
+ 'availability_zone',
+ 'display_description',
+ 'display_name',
+ 'id',
+ 'properties',
+ 'size',
+ 'status',
+ 'type',
+ )
+ self.assertEqual(columns, collist)
+ datalist = (
+ 'detached',
+ volume_fakes.volume_zone,
+ volume_fakes.volume_description,
+ volume_fakes.volume_name,
+ volume_fakes.volume_id,
+ volume_fakes.volume_metadata_str,
volume_fakes.volume_size,
'',
+ volume_fakes.volume_type,
)
self.assertEqual(data, datalist)
@@ -146,10 +210,6 @@ class TestVolumeCreate(TestVolume):
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
- # Set expected values
- #kwargs = {
- # 'metadata': volume_fakes.volume_metadata,
- #}
# VolumeManager.create(size, snapshot_id=, source_volid=,
# display_name=, display_description=,
# volume_type=, user_id=,
@@ -172,22 +232,26 @@ class TestVolumeCreate(TestVolume):
collist = (
'attach_status',
+ 'availability_zone',
'display_description',
'display_name',
'id',
'properties',
'size',
'status',
+ 'type',
)
self.assertEqual(columns, collist)
datalist = (
- 'detatched',
+ 'detached',
+ volume_fakes.volume_zone,
volume_fakes.volume_description,
volume_fakes.volume_name,
volume_fakes.volume_id,
- '',
+ volume_fakes.volume_metadata_str,
volume_fakes.volume_size,
'',
+ volume_fakes.volume_type,
)
self.assertEqual(data, datalist)
@@ -222,10 +286,6 @@ class TestVolumeCreate(TestVolume):
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
- # Set expected values
- #kwargs = {
- # 'metadata': volume_fakes.volume_metadata,
- #}
# VolumeManager.create(size, snapshot_id=, source_volid=,
# display_name=, display_description=,
# volume_type=, user_id=,
@@ -248,21 +308,86 @@ class TestVolumeCreate(TestVolume):
collist = (
'attach_status',
+ 'availability_zone',
'display_description',
'display_name',
'id',
'properties',
'size',
'status',
+ 'type',
)
self.assertEqual(columns, collist)
datalist = (
- 'detatched',
+ 'detached',
+ volume_fakes.volume_zone,
volume_fakes.volume_description,
volume_fakes.volume_name,
volume_fakes.volume_id,
+ volume_fakes.volume_metadata_str,
+ volume_fakes.volume_size,
'',
+ volume_fakes.volume_type,
+ )
+ self.assertEqual(data, datalist)
+
+ def test_volume_create_properties(self):
+ arglist = [
+ '--property', 'Alpha=a',
+ '--property', 'Beta=b',
+ '--size', str(volume_fakes.volume_size),
+ volume_fakes.volume_name,
+ ]
+ verifylist = [
+ ('property', {'Alpha': 'a', 'Beta': 'b'}),
+ ('size', volume_fakes.volume_size),
+ ('name', volume_fakes.volume_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # VolumeManager.create(size, snapshot_id=, source_volid=,
+ # display_name=, display_description=,
+ # volume_type=, user_id=,
+ # project_id=, availability_zone=,
+ # metadata=, imageRef=)
+ self.volumes_mock.create.assert_called_with(
+ volume_fakes.volume_size,
+ None,
+ None,
+ volume_fakes.volume_name,
+ None,
+ None,
+ None,
+ None,
+ None,
+ {'Alpha': 'a', 'Beta': 'b'},
+ None,
+ )
+
+ collist = (
+ 'attach_status',
+ 'availability_zone',
+ 'display_description',
+ 'display_name',
+ 'id',
+ 'properties',
+ 'size',
+ 'status',
+ 'type',
+ )
+ self.assertEqual(columns, collist)
+ datalist = (
+ 'detached',
+ volume_fakes.volume_zone,
+ volume_fakes.volume_description,
+ volume_fakes.volume_name,
+ volume_fakes.volume_id,
+ volume_fakes.volume_metadata_str,
volume_fakes.volume_size,
'',
+ volume_fakes.volume_type,
)
self.assertEqual(data, datalist)
diff --git a/openstackclient/volume/__init__.py b/openstackclient/volume/__init__.py
index 85ac250..e69de29 100644
--- a/openstackclient/volume/__init__.py
+++ b/openstackclient/volume/__init__.py
@@ -1,14 +0,0 @@
-# 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.
-#
diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py
index e04e8cd..9b37b8f 100644
--- a/openstackclient/volume/client.py
+++ b/openstackclient/volume/client.py
@@ -15,8 +15,13 @@
import logging
+from cinderclient.v1 import volume_snapshots
+from cinderclient.v1 import volumes
from openstackclient.common import utils
+# Monkey patch for v1 cinderclient
+volumes.Volume.NAME_ATTR = 'display_name'
+volume_snapshots.Snapshot.NAME_ATTR = 'display_name'
LOG = logging.getLogger(__name__)
@@ -37,6 +42,10 @@ def make_client(instance):
)
LOG.debug('instantiating volume client')
+
+ # Set client http_log_debug to True if verbosity level is high enough
+ http_log_debug = utils.get_effective_log_level() <= logging.DEBUG
+
client = volume_client(
username=instance._username,
api_key=instance._password,
@@ -44,8 +53,21 @@ def make_client(instance):
auth_url=instance._auth_url,
cacert=instance._cacert,
insecure=instance._insecure,
+ region_name=instance._region_name,
+ http_log_debug=http_log_debug
)
+ # Populate the Cinder client to skip another auth query to Identity
+ if instance._url:
+ # token flow
+ client.client.management_url = instance._url
+ else:
+ # password flow
+ client.client.management_url = instance.get_endpoint_for_service_type(
+ API_NAME)
+ client.client.service_catalog = instance._service_catalog
+ client.client.auth_token = instance._token
+
return client
diff --git a/openstackclient/volume/v1/__init__.py b/openstackclient/volume/v1/__init__.py
index 85ac250..e69de29 100644
--- a/openstackclient/volume/v1/__init__.py
+++ b/openstackclient/volume/v1/__init__.py
@@ -1,14 +0,0 @@
-# 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.
-#
diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py
index dbf6eb7..992fa7b 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',
@@ -57,7 +57,7 @@ class CreateBackup(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
volume_client = self.app.client_manager.volume
volume_id = utils.find_resource(volume_client.volumes,
parsed_args.volume).id
@@ -87,7 +87,7 @@ class DeleteBackup(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
volume_client = self.app.client_manager.volume
backup_id = utils.find_resource(volume_client.backups,
parsed_args.backup).id
@@ -101,7 +101,7 @@ class ListBackup(lister.Lister):
log = logging.getLogger(__name__ + '.ListBackup')
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
columns = (
'ID',
'Display Name',
@@ -135,7 +135,7 @@ class RestoreBackup(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
volume_client = self.app.client_manager.volume
backup = utils.find_resource(volume_client.backups,
parsed_args.backup)
@@ -159,7 +159,7 @@ class ShowBackup(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
volume_client = self.app.client_manager.volume
backup = utils.find_resource(volume_client.backups,
parsed_args.backup)
diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py
index d3a56b7..9cc3c4c 100644
--- a/openstackclient/volume/v1/snapshot.py
+++ b/openstackclient/volume/v1/snapshot.py
@@ -59,7 +59,7 @@ class CreateSnapshot(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
volume_client = self.app.client_manager.volume
volume_id = utils.find_resource(volume_client.volumes,
parsed_args.volume).id
@@ -88,7 +88,7 @@ class DeleteSnapshot(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
volume_client = self.app.client_manager.volume
snapshot_id = utils.find_resource(volume_client.volume_snapshots,
parsed_args.snapshot).id
@@ -102,7 +102,7 @@ class ListSnapshot(lister.Lister):
log = logging.getLogger(__name__ + '.ListSnapshot')
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
columns = (
'ID',
'Display Name',
@@ -140,7 +140,7 @@ class SetSnapshot(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
volume_client = self.app.client_manager.volume
snapshot = utils.find_resource(volume_client.volume_snapshots,
parsed_args.snapshot)
@@ -171,7 +171,7 @@ class ShowSnapshot(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
volume_client = self.app.client_manager.volume
snapshot = utils.find_resource(volume_client.volume_snapshots,
parsed_args.snapshot)
diff --git a/openstackclient/volume/v1/type.py b/openstackclient/volume/v1/type.py
index ecf2278..71bfc9e 100644
--- a/openstackclient/volume/v1/type.py
+++ b/openstackclient/volume/v1/type.py
@@ -48,18 +48,13 @@ class CreateVolumeType(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
volume_client = self.app.client_manager.volume
- volume_type = volume_client.volume_types.create(
- parsed_args.name
- )
+ volume_type = volume_client.volume_types.create(parsed_args.name)
+ volume_type._info.pop('extra_specs')
if parsed_args.property:
- volume_type.set_keys(parsed_args.property)
- # Map 'extra_specs' column to 'properties'
- volume_type._info.update(
- {'properties': utils.format_dict(
- volume_type._info.pop('extra_specs'))}
- )
+ result = volume_type.set_keys(parsed_args.property)
+ volume_type._info.update({'properties': utils.format_dict(result)})
info = {}
info.update(volume_type._info)
@@ -81,7 +76,7 @@ class DeleteVolumeType(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
volume_client = self.app.client_manager.volume
volume_type_id = utils.find_resource(
volume_client.volume_types, parsed_args.volume_type).id
@@ -100,11 +95,11 @@ 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):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
if parsed_args.long:
columns = ('ID', 'Name', 'Extra Specs')
column_headers = ('ID', 'Name', 'Properties')
@@ -141,7 +136,7 @@ class SetVolumeType(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
volume_client = self.app.client_manager.volume
volume_type = utils.find_resource(
volume_client.volume_types, parsed_args.volume_type)
@@ -175,7 +170,7 @@ class UnsetVolumeType(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
volume_client = self.app.client_manager.volume
volume_type = utils.find_resource(
volume_client.volume_types,
diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py
index 0253bc1..3e4af56 100644
--- a/openstackclient/volume/v1/volume.py
+++ b/openstackclient/volume/v1/volume.py
@@ -56,7 +56,7 @@ class CreateVolume(show.ShowOne):
help='Description of the volume',
)
parser.add_argument(
- '--volume-type',
+ '--type',
metavar='<volume-type>',
help='Type of volume',
)
@@ -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',
@@ -96,7 +96,7 @@ class CreateVolume(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
identity_client = self.app.client_manager.identity
volume_client = self.app.client_manager.volume
@@ -124,7 +124,7 @@ class CreateVolume(show.ShowOne):
source_volume,
parsed_args.name,
parsed_args.description,
- parsed_args.volume_type,
+ parsed_args.type,
user,
project,
parsed_args.availability_zone,
@@ -133,7 +133,10 @@ class CreateVolume(show.ShowOne):
)
# Map 'metadata' column to 'properties'
volume._info.update(
- {'properties': utils.format_dict(volume._info.pop('metadata'))}
+ {
+ 'properties': utils.format_dict(volume._info.pop('metadata')),
+ 'type': volume._info.pop('volume_type'),
+ },
)
return zip(*sorted(six.iteritems(volume._info)))
@@ -161,7 +164,7 @@ class DeleteVolume(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
volume_client = self.app.client_manager.volume
volume = utils.find_resource(
volume_client.volumes, parsed_args.volume)
@@ -204,7 +207,7 @@ class ListVolume(lister.Lister):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
if parsed_args.long:
columns = (
@@ -290,7 +293,7 @@ class SetVolume(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
volume_client = self.app.client_manager.volume
volume = utils.find_resource(volume_client.volumes, parsed_args.volume)
@@ -326,12 +329,15 @@ class ShowVolume(show.ShowOne):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
volume_client = self.app.client_manager.volume
volume = utils.find_resource(volume_client.volumes, parsed_args.volume)
# Map 'metadata' column to 'properties'
volume._info.update(
- {'properties': utils.format_dict(volume._info.pop('metadata'))}
+ {
+ 'properties': utils.format_dict(volume._info.pop('metadata')),
+ 'type': volume._info.pop('volume_type'),
+ },
)
if 'os-vol-tenant-attr:tenant_id' in volume._info:
volume._info.update(
@@ -364,7 +370,7 @@ class UnsetVolume(command.Command):
return parser
def take_action(self, parsed_args):
- self.log.debug('take_action(%s)' % parsed_args)
+ self.log.debug('take_action(%s)', parsed_args)
volume_client = self.app.client_manager.volume
volume = utils.find_resource(
volume_client.volumes, parsed_args.volume)
diff --git a/requirements.txt b/requirements.txt
index eee138a..484c454 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,10 +1,10 @@
-pbr>=0.5.21,<1.0
+pbr>=0.6,!=0.7,<1.0
cliff>=1.4.3
-keyring>=1.6.1,<2.0
+keyring>=2.1
pycrypto>=2.6
python-glanceclient>=0.9.0
-python-keystoneclient>=0.4.1
-python-novaclient>=2.15.0
+python-keystoneclient>=0.9.0
+python-novaclient>=2.17.0
python-cinderclient>=1.0.6
requests>=1.1
-six>=1.4.1
+six>=1.7.0
diff --git a/setup.cfg b/setup.cfg
index 116c692..571a41f 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -38,12 +38,13 @@ openstack.common =
limits_show = openstackclient.common.limits:ShowLimits
quota_set = openstackclient.common.quota:SetQuota
quota_show = openstackclient.common.quota:ShowQuota
+ extension_list = openstackclient.common.extension:ListExtension
openstack.compute.v2 =
- agent_create = openstackclient.compute.v2.agent:CreateAgent
- agent_delete = openstackclient.compute.v2.agent:DeleteAgent
- agent_list = openstackclient.compute.v2.agent:ListAgent
- agent_set = openstackclient.compute.v2.agent:SetAgent
+ compute_agent_create = openstackclient.compute.v2.agent:CreateAgent
+ compute_agent_delete = openstackclient.compute.v2.agent:DeleteAgent
+ compute_agent_list = openstackclient.compute.v2.agent:ListAgent
+ compute_agent_set = openstackclient.compute.v2.agent:SetAgent
aggregate_add_host = openstackclient.compute.v2.aggregate:AddAggregateHost
aggregate_create = openstackclient.compute.v2.aggregate:CreateAggregate
@@ -151,6 +152,9 @@ openstack.identity.v2_0 =
service_list =openstackclient.identity.v2_0.service:ListService
service_show =openstackclient.identity.v2_0.service:ShowService
+ token_issue =openstackclient.identity.v2_0.token:CreateToken
+ token_revoke =openstackclient.identity.v2_0.token:DeleteToken
+
user_role_list = openstackclient.identity.v2_0.role:ListUserRole
user_create = openstackclient.identity.v2_0.user:CreateUser
@@ -160,10 +164,7 @@ openstack.identity.v2_0 =
user_show = openstackclient.identity.v2_0.user:ShowUser
openstack.identity.v3 =
- access_token_authenticate = openstackclient.identity.v3.token:AuthenticateAccessToken
access_token_create = openstackclient.identity.v3.token:CreateAccessToken
- access_token_delete = openstackclient.identity.v3.token:DeleteAccessToken
- access_token_list = openstackclient.identity.v3.token:ListAccessToken
consumer_create = openstackclient.identity.v3.consumer:CreateConsumer
consumer_delete = openstackclient.identity.v3.consumer:DeleteConsumer
@@ -198,6 +199,12 @@ openstack.identity.v3 =
group_set = openstackclient.identity.v3.group:SetGroup
group_show = openstackclient.identity.v3.group:ShowGroup
+ identity_provider_create = openstackclient.identity.v3.identity_provider:CreateIdentityProvider
+ identity_provider_delete = openstackclient.identity.v3.identity_provider:DeleteIdentityProvider
+ identity_provider_list = openstackclient.identity.v3.identity_provider:ListIdentityProvider
+ identity_provider_set = openstackclient.identity.v3.identity_provider:SetIdentityProvider
+ identity_provider_show = openstackclient.identity.v3.identity_provider:ShowIdentityProvider
+
policy_create = openstackclient.identity.v3.policy:CreatePolicy
policy_delete = openstackclient.identity.v3.policy:DeletePolicy
policy_list = openstackclient.identity.v3.policy:ListPolicy
@@ -220,6 +227,7 @@ openstack.identity.v3 =
role_remove = openstackclient.identity.v3.role:RemoveRole
role_show = openstackclient.identity.v3.role:ShowRole
role_set = openstackclient.identity.v3.role:SetRole
+ role_assignment_list = openstackclient.identity.v3.role_assignment:ListRoleAssignment
service_create = openstackclient.identity.v3.service:CreateService
service_delete = openstackclient.identity.v3.service:DeleteService
@@ -227,6 +235,8 @@ openstack.identity.v3 =
service_show = openstackclient.identity.v3.service:ShowService
service_set = openstackclient.identity.v3.service:SetService
+ token_issue = openstackclient.identity.v3.token:CreateToken
+
user_create = openstackclient.identity.v3.user:CreateUser
user_delete = openstackclient.identity.v3.user:DeleteUser
user_list = openstackclient.identity.v3.user:ListUser
diff --git a/setup.py b/setup.py
index 70c2b3f..7363757 100644
--- a/setup.py
+++ b/setup.py
@@ -17,6 +17,14 @@
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
import setuptools
+# In python < 2.7.4, a lazy loading of package `pbr` will break
+# setuptools if some other modules registered functions in `atexit`.
+# solution from: http://bugs.python.org/issue15881#msg170215
+try:
+ import multiprocessing # noqa
+except ImportError:
+ pass
+
setuptools.setup(
setup_requires=['pbr'],
pbr=True)
diff --git a/test-requirements.txt b/test-requirements.txt
index 34becca..1b872c3 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,12 +1,12 @@
-hacking>=0.8.0,<0.9
+hacking>=0.9.1,<0.10
coverage>=3.6
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
+sphinx>=1.1.2,!=1.2.0,<1.3
+testrepository>=0.0.18
+testtools>=0.9.34
+WebOb>=1.2.3
Babel>=1.3
diff --git a/tools/install_venv.py b/tools/install_venv.py
index a5891d6..770ae73 100644
--- a/tools/install_venv.py
+++ b/tools/install_venv.py
@@ -59,7 +59,6 @@ def main(argv):
install.check_dependencies()
install.create_virtualenv(no_site_packages=options.no_site_packages)
install.install_dependencies()
- install.post_process()
print_help()
diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py
index 1bab88a..46822e3 100644
--- a/tools/install_venv_common.py
+++ b/tools/install_venv_common.py
@@ -1,5 +1,3 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
# Copyright 2013 OpenStack Foundation
# Copyright 2013 IBM Corp.
#
diff --git a/tox.ini b/tox.ini
index f6a5c33..c646457 100644
--- a/tox.ini
+++ b/tox.ini
@@ -7,9 +7,6 @@ skipdist = True
usedevelop = True
install_command = pip install -U {opts} {packages}
setenv = VIRTUAL_ENV={envdir}
- LANG=en_US.UTF-8
- LANGUAGE=en_US:en
- LC_ALL=C
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands = python setup.py testr --testr-args='{posargs}'
@@ -26,7 +23,11 @@ commands = python setup.py test --coverage --testr-args='{posargs}'
[tox:jenkins]
downloadcache = ~/cache/pip
+[testenv:docs]
+commands=
+ python setup.py build_sphinx
+
[flake8]
-ignore = E126,E202,W602,H302,H402
+ignore = E126,E202,W602,H302,H402,E265,H305,H307,H405,H904
show-source = True
exclude = .venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,tools