Package Products :: Package ZenUtils :: Module zenpack |
[hide private]
[frames] | no frames]

# Source Code for Module Products.ZenUtils.zenpack

      1  ###########################################################################
      2  #
      3  # This program is part of Zenoss Core, an open source monitoring platform.
      4  # Copyright (C) 2007, Zenoss Inc.
      5  #
      6  # This program is free software; you can redistribute it and/or modify it
      7  # under the terms of the GNU General Public License version 2 as published by
      8  # the Free Software Foundation.
      9  #
     10  # For complete information please visit: http://www.zenoss.com/oss/
     11  #
     12  ###########################################################################
     13  __doc__ = "Manage ZenPacks"
     14
     15  import os, sys
     16  import logging
     17  import ConfigParser
     18  from zipfile import ZipFile
     19  from StringIO import StringIO
     20
     21  import Globals
     22  import transaction
     23  from ZODB.POSException import ConflictError
     24
     25  from Products.ZenModel.ZenPack import ZenPack, ZenPackException
     26  from Products.ZenModel.ZenPack import ZenPackNeedMigrateException
     27  from Products.ZenUtils.ZenScriptBase import ZenScriptBase
     28  from Products.ZenUtils.Utils import cleanupSkins, zenPath
     29  import Products.ZenModel.ZenPackLoader as ZPL
     30  from Products.ZenModel.ZenPackLoader import CONFIG_FILE, CONFIG_SECTION_ABOUT
     31  import ZenPackCmd as EggPackCmd
     32
     33  HIGHER_THAN_CRITICAL = 100
     34
     35 -def RemoveZenPack(dmd, packName, log=None,
     36                          skipDepsCheck=False, leaveObjects=True,
     37                          deleteFiles=True):
     38      if log:
     39          log.debug('Removing Pack "%s"' % packName)
     40      if not skipDepsCheck:
     41          for pack in dmd.ZenPackManager.packs():
     42              if packName in pack.requires:
     43                  raise ZenPackException('Pack %s depends on pack %s, '
     44                                          'not removing' % (pack.id, packName))
     45      zp = None
     46      try:
     47          zp = dmd.ZenPackManager.packs._getOb(packName)
     48      except AttributeError, ex:
     49          # Pack not in zeo, might still exist in filesystem
     50          if log:
     51              log.debug('No ZenPack named %s in zeo' % packName)
     52      if zp:
     53          try:
     54              # In 2.2 we added an additional parameter to the remove method.
     55              # Any ZenPack subclasses that haven't been updated to take the new
     56              # parameter will throw a TypeError.
     57              # The newer version of zenoss-supplied ZenPacks monkey patch
     58              # older installed versions during an upgrade so that the remove
     59              # accepts the leaveObjects method.
     60              zp.remove(dmd, leaveObjects=True)
     61          except TypeError:
     62              zp.remove(dmd)
     63          dmd.ZenPackManager.packs._delObject(packName)
     64      root = zenPath('Products', packName)
     65      if deleteFiles:
     66          if log:
     67              log.debug('Removing %s' % root)
     68          recurse = ""
     69          if os.path.isdir(root):
     70              recurse = "r"
     71          os.system('rm -%sf %s' % (recurse, root))
     72      cleanupSkins(dmd)
     73      return True
     74
     75
     76 -class ZenPackCmd(ZenScriptBase):
     77      "Manage ZenPacks"
     78
     79
     80 -    def run(self):
     81          "Execute the user's request"
     82          if self.args:
     83              print "Require one of --install, --remove or --list flags."
     84              self.parser.print_help()
     85              return
     86
     87          if self.options.installPackName:
     88              eggInstall = (self.options.installPackName.lower().endswith('.egg')
     89                  or os.path.exists(os.path.join(self.options.installPackName,
     90                                                  'setup.py')))
     91
     92          # files-only just lays the files down and doesn't "install"
     93          # them into zeo
     94          class ZPProxy:
     95              def __init__(self, zpId):
     96                  self.id = zpId
     97              def path(self, *parts):
     98                  return zenPath('Products', self.id, *parts)
     99          if self.options.installPackName and self.options.filesOnly:
    100              if eggInstall:
    101                  return  EggPackCmd.InstallZenPack(None,
    102                              self.options.installPackName,
    103                                  filesOnly=True)
    104              packName = self.extract(self.options.installPackName)
    105              proxy = ZPProxy(packName)
    106              for loader in (ZPL.ZPLDaemons(), ZPL.ZPLBin(), ZPL.ZPLLibExec()):
    107                  loader.load(proxy, None)
    108              return
    109          if self.options.removePackName and self.options.filesOnly:
    110              # Remove files-only is not yet supported for egg zenpacks
    111              # todo
    112              proxy = ZPProxy(self.options.removePackName)
    113              for loader in (ZPL.ZPLDaemons(), ZPL.ZPLBin(), ZPL.ZPLLibExec()):
    114                  loader.unload(proxy, None)
    115              os.system('rm -rf %s' % zenPath('Products',
    116                                              self.options.removePackName))
    117              return
    118
    119
    120
    121          self.connect()
    122
    123          if not getattr(self.dmd, 'ZenPackManager', None):
    124              raise ZenPackNeedMigrateException('Your Zenoss database appears'
    125                  ' to be out of date. Try running zenmigrate to update.')
    126
    127          if self.options.installPackName:
    128              if eggInstall:
    129                  return EggPackCmd.InstallEggAndZenPack(
    130                      self.dmd,
    131                      self.options.installPackName,
    132                      link=self.options.link,
    133                      filesOnly=False,
    134                      previousVersion= self.options.previousVersion)
    135              if not self.preInstallCheck():
    136                  self.stop('%s not installed' % self.options.installPackName)
    137              if os.path.isfile(self.options.installPackName):
    138                  packName = self.extract(self.options.installPackName)
    139              elif os.path.isdir(self.options.installPackName):
    140                  if self.options.link:
    141                      packName = self.linkDir(self.options.installPackName)
    142                  else:
    143                      packName = self.copyDir(self.options.installPackName)
    144              else:
    145                  self.stop('%s does not appear to be a valid file or directory.'
    146                            % self.options.installPackName)
    147              # We want to make sure all zenpacks have a skins directory and that it
    148              # is registered. The zip file may not contain a skins directory, so we
    149              # create one here if need be.  The directory should be registered
    150              # by the zenpack's __init__.py and the skin should be registered
    151              # by ZPLSkins loader.
    152              skinsSubdir = zenPath('Products', packName, 'skins', packName)
    153              if not os.path.exists(skinsSubdir):
    154                  os.makedirs(skinsSubdir, 0750)
    155              self.install(packName)
    156
    157  #        elif self.options.fetch:
    158  #            return EggPackCmd.FetchAndInstallZenPack(self.dmd,
    159  #                                self.options.fetch, self.options.fetchVers)
    160          elif self.options.removePackName:
    161              pack = self.dmd.ZenPackManager.packs._getOb(
    162                                          self.options.removePackName, None)
    163              if not pack:
    164                  raise ZenPackException('ZenPack %s is not installed.' %
    165                                          self.options.removePackName)
    166              if pack.isEggPack():
    167                  return EggPackCmd.RemoveZenPack(self.dmd,
    168                                                  self.options.removePackName)
    169              RemoveZenPack(self.dmd, self.options.removePackName, self.log)
    170
    171          elif self.options.list:
    172              for zpId in self.dmd.ZenPackManager.packs.objectIds():
    173                  try:
    174                      zp = self.dmd.ZenPackManager.packs._getOb(zpId, None)
    175                  except AttributeError:
    176                      zp = None
    177                  if not zp:
    178                      desc = 'broken'
    179                  elif zp.isEggPack():
    180                      desc = zp.eggPath()
    181                  else:
    182                      desc = zp.path()
    183                  print('%s (%s)' % (zpId,  desc))
    184
    185          transaction.commit()
    186
    187
    188 -    def preInstallCheck(self):
    189          ''' Check that prerequisite zenpacks are installed.
    190          Return True if no prereqs specified or if they are present.
    191          False otherwise.
    192          '''
    193          if os.path.isfile(self.options.installPackName):
    194              zf = ZipFile(self.options.installPackName)
    195              for name in zf.namelist():
    196                  if name.endswith == '/%s' % CONFIG_FILE:
    197                      sio = StringIO(zf.read(name))
    198              else:
    199                  return True
    200          else:
    201              name = os.path.join(self.options.installPackName, CONFIG_FILE)
    202              if os.path.isfile(name):
    203                  fp = open(name)
    204                  sio = StringIO(fp.read())
    205                  fp.close()
    206              else:
    207                  return True
    208
    209          parser = ConfigParser.SafeConfigParser()
    210          parser.readfp(sio, name)
    211          if parser.has_section(CONFIG_SECTION_ABOUT) \
    212              and parser.has_option(CONFIG_SECTION_ABOUT, 'requires'):
    213              requires = eval(parser.get(CONFIG_SECTION_ABOUT, 'requires'))
    214              if not isinstance(requires, list):
    215                  requires = [zp.strip() for zp in requires.split(',')]
    216              missing = [zp for zp in requires
    217                      if zp not in self.dataroot.ZenPackManager.packs.objectIds()]
    218              if missing:
    219                  self.log.error('ZenPack %s was not installed because'
    220                                  % self.options.installPackName
    221                                  + ' it requires the following ZenPack(s): %s'
    222                                  % ', '.join(missing))
    223                  return False
    224          return True
    225
    226
    227 -    def install(self, packName):
    228
    229          zp = None
    230          try:
    231              # hide uncatalog error messages since they do not do any harm
    232              log = logging.getLogger('Zope.ZCatalog')
    233              oldLevel = log.getEffectiveLevel()
    234              log.setLevel(HIGHER_THAN_CRITICAL)
    235              zp = self.dmd.ZenPackManager.packs._getOb(packName)
    236              self.log.info('Upgrading %s' % packName)
    237              zp.upgrade(self.app)
    238          except AttributeError:
    239              try:
    240                  module =  __import__('Products.' + packName, globals(), {}, [''])
    241                  zp = module.ZenPack(packName)
    242              except (ImportError, AttributeError), ex:
    243                  self.log.debug("Unable to find custom ZenPack (%s), "
    244                                 "defaulting to generic ZenPack",
    245                                 ex)
    246                  zp = ZenPack(packName)
    247              self.dmd.ZenPackManager.packs._setObject(packName, zp)
    248              zp = self.dmd.ZenPackManager.packs._getOb(packName)
    249              zp.install(self.app)
    250          finally:
    251              log.setLevel(oldLevel)
    252          if zp:
    253              for required in zp.requires:
    254                  try:
    255                      self.dmd.ZenPackManager.packs._getOb(required)
    256                  except:
    257                      self.log.error("Pack %s requires pack %s: not installing",
    258                                     packName, required)
    259                      return
    260          transaction.commit()
    261
    262 -    def extract(self, fname):
    263          "Unpack a ZenPack, and return the name"
    264          if not os.path.isfile(fname):
    265              self.stop('Unable to open file "%s"' % fname)
    266          zf = ZipFile(fname)
    267          name = zf.namelist()[0]
    268          packName = name.split('/')[0]
    269          self.log.debug('Extracting ZenPack "%s"' % packName)
    270          for name in zf.namelist():
    271              fullname = zenPath('Products', name)
    272              self.log.debug('Extracting %s' % name)
    273              if name.find('/.svn') > -1: continue
    274              if name.endswith('~'): continue
    275              if name.endswith('/'):
    276                  if not os.path.exists(fullname):
    277                      os.makedirs(fullname, 0750)
    278              else:
    279                  base = os.path.dirname(fullname)
    280                  if not os.path.isdir(base):
    281                      os.makedirs(base, 0750)
    282                  file(fullname, 'wb').write(zf.read(name))
    283          return packName
    284
    285
    286 -    def copyDir(self, srcDir):
    287          '''Copy an unzipped zenpack to the appropriate location.
    288          Return the name.
    289          '''
    290          # Normalize srcDir to not end with slash
    291          if srcDir.endswith('/'):
    292              srcDir = srcDir[:-1]
    293
    294          if not os.path.isdir(srcDir):
    295              self.stop('Specified directory does not appear to exist: %s' %
    296                          srcDir)
    297
    298          # Determine name of pack and it's destination directory
    299          packName = os.path.split(srcDir)[1]
    300          root = zenPath('Products', packName)
    301
    302          # Continue without copying if the srcDir is already in Products
    303          if os.path.exists(root) and os.path.samefile(root, srcDir):
    304              self.log.debug('Directory already in %s, not copying.',
    305                             zenPath('Products'))
    306              return packName
    307
    308          # Copy the source dir over to Products
    309          self.log.debug('Copying %s' % packName)
    310          result = os.system('cp -r %s %s' % (srcDir, zenPath('Products')))
    311          if result == -1:
    312              self.stop('Error copying %s to %s' % (srcDir, zenPath('Products')))
    313
    314          return packName
    315
    316
    317 -    def linkDir(self, srcDir):
    318          '''Symlink the srcDir into Products
    319          Return the name.
    320          '''
    321          # Normalize srcDir to not end with slash
    322          if srcDir.endswith('/'):
    323              srcDir = srcDir[:-1]
    324
    325          # Need absolute path for links
    326          srcDir = os.path.abspath(srcDir)
    327
    328          if not os.path.isdir(srcDir):
    329              self.stop('Specified directory does not appear to exist: %s' %
    330                          srcDir)
    331
    332          # Determine name of pack and it's destination directory
    333          packName = os.path.split(srcDir)[1]
    334          root = zenPath('Products', packName)
    335
    336          # Continue without copying if the srcDir is already in Products
    337          if os.path.exists(root) and os.path.samefile(root, srcDir):
    338              self.log.debug('Directory already in %s, not copying.',
    339                             zenPath('Products'))
    340              return packName
    341
    342          targetdir = zenPath("Products", packName)
    343          cmd = 'test -d %s && rm -rf %s' % (targetdir, targetdir)
    344          os.system(cmd)
    345          cmd = 'ln -s %s %s' % (srcDir, zenPath("Products"))
    346          os.system(cmd)
    347
    348          return packName
    349
    350
    351 -    def stop(self, why):
    352          self.log.error("zenpack stopped: %s", why)
    353          sys.exit(1)
    354
    355
    356 -    def buildOptions(self):
    357          self.parser.add_option('--install',
    358                                 dest='installPackName',
    359                                 default=None,
    360                                 help="Path to the ZenPack to install.")
    361  #        self.parser.add_option('--fetch',
    362  #                               dest='fetch',
    363  #                               default=None,
    364  #                               help='Name of ZenPack to retrieve from '
    365  #                                    'Zenoss.net and install.')
    366  #        self.parser.add_option('--fetchVers',
    367  #                               dest='fetchVers',
    368  #                               default=None,
    369  #                               help='Use with --fetch to specify a version'
    370  #                                    ' for the ZenPack to download and install.')
    371          self.parser.add_option('--remove',
    372                                 dest='removePackName',
    373                                 default=None,
    374                                 help="Name of the ZenPack to remove.")
    375          self.parser.add_option('--list',
    376                                 dest='list',
    377                                 action="store_true",
    378                                 default=False,
    379                                 help='List installed ZenPacks')
    380          self.parser.add_option('--link',
    381                                 dest='link',
    382                                 action="store_true",
    383                                 default=False,
    384                                 help="Install the ZenPack in place, without "
    385                                          "copying into $ZENHOME/ZenPacks.")
    386          self.parser.add_option('--files-only',
    387                                 dest='filesOnly',
    388                                 action="store_true",
    389                                 default=False,
    390                                 help='Install the ZenPack files onto the '
    391                                          'filesystem, but do not install the '
    392                                          'ZenPack into Zenoss.')
    393          self.parser.add_option('--previousversion',
    394                                 dest='previousVersion',
    395                                 default=None,
    396                                 help="Previous version of the zenpack;"
    397                                      ' used during upgrades')
    398
    399          ZenScriptBase.buildOptions(self)
    400
    401  if __name__ == '__main__':
    402      logging.basicConfig()
    403      log = logging.getLogger('zen.ZenPackCmd')
    404      try:
    405          zp = ZenPackCmd()
    406          zp.run()
    407      except ConflictError:
    408          raise
    409      except:
    410          log.exception('zenpack command failed')
    411          sys.exit(-1)
    412