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