| #!/usr/bin/python |
| # -*- coding: utf-8 -*- |
| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you 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. |
| |
| try: |
| import atexit |
| import cmd |
| import json |
| import logging |
| import os |
| import pdb |
| import shlex |
| import sys |
| import types |
| |
| from cachemaker import loadcache, savecache, monkeycache, splitverbsubject |
| from config import __version__, cache_file |
| from config import read_config, write_config |
| from prettytable import PrettyTable |
| from printer import monkeyprint |
| from requester import monkeyrequest |
| except ImportError, e: |
| print("Import error in %s : %s" % (__name__, e)) |
| import sys |
| sys.exit() |
| |
| try: |
| from precache import apicache |
| except ImportError: |
| apicache = {} |
| |
| try: |
| import readline |
| except ImportError, e: |
| print("Module readline not found, autocompletions will fail", e) |
| else: |
| import rlcompleter |
| if 'libedit' in readline.__doc__: |
| readline.parse_and_bind("bind ^I rl_complete") |
| else: |
| readline.parse_and_bind("tab: complete") |
| |
| log_fmt = '%(asctime)s - %(filename)s:%(lineno)s - [%(levelname)s] %(message)s' |
| logger = logging.getLogger(__name__) |
| |
| |
| class CloudMonkeyShell(cmd.Cmd, object): |
| intro = ("☁ Apache CloudStack 🐵 cloudmonkey " + __version__ + |
| ". Type help or ? to list commands.\n") |
| ruler = "=" |
| cache_file = cache_file |
| config_options = [] |
| |
| def __init__(self, pname): |
| self.program_name = pname |
| self.config_options = read_config(self.get_attr, self.set_attr) |
| self.loadcache() |
| self.prompt = self.prompt.strip() + " " # Cosmetic fix for prompt |
| |
| logging.basicConfig(filename=self.log_file, |
| level=logging.DEBUG, format=log_fmt) |
| logger.debug("Loaded config fields:\n%s" % map(lambda x: "%s=%s" % |
| (x, getattr(self, x)), |
| self.config_options)) |
| cmd.Cmd.__init__(self) |
| |
| try: |
| if os.path.exists(self.history_file): |
| readline.read_history_file(self.history_file) |
| except IOError, e: |
| logger.debug("Error: Unable to read history. " + str(e)) |
| atexit.register(readline.write_history_file, self.history_file) |
| |
| def get_attr(self, field): |
| return getattr(self, field) |
| |
| def set_attr(self, field, value): |
| return setattr(self, field, value) |
| |
| def emptyline(self): |
| pass |
| |
| def cmdloop(self, intro=None): |
| print(self.intro) |
| while True: |
| try: |
| super(CloudMonkeyShell, self).cmdloop(intro="") |
| self.postloop() |
| except KeyboardInterrupt: |
| print("^C") |
| |
| def loadcache(self): |
| if os.path.exists(self.cache_file): |
| self.apicache = loadcache(self.cache_file) |
| else: |
| self.apicache = apicache |
| if 'verbs' in self.apicache: |
| self.verbs = self.apicache['verbs'] |
| |
| for verb in self.verbs: |
| def add_grammar(verb): |
| def grammar_closure(self, args): |
| if self.pipe_runner("%s %s" % (verb, args)): |
| return |
| if ' --help' in args or ' -h' in args: |
| self.do_help("%s %s" % (verb, args)) |
| return |
| try: |
| args_partition = args.partition(" ") |
| cmd = self.apicache[verb][args_partition[0]]['name'] |
| args = args_partition[2] |
| except KeyError, e: |
| self.monkeyprint("Error: invalid %s api arg" % verb, e) |
| return |
| self.default("%s %s" % (cmd, args)) |
| return grammar_closure |
| |
| grammar_handler = add_grammar(verb) |
| grammar_handler.__doc__ = "%ss resources" % verb.capitalize() |
| grammar_handler.__name__ = 'do_' + str(verb) |
| setattr(self.__class__, grammar_handler.__name__, grammar_handler) |
| |
| def monkeyprint(self, *args): |
| output = "" |
| try: |
| for arg in args: |
| if isinstance(type(arg), types.NoneType): |
| continue |
| output += str(arg) |
| except Exception, e: |
| print(e) |
| |
| if self.color == 'true': |
| monkeyprint(output) |
| else: |
| print(output) |
| |
| def print_result(self, result, result_filter=None): |
| if result is None or len(result) == 0: |
| return |
| |
| def printer_helper(printer, toprow): |
| if printer: |
| self.monkeyprint(printer) |
| return PrettyTable(toprow) |
| |
| def print_result_tabular(result, result_filter=None): |
| toprow = None |
| printer = None |
| for node in result: |
| if toprow != node.keys(): |
| if result_filter is not None and len(result_filter) != 0: |
| commonkeys = filter(lambda x: x in node.keys(), |
| result_filter) |
| if commonkeys != toprow: |
| toprow = commonkeys |
| printer = printer_helper(printer, toprow) |
| else: |
| toprow = node.keys() |
| printer = printer_helper(printer, toprow) |
| row = map(lambda x: node[x], toprow) |
| if printer and row: |
| printer.add_row(row) |
| if printer: |
| self.monkeyprint(printer) |
| |
| def print_result_as_dict(result, result_filter=None): |
| for key in sorted(result.keys(), key=lambda x: |
| x not in ['id', 'count', 'name'] and x): |
| if not (isinstance(result[key], list) or |
| isinstance(result[key], dict)): |
| self.monkeyprint("%s = %s" % (key, result[key])) |
| else: |
| self.monkeyprint(key + ":") |
| self.print_result(result[key], result_filter) |
| |
| def print_result_as_list(result, result_filter=None): |
| for node in result: |
| # Tabular print if it's a list of dict and tabularize is true |
| if isinstance(node, dict) and self.tabularize == 'true': |
| print_result_tabular(result, result_filter) |
| break |
| self.print_result(node) |
| if len(result) > 1: |
| self.monkeyprint(self.ruler * 80) |
| |
| if isinstance(result, dict): |
| print_result_as_dict(result, result_filter) |
| elif isinstance(result, list): |
| print_result_as_list(result, result_filter) |
| elif isinstance(result, str): |
| print result |
| elif not (str(result) is None): |
| self.monkeyprint(result) |
| |
| def make_request(self, command, args={}, isasync=False): |
| response, error = monkeyrequest(command, args, isasync, |
| self.asyncblock, logger, |
| self.host, self.port, |
| self.apikey, self.secretkey, |
| self.timeout, self.protocol, self.path) |
| if error is not None: |
| self.monkeyprint(error) |
| return response |
| |
| def default(self, args): |
| if self.pipe_runner(args): |
| return |
| |
| apiname = args.partition(' ')[0] |
| verb, subject = splitverbsubject(apiname) |
| |
| lexp = shlex.shlex(args.strip()) |
| lexp.whitespace = " " |
| lexp.whitespace_split = True |
| lexp.posix = True |
| args = [] |
| while True: |
| next_val = lexp.next() |
| if next_val is None: |
| break |
| args.append(next_val.replace('\x00', '')) |
| |
| args_dict = dict(map(lambda x: [x.partition("=")[0], |
| x.partition("=")[2]], |
| args[1:])[x] for x in range(len(args) - 1)) |
| field_filter = None |
| if 'filter' in args_dict: |
| field_filter = filter(lambda x: x is not '', |
| map(lambda x: x.strip(), |
| args_dict.pop('filter').split(','))) |
| |
| missing_args = [] |
| if verb in self.apicache: |
| missing_args = filter(lambda x: x not in args_dict.keys(), |
| self.apicache[verb][subject]['requiredparams']) |
| |
| if len(missing_args) > 0: |
| self.monkeyprint("Missing arguments: ", ' '.join(missing_args)) |
| return |
| |
| isasync = False |
| if 'asyncapis' in self.apicache: |
| isasync = apiname in self.apicache['asyncapis'] |
| |
| result = self.make_request(apiname, args_dict, isasync) |
| |
| if result is None: |
| return |
| try: |
| responsekeys = filter(lambda x: 'response' in x, result.keys()) |
| for responsekey in responsekeys: |
| self.print_result(result[responsekey], field_filter) |
| print |
| except Exception as e: |
| self.monkeyprint("🙈 Error on parsing and printing", e) |
| |
| def completedefault(self, text, line, begidx, endidx): |
| partitions = line.partition(" ") |
| verb = partitions[0].strip() |
| rline = partitions[2].lstrip().partition(" ") |
| subject = rline[0] |
| separator = rline[1] |
| params = rline[2].lstrip() |
| |
| if verb not in self.verbs: |
| return [] |
| |
| autocompletions = [] |
| search_string = "" |
| |
| if separator != " ": # Complete verb subjects |
| autocompletions = self.apicache[verb].keys() |
| search_string = subject |
| else: # Complete subject params |
| autocompletions = map(lambda x: x + "=", |
| map(lambda x: x['name'], |
| self.apicache[verb][subject]['params'])) |
| search_string = text |
| |
| if self.tabularize == "true" and subject != "": |
| autocompletions.append("filter=") |
| return [s for s in autocompletions if s.startswith(search_string)] |
| |
| |
| def do_sync(self, args): |
| """ |
| Asks cloudmonkey to discovery and sync apis available on user specified |
| CloudStack host server which has the API discovery plugin, on failure |
| it rollbacks last datastore or api precached datastore. |
| """ |
| response = self.make_request("listApis") |
| self.apicache = monkeycache(response) |
| if response is None: |
| monkeyprint("Failed to sync apis, check your config") |
| return |
| savecache(self.apicache, self.cache_file) |
| self.loadcache() |
| |
| def do_api(self, args): |
| """ |
| Make raw api calls. Syntax: api <apiName> <args>=<values>. |
| |
| Example: |
| api listAccount listall=true |
| """ |
| if len(args) > 0: |
| return self.default(args) |
| else: |
| self.monkeyprint("Please use a valid syntax") |
| |
| def do_set(self, args): |
| """ |
| Set config for cloudmonkey. For example, options can be: |
| host, port, apikey, secretkey, log_file, history_file |
| You may also edit your ~/.cloudmonkey_config instead of using set. |
| |
| Example: |
| set host 192.168.56.2 |
| set prompt 🐵 cloudmonkey> |
| set log_file /var/log/cloudmonkey.log |
| """ |
| args = args.strip().partition(" ") |
| key, value = (args[0], args[2]) |
| setattr(self, key, value) # keys and attributes should have same names |
| self.prompt = self.prompt.strip() + " " # prompt fix |
| write_config(self.get_attr) |
| |
| def complete_set(self, text, line, begidx, endidx): |
| mline = line.partition(" ")[2] |
| offs = len(mline) - len(text) |
| return [s[offs:] for s in self.config_options |
| if s.startswith(mline)] |
| |
| def pipe_runner(self, args): |
| if args.find(' |') > -1: |
| pname = self.program_name |
| if '.py' in pname: |
| pname = "python " + pname |
| self.do_shell("%s %s" % (pname, args)) |
| return True |
| return False |
| |
| def do_shell(self, args): |
| """ |
| Execute shell commands using shell <command> or !<command> |
| |
| Example: |
| !ls |
| shell ls |
| !for((i=0; i<10; i++)); do cloudmonkey create user account=admin \ |
| email=test@test.tt firstname=user$i lastname=user$i \ |
| password=password username=user$i; done |
| """ |
| os.system(args) |
| |
| def do_help(self, args): |
| """ |
| Show help docs for various topics |
| |
| Example: |
| help list |
| help list users |
| ?list |
| ?list users |
| """ |
| fields = args.partition(" ") |
| if fields[2] == "": |
| cmd.Cmd.do_help(self, args) |
| else: |
| verb = fields[0] |
| subject = fields[2].partition(" ")[0] |
| if subject in self.apicache[verb]: |
| api = self.apicache[verb][subject] |
| helpdoc = "(%s) %s" % (api['name'], api['description']) |
| helpdoc = api['description'] |
| if api['isasync']: |
| helpdoc += "\nThis API is asynchronous." |
| required = api['requiredparams'] |
| if len(required) > 0: |
| helpdoc += "\nRequired params are %s" % ' '.join(required) |
| helpdoc += "\nParameters\n" + "=" * 10 |
| for param in api['params']: |
| helpdoc += "\n%s = (%s) %s" % (param['name'], param['type'], param['description']) |
| self.monkeyprint(helpdoc) |
| else: |
| self.monkeyprint("Error: no such api (%s) on %s" % |
| (subject, verb)) |
| |
| def complete_help(self, text, line, begidx, endidx): |
| fields = line.partition(" ") |
| subfields = fields[2].partition(" ") |
| |
| if subfields[1] != " ": |
| return cmd.Cmd.complete_help(self, text, line, begidx, endidx) |
| else: |
| line = fields[2] |
| text = subfields[2] |
| return self.completedefault(text, line, begidx, endidx) |
| |
| def do_EOF(self, args): |
| """ |
| Quit on Ctrl+d or EOF |
| """ |
| sys.exit() |
| |
| def do_exit(self, args): |
| """ |
| Quit CloudMonkey CLI |
| """ |
| return self.do_quit(args) |
| |
| def do_quit(self, args): |
| """ |
| Quit CloudMonkey CLI |
| """ |
| self.monkeyprint("Bye!") |
| return self.do_EOF(args) |
| |
| |
| def main(): |
| shell = CloudMonkeyShell(sys.argv[0]) |
| if len(sys.argv) > 1: |
| shell.onecmd(' '.join(sys.argv[1:])) |
| else: |
| shell.cmdloop() |
| |
| if __name__ == "__main__": |
| main() |