PageRenderTime 46ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/apps/releasemanager/lib.py

https://github.com/ITIDO/gfatm
Python | 373 lines | 365 code | 8 blank | 0 comment | 1 complexity | 392899ffb55586a41a3ada9b48429a27 MD5 | raw file
  1. import sys
  2. import os
  3. import urllib2
  4. import tempfile
  5. import shutil
  6. import re
  7. import shlex
  8. from django.middleware.common import *
  9. import tempfile as tmp
  10. from zipfile import ZipFile
  11. from cStringIO import StringIO
  12. from subprocess import Popen, PIPE
  13. from releasemanager import xformvalidator
  14. from django.conf import settings
  15. from releasemanager.jar import extract_xforms
  16. from xformmanager.models import FormDefModel
  17. from xformmanager.manager import XFormManager
  18. import logging
  19. import traceback
  20. from xformmanager.xformdef import FormDef
  21. from releasemanager.exceptions import XFormConflictError, FormReleaseError
  22. UNKNOWN_IP = "0.0.0.0"
  23. def rlistdir(start_path, paths=[], prepend='', ignore_hidden=True):
  24. ''' list dirs recursively '''
  25. paths = []
  26. for f in os.listdir(start_path):
  27. if ignore_hidden and f.startswith('.'): continue
  28. full_path = os.path.join(start_path, f)
  29. if os.path.isdir(full_path):
  30. rlistdir(full_path, paths, f)
  31. paths.append(os.path.join(prepend, f))
  32. return paths
  33. def add_to_jar(jar_file, path_to_add, application_name=''):
  34. '''adds files under /path_to_add to jar_file, return path to the new JAR'''
  35. if not os.path.isdir(path_to_add):
  36. raise "Trying to add non-existant directory '%s' to JAR" % str(path_to_add)
  37. if not jar_file.endswith('.jar') or not os.path.isfile(jar_file):
  38. raise "'%s' isn't a JAR file" % jar_file
  39. newjar_filename = os.path.join(tmp.mkdtemp(), os.path.basename(jar_file))
  40. oldjar = ZipFile(jar_file, 'r')
  41. newjar = ZipFile(newjar_filename, 'w')
  42. # Here we do some juggling, since ZipFile doesn't have a delete method
  43. # first, add all the resource set files
  44. files = rlistdir(path_to_add)
  45. for f in files:
  46. full_path = os.path.join(path_to_add, f)
  47. if os.path.isdir(full_path): continue
  48. newjar.write(full_path, str(f))
  49. # Now update the application name in the manifest if relevant
  50. if application_name is not '':
  51. #Define the location of the manifest in either jar
  52. manifest_loc = os.path.join("META-INF","MANIFEST.MF")
  53. #Turn the manifest into a dictionary
  54. manifestdata = jad_to_dict(oldjar.read(manifest_loc))
  55. #Rewrite the midlet name
  56. manifestdata['MIDlet-Name'] = application_name
  57. #Blobify
  58. manifest_file = dict_to_jad(manifestdata)
  59. #Write to new jar
  60. newjar.writestr(manifest_loc, manifest_file)
  61. # now add the JAR files, taking care not to add filenames that already exist in the resource set
  62. existing_files = newjar.namelist()
  63. for f in oldjar.infolist():
  64. if f.filename in existing_files:
  65. continue
  66. buffer = oldjar.read(f.filename)
  67. newjar.writestr(f, buffer)
  68. newjar.close()
  69. oldjar.close()
  70. return newjar_filename
  71. def jad_to_dict(jad_contents):
  72. jad = {}
  73. for line in jad_contents.strip().split("\n"):
  74. key, val = line.split(':',1)
  75. jad[key] = val.strip()
  76. return jad
  77. def dict_to_jad(jad_dict):
  78. # create new JAD data blob
  79. new_content = ''
  80. for i in jad_dict:
  81. if jad_dict[i] is not None:
  82. new_content += "%s: %s\n" % (i, jad_dict[i])
  83. return new_content
  84. def modify_jad(jad_file, modify_dict):
  85. '''
  86. Modify JAD contents. key:value pairs are read from modify_dict
  87. If value is None the key is deleted from the JAD
  88. '''
  89. jad = jad_to_dict(open(jad_file).read())
  90. for i in modify_dict:
  91. jad[i] = modify_dict[i]
  92. # create new JAD
  93. new_content = dict_to_jad(jad)
  94. f = open(jad_file, 'w')
  95. f.write(new_content)
  96. f.close()
  97. return jad_file
  98. def create_zip(target, jar_file, jad_file):
  99. ''' create zip from the jar & jad '''
  100. target = str(target) ; jar_file = str(jar_file) ; jad_file = str(jad_file)
  101. zf = ZipFile(target, 'w')
  102. zf.write(jar_file, os.path.basename(jar_file))
  103. zf.write(jad_file, os.path.basename(jad_file))
  104. zf.close()
  105. return target
  106. # #### Deprecating ZIP URL #####
  107. # # http://bitbucket.org/ctsims/resourcetest/get/tip.zip
  108. # def grab_from(url):
  109. # '''copy a file from a URL to a local tmp dir, returns path to local copy'''
  110. # u = urllib2.urlopen(url)
  111. # u = u.read()
  112. #
  113. # x, tmpfile = tmp.mkstemp()
  114. # f = open(tmpfile, 'w')
  115. # f.write(u)
  116. # f.close()
  117. #
  118. # return tmpfile
  119. #
  120. #
  121. # def unzip(zip_file, target_dir=None):
  122. # '''
  123. # extracts a resources zip.
  124. # assumes that all files are on one root dir
  125. # returns path of extracted files
  126. # '''
  127. # zf = ZipFile(zip_file)
  128. # if target_dir is None:
  129. # target_dir = tmp.mkdtemp()
  130. # elif not os.path.exists(target_dir):
  131. # os.makedirs(target_dir)
  132. #
  133. # namelist = zf.namelist()
  134. # filelist = filter( lambda x: not x.endswith( '/' ), namelist )
  135. #
  136. # for filename in filelist:
  137. # basename = os.path.basename(filename)
  138. # if basename.startswith('.'): continue #filename.endswith(".xml") or filename.endswith(".xhtml"):
  139. #
  140. # target_file = os.path.join(target_dir, basename)
  141. #
  142. # out = open(target_file, 'wb')
  143. #
  144. # buffer = StringIO( zf.read(filename))
  145. # buflen = 2 ** 20
  146. # datum = buffer.read(buflen)
  147. #
  148. # while datum:
  149. # out.write(datum)
  150. # datum = buffer.read(buflen)
  151. # out.close()
  152. #
  153. # return target_dir
  154. def clone_from(url):
  155. if re.match(r'https?:\/\/.*bitbucket.org\/', url) is not None:
  156. # hg won't clone to an existing directory, and tempfile won't return just a name without creating a dir
  157. # so just delete the new tmp dir and let hg recreate it in clone
  158. tmpdir = tmp.mkdtemp()
  159. os.rmdir(tmpdir)
  160. # obviously, this depends on a particular URL format.
  161. # if we stick with bitbucket, standardize on an expected URL.
  162. hg_root, path = url.split('/src')
  163. path = path.replace('/tip', '')
  164. path = path.lstrip('/') # dont confuse os.path.join
  165. clone_cmd = ["hg", "clone", hg_root, tmpdir]
  166. p = Popen(clone_cmd, stdout=PIPE, stderr=PIPE, shell=False)
  167. err = p.stderr.read().strip()
  168. if err != '': raise err
  169. return os.path.join(tmpdir, path)
  170. else:
  171. raise "Unknown SCM URL"
  172. # unused for now. move it later to a short_url app as HQ-wide service.
  173. def get_bitly_url_for(url):
  174. try:
  175. bitly_login = settings.RAPIDSMS_APPS['releasemanager']['bitly_login']
  176. bitly_apikey = settings.RAPIDSMS_APPS['releasemanager']['bitly_apikey']
  177. except:
  178. return False
  179. bitly_url = "http://api.bit.ly/v3/shorten?login=dmgi&apiKey=R_af7d5c0d899197fe43e18acceebd5cdb&uri=%s&format=txt" % url
  180. u = urllib2.urlopen(bitly_url)
  181. short_url = u.read().strip()
  182. if short_url == '': return False
  183. return short_url
  184. def validate_resources(resources):
  185. """
  186. Validate a list of resources, currently by trying to parse everything
  187. like an xform and then register it. Returns a dictionary of resource names
  188. with values either None if validaiton was successful, otherwise the
  189. exception that was thrown during validation.
  190. """
  191. errors = {}
  192. for file in os.listdir(resources):
  193. if file.endswith(".xml") or file.endswith(".xhtml"):
  194. try:
  195. xformvalidator.validate(os.path.join(resources, file))
  196. errors[file] = None
  197. logging.debug("%s validates successfully" % file)
  198. except Exception, e:
  199. errors[file] = e
  200. return errors
  201. def register_forms(build, good_forms):
  202. """Try to register the forms from jar_file."""
  203. # this was copied and modified from buildmanager models.
  204. errors = {}
  205. to_skip = []
  206. to_register = []
  207. path = tempfile.tempdir
  208. xforms = extract_xforms(build.jar_file, path)
  209. for form in xforms:
  210. if os.path.basename(form) not in good_forms:
  211. logging.debug("skipping %s, not a good form" % form)
  212. continue
  213. try:
  214. formdef = xformvalidator.validate(form)
  215. modelform = FormDefModel.get_model(formdef.target_namespace,
  216. build.resource_set.domain,
  217. formdef.version)
  218. if modelform:
  219. # if the model form exists we must ensure it is compatible
  220. # with the version we are trying to release
  221. existing_formdef = modelform.to_formdef()
  222. differences = existing_formdef.get_differences(formdef)
  223. if differences.is_empty():
  224. # this is all good
  225. to_skip.append(form)
  226. else:
  227. raise XFormConflictError(("Schema %s is not compatible with %s."
  228. "Because of the following differences:"
  229. "%s"
  230. "You must update your version number!")
  231. % (existing_formdef, formdef, differences))
  232. else:
  233. # this must be registered
  234. to_register.append(form)
  235. except FormDef.FormDefError, e:
  236. # we'll allow warnings through
  237. if e.category == FormDef.FormDefError.WARNING: pass
  238. else: errors[form] = e
  239. except Exception, e:
  240. info = sys.exc_info()
  241. logging.error("Error preprocessing form in build manager: %s\n%s" % \
  242. (e, traceback.print_tb(info[2])))
  243. errors[form] = e
  244. if errors:
  245. return errors
  246. # finally register
  247. manager = XFormManager()
  248. # TODO: we need transaction management
  249. for form in to_register:
  250. try:
  251. formdefmodel = manager.add_schema(os.path.basename(form),
  252. open(form, "r"),
  253. build.resource_set.domain)
  254. # TODO, find better values for these?
  255. formdefmodel.submit_ip = UNKNOWN_IP
  256. user = None
  257. formdefmodel.uploaded_by = user
  258. formdefmodel.bytes_received = len(form)
  259. formdefmodel.form_display_name = os.path.basename(form)
  260. formdefmodel.save()
  261. errors[form] = None
  262. except Exception, e:
  263. # log the error with the stack, otherwise this is hard to track down
  264. info = sys.exc_info()
  265. logging.error("Error registering form in build manager: %s\n%s" % \
  266. (e, traceback.print_tb(info[2])))
  267. errors[form] = FormReleaseError("%s" % e)
  268. return errors
  269. def sign_jar(jar_file, jad_file):
  270. ''' run jadTool on the newly created JAR '''
  271. jad_tool = settings.RAPIDSMS_APPS['releasemanager']['jadtool_path']
  272. key_store = settings.RAPIDSMS_APPS['releasemanager']['key_store']
  273. key_alias = settings.RAPIDSMS_APPS['releasemanager']['key_alias']
  274. store_pass = settings.RAPIDSMS_APPS['releasemanager']['store_pass']
  275. key_pass = settings.RAPIDSMS_APPS['releasemanager']['key_pass']
  276. # prevent linux unicode errors
  277. jar_file = str(jar_file)
  278. jad_file = str(jad_file)
  279. # remove traces of former jar signings, if any
  280. modify_jad(jad_file, {
  281. "MIDlet-Certificate-1-1" : None,
  282. "MIDlet-Certificate-1-2" : None,
  283. "MIDlet-Certificate-1-3" : None,
  284. "MIDlet-Jar-RSA-SHA1" : None,
  285. "MIDlet-Permissions" : None
  286. })
  287. step_one = "java -jar %s -addjarsig -jarfile %s -alias %s -keystore %s -storepass %s -keypass %s -inputjad %s -outputjad %s" % \
  288. (jad_tool, jar_file, key_alias, key_store, store_pass, key_pass, jad_file, jad_file)
  289. step_two = "java -jar %s -addcert -alias %s -keystore %s -storepass %s -inputjad %s -outputjad %s" % \
  290. (jad_tool, key_alias, key_store, store_pass, jad_file, jad_file)
  291. for step in (step_one, step_two):
  292. p = Popen(shlex.split(step), stdout=PIPE, stderr=PIPE, shell=False)
  293. err = p.stderr.read().strip()
  294. if err != '': raise err
  295. jad_file = modify_jad(jad_file, {
  296. "MIDlet-Permissions" : "javax.microedition.io.Connector.file.read,javax.microedition.io.Connector.ssl,javax.microedition.io.Connector.file.write,javax.microedition.io.Connector.comm,javax.microedition.io.Connector.http,javax.microedition.io.Connector.https"
  297. })
  298. return jar_file, jad_file