tools: Remove cloudmonkey, add info in INSTALL.md

After some discussion on the dev ML[1], we decided to move tools/cli which
contained cloudmonkey to a new git repository [2]. We did that by retaining
its history. In this commit, we remove tools/cli and add information on where
to find cloudmonkey. This is help us speed up cloudmonkey's development and
releases, now that with ApiDiscovery it's completely independent of any other
CloudStack modules.

[1] http://markmail.org/message/tjlr753xfhpw4uk4
[2] https://git-wip-us.apache.org/repos/asf?p=cloudstack-cloudmonkey.git

Signed-off-by: Rohit Yadav <bhaisaab@apache.org>
This commit is contained in:
Rohit Yadav 2013-07-28 22:49:17 +05:30
parent 76c90efa40
commit 6f84e74a68
11 changed files with 8 additions and 1291 deletions

View File

@ -213,6 +213,13 @@ Install needed packages:
$ yum install cloud-agent # agent (kvm)
$ yum install cloud-usage # usage server
## Installing CloudMonkey CLI
CloudMonkey is a CLI for Apache CloudStack. It was earlier in `tools/cli` within
the source code but now it has its own repository:
https://git-wip-us.apache.org/repos/asf?p=cloudstack-cloudmonkey.git
## Notes
If you will be using Xen as your hypervisor, please download [vhd-util](http://download.cloud.com.s3.amazonaws.com/tools/vhd-util)

1
tools/cli/README Normal file
View File

@ -0,0 +1 @@
Moved to https://git-wip-us.apache.org/repos/asf?p=cloudstack-cloudmonkey.git

View File

@ -1,23 +0,0 @@
# 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:
from config import __version__, __description__
from config import __maintainer__, __maintaineremail__
from config import __project__, __projecturl__, __projectemail__
except ImportError, e:
print e

View File

@ -1,181 +0,0 @@
# -*- 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 json
import os
import types
from config import config_fields
except ImportError, e:
import sys
print "ImportError", e
sys.exit(1)
def getvalue(dictionary, key):
if key in dictionary:
return dictionary[key]
else:
return None
def splitcsvstring(string):
if string is not None:
return filter(lambda x: x.strip() != '', string.split(','))
else:
return []
def splitverbsubject(string):
idx = 0
for char in string:
if char.islower():
idx += 1
else:
break
return string[:idx].lower(), string[idx:].lower()
def savecache(apicache, json_file):
"""
Saves apicache dictionary as json_file, returns dictionary as indented str
"""
if apicache is None or apicache is {}:
return ""
apicachestr = json.dumps(apicache, indent=2)
with open(json_file, 'w') as cache_file:
cache_file.write(apicachestr)
return apicachestr
def loadcache(json_file):
"""
Loads json file as dictionary, feeds it to monkeycache and spits result
"""
f = open(json_file, 'r')
data = f.read()
f.close()
try:
apicache = json.loads(data)
except ValueError, e:
print "Error processing json:", json_file, e
return {}
return apicache
def monkeycache(apis):
"""
Feed this a dictionary of api bananas, it spits out processed cache
"""
if isinstance(type(apis), types.NoneType) or apis is None:
return {}
responsekey = filter(lambda x: 'response' in x, apis.keys())
if len(responsekey) == 0:
print "[monkeycache] Invalid dictionary, has no response"
return None
if len(responsekey) != 1:
print "[monkeycache] Multiple responsekeys, chosing first one"
responsekey = responsekey[0]
verbs = set()
cache = {}
cache['count'] = getvalue(apis[responsekey], 'count')
cache['asyncapis'] = []
apilist = getvalue(apis[responsekey], 'api')
if apilist is None:
print "[monkeycache] Server response issue, no apis found"
for api in apilist:
name = getvalue(api, 'name')
verb, subject = splitverbsubject(name)
apidict = {}
apidict['name'] = name
apidict['description'] = getvalue(api, 'description')
apidict['isasync'] = getvalue(api, 'isasync')
if apidict['isasync']:
cache['asyncapis'].append(name)
apidict['related'] = splitcsvstring(getvalue(api, 'related'))
required = []
apiparams = []
for param in getvalue(api, 'params'):
apiparam = {}
apiparam['name'] = getvalue(param, 'name')
apiparam['description'] = getvalue(param, 'description')
apiparam['required'] = (getvalue(param, 'required') is True)
apiparam['length'] = int(getvalue(param, 'length'))
apiparam['type'] = getvalue(param, 'type')
apiparam['related'] = splitcsvstring(getvalue(param, 'related'))
if apiparam['required']:
required.append(apiparam['name'])
apiparams.append(apiparam)
apidict['requiredparams'] = required
apidict['params'] = apiparams
if verb not in cache:
cache[verb] = {}
cache[verb][subject] = apidict
verbs.add(verb)
cache['verbs'] = list(verbs)
return cache
def main(json_file):
"""
cachemaker.py creates a precache datastore of all available apis of
CloudStack and dumps the precache dictionary in an
importable python module. This way we cheat on the runtime overhead of
completing commands and help docs. This reduces the overall search and
cache_miss (computation) complexity from O(n) to O(1) for any valid cmd.
"""
f = open("precache.py", "w")
f.write("""# -*- coding: utf-8 -*-
# Auto-generated code by cachemaker.py
# 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.""")
f.write("\napicache = %s" % loadcache(json_file))
f.close()
if __name__ == "__main__":
cache_file = config_fields['core']['cache_file']
print "[cachemaker] Pre-caching using user's cloudmonkey cache", cache_file
if os.path.exists(cache_file):
main(cache_file)
else:
print "[cachemaker] Unable to cache apis, file not found", cache_file
print "[cachemaker] Run cloudmonkey sync to generate cache"

View File

@ -1,538 +0,0 @@
#!/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
import copy
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_json(result, result_filter=None):
tfilter = {} # temp var to hold a dict of the filters
tresult = copy.deepcopy(result) # dupe the result to filter
if result_filter is not None:
for res in result_filter:
tfilter[res] = 1
myresults = {}
for okey, oval in result.iteritems():
if isinstance(oval, dict):
for tkey in x:
if tkey not in tfilter:
try:
del(tresult[okey][x][tkey])
except:
pass
elif isinstance(oval, list):
for x in range(len(oval)):
if isinstance(oval[x], dict):
for tkey in oval[x]:
if tkey not in tfilter:
try:
del(tresult[okey][x][tkey])
except:
pass
else:
try:
del(tresult[okey][x])
except:
pass
print json.dumps(tresult,
sort_keys=True,
indent=2,
separators=(',', ': '))
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):
if self.display == "json":
print_result_json(result, result_filter)
return
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:
if isinstance(node, dict) and self.display == 'table':
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 subject != "" and (self.display == "table" or
self.display == "json"):
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()

View File

@ -1,122 +0,0 @@
# -*- 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.
# Use following rules for versioning:
# <cloudstack version>-<cli increment, starts from 0>
__version__ = "4.2.0-0"
__description__ = "Command Line Interface for Apache CloudStack"
__maintainer__ = "Rohit Yadav"
__maintaineremail__ = "bhaisaab@apache.org"
__project__ = "The Apache CloudStack Team"
__projectemail__ = "dev@cloudstack.apache.org"
__projecturl__ = "http://cloudstack.apache.org"
try:
import os
import sys
from ConfigParser import ConfigParser, SafeConfigParser
from os.path import expanduser
except ImportError, e:
print "ImportError", e
param_type = ['boolean', 'date', 'float', 'integer', 'short', 'list',
'long', 'object', 'map', 'string', 'tzdate', 'uuid']
iterable_type = ['set', 'list', 'object']
config_dir = expanduser('~/.cloudmonkey')
config_file = expanduser(config_dir + '/config')
# cloudmonkey config fields
config_fields = {'core': {}, 'server': {}, 'user': {}, 'ui': {}}
# core
config_fields['core']['asyncblock'] = 'true'
config_fields['core']['paramcompletion'] = 'false'
config_fields['core']['cache_file'] = expanduser(config_dir + '/cache')
config_fields['core']['history_file'] = expanduser(config_dir + '/history')
config_fields['core']['log_file'] = expanduser(config_dir + '/log')
# ui
config_fields['ui']['color'] = 'true'
config_fields['ui']['prompt'] = '> '
config_fields['ui']['display'] = 'default'
# server
config_fields['server']['host'] = 'localhost'
config_fields['server']['path'] = '/client/api'
config_fields['server']['port'] = '8080'
config_fields['server']['protocol'] = 'http'
config_fields['server']['timeout'] = '3600'
# user
config_fields['user']['apikey'] = ''
config_fields['user']['secretkey'] = ''
def write_config(get_attr, config_file, first_time=False):
global config_fields
config = ConfigParser()
for section in config_fields.keys():
config.add_section(section)
for key in config_fields[section].keys():
if first_time:
config.set(section, key, config_fields[section][key])
else:
config.set(section, key, get_attr(key))
with open(config_file, 'w') as cfg:
config.write(cfg)
return config
def read_config(get_attr, set_attr, config_file):
global config_fields, config_dir
if not os.path.exists(config_dir):
os.makedirs(config_dir)
config_options = reduce(lambda x, y: x + y, map(lambda x:
config_fields[x].keys(), config_fields.keys()))
if os.path.exists(config_file):
config = ConfigParser()
try:
with open(config_file, 'r') as cfg:
config.readfp(cfg)
except IOError, e:
print "Error: config_file not found", e
else:
config = write_config(get_attr, config_file, True)
print "Welcome! Using `set` configure the necessary settings:"
print " ".join(sorted(config_options))
print "Config file:", config_file
print "After setting up, run the `sync` command to sync apis\n"
missing_keys = []
for section in config_fields.keys():
for key in config_fields[section].keys():
try:
set_attr(key, config.get(section, key))
except Exception:
missing_keys.append(key)
if len(missing_keys) > 0:
print "Please fix `%s` in %s" % (', '.join(missing_keys), config_file)
sys.exit()
return config_options

View File

@ -1,120 +0,0 @@
# -*- 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:
from pygments import highlight
from pygments.console import ansiformat
from pygments.formatter import Formatter
from pygments.formatters import Terminal256Formatter
from pygments.lexer import bygroups, include, RegexLexer
from pygments.token import *
import sys
except ImportError, e:
print e
MONKEY_COLORS = {
Token: '',
Whitespace: 'reset',
Text: 'reset',
Name: 'green',
Operator: 'teal',
Operator.Word: 'lightgray',
String: 'purple',
Keyword: '_red_',
Error: 'red',
Literal: 'yellow',
Number: 'blue',
}
def get_colorscheme():
return MONKEY_COLORS
class MonkeyLexer(RegexLexer):
keywords = ['[a-z]*id', '^[a-z A-Z]*:']
attributes = ['[Tt]rue', '[Ff]alse']
params = ['[a-z]*[Nn]ame', 'type', '[Ss]tate']
uuid_rgx = r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
date_rgx = r'[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9:]{8}-[0-9]{4}'
def makelistre(lis):
return r'(' + r'|'.join(lis) + r')'
tokens = {
'root': [
(r' ', Whitespace),
(date_rgx, Number),
(uuid_rgx, Literal),
(r'(?:\b\d+\b(?:-\b\d+|%)?)', Number),
(r'^[-=]*\n', Operator.Word),
(r'Error', Error),
(makelistre(attributes), Literal),
(makelistre(params) + r'( = )(.*)', bygroups(Name, Operator,
String)),
(makelistre(keywords), Keyword),
(makelistre(params), Name),
(r'(^[a-zA-Z]* )(=)', bygroups(Name, Operator)),
(r'\S+', Text),
]
}
def analyse_text(text):
npos = text.find('\n')
if npos < 3:
return False
return text[0] == '[' and text[npos - 1] == ']'
class MonkeyFormatter(Formatter):
def __init__(self, **options):
Formatter.__init__(self, **options)
self.colorscheme = get_colorscheme()
def format(self, tokensource, outfile):
return Formatter.format(self, tokensource, outfile)
def format_unencoded(self, tokensource, outfile):
for ttype, value in tokensource:
color = self.colorscheme.get(ttype)
while color is None:
ttype = ttype[:-1]
color = self.colorscheme.get(ttype)
if color:
spl = value.split('\n')
for line in spl[:-1]:
if line:
outfile.write(ansiformat(color, line))
outfile.write('\n')
if spl[-1]:
outfile.write(ansiformat(color, spl[-1]))
else:
outfile.write(value)
def monkeyprint(text):
fmter = MonkeyFormatter()
lexer = MonkeyLexer()
lexer.encoding = 'utf-8'
fmter.encoding = 'utf-8'
highlight(text, lexer, fmter, sys.stdout)

View File

@ -1,165 +0,0 @@
#!/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 base64
import hashlib
import hmac
import httplib
import json
import os
import pdb
import re
import shlex
import sys
import time
import types
import urllib
import urllib2
from urllib2 import urlopen, HTTPError, URLError
except ImportError, e:
print "Import error in %s : %s" % (__name__, e)
import sys
sys.exit()
def logger_debug(logger, message):
if logger is not None:
logger.debug(message)
def make_request(command, args, logger, host, port,
apikey, secretkey, protocol, path):
response = None
error = None
if protocol != 'http' and protocol != 'https':
error = "Protocol must be 'http' or 'https'"
return None, error
if args is None:
args = {}
args["command"] = command
args["apiKey"] = apikey
args["response"] = "json"
request = zip(args.keys(), args.values())
request.sort(key=lambda x: x[0].lower())
request_url = "&".join(["=".join([r[0], urllib.quote_plus(str(r[1]))])
for r in request])
hashStr = "&".join(["=".join([r[0].lower(),
str.lower(urllib.quote_plus(str(r[1]))).replace("+",
"%20")]) for r in request])
sig = urllib.quote_plus(base64.encodestring(hmac.new(secretkey, hashStr,
hashlib.sha1).digest()).strip())
request_url += "&signature=%s" % sig
request_url = "%s://%s:%s%s?%s" % (protocol, host, port, path, request_url)
try:
logger_debug(logger, "Request sent: %s" % request_url)
connection = urllib2.urlopen(request_url)
response = connection.read()
except HTTPError, e:
error = "%s: %s" % (e.msg, e.info().getheader('X-Description'))
except URLError, e:
error = e.reason
logger_debug(logger, "Response received: %s" % response)
if error is not None:
logger_debug(logger, "Error: %s" % (error))
return response, error
return response, error
def monkeyrequest(command, args, isasync, asyncblock, logger, host, port,
apikey, secretkey, timeout, protocol, path):
response = None
error = None
logger_debug(logger, "======== START Request ========")
logger_debug(logger, "Requesting command=%s, args=%s" % (command, args))
response, error = make_request(command, args, logger, host, port,
apikey, secretkey, protocol, path)
logger_debug(logger, "======== END Request ========\n")
if error is not None:
return response, error
def process_json(response):
try:
response = json.loads(str(response))
except ValueError, e:
error = "Error processing json response, %s" % e
logger_debug(logger, "Error processing json: %s" % e)
return response
response = process_json(response)
if response is None:
return response, error
isasync = isasync and (asyncblock == "true")
responsekey = filter(lambda x: 'response' in x, response.keys())[0]
if isasync and 'jobid' in response[responsekey]:
jobid = response[responsekey]['jobid']
command = "queryAsyncJobResult"
request = {'jobid': jobid}
timeout = int(timeout)
pollperiod = 2
progress = 1
while timeout > 0:
print '\r' + '.' * progress,
sys.stdout.flush()
time.sleep(pollperiod)
timeout = timeout - pollperiod
progress += 1
logger_debug(logger, "Job %s to timeout in %ds" % (jobid, timeout))
response, error = make_request(command, request, logger,
host, port, apikey, secretkey,
protocol, path)
if error is not None:
return response, error
response = process_json(response)
responsekeys = filter(lambda x: 'response' in x, response.keys())
if len(responsekeys) < 1:
continue
result = response[responsekeys[0]]
jobstatus = result['jobstatus']
if jobstatus == 2:
jobresult = result["jobresult"]
error = "\rAsync job %s failed\nError %s, %s" % (jobid,
jobresult["errorcode"], jobresult["errortext"])
return response, error
elif jobstatus == 1:
print "\r" + " " * progress,
return response, error
else:
logger_debug(logger, "We should not arrive here!")
sys.stdout.flush()
error = "Error: Async query timeout occurred for jobid %s" % jobid
return response, error

View File

@ -1,73 +0,0 @@
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-cli</artifactId>
<name>Apache CloudStack cloudmonkey cli</name>
<packaging>pom</packaging>
<parent>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-tools</artifactId>
<version>4.2.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<build>
<defaultGoal>install</defaultGoal>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<executions>
<execution>
<id>cachemaker</id>
<phase>compile</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<workingDirectory>${basedir}/cloudmonkey</workingDirectory>
<executable>python</executable>
<arguments>
<argument>cachemaker.py</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>package</id>
<phase>compile</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<workingDirectory>${basedir}</workingDirectory>
<executable>python</executable>
<arguments>
<argument>setup.py</argument>
<argument>sdist</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,68 +0,0 @@
# 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:
from setuptools import setup, find_packages
except ImportError:
from distribute_setup import use_setuptools
use_setuptools()
from setuptools import setup, find_packages
from cloudmonkey import __version__, __description__
from cloudmonkey import __maintainer__, __maintaineremail__
from cloudmonkey import __project__, __projecturl__, __projectemail__
try:
import readline
except ImportError:
requires.append('readline')
setup(
name = 'cloudmonkey',
version = __version__,
author = __project__,
author_email = __projectemail__,
maintainer = __maintainer__,
maintainer_email = __maintaineremail__,
url = __projecturl__,
description = __description__,
long_description = "cloudmonkey is a CLI for Apache CloudStack",
platforms = ("Any",),
license = 'ASL 2.0',
packages = find_packages(),
install_requires = [
'Pygments>=1.5',
'prettytable>=0.6',
],
include_package_data = True,
zip_safe = False,
classifiers = [
"Development Status :: 5 - Production/Stable",
"Environment :: Console",
"Intended Audience :: Developers",
"Intended Audience :: End Users/Desktop",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Topic :: Software Development :: Testing",
"Topic :: Software Development :: Interpreters",
"Topic :: Utilities",
],
entry_points="""
[console_scripts]
cloudmonkey = cloudmonkey.cloudmonkey:main
""",
)

View File

@ -37,7 +37,6 @@
<modules>
<module>apidoc</module>
<module>marvin</module>
<module>cli</module>
<module>devcloud</module>
<module>devcloud-kvm</module>
</modules>