Package Products :: Package ZenUtils :: Module zenpack
[hide private]
[frames] | no frames]

Source Code for Module Products.ZenUtils.zenpack

  1  ########################################################################### 
  2  # 
  3  # This program is part of Zenoss Core, an open source monitoring platform. 
  4  # Copyright (C) 2007, Zenoss Inc. 
  5  # 
  6  # This program is free software; you can redistribute it and/or modify it 
  7  # under the terms of the GNU General Public License version 2 or (at your 
  8  # option) any later version as published by the Free Software Foundation. 
  9  # 
 10  # For complete information please visit: http://www.zenoss.com/oss/ 
 11  # 
 12  ########################################################################### 
 13  __doc__ = "Manage ZenPacks" 
 14   
 15  import os, sys 
 16  import contextlib 
 17  import logging 
 18  import ConfigParser 
 19  import subprocess 
 20  from zipfile import ZipFile 
 21  from StringIO import StringIO 
 22   
 23  import Globals 
 24  import transaction 
 25  from ZODB.POSException import ConflictError 
 26   
 27  from Products.ZenModel.ZenPack import ZenPack, ZenPackException 
 28  from Products.ZenModel.ZenPack import ZenPackNeedMigrateException 
 29  from Products.ZenUtils.ZenScriptBase import ZenScriptBase 
 30  from Products.ZenUtils.Utils import cleanupSkins, zenPath, binPath, get_temp_dir 
 31  import Products.ZenModel.ZenPackLoader as ZPL 
 32  from Products.ZenModel.ZenPackLoader import CONFIG_FILE, CONFIG_SECTION_ABOUT 
 33  import ZenPackCmd as EggPackCmd 
 34  from Products.Zuul import getFacade 
 35   
 36  HIGHER_THAN_CRITICAL = 100 
 37   
38 -def RemoveZenPack(dmd, packName, log=None, 39 skipDepsCheck=False, leaveObjects=True, 40 deleteFiles=True):
41 if log: 42 log.debug('Removing Pack "%s"' % packName) 43 if not skipDepsCheck: 44 for pack in dmd.ZenPackManager.packs(): 45 if packName in pack.requires: 46 raise ZenPackException('Pack %s depends on pack %s, ' 47 'not removing' % (pack.id, packName)) 48 zp = None 49 try: 50 zp = dmd.ZenPackManager.packs._getOb(packName) 51 except AttributeError, ex: 52 # Pack not in zeo, might still exist in filesystem 53 if log: 54 log.debug('No ZenPack named %s in zeo' % packName) 55 if zp: 56 try: 57 # In 2.2 we added an additional parameter to the remove method. 58 # Any ZenPack subclasses that haven't been updated to take the new 59 # parameter will throw a TypeError. 60 # The newer version of zenoss-supplied ZenPacks monkey patch 61 # older installed versions during an upgrade so that the remove 62 # accepts the leaveObjects method. 63 zp.remove(dmd, leaveObjects=True) 64 except TypeError: 65 zp.remove(dmd) 66 dmd.ZenPackManager.packs._delObject(packName) 67 root = zenPath('Products', packName) 68 if deleteFiles: 69 if log: 70 log.debug('Removing %s' % root) 71 recurse = "" 72 if os.path.isdir(root): 73 recurse = "r" 74 os.system('rm -%sf %s' % (recurse, root)) 75 cleanupSkins(dmd) 76 return True
77 78
79 -class ZenPackCmd(ZenScriptBase):
80 "Manage ZenPacks" 81
82 - def _verifyZepRunning(self):
83 zep = getFacade('zep') 84 try: 85 zep.getConfig() 86 return True 87 except: 88 pass
89
90 - def run(self):
91 "Execute the user's request" 92 if self.args: 93 print "Require one of --install, --remove or --list flags." 94 self.parser.print_help() 95 return 96 97 if self.options.installPackName: 98 eggInstall = (self.options.installPackName.lower().endswith('.egg') 99 or os.path.exists(os.path.join(self.options.installPackName, 100 'setup.py'))) 101 102 # files-only just lays the files down and doesn't "install" 103 # them into zeo 104 class ZPProxy: 105 def __init__(self, zpId): 106 self.id = zpId
107 def path(self, *parts): 108 return zenPath('Products', self.id, *parts)
109 if self.options.installPackName and self.options.filesOnly: 110 if eggInstall: 111 return EggPackCmd.InstallZenPack(None, 112 self.options.installPackName, 113 filesOnly=True) 114 packName = self.extract(self.options.installPackName) 115 proxy = ZPProxy(packName) 116 for loader in (ZPL.ZPLDaemons(), ZPL.ZPLBin(), ZPL.ZPLLibExec()): 117 loader.load(proxy, None) 118 return 119 if self.options.removePackName and self.options.filesOnly: 120 # Remove files-only is not yet supported for egg zenpacks 121 # todo 122 proxy = ZPProxy(self.options.removePackName) 123 for loader in (ZPL.ZPLDaemons(), ZPL.ZPLBin(), ZPL.ZPLLibExec()): 124 loader.unload(proxy, None) 125 os.system('rm -rf %s' % zenPath('Products', 126 self.options.removePackName)) 127 return 128 129 130 131 self.connect() 132 133 if not getattr(self.dmd, 'ZenPackManager', None): 134 raise ZenPackNeedMigrateException('Your Zenoss database appears' 135 ' to be out of date. Try running zenmigrate to update.') 136 137 if not self._verifyZepRunning() and (self.options.installPackName or self.options.removePackName): 138 print >> sys.stderr, "Error: Required daemon zeneventserver not running." 139 print >> sys.stderr, "Execute 'zeneventserver start' and retry the ZenPack installation." 140 sys.exit(1) 141 142 if self.options.installPackName: 143 if not self.preInstallCheck(eggInstall): 144 self.stop('%s not installed' % self.options.installPackName) 145 if eggInstall: 146 return EggPackCmd.InstallEggAndZenPack( 147 self.dmd, 148 self.options.installPackName, 149 link=self.options.link, 150 filesOnly=False, 151 previousVersion= self.options.previousVersion) 152 if os.path.isfile(self.options.installPackName): 153 packName = self.extract(self.options.installPackName) 154 elif os.path.isdir(self.options.installPackName): 155 if self.options.link: 156 packName = self.linkDir(self.options.installPackName) 157 else: 158 packName = self.copyDir(self.options.installPackName) 159 else: 160 self.stop('%s does not appear to be a valid file or directory.' 161 % self.options.installPackName) 162 # We want to make sure all zenpacks have a skins directory and that it 163 # is registered. The zip file may not contain a skins directory, so we 164 # create one here if need be. The directory should be registered 165 # by the zenpack's __init__.py and the skin should be registered 166 # by ZPLSkins loader. 167 skinsSubdir = zenPath('Products', packName, 'skins', packName) 168 if not os.path.exists(skinsSubdir): 169 os.makedirs(skinsSubdir, 0750) 170 self.install(packName) 171 172 # elif self.options.fetch: 173 # return EggPackCmd.FetchAndInstallZenPack(self.dmd, 174 # self.options.fetch, self.options.fetchVers) 175 elif self.options.removePackName: 176 pack = self.dmd.ZenPackManager.packs._getOb( 177 self.options.removePackName, None) 178 if not pack: 179 raise ZenPackException('ZenPack %s is not installed.' % 180 self.options.removePackName) 181 if pack.isEggPack(): 182 return EggPackCmd.RemoveZenPack(self.dmd, 183 self.options.removePackName) 184 RemoveZenPack(self.dmd, self.options.removePackName, self.log) 185 186 elif self.options.list: 187 for zpId in self.dmd.ZenPackManager.packs.objectIds(): 188 try: 189 zp = self.dmd.ZenPackManager.packs._getOb(zpId, None) 190 except AttributeError: 191 zp = None 192 if not zp: 193 desc = 'broken' 194 elif zp.isEggPack(): 195 desc = zp.eggPath() 196 else: 197 desc = zp.path() 198 print('%s (%s)' % (zpId, desc)) 199 200 transaction.commit() 201 202
203 - def preInstallCheck(self, eggInstall=True):
204 ''' Check that prerequisite zenpacks are installed. 205 Return True if no prereqs specified or if they are present. 206 False otherwise. 207 ''' 208 if eggInstall: 209 installedPacks = dict((pack.id, pack.version) \ 210 for pack in self.dataroot.ZenPackManager.packs()) 211 212 if self.options.installPackName.lower().endswith('.egg'): 213 # standard prebuilt egg 214 zf = ZipFile(self.options.installPackName) 215 if 'EGG-INFO/requires.txt' in zf.namelist(): 216 reqZenpacks = zf.read('EGG-INFO/requires.txt').split('\n') 217 else: 218 return True 219 else: 220 # source egg, no prebuilt egg-info 221 with get_temp_dir() as tempEggDir: 222 cmd = '%s setup.py egg_info -e %s' % \ 223 (binPath('python'), tempEggDir) 224 subprocess.call(cmd, shell=True, 225 stdout=open('/dev/null', 'w'), 226 cwd=self.options.installPackName) 227 228 eggRequires = os.path.join(tempEggDir, 229 self.options.installPackName + '.egg-info', 230 'requires.txt') 231 if os.path.isfile(eggRequires): 232 reqZenpacks = open(eggRequires, 'r').read().split('\n') 233 else: 234 return True 235 236 for req in reqZenpacks: 237 zpName, zpVersion = req.strip(), None 238 operatorPos = req.find('>=') 239 if operatorPos > 0: 240 zpName = req[:operatorPos].strip() 241 zpVersion = req[operatorPos+2:].strip() 242 243 if zpName in installedPacks: 244 if zpVersion and installedPacks[zpName] < zpVersion: 245 self.log.error( 246 'Zenpack %s requires %s to be at version %s' % 247 (self.options.installPackName, zpName, zpVersion)) 248 return False 249 else: 250 self.log.error('Zenpack %s requires %s %s' % 251 (self.options.installPackName, zpName, 252 zpVersion if zpVersion else '')) 253 return False 254 return True 255 256 if os.path.isfile(self.options.installPackName): 257 zf = ZipFile(self.options.installPackName) 258 for name in zf.namelist(): 259 if name.endswith == '/%s' % CONFIG_FILE: 260 sio = StringIO(zf.read(name)) 261 else: 262 return True 263 else: 264 name = os.path.join(self.options.installPackName, CONFIG_FILE) 265 if os.path.isfile(name): 266 fp = open(name) 267 sio = StringIO(fp.read()) 268 fp.close() 269 else: 270 return True 271 272 parser = ConfigParser.SafeConfigParser() 273 parser.readfp(sio, name) 274 if parser.has_section(CONFIG_SECTION_ABOUT) \ 275 and parser.has_option(CONFIG_SECTION_ABOUT, 'requires'): 276 requires = eval(parser.get(CONFIG_SECTION_ABOUT, 'requires')) 277 if not isinstance(requires, list): 278 requires = [zp.strip() for zp in requires.split(',')] 279 missing = [zp for zp in requires 280 if zp not in self.dataroot.ZenPackManager.packs.objectIds()] 281 if missing: 282 self.log.error('ZenPack %s was not installed because' 283 % self.options.installPackName 284 + ' it requires the following ZenPack(s): %s' 285 % ', '.join(missing)) 286 return False 287 return True
288 289
290 - def install(self, packName):
291 292 zp = None 293 try: 294 # hide uncatalog error messages since they do not do any harm 295 log = logging.getLogger('Zope.ZCatalog') 296 oldLevel = log.getEffectiveLevel() 297 log.setLevel(HIGHER_THAN_CRITICAL) 298 zp = self.dmd.ZenPackManager.packs._getOb(packName) 299 self.log.info('Upgrading %s' % packName) 300 zp.upgrade(self.app) 301 except AttributeError: 302 try: 303 module = __import__('Products.' + packName, globals(), {}, ['']) 304 zp = module.ZenPack(packName) 305 except (ImportError, AttributeError), ex: 306 self.log.debug("Unable to find custom ZenPack (%s), " 307 "defaulting to generic ZenPack", 308 ex) 309 zp = ZenPack(packName) 310 self.dmd.ZenPackManager.packs._setObject(packName, zp) 311 zp = self.dmd.ZenPackManager.packs._getOb(packName) 312 zp.install(self.app) 313 finally: 314 log.setLevel(oldLevel) 315 if zp: 316 for required in zp.requires: 317 try: 318 self.dmd.ZenPackManager.packs._getOb(required) 319 except: 320 self.log.error("Pack %s requires pack %s: not installing", 321 packName, required) 322 return 323 transaction.commit()
324
325 - def extract(self, fname):
326 "Unpack a ZenPack, and return the name" 327 if not os.path.isfile(fname): 328 self.stop('Unable to open file "%s"' % fname) 329 zf = ZipFile(fname) 330 name = zf.namelist()[0] 331 packName = name.split('/')[0] 332 self.log.debug('Extracting ZenPack "%s"' % packName) 333 for name in zf.namelist(): 334 fullname = zenPath('Products', name) 335 self.log.debug('Extracting %s' % name) 336 if name.find('/.svn') > -1: continue 337 if name.endswith('~'): continue 338 if name.endswith('/'): 339 if not os.path.exists(fullname): 340 os.makedirs(fullname, 0750) 341 else: 342 base = os.path.dirname(fullname) 343 if not os.path.isdir(base): 344 os.makedirs(base, 0750) 345 file(fullname, 'wb').write(zf.read(name)) 346 return packName
347 348
349 - def copyDir(self, srcDir):
350 '''Copy an unzipped zenpack to the appropriate location. 351 Return the name. 352 ''' 353 # Normalize srcDir to not end with slash 354 if srcDir.endswith('/'): 355 srcDir = srcDir[:-1] 356 357 if not os.path.isdir(srcDir): 358 self.stop('Specified directory does not appear to exist: %s' % 359 srcDir) 360 361 # Determine name of pack and it's destination directory 362 packName = os.path.split(srcDir)[1] 363 root = zenPath('Products', packName) 364 365 # Continue without copying if the srcDir is already in Products 366 if os.path.exists(root) and os.path.samefile(root, srcDir): 367 self.log.debug('Directory already in %s, not copying.', 368 zenPath('Products')) 369 return packName 370 371 # Copy the source dir over to Products 372 self.log.debug('Copying %s' % packName) 373 result = os.system('cp -r %s %s' % (srcDir, zenPath('Products'))) 374 if result == -1: 375 self.stop('Error copying %s to %s' % (srcDir, zenPath('Products'))) 376 377 return packName
378 379
380 - def linkDir(self, srcDir):
381 '''Symlink the srcDir into Products 382 Return the name. 383 ''' 384 # Normalize srcDir to not end with slash 385 if srcDir.endswith('/'): 386 srcDir = srcDir[:-1] 387 388 # Need absolute path for links 389 srcDir = os.path.abspath(srcDir) 390 391 if not os.path.isdir(srcDir): 392 self.stop('Specified directory does not appear to exist: %s' % 393 srcDir) 394 395 # Determine name of pack and it's destination directory 396 packName = os.path.split(srcDir)[1] 397 root = zenPath('Products', packName) 398 399 # Continue without copying if the srcDir is already in Products 400 if os.path.exists(root) and os.path.samefile(root, srcDir): 401 self.log.debug('Directory already in %s, not copying.', 402 zenPath('Products')) 403 return packName 404 405 targetdir = zenPath("Products", packName) 406 cmd = 'test -d %s && rm -rf %s' % (targetdir, targetdir) 407 os.system(cmd) 408 cmd = 'ln -s %s %s' % (srcDir, zenPath("Products")) 409 os.system(cmd) 410 411 return packName
412 413
414 - def stop(self, why):
415 self.log.error("zenpack stopped: %s", why) 416 sys.exit(1)
417 418
419 - def buildOptions(self):
420 self.parser.add_option('--install', 421 dest='installPackName', 422 default=None, 423 help="Path to the ZenPack to install.") 424 # self.parser.add_option('--fetch', 425 # dest='fetch', 426 # default=None, 427 # help='Name of ZenPack to retrieve from ' 428 # 'Zenoss.net and install.') 429 # self.parser.add_option('--fetchVers', 430 # dest='fetchVers', 431 # default=None, 432 # help='Use with --fetch to specify a version' 433 # ' for the ZenPack to download and install.') 434 self.parser.add_option('--remove', '--delete', '--uninstall', '--erase', 435 dest='removePackName', 436 default=None, 437 help="Name of the ZenPack to remove.") 438 self.parser.add_option('--list', 439 dest='list', 440 action="store_true", 441 default=False, 442 help='List installed ZenPacks') 443 self.parser.add_option('--link', 444 dest='link', 445 action="store_true", 446 default=False, 447 help="Install the ZenPack in place, without " 448 "copying into $ZENHOME/ZenPacks.") 449 self.parser.add_option('--files-only', 450 dest='filesOnly', 451 action="store_true", 452 default=False, 453 help='Install the ZenPack files onto the ' 454 'filesystem, but do not install the ' 455 'ZenPack into Zenoss.') 456 self.parser.add_option('--previousversion', 457 dest='previousVersion', 458 default=None, 459 help="Previous version of the zenpack;" 460 ' used during upgrades') 461 self.parser.prog = "zenpack" 462 ZenScriptBase.buildOptions(self)
463 464 if __name__ == '__main__': 465 logging.basicConfig() 466 log = logging.getLogger('zen.ZenPackCmd') 467 try: 468 zp = ZenPackCmd() 469 zp.run() 470 except ConflictError: 471 raise 472 except SystemExit as e: 473 if e.code: 474 sys.exit(e.code) 475 except: 476 log.exception('zenpack command failed') 477 sys.exit(-1) 478