module Chook::HandledEvent::Handlers

The Handlers namespace module

Constants

DEFAULT_HANDLER_DIR
DO_NOT_LOAD_PREFIX

Don't load any handlers whose filenames start with this

INTERNAL_HANDLER_BLOCK_START_RE

internal handler files must match this regex somewhere

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

Public Class Methods

all_handler_paths() click to toggle source

the Pathname objects for all loaded handlers

@return [Array<Pathname>]

# 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) click to toggle source

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

@param [Pathname] filename The filename from which to glean the

event name.

@return [String,nil] The matching event name or nil if no match

# 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
handlers() click to toggle source

Getter for @handlers

@return [Hash{String => Array}] a mapping of Event Names as they 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)
# File lib/chook/event/handled_event/handlers.rb, line 111
def self.handlers
  @handlers ||= {}
end
load_external_handler(handler_file, event_name, named) click to toggle source

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

@return [Boolean] did we load an external handler?

# 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) click to toggle source

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.

@param handler_file [Pathname] the file from which to load the handler

@return [void]

# 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) click to toggle source

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)

@param from_dir [String, Pathname] directory from which to load the

handlers. Defaults to CONFIG.handler_dir or DEFAULT_HANDLER_DIR if
config is unset

@param reload [Boolean] should we reload handlers if they've already

been loaded?

@return [void]

# 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) click to toggle source

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

@param handler_file the handler file

@return [Object] and anonymous object that has a .handle method

# 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) click to toggle source

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.

@param handler_file [Pathname] the file from which to load the handler

@return [void]

# 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_handler() click to toggle source

self loaded_handler=

@return [Obj,nil] the most recent Proc loaded from a handler file. destined for storage in @handlers

# File lib/chook/event/handled_event/handlers.rb, line 89
def self.loaded_handler
  @loaded_handler
end
loaded_handler=(anon_obj) click to toggle source

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

@param a_proc [Object] An object instance with a handle method

# File lib/chook/event/handled_event/handlers.rb, line 99
def self.loaded_handler=(anon_obj)
  @loaded_handler = anon_obj
end
named_handlers() click to toggle source

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

}

@return [Hash {String => Pathname, Proc}]

# File lib/chook/event/handled_event/handlers.rb, line 139
def self.named_handlers
  @named_handlers ||= {}
end
reloading?() click to toggle source

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

# File lib/chook/event/handled_event/handlers.rb, line 117
def self.reloading?
  @reloading
end