mirror of
				https://github.com/apache/cloudstack.git
				synced 2025-10-26 08:42:29 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			513 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			513 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 clint
 | |
|     import codecs
 | |
|     import json
 | |
|     import logging
 | |
|     import os
 | |
|     import pdb
 | |
|     import sets
 | |
|     import shlex
 | |
|     import sys
 | |
|     import time
 | |
|     import types
 | |
| 
 | |
|     from clint.textui import colored
 | |
|     from ConfigParser import ConfigParser, SafeConfigParser
 | |
|     from urllib2 import HTTPError, URLError
 | |
|     from httplib import BadStatusLine
 | |
| 
 | |
|     from common import __version__, config_file, config_fields
 | |
|     from common import grammar, precached_verbs
 | |
|     from marvin.cloudstackConnection import cloudConnection
 | |
|     from marvin.cloudstackException import cloudstackAPIException
 | |
|     from marvin.cloudstackAPI import *
 | |
|     from marvin import cloudstackAPI
 | |
| except ImportError, e:
 | |
|     print "Import error in %s : %s" % (__name__, e)
 | |
|     import sys
 | |
|     sys.exit()
 | |
| 
 | |
| # Fix autocompletion issue, can be put in .pythonstartup
 | |
| 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__)
 | |
| completions = cloudstackAPI.__all__
 | |
| 
 | |
| 
 | |
| class CloudStackShell(cmd.Cmd):
 | |
|     intro = ("☁ Apache CloudStack 🐵 cloudmonkey " + __version__ +
 | |
|              ". Type help or ? to list commands.\n")
 | |
|     ruler = "="
 | |
|     config_file = config_file
 | |
|     config_fields = config_fields
 | |
|     grammar = grammar
 | |
|     # datastructure {'verb': {cmd': ['api', [params], doc, required=[]]}}
 | |
|     cache_verbs = precached_verbs
 | |
| 
 | |
|     def __init__(self):
 | |
|         if os.path.exists(self.config_file):
 | |
|             config = self.read_config()
 | |
|         else:
 | |
|             for key in self.config_fields.keys():
 | |
|                 setattr(self, key, self.config_fields[key])
 | |
|             config = self.write_config()
 | |
|             print "Welcome! Using `set` configure the necessary settings:"
 | |
|             print " ".join(sorted(self.config_fields.keys()))
 | |
|             print "For debugging, tail -f", self.log_file, "\n"
 | |
| 
 | |
|         for key in self.config_fields.keys():
 | |
|             try:
 | |
|                 setattr(self, key, config.get('CLI', key))
 | |
|                 self.config_fields[key] = config.get('CLI', key)
 | |
|             except Exception:
 | |
|                 print "Please fix `%s` config in %s" % (key, self.config_file)
 | |
|                 sys.exit()
 | |
| 
 | |
|         self.prompt += " "  # Cosmetic fix for prompt
 | |
|         logging.basicConfig(filename=self.log_file,
 | |
|                             level=logging.DEBUG, format=log_fmt)
 | |
|         logger.debug("Loaded config fields:\n%s" % self.config_fields)
 | |
| 
 | |
|         cmd.Cmd.__init__(self)
 | |
|         # Update config if config_file does not exist
 | |
|         if not os.path.exists(self.config_file):
 | |
|             config = self.write_config()
 | |
| 
 | |
|         # Enable history support
 | |
|         try:
 | |
|             if os.path.exists(self.history_file):
 | |
|                 readline.read_history_file(self.history_file)
 | |
|             atexit.register(readline.write_history_file, self.history_file)
 | |
|         except IOError:
 | |
|             print("Error: history support")
 | |
| 
 | |
|     def read_config(self):
 | |
|         config = ConfigParser()
 | |
|         try:
 | |
|             with open(self.config_file, 'r') as cfg:
 | |
|                 config.readfp(cfg)
 | |
|         except IOError, e:
 | |
|             self.print_shell("Error: config_file not found", e)
 | |
|         return config
 | |
| 
 | |
|     def write_config(self):
 | |
|         config = ConfigParser()
 | |
|         config.add_section('CLI')
 | |
|         for key in self.config_fields.keys():
 | |
|             config.set('CLI', key, getattr(self, key))
 | |
|         with open(self.config_file, 'w') as cfg:
 | |
|             config.write(cfg)
 | |
|         return config
 | |
| 
 | |
|     def emptyline(self):
 | |
|         pass
 | |
| 
 | |
|     def print_shell(self, *args):
 | |
|         try:
 | |
|             for arg in args:
 | |
|                 arg = str(arg)
 | |
|                 if isinstance(type(args), types.NoneType):
 | |
|                     continue
 | |
|                 if self.color == 'true':
 | |
|                     if str(arg).count(self.ruler) == len(str(arg)):
 | |
|                         print colored.green(arg),
 | |
|                     elif 'Error' in arg:
 | |
|                         print colored.red(arg),
 | |
|                     elif ":\n=" in arg:
 | |
|                         print colored.red(arg),
 | |
|                     elif ':' in arg:
 | |
|                         print colored.blue(arg),
 | |
|                     elif 'type' in arg:
 | |
|                         print colored.green(arg),
 | |
|                     elif 'state' in arg or 'count' in arg:
 | |
|                         print colored.yellow(arg),
 | |
|                     elif 'id =' in arg:
 | |
|                         print colored.cyan(arg),
 | |
|                     elif 'name =' in arg:
 | |
|                         print colored.magenta(arg),
 | |
|                     else:
 | |
|                         print arg,
 | |
|                 else:
 | |
|                     print arg,
 | |
|             print
 | |
|         except Exception, e:
 | |
|             print colored.red("Error: "), e
 | |
| 
 | |
|     def print_result(self, result):
 | |
|         if result is None or len(result) == 0:
 | |
|             return
 | |
| 
 | |
|         def print_result_as_dict(result):
 | |
|             for key in result.keys():
 | |
|                 if not (isinstance(result[key], list) or
 | |
|                         isinstance(result[key], dict)):
 | |
|                     self.print_shell("%s = %s" % (key, result[key]))
 | |
|                 else:
 | |
|                     self.print_shell(key + ":\n" + len(key) * "=")
 | |
|                     self.print_result(result[key])
 | |
| 
 | |
|         def print_result_as_list(result):
 | |
|             for node in result:
 | |
|                 self.print_result(node)
 | |
|                 if len(result) > 1:
 | |
|                     self.print_shell(self.ruler * 80)
 | |
| 
 | |
|         if isinstance(result, dict):
 | |
|             print_result_as_dict(result)
 | |
|         elif isinstance(result, list):
 | |
|             print_result_as_list(result)
 | |
|         elif isinstance(result, str):
 | |
|             print result
 | |
|         elif not (str(result) is None):
 | |
|             self.print_shell(result)
 | |
| 
 | |
|     def make_request(self, command, requests={}, isAsync=False):
 | |
|         conn = cloudConnection(self.host, port=int(self.port),
 | |
|                                apiKey=self.apikey, securityKey=self.secretkey,
 | |
|                                asyncTimeout=self.timeout, logging=logger,
 | |
|                                protocol=self.protocol, path=self.path)
 | |
|         response = None
 | |
|         logger.debug("====START Request====")
 | |
|         logger.debug("Requesting command=%s, args=%s" % (command, requests))
 | |
|         try:
 | |
|             response = conn.make_request_with_auth(command, requests)
 | |
|         except cloudstackAPIException, e:
 | |
|             self.print_shell("API Error:", e)
 | |
|         except HTTPError, e:
 | |
|             self.print_shell(e)
 | |
|         except (URLError, BadStatusLine), e:
 | |
|             self.print_shell("Connection Error:", e)
 | |
|         logger.debug("====END Request====\n")
 | |
| 
 | |
|         def process_json(response):
 | |
|             try:
 | |
|                 response = json.loads(str(response))
 | |
|             except ValueError, e:
 | |
|                 pass
 | |
|             return response
 | |
| 
 | |
|         response = process_json(response)
 | |
|         if response is None:
 | |
|             return
 | |
| 
 | |
|         isAsync = isAsync and (self.asyncblock == "true")
 | |
|         if isAsync and 'jobid' in response[response.keys()[0]]:
 | |
|             jobId = response[response.keys()[0]]['jobid']
 | |
|             command = "queryAsyncJobResult"
 | |
|             requests = {'jobid': jobId}
 | |
|             timeout = int(self.timeout)
 | |
|             while timeout > 0:
 | |
|                 response = process_json(conn.make_request_with_auth(command,
 | |
|                                                                     requests))
 | |
|                 result = response[response.keys()[0]]
 | |
|                 jobstatus = result['jobstatus']
 | |
|                 if jobstatus == 2:
 | |
|                     jobresult = result["jobresult"]
 | |
|                     self.print_shell("Async query failed for jobid=",
 | |
|                                      jobId, "\nError", jobresult["errorcode"],
 | |
|                                      jobresult["errortext"])
 | |
|                     return
 | |
|                 elif jobstatus == 1:
 | |
|                     return response
 | |
|                 time.sleep(4)
 | |
|                 timeout = timeout - 4
 | |
|                 logger.debug("job: %s to timeout in %ds" % (jobId, timeout))
 | |
|             self.print_shell("Error:", "Async query timeout for jobid=", jobId)
 | |
| 
 | |
|         return response
 | |
| 
 | |
|     def get_api_module(self, api_name, api_class_strs=[]):
 | |
|         try:
 | |
|             api_mod = __import__("marvin.cloudstackAPI.%s" % api_name,
 | |
|                                  globals(), locals(), api_class_strs, -1)
 | |
|         except ImportError, e:
 | |
|             self.print_shell("Error: API not found", e)
 | |
|             return None
 | |
|         return api_mod
 | |
| 
 | |
|     def default(self, args):
 | |
|         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)
 | |
|         api_name = args[0]
 | |
| 
 | |
|         args_dict = dict(map(lambda x: [x.partition("=")[0],
 | |
|                                         x.partition("=")[2]],
 | |
|                              args[1:])[x] for x in range(len(args) - 1))
 | |
| 
 | |
|         # FIXME: With precaching, dynamic loading can be removed
 | |
|         api_cmd_str = "%sCmd" % api_name
 | |
|         api_mod = self.get_api_module(api_name, [api_cmd_str])
 | |
|         if api_mod is None:
 | |
|             return
 | |
| 
 | |
|         try:
 | |
|             api_cmd = getattr(api_mod, api_cmd_str)
 | |
|         except AttributeError, e:
 | |
|             self.print_shell("Error: API attribute %s not found!" % e)
 | |
|             return
 | |
| 
 | |
|         for attribute in args_dict.keys():
 | |
|             setattr(api_cmd, attribute, args_dict[attribute])
 | |
| 
 | |
|         command = api_cmd()
 | |
|         missing_args = list(sets.Set(command.required).difference(
 | |
|                             sets.Set(args_dict.keys())))
 | |
| 
 | |
|         if len(missing_args) > 0:
 | |
|             self.print_shell("Missing arguments:", ' '.join(missing_args))
 | |
|             return
 | |
| 
 | |
|         isAsync = False
 | |
|         if "isAsync" in dir(command):
 | |
|             isAsync = (command.isAsync == "true")
 | |
| 
 | |
|         result = self.make_request(api_name, args_dict, isAsync)
 | |
|         if result is None:
 | |
|             return
 | |
|         try:
 | |
|             self.print_result(result.values())
 | |
|             print
 | |
|         except Exception as e:
 | |
|             self.print_shell("🙈  Error on parsing and printing", e)
 | |
| 
 | |
|     def cache_verb_miss(self, verb):
 | |
|         self.print_shell("Oops: Verb %s should have been precached" % verb)
 | |
|         completions_found = filter(lambda x: x.startswith(verb), completions)
 | |
|         self.cache_verbs[verb] = {}
 | |
|         for api_name in completions_found:
 | |
|             api_cmd_str = "%sCmd" % api_name
 | |
|             api_mod = self.get_api_module(api_name, [api_cmd_str])
 | |
|             if api_mod is None:
 | |
|                 continue
 | |
|             try:
 | |
|                 api_cmd = getattr(api_mod, api_cmd_str)()
 | |
|                 required = api_cmd.required
 | |
|                 doc = api_mod.__doc__
 | |
|             except AttributeError, e:
 | |
|                 self.print_shell("Error: API attribute %s not found!" % e)
 | |
|             params = filter(lambda x: '__' not in x and 'required' not in x,
 | |
|                             dir(api_cmd))
 | |
|             if len(required) > 0:
 | |
|                 doc += "\nRequired args: %s" % " ".join(required)
 | |
|             doc += "\nArgs: %s" % " ".join(params)
 | |
|             api_name_lower = api_name.replace(verb, '').lower()
 | |
|             self.cache_verbs[verb][api_name_lower] = [api_name, params, doc,
 | |
|                                                       required]
 | |
| 
 | |
|     def completedefault(self, text, line, begidx, endidx):
 | |
|         partitions = line.partition(" ")
 | |
|         verb = partitions[0]
 | |
|         rline = partitions[2].partition(" ")
 | |
|         subject = rline[0]
 | |
|         separator = rline[1]
 | |
|         params = rline[2]
 | |
| 
 | |
|         if verb not in self.grammar:
 | |
|             return []
 | |
| 
 | |
|         autocompletions = []
 | |
|         search_string = ""
 | |
| 
 | |
|         if verb not in self.cache_verbs:
 | |
|             self.cache_verb_miss(verb)
 | |
| 
 | |
|         if separator != " ":   # Complete verb subjects
 | |
|             autocompletions = self.cache_verbs[verb].keys()
 | |
|             search_string = subject
 | |
|         else:                  # Complete subject params
 | |
|             autocompletions = map(lambda x: x + "=",
 | |
|                                   self.cache_verbs[verb][subject][1])
 | |
|             search_string = text
 | |
| 
 | |
|         return [s for s in autocompletions if s.startswith(search_string)]
 | |
| 
 | |
|     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.print_shell("Please use a valid syntax")
 | |
| 
 | |
|     def complete_api(self, text, line, begidx, endidx):
 | |
|         mline = line.partition(" ")[2]
 | |
|         offs = len(mline) - len(text)
 | |
|         return [s[offs:] for s in completions if s.startswith(mline)]
 | |
| 
 | |
|     def do_set(self, args):
 | |
|         """
 | |
|         Set config for CloudStack CLI. Available options are:
 | |
|         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])
 | |
|         # Note: keys and class attributes should have same names
 | |
|         setattr(self, key, value)
 | |
|         self.write_config()
 | |
| 
 | |
|     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_fields.keys()
 | |
|                 if s.startswith(mline)]
 | |
| 
 | |
|     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 verb not in self.cache_verbs:
 | |
|                 self.cache_verb_miss(verb)
 | |
| 
 | |
|             if subject in self.cache_verbs[verb]:
 | |
|                 self.print_shell(self.cache_verbs[verb][subject][2])
 | |
|             else:
 | |
|                 self.print_shell("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_exit(self, args):
 | |
|         """
 | |
|         Quit Apache CloudStack CLI
 | |
|         """
 | |
|         return self.do_quit(args)
 | |
| 
 | |
|     def do_quit(self, args):
 | |
|         """
 | |
|         Quit Apache CloudStack CLI
 | |
|         """
 | |
|         self.print_shell("Bye!")
 | |
|         return self.do_EOF(args)
 | |
| 
 | |
|     def do_EOF(self, args):
 | |
|         """
 | |
|         Quit on Ctrl+d or EOF
 | |
|         """
 | |
|         return True
 | |
| 
 | |
| 
 | |
| def main():
 | |
|     # Create handlers on the fly using closures
 | |
|     self = CloudStackShell
 | |
|     global grammar
 | |
|     for rule in grammar:
 | |
|         def add_grammar(rule):
 | |
|             def grammar_closure(self, args):
 | |
|                 if '|' in args:  # FIXME: Consider parsing issues
 | |
|                     prog_name = sys.argv[0]
 | |
|                     if '.py' in prog_name:
 | |
|                         prog_name = "python " + prog_name
 | |
|                     self.do_shell("%s %s %s" % (prog_name, rule, args))
 | |
|                     return
 | |
|                 if not rule in self.cache_verbs:
 | |
|                     self.cache_verb_miss(rule)
 | |
|                 try:
 | |
|                     args_partition = args.partition(" ")
 | |
|                     res = self.cache_verbs[rule][args_partition[0]]
 | |
| 
 | |
|                 except KeyError, e:
 | |
|                     self.print_shell("Error: invalid %s api arg" % rule, e)
 | |
|                     return
 | |
|                 if ' --help' in args or ' -h' in args:
 | |
|                     self.print_shell(res[2])
 | |
|                     return
 | |
|                 self.default(res[0] + " " + args_partition[2])
 | |
|             return grammar_closure
 | |
| 
 | |
|         grammar_handler = add_grammar(rule)
 | |
|         grammar_handler.__doc__ = "%ss resources" % rule.capitalize()
 | |
|         grammar_handler.__name__ = 'do_' + rule
 | |
|         setattr(self, grammar_handler.__name__, grammar_handler)
 | |
| 
 | |
|     shell = CloudStackShell()
 | |
|     if len(sys.argv) > 1:
 | |
|         shell.onecmd(' '.join(sys.argv[1:]))
 | |
|     else:
 | |
|         shell.cmdloop()
 | |
| 
 | |
| if __name__ == "__main__":
 | |
|     main()
 |