PageRenderTime 53ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/indico/MaKaC/consoleScripts/installBase.py

https://github.com/ferhatelmas/indico
Python | 594 lines | 507 code | 26 blank | 61 comment | 34 complexity | 13fd8bbc39760d70a4ee71bfe04efe42 MD5 | raw file
Possible License(s): GPL-3.0
  1. # -*- coding: utf-8 -*-
  2. ##
  3. ##
  4. ## This file is part of Indico.
  5. ## Copyright (C) 2002 - 2014 European Organization for Nuclear Research (CERN).
  6. ##
  7. ## Indico is free software; you can redistribute it and/or
  8. ## modify it under the terms of the GNU General Public License as
  9. ## published by the Free Software Foundation; either version 3 of the
  10. ## License, or (at your option) any later version.
  11. ##
  12. ## Indico is distributed in the hope that it will be useful, but
  13. ## WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. ## General Public License for more details.
  16. ##
  17. ## You should have received a copy of the GNU General Public License
  18. ## along with Indico;if not, see <http://www.gnu.org/licenses/>.
  19. """
  20. This file contains functions used by both 'python setup.py install' and after-easy_install
  21. based installations.
  22. """
  23. import commands
  24. import os
  25. import re
  26. import shutil
  27. import sys
  28. import pkg_resources
  29. import copy
  30. if sys.platform == 'linux2':
  31. import pwd
  32. import grp
  33. import MaKaC
  34. globals()['INDICO_INSTALL'] = False
  35. LOCALDATABASEDIR = '/opt/indico/db'
  36. # Egg directory + etc/indico.conf
  37. PWD_INDICO_CONF = os.path.abspath(os.path.join(
  38. os.path.split(os.path.dirname(MaKaC.__file__))[0],'etc', 'indico.conf'
  39. ))
  40. def setIndicoInstallMode(newmode):
  41. """
  42. Sets indico install mode.
  43. This function and getIndicoInstallMode are used by some __init__.py inside
  44. MaKaC to load or not some modules. At installation time those modules are not
  45. available. That's why we need to skip them. They are imported there only as
  46. shortcuts.
  47. """
  48. global INDICO_INSTALL
  49. INDICO_INSTALL = newmode
  50. def getIndicoInstallMode():
  51. global INDICO_INSTALL
  52. return INDICO_INSTALL
  53. def createDirs(directories):
  54. '''Creates directories that are not automatically created by setup.install or easy_install'''
  55. for d in ['log', 'tmp', 'cache', 'archive']:
  56. if not os.path.exists(directories[d]):
  57. os.makedirs(directories[d])
  58. def upgrade_indico_conf(existing_conf, new_conf, mixinValues={}):
  59. """
  60. Copies new_conf values to existing_conf file preserving existing_conf's values
  61. If mixinValues is given its items will be preserved above existing_conf's values and new_conf's values
  62. """
  63. # We retrieve values from newest indico.conf
  64. execfile(new_conf)
  65. new_values = locals().copy()
  66. # We retrieve values from existing indico.conf
  67. execfile(existing_conf)
  68. existing_values = locals().copy()
  69. new_values.update(existing_values)
  70. new_values.update(mixinValues)
  71. # We have to preserve options not currently present in the bundled indico.conf because they
  72. # may belong to a plugin. This functionality can lead to forget adding new options to
  73. # Configuration.py so be careful my friend.
  74. # We remove vars defined here that aren't options
  75. for k in ('new_values', 'new_conf', 'existing_conf', 'mixinValues'):
  76. new_values.pop(k)
  77. result_values = new_values
  78. # We update a current copy of indico.conf with the new values
  79. new_contents = open(new_conf).read()
  80. for k in new_values:
  81. if new_values[k].__class__ == str:
  82. regexp = re.compile('^(%s\s*=\s*[\'"]{1})([^\'"]*)([\'"]{1})' % k, re.MULTILINE)
  83. if regexp.search(new_contents):
  84. new_contents = re.sub(regexp, "\\g<1>%s\\3" % result_values[k], new_contents)
  85. else:
  86. new_contents = "%s\n%s = '%s'" % (new_contents, k, result_values[k])
  87. elif new_values[k].__class__ == int:
  88. regexp = re.compile('^(%s\s*=\s*)([0-9]+)' % k, re.MULTILINE)
  89. if regexp.search(new_contents):
  90. new_contents = re.sub(regexp, "\\g<1>%s" % result_values[k], new_contents)
  91. else:
  92. new_contents = "%s\n%s = %s" % (new_contents, k, str(result_values[k]))
  93. elif new_values[k].__class__ == bool:
  94. regexp = re.compile('^(%s\s*=\s*)(True|False)' % k, re.MULTILINE)
  95. if regexp.search(new_contents):
  96. new_contents = re.sub(regexp, "\\g<1>%s" % result_values[k], new_contents)
  97. else:
  98. new_contents = "%s\n%s = %s" % (new_contents, k, str(result_values[k]))
  99. elif new_values[k].__class__ == tuple:
  100. regexp = re.compile('^(%s\s*=\s*)[\(]{1}([^\)]+)[\)]{1}' % k, re.MULTILINE)
  101. if regexp.search(new_contents):
  102. new_contents = re.sub(regexp, "\\g<1>%s" % str(result_values[k]), new_contents)
  103. else:
  104. new_contents = "%s\n%s = %s" % (new_contents, k, str(result_values[k]))
  105. elif new_values[k].__class__ == dict:
  106. regexp = re.compile('^(%s\s*=\s*)[\{](.+)[\}$]' % k, re.MULTILINE)
  107. if regexp.search(new_contents):
  108. new_contents = re.sub(regexp, "\\g<1>%s" % str(result_values[k]), new_contents)
  109. else:
  110. new_contents = "%s\n%s = %s" % (new_contents, k, str(result_values[k]))
  111. elif new_values[k].__class__ == list:
  112. regexp = re.compile('^(%s\s*=\s*)[\[]{1}([^\]]+)[\]]{1}' % k, re.MULTILINE)
  113. if regexp.search(new_contents):
  114. new_contents = re.sub(regexp, "\\g<1>%s" % str(result_values[k]), new_contents)
  115. else:
  116. new_contents = "%s\n%s = %s" % (new_contents, k, str(result_values[k]))
  117. else:
  118. raise Exception('Invalid config value "%s = %s"' % (k, new_values[k]))
  119. # We write unknown options to the end of the file, they may not be just outdated options but plugins'
  120. open(existing_conf, 'w').write(new_contents)
  121. def modifyOnDiskIndicoConfOption(indico_conf, optionName, optionValue):
  122. upgrade_indico_conf(indico_conf, indico_conf, {optionName: optionValue})
  123. def updateIndicoConfPathInsideMaKaCConfig(indico_conf_path, makacconfigpy_path):
  124. '''Modifies the location of indico.conf referenced inside makacconfigpy_path to
  125. point to indico_conf_path'''
  126. fdata = open(makacconfigpy_path).read()
  127. fdata = re.sub(r'indico_conf\s*=\s*[\'"]([^\'"]*)[\'"]', "indico_conf = \"%s\"" % indico_conf_path, fdata)
  128. open(makacconfigpy_path, 'w').write(fdata)
  129. def _updateMaKaCEggCache(file, path):
  130. fdata = open(file).read()
  131. fdata = re.sub('\/opt\/indico\/tmp', path, fdata)
  132. open(file, 'w').write(fdata)
  133. def compileAllLanguages(cmd):
  134. '''Generates .mo files from .po files'''
  135. try:
  136. pkg_resources.require('babel')
  137. except pkg_resources.DistributionNotFound:
  138. print """
  139. Babel not found! Babel is needed for internationalization if you're building Indico from source. Please install it and re-run this program.
  140. i.e. try 'easy_install babel'"""
  141. sys.exit(-1)
  142. # call commands directly
  143. cc = cmd.distribution.get_command_obj('compile_catalog')
  144. cc.run()
  145. gjs = cmd.distribution.get_command_obj('compile_catalog_js')
  146. gjs.run()
  147. def copyTreeSilently(source, target):
  148. '''Copies source tree to target tree overwriting existing files'''
  149. source = os.path.normpath(source)
  150. target = os.path.normpath(target)
  151. for root, dirs, files in os.walk(source, topdown=False):
  152. for name in files:
  153. fullpath = os.path.normpath(os.path.join(root, name))
  154. dstfile = os.path.normpath(fullpath.replace(source, target))
  155. targetdir = os.path.dirname(dstfile)
  156. if not os.path.exists(targetdir):
  157. os.makedirs(targetdir)
  158. try:
  159. shutil.copy(fullpath, dstfile)
  160. except Exception, e:
  161. print e
  162. def _checkModPythonIsInstalled():
  163. pass
  164. def _guessApacheUidGid():
  165. finalUser, finalGroup = None, None
  166. for username in ['apache', 'www-data', 'www']:
  167. try:
  168. finalUser = pwd.getpwnam(username)
  169. break
  170. except KeyError:
  171. pass
  172. for groupname in ['apache', 'www-data', 'www']:
  173. try:
  174. finalGroup = grp.getgrnam(groupname)
  175. break
  176. except KeyError:
  177. pass
  178. return finalUser, finalGroup
  179. def _findApacheUserGroup(uid, gid):
  180. # if we are on linux we need to give proper permissions to the results directory
  181. if sys.platform == "linux2":
  182. prompt = True
  183. # Check to see if uid/gid were provided through commandline
  184. if uid and gid:
  185. try:
  186. accessuser = pwd.getpwuid(int(uid)).pw_name
  187. accessgroup = grp.getgrgid(int(gid)).gr_name
  188. prompt = False
  189. except KeyError:
  190. print 'uid/gid pair (%s/%s) provided through command line is false' % (uid, gid)
  191. else:
  192. print "uid/gid not provided. Trying to guess them... ",
  193. uid, gid = _guessApacheUidGid()
  194. if uid and gid:
  195. print "found %s(%s) %s(%s)" % (uid.pw_name, uid.pw_uid,
  196. gid.gr_name, gid.gr_gid)
  197. accessuser = uid.pw_name
  198. accessgroup = gid.gr_name
  199. prompt = False
  200. if prompt == True:
  201. valid_credentials = False
  202. while not valid_credentials:
  203. accessuser = raw_input("\nPlease enter the user that will be running the Apache server: ")
  204. accessgroup = raw_input("\nPlease enter the group that will be running the Apache server: ")
  205. try:
  206. pwd.getpwnam(accessuser)
  207. grp.getgrnam(accessgroup)
  208. valid_credentials = True
  209. except KeyError:
  210. print "\nERROR: Invalid user/group pair (%s/%s)" % (accessuser, accessgroup)
  211. return accessuser, accessgroup
  212. else: #for windows
  213. return "apache", "apache"
  214. def _checkDirPermissions(directories, dbInstalledBySetupPy=False, accessuser=None, accessgroup=None):
  215. '''Makes sure that directories which need write access from Apache have
  216. the correct permissions
  217. - dbInstalledBySetupPy if True, means that the dbdir has been created by the setup
  218. process and needs to be checked.
  219. - uid and gid: if they are valid user_ids and group_ids they will be used to chown
  220. the directories instead of the indico.conf ones.
  221. '''
  222. print "\nWe need to 'sudo' in order to set the permissions of some directories..."
  223. if sys.platform == "linux2":
  224. dirs2check = list(directories[x] for x in ['htdocs', 'log', 'tmp', 'cache', 'archive'] if directories.has_key(x))
  225. if dbInstalledBySetupPy:
  226. dirs2check.append(dbInstalledBySetupPy)
  227. for dir in dirs2check:
  228. stat_info = os.stat(dir)
  229. if pwd.getpwuid(int(stat_info.st_uid)).pw_name != accessuser:
  230. print commands.getoutput("if test $(which sudo); then CMD=\"sudo\"; fi; $CMD chown -R %s:%s %s" % (accessuser, accessgroup, dir))
  231. elif grp.getgrgid(int(stat_info.st_gid)).gr_name != accessgroup:
  232. os.chown(dir, pwd.getpwnam(accessuser).pw_uid, grp.getgrnam(accessgroup).gr_gid)
  233. def _existingConfiguredEgg():
  234. '''Returns true if an existing EGG has been detected.'''
  235. # remove '.' and './indico'
  236. path = copy.copy(sys.path)
  237. path = path[2:]
  238. env = pkg_resources.Environment(search_path=path)
  239. env.scan(search_path=path)
  240. # search for all indico dists
  241. indico_dists = env['indico']
  242. for dist in indico_dists:
  243. eggPath = dist.location
  244. print "* EGG found at %s..." % eggPath,
  245. fdata = open(os.path.join(eggPath,'MaKaC','common','MaKaCConfig.py'), 'r').read()
  246. m = re.search('^\s*indico_conf\s*=\s*[\'"]{1}([^\'"]*)[\'"]{1}', fdata, re.MULTILINE)
  247. if m and m.group(1) != '':
  248. print '%s' % m.group(1)
  249. return m.group(1)
  250. else:
  251. print 'unconfigured'
  252. return None
  253. def _extractDirsFromConf(conf):
  254. execfile(conf)
  255. values = locals().copy()
  256. return {'bin': values['BinDir'],
  257. 'doc': values['DocumentationDir'],
  258. 'etc': values['ConfigurationDir'],
  259. 'htdocs': values['HtdocsDir'],
  260. 'tmp': values['UploadedFilesTempDir'],
  261. 'log': values['LogDir'],
  262. 'cache': values['XMLCacheDir'],
  263. 'archive': values['ArchiveDir'],
  264. 'db': LOCALDATABASEDIR}
  265. def _replacePrefixInConf(filePath, prefix):
  266. fdata = open(filePath).read()
  267. fdata = re.sub('\/opt\/indico', prefix, fdata)
  268. open(filePath, 'w').write(fdata)
  269. def _updateDbConfigFiles(cfg_dir, uid=None, port=None, **kwargs):
  270. """
  271. Update parameters inside DB config files
  272. """
  273. kwargs['etc'] = cfg_dir
  274. for fname in ['zodb.conf', 'zdctl.conf']:
  275. with open(os.path.join(cfg_dir, fname), 'r+') as f:
  276. fdata = f.read()
  277. for dirname in ['db', 'log', 'tmp', 'etc']:
  278. fdata = re.sub(r'\/opt\/indico\/{0}'.format(dirname), kwargs.get(dirname, dirname), fdata)
  279. if uid:
  280. fdata = re.compile(r'^(\s*user\s+)apache[^\S\n]*', re.MULTILINE).sub(r'\g<1>{0}'.format(uid), fdata)
  281. if port is not None:
  282. fdata = re.compile(r'^(\s*address\s+localhost:)\d+[^\S\n]*', re.MULTILINE).sub(r'\g<1>{0}'.format(port), fdata)
  283. f.seek(0)
  284. f.write(fdata)
  285. def indico_pre_install(defaultPrefix, force_upgrade=False, existingConfig=None):
  286. """
  287. defaultPrefix is the default prefix dir where Indico will be installed
  288. """
  289. upgrade = False
  290. # Configuration specified in the command-line
  291. if existingConfig:
  292. existing = existingConfig
  293. # upgrade is mandatory
  294. upgrade = True
  295. else:
  296. # Config not specified
  297. # automatically find an EGG in the site-packages path
  298. existing = _existingConfiguredEgg()
  299. # if an EGG was found but upgrade is not forced
  300. if existing and not force_upgrade:
  301. # Ask the user
  302. opt = None
  303. while opt not in ('', 'e', 'E', 'u'):
  304. opt = raw_input('''
  305. An existing Indico configuration has been detected at:
  306. %s
  307. At this point you can:
  308. [u]pgrade the existing installation
  309. [E]xit this installation process
  310. What do you want to do [u/E]? ''' % existing)
  311. if opt in ('', 'e', 'E'):
  312. print "\nExiting installation..\n"
  313. sys.exit()
  314. elif opt == 'u':
  315. upgrade = True
  316. else:
  317. print "\nInvalid answer. Exiting installation..\n"
  318. sys.exit()
  319. # if and EGG was found and upgrade is forced
  320. elif existing and force_upgrade:
  321. upgrade = True
  322. if upgrade:
  323. print 'Upgrading the existing Indico installation..'
  324. return _extractDirsFromConf(existing)
  325. else:
  326. # then, in case no previous installation exists
  327. return fresh_install(defaultPrefix)
  328. def fresh_install(defaultPrefix):
  329. # start from scratch
  330. print "No previous installation of Indico was found."
  331. print "Please specify a directory prefix:"
  332. # ask for a directory prefix
  333. prefixDir = raw_input('[%s]: ' % defaultPrefix).strip()
  334. if prefixDir == '':
  335. prefixDir = defaultPrefix
  336. configDir = os.path.join(prefixDir, 'etc')
  337. # create the directory that will contain the configuration files
  338. if not os.path.exists(configDir):
  339. os.makedirs(configDir)
  340. indicoconfpath = os.path.join(configDir, 'indico.conf')
  341. opt = raw_input('''
  342. You now need to configure Indico, by editing indico.conf or letting us do it for you.
  343. At this point you can:
  344. [c]opy the default values in etc/indico.conf.sample to a new etc/indico.conf
  345. and continue the installation
  346. [A]bort the installation in order to inspect etc/indico.conf.sample
  347. and / or to make your own etc/indico.conf
  348. What do you want to do [c/a]? ''')
  349. if opt in ('c', 'C'):
  350. shutil.copy(PWD_INDICO_CONF + '.sample', indicoconfpath)
  351. _replacePrefixInConf(indicoconfpath, prefixDir)
  352. elif opt in ('', 'a', 'A'):
  353. print "\nExiting installation..\n"
  354. sys.exit()
  355. else:
  356. print "\nInvalid answer. Exiting installation..\n"
  357. sys.exit()
  358. activemakacconfig = os.path.join(os.path.dirname(os.path.abspath(MaKaC.__file__)), 'common', 'MaKaCConfig.py')
  359. updateIndicoConfPathInsideMaKaCConfig(indicoconfpath, activemakacconfig)
  360. return dict((dirName, os.path.join(prefixDir, dirName))
  361. for dirName in ['bin','doc','etc','htdocs','tmp','log','cache','db','archive'])
  362. def indico_post_install(targetDirs, sourceDirs, makacconfig_base_dir, package_dir, force_no_db = False, uid=None, gid=None, dbDir=LOCALDATABASEDIR):
  363. from indico.core.config import Config
  364. if 'db' in targetDirs:
  365. # we don't want that the db directory be created
  366. dbDir = targetDirs['db']
  367. del targetDirs['db']
  368. print "Creating directories for resources... ",
  369. # Create the directories where the resources will be installed
  370. createDirs(targetDirs)
  371. print "done!"
  372. # target configuration file (may exist or not)
  373. newConf = os.path.join(targetDirs['etc'],'indico.conf')
  374. # source configuration file (package)
  375. sourceConf = os.path.join(sourceDirs['etc'], 'indico.conf.sample')
  376. # if there is a source config
  377. if os.path.exists(sourceConf):
  378. if not os.path.exists(newConf):
  379. # just copy if there is no config yet
  380. shutil.copy(sourceConf, newConf)
  381. else:
  382. print "Upgrading indico.conf...",
  383. # upgrade the existing one
  384. upgrade_indico_conf(newConf, sourceConf)
  385. print "done!"
  386. # change MaKaCConfig.py to include the config
  387. updateIndicoConfPathInsideMaKaCConfig(newConf,
  388. os.path.join(makacconfig_base_dir, 'MaKaCConfig.py'))
  389. # copy the db config files
  390. for f in [xx for xx in ('%s/zdctl.conf' % targetDirs['etc'],
  391. '%s/zodb.conf' % targetDirs['etc'],
  392. '%s/logging.conf' %targetDirs['etc']) if not os.path.exists(xx)]:
  393. shutil.copy('%s.sample' % f, f)
  394. # Shall we create a DB?
  395. dbInstalledBySetupPy = False
  396. dbpath = None
  397. if force_no_db:
  398. print 'Skipping database detection'
  399. else:
  400. if os.path.exists(dbDir):
  401. dbpath = dbDir
  402. print 'Successfully found a database directory at %s' % dbDir
  403. else:
  404. opt = None
  405. while opt not in ('Y', 'y', 'n', ''):
  406. opt = raw_input('''\nWe cannot find a configured database at %s.
  407. Do you want to create a new database now [Y/n]? ''' % dbDir)
  408. if opt in ('Y', 'y', ''):
  409. dbInstalledBySetupPy = True
  410. dbpath_ok = False
  411. while not dbpath_ok:
  412. dbpath = raw_input('''\nWhere do you want to install the database [%s]? ''' % dbDir)
  413. if dbpath.strip() == '':
  414. dbpath = dbDir
  415. try:
  416. os.makedirs(dbpath)
  417. dbpath_ok = True
  418. except Exception, e:
  419. print e
  420. print 'Unable to create database at %s, please make sure that you have permissions to create that directory' % dbpath
  421. elif opt == 'n':
  422. pass
  423. #we delete an existing vars.js.tpl.tmp
  424. tmp_dir = targetDirs['tmp']
  425. varsJsTplTmpPath = os.path.join(tmp_dir, 'vars.js.tpl.tmp')
  426. if os.path.exists(varsJsTplTmpPath):
  427. print 'Old vars.js.tpl.tmp found at: %s. Removing' % varsJsTplTmpPath
  428. os.remove(varsJsTplTmpPath)
  429. if dbInstalledBySetupPy:
  430. dbParam = dbpath
  431. else:
  432. dbParam = None
  433. # find the apache user/group
  434. user, group = _findApacheUserGroup(uid, gid)
  435. # set the directory for the egg cache
  436. _updateMaKaCEggCache(os.path.join(package_dir, 'MaKaC', '__init__.py'), targetDirs['tmp'])
  437. if not force_no_db and dbpath:
  438. # change the db config files (paths + apache user/group)
  439. _updateDbConfigFiles(targetDirs['etc'],
  440. log=targetDirs['log'],
  441. db=dbpath,
  442. tmp=targetDirs['tmp'],
  443. uid=user)
  444. # check permissions
  445. _checkDirPermissions(targetDirs, dbInstalledBySetupPy=dbParam, accessuser=user, accessgroup=group)
  446. # check that mod_python is installed
  447. _checkModPythonIsInstalled()
  448. print """
  449. Congratulations!
  450. Indico has been installed correctly.
  451. indico.conf: %s/indico.conf
  452. BinDir: %s
  453. DocumentationDir: %s
  454. ConfigurationDir: %s
  455. HtdocsDir: %s
  456. For information on how to configure Apache HTTPD, take a look at:
  457. http://indico-software.org/wiki/Admin/Installation#a3.ConfiguringApache
  458. Please do not forget to start the 'taskDaemon' in order to use alarms, creation
  459. of off-line websites, reminders, etc. You can find it in './bin/taskDaemon.py'
  460. %s
  461. """ % (targetDirs['etc'], targetDirs['bin'], targetDirs['doc'], targetDirs['etc'], targetDirs['htdocs'], _databaseText(targetDirs['etc']))
  462. def _databaseText(cfgPrefix):
  463. return """If you are running ZODB on this host:
  464. - Review %s/zodb.conf and %s/zdctl.conf to make sure everything is ok.
  465. - To start the database run: zdaemon -C %s/zdctl.conf start
  466. """ % (cfgPrefix, cfgPrefix, cfgPrefix)