From f0cefcc77d593198b6e295e92a8bf05fedc8c8ff Mon Sep 17 00:00:00 2001 From: Bhuvan Arumugam Date: Sun, 8 Jul 2012 16:06:32 -0700 Subject: [PATCH 0001/3614] Keyring support for openstackclient. Bug: 1030440 If password is defined in keyring, use it; otherwise, prompt for the password. Keying is configured using command line switch, --os-use-keyring or env(OS_USE_KEYRING). * openstackclient/common/openstackkeyring.py The abstract class for keyring, specifically for openstack. The class is used to store encrypted password in keyring, without prompting for keyring password. The encrypted password is stored in ~/.openstack-keyring.cfg file. * openstack-common.py Update openstackkeyring library from openstack.common. * openstackclient/shell.py OpenStackClient.build_option_parser(): New boolean argument, --os-use-keyring, default to env(OS_USE_KEYRING). OpenStackClient.authenticate_user(): Get password from keyring, if it is defined; otherwise, prompt for the password. If user enter a password and keyring is enabled, store it in keyring. OpenStackClient.init_keyring_backend(): New method to define openstack backend for keyring. OpenStackClient.get_password_from_keyring(): New method to get password from keyring. OpenStackClient.set_password_in_keyring(): New method go set password in keyring. * toos/pip-requires Define keyring and pycrypto as one of dependent. Change-Id: I36d3a63054658c0ef0553d68b38fefbc236930ef --- openstack-common.conf | 2 +- openstackclient/common/openstackkeyring.py | 65 ++++++++++++++++++++++ openstackclient/shell.py | 44 +++++++++++++++ tools/pip-requires | 2 + 4 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 openstackclient/common/openstackkeyring.py diff --git a/openstack-common.conf b/openstack-common.conf index 08e091b17a..6b8b86a0c7 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -1,7 +1,7 @@ [DEFAULT] # The list of modules to copy from openstack-common -modules=setup +modules=setup,openstackkeyring # The base module to hold the copy of openstack.common base=openstackclient diff --git a/openstackclient/common/openstackkeyring.py b/openstackclient/common/openstackkeyring.py new file mode 100644 index 0000000000..3a5ce27f8c --- /dev/null +++ b/openstackclient/common/openstackkeyring.py @@ -0,0 +1,65 @@ +# Copyright 2011 OpenStack LLC. +# All Rights Reserved +# +# 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. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +""" +Keyring backend for Openstack, to store encrypted password in a file. +""" + +from Crypto.Cipher import AES + +import crypt +import keyring +import os + +KEYRING_FILE = os.path.join(os.path.expanduser('~'), '.openstack-keyring.cfg') + + +class OpenstackKeyring(keyring.backend.BasicFileKeyring): + """ Openstack Keyring to store encrypted password """ + + filename = KEYRING_FILE + + def supported(self): + """ applicable for all platforms, but not recommend """ + pass + + def _init_crypter(self): + """ initialize the crypter using the class name """ + block_size = 32 + padding = '0' + + # init the cipher with the class name, upto block_size + password = __name__[block_size:] + password = password + (block_size - len(password) % \ + block_size) * padding + return AES.new(password, AES.MODE_CFB) + + def encrypt(self, password): + """ encrypt the given password """ + crypter = self._init_crypter() + return crypter.encrypt(password) + + def decrypt(self, password_encrypted): + """ decrypt the given password """ + crypter = self._init_crypter() + return crypter.decrypt(password_encrypted) + + +def os_keyring(): + """ initialize the openstack keyring """ + return keyring.core.load_keyring(None, + 'openstackclient.common.openstackkeyring.OpenstackKeyring') diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 3d0adf9937..531ac258d6 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -29,10 +29,12 @@ from openstackclient.common import clientmanager from openstackclient.common import exceptions as exc +from openstackclient.common import openstackkeyring from openstackclient.common import utils VERSION = '0.1' +KEYRING_SERVICE = 'openstack' def env(*vars, **kwargs): @@ -123,6 +125,18 @@ def build_option_parser(self, description, version): default=env('OS_URL'), help='Defaults to env[OS_URL]') + env_os_keyring = env('OS_USE_KEYRING', default=False) + if type(env_os_keyring) == str: + if env_os_keyring.lower() in ['true', '1']: + env_os_keyring = True + else: + env_os_keyring = False + parser.add_argument('--os-use-keyring', + default=env_os_keyring, + action='store_true', + help='Use keyring to store password, ' + 'default=False (Env: OS_USE_KEYRING)') + return parser def authenticate_user(self): @@ -149,12 +163,14 @@ def authenticate_user(self): "You must provide a username via" " either --os-username or env[OS_USERNAME]") + self.get_password_from_keyring() if not self.options.os_password: # No password, if we've got a tty, try prompting for it if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty(): # Check for Ctl-D try: self.options.os_password = getpass.getpass() + self.set_password_in_keyring() except EOFError: pass # No password because we did't have a tty or the @@ -188,6 +204,34 @@ def authenticate_user(self): ) return + def init_keyring_backend(self): + """Initialize openstack backend to use for keyring""" + return openstackkeyring.os_keyring() + + def get_password_from_keyring(self): + """Get password from keyring, if it's set""" + if self.options.os_use_keyring: + service = KEYRING_SERVICE + backend = self.init_keyring_backend() + if not self.options.os_password: + password = backend.get_password(service, + self.options.os_username) + self.options.os_password = password + + def set_password_in_keyring(self): + """Set password in keyring for this user""" + if self.options.os_use_keyring: + service = KEYRING_SERVICE + backend = self.init_keyring_backend() + if self.options.os_password: + password = backend.get_password(service, + self.options.os_username) + # either password is not set in keyring, or it is different + if password != self.options.os_password: + backend.set_password(service, + self.options.os_username, + self.options.os_password) + def initialize_app(self, argv): """Global app init bits: diff --git a/tools/pip-requires b/tools/pip-requires index efcaf7fe01..d4ad68b62a 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -1,7 +1,9 @@ cliff argparse httplib2 +keyring prettytable +pycrypto python-keystoneclient>=0.1,<0.2 python-novaclient>=2,<3 simplejson From 5bf17126b908c803e5c2eedb2614ea75a914fb18 Mon Sep 17 00:00:00 2001 From: Clark Boylan Date: Tue, 21 Aug 2012 14:37:29 -0700 Subject: [PATCH 0002/3614] Add nosehtmloutput as a test dependency. Adding nosehtmloutput as a test dependency allows nose to output its results to an html file. This will be used by Jenkins to save logs on a different server. Change-Id: I9f7bdf848aeb0fee727da9cd3b651b3a3ce53182 --- tools/test-requires | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/test-requires b/tools/test-requires index b0ebc3e9b1..1e9012db09 100644 --- a/tools/test-requires +++ b/tools/test-requires @@ -7,6 +7,7 @@ nose nose-exclude nosexcover openstack.nose_plugin +nosehtmloutput pep8==0.6.1 sphinx>=1.1.2 unittest2 From 8010e773accce2d24c04659c88ac0c040c9a1932 Mon Sep 17 00:00:00 2001 From: Bhuvan Arumugam Date: Wed, 22 Aug 2012 15:57:59 -0700 Subject: [PATCH 0003/3614] Document the use of keyring. * README.rst Document the option --os-use-keyring, and environment variable OS_USE_KEYRING, to enable keyring. Change-Id: I54ceb2d2692fecca328da16f6ed14db8d09a6eb7 --- README.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 4ea7000d4b..b8e26c9fae 100644 --- a/README.rst +++ b/README.rst @@ -67,6 +67,7 @@ The 'password flow' variation is most commonly used:: export OS_TENANT_NAME= export OS_USERNAME= export OS_PASSWORD= # (optional) + export OS_USE_KEYRING=true # (optional) The corresponding command-line options look very similar:: @@ -74,9 +75,12 @@ The corresponding command-line options look very similar:: --os-tenant-name --os-username [--os-password ] + [--os-use-keyring] If a password is not provided above (in plaintext), you will be interactively -prompted to provide one securely. +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 and a URL pointing directly to the service API that presumably was acquired From 2372142eaa9c584ba4bb37dc80d7ee6162560a77 Mon Sep 17 00:00:00 2001 From: lrqrun Date: Wed, 29 Aug 2012 14:17:08 +0800 Subject: [PATCH 0004/3614] Fix PEP8 issues. Fix some pep8 issues in doc/source/conf.py and tests/test_shell.py make the code looks pretty. Change-Id: I927f03aff4190f1ac30eb56e7c545de555978c31 --- doc/source/conf.py | 51 +++++++++++++++++++++++++++------------------ tests/test_shell.py | 2 +- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 6331470bc6..df1e9e5f61 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- # -# OpenStack Command Line Client documentation build configuration file, created by -# sphinx-quickstart on Wed May 16 12:05:58 2012. +# OpenStack Command Line Client documentation build configuration file, created +# by sphinx-quickstart on Wed May 16 12:05:58 2012. # -# This file is execfile()d with the current directory set to its containing dir. +# This file is execfile()d with the current directory set to its containing +# dir. # # Note that not all possible configuration values are present in this # autogenerated file. @@ -11,21 +12,25 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +import os +import sys # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) -# -- General configuration ----------------------------------------------------- +# -- General configuration ---------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.todo'] +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', + 'sphinx.ext.doctest', + 'sphinx.ext.intersphinx', + 'sphinx.ext.todo'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -66,7 +71,8 @@ # directories to ignore when looking for source files. exclude_patterns = [] -# The reST default role (used for this markup: `text`) to use for all documents. +# The reST default role (used for this markup: `text`) to use for all +# documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. @@ -87,7 +93,7 @@ #modindex_common_prefix = [] -# -- Options for HTML output --------------------------------------------------- +# -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. @@ -167,7 +173,7 @@ htmlhelp_basename = 'OpenStackCommandLineClientdoc' -# -- Options for LaTeX output -------------------------------------------------- +# -- Options for LaTeX output ------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). @@ -181,10 +187,12 @@ } # Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). +# (source start file, target name, title, author, documentclass [howto/manual]) +# . latex_documents = [ - ('index', 'OpenStackCommandLineClient.tex', u'OpenStack Command Line Client Documentation', - u'OpenStack', 'manual'), + ('index', 'OpenStackCommandLineClient.tex', + u'OpenStack Command Line Client Documentation', + u'OpenStack', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -208,12 +216,13 @@ #latex_domain_indices = True -# -- Options for manual page output -------------------------------------------- +# -- Options for manual page output ------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'openstackcommandlineclient', u'OpenStack Command Line Client Documentation', + ('index', 'openstackcommandlineclient', + u'OpenStack Command Line Client Documentation', [u'OpenStack'], 1) ] @@ -221,15 +230,17 @@ #man_show_urls = False -# -- Options for Texinfo output ------------------------------------------------ +# -- Options for Texinfo output ----------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'OpenStackCommandLineClient', u'OpenStack Command Line Client Documentation', - u'OpenStack', 'OpenStackCommandLineClient', 'One line description of project.', - 'Miscellaneous'), + ('index', 'OpenStackCommandLineClient', + u'OpenStack Command Line Client Documentation', + u'OpenStack', 'OpenStackCommandLineClient', + 'One line description of project.', + 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. diff --git a/tests/test_shell.py b/tests/test_shell.py index 850b57f14e..6410935a0e 100644 --- a/tests/test_shell.py +++ b/tests/test_shell.py @@ -66,7 +66,7 @@ def setUp(self): # Make a fake shell object, a helping wrapper to call it, and a quick # way of asserting that certain API calls were made. global shell, _shell, assert_called, assert_called_anytime - shell = lambda sh,cmd: sh.run(cmd.split()) + shell = lambda sh, cmd: sh.run(cmd.split()) # Patch out some common methods #self.auth_patch = mock.patch( From 90a1c65f3ac90b1077eb3ea2f5fbe8a039ee9290 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 20 Aug 2012 18:02:30 -0500 Subject: [PATCH 0005/3614] Update compute client bits * add server create, delete, pause, reboot, rebuild resume, suspend, unpause commands Change-Id: I728ec199e4562bd621c3a73106c90d8b790b459a --- openstackclient/compute/client.py | 17 +- openstackclient/compute/v2/server.py | 578 +++++++++++++++++++++++++-- setup.py | 29 +- 3 files changed, 580 insertions(+), 44 deletions(-) diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py index a59b6e0063..3c17b17ad6 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -17,19 +17,28 @@ import logging -from novaclient import client as nova_client +from openstackclient.common import exceptions as exc +from openstackclient.common import utils LOG = logging.getLogger(__name__) API_NAME = 'compute' +API_VERSIONS = { + '1.1': 'novaclient.v1_1.client.Client', + '2': 'novaclient.v1_1.client.Client', +} def make_client(instance): """Returns a compute service client. """ - LOG.debug('instantiating compute client') - client = nova_client.Client( - version=instance._api_version[API_NAME], + compute_client = utils.get_client_class( + API_NAME, + instance._api_version[API_NAME], + API_VERSIONS, + ) + LOG.debug('instantiating compute client: %s' % compute_client) + client = compute_client( username=instance._username, api_key=instance._password, project_id=instance._tenant_name, diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index e72c5735ec..e496bd459a 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -21,11 +21,15 @@ import logging import os +import sys +import time from cliff import command from cliff import lister from cliff import show +from novaclient.v1_1 import servers +from openstackclient.common import exceptions from openstackclient.common import utils @@ -33,6 +37,7 @@ def _format_servers_list_networks(server): """Return a string containing the networks a server is attached to. :param server: a single Server resource + :rtype: a string of formatted network addresses """ output = [] for (network, addresses) in server.networks.items(): @@ -44,6 +49,318 @@ def _format_servers_list_networks(server): return '; '.join(output) +def _prep_server_detail(compute_client, server): + """Prepare the detailed server dict for printing + + :param compute_client: a compute client instance + :param server: a Server resource + :rtype: a dict of server details + """ + info = server._info.copy() + + # Call .get() to retrieve all of the server information + # as findall(name=blah) and REST /details are not the same + # and do not return flavor and image information. + server = compute_client.servers.get(info['id']) + info.update(server._info) + + # Convert the image blob to a name + image_info = info.get('image', {}) + image_id = image_info.get('id', '') + image = utils.find_resource(compute_client.images, image_id) + info['image'] = "%s (%s)" % (image.name, image_id) + + # Convert the flavor blob to a name + flavor_info = info.get('flavor', {}) + flavor_id = flavor_info.get('id', '') + flavor = utils.find_resource(compute_client.flavors, flavor_id) + info['flavor'] = "%s (%s)" % (flavor.name, flavor_id) + + # NOTE(dtroyer): novaclient splits these into separate entries... + # Format addresses in a useful way + info['addresses'] = _format_servers_list_networks(server) + + # Remove values that are long and not too useful + info.pop('links', None) + + return info + + +def _wait_for_status(poll_fn, obj_id, final_ok_states, poll_period=5, + status_field="status"): + """Block while an action is being performed + + :param poll_fn: a function to retrieve the state of the object + :param obj_id: the id of the object + :param final_ok_states: a tuple of the states of the object that end the + wait as success, ex ['active'] + :param poll_period: the wait time between checks of object status + :param status_field: field name containing the status to be checked + """ + log = logging.getLogger(__name__ + '._wait_for_status') + while True: + obj = poll_fn(obj_id) + + status = getattr(obj, status_field) + + if status: + status = status.lower() + + if status in final_ok_states: + log.debug('Wait terminated with success') + retval = True + break + elif status == "error": + log.error('Wait terminated with an error') + retval = False + break + + time.sleep(poll_period) + + return retval + + +class CreateServer(show.ShowOne): + """Create server command""" + + api = "compute" + log = logging.getLogger(__name__ + '.CreateServer') + + def get_parser(self, prog_name): + parser = super(CreateServer, self).get_parser(prog_name) + parser.add_argument( + 'server_name', + metavar='', + help='New server name', + ) + parser.add_argument( + '--image', + metavar='', + required=True, + help='Create server from this image', + ) + parser.add_argument( + '--flavor', + metavar='', + required=True, + help='Create server with this flavor', + ) + parser.add_argument( + '--security-group', + metavar='', + action='append', + default=[], + help='Security group to assign to this server ' \ + '(repeat for multiple groups)', + ) + parser.add_argument( + '--key-name', + metavar='', + help='Keypair to inject into this server (optional extension)', + ) + parser.add_argument( + '--meta-data', + metavar='', + action='append', + default=[], + help='Metadata to store for this server ' \ + '(repeat for multiple values)', + ) + parser.add_argument( + '--file', + metavar='', + action='append', + default=[], + help='File to inject into image before boot ' \ + '(repeat for multiple files)', + ) + parser.add_argument( + '--user-data', + metavar='', + help='User data file to be serverd by the metadata server', + ) + parser.add_argument( + '--availability-zone', + metavar='', + help='Keypair to inject into this server', + ) + parser.add_argument( + '--block-device-mapping', + metavar='', + action='append', + default=[], + help='Map block devices; map is ' \ + '::: ' \ + '(optional extension)', + ) + parser.add_argument( + '--nic', + metavar='', + action='append', + default=[], + help='Specify NIC configuration (optional extension)', + ) + parser.add_argument( + '--hint', + metavar='', + action='append', + default=[], + help='Hints for the scheduler (optional extension)', + ) + parser.add_argument( + '--config-drive', + metavar='|True', + default=False, + help='Use specified volume as the config drive, ' \ + 'or \'True\' to use an ephemeral drive', + ) + parser.add_argument( + '--min', + metavar='', + type=int, + default=1, + help='Minimum number of servers to launch (default=1)', + ) + parser.add_argument( + '--max', + metavar='', + type=int, + default=1, + help='Maximum number of servers to launch (default=1)', + ) + parser.add_argument( + '--wait', + dest='wait', + action='store_true', + help='Wait for server to become active to return', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + compute_client = self.app.client_manager.compute + + # Lookup parsed_args.image + image = utils.find_resource(compute_client.images, + parsed_args.image) + + # Lookup parsed_args.flavor + flavor = utils.find_resource(compute_client.flavors, + parsed_args.flavor) + + boot_args = [parsed_args.server_name, image, flavor] + + meta = dict(v.split('=', 1) for v in parsed_args.meta_data) + + files = {} + for f in parsed_args.file: + dst, src = f.split('=', 1) + try: + files[dst] = open(src) + except IOError, e: + raise exceptions.CommandError("Can't open '%s': %s" % (src, e)) + + if parsed_args.min > parsed_args.max: + raise exceptions.CommandError("min instances should be <= " + "max instances") + if parsed_args.min < 1: + raise exceptions.CommandError("min instances should be > 0") + if parsed_args.max < 1: + raise exceptions.CommandError("max instances should be > 0") + + userdata = None + if parsed_args.user_data: + try: + userdata = open(parsed_args.user_data) + except IOError, e: + raise exceptions.CommandError("Can't open '%s': %s" % \ + (parsed_args.user_data, e)) + + block_device_mapping = dict(v.split('=', 1) + for v in parsed_args.block_device_mapping) + + nics = [] + for nic_str in parsed_args.nic: + nic_info = {"net-id": "", "v4-fixed-ip": ""} + nic_info.update(dict(kv_str.split("=", 1) + for kv_str in nic_str.split(","))) + nics.append(nic_info) + + hints = {} + for hint in parsed_args.hint: + key, _sep, value = hint.partition('=') + # NOTE(vish): multiple copies of the same hint will + # result in a list of values + if key in hints: + if isinstance(hints[key], basestring): + hints[key] = [hints[key]] + hints[key] += [value] + else: + hints[key] = value + + # What does a non-boolean value for config-drive do? + # --config-drive argument is either a volume id or + # 'True' (or '1') to use an ephemeral volume + if str(parsed_args.config_drive).lower() in ("true", "1"): + config_drive = True + elif str(parsed_args.config_drive).lower() in ("false", "0", + "", "none"): + config_drive = None + else: + config_drive = parsed_args.config_drive + + boot_kwargs = dict( + meta=meta, + files=files, + reservation_id=None, + min_count=parsed_args.min, + max_count=parsed_args.max, + security_groups=parsed_args.security_group, + userdata=userdata, + key_name=parsed_args.key_name, + availability_zone=parsed_args.availability_zone, + block_device_mapping=block_device_mapping, + nics=nics, + scheduler_hints=hints, + config_drive=config_drive, + ) + + 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: + _wait_for_status(compute_client.servers.get, server._info['id'], + ['active']) + + details = _prep_server_detail(compute_client, server) + return zip(*sorted(details.iteritems())) + + +class DeleteServer(command.Command): + """Delete server command""" + + api = 'compute' + log = logging.getLogger(__name__ + '.DeleteServer') + + def get_parser(self, prog_name): + parser = super(DeleteServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Name or ID of server to delete', + ) + return parser + + def take_action(self, 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) + compute_client.servers.delete(server.id) + return + + class ListServer(lister.Lister): """List server command""" @@ -54,40 +371,48 @@ def get_parser(self, prog_name): parser = super(ListServer, self).get_parser(prog_name) parser.add_argument( '--reservation-id', + metavar='', help='only return instances that match the reservation', ) parser.add_argument( '--ip', + metavar='', help='regular expression to match IP address', ) parser.add_argument( '--ip6', + metavar='', help='regular expression to match IPv6 address', ) parser.add_argument( '--name', + metavar='', help='regular expression to match name', ) parser.add_argument( '--instance-name', + metavar='', help='regular expression to match instance name', ) parser.add_argument( '--status', + metavar='', help='search by server status', # FIXME(dhellmann): Add choices? ) parser.add_argument( '--flavor', + metavar='', help='search by flavor ID', ) parser.add_argument( '--image', + metavar='', help='search by image ID', ) parser.add_argument( '--host', - metavar='HOSTNAME', + metavar='', help='search by hostname', ) parser.add_argument( @@ -100,23 +425,23 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) - nova_client = self.app.client_manager.compute + compute_client = self.app.client_manager.compute search_opts = { - 'all_tenants': parsed_args.all_tenants, 'reservation_id': parsed_args.reservation_id, 'ip': parsed_args.ip, 'ip6': parsed_args.ip6, 'name': parsed_args.name, - 'image': parsed_args.image, - 'flavor': parsed_args.flavor, + 'instance_name': parsed_args.instance_name, 'status': parsed_args.status, + 'flavor': parsed_args.flavor, + 'image': parsed_args.image, 'host': parsed_args.host, - 'instance_name': parsed_args.instance_name, + 'all_tenants': parsed_args.all_tenants, } self.log.debug('search options: %s', search_opts) # FIXME(dhellmann): Consider adding other columns columns = ('ID', 'Name', 'Status', 'Networks') - data = nova_client.servers.list(search_opts=search_opts) + data = compute_client.servers.list(search_opts=search_opts) return (columns, (utils.get_item_properties( s, columns, @@ -125,6 +450,165 @@ def take_action(self, parsed_args): ) +class PauseServer(command.Command): + """Pause server command""" + + api = 'compute' + log = logging.getLogger(__name__ + '.PauseServer') + + def get_parser(self, prog_name): + parser = super(PauseServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Name or ID of server to pause', + ) + return parser + + def take_action(self, 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) + server.pause() + return + + +class RebootServer(command.Command): + """Reboot server command""" + + api = 'compute' + log = logging.getLogger(__name__ + '.RebootServer') + + def get_parser(self, prog_name): + parser = super(RebootServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Name or ID of server to reboot', + ) + group = parser.add_mutually_exclusive_group() + group.add_argument( + '--hard', + dest='reboot_type', + action='store_const', + const=servers.REBOOT_HARD, + default=servers.REBOOT_SOFT, + help='Perform a hard reboot', + ) + group.add_argument( + '--soft', + dest='reboot_type', + action='store_const', + const=servers.REBOOT_SOFT, + default=servers.REBOOT_SOFT, + help='Perform a soft reboot', + ) + parser.add_argument( + '--wait', + dest='wait', + action='store_true', + help='Wait for server to become active to return', + ) + return parser + + def take_action(self, 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) + server.reboot(parsed_args.reboot_type) + + if parsed_args.wait: + _wait_for_status(compute_client.servers.get, server.id, + ['active']) + + return + + +class RebuildServer(show.ShowOne): + """Rebuild server command""" + + api = "compute" + log = logging.getLogger(__name__ + '.RebuildServer') + + def get_parser(self, prog_name): + parser = super(RebuildServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Server name or ID', + ) + parser.add_argument( + '--image', + metavar='', + required=True, + help='Recreate server from this image', + ) + parser.add_argument( + '--rebuild-password', + metavar='', + default=False, + help="Set the provided password on the rebuild instance", + ) + parser.add_argument( + '--wait', + dest='wait', + action='store_true', + help='Wait for server to become active to return', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + compute_client = self.app.client_manager.compute + + # Lookup parsed_args.image + image = utils.find_resource(compute_client.images, parsed_args.image) + + server = utils.find_resource( + compute_client.servers, parsed_args.server) + + _password = None + if parsed_args.rebuild_password is not False: + _password = args.rebuild_password + + kwargs = {} + server = server.rebuild(image, _password, **kwargs) + + # TODO(dtroyer): force silent=True if output filter != table + if parsed_args.wait: + _wait_for_status(compute_client.servers.get, server._info['id'], + ['active']) + + details = _prep_server_detail(compute_client, server) + return zip(*sorted(details.iteritems())) + + +class ResumeServer(command.Command): + """Resume server command""" + + api = 'compute' + log = logging.getLogger(__name__ + '.ResumeServer') + + def get_parser(self, prog_name): + parser = super(ResumeServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Name or ID of server to resume', + ) + return parser + + def take_action(self, 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) + server.resume() + return + + class ShowServer(show.ShowOne): """Show server command""" @@ -136,32 +620,62 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help='Name or ID of server to display') + help='Name or ID of server to display'), + return parser + + def take_action(self, 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) + + details = _prep_server_detail(compute_client, server) + return zip(*sorted(details.iteritems())) + + +class SuspendServer(command.Command): + """Suspend server command""" + + api = 'compute' + log = logging.getLogger(__name__ + '.SuspendServer') + + def get_parser(self, prog_name): + parser = super(SuspendServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Name or ID of server to suspend', + ) + return parser + + def take_action(self, 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) + server.suspend() + return + + +class UnpauseServer(command.Command): + """Unpause server command""" + + api = 'compute' + log = logging.getLogger(__name__ + '.UnpauseServer') + + def get_parser(self, prog_name): + parser = super(UnpauseServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Name or ID of server to unpause', + ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) - nova_client = self.app.client_manager.compute - server = utils.find_resource(nova_client.servers, parsed_args.server) - - info = {} - info.update(server._info) - - # Convert the flavor blob to a name - flavor_info = info.get('flavor', {}) - flavor_id = flavor_info.get('id', '') - flavor = utils.find_resource(nova_client.flavors, flavor_id) - info['flavor'] = flavor.name - - # Convert the image blob to a name - image_info = info.get('image', {}) - image_id = image_info.get('id', '') - image = utils.find_resource(nova_client.images, image_id) - info['image'] = image.name - - # Format addresses in a useful way - info['addresses'] = _format_servers_list_networks(server) - - # Remove a couple of values that are long and not too useful - info.pop('links', None) - return zip(*sorted(info.iteritems())) + compute_client = self.app.client_manager.compute + server = utils.find_resource( + compute_client.servers, parsed_args.server) + server.unpause() + return diff --git a/setup.py b/setup.py index 9a6b5b199d..728a062e36 100644 --- a/setup.py +++ b/setup.py @@ -55,6 +55,15 @@ def read(fname): entry_points={ 'console_scripts': ['openstack=openstackclient.shell:main'], 'openstack.cli': [ + 'create_endpoint=' + + 'openstackclient.identity.v2_0.endpoint:CreateEndpoint', + 'delete_endpoint=' + + 'openstackclient.identity.v2_0.endpoint:DeleteEndpoint', + 'list_endpoint=' + + 'openstackclient.identity.v2_0.endpoint:ListEndpoint', + 'show_endpoint=' + + 'openstackclient.identity.v2_0.endpoint:ShowEndpoint', + 'add_role=' + 'openstackclient.identity.v2_0.role:AddRole', 'create_role=' + @@ -65,22 +74,25 @@ def read(fname): 'remove_role=' + 'openstackclient.identity.v2_0.role:RemoveRole', 'show_role=openstackclient.identity.v2_0.role:ShowRole', + + 'create_server=openstackclient.compute.v2.server:CreateServer', + 'delete_server=openstackclient.compute.v2.server:DeleteServer', 'list_server=openstackclient.compute.v2.server:ListServer', + 'pause_server=openstackclient.compute.v2.server:PauseServer', + 'reboot_server=openstackclient.compute.v2.server:RebootServer', + 'rebuild_server=openstackclient.compute.v2.server:RebuildServer', + 'resume_server=openstackclient.compute.v2.server:ResumeServer', 'show_server=openstackclient.compute.v2.server:ShowServer', - 'create_endpoint=' + - 'openstackclient.identity.v2_0.endpoint:CreateEndpoint', - 'delete_endpoint=' + - 'openstackclient.identity.v2_0.endpoint:DeleteEndpoint', - 'list_endpoint=' + - 'openstackclient.identity.v2_0.endpoint:ListEndpoint', - 'show_endpoint=' + - 'openstackclient.identity.v2_0.endpoint:ShowEndpoint', + 'suspend_server=openstackclient.compute.v2.server:SuspendServer', + 'unpause_server=openstackclient.compute.v2.server:UnpauseServer', + 'create_service=' + 'openstackclient.identity.v2_0.service:CreateService', 'delete_service=' + 'openstackclient.identity.v2_0.service:DeleteService', 'list_service=openstackclient.identity.v2_0.service:ListService', 'show_service=openstackclient.identity.v2_0.service:ShowService', + 'create_tenant=' + 'openstackclient.identity.v2_0.tenant:CreateTenant', 'delete_tenant=' + @@ -88,6 +100,7 @@ def read(fname): 'list_tenant=openstackclient.identity.v2_0.tenant:ListTenant', 'set_tenant=openstackclient.identity.v2_0.tenant:SetTenant', 'show_tenant=openstackclient.identity.v2_0.tenant:ShowTenant', + 'create_user=' + 'openstackclient.identity.v2_0.user:CreateUser', 'delete_user=' + From c49b049af8654e8a4f89454fc9c706c99c7cbfa0 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 5 Sep 2012 11:12:32 -0500 Subject: [PATCH 0006/3614] Add cliff prereqs The real issue is cmd2 not pulling in pyparsing properly on a fresh system. Change-Id: I9bae29f9a664431d0145ebc5a0cc4caec638d739 --- tools/pip-requires | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/pip-requires b/tools/pip-requires index d4ad68b62a..0d107ef25a 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -1,8 +1,10 @@ -cliff argparse +cliff +cmd2 httplib2 keyring prettytable +pyparsing pycrypto python-keystoneclient>=0.1,<0.2 python-novaclient>=2,<3 From fd85c7e1f4dc1017f784c384a507ce15a81dc327 Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Mon, 22 Oct 2012 18:48:25 -0400 Subject: [PATCH 0007/3614] Add OpenStack trove classifier for PyPI Add trove classifier to have the client listed among the other OpenStack-related projets on PyPI. Change-Id: I34c47bde5885a3f436c100cda5202d6ad8356131 Signed-off-by: Doug Hellmann --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 728a062e36..7dde53f2bf 100644 --- a/setup.py +++ b/setup.py @@ -42,6 +42,7 @@ def read(fname): classifiers=[ 'Development Status :: 2 - Pre-Alpha', 'Environment :: Console', + 'Environment :: OpenStack', 'Intended Audience :: Developers', 'Intended Audience :: Information Technology', 'License :: OSI Approved :: Apache Software License', From 67e413a224d8bec7729ec1aa703c6e4c91ee243b Mon Sep 17 00:00:00 2001 From: Alessandro Pilotti Date: Mon, 5 Nov 2012 18:34:15 +0200 Subject: [PATCH 0008/3614] Fixes setup compatibility issue on Windows Fixes Bug #1052161 "python setup.py build" fails on Windows due to a hardcoded shell path: /bin/sh setup.py updated using openstack-common/update.py Change-Id: I33d38e0f96b6d124248c4a31959952d61cf1eb16 --- openstackclient/openstack/common/setup.py | 102 +++++++++++++--------- 1 file changed, 63 insertions(+), 39 deletions(-) diff --git a/openstackclient/openstack/common/setup.py b/openstackclient/openstack/common/setup.py index caf06fa5b5..e6f72f034e 100644 --- a/openstackclient/openstack/common/setup.py +++ b/openstackclient/openstack/common/setup.py @@ -31,12 +31,13 @@ def parse_mailmap(mailmap='.mailmap'): mapping = {} if os.path.exists(mailmap): - fp = open(mailmap, 'r') - for l in fp: - l = l.strip() - if not l.startswith('#') and ' ' in l: - canonical_email, alias = l.split(' ') - mapping[alias] = canonical_email + with open(mailmap, 'r') as fp: + for l in fp: + l = l.strip() + if not l.startswith('#') and ' ' in l: + canonical_email, alias = [x for x in l.split(' ') + if x.startswith('<')] + mapping[alias] = canonical_email return mapping @@ -51,10 +52,10 @@ def canonicalize_emails(changelog, mapping): # Get requirements from the first file that exists def get_reqs_from_files(requirements_files): - reqs_in = [] for requirements_file in requirements_files: if os.path.exists(requirements_file): - return open(requirements_file, 'r').read().split('\n') + with open(requirements_file, 'r') as fil: + return fil.read().split('\n') return [] @@ -116,8 +117,12 @@ def write_requirements(): def _run_shell_command(cmd): - output = subprocess.Popen(["/bin/sh", "-c", cmd], - stdout=subprocess.PIPE) + if os.name == 'nt': + output = subprocess.Popen(["cmd.exe", "/C", cmd], + stdout=subprocess.PIPE) + else: + output = subprocess.Popen(["/bin/sh", "-c", cmd], + stdout=subprocess.PIPE) out = output.communicate() if len(out) == 0: return None @@ -135,11 +140,19 @@ def _get_git_next_version_suffix(branch_name): _run_shell_command("git fetch origin +refs/meta/*:refs/remotes/meta/*") milestone_cmd = "git show meta/openstack/release:%s" % branch_name milestonever = _run_shell_command(milestone_cmd) - if not milestonever: - milestonever = "" + if milestonever: + first_half = "%s~%s" % (milestonever, datestamp) + else: + first_half = datestamp + post_version = _get_git_post_version() - revno = post_version.split(".")[-1] - return "%s~%s.%s%s" % (milestonever, datestamp, revno_prefix, revno) + # post version should look like: + # 0.1.1.4.gcc9e28a + # where the bit after the last . is the short sha, and the bit between + # the last and second to last is the revno count + (revno, sha) = post_version.split(".")[-2:] + second_half = "%s%s.%s" % (revno_prefix, revno, sha) + return ".".join((first_half, second_half)) def _get_git_current_tag(): @@ -161,39 +174,48 @@ def _get_git_post_version(): cmd = "git --no-pager log --oneline" out = _run_shell_command(cmd) revno = len(out.split("\n")) + sha = _run_shell_command("git describe --always") else: tag_infos = tag_info.split("-") base_version = "-".join(tag_infos[:-2]) - revno = tag_infos[-2] - return "%s.%s" % (base_version, revno) + (revno, sha) = tag_infos[-2:] + return "%s.%s.%s" % (base_version, revno, sha) def write_git_changelog(): """Write a changelog based on the git changelog.""" - if os.path.isdir('.git'): - git_log_cmd = 'git log --stat' - changelog = _run_shell_command(git_log_cmd) - mailmap = parse_mailmap() - with open("ChangeLog", "w") as changelog_file: - changelog_file.write(canonicalize_emails(changelog, mailmap)) + new_changelog = 'ChangeLog' + if not os.getenv('SKIP_WRITE_GIT_CHANGELOG'): + if os.path.isdir('.git'): + git_log_cmd = 'git log --stat' + changelog = _run_shell_command(git_log_cmd) + mailmap = parse_mailmap() + with open(new_changelog, "w") as changelog_file: + changelog_file.write(canonicalize_emails(changelog, mailmap)) + else: + open(new_changelog, 'w').close() def generate_authors(): """Create AUTHORS file using git commits.""" - jenkins_email = 'jenkins@review.openstack.org' + jenkins_email = 'jenkins@review.(openstack|stackforge).org' old_authors = 'AUTHORS.in' new_authors = 'AUTHORS' - if os.path.isdir('.git'): - # don't include jenkins email address in AUTHORS file - git_log_cmd = ("git log --format='%aN <%aE>' | sort -u | " - "grep -v " + jenkins_email) - changelog = _run_shell_command(git_log_cmd) - mailmap = parse_mailmap() - with open(new_authors, 'w') as new_authors_fh: - new_authors_fh.write(canonicalize_emails(changelog, mailmap)) - if os.path.exists(old_authors): - with open(old_authors, "r") as old_authors_fh: - new_authors_fh.write('\n' + old_authors_fh.read()) + if not os.getenv('SKIP_GENERATE_AUTHORS'): + if os.path.isdir('.git'): + # don't include jenkins email address in AUTHORS file + git_log_cmd = ("git log --format='%aN <%aE>' | sort -u | " + "egrep -v '" + jenkins_email + "'") + changelog = _run_shell_command(git_log_cmd) + mailmap = parse_mailmap() + with open(new_authors, 'w') as new_authors_fh: + new_authors_fh.write(canonicalize_emails(changelog, mailmap)) + if os.path.exists(old_authors): + with open(old_authors, "r") as old_authors_fh: + new_authors_fh.write('\n' + old_authors_fh.read()) + else: + open(new_authors, 'w').close() + _rst_template = """%(heading)s %(underline)s @@ -207,7 +229,7 @@ def generate_authors(): def read_versioninfo(project): """Read the versioninfo file. If it doesn't exist, we're in a github - zipball, and there's really know way to know what version we really + zipball, and there's really no way to know what version we really are, but that should be ok, because the utility of that should be just about nil if this code path is in use in the first place.""" versioninfo_path = os.path.join(project, 'versioninfo') @@ -221,7 +243,8 @@ def read_versioninfo(project): def write_versioninfo(project, version): """Write a simple file containing the version of the package.""" - open(os.path.join(project, 'versioninfo'), 'w').write("%s\n" % version) + with open(os.path.join(project, 'versioninfo'), 'w') as fil: + fil.write("%s\n" % version) def get_cmdclass(): @@ -312,7 +335,8 @@ def get_git_branchname(): def get_pre_version(projectname, base_version): - """Return a version which is based""" + """Return a version which is leading up to a version that will + be released in the future.""" if os.path.isdir('.git'): current_tag = _get_git_current_tag() if current_tag is not None: @@ -324,10 +348,10 @@ def get_pre_version(projectname, base_version): version_suffix = _get_git_next_version_suffix(branch_name) version = "%s~%s" % (base_version, version_suffix) write_versioninfo(projectname, version) - return version.split('~')[0] + return version else: version = read_versioninfo(projectname) - return version.split('~')[0] + return version def get_post_version(projectname): From 2fad9aa0f61a1ef2f93139000eeb81613a6b4456 Mon Sep 17 00:00:00 2001 From: "James E. Blair" Date: Fri, 16 Nov 2012 16:38:03 -0800 Subject: [PATCH 0009/3614] Remove upper bounds on openstack dependencies. Change-Id: Ib2980bc219aba66cfdbc67b7fc4eafbf6501ef23 --- tools/pip-requires | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/pip-requires b/tools/pip-requires index d4ad68b62a..14da677437 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -4,6 +4,6 @@ httplib2 keyring prettytable pycrypto -python-keystoneclient>=0.1,<0.2 -python-novaclient>=2,<3 +python-keystoneclient>=0.1 +python-novaclient>=2 simplejson From 0fe3efaacb5a7eb44d950b4ac7d16df2a495a9d6 Mon Sep 17 00:00:00 2001 From: Ben Andrews Date: Sun, 16 Dec 2012 20:48:36 -0500 Subject: [PATCH 0010/3614] bug 1091029 sets version that pip can use for pyparser to one that is for python 2.X. 2.0.0 is only for python 3 Change-Id: Ief16981b5e2c7d8716fdf77e15998cc9ffae9779 Fixes: bug #1091029 --- tools/pip-requires | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/pip-requires b/tools/pip-requires index 0d107ef25a..30bbd129ce 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -4,7 +4,7 @@ cmd2 httplib2 keyring prettytable -pyparsing +pyparsing>=1.5.6,<2.0 pycrypto python-keystoneclient>=0.1,<0.2 python-novaclient>=2,<3 From e7585624ac2a23dd0c937b16cf2e6b12b9e0125e Mon Sep 17 00:00:00 2001 From: Henry Nash Date: Sat, 17 Nov 2012 23:51:39 +0000 Subject: [PATCH 0011/3614] Increment minimum required keystoneclient version number The movement of auth_token from keystone to keystoneclient is part of the updated 0.2 version of keystoneclient. The server still maintains an import back from the client for backward compatibility. However, in order to support this, installations must upgrade to the latest 0.2 version of the client. Change-Id: I1ed1ebebbd56b2ed9c035c9e7d83783c8b2ae5fc --- tools/pip-requires | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/pip-requires b/tools/pip-requires index 0c91def35f..e012257cdc 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -6,6 +6,6 @@ keyring prettytable pyparsing pycrypto -python-keystoneclient>=0.1 +python-keystoneclient>=0.2,<1.0 python-novaclient>=2 simplejson From fe3123b802cae77cf14d161cf6460a3264e6e4c2 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 27 Dec 2012 11:50:53 -0600 Subject: [PATCH 0012/3614] Move from unittest2 to testtools Change-Id: I475a082af0660e0ee0e86ca4cd1bf0e2d711e3ed --- tests/test_shell.py | 31 +++++++++++++++++++------------ tests/utils.py | 6 +++--- tools/test-requires | 5 +++-- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/tests/test_shell.py b/tests/test_shell.py index 6410935a0e..e897fdfa0b 100644 --- a/tests/test_shell.py +++ b/tests/test_shell.py @@ -18,6 +18,7 @@ import os import mock +import fixtures from openstackclient import shell as os_shell from tests import utils @@ -50,18 +51,25 @@ def make_shell(): class ShellTest(utils.TestCase): + FAKE_ENV = { + 'OS_AUTH_URL': DEFAULT_AUTH_URL, + 'OS_TENANT_ID': DEFAULT_TENANT_ID, + 'OS_TENANT_NAME': DEFAULT_TENANT_NAME, + 'OS_USERNAME': DEFAULT_USERNAME, + 'OS_PASSWORD': DEFAULT_PASSWORD, + 'OS_REGION_NAME': DEFAULT_REGION_NAME, + } + def setUp(self): """ Patch os.environ to avoid required auth info""" - global _old_env - fake_env = { - 'OS_AUTH_URL': DEFAULT_AUTH_URL, - 'OS_TENANT_ID': DEFAULT_TENANT_ID, - 'OS_TENANT_NAME': DEFAULT_TENANT_NAME, - 'OS_USERNAME': DEFAULT_USERNAME, - 'OS_PASSWORD': DEFAULT_PASSWORD, - 'OS_REGION_NAME': DEFAULT_REGION_NAME, - } - _old_env, os.environ = os.environ, fake_env.copy() + super(ShellTest, self).setUp() + for var in self.FAKE_ENV: + self.useFixture( + fixtures.EnvironmentVariable( + var, + self.FAKE_ENV[var] + ) + ) # Make a fake shell object, a helping wrapper to call it, and a quick # way of asserting that certain API calls were made. @@ -77,10 +85,9 @@ def setUp(self): self.cmd_save = self.cmd_patch.start() def tearDown(self): - global _old_env - os.environ = _old_env #self.auth_patch.stop() self.cmd_patch.stop() + super(ShellTest, self).tearDown() def test_shell_args(self): sh = make_shell() diff --git a/tests/utils.py b/tests/utils.py index 3535360d25..792fe88ead 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -3,10 +3,10 @@ import time import mox -import unittest2 +import testtools -class TestCase(unittest2.TestCase): +class TestCase(testtools.TestCase): def setUp(self): super(TestCase, self).setUp() @@ -16,6 +16,6 @@ def setUp(self): def tearDown(self): time.time = self._original_time - super(TestCase, self).tearDown() self.mox.UnsetStubs() self.mox.VerifyAll() + super(TestCase, self).tearDown() diff --git a/tools/test-requires b/tools/test-requires index 1e9012db09..40350f0256 100644 --- a/tools/test-requires +++ b/tools/test-requires @@ -1,13 +1,14 @@ distribute>=0.6.24 coverage +fixtures mock mox nose nose-exclude +nosehtmloutput nosexcover openstack.nose_plugin -nosehtmloutput pep8==0.6.1 sphinx>=1.1.2 -unittest2 +testtools>=0.9.22 From eb1ae2e9f23d618069e5eee7abedbd5a049d2878 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Wed, 26 Dec 2012 14:22:23 -0600 Subject: [PATCH 0013/3614] Adds Glance API v2 support. Change-Id: Ib0325e62a7e50aa94e852a73f9a2cb95daa8d5f6 --- openstackclient/common/clientmanager.py | 4 +- openstackclient/common/utils.py | 2 +- openstackclient/image/client.py | 41 +++++++++ openstackclient/image/v2/__init__.py | 0 openstackclient/image/v2/image.py | 109 ++++++++++++++++++++++++ setup.py | 3 + 6 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 openstackclient/image/client.py create mode 100644 openstackclient/image/v2/__init__.py create mode 100644 openstackclient/image/v2/image.py diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 0a9f9102d3..73c2e5708c 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -23,6 +23,7 @@ from openstackclient.common import exceptions as exc from openstackclient.compute import client as compute_client from openstackclient.identity import client as identity_client +from openstackclient.image import client as image_client LOG = logging.getLogger(__name__) @@ -46,8 +47,9 @@ class ClientManager(object): """Manages access to API clients, including authentication. """ - identity = ClientCache(identity_client.make_client) compute = ClientCache(compute_client.make_client) + identity = ClientCache(identity_client.make_client) + image = ClientCache(image_client.make_client) def __init__(self, token=None, url=None, auth_url=None, diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index ea8170bc7a..70555be5fa 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -125,6 +125,6 @@ def get_client_class(api_name, version, version_map): except (KeyError, ValueError): msg = "Invalid %s client version '%s'. must be one of: %s" % ( (api_name, version, ', '.join(version_map.keys()))) - raise exc.UnsupportedVersion(msg) + raise exceptions.UnsupportedVersion(msg) return import_class(client_path) diff --git a/openstackclient/image/client.py b/openstackclient/image/client.py new file mode 100644 index 0000000000..0a7c576e7b --- /dev/null +++ b/openstackclient/image/client.py @@ -0,0 +1,41 @@ +# Copyright 2012 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 logging + +from openstackclient.common import utils + + +LOG = logging.getLogger(__name__) + +API_NAME = "image" +API_VERSIONS = { + # FIXME(jk0): Temporary 1.0 -> 2 mapping. + "1.0": "glanceclient.v2.client.Client" +} + + +def make_client(instance): + """Returns an image service client.""" + image_client = utils.get_client_class( + API_NAME, + instance._api_version[API_NAME], + API_VERSIONS + ) + + if not instance._url: + instance._url = instance.get_endpoint_for_service_type(API_NAME) + + return image_client(instance._url, token=instance._token) diff --git a/openstackclient/image/v2/__init__.py b/openstackclient/image/v2/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py new file mode 100644 index 0000000000..41520612d6 --- /dev/null +++ b/openstackclient/image/v2/image.py @@ -0,0 +1,109 @@ +# Copyright 2012 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. +# + +"""Image Action Implementations""" + +import logging + +from cliff import command +from cliff import lister + +from glanceclient.common import utils as gc_utils +from openstackclient.common import utils + + +class ListImage(lister.Lister): + """List image command""" + + api = "image" + log = logging.getLogger(__name__ + ".ListImage") + + def get_parser(self, prog_name): + parser = super(ListImage, self).get_parser(prog_name) + parser.add_argument( + "--page-size", + metavar="", + help="Number of images to request in each paginated request.", + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + image_client = self.app.client_manager.image + + kwargs = {} + if parsed_args.page_size is not None: + kwargs["page_size"] = parsed_args.page_size + + data = image_client.images.list(**kwargs) + columns = ["ID", "Name"] + + return (columns, + (utils.get_item_properties( + s, columns, + ) for s in data), + ) + + +class SaveImage(command.Command): + """Save image command""" + + api = "image" + log = logging.getLogger(__name__ + ".SaveImage") + + def get_parser(self, prog_name): + parser = super(SaveImage, self).get_parser(prog_name) + parser.add_argument( + "--file", + metavar="", + help="Local file to save downloaded image data to. " + "If this is not specified the image data will be " + "written to stdout.", + ) + parser.add_argument( + "id", + metavar="", + help="ID of image to describe.", + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + image_client = self.app.client_manager.image + + data = image_client.images.data(parsed_args.id) + gc_utils.save_image(data, parsed_args.file) + + +class ShowImage(command.Command): + """Show image command""" + + api = "image" + log = logging.getLogger(__name__ + ".ShowImage") + + def get_parser(self, prog_name): + parser = super(ShowImage, self).get_parser(prog_name) + parser.add_argument( + "id", + metavar="", + help="ID of image to describe.", + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + image_client = self.app.client_manager.image + + gc_utils.print_dict(image_client.images.get(parsed_args.id)) diff --git a/setup.py b/setup.py index 7dde53f2bf..df9fefffe7 100644 --- a/setup.py +++ b/setup.py @@ -110,6 +110,9 @@ def read(fname): 'set_user=openstackclient.identity.v2_0.user:SetUser', 'show_user=openstackclient.identity.v2_0.user:ShowUser', 'list_user-role=openstackclient.identity.v2_0.role:ListUserRole', + 'list_image=openstackclient.image.v2.image:ListImage', + 'show_image=openstackclient.image.v2.image:ShowImage', + 'save_image=openstackclient.image.v2.image:SaveImage', ] } ) From 089f4cf7164053ddda9e94254d5a03df6edba44a Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Mon, 21 Jan 2013 15:36:55 -0600 Subject: [PATCH 0014/3614] Updated gitignore and manifest. Change-Id: Ifcac1c9177865da9d248769961ec17fa8f44e67a --- .gitignore | 12 +++++++----- MANIFEST.in | 9 ++++++++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 4624f7dd23..e038b53ce1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,17 @@ +*.DS_Store +*.egg* *.log -*.log.* +*.mo *.pyc +*.swo *.swp *~ .openstackclient-venv +.tox .venv AUTHORS build -dist -python_openstackclient.egg-info -.tox/ ChangeLog -TAGS +dist +doc/build openstackclient/versioninfo diff --git a/MANIFEST.in b/MANIFEST.in index 0856b5efb6..a7f747b848 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,13 @@ include AUTHORS +include ChangeLog include LICENSE -include README.rst include openstackclient/versioninfo +include README.rst + recursive-include doc * recursive-include tests * + +exclude .gitignore +exclude .gitreview + +global-exclude *.pyc From c1ea2989049c102fde0ea22ac06d066a34d7b0db Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Mon, 21 Jan 2013 13:44:38 -0600 Subject: [PATCH 0015/3614] Clean up test environment and remove unused imports. First round of adding more complete unit test coverage. Change-Id: Ic1979c499ca6fcb784892a95954a3527539c4e53 --- .mailmap | 2 + README.rst | 1 - openstackclient/common/clientmanager.py | 1 - openstackclient/common/openstackkeyring.py | 1 - openstackclient/common/utils.py | 2 - openstackclient/compute/client.py | 1 - openstackclient/compute/v2/server.py | 1 - openstackclient/identity/client.py | 1 - openstackclient/openstack/common/setup.py | 4 - run_tests.sh | 147 +++++++++++-- tests/utils.py | 4 - tools/install_venv.py | 244 +++++++++++++++++++++ tools/pip-requires | 7 +- tools/test-requires | 6 +- tools/with_venv.sh | 12 +- 15 files changed, 377 insertions(+), 57 deletions(-) create mode 100644 .mailmap create mode 100644 tools/install_venv.py diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000000..c3f1f5615d --- /dev/null +++ b/.mailmap @@ -0,0 +1,2 @@ + + diff --git a/README.rst b/README.rst index b8e26c9fae..1b2bdec1be 100644 --- a/README.rst +++ b/README.rst @@ -98,7 +98,6 @@ Additional command-line options and their associated environment variables are listed here:: --debug # turns on some debugging of the API conversation - (via httplib2) --verbose | -v # Increase verbosity of output. Can be repeated. --quiet | -q # suppress output except warnings and errors --help | -h # show a help message and exit diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 73c2e5708c..4d04921304 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -20,7 +20,6 @@ import logging -from openstackclient.common import exceptions as exc from openstackclient.compute import client as compute_client from openstackclient.identity import client as identity_client from openstackclient.image import client as image_client diff --git a/openstackclient/common/openstackkeyring.py b/openstackclient/common/openstackkeyring.py index 3a5ce27f8c..2b03e75345 100644 --- a/openstackclient/common/openstackkeyring.py +++ b/openstackclient/common/openstackkeyring.py @@ -21,7 +21,6 @@ from Crypto.Cipher import AES -import crypt import keyring import os diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 70555be5fa..c099889d29 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -23,8 +23,6 @@ import sys import uuid -import prettytable - from openstackclient.common import exceptions diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py index 3c17b17ad6..bef9f949db 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -17,7 +17,6 @@ import logging -from openstackclient.common import exceptions as exc from openstackclient.common import utils LOG = logging.getLogger(__name__) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index e496bd459a..366d7640ef 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -21,7 +21,6 @@ import logging import os -import sys import time from cliff import command diff --git a/openstackclient/identity/client.py b/openstackclient/identity/client.py index b7066e5e62..3d58ad7ba8 100644 --- a/openstackclient/identity/client.py +++ b/openstackclient/identity/client.py @@ -17,7 +17,6 @@ import logging -from openstackclient.common import exceptions as exc from openstackclient.common import utils LOG = logging.getLogger(__name__) diff --git a/openstackclient/openstack/common/setup.py b/openstackclient/openstack/common/setup.py index e6f72f034e..e960002d26 100644 --- a/openstackclient/openstack/common/setup.py +++ b/openstackclient/openstack/common/setup.py @@ -78,10 +78,6 @@ def parse_requirements(requirements_files=['requirements.txt', # -f lines are for index locations, and don't get used here elif re.match(r'\s*-f\s+', line): pass - # argparse is part of the standard library starting with 2.7 - # adding it to the requirements list screws distro installs - elif line == 'argparse' and sys.version_info >= (2, 7): - pass else: requirements.append(line) diff --git a/run_tests.sh b/run_tests.sh index b9252cac79..4700c1164a 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -1,49 +1,152 @@ #!/bin/bash +set -eu + function usage { echo "Usage: $0 [OPTION]..." - echo "Run python-openstackclient's test suite(s)" + echo "Run python-openstackclient test suite" echo "" + echo " -V, --virtual-env Always use virtualenv. Install automatically if not present" + echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment" + echo " -s, --no-site-packages Isolate the virtualenv from the global Python environment" + echo " -x, --stop Stop running tests after the first error or failure." + echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added." echo " -p, --pep8 Just run pep8" + echo " -P, --no-pep8 Don't run pep8" + echo " -c, --coverage Generate coverage report" echo " -h, --help Print this usage message" + echo " --hide-elapsed Don't print the elapsed time for each test along with slow test list" echo "" - echo "This script is deprecated and currently retained for compatibility." - echo 'You can run the full test suite for multiple environments by running "tox".' - echo 'You can run tests for only python 2.7 by running "tox -e py27", or run only' - echo 'the pep8 tests with "tox -e pep8".' + echo "Note: with no options specified, the script will try to run the tests in a virtual environment," + echo " If no virtualenv is found, the script will ask if you would like to create one. If you " + echo " prefer to run tests NOT in a virtual environment, simply pass the -N option." exit } -command -v tox > /dev/null 2>&1 -if [ $? -ne 0 ]; then - echo 'This script requires "tox" to run.' - echo 'You can install it with "pip install tox".' - exit 1; -fi - -just_pep8=0 - function process_option { case "$1" in -h|--help) usage;; - -p|--pep8) let just_pep8=1;; + -V|--virtual-env) always_venv=1; never_venv=0;; + -N|--no-virtual-env) always_venv=0; never_venv=1;; + -s|--no-site-packages) no_site_packages=1;; + -f|--force) force=1;; + -p|--pep8) just_pep8=1;; + -P|--no-pep8) no_pep8=1;; + -c|--coverage) coverage=1;; + -*) noseopts="$noseopts $1";; + *) noseargs="$noseargs $1" esac } +venv=.venv +with_venv=tools/with_venv.sh +always_venv=0 +never_venv=0 +force=0 +no_site_packages=0 +installvenvopts= +noseargs= +noseopts= +wrapper="" +just_pep8=0 +no_pep8=0 +coverage=0 + for arg in "$@"; do process_option $arg done +# If enabled, tell nose to collect coverage data +if [ $coverage -eq 1 ]; then + noseopts="$noseopts --with-coverage --cover-package=openstackclient" +fi + +if [ $no_site_packages -eq 1 ]; then + installvenvopts="--no-site-packages" +fi + +function run_tests { + # Just run the test suites in current environment + ${wrapper} $NOSETESTS + # If we get some short import error right away, print the error log directly + RESULT=$? + return $RESULT +} + +function run_pep8 { + echo "Running pep8 ..." + srcfiles="openstackclient tests" + # Just run PEP8 in current environment + # + # NOTE(sirp): W602 (deprecated 3-arg raise) is being ignored for the + # following reasons: + # + # 1. It's needed to preserve traceback information when re-raising + # exceptions; this is needed b/c Eventlet will clear exceptions when + # switching contexts. + # + # 2. There doesn't appear to be an alternative, "pep8-tool" compatible way of doing this + # in Python 2 (in Python 3 `with_traceback` could be used). + # + # 3. Can find no corroborating evidence that this is deprecated in Python 2 + # other than what the PEP8 tool claims. It is deprecated in Python 3, so, + # perhaps the mistake was thinking that the deprecation applied to Python 2 + # as well. + pep8_opts="--ignore=E202,W602 --repeat" + ${wrapper} pep8 ${pep8_opts} ${srcfiles} +} + +NOSETESTS="nosetests $noseopts $noseargs" + +if [ $never_venv -eq 0 ] +then + # Remove the virtual environment if --force used + if [ $force -eq 1 ]; then + echo "Cleaning virtualenv..." + rm -rf ${venv} + fi + if [ -e ${venv} ]; then + wrapper="${with_venv}" + else + if [ $always_venv -eq 1 ]; then + # Automatically install the virtualenv + python tools/install_venv.py $installvenvopts + wrapper="${with_venv}" + else + echo -e "No virtual environment found...create one? (Y/n) \c" + read use_ve + if [ "x$use_ve" = "xY" -o "x$use_ve" = "x" -o "x$use_ve" = "xy" ]; then + # Install the virtualenv and run the test suite in it + python tools/install_venv.py $installvenvopts + wrapper=${with_venv} + fi + fi + fi +fi + +# Delete old coverage data from previous runs +if [ $coverage -eq 1 ]; then + ${wrapper} coverage erase +fi + if [ $just_pep8 -eq 1 ]; then - tox -e pep8 - exit + run_pep8 + exit fi -tox -e py27 $toxargs 2>&1 | tee run_tests.err.log || exit -if [ ${PIPESTATUS[0]} -ne 0 ]; then - exit ${PIPESTATUS[0]} +run_tests + +# NOTE(sirp): we only want to run pep8 when we're running the full-test suite, +# not when we're running tests individually. To handle this, we need to +# distinguish between options (noseopts), which begin with a '-', and +# arguments (noseargs). +if [ -z "$noseargs" ]; then + if [ $no_pep8 -eq 0 ]; then + run_pep8 + fi fi -if [ -z "$toxargs" ]; then - tox -e pep8 +if [ $coverage -eq 1 ]; then + echo "Generating coverage report in covhtml/" + ${wrapper} coverage html -d covhtml -i fi diff --git a/tests/utils.py b/tests/utils.py index 792fe88ead..633442c92f 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -2,7 +2,6 @@ import time -import mox import testtools @@ -10,12 +9,9 @@ class TestCase(testtools.TestCase): def setUp(self): super(TestCase, self).setUp() - self.mox = mox.Mox() self._original_time = time.time time.time = lambda: 1234 def tearDown(self): time.time = self._original_time - self.mox.UnsetStubs() - self.mox.VerifyAll() super(TestCase, self).tearDown() diff --git a/tools/install_venv.py b/tools/install_venv.py new file mode 100644 index 0000000000..0957d26b17 --- /dev/null +++ b/tools/install_venv.py @@ -0,0 +1,244 @@ +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Copyright 2010 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. + +""" +Installation script for python-openstackclient's development virtualenv +""" + +import optparse +import os +import subprocess +import sys +import platform + + +ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) +VENV = os.path.join(ROOT, '.venv') +PIP_REQUIRES = os.path.join(ROOT, 'tools', 'pip-requires') +TEST_REQUIRES = os.path.join(ROOT, 'tools', 'test-requires') +PY_VERSION = "python%s.%s" % (sys.version_info[0], sys.version_info[1]) + + +def die(message, *args): + print >> sys.stderr, message % args + sys.exit(1) + + +def check_python_version(): + if sys.version_info < (2, 6): + die("Need Python Version >= 2.6") + + +def run_command_with_code(cmd, redirect_output=True, check_exit_code=True): + """ + Runs a command in an out-of-process shell, returning the + output of that command. Working directory is ROOT. + """ + if redirect_output: + stdout = subprocess.PIPE + else: + stdout = None + + proc = subprocess.Popen(cmd, cwd=ROOT, stdout=stdout) + output = proc.communicate()[0] + if check_exit_code and proc.returncode != 0: + die('Command "%s" failed.\n%s', ' '.join(cmd), output) + return (output, proc.returncode) + + +def run_command(cmd, redirect_output=True, check_exit_code=True): + return run_command_with_code(cmd, redirect_output, check_exit_code)[0] + + +class Distro(object): + + def check_cmd(self, cmd): + return bool(run_command(['which', cmd], check_exit_code=False).strip()) + + def install_virtualenv(self): + if self.check_cmd('virtualenv'): + return + + if self.check_cmd('easy_install'): + print 'Installing virtualenv via easy_install...', + if run_command(['easy_install', 'virtualenv']): + print 'Succeeded' + return + else: + print 'Failed' + + die('ERROR: virtualenv not found.\n\nDevelopment' + ' requires virtualenv, please install it using your' + ' favorite package management tool') + + def post_process(self): + """Any distribution-specific post-processing gets done here. + + In particular, this is useful for applying patches to code inside + the venv.""" + pass + + +class Debian(Distro): + """This covers all Debian-based distributions.""" + + def check_pkg(self, pkg): + return run_command_with_code(['dpkg', '-l', pkg], + check_exit_code=False)[1] == 0 + + def apt_install(self, pkg, **kwargs): + run_command(['sudo', 'apt-get', 'install', '-y', pkg], **kwargs) + + def apply_patch(self, originalfile, patchfile): + run_command(['patch', originalfile, patchfile]) + + def install_virtualenv(self): + if self.check_cmd('virtualenv'): + return + + if not self.check_pkg('python-virtualenv'): + self.apt_install('python-virtualenv', check_exit_code=False) + + super(Debian, self).install_virtualenv() + + +class Fedora(Distro): + """This covers all Fedora-based distributions. + + Includes: Fedora, RHEL, CentOS, Scientific Linux""" + + def check_pkg(self, pkg): + return run_command_with_code(['rpm', '-q', pkg], + check_exit_code=False)[1] == 0 + + def yum_install(self, pkg, **kwargs): + run_command(['sudo', 'yum', 'install', '-y', pkg], **kwargs) + + def apply_patch(self, originalfile, patchfile): + run_command(['patch', originalfile, patchfile]) + + def install_virtualenv(self): + if self.check_cmd('virtualenv'): + return + + if not self.check_pkg('python-virtualenv'): + self.yum_install('python-virtualenv', check_exit_code=False) + + super(Fedora, self).install_virtualenv() + + +def get_distro(): + if os.path.exists('/etc/fedora-release') or \ + os.path.exists('/etc/redhat-release'): + return Fedora() + elif os.path.exists('/etc/debian_version'): + return Debian() + else: + return Distro() + + +def check_dependencies(): + get_distro().install_virtualenv() + + +def create_virtualenv(venv=VENV, no_site_packages=True): + """Creates the virtual environment and installs PIP only into the + virtual environment + """ + print 'Creating venv...', + if no_site_packages: + run_command(['virtualenv', '-q', '--no-site-packages', VENV]) + else: + run_command(['virtualenv', '-q', VENV]) + print 'done.' + print 'Installing pip in virtualenv...', + if not run_command(['tools/with_venv.sh', 'easy_install', + 'pip>1.0']).strip(): + die("Failed to install pip.") + print 'done.' + + +def pip_install(*args): + run_command(['tools/with_venv.sh', + 'pip', 'install', '--upgrade'] + list(args), + redirect_output=False) + + +def install_dependencies(venv=VENV): + print 'Installing dependencies with pip (this can take a while)...' + + # First things first, make sure our venv has the latest pip and distribute. + pip_install('pip') + pip_install('distribute') + + pip_install('-r', PIP_REQUIRES) + pip_install('-r', TEST_REQUIRES) + + # Tell the virtual env how to "import openstackclient" + pthfile = os.path.join(venv, "lib", PY_VERSION, "site-packages", + "openstackclient.pth") + f = open(pthfile, 'w') + f.write("%s\n" % ROOT) + + +def post_process(): + get_distro().post_process() + + +def print_help(): + help = """ + python-openstackclient development environment setup is complete. + + python-openstackclient development uses virtualenv to track and manage Python + dependencies while in development and testing. + + To activate the python-openstackclient virtualenv for the extent of your current + shell session you can run: + + $ source .venv/bin/activate + + Or, if you prefer, you can run commands in the virtualenv on a case by case + basis by running: + + $ tools/with_venv.sh + + Also, make test will automatically use the virtualenv. + """ + print help + + +def parse_args(): + """Parse command-line arguments""" + parser = optparse.OptionParser() + parser.add_option("-n", "--no-site-packages", dest="no_site_packages", + default=False, action="store_true", + help="Do not inherit packages from global Python install") + return parser.parse_args() + + +def main(argv): + (options, args) = parse_args() + check_python_version() + check_dependencies() + create_virtualenv(no_site_packages=options.no_site_packages) + install_dependencies() + post_process() + print_help() + +if __name__ == '__main__': + main(sys.argv) diff --git a/tools/pip-requires b/tools/pip-requires index 1aa8fed1d8..af2c56ad64 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -1,11 +1,6 @@ -argparse cliff -cmd2 -httplib2 keyring -prettytable -pyparsing>=1.5.6,<2.0 pycrypto +python-glanceclient>=0.5.1 python-keystoneclient>=0.2,<1.0 python-novaclient>=2 -simplejson diff --git a/tools/test-requires b/tools/test-requires index 40350f0256..d96d52b60d 100644 --- a/tools/test-requires +++ b/tools/test-requires @@ -1,14 +1,12 @@ distribute>=0.6.24 -coverage fixtures mock -mox nose nose-exclude -nosehtmloutput nosexcover +nosehtmloutput openstack.nose_plugin -pep8==0.6.1 +pep8==1.1 sphinx>=1.1.2 testtools>=0.9.22 diff --git a/tools/with_venv.sh b/tools/with_venv.sh index e6e44f599d..c8d2940fc7 100755 --- a/tools/with_venv.sh +++ b/tools/with_venv.sh @@ -1,10 +1,4 @@ #!/bin/bash - -command -v tox > /dev/null 2>&1 -if [ $? -ne 0 ]; then - echo 'This script requires "tox" to run.' - echo 'You can install it with "pip install tox".' - exit 1; -fi - -tox -evenv -- $@ +TOOLS=`dirname $0` +VENV=$TOOLS/../.venv +source $VENV/bin/activate && $@ From 63c8bb5306d15c7679b2cd1d22b6ee556967863d Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 22 Jan 2013 11:09:11 -0600 Subject: [PATCH 0016/3614] Migrate from nose to testr. Run tests with testr for parallel execution. Part of blueprint grizzly-testtools. Change-Id: I560592186f2f440049a451a32e58067262ab62d0 --- .gitignore | 1 + .testr.conf | 4 +++ HACKING | 8 ++++++ run_tests.sh | 60 +++++++++++++++++++++++++++++++++------------ setup.cfg | 8 ------ setup.py | 1 - tests/utils.py | 17 +++++++------ tools/test-requires | 11 ++++----- tox.ini | 32 ++++-------------------- 9 files changed, 78 insertions(+), 64 deletions(-) create mode 100644 .testr.conf diff --git a/.gitignore b/.gitignore index e038b53ce1..8b413ce11d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ *.swp *~ .openstackclient-venv +.testrepository .tox .venv AUTHORS diff --git a/.testr.conf b/.testr.conf new file mode 100644 index 0000000000..2109af6ce0 --- /dev/null +++ b/.testr.conf @@ -0,0 +1,4 @@ +[DEFAULT] +test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 ${PYTHON:-python} -m subunit.run discover -t ./ ./tests $LISTOPT $IDOPTION +test_id_option=--load-list $IDFILE +test_list_option=--list diff --git a/HACKING b/HACKING index 1218e5f712..e9bcb7eaf4 100644 --- a/HACKING +++ b/HACKING @@ -112,3 +112,11 @@ Text encoding returntext = do_some_magic_with(mytext) returnstring = returntext.encode('utf-8') outfile.write(returnstring) + +Running Tests +------------- +The testing system is based on a combination of tox and testr. If you just +want to run the whole suite, run `tox` and all will be fine. However, if +you'd like to dig in a bit more, you might want to learn some things about +testr itself. A basic walkthrough for OpenStack can be found at +http://wiki.openstack.org/testr diff --git a/run_tests.sh b/run_tests.sh index 4700c1164a..ff5f83ec4e 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -33,8 +33,8 @@ function process_option { -p|--pep8) just_pep8=1;; -P|--no-pep8) no_pep8=1;; -c|--coverage) coverage=1;; - -*) noseopts="$noseopts $1";; - *) noseargs="$noseargs $1" + -*) testropts="$testropts $1";; + *) testrargs="$testrargs $1" esac } @@ -45,34 +45,62 @@ never_venv=0 force=0 no_site_packages=0 installvenvopts= -noseargs= -noseopts= +testrargs= +testropts= wrapper="" just_pep8=0 no_pep8=0 coverage=0 +LANG=en_US.UTF-8 +LANGUAGE=en_US:en +LC_ALL=C + for arg in "$@"; do process_option $arg done -# If enabled, tell nose to collect coverage data -if [ $coverage -eq 1 ]; then - noseopts="$noseopts --with-coverage --cover-package=openstackclient" -fi - if [ $no_site_packages -eq 1 ]; then installvenvopts="--no-site-packages" fi +function init_testr { + if [ ! -d .testrepository ]; then + ${wrapper} testr init + fi +} + function run_tests { + # Cleanup *.pyc + ${wrapper} find . -type f -name "*.pyc" -delete + + if [ $coverage -eq 1 ]; then + # Do not test test_coverage_ext when gathering coverage. + if [ "x$testrargs" = "x" ]; then + testrargs = "^(?!.*test_coverage_ext).*$" + fi + export PYTHON="${wrapper} coverage run --source novaclient --parallel-mode" + fi # Just run the test suites in current environment - ${wrapper} $NOSETESTS - # If we get some short import error right away, print the error log directly + set +e + TESTRTESTS="$TESTRTESTS $testrargs" + echo "Running \`${wrapper} $TESTRTESTS\`" + ${wrapper} $TESTRTESTS RESULT=$? + set -e + + copy_subunit_log + return $RESULT } +function copy_subunit_log { + LOGNAME=`cat .testrepository/next-stream` + LOGNAME=$(($LOGNAME - 1)) + LOGNAME=".testrepository/${LOGNAME}" + cp $LOGNAME subunit.log +} + function run_pep8 { echo "Running pep8 ..." srcfiles="openstackclient tests" @@ -96,7 +124,7 @@ function run_pep8 { ${wrapper} pep8 ${pep8_opts} ${srcfiles} } -NOSETESTS="nosetests $noseopts $noseargs" +TESTRTESTS="testr run --parallel $testropts" if [ $never_venv -eq 0 ] then @@ -134,13 +162,14 @@ if [ $just_pep8 -eq 1 ]; then exit fi +init_testr run_tests # NOTE(sirp): we only want to run pep8 when we're running the full-test suite, # not when we're running tests individually. To handle this, we need to # distinguish between options (noseopts), which begin with a '-', and -# arguments (noseargs). -if [ -z "$noseargs" ]; then +# arguments (testrargs). +if [ -z "$testrargs" ]; then if [ $no_pep8 -eq 0 ]; then run_pep8 fi @@ -148,5 +177,6 @@ fi if [ $coverage -eq 1 ]; then echo "Generating coverage report in covhtml/" - ${wrapper} coverage html -d covhtml -i + ${wrapper} cverage combine + ${wrapper} coverage html --include='novaclient/*' --omit='novaclient/openstack/common/*' -d covhtml -i fi diff --git a/setup.cfg b/setup.cfg index 27d2986511..11c72013c2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,11 +1,3 @@ -[nosetests] -cover-package = openstackclient -cover-html = true -cover-erase = true -cover-inclusive = true -verbosity=2 -detailed-errors=1 - [build_sphinx] source-dir = doc/source build-dir = doc/build diff --git a/setup.py b/setup.py index df9fefffe7..fe0c2ebdb8 100644 --- a/setup.py +++ b/setup.py @@ -52,7 +52,6 @@ def read(fname): install_requires=requires, dependency_links=dependency_links, cmdclass=setup.get_cmdclass(), - test_suite="nose.collector", entry_points={ 'console_scripts': ['openstack=openstackclient.shell:main'], 'openstack.cli': [ diff --git a/tests/utils.py b/tests/utils.py index 633442c92f..9027472578 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,17 +1,20 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -import time +import os +import fixtures import testtools class TestCase(testtools.TestCase): - def setUp(self): super(TestCase, self).setUp() - self._original_time = time.time - time.time = lambda: 1234 + if (os.environ.get("OS_STDOUT_NOCAPTURE") == "True" and + os.environ.get("OS_STDOUT_NOCAPTURE") == "1"): + stdout = self.useFixture(fixtures.StringStream("stdout")).stream + self.useFixture(fixtures.MonkeyPatch("sys.stdout", stdout)) + if (os.environ.get("OS_STDERR_NOCAPTURE") == "True" and + os.environ.get("OS_STDERR_NOCAPTURE") == "1"): + stderr = self.useFixture(fixtures.StringStream("stderr")).stream + self.useFixture(fixtures.MonkeyPatch("sys.stderr", stderr)) def tearDown(self): - time.time = self._original_time super(TestCase, self).tearDown() diff --git a/tools/test-requires b/tools/test-requires index d96d52b60d..7fb687d25c 100644 --- a/tools/test-requires +++ b/tools/test-requires @@ -1,12 +1,11 @@ distribute>=0.6.24 -fixtures +coverage +discover +fixtures>=0.3.12 mock -nose -nose-exclude -nosexcover -nosehtmloutput openstack.nose_plugin pep8==1.1 sphinx>=1.1.2 -testtools>=0.9.22 +testrepository>=0.0.13 +testtools>=0.9.26 diff --git a/tox.ini b/tox.ini index f562534181..6b4a96dbdb 100644 --- a/tox.ini +++ b/tox.ini @@ -3,14 +3,12 @@ envlist = py26,py27,pep8 [testenv] setenv = VIRTUAL_ENV={envdir} - NOSE_WITH_OPENSTACK=1 - NOSE_OPENSTACK_COLOR=1 - NOSE_OPENSTACK_RED=0.05 - NOSE_OPENSTACK_YELLOW=0.025 - NOSE_OPENSTACK_SHOW_ELAPSED=1 + LANG=en_US.UTF-8 + LANGUAGE=en_US:en + LC_ALL=C deps = -r{toxinidir}/tools/pip-requires -r{toxinidir}/tools/test-requires -commands = nosetests +commands = python setup.py testr --testr-args='{posargs}' [testenv:pep8] deps = pep8==1.1 @@ -20,27 +18,7 @@ commands = pep8 --repeat --show-source openstackclient setup.py commands = {posargs} [testenv:cover] -commands = nosetests --cover-erase --cover-package=openstackclient --with-xcoverage +commands = python setup.py testr --coverage --testr-args='{posargs}' [tox:jenkins] downloadcache = ~/cache/pip - -[testenv:jenkins26] -basepython = python2.6 -setenv = NOSE_WITH_XUNIT=1 -deps = file://{toxinidir}/.cache.bundle - -[testenv:jenkins27] -basepython = python2.7 -setenv = NOSE_WITH_XUNIT=1 -deps = file://{toxinidir}/.cache.bundle - -[testenv:jenkinscover] -deps = file://{toxinidir}/.cache.bundle -setenv = NOSE_WITH_XUNIT=1 -commands = nosetests --cover-erase --cover-package=openstackclient --with-xcoverage - -[testenv:jenkinsvenv] -deps = file://{toxinidir}/.cache.bundle -setenv = NOSE_WITH_XUNIT=1 -commands = {posargs} From df34db8afbe85e64a4b5d11338ef08b3ed287539 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 22 Jan 2013 14:03:35 -0600 Subject: [PATCH 0017/3614] Remove incorrect 'raise'. Fixes bug 1096102. Change-Id: Ibcdccd949566f47bb516c7562149d6b0100fce18 --- openstackclient/identity/v2_0/endpoint.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openstackclient/identity/v2_0/endpoint.py b/openstackclient/identity/v2_0/endpoint.py index a9a2e7a40b..cb06b6abbd 100644 --- a/openstackclient/identity/v2_0/endpoint.py +++ b/openstackclient/identity/v2_0/endpoint.py @@ -211,8 +211,6 @@ def take_action(self, parsed_args): msg = "No service with a type, name or ID of '%s' exists" \ % parsed_args.service raise exceptions.CommandError(msg) - else: - raise data = identity_client.endpoints.list() for ep in data: From 0a4912f9fb07a7330a2527af4cc7c8949c685401 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 24 Jan 2013 12:00:30 -0600 Subject: [PATCH 0018/3614] Standardize on a copyright header and ensure all files have them. Change-Id: I64812bca01ca655c9cf9239a0daea84907082a29 --- openstackclient/__init__.py | 14 ++++++++++++++ openstackclient/common/__init__.py | 14 ++++++++++++++ openstackclient/common/clientmanager.py | 22 ++++++++++------------ openstackclient/common/command.py | 22 ++++++++++------------ openstackclient/common/exceptions.py | 22 ++++++++++------------ openstackclient/common/openstackkeyring.py | 22 ++++++++++------------ openstackclient/common/utils.py | 22 ++++++++++------------ openstackclient/compute/__init__.py | 14 ++++++++++++++ openstackclient/compute/client.py | 22 ++++++++++------------ openstackclient/compute/v2/__init__.py | 14 ++++++++++++++ openstackclient/compute/v2/server.py | 22 ++++++++++------------ openstackclient/identity/__init__.py | 14 ++++++++++++++ openstackclient/identity/client.py | 22 ++++++++++------------ openstackclient/identity/v2_0/__init__.py | 14 ++++++++++++++ openstackclient/identity/v2_0/endpoint.py | 22 ++++++++++------------ openstackclient/identity/v2_0/role.py | 22 ++++++++++------------ openstackclient/identity/v2_0/service.py | 22 ++++++++++------------ openstackclient/identity/v2_0/tenant.py | 22 ++++++++++------------ openstackclient/identity/v2_0/user.py | 22 ++++++++++------------ openstackclient/image/__init__.py | 14 ++++++++++++++ openstackclient/image/client.py | 2 +- openstackclient/image/v2/__init__.py | 14 ++++++++++++++ openstackclient/image/v2/image.py | 2 +- openstackclient/shell.py | 22 ++++++++++------------ setup.py | 22 ++++++++++------------ tests/__init__.py | 14 ++++++++++++++ tests/test_clientmanager_clientcache.py | 14 ++++++++++++++ tests/test_shell.py | 22 ++++++++++------------ tests/utils.py | 15 +++++++++++++++ 29 files changed, 317 insertions(+), 194 deletions(-) diff --git a/openstackclient/__init__.py b/openstackclient/__init__.py index e69de29bb2..85ac2501b8 100644 --- a/openstackclient/__init__.py +++ b/openstackclient/__init__.py @@ -0,0 +1,14 @@ +# 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/__init__.py b/openstackclient/common/__init__.py index e69de29bb2..85ac2501b8 100644 --- a/openstackclient/common/__init__.py +++ b/openstackclient/common/__init__.py @@ -0,0 +1,14 @@ +# 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 4d04921304..f6f6642e6a 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -1,19 +1,17 @@ -# Copyright 2012 OpenStack LLC. -# All Rights Reserved +# 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 +# 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 +# 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. +# 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. # -# vim: tabstop=4 shiftwidth=4 softtabstop=4 """Manage access to the clients, including authenticating when needed. """ diff --git a/openstackclient/common/command.py b/openstackclient/common/command.py index 405f65556f..cfcb605c36 100644 --- a/openstackclient/common/command.py +++ b/openstackclient/common/command.py @@ -1,19 +1,17 @@ -# Copyright 2012 OpenStack LLC. -# All Rights Reserved +# 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 +# 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 +# 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. +# 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. # -# vim: tabstop=4 shiftwidth=4 softtabstop=4 """ OpenStack base command diff --git a/openstackclient/common/exceptions.py b/openstackclient/common/exceptions.py index 465c3c4843..9dfb4bc785 100644 --- a/openstackclient/common/exceptions.py +++ b/openstackclient/common/exceptions.py @@ -1,19 +1,17 @@ -# Copyright 2012 OpenStack LLC. -# All Rights Reserved. +# 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 +# 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 +# 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. +# 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. # -# vim: tabstop=4 shiftwidth=4 softtabstop=4 """ Exception definitions. diff --git a/openstackclient/common/openstackkeyring.py b/openstackclient/common/openstackkeyring.py index 2b03e75345..78c74b8f4a 100644 --- a/openstackclient/common/openstackkeyring.py +++ b/openstackclient/common/openstackkeyring.py @@ -1,19 +1,17 @@ -# Copyright 2011 OpenStack LLC. -# All Rights Reserved +# Copyright 2011-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 +# 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 +# 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. +# 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. # -# vim: tabstop=4 shiftwidth=4 softtabstop=4 """ Keyring backend for Openstack, to store encrypted password in a file. diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index c099889d29..19cca3f5f6 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -1,19 +1,17 @@ -# Copyright 2012 OpenStack LLC. -# All Rights Reserved. +# 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 +# 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 +# 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. +# 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. # -# vim: tabstop=4 shiftwidth=4 softtabstop=4 """ Common client utilities diff --git a/openstackclient/compute/__init__.py b/openstackclient/compute/__init__.py index e69de29bb2..85ac2501b8 100644 --- a/openstackclient/compute/__init__.py +++ b/openstackclient/compute/__init__.py @@ -0,0 +1,14 @@ +# 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 bef9f949db..e1db22c811 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -1,19 +1,17 @@ -# Copyright 2012 OpenStack LLC. -# All Rights Reserved +# 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 +# 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 +# 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. +# 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. # -# vim: tabstop=4 shiftwidth=4 softtabstop=4 import logging diff --git a/openstackclient/compute/v2/__init__.py b/openstackclient/compute/v2/__init__.py index e69de29bb2..85ac2501b8 100644 --- a/openstackclient/compute/v2/__init__.py +++ b/openstackclient/compute/v2/__init__.py @@ -0,0 +1,14 @@ +# 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/server.py b/openstackclient/compute/v2/server.py index 366d7640ef..a334e19832 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1,19 +1,17 @@ -# Copyright 2012 OpenStack LLC. -# All Rights Reserved. +# 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 +# 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 +# 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. +# 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. # -# vim: tabstop=4 shiftwidth=4 softtabstop=4 """ Server action implementations diff --git a/openstackclient/identity/__init__.py b/openstackclient/identity/__init__.py index e69de29bb2..85ac2501b8 100644 --- a/openstackclient/identity/__init__.py +++ b/openstackclient/identity/__init__.py @@ -0,0 +1,14 @@ +# 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 3d58ad7ba8..65bd14ce2d 100644 --- a/openstackclient/identity/client.py +++ b/openstackclient/identity/client.py @@ -1,19 +1,17 @@ -# Copyright 2012 OpenStack LLC. -# All Rights Reserved +# 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 +# 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 +# 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. +# 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. # -# vim: tabstop=4 shiftwidth=4 softtabstop=4 import logging diff --git a/openstackclient/identity/v2_0/__init__.py b/openstackclient/identity/v2_0/__init__.py index e69de29bb2..85ac2501b8 100644 --- a/openstackclient/identity/v2_0/__init__.py +++ b/openstackclient/identity/v2_0/__init__.py @@ -0,0 +1,14 @@ +# 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/endpoint.py b/openstackclient/identity/v2_0/endpoint.py index cb06b6abbd..f9c689ec80 100644 --- a/openstackclient/identity/v2_0/endpoint.py +++ b/openstackclient/identity/v2_0/endpoint.py @@ -1,19 +1,17 @@ -# Copyright 2012 OpenStack LLC. -# All Rights Reserved. +# 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 +# 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 +# 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. +# 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. # -# vim: tabstop=4 shiftwidth=4 softtabstop=4 """ Endpoint action implementations diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index 99143109ff..49e0365357 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -1,19 +1,17 @@ -# Copyright 2012 OpenStack LLC. -# All Rights Reserved. +# 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 +# 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 +# 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. +# 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. # -# vim: tabstop=4 shiftwidth=4 softtabstop=4 """ Role action implementations diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index da4af2eb2f..89fe605abc 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -1,19 +1,17 @@ -# Copyright 2012 OpenStack LLC. -# All Rights Reserved. +# 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 +# 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 +# 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. +# 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. # -# vim: tabstop=4 shiftwidth=4 softtabstop=4 """ Service action implementations diff --git a/openstackclient/identity/v2_0/tenant.py b/openstackclient/identity/v2_0/tenant.py index 21516f50b4..f24bc7265d 100644 --- a/openstackclient/identity/v2_0/tenant.py +++ b/openstackclient/identity/v2_0/tenant.py @@ -1,19 +1,17 @@ -# Copyright 2012 OpenStack LLC. -# All Rights Reserved. +# 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 +# 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 +# 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. +# 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. # -# vim: tabstop=4 shiftwidth=4 softtabstop=4 """ Tenant action implementations diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index fc7a9d73e0..cf69d73f5e 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -1,19 +1,17 @@ -# Copyright 2012 OpenStack LLC. -# All Rights Reserved. +# 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 +# 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 +# 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. +# 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. # -# vim: tabstop=4 shiftwidth=4 softtabstop=4 """ User action implementations diff --git a/openstackclient/image/__init__.py b/openstackclient/image/__init__.py index e69de29bb2..85ac2501b8 100644 --- a/openstackclient/image/__init__.py +++ b/openstackclient/image/__init__.py @@ -0,0 +1,14 @@ +# 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 0a7c576e7b..5285e6d7d8 100644 --- a/openstackclient/image/client.py +++ b/openstackclient/image/client.py @@ -1,4 +1,4 @@ -# Copyright 2012 OpenStack, LLC. +# 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 diff --git a/openstackclient/image/v2/__init__.py b/openstackclient/image/v2/__init__.py index e69de29bb2..85ac2501b8 100644 --- a/openstackclient/image/v2/__init__.py +++ b/openstackclient/image/v2/__init__.py @@ -0,0 +1,14 @@ +# 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 41520612d6..2e3515db8a 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -1,4 +1,4 @@ -# Copyright 2012 OpenStack, LLC. +# 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 diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 531ac258d6..c007fc53b4 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -1,19 +1,17 @@ -# Copyright 2012 OpenStack LLC. -# All Rights Reserved +# 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 +# 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 +# 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. +# 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. # -# vim: tabstop=4 shiftwidth=4 softtabstop=4 """ Command-line interface to the OpenStack APIs diff --git a/setup.py b/setup.py index fe0c2ebdb8..fdc0d634b4 100644 --- a/setup.py +++ b/setup.py @@ -1,19 +1,17 @@ -# Copyright 2012 OpenStack LLC. -# All Rights Reserved +# 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 +# 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 +# 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. +# 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. # -# vim: tabstop=4 shiftwidth=4 softtabstop=4 import os diff --git a/tests/__init__.py b/tests/__init__.py index e69de29bb2..85ac2501b8 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1,14 @@ +# 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/tests/test_clientmanager_clientcache.py b/tests/test_clientmanager_clientcache.py index 200da01fd3..8ebf14be42 100644 --- a/tests/test_clientmanager_clientcache.py +++ b/tests/test_clientmanager_clientcache.py @@ -1,3 +1,17 @@ +# 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. +# from openstackclient.common import clientmanager diff --git a/tests/test_shell.py b/tests/test_shell.py index e897fdfa0b..cf4fc9244f 100644 --- a/tests/test_shell.py +++ b/tests/test_shell.py @@ -1,19 +1,17 @@ -# Copyright 2012 OpenStack LLC. -# All Rights Reserved +# 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 +# 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 +# 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. +# 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. # -# vim: tabstop=4 shiftwidth=4 softtabstop=4 import os import mock diff --git a/tests/utils.py b/tests/utils.py index 9027472578..5c4b50c623 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,3 +1,18 @@ +# 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 os import fixtures From def0f8ab7719f86f16424e941247732f8a98b735 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 24 Jan 2013 12:08:35 -0600 Subject: [PATCH 0019/3614] Sync latest openstack-common updates. Change-Id: I09adc9b5c01aa97ffba58dff8a696172e8654e3e --- openstackclient/openstack/common/setup.py | 209 +++++++++------------- setup.py | 6 +- 2 files changed, 88 insertions(+), 127 deletions(-) diff --git a/openstackclient/openstack/common/setup.py b/openstackclient/openstack/common/setup.py index e960002d26..53af7203ea 100644 --- a/openstackclient/openstack/common/setup.py +++ b/openstackclient/openstack/common/setup.py @@ -1,6 +1,7 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack LLC. +# Copyright 2012-2013 Hewlett-Packard Development Company, L.P. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -19,7 +20,7 @@ Utilities with minimum-depends for use in setup.py """ -import datetime +import email import os import re import subprocess @@ -33,11 +34,12 @@ def parse_mailmap(mailmap='.mailmap'): if os.path.exists(mailmap): with open(mailmap, 'r') as fp: for l in fp: - l = l.strip() - if not l.startswith('#') and ' ' in l: - canonical_email, alias = [x for x in l.split(' ') - if x.startswith('<')] - mapping[alias] = canonical_email + try: + canonical_email, alias = re.match( + r'[^#]*?(<.+>).*(<.+>).*', l).groups() + except AttributeError: + continue + mapping[alias] = canonical_email return mapping @@ -45,8 +47,8 @@ def canonicalize_emails(changelog, mapping): """Takes in a string and an email alias mapping and replaces all instances of the aliases in the string with their real email. """ - for alias, email in mapping.iteritems(): - changelog = changelog.replace(alias, email) + for alias, email_address in mapping.iteritems(): + changelog = changelog.replace(alias, email_address) return changelog @@ -78,6 +80,10 @@ def parse_requirements(requirements_files=['requirements.txt', # -f lines are for index locations, and don't get used here elif re.match(r'\s*-f\s+', line): pass + # argparse is part of the standard library starting with 2.7 + # adding it to the requirements list screws distro installs + elif line == 'argparse' and sys.version_info >= (2, 7): + pass else: requirements.append(line) @@ -102,23 +108,17 @@ def parse_dependency_links(requirements_files=['requirements.txt', return dependency_links -def write_requirements(): - venv = os.environ.get('VIRTUAL_ENV', None) - if venv is not None: - with open("requirements.txt", "w") as req_file: - output = subprocess.Popen(["pip", "-E", venv, "freeze", "-l"], - stdout=subprocess.PIPE) - requirements = output.communicate()[0].strip() - req_file.write(requirements) - - -def _run_shell_command(cmd): +def _run_shell_command(cmd, throw_on_error=False): if os.name == 'nt': output = subprocess.Popen(["cmd.exe", "/C", cmd], - stdout=subprocess.PIPE) + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) else: output = subprocess.Popen(["/bin/sh", "-c", cmd], - stdout=subprocess.PIPE) + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + if output.returncode and throw_on_error: + raise Exception("%s returned %d" % cmd, output.returncode) out = output.communicate() if len(out) == 0: return None @@ -127,57 +127,6 @@ def _run_shell_command(cmd): return out[0].strip() -def _get_git_next_version_suffix(branch_name): - datestamp = datetime.datetime.now().strftime('%Y%m%d') - if branch_name == 'milestone-proposed': - revno_prefix = "r" - else: - revno_prefix = "" - _run_shell_command("git fetch origin +refs/meta/*:refs/remotes/meta/*") - milestone_cmd = "git show meta/openstack/release:%s" % branch_name - milestonever = _run_shell_command(milestone_cmd) - if milestonever: - first_half = "%s~%s" % (milestonever, datestamp) - else: - first_half = datestamp - - post_version = _get_git_post_version() - # post version should look like: - # 0.1.1.4.gcc9e28a - # where the bit after the last . is the short sha, and the bit between - # the last and second to last is the revno count - (revno, sha) = post_version.split(".")[-2:] - second_half = "%s%s.%s" % (revno_prefix, revno, sha) - return ".".join((first_half, second_half)) - - -def _get_git_current_tag(): - return _run_shell_command("git tag --contains HEAD") - - -def _get_git_tag_info(): - return _run_shell_command("git describe --tags") - - -def _get_git_post_version(): - current_tag = _get_git_current_tag() - if current_tag is not None: - return current_tag - else: - tag_info = _get_git_tag_info() - if tag_info is None: - base_version = "0.0" - cmd = "git --no-pager log --oneline" - out = _run_shell_command(cmd) - revno = len(out.split("\n")) - sha = _run_shell_command("git describe --always") - else: - tag_infos = tag_info.split("-") - base_version = "-".join(tag_infos[:-2]) - (revno, sha) = tag_infos[-2:] - return "%s.%s.%s" % (base_version, revno, sha) - - def write_git_changelog(): """Write a changelog based on the git changelog.""" new_changelog = 'ChangeLog' @@ -223,26 +172,6 @@ def generate_authors(): """ -def read_versioninfo(project): - """Read the versioninfo file. If it doesn't exist, we're in a github - zipball, and there's really no way to know what version we really - are, but that should be ok, because the utility of that should be - just about nil if this code path is in use in the first place.""" - versioninfo_path = os.path.join(project, 'versioninfo') - if os.path.exists(versioninfo_path): - with open(versioninfo_path, 'r') as vinfo: - version = vinfo.read().strip() - else: - version = "0.0.0" - return version - - -def write_versioninfo(project, version): - """Write a simple file containing the version of the package.""" - with open(os.path.join(project, 'versioninfo'), 'w') as fil: - fil.write("%s\n" % version) - - def get_cmdclass(): """Return dict of commands to run from setup.py.""" @@ -272,6 +201,9 @@ def run(self): from sphinx.setup_command import BuildDoc class LocalBuildDoc(BuildDoc): + + builders = ['html', 'man'] + def generate_autoindex(self): print "**Autodocumenting from %s" % os.path.abspath(os.curdir) modules = {} @@ -307,56 +239,83 @@ def run(self): if not os.getenv('SPHINX_DEBUG'): self.generate_autoindex() - for builder in ['html', 'man']: + for builder in self.builders: self.builder = builder self.finalize_options() self.project = self.distribution.get_name() self.version = self.distribution.get_version() self.release = self.distribution.get_version() BuildDoc.run(self) + + class LocalBuildLatex(LocalBuildDoc): + builders = ['latex'] + cmdclass['build_sphinx'] = LocalBuildDoc + cmdclass['build_sphinx_latex'] = LocalBuildLatex except ImportError: pass return cmdclass -def get_git_branchname(): - for branch in _run_shell_command("git branch --color=never").split("\n"): - if branch.startswith('*'): - _branch_name = branch.split()[1].strip() - if _branch_name == "(no": - _branch_name = "no-branch" - return _branch_name - +def get_version_from_git(pre_version): + """Return a version which is equal to the tag that's on the current + revision if there is one, or tag plus number of additional revisions + if the current revision has no tag.""" -def get_pre_version(projectname, base_version): - """Return a version which is leading up to a version that will - be released in the future.""" if os.path.isdir('.git'): - current_tag = _get_git_current_tag() - if current_tag is not None: - version = current_tag + if pre_version: + try: + return _run_shell_command( + "git describe --exact-match", + throw_on_error=True).replace('-', '.') + except Exception: + sha = _run_shell_command("git log -n1 --pretty=format:%h") + describe = _run_shell_command("git describe --always") + revno = describe.rsplit("-", 2)[-2] + return "%s.a%s.g%s" % (pre_version, revno, sha) else: - branch_name = os.getenv('BRANCHNAME', - os.getenv('GERRIT_REFNAME', - get_git_branchname())) - version_suffix = _get_git_next_version_suffix(branch_name) - version = "%s~%s" % (base_version, version_suffix) - write_versioninfo(projectname, version) - return version - else: - version = read_versioninfo(projectname) - return version + return _run_shell_command( + "git describe --always").replace('-', '.') + return None -def get_post_version(projectname): - """Return a version which is equal to the tag that's on the current - revision if there is one, or tag plus number of additional revisions - if the current revision has no tag.""" +def get_version_from_pkg_info(package_name): + """Get the version from PKG-INFO file if we can.""" + try: + pkg_info_file = open('PKG-INFO', 'r') + except (IOError, OSError): + return None + try: + pkg_info = email.message_from_file(pkg_info_file) + except email.MessageError: + return None + # Check to make sure we're in our own dir + if pkg_info.get('Name', None) != package_name: + return None + return pkg_info.get('Version', None) - if os.path.isdir('.git'): - version = _get_git_post_version() - write_versioninfo(projectname, version) + +def get_version(package_name, pre_version=None): + """Get the version of the project. First, try getting it from PKG-INFO, if + it exists. If it does, that means we're in a distribution tarball or that + install has happened. Otherwise, if there is no PKG-INFO file, pull the + version from git. + + We do not support setup.py version sanity in git archive tarballs, nor do + we support packagers directly sucking our git repo into theirs. We expect + that a source tarball be made from our git repo - or that if someone wants + to make a source tarball from a fork of our repo with additional tags in it + that they understand and desire the results of doing that. + """ + version = os.environ.get("OSLO_PACKAGE_VERSION", None) + if version: + return version + version = get_version_from_pkg_info(package_name) + if version: + return version + version = get_version_from_git(pre_version) + if version: return version - return read_versioninfo(projectname) + raise Exception("Versioning for this project requires either an sdist" + " tarball, or access to an upstream git repository.") diff --git a/setup.py b/setup.py index fe0c2ebdb8..43178db72b 100644 --- a/setup.py +++ b/setup.py @@ -22,6 +22,7 @@ from openstackclient.openstack.common import setup +project = "python-openstackclient" requires = setup.parse_requirements() dependency_links = setup.parse_dependency_links() @@ -29,9 +30,10 @@ def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() + setuptools.setup( - name="python-openstackclient", - version=setup.get_post_version('openstackclient'), + name=project, + version=setup.get_version(project), description="OpenStack command-line client", long_description=read('README.rst'), url='https://github.com/openstack/python-openstackclient', From 4c91e7c56e4379fad09d35aa2f0e09c3f3819622 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 24 Jan 2013 11:39:18 -0600 Subject: [PATCH 0020/3614] Use the cliff framework's ShowOne as intended for `show image`. Also reformat code to follow newer PEP8 version. Full cleanup and PEP8 version bump coming in later patch. Change-Id: Ida3dd9a89660b9c4a91449846dd2d6e223ef187a --- openstackclient/image/v2/image.py | 33 ++++++++++++++----------------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 41520612d6..a9f5b46adc 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -19,6 +19,7 @@ from cliff import command from cliff import lister +from cliff import show from glanceclient.common import utils as gc_utils from openstackclient.common import utils @@ -35,12 +36,12 @@ def get_parser(self, prog_name): parser.add_argument( "--page-size", metavar="", - help="Number of images to request in each paginated request.", - ) + help="Number of images to request in each paginated request.") return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)" % parsed_args) + image_client = self.app.client_manager.image kwargs = {} @@ -50,11 +51,7 @@ def take_action(self, parsed_args): data = image_client.images.list(**kwargs) columns = ["ID", "Name"] - return (columns, - (utils.get_item_properties( - s, columns, - ) for s in data), - ) + return (columns, (utils.get_item_properties(s, columns) for s in data)) class SaveImage(command.Command): @@ -68,26 +65,25 @@ def get_parser(self, prog_name): parser.add_argument( "--file", metavar="", - help="Local file to save downloaded image data to. " - "If this is not specified the image data will be " - "written to stdout.", - ) + help="Local file to save downloaded image data " + "to. If this is not specified the image " + "data will be written to stdout.") parser.add_argument( "id", metavar="", - help="ID of image to describe.", - ) + help="ID of image to describe.") return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)" % parsed_args) - image_client = self.app.client_manager.image + image_client = self.app.client_manager.image data = image_client.images.data(parsed_args.id) + gc_utils.save_image(data, parsed_args.file) -class ShowImage(command.Command): +class ShowImage(show.ShowOne): """Show image command""" api = "image" @@ -98,12 +94,13 @@ def get_parser(self, prog_name): parser.add_argument( "id", metavar="", - help="ID of image to describe.", - ) + help="ID of image to describe.") return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)" % parsed_args) + image_client = self.app.client_manager.image + data = image_client.images.get(parsed_args.id) - gc_utils.print_dict(image_client.images.get(parsed_args.id)) + return zip(*sorted(data.iteritems())) From 72adfc61f228688e2514f32e7d8cfa3aa190cba8 Mon Sep 17 00:00:00 2001 From: Ilya Persky Date: Fri, 25 Jan 2013 13:49:55 +0200 Subject: [PATCH 0021/3614] Fix test runner run_tests.sh is broken run_tests.sh contains incorrect variable assignment at line 80 which makes script to fail when trying to run tests. Fixes: bug #1105000 Change-Id: Ib29c50f28d8b7e09935cd4136e709e0e0141ad2a --- run_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run_tests.sh b/run_tests.sh index ff5f83ec4e..47a5207d64 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -77,7 +77,7 @@ function run_tests { if [ $coverage -eq 1 ]; then # Do not test test_coverage_ext when gathering coverage. if [ "x$testrargs" = "x" ]; then - testrargs = "^(?!.*test_coverage_ext).*$" + testrargs="^(?!.*test_coverage_ext).*$" fi export PYTHON="${wrapper} coverage run --source novaclient --parallel-mode" fi From b25353434873c2586fee5bb3e0180cefe22136bc Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 17 Jan 2013 22:11:05 -0500 Subject: [PATCH 0022/3614] v3 identity - group and project api updated with latest comments modified entry points in setup.py added group.py (v3) added project.py (v3) fixed indentation updated to include new headers Change-Id: Ice68b6c5bacb68d2e95321d903043056a9b8e810 --- openstackclient/identity/client.py | 1 + openstackclient/identity/v3/__init__.py | 14 ++ openstackclient/identity/v3/group.py | 204 ++++++++++++++++++++ openstackclient/identity/v3/project.py | 236 ++++++++++++++++++++++++ setup.py | 12 ++ 5 files changed, 467 insertions(+) create mode 100644 openstackclient/identity/v3/__init__.py create mode 100644 openstackclient/identity/v3/group.py create mode 100644 openstackclient/identity/v3/project.py diff --git a/openstackclient/identity/client.py b/openstackclient/identity/client.py index 3d58ad7ba8..3f4295ecd5 100644 --- a/openstackclient/identity/client.py +++ b/openstackclient/identity/client.py @@ -24,6 +24,7 @@ API_NAME = 'identity' API_VERSIONS = { '2.0': 'keystoneclient.v2_0.client.Client', + '3': 'keystoneclient.v3.client.Client', } diff --git a/openstackclient/identity/v3/__init__.py b/openstackclient/identity/v3/__init__.py new file mode 100644 index 0000000000..85ac2501b8 --- /dev/null +++ b/openstackclient/identity/v3/__init__.py @@ -0,0 +1,14 @@ +# 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/group.py b/openstackclient/identity/v3/group.py new file mode 100644 index 0000000000..f783eb4fcb --- /dev/null +++ b/openstackclient/identity/v3/group.py @@ -0,0 +1,204 @@ +# 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. +# + +""" +Group action implementations +""" + +import logging + +from cliff import command +from cliff import lister +from cliff import show + +from openstackclient.common import utils + + +class CreateGroup(show.ShowOne): + """Create group command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.CreateGroup') + + def get_parser(self, prog_name): + parser = super(CreateGroup, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar='', + help='New group name', + ) + parser.add_argument( + '--description', + metavar='', + help='New group description', + ) + parser.add_argument( + '--domain', + metavar='', + help='References the domain ID or name which owns the group', + ) + + 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.domain: + domain = utils.find_resource( + identity_client.domains, parsed_args.domain).id + else: + domain = None + group = identity_client.groups.create( + parsed_args.name, + domain=domain, + description=parsed_args.description, + ) + + info = {} + info.update(group._info) + return zip(*sorted(info.iteritems())) + + +class DeleteGroup(command.Command): + """Delete group command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.DeleteGroup') + + def get_parser(self, prog_name): + parser = super(DeleteGroup, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar='', + help='Name or ID of group to delete', + ) + return parser + + def take_action(self, 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) + return + + +class ListGroup(lister.Lister): + """List group command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.ListGroup') + + def get_parser(self, prog_name): + parser = super(ListGroup, 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) + if parsed_args.long: + columns = ('ID', 'Name', 'Domain ID', 'Description') + else: + columns = ('ID', 'Name') + data = self.app.client_manager.identity.groups.list() + return (columns, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data), + ) + + +class SetGroup(command.Command): + """Set group command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.SetGroup') + + def get_parser(self, prog_name): + parser = super(SetGroup, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar='', + help='Name or ID of group to change', + ) + parser.add_argument( + '--name', + metavar='', + help='New group name', + ) + parser.add_argument( + '--domain', + metavar='', + help='New domain name or ID that will now own the group', + ) + parser.add_argument( + '--description', + metavar='', + help='New group description', + ) + return parser + + def take_action(self, 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 = {} + if parsed_args.name: + kwargs['name'] = parsed_args.name + if parsed_args.description: + kwargs['description'] = parsed_args.description + if parsed_args.domain: + domain = utils.find_resource( + identity_client.domains, parsed_args.domain).id + kwargs['domain'] = domain + + if not len(kwargs): + stdout.write("Group not updated, no arguments present") + return + identity_client.groups.update(group.id, **kwargs) + return + + +class ShowGroup(show.ShowOne): + """Show group command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.ShowGroup') + + def get_parser(self, prog_name): + parser = super(ShowGroup, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar='', + help='Name or ID of group to display', + ) + return parser + + def take_action(self, 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) + + info = {} + info.update(group._info) + return zip(*sorted(info.iteritems())) diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py new file mode 100644 index 0000000000..06022e4510 --- /dev/null +++ b/openstackclient/identity/v3/project.py @@ -0,0 +1,236 @@ +# 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. +# + +""" +Project action implementations +""" + +import logging + +from cliff import command +from cliff import lister +from cliff import show + +from openstackclient.common import utils + + +class CreateProject(show.ShowOne): + """Create project command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.CreateProject') + + def get_parser(self, prog_name): + parser = super(CreateProject, self).get_parser(prog_name) + parser.add_argument( + 'project_name', + metavar='', + help='New project name', + ) + parser.add_argument( + '--domain', + metavar='', + help='References the domain name or ID which owns the project', + ) + parser.add_argument( + '--description', + metavar='', + help='New project description', + ) + # FIXME (stevemar): need to update enabled/disabled as per Dolph's + # comments in 19999/4 + enable_group = parser.add_mutually_exclusive_group() + enable_group.add_argument( + '--enable', + dest='enabled', + action='store_true', + default=True, + help='Enable project', + ) + enable_group.add_argument( + '--disable', + dest='enabled', + action='store_false', + help='Disable project', + ) + 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.domain: + domain = utils.find_resource( + identity_client.domains, parsed_args.domain).id + else: + domain = None + project = identity_client.projects.create( + parsed_args.project_name, + domain=domain, + description=parsed_args.description, + enabled=parsed_args.enabled, + ) + + info = {} + info.update(project._info) + return zip(*sorted(info.iteritems())) + + +class DeleteProject(command.Command): + """Delete project command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.DeleteProject') + + def get_parser(self, prog_name): + parser = super(DeleteProject, self).get_parser(prog_name) + parser.add_argument( + 'project', + metavar='', + help='Name or ID of project to delete', + ) + return parser + + def take_action(self, 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) + identity_client.projects.delete(project.id) + return + + +class ListProject(lister.Lister): + """List project command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.ListProject') + + def get_parser(self, prog_name): + parser = super(ListProject, 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) + if parsed_args.long: + columns = ('ID', 'Name', 'Domain ID', 'Description', 'Enabled') + else: + columns = ('ID', 'Name') + data = self.app.client_manager.identity.projects.list() + return (columns, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data), + ) + + +class SetProject(command.Command): + """Set project command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.SetProject') + + def get_parser(self, prog_name): + parser = super(SetProject, self).get_parser(prog_name) + parser.add_argument( + 'project', + metavar='', + help='Name or ID of project to change', + ) + parser.add_argument( + '--name', + metavar='', + help='New project name', + ) + parser.add_argument( + '--domain', + metavar='', + help='New domain name or ID that will now own the project', + ) + parser.add_argument( + '--description', + metavar='', + help='New project description', + ) + enable_group = parser.add_mutually_exclusive_group() + enable_group.add_argument( + '--enable', + dest='enabled', + action='store_true', + default=True, + help='Enable project (default)', + ) + enable_group.add_argument( + '--disable', + dest='enabled', + action='store_false', + help='Disable project', + ) + return parser + + def take_action(self, 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) + kwargs = {} + if parsed_args.name: + kwargs['name'] = parsed_args.name + if parsed_args.domain: + domain = utils.find_resource( + identity_client.domains, parsed_args.domain).id + kwargs['domain'] = domain + if parsed_args.description: + kwargs['description'] = parsed_args.description + if 'enabled' in parsed_args: + kwargs['enabled'] = parsed_args.enabled + + if kwargs == {}: + stdout.write("Project not updated, no arguments present") + return + project.update(**kwargs) + return + + +class ShowProject(show.ShowOne): + """Show project command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.ShowProject') + + def get_parser(self, prog_name): + parser = super(ShowProject, self).get_parser(prog_name) + parser.add_argument( + 'project', + metavar='', + help='Name or ID of project to display', + ) + return parser + + def take_action(self, 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) + + info = {} + info.update(project._info) + return zip(*sorted(info.iteritems())) diff --git a/setup.py b/setup.py index fe0c2ebdb8..261c6130b3 100644 --- a/setup.py +++ b/setup.py @@ -112,6 +112,18 @@ def read(fname): 'list_image=openstackclient.image.v2.image:ListImage', 'show_image=openstackclient.image.v2.image:ShowImage', 'save_image=openstackclient.image.v2.image:SaveImage', + 'create_group=openstackclient.identity.v3.group:CreateGroup', + 'delete_group=openstackclient.identity.v3.group:DeleteGroup', + 'set_group=openstackclient.identity.v3.group:SetGroup', + 'show_group=openstackclient.identity.v3.group:ShowGroup', + 'list_group=openstackclient.identity.v3.group:ListGroup', + 'create_project=' + + 'openstackclient.identity.v3.project:CreateProject', + 'delete_project=' + + 'openstackclient.identity.v3.project:DeleteProject', + 'set_project=openstackclient.identity.v3.project:SetProject', + 'show_project=openstackclient.identity.v3.project:ShowProject', + 'list_project=openstackclient.identity.v3.project:ListProject', ] } ) From 4297e5781b03bad1c67cfdaa6cfdb73f6d1385de Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 24 Jan 2013 12:33:17 -0600 Subject: [PATCH 0023/3614] First pass at adding compute unit tests. Change-Id: Icf3340d457f75eec89bb0e5c9b4b953c3b81020f --- tests/compute/__init__.py | 14 +++++ tests/compute/test_compute.py | 59 +++++++++++++++++++ ...r_clientcache.py => test_clientmanager.py} | 25 ++++---- tests/test_shell.py | 9 ++- tests/utils.py | 5 +- 5 files changed, 91 insertions(+), 21 deletions(-) create mode 100644 tests/compute/__init__.py create mode 100644 tests/compute/test_compute.py rename tests/{test_clientmanager_clientcache.py => test_clientmanager.py} (59%) diff --git a/tests/compute/__init__.py b/tests/compute/__init__.py new file mode 100644 index 0000000000..ebf59b327e --- /dev/null +++ b/tests/compute/__init__.py @@ -0,0 +1,14 @@ +# 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/tests/compute/test_compute.py b/tests/compute/test_compute.py new file mode 100644 index 0000000000..bbac7123af --- /dev/null +++ b/tests/compute/test_compute.py @@ -0,0 +1,59 @@ +# 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. +# + +import mock + +from openstackclient.common import clientmanager +from openstackclient.compute import client as compute_client +from tests import utils + + +class FakeClient(object): + def __init__(self, endpoint=None, **kwargs): + self.client = mock.MagicMock() + self.servers = mock.MagicMock() + + self.client.auth_url = kwargs['auth_url'] + + +class TestCompute(utils.TestCase): + def setUp(self): + super(TestCompute, self).setUp() + + self.auth_token = "foobar" + self.auth_url = "http://0.0.0.0" + + api_version = {"compute": "2"} + + compute_client.API_VERSIONS = { + "2": "tests.compute.test_compute.FakeClient" + } + + self.cm = clientmanager.ClientManager(token=self.auth_token, + url=self.auth_url, + auth_url=self.auth_url, + api_version=api_version) + + def test_make_client(self): + test_servers = [ + ["id 1", "name 1", "status 1", "networks 1"], + ["id 2", "name 2", "status 2", "networks 2"] + ] + + self.cm.compute.servers.list.return_value = test_servers + + self.assertEqual(self.cm.compute.servers.list(), test_servers) + self.assertEqual(self.cm.compute.client.auth_token, self.auth_token) + self.assertEqual(self.cm.compute.client.auth_url, self.auth_url) diff --git a/tests/test_clientmanager_clientcache.py b/tests/test_clientmanager.py similarity index 59% rename from tests/test_clientmanager_clientcache.py rename to tests/test_clientmanager.py index 8ebf14be42..fe99c599f1 100644 --- a/tests/test_clientmanager_clientcache.py +++ b/tests/test_clientmanager.py @@ -14,23 +14,22 @@ # from openstackclient.common import clientmanager - - -def factory(inst): - return object() +from tests import utils class Container(object): + attr = clientmanager.ClientCache(lambda x: object()) - attr = clientmanager.ClientCache(factory) + def __init__(self): + pass - def init_token(self): - return +class TestClientManager(utils.TestCase): + def setUp(self): + super(TestClientManager, self).setUp() -def test_singleton(): - # Verify that the ClientCache descriptor only - # invokes the factory one time and always - # returns the same value after that. - c = Container() - assert c.attr is c.attr + def test_singleton(self): + # NOTE(dtroyer): Verify that the ClientCache descriptor only invokes + # the factory one time and always returns the same value after that. + c = Container() + self.assertEqual(c.attr, c.attr) diff --git a/tests/test_shell.py b/tests/test_shell.py index cf4fc9244f..87a7795a9b 100644 --- a/tests/test_shell.py +++ b/tests/test_shell.py @@ -13,10 +13,10 @@ # under the License. # +import fixtures import os import mock -import fixtures from openstackclient import shell as os_shell from tests import utils @@ -47,8 +47,7 @@ def make_shell(): return _shell -class ShellTest(utils.TestCase): - +class TestShell(utils.TestCase): FAKE_ENV = { 'OS_AUTH_URL': DEFAULT_AUTH_URL, 'OS_TENANT_ID': DEFAULT_TENANT_ID, @@ -60,7 +59,7 @@ class ShellTest(utils.TestCase): def setUp(self): """ Patch os.environ to avoid required auth info""" - super(ShellTest, self).setUp() + super(TestShell, self).setUp() for var in self.FAKE_ENV: self.useFixture( fixtures.EnvironmentVariable( @@ -85,7 +84,7 @@ def setUp(self): def tearDown(self): #self.auth_patch.stop() self.cmd_patch.stop() - super(ShellTest, self).tearDown() + super(TestShell, self).tearDown() def test_shell_args(self): sh = make_shell() diff --git a/tests/utils.py b/tests/utils.py index 5c4b50c623..75515fad56 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -22,14 +22,13 @@ class TestCase(testtools.TestCase): def setUp(self): super(TestCase, self).setUp() + if (os.environ.get("OS_STDOUT_NOCAPTURE") == "True" and os.environ.get("OS_STDOUT_NOCAPTURE") == "1"): stdout = self.useFixture(fixtures.StringStream("stdout")).stream self.useFixture(fixtures.MonkeyPatch("sys.stdout", stdout)) + if (os.environ.get("OS_STDERR_NOCAPTURE") == "True" and os.environ.get("OS_STDERR_NOCAPTURE") == "1"): stderr = self.useFixture(fixtures.StringStream("stderr")).stream self.useFixture(fixtures.MonkeyPatch("sys.stderr", stderr)) - - def tearDown(self): - super(TestCase, self).tearDown() From 64cd45b03d4ca740e9f28688bca9f4032fea9739 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 29 Jan 2013 12:06:31 -0600 Subject: [PATCH 0024/3614] Ensure that image and identity clients are constructed properly. Also removed tests that don't test anything. Change-Id: I0cb420248c8a1374110c014063fe029686e90216 --- tests/compute/test_compute.py | 29 +++++++------------ tests/identity/__init__.py | 14 +++++++++ tests/identity/test_identity.py | 50 ++++++++++++++++++++++++++++++++ tests/image/__init__.py | 14 +++++++++ tests/image/test_image.py | 51 +++++++++++++++++++++++++++++++++ 5 files changed, 139 insertions(+), 19 deletions(-) create mode 100644 tests/identity/__init__.py create mode 100644 tests/identity/test_identity.py create mode 100644 tests/image/__init__.py create mode 100644 tests/image/test_image.py diff --git a/tests/compute/test_compute.py b/tests/compute/test_compute.py index bbac7123af..2673f994c7 100644 --- a/tests/compute/test_compute.py +++ b/tests/compute/test_compute.py @@ -20,40 +20,31 @@ from tests import utils +AUTH_TOKEN = "foobar" +AUTH_URL = "http://0.0.0.0" + + class FakeClient(object): def __init__(self, endpoint=None, **kwargs): self.client = mock.MagicMock() - self.servers = mock.MagicMock() - - self.client.auth_url = kwargs['auth_url'] + self.client.auth_url = AUTH_URL class TestCompute(utils.TestCase): def setUp(self): super(TestCompute, self).setUp() - self.auth_token = "foobar" - self.auth_url = "http://0.0.0.0" - api_version = {"compute": "2"} compute_client.API_VERSIONS = { "2": "tests.compute.test_compute.FakeClient" } - self.cm = clientmanager.ClientManager(token=self.auth_token, - url=self.auth_url, - auth_url=self.auth_url, + self.cm = clientmanager.ClientManager(token=AUTH_TOKEN, + url=AUTH_URL, + auth_url=AUTH_URL, api_version=api_version) def test_make_client(self): - test_servers = [ - ["id 1", "name 1", "status 1", "networks 1"], - ["id 2", "name 2", "status 2", "networks 2"] - ] - - self.cm.compute.servers.list.return_value = test_servers - - self.assertEqual(self.cm.compute.servers.list(), test_servers) - self.assertEqual(self.cm.compute.client.auth_token, self.auth_token) - self.assertEqual(self.cm.compute.client.auth_url, self.auth_url) + self.assertEqual(self.cm.compute.client.auth_token, AUTH_TOKEN) + self.assertEqual(self.cm.compute.client.auth_url, AUTH_URL) diff --git a/tests/identity/__init__.py b/tests/identity/__init__.py new file mode 100644 index 0000000000..ebf59b327e --- /dev/null +++ b/tests/identity/__init__.py @@ -0,0 +1,14 @@ +# 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/tests/identity/test_identity.py b/tests/identity/test_identity.py new file mode 100644 index 0000000000..5b2cf4e334 --- /dev/null +++ b/tests/identity/test_identity.py @@ -0,0 +1,50 @@ +# 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. +# + +import mock + +from openstackclient.common import clientmanager +from openstackclient.identity import client as identity_client +from tests import utils + + +AUTH_TOKEN = "foobar" +AUTH_URL = "http://0.0.0.0" + + +class FakeClient(object): + def __init__(self, endpoint=None, **kwargs): + self.auth_token = AUTH_TOKEN + self.auth_url = AUTH_URL + + +class TestIdentity(utils.TestCase): + def setUp(self): + super(TestIdentity, self).setUp() + + api_version = {"identity": "2.0"} + + identity_client.API_VERSIONS = { + "2.0": "tests.identity.test_identity.FakeClient" + } + + self.cm = clientmanager.ClientManager(token=AUTH_TOKEN, + url=AUTH_URL, + auth_url=AUTH_URL, + api_version=api_version) + + def test_make_client(self): + self.assertEqual(self.cm.identity.auth_token, AUTH_TOKEN) + self.assertEqual(self.cm.identity.auth_url, AUTH_URL) diff --git a/tests/image/__init__.py b/tests/image/__init__.py new file mode 100644 index 0000000000..ebf59b327e --- /dev/null +++ b/tests/image/__init__.py @@ -0,0 +1,14 @@ +# 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/tests/image/test_image.py b/tests/image/test_image.py new file mode 100644 index 0000000000..60b2142943 --- /dev/null +++ b/tests/image/test_image.py @@ -0,0 +1,51 @@ +# 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. +# + +import mock + +from openstackclient.common import clientmanager +from openstackclient.image import client as image_client +from tests import utils + + +AUTH_TOKEN = "foobar" +AUTH_URL = "http://0.0.0.0" + + +class FakeClient(object): + def __init__(self, endpoint=None, **kwargs): + self.client = mock.MagicMock() + self.client.auth_token = AUTH_TOKEN + self.client.auth_url = AUTH_URL + + +class TestImage(utils.TestCase): + def setUp(self): + super(TestImage, self).setUp() + + api_version = {"image": "2"} + + image_client.API_VERSIONS = { + "2": "tests.image.test_image.FakeClient" + } + + self.cm = clientmanager.ClientManager(token=AUTH_TOKEN, + url=AUTH_URL, + auth_url=AUTH_URL, + api_version=api_version) + + def test_make_client(self): + self.assertEqual(self.cm.image.client.auth_token, AUTH_TOKEN) + self.assertEqual(self.cm.image.client.auth_url, AUTH_URL) From 37b75a10c253e3be3e34cac9906c9e0ac9556635 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 29 Jan 2013 15:12:39 -0600 Subject: [PATCH 0025/3614] Updated README to reflect latest changes. Change-Id: I996d5c0ec6e480fcbdc546bad238eee2e20504d6 --- README.rst | 60 ++++++++++++++++++++++-------------------------------- 1 file changed, 24 insertions(+), 36 deletions(-) diff --git a/README.rst b/README.rst index 1b2bdec1be..292e05258b 100644 --- a/README.rst +++ b/README.rst @@ -2,63 +2,56 @@ 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 +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 actual REST API client actions. -This is an implementation of the design goals shown in +This is an implementation of the design goals shown in http://wiki.openstack.org/UnifiedCLI. The primary goal is to provide a unified shell command structure and a common language to describe operations in OpenStack. python-openstackclient is designed to add support for API extensions via a -plugin mechanism +plugin mechanism. -For release management: +For release management:: - * https://launchpad.net/python-openstackclient + * https://launchpad.net/python-openstackclient -For blueprints and feature specifications: +For blueprints and feature specifications:: - * https://blueprints.launchpad.net/python-openstackclient + * https://blueprints.launchpad.net/python-openstackclient -For issue tracking: +For issue tracking:: - * https://bugs.launchpad.net/python-openstackclient + * https://bugs.launchpad.net/python-openstackclient Getting Started =============== We recommend using a virtualenv to install the client. This description -uses `virtualenvwrapper`_ to create the virtualenv. Install the prereqs, -then build the egg, and install the client into the virtualenv:: +uses the `install_venv.py`_ script to create the virtualenv:: - mkvirtualenv openstackclient - pip install -r tools/pip-requires - python setup.py build - easy_install dist/python_openstackclient-0.1-py2.7.egg + python tools/install_venv.py + source .venv/bin/activate + python setup.py develop -.. _virtualenvwrapper: http://www.doughellmann.com/projects/virtualenvwrapper +Unit tests can be ran simply by running:: -If you want to work in development mode, do this instead:: - - mkvirtualenv openstackclient - pip install -r tools/pip-requires - python setup.py develop - -Toxicity tests can be ran simply by running ``run_tests.sh`` + run_tests.sh The client can be called interactively by simply typing:: + openstack Alternatively command line parameters can be called non-interactively:: - openstack --help + openstack --help Configuration ============= -The cli is configured via environment variables and command-line +The CLI is configured via environment variables and command-line options as listed in http://wiki.openstack.org/UnifiedCLI/Authentication. The 'password flow' variation is most commonly used:: @@ -66,8 +59,8 @@ The 'password flow' variation is most commonly used:: export OS_AUTH_URL= export OS_TENANT_NAME= export OS_USERNAME= - export OS_PASSWORD= # (optional) - export OS_USE_KEYRING=true # (optional) + export OS_PASSWORD= # (optional) + export OS_USE_KEYRING=true # (optional) The corresponding command-line options look very similar:: @@ -102,8 +95,8 @@ are listed here:: --quiet | -q # suppress output except warnings and errors --help | -h # show a help message and exit -Building Contributor Documentation -================================== +Building Documentation +====================== This documentation is written by contributors, for contributors. @@ -113,15 +106,10 @@ The source is maintained in the ``doc/source`` folder using .. _reStructuredText: http://docutils.sourceforge.net/rst.html .. _Sphinx: http://sphinx.pocoo.org/ -* Building Automatically:: - - $ ./run_tests.sh --docs - * Building Manually:: $ export DJANGO_SETTINGS_MODULE=local.local_settings $ python doc/generate_autodoc_index.py $ sphinx-build -b html doc/source build/sphinx/html -Results are in the `build/sphinx/html` directory - +Results are in the `build/sphinx/html` directory. From 67bba28ed1d16300fb23cf3466a503d3fcf7d3b4 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 29 Jan 2013 16:12:21 -0600 Subject: [PATCH 0026/3614] Use install_venv_common.py from oslo. This syncs install_venv_common.py from oslo and reworks the tools/install_venv.py script to use the new library. Change-Id: I3426a7f51b0018e074cc6f4b1d70b38e52464a38 --- openstack-common.conf | 2 +- openstackclient/openstack/common/setup.py | 20 +- tools/install_venv.py | 231 +++------------------- tools/install_venv_common.py | 225 +++++++++++++++++++++ 4 files changed, 270 insertions(+), 208 deletions(-) create mode 100644 tools/install_venv_common.py diff --git a/openstack-common.conf b/openstack-common.conf index 6b8b86a0c7..c6947e534a 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -1,7 +1,7 @@ [DEFAULT] # The list of modules to copy from openstack-common -modules=setup,openstackkeyring +modules=install_venv_common,openstackkeyring,setup # The base module to hold the copy of openstack.common base=openstackclient diff --git a/openstackclient/openstack/common/setup.py b/openstackclient/openstack/common/setup.py index 53af7203ea..fb187fff46 100644 --- a/openstackclient/openstack/common/setup.py +++ b/openstackclient/openstack/common/setup.py @@ -258,6 +258,22 @@ class LocalBuildLatex(LocalBuildDoc): return cmdclass +def _get_revno(): + """Return the number of commits since the most recent tag. + + We use git-describe to find this out, but if there are no + tags then we fall back to counting commits since the beginning + of time. + """ + describe = _run_shell_command("git describe --always") + if "-" in describe: + return describe.rsplit("-", 2)[-2] + + # no tags found + revlist = _run_shell_command("git rev-list --abbrev-commit HEAD") + return len(revlist.splitlines()) + + def get_version_from_git(pre_version): """Return a version which is equal to the tag that's on the current revision if there is one, or tag plus number of additional revisions @@ -271,9 +287,7 @@ def get_version_from_git(pre_version): throw_on_error=True).replace('-', '.') except Exception: sha = _run_shell_command("git log -n1 --pretty=format:%h") - describe = _run_shell_command("git describe --always") - revno = describe.rsplit("-", 2)[-2] - return "%s.a%s.g%s" % (pre_version, revno, sha) + return "%s.a%s.g%s" % (pre_version, _get_revno(), sha) else: return _run_shell_command( "git describe --always").replace('-', '.') diff --git a/tools/install_venv.py b/tools/install_venv.py index 0957d26b17..d247c02376 100644 --- a/tools/install_venv.py +++ b/tools/install_venv.py @@ -1,203 +1,26 @@ -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. +# Copyright 2013 OpenStack, LLC. # -# Copyright 2010 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 # -# 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 # -# 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. # -# 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. """ Installation script for python-openstackclient's development virtualenv """ -import optparse import os -import subprocess import sys -import platform - -ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) -VENV = os.path.join(ROOT, '.venv') -PIP_REQUIRES = os.path.join(ROOT, 'tools', 'pip-requires') -TEST_REQUIRES = os.path.join(ROOT, 'tools', 'test-requires') -PY_VERSION = "python%s.%s" % (sys.version_info[0], sys.version_info[1]) - - -def die(message, *args): - print >> sys.stderr, message % args - sys.exit(1) - - -def check_python_version(): - if sys.version_info < (2, 6): - die("Need Python Version >= 2.6") - - -def run_command_with_code(cmd, redirect_output=True, check_exit_code=True): - """ - Runs a command in an out-of-process shell, returning the - output of that command. Working directory is ROOT. - """ - if redirect_output: - stdout = subprocess.PIPE - else: - stdout = None - - proc = subprocess.Popen(cmd, cwd=ROOT, stdout=stdout) - output = proc.communicate()[0] - if check_exit_code and proc.returncode != 0: - die('Command "%s" failed.\n%s', ' '.join(cmd), output) - return (output, proc.returncode) - - -def run_command(cmd, redirect_output=True, check_exit_code=True): - return run_command_with_code(cmd, redirect_output, check_exit_code)[0] - - -class Distro(object): - - def check_cmd(self, cmd): - return bool(run_command(['which', cmd], check_exit_code=False).strip()) - - def install_virtualenv(self): - if self.check_cmd('virtualenv'): - return - - if self.check_cmd('easy_install'): - print 'Installing virtualenv via easy_install...', - if run_command(['easy_install', 'virtualenv']): - print 'Succeeded' - return - else: - print 'Failed' - - die('ERROR: virtualenv not found.\n\nDevelopment' - ' requires virtualenv, please install it using your' - ' favorite package management tool') - - def post_process(self): - """Any distribution-specific post-processing gets done here. - - In particular, this is useful for applying patches to code inside - the venv.""" - pass - - -class Debian(Distro): - """This covers all Debian-based distributions.""" - - def check_pkg(self, pkg): - return run_command_with_code(['dpkg', '-l', pkg], - check_exit_code=False)[1] == 0 - - def apt_install(self, pkg, **kwargs): - run_command(['sudo', 'apt-get', 'install', '-y', pkg], **kwargs) - - def apply_patch(self, originalfile, patchfile): - run_command(['patch', originalfile, patchfile]) - - def install_virtualenv(self): - if self.check_cmd('virtualenv'): - return - - if not self.check_pkg('python-virtualenv'): - self.apt_install('python-virtualenv', check_exit_code=False) - - super(Debian, self).install_virtualenv() - - -class Fedora(Distro): - """This covers all Fedora-based distributions. - - Includes: Fedora, RHEL, CentOS, Scientific Linux""" - - def check_pkg(self, pkg): - return run_command_with_code(['rpm', '-q', pkg], - check_exit_code=False)[1] == 0 - - def yum_install(self, pkg, **kwargs): - run_command(['sudo', 'yum', 'install', '-y', pkg], **kwargs) - - def apply_patch(self, originalfile, patchfile): - run_command(['patch', originalfile, patchfile]) - - def install_virtualenv(self): - if self.check_cmd('virtualenv'): - return - - if not self.check_pkg('python-virtualenv'): - self.yum_install('python-virtualenv', check_exit_code=False) - - super(Fedora, self).install_virtualenv() - - -def get_distro(): - if os.path.exists('/etc/fedora-release') or \ - os.path.exists('/etc/redhat-release'): - return Fedora() - elif os.path.exists('/etc/debian_version'): - return Debian() - else: - return Distro() - - -def check_dependencies(): - get_distro().install_virtualenv() - - -def create_virtualenv(venv=VENV, no_site_packages=True): - """Creates the virtual environment and installs PIP only into the - virtual environment - """ - print 'Creating venv...', - if no_site_packages: - run_command(['virtualenv', '-q', '--no-site-packages', VENV]) - else: - run_command(['virtualenv', '-q', VENV]) - print 'done.' - print 'Installing pip in virtualenv...', - if not run_command(['tools/with_venv.sh', 'easy_install', - 'pip>1.0']).strip(): - die("Failed to install pip.") - print 'done.' - - -def pip_install(*args): - run_command(['tools/with_venv.sh', - 'pip', 'install', '--upgrade'] + list(args), - redirect_output=False) - - -def install_dependencies(venv=VENV): - print 'Installing dependencies with pip (this can take a while)...' - - # First things first, make sure our venv has the latest pip and distribute. - pip_install('pip') - pip_install('distribute') - - pip_install('-r', PIP_REQUIRES) - pip_install('-r', TEST_REQUIRES) - - # Tell the virtual env how to "import openstackclient" - pthfile = os.path.join(venv, "lib", PY_VERSION, "site-packages", - "openstackclient.pth") - f = open(pthfile, 'w') - f.write("%s\n" % ROOT) - - -def post_process(): - get_distro().post_process() +import install_venv_common as install_venv def print_help(): @@ -222,23 +45,23 @@ def print_help(): print help -def parse_args(): - """Parse command-line arguments""" - parser = optparse.OptionParser() - parser.add_option("-n", "--no-site-packages", dest="no_site_packages", - default=False, action="store_true", - help="Do not inherit packages from global Python install") - return parser.parse_args() - - def main(argv): - (options, args) = parse_args() - check_python_version() - check_dependencies() - create_virtualenv(no_site_packages=options.no_site_packages) - install_dependencies() - post_process() + root = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + venv = os.path.join(root, ".venv") + pip_requires = os.path.join(root, "tools", "pip-requires") + test_requires = os.path.join(root, "tools", "test-requires") + py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1]) + project = "python-openstackclient" + install = install_venv.InstallVenv(root, venv, pip_requires, test_requires, + py_version, project) + options = install.parse_args(argv) + install.check_python_version() + install.check_dependencies() + install.create_virtualenv(no_site_packages=options.no_site_packages) + install.install_dependencies() + install.post_process() print_help() -if __name__ == '__main__': + +if __name__ == "__main__": main(sys.argv) diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py new file mode 100644 index 0000000000..a92e4ab818 --- /dev/null +++ b/tools/install_venv_common.py @@ -0,0 +1,225 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 OpenStack, LLC +# Copyright 2013 IBM Corp. +# +# 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. + +"""Provides methods needed by installation script for OpenStack development +virtual environments. + +Synced in from openstack-common +""" + +import os +import subprocess +import sys + +from openstackclient.openstack.common import cfg + + +class InstallVenv(object): + + def __init__(self, root, venv, pip_requires, test_requires, py_version, + project): + self.root = root + self.venv = venv + self.pip_requires = pip_requires + self.test_requires = test_requires + self.py_version = py_version + self.project = project + + def die(self, message, *args): + print >> sys.stderr, message % args + sys.exit(1) + + def check_python_version(self): + if sys.version_info < (2, 6): + self.die("Need Python Version >= 2.6") + + def run_command_with_code(self, cmd, redirect_output=True, + check_exit_code=True): + """Runs a command in an out-of-process shell. + + Returns the output of that command. Working directory is ROOT. + """ + if redirect_output: + stdout = subprocess.PIPE + else: + stdout = None + + proc = subprocess.Popen(cmd, cwd=self.root, stdout=stdout) + output = proc.communicate()[0] + if check_exit_code and proc.returncode != 0: + self.die('Command "%s" failed.\n%s', ' '.join(cmd), output) + return (output, proc.returncode) + + def run_command(self, cmd, redirect_output=True, check_exit_code=True): + return self.run_command_with_code(cmd, redirect_output, + check_exit_code)[0] + + def get_distro(self): + if (os.path.exists('/etc/fedora-release') or + os.path.exists('/etc/redhat-release')): + return Fedora(self.root, self.venv, self.pip_requires, + self.test_requires, self.py_version, self.project) + else: + return Distro(self.root, self.venv, self.pip_requires, + self.test_requires, self.py_version, self.project) + + def check_dependencies(self): + self.get_distro().install_virtualenv() + + def create_virtualenv(self, no_site_packages=True): + """Creates the virtual environment and installs PIP. + + Creates the virtual environment and installs PIP only into the + virtual environment. + """ + if not os.path.isdir(self.venv): + print 'Creating venv...', + if no_site_packages: + self.run_command(['virtualenv', '-q', '--no-site-packages', + self.venv]) + else: + self.run_command(['virtualenv', '-q', self.venv]) + print 'done.' + print 'Installing pip in virtualenv...', + if not self.run_command(['tools/with_venv.sh', 'easy_install', + 'pip>1.0']).strip(): + self.die("Failed to install pip.") + print 'done.' + else: + print "venv already exists..." + pass + + def pip_install(self, *args): + self.run_command(['tools/with_venv.sh', + 'pip', 'install', '--upgrade'] + list(args), + redirect_output=False) + + def install_dependencies(self): + print 'Installing dependencies with pip (this can take a while)...' + + # First things first, make sure our venv has the latest pip and + # distribute. + # NOTE: we keep pip at version 1.1 since the most recent version causes + # the .venv creation to fail. See: + # https://bugs.launchpad.net/nova/+bug/1047120 + self.pip_install('pip==1.1') + self.pip_install('distribute') + + # Install greenlet by hand - just listing it in the requires file does + # not + # get it installed in the right order + self.pip_install('greenlet') + + self.pip_install('-r', self.pip_requires) + self.pip_install('-r', self.test_requires) + + def post_process(self): + self.get_distro().post_process() + + def parse_args(self, argv): + """Parses command-line arguments.""" + cli_opts = [ + cfg.BoolOpt('no-site-packages', + default=False, + short='n', + help="Do not inherit packages from global Python" + "install"), + ] + CLI = cfg.ConfigOpts() + CLI.register_cli_opts(cli_opts) + CLI(argv[1:]) + return CLI + + +class Distro(InstallVenv): + + def check_cmd(self, cmd): + return bool(self.run_command(['which', cmd], + check_exit_code=False).strip()) + + def install_virtualenv(self): + if self.check_cmd('virtualenv'): + return + + if self.check_cmd('easy_install'): + print 'Installing virtualenv via easy_install...', + if self.run_command(['easy_install', 'virtualenv']): + print 'Succeeded' + return + else: + print 'Failed' + + self.die('ERROR: virtualenv not found.\n\n%s development' + ' requires virtualenv, please install it using your' + ' favorite package management tool' % self.project) + + def post_process(self): + """Any distribution-specific post-processing gets done here. + + In particular, this is useful for applying patches to code inside + the venv. + """ + pass + + +class Fedora(Distro): + """This covers all Fedora-based distributions. + + Includes: Fedora, RHEL, CentOS, Scientific Linux + """ + + def check_pkg(self, pkg): + return self.run_command_with_code(['rpm', '-q', pkg], + check_exit_code=False)[1] == 0 + + def yum_install(self, pkg, **kwargs): + print "Attempting to install '%s' via yum" % pkg + self.run_command(['sudo', 'yum', 'install', '-y', pkg], **kwargs) + + def apply_patch(self, originalfile, patchfile): + self.run_command(['patch', originalfile, patchfile]) + + def install_virtualenv(self): + if self.check_cmd('virtualenv'): + return + + if not self.check_pkg('python-virtualenv'): + self.yum_install('python-virtualenv', check_exit_code=False) + + super(Fedora, self).install_virtualenv() + + def post_process(self): + """Workaround for a bug in eventlet. + + This currently affects RHEL6.1, but the fix can safely be + applied to all RHEL and Fedora distributions. + + This can be removed when the fix is applied upstream. + + Nova: https://bugs.launchpad.net/nova/+bug/884915 + Upstream: https://bitbucket.org/which_linden/eventlet/issue/89 + """ + + # Install "patch" program if it's not there + if not self.check_pkg('patch'): + self.yum_install('patch') + + # Apply the eventlet patch + self.apply_patch(os.path.join(self.venv, 'lib', self.py_version, + 'site-packages', + 'eventlet/green/subprocess.py'), + 'contrib/redhat-eventlet.patch') From 282649ca40a6d91a253d4191380be10db43375f2 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 29 Jan 2013 16:44:58 -0600 Subject: [PATCH 0027/3614] Remove old/unsupported options from run_tests help message. Change-Id: Ie0d796ecbd3c98b496a225c2a2c128db94a1831e --- run_tests.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/run_tests.sh b/run_tests.sh index 47a5207d64..5a5ff701db 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -9,13 +9,11 @@ function usage { echo " -V, --virtual-env Always use virtualenv. Install automatically if not present" echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment" echo " -s, --no-site-packages Isolate the virtualenv from the global Python environment" - echo " -x, --stop Stop running tests after the first error or failure." echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added." echo " -p, --pep8 Just run pep8" echo " -P, --no-pep8 Don't run pep8" echo " -c, --coverage Generate coverage report" echo " -h, --help Print this usage message" - echo " --hide-elapsed Don't print the elapsed time for each test along with slow test list" echo "" echo "Note: with no options specified, the script will try to run the tests in a virtual environment," echo " If no virtualenv is found, the script will ask if you would like to create one. If you " @@ -177,6 +175,6 @@ fi if [ $coverage -eq 1 ]; then echo "Generating coverage report in covhtml/" - ${wrapper} cverage combine + ${wrapper} coverage combine ${wrapper} coverage html --include='novaclient/*' --omit='novaclient/openstack/common/*' -d covhtml -i fi From 17a87b9c0270053fc3ecfaade6606d135894e51c Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Wed, 30 Jan 2013 13:56:12 -0600 Subject: [PATCH 0028/3614] Clean up test_shell so that the tests are captured though the test framework. Also makes a small modification to gitignore. Change-Id: Iefbfbcfd35270b06ed65edb34708fa9b4d5bf563 --- .gitignore | 1 + run_tests.sh | 4 +- tests/test_shell.py | 495 +++++++++++++++++++------------------------- 3 files changed, 219 insertions(+), 281 deletions(-) diff --git a/.gitignore b/.gitignore index 8b413ce11d..d6f1cdbc63 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ *.swo *.swp *~ +.coverage .openstackclient-venv .testrepository .tox diff --git a/run_tests.sh b/run_tests.sh index 5a5ff701db..71b336e58a 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -77,7 +77,7 @@ function run_tests { if [ "x$testrargs" = "x" ]; then testrargs="^(?!.*test_coverage_ext).*$" fi - export PYTHON="${wrapper} coverage run --source novaclient --parallel-mode" + export PYTHON="${wrapper} coverage run --source openstackclient --parallel-mode" fi # Just run the test suites in current environment set +e @@ -176,5 +176,5 @@ fi if [ $coverage -eq 1 ]; then echo "Generating coverage report in covhtml/" ${wrapper} coverage combine - ${wrapper} coverage html --include='novaclient/*' --omit='novaclient/openstack/common/*' -d covhtml -i + ${wrapper} coverage html --include='openstackclient/*' --omit='openstackclient/openstack/common/*' -d covhtml -i fi diff --git a/tests/test_shell.py b/tests/test_shell.py index 87a7795a9b..ac634c3235 100644 --- a/tests/test_shell.py +++ b/tests/test_shell.py @@ -13,313 +13,250 @@ # under the License. # -import fixtures import os import mock -from openstackclient import shell as os_shell +from openstackclient import shell from tests import utils -DEFAULT_AUTH_URL = 'http://127.0.0.1:5000/v2.0/' -DEFAULT_TENANT_ID = 'xxxx-yyyy-zzzz' -DEFAULT_TENANT_NAME = 'joe_tenant' -DEFAULT_USERNAME = 'joe_user' -DEFAULT_PASSWORD = 'password' -DEFAULT_REGION_NAME = 'ZZ9_Plural_Z_Alpha' -DEFAULT_TOKEN = 'xyzpdq' -DEFAULT_SERVICE_URL = 'http://127.0.0.1:8771/v3.0/' +DEFAULT_USERNAME = "username" +DEFAULT_PASSWORD = "password" +DEFAULT_TENANT_ID = "xxxx-yyyy-zzzz" +DEFAULT_TENANT_NAME = "tenant" +DEFAULT_TOKEN = "token" +DEFAULT_REGION_NAME = "ZZ9_Plural_Z_Alpha" +DEFAULT_AUTH_URL = "http://127.0.0.1:5000/v2.0/" +DEFAULT_SERVICE_URL = "http://127.0.0.1:8771/v3.0/" -DEFAULT_COMPUTE_API_VERSION = '42' -DEFAULT_IDENTITY_API_VERSION = '42.0' -DEFAULT_IMAGE_API_VERSION = 'v42' +DEFAULT_COMPUTE_API_VERSION = "2" +DEFAULT_IDENTITY_API_VERSION = "2.0" +DEFAULT_IMAGE_API_VERSION = "v2" -# These values are hard-coded in the client libs -LIB_COMPUTE_API_VERSION = '2' -LIB_IDENTITY_API_VERSION = '2.0' -LIB_IMAGE_API_VERSION = '1.0' +LIB_COMPUTE_API_VERSION = "2" +LIB_IDENTITY_API_VERSION = "2.0" +LIB_IMAGE_API_VERSION = "1.0" def make_shell(): - """Create a new command shell and mock out some bits""" - _shell = os_shell.OpenStackShell() + """Create a new command shell and mock out some bits.""" + _shell = shell.OpenStackShell() _shell.command_manager = mock.Mock() + return _shell -class TestShell(utils.TestCase): - FAKE_ENV = { - 'OS_AUTH_URL': DEFAULT_AUTH_URL, - 'OS_TENANT_ID': DEFAULT_TENANT_ID, - 'OS_TENANT_NAME': DEFAULT_TENANT_NAME, - 'OS_USERNAME': DEFAULT_USERNAME, - 'OS_PASSWORD': DEFAULT_PASSWORD, - 'OS_REGION_NAME': DEFAULT_REGION_NAME, - } +def fake_execute(shell, cmd): + """Pretend to execute shell commands.""" + return shell.run(cmd.split()) + +class TestShell(utils.TestCase): def setUp(self): - """ Patch os.environ to avoid required auth info""" super(TestShell, self).setUp() - for var in self.FAKE_ENV: - self.useFixture( - fixtures.EnvironmentVariable( - var, - self.FAKE_ENV[var] - ) - ) - - # Make a fake shell object, a helping wrapper to call it, and a quick - # way of asserting that certain API calls were made. - global shell, _shell, assert_called, assert_called_anytime - shell = lambda sh, cmd: sh.run(cmd.split()) - - # Patch out some common methods - #self.auth_patch = mock.patch( - # 'openstackclient.shell.OpenStackShell.authenticate_user') - #self.auth_save = self.auth_patch.start() - self.cmd_patch = mock.patch( - 'openstackclient.shell.OpenStackShell.run_subcommand') + patch = "openstackclient.shell.OpenStackShell.run_subcommand" + self.cmd_patch = mock.patch(patch) self.cmd_save = self.cmd_patch.start() + self.app = mock.Mock("Test Shell") def tearDown(self): - #self.auth_patch.stop() - self.cmd_patch.stop() super(TestShell, self).tearDown() + self.cmd_patch.stop() - def test_shell_args(self): - sh = make_shell() - initapp_mock = mock.Mock('default environment') - with mock.patch( - 'openstackclient.shell.OpenStackShell.initialize_app', - initapp_mock): - shell(sh, 'list user') - initapp_mock.assert_called_with((['list', 'user'])) - - def test_shell_auth_password_flow(self): - - def test_auth(desc, cmd_options, default_args): - initapp_mock = mock.Mock(desc) - with mock.patch( - 'openstackclient.shell.OpenStackShell.initialize_app', - initapp_mock): - cmd = cmd_options + ' list tenant' - shell(sh, cmd) - initapp_mock.assert_called_with(['list', 'tenant']) - assert sh.options.os_auth_url == default_args['auth_url'] - assert sh.options.os_tenant_id == \ - default_args['tenant_id'] - assert sh.options.os_tenant_name == \ - default_args['tenant_name'] - assert sh.options.os_username == default_args['username'] - assert sh.options.os_password == default_args['password'] - assert sh.options.os_region_name == \ - default_args['region_name'] - - # Test the default - sh = make_shell() - test_auth('default environment', '', - {'auth_url': DEFAULT_AUTH_URL, - 'tenant_id': DEFAULT_TENANT_ID, - 'tenant_name': DEFAULT_TENANT_NAME, - 'username': DEFAULT_USERNAME, - 'password': DEFAULT_PASSWORD, - 'region_name': DEFAULT_REGION_NAME, - }) - - # Test an empty environment - save_env, os.environ = os.environ, {} - sh = make_shell() - test_auth('empty environment', '', - {'auth_url': '', - 'tenant_id': '', - 'tenant_name': '', - 'username': '', - 'password': '', - 'region_name': '', - }) - - # Test command-line arguments - sh = make_shell() - test_auth('cli arguments', '--os-auth-url ' + DEFAULT_AUTH_URL, - {'auth_url': DEFAULT_AUTH_URL, - 'tenant_id': '', - 'tenant_name': '', - 'username': '', - 'password': '', - 'region_name': '', - }) - - sh = make_shell() - test_auth('cli arguments', '--os-tenant-id ' + DEFAULT_TENANT_ID, - {'auth_url': '', - 'tenant_id': DEFAULT_TENANT_ID, - 'tenant_name': '', - 'username': '', - 'password': '', - 'region_name': '', - }) - - sh = make_shell() - test_auth('cli arguments', '--os-tenant-name ' + DEFAULT_TENANT_NAME, - {'auth_url': '', - 'tenant_id': '', - 'tenant_name': DEFAULT_TENANT_NAME, - 'username': '', - 'password': '', - 'region_name': '', - }) - - sh = make_shell() - test_auth('cli arguments', '--os-username ' + DEFAULT_USERNAME, - {'auth_url': '', - 'tenant_id': '', - 'tenant_name': '', - 'username': DEFAULT_USERNAME, - 'password': '', - 'region_name': '', - }) - - sh = make_shell() - test_auth('cli arguments', '--os-password ' + DEFAULT_PASSWORD, - {'auth_url': '', - 'tenant_id': '', - 'tenant_name': '', - 'username': '', - 'password': DEFAULT_PASSWORD, - 'region_name': '', - }) - - sh = make_shell() - test_auth('cli arguments', '--os-region-name ' + DEFAULT_REGION_NAME, - {'auth_url': '', - 'tenant_id': '', - 'tenant_name': '', - 'username': '', - 'password': '', - 'region_name': DEFAULT_REGION_NAME, - }) - - # Restore environment - os.environ = save_env - - def test_shell_auth_token_flow(self): - - def test_auth(desc, cmd_options, default_args): - initapp_mock = mock.Mock(desc) - with mock.patch( - 'openstackclient.shell.OpenStackShell.initialize_app', - initapp_mock): - cmd = cmd_options + ' list role' - shell(sh, cmd) - initapp_mock.assert_called_with(['list', 'role']) - assert sh.options.os_token == default_args['os_token'] - assert sh.options.os_url == default_args['os_url'] - - token_env = { - 'OS_TOKEN': DEFAULT_TOKEN, - 'OS_URL': DEFAULT_SERVICE_URL, + def _assert_password_auth(self, cmd_options, default_args): + with mock.patch("openstackclient.shell.OpenStackShell.initialize_app", + self.app): + _shell, _cmd = make_shell(), cmd_options + " list tenant" + fake_execute(_shell, _cmd) + + self.app.assert_called_with(["list", "tenant"]) + self.assertEqual(_shell.options.os_auth_url, + default_args["auth_url"]) + self.assertEqual(_shell.options.os_tenant_id, + default_args["tenant_id"]) + self.assertEqual(_shell.options.os_tenant_name, + default_args["tenant_name"]) + self.assertEqual(_shell.options.os_username, + default_args["username"]) + self.assertEqual(_shell.options.os_password, + default_args["password"]) + self.assertEqual(_shell.options.os_region_name, + default_args["region_name"]) + + def _assert_token_auth(self, cmd_options, default_args): + with mock.patch("openstackclient.shell.OpenStackShell.initialize_app", + self.app): + _shell, _cmd = make_shell(), cmd_options + " list role" + fake_execute(_shell, _cmd) + + self.app.assert_called_with(["list", "role"]) + self.assertEqual(_shell.options.os_token, default_args["os_token"]) + self.assertEqual(_shell.options.os_url, default_args["os_url"]) + + def _assert_cli(self, cmd_options, default_args): + with mock.patch("openstackclient.shell.OpenStackShell.initialize_app", + self.app): + _shell, _cmd = make_shell(), cmd_options + " list server" + fake_execute(_shell, _cmd) + + self.app.assert_called_with(["list", "server"]) + self.assertEqual(_shell.options.os_compute_api_version, + default_args["compute_api_version"]) + self.assertEqual(_shell.options.os_identity_api_version, + default_args["identity_api_version"]) + self.assertEqual(_shell.options.os_image_api_version, + default_args["image_api_version"]) + + +class TestShellPasswordAuth(TestShell): + def setUp(self): + super(TestShellPasswordAuth, self).setUp() + self.orig_env, os.environ = os.environ, {} + + def tearDown(self): + super(TestShellPasswordAuth, self).tearDown() + os.environ = self.orig_env + + def test_only_url_flow(self): + flag = "--os-auth-url " + DEFAULT_AUTH_URL + kwargs = { + "auth_url": DEFAULT_AUTH_URL, + "tenant_id": "", + "tenant_name": "", + "username": "", + "password": "", + "region_name": "" + } + self._assert_password_auth(flag, kwargs) + + def test_only_tenant_id_flow(self): + flag = "--os-tenant-id " + DEFAULT_TENANT_ID + kwargs = { + "auth_url": "", + "tenant_id": DEFAULT_TENANT_ID, + "tenant_name": "", + "username": "", + "password": "", + "region_name": "" + } + self._assert_password_auth(flag, kwargs) + + def test_only_tenant_name_flow(self): + flag = "--os-tenant-name " + DEFAULT_TENANT_NAME + kwargs = { + "auth_url": "", + "tenant_id": "", + "tenant_name": DEFAULT_TENANT_NAME, + "username": "", + "password": "", + "region_name": "" + } + self._assert_password_auth(flag, kwargs) + + def test_only_username_flow(self): + flag = "--os-username " + DEFAULT_USERNAME + kwargs = { + "auth_url": "", + "tenant_id": "", + "tenant_name": "", + "username": DEFAULT_USERNAME, + "password": "", + "region_name": "" + } + self._assert_password_auth(flag, kwargs) + + def test_only_password_flow(self): + flag = "--os-password " + DEFAULT_PASSWORD + kwargs = { + "auth_url": "", + "tenant_id": "", + "tenant_name": "", + "username": "", + "password": DEFAULT_PASSWORD, + "region_name": "" + } + self._assert_password_auth(flag, kwargs) + + def test_only_region_name_flow(self): + flag = "--os-region-name " + DEFAULT_REGION_NAME + kwargs = { + "auth_url": "", + "tenant_id": "", + "tenant_name": "", + "username": "", + "password": "", + "region_name": DEFAULT_REGION_NAME } - save_env, os.environ = os.environ, token_env.copy() + self._assert_password_auth(flag, kwargs) - # Test the default - sh = make_shell() - test_auth('default environment', '', - {'os_token': DEFAULT_TOKEN, - 'os_url': DEFAULT_SERVICE_URL, - }) - # Test an empty environment +class TestShellTokenAuth(TestShell): + def setUp(self): + super(TestShellTokenAuth, self).setUp() + env = { + "OS_TOKEN": DEFAULT_TOKEN, + "OS_URL": DEFAULT_SERVICE_URL, + } + self.orig_env, os.environ = os.environ, env.copy() + + def tearDown(self): + super(TestShellTokenAuth, self).tearDown() + os.environ = self.orig_env + + def test_default_auth(self): + flag = "" + kwargs = { + "os_token": DEFAULT_TOKEN, + "os_url": DEFAULT_SERVICE_URL + } + self._assert_token_auth(flag, kwargs) + + def test_empty_auth(self): os.environ = {} - sh = make_shell() - test_auth('empty environment', '', - {'os_token': '', - 'os_url': '', - }) - - # Test command-line arguments - sh = make_shell() - test_auth('cli arguments', '--os-token ' + DEFAULT_TOKEN, - {'os_token': DEFAULT_TOKEN, - 'os_url': '', - }) - - sh = make_shell() - test_auth('cli arguments', '--os-url ' + DEFAULT_SERVICE_URL, - {'os_token': '', - 'os_url': DEFAULT_SERVICE_URL, - }) - - # Restore environment - os.environ = save_env - - def test_shell_cli_options(self): - - def test_vars(desc, cmd_options, default_args): - initapp_mock = mock.Mock(desc) - with mock.patch( - 'openstackclient.shell.OpenStackShell.initialize_app', - initapp_mock): - cmd = cmd_options + ' list server' - shell(sh, cmd) - initapp_mock.assert_called_with(['list', 'server']) - print "options: %s" % sh.options - print "args: %s" % default_args - assert sh.options.os_compute_api_version == \ - default_args['compute_api_version'] - assert sh.options.os_identity_api_version == \ - default_args['identity_api_version'] - assert sh.options.os_image_api_version == \ - default_args['image_api_version'] - - option_env = { - 'OS_COMPUTE_API_VERSION': DEFAULT_COMPUTE_API_VERSION, - 'OS_IDENTITY_API_VERSION': DEFAULT_IDENTITY_API_VERSION, - 'OS_IMAGE_API_VERSION': DEFAULT_IMAGE_API_VERSION, + flag = "" + kwargs = { + "os_token": "", + "os_url": "" } - save_env, os.environ = os.environ, option_env.copy() + self._assert_token_auth(flag, kwargs) - # Test the default - sh = make_shell() - test_vars('default environment', '', - {'compute_api_version': DEFAULT_COMPUTE_API_VERSION, - 'identity_api_version': DEFAULT_IDENTITY_API_VERSION, - 'image_api_version': DEFAULT_IMAGE_API_VERSION, - }) - # Test an empty environment +class TestShellCli(TestShell): + def setUp(self): + super(TestShellCli, self).setUp() + env = { + "OS_COMPUTE_API_VERSION": DEFAULT_COMPUTE_API_VERSION, + "OS_IDENTITY_API_VERSION": DEFAULT_IDENTITY_API_VERSION, + "OS_IMAGE_API_VERSION": DEFAULT_IMAGE_API_VERSION, + } + self.orig_env, os.environ = os.environ, env.copy() + + def tearDown(self): + super(TestShellCli, self).tearDown() + os.environ = self.orig_env + + def test_shell_args(self): + _shell = make_shell() + with mock.patch("openstackclient.shell.OpenStackShell.initialize_app", + self.app): + fake_execute(_shell, "list user") + self.app.assert_called_with(["list", "user"]) + + def test_default_env(self): + flag = "" + kwargs = { + "compute_api_version": DEFAULT_COMPUTE_API_VERSION, + "identity_api_version": DEFAULT_IDENTITY_API_VERSION, + "image_api_version": DEFAULT_IMAGE_API_VERSION + } + self._assert_cli(flag, kwargs) + + def test_empty_env(self): os.environ = {} - sh = make_shell() - # This should fall back to the defaults hard-coded in the client libs - test_vars('empty environment', '', - {'compute_api_version': LIB_COMPUTE_API_VERSION, - 'identity_api_version': LIB_IDENTITY_API_VERSION, - 'image_api_version': LIB_IMAGE_API_VERSION, - }) - - # Test command-line arguments - sh = make_shell() - test_vars('cli arguments', - '--os-compute-api-version ' + DEFAULT_COMPUTE_API_VERSION, - {'compute_api_version': DEFAULT_COMPUTE_API_VERSION, - 'identity_api_version': LIB_IDENTITY_API_VERSION, - 'image_api_version': LIB_IMAGE_API_VERSION, - }) - - sh = make_shell() - test_vars('cli arguments', - '--os-identity-api-version ' + DEFAULT_IDENTITY_API_VERSION, - {'compute_api_version': LIB_COMPUTE_API_VERSION, - 'identity_api_version': DEFAULT_IDENTITY_API_VERSION, - 'image_api_version': LIB_IMAGE_API_VERSION, - }) - - sh = make_shell() - test_vars('cli arguments', - '--os-image-api-version ' + DEFAULT_IMAGE_API_VERSION, - {'compute_api_version': LIB_COMPUTE_API_VERSION, - 'identity_api_version': LIB_IDENTITY_API_VERSION, - 'image_api_version': DEFAULT_IMAGE_API_VERSION, - }) - - # Restore environment - os.environ = save_env + flag = "" + kwargs = { + "compute_api_version": LIB_COMPUTE_API_VERSION, + "identity_api_version": LIB_IDENTITY_API_VERSION, + "image_api_version": LIB_IMAGE_API_VERSION + } + self._assert_cli(flag, kwargs) From 6a785bc6e00b9a585b9058bc7b72d8c38b747f0f Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 30 Jan 2013 21:36:43 -0600 Subject: [PATCH 0029/3614] Copy cfg and iniparser from oslo-incubator https://review.openstack.org/20753 introduced tools/install_venv_common.py but not it's dependencies openstack.common.{cfg,iniparser} Change-Id: I270a1d8f6fd8f93988a5e2ccbc446adda0a6cd81 --- openstack-common.conf | 2 +- openstackclient/openstack/common/cfg.py | 1731 +++++++++++++++++ openstackclient/openstack/common/iniparser.py | 130 ++ 3 files changed, 1862 insertions(+), 1 deletion(-) create mode 100644 openstackclient/openstack/common/cfg.py create mode 100644 openstackclient/openstack/common/iniparser.py diff --git a/openstack-common.conf b/openstack-common.conf index c6947e534a..b99037bb3a 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -1,7 +1,7 @@ [DEFAULT] # The list of modules to copy from openstack-common -modules=install_venv_common,openstackkeyring,setup +modules=cfg,iniparser,install_venv_common,openstackkeyring,setup # The base module to hold the copy of openstack.common base=openstackclient diff --git a/openstackclient/openstack/common/cfg.py b/openstackclient/openstack/common/cfg.py new file mode 100644 index 0000000000..cba3145a59 --- /dev/null +++ b/openstackclient/openstack/common/cfg.py @@ -0,0 +1,1731 @@ +# 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/iniparser.py b/openstackclient/openstack/common/iniparser.py new file mode 100644 index 0000000000..9bf399f0c7 --- /dev/null +++ b/openstackclient/openstack/common/iniparser.py @@ -0,0 +1,130 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 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. + + +class ParseError(Exception): + def __init__(self, message, lineno, line): + self.msg = message + self.line = line + self.lineno = lineno + + def __str__(self): + return 'at line %d, %s: %r' % (self.lineno, self.msg, self.line) + + +class BaseParser(object): + lineno = 0 + parse_exc = ParseError + + def _assignment(self, key, value): + self.assignment(key, value) + return None, [] + + def _get_section(self, line): + if line[-1] != ']': + return self.error_no_section_end_bracket(line) + if len(line) <= 2: + return self.error_no_section_name(line) + + return line[1:-1] + + def _split_key_value(self, line): + colon = line.find(':') + equal = line.find('=') + if colon < 0 and equal < 0: + return self.error_invalid_assignment(line) + + if colon < 0 or (equal >= 0 and equal < colon): + key, value = line[:equal], line[equal + 1:] + else: + key, value = line[:colon], line[colon + 1:] + + value = value.strip() + if ((value and value[0] == value[-1]) and + (value[0] == "\"" or value[0] == "'")): + value = value[1:-1] + return key.strip(), [value] + + def parse(self, lineiter): + key = None + value = [] + + for line in lineiter: + self.lineno += 1 + + line = line.rstrip() + if not line: + # Blank line, ends multi-line values + if key: + key, value = self._assignment(key, value) + continue + elif line[0] in (' ', '\t'): + # Continuation of previous assignment + if key is None: + self.error_unexpected_continuation(line) + else: + value.append(line.lstrip()) + continue + + if key: + # Flush previous assignment, if any + key, value = self._assignment(key, value) + + if line[0] == '[': + # Section start + section = self._get_section(line) + if section: + self.new_section(section) + elif line[0] in '#;': + self.comment(line[1:].lstrip()) + else: + key, value = self._split_key_value(line) + if not key: + return self.error_empty_key(line) + + if key: + # Flush previous assignment, if any + self._assignment(key, value) + + def assignment(self, key, value): + """Called when a full assignment is parsed""" + raise NotImplementedError() + + def new_section(self, section): + """Called when a new section is started""" + raise NotImplementedError() + + def comment(self, comment): + """Called when a comment is parsed""" + pass + + def error_invalid_assignment(self, line): + raise self.parse_exc("No ':' or '=' found in assignment", + self.lineno, line) + + def error_empty_key(self, line): + raise self.parse_exc('Key cannot be empty', self.lineno, line) + + def error_unexpected_continuation(self, line): + raise self.parse_exc('Unexpected continuation line', + self.lineno, line) + + def error_no_section_end_bracket(self, line): + raise self.parse_exc('Invalid section (must end with ])', + self.lineno, line) + + def error_no_section_name(self, line): + raise self.parse_exc('Empty section name', self.lineno, line) From b26cb5bf683e7f4f03d9704524a188b76ac5e9b9 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 31 Jan 2013 13:31:41 -0600 Subject: [PATCH 0030/3614] Upgraded to PEP8 1.3.3 to stay aligned with Nova, etc. Made all the necessary changes to pass new PEP8 standards. Also cleaned up docstrings to conform to the HACKING stanards. Change-Id: Ib8df3030da7a7885655689ab5da0717748c9edbe --- openstackclient/common/clientmanager.py | 25 +-- openstackclient/common/command.py | 8 +- openstackclient/common/exceptions.py | 57 +++---- openstackclient/common/openstackkeyring.py | 26 ++- openstackclient/common/utils.py | 4 +- openstackclient/compute/client.py | 9 +- openstackclient/compute/v2/server.py | 175 ++++++++------------- openstackclient/identity/client.py | 13 +- openstackclient/identity/v2_0/endpoint.py | 26 ++- openstackclient/identity/v2_0/role.py | 86 ++++------ openstackclient/identity/v2_0/service.py | 25 ++- openstackclient/identity/v2_0/tenant.py | 58 +++---- openstackclient/identity/v2_0/user.py | 78 ++++----- openstackclient/identity/v3/group.py | 53 +++---- openstackclient/identity/v3/project.py | 70 ++++----- openstackclient/image/client.py | 3 +- openstackclient/shell.py | 72 ++++----- run_tests.sh | 2 +- setup.py | 53 +++---- tools/test-requires | 2 +- tox.ini | 2 +- 21 files changed, 325 insertions(+), 522 deletions(-) diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index f6f6642e6a..830ecde709 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -13,8 +13,7 @@ # under the License. # -"""Manage access to the clients, including authenticating when needed. -""" +"""Manage access to the clients, including authenticating when needed.""" import logging @@ -22,13 +21,12 @@ from openstackclient.identity import client as identity_client from openstackclient.image import client as image_client + LOG = logging.getLogger(__name__) class ClientCache(object): - """Descriptor class for caching created client handles. - """ - + """Descriptor class for caching created client handles.""" def __init__(self, factory): self.factory = factory self._handle = None @@ -41,20 +39,14 @@ def __get__(self, instance, owner): class ClientManager(object): - """Manages access to API clients, including authentication. - """ - + """Manages access to API clients, including authentication.""" compute = ClientCache(compute_client.make_client) identity = ClientCache(identity_client.make_client) image = ClientCache(image_client.make_client) - def __init__(self, token=None, url=None, - auth_url=None, - tenant_name=None, tenant_id=None, - username=None, password=None, - region_name=None, - api_version=None, - ): + def __init__(self, token=None, url=None, auth_url=None, tenant_name=None, + tenant_id=None, username=None, password=None, + region_name=None, api_version=None): self._token = token self._url = url self._auth_url = auth_url @@ -74,8 +66,7 @@ def __init__(self, token=None, url=None, return def get_endpoint_for_service_type(self, service_type): - """Return the endpoint URL for the service type. - """ + """Return the endpoint URL for the service type.""" # See if we are using password flow auth, i.e. we have a # service catalog to select endpoints from if self._service_catalog: diff --git a/openstackclient/common/command.py b/openstackclient/common/command.py index cfcb605c36..64e855df67 100644 --- a/openstackclient/common/command.py +++ b/openstackclient/common/command.py @@ -13,17 +13,13 @@ # under the License. # -""" -OpenStack base command -""" +"""OpenStack base command""" from cliff.command import Command class OpenStackCommand(Command): - """Base class for OpenStack commands - """ - + """Base class for OpenStack commands.""" api = None def run(self, parsed_args): diff --git a/openstackclient/common/exceptions.py b/openstackclient/common/exceptions.py index 9dfb4bc785..ab043db042 100644 --- a/openstackclient/common/exceptions.py +++ b/openstackclient/common/exceptions.py @@ -13,9 +13,7 @@ # under the License. # -""" -Exception definitions. -""" +"""Exception definitions.""" class CommandError(Exception): @@ -27,8 +25,7 @@ class AuthorizationFailure(Exception): class NoTokenLookupException(Exception): - """This form of authentication does not support looking up - endpoints from an existing token.""" + """This does not support looking up endpoints from an existing token.""" pass @@ -38,15 +35,12 @@ class EndpointNotFound(Exception): class UnsupportedVersion(Exception): - """Indicates that the user is trying to use an unsupported - version of the API""" + """The user is trying to use an unsupported version of the API""" pass class ClientException(Exception): - """ - The base exception class for all exceptions this library raises. - """ + """The base exception class for all exceptions this library raises.""" def __init__(self, code, message=None, details=None): self.code = code self.message = message or self.__class__.message @@ -57,59 +51,44 @@ def __str__(self): class BadRequest(ClientException): - """ - HTTP 400 - Bad request: you sent some malformed data. - """ + """HTTP 400 - Bad request: you sent some malformed data.""" http_status = 400 message = "Bad request" class Unauthorized(ClientException): - """ - HTTP 401 - Unauthorized: bad credentials. - """ + """HTTP 401 - Unauthorized: bad credentials.""" http_status = 401 message = "Unauthorized" class Forbidden(ClientException): - """ - HTTP 403 - Forbidden: your credentials don't give you access to this - resource. - """ + """HTTP 403 - Forbidden: not authorized to access to this resource.""" http_status = 403 message = "Forbidden" class NotFound(ClientException): - """ - HTTP 404 - Not found - """ + """HTTP 404 - Not found""" http_status = 404 message = "Not found" class Conflict(ClientException): - """ - HTTP 409 - Conflict - """ + """HTTP 409 - Conflict""" http_status = 409 message = "Conflict" class OverLimit(ClientException): - """ - HTTP 413 - Over limit: you're over the API limits for this time period. - """ + """HTTP 413 - Over limit: reached the API limits for this time period.""" http_status = 413 message = "Over limit" # NotImplemented is a python keyword. class HTTPNotImplemented(ClientException): - """ - HTTP 501 - Not Implemented: the server does not support this operation. - """ + """HTTP 501 - Not Implemented: server does not support this operation.""" http_status = 501 message = "Not Implemented" @@ -120,14 +99,18 @@ class HTTPNotImplemented(ClientException): # for c in ClientException.__subclasses__()) # # Instead, we have to hardcode it: -_code_map = dict((c.http_status, c) for c in [BadRequest, Unauthorized, - Forbidden, NotFound, OverLimit, HTTPNotImplemented]) +_code_map = dict((c.http_status, c) for c in [ + BadRequest, + Unauthorized, + Forbidden, + NotFound, + OverLimit, + HTTPNotImplemented +]) def from_response(response, body): - """ - Return an instance of a ClientException or subclass - based on an httplib2 response. + """Return an instance of a ClientException based on an httplib2 response. Usage:: diff --git a/openstackclient/common/openstackkeyring.py b/openstackclient/common/openstackkeyring.py index 78c74b8f4a..e7431e5488 100644 --- a/openstackclient/common/openstackkeyring.py +++ b/openstackclient/common/openstackkeyring.py @@ -13,50 +13,48 @@ # 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 import keyring import os + KEYRING_FILE = os.path.join(os.path.expanduser('~'), '.openstack-keyring.cfg') class OpenstackKeyring(keyring.backend.BasicFileKeyring): - """ Openstack Keyring to store encrypted password """ - + """Openstack Keyring to store encrypted password.""" filename = KEYRING_FILE def supported(self): - """ applicable for all platforms, but not recommend """ + """Applicable for all platforms, but not recommend.""" pass def _init_crypter(self): - """ initialize the crypter using the class name """ + """Initialize the crypter using the class name.""" block_size = 32 padding = '0' # init the cipher with the class name, upto block_size password = __name__[block_size:] - password = password + (block_size - len(password) % \ - block_size) * padding + password = password + (block_size - len(password) % + block_size) * padding return AES.new(password, AES.MODE_CFB) def encrypt(self, password): - """ encrypt the given password """ + """Encrypt the given password.""" crypter = self._init_crypter() return crypter.encrypt(password) def decrypt(self, password_encrypted): - """ decrypt the given password """ + """Decrypt the given password.""" crypter = self._init_crypter() return crypter.decrypt(password_encrypted) def os_keyring(): - """ initialize the openstack keyring """ - return keyring.core.load_keyring(None, - 'openstackclient.common.openstackkeyring.OpenstackKeyring') + """Initialize the openstack keyring.""" + keyring = 'openstackclient.common.openstackkeyring.OpenstackKeyring' + return keyring.core.load_keyring(None, keyring) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 19cca3f5f6..6477285eb0 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -13,9 +13,7 @@ # under the License. # -""" -Common client utilities -""" +"""Common client utilities""" import os import sys diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py index e1db22c811..f7ebfe3e8e 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -27,13 +27,11 @@ def make_client(instance): - """Returns a compute service client. - """ + """Returns a compute service client.""" compute_client = utils.get_client_class( API_NAME, instance._api_version[API_NAME], - API_VERSIONS, - ) + API_VERSIONS) LOG.debug('instantiating compute client: %s' % compute_client) client = compute_client( username=instance._username, @@ -49,8 +47,7 @@ def make_client(instance): extensions=[], service_type=API_NAME, # FIXME(dhellmann): what is service_name? - service_name='', - ) + service_name='') # Populate the Nova client to skip another auth query to Identity if instance._url: diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index a334e19832..24566a665f 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -13,9 +13,7 @@ # under the License. # -""" -Server action implementations -""" +"""Server action implementations""" import logging import os @@ -128,109 +126,93 @@ def get_parser(self, prog_name): parser.add_argument( 'server_name', metavar='', - help='New server name', - ) + help='New server name') parser.add_argument( '--image', metavar='', required=True, - help='Create server from this image', - ) + help='Create server from this image') parser.add_argument( '--flavor', metavar='', required=True, - help='Create server with this flavor', - ) + help='Create server with this flavor') parser.add_argument( '--security-group', metavar='', action='append', default=[], - help='Security group to assign to this server ' \ - '(repeat for multiple groups)', - ) + help='Security group to assign to this server ' + '(repeat for multiple groups)') parser.add_argument( '--key-name', metavar='', - help='Keypair to inject into this server (optional extension)', - ) + help='Keypair to inject into this server (optional extension)') parser.add_argument( '--meta-data', metavar='', action='append', default=[], - help='Metadata to store for this server ' \ - '(repeat for multiple values)', - ) + help='Metadata to store for this server ' + '(repeat for multiple values)') parser.add_argument( '--file', metavar='', action='append', default=[], - help='File to inject into image before boot ' \ - '(repeat for multiple files)', - ) + help='File to inject into image before boot ' + '(repeat for multiple files)') parser.add_argument( '--user-data', metavar='', - help='User data file to be serverd by the metadata server', - ) + help='User data file to be serverd by the metadata server') parser.add_argument( '--availability-zone', metavar='', - help='Keypair to inject into this server', - ) + help='Keypair to inject into this server') parser.add_argument( '--block-device-mapping', metavar='', action='append', default=[], - help='Map block devices; map is ' \ - '::: ' \ - '(optional extension)', - ) + help='Map block devices; map is ' + '::: ' + '(optional extension)') parser.add_argument( '--nic', metavar='', action='append', default=[], - help='Specify NIC configuration (optional extension)', - ) + help='Specify NIC configuration (optional extension)') parser.add_argument( '--hint', metavar='', action='append', default=[], - help='Hints for the scheduler (optional extension)', - ) + help='Hints for the scheduler (optional extension)') parser.add_argument( '--config-drive', metavar='|True', default=False, - help='Use specified volume as the config drive, ' \ - 'or \'True\' to use an ephemeral drive', - ) + help='Use specified volume as the config drive, ' + 'or \'True\' to use an ephemeral drive') parser.add_argument( '--min', metavar='', type=int, default=1, - help='Minimum number of servers to launch (default=1)', - ) + help='Minimum number of servers to launch (default=1)') parser.add_argument( '--max', metavar='', type=int, default=1, - help='Maximum number of servers to launch (default=1)', - ) + help='Maximum number of servers to launch (default=1)') parser.add_argument( '--wait', dest='wait', action='store_true', - help='Wait for server to become active to return', - ) + help='Wait for server to become active to return') return parser def take_action(self, parsed_args): @@ -239,11 +221,11 @@ def take_action(self, parsed_args): # Lookup parsed_args.image image = utils.find_resource(compute_client.images, - parsed_args.image) + parsed_args.image) # Lookup parsed_args.flavor flavor = utils.find_resource(compute_client.flavors, - parsed_args.flavor) + parsed_args.flavor) boot_args = [parsed_args.server_name, image, flavor] @@ -259,7 +241,7 @@ def take_action(self, parsed_args): if parsed_args.min > parsed_args.max: raise exceptions.CommandError("min instances should be <= " - "max instances") + "max instances") if parsed_args.min < 1: raise exceptions.CommandError("min instances should be > 0") if parsed_args.max < 1: @@ -270,17 +252,17 @@ def take_action(self, parsed_args): try: userdata = open(parsed_args.user_data) except IOError, e: - raise exceptions.CommandError("Can't open '%s': %s" % \ - (parsed_args.user_data, e)) + raise exceptions.CommandError("Can't open '%s': %s" % + (parsed_args.user_data, e)) block_device_mapping = dict(v.split('=', 1) - for v in parsed_args.block_device_mapping) + for v in parsed_args.block_device_mapping) nics = [] for nic_str in parsed_args.nic: nic_info = {"net-id": "", "v4-fixed-ip": ""} nic_info.update(dict(kv_str.split("=", 1) - for kv_str in nic_str.split(","))) + for kv_str in nic_str.split(","))) nics.append(nic_info) hints = {} @@ -301,7 +283,7 @@ def take_action(self, parsed_args): if str(parsed_args.config_drive).lower() in ("true", "1"): config_drive = True elif str(parsed_args.config_drive).lower() in ("false", "0", - "", "none"): + "", "none"): config_drive = None else: config_drive = parsed_args.config_drive @@ -319,8 +301,7 @@ def take_action(self, parsed_args): block_device_mapping=block_device_mapping, nics=nics, scheduler_hints=hints, - config_drive=config_drive, - ) + config_drive=config_drive) self.log.debug('boot_args: %s' % boot_args) self.log.debug('boot_kwargs: %s' % boot_kwargs) @@ -328,7 +309,7 @@ def take_action(self, parsed_args): if parsed_args.wait: _wait_for_status(compute_client.servers.get, server._info['id'], - ['active']) + ['active']) details = _prep_server_detail(compute_client, server) return zip(*sorted(details.iteritems())) @@ -345,8 +326,7 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help='Name or ID of server to delete', - ) + help='Name or ID of server to delete') return parser def take_action(self, parsed_args): @@ -369,55 +349,45 @@ def get_parser(self, prog_name): parser.add_argument( '--reservation-id', metavar='', - help='only return instances that match the reservation', - ) + help='only return instances that match the reservation') parser.add_argument( '--ip', metavar='', - help='regular expression to match IP address', - ) + help='regular expression to match IP address') parser.add_argument( '--ip6', metavar='', - help='regular expression to match IPv6 address', - ) + help='regular expression to match IPv6 address') parser.add_argument( '--name', metavar='', - help='regular expression to match name', - ) + help='regular expression to match name') parser.add_argument( '--instance-name', metavar='', - help='regular expression to match instance name', - ) + help='regular expression to match instance name') parser.add_argument( '--status', metavar='', - help='search by server status', # FIXME(dhellmann): Add choices? - ) + help='search by server status') parser.add_argument( '--flavor', metavar='', - help='search by flavor ID', - ) + help='search by flavor ID') parser.add_argument( '--image', metavar='', - help='search by image ID', - ) + help='search by image ID') parser.add_argument( '--host', metavar='', - help='search by hostname', - ) + help='search by hostname') parser.add_argument( '--all-tenants', action='store_true', default=bool(int(os.environ.get("ALL_TENANTS", 0))), - help='display information from all tenants (admin only)', - ) + help='display information from all tenants (admin only)') return parser def take_action(self, parsed_args): @@ -434,7 +404,7 @@ def take_action(self, parsed_args): 'image': parsed_args.image, 'host': parsed_args.host, 'all_tenants': parsed_args.all_tenants, - } + } self.log.debug('search options: %s', search_opts) # FIXME(dhellmann): Consider adding other columns columns = ('ID', 'Name', 'Status', 'Networks') @@ -443,8 +413,7 @@ def take_action(self, parsed_args): (utils.get_item_properties( s, columns, formatters={'Networks': _format_servers_list_networks}, - ) for s in data), - ) + ) for s in data)) class PauseServer(command.Command): @@ -458,8 +427,7 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help='Name or ID of server to pause', - ) + help='Name or ID of server to pause') return parser def take_action(self, parsed_args): @@ -482,8 +450,7 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help='Name or ID of server to reboot', - ) + help='Name or ID of server to reboot') group = parser.add_mutually_exclusive_group() group.add_argument( '--hard', @@ -491,22 +458,19 @@ def get_parser(self, prog_name): action='store_const', const=servers.REBOOT_HARD, default=servers.REBOOT_SOFT, - help='Perform a hard reboot', - ) + help='Perform a hard reboot') group.add_argument( '--soft', dest='reboot_type', action='store_const', const=servers.REBOOT_SOFT, default=servers.REBOOT_SOFT, - help='Perform a soft reboot', - ) + help='Perform a soft reboot') parser.add_argument( '--wait', dest='wait', action='store_true', - help='Wait for server to become active to return', - ) + help='Wait for server to become active to return') return parser def take_action(self, parsed_args): @@ -518,7 +482,7 @@ def take_action(self, parsed_args): if parsed_args.wait: _wait_for_status(compute_client.servers.get, server.id, - ['active']) + ['active']) return @@ -534,26 +498,22 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help='Server name or ID', - ) + help='Server name or ID') parser.add_argument( '--image', metavar='', required=True, - help='Recreate server from this image', - ) + help='Recreate server from this image') parser.add_argument( '--rebuild-password', metavar='', default=False, - help="Set the provided password on the rebuild instance", - ) + help="Set the provided password on the rebuild instance") parser.add_argument( '--wait', dest='wait', action='store_true', - help='Wait for server to become active to return', - ) + help='Wait for server to become active to return') return parser def take_action(self, parsed_args): @@ -576,7 +536,7 @@ def take_action(self, parsed_args): # TODO(dtroyer): force silent=True if output filter != table if parsed_args.wait: _wait_for_status(compute_client.servers.get, server._info['id'], - ['active']) + ['active']) details = _prep_server_detail(compute_client, server) return zip(*sorted(details.iteritems())) @@ -593,8 +553,7 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help='Name or ID of server to resume', - ) + help='Name or ID of server to resume') return parser def take_action(self, parsed_args): @@ -617,14 +576,14 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help='Name or ID of server to display'), + help='Name or ID of server to display') return parser def take_action(self, 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) + parsed_args.server) details = _prep_server_detail(compute_client, server) return zip(*sorted(details.iteritems())) @@ -641,15 +600,14 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help='Name or ID of server to suspend', - ) + help='Name or ID of server to suspend') return parser def take_action(self, 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) + server = utils.find_resource(compute_client.servers, + parsed_args.server) server.suspend() return @@ -665,14 +623,13 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help='Name or ID of server to unpause', - ) + help='Name or ID of server to unpause') return parser def take_action(self, 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) + server = utils.find_resource(compute_client.servers, + parsed_args.server) server.unpause() return diff --git a/openstackclient/identity/client.py b/openstackclient/identity/client.py index 84ff2b0532..748d166683 100644 --- a/openstackclient/identity/client.py +++ b/openstackclient/identity/client.py @@ -17,6 +17,7 @@ from openstackclient.common import utils + LOG = logging.getLogger(__name__) API_NAME = 'identity' @@ -27,19 +28,16 @@ def make_client(instance): - """Returns an identity service client. - """ + """Returns an identity service client.""" identity_client = utils.get_client_class( API_NAME, instance._api_version[API_NAME], - API_VERSIONS, - ) + API_VERSIONS) if instance._url: LOG.debug('instantiating identity client: token flow') client = identity_client( endpoint=instance._url, - token=instance._token, - ) + token=instance._token) else: LOG.debug('instantiating identity client: password flow') client = identity_client( @@ -48,6 +46,5 @@ def make_client(instance): tenant_name=instance._tenant_name, tenant_id=instance._tenant_id, auth_url=instance._auth_url, - region_name=instance._region_name, - ) + region_name=instance._region_name) return client diff --git a/openstackclient/identity/v2_0/endpoint.py b/openstackclient/identity/v2_0/endpoint.py index f9c689ec80..4ce4fcb20a 100644 --- a/openstackclient/identity/v2_0/endpoint.py +++ b/openstackclient/identity/v2_0/endpoint.py @@ -13,9 +13,7 @@ # under the License. # -""" -Endpoint action implementations -""" +"""Endpoint action implementations""" import logging @@ -61,15 +59,14 @@ def get_parser(self, prog_name): def take_action(self, 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 = utils.find_resource(identity_client.services, + parsed_args.service) endpoint = identity_client.endpoints.create( parsed_args.region, service.id, parsed_args.publicurl, parsed_args.adminurl, - parsed_args.internalurl, - ) + parsed_args.internalurl,) info = {} info.update(endpoint._info) @@ -119,7 +116,7 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity if parsed_args.long: columns = ('ID', 'Region', 'Service Name', 'Service Type', - 'PublicURL', 'AdminURL', 'InternalURL') + 'PublicURL', 'AdminURL', 'InternalURL') else: columns = ('ID', 'Region', 'Service Name', 'Service Type') data = identity_client.endpoints.list() @@ -133,8 +130,7 @@ def take_action(self, parsed_args): (utils.get_item_properties( s, columns, formatters={}, - ) for s in data), - ) + ) for s in data)) class ShowEndpoint(show.ShowOne): @@ -154,7 +150,7 @@ def get_parser(self, prog_name): metavar='', default='publicURL', help='Endpoint type: publicURL, internalURL, adminURL ' + - '(default publicURL)') + '(default publicURL)') parser.add_argument( '--attr', metavar='', @@ -196,8 +192,8 @@ def take_action(self, parsed_args): # 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) + service = utils.find_resource(identity_client.services, + parsed_args.service) except exceptions.CommandError: try: # search for service type @@ -215,8 +211,8 @@ def take_action(self, parsed_args): if ep.service_id == service.id: info = {} info.update(ep._info) - service = utils.find_resource( - identity_client.services, ep.service_id) + service = utils.find_resource(identity_client.services, + ep.service_id) info['service_name'] = service.name info['service_type'] = service.type return zip(*sorted(info.iteritems())) diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index 49e0365357..5c905d916f 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -13,9 +13,7 @@ # under the License. # -""" -Role action implementations -""" +"""Role action implementations""" import logging @@ -37,36 +35,30 @@ def get_parser(self, prog_name): parser.add_argument( 'role', metavar='', - help='Role name or ID to add to user', - ) + help='Role name or ID to add to user') parser.add_argument( '--tenant', metavar='', required=True, - help='Name or ID of tenant to include', - ) + help='Name or ID of tenant to include') parser.add_argument( '--user', metavar='', required=True, - help='Name or ID of user to include', - ) + help='Name or ID of user to include') return parser def take_action(self, 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) - tenant = utils.find_resource( - identity_client.tenants, parsed_args.tenant) - user = utils.find_resource( - identity_client.users, parsed_args.user) + role = utils.find_resource(identity_client.roles, parsed_args.role) + tenant = utils.find_resource(identity_client.tenants, + parsed_args.tenant) + user = utils.find_resource(identity_client.users, parsed_args.user) role = identity_client.roles.add_user_role( user, role, - tenant, - ) + tenant) info = {} info.update(role._info) @@ -84,16 +76,13 @@ def get_parser(self, prog_name): parser.add_argument( 'role_name', metavar='', - help='New role name', - ) + help='New role name') return parser def take_action(self, 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, - ) + role = identity_client.roles.create(parsed_args.role_name) info = {} info.update(role._info) @@ -111,15 +100,13 @@ def get_parser(self, prog_name): parser.add_argument( 'role', metavar='', - help='Name or ID of role to delete', - ) + help='Name or ID of role to delete') return parser def take_action(self, 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) + role = utils.find_resource(identity_client.roles, parsed_args.role) identity_client.roles.delete(role.id) return @@ -138,8 +125,7 @@ def take_action(self, parsed_args): (utils.get_item_properties( s, columns, formatters={}, - ) for s in data), - ) + ) for s in data)) class ListUserRole(lister.Lister): @@ -154,13 +140,11 @@ def get_parser(self, prog_name): 'user', metavar='', nargs='?', - help='Name or ID of user to include', - ) + help='Name or ID of user to include') parser.add_argument( '--tenant', metavar='', - help='Name or ID of tenant to include', - ) + help='Name or ID of tenant to include') return parser def take_action(self, parsed_args): @@ -176,10 +160,9 @@ def take_action(self, parsed_args): if not parsed_args.user: parsed_args.user = identity_client.auth_user_id - tenant = utils.find_resource( - identity_client.tenants, parsed_args.tenant) - user = utils.find_resource( - identity_client.users, parsed_args.user) + tenant = utils.find_resource(identity_client.tenants, + parsed_args.tenant) + user = utils.find_resource(identity_client.users, parsed_args.user) data = identity_client.roles.roles_for_user(user.id, tenant.id) @@ -192,8 +175,7 @@ def take_action(self, parsed_args): (utils.get_item_properties( s, columns, formatters={}, - ) for s in data), - ) + ) for s in data)) class RemoveRole(command.Command): @@ -207,36 +189,30 @@ def get_parser(self, prog_name): parser.add_argument( 'role', metavar='', - help='Role name or ID to remove from user', - ) + help='Role name or ID to remove from user') parser.add_argument( '--tenant', metavar='', required=True, - help='Name or ID of tenant', - ) + help='Name or ID of tenant') parser.add_argument( '--user', metavar='', required=True, - help='Name or ID of user', - ) + help='Name or ID of user') return parser def take_action(self, 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) - tenant = utils.find_resource( - identity_client.tenants, parsed_args.tenant) - user = utils.find_resource( - identity_client.users, parsed_args.user) + role = utils.find_resource(identity_client.roles, parsed_args.role) + tenant = utils.find_resource(identity_client.tenants, + parsed_args.tenant) + user = utils.find_resource(identity_client.users, parsed_args.user) identity_client.roles.remove_user_role( user.id, role.id, - tenant.id, - ) + tenant.id) class ShowRole(show.ShowOne): @@ -250,15 +226,13 @@ def get_parser(self, prog_name): parser.add_argument( 'role', metavar='', - help='Name or ID of role to display', - ) + help='Name or ID of role to display') return parser def take_action(self, 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) + role = utils.find_resource(identity_client.roles, parsed_args.role) info = {} info.update(role._info) diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index 89fe605abc..21e32a51b1 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -13,9 +13,7 @@ # under the License. # -""" -Service action implementations -""" +"""Service action implementations""" import logging @@ -44,13 +42,11 @@ def get_parser(self, prog_name): '--type', metavar='', required=True, - help='New service type', - ) + help='New service type') parser.add_argument( '--description', metavar='', - help='New service description', - ) + help='New service description') return parser def take_action(self, parsed_args): @@ -59,8 +55,7 @@ def take_action(self, parsed_args): service = identity_client.services.create( parsed_args.name, parsed_args.type, - parsed_args.description, - ) + parsed_args.description) info = {} info.update(service._info) @@ -78,8 +73,7 @@ def get_parser(self, prog_name): parser.add_argument( 'service', metavar='', - help='ID of service to delete', - ) + help='ID of service to delete') return parser def take_action(self, parsed_args): @@ -115,8 +109,7 @@ def take_action(self, parsed_args): (utils.get_item_properties( s, columns, formatters={}, - ) for s in data), - ) + ) for s in data)) class ShowService(show.ShowOne): @@ -138,8 +131,8 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity try: # search for the usual ID or name - service = utils.find_resource( - identity_client.services, parsed_args.service) + service = utils.find_resource(identity_client.services, + parsed_args.service) except exceptions.CommandError: try: # search for service type @@ -149,7 +142,7 @@ def take_action(self, parsed_args): # common client exceptions except identity_exc.NotFound: msg = "No service with a type, name or ID of '%s' exists." % \ - name_or_id + name_or_id raise exceptions.CommandError(msg) info = {} diff --git a/openstackclient/identity/v2_0/tenant.py b/openstackclient/identity/v2_0/tenant.py index f24bc7265d..8a2f738f4a 100644 --- a/openstackclient/identity/v2_0/tenant.py +++ b/openstackclient/identity/v2_0/tenant.py @@ -13,9 +13,7 @@ # under the License. # -""" -Tenant action implementations -""" +"""Tenant action implementations""" import logging @@ -37,27 +35,23 @@ def get_parser(self, prog_name): parser.add_argument( 'tenant_name', metavar='', - help='New tenant name', - ) + help='New tenant name') parser.add_argument( '--description', metavar='', - help='New tenant description', - ) + help='New tenant description') enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', dest='enabled', action='store_true', default=True, - help='Enable tenant', - ) + help='Enable tenant') enable_group.add_argument( '--disable', dest='enabled', action='store_false', - help='Disable tenant', - ) + help='Disable tenant') return parser def take_action(self, parsed_args): @@ -66,8 +60,7 @@ def take_action(self, parsed_args): tenant = identity_client.tenants.create( parsed_args.tenant_name, description=parsed_args.description, - enabled=parsed_args.enabled, - ) + enabled=parsed_args.enabled) info = {} info.update(tenant._info) @@ -85,15 +78,14 @@ def get_parser(self, prog_name): parser.add_argument( 'tenant', metavar='', - help='Name or ID of tenant to delete', - ) + help='Name or ID of tenant to delete') return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) identity_client = self.app.client_manager.identity - tenant = utils.find_resource( - identity_client.tenants, parsed_args.tenant) + tenant = utils.find_resource(identity_client.tenants, + parsed_args.tenant) identity_client.tenants.delete(tenant.id) return @@ -110,8 +102,7 @@ def get_parser(self, prog_name): '--long', action='store_true', default=False, - help='Additional fields are listed in output', - ) + help='Additional fields are listed in output') return parser def take_action(self, parsed_args): @@ -125,8 +116,7 @@ def take_action(self, parsed_args): (utils.get_item_properties( s, columns, formatters={}, - ) for s in data), - ) + ) for s in data)) class SetTenant(command.Command): @@ -140,39 +130,34 @@ def get_parser(self, prog_name): parser.add_argument( 'tenant', metavar='', - help='Name or ID of tenant to change', - ) + help='Name or ID of tenant to change') parser.add_argument( '--name', metavar='', - help='New tenant name', - ) + help='New tenant name') parser.add_argument( '--description', metavar='', - help='New tenant description', - ) + help='New tenant description') enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', dest='enabled', action='store_true', default=True, - help='Enable tenant (default)', - ) + help='Enable tenant (default)') enable_group.add_argument( '--disable', dest='enabled', action='store_false', - help='Disable tenant', - ) + help='Disable tenant') return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) identity_client = self.app.client_manager.identity - tenant = utils.find_resource( - identity_client.tenants, parsed_args.tenant) + tenant = utils.find_resource(identity_client.tenants, + parsed_args.tenant) kwargs = {} if parsed_args.name: kwargs['name'] = parsed_args.name @@ -199,15 +184,14 @@ def get_parser(self, prog_name): parser.add_argument( 'tenant', metavar='', - help='Name or ID of tenant to display', - ) + help='Name or ID of tenant to display') return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) identity_client = self.app.client_manager.identity - tenant = utils.find_resource( - identity_client.tenants, parsed_args.tenant) + tenant = utils.find_resource(identity_client.tenants, + parsed_args.tenant) info = {} info.update(tenant._info) diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index cf69d73f5e..2017e5e3d2 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -13,9 +13,7 @@ # under the License. # -""" -User action implementations -""" +"""User action implementations""" import logging @@ -37,45 +35,39 @@ def get_parser(self, prog_name): parser.add_argument( 'name', metavar='', - help='New user name', - ) + help='New user name') parser.add_argument( '--password', metavar='', - help='New user password', - ) + help='New user password') parser.add_argument( '--email', metavar='', - help='New user email address', - ) + help='New user email address') parser.add_argument( '--tenant', metavar='', - help='New default tenant name or ID', - ) + help='New default tenant name or ID') enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', dest='enabled', action='store_true', default=True, - help='Enable user', - ) + help='Enable user') enable_group.add_argument( '--disable', dest='enabled', action='store_false', - help='Disable user', - ) + help='Disable user') 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.tenant: - tenant_id = utils.find_resource( - identity_client.tenants, parsed_args.tenant).id + tenant_id = utils.find_resource(identity_client.tenants, + parsed_args.tenant).id else: tenant_id = None user = identity_client.users.create( @@ -83,8 +75,7 @@ def take_action(self, parsed_args): parsed_args.password, parsed_args.email, tenant_id=tenant_id, - enabled=parsed_args.enabled, - ) + enabled=parsed_args.enabled) info = {} info.update(user._info) @@ -102,15 +93,13 @@ def get_parser(self, prog_name): parser.add_argument( 'user', metavar='', - help='Name or ID of user to delete', - ) + help='Name or ID of user to delete') 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) + user = utils.find_resource(identity_client.users, parsed_args.user) identity_client.users.delete(user.id) return @@ -126,14 +115,12 @@ def get_parser(self, prog_name): parser.add_argument( '--tenant', metavar='', - help='Name or ID of tenant to filter users', - ) + help='Name or ID of tenant to filter users') parser.add_argument( '--long', action='store_true', default=False, - help='Additional fields are listed in output', - ) + help='Additional fields are listed in output') return parser def take_action(self, parsed_args): @@ -147,8 +134,7 @@ def take_action(self, parsed_args): (utils.get_item_properties( s, columns, formatters={}, - ) for s in data), - ) + ) for s in data)) class SetUser(command.Command): @@ -162,57 +148,49 @@ def get_parser(self, prog_name): parser.add_argument( 'user', metavar='', - help='Name or ID of user to change', - ) + help='Name or ID of user to change') parser.add_argument( '--name', metavar='', - help='New user name', - ) + help='New user name') parser.add_argument( '--password', metavar='', - help='New user password', - ) + help='New user password') parser.add_argument( '--email', metavar='', - help='New user email address', - ) + help='New user email address') parser.add_argument( '--tenant', metavar='', - help='New default tenant name or ID', - ) + help='New default tenant name or ID') enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', dest='enabled', action='store_true', default=True, - help='Enable user (default)', - ) + help='Enable user (default)') enable_group.add_argument( '--disable', dest='enabled', action='store_false', - help='Disable user', - ) + help='Disable user') 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) + user = utils.find_resource(identity_client.users, parsed_args.user) kwargs = {} if parsed_args.name: kwargs['name'] = parsed_args.name if parsed_args.email: kwargs['email'] = parsed_args.email if parsed_args.tenant: - tenant_id = utils.find_resource( - identity_client.tenants, parsed_args.tenant).id + tenant_id = utils.find_resource(identity_client.tenants, + parsed_args.tenant).id kwargs['tenantId'] = tenant_id if 'enabled' in parsed_args: kwargs['enabled'] = parsed_args.enabled @@ -235,15 +213,13 @@ def get_parser(self, prog_name): parser.add_argument( 'user', metavar='', - help='Name or ID of user to display', - ) + help='Name or ID of user to display') 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) + user = utils.find_resource(identity_client.users, parsed_args.user) info = {} info.update(user._info) diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index f783eb4fcb..3a9b80ed11 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -13,9 +13,7 @@ # under the License. # -""" -Group action implementations -""" +"""Group action implementations""" import logging @@ -37,18 +35,15 @@ def get_parser(self, prog_name): parser.add_argument( 'name', metavar='', - help='New group name', - ) + help='New group name') parser.add_argument( '--description', metavar='', - help='New group description', - ) + help='New group description') parser.add_argument( '--domain', metavar='', - help='References the domain ID or name which owns the group', - ) + help='References the domain ID or name which owns the group') return parser @@ -56,15 +51,14 @@ def take_action(self, 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, parsed_args.domain).id + domain = utils.find_resource(identity_client.domains, + parsed_args.domain).id else: domain = None group = identity_client.groups.create( parsed_args.name, domain=domain, - description=parsed_args.description, - ) + description=parsed_args.description) info = {} info.update(group._info) @@ -82,15 +76,13 @@ def get_parser(self, prog_name): parser.add_argument( 'group', metavar='', - help='Name or ID of group to delete', - ) + help='Name or ID of group to delete') return parser def take_action(self, 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) + group = utils.find_resource(identity_client.groups, parsed_args.group) identity_client.groups.delete(group.id) return @@ -107,8 +99,7 @@ def get_parser(self, prog_name): '--long', action='store_true', default=False, - help='Additional fields are listed in output', - ) + help='Additional fields are listed in output') return parser def take_action(self, parsed_args): @@ -122,8 +113,7 @@ def take_action(self, parsed_args): (utils.get_item_properties( s, columns, formatters={}, - ) for s in data), - ) + ) for s in data)) class SetGroup(command.Command): @@ -137,30 +127,25 @@ def get_parser(self, prog_name): parser.add_argument( 'group', metavar='', - help='Name or ID of group to change', - ) + help='Name or ID of group to change') parser.add_argument( '--name', metavar='', - help='New group name', - ) + help='New group name') parser.add_argument( '--domain', metavar='', - help='New domain name or ID that will now own the group', - ) + help='New domain name or ID that will now own the group') parser.add_argument( '--description', metavar='', - help='New group description', - ) + help='New group description') return parser def take_action(self, 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) + group = utils.find_resource(identity_client.groups, parsed_args.group) kwargs = {} if parsed_args.name: kwargs['name'] = parsed_args.name @@ -189,15 +174,13 @@ def get_parser(self, prog_name): parser.add_argument( 'group', metavar='', - help='Name or ID of group to display', - ) + help='Name or ID of group to display') return parser def take_action(self, 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) + group = utils.find_resource(identity_client.groups, parsed_args.group) info = {} info.update(group._info) diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 06022e4510..d1e67acc2f 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -13,9 +13,7 @@ # under the License. # -""" -Project action implementations -""" +"""Project action implementations""" import logging @@ -37,18 +35,15 @@ def get_parser(self, prog_name): parser.add_argument( 'project_name', metavar='', - help='New project name', - ) + help='New project name') parser.add_argument( '--domain', metavar='', - help='References the domain name or ID which owns the project', - ) + help='References the domain name or ID which owns the project') parser.add_argument( '--description', metavar='', - help='New project description', - ) + help='New project description') # FIXME (stevemar): need to update enabled/disabled as per Dolph's # comments in 19999/4 enable_group = parser.add_mutually_exclusive_group() @@ -57,30 +52,29 @@ def get_parser(self, prog_name): dest='enabled', action='store_true', default=True, - help='Enable project', - ) + help='Enable project') enable_group.add_argument( '--disable', dest='enabled', action='store_false', - help='Disable project', - ) + help='Disable project') 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.domain: - domain = utils.find_resource( - identity_client.domains, parsed_args.domain).id + domain = utils.find_resource(identity_client.domains, + parsed_args.domain).id else: domain = None + project = identity_client.projects.create( parsed_args.project_name, domain=domain, description=parsed_args.description, - enabled=parsed_args.enabled, - ) + enabled=parsed_args.enabled) info = {} info.update(project._info) @@ -98,15 +92,14 @@ def get_parser(self, prog_name): parser.add_argument( 'project', metavar='', - help='Name or ID of project to delete', - ) + help='Name or ID of project to delete') return parser def take_action(self, 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) + project = utils.find_resource(identity_client.projects, + parsed_args.project) identity_client.projects.delete(project.id) return @@ -123,8 +116,7 @@ def get_parser(self, prog_name): '--long', action='store_true', default=False, - help='Additional fields are listed in output', - ) + help='Additional fields are listed in output') return parser def take_action(self, parsed_args): @@ -138,8 +130,7 @@ def take_action(self, parsed_args): (utils.get_item_properties( s, columns, formatters={}, - ) for s in data), - ) + ) for s in data)) class SetProject(command.Command): @@ -153,44 +144,38 @@ def get_parser(self, prog_name): parser.add_argument( 'project', metavar='', - help='Name or ID of project to change', - ) + help='Name or ID of project to change') parser.add_argument( '--name', metavar='', - help='New project name', - ) + help='New project name') parser.add_argument( '--domain', metavar='', - help='New domain name or ID that will now own the project', - ) + help='New domain name or ID that will now own the project') parser.add_argument( '--description', metavar='', - help='New project description', - ) + help='New project description') enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', dest='enabled', action='store_true', default=True, - help='Enable project (default)', - ) + help='Enable project (default)') enable_group.add_argument( '--disable', dest='enabled', action='store_false', - help='Disable project', - ) + help='Disable project') return parser def take_action(self, 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) + project = utils.find_resource(identity_client.projects, + parsed_args.project) kwargs = {} if parsed_args.name: kwargs['name'] = parsed_args.name @@ -221,15 +206,14 @@ def get_parser(self, prog_name): parser.add_argument( 'project', metavar='', - help='Name or ID of project to display', - ) + help='Name or ID of project to display') return parser def take_action(self, 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) + project = utils.find_resource(identity_client.projects, + parsed_args.project) info = {} info.update(project._info) diff --git a/openstackclient/image/client.py b/openstackclient/image/client.py index 5285e6d7d8..23e9a3e9f5 100644 --- a/openstackclient/image/client.py +++ b/openstackclient/image/client.py @@ -32,8 +32,7 @@ def make_client(instance): image_client = utils.get_client_class( API_NAME, instance._api_version[API_NAME], - API_VERSIONS - ) + API_VERSIONS) if not instance._url: instance._url = instance.get_endpoint_for_service_type(API_NAME) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index c007fc53b4..5dc0457213 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -13,9 +13,7 @@ # under the License. # -""" -Command-line interface to the OpenStack APIs -""" +"""Command-line interface to the OpenStack APIs""" import getpass import logging @@ -59,8 +57,7 @@ def __init__(self): super(OpenStackShell, self).__init__( description=__doc__.strip(), version=VERSION, - command_manager=CommandManager('openstack.cli'), - ) + command_manager=CommandManager('openstack.cli')) # This is instantiated in initialize_app() only when using # password flow auth @@ -69,57 +66,64 @@ def __init__(self): def build_option_parser(self, description, version): parser = super(OpenStackShell, self).build_option_parser( description, - version, - ) + version) # Global arguments - parser.add_argument('--os-auth-url', metavar='', + parser.add_argument( + '--os-auth-url', + metavar='', default=env('OS_AUTH_URL'), help='Authentication URL (Env: OS_AUTH_URL)') - - parser.add_argument('--os-tenant-name', metavar='', + parser.add_argument( + '--os-tenant-name', + metavar='', default=env('OS_TENANT_NAME'), help='Authentication tenant name (Env: OS_TENANT_NAME)') - - parser.add_argument('--os-tenant-id', metavar='', + parser.add_argument( + '--os-tenant-id', + metavar='', default=env('OS_TENANT_ID'), help='Authentication tenant ID (Env: OS_TENANT_ID)') - - parser.add_argument('--os-username', metavar='', + parser.add_argument( + '--os-username', + metavar='', default=utils.env('OS_USERNAME'), help='Authentication username (Env: OS_USERNAME)') - - parser.add_argument('--os-password', metavar='', + parser.add_argument( + '--os-password', + metavar='', default=utils.env('OS_PASSWORD'), help='Authentication password (Env: OS_PASSWORD)') - - parser.add_argument('--os-region-name', metavar='', + parser.add_argument( + '--os-region-name', + metavar='', default=env('OS_REGION_NAME'), help='Authentication region name (Env: OS_REGION_NAME)') - - parser.add_argument('--os-identity-api-version', + parser.add_argument( + '--os-identity-api-version', metavar='', default=env('OS_IDENTITY_API_VERSION', default='2.0'), help='Identity API version, default=2.0 ' - '(Env: OS_IDENTITY_API_VERSION)') - - parser.add_argument('--os-compute-api-version', + '(Env: OS_IDENTITY_API_VERSION)') + parser.add_argument( + '--os-compute-api-version', metavar='', default=env('OS_COMPUTE_API_VERSION', default='2'), help='Compute API version, default=2 ' - '(Env: OS_COMPUTE_API_VERSION)') - - parser.add_argument('--os-image-api-version', + '(Env: OS_COMPUTE_API_VERSION)') + parser.add_argument( + '--os-image-api-version', metavar='', default=env('OS_IMAGE_API_VERSION', default='1.0'), - help='Image API version, default=1.0 ' - '(Env: OS_IMAGE_API_VERSION)') - - parser.add_argument('--os-token', metavar='', + help='Image API version, default=1.0 (Env: OS_IMAGE_API_VERSION)') + parser.add_argument( + '--os-token', + metavar='', default=env('OS_TOKEN'), help='Defaults to env[OS_TOKEN]') - - parser.add_argument('--os-url', metavar='', + parser.add_argument( + '--os-url', + metavar='', default=env('OS_URL'), help='Defaults to env[OS_URL]') @@ -198,8 +202,7 @@ def authenticate_user(self): username=self.options.os_username, password=self.options.os_password, region_name=self.options.os_region_name, - api_version=self.api_version, - ) + api_version=self.api_version) return def init_keyring_backend(self): @@ -260,7 +263,6 @@ def initialize_app(self, argv): def prepare_to_run_command(self, cmd): """Set up auth and API versions""" self.log.debug('prepare_to_run_command %s', cmd.__class__.__name__) - self.log.debug("api: %s" % cmd.api if hasattr(cmd, 'api') else None) return diff --git a/run_tests.sh b/run_tests.sh index 71b336e58a..8a4ae624c4 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -101,7 +101,7 @@ function copy_subunit_log { function run_pep8 { echo "Running pep8 ..." - srcfiles="openstackclient tests" + srcfiles="openstackclient tests setup.py" # Just run PEP8 in current environment # # NOTE(sirp): W602 (deprecated 3-arg raise) is being ignored for the diff --git a/setup.py b/setup.py index 4d4f240a5c..6ee3d45119 100644 --- a/setup.py +++ b/setup.py @@ -40,14 +40,14 @@ def read(fname): author_email='openstack@lists.launchpad.net', packages=setuptools.find_packages(exclude=['tests', 'tests.*']), classifiers=[ - 'Development Status :: 2 - Pre-Alpha', - 'Environment :: Console', - 'Environment :: OpenStack', - 'Intended Audience :: Developers', - 'Intended Audience :: Information Technology', - 'License :: OSI Approved :: Apache Software License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', + 'Development Status :: 2 - Pre-Alpha', + 'Environment :: Console', + 'Environment :: OpenStack', + 'Intended Audience :: Developers', + 'Intended Audience :: Information Technology', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', ], install_requires=requires, dependency_links=dependency_links, @@ -56,25 +56,23 @@ def read(fname): 'console_scripts': ['openstack=openstackclient.shell:main'], 'openstack.cli': [ 'create_endpoint=' + - 'openstackclient.identity.v2_0.endpoint:CreateEndpoint', + 'openstackclient.identity.v2_0.endpoint:CreateEndpoint', 'delete_endpoint=' + - 'openstackclient.identity.v2_0.endpoint:DeleteEndpoint', + 'openstackclient.identity.v2_0.endpoint:DeleteEndpoint', 'list_endpoint=' + - 'openstackclient.identity.v2_0.endpoint:ListEndpoint', + 'openstackclient.identity.v2_0.endpoint:ListEndpoint', 'show_endpoint=' + - 'openstackclient.identity.v2_0.endpoint:ShowEndpoint', - + 'openstackclient.identity.v2_0.endpoint:ShowEndpoint', 'add_role=' + - 'openstackclient.identity.v2_0.role:AddRole', + 'openstackclient.identity.v2_0.role:AddRole', 'create_role=' + - 'openstackclient.identity.v2_0.role:CreateRole', + 'openstackclient.identity.v2_0.role:CreateRole', 'delete_role=' + - 'openstackclient.identity.v2_0.role:DeleteRole', + 'openstackclient.identity.v2_0.role:DeleteRole', 'list_role=openstackclient.identity.v2_0.role:ListRole', 'remove_role=' + - 'openstackclient.identity.v2_0.role:RemoveRole', + 'openstackclient.identity.v2_0.role:RemoveRole', 'show_role=openstackclient.identity.v2_0.role:ShowRole', - 'create_server=openstackclient.compute.v2.server:CreateServer', 'delete_server=openstackclient.compute.v2.server:DeleteServer', 'list_server=openstackclient.compute.v2.server:ListServer', @@ -85,26 +83,23 @@ def read(fname): 'show_server=openstackclient.compute.v2.server:ShowServer', 'suspend_server=openstackclient.compute.v2.server:SuspendServer', 'unpause_server=openstackclient.compute.v2.server:UnpauseServer', - 'create_service=' + - 'openstackclient.identity.v2_0.service:CreateService', + 'openstackclient.identity.v2_0.service:CreateService', 'delete_service=' + - 'openstackclient.identity.v2_0.service:DeleteService', + 'openstackclient.identity.v2_0.service:DeleteService', 'list_service=openstackclient.identity.v2_0.service:ListService', 'show_service=openstackclient.identity.v2_0.service:ShowService', - 'create_tenant=' + - 'openstackclient.identity.v2_0.tenant:CreateTenant', + 'openstackclient.identity.v2_0.tenant:CreateTenant', 'delete_tenant=' + - 'openstackclient.identity.v2_0.tenant:DeleteTenant', + 'openstackclient.identity.v2_0.tenant:DeleteTenant', 'list_tenant=openstackclient.identity.v2_0.tenant:ListTenant', 'set_tenant=openstackclient.identity.v2_0.tenant:SetTenant', 'show_tenant=openstackclient.identity.v2_0.tenant:ShowTenant', - 'create_user=' + - 'openstackclient.identity.v2_0.user:CreateUser', + 'openstackclient.identity.v2_0.user:CreateUser', 'delete_user=' + - 'openstackclient.identity.v2_0.user:DeleteUser', + 'openstackclient.identity.v2_0.user:DeleteUser', 'list_user=openstackclient.identity.v2_0.user:ListUser', 'set_user=openstackclient.identity.v2_0.user:SetUser', 'show_user=openstackclient.identity.v2_0.user:ShowUser', @@ -118,9 +113,9 @@ def read(fname): 'show_group=openstackclient.identity.v3.group:ShowGroup', 'list_group=openstackclient.identity.v3.group:ListGroup', 'create_project=' + - 'openstackclient.identity.v3.project:CreateProject', + 'openstackclient.identity.v3.project:CreateProject', 'delete_project=' + - 'openstackclient.identity.v3.project:DeleteProject', + 'openstackclient.identity.v3.project:DeleteProject', 'set_project=openstackclient.identity.v3.project:SetProject', 'show_project=openstackclient.identity.v3.project:ShowProject', 'list_project=openstackclient.identity.v3.project:ListProject', diff --git a/tools/test-requires b/tools/test-requires index 7fb687d25c..405b408ffa 100644 --- a/tools/test-requires +++ b/tools/test-requires @@ -5,7 +5,7 @@ discover fixtures>=0.3.12 mock openstack.nose_plugin -pep8==1.1 +pep8==1.3.3 sphinx>=1.1.2 testrepository>=0.0.13 testtools>=0.9.26 diff --git a/tox.ini b/tox.ini index 6b4a96dbdb..fb1b2ba699 100644 --- a/tox.ini +++ b/tox.ini @@ -11,7 +11,7 @@ deps = -r{toxinidir}/tools/pip-requires commands = python setup.py testr --testr-args='{posargs}' [testenv:pep8] -deps = pep8==1.1 +deps = pep8==1.3.3 commands = pep8 --repeat --show-source openstackclient setup.py [testenv:venv] From b675ca4f7f254dd24ee9091dcbdf74b2b9d7aa3b Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 31 Jan 2013 17:35:47 -0600 Subject: [PATCH 0031/3614] Sync latest openstack-common. This fixes an issue when trying to run install_venv from within the source directory. Change-Id: Id4dcb070319ec52d0a1b466e911fbfdf805db613 --- tools/install_venv_common.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py index a92e4ab818..5cef255509 100644 --- a/tools/install_venv_common.py +++ b/tools/install_venv_common.py @@ -25,6 +25,13 @@ import subprocess import sys + +possible_topdir = os.getcwd() +if os.path.exists(os.path.join(possible_topdir, "openstackclient", + "__init__.py")): + sys.path.insert(0, possible_topdir) + + from openstackclient.openstack.common import cfg From 1c44d260dc6d8e911c25219492cfe9ece8e50f8f Mon Sep 17 00:00:00 2001 From: Alessio Ababilov Date: Wed, 6 Feb 2013 16:47:06 +0200 Subject: [PATCH 0032/3614] Update .coveragerc Set up proper source and omit options. Change-Id: Ia067d229eb3d7a5806364de22724c16166950bc1 Implements: blueprint update-coveragerc --- .coveragerc | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000000..9c8f046281 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,7 @@ +[run] +branch = True +source = openstackclient +omit = openstackclient/openstack/* + +[report] +ignore-errors = True From fbc412e533bd7cb07c6d930e194f660e14b2319f Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 31 Jan 2013 19:30:25 -0600 Subject: [PATCH 0033/3614] Multiple API version support * Use multiple entry point groups to represent each API+version combination supported * Add some tests Try it out: * Right now only '* user' commands have multiple overlapping versions; you can see the selection between v2.0 and v3 by looking at the command help output for 'tenant' vs 'project': os --os-identity-api-version=2.0 help set user os --os-identity-api-version=3 help set user Change-Id: I7114fd246843df0243d354a7cce697810bb7de62 --- HACKING | 2 +- doc/source/commands.rst | 60 ++++++ openstackclient/common/commandmanager.py | 42 ++++ openstackclient/identity/v2_0/user.py | 4 +- openstackclient/identity/v3/user.py | 247 +++++++++++++++++++++++ openstackclient/shell.py | 72 ++++++- setup.py | 45 +++-- tests/{ => common}/test_clientmanager.py | 0 tests/common/test_commandmanager.py | 71 +++++++ tests/test_shell.py | 24 +++ 10 files changed, 540 insertions(+), 27 deletions(-) create mode 100644 doc/source/commands.rst create mode 100644 openstackclient/common/commandmanager.py create mode 100644 openstackclient/identity/v3/user.py rename tests/{ => common}/test_clientmanager.py (100%) create mode 100644 tests/common/test_commandmanager.py diff --git a/HACKING b/HACKING index e9bcb7eaf4..dd31ccd5d1 100644 --- a/HACKING +++ b/HACKING @@ -39,8 +39,8 @@ Human Alphabetical Order Examples import logging import random import StringIO + import testtools import time - import unittest from nova import flags from nova import test diff --git a/doc/source/commands.rst b/doc/source/commands.rst new file mode 100644 index 0000000000..40a2425803 --- /dev/null +++ b/doc/source/commands.rst @@ -0,0 +1,60 @@ +======== +Commands +======== + +Command Structure +================= + +OpenStack Client uses a command form ``verb object``. + +Note that 'object' here refers to the target of a command's action. In coding +discussions 'object' has its usual Python meaning. Go figure. + +Commands take the form:: + + openstack [] [] + +Command Arguments +----------------- + + * All long option names use two dashes ('--') as the prefix and a single dash + ('-') as the interpolation character. Some common options also have the + traditional single letter name prefixed by a single dash ('-'). + * Global options generally 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 can be derived from + the option name by dropping the leading '--', converting all embedded dashes + ('-') to underscores ('_'), and converting the name to upper case. + * Positional arguments trail command options. In commands that require two or + more objects be acted upon, such as 'attach A to B', both objects appear + as positional arguments. If they also appear in the command object they are + in the same order. + + +Implementation +============== + +The command structure is designed to support seamless addition of extension +command modules via entry points. The extensions are assumed to be subclasses +of Cliff's command.Command object. + +Command Entry Points +-------------------- + +Commands are added to the client using distribute's entry points in ``setup.py``. +There is a single common group ``openstack.cli`` for commands that are not versioned, +and a group for each combination of OpenStack API and version that is +supported. For example, to support Identity API v3 there is a group called +``openstack.identity.v3`` that contains the individual commands. The command +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 +``setup.py`` with:: + + 'openstack.identity.v3': [ + # ... + 'list_user=openstackclient.identity.v3.user:ListUser', + # ... + ], diff --git a/openstackclient/common/commandmanager.py b/openstackclient/common/commandmanager.py new file mode 100644 index 0000000000..e366034aab --- /dev/null +++ b/openstackclient/common/commandmanager.py @@ -0,0 +1,42 @@ +# 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. +# + +"""Modify Cliff's CommandManager""" + +import logging +import pkg_resources + +import cliff.commandmanager + + +LOG = logging.getLogger(__name__) + + +class CommandManager(cliff.commandmanager.CommandManager): + """Alters Cliff's default CommandManager behaviour to load additiona + command groups after initialization. + """ + def _load_commands(self, group=None): + if not group: + group = self.namespace + for ep in pkg_resources.iter_entry_points(group): + LOG.debug('found command %r' % ep.name) + self.commands[ep.name.replace('_', ' ')] = ep + return + + def add_command_group(self, group=None): + """Adds another group of command entrypoints""" + if group: + self._load_commands(group) diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index 2017e5e3d2..840cc50084 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -13,7 +13,7 @@ # under the License. # -"""User action implementations""" +"""Identity v2.0 User action implementations""" import logging @@ -126,7 +126,7 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) if parsed_args.long: - columns = ('ID', 'Name', 'TenantId', 'Email', 'Enabled') + columns = ('ID', 'Name', 'Tenant Id', 'Email', 'Enabled') else: columns = ('ID', 'Name') data = self.app.client_manager.identity.users.list() diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py new file mode 100644 index 0000000000..bf592d8117 --- /dev/null +++ b/openstackclient/identity/v3/user.py @@ -0,0 +1,247 @@ +# 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. +# + +"""Identity v3 User action implementations""" + +import logging + +from cliff import command +from cliff import lister +from cliff import show + +from openstackclient.common import utils + + +class CreateUser(show.ShowOne): + """Create user command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.CreateUser') + + def get_parser(self, prog_name): + parser = super(CreateUser, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar='', + help='New user name', + ) + parser.add_argument( + '--password', + metavar='', + help='New user password', + ) + parser.add_argument( + '--email', + metavar='', + help='New user email address', + ) + parser.add_argument( + '--project', + metavar='', + help='New default project name or ID', + ) + enable_group = parser.add_mutually_exclusive_group() + enable_group.add_argument( + '--enable', + dest='enabled', + action='store_true', + default=True, + help='Enable user', + ) + enable_group.add_argument( + '--disable', + dest='enabled', + action='store_false', + help='Disable user', + ) + 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.project: + project_id = utils.find_resource( + identity_client.projects, parsed_args.project).id + else: + project_id = None + user = identity_client.users.create( + parsed_args.name, + parsed_args.password, + parsed_args.email, + project_id=project_id, + enabled=parsed_args.enabled, + ) + + info = {} + info.update(user._info) + return zip(*sorted(info.iteritems())) + + +class DeleteUser(command.Command): + """Delete user command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.DeleteUser') + + def get_parser(self, prog_name): + parser = super(DeleteUser, self).get_parser(prog_name) + parser.add_argument( + 'user', + metavar='', + help='Name or ID of user to delete', + ) + 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) + identity_client.users.delete(user.id) + return + + +class ListUser(lister.Lister): + """List user command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.ListUser') + + def get_parser(self, prog_name): + parser = super(ListUser, self).get_parser(prog_name) + parser.add_argument( + '--project', + metavar='', + help='Name or ID of project to filter users', + ) + 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) + if parsed_args.long: + columns = ('ID', 'Name', 'Project Id', 'Email', 'Enabled') + else: + columns = ('ID', 'Name') + data = self.app.client_manager.identity.users.list() + return (columns, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data)) + + +class SetUser(command.Command): + """Set user command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.SetUser') + + def get_parser(self, prog_name): + parser = super(SetUser, self).get_parser(prog_name) + parser.add_argument( + 'user', + metavar='', + help='Name or ID of user to change', + ) + parser.add_argument( + '--name', + metavar='', + help='New user name', + ) + parser.add_argument( + '--password', + metavar='', + help='New user password', + ) + parser.add_argument( + '--email', + metavar='', + help='New user email address', + ) + parser.add_argument( + '--project', + metavar='', + help='New default project name or ID', + ) + enable_group = parser.add_mutually_exclusive_group() + enable_group.add_argument( + '--enable', + dest='enabled', + action='store_true', + default=True, + help='Enable user (default)', + ) + enable_group.add_argument( + '--disable', + dest='enabled', + action='store_false', + help='Disable user', + ) + 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) + kwargs = {} + if parsed_args.name: + kwargs['name'] = parsed_args.name + if parsed_args.email: + kwargs['email'] = parsed_args.email + if parsed_args.project: + project_id = utils.find_resource( + identity_client.projects, parsed_args.project).id + kwargs['projectId'] = project_id + if 'enabled' in parsed_args: + kwargs['enabled'] = parsed_args.enabled + + if not len(kwargs): + stdout.write("User not updated, no arguments present") + return + identity_client.users.update(user.id, **kwargs) + return + + +class ShowUser(show.ShowOne): + """Show user command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.ShowUser') + + def get_parser(self, prog_name): + parser = super(ShowUser, self).get_parser(prog_name) + parser.add_argument( + 'user', + metavar='', + help='Name or ID of user to display', + ) + 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) + + info = {} + info.update(user._info) + return zip(*sorted(info.iteritems())) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 5dc0457213..2654d658d8 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -21,17 +21,22 @@ import sys from cliff.app import App -from cliff.commandmanager import CommandManager +from cliff.help import HelpAction from openstackclient.common import clientmanager from openstackclient.common import exceptions as exc from openstackclient.common import openstackkeyring from openstackclient.common import utils +from openstackclient.common.commandmanager import CommandManager VERSION = '0.1' KEYRING_SERVICE = 'openstack' +DEFAULT_COMPUTE_API_VERSION = '2' +DEFAULT_IDENTITY_API_VERSION = '2.0' +DEFAULT_IMAGE_API_VERSION = '1.0' + def env(*vars, **kwargs): """Search for the first defined of possibly many env vars @@ -63,6 +68,35 @@ def __init__(self): # password flow auth self.auth_client = None + # NOTE(dtroyer): This hack changes the help action that Cliff + # automatically adds to the parser so we can defer + # its execution until after the api-versioned commands + # have been loaded. There doesn't seem to be a + # way to edit/remove anything from an existing parser. + + # Replace the cliff-added HelpAction to defer its execution + self.DeferredHelpAction = None + for a in self.parser._actions: + if type(a) == HelpAction: + # Found it, save and replace it + self.DeferredHelpAction = a + + # These steps are argparse-implementation-dependent + self.parser._actions.remove(a) + if self.parser._option_string_actions['-h']: + del self.parser._option_string_actions['-h'] + if self.parser._option_string_actions['--help']: + del self.parser._option_string_actions['--help'] + + # Make a new help option to just set a flag + self.parser.add_argument( + '-h', '--help', + action='store_true', + dest='deferred_help', + default=False, + help="show this help message and exit", + ) + def build_option_parser(self, description, version): parser = super(OpenStackShell, self).build_option_parser( description, @@ -102,20 +136,30 @@ def build_option_parser(self, description, version): parser.add_argument( '--os-identity-api-version', metavar='', - default=env('OS_IDENTITY_API_VERSION', default='2.0'), - help='Identity API version, default=2.0 ' - '(Env: OS_IDENTITY_API_VERSION)') + default=env( + 'OS_IDENTITY_API_VERSION', + default=DEFAULT_IDENTITY_API_VERSION), + help='Identity API version, default=' + + DEFAULT_IDENTITY_API_VERSION + + ' (Env: OS_IDENTITY_API_VERSION)') parser.add_argument( '--os-compute-api-version', metavar='', - default=env('OS_COMPUTE_API_VERSION', default='2'), - help='Compute API version, default=2 ' - '(Env: OS_COMPUTE_API_VERSION)') + default=env( + 'OS_COMPUTE_API_VERSION', + default=DEFAULT_COMPUTE_API_VERSION), + help='Compute API version, default=' + + DEFAULT_COMPUTE_API_VERSION + + ' (Env: OS_COMPUTE_API_VERSION)') parser.add_argument( '--os-image-api-version', metavar='', - default=env('OS_IMAGE_API_VERSION', default='1.0'), - help='Image API version, default=1.0 (Env: OS_IMAGE_API_VERSION)') + default=env( + 'OS_IMAGE_API_VERSION', + default=DEFAULT_IMAGE_API_VERSION), + help='Image API version, default=' + + DEFAULT_IMAGE_API_VERSION + + ' (Env: OS_IMAGE_API_VERSION)') parser.add_argument( '--os-token', metavar='', @@ -251,6 +295,16 @@ def initialize_app(self, argv): 'image': self.options.os_image_api_version, } + # Add the API version-specific commands + for api in self.api_version.keys(): + version = '.v' + self.api_version[api].replace('.', '_') + self.command_manager.add_command_group( + 'openstack.' + api + version) + + # Handle deferred help and exit + if self.options.deferred_help: + self.DeferredHelpAction(self.parser, self.parser, None, None) + # If the user is not asking for help, make sure they # have given us auth. cmd_name = None diff --git a/setup.py b/setup.py index 6ee3d45119..ffd72f7bce 100644 --- a/setup.py +++ b/setup.py @@ -55,6 +55,8 @@ def read(fname): entry_points={ 'console_scripts': ['openstack=openstackclient.shell:main'], 'openstack.cli': [ + ], + 'openstack.identity.v2_0': [ 'create_endpoint=' + 'openstackclient.identity.v2_0.endpoint:CreateEndpoint', 'delete_endpoint=' + @@ -73,16 +75,6 @@ def read(fname): 'remove_role=' + 'openstackclient.identity.v2_0.role:RemoveRole', 'show_role=openstackclient.identity.v2_0.role:ShowRole', - 'create_server=openstackclient.compute.v2.server:CreateServer', - 'delete_server=openstackclient.compute.v2.server:DeleteServer', - 'list_server=openstackclient.compute.v2.server:ListServer', - 'pause_server=openstackclient.compute.v2.server:PauseServer', - 'reboot_server=openstackclient.compute.v2.server:RebootServer', - 'rebuild_server=openstackclient.compute.v2.server:RebuildServer', - 'resume_server=openstackclient.compute.v2.server:ResumeServer', - 'show_server=openstackclient.compute.v2.server:ShowServer', - 'suspend_server=openstackclient.compute.v2.server:SuspendServer', - 'unpause_server=openstackclient.compute.v2.server:UnpauseServer', 'create_service=' + 'openstackclient.identity.v2_0.service:CreateService', 'delete_service=' + @@ -96,6 +88,7 @@ def read(fname): 'list_tenant=openstackclient.identity.v2_0.tenant:ListTenant', 'set_tenant=openstackclient.identity.v2_0.tenant:SetTenant', 'show_tenant=openstackclient.identity.v2_0.tenant:ShowTenant', + 'list_user-role=openstackclient.identity.v2_0.role:ListUserRole', 'create_user=' + 'openstackclient.identity.v2_0.user:CreateUser', 'delete_user=' + @@ -103,10 +96,8 @@ def read(fname): 'list_user=openstackclient.identity.v2_0.user:ListUser', 'set_user=openstackclient.identity.v2_0.user:SetUser', 'show_user=openstackclient.identity.v2_0.user:ShowUser', - 'list_user-role=openstackclient.identity.v2_0.role:ListUserRole', - 'list_image=openstackclient.image.v2.image:ListImage', - 'show_image=openstackclient.image.v2.image:ShowImage', - 'save_image=openstackclient.image.v2.image:SaveImage', + ], + 'openstack.identity.v3': [ 'create_group=openstackclient.identity.v3.group:CreateGroup', 'delete_group=openstackclient.identity.v3.group:DeleteGroup', 'set_group=openstackclient.identity.v3.group:SetGroup', @@ -119,6 +110,30 @@ def read(fname): 'set_project=openstackclient.identity.v3.project:SetProject', 'show_project=openstackclient.identity.v3.project:ShowProject', 'list_project=openstackclient.identity.v3.project:ListProject', - ] + 'create_user=' + + 'openstackclient.identity.v3.user:CreateUser', + 'delete_user=' + + 'openstackclient.identity.v3.user:DeleteUser', + 'list_user=openstackclient.identity.v3.user:ListUser', + 'set_user=openstackclient.identity.v3.user:SetUser', + 'show_user=openstackclient.identity.v3.user:ShowUser', + ], + 'openstack.image.v2': [ + 'list_image=openstackclient.image.v2.image:ListImage', + 'show_image=openstackclient.image.v2.image:ShowImage', + 'save_image=openstackclient.image.v2.image:SaveImage', + ], + 'openstack.compute.v2': [ + 'create_server=openstackclient.compute.v2.server:CreateServer', + 'delete_server=openstackclient.compute.v2.server:DeleteServer', + 'list_server=openstackclient.compute.v2.server:ListServer', + 'pause_server=openstackclient.compute.v2.server:PauseServer', + 'reboot_server=openstackclient.compute.v2.server:RebootServer', + 'rebuild_server=openstackclient.compute.v2.server:RebuildServer', + 'resume_server=openstackclient.compute.v2.server:ResumeServer', + 'show_server=openstackclient.compute.v2.server:ShowServer', + 'suspend_server=openstackclient.compute.v2.server:SuspendServer', + 'unpause_server=openstackclient.compute.v2.server:UnpauseServer', + ], } ) diff --git a/tests/test_clientmanager.py b/tests/common/test_clientmanager.py similarity index 100% rename from tests/test_clientmanager.py rename to tests/common/test_clientmanager.py diff --git a/tests/common/test_commandmanager.py b/tests/common/test_commandmanager.py new file mode 100644 index 0000000000..f0a0b3418d --- /dev/null +++ b/tests/common/test_commandmanager.py @@ -0,0 +1,71 @@ +# 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 commandmanager +from tests import utils + + +class FakeCommand(object): + @classmethod + def load(cls): + return cls + + def __init__(self): + return + +FAKE_CMD_ONE = FakeCommand +FAKE_CMD_TWO = FakeCommand +FAKE_CMD_ALPHA = FakeCommand +FAKE_CMD_BETA = FakeCommand + + +class FakeCommandManager(commandmanager.CommandManager): + commands = {} + + def _load_commands(self, group=None): + if not group: + self.commands['one'] = FAKE_CMD_ONE + self.commands['two'] = FAKE_CMD_TWO + else: + self.commands['alpha'] = FAKE_CMD_ALPHA + self.commands['beta'] = FAKE_CMD_BETA + + +class TestCommandManager(utils.TestCase): + def test_add_command_group(self): + mgr = FakeCommandManager('test') + + # Make sure add_command() still functions + mock_cmd_one = mock.Mock() + mgr.add_command('mock', mock_cmd_one) + cmd_mock, name, args = mgr.find_command(['mock']) + self.assertEqual(cmd_mock, mock_cmd_one) + + # Find a command added in initialization + cmd_one, name, args = mgr.find_command(['one']) + self.assertEqual(cmd_one, FAKE_CMD_ONE) + + # Load another command group + mgr.add_command_group('latin') + + # Find a new command + cmd_alpha, name, args = mgr.find_command(['alpha']) + self.assertEqual(cmd_alpha, FAKE_CMD_ALPHA) + + # Ensure that the original commands were not overwritten + cmd_two, name, args = mgr.find_command(['two']) + self.assertEqual(cmd_two, FAKE_CMD_TWO) diff --git a/tests/test_shell.py b/tests/test_shell.py index ac634c3235..d259785fa7 100644 --- a/tests/test_shell.py +++ b/tests/test_shell.py @@ -108,6 +108,30 @@ def _assert_cli(self, cmd_options, default_args): default_args["image_api_version"]) +class TestShellHelp(TestShell): + """Test the deferred help flag""" + def setUp(self): + super(TestShellHelp, self).setUp() + self.orig_env, os.environ = os.environ, {} + + def tearDown(self): + super(TestShellHelp, self).tearDown() + os.environ = self.orig_env + + def test_help_options(self): + flag = "-h list server" + kwargs = { + "deferred_help": True, + } + with mock.patch("openstackclient.shell.OpenStackShell.initialize_app", + self.app): + _shell, _cmd = make_shell(), flag + fake_execute(_shell, _cmd) + + self.assertEqual(_shell.options.deferred_help, + kwargs["deferred_help"]) + + class TestShellPasswordAuth(TestShell): def setUp(self): super(TestShellPasswordAuth, self).setUp() From 7072b4f802ff5567f0781f89f360c5be43a7977e Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Fri, 1 Feb 2013 01:12:05 -0600 Subject: [PATCH 0034/3614] Add Cinder API V1 Support made the changes suggested by dtroyer added client modified setup.py entry points updated pip required added support for create/delete/list volume types openstack list type openstack create type typeName openstack delete type typeNameOrId Change-Id: I43655de151582e37f14dc9550151a66db7a009ab --- openstackclient/common/clientmanager.py | 2 + openstackclient/shell.py | 11 +++ openstackclient/volume/__init__.py | 14 ++++ openstackclient/volume/client.py | 45 ++++++++++++ openstackclient/volume/v1/__init__.py | 14 ++++ openstackclient/volume/v1/type.py | 92 +++++++++++++++++++++++++ setup.py | 5 ++ tools/pip-requires | 1 + 8 files changed, 184 insertions(+) create mode 100644 openstackclient/volume/__init__.py create mode 100644 openstackclient/volume/client.py create mode 100644 openstackclient/volume/v1/__init__.py create mode 100644 openstackclient/volume/v1/type.py diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 830ecde709..a1b838a22e 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -20,6 +20,7 @@ from openstackclient.compute import client as compute_client from openstackclient.identity import client as identity_client from openstackclient.image import client as image_client +from openstackclient.volume import client as volume_client LOG = logging.getLogger(__name__) @@ -43,6 +44,7 @@ class ClientManager(object): compute = ClientCache(compute_client.make_client) identity = ClientCache(identity_client.make_client) image = ClientCache(image_client.make_client) + volume = ClientCache(volume_client.make_client) def __init__(self, token=None, url=None, auth_url=None, tenant_name=None, tenant_id=None, username=None, password=None, diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 2654d658d8..ffa0245a79 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -36,6 +36,7 @@ DEFAULT_COMPUTE_API_VERSION = '2' DEFAULT_IDENTITY_API_VERSION = '2.0' DEFAULT_IMAGE_API_VERSION = '1.0' +DEFAULT_VOLUME_API_VERSION = '1' def env(*vars, **kwargs): @@ -160,6 +161,15 @@ def build_option_parser(self, description, version): help='Image API version, default=' + DEFAULT_IMAGE_API_VERSION + ' (Env: OS_IMAGE_API_VERSION)') + parser.add_argument( + '--os-volume-api-version', + metavar='', + default=env( + 'OS_VOLUME_API_VERSION', + default=DEFAULT_VOLUME_API_VERSION), + help='Volume API version, default=' + + DEFAULT_VOLUME_API_VERSION + + ' (Env: OS_VOLUME_API_VERSION)') parser.add_argument( '--os-token', metavar='', @@ -293,6 +303,7 @@ def initialize_app(self, argv): 'compute': self.options.os_compute_api_version, 'identity': self.options.os_identity_api_version, 'image': self.options.os_image_api_version, + 'volume': self.options.os_volume_api_version, } # Add the API version-specific commands diff --git a/openstackclient/volume/__init__.py b/openstackclient/volume/__init__.py new file mode 100644 index 0000000000..85ac2501b8 --- /dev/null +++ b/openstackclient/volume/__init__.py @@ -0,0 +1,14 @@ +# 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 new file mode 100644 index 0000000000..c1acff2fe6 --- /dev/null +++ b/openstackclient/volume/client.py @@ -0,0 +1,45 @@ +# 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 logging + +from openstackclient.common import utils + + +LOG = logging.getLogger(__name__) + +API_NAME = "volume" +API_VERSIONS = { + "1": "cinderclient.v1.client.Client" +} + + +def make_client(instance): + """Returns a volume service client.""" + volume_client = utils.get_client_class( + API_NAME, + instance._api_version[API_NAME], + API_VERSIONS + ) + + LOG.debug('instantiating volume client') + client = volume_client( + username=instance._username, + api_key=instance._password, + project_id=instance._tenant_name, + auth_url=instance._auth_url, + ) + + return client diff --git a/openstackclient/volume/v1/__init__.py b/openstackclient/volume/v1/__init__.py new file mode 100644 index 0000000000..85ac2501b8 --- /dev/null +++ b/openstackclient/volume/v1/__init__.py @@ -0,0 +1,14 @@ +# 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/type.py b/openstackclient/volume/v1/type.py new file mode 100644 index 0000000000..124f67d844 --- /dev/null +++ b/openstackclient/volume/v1/type.py @@ -0,0 +1,92 @@ +# 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. +# + +"""Volume v1 Type action implementations""" + +import logging + +from cliff import command +from cliff import lister +from cliff import show + +from openstackclient.common import utils + + +class CreateType(show.ShowOne): + """Create type command""" + + api = 'volume' + log = logging.getLogger(__name__ + '.CreateType') + + def get_parser(self, prog_name): + parser = super(CreateType, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar='', + help='New type name', + ) + return parser + + def take_action(self, 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 + ) + + info = {} + info.update(volume_type._info) + return zip(*sorted(info.iteritems())) + + +class DeleteType(command.Command): + """Delete type command""" + + api = 'volume' + log = logging.getLogger(__name__ + '.DeleteType') + + def get_parser(self, prog_name): + parser = super(DeleteType, self).get_parser(prog_name) + parser.add_argument( + 'type', + metavar='', + help='Name or ID of type to delete', + ) + return parser + + def take_action(self, 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.type) + volume_client.volume_types.delete(volume_type.id) + return + + +class ListType(lister.Lister): + """List type command""" + + api = 'volume' + log = logging.getLogger(__name__ + '.ListType') + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + columns = ('ID', 'Name') + data = self.app.client_manager.volume.volume_types.list() + return (columns, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data)) diff --git a/setup.py b/setup.py index ffd72f7bce..0eda4b8bb9 100644 --- a/setup.py +++ b/setup.py @@ -135,5 +135,10 @@ def read(fname): 'suspend_server=openstackclient.compute.v2.server:SuspendServer', 'unpause_server=openstackclient.compute.v2.server:UnpauseServer', ], + 'openstack.volume.v1': [ + 'create_type=openstackclient.volume.v1.type:CreateType', + 'delete_type=openstackclient.volume.v1.type:DeleteType', + 'list_type=openstackclient.volume.v1.type:ListType', + ] } ) diff --git a/tools/pip-requires b/tools/pip-requires index af2c56ad64..720413ed74 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -4,3 +4,4 @@ pycrypto python-glanceclient>=0.5.1 python-keystoneclient>=0.2,<1.0 python-novaclient>=2 +python-cinderclient>=1 From 79001ce88a7c0a2197cad32ae84565822ad9b40e Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Mon, 11 Feb 2013 16:10:25 -0600 Subject: [PATCH 0035/3614] Add volume test cases and structure add basic unit test for client update/modify test_shell.py to include volume Change-Id: I7d08e15a2711da5e51590b8a82eca3a1234962f8 --- tests/test_shell.py | 11 ++++++-- tests/volume/__init__.py | 14 ++++++++++ tests/volume/test_volume.py | 51 +++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 tests/volume/__init__.py create mode 100644 tests/volume/test_volume.py diff --git a/tests/test_shell.py b/tests/test_shell.py index d259785fa7..6b9b07609f 100644 --- a/tests/test_shell.py +++ b/tests/test_shell.py @@ -32,10 +32,12 @@ DEFAULT_COMPUTE_API_VERSION = "2" DEFAULT_IDENTITY_API_VERSION = "2.0" DEFAULT_IMAGE_API_VERSION = "v2" +DEFAULT_VOLUME_API_VERSION = "1" LIB_COMPUTE_API_VERSION = "2" LIB_IDENTITY_API_VERSION = "2.0" LIB_IMAGE_API_VERSION = "1.0" +LIB_VOLUME_API_VERSION = "1" def make_shell(): @@ -106,6 +108,8 @@ def _assert_cli(self, cmd_options, default_args): default_args["identity_api_version"]) self.assertEqual(_shell.options.os_image_api_version, default_args["image_api_version"]) + self.assertEqual(_shell.options.os_volume_api_version, + default_args["volume_api_version"]) class TestShellHelp(TestShell): @@ -252,6 +256,7 @@ def setUp(self): "OS_COMPUTE_API_VERSION": DEFAULT_COMPUTE_API_VERSION, "OS_IDENTITY_API_VERSION": DEFAULT_IDENTITY_API_VERSION, "OS_IMAGE_API_VERSION": DEFAULT_IMAGE_API_VERSION, + "OS_VOLUME_API_VERSION": DEFAULT_VOLUME_API_VERSION, } self.orig_env, os.environ = os.environ, env.copy() @@ -271,7 +276,8 @@ def test_default_env(self): kwargs = { "compute_api_version": DEFAULT_COMPUTE_API_VERSION, "identity_api_version": DEFAULT_IDENTITY_API_VERSION, - "image_api_version": DEFAULT_IMAGE_API_VERSION + "image_api_version": DEFAULT_IMAGE_API_VERSION, + "volume_api_version": DEFAULT_VOLUME_API_VERSION } self._assert_cli(flag, kwargs) @@ -281,6 +287,7 @@ def test_empty_env(self): kwargs = { "compute_api_version": LIB_COMPUTE_API_VERSION, "identity_api_version": LIB_IDENTITY_API_VERSION, - "image_api_version": LIB_IMAGE_API_VERSION + "image_api_version": LIB_IMAGE_API_VERSION, + "volume_api_version": LIB_VOLUME_API_VERSION } self._assert_cli(flag, kwargs) diff --git a/tests/volume/__init__.py b/tests/volume/__init__.py new file mode 100644 index 0000000000..ebf59b327e --- /dev/null +++ b/tests/volume/__init__.py @@ -0,0 +1,14 @@ +# 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/tests/volume/test_volume.py b/tests/volume/test_volume.py new file mode 100644 index 0000000000..8c60dd1242 --- /dev/null +++ b/tests/volume/test_volume.py @@ -0,0 +1,51 @@ +# 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. +# + +import mock + +from openstackclient.common import clientmanager +from openstackclient.volume import client as volume_client +from tests import utils + + +AUTH_TOKEN = "foobar" +AUTH_URL = "http://0.0.0.0" + + +class FakeClient(object): + def __init__(self, endpoint=None, **kwargs): + self.client = mock.MagicMock() + self.client.auth_token = AUTH_TOKEN + self.client.auth_url = AUTH_URL + + +class TestVolume(utils.TestCase): + def setUp(self): + super(TestVolume, self).setUp() + + api_version = {"volume": "1"} + + volume_client.API_VERSIONS = { + "1": "tests.volume.test_volume.FakeClient" + } + + self.cm = clientmanager.ClientManager(token=AUTH_TOKEN, + url=AUTH_URL, + auth_url=AUTH_URL, + api_version=api_version) + + def test_make_client(self): + self.assertEqual(self.cm.volume.client.auth_token, AUTH_TOKEN) + self.assertEqual(self.cm.volume.client.auth_url, AUTH_URL) From 237f0dd612c426abdc35b9f21c4cca040e3f4264 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Mon, 18 Feb 2013 12:58:06 -0600 Subject: [PATCH 0036/3614] Correct the version mapping to image service. This was preventing image support from being activated. Change-Id: I1b7ab9174b90c55423b244ca63402d33b4411a49 --- openstackclient/image/client.py | 4 ++-- openstackclient/shell.py | 2 +- tests/test_shell.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openstackclient/image/client.py b/openstackclient/image/client.py index 23e9a3e9f5..8a63da9c76 100644 --- a/openstackclient/image/client.py +++ b/openstackclient/image/client.py @@ -22,8 +22,8 @@ API_NAME = "image" API_VERSIONS = { - # FIXME(jk0): Temporary 1.0 -> 2 mapping. - "1.0": "glanceclient.v2.client.Client" + "1.0": "glanceclient.v2.client.Client", + "2": "glanceclient.v2.client.Client" } diff --git a/openstackclient/shell.py b/openstackclient/shell.py index ffa0245a79..29f9f70a7d 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -35,7 +35,7 @@ DEFAULT_COMPUTE_API_VERSION = '2' DEFAULT_IDENTITY_API_VERSION = '2.0' -DEFAULT_IMAGE_API_VERSION = '1.0' +DEFAULT_IMAGE_API_VERSION = '2' DEFAULT_VOLUME_API_VERSION = '1' diff --git a/tests/test_shell.py b/tests/test_shell.py index 6b9b07609f..c33068d868 100644 --- a/tests/test_shell.py +++ b/tests/test_shell.py @@ -36,7 +36,7 @@ LIB_COMPUTE_API_VERSION = "2" LIB_IDENTITY_API_VERSION = "2.0" -LIB_IMAGE_API_VERSION = "1.0" +LIB_IMAGE_API_VERSION = "2" LIB_VOLUME_API_VERSION = "1" From ac8b4ce4ac03ef884fde4fed5be00e3f5b42df8e Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 19 Feb 2013 12:38:22 -0600 Subject: [PATCH 0037/3614] Added compute agent support. Change-Id: I818a2ea51a773f50da385cbdd71771a4ac923bd7 --- openstackclient/compute/v2/agent.py | 168 ++++++++++++++++++++++++++++ setup.py | 4 + 2 files changed, 172 insertions(+) create mode 100644 openstackclient/compute/v2/agent.py diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py new file mode 100644 index 0000000000..2af701a6fc --- /dev/null +++ b/openstackclient/compute/v2/agent.py @@ -0,0 +1,168 @@ +# 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. +# + +"""Agent action implementations""" + +import logging + +from cliff import command +from cliff import lister +from cliff import show + +from novaclient.v1_1 import agents +from openstackclient.common import utils + + +class CreateAgent(show.ShowOne): + """Create agent command""" + + api = "compute" + log = logging.getLogger(__name__ + ".CreateAgent") + + def get_parser(self, prog_name): + parser = super(CreateAgent, self).get_parser(prog_name) + parser.add_argument( + "os", + metavar="", + help="Type of OS") + parser.add_argument( + "architecture", + metavar="", + help="Type of architecture") + parser.add_argument( + "version", + metavar="", + help="Version") + parser.add_argument( + "url", + metavar="", + help="URL") + parser.add_argument( + "md5hash", + metavar="", + help="MD5 hash") + parser.add_argument( + "hypervisor", + metavar="", + help="Type of hypervisor", + default="xen") + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + compute_client = self.app.client_manager.compute + args = ( + parsed_args.os, + parsed_args.architecture, + parsed_args.version, + parsed_args.url, + parsed_args.md5hash, + parsed_args.hypervisor + ) + agent = compute_client.agents.create(*args)._info.copy() + return zip(*sorted(agent.iteritems())) + + +class DeleteAgent(command.Command): + """Delete agent command""" + + api = "compute" + log = logging.getLogger(__name__ + ".DeleteAgent") + + def get_parser(self, prog_name): + parser = super(DeleteAgent, self).get_parser(prog_name) + parser.add_argument( + "id", + metavar="", + help="ID of agent to delete") + return parser + + def take_action(self, 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""" + + api = "compute" + log = logging.getLogger(__name__ + ".ListAgent") + + def get_parser(self, prog_name): + parser = super(ListAgent, self).get_parser(prog_name) + parser.add_argument( + "--hypervisor", + metavar="", + help="Type of hypervisor") + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + compute_client = self.app.client_manager.compute + columns = ( + "Agent_ID", + "Hypervisor", + "OS", + "Architecture", + "Version", + "Md5Hash", + "URL" + ) + data = compute_client.agents.list(parsed_args.hypervisor) + return (columns, + (utils.get_item_properties( + s, columns, + ) for s in data)) + + +class SetAgent(show.ShowOne): + """Set agent command""" + + api = "compute" + log = logging.getLogger(__name__ + ".SetAgent") + + def get_parser(self, prog_name): + parser = super(SetAgent, self).get_parser(prog_name) + parser.add_argument( + "id", + metavar="", + help="ID of the agent build") + parser.add_argument( + "version", + metavar="", + help="Version of the agent") + parser.add_argument( + "url", + metavar="", + help="URL") + parser.add_argument( + "md5hash", + metavar="", + help="MD5 hash") + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + compute_client = self.app.client_manager.compute + args = ( + parsed_args.id, + parsed_args.version, + parsed_args.url, + parsed_args.md5hash + ) + agent = compute_client.agents.update(*args)._info.copy() + return zip(*sorted(agent.iteritems())) diff --git a/setup.py b/setup.py index 0eda4b8bb9..2e1b914f67 100644 --- a/setup.py +++ b/setup.py @@ -124,13 +124,17 @@ def read(fname): 'save_image=openstackclient.image.v2.image:SaveImage', ], 'openstack.compute.v2': [ + 'create_agent=openstackclient.compute.v2.agent:CreateAgent', 'create_server=openstackclient.compute.v2.server:CreateServer', + 'delete_agent=openstackclient.compute.v2.agent:DeleteAgent', 'delete_server=openstackclient.compute.v2.server:DeleteServer', + 'list_agent=openstackclient.compute.v2.agent:ListAgent', 'list_server=openstackclient.compute.v2.server:ListServer', 'pause_server=openstackclient.compute.v2.server:PauseServer', 'reboot_server=openstackclient.compute.v2.server:RebootServer', 'rebuild_server=openstackclient.compute.v2.server:RebuildServer', 'resume_server=openstackclient.compute.v2.server:ResumeServer', + 'set_agent=openstackclient.compute.v2.agent:SetAgent', 'show_server=openstackclient.compute.v2.server:ShowServer', 'suspend_server=openstackclient.compute.v2.server:SuspendServer', 'unpause_server=openstackclient.compute.v2.server:UnpauseServer', From 87375392a8fd4e6583734be5e5a31801ffa4016d Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Mon, 25 Feb 2013 11:57:28 -0600 Subject: [PATCH 0038/3614] Remove underscore. Change-Id: Iaf2791b96e81d6a0d4846adb3128e4dff61faf30 --- openstackclient/compute/v2/agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py index 2af701a6fc..05a0a94cd3 100644 --- a/openstackclient/compute/v2/agent.py +++ b/openstackclient/compute/v2/agent.py @@ -114,7 +114,7 @@ def take_action(self, parsed_args): self.log.debug("take_action(%s)" % parsed_args) compute_client = self.app.client_manager.compute columns = ( - "Agent_ID", + "Agent ID", "Hypervisor", "OS", "Architecture", From f67daad71667c85e4cb5621b8755dbed39b23304 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Mon, 25 Feb 2013 11:22:18 -0600 Subject: [PATCH 0039/3614] Added compute flavor support. Change-Id: Idb1eb2f838074ce5fb3d4aa7b72fd747ac6915c7 --- openstackclient/compute/v2/flavor.py | 186 +++++++++++++++++++++++++++ setup.py | 4 + 2 files changed, 190 insertions(+) create mode 100644 openstackclient/compute/v2/flavor.py diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py new file mode 100644 index 0000000000..40418132e7 --- /dev/null +++ b/openstackclient/compute/v2/flavor.py @@ -0,0 +1,186 @@ +# 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. +# + +"""Flavor action implementations""" + +import logging + +from cliff import command +from cliff import lister +from cliff import show + +from novaclient.v1_1 import flavors +from openstackclient.common import utils + + +class CreateFlavor(show.ShowOne): + """Create flavor command""" + + api = "compute" + log = logging.getLogger(__name__ + ".CreateFlavor") + + def get_parser(self, prog_name): + parser = super(CreateFlavor, self).get_parser(prog_name) + parser.add_argument( + "name", + metavar="", + help="Name of the new flavor") + parser.add_argument( + "id", + metavar="", + help="Unique ID (integer or UUID) for the new flavor." + " If specifying 'auto', a UUID will be generated as id") + parser.add_argument( + "ram", + type=int, + metavar="", + help="Memory size in MB") + parser.add_argument( + "disk", + type=int, + metavar="", + help="Disk size in GB") + parser.add_argument( + "--ephemeral", + type=int, + metavar="", + help="Ephemeral space size in GB (default 0)", + default=0) + parser.add_argument( + "vcpus", + type=int, + metavar="", + help="Number of vcpus") + parser.add_argument( + "--swap", + type=int, + metavar="", + help="Swap space size in MB (default 0)", + default=0) + parser.add_argument( + "--rxtx-factor", + type=int, + metavar="", + help="RX/TX factor (default 1)", + default=1) + public_group = parser.add_mutually_exclusive_group() + public_group.add_argument( + "--public", + dest="public", + default=True, + help="Make flavor inaccessible to the public (default)", + action="store_true") + public_group.add_argument( + "--private", + dest="public", + help="Make flavor inaccessible to the public", + action="store_false") + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + compute_client = self.app.client_manager.compute + + args = ( + parsed_args.name, + parsed_args.ram, + parsed_args.vcpus, + parsed_args.disk, + parsed_args.id, + parsed_args.ephemeral, + parsed_args.swap, + parsed_args.rxtx_factor, + parsed_args.public + ) + + flavor = compute_client.flavors.create(*args)._info.copy() + flavor.pop("links") + + return zip(*sorted(flavor.iteritems())) + + +class DeleteFlavor(command.Command): + """Delete flavor command""" + + api = "compute" + log = logging.getLogger(__name__ + ".DeleteFlavor") + + def get_parser(self, prog_name): + parser = super(DeleteFlavor, self).get_parser(prog_name) + parser.add_argument( + "flavor", + metavar="", + help="Name or ID of flavor to delete") + return parser + + def take_action(self, 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) + compute_client.flavors.delete(flavor.id) + return + + +class ListFlavor(lister.Lister): + """List flavor command""" + + api = "compute" + log = logging.getLogger(__name__ + ".ListFlavor") + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + compute_client = self.app.client_manager.compute + columns = ( + "ID", + "Name", + "RAM", + "Disk", + "Ephemeral", + "Swap", + "VCPUs", + "RXTX Factor", + "Is Public", + "Extra Specs" + ) + data = compute_client.flavors.list() + return (columns, + (utils.get_item_properties( + s, columns, + ) for s in data)) + + +class ShowFlavor(show.ShowOne): + """Show flavor command""" + + api = "compute" + log = logging.getLogger(__name__ + ".ShowFlavor") + + def get_parser(self, prog_name): + parser = super(ShowFlavor, self).get_parser(prog_name) + parser.add_argument( + "flavor", + metavar="", + help="Name or ID of flavor to display") + return parser + + def take_action(self, 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() + flavor.pop("links") + + return zip(*sorted(flavor.iteritems())) diff --git a/setup.py b/setup.py index 2e1b914f67..3ad427f5bc 100644 --- a/setup.py +++ b/setup.py @@ -125,16 +125,20 @@ def read(fname): ], 'openstack.compute.v2': [ 'create_agent=openstackclient.compute.v2.agent:CreateAgent', + 'create_flavor=openstackclient.compute.v2.flavor:CreateFlavor', 'create_server=openstackclient.compute.v2.server:CreateServer', 'delete_agent=openstackclient.compute.v2.agent:DeleteAgent', + 'delete_flavor=openstackclient.compute.v2.flavor:DeleteFlavor', 'delete_server=openstackclient.compute.v2.server:DeleteServer', 'list_agent=openstackclient.compute.v2.agent:ListAgent', + 'list_flavor=openstackclient.compute.v2.flavor:ListFlavor', 'list_server=openstackclient.compute.v2.server:ListServer', 'pause_server=openstackclient.compute.v2.server:PauseServer', 'reboot_server=openstackclient.compute.v2.server:RebootServer', 'rebuild_server=openstackclient.compute.v2.server:RebuildServer', 'resume_server=openstackclient.compute.v2.server:ResumeServer', 'set_agent=openstackclient.compute.v2.agent:SetAgent', + 'show_flavor=openstackclient.compute.v2.flavor:ShowFlavor', 'show_server=openstackclient.compute.v2.server:ShowServer', 'suspend_server=openstackclient.compute.v2.server:SuspendServer', 'unpause_server=openstackclient.compute.v2.server:UnpauseServer', From ed809e059a85f70e8f89d1a59e533064b9e9143e Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sun, 3 Mar 2013 00:42:42 -0600 Subject: [PATCH 0040/3614] Add quota v1 support for volume added dhellmann's suggestions created entry points in setup.py added show/set/list quotas for v1 volume Change-Id: I8ed6a9518007b31cafeaa70a54d5bf54a549195b --- openstackclient/volume/v1/quota.py | 113 +++++++++++++++++++++++++++++ setup.py | 3 + 2 files changed, 116 insertions(+) create mode 100644 openstackclient/volume/v1/quota.py diff --git a/openstackclient/volume/v1/quota.py b/openstackclient/volume/v1/quota.py new file mode 100644 index 0000000000..ae6c50f5e3 --- /dev/null +++ b/openstackclient/volume/v1/quota.py @@ -0,0 +1,113 @@ +# 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. +# + +"""Volume v1 Quota action implementations""" + +import logging + +from cliff import command +from cliff import show + +from openstackclient.common import utils + + +class ListQuota(show.ShowOne): + """List quota command""" + + api = 'volume' + log = logging.getLogger(__name__ + '.ListQuota') + + def get_parser(self, prog_name): + parser = super(ListQuota, self).get_parser(prog_name) + parser.add_argument( + 'tenant', + metavar='', + help='ID of tenant to list the default quotas for') + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + volume_client = self.app.client_manager.volume + defaults = volume_client.quotas.defaults(parsed_args.tenant) + + return zip(*sorted(defaults._info.iteritems())) + + +class SetQuota(command.Command): + """Set quota command""" + + api = 'volume' + log = logging.getLogger(__name__ + '.SetQuota') + + def get_parser(self, prog_name): + parser = super(SetQuota, self).get_parser(prog_name) + parser.add_argument( + 'tenant', + metavar='', + help='ID of tenant to set the quotas for') + parser.add_argument( + '--volumes', + metavar='', + type=int, + help='New value for the volumes quota') + parser.add_argument( + '--gigabytes', + metavar='', + type=int, + help='New value for the gigabytes quota') + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + kwargs = {} + if parsed_args.volumes: + kwargs['volumes'] = parsed_args.volumes + if parsed_args.gigabytes: + kwargs['gigabytes'] = parsed_args.gigabytes + + if kwargs == {}: + stdout.write("Quota not updated, no arguments present") + return + + volume_client = self.app.client_manager.volume + volume_client.quotas.update(parsed_args.tenant, + parsed_args.volumes, + parsed_args.gigabytes) + + return + + +class ShowQuota(show.ShowOne): + """Show quota command""" + + api = 'volume' + log = logging.getLogger(__name__ + '.ShowQuota') + + def get_parser(self, prog_name): + parser = super(ShowQuota, self).get_parser(prog_name) + parser.add_argument( + 'tenant', + metavar='', + help='ID of tenant to list the quotas for') + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + volume_client = self.app.client_manager.volume + quota = utils.find_resource(volume_client.quotas, + parsed_args.tenant) + + return zip(*sorted(quota._info.iteritems())) diff --git a/setup.py b/setup.py index 3ad427f5bc..6f3e407931 100644 --- a/setup.py +++ b/setup.py @@ -147,6 +147,9 @@ def read(fname): 'create_type=openstackclient.volume.v1.type:CreateType', 'delete_type=openstackclient.volume.v1.type:DeleteType', 'list_type=openstackclient.volume.v1.type:ListType', + 'show_quota=openstackclient.volume.v1.quota:ShowQuota', + 'list_quota=openstackclient.volume.v1.quota:ListQuota', + 'set_quota=openstackclient.volume.v1.quota:SetQuota', ] } ) From 7266c695b3bc592558a1170bd00e5904fa3dd303 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 5 Mar 2013 10:30:36 -0600 Subject: [PATCH 0041/3614] Added compute service support. Change-Id: I548e35396534b5498f31a45c752984f6d33357c3 --- openstackclient/compute/v2/service.py | 111 ++++++++++++++++++++++++++ setup.py | 4 + 2 files changed, 115 insertions(+) create mode 100644 openstackclient/compute/v2/service.py diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py new file mode 100644 index 0000000000..3bd7481499 --- /dev/null +++ b/openstackclient/compute/v2/service.py @@ -0,0 +1,111 @@ +# 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. +# + +"""Service action implementations""" + +import logging + +from cliff import lister + +from novaclient.v1_1 import services +from openstackclient.common import utils + + +class ListService(lister.Lister): + """List service command""" + + api = "compute" + log = logging.getLogger(__name__ + ".ListService") + + def get_parser(self, prog_name): + parser = super(ListService, self).get_parser(prog_name) + parser.add_argument( + "--host", + metavar="", + help="Name of host") + parser.add_argument( + "--service", + metavar="", + help="Name of service") + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + compute_client = self.app.client_manager.compute + columns = ( + "Binary", + "Host", + "Zone", + "Status", + "State", + "Updated At" + ) + data = compute_client.services.list(parsed_args.host, + parsed_args.service) + return (columns, + (utils.get_item_properties( + s, columns, + ) for s in data)) + + +class SetService(lister.Lister): + """Set service command""" + + api = "compute" + log = logging.getLogger(__name__ + ".SetService") + + def get_parser(self, prog_name): + parser = super(SetService, self).get_parser(prog_name) + parser.add_argument( + "host", + metavar="", + help="Name of host") + parser.add_argument( + "service", + metavar="", + help="Name of service") + enabled_group = parser.add_mutually_exclusive_group() + enabled_group.add_argument( + "--enable", + dest="enabled", + default=True, + help="Enable a service", + action="store_true") + enabled_group.add_argument( + "--disable", + dest="enabled", + help="Disable a service", + action="store_false") + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + compute_client = self.app.client_manager.compute + columns = ( + "Host", + "Service", + "Disabled" + ) + + if parsed_args.enabled: + action = compute_client.services.enable + else: + action = compute_client.services.disable + + data = action(parsed_args.host, parsed_args.service) + return (columns, + (utils.get_item_properties( + s, columns, + ) for s in data)) diff --git a/setup.py b/setup.py index 3ad427f5bc..bf4c8bb96c 100644 --- a/setup.py +++ b/setup.py @@ -133,12 +133,16 @@ def read(fname): 'list_agent=openstackclient.compute.v2.agent:ListAgent', 'list_flavor=openstackclient.compute.v2.flavor:ListFlavor', 'list_server=openstackclient.compute.v2.server:ListServer', + 'list_compute-service=' + + 'openstackclient.compute.v2.service:ListService', 'pause_server=openstackclient.compute.v2.server:PauseServer', 'reboot_server=openstackclient.compute.v2.server:RebootServer', 'rebuild_server=openstackclient.compute.v2.server:RebuildServer', 'resume_server=openstackclient.compute.v2.server:ResumeServer', 'set_agent=openstackclient.compute.v2.agent:SetAgent', 'show_flavor=openstackclient.compute.v2.flavor:ShowFlavor', + 'set_compute-service=' + + 'openstackclient.compute.v2.service:SetService', 'show_server=openstackclient.compute.v2.server:ShowServer', 'suspend_server=openstackclient.compute.v2.server:SuspendServer', 'unpause_server=openstackclient.compute.v2.server:UnpauseServer', From 5377da34065afdf96f2dd873419d77583844dfdd Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sun, 3 Mar 2013 20:34:18 -0600 Subject: [PATCH 0042/3614] Add metadata support for volume type rebase added set and unset metadata Change-Id: I8d969e1de305975d7c71a0bad3a62b15f423c3e0 --- openstackclient/volume/v1/type.py | 67 +++++++++++++++++++++++++++++++ setup.py | 2 + 2 files changed, 69 insertions(+) diff --git a/openstackclient/volume/v1/type.py b/openstackclient/volume/v1/type.py index 124f67d844..9f4d3df445 100644 --- a/openstackclient/volume/v1/type.py +++ b/openstackclient/volume/v1/type.py @@ -90,3 +90,70 @@ def take_action(self, parsed_args): s, columns, formatters={}, ) for s in data)) + + +class SetType(command.Command): + """Set type command""" + + api = 'volume' + log = logging.getLogger(__name__ + '.SetType') + + def get_parser(self, prog_name): + parser = super(SetType, self).get_parser(prog_name) + parser.add_argument( + 'type', + metavar='', + help='Type ID to update', + ) + parser.add_argument( + 'meta_data', + metavar='', + help='meta-data to add to volume type', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + meta = dict(v.split('=') for v in parsed_args.meta_data.split(' ')) + volume_client = self.app.client_manager.volume + volume_type = volume_client.volume_types.get( + parsed_args.type + ) + + volume_type.set_keys(meta) + + return + + +class UnsetType(command.Command): + """Unset type command""" + + api = 'volume' + log = logging.getLogger(__name__ + '.UnsetType') + + def get_parser(self, prog_name): + parser = super(UnsetType, self).get_parser(prog_name) + parser.add_argument( + 'type', + metavar='', + help='Type ID to update', + ) + parser.add_argument( + 'meta_data', + metavar='', + help='meta-data to remove from volume type (key only)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + volume_client = self.app.client_manager.volume + volume_type = volume_client.volume_types.get( + parsed_args.type + ) + key_list = [] + key_list.append(parsed_args.meta_data) + volume_type.unset_keys(key_list) + + return diff --git a/setup.py b/setup.py index 15a41eb7eb..e4ea3f93d3 100644 --- a/setup.py +++ b/setup.py @@ -151,6 +151,8 @@ def read(fname): 'create_type=openstackclient.volume.v1.type:CreateType', 'delete_type=openstackclient.volume.v1.type:DeleteType', 'list_type=openstackclient.volume.v1.type:ListType', + 'set_type=openstackclient.volume.v1.type:SetType', + 'unset_type=openstackclient.volume.v1.type:UnsetType', 'show_quota=openstackclient.volume.v1.quota:ShowQuota', 'list_quota=openstackclient.volume.v1.quota:ListQuota', 'set_quota=openstackclient.volume.v1.quota:SetQuota', From 989485c7f9f685fc2d0fc2c6e7786a087a6042ac Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Sun, 24 Feb 2013 20:00:39 -0600 Subject: [PATCH 0043/3614] Change create flavor to use default arguments Most of the arguments required by the create flavor API can have reasonable defaults and therefore can be made optional in the CLI. This brings create flavor in line with the documented args in the wiki at https://wiki.openstack.org/wiki/UnifiedCLI/Mapping#flavor Change-Id: Iecb3baf72f9dc3981742ff7989780894e37921c9 --- openstackclient/compute/v2/flavor.py | 48 +++++++++++++++------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 40418132e7..0b48da86de 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -38,37 +38,41 @@ def get_parser(self, prog_name): metavar="", help="Name of the new flavor") parser.add_argument( - "id", + "--id", metavar="", - help="Unique ID (integer or UUID) for the new flavor." - " If specifying 'auto', a UUID will be generated as id") + default='auto', + help="Unique flavor ID; 'auto' will create a UUID " + "(default: auto)") parser.add_argument( - "ram", + "--ram", type=int, - metavar="", - help="Memory size in MB") + metavar="", + default=256, + help="Memory size in MB (default 256M)") parser.add_argument( - "disk", + "--disk", type=int, - metavar="", - help="Disk size in GB") + metavar="", + default=0, + help="Disk size in GB (default 0G)") parser.add_argument( "--ephemeral", type=int, - metavar="", - help="Ephemeral space size in GB (default 0)", + metavar="", + help="Ephemeral disk size in GB (default 0G)", default=0) - parser.add_argument( - "vcpus", - type=int, - metavar="", - help="Number of vcpus") parser.add_argument( "--swap", type=int, - metavar="", - help="Swap space size in MB (default 0)", + metavar="", + help="Swap space size in GB (default 0G)", default=0) + parser.add_argument( + "--vcpus", + type=int, + metavar="", + default=1, + help="Number of vcpus (default 1)") parser.add_argument( "--rxtx-factor", type=int, @@ -79,14 +83,14 @@ def get_parser(self, prog_name): public_group.add_argument( "--public", dest="public", + action="store_true", default=True, - help="Make flavor inaccessible to the public (default)", - action="store_true") + help="Flavor is accessible to the public (default)") public_group.add_argument( "--private", dest="public", - help="Make flavor inaccessible to the public", - action="store_false") + action="store_false", + help="Flavor is inaccessible to the public") return parser def take_action(self, parsed_args): From 3b765c82531d9c5704854443e49f1accb5995a0e Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 6 Mar 2013 14:39:48 -0600 Subject: [PATCH 0044/3614] Clean up args and help strings in server commands Help string corrections and align args with wiki at https://wiki.openstack.org/wiki/UnifiedCLI/Mapping#server Change-Id: Ifd6f587d383534142003277e64532f77ecb37106 --- openstackclient/compute/v2/server.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 24566a665f..898614b567 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -165,11 +165,11 @@ def get_parser(self, prog_name): parser.add_argument( '--user-data', metavar='', - help='User data file to be serverd by the metadata server') + help='User data file to serve from the metadata server') parser.add_argument( '--availability-zone', metavar='', - help='Keypair to inject into this server') + help='Select an availability zone for the server') parser.add_argument( '--block-device-mapping', metavar='', @@ -212,7 +212,7 @@ def get_parser(self, prog_name): '--wait', dest='wait', action='store_true', - help='Wait for server to become active to return') + help='Wait for servers to become active') return parser def take_action(self, parsed_args): @@ -362,10 +362,6 @@ def get_parser(self, prog_name): '--name', metavar='', help='regular expression to match name') - parser.add_argument( - '--instance-name', - metavar='', - help='regular expression to match instance name') parser.add_argument( '--status', metavar='', @@ -383,6 +379,10 @@ def get_parser(self, prog_name): '--host', metavar='', help='search by hostname') + parser.add_argument( + '--instance-name', + metavar='', + help='regular expression to match instance name (admin only)') parser.add_argument( '--all-tenants', action='store_true', @@ -505,8 +505,8 @@ def get_parser(self, prog_name): required=True, help='Recreate server from this image') parser.add_argument( - '--rebuild-password', - metavar='', + '--password', + metavar='', default=False, help="Set the provided password on the rebuild instance") parser.add_argument( From 47068b1dd771f8b3ebce4c52f1de489cb30651e3 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Wed, 6 Mar 2013 15:18:04 -0600 Subject: [PATCH 0045/3614] Add compute hosts support. Change-Id: I51c443512a82729564b76e6f835195ff193232d2 --- openstackclient/compute/v2/host.py | 83 ++++++++++++++++++++++++++++++ setup.py | 4 +- 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 openstackclient/compute/v2/host.py diff --git a/openstackclient/compute/v2/host.py b/openstackclient/compute/v2/host.py new file mode 100644 index 0000000000..e74a0ade7c --- /dev/null +++ b/openstackclient/compute/v2/host.py @@ -0,0 +1,83 @@ +# 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. +# + +"""Host action implementations""" + +import logging + +from cliff import lister + +from novaclient.v1_1 import hosts +from openstackclient.common import utils + + +class ListHost(lister.Lister): + """List host command""" + + api = "compute" + log = logging.getLogger(__name__ + ".ListHost") + + def get_parser(self, prog_name): + parser = super(ListHost, self).get_parser(prog_name) + parser.add_argument( + "--zone", + metavar="", + help="Only return hosts in the availability zone.") + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + compute_client = self.app.client_manager.compute + columns = ( + "Host Name", + "Service", + "Zone" + ) + data = compute_client.hosts.list_all(parsed_args.zone) + return (columns, + (utils.get_item_properties( + s, columns, + ) for s in data)) + + +class ShowHost(lister.Lister): + """Show host command""" + + api = "compute" + log = logging.getLogger(__name__ + ".ShowHost") + + def get_parser(self, prog_name): + parser = super(ShowHost, self).get_parser(prog_name) + parser.add_argument( + "host", + metavar="", + help="Name of host") + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + compute_client = self.app.client_manager.compute + columns = ( + "Host", + "Project", + "CPU", + "Memory MB", + "Disk GB" + ) + data = compute_client.hosts.get(parsed_args.host) + return (columns, + (utils.get_item_properties( + s, columns, + ) for s in data)) diff --git a/setup.py b/setup.py index e4ea3f93d3..f7acedf440 100644 --- a/setup.py +++ b/setup.py @@ -132,6 +132,7 @@ def read(fname): 'delete_server=openstackclient.compute.v2.server:DeleteServer', 'list_agent=openstackclient.compute.v2.agent:ListAgent', 'list_flavor=openstackclient.compute.v2.flavor:ListFlavor', + 'list_host=openstackclient.compute.v2.host:ListHost', 'list_server=openstackclient.compute.v2.server:ListServer', 'list_compute-service=' + 'openstackclient.compute.v2.service:ListService', @@ -140,9 +141,10 @@ def read(fname): 'rebuild_server=openstackclient.compute.v2.server:RebuildServer', 'resume_server=openstackclient.compute.v2.server:ResumeServer', 'set_agent=openstackclient.compute.v2.agent:SetAgent', - 'show_flavor=openstackclient.compute.v2.flavor:ShowFlavor', 'set_compute-service=' + 'openstackclient.compute.v2.service:SetService', + 'show_flavor=openstackclient.compute.v2.flavor:ShowFlavor', + 'show_host=openstackclient.compute.v2.host:ShowHost', 'show_server=openstackclient.compute.v2.server:ShowServer', 'suspend_server=openstackclient.compute.v2.server:SuspendServer', 'unpause_server=openstackclient.compute.v2.server:UnpauseServer', From abbbed534d296c6fe1824ee010ead65292cc3a58 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Fri, 1 Mar 2013 16:36:08 -0600 Subject: [PATCH 0046/3614] Add volume support for openstack client fix with dtroyer's comments rebase fix with dhellmann's comments create/list/delete/show/set for volume commands Change-Id: Id8236685d815dbf73873bab2363d82274a9aa556 --- openstackclient/volume/v1/volume.py | 216 ++++++++++++++++++++++++++++ setup.py | 5 + 2 files changed, 221 insertions(+) create mode 100644 openstackclient/volume/v1/volume.py diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py new file mode 100644 index 0000000000..f2d0851078 --- /dev/null +++ b/openstackclient/volume/v1/volume.py @@ -0,0 +1,216 @@ +# 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. +# + +"""Volume v1 Volume action implementations""" + +import logging +import sys + +from cliff import command +from cliff import lister +from cliff import show + +from openstackclient.common import utils + + +class CreateVolume(show.ShowOne): + """Create volume command""" + + api = 'volume' + log = logging.getLogger(__name__ + '.CreateVolume') + + def get_parser(self, prog_name): + parser = super(CreateVolume, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar='', + help='Name of the volume', + ) + parser.add_argument( + '--size', + metavar='', + required=True, + type=int, + help='New volume size', + ) + parser.add_argument( + '--snapshot-id', + metavar='', + help='ID of the snapshot', + ) + parser.add_argument( + '--description', + metavar='', + help='Description of the volume', + ) + parser.add_argument( + '--volume-type', + metavar='', + help='Type of volume', + ) + parser.add_argument( + '--user-id', + metavar='', + help='User id derived from context', + ) + parser.add_argument( + '--project-id', + metavar='', + help='Project id derived from context', + ) + parser.add_argument( + '--availability-zone', + metavar='', + help='Availability Zone to use', + ) + parser.add_argument( + '--metadata', + metavar='', + help='Optional metadata to set on volume creation', + ) + parser.add_argument( + '--image-ref', + metavar='', + help='reference to an image stored in glance', + ) + parser.add_argument( + '--source-volid', + metavar='', + help='ID of source volume to clone from', + ) + + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + volume_client = self.app.client_manager.volume + volume = volume_client.volumes.create( + parsed_args.size, + parsed_args.snapshot_id, + parsed_args.source_volid, + parsed_args.name, + parsed_args.description, + parsed_args.volume_type, + parsed_args.user_id, + parsed_args.project_id, + parsed_args.availability_zone, + parsed_args.metadata, + parsed_args.image_ref + ) + + return zip(*sorted(volume._info.iteritems())) + + +class DeleteVolume(command.Command): + """Delete volume command""" + + api = 'volume' + log = logging.getLogger(__name__ + '.DeleteVolume') + + def get_parser(self, prog_name): + parser = super(DeleteVolume, self).get_parser(prog_name) + parser.add_argument( + 'volume', + metavar='', + help='ID of volume to delete', + ) + return parser + + def take_action(self, 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) + volume_client.volumes.delete(volume.id) + return + + +class ListVolume(lister.Lister): + """List volume command""" + + api = 'volume' + log = logging.getLogger(__name__ + '.ListVolume') + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + columns = ('ID', 'Status', 'Display Name', 'Size', 'Volume Type') + data = self.app.client_manager.volume.volumes.list() + return (columns, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data)) + + +class SetVolume(command.Command): + """Set volume command""" + + api = 'volume' + log = logging.getLogger(__name__ + '.SetVolume') + + def get_parser(self, prog_name): + parser = super(SetVolume, self).get_parser(prog_name) + parser.add_argument( + 'volume', + metavar='', + help='ID of volume to change') + parser.add_argument( + '--name', + metavar='', + help='New volume name') + parser.add_argument( + '--description', + metavar='', + help='New volume description') + return parser + + def take_action(self, 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) + kwargs = {} + if parsed_args.name: + kwargs['display_name'] = parsed_args.name + if parsed_args.description: + kwargs['display_description'] = parsed_args.description + + if not kwargs: + sys.stdout.write("Volume not updated, no arguments present \n") + return + volume_client.volumes.update(volume.id, **kwargs) + return + + +class ShowVolume(show.ShowOne): + """Show volume command""" + + api = 'volume' + log = logging.getLogger(__name__ + '.ShowVolume') + + def get_parser(self, prog_name): + parser = super(ShowVolume, self).get_parser(prog_name) + parser.add_argument( + 'volume', + metavar='', + help='ID of volume to display') + return parser + + def take_action(self, 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) + + return zip(*sorted(volume._info.iteritems())) diff --git a/setup.py b/setup.py index e4ea3f93d3..922d103bbd 100644 --- a/setup.py +++ b/setup.py @@ -156,6 +156,11 @@ def read(fname): 'show_quota=openstackclient.volume.v1.quota:ShowQuota', 'list_quota=openstackclient.volume.v1.quota:ListQuota', 'set_quota=openstackclient.volume.v1.quota:SetQuota', + 'create_volume=openstackclient.volume.v1.volume:CreateVolume', + 'delete_volume=openstackclient.volume.v1.volume:DeleteVolume', + 'list_volume=openstackclient.volume.v1.volume:ListVolume', + 'set_volume=openstackclient.volume.v1.volume:SetVolume', + 'show_volume=openstackclient.volume.v1.volume:ShowVolume', ] } ) From 55462e8f561af31dbd9a34d317758916d1287d72 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Wed, 30 Jan 2013 18:56:47 -0600 Subject: [PATCH 0047/3614] add domain, credential to identity v3 api moved policy.py to it's own patch minor changes based on comments reworked it now that the domain specs have changed added credential - create, delete, update, show, list added domain - create, delete, update, show, list update setup.py entry points Change-Id: I6bbbb8fdfbc3e76ba75374e9579eb92c96c928fe --- openstackclient/identity/v3/credential.py | 199 ++++++++++++++++++++++ openstackclient/identity/v3/domain.py | 193 +++++++++++++++++++++ setup.py | 15 ++ 3 files changed, 407 insertions(+) create mode 100644 openstackclient/identity/v3/credential.py create mode 100644 openstackclient/identity/v3/domain.py diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py new file mode 100644 index 0000000000..c98260526e --- /dev/null +++ b/openstackclient/identity/v3/credential.py @@ -0,0 +1,199 @@ +# 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. +# + +"""Identity v3 Credential action implementations""" + +import logging +import sys + +from cliff import command +from cliff import lister +from cliff import show + +from openstackclient.common import utils + + +class CreateCredential(show.ShowOne): + """Create credential command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.CreateCredential') + + def get_parser(self, prog_name): + parser = super(CreateCredential, self).get_parser(prog_name) + parser.add_argument( + 'user', + metavar='', + help='Name or ID of user that owns the credential', + ) + parser.add_argument( + '--type', + default="cert", + metavar='', + choices=['ec2', 'cert'], + help='New credential type', + ) + parser.add_argument( + 'data', + metavar='', + help='New credential data', + ) + parser.add_argument( + '--project', + metavar='', + help='Project name or ID which limits the scope of the credential', + ) + return parser + + def take_action(self, 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 + if parsed_args.project: + project = utils.find_resource(identity_client.projects, + parsed_args.project).id + else: + project = None + credential = identity_client.credentials.create( + user_id, + parsed_args.type, + parsed_args.data, + project=project) + + return zip(*sorted(credential._info.iteritems())) + + +class DeleteCredential(command.Command): + """Delete credential command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.DeleteCredential') + + def get_parser(self, prog_name): + parser = super(DeleteCredential, self).get_parser(prog_name) + parser.add_argument( + 'credential', + metavar='', + help='ID of credential to delete', + ) + 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.credentials.delete(parsed_args.credential) + return + + +class ListCredential(lister.Lister): + """List credential command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.ListCredential') + + def take_action(self, 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, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data)) + + +class SetCredential(command.Command): + """Set credential command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.SetCredential') + + def get_parser(self, prog_name): + parser = super(SetCredential, self).get_parser(prog_name) + parser.add_argument( + 'credential', + metavar='', + help='ID of credential to change', + ) + parser.add_argument( + '--user', + metavar='', + help='Name or ID of user that owns the credential', + ) + parser.add_argument( + '--type', + metavar='', + choices=['ec2', 'cert'], + help='New credential type', + ) + parser.add_argument( + '--data', + metavar='', + help='New credential data', + ) + parser.add_argument( + '--project', + metavar='', + help='Project name or ID which limits the scope of the credential', + ) + return parser + + def take_action(self, 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 + kwargs = {} + if user_id: + kwargs['user'] = user_id + if parsed_args.type: + kwargs['type'] = parsed_args.type + if parsed_args.data: + kwargs['data'] = parsed_args.data + if parsed_args.project: + project = utils.find_resource(identity_client.projects, + parsed_args.project).id + kwargs['project'] = project + + if not kwargs: + sys.stdout.write("Credential not updated, no arguments present") + return + identity_client.credentials.update(parsed_args.credential, **kwargs) + return + + +class ShowCredential(show.ShowOne): + """Show credential command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.ShowCredential') + + def get_parser(self, prog_name): + parser = super(ShowCredential, self).get_parser(prog_name) + parser.add_argument( + 'credential', + metavar='', + help='ID of credential to display', + ) + return parser + + def take_action(self, 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) + + return zip(*sorted(credential._info.iteritems())) diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py new file mode 100644 index 0000000000..e3eaec2fbc --- /dev/null +++ b/openstackclient/identity/v3/domain.py @@ -0,0 +1,193 @@ +# 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. +# + +"""Identity v3 Domain action implementations""" + +import logging +import sys + +from cliff import command +from cliff import lister +from cliff import show + +from openstackclient.common import utils + + +class CreateDomain(show.ShowOne): + """Create domain command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.CreateDomain') + + def get_parser(self, prog_name): + parser = super(CreateDomain, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar='', + help='New domain name', + ) + parser.add_argument( + '--description', + metavar='', + help='New domain description', + ) + enable_group = parser.add_mutually_exclusive_group() + enable_group.add_argument( + '--enable', + dest='enabled', + action='store_true', + default=True, + help='Enable domain') + enable_group.add_argument( + '--disable', + dest='enabled', + action='store_false', + help='Disable domain') + return parser + + def take_action(self, 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, + enabled=parsed_args.enabled, + ) + + return zip(*sorted(domain._info.iteritems())) + + +class DeleteDomain(command.Command): + """Delete domain command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.DeleteDomain') + + def get_parser(self, prog_name): + parser = super(DeleteDomain, self).get_parser(prog_name) + parser.add_argument( + 'domain', + metavar='', + help='Name or ID of domain to delete', + ) + return parser + + def take_action(self, 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) + identity_client.domains.delete(domain.id) + return + + +class ListDomain(lister.Lister): + """List domain command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.ListDomain') + + def take_action(self, 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, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data)) + + +class SetDomain(command.Command): + """Set domain command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.SetDomain') + + def get_parser(self, prog_name): + parser = super(SetDomain, self).get_parser(prog_name) + parser.add_argument( + 'domain', + metavar='', + help='Name or ID of domain to change', + ) + parser.add_argument( + '--name', + metavar='', + help='New domain name', + ) + parser.add_argument( + '--description', + metavar='', + help='New domain description', + ) + enable_group = parser.add_mutually_exclusive_group() + enable_group.add_argument( + '--enable', + dest='enabled', + action='store_true', + default=True, + help='Enable domain (default)', + ) + enable_group.add_argument( + '--disable', + dest='enabled', + action='store_false', + help='Disable domain', + ) + return parser + + def take_action(self, 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) + kwargs = {} + if parsed_args.name: + kwargs['name'] = parsed_args.name + if parsed_args.description: + kwargs['description'] = parsed_args.description + if 'enabled' in parsed_args: + kwargs['enabled'] = parsed_args.enabled + + if not kwargs: + sys.stdout.write("Domain not updated, no arguments present") + return + identity_client.domains.update(domain.id, **kwargs) + return + + +class ShowDomain(show.ShowOne): + """Show domain command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.ShowDomain') + + def get_parser(self, prog_name): + parser = super(ShowDomain, self).get_parser(prog_name) + parser.add_argument( + 'domain', + metavar='', + help='Name or ID of domain to display', + ) + return parser + + def take_action(self, 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) + + return zip(*sorted(domain._info.iteritems())) diff --git a/setup.py b/setup.py index e4ea3f93d3..f91f9eaa26 100644 --- a/setup.py +++ b/setup.py @@ -117,6 +117,21 @@ def read(fname): 'list_user=openstackclient.identity.v3.user:ListUser', 'set_user=openstackclient.identity.v3.user:SetUser', 'show_user=openstackclient.identity.v3.user:ShowUser', + 'create_credential=' + + 'openstackclient.identity.v3.credential:CreateCredential', + 'delete_credential=' + + 'openstackclient.identity.v3.credential:DeleteCredential', + 'set_credential=' + + 'openstackclient.identity.v3.credential:SetCredential', + 'show_credential=' + + 'openstackclient.identity.v3.credential:ShowCredential', + 'list_credential=' + + 'openstackclient.identity.v3.credential:ListCredential', + 'create_domain=openstackclient.identity.v3.domain:CreateDomain', + 'delete_domain=openstackclient.identity.v3.domain:DeleteDomain', + 'set_domain=openstackclient.identity.v3.domain:SetDomain', + 'show_domain=openstackclient.identity.v3.domain:ShowDomain', + 'list_domain=openstackclient.identity.v3.domain:ListDomain', ], 'openstack.image.v2': [ 'list_image=openstackclient.image.v2.image:ListImage', From 0c4929373e04c48638dc3facd47e39933cc9ac49 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Mon, 4 Mar 2013 22:27:07 -0600 Subject: [PATCH 0048/3614] Add snapshot support for v1 volume rebase again, and change util to look for display_name too minor changes and rebase add create/delete/list/set/show support for snapshot Change-Id: I80261653fa919555a44ddda07b0a827ccd16e5e0 --- openstackclient/common/utils.py | 5 + openstackclient/volume/v1/snapshot.py | 183 ++++++++++++++++++++++++++ setup.py | 7 + 3 files changed, 195 insertions(+) create mode 100644 openstackclient/volume/v1/snapshot.py diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 6477285eb0..76532fcb02 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -46,6 +46,11 @@ 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: + 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) diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py new file mode 100644 index 0000000000..a4cf86bfd5 --- /dev/null +++ b/openstackclient/volume/v1/snapshot.py @@ -0,0 +1,183 @@ +# 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. +# + +"""Volume v1 Snapshot action implementations""" + +import logging +import sys + +from cliff import command +from cliff import lister +from cliff import show + +from openstackclient.common import utils + + +class CreateSnapshot(show.ShowOne): + """Create snapshot command""" + + api = 'volume' + log = logging.getLogger(__name__ + '.CreateSnapshot') + + def get_parser(self, prog_name): + parser = super(CreateSnapshot, self).get_parser(prog_name) + parser.add_argument( + 'volume', + metavar='', + help='The name or ID of the volume to snapshot', + ) + parser.add_argument( + '--name', + metavar='', + required=True, + help='Name of the snapshot', + ) + parser.add_argument( + '--description', + metavar='', + help='Description of the snapshot', + ) + parser.add_argument( + '--force', + dest='force', + action='store_true', + default=False, + help='Create a snapshot attached to an instance. Default is False', + ) + return parser + + def take_action(self, 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 + snapshot = volume_client.volume_snapshots.create( + volume_id, + parsed_args.force, + parsed_args.name, + parsed_args.description + ) + + return zip(*sorted(snapshot._info.iteritems())) + + +class DeleteSnapshot(command.Command): + """Delete snapshot command""" + + api = 'volume' + log = logging.getLogger(__name__ + '.DeleteSnapshot') + + def get_parser(self, prog_name): + parser = super(DeleteSnapshot, self).get_parser(prog_name) + parser.add_argument( + 'snapshot', + metavar='', + help='Name or ID of snapshot to delete', + ) + return parser + + def take_action(self, 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 + volume_client.volume_snapshots.delete(snapshot_id) + return + + +class ListSnapshot(lister.Lister): + """List snapshot command""" + + api = 'volume' + log = logging.getLogger(__name__ + '.ListSnapshot') + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + columns = ( + 'ID', + 'Display Name', + 'Display Description', + 'Status', + 'Size' + ) + data = self.app.client_manager.volume.volume_snapshots.list() + return (columns, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data)) + + +class SetSnapshot(command.Command): + """Set snapshot command""" + + api = 'volume' + log = logging.getLogger(__name__ + '.SetSnapshot') + + def get_parser(self, prog_name): + parser = super(SetSnapshot, self).get_parser(prog_name) + parser.add_argument( + 'snapshot', + metavar='', + help='Name or ID of snapshot to change') + parser.add_argument( + '--name', + metavar='', + help='New snapshot name') + parser.add_argument( + '--description', + metavar='', + help='New snapshot description') + return parser + + def take_action(self, 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) + kwargs = {} + if parsed_args.name: + kwargs['display_name'] = parsed_args.name + if parsed_args.description: + kwargs['display_description'] = parsed_args.description + + if not kwargs: + sys.stdout.write("Snapshot not updated, no arguments present") + return + snapshot.update(**kwargs) + return + + +class ShowSnapshot(show.ShowOne): + """Show snapshot command""" + + api = 'volume' + log = logging.getLogger(__name__ + '.ShowSnapshot') + + def get_parser(self, prog_name): + parser = super(ShowSnapshot, self).get_parser(prog_name) + parser.add_argument( + 'snapshot', + metavar='', + help='Name or ID of snapshot to display') + return parser + + def take_action(self, 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) + + return zip(*sorted(snapshot._info.iteritems())) diff --git a/setup.py b/setup.py index 922d103bbd..018628aeb8 100644 --- a/setup.py +++ b/setup.py @@ -161,6 +161,13 @@ def read(fname): 'list_volume=openstackclient.volume.v1.volume:ListVolume', 'set_volume=openstackclient.volume.v1.volume:SetVolume', 'show_volume=openstackclient.volume.v1.volume:ShowVolume', + 'create_snapshot=' + + 'openstackclient.volume.v1.snapshot:CreateSnapshot', + 'delete_snapshot=' + + 'openstackclient.volume.v1.snapshot:DeleteSnapshot', + 'list_snapshot=openstackclient.volume.v1.snapshot:ListSnapshot', + 'set_snapshot=openstackclient.volume.v1.snapshot:SetSnapshot', + 'show_snapshot=openstackclient.volume.v1.snapshot:ShowSnapshot', ] } ) From 1ddc30100f863c223f3ebef0ca0e56e9c8a9cfbb Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sat, 9 Mar 2013 00:25:06 -0600 Subject: [PATCH 0049/3614] Add force-delete option for volumes Again, looking thorouhgly through cinders commands, i noticed an force-delete command, which was for volumes only. Decided it would work best as an optional argument for the regular delete case Change-Id: I9de6040ea0ad71c2a9c8edc7be18779d39e7ede0 --- openstackclient/volume/v1/volume.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index f2d0851078..6b0d7cd65e 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -125,7 +125,14 @@ def get_parser(self, prog_name): parser.add_argument( 'volume', metavar='', - help='ID of volume to delete', + help='Name or ID of volume to delete', + ) + parser.add_argument( + '--force', + dest='force', + action='store_true', + default=False, + help='Attempt forced removal of a volume, regardless of state', ) return parser @@ -134,7 +141,10 @@ def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume = utils.find_resource( volume_client.volumes, parsed_args.volume) - volume_client.volumes.delete(volume.id) + if parsed_args.force: + volume_client.volumes.force_delete(volume.id) + else: + volume_client.volumes.delete(volume.id) return From 13d88407d640e34f2378000a2335db650251bded Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Sat, 9 Mar 2013 00:20:16 -0600 Subject: [PATCH 0050/3614] Turn down requests logging level Looks like the default is INFO, we don't need to see that unless --debug is specified. Change-Id: Ieee5d4dc5102f11536ecc6330461f86101811f9e --- openstackclient/shell.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 29f9f70a7d..b30e01e7e4 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -297,6 +297,13 @@ def initialize_app(self, argv): 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) + else: + requests_log.setLevel(logging.WARNING) + # stash selected API versions for later # TODO(dtroyer): how do extenstions add their version requirements? self.api_version = { From 211a4bcbf2bb875df490839ab91e8d7a5f1fafd0 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Mon, 11 Mar 2013 15:08:49 -0500 Subject: [PATCH 0051/3614] Add optional arguments to volume list command Added --name, --all-tenants, and --status as search params Change-Id: Ibaa2a7f6862c2ff7dbe24f6bea971db3e70bc120 --- openstackclient/volume/v1/volume.py | 34 +++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 6b0d7cd65e..6e110a4ae3 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -154,10 +154,40 @@ class ListVolume(lister.Lister): api = 'volume' log = logging.getLogger(__name__ + '.ListVolume') + def get_parser(self, prog_name): + parser = super(ListVolume, self).get_parser(prog_name) + parser.add_argument( + '--status', + metavar='', + help='Filter results by status', + ) + parser.add_argument( + '--name', + metavar='', + help='Filter results by name', + ) + parser.add_argument( + '--all-tenants', + action='store_true', + default=False, + help='Display information from all tenants (Admin-only)', + ) + return parser + def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) - columns = ('ID', 'Status', 'Display Name', 'Size', 'Volume Type') - data = self.app.client_manager.volume.volumes.list() + columns = ('ID', 'Status', 'Display Name', 'Size', + 'Volume Type', 'Bootable', 'Attached to') + + search_opts = { + 'all_tenants': parsed_args.all_tenants, + 'display_name': parsed_args.name, + 'status': parsed_args.status, + } + + volume_client = self.app.client_manager.volume + data = volume_client.volumes.list(search_opts=search_opts) + return (columns, (utils.get_item_properties( s, columns, From ebfad587b32e31bc08e2e78f36b0b40131745a1e Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Mon, 11 Mar 2013 18:07:48 -0500 Subject: [PATCH 0052/3614] Added compute hypervisor support. Change-Id: Ib8109550b06d152773394a1d15f6202b9f9b029c --- openstackclient/compute/v2/hypervisor.py | 84 ++++++++++++++++++++++++ setup.py | 4 ++ 2 files changed, 88 insertions(+) create mode 100644 openstackclient/compute/v2/hypervisor.py diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py new file mode 100644 index 0000000000..35866aecea --- /dev/null +++ b/openstackclient/compute/v2/hypervisor.py @@ -0,0 +1,84 @@ +# 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. +# + +"""Hypervisor action implementations""" + +import logging + +from cliff import lister +from cliff import show + +from openstackclient.common import utils + + +class ListHypervisor(lister.Lister): + """List hypervisor command""" + + api = "compute" + log = logging.getLogger(__name__ + ".ListHypervisor") + + def get_parser(self, prog_name): + parser = super(ListHypervisor, self).get_parser(prog_name) + parser.add_argument( + "--matching", + metavar="", + help="List hypervisors with hostnames matching the given" + " substring") + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + compute_client = self.app.client_manager.compute + columns = ( + "ID", + "Hypervisor Hostname" + ) + + if parsed_args.matching: + data = compute_client.hypervisors.search(parsed_args.matching) + else: + data = compute_client.hypervisors.list() + + return (columns, + (utils.get_item_properties( + s, columns, + ) for s in data)) + + +class ShowHypervisor(show.ShowOne): + """Show hypervisor command""" + + api = "compute" + log = logging.getLogger(__name__ + ".ShowHypervisor") + + def get_parser(self, prog_name): + parser = super(ShowHypervisor, self).get_parser(prog_name) + parser.add_argument( + "id", + metavar="", + help="ID of the hypervisor to display") + return parser + + def take_action(self, 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() + + hypervisor["service_id"] = hypervisor["service"]["id"] + hypervisor["service_host"] = hypervisor["service"]["host"] + del hypervisor["service"] + + return zip(*sorted(hypervisor.iteritems())) diff --git a/setup.py b/setup.py index 57ee32b469..54976f1aa8 100644 --- a/setup.py +++ b/setup.py @@ -148,6 +148,8 @@ def read(fname): 'list_agent=openstackclient.compute.v2.agent:ListAgent', 'list_flavor=openstackclient.compute.v2.flavor:ListFlavor', 'list_host=openstackclient.compute.v2.host:ListHost', + 'list_hypervisor=' + + 'openstackclient.compute.v2.hypervisor:ListHypervisor', 'list_server=openstackclient.compute.v2.server:ListServer', 'list_compute-service=' + 'openstackclient.compute.v2.service:ListService', @@ -160,6 +162,8 @@ def read(fname): 'openstackclient.compute.v2.service:SetService', 'show_flavor=openstackclient.compute.v2.flavor:ShowFlavor', 'show_host=openstackclient.compute.v2.host:ShowHost', + 'show_hypervisor=' + + 'openstackclient.compute.v2.hypervisor:ShowHypervisor', 'show_server=openstackclient.compute.v2.server:ShowServer', 'suspend_server=openstackclient.compute.v2.server:SuspendServer', 'unpause_server=openstackclient.compute.v2.server:UnpauseServer', From e5d9ba5491b4764c7a4c8423a8b89b9c0f980b3b Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Tue, 12 Mar 2013 16:27:53 -0500 Subject: [PATCH 0053/3614] Add role v3 support to identity in openstack client Added create/delete/set/list/show support for roles Broken up to make reviewing easier. Will add more functionality (add/remove) later Change-Id: I95bddd27d8d9d251ad2fd60c3e3ee1e2cbcd7d4b --- openstackclient/identity/v3/role.py | 144 ++++++++++++++++++++++++++++ setup.py | 7 ++ 2 files changed, 151 insertions(+) create mode 100644 openstackclient/identity/v3/role.py diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py new file mode 100644 index 0000000000..5b4ceb1da5 --- /dev/null +++ b/openstackclient/identity/v3/role.py @@ -0,0 +1,144 @@ +# 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. +# + +"""Identity v3 Role action implementations""" + +import logging +import sys + +from cliff import command +from cliff import lister +from cliff import show + +from openstackclient.common import utils + + +class CreateRole(show.ShowOne): + """Create new role""" + + api = 'identity' + log = logging.getLogger(__name__ + '.CreateRole') + + def get_parser(self, prog_name): + parser = super(CreateRole, self).get_parser(prog_name) + parser.add_argument( + 'role-name', + metavar='', + help='New role name') + return parser + + def take_action(self, 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) + + return zip(*sorted(role._info.iteritems())) + + +class DeleteRole(command.Command): + """Delete existing role""" + + api = 'identity' + log = logging.getLogger(__name__ + '.DeleteRole') + + def get_parser(self, prog_name): + parser = super(DeleteRole, self).get_parser(prog_name) + parser.add_argument( + 'role', + metavar='', + help='Name or ID of role to delete') + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + identity_client = self.app.client_manager.identity + role_id = utils.find_resource(identity_client.roles, + parsed_args.role) + identity_client.roles.delete(role_id) + return + + +class ListRole(lister.Lister): + """List roles""" + + api = 'identity' + log = logging.getLogger(__name__ + '.ListRole') + + 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() + return (columns, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data)) + + +class SetRole(command.Command): + """Set role command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.SetRole') + + def get_parser(self, prog_name): + parser = super(SetRole, self).get_parser(prog_name) + parser.add_argument( + 'role', + metavar='', + help='Name or ID of role to change', + ) + parser.add_argument( + '--name', + metavar='', + help='New role name', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + identity_client = self.app.client_manager.identity + role_id = utils.find_resource(identity_client.roles, + parsed_args.role) + + if not parsed_args.name: + sys.stdout.write("Role not updated, no arguments present") + return + + identity_client.roles.update(role_id, parsed_args.name) + return + + +class ShowRole(show.ShowOne): + """Show single role""" + + api = 'identity' + log = logging.getLogger(__name__ + '.ShowRole') + + def get_parser(self, prog_name): + parser = super(ShowRole, self).get_parser(prog_name) + parser.add_argument( + 'role', + metavar='', + help='Name or ID of role to display') + return parser + + def take_action(self, 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) + + return zip(*sorted(role._info.iteritems())) diff --git a/setup.py b/setup.py index 54976f1aa8..594d03bb71 100644 --- a/setup.py +++ b/setup.py @@ -132,6 +132,13 @@ def read(fname): 'set_domain=openstackclient.identity.v3.domain:SetDomain', 'show_domain=openstackclient.identity.v3.domain:ShowDomain', 'list_domain=openstackclient.identity.v3.domain:ListDomain', + 'create_role=' + + 'openstackclient.identity.v3.role:CreateRole', + 'delete_role=' + + 'openstackclient.identity.v3.role:DeleteRole', + 'list_role=openstackclient.identity.v3.role:ListRole', + 'show_role=openstackclient.identity.v3.role:ShowRole', + 'set_role=openstackclient.identity.v3.role:SetRole', ], 'openstack.image.v2': [ 'list_image=openstackclient.image.v2.image:ListImage', From f54012cf8ea196eef2a68d0b5faa4684c48efe45 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Fri, 15 Mar 2013 11:10:46 -0500 Subject: [PATCH 0054/3614] Removed unused imports. Change-Id: Ib1bae16f996559c008fb1fe0b74f26b152854ea8 --- openstackclient/compute/v2/agent.py | 1 - openstackclient/compute/v2/flavor.py | 1 - openstackclient/compute/v2/host.py | 1 - openstackclient/compute/v2/service.py | 1 - 4 files changed, 4 deletions(-) diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py index 05a0a94cd3..46ab991b4c 100644 --- a/openstackclient/compute/v2/agent.py +++ b/openstackclient/compute/v2/agent.py @@ -21,7 +21,6 @@ from cliff import lister from cliff import show -from novaclient.v1_1 import agents from openstackclient.common import utils diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 0b48da86de..1c6d3e3ab3 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -21,7 +21,6 @@ from cliff import lister from cliff import show -from novaclient.v1_1 import flavors from openstackclient.common import utils diff --git a/openstackclient/compute/v2/host.py b/openstackclient/compute/v2/host.py index e74a0ade7c..f9c23a42cd 100644 --- a/openstackclient/compute/v2/host.py +++ b/openstackclient/compute/v2/host.py @@ -19,7 +19,6 @@ from cliff import lister -from novaclient.v1_1 import hosts from openstackclient.common import utils diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 3bd7481499..5dac0e1ca9 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -19,7 +19,6 @@ from cliff import lister -from novaclient.v1_1 import services from openstackclient.common import utils From 4fd5dd3b1afca700073affe313732c1d0540215e Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 8 Mar 2013 15:39:48 -0600 Subject: [PATCH 0055/3614] Add a simple extension hook This enables stand-alone modules to hook in to the command-line handler in a dedicated API namespace. These extensions have access to the existing clients via the client manager but no way to add another client to the manager is provided.. blueprint client-extensions Change-Id: I4b2eaa780d8ba881db23d950d09b8702e9e314ef --- openstackclient/shell.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 29f9f70a7d..eef2d0612a 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -298,7 +298,6 @@ def initialize_app(self, argv): super(OpenStackShell, self).initialize_app(argv) # stash selected API versions for later - # TODO(dtroyer): how do extenstions add their version requirements? self.api_version = { 'compute': self.options.os_compute_api_version, 'identity': self.options.os_identity_api_version, @@ -312,6 +311,19 @@ def initialize_app(self, argv): self.command_manager.add_command_group( 'openstack.' + api + version) + # This is the naive extension implementation referred to in + # blueprint 'client-extensions' + # Extension modules can register their commands in an + # 'openstack.extension' entry point group: + # entry_points={ + # 'openstack.extension': [ + # 'list_repo=qaz.github.repo:ListRepo', + # 'show_repo=qaz.github.repo:ShowRepo', + # ], + # } + self.command_manager.add_command_group( + 'openstack.extension') + # Handle deferred help and exit if self.options.deferred_help: self.DeferredHelpAction(self.parser, self.parser, None, None) From b26bbae598b00cf44f8dc493d4022f09f73c6807 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Wed, 13 Mar 2013 15:09:35 -0500 Subject: [PATCH 0056/3614] Add functionality for add-role commands keep the functions sorted Please review carefully as I intend to mimic this logic with list and remove, I'm open to suggestions about handling thigs differently Change-Id: Ia6359134c44447f3b758870c4dc306ec1f970852 --- openstackclient/identity/v3/role.py | 81 +++++++++++++++++++++++++++++ setup.py | 1 + 2 files changed, 82 insertions(+) diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 5b4ceb1da5..faff906293 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -25,6 +25,87 @@ from openstackclient.common import utils +class AddRole(command.Command): + """Add role command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.AddRole') + + def get_parser(self, prog_name): + parser = super(AddRole, self).get_parser(prog_name) + parser.add_argument( + 'role', + metavar='', + help='Name or ID of role to add', + ) + user_or_group = parser.add_mutually_exclusive_group() + user_or_group.add_argument( + '--user', + metavar='', + help='Name or ID of user to assign a role', + ) + user_or_group.add_argument( + '--group', + metavar='', + help='Name or ID of group to assign a role', + ) + domain_or_project = parser.add_mutually_exclusive_group() + domain_or_project.add_argument( + '--domain', + metavar='', + help='Name or ID of domain where user or group resides', + ) + domain_or_project.add_argument( + '--project', + metavar='', + help='Name or ID of project where user or group resides', + ) + return parser + + def take_action(self, 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 + and not parsed_args.group and not parsed_args.project): + sys.stdout.write("Role not updated, no arguments present \n") + return + + role_id = utils.find_resource(identity_client.roles, + parsed_args.role).id + + if (parsed_args.user and parsed_args.domain): + user = utils.find_resource(identity_client.users, + parsed_args.user) + domain = utils.find_resource(identity_client.domains, + parsed_args.domain) + identity_client.roles.grant(role_id, user=user, domain=domain) + return + elif (parsed_args.user and parsed_args.project): + user = utils.find_resource(identity_client.users, + parsed_args.user) + project = utils.find_resource(identity_client.projects, + parsed_args.project) + identity_client.roles.grant(role_id, user=user, project=project) + return + elif (parsed_args.group and parsed_args.project): + group = utils.find_resource(identity_client.group, + parsed_args.group) + project = utils.find_resource(identity_client.projects, + parsed_args.project) + identity_client.roles.grant(role_id, group=group, project=project) + return + elif (parsed_args.group and parsed_args.domain): + group = utils.find_resource(identity_client.group, + parsed_args.group) + domain = utils.find_resource(identity_client.domains, + parsed_args.domain) + identity_client.roles.grant(role_id, group=group, domain=domain) + return + else: + return + + class CreateRole(show.ShowOne): """Create new role""" diff --git a/setup.py b/setup.py index 04466a5179..d7926f0773 100644 --- a/setup.py +++ b/setup.py @@ -139,6 +139,7 @@ def read(fname): 'list_role=openstackclient.identity.v3.role:ListRole', 'show_role=openstackclient.identity.v3.role:ShowRole', 'set_role=openstackclient.identity.v3.role:SetRole', + 'add_role=openstackclient.identity.v3.role:AddRole', ], 'openstack.image.v2': [ 'list_image=openstackclient.image.v2.image:ListImage', From 907dbdb897fe13fc46022f93c1c3e2f7bcec19be Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Tue, 12 Mar 2013 12:11:20 -0500 Subject: [PATCH 0057/3614] Add service v3 support for identity simple rebase rewrite help for create, remove choices Added create/delete/list/show/set for service Modified setup.py Change-Id: I5ced0e214cc2f7fc70493c66b4381040ce8122fe --- openstackclient/identity/v3/service.py | 186 +++++++++++++++++++++++++ setup.py | 7 + 2 files changed, 193 insertions(+) create mode 100644 openstackclient/identity/v3/service.py diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py new file mode 100644 index 0000000000..023c858e84 --- /dev/null +++ b/openstackclient/identity/v3/service.py @@ -0,0 +1,186 @@ +# 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. +# + +"""Identity v3 Service action implementations""" + +import logging +import sys + +from cliff import command +from cliff import lister +from cliff import show + +from openstackclient.common import utils + + +class CreateService(show.ShowOne): + """Create service command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.CreateService') + + def get_parser(self, prog_name): + parser = super(CreateService, self).get_parser(prog_name) + parser.add_argument( + 'type', + metavar='', + help='New service type (compute, image, identity, volume, etc)') + parser.add_argument( + '--name', + metavar='', + help='New service name') + enable_group = parser.add_mutually_exclusive_group() + enable_group.add_argument( + '--enable', + dest='enabled', + action='store_true', + default=True, + help='Enable user') + enable_group.add_argument( + '--disable', + dest='enabled', + action='store_false', + help='Disable user') + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + identity_client = self.app.client_manager.identity + service = identity_client.services.create( + parsed_args.name, + parsed_args.type, + parsed_args.enabled) + + return zip(*sorted(service._info.iteritems())) + + +class DeleteService(command.Command): + """Delete service command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.DeleteService') + + def get_parser(self, prog_name): + parser = super(DeleteService, self).get_parser(prog_name) + parser.add_argument( + 'service', + metavar='', + help='Name or ID of service to delete') + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + identity_client = self.app.client_manager.identity + + service_id = utils.find_resource( + identity_client.services, parsed_args.service).id + + identity_client.services.delete(service_id) + return + + +class ListService(lister.Lister): + """List service command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.ListService') + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + columns = ('ID', 'Name', 'Type', 'Enabled') + data = self.app.client_manager.identity.services.list() + return (columns, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data)) + + +class SetService(show.ShowOne): + """Set service command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.SetService') + + def get_parser(self, prog_name): + parser = super(SetService, self).get_parser(prog_name) + parser.add_argument( + 'service', + metavar='', + help='Service name or ID to update') + parser.add_argument( + '--type', + metavar='', + help='New service type (compute, image, identity, volume, etc)') + parser.add_argument( + '--name', + metavar='', + help='New service name') + enable_group = parser.add_mutually_exclusive_group() + enable_group.add_argument( + '--enable', + dest='enabled', + action='store_true', + default=True, + help='Enable user') + enable_group.add_argument( + '--disable', + dest='enabled', + action='store_false', + help='Disable user') + return parser + + def take_action(self, 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) + + if not parsed_args.name and not parsed_args.type: + sys.stdout.write("Service not updated, no arguments present") + return + + identity_client.services.update( + service, + parsed_args.name, + parsed_args.type, + parsed_args.enabled) + + return + + +class ShowService(show.ShowOne): + """Show service command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.ShowService') + + def get_parser(self, prog_name): + parser = super(ShowService, self).get_parser(prog_name) + parser.add_argument( + 'service', + metavar='', + help='Type, name or ID of service to display') + return parser + + def take_action(self, 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) + + return zip(*sorted(service._info.iteritems())) diff --git a/setup.py b/setup.py index d7926f0773..5d7a8b61b1 100644 --- a/setup.py +++ b/setup.py @@ -140,6 +140,13 @@ def read(fname): 'show_role=openstackclient.identity.v3.role:ShowRole', 'set_role=openstackclient.identity.v3.role:SetRole', 'add_role=openstackclient.identity.v3.role:AddRole', + 'create_service=' + + 'openstackclient.identity.v3.service:CreateService', + 'delete_service=' + + 'openstackclient.identity.v3.service:DeleteService', + 'list_service=openstackclient.identity.v3.service:ListService', + 'show_service=openstackclient.identity.v3.service:ShowService', + 'set_service=openstackclient.identity.v3.service:SetService', ], 'openstack.image.v2': [ 'list_image=openstackclient.image.v2.image:ListImage', From 95c6e5f11d22ac34d73170e071aceaae6680847b Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Tue, 12 Mar 2013 10:15:55 -0500 Subject: [PATCH 0058/3614] Add endpoint v3 functionality simple rebase integrated dolphm and dtroyer comments Added create,list,delete,set,show endpoints modified setup.py Change-Id: Id6153db16db44130beb3b44a8e3e2f72e9a01e5f --- openstackclient/identity/v3/endpoint.py | 247 ++++++++++++++++++++++++ setup.py | 7 + 2 files changed, 254 insertions(+) create mode 100644 openstackclient/identity/v3/endpoint.py diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py new file mode 100644 index 0000000000..4d9b680882 --- /dev/null +++ b/openstackclient/identity/v3/endpoint.py @@ -0,0 +1,247 @@ +# 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. +# + +"""Identity v3 Endpoint action implementations""" + +import logging +import sys + +from cliff import command +from cliff import lister +from cliff import show + +from openstackclient.common import utils + + +class CreateEndpoint(show.ShowOne): + """Create endpoint command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.CreateEndpoint') + + def get_parser(self, prog_name): + parser = super(CreateEndpoint, self).get_parser(prog_name) + parser.add_argument( + 'service', + metavar='', + help='Name or ID of new endpoint service') + parser.add_argument( + 'interface', + metavar='', + choices=['admin', 'public', 'internal'], + help='New endpoint interface, must be admin, public or internal') + parser.add_argument( + 'url', + metavar='', + help='New endpoint URL') + parser.add_argument( + '--region', + metavar='', + help='New endpoint region') + enable_group = parser.add_mutually_exclusive_group() + enable_group.add_argument( + '--enable', + dest='enabled', + action='store_true', + default=True, + help='Enable user', + ) + enable_group.add_argument( + '--disable', + dest='enabled', + action='store_false', + help='Disable user', + ) + return parser + + def take_action(self, 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) + + endpoint = identity_client.endpoints.create( + service.id, + parsed_args.url, + parsed_args.interface, + parsed_args.region, + parsed_args.enabled + ) + + info = {} + info.update(endpoint._info) + info['service_name'] = service.name + info['service_type'] = service.type + return zip(*sorted(info.iteritems())) + + +class DeleteEndpoint(command.Command): + """Delete endpoint command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.DeleteEndpoint') + + def get_parser(self, prog_name): + parser = super(DeleteEndpoint, self).get_parser(prog_name) + parser.add_argument( + 'endpoint', + metavar='', + help='ID of endpoint to delete') + return parser + + def take_action(self, 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 + identity_client.endpoints.delete(endpoint_id) + return + + +class ListEndpoint(lister.Lister): + """List endpoint command""" + + api = 'identity' + 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) + 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') + data = identity_client.endpoints.list() + + for ep in data: + service = utils.find_resource( + identity_client.services, ep.service_id) + ep.service_name = service.name + ep.service_type = service.type + return (columns, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data)) + + +class SetEndpoint(command.Command): + """Set endpoint command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.SetEndpoint') + + def get_parser(self, prog_name): + parser = super(SetEndpoint, self).get_parser(prog_name) + parser.add_argument( + 'endpoint', + metavar='', + help='ID of endpoint to update') + parser.add_argument( + '--interface', + metavar='', + choices=['admin', 'public', 'internal'], + help='New endpoint interface, must be admin|public|internal') + parser.add_argument( + '--url', + metavar='', + help='New endpoint URL') + parser.add_argument( + '--service', + metavar='', + help='Name or ID of new endpoint service') + parser.add_argument( + '--region', + metavar='', + help='New endpoint region') + enable_group = parser.add_mutually_exclusive_group() + enable_group.add_argument( + '--enable', + dest='enabled', + action='store_true', + default=True, + help='Enable user', + ) + enable_group.add_argument( + '--disable', + dest='enabled', + action='store_false', + help='Disable user', + ) + return parser + + def take_action(self, 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) + + if (not parsed_args.interface and not parsed_args.url + and not parsed_args.service and not parsed_args.region): + sys.stdout.write("Endpoint not updated, no arguments present") + return + + identity_client.endpoints.update( + endpoint.id, + service.id, + parsed_args.url, + parsed_args.interface, + parsed_args.region, + parsed_args.enabled + ) + + return + + +class ShowEndpoint(show.ShowOne): + """Show endpoint command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.ShowEndpoint') + + def get_parser(self, prog_name): + parser = super(ShowEndpoint, self).get_parser(prog_name) + parser.add_argument( + 'endpoint', + metavar='', + help='ID of endpoint to display') + return parser + + def take_action(self, 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) + + info = {} + info.update(endpoint._info) + info['service_name'] = service.name + info['service_type'] = service.type + return zip(*sorted(info.iteritems())) diff --git a/setup.py b/setup.py index 5d7a8b61b1..861497fa2a 100644 --- a/setup.py +++ b/setup.py @@ -147,6 +147,13 @@ def read(fname): 'list_service=openstackclient.identity.v3.service:ListService', 'show_service=openstackclient.identity.v3.service:ShowService', 'set_service=openstackclient.identity.v3.service:SetService', + 'create_endpoint=' + + 'openstackclient.identity.v3.endpoint:CreateEndpoint', + 'delete_endpoint=' + + 'openstackclient.identity.v3.endpoint:DeleteEndpoint', + 'set_endpoint=openstackclient.identity.v3.endpoint:SetEndpoint', + 'show_endpoint=openstackclient.identity.v3.endpoint:ShowEndpoint', + 'list_endpoint=openstackclient.identity.v3.endpoint:ListEndpoint', ], 'openstack.image.v2': [ 'list_image=openstackclient.image.v2.image:ListImage', From b175184f239d3933435045505750c0fa1cd8cc0c Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sat, 9 Mar 2013 00:10:05 -0600 Subject: [PATCH 0059/3614] Add extra-specs support for volume-type changed to volume-type changed command to --long, added a formatter for key=value Just noticed there is an option in cinder for a command called extra-specs-list, only relates to volume-types, to list the meta-data key/value pair. Added that functionality. Also made minor changes so delete/set/unset can be called by name. Change-Id: If534ccd1d8a3abc6d235c60ec9a964b88e3fa66d --- openstackclient/volume/v1/type.py | 105 ++++++++++++++++++------------ setup.py | 12 ++-- 2 files changed, 72 insertions(+), 45 deletions(-) diff --git a/openstackclient/volume/v1/type.py b/openstackclient/volume/v1/type.py index 9f4d3df445..9d79f8b3e3 100644 --- a/openstackclient/volume/v1/type.py +++ b/openstackclient/volume/v1/type.py @@ -24,18 +24,18 @@ from openstackclient.common import utils -class CreateType(show.ShowOne): - """Create type command""" +class CreateVolumeType(show.ShowOne): + """Create volume type command""" api = 'volume' - log = logging.getLogger(__name__ + '.CreateType') + log = logging.getLogger(__name__ + '.CreateVolumeType') def get_parser(self, prog_name): - parser = super(CreateType, self).get_parser(prog_name) + parser = super(CreateVolumeType, self).get_parser(prog_name) parser.add_argument( 'name', metavar='', - help='New type name', + help='New volume type name', ) return parser @@ -51,59 +51,71 @@ def take_action(self, parsed_args): return zip(*sorted(info.iteritems())) -class DeleteType(command.Command): - """Delete type command""" +class DeleteVolumeType(command.Command): + """Delete volume type command""" api = 'volume' - log = logging.getLogger(__name__ + '.DeleteType') + log = logging.getLogger(__name__ + '.DeleteVolumeType') def get_parser(self, prog_name): - parser = super(DeleteType, self).get_parser(prog_name) + parser = super(DeleteVolumeType, self).get_parser(prog_name) parser.add_argument( - 'type', - metavar='', - help='Name or ID of type to delete', + 'volume_type', + metavar='', + help='Name or ID of volume type to delete', ) return parser def take_action(self, 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.type) - volume_client.volume_types.delete(volume_type.id) + volume_type_id = utils.find_resource( + volume_client.volume_types, parsed_args.volume_type).id + volume_client.volume_types.delete(volume_type_id) return -class ListType(lister.Lister): - """List type command""" +class ListVolumeType(lister.Lister): + """List volume type command""" api = 'volume' - log = logging.getLogger(__name__ + '.ListType') + log = logging.getLogger(__name__ + '.ListVolumeType') + + def get_parser(self, prog_name): + parser = super(ListVolumeType, 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) - columns = ('ID', 'Name') + if parsed_args.long: + columns = ('ID', 'Name', 'Extra Specs') + else: + columns = ('ID', 'Name') data = self.app.client_manager.volume.volume_types.list() return (columns, (utils.get_item_properties( s, columns, - formatters={}, + formatters={'Extra Specs': _format_type_list_extra_specs}, ) for s in data)) -class SetType(command.Command): - """Set type command""" +class SetVolumeType(command.Command): + """Set volume type command""" api = 'volume' - log = logging.getLogger(__name__ + '.SetType') + log = logging.getLogger(__name__ + '.SetVolumeType') def get_parser(self, prog_name): - parser = super(SetType, self).get_parser(prog_name) + parser = super(SetVolumeType, self).get_parser(prog_name) parser.add_argument( - 'type', - metavar='', - help='Type ID to update', + 'volume_type', + metavar='', + help='Volume type name or ID to update', ) parser.add_argument( 'meta_data', @@ -117,27 +129,26 @@ def take_action(self, parsed_args): meta = dict(v.split('=') for v in parsed_args.meta_data.split(' ')) volume_client = self.app.client_manager.volume - volume_type = volume_client.volume_types.get( - parsed_args.type - ) + volume_type = utils.find_resource( + volume_client.volume_types, parsed_args.volume_type) volume_type.set_keys(meta) return -class UnsetType(command.Command): - """Unset type command""" +class UnsetVolumeType(command.Command): + """Unset volume type command""" api = 'volume' - log = logging.getLogger(__name__ + '.UnsetType') + log = logging.getLogger(__name__ + '.UnsetVolumeType') def get_parser(self, prog_name): - parser = super(UnsetType, self).get_parser(prog_name) + parser = super(UnsetVolumeType, self).get_parser(prog_name) parser.add_argument( - 'type', - metavar='', - help='Type ID to update', + 'volume_type', + metavar='', + help='Type ID or name to update', ) parser.add_argument( 'meta_data', @@ -149,11 +160,25 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) volume_client = self.app.client_manager.volume - volume_type = volume_client.volume_types.get( - parsed_args.type - ) + volume_type = utils.find_resource( + volume_client.volume_types, parsed_args.volume_type) + key_list = [] key_list.append(parsed_args.meta_data) volume_type.unset_keys(key_list) return + + +def _format_type_list_extra_specs(vol_type): + """Return a string containing the key value pairs + + :param server: a single VolumeType resource + :rtype: a string formatted to key=value + """ + + keys = vol_type.get_keys() + output = "" + for s in keys: + output = output + s + "=" + keys[s] + "; " + return output diff --git a/setup.py b/setup.py index 861497fa2a..eb193b4c24 100644 --- a/setup.py +++ b/setup.py @@ -191,11 +191,13 @@ def read(fname): 'unpause_server=openstackclient.compute.v2.server:UnpauseServer', ], 'openstack.volume.v1': [ - 'create_type=openstackclient.volume.v1.type:CreateType', - 'delete_type=openstackclient.volume.v1.type:DeleteType', - 'list_type=openstackclient.volume.v1.type:ListType', - 'set_type=openstackclient.volume.v1.type:SetType', - 'unset_type=openstackclient.volume.v1.type:UnsetType', + 'create_volume-type=' + + 'openstackclient.volume.v1.type:CreateVolumeType', + 'delete_volume-type=' + + 'openstackclient.volume.v1.type:DeleteVolumeType', + 'list_volume-type=openstackclient.volume.v1.type:ListVolumeType', + 'set_volume-type=openstackclient.volume.v1.type:SetVolumeType', + 'unset_volume-type=openstackclient.volume.v1.type:UnsetVolumeType', 'show_quota=openstackclient.volume.v1.quota:ShowQuota', 'list_quota=openstackclient.volume.v1.quota:ListQuota', 'set_quota=openstackclient.volume.v1.quota:SetQuota', From 73fb88e9317f5f2e5270aca4976ffecd06621b0c Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 21 Mar 2013 12:21:55 -0500 Subject: [PATCH 0060/3614] Make entry point strings readable PEP8 E126 wants continued strings to line up vertically, totally destroying the readability and visual indication of the beginning of a string in a list * Ignore PEP8 E126 in order to indent the entry point strings in a readable manner. * Sort the enrty point command strings by object then verb. * Bring other ignores from run_tests.sh to tox.ini Change-Id: I2593de7d6c058322101bc68636317cdba29fe664 --- run_tests.sh | 2 +- setup.py | 226 ++++++++++++++++++++++++++++----------------------- tox.ini | 2 +- 3 files changed, 125 insertions(+), 105 deletions(-) diff --git a/run_tests.sh b/run_tests.sh index 8a4ae624c4..4992dee867 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -118,7 +118,7 @@ function run_pep8 { # other than what the PEP8 tool claims. It is deprecated in Python 3, so, # perhaps the mistake was thinking that the deprecation applied to Python 2 # as well. - pep8_opts="--ignore=E202,W602 --repeat" + pep8_opts="--ignore=E126,E202,W602 --repeat" ${wrapper} pep8 ${pep8_opts} ${srcfiles} } diff --git a/setup.py b/setup.py index eb193b4c24..336c7898f7 100644 --- a/setup.py +++ b/setup.py @@ -57,162 +57,182 @@ def read(fname): 'openstack.cli': [ ], 'openstack.identity.v2_0': [ - 'create_endpoint=' + - 'openstackclient.identity.v2_0.endpoint:CreateEndpoint', - 'delete_endpoint=' + - 'openstackclient.identity.v2_0.endpoint:DeleteEndpoint', - 'list_endpoint=' + - 'openstackclient.identity.v2_0.endpoint:ListEndpoint', - 'show_endpoint=' + - 'openstackclient.identity.v2_0.endpoint:ShowEndpoint', - 'add_role=' + - 'openstackclient.identity.v2_0.role:AddRole', - 'create_role=' + - 'openstackclient.identity.v2_0.role:CreateRole', - 'delete_role=' + - 'openstackclient.identity.v2_0.role:DeleteRole', + 'create_endpoint=' + 'openstackclient.identity.v2_0.endpoint:CreateEndpoint', + 'delete_endpoint=' + 'openstackclient.identity.v2_0.endpoint:DeleteEndpoint', + 'list_endpoint=' + 'openstackclient.identity.v2_0.endpoint:ListEndpoint', + 'show_endpoint=' + 'openstackclient.identity.v2_0.endpoint:ShowEndpoint', + + 'add_role=' + 'openstackclient.identity.v2_0.role:AddRole', + 'create_role=' + 'openstackclient.identity.v2_0.role:CreateRole', + 'delete_role=' + 'openstackclient.identity.v2_0.role:DeleteRole', 'list_role=openstackclient.identity.v2_0.role:ListRole', - 'remove_role=' + - 'openstackclient.identity.v2_0.role:RemoveRole', + 'remove_role=' + 'openstackclient.identity.v2_0.role:RemoveRole', 'show_role=openstackclient.identity.v2_0.role:ShowRole', - 'create_service=' + - 'openstackclient.identity.v2_0.service:CreateService', - 'delete_service=' + - 'openstackclient.identity.v2_0.service:DeleteService', + + 'create_service=' + 'openstackclient.identity.v2_0.service:CreateService', + 'delete_service=' + 'openstackclient.identity.v2_0.service:DeleteService', 'list_service=openstackclient.identity.v2_0.service:ListService', 'show_service=openstackclient.identity.v2_0.service:ShowService', - 'create_tenant=' + - 'openstackclient.identity.v2_0.tenant:CreateTenant', - 'delete_tenant=' + - 'openstackclient.identity.v2_0.tenant:DeleteTenant', + + 'create_tenant=' + 'openstackclient.identity.v2_0.tenant:CreateTenant', + 'delete_tenant=' + 'openstackclient.identity.v2_0.tenant:DeleteTenant', 'list_tenant=openstackclient.identity.v2_0.tenant:ListTenant', 'set_tenant=openstackclient.identity.v2_0.tenant:SetTenant', 'show_tenant=openstackclient.identity.v2_0.tenant:ShowTenant', + 'list_user-role=openstackclient.identity.v2_0.role:ListUserRole', - 'create_user=' + - 'openstackclient.identity.v2_0.user:CreateUser', - 'delete_user=' + - 'openstackclient.identity.v2_0.user:DeleteUser', + + 'create_user=' + 'openstackclient.identity.v2_0.user:CreateUser', + 'delete_user=' + 'openstackclient.identity.v2_0.user:DeleteUser', 'list_user=openstackclient.identity.v2_0.user:ListUser', 'set_user=openstackclient.identity.v2_0.user:SetUser', 'show_user=openstackclient.identity.v2_0.user:ShowUser', ], 'openstack.identity.v3': [ + 'create_credential=' + 'openstackclient.identity.v3.credential:CreateCredential', + 'delete_credential=' + 'openstackclient.identity.v3.credential:DeleteCredential', + 'list_credential=' + 'openstackclient.identity.v3.credential:ListCredential', + 'set_credential=' + 'openstackclient.identity.v3.credential:SetCredential', + 'show_credential=' + 'openstackclient.identity.v3.credential:ShowCredential', + + 'create_domain=openstackclient.identity.v3.domain:CreateDomain', + 'delete_domain=openstackclient.identity.v3.domain:DeleteDomain', + 'list_domain=openstackclient.identity.v3.domain:ListDomain', + 'set_domain=openstackclient.identity.v3.domain:SetDomain', + 'show_domain=openstackclient.identity.v3.domain:ShowDomain', + + 'create_endpoint=' + 'openstackclient.identity.v3.endpoint:CreateEndpoint', + 'delete_endpoint=' + 'openstackclient.identity.v3.endpoint:DeleteEndpoint', + 'set_endpoint=openstackclient.identity.v3.endpoint:SetEndpoint', + 'show_endpoint=openstackclient.identity.v3.endpoint:ShowEndpoint', + 'list_endpoint=openstackclient.identity.v3.endpoint:ListEndpoint', + 'create_group=openstackclient.identity.v3.group:CreateGroup', 'delete_group=openstackclient.identity.v3.group:DeleteGroup', + 'list_group=openstackclient.identity.v3.group:ListGroup', 'set_group=openstackclient.identity.v3.group:SetGroup', 'show_group=openstackclient.identity.v3.group:ShowGroup', - 'list_group=openstackclient.identity.v3.group:ListGroup', - 'create_project=' + - 'openstackclient.identity.v3.project:CreateProject', - 'delete_project=' + - 'openstackclient.identity.v3.project:DeleteProject', + + 'create_project=' + 'openstackclient.identity.v3.project:CreateProject', + 'delete_project=' + 'openstackclient.identity.v3.project:DeleteProject', + 'list_project=openstackclient.identity.v3.project:ListProject', 'set_project=openstackclient.identity.v3.project:SetProject', 'show_project=openstackclient.identity.v3.project:ShowProject', - 'list_project=openstackclient.identity.v3.project:ListProject', - 'create_user=' + - 'openstackclient.identity.v3.user:CreateUser', - 'delete_user=' + - 'openstackclient.identity.v3.user:DeleteUser', - 'list_user=openstackclient.identity.v3.user:ListUser', - 'set_user=openstackclient.identity.v3.user:SetUser', - 'show_user=openstackclient.identity.v3.user:ShowUser', - 'create_credential=' + - 'openstackclient.identity.v3.credential:CreateCredential', - 'delete_credential=' + - 'openstackclient.identity.v3.credential:DeleteCredential', - 'set_credential=' + - 'openstackclient.identity.v3.credential:SetCredential', - 'show_credential=' + - 'openstackclient.identity.v3.credential:ShowCredential', - 'list_credential=' + - 'openstackclient.identity.v3.credential:ListCredential', - 'create_domain=openstackclient.identity.v3.domain:CreateDomain', - 'delete_domain=openstackclient.identity.v3.domain:DeleteDomain', - 'set_domain=openstackclient.identity.v3.domain:SetDomain', - 'show_domain=openstackclient.identity.v3.domain:ShowDomain', - 'list_domain=openstackclient.identity.v3.domain:ListDomain', - 'create_role=' + - 'openstackclient.identity.v3.role:CreateRole', - 'delete_role=' + - 'openstackclient.identity.v3.role:DeleteRole', + + 'add_role=openstackclient.identity.v3.role:AddRole', + 'create_role=' + 'openstackclient.identity.v3.role:CreateRole', + 'delete_role=' + 'openstackclient.identity.v3.role:DeleteRole', 'list_role=openstackclient.identity.v3.role:ListRole', 'show_role=openstackclient.identity.v3.role:ShowRole', 'set_role=openstackclient.identity.v3.role:SetRole', - 'add_role=openstackclient.identity.v3.role:AddRole', - 'create_service=' + - 'openstackclient.identity.v3.service:CreateService', - 'delete_service=' + - 'openstackclient.identity.v3.service:DeleteService', + + 'create_service=' + 'openstackclient.identity.v3.service:CreateService', + 'delete_service=' + 'openstackclient.identity.v3.service:DeleteService', 'list_service=openstackclient.identity.v3.service:ListService', 'show_service=openstackclient.identity.v3.service:ShowService', 'set_service=openstackclient.identity.v3.service:SetService', - 'create_endpoint=' + - 'openstackclient.identity.v3.endpoint:CreateEndpoint', - 'delete_endpoint=' + - 'openstackclient.identity.v3.endpoint:DeleteEndpoint', - 'set_endpoint=openstackclient.identity.v3.endpoint:SetEndpoint', - 'show_endpoint=openstackclient.identity.v3.endpoint:ShowEndpoint', - 'list_endpoint=openstackclient.identity.v3.endpoint:ListEndpoint', + + 'create_user=' + 'openstackclient.identity.v3.user:CreateUser', + 'delete_user=' + 'openstackclient.identity.v3.user:DeleteUser', + 'list_user=openstackclient.identity.v3.user:ListUser', + 'set_user=openstackclient.identity.v3.user:SetUser', + 'show_user=openstackclient.identity.v3.user:ShowUser', ], 'openstack.image.v2': [ 'list_image=openstackclient.image.v2.image:ListImage', - 'show_image=openstackclient.image.v2.image:ShowImage', 'save_image=openstackclient.image.v2.image:SaveImage', + 'show_image=openstackclient.image.v2.image:ShowImage', ], 'openstack.compute.v2': [ 'create_agent=openstackclient.compute.v2.agent:CreateAgent', - 'create_flavor=openstackclient.compute.v2.flavor:CreateFlavor', - 'create_server=openstackclient.compute.v2.server:CreateServer', 'delete_agent=openstackclient.compute.v2.agent:DeleteAgent', - 'delete_flavor=openstackclient.compute.v2.flavor:DeleteFlavor', - 'delete_server=openstackclient.compute.v2.server:DeleteServer', 'list_agent=openstackclient.compute.v2.agent:ListAgent', + 'set_agent=openstackclient.compute.v2.agent:SetAgent', + + 'list_compute-service=' + 'openstackclient.compute.v2.service:ListService', + 'set_compute-service=' + 'openstackclient.compute.v2.service:SetService', + + 'create_flavor=openstackclient.compute.v2.flavor:CreateFlavor', + 'delete_flavor=openstackclient.compute.v2.flavor:DeleteFlavor', 'list_flavor=openstackclient.compute.v2.flavor:ListFlavor', + 'show_flavor=openstackclient.compute.v2.flavor:ShowFlavor', + 'list_host=openstackclient.compute.v2.host:ListHost', - 'list_hypervisor=' + - 'openstackclient.compute.v2.hypervisor:ListHypervisor', + 'show_host=openstackclient.compute.v2.host:ShowHost', + + 'list_hypervisor=' + 'openstackclient.compute.v2.hypervisor:ListHypervisor', + 'show_hypervisor=' + 'openstackclient.compute.v2.hypervisor:ShowHypervisor', + + 'create_server=openstackclient.compute.v2.server:CreateServer', + 'delete_server=openstackclient.compute.v2.server:DeleteServer', 'list_server=openstackclient.compute.v2.server:ListServer', - 'list_compute-service=' + - 'openstackclient.compute.v2.service:ListService', 'pause_server=openstackclient.compute.v2.server:PauseServer', 'reboot_server=openstackclient.compute.v2.server:RebootServer', 'rebuild_server=openstackclient.compute.v2.server:RebuildServer', 'resume_server=openstackclient.compute.v2.server:ResumeServer', - 'set_agent=openstackclient.compute.v2.agent:SetAgent', - 'set_compute-service=' + - 'openstackclient.compute.v2.service:SetService', - 'show_flavor=openstackclient.compute.v2.flavor:ShowFlavor', - 'show_host=openstackclient.compute.v2.host:ShowHost', - 'show_hypervisor=' + - 'openstackclient.compute.v2.hypervisor:ShowHypervisor', 'show_server=openstackclient.compute.v2.server:ShowServer', 'suspend_server=openstackclient.compute.v2.server:SuspendServer', 'unpause_server=openstackclient.compute.v2.server:UnpauseServer', ], 'openstack.volume.v1': [ - 'create_volume-type=' + - 'openstackclient.volume.v1.type:CreateVolumeType', - 'delete_volume-type=' + - 'openstackclient.volume.v1.type:DeleteVolumeType', - 'list_volume-type=openstackclient.volume.v1.type:ListVolumeType', - 'set_volume-type=openstackclient.volume.v1.type:SetVolumeType', - 'unset_volume-type=openstackclient.volume.v1.type:UnsetVolumeType', - 'show_quota=openstackclient.volume.v1.quota:ShowQuota', 'list_quota=openstackclient.volume.v1.quota:ListQuota', 'set_quota=openstackclient.volume.v1.quota:SetQuota', + 'show_quota=openstackclient.volume.v1.quota:ShowQuota', + + 'create_snapshot=' + 'openstackclient.volume.v1.snapshot:CreateSnapshot', + 'delete_snapshot=' + 'openstackclient.volume.v1.snapshot:DeleteSnapshot', + 'list_snapshot=openstackclient.volume.v1.snapshot:ListSnapshot', + 'set_snapshot=openstackclient.volume.v1.snapshot:SetSnapshot', + 'show_snapshot=openstackclient.volume.v1.snapshot:ShowSnapshot', + 'create_volume=openstackclient.volume.v1.volume:CreateVolume', 'delete_volume=openstackclient.volume.v1.volume:DeleteVolume', 'list_volume=openstackclient.volume.v1.volume:ListVolume', 'set_volume=openstackclient.volume.v1.volume:SetVolume', 'show_volume=openstackclient.volume.v1.volume:ShowVolume', - 'create_snapshot=' + - 'openstackclient.volume.v1.snapshot:CreateSnapshot', - 'delete_snapshot=' + - 'openstackclient.volume.v1.snapshot:DeleteSnapshot', - 'list_snapshot=openstackclient.volume.v1.snapshot:ListSnapshot', - 'set_snapshot=openstackclient.volume.v1.snapshot:SetSnapshot', - 'show_snapshot=openstackclient.volume.v1.snapshot:ShowSnapshot', + + 'create_volume-type=' + 'openstackclient.volume.v1.type:CreateVolumeType', + 'delete_volume-type=' + 'openstackclient.volume.v1.type:DeleteVolumeType', + 'list_volume-type=openstackclient.volume.v1.type:ListVolumeType', + 'set_volume-type=openstackclient.volume.v1.type:SetVolumeType', + 'unset_volume-type=openstackclient.volume.v1.type:UnsetVolumeType', ] } ) diff --git a/tox.ini b/tox.ini index fb1b2ba699..9b5a843bf1 100644 --- a/tox.ini +++ b/tox.ini @@ -12,7 +12,7 @@ commands = python setup.py testr --testr-args='{posargs}' [testenv:pep8] deps = pep8==1.3.3 -commands = pep8 --repeat --show-source openstackclient setup.py +commands = pep8 --ignore=E126,E202,W602 --repeat --show-source openstackclient setup.py [testenv:venv] commands = {posargs} From 04af8fa8f1b0654dbb2402ecd8158aff7fc77278 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Fri, 22 Mar 2013 23:36:13 -0500 Subject: [PATCH 0061/3614] Add metadata support for volume Now able to pass metadata to the create method, as well as update it with set, and remove it with unset. I'm currently passing it as an optional param like the following: --meta-data=key1=value1 which seems weird, what about --meta-data=key1:value1 I'd have to update type too if that is the case. Change-Id: I0fc8398768da015c1f53c5c564c68a2cfb53e778 --- openstackclient/volume/v1/volume.py | 88 ++++++++++++++++++++++++++--- setup.py | 1 + 2 files changed, 82 insertions(+), 7 deletions(-) diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 6e110a4ae3..174ca3d410 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -76,8 +76,8 @@ def get_parser(self, prog_name): help='Availability Zone to use', ) parser.add_argument( - '--metadata', - metavar='', + '--meta-data', + metavar='', help='Optional metadata to set on volume creation', ) parser.add_argument( @@ -97,6 +97,11 @@ def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) volume_client = self.app.client_manager.volume + + meta = None + if parsed_args.meta_data: + meta = dict(v.split('=') for v in parsed_args.meta_data.split(' ')) + volume = volume_client.volumes.create( parsed_args.size, parsed_args.snapshot_id, @@ -107,7 +112,7 @@ def take_action(self, parsed_args): parsed_args.user_id, parsed_args.project_id, parsed_args.availability_zone, - parsed_args.metadata, + meta, parsed_args.image_ref ) @@ -172,12 +177,22 @@ def get_parser(self, prog_name): default=False, help='Display information from all tenants (Admin-only)', ) + parser.add_argument( + '--long', + action='store_true', + default=False, + help='Display meta-data', + ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) + columns = ('ID', 'Status', 'Display Name', 'Size', 'Volume Type', 'Bootable', 'Attached to') + if parsed_args.long: + columns = ('ID', 'Status', 'Display Name', 'Size', + 'Volume Type', 'Bootable', 'Attached to', 'Meta-data') search_opts = { 'all_tenants': parsed_args.all_tenants, @@ -191,7 +206,7 @@ def take_action(self, parsed_args): return (columns, (utils.get_item_properties( s, columns, - formatters={}, + formatters={'Meta-data': _format_meta_data}, ) for s in data)) @@ -206,7 +221,7 @@ def get_parser(self, prog_name): parser.add_argument( 'volume', metavar='', - help='ID of volume to change') + help='Name or ID of volume to change') parser.add_argument( '--name', metavar='', @@ -215,19 +230,29 @@ def get_parser(self, prog_name): '--description', metavar='', help='New volume description') + parser.add_argument( + '--meta-data', + metavar='', + help='meta-data to add to volume') return parser def take_action(self, 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) + + meta = None + if parsed_args.meta_data: + meta = dict(v.split('=') for v in parsed_args.meta_data.split(' ')) + volume_client.volumes.set_metadata(volume.id, meta) + kwargs = {} if parsed_args.name: kwargs['display_name'] = parsed_args.name if parsed_args.description: kwargs['display_description'] = parsed_args.description - if not kwargs: + if not kwargs and not meta: sys.stdout.write("Volume not updated, no arguments present \n") return volume_client.volumes.update(volume.id, **kwargs) @@ -245,7 +270,7 @@ def get_parser(self, prog_name): parser.add_argument( 'volume', metavar='', - help='ID of volume to display') + help='Name or ID of volume to display') return parser def take_action(self, parsed_args): @@ -254,3 +279,52 @@ def take_action(self, parsed_args): volume = utils.find_resource(volume_client.volumes, parsed_args.volume) return zip(*sorted(volume._info.iteritems())) + + +class UnsetVolume(command.Command): + """Unset volume command""" + + api = 'volume' + log = logging.getLogger(__name__ + '.UnsetVolume') + + def get_parser(self, prog_name): + parser = super(UnsetVolume, self).get_parser(prog_name) + parser.add_argument( + 'volume', + metavar='', + help='Name or ID of volume to change') + parser.add_argument( + '--meta-data', + metavar='', + help='meta-data to remove from volume (key only)') + return parser + + def take_action(self, 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) + + if not parsed_args.meta_data: + sys.stdout.write("Volume not updated, no arguments present \n") + return + + key_list = [] + key_list.append(parsed_args.meta_data) + volume_client.volumes.delete_metadata(volume.id, key_list) + + return + + +def _format_meta_data(volume): + """Return a string containing the key value pairs + + :param server: a single volume resource + :rtype: a string formatted to key=value + """ + + keys = volume.metadata + output = "" + for s in keys: + output = output + s + "=" + keys[s] + "; " + return output diff --git a/setup.py b/setup.py index 336c7898f7..f3b7deb629 100644 --- a/setup.py +++ b/setup.py @@ -225,6 +225,7 @@ def read(fname): 'list_volume=openstackclient.volume.v1.volume:ListVolume', 'set_volume=openstackclient.volume.v1.volume:SetVolume', 'show_volume=openstackclient.volume.v1.volume:ShowVolume', + 'unset_volume=openstackclient.volume.v1.volume:UnsetVolume', 'create_volume-type=' 'openstackclient.volume.v1.type:CreateVolumeType', From 76e06d144e70c0f532f0258c7176013e91c20fd2 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Fri, 8 Mar 2013 11:15:21 -0600 Subject: [PATCH 0062/3614] Add policy to identity v3 use file for data blobs add create/set/delete/show/list policy for v3 Change-Id: I5f68ef89dfb2241ea1aca00736ee6df5f6f03a9b --- openstackclient/identity/v3/policy.py | 186 ++++++++++++++++++++++++++ setup.py | 6 + 2 files changed, 192 insertions(+) create mode 100644 openstackclient/identity/v3/policy.py diff --git a/openstackclient/identity/v3/policy.py b/openstackclient/identity/v3/policy.py new file mode 100644 index 0000000000..ccfc363569 --- /dev/null +++ b/openstackclient/identity/v3/policy.py @@ -0,0 +1,186 @@ +# 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. +# + +"""Identity v3 Policy action implementations""" + +import logging +import sys + +from cliff import command +from cliff import lister +from cliff import show + +from openstackclient.common import utils + + +class CreatePolicy(show.ShowOne): + """Create policy command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.CreatePolicy') + + def get_parser(self, prog_name): + parser = super(CreatePolicy, self).get_parser(prog_name) + parser.add_argument( + '--type', + metavar='', + default="application/json", + help='New MIME Type of the policy blob - i.e.: application/json', + ) + parser.add_argument( + 'blob_file', + metavar='', + help='New policy rule set itself, as a serialized blob, in a file', + ) + 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) + + identity_client = self.app.client_manager.identity + policy = identity_client.policies.create( + blob, type=parsed_args.type + ) + + return zip(*sorted(policy._info.iteritems())) + + +class DeletePolicy(command.Command): + """Delete policy command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.DeletePolicy') + + def get_parser(self, prog_name): + parser = super(DeletePolicy, self).get_parser(prog_name) + parser.add_argument( + 'policy', + metavar='', + help='ID of policy to delete', + ) + 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.policies.delete(parsed_args.policy) + return + + +class ListPolicy(lister.Lister): + """List policy command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.ListPolicy') + + def get_parser(self, prog_name): + parser = super(ListPolicy, self).get_parser(prog_name) + parser.add_argument( + '--include-blob', + 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) + if parsed_args.include_blob: + columns = ('ID', 'Type', 'Blob') + else: + columns = ('ID', 'Type') + data = self.app.client_manager.identity.policies.list() + return (columns, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data)) + + +class SetPolicy(command.Command): + """Set policy command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.SetPolicy') + + def get_parser(self, prog_name): + parser = super(SetPolicy, self).get_parser(prog_name) + parser.add_argument( + 'policy', + metavar='', + help='ID of policy to change', + ) + parser.add_argument( + '--type', + metavar='', + help='New MIME Type of the policy blob - i.e.: application/json', + ) + parser.add_argument( + '--blob-file', + metavar='', + help='New policy rule set itself, as a serialized blob, in a file', + ) + return parser + + def take_action(self, 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) + + kwargs = {} + if blob: + kwargs['blob'] = blob + if parsed_args.type: + kwargs['type'] = parsed_args.type + + if not kwargs: + sys.stdout.write("Policy not updated, no arguments present \n") + return + identity_client.policies.update(parsed_args.policy, **kwargs) + return + + +class ShowPolicy(show.ShowOne): + """Show policy command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.ShowPolicy') + + def get_parser(self, prog_name): + parser = super(ShowPolicy, self).get_parser(prog_name) + parser.add_argument( + 'policy', + metavar='', + help='ID of policy to display', + ) + return parser + + def take_action(self, 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(policy._info.iteritems())) + + +def _read_blob_file_contents(blob_file): + with open(blob_file) as file: + blob = file.read().strip() + return blob diff --git a/setup.py b/setup.py index 336c7898f7..7faa5644fa 100644 --- a/setup.py +++ b/setup.py @@ -134,6 +134,12 @@ def read(fname): 'set_group=openstackclient.identity.v3.group:SetGroup', 'show_group=openstackclient.identity.v3.group:ShowGroup', + 'create_policy=openstackclient.identity.v3.policy:CreatePolicy', + 'delete_policy=openstackclient.identity.v3.policy:DeletePolicy', + 'list_policy=openstackclient.identity.v3.policy:ListPolicy', + 'set_policy=openstackclient.identity.v3.policy:SetPolicy', + 'show_policy=openstackclient.identity.v3.policy:ShowPolicy', + 'create_project=' 'openstackclient.identity.v3.project:CreateProject', 'delete_project=' From 3641b03690b39c55e570ec15ee2c16d4ec591847 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 8 Apr 2013 17:23:50 -0500 Subject: [PATCH 0063/3614] metadata is one word Change-Id: I2baff95c9b6dcc95edf5d5da74814ff37883cd2b --- openstackclient/compute/v2/server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 898614b567..1abd8dd0e8 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -149,7 +149,7 @@ def get_parser(self, prog_name): metavar='', help='Keypair to inject into this server (optional extension)') parser.add_argument( - '--meta-data', + '--metadata', metavar='', action='append', default=[], @@ -229,7 +229,7 @@ def take_action(self, parsed_args): boot_args = [parsed_args.server_name, image, flavor] - meta = dict(v.split('=', 1) for v in parsed_args.meta_data) + meta = dict(v.split('=', 1) for v in parsed_args.metadata) files = {} for f in parsed_args.file: From 364769f97851828a809d82e28cfec2f6a6fd4560 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 12 Apr 2013 17:01:38 -0500 Subject: [PATCH 0064/3614] Add compute keypair commands Add create, delete, list, show keypair commands Part of blueprint nova-client Change-Id: Ieba00d3b3e3a326f875c01ac2a2b9bbd036cd7c9 --- openstackclient/compute/v2/keypair.py | 160 ++++++++++++++++++++++++++ setup.py | 9 ++ 2 files changed, 169 insertions(+) create mode 100644 openstackclient/compute/v2/keypair.py diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py new file mode 100644 index 0000000000..7987574fae --- /dev/null +++ b/openstackclient/compute/v2/keypair.py @@ -0,0 +1,160 @@ +# 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. +# + +"""Keypair action implementations""" + +import logging +import os +import sys + +from cliff import command +from cliff import lister +from cliff import show + +from openstackclient.common import exceptions +from openstackclient.common import utils + + +class CreateKeypair(show.ShowOne): + """Create keypair command""" + + api = "compute" + log = logging.getLogger(__name__ + '.CreateKeypair') + + def get_parser(self, prog_name): + parser = super(CreateKeypair, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar='', + help='New keypair name', + ) + parser.add_argument( + '--public-key', + metavar='', + help='Filename for public key to add', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + compute_client = self.app.client_manager.compute + + public_key = parsed_args.public_key + if public_key: + try: + with open(os.path.expanduser(parsed_args.public_key)) as p: + public_key = p.read() + except IOError as e: + raise exceptions.CommandError( + "Key file %s not found: %s" % (parsed_args.public_key, e)) + + keypair = compute_client.keypairs.create( + parsed_args.name, + public_key=public_key, + ) + + # NOTE(dtroyer): how do we want to handle the display of the private + # key when it needs to be communicated back to the user + # For now, duplicate nova keypair-add command output + info = {} + if public_key: + info.update(keypair._info) + del info['public_key'] + return zip(*sorted(info.iteritems())) + else: + sys.stdout.write(keypair.private_key) + return ({}, {}) + + +class DeleteKeypair(command.Command): + """Delete keypair command""" + + api = "compute" + log = logging.getLogger(__name__ + '.DeleteKeypair') + + def get_parser(self, prog_name): + parser = super(DeleteKeypair, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar='', + help='Name of keypair to delete', + ) + return parser + + def take_action(self, 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 + + +class ListKeypair(lister.Lister): + """List keypair command""" + + api = "compute" + log = logging.getLogger(__name__ + ".ListKeypair") + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + compute_client = self.app.client_manager.compute + columns = ( + "Name", + "Fingerprint" + ) + data = compute_client.keypairs.list() + + return (columns, + (utils.get_item_properties( + s, columns, + ) for s in data)) + + +class ShowKeypair(show.ShowOne): + """Show keypair command""" + + api = 'compute' + log = logging.getLogger(__name__ + '.ShowKeypair') + + def get_parser(self, prog_name): + parser = super(ShowKeypair, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar='', + help='Name of keypair to display', + ) + parser.add_argument( + '--public-key', + action='store_true', + default=False, + help='Include public key in output', + ) + return parser + + def take_action(self, 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']) + if not parsed_args.public_key: + del info['public_key'] + return zip(*sorted(info.iteritems())) + else: + # NOTE(dtroyer): a way to get the public key in a similar form + # as the private key in the create command + sys.stdout.write(keypair.public_key) + return ({}, {}) diff --git a/setup.py b/setup.py index 9666cf95f3..f37ac5cc21 100644 --- a/setup.py +++ b/setup.py @@ -202,6 +202,15 @@ def read(fname): 'show_hypervisor=' 'openstackclient.compute.v2.hypervisor:ShowHypervisor', + 'create_keypair=' + 'openstackclient.compute.v2.keypair:CreateKeypair', + 'delete_keypair=' + 'openstackclient.compute.v2.keypair:DeleteKeypair', + 'list_keypair=' + 'openstackclient.compute.v2.keypair:ListKeypair', + 'show_keypair=' + 'openstackclient.compute.v2.keypair:ShowKeypair', + 'create_server=openstackclient.compute.v2.server:CreateServer', 'delete_server=openstackclient.compute.v2.server:DeleteServer', 'list_server=openstackclient.compute.v2.server:ListServer', From e9021c213b0f98195920e63aa573c040d5a3cf6d Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Sun, 14 Apr 2013 17:02:45 -0500 Subject: [PATCH 0065/3614] Add fixed-ip and floating-ip commands Adds: * fixed-ip commands: add, remove * floating-ip commands: add, create, delete, list, remove * floating-ip-poo command: list Also uses NAME_ATTR in the Resource class if present to determine the attribute to be used for searching in utils.find_resource() Change-Id: Ifd8fa60f880fc4050dea182ac24553cc2c4bff15 --- openstackclient/common/utils.py | 9 +- openstackclient/compute/v2/fixedip.py | 87 ++++++++++ openstackclient/compute/v2/floatingip.py | 160 +++++++++++++++++++ openstackclient/compute/v2/floatingippool.py | 43 +++++ setup.py | 17 ++ 5 files changed, 315 insertions(+), 1 deletion(-) create mode 100644 openstackclient/compute/v2/fixedip.py create mode 100644 openstackclient/compute/v2/floatingip.py create mode 100644 openstackclient/compute/v2/floatingippool.py diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 76532fcb02..8a7926754c 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -38,9 +38,16 @@ def find_resource(manager, name_or_id): except (ValueError, exceptions.NotFound): pass + kwargs = {} + if 'NAME_ATTR' in manager.resource_class.__dict__: + # novaclient does this for oddball resources + kwargs[manager.resource_class.NAME_ATTR] = name_or_id + else: + kwargs['name'] = name_or_id + # finally try to find entity by name try: - return manager.find(name=name_or_id) + return manager.find(**kwargs) # FIXME(dtroyer): The exception to catch here is dependent on which # client library the manager passed in belongs to. # Eventually this should be pulled from a common set diff --git a/openstackclient/compute/v2/fixedip.py b/openstackclient/compute/v2/fixedip.py new file mode 100644 index 0000000000..d0687fd717 --- /dev/null +++ b/openstackclient/compute/v2/fixedip.py @@ -0,0 +1,87 @@ +# 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. +# + +"""Fixed IP action implementations""" + +import logging + +from cliff import command + +from openstackclient.common import utils + + +class AddFixedIP(command.Command): + """Add fixed-ip command""" + + api = "compute" + log = logging.getLogger(__name__ + ".AddFixedIP") + + def get_parser(self, prog_name): + parser = super(AddFixedIP, self).get_parser(prog_name) + parser.add_argument( + "network", + metavar="", + help="Name of the network to fetch an IP address from", + ) + parser.add_argument( + "server", + metavar="", + help="Name of the server to receive the IP address", + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + compute_client = self.app.client_manager.compute + + network = utils.find_resource( + compute_client.networks, parsed_args.network) + + server = utils.find_resource( + compute_client.servers, parsed_args.server) + + server.add_fixed_ip(network.id) + return + + +class RemoveFixedIP(command.Command): + """Remove fixed-ip command""" + + api = "compute" + log = logging.getLogger(__name__ + ".RemoveFixedIP") + + def get_parser(self, prog_name): + parser = super(RemoveFixedIP, self).get_parser(prog_name) + parser.add_argument( + "ip_address", + metavar="", + help="IP address to remove from server", + ) + parser.add_argument( + "server", + metavar="", + help="Name of the server to remove the IP address from", + ) + return parser + + def take_action(self, 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) + + server.remove_fixed_ip(parsed_args.ip_address) + return diff --git a/openstackclient/compute/v2/floatingip.py b/openstackclient/compute/v2/floatingip.py new file mode 100644 index 0000000000..5a4b5f9a5d --- /dev/null +++ b/openstackclient/compute/v2/floatingip.py @@ -0,0 +1,160 @@ +# 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. +# + +"""Floating IP action implementations""" + +import logging + +from cliff import command +from cliff import lister +from cliff import show + +from openstackclient.common import utils + + +class AddFloatingIP(command.Command): + """Add floating-ip command""" + + api = "compute" + log = logging.getLogger(__name__ + ".AddFloatingIP") + + def get_parser(self, prog_name): + parser = super(AddFloatingIP, self).get_parser(prog_name) + parser.add_argument( + "ip_address", + metavar="", + help="IP address to add to server", + ) + parser.add_argument( + "server", + metavar="", + help="Name of the server to receive the IP address", + ) + return parser + + def take_action(self, 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) + + server.add_floating_ip(parsed_args.ip_address) + return + + +class CreateFloatingIP(show.ShowOne): + """Create floating-ip command""" + + api = 'compute' + log = logging.getLogger(__name__ + '.CreateFloatingIP') + + def get_parser(self, prog_name): + parser = super(CreateFloatingIP, self).get_parser(prog_name) + parser.add_argument( + 'pool', + metavar='', + help='Pool to fetch floating IP from', + ) + return parser + + def take_action(self, 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) + + info = {} + info.update(floating_ip._info) + return zip(*sorted(info.iteritems())) + + +class DeleteFloatingIP(command.Command): + """Delete floating-ip command""" + + api = 'compute' + log = logging.getLogger(__name__ + '.DeleteFloatingIP') + + def get_parser(self, prog_name): + parser = super(DeleteFloatingIP, self).get_parser(prog_name) + parser.add_argument( + "ip_address", + metavar="", + help="IP address to add to server", + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + compute_client = self.app.client_manager.compute + + floating_ip = utils.find_resource( + compute_client.floating_ips, + parsed_args.ip_address, + ) + + compute_client.floating_ips.delete(floating_ip) + return + + +class ListFloatingIP(lister.Lister): + """List floating-ip command""" + + api = 'compute' + log = logging.getLogger(__name__ + '.ListFloatingIP') + + def take_action(self, 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') + + data = compute_client.floating_ips.list() + + return (columns, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data)) + + +class RemoveFloatingIP(command.Command): + """Remove floating-ip command""" + + api = "compute" + log = logging.getLogger(__name__ + ".RemoveFloatingIP") + + def get_parser(self, prog_name): + parser = super(RemoveFloatingIP, self).get_parser(prog_name) + parser.add_argument( + "ip_address", + metavar="", + help="IP address to remove from server", + ) + parser.add_argument( + "server", + metavar="", + help="Name of the server to remove the IP address from", + ) + return parser + + def take_action(self, 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) + + server.remove_floating_ip(parsed_args.ip_address) + return diff --git a/openstackclient/compute/v2/floatingippool.py b/openstackclient/compute/v2/floatingippool.py new file mode 100644 index 0000000000..54814e046d --- /dev/null +++ b/openstackclient/compute/v2/floatingippool.py @@ -0,0 +1,43 @@ +# 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. +# + +"""Floating IP Pool action implementations""" + +import logging + +from cliff import lister + +from openstackclient.common import utils + + +class ListFloatingIPPool(lister.Lister): + """List floating-ip-pool command""" + + api = 'compute' + log = logging.getLogger(__name__ + '.ListFloatingIPPool') + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + compute_client = self.app.client_manager.compute + + columns = ('Name',) + + data = compute_client.floating_ip_pools.list() + + return (columns, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data)) diff --git a/setup.py b/setup.py index 9666cf95f3..d59141787d 100644 --- a/setup.py +++ b/setup.py @@ -189,11 +189,28 @@ def read(fname): 'set_compute-service=' 'openstackclient.compute.v2.service:SetService', + 'add_fixed-ip=openstackclient.compute.v2.fixedip:AddFixedIP', + 'remove_fixed-ip=openstackclient.compute.v2.fixedip:RemoveFixedIP', + 'create_flavor=openstackclient.compute.v2.flavor:CreateFlavor', 'delete_flavor=openstackclient.compute.v2.flavor:DeleteFlavor', 'list_flavor=openstackclient.compute.v2.flavor:ListFlavor', 'show_flavor=openstackclient.compute.v2.flavor:ShowFlavor', + 'add_floating-ip=' + 'openstackclient.compute.v2.floatingip:AddFloatingIP', + 'create_floating-ip=' + 'openstackclient.compute.v2.floatingip:CreateFloatingIP', + 'delete_floating-ip=' + 'openstackclient.compute.v2.floatingip:DeleteFloatingIP', + 'list_floating-ip=' + 'openstackclient.compute.v2.floatingip:ListFloatingIP', + 'remove_floating-ip=' + 'openstackclient.compute.v2.floatingip:RemoveFloatingIP', + + 'list_floating-ip-pool=' + 'openstackclient.compute.v2.floatingippool:ListFloatingIPPool', + 'list_host=openstackclient.compute.v2.host:ListHost', 'show_host=openstackclient.compute.v2.host:ShowHost', From 6da61e03f03f9a65cbc9203ea9fdd47c64990184 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 9 Apr 2013 13:59:12 -0500 Subject: [PATCH 0066/3614] Adds image `create` and `delete` functionality. We use the V1 API for `create` since it does not yet exist in the V2 API in glanceclient. For blueprint glance-client. Change-Id: Ifa819c14f6a013f4530d16247a671e5a1c740a28 --- openstackclient/image/client.py | 2 +- openstackclient/image/v1/__init__.py | 14 +++ openstackclient/image/v1/image.py | 156 +++++++++++++++++++++++++++ openstackclient/image/v2/image.py | 23 +++- setup.py | 4 + tools/pip-requires | 2 +- 6 files changed, 198 insertions(+), 3 deletions(-) create mode 100644 openstackclient/image/v1/__init__.py create mode 100644 openstackclient/image/v1/image.py diff --git a/openstackclient/image/client.py b/openstackclient/image/client.py index 8a63da9c76..371605692a 100644 --- a/openstackclient/image/client.py +++ b/openstackclient/image/client.py @@ -22,7 +22,7 @@ API_NAME = "image" API_VERSIONS = { - "1.0": "glanceclient.v2.client.Client", + "1": "glanceclient.v1.client.Client", "2": "glanceclient.v2.client.Client" } diff --git a/openstackclient/image/v1/__init__.py b/openstackclient/image/v1/__init__.py new file mode 100644 index 0000000000..ebf59b327e --- /dev/null +++ b/openstackclient/image/v1/__init__.py @@ -0,0 +1,14 @@ +# 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 new file mode 100644 index 0000000000..fa566bd916 --- /dev/null +++ b/openstackclient/image/v1/image.py @@ -0,0 +1,156 @@ +# 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. +# + +"""Image V1 Action Implementations""" + +import logging +import os +import sys + +if os.name == "nt": + import msvcrt +else: + msvcrt = None + +from cliff import show + + +class CreateImage(show.ShowOne): + """Create image command""" + + api = "image" + log = logging.getLogger(__name__ + ".CreateImage") + + def get_parser(self, prog_name): + parser = super(CreateImage, self).get_parser(prog_name) + parser.add_argument( + "name", + metavar="", + help="Name of image.") + parser.add_argument( + "--disk_format", + default="raw", + metavar="", + help="Disk format of image.") + parser.add_argument( + "--id", + metavar="", + help="ID of image to reserve.") + parser.add_argument( + "--store", + metavar="", + help="Store to upload image to.") + parser.add_argument( + "--container-format", + default="bare", + metavar="", + help="Container format of image.") + parser.add_argument( + "--owner", + metavar="", + help="Owner of the image.") + parser.add_argument( + "--size", + metavar="", + help="Size of image in bytes. Only used with --location and" + " --copy-from.") + parser.add_argument( + "--min-disk", + metavar="", + help="Minimum size of disk needed to boot image in gigabytes.") + parser.add_argument( + "--min-ram", + metavar="", + help="Minimum amount of ram needed to boot image in megabytes.") + parser.add_argument( + "--location", + metavar="", + help="URL where the data for this image already resides.") + parser.add_argument( + "--file", + metavar="", + help="Local file that contains disk image.") + parser.add_argument( + "--checksum", + metavar="", + help="Hash of image data used for verification.") + parser.add_argument( + "--copy-from", + metavar="", + help="Similar to --location, but this indicates that the image" + " should immediately be copied from the data store.") + parser.add_argument( + "--metadata", + metavar="", + default=[], + action="append", + help="Arbitrary metadata to associate with image.") + 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).") + protected_group.add_argument( + "--unprotected", + dest="protected", + action="store_false", + default=False, + help="Allow images to be deleted (default: True).") + 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).") + public_group.add_argument( + "--private", + dest="is_public", + action="store_false", + help="Image is inaccessible to the public.") + 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") + + args["properties"] = {} + for _metadata in args.pop("metadata"): + key, value = _metadata.split("=", 1) + args["properties"][key] = value + + if "location" not in args and "copy_from" not in args: + if "file" in args: + args["data"] = open(args.pop("file"), "rb") + else: + args["data"] = None + if sys.stdin.isatty() is not True: + if msvcrt: + msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) + args["data"] = sys.stdin + + image_client = self.app.client_manager.image + data = image_client.images.create(**args)._info.copy() + + return zip(*sorted(data.iteritems())) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index ce53df209f..3b9b349d30 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -13,7 +13,7 @@ # under the License. # -"""Image Action Implementations""" +"""Image V2 Action Implementations""" import logging @@ -25,6 +25,27 @@ from openstackclient.common import utils +class DeleteImage(command.Command): + """Delete image command""" + + api = "image" + log = logging.getLogger(__name__ + ".DeleteImage") + + def get_parser(self, prog_name): + parser = super(DeleteImage, self).get_parser(prog_name) + parser.add_argument( + "id", + metavar="", + help="ID of image to delete.") + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + + image_client = self.app.client_manager.image + image_client.images.delete(parsed_args.id) + + class ListImage(lister.Lister): """List image command""" diff --git a/setup.py b/setup.py index 9666cf95f3..473f300424 100644 --- a/setup.py +++ b/setup.py @@ -173,7 +173,11 @@ def read(fname): 'set_user=openstackclient.identity.v3.user:SetUser', 'show_user=openstackclient.identity.v3.user:ShowUser', ], + 'openstack.image.v1': [ + 'create_image=openstackclient.image.v1.image:CreateImage', + ], 'openstack.image.v2': [ + 'delete_image=openstackclient.image.v2.image:DeleteImage', 'list_image=openstackclient.image.v2.image:ListImage', 'save_image=openstackclient.image.v2.image:SaveImage', 'show_image=openstackclient.image.v2.image:ShowImage', diff --git a/tools/pip-requires b/tools/pip-requires index 720413ed74..9b71d402bf 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -1,7 +1,7 @@ cliff keyring pycrypto -python-glanceclient>=0.5.1 +python-glanceclient>=0.9.0,<2 python-keystoneclient>=0.2,<1.0 python-novaclient>=2 python-cinderclient>=1 From 53bbff67eb5c4e888cfca0bd6c70d868ac300377 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 15 Apr 2013 17:32:44 -0500 Subject: [PATCH 0067/3614] Add console commands Adds: * console-log * console-url Part of blueprint nova-client Change-Id: Ibea7f96382283770988d05379d41a148f8cd8c4f --- openstackclient/compute/v2/console.py | 123 ++++++++++++++++++++++++++ setup.py | 5 ++ 2 files changed, 128 insertions(+) create mode 100644 openstackclient/compute/v2/console.py diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py new file mode 100644 index 0000000000..8ed0d7f21a --- /dev/null +++ b/openstackclient/compute/v2/console.py @@ -0,0 +1,123 @@ +# 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. +# + +"""Console action implementations""" + +import logging +import sys + +from cliff import command +from cliff import show + +from openstackclient.common import utils + + +class ShowConsoleLog(command.Command): + """Show console-log command""" + + api = 'compute' + log = logging.getLogger(__name__ + '.ShowConsoleLog') + + def get_parser(self, prog_name): + parser = super(ShowConsoleLog, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Name or ID of server to display console log', + ) + parser.add_argument( + '--lines', + metavar='', + type=int, + default=None, + help='Number of lines to display from the end of the log ' + '(default=all)', + ) + return parser + + def take_action(self, 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) + sys.stdout.write(data) + return + + +class ShowConsoleURL(show.ShowOne): + """Show console-url command""" + + api = 'compute' + log = logging.getLogger(__name__ + '.ShowConsoleURL') + + def get_parser(self, prog_name): + parser = super(ShowConsoleURL, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Name or ID of server to display console log', + ) + type_group = parser.add_mutually_exclusive_group() + type_group.add_argument( + '--novnc', + dest='url_type', + action='store_const', + const='novnc', + default='novnc', + help='Show noVNC console URL (default)', + ) + type_group.add_argument( + '--xvpvnc', + dest='url_type', + action='store_const', + const='xvpvnc', + help='Show xpvnc console URL', + ) + type_group.add_argument( + '--spice', + dest='url_type', + action='store_const', + const='spice', + help='Show SPICE console URL', + ) + return parser + + def take_action(self, 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, + ) + + print "type: %s" % parsed_args.url_type + if parsed_args.url_type in ['novnc', 'xvpvnc']: + data = server.get_vnc_console(parsed_args.url_type) + if parsed_args.url_type in ['spice']: + data = server.get_spice_console(parsed_args.url_type) + + if not data: + return ({}, {}) + print "data: %s" % data['console'] + + info = {} + info.update(data['console']) + return zip(*sorted(info.iteritems())) diff --git a/setup.py b/setup.py index c2482d38d4..39290136b9 100644 --- a/setup.py +++ b/setup.py @@ -193,6 +193,11 @@ def read(fname): 'set_compute-service=' 'openstackclient.compute.v2.service:SetService', + 'show_console-log=' + 'openstackclient.compute.v2.console:ShowConsoleLog', + 'show_console-url=' + 'openstackclient.compute.v2.console:ShowConsoleURL', + 'add_fixed-ip=openstackclient.compute.v2.fixedip:AddFixedIP', 'remove_fixed-ip=openstackclient.compute.v2.fixedip:RemoveFixedIP', From b16f210bbf52bbd8f6f7ec9bb48a8a45219beb95 Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Mon, 29 Apr 2013 21:29:07 -0400 Subject: [PATCH 0068/3614] Switch to noun-verb command forms Reverse the commands to use nouns followed by verbs to allow users to take full advantage of tab completion. Compound nouns (e.g., "floating-ip") are also reversed (e.g., "ip floating list" and "ip fixed list"). blueprint nouns-vs-verbs Change-Id: Icf09fb8d7dbd09772bddbbeb74f9a379d9189b72 Signed-off-by: Doug Hellmann --- setup.py | 278 +++++++++++++++++++++++++++---------------------------- 1 file changed, 139 insertions(+), 139 deletions(-) diff --git a/setup.py b/setup.py index 39290136b9..9181fe5739 100644 --- a/setup.py +++ b/setup.py @@ -56,225 +56,225 @@ def read(fname): 'console_scripts': ['openstack=openstackclient.shell:main'], 'openstack.cli': [ ], - 'openstack.identity.v2_0': [ - 'create_endpoint=' + 'openstack.identity.v3_0': [ + 'endpoint_create=' 'openstackclient.identity.v2_0.endpoint:CreateEndpoint', - 'delete_endpoint=' + 'endpoint_delete=' 'openstackclient.identity.v2_0.endpoint:DeleteEndpoint', - 'list_endpoint=' + 'endpoint_list=' 'openstackclient.identity.v2_0.endpoint:ListEndpoint', - 'show_endpoint=' + 'endpoint_show=' 'openstackclient.identity.v2_0.endpoint:ShowEndpoint', - 'add_role=' + 'role_add=' 'openstackclient.identity.v2_0.role:AddRole', - 'create_role=' + 'role_create=' 'openstackclient.identity.v2_0.role:CreateRole', - 'delete_role=' + 'role_delete=' 'openstackclient.identity.v2_0.role:DeleteRole', - 'list_role=openstackclient.identity.v2_0.role:ListRole', - 'remove_role=' + 'role_list=openstackclient.identity.v2_0.role:ListRole', + 'role_remove=' 'openstackclient.identity.v2_0.role:RemoveRole', - 'show_role=openstackclient.identity.v2_0.role:ShowRole', + 'role_show=openstackclient.identity.v2_0.role:ShowRole', - 'create_service=' + 'service_create=' 'openstackclient.identity.v2_0.service:CreateService', - 'delete_service=' + 'service_delete=' 'openstackclient.identity.v2_0.service:DeleteService', - 'list_service=openstackclient.identity.v2_0.service:ListService', - 'show_service=openstackclient.identity.v2_0.service:ShowService', + 'service_list=openstackclient.identity.v2_0.service:ListService', + 'service_show=openstackclient.identity.v2_0.service:ShowService', - 'create_tenant=' + 'tenant_create=' 'openstackclient.identity.v2_0.tenant:CreateTenant', - 'delete_tenant=' + 'tenant_delete=' 'openstackclient.identity.v2_0.tenant:DeleteTenant', - 'list_tenant=openstackclient.identity.v2_0.tenant:ListTenant', - 'set_tenant=openstackclient.identity.v2_0.tenant:SetTenant', - 'show_tenant=openstackclient.identity.v2_0.tenant:ShowTenant', + 'tenant_list=openstackclient.identity.v2_0.tenant:ListTenant', + 'tenant_set=openstackclient.identity.v2_0.tenant:SetTenant', + 'tenant_show=openstackclient.identity.v2_0.tenant:ShowTenant', - 'list_user-role=openstackclient.identity.v2_0.role:ListUserRole', + 'user_role_list=openstackclient.identity.v2_0.role:ListUserRole', - 'create_user=' + 'user_create=' 'openstackclient.identity.v2_0.user:CreateUser', - 'delete_user=' + 'user_delete=' 'openstackclient.identity.v2_0.user:DeleteUser', - 'list_user=openstackclient.identity.v2_0.user:ListUser', - 'set_user=openstackclient.identity.v2_0.user:SetUser', - 'show_user=openstackclient.identity.v2_0.user:ShowUser', + 'user_list=openstackclient.identity.v2_0.user:ListUser', + 'user_set=openstackclient.identity.v2_0.user:SetUser', + 'user_show=openstackclient.identity.v2_0.user:ShowUser', ], 'openstack.identity.v3': [ - 'create_credential=' + 'credential_create=' 'openstackclient.identity.v3.credential:CreateCredential', - 'delete_credential=' + 'credential_delete=' 'openstackclient.identity.v3.credential:DeleteCredential', - 'list_credential=' + 'credential_list=' 'openstackclient.identity.v3.credential:ListCredential', - 'set_credential=' + 'credential_set=' 'openstackclient.identity.v3.credential:SetCredential', - 'show_credential=' + 'credential_show=' 'openstackclient.identity.v3.credential:ShowCredential', - 'create_domain=openstackclient.identity.v3.domain:CreateDomain', - 'delete_domain=openstackclient.identity.v3.domain:DeleteDomain', - 'list_domain=openstackclient.identity.v3.domain:ListDomain', - 'set_domain=openstackclient.identity.v3.domain:SetDomain', - 'show_domain=openstackclient.identity.v3.domain:ShowDomain', + 'domain_create=openstackclient.identity.v3.domain:CreateDomain', + 'domain_delete=openstackclient.identity.v3.domain:DeleteDomain', + 'domain_list=openstackclient.identity.v3.domain:ListDomain', + 'domain_set=openstackclient.identity.v3.domain:SetDomain', + 'domain_show=openstackclient.identity.v3.domain:ShowDomain', - 'create_endpoint=' + 'endpoint_create=' 'openstackclient.identity.v3.endpoint:CreateEndpoint', - 'delete_endpoint=' + 'endpoint_delete=' 'openstackclient.identity.v3.endpoint:DeleteEndpoint', - 'set_endpoint=openstackclient.identity.v3.endpoint:SetEndpoint', - 'show_endpoint=openstackclient.identity.v3.endpoint:ShowEndpoint', - 'list_endpoint=openstackclient.identity.v3.endpoint:ListEndpoint', - - 'create_group=openstackclient.identity.v3.group:CreateGroup', - 'delete_group=openstackclient.identity.v3.group:DeleteGroup', - 'list_group=openstackclient.identity.v3.group:ListGroup', - 'set_group=openstackclient.identity.v3.group:SetGroup', - 'show_group=openstackclient.identity.v3.group:ShowGroup', - - 'create_policy=openstackclient.identity.v3.policy:CreatePolicy', - 'delete_policy=openstackclient.identity.v3.policy:DeletePolicy', - 'list_policy=openstackclient.identity.v3.policy:ListPolicy', - 'set_policy=openstackclient.identity.v3.policy:SetPolicy', - 'show_policy=openstackclient.identity.v3.policy:ShowPolicy', - - 'create_project=' + 'endpoint_set=openstackclient.identity.v3.endpoint:SetEndpoint', + 'endpoint_show=openstackclient.identity.v3.endpoint:ShowEndpoint', + 'endpoint_list=openstackclient.identity.v3.endpoint:ListEndpoint', + + 'group_create=openstackclient.identity.v3.group:CreateGroup', + 'group_delete=openstackclient.identity.v3.group:DeleteGroup', + 'group_list=openstackclient.identity.v3.group:ListGroup', + 'group_set=openstackclient.identity.v3.group:SetGroup', + 'group_show=openstackclient.identity.v3.group:ShowGroup', + + 'policy_create=openstackclient.identity.v3.policy:CreatePolicy', + 'policy_delete=openstackclient.identity.v3.policy:DeletePolicy', + 'policy_list=openstackclient.identity.v3.policy:ListPolicy', + 'policy_set=openstackclient.identity.v3.policy:SetPolicy', + 'policy_show=openstackclient.identity.v3.policy:ShowPolicy', + + 'project_create=' 'openstackclient.identity.v3.project:CreateProject', - 'delete_project=' + 'project_delete=' 'openstackclient.identity.v3.project:DeleteProject', - 'list_project=openstackclient.identity.v3.project:ListProject', - 'set_project=openstackclient.identity.v3.project:SetProject', - 'show_project=openstackclient.identity.v3.project:ShowProject', + 'project_list=openstackclient.identity.v3.project:ListProject', + 'project_set=openstackclient.identity.v3.project:SetProject', + 'project_show=openstackclient.identity.v3.project:ShowProject', - 'add_role=openstackclient.identity.v3.role:AddRole', - 'create_role=' + 'role_add=openstackclient.identity.v3.role:AddRole', + 'role_create=' 'openstackclient.identity.v3.role:CreateRole', - 'delete_role=' + 'role_delete=' 'openstackclient.identity.v3.role:DeleteRole', - 'list_role=openstackclient.identity.v3.role:ListRole', - 'show_role=openstackclient.identity.v3.role:ShowRole', - 'set_role=openstackclient.identity.v3.role:SetRole', + 'role_list=openstackclient.identity.v3.role:ListRole', + 'role_show=openstackclient.identity.v3.role:ShowRole', + 'role_set=openstackclient.identity.v3.role:SetRole', - 'create_service=' + 'service_create=' 'openstackclient.identity.v3.service:CreateService', - 'delete_service=' + 'service_delete=' 'openstackclient.identity.v3.service:DeleteService', - 'list_service=openstackclient.identity.v3.service:ListService', - 'show_service=openstackclient.identity.v3.service:ShowService', - 'set_service=openstackclient.identity.v3.service:SetService', + 'service_list=openstackclient.identity.v3.service:ListService', + 'service_show=openstackclient.identity.v3.service:ShowService', + 'service_set=openstackclient.identity.v3.service:SetService', - 'create_user=' + 'user_create=' 'openstackclient.identity.v3.user:CreateUser', - 'delete_user=' + 'user_delete=' 'openstackclient.identity.v3.user:DeleteUser', - 'list_user=openstackclient.identity.v3.user:ListUser', - 'set_user=openstackclient.identity.v3.user:SetUser', - 'show_user=openstackclient.identity.v3.user:ShowUser', + 'user_list=openstackclient.identity.v3.user:ListUser', + 'user_set=openstackclient.identity.v3.user:SetUser', + 'user_show=openstackclient.identity.v3.user:ShowUser', ], 'openstack.image.v1': [ - 'create_image=openstackclient.image.v1.image:CreateImage', + 'image_create=openstackclient.image.v1.image:CreateImage', ], 'openstack.image.v2': [ - 'delete_image=openstackclient.image.v2.image:DeleteImage', - 'list_image=openstackclient.image.v2.image:ListImage', - 'save_image=openstackclient.image.v2.image:SaveImage', - 'show_image=openstackclient.image.v2.image:ShowImage', + 'image_delete=openstackclient.image.v2.image:DeleteImage', + 'image_list=openstackclient.image.v2.image:ListImage', + 'image_save=openstackclient.image.v2.image:SaveImage', + 'image_show=openstackclient.image.v2.image:ShowImage', ], 'openstack.compute.v2': [ - 'create_agent=openstackclient.compute.v2.agent:CreateAgent', - 'delete_agent=openstackclient.compute.v2.agent:DeleteAgent', - 'list_agent=openstackclient.compute.v2.agent:ListAgent', - 'set_agent=openstackclient.compute.v2.agent:SetAgent', + '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', - 'list_compute-service=' + 'compute_service_list=' 'openstackclient.compute.v2.service:ListService', - 'set_compute-service=' + 'compute_service_set=' 'openstackclient.compute.v2.service:SetService', - 'show_console-log=' + 'console_log_show=' 'openstackclient.compute.v2.console:ShowConsoleLog', - 'show_console-url=' + 'console_url_show=' 'openstackclient.compute.v2.console:ShowConsoleURL', - 'add_fixed-ip=openstackclient.compute.v2.fixedip:AddFixedIP', - 'remove_fixed-ip=openstackclient.compute.v2.fixedip:RemoveFixedIP', + 'ip_fixed_add=openstackclient.compute.v2.fixedip:AddFixedIP', + 'ip_fixed_remove=openstackclient.compute.v2.fixedip:RemoveFixedIP', - 'create_flavor=openstackclient.compute.v2.flavor:CreateFlavor', - 'delete_flavor=openstackclient.compute.v2.flavor:DeleteFlavor', - 'list_flavor=openstackclient.compute.v2.flavor:ListFlavor', - 'show_flavor=openstackclient.compute.v2.flavor:ShowFlavor', + 'flavor_create=openstackclient.compute.v2.flavor:CreateFlavor', + 'flavor_delete=openstackclient.compute.v2.flavor:DeleteFlavor', + 'flavor_list=openstackclient.compute.v2.flavor:ListFlavor', + 'flavor_show=openstackclient.compute.v2.flavor:ShowFlavor', - 'add_floating-ip=' + 'ip_floating_add=' 'openstackclient.compute.v2.floatingip:AddFloatingIP', - 'create_floating-ip=' + 'ip_floating_create=' 'openstackclient.compute.v2.floatingip:CreateFloatingIP', - 'delete_floating-ip=' + 'ip_floating_delete=' 'openstackclient.compute.v2.floatingip:DeleteFloatingIP', - 'list_floating-ip=' + 'ip_floating_list=' 'openstackclient.compute.v2.floatingip:ListFloatingIP', - 'remove_floating-ip=' + 'ip_floating_remove=' 'openstackclient.compute.v2.floatingip:RemoveFloatingIP', - 'list_floating-ip-pool=' + 'ip_floating_pool_list=' 'openstackclient.compute.v2.floatingippool:ListFloatingIPPool', - 'list_host=openstackclient.compute.v2.host:ListHost', - 'show_host=openstackclient.compute.v2.host:ShowHost', + 'host_list=openstackclient.compute.v2.host:ListHost', + 'host_show=openstackclient.compute.v2.host:ShowHost', - 'list_hypervisor=' + 'hypervisor_list=' 'openstackclient.compute.v2.hypervisor:ListHypervisor', - 'show_hypervisor=' + 'hypervisor_show=' 'openstackclient.compute.v2.hypervisor:ShowHypervisor', - 'create_keypair=' + 'keypair_create=' 'openstackclient.compute.v2.keypair:CreateKeypair', - 'delete_keypair=' + 'keypair_delete=' 'openstackclient.compute.v2.keypair:DeleteKeypair', - 'list_keypair=' + 'keypair_list=' 'openstackclient.compute.v2.keypair:ListKeypair', - 'show_keypair=' + 'keypair_show=' 'openstackclient.compute.v2.keypair:ShowKeypair', - 'create_server=openstackclient.compute.v2.server:CreateServer', - 'delete_server=openstackclient.compute.v2.server:DeleteServer', - 'list_server=openstackclient.compute.v2.server:ListServer', - 'pause_server=openstackclient.compute.v2.server:PauseServer', - 'reboot_server=openstackclient.compute.v2.server:RebootServer', - 'rebuild_server=openstackclient.compute.v2.server:RebuildServer', - 'resume_server=openstackclient.compute.v2.server:ResumeServer', - 'show_server=openstackclient.compute.v2.server:ShowServer', - 'suspend_server=openstackclient.compute.v2.server:SuspendServer', - 'unpause_server=openstackclient.compute.v2.server:UnpauseServer', + 'server_create=openstackclient.compute.v2.server:CreateServer', + 'server_delete=openstackclient.compute.v2.server:DeleteServer', + 'server_list=openstackclient.compute.v2.server:ListServer', + 'server_pause=openstackclient.compute.v2.server:PauseServer', + 'server_reboot=openstackclient.compute.v2.server:RebootServer', + 'server_rebuild=openstackclient.compute.v2.server:RebuildServer', + 'server_resume=openstackclient.compute.v2.server:ResumeServer', + 'server_show=openstackclient.compute.v2.server:ShowServer', + 'server_suspend=openstackclient.compute.v2.server:SuspendServer', + 'server_unpause=openstackclient.compute.v2.server:UnpauseServer', ], 'openstack.volume.v1': [ - 'list_quota=openstackclient.volume.v1.quota:ListQuota', - 'set_quota=openstackclient.volume.v1.quota:SetQuota', - 'show_quota=openstackclient.volume.v1.quota:ShowQuota', + 'quota_list=openstackclient.volume.v1.quota:ListQuota', + 'quota_set=openstackclient.volume.v1.quota:SetQuota', + 'quota_show=openstackclient.volume.v1.quota:ShowQuota', - 'create_snapshot=' + 'snapshot_create=' 'openstackclient.volume.v1.snapshot:CreateSnapshot', - 'delete_snapshot=' + 'snapshot_delete=' 'openstackclient.volume.v1.snapshot:DeleteSnapshot', - 'list_snapshot=openstackclient.volume.v1.snapshot:ListSnapshot', - 'set_snapshot=openstackclient.volume.v1.snapshot:SetSnapshot', - 'show_snapshot=openstackclient.volume.v1.snapshot:ShowSnapshot', - - 'create_volume=openstackclient.volume.v1.volume:CreateVolume', - 'delete_volume=openstackclient.volume.v1.volume:DeleteVolume', - 'list_volume=openstackclient.volume.v1.volume:ListVolume', - 'set_volume=openstackclient.volume.v1.volume:SetVolume', - 'show_volume=openstackclient.volume.v1.volume:ShowVolume', - 'unset_volume=openstackclient.volume.v1.volume:UnsetVolume', - - 'create_volume-type=' + 'snapshot_list=openstackclient.volume.v1.snapshot:ListSnapshot', + 'snapshot_set=openstackclient.volume.v1.snapshot:SetSnapshot', + 'snapshot_show=openstackclient.volume.v1.snapshot:ShowSnapshot', + + 'volume_create=openstackclient.volume.v1.volume:CreateVolume', + 'volume_delete=openstackclient.volume.v1.volume:DeleteVolume', + 'volume_list=openstackclient.volume.v1.volume:ListVolume', + 'volume_set=openstackclient.volume.v1.volume:SetVolume', + 'volume_show=openstackclient.volume.v1.volume:ShowVolume', + 'volume_unset=openstackclient.volume.v1.volume:UnsetVolume', + + 'volume_type_create=' 'openstackclient.volume.v1.type:CreateVolumeType', - 'delete_volume-type=' + 'volume_type_delete=' 'openstackclient.volume.v1.type:DeleteVolumeType', - 'list_volume-type=openstackclient.volume.v1.type:ListVolumeType', - 'set_volume-type=openstackclient.volume.v1.type:SetVolumeType', - 'unset_volume-type=openstackclient.volume.v1.type:UnsetVolumeType', + 'volume_type_list=openstackclient.volume.v1.type:ListVolumeType', + 'volume_type_set=openstackclient.volume.v1.type:SetVolumeType', + 'volume_type_unset=openstackclient.volume.v1.type:UnsetVolumeType', ] } ) From a2e3a16221915946ee0d847b1e95187c0f628079 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 7 May 2013 11:18:38 -0500 Subject: [PATCH 0069/3614] Rename all instances of 'metadata' to 'property'. Change-Id: I454cbe685dc5afa0a09ecc976a90d6eb6bc57d14 --- openstackclient/compute/v2/server.py | 6 +++--- openstackclient/image/v1/image.py | 8 ++++---- openstackclient/volume/v1/volume.py | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 1abd8dd0e8..79deaca48d 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -149,11 +149,11 @@ def get_parser(self, prog_name): metavar='', help='Keypair to inject into this server (optional extension)') parser.add_argument( - '--metadata', + '--property', metavar='', action='append', default=[], - help='Metadata to store for this server ' + help='Property to store for this server ' '(repeat for multiple values)') parser.add_argument( '--file', @@ -229,7 +229,7 @@ def take_action(self, parsed_args): boot_args = [parsed_args.server_name, image, flavor] - meta = dict(v.split('=', 1) for v in parsed_args.metadata) + meta = dict(v.split('=', 1) for v in parsed_args.property) files = {} for f in parsed_args.file: diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index fa566bd916..43cae21c07 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -92,11 +92,11 @@ def get_parser(self, prog_name): help="Similar to --location, but this indicates that the image" " should immediately be copied from the data store.") parser.add_argument( - "--metadata", + "--property", metavar="", default=[], action="append", - help="Arbitrary metadata to associate with image.") + help="Arbitrary property to associate with image.") protected_group = parser.add_mutually_exclusive_group() protected_group.add_argument( "--protected", @@ -136,8 +136,8 @@ def take_action(self, parsed_args): args.pop("variables") args["properties"] = {} - for _metadata in args.pop("metadata"): - key, value = _metadata.split("=", 1) + for _property in args.pop("property"): + key, value = _property.split("=", 1) args["properties"][key] = value if "location" not in args and "copy_from" not in args: diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 174ca3d410..f9641c5482 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -76,9 +76,9 @@ def get_parser(self, prog_name): help='Availability Zone to use', ) parser.add_argument( - '--meta-data', + '--property', metavar='', - help='Optional metadata to set on volume creation', + help='Optional property to set on volume creation', ) parser.add_argument( '--image-ref', @@ -242,8 +242,8 @@ def take_action(self, parsed_args): volume = utils.find_resource(volume_client.volumes, parsed_args.volume) meta = None - if parsed_args.meta_data: - meta = dict(v.split('=') for v in parsed_args.meta_data.split(' ')) + if parsed_args.property: + meta = dict(v.split('=') for v in parsed_args.property.split(' ')) volume_client.volumes.set_metadata(volume.id, meta) kwargs = {} From 016a0b301e0ecfea5d84b09e7f1e22a86953c1c1 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Tue, 14 May 2013 08:28:48 -0700 Subject: [PATCH 0070/3614] Fix flake8 errors in anticipation of flake8 patch. Change-Id: Ifdc4322b699f2bd91a6900e55695acd3d736568e --- openstackclient/common/command.py | 4 ++-- openstackclient/common/utils.py | 2 +- openstackclient/compute/v2/server.py | 2 +- openstackclient/identity/v2_0/service.py | 6 ++++-- openstackclient/identity/v2_0/tenant.py | 3 ++- openstackclient/identity/v2_0/user.py | 3 ++- openstackclient/identity/v3/group.py | 3 ++- openstackclient/identity/v3/project.py | 3 ++- openstackclient/identity/v3/user.py | 3 ++- openstackclient/shell.py | 14 +++++++------- openstackclient/volume/v1/quota.py | 3 ++- tests/identity/test_identity.py | 2 -- tests/test_shell.py | 2 +- 13 files changed, 28 insertions(+), 22 deletions(-) diff --git a/openstackclient/common/command.py b/openstackclient/common/command.py index 64e855df67..59cd0da281 100644 --- a/openstackclient/common/command.py +++ b/openstackclient/common/command.py @@ -15,10 +15,10 @@ """OpenStack base command""" -from cliff.command import Command +from cliff import command -class OpenStackCommand(Command): +class OpenStackCommand(command.Command): """Base class for OpenStack commands.""" api = None diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 8a7926754c..56f9cd17d5 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -55,7 +55,7 @@ def find_resource(manager, name_or_id): except Exception as ex: try: return manager.find(display_name=name_or_id) - except: + except Exception: pass if type(ex).__name__ == 'NotFound': diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 79deaca48d..a2496fad12 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -528,7 +528,7 @@ def take_action(self, parsed_args): _password = None if parsed_args.rebuild_password is not False: - _password = args.rebuild_password + _password = parsed_args.rebuild_password kwargs = {} server = server.rebuild(image, _password, **kwargs) diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index 21e32a51b1..51abfc18ce 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -141,8 +141,10 @@ def take_action(self, parsed_args): # 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_or_id + msg = "No service with exists." + # TODO(mordred): Where does name_or_id come from? + # msg = ("No service with a type, name or ID of '%s' exists." % + # name_or_id) raise exceptions.CommandError(msg) info = {} diff --git a/openstackclient/identity/v2_0/tenant.py b/openstackclient/identity/v2_0/tenant.py index 8a2f738f4a..00a6a977cf 100644 --- a/openstackclient/identity/v2_0/tenant.py +++ b/openstackclient/identity/v2_0/tenant.py @@ -16,6 +16,7 @@ """Tenant action implementations""" import logging +import sys from cliff import command from cliff import lister @@ -167,7 +168,7 @@ def take_action(self, parsed_args): kwargs['enabled'] = parsed_args.enabled if kwargs == {}: - stdout.write("Tenant not updated, no arguments present") + sys.stdout.write("Tenant not updated, no arguments present") return 0 tenant.update(**kwargs) return diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index 840cc50084..03da60085f 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -16,6 +16,7 @@ """Identity v2.0 User action implementations""" import logging +import sys from cliff import command from cliff import lister @@ -196,7 +197,7 @@ def take_action(self, parsed_args): kwargs['enabled'] = parsed_args.enabled if not len(kwargs): - stdout.write("User not updated, no arguments present") + sys.stdout.write("User not updated, no arguments present") return identity_client.users.update(user.id, **kwargs) return diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index 3a9b80ed11..0562b76630 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -16,6 +16,7 @@ """Group action implementations""" import logging +import sys from cliff import command from cliff import lister @@ -157,7 +158,7 @@ def take_action(self, parsed_args): kwargs['domain'] = domain if not len(kwargs): - stdout.write("Group not updated, no arguments present") + sys.stdout.write("Group not updated, no arguments present") return identity_client.groups.update(group.id, **kwargs) return diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index d1e67acc2f..84271cd9a5 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -16,6 +16,7 @@ """Project action implementations""" import logging +import sys from cliff import command from cliff import lister @@ -189,7 +190,7 @@ def take_action(self, parsed_args): kwargs['enabled'] = parsed_args.enabled if kwargs == {}: - stdout.write("Project not updated, no arguments present") + sys.stdout.write("Project not updated, no arguments present") return project.update(**kwargs) return diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index bf592d8117..7bd3706593 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -16,6 +16,7 @@ """Identity v3 User action implementations""" import logging +import sys from cliff import command from cliff import lister @@ -215,7 +216,7 @@ def take_action(self, parsed_args): kwargs['enabled'] = parsed_args.enabled if not len(kwargs): - stdout.write("User not updated, no arguments present") + sys.stdout.write("User not updated, no arguments present") return identity_client.users.update(user.id, **kwargs) return diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 303771c6b1..cd5ab552bf 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -20,14 +20,14 @@ import os import sys -from cliff.app import App -from cliff.help import HelpAction +from cliff import app +from cliff import help from openstackclient.common import clientmanager +from openstackclient.common.commandmanager import CommandManager from openstackclient.common import exceptions as exc from openstackclient.common import openstackkeyring from openstackclient.common import utils -from openstackclient.common.commandmanager import CommandManager VERSION = '0.1' @@ -53,7 +53,7 @@ def env(*vars, **kwargs): return kwargs.get('default', '') -class OpenStackShell(App): +class OpenStackShell(app.App): CONSOLE_MESSAGE_FORMAT = '%(levelname)s: %(name)s %(message)s' @@ -75,10 +75,10 @@ def __init__(self): # have been loaded. There doesn't seem to be a # way to edit/remove anything from an existing parser. - # Replace the cliff-added HelpAction to defer its execution + # Replace the cliff-added help.HelpAction to defer its execution self.DeferredHelpAction = None for a in self.parser._actions: - if type(a) == HelpAction: + if type(a) == help.HelpAction: # Found it, save and replace it self.DeferredHelpAction = a @@ -359,7 +359,7 @@ def clean_up(self, cmd, result, err): def main(argv=sys.argv[1:]): try: return OpenStackShell().run(argv) - except: + except Exception: return 1 diff --git a/openstackclient/volume/v1/quota.py b/openstackclient/volume/v1/quota.py index ae6c50f5e3..4f4e97e8f5 100644 --- a/openstackclient/volume/v1/quota.py +++ b/openstackclient/volume/v1/quota.py @@ -16,6 +16,7 @@ """Volume v1 Quota action implementations""" import logging +import sys from cliff import command from cliff import show @@ -79,7 +80,7 @@ def take_action(self, parsed_args): kwargs['gigabytes'] = parsed_args.gigabytes if kwargs == {}: - stdout.write("Quota not updated, no arguments present") + sys.stdout.write("Quota not updated, no arguments present") return volume_client = self.app.client_manager.volume diff --git a/tests/identity/test_identity.py b/tests/identity/test_identity.py index 5b2cf4e334..52bbd22218 100644 --- a/tests/identity/test_identity.py +++ b/tests/identity/test_identity.py @@ -13,8 +13,6 @@ # under the License. # -import mock - from openstackclient.common import clientmanager from openstackclient.identity import client as identity_client from tests import utils diff --git a/tests/test_shell.py b/tests/test_shell.py index c33068d868..d0eb5b0d39 100644 --- a/tests/test_shell.py +++ b/tests/test_shell.py @@ -13,8 +13,8 @@ # under the License. # -import os import mock +import os from openstackclient import shell from tests import utils From 967d929207fa52c00acaa0a3e1b63bbe8e7f3835 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Tue, 14 May 2013 08:29:16 -0700 Subject: [PATCH 0071/3614] Migrate to flake8. Fixes bug 1172444 Change-Id: Ieca721663aea2fd31753df4abfb5b01a7145b26a --- openstackclient/shell.py | 4 ++-- tools/test-requires | 7 ++++++- tox.ini | 8 ++++++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index cd5ab552bf..35d8255ddb 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -24,7 +24,7 @@ from cliff import help from openstackclient.common import clientmanager -from openstackclient.common.commandmanager import CommandManager +from openstackclient.common import commandmanager from openstackclient.common import exceptions as exc from openstackclient.common import openstackkeyring from openstackclient.common import utils @@ -63,7 +63,7 @@ def __init__(self): super(OpenStackShell, self).__init__( description=__doc__.strip(), version=VERSION, - command_manager=CommandManager('openstack.cli')) + command_manager=commandmanager.CommandManager('openstack.cli')) # This is instantiated in initialize_app() only when using # password flow auth diff --git a/tools/test-requires b/tools/test-requires index 405b408ffa..1eb2509c73 100644 --- a/tools/test-requires +++ b/tools/test-requires @@ -1,3 +1,9 @@ +# Install bounded pep8/pyflakes first, then let flake8 install +pep8==1.4.5 +pyflakes==0.7.2 +flake8==2.0 +hacking>=0.5.3,<0.6 + distribute>=0.6.24 coverage @@ -5,7 +11,6 @@ discover fixtures>=0.3.12 mock openstack.nose_plugin -pep8==1.3.3 sphinx>=1.1.2 testrepository>=0.0.13 testtools>=0.9.26 diff --git a/tox.ini b/tox.ini index 9b5a843bf1..f6de18d137 100644 --- a/tox.ini +++ b/tox.ini @@ -11,8 +11,7 @@ deps = -r{toxinidir}/tools/pip-requires commands = python setup.py testr --testr-args='{posargs}' [testenv:pep8] -deps = pep8==1.3.3 -commands = pep8 --ignore=E126,E202,W602 --repeat --show-source openstackclient setup.py +commands = flake8 [testenv:venv] commands = {posargs} @@ -22,3 +21,8 @@ commands = python setup.py testr --coverage --testr-args='{posargs}' [tox:jenkins] downloadcache = ~/cache/pip + +[flake8] +ignore = E126,E202,W602,H402 +show-source = True +exclude = .venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,tools From d49fcb726d078b25566e57c7604d0d52d9998b22 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Tue, 14 May 2013 08:42:41 -0700 Subject: [PATCH 0072/3614] Migrate to pbr. Fixes bug 1179007 Change-Id: Ief74b121dcad28bb1c2b6044ef72e0cbd51e8f65 --- .gitignore | 1 - MANIFEST.in | 1 - openstack-common.conf | 2 +- openstackclient/openstack/common/setup.py | 335 ---------------------- setup.cfg | 204 +++++++++++++ setup.py | 287 +----------------- tools/pip-requires | 2 + 7 files changed, 221 insertions(+), 611 deletions(-) delete mode 100644 openstackclient/openstack/common/setup.py diff --git a/.gitignore b/.gitignore index d6f1cdbc63..a8d9f3d69d 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,3 @@ build ChangeLog dist doc/build -openstackclient/versioninfo diff --git a/MANIFEST.in b/MANIFEST.in index a7f747b848..fd87b80fdf 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,7 +1,6 @@ include AUTHORS include ChangeLog include LICENSE -include openstackclient/versioninfo include README.rst recursive-include doc * diff --git a/openstack-common.conf b/openstack-common.conf index b99037bb3a..58c0dee0ef 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -1,7 +1,7 @@ [DEFAULT] # The list of modules to copy from openstack-common -modules=cfg,iniparser,install_venv_common,openstackkeyring,setup +modules=cfg,iniparser,install_venv_common,openstackkeyring # The base module to hold the copy of openstack.common base=openstackclient diff --git a/openstackclient/openstack/common/setup.py b/openstackclient/openstack/common/setup.py deleted file mode 100644 index fb187fff46..0000000000 --- a/openstackclient/openstack/common/setup.py +++ /dev/null @@ -1,335 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 OpenStack LLC. -# Copyright 2012-2013 Hewlett-Packard Development Company, L.P. -# All Rights Reserved. -# -# 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. - -""" -Utilities with minimum-depends for use in setup.py -""" - -import email -import os -import re -import subprocess -import sys - -from setuptools.command import sdist - - -def parse_mailmap(mailmap='.mailmap'): - mapping = {} - if os.path.exists(mailmap): - with open(mailmap, 'r') as fp: - for l in fp: - try: - canonical_email, alias = re.match( - r'[^#]*?(<.+>).*(<.+>).*', l).groups() - except AttributeError: - continue - mapping[alias] = canonical_email - return mapping - - -def canonicalize_emails(changelog, mapping): - """Takes in a string and an email alias mapping and replaces all - instances of the aliases in the string with their real email. - """ - for alias, email_address in mapping.iteritems(): - changelog = changelog.replace(alias, email_address) - return changelog - - -# Get requirements from the first file that exists -def get_reqs_from_files(requirements_files): - for requirements_file in requirements_files: - if os.path.exists(requirements_file): - with open(requirements_file, 'r') as fil: - return fil.read().split('\n') - return [] - - -def parse_requirements(requirements_files=['requirements.txt', - 'tools/pip-requires']): - requirements = [] - for line in get_reqs_from_files(requirements_files): - # For the requirements list, we need to inject only the portion - # after egg= so that distutils knows the package it's looking for - # such as: - # -e git://github.com/openstack/nova/master#egg=nova - if re.match(r'\s*-e\s+', line): - requirements.append(re.sub(r'\s*-e\s+.*#egg=(.*)$', r'\1', - line)) - # such as: - # http://github.com/openstack/nova/zipball/master#egg=nova - elif re.match(r'\s*https?:', line): - requirements.append(re.sub(r'\s*https?:.*#egg=(.*)$', r'\1', - line)) - # -f lines are for index locations, and don't get used here - elif re.match(r'\s*-f\s+', line): - pass - # argparse is part of the standard library starting with 2.7 - # adding it to the requirements list screws distro installs - elif line == 'argparse' and sys.version_info >= (2, 7): - pass - else: - requirements.append(line) - - return requirements - - -def parse_dependency_links(requirements_files=['requirements.txt', - 'tools/pip-requires']): - dependency_links = [] - # dependency_links inject alternate locations to find packages listed - # in requirements - for line in get_reqs_from_files(requirements_files): - # skip comments and blank lines - if re.match(r'(\s*#)|(\s*$)', line): - continue - # lines with -e or -f need the whole line, minus the flag - if re.match(r'\s*-[ef]\s+', line): - dependency_links.append(re.sub(r'\s*-[ef]\s+', '', line)) - # lines that are only urls can go in unmolested - elif re.match(r'\s*https?:', line): - dependency_links.append(line) - return dependency_links - - -def _run_shell_command(cmd, throw_on_error=False): - if os.name == 'nt': - output = subprocess.Popen(["cmd.exe", "/C", cmd], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - else: - output = subprocess.Popen(["/bin/sh", "-c", cmd], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - if output.returncode and throw_on_error: - raise Exception("%s returned %d" % cmd, output.returncode) - out = output.communicate() - if len(out) == 0: - return None - if len(out[0].strip()) == 0: - return None - return out[0].strip() - - -def write_git_changelog(): - """Write a changelog based on the git changelog.""" - new_changelog = 'ChangeLog' - if not os.getenv('SKIP_WRITE_GIT_CHANGELOG'): - if os.path.isdir('.git'): - git_log_cmd = 'git log --stat' - changelog = _run_shell_command(git_log_cmd) - mailmap = parse_mailmap() - with open(new_changelog, "w") as changelog_file: - changelog_file.write(canonicalize_emails(changelog, mailmap)) - else: - open(new_changelog, 'w').close() - - -def generate_authors(): - """Create AUTHORS file using git commits.""" - jenkins_email = 'jenkins@review.(openstack|stackforge).org' - old_authors = 'AUTHORS.in' - new_authors = 'AUTHORS' - if not os.getenv('SKIP_GENERATE_AUTHORS'): - if os.path.isdir('.git'): - # don't include jenkins email address in AUTHORS file - git_log_cmd = ("git log --format='%aN <%aE>' | sort -u | " - "egrep -v '" + jenkins_email + "'") - changelog = _run_shell_command(git_log_cmd) - mailmap = parse_mailmap() - with open(new_authors, 'w') as new_authors_fh: - new_authors_fh.write(canonicalize_emails(changelog, mailmap)) - if os.path.exists(old_authors): - with open(old_authors, "r") as old_authors_fh: - new_authors_fh.write('\n' + old_authors_fh.read()) - else: - open(new_authors, 'w').close() - - -_rst_template = """%(heading)s -%(underline)s - -.. automodule:: %(module)s - :members: - :undoc-members: - :show-inheritance: -""" - - -def get_cmdclass(): - """Return dict of commands to run from setup.py.""" - - cmdclass = dict() - - def _find_modules(arg, dirname, files): - for filename in files: - if filename.endswith('.py') and filename != '__init__.py': - arg["%s.%s" % (dirname.replace('/', '.'), - filename[:-3])] = True - - class LocalSDist(sdist.sdist): - """Builds the ChangeLog and Authors files from VC first.""" - - def run(self): - write_git_changelog() - generate_authors() - # sdist.sdist is an old style class, can't use super() - sdist.sdist.run(self) - - cmdclass['sdist'] = LocalSDist - - # If Sphinx is installed on the box running setup.py, - # enable setup.py to build the documentation, otherwise, - # just ignore it - try: - from sphinx.setup_command import BuildDoc - - class LocalBuildDoc(BuildDoc): - - builders = ['html', 'man'] - - def generate_autoindex(self): - print "**Autodocumenting from %s" % os.path.abspath(os.curdir) - modules = {} - option_dict = self.distribution.get_option_dict('build_sphinx') - source_dir = os.path.join(option_dict['source_dir'][1], 'api') - if not os.path.exists(source_dir): - os.makedirs(source_dir) - for pkg in self.distribution.packages: - if '.' not in pkg: - os.path.walk(pkg, _find_modules, modules) - module_list = modules.keys() - module_list.sort() - autoindex_filename = os.path.join(source_dir, 'autoindex.rst') - with open(autoindex_filename, 'w') as autoindex: - autoindex.write(""".. toctree:: - :maxdepth: 1 - -""") - for module in module_list: - output_filename = os.path.join(source_dir, - "%s.rst" % module) - heading = "The :mod:`%s` Module" % module - underline = "=" * len(heading) - values = dict(module=module, heading=heading, - underline=underline) - - print "Generating %s" % output_filename - with open(output_filename, 'w') as output_file: - output_file.write(_rst_template % values) - autoindex.write(" %s.rst\n" % module) - - def run(self): - if not os.getenv('SPHINX_DEBUG'): - self.generate_autoindex() - - for builder in self.builders: - self.builder = builder - self.finalize_options() - self.project = self.distribution.get_name() - self.version = self.distribution.get_version() - self.release = self.distribution.get_version() - BuildDoc.run(self) - - class LocalBuildLatex(LocalBuildDoc): - builders = ['latex'] - - cmdclass['build_sphinx'] = LocalBuildDoc - cmdclass['build_sphinx_latex'] = LocalBuildLatex - except ImportError: - pass - - return cmdclass - - -def _get_revno(): - """Return the number of commits since the most recent tag. - - We use git-describe to find this out, but if there are no - tags then we fall back to counting commits since the beginning - of time. - """ - describe = _run_shell_command("git describe --always") - if "-" in describe: - return describe.rsplit("-", 2)[-2] - - # no tags found - revlist = _run_shell_command("git rev-list --abbrev-commit HEAD") - return len(revlist.splitlines()) - - -def get_version_from_git(pre_version): - """Return a version which is equal to the tag that's on the current - revision if there is one, or tag plus number of additional revisions - if the current revision has no tag.""" - - if os.path.isdir('.git'): - if pre_version: - try: - return _run_shell_command( - "git describe --exact-match", - throw_on_error=True).replace('-', '.') - except Exception: - sha = _run_shell_command("git log -n1 --pretty=format:%h") - return "%s.a%s.g%s" % (pre_version, _get_revno(), sha) - else: - return _run_shell_command( - "git describe --always").replace('-', '.') - return None - - -def get_version_from_pkg_info(package_name): - """Get the version from PKG-INFO file if we can.""" - try: - pkg_info_file = open('PKG-INFO', 'r') - except (IOError, OSError): - return None - try: - pkg_info = email.message_from_file(pkg_info_file) - except email.MessageError: - return None - # Check to make sure we're in our own dir - if pkg_info.get('Name', None) != package_name: - return None - return pkg_info.get('Version', None) - - -def get_version(package_name, pre_version=None): - """Get the version of the project. First, try getting it from PKG-INFO, if - it exists. If it does, that means we're in a distribution tarball or that - install has happened. Otherwise, if there is no PKG-INFO file, pull the - version from git. - - We do not support setup.py version sanity in git archive tarballs, nor do - we support packagers directly sucking our git repo into theirs. We expect - that a source tarball be made from our git repo - or that if someone wants - to make a source tarball from a fork of our repo with additional tags in it - that they understand and desire the results of doing that. - """ - version = os.environ.get("OSLO_PACKAGE_VERSION", None) - if version: - return version - version = get_version_from_pkg_info(package_name) - if version: - return version - version = get_version_from_git(pre_version) - if version: - return version - raise Exception("Versioning for this project requires either an sdist" - " tarball, or access to an upstream git repository.") diff --git a/setup.cfg b/setup.cfg index 11c72013c2..1990b2c5a3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,207 @@ +[metadata] +name = python-openstackclient +summary = OpenStack Command-line Client +description-file = + README.rst +author = OpenStack +author-email = openstack-dev@lists.openstack.org +home-page = http://www.openstack.org/ +classifier = + Environment :: OpenStack + Intended Audience :: Information Technology + Intended Audience :: System Administrators + License :: OSI Approved :: Apache Software License + Operating System :: POSIX :: Linux + Programming Language :: Python + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 2.6 + +[files] +packages = + openstackclient + +[global] +setup-hooks = + pbr.hooks.setup_hook + +[entry_points] +console_scripts = + openstack = openstackclient.shell:main + +openstack.cli = + +openstack.identity.v3_0 = + endpoint_create = openstackclient.identity.v2_0.endpoint:CreateEndpoint + endpoint_delete = openstackclient.identity.v2_0.endpoint:DeleteEndpoint + endpoint_list = openstackclient.identity.v2_0.endpoint:ListEndpoint + endpoint_show = openstackclient.identity.v2_0.endpoint:ShowEndpoint + + role_add = openstackclient.identity.v2_0.role:AddRole + role_create = openstackclient.identity.v2_0.role:CreateRole + role_delete = openstackclient.identity.v2_0.role:DeleteRole + role_list =openstackclient.identity.v2_0.role:ListRole + role_remove = openstackclient.identity.v2_0.role:RemoveRole + role_show =openstackclient.identity.v2_0.role:ShowRole + + service_create = openstackclient.identity.v2_0.service:CreateService + service_delete = openstackclient.identity.v2_0.service:DeleteService + service_list =openstackclient.identity.v2_0.service:ListService + service_show =openstackclient.identity.v2_0.service:ShowService + + tenant_create = openstackclient.identity.v2_0.tenant:CreateTenant + tenant_delete = openstackclient.identity.v2_0.tenant:DeleteTenant + tenant_list = openstackclient.identity.v2_0.tenant:ListTenant + tenant_set = openstackclient.identity.v2_0.tenant:SetTenant + tenant_show = openstackclient.identity.v2_0.tenant:ShowTenant + + user_role_list = openstackclient.identity.v2_0.role:ListUserRole + + user_create = openstackclient.identity.v2_0.user:CreateUser + user_delete = openstackclient.identity.v2_0.user:DeleteUser + user_list = openstackclient.identity.v2_0.user:ListUser + user_set = openstackclient.identity.v2_0.user:SetUser + user_show = openstackclient.identity.v2_0.user:ShowUser + +openstack.identity.v3 = + credential_create = openstackclient.identity.v3.credential:CreateCredential + credential_delete = openstackclient.identity.v3.credential:DeleteCredential + credential_list = openstackclient.identity.v3.credential:ListCredential + credential_set = openstackclient.identity.v3.credential:SetCredential + credential_show = openstackclient.identity.v3.credential:ShowCredential + + domain_create = openstackclient.identity.v3.domain:CreateDomain + domain_delete = openstackclient.identity.v3.domain:DeleteDomain + domain_list = openstackclient.identity.v3.domain:ListDomain + domain_set = openstackclient.identity.v3.domain:SetDomain + domain_show = openstackclient.identity.v3.domain:ShowDomain + + endpoint_create = openstackclient.identity.v3.endpoint:CreateEndpoint + endpoint_delete = openstackclient.identity.v3.endpoint:DeleteEndpoint + endpoint_set = openstackclient.identity.v3.endpoint:SetEndpoint + endpoint_show = openstackclient.identity.v3.endpoint:ShowEndpoint + endpoint_list = openstackclient.identity.v3.endpoint:ListEndpoint + + group_create = openstackclient.identity.v3.group:CreateGroup + group_delete = openstackclient.identity.v3.group:DeleteGroup + group_list = openstackclient.identity.v3.group:ListGroup + group_set = openstackclient.identity.v3.group:SetGroup + group_show = openstackclient.identity.v3.group:ShowGroup + + policy_create = openstackclient.identity.v3.policy:CreatePolicy + policy_delete = openstackclient.identity.v3.policy:DeletePolicy + policy_list = openstackclient.identity.v3.policy:ListPolicy + policy_set = openstackclient.identity.v3.policy:SetPolicy + policy_show = openstackclient.identity.v3.policy:ShowPolicy + + project_create = openstackclient.identity.v3.project:CreateProject + project_delete = openstackclient.identity.v3.project:DeleteProject + project_list = openstackclient.identity.v3.project:ListProject + project_set = openstackclient.identity.v3.project:SetProject + project_show = openstackclient.identity.v3.project:ShowProject + + role_add = openstackclient.identity.v3.role:AddRole + role_create = openstackclient.identity.v3.role:CreateRole + role_delete = openstackclient.identity.v3.role:DeleteRole + role_list = openstackclient.identity.v3.role:ListRole + role_show = openstackclient.identity.v3.role:ShowRole + role_set = openstackclient.identity.v3.role:SetRole + + service_create = openstackclient.identity.v3.service:CreateService + service_delete = openstackclient.identity.v3.service:DeleteService + service_list = openstackclient.identity.v3.service:ListService + service_show = openstackclient.identity.v3.service:ShowService + service_set = openstackclient.identity.v3.service:SetService + + user_create = openstackclient.identity.v3.user:CreateUser + user_delete = openstackclient.identity.v3.user:DeleteUser + user_list = openstackclient.identity.v3.user:ListUser + user_set = openstackclient.identity.v3.user:SetUser + user_show = openstackclient.identity.v3.user:ShowUser + +openstack.image.v1 = + image_create = openstackclient.image.v1.image:CreateImage + +openstack.image.v2 = + image_delete = openstackclient.image.v2.image:DeleteImage + image_list = openstackclient.image.v2.image:ListImage + image_save = openstackclient.image.v2.image:SaveImage + image_show = openstackclient.image.v2.image:ShowImage + +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_service_list = openstackclient.compute.v2.service:ListService + compute_service_set = openstackclient.compute.v2.service:SetService + + console_log_show = openstackclient.compute.v2.console:ShowConsoleLog + console_url_show = openstackclient.compute.v2.console:ShowConsoleURL + + ip_fixed_add = openstackclient.compute.v2.fixedip:AddFixedIP + ip_fixed_remove = openstackclient.compute.v2.fixedip:RemoveFixedIP + + flavor_create = openstackclient.compute.v2.flavor:CreateFlavor + flavor_delete = openstackclient.compute.v2.flavor:DeleteFlavor + flavor_list = openstackclient.compute.v2.flavor:ListFlavor + flavor_show = openstackclient.compute.v2.flavor:ShowFlavor + + ip_floating_add = openstackclient.compute.v2.floatingip:AddFloatingIP + ip_floating_create = openstackclient.compute.v2.floatingip:CreateFloatingIP + ip_floating_delete = openstackclient.compute.v2.floatingip:DeleteFloatingIP + ip_floating_list = openstackclient.compute.v2.floatingip:ListFloatingIP + ip_floating_remove = openstackclient.compute.v2.floatingip:RemoveFloatingIP + + ip_floating_pool_list = openstackclient.compute.v2.floatingippool:ListFloatingIPPool + + host_list = openstackclient.compute.v2.host:ListHost + host_show = openstackclient.compute.v2.host:ShowHost + + hypervisor_list = openstackclient.compute.v2.hypervisor:ListHypervisor + hypervisor_show = openstackclient.compute.v2.hypervisor:ShowHypervisor + + keypair_create = openstackclient.compute.v2.keypair:CreateKeypair + keypair_delete = openstackclient.compute.v2.keypair:DeleteKeypair + keypair_list = openstackclient.compute.v2.keypair:ListKeypair + keypair_show = openstackclient.compute.v2.keypair:ShowKeypair + + server_create = openstackclient.compute.v2.server:CreateServer + server_delete = openstackclient.compute.v2.server:DeleteServer + server_list = openstackclient.compute.v2.server:ListServer + server_pause = openstackclient.compute.v2.server:PauseServer + server_reboot = openstackclient.compute.v2.server:RebootServer + server_rebuild = openstackclient.compute.v2.server:RebuildServer + server_resume = openstackclient.compute.v2.server:ResumeServer + server_show = openstackclient.compute.v2.server:ShowServer + server_suspend = openstackclient.compute.v2.server:SuspendServer + server_unpause = openstackclient.compute.v2.server:UnpauseServer + +openstack.volume.v1 = + quota_list = openstackclient.volume.v1.quota:ListQuota + quota_set = openstackclient.volume.v1.quota:SetQuota + quota_show = openstackclient.volume.v1.quota:ShowQuota + + snapshot_create = openstackclient.volume.v1.snapshot:CreateSnapshot + snapshot_delete = openstackclient.volume.v1.snapshot:DeleteSnapshot + snapshot_list = openstackclient.volume.v1.snapshot:ListSnapshot + snapshot_set = openstackclient.volume.v1.snapshot:SetSnapshot + snapshot_show = openstackclient.volume.v1.snapshot:ShowSnapshot + + volume_create = openstackclient.volume.v1.volume:CreateVolume + volume_delete = openstackclient.volume.v1.volume:DeleteVolume + volume_list = openstackclient.volume.v1.volume:ListVolume + volume_set = openstackclient.volume.v1.volume:SetVolume + volume_show = openstackclient.volume.v1.volume:ShowVolume + volume_unset = openstackclient.volume.v1.volume:UnsetVolume + + volume_type_create = openstackclient.volume.v1.type:CreateVolumeType + volume_type_delete = openstackclient.volume.v1.type:DeleteVolumeType + volume_type_list = openstackclient.volume.v1.type:ListVolumeType + volume_type_set = openstackclient.volume.v1.type:SetVolumeType + volume_type_unset = openstackclient.volume.v1.type:UnsetVolumeType + [build_sphinx] source-dir = doc/source build-dir = doc/build diff --git a/setup.py b/setup.py index 9181fe5739..3144d17a05 100644 --- a/setup.py +++ b/setup.py @@ -1,280 +1,21 @@ -# Copyright 2012-2013 OpenStack, LLC. +#!/usr/bin/env python +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # -# 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 +# 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 +# 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 os +# 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 setuptools -from openstackclient.openstack.common import setup - - -project = "python-openstackclient" -requires = setup.parse_requirements() -dependency_links = setup.parse_dependency_links() - - -def read(fname): - return open(os.path.join(os.path.dirname(__file__), fname)).read() - - setuptools.setup( - name=project, - version=setup.get_version(project), - description="OpenStack command-line client", - long_description=read('README.rst'), - url='https://github.com/openstack/python-openstackclient', - license="Apache License, Version 2.0", - author='OpenStack Client Contributors', - author_email='openstack@lists.launchpad.net', - packages=setuptools.find_packages(exclude=['tests', 'tests.*']), - classifiers=[ - 'Development Status :: 2 - Pre-Alpha', - 'Environment :: Console', - 'Environment :: OpenStack', - 'Intended Audience :: Developers', - 'Intended Audience :: Information Technology', - 'License :: OSI Approved :: Apache Software License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - ], - install_requires=requires, - dependency_links=dependency_links, - cmdclass=setup.get_cmdclass(), - entry_points={ - 'console_scripts': ['openstack=openstackclient.shell:main'], - 'openstack.cli': [ - ], - 'openstack.identity.v3_0': [ - 'endpoint_create=' - 'openstackclient.identity.v2_0.endpoint:CreateEndpoint', - 'endpoint_delete=' - 'openstackclient.identity.v2_0.endpoint:DeleteEndpoint', - 'endpoint_list=' - 'openstackclient.identity.v2_0.endpoint:ListEndpoint', - 'endpoint_show=' - 'openstackclient.identity.v2_0.endpoint:ShowEndpoint', - - 'role_add=' - 'openstackclient.identity.v2_0.role:AddRole', - 'role_create=' - 'openstackclient.identity.v2_0.role:CreateRole', - 'role_delete=' - 'openstackclient.identity.v2_0.role:DeleteRole', - 'role_list=openstackclient.identity.v2_0.role:ListRole', - 'role_remove=' - 'openstackclient.identity.v2_0.role:RemoveRole', - 'role_show=openstackclient.identity.v2_0.role:ShowRole', - - 'service_create=' - 'openstackclient.identity.v2_0.service:CreateService', - 'service_delete=' - 'openstackclient.identity.v2_0.service:DeleteService', - 'service_list=openstackclient.identity.v2_0.service:ListService', - 'service_show=openstackclient.identity.v2_0.service:ShowService', - - 'tenant_create=' - 'openstackclient.identity.v2_0.tenant:CreateTenant', - 'tenant_delete=' - 'openstackclient.identity.v2_0.tenant:DeleteTenant', - 'tenant_list=openstackclient.identity.v2_0.tenant:ListTenant', - 'tenant_set=openstackclient.identity.v2_0.tenant:SetTenant', - 'tenant_show=openstackclient.identity.v2_0.tenant:ShowTenant', - - 'user_role_list=openstackclient.identity.v2_0.role:ListUserRole', - - 'user_create=' - 'openstackclient.identity.v2_0.user:CreateUser', - 'user_delete=' - 'openstackclient.identity.v2_0.user:DeleteUser', - 'user_list=openstackclient.identity.v2_0.user:ListUser', - 'user_set=openstackclient.identity.v2_0.user:SetUser', - 'user_show=openstackclient.identity.v2_0.user:ShowUser', - ], - 'openstack.identity.v3': [ - 'credential_create=' - 'openstackclient.identity.v3.credential:CreateCredential', - 'credential_delete=' - 'openstackclient.identity.v3.credential:DeleteCredential', - 'credential_list=' - 'openstackclient.identity.v3.credential:ListCredential', - 'credential_set=' - 'openstackclient.identity.v3.credential:SetCredential', - 'credential_show=' - 'openstackclient.identity.v3.credential:ShowCredential', - - 'domain_create=openstackclient.identity.v3.domain:CreateDomain', - 'domain_delete=openstackclient.identity.v3.domain:DeleteDomain', - 'domain_list=openstackclient.identity.v3.domain:ListDomain', - 'domain_set=openstackclient.identity.v3.domain:SetDomain', - 'domain_show=openstackclient.identity.v3.domain:ShowDomain', - - 'endpoint_create=' - 'openstackclient.identity.v3.endpoint:CreateEndpoint', - 'endpoint_delete=' - 'openstackclient.identity.v3.endpoint:DeleteEndpoint', - 'endpoint_set=openstackclient.identity.v3.endpoint:SetEndpoint', - 'endpoint_show=openstackclient.identity.v3.endpoint:ShowEndpoint', - 'endpoint_list=openstackclient.identity.v3.endpoint:ListEndpoint', - - 'group_create=openstackclient.identity.v3.group:CreateGroup', - 'group_delete=openstackclient.identity.v3.group:DeleteGroup', - 'group_list=openstackclient.identity.v3.group:ListGroup', - 'group_set=openstackclient.identity.v3.group:SetGroup', - 'group_show=openstackclient.identity.v3.group:ShowGroup', - - 'policy_create=openstackclient.identity.v3.policy:CreatePolicy', - 'policy_delete=openstackclient.identity.v3.policy:DeletePolicy', - 'policy_list=openstackclient.identity.v3.policy:ListPolicy', - 'policy_set=openstackclient.identity.v3.policy:SetPolicy', - 'policy_show=openstackclient.identity.v3.policy:ShowPolicy', - - 'project_create=' - 'openstackclient.identity.v3.project:CreateProject', - 'project_delete=' - 'openstackclient.identity.v3.project:DeleteProject', - 'project_list=openstackclient.identity.v3.project:ListProject', - 'project_set=openstackclient.identity.v3.project:SetProject', - 'project_show=openstackclient.identity.v3.project:ShowProject', - - 'role_add=openstackclient.identity.v3.role:AddRole', - 'role_create=' - 'openstackclient.identity.v3.role:CreateRole', - 'role_delete=' - 'openstackclient.identity.v3.role:DeleteRole', - 'role_list=openstackclient.identity.v3.role:ListRole', - 'role_show=openstackclient.identity.v3.role:ShowRole', - 'role_set=openstackclient.identity.v3.role:SetRole', - - 'service_create=' - 'openstackclient.identity.v3.service:CreateService', - 'service_delete=' - 'openstackclient.identity.v3.service:DeleteService', - 'service_list=openstackclient.identity.v3.service:ListService', - 'service_show=openstackclient.identity.v3.service:ShowService', - 'service_set=openstackclient.identity.v3.service:SetService', - - 'user_create=' - 'openstackclient.identity.v3.user:CreateUser', - 'user_delete=' - 'openstackclient.identity.v3.user:DeleteUser', - 'user_list=openstackclient.identity.v3.user:ListUser', - 'user_set=openstackclient.identity.v3.user:SetUser', - 'user_show=openstackclient.identity.v3.user:ShowUser', - ], - 'openstack.image.v1': [ - 'image_create=openstackclient.image.v1.image:CreateImage', - ], - 'openstack.image.v2': [ - 'image_delete=openstackclient.image.v2.image:DeleteImage', - 'image_list=openstackclient.image.v2.image:ListImage', - 'image_save=openstackclient.image.v2.image:SaveImage', - 'image_show=openstackclient.image.v2.image:ShowImage', - ], - '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_service_list=' - 'openstackclient.compute.v2.service:ListService', - 'compute_service_set=' - 'openstackclient.compute.v2.service:SetService', - - 'console_log_show=' - 'openstackclient.compute.v2.console:ShowConsoleLog', - 'console_url_show=' - 'openstackclient.compute.v2.console:ShowConsoleURL', - - 'ip_fixed_add=openstackclient.compute.v2.fixedip:AddFixedIP', - 'ip_fixed_remove=openstackclient.compute.v2.fixedip:RemoveFixedIP', - - 'flavor_create=openstackclient.compute.v2.flavor:CreateFlavor', - 'flavor_delete=openstackclient.compute.v2.flavor:DeleteFlavor', - 'flavor_list=openstackclient.compute.v2.flavor:ListFlavor', - 'flavor_show=openstackclient.compute.v2.flavor:ShowFlavor', - - 'ip_floating_add=' - 'openstackclient.compute.v2.floatingip:AddFloatingIP', - 'ip_floating_create=' - 'openstackclient.compute.v2.floatingip:CreateFloatingIP', - 'ip_floating_delete=' - 'openstackclient.compute.v2.floatingip:DeleteFloatingIP', - 'ip_floating_list=' - 'openstackclient.compute.v2.floatingip:ListFloatingIP', - 'ip_floating_remove=' - 'openstackclient.compute.v2.floatingip:RemoveFloatingIP', - - 'ip_floating_pool_list=' - 'openstackclient.compute.v2.floatingippool:ListFloatingIPPool', - - 'host_list=openstackclient.compute.v2.host:ListHost', - 'host_show=openstackclient.compute.v2.host:ShowHost', - - 'hypervisor_list=' - 'openstackclient.compute.v2.hypervisor:ListHypervisor', - 'hypervisor_show=' - 'openstackclient.compute.v2.hypervisor:ShowHypervisor', - - 'keypair_create=' - 'openstackclient.compute.v2.keypair:CreateKeypair', - 'keypair_delete=' - 'openstackclient.compute.v2.keypair:DeleteKeypair', - 'keypair_list=' - 'openstackclient.compute.v2.keypair:ListKeypair', - 'keypair_show=' - 'openstackclient.compute.v2.keypair:ShowKeypair', - - 'server_create=openstackclient.compute.v2.server:CreateServer', - 'server_delete=openstackclient.compute.v2.server:DeleteServer', - 'server_list=openstackclient.compute.v2.server:ListServer', - 'server_pause=openstackclient.compute.v2.server:PauseServer', - 'server_reboot=openstackclient.compute.v2.server:RebootServer', - 'server_rebuild=openstackclient.compute.v2.server:RebuildServer', - 'server_resume=openstackclient.compute.v2.server:ResumeServer', - 'server_show=openstackclient.compute.v2.server:ShowServer', - 'server_suspend=openstackclient.compute.v2.server:SuspendServer', - 'server_unpause=openstackclient.compute.v2.server:UnpauseServer', - ], - 'openstack.volume.v1': [ - 'quota_list=openstackclient.volume.v1.quota:ListQuota', - 'quota_set=openstackclient.volume.v1.quota:SetQuota', - 'quota_show=openstackclient.volume.v1.quota:ShowQuota', - - 'snapshot_create=' - 'openstackclient.volume.v1.snapshot:CreateSnapshot', - 'snapshot_delete=' - 'openstackclient.volume.v1.snapshot:DeleteSnapshot', - 'snapshot_list=openstackclient.volume.v1.snapshot:ListSnapshot', - 'snapshot_set=openstackclient.volume.v1.snapshot:SetSnapshot', - 'snapshot_show=openstackclient.volume.v1.snapshot:ShowSnapshot', - - 'volume_create=openstackclient.volume.v1.volume:CreateVolume', - 'volume_delete=openstackclient.volume.v1.volume:DeleteVolume', - 'volume_list=openstackclient.volume.v1.volume:ListVolume', - 'volume_set=openstackclient.volume.v1.volume:SetVolume', - 'volume_show=openstackclient.volume.v1.volume:ShowVolume', - 'volume_unset=openstackclient.volume.v1.volume:UnsetVolume', - - 'volume_type_create=' - 'openstackclient.volume.v1.type:CreateVolumeType', - 'volume_type_delete=' - 'openstackclient.volume.v1.type:DeleteVolumeType', - 'volume_type_list=openstackclient.volume.v1.type:ListVolumeType', - 'volume_type_set=openstackclient.volume.v1.type:SetVolumeType', - 'volume_type_unset=openstackclient.volume.v1.type:UnsetVolumeType', - ] - } -) + setup_requires=['d2to1', 'pbr>=0.5,<0.6'], + d2to1=True) diff --git a/tools/pip-requires b/tools/pip-requires index 9b71d402bf..fe30e2dd91 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -1,3 +1,5 @@ +d2to1>=0.2.10,<0.3 +pbr>=0.5,<0.6 cliff keyring pycrypto From 02a4f16f16019a8e4efa20c183d89c030147f0bb Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Fri, 17 May 2013 06:42:48 -0500 Subject: [PATCH 0073/3614] Add domain and description to user for v3 identity * splitting the changes seen in 27142 to a few new patches * this one will just update v3 user to have description and domain Change-Id: I9b4c365703da27e26ddc702f37cf5928e19cebdc --- openstackclient/identity/v3/user.py | 45 ++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 7bd3706593..5e6282eb8c 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -53,6 +53,16 @@ def get_parser(self, prog_name): metavar='', help='New default project name or ID', ) + parser.add_argument( + '--domain', + metavar='', + help='New default domain name or ID', + ) + parser.add_argument( + '--description', + metavar='', + help='Description for new user', + ) enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', @@ -72,17 +82,27 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) identity_client = self.app.client_manager.identity + if parsed_args.project: project_id = utils.find_resource( identity_client.projects, parsed_args.project).id else: project_id = None + + if parsed_args.domain: + domain_id = utils.find_resource( + identity_client.domains, parsed_args.domain).id + else: + domain_id = None + user = identity_client.users.create( parsed_args.name, + domain_id, + project_id, parsed_args.password, parsed_args.email, - project_id=project_id, - enabled=parsed_args.enabled, + parsed_args.description, + parsed_args.enabled ) info = {} @@ -138,7 +158,8 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) if parsed_args.long: - columns = ('ID', 'Name', 'Project Id', 'Email', 'Enabled') + columns = ('ID', 'Name', 'Project Id', 'Domain Id', + 'Description', 'Email', 'Enabled') else: columns = ('ID', 'Name') data = self.app.client_manager.identity.users.list() @@ -177,10 +198,20 @@ def get_parser(self, prog_name): metavar='', help='New user email address', ) + parser.add_argument( + '--domain', + metavar='', + help='New domain name or ID', + ) parser.add_argument( '--project', metavar='', - help='New default project name or ID', + help='New project name or ID', + ) + parser.add_argument( + '--description', + metavar='', + help='New description', ) enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( @@ -208,10 +239,16 @@ def take_action(self, parsed_args): kwargs['name'] = parsed_args.name if parsed_args.email: kwargs['email'] = parsed_args.email + if parsed_args.description: + kwargs['description'] = parsed_args.description if parsed_args.project: project_id = utils.find_resource( identity_client.projects, parsed_args.project).id kwargs['projectId'] = project_id + if parsed_args.domain: + domain_id = utils.find_resource( + identity_client.domains, parsed_args.domain).id + kwargs['domainId'] = domain_id if 'enabled' in parsed_args: kwargs['enabled'] = parsed_args.enabled From ea9ec1c6bc00ee90b288808f6d48d2ed420cf4b5 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 11 Apr 2013 16:45:34 -0500 Subject: [PATCH 0074/3614] Tweak volume commands and add k=v argparse action Basic cleanups: * change metadata to property * add new KeyValueAction to parse the property options * multiple properties can be set using multiple --property args * consistent formatting * do lookups for volume args Change-Id: Ib6c43f01ad46b395aee8c61e886f42e2a5f5573e --- openstackclient/common/parseractions.py | 34 ++++++++ openstackclient/volume/v1/type.py | 36 +++++--- openstackclient/volume/v1/volume.py | 107 ++++++++++++++---------- tests/common/test_parseractions.py | 104 +++++++++++++++++++++++ 4 files changed, 223 insertions(+), 58 deletions(-) create mode 100644 openstackclient/common/parseractions.py create mode 100644 tests/common/test_parseractions.py diff --git a/openstackclient/common/parseractions.py b/openstackclient/common/parseractions.py new file mode 100644 index 0000000000..f111c26427 --- /dev/null +++ b/openstackclient/common/parseractions.py @@ -0,0 +1,34 @@ +# 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. +# + +"""argparse Custom Actions""" + +import argparse + + +class KeyValueAction(argparse.Action): + """A custom action to parse arguments as key=value pairs. + Ensures that dest is a dict + """ + def __call__(self, parser, namespace, values, option_string=None): + # Make sure we have an empty dict rather than None + if getattr(namespace, self.dest, None) is None: + setattr(namespace, self.dest, {}) + + # Add value if an assignment else remove it + if '=' in values: + getattr(namespace, self.dest, {}).update([values.split('=', 1)]) + else: + getattr(namespace, self.dest, {}).pop(values, None) diff --git a/openstackclient/volume/v1/type.py b/openstackclient/volume/v1/type.py index 9d79f8b3e3..e146ee3f61 100644 --- a/openstackclient/volume/v1/type.py +++ b/openstackclient/volume/v1/type.py @@ -21,6 +21,7 @@ from cliff import lister from cliff import show +from openstackclient.common import parseractions from openstackclient.common import utils @@ -118,21 +119,22 @@ def get_parser(self, prog_name): help='Volume type name or ID to update', ) parser.add_argument( - 'meta_data', + '--property', metavar='', - help='meta-data to add to volume type', + action=parseractions.KeyValueAction, + help='Property to add/change for this volume type ' + '(repeat option to set multiple properties)', ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) - - meta = dict(v.split('=') for v in parsed_args.meta_data.split(' ')) volume_client = self.app.client_manager.volume volume_type = utils.find_resource( volume_client.volume_types, parsed_args.volume_type) - volume_type.set_keys(meta) + if parsed_args.property: + volume_type.set_keys(parsed_args.property) return @@ -148,12 +150,15 @@ def get_parser(self, prog_name): parser.add_argument( 'volume_type', metavar='', - help='Type ID or name to update', + help='Type ID or name to remove', ) parser.add_argument( - 'meta_data', + '--property', metavar='', - help='meta-data to remove from volume type (key only)', + action='append', + default=[], + help='Property key to remove from volume ' + '(repeat option to remove multiple properties)', ) return parser @@ -161,12 +166,17 @@ def take_action(self, 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) - - key_list = [] - key_list.append(parsed_args.meta_data) - volume_type.unset_keys(key_list) + volume_client.volume_types, + parsed_args.volume_type, + ) + if parsed_args.property: + volume_client.volumes.delete_metadata( + volume_type.id, + parsed_args.property, + ) + else: + self.app.log.error("No changes requested\n") return diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index f9641c5482..43253c4092 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -16,12 +16,12 @@ """Volume v1 Volume action implementations""" import logging -import sys from cliff import command from cliff import lister from cliff import show +from openstackclient.common import parseractions from openstackclient.common import utils @@ -63,32 +63,34 @@ def get_parser(self, prog_name): parser.add_argument( '--user-id', metavar='', - help='User id derived from context', + help='Override user id derived from context (admin only)', ) parser.add_argument( '--project-id', metavar='', - help='Project id derived from context', + help='Override project id derived from context (admin only)', ) parser.add_argument( '--availability-zone', metavar='', - help='Availability Zone to use', + help='Availability zone to use', ) parser.add_argument( '--property', metavar='', - help='Optional property to set on volume creation', + action=parseractions.KeyValueAction, + help='Property to store for this volume ' + '(repeat option to set multiple properties)', ) parser.add_argument( - '--image-ref', - metavar='', - help='reference to an image stored in glance', + '--image', + metavar='', + help='Reference to a stored image', ) parser.add_argument( - '--source-volid', - metavar='', - help='ID of source volume to clone from', + '--source', + metavar='', + help='Source for volume clone', ) return parser @@ -98,22 +100,25 @@ def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - meta = None - if parsed_args.meta_data: - meta = dict(v.split('=') for v in parsed_args.meta_data.split(' ')) + source_volume = None + if parsed_args.source: + source_volume = utils.find_resource( + volume_client.volumes, + parsed_args.source, + ).id volume = volume_client.volumes.create( parsed_args.size, parsed_args.snapshot_id, - parsed_args.source_volid, + source_volume, parsed_args.name, parsed_args.description, parsed_args.volume_type, parsed_args.user_id, parsed_args.project_id, parsed_args.availability_zone, - meta, - parsed_args.image_ref + parsed_args.property, + parsed_args.image ) return zip(*sorted(volume._info.iteritems())) @@ -175,13 +180,13 @@ def get_parser(self, prog_name): '--all-tenants', action='store_true', default=False, - help='Display information from all tenants (Admin-only)', + help='Display information from all tenants (admin only)', ) parser.add_argument( '--long', action='store_true', default=False, - help='Display meta-data', + help='Display properties', ) return parser @@ -221,19 +226,25 @@ def get_parser(self, prog_name): parser.add_argument( 'volume', metavar='', - help='Name or ID of volume to change') + help='Name or ID of volume to change', + ) parser.add_argument( '--name', - metavar='', - help='New volume name') + metavar='', + help='New volume name', + ) parser.add_argument( '--description', - metavar='', - help='New volume description') + metavar='', + help='New volume description', + ) parser.add_argument( - '--meta-data', + '--property', metavar='', - help='meta-data to add to volume') + action=parseractions.KeyValueAction, + help='Property to add/change for this volume ' + '(repeat option to set multiple properties)', + ) return parser def take_action(self, parsed_args): @@ -241,21 +252,22 @@ def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume = utils.find_resource(volume_client.volumes, parsed_args.volume) - meta = None if parsed_args.property: - meta = dict(v.split('=') for v in parsed_args.property.split(' ')) - volume_client.volumes.set_metadata(volume.id, meta) + print "property: %s" % parsed_args.property + volume_client.volumes.set_metadata(volume.id, parsed_args.property) kwargs = {} if parsed_args.name: kwargs['display_name'] = parsed_args.name if parsed_args.description: kwargs['display_description'] = parsed_args.description + if kwargs: + print "kwargs: %s" % kwargs + volume_client.volumes.update(volume.id, **kwargs) + + if not kwargs and not parsed_args.property: + self.app.log.error("No changes requested\n") - if not kwargs and not meta: - sys.stdout.write("Volume not updated, no arguments present \n") - return - volume_client.volumes.update(volume.id, **kwargs) return @@ -270,7 +282,8 @@ def get_parser(self, prog_name): parser.add_argument( 'volume', metavar='', - help='Name or ID of volume to display') + help='Name or ID of volume to display', + ) return parser def take_action(self, parsed_args): @@ -292,11 +305,16 @@ def get_parser(self, prog_name): parser.add_argument( 'volume', metavar='', - help='Name or ID of volume to change') + help='Name or ID of volume to change', + ) parser.add_argument( - '--meta-data', + '--property', metavar='', - help='meta-data to remove from volume (key only)') + action='append', + default=[], + help='Property key to remove from volume ' + '(repeat to set multiple values)', + ) return parser def take_action(self, parsed_args): @@ -305,14 +323,13 @@ def take_action(self, parsed_args): volume = utils.find_resource( volume_client.volumes, parsed_args.volume) - if not parsed_args.meta_data: - sys.stdout.write("Volume not updated, no arguments present \n") - return - - key_list = [] - key_list.append(parsed_args.meta_data) - volume_client.volumes.delete_metadata(volume.id, key_list) - + if parsed_args.property: + volume_client.volumes.delete_metadata( + volume.id, + parsed_args.property, + ) + else: + self.app.log.error("No changes requested\n") return diff --git a/tests/common/test_parseractions.py b/tests/common/test_parseractions.py new file mode 100644 index 0000000000..f48c4d721b --- /dev/null +++ b/tests/common/test_parseractions.py @@ -0,0 +1,104 @@ +# 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. +# + +import argparse + +from openstackclient.common import parseractions +from tests import utils + + +class TestKeyValueAction(utils.TestCase): + def test_good_values(self): + parser = argparse.ArgumentParser() + + # Set up our typical usage + parser.add_argument( + '--property', + metavar='', + action=parseractions.KeyValueAction, + help='Property to store for this volume ' + '(repeat option to set multiple properties)', + ) + + results = parser.parse_args([ + '--property', 'red=', + '--property', 'green=100%', + '--property', 'blue=50%', + ]) + + actual = getattr(results, 'property', {}) + # All should pass through unmolested + expect = {'red': '', 'green': '100%', 'blue': '50%'} + self.assertDictEqual(expect, actual) + + def test_default_values(self): + parser = argparse.ArgumentParser() + + # Set up our typical usage + parser.add_argument( + '--property', + metavar='', + action=parseractions.KeyValueAction, + default={'green': '20%', 'format': '#rgb'}, + help='Property to store for this volume ' + '(repeat option to set multiple properties)', + ) + + results = parser.parse_args([ + '--property', 'red=', + '--property', 'green=100%', + '--property', 'blue=50%', + ]) + + actual = getattr(results, 'property', {}) + # Verify green default is changed, format default is unchanged + expect = {'red': '', 'green': '100%', 'blue': '50%', 'format': '#rgb'} + self.assertDictEqual(expect, actual) + + def test_error_values(self): + parser = argparse.ArgumentParser() + + # Set up our typical usage + parser.add_argument( + '--property', + metavar='', + action=parseractions.KeyValueAction, + default={'green': '20%', 'blue': '40%'}, + help='Property to store for this volume ' + '(repeat option to set multiple properties)', + ) + + results = parser.parse_args([ + '--property', 'red', + '--property', 'green=100%', + '--property', 'blue', + ]) + + failhere = None + actual = getattr(results, 'property', {}) + # Verify non-existant red key + try: + failhere = actual['red'] + except Exception as e: + self.assertTrue(type(e) == KeyError) + # Verify removal of blue key + try: + failhere = actual['blue'] + except Exception as e: + self.assertTrue(type(e) == KeyError) + # There should be no red or blue + expect = {'green': '100%'} + self.assertDictEqual(expect, actual) + self.assertEqual(failhere, None) From bf588ed9c4db954316b62133cc936cfcb0ea7de1 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 24 May 2013 16:59:35 -0500 Subject: [PATCH 0075/3614] Fix identity v2.0 entry point Change-Id: Ifae91a612fcd8b66660b93f6ea81d37e0f1bce1d --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 1990b2c5a3..eeb0c7fc28 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,7 +31,7 @@ console_scripts = openstack.cli = -openstack.identity.v3_0 = +openstack.identity.v2_0 = endpoint_create = openstackclient.identity.v2_0.endpoint:CreateEndpoint endpoint_delete = openstackclient.identity.v2_0.endpoint:DeleteEndpoint endpoint_list = openstackclient.identity.v2_0.endpoint:ListEndpoint From bac0718764ac5e7cc22adf24d035db0be0cf90d9 Mon Sep 17 00:00:00 2001 From: Zhenguo Niu Date: Wed, 29 May 2013 17:36:23 +0800 Subject: [PATCH 0076/3614] Rename requires files to standard names. Rename tools/pip-requires to requirements.txt and tools/test-requires to test-requirements.txt. These are standard files, and tools in the general world are growing intelligence about them. Change-Id: I903213fda94a833335abaa7ad9a90bbb688ec15a Fixes: bug #1179008 --- tools/pip-requires => requirements.txt | 0 tools/test-requires => test-requirements.txt | 0 tools/install_venv.py | 4 ++-- tox.ini | 4 ++-- 4 files changed, 4 insertions(+), 4 deletions(-) rename tools/pip-requires => requirements.txt (100%) rename tools/test-requires => test-requirements.txt (100%) diff --git a/tools/pip-requires b/requirements.txt similarity index 100% rename from tools/pip-requires rename to requirements.txt diff --git a/tools/test-requires b/test-requirements.txt similarity index 100% rename from tools/test-requires rename to test-requirements.txt diff --git a/tools/install_venv.py b/tools/install_venv.py index d247c02376..a5891d6710 100644 --- a/tools/install_venv.py +++ b/tools/install_venv.py @@ -48,8 +48,8 @@ def print_help(): def main(argv): root = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) venv = os.path.join(root, ".venv") - pip_requires = os.path.join(root, "tools", "pip-requires") - test_requires = os.path.join(root, "tools", "test-requires") + pip_requires = os.path.join(root, "requirements.txt") + test_requires = os.path.join(root, "test-requirements.txt") py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1]) project = "python-openstackclient" install = install_venv.InstallVenv(root, venv, pip_requires, test_requires, diff --git a/tox.ini b/tox.ini index f6de18d137..ece55df54b 100644 --- a/tox.ini +++ b/tox.ini @@ -6,8 +6,8 @@ setenv = VIRTUAL_ENV={envdir} LANG=en_US.UTF-8 LANGUAGE=en_US:en LC_ALL=C -deps = -r{toxinidir}/tools/pip-requires - -r{toxinidir}/tools/test-requires +deps = -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt commands = python setup.py testr --testr-args='{posargs}' [testenv:pep8] From 7183a11f09f2d1958ed6251ae227afc8cbb8cc45 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Sat, 1 Jun 2013 19:58:22 -0500 Subject: [PATCH 0077/3614] python3: Introduce py33 to tox.ini Introduce py33 to tox.ini to make testing with python3 easier. Change-Id: I7a775ac51e0bc0a5929184af47d51ea1cc4e3219 Signed-off-by: Chuck Short --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index ece55df54b..61bdc9dec4 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py26,py27,pep8 +envlist = py26,py27,py33,pep8 [testenv] setenv = VIRTUAL_ENV={envdir} From ea31333c49224faac1530f258c15bdeed95a94e8 Mon Sep 17 00:00:00 2001 From: Hugh Saunders Date: Mon, 3 Jun 2013 11:10:09 +0100 Subject: [PATCH 0078/3614] Add volume backup commands Change-Id: Iedccd329ff6fb3155eb29649cd0bc84cfc5ebedf Implements: blueprint volume-backup --- openstackclient/volume/v1/backup.py | 171 ++++++++++++++++++++++++++++ setup.cfg | 6 + 2 files changed, 177 insertions(+) create mode 100644 openstackclient/volume/v1/backup.py diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py new file mode 100644 index 0000000000..cd1afd60b7 --- /dev/null +++ b/openstackclient/volume/v1/backup.py @@ -0,0 +1,171 @@ +# 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. +# + +"""Volume v1 Backup action implementations""" + +import logging + +from cliff import command +from cliff import lister +from cliff import show + +from openstackclient.common import utils + + +class CreateBackup(show.ShowOne): + """Create backup command""" + + api = 'volume' + log = logging.getLogger(__name__ + '.CreateBackup') + + def get_parser(self, prog_name): + parser = super(CreateBackup, self).get_parser(prog_name) + parser.add_argument( + 'volume', + metavar='', + help='The name or ID of the volume to backup', + ) + parser.add_argument( + '--container', + metavar='', + required=False, + help='Optional Backup container name.', + ) + parser.add_argument( + '--name', + metavar='', + required=False, + help='Name of the backup', + ) + parser.add_argument( + '--description', + metavar='', + help='Description of the backup', + ) + return parser + + def take_action(self, 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 + backup = volume_client.backups.create( + volume_id, + parsed_args.volume, + parsed_args.name, + parsed_args.description + ) + + backup._info.pop('links') + return zip(*sorted(backup._info.iteritems())) + + +class DeleteBackup(command.Command): + """Delete backup command""" + + api = 'volume' + log = logging.getLogger(__name__ + '.DeleteBackup') + + def get_parser(self, prog_name): + parser = super(DeleteBackup, self).get_parser(prog_name) + parser.add_argument( + 'backup', + metavar='', + help='Name or ID of backup to delete', + ) + return parser + + def take_action(self, 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 + volume_client.backups.delete(backup_id) + return + + +class ListBackup(lister.Lister): + """List backup command""" + + api = 'volume' + log = logging.getLogger(__name__ + '.ListBackup') + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + columns = ( + 'ID', + 'Display Name', + 'Display Description', + 'Status', + 'Size' + ) + data = self.app.client_manager.volume.backups.list() + return (columns, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data)) + + +class RestoreBackup(command.Command): + """Restore backup command""" + + api = 'volume' + log = logging.getLogger(__name__ + '.RestoreBackup') + + def get_parser(self, prog_name): + parser = super(RestoreBackup, self).get_parser(prog_name) + parser.add_argument( + 'backup', + metavar='', + help='ID of backup to restore') + parser.add_argument( + 'volume', + metavar='', + help='ID of volume to restore to') + return parser + + def take_action(self, 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) + destination_volume = utils.find_resource(volume_client.volumes, + parsed_args.volume) + return volume_client.restores.restore(backup.id, + destination_volume.id) + + +class ShowBackup(show.ShowOne): + """Show backup command""" + + api = 'volume' + log = logging.getLogger(__name__ + '.ShowBackup') + + def get_parser(self, prog_name): + parser = super(ShowBackup, self).get_parser(prog_name) + parser.add_argument( + 'backup', + metavar='', + help='Name or ID of backup to display') + return parser + + def take_action(self, 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) + backup._info.pop('links') + return zip(*sorted(backup._info.iteritems())) diff --git a/setup.cfg b/setup.cfg index eeb0c7fc28..535fb40507 100644 --- a/setup.cfg +++ b/setup.cfg @@ -189,6 +189,12 @@ openstack.volume.v1 = snapshot_set = openstackclient.volume.v1.snapshot:SetSnapshot snapshot_show = openstackclient.volume.v1.snapshot:ShowSnapshot + backup_create = openstackclient.volume.v1.backup:CreateBackup + backup_delete = openstackclient.volume.v1.backup:DeleteBackup + backup_list = openstackclient.volume.v1.backup:ListBackup + backup_restore = openstackclient.volume.v1.backup:RestoreBackup + backup_show = openstackclient.volume.v1.backup:ShowBackup + volume_create = openstackclient.volume.v1.volume:CreateVolume volume_delete = openstackclient.volume.v1.volume:DeleteVolume volume_list = openstackclient.volume.v1.volume:ListVolume From d542def039d42d774d61800073bc9d1ba86ff4a2 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Tue, 11 Jun 2013 11:33:28 -0700 Subject: [PATCH 0079/3614] Remove explicit distribute depend. Causes issues with the recent re-merge with setuptools. Advice from upstream is to stop doing explicit depends. Change-Id: Ic83dca8c17799335b76311d1d657adcd1dc94f01 --- test-requirements.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/test-requirements.txt b/test-requirements.txt index 1eb2509c73..392348e248 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -4,8 +4,6 @@ pyflakes==0.7.2 flake8==2.0 hacking>=0.5.3,<0.6 -distribute>=0.6.24 - coverage discover fixtures>=0.3.12 From bc3039a43c5d4f783e8ef435ec43d4eee5e2f83a Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 10 Jun 2013 14:06:41 -0500 Subject: [PATCH 0080/3614] Fix py26 tests: assertDictEqual assertDictEqual is not present in py26 so shim it in here stolen from python-keystoneclient/tests/test_auth_token_middleware.py Change-Id: Ifd5990a8c03d11ee93cddc2f61653255970d974c --- tests/utils.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/tests/utils.py b/tests/utils.py index 75515fad56..3e24ff4a99 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -16,12 +16,13 @@ import os import fixtures +import sys import testtools class TestCase(testtools.TestCase): def setUp(self): - super(TestCase, self).setUp() + testtools.TestCase.setUp(self) if (os.environ.get("OS_STDOUT_NOCAPTURE") == "True" and os.environ.get("OS_STDOUT_NOCAPTURE") == "1"): @@ -32,3 +33,27 @@ def setUp(self): os.environ.get("OS_STDERR_NOCAPTURE") == "1"): stderr = self.useFixture(fixtures.StringStream("stderr")).stream self.useFixture(fixtures.MonkeyPatch("sys.stderr", stderr)) + + # 2.6 doesn't have the assert dict equals so make sure that it exists + if tuple(sys.version_info)[0:2] < (2, 7): + + def assertIsInstance(self, obj, cls, msg=None): + """Same as self.assertTrue(isinstance(obj, cls)), with a nicer + default message + """ + if not isinstance(obj, cls): + standardMsg = '%s is not an instance of %r' % (obj, cls) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertDictEqual(self, d1, d2, msg=None): + # Simple version taken from 2.7 + self.assertIsInstance(d1, dict, + 'First argument is not a dictionary') + self.assertIsInstance(d2, dict, + 'Second argument is not a dictionary') + if d1 != d2: + if msg: + self.fail(msg) + else: + standardMsg = '%r != %r' % (d1, d2) + self.fail(standardMsg) From dd3aa0b671fec6e6c23ae69016fdc06e7c529297 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Tue, 21 May 2013 00:57:19 -0500 Subject: [PATCH 0081/3614] Add OAuth support for Identity V3 Added client side support for: * consumer CRUD * create request token * create access token * authroize request token blueprint: delegated-auth-via-oauth Change-Id: I8d325fcab07ac4dfd124a6e55053ded8d6bf662e --- openstackclient/identity/v3/oauth.py | 264 +++++++++++++++++++++++++++ setup.cfg | 11 ++ 2 files changed, 275 insertions(+) create mode 100644 openstackclient/identity/v3/oauth.py diff --git a/openstackclient/identity/v3/oauth.py b/openstackclient/identity/v3/oauth.py new file mode 100644 index 0000000000..0b5ae4dba7 --- /dev/null +++ b/openstackclient/identity/v3/oauth.py @@ -0,0 +1,264 @@ +# 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. +# + +"""Identity v3 OAuth action implementations""" + +import logging +import sys + +from cliff import command +from cliff import lister +from cliff import show + +from openstackclient.common import utils + + +class AuthorizeRequestToken(show.ShowOne): + """Authorize request token command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.AuthorizeRequestToken') + + def get_parser(self, prog_name): + parser = super(AuthorizeRequestToken, self).get_parser(prog_name) + parser.add_argument( + '--request-key', + metavar='', + help='Consumer key', + required=True + ) + parser.add_argument( + '--user-token', + metavar='', + help='Token of authorizing user', + required=True + ) + parser.add_argument( + '--roles', + metavar='', + help='Role to authorize', + required=True + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + oauth_client = self.app.client_manager.identity.oauths + + verifier_pin = oauth_client.authorize_request_token( + parsed_args.request_key, parsed_args.user_token, + parsed_args.roles) + info = {} + info.update(verifier_pin._info) + return zip(*sorted(info.iteritems())) + + +class CreateAccessToken(show.ShowOne): + """Create access token command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.CreateAccessToken') + + def get_parser(self, prog_name): + parser = super(CreateAccessToken, self).get_parser(prog_name) + parser.add_argument( + '--consumer-key', + metavar='', + help='Consumer key', + required=True + ) + parser.add_argument( + '--request-key', + metavar='', + help='Consumer key', + required=True + ) + parser.add_argument( + '--verifier', + metavar='', + help='Verifier Pin', + required=True + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + oauth_client = self.app.client_manager.identity.oauths + access_token = oauth_client.create_access_token( + parsed_args.consumer_key, parsed_args.request_key, + parsed_args.verifier) + info = {} + info.update(access_token._info) + return zip(*sorted(info.iteritems())) + + +class CreateConsumer(show.ShowOne): + """Create consumer command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.CreateConsumer') + + def get_parser(self, prog_name): + parser = super(CreateConsumer, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar='', + help='New consumer name', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + identity_client = self.app.client_manager.identity + consumer = identity_client.oauths.create_consumer( + parsed_args.name + ) + info = {} + info.update(consumer._info) + return zip(*sorted(info.iteritems())) + + +class CreateRequestToken(show.ShowOne): + """Create request token command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.CreateRequestToken') + + def get_parser(self, prog_name): + parser = super(CreateRequestToken, self).get_parser(prog_name) + parser.add_argument( + '--consumer-key', + metavar='', + help='Consumer key', + required=True + ) + parser.add_argument( + '--roles', + metavar='', + help='Role requested', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + oauth_client = self.app.client_manager.identity.oauths + request_token = oauth_client.create_request_token( + parsed_args.consumer_key, parsed_args.roles) + info = {} + info.update(request_token._info) + return zip(*sorted(info.iteritems())) + + +class DeleteConsumer(command.Command): + """Delete consumer command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.DeleteConsumer') + + def get_parser(self, prog_name): + parser = super(DeleteConsumer, self).get_parser(prog_name) + parser.add_argument( + 'consumer', + metavar='', + help='Name or ID of consumer to delete', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + identity_client = self.app.client_manager.identity + consumer = utils.find_resource( + identity_client.oauths, parsed_args.consumer) + identity_client.oauths.delete_consumer(consumer.id) + return + + +class ListConsumer(lister.Lister): + """List consumer command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.ListConsumer') + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + columns = ('ID', 'Name', 'Consumer Key', 'Consumer Secret') + data = self.app.client_manager.identity.oauths.list_consumers() + return (columns, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data)) + + +class SetConsumer(command.Command): + """Set consumer command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.SetConsumer') + + def get_parser(self, prog_name): + parser = super(SetConsumer, self).get_parser(prog_name) + parser.add_argument( + 'consumer', + metavar='', + help='Name or ID of consumer to change', + ) + parser.add_argument( + '--name', + metavar='', + help='New consumer name', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + identity_client = self.app.client_manager.identity + consumer = utils.find_resource( + identity_client.oauths, parsed_args.consumer) + kwargs = {} + if parsed_args.name: + kwargs['name'] = parsed_args.name + + if not len(kwargs): + sys.stdout.write("Consumer not updated, no arguments present") + return + identity_client.oauths.update_consumer(consumer.id, **kwargs) + return + + +class ShowConsumer(show.ShowOne): + """Show consumer command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.ShowConsumer') + + def get_parser(self, prog_name): + parser = super(ShowConsumer, self).get_parser(prog_name) + parser.add_argument( + 'consumer', + metavar='', + help='Name or ID of consumer to display', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + identity_client = self.app.client_manager.identity + consumer = utils.find_resource( + identity_client.oauths, parsed_args.consumer) + + info = {} + info.update(consumer._info) + return zip(*sorted(info.iteritems())) diff --git a/setup.cfg b/setup.cfg index 535fb40507..4b2142dd2c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -64,6 +64,14 @@ openstack.identity.v2_0 = user_show = openstackclient.identity.v2_0.user:ShowUser openstack.identity.v3 = + access_token_create = openstackclient.identity.v3.oauth:CreateAccessToken + + consumer_create = openstackclient.identity.v3.oauth:CreateConsumer + consumer_delete = openstackclient.identity.v3.oauth:DeleteConsumer + consumer_list = openstackclient.identity.v3.oauth:ListConsumer + consumer_set = openstackclient.identity.v3.oauth:SetConsumer + consumer_show = openstackclient.identity.v3.oauth:ShowConsumer + credential_create = openstackclient.identity.v3.credential:CreateCredential credential_delete = openstackclient.identity.v3.credential:DeleteCredential credential_list = openstackclient.identity.v3.credential:ListCredential @@ -100,6 +108,9 @@ openstack.identity.v3 = project_set = openstackclient.identity.v3.project:SetProject project_show = openstackclient.identity.v3.project:ShowProject + request_token_authorize = openstackclient.identity.v3.oauth:AuthorizeRequestToken + request_token_create = openstackclient.identity.v3.oauth:CreateRequestToken + role_add = openstackclient.identity.v3.role:AddRole role_create = openstackclient.identity.v3.role:CreateRole role_delete = openstackclient.identity.v3.role:DeleteRole From 108f78d98994435fbd234196b3f23948b4c8bca6 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Sun, 30 Jun 2013 23:30:27 -0400 Subject: [PATCH 0082/3614] Remove python3 incompatible exception syntax. Change-Id: I5f0687a83362ceebf03bae4a0a5b109aad7e5200 --- openstackclient/compute/v2/server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index a2496fad12..8b134aa92f 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -236,7 +236,7 @@ def take_action(self, parsed_args): dst, src = f.split('=', 1) try: files[dst] = open(src) - except IOError, e: + except IOError as e: raise exceptions.CommandError("Can't open '%s': %s" % (src, e)) if parsed_args.min > parsed_args.max: @@ -251,7 +251,7 @@ def take_action(self, parsed_args): if parsed_args.user_data: try: userdata = open(parsed_args.user_data) - except IOError, e: + except IOError as e: raise exceptions.CommandError("Can't open '%s': %s" % (parsed_args.user_data, e)) From 196daf859b1557fba24dbcf9870d3a834da86916 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Sun, 30 Jun 2013 23:01:17 -0400 Subject: [PATCH 0083/3614] Move tests into project package. There are several reasons for this. One is that the majority of OpenStack packages behave this way. The second is that it makes writing software that extends something easier to test (which is a clear usecase for openstackclient) And third, tests/__init__.py implies a global package named "tests" - which I'm pretty sure we're not providing. Change-Id: Ic708ffd92aea78c2ffc1a8579af0587af4fca4ff --- .testr.conf | 2 +- {tests => openstackclient/tests}/__init__.py | 0 {tests => openstackclient/tests}/common/test_clientmanager.py | 2 +- .../tests}/common/test_commandmanager.py | 2 +- {tests => openstackclient/tests}/common/test_parseractions.py | 2 +- {tests => openstackclient/tests}/compute/__init__.py | 0 {tests => openstackclient/tests}/compute/test_compute.py | 4 ++-- {tests => openstackclient/tests}/identity/__init__.py | 0 {tests => openstackclient/tests}/identity/test_identity.py | 4 ++-- {tests => openstackclient/tests}/image/__init__.py | 0 {tests => openstackclient/tests}/image/test_image.py | 4 ++-- {tests => openstackclient/tests}/test_shell.py | 2 +- {tests => openstackclient/tests}/utils.py | 0 {tests => openstackclient/tests}/volume/__init__.py | 0 {tests => openstackclient/tests}/volume/test_volume.py | 4 ++-- 15 files changed, 13 insertions(+), 13 deletions(-) rename {tests => openstackclient/tests}/__init__.py (100%) rename {tests => openstackclient/tests}/common/test_clientmanager.py (96%) rename {tests => openstackclient/tests}/common/test_commandmanager.py (98%) rename {tests => openstackclient/tests}/common/test_parseractions.py (98%) rename {tests => openstackclient/tests}/compute/__init__.py (100%) rename {tests => openstackclient/tests}/compute/test_compute.py (93%) rename {tests => openstackclient/tests}/identity/__init__.py (100%) rename {tests => openstackclient/tests}/identity/test_identity.py (92%) rename {tests => openstackclient/tests}/image/__init__.py (100%) rename {tests => openstackclient/tests}/image/test_image.py (93%) rename {tests => openstackclient/tests}/test_shell.py (99%) rename {tests => openstackclient/tests}/utils.py (100%) rename {tests => openstackclient/tests}/volume/__init__.py (100%) rename {tests => openstackclient/tests}/volume/test_volume.py (93%) diff --git a/.testr.conf b/.testr.conf index 2109af6ce0..d152a5aa93 100644 --- a/.testr.conf +++ b/.testr.conf @@ -1,4 +1,4 @@ [DEFAULT] -test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 ${PYTHON:-python} -m subunit.run discover -t ./ ./tests $LISTOPT $IDOPTION +test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 ${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list diff --git a/tests/__init__.py b/openstackclient/tests/__init__.py similarity index 100% rename from tests/__init__.py rename to openstackclient/tests/__init__.py diff --git a/tests/common/test_clientmanager.py b/openstackclient/tests/common/test_clientmanager.py similarity index 96% rename from tests/common/test_clientmanager.py rename to openstackclient/tests/common/test_clientmanager.py index fe99c599f1..395f6ec33d 100644 --- a/tests/common/test_clientmanager.py +++ b/openstackclient/tests/common/test_clientmanager.py @@ -14,7 +14,7 @@ # from openstackclient.common import clientmanager -from tests import utils +from openstackclient.tests import utils class Container(object): diff --git a/tests/common/test_commandmanager.py b/openstackclient/tests/common/test_commandmanager.py similarity index 98% rename from tests/common/test_commandmanager.py rename to openstackclient/tests/common/test_commandmanager.py index f0a0b3418d..4953c29754 100644 --- a/tests/common/test_commandmanager.py +++ b/openstackclient/tests/common/test_commandmanager.py @@ -16,7 +16,7 @@ import mock from openstackclient.common import commandmanager -from tests import utils +from openstackclient.tests import utils class FakeCommand(object): diff --git a/tests/common/test_parseractions.py b/openstackclient/tests/common/test_parseractions.py similarity index 98% rename from tests/common/test_parseractions.py rename to openstackclient/tests/common/test_parseractions.py index f48c4d721b..705e7e9c36 100644 --- a/tests/common/test_parseractions.py +++ b/openstackclient/tests/common/test_parseractions.py @@ -16,7 +16,7 @@ import argparse from openstackclient.common import parseractions -from tests import utils +from openstackclient.tests import utils class TestKeyValueAction(utils.TestCase): diff --git a/tests/compute/__init__.py b/openstackclient/tests/compute/__init__.py similarity index 100% rename from tests/compute/__init__.py rename to openstackclient/tests/compute/__init__.py diff --git a/tests/compute/test_compute.py b/openstackclient/tests/compute/test_compute.py similarity index 93% rename from tests/compute/test_compute.py rename to openstackclient/tests/compute/test_compute.py index 2673f994c7..9d2061d284 100644 --- a/tests/compute/test_compute.py +++ b/openstackclient/tests/compute/test_compute.py @@ -17,7 +17,7 @@ from openstackclient.common import clientmanager from openstackclient.compute import client as compute_client -from tests import utils +from openstackclient.tests import utils AUTH_TOKEN = "foobar" @@ -37,7 +37,7 @@ def setUp(self): api_version = {"compute": "2"} compute_client.API_VERSIONS = { - "2": "tests.compute.test_compute.FakeClient" + "2": "openstackclient.tests.compute.test_compute.FakeClient" } self.cm = clientmanager.ClientManager(token=AUTH_TOKEN, diff --git a/tests/identity/__init__.py b/openstackclient/tests/identity/__init__.py similarity index 100% rename from tests/identity/__init__.py rename to openstackclient/tests/identity/__init__.py diff --git a/tests/identity/test_identity.py b/openstackclient/tests/identity/test_identity.py similarity index 92% rename from tests/identity/test_identity.py rename to openstackclient/tests/identity/test_identity.py index 52bbd22218..04bbd20f49 100644 --- a/tests/identity/test_identity.py +++ b/openstackclient/tests/identity/test_identity.py @@ -15,7 +15,7 @@ from openstackclient.common import clientmanager from openstackclient.identity import client as identity_client -from tests import utils +from openstackclient.tests import utils AUTH_TOKEN = "foobar" @@ -35,7 +35,7 @@ def setUp(self): api_version = {"identity": "2.0"} identity_client.API_VERSIONS = { - "2.0": "tests.identity.test_identity.FakeClient" + "2.0": "openstackclient.tests.identity.test_identity.FakeClient" } self.cm = clientmanager.ClientManager(token=AUTH_TOKEN, diff --git a/tests/image/__init__.py b/openstackclient/tests/image/__init__.py similarity index 100% rename from tests/image/__init__.py rename to openstackclient/tests/image/__init__.py diff --git a/tests/image/test_image.py b/openstackclient/tests/image/test_image.py similarity index 93% rename from tests/image/test_image.py rename to openstackclient/tests/image/test_image.py index 60b2142943..f4c8d72e37 100644 --- a/tests/image/test_image.py +++ b/openstackclient/tests/image/test_image.py @@ -17,7 +17,7 @@ from openstackclient.common import clientmanager from openstackclient.image import client as image_client -from tests import utils +from openstackclient.tests import utils AUTH_TOKEN = "foobar" @@ -38,7 +38,7 @@ def setUp(self): api_version = {"image": "2"} image_client.API_VERSIONS = { - "2": "tests.image.test_image.FakeClient" + "2": "openstackclient.tests.image.test_image.FakeClient" } self.cm = clientmanager.ClientManager(token=AUTH_TOKEN, diff --git a/tests/test_shell.py b/openstackclient/tests/test_shell.py similarity index 99% rename from tests/test_shell.py rename to openstackclient/tests/test_shell.py index d0eb5b0d39..f479b11e4b 100644 --- a/tests/test_shell.py +++ b/openstackclient/tests/test_shell.py @@ -17,7 +17,7 @@ import os from openstackclient import shell -from tests import utils +from openstackclient.tests import utils DEFAULT_USERNAME = "username" diff --git a/tests/utils.py b/openstackclient/tests/utils.py similarity index 100% rename from tests/utils.py rename to openstackclient/tests/utils.py diff --git a/tests/volume/__init__.py b/openstackclient/tests/volume/__init__.py similarity index 100% rename from tests/volume/__init__.py rename to openstackclient/tests/volume/__init__.py diff --git a/tests/volume/test_volume.py b/openstackclient/tests/volume/test_volume.py similarity index 93% rename from tests/volume/test_volume.py rename to openstackclient/tests/volume/test_volume.py index 8c60dd1242..548fbf7413 100644 --- a/tests/volume/test_volume.py +++ b/openstackclient/tests/volume/test_volume.py @@ -16,8 +16,8 @@ import mock from openstackclient.common import clientmanager +from openstackclient.tests import utils from openstackclient.volume import client as volume_client -from tests import utils AUTH_TOKEN = "foobar" @@ -38,7 +38,7 @@ def setUp(self): api_version = {"volume": "1"} volume_client.API_VERSIONS = { - "1": "tests.volume.test_volume.FakeClient" + "1": "openstackclient.tests.volume.test_volume.FakeClient" } self.cm = clientmanager.ClientManager(token=AUTH_TOKEN, From 9dbf46b8370becd1100837ee1868c7532e632b81 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Mon, 10 Jun 2013 09:19:54 -0500 Subject: [PATCH 0084/3614] Add methods for user and group interactions * Add user to group * Contains user in group * Remove user from group Change-Id: If5219fa9d4761d7b97950c39556b3e1b8aab6517 --- openstackclient/identity/v3/group.py | 117 +++++++++++++++++++++++++++ setup.cfg | 3 + 2 files changed, 120 insertions(+) diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index 0562b76630..de4fe2d276 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -25,6 +25,84 @@ from openstackclient.common import utils +class AddUserToGroup(command.Command): + """Add user to group""" + + api = 'identity' + log = logging.getLogger(__name__ + '.AddUserToGroup') + + def get_parser(self, prog_name): + parser = super(AddUserToGroup, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar='', + help='Group name or ID that user will be added to', + ) + parser.add_argument( + 'user', + metavar='', + help='User name or ID to add to group', + ) + return parser + + def take_action(self, 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 + group_id = utils.find_resource(identity_client.groups, + parsed_args.group).id + + try: + identity_client.users.add_to_group(user_id, group_id) + except Exception: + sys.stderr.write("%s not added to group %s\n" % + (parsed_args.user, parsed_args.group)) + else: + sys.stdout.write("%s added to group %s\n" % + (parsed_args.user, parsed_args.group)) + + +class CheckUserInGroup(command.Command): + """Checks that user is in a specific group""" + + api = 'identity' + log = logging.getLogger(__name__ + '.CheckUserInGroup') + + def get_parser(self, prog_name): + parser = super(CheckUserInGroup, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar='', + help='Group name or ID that user will be added to', + ) + parser.add_argument( + 'user', + metavar='', + help='User name or ID to add to group', + ) + return parser + + def take_action(self, 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 + group_id = utils.find_resource(identity_client.groups, + parsed_args.group).id + + try: + identity_client.users.check_in_group(user_id, group_id) + except Exception: + sys.stderr.write("%s not in group %s\n" % + (parsed_args.user, parsed_args.group)) + else: + sys.stdout.write("%s in group %s\n" % + (parsed_args.user, parsed_args.group)) + + class CreateGroup(show.ShowOne): """Create group command""" @@ -117,6 +195,45 @@ def take_action(self, parsed_args): ) for s in data)) +class RemoveUserFromGroup(command.Command): + """Remove user to group""" + + api = 'identity' + log = logging.getLogger(__name__ + '.RemoveUserFromGroup') + + def get_parser(self, prog_name): + parser = super(RemoveUserFromGroup, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar='', + help='Group name or ID that user will be removed from', + ) + parser.add_argument( + 'user', + metavar='', + help='User name or ID to remove from group', + ) + return parser + + def take_action(self, 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 + group_id = utils.find_resource(identity_client.groups, + parsed_args.group).id + + try: + identity_client.users.remove_from_group(user_id, group_id) + except Exception: + sys.stderr.write("%s not removed from group %s\n" % + (parsed_args.user, parsed_args.group)) + else: + sys.stdout.write("%s removed from group %s\n" % + (parsed_args.user, parsed_args.group)) + + class SetGroup(command.Command): """Set group command""" diff --git a/setup.cfg b/setup.cfg index 4b2142dd2c..a70306ea0c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -90,9 +90,12 @@ openstack.identity.v3 = endpoint_show = openstackclient.identity.v3.endpoint:ShowEndpoint endpoint_list = openstackclient.identity.v3.endpoint:ListEndpoint + group_add_user = openstackclient.identity.v3.group:AddUserToGroup + group_contains_user = openstackclient.identity.v3.group:CheckUserInGroup group_create = openstackclient.identity.v3.group:CreateGroup group_delete = openstackclient.identity.v3.group:DeleteGroup group_list = openstackclient.identity.v3.group:ListGroup + group_remove_user = openstackclient.identity.v3.group:RemoveUserFromGroup group_set = openstackclient.identity.v3.group:SetGroup group_show = openstackclient.identity.v3.group:ShowGroup From f29a849ffcc203e7038fd2a026e0f755dcf2c1fc Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 18 Apr 2013 17:49:42 -0500 Subject: [PATCH 0085/3614] Finish up v3 role commands * Add remove role * Add --role to group list * Add --role to user list * Fix groups in AddRole() * Remove the tweaks to utils.find_resource for domains; will address that across domains, projects, users and groups in another patch. I want to nail down the structure of these commands and get that into place Change-Id: I8673dd8221ef88978dada5a2833c187026bdb31a --- openstackclient/common/utils.py | 11 ++- openstackclient/identity/v3/group.py | 95 +++++++++++++++++-- openstackclient/identity/v3/role.py | 135 +++++++++++++++++++++------ openstackclient/identity/v3/user.py | 92 ++++++++++++++++-- openstackclient/shell.py | 15 ++- setup.cfg | 1 + 6 files changed, 304 insertions(+), 45 deletions(-) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 56f9cd17d5..06542887e2 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -24,20 +24,27 @@ def find_resource(manager, name_or_id): """Helper for the _find_* methods.""" - # first try to get entity as integer id + + # Try to get entity as integer id try: if isinstance(name_or_id, int) or name_or_id.isdigit(): return manager.get(int(name_or_id)) except exceptions.NotFound: pass - # now try to get entity as uuid + # 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) + except Exception: + pass + kwargs = {} if 'NAME_ATTR' in manager.resource_class.__dict__: # novaclient does this for oddball resources diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index 0562b76630..3e7368f156 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -89,27 +89,108 @@ def take_action(self, parsed_args): class ListGroup(lister.Lister): - """List group command""" + """List groups and optionally roles assigned to groups""" api = 'identity' log = logging.getLogger(__name__ + '.ListGroup') def get_parser(self, prog_name): parser = super(ListGroup, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar='', + 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 ', + ) + domain_or_project = parser.add_mutually_exclusive_group() + domain_or_project.add_argument( + '--domain', + metavar='', + help='Filter list by [Only valid with --role]', + ) + domain_or_project.add_argument( + '--project', + metavar='', + help='Filter list by [Only valid with --role]', + ) parser.add_argument( '--long', action='store_true', default=False, - help='Additional fields are listed in output') + help='Additional fields are listed in output', + ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) - if parsed_args.long: - columns = ('ID', 'Name', 'Domain ID', 'Description') + 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 ([], []) else: - columns = ('ID', 'Name') - data = self.app.client_manager.identity.groups.list() + # List groups + if parsed_args.long: + columns = ('ID', 'Name', 'Domain ID', 'Description') + else: + columns = ('ID', 'Name') + data = identity_client.groups.list() + return (columns, (utils.get_item_properties( s, columns, @@ -158,7 +239,7 @@ def take_action(self, parsed_args): kwargs['domain'] = domain if not len(kwargs): - sys.stdout.write("Group not updated, no arguments present") + sys.stderr.write("Group not updated, no arguments present") return identity_client.groups.update(group.id, **kwargs) return diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index faff906293..7387509ae2 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -26,7 +26,7 @@ class AddRole(command.Command): - """Add role command""" + """Adds a role to a user or group on a domain or project""" api = 'identity' log = logging.getLogger(__name__ + '.AddRole') @@ -42,23 +42,24 @@ def get_parser(self, prog_name): user_or_group.add_argument( '--user', metavar='', - help='Name or ID of user to assign a role', + help='Name or ID of user to add a role', ) user_or_group.add_argument( '--group', metavar='', - help='Name or ID of group to assign a role', + help='Name or ID of group to add a role', ) domain_or_project = parser.add_mutually_exclusive_group() domain_or_project.add_argument( '--domain', metavar='', - help='Name or ID of domain where user or group resides', + default='default', + help='Name or ID of domain associated with user or group', ) domain_or_project.add_argument( '--project', metavar='', - help='Name or ID of project where user or group resides', + help='Name or ID of project associated with user or group', ) return parser @@ -68,42 +69,40 @@ def take_action(self, parsed_args): if (not parsed_args.user and not parsed_args.domain and not parsed_args.group and not parsed_args.project): - sys.stdout.write("Role not updated, no arguments present \n") + sys.stderr.write("Role not added, no arguments present\n") return role_id = utils.find_resource(identity_client.roles, parsed_args.role).id - if (parsed_args.user and parsed_args.domain): + if parsed_args.user and parsed_args.domain: user = utils.find_resource(identity_client.users, parsed_args.user) domain = utils.find_resource(identity_client.domains, parsed_args.domain) identity_client.roles.grant(role_id, user=user, domain=domain) - return - elif (parsed_args.user and parsed_args.project): + elif parsed_args.user and parsed_args.project: user = utils.find_resource(identity_client.users, parsed_args.user) project = utils.find_resource(identity_client.projects, parsed_args.project) identity_client.roles.grant(role_id, user=user, project=project) - return - elif (parsed_args.group and parsed_args.project): + elif parsed_args.group and parsed_args.domain: + group = utils.find_resource(identity_client.groups, + parsed_args.group) + domain = utils.find_resource(identity_client.domains, + parsed_args.domain) + identity_client.roles.grant(role_id, group=group, domain=domain) + elif parsed_args.group and parsed_args.project: group = utils.find_resource(identity_client.group, parsed_args.group) project = utils.find_resource(identity_client.projects, parsed_args.project) identity_client.roles.grant(role_id, group=group, project=project) - return - elif (parsed_args.group and parsed_args.domain): - group = utils.find_resource(identity_client.group, - parsed_args.group) - domain = utils.find_resource(identity_client.domains, - parsed_args.domain) - identity_client.roles.grant(role_id, group=group, domain=domain) - return else: - return + sys.stderr.write("Role not added, incorrect set of arguments \ + provided. See openstack --help for more details\n") + return class CreateRole(show.ShowOne): @@ -115,15 +114,16 @@ class CreateRole(show.ShowOne): def get_parser(self, prog_name): parser = super(CreateRole, self).get_parser(prog_name) parser.add_argument( - 'role-name', + 'name', metavar='', - help='New role name') + help='New role name', + ) return parser def take_action(self, 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) + role = identity_client.roles.create(parsed_args.name) return zip(*sorted(role._info.iteritems())) @@ -139,7 +139,8 @@ def get_parser(self, prog_name): parser.add_argument( 'role', metavar='', - help='Name or ID of role to delete') + help='Name or ID of role to delete', + ) return parser def take_action(self, parsed_args): @@ -168,6 +169,85 @@ def take_action(self, parsed_args): ) for s in data)) +class RemoveRole(command.Command): + """Remove role command""" + + api = 'identity' + log = logging.getLogger(__name__ + '.RemoveRole') + + def get_parser(self, prog_name): + parser = super(RemoveRole, self).get_parser(prog_name) + parser.add_argument( + 'role', + metavar='', + help='Name or ID of role to remove', + ) + user_or_group = parser.add_mutually_exclusive_group() + user_or_group.add_argument( + '--user', + metavar='', + help='Name or ID of user to remove a role', + ) + user_or_group.add_argument( + '--group', + metavar='', + help='Name or ID of group to remove a role', + ) + domain_or_project = parser.add_mutually_exclusive_group() + domain_or_project.add_argument( + '--domain', + metavar='', + help='Name or ID of domain associated with user or group', + ) + domain_or_project.add_argument( + '--project', + metavar='', + help='Name or ID of project associated with user or group', + ) + return parser + + def take_action(self, 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 + and not parsed_args.group and not parsed_args.project): + sys.stdout.write("Role not updated, no arguments present\n") + return + + role_id = utils.find_resource(identity_client.roles, + parsed_args.role).id + + if parsed_args.user and parsed_args.domain: + user = utils.find_resource(identity_client.users, + parsed_args.user) + domain = utils.find_resource(identity_client.domains, + parsed_args.domain) + identity_client.roles.revoke(role_id, user=user, domain=domain) + elif parsed_args.user and parsed_args.project: + user = utils.find_resource(identity_client.users, + parsed_args.user) + project = utils.find_resource(identity_client.projects, + parsed_args.project) + identity_client.roles.revoke(role_id, user=user, project=project) + elif parsed_args.group and parsed_args.project: + group = utils.find_resource(identity_client.group, + parsed_args.group) + project = utils.find_resource(identity_client.projects, + parsed_args.project) + identity_client.roles.revoke(role_id, group=group, project=project) + elif parsed_args.group and parsed_args.domain: + group = utils.find_resource(identity_client.group, + parsed_args.group) + domain = utils.find_resource(identity_client.domains, + parsed_args.domain) + identity_client.roles.revoke(role_id, group=group, domain=domain) + else: + sys.stderr.write("Role not removed, incorrect set of arguments \ + provided. See openstack --help for more details\n") + return + + class SetRole(command.Command): """Set role command""" @@ -179,7 +259,7 @@ def get_parser(self, prog_name): parser.add_argument( 'role', metavar='', - help='Name or ID of role to change', + help='Name or ID of role to update', ) parser.add_argument( '--name', @@ -195,7 +275,7 @@ def take_action(self, parsed_args): parsed_args.role) if not parsed_args.name: - sys.stdout.write("Role not updated, no arguments present") + sys.stderr.write("Role not updated, no arguments present") return identity_client.roles.update(role_id, parsed_args.name) @@ -213,7 +293,8 @@ def get_parser(self, prog_name): parser.add_argument( 'role', metavar='', - help='Name or ID of role to display') + help='Name or ID of role to display', + ) return parser def take_action(self, parsed_args): diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 5e6282eb8c..53550cda56 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -135,7 +135,7 @@ def take_action(self, parsed_args): class ListUser(lister.Lister): - """List user command""" + """List users and optionally roles assigned to users""" api = 'identity' log = logging.getLogger(__name__ + '.ListUser') @@ -143,9 +143,27 @@ class ListUser(lister.Lister): def get_parser(self, prog_name): parser = super(ListUser, self).get_parser(prog_name) parser.add_argument( + 'user', + metavar='', + 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 ', + ) + domain_or_project = parser.add_mutually_exclusive_group() + domain_or_project.add_argument( + '--domain', + metavar='', + help='Filter list by [Only valid with --role]', + ) + domain_or_project.add_argument( '--project', metavar='', - help='Name or ID of project to filter users', + help='Filter list by [Only valid with --role]', ) parser.add_argument( '--long', @@ -157,12 +175,70 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) - if parsed_args.long: - columns = ('ID', 'Name', 'Project Id', 'Domain Id', - 'Description', 'Email', 'Enabled') + 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 ([], []) else: - columns = ('ID', 'Name') - data = self.app.client_manager.identity.users.list() + # 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() + return (columns, (utils.get_item_properties( s, columns, @@ -253,7 +329,7 @@ def take_action(self, parsed_args): kwargs['enabled'] = parsed_args.enabled if not len(kwargs): - sys.stdout.write("User not updated, no arguments present") + sys.stderr.write("User not updated, no arguments present") return identity_client.users.update(user.id, **kwargs) return diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 35d8255ddb..e5353194cd 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -37,6 +37,7 @@ DEFAULT_IDENTITY_API_VERSION = '2.0' DEFAULT_IMAGE_API_VERSION = '2' DEFAULT_VOLUME_API_VERSION = '1' +DEFAULT_DOMAIN = 'default' def env(*vars, **kwargs): @@ -134,6 +135,15 @@ def build_option_parser(self, description, version): metavar='', default=env('OS_REGION_NAME'), help='Authentication region name (Env: OS_REGION_NAME)') + parser.add_argument( + '--os-default-domain', + metavar='', + default=env( + 'OS_DEFAULT_DOMAIN', + default=DEFAULT_DOMAIN), + help='Default domain ID, default=' + + DEFAULT_DOMAIN + + ' (Env: OS_DEFAULT_DOMAIN)') parser.add_argument( '--os-identity-api-version', metavar='', @@ -304,7 +314,10 @@ def initialize_app(self, argv): else: requests_log.setLevel(logging.WARNING) - # stash selected API versions for later + # Save default domain + self.default_domain = self.options.os_default_domain + + # Stash selected API versions for later self.api_version = { 'compute': self.options.os_compute_api_version, 'identity': self.options.os_identity_api_version, diff --git a/setup.cfg b/setup.cfg index 535fb40507..03583c59d4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -104,6 +104,7 @@ openstack.identity.v3 = role_create = openstackclient.identity.v3.role:CreateRole role_delete = openstackclient.identity.v3.role:DeleteRole role_list = openstackclient.identity.v3.role:ListRole + role_remove = openstackclient.identity.v3.role:RemoveRole role_show = openstackclient.identity.v3.role:ShowRole role_set = openstackclient.identity.v3.role:SetRole From 7298dd5e72700206c4bcabdcbaa3720a4147cbe7 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 3 Jul 2013 12:10:37 -0500 Subject: [PATCH 0086/3614] Add EC2 credentials CRUD ec2 credentials: create, delete, list, show Change-Id: I82ff84ed433cd9a2da9534bf5f584a2e1a3fe68c --- openstackclient/identity/v2_0/ec2creds.py | 185 ++++++++++++++++++++++ setup.cfg | 5 + 2 files changed, 190 insertions(+) create mode 100644 openstackclient/identity/v2_0/ec2creds.py diff --git a/openstackclient/identity/v2_0/ec2creds.py b/openstackclient/identity/v2_0/ec2creds.py new file mode 100644 index 0000000000..6a5d2af30f --- /dev/null +++ b/openstackclient/identity/v2_0/ec2creds.py @@ -0,0 +1,185 @@ +# 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. +# + +"""EC2 Credentials action implementations""" + +import logging + +from cliff import command +from cliff import lister +from cliff import show + +from openstackclient.common import utils + + +class CreateEC2Creds(show.ShowOne): + """Create EC2 credentials""" + + api = "identity" + log = logging.getLogger(__name__ + ".CreateEC2Creds") + + def get_parser(self, prog_name): + parser = super(CreateEC2Creds, self).get_parser(prog_name) + parser.add_argument( + '--project', + metavar='', + help='Specify a project [admin only]', + ) + parser.add_argument( + '--user', + metavar='', + help='Specify a user [admin only]', + ) + 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.project: + project = utils.find_resource( + identity_client.tenants, + parsed_args.project, + ).id + else: + # Get the project from the current auth + project = identity_client.auth_tenant_id + if parsed_args.user: + user = utils.find_resource( + identity_client.users, + parsed_args.user, + ).id + else: + # Get the user from the current auth + user = identity_client.auth_user_id + + creds = identity_client.ec2.create(user, project) + + info = {} + info.update(creds._info) + return zip(*sorted(info.iteritems())) + + +class DeleteEC2Creds(command.Command): + """Delete EC2 credentials""" + + api = 'identity' + log = logging.getLogger(__name__ + '.DeleteEC2Creds') + + def get_parser(self, prog_name): + parser = super(DeleteEC2Creds, self).get_parser(prog_name) + parser.add_argument( + 'access_key', + metavar='', + help='Credentials access key', + ) + parser.add_argument( + '--user', + metavar='', + help='Specify a user [admin only]', + ) + 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.user: + user = utils.find_resource( + identity_client.users, + parsed_args.user, + ).id + else: + # Get the user from the current auth + user = identity_client.auth_user_id + + identity_client.ec2.delete(user, parsed_args.access_key) + + +class ListEC2Creds(lister.Lister): + """List EC2 credentials""" + + api = 'identity' + log = logging.getLogger(__name__ + '.ListEC2Creds') + + def get_parser(self, prog_name): + parser = super(ListEC2Creds, self).get_parser(prog_name) + parser.add_argument( + '--user', + metavar='', + help='Specify a user [admin only]', + ) + 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.user: + user = utils.find_resource( + identity_client.users, + parsed_args.user, + ).id + else: + # Get the user from the current auth + user = identity_client.auth_user_id + + columns = ('Access', 'Secret', 'Project ID', 'User ID') + data = identity_client.ec2.list(user) + return (columns, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data)) + + +class ShowEC2Creds(show.ShowOne): + """Show EC2 credentials""" + + api = 'identity' + log = logging.getLogger(__name__ + '.ShowEC2Creds') + + def get_parser(self, prog_name): + parser = super(ShowEC2Creds, self).get_parser(prog_name) + parser.add_argument( + 'access_key', + metavar='', + help='Credentials access key', + ) + parser.add_argument( + '--user', + metavar='', + help='Specify a user [admin only]', + ) + 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.user: + user = utils.find_resource( + identity_client.users, + parsed_args.user, + ).id + else: + # Get the user from the current auth + user = identity_client.auth_user_id + + creds = identity_client.ec2.get(user, parsed_args.access_key) + + info = {} + info.update(creds._info) + return zip(*sorted(info.iteritems())) diff --git a/setup.cfg b/setup.cfg index 4b2142dd2c..27f3e1d582 100644 --- a/setup.cfg +++ b/setup.cfg @@ -32,6 +32,11 @@ console_scripts = openstack.cli = openstack.identity.v2_0 = + ec2_credentials_create = openstackclient.identity.v2_0.ec2creds:CreateEC2Creds + ec2_credentials_delete = openstackclient.identity.v2_0.ec2creds:DeleteEC2Creds + ec2_credentials_list = openstackclient.identity.v2_0.ec2creds:ListEC2Creds + ec2_credentials_show = openstackclient.identity.v2_0.ec2creds:ShowEC2Creds + endpoint_create = openstackclient.identity.v2_0.endpoint:CreateEndpoint endpoint_delete = openstackclient.identity.v2_0.endpoint:DeleteEndpoint endpoint_list = openstackclient.identity.v2_0.endpoint:ListEndpoint From d7501c352dc95360ba9b432cdec1f1b8b822b0e6 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Fri, 5 Jul 2013 22:30:54 -0400 Subject: [PATCH 0087/3614] Update documentation with info about setup.cfg Change-Id: If87df1e6415d0b70b6605b1d89eda639fc44a0b6 --- doc/source/commands.rst | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 40a2425803..0c278d0525 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -41,20 +41,19 @@ of Cliff's command.Command object. Command Entry Points -------------------- -Commands are added to the client using distribute's entry points in ``setup.py``. +Commands are added to the client using setuptools's entry points in ``setup.cfg``. There is a single common group ``openstack.cli`` for commands that are not versioned, and a group for each combination of OpenStack API and version that is supported. For example, to support Identity API v3 there is a group called ``openstack.identity.v3`` that contains the individual commands. The command entry points have the form:: - "verb_object=fully.qualified.module.vXX.object:VerbObject" + verb_object = fully.qualified.module.vXX.object:VerbObject For example, the 'list user' command fir the Identity API is identified in -``setup.py`` with:: +``setup.cfg`` with:: - 'openstack.identity.v3': [ + openstack.identity.v3 = # ... - 'list_user=openstackclient.identity.v3.user:ListUser', + list_user = openstackclient.identity.v3.user:ListUser # ... - ], From 4022e41c6420cb581da619091bd433a3d63af0ea Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Fri, 5 Jul 2013 22:31:15 -0400 Subject: [PATCH 0088/3614] Sync install_venv_common from oslo Change-Id: I0a57c658e0f89d13963862013793e12ae208c05b --- tools/install_venv_common.py | 106 ++++++++++++++--------------------- 1 file changed, 43 insertions(+), 63 deletions(-) diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py index 5cef255509..f428c1e021 100644 --- a/tools/install_venv_common.py +++ b/tools/install_venv_common.py @@ -1,6 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2013 OpenStack, LLC +# Copyright 2013 OpenStack Foundation # Copyright 2013 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -18,36 +18,34 @@ """Provides methods needed by installation script for OpenStack development virtual environments. +Since this script is used to bootstrap a virtualenv from the system's Python +environment, it should be kept strictly compatible with Python 2.6. + Synced in from openstack-common """ +from __future__ import print_function + +import optparse import os import subprocess import sys -possible_topdir = os.getcwd() -if os.path.exists(os.path.join(possible_topdir, "openstackclient", - "__init__.py")): - sys.path.insert(0, possible_topdir) - - -from openstackclient.openstack.common import cfg - - class InstallVenv(object): - def __init__(self, root, venv, pip_requires, test_requires, py_version, + def __init__(self, root, venv, requirements, + test_requirements, py_version, project): self.root = root self.venv = venv - self.pip_requires = pip_requires - self.test_requires = test_requires + self.requirements = requirements + self.test_requirements = test_requirements self.py_version = py_version self.project = project def die(self, message, *args): - print >> sys.stderr, message % args + print(message % args, file=sys.stderr) sys.exit(1) def check_python_version(self): @@ -58,7 +56,7 @@ def run_command_with_code(self, cmd, redirect_output=True, check_exit_code=True): """Runs a command in an out-of-process shell. - Returns the output of that command. Working directory is ROOT. + Returns the output of that command. Working directory is self.root. """ if redirect_output: stdout = subprocess.PIPE @@ -78,11 +76,13 @@ def run_command(self, cmd, redirect_output=True, check_exit_code=True): def get_distro(self): if (os.path.exists('/etc/fedora-release') or os.path.exists('/etc/redhat-release')): - return Fedora(self.root, self.venv, self.pip_requires, - self.test_requires, self.py_version, self.project) + return Fedora( + self.root, self.venv, self.requirements, + self.test_requirements, self.py_version, self.project) else: - return Distro(self.root, self.venv, self.pip_requires, - self.test_requires, self.py_version, self.project) + return Distro( + self.root, self.venv, self.requirements, + self.test_requirements, self.py_version, self.project) def check_dependencies(self): self.get_distro().install_virtualenv() @@ -94,20 +94,15 @@ def create_virtualenv(self, no_site_packages=True): virtual environment. """ if not os.path.isdir(self.venv): - print 'Creating venv...', + print('Creating venv...', end=' ') if no_site_packages: self.run_command(['virtualenv', '-q', '--no-site-packages', self.venv]) else: self.run_command(['virtualenv', '-q', self.venv]) - print 'done.' - print 'Installing pip in virtualenv...', - if not self.run_command(['tools/with_venv.sh', 'easy_install', - 'pip>1.0']).strip(): - self.die("Failed to install pip.") - print 'done.' + print('done.') else: - print "venv already exists..." + print("venv already exists...") pass def pip_install(self, *args): @@ -116,40 +111,27 @@ def pip_install(self, *args): redirect_output=False) def install_dependencies(self): - print 'Installing dependencies with pip (this can take a while)...' + print('Installing dependencies with pip (this can take a while)...') # First things first, make sure our venv has the latest pip and - # distribute. - # NOTE: we keep pip at version 1.1 since the most recent version causes - # the .venv creation to fail. See: - # https://bugs.launchpad.net/nova/+bug/1047120 - self.pip_install('pip==1.1') - self.pip_install('distribute') + # setuptools. + self.pip_install('pip>=1.3') + self.pip_install('setuptools') - # Install greenlet by hand - just listing it in the requires file does - # not - # get it installed in the right order - self.pip_install('greenlet') - - self.pip_install('-r', self.pip_requires) - self.pip_install('-r', self.test_requires) + self.pip_install('-r', self.requirements) + self.pip_install('-r', self.test_requirements) def post_process(self): self.get_distro().post_process() def parse_args(self, argv): """Parses command-line arguments.""" - cli_opts = [ - cfg.BoolOpt('no-site-packages', - default=False, - short='n', - help="Do not inherit packages from global Python" - "install"), - ] - CLI = cfg.ConfigOpts() - CLI.register_cli_opts(cli_opts) - CLI(argv[1:]) - return CLI + parser = optparse.OptionParser() + parser.add_option('-n', '--no-site-packages', + action='store_true', + help="Do not inherit packages from global Python " + "install") + return parser.parse_args(argv[1:])[0] class Distro(InstallVenv): @@ -163,12 +145,12 @@ def install_virtualenv(self): return if self.check_cmd('easy_install'): - print 'Installing virtualenv via easy_install...', + print('Installing virtualenv via easy_install...', end=' ') if self.run_command(['easy_install', 'virtualenv']): - print 'Succeeded' + print('Succeeded') return else: - print 'Failed' + print('Failed') self.die('ERROR: virtualenv not found.\n\n%s development' ' requires virtualenv, please install it using your' @@ -193,19 +175,16 @@ def check_pkg(self, pkg): return self.run_command_with_code(['rpm', '-q', pkg], check_exit_code=False)[1] == 0 - def yum_install(self, pkg, **kwargs): - print "Attempting to install '%s' via yum" % pkg - self.run_command(['sudo', 'yum', 'install', '-y', pkg], **kwargs) - def apply_patch(self, originalfile, patchfile): - self.run_command(['patch', originalfile, patchfile]) + self.run_command(['patch', '-N', originalfile, patchfile], + check_exit_code=False) def install_virtualenv(self): if self.check_cmd('virtualenv'): return if not self.check_pkg('python-virtualenv'): - self.yum_install('python-virtualenv', check_exit_code=False) + self.die("Please install 'python-virtualenv'.") super(Fedora, self).install_virtualenv() @@ -218,12 +197,13 @@ def post_process(self): This can be removed when the fix is applied upstream. Nova: https://bugs.launchpad.net/nova/+bug/884915 - Upstream: https://bitbucket.org/which_linden/eventlet/issue/89 + Upstream: https://bitbucket.org/eventlet/eventlet/issue/89 + RHEL: https://bugzilla.redhat.com/958868 """ # Install "patch" program if it's not there if not self.check_pkg('patch'): - self.yum_install('patch') + self.die("Please install 'patch'.") # Apply the eventlet patch self.apply_patch(os.path.join(self.venv, 'lib', self.py_version, From 713908385a183cada8c9e77bb74c2ca381a38660 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Wed, 3 Jul 2013 15:26:33 -0500 Subject: [PATCH 0089/3614] Add authenticate method to oauth code Forgot to add one last method when I initially checked in the first set of code; the authenticate method will return an actual keystone token that the user may now use. Also, I added some changes to other methods because the client has been updated. Change-Id: Ie2707689e0df1fb1bc92177f932baf23fe1ca920 --- openstackclient/identity/v3/oauth.py | 107 ++++++++++++++++++++------- setup.cfg | 1 + 2 files changed, 81 insertions(+), 27 deletions(-) diff --git a/openstackclient/identity/v3/oauth.py b/openstackclient/identity/v3/oauth.py index 0b5ae4dba7..d3345f0021 100644 --- a/openstackclient/identity/v3/oauth.py +++ b/openstackclient/identity/v3/oauth.py @@ -25,6 +25,49 @@ from openstackclient.common import utils +class AuthenticateAccessToken(show.ShowOne): + """Authenticate access token - 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='', + help='Consumer key', + required=True + ) + parser.add_argument( + '--consumer-secret', + metavar='', + help='Consumer secret', + required=True + ) + parser.add_argument( + '--access-key', + metavar='', + help='Access token key', + required=True + ) + parser.add_argument( + '--access-secret', + metavar='', + help='Access token secret', + required=True + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + oauth_client = self.app.client_manager.identity.oauth + keystone_token = oauth_client.authenticate( + parsed_args.consumer_key, parsed_args.consumer_secret, + parsed_args.access_key, parsed_args.access_secret) + return zip(*sorted(keystone_token.iteritems())) + + class AuthorizeRequestToken(show.ShowOne): """Authorize request token command""" @@ -39,12 +82,6 @@ def get_parser(self, prog_name): help='Consumer key', required=True ) - parser.add_argument( - '--user-token', - metavar='', - help='Token of authorizing user', - required=True - ) parser.add_argument( '--roles', metavar='', @@ -55,11 +92,10 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) - oauth_client = self.app.client_manager.identity.oauths + oauth_client = self.app.client_manager.identity.oauth verifier_pin = oauth_client.authorize_request_token( - parsed_args.request_key, parsed_args.user_token, - parsed_args.roles) + parsed_args.request_key, parsed_args.roles) info = {} info.update(verifier_pin._info) return zip(*sorted(info.iteritems())) @@ -79,10 +115,22 @@ def get_parser(self, prog_name): help='Consumer key', required=True ) + parser.add_argument( + '--consumer-secret', + metavar='', + help='Consumer secret', + required=True + ) parser.add_argument( '--request-key', metavar='', - help='Consumer key', + help='Request token key', + required=True + ) + parser.add_argument( + '--request-secret', + metavar='', + help='Request token secret', required=True ) parser.add_argument( @@ -95,13 +143,12 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) - oauth_client = self.app.client_manager.identity.oauths + oauth_client = self.app.client_manager.identity.oauth access_token = oauth_client.create_access_token( - parsed_args.consumer_key, parsed_args.request_key, + parsed_args.consumer_key, parsed_args.consumer_secret, + parsed_args.request_key, parsed_args.request_secret, parsed_args.verifier) - info = {} - info.update(access_token._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(access_token.iteritems())) class CreateConsumer(show.ShowOne): @@ -122,7 +169,7 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) identity_client = self.app.client_manager.identity - consumer = identity_client.oauths.create_consumer( + consumer = identity_client.oauth.create_consumer( parsed_args.name ) info = {} @@ -144,6 +191,12 @@ def get_parser(self, prog_name): help='Consumer key', required=True ) + parser.add_argument( + '--consumer-secret', + metavar='', + help='Consumer secret', + required=True + ) parser.add_argument( '--roles', metavar='', @@ -153,12 +206,12 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) - oauth_client = self.app.client_manager.identity.oauths + oauth_client = self.app.client_manager.identity.oauth request_token = oauth_client.create_request_token( - parsed_args.consumer_key, parsed_args.roles) - info = {} - info.update(request_token._info) - return zip(*sorted(info.iteritems())) + parsed_args.consumer_key, + parsed_args.consumer_secret, + parsed_args.roles) + return zip(*sorted(request_token.iteritems())) class DeleteConsumer(command.Command): @@ -180,8 +233,8 @@ def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) identity_client = self.app.client_manager.identity consumer = utils.find_resource( - identity_client.oauths, parsed_args.consumer) - identity_client.oauths.delete_consumer(consumer.id) + identity_client.oauth, parsed_args.consumer) + identity_client.oauth.delete_consumer(consumer.id) return @@ -194,7 +247,7 @@ class ListConsumer(lister.Lister): def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) columns = ('ID', 'Name', 'Consumer Key', 'Consumer Secret') - data = self.app.client_manager.identity.oauths.list_consumers() + data = self.app.client_manager.identity.oauth.list_consumers() return (columns, (utils.get_item_properties( s, columns, @@ -226,7 +279,7 @@ def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) identity_client = self.app.client_manager.identity consumer = utils.find_resource( - identity_client.oauths, parsed_args.consumer) + identity_client.oauth, parsed_args.consumer) kwargs = {} if parsed_args.name: kwargs['name'] = parsed_args.name @@ -234,7 +287,7 @@ def take_action(self, parsed_args): if not len(kwargs): sys.stdout.write("Consumer not updated, no arguments present") return - identity_client.oauths.update_consumer(consumer.id, **kwargs) + identity_client.oauth.update_consumer(consumer.id, **kwargs) return @@ -257,7 +310,7 @@ def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) identity_client = self.app.client_manager.identity consumer = utils.find_resource( - identity_client.oauths, parsed_args.consumer) + identity_client.oauth, parsed_args.consumer) info = {} info.update(consumer._info) diff --git a/setup.cfg b/setup.cfg index 2068a92a5a..2aae47df48 100644 --- a/setup.cfg +++ b/setup.cfg @@ -69,6 +69,7 @@ openstack.identity.v2_0 = user_show = openstackclient.identity.v2_0.user:ShowUser openstack.identity.v3 = + access_token_authenticate = openstackclient.identity.v3.oauth:AuthenticateAccessToken access_token_create = openstackclient.identity.v3.oauth:CreateAccessToken consumer_create = openstackclient.identity.v3.oauth:CreateConsumer From 1a0d5ccc68f65394292992b48afe20241e89e7b8 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 11 Jul 2013 22:40:24 -0500 Subject: [PATCH 0090/3614] Remove api = apiName calls from each method As discussed in https://review.openstack.org/#/c/36352/ for each command, we were setting api = identity or volume... etc, this was for an old way of calling commands that are is no longer used. Also removed openstackclient/common/command.py Change-Id: I2705f35d343f2ae729dc22d6aed0b852b2f8ca19 --- openstackclient/common/command.py | 29 -------------------- openstackclient/compute/v2/agent.py | 4 --- openstackclient/compute/v2/console.py | 2 -- openstackclient/compute/v2/fixedip.py | 2 -- openstackclient/compute/v2/flavor.py | 4 --- openstackclient/compute/v2/floatingip.py | 5 ---- openstackclient/compute/v2/floatingippool.py | 1 - openstackclient/compute/v2/host.py | 2 -- openstackclient/compute/v2/hypervisor.py | 2 -- openstackclient/compute/v2/keypair.py | 4 --- openstackclient/compute/v2/server.py | 9 ------ openstackclient/compute/v2/service.py | 2 -- openstackclient/identity/v2_0/ec2creds.py | 4 --- openstackclient/identity/v2_0/endpoint.py | 4 --- openstackclient/identity/v2_0/role.py | 7 ----- openstackclient/identity/v2_0/service.py | 4 --- openstackclient/identity/v2_0/tenant.py | 5 ---- openstackclient/identity/v2_0/user.py | 5 ---- openstackclient/identity/v3/credential.py | 5 ---- openstackclient/identity/v3/domain.py | 5 ---- openstackclient/identity/v3/endpoint.py | 5 ---- openstackclient/identity/v3/group.py | 8 ------ openstackclient/identity/v3/oauth.py | 8 ------ openstackclient/identity/v3/policy.py | 5 ---- openstackclient/identity/v3/project.py | 5 ---- openstackclient/identity/v3/role.py | 7 ----- openstackclient/identity/v3/service.py | 5 ---- openstackclient/identity/v3/user.py | 5 ---- openstackclient/image/v1/image.py | 1 - openstackclient/image/v2/image.py | 4 --- openstackclient/volume/v1/backup.py | 5 ---- openstackclient/volume/v1/snapshot.py | 5 ---- openstackclient/volume/v1/type.py | 5 ---- openstackclient/volume/v1/volume.py | 6 ---- 34 files changed, 179 deletions(-) delete mode 100644 openstackclient/common/command.py diff --git a/openstackclient/common/command.py b/openstackclient/common/command.py deleted file mode 100644 index 59cd0da281..0000000000 --- a/openstackclient/common/command.py +++ /dev/null @@ -1,29 +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. -# - -"""OpenStack base command""" - -from cliff import command - - -class OpenStackCommand(command.Command): - """Base class for OpenStack commands.""" - api = None - - def run(self, parsed_args): - if not self.api: - return - else: - return super(OpenStackCommand, self).run(parsed_args) diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py index 46ab991b4c..aac69d8a0b 100644 --- a/openstackclient/compute/v2/agent.py +++ b/openstackclient/compute/v2/agent.py @@ -27,7 +27,6 @@ class CreateAgent(show.ShowOne): """Create agent command""" - api = "compute" log = logging.getLogger(__name__ + ".CreateAgent") def get_parser(self, prog_name): @@ -77,7 +76,6 @@ def take_action(self, parsed_args): class DeleteAgent(command.Command): """Delete agent command""" - api = "compute" log = logging.getLogger(__name__ + ".DeleteAgent") def get_parser(self, prog_name): @@ -98,7 +96,6 @@ def take_action(self, parsed_args): class ListAgent(lister.Lister): """List agent command""" - api = "compute" log = logging.getLogger(__name__ + ".ListAgent") def get_parser(self, prog_name): @@ -131,7 +128,6 @@ def take_action(self, parsed_args): class SetAgent(show.ShowOne): """Set agent command""" - api = "compute" log = logging.getLogger(__name__ + ".SetAgent") def get_parser(self, prog_name): diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index 8ed0d7f21a..a67b004c05 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -27,7 +27,6 @@ class ShowConsoleLog(command.Command): """Show console-log command""" - api = 'compute' log = logging.getLogger(__name__ + '.ShowConsoleLog') def get_parser(self, prog_name): @@ -65,7 +64,6 @@ def take_action(self, parsed_args): class ShowConsoleURL(show.ShowOne): """Show console-url command""" - api = 'compute' log = logging.getLogger(__name__ + '.ShowConsoleURL') def get_parser(self, prog_name): diff --git a/openstackclient/compute/v2/fixedip.py b/openstackclient/compute/v2/fixedip.py index d0687fd717..c41fed45c4 100644 --- a/openstackclient/compute/v2/fixedip.py +++ b/openstackclient/compute/v2/fixedip.py @@ -25,7 +25,6 @@ class AddFixedIP(command.Command): """Add fixed-ip command""" - api = "compute" log = logging.getLogger(__name__ + ".AddFixedIP") def get_parser(self, prog_name): @@ -59,7 +58,6 @@ def take_action(self, parsed_args): class RemoveFixedIP(command.Command): """Remove fixed-ip command""" - api = "compute" log = logging.getLogger(__name__ + ".RemoveFixedIP") def get_parser(self, prog_name): diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 1c6d3e3ab3..4d53a4122e 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -27,7 +27,6 @@ class CreateFlavor(show.ShowOne): """Create flavor command""" - api = "compute" log = logging.getLogger(__name__ + ".CreateFlavor") def get_parser(self, prog_name): @@ -117,7 +116,6 @@ def take_action(self, parsed_args): class DeleteFlavor(command.Command): """Delete flavor command""" - api = "compute" log = logging.getLogger(__name__ + ".DeleteFlavor") def get_parser(self, prog_name): @@ -140,7 +138,6 @@ def take_action(self, parsed_args): class ListFlavor(lister.Lister): """List flavor command""" - api = "compute" log = logging.getLogger(__name__ + ".ListFlavor") def take_action(self, parsed_args): @@ -168,7 +165,6 @@ def take_action(self, parsed_args): class ShowFlavor(show.ShowOne): """Show flavor command""" - api = "compute" log = logging.getLogger(__name__ + ".ShowFlavor") def get_parser(self, prog_name): diff --git a/openstackclient/compute/v2/floatingip.py b/openstackclient/compute/v2/floatingip.py index 5a4b5f9a5d..1b07beb3d3 100644 --- a/openstackclient/compute/v2/floatingip.py +++ b/openstackclient/compute/v2/floatingip.py @@ -27,7 +27,6 @@ class AddFloatingIP(command.Command): """Add floating-ip command""" - api = "compute" log = logging.getLogger(__name__ + ".AddFloatingIP") def get_parser(self, prog_name): @@ -58,7 +57,6 @@ def take_action(self, parsed_args): class CreateFloatingIP(show.ShowOne): """Create floating-ip command""" - api = 'compute' log = logging.getLogger(__name__ + '.CreateFloatingIP') def get_parser(self, prog_name): @@ -83,7 +81,6 @@ def take_action(self, parsed_args): class DeleteFloatingIP(command.Command): """Delete floating-ip command""" - api = 'compute' log = logging.getLogger(__name__ + '.DeleteFloatingIP') def get_parser(self, prog_name): @@ -111,7 +108,6 @@ def take_action(self, parsed_args): class ListFloatingIP(lister.Lister): """List floating-ip command""" - api = 'compute' log = logging.getLogger(__name__ + '.ListFloatingIP') def take_action(self, parsed_args): @@ -132,7 +128,6 @@ def take_action(self, parsed_args): class RemoveFloatingIP(command.Command): """Remove floating-ip command""" - api = "compute" log = logging.getLogger(__name__ + ".RemoveFloatingIP") def get_parser(self, prog_name): diff --git a/openstackclient/compute/v2/floatingippool.py b/openstackclient/compute/v2/floatingippool.py index 54814e046d..e1da97c3d8 100644 --- a/openstackclient/compute/v2/floatingippool.py +++ b/openstackclient/compute/v2/floatingippool.py @@ -25,7 +25,6 @@ class ListFloatingIPPool(lister.Lister): """List floating-ip-pool command""" - api = 'compute' log = logging.getLogger(__name__ + '.ListFloatingIPPool') def take_action(self, parsed_args): diff --git a/openstackclient/compute/v2/host.py b/openstackclient/compute/v2/host.py index f9c23a42cd..44f457d933 100644 --- a/openstackclient/compute/v2/host.py +++ b/openstackclient/compute/v2/host.py @@ -25,7 +25,6 @@ class ListHost(lister.Lister): """List host command""" - api = "compute" log = logging.getLogger(__name__ + ".ListHost") def get_parser(self, prog_name): @@ -54,7 +53,6 @@ def take_action(self, parsed_args): class ShowHost(lister.Lister): """Show host command""" - api = "compute" log = logging.getLogger(__name__ + ".ShowHost") def get_parser(self, prog_name): diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py index 35866aecea..ad69d3285a 100644 --- a/openstackclient/compute/v2/hypervisor.py +++ b/openstackclient/compute/v2/hypervisor.py @@ -26,7 +26,6 @@ class ListHypervisor(lister.Lister): """List hypervisor command""" - api = "compute" log = logging.getLogger(__name__ + ".ListHypervisor") def get_parser(self, prog_name): @@ -60,7 +59,6 @@ def take_action(self, parsed_args): class ShowHypervisor(show.ShowOne): """Show hypervisor command""" - api = "compute" log = logging.getLogger(__name__ + ".ShowHypervisor") def get_parser(self, prog_name): diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index 7987574fae..65f3679b71 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -30,7 +30,6 @@ class CreateKeypair(show.ShowOne): """Create keypair command""" - api = "compute" log = logging.getLogger(__name__ + '.CreateKeypair') def get_parser(self, prog_name): @@ -81,7 +80,6 @@ def take_action(self, parsed_args): class DeleteKeypair(command.Command): """Delete keypair command""" - api = "compute" log = logging.getLogger(__name__ + '.DeleteKeypair') def get_parser(self, prog_name): @@ -103,7 +101,6 @@ def take_action(self, parsed_args): class ListKeypair(lister.Lister): """List keypair command""" - api = "compute" log = logging.getLogger(__name__ + ".ListKeypair") def take_action(self, parsed_args): @@ -124,7 +121,6 @@ def take_action(self, parsed_args): class ShowKeypair(show.ShowOne): """Show keypair command""" - api = 'compute' log = logging.getLogger(__name__ + '.ShowKeypair') def get_parser(self, prog_name): diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 8b134aa92f..e78144b084 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -118,7 +118,6 @@ def _wait_for_status(poll_fn, obj_id, final_ok_states, poll_period=5, class CreateServer(show.ShowOne): """Create server command""" - api = "compute" log = logging.getLogger(__name__ + '.CreateServer') def get_parser(self, prog_name): @@ -318,7 +317,6 @@ def take_action(self, parsed_args): class DeleteServer(command.Command): """Delete server command""" - api = 'compute' log = logging.getLogger(__name__ + '.DeleteServer') def get_parser(self, prog_name): @@ -341,7 +339,6 @@ def take_action(self, parsed_args): class ListServer(lister.Lister): """List server command""" - api = 'compute' log = logging.getLogger(__name__ + '.ListServer') def get_parser(self, prog_name): @@ -419,7 +416,6 @@ def take_action(self, parsed_args): class PauseServer(command.Command): """Pause server command""" - api = 'compute' log = logging.getLogger(__name__ + '.PauseServer') def get_parser(self, prog_name): @@ -442,7 +438,6 @@ def take_action(self, parsed_args): class RebootServer(command.Command): """Reboot server command""" - api = 'compute' log = logging.getLogger(__name__ + '.RebootServer') def get_parser(self, prog_name): @@ -490,7 +485,6 @@ def take_action(self, parsed_args): class RebuildServer(show.ShowOne): """Rebuild server command""" - api = "compute" log = logging.getLogger(__name__ + '.RebuildServer') def get_parser(self, prog_name): @@ -545,7 +539,6 @@ def take_action(self, parsed_args): class ResumeServer(command.Command): """Resume server command""" - api = 'compute' log = logging.getLogger(__name__ + '.ResumeServer') def get_parser(self, prog_name): @@ -592,7 +585,6 @@ def take_action(self, parsed_args): class SuspendServer(command.Command): """Suspend server command""" - api = 'compute' log = logging.getLogger(__name__ + '.SuspendServer') def get_parser(self, prog_name): @@ -615,7 +607,6 @@ def take_action(self, parsed_args): class UnpauseServer(command.Command): """Unpause server command""" - api = 'compute' log = logging.getLogger(__name__ + '.UnpauseServer') def get_parser(self, prog_name): diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 5dac0e1ca9..5e57e0aa5e 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -25,7 +25,6 @@ class ListService(lister.Lister): """List service command""" - api = "compute" log = logging.getLogger(__name__ + ".ListService") def get_parser(self, prog_name): @@ -62,7 +61,6 @@ def take_action(self, parsed_args): class SetService(lister.Lister): """Set service command""" - api = "compute" log = logging.getLogger(__name__ + ".SetService") def get_parser(self, prog_name): diff --git a/openstackclient/identity/v2_0/ec2creds.py b/openstackclient/identity/v2_0/ec2creds.py index 6a5d2af30f..953a3de6ad 100644 --- a/openstackclient/identity/v2_0/ec2creds.py +++ b/openstackclient/identity/v2_0/ec2creds.py @@ -27,7 +27,6 @@ class CreateEC2Creds(show.ShowOne): """Create EC2 credentials""" - api = "identity" log = logging.getLogger(__name__ + ".CreateEC2Creds") def get_parser(self, prog_name): @@ -75,7 +74,6 @@ def take_action(self, parsed_args): class DeleteEC2Creds(command.Command): """Delete EC2 credentials""" - api = 'identity' log = logging.getLogger(__name__ + '.DeleteEC2Creds') def get_parser(self, prog_name): @@ -111,7 +109,6 @@ def take_action(self, parsed_args): class ListEC2Creds(lister.Lister): """List EC2 credentials""" - api = 'identity' log = logging.getLogger(__name__ + '.ListEC2Creds') def get_parser(self, prog_name): @@ -148,7 +145,6 @@ def take_action(self, parsed_args): class ShowEC2Creds(show.ShowOne): """Show EC2 credentials""" - api = 'identity' log = logging.getLogger(__name__ + '.ShowEC2Creds') def get_parser(self, prog_name): diff --git a/openstackclient/identity/v2_0/endpoint.py b/openstackclient/identity/v2_0/endpoint.py index 4ce4fcb20a..680465ae47 100644 --- a/openstackclient/identity/v2_0/endpoint.py +++ b/openstackclient/identity/v2_0/endpoint.py @@ -29,7 +29,6 @@ class CreateEndpoint(show.ShowOne): """Create endpoint command""" - api = 'identity' log = logging.getLogger(__name__ + '.CreateEndpoint') def get_parser(self, prog_name): @@ -78,7 +77,6 @@ def take_action(self, parsed_args): class DeleteEndpoint(command.Command): """Delete endpoint command""" - api = 'identity' log = logging.getLogger(__name__ + '.DeleteEndpoint') def get_parser(self, prog_name): @@ -99,7 +97,6 @@ def take_action(self, parsed_args): class ListEndpoint(lister.Lister): """List endpoint command""" - api = 'identity' log = logging.getLogger(__name__ + '.ListEndpoint') def get_parser(self, prog_name): @@ -136,7 +133,6 @@ def take_action(self, parsed_args): class ShowEndpoint(show.ShowOne): """Show endpoint command""" - api = 'identity' log = logging.getLogger(__name__ + '.ShowEndpoint') def get_parser(self, prog_name): diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index 5c905d916f..867230b6b2 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -27,7 +27,6 @@ class AddRole(show.ShowOne): """Add role to tenant:user""" - api = 'identity' log = logging.getLogger(__name__ + '.AddRole') def get_parser(self, prog_name): @@ -68,7 +67,6 @@ def take_action(self, parsed_args): class CreateRole(show.ShowOne): """Create new role""" - api = 'identity' log = logging.getLogger(__name__ + '.CreateRole') def get_parser(self, prog_name): @@ -92,7 +90,6 @@ def take_action(self, parsed_args): class DeleteRole(command.Command): """Delete existing role""" - api = 'identity' log = logging.getLogger(__name__ + '.DeleteRole') def get_parser(self, prog_name): @@ -114,7 +111,6 @@ def take_action(self, parsed_args): class ListRole(lister.Lister): """List roles""" - api = 'identity' log = logging.getLogger(__name__ + '.ListRole') def take_action(self, parsed_args): @@ -131,7 +127,6 @@ def take_action(self, parsed_args): class ListUserRole(lister.Lister): """List user-role assignments""" - api = 'identity' log = logging.getLogger(__name__ + '.ListUserRole') def get_parser(self, prog_name): @@ -181,7 +176,6 @@ def take_action(self, parsed_args): class RemoveRole(command.Command): """Remove role from tenant:user""" - api = 'identity' log = logging.getLogger(__name__ + '.RemoveRole') def get_parser(self, prog_name): @@ -218,7 +212,6 @@ def take_action(self, parsed_args): class ShowRole(show.ShowOne): """Show single role""" - api = 'identity' log = logging.getLogger(__name__ + '.ShowRole') def get_parser(self, prog_name): diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index 51abfc18ce..9940cb910a 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -29,7 +29,6 @@ class CreateService(show.ShowOne): """Create service command""" - api = 'identity' log = logging.getLogger(__name__ + '.CreateService') def get_parser(self, prog_name): @@ -65,7 +64,6 @@ def take_action(self, parsed_args): class DeleteService(command.Command): """Delete service command""" - api = 'identity' log = logging.getLogger(__name__ + '.DeleteService') def get_parser(self, prog_name): @@ -86,7 +84,6 @@ def take_action(self, parsed_args): class ListService(lister.Lister): """List service command""" - api = 'identity' log = logging.getLogger(__name__ + '.ListService') def get_parser(self, prog_name): @@ -115,7 +112,6 @@ def take_action(self, parsed_args): class ShowService(show.ShowOne): """Show service command""" - api = 'identity' log = logging.getLogger(__name__ + '.ShowService') def get_parser(self, prog_name): diff --git a/openstackclient/identity/v2_0/tenant.py b/openstackclient/identity/v2_0/tenant.py index 00a6a977cf..c9a423c5f1 100644 --- a/openstackclient/identity/v2_0/tenant.py +++ b/openstackclient/identity/v2_0/tenant.py @@ -28,7 +28,6 @@ class CreateTenant(show.ShowOne): """Create tenant command""" - api = 'identity' log = logging.getLogger(__name__ + '.CreateTenant') def get_parser(self, prog_name): @@ -71,7 +70,6 @@ def take_action(self, parsed_args): class DeleteTenant(command.Command): """Delete tenant command""" - api = 'identity' log = logging.getLogger(__name__ + '.DeleteTenant') def get_parser(self, prog_name): @@ -94,7 +92,6 @@ def take_action(self, parsed_args): class ListTenant(lister.Lister): """List tenant command""" - api = 'identity' log = logging.getLogger(__name__ + '.ListTenant') def get_parser(self, prog_name): @@ -123,7 +120,6 @@ def take_action(self, parsed_args): class SetTenant(command.Command): """Set tenant command""" - api = 'identity' log = logging.getLogger(__name__ + '.SetTenant') def get_parser(self, prog_name): @@ -177,7 +173,6 @@ def take_action(self, parsed_args): class ShowTenant(show.ShowOne): """Show tenant command""" - api = 'identity' log = logging.getLogger(__name__ + '.ShowTenant') def get_parser(self, prog_name): diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index 03da60085f..39be81af82 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -28,7 +28,6 @@ class CreateUser(show.ShowOne): """Create user command""" - api = 'identity' log = logging.getLogger(__name__ + '.CreateUser') def get_parser(self, prog_name): @@ -86,7 +85,6 @@ def take_action(self, parsed_args): class DeleteUser(command.Command): """Delete user command""" - api = 'identity' log = logging.getLogger(__name__ + '.DeleteUser') def get_parser(self, prog_name): @@ -108,7 +106,6 @@ def take_action(self, parsed_args): class ListUser(lister.Lister): """List user command""" - api = 'identity' log = logging.getLogger(__name__ + '.ListUser') def get_parser(self, prog_name): @@ -141,7 +138,6 @@ def take_action(self, parsed_args): class SetUser(command.Command): """Set user command""" - api = 'identity' log = logging.getLogger(__name__ + '.SetUser') def get_parser(self, prog_name): @@ -206,7 +202,6 @@ def take_action(self, parsed_args): class ShowUser(show.ShowOne): """Show user command""" - api = 'identity' log = logging.getLogger(__name__ + '.ShowUser') def get_parser(self, prog_name): diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py index c98260526e..a2fb43a256 100644 --- a/openstackclient/identity/v3/credential.py +++ b/openstackclient/identity/v3/credential.py @@ -28,7 +28,6 @@ class CreateCredential(show.ShowOne): """Create credential command""" - api = 'identity' log = logging.getLogger(__name__ + '.CreateCredential') def get_parser(self, prog_name): @@ -79,7 +78,6 @@ def take_action(self, parsed_args): class DeleteCredential(command.Command): """Delete credential command""" - api = 'identity' log = logging.getLogger(__name__ + '.DeleteCredential') def get_parser(self, prog_name): @@ -101,7 +99,6 @@ def take_action(self, parsed_args): class ListCredential(lister.Lister): """List credential command""" - api = 'identity' log = logging.getLogger(__name__ + '.ListCredential') def take_action(self, parsed_args): @@ -118,7 +115,6 @@ def take_action(self, parsed_args): class SetCredential(command.Command): """Set credential command""" - api = 'identity' log = logging.getLogger(__name__ + '.SetCredential') def get_parser(self, prog_name): @@ -178,7 +174,6 @@ def take_action(self, parsed_args): class ShowCredential(show.ShowOne): """Show credential command""" - api = 'identity' log = logging.getLogger(__name__ + '.ShowCredential') def get_parser(self, prog_name): diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index e3eaec2fbc..f6064a589c 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -28,7 +28,6 @@ class CreateDomain(show.ShowOne): """Create domain command""" - api = 'identity' log = logging.getLogger(__name__ + '.CreateDomain') def get_parser(self, prog_name): @@ -72,7 +71,6 @@ def take_action(self, parsed_args): class DeleteDomain(command.Command): """Delete domain command""" - api = 'identity' log = logging.getLogger(__name__ + '.DeleteDomain') def get_parser(self, prog_name): @@ -96,7 +94,6 @@ def take_action(self, parsed_args): class ListDomain(lister.Lister): """List domain command""" - api = 'identity' log = logging.getLogger(__name__ + '.ListDomain') def take_action(self, parsed_args): @@ -113,7 +110,6 @@ def take_action(self, parsed_args): class SetDomain(command.Command): """Set domain command""" - api = 'identity' log = logging.getLogger(__name__ + '.SetDomain') def get_parser(self, prog_name): @@ -172,7 +168,6 @@ def take_action(self, parsed_args): class ShowDomain(show.ShowOne): """Show domain command""" - api = 'identity' log = logging.getLogger(__name__ + '.ShowDomain') def get_parser(self, prog_name): diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 4d9b680882..c5f3ebadff 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -28,7 +28,6 @@ class CreateEndpoint(show.ShowOne): """Create endpoint command""" - api = 'identity' log = logging.getLogger(__name__ + '.CreateEndpoint') def get_parser(self, prog_name): @@ -90,7 +89,6 @@ def take_action(self, parsed_args): class DeleteEndpoint(command.Command): """Delete endpoint command""" - api = 'identity' log = logging.getLogger(__name__ + '.DeleteEndpoint') def get_parser(self, prog_name): @@ -113,7 +111,6 @@ def take_action(self, parsed_args): class ListEndpoint(lister.Lister): """List endpoint command""" - api = 'identity' log = logging.getLogger(__name__ + '.ListEndpoint') def get_parser(self, prog_name): @@ -150,7 +147,6 @@ def take_action(self, parsed_args): class SetEndpoint(command.Command): """Set endpoint command""" - api = 'identity' log = logging.getLogger(__name__ + '.SetEndpoint') def get_parser(self, prog_name): @@ -220,7 +216,6 @@ def take_action(self, parsed_args): class ShowEndpoint(show.ShowOne): """Show endpoint command""" - api = 'identity' log = logging.getLogger(__name__ + '.ShowEndpoint') def get_parser(self, prog_name): diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index 21d73966aa..ca0493ebbb 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -28,7 +28,6 @@ class AddUserToGroup(command.Command): """Add user to group""" - api = 'identity' log = logging.getLogger(__name__ + '.AddUserToGroup') def get_parser(self, prog_name): @@ -67,7 +66,6 @@ def take_action(self, parsed_args): class CheckUserInGroup(command.Command): """Checks that user is in a specific group""" - api = 'identity' log = logging.getLogger(__name__ + '.CheckUserInGroup') def get_parser(self, prog_name): @@ -106,7 +104,6 @@ def take_action(self, parsed_args): class CreateGroup(show.ShowOne): """Create group command""" - api = 'identity' log = logging.getLogger(__name__ + '.CreateGroup') def get_parser(self, prog_name): @@ -147,7 +144,6 @@ def take_action(self, parsed_args): class DeleteGroup(command.Command): """Delete group command""" - api = 'identity' log = logging.getLogger(__name__ + '.DeleteGroup') def get_parser(self, prog_name): @@ -169,7 +165,6 @@ def take_action(self, parsed_args): class ListGroup(lister.Lister): """List groups and optionally roles assigned to groups""" - api = 'identity' log = logging.getLogger(__name__ + '.ListGroup') def get_parser(self, prog_name): @@ -279,7 +274,6 @@ def take_action(self, parsed_args): class RemoveUserFromGroup(command.Command): """Remove user to group""" - api = 'identity' log = logging.getLogger(__name__ + '.RemoveUserFromGroup') def get_parser(self, prog_name): @@ -318,7 +312,6 @@ def take_action(self, parsed_args): class SetGroup(command.Command): """Set group command""" - api = 'identity' log = logging.getLogger(__name__ + '.SetGroup') def get_parser(self, prog_name): @@ -365,7 +358,6 @@ def take_action(self, parsed_args): class ShowGroup(show.ShowOne): """Show group command""" - api = 'identity' log = logging.getLogger(__name__ + '.ShowGroup') def get_parser(self, prog_name): diff --git a/openstackclient/identity/v3/oauth.py b/openstackclient/identity/v3/oauth.py index 0b5ae4dba7..1e803e15cf 100644 --- a/openstackclient/identity/v3/oauth.py +++ b/openstackclient/identity/v3/oauth.py @@ -28,7 +28,6 @@ class AuthorizeRequestToken(show.ShowOne): """Authorize request token command""" - api = 'identity' log = logging.getLogger(__name__ + '.AuthorizeRequestToken') def get_parser(self, prog_name): @@ -68,7 +67,6 @@ def take_action(self, parsed_args): class CreateAccessToken(show.ShowOne): """Create access token command""" - api = 'identity' log = logging.getLogger(__name__ + '.CreateAccessToken') def get_parser(self, prog_name): @@ -107,7 +105,6 @@ def take_action(self, parsed_args): class CreateConsumer(show.ShowOne): """Create consumer command""" - api = 'identity' log = logging.getLogger(__name__ + '.CreateConsumer') def get_parser(self, prog_name): @@ -133,7 +130,6 @@ def take_action(self, parsed_args): class CreateRequestToken(show.ShowOne): """Create request token command""" - api = 'identity' log = logging.getLogger(__name__ + '.CreateRequestToken') def get_parser(self, prog_name): @@ -164,7 +160,6 @@ def take_action(self, parsed_args): class DeleteConsumer(command.Command): """Delete consumer command""" - api = 'identity' log = logging.getLogger(__name__ + '.DeleteConsumer') def get_parser(self, prog_name): @@ -188,7 +183,6 @@ def take_action(self, parsed_args): class ListConsumer(lister.Lister): """List consumer command""" - api = 'identity' log = logging.getLogger(__name__ + '.ListConsumer') def take_action(self, parsed_args): @@ -205,7 +199,6 @@ def take_action(self, parsed_args): class SetConsumer(command.Command): """Set consumer command""" - api = 'identity' log = logging.getLogger(__name__ + '.SetConsumer') def get_parser(self, prog_name): @@ -241,7 +234,6 @@ def take_action(self, parsed_args): class ShowConsumer(show.ShowOne): """Show consumer command""" - api = 'identity' log = logging.getLogger(__name__ + '.ShowConsumer') def get_parser(self, prog_name): diff --git a/openstackclient/identity/v3/policy.py b/openstackclient/identity/v3/policy.py index ccfc363569..ac14cc464d 100644 --- a/openstackclient/identity/v3/policy.py +++ b/openstackclient/identity/v3/policy.py @@ -28,7 +28,6 @@ class CreatePolicy(show.ShowOne): """Create policy command""" - api = 'identity' log = logging.getLogger(__name__ + '.CreatePolicy') def get_parser(self, prog_name): @@ -61,7 +60,6 @@ def take_action(self, parsed_args): class DeletePolicy(command.Command): """Delete policy command""" - api = 'identity' log = logging.getLogger(__name__ + '.DeletePolicy') def get_parser(self, prog_name): @@ -83,7 +81,6 @@ def take_action(self, parsed_args): class ListPolicy(lister.Lister): """List policy command""" - api = 'identity' log = logging.getLogger(__name__ + '.ListPolicy') def get_parser(self, prog_name): @@ -113,7 +110,6 @@ def take_action(self, parsed_args): class SetPolicy(command.Command): """Set policy command""" - api = 'identity' log = logging.getLogger(__name__ + '.SetPolicy') def get_parser(self, prog_name): @@ -159,7 +155,6 @@ def take_action(self, parsed_args): class ShowPolicy(show.ShowOne): """Show policy command""" - api = 'identity' log = logging.getLogger(__name__ + '.ShowPolicy') def get_parser(self, prog_name): diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 84271cd9a5..9d1e360bac 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -28,7 +28,6 @@ class CreateProject(show.ShowOne): """Create project command""" - api = 'identity' log = logging.getLogger(__name__ + '.CreateProject') def get_parser(self, prog_name): @@ -85,7 +84,6 @@ def take_action(self, parsed_args): class DeleteProject(command.Command): """Delete project command""" - api = 'identity' log = logging.getLogger(__name__ + '.DeleteProject') def get_parser(self, prog_name): @@ -108,7 +106,6 @@ def take_action(self, parsed_args): class ListProject(lister.Lister): """List project command""" - api = 'identity' log = logging.getLogger(__name__ + '.ListProject') def get_parser(self, prog_name): @@ -137,7 +134,6 @@ def take_action(self, parsed_args): class SetProject(command.Command): """Set project command""" - api = 'identity' log = logging.getLogger(__name__ + '.SetProject') def get_parser(self, prog_name): @@ -199,7 +195,6 @@ def take_action(self, parsed_args): class ShowProject(show.ShowOne): """Show project command""" - api = 'identity' log = logging.getLogger(__name__ + '.ShowProject') def get_parser(self, prog_name): diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 7387509ae2..9be3b78410 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -28,7 +28,6 @@ class AddRole(command.Command): """Adds a role to a user or group on a domain or project""" - api = 'identity' log = logging.getLogger(__name__ + '.AddRole') def get_parser(self, prog_name): @@ -108,7 +107,6 @@ def take_action(self, parsed_args): class CreateRole(show.ShowOne): """Create new role""" - api = 'identity' log = logging.getLogger(__name__ + '.CreateRole') def get_parser(self, prog_name): @@ -131,7 +129,6 @@ def take_action(self, parsed_args): class DeleteRole(command.Command): """Delete existing role""" - api = 'identity' log = logging.getLogger(__name__ + '.DeleteRole') def get_parser(self, prog_name): @@ -155,7 +152,6 @@ def take_action(self, parsed_args): class ListRole(lister.Lister): """List roles""" - api = 'identity' log = logging.getLogger(__name__ + '.ListRole') def take_action(self, parsed_args): @@ -172,7 +168,6 @@ def take_action(self, parsed_args): class RemoveRole(command.Command): """Remove role command""" - api = 'identity' log = logging.getLogger(__name__ + '.RemoveRole') def get_parser(self, prog_name): @@ -251,7 +246,6 @@ def take_action(self, parsed_args): class SetRole(command.Command): """Set role command""" - api = 'identity' log = logging.getLogger(__name__ + '.SetRole') def get_parser(self, prog_name): @@ -285,7 +279,6 @@ def take_action(self, parsed_args): class ShowRole(show.ShowOne): """Show single role""" - api = 'identity' log = logging.getLogger(__name__ + '.ShowRole') def get_parser(self, prog_name): diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py index 023c858e84..5c82284c52 100644 --- a/openstackclient/identity/v3/service.py +++ b/openstackclient/identity/v3/service.py @@ -28,7 +28,6 @@ class CreateService(show.ShowOne): """Create service command""" - api = 'identity' log = logging.getLogger(__name__ + '.CreateService') def get_parser(self, prog_name): @@ -69,7 +68,6 @@ def take_action(self, parsed_args): class DeleteService(command.Command): """Delete service command""" - api = 'identity' log = logging.getLogger(__name__ + '.DeleteService') def get_parser(self, prog_name): @@ -94,7 +92,6 @@ def take_action(self, parsed_args): class ListService(lister.Lister): """List service command""" - api = 'identity' log = logging.getLogger(__name__ + '.ListService') def take_action(self, parsed_args): @@ -111,7 +108,6 @@ def take_action(self, parsed_args): class SetService(show.ShowOne): """Set service command""" - api = 'identity' log = logging.getLogger(__name__ + '.SetService') def get_parser(self, prog_name): @@ -165,7 +161,6 @@ def take_action(self, parsed_args): class ShowService(show.ShowOne): """Show service command""" - api = 'identity' log = logging.getLogger(__name__ + '.ShowService') def get_parser(self, prog_name): diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 53550cda56..aac7027450 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -28,7 +28,6 @@ class CreateUser(show.ShowOne): """Create user command""" - api = 'identity' log = logging.getLogger(__name__ + '.CreateUser') def get_parser(self, prog_name): @@ -113,7 +112,6 @@ def take_action(self, parsed_args): class DeleteUser(command.Command): """Delete user command""" - api = 'identity' log = logging.getLogger(__name__ + '.DeleteUser') def get_parser(self, prog_name): @@ -137,7 +135,6 @@ def take_action(self, parsed_args): class ListUser(lister.Lister): """List users and optionally roles assigned to users""" - api = 'identity' log = logging.getLogger(__name__ + '.ListUser') def get_parser(self, prog_name): @@ -249,7 +246,6 @@ def take_action(self, parsed_args): class SetUser(command.Command): """Set user command""" - api = 'identity' log = logging.getLogger(__name__ + '.SetUser') def get_parser(self, prog_name): @@ -338,7 +334,6 @@ def take_action(self, parsed_args): class ShowUser(show.ShowOne): """Show user command""" - api = 'identity' log = logging.getLogger(__name__ + '.ShowUser') def get_parser(self, prog_name): diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 43cae21c07..5b4a939daa 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -30,7 +30,6 @@ class CreateImage(show.ShowOne): """Create image command""" - api = "image" log = logging.getLogger(__name__ + ".CreateImage") def get_parser(self, prog_name): diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 3b9b349d30..61273aa2c3 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -28,7 +28,6 @@ class DeleteImage(command.Command): """Delete image command""" - api = "image" log = logging.getLogger(__name__ + ".DeleteImage") def get_parser(self, prog_name): @@ -49,7 +48,6 @@ def take_action(self, parsed_args): class ListImage(lister.Lister): """List image command""" - api = "image" log = logging.getLogger(__name__ + ".ListImage") def get_parser(self, prog_name): @@ -78,7 +76,6 @@ def take_action(self, parsed_args): class SaveImage(command.Command): """Save image command""" - api = "image" log = logging.getLogger(__name__ + ".SaveImage") def get_parser(self, prog_name): @@ -107,7 +104,6 @@ def take_action(self, parsed_args): class ShowImage(show.ShowOne): """Show image command""" - api = "image" log = logging.getLogger(__name__ + ".ShowImage") def get_parser(self, prog_name): diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py index cd1afd60b7..8ef666c1fa 100644 --- a/openstackclient/volume/v1/backup.py +++ b/openstackclient/volume/v1/backup.py @@ -27,7 +27,6 @@ class CreateBackup(show.ShowOne): """Create backup command""" - api = 'volume' log = logging.getLogger(__name__ + '.CreateBackup') def get_parser(self, prog_name): @@ -75,7 +74,6 @@ def take_action(self, parsed_args): class DeleteBackup(command.Command): """Delete backup command""" - api = 'volume' log = logging.getLogger(__name__ + '.DeleteBackup') def get_parser(self, prog_name): @@ -99,7 +97,6 @@ def take_action(self, parsed_args): class ListBackup(lister.Lister): """List backup command""" - api = 'volume' log = logging.getLogger(__name__ + '.ListBackup') def take_action(self, parsed_args): @@ -122,7 +119,6 @@ def take_action(self, parsed_args): class RestoreBackup(command.Command): """Restore backup command""" - api = 'volume' log = logging.getLogger(__name__ + '.RestoreBackup') def get_parser(self, prog_name): @@ -151,7 +147,6 @@ def take_action(self, parsed_args): class ShowBackup(show.ShowOne): """Show backup command""" - api = 'volume' log = logging.getLogger(__name__ + '.ShowBackup') def get_parser(self, prog_name): diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index a4cf86bfd5..6055a4d8af 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -28,7 +28,6 @@ class CreateSnapshot(show.ShowOne): """Create snapshot command""" - api = 'volume' log = logging.getLogger(__name__ + '.CreateSnapshot') def get_parser(self, prog_name): @@ -76,7 +75,6 @@ def take_action(self, parsed_args): class DeleteSnapshot(command.Command): """Delete snapshot command""" - api = 'volume' log = logging.getLogger(__name__ + '.DeleteSnapshot') def get_parser(self, prog_name): @@ -100,7 +98,6 @@ def take_action(self, parsed_args): class ListSnapshot(lister.Lister): """List snapshot command""" - api = 'volume' log = logging.getLogger(__name__ + '.ListSnapshot') def take_action(self, parsed_args): @@ -123,7 +120,6 @@ def take_action(self, parsed_args): class SetSnapshot(command.Command): """Set snapshot command""" - api = 'volume' log = logging.getLogger(__name__ + '.SetSnapshot') def get_parser(self, prog_name): @@ -163,7 +159,6 @@ def take_action(self, parsed_args): class ShowSnapshot(show.ShowOne): """Show snapshot command""" - api = 'volume' log = logging.getLogger(__name__ + '.ShowSnapshot') def get_parser(self, prog_name): diff --git a/openstackclient/volume/v1/type.py b/openstackclient/volume/v1/type.py index e146ee3f61..dab21d9911 100644 --- a/openstackclient/volume/v1/type.py +++ b/openstackclient/volume/v1/type.py @@ -28,7 +28,6 @@ class CreateVolumeType(show.ShowOne): """Create volume type command""" - api = 'volume' log = logging.getLogger(__name__ + '.CreateVolumeType') def get_parser(self, prog_name): @@ -55,7 +54,6 @@ def take_action(self, parsed_args): class DeleteVolumeType(command.Command): """Delete volume type command""" - api = 'volume' log = logging.getLogger(__name__ + '.DeleteVolumeType') def get_parser(self, prog_name): @@ -79,7 +77,6 @@ def take_action(self, parsed_args): class ListVolumeType(lister.Lister): """List volume type command""" - api = 'volume' log = logging.getLogger(__name__ + '.ListVolumeType') def get_parser(self, prog_name): @@ -108,7 +105,6 @@ def take_action(self, parsed_args): class SetVolumeType(command.Command): """Set volume type command""" - api = 'volume' log = logging.getLogger(__name__ + '.SetVolumeType') def get_parser(self, prog_name): @@ -142,7 +138,6 @@ def take_action(self, parsed_args): class UnsetVolumeType(command.Command): """Unset volume type command""" - api = 'volume' log = logging.getLogger(__name__ + '.UnsetVolumeType') def get_parser(self, prog_name): diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 43253c4092..f1e421f488 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -28,7 +28,6 @@ class CreateVolume(show.ShowOne): """Create volume command""" - api = 'volume' log = logging.getLogger(__name__ + '.CreateVolume') def get_parser(self, prog_name): @@ -127,7 +126,6 @@ def take_action(self, parsed_args): class DeleteVolume(command.Command): """Delete volume command""" - api = 'volume' log = logging.getLogger(__name__ + '.DeleteVolume') def get_parser(self, prog_name): @@ -161,7 +159,6 @@ def take_action(self, parsed_args): class ListVolume(lister.Lister): """List volume command""" - api = 'volume' log = logging.getLogger(__name__ + '.ListVolume') def get_parser(self, prog_name): @@ -218,7 +215,6 @@ def take_action(self, parsed_args): class SetVolume(command.Command): """Set volume command""" - api = 'volume' log = logging.getLogger(__name__ + '.SetVolume') def get_parser(self, prog_name): @@ -274,7 +270,6 @@ def take_action(self, parsed_args): class ShowVolume(show.ShowOne): """Show volume command""" - api = 'volume' log = logging.getLogger(__name__ + '.ShowVolume') def get_parser(self, prog_name): @@ -297,7 +292,6 @@ def take_action(self, parsed_args): class UnsetVolume(command.Command): """Unset volume command""" - api = 'volume' log = logging.getLogger(__name__ + '.UnsetVolume') def get_parser(self, prog_name): From 75dcdb0c6637afe5c14e5c60f7719182bda7ab4a Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Tue, 9 Jul 2013 17:10:33 -0500 Subject: [PATCH 0091/3614] Add show limits command * This is a combination of the compute and volume API limits as they are very similar. As such, the command lives in a new command group 'openstack.common' that is unversioned. * Implements 'limits show [--absolute|--rate] Updated for https://review.openstack.org/#/c/36772/ Bug: 1172057 Change-Id: I2bd181cd0d098f7143360ae67944c2f221379af5 --- openstackclient/common/limits.py | 79 ++++++++++++++++++++++++++++++++ openstackclient/shell.py | 4 ++ setup.cfg | 3 ++ 3 files changed, 86 insertions(+) create mode 100644 openstackclient/common/limits.py diff --git a/openstackclient/common/limits.py b/openstackclient/common/limits.py new file mode 100644 index 0000000000..bbc15228e8 --- /dev/null +++ b/openstackclient/common/limits.py @@ -0,0 +1,79 @@ +# 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. +# + +"""Limits Action Implementation""" + +import itertools +import logging + +from cliff import lister + +from openstackclient.common import utils + + +class ShowLimits(lister.Lister): + """Show compute and volume limits""" + + log = logging.getLogger(__name__ + '.ShowLimits') + + def get_parser(self, prog_name): + parser = super(ShowLimits, self).get_parser(prog_name) + type_group = parser.add_mutually_exclusive_group() + type_group.add_argument( + "--absolute", + dest="is_absolute", + action="store_true", + default=False, + help="Show absolute limits") + type_group.add_argument( + "--rate", + dest="is_rate", + action="store_true", + default=False, + help="Show rate limits") + parser.add_argument( + "--reserved", + dest="is_reserved", + action="store_true", + default=False, + help="Include reservations count [only valid with --absolute]") + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + compute_client = self.app.client_manager.compute + volume_client = self.app.client_manager.volume + + compute_limits = compute_client.limits.get(parsed_args.is_reserved) + volume_limits = volume_client.limits.get() + + if parsed_args.is_absolute: + compute_limits = compute_limits.absolute + volume_limits = volume_limits.absolute + columns = ["Name", "Value"] + return (columns, (utils.get_item_properties(s, columns) + for s in itertools.chain(compute_limits, volume_limits))) + + elif parsed_args.is_rate: + compute_limits = compute_limits.rate + volume_limits = volume_limits.rate + columns = ["Verb", "URI", "Value", "Remain", "Unit", + "Next Available"] + return (columns, (utils.get_item_properties(s, columns) + for s in itertools.chain(compute_limits, volume_limits))) + + else: + return ({}, {}) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index e5353194cd..561b8ddf6c 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -331,6 +331,10 @@ def initialize_app(self, argv): self.command_manager.add_command_group( 'openstack.' + api + version) + # Commands that span multiple APIs + self.command_manager.add_command_group( + 'openstack.common') + # This is the naive extension implementation referred to in # blueprint 'client-extensions' # Extension modules can register their commands in an diff --git a/setup.cfg b/setup.cfg index 2068a92a5a..1e7911bda6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,6 +31,9 @@ console_scripts = openstack.cli = +openstack.common = + limits_show = openstackclient.common.limits:ShowLimits + openstack.identity.v2_0 = ec2_credentials_create = openstackclient.identity.v2_0.ec2creds:CreateEC2Creds ec2_credentials_delete = openstackclient.identity.v2_0.ec2creds:DeleteEC2Creds From f768ea2b228257030de4696bfefe61d9fc917723 Mon Sep 17 00:00:00 2001 From: Alessio Ababilov Date: Tue, 16 Jul 2013 15:20:03 +0300 Subject: [PATCH 0092/3614] Update openstack-common.conf format Change-Id: Ifa6718331d3da91f0e9515a809484808bd6317f9 --- openstack-common.conf | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openstack-common.conf b/openstack-common.conf index 58c0dee0ef..867e204af7 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -1,7 +1,10 @@ [DEFAULT] # The list of modules to copy from openstack-common -modules=cfg,iniparser,install_venv_common,openstackkeyring +module=cfg +module=iniparser +module=install_venv_common +module=openstackkeyring # The base module to hold the copy of openstack.common base=openstackclient From bbb71e7ce2cb8bc81318858823018ff494c61a40 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 17 Jan 2013 14:16:32 -0600 Subject: [PATCH 0093/3614] Add --catalog to service show Shows endpoints from the service catalog rather than the system services. Change-Id: I842916af9f7c0a76c4d3e27e419bf0fec059ec78 --- openstackclient/identity/v2_0/service.py | 67 ++++++++++++++++-------- 1 file changed, 44 insertions(+), 23 deletions(-) diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index 9940cb910a..629475df00 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# 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 @@ -110,7 +110,7 @@ def take_action(self, parsed_args): class ShowService(show.ShowOne): - """Show service command""" + """Show cloud service information""" log = logging.getLogger(__name__ + '.ShowService') @@ -119,30 +119,51 @@ def get_parser(self, prog_name): parser.add_argument( 'service', metavar='', - help='Type, name or ID of service to display') + help='Type, name or ID of service to display', + ) + parser.add_argument( + '--catalog', + action='store_true', + default=False, + help='Show service catalog information', + ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) identity_client = self.app.client_manager.identity - 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 exists." - # TODO(mordred): Where does name_or_id come from? - # msg = ("No service with a type, name or ID of '%s' exists." % - # name_or_id) - raise exceptions.CommandError(msg) - info = {} - info.update(service._info) - return zip(*sorted(info.iteritems())) + if parsed_args.catalog: + endpoints = identity_client.service_catalog.get_endpoints( + service_type=parsed_args.service) + for (service, service_endpoints) in endpoints.iteritems(): + if service_endpoints: + info = {"type": service} + info.update(service_endpoints[0]) + return zip(*sorted(info.iteritems())) + + msg = ("No service catalog with a type, name or ID of '%s' " + "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) + + info = {} + info.update(service._info) + return zip(*sorted(info.iteritems())) From 6146213e327729a2a48a09de35087ca2be9786e5 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Tue, 16 Jul 2013 13:52:59 -0500 Subject: [PATCH 0094/3614] Add list and delete authorizations for oauth commands * List user authorizations * Delete user authorization * Grouped the commands with oauth prefix Change-Id: I032ffa25181aad0fb4689f69cdca5a7adc6e29f1 --- openstackclient/identity/v3/oauth.py | 87 +++++++++++++++++++++++++++- setup.cfg | 14 +++-- 2 files changed, 94 insertions(+), 7 deletions(-) diff --git a/openstackclient/identity/v3/oauth.py b/openstackclient/identity/v3/oauth.py index b8f01e6458..bcbbdf7e09 100644 --- a/openstackclient/identity/v3/oauth.py +++ b/openstackclient/identity/v3/oauth.py @@ -26,7 +26,7 @@ class AuthenticateAccessToken(show.ShowOne): - """Authenticate access token - receive keystone token""" + """Authenticate access token to receive keystone token""" api = 'identity' log = logging.getLogger(__name__ + '.AuthenticateAccessToken') @@ -233,6 +233,36 @@ def take_action(self, parsed_args): return +class DeleteUserAuthorization(command.Command): + """Delete user authorization command""" + + log = logging.getLogger(__name__ + '.DeleteUserAuthorization') + + def get_parser(self, prog_name): + parser = super(DeleteUserAuthorization, self).get_parser(prog_name) + parser.add_argument( + 'user', + metavar='', + help='Name or Id of user', + ) + parser.add_argument( + 'access_id', + metavar='', + help='Access Id 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.oauth.delete_authorization(user, + parsed_args.access_id) + return + + class ListConsumer(lister.Lister): """List consumer command""" @@ -249,6 +279,37 @@ def take_action(self, parsed_args): ) for s in data)) +class ListUserAuthorizations(lister.Lister): + """List user authorizations command""" + + log = logging.getLogger(__name__ + '.ListUserAuthorizations') + + def get_parser(self, prog_name): + parser = super(ListUserAuthorizations, self).get_parser(prog_name) + parser.add_argument( + 'user', + metavar='', + help='Name or Id of user', + ) + 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 + + columns = ('Access Key', 'Consumer Key', 'Issued At', + 'Project Id', 'User Id', 'Requested Roles') + data = identity_client.oauth.list_authorizations(user) + return (columns, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data)) + + class SetConsumer(command.Command): """Set consumer command""" @@ -284,6 +345,30 @@ def take_action(self, parsed_args): return +class ShowAuthorizationPin(show.ShowOne): + """Show Authorization pin command""" + + log = logging.getLogger(__name__ + '.ShowAuthorizationPin') + + def get_parser(self, prog_name): + parser = super(ShowAuthorizationPin, self).get_parser(prog_name) + parser.add_argument( + 'request_id', + metavar='', + help='Show pin for request token', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + identity_client = self.app.client_manager.identity + data = identity_client.oauth.get_authorization_pin( + parsed_args.request_id) + info = {} + info.update(data._info) + return zip(*sorted(info.iteritems())) + + class ShowConsumer(show.ShowOne): """Show consumer command""" diff --git a/setup.cfg b/setup.cfg index 2aae47df48..7447dbc50c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -69,9 +69,6 @@ openstack.identity.v2_0 = user_show = openstackclient.identity.v2_0.user:ShowUser openstack.identity.v3 = - access_token_authenticate = openstackclient.identity.v3.oauth:AuthenticateAccessToken - access_token_create = openstackclient.identity.v3.oauth:CreateAccessToken - consumer_create = openstackclient.identity.v3.oauth:CreateConsumer consumer_delete = openstackclient.identity.v3.oauth:DeleteConsumer consumer_list = openstackclient.identity.v3.oauth:ListConsumer @@ -105,6 +102,14 @@ openstack.identity.v3 = group_set = openstackclient.identity.v3.group:SetGroup group_show = openstackclient.identity.v3.group:ShowGroup + oauth_access_token_authenticate = openstackclient.identity.v3.oauth:AuthenticateAccessToken + oauth_access_token_create = openstackclient.identity.v3.oauth:CreateAccessToken + oauth_request_token_authorize = openstackclient.identity.v3.oauth:AuthorizeRequestToken + oauth_request_token_create = openstackclient.identity.v3.oauth:CreateRequestToken + oauth_authorization_delete = openstackclient.identity.v3.oauth:DeleteUserAuthorization + oauth_authorization_list = openstackclient.identity.v3.oauth:ListUserAuthorizations + oauth_authorization_show = openstackclient.identity.v3.oauth:ShowAuthorizationPin + policy_create = openstackclient.identity.v3.policy:CreatePolicy policy_delete = openstackclient.identity.v3.policy:DeletePolicy policy_list = openstackclient.identity.v3.policy:ListPolicy @@ -117,9 +122,6 @@ openstack.identity.v3 = project_set = openstackclient.identity.v3.project:SetProject project_show = openstackclient.identity.v3.project:ShowProject - request_token_authorize = openstackclient.identity.v3.oauth:AuthorizeRequestToken - request_token_create = openstackclient.identity.v3.oauth:CreateRequestToken - role_add = openstackclient.identity.v3.role:AddRole role_create = openstackclient.identity.v3.role:CreateRole role_delete = openstackclient.identity.v3.role:DeleteRole From 87104a28d75bd77df8b7ceb44600cd2b3971b4ae Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 3 Jul 2013 16:46:00 -0500 Subject: [PATCH 0095/3614] Add quota commands * Add quota set and quota show commands; these work on both the compute and volume APIs * Add the --class variation on the above commands Note: this replaces the existing volume-only quota commands and eliminates quota list Blueprint: cinder-client Bug: 1172064 Change-Id: I766d40e410e48f05e36e17e567a4f01a9411b40e --- openstackclient/common/quota.py | 192 +++++++++++++++++++++++++++++ openstackclient/volume/v1/quota.py | 114 ----------------- setup.cfg | 6 +- 3 files changed, 194 insertions(+), 118 deletions(-) create mode 100644 openstackclient/common/quota.py delete mode 100644 openstackclient/volume/v1/quota.py diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py new file mode 100644 index 0000000000..fd482da9e6 --- /dev/null +++ b/openstackclient/common/quota.py @@ -0,0 +1,192 @@ +# Copyright 2012 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. +# + +"""Quota action implementations""" + +import itertools +import logging +import six +import sys + +from cliff import command +from cliff import show + + +# List the quota items, map the internal argument name to the option +# name that the user sees. + +COMPUTE_QUOTAS = { + 'cores': 'cores', + 'fixed_ips': 'fixed-ips', + 'floating_ips': 'floating-ips', + 'injected_file_content_bytes': 'injected-file-size', + 'injected_file_path_bytes': 'injected-path-size', + 'injected_files': 'injected-files', + 'instances': 'instances', + 'key_pairs': 'key-pairs', + 'metadata_items': 'properties', + 'ram': 'ram', + 'security_group_rules': 'secgroup-rules', + 'security_groups': 'secgroups', +} + +VOLUME_QUOTAS = { + 'gigabytes': 'gigabytes', + 'snapshots': 'snapshots', + 'volumes': 'volumes', +} + + +class SetQuota(command.Command): + """Set quotas for project or class""" + + log = logging.getLogger(__name__ + '.SetQuota') + + def get_parser(self, prog_name): + parser = super(SetQuota, self).get_parser(prog_name) + parser.add_argument( + 'project', + metavar='', + help='Set quotas for this project or class (name/ID)', + ) + parser.add_argument( + '--class', + dest='quota_class', + action='store_true', + default=False, + help='Set quotas for ', + ) + for k, v in itertools.chain( + COMPUTE_QUOTAS.items(), VOLUME_QUOTAS.items()): + parser.add_argument( + '--%s' % v, + metavar='' % v, + type=int, + help='New value for the %s quota' % v, + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + compute_client = self.app.client_manager.compute + volume_client = self.app.client_manager.volume + + compute_kwargs = {} + for k, v in COMPUTE_QUOTAS.items(): + if v in parsed_args: + compute_kwargs[k] = getattr(parsed_args, v, None) + + volume_kwargs = {} + for k, v in VOLUME_QUOTAS.items(): + if v in parsed_args: + volume_kwargs[k] = getattr(parsed_args, v, None) + + if compute_kwargs == {} and volume_kwargs == {}: + sys.stderr.write("No quotas updated") + return + + if parsed_args.quota_class: + if compute_kwargs: + compute_client.quota_classes.update( + parsed_args.project, + **compute_kwargs) + if volume_kwargs: + volume_client.quota_classes.update( + parsed_args.project, + **volume_kwargs) + else: + if compute_kwargs: + compute_client.quotas.update( + parsed_args.project, + **compute_kwargs) + if volume_kwargs: + volume_client.quotas.update( + parsed_args.project, + **volume_kwargs) + + +class ShowQuota(show.ShowOne): + """Show quotas for project or class""" + + log = logging.getLogger(__name__ + '.ShowQuota') + + def get_parser(self, prog_name): + parser = super(ShowQuota, self).get_parser(prog_name) + parser.add_argument( + 'project', + metavar='', + help='Show this project or class (name/ID)', + ) + type_group = parser.add_mutually_exclusive_group() + type_group.add_argument( + '--class', + dest='quota_class', + action='store_true', + default=False, + help='Show quotas for ', + ) + type_group.add_argument( + '--default', + dest='default', + action='store_true', + default=False, + help='Show default quotas for ' + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + compute_client = self.app.client_manager.compute + volume_client = self.app.client_manager.volume + + # NOTE(dtroyer): These quota API calls do not validate the project + # or class arguments and return what appears to be + # the default quota values if the project or class + # does not exist. If this is determined to be the + # intended behaviour of the API we will validate + # the argument with Identity ourselves later. + if parsed_args.quota_class: + compute_quota = compute_client.quota_classes.get( + parsed_args.project) + volume_quota = volume_client.quota_classes.get( + parsed_args.project) + elif parsed_args.default: + compute_quota = compute_client.quotas.defaults( + parsed_args.project) + volume_quota = volume_client.quotas.defaults( + parsed_args.project) + else: + compute_quota = compute_client.quotas.get(parsed_args.project) + volume_quota = volume_client.quotas.get(parsed_args.project) + + info = {} + info.update(compute_quota._info) + info.update(volume_quota._info) + + # Map the internal quota names to the external ones + for k, v in itertools.chain( + COMPUTE_QUOTAS.items(), VOLUME_QUOTAS.items()): + if not k == v and info[k]: + info[v] = info[k] + info.pop(k) + + # Handle project ID special as it only appears in output + if info['id']: + info['project'] = info['id'] + info.pop('id') + + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/volume/v1/quota.py b/openstackclient/volume/v1/quota.py deleted file mode 100644 index 4f4e97e8f5..0000000000 --- a/openstackclient/volume/v1/quota.py +++ /dev/null @@ -1,114 +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. -# - -"""Volume v1 Quota action implementations""" - -import logging -import sys - -from cliff import command -from cliff import show - -from openstackclient.common import utils - - -class ListQuota(show.ShowOne): - """List quota command""" - - api = 'volume' - log = logging.getLogger(__name__ + '.ListQuota') - - def get_parser(self, prog_name): - parser = super(ListQuota, self).get_parser(prog_name) - parser.add_argument( - 'tenant', - metavar='', - help='ID of tenant to list the default quotas for') - return parser - - def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) - volume_client = self.app.client_manager.volume - defaults = volume_client.quotas.defaults(parsed_args.tenant) - - return zip(*sorted(defaults._info.iteritems())) - - -class SetQuota(command.Command): - """Set quota command""" - - api = 'volume' - log = logging.getLogger(__name__ + '.SetQuota') - - def get_parser(self, prog_name): - parser = super(SetQuota, self).get_parser(prog_name) - parser.add_argument( - 'tenant', - metavar='', - help='ID of tenant to set the quotas for') - parser.add_argument( - '--volumes', - metavar='', - type=int, - help='New value for the volumes quota') - parser.add_argument( - '--gigabytes', - metavar='', - type=int, - help='New value for the gigabytes quota') - return parser - - def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) - - kwargs = {} - if parsed_args.volumes: - kwargs['volumes'] = parsed_args.volumes - if parsed_args.gigabytes: - kwargs['gigabytes'] = parsed_args.gigabytes - - if kwargs == {}: - sys.stdout.write("Quota not updated, no arguments present") - return - - volume_client = self.app.client_manager.volume - volume_client.quotas.update(parsed_args.tenant, - parsed_args.volumes, - parsed_args.gigabytes) - - return - - -class ShowQuota(show.ShowOne): - """Show quota command""" - - api = 'volume' - log = logging.getLogger(__name__ + '.ShowQuota') - - def get_parser(self, prog_name): - parser = super(ShowQuota, self).get_parser(prog_name) - parser.add_argument( - 'tenant', - metavar='', - help='ID of tenant to list the quotas for') - return parser - - def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) - volume_client = self.app.client_manager.volume - quota = utils.find_resource(volume_client.quotas, - parsed_args.tenant) - - return zip(*sorted(quota._info.iteritems())) diff --git a/setup.cfg b/setup.cfg index bbda913db1..475e2077bb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -33,6 +33,8 @@ openstack.cli = openstack.common = limits_show = openstackclient.common.limits:ShowLimits + quota_set = openstackclient.common.quota:SetQuota + quota_show = openstackclient.common.quota:ShowQuota openstack.identity.v2_0 = ec2_credentials_create = openstackclient.identity.v2_0.ec2creds:CreateEC2Creds @@ -203,10 +205,6 @@ openstack.compute.v2 = server_unpause = openstackclient.compute.v2.server:UnpauseServer openstack.volume.v1 = - quota_list = openstackclient.volume.v1.quota:ListQuota - quota_set = openstackclient.volume.v1.quota:SetQuota - quota_show = openstackclient.volume.v1.quota:ShowQuota - snapshot_create = openstackclient.volume.v1.snapshot:CreateSnapshot snapshot_delete = openstackclient.volume.v1.snapshot:DeleteSnapshot snapshot_list = openstackclient.volume.v1.snapshot:ListSnapshot From cdaee1b71e21df56e6127689801240274af9d847 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 8 Jul 2013 16:48:10 -0500 Subject: [PATCH 0096/3614] Complete Image v1 * Add v1 versions of image delete, list, save, set, show * Change default Image API to v1 Rebased for https://review.openstack.org/#/c/36772/ Change-Id: Ie2bfe660aac8a0fcf651c67fd1ea4842e76ce377 --- openstackclient/image/client.py | 60 +++++- openstackclient/image/v1/image.py | 307 +++++++++++++++++++++++++--- openstackclient/image/v2/image.py | 65 +++--- openstackclient/shell.py | 4 +- openstackclient/tests/test_shell.py | 2 +- setup.cfg | 5 + 6 files changed, 383 insertions(+), 60 deletions(-) diff --git a/openstackclient/image/client.py b/openstackclient/image/client.py index 371605692a..70bef1c8a6 100644 --- a/openstackclient/image/client.py +++ b/openstackclient/image/client.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# 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 @@ -15,6 +15,9 @@ import logging +from glanceclient import exc as gc_exceptions +from glanceclient.v1 import client as gc_v1_client +from glanceclient.v1 import images as gc_v1_images from openstackclient.common import utils @@ -22,8 +25,8 @@ API_NAME = "image" API_VERSIONS = { - "1": "glanceclient.v1.client.Client", - "2": "glanceclient.v2.client.Client" + "1": "openstackclient.image.client.Client_v1", + "2": "glanceclient.v2.client.Client", } @@ -38,3 +41,54 @@ def make_client(instance): instance._url = instance.get_endpoint_for_service_type(API_NAME) return image_client(instance._url, token=instance._token) + + +# NOTE(dtroyer): glanceclient.v1.image.ImageManager() doesn't have a find() +# method so add one here until the common client libs arrive +# A similar subclass will be required for v2 + +class Client_v1(gc_v1_client.Client): + """An image v1 client that uses ImageManager_v1""" + + def __init__(self, *args, **kwargs): + super(Client_v1, self).__init__(*args, **kwargs) + self.images = ImageManager_v1(self) + + +class ImageManager_v1(gc_v1_images.ImageManager): + """Add find() and findall() to the ImageManager class""" + + def find(self, **kwargs): + """Find a single item with attributes matching ``**kwargs``. + + This isn't very efficient: it loads the entire list then filters on + the Python side. + """ + rl = self.findall(**kwargs) + num = len(rl) + + if num == 0: + raise gc_exceptions.NotFound + elif num > 1: + raise gc_exceptions.NoUniqueMatch + else: + return rl[0] + + def findall(self, **kwargs): + """Find all items with attributes matching ``**kwargs``. + + This isn't very efficient: it loads the entire list then filters on + the Python side. + """ + found = [] + searches = kwargs.items() + + for obj in self.list(): + try: + if all(getattr(obj, attr) == value + for (attr, value) in searches): + found.append(obj) + except AttributeError: + continue + + return found diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 5b4a939daa..0213ed1e28 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -1,4 +1,4 @@ -# Copyright 2013 OpenStack, LLC. +# 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 @@ -17,6 +17,7 @@ import logging import os +import six import sys if os.name == "nt": @@ -24,11 +25,18 @@ else: msvcrt = None +from cliff import command +from cliff import lister from cliff import show +from glanceclient.common import utils as gc_utils +from openstackclient.common import exceptions +from openstackclient.common import parseractions +from openstackclient.common import utils + class CreateImage(show.ShowOne): - """Create image command""" + """Create/upload an image""" log = logging.getLogger(__name__ + ".CreateImage") @@ -37,89 +45,108 @@ def get_parser(self, prog_name): parser.add_argument( "name", metavar="", - help="Name of image.") + help="Name of image", + ) parser.add_argument( "--disk_format", default="raw", metavar="", - help="Disk format of image.") + help="Disk format of image", + ) parser.add_argument( "--id", metavar="", - help="ID of image to reserve.") + help="ID of image to reserve", + ) parser.add_argument( "--store", metavar="", - help="Store to upload image to.") + help="Store to upload image to", + ) parser.add_argument( "--container-format", default="bare", metavar="", - help="Container format of image.") + help="Container format of image", + ) parser.add_argument( "--owner", - metavar="", - help="Owner of the image.") + metavar="", + help="Owner of the image", + ) parser.add_argument( "--size", metavar="", help="Size of image in bytes. Only used with --location and" - " --copy-from.") + " --copy-from", + ) parser.add_argument( "--min-disk", metavar="", - help="Minimum size of disk needed to boot image in gigabytes.") + help="Minimum size of disk needed to boot image in gigabytes", + ) parser.add_argument( "--min-ram", metavar="", - help="Minimum amount of ram needed to boot image in megabytes.") + help="Minimum amount of ram needed to boot image in megabytes", + ) parser.add_argument( "--location", metavar="", - help="URL where the data for this image already resides.") + help="URL where the data for this image already resides", + ) parser.add_argument( "--file", metavar="", - help="Local file that contains disk image.") + help="Local file that contains disk image", + ) parser.add_argument( "--checksum", metavar="", - help="Hash of image data used for verification.") + help="Hash of image data used for verification", + ) parser.add_argument( "--copy-from", metavar="", help="Similar to --location, but this indicates that the image" - " should immediately be copied from the data store.") + " should immediately be copied from the data store", + ) parser.add_argument( "--property", + dest="properties", metavar="", - default=[], - action="append", - help="Arbitrary property to associate with image.") + action=parseractions.KeyValueAction, + help="Set property on this image " + '(repeat option to set multiple properties)', + ) 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 (default: False)", + ) protected_group.add_argument( "--unprotected", dest="protected", action="store_false", default=False, - help="Allow images to be deleted (default: True).") + help="Allow images to be deleted (default: True)", + ) 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 (default)", + ) public_group.add_argument( "--private", dest="is_public", action="store_false", - help="Image is inaccessible to the public.") + help="Image is inaccessible to the public", + ) return parser def take_action(self, parsed_args): @@ -134,11 +161,6 @@ def take_action(self, parsed_args): args.pop("prefix") args.pop("variables") - args["properties"] = {} - for _property in args.pop("property"): - key, value = _property.split("=", 1) - args["properties"][key] = value - if "location" not in args and "copy_from" not in args: if "file" in args: args["data"] = open(args.pop("file"), "rb") @@ -150,6 +172,231 @@ def take_action(self, parsed_args): args["data"] = sys.stdin image_client = self.app.client_manager.image - data = image_client.images.create(**args)._info.copy() + try: + image = utils.find_resource( + image_client.images, + parsed_args.name, + ) + except exceptions.CommandError: + # This is normal for a create or reserve (create w/o an image) + image = image_client.images.create(**args) + 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) + + info = {} + info.update(image._info) + return zip(*sorted(six.iteritems(info))) + + +class DeleteImage(command.Command): + """Delete an image""" + + log = logging.getLogger(__name__ + ".DeleteImage") + + def get_parser(self, prog_name): + parser = super(DeleteImage, self).get_parser(prog_name) + parser.add_argument( + "image", + metavar="", + help="Name or ID of image to delete", + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + + image_client = self.app.client_manager.image + image = utils.find_resource( + image_client.images, + parsed_args.image, + ) + image_client.images.delete(image) + + +class ListImage(lister.Lister): + """List available images""" + + log = logging.getLogger(__name__ + ".ListImage") + + def get_parser(self, prog_name): + parser = super(ListImage, self).get_parser(prog_name) + parser.add_argument( + "--page-size", + metavar="", + help="Number of images to request in each paginated request", + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + + image_client = self.app.client_manager.image + + kwargs = {} + if parsed_args.page_size is not None: + kwargs["page_size"] = parsed_args.page_size + + data = image_client.images.list(**kwargs) + columns = ["ID", "Name"] + + return (columns, (utils.get_item_properties(s, columns) for s in data)) + + +class SaveImage(command.Command): + """Save an image locally""" + + log = logging.getLogger(__name__ + ".SaveImage") + + def get_parser(self, prog_name): + parser = super(SaveImage, self).get_parser(prog_name) + parser.add_argument( + "--file", + metavar="", + help="Downloaded image save filename [default: stdout]", + ) + parser.add_argument( + "image", + metavar="", + help="Name or ID of image to delete", + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + + image_client = self.app.client_manager.image + image = utils.find_resource( + image_client.images, + parsed_args.image, + ) + data = image_client.images.data(image) + + gc_utils.save_image(data, parsed_args.file) + + +class SetImage(show.ShowOne): + """Change image properties""" + + log = logging.getLogger(__name__ + ".SetImage") + + def get_parser(self, prog_name): + parser = super(SetImage, self).get_parser(prog_name) + parser.add_argument( + "image", + metavar="", + help="Name or ID of image to change", + ) + parser.add_argument( + "--name", + metavar="", + help="Name of image", + ) + parser.add_argument( + "--owner", + metavar="", + help="Owner of the image", + ) + parser.add_argument( + "--min-disk", + metavar="", + help="Minimum size of disk needed to boot image in gigabytes", + ) + parser.add_argument( + "--min-ram", + metavar="", + help="Minimum amount of ram needed to boot image in megabytes", + ) + parser.add_argument( + "--property", + dest="properties", + metavar="", + action=parseractions.KeyValueAction, + help="Set property on this image " + '(repeat option to set multiple properties)', + ) + 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)", + ) + protected_group.add_argument( + "--unprotected", + dest="protected", + action="store_false", + default=False, + help="Allow images to be deleted (default: True)", + ) + 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)", + ) + public_group.add_argument( + "--private", + dest="is_public", + action="store_false", + help="Image is inaccessible to the public", + ) + 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") + + image_client = self.app.client_manager.image + image = utils.find_resource( + image_client.images, + image_arg, + ) + # Merge properties + args["properties"].update(image.properties) + image = image_client.images.update(image, **args) + + info = {} + info.update(image._info) + return zip(*sorted(six.iteritems(info))) + + +class ShowImage(show.ShowOne): + """Show image details""" + + log = logging.getLogger(__name__ + ".ShowImage") + + def get_parser(self, prog_name): + parser = super(ShowImage, self).get_parser(prog_name) + parser.add_argument( + "image", + metavar="", + help="Name or ID of image to display", + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + + image_client = self.app.client_manager.image + image = utils.find_resource( + image_client.images, + parsed_args.image, + ) - return zip(*sorted(data.iteritems())) + info = {} + info.update(image._info) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 61273aa2c3..e84e0d0191 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# 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 @@ -16,6 +16,7 @@ """Image V2 Action Implementations""" import logging +import six from cliff import command from cliff import lister @@ -26,27 +27,32 @@ class DeleteImage(command.Command): - """Delete image command""" + """Delete an image""" log = logging.getLogger(__name__ + ".DeleteImage") def get_parser(self, prog_name): parser = super(DeleteImage, self).get_parser(prog_name) parser.add_argument( - "id", - metavar="", - help="ID of image to delete.") + "image", + metavar="", + help="Name or ID of image to delete", + ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)" % parsed_args) image_client = self.app.client_manager.image - image_client.images.delete(parsed_args.id) + image = utils.find_resource( + image_client.images, + parsed_args.image, + ) + image_client.images.delete(image) class ListImage(lister.Lister): - """List image command""" + """List available images""" log = logging.getLogger(__name__ + ".ListImage") @@ -55,7 +61,8 @@ def get_parser(self, prog_name): parser.add_argument( "--page-size", metavar="", - help="Number of images to request in each paginated request.") + help="Number of images to request in each paginated request", + ) return parser def take_action(self, parsed_args): @@ -74,7 +81,7 @@ def take_action(self, parsed_args): class SaveImage(command.Command): - """Save image command""" + """Save an image locally""" log = logging.getLogger(__name__ + ".SaveImage") @@ -82,42 +89,52 @@ def get_parser(self, prog_name): parser = super(SaveImage, self).get_parser(prog_name) parser.add_argument( "--file", - metavar="", - help="Local file to save downloaded image data " - "to. If this is not specified the image " - "data will be written to stdout.") + metavar="", + help="Downloaded image save filename [default: stdout]", + ) parser.add_argument( - "id", - metavar="", - help="ID of image to describe.") + "image", + metavar="", + help="Name or ID of image to delete", + ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)" % parsed_args) image_client = self.app.client_manager.image - data = image_client.images.data(parsed_args.id) + image = utils.find_resource( + image_client.images, + parsed_args.image, + ) + data = image_client.images.data(image) gc_utils.save_image(data, parsed_args.file) class ShowImage(show.ShowOne): - """Show image command""" + """Show image details""" log = logging.getLogger(__name__ + ".ShowImage") def get_parser(self, prog_name): parser = super(ShowImage, self).get_parser(prog_name) parser.add_argument( - "id", - metavar="", - help="ID of image to describe.") + "image", + metavar="", + help="Name or ID of image to display", + ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)" % parsed_args) image_client = self.app.client_manager.image - data = image_client.images.get(parsed_args.id) - - return zip(*sorted(data.iteritems())) + image = utils.find_resource( + image_client.images, + parsed_args.image, + ) + + info = {} + info.update(image._info) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index e5353194cd..cdfe2b1dbb 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# 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 @@ -35,7 +35,7 @@ DEFAULT_COMPUTE_API_VERSION = '2' DEFAULT_IDENTITY_API_VERSION = '2.0' -DEFAULT_IMAGE_API_VERSION = '2' +DEFAULT_IMAGE_API_VERSION = '1' DEFAULT_VOLUME_API_VERSION = '1' DEFAULT_DOMAIN = 'default' diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py index f479b11e4b..ca87997f67 100644 --- a/openstackclient/tests/test_shell.py +++ b/openstackclient/tests/test_shell.py @@ -36,7 +36,7 @@ LIB_COMPUTE_API_VERSION = "2" LIB_IDENTITY_API_VERSION = "2.0" -LIB_IMAGE_API_VERSION = "2" +LIB_IMAGE_API_VERSION = "1" LIB_VOLUME_API_VERSION = "1" diff --git a/setup.cfg b/setup.cfg index 2068a92a5a..027e7be4a0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -141,6 +141,11 @@ openstack.identity.v3 = openstack.image.v1 = image_create = openstackclient.image.v1.image:CreateImage + image_delete = openstackclient.image.v1.image:DeleteImage + image_list = openstackclient.image.v1.image:ListImage + image_save = openstackclient.image.v1.image:SaveImage + image_set = openstackclient.image.v1.image:SetImage + image_show = openstackclient.image.v1.image:ShowImage openstack.image.v2 = image_delete = openstackclient.image.v2.image:DeleteImage From 2cc996356c438e45a876a6cb51a332d070044c1e Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 11 Jul 2013 15:41:18 -0500 Subject: [PATCH 0097/3614] Add aggregate commands * Add aggregate: add host, create, delete, list, remove host, set, show * Add list --long option * Filter 'availability_zone' from the metadata fields * Rename 'metadata' column to 'properties' in all output Bug: 1172032 Blueprint: nova-client Change-Id: Icd408c2b34af07f5102f53d3778d8546952a12c5 --- openstackclient/compute/v2/aggregate.py | 322 ++++++++++++++++++++++++ setup.cfg | 8 + 2 files changed, 330 insertions(+) create mode 100644 openstackclient/compute/v2/aggregate.py diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py new file mode 100644 index 0000000000..d786d7e5b0 --- /dev/null +++ b/openstackclient/compute/v2/aggregate.py @@ -0,0 +1,322 @@ +# Copyright 2012 OpenStack Foundation +# 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. +# + +"""Compute v2 Aggregate action implementations""" + +import logging +import six + +from cliff import command +from cliff import lister +from cliff import show + +from openstackclient.common import parseractions +from openstackclient.common import utils + + +class AddAggregateHost(show.ShowOne): + """Add host to aggregate""" + + log = logging.getLogger(__name__ + '.AddAggregateHost') + + def get_parser(self, prog_name): + parser = super(AddAggregateHost, self).get_parser(prog_name) + parser.add_argument( + 'aggregate', + metavar='', + help='Name or ID of aggregate', + ) + parser.add_argument( + 'host', + metavar='', + help='Host to add to aggregate', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + + compute_client = self.app.client_manager.compute + + aggregate = utils.find_resource( + compute_client.aggregates, + parsed_args.aggregate, + ) + data = compute_client.aggregates.add_host(aggregate, parsed_args.host) + + info = {} + info.update(data._info) + return zip(*sorted(six.iteritems(info))) + + +class CreateAggregate(show.ShowOne): + """Create a new aggregate""" + + log = logging.getLogger(__name__ + ".CreateAggregate") + + def get_parser(self, prog_name): + parser = super(CreateAggregate, self).get_parser(prog_name) + parser.add_argument( + "name", + metavar="", + help="New aggregate name", + ) + parser.add_argument( + "--zone", + metavar="", + help="Availability zone name", + ) + parser.add_argument( + "--property", + metavar="", + action=parseractions.KeyValueAction, + help='Property to add to this aggregate ' + '(repeat option to set multiple properties)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + + compute_client = self.app.client_manager.compute + + info = {} + data = compute_client.aggregates.create( + parsed_args.name, + parsed_args.zone, + ) + info.update(data._info) + + if parsed_args.property: + info.update(compute_client.aggregates.set_metadata( + data, + parsed_args.property, + )._info) + + return zip(*sorted(six.iteritems(info))) + + +class DeleteAggregate(command.Command): + """Delete an existing aggregate""" + + log = logging.getLogger(__name__ + '.DeleteAggregate') + + def get_parser(self, prog_name): + parser = super(DeleteAggregate, self).get_parser(prog_name) + parser.add_argument( + 'aggregate', + metavar='', + help='Name or ID of aggregate to delete', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + compute_client = self.app.client_manager.compute + data = utils.find_resource( + compute_client.aggregates, + parsed_args.aggregate, + ) + compute_client.aggregates.delete(data.id) + return + + +class ListAggregate(lister.Lister): + """List all aggregates""" + + log = logging.getLogger(__name__ + ".ListAggregate") + + def get_parser(self, prog_name): + parser = super(ListAggregate, self).get_parser(prog_name) + parser.add_argument( + '--long', + action='store_true', + default=False, + help='List additional fields in output') + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + + compute_client = self.app.client_manager.compute + + data = compute_client.aggregates.list() + + if parsed_args.long: + # Remove availability_zone from metadata because Nova doesn't + for d in data: + if 'availability_zone' in d.metadata: + d.metadata.pop('availability_zone') + # This is the easiest way to change column headers + column_headers = ( + "ID", + "Name", + "Availability Zone", + "Properties", + ) + columns = ( + "ID", + "Name", + "Availability Zone", + "Metadata", + ) + else: + column_headers = columns = ( + "ID", + "Name", + "Availability Zone", + ) + + return (column_headers, + (utils.get_item_properties( + s, columns, + ) for s in data)) + + +class RemoveAggregateHost(show.ShowOne): + """Remove host from aggregate""" + + log = logging.getLogger(__name__ + '.RemoveAggregateHost') + + def get_parser(self, prog_name): + parser = super(RemoveAggregateHost, self).get_parser(prog_name) + parser.add_argument( + 'aggregate', + metavar='', + help='Name or ID of aggregate', + ) + parser.add_argument( + 'host', + metavar='', + help='Host to remove from aggregate', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + + compute_client = self.app.client_manager.compute + + aggregate = utils.find_resource( + compute_client.aggregates, + parsed_args.aggregate, + ) + data = compute_client.aggregates.remove_host( + aggregate, + parsed_args.host, + ) + + info = {} + info.update(data._info) + return zip(*sorted(six.iteritems(info))) + + +class SetAggregate(show.ShowOne): + """Set aggregate properties""" + + log = logging.getLogger(__name__ + '.SetAggregate') + + def get_parser(self, prog_name): + parser = super(SetAggregate, self).get_parser(prog_name) + parser.add_argument( + 'aggregate', + metavar='', + help='Name or ID of aggregate to display', + ) + parser.add_argument( + '--name', + metavar='', + help='New aggregate name', + ) + parser.add_argument( + "--zone", + metavar="", + help="Availability zone name", + ) + parser.add_argument( + "--property", + metavar="", + action=parseractions.KeyValueAction, + help='Property to add/change for this aggregate ' + '(repeat option to set multiple properties)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + compute_client = self.app.client_manager.compute + aggregate = utils.find_resource( + compute_client.aggregates, + parsed_args.aggregate, + ) + + info = {} + kwargs = {} + if parsed_args.name: + kwargs['name'] = parsed_args.name + if parsed_args.zone: + kwargs['availability_zone'] = parsed_args.zone + if kwargs: + info.update(compute_client.aggregates.update( + aggregate, + kwargs + )._info) + + if parsed_args.property: + info.update(compute_client.aggregates.set_metadata( + aggregate, + parsed_args.property, + )._info) + + if info: + return zip(*sorted(six.iteritems(info))) + else: + return ({}, {}) + + +class ShowAggregate(show.ShowOne): + """Show a specific aggregate""" + + log = logging.getLogger(__name__ + '.ShowAggregate') + + def get_parser(self, prog_name): + parser = super(ShowAggregate, self).get_parser(prog_name) + parser.add_argument( + 'aggregate', + metavar='', + help='Name or ID of aggregate to display', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + compute_client = self.app.client_manager.compute + data = utils.find_resource( + compute_client.aggregates, + parsed_args.aggregate, + ) + # Remove availability_zone from metadata because Nova doesn't + if 'availability_zone' in data.metadata: + data.metadata.pop('availability_zone') + # Map 'metadata' column to 'properties' + data._info.update({'properties': data._info.pop('metadata')}) + + info = {} + info.update(data._info) + return zip(*sorted(six.iteritems(info))) diff --git a/setup.cfg b/setup.cfg index 2068a92a5a..dade8c5885 100644 --- a/setup.cfg +++ b/setup.cfg @@ -154,6 +154,14 @@ openstack.compute.v2 = agent_list = openstackclient.compute.v2.agent:ListAgent agent_set = openstackclient.compute.v2.agent:SetAgent + aggregate_add_host = openstackclient.compute.v2.aggregate:AddAggregateHost + aggregate_create = openstackclient.compute.v2.aggregate:CreateAggregate + aggregate_delete = openstackclient.compute.v2.aggregate:DeleteAggregate + aggregate_list = openstackclient.compute.v2.aggregate:ListAggregate + aggregate_remove_host = openstackclient.compute.v2.aggregate:RemoveAggregateHost + aggregate_set = openstackclient.compute.v2.aggregate:SetAggregate + aggregate_show = openstackclient.compute.v2.aggregate:ShowAggregate + compute_service_list = openstackclient.compute.v2.service:ListService compute_service_set = openstackclient.compute.v2.service:SetService From b4904b0a4a83db57e9ed70dad5ebefab85201e2f Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sat, 20 Jul 2013 16:19:43 -0500 Subject: [PATCH 0098/3614] Add password field to set user Noticed this was missing in set user, the password from parsed args wasn't being passed in. Change-Id: I0eb748444b86b374265b6e1dd02f69a922a9b043 --- openstackclient/identity/v3/user.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index aac7027450..8ee535dc33 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -311,6 +311,8 @@ def take_action(self, parsed_args): kwargs['name'] = parsed_args.name if parsed_args.email: kwargs['email'] = parsed_args.email + if parsed_args.password: + kwargs['password'] = parsed_args.password if parsed_args.description: kwargs['description'] = parsed_args.description if parsed_args.project: From 818c94875221f606ed56f276c1cbd320a9106754 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 18 Jul 2013 11:10:34 -0500 Subject: [PATCH 0099/3614] Clean up properties (metadata) formatting * Reformat default dict output to key='value' using utils.format_dict() * Changes utils.get_item_properties() to pass the specific field to the formatter function rather than the entire resource object, this allows the formatter to handle multiple attributes. * Updates server, volume, volume type commands Change-Id: I90eebf6b84ae200532f09cd925f371598ea54a64 --- openstackclient/common/utils.py | 28 +++++++--- openstackclient/compute/v2/server.py | 76 +++++++++++++++++++------- openstackclient/volume/v1/type.py | 46 ++++++++-------- openstackclient/volume/v1/volume.py | 82 ++++++++++++++++++---------- 4 files changed, 154 insertions(+), 78 deletions(-) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 06542887e2..2f2f5718f5 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# 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 @@ -73,6 +73,20 @@ def find_resource(manager, name_or_id): raise +def format_dict(data): + """Return a formatted string of key value pairs + + :param data: a dict + :param format: optional formatting hints + :rtype: a string formatted to key='value' + """ + + output = "" + for s in data: + output = output + s + "='" + data[s] + "', " + return output[:-2] + + def get_item_properties(item, fields, mixed_case_fields=[], formatters={}): """Return a tuple containing the item properties. @@ -85,14 +99,14 @@ def get_item_properties(item, fields, mixed_case_fields=[], formatters={}): row = [] for field in fields: + if field in mixed_case_fields: + field_name = field.replace(' ', '_') + else: + field_name = field.lower().replace(' ', '_') + data = getattr(item, field_name, '') if field in formatters: - row.append(formatters[field](item)) + row.append(formatters[field](data)) else: - if field in mixed_case_fields: - field_name = field.replace(' ', '_') - else: - field_name = field.lower().replace(' ', '_') - data = getattr(item, field_name, '') row.append(data) return tuple(row) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index e78144b084..a8c86a2e66 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# 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 @@ -13,7 +13,7 @@ # under the License. # -"""Server action implementations""" +"""Compute v2 Server action implementations""" import logging import os @@ -25,17 +25,18 @@ from novaclient.v1_1 import servers from openstackclient.common import exceptions +from openstackclient.common import parseractions from openstackclient.common import utils -def _format_servers_list_networks(server): - """Return a string containing the networks a server is attached to. +def _format_servers_list_networks(networks): + """Return a formatted string of a server's networks - :param server: a single Server resource + :param server: a Server.networks field :rtype: a string of formatted network addresses """ output = [] - for (network, addresses) in server.networks.items(): + for (network, addresses) in networks.items(): if not addresses: continue addresses_csv = ', '.join(addresses) @@ -73,7 +74,12 @@ def _prep_server_detail(compute_client, server): # NOTE(dtroyer): novaclient splits these into separate entries... # Format addresses in a useful way - info['addresses'] = _format_servers_list_networks(server) + info['addresses'] = _format_servers_list_networks(server.networks) + + # Map 'metadata' field to 'properties' + info.update( + {'properties': utils.format_dict(info.pop('metadata'))} + ) # Remove values that are long and not too useful info.pop('links', None) @@ -116,7 +122,7 @@ def _wait_for_status(poll_fn, obj_id, final_ok_states, poll_period=5, class CreateServer(show.ShowOne): - """Create server command""" + """Create a new server""" log = logging.getLogger(__name__ + '.CreateServer') @@ -150,9 +156,8 @@ def get_parser(self, prog_name): parser.add_argument( '--property', metavar='', - action='append', - default=[], - help='Property to store for this server ' + action=parseractions.KeyValueAction, + help='Set a property on this server ' '(repeat for multiple values)') parser.add_argument( '--file', @@ -228,8 +233,6 @@ def take_action(self, parsed_args): boot_args = [parsed_args.server_name, image, flavor] - meta = dict(v.split('=', 1) for v in parsed_args.property) - files = {} for f in parsed_args.file: dst, src = f.split('=', 1) @@ -288,7 +291,7 @@ def take_action(self, parsed_args): config_drive = parsed_args.config_drive boot_kwargs = dict( - meta=meta, + meta=parsed_args.property, files=files, reservation_id=None, min_count=parsed_args.min, @@ -337,7 +340,7 @@ def take_action(self, parsed_args): class ListServer(lister.Lister): - """List server command""" + """List servers""" log = logging.getLogger(__name__ + '.ListServer') @@ -385,6 +388,11 @@ def get_parser(self, prog_name): action='store_true', default=bool(int(os.environ.get("ALL_TENANTS", 0))), help='display information from all tenants (admin only)') + parser.add_argument( + '--long', + action='store_true', + default=False, + help='Additional fields are listed in output') return parser def take_action(self, parsed_args): @@ -403,13 +411,43 @@ def take_action(self, parsed_args): 'all_tenants': parsed_args.all_tenants, } self.log.debug('search options: %s', search_opts) - # FIXME(dhellmann): Consider adding other columns - columns = ('ID', 'Name', 'Status', 'Networks') + + if parsed_args.long: + columns = ( + 'ID', + 'Name', + 'Status', + 'Networks', + 'OS-EXT-AZ:availability_zone', + 'OS-EXT-SRV-ATTR:host', + 'Metadata', + ) + column_headers = ( + 'ID', + 'Name', + 'Status', + 'Networks', + 'Availability Zone', + 'Host', + 'Properties', + ) + mixed_case_fields = [ + 'OS-EXT-AZ:availability_zone', + 'OS-EXT-SRV-ATTR:host', + ] + else: + columns = ('ID', 'Name', 'Status', 'Networks') + column_headers = columns + mixed_case_fields = [] data = compute_client.servers.list(search_opts=search_opts) - return (columns, + return (column_headers, (utils.get_item_properties( s, columns, - formatters={'Networks': _format_servers_list_networks}, + mixed_case_fields=mixed_case_fields, + formatters={ + 'Networks': _format_servers_list_networks, + 'Metadata': utils.format_dict, + }, ) for s in data)) diff --git a/openstackclient/volume/v1/type.py b/openstackclient/volume/v1/type.py index dab21d9911..0d9fdb39de 100644 --- a/openstackclient/volume/v1/type.py +++ b/openstackclient/volume/v1/type.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# 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 @@ -26,7 +26,7 @@ class CreateVolumeType(show.ShowOne): - """Create volume type command""" + """Create new volume type""" log = logging.getLogger(__name__ + '.CreateVolumeType') @@ -37,6 +37,13 @@ def get_parser(self, prog_name): metavar='', help='New volume type name', ) + parser.add_argument( + '--property', + metavar='', + action=parseractions.KeyValueAction, + help='Property to add for this volume type ' + '(repeat option to set multiple properties)', + ) return parser def take_action(self, parsed_args): @@ -45,6 +52,13 @@ def take_action(self, parsed_args): volume_type = volume_client.volume_types.create( parsed_args.name ) + 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'))} + ) info = {} info.update(volume_type._info) @@ -52,7 +66,7 @@ def take_action(self, parsed_args): class DeleteVolumeType(command.Command): - """Delete volume type command""" + """Delete volume type""" log = logging.getLogger(__name__ + '.DeleteVolumeType') @@ -75,7 +89,7 @@ def take_action(self, parsed_args): class ListVolumeType(lister.Lister): - """List volume type command""" + """List volume types""" log = logging.getLogger(__name__ + '.ListVolumeType') @@ -92,18 +106,20 @@ def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) if parsed_args.long: columns = ('ID', 'Name', 'Extra Specs') + column_headers = ('ID', 'Name', 'Properties') else: columns = ('ID', 'Name') + column_headers = columns data = self.app.client_manager.volume.volume_types.list() - return (columns, + return (column_headers, (utils.get_item_properties( s, columns, - formatters={'Extra Specs': _format_type_list_extra_specs}, + formatters={'Extra Specs': utils.format_dict}, ) for s in data)) class SetVolumeType(command.Command): - """Set volume type command""" + """Set volume type property""" log = logging.getLogger(__name__ + '.SetVolumeType') @@ -136,7 +152,7 @@ def take_action(self, parsed_args): class UnsetVolumeType(command.Command): - """Unset volume type command""" + """Unset volume type property""" log = logging.getLogger(__name__ + '.UnsetVolumeType') @@ -173,17 +189,3 @@ def take_action(self, parsed_args): else: self.app.log.error("No changes requested\n") return - - -def _format_type_list_extra_specs(vol_type): - """Return a string containing the key value pairs - - :param server: a single VolumeType resource - :rtype: a string formatted to key=value - """ - - keys = vol_type.get_keys() - output = "" - for s in keys: - output = output + s + "=" + keys[s] + "; " - return output diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index f1e421f488..b74fe45249 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# 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 @@ -26,7 +26,7 @@ class CreateVolume(show.ShowOne): - """Create volume command""" + """Create new volume""" log = logging.getLogger(__name__ + '.CreateVolume') @@ -119,12 +119,16 @@ def take_action(self, parsed_args): parsed_args.property, parsed_args.image ) + # Map 'metadata' column to 'properties' + volume._info.update( + {'properties': utils.format_dict(volume._info.pop('metadata'))} + ) return zip(*sorted(volume._info.iteritems())) class DeleteVolume(command.Command): - """Delete volume command""" + """Delete volume""" log = logging.getLogger(__name__ + '.DeleteVolume') @@ -157,7 +161,7 @@ def take_action(self, parsed_args): class ListVolume(lister.Lister): - """List volume command""" + """List volumes""" log = logging.getLogger(__name__ + '.ListVolume') @@ -190,12 +194,42 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) - columns = ('ID', 'Status', 'Display Name', 'Size', - 'Volume Type', 'Bootable', 'Attached to') if parsed_args.long: - columns = ('ID', 'Status', 'Display Name', 'Size', - 'Volume Type', 'Bootable', 'Attached to', 'Meta-data') - + columns = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Volume Type', + 'Bootable', + 'Attached to', + 'Metadata', + ) + column_headers = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Type', + 'Bootable', + 'Attached', + 'Properties', + ) + else: + columns = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + column_headers = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached', + ) search_opts = { 'all_tenants': parsed_args.all_tenants, 'display_name': parsed_args.name, @@ -205,15 +239,15 @@ def take_action(self, parsed_args): volume_client = self.app.client_manager.volume data = volume_client.volumes.list(search_opts=search_opts) - return (columns, + return (column_headers, (utils.get_item_properties( s, columns, - formatters={'Meta-data': _format_meta_data}, + formatters={'Metadata': utils.format_dict}, ) for s in data)) class SetVolume(command.Command): - """Set volume command""" + """Set volume properties""" log = logging.getLogger(__name__ + '.SetVolume') @@ -249,7 +283,6 @@ def take_action(self, parsed_args): volume = utils.find_resource(volume_client.volumes, parsed_args.volume) if parsed_args.property: - print "property: %s" % parsed_args.property volume_client.volumes.set_metadata(volume.id, parsed_args.property) kwargs = {} @@ -258,7 +291,6 @@ def take_action(self, parsed_args): if parsed_args.description: kwargs['display_description'] = parsed_args.description if kwargs: - print "kwargs: %s" % kwargs volume_client.volumes.update(volume.id, **kwargs) if not kwargs and not parsed_args.property: @@ -268,7 +300,7 @@ def take_action(self, parsed_args): class ShowVolume(show.ShowOne): - """Show volume command""" + """Show specific volume""" log = logging.getLogger(__name__ + '.ShowVolume') @@ -285,12 +317,16 @@ def take_action(self, 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'))} + ) return zip(*sorted(volume._info.iteritems())) class UnsetVolume(command.Command): - """Unset volume command""" + """Unset volume properties""" log = logging.getLogger(__name__ + '.UnsetVolume') @@ -325,17 +361,3 @@ def take_action(self, parsed_args): else: self.app.log.error("No changes requested\n") return - - -def _format_meta_data(volume): - """Return a string containing the key value pairs - - :param server: a single volume resource - :rtype: a string formatted to key=value - """ - - keys = volume.metadata - output = "" - for s in keys: - output = output + s + "=" + keys[s] + "; " - return output From 8dd9feb6434243c72f8bd27994418be6409c6c96 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sun, 21 Jul 2013 14:45:22 -0500 Subject: [PATCH 0100/3614] Change volume manager to volume type, unset property for type In the unset method in volume_type, it was calling the volume manager, instead of the volume_type. Bug: 1203561 Change-Id: Iea1a9214db90f15815a456955040c0c5a795ff3d --- openstackclient/volume/v1/type.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/openstackclient/volume/v1/type.py b/openstackclient/volume/v1/type.py index dab21d9911..78fc322ed2 100644 --- a/openstackclient/volume/v1/type.py +++ b/openstackclient/volume/v1/type.py @@ -166,10 +166,7 @@ def take_action(self, parsed_args): ) if parsed_args.property: - volume_client.volumes.delete_metadata( - volume_type.id, - parsed_args.property, - ) + volume_type.unset_keys(parsed_args.property) else: self.app.log.error("No changes requested\n") return From 61beeb7e203eda719e167d1fd2740f0b3225e11f Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 22 Jul 2013 10:23:51 -0500 Subject: [PATCH 0101/3614] Fix --password in server rebuild Use correct attribute to get password in server rebuild command. Fixes bug 1190722 Change-Id: Ibe2ccb8840a385319781885b8aadca6e1ba4cc43 --- openstackclient/compute/v2/server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index e78144b084..08742f37d8 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -521,8 +521,8 @@ def take_action(self, parsed_args): compute_client.servers, parsed_args.server) _password = None - if parsed_args.rebuild_password is not False: - _password = parsed_args.rebuild_password + if parsed_args.password is not False: + _password = parsed_args.password kwargs = {} server = server.rebuild(image, _password, **kwargs) From 3789cdfebe2d84fd77a4549dbdb08346f5d8e280 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sun, 21 Jul 2013 03:28:27 -0500 Subject: [PATCH 0102/3614] Add server diagnose for compute api Add server diagnose for compute api as per blueprint: nova-client Change-Id: I0a2c13e36e1e13f61ef4ba00ec146634f9644648 --- openstackclient/compute/v2/server.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index a8c86a2e66..5922f759c7 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -17,6 +17,7 @@ import logging import os +import sys import time from cliff import command @@ -599,7 +600,6 @@ def take_action(self, parsed_args): class ShowServer(show.ShowOne): """Show server command""" - api = 'compute' log = logging.getLogger(__name__ + '.ShowServer') def get_parser(self, prog_name): @@ -608,6 +608,11 @@ def get_parser(self, prog_name): 'server', metavar='', help='Name or ID of server to display') + parser.add_argument( + '--diagnostics', + action='store_true', + default=False, + help='Display diagnostics information for a given server') return parser def take_action(self, parsed_args): @@ -616,8 +621,15 @@ def take_action(self, parsed_args): server = utils.find_resource(compute_client.servers, parsed_args.server) - details = _prep_server_detail(compute_client, server) - return zip(*sorted(details.iteritems())) + if parsed_args.diagnostics: + (resp, data) = server.diagnostics() + if not resp.status_code == 200: + sys.stderr.write("Error retrieving diagnostics data") + return ({}, {}) + else: + data = _prep_server_detail(compute_client, server) + + return zip(*sorted(data.iteritems())) class SuspendServer(command.Command): From fcc34652ecad4029998e45c32016af1b03256fc5 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sun, 21 Jul 2013 02:52:25 -0500 Subject: [PATCH 0103/3614] Add usage command for compute api As per the blueprint: nova-client, adding usage command for compute Change-Id: Ib694b0b1ebf56b2a62b6f09c67ffaa6959911605 --- openstackclient/compute/v2/usage.py | 91 +++++++++++++++++++++++++++++ setup.cfg | 2 + 2 files changed, 93 insertions(+) create mode 100644 openstackclient/compute/v2/usage.py diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py new file mode 100644 index 0000000000..0bfbc9b84d --- /dev/null +++ b/openstackclient/compute/v2/usage.py @@ -0,0 +1,91 @@ +# 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. +# + +"""Usage action implementations""" + +import datetime +import logging + +from cliff import lister + +from openstackclient.common import utils + + +class ListUsage(lister.Lister): + """List resource usage per project. """ + + log = logging.getLogger(__name__ + ".ListUsage") + + def get_parser(self, prog_name): + parser = super(ListUsage, self).get_parser(prog_name) + parser.add_argument( + "--start", + metavar="", + default=None, + help="Usage range start date ex 2012-01-20" + " (default: 4 weeks ago)." + ) + parser.add_argument( + "--end", + metavar="", + default=None, + help="Usage range end date, ex 2012-01-20 (default: tomorrow)" + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + compute_client = self.app.client_manager.compute + columns = ( + "tenant_id", + "total_memory_mb_usage", + "total_vcpus_usage", + "total_local_gb_usage" + ) + column_headers = ( + "Project ID", + "RAM MB-Hours", + "CPU Hours", + "Disk GB-Hours" + ) + + dateformat = "%Y-%m-%d" + now = datetime.datetime.utcnow() + + if parsed_args.start: + start = datetime.datetime.strptime(parsed_args.start, dateformat) + else: + start = now - datetime.timedelta(weeks=4) + + if parsed_args.end: + end = datetime.datetime.strptime(parsed_args.end, dateformat) + else: + end = now + datetime.timedelta(days=1) + + usage_list = compute_client.usage.list(start, end) + + if len(usage_list) > 0: + print("Usage from %s to %s:" % (start.strftime(dateformat), + end.strftime(dateformat))) + + return (column_headers, + (utils.get_item_properties( + s, columns, + formatters={ + 'total_memory_mb_usage': lambda x: float("%.2f" % x), + 'total_vcpus_usage': lambda x: float("%.2f" % x), + 'total_local_gb_usage': lambda x: float("%.2f" % x), + }, + ) for s in usage_list)) diff --git a/setup.cfg b/setup.cfg index b3a6657bf7..60fa17f897 100644 --- a/setup.cfg +++ b/setup.cfg @@ -208,6 +208,8 @@ openstack.compute.v2 = keypair_list = openstackclient.compute.v2.keypair:ListKeypair keypair_show = openstackclient.compute.v2.keypair:ShowKeypair + project_usage_list = openstackclient.compute.v2.usage:ListUsage + server_create = openstackclient.compute.v2.server:CreateServer server_delete = openstackclient.compute.v2.server:DeleteServer server_list = openstackclient.compute.v2.server:ListServer From 3ff6378c23ab103647310d5d64b1e51bf3cdcf04 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 25 Jul 2013 12:17:25 -0500 Subject: [PATCH 0104/3614] Add server commands: (un)lock, (un)rescue, (un)set, add/remove volume * server lock/unlock, rescue/unrescue, set/unset * add/remove volume Blueprint: nova-client Change-Id: I3709ecdb297ab15ad44df09d89af840164271a66 --- openstackclient/compute/v2/server.py | 343 +++++++++++++++++++++++++-- setup.cfg | 8 + 2 files changed, 325 insertions(+), 26 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index ce2390d7d3..009b49799e 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -15,8 +15,10 @@ """Compute v2 Server action implementations""" +import getpass import logging import os +import six import sys import time @@ -122,6 +124,52 @@ def _wait_for_status(poll_fn, obj_id, final_ok_states, poll_period=5, return retval +class AddServerVolume(command.Command): + """Add volume to server""" + + log = logging.getLogger(__name__ + '.AddServerVolume') + + def get_parser(self, prog_name): + parser = super(AddServerVolume, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Server (name or ID)', + ) + parser.add_argument( + 'volume', + metavar='', + help='Volume to add (name or ID)', + ) + parser.add_argument( + '--device', + metavar='', + help='Server internal device name for volume', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + + compute_client = self.app.client_manager.compute + volume_client = self.app.client_manager.volume + + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + volume = utils.find_resource( + volume_client.volumes, + parsed_args.volume, + ) + + compute_client.volumes.create_server_volume( + server.id, + volume.id, + parsed_args.device, + ) + + class CreateServer(show.ShowOne): """Create a new server""" @@ -452,8 +500,32 @@ def take_action(self, parsed_args): ) for s in data)) +class LockServer(command.Command): + """Lock server""" + + log = logging.getLogger(__name__ + '.LockServer') + + def get_parser(self, prog_name): + parser = super(LockServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Server (name or ID)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + compute_client = self.app.client_manager.compute + utils.find_resource( + compute_client.servers, + parsed_args.server, + ).lock() + + class PauseServer(command.Command): - """Pause server command""" + """Pause server""" log = logging.getLogger(__name__ + '.PauseServer') @@ -462,16 +534,18 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help='Name or ID of server to pause') + help='Server (name or ID)', + ) return parser def take_action(self, 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) - server.pause() - return + utils.find_resource( + compute_client.servers, + parsed_args.server, + ).pause() class RebootServer(command.Command): @@ -575,8 +649,73 @@ def take_action(self, parsed_args): return zip(*sorted(details.iteritems())) +class RemoveServerVolume(command.Command): + """Remove volume from server""" + + log = logging.getLogger(__name__ + '.RemoveServerVolume') + + def get_parser(self, prog_name): + parser = super(RemoveServerVolume, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Server (name or ID)', + ) + parser.add_argument( + 'volume', + metavar='', + help='Volume to remove (name or ID)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + + compute_client = self.app.client_manager.compute + volume_client = self.app.client_manager.volume + + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + volume = utils.find_resource( + volume_client.volumes, + parsed_args.volume, + ) + + compute_client.volumes.delete_server_volume( + server.id, + volume.id, + ) + + +class RescueServer(show.ShowOne): + """Put server in rescue mode""" + + log = logging.getLogger(__name__ + '.RescueServer') + + def get_parser(self, prog_name): + parser = super(RescueServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Server (name or ID)', + ) + return parser + + def take_action(self, 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, + ).rescue() + return zip(*sorted(six.iteritems(server._info))) + + class ResumeServer(command.Command): - """Resume server command""" + """Resume server""" log = logging.getLogger(__name__ + '.ResumeServer') @@ -585,20 +724,81 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help='Name or ID of server to resume') + help='Server (name or ID)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + compute_client = self.app.client_manager.compute + utils.find_resource( + compute_client.servers, + parsed_args.server, + ) .resume() + + +class SetServer(command.Command): + """Set server properties""" + + log = logging.getLogger(__name__ + '.SetServer') + + def get_parser(self, prog_name): + parser = super(SetServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Server (name or ID)', + ) + parser.add_argument( + '--name', + metavar='', + help='New server name', + ) + parser.add_argument( + '--root-password', + action="store_true", + help='Set new root password (interactive only)', + ) + parser.add_argument( + "--property", + metavar="", + action=parseractions.KeyValueAction, + help='Property to add/change for this server ' + '(repeat option to set multiple properties)', + ) return parser def take_action(self, 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) - server.resume() - return + compute_client.servers, + parsed_args.server, + ) + + if parsed_args.name: + server.update(name=parsed_args.name) + + if parsed_args.property: + compute_client.servers.set_meta( + server, + parsed_args.property, + ) + + if parsed_args.root_password: + p1 = getpass.getpass('New password: ') + p2 = getpass.getpass('Retype new password: ') + if p1 == p2: + server.change_password(p1) + else: + raise exceptions.CommandError( + "Passwords do not match, password unchanged") class ShowServer(show.ShowOne): - """Show server command""" + """Show server details""" log = logging.getLogger(__name__ + '.ShowServer') @@ -607,12 +807,14 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help='Name or ID of server to display') + help='Server to show (name or ID)', + ) parser.add_argument( '--diagnostics', action='store_true', default=False, - help='Display diagnostics information for a given server') + help='Display diagnostics information for a given server', + ) return parser def take_action(self, parsed_args): @@ -633,7 +835,7 @@ def take_action(self, parsed_args): class SuspendServer(command.Command): - """Suspend server command""" + """Suspend server""" log = logging.getLogger(__name__ + '.SuspendServer') @@ -642,20 +844,46 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help='Name or ID of server to suspend') + help='Server (name or ID)', + ) return parser def take_action(self, 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) - server.suspend() - return + utils.find_resource( + compute_client.servers, + parsed_args.server, + ).suspend() + + +class UnlockServer(command.Command): + """Unlock server""" + + log = logging.getLogger(__name__ + '.UnlockServer') + + def get_parser(self, prog_name): + parser = super(UnlockServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Server (name or ID)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + compute_client = self.app.client_manager.compute + utils.find_resource( + compute_client.servers, + parsed_args.server, + ).unlock() class UnpauseServer(command.Command): - """Unpause server command""" + """Unpause server""" log = logging.getLogger(__name__ + '.UnpauseServer') @@ -664,13 +892,76 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help='Name or ID of server to unpause') + help='Server (name or ID)', + ) return parser def take_action(self, 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) - server.unpause() - return + utils.find_resource( + compute_client.servers, + parsed_args.server, + ).unpause() + + +class UnrescueServer(command.Command): + """Restore server from rescue mode""" + + log = logging.getLogger(__name__ + '.UnrescueServer') + + def get_parser(self, prog_name): + parser = super(UnrescueServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Server (name or ID)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + compute_client = self.app.client_manager.compute + utils.find_resource( + compute_client.servers, + parsed_args.server, + ).unrescue() + + +class UnsetServer(command.Command): + """Unset server properties""" + + log = logging.getLogger(__name__ + '.UnsetServer') + + def get_parser(self, prog_name): + parser = super(UnsetServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Server (name or ID)', + ) + parser.add_argument( + '--property', + metavar='', + action='append', + default=[], + help='Property key to remove from server ' + '(repeat to set multiple values)', + ) + return parser + + def take_action(self, 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, + ) + + if parsed_args.property: + compute_client.servers.delete_meta( + server, + parsed_args.property, + ) diff --git a/setup.cfg b/setup.cfg index 60fa17f897..2fc34f37ea 100644 --- a/setup.cfg +++ b/setup.cfg @@ -210,16 +210,24 @@ openstack.compute.v2 = project_usage_list = openstackclient.compute.v2.usage:ListUsage + server_add_volume = openstackclient.compute.v2.server:AddServerVolume server_create = openstackclient.compute.v2.server:CreateServer server_delete = openstackclient.compute.v2.server:DeleteServer server_list = openstackclient.compute.v2.server:ListServer + server_lock = openstackclient.compute.v2.server:LockServer server_pause = openstackclient.compute.v2.server:PauseServer server_reboot = openstackclient.compute.v2.server:RebootServer server_rebuild = openstackclient.compute.v2.server:RebuildServer + server_remove_volume = openstackclient.compute.v2.server:RemoveServerVolume + server_rescue = openstackclient.compute.v2.server:RescueServer server_resume = openstackclient.compute.v2.server:ResumeServer + server_set = openstackclient.compute.v2.server:SetServer server_show = openstackclient.compute.v2.server:ShowServer server_suspend = openstackclient.compute.v2.server:SuspendServer + server_unlock = openstackclient.compute.v2.server:UnlockServer server_unpause = openstackclient.compute.v2.server:UnpauseServer + server_unrescue = openstackclient.compute.v2.server:UnrescueServer + server_unset = openstackclient.compute.v2.server:UnsetServer openstack.volume.v1 = snapshot_create = openstackclient.volume.v1.snapshot:CreateSnapshot From 3cc313a60db6d1a5a89c572ed10bca11402912d4 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 26 Jul 2013 15:00:21 -0500 Subject: [PATCH 0105/3614] Add server migrate command Blueprint: nova-client Note: I've tested that the API calls are made correctly but do not have an environment with migration proerly enabled to watch it complete... Change-Id: Ideaf0985d43aa2be22390cf0d2850124c549632d --- openstackclient/compute/v2/server.py | 92 ++++++++++++++++++++++++++++ setup.cfg | 1 + 2 files changed, 93 insertions(+) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 009b49799e..8c31e5d9e7 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -524,6 +524,98 @@ def take_action(self, parsed_args): ).lock() +# FIXME(dtroyer): Here is what I want, how with argparse/cliff? +# server migrate [--wait] \ +# [--live +# [--shared-migration | --block-migration] +# [--disk-overcommit | --no-disk-overcommit]] +# +# +# live_parser = parser.add_argument_group(title='Live migration options') +# then adding the groups doesn't seem to work + +class MigrateServer(command.Command): + """Migrate server to different host""" + + log = logging.getLogger(__name__ + '.MigrateServer') + + def get_parser(self, prog_name): + parser = super(MigrateServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Server to migrate (name or ID)', + ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for resize to complete', + ) + parser.add_argument( + '--live', + metavar='', + help='Target hostname', + ) + migration_group = parser.add_mutually_exclusive_group() + migration_group.add_argument( + '--shared-migration', + dest='shared_migration', + action='store_true', + default=True, + help='Perform a shared live migration (default)', + ) + migration_group.add_argument( + '--block-migration', + dest='shared_migration', + action='store_false', + help='Perform a block live migration', + ) + disk_group = parser.add_mutually_exclusive_group() + disk_group.add_argument( + '--no-disk-overcommit', + dest='disk_overcommit', + action='store_false', + default=False, + help='Do not over-commit disk on the destination host (default)', + ) + disk_group.add_argument( + '--disk-overcommit', + action='store_true', + default=False, + help='Allow disk over-commit on the destination host', + ) + return parser + + def take_action(self, 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, + ) + if parsed_args.live: + server.live_migrate( + parsed_args.live, + parsed_args.shared_migration, + parsed_args.disk_overcommit, + ) + else: + server.migrate() + + if parsed_args.wait: + if utils.wait_for_status( + compute_client.servers.get, + server.id, + #callback=_show_progress, + ): + sys.stdout.write('Complete\n') + else: + sys.stdout.write('\nError migrating server') + raise SystemExit + + class PauseServer(command.Command): """Pause server""" diff --git a/setup.cfg b/setup.cfg index 2fc34f37ea..c2a744f3d6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -215,6 +215,7 @@ openstack.compute.v2 = server_delete = openstackclient.compute.v2.server:DeleteServer server_list = openstackclient.compute.v2.server:ListServer server_lock = openstackclient.compute.v2.server:LockServer + server_migrate = openstackclient.compute.v2.server:MigrateServer server_pause = openstackclient.compute.v2.server:PauseServer server_reboot = openstackclient.compute.v2.server:RebootServer server_rebuild = openstackclient.compute.v2.server:RebuildServer From 65d2a14e3e834ce0c57c879ec7d42715058254bf Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 25 Jul 2013 18:00:33 -0500 Subject: [PATCH 0106/3614] Add server resize command * add server resize * update --wait handling for server create, reboot, rebuild * move _wait_for_status to utils Blueprint: nova-client Rebased after https://review.openstack.org/38162 was committed Change-Id: I7a43b996feecadc7628fcfe20cd5b17333762739 --- openstackclient/common/utils.py | 33 +++++ openstackclient/compute/v2/server.py | 179 +++++++++++++++++---------- setup.cfg | 1 + 3 files changed, 149 insertions(+), 64 deletions(-) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 2f2f5718f5..fd504ea17d 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -17,6 +17,7 @@ import os import sys +import time import uuid from openstackclient.common import exceptions @@ -155,3 +156,35 @@ def get_client_class(api_name, version, version_map): raise exceptions.UnsupportedVersion(msg) return import_class(client_path) + + +def wait_for_status(status_f, + res_id, + status_field='status', + success_status=['active'], + sleep_time=5, + callback=None): + """Wait for status change on a resource during a long-running operation + + :param status_f: a status function that takes a single id argument + :param res_id: the resource id to watch + :param success_status: a list of status strings for successful completion + :param status_field: the status attribute in the returned resource object + :param sleep_time: wait this long (seconds) + :param callback: called per sleep cycle, useful to display progress + :rtype: True on success + """ + while True: + res = status_f(res_id) + status = getattr(res, status_field, '').lower() + if status in success_status: + retval = True + break + elif status == 'error': + retval = False + break + if callback: + progress = getattr(res, 'progress', None) or 0 + callback(progress) + time.sleep(sleep_time) + return retval diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 8c31e5d9e7..8f81dfdb2f 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -20,7 +20,6 @@ import os import six import sys -import time from cliff import command from cliff import lister @@ -90,38 +89,10 @@ def _prep_server_detail(compute_client, server): return info -def _wait_for_status(poll_fn, obj_id, final_ok_states, poll_period=5, - status_field="status"): - """Block while an action is being performed - - :param poll_fn: a function to retrieve the state of the object - :param obj_id: the id of the object - :param final_ok_states: a tuple of the states of the object that end the - wait as success, ex ['active'] - :param poll_period: the wait time between checks of object status - :param status_field: field name containing the status to be checked - """ - log = logging.getLogger(__name__ + '._wait_for_status') - while True: - obj = poll_fn(obj_id) - - status = getattr(obj, status_field) - - if status: - status = status.lower() - - if status in final_ok_states: - log.debug('Wait terminated with success') - retval = True - break - elif status == "error": - log.error('Wait terminated with an error') - retval = False - break - - time.sleep(poll_period) - - return retval +def _show_progress(progress): + if progress: + sys.stdout.write('\rProgress: %s' % progress) + sys.stdout.flush() class AddServerVolume(command.Command): @@ -263,9 +234,9 @@ def get_parser(self, prog_name): help='Maximum number of servers to launch (default=1)') parser.add_argument( '--wait', - dest='wait', action='store_true', - help='Wait for servers to become active') + help='Wait for build to complete', + ) return parser def take_action(self, parsed_args): @@ -359,8 +330,17 @@ def take_action(self, parsed_args): server = compute_client.servers.create(*boot_args, **boot_kwargs) if parsed_args.wait: - _wait_for_status(compute_client.servers.get, server._info['id'], - ['active']) + if utils.wait_for_status( + compute_client.servers.get, + server.id, + callback=_show_progress, + ): + sys.stdout.write('\n') + else: + self.log.error('Error creating server: %s' % + parsed_args.server_name) + sys.stdout.write('\nError creating server') + raise SystemExit details = _prep_server_detail(compute_client, server) return zip(*sorted(details.iteritems())) @@ -641,7 +621,7 @@ def take_action(self, parsed_args): class RebootServer(command.Command): - """Reboot server command""" + """Perform a hard or soft server reboot""" log = logging.getLogger(__name__ + '.RebootServer') @@ -650,7 +630,8 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help='Name or ID of server to reboot') + help='Server (name or ID)', + ) group = parser.add_mutually_exclusive_group() group.add_argument( '--hard', @@ -658,19 +639,21 @@ def get_parser(self, prog_name): action='store_const', const=servers.REBOOT_HARD, default=servers.REBOOT_SOFT, - help='Perform a hard reboot') + help='Perform a hard reboot', + ) group.add_argument( '--soft', dest='reboot_type', action='store_const', const=servers.REBOOT_SOFT, default=servers.REBOOT_SOFT, - help='Perform a soft reboot') + help='Perform a soft reboot', + ) parser.add_argument( '--wait', - dest='wait', action='store_true', - help='Wait for server to become active to return') + help='Wait for reboot to complete', + ) return parser def take_action(self, parsed_args): @@ -681,14 +664,19 @@ def take_action(self, parsed_args): server.reboot(parsed_args.reboot_type) if parsed_args.wait: - _wait_for_status(compute_client.servers.get, server.id, - ['active']) - - return + if utils.wait_for_status( + compute_client.servers.get, + server.id, + callback=_show_progress, + ): + sys.stdout.write('\nReboot complete\n') + else: + sys.stdout.write('\nError rebooting server\n') + raise SystemExit class RebuildServer(show.ShowOne): - """Rebuild server command""" + """Rebuild server""" log = logging.getLogger(__name__ + '.RebuildServer') @@ -697,22 +685,24 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help='Server name or ID') + help='Server (name or ID)', + ) parser.add_argument( '--image', metavar='', required=True, - help='Recreate server from this image') + help='Recreate server from this image', + ) parser.add_argument( '--password', metavar='', - default=False, - help="Set the provided password on the rebuild instance") + help="Set the password on the rebuilt instance", + ) parser.add_argument( '--wait', - dest='wait', action='store_true', - help='Wait for server to become active to return') + help='Wait for rebuild to complete', + ) return parser def take_action(self, parsed_args): @@ -725,17 +715,17 @@ def take_action(self, parsed_args): server = utils.find_resource( compute_client.servers, parsed_args.server) - _password = None - if parsed_args.password is not False: - _password = parsed_args.password - - kwargs = {} - server = server.rebuild(image, _password, **kwargs) - - # TODO(dtroyer): force silent=True if output filter != table + server = server.rebuild(image, parsed_args.password) if parsed_args.wait: - _wait_for_status(compute_client.servers.get, server._info['id'], - ['active']) + if utils.wait_for_status( + compute_client.servers.get, + server.id, + callback=_show_progress, + ): + sys.stdout.write('\nComplete\n') + else: + sys.stdout.write('\nError rebuilding server') + raise SystemExit details = _prep_server_detail(compute_client, server) return zip(*sorted(details.iteritems())) @@ -806,6 +796,67 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(server._info))) +class ResizeServer(command.Command): + """Convert server to a new flavor""" + + log = logging.getLogger(__name__ + '.ResizeServer') + + def get_parser(self, prog_name): + parser = super(ResizeServer, self).get_parser(prog_name) + phase_group = parser.add_mutually_exclusive_group() + phase_group.add_argument( + '--flavor', + metavar='', + help='Resize server to this flavor', + ) + phase_group.add_argument( + '--verify', + action="store_true", + help='Verify previous server resize', + ) + phase_group.add_argument( + '--revert', + action="store_true", + help='Restore server before resize', + ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for resize to complete', + ) + return parser + + def take_action(self, 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, + ) + if parsed_args.flavor: + flavor = utils.find_resource( + compute_client.flavors, + parsed_args.flavor, + ) + server.resize(flavor) + if parsed_args.wait: + if utils.wait_for_status( + compute_client.servers.get, + server.id, + success_status=['active', 'verify_resize'], + callback=_show_progress, + ): + sys.stdout.write('Complete\n') + else: + sys.stdout.write('\nError resizing server') + raise SystemExit + elif parsed_args.verify: + server.confirm_resize() + elif parsed_args.revert: + server.revert_resize() + + class ResumeServer(command.Command): """Resume server""" diff --git a/setup.cfg b/setup.cfg index c2a744f3d6..7fd06a2ee6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -221,6 +221,7 @@ openstack.compute.v2 = server_rebuild = openstackclient.compute.v2.server:RebuildServer server_remove_volume = openstackclient.compute.v2.server:RemoveServerVolume server_rescue = openstackclient.compute.v2.server:RescueServer + server_resize = openstackclient.compute.v2.server:ResizeServer server_resume = openstackclient.compute.v2.server:ResumeServer server_set = openstackclient.compute.v2.server:SetServer server_show = openstackclient.compute.v2.server:ShowServer From c94e262df8d2d37e6c2043a3c3d0bc1cb78348a5 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 12 Jul 2013 15:49:03 -0500 Subject: [PATCH 0107/3614] Add security group commands * Add security group: create, delete, list, set, show * Add server: add secgroup, remove secgroup * Add security group rule: create, delete, list * Add Oslo's strutils and gettextutils * Adds parseractions.RangeAction() to handle option arguments of either a single number or a range of numbers: '--port 25' or '--port 1024:65535' Blueprint: nova-client Change-Id: Iad2de1b273ba29197709fc4c6a1036b4ae99725f --- openstack-common.conf | 1 + openstackclient/common/parseractions.py | 24 ++ openstackclient/common/utils.py | 5 +- openstackclient/compute/v2/security_group.py | 394 ++++++++++++++++++ openstackclient/compute/v2/server.py | 73 ++++ openstackclient/identity/client.py | 13 +- .../openstack/common/gettextutils.py | 259 ++++++++++++ openstackclient/openstack/common/strutils.py | 218 ++++++++++ setup.cfg | 11 + 9 files changed, 996 insertions(+), 2 deletions(-) create mode 100644 openstackclient/compute/v2/security_group.py create mode 100644 openstackclient/openstack/common/gettextutils.py create mode 100644 openstackclient/openstack/common/strutils.py diff --git a/openstack-common.conf b/openstack-common.conf index 867e204af7..5e55d5867c 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -5,6 +5,7 @@ module=cfg module=iniparser module=install_venv_common module=openstackkeyring +module=strutils # The base module to hold the copy of openstack.common base=openstackclient diff --git a/openstackclient/common/parseractions.py b/openstackclient/common/parseractions.py index f111c26427..644472d88e 100644 --- a/openstackclient/common/parseractions.py +++ b/openstackclient/common/parseractions.py @@ -32,3 +32,27 @@ def __call__(self, parser, namespace, values, option_string=None): getattr(namespace, self.dest, {}).update([values.split('=', 1)]) else: getattr(namespace, self.dest, {}).pop(values, None) + + +class RangeAction(argparse.Action): + """A custom action to parse a single value or a range of values.""" + def __call__(self, parser, namespace, values, option_string=None): + range = values.split(':') + if len(range) == 0: + # Nothing passed, return a zero default + setattr(namespace, self.dest, (0, 0)) + elif len(range) == 1: + # Only a single value is present + setattr(namespace, self.dest, (int(range[0]), int(range[0]))) + elif len(range) == 2: + # Range of two values + if int(range[0]) <= int(range[1]): + setattr(namespace, self.dest, (int(range[0]), int(range[1]))) + else: + msg = "Invalid range, %s is not less than %s" % \ + (range[0], range[1]) + raise argparse.ArgumentError(self, msg) + else: + # Too many values + msg = "Invalid range, too many values" + raise argparse.ArgumentError(self, msg) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index fd504ea17d..4d2afd15b3 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -16,11 +16,13 @@ """Common client utilities""" import os +import six import sys import time import uuid from openstackclient.common import exceptions +from openstackclient.openstack.common import strutils def find_resource(manager, name_or_id): @@ -84,7 +86,8 @@ def format_dict(data): output = "" for s in data: - output = output + s + "='" + data[s] + "', " + output = output + s + "='" + \ + strutils.safe_encode(six.text_type(data[s])) + "', " return output[:-2] diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py new file mode 100644 index 0000000000..a1dc786d71 --- /dev/null +++ b/openstackclient/compute/v2/security_group.py @@ -0,0 +1,394 @@ +# Copyright 2012 OpenStack Foundation +# 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. +# + +"""Compute v2 Security Group action implementations""" + +import logging +import six + +from cliff import command +from cliff import lister +from cliff import show + +from novaclient.v1_1 import security_group_rules +from openstackclient.common import parseractions +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'] + if info['ip_protocol'] == 'icmp': + info['port_range'] = '' + return info + + +class CreateSecurityGroup(show.ShowOne): + """Create a new security group""" + + log = logging.getLogger(__name__ + ".CreateSecurityGroup") + + def get_parser(self, prog_name): + parser = super(CreateSecurityGroup, self).get_parser(prog_name) + parser.add_argument( + "name", + metavar="", + help="New security group name", + ) + parser.add_argument( + "--description", + metavar="", + help="Security group description", + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + + compute_client = self.app.client_manager.compute + + data = compute_client.security_groups.create( + parsed_args.name, + parsed_args.description, + ) + + info = {} + info.update(data._info) + return zip(*sorted(six.iteritems(info))) + + +class DeleteSecurityGroup(command.Command): + """Delete a security group""" + + log = logging.getLogger(__name__ + '.DeleteSecurityGroup') + + def get_parser(self, prog_name): + parser = super(DeleteSecurityGroup, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar='', + help='Name or ID of security group to delete', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + compute_client = self.app.client_manager.compute + data = utils.find_resource( + compute_client.security_groups, + parsed_args.group, + ) + compute_client.security_groups.delete(data.id) + return + + +class ListSecurityGroup(lister.Lister): + """List all security groups""" + + log = logging.getLogger(__name__ + ".ListSecurityGroup") + + def get_parser(self, prog_name): + parser = super(ListSecurityGroup, self).get_parser(prog_name) + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help='Display information from all projects (admin only)', + ) + return parser + + def take_action(self, parsed_args): + + def _get_project(project_id): + try: + return getattr(project_hash[project_id], 'name', project_id) + except KeyError: + return project_id + + self.log.debug("take_action(%s)" % parsed_args) + + compute_client = self.app.client_manager.compute + columns = ( + "ID", + "Name", + "Description", + ) + column_headers = columns + if parsed_args.all_projects: + # TODO(dtroyer): Translate Project_ID to Project (name) + columns = columns + ('Tenant ID',) + column_headers = column_headers + ('Project',) + search = {'all_tenants': parsed_args.all_projects} + data = compute_client.security_groups.list(search_opts=search) + + projects = self.app.client_manager.identity.projects.list() + project_hash = {} + for project in projects: + project_hash[project.id] = project + + return (column_headers, + (utils.get_item_properties( + s, columns, + formatters={'Tenant ID': _get_project}, + ) for s in data)) + + +class SetSecurityGroup(show.ShowOne): + """Set security group properties""" + + log = logging.getLogger(__name__ + '.SetSecurityGroup') + + def get_parser(self, prog_name): + parser = super(SetSecurityGroup, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar='', + help='Name or ID of security group to change', + ) + parser.add_argument( + '--name', + metavar='', + help='New security group name', + ) + parser.add_argument( + "--description", + metavar="", + help="New security group name", + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + compute_client = self.app.client_manager.compute + data = utils.find_resource( + compute_client.security_groups, + parsed_args.group, + ) + + if parsed_args.name: + data.name = parsed_args.name + if parsed_args.description: + data.description = parsed_args.description + + info = {} + info.update(compute_client.security_groups.update( + data, + data.name, + data.description, + )._info) + + if info: + return zip(*sorted(six.iteritems(info))) + else: + return ({}, {}) + + +class ShowSecurityGroup(show.ShowOne): + """Show a specific security group""" + + log = logging.getLogger(__name__ + '.ShowSecurityGroup') + + def get_parser(self, prog_name): + parser = super(ShowSecurityGroup, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar='', + help='Name or ID of security group to change', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + compute_client = self.app.client_manager.compute + info = {} + info.update(utils.find_resource( + compute_client.security_groups, + parsed_args.group, + )._info) + rules = [] + for r in info['rules']: + rules.append(utils.format_dict(_xform_security_group_rule(r))) + + # Format rules into a list of strings + info.update( + {'rules': rules} + ) + # Map 'tenant_id' column to 'project_id' + info.update( + {'project_id': info.pop('tenant_id')} + ) + + return zip(*sorted(six.iteritems(info))) + + +class CreateSecurityGroupRule(show.ShowOne): + """Create a new security group rule""" + + log = logging.getLogger(__name__ + ".CreateSecurityGroupRule") + + def get_parser(self, prog_name): + parser = super(CreateSecurityGroupRule, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar='', + help='Create rule in this security group', + ) + parser.add_argument( + "--proto", + metavar="", + default="tcp", + help="IP protocol (icmp, tcp, udp; default: tcp)", + ) + parser.add_argument( + "--src-ip", + metavar="", + default="0.0.0.0/0", + help="Source IP (may use CIDR notation; default: 0.0.0.0/0)", + ) + parser.add_argument( + "--dst-port", + metavar="", + action=parseractions.RangeAction, + help="Destination port, may be a range: 137:139 (default: 0; " + "only required for proto tcp and udp)", + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + + compute_client = self.app.client_manager.compute + group = utils.find_resource( + compute_client.security_groups, + parsed_args.group, + ) + from_port, to_port = parsed_args.dst_port + data = compute_client.security_group_rules.create( + group.id, + parsed_args.proto, + from_port, + to_port, + parsed_args.src_ip, + ) + + info = _xform_security_group_rule(data._info) + return zip(*sorted(six.iteritems(info))) + + +class DeleteSecurityGroupRule(command.Command): + """Delete a security group rule""" + + log = logging.getLogger(__name__ + '.DeleteSecurityGroupRule') + + def get_parser(self, prog_name): + parser = super(DeleteSecurityGroupRule, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar='', + help='Create rule in this security group', + ) + parser.add_argument( + "--proto", + metavar="", + default="tcp", + help="IP protocol (icmp, tcp, udp; default: tcp)", + ) + parser.add_argument( + "--src-ip", + metavar="", + default="0.0.0.0/0", + help="Source IP (may use CIDR notation; default: 0.0.0.0/0)", + ) + parser.add_argument( + "--dst-port", + metavar="", + action=parseractions.RangeAction, + help="Destination port, may be a range: 137:139 (default: 0; " + "only required for proto tcp and udp)", + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + compute_client = self.app.client_manager.compute + group = utils.find_resource( + compute_client.security_groups, + parsed_args.group, + ) + from_port, to_port = parsed_args.dst_port + # sigh...delete by ID? + compute_client.security_group_rules.delete( + group.id, + parsed_args.proto, + from_port, + to_port, + parsed_args.src_ip, + ) + return + + +class ListSecurityGroupRule(lister.Lister): + """List all security group rules""" + + log = logging.getLogger(__name__ + ".ListSecurityGroupRule") + + def get_parser(self, prog_name): + parser = super(ListSecurityGroupRule, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar='', + help='Create rule in this security group', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + + compute_client = self.app.client_manager.compute + group = utils.find_resource( + compute_client.security_groups, + parsed_args.group, + ) + + # Argh, the rules are not Resources... + rules = [] + for rule in group.rules: + rules.append(security_group_rules.SecurityGroupRule( + compute_client.security_group_rules, + _xform_security_group_rule(rule), + )) + + columns = column_headers = ( + "ID", + "IP Protocol", + "IP Range", + "Port Range", + ) + return (column_headers, + (utils.get_item_properties( + s, columns, + ) for s in rules)) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 8f81dfdb2f..6be97981a9 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -141,6 +141,43 @@ def take_action(self, parsed_args): ) +class AddServerSecurityGroup(command.Command): + """Add security group to server""" + + log = logging.getLogger(__name__ + '.AddServerSecurityGroup') + + def get_parser(self, prog_name): + parser = super(AddServerSecurityGroup, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Name or ID of server to use', + ) + parser.add_argument( + 'group', + metavar='', + help='Name or ID of security group to add to server', + ) + return parser + + def take_action(self, 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, + ) + security_group = utils.find_resource( + compute_client.security_groups, + parsed_args.group, + ) + + server.add_security_group(security_group) + return + + class CreateServer(show.ShowOne): """Create a new server""" @@ -731,6 +768,42 @@ def take_action(self, parsed_args): return zip(*sorted(details.iteritems())) +class RemoveServerSecurityGroup(command.Command): + """Remove security group from server""" + + log = logging.getLogger(__name__ + '.RemoveServerSecurityGroup') + + def get_parser(self, prog_name): + parser = super(RemoveServerSecurityGroup, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Name or ID of server to use', + ) + parser.add_argument( + 'group', + metavar='', + help='Name or ID of security group to remove from server', + ) + return parser + + def take_action(self, 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, + ) + security_group = utils.find_resource( + compute_client.security_groups, + parsed_args.group, + ) + + server.remove_security_group(security_group) + + class RemoveServerVolume(command.Command): """Remove volume from server""" diff --git a/openstackclient/identity/client.py b/openstackclient/identity/client.py index 748d166683..0f8fbb815d 100644 --- a/openstackclient/identity/client.py +++ b/openstackclient/identity/client.py @@ -15,6 +15,7 @@ import logging +from keystoneclient.v2_0 import client as identity_client_v2_0 from openstackclient.common import utils @@ -22,7 +23,7 @@ API_NAME = 'identity' API_VERSIONS = { - '2.0': 'keystoneclient.v2_0.client.Client', + '2.0': 'openstackclient.identity.client.IdentityClientv2_0', '3': 'keystoneclient.v3.client.Client', } @@ -48,3 +49,13 @@ def make_client(instance): auth_url=instance._auth_url, region_name=instance._region_name) return client + + +class IdentityClientv2_0(identity_client_v2_0.Client): + """Tweak the earlier client class to deal with some changes""" + def __getattr__(self, name): + # Map v3 'projects' back to v2 'tenants' + if name == "projects": + return self.tenants + else: + raise AttributeError, name diff --git a/openstackclient/openstack/common/gettextutils.py b/openstackclient/openstack/common/gettextutils.py new file mode 100644 index 0000000000..2dd5449e6c --- /dev/null +++ b/openstackclient/openstack/common/gettextutils.py @@ -0,0 +1,259 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 Red Hat, Inc. +# All Rights Reserved. +# Copyright 2013 IBM Corp. +# +# 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. + +""" +gettext for openstack-common modules. + +Usual usage in an openstack.common module: + + from openstackclient.openstack.common.gettextutils import _ +""" + +import copy +import gettext +import logging.handlers +import os +import re +import UserString + +import six + +_localedir = os.environ.get('openstackclient'.upper() + '_LOCALEDIR') +_t = gettext.translation('openstackclient', localedir=_localedir, fallback=True) + + +def _(msg): + return _t.ugettext(msg) + + +def install(domain): + """Install a _() function using the given translation domain. + + Given a translation domain, install a _() function using gettext's + install() function. + + The main difference from gettext.install() is that we allow + overriding the default localedir (e.g. /usr/share/locale) using + a translation-domain-specific environment variable (e.g. + NOVA_LOCALEDIR). + """ + gettext.install(domain, + localedir=os.environ.get(domain.upper() + '_LOCALEDIR'), + unicode=True) + + +""" +Lazy gettext functionality. + +The following is an attempt to introduce a deferred way +to do translations on messages in OpenStack. We attempt to +override the standard _() function and % (format string) operation +to build Message objects that can later be translated when we have +more information. Also included is an example LogHandler that +translates Messages to an associated locale, effectively allowing +many logs, each with their own locale. +""" + + +def get_lazy_gettext(domain): + """Assemble and return a lazy gettext function for a given domain. + + Factory method for a project/module to get a lazy gettext function + for its own translation domain (i.e. nova, glance, cinder, etc.) + """ + + def _lazy_gettext(msg): + """Create and return a Message object. + + Message encapsulates a string so that we can translate it later when + needed. + """ + return Message(msg, domain) + + return _lazy_gettext + + +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.params = None + self.locale = 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) + + full_msg = (self._left_extra_msg + + lang.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) + + 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] = unicode(dict_param[key]) + + return params + + 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 + if other is None: + self.params = (other, ) + elif isinstance(other, dict): + self.params = self._save_dictionary_parameter(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 = unicode(other) + + return self + + # overrides to be more string-like + def __unicode__(self): + return self.data + + def __str__(self): + return self.data.encode('utf-8') + + 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]) + + return new_dict + + def __setstate__(self, state): + for (k, v) in state.items(): + setattr(self, k, v) + + # operator overloads + def __add__(self, other): + copied = copy.deepcopy(self) + copied._right_extra_msg += other.__str__() + return copied + + def __radd__(self, other): + copied = copy.deepcopy(self) + copied._left_extra_msg += other.__str__() + return copied + + 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) + + +class LocaleHandler(logging.Handler): + """Handler that can have a locale associated to translate Messages. + + 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. + """ + + def __init__(self, locale, target): + """Initialize a LocaleHandler + + :param locale: locale to use for translating messages + :param target: logging.Handler object to forward + LogRecord objects to after translation + """ + logging.Handler.__init__(self) + self.locale = locale + self.target = target + + def emit(self, record): + if isinstance(record.msg, Message): + # set the locale and resolve to a string + record.msg.locale = self.locale + + self.target.emit(record) diff --git a/openstackclient/openstack/common/strutils.py b/openstackclient/openstack/common/strutils.py new file mode 100644 index 0000000000..e3f26a7876 --- /dev/null +++ b/openstackclient/openstack/common/strutils.py @@ -0,0 +1,218 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack Foundation. +# All Rights Reserved. +# +# 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. + +""" +System-level utilities and helper functions. +""" + +import re +import sys +import unicodedata + +import six + +from openstackclient.openstack.common.gettextutils import _ # noqa + + +# 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, +} +BYTE_REGEX = re.compile(r'(^-?\d+)(\D*)') + +TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes') +FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no') + +SLUGIFY_STRIP_RE = re.compile(r"[^\w\s-]") +SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+") + + +def int_from_bool_as_string(subject): + """Interpret a string as a boolean and return either 1 or 0. + + Any string value in: + + ('True', 'true', 'On', 'on', '1') + + is interpreted as a boolean True. + + Useful for JSON-decoded stuff and config file parsing + """ + return bool_from_string(subject) and 1 or 0 + + +def bool_from_string(subject, strict=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. + + Useful for JSON-decoded stuff and config file parsing. + + If `strict=True`, unrecognized values, including None, will raise a + ValueError which is useful when parsing values passed in from an API call. + Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'. + """ + if not isinstance(subject, six.string_types): + subject = str(subject) + + lowered = subject.strip().lower() + + if lowered in TRUE_STRINGS: + return True + elif lowered in FALSE_STRINGS: + return False + elif strict: + acceptable = ', '.join( + "'%s'" % s for s in sorted(TRUE_STRINGS + FALSE_STRINGS)) + msg = _("Unrecognized value '%(val)s', acceptable values are:" + " %(acceptable)s") % {'val': subject, + 'acceptable': acceptable} + raise ValueError(msg) + else: + return False + + +def safe_decode(text, incoming=None, errors='strict'): + """Decodes incoming str using `incoming` if they're not already unicode. + + :param incoming: Text's current encoding + :param errors: Errors handling policy. See here for valid + 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 + """ + if not isinstance(text, six.string_types): + raise TypeError("%s can't be decoded" % type(text)) + + if isinstance(text, six.text_type): + return text + + if not incoming: + incoming = (sys.stdin.encoding or + sys.getdefaultencoding()) + + try: + return text.decode(incoming, errors) + except UnicodeDecodeError: + # Note(flaper87) If we get here, it means that + # sys.stdin.encoding / sys.getdefaultencoding + # didn't return a suitable encoding to decode + # text. This happens mostly when global LANG + # var is not set correctly and there's no + # default encoding. In this case, most likely + # python will use ASCII or ANSI encoders as + # default encodings but they won't be capable + # of decoding non-ASCII characters. + # + # Also, UTF-8 is being used since it's an ASCII + # extension. + return text.decode('utf-8', errors) + + +def safe_encode(text, incoming=None, + encoding='utf-8', errors='strict'): + """Encodes incoming str/unicode using `encoding`. + + If incoming is not specified, text is expected to be encoded with + current python's default encoding. (`sys.getdefaultencoding`) + + :param incoming: Text's current encoding + :param encoding: Expected encoding for text (Default UTF-8) + :param errors: Errors handling policy. See here for valid + 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 + """ + if not isinstance(text, six.string_types): + raise TypeError("%s can't be encoded" % type(text)) + + if not incoming: + incoming = (sys.stdin.encoding or + sys.getdefaultencoding()) + + if isinstance(text, six.text_type): + 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) + + return text + + +def to_bytes(text, default=0): + """Converts a string into an integer of bytes. + + 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) + + :param text: String input for bytes size conversion. + :param default: Default return value when text is blank. + + """ + match = BYTE_REGEX.search(text) + if match: + magnitude = int(match.group(1)) + mult_key_org = match.group(2) + if not mult_key_org: + return magnitude + elif text: + msg = _('Invalid string format: %s') % text + raise TypeError(msg) + 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 + + +def to_slug(value, incoming=None, errors="strict"): + """Normalize string. + + Convert to lowercase, remove non-word characters, and convert spaces + to hyphens. + + Inspired by Django's `slugify` filter. + + :param value: Text to slugify + :param incoming: Text's current encoding + :param errors: Errors handling policy. See here for valid + values http://docs.python.org/2/library/codecs.html + :returns: slugified unicode representation of `value` + :raises TypeError: If text is not an instance of str + """ + value = safe_decode(value, incoming, errors) + # NOTE(aababilov): no need to use safe_(encode|decode) here: + # encodings are always "ascii", error handling is always "ignore" + # and types are always known (first: unicode; second: str) + value = unicodedata.normalize("NFKD", value).encode( + "ascii", "ignore").decode("ascii") + value = SLUGIFY_STRIP_RE.sub("", value).strip().lower() + return SLUGIFY_HYPHENATE_RE.sub("-", value) diff --git a/setup.cfg b/setup.cfg index 7fd06a2ee6..0fa59c4020 100644 --- a/setup.cfg +++ b/setup.cfg @@ -210,6 +210,16 @@ openstack.compute.v2 = project_usage_list = openstackclient.compute.v2.usage:ListUsage + security_group_create = openstackclient.compute.v2.secgroup:CreateSecurityGroup + security_group_delete = openstackclient.compute.v2.secgroup:DeleteSecurityGroup + security_group_list = openstackclient.compute.v2.secgroup:ListSecurityGroup + security_group_set = openstackclient.compute.v2.secgroup:SetSecurityGroup + security_group_show = openstackclient.compute.v2.secgroup:ShowSecurityGroup + security_group_rule_create = openstackclient.compute.v2.secgroup:CreateSecurityGroupRule + security_group_rule_delete = openstackclient.compute.v2.secgroup:DeleteSecurityGroupRule + security_group_rule_list = openstackclient.compute.v2.secgroup:ListSecurityGroupRule + + server_add_security_group = openstackclient.compute.v2.server:AddServerSecurityGroup server_add_volume = openstackclient.compute.v2.server:AddServerVolume server_create = openstackclient.compute.v2.server:CreateServer server_delete = openstackclient.compute.v2.server:DeleteServer @@ -219,6 +229,7 @@ openstack.compute.v2 = server_pause = openstackclient.compute.v2.server:PauseServer server_reboot = openstackclient.compute.v2.server:RebootServer server_rebuild = openstackclient.compute.v2.server:RebuildServer + server_remove_security_group = openstackclient.compute.v2.server:RemoveServerSecurityGroup server_remove_volume = openstackclient.compute.v2.server:RemoveServerVolume server_rescue = openstackclient.compute.v2.server:RescueServer server_resize = openstackclient.compute.v2.server:ResizeServer From dfb0e3e3c1b5b5563279bebfe222ed4762f79494 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 3 Jul 2013 18:12:58 -0500 Subject: [PATCH 0108/3614] Begin Python 3 compatability * use six.iteritems() * replace basestring with six.string_types * convert print statements to functions (they're all debugging and should be removed eventually anyway) * clean up OpenStack copyright: LLC -> Foundation Change-Id: Icb14212bcb408e63816bfec3922a697bc1a6c946 --- openstackclient/compute/v2/agent.py | 7 ++++--- openstackclient/compute/v2/console.py | 7 +++---- openstackclient/compute/v2/flavor.py | 7 ++++--- openstackclient/compute/v2/floatingip.py | 3 ++- openstackclient/compute/v2/hypervisor.py | 3 ++- openstackclient/compute/v2/keypair.py | 5 +++-- openstackclient/compute/v2/server.py | 8 ++++---- openstackclient/identity/v2_0/ec2creds.py | 8 +++++--- openstackclient/identity/v2_0/endpoint.py | 9 +++++---- openstackclient/identity/v2_0/role.py | 9 +++++---- openstackclient/identity/v2_0/service.py | 9 +++++---- openstackclient/identity/v2_0/tenant.py | 7 ++++--- openstackclient/identity/v2_0/user.py | 7 ++++--- openstackclient/identity/v3/credential.py | 7 ++++--- openstackclient/identity/v3/domain.py | 7 ++++--- openstackclient/identity/v3/endpoint.py | 7 ++++--- openstackclient/identity/v3/group.py | 7 ++++--- openstackclient/identity/v3/oauth.py | 17 +++++++++-------- openstackclient/identity/v3/policy.py | 7 ++++--- openstackclient/identity/v3/project.py | 7 ++++--- openstackclient/identity/v3/role.py | 7 ++++--- openstackclient/identity/v3/service.py | 7 ++++--- openstackclient/identity/v3/user.py | 7 ++++--- openstackclient/volume/v1/backup.py | 7 ++++--- openstackclient/volume/v1/snapshot.py | 5 +++-- openstackclient/volume/v1/type.py | 3 ++- openstackclient/volume/v1/volume.py | 5 +++-- 27 files changed, 107 insertions(+), 82 deletions(-) diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py index aac69d8a0b..b79ebbe761 100644 --- a/openstackclient/compute/v2/agent.py +++ b/openstackclient/compute/v2/agent.py @@ -1,4 +1,4 @@ -# Copyright 2013 OpenStack, LLC. +# 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 @@ -16,6 +16,7 @@ """Agent action implementations""" import logging +import six from cliff import command from cliff import lister @@ -70,7 +71,7 @@ def take_action(self, parsed_args): parsed_args.hypervisor ) agent = compute_client.agents.create(*args)._info.copy() - return zip(*sorted(agent.iteritems())) + return zip(*sorted(six.iteritems(agent))) class DeleteAgent(command.Command): @@ -160,4 +161,4 @@ def take_action(self, parsed_args): parsed_args.md5hash ) agent = compute_client.agents.update(*args)._info.copy() - return zip(*sorted(agent.iteritems())) + return zip(*sorted(six.iteritems(agent))) diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index a67b004c05..8f49c5134a 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -13,9 +13,10 @@ # under the License. # -"""Console action implementations""" +"""Compute v2 Console action implementations""" import logging +import six import sys from cliff import command @@ -106,7 +107,6 @@ def take_action(self, parsed_args): parsed_args.server, ) - print "type: %s" % parsed_args.url_type if parsed_args.url_type in ['novnc', 'xvpvnc']: data = server.get_vnc_console(parsed_args.url_type) if parsed_args.url_type in ['spice']: @@ -114,8 +114,7 @@ def take_action(self, parsed_args): if not data: return ({}, {}) - print "data: %s" % data['console'] info = {} info.update(data['console']) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 4d53a4122e..d1d08d8df0 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -1,4 +1,4 @@ -# Copyright 2013 OpenStack, LLC. +# 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 @@ -16,6 +16,7 @@ """Flavor action implementations""" import logging +import six from cliff import command from cliff import lister @@ -110,7 +111,7 @@ def take_action(self, parsed_args): flavor = compute_client.flavors.create(*args)._info.copy() flavor.pop("links") - return zip(*sorted(flavor.iteritems())) + return zip(*sorted(six.iteritems(flavor))) class DeleteFlavor(command.Command): @@ -182,4 +183,4 @@ def take_action(self, parsed_args): parsed_args.flavor)._info.copy() flavor.pop("links") - return zip(*sorted(flavor.iteritems())) + return zip(*sorted(six.iteritems(flavor))) diff --git a/openstackclient/compute/v2/floatingip.py b/openstackclient/compute/v2/floatingip.py index 1b07beb3d3..7ed847f5f4 100644 --- a/openstackclient/compute/v2/floatingip.py +++ b/openstackclient/compute/v2/floatingip.py @@ -16,6 +16,7 @@ """Floating IP action implementations""" import logging +import six from cliff import command from cliff import lister @@ -75,7 +76,7 @@ def take_action(self, parsed_args): info = {} info.update(floating_ip._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class DeleteFloatingIP(command.Command): diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py index ad69d3285a..535062e8da 100644 --- a/openstackclient/compute/v2/hypervisor.py +++ b/openstackclient/compute/v2/hypervisor.py @@ -16,6 +16,7 @@ """Hypervisor action implementations""" import logging +import six from cliff import lister from cliff import show @@ -79,4 +80,4 @@ def take_action(self, parsed_args): hypervisor["service_host"] = hypervisor["service"]["host"] del hypervisor["service"] - return zip(*sorted(hypervisor.iteritems())) + return zip(*sorted(six.iteritems(hypervisor))) diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index 65f3679b71..d68dae0643 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -17,6 +17,7 @@ import logging import os +import six import sys from cliff import command @@ -71,7 +72,7 @@ def take_action(self, parsed_args): if public_key: info.update(keypair._info) del info['public_key'] - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) else: sys.stdout.write(keypair.private_key) return ({}, {}) @@ -148,7 +149,7 @@ def take_action(self, parsed_args): info.update(keypair._info['keypair']) if not parsed_args.public_key: del info['public_key'] - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) else: # NOTE(dtroyer): a way to get the public key in a similar form # as the private key in the create command diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 8f81dfdb2f..42529af586 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -293,7 +293,7 @@ def take_action(self, parsed_args): # NOTE(vish): multiple copies of the same hint will # result in a list of values if key in hints: - if isinstance(hints[key], basestring): + if isinstance(hints[key], six.string_types): hints[key] = [hints[key]] hints[key] += [value] else: @@ -343,7 +343,7 @@ def take_action(self, parsed_args): raise SystemExit details = _prep_server_detail(compute_client, server) - return zip(*sorted(details.iteritems())) + return zip(*sorted(six.iteritems(details))) class DeleteServer(command.Command): @@ -728,7 +728,7 @@ def take_action(self, parsed_args): raise SystemExit details = _prep_server_detail(compute_client, server) - return zip(*sorted(details.iteritems())) + return zip(*sorted(six.iteritems(details))) class RemoveServerVolume(command.Command): @@ -974,7 +974,7 @@ def take_action(self, parsed_args): else: data = _prep_server_detail(compute_client, server) - return zip(*sorted(data.iteritems())) + return zip(*sorted(six.iteritems(data))) class SuspendServer(command.Command): diff --git a/openstackclient/identity/v2_0/ec2creds.py b/openstackclient/identity/v2_0/ec2creds.py index 953a3de6ad..cb3a51658f 100644 --- a/openstackclient/identity/v2_0/ec2creds.py +++ b/openstackclient/identity/v2_0/ec2creds.py @@ -1,3 +1,4 @@ +# Copyright 2012 OpenStack Foundation # Copyright 2013 Nebula Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -13,9 +14,10 @@ # under the License. # -"""EC2 Credentials action implementations""" +"""Identity v2 EC2 Credentials action implementations""" import logging +import six from cliff import command from cliff import lister @@ -68,7 +70,7 @@ def take_action(self, parsed_args): info = {} info.update(creds._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class DeleteEC2Creds(command.Command): @@ -178,4 +180,4 @@ def take_action(self, parsed_args): info = {} info.update(creds._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/identity/v2_0/endpoint.py b/openstackclient/identity/v2_0/endpoint.py index 680465ae47..5a050fa190 100644 --- a/openstackclient/identity/v2_0/endpoint.py +++ b/openstackclient/identity/v2_0/endpoint.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# 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 @@ -16,6 +16,7 @@ """Endpoint action implementations""" import logging +import six from cliff import command from cliff import lister @@ -71,7 +72,7 @@ def take_action(self, parsed_args): info.update(endpoint._info) info['service_name'] = service.name info['service_type'] = service.type - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class DeleteEndpoint(command.Command): @@ -183,7 +184,7 @@ def take_action(self, parsed_args): url = identity_client.service_catalog.url_for(**kwargs) info = {'%s.%s' % (parsed_args.service, parsed_args.type): url} - return zip(*sorted(info.iteritems())) + 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 @@ -211,4 +212,4 @@ def take_action(self, parsed_args): ep.service_id) info['service_name'] = service.name info['service_type'] = service.type - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index 867230b6b2..61a83af575 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# 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 @@ -16,6 +16,7 @@ """Role action implementations""" import logging +import six from cliff import command from cliff import lister @@ -61,7 +62,7 @@ def take_action(self, parsed_args): info = {} info.update(role._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class CreateRole(show.ShowOne): @@ -84,7 +85,7 @@ def take_action(self, parsed_args): info = {} info.update(role._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class DeleteRole(command.Command): @@ -229,4 +230,4 @@ def take_action(self, parsed_args): info = {} info.update(role._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index 629475df00..2e81805bb2 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -16,6 +16,7 @@ """Service action implementations""" import logging +import six from cliff import command from cliff import lister @@ -58,7 +59,7 @@ def take_action(self, parsed_args): info = {} info.update(service._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class DeleteService(command.Command): @@ -136,11 +137,11 @@ def take_action(self, parsed_args): if parsed_args.catalog: endpoints = identity_client.service_catalog.get_endpoints( service_type=parsed_args.service) - for (service, service_endpoints) in endpoints.iteritems(): + for (service, service_endpoints) in six.iteritems(endpoints): if service_endpoints: info = {"type": service} info.update(service_endpoints[0]) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) msg = ("No service catalog with a type, name or ID of '%s' " "exists." % (parsed_args.service)) @@ -166,4 +167,4 @@ def take_action(self, parsed_args): info = {} info.update(service._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/identity/v2_0/tenant.py b/openstackclient/identity/v2_0/tenant.py index c9a423c5f1..d535716cac 100644 --- a/openstackclient/identity/v2_0/tenant.py +++ b/openstackclient/identity/v2_0/tenant.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# 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 @@ -16,6 +16,7 @@ """Tenant action implementations""" import logging +import six import sys from cliff import command @@ -64,7 +65,7 @@ def take_action(self, parsed_args): info = {} info.update(tenant._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class DeleteTenant(command.Command): @@ -191,4 +192,4 @@ def take_action(self, parsed_args): info = {} info.update(tenant._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index 39be81af82..8c8e2622db 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# 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 @@ -16,6 +16,7 @@ """Identity v2.0 User action implementations""" import logging +import six import sys from cliff import command @@ -79,7 +80,7 @@ def take_action(self, parsed_args): info = {} info.update(user._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class DeleteUser(command.Command): @@ -219,4 +220,4 @@ def take_action(self, parsed_args): info = {} info.update(user._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py index a2fb43a256..b82825f00b 100644 --- a/openstackclient/identity/v3/credential.py +++ b/openstackclient/identity/v3/credential.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# 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 @@ -16,6 +16,7 @@ """Identity v3 Credential action implementations""" import logging +import six import sys from cliff import command @@ -72,7 +73,7 @@ def take_action(self, parsed_args): parsed_args.data, project=project) - return zip(*sorted(credential._info.iteritems())) + return zip(*sorted(six.iteritems(credential._info))) class DeleteCredential(command.Command): @@ -191,4 +192,4 @@ def take_action(self, parsed_args): credential = utils.find_resource(identity_client.credentials, parsed_args.credential) - return zip(*sorted(credential._info.iteritems())) + return zip(*sorted(six.iteritems(credential._info))) diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index f6064a589c..1e9a4a2a0b 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# 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 @@ -16,6 +16,7 @@ """Identity v3 Domain action implementations""" import logging +import six import sys from cliff import command @@ -65,7 +66,7 @@ def take_action(self, parsed_args): enabled=parsed_args.enabled, ) - return zip(*sorted(domain._info.iteritems())) + return zip(*sorted(six.iteritems(domain._info))) class DeleteDomain(command.Command): @@ -185,4 +186,4 @@ def take_action(self, parsed_args): domain = utils.find_resource(identity_client.domains, parsed_args.domain) - return zip(*sorted(domain._info.iteritems())) + return zip(*sorted(six.iteritems(domain._info))) diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index c5f3ebadff..43da07aaad 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# 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 @@ -16,6 +16,7 @@ """Identity v3 Endpoint action implementations""" import logging +import six import sys from cliff import command @@ -83,7 +84,7 @@ def take_action(self, parsed_args): info.update(endpoint._info) info['service_name'] = service.name info['service_type'] = service.type - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class DeleteEndpoint(command.Command): @@ -239,4 +240,4 @@ def take_action(self, parsed_args): info.update(endpoint._info) info['service_name'] = service.name info['service_type'] = service.type - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index ca0493ebbb..b5d24ef5f8 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# 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 @@ -16,6 +16,7 @@ """Group action implementations""" import logging +import six import sys from cliff import command @@ -138,7 +139,7 @@ def take_action(self, parsed_args): info = {} info.update(group._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class DeleteGroup(command.Command): @@ -375,4 +376,4 @@ def take_action(self, parsed_args): info = {} info.update(group._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/identity/v3/oauth.py b/openstackclient/identity/v3/oauth.py index bcbbdf7e09..1672cd24f3 100644 --- a/openstackclient/identity/v3/oauth.py +++ b/openstackclient/identity/v3/oauth.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# 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 @@ -16,6 +16,7 @@ """Identity v3 OAuth action implementations""" import logging +import six import sys from cliff import command @@ -65,7 +66,7 @@ def take_action(self, parsed_args): keystone_token = oauth_client.authenticate( parsed_args.consumer_key, parsed_args.consumer_secret, parsed_args.access_key, parsed_args.access_secret) - return zip(*sorted(keystone_token.iteritems())) + return zip(*sorted(six.iteritems(keystone_token))) class AuthorizeRequestToken(show.ShowOne): @@ -97,7 +98,7 @@ def take_action(self, parsed_args): parsed_args.request_key, parsed_args.roles) info = {} info.update(verifier_pin._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class CreateAccessToken(show.ShowOne): @@ -146,7 +147,7 @@ def take_action(self, parsed_args): parsed_args.consumer_key, parsed_args.consumer_secret, parsed_args.request_key, parsed_args.request_secret, parsed_args.verifier) - return zip(*sorted(access_token.iteritems())) + return zip(*sorted(six.iteritems(access_token))) class CreateConsumer(show.ShowOne): @@ -171,7 +172,7 @@ def take_action(self, parsed_args): ) info = {} info.update(consumer._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class CreateRequestToken(show.ShowOne): @@ -207,7 +208,7 @@ def take_action(self, parsed_args): parsed_args.consumer_key, parsed_args.consumer_secret, parsed_args.roles) - return zip(*sorted(request_token.iteritems())) + return zip(*sorted(six.iteritems(request_token))) class DeleteConsumer(command.Command): @@ -366,7 +367,7 @@ def take_action(self, parsed_args): parsed_args.request_id) info = {} info.update(data._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class ShowConsumer(show.ShowOne): @@ -391,4 +392,4 @@ def take_action(self, parsed_args): info = {} info.update(consumer._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/identity/v3/policy.py b/openstackclient/identity/v3/policy.py index ac14cc464d..cdbb1cf299 100644 --- a/openstackclient/identity/v3/policy.py +++ b/openstackclient/identity/v3/policy.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# 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 @@ -16,6 +16,7 @@ """Identity v3 Policy action implementations""" import logging +import six import sys from cliff import command @@ -54,7 +55,7 @@ def take_action(self, parsed_args): blob, type=parsed_args.type ) - return zip(*sorted(policy._info.iteritems())) + return zip(*sorted(six.iteritems(policy._info))) class DeletePolicy(command.Command): @@ -172,7 +173,7 @@ def take_action(self, parsed_args): policy = utils.find_resource(identity_client.policies, parsed_args.policy) - return zip(*sorted(policy._info.iteritems())) + return zip(*sorted(six.iteritems(policy._info))) def _read_blob_file_contents(blob_file): diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 9d1e360bac..05722b54d1 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# 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 @@ -16,6 +16,7 @@ """Project action implementations""" import logging +import six import sys from cliff import command @@ -78,7 +79,7 @@ def take_action(self, parsed_args): info = {} info.update(project._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class DeleteProject(command.Command): @@ -213,4 +214,4 @@ def take_action(self, parsed_args): info = {} info.update(project._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 9be3b78410..5403d4cb8c 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# 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 @@ -16,6 +16,7 @@ """Identity v3 Role action implementations""" import logging +import six import sys from cliff import command @@ -123,7 +124,7 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity role = identity_client.roles.create(parsed_args.name) - return zip(*sorted(role._info.iteritems())) + return zip(*sorted(six.iteritems(role._info))) class DeleteRole(command.Command): @@ -296,4 +297,4 @@ def take_action(self, parsed_args): role = utils.find_resource(identity_client.roles, parsed_args.role) - return zip(*sorted(role._info.iteritems())) + return zip(*sorted(six.iteritems(role._info))) diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py index 5c82284c52..77efbeadfc 100644 --- a/openstackclient/identity/v3/service.py +++ b/openstackclient/identity/v3/service.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# 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 @@ -16,6 +16,7 @@ """Identity v3 Service action implementations""" import logging +import six import sys from cliff import command @@ -62,7 +63,7 @@ def take_action(self, parsed_args): parsed_args.type, parsed_args.enabled) - return zip(*sorted(service._info.iteritems())) + return zip(*sorted(six.iteritems(service._info))) class DeleteService(command.Command): @@ -178,4 +179,4 @@ def take_action(self, parsed_args): service = utils.find_resource(identity_client.services, parsed_args.service) - return zip(*sorted(service._info.iteritems())) + return zip(*sorted(six.iteritems(service._info))) diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 8ee535dc33..b90527a3f5 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# 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 @@ -16,6 +16,7 @@ """Identity v3 User action implementations""" import logging +import six import sys from cliff import command @@ -106,7 +107,7 @@ def take_action(self, parsed_args): info = {} info.update(user._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class DeleteUser(command.Command): @@ -355,4 +356,4 @@ def take_action(self, parsed_args): info = {} info.update(user._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py index 8ef666c1fa..dbf6eb7370 100644 --- a/openstackclient/volume/v1/backup.py +++ b/openstackclient/volume/v1/backup.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# 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 @@ -16,6 +16,7 @@ """Volume v1 Backup action implementations""" import logging +import six from cliff import command from cliff import lister @@ -68,7 +69,7 @@ def take_action(self, parsed_args): ) backup._info.pop('links') - return zip(*sorted(backup._info.iteritems())) + return zip(*sorted(six.iteritems(backup._info))) class DeleteBackup(command.Command): @@ -163,4 +164,4 @@ def take_action(self, parsed_args): backup = utils.find_resource(volume_client.backups, parsed_args.backup) backup._info.pop('links') - return zip(*sorted(backup._info.iteritems())) + return zip(*sorted(six.iteritems(backup._info))) diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index 6055a4d8af..d3a56b759e 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -16,6 +16,7 @@ """Volume v1 Snapshot action implementations""" import logging +import six import sys from cliff import command @@ -69,7 +70,7 @@ def take_action(self, parsed_args): parsed_args.description ) - return zip(*sorted(snapshot._info.iteritems())) + return zip(*sorted(six.iteritems(snapshot._info))) class DeleteSnapshot(command.Command): @@ -175,4 +176,4 @@ def take_action(self, parsed_args): snapshot = utils.find_resource(volume_client.volume_snapshots, parsed_args.snapshot) - return zip(*sorted(snapshot._info.iteritems())) + return zip(*sorted(six.iteritems(snapshot._info))) diff --git a/openstackclient/volume/v1/type.py b/openstackclient/volume/v1/type.py index 895f237776..ecf22781b4 100644 --- a/openstackclient/volume/v1/type.py +++ b/openstackclient/volume/v1/type.py @@ -16,6 +16,7 @@ """Volume v1 Type action implementations""" import logging +import six from cliff import command from cliff import lister @@ -62,7 +63,7 @@ def take_action(self, parsed_args): info = {} info.update(volume_type._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class DeleteVolumeType(command.Command): diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index b74fe45249..97f8d31d0a 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -16,6 +16,7 @@ """Volume v1 Volume action implementations""" import logging +import six from cliff import command from cliff import lister @@ -124,7 +125,7 @@ def take_action(self, parsed_args): {'properties': utils.format_dict(volume._info.pop('metadata'))} ) - return zip(*sorted(volume._info.iteritems())) + return zip(*sorted(six.iteritems(volume._info))) class DeleteVolume(command.Command): @@ -322,7 +323,7 @@ def take_action(self, parsed_args): {'properties': utils.format_dict(volume._info.pop('metadata'))} ) - return zip(*sorted(volume._info.iteritems())) + return zip(*sorted(six.iteritems(volume._info))) class UnsetVolume(command.Command): From 978c2e7dec712c1dd355cf9f5eb6a7270fa2c385 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 26 Jul 2013 16:26:50 -0500 Subject: [PATCH 0109/3614] Add server ssh command Change-Id: I9317ad6a47818d5479a046b4be8c5adbbce613ef --- openstackclient/compute/v2/server.py | 159 +++++++++++++++++++++++++++ setup.cfg | 1 + 2 files changed, 160 insertions(+) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 8f81dfdb2f..eb4ccaba23 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -15,6 +15,7 @@ """Compute v2 Server action implementations""" +import argparse import getpass import logging import os @@ -977,6 +978,164 @@ def take_action(self, parsed_args): return zip(*sorted(data.iteritems())) +class SshServer(command.Command): + """Ssh to server""" + + log = logging.getLogger(__name__ + '.SshServer') + + def get_parser(self, prog_name): + parser = super(SshServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Server (name or ID)', + ) + parser.add_argument( + '--login', + metavar='', + help='Login name (ssh -l option)', + ) + parser.add_argument( + '-l', + metavar='', + help=argparse.SUPPRESS, + ) + parser.add_argument( + '--port', + metavar='', + type=int, + help='Destination port (ssh -p option)', + ) + parser.add_argument( + '-p', + metavar='', + dest='port', + type=int, + help=argparse.SUPPRESS, + ) + parser.add_argument( + '--identity', + metavar='', + help='Private key file (ssh -i option)', + ) + parser.add_argument( + '-i', + metavar='', + dest='identity', + help=argparse.SUPPRESS, + ) + parser.add_argument( + '--option', + metavar='', + help='Options in ssh_config(5) format (ssh -o option)', + ) + parser.add_argument( + '-o', + metavar='