diff options
-rw-r--r-- | docs/plugins.rst | 1 | ||||
-rw-r--r-- | sos/report/plugins/__init__.py | 834 |
2 files changed, 689 insertions, 146 deletions
diff --git a/docs/plugins.rst b/docs/plugins.rst index 9df84227..a1fb0627 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -4,5 +4,4 @@ .. automodule:: sos.report.plugins :noindex: :members: - :undoc-members: :show-inheritance: diff --git a/sos/report/plugins/__init__.py b/sos/report/plugins/__init__.py index 46fa7286..bc601d12 100644 --- a/sos/report/plugins/__init__.py +++ b/sos/report/plugins/__init__.py @@ -78,13 +78,42 @@ _cert_replace = "-----SCRUBBED" class SoSPredicate(object): """A class to implement collection predicates. - A predicate gates the collection of data by an sos plugin. For any - `add_cmd_output()`, `add_copy_spec()` or `add_journal()` call, the - passed predicate will be evaulated and collection will proceed if - the result is `True`, and not otherwise. + A predicate gates the collection of data by an sos plugin. For any + `add_cmd_output()`, `add_copy_spec()` or `add_journal()` call, the + passed predicate will be evaulated and collection will proceed if + the result is `True`, and not otherwise. + + Predicates may be used to control conditional data collection + without the need for explicit conditional blocks in plugins. + + :param owner: The ``Plugin`` object creating the predicate + :type owner: ``Plugin`` + + :param dry_run: Is sos running in dry_run mode? + :type dry_run: ``bool`` + + :param kmods: Kernel module name(s) to check presence of + :type kmods: ``list``, or ``str`` of single name + + :param services: Service name(s) to check if running + :type services: ``list``, or ``str`` of single name + + :param packages: Package name(s) to check presence of + :type packages: ``list``, or ``str`` of single name + + :param cmd_outputs: Command to run, with output string to check + :type cmd_outputs: ``list`` of ``dict``s, or single ``dict`` taking form + {'cmd': <command to run>, + 'output': <string that output should contain>} + :param arch: Architecture(s) that the local system is matched + against + :type arch: ``list``, or ``str`` of single architecture + + :param required: For each parameter provided, should the checks + require all items, no items, or any items provided + :type required: ``dict``, with keys matching parameter names and values + being either 'any', 'all', or 'none. Default 'any'. - Predicates may be used to control conditional data collection - without the need for explicit conditional blocks in plugins. """ #: The plugin that owns this predicate _owner = None @@ -320,7 +349,7 @@ class SoSPredicate(object): def __init__(self, owner, dry_run=False, kmods=[], services=[], packages=[], cmd_outputs=[], arch=[], required={}): - """Initialise a new SoSPredicate object. + """Initialise a new SoSPredicate object """ self._owner = owner self.kmods = list(kmods) @@ -371,25 +400,53 @@ class SoSCommand(object): class Plugin(object): - """ This is the base class for sosreport plugins. Plugins should subclass + """This is the base class for sosreport plugins. Plugins should subclass this and set the class variables where applicable. - plugin_name is a string returned by plugin.name(). If this is set to None - (the default) class\\_.__name__.tolower() will be returned. Be sure to set - this if you are defining multiple plugins that do the same thing on - different platforms. + :param commons: A set of information that is shared internally so that + plugins may access the same dataset. This is provided + automatically by sos + :type commons: ``dict`` + + Each `Plugin()` subclass should also subclass at least one tagging class, + e.g. ``RedHatPlugin``, to support that distribution. If different + distributions require different collections, each distribution should have + its own subclass of the Plugin that also subclasses the tagging class for + their respective distributions. + + :cvar plugin_name: The name of the plugin, will be returned by `name()` + :vartype plugin_name: ``str`` + + :cvar version: The version of the plugin, defaults to 'unversioned' + :vartype version: ``str`` + + :cvar packages: Package name(s) that, if installed, enable this plugin + :vartype packages: ``tuple`` + + :cvar files: File path(s) that, if present, enable this plugin + :vartype files: ``tuple`` - version is a string representing the version of the plugin. This can be - useful for post-collection tooling. + :cvar commands: Executables that, if present, enable this plugin + :vartype commands: ``tuple`` - packages (files) is an iterable of the names of packages (the paths - of files) to check for before running this plugin. If any of these packages - or files is found on the system, the default implementation of - check_enabled will return True. + :cvar kernel_mods: Kernel module(s) that, if loaded, enable this plugin + :vartype kernel_mods: ``tuple`` - profiles is an iterable of profile names that this plugin belongs to. - Whenever any of the profiles is selected on the command line the plugin - will be enabled (subject to normal check_enabled tests). + :cvar services: Service name(s) that, if running, enable this plugin + :vartype services: ``tuple`` + + :cvar architectures: Architecture(s) this plugin is enabled for. Defaults + to 'none' to enable on all arches. + :vartype architectures: ``tuple``, or ``None`` + + :cvar profiles: Name(s) of profile(s) this plugin belongs to + :vartype profiles: ``tuple`` + + :cvar plugin_timeout: Timeout in seconds for this plugin as a whole + :vartype plugin_timeout: ``int`` + + :cvar cmd_timeout: Timeout in seconds for individual commands + :vartype cmd_timeout: ``int`` """ plugin_name = None @@ -456,6 +513,9 @@ class Plugin(object): def set_plugin_manifest(self, manifest): """Pass in a manifest object to the plugin to write to + + :param manifest: The manifest that the plugin will add metadata to + :type manifest: ``SoSManifest`` """ self.manifest = manifest # add these here for organization when they actually get set later @@ -511,15 +571,18 @@ class Plugin(object): handed to that call to use as a polling method, to avoid passing the entire plugin object. - Returns True if timeout has been hit, else False. + :returns: ``True`` if timeout has been hit, else ``False`` + :rtype: ``bool`` """ return self._timeout_hit @classmethod def name(cls): - """Returns the plugin's name as a string. This should return a - lowercase string. + """Get the name of the plugin + + :returns: The name of the plugin, in lowercase + :rtype: ``str`` """ if cls.plugin_name: return cls.plugin_name @@ -541,11 +604,27 @@ class Plugin(object): self.soslog.debug(self._format_msg(msg)) def join_sysroot(self, path): + """Join a given path with the configured sysroot + + :param path: The filesystem path that needs to be joined + :type path: ``str`` + + :returns: The joined filesystem path + :rtype: ``str`` + """ if path[0] == os.sep: path = path[1:] return os.path.join(self.sysroot, path) def strip_sysroot(self, path): + """Remove the configured sysroot from a filesystem path + + :param path: The filesystem path to strip sysroot from + :type path: ``str`` + + :returns: The stripped filesystem path + :rtype: ``str`` + """ if not self.use_sysroot(): return path if path.startswith(self.sysroot): @@ -553,61 +632,134 @@ class Plugin(object): return path def use_sysroot(self): + """Determine if the configured sysroot needs to be used + + :returns: ``True`` if sysroot is not `/`, else ``False`` + :rtype: ``bool`` + """ return self.sysroot != os.path.abspath(os.sep) def tmp_in_sysroot(self): + """Check if sysroot is within the archive's temp directory + + :returns: ``True`` if sysroot is in the archive's temp directory, else + ``False`` + :rtype: ``bool`` + """ paths = [self.sysroot, self.archive.get_tmp_dir()] return os.path.commonprefix(paths) == self.sysroot def is_installed(self, package_name): - """Is the package $package_name installed?""" + """Is the package $package_name installed? + + :param package_name: The name of the package to check + :type package_name: ``str`` + + :returns: ``True`` id the package is installed, else ``False`` + :rtype: ``bool`` + """ return self.policy.pkg_by_name(package_name) is not None def is_service(self, name): - """Does the service $name exist on the system?""" + """Does the service $name exist on the system? + + :param name: The name of the service to check + :type name: ``str`` + + :returns: ``True`` if service is present on the system, else ``False`` + :rtype: ``bool`` + """ return self.policy.init_system.is_service(name) def is_service_enabled(self, name): - """Is the service $name enabled?""" + """Is the service $name enabled? + + :param name: The name of the service to check + :type name: ``str`` + + :returns: ``True if service is enabled on the system, else ``False`` + :rtype: ``bool`` + """ return self.policy.init_system.is_enabled(name) def is_service_disabled(self, name): - """Is the service $name disabled?""" + """Is the service $name disabled? + + :param name: The name of the service to check + :type name: ``str`` + + :returns: ``True`` if service is disabled on the system, else ``False`` + :rtype: ``bool`` + """ return self.policy.init_system.is_disabled(name) def is_service_running(self, name): - """Is the service $name currently running?""" + """Is the service $name currently running? + + :param name: The name of the service to check + :type name: ``str`` + + :returns: ``True`` if the service is running on the system, else + ``False`` + :rtype: ``bool`` + """ return self.policy.init_system.is_running(name) def get_service_status(self, name): - """Return the reported status for service $name""" + """Return the reported status for service $name + + :param name: The name of the service to check + :type name: ``str`` + + :returns: The state of the service according to the init system + :rtype: ``str`` + """ return self.policy.init_system.get_service_status(name)['status'] def get_service_names(self, regex): - """Get all service names matching regex""" + """Get all service names matching regex + + :param regex: A regex to match service names against + :type regex: ``str`` + + :returns: All service name(s) matching the given `regex` + :rtype: ``list`` + """ return self.policy.init_system.get_service_names(regex) def set_predicate(self, pred): """Set or clear the default predicate for this plugin. + + :param pred: The predicate to use as the default for this plugin + :type pred: ``SoSPredicate`` """ self.predicate = pred def set_cmd_predicate(self, pred): - """Set or clear the default predicate for command collection - for this plugin. If set, this predecate takes precedence - over the `Plugin` default predicate for command and journal - data collection. + """Set or clear the default predicate for command collection for this + plugin. If set, this predecate takes precedence over the `Plugin` + default predicate for command and journal data collection. + + :param pred: The predicate to use as the default command predicate + :type pred: ``SoSPredicate`` """ self.cmd_predicate = pred def get_predicate(self, cmd=False, pred=None): - """Get the current default `Plugin` or command predicate. If the - `cmd` argument is `True`, the current command predicate is - returned if set, otherwise the default `Plugin` predicate - will be returned (which may be `None`). + """Get the current default `Plugin` or command predicate. + + :param cmd: If a command predicate is set, should it be used. + :type cmd: ``bool`` - If no default predicate is set and a `pred` value is passed - it will be returned. + :param pred: An optional predicate to pass if no command or plugin + predicate is set + :type pred: ``SoSPredicate`` + + :returns: `pred` if neither a command predicate or plugin predicate + is set. The command predicate if one is set and `cmd` is + ``True``, else the plugin default predicate (which may be + ``None``). + :rtype: ``SoSPredicate`` or ``None`` """ if pred is not None: return pred @@ -618,10 +770,14 @@ class Plugin(object): def test_predicate(self, cmd=False, pred=None): """Test the current predicate and return its value. - :param cmd: ``True`` if the predicate is gating a command or - ``False`` otherwise. - :param pred: An optional predicate to override the current - ``Plugin`` or command predicate. + :param cmd: ``True`` if the predicate is gating a command or + ``False`` otherwise. + :param pred: An optional predicate to override the current + ``Plugin`` or command predicate. + + :returns: ``True`` or ``False`` based on predicate evaluation, or + ``False`` if no predicate + :rtype: ``bool`` """ pred = self.get_predicate(cmd=cmd, pred=pred) if pred is not None: @@ -632,12 +788,27 @@ class Plugin(object): changes=False): """Log that a command was skipped due to predicate evaluation. - Emit a warning message indicating that a command was skipped due - to predicate evaluation. If ``kmods`` or ``services`` are ``True`` - then the list of expected kernel modules or services will be - included in the log message. If ``allow_changes`` is ``True`` a - message indicating that the missing data can be collected by using - the "--allow-system-changes" command line option will be included. + Emit a warning message indicating that a command was skipped due + to predicate evaluation. If ``kmods`` or ``services`` are ``True`` + then the list of expected kernel modules or services will be + included in the log message. If ``allow_changes`` is ``True`` a + message indicating that the missing data can be collected by using + the "--allow-system-changes" command line option will be included. + + :param pred: The predicate that caused the command to be skipped + :type pred: ``SoSPredicate`` + + :param cmd: The command that was skipped + :type cmd: ``str`` + + :param kmods: Did kernel modules cause the command to be skipped + :type kmods: ``bool`` + + :param services: Did services cause the command to be skipped + :type services: ``bool`` + + :param changes: Is the `--allow-system-changes` enabled + :type changes: ``bool`` """ msg = "skipped command '%s': %s" % (cmd, pred.report_failure()) @@ -647,12 +818,18 @@ class Plugin(object): self._log_warn(msg) def do_cmd_private_sub(self, cmd, desc=""): - """Remove certificate and key output archived by sosreport. cmd - is the command name from which output is collected (i.e. exlcuding - parameters). Any matching instances are replaced with: '-----SCRUBBED' - and this function does not take a regexp or substituting string. + """Remove certificate and key output archived by sos report. + Any matching instances are replaced with: '-----SCRUBBED' and this + function does not take a regexp or substituting string. + + :param cmd: The name of the binary to scrub certificate output from + :type cmd: ``str`` + + :param desc: An identifier to add to the `SCRUBBED` header line + :type desc: ``str`` - This function returns the number of replacements made. + :returns: Number of replacements made + :rtype: ``int`` """ if not self.executed_commands: return 0 @@ -666,14 +843,25 @@ class Plugin(object): def do_cmd_output_sub(self, cmd, regexp, subst): """Apply a regexp substitution to command output archived by sosreport. - cmd is the command name from which output is collected (i.e. excluding - parameters). The regexp can be a string or a compiled re object. The - substitution string, subst, is a string that replaces each occurrence - of regexp in each file collected from cmd. Internally 'cmd' is treated - as a glob with a leading and trailing '*' and each matching file from - the current module's command list is subjected to the replacement. - This function returns the number of replacements made. + This is used to obfuscate sensitive information captured by command + output collection via plugins. + + :param cmd: The command name/binary name for collected output that + needs to be obfuscated. Internally globbed with a leading + and trailing `*` + :type cmd: ``str`` + + :param regexp: A regex to match the contents of the command output + against + :type regexp: ``str`` or compile ``re`` object + + :param subst: The substitution string used to replace matches from + `regexp` + :type subst: ``str`` + + :returns: Number of replacements made + :rtype: ``int`` """ globstr = '*' + cmd + '*' pattern = regexp.pattern if hasattr(regexp, "pattern") else regexp @@ -718,9 +906,11 @@ class Plugin(object): "-----SCRUBBED RSA PRIVATE KEY" so that support representatives can at least be informed of what type of content it was originally. - Positional arguments: - :param pathregex: A string or regex of a filename to match against - :param desc: A description of the replaced content + :param pathregex: A string or regex of a filename to match against + :type pathregex: ``str`` + + :param desc: A description of the replaced content + :type desc: ``str`` """ self._log_debug("Scrubbing certs and keys for paths matching %s" % pathregex) @@ -735,11 +925,19 @@ class Plugin(object): def do_file_sub(self, srcpath, regexp, subst): """Apply a regexp substitution to a file archived by sosreport. - srcpath is the path in the archive where the file can be found. regexp - can be a regexp string or a compiled re object. subst is a string to - replace each occurance of regexp in the content of srcpath. - This function returns the number of replacements made. + :param srcpath: Path in the archive where the file can be found + :type srcpath: ``str`` + + :param regexp: A regex to match the contents of the file + :type regexp: ``str`` or compiled ``re`` object + + :param subst: The substitution string to be used to replace matches + within the file + :type subst: ``str`` + + :returns: Number of replacements made + :rtype: ``int`` """ try: path = self._get_dest_for_srcpath(srcpath) @@ -773,10 +971,17 @@ class Plugin(object): def do_path_regex_sub(self, pathexp, regexp, subst): """Apply a regexp substituation to a set of files archived by sos. The set of files to be substituted is generated by matching - collected file pathnames against pathexp which may be a regular - expression string or compiled re object. The portion of the file - to be replaced is specified via regexp and the replacement string - is passed in subst.""" + collected file pathnames against `pathexp`. + + :param pathexp: A regex to match filenames within the archive + :type pathexp: ``str`` or compiled ``re`` object + + :param regexp: A regex to match against the contents of each file + :type regexp: ``str`` or compiled ``re`` object + + :param subst: The substituion string to be used to replace matches + :type subst: ``str`` + """ if not hasattr(pathexp, "match"): pathexp = re.compile(pathexp) match = pathexp.match @@ -943,8 +1148,11 @@ class Plugin(object): }) def add_forbidden_path(self, forbidden): - """Specify a path, or list of paths, to not copy, even if it's - part of a copy_specs[] entry. + """Specify a path, or list of paths, to not copy, even if it's part of + an ``add_copy_spec()`` call + + :param forbidden: A filepath to forbid collection from + :type forbidden: ``str`` or a ``list`` of strings """ if isinstance(forbidden, str): forbidden = [forbidden] @@ -962,8 +1170,16 @@ class Plugin(object): return (self.opt_names, self.opt_parms) def set_option(self, optionname, value): - """Set the named option to value. Ensure the original type - of the option value is preserved. + """Set the named option to value. Ensure the original type of the + option value is preserved + + :param optioname: The name of the option to set + :type optioname: ``str`` + + :param value: The value to set the option to + + :returns: ``True`` if the option is successfully set, else ``False`` + :rtype: ``bool`` """ for name, parms in zip(self.opt_names, self.opt_parms): if name == optionname: @@ -980,12 +1196,20 @@ class Plugin(object): return False def get_option(self, optionname, default=0): - """Returns the first value that matches 'optionname' in parameters - passed in via the command line or set via set_option or via the - global_plugin_options dictionary, in that order. + """Retrieve the value of the requested option, searching in order: + parameters passed from the command line, set via `set_option()`, or the + global_plugin_options dict. + + `optionname` may be iterable, in which case this function will return + the first match. + + :param optionname: The name of the option to retrieve the value of + :type optionname: ``str`` - optionaname may be iterable, in which case the first option that - matches any of the option names is returned. + :param default: Optionally provide a default value to return if no + option matching `optionname` is found. Default 0 + + :returns: The value of `optionname` if found, else `default` """ global_options = ( @@ -1070,11 +1294,39 @@ class Plugin(object): def add_copy_spec(self, copyspecs, sizelimit=None, maxage=None, tailit=True, pred=None, tags=[]): - """Add a file or glob but limit it to sizelimit megabytes. Collect - files with mtime not older than maxage hours. - 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. + """Add a file, directory, or regex matching filepaths to the archive + + :param copyspecs: A file, directory, or regex matching filepaths + :type copyspecs: ``str`` or a ``list`` of strings + + :param sizelimit: Limit the total size of collections from `copyspecs` + to this size in MB + :type sizelimit: ``int`` + + :param maxage: Collect files with `mtime` not older than this many + hours + :type maxage: ``int`` + + :param tailit: Should a file that exceeds `sizelimit` be tail'ed to fit + the remaining space to meet `sizelimit` + :type tailit: ``bool`` + + :param pred: A predicate to gate if `copyspecs` should be collected + :type pred: ``SoSPredicate`` + + :param tags: A tag or set of tags to add to the metadata information + for this collection + :type tags: ``str`` or a ``list`` of strings + + `copyspecs` will be expanded and/or globbed as appropriate. Specifying + a directory here will cause the plugin to attempt to collect the entire + directory, recursively. + + Note that `sizelimit` is applied to each `copyspec`, not each file + individually. For example, a copyspec of + ``['/etc/foo', '/etc/bar.conf']`` and a `sizelimit` of 25 means that + sos will collect up to 25MB worth of files within `/etc/foo`, and will + collect the last 25MB of `/etc/bar.conf`. """ since = None if self.get_option('since'): @@ -1230,16 +1482,42 @@ class Plugin(object): specified devices. Commands passed to this should include a '%(dev)s' variable for substitution. - devices - Either 'block', 'fibre', or a list of device paths to - run against. If set to 'block' or 'fibre', the commands - will be run against the matching list of discovered - devices. - prepend_path - the leading path for block device names, e.g. - '/dev/' or '/sys/block/'. - whitelist - limit the devices iterated over. May be a str or list - of strings (e.g. regexes) - blacklist - exclude the devices iterated over. May be a str or list - of strings (e.g. regexes) + :param cmds: The command(s) to run against the list of devices + :type cmds: ``str`` or a ``list`` of strings + + :param devices: The device paths to run `cmd` against. If set to + `block` or `fibre`, the commands will be run against + the matching list of discovered devices + :type devices: ``str`` or a ``list`` of device paths + + :param timeout: Timeout in seconds to allow each `cmd` to run + :type timeout: ``int`` + + :param sizelimit: Maximum amount of output to collect, in MB + :type sizelimit: ``int`` + + :param chroot: Should sos chroot the command(s) being run + :type chroot: ``bool`` + + :param runat: Set the filesystem location to execute the command from + :type runat: ``str`` + + :param env: Set environment variables for the command(s) being run + :type env: ``dict`` + + :param binary: Is the output collected going to be binary data + :type binary: ``bool`` + + :param prepend_path: The leading path for block device names + :type prepend_path: ``str`` or ``None`` + + :param whitelist: Limit the devices the `cmds` will be run against to + devices matching these item(s) + :type whitelist: ``list`` of ``str`` + + :param blacklist: Do not run `cmds` against devices matching these + item(s) + :type blacklist: ``list`` of ``str`` """ _dev_tags = [] if isinstance(tags, str): @@ -1312,7 +1590,63 @@ class Plugin(object): chroot=True, runat=None, env=None, binary=False, sizelimit=None, pred=None, subdir=None, changes=False, foreground=False, tags=[]): - """Run a program or a list of programs and collect the output""" + """Run a program or a list of programs and collect the output + + Output will be limited to `sizelimit`, collecting the last X amount + of command output matching `sizelimit`. Unless `suggest_filename` is + set, the file that the output is saved to will match the command as + it was executed, and will be saved under `sos_commands/$plugin` + + :param cmds: The command(s) to execute + :type cmds: ``str`` or a ``list`` of strings + + :param suggest_filename: Override the name of the file output is saved + to within the archive + :type suggest_filename: ``str`` + + :param root_symlink: If set, create a symlink with this name in the + archive root + :type root_symlink: ``str`` + + :param timeout: Timeout in seconds to allow each `cmd` to run for + :type timeout: ``int`` + + :param stderr: Should stderr output be collected + :type stderr: ``bool`` + + :param chroot: Should sos chroot the `cmds` being run + :type chroot: ``bool`` + + :param runat: Run the `cmds` from this location in the filesystem + :type runat: ``str`` + + :param env: Set environment variables for the `cmds` being run + :type env: ``dict`` + + :param binary: Is the command expected to produce binary output + :type binary: ``bool`` + + :param sizelimit: Maximum amount of output in MB to save + :type sizelimit: ``int`` + + :param pred: A predicate to gate if `cmds` should be collected or not + :type pred: ``SoSPredicate`` + + :param subdir: Save output to this subdirectory, within the plugin's + directory under sos_commands + :type subdir: ``str`` + + :param changes: Do `cmds` have the potential to change system state + :type changes: ``int`` + + :param foreground: Should the `cmds` be run in the foreground, with an + attached TTY + :type foreground: ``bool`` + + :param tags: A tag or set of tags to add to the metadata entries for + the `cmds` being run + :type tags: ``str`` or a ``list`` of strings + """ if isinstance(cmds, str): cmds = [cmds] if len(cmds) > 1 and (suggest_filename or root_symlink): @@ -1360,8 +1694,18 @@ class Plugin(object): return [] def get_cmd_output_path(self, name=None, make=True): - """Return a path into which this module should store collected - command output + """Get the path where this plugin will save command output + + :param name: Optionally specify a filename to use as part of the + command output path + :type name: ``str`` or ``None`` + + :param make: Attempt to create the command output path + :type make: ``bool`` + + :returns: The path where the plugin will write command output data + within the archive + :rtype: ``str`` """ cmd_output_path = os.path.join(self.archive.get_tmp_dir(), 'sos_commands', self.name()) @@ -1373,9 +1717,16 @@ class Plugin(object): return cmd_output_path def file_grep(self, regexp, *fnames): - """Returns lines matched in fnames, where fnames can either be - pathnames to files to grep through or open file objects to grep through - line by line. + """Grep through file(s) for a specific string or regex + + :param regexp: The string or regex to search for + :type regexp: ``str`` + + :param fnames: Paths to grep through + :type fnames: ``str``, ``list`` of string, or open file objects + + :returns: Lines matching `regexp` + :rtype: ``str`` """ return grep(regexp, *fnames) @@ -1414,9 +1765,12 @@ class Plugin(object): def add_env_var(self, name): """Add an environment variable to the list of to-be-collected env vars. - Accepts either a single variable name or a list of names. Any value - given will be added as provided to the method, as well as an upper- - and lower- cased version. + Collected environment variables will be saved to an `environment` file + in the archive root, and any variable specified for collection will be + collected in lowercase, uppercase, and the form provided + + :param name: The name of the environment variable to collect + :type name: ``str`` """ if not isinstance(name, list): name = [name] @@ -1427,7 +1781,18 @@ class Plugin(object): self._env_vars.update([env, env.upper(), env.lower()]) def add_string_as_file(self, content, filename, pred=None): - """Add a string to the archive as a file named `filename`""" + """Add a string to the archive as a file + + :param content: The string to write to the archive + :type content: ``str`` + + :param filename: The name of the file to write `content` to + :type filename: ``str`` + + :param pred: A predicate to gate if the string should be added to the + archive or not + :type pred: ``SoSPredicate`` + """ # Generate summary string for logging summary = content.splitlines()[0] if content else '' @@ -1574,7 +1939,53 @@ class Plugin(object): binary=False, sizelimit=None, pred=None, subdir=None, tags=[]): """Execute a command and save the output to a file for inclusion in the - report. + report, then return the results for further use by the plugin + + :param cmd: The command to run + :type cmd: ``str`` + + :param suggest_filename: Filename to use when writing to the + archive + :param suggest_filename: ``str`` + + :param root_symlink: Create a symlink in the archive root + :type root_symlink: ``bool`` + + :param timeout: Time in seconds to allow a cmd to run + :type timeout: ``int`` + + :param stderr: Write stderr to stdout? + :type stderr: ``bool`` + + :param chroot: Perform chroot before running cmd? + :type chroot: ``bool`` + + :param runat: Run the command from this location, + overriding chroot + :type runat: ``str`` + + :param env: Environment vars to set for the cmd + :type env: ``dict`` + + :param binary: Is the output in binary? + :type binary: ``bool`` + + :param sizelimit: Maximum size in MB of output to save + :type sizelimit: ``int`` + + :param subdir: Subdir in plugin directory to save to + :type subdir: ``str`` + + :param changes: Does this cmd potentially make a change + on the system? + :type changes: ``bool`` + + :param tags: Add tags in the archive manifest + :type tags: ``str`` or a ``list`` of strings + + :returns: `cmd` exit status, output, and the filepath within the + archive output was saved to + :rtype: ``dict`` """ if not self.test_predicate(cmd=True, pred=pred): self._log_info("skipped cmd output '%s' due to predicate (%s)" % @@ -1600,6 +2011,41 @@ class Plugin(object): Use this method in a plugin's setup() if command output is needed to build subsequent commands added to a report via add_cmd_output(). + + :param cmd: The command to run + :type cmd: ``str`` + + :param timeout: Time in seconds to allow a cmd to run + :type timeout: ``int`` + + :param stderr: Write stderr to stdout? + :type stderr: ``bool`` + + :param chroot: Perform chroot before running cmd? + :type chroot: ``bool`` + + :param runat: Run the command from this location, + overriding chroot + :type runat: ``str`` + + :param env: Environment vars to set for the cmd + :type env: ``dict`` + + :param binary: Is the output in binary? + :type binary: ``bool`` + + :param pred: A predicate to gate execution of the `cmd` + :type pred: ``SoSPredicate`` + + :param foreground: Run the `cmd` in the foreground with a TTY + :type foreground: ``bool`` + + :param container: Execute this command in a container with + this name + :type container: ``str`` + + :returns: Command exit status and output + :rtype: ``dict`` """ _default = {'status': None, 'output': ''} if not self.test_predicate(cmd=True, pred=pred): @@ -1641,6 +2087,12 @@ class Plugin(object): def container_exists(self, name): """If a container runtime is present, check to see if a container with a given name is currently running + + :param name: The name of the container to check presence of + :type name: ``str`` + + :returns: ``True`` if `name` exists, else ``False`` + :rtype: ``bool`` """ _runtime = self._get_container_runtime() if _runtime is not None: @@ -1649,17 +2101,35 @@ class Plugin(object): return False def get_container_by_name(self, name): + """Get the container ID for a specific container + + :param name: The name of the container + :type name: ``str`` + + :returns: The ID of the container if it exists + :rtype: ``str`` or ``None`` + """ _runtime = self._get_container_runtime() if _runtime is not None: return _runtime.get_container_by_name(name) return None def get_containers(self, runtime=None, get_all=False): - """Return a list of all container IDs from the Policy's - ContainerRuntime. + """Return a list of all container IDs from the ``Policy`` + ``ContainerRuntime`` - If `runtime` is not provided, use the Policy default. If the specified - `runtime is not loaded, return empty. + If `runtime` is not provided, use the ``Policy`` default + + :param runtime: The container runtime to use, if not the default + runtime detected and loaded by the ``Policy`` + :type runtime: ``str`` + + :param get_all: Return all containers known to the `runtime`, even + those that have terminated + :type get_all: ``bool`` + + :returns: All container IDs found by the ``ContainerRuntime`` + :rtype: ``list`` """ _runtime = self._get_container_runtime(runtime=runtime) if _runtime is not None: @@ -1674,7 +2144,14 @@ class Plugin(object): ContainerRuntime If `runtime` is not provided, use the Policy default. If the specified - `runtime is not loaded, return empty. + `runtime` is not loaded, return empty. + + :param runtime: The container runtime to use, if not using the + default runtime detected by the ``Policy`` + :type runtime: ``str`` + + :returns: A list of container images known to the `runtime` + :rtype: ``list`` """ _runtime = self._get_container_runtime(runtime=runtime) if _runtime is not None: @@ -1686,7 +2163,14 @@ class Plugin(object): ContainerRuntime If `runtime` is not provided, use the Policy default. If the specified - `runtime is not loaded, return empty. + `runtime` is not loaded, return empty. + + :param runtime: The container runtime to use, if not using the + default runtime detected by the ``Policy`` + :type runtime: ``str`` + + :returns: A list of container volumes known to the `runtime` + :rtype: ``list`` """ _runtime = self._get_container_runtime(runtime=runtime) if _runtime is not None: @@ -1694,44 +2178,78 @@ class Plugin(object): return [] def get_container_logs(self, container, **kwargs): - """Helper to get the `logs` output for a given container + """Helper to get the ``logs`` output for a given container Supports passthru of add_cmd_output() options + + :param container: The name of the container to retrieve logs from + :type container: ``str`` + + :param kwargs: Any kwargs supported by ``add_cmd_output()`` are + supported here """ _runtime = self._get_container_runtime() if _runtime is not None: self.add_cmd_output(_runtime.get_logs_command(container), **kwargs) def fmt_container_cmd(self, container, cmd): + """Format a command to be executed by the loaded ``ContainerRuntime`` + in a specified container + + :param container: The name of the container to execute the `cmd` in + :type container: ``str`` + + :param cmd: The command to run within the container + :type cmd: ``str`` + + :returns: The command to execute so that the specified `cmd` will run + within the `container` and not on the host + :rtype: ``str`` + """ if self.container_exists(container): _runtime = self._get_container_runtime() return _runtime.fmt_container_cmd(container, cmd) return cmd def is_module_loaded(self, module_name): - """Return whether specified module as module_name is loaded or not""" + """Determine whether specified module is loaded or not + + :param module_name: Name of kernel module to check for presence + :type module_name: ``str`` + + :returns: ``True`` if the module is loaded, else ``False`` + :rtype: ``bool`` + """ return module_name in self.policy.kernel_mods # For adding output def add_alert(self, alertstring): """Add an alert to the collection of alerts for this plugin. These will be displayed in the report + + :param alertstring: The text to add as an alert + :type alertstring: ``str`` """ self.alerts.append(alertstring) def add_custom_text(self, text): """Append text to the custom text that is included in the report. This is freeform and can include html. + + :param text: The text to include in the report + :type text: ``str`` """ self.custom_text += text def add_service_status(self, services, **kwargs): - """Collect service status information based on the InitSystem used. + """Collect service status information based on the ``InitSystem`` used + + :param services: Service name(s) to collect statuses for + :type services: ``str`` or a ``list`` of strings - :param services: A string, or list of strings, specifying the services - to collect - :param kwargs Optional arguments to pass to _add_cmd_output + :param kwargs: Optional arguments to pass to _add_cmd_output (timeout, predicate, suggest_filename,..) + """ if isinstance(services, str): services = [services] @@ -1752,35 +2270,41 @@ class Plugin(object): sizelimit=None, pred=None, tags=[]): """Collect journald logs from one of more units. - :param units: A string, or list of strings specifying the - systemd units for which journal entries will be - collected. + :param units: Which journald units to collect + :type units: ``str`` or a ``list`` of strings - :param boot: A string selecting a boot index using the - journalctl syntax. The special values 'this' and - 'last' are also accepted. + :param boot: A boot index using the journalctl syntax. The special + values 'this' and 'last' are also accepted. + :type boot: ``str`` - :param since: A string representation of the start time for - journal messages. + :param since: Start time for journal messages + :type since: ``str`` - :param until: A string representation of the end time for - journal messages. + :param until: End time forjournal messages + :type until: ``str`` - :param lines: The maximum number of lines to be collected. + :param lines: The maximum number of lines to be collected + :type lines: ``int`` - :param allfields: A bool. Include all journal fields - regardless of size or non-printable - characters. + :param allfields: Include all journal fields regardless of size or + non-printable characters + :type allfields: ``bool`` - :param output: A journalctl output control string, for - example "verbose". + :param output: Journalctl output control string, for example "verbose" + :type output: ``str`` + + :param timeout: An optional timeout in seconds + :type timeout: ``int`` + + :param identifier: An optional message identifier + :type identifier: ``str`` + + :param catalog: Augment lines with descriptions from the system catalog + :type catalog: ``bool`` - :param timeout: An optional timeout in seconds. - :param identifier: An optional message identifier. - :param catalog: Bool. If True, augment lines with descriptions - from the system catalog. :param sizelimit: Limit to the size of output returned in MB. Defaults to the value of --log-size. + :type sizelimit: ``int`` """ journal_cmd = "journalctl --no-pager " unit_opt = " --unit %s" @@ -1922,7 +2446,7 @@ class Plugin(object): self._log_debug("collected plugin '%s' in %s" % fields) def get_description(self): - """ This function will return the description for the plugin""" + """This function will return the description for the plugin""" try: return self.short_desc except Exception: @@ -1944,6 +2468,10 @@ class Plugin(object): For plugins with more complex enablement checks this method may be overridden. + + :returns: ``True`` if the plugin should be run for this system, else + ``False`` + :rtype: ``bool`` """ # some files or packages have been specified for this package if any([self.files, self.packages, self.commands, self.kernel_mods, @@ -2006,6 +2534,10 @@ class Plugin(object): """Checks whether or not the system is running on an architecture that the plugin allows. If not architecture is set, assume plugin can run on all arches. + + :returns: ``True`` if the host's architecture allows the plugin to + run, else ``False`` + :rtype: ``bool`` """ if self.architectures is None: return True @@ -2055,7 +2587,13 @@ class Plugin(object): def check_process_by_name(self, process): """Checks if a named process is found in /proc/[0-9]*/cmdline. - Returns either True or False.""" + + :param process: The name of the process + :type process: ``str`` + + :returns: ``True`` if the process exists, else ``False`` + :rtype: ``bool`` + """ status = False cmd_line_glob = "/proc/[0-9]*/cmdline" try: @@ -2070,8 +2608,14 @@ class Plugin(object): return status def get_process_pids(self, process): - """Returns PIDs of all processes with process name. - If the process doesn't exist, returns an empty list""" + """Get a list of all PIDs that match a specified name + + :param process: The name of the process the get PIDs for + :type process: ``str`` + + :returns: A list of PIDs + :rtype: ``list`` + """ pids = [] cmd_line_glob = "/proc/[0-9]*/cmdline" cmd_line_paths = glob.glob(cmd_line_glob) |