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

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