chook 1.0.1.b2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/CHANGES.md +21 -0
- data/README.md +243 -36
- data/bin/chook-server +29 -1
- data/data/chook.conf.example +104 -0
- data/data/sample_handlers/RestAPIOperation.rb +12 -8
- data/data/sample_handlers/SmartGroupComputerMembershipChange.rb +3 -6
- data/data/sample_jsons/SmartGroupComputerMembershipChange.json +3 -1
- data/data/sample_jsons/SmartGroupMobileDeviceMembershipChange.json +3 -1
- data/lib/chook/configuration.rb +20 -8
- data/lib/chook/event/handled_event.rb +15 -12
- data/lib/chook/event/handled_event/handlers.rb +136 -83
- data/lib/chook/event/handled_event_logger.rb +86 -0
- data/lib/chook/event_handling.rb +1 -0
- data/lib/chook/foundation.rb +2 -0
- data/lib/chook/procs.rb +17 -1
- data/lib/chook/server.rb +71 -74
- data/lib/chook/server/log.rb +215 -0
- data/lib/chook/server/public/css/chook.css +125 -0
- data/lib/chook/server/public/imgs/ChookLogoAlMcWhiggin.png +0 -0
- data/lib/chook/server/public/js/chook.js +127 -0
- data/lib/chook/server/public/js/logstream.js +101 -0
- data/lib/chook/server/routes.rb +45 -0
- data/lib/chook/server/routes/handle_webhook_event.rb +22 -3
- data/lib/chook/server/routes/handlers.rb +52 -0
- data/lib/chook/server/routes/home.rb +34 -1
- data/lib/chook/server/routes/log.rb +106 -0
- data/lib/chook/server/views/admin.haml +11 -0
- data/lib/chook/server/views/bak.haml +48 -0
- data/lib/chook/server/views/config.haml +15 -0
- data/lib/chook/server/views/handlers.haml +48 -0
- data/lib/chook/server/views/layout.haml +39 -0
- data/lib/chook/server/views/logstream.haml +32 -0
- data/lib/chook/server/views/sketch_admin +44 -0
- data/lib/chook/subject.rb +1 -2
- data/lib/chook/subject/smart_group.rb +6 -0
- data/lib/chook/version.rb +1 -1
- metadata +73 -18
@@ -23,23 +23,27 @@
|
|
23
23
|
###
|
24
24
|
###
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
whook = event.webhook
|
26
|
+
# this module is just a namespace so we don't conflict with other handlers
|
27
|
+
module APIOpHandler
|
29
28
|
|
30
29
|
REPORT_ACTIONS = {
|
31
30
|
'PUT' => 'update',
|
32
31
|
'POST' => 'create',
|
33
32
|
'DELETE' => 'delete'
|
34
|
-
}.freeze
|
33
|
+
}.freeze
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
Chook.event_handler do |event|
|
38
|
+
event.logger.debug "This is a handler-level debug message for event #{event.object_id}"
|
35
39
|
|
36
|
-
action = REPORT_ACTIONS[
|
40
|
+
action = APIOpHandler::REPORT_ACTIONS[event.subject.restAPIOperationType]
|
37
41
|
|
38
42
|
return nil unless action
|
39
43
|
|
40
44
|
puts <<-ENDMSG
|
41
|
-
The JSS WebHook named '#{
|
42
|
-
It indicates that Casper user '#{
|
43
|
-
the JSS #{
|
45
|
+
The JSS WebHook named '#{event.webhook_name}' was just triggered.
|
46
|
+
It indicates that Casper user '#{event.subject.authorizedUsername}' just used the JSS API to #{action}
|
47
|
+
the JSS #{event.subject.objectTypeName} named '#{event.subject.objectName}' (id #{event.subject.objectID})
|
44
48
|
ENDMSG
|
45
49
|
end
|
@@ -23,11 +23,8 @@
|
|
23
23
|
###
|
24
24
|
###
|
25
25
|
|
26
|
-
HANDLER_NAME = File.basename __FILE__
|
27
|
-
CHANGELOG = '/tmp/smart-computer-group-changes.log'.freeze
|
28
|
-
|
29
26
|
Chook.event_handler do |event|
|
30
|
-
|
31
|
-
|
32
|
-
|
27
|
+
event.logger.debug "Computer Smart Group Changed: #{event.subject.name}"
|
28
|
+
event.logger.debug " Additions: #{event.subject.groupAddedDevicesIds.join ', '}"
|
29
|
+
event.logger.debug " Removals: #{event.subject.groupRemovedDevicesIds.join ', '}"
|
33
30
|
end
|
data/lib/chook/configuration.rb
CHANGED
@@ -35,16 +35,26 @@ module Chook
|
|
35
35
|
# The location of the default config file
|
36
36
|
DEFAULT_CONF_FILE = Pathname.new '/etc/chook.conf'
|
37
37
|
|
38
|
-
|
38
|
+
SAMPLE_CONF_FILE = Pathname.new(__FILE__).parent.parent.parent + 'data/chook.conf.example'
|
39
|
+
|
40
|
+
# The attribute keys we maintain, and how they should be converted to
|
41
|
+
# the value used by chook internally.
|
42
|
+
#
|
43
|
+
# For descriptions of the keys, see data/chook.conf.example
|
44
|
+
#
|
39
45
|
CONF_KEYS = {
|
40
|
-
|
41
|
-
|
42
|
-
handler_dir:
|
46
|
+
port: :to_i,
|
47
|
+
concurrency: Chook::Procs::STRING_TO_BOOLEAN,
|
48
|
+
handler_dir: Chook::Procs::STRING_TO_PATHNAME,
|
43
49
|
use_ssl: Chook::Procs::STRING_TO_BOOLEAN,
|
44
|
-
ssl_private_key_path: Chook::Procs::STRING_TO_PATHNAME,
|
45
|
-
ssl_private_key_pw_path: nil,
|
46
50
|
ssl_cert_path: Chook::Procs::STRING_TO_PATHNAME,
|
47
|
-
|
51
|
+
ssl_private_key_path: Chook::Procs::STRING_TO_PATHNAME,
|
52
|
+
log_file: Chook::Procs::STRING_TO_PATHNAME,
|
53
|
+
log_level: Chook::Procs::STRING_TO_LOG_LEVEL,
|
54
|
+
log_max_megs: :to_i,
|
55
|
+
logs_to_keep: :to_i,
|
56
|
+
webhooks_user: nil,
|
57
|
+
webhooks_user_pw: nil
|
48
58
|
}.freeze
|
49
59
|
|
50
60
|
# Class Variables
|
@@ -193,6 +203,8 @@ module Chook
|
|
193
203
|
end # class Configuration
|
194
204
|
|
195
205
|
# The single instance of Configuration
|
196
|
-
|
206
|
+
def self.config
|
207
|
+
Chook::Configuration.instance
|
208
|
+
end
|
197
209
|
|
198
210
|
end # module
|
@@ -25,7 +25,6 @@
|
|
25
25
|
|
26
26
|
require 'chook/event/handled_event/handlers'
|
27
27
|
|
28
|
-
#
|
29
28
|
module Chook
|
30
29
|
|
31
30
|
# Load sample JSON files, one per event type
|
@@ -62,9 +61,6 @@ module Chook
|
|
62
61
|
|
63
62
|
#### Class Methods
|
64
63
|
|
65
|
-
# Given some raw_json from the jss, create and return the correct
|
66
|
-
# HandledEvent subclass
|
67
|
-
|
68
64
|
# For each event type in Chook::Event::EVENTS
|
69
65
|
# generate a class for it, set its SUBJECT_CLASS constant
|
70
66
|
# and add it to the HandledEvents module.
|
@@ -99,6 +95,7 @@ module Chook
|
|
99
95
|
# @return [JSSWebHooks::Event subclass] the Event subclass matching the event
|
100
96
|
#
|
101
97
|
def self.parse_event(raw_event_json)
|
98
|
+
return nil if raw_event_json.to_s.empty?
|
102
99
|
event_json = JSON.parse(raw_event_json, symbolize_names: true)
|
103
100
|
event_name = event_json[:webhook][:webhookEvent]
|
104
101
|
Chook::HandledEvents.const_get(event_name).new raw_event_json
|
@@ -122,12 +119,15 @@ module Chook
|
|
122
119
|
end # init
|
123
120
|
|
124
121
|
def handle
|
125
|
-
|
122
|
+
handler_key = self.class.const_get(Chook::Event::EVENT_NAME_CONST)
|
123
|
+
handlers = Handlers.handlers[handler_key]
|
124
|
+
return 'No handlers loaded' unless handlers.is_a? Array
|
125
|
+
|
126
126
|
handlers.each do |handler|
|
127
127
|
case handler
|
128
128
|
when Pathname
|
129
129
|
pipe_to_executable handler
|
130
|
-
when
|
130
|
+
when Object
|
131
131
|
handle_with_proc handler
|
132
132
|
end # case
|
133
133
|
end # @handlers.each do |handler|
|
@@ -135,18 +135,21 @@ module Chook
|
|
135
135
|
# the handle method should return a string,
|
136
136
|
# which is the body of the HTTP result for
|
137
137
|
# POSTing the event
|
138
|
-
"Processed by #{handlers.count} handlers
|
138
|
+
"Processed by #{handlers.count} handlers"
|
139
139
|
end # def handle
|
140
140
|
|
141
|
-
# TODO: Add something here that cleans up old threads and forks
|
142
141
|
def pipe_to_executable(handler)
|
143
|
-
|
144
|
-
|
145
|
-
end
|
142
|
+
logger.debug "Sending JSON to stdin of '#{handler}'"
|
143
|
+
IO.popen([handler.to_s], 'w') { |h| h.puts @raw_json }
|
146
144
|
end
|
147
145
|
|
148
146
|
def handle_with_proc(handler)
|
149
|
-
|
147
|
+
logger.debug "Running Handler defined in #{handler.handler_file}"
|
148
|
+
handler.handle self
|
149
|
+
end
|
150
|
+
|
151
|
+
def logger
|
152
|
+
@logger ||= Chook::HandledEventLogger.new self
|
150
153
|
end
|
151
154
|
|
152
155
|
end # class HandledEvent
|
@@ -1,36 +1,45 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
1
|
+
# Copyright 2017 Pixar
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "Apache License")
|
4
|
+
# with the following modification; you may not use this file except in
|
5
|
+
# compliance with the Apache License and the following modification to it:
|
6
|
+
# Section 6. Trademarks. is deleted and replaced with:
|
7
|
+
#
|
8
|
+
# 6. Trademarks. This License does not grant permission to use the trade
|
9
|
+
# names, trademarks, service marks, or product names of the Licensor
|
10
|
+
# and its affiliates, except as required to comply with Section 4(c) of
|
11
|
+
# the License and to reproduce the content of the NOTICE file.
|
12
|
+
#
|
13
|
+
# You may obtain a copy of the Apache License at
|
14
|
+
#
|
15
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
16
|
+
#
|
17
|
+
# Unless required by applicable law or agreed to in writing, software
|
18
|
+
# distributed under the Apache License with the above modification is
|
19
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
20
|
+
# KIND, either express or implied. See the Apache License for the specific
|
21
|
+
# language governing permissions and limitations under the Apache License.
|
22
|
+
#
|
23
|
+
#
|
24
|
+
|
25
25
|
module Chook
|
26
26
|
|
27
|
-
# This method is used by the Ruby event-handler files.
|
27
|
+
# This method is used by the Ruby 'internal' event-handler files.
|
28
|
+
#
|
29
|
+
# those handlers are defined by passing a block to this method, like so:
|
30
|
+
#
|
31
|
+
# Chook.event_handler do |event|
|
32
|
+
# # so something with the event
|
33
|
+
# end
|
28
34
|
#
|
29
|
-
# Loading them
|
35
|
+
# Loading them will call this method and pass in a block
|
30
36
|
# with one parameter: a Chook::HandledEvent subclass instance.
|
31
37
|
#
|
32
|
-
# The block is then converted to a
|
33
|
-
#
|
38
|
+
# The block is then converted to a #handle method in an anonymous object.
|
39
|
+
# The object is stored for use by the event identified by the filename.
|
40
|
+
#
|
41
|
+
# By storing it as a method in an object, the handlers themselves
|
42
|
+
# can use #break or #return to exit (or even #next)
|
34
43
|
#
|
35
44
|
# NOTE: the files should be read with 'load' not 'require', so that they can
|
36
45
|
# be re-loaded as needed
|
@@ -44,7 +53,13 @@ module Chook
|
|
44
53
|
# @return [Proc] the block converted to a Proc
|
45
54
|
#
|
46
55
|
def self.event_handler(&block)
|
47
|
-
|
56
|
+
obj = Object.new
|
57
|
+
obj.define_singleton_method(:handle, &block)
|
58
|
+
# Loading the file created the object by calling this method
|
59
|
+
# but to access it after loading the file, we need to
|
60
|
+
# store it in here:
|
61
|
+
HandledEvent::Handlers.loaded_handler = obj
|
62
|
+
Chook.logger.debug "Code block for 'Chook.event_handler' loaded into \#handle method of runner-object #{obj.object_id}"
|
48
63
|
end
|
49
64
|
|
50
65
|
# the server class
|
@@ -53,53 +68,42 @@ module Chook
|
|
53
68
|
# The Handlers namespace module
|
54
69
|
module Handlers
|
55
70
|
|
56
|
-
# Module Constants
|
57
|
-
############################
|
58
|
-
|
59
71
|
DEFAULT_HANDLER_DIR = '/Library/Application Support/Chook'.freeze
|
60
72
|
|
61
|
-
#
|
62
|
-
|
63
|
-
|
64
|
-
# This holds the most recently loaded Proc handler
|
65
|
-
# until it can be stored in the @handlers Hash
|
66
|
-
@loaded_handler = nil
|
73
|
+
# internal handler files must match this regex somewhere
|
74
|
+
INTERNAL_HANDLER_BLOCK_START_RE = /Chook.event_handler( ?\{| do) *\|/
|
67
75
|
|
68
|
-
#
|
76
|
+
# self loaded_handler=
|
69
77
|
#
|
70
|
-
# @return [
|
78
|
+
# @return [Obj,nil] the most recent Proc loaded from a handler file.
|
71
79
|
# destined for storage in @handlers
|
72
80
|
#
|
73
81
|
def self.loaded_handler
|
74
82
|
@loaded_handler
|
75
83
|
end
|
76
84
|
|
77
|
-
#
|
85
|
+
# A holding place for internal handlers as they are loaded
|
86
|
+
# before being added to the @handlers Hash
|
87
|
+
# see Chook.event_handler(&block)
|
78
88
|
#
|
79
|
-
# @param a_proc [
|
89
|
+
# @param a_proc [Object] An object instance with a #handle method
|
80
90
|
#
|
81
|
-
def self.loaded_handler=(
|
82
|
-
@loaded_handler =
|
91
|
+
def self.loaded_handler=(anon_obj)
|
92
|
+
@loaded_handler = anon_obj
|
83
93
|
end
|
84
94
|
|
85
|
-
#
|
86
|
-
#
|
87
|
-
#
|
88
|
-
#
|
89
|
-
|
90
|
-
|
91
|
-
#
|
95
|
+
# Getter for @handlers
|
96
|
+
#
|
97
|
+
# @return [Hash{String => Array}] a mapping of Event Names as they
|
98
|
+
# come from the JSS to an Array of handlers for the event.
|
99
|
+
# The handlers are either Pathnames to executable external handlers
|
100
|
+
# or Objcts with a #handle method, for internal handlers
|
101
|
+
# (The objects also have a #handler_file attribute that is the Pathname)
|
92
102
|
#
|
93
|
-
# @return [Hash{String => Array}] a mapping of Event Names as the come from
|
94
|
-
# the JSS to an Array of handlers for the event. The handlers are either
|
95
|
-
# Proc objects to call from within ruby, or Pathnames to executable files
|
96
|
-
# which will take raw JSON on stdin.
|
97
103
|
def self.handlers
|
98
104
|
@handlers
|
99
105
|
end
|
100
|
-
|
101
|
-
# Module Methods
|
102
|
-
############################
|
106
|
+
@handlers ||= {}
|
103
107
|
|
104
108
|
# Load all the event handlers from the handler_dir or an arbitrary dir.
|
105
109
|
#
|
@@ -112,23 +116,31 @@ module Chook
|
|
112
116
|
#
|
113
117
|
# @return [void]
|
114
118
|
#
|
115
|
-
def self.load_handlers(from_dir: Chook
|
119
|
+
def self.load_handlers(from_dir: Chook.config.handler_dir, reload: false)
|
120
|
+
# use default if needed
|
116
121
|
from_dir ||= DEFAULT_HANDLER_DIR
|
122
|
+
handler_dir = Pathname.new(from_dir)
|
123
|
+
load_type = 'Loading'
|
124
|
+
|
117
125
|
if reload
|
118
|
-
@handlers_loaded_from = nil
|
119
126
|
@handlers = {}
|
120
127
|
@loaded_handler = nil
|
128
|
+
load_type = 'Re-loading'
|
121
129
|
end
|
122
130
|
|
123
|
-
|
124
|
-
|
131
|
+
Chook.logger.info "#{load_type} handlers from directory: #{handler_dir}"
|
132
|
+
|
133
|
+
unless handler_dir.directory? && handler_dir.readable?
|
134
|
+
Chook.logger.error "Handler directory '#{from_dir}' not a readable directory. No handlers loaded. "
|
135
|
+
return
|
136
|
+
end
|
125
137
|
|
126
138
|
handler_dir.children.each do |handler_file|
|
127
139
|
load_handler(handler_file) if handler_file.file? && handler_file.readable?
|
128
140
|
end
|
129
141
|
|
130
|
-
@
|
131
|
-
@
|
142
|
+
Chook.logger.info "Loaded #{@handlers.values.flatten.size} handlers for #{@handlers.keys.size} event triggers"
|
143
|
+
@loaded_handler = nil
|
132
144
|
end # load handlers
|
133
145
|
|
134
146
|
# Load an event handler from a file.
|
@@ -158,33 +170,67 @@ module Chook
|
|
158
170
|
# @return [void]
|
159
171
|
#
|
160
172
|
def self.load_handler(from_file)
|
173
|
+
Chook.logger.debug "Starting load of handler file '#{from_file.basename}'"
|
161
174
|
handler_file = Pathname.new from_file
|
162
175
|
event_name = event_name_from_handler_filename(handler_file)
|
163
|
-
|
176
|
+
unless event_name
|
177
|
+
Chook.logger.debug "Ignoring file '#{from_file.basename}'"
|
178
|
+
return
|
179
|
+
end
|
164
180
|
|
165
181
|
# create an array for this event's handlers, if needed
|
166
182
|
@handlers[event_name] ||= []
|
167
183
|
|
168
|
-
if handler_file
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
184
|
+
return if load_external_handler(handler_file, event_name)
|
185
|
+
|
186
|
+
load_internal_handler(handler_file, event_name)
|
187
|
+
end # self.load_handler(handler_file)
|
188
|
+
|
189
|
+
# if the given file is executable, store it's path as a handler for the event
|
190
|
+
#
|
191
|
+
#
|
192
|
+
def self.load_external_handler(handler_file, event_name)
|
193
|
+
return false unless handler_file.executable?
|
194
|
+
|
195
|
+
Chook.logger.info "Loading external handler file '#{handler_file.basename}' for #{event_name} events"
|
196
|
+
|
197
|
+
# store the Pathname, we'll pipe JSON to it
|
198
|
+
@handlers[event_name] << handler_file
|
199
|
+
true
|
200
|
+
end
|
201
|
+
|
202
|
+
# if a given path is not executable, try to load it as an internal handler
|
203
|
+
#
|
204
|
+
#
|
205
|
+
def self.load_internal_handler(handler_file, event_name)
|
206
|
+
# load the file. If written correctly, it will
|
207
|
+
# put an anon. Object with a #handle method into @loaded_handler
|
208
|
+
Chook.logger.info "Loading internal handler file '#{handler_file.basename}' for #{event_name} events"
|
209
|
+
|
210
|
+
unless handler_file.read =~ INTERNAL_HANDLER_BLOCK_START_RE
|
211
|
+
Chook.logger.error "Internal handler file '#{handler_file.basename}' missing event_handler block"
|
174
212
|
return
|
175
213
|
end
|
176
214
|
|
177
|
-
#
|
178
|
-
#
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
215
|
+
# reset @loaded_handler - the `load` call will refill it
|
216
|
+
# see Chook.event_handler
|
217
|
+
@loaded_handler = nil
|
218
|
+
begin
|
219
|
+
load handler_file.to_s
|
220
|
+
raise '@loaded handler nil after loading file' unless @loaded_handler
|
221
|
+
rescue => e
|
222
|
+
Chook.logger.error "FAILED loading internal handler file '#{handler_file.basename}': #{e}"
|
223
|
+
return
|
186
224
|
end
|
187
|
-
|
225
|
+
|
226
|
+
# add a method to the object to get its filename
|
227
|
+
@loaded_handler.define_singleton_method(:handler_file) { handler_file.basename.to_s }
|
228
|
+
|
229
|
+
@handlers[event_name] << @loaded_handler
|
230
|
+
|
231
|
+
Chook.logger.debug "Loaded internal handler file '#{handler_file.basename}'"
|
232
|
+
@loaded_handler = nil
|
233
|
+
end
|
188
234
|
|
189
235
|
# Given a handler filename, return the event name it wants to handle
|
190
236
|
#
|
@@ -194,9 +240,16 @@ module Chook
|
|
194
240
|
# @return [String,nil] The matching event name or nil if no match
|
195
241
|
#
|
196
242
|
def self.event_name_from_handler_filename(filename)
|
243
|
+
filename = filename.basename
|
197
244
|
@event_names ||= Chook::Event::EVENTS.keys
|
198
|
-
desired_event_name = filename.
|
199
|
-
@event_names.select { |n| desired_event_name.casecmp(n).zero? }.first
|
245
|
+
desired_event_name = filename.to_s.split(/\.|-|_/).first
|
246
|
+
ename = @event_names.select { |n| desired_event_name.casecmp(n).zero? }.first
|
247
|
+
if ename
|
248
|
+
Chook.logger.debug "Found event name '#{ename}' at start of filename '#{filename}'"
|
249
|
+
else
|
250
|
+
Chook.logger.debug "No known event name at start of filename '#{filename}'"
|
251
|
+
end
|
252
|
+
ename
|
200
253
|
end
|
201
254
|
|
202
255
|
end # module Handler
|