require "msgpack" module Immunio # Holds the status of a plugin. # # A plugin can have one of four statuses: # # - pending: initial state, waiting to be loaded # - loaded: successfully loaded # - failed: error while loading # - disabled: disabled in config, will never be loaded # # Each registered plugin is reported to the backend via the `EnvironmentReporter`. class Plugin attr_reader :status attr_accessor :version attr_accessor :hooks def initialize(name, hooks = []) @name = name @status = 'pending' @version = nil @hooks = hooks end def loaded!(version) @status = 'loaded' @version = version Immunio.logger.debug "Plugin #{@name} v#{version} loaded successfully" end def disabled! @status = 'disabled' Immunio.logger.debug "Plugin #{@name} is disabled" end def failed!(error) @status = 'failed' Immunio.logger.error "Plugin #{@name} failed to load: #{error}" end def inspect "<#{self.class} name=#{@name.inspect} status=#{@status.inspect} version=#{@version.inspect} hooks=#{@hooks.inspect}>" end def to_msgpack(packer) packer.write_map_header 3 # `name` is provided as the key in `registered` packer.write('status').write(@status) packer.write('version').write(@version) packer.write('hooks').write(@hooks) end def self.registered @registered ||= {} end # DSL to register a plugin, its status and version. # # A `feature` name can be passed to determine if the plugin should be enabled. If the # plugin is disabled, the block will not run. # # You MUST explicitly call `plugin.loaded!` in the block to indicate that the plugin was # loaded successfully, or else it will be kept as 'pending'. # # Eg.: # # Immunio::Plugin.load 'SomePluginName', feature: 'xss' do |plugin| # if defined? SomePlugin # # Your loading code here ... # plugin.loaded! SomePluginName::VERSION # end # end # def self.load(name, options = {}) if options.key? :feature enabled = Immunio.agent.plugin_enabled?(options[:feature]) else enabled = true end plugin = registered[name] = new(name, options.fetch(:hooks, [])) unless enabled # plugin is disabled plugin.disabled! return end Immunio.logger.debug "Loading plugin #{name} ..." begin yield plugin rescue StandardError, LoadError => e plugin.failed! e end end end end