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

# Source Code for Module Products.ZenModel.ZenPack

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