From 49ecfc66cb885fe127df3d7db03970b48b4375d7 Mon Sep 17 00:00:00 2001 From: Micah Kemp Date: Mon, 6 Jan 2020 21:58:20 -0500 Subject: [PATCH] Initial decoration functionality. --- splunklib/modularinput/__init__.py | 1 + splunklib/modularinput/argument.py | 4 +- splunklib/modularinput/decorators.py | 90 ++++++++++++++++++++++++++++ splunklib/modularinput/script.py | 44 ++++++++++---- 4 files changed, 127 insertions(+), 12 deletions(-) create mode 100644 splunklib/modularinput/decorators.py diff --git a/splunklib/modularinput/__init__.py b/splunklib/modularinput/__init__.py index ace954a02..adbf9f183 100755 --- a/splunklib/modularinput/__init__.py +++ b/splunklib/modularinput/__init__.py @@ -4,6 +4,7 @@ from splunklib.modularinput import * """ from .argument import Argument +from .decorators import Configuration from .event import Event from .event_writer import EventWriter from .input_definition import InputDefinition diff --git a/splunklib/modularinput/argument.py b/splunklib/modularinput/argument.py index 04214d16d..b269f7be4 100755 --- a/splunklib/modularinput/argument.py +++ b/splunklib/modularinput/argument.py @@ -48,7 +48,7 @@ class Argument(object): data_type_number = "NUMBER" data_type_string = "STRING" - def __init__(self, name, description=None, validation=None, + def __init__(self, name=None, description=None, validation=None, data_type=data_type_string, required_on_edit=False, required_on_create=False, title=None): """ :param name: ``string``, identifier for this argument in Splunk. @@ -100,4 +100,4 @@ def add_to_document(self, parent): for name, value in subelements: ET.SubElement(arg, name).text = str(value).lower() - return arg \ No newline at end of file + return arg diff --git a/splunklib/modularinput/decorators.py b/splunklib/modularinput/decorators.py new file mode 100644 index 000000000..cef42fdd6 --- /dev/null +++ b/splunklib/modularinput/decorators.py @@ -0,0 +1,90 @@ +# coding=utf-8 +# +# Copyright © 2011-2015 Splunk, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"): you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from __future__ import absolute_import +from splunklib import six + +from .script import Script +from .scheme import Scheme +from .argument import Argument + +from inspect import getmembers + +class InputItems(object): + def __init__(self, scheme, inputs): + self._scheme = scheme + self._inputs = inputs + + def __iter__(self): + for input_name, input_config in self._inputs.items(): + input = InputItem(self._scheme, input_config) + yield [input_name, input] + +class InputItem(object): + def __init__(self, scheme, config): + for name, value in config.items(): + for argument in scheme.arguments: + if argument.name == name: + if argument.data_type == argument.data_type_boolean: + value = value.lower() not in ['n', 'no', 'f', 'false', '0'] + elif argument.data_type == argument.data_type_number: + # assume float, as it can later be cast to int without issue + value = float(value) + setattr(self, name, value) + +class Configuration(object): + """ Defines the configuration settings for a modular input. + """ + def __init__(self, o=None, **kwargs): + self._settings = kwargs + + def __call__(self, o): + o.name = o.__name__ + o._settings = self._settings + + # called by Script.get_scheme (unless overridden) + def decorated_get_scheme(fn_self): + scheme = Scheme(fn_self.name) + + for setting_name, setting_value in fn_self._settings.items(): + setattr(scheme, setting_name, setting_value) + + for argument in fn_self._arguments: + scheme.add_argument(argument) + + return scheme + + # called by Script.stream_events (unless overridden) + def decorated_stream_events(fn_self, inputs, ew): + input_items = InputItems(fn_self.get_scheme(), inputs.inputs) + fn_self.preflight(input_items, ew) + for input_name, input_config in input_items: + fn_self.process_input(input_name, input_config, ew) + fn_self.postflight(input_items, ew) + + setattr(o, 'decorated_get_scheme', decorated_get_scheme) + setattr(o, 'decorated_stream_events', decorated_stream_events) + + is_configuration_setting = lambda attribute: isinstance(attribute, Argument) + definitions = getmembers(o, is_configuration_setting) + + o._arguments = [] + for name, argument in definitions: + if not argument.name: + argument.name = name + o._arguments.append(argument) + + return o diff --git a/splunklib/modularinput/script.py b/splunklib/modularinput/script.py index a254dfa25..e77035c42 100755 --- a/splunklib/modularinput/script.py +++ b/splunklib/modularinput/script.py @@ -13,7 +13,6 @@ # under the License. from __future__ import absolute_import -from abc import ABCMeta, abstractmethod from splunklib.six.moves.urllib.parse import urlsplit import sys @@ -29,8 +28,8 @@ import xml.etree.ElementTree as ET -class Script(six.with_metaclass(ABCMeta, object)): - """An abstract base class for implementing modular inputs. +class Script(object): + """A base class for implementing modular inputs. Subclasses should override ``get_scheme``, ``stream_events``, and optionally ``validate_input`` if the modular input uses @@ -74,6 +73,12 @@ def run_script(self, args, event_writer, input_stream): event_writer.close() return 0 + elif str(args[1]).lower() == "--spec": + scheme = self.get_scheme() + print("[{0}]".format(self.name)) + for argument in scheme.arguments: + print("{0} = <{1}>".format(argument.name, argument.data_type.lower())) + return 0 elif str(args[1]).lower() == "--scheme": # Splunk has requested XML specifying the scheme for this # modular input Return it and exit. @@ -143,12 +148,19 @@ def service(self): return self._service - @abstractmethod + # decorating a Script adds a valid decorated_get_scheme function + def decorated_get_scheme(self): + raise NotImplementedError('Script.get_scheme(self)') + + # overriding get_scheme results in decorated_get_scheme not being called def get_scheme(self): """The scheme defines the parameters understood by this modular input. + You must override this method or use the Configuration decorator to use the auto-scheme. + :return: a ``Scheme`` object representing the parameters for this modular input. """ + return self.decorated_get_scheme() def validate_input(self, definition): """Handles external validation for modular input kinds. @@ -168,11 +180,23 @@ def validate_input(self, definition): """ pass - @abstractmethod + # if Script.decorated_stream_events is called it means stream_events wasn't overridden and the class wasn't decorated + def decorated_stream_events(self, inputs, ew): + raise NotImplementedError('Script.stream_events(self, inputs, ew)') + + # in Script we define stream_events to call decorated_stream_events. stream_events should either be overridden or the class should be decorated + # to create decorated_stream_events (which calls preflight, process_input, postflight) def stream_events(self, inputs, ew): - """The method called to stream events into Splunk. It should do all of its output via - EventWriter rather than assuming that there is a console attached. + self.decorated_stream_events(inputs, ew) - :param inputs: An ``InputDefinition`` object. - :param ew: An object with methods to write events and log messages to Splunk. - """ + # preflight is called before each input is called with process_input + def preflight(fn_self, inputs, ew): + pass + + # process_input is called once per input. it should be overridden when the class is decorated + def process_input(fn_self, input_name, input_item, ew): + raise Exception("Not Implemented: Script.process_input(self, input_name, input_item, ew)") + + # postflight is called after each input is called with process_input + def postflight(fn_self, inputs, ew): + pass