Adding extension framework.
Change-Id: If882f7a822ef6b1e58666b3af6f7166ab0a230fe
This commit is contained in:
parent
a905e5fe07
commit
bb879dd10b
@ -25,7 +25,7 @@ pull requests.
|
||||
.. _Gerrit: http://wiki.openstack.org/GerritWorkflow
|
||||
|
||||
This code a fork of `Jacobian's python-cloudservers`__ If you need API support
|
||||
for the Rackspace API soley or the BSD license, you should use that repository.
|
||||
for the Rackspace API solely or the BSD license, you should use that repository.
|
||||
python-client is licensed under the Apache License like the rest of OpenStack.
|
||||
|
||||
__ http://github.com/jacobian/python-cloudservers
|
||||
|
@ -74,7 +74,10 @@ class Manager(object):
|
||||
# NOTE(ja): keystone returns values as list as {'values': [ ... ]}
|
||||
# unlike other services which just return the list...
|
||||
if type(data) is dict:
|
||||
data = data['values']
|
||||
try:
|
||||
data = data['values']
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
with self.uuid_cache(obj_class, mode="w"):
|
||||
return [obj_class(self, res, loaded=True) for res in data if res]
|
||||
|
@ -20,10 +20,13 @@ Command-line interface to the OpenStack Nova API.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import httplib2
|
||||
import imp
|
||||
import os
|
||||
import sys
|
||||
|
||||
from novaclient import base
|
||||
from novaclient import exceptions as exc
|
||||
from novaclient import utils
|
||||
from novaclient.v1_0 import shell as shell_v1_0
|
||||
@ -96,7 +99,7 @@ class OpenStackComputeShell(object):
|
||||
|
||||
return parser
|
||||
|
||||
def get_subcommand_parser(self, version):
|
||||
def get_subcommand_parser(self, version, extensions):
|
||||
parser = self.get_base_parser()
|
||||
|
||||
self.subcommands = {}
|
||||
@ -115,8 +118,49 @@ class OpenStackComputeShell(object):
|
||||
self._find_actions(subparsers, actions_module)
|
||||
self._find_actions(subparsers, self)
|
||||
|
||||
for _, _, ext_module in extensions:
|
||||
self._find_actions(subparsers, ext_module)
|
||||
|
||||
self._add_bash_completion_subparser(subparsers)
|
||||
|
||||
return parser
|
||||
|
||||
def _discover_extensions(self, version):
|
||||
module_path = os.path.dirname(os.path.abspath(__file__))
|
||||
version_str = "v%s" % version.replace('.', '_')
|
||||
ext_path = os.path.join(module_path, version_str, 'contrib')
|
||||
ext_glob = os.path.join(ext_path, "*.py")
|
||||
|
||||
extensions = []
|
||||
for ext_path in glob.iglob(ext_glob):
|
||||
name = os.path.basename(ext_path)[:-3]
|
||||
ext_module = imp.load_source(name, ext_path)
|
||||
|
||||
# Extract Manager class
|
||||
ext_manager_class = None
|
||||
for attr_value in ext_module.__dict__.values():
|
||||
try:
|
||||
if issubclass(attr_value, base.Manager):
|
||||
ext_manager_class = attr_value
|
||||
break
|
||||
except TypeError:
|
||||
continue # in case attr_value isn't a class...
|
||||
|
||||
if not ext_manager_class:
|
||||
raise Exception("Could not find Manager class in extension.")
|
||||
|
||||
extensions.append((name, ext_manager_class, ext_module))
|
||||
|
||||
return extensions
|
||||
|
||||
def _add_bash_completion_subparser(self, subparsers):
|
||||
subparser = subparsers.add_parser('bash_completion',
|
||||
add_help=False,
|
||||
formatter_class=OpenStackHelpFormatter
|
||||
)
|
||||
self.subcommands['bash_completion'] = subparser
|
||||
subparser.set_defaults(func=self.do_bash_completion)
|
||||
|
||||
def _find_actions(self, subparsers, actions_module):
|
||||
for attr in (a for a in dir(actions_module) if a.startswith('do_')):
|
||||
# I prefer to be hypen-separated instead of underscores.
|
||||
@ -147,7 +191,9 @@ class OpenStackComputeShell(object):
|
||||
(options, args) = parser.parse_known_args(argv)
|
||||
|
||||
# build available subcommands based on version
|
||||
subcommand_parser = self.get_subcommand_parser(options.version)
|
||||
extensions = self._discover_extensions(options.version)
|
||||
subcommand_parser = self.get_subcommand_parser(
|
||||
options.version, extensions)
|
||||
self.parser = subcommand_parser
|
||||
|
||||
# Parse args again and call whatever callback was selected
|
||||
@ -161,6 +207,9 @@ class OpenStackComputeShell(object):
|
||||
if args.func == self.do_help:
|
||||
self.do_help(args)
|
||||
return 0
|
||||
elif args.func == self.do_bash_completion:
|
||||
self.do_bash_completion(args)
|
||||
return 0
|
||||
|
||||
(user, apikey, password, projectid, url, region_name,
|
||||
endpoint_name, insecure) = (args.username, args.apikey,
|
||||
@ -199,7 +248,8 @@ class OpenStackComputeShell(object):
|
||||
self.cs = self.get_api_class(options.version)(user, password,
|
||||
projectid, url, insecure,
|
||||
region_name=region_name,
|
||||
endpoint_name=endpoint_name)
|
||||
endpoint_name=endpoint_name,
|
||||
extensions=extensions)
|
||||
|
||||
try:
|
||||
self.cs.authenticate()
|
||||
@ -221,6 +271,22 @@ class OpenStackComputeShell(object):
|
||||
except KeyError:
|
||||
return shell_v1_0.CLIENT_CLASS
|
||||
|
||||
def do_bash_completion(self, args):
|
||||
"""
|
||||
Prints all of the commands and options to stdout so that the
|
||||
nova.bash_completion script doesn't have to hard code them.
|
||||
"""
|
||||
commands = set()
|
||||
options = set()
|
||||
for sc_str, sc in self.subcommands.items():
|
||||
commands.add(sc_str)
|
||||
for option in sc._optionals._option_string_actions.keys():
|
||||
options.add(option)
|
||||
|
||||
commands.remove('bash-completion')
|
||||
commands.remove('bash_completion')
|
||||
print ' '.join(commands | options)
|
||||
|
||||
@utils.arg('command', metavar='<subcommand>', nargs='?',
|
||||
help='Display help for <subcommand>')
|
||||
def do_help(self, args):
|
||||
|
@ -71,3 +71,15 @@ def find_resource(manager, name_or_id):
|
||||
msg = "No %s with a name or ID of '%s' exists." % \
|
||||
(manager.resource_class.__name__.lower(), name_or_id)
|
||||
raise exceptions.CommandError(msg)
|
||||
|
||||
|
||||
def _format_servers_list_networks(server):
|
||||
output = []
|
||||
for (network, addresses) in server.networks.items():
|
||||
if len(addresses) == 0:
|
||||
continue
|
||||
addresses_csv = ', '.join(addresses)
|
||||
group = "%s=%s" % (network, addresses_csv)
|
||||
output.append(group)
|
||||
|
||||
return '; '.join(output)
|
||||
|
@ -27,7 +27,7 @@ class Client(object):
|
||||
|
||||
def __init__(self, username, api_key, project_id, auth_url=None,
|
||||
insecure=False, timeout=None, token=None, region_name=None,
|
||||
endpoint_name='publicURL'):
|
||||
endpoint_name='publicURL', extensions=None):
|
||||
|
||||
# FIXME(comstud): Rename the api_key argument above when we
|
||||
# know it's not being used as keyword argument
|
||||
@ -40,6 +40,11 @@ class Client(object):
|
||||
self.servers = servers.ServerManager(self)
|
||||
self.zones = zones.ZoneManager(self)
|
||||
|
||||
# Add in any extensions...
|
||||
if extensions:
|
||||
for (ext_name, ext_manager_class, ext_module) in extensions:
|
||||
setattr(self, ext_name, ext_manager_class(self))
|
||||
|
||||
_auth_url = auth_url or 'https://auth.api.rackspacecloud.com/v1.0'
|
||||
|
||||
self.client = client.HTTPClient(username,
|
||||
|
@ -33,7 +33,7 @@ class Client(object):
|
||||
# FIXME(jesse): project_id isn't required to authenticate
|
||||
def __init__(self, username, api_key, project_id, auth_url,
|
||||
insecure=False, timeout=None, token=None, region_name=None,
|
||||
endpoint_name='publicURL'):
|
||||
endpoint_name='publicURL', extensions=None):
|
||||
# FIXME(comstud): Rename the api_key argument above when we
|
||||
# know it's not being used as keyword argument
|
||||
password = api_key
|
||||
@ -53,6 +53,11 @@ class Client(object):
|
||||
self.security_group_rules = \
|
||||
security_group_rules.SecurityGroupRuleManager(self)
|
||||
|
||||
# Add in any extensions...
|
||||
if extensions:
|
||||
for (ext_name, ext_manager_class, ext_module) in extensions:
|
||||
setattr(self, ext_name, ext_manager_class(self))
|
||||
|
||||
self.client = client.HTTPClient(username,
|
||||
password,
|
||||
project_id,
|
||||
|
@ -481,23 +481,11 @@ def do_list(cs, args):
|
||||
id_col = 'ID'
|
||||
|
||||
columns = [id_col, 'Name', 'Status', 'Networks']
|
||||
formatters = {'Networks': _format_servers_list_networks}
|
||||
formatters = {'Networks': utils._format_servers_list_networks}
|
||||
utils.print_list(cs.servers.list(search_opts=search_opts), columns,
|
||||
formatters)
|
||||
|
||||
|
||||
def _format_servers_list_networks(server):
|
||||
output = []
|
||||
for (network, addresses) in server.networks.items():
|
||||
if len(addresses) == 0:
|
||||
continue
|
||||
addresses_csv = ', '.join(addresses)
|
||||
group = "%s=%s" % (network, addresses_csv)
|
||||
output.append(group)
|
||||
|
||||
return '; '.join(output)
|
||||
|
||||
|
||||
@utils.arg('--hard',
|
||||
dest='reboot_type',
|
||||
action='store_const',
|
||||
|
@ -92,9 +92,8 @@ 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.
|
||||
${wrapper} pep8 --repeat --show-pep8 --show-source \
|
||||
--ignore=E202,W602 \
|
||||
${srcfiles}
|
||||
pep8_opts="--ignore=E202,W602 --repeat"
|
||||
${wrapper} pep8 ${pep8_opts} ${srcfiles}
|
||||
}
|
||||
|
||||
NOSETESTS="nosetests $noseopts $noseargs"
|
||||
|
@ -1,3 +1,17 @@
|
||||
# Copyright 2011 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 httplib2
|
||||
|
||||
from novaclient import client as base_client
|
||||
|
@ -4,15 +4,8 @@ _nova()
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
opts="add-fixed-ip backup backup-schedule backup-schedule-delete boot
|
||||
boot-for-account delete delete diagnostics flavor-list image-create
|
||||
image-delete image-list ip-share ip-unshare ipgroup-create
|
||||
ipgroup-delete ipgroup-list ipgroup-show list migrate pause reboot
|
||||
rebuild remove-fixed-ip rename rescue resize resize-confirm
|
||||
resize-revert resume root-password show suspend unpause unrescue
|
||||
zone zone-add zone-boot zone-delete zone-info zone-list help
|
||||
--debug --endpoint_name --password --projectid --region_name --url
|
||||
--username --version"
|
||||
|
||||
opts="$(nova bash_completion)"
|
||||
|
||||
UUID_CACHE=~/.novaclient_cached_*_uuids
|
||||
opts+=" "$(cat $UUID_CACHE 2> /dev/null | tr '\n' ' ')
|
||||
|
Loading…
Reference in New Issue
Block a user