# # Author:: Claire McQuin () # Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef-config/path_helper" require_relative "log" require_relative "mash" require_relative "dsl" require "pathname" unless defined?(Pathname) module Ohai # Ohai plugin loader. Finds all the plugins specified in the # Ohai.config :plugin_path (supports a single or multiple path setting # here), evaluates them and returns plugin objects. class Loader attr_reader :logger def initialize(controller) @controller = controller @logger = controller.logger.with_child(subsystem: "loader") @v7_plugin_classes = [] end # Searches all plugin paths and returns an Array of file paths to plugins # # @param dir [Array, String] directory/directories to load plugins from # @return [Array] def plugin_files_by_dir(plugin_dir = Ohai.config[:plugin_path]) Array(plugin_dir).map do |path| if Dir.exist?(path) Ohai::Log.trace("Searching for Ohai plugins in #{path}") escaped = ChefConfig::PathHelper.escape_glob_dir(path) Dir[File.join(escaped, "**", "*.rb")] else Ohai::Log.debug("The plugin path #{path} does not exist. Skipping...") [] end end.flatten end # loads all plugin classes # # @return [Array] def load_all plugin_files_by_dir.each do |plugin_file| load_plugin_class(plugin_file) end collect_v7_plugins end # load additional plugins classes from a given directory # @param from [String] path to a directory with additional plugins to load def load_additional(from) from = [ Ohai.config[:plugin_path], from].flatten plugin_files_by_dir(from).collect do |plugin_file| logger.trace "Loading additional plugin: #{plugin_file}" load_v7_plugin(load_plugin_class(plugin_file)) end end # Load a specified file as an ohai plugin and creates an instance of it. # Not used by ohai itself, but is used in the specs to load plugins for testing # # @private # @param plugin_path [String] def load_plugin(plugin_path) plugin_class = load_plugin_class(plugin_path) return nil unless plugin_class.is_a?(Class) if plugin_class < Ohai::DSL::Plugin::VersionVII load_v7_plugin(plugin_class) else raise Exceptions::IllegalPluginDefinition, "cannot create plugin of type #{plugin_class}" end end # load an ohai plugin object class from file # @param plugin_path String the path to the ohai plugin # # @return [Object] class object for the ohai plugin defined in the file def load_plugin_class(plugin_path) # Read the contents of the plugin to understand if it's a V6 or V7 plugin. contents = "" begin logger.trace("Loading plugin at #{plugin_path}") contents << IO.read(plugin_path) rescue IOError, Errno::ENOENT logger.warn("Unable to open or read plugin at #{plugin_path}") return nil end # We assume that a plugin is a V7 plugin if it contains Ohai.plugin in its contents. if contents.include?("Ohai.plugin") load_v7_plugin_class(contents, plugin_path) else raise Exceptions::IllegalPluginDefinition, "[DEPRECATION] Plugin at #{plugin_path}"\ " is a version 6 plugin. Version 6 plugins are no longer supported by Ohai. This"\ " plugin will need to be updated to the v7 Ohai plugin format. See"\ " https://docs.chef.io/ohai_custom.html for v7 syntax." end end private def collect_provides(plugin) plugin_provides = plugin.class.provides_attrs @controller.provides_map.set_providers_for(plugin, plugin_provides) end def collect_v7_plugins @v7_plugin_classes.each do |plugin_class| load_v7_plugin(plugin_class) end end # load an Ohai v7 plugin class from a string of the object # @param contents [String] text of the plugin object # @param plugin_path [String] the path to the plugin file where the contents came from # # @return [Ohai::DSL::Plugin::VersionVII] Ohai plugin object def load_v7_plugin_class(contents, plugin_path) plugin_class = eval(contents, TOPLEVEL_BINDING, plugin_path) # rubocop: disable Security/Eval unless plugin_class.is_a?(Class) && plugin_class < Ohai::DSL::Plugin raise Ohai::Exceptions::IllegalPluginDefinition, "Plugin file cannot contain any statements after the plugin definition" end plugin_class.sources << plugin_path @v7_plugin_classes << plugin_class unless @v7_plugin_classes.include?(plugin_class) plugin_class rescue SystemExit, Interrupt # rubocop: disable Lint/ShadowedException raise rescue Ohai::Exceptions::InvalidPluginName => e logger.warn("Plugin Name Error: <#{plugin_path}>: #{e.message}") rescue Ohai::Exceptions::IllegalPluginDefinition => e logger.warn("Plugin Definition Error: <#{plugin_path}>: #{e.message}") rescue NoMethodError => e logger.warn("Plugin Method Error: <#{plugin_path}>: unsupported operation \'#{e.name}\'") rescue SyntaxError => e # split on occurrences of # : syntax error, # :##: syntax error, # to remove from error message parts = e.message.split(/<.*>[:[0-9]+]*: syntax error, /) parts.each do |part| next if part.length == 0 logger.warn("Plugin Syntax Error: <#{plugin_path}>: #{part}") end rescue Exception => e logger.warn("Plugin Error: <#{plugin_path}>: #{e.message}") logger.trace("Plugin Error: <#{plugin_path}>: #{e.inspect}, #{e.backtrace.join('\n')}") end def load_v7_plugin(plugin_class) plugin = plugin_class.new(@controller.data, @controller.logger) collect_provides(plugin) plugin end end end