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

Source Code for Module Products.ZenModel.ZenPack

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