# frozen_string_literal: true # # Author:: Adam Jacob () # 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_relative "version" require_relative "loader" require_relative "log" require_relative "mash" require_relative "runner" require_relative "dsl" require_relative "mixin/shell_out" require_relative "mixin/os" require_relative "mixin/string" require_relative "mixin/constant_helper" require_relative "provides_map" require_relative "hints" require_relative "config" require_relative "train_transport" require "ffi_yajl" unless defined?(FFI_Yajl) require "cgi" unless defined?(CGI) module Ohai # The class used by Ohai::Application and Chef to actually collect data class System include Ohai::Mixin::ConstantHelper attr_accessor :data attr_reader :config attr_reader :provides_map attr_reader :logger attr_writer :transport_connection # the cli flag is used to determine if we're being constructed by # something like chef-client (which doesn't set this flag) and # which sets up its own loggers, or if we're coming from Ohai::Application # and therefore need to configure Ohai's own logger. def initialize(config = {}) @cli = config[:invoked_from_cli] @plugin_path = "" @config = config @failed_plugins = [] @logger = config[:logger] || Ohai::Log.with_child @logger.metadata = { system: "ohai", version: Ohai::VERSION } reset_system end # clears the current collected data, clears the provides map for plugins, # refreshes hints, and reconfigures ohai. In short this gets Ohai into a first run state # # @return [void] def reset_system @data = Mash.new @provides_map = ProvidesMap.new configure_ohai configure_logging if @cli @loader = Ohai::Loader.new(self) Ohai::Hints.refresh_hints # Remove the previously defined plugins recursive_remove_constants(Ohai::NamedPlugin) end def runner @runner ||= Ohai::Runner.new(self, true).tap do |runner| runner.transport_connection = transport_connection unless transport_connection.nil? end end def [](key) @data[key] end # Resets the system and loads then runs the plugins. This is the primary method called # to run the system. # # @param [Array] attribute_filter the attributes to run. All will be run if not specified # # @return [void] def all_plugins(attribute_filter = nil) # Reset the system when all_plugins is called since this function # can be run multiple times in order to pick up any changes in the # config or plugins with Chef. reset_system load_plugins run_plugins(true, attribute_filter) end # load all plugins by calling Ohai::Loader.load_all # # @see Ohai::Loader.load_all def load_plugins @loader.load_all end # get backend parameters for target mode # # @return [Train::Transport] def transport_connection @transport_connection ||= Ohai::TrainTransport.new(logger).build_transport&.connection end # run all plugins or those that match the attribute filter is provided # # @param safe [Boolean] # @param [Array] attribute_filter the attributes to run. All will be run if not specified # # @return [Mash] def run_plugins(safe = false, attribute_filter = nil) begin @provides_map.all_plugins(attribute_filter).each do |plugin| runner.run_plugin(plugin) end transport_connection.close unless transport_connection.nil? rescue Ohai::Exceptions::AttributeNotFound, Ohai::Exceptions::DependencyCycle => e logger.error("Encountered error while running plugins: #{e.inspect}") raise end critical_failed = Ohai::Config.ohai[:critical_plugins] & runner.failed_plugins unless critical_failed.empty? msg = "The following Ohai plugins marked as critical failed: #{critical_failed}" if @cli logger.error(msg) exit(true) else raise Ohai::Exceptions::CriticalPluginFailure, "#{msg}. Failing Chef run." end end # Freeze all strings. freeze_strings! end # @param [String] plugin_path # # @return [void] # def run_additional_plugins(plugin_path) @loader.load_additional(plugin_path).each do |plugin| logger.trace "Running plugin #{plugin}" runner.run_plugin(plugin) end freeze_strings! end # # Serialize this object as a hash # def to_json FFI_Yajl::Encoder.new.encode(@data) end # # Pretty Print this object as JSON # def json_pretty_print(item = nil) FFI_Yajl::Encoder.new(pretty: true, validate_utf8: false).encode(item || @data) end def attributes_print(a) data = @data a.split("/").each do |part| data = data[part] end raise ArgumentError, "I cannot find an attribute named #{a}!" if data.nil? case data when Hash, Mash, Array, Integer json_pretty_print(data) when String if data.respond_to?(:lines) json_pretty_print(data.lines.to_a) else json_pretty_print(data.to_a) end else raise ArgumentError, "I can only generate JSON for Hashes, Mashes, Arrays and Strings. You fed me a #{data.class}!" end end private def configure_ohai Ohai.config.merge!(@config) # add any additional CLI passed directories to the plugin path excluding duplicates unless Ohai.config[:directory].nil? # make sure the directory config is an array since it could be a string set in client.rb Array(Ohai.config[:directory]).each do |dir| next if Ohai.config[:plugin_path].include?(dir) Ohai.config[:plugin_path] << dir end end logger.debug("Running Ohai with the following configuration: #{Ohai.config.configuration}") end def configure_logging if Ohai.config[:log_level] == :auto Ohai::Log.level = :info else Ohai::Log.level = Ohai.config[:log_level] end end # Freeze all string values in @data. This makes them immutable and saves # a bit of RAM. # # @api private # @return [void] def freeze_strings! # Recursive visitor pattern helper. visitor = lambda do |val| case val when Hash val.each_value { |v| visitor.call(v) } when Array val.each { |v| visitor.call(v) } when String val.freeze end end visitor.call(@data) end # Extract additional backend parameters from Target Mode URL. # # @api private # @return [Hash] def backend_parameters uri = URI.parse(config[:target]) return {} unless uri.query CGI.parse(uri.query).map { |k, v| [k.to_sym, v.first] }.to_h end end end