1# Licensed under the Apache License, Version 2.0 (the "License"); you may 2# not use this file except in compliance with the License. You may obtain 3# a copy of the License at 4# 5# http://www.apache.org/licenses/LICENSE-2.0 6# 7# Unless required by applicable law or agreed to in writing, software 8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10# License for the specific language governing permissions and limitations 11# under the License. 12 13""" 14Command-line interface to the OpenStack Telemetry API. 15""" 16 17from __future__ import print_function 18 19import argparse 20import logging 21import sys 22import warnings 23 24from oslo_utils import encodeutils 25from oslo_utils import importutils 26import six 27 28import ceilometerclient 29from ceilometerclient import client as ceiloclient 30from ceilometerclient.common import utils 31from ceilometerclient import exc 32 33 34def _positive_non_zero_int(argument_value): 35 if argument_value is None: 36 return None 37 try: 38 value = int(argument_value) 39 except ValueError: 40 msg = "%s must be an integer" % argument_value 41 raise argparse.ArgumentTypeError(msg) 42 if value <= 0: 43 msg = "%s must be greater than 0" % argument_value 44 raise argparse.ArgumentTypeError(msg) 45 return value 46 47 48class CeilometerShell(object): 49 50 def __init__(self): 51 self.auth_plugin = ceiloclient.AuthPlugin() 52 53 def get_base_parser(self): 54 parser = argparse.ArgumentParser( 55 prog='ceilometer', 56 description=__doc__.strip(), 57 epilog='See "ceilometer help COMMAND" ' 58 'for help on a specific command.', 59 add_help=False, 60 formatter_class=HelpFormatter, 61 ) 62 63 # Global arguments 64 parser.add_argument('-h', '--help', 65 action='store_true', 66 help=argparse.SUPPRESS, 67 ) 68 69 parser.add_argument('--version', 70 action='version', 71 version=ceilometerclient.__version__) 72 73 parser.add_argument('-d', '--debug', 74 default=bool(utils.env('CEILOMETERCLIENT_DEBUG') 75 ), 76 action='store_true', 77 help='Defaults to env[CEILOMETERCLIENT_DEBUG].') 78 79 parser.add_argument('-v', '--verbose', 80 default=False, action="store_true", 81 help="Print more verbose output.") 82 83 parser.add_argument('--timeout', 84 default=600, 85 type=_positive_non_zero_int, 86 help='Number of seconds to wait for a response.') 87 88 parser.add_argument('--ceilometer-url', metavar='<CEILOMETER_URL>', 89 dest='os_endpoint', 90 default=utils.env('CEILOMETER_URL'), 91 help=("DEPRECATED, use --os-endpoint instead. " 92 "Defaults to env[CEILOMETER_URL].")) 93 94 parser.add_argument('--ceilometer_url', 95 dest='os_endpoint', 96 help=argparse.SUPPRESS) 97 98 parser.add_argument('--ceilometer-api-version', 99 default=utils.env( 100 'CEILOMETER_API_VERSION', default='2'), 101 help='Defaults to env[CEILOMETER_API_VERSION] ' 102 'or 2.') 103 104 parser.add_argument('--ceilometer_api_version', 105 help=argparse.SUPPRESS) 106 107 self.auth_plugin.add_opts(parser) 108 self.auth_plugin.add_common_opts(parser) 109 110 return parser 111 112 def get_subcommand_parser(self, version): 113 parser = self.get_base_parser() 114 115 self.subcommands = {} 116 subparsers = parser.add_subparsers(metavar='<subcommand>') 117 submodule = importutils.import_versioned_module('ceilometerclient', 118 version, 'shell') 119 self._find_actions(subparsers, submodule) 120 self._find_actions(subparsers, self) 121 122 return parser 123 124 def _find_actions(self, subparsers, actions_module): 125 for attr in (a for a in dir(actions_module) if a.startswith('do_')): 126 # I prefer to be hypen-separated instead of underscores. 127 command = attr[3:].replace('_', '-') 128 callback = getattr(actions_module, attr) 129 desc = callback.__doc__ or '' 130 help = desc.strip().split('\n')[0] 131 arguments = getattr(callback, 'arguments', []) 132 133 subparser = subparsers.add_parser(command, help=help, 134 description=desc, 135 add_help=False, 136 formatter_class=HelpFormatter) 137 subparser.add_argument('-h', '--help', action='help', 138 help=argparse.SUPPRESS) 139 self.subcommands[command] = subparser 140 for (args, kwargs) in arguments: 141 subparser.add_argument(*args, **kwargs) 142 subparser.set_defaults(func=callback) 143 144 @staticmethod 145 def _setup_logging(debug): 146 format = '%(levelname)s (%(module)s) %(message)s' 147 if debug: 148 logging.basicConfig(format=format, level=logging.DEBUG) 149 else: 150 logging.basicConfig(format=format, level=logging.WARN) 151 logging.getLogger('iso8601').setLevel(logging.WARNING) 152 logging.getLogger('urllib3.connectionpool').setLevel(logging.WARNING) 153 154 def parse_args(self, argv): 155 # Parse args once to find version 156 parser = self.get_base_parser() 157 (options, args) = parser.parse_known_args(argv) 158 self.auth_plugin.parse_opts(options) 159 self._setup_logging(options.debug) 160 161 # build available subcommands based on version 162 api_version = options.ceilometer_api_version 163 subcommand_parser = self.get_subcommand_parser(api_version) 164 self.parser = subcommand_parser 165 166 # Handle top-level --help/-h before attempting to parse 167 # a command off the command line 168 if options.help or not argv: 169 self.do_help(options) 170 return 0 171 172 # Return parsed args 173 return api_version, subcommand_parser.parse_args(argv) 174 175 def main(self, argv): 176 warnings.warn( 177 "ceilometerclient is now deprecated as the Ceilometer API has " 178 "been deprecated. Please use either aodhclient, pankoclient or " 179 "gnocchiclient.") 180 parsed = self.parse_args(argv) 181 if parsed == 0: 182 return 0 183 api_version, args = parsed 184 185 # Short-circuit and deal with help command right away. 186 if args.func == self.do_help: 187 self.do_help(args) 188 return 0 189 elif args.func == self.do_bash_completion: 190 self.do_bash_completion(args) 191 return 0 192 193 if not ((self.auth_plugin.opts.get('token') 194 or self.auth_plugin.opts.get('auth_token')) 195 and self.auth_plugin.opts['endpoint']): 196 if not self.auth_plugin.opts['username']: 197 raise exc.CommandError("You must provide a username via " 198 "either --os-username or via " 199 "env[OS_USERNAME]") 200 201 if not self.auth_plugin.opts['password']: 202 raise exc.CommandError("You must provide a password via " 203 "either --os-password or via " 204 "env[OS_PASSWORD]") 205 206 if not (args.os_project_id or args.os_project_name 207 or args.os_tenant_id or args.os_tenant_name): 208 # steer users towards Keystone V3 API 209 raise exc.CommandError("You must provide a project_id " 210 "(or name) via either --os-project-id " 211 "or via env[OS_PROJECT_ID]") 212 213 if not self.auth_plugin.opts['auth_url']: 214 raise exc.CommandError("You must provide an auth url via " 215 "either --os-auth-url or via " 216 "env[OS_AUTH_URL]") 217 218 client_kwargs = vars(args) 219 client_kwargs.update(self.auth_plugin.opts) 220 client_kwargs['auth_plugin'] = self.auth_plugin 221 client = ceiloclient.get_client(api_version, **client_kwargs) 222 # call whatever callback was selected 223 try: 224 args.func(client, args) 225 except exc.HTTPUnauthorized: 226 raise exc.CommandError("Invalid OpenStack Identity credentials.") 227 228 def do_bash_completion(self, args): 229 """Prints all of the commands and options to stdout. 230 231 The ceilometer.bash_completion script doesn't have to hard code them. 232 """ 233 commands = set() 234 options = set() 235 for sc_str, sc in self.subcommands.items(): 236 commands.add(sc_str) 237 for option in list(sc._optionals._option_string_actions): 238 options.add(option) 239 240 commands.remove('bash-completion') 241 print(' '.join(commands | options)) 242 243 @utils.arg('command', metavar='<subcommand>', nargs='?', 244 help='Display help for <subcommand>') 245 def do_help(self, args): 246 """Display help about this program or one of its subcommands.""" 247 if getattr(args, 'command', None): 248 if args.command in self.subcommands: 249 self.subcommands[args.command].print_help() 250 else: 251 raise exc.CommandError("'%s' is not a valid subcommand" % 252 args.command) 253 else: 254 self.parser.print_help() 255 256 257class HelpFormatter(argparse.HelpFormatter): 258 def __init__(self, prog, indent_increment=2, max_help_position=32, 259 width=None): 260 super(HelpFormatter, self).__init__(prog, indent_increment, 261 max_help_position, width) 262 263 def start_section(self, heading): 264 # Title-case the headings 265 heading = '%s%s' % (heading[0].upper(), heading[1:]) 266 super(HelpFormatter, self).start_section(heading) 267 268 269def main(args=None): 270 try: 271 if args is None: 272 args = sys.argv[1:] 273 274 CeilometerShell().main(args) 275 276 except Exception as e: 277 if '--debug' in args or '-d' in args: 278 raise 279 else: 280 print(encodeutils.safe_encode(six.text_type(e)), file=sys.stderr) 281 sys.exit(1) 282 except KeyboardInterrupt: 283 print("Stopping Ceilometer Client", file=sys.stderr) 284 sys.exit(130) 285 286if __name__ == "__main__": 287 main() 288