#!/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 = [] verbs = [] 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 and subject in self.apicache[verb]: 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 =. 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 or ! 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']) 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()