python-blazarclient/blazarclient/shell.py
Pierre Riteau ea96686ffb Add support for floating IP reservation
Change-Id: I7a73a4e7d2026eba2d0a3d6dfe9db42879ff6416
2019-05-30 11:29:04 +01:00

530 lines
19 KiB
Python

# Copyright (c) 2013 Mirantis 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.
"""
Command-line interface to the Blazar APIs
"""
from __future__ import print_function
import argparse
import logging
import os
import sys
from cliff import app
from cliff import commandmanager
from keystoneauth1 import identity
from keystoneauth1 import session
from oslo_utils import encodeutils
import six
from blazarclient import client as blazar_client
from blazarclient import exception
from blazarclient import utils
from blazarclient.v1.shell_commands import floatingips
from blazarclient.v1.shell_commands import hosts
from blazarclient.v1.shell_commands import leases
from blazarclient import version as base_version
COMMANDS_V1 = {
'lease-list': leases.ListLeases,
'lease-show': leases.ShowLease,
'lease-create': leases.CreateLease,
'lease-update': leases.UpdateLease,
'lease-delete': leases.DeleteLease,
'host-list': hosts.ListHosts,
'host-show': hosts.ShowHost,
'host-create': hosts.CreateHost,
'host-update': hosts.UpdateHost,
'host-delete': hosts.DeleteHost,
'floatingip-list': floatingips.ListFloatingIPs,
'floatingip-show': floatingips.ShowFloatingIP,
'floatingip-create': floatingips.CreateFloatingIP,
'floatingip-delete': floatingips.DeleteFloatingIP,
}
VERSION = 1
DEFAULT_API_VERSION = 1
COMMANDS = {'v1': COMMANDS_V1}
def run_command(cmd, cmd_parser, sub_argv):
_argv = sub_argv
index = -1
values_specs = []
if '--' in sub_argv:
index = sub_argv.index('--')
_argv = sub_argv[:index]
values_specs = sub_argv[index:]
known_args, _values_specs = cmd_parser.parse_known_args(_argv)
cmd.values_specs = (index == -1 and _values_specs or values_specs)
return cmd.run(known_args)
def env(*_vars, **kwargs):
"""Search for the first defined of possibly many env vars.
Returns the first environment variable defined in vars, or
returns the default defined in kwargs.
"""
for v in _vars:
value = os.environ.get(v, None)
if value:
return value
return kwargs.get('default', '')
class HelpAction(argparse.Action):
"""Provide a custom action so the -h and --help options
to the main app will print a list of the commands.
The commands are determined by checking the CommandManager
instance, passed in as the "default" value for the action.
"""
def __call__(self, parser, namespace, values, option_string=None):
outputs = []
max_len = 0
app = self.default
parser.print_help(app.stdout)
app.stdout.write('\nCommands for API %s:\n' % app.api_version)
command_manager = app.command_manager
for name, ep in sorted(command_manager):
factory = ep.load()
cmd = factory(self, None)
one_liner = cmd.get_description().split('\n')[0]
outputs.append((name, one_liner))
max_len = max(len(name), max_len)
for (name, one_liner) in outputs:
app.stdout.write(' %s %s\n' % (name.ljust(max_len), one_liner))
sys.exit(0)
class BlazarShell(app.App):
"""Manager class for the Blazar CLI."""
CONSOLE_MESSAGE_FORMAT = '%(message)s'
DEBUG_MESSAGE_FORMAT = '%(levelname)s: %(name)s %(message)s'
log = logging.getLogger(__name__)
def __init__(self):
super(BlazarShell, self).__init__(
description=__doc__.strip(),
version=VERSION,
command_manager=commandmanager.CommandManager('blazar.cli'), )
self.commands = COMMANDS
def build_option_parser(self, description, version, argparse_kwargs=None):
"""Return an argparse option parser for this application.
Subclasses may override this method to extend
the parser with more global options.
"""
parser = argparse.ArgumentParser(
description=description,
add_help=False)
parser.add_argument(
'--version',
action='version',
version=base_version.__version__)
parser.add_argument(
'-v', '--verbose',
action='count',
dest='verbose_level',
default=self.DEFAULT_VERBOSE_LEVEL,
help='Increase verbosity of output. Can be repeated.')
parser.add_argument(
'-q', '--quiet',
action='store_const',
dest='verbose_level',
const=0,
help='suppress output except warnings and errors')
help_action = parser.add_argument(
'-h', '--help',
action=HelpAction,
nargs=0,
default=self,
help="show this help message and exit")
parser.add_argument(
'--debug',
default=False,
action='store_true',
help='Print debugging output')
# Removes help action to defer its execution
self.deferred_help_action = help_action
parser._actions.remove(help_action)
del parser._option_string_actions['-h']
del parser._option_string_actions['--help']
parser.add_argument(
'-h', '--help',
action='store_true',
dest='deferred_help',
default=False,
help="Show this help message and exit",
)
# Global arguments
parser.add_argument(
'--os-reservation-api-version',
default=env('OS_RESERVATION_API_VERSION',
default=DEFAULT_API_VERSION),
help='Accepts 1 now, defaults to 1.')
parser.add_argument(
'--os_reservation_api_version',
help=argparse.SUPPRESS)
parser.add_argument(
'--os-auth-strategy', metavar='<auth-strategy>',
default=env('OS_AUTH_STRATEGY', default='keystone'),
help='Authentication strategy (Env: OS_AUTH_STRATEGY'
', default keystone). For now, any other value will'
' disable the authentication')
parser.add_argument(
'--os_auth_strategy',
help=argparse.SUPPRESS)
parser.add_argument(
'--os-auth-url', metavar='<auth-url>',
default=env('OS_AUTH_URL'),
help='Authentication URL (Env: OS_AUTH_URL)')
parser.add_argument(
'--os_auth_url',
help=argparse.SUPPRESS)
parser.add_argument(
'--os-project-name', metavar='<auth-project-name>',
default=env('OS_PROJECT_NAME'),
help='Authentication project name (Env: OS_PROJECT_NAME)')
parser.add_argument(
'--os_project_name',
help=argparse.SUPPRESS)
parser.add_argument(
'--os-project-id', metavar='<auth-project-id>',
default=env('OS_PROJECT_ID'),
help='Authentication project ID (Env: OS_PROJECT_ID)')
parser.add_argument(
'--os_project_id',
help=argparse.SUPPRESS)
parser.add_argument(
'--os-project-domain-name', metavar='<auth-project-domain-name>',
default=env('OS_PROJECT_DOMAIN_NAME'),
help='Authentication project domain name '
'(Env: OS_PROJECT_DOMAIN_NAME)')
parser.add_argument(
'--os_project_domain_name',
help=argparse.SUPPRESS)
parser.add_argument(
'--os-project-domain-id', metavar='<auth-project-domain-id>',
default=env('OS_PROJECT_DOMAIN_ID'),
help='Authentication project domain ID '
'(Env: OS_PROJECT_DOMAIN_ID)')
parser.add_argument(
'--os_project_domain_id',
help=argparse.SUPPRESS)
parser.add_argument(
'--os-tenant-name', metavar='<auth-tenant-name>',
default=env('OS_TENANT_NAME'),
help='Authentication tenant name (Env: OS_TENANT_NAME)')
parser.add_argument(
'--os_tenant_name',
help=argparse.SUPPRESS)
parser.add_argument(
'--os-tenant-id', metavar='<auth-tenant-id>',
default=env('OS_TENANT_ID'),
help='Authentication tenant name (Env: OS_TENANT_ID)')
parser.add_argument(
'--os-username', metavar='<auth-username>',
default=utils.env('OS_USERNAME'),
help='Authentication username (Env: OS_USERNAME)')
parser.add_argument(
'--os_username',
help=argparse.SUPPRESS)
parser.add_argument(
'--os-user-domain-name', metavar='<auth-user-domain-name>',
default=env('OS_USER_DOMAIN_NAME'),
help='Authentication user domain name (Env: OS_USER_DOMAIN_NAME)')
parser.add_argument(
'--os_user_domain_name',
help=argparse.SUPPRESS)
parser.add_argument(
'--os-user-domain-id', metavar='<auth-user-domain-id>',
default=env('OS_USER_DOMAIN_ID'),
help='Authentication user domain ID (Env: OS_USER_DOMAIN_ID)')
parser.add_argument(
'--os_user_domain_id',
help=argparse.SUPPRESS)
parser.add_argument(
'--os-password', metavar='<auth-password>',
default=utils.env('OS_PASSWORD'),
help='Authentication password (Env: OS_PASSWORD)')
parser.add_argument(
'--os_password',
help=argparse.SUPPRESS)
parser.add_argument(
'--os-region-name', metavar='<auth-region-name>',
default=env('OS_REGION_NAME'),
help='Authentication region name (Env: OS_REGION_NAME)')
parser.add_argument(
'--os_region_name',
help=argparse.SUPPRESS)
parser.add_argument(
'--os-token', metavar='<token>',
default=env('OS_TOKEN'),
help='Defaults to env[OS_TOKEN]')
parser.add_argument(
'--os_token',
help=argparse.SUPPRESS)
parser.add_argument(
'--service-type', metavar='<service-type>',
default=env('BLAZAR_SERVICE_TYPE', default='reservation'),
help='Defaults to env[BLAZAR_SERVICE_TYPE] or reservation.')
parser.add_argument(
'--endpoint-type', metavar='<endpoint-type>',
default=env('OS_ENDPOINT_TYPE', default='publicURL'),
help='Defaults to env[OS_ENDPOINT_TYPE] or publicURL.')
parser.add_argument(
'--os-cacert',
metavar='<ca-certificate>',
default=env('OS_CACERT', default=None),
help="Specify a CA bundle file to use in "
"verifying a TLS (https) server certificate. "
"Defaults to env[OS_CACERT]")
parser.add_argument(
'--insecure',
action='store_true',
default=env('BLAZARCLIENT_INSECURE', default=False),
help="Explicitly allow blazarclient to perform \"insecure\" "
"SSL (https) requests. The server's certificate will "
"not be verified against any certificate authorities. "
"This option should be used with caution.")
return parser
def _bash_completion(self):
"""Prints all of the commands and options for bash-completion."""
commands = set()
options = set()
for option, _action in self.parser._option_string_actions.items():
options.add(option)
for command_name, command in self.command_manager:
commands.add(command_name)
cmd_factory = command.load()
cmd = cmd_factory(self, None)
cmd_parser = cmd.get_parser('')
for option, _action in cmd_parser._option_string_actions.items():
options.add(option)
print(' '.join(commands | options))
def run(self, argv):
"""Equivalent to the main program for the application.
:param argv: input arguments and options
:paramtype argv: list of str
"""
try:
self.options, remainder = self.parser.parse_known_args(argv)
self.api_version = 'v%s' % self.options.os_reservation_api_version
for k, v in self.commands[self.api_version].items():
self.command_manager.add_command(k, v)
index = 0
command_pos = -1
help_pos = -1
help_command_pos = -1
for arg in argv:
if arg == 'bash-completion':
self._bash_completion()
return 0
if arg in self.commands[self.api_version]:
if command_pos == -1:
command_pos = index
elif arg in ('-h', '--help'):
if help_pos == -1:
help_pos = index
elif arg == 'help':
if help_command_pos == -1:
help_command_pos = index
index += 1
if -1 < command_pos < help_pos:
argv = ['help', argv[command_pos]]
if help_command_pos > -1 and command_pos == -1:
argv[help_command_pos] = '--help'
if self.options.deferred_help:
self.deferred_help_action(self.parser, self.parser, None, None)
self.configure_logging()
self.interactive_mode = not remainder
self.initialize_app(remainder)
except Exception as err:
if self.options.debug:
self.log.exception(six.text_type(err))
raise
else:
self.log.error(six.text_type(err))
return 1
if self.interactive_mode:
_argv = [sys.argv[0]]
sys.argv = _argv
result = self.interact()
else:
result = self.run_subcommand(remainder)
return result
def run_subcommand(self, argv):
subcommand = self.command_manager.find_command(argv)
cmd_factory, cmd_name, sub_argv = subcommand
cmd = cmd_factory(self, self.options)
result = 1
try:
self.prepare_to_run_command(cmd)
full_name = (cmd_name if self.interactive_mode else
' '.join([self.NAME, cmd_name]))
cmd_parser = cmd.get_parser(full_name)
return run_command(cmd, cmd_parser, sub_argv)
except Exception as err:
if self.options.debug:
self.log.exception(six.text_type(err))
else:
self.log.error(six.text_type(err))
try:
self.clean_up(cmd, result, err)
except Exception as err2:
if self.options.debug:
self.log.exception(six.text_type(err2))
else:
self.log.error('Could not clean up: %s',
six.text_type(err2))
if self.options.debug:
raise
else:
try:
self.clean_up(cmd, result, None)
except Exception as err3:
if self.options.debug:
self.log.exception(six.text_type(err3))
else:
self.log.error('Could not clean up: %s',
six.text_type(err3))
return result
def authenticate_user(self):
"""Authenticate user and set client by using passed params."""
if self.options.os_token:
auth = identity.Token(
auth_url=self.options.os_auth_url,
token=self.options.os_token,
tenant_id=self.options.os_tenant_id,
tenant_name=self.options.os_tenant_name,
project_id=self.options.os_project_id,
project_name=self.options.os_project_name,
project_domain_id=self.options.os_project_domain_id,
project_domain_name=self.options.os_project_domain_name
)
else:
auth = identity.Password(
auth_url=self.options.os_auth_url,
username=self.options.os_username,
tenant_id=self.options.os_tenant_id,
tenant_name=self.options.os_tenant_name,
password=self.options.os_password,
project_id=self.options.os_project_id,
project_name=self.options.os_project_name,
project_domain_id=self.options.os_project_domain_id,
project_domain_name=self.options.os_project_domain_name,
user_domain_id=self.options.os_user_domain_id,
user_domain_name=self.options.os_user_domain_name
)
sess = session.Session(
auth=auth,
verify=(self.options.os_cacert or not self.options.insecure)
)
self.client = blazar_client.Client(
self.options.os_reservation_api_version,
session=sess,
service_type=self.options.service_type,
interface=self.options.endpoint_type,
region_name=self.options.os_region_name,
)
return
def initialize_app(self, argv):
"""Global app init bits:
* set up API versions
* validate authentication info
"""
super(BlazarShell, self).initialize_app(argv)
cmd_name = None
if argv:
cmd_info = self.command_manager.find_command(argv)
cmd_factory, cmd_name, sub_argv = cmd_info
if self.interactive_mode or cmd_name != 'help':
self.authenticate_user()
def clean_up(self, cmd, result, err):
self.log.debug('clean_up %s', cmd.__class__.__name__)
if err:
self.log.debug('got an error: %s', six.text_type(err))
def configure_logging(self):
"""Create logging handlers for any log output."""
root_logger = logging.getLogger('')
# Set up logging to a file
root_logger.setLevel(logging.DEBUG)
# Send higher-level messages to the console via stderr
console = logging.StreamHandler(self.stderr)
if self.options.debug:
console_level = logging.DEBUG
else:
console_level = {0: logging.WARNING,
1: logging.INFO,
2: logging.DEBUG}.get(self.options.verbose_level,
logging.DEBUG)
console.setLevel(console_level)
if logging.DEBUG == console_level:
formatter = logging.Formatter(self.DEBUG_MESSAGE_FORMAT)
else:
formatter = logging.Formatter(self.CONSOLE_MESSAGE_FORMAT)
console.setFormatter(formatter)
root_logger.addHandler(console)
return
def main(argv=sys.argv[1:]):
try:
return BlazarShell().run(list(map(encodeutils.safe_decode, argv)))
except exception.BlazarClientException:
return 1
except Exception as e:
print(six.text_type(e))
return 1
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))