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 or (at your
       8  # option) any later version as published by 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                  ZPTriggerAction(), ZPZep())
     194
     195      _properties = ZenModelRM._properties + (
     196          {'id':'objectPaths','type':'lines','mode':'w'},
     197          {'id':'version', 'type':'string', 'mode':'w', 'description':'ZenPack version'},
     198          {'id':'author', 'type':'string', 'mode':'w', 'description':'ZenPack author'},
     199          {'id':'organization', 'type':'string', 'mode':'w',
     200                'description':'Sponsoring organization for the ZenPack'},
     201          {'id':'url', 'type':'string', 'mode':'w', 'description':'Homepage for the ZenPack'},
     202          {'id':'license', 'type':'string', 'mode':'w',
     203                'description':'Name of the license under which this ZenPack is available'},
     204          {'id':'compatZenossVers', 'type':'string', 'mode':'w',
     205                'description':'Which Zenoss versions can load this ZenPack'},
     206      )
     207
     208      _relations =  (
     209          # root is deprecated, use manager now instead
     210          # root should be removed post zenoss 2.2
     211          ('root', ToOne(ToManyCont, 'Products.ZenModel.DataRoot', 'packs')),
     212          ('manager',
     213              ToOne(ToManyCont, 'Products.ZenModel.ZenPackManager', 'packs')),
     214          ("packables", ToMany(ToOne, "Products.ZenModel.ZenPackable", "pack")),
     215          )
     216
     217      factory_type_information = (
     218          { 'immediate_view' : 'viewPackDetail',
     219            'factory'        : 'manage_addZenPack',
     220            'actions'        :
     221            (
     222             { 'id'            : 'viewPackDetail'
     223               , 'name'          : 'Detail'
     224               , 'action'        : 'viewPackDetail'
     225               , 'permissions'   : ( "Manage DMD", )
     226               },
     227             )
     228            },
     229          )
     230
     231      packZProperties = [
     232          ]
     233
     234      security = ClassSecurityInfo()
     235
     236
     237 -    def __init__(self, id, title=None, buildRelations=True):
     238          #self.dependencies = {'zenpacksupport':''}
     239          self.dependencies = {}
     240          ZenModelRM.__init__(self, id, title, buildRelations)
     241
     242
     243 -    def install(self, app):
     244          """
     245          Stop daemons, load any loaders, create zProperties, migrate and start daemons
     246
     247          @param app: ZenPack
     248          @type app: ZenPack object
     249          """
     250          self.stopDaemons()
     251          for loader in self.loaders:
     252              loader.load(self, app)
     253          self.createZProperties(app)
     254          previousVersion = self.prevZenPackVersion
     255          self.migrate(previousVersion)
     256          self.startDaemons()
     257
     258
     259 -    def upgrade(self, app):
     260          """
     261          This is essentially an install() call except that a different method
     262          is called on the loaders.
     263          NB: Newer ZenPacks (egg style) do not use this upgrade method.  Instead
     264          the proper method is to remove(leaveObjects=True) and install again.
     265          See ZenPackCmd.InstallDistAsZenPack().
     266
     267          @param app: ZenPack
     268          @type app: ZenPack object
     269          """
     270          self.stopDaemons()
     271          for loader in self.loaders:
     272              loader.upgrade(self, app)
     273          self.createZProperties(app)
     274          self.migrate()
     275          self.startDaemons()
     276
     277
     278 -    def remove(self, app, leaveObjects=False):
     279          """
     280          This prepares the ZenPack for removal but does not actually remove
     281          the instance from ZenPackManager.packs  This is sometimes called during
     282          the course of an upgrade where the loaders' unload methods need to
     283          be run.
     284
     285          @param app: ZenPack
     286          @type app: ZenPack object
     287          @param leaveObjects: remove zProperties and things?
     288          @type leaveObjects: boolean
     289          """
     290          self.stopDaemons()
     291          for loader in self.loaders:
     292              loader.unload(self, app, leaveObjects)
     293          if not leaveObjects:
     294              self.removeZProperties(app)
     295              self.removeCatalogedObjects(app)
     296
     297 -    def backup(self, backupDir, logger):
     298          """
     299          Method called when zenbackup is run. Override in ZenPack to add any
     300          ZenPack-specific backup operations.
     301
     302          @param backupDir: Temporary directory that gets zipped to form backup
     303          @type backupDir: string
     304          @param logger: Backup log handler
     305          @type logger: Log object
     306          """
     307          pass
     308
     309 -    def restore(self, backupDir, logger):
     310          """
     311          Method called when zenrestore is run. Override in ZenPack to add any
     312          ZenPack-specific restore operations.
     313
     314          @param backupDir: Temporary directory that contains the unzipped backup
     315          @type backupDir: string
     316          @param logger: Restore log handler
     317          @type logger: Log object
     318          """
     319          pass
     320
     321
     322 -    def migrate(self, previousVersion=None):
     323          """
     324          Migrate to a new version
     325
     326          @param previousVersion: previous version number
     327          @type previousVersion: string
     328          """
     329          instances = []
     330          # find all the migrate modules
     331          root = self.path("migrate")
     332          for p, ds, fs in os.walk(root):
     333              for f in fs:
     334                  if f.endswith('.py') and not f.startswith("__"):
     335                      path = os.path.join(p[len(root) + 1:], f)
     336                      log.debug("Loading %s", path)
     337                      sys.path.insert(0, p)
     338                      try:
     339                          try:
     340                              c = importClass(path[:-3].replace("/", "."))
     341                              instances.append(c())
     342                          finally:
     343                              sys.path.remove(p)
     344                      except ImportError, ex:
     345                          log.exception("Problem loading migration step %s", path)
     346          # sort them by version number
     347          def versionCmp(migrate1, migrate2):
     348              return cmp(migrate1.version, migrate2.version)
     349          instances.sort(versionCmp)
     350          # install those that are newer than previous or our pack version
     351          migrateCutoff = getVersionTupleFromString(self.version)
     352          if previousVersion:
     353              migrateCutoff = getVersionTupleFromString(previousVersion)
     354          recover = []
     355
     356          try:
     357              for instance in instances:
     358                  if instance.version >= migrateCutoff:
     359                      recover.append(instance)
     360                      instance.migrate(self)
     361          except Exception, ex:
     362              # give the pack a chance to recover from problems
     363              recover.reverse()
     364              for r in recover:
     365                  r.recover(self)
     366              raise
     367
     368
     369 -    def list(self, app):
     370          """
     371          Show the list of loaders
     372
     373          @param app: ZenPack
     374          @type app: ZenPack object
     375          @return: list of loaders
     376          @rtype: list of objects
     377          """
     378          result = []
     379          for loader in self.loaders:
     380              result.append((loader.name,
     381                             [item for item in loader.list(self, app)]))
     382          return result
     383
     384 -    def register_portlets(self):
     385          """
     386          Registers ExtJS portlets from a ZenPack. Override in ZenPack. ID and
     387          title are required, height and permissions are optional. See
     388          ZenWidgets.PortletManager.register_extjsPortlet.
     389
     390          @return: List of dictionary objects describing a portlet
     391          @rtype: List of dicts
     392          """
     393          return []
     394
     395 -    def createZProperties(self, app):
     396          """
     397          Create zProperties in the ZenPack's self.packZProperties
     398
     399          @param app: ZenPack
     400          @type app: ZenPack object
     401          """
     402          # for brand new installs, define an instance for each of the zenpacks
     403          # zprops on dmd.Devices
     404          for name, value, pType in self.packZProperties:
     405              if not app.zport.dmd.Devices.hasProperty(name):
     406                  app.zport.dmd.Devices._setProperty(name, value, pType)
     407
     408
     409 -    def removeZProperties(self, app):
     410          """
     411          Remove any zProperties defined in the ZenPack
     412
     413          @param app: ZenPack
     414          @type app: ZenPack object
     415          """
     416          for name, value, pType in self.packZProperties:
     417              app.zport.dmd.Devices._delProperty(name)
     418
     419
     420 -    def removeCatalogedObjects(self, app):
     421          """
     422          Delete all objects in the zenPackPersistence catalog that are
     423          associated with this zenpack.
     424
     425          @param app: ZenPack
     426          @type app: ZenPack object
     427          """
     428          objects = self.getCatalogedObjects()
     429          for o in objects:
     430              parent = aq_parent(o)
     431              if parent:
     432                  parent._delObject(o.id)
     433
     434
     435 -    def getCatalogedObjects(self):
     436          """
     437          Return a list of objects from the ZenPackPersistence catalog
     438          for this zenpack.
     439          """
     440          from ZenPackPersistence import GetCatalogedObjects
     441          return GetCatalogedObjects(self.dmd, self.id) or []
     442
     443
     444 -    def zmanage_editProperties(self, REQUEST, redirect=False):
     445          """
     446          Edit a ZenPack object
     447          """
     448
     449          if self.isEggPack():
     450              # Handle the dependencies fields and recreate self.dependencies
     451              newDeps = {}
     452              depNames = REQUEST.get('dependencies', [])
     453              if not isinstance(depNames, list):
     454                  depNames = [depNames]
     455              newDeps = {}
     456              for depName in depNames:
     457                  fieldName = 'version_%s' % depName
     458                  vers = REQUEST.get(fieldName, '').strip()
     459                  if vers and vers[0] in string.digits:
     460                      vers = '==' + vers
     461                  try:
     462                      req = pkg_resources.Requirement.parse(depName + vers)
     463                  except ValueError:
     464                      messaging.IMessageSender(self).sendToBrowser(
     465                          'Error',
     466                          '%s is not a valid version specification.' % vers,
     467                          priority=messaging.WARNING
     468                      )
     469                      return self.callZenScreen(REQUEST)
     470                  zp = self.dmd.ZenPackManager.packs._getOb(depName, None)
     471                  if not zp:
     472                      messaging.IMessageSender(self).sendToBrowser(
     473                          'Error',
     474                          '%s is not installed.' % depName,
     475                          priority=messaging.WARNING
     476                      )
     477                      return self.callZenScreen(REQUEST)
     478                  if not req.__contains__(zp.version):
     479                      messaging.IMessageSender(self).sendToBrowser(
     480                          'Error',
     481                          ('The required version for %s (%s) ' % (depName, vers) +
     482                          'does not match the installed version (%s).' %
     483                           zp.version),
     484                          priority=messaging.WARNING
     485                      )
     486                      return self.callZenScreen(REQUEST)
     487                  newDeps[depName] = vers
     488                  REQUEST.form[fieldName] = vers
     489              self.dependencies = newDeps
     490              # Check the value of compatZenossVers and the dependencies to
     491              # make sure that they match installed versions
     492              compatZenossVers = REQUEST.form['compatZenossVers'] or ''
     493              if compatZenossVers:
     494                  if compatZenossVers[0] in string.digits:
     495                      compatZenossVers = '==' + compatZenossVers
     496                  try:
     497                      req = pkg_resources.Requirement.parse(
     498                                                  'zenoss%s' % compatZenossVers)
     499                  except ValueError:
     500                      messaging.IMessageSender(self).sendToBrowser(
     501                          'Error',
     502                          ('%s is not a valid version specification for Zenoss.'
     503                                                          % compatZenossVers),
     504                          priority=messaging.WARNING
     505                      )
     506                  if not req.__contains__(ZENOSS_VERSION):
     507                      messaging.IMessageSender(self).sendToBrowser(
     508                          'Error',
     509                          ('%s does not match this version of Zenoss (%s).' %
     510                              (compatZenossVers, ZENOSS_VERSION)),
     511                          priority=messaging.WARNING
     512                      )
     513                      return self.callZenScreen(REQUEST)
     514                  REQUEST.form['compatZenossVers'] = compatZenossVers
     515
     516          result =  ZenModelRM.zmanage_editProperties(self, REQUEST, redirect)
     517
     518          if self.isEggPack():
     519              self.writeSetupValues()
     520              self.buildEggInfo()
     521          return result
     522
     523
     524 -    def manage_deletePackable(self, packables=(), REQUEST=None):
     525          "Delete objects from this ZenPack"
     526          from sets import Set
     527          packables = Set(packables)
     528          for obj in self.packables():
     529              if obj.getPrimaryUrlPath() in packables:
     530                  self.packables.removeRelation(obj)
     531          if REQUEST:
     532              messaging.IMessageSender(self).sendToBrowser(
     533                  'Objects Deleted',
     534                  'Deleted objects from ZenPack %s.' % self.id
     535              )
     536              return self.callZenScreen(REQUEST)
     537
     538
     539 -    def manage_uploadPack(self, znetProject, description, REQUEST=None):
     540          """
     541          Create a new release of the given project.
     542          """
     543          import Products.ZenUtils.ZenPackCmd as ZenPackCmd
     544          userSettings = self.dmd.ZenUsers.getUserSettings()
     545          ZenPackCmd.UploadZenPack(self.dmd, self.id, znetProject, description,
     546              userSettings.zenossNetUser, userSettings.zenossNetPassword)
     547          if REQUEST:
     548              messaging.IMessageSender(self).sendToBrowser(
     549                  'ZenPack Uploaded',
     550                  'ZenPack uploaded to Zenoss.net.'
     551              )
     552              return self.callZenScreen(REQUEST)
     553
     554
     555      security.declareProtected(ZEN_MANAGE_DMD, 'manage_exportPack')
     556 -    def manage_exportPack(self, download="no", REQUEST=None):
     557          """
     558          Export the ZenPack to the /export directory
     559
     560          @param download: download to client's desktop? ('yes' vs anything else)
     561          @type download: string
     562          @type download: string
     563          @param REQUEST: Zope REQUEST object
     564          @type REQUEST: Zope REQUEST object
     565          @todo: make this more modular
     566          @todo: add better XML headers
     567          """
     568          if not self.isDevelopment():
     569              msg = 'Only ZenPacks installed in development mode can be exported.'
     570              if REQUEST:
     571                  messaging.IMessageSender(self).sendToBrowser(
     572                          'Error', msg, priority=messaging.WARNING)
     573                  return self.callZenScreen(REQUEST)
     574              raise ZenPackDevelopmentModeExeption(msg)
     575
     576          from StringIO import StringIO
     577          xml = StringIO()
     578
     579          # Write out packable objects
     580          # TODO: When the DTD gets created, add the reference here
     581          xml.write("""<?xml version="1.0"?>\n""")
     582          xml.write("<objects>\n")
     583
     584          packables = eliminateDuplicates(self.packables())
     585          for obj in packables:
     586              # obj = aq_base(obj)
     587              xml.write('<!-- %r -->\n' % (obj.getPrimaryPath(),))
     588              obj.exportXml(xml,['devices','networks','pack'],True)
     589          xml.write("</objects>\n")
     590          path = self.path('objects')
     591          if not os.path.isdir(path):
     592              os.mkdir(path, 0750)
     593          objects = file(os.path.join(path, 'objects.xml'), 'w')
     594          objects.write(xml.getvalue())
     595          objects.close()
     596
     597          # Create skins dir if not there
     598          path = self.path('skins')
     599          if not os.path.isdir(path):
     600              os.makedirs(path, 0750)
     601
     602          # Create __init__.py
     603          init = self.path('__init__.py')
     604          if not os.path.isfile(init):
     605              fp = file(init, 'w')
     606              fp.write(
     607  '''
     608  import Globals
     609  from Products.CMFCore.DirectoryView import registerDirectory
     610  registerDirectory("skins", globals())
     611  ''')
     612              fp.close()
     613
     614          if self.isEggPack():
     615              # Create the egg
     616              exportDir = zenPath('export')
     617              if not os.path.isdir(exportDir):
     618                  os.makedirs(exportDir, 0750)
     619              eggPath = self.eggPath()
     620              os.chdir(eggPath)
     621              if os.path.isdir(os.path.join(eggPath, 'dist')):
     622                  os.system('rm -rf dist/*')
     623              p = subprocess.Popen('python setup.py bdist_egg',
     624                              stderr=sys.stderr,
     625                              shell=True,
     626                              cwd=eggPath)
     627              p.wait()
     628              os.system('cp dist/* %s' % exportDir)
     629              exportFileName = self.eggName()
     630          else:
     631              # Create about.txt
     632              about = self.path(CONFIG_FILE)
     633              values = {}
     634              parser = ConfigParser.SafeConfigParser()
     635              if os.path.isfile(about):
     636                  try:
     637                      parser.read(about)
     638                      values = dict(parser.items(CONFIG_SECTION_ABOUT))
     639                  except ConfigParser.Error:
     640                      pass
     641              current = [(p['id'], str(getattr(self, p['id'], '') or ''))
     642                          for p in self._properties]
     643              values.update(dict(current))
     644              if not parser.has_section(CONFIG_SECTION_ABOUT):
     645                  parser.add_section(CONFIG_SECTION_ABOUT)
     646              for key, value in values.items():
     647                  parser.set(CONFIG_SECTION_ABOUT, key, value)
     648              fp = file(about, 'w')
     649              try:
     650                  parser.write(fp)
     651              finally:
     652                  fp.close()
     653              # Create the zip file
     654              path = zenPath('export')
     655              if not os.path.isdir(path):
     656                  os.makedirs(path, 0750)
     657              from zipfile import ZipFile, ZIP_DEFLATED
     658              zipFilePath = os.path.join(path, '%s.zip' % self.id)
     659              zf = ZipFile(zipFilePath, 'w', ZIP_DEFLATED)
     660              base = zenPath('Products')
     661              for p, ds, fd in os.walk(self.path()):
     662                  if p.split('/')[-1].startswith('.'): continue
     663                  for f in fd:
     664                      if f.startswith('.'): continue
     665                      if f.endswith('.pyc'): continue
     666                      filename = os.path.join(p, f)
     667                      zf.write(filename, filename[len(base)+1:])
     668                  ds[:] = [d for d in ds if d[0] != '.']
     669              zf.close()
     670              exportFileName = '%s.zip' % self.id
     671
     672          if REQUEST:
     673              dlLink = '- <a target="_blank" href="%s/manage_download">' \
     674                       'Download Zenpack</a>' % self.absolute_url_path()
     675              messaging.IMessageSender(self).sendToBrowser(
     676                  'ZenPack Exported',
     677                  'ZenPack exported to $ZENHOME/export/%s %s' %
     678                          (exportFileName, dlLink if download == 'yes' else ''),
     679                  messaging.CRITICAL if download == 'yes' else messaging.INFO
     680              )
     681              return self.callZenScreen(REQUEST)
     682
     683          return exportFileName
     684
     685 -    def manage_download(self, REQUEST):
     686          """
     687          Download the already exported zenpack from $ZENHOME/export
     688
     689          @param REQUEST: Zope REQUEST object
     690          @type REQUEST: Zope REQUEST object
     691          """
     692          if self.isEggPack():
     693              filename = self.eggName()
     694          else:
     695              filename = '%s.zip' % self.id
     696          path = os.path.join(zenPath('export'), filename)
     697          if os.path.isfile(path):
     698              REQUEST.RESPONSE.setHeader('content-type', 'application/zip')
     699              REQUEST.RESPONSE.setHeader('content-disposition',
     700                                          'attachment; filename=%s' %
     701                                          filename)
     702              zf = file(path, 'r')
     703              try:
     704                  REQUEST.RESPONSE.write(zf.read())
     705              finally:
     706                  zf.close()
     707          else:
     708              messaging.IMessageSender(self).sendToBrowser(
     709                  'Error',
     710                  'An error has occurred. The ZenPack could not be exported.',
     711                  priority=messaging.WARNING
     712              )
     713              return self.callZenScreen(REQUEST)
     714
     715
     716 -    def _getClassesByPath(self, name):
     717          dsClasses = []
     718          for path, dirs, files in os.walk(self.path(name)):
     719              dirs[:] = [d for d in dirs if not d.startswith('.')]
     720              for f in files:
     721                  if not f.startswith('.') \
     722                          and f.endswith('.py') \
     723                          and not f == '__init__.py':
     724                      subPath = path[len(self.path()):]
     725                      parts = subPath.strip('/').split('/')
     726                      parts.append(f[:f.rfind('.')])
     727                      modName = '.'.join([self.moduleName()] + parts)
     728                      dsClasses.append(importClass(modName))
     729          return dsClasses
     730
     731 -    def getDataSourceClasses(self):
     732          return self._getClassesByPath('datasources')
     733
     734 -    def getThresholdClasses(self):
     735          return self._getClassesByPath('thresholds')
     736
     737 -    def getFilenames(self):
     738          """
     739          Get the filenames of a ZenPack exclude .svn, .pyc and .xml files
     740          """
     741          filenames = []
     742          for root, dirs, files in os.walk(self.path()):
     743              if root.find('.svn') == -1:
     744                      for f in files:
     745                          if not f.endswith('.pyc') \
     746                          and not f.endswith('.xml'):
     747                              filenames.append('%s/%s' % (root, f))
     748          return filenames
     749
     750
     751 -    def getDaemonNames(self):
     752          """
     753          Return a list of daemons in the daemon subdirectory that should be
     754          stopped/started before/after an install or an upgrade of the zenpack.
     755          """
     756          daemonsDir = os.path.join(self.path(), 'daemons')
     757          if os.path.isdir(daemonsDir):
     758              daemons = [f for f in os.listdir(daemonsDir)
     759                          if os.path.isfile(os.path.join(daemonsDir,f))]
     760          else:
     761              daemons = []
     762          return daemons
     763
     764
     765 -    def stopDaemons(self):
     766          """
     767          Stop all the daemons provided by this pack.
     768          Called before an upgrade or a removal of the pack.
     769          """
     770          return
     771          for d in self.getDaemonNames():
     772              self.About.doDaemonAction(d, 'stop')
     773
     774
     775 -    def startDaemons(self):
     776          """
     777          Start all the daemons provided by this pack.
     778          Called after an upgrade or an install of the pack.
     779          """
     780          return
     781          for d in self.getDaemonNames():
     782              self.About.doDaemonAction(d, 'start')
     783
     784
     785 -    def restartDaemons(self):
     786          """
     787          Restart all the daemons provided by this pack.
     788          Called after an upgrade or an install of the pack.
     789          """
     790          for d in self.getDaemonNames():
     791              self.About.doDaemonAction(d, 'restart')
     792
     793
     794 -    def path(self, *parts):
     795          """
     796          Return the path to the ZenPack module.
     797          It would be convenient to store the module name/path in the zenpack
     798          object, however this would make things more complicated when the
     799          name of the package under ZenPacks changed on us (do to a user edit.)
     800          """
     801          if self.isEggPack():
     802              module = self.getModule()
     803              return os.path.join(module.__path__[0], *[p.strip('/') for p in parts])
     804          return zenPath('Products', self.id, *parts)
     805
     806
     807 -    def isDevelopment(self):
     808          """
     809          Return True if
     810          1) the pack is an old-style ZenPack (not a Python egg)
     811          or
     812          2) the pack is a Python egg and is a source install (includes a
     813          setup.py file)
     814
     815          Returns False otherwise.
     816          """
     817          if self.isEggPack():
     818              return os.path.isfile(self.eggPath('setup.py'))
     819          return True
     820
     821
     822 -    def isEggPack(self):
     823          """
     824          Return True if this is a new-style (egg) zenpack, false otherwise
     825          """
     826          return self.eggPack
     827
     828
     829 -    def moduleName(self):
     830          """
     831          Return the importable dotted module name for this zenpack.
     832          """
     833          if self.isEggPack():
     834              name = self.getModule().__name__
     835          else:
     836              name = 'Products.%s' % self.id
     837          return name
     838
     839
     840      ##########
     841      # Egg-related methods
     842      # Nothing below here should be called for old-style zenpacks
     843      ##########
     844
     845
     846 -    def writeSetupValues(self):
     847          """
     848          Write appropriate values to the setup.py file
     849          """
     850          import Products.ZenUtils.ZenPackCmd as ZenPackCmd
     851          if not self.isEggPack():
     852              raise ZenPackException('Calling writeSetupValues on non-egg zenpack.')
     853          # I don't think we need to specify packages anymore now that we are
     854          # using find_packages() in setup.py
     855          packages = []
     856          parts = self.id.split('.')
     857          for i in range(len(parts)):
     858              packages.append('.'.join(parts[:i+1]))
     859
     860          attrs = dict(
     861              NAME=self.id,
     862              VERSION=self.version,
     863              AUTHOR=self.author,
     864              LICENSE=self.license,
     865              NAMESPACE_PACKAGES=packages[:-1],
     866              PACKAGES = packages,
     867              INSTALL_REQUIRES = ['%s%s' % d for d in self.dependencies.items()],
     868              COMPAT_ZENOSS_VERS = self.compatZenossVers,
     869              PREV_ZENPACK_NAME = self.prevZenPackName,
     870              )
     871          ZenPackCmd.WriteSetup(self.eggPath('setup.py'), attrs)
     872
     873
     874 -    def buildEggInfo(self):
     875          """
     876          Rebuild the egg info to update dependencies, etc
     877          """
     878          p = subprocess.Popen('python setup.py egg_info',
     879                          stderr=sys.stderr,
     880                          shell=True,
     881                          cwd=self.eggPath())
     882          p.wait()
     883
     884
     885 -    def getDistribution(self):
     886          """
     887          Return the distribution that provides this zenpack
     888          """
     889          if not self.isEggPack():
     890              raise ZenPackException('Calling getDistribution on non-egg zenpack.')
     891          return pkg_resources.get_distribution(self.id)
     892
     893
     894 -    def getEntryPoint(self):
     895          """
     896          Return a tuple of (packName, packEntry) that comes from the
     897          distribution entry map for zenoss.zenopacks.
     898          """
     899          if not self.isEggPack():
     900              raise ZenPackException('Calling getEntryPoints on non-egg zenpack.')
     901          dist = self.getDistribution()
     902          entryMap = pkg_resources.get_entry_map(dist, 'zenoss.zenpacks')
     903          if not entryMap or len(entryMap) > 1:
     904              raise ZenPackException('A ZenPack egg must contain exactly one'
     905                      ' zenoss.zenpacks entry point.  This egg appears to contain'
     906                      ' %s such entry points.' % len(entryMap))
     907          packName, packEntry = entryMap.items()[0]
     908          return (packName, packEntry)
     909
     910
     911 -    def getModule(self):
     912          """
     913          Get the loaded module from the given entry point.  if not packEntry
     914          then retrieve it.
     915          """
     916          if not self.isEggPack():
     917              raise ZenPackException('Calling getModule on non-egg zenpack.')
     918          _, packEntry = self.getEntryPoint()
     919          return packEntry.load()
     920
     921
     922 -    def eggPath(self, *parts):
     923          """
     924          Return the path to the egg supplying this zenpack
     925          """
     926          if not self.isEggPack():
     927              raise ZenPackException('Calling eggPath on non-egg zenpack.')
     928          d = self.getDistribution()
     929          return os.path.join(d.location, *[p.strip('/') for p in parts])
     930
     931
     932 -    def eggName(self):
     933          if not self.isEggPack():
     934              raise ZenPackException('Calling eggName on non-egg zenpack.')
     935          d = self.getDistribution()
     936          return d.egg_name() + '.egg'
     937
     938
     939 -    def shouldDeleteFilesOnRemoval(self):
     940          """
     941          Return True if the egg itself should be deleted when this ZenPack
     942          is removed from Zenoss.
     943          If the ZenPack code resides in $ZENHOME/ZenPacks then it is
     944          deleted, otherwise it is not.
     945          """
     946          eggPath = self.eggPath()
     947          oneFolderUp = eggPath[:eggPath.rfind('/')]
     948          if oneFolderUp == zenPath('ZenPacks'):
     949              delete = True
     950          else:
     951              delete = False
     952          return delete
     953
     954
     955 -    def getPackageName(self):
     956          """
     957          Return the name of submodule of zenpacks that contains this zenpack.
     958          """
     959          if not self.isEggPack():
     960              raise ZenPackException('Calling getPackageName on a non-egg '
     961                                          'zenpack')
     962          modName = self.moduleName()
     963          return modName.split('.')[1]
     964
     965
     966 -    def getEligibleDependencies(self):
     967          """
     968          Return a list of installed zenpacks that could be listed as
     969          dependencies for this zenpack
     970          """
     971          result = []
     972          for zp in self.dmd.ZenPackManager.packs():
     973              try:
     974                  if zp.id != self.id and zp.isEggPack():
     975                      result.append(zp)
     976              except AttributeError:
     977                 pass
     978          return result
     979
     980
     981 -    def isInZenPacksDir(self):
     982          """
     983          Return True if the egg is located in the ZenPacks directory,
     984          False otherwise.
     985          """
     986          zpDir = zenPath('ZenPacks') + '/'
     987          eggDir = self.eggPath()
     988          return eggDir.startswith(zpDir)
     989
     990
     991 -    def isBroken(self):
     992          """
     993          Make sure that the ZenPack can be instantiated and that it
     994          is physically present on the filesystem.
     995          """
     996          # Well, if zope has an object to call this method on then
     997          # we know that it can be instantiated.  Templates will need
     998          # to catch the case where a broken object won't have an isBroken
     999          # method.
    1000          # So here we just need to check for presence on the filesystem.
    1001          try:
    1002              if not os.path.isdir(self.path()):
    1003                  return True
    1004          except pkg_resources.DistributionNotFound:
    1005              return True
    1006
    1007          # If packables throws an exception the pack is broken.
    1008          try:
    1009              unused = self.packables()
    1010          except Exception:
    1011              return True
    1012
    1013          return False
    1014
    1015
    1016
    1017  # ZenPackBase is here for backwards compatibility with older installed
    1018  # zenpacks that used it.  ZenPackBase was rolled into ZenPack when we
    1019  # started using about.txt files instead of ZenPack subclasses to set
    1020  # zenpack metadata.
    1021  ZenPackBase = ZenPack
    1022
    1023  InitializeClass(ZenPack)
    1024