Skip to content

Instantly share code, notes, and snippets.

@James-Newman
Last active June 23, 2021 13:37
Show Gist options
  • Save James-Newman/9609c84688a0b9a4fee842878b9a5b00 to your computer and use it in GitHub Desktop.
Save James-Newman/9609c84688a0b9a4fee842878b9a5b00 to your computer and use it in GitHub Desktop.
## This is an example of executing code when your zenpack gets installed (or removed)
## This __init__.py file should be located at ZenPacks.namespace.Name/ZenPacks/namespace/Name/__init__.py
## This code should be carefully added to your zenpack so as not to accidently overwrite key methods (
'''
Here begins the non default code
'''
# Import all the things.
import logging
from Products.ZenModel.ZenPack import ZenPack as ZenPackBase
# Set Logger to the ZenPack.CustomLoader logger, you can change the logger name to whatever you like.
# Log information shows up in $ZENHOME/log/events.log, as well as stdout if installing/removing via the command line
log = logging.getLogger('zen.ZenPack.CustomLoader')
# Make our configuration variable names global,
# its easier than passing them around to every method
global deviceClassToAdd
global templatesToAdd
global eventClassToAdd
global leaveDeviceClass
global updateTemplates
'''
Create device class, event class and assign monitoring templates
'''
# If you add a device class to a ZenPack, when you remove the zenpack, it removes the device class
# This may have undesirable / catastrophic results as removing a device class removes all devices and subdevices/classes without warning
# Adding the device class in this way means that we have greater control over the device class and what properties get set and how
# We use deviceClassToAdd as a dictionary of classes and templates
# Template name should be identical to the one either already in Zenoss or provided in the objects.xml or zenpacklib.yml file within the ZenPack
# Thats right, you can use this way of adding templates to add classes that are provided by other zenpacks
# Leaving the value as an empty list means that no changes get done, the class just inherits from the parent
# If a parent doesn't exist, it automatically gets created
deviceClassToAdd = {'Server/Linux/Example/AuthenticationServer' : ['AuthenticationServerCore', 'AuthenticationServerUsers'],
'Server/Linux/Example/Dummy': []
}
# Event classes are much less impactful when removing a zenpack, The only problem you might find is when multiple zenpacks/templates
# share a common event class
# As such, I find this approach much easier, this also covers issues where you want to add an event class as a child of another class
# however the parent doesn't exist on the zenoss install you're installing to.
# If a parent doesn't exist, it automatically gets created
eventClassToAdd = ['App/Example/AuthenticationServer',
'/Example/Dummy']
# These our the properties that mean our classes stick around during an uninstall or not
# Times when you want to keep the classes are when you have other zenpacks that are using the class, or a child of the class
# Or when you want to keep things around for posterity because you're a sentimental old fool and you like a bit of whimsy.
leaveDeviceClass = True
leaveEventClass = True
'''
End Lazy Loader config
'''
# This is now the default ZenPack(ZenPackBase) class that you *should* find in your __init__.py file already
# Some parts you may want to remove depending on if you're using zenpacklib or not (you probably do want to be using zenpacklib)
class ZenPack(ZenPackBase):
'''
Only defiine zProperties here if you're not using zenpacklib!!
'''
# All zProperties defined here will automatically be created when the
# ZenPack is installed. These properties will be removed when removing the ZenPack.
packZProperties = [
('zAuthenticationServerJmxPort', 12345, 'int'),
]
# This is the standard install method. It gets called when you install the zenpack
def install(self, app):
log.info('Beginning Installation.')
# Create the new device classes and add templates
# We iterate over the class/templates in the deviceClassToAdd dictionary
for classToAdd, templatesToAdd in deviceClassToAdd.iteritems():
# Add the requested device class
# Here we kick our the actual creation to a new method to make things easier for us
addedDeviceClass = self.createDeviceOrganiserPath(classToAdd)
# You can also run a bunch of custom stuff here on the new addedDeviceClass if you want
# for example you can set the properties using
# addedDeviceClass.zCommandUsername = "root"
# addedDeviceClass.zCommandPassword = "NOTSECURE"
# Obviously, any passwords you put in here aren't secure, as your __init__.py is plain text!
# Once the class is added, we can add the template, only if the template list has an element
# Again we kick it out to a new method to make things easier for us
if len(templatesToAdd) > 0:
# Add the requested templates to the new device class
self.setTemplates(addedDeviceClass, templatesToAdd)
# Create the event class
# Simple one this, again we kick it out to another new method
for eventClass in eventClassToAdd:
addedEventClass = self.createEventOrganiserPath(eventClass)
# You can also run a bunch of custom stuff here on the new addedEventClass if you want
# for example you can set the transform using
# addedEventClass.transform = "evt.severity = 5"
# But wait, theres more!
# You can also manipulate code here that's not added as part of your zenpack at all.
# Say you were just adding a template to an existing class, but not the class itself (updated linux monitoring for your environment for example)
# You can adjust properties here as well directly by using the object directly from dmd
# dmd.Devices.Server.Linux.zKeyPath = "/opt/zenoss/SSHKeys/zenoss_key"
# The same goes for event classes using
# dmd.Events.App.Example.transform = "evt.severity = 5"
# Instruct Zenoss to install any objects into Zope from the objects.xml file contained inside the ZenPack
# Once you get down here, running this next line will tell zenoss to install the zenpack as it normally would
# Double check with your existing __init__.py file if you're using zenpacklib as I believe it does the install
# in a different way
ZenPackBase.install(self, app)
# This is the standard remove method
# it gets called when you remove the zenpack
def remove(self, app, leaveObjects=False):
log.info('Beginning ZenPack removal.')
# Remove the device class, this ensures that we don't remove devices as well if we don't want to
# Again we iterate over the device classes and templates that the zenpack adds
for classToRemove, templatesToRemove in deviceClassToAdd.iteritems():
if self.dmd.Devices.getOrganizer(classToRemove):
# Only if we're removing templates do we run the new method to remove templates from device classes
if len(templatesToRemove) > 0:
self.removeTemplatesFromDeviceClass(classToRemove, templatesToRemove)
# This check here is what stops us removing device classes. If we have leaveDeviceClass set to True,
# then the device class will be left behind
if leaveDeviceClass == False:
deviceList = self.removeDeviceOrganiser(classToRemove)
# Same thing for the event class
if leaveEventClass == False:
# Iterate over the event classes that we added and remove them
for eventClass in eventClassToAdd:
if self.dmd.Events.getOrganizer(eventClassToAdd):
self.removeEventOrganiser(eventClassToAdd)
# But wait, theres more!
# If you made any custom changes to the install method, you might want to revert them in the remove method as well!
# Or you might not! You can choose!
# You can also manipulate code here that's not added as part of your zenpack at all.
# Say you were just adding a template to an existing class, but not the class itself (updated linux monitoring for your environment for example)
# You can adjust properties here as well directly by using the object directly from dmd
# dmd.Devices.Server.Linux.zKeyPath = "/opt/zenoss/SSHKeys/zenoss_key"
# The same goes for event classes using
# dmd.Events.App.Example.transform = "evt.severity = 5"
# Instruct Zenoss to remove any objects from Zope from the objects.xml file contained inside the ZenPack
# Once you get down here, running this next line will tell zenoss to remove the zenpack as it normally would
# Double check with your existing __init__.py file if you're using zenpacklib as I believe it does the remove
# in a different way
ZenPackBase.remove(self, app)
'''The Installation Methods'''
# All the new custom methods that will install things for you
def createDeviceOrganiserPath(self, deviceClassToAddString):
'''
This creates the iterative device class path
Why do we do it this way? Well if you want to add /Server/Linux/Foo/Bar in your zenpack
but /Server/Linux/Foo doesn't exist in your zenoss install then you get a slightly confusing
KeyError exception when you try and install things
Adding the device class iteratively gets around this
'''
# Split up the requested device class and sequentially create it.
classList = []
classList = deviceClassToAddString.split('/')
# Loop over the Class List and create all required child classes.
for i in range(len(classList)):
org = ('/').join(classList[:len(classList)+1-(len(classList)-i)]) # The sequenial path generator! Fear it's confusion.
try:
# Test for the class already existing
if self.dmd.Devices.getOrganizer(org):
log.info('Device Class %s already exists.', str(org))
except KeyError:
# The class doesn't exist, so we create it.
log.info('Creating new device class at %s', str(org))
self.dmd.Devices.createOrganizer(org)
from transaction import commit
commit()
return self.dmd.Devices.getOrganizer(deviceClassToAddString)
def setTemplates(self, deviceClass, newTemplates):
'''
This new method sets the templates for us. We do this by
manipulating the zDeviceTemplates property of the class
'''
# Obtain the zDeviceTemplates of the newly created class, and add any extras.
# We don't need to worry about getting the parent templates and artificially inheriting them,
# Zenoss takes care of this for us.
log.info('The following templates will be added; %s.', str(newTemplates))
# Get the zDeviceTemplates of the new device class and copy it to a new list
templates = list(deviceClass.zDeviceTemplates)
log.info('The following templates have been inherited already; %s', str(templates))
# Loop over the list of templates provided in the config section
updateTemplates = False
for template in newTemplates:
if template not in templates:
# Template is new, so we add it to the templates list.
templates.append(template)
updateTemplates = True
log.info('%s added to templates', template)
if updateTemplates == True:
# If we need to update the templates on the device class, here we set the Zen Property and commit the change
# Doing this automatically sets the zDeviceTemplates as a local copy
# It will stop inheriting changes to parent properties!
deviceClass.setZenProperty( 'zDeviceTemplates', templates )
log.info('Device Class zDeviceTemplates updated to: %s', str(templates))
from transaction import commit
commit()
else:
# We don't have to update the templates, so we just log that and end the function
# This way we don't start creating local copies of the zproperty if you dont need to
log.info('No new templates need to be added.')
def createEventOrganiserPath(self, eventClass):
'''
This is very similar to the deviceClass method above, but this only only creates
event classes.
'''
# Split up the requested event class and sequentially create it.
classList = []
classList = eventClass.split('/')
# Loop over the Event list and create all required child classes.
for i in range(len(classList)):
org = ('/').join(classList[:len(classList)+1-(len(classList)-i)]) # The sequenial path generator! Fear it's confusion.
try:
# Test if the event class already exists
if self.dmd.Events.getOrganizer(org):
log.info('Event Class %s already exists.', str(org))
except KeyError:
# The class doesn't exist, so we create it
log.info('Creating new event class at %s', str(org))
self.dmd.Events.createOrganizer(org)
from transaction import commit
commit()
return self.dmd.Events.getOrganizer(eventClass)
'''The Removal Methods'''
def removeTemplatesFromDeviceClass(self, deviceClassToRemoveString, templatesToRemove):
'''
This new method removes the previously added templates for us. We do this by
manipulating the zDeviceTemplates property of the class
'''
# Obtain the device class that we need to remeove the tempalte from
deviceClass = self.dmd.Devices.getOrganizer(deviceClassToRemoveString)
# Copy the device template list from the device class
templates = list(deviceClass.zDeviceTemplates)
# Loop over the list of templates that we need to remove from the device class
updateTemplates = False
for template in templatesToRemove:
if template in templates:
# The template to remove has been found in the current templates. We remove it from the list
templates.remove(template)
log.info('%s has been configured to be removed from the Device Class %s' % (template, deviceClassToRemoveString))
updateTemplates = True
else:
# The template hasn't been found. We don't need to remove the template.
log.info("%s hasn't been found within the existing list of templates: %s" % (template, templates))
if updateTemplates == True:
# Taking our new templates list, we set the Device class property as required and commit
# It should be noted that this will still leave the zProperty as a local copy
# its left as an exercise for the reader to determine if they want this behaviour,
# and if not, adjust the code accordingly.
deviceClass.setZenProperty( 'zDeviceTemplates', templates )
log.info('Device Class zDeviceTemplates updated to: %s', str(templates))
from transaction import commit
commit()
else:
# Nothing to be done. Log and end the function.
log.info('No templates need to be removed')
def removeDeviceOrganiser(self, deviceClassToRemoveString):
'''
If you want to remove the device class then this chap will run
The benefit of using this code is that while the deviceClass gets removed
the devices in it will be moved to a new Homeless class so that the don't get
completely deleted. You can then delete them manually at your own discretion
'''
# Get our created child device class
deviceClassToRemove = self.dmd.Devices.getOrganizer(deviceClassToRemoveString)
log.info('The following device class will be removed: %s', str(deviceClassToRemove))
# Check if the device class is empty of devices, if its not remidiate.
if len(deviceClassToRemove.getSubDevices_recursive()) > 0:
log.warn('Devices exist within this device class. They will be moved to a holding area under the existing OS class.')
# Set the parent OS device Class, either a two stage Class is returned (Server/Linux), or a one stage class (Networking)
try:
parentOSClass = ('/').join(deviceClassToRemoveString.split('/')[:2])
except:
parentOSClass = ('/').join(deviceClassToRemoveString.split('/')[:1])
# Create a homeless class string based on the parent class
homelessClass = parentOSClass + '/' + 'Homeless'
try:
# Try and get the homeless class object
if self.dmd.Devices.getOrganizer(homelessClass):
log.info('Device Class %s already exists.', str(homelessClass))
except KeyError:
# The homeless class doesn't exist, so we create it
log.info('Creating new Device Class at %s', str(homelessClass))
self.dmd.Devices.createOrganizer(homelessClass)
for dev in deviceClassToRemove.devices():
# Move the devices in our target device class to the homeless device class
log.info('Moving device %s to the Homeless Device Class.', dev.id)
dev.changeDeviceClass(homelessClass)
# Test again to see if there are devices in the class. If there are, something very wrong has broken and we log an error
if len(deviceClassToRemove.getSubDevices_recursive()) > 0:
log.error('Devices exist within this Device Class. Even after moving them to a new class. Something very wrong has happened. The Device Class will NOT be removed.')
else:
# Everything has worked correctly, we remove our target device class
log.info('Attempting to remove the device class.')
classParent = deviceClassToRemove.getParentNode()
classParent.manage_deleteOrganizer(deviceClassToRemoveString.split('/')[-1])
else:
# No devices exist within our target device class, we can just remove the class
classParent = deviceClassToRemove.getParentNode()
classParent.manage_deleteOrganizer(deviceClassToRemoveString.split('/')[-1])
def removeEventOrganiser(self, eventClassToRemoveString):
'''
Removing the event class is much easier, we don't need to worry about devices or events
being contained within the event class here.
'''
log.info('Removing the Event Class %s', eventClassToRemoveString)
# Get our created child event class
eventClassToRemove = self.dmd.Events.getOrganizer(eventClassToRemoveString)
# Get our event class parent
classParent = eventClassToRemove.getParentNode()
# And remove the Event Class in question from its parent.
classParent.manage_deleteOrganizer(eventClassToRemoveString.split('/')[-1])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment