From 066d94f6f92ce1c0d449ce8f0d7f6a44f09914a9 Mon Sep 17 00:00:00 2001 From: edison Date: Tue, 26 Oct 2010 18:05:11 -0700 Subject: [PATCH] Add cloud-tool into FOSS --- client/tomcatconf/commands.properties.in | 4 +- cloud-cli/bindir/cloud-tool | 11 ++ cloud-cli/cloudapis/__init__.py | 25 +++ cloud-cli/cloudapis/amazon.py | 63 ++++++ cloud-cli/cloudapis/cloud.py | 113 +++++++++++ cloud-cli/cloudtool/__init__.py | 51 +++++ cloud-cli/cloudtool/utils.py | 185 ++++++++++++++++++ cloud.spec | 14 ++ .../BuildCommandLineInputFile.java | 166 ++++++++++++++++ wscript_build | 25 +++ wscript_configure | 4 + 11 files changed, 659 insertions(+), 2 deletions(-) create mode 100755 cloud-cli/bindir/cloud-tool create mode 100644 cloud-cli/cloudapis/__init__.py create mode 100644 cloud-cli/cloudapis/amazon.py create mode 100644 cloud-cli/cloudapis/cloud.py create mode 100644 cloud-cli/cloudtool/__init__.py create mode 100644 cloud-cli/cloudtool/utils.py create mode 100644 utils/src/com/cloud/utils/commandlinetool/BuildCommandLineInputFile.java diff --git a/client/tomcatconf/commands.properties.in b/client/tomcatconf/commands.properties.in index e1989cbc6a3..7e831e13054 100755 --- a/client/tomcatconf/commands.properties.in +++ b/client/tomcatconf/commands.properties.in @@ -210,8 +210,8 @@ authorizeNetworkGroupIngress=com.cloud.api.commands.AuthorizeNetworkGroupIngress revokeNetworkGroupIngress=com.cloud.api.commands.RevokeNetworkGroupIngressCmd;11 listNetworkGroups=com.cloud.api.commands.ListNetworkGroupsCmd;11 -registerPreallocatedLun=com.cloud.server.api.commands.RegisterPreallocatedLunCmd;1 -deletePreallocatedLun=com.cloud.server.api.commands.DeletePreallocatedLunCmd;1 +registerPreallocatedLun=com.cloud.api.commands.RegisterPreallocatedLunCmd;1 +deletePreallocatedLun=com.cloud.api.commands.DeletePreallocatedLunCmd;1 listPreallocatedLuns=com.cloud.api.commands.ListPreallocatedLunsCmd;1 #### vm group commands diff --git a/cloud-cli/bindir/cloud-tool b/cloud-cli/bindir/cloud-tool new file mode 100755 index 00000000000..4eb33837dbc --- /dev/null +++ b/cloud-cli/bindir/cloud-tool @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +import sys +import os + +sys.path.append(os.path.dirname(os.path.dirname(__file__))) + +import cloudtool + +ret = cloudtool.main() +if ret: sys.exit(ret) diff --git a/cloud-cli/cloudapis/__init__.py b/cloud-cli/cloudapis/__init__.py new file mode 100644 index 00000000000..5fd9a7ad1f3 --- /dev/null +++ b/cloud-cli/cloudapis/__init__.py @@ -0,0 +1,25 @@ +''' +Created on Aug 2, 2010 + +@author: rudd-o +''' + +import os,pkgutil + +def get_all_apis(): + apis = [] + for x in pkgutil.walk_packages([os.path.dirname(__file__)]): + loader = x[0].find_module(x[1]) + try: module = loader.load_module("cloudapis." + x[1]) + except ImportError: continue + apis.append(module) + return apis + +def lookup_api(api_name): + api = None + matchingapi = [ x for x in get_all_apis() if api_name.replace("-","_") == x.__name__.split(".")[-1] ] + if not matchingapi: api = None + else: api = matchingapi[0] + if api: api = getattr(api,"implementor") + return api + diff --git a/cloud-cli/cloudapis/amazon.py b/cloud-cli/cloudapis/amazon.py new file mode 100644 index 00000000000..c6911d6ed2d --- /dev/null +++ b/cloud-cli/cloudapis/amazon.py @@ -0,0 +1,63 @@ +'''Implements the Amazon API''' + + +import boto.ec2 +import os +from cloudtool.utils import describe,OptionConflictError,OptionValueError +raise ImportError + +class AmazonAPI: + + @describe("access_key", "Amazon access key") + @describe("secret_key", "Amazon secret key") + @describe("region", "Amazon region") + @describe("endpoint", "Amazon endpoint") + def __init__(self, + access_key=os.environ.get("AWS_ACCESS_KEY_ID",None), + secret_key=os.environ.get("AWS_SECRET_ACCESS_KEY",None), + region=None, + endpoint=None): + if not access_key: raise OptionValueError,"you need to specify an access key" + if not secret_key: raise OptionValueError,"you need to specify a secret key" + if region and endpoint: + raise OptionConflictError,("mutually exclusive with --endpoint",'--region') + self.__dict__.update(locals()) + + def _get_regions(self): + return boto.ec2.regions(aws_access_key_id=self.access_key,aws_secret_access_key=self.secret_key) + + def _get_region(self,name): + try: return [ x for x in self._get_regions() if x.name == name ][0] + except IndexError: raise KeyError,name + + def _connect(self): + if self.region: + region = self._get_region(self.region) + self.connection = region.connect( + aws_access_key_id=self.access_key, + aws_secret_access_key=self.secret_key + ) + else: + self.connection = boto.ec2.connection.EC2Connection( + host=self.endpoint, + aws_access_key_id=self.access_key, + aws_secret_access_key=self.secret_key + ) + def list_regions(self): + """Lists all regions""" + regions = self._get_regions() + for r in regions: print r + + def get_all_images(self): + """Lists all images""" + self._connect() + images = self.connection.get_all_images() + for i in images: print i + + def get_region(self): + """Gets the region you're connecting to""" + self._connect() + print self.connection.region + + +implementor = AmazonAPI \ No newline at end of file diff --git a/cloud-cli/cloudapis/cloud.py b/cloud-cli/cloudapis/cloud.py new file mode 100644 index 00000000000..69abf9ef17f --- /dev/null +++ b/cloud-cli/cloudapis/cloud.py @@ -0,0 +1,113 @@ +'''Implements the Cloud.com API''' + + +from cloudtool.utils import describe +import urllib +import urllib2 +import os +import xml.dom.minidom + +class CloudAPI: + + @describe("server", "Management Server host name or address") + @describe("responseformat", "Response format: xml or json") + def __init__(self, + server="127.0.0.1:8096", + responseformat="xml", + ): + self.__dict__.update(locals()) + + def _make_request(self,command,parameters=None): + '''Command is a string, parameters is a dictionary''' + if ":" in self.server: + host,port = self.server.split(":") + port = int(port) + else: + host = self.server + port = 8096 + + url = "http://" + self.server + "/?" + + if not parameters: parameters = {} + parameters["command"] = command + parameters["response"] = self.responseformat + querystring = urllib.urlencode(parameters) + url += querystring + + f = urllib2.urlopen(url) + + data = f.read() + + return data + + +def load_dynamic_methods(): + '''creates smart function objects for every method in the commands.xml file''' + + def getText(nodelist): + rc = [] + for node in nodelist: + if node.nodeType == node.TEXT_NODE: rc.append(node.data) + return ''.join(rc) + + # FIXME figure out installation and packaging + xmlfile = os.path.join("/etc/cloud/cli/","commands.xml") + dom = xml.dom.minidom.parse(xmlfile) + + for cmd in dom.getElementsByTagName("command"): + name = getText(cmd.getElementsByTagName('name')[0].childNodes).strip() + assert name + + description = cmd.getElementsByTagName('name')[0].getAttribute("description") + if description: description = '"""%s"""' % description + else: description = '' + arguments = [] + options = [] + descriptions = [] + + for param in cmd.getElementsByTagName('arg'): + argname = getText(param.childNodes).strip() + assert argname + + required = param.getAttribute("required").strip() + if required == 'true': required = True + elif required == 'false': required = False + else: raise AssertionError, "Not reached" + if required: arguments.append(argname) + options.append(argname) + + description = param.getAttribute("description").strip() + if description: descriptions.append( (argname,description) ) + + funcparams = ["self"] + [ "%s=None"%o for o in options ] + funcparams = ", ".join(funcparams) + + code = """ + def %s(%s): + %s + parms = locals() + del parms["self"] + for arg in %r: + if locals()[arg] is None: + raise TypeError, "%%s is a required option"%%arg + for k,v in parms.items(): + if v is None: del parms[k] + output = self._make_request("%s",parms) + return output + """%(name,funcparams,description,arguments,name) + + namespace = {} + exec code.strip() in namespace + + func = namespace[name] + for argname,description in descriptions: + func = describe(argname,description)(func) + + yield (name,func) + + +for name,meth in load_dynamic_methods(): setattr(CloudAPI,name,meth) + +implementor = CloudAPI + +del name,meth,describe,load_dynamic_methods diff --git a/cloud-cli/cloudtool/__init__.py b/cloud-cli/cloudtool/__init__.py new file mode 100644 index 00000000000..e6d00be5fe1 --- /dev/null +++ b/cloud-cli/cloudtool/__init__.py @@ -0,0 +1,51 @@ +''' +Created on Aug 2, 2010 + +@author: rudd-o +''' + +import sys +import cloudapis as apis +import cloudtool.utils as utils + + +def main(argv=None): + + if argv == None: + argv = sys.argv + + prelim_args = [ x for x in argv[1:] if not x.startswith('-') ] + parser = utils.get_parser() + + api = __import__("cloudapis") + apis = getattr(api, "implementor") + if len(prelim_args) == 0: + parser.error("you need to specify an API as the first argument\n\nSupported APIs:\n" + "\n".join(utils.get_api_list())) + elif len(prelim_args) == 1: + commandlist = utils.get_command_list(apis) + parser.error("you need to specify a command name as the second argument\n\nCommands supported by the %s API:\n"%prelim_args[0] + "\n".join(commandlist)) + + command = utils.lookup_command_in_api(apis,prelim_args[1]) + if not command: parser.error("command %r not supported by the %s API"%(prelim_args[1],prelim_args[0])) + + parser = utils.get_parser(apis.__init__,command) + argv = argv[1:] + opts,args,api_optionsdict,cmd_optionsdict = parser.parse_args(argv) + + + try: + api = apis(**api_optionsdict) + except utils.OptParseError,e: + parser.error(str(e)) + + command = utils.lookup_command_in_api(api,args[1]) + + # we now discard the first two arguments as those necessarily are the api and command names + args = args[2:] + + try: return command(*args,**cmd_optionsdict) + except TypeError,e: parser.error(str(e)) + + +if __name__ == '__main__': + main(argv) diff --git a/cloud-cli/cloudtool/utils.py b/cloud-cli/cloudtool/utils.py new file mode 100644 index 00000000000..2bcef9569c3 --- /dev/null +++ b/cloud-cli/cloudtool/utils.py @@ -0,0 +1,185 @@ +''' +Created on Aug 2, 2010 + +@author: rudd-o +''' + + +import sys +import os +import inspect +from optparse import OptionParser, OptParseError, BadOptionError, OptionError, OptionConflictError, OptionValueError +import cloudapis as apis + + +def describe(name,desc): + def inner(decoratee): + if not hasattr(decoratee,"descriptions"): decoratee.descriptions = {} + decoratee.descriptions[name] = desc + return decoratee + return inner + + +def error(msg): + sys.stderr.write(msg) + sys.stderr.write("\n") + + +class MyOptionParser(OptionParser): + def error(self, msg): + error("%s: %s\n" % (self.get_prog_name(),msg)) + self.print_usage(sys.stderr) + self.exit(os.EX_USAGE) + + def parse_args(self,*args,**kwargs): + options,arguments = OptionParser.parse_args(self,*args,**kwargs) + + def prune_options(options,alist): + """Given 'options' -- a list of arguments to OptionParser.add_option, + and a set of optparse Values, return a dictionary of only those values + that apply exclusively to 'options'""" + return dict( [ (k,getattr(options,k)) for k in dir(options) if k in alist ] ) + + api_options = prune_options(options,self.api_dests) + cmd_options = prune_options(options,self.cmd_dests) + + return options,arguments,api_options,cmd_options + + +def get_parser(api_callable=None,cmd_callable=None): # this should probably be the __init__ method of myoptionparser + + def getdefaulttag(default): + if default is not None: return " [Default: %default]" + return '' + + def get_arguments_and_options(callable): + """Infers and returns arguments and options based on a callable's signature. + Cooperates with decorator @describe""" + try: + funcargs = inspect.getargspec(callable).args + defaults = inspect.getargspec(callable).defaults + except: + funcargs = inspect.getargspec(callable)[0] + defaults = inspect.getargspec(callable)[3] + if not defaults: defaults = [] + args = funcargs[1:len(funcargs)-len(defaults)] # this assumes self, so assumes methods + opts = funcargs[len(funcargs)-len(defaults):] + try: descriptions = callable.descriptions + except AttributeError: descriptions = {} + arguments = [ (argname, descriptions.get(argname,'') ) for argname in args ] + options = [ [ + ("--%s"%argname.replace("_","-"),), + { + "dest":argname, + "help":descriptions.get(argname,'') + getdefaulttag(default), + "default":default, + } + ] for argname,default in zip(opts,defaults) ] + return arguments,options + + basic_usage = "usage: %prog [options...] " + + api_name = "" + cmd_name = "" + description = "%prog is a command-line tool to access several cloud APIs." + arguments = '' + argexp = "" + + if api_callable: + api_name = api_callable.__module__.split(".")[-1].replace("_","-") + api_arguments,api_options = get_arguments_and_options(api_callable) + assert len(api_arguments) is 0 # no mandatory arguments for class initializers + + if cmd_callable: + cmd_name = cmd_callable.func_name.replace("_","-") + cmd_arguments,cmd_options = get_arguments_and_options(cmd_callable) + if cmd_arguments: + arguments = " " + " ".join( [ s[0].upper() for s in cmd_arguments ] ) + argexp = "\n\nArguments:\n" + "\n".join ( " %s\n %s"%(s.upper(),u) for s,u in cmd_arguments ) + description = cmd_callable.__doc__ + + api_command = "%s %s"%(api_name,cmd_name) + + if description: description = "\n\n" + description + else: description = '' + + usage = basic_usage + api_command + arguments + description + argexp + + parser = MyOptionParser(usage=usage, add_help_option=False) + + parser.add_option('--help', action="help") + + group = parser.add_option_group("General options") + group.add_option('-v', '--verbose', dest="verbose", help="Print extra output") + + # now we need to derive the short options. we initialize the known fixed longopts and shortopts + longopts = [ '--verbose' ] + + # we add the known long options, sorted and grouped to guarantee a stable short opt set regardless of order + if api_callable and api_options: + longopts += sorted([ x[0][0] for x in api_options ]) + if cmd_callable and cmd_options: + longopts += sorted([ x[0][0] for x in cmd_options ]) + + # we use this function to derive a suitable short option and remember the already-used short options + def derive_shortopt(longopt,usedopts): + """longopt begins with a dash""" + shortopt = None + for x in xrange(2,10000): + try: shortopt = "-" + longopt[x] + except IndexError: + shortopt = None + break + if shortopt in usedopts: continue + usedopts.append(shortopt) + break + return shortopt + + # now we loop through the long options and assign a suitable short option, saving the short option for later use + long_to_short = {} + alreadyusedshorts = [] + for longopt in longopts: + long_to_short[longopt] = derive_shortopt(longopt,alreadyusedshorts) + + parser.api_dests = [] + if api_callable and api_options: + group = parser.add_option_group("Options for the %s API"%api_name) + for a in api_options: + shortopt = long_to_short[a[0][0]] + if shortopt: group.add_option(shortopt,a[0][0],**a[1]) + else: group.add_option(a[0][0],**a[1]) + parser.api_dests.append(a[1]["dest"]) + + parser.cmd_dests = [] + if cmd_callable and cmd_options: + group = parser.add_option_group("Options for the %s command"%cmd_name) + for a in cmd_options: + shortopt = long_to_short[a[0][0]] + if shortopt: group.add_option(shortopt,a[0][0],**a[1]) + else: group.add_option(a[0][0],**a[1]) + parser.cmd_dests.append(a[1]["dest"]) + + return parser + +def lookup_command_in_api(api,command_name): + command = getattr(api,command_name.replace("-","_"),None) + return command + +def get_api_list(): + apilist = [] + for api in apis.get_all_apis(): + api_module = api + api_name = api.__name__.split(".")[-1] + if not api_name.startswith("_") and hasattr(api_module,'__doc__'): + apilist.append( " %20s %s"%(api_name.replace("_",'-'),api_module.__doc__) ) + return apilist + +def get_command_list(api): + cmds = [] + for cmd_name in dir(api): + cmd = getattr(api,cmd_name) + if callable(cmd) and not cmd_name.startswith("_"): + if cmd.__doc__: docstring = cmd.__doc__ + else: docstring = '' + cmds.append( " %s %s"%(cmd_name.replace('_','-'),docstring) ) + return cmds diff --git a/cloud.spec b/cloud.spec index cebe04292e0..9197f7ae8e7 100644 --- a/cloud.spec +++ b/cloud.spec @@ -274,6 +274,13 @@ Group: System Environment/Libraries The Cloud.com console proxy is the service in charge of granting console access into virtual machines managed by the Cloud.com CloudStack. +%package cli +Summary: Cloud.com command line tools +Requires: python +Group: System Environment/Libraries +%description cli +The Cloud.com command line tools contain a few Python modules that can call cloudStack APIs. + %if %{_premium} @@ -619,6 +626,13 @@ fi %attr(0755,root,root) %{_bindir}/%{name}-setup-console-proxy %dir %attr(770,root,root) %{_localstatedir}/log/%{name}/console-proxy +%files cli +%{_bindir}/%{name}-tool +%{_sysconfdir}/%{name}/cli/commands.xml +%dir %{_prefix}/lib*/python*/site-packages/%{name}tool +%{_prefix}/lib*/python*/site-packages/%{name}tool/* +%{_prefix}/lib*/python*/site-packages/%{name}apis.py + %if %{_premium} %files test diff --git a/utils/src/com/cloud/utils/commandlinetool/BuildCommandLineInputFile.java b/utils/src/com/cloud/utils/commandlinetool/BuildCommandLineInputFile.java new file mode 100644 index 00000000000..f2a77615b9f --- /dev/null +++ b/utils/src/com/cloud/utils/commandlinetool/BuildCommandLineInputFile.java @@ -0,0 +1,166 @@ +/** + * Copyright (C) 2010 Cloud.com, Inc. All rights reserved. + * + * This software is licensed under the GNU General Public License v3 or later. + * + * It is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package com.cloud.utils.commandlinetool; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.Properties; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Text; + +import com.cloud.utils.Pair; + +public class BuildCommandLineInputFile { + private static Properties api_commands = new Properties(); + private static String dirName=""; + + public static void main (String[] args) { + Properties preProcessedCommands = new Properties(); + Class clas = null; + Enumeration e = null; + String[] fileNames = null; + + //load properties + List argsList = Arrays.asList(args); + Iterator iter = argsList.iterator(); + while (iter.hasNext()) { + String arg = iter.next(); + // populate the file names + if (arg.equals("-f")) { + fileNames = iter.next().split(","); + } + if (arg.equals("-d")) { + dirName = iter.next(); + } + } + + if ((fileNames == null) || (fileNames.length == 0)){ + System.out.println("Please specify input file(s) separated by coma using -f option"); + System.exit(2); + } + + for (String fileName : fileNames) { + try { + FileInputStream in = new FileInputStream(fileName); + preProcessedCommands.load(in); + }catch (FileNotFoundException ex) { + System.out.println("Can't find file " + fileName); + System.exit(2); + } catch (IOException ex1) { + System.out.println("Error reading from file " + ex1); + System.exit(2); + } + } + + + for (Object key : preProcessedCommands.keySet()) { + String preProcessedCommand = preProcessedCommands.getProperty((String)key); + String[] commandParts = preProcessedCommand.split(";"); + api_commands.put(key, commandParts[0]); + } + + + e = api_commands.propertyNames(); + + try { + DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); + Document doc = docBuilder.newDocument(); + Element root = doc.createElement("commands"); + doc.appendChild(root); + + while (e.hasMoreElements()) { + String key = (String) e.nextElement(); + try { + clas = Class.forName(api_commands.getProperty(key)); + Element child1 = doc.createElement("command"); + root.appendChild(child1); + Element child2 = doc.createElement("name"); + child1.appendChild(child2); + Text text = doc.createTextNode(key); + child2.appendChild(text); + + Field m[] = clas.getDeclaredFields(); + for (int i = 0; i < m.length; i++) { + if (m[i].getName().endsWith("s_properties")) { + m[i].setAccessible(true); + List> properties = (List>) m[i].get(null); + for (Pair property : properties){ + if (!property.first().toString().equals("ACCOUNT_OBJ") && !property.first().toString().equals("USER_ID")){ + Element child3 = doc.createElement("arg"); + child1.appendChild(child3); + Class clas2 = property.first().getClass(); + Method m2 = clas2.getMethod("getName"); + text = doc.createTextNode(m2.invoke(property.first()).toString()); + child3.appendChild(text); + child3.setAttribute("required", property.second().toString()); + } + } + } + } + } catch (ClassNotFoundException ex2) { + System.out.println("Can't find class " + api_commands.getProperty(key)); + System.exit(2); + } + } + TransformerFactory transfac = TransformerFactory.newInstance(); + Transformer trans = transfac.newTransformer(); + trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + trans.setOutputProperty(OutputKeys.INDENT, "yes"); + + StringWriter sw = new StringWriter(); + StreamResult result = new StreamResult(sw); + DOMSource source = new DOMSource(doc); + trans.transform(source, result); + String xmlString = sw.toString(); + + //write xml to file + File f=new File(dirName + "/commands.xml"); + Writer output = new BufferedWriter(new FileWriter(f)); + output.write(xmlString); + output.close(); + } catch (Exception ex) { + System.out.println(ex); + System.exit(2); + } + } + +} \ No newline at end of file diff --git a/wscript_build b/wscript_build index 943e2b037c0..6cb90d4afcd 100644 --- a/wscript_build +++ b/wscript_build @@ -412,6 +412,31 @@ for vendor in _glob(_join("vendor","*")) + _glob(_join("cloudstack-proprietary", # ====================== End vendor-specific plugins ==================== +def generate_xml_api_description(task): + relationship = Utils.relpath(sourcedir,os.getcwd()) + cp = [ _join(relationship,x) for x in task.generator.env.CLASSPATH.split(pathsep) ] + buildproducts = [ x.bldpath(task.env) for x in task.inputs ] + jars = [ x for x in buildproducts if x.endswith("jar") ] + properties = [ x for x in buildproducts if x.endswith("properties") ] + cp += jars + cp = pathsep.join(cp) + arguments = ["-f",",".join(properties),"-d",builddir] + ret = Utils.exec_command(["java","-cp",cp,"com.cloud.utils.commandlinetool.BuildCommandLineInputFile"]+arguments,log=True) + return ret +props = " client/tomcatconf/commands.properties" +jarnames = ['utils','server','core', 'api'] +tgen = bld( + rule = generate_xml_api_description, + source = " ".join( [ 'target/jar/cloud-%s.jar'%x for x in jarnames ] ) + props, + target = 'commands.xml', + name = 'xmlapi', + after = 'runant', + install_path="${CLIDIR}" +) +#bld.process_after(tgen) + +bld.install_files("${PYTHONDIR}/cloudtool", 'cloud-cli/cloudtool/*') +bld.install_as("${PYTHONDIR}/cloudapis.py", 'cloud-cli/cloudapis/cloud.py') # ====================== Magic! ========================================= diff --git a/wscript_configure b/wscript_configure index 60c31b14d2a..31198361bbd 100644 --- a/wscript_configure +++ b/wscript_configure @@ -273,6 +273,8 @@ conf.env.CPLIBDIR = Utils.subst_vars(_join("${LIBDIR}","${CPPATH}"),conf.env) conf.env.CPSYSCONFDIR = Utils.subst_vars(_join("${SYSCONFDIR}","${CPPATH}"),conf.env) conf.env.CPLOGDIR = Utils.subst_vars(_join("${LOCALSTATEDIR}","log","${CPPATH}"),conf.env) +conf.env.CLIPATH = _join(conf.env.PACKAGE,"cli") + conf.env.IPALLOCATORLIBDIR = Utils.subst_vars(_join("${LIBDIR}","${IPALLOCATORPATH}"),conf.env) conf.env.IPALLOCATORSYSCONFDIR = Utils.subst_vars(_join("${SYSCONFDIR}","${IPALLOCATORPATH}"),conf.env) conf.env.IPALLOCATORLOGDIR = Utils.subst_vars(_join("${LOCALSTATEDIR}","log","${IPALLOCATORPATH}"),conf.env) @@ -286,6 +288,8 @@ conf.env.IPALOCATORLOG = _join(conf.env.IPALLOCATORLOGDIR,"ipallocator.log") conf.env.SETUPDATADIR = Utils.subst_vars(_join("${DATADIR}","${SETUPPATH}"),conf.env) +conf.env.CLIDIR = Utils.subst_vars(_join("${SYSCONFDIR}","${CLIPATH}"),conf.env) + conf.env.SERVERSYSCONFDIR = Utils.subst_vars(_join("${SYSCONFDIR}","${SERVERPATH}"),conf.env) if conf.env.DISTRO in ["Windows"]: