Module: Chook::HandledEvent::Handlers

Defined in:
lib/chook/event/handled_event/handlers.rb

Overview

The Handlers namespace module

Constant Summary collapse

DO_NOT_LOAD_PREFIX =

Don’t load any handlers whose filenames start with this

'Ignore-'.freeze
DEFAULT_HANDLER_DIR =
'/Library/Application Support/Chook'.freeze
NAMED_HANDLER_SUBDIR =

Handlers that are only called by name using the route:

post '/handler/:handler_name'

are located in this subdirection of the handler directory

'NamedHandlers'.freeze
INTERNAL_HANDLER_BLOCK_START_RE =

internal handler files must match this regex somewhere

/Chook.event_handler( ?\{| do) *\|/

Class Method Summary collapse

Class Method Details

.all_handler_pathsArray<Pathname>

the Pathname objects for all loaded handlers

Returns:

  • (Array<Pathname>)


147
148
149
150
151
152
153
# File 'lib/chook/event/handled_event/handlers.rb', line 147

def self.all_handler_paths
  hndlrs = named_handlers.values
  hndlrs += handlers.values.flatten
  hndlrs.map do |hndlr|
    hndlr.is_a?(Pathname) ? hndlr : hndlr.handler_file
  end
end

.event_name_from_handler_filename(filename) ⇒ String?

Given a handler filename, return the event name it wants to handle

Parameters:

  • filename (Pathname)

    The filename from which to glean the event name.

Returns:

  • (String, nil)

    The matching event name or nil if no match



351
352
353
354
355
356
357
358
359
360
361
362
# File 'lib/chook/event/handled_event/handlers.rb', line 351

def self.event_name_from_handler_filename(filename)
  filename = filename.basename
  @event_names ||= Chook::Event::EVENTS.keys
  desired_event_name = filename.to_s.split(/\.|-|_/).first
  ename = @event_names.select { |n| desired_event_name.casecmp(n).zero? }.first
  if ename
    Chook.logger.debug "Found event name '#{ename}' at start of filename '#{filename}'"
  else
    Chook.logger.debug "No known event name at start of filename '#{filename}'"
  end
  ename
end

.handlersHash{String => Array}

Getter for @handlers

come from the JSS to an Array of handlers for the event. The handlers are either Pathnames to executable external handlers or Objcts with a #handle method, for internal handlers

(The objects also have a #handler_file attribute that is the Pathname)

Returns:

  • (Hash{String => Array})

    a mapping of Event Names as they



111
112
113
# File 'lib/chook/event/handled_event/handlers.rb', line 111

def self.handlers
  @handlers ||= {}
end

.load_external_handler(handler_file, event_name, named) ⇒ Boolean

if the given file is executable, store it’s path as a handler for the event

Returns:

  • (Boolean)

    did we load an external handler?



295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'lib/chook/event/handled_event/handlers.rb', line 295

def self.load_external_handler(handler_file, event_name, named)
  return false unless handler_file.executable?

  say_named = named ? 'named ' : ''
  Chook.logger.info "Loading #{say_named}external handler file '#{handler_file.basename}' for #{event_name} events"

  if named
    named_handlers[event_name][handler_file.basename.to_s] = handler_file
  else
    # store the Pathname, we'll pipe JSON to it
    handlers[event_name] << handler_file
  end

  true
end

.load_general_handler(handler_file) ⇒ void

This method returns an undefined value.

Load a general event handler from a file.

General Handler files must begin with the name of the event they handle, e.g. ComputerAdded, followed by: nothing, a dot, a dash, or and underscore. Case doesn’t matter. So all of these are OK: ComputerAdded computeradded.sh COMPUTERAdded_notify_team Computeradded-update-ldap There can be as many as desired for each event.

Parameters:

  • handler_file (Pathname)

    the file from which to load the handler



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/chook/event/handled_event/handlers.rb', line 240

def self.load_general_handler(handler_file)
  Chook.logger.debug "Starting load of general handler file '#{handler_file.basename}'"

  event_name = event_name_from_handler_filename(handler_file)
  unless event_name
    Chook.logger.debug "Ignoring general handler file '#{handler_file.basename}': Filename doesn't start with event name"
    return
  end

  # create an array for this event's handlers, if needed
  handlers[event_name] ||= []

  # external? if so, its executable and we only care about its pathname
  if handler_file.executable?
    Chook.logger.info "Loading external general handler file '#{handler_file.basename}' for #{event_name} events"
    handlers[event_name] << handler_file
    return
  end

  # Internal, we store an object with a .handle method
  Chook.logger.info "Loading internal general handler file '#{handler_file.basename}' for #{event_name} events"
  load_internal_handler handler_file
  handlers[event_name] << @loaded_handler if @loaded_handler

end

.load_handlers(from_dir: Chook.config.handler_dir, reload: false) ⇒ void

This method returns an undefined value.

Load all the event handlers from the handler_dir or an arbitrary dir.

Handler files must be either:

- An executable file, which will have the raw JSON from the JSS piped
  to it's stdin when executed

or

- A non-executable file of ruby code like this:
  Chook.event_handler do |event|
    # your code goes here.
  end

(see the Chook README for details about writing the ruby handlers)

Parameters:

  • from_dir (String, Pathname) (defaults to: Chook.config.handler_dir)

    directory from which to load the handlers. Defaults to CONFIG.handler_dir or DEFAULT_HANDLER_DIR if config is unset

  • reload (Boolean) (defaults to: false)

    should we reload handlers if they’ve already been loaded?



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/chook/event/handled_event/handlers.rb', line 178

def self.load_handlers(from_dir: Chook.config.handler_dir, reload: false)
  # use default if needed
  from_dir ||= DEFAULT_HANDLER_DIR
  handler_dir = Pathname.new(from_dir)
  named_handler_dir = handler_dir + NAMED_HANDLER_SUBDIR
  load_type = 'Loading'

  if reload
    @reloading = true
    @handlers = {}
    @named_handlers = {}
    @loaded_handler = nil
    load_type = 'Re-loading'
  end

  # General Handlers
  Chook.logger.info "#{load_type} general handlers from directory: #{handler_dir}"
  if handler_dir.directory? && handler_dir.readable?
    handler_dir.children.each do |handler_file|
      # ignore if marked to
      next if handler_file.basename.to_s.start_with? DO_NOT_LOAD_PREFIX

      load_general_handler(handler_file) if handler_file.file? && handler_file.readable?
    end
    Chook.logger.info handlers.empty? ? 'No general handlers found' : "Loaded #{handlers.values.flatten.size} general handlers for #{handlers.keys.size} event triggers"
  else
    Chook.logger.error "General handler directory '#{from_dir}' not a readable directory. No general handlers loaded. "
  end

  # Named Handlers
  Chook.logger.info "#{load_type} named handlers from directory: #{named_handler_dir}"
  if named_handler_dir.directory? && named_handler_dir.readable?
    named_handler_dir.children.each do |handler_file|
      # ignore if marked to
      next if handler_file.basename.to_s.start_with? DO_NOT_LOAD_PREFIX

      load_named_handler(handler_file) if handler_file.file? && handler_file.readable?
    end
    Chook.logger.info "Loaded #{named_handlers.size} named handlers"
  else
    Chook.logger.error "Named handler directory '#{named_handler_dir}' not a readable directory. No named handlers loaded. "
  end

  @reloading = false
end

.load_internal_handler(handler_file) ⇒ Object

if a given path is not executable, try to load it as an internal handler

Parameters:

  • handler_file (Pathname)

    the handler file

Returns:

  • (Object)

    and anonymous object that has a .handle method



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
# File 'lib/chook/event/handled_event/handlers.rb', line 317

def self.load_internal_handler(handler_file)
  # load the file. If written correctly, it will
  # put an anon. Object with a #handle method into @loaded_handler
  unless handler_file.read =~ INTERNAL_HANDLER_BLOCK_START_RE
    Chook.logger.error "Internal handler file '#{handler_file}' missing event_handler block"
    return nil
  end

  # reset @loaded_handler - the `load` call will refill it
  # see Chook.event_handler
  @loaded_handler = nil

  begin
    load handler_file.to_s
    raise '@loaded handler nil after loading file' unless @loaded_handler
  rescue => e
    Chook.logger.error "FAILED loading internal handler file '#{handler_file}': #{e}"
    return
  end

  # add a method to the object to get its Pathname
  @loaded_handler.define_singleton_method(:handler_file) { handler_file }

  # return it
  @loaded_handler
end

.load_named_handler(handler_file) ⇒ void

This method returns an undefined value.

Load a named event handler from a file.

Named Handler files can have any name, as they are called directly from a Jamf webhook via URL.

Parameters:

  • handler_file (Pathname)

    the file from which to load the handler



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/chook/event/handled_event/handlers.rb', line 275

def self.load_named_handler(handler_file)
  Chook.logger.debug "Starting load of named handler file '#{handler_file.basename}'"

  # external? if so, its executable and we only care about its pathname
  if handler_file.executable?
    Chook.logger.info "Loading external named handler file '#{handler_file.basename}'"
    named_handlers[handler_file.basename.to_s] = handler_file
    return
  end

  # Internal, we store an object with a .handle method
  Chook.logger.info "Loading internal named handler file '#{handler_file.basename}'"
  load_internal_handler handler_file
  named_handlers[handler_file.basename.to_s] = @loaded_handler if @loaded_handler
end

.loaded_handlerObj?

self loaded_handler=

destined for storage in @handlers

Returns:

  • (Obj, nil)

    the most recent Proc loaded from a handler file.



89
90
91
# File 'lib/chook/event/handled_event/handlers.rb', line 89

def self.loaded_handler
  @loaded_handler
end

.loaded_handler=(anon_obj) ⇒ Object

A holding place for internal handlers as they are loaded before being added to the @handlers Hash see Chook.event_handler(&block)

Parameters:

  • a_proc (Object)

    An object instance with a #handle method



99
100
101
# File 'lib/chook/event/handled_event/handlers.rb', line 99

def self.loaded_handler=(anon_obj)
  @loaded_handler = anon_obj
end

.named_handlersHash {String => Pathname, Proc}

getter for @named_handlers These handlers are called by name via the route “ post ‘/handler/:handler_name’”

They are not tied to any event type by their filenames its up to the writers of the handlers to make sure the webhook that calls them is sending the correct event type.

The data structure of @named_handlers is a Hash of Strings to Pathnames or Anon Objects:

handler_filename => Pathname or Obj,
handler_filename => Pathname or Obj,
handler_filename => Pathname or Obj

Returns:

  • (Hash {String => Pathname, Proc})


139
140
141
# File 'lib/chook/event/handled_event/handlers.rb', line 139

def self.named_handlers
  @named_handlers ||= {}
end

.reloading?Boolean

Handlers can check Chook::HandledEvent::Handlers.reloading? and do stuff if desired.

Returns:

  • (Boolean)


117
118
119
# File 'lib/chook/event/handled_event/handlers.rb', line 117

def self.reloading?
  @reloading
end