You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by bh...@apache.org on 2013/02/01 23:35:19 UTC

git commit: refs/heads/master - CLOUDSTACK-1037: Make cloudmonkey awesome-er

Updated Branches:
  refs/heads/master 77e3aade0 -> 631b6fd46


CLOUDSTACK-1037: Make cloudmonkey awesome-er

Signed-off-by: Rohit Yadav <bh...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/commit/631b6fd4
Tree: http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/tree/631b6fd4
Diff: http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/diff/631b6fd4

Branch: refs/heads/master
Commit: 631b6fd46a4371425991127cf19379feea16f869
Parents: 77e3aad
Author: Rohit Yadav <bh...@apache.org>
Authored: Fri Feb 1 14:34:49 2013 -0800
Committer: Rohit Yadav <bh...@apache.org>
Committed: Fri Feb 1 14:34:49 2013 -0800

----------------------------------------------------------------------
 tools/cli/cloudmonkey/__init__.py    |    2 +-
 tools/cli/cloudmonkey/cachegen.py    |   97 -----------
 tools/cli/cloudmonkey/cachemaker.py  |  145 ++++++++++++++++
 tools/cli/cloudmonkey/cloudmonkey.py |  256 ++++++++---------------------
 tools/cli/cloudmonkey/common.py      |   59 -------
 tools/cli/cloudmonkey/config.py      |  115 +++++++++++++
 tools/cli/cloudmonkey/lexer.py       |  121 --------------
 tools/cli/cloudmonkey/printer.py     |  133 +++++++++++++++
 tools/cli/cloudmonkey/requester.py   |  153 +++++++++++++++++
 tools/cli/pom.xml                    |    4 +-
 10 files changed, 615 insertions(+), 470 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/631b6fd4/tools/cli/cloudmonkey/__init__.py
----------------------------------------------------------------------
diff --git a/tools/cli/cloudmonkey/__init__.py b/tools/cli/cloudmonkey/__init__.py
index 9674777..e4c4e6d 100644
--- a/tools/cli/cloudmonkey/__init__.py
+++ b/tools/cli/cloudmonkey/__init__.py
@@ -16,6 +16,6 @@
 # under the License.
 
 try:
-    from common import __version__
+    from config import __version__
 except ImportError, e:
     print e

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/631b6fd4/tools/cli/cloudmonkey/cachegen.py
----------------------------------------------------------------------
diff --git a/tools/cli/cloudmonkey/cachegen.py b/tools/cli/cloudmonkey/cachegen.py
deleted file mode 100644
index 509c0c6..0000000
--- a/tools/cli/cloudmonkey/cachegen.py
+++ /dev/null
@@ -1,97 +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:
-    import re
-    from marvin.cloudstackAPI import *
-    from marvin import cloudstackAPI
-except ImportError, e:
-    import sys
-    print "ImportError", e
-    sys.exit(1)
-
-completions = cloudstackAPI.__all__
-
-
-def get_api_module(api_name, api_class_strs=[]):
-    try:
-        api_mod = __import__("marvin.cloudstackAPI.%s" % api_name,
-                             globals(), locals(), api_class_strs, -1)
-    except ImportError, e:
-        print "Error: API not found", e
-        return None
-    return api_mod
-
-
-def main():
-    """
-    cachegen.py creates a precached dictionary for all the available verbs in
-    the predefined grammar of cloudmonkey, it dumps the 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.
-    """
-    pattern = re.compile("[A-Z]")
-    verbs = list(set([x[:pattern.search(x).start()] for x in completions
-                 if pattern.search(x) is not None]).difference(['cloudstack']))
-    # datastructure {'verb': {cmd': ['api', [params], doc, required=[]]}}
-    cache_verbs = {}
-    for verb in verbs:
-        completions_found = filter(lambda x: x.startswith(verb), completions)
-        cache_verbs[verb] = {}
-        for api_name in completions_found:
-            api_cmd_str = "%sCmd" % api_name
-            api_mod = 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:
-                print "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()
-            cache_verbs[verb][api_name_lower] = [api_name, params, doc,
-                                                 required]
-    f = open("precache.py", "w")
-    f.write("""# Auto-generated code by cachegen.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("\nprecached_verbs = %s" % cache_verbs)
-    f.close()
-
-if __name__ == "__main__":
-    main()

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/631b6fd4/tools/cli/cloudmonkey/cachemaker.py
----------------------------------------------------------------------
diff --git a/tools/cli/cloudmonkey/cachemaker.py b/tools/cli/cloudmonkey/cachemaker.py
new file mode 100644
index 0000000..264fa60
--- /dev/null
+++ b/tools/cli/cloudmonkey/cachemaker.py
@@ -0,0 +1,145 @@
+# -*- 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 re
+
+    from requester import monkeyrequest
+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 csv_str_as_list(string):
+    if string is not None:
+        return filter(lambda x: x.strip() != '', string.split(','))
+    else:
+        return []
+
+
+def cachegen_from_file(json_file):
+    f = open(json_file, 'r')
+    data = f.read()
+    f.close()
+    try:
+        apis = json.loads(data)
+    except ValueError, e:
+        print "Error processing json in cachegen()", e
+    return cachegen(apis)
+
+
+def cachegen(apis):
+    pattern = re.compile("[A-Z]")
+    responsekey = filter(lambda x: 'response' in x, apis.keys())
+
+    if len(responsekey) == 0:
+        print "[cachegen] Invalid dictionary, has no response"
+        return None
+    if len(responsekey) != 1:
+        print "[cachegen] Multiple responsekeys, chosing first one"
+
+    responsekey = responsekey[0]
+    verbs = set()
+    cache = {}
+    cache['count'] = getvalue(apis[responsekey], 'count')
+
+    for api in getvalue(apis[responsekey], 'api'):
+        name = getvalue(api, 'name')
+        response = getvalue(api, 'response')
+
+        idx = pattern.search(name).start()
+        verb = name[:idx]
+        subject = name[idx:]
+
+        apidict = {}
+        apidict['name'] = name
+        apidict['description'] = getvalue(api, 'description')
+        apidict['isasync'] = getvalue(api, 'isasync')
+        apidict['related'] = csv_str_as_list(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'] = csv_str_as_list(getvalue(param, 'related'))
+            if apiparam['required']:
+                required.append(apiparam['name'])
+            apiparams.append(apiparam)
+
+        apidict['requiredparams'] = required
+        apidict['params'] = apiparams
+        apidict['response'] = getvalue(api, 'response')
+        cache[verb] = {subject: apidict}
+        verbs.add(verb)
+
+    cache['verbs'] = list(verbs)
+    return cache
+
+
+def main(json_file):
+    """
+    cachegen.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 cachegen.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("\nprecache = %s" % cachegen_from_file(json_file))
+    f.close()
+
+if __name__ == "__main__":
+    json_file = 'listapis.json'
+    if os.path.exists(json_file):
+        main(json_file)
+    else:
+        pass
+        #print "[ERROR] cli:cachegen is unable to locate %s" % json_file

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/631b6fd4/tools/cli/cloudmonkey/cloudmonkey.py
----------------------------------------------------------------------
diff --git a/tools/cli/cloudmonkey/cloudmonkey.py b/tools/cli/cloudmonkey/cloudmonkey.py
index 646ad40..49f3ee9 100644
--- a/tools/cli/cloudmonkey/cloudmonkey.py
+++ b/tools/cli/cloudmonkey/cloudmonkey.py
@@ -20,7 +20,6 @@
 try:
     import atexit
     import cmd
-    import codecs
     import json
     import logging
     import os
@@ -28,18 +27,16 @@ try:
     import re
     import shlex
     import sys
-    import time
-    import types
 
-    from ConfigParser import ConfigParser, SafeConfigParser
     from urllib2 import HTTPError, URLError
     from httplib import BadStatusLine
 
-    from prettytable import PrettyTable
-    from common import __version__, config_dir, config_file, config_fields
-    from common import precached_verbs
-    from lexer import monkeyprint
+    from config import __version__, config_file
+    from config import precached_verbs, read_config, write_config
+    from printer import monkeyprint
+    from requester import monkeyrequest
 
+    from prettytable import PrettyTable
     from marvin.cloudstackConnection import cloudConnection
     from marvin.cloudstackException import cloudstackAPIException
     from marvin.cloudstackAPI import *
@@ -70,8 +67,7 @@ class CloudMonkeyShell(cmd.Cmd, object):
     intro = ("☁ Apache CloudStack 🐵 cloudmonkey " + __version__ +
              ". Type help or ? to list commands.\n")
     ruler = "="
-    config_dir = config_dir
-    config_file = config_file
+    apicache = {}
     # datastructure {'verb': {cmd': ['api', [params], doc, required=[]]}}
     cache_verbs = precached_verbs
     config_options = []
@@ -79,31 +75,8 @@ class CloudMonkeyShell(cmd.Cmd, object):
     def __init__(self, pname, verbs):
         self.program_name = pname
         self.verbs = verbs
-        global config_fields
-        first_time = False
-        if not os.path.exists(self.config_dir):
-            os.makedirs(self.config_dir)
-        if os.path.exists(self.config_file):
-            config = self.read_config()
-        else:
-            first_time = True
-            config = self.write_config(first_time)
-
-        for section in config_fields.keys():
-            for key in config_fields[section].keys():
-                try:
-                    self.config_options.append(key)
-                    setattr(self, key, config.get(section, key))
-                except Exception:
-                    print "Please fix `%s` in %s" % (key, self.config_file)
-                    sys.exit()
-
-        if first_time:
-            print "Welcome! Using `set` configure the necessary settings:"
-            print " ".join(sorted(self.config_options))
-            print "Config file:", self.config_file
-            print "For debugging, tail -f", self.log_file, "\n"
 
+        self.config_options = read_config(self.get_attr, self.set_attr)
         self.prompt = self.prompt.strip() + " "  # Cosmetic fix for prompt
 
         logging.basicConfig(filename=self.log_file,
@@ -111,40 +84,20 @@ class CloudMonkeyShell(cmd.Cmd, object):
         logger.debug("Loaded config fields:\n%s" % map(lambda x: "%s=%s" %
                                                        (x, getattr(self, x)),
                                                        self.config_options))
-
         cmd.Cmd.__init__(self)
-        if not os.path.exists(self.config_file):
-            config = self.write_config()
 
         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")
+            monkeyprint("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, 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, getattr(self, key))
-        with open(self.config_file, 'w') as cfg:
-            config.write(cfg)
-        return config
+    def get_attr(self, field):
+        return getattr(self, field)
+
+    def set_attr(self, field, value):
+        return setattr(self, field, value)
 
     def emptyline(self):
         pass
@@ -158,20 +111,8 @@ class CloudMonkeyShell(cmd.Cmd, object):
             except KeyboardInterrupt:
                 print("^C")
 
-    def print_shell(self, *args):
-        output = ""
-        try:
-            for arg in args:
-                arg = str(arg)
-                if isinstance(type(args), types.NoneType):
-                    continue
-                output += arg
-            if self.color == 'true':
-                monkeyprint(output)
-            else:
-                print output
-        except Exception, e:
-            self.print_shell("Error: " + e)
+    def monkeyprint(self, *args):
+        monkeyprint((self.color == 'true'), *args)
 
     def print_result(self, result, result_filter=None):
         if result is None or len(result) == 0:
@@ -179,7 +120,7 @@ class CloudMonkeyShell(cmd.Cmd, object):
 
         def printer_helper(printer, toprow):
             if printer:
-                self.print_shell(printer)
+                self.monkeyprint(printer)
             return PrettyTable(toprow)
 
         def print_result_tabular(result, result_filter=None):
@@ -200,16 +141,16 @@ class CloudMonkeyShell(cmd.Cmd, object):
                 if printer and row:
                     printer.add_row(row)
             if printer:
-                self.print_shell(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.print_shell("%s = %s" % (key, result[key]))
+                    self.monkeyprint("%s = %s" % (key, result[key]))
                 else:
-                    self.print_shell(key + ":")
+                    self.monkeyprint(key + ":")
                     self.print_result(result[key], result_filter)
 
         def print_result_as_list(result, result_filter=None):
@@ -220,7 +161,7 @@ class CloudMonkeyShell(cmd.Cmd, object):
                     break
                 self.print_result(node)
                 if len(result) > 1:
-                    self.print_shell(self.ruler * 80)
+                    self.monkeyprint(self.ruler * 80)
 
         if isinstance(result, dict):
             print_result_as_dict(result, result_filter)
@@ -229,92 +170,18 @@ class CloudMonkeyShell(cmd.Cmd, object):
         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")
-        responsekey = filter(lambda x: 'response' in x, response.keys())[0]
-        if isAsync and 'jobid' in response[responsekey]:
-            jobId = response[responsekey]['jobid']
-            command = "queryAsyncJobResult"
-            requests = {'jobid': jobId}
-            timeout = int(self.timeout)
-            pollperiod = 3
-            progress = 1
-            while timeout > 0:
-                print '\r' + '.' * progress,
-                sys.stdout.flush()
-                response = process_json(conn.make_request_with_auth(command,
-                                                                    requests))
-                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"]
-                    self.print_shell("\rAsync query failed for jobid",
-                                     jobId, "\nError", jobresult["errorcode"],
-                                     jobresult["errortext"])
-                    return
-                elif jobstatus == 1:
-                    print '\r',
-                    return response
-                time.sleep(pollperiod)
-                timeout = timeout - pollperiod
-                progress += 1
-                logger.debug("job: %s to timeout in %ds" % (jobId, timeout))
-            self.print_shell("Error:", "Async query timeout for jobid", jobId)
-
+            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 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 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 default(self, args):
         if self.pipe_runner(args):
             return
@@ -340,31 +207,20 @@ class CloudMonkeyShell(cmd.Cmd, object):
                                   map(lambda x: x.strip(),
                                       args_dict.pop('filter').split(',')))
 
-        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 = filter(lambda x: x not in args_dict.keys(),
-                              command.required)
+        #command = api_cmd()
+        #missing_args = filter(lambda x: x not in args_dict.keys(),
+        #                      command.required)
 
-        if len(missing_args) > 0:
-            self.print_shell("Missing arguments: ", ' '.join(missing_args))
-            return
+        #if len(missing_args) > 0:
+        #    self.monkeyprint("Missing arguments: ", ' '.join(missing_args))
+        #    return
 
         isAsync = False
-        if "isAsync" in dir(command):
-            isAsync = (command.isAsync == "true")
+        #if "isAsync" in dir(command):
+        #    isAsync = (command.isAsync == "true")
 
         result = self.make_request(api_name, args_dict, isAsync)
         if result is None:
@@ -375,7 +231,7 @@ class CloudMonkeyShell(cmd.Cmd, object):
                 self.print_result(result[responsekey], field_filter)
             print
         except Exception as e:
-            self.print_shell("🙈  Error on parsing and printing", e)
+            self.monkeyprint("🙈  Error on parsing and printing", e)
 
     def completedefault(self, text, line, begidx, endidx):
         partitions = line.partition(" ")
@@ -403,6 +259,17 @@ class CloudMonkeyShell(cmd.Cmd, object):
             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")
+        f = open('test.json', "w")
+        f.write(json.dumps(response))
+        f.close()
+
     def do_api(self, args):
         """
         Make raw api calls. Syntax: api <apiName> <args>=<values>.
@@ -413,7 +280,7 @@ class CloudMonkeyShell(cmd.Cmd, object):
         if len(args) > 0:
             return self.default(args)
         else:
-            self.print_shell("Please use a valid syntax")
+            self.monkeyprint("Please use a valid syntax")
 
     def complete_api(self, text, line, begidx, endidx):
         mline = line.partition(" ")[2]
@@ -435,7 +302,7 @@ class CloudMonkeyShell(cmd.Cmd, object):
         key, value = (args[0], args[2])
         setattr(self, key, value)  # keys and attributes should have same names
         self.prompt = self.prompt.strip() + " "  # prompt fix
-        self.write_config()
+        write_config(self.get_attr)
 
     def complete_set(self, text, line, begidx, endidx):
         mline = line.partition(" ")[2]
@@ -443,6 +310,15 @@ class CloudMonkeyShell(cmd.Cmd, object):
         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>
@@ -474,9 +350,9 @@ class CloudMonkeyShell(cmd.Cmd, object):
             subject = fields[2].partition(" ")[0]
 
             if subject in self.cache_verbs[verb]:
-                self.print_shell(self.cache_verbs[verb][subject][2])
+                self.monkeyprint(self.cache_verbs[verb][subject][2])
             else:
-                self.print_shell("Error: no such api (%s) on %s" %
+                self.monkeyprint("Error: no such api (%s) on %s" %
                                  (subject, verb))
 
     def complete_help(self, text, line, begidx, endidx):
@@ -500,7 +376,7 @@ class CloudMonkeyShell(cmd.Cmd, object):
         """
         Quit CloudMonkey CLI
         """
-        self.print_shell("Bye!")
+        self.monkeyprint("Bye!")
         return self.do_EOF(args)
 
     def do_EOF(self, args):
@@ -526,10 +402,10 @@ def main():
                     helpdoc = res[2]
                     args = args_partition[2]
                 except KeyError, e:
-                    self.print_shell("Error: invalid %s api arg" % verb, e)
+                    self.monkeyprint("Error: invalid %s api arg" % verb, e)
                     return
                 if ' --help' in args or ' -h' in args:
-                    self.print_shell(helpdoc)
+                    self.monkeyprint(helpdoc)
                     return
                 self.default("%s %s" % (cmd, args))
             return grammar_closure

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/631b6fd4/tools/cli/cloudmonkey/common.py
----------------------------------------------------------------------
diff --git a/tools/cli/cloudmonkey/common.py b/tools/cli/cloudmonkey/common.py
deleted file mode 100644
index 05767a5..0000000
--- a/tools/cli/cloudmonkey/common.py
+++ /dev/null
@@ -1,59 +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.1.0-0"
-
-try:
-    from os.path import expanduser
-    import os
-    from precache import precached_verbs
-except ImportError, e:
-    precached_verbs = {}
-
-param_type = ['boolean', 'date', 'float', 'integer', 'short', 'list',
-              'long', 'object', 'map', 'string', 'tzdate', 'uuid']
-
-config_dir = expanduser('~/.cloudmonkey')
-config_file = expanduser(config_dir + '/config')
-
-# cloudmonkey config fields
-config_fields = {'core': {}, 'ui': {}, 'server': {}, 'user': {}}
-
-# core
-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']['tabularize'] = 'false'
-
-# server
-config_fields['server']['asyncblock'] = 'true'
-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'] = ''

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/631b6fd4/tools/cli/cloudmonkey/config.py
----------------------------------------------------------------------
diff --git a/tools/cli/cloudmonkey/config.py b/tools/cli/cloudmonkey/config.py
new file mode 100644
index 0000000..b6de641
--- /dev/null
+++ b/tools/cli/cloudmonkey/config.py
@@ -0,0 +1,115 @@
+# -*- 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.1.0-0"
+
+try:
+    import os
+    import sys
+
+    from ConfigParser import ConfigParser, SafeConfigParser
+    from os.path import expanduser
+    from precache import precached_verbs
+except ImportError, e:
+    precached_verbs = {}
+
+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': {}, 'ui': {}, 'server': {}, 'user': {}}
+
+# core
+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']['tabularize'] = 'false'
+
+# server
+config_fields['server']['asyncblock'] = 'true'
+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, first_time=False):
+    global config_fields, config_file
+    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):
+    global config_fields, config_dir, config_file
+    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, True)
+        print "Welcome! Using `set` configure the necessary settings:"
+        print " ".join(sorted(config_options))
+        print "Config file:", config_file
+
+    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.appned(key)
+
+    if len(missing_keys) > 0:
+        print "Please fix `%s` in %s" % (key, config_file)
+        sys.exit()
+
+    return config_options

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/631b6fd4/tools/cli/cloudmonkey/lexer.py
----------------------------------------------------------------------
diff --git a/tools/cli/cloudmonkey/lexer.py b/tools/cli/cloudmonkey/lexer.py
deleted file mode 100644
index 373c9f2..0000000
--- a/tools/cli/cloudmonkey/lexer.py
+++ /dev/null
@@ -1,121 +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(keywords), Keyword),
-            (makelistre(attributes), Literal),
-            (makelistre(params) + r'( = )(.*)', bygroups(Name, Operator,
-                                                         String)),
-            (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):
-        self.encoding = outfile.encoding
-        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)

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/631b6fd4/tools/cli/cloudmonkey/printer.py
----------------------------------------------------------------------
diff --git a/tools/cli/cloudmonkey/printer.py b/tools/cli/cloudmonkey/printer.py
new file mode 100644
index 0000000..40f8473
--- /dev/null
+++ b/tools/cli/cloudmonkey/printer.py
@@ -0,0 +1,133 @@
+# -*- 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
+    import types
+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(keywords), Keyword),
+            (makelistre(attributes), Literal),
+            (makelistre(params) + r'( = )(.*)', bygroups(Name, Operator,
+                                                         String)),
+            (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(color=True, *args):
+    fmter = MonkeyFormatter()
+    lexer = MonkeyLexer()
+    lexer.encoding = 'utf-8'
+    fmter.encoding = 'utf-8'
+    output = ""
+    try:
+        for arg in args:
+            if isinstance(type(arg), types.NoneType):
+                continue
+            output += str(arg)
+    except Exception, e:
+        print e
+
+    if color:
+        highlight(output, lexer, fmter, sys.stdout)
+    else:
+        print output

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/631b6fd4/tools/cli/cloudmonkey/requester.py
----------------------------------------------------------------------
diff --git a/tools/cli/cloudmonkey/requester.py b/tools/cli/cloudmonkey/requester.py
new file mode 100644
index 0000000..5c4cd1e
--- /dev/null
+++ b/tools/cli/cloudmonkey/requester.py
@@ -0,0 +1,153 @@
+#!/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
+
+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: str.lower(x[0]))
+
+    request_url = "&".join(["=".join([r[0], urllib.quote_plus(str(r[1]))])
+                           for r in request])
+    hashStr = "&".join(["=".join([str.lower(r[0]),
+                       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 Exception, e:
+        error = str(e)
+
+    logger_debug(logger, "Response received: %s" % response)
+    if error is not None:
+        logger_debug(logger, 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", 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 = 3
+        progress = 1
+        while timeout > 0:
+            print '\r' + '.' * progress,
+            time.sleep(pollperiod)
+            timeout = timeout - pollperiod
+            progress += 1
+            logger_debug(logger, "Job %s to timeout in %ds" % (jobid, timeout))
+            sys.stdout.flush()
+            response, error = monkeyrequest(command, request, isasync,
+                                            asyncblock, logger,
+                                            host, port,  apikey, secretkey,
+                                            timeout, protocol, path)
+            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',
+                return response, error
+        error = "Error: Async query timeout occurred for jobid %s" % jobid
+
+    return response, error

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/631b6fd4/tools/cli/pom.xml
----------------------------------------------------------------------
diff --git a/tools/cli/pom.xml b/tools/cli/pom.xml
index aba5ec3..582cc57 100644
--- a/tools/cli/pom.xml
+++ b/tools/cli/pom.xml
@@ -72,7 +72,7 @@
             </configuration>
           </execution>
           <execution>
-            <id>cachegen</id>
+            <id>cachemaker</id>
             <phase>compile</phase>
             <goals>
               <goal>exec</goal>
@@ -81,7 +81,7 @@
               <workingDirectory>${basedir}/cloudmonkey</workingDirectory>
               <executable>python</executable>
               <arguments>
-                  <argument>cachegen.py</argument>
+                  <argument>cachemaker.py</argument>
               </arguments>
             </configuration>
           </execution>