Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modular Input Decorators #300

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions splunklib/modularinput/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions splunklib/modularinput/argument.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
return arg
90 changes: 90 additions & 0 deletions splunklib/modularinput/decorators.py
Original file line number Diff line number Diff line change
@@ -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
44 changes: 34 additions & 10 deletions splunklib/modularinput/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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