Skip to content

How to Write a Plugin

jhjaggars edited this page Mar 8, 2012 · 35 revisions

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. Currently there is only one tagging superclass, RedHatPlugin.

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.addCopySpecs([
            "/path/to/something/interesting",
            "/path/to/something/else/interesting",
            ])

The above will copy the two files specified in the addCopySpecs method to the final archive.

I want to run a program too

If you wish to collect the output of a program as part of your collection process then you only need to call the collectExtOutput method.

from sos.plugins import Plugin, RedHatPlugin

class CopyTest(Plugin, RedHatPlugin):
    """This plugin copies some stuff"""

    def setup(self):
        self.collectExtOutput("/path/to/bin -v")
        self.collectExtOutput('/path/to/bin | /path/to/another/bin | grep -v "stuff I want to omit"')

The collectExtOutput method will execute it's argument via the shell. The output of the command will be added to the report archive under sos_commands/plugin_name/mangled_command_name. Additionally, the command will be added to the report index automatically.

Dependencies

You can inform sosreport that your plugin should not be run unless certain conditions are met. In order to do this define a checkenabled method. Sosreport provides default behavior for this method if all you want to do is check for files on the system or for installed packages.

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 = ('nameOfPackageINeed',)

    def setup(self):
        self.addCopySpecs([
            "/path/to/something",
            "/path/to/something/else",
        ])

Note: if you use a tuple for files or 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 checkenabled method.

from sos.plugins import Plugin, RedHatPlugin
from os.path import exists

class DepTest(Plugin, RedHatPlugin):
    """This plugin depends on something"""

    def checkenabled(self):
        return exists('/path/to/thing/i/need') and exists('/path/to/other/thing/i/need')

    def setup(self):
        self.addCopySpecs([
            "/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 requires_root to False.

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 RedHatPlugin and DebianPlugin. This ensures that your shared plugin class does not get run as a plugin on its own.