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