1
2
3
4
5
6
7
8
9
10
11
12
13
14 __doc__ = """ZenPack
15 ZenPacks base definitions
16 """
17
18 import exceptions
19 import string
20 import subprocess
21 import os
22 import sys
23
24 from Globals import InitializeClass
25 from Products.ZenModel.ZenModelRM import ZenModelRM
26 from Products.ZenRelations.RelSchema import *
27 from Products.ZenUtils.Utils import importClass, zenPath
28 from Products.ZenUtils.Version import getVersionTupleFromString
29 from Products.ZenUtils.Version import Version as VersionBase
30 from Products.ZenUtils.PkgResources import pkg_resources
31 from Products.ZenModel.ZenPackLoader import *
32 from Products.ZenWidgets import messaging
33 from AccessControl import ClassSecurityInfo
34 from ZenossSecurity import ZEN_MANAGE_DMD
35 from Acquisition import aq_parent
36 from Products.ZenModel.ZVersion import VERSION as ZENOSS_VERSION
37
38
41
44
47
50
53
56
59 VersionBase.__init__(self, 'Zenoss', *args, **kw)
60
61
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 def compare(x, y):
74 """
75 Comparison function based on getPrimaryPath()
76
77 @param x: object
78 @type x: object
79 @param y: object
80 @type y: object
81 @return: cmp-style return code
82 @rtype: numeric
83 """
84 return cmp(x.getPrimaryPath(), y.getPrimaryPath())
85
86 objs.sort(compare)
87 result = []
88 for obj in objs:
89 for alreadyInList in result:
90 path = alreadyInList.getPrimaryPath()
91 if obj.getPrimaryPath()[:len(path)] == path:
92 break
93 else:
94 result.append(obj)
95 return result
96
97
99 """
100 Base class for defining migration methods
101 """
102 version = Version(0, 0, 0)
103
105 """
106 ZenPack-specific migrate() method to be overridden
107
108 @param pack: ZenPack object
109 @type pack: ZenPack object
110 """
111 pass
112
114 """
115 ZenPack-specific recover() method to be overridden
116
117 @param pack: ZenPack object
118 @type pack: ZenPack object
119 """
120 pass
121
122
123
124
126 """
127 Base class for ZenPack migrate steps that need to switch classes of
128 datasources and reindex them. This is frequently done in migrate
129 scripts for 2.2 when ZenPacks are migrated to python eggs.
130 """
131
132 dsClass = None
133
134
135
136 oldDsModuleName = ''
137 oldDsClassName = ''
138
139 reIndex = False
140
165
166
168 """
169 The root of all ZenPacks: has no implementation,
170 but sits here to be the target of the Relation
171 """
172
173 objectPaths = None
174
175
176 version = '0.1'
177 author = ''
178 organization = ''
179 url = ''
180 license = ''
181 compatZenossVers = ''
182 prevZenPackName = ''
183 prevZenPackVersion = None
184
185
186
187 eggPack = False
188
189 requires = ()
190
191 loaders = (ZPLObject(), ZPLReport(), ZPLDaemons(), ZPLBin(), ZPLLibExec(),
192 ZPLSkins(), ZPLDataSources(), ZPLLibraries(), ZPLAbout(),
193 ZPTriggerAction(), ZPZep())
194
195 _properties = ZenModelRM._properties + (
196 {'id':'objectPaths','type':'lines','mode':'w'},
197 {'id':'version', 'type':'string', 'mode':'w', 'description':'ZenPack version'},
198 {'id':'author', 'type':'string', 'mode':'w', 'description':'ZenPack author'},
199 {'id':'organization', 'type':'string', 'mode':'w',
200 'description':'Sponsoring organization for the ZenPack'},
201 {'id':'url', 'type':'string', 'mode':'w', 'description':'Homepage for the ZenPack'},
202 {'id':'license', 'type':'string', 'mode':'w',
203 'description':'Name of the license under which this ZenPack is available'},
204 {'id':'compatZenossVers', 'type':'string', 'mode':'w',
205 'description':'Which Zenoss versions can load this ZenPack'},
206 )
207
208 _relations = (
209
210
211 ('root', ToOne(ToManyCont, 'Products.ZenModel.DataRoot', 'packs')),
212 ('manager',
213 ToOne(ToManyCont, 'Products.ZenModel.ZenPackManager', 'packs')),
214 ("packables", ToMany(ToOne, "Products.ZenModel.ZenPackable", "pack")),
215 )
216
217 factory_type_information = (
218 { 'immediate_view' : 'viewPackDetail',
219 'factory' : 'manage_addZenPack',
220 'actions' :
221 (
222 { 'id' : 'viewPackDetail'
223 , 'name' : 'Detail'
224 , 'action' : 'viewPackDetail'
225 , 'permissions' : ( "Manage DMD", )
226 },
227 )
228 },
229 )
230
231 packZProperties = [
232 ]
233
234 security = ClassSecurityInfo()
235
236
237 - def __init__(self, id, title=None, buildRelations=True):
241
242
257
258
260 """
261 This is essentially an install() call except that a different method
262 is called on the loaders.
263 NB: Newer ZenPacks (egg style) do not use this upgrade method. Instead
264 the proper method is to remove(leaveObjects=True) and install again.
265 See ZenPackCmd.InstallDistAsZenPack().
266
267 @param app: ZenPack
268 @type app: ZenPack object
269 """
270 self.stopDaemons()
271 for loader in self.loaders:
272 loader.upgrade(self, app)
273 self.createZProperties(app)
274 self.migrate()
275 self.startDaemons()
276
277
278 - def remove(self, app, leaveObjects=False):
279 """
280 This prepares the ZenPack for removal but does not actually remove
281 the instance from ZenPackManager.packs This is sometimes called during
282 the course of an upgrade where the loaders' unload methods need to
283 be run.
284
285 @param app: ZenPack
286 @type app: ZenPack object
287 @param leaveObjects: remove zProperties and things?
288 @type leaveObjects: boolean
289 """
290 self.stopDaemons()
291 for loader in self.loaders:
292 loader.unload(self, app, leaveObjects)
293 if not leaveObjects:
294 self.removeZProperties(app)
295 self.removeCatalogedObjects(app)
296
297 - def backup(self, backupDir, logger):
298 """
299 Method called when zenbackup is run. Override in ZenPack to add any
300 ZenPack-specific backup operations.
301
302 @param backupDir: Temporary directory that gets zipped to form backup
303 @type backupDir: string
304 @param logger: Backup log handler
305 @type logger: Log object
306 """
307 pass
308
309 - def restore(self, backupDir, logger):
310 """
311 Method called when zenrestore is run. Override in ZenPack to add any
312 ZenPack-specific restore operations.
313
314 @param backupDir: Temporary directory that contains the unzipped backup
315 @type backupDir: string
316 @param logger: Restore log handler
317 @type logger: Log object
318 """
319 pass
320
321
322 - def migrate(self, previousVersion=None):
323 """
324 Migrate to a new version
325
326 @param previousVersion: previous version number
327 @type previousVersion: string
328 """
329 instances = []
330
331 root = self.path("migrate")
332 for p, ds, fs in os.walk(root):
333 for f in fs:
334 if f.endswith('.py') and not f.startswith("__"):
335 path = os.path.join(p[len(root) + 1:], f)
336 log.debug("Loading %s", path)
337 sys.path.insert(0, p)
338 try:
339 try:
340 c = importClass(path[:-3].replace("/", "."))
341 instances.append(c())
342 finally:
343 sys.path.remove(p)
344 except ImportError, ex:
345 log.exception("Problem loading migration step %s", path)
346
347 def versionCmp(migrate1, migrate2):
348 return cmp(migrate1.version, migrate2.version)
349 instances.sort(versionCmp)
350
351 migrateCutoff = getVersionTupleFromString(self.version)
352 if previousVersion:
353 migrateCutoff = getVersionTupleFromString(previousVersion)
354 recover = []
355
356 try:
357 for instance in instances:
358 if instance.version >= migrateCutoff:
359 recover.append(instance)
360 instance.migrate(self)
361 except Exception, ex:
362
363 recover.reverse()
364 for r in recover:
365 r.recover(self)
366 raise
367
368
369 - def list(self, app):
370 """
371 Show the list of loaders
372
373 @param app: ZenPack
374 @type app: ZenPack object
375 @return: list of loaders
376 @rtype: list of objects
377 """
378 result = []
379 for loader in self.loaders:
380 result.append((loader.name,
381 [item for item in loader.list(self, app)]))
382 return result
383
385 """
386 Registers ExtJS portlets from a ZenPack. Override in ZenPack. ID and
387 title are required, height and permissions are optional. See
388 ZenWidgets.PortletManager.register_extjsPortlet.
389
390 @return: List of dictionary objects describing a portlet
391 @rtype: List of dicts
392 """
393 return []
394
396 """
397 Create zProperties in the ZenPack's self.packZProperties
398
399 @param app: ZenPack
400 @type app: ZenPack object
401 """
402
403
404 for name, value, pType in self.packZProperties:
405 if not app.zport.dmd.Devices.hasProperty(name):
406 app.zport.dmd.Devices._setProperty(name, value, pType)
407
408
410 """
411 Remove any zProperties defined in the ZenPack
412
413 @param app: ZenPack
414 @type app: ZenPack object
415 """
416 for name, value, pType in self.packZProperties:
417 app.zport.dmd.Devices._delProperty(name)
418
419
421 """
422 Delete all objects in the zenPackPersistence catalog that are
423 associated with this zenpack.
424
425 @param app: ZenPack
426 @type app: ZenPack object
427 """
428 objects = self.getCatalogedObjects()
429 for o in objects:
430 parent = aq_parent(o)
431 if parent:
432 parent._delObject(o.id)
433
434
442
443
445 """
446 Edit a ZenPack object
447 """
448
449 if self.isEggPack():
450
451 newDeps = {}
452 depNames = REQUEST.get('dependencies', [])
453 if not isinstance(depNames, list):
454 depNames = [depNames]
455 newDeps = {}
456 for depName in depNames:
457 fieldName = 'version_%s' % depName
458 vers = REQUEST.get(fieldName, '').strip()
459 if vers and vers[0] in string.digits:
460 vers = '==' + vers
461 try:
462 req = pkg_resources.Requirement.parse(depName + vers)
463 except ValueError:
464 messaging.IMessageSender(self).sendToBrowser(
465 'Error',
466 '%s is not a valid version specification.' % vers,
467 priority=messaging.WARNING
468 )
469 return self.callZenScreen(REQUEST)
470 zp = self.dmd.ZenPackManager.packs._getOb(depName, None)
471 if not zp:
472 messaging.IMessageSender(self).sendToBrowser(
473 'Error',
474 '%s is not installed.' % depName,
475 priority=messaging.WARNING
476 )
477 return self.callZenScreen(REQUEST)
478 if not req.__contains__(zp.version):
479 messaging.IMessageSender(self).sendToBrowser(
480 'Error',
481 ('The required version for %s (%s) ' % (depName, vers) +
482 'does not match the installed version (%s).' %
483 zp.version),
484 priority=messaging.WARNING
485 )
486 return self.callZenScreen(REQUEST)
487 newDeps[depName] = vers
488 REQUEST.form[fieldName] = vers
489 self.dependencies = newDeps
490
491
492 compatZenossVers = REQUEST.form['compatZenossVers'] or ''
493 if compatZenossVers:
494 if compatZenossVers[0] in string.digits:
495 compatZenossVers = '==' + compatZenossVers
496 try:
497 req = pkg_resources.Requirement.parse(
498 'zenoss%s' % compatZenossVers)
499 except ValueError:
500 messaging.IMessageSender(self).sendToBrowser(
501 'Error',
502 ('%s is not a valid version specification for Zenoss.'
503 % compatZenossVers),
504 priority=messaging.WARNING
505 )
506 if not req.__contains__(ZENOSS_VERSION):
507 messaging.IMessageSender(self).sendToBrowser(
508 'Error',
509 ('%s does not match this version of Zenoss (%s).' %
510 (compatZenossVers, ZENOSS_VERSION)),
511 priority=messaging.WARNING
512 )
513 return self.callZenScreen(REQUEST)
514 REQUEST.form['compatZenossVers'] = compatZenossVers
515
516 result = ZenModelRM.zmanage_editProperties(self, REQUEST, redirect)
517
518 if self.isEggPack():
519 self.writeSetupValues()
520 self.buildEggInfo()
521 return result
522
523
537
538
553
554
555 security.declareProtected(ZEN_MANAGE_DMD, 'manage_exportPack')
557 """
558 Export the ZenPack to the /export directory
559
560 @param download: download to client's desktop? ('yes' vs anything else)
561 @type download: string
562 @type download: string
563 @param REQUEST: Zope REQUEST object
564 @type REQUEST: Zope REQUEST object
565 @todo: make this more modular
566 @todo: add better XML headers
567 """
568 if not self.isDevelopment():
569 msg = 'Only ZenPacks installed in development mode can be exported.'
570 if REQUEST:
571 messaging.IMessageSender(self).sendToBrowser(
572 'Error', msg, priority=messaging.WARNING)
573 return self.callZenScreen(REQUEST)
574 raise ZenPackDevelopmentModeExeption(msg)
575
576 from StringIO import StringIO
577 xml = StringIO()
578
579
580
581 xml.write("""<?xml version="1.0"?>\n""")
582 xml.write("<objects>\n")
583
584 packables = eliminateDuplicates(self.packables())
585 for obj in packables:
586
587 xml.write('<!-- %r -->\n' % (obj.getPrimaryPath(),))
588 obj.exportXml(xml,['devices','networks','pack'],True)
589 xml.write("</objects>\n")
590 path = self.path('objects')
591 if not os.path.isdir(path):
592 os.mkdir(path, 0750)
593 objects = file(os.path.join(path, 'objects.xml'), 'w')
594 objects.write(xml.getvalue())
595 objects.close()
596
597
598 path = self.path('skins')
599 if not os.path.isdir(path):
600 os.makedirs(path, 0750)
601
602
603 init = self.path('__init__.py')
604 if not os.path.isfile(init):
605 fp = file(init, 'w')
606 fp.write(
607 '''
608 import Globals
609 from Products.CMFCore.DirectoryView import registerDirectory
610 registerDirectory("skins", globals())
611 ''')
612 fp.close()
613
614 if self.isEggPack():
615
616 exportDir = zenPath('export')
617 if not os.path.isdir(exportDir):
618 os.makedirs(exportDir, 0750)
619 eggPath = self.eggPath()
620 os.chdir(eggPath)
621 if os.path.isdir(os.path.join(eggPath, 'dist')):
622 os.system('rm -rf dist/*')
623 p = subprocess.Popen('python setup.py bdist_egg',
624 stderr=sys.stderr,
625 shell=True,
626 cwd=eggPath)
627 p.wait()
628 os.system('cp dist/* %s' % exportDir)
629 exportFileName = self.eggName()
630 else:
631
632 about = self.path(CONFIG_FILE)
633 values = {}
634 parser = ConfigParser.SafeConfigParser()
635 if os.path.isfile(about):
636 try:
637 parser.read(about)
638 values = dict(parser.items(CONFIG_SECTION_ABOUT))
639 except ConfigParser.Error:
640 pass
641 current = [(p['id'], str(getattr(self, p['id'], '') or ''))
642 for p in self._properties]
643 values.update(dict(current))
644 if not parser.has_section(CONFIG_SECTION_ABOUT):
645 parser.add_section(CONFIG_SECTION_ABOUT)
646 for key, value in values.items():
647 parser.set(CONFIG_SECTION_ABOUT, key, value)
648 fp = file(about, 'w')
649 try:
650 parser.write(fp)
651 finally:
652 fp.close()
653
654 path = zenPath('export')
655 if not os.path.isdir(path):
656 os.makedirs(path, 0750)
657 from zipfile import ZipFile, ZIP_DEFLATED
658 zipFilePath = os.path.join(path, '%s.zip' % self.id)
659 zf = ZipFile(zipFilePath, 'w', ZIP_DEFLATED)
660 base = zenPath('Products')
661 for p, ds, fd in os.walk(self.path()):
662 if p.split('/')[-1].startswith('.'): continue
663 for f in fd:
664 if f.startswith('.'): continue
665 if f.endswith('.pyc'): continue
666 filename = os.path.join(p, f)
667 zf.write(filename, filename[len(base)+1:])
668 ds[:] = [d for d in ds if d[0] != '.']
669 zf.close()
670 exportFileName = '%s.zip' % self.id
671
672 if REQUEST:
673 dlLink = '- <a target="_blank" href="%s/manage_download">' \
674 'Download Zenpack</a>' % self.absolute_url_path()
675 messaging.IMessageSender(self).sendToBrowser(
676 'ZenPack Exported',
677 'ZenPack exported to $ZENHOME/export/%s %s' %
678 (exportFileName, dlLink if download == 'yes' else ''),
679 messaging.CRITICAL if download == 'yes' else messaging.INFO
680 )
681 return self.callZenScreen(REQUEST)
682
683 return exportFileName
684
686 """
687 Download the already exported zenpack from $ZENHOME/export
688
689 @param REQUEST: Zope REQUEST object
690 @type REQUEST: Zope REQUEST object
691 """
692 if self.isEggPack():
693 filename = self.eggName()
694 else:
695 filename = '%s.zip' % self.id
696 path = os.path.join(zenPath('export'), filename)
697 if os.path.isfile(path):
698 REQUEST.RESPONSE.setHeader('content-type', 'application/zip')
699 REQUEST.RESPONSE.setHeader('content-disposition',
700 'attachment; filename=%s' %
701 filename)
702 zf = file(path, 'r')
703 try:
704 REQUEST.RESPONSE.write(zf.read())
705 finally:
706 zf.close()
707 else:
708 messaging.IMessageSender(self).sendToBrowser(
709 'Error',
710 'An error has occurred. The ZenPack could not be exported.',
711 priority=messaging.WARNING
712 )
713 return self.callZenScreen(REQUEST)
714
715
717 dsClasses = []
718 for path, dirs, files in os.walk(self.path(name)):
719 dirs[:] = [d for d in dirs if not d.startswith('.')]
720 for f in files:
721 if not f.startswith('.') \
722 and f.endswith('.py') \
723 and not f == '__init__.py':
724 subPath = path[len(self.path()):]
725 parts = subPath.strip('/').split('/')
726 parts.append(f[:f.rfind('.')])
727 modName = '.'.join([self.moduleName()] + parts)
728 dsClasses.append(importClass(modName))
729 return dsClasses
730
733
736
738 """
739 Get the filenames of a ZenPack exclude .svn, .pyc and .xml files
740 """
741 filenames = []
742 for root, dirs, files in os.walk(self.path()):
743 if root.find('.svn') == -1:
744 for f in files:
745 if not f.endswith('.pyc') \
746 and not f.endswith('.xml'):
747 filenames.append('%s/%s' % (root, f))
748 return filenames
749
750
752 """
753 Return a list of daemons in the daemon subdirectory that should be
754 stopped/started before/after an install or an upgrade of the zenpack.
755 """
756 daemonsDir = os.path.join(self.path(), 'daemons')
757 if os.path.isdir(daemonsDir):
758 daemons = [f for f in os.listdir(daemonsDir)
759 if os.path.isfile(os.path.join(daemonsDir,f))]
760 else:
761 daemons = []
762 return daemons
763
764
766 """
767 Stop all the daemons provided by this pack.
768 Called before an upgrade or a removal of the pack.
769 """
770 return
771 for d in self.getDaemonNames():
772 self.About.doDaemonAction(d, 'stop')
773
774
776 """
777 Start all the daemons provided by this pack.
778 Called after an upgrade or an install of the pack.
779 """
780 return
781 for d in self.getDaemonNames():
782 self.About.doDaemonAction(d, 'start')
783
784
786 """
787 Restart all the daemons provided by this pack.
788 Called after an upgrade or an install of the pack.
789 """
790 for d in self.getDaemonNames():
791 self.About.doDaemonAction(d, 'restart')
792
793
794 - def path(self, *parts):
795 """
796 Return the path to the ZenPack module.
797 It would be convenient to store the module name/path in the zenpack
798 object, however this would make things more complicated when the
799 name of the package under ZenPacks changed on us (do to a user edit.)
800 """
801 if self.isEggPack():
802 module = self.getModule()
803 return os.path.join(module.__path__[0], *[p.strip('/') for p in parts])
804 return zenPath('Products', self.id, *parts)
805
806
808 """
809 Return True if
810 1) the pack is an old-style ZenPack (not a Python egg)
811 or
812 2) the pack is a Python egg and is a source install (includes a
813 setup.py file)
814
815 Returns False otherwise.
816 """
817 if self.isEggPack():
818 return os.path.isfile(self.eggPath('setup.py'))
819 return True
820
821
823 """
824 Return True if this is a new-style (egg) zenpack, false otherwise
825 """
826 return self.eggPack
827
828
830 """
831 Return the importable dotted module name for this zenpack.
832 """
833 if self.isEggPack():
834 name = self.getModule().__name__
835 else:
836 name = 'Products.%s' % self.id
837 return name
838
839
840
841
842
843
844
845
847 """
848 Write appropriate values to the setup.py file
849 """
850 import Products.ZenUtils.ZenPackCmd as ZenPackCmd
851 if not self.isEggPack():
852 raise ZenPackException('Calling writeSetupValues on non-egg zenpack.')
853
854
855 packages = []
856 parts = self.id.split('.')
857 for i in range(len(parts)):
858 packages.append('.'.join(parts[:i+1]))
859
860 attrs = dict(
861 NAME=self.id,
862 VERSION=self.version,
863 AUTHOR=self.author,
864 LICENSE=self.license,
865 NAMESPACE_PACKAGES=packages[:-1],
866 PACKAGES = packages,
867 INSTALL_REQUIRES = ['%s%s' % d for d in self.dependencies.items()],
868 COMPAT_ZENOSS_VERS = self.compatZenossVers,
869 PREV_ZENPACK_NAME = self.prevZenPackName,
870 )
871 ZenPackCmd.WriteSetup(self.eggPath('setup.py'), attrs)
872
873
875 """
876 Rebuild the egg info to update dependencies, etc
877 """
878 p = subprocess.Popen('python setup.py egg_info',
879 stderr=sys.stderr,
880 shell=True,
881 cwd=self.eggPath())
882 p.wait()
883
884
886 """
887 Return the distribution that provides this zenpack
888 """
889 if not self.isEggPack():
890 raise ZenPackException('Calling getDistribution on non-egg zenpack.')
891 return pkg_resources.get_distribution(self.id)
892
893
894 - def getEntryPoint(self):
895 """
896 Return a tuple of (packName, packEntry) that comes from the
897 distribution entry map for zenoss.zenopacks.
898 """
899 if not self.isEggPack():
900 raise ZenPackException('Calling getEntryPoints on non-egg zenpack.')
901 dist = self.getDistribution()
902 entryMap = pkg_resources.get_entry_map(dist, 'zenoss.zenpacks')
903 if not entryMap or len(entryMap) > 1:
904 raise ZenPackException('A ZenPack egg must contain exactly one'
905 ' zenoss.zenpacks entry point. This egg appears to contain'
906 ' %s such entry points.' % len(entryMap))
907 packName, packEntry = entryMap.items()[0]
908 return (packName, packEntry)
909
910
912 """
913 Get the loaded module from the given entry point. if not packEntry
914 then retrieve it.
915 """
916 if not self.isEggPack():
917 raise ZenPackException('Calling getModule on non-egg zenpack.')
918 _, packEntry = self.getEntryPoint()
919 return packEntry.load()
920
921
923 """
924 Return the path to the egg supplying this zenpack
925 """
926 if not self.isEggPack():
927 raise ZenPackException('Calling eggPath on non-egg zenpack.')
928 d = self.getDistribution()
929 return os.path.join(d.location, *[p.strip('/') for p in parts])
930
931
937
938
940 """
941 Return True if the egg itself should be deleted when this ZenPack
942 is removed from Zenoss.
943 If the ZenPack code resides in $ZENHOME/ZenPacks then it is
944 deleted, otherwise it is not.
945 """
946 eggPath = self.eggPath()
947 oneFolderUp = eggPath[:eggPath.rfind('/')]
948 if oneFolderUp == zenPath('ZenPacks'):
949 delete = True
950 else:
951 delete = False
952 return delete
953
954
956 """
957 Return the name of submodule of zenpacks that contains this zenpack.
958 """
959 if not self.isEggPack():
960 raise ZenPackException('Calling getPackageName on a non-egg '
961 'zenpack')
962 modName = self.moduleName()
963 return modName.split('.')[1]
964
965
967 """
968 Return a list of installed zenpacks that could be listed as
969 dependencies for this zenpack
970 """
971 result = []
972 for zp in self.dmd.ZenPackManager.packs():
973 try:
974 if zp.id != self.id and zp.isEggPack():
975 result.append(zp)
976 except AttributeError:
977 pass
978 return result
979
980
982 """
983 Return True if the egg is located in the ZenPacks directory,
984 False otherwise.
985 """
986 zpDir = zenPath('ZenPacks') + '/'
987 eggDir = self.eggPath()
988 return eggDir.startswith(zpDir)
989
990
992 """
993 Make sure that the ZenPack can be instantiated and that it
994 is physically present on the filesystem.
995 """
996
997
998
999
1000
1001 try:
1002 if not os.path.isdir(self.path()):
1003 return True
1004 except pkg_resources.DistributionNotFound:
1005 return True
1006
1007
1008 try:
1009 unused = self.packables()
1010 except Exception:
1011 return True
1012
1013 return False
1014
1015
1016
1017
1018
1019
1020
1021 ZenPackBase = ZenPack
1022
1023 InitializeClass(ZenPack)
1024