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 dlLink = '- <a target="_blank" href="%s/manage_download">' \ 638 'Download Zenpack</a>' % self.absolute_url_path() 639 messaging.IMessageSender(self).sendToBrowser( 640 'ZenPack Exported', 641 'ZenPack exported to $ZENHOME/export/%s %s' % 642 (exportFileName, dlLink if download == 'yes' else ''), 643 messaging.CRITICAL if download == 'yes' else messaging.INFO 644 ) 645 return self.callZenScreen(REQUEST) 646 647 return exportFileName
648
649 - def manage_download(self, REQUEST):
650 """ 651 Download the already exported zenpack from $ZENHOME/export 652 653 @param REQUEST: Zope REQUEST object 654 @type REQUEST: Zope REQUEST object 655 """ 656 if self.isEggPack(): 657 filename = self.eggName() 658 else: 659 filename = '%s.zip' % self.id 660 path = os.path.join(zenPath('export'), filename) 661 if os.path.isfile(path): 662 REQUEST.RESPONSE.setHeader('content-type', 'application/zip') 663 REQUEST.RESPONSE.setHeader('content-disposition', 664 'attachment; filename=%s' % 665 filename) 666 zf = file(path, 'r') 667 try: 668 REQUEST.RESPONSE.write(zf.read()) 669 finally: 670 zf.close() 671 else: 672 messaging.IMessageSender(self).sendToBrowser( 673 'Error', 674 'An error has occurred. The ZenPack could not be exported.', 675 priority=messaging.WARNING 676 ) 677 return self.callZenScreen(REQUEST)
678 679
680 - def _getClassesByPath(self, name):
681 dsClasses = [] 682 for path, dirs, files in os.walk(self.path(name)): 683 dirs[:] = [d for d in dirs if not d.startswith('.')] 684 for f in files: 685 if not f.startswith('.') \ 686 and f.endswith('.py') \ 687 and not f == '__init__.py': 688 subPath = path[len(self.path()):] 689 parts = subPath.strip('/').split('/') 690 parts.append(f[:f.rfind('.')]) 691 modName = '.'.join([self.moduleName()] + parts) 692 dsClasses.append(importClass(modName)) 693 return dsClasses
694
695 - def getDataSourceClasses(self):
696 return self._getClassesByPath('datasources')
697
698 - def getThresholdClasses(self):
699 return self._getClassesByPath('thresholds')
700
701 - def getFilenames(self):
702 """ 703 Get the filenames of a ZenPack exclude .svn, .pyc and .xml files 704 """ 705 filenames = [] 706 for root, dirs, files in os.walk(self.path()): 707 if root.find('.svn') == -1: 708 for f in files: 709 if not f.endswith('.pyc') \ 710 and not f.endswith('.xml'): 711 filenames.append('%s/%s' % (root, f)) 712 return filenames
713 714
715 - def getDaemonNames(self):
716 """ 717 Return a list of daemons in the daemon subdirectory that should be 718 stopped/started before/after an install or an upgrade of the zenpack. 719 """ 720 daemonsDir = os.path.join(self.path(), 'daemons') 721 if os.path.isdir(daemonsDir): 722 daemons = [f for f in os.listdir(daemonsDir) 723 if os.path.isfile(os.path.join(daemonsDir,f))] 724 else: 725 daemons = [] 726 return daemons
727 728
729 - def stopDaemons(self):
730 """ 731 Stop all the daemons provided by this pack. 732 Called before an upgrade or a removal of the pack. 733 """ 734 return 735 for d in self.getDaemonNames(): 736 self.About.doDaemonAction(d, 'stop')
737 738
739 - def startDaemons(self):
740 """ 741 Start all the daemons provided by this pack. 742 Called after an upgrade or an install of the pack. 743 """ 744 return 745 for d in self.getDaemonNames(): 746 self.About.doDaemonAction(d, 'start')
747 748
749 - def restartDaemons(self):
750 """ 751 Restart all the daemons provided by this pack. 752 Called after an upgrade or an install of the pack. 753 """ 754 for d in self.getDaemonNames(): 755 self.About.doDaemonAction(d, 'restart')
756 757
758 - def path(self, *parts):
759 """ 760 Return the path to the ZenPack module. 761 It would be convenient to store the module name/path in the zenpack 762 object, however this would make things more complicated when the 763 name of the package under ZenPacks changed on us (do to a user edit.) 764 """ 765 if self.isEggPack(): 766 module = self.getModule() 767 return os.path.join(module.__path__[0], *[p.strip('/') for p in parts]) 768 return zenPath('Products', self.id, *parts)
769 770
771 - def isDevelopment(self):
772 """ 773 Return True if 774 1) the pack is an old-style ZenPack (not a Python egg) 775 or 776 2) the pack is a Python egg and is a source install (includes a 777 setup.py file) 778 779 Returns False otherwise. 780 """ 781 if self.isEggPack(): 782 return os.path.isfile(self.eggPath('setup.py')) 783 return True
784 785
786 - def isEggPack(self):
787 """ 788 Return True if this is a new-style (egg) zenpack, false otherwise 789 """ 790 return self.eggPack
791 792
793 - def moduleName(self):
794 """ 795 Return the importable dotted module name for this zenpack. 796 """ 797 if self.isEggPack(): 798 name = self.getModule().__name__ 799 else: 800 name = 'Products.%s' % self.id 801 return name
802 803 804 ########## 805 # Egg-related methods 806 # Nothing below here should be called for old-style zenpacks 807 ########## 808 809
810 - def writeSetupValues(self):
811 """ 812 Write appropriate values to the setup.py file 813 """ 814 import Products.ZenUtils.ZenPackCmd as ZenPackCmd 815 if not self.isEggPack(): 816 raise ZenPackException('Calling writeSetupValues on non-egg zenpack.') 817 # I don't think we need to specify packages anymore now that we are 818 # using find_packages() in setup.py 819 packages = [] 820 parts = self.id.split('.') 821 for i in range(len(parts)): 822 packages.append('.'.join(parts[:i+1])) 823 824 attrs = dict( 825 NAME=self.id, 826 VERSION=self.version, 827 AUTHOR=self.author, 828 LICENSE=self.license, 829 NAMESPACE_PACKAGES=packages[:-1], 830 PACKAGES = packages, 831 INSTALL_REQUIRES = ['%s%s' % d for d in self.dependencies.items()], 832 COMPAT_ZENOSS_VERS = self.compatZenossVers, 833 PREV_ZENPACK_NAME = self.prevZenPackName, 834 ) 835 ZenPackCmd.WriteSetup(self.eggPath('setup.py'), attrs)
836 837
838 - def buildEggInfo(self):
839 """ 840 Rebuild the egg info to update dependencies, etc 841 """ 842 p = subprocess.Popen('python setup.py egg_info', 843 stderr=sys.stderr, 844 shell=True, 845 cwd=self.eggPath()) 846 p.wait()
847 848
849 - def getDistribution(self):
850 """ 851 Return the distribution that provides this zenpack 852 """ 853 if not self.isEggPack(): 854 raise ZenPackException('Calling getDistribution on non-egg zenpack.') 855 return pkg_resources.get_distribution(self.id)
856 857
858 - def getEntryPoint(self):
859 """ 860 Return a tuple of (packName, packEntry) that comes from the 861 distribution entry map for zenoss.zenopacks. 862 """ 863 if not self.isEggPack(): 864 raise ZenPackException('Calling getEntryPoints on non-egg zenpack.') 865 dist = self.getDistribution() 866 entryMap = pkg_resources.get_entry_map(dist, 'zenoss.zenpacks') 867 if not entryMap or len(entryMap) > 1: 868 raise ZenPackException('A ZenPack egg must contain exactly one' 869 ' zenoss.zenpacks entry point. This egg appears to contain' 870 ' %s such entry points.' % len(entryMap)) 871 packName, packEntry = entryMap.items()[0] 872 return (packName, packEntry)
873 874
875 - def getModule(self):
876 """ 877 Get the loaded module from the given entry point. if not packEntry 878 then retrieve it. 879 """ 880 if not self.isEggPack(): 881 raise ZenPackException('Calling getModule on non-egg zenpack.') 882 _, packEntry = self.getEntryPoint() 883 return packEntry.load()
884 885
886 - def eggPath(self, *parts):
887 """ 888 Return the path to the egg supplying this zenpack 889 """ 890 if not self.isEggPack(): 891 raise ZenPackException('Calling eggPath on non-egg zenpack.') 892 d = self.getDistribution() 893 return os.path.join(d.location, *[p.strip('/') for p in parts])
894 895
896 - def eggName(self):
897 if not self.isEggPack(): 898 raise ZenPackException('Calling eggName on non-egg zenpack.') 899 d = self.getDistribution() 900 return d.egg_name() + '.egg'
901 902
903 - def shouldDeleteFilesOnRemoval(self):
904 """ 905 Return True if the egg itself should be deleted when this ZenPack 906 is removed from Zenoss. 907 If the ZenPack code resides in $ZENHOME/ZenPacks then it is 908 deleted, otherwise it is not. 909 """ 910 eggPath = self.eggPath() 911 oneFolderUp = eggPath[:eggPath.rfind('/')] 912 if oneFolderUp == zenPath('ZenPacks'): 913 delete = True 914 else: 915 delete = False 916 return delete
917 918
919 - def getPackageName(self):
920 """ 921 Return the name of submodule of zenpacks that contains this zenpack. 922 """ 923 if not self.isEggPack(): 924 raise ZenPackException('Calling getPackageName on a non-egg ' 925 'zenpack') 926 modName = self.moduleName() 927 return modName.split('.')[1]
928 929
930 - def getEligibleDependencies(self):
931 """ 932 Return a list of installed zenpacks that could be listed as 933 dependencies for this zenpack 934 """ 935 result = [] 936 for zp in self.dmd.ZenPackManager.packs(): 937 try: 938 if zp.id != self.id and zp.isEggPack(): 939 result.append(zp) 940 except AttributeError: 941 pass 942 return result
943 944
945 - def isInZenPacksDir(self):
946 """ 947 Return True if the egg is located in the ZenPacks directory, 948 False otherwise. 949 """ 950 zpDir = zenPath('ZenPacks') + '/' 951 eggDir = self.eggPath() 952 return eggDir.startswith(zpDir)
953 954
955 - def isBroken(self):
956 """ 957 Make sure that the ZenPack can be instantiated and that it 958 is physically present on the filesystem. 959 """ 960 # Well, if zope has an object to call this method on then 961 # we know that it can be instantiated. Templates will need 962 # to catch the case where a broken object won't have an isBroken 963 # method. 964 # So here we just need to check for presence on the filesystem. 965 try: 966 if not os.path.isdir(self.path()): 967 return True 968 except pkg_resources.DistributionNotFound: 969 return True 970 971 # If packables throws an exception the pack is broken. 972 try: 973 unused = self.packables() 974 except Exception: 975 return True 976 977 return False
978 979 980 981 # ZenPackBase is here for backwards compatibility with older installed 982 # zenpacks that used it. ZenPackBase was rolled into ZenPack when we 983 # started using about.txt files instead of ZenPack subclasses to set 984 # zenpack metadata. 985 ZenPackBase = ZenPack 986 987 InitializeClass(ZenPack) 988