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

Source Code for Module Products.ZenUtils.zenpack

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