python-fuelclient/fuelclient/cli/parser.py

247 lines
9.6 KiB
Python

# Copyright 2014 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.
import argparse
import sys
from fuelclient.cli.actions import actions
from fuelclient.cli.arguments import get_version_arg
from fuelclient.cli.arguments import substitutions
from fuelclient.cli.error import exceptions_decorator
from fuelclient.cli.error import ParserException
from fuelclient.cli.serializers import Serializer
from fuelclient import consts
from fuelclient import fuelclient_settings
from fuelclient import profiler
from fuelclient import utils
class Parser(object):
"""Parser class - encapsulates argparse's ArgumentParser
and based on available actions, serializers and additional flags
populates it.
"""
def __init__(self, argv):
self.args = argv
self.parser = argparse.ArgumentParser(
usage="""
fuel [optional args] <namespace> [action] [flags]
DEPRECATION WARNING:
In an upcoming release of Fuel Client, the syntax will
be changed to the following:
fuel [general flags] <entity> <action> [action flags]
where both [general flags] and [action flags] are derivatives
from [optional args] and [flags]; <entity> is a derivative from
<namespace>. Keep in mind that specifying <action> will be
mandatory.
Some of the [optional args] are going to specific to a
particular <entity> and <action> context in the upcoming
release of Fuel Client, so specifying them
before either <namespace> or <action> will not be possible.
Example:
Correct: fuel node list --env 1
Wrong: fuel --env 1 node list
The table below describes the upcoming changes to commands
which will be removed or changed significantly.
+--------------------------------------------------------+
| Old command | New command |
+------------------------+-------------------------------+
| fuel deploy-changes | fuel env deploy |
+------------------------+-------------------------------+
| fuel node --set --env | fuel env add nodes |
+------------------------+-------------------------------+
| fuel network <> --env | fuel env network <> |
+------------------------+-------------------------------+
| fuel settings <> --env | fuel env settings <> |
+------------------------+-------------------------------+
| fuel stop | fuel env stop-deploy |
+------------------------+-------------------------------+
Further information will be located in Fuel Documentation and
on our Wiki page: https://wiki.openstack.org/wiki/Fuel_CLI
You can check out an experimental version of the new
Fuel Client by using the following command:
fuel2 --help
"""
)
self.universal_flags = []
self.credential_flags = []
self.subparsers = self.parser.add_subparsers(
title="Namespaces",
metavar="",
dest="action",
help='actions'
)
self.generate_actions()
self.add_version_args()
self.add_debug_arg()
self.add_serializers_args()
utils.add_os_cli_parameters(self.parser)
def generate_actions(self):
for action, action_object in actions.items():
action_parser = self.subparsers.add_parser(
action,
prog="fuel {0}".format(action),
help=action_object.__doc__,
formatter_class=argparse.RawTextHelpFormatter,
epilog=action_object.examples
)
for argument in action_object.args:
if isinstance(argument, dict):
action_parser.add_argument(
*argument["args"],
**argument["params"]
)
elif isinstance(argument, tuple):
required = argument[0]
group = action_parser.add_mutually_exclusive_group(
required=required)
for argument_in_group in argument[1:]:
group.add_argument(
*argument_in_group["args"],
**argument_in_group["params"]
)
def parse(self):
self.prepare_args()
if len(self.args) < 2:
self.parser.print_help()
sys.exit(0)
parsed_params = self.parser.parse_args(self.args[1:])
settings = fuelclient_settings.get_settings()
settings.update_from_command_line_options(parsed_params)
if parsed_params.action not in actions:
self.parser.print_help()
sys.exit(0)
if profiler.profiling_enabled():
handler_name = parsed_params.action
method_name = ''.join([method for method in parsed_params.__dict__
if getattr(parsed_params, method) is True])
prof = profiler.Profiler(method_name, handler_name)
actions[parsed_params.action].action_func(parsed_params)
if profiler.profiling_enabled():
prof.save_data()
def add_serializers_args(self):
serializers = self.parser.add_mutually_exclusive_group()
for format_name in Serializer.serializers.keys():
serialization_flag = "--{0}".format(format_name)
self.universal_flags.append(serialization_flag)
serializers.add_argument(
serialization_flag,
dest=consts.SERIALIZATION_FORMAT_FLAG,
action="store_const",
const=format_name,
help="prints only {0} to stdout".format(format_name),
default=False
)
def add_debug_arg(self):
self.universal_flags.append("--debug")
self.parser.add_argument(
"--debug",
dest="debug",
action="store_true",
help="prints details of all HTTP request",
default=False
)
def add_version_args(self):
arg = get_version_arg()
self.parser.add_argument(*arg["args"], **arg["params"])
def prepare_args(self):
# replace some args from dict substitutions
self.args = [substitutions.get(arg, arg) for arg in self.args]
# move general used flags before actions, otherwise they will be used
# as a part of action by action_generator
for flag in self.credential_flags:
self.move_argument_before_action(flag)
for flag in self.universal_flags:
self.move_argument_before_action(flag, has_value=False)
self.move_argument_after_action("--env",)
def move_argument_before_action(self, flag, has_value=True):
"""We need to move general argument before action, we use them
not directly in action but in DefaultAPIClient.
"""
for arg in self.args:
if flag in arg:
if "=" in arg or not has_value:
index_of_flag = self.args.index(arg)
flag = self.args.pop(index_of_flag)
self.args.insert(1, flag)
else:
try:
index_of_flag = self.args.index(arg)
flag = self.args.pop(index_of_flag)
value = self.args.pop(index_of_flag)
self.args.insert(1, value)
self.args.insert(1, flag)
except IndexError:
raise ParserException(
'Corresponding value must follow "{0}" flag'
.format(arg)
)
break
def move_argument_after_action(self, flag):
for arg in self.args:
if flag in arg:
# if declaration with '=' sign (e.g. --env-id=1)
if "=" in arg:
index_of_flag = self.args.index(arg)
flag = self.args.pop(index_of_flag)
self.args.append(flag)
else:
try:
index_of_flag = self.args.index(arg)
self.args.pop(index_of_flag)
flag = self.args.pop(index_of_flag)
self.args.append(arg)
self.args.append(flag)
except IndexError:
raise ParserException(
'Corresponding value must follow "{0}" flag'
.format(arg)
)
break
@exceptions_decorator
def main(args=sys.argv):
parser = Parser(args)
parser.parse()