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
|
.. _Gerrit: http://wiki.openstack.org/GerritWorkflow
|
||||||
|
|
||||||
This code a fork of `Jacobian's python-cloudservers`__ If you need API support
|
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.
|
python-client is licensed under the Apache License like the rest of OpenStack.
|
||||||
|
|
||||||
__ http://github.com/jacobian/python-cloudservers
|
__ http://github.com/jacobian/python-cloudservers
|
||||||
|
@ -74,7 +74,10 @@ class Manager(object):
|
|||||||
# NOTE(ja): keystone returns values as list as {'values': [ ... ]}
|
# NOTE(ja): keystone returns values as list as {'values': [ ... ]}
|
||||||
# unlike other services which just return the list...
|
# unlike other services which just return the list...
|
||||||
if type(data) is dict:
|
if type(data) is dict:
|
||||||
data = data['values']
|
try:
|
||||||
|
data = data['values']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
with self.uuid_cache(obj_class, mode="w"):
|
with self.uuid_cache(obj_class, mode="w"):
|
||||||
return [obj_class(self, res, loaded=True) for res in data if res]
|
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 argparse
|
||||||
|
import glob
|
||||||
import httplib2
|
import httplib2
|
||||||
|
import imp
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from novaclient import base
|
||||||
from novaclient import exceptions as exc
|
from novaclient import exceptions as exc
|
||||||
from novaclient import utils
|
from novaclient import utils
|
||||||
from novaclient.v1_0 import shell as shell_v1_0
|
from novaclient.v1_0 import shell as shell_v1_0
|
||||||
@ -96,7 +99,7 @@ class OpenStackComputeShell(object):
|
|||||||
|
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def get_subcommand_parser(self, version):
|
def get_subcommand_parser(self, version, extensions):
|
||||||
parser = self.get_base_parser()
|
parser = self.get_base_parser()
|
||||||
|
|
||||||
self.subcommands = {}
|
self.subcommands = {}
|
||||||
@ -115,8 +118,49 @@ class OpenStackComputeShell(object):
|
|||||||
self._find_actions(subparsers, actions_module)
|
self._find_actions(subparsers, actions_module)
|
||||||
self._find_actions(subparsers, self)
|
self._find_actions(subparsers, self)
|
||||||
|
|
||||||
|
for _, _, ext_module in extensions:
|
||||||
|
self._find_actions(subparsers, ext_module)
|
||||||
|
|
||||||
|
self._add_bash_completion_subparser(subparsers)
|
||||||
|
|
||||||
return parser
|
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):
|
def _find_actions(self, subparsers, actions_module):
|
||||||
for attr in (a for a in dir(actions_module) if a.startswith('do_')):
|
for attr in (a for a in dir(actions_module) if a.startswith('do_')):
|
||||||
# I prefer to be hypen-separated instead of underscores.
|
# I prefer to be hypen-separated instead of underscores.
|
||||||
@ -147,7 +191,9 @@ class OpenStackComputeShell(object):
|
|||||||
(options, args) = parser.parse_known_args(argv)
|
(options, args) = parser.parse_known_args(argv)
|
||||||
|
|
||||||
# build available subcommands based on version
|
# 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
|
self.parser = subcommand_parser
|
||||||
|
|
||||||
# Parse args again and call whatever callback was selected
|
# Parse args again and call whatever callback was selected
|
||||||
@ -161,6 +207,9 @@ class OpenStackComputeShell(object):
|
|||||||
if args.func == self.do_help:
|
if args.func == self.do_help:
|
||||||
self.do_help(args)
|
self.do_help(args)
|
||||||
return 0
|
return 0
|
||||||
|
elif args.func == self.do_bash_completion:
|
||||||
|
self.do_bash_completion(args)
|
||||||
|
return 0
|
||||||
|
|
||||||
(user, apikey, password, projectid, url, region_name,
|
(user, apikey, password, projectid, url, region_name,
|
||||||
endpoint_name, insecure) = (args.username, args.apikey,
|
endpoint_name, insecure) = (args.username, args.apikey,
|
||||||
@ -199,7 +248,8 @@ class OpenStackComputeShell(object):
|
|||||||
self.cs = self.get_api_class(options.version)(user, password,
|
self.cs = self.get_api_class(options.version)(user, password,
|
||||||
projectid, url, insecure,
|
projectid, url, insecure,
|
||||||
region_name=region_name,
|
region_name=region_name,
|
||||||
endpoint_name=endpoint_name)
|
endpoint_name=endpoint_name,
|
||||||
|
extensions=extensions)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.cs.authenticate()
|
self.cs.authenticate()
|
||||||
@ -221,6 +271,22 @@ class OpenStackComputeShell(object):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
return shell_v1_0.CLIENT_CLASS
|
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='?',
|
@utils.arg('command', metavar='<subcommand>', nargs='?',
|
||||||
help='Display help for <subcommand>')
|
help='Display help for <subcommand>')
|
||||||
def do_help(self, args):
|
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." % \
|
msg = "No %s with a name or ID of '%s' exists." % \
|
||||||
(manager.resource_class.__name__.lower(), name_or_id)
|
(manager.resource_class.__name__.lower(), name_or_id)
|
||||||
raise exceptions.CommandError(msg)
|
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,
|
def __init__(self, username, api_key, project_id, auth_url=None,
|
||||||
insecure=False, timeout=None, token=None, region_name=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
|
# FIXME(comstud): Rename the api_key argument above when we
|
||||||
# know it's not being used as keyword argument
|
# know it's not being used as keyword argument
|
||||||
@ -40,6 +40,11 @@ class Client(object):
|
|||||||
self.servers = servers.ServerManager(self)
|
self.servers = servers.ServerManager(self)
|
||||||
self.zones = zones.ZoneManager(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'
|
_auth_url = auth_url or 'https://auth.api.rackspacecloud.com/v1.0'
|
||||||
|
|
||||||
self.client = client.HTTPClient(username,
|
self.client = client.HTTPClient(username,
|
||||||
|
@ -33,7 +33,7 @@ class Client(object):
|
|||||||
# FIXME(jesse): project_id isn't required to authenticate
|
# FIXME(jesse): project_id isn't required to authenticate
|
||||||
def __init__(self, username, api_key, project_id, auth_url,
|
def __init__(self, username, api_key, project_id, auth_url,
|
||||||
insecure=False, timeout=None, token=None, region_name=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
|
# FIXME(comstud): Rename the api_key argument above when we
|
||||||
# know it's not being used as keyword argument
|
# know it's not being used as keyword argument
|
||||||
password = api_key
|
password = api_key
|
||||||
@ -53,6 +53,11 @@ class Client(object):
|
|||||||
self.security_group_rules = \
|
self.security_group_rules = \
|
||||||
security_group_rules.SecurityGroupRuleManager(self)
|
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,
|
self.client = client.HTTPClient(username,
|
||||||
password,
|
password,
|
||||||
project_id,
|
project_id,
|
||||||
|
@ -481,23 +481,11 @@ def do_list(cs, args):
|
|||||||
id_col = 'ID'
|
id_col = 'ID'
|
||||||
|
|
||||||
columns = [id_col, 'Name', 'Status', 'Networks']
|
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,
|
utils.print_list(cs.servers.list(search_opts=search_opts), columns,
|
||||||
formatters)
|
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',
|
@utils.arg('--hard',
|
||||||
dest='reboot_type',
|
dest='reboot_type',
|
||||||
action='store_const',
|
action='store_const',
|
||||||
|
@ -92,9 +92,8 @@ function run_pep8 {
|
|||||||
# other than what the PEP8 tool claims. It is deprecated in Python 3, so,
|
# 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
|
# perhaps the mistake was thinking that the deprecation applied to Python 2
|
||||||
# as well.
|
# as well.
|
||||||
${wrapper} pep8 --repeat --show-pep8 --show-source \
|
pep8_opts="--ignore=E202,W602 --repeat"
|
||||||
--ignore=E202,W602 \
|
${wrapper} pep8 ${pep8_opts} ${srcfiles}
|
||||||
${srcfiles}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NOSETESTS="nosetests $noseopts $noseargs"
|
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
|
import httplib2
|
||||||
|
|
||||||
from novaclient import client as base_client
|
from novaclient import client as base_client
|
||||||
|
@ -4,15 +4,8 @@ _nova()
|
|||||||
COMPREPLY=()
|
COMPREPLY=()
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
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
|
opts="$(nova bash_completion)"
|
||||||
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"
|
|
||||||
|
|
||||||
UUID_CACHE=~/.novaclient_cached_*_uuids
|
UUID_CACHE=~/.novaclient_cached_*_uuids
|
||||||
opts+=" "$(cat $UUID_CACHE 2> /dev/null | tr '\n' ' ')
|
opts+=" "$(cat $UUID_CACHE 2> /dev/null | tr '\n' ' ')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user