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