diff options
author | John Keiser <john@johnkeiser.com> | 2015-05-28 13:27:53 -0700 |
---|---|---|
committer | John Keiser <john@johnkeiser.com> | 2015-05-28 13:27:53 -0700 |
commit | a8725192286927dd6a9c275dd205b81db5e2ee90 (patch) | |
tree | 193375a0afeb1c4edca157138d5b19585628fa71 | |
parent | 678d696b169d6fbcf5966124f41847bfc3282c13 (diff) | |
parent | cfb7b3e79a4092b705648e00bf5997fbd7014e93 (diff) | |
download | chef-a8725192286927dd6a9c275dd205b81db5e2ee90.tar.gz |
Comment up Chef::Client and privatize/deprecate unused methods
-rw-r--r-- | lib/chef/client.rb | 795 | ||||
-rw-r--r-- | lib/chef/mixin/deprecation.rb | 24 | ||||
-rw-r--r-- | spec/unit/deprecation_spec.rb | 55 |
3 files changed, 665 insertions, 209 deletions
diff --git a/lib/chef/client.rb b/lib/chef/client.rb index f3e7722509..51e78e60a9 100644 --- a/lib/chef/client.rb +++ b/lib/chef/client.rb @@ -50,6 +50,7 @@ require 'chef/run_lock' require 'chef/policy_builder' require 'chef/request_id' require 'chef/platform/rebooter' +require 'chef/mixin/deprecation' require 'ohai' require 'rbconfig' @@ -60,108 +61,100 @@ class Chef class Client include Chef::Mixin::PathSanity - # IO stream that will be used as 'STDOUT' for formatters. Formatters are - # configured during `initialize`, so this provides a convenience for - # setting alternative IO stream during tests. - STDOUT_FD = STDOUT - - # IO stream that will be used as 'STDERR' for formatters. Formatters are - # configured during `initialize`, so this provides a convenience for - # setting alternative IO stream during tests. - STDERR_FD = STDERR - - # Clears all notifications for client run status events. - # Primarily for testing purposes. - def self.clear_notifications - @run_start_notifications = nil - @run_completed_successfully_notifications = nil - @run_failed_notifications = nil - end - - # The list of notifications to be run when the client run starts. - def self.run_start_notifications - @run_start_notifications ||= [] - end + extend Chef::Mixin::Deprecation - # The list of notifications to be run when the client run completes - # successfully. - def self.run_completed_successfully_notifications - @run_completed_successfully_notifications ||= [] - end - - # The list of notifications to be run when the client run fails. - def self.run_failed_notifications - @run_failed_notifications ||= [] - end - - # Add a notification for the 'client run started' event. The notification - # is provided as a block. The current Chef::RunStatus object will be passed - # to the notification_block when the event is triggered. - def self.when_run_starts(¬ification_block) - run_start_notifications << notification_block - end - - # Add a notification for the 'client run success' event. The notification - # is provided as a block. The current Chef::RunStatus object will be passed - # to the notification_block when the event is triggered. - def self.when_run_completes_successfully(¬ification_block) - run_completed_successfully_notifications << notification_block - end + # + # The status of the Chef run. + # + # @return [Chef::RunStatus] + # + attr_reader :run_status - # Add a notification for the 'client run failed' event. The notification - # is provided as a block. The current Chef::RunStatus is passed to the - # notification_block when the event is triggered. - def self.when_run_fails(¬ification_block) - run_failed_notifications << notification_block + # + # The node represented by this client. + # + # @return [Chef::Node] + # + def node + run_status.node end - - # Callback to fire notifications that the Chef run is starting - def run_started - self.class.run_start_notifications.each do |notification| - notification.call(run_status) - end - @events.run_started(run_status) + def node=(value) + run_status.node = value end - # Callback to fire notifications that the run completed successfully - def run_completed_successfully - success_handlers = self.class.run_completed_successfully_notifications - success_handlers.each do |notification| - notification.call(run_status) - end - end + # + # The ohai system used by this client. + # + # @return [Ohai::System] + # + attr_reader :ohai - # Callback to fire notifications that the Chef run failed - def run_failed - failure_handlers = self.class.run_failed_notifications - failure_handlers.each do |notification| - notification.call(run_status) - end - end + # + # The rest object used to communicate with the Chef server. + # + # @return [Chef::REST] + # + attr_reader :rest - attr_accessor :node - attr_accessor :ohai - attr_accessor :rest + # + # The runner used to converge. + # + # @return [Chef::Runner] + # attr_accessor :runner + # + # Extra node attributes that were applied to the node. + # + # @return [Hash] + # attr_reader :json_attribs - attr_reader :run_status + + # + # The event dispatcher for the Chef run, including any configured output + # formatters and event loggers. + # + # @return [EventDispatch::Dispatcher] + # + # @see Chef::Formatters + # @see Chef::Config#formatters + # @see Chef::Config#stdout + # @see Chef::Config#stderr + # @see Chef::Config#force_logger + # @see Chef::Config#force_formatter + # TODO add stdout, stderr, and default formatters to Chef::Config so the + # defaults aren't calculated here. Remove force_logger and force_formatter + # from this code. + # @see Chef::EventLoggers + # @see Chef::Config#disable_event_logger + # @see Chef::Config#event_loggers + # @see Chef::Config#event_handlers + # attr_reader :events + # # Creates a new Chef::Client. + # + # @param json_attribs [Hash] Node attributes to layer into the node when it is + # fetched. + # @param args [Hash] Options: + # @option args [Array<RunList::RunListItem>] :override_runlist A runlist to + # use instead of the node's embedded run list. + # @option args [Array<String>] :specific_recipes A list of recipe file paths + # to load after the run list has been loaded. + # def initialize(json_attribs=nil, args={}) @json_attribs = json_attribs || {} - @node = nil - @runner = nil @ohai = Ohai::System.new event_handlers = configure_formatters + configure_event_loggers event_handlers += Array(Chef::Config[:event_handlers]) @events = EventDispatch::Dispatcher.new(*event_handlers) + # TODO it seems like a bad idea to be deletin' other peoples' hashes. @override_runlist = args.delete(:override_runlist) @specific_recipes = args.delete(:specific_recipes) - @run_status = Chef::RunStatus.new(node, events) + @run_status = Chef::RunStatus.new(nil, events) if new_runlist = args.delete(:runlist) @json_attribs["run_list"] = new_runlist @@ -175,6 +168,173 @@ class Chef Chef.set_resource_priority_map(Chef::Platform::ResourcePriorityMap.instance) end + # + # Do a full run for this Chef::Client. + # + # Locks the run while doing its job. + # + # Fires run_start before doing anything and fires run_completed or + # run_failed when finished. Also notifies client listeners of run_started + # at the beginning of Compile, and run_completed_successfully or run_failed + # when all is complete. + # + # Phase 1: Setup + # -------------- + # Gets information about the system and the run we are doing. + # + # 1. Run ohai to collect system information. + # 2. Register / connect to the Chef server (unless in solo mode). + # 3. Retrieve the node (or create a new one). + # 4. Merge in json_attribs, Chef::Config.environment, and override_run_list. + # + # @see #run_ohai + # @see #load_node + # @see #build_node + # @see Chef::Config#lockfile + # @see Chef::RunLock#acquire + # + # Phase 2: Compile + # ---------------- + # Decides *what* we plan to converge by compiling recipes. + # + # 1. Sync required cookbooks to the local cache. + # 2. Load libraries from all cookbooks. + # 3. Load attributes from all cookbooks. + # 4. Load LWRPs from all cookbooks. + # 5. Load resource definitions from all cookbooks. + # 6. Load recipes in the run list. + # 7. Load recipes from the command line. + # + # @see #setup_run_context Syncs and compiles cookbooks. + # @see Chef::CookbookCompiler#compile + # + # Phase 3: Converge + # ----------------- + # Brings the system up to date. + # + # 1. Converge the resources built from recipes in Phase 2. + # 2. Save the node. + # 3. Reboot if we were asked to. + # + # @see #converge_and_save + # @see Chef::Runner + # + # Phase 4: Audit + # -------------- + # Runs 'control_group' audits in recipes. This entire section can be enabled or disabled with config. + # + # 1. 'control_group' DSL collects audits during Phase 2 + # 2. Audits are run using RSpec + # 3. Errors are collected and reported using the formatters + # + # @see #run_audits + # @see Chef::Audit::Runner#run + # + # @raise [Chef::Exceptions::RunFailedWrappingError] If converge or audit failed. + # + # @see Chef::Config#enforce_path_sanity + # @see Chef::Config#solo + # @see Chef::Config#audit_mode + # + # @return Always returns true. + # + def run + run_error = nil + + runlock = RunLock.new(Chef::Config.lockfile) + # TODO feels like acquire should have its own block arg for this + runlock.acquire + # don't add code that may fail before entering this section to be sure to release lock + begin + runlock.save_pid + + request_id = Chef::RequestID.instance.request_id + run_context = nil + events.run_start(Chef::VERSION) + Chef::Log.info("*** Chef #{Chef::VERSION} ***") + Chef::Log.info "Chef-client pid: #{Process.pid}" + Chef::Log.debug("Chef-client request_id: #{request_id}") + enforce_path_sanity + run_ohai + + register unless Chef::Config[:solo] + + load_node + + build_node + + run_status.run_id = request_id + run_status.start_clock + Chef::Log.info("Starting Chef Run for #{node.name}") + run_started + + do_windows_admin_check + + run_context = setup_run_context + + if Chef::Config[:audit_mode] != :audit_only + converge_error = converge_and_save(run_context) + end + + if Chef::Config[:why_run] == true + # why_run should probably be renamed to why_converge + Chef::Log.debug("Not running controls in 'why_run' mode - this mode is used to see potential converge changes") + elsif Chef::Config[:audit_mode] != :disabled + audit_error = run_audits(run_context) + end + + # Raise converge_error so run_failed reporters/events are processed. + raise converge_error if converge_error + + run_status.stop_clock + Chef::Log.info("Chef Run complete in #{run_status.elapsed_time} seconds") + run_completed_successfully + events.run_completed(node) + + # rebooting has to be the last thing we do, no exceptions. + Chef::Platform::Rebooter.reboot_if_needed!(node) + rescue Exception => run_error + # CHEF-3336: Send the error first in case something goes wrong below and we don't know why + Chef::Log.debug("Re-raising exception: #{run_error.class} - #{run_error.message}\n#{run_error.backtrace.join("\n ")}") + # If we failed really early, we may not have a run_status yet. Too early for these to be of much use. + if run_status + run_status.stop_clock + run_status.exception = run_error + run_failed + end + events.run_failed(run_error) + ensure + Chef::RequestID.instance.reset_request_id + request_id = nil + @run_status = nil + run_context = nil + runlock.release + GC.start + end + + # Raise audit, converge, and other errors here so that we exit + # with the proper exit status code and everything gets raised + # as a RunFailedWrappingError + if run_error || converge_error || audit_error + error = if run_error == converge_error + Chef::Exceptions::RunFailedWrappingError.new(converge_error, audit_error) + else + Chef::Exceptions::RunFailedWrappingError.new(run_error, converge_error, audit_error) + end + error.fill_backtrace + Chef::Application.debug_stacktrace(error) + raise error + end + + true + end + + # + # Private API + # TODO make this stuff protected or private + # + + # @api private def configure_formatters formatters_for_run.map do |formatter_name, output_path| if output_path.nil? @@ -187,6 +347,7 @@ class Chef end end + # @api private def formatters_for_run if Chef::Config.formatters.empty? [default_formatter] @@ -195,6 +356,7 @@ class Chef end end + # @api private def default_formatter if (STDOUT.tty? && !Chef::Config[:force_logger]) || Chef::Config[:force_formatter] [:doc] @@ -203,6 +365,7 @@ class Chef end end + # @api private def configure_event_loggers if Chef::Config.disable_event_logger [] @@ -219,8 +382,9 @@ class Chef end end - # Resource repoters send event information back to the chef server for processing. - # Can only be called after we have a @rest object + # Resource reporters send event information back to the chef server for + # processing. Can only be called after we have a @rest object + # @api private def register_reporters [ Chef::ResourceReporter.new(rest), @@ -230,43 +394,123 @@ class Chef end end + # + # Callback to fire notifications that the Chef run is starting + # + # @api private + # + def run_started + self.class.run_start_notifications.each do |notification| + notification.call(run_status) + end + events.run_started(run_status) + end + + # + # Callback to fire notifications that the run completed successfully + # + # @api private + # + def run_completed_successfully + success_handlers = self.class.run_completed_successfully_notifications + success_handlers.each do |notification| + notification.call(run_status) + end + end + + # + # Callback to fire notifications that the Chef run failed + # + # @api private + # + def run_failed + failure_handlers = self.class.run_failed_notifications + failure_handlers.each do |notification| + notification.call(run_status) + end + end + + # # Instantiates a Chef::Node object, possibly loading the node's prior state - # when using chef-client. Delegates to policy_builder. Injects the built node - # into the Chef class. + # when using chef-client. Sets Chef.node to the new node. # # @return [Chef::Node] The node object for this Chef run + # + # @see Chef::PolicyBuilder#load_node + # + # @api private + # def load_node policy_builder.load_node - @node = policy_builder.node - Chef.set_node(@node) + run_status.node = policy_builder.node + Chef.set_node(policy_builder.node) node end - # Mutates the `node` object to prepare it for the chef run. Delegates to - # policy_builder + # + # Mutates the `node` object to prepare it for the chef run. # # @return [Chef::Node] The updated node object + # + # @see Chef::PolicyBuilder#build_node + # + # @api private + # def build_node policy_builder.build_node - @run_status.node = node + run_status.node = node node end + # + # Sync cookbooks to local cache. + # + # TODO this appears to be unused. + # + # @see Chef::PolicyBuilder#sync_cookbooks + # + # @api private + # + def sync_cookbooks + policy_builder.sync_cookbooks + end + + # + # Sets up the run context. + # + # @see Chef::PolicyBuilder#setup_run_context + # + # @return The newly set up run context + # + # @api private def setup_run_context - run_context = policy_builder.setup_run_context(@specific_recipes) + run_context = policy_builder.setup_run_context(specific_recipes) assert_cookbook_path_not_empty(run_context) run_status.run_context = run_context run_context end - def sync_cookbooks - policy_builder.sync_cookbooks - end - + # + # The PolicyBuilder strategy for figuring out run list and cookbooks. + # + # @return [Chef::PolicyBuilder::Policyfile, Chef::PolicyBuilder::ExpandNodeObject] + # + # @api private + # def policy_builder - @policy_builder ||= Chef::PolicyBuilder.strategy.new(node_name, ohai.data, json_attribs, @override_runlist, events) + @policy_builder ||= Chef::PolicyBuilder.strategy.new(node_name, ohai.data, json_attribs, override_runlist, events) end + # + # Save the updated node to Chef. + # + # Does not save if we are in solo mode or using override_runlist. + # + # @see Chef::Node#save + # @see Chef::Config#solo + # + # @api private + # def save_updated_node if Chef::Config[:solo] # nothing to do @@ -274,16 +518,46 @@ class Chef Chef::Log.warn("Skipping final node save because override_runlist was given") else Chef::Log.debug("Saving the current state of node #{node_name}") - @node.save + node.save end end + # + # Run ohai plugins. Runs all ohai plugins unless minimal_ohai is specified. + # + # Sends the ohai_completed event when finished. + # + # @see Chef::EventDispatcher# + # @see Chef::Config#minimal_ohai + # + # @api private + # def run_ohai filter = Chef::Config[:minimal_ohai] ? %w[fqdn machinename hostname platform platform_version os os_version] : nil ohai.all_plugins(filter) - @events.ohai_completed(node) + events.ohai_completed(node) end + # + # Figure out the node name we are working with. + # + # It tries these, in order: + # - Chef::Config.node_name + # - ohai[:fqdn] + # - ohai[:machinename] + # - ohai[:hostname] + # + # If we are running against a server with authentication protocol < 1.0, we + # *require* authentication protocol version 1.1. + # + # @raise [Chef::Exceptions::CannotDetermineNodeName] If the node name is not + # set and cannot be determined via ohai. + # + # @see Chef::Config#node_name + # @see Chef::Config#authentication_protocol_version + # + # @api private + # def node_name name = Chef::Config[:node_name] || ohai[:fqdn] || ohai[:machinename] || ohai[:hostname] Chef::Config[:node_name] = name @@ -292,6 +566,8 @@ class Chef # node names > 90 bytes only work with authentication protocol >= 1.1 # see discussion in config.rb. + # TODO use a computed default in Chef::Config to determine this instead of + # setting it. if name.bytesize > 90 Chef::Config[:authentication_protocol_version] = "1.1" end @@ -300,46 +576,86 @@ class Chef end # - # === Returns - # rest<Chef::REST>:: returns Chef::REST connection object + # Determine our private key and set up the connection to the Chef server. + # + # Skips registration and fires the `skipping_registration` event if + # Chef::Config.client_key is unspecified or already exists. + # + # If Chef::Config.client_key does not exist, we register the client with the + # Chef server and fire the registration_start and registration_completed events. + # + # @return [Chef::REST] The server connection object. + # + # @see Chef::Config#chef_server_url + # @see Chef::Config#client_key + # @see Chef::ApiClient::Registration#run + # @see Chef::EventDispatcher#skipping_registration + # @see Chef::EventDispatcher#registration_start + # @see Chef::EventDispatcher#registration_completed + # @see Chef::EventDispatcher#registration_failed + # + # @api private + # def register(client_name=node_name, config=Chef::Config) if !config[:client_key] - @events.skipping_registration(client_name, config) + events.skipping_registration(client_name, config) Chef::Log.debug("Client key is unspecified - skipping registration") elsif File.exists?(config[:client_key]) - @events.skipping_registration(client_name, config) + events.skipping_registration(client_name, config) Chef::Log.debug("Client key #{config[:client_key]} is present - skipping registration") else - @events.registration_start(node_name, config) + events.registration_start(node_name, config) Chef::Log.info("Client key #{config[:client_key]} is not present - registering") Chef::ApiClient::Registration.new(node_name, config[:client_key]).run - @events.registration_completed + events.registration_completed end # We now have the client key, and should use it from now on. @rest = Chef::REST.new(config[:chef_server_url], client_name, config[:client_key]) register_reporters rescue Exception => e + # TODO this should probably only ever fire if we *started* registration. + # Move it to the block above. # TODO: munge exception so a semantic failure message can be given to the # user - @events.registration_failed(client_name, e, config) + events.registration_failed(client_name, e, config) raise end - # Converges the node. # - # === Returns - # The thrown exception, if there was one. If this returns nil the converge was successful. + # Converges all compiled resources. + # + # Fires the converge_start, converge_complete and converge_failed events. + # + # If the exception `:end_client_run_early` is thrown during convergence, it + # does not mark the run complete *or* failed, and returns `nil` + # + # @param run_context The run context. + # + # @return The thrown exception, if we are in audit mode. `nil` means the + # converge was successful or ended early. + # + # @raise Any converge exception, unless we are in audit mode, in which case + # we *return* the exception. + # + # @see Chef::Runner#converge + # @see Chef::Config#audit_mode + # @see Chef::EventDispatch#converge_start + # @see Chef::EventDispatch#converge_complete + # @see Chef::EventDispatch#converge_failed + # + # @api private + # def converge(run_context) converge_exception = nil catch(:end_client_run_early) do begin - @events.converge_start(run_context) + events.converge_start(run_context) Chef::Log.debug("Converging node #{node_name}") @runner = Chef::Runner.new(run_context) @runner.converge - @events.converge_complete + events.converge_complete rescue Exception => e - @events.converge_failed(e) + events.converge_failed(e) raise e if Chef::Config[:audit_mode] == :disabled converge_exception = e end @@ -347,8 +663,28 @@ class Chef converge_exception end + # + # Converge the node via and then save it if successful. + # + # @param run_context The run context. + # + # @return The thrown exception, if we are in audit mode. `nil` means the + # converge was successful or ended early. + # + # @raise Any converge or node save exception, unless we are in audit mode, + # in which case we *return* the exception. + # + # @see #converge + # @see #save_updated_mode + # @see Chef::Config#audit_mode + # + # @api private + # # We don't want to change the old API on the `converge` method to have it perform # saving. So we wrap it in this method. + # TODO given this seems to be pretty internal stuff, how badly do we need to + # split this stuff up? + # def converge_and_save(run_context) converge_exception = converge(run_context) unless converge_exception @@ -362,39 +698,67 @@ class Chef converge_exception end + # + # Run the audit phase. + # + # Triggers the audit_phase_start, audit_phase_complete and + # audit_phase_failed events. + # + # @param run_context The run context. + # + # @return Any thrown exceptions. `nil` if successful. + # + # @see Chef::Audit::Runner#run + # @see Chef::EventDispatch#audit_phase_start + # @see Chef::EventDispatch#audit_phase_complete + # @see Chef::EventDispatch#audit_phase_failed + # + # @api private + # def run_audits(run_context) - audit_exception = nil begin - @events.audit_phase_start(run_status) + events.audit_phase_start(run_status) Chef::Log.info("Starting audit phase") auditor = Chef::Audit::Runner.new(run_context) auditor.run if auditor.failed? audit_exception = Chef::Exceptions::AuditsFailed.new(auditor.num_failed, auditor.num_total) - @events.audit_phase_failed(audit_exception) + events.audit_phase_failed(audit_exception) else - @events.audit_phase_complete + events.audit_phase_complete end rescue Exception => e Chef::Log.error("Audit phase failed with error message: #{e.message}") - @events.audit_phase_failed(e) + events.audit_phase_failed(e) audit_exception = e end audit_exception end - # Expands the run list. Delegates to the policy_builder. # - # Normally this does not need to be called from here, it will be called by - # build_node. This is provided so external users (like the chefspec - # project) can inject custom behavior into the run process. + # Expands the run list. + # + # @return [Chef::RunListExpansion] The expanded run list. + # + # @see Chef::PolicyBuilder#expand_run_list # - # === Returns - # RunListExpansion: A RunListExpansion or API compatible object. def expanded_run_list policy_builder.expand_run_list end + # + # Check if the user has Administrator privileges on windows. + # + # Throws an error if the user is not an admin, and + # `Chef::Config.fatal_windows_admin_check` is true. + # + # @raise [Chef::Exceptions::WindowsNotAdmin] If the user is not an admin. + # + # @see Chef::platform#windows? + # @see Chef::Config#fatal_windows_admin_check + # + # @api private + # def do_windows_admin_check if Chef::Platform.windows? Chef::Log.debug("Checking for administrator privileges....") @@ -414,108 +778,121 @@ class Chef end end - # Do a full run for this Chef::Client. Calls: - # - # * run_ohai - Collect information about the system - # * build_node - Get the last known state, merge with local changes - # * register - If not in solo mode, make sure the server knows about this client - # * sync_cookbooks - If not in solo mode, populate the local cache with the node's cookbooks - # * converge - Bring this system up to date - # - # === Returns - # true:: Always returns true. - def run - run_error = nil - - runlock = RunLock.new(Chef::Config.lockfile) - runlock.acquire - # don't add code that may fail before entering this section to be sure to release lock - begin - runlock.save_pid - - request_id = Chef::RequestID.instance.request_id - run_context = nil - @events.run_start(Chef::VERSION) - Chef::Log.info("*** Chef #{Chef::VERSION} ***") - Chef::Log.info "Chef-client pid: #{Process.pid}" - Chef::Log.debug("Chef-client request_id: #{request_id}") - enforce_path_sanity - run_ohai - - register unless Chef::Config[:solo] - - load_node - - build_node + # Notification registration + class<<self + # + # Add a listener for the 'client run started' event. + # + # @param notification_block The callback (takes |run_status| parameter). + # @yieldparam [Chef::RunStatus] run_status The run status. + # + def when_run_starts(¬ification_block) + run_start_notifications << notification_block + end - run_status.run_id = request_id - run_status.start_clock - Chef::Log.info("Starting Chef Run for #{node.name}") - run_started + # + # Add a listener for the 'client run success' event. + # + # @param notification_block The callback (takes |run_status| parameter). + # @yieldparam [Chef::RunStatus] run_status The run status. + # + def when_run_completes_successfully(¬ification_block) + run_completed_successfully_notifications << notification_block + end - do_windows_admin_check + # + # Add a listener for the 'client run failed' event. + # + # @param notification_block The callback (takes |run_status| parameter). + # @yieldparam [Chef::RunStatus] run_status The run status. + # + def when_run_fails(¬ification_block) + run_failed_notifications << notification_block + end - run_context = setup_run_context + # + # Clears all listeners for client run status events. + # + # Primarily for testing purposes. + # + # @api private + # + def clear_notifications + @run_start_notifications = nil + @run_completed_successfully_notifications = nil + @run_failed_notifications = nil + end - if Chef::Config[:audit_mode] != :audit_only - converge_error = converge_and_save(run_context) - end + # + # TODO These seem protected to me. + # + + # + # Listeners to be run when the client run starts. + # + # @return [Array<Proc>] + # + # @api private + # + def run_start_notifications + @run_start_notifications ||= [] + end - if Chef::Config[:why_run] == true - # why_run should probably be renamed to why_converge - Chef::Log.debug("Not running controls in 'why_run' mode - this mode is used to see potential converge changes") - elsif Chef::Config[:audit_mode] != :disabled - audit_error = run_audits(run_context) - end + # + # Listeners to be run when the client run completes successfully. + # + # @return [Array<Proc>] + # + # @api private + # + def run_completed_successfully_notifications + @run_completed_successfully_notifications ||= [] + end - # Raise converge_error so run_failed reporters/events are processed. - raise converge_error if converge_error + # + # Listeners to be run when the client run fails. + # + # @return [Array<Proc>] + # + # @api private + # + def run_failed_notifications + @run_failed_notifications ||= [] + end + end - run_status.stop_clock - Chef::Log.info("Chef Run complete in #{run_status.elapsed_time} seconds") - run_completed_successfully - @events.run_completed(node) + # + # IO stream that will be used as 'STDOUT' for formatters. Formatters are + # configured during `initialize`, so this provides a convenience for + # setting alternative IO stream during tests. + # + # @api private + # + STDOUT_FD = STDOUT - # rebooting has to be the last thing we do, no exceptions. - Chef::Platform::Rebooter.reboot_if_needed!(node) - rescue Exception => run_error - # CHEF-3336: Send the error first in case something goes wrong below and we don't know why - Chef::Log.debug("Re-raising exception: #{run_error.class} - #{run_error.message}\n#{run_error.backtrace.join("\n ")}") - # If we failed really early, we may not have a run_status yet. Too early for these to be of much use. - if run_status - run_status.stop_clock - run_status.exception = run_error - run_failed - end - @events.run_failed(run_error) - ensure - Chef::RequestID.instance.reset_request_id - request_id = nil - @run_status = nil - run_context = nil - runlock.release - GC.start - end + # + # IO stream that will be used as 'STDERR' for formatters. Formatters are + # configured during `initialize`, so this provides a convenience for + # setting alternative IO stream during tests. + # + # @api private + # + STDERR_FD = STDERR - # Raise audit, converge, and other errors here so that we exit - # with the proper exit status code and everything gets raised - # as a RunFailedWrappingError - if run_error || converge_error || audit_error - error = if run_error == converge_error - Chef::Exceptions::RunFailedWrappingError.new(converge_error, audit_error) - else - Chef::Exceptions::RunFailedWrappingError.new(run_error, converge_error, audit_error) - end - error.fill_backtrace - Chef::Application.debug_stacktrace(error) - raise error - end + # + # Deprecated writers + # - true - end + include Chef::Mixin::Deprecation + deprecated_attr_writer :ohai, "There is no alternative. Leave ohai alone!" + deprecated_attr_writer :rest, "There is no alternative. Leave rest alone!" + deprecated_attr :runner, "There is no alternative. Leave runner alone!" private + attr_reader :override_runlist + attr_reader :specific_recipes + def empty_directory?(path) !File.exists?(path) || (Dir.entries(path).size <= 2) end diff --git a/lib/chef/mixin/deprecation.rb b/lib/chef/mixin/deprecation.rb index 489f27c339..a3eacf75cb 100644 --- a/lib/chef/mixin/deprecation.rb +++ b/lib/chef/mixin/deprecation.rb @@ -95,6 +95,30 @@ class Chef DeprecatedInstanceVariable.new(obj, name, level) end + def deprecated_attr(name, alternative) + deprecated_attr_reader(name, alternative) + deprecated_attr_writer(name, alternative) + end + + def deprecated_attr_reader(name, alternative, level=:warn) + define_method(name) do + Chef::Log.deprecation("#{self.class}.#{name} is deprecated. Support will be removed in a future release.") + Chef::Log.deprecation(alternative) + Chef::Log.deprecation("Called from:") + caller[0..3].each {|c| Chef::Log.deprecation(c)} + instance_variable_get("@#{name}") + end + end + + def deprecated_attr_writer(name, alternative, level=:warn) + define_method("#{name}=") do |value| + Chef::Log.deprecation("Writing to #{self.class}.#{name} with #{name}= is deprecated. Support will be removed in a future release.") + Chef::Log.deprecation(alternative) + Chef::Log.deprecation("Called from:") + caller[0..3].each {|c| Chef::Log.deprecation(c)} + instance_variable_set("@#{name}", value) + end + end end end end diff --git a/spec/unit/deprecation_spec.rb b/spec/unit/deprecation_spec.rb index f824cb7c76..2e1f3c39f3 100644 --- a/spec/unit/deprecation_spec.rb +++ b/spec/unit/deprecation_spec.rb @@ -95,4 +95,59 @@ describe Chef::Deprecation do expect { test_instance.deprecated_method(10) }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) end + context "When a class has deprecated_attr, _reader and _writer" do + before(:context) do + class DeprecatedAttrTest + extend Chef::Mixin::Deprecation + def initialize + @a = @r = @w = 1 + end + deprecated_attr :a, "a" + deprecated_attr_reader :r, "r" + deprecated_attr_writer :w, "w" + end + end + + it "The deprecated_attr emits warnings" do + test = DeprecatedAttrTest.new + expect { test.a = 10 }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) + expect { test.a }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) + end + + it "The deprecated_attr_writer emits warnings, and does not create a reader" do + test = DeprecatedAttrTest.new + expect { test.w = 10 }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) + expect { test.w }.to raise_error(NoMethodError) + end + + it "The deprecated_attr_reader emits warnings, and does not create a writer" do + test = DeprecatedAttrTest.new + expect { test.r = 10 }.to raise_error(NoMethodError) + expect { test.r }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) + end + + context "With deprecation warnings not throwing exceptions" do + before do + Chef::Config[:treat_deprecation_warnings_as_errors] = false + end + + it "The deprecated_attr can be written to and read from" do + test = DeprecatedAttrTest.new + test.a = 10 + expect(test.a).to eq 10 + end + + it "The deprecated_attr_reader can be read from" do + test = DeprecatedAttrTest.new + expect(test.r).to eq 1 + end + + it "The deprecated_attr_writer can be written to" do + test = DeprecatedAttrTest.new + test.w = 10 + expect(test.instance_eval { @w }).to eq 10 + end + end + end + end |