How to Write a Plugin
Where do they go?
Plugins live in the sos.plugins package. This is the first place that a proper python package exists on the python path (e.g. ./sos/plugins)
The bare minimum
Essentially you need to create a class that inherits from
Plugin and at least one of the provided tagging superclasses.
from sos.plugins import Plugin, RedHatPlugin class MyPlugin(Plugin, RedHatPlugin): pass
Let's copy some stuff
In order for a plugin to actually do any work one of the hook methods must be defined. The
setup hook is called during normal collection on every enabled plugin.
from sos.plugins import Plugin, RedHatPlugin class CopyTest(Plugin, RedHatPlugin): """This plugin copies some stuff""" def setup(self): self.add_copy_spec([ "/path/to/something/interesting", "/path/to/something/else/interesting", ])
The above will copy the two files specified in the
add_copy_spec method to the final archive.
If you only need to add a single file you can call the
add_copy_spec method instead:
def setup(self): self.add_copy_spec("/path/to/something/interesting")
A final version allows optional size-limiting and 'tailing' of files to limit the final report size. Currently this method should only be used for single files or globs that expand to a list of files; size-limiting is not applied to directories added recursively via this method. The
tailit option should only be used with line formatted text data; attempting to use this option with binary files will lead to unpredictable results.
def add_copy_spec_limit(self, copyspec, sizelimit=None, tailit=True): """Add a file or glob but limit it to sizelimit megabytes. If fname is a single file the file will be tailed to meet sizelimit. If the first file in a glob is too large it will be tailed to meet the sizelimit. """
I want to run a program too
If you wish to collect the output of a program as part of your collection process call the
from sos.plugins import Plugin, RedHatPlugin class CopyTest(Plugin, RedHatPlugin): """This plugin copies some stuff""" def setup(self): self.add_cmd_output("bin -v")
add_cmd_output method will also accept a list of programs to run. In this case any other parameters provided to the method will be applied to every program in the list. Note that if using the
root_symlink parameter only a single command is supported.
add_cmd_output method will execute it's argument without a shell using the PATH specified by the active policy. There is normally no need to specify an absolute path. If you need to use shell syntax this can be done by calling
sh -c "<command string>".
The output of the command will be added to the report archive under
sos_commands/plugin_name/mangled_command_name. Mangling converts spaces to underscores and removes other characters that are illegal or problematic in path names.
Additionally, the command will be added to the report index automatically.
Attempting to run a command that isn't installed is not treated as an error (but errors produced by commands that are found are logged) - it's fine to go ahead and speculatively try commands in a plugin without explicitly checking for their presence.
add_cmd_output method also accepts several keyword parameters to control the collection process:
def add_cmd_output(self, exe, suggest_filename=None, root_symlink=None, timeout=300, stderr=True, chroot=True, runat=None): """Run a program or list of programs and collect the output"""
suggest_filename allows a plugin to override the default choice of file name in the report archive.
A symbolic link to the collected file from the report's root directory can be requested using the
timeout parameter sets a maximum time (in seconds) to wait for the child process to exit. After this time sos will abandon the child and continue with report generation.
stderr parameter is
True the stderr stream of the child process will be captured along with stdout; otherwise stderr is discarded.
chroot parameter is
True commands are executed in the configured system root directory (which may not be
/). This parameter has no effect unless sos is running in a chrooted environment.
A directory may be specified via the
runat program. The child will switch to this directory before executing the command.
You can inform sosreport that your plugin should only run when certain conditions are met. The default behavior checks for the presence of files or packages specified in the plugin class. More complex checks can be implemented by overriding the
check_enabled method of the base
from sos.plugins import Plugin, RedHatPlugin class DepTest(Plugin, RedHatPlugin): """This plugin depends on a file or package""" files = ('/path/to/file/I/need',) packages = ('name-of-package',) def setup(self): self.add_copy_spec([ "/path/to/something", "/path/to/something/else", ])
Note: if you use a tuple for
packages be sure that you place the trailing comma in the case of a 1-tuple.
('some string') does not create a 1-tuple, but
('some string',) does.
Be aware that if any of the files or packages are found then the plugin will attempt to run. If you need to ensure that multiple files are in place or multiple packages are in place then you will want to implement your own
from sos.plugins import Plugin, RedHatPlugin from os.path import exists class DepTest(Plugin, RedHatPlugin): """This plugin depends on something""" def check_enabled(self): files = [ '/path/to/thing/i/need', '/path/to/other/thing/i/need' ] return all(map(exists,files)) def setup(self): self.add_copy_spec([ "/path/to/something", "/path/to/something/else", ])
Run as a Regular User
Most plugins require super user access to perform their tasks. However, certain plugins may not have this requirement. If your plugin does not need root access then you can set a class-level variable
from sos.plugins import Plugin, IndependentPlugin class MyPlugin(Plugin, IndependentPlugin): requires_root = False def setup(self): pass
Cross Platform Plugins
If your plugin needs to run on multiple platforms you can do that a couple ways. If the rules for collection are the same on every platform (like for say JBOSS) then subclassing
IndependentPlugin is all you really need to do:
from sos.plugins import Plugin, IndependentPlugin class MyPlugin(Plugin, IndependentPlugin): def setup(self): pass # do stuff
However, if you need to do different things on different platforms you need to define one plugin per platform, like so:
from sos.plugins import Plugin, RedHatPlugin, DebianPlugin class MyRedHatPlugin(Plugin, RedHatPlugin): name = "myplugin" def setup(self): pass # do red hat specific stuff class MyDebianPlugin(Plugin, DebianPlugin): name = "myplugin" def setup(self): pass # do debian specific stuff
Notice how both plugins have a class-level
name attribute. This should be the same for all platform-specific implementations of your plugin. The name attribute determines the name presented to the user for plugin selection as well as option definition.
In some cases you may wish to share certain bits between platform-specific plugins, in this case you can make a common shared superclass:
from sos.plugins import Plugin, RedHatPlugin, DebianPlugin class MyPlugin(Plugin): name = "myplugin" def setup(self): pass # do common things here class MyRedHatPlugin(MyPlugin, RedHatPlugin): def setup(self): super(MyRedHatPlugin, self).setup() pass # do red hat specific stuff class MyDebianPlugin(MyPlugin, DebianPlugin): def setup(self): super(MyDebianPlugin, self).setup() pass # do debian specific stuff
Note how the leaf classes are still the only ones that subclass things like
DebianPlugin. This ensures that your shared plugin class does not get run as a plugin on its own. Note that for this scheme to work correctly it's important for the leaf classes to use appropriate
super(MySuperClass, self).method() calls in order to properly inherit the generic plugin's behavior (unless intentionally overriding an entire method).
Things to remember
If unicode characters are included in the source of your plugin commonly seen with a person's name in the copyright. Python 2 will require adding the following to the code somewhere near the top:
# -*- coding: utf8 -*-