1+ #!/usr/bin/env python
2+ #
3+ # Copyright 2013 Splunk, Inc.
4+ #
5+ # Licensed under the Apache License, Version 2.0 (the "License"): you may
6+ # not use this file except in compliance with the License. You may obtain
7+ # a copy of the License at
8+ #
9+ # http://www.apache.org/licenses/LICENSE-2.0
10+ #
11+ # Unless required by applicable law or agreed to in writing, software
12+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+ # License for the specific language governing permissions and limitations
15+ # under the License.
16+
17+ import sys , urllib2 , json
18+
19+ from splunklib .modularinput import *
20+
21+ try :
22+ import xml .etree .cElementTree as ET
23+ except ImportError :
24+ import xml .etree .ElementTree as ET
25+
26+ class MyScript (Script ):
27+ """All modular inputs should inherit from the abstract base class Script
28+ from splunklib.modularinput.script.
29+ They must override the get_scheme and stream_events functions, and,
30+ if the scheme returned by get_scheme has Scheme.use_external_validation
31+ set to True, the validate_input function.
32+ """
33+ def get_scheme (self ):
34+ """When Splunk starts, it looks for all the modular inputs defined by
35+ its configuration, and tries to run them with the argument --scheme.
36+ Splunkd expects the modular inputs to print a description of the
37+ input in XML on stdout. The modular input framework takes care of all
38+ the details of formatting XML and printing it. The user need only
39+ override get_scheme and return a new Scheme object.
40+
41+ :return: scheme, a Scheme object
42+ """
43+ # Splunk will display "Github Repository Forks" to users for this input
44+ scheme = Scheme ("Github Repository Forks" )
45+
46+ scheme .description = "Streams events giving the number of forks of a GitHub repository."
47+ # If you set external validation to True, without overriding validate_input,
48+ # the script will accept anything as valid. Generally you only need external
49+ # validation if there are relationships you must maintain among the
50+ # parameters, such as requiring min to be less than max in this example,
51+ # or you need to check that some resource is reachable or valid.
52+ # Otherwise, Splunk lets you specify a validation string for each argument
53+ # and will run validation internally using that string.
54+ scheme .use_external_validation = True
55+ scheme .use_single_instance = True
56+
57+ owner_argument = Argument ("owner" )
58+ owner_argument .data_type = Argument .data_type_string
59+ owner_argument .description = "Github user or organization that created the repository."
60+ owner_argument .required_on_create = True
61+ # If you are not using external validation, you would add something like:
62+ #
63+ # scheme.validation = "owner==splunk"
64+ scheme .add_argument (owner_argument )
65+
66+ repo_name_argument = Argument ("repo_name" )
67+ repo_name_argument .data_type = Argument .data_type_string
68+ repo_name_argument .description = "Name of the Github repository."
69+ repo_name_argument .required_on_create = True
70+ scheme .add_argument (repo_name_argument )
71+
72+ return scheme
73+
74+ def validate_input (self , validation_definition ):
75+ """In this example we are using external validation to verify that the Github
76+ repository exists. If validate_input does not raise an Exception, the input
77+ is assumed to be valid. Otherwise it prints the exception as an error message
78+ when telling splunkd that the configuration is invalid.
79+
80+ When using external validation, after splunkd calls the modular input with
81+ --scheme to get a scheme, it calls it again with --validate-arguments for
82+ each instance of the modular input in its configuration files, feeding XML
83+ on stdin to the modular input to do validation. It is called the same way
84+ whenever a modular input's configuration is edited.
85+
86+ :param validation_definition: a ValidationDefinition object
87+ """
88+ # Get the values of the parameters, and construct a URL for the Github API
89+ owner = validation_definition .parameters ["owner" ]
90+ repo_name = validation_definition .parameters ["repo_name" ]
91+ repo_url = "https://api.github.com/repos/%s/%s" % (owner , repo_name )
92+
93+ # Read the response from the Github API, then parse the JSON data into an object
94+ response = urllib2 .urlopen (repo_url ).read ()
95+ jsondata = json .loads (response )
96+
97+ # If there is only 1 field in the jsondata object,some kind or error occurred
98+ # with the Github API.
99+ # Typically, this will happen with an invalid repository.
100+ if len (jsondata ) == 1 :
101+ raise ValueError ("The Github repository was not found." )
102+
103+ # If the API response seems normal, validate the fork count
104+ # If there's something wrong with getting fork_count, raise a ValueError
105+ try :
106+ fork_count = int (jsondata ["forks_count" ])
107+ except ValueError as ve :
108+ raise ValueError ("Invalid fork count: %s" , ve .message )
109+
110+ def stream_events (self , inputs , ew ):
111+ """This function handles all the action: splunk calls this modular input
112+ without arguments, streams XML describing the inputs to stdin, and waits
113+ for XML on stout describing events.
114+
115+ If you set use_single_instance to True on the scheme in get_scheme, it
116+ will pass all the instances of this input to a single instance of this
117+ script.
118+
119+ :param inputs: an InputDefinition object
120+ :param ew: an EventWriter object
121+ """
122+ # Go through each input for this modular input
123+ for input_name , input_item in inputs .inputs .iteritems ():
124+ # Get fields from the InputDefinition object
125+ owner = input_item ["owner" ]
126+ repo_name = input_item ["repo_name" ]
127+
128+ # Get the fork count from the Github API
129+ repo_url = "https://api.github.com/repos/%s/%s" % (owner , repo_name )
130+ response = urllib2 .urlopen (repo_url ).read ()
131+ jsondata = json .loads (response )
132+ fork_count = jsondata ["forks_count" ]
133+
134+ # Create an Event object, and set its fields
135+ event = Event ()
136+ event .stanza = input_name
137+ event .data = 'owner="%s" repository="%s" fork_count=%s' % \
138+ (owner .replace ('"' , '\\ "' ), repo_name .replace ('"' , '\\ "' ), fork_count )
139+
140+ # Tell the EventWriter to write this event
141+ ew .write_event (event )
142+
143+ if __name__ == "__main__" :
144+ sys .exit (MyScript ().run (sys .argv ))
0 commit comments