1
2
3
4
5
6
7
8
9
10
11 """Script to manage ZenPacks."""
12
13 import os, sys
14 import contextlib
15 import logging
16 import ConfigParser
17 import optparse
18 import subprocess
19 from zipfile import ZipFile
20 from StringIO import StringIO
21 from pkg_resources import parse_requirements
22
23 import Globals
24 import transaction
25 from zenoss.protocols.services import ServiceException
26 from ZODB.POSException import ConflictError
27
28 from Products.ZenMessaging.audit import audit
29 from Products.ZenMessaging.queuemessaging.schema import removeZenPackQueuesExchanges
30 from Products.ZenModel.ZenPack import ZenPack, ZenPackException
31 from Products.ZenModel.ZenPack import ZenPackNeedMigrateException
32 from Products.ZenUtils.ZenScriptBase import ZenScriptBase
33 from Products.ZenUtils.Utils import cleanupSkins, zenPath, binPath, get_temp_dir
34 import Products.ZenModel.ZenPackLoader as ZPL
35 from Products.ZenModel.ZenPackLoader import CONFIG_FILE, CONFIG_SECTION_ABOUT
36 import ZenPackCmd as EggPackCmd
37 from Products.Zuul import getFacade
38
39 HIGHER_THAN_CRITICAL = 100
40
41 -def RemoveZenPack(dmd, packName, log=None,
42 skipDepsCheck=False, leaveObjects=True,
43 deleteFiles=True):
81
82
84 """Manage ZenPacks"""
85
87 zep = getFacade('zep')
88 try:
89 zep.getConfig()
90 return True
91 except ServiceException:
92 return False
93
95 """Execute the user's request"""
96 if self.args:
97 print "Require one of --install, --remove or --list flags."
98 self.parser.print_help()
99 return
100
101 if self.options.installPackName:
102 audit('Shell.ZenPack.Install', zenpack=self.options.installPackName)
103 elif self.options.fetch:
104 audit('Shell.ZenPack.Fetch', zenpack=self.options.fetch)
105 elif self.options.removePackName:
106 audit('Shell.ZenPack.Remove', zenpack=self.options.removePackName)
107
108 if self.options.installPackName:
109 eggInstall = (self.options.installPackName.lower().endswith('.egg')
110 or os.path.exists(os.path.join(self.options.installPackName,
111 'setup.py')))
112
113
114
115 class ZPProxy:
116 def __init__(self, zpId):
117 self.id = zpId
118 def path(self, *parts):
119 return zenPath('Products', self.id, *parts)
120 if self.options.installPackName and self.options.filesOnly:
121 if eggInstall:
122 return EggPackCmd.InstallZenPack(None,
123 self.options.installPackName,
124 filesOnly=True)
125 packName = self.extract(self.options.installPackName)
126 proxy = ZPProxy(packName)
127 for loader in (ZPL.ZPLDaemons(), ZPL.ZPLBin(), ZPL.ZPLLibExec()):
128 loader.load(proxy, None)
129 return
130 if self.options.removePackName and self.options.filesOnly:
131
132
133 proxy = ZPProxy(self.options.removePackName)
134 for loader in (ZPL.ZPLDaemons(), ZPL.ZPLBin(), ZPL.ZPLLibExec()):
135 loader.unload(proxy, None)
136 os.system('rm -rf %s' % zenPath('Products',
137 self.options.removePackName))
138 return
139
140
141
142 self.connect()
143
144 if not getattr(self.dmd, 'ZenPackManager', None):
145 raise ZenPackNeedMigrateException('Your Zenoss database appears'
146 ' to be out of date. Try running zenmigrate to update.')
147
148 if (self.options.installPackName or self.options.removePackName) and not self._verifyZepRunning():
149 print >> sys.stderr, "Error: Required daemon zeneventserver not running."
150 print >> sys.stderr, "Execute 'zeneventserver start' and retry the ZenPack installation."
151 sys.exit(1)
152
153 if self.options.installPackName:
154 if not self.preInstallCheck(eggInstall):
155 self.stop('%s not installed' % self.options.installPackName)
156 if eggInstall:
157 return EggPackCmd.InstallEggAndZenPack(
158 self.dmd,
159 self.options.installPackName,
160 link=self.options.link,
161 filesOnly=False,
162 previousVersion= self.options.previousVersion,
163 fromUI=self.options.fromui)
164 if os.path.isfile(self.options.installPackName):
165 packName = self.extract(self.options.installPackName)
166 elif os.path.isdir(self.options.installPackName):
167 if self.options.link:
168 packName = self.linkDir(self.options.installPackName)
169 else:
170 packName = self.copyDir(self.options.installPackName)
171 else:
172 self.stop('%s does not appear to be a valid file or directory.'
173 % self.options.installPackName)
174
175
176
177
178
179 skinsSubdir = zenPath('Products', packName, 'skins', packName)
180 if not os.path.exists(skinsSubdir):
181 os.makedirs(skinsSubdir, 0750)
182 self.install(packName)
183
184 elif self.options.fetch:
185 return EggPackCmd.FetchAndInstallZenPack(
186 self.dmd, self.options.fetch)
187 elif self.options.removePackName:
188 pack = self.dmd.ZenPackManager.packs._getOb(
189 self.options.removePackName, None)
190
191 if not pack:
192 raise ZenPackException('ZenPack %s is not installed.' %
193 self.options.removePackName)
194 removeZenPackQueuesExchanges(pack.path())
195 if pack.isEggPack():
196 return EggPackCmd.RemoveZenPack(self.dmd,
197 self.options.removePackName)
198 RemoveZenPack(self.dmd, self.options.removePackName, self.log)
199
200 elif self.options.list:
201 for zpId in self.dmd.ZenPackManager.packs.objectIds():
202 try:
203 zp = self.dmd.ZenPackManager.packs._getOb(zpId, None)
204 except AttributeError:
205 zp = None
206 if not zp:
207 desc = 'broken'
208 elif zp.isEggPack():
209 desc = zp.eggPath()
210 else:
211 desc = zp.path()
212 print('%s (%s)' % (zpId, desc))
213
214 transaction.commit()
215
216
218 """Check that prerequisite zenpacks are installed.
219 Return True if no prereqs specified or if they are present.
220 False otherwise.
221 """
222 if eggInstall:
223 installedPacks = dict((pack.id, pack.version) \
224 for pack in self.dataroot.ZenPackManager.packs())
225
226 if self.options.installPackName.lower().endswith('.egg'):
227
228 zf = ZipFile(self.options.installPackName)
229 if 'EGG-INFO/requires.txt' in zf.namelist():
230 reqZenpacks = zf.read('EGG-INFO/requires.txt').split('\n')
231 else:
232 return True
233 else:
234
235 with get_temp_dir() as tempEggDir:
236 cmd = '%s setup.py egg_info -e %s' % \
237 (binPath('python'), tempEggDir)
238 subprocess.call(cmd, shell=True,
239 stdout=open('/dev/null', 'w'),
240 cwd=self.options.installPackName)
241
242 eggRequires = os.path.join(tempEggDir,
243 self.options.installPackName + '.egg-info',
244 'requires.txt')
245 if os.path.isfile(eggRequires):
246 reqZenpacks = open(eggRequires, 'r').read().split('\n')
247 else:
248 return True
249
250 prereqsMet = True
251 for req in reqZenpacks:
252 for parsed_req in parse_requirements([req]):
253 installed_version = installedPacks.get(parsed_req.project_name, None)
254 if installed_version is None:
255 self.log.error('Zenpack %s requires %s' %
256 (self.options.installPackName, parsed_req))
257 prereqsMet = False
258 else:
259 if not installed_version in parsed_req:
260 self.log.error(
261 'Zenpack %s requires %s, found: %s' %
262 (self.options.installPackName, parsed_req, installed_version))
263 prereqsMet = False
264 return prereqsMet
265
266 if os.path.isfile(self.options.installPackName):
267 zf = ZipFile(self.options.installPackName)
268 for name in zf.namelist():
269 if name.endswith == '/%s' % CONFIG_FILE:
270 sio = StringIO(zf.read(name))
271 else:
272 return True
273 else:
274 name = os.path.join(self.options.installPackName, CONFIG_FILE)
275 if os.path.isfile(name):
276 fp = open(name)
277 sio = StringIO(fp.read())
278 fp.close()
279 else:
280 return True
281
282 parser = ConfigParser.SafeConfigParser()
283 parser.readfp(sio, name)
284 if parser.has_section(CONFIG_SECTION_ABOUT) \
285 and parser.has_option(CONFIG_SECTION_ABOUT, 'requires'):
286 requires = eval(parser.get(CONFIG_SECTION_ABOUT, 'requires'))
287 if not isinstance(requires, list):
288 requires = [zp.strip() for zp in requires.split(',')]
289 missing = [zp for zp in requires
290 if zp not in self.dataroot.ZenPackManager.packs.objectIds()]
291 if missing:
292 self.log.error('ZenPack %s was not installed because'
293 % self.options.installPackName
294 + ' it requires the following ZenPack(s): %s'
295 % ', '.join(missing))
296 return False
297 return True
298
299
301 zp = None
302 try:
303
304 log = logging.getLogger('Zope.ZCatalog')
305 oldLevel = log.getEffectiveLevel()
306 log.setLevel(HIGHER_THAN_CRITICAL)
307 zp = self.dmd.ZenPackManager.packs._getOb(packName)
308 self.log.info('Upgrading %s' % packName)
309 zp.upgrade(self.app)
310 except AttributeError:
311 try:
312 module = __import__('Products.' + packName, globals(), {}, [''])
313 zp = module.ZenPack(packName)
314 except (ImportError, AttributeError), ex:
315 self.log.debug("Unable to find custom ZenPack (%s), "
316 "defaulting to generic ZenPack",
317 ex)
318 zp = ZenPack(packName)
319 self.dmd.ZenPackManager.packs._setObject(packName, zp)
320 zp = self.dmd.ZenPackManager.packs._getOb(packName)
321 zp.install(self.app)
322 finally:
323 log.setLevel(oldLevel)
324 if zp:
325 for required in zp.requires:
326 try:
327 self.dmd.ZenPackManager.packs._getOb(required)
328 except:
329 self.log.error("Pack %s requires pack %s: not installing",
330 packName, required)
331 return
332 transaction.commit()
333
335 """Unpack a ZenPack, and return the name"""
336 if not os.path.isfile(fname):
337 self.stop('Unable to open file "%s"' % fname)
338 zf = ZipFile(fname)
339 name = zf.namelist()[0]
340 packName = name.split('/')[0]
341 self.log.debug('Extracting ZenPack "%s"' % packName)
342 for name in zf.namelist():
343 fullname = zenPath('Products', name)
344 self.log.debug('Extracting %s' % name)
345 if name.find('/.svn') > -1: continue
346 if name.endswith('~'): continue
347 if name.endswith('/'):
348 if not os.path.exists(fullname):
349 os.makedirs(fullname, 0750)
350 else:
351 base = os.path.dirname(fullname)
352 if not os.path.isdir(base):
353 os.makedirs(base, 0750)
354 file(fullname, 'wb').write(zf.read(name))
355 return packName
356
357
359 """Copy an unzipped zenpack to the appropriate location.
360 Return the name.
361 """
362
363 if srcDir.endswith('/'):
364 srcDir = srcDir[:-1]
365
366 if not os.path.isdir(srcDir):
367 self.stop('Specified directory does not appear to exist: %s' %
368 srcDir)
369
370
371 packName = os.path.split(srcDir)[1]
372 root = zenPath('Products', packName)
373
374
375 if os.path.exists(root) and os.path.samefile(root, srcDir):
376 self.log.debug('Directory already in %s, not copying.',
377 zenPath('Products'))
378 return packName
379
380
381 self.log.debug('Copying %s' % packName)
382 result = os.system('cp -r %s %s' % (srcDir, zenPath('Products')))
383 if result == -1:
384 self.stop('Error copying %s to %s' % (srcDir, zenPath('Products')))
385
386 return packName
387
388
390 """Symlink the srcDir into Products
391 Return the name.
392 """
393
394 if srcDir.endswith('/'):
395 srcDir = srcDir[:-1]
396
397
398 srcDir = os.path.abspath(srcDir)
399
400 if not os.path.isdir(srcDir):
401 self.stop('Specified directory does not appear to exist: %s' %
402 srcDir)
403
404
405 packName = os.path.split(srcDir)[1]
406 root = zenPath('Products', packName)
407
408
409 if os.path.exists(root) and os.path.samefile(root, srcDir):
410 self.log.debug('Directory already in %s, not copying.',
411 zenPath('Products'))
412 return packName
413
414 targetdir = zenPath("Products", packName)
415 cmd = 'test -d %s && rm -rf %s' % (targetdir, targetdir)
416 os.system(cmd)
417 cmd = 'ln -s %s %s' % (srcDir, zenPath("Products"))
418 os.system(cmd)
419
420 return packName
421
422
423 - def stop(self, why):
424 self.log.error("zenpack stopped: %s", why)
425 sys.exit(1)
426
427
429 self.parser.add_option('--install',
430 dest='installPackName',
431 default=None,
432 help="Path to the ZenPack to install.")
433 self.parser.add_option('--fetch',
434 dest='fetch',
435 default=None,
436 help='Name of ZenPack to retrieve from '
437 'Zenoss and install.')
438 self.parser.add_option('--remove', '--delete', '--uninstall', '--erase',
439 dest='removePackName',
440 default=None,
441 help="Name of the ZenPack to remove.")
442 self.parser.add_option('--list',
443 dest='list',
444 action="store_true",
445 default=False,
446 help='List installed ZenPacks')
447 self.parser.add_option('--link',
448 dest='link',
449 action="store_true",
450 default=False,
451 help="Install the ZenPack in place, without "
452 "copying into $ZENHOME/ZenPacks.")
453 self.parser.add_option('--files-only',
454 dest='filesOnly',
455 action="store_true",
456 default=False,
457 help='Install the ZenPack files onto the '
458 'filesystem, but do not install the '
459 'ZenPack into Zenoss.')
460 self.parser.add_option('--fromui',
461 dest='fromui',
462 action="store_true",
463 default=False,
464 help=optparse.SUPPRESS_HELP)
465 self.parser.add_option('--previousversion',
466 dest='previousVersion',
467 default=None,
468 help="Previous version of the zenpack;"
469 ' used during upgrades')
470 self.parser.prog = "zenpack"
471 ZenScriptBase.buildOptions(self)
472
473 if __name__ == '__main__':
474 logging.basicConfig()
475 log = logging.getLogger('zen.ZenPackCmd')
476 try:
477 zp = ZenPackCmd()
478 zp.run()
479 except ConflictError:
480 raise
481 except SystemExit as e:
482 if e.code:
483 sys.exit(e.code)
484 except:
485 log.exception('zenpack command failed')
486 sys.exit(-1)
487