mirror of
				https://github.com/apache/cloudstack.git
				synced 2025-10-26 08:42:29 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			500 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			500 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/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__, __description__, __projecturl__
 | |
|     from config import read_config, write_config, config_file
 | |
|     from optparse import OptionParser
 | |
|     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 = {'count': 0, 'verbs': [], 'asyncapis': []}
 | |
| 
 | |
| 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 = "="
 | |
|     config_options = []
 | |
|     verbs = []
 | |
| 
 | |
|     def __init__(self, pname, cfile):
 | |
|         self.program_name = pname
 | |
|         self.config_file = cfile
 | |
|         self.config_options = read_config(self.get_attr, self.set_attr,
 | |
|                                           self.config_file)
 | |
|         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 = []
 | |
|         if verb in self.apicache and subject in self.apicache[verb]:
 | |
|             missing = filter(lambda x: x not in args_dict.keys(),
 | |
|                              self.apicache[verb][subject]['requiredparams'])
 | |
| 
 | |
|         if len(missing) > 0:
 | |
|             self.monkeyprint("Missing arguments: ", ' '.join(missing))
 | |
|             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.paramcompletion == 'true':
 | |
|                 param = line.split(" ")[-1]
 | |
|                 idx = param.find("=")
 | |
|                 value = param[idx + 1:]
 | |
|                 param = param[:idx]
 | |
|                 if len(value) < 36 and idx != -1:
 | |
|                     params = self.apicache[verb][subject]['params']
 | |
|                     related = filter(lambda x: x['name'] == param,
 | |
|                                      params)[0]['related']
 | |
|                     api = min(filter(lambda x: 'list' in x, related), key=len)
 | |
|                     response = self.make_request(api, args={'listall': 'true'})
 | |
|                     responsekey = filter(lambda x: 'response' in x,
 | |
|                                          response.keys())[0]
 | |
|                     result = response[responsekey]
 | |
|                     uuids = []
 | |
|                     for key in result.keys():
 | |
|                         if isinstance(result[key], list):
 | |
|                             for element in result[key]:
 | |
|                                 if 'id' in element.keys():
 | |
|                                     uuids.append(element['id'])
 | |
|                     autocompletions = uuids
 | |
|                     search_string = value
 | |
| 
 | |
|         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")
 | |
|         if response is None:
 | |
|             monkeyprint("Failed to sync apis, please check your config?")
 | |
|             monkeyprint("Note: `sync` requires api discovery service enabled" +
 | |
|                         " on the CloudStack management server")
 | |
|             return
 | |
|         self.apicache = monkeycache(response)
 | |
|         savecache(self.apicache, self.cache_file)
 | |
|         monkeyprint("%s APIs discovered and cached" % self.apicache["count"])
 | |
|         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, self.config_file)
 | |
| 
 | |
|     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'])
 | |
|                 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)
 | |
| 
 | |
| 
 | |
| class MonkeyParser(OptionParser):
 | |
|     def format_help(self, formatter=None):
 | |
|         if formatter is None:
 | |
|             formatter = self.formatter
 | |
|         result = []
 | |
|         if self.usage:
 | |
|             result.append("Usage: cloudmonkey [options] [cmds] [params]\n\n")
 | |
|         if self.description:
 | |
|             result.append(self.format_description(formatter) + "\n")
 | |
|         result.append(self.format_option_help(formatter))
 | |
|         result.append("\nTry cloudmonkey [help|?]\n")
 | |
|         return "".join(result)
 | |
| 
 | |
| 
 | |
| def main():
 | |
|     parser = MonkeyParser()
 | |
|     parser.add_option("-c", "--config-file",
 | |
|                       dest="cfile", default=config_file,
 | |
|                       help="config file for cloudmonkey", metavar="FILE")
 | |
|     parser.add_option("-v", "--version",
 | |
|                       action="store_true", dest="version", default=False,
 | |
|                       help="prints cloudmonkey version information")
 | |
| 
 | |
|     (options, args) = parser.parse_args()
 | |
|     if options.version:
 | |
|         print "cloudmonkey", __version__
 | |
|         print __description__, "(%s)" % __projecturl__
 | |
|         sys.exit(0)
 | |
| 
 | |
|     shell = CloudMonkeyShell(sys.argv[0], options.cfile)
 | |
|     if len(args) > 0:
 | |
|         shell.onecmd(' '.join(args))
 | |
|     else:
 | |
|         shell.cmdloop()
 | |
| 
 | |
| if __name__ == "__main__":
 | |
|     main()
 |