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 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 -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 194 _properties = ZenModelRM._properties + ( 195 {'id':'objectPaths','type':'lines','mode':'w'}, 196 {'id':'version', 'type':'string', 'mode':'w', 'description':'ZenPack version'}, 197 {'id':'author', 'type':'string', 'mode':'w', 'description':'ZenPack author'}, 198 {'id':'organization', 'type':'string', 'mode':'w', 199 'description':'Sponsoring organization for the ZenPack'}, 200 {'id':'url', 'type':'string', 'mode':'w', 'description':'Homepage for the ZenPack'}, 201 {'id':'license', 'type':'string', 'mode':'w', 202 'description':'Name of the license under which this ZenPack is available'}, 203 {'id':'compatZenossVers', 'type':'string', 'mode':'w', 204 'description':'Which Zenoss versions can load this ZenPack'}, 205 ) 206 207 _relations = ( 208 # root is deprecated, use manager now instead 209 # root should be removed post zenoss 2.2 210 ('root', ToOne(ToManyCont, 'Products.ZenModel.DataRoot', 'packs')), 211 ('manager', 212 ToOne(ToManyCont, 'Products.ZenModel.ZenPackManager', 'packs')), 213 ("packables", ToMany(ToOne, "Products.ZenModel.ZenPackable", "pack")), 214 ) 215 216 factory_type_information = ( 217 { 'immediate_view' : 'viewPackDetail', 218 'factory' : 'manage_addZenPack', 219 'actions' : 220 ( 221 { 'id' : 'viewPackDetail' 222 , 'name' : 'Detail' 223 , 'action' : 'viewPackDetail' 224 , 'permissions' : ( "Manage DMD", ) 225 }, 226 ) 227 }, 228 ) 229 230 packZProperties = [ 231 ] 232 233 security = ClassSecurityInfo() 234 235
236 - def __init__(self, id, title=None, buildRelations=True):
237 #self.dependencies = {'zenpacksupport':''} 238 self.dependencies = {} 239 ZenModelRM.__init__(self, id, title, buildRelations)
240 241
242 - def install(self, app):
243 """ 244 Stop daemons, load any loaders, create zProperties, migrate and start daemons 245 246 @param app: ZenPack 247 @type app: ZenPack object 248 """ 249 self.stopDaemons() 250 for loader in self.loaders: 251 loader.load(self, app) 252 self.createZProperties(app) 253 previousVersion = self.prevZenPackVersion 254 self.migrate(previousVersion) 255 self.startDaemons()
256 257
258 - def upgrade(self, app):
259 """ 260 This is essentially an install() call except that a different method 261 is called on the loaders. 262 NB: Newer ZenPacks (egg style) do not use this upgrade method. Instead 263 the proper method is to remove(leaveObjects=True) and install again. 264 See ZenPackCmd.InstallDistAsZenPack(). 265 266 @param app: ZenPack 267 @type app: ZenPack object 268 """ 269 self.stopDaemons() 270 for loader in self.loaders: 271 loader.upgrade(self, app) 272 self.createZProperties(app) 273 self.migrate() 274 self.startDaemons()
275 276
277 - def remove(self, app, leaveObjects=False):
278 """ 279 This prepares the ZenPack for removal but does not actually remove 280 the instance from ZenPackManager.packs This is sometimes called during 281 the course of an upgrade where the loaders' unload methods need to 282 be run. 283 284 @param app: ZenPack 285 @type app: ZenPack object 286 @param leaveObjects: remove zProperties and things? 287 @type leaveObjects: boolean 288 """ 289 self.stopDaemons() 290 for loader in self.loaders: 291 loader.unload(self, app, leaveObjects) 292 if not leaveObjects: 293 self.removeZProperties(app) 294 self.removeCatalogedObjects(app)
295 296
297 - def migrate(self, previousVersion=None):
298 """ 299 Migrate to a new version 300 301 @param previousVersion: previous version number 302 @type previousVersion: string 303 """ 304 instances = [] 305 # find all the migrate modules 306 root = self.path("migrate") 307 for p, ds, fs in os.walk(root): 308 for f in fs: 309 if f.endswith('.py') and not f.startswith("__"): 310 path = os.path.join(p[len(root) + 1:], f) 311 log.debug("Loading %s", path) 312 sys.path.insert(0, p) 313 try: 314 try: 315 c = importClass(path[:-3].replace("/", ".")) 316 instances.append(c()) 317 finally: 318 sys.path.remove(p) 319 except ImportError, ex: 320 log.exception("Problem loading migration step %s", path) 321 # sort them by version number 322 def versionCmp(migrate1, migrate2): 323 return cmp(migrate1.version, migrate2.version)
324 instances.sort(versionCmp) 325 # install those that are newer than previous or our pack version 326 migrateCutoff = getVersionTupleFromString(self.version) 327 if previousVersion: 328 migrateCutoff = getVersionTupleFromString(previousVersion) 329 recover = [] 330 331 try: 332 for instance in instances: 333 if instance.version >= migrateCutoff: 334 recover.append(instance) 335 instance.migrate(self) 336 except Exception, ex: 337 # give the pack a chance to recover from problems 338 recover.reverse() 339 for r in recover: 340 r.recover(self) 341 raise
342 343
344 - def list(self, app):
345 """ 346 Show the list of loaders 347 348 @param app: ZenPack 349 @type app: ZenPack object 350 @return: list of loaders 351 @rtype: list of objects 352 """ 353 result = [] 354 for loader in self.loaders: 355 result.append((loader.name, 356 [item for item in loader.list(self, app)])) 357 return result
358
359 - def createZProperties(self, app):
360 """ 361 Create zProperties in the ZenPack's self.packZProperties 362 363 @param app: ZenPack 364 @type app: ZenPack object 365 """ 366 # for brand new installs, define an instance for each of the zenpacks 367 # zprops on dmd.Devices 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 if not os.path.isdir(self.path()): 965 return True 966 967 # If packables throws an exception the pack is broken. 968 try: 969 unused = self.packables() 970 except Exception: 971 return True 972 973 return False
974 975 976 977 # ZenPackBase is here for backwards compatibility with older installed 978 # zenpacks that used it. ZenPackBase was rolled into ZenPack when we 979 # started using about.txt files instead of ZenPack subclasses to set 980 # zenpack metadata. 981 ZenPackBase = ZenPack 982 983 InitializeClass(ZenPack) 984