# # Author:: Tyler Cloke () # # Copyright:: Copyright 2012-2016, 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/event_dispatch/base" require "chef/formatters/error_inspectors" require "chef/formatters/error_description" require "chef/formatters/error_mapper" require "chef/formatters/indentable_output_stream" class Chef # == Chef::Formatters # Formatters handle printing output about the progress/status of a chef # client run to the user's screen. module Formatters class UnknownFormatter < StandardError; end def self.formatters_by_name @formatters_by_name ||= {} end def self.register(name, formatter) formatters_by_name[name.to_s] = formatter end def self.by_name(name) formatters_by_name[name] end def self.available_formatters formatters_by_name.keys end #-- # TODO: is it too clever to be defining new() on a module like this? def self.new(name, out, err) formatter_class = by_name(name.to_s) raise UnknownFormatter, "No output formatter found for #{name} (available: #{available_formatters.join(', ')})" unless formatter_class formatter_class.new(out, err) end # == Formatters::Base # Base class that all formatters should inherit from. class Base < EventDispatch::Base include ErrorMapper def self.cli_name(name) Chef::Formatters.register(name, self) end attr_reader :out attr_reader :err attr_reader :output def initialize(out, err) @output = IndentableOutputStream.new(out, err) end def puts(*args) @output.puts(*args) end def print(*args) @output.print(*args) end def puts_line(*args) @output.puts_line(*args) end def start_line(*args) @output.start_line(*args) end def indent_by(amount) @output.indent += amount if @output.indent < 0 # This is left commented out for now. We need to uncomment it and fix at least one bug in # the formatter, and then leave this line uncommented in the future. #Chef::Log.warn "Internal Formatter Error -- Attempt to indent by negative number of spaces" @output.indent = 0 end @output.indent end # Input: a Formatters::ErrorDescription object. # Outputs error to STDOUT. def display_error(description) puts("") description.display(output) end def registration_failed(node_name, exception, config) #A Formatters::ErrorDescription object description = ErrorMapper.registration_failed(node_name, exception, config) display_error(description) end def node_load_failed(node_name, exception, config) description = ErrorMapper.node_load_failed(node_name, exception, config) display_error(description) end def run_list_expand_failed(node, exception) description = ErrorMapper.run_list_expand_failed(node, exception) display_error(description) end def cookbook_resolution_failed(expanded_run_list, exception) description = ErrorMapper.cookbook_resolution_failed(expanded_run_list, exception) display_error(description) end def cookbook_sync_failed(cookbooks, exception) description = ErrorMapper.cookbook_sync_failed(cookbooks, exception) display_error(description) end def resource_failed(resource, action, exception) description = ErrorMapper.resource_failed(resource, action, exception) display_error(description) end # Generic callback for any attribute/library/lwrp/recipe file in a # cookbook getting loaded. The per-filetype callbacks for file load are # overriden so that they call this instead. This means that a subclass of # Formatters::Base can implement #file_loaded to do the same thing for # every kind of file that Chef loads from a recipe instead of # implementing all the per-filetype callbacks. def file_loaded(path) end # Generic callback for any attribute/library/lwrp/recipe file throwing an # exception when loaded. Default behavior is to use CompileErrorInspector # to print contextual info about the failure. def file_load_failed(path, exception) description = ErrorMapper.file_load_failed(path, exception) display_error(description) end def recipe_not_found(exception) description = ErrorMapper.file_load_failed(nil, exception) display_error(description) end # Delegates to #file_loaded def library_file_loaded(path) file_loaded(path) end # Delegates to #file_load_failed def library_file_load_failed(path, exception) file_load_failed(path, exception) end # Delegates to #file_loaded def lwrp_file_loaded(path) file_loaded(path) end # Delegates to #file_load_failed def lwrp_file_load_failed(path, exception) file_load_failed(path, exception) end # Delegates to #file_loaded def attribute_file_loaded(path) file_loaded(path) end # Delegates to #file_load_failed def attribute_file_load_failed(path, exception) file_load_failed(path, exception) end # Delegates to #file_loaded def definition_file_loaded(path) file_loaded(path) end # Delegates to #file_load_failed def definition_file_load_failed(path, exception) file_load_failed(path, exception) end # Delegates to #file_loaded def recipe_file_loaded(path, recipe) file_loaded(path) end # Delegates to #file_load_failed def recipe_file_load_failed(path, exception, recipe) file_load_failed(path, exception) end def deprecation(message, location = caller(2..2)[0]) Chef::Log.deprecation("#{message} at #{location}") end def is_formatter? true end end # == NullFormatter # Formatter that doesn't actually produce any output. You can use this to # disable the use of output formatters. class NullFormatter < Base cli_name(:null) def is_formatter? false end end end end