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