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