Package Products :: Package ZenModel :: Module ZenPack
[hide private]
[frames] | no frames]

Source Code for Module Products.ZenModel.ZenPack

   1  ########################################################################### 
   2  # 
   3  # This program is part of Zenoss Core, an open source monitoring platform. 
   4  # Copyright (C) 2007,2008 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   
  14  __doc__ = """ZenPack 
  15  ZenPacks base definitions 
  16  """ 
  17   
  18  import exceptions 
  19  import string 
  20  import subprocess 
  21  import os 
  22  import sys 
  23   
  24  from Globals import InitializeClass 
  25  from Products.ZenModel.ZenModelRM import ZenModelRM 
  26  from Products.ZenRelations.RelSchema import * 
  27  from Products.ZenUtils.Utils import importClass, zenPath 
  28  from Products.ZenUtils.Version import getVersionTupleFromString 
  29  from Products.ZenUtils.Version import Version as VersionBase 
  30  from Products.ZenUtils.PkgResources import pkg_resources 
  31  from Products.ZenModel.ZenPackLoader import * 
  32  from Products.ZenWidgets import messaging 
  33  from AccessControl import ClassSecurityInfo 
  34  from ZenossSecurity import ZEN_MANAGE_DMD 
  35  from Acquisition import aq_parent 
  36  from Products.ZenModel.ZVersion import VERSION as ZENOSS_VERSION 
  37   
  38   
39 -class ZenPackException(exceptions.Exception):
40 pass
41
42 -class ZenPackNotFoundException(ZenPackException):
43 pass
44
45 -class ZenPackDuplicateNameException(ZenPackException):
46 pass
47
48 -class ZenPackNeedMigrateException(ZenPackException):
49 pass
50
51 -class ZenPackDependentsException(ZenPackException):
52 pass
53
54 -class ZenPackDevelopmentModeExeption(ZenPackException):
55 pass
56
57 -class Version(VersionBase):
58 - def __init__(self, *args, **kw):
59 VersionBase.__init__(self, 'Zenoss', *args, **kw)
60 61
62 -def eliminateDuplicates(objs):
63 """ 64 Given a list of objects, return the sorted list of unique objects 65 where uniqueness is based on the getPrimaryPath() results. 66 67 @param objs: list of objects 68 @type objs: list of objects 69 @return: sorted list of objects 70 @rtype: list of objects 71 """ 72 73 def compare(x, y): 74 """ 75 Comparison function based on getPrimaryPath() 76 77 @param x: object 78 @type x: object 79 @param y: object 80 @type y: object 81 @return: cmp-style return code 82 @rtype: numeric 83 """ 84 return cmp(x.getPrimaryPath(), y.getPrimaryPath())
85 86 objs.sort(compare) 87 result = [] 88 for obj in objs: 89 for alreadyInList in result: 90 path = alreadyInList.getPrimaryPath() 91 if obj.getPrimaryPath()[:len(path)] == path: 92 break 93 else: 94 result.append(obj) 95 return result 96 97
98 -class ZenPackMigration:
99 """ 100 Base class for defining migration methods 101 """ 102 version = Version(0, 0, 0) 103
104 - def migrate(self, pack):
105 """ 106 ZenPack-specific migrate() method to be overridden 107 108 @param pack: ZenPack object 109 @type pack: ZenPack object 110 """ 111 pass
112
113 - def recover(self, pack):
114 """ 115 ZenPack-specific recover() method to be overridden 116 117 @param pack: ZenPack object 118 @type pack: ZenPack object 119 """ 120 pass
121 122 123 124
125 -class ZenPackDataSourceMigrateBase(ZenPackMigration):
126 """ 127 Base class for ZenPack migrate steps that need to switch classes of 128 datasources and reindex them. This is frequently done in migrate 129 scripts for 2.2 when ZenPacks are migrated to python eggs. 130 """ 131 # dsClass is the actual class of the datasource provided by this ZenPack 132 dsClass = None 133 # These are the names of the module and the class of the datasource as 134 # provided by previous versios of this ZenPack. If these are provided 135 # then any instances of them will be converted to instances of dsClass. 136 oldDsModuleName = '' 137 oldDsClassName = '' 138 # If reIndex is True then any instances of dsClass are reindexed. 139 reIndex = False 140
141 - def migrate(self, pack):
142 """ 143 Attempt to import oidDsModuleName and then any templates 144 145 @param pack: ZenPack object 146 @type pack: ZenPack object 147 """ 148 if self.oldDsModuleName and self.oldDsClassName and self.dsClass: 149 try: 150 exec('import %s' % self.oldDsModuleName) 151 oldClass = eval('%s.%s' % (self.oldDsModuleName, 152 self.oldDsClassName)) 153 except ImportError: 154 # The old-style code no longer exists in Products, 155 # so we assume the migration has already happened. 156 oldClass = None 157 158 from Products.ZenModel.RRDTemplate import YieldAllRRDTemplates 159 for template in YieldAllRRDTemplates(pack.dmd, None): 160 for ds in template.datasources(): 161 if oldClass and self.dsClass and isinstance(ds, oldClass): 162 ds.__class__ = self.dsClass 163 if self.reIndex and isinstance(ds, self.dsClass): 164 ds.index_object()
165 166
167 -class ZenPack(ZenModelRM):
168 """ 169 The root of all ZenPacks: has no implementation, 170 but sits here to be the target of the Relation 171 """ 172 173 objectPaths = None 174 175 # Metadata 176 version = '0.1' 177 author = '' 178 organization = '' 179 url = '' 180 license = '' 181 compatZenossVers = '' 182 prevZenPackName = '' 183 prevZenPackVersion = None 184 185 # New-style zenpacks (eggs) have this set to True when they are 186 # first installed 187 eggPack = False 188 189 requires = () # deprecated 190 191 loaders = (ZPLObject(), ZPLReport(), ZPLDaemons(), ZPLBin(), ZPLLibExec(), 192 ZPLSkins(), ZPLDataSources(), ZPLLibraries(), ZPLAbout(), 193 ZPTriggerAction(), ZPZep()) 194 195 _properties = ZenModelRM._properties + ( 196 {'id':'objectPaths','type':'lines','mode':'w'}, 197 {'id':'version', 'type':'string', 'mode':'w', 'description':'ZenPack version'}, 198 {'id':'author', 'type':'string', 'mode':'w', 'description':'ZenPack author'}, 199 {'id':'organization', 'type':'string', 'mode':'w', 200 'description':'Sponsoring organization for the ZenPack'}, 201 {'id':'url', 'type':'string', 'mode':'w', 'description':'Homepage for the ZenPack'}, 202 {'id':'license', 'type':'string', 'mode':'w', 203 'description':'Name of the license under which this ZenPack is available'}, 204 {'id':'compatZenossVers', 'type':'string', 'mode':'w', 205 'description':'Which Zenoss versions can load this ZenPack'}, 206 ) 207 208 _relations = ( 209 # root is deprecated, use manager now instead 210 # root should be removed post zenoss 2.2 211 ('root', ToOne(ToManyCont, 'Products.ZenModel.DataRoot', 'packs')), 212 ('manager', 213 ToOne(ToManyCont, 'Products.ZenModel.ZenPackManager', 'packs')), 214 ("packables", ToMany(ToOne, "Products.ZenModel.ZenPackable", "pack")), 215 ) 216 217 factory_type_information = ( 218 { 'immediate_view' : 'viewPackDetail', 219 'factory' : 'manage_addZenPack', 220 'actions' : 221 ( 222 { 'id' : 'viewPackDetail' 223 , 'name' : 'Detail' 224 , 'action' : 'viewPackDetail' 225 , 'permissions' : ( "Manage DMD", ) 226 }, 227 ) 228 }, 229 ) 230 231 packZProperties = [ 232 ] 233 234 security = ClassSecurityInfo() 235 236
237 - def __init__(self, id, title=None, buildRelations=True):
238 #self.dependencies = {'zenpacksupport':''} 239 self.dependencies = {} 240 ZenModelRM.__init__(self, id, title, buildRelations)
241 242
243 - def install(self, app):
244 """ 245 Stop daemons, load any loaders, create zProperties, migrate and start daemons 246 247 @param app: ZenPack 248 @type app: ZenPack object 249 """ 250 self.stopDaemons() 251 for loader in self.loaders: 252 loader.load(self, app) 253 self.createZProperties(app) 254 previousVersion = self.prevZenPackVersion 255 self.migrate(previousVersion) 256 self.startDaemons()
257 258
259 - def upgrade(self, app):
260 """ 261 This is essentially an install() call except that a different method 262 is called on the loaders. 263 NB: Newer ZenPacks (egg style) do not use this upgrade method. Instead 264 the proper method is to remove(leaveObjects=True) and install again. 265 See ZenPackCmd.InstallDistAsZenPack(). 266 267 @param app: ZenPack 268 @type app: ZenPack object 269 """ 270 self.stopDaemons() 271 for loader in self.loaders: 272 loader.upgrade(self, app) 273 self.createZProperties(app) 274 self.migrate() 275 self.startDaemons()
276 277
278 - def remove(self, app, leaveObjects=False):
279 """ 280 This prepares the ZenPack for removal but does not actually remove 281 the instance from ZenPackManager.packs This is sometimes called during 282 the course of an upgrade where the loaders' unload methods need to 283 be run. 284 285 @param app: ZenPack 286 @type app: ZenPack object 287 @param leaveObjects: remove zProperties and things? 288 @type leaveObjects: boolean 289 """ 290 self.stopDaemons() 291 for loader in self.loaders: 292 loader.unload(self, app, leaveObjects) 293 if not leaveObjects: 294 self.removeZProperties(app) 295 self.removeCatalogedObjects(app)
296
297 - def backup(self, backupDir, logger):
298 """ 299 Method called when zenbackup is run. Override in ZenPack to add any 300 ZenPack-specific backup operations. 301 302 @param backupDir: Temporary directory that gets zipped to form backup 303 @type backupDir: string 304 @param logger: Backup log handler 305 @type logger: Log object 306 """ 307 pass
308
309 - def restore(self, backupDir, logger):
310 """ 311 Method called when zenrestore is run. Override in ZenPack to add any 312 ZenPack-specific restore operations. 313 314 @param backupDir: Temporary directory that contains the unzipped backup 315 @type backupDir: string 316 @param logger: Restore log handler 317 @type logger: Log object 318 """ 319 pass
320 321
322 - def migrate(self, previousVersion=None):
323 """ 324 Migrate to a new version 325 326 @param previousVersion: previous version number 327 @type previousVersion: string 328 """ 329 instances = [] 330 # find all the migrate modules 331 root = self.path("migrate") 332 for p, ds, fs in os.walk(root): 333 for f in fs: 334 if f.endswith('.py') and not f.startswith("__"): 335 path = os.path.join(p[len(root) + 1:], f) 336 log.debug("Loading %s", path) 337 sys.path.insert(0, p) 338 try: 339 try: 340 c = importClass(path[:-3].replace("/", ".")) 341 instances.append(c()) 342 finally: 343 sys.path.remove(p) 344 except ImportError, ex: 345 log.exception("Problem loading migration step %s", path) 346 # sort them by version number 347 def versionCmp(migrate1, migrate2): 348 return cmp(migrate1.version, migrate2.version)
349 instances.sort(versionCmp) 350 # install those that are newer than previous or our pack version 351 migrateCutoff = getVersionTupleFromString(self.version) 352 if previousVersion: 353 migrateCutoff = getVersionTupleFromString(previousVersion) 354 recover = [] 355 356 try: 357 for instance in instances: 358 if instance.version >= migrateCutoff: 359 recover.append(instance) 360 instance.migrate(self) 361 except Exception, ex: 362 # give the pack a chance to recover from problems 363 recover.reverse() 364 for r in recover: 365 r.recover(self) 366 raise
367 368
369 - def list(self, app):
370 """ 371 Show the list of loaders 372 373 @param app: ZenPack 374 @type app: ZenPack object 375 @return: list of loaders 376 @rtype: list of objects 377 """ 378 result = [] 379 for loader in self.loaders: 380 result.append((loader.name, 381 [item for item in loader.list(self, app)])) 382 return result
383
384 - def register_portlets(self):
385 """ 386 Registers ExtJS portlets from a ZenPack. Override in ZenPack. ID and 387 title are required, height and permissions are optional. See 388 ZenWidgets.PortletManager.register_extjsPortlet. 389 390 @return: List of dictionary objects describing a portlet 391 @rtype: List of dicts 392 """ 393 return []
394
395 - def createZProperties(self, app):
396 """ 397 Create zProperties in the ZenPack's self.packZProperties 398 399 @param app: ZenPack 400 @type app: ZenPack object 401 """ 402 # for brand new installs, define an instance for each of the zenpacks 403 # zprops on dmd.Devices 404 for name, value, pType in self.packZProperties: 405 if not app.zport.dmd.Devices.hasProperty(name): 406 app.zport.dmd.Devices._setProperty(name, value, pType)
407 408
409 - def removeZProperties(self, app):
410 """ 411 Remove any zProperties defined in the ZenPack 412 413 @param app: ZenPack 414 @type app: ZenPack object 415 """ 416 for name, value, pType in self.packZProperties: 417 app.zport.dmd.Devices._delProperty(name)
418 419
420 - def removeCatalogedObjects(self, app):
421 """ 422 Delete all objects in the zenPackPersistence catalog that are 423 associated with this zenpack. 424 425 @param app: ZenPack 426 @type app: ZenPack object 427 """ 428 objects = self.getCatalogedObjects() 429 for o in objects: 430 parent = aq_parent(o) 431 if parent: 432 parent._delObject(o.id)
433 434
435 - def getCatalogedObjects(self):
436 """ 437 Return a list of objects from the ZenPackPersistence catalog 438 for this zenpack. 439 """ 440 from ZenPackPersistence import GetCatalogedObjects 441 return GetCatalogedObjects(self.dmd, self.id) or []
442 443
444 - def zmanage_editProperties(self, REQUEST, redirect=False):
445 """ 446 Edit a ZenPack object 447 """ 448 449 if self.isEggPack(): 450 # Handle the dependencies fields and recreate self.dependencies 451 newDeps = {} 452 depNames = REQUEST.get('dependencies', []) 453 if not isinstance(depNames, list): 454 depNames = [depNames] 455 newDeps = {} 456 for depName in depNames: 457 fieldName = 'version_%s' % depName 458 vers = REQUEST.get(fieldName, '').strip() 459 if vers and vers[0] in string.digits: 460 vers = '==' + vers 461 try: 462 req = pkg_resources.Requirement.parse(depName + vers) 463 except ValueError: 464 messaging.IMessageSender(self).sendToBrowser( 465 'Error', 466 '%s is not a valid version specification.' % vers, 467 priority=messaging.WARNING 468 ) 469 return self.callZenScreen(REQUEST) 470 zp = self.dmd.ZenPackManager.packs._getOb(depName, None) 471 if not zp: 472 messaging.IMessageSender(self).sendToBrowser( 473 'Error', 474 '%s is not installed.' % depName, 475 priority=messaging.WARNING 476 ) 477 return self.callZenScreen(REQUEST) 478 if not req.__contains__(zp.version): 479 messaging.IMessageSender(self).sendToBrowser( 480 'Error', 481 ('The required version for %s (%s) ' % (depName, vers) + 482 'does not match the installed version (%s).' % 483 zp.version), 484 priority=messaging.WARNING 485 ) 486 return self.callZenScreen(REQUEST) 487 newDeps[depName] = vers 488 REQUEST.form[fieldName] = vers 489 self.dependencies = newDeps 490 # Check the value of compatZenossVers and the dependencies to 491 # make sure that they match installed versions 492 compatZenossVers = REQUEST.form['compatZenossVers'] or '' 493 if compatZenossVers: 494 if compatZenossVers[0] in string.digits: 495 compatZenossVers = '==' + compatZenossVers 496 try: 497 req = pkg_resources.Requirement.parse( 498 'zenoss%s' % compatZenossVers) 499 except ValueError: 500 messaging.IMessageSender(self).sendToBrowser( 501 'Error', 502 ('%s is not a valid version specification for Zenoss.' 503 % compatZenossVers), 504 priority=messaging.WARNING 505 ) 506 if not req.__contains__(ZENOSS_VERSION): 507 messaging.IMessageSender(self).sendToBrowser( 508 'Error', 509 ('%s does not match this version of Zenoss (%s).' % 510 (compatZenossVers, ZENOSS_VERSION)), 511 priority=messaging.WARNING 512 ) 513 return self.callZenScreen(REQUEST) 514 REQUEST.form['compatZenossVers'] = compatZenossVers 515 516 result = ZenModelRM.zmanage_editProperties(self, REQUEST, redirect) 517 518 if self.isEggPack(): 519 self.writeSetupValues() 520 self.buildEggInfo() 521 return result
522 523
524 - def manage_deletePackable(self, packables=(), REQUEST=None):
525 "Delete objects from this ZenPack" 526 from sets import Set 527 packables = Set(packables) 528 for obj in self.packables(): 529 if obj.getPrimaryUrlPath() in packables: 530 self.packables.removeRelation(obj) 531 if REQUEST: 532 messaging.IMessageSender(self).sendToBrowser( 533 'Objects Deleted', 534 'Deleted objects from ZenPack %s.' % self.id 535 ) 536 return self.callZenScreen(REQUEST)
537 538
539 - def manage_uploadPack(self, znetProject, description, REQUEST=None):
540 """ 541 Create a new release of the given project. 542 """ 543 import Products.ZenUtils.ZenPackCmd as ZenPackCmd 544 userSettings = self.dmd.ZenUsers.getUserSettings() 545 ZenPackCmd.UploadZenPack(self.dmd, self.id, znetProject, description, 546 userSettings.zenossNetUser, userSettings.zenossNetPassword) 547 if REQUEST: 548 messaging.IMessageSender(self).sendToBrowser( 549 'ZenPack Uploaded', 550 'ZenPack uploaded to Zenoss.net.' 551 ) 552 return self.callZenScreen(REQUEST)
553 554 555 security.declareProtected(ZEN_MANAGE_DMD, 'manage_exportPack')
556 - def manage_exportPack(self, download="no", REQUEST=None):
557 """ 558 Export the ZenPack to the /export directory 559 560 @param download: download to client's desktop? ('yes' vs anything else) 561 @type download: string 562 @type download: string 563 @param REQUEST: Zope REQUEST object 564 @type REQUEST: Zope REQUEST object 565 @todo: make this more modular 566 @todo: add better XML headers 567 """ 568 if not self.isDevelopment(): 569 msg = 'Only ZenPacks installed in development mode can be exported.' 570 if REQUEST: 571 messaging.IMessageSender(self).sendToBrowser( 572 'Error', msg, priority=messaging.WARNING) 573 return self.callZenScreen(REQUEST) 574 raise ZenPackDevelopmentModeExeption(msg) 575 576 from StringIO import StringIO 577 xml = StringIO() 578 579 # Write out packable objects 580 # TODO: When the DTD gets created, add the reference here 581 xml.write("""<?xml version="1.0"?>\n""") 582 xml.write("<objects>\n") 583 584 packables = eliminateDuplicates(self.packables()) 585 for obj in packables: 586 # obj = aq_base(obj) 587 xml.write('<!-- %r -->\n' % (obj.getPrimaryPath(),)) 588 obj.exportXml(xml,['devices','networks','pack'],True) 589 xml.write("</objects>\n") 590 path = self.path('objects') 591 if not os.path.isdir(path): 592 os.mkdir(path, 0750) 593 objects = file(os.path.join(path, 'objects.xml'), 'w') 594 objects.write(xml.getvalue()) 595 objects.close() 596 597 # Create skins dir if not there 598 path = self.path('skins') 599 if not os.path.isdir(path): 600 os.makedirs(path, 0750) 601 602 # Create __init__.py 603 init = self.path('__init__.py') 604 if not os.path.isfile(init): 605 fp = file(init, 'w') 606 fp.write( 607 ''' 608 import Globals 609 from Products.CMFCore.DirectoryView import registerDirectory 610 registerDirectory("skins", globals()) 611 ''') 612 fp.close() 613 614 if self.isEggPack(): 615 # Create the egg 616 exportDir = zenPath('export') 617 if not os.path.isdir(exportDir): 618 os.makedirs(exportDir, 0750) 619 eggPath = self.eggPath() 620 os.chdir(eggPath) 621 if os.path.isdir(os.path.join(eggPath, 'dist')): 622 os.system('rm -rf dist/*') 623 p = subprocess.Popen('python setup.py bdist_egg', 624 stderr=sys.stderr, 625 shell=True, 626 cwd=eggPath) 627 p.wait() 628 os.system('cp dist/* %s' % exportDir) 629 exportFileName = self.eggName() 630 else: 631 # Create about.txt 632 about = self.path(CONFIG_FILE) 633 values = {} 634 parser = ConfigParser.SafeConfigParser() 635 if os.path.isfile(about): 636 try: 637 parser.read(about) 638 values = dict(parser.items(CONFIG_SECTION_ABOUT)) 639 except ConfigParser.Error: 640 pass 641 current = [(p['id'], str(getattr(self, p['id'], '') or '')) 642 for p in self._properties] 643 values.update(dict(current)) 644 if not parser.has_section(CONFIG_SECTION_ABOUT): 645 parser.add_section(CONFIG_SECTION_ABOUT) 646 for key, value in values.items(): 647 parser.set(CONFIG_SECTION_ABOUT, key, value) 648 fp = file(about, 'w') 649 try: 650 parser.write(fp) 651 finally: 652 fp.close() 653 # Create the zip file 654 path = zenPath('export') 655 if not os.path.isdir(path): 656 os.makedirs(path, 0750) 657 from zipfile import ZipFile, ZIP_DEFLATED 658 zipFilePath = os.path.join(path, '%s.zip' % self.id) 659 zf = ZipFile(zipFilePath, 'w', ZIP_DEFLATED) 660 base = zenPath('Products') 661 for p, ds, fd in os.walk(self.path()): 662 if p.split('/')[-1].startswith('.'): continue 663 for f in fd: 664 if f.startswith('.'): continue 665 if f.endswith('.pyc'): continue 666 filename = os.path.join(p, f) 667 zf.write(filename, filename[len(base)+1:]) 668 ds[:] = [d for d in ds if d[0] != '.'] 669 zf.close() 670 exportFileName = '%s.zip' % self.id 671 672 if REQUEST: 673 dlLink = '- <a target="_blank" href="%s/manage_download">' \ 674 'Download Zenpack</a>' % self.absolute_url_path() 675 messaging.IMessageSender(self).sendToBrowser( 676 'ZenPack Exported', 677 'ZenPack exported to $ZENHOME/export/%s %s' % 678 (exportFileName, dlLink if download == 'yes' else ''), 679 messaging.CRITICAL if download == 'yes' else messaging.INFO 680 ) 681 return self.callZenScreen(REQUEST) 682 683 return exportFileName
684
685 - def manage_download(self, REQUEST):
686 """ 687 Download the already exported zenpack from $ZENHOME/export 688 689 @param REQUEST: Zope REQUEST object 690 @type REQUEST: Zope REQUEST object 691 """ 692 if self.isEggPack(): 693 filename = self.eggName() 694 else: 695 filename = '%s.zip' % self.id 696 path = os.path.join(zenPath('export'), filename) 697 if os.path.isfile(path): 698 REQUEST.RESPONSE.setHeader('content-type', 'application/zip') 699 REQUEST.RESPONSE.setHeader('content-disposition', 700 'attachment; filename=%s' % 701 filename) 702 zf = file(path, 'r') 703 try: 704 REQUEST.RESPONSE.write(zf.read()) 705 finally: 706 zf.close() 707 else: 708 messaging.IMessageSender(self).sendToBrowser( 709 'Error', 710 'An error has occurred. The ZenPack could not be exported.', 711 priority=messaging.WARNING 712 ) 713 return self.callZenScreen(REQUEST)
714 715
716 - def _getClassesByPath(self, name):
717 dsClasses = [] 718 for path, dirs, files in os.walk(self.path(name)): 719 dirs[:] = [d for d in dirs if not d.startswith('.')] 720 for f in files: 721 if not f.startswith('.') \ 722 and f.endswith('.py') \ 723 and not f == '__init__.py': 724 subPath = path[len(self.path()):] 725 parts = subPath.strip('/').split('/') 726 parts.append(f[:f.rfind('.')]) 727 modName = '.'.join([self.moduleName()] + parts) 728 dsClasses.append(importClass(modName)) 729 return dsClasses
730
731 - def getDataSourceClasses(self):
732 return self._getClassesByPath('datasources')
733
734 - def getThresholdClasses(self):
735 return self._getClassesByPath('thresholds')
736
737 - def getFilenames(self):
738 """ 739 Get the filenames of a ZenPack exclude .svn, .pyc and .xml files 740 """ 741 filenames = [] 742 for root, dirs, files in os.walk(self.path()): 743 if root.find('.svn') == -1: 744 for f in files: 745 if not f.endswith('.pyc') \ 746 and not f.endswith('.xml'): 747 filenames.append('%s/%s' % (root, f)) 748 return filenames
749 750
751 - def getDaemonNames(self):
752 """ 753 Return a list of daemons in the daemon subdirectory that should be 754 stopped/started before/after an install or an upgrade of the zenpack. 755 """ 756 daemonsDir = os.path.join(self.path(), 'daemons') 757 if os.path.isdir(daemonsDir): 758 daemons = [f for f in os.listdir(daemonsDir) 759 if os.path.isfile(os.path.join(daemonsDir,f))] 760 else: 761 daemons = [] 762 return daemons
763 764
765 - def stopDaemons(self):
766 """ 767 Stop all the daemons provided by this pack. 768 Called before an upgrade or a removal of the pack. 769 """ 770 return 771 for d in self.getDaemonNames(): 772 self.About.doDaemonAction(d, 'stop')
773 774
775 - def startDaemons(self):
776 """ 777 Start all the daemons provided by this pack. 778 Called after an upgrade or an install of the pack. 779 """ 780 return 781 for d in self.getDaemonNames(): 782 self.About.doDaemonAction(d, 'start')
783 784
785 - def restartDaemons(self):
786 """ 787 Restart all the daemons provided by this pack. 788 Called after an upgrade or an install of the pack. 789 """ 790 for d in self.getDaemonNames(): 791 self.About.doDaemonAction(d, 'restart')
792 793
794 - def path(self, *parts):
795 """ 796 Return the path to the ZenPack module. 797 It would be convenient to store the module name/path in the zenpack 798 object, however this would make things more complicated when the 799 name of the package under ZenPacks changed on us (do to a user edit.) 800 """ 801 if self.isEggPack(): 802 module = self.getModule() 803 return os.path.join(module.__path__[0], *[p.strip('/') for p in parts]) 804 return zenPath('Products', self.id, *parts)
805 806
807 - def isDevelopment(self):
808 """ 809 Return True if 810 1) the pack is an old-style ZenPack (not a Python egg) 811 or 812 2) the pack is a Python egg and is a source install (includes a 813 setup.py file) 814 815 Returns False otherwise. 816 """ 817 if self.isEggPack(): 818 return os.path.isfile(self.eggPath('setup.py')) 819 return True
820 821
822 - def isEggPack(self):
823 """ 824 Return True if this is a new-style (egg) zenpack, false otherwise 825 """ 826 return self.eggPack
827 828
829 - def moduleName(self):
830 """ 831 Return the importable dotted module name for this zenpack. 832 """ 833 if self.isEggPack(): 834 name = self.getModule().__name__ 835 else: 836 name = 'Products.%s' % self.id 837 return name
838 839 840 ########## 841 # Egg-related methods 842 # Nothing below here should be called for old-style zenpacks 843 ########## 844 845
846 - def writeSetupValues(self):
847 """ 848 Write appropriate values to the setup.py file 849 """ 850 import Products.ZenUtils.ZenPackCmd as ZenPackCmd 851 if not self.isEggPack(): 852 raise ZenPackException('Calling writeSetupValues on non-egg zenpack.') 853 # I don't think we need to specify packages anymore now that we are 854 # using find_packages() in setup.py 855 packages = [] 856 parts = self.id.split('.') 857 for i in range(len(parts)): 858 packages.append('.'.join(parts[:i+1])) 859 860 attrs = dict( 861 NAME=self.id, 862 VERSION=self.version, 863 AUTHOR=self.author, 864 LICENSE=self.license, 865 NAMESPACE_PACKAGES=packages[:-1], 866 PACKAGES = packages, 867 INSTALL_REQUIRES = ['%s%s' % d for d in self.dependencies.items()], 868 COMPAT_ZENOSS_VERS = self.compatZenossVers, 869 PREV_ZENPACK_NAME = self.prevZenPackName, 870 ) 871 ZenPackCmd.WriteSetup(self.eggPath('setup.py'), attrs)
872 873
874 - def buildEggInfo(self):
875 """ 876 Rebuild the egg info to update dependencies, etc 877 """ 878 p = subprocess.Popen('python setup.py egg_info', 879 stderr=sys.stderr, 880 shell=True, 881 cwd=self.eggPath()) 882 p.wait()
883 884
885 - def getDistribution(self):
886 """ 887 Return the distribution that provides this zenpack 888 """ 889 if not self.isEggPack(): 890 raise ZenPackException('Calling getDistribution on non-egg zenpack.') 891 return pkg_resources.get_distribution(self.id)
892 893
894 - def getEntryPoint(self):
895 """ 896 Return a tuple of (packName, packEntry) that comes from the 897 distribution entry map for zenoss.zenopacks. 898 """ 899 if not self.isEggPack(): 900 raise ZenPackException('Calling getEntryPoints on non-egg zenpack.') 901 dist = self.getDistribution() 902 entryMap = pkg_resources.get_entry_map(dist, 'zenoss.zenpacks') 903 if not entryMap or len(entryMap) > 1: 904 raise ZenPackException('A ZenPack egg must contain exactly one' 905 ' zenoss.zenpacks entry point. This egg appears to contain' 906 ' %s such entry points.' % len(entryMap)) 907 packName, packEntry = entryMap.items()[0] 908 return (packName, packEntry)
909 910
911 - def getModule(self):
912 """ 913 Get the loaded module from the given entry point. if not packEntry 914 then retrieve it. 915 """ 916 if not self.isEggPack(): 917 raise ZenPackException('Calling getModule on non-egg zenpack.') 918 _, packEntry = self.getEntryPoint() 919 return packEntry.load()
920 921
922 - def eggPath(self, *parts):
923 """ 924 Return the path to the egg supplying this zenpack 925 """ 926 if not self.isEggPack(): 927 raise ZenPackException('Calling eggPath on non-egg zenpack.') 928 d = self.getDistribution() 929 return os.path.join(d.location, *[p.strip('/') for p in parts])
930 931
932 - def eggName(self):
933 if not self.isEggPack(): 934 raise ZenPackException('Calling eggName on non-egg zenpack.') 935 d = self.getDistribution() 936 return d.egg_name() + '.egg'
937 938
939 - def shouldDeleteFilesOnRemoval(self):
940 """ 941 Return True if the egg itself should be deleted when this ZenPack 942 is removed from Zenoss. 943 If the ZenPack code resides in $ZENHOME/ZenPacks then it is 944 deleted, otherwise it is not. 945 """ 946 eggPath = self.eggPath() 947 oneFolderUp = eggPath[:eggPath.rfind('/')] 948 if oneFolderUp == zenPath('ZenPacks'): 949 delete = True 950 else: 951 delete = False 952 return delete
953 954
955 - def getPackageName(self):
956 """ 957 Return the name of submodule of zenpacks that contains this zenpack. 958 """ 959 if not self.isEggPack(): 960 raise ZenPackException('Calling getPackageName on a non-egg ' 961 'zenpack') 962 modName = self.moduleName() 963 return modName.split('.')[1]
964 965
966 - def getEligibleDependencies(self):
967 """ 968 Return a list of installed zenpacks that could be listed as 969 dependencies for this zenpack 970 """ 971 result = [] 972 for zp in self.dmd.ZenPackManager.packs(): 973 try: 974 if zp.id != self.id and zp.isEggPack(): 975 result.append(zp) 976 except AttributeError: 977 pass 978 return result
979 980
981 - def isInZenPacksDir(self):
982 """ 983 Return True if the egg is located in the ZenPacks directory, 984 False otherwise. 985 """ 986 zpDir = zenPath('ZenPacks') + '/' 987 eggDir = self.eggPath() 988 return eggDir.startswith(zpDir)
989 990
991 - def isBroken(self):
992 """ 993 Make sure that the ZenPack can be instantiated and that it 994 is physically present on the filesystem. 995 """ 996 # Well, if zope has an object to call this method on then 997 # we know that it can be instantiated. Templates will need 998 # to catch the case where a broken object won't have an isBroken 999 # method. 1000 # So here we just need to check for presence on the filesystem. 1001 try: 1002 if not os.path.isdir(self.path()): 1003 return True 1004 except pkg_resources.DistributionNotFound: 1005 return True 1006 1007 # If packables throws an exception the pack is broken. 1008 try: 1009 unused = self.packables() 1010 except Exception: 1011 return True 1012 1013 return False
1014 1015 1016 1017 # ZenPackBase is here for backwards compatibility with older installed 1018 # zenpacks that used it. ZenPackBase was rolled into ZenPack when we 1019 # started using about.txt files instead of ZenPack subclasses to set 1020 # zenpack metadata. 1021 ZenPackBase = ZenPack 1022 1023 InitializeClass(ZenPack) 1024