require_relative "base" require_relative "../config" require_relative "../dist" class Chef module Formatters # Formatter similar to RSpec's documentation formatter. Uses indentation to # show context. class Doc < Formatters::Base attr_reader :start_time, :end_time cli_name(:doc) def initialize(out, err) super @updated_resources = 0 @up_to_date_resources = 0 @start_time = Time.now @end_time = @start_time @skipped_resources = 0 @progress = {} end def elapsed_time end_time - start_time end def pretty_elapsed_time time = elapsed_time if time < 60 message = Time.at(time).utc.strftime("%S seconds") elsif time < 3600 message = Time.at(time).utc.strftime("%M minutes %S seconds") else message = Time.at(time).utc.strftime("%H hours %M minutes %S seconds") end message end def run_start(version, run_status) puts_line "Starting #{Chef::Dist::PRODUCT}, version #{version}" puts_line "Targeting node: #{Chef::Config.target_mode.host}" if Chef::Config.target_mode? puts_line "OpenSSL FIPS 140 mode enabled" if Chef::Config[:fips] end def total_resources @up_to_date_resources + @updated_resources + @skipped_resources end def run_completed(node) @end_time = Time.now # Print out deprecations. unless deprecations.empty? puts_line "" puts_line "Deprecated features used!" deprecations.each do |message, details| locations = details[:locations] if locations.size == 1 puts_line " #{message} at 1 location:" else puts_line " #{message} at #{locations.size} locations:" end locations.each do |location| prefix = " - " Array(location).each do |line| puts_line "#{prefix}#{line}" prefix = " " end end unless details[:url].nil? puts_line " See #{details[:url]} for further details." end end puts_line "" end if Chef::Config[:why_run] puts_line "#{Chef::Dist::PRODUCT} finished, #{@updated_resources}/#{total_resources} resources would have been updated" else puts_line "#{Chef::Dist::PRODUCT} finished, #{@updated_resources}/#{total_resources} resources updated in #{pretty_elapsed_time}" end end def run_failed(exception) @end_time = Time.now if Chef::Config[:why_run] puts_line "#{Chef::Dist::PRODUCT} failed. #{@updated_resources} resources would have been updated" else puts_line "#{Chef::Dist::PRODUCT} failed. #{@updated_resources} resources updated in #{pretty_elapsed_time}" end end # Called right after ohai runs. def ohai_completed(node); end # Already have a client key, assuming this node has registered. def skipping_registration(node_name, config); end # About to attempt to register as +node_name+ def registration_start(node_name, config) puts_line "Creating a new client identity for #{node_name} using the validator key." end def registration_completed; end def node_load_start(node_name, config); end # Failed to load node data from the server def node_load_failed(node_name, exception, config) super end # Default and override attrs from roles have been computed, but not yet applied. # Normal attrs from JSON have been added to the node. def node_load_completed(node, expanded_run_list, config); end def policyfile_loaded(policy) puts_line "Using policy '#{policy["name"]}' at revision '#{policy["revision_id"]}'" end # Called before the cookbook collection is fetched from the server. def cookbook_resolution_start(expanded_run_list) puts_line "resolving cookbooks for run list: #{expanded_run_list.inspect}" end # Called when there is an error getting the cookbook collection from the # server. def cookbook_resolution_failed(expanded_run_list, exception) super end # Called when the cookbook collection is returned from the server. def cookbook_resolution_complete(cookbook_collection); end # Called before unneeded cookbooks are removed def cookbook_clean_start; end # Called after the file at +path+ is removed. It may be removed if the # cookbook containing it was removed from the run list, or if the file was # removed from the cookbook. def removed_cookbook_file(path); end # Called when cookbook cleaning is finished. def cookbook_clean_complete; end # Called before cookbook sync starts def cookbook_sync_start(cookbook_count) puts_line "Synchronizing Cookbooks:" indent end # Called when cookbook +cookbook+ has been sync'd def synchronized_cookbook(cookbook_name, cookbook) puts_line "- #{cookbook.name} (#{cookbook.version})" end # Called when an individual file in a cookbook has been updated def updated_cookbook_file(cookbook_name, path); end # Called after all cookbooks have been sync'd. def cookbook_sync_complete unindent end # Called when starting to collect gems from the cookbooks def cookbook_gem_start(gems) puts_line "Installing Cookbook Gems:" indent end # Called when the result of installing the bundle is to install the gem def cookbook_gem_installing(gem, version) puts_line "- Installing #{gem} #{version}", :green end # Called when the result of installing the bundle is to use the gem def cookbook_gem_using(gem, version) puts_line "- Using #{gem} #{version}" end # Called when finished installing cookbook gems def cookbook_gem_finished unindent end # Called when cookbook gem installation fails def cookbook_gem_failed(exception) unindent end # Called when cookbook loading starts. def library_load_start(file_count) puts_line "Compiling Cookbooks..." end # Called after a file in a cookbook is loaded. def file_loaded(path); end # Called when recipes have been loaded. def recipe_load_complete; end # Called before convergence starts def converge_start(run_context) puts_line "Converging #{run_context.resource_collection.all_resources.size} resources" end # Called when the converge phase is finished. def converge_complete unindent if @current_recipe end def converge_failed(e) # Currently a failed converge is handled the same way as a successful converge converge_complete end # Called before action is executed on a resource. def resource_action_start(resource, action, notification_type = nil, notifier = nil) if resource.cookbook_name && resource.recipe_name resource_recipe = "#{resource.cookbook_name}::#{resource.recipe_name}" else resource_recipe = "" end if resource_recipe != @current_recipe && !resource.enclosing_provider unindent if @current_recipe puts_line "Recipe: #{resource_recipe}" @current_recipe = resource_recipe indent end # @todo info about notifies start_line "* #{resource} action #{action}", stream: resource indent end def resource_update_progress(resource, current, total, interval) @progress[resource] ||= -1 percent_complete = (current.to_f / total.to_f * 100).to_i unless total.to_f == 0.0 if percent_complete && percent_complete > @progress[resource] @progress[resource] = percent_complete if percent_complete % interval == 0 start_line " - Progress: #{percent_complete}%", :green end end end # Called when a resource fails, but will retry. def resource_failed_retriable(resource, action, retry_count, exception); end # Called when a resource fails and will not be retried. def resource_failed(resource, action, exception) super unindent end # Called when a resource action has been skipped b/c of a conditional def resource_skipped(resource, action, conditional) @skipped_resources += 1 # TODO: more info about conditional puts " (skipped due to #{conditional.short_description})", stream: resource unindent end # Called after #load_current_resource has run. def resource_current_state_loaded(resource, action, current_resource); end # Called when a resource has no converge actions, e.g., it was already correct. def resource_up_to_date(resource, action) @up_to_date_resources += 1 puts " (up to date)", stream: resource unindent end def resource_bypassed(resource, action, provider) puts " (Skipped: whyrun not supported by provider #{provider.class.name})", stream: resource unindent end def output_record(line); end # Called when a change has been made to a resource. May be called multiple # times per resource, e.g., a file may have its content updated, and then # its permissions updated. def resource_update_applied(resource, action, update) prefix = Chef::Config[:why_run] ? "Would " : "" Array(update).each do |line| next if line.nil? output_record line if line.is_a? String start_line "- #{prefix}#{line}", :green elsif line.is_a? Array # Expanded output - delta # @todo should we have a resource_update_delta callback? line.each do |detail| start_line detail, :white end end end end # Called after a resource has been completely converged. def resource_updated(resource, action) @updated_resources += 1 unindent puts "\n" end # Called when resource current state load is skipped due to the provider # not supporting whyrun mode. def resource_current_state_load_bypassed(resource, action, current_resource) puts_line("* Whyrun not supported for #{resource}, bypassing load.", :yellow) end def stream_output(stream, output, options = {}) print(output, { stream: stream }.merge(options)) end # Called before handlers run def handlers_start(handler_count) puts "" puts "Running handlers:" indent end # Called after an individual handler has run def handler_executed(handler) puts_line "- #{handler.class.name}" end # Called after all handlers have executed def handlers_completed unindent puts_line "Running handlers complete\n" end # Called when a provider makes an assumption after a failed assertion # in whyrun mode, in order to allow execution to continue def whyrun_assumption(action, resource, message) return unless message [ message ].flatten.each do |line| start_line("* #{line}", :yellow) end end # Called when an assertion declared by a provider fails def provider_requirement_failed(action, resource, exception, message) return unless message color = Chef::Config[:why_run] ? :yellow : :red [ message ].flatten.each do |line| start_line("* #{line}", color) end end # (see Base#deprecation) def deprecation(deprecation, _location = nil) if Chef::Config[:treat_deprecation_warnings_as_errors] super elsif !deprecation.silenced? # Save non-silenced deprecations to the screen until the end. deprecations[deprecation.message] ||= { url: deprecation.url, locations: Set.new } deprecations[deprecation.message][:locations] << deprecation.location end end def indent indent_by(2) end def unindent indent_by(-2) end protected def deprecations @deprecations ||= {} end end end end