python-glareclient/glareclient/shell.py

341 lines
12 KiB
Python

# Copyright 2017 - Nokia Networks
#
# 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 Glare APIs
"""
import argparse
import logging
import sys
from cliff import app
from cliff import commandmanager
from osc_lib.command import command
from glareclient import client
from glareclient.common import utils
import glareclient.osc.v1.artifacts
import glareclient.osc.v1.blobs
class OpenStackHelpFormatter(argparse.HelpFormatter):
def __init__(self, prog, indent_increment=2, max_help_position=32,
width=None):
super(OpenStackHelpFormatter, self).__init__(
prog,
indent_increment,
max_help_position,
width
)
def start_section(self, heading):
# Title-case the headings.
heading = '%s%s' % (heading[0].upper(), heading[1:])
super(OpenStackHelpFormatter, self).start_section(heading)
class HelpAction(argparse.Action):
"""Custom help 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 v1 :\n')
for name, ep in sorted(app.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 BashCompletionCommand(command.Command):
"""Prints all of the commands and options for bash-completion."""
def take_action(self, parsed_args):
commands = set()
options = set()
for option, _action in self.app.parser._option_string_actions.items():
options.add(option)
for command_name, _cmd in self.app.command_manager:
commands.add(command_name)
print(' '.join(commands | options))
class GlareShell(app.App):
def __init__(self):
super(GlareShell, self).__init__(
description=__doc__.strip(),
version=glareclient.__version__,
command_manager=commandmanager.CommandManager('glare.cli'),
)
self._set_shell_commands(self._get_commands())
def configure_logging(self):
log_lvl = logging.DEBUG if self.options.debug else logging.WARNING
logging.basicConfig(
format="%(levelname)s (%(module)s) %(message)s",
level=log_lvl
)
logging.getLogger('iso8601').setLevel(logging.WARNING)
if self.options.verbose_level <= 1:
logging.getLogger('requests').setLevel(logging.WARNING)
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.
:param description: full description of the application
:paramtype description: str
:param version: version number for the application
:paramtype version: str
:param argparse_kwargs: extra keyword argument passed to the
ArgumentParser constructor
:paramtype extra_kwargs: dict
"""
argparse_kwargs = argparse_kwargs or {}
parser = argparse.ArgumentParser(
description=description,
add_help=False,
formatter_class=OpenStackHelpFormatter,
**argparse_kwargs
)
parser.add_argument(
'--version',
action='version',
version='%(prog)s {0}'.format(version),
help='Show program\'s version number and exit.'
)
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(
'--log-file',
action='store',
default=None,
help='Specify a file to log output. Disabled by default.',
)
parser.add_argument(
'-q', '--quiet',
action='store_const',
dest='verbose_level',
const=0,
help='Suppress output except warnings and errors.',
)
parser.add_argument(
'-h', '--help',
action=HelpAction,
nargs=0,
default=self, # tricky
help="Show this help message and exit.",
)
parser.add_argument(
'--debug',
default=False,
action='store_true',
help='Show tracebacks on errors.',
)
parser.add_argument(
'--os-glare-url',
action='store',
dest='glare_url',
default=utils.env('OS_GLARE_URL'),
help='Glare API host (Env: OS_GLARE_URL)'
)
parser.add_argument(
'--os-glare-version',
action='store',
dest='glare_version',
default=utils.env('OS_GLARE_VERSION', default='v1'),
help='Glare API version (default = v1) (Env: '
'OS_GLARE_VERSION)'
)
parser.add_argument(
'--keycloak-auth-url',
action='store',
dest='keycloak_auth_url',
default=utils.env('KEYCLOAK_AUTH_URL'),
help='Keycloak auth url (Env: KEYCLOAK_AUTH_URL)')
parser.add_argument(
'--openid-client-id',
action='store',
dest='openid_client_id',
default=utils.env('OPENID_CLIENT_ID') or 'admin-cli',
help='Client ID (according to OpenID Connect)'
' (Env: OPENID_CLIENT_ID)')
parser.add_argument(
'--auth-token',
action='store',
dest='auth_token',
default=utils.env('AUTH_TOKEN'),
help='Authentication token (Env: AUTH_TOKEN)')
parser.add_argument(
'--keycloak-realm-name',
action='store',
dest='keycloak_realm_name',
default=utils.env('KEYCLOAK_REALM_NAME'),
help='With keycloak glare auth type: Realm name to scope to'
' (Env: KEYCLOAK_REALM_NAME)')
parser.add_argument(
'--keycloak-username',
action='store',
dest='keycloak_username',
default=utils.env('KEYCLOAK_USERNAME'),
help='Keycloak username (Env: KEYCLOAK_USERNAME)')
parser.add_argument(
'--keycloak-password',
action='store',
dest='keycloak_password',
default=utils.env('KEYCLOAK_PASSWORD'),
help='Keycloak user password (Env: KEYCLOAK_PASSWORD)')
parser.add_argument(
'--cert',
action='store',
dest='cert_file',
default=utils.env('OS_GLARE_CERT'),
help='Client Certificate (Env: OS_GLARE_CERT)'
)
parser.add_argument(
'--key',
action='store',
dest='key_file',
default=utils.env('OS_GLARE_KEY'),
help='Client Key (Env: OS_GLARE_KEY)'
)
parser.add_argument(
'--cacert',
action='store',
dest='cacert',
default=utils.env('OS_GLARE_CACERT'),
help='Authentication CA Certificate (Env: OS_GLARE_CACERT)'
)
parser.add_argument(
'--insecure',
action='store_true',
dest='insecure',
default=utils.env('GLARECLIENT_INSECURE', default=False),
help='Disables SSL/TLS certificate verification '
'(Env: GLARELCLIENT_INSECURE)'
)
return parser
def initialize_app(self, argv):
self._clear_shell_commands()
self._set_shell_commands(self._get_commands())
# bash-completion and help messages should not require client creation
need_client = not (
('bash-completion' in argv) or
('help' in argv) or
('-h' in argv) or
('--help' in argv) or
not argv)
self.client = self._create_client() if need_client else None
# Adding client_manager variable to make glare client work with
# unified OpenStack client.
ClientManager = type(
'ClientManager',
(object,),
dict(artifact=self.client)
)
self.client_manager = ClientManager()
def _create_client(self):
return client.Client(
endpoint=self.options.glare_url,
auth_token=self.options.auth_token,
keycloak_auth_url=self.options.keycloak_auth_url,
openid_client_id=self.options.openid_client_id,
keycloak_realm_name=self.options.keycloak_realm_name,
keycloak_username=self.options.keycloak_username,
keycloak_password=self.options.keycloak_password,
cert_file=self.options.cert_file,
key_file=self.options.key_file,
cacert=self.options.cacert,
insecure=self.options.insecure
)
def _set_shell_commands(self, cmds_dict):
for k, v in cmds_dict.items():
self.command_manager.add_command(k, v)
def _clear_shell_commands(self):
exclude_cmds = ['help', 'complete']
cmds = self.command_manager.commands.copy()
for k, v in cmds.items():
if k not in exclude_cmds:
self.command_manager.commands.pop(k)
@staticmethod
def _get_commands():
return {
'bash-completion': BashCompletionCommand,
'list': glareclient.osc.v1.artifacts.ListArtifacts,
'show': glareclient.osc.v1.artifacts.ShowArtifact,
'create': glareclient.osc.v1.artifacts.CreateArtifact,
'delete': glareclient.osc.v1.artifacts.DeleteArtifact,
'update': glareclient.osc.v1.artifacts.UpdateArtifact,
'activate': glareclient.osc.v1.artifacts.ActivateArtifact,
'deactivate': glareclient.osc.v1.artifacts.DeactivateArtifact,
'reactivate': glareclient.osc.v1.artifacts.ReactivateArtifact,
'publish': glareclient.osc.v1.artifacts.PublishArtifact,
'add-tag': glareclient.osc.v1.artifacts.AddTag,
'remove-tag': glareclient.osc.v1.artifacts.RemoveTag,
'type-list': glareclient.osc.v1.artifacts.TypeList,
'schema': glareclient.osc.v1.artifacts.TypeSchema,
'upload': glareclient.osc.v1.blobs.UploadBlob,
'download': glareclient.osc.v1.blobs.DownloadBlob,
'location': glareclient.osc.v1.blobs.AddLocation,
'remove-location': glareclient.osc.v1.blobs.RemoveLocation
}
def main(argv=sys.argv[1:]):
return GlareShell().run(argv)
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))