Merge pull request #27 from TimSimpsonR/better_cli-2
Added a ton of CLI options, plus fixed a CI bug.
This commit is contained in:
@@ -24,6 +24,8 @@ def get_authenticator_cls(cls_or_name):
|
||||
return KeyStoneV2Authenticator
|
||||
elif cls_or_name == "rax":
|
||||
return RaxAuthenticator
|
||||
elif cls_or_name == "auth1.1":
|
||||
return Auth1_1
|
||||
elif cls_or_name == "fake":
|
||||
return FakeAuth
|
||||
|
||||
@@ -40,6 +42,8 @@ class Authenticator(object):
|
||||
|
||||
"""
|
||||
|
||||
URL_REQUIRED=True
|
||||
|
||||
def __init__(self, client, type, url, username, password, tenant,
|
||||
region=None, service_type=None, service_name=None,
|
||||
service_url=None):
|
||||
@@ -54,7 +58,7 @@ class Authenticator(object):
|
||||
self.service_name = service_name
|
||||
self.service_url = service_url
|
||||
|
||||
def _authenticate(self, url, body):
|
||||
def _authenticate(self, url, body, root_key='access'):
|
||||
"""Authenticate and extract the service catalog."""
|
||||
# Make sure we follow redirects when trying to reach Keystone
|
||||
tmp_follow_all_redirects = self.client.follow_all_redirects
|
||||
@@ -70,7 +74,8 @@ class Authenticator(object):
|
||||
return ServiceCatalog(body, region=self.region,
|
||||
service_type=self.service_type,
|
||||
service_name=self.service_name,
|
||||
service_url=self.service_url)
|
||||
service_url=self.service_url,
|
||||
root_key=root_key)
|
||||
except exceptions.AmbiguousEndpoints:
|
||||
print "Found more than one valid endpoint. Use a more "\
|
||||
"restrictive filter"
|
||||
@@ -93,6 +98,8 @@ class Authenticator(object):
|
||||
class KeyStoneV2Authenticator(Authenticator):
|
||||
|
||||
def authenticate(self):
|
||||
if self.url is None:
|
||||
raise exceptions.AuthUrlNotGiven()
|
||||
return self._v2_auth(self.url)
|
||||
|
||||
def _v2_auth(self, url):
|
||||
@@ -110,9 +117,40 @@ class KeyStoneV2Authenticator(Authenticator):
|
||||
return self._authenticate(url, body)
|
||||
|
||||
|
||||
class Auth1_1(Authenticator):
|
||||
|
||||
def authenticate(self):
|
||||
"""Authenticate against a v2.0 auth service."""
|
||||
if self.url is None:
|
||||
raise exceptions.AuthUrlNotGiven()
|
||||
auth_url = self.url
|
||||
body = {"credentials": {"username": self.username,
|
||||
"key": self.password}}
|
||||
return self._authenticate(auth_url, body, root_key='auth')
|
||||
|
||||
try:
|
||||
print(resp_body)
|
||||
self.auth_token = resp_body['auth']['token']['id']
|
||||
except KeyError:
|
||||
raise nova_exceptions.AuthorizationFailure()
|
||||
|
||||
catalog = resp_body['auth']['serviceCatalog']
|
||||
if 'cloudDatabases' not in catalog:
|
||||
raise nova_exceptions.EndpointNotFound()
|
||||
endpoints = catalog['cloudDatabases']
|
||||
for endpoint in endpoints:
|
||||
if self.region_name is None or \
|
||||
endpoint['region'] == self.region_name:
|
||||
self.management_url = endpoint['publicURL']
|
||||
return
|
||||
raise nova_exceptions.EndpointNotFound()
|
||||
|
||||
|
||||
class RaxAuthenticator(Authenticator):
|
||||
|
||||
def authenticate(self):
|
||||
if self.url is None:
|
||||
raise exceptions.AuthUrlNotGiven()
|
||||
return self._rax_auth(self.url)
|
||||
|
||||
def _rax_auth(self, url):
|
||||
@@ -155,7 +193,7 @@ class ServiceCatalog(object):
|
||||
"""
|
||||
|
||||
def __init__(self, resource_dict, region=None, service_type=None,
|
||||
service_name=None, service_url=None):
|
||||
service_name=None, service_url=None, root_key='access'):
|
||||
self.catalog = resource_dict
|
||||
self.region = region
|
||||
self.service_type = service_type
|
||||
@@ -163,6 +201,7 @@ class ServiceCatalog(object):
|
||||
self.service_url = service_url
|
||||
self.management_url = None
|
||||
self.public_url = None
|
||||
self.root_key = root_key
|
||||
self._load()
|
||||
|
||||
def _load(self):
|
||||
@@ -178,7 +217,7 @@ class ServiceCatalog(object):
|
||||
self.management_url = self.service_url
|
||||
|
||||
def get_token(self):
|
||||
return self.catalog['access']['token']['id']
|
||||
return self.catalog[self.root_key]['token']['id']
|
||||
|
||||
def get_management_url(self):
|
||||
return self.management_url
|
||||
@@ -202,11 +241,11 @@ class ServiceCatalog(object):
|
||||
raise exceptions.EndpointNotFound()
|
||||
|
||||
# We don't always get a service catalog back ...
|
||||
if not 'serviceCatalog' in self.catalog['access']:
|
||||
if not 'serviceCatalog' in self.catalog[self.root_key]:
|
||||
raise exceptions.EndpointNotFound()
|
||||
|
||||
# Full catalog ...
|
||||
catalog = self.catalog['access']['serviceCatalog']
|
||||
catalog = self.catalog[self.root_key]['serviceCatalog']
|
||||
|
||||
for service in catalog:
|
||||
if service.get("type") != self.service_type:
|
||||
|
@@ -18,6 +18,7 @@
|
||||
Reddwarf Command line tool
|
||||
"""
|
||||
|
||||
#TODO(tim.simpson): optparse is deprecated. Replace with argparse.
|
||||
import optparse
|
||||
import os
|
||||
import sys
|
||||
@@ -36,7 +37,7 @@ if os.path.exists(os.path.join(possible_topdir, 'reddwarfclient',
|
||||
from reddwarfclient import common
|
||||
|
||||
|
||||
class InstanceCommands(common.CommandsBase):
|
||||
class InstanceCommands(common.AuthedCommandsBase):
|
||||
"""Commands to perform various instances operations and actions"""
|
||||
|
||||
params = [
|
||||
@@ -98,17 +99,17 @@ class InstanceCommands(common.CommandsBase):
|
||||
self._pretty_print(self.dbaas.instances.reset_password, self.id)
|
||||
|
||||
|
||||
class FlavorsCommands(common.CommandsBase):
|
||||
class FlavorsCommands(common.AuthedCommandsBase):
|
||||
"""Commands for listing Flavors"""
|
||||
|
||||
params = []
|
||||
|
||||
def list(self):
|
||||
"""List the available flavors"""
|
||||
self._pretty_print(self.dbaas.flavors.list)
|
||||
self._pretty_list(self.dbaas.flavors.list)
|
||||
|
||||
|
||||
class DatabaseCommands(common.CommandsBase):
|
||||
class DatabaseCommands(common.AuthedCommandsBase):
|
||||
"""Database CRUD operations on an instance"""
|
||||
|
||||
params = [
|
||||
@@ -135,7 +136,7 @@ class DatabaseCommands(common.CommandsBase):
|
||||
self._pretty_paged(self.dbaas.databases.list, self.id)
|
||||
|
||||
|
||||
class UserCommands(common.CommandsBase):
|
||||
class UserCommands(common.AuthedCommandsBase):
|
||||
"""User CRUD operations on an instance"""
|
||||
params = [
|
||||
'id',
|
||||
@@ -164,7 +165,7 @@ class UserCommands(common.CommandsBase):
|
||||
self._pretty_paged(self.dbaas.users.list, self.id)
|
||||
|
||||
|
||||
class RootCommands(common.CommandsBase):
|
||||
class RootCommands(common.AuthedCommandsBase):
|
||||
"""Root user related operations on an instance"""
|
||||
|
||||
params = [
|
||||
@@ -186,7 +187,7 @@ class RootCommands(common.CommandsBase):
|
||||
self._pretty_print(self.dbaas.root.is_root_enabled, self.id)
|
||||
|
||||
|
||||
class VersionCommands(common.CommandsBase):
|
||||
class VersionCommands(common.AuthedCommandsBase):
|
||||
"""List available versions"""
|
||||
|
||||
params = [
|
||||
@@ -199,31 +200,6 @@ class VersionCommands(common.CommandsBase):
|
||||
self._pretty_print(self.dbaas.versions.index, self.url)
|
||||
|
||||
|
||||
def config_options(oparser):
|
||||
oparser.add_option("--auth_url", default="http://localhost:5000/v2.0",
|
||||
help="Auth API endpoint URL with port and version. \
|
||||
Default: http://localhost:5000/v2.0")
|
||||
oparser.add_option("--username", help="Login username")
|
||||
oparser.add_option("--apikey", help="Api key")
|
||||
oparser.add_option("--tenant_id",
|
||||
help="Tenant Id associated with the account")
|
||||
oparser.add_option("--auth_type", default="keystone",
|
||||
help="Auth type to support different auth environments, \
|
||||
Supported values are 'keystone', 'rax'.")
|
||||
oparser.add_option("--service_type", default="reddwarf",
|
||||
help="Service type is a name associated for the catalog")
|
||||
oparser.add_option("--service_name", default="Reddwarf",
|
||||
help="Service name as provided in the service catalog")
|
||||
oparser.add_option("--service_url", default="",
|
||||
help="Service endpoint to use if the catalog doesn't \
|
||||
have one")
|
||||
oparser.add_option("--region", default="RegionOne",
|
||||
help="Region the service is located in")
|
||||
oparser.add_option("-i", "--insecure", action="store_true",
|
||||
dest="insecure", default=False,
|
||||
help="Run in insecure mode for https endpoints.")
|
||||
|
||||
|
||||
COMMANDS = {'auth': common.Auth,
|
||||
'instance': InstanceCommands,
|
||||
'flavor': FlavorsCommands,
|
||||
@@ -235,10 +211,7 @@ COMMANDS = {'auth': common.Auth,
|
||||
|
||||
def main():
|
||||
# Parse arguments
|
||||
oparser = optparse.OptionParser(usage="%prog [options] <cmd> <action> <args>",
|
||||
version='1.0',
|
||||
conflict_handler='resolve')
|
||||
config_options(oparser)
|
||||
oparser = common.CliOptions.create_optparser()
|
||||
for k, v in COMMANDS.items():
|
||||
v._prepare_parser(oparser)
|
||||
(options, args) = oparser.parse_args()
|
||||
@@ -246,11 +219,21 @@ def main():
|
||||
if not args:
|
||||
common.print_commands(COMMANDS)
|
||||
|
||||
if options.verbose:
|
||||
os.environ['RDC_PP'] = "True"
|
||||
os.environ['REDDWARFCLIENT_DEBUG'] = "True"
|
||||
|
||||
# Pop the command and check if it's in the known commands
|
||||
cmd = args.pop(0)
|
||||
if cmd in COMMANDS:
|
||||
fn = COMMANDS.get(cmd)
|
||||
command_object = fn(oparser)
|
||||
command_object = None
|
||||
try:
|
||||
command_object = fn(oparser)
|
||||
except Exception as ex:
|
||||
if options.debug:
|
||||
raise
|
||||
print(ex)
|
||||
|
||||
# Get a list of supported actions for the command
|
||||
actions = common.methods_of(command_object)
|
||||
@@ -261,10 +244,15 @@ def main():
|
||||
# Check for a valid action and perform that action
|
||||
action = args.pop(0)
|
||||
if action in actions:
|
||||
try:
|
||||
if not options.debug:
|
||||
try:
|
||||
getattr(command_object, action)()
|
||||
except Exception as ex:
|
||||
if options.debug:
|
||||
raise
|
||||
print ex
|
||||
else:
|
||||
getattr(command_object, action)()
|
||||
except Exception as ex:
|
||||
print ex
|
||||
else:
|
||||
common.print_actions(cmd, actions)
|
||||
else:
|
||||
|
@@ -18,6 +18,7 @@ import logging
|
||||
import os
|
||||
import time
|
||||
import urlparse
|
||||
import sys
|
||||
|
||||
try:
|
||||
import json
|
||||
@@ -37,12 +38,17 @@ _logger = logging.getLogger(__name__)
|
||||
RDC_PP = os.environ.get("RDC_PP", "False") == "True"
|
||||
|
||||
|
||||
if 'REDDWARFCLIENT_DEBUG' in os.environ and os.environ['REDDWARFCLIENT_DEBUG']:
|
||||
ch = logging.StreamHandler()
|
||||
def log_to_streamhandler(stream=None):
|
||||
stream = stream or sys.stderr
|
||||
ch = logging.StreamHandler(stream)
|
||||
_logger.setLevel(logging.DEBUG)
|
||||
_logger.addHandler(ch)
|
||||
|
||||
|
||||
if 'REDDWARFCLIENT_DEBUG' in os.environ and os.environ['REDDWARFCLIENT_DEBUG']:
|
||||
log_to_streamhandler()
|
||||
|
||||
|
||||
class ReddwarfHTTPClient(httplib2.Http):
|
||||
|
||||
USER_AGENT = 'python-reddwarfclient'
|
||||
@@ -60,7 +66,10 @@ class ReddwarfHTTPClient(httplib2.Http):
|
||||
self.username = user
|
||||
self.password = password
|
||||
self.tenant = tenant
|
||||
self.auth_url = auth_url.rstrip('/')
|
||||
if auth_url:
|
||||
self.auth_url = auth_url.rstrip('/')
|
||||
else:
|
||||
self.auth_url = None
|
||||
self.region_name = region_name
|
||||
self.endpoint_type = endpoint_type
|
||||
self.service_url = service_url
|
||||
@@ -165,7 +174,13 @@ class ReddwarfHTTPClient(httplib2.Http):
|
||||
self.http_log(args, kwargs, resp, body)
|
||||
|
||||
if body:
|
||||
body = self.morph_response_body(body)
|
||||
try:
|
||||
body = self.morph_response_body(body)
|
||||
except exceptions.ResponseFormatError:
|
||||
# Acceptable only if the response status is an error code.
|
||||
# Otherwise its the API or client misbehaving.
|
||||
self.raise_error_from_status(resp, None)
|
||||
raise # Not accepted!
|
||||
else:
|
||||
body = None
|
||||
|
||||
@@ -174,6 +189,10 @@ class ReddwarfHTTPClient(httplib2.Http):
|
||||
|
||||
return resp, body
|
||||
|
||||
def raise_error_from_status(self, resp, body):
|
||||
if resp.status in (400, 401, 403, 404, 408, 409, 413, 500, 501):
|
||||
raise exceptions.from_response(resp, body)
|
||||
|
||||
def morph_request(self, kwargs):
|
||||
kwargs['headers']['Accept'] = 'application/json'
|
||||
kwargs['headers']['Content-Type'] = 'application/json'
|
||||
@@ -181,7 +200,10 @@ class ReddwarfHTTPClient(httplib2.Http):
|
||||
kwargs['body'] = json.dumps(kwargs['body'])
|
||||
|
||||
def morph_response_body(self, body_string):
|
||||
return json.loads(body_string)
|
||||
try:
|
||||
return json.loads(body_string)
|
||||
except ValueError:
|
||||
raise exceptions.ResponseFormatError()
|
||||
|
||||
def _time_request(self, url, method, **kwargs):
|
||||
start_time = time.time()
|
||||
@@ -236,12 +258,23 @@ class ReddwarfHTTPClient(httplib2.Http):
|
||||
|
||||
"""
|
||||
catalog = self.authenticator.authenticate()
|
||||
self.auth_token = catalog.get_token()
|
||||
if not self.service_url:
|
||||
if self.service_url:
|
||||
possible_service_url = None
|
||||
else:
|
||||
if self.endpoint_type == "publicURL":
|
||||
self.service_url = catalog.get_public_url()
|
||||
possible_service_url = catalog.get_public_url()
|
||||
elif self.endpoint_type == "adminURL":
|
||||
self.service_url = catalog.get_management_url()
|
||||
possible_service_url = catalog.get_management_url()
|
||||
self.authenticate_with_token(catalog.get_token(), possible_service_url)
|
||||
|
||||
def authenticate_with_token(self, token, service_url=None):
|
||||
self.auth_token = token
|
||||
if not self.service_url:
|
||||
if not service_url:
|
||||
raise exceptions.ServiceUrlNotGiven()
|
||||
else:
|
||||
self.service_url = service_url
|
||||
|
||||
|
||||
|
||||
class Dbaas(object):
|
||||
|
@@ -12,42 +12,18 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
import json
|
||||
import optparse
|
||||
import os
|
||||
import pickle
|
||||
import sys
|
||||
|
||||
from reddwarfclient.client import Dbaas
|
||||
from reddwarfclient import client
|
||||
from reddwarfclient.xml import ReddwarfXmlClient
|
||||
from reddwarfclient import exceptions
|
||||
|
||||
|
||||
APITOKEN = os.path.expanduser("~/.apitoken")
|
||||
|
||||
|
||||
def get_client():
|
||||
"""Load an existing apitoken if available"""
|
||||
try:
|
||||
with open(APITOKEN, 'rb') as token:
|
||||
apitoken = pickle.load(token)
|
||||
dbaas = Dbaas(apitoken._user, apitoken._apikey,
|
||||
tenant=apitoken._tenant, auth_url=apitoken._auth_url,
|
||||
auth_strategy=apitoken._auth_strategy,
|
||||
service_type=apitoken._service_type,
|
||||
service_name=apitoken._service_name,
|
||||
service_url=apitoken._service_url,
|
||||
insecure=apitoken._insecure,
|
||||
region_name=apitoken._region_name)
|
||||
dbaas.client.auth_token = apitoken._token
|
||||
return dbaas
|
||||
except IOError:
|
||||
print "ERROR: You need to login first and get an auth token\n"
|
||||
sys.exit(1)
|
||||
except:
|
||||
print "ERROR: There was an error using your existing auth token, " \
|
||||
"please login again.\n"
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def methods_of(obj):
|
||||
"""Get all callable methods of an object that don't start with underscore
|
||||
returns a list of tuples of the form (method_name, method)"""
|
||||
@@ -92,24 +68,110 @@ def limit_url(url, limit=None, marker=None):
|
||||
return url + query
|
||||
|
||||
|
||||
class APIToken(object):
|
||||
class CliOptions(object):
|
||||
"""A token object containing the user, apikey and token which
|
||||
is pickleable."""
|
||||
|
||||
def __init__(self, user, apikey, tenant, token, auth_url, auth_strategy,
|
||||
service_type, service_name, service_url, region_name,
|
||||
insecure):
|
||||
self._user = user
|
||||
self._apikey = apikey
|
||||
self._tenant = tenant
|
||||
self._token = token
|
||||
self._auth_url = auth_url
|
||||
self._auth_strategy = auth_strategy
|
||||
self._service_type = service_type
|
||||
self._service_name = service_name
|
||||
self._service_url = service_url
|
||||
self._region_name = region_name
|
||||
self._insecure = insecure
|
||||
APITOKEN = os.path.expanduser("~/.apitoken")
|
||||
|
||||
DEFAULT_VALUES = {
|
||||
'username':None,
|
||||
'apikey':None,
|
||||
'tenant_id':None,
|
||||
'auth_url':None,
|
||||
'auth_type':'keystone',
|
||||
'service_type':'reddwarf',
|
||||
'service_name':'Reddwarf',
|
||||
'region':'RegionOne',
|
||||
'service_url':None,
|
||||
'insecure':False,
|
||||
'verbose':False,
|
||||
'debug':False,
|
||||
'token':None,
|
||||
'xml':None,
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
for key, value in self.DEFAULT_VALUES.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
@classmethod
|
||||
def default(cls):
|
||||
kwargs = copy.deepcopy(cls.DEFAULT_VALUES)
|
||||
return cls(**kwargs)
|
||||
|
||||
@classmethod
|
||||
def load_from_file(cls):
|
||||
try:
|
||||
with open(cls.APITOKEN, 'rb') as token:
|
||||
return pickle.load(token)
|
||||
except IOError:
|
||||
pass # File probably not found.
|
||||
except:
|
||||
print("ERROR: Token file found at %s was corrupt." % cls.APITOKEN)
|
||||
return cls.default()
|
||||
|
||||
|
||||
@classmethod
|
||||
def save_from_instance_fields(cls, instance):
|
||||
apitoken = cls.default()
|
||||
for key, default_value in cls.DEFAULT_VALUES.items():
|
||||
final_value = getattr(instance, key, default_value)
|
||||
setattr(apitoken, key, final_value)
|
||||
with open(cls.APITOKEN, 'wb') as token:
|
||||
pickle.dump(apitoken, token, protocol=2)
|
||||
|
||||
|
||||
@classmethod
|
||||
def create_optparser(cls):
|
||||
oparser = optparse.OptionParser(
|
||||
usage="%prog [options] <cmd> <action> <args>",
|
||||
version='1.0', conflict_handler='resolve')
|
||||
file = cls.load_from_file()
|
||||
def add_option(*args, **kwargs):
|
||||
if len(args) == 1:
|
||||
name = args[0]
|
||||
else:
|
||||
name = args[1]
|
||||
kwargs['default'] = getattr(file, name, cls.DEFAULT_VALUES[name])
|
||||
oparser.add_option("--%s" % name, **kwargs)
|
||||
|
||||
add_option("verbose", action="store_true",
|
||||
help="Show equivalent curl statement along "
|
||||
"with actual HTTP communication.")
|
||||
add_option("debug", action="store_true",
|
||||
help="Show the stack trace on errors.")
|
||||
add_option("auth_url", help="Auth API endpoint URL with port and "
|
||||
"version. Default: http://localhost:5000/v2.0")
|
||||
add_option("username", help="Login username")
|
||||
add_option("apikey", help="Api key")
|
||||
add_option("tenant_id",
|
||||
help="Tenant Id associated with the account")
|
||||
add_option("auth_type",
|
||||
help="Auth type to support different auth environments, \
|
||||
Supported values are 'keystone', 'rax'.")
|
||||
add_option("service_type",
|
||||
help="Service type is a name associated for the catalog")
|
||||
add_option("service_name",
|
||||
help="Service name as provided in the service catalog")
|
||||
add_option("service_url",
|
||||
help="Service endpoint to use if the catalog doesn't have one.")
|
||||
add_option("region", help="Region the service is located in")
|
||||
add_option("insecure", action="store_true",
|
||||
help="Run in insecure mode for https endpoints.")
|
||||
add_option("token", help="Token from a prior login.")
|
||||
add_option("xml", action="store_true", help="Changes format to XML.")
|
||||
|
||||
|
||||
oparser.add_option("--secure", action="store_false", dest="insecure",
|
||||
help="Run in insecure mode for https endpoints.")
|
||||
oparser.add_option("--json", action="store_false", dest="xml",
|
||||
help="Changes format to JSON.")
|
||||
oparser.add_option("--terse", action="store_false", dest="verbose",
|
||||
help="Toggles verbose mode off.")
|
||||
oparser.add_option("--hide-debug", action="store_false", dest="debug",
|
||||
help="Toggles debug mode off.")
|
||||
return oparser
|
||||
|
||||
|
||||
class ArgumentRequired(Exception):
|
||||
@@ -123,15 +185,49 @@ class ArgumentRequired(Exception):
|
||||
class CommandsBase(object):
|
||||
params = []
|
||||
|
||||
def __init__(self, parser):
|
||||
self._parse_options(parser)
|
||||
|
||||
def get_client(self):
|
||||
"""Creates the all important client object."""
|
||||
try:
|
||||
if self.xml:
|
||||
client_cls = ReddwarfXmlClient
|
||||
else:
|
||||
client_cls = client.ReddwarfHTTPClient
|
||||
if self.verbose:
|
||||
client.log_to_streamhandler(sys.stdout)
|
||||
client.RDC_PP = True
|
||||
|
||||
return client.Dbaas(self.username, self.apikey, self.tenant_id,
|
||||
auth_url=self.auth_url,
|
||||
auth_strategy=self.auth_type,
|
||||
service_type=self.service_type,
|
||||
service_name=self.service_name,
|
||||
region_name=self.region,
|
||||
service_url=self.service_url,
|
||||
insecure=self.insecure,
|
||||
client_cls=client_cls)
|
||||
except:
|
||||
if self.debug:
|
||||
raise
|
||||
print sys.exc_info()[1]
|
||||
|
||||
def _safe_exec(self, func, *args, **kwargs):
|
||||
if not self.debug:
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except:
|
||||
print(sys.exc_info()[1])
|
||||
return None
|
||||
else:
|
||||
return func(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def _prepare_parser(cls, parser):
|
||||
for param in cls.params:
|
||||
parser.add_option("--%s" % param)
|
||||
|
||||
def __init__(self, parser):
|
||||
self.dbaas = get_client()
|
||||
self._parse_options(parser)
|
||||
|
||||
def _parse_options(self, parser):
|
||||
opts, args = parser.parse_args()
|
||||
for param in opts.__dict__:
|
||||
@@ -140,9 +236,8 @@ class CommandsBase(object):
|
||||
|
||||
def _require(self, *params):
|
||||
for param in params:
|
||||
if any([param not in self.params,
|
||||
not hasattr(self, param)]):
|
||||
raise ArgumentRequired(param)
|
||||
if not hasattr(self, param):
|
||||
raise ArgumentRequired(param)
|
||||
if not getattr(self, param):
|
||||
raise ArgumentRequired(param)
|
||||
|
||||
@@ -156,11 +251,23 @@ class CommandsBase(object):
|
||||
setattr(self, param, raw)
|
||||
|
||||
def _pretty_print(self, func, *args, **kwargs):
|
||||
try:
|
||||
if self.verbose:
|
||||
self._safe_exec(func, *args, **kwargs)
|
||||
return # Skip this, since the verbose stuff will show up anyway.
|
||||
def wrapped_func():
|
||||
result = func(*args, **kwargs)
|
||||
print json.dumps(result._info, sort_keys=True, indent=4)
|
||||
except:
|
||||
print sys.exc_info()[1]
|
||||
self._safe_exec(wrapped_func)
|
||||
|
||||
def _dumps(self, item):
|
||||
return json.dumps(item, sort_keys=True, indent=4)
|
||||
|
||||
def _pretty_list(self, func, *args, **kwargs):
|
||||
result = self._safe_exec(func, *args, **kwargs)
|
||||
if self.verbose:
|
||||
return
|
||||
for item in result:
|
||||
print self._dumps(item._info)
|
||||
|
||||
def _pretty_paged(self, func, *args, **kwargs):
|
||||
try:
|
||||
@@ -168,12 +275,16 @@ class CommandsBase(object):
|
||||
if limit:
|
||||
limit = int(limit, 10)
|
||||
result = func(*args, limit=limit, marker=self.marker, **kwargs)
|
||||
if self.verbose:
|
||||
return # Verbose already shows the output, so skip this.
|
||||
for item in result:
|
||||
print json.dumps(item._info, sort_keys=True, indent=4)
|
||||
print self._dumps(item._info)
|
||||
if result.links:
|
||||
for link in result.links:
|
||||
print json.dumps(link, sort_keys=True, indent=4)
|
||||
print self._dumps((link))
|
||||
except:
|
||||
if self.debug:
|
||||
raise
|
||||
print sys.exc_info()[1]
|
||||
|
||||
|
||||
@@ -195,37 +306,52 @@ class Auth(CommandsBase):
|
||||
]
|
||||
|
||||
def __init__(self, parser):
|
||||
self.parser = parser
|
||||
super(Auth, self).__init__(parser)
|
||||
self.dbaas = None
|
||||
self._parse_options(parser)
|
||||
|
||||
def login(self):
|
||||
"""Login to retrieve an auth token to use for other api calls"""
|
||||
self._require('username', 'apikey', 'tenant_id')
|
||||
self._require('username', 'apikey', 'tenant_id', 'auth_url')
|
||||
try:
|
||||
self.dbaas = Dbaas(self.username, self.apikey, self.tenant_id,
|
||||
auth_url=self.auth_url,
|
||||
auth_strategy=self.auth_type,
|
||||
service_type=self.service_type,
|
||||
service_name=self.service_name,
|
||||
region_name=self.region,
|
||||
service_url=self.service_url,
|
||||
insecure=self.insecure)
|
||||
self.dbaas = self.get_client()
|
||||
self.dbaas.authenticate()
|
||||
apitoken = APIToken(self.username, self.apikey,
|
||||
self.tenant_id, self.dbaas.client.auth_token,
|
||||
self.auth_url, self.auth_type,
|
||||
self.service_type, self.service_name,
|
||||
self.service_url, self.region,
|
||||
self.insecure)
|
||||
|
||||
with open(APITOKEN, 'wb') as token:
|
||||
pickle.dump(apitoken, token, protocol=2)
|
||||
print apitoken._token
|
||||
self.token = self.dbaas.client.auth_token
|
||||
self.service_url = self.dbaas.client.service_url
|
||||
CliOptions.save_from_instance_fields(self)
|
||||
print(self.token)
|
||||
except:
|
||||
if self.debug:
|
||||
raise
|
||||
print sys.exc_info()[1]
|
||||
|
||||
|
||||
class AuthedCommandsBase(CommandsBase):
|
||||
"""Commands that work only with an authicated client."""
|
||||
|
||||
def __init__(self, parser):
|
||||
"""Makes sure a token is available somehow and logs in."""
|
||||
super(AuthedCommandsBase, self).__init__(parser)
|
||||
try:
|
||||
self._require('token')
|
||||
except ArgumentRequired:
|
||||
if self.debug:
|
||||
raise
|
||||
print('No token argument supplied. Use the "auth login" command '
|
||||
'to log in and get a token.\n')
|
||||
sys.exit(1)
|
||||
try:
|
||||
self._require('service_url')
|
||||
except ArgumentRequired:
|
||||
if self.debug:
|
||||
raise
|
||||
print('No service_url given.\n')
|
||||
sys.exit(1)
|
||||
self.dbaas = self.get_client()
|
||||
# Actually set the token to avoid a re-auth.
|
||||
self.dbaas.client.auth_token = self.token
|
||||
self.dbaas.client.authenticate_with_token(self.token, self.service_url)
|
||||
|
||||
|
||||
class Paginated(object):
|
||||
""" Pretends to be a list if you iterate over it, but also keeps a
|
||||
next property you can use to get the next page of data. """
|
||||
|
@@ -41,6 +41,20 @@ class EndpointNotFound(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AuthUrlNotGiven(EndpointNotFound):
|
||||
"""The auth url was not given."""
|
||||
pass
|
||||
|
||||
|
||||
class ServiceUrlNotGiven(EndpointNotFound):
|
||||
"""The service url was not given."""
|
||||
pass
|
||||
|
||||
|
||||
class ResponseFormatError(Exception):
|
||||
"""Could not parse the response format."""
|
||||
pass
|
||||
|
||||
class AmbiguousEndpoints(Exception):
|
||||
"""Found more than one matching endpoint in Service Catalog."""
|
||||
def __init__(self, endpoints=None):
|
||||
@@ -159,4 +173,5 @@ def from_response(response, body):
|
||||
details = error.get('details', None)
|
||||
return cls(code=response.status, message=message, details=details)
|
||||
else:
|
||||
request_id = response.get('x-compute-request-id')
|
||||
return cls(code=response.status, request_id=request_id)
|
||||
|
@@ -44,143 +44,112 @@ def _pretty_print(info):
|
||||
print json.dumps(info, sort_keys=True, indent=4)
|
||||
|
||||
|
||||
class HostCommands(object):
|
||||
class HostCommands(common.AuthedCommandsBase):
|
||||
"""Commands to list info on hosts"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
params = [
|
||||
'name',
|
||||
]
|
||||
|
||||
def get(self, name):
|
||||
def update_all(self):
|
||||
"""Update all instances on a host"""
|
||||
self._require('name')
|
||||
self.dbaas.hosts.update_all(self.name)
|
||||
|
||||
def get(self):
|
||||
"""List details for the specified host"""
|
||||
dbaas = common.get_client()
|
||||
try:
|
||||
_pretty_print(dbaas.hosts.get(name)._info)
|
||||
except:
|
||||
print sys.exc_info()[1]
|
||||
self._require('name')
|
||||
self._pretty_print(self.dbaas.hosts.get, self.name)
|
||||
|
||||
def list(self):
|
||||
"""List all compute hosts"""
|
||||
dbaas = common.get_client()
|
||||
try:
|
||||
for host in dbaas.hosts.index():
|
||||
_pretty_print(host._info)
|
||||
except:
|
||||
print sys.exc_info()[1]
|
||||
self._pretty_list(self.dbaas.hosts.index)
|
||||
|
||||
|
||||
class RootCommands(object):
|
||||
class RootCommands(common.AuthedCommandsBase):
|
||||
"""List details about the root info for an instance."""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
params = [
|
||||
'id',
|
||||
]
|
||||
|
||||
def history(self, id):
|
||||
def history(self):
|
||||
"""List root history for the instance."""
|
||||
dbaas = common.get_client()
|
||||
try:
|
||||
result = dbaas.management.root_enabled_history(id)
|
||||
_pretty_print(result._info)
|
||||
except:
|
||||
print sys.exc_info()[1]
|
||||
self._require('id')
|
||||
self._pretty_print(self.dbaas.management.root_enabled_history, self.id)
|
||||
|
||||
|
||||
class AccountCommands(object):
|
||||
class AccountCommands(common.AuthedCommandsBase):
|
||||
"""Commands to list account info"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
params = [
|
||||
'id',
|
||||
]
|
||||
|
||||
def list(self):
|
||||
"""List all accounts with non-deleted instances"""
|
||||
dbaas = common.get_client()
|
||||
try:
|
||||
_pretty_print(dbaas.accounts.index()._info)
|
||||
except:
|
||||
print sys.exc_info()[1]
|
||||
self._pretty_print(self.dbaas.accounts.index)
|
||||
|
||||
def get(self, acct):
|
||||
def get(self):
|
||||
"""List details for the account provided"""
|
||||
dbaas = common.get_client()
|
||||
try:
|
||||
_pretty_print(dbaas.accounts.show(acct)._info)
|
||||
except:
|
||||
print sys.exc_info()[1]
|
||||
self._require('id')
|
||||
self._pretty_print(self.dbaas.accounts.show, self.id)
|
||||
|
||||
|
||||
class InstanceCommands(object):
|
||||
class InstanceCommands(common.AuthedCommandsBase):
|
||||
"""List details about an instance."""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
params = [
|
||||
'deleted',
|
||||
'id',
|
||||
'limit',
|
||||
'marker',
|
||||
]
|
||||
|
||||
def get(self, id):
|
||||
def get(self):
|
||||
"""List details for the instance."""
|
||||
dbaas = common.get_client()
|
||||
try:
|
||||
result = dbaas.management.show(id)
|
||||
_pretty_print(result._info)
|
||||
except:
|
||||
print sys.exc_info()[1]
|
||||
self._require('id')
|
||||
self._pretty_print(self.dbaas.management.show, self.id)
|
||||
|
||||
def list(self, deleted=None, limit=None, marker=None):
|
||||
def list(self):
|
||||
"""List all instances for account"""
|
||||
dbaas = common.get_client()
|
||||
if limit:
|
||||
limit = int(limit, 10)
|
||||
try:
|
||||
instances = dbaas.management.index(deleted, limit, marker)
|
||||
for instance in instances:
|
||||
_pretty_print(instance._info)
|
||||
if instances.links:
|
||||
for link in instances.links:
|
||||
_pretty_print(link)
|
||||
except:
|
||||
print sys.exc_info()[1]
|
||||
deleted = None
|
||||
if self.deleted is not None:
|
||||
if self.deleted.lower() in ['true']:
|
||||
deleted = True
|
||||
elif self.deleted.lower() in ['false']:
|
||||
deleted = False
|
||||
self._pretty_paged(self.dbaas.management.index, deleted=deleted)
|
||||
|
||||
def diagnostic(self, id):
|
||||
def diagnostic(self):
|
||||
"""List diagnostic details about an instance."""
|
||||
self._require('id')
|
||||
dbaas = common.get_client()
|
||||
try:
|
||||
result = dbaas.diagnostics.get(id)
|
||||
_pretty_print(result._info)
|
||||
except:
|
||||
print sys.exc_info()[1]
|
||||
self._pretty_print(self.dbaas.diagnostics.get, self.id)
|
||||
|
||||
def stop(self, id):
|
||||
def stop(self):
|
||||
"""Stop MySQL on the given instance."""
|
||||
dbaas = common.get_client()
|
||||
try:
|
||||
result = dbaas.management.stop(id)
|
||||
except:
|
||||
print sys.exc_info()[1]
|
||||
self._require('id')
|
||||
self._pretty_print(self.dbaas.management.stop, self.id)
|
||||
|
||||
def reboot(self, id):
|
||||
"""Reboot the instance."""
|
||||
dbaas = common.get_client()
|
||||
try:
|
||||
result = dbaas.management.reboot(id)
|
||||
except:
|
||||
print sys.exec_info()[1]
|
||||
self._require('id')
|
||||
self._pretty_print(self.dbaas.management.reboot, self.id)
|
||||
|
||||
|
||||
class StorageCommands(object):
|
||||
class StorageCommands(common.AuthedCommandsBase):
|
||||
"""Commands to list devices info"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
params = []
|
||||
|
||||
def list(self):
|
||||
"""List details for the storage device"""
|
||||
dbaas = common.get_client()
|
||||
try:
|
||||
for storage in dbaas.storage.index():
|
||||
_pretty_print(storage._info)
|
||||
except:
|
||||
print sys.exc_info()[1]
|
||||
self._pretty_list(self.dbaas.storage.index)
|
||||
|
||||
|
||||
def config_options():
|
||||
global oparser
|
||||
def config_options(oparser):
|
||||
oparser.add_option("-u", "--url", default="http://localhost:5000/v1.1",
|
||||
help="Auth API endpoint URL with port and version. \
|
||||
Default: http://localhost:5000/v1.1")
|
||||
@@ -196,10 +165,9 @@ COMMANDS = {'account': AccountCommands,
|
||||
|
||||
def main():
|
||||
# Parse arguments
|
||||
global oparser
|
||||
oparser = optparse.OptionParser("%prog [options] <cmd> <action> <args>",
|
||||
version='1.0')
|
||||
config_options()
|
||||
oparser = common.CliOptions.create_optparser()
|
||||
for k, v in COMMANDS.items():
|
||||
v._prepare_parser(oparser)
|
||||
(options, args) = oparser.parse_args()
|
||||
|
||||
if not args:
|
||||
@@ -209,7 +177,13 @@ def main():
|
||||
cmd = args.pop(0)
|
||||
if cmd in COMMANDS:
|
||||
fn = COMMANDS.get(cmd)
|
||||
command_object = fn()
|
||||
command_object = None
|
||||
try:
|
||||
command_object = fn(oparser)
|
||||
except Exception as ex:
|
||||
if options.debug:
|
||||
raise
|
||||
print(ex)
|
||||
|
||||
# Get a list of supported actions for the command
|
||||
actions = common.methods_of(command_object)
|
||||
@@ -220,20 +194,12 @@ def main():
|
||||
# Check for a valid action and perform that action
|
||||
action = args.pop(0)
|
||||
if action in actions:
|
||||
fn = actions.get(action)
|
||||
|
||||
try:
|
||||
fn(*args)
|
||||
sys.exit(0)
|
||||
except TypeError as err:
|
||||
print "Possible wrong number of arguments supplied."
|
||||
print "%s %s: %s" % (cmd, action, fn.__doc__)
|
||||
print "\t\t", [fn.func_code.co_varnames[i] for i in
|
||||
range(fn.func_code.co_argcount)]
|
||||
print "ERROR: %s" % err
|
||||
except Exception:
|
||||
print "Command failed, please check the log for more info."
|
||||
raise
|
||||
getattr(command_object, action)()
|
||||
except Exception as ex:
|
||||
if options.debug:
|
||||
raise
|
||||
print ex
|
||||
else:
|
||||
common.print_actions(cmd, actions)
|
||||
else:
|
||||
|
@@ -2,6 +2,7 @@ from lxml import etree
|
||||
import json
|
||||
from numbers import Number
|
||||
|
||||
from reddwarfclient import exceptions
|
||||
from reddwarfclient.client import ReddwarfHTTPClient
|
||||
|
||||
|
||||
@@ -196,7 +197,10 @@ class ReddwarfXmlClient(ReddwarfHTTPClient):
|
||||
# The root XML element always becomes a dictionary with a single
|
||||
# field, which has the same key as the elements name.
|
||||
result = {}
|
||||
root_element = etree.XML(body_string)
|
||||
try:
|
||||
root_element = etree.XML(body_string)
|
||||
except etree.XMLSyntaxError:
|
||||
raise exceptions.ResponseFormatError()
|
||||
root_name = normalize_tag(root_element)
|
||||
root_value, links = root_element_to_json(root_name, root_element)
|
||||
result = { root_name:root_value }
|
||||
|
Reference in New Issue
Block a user