@@ -40,6 +40,73 @@ class Chef
4040 # The main object in a Chef run. Preps a Chef::Node and Chef::RunContext,
4141 # syncs cookbooks if necessary, and triggers convergence.
4242 class Client
43+
44+ # Clears all notifications for client run status events.
45+ # Primarily for testing purposes.
46+ def self . clear_notifications
47+ @run_start_notifications = nil
48+ @run_completed_successfully_notifications = nil
49+ @run_failed_notifications = nil
50+ end
51+
52+ # The list of notifications to be run when the client run starts.
53+ def self . run_start_notifications
54+ @run_start_notifications ||= [ ]
55+ end
56+
57+ # The list of notifications to be run when the client run completes
58+ # successfully.
59+ def self . run_completed_successfully_notifications
60+ @run_completed_successfully_notifications ||= [ ]
61+ end
62+
63+ # The list of notifications to be run when the client run fails.
64+ def self . run_failed_notifications
65+ @run_failed_notifications ||= [ ]
66+ end
67+
68+ # Add a notification for the 'client run started' event. The notification
69+ # is provided as a block. The current Chef::RunStatus object will be passed
70+ # to the notification_block when the event is triggered.
71+ def self . when_run_starts ( ¬ification_block )
72+ run_start_notifications << notification_block
73+ end
74+
75+ # Add a notification for the 'client run success' event. The notification
76+ # is provided as a block. The current Chef::RunStatus object will be passed
77+ # to the notification_block when the event is triggered.
78+ def self . when_run_completes_successfully ( ¬ification_block )
79+ run_completed_successfully_notifications << notification_block
80+ end
81+
82+ # Add a notification for the 'client run failed' event. The notification
83+ # is provided as a block. The current Chef::RunStatus is passed to the
84+ # notification_block when the event is triggered.
85+ def self . when_run_fails ( ¬ification_block )
86+ run_failed_notifications << notification_block
87+ end
88+
89+ # Callback to fire notifications that the Chef run is starting
90+ def run_started
91+ self . class . run_start_notifications . each do |notification |
92+ notification . call ( run_status )
93+ end
94+ end
95+
96+ # Callback to fire notifications that the run completed successfully
97+ def run_completed_successfully
98+ self . class . run_completed_successfully_notifications . each do |notification |
99+ notification . call ( run_status )
100+ end
101+ end
102+
103+ # Callback to fire notifications that the Chef run failed
104+ def run_failed
105+ self . class . run_failed_notifications . each do |notification |
106+ notification . call ( run_status )
107+ end
108+ end
109+
43110 attr_accessor :node
44111 attr_accessor :ohai
45112 attr_accessor :rest
@@ -49,10 +116,13 @@ class Client
49116 # TODO: timh/cw: 5-19-2010: json_attribs should be moved to RunContext?
50117 attr_reader :json_attribs
51118
119+ attr_reader :run_status
120+
52121 # Creates a new Chef::Client.
53122 def initialize ( json_attribs = nil )
54123 @json_attribs = json_attribs
55124 @node = nil
125+ @run_status = nil
56126 @runner = nil
57127 @ohai = Ohai ::System . new
58128 end
@@ -75,9 +145,10 @@ def run
75145 build_node
76146
77147 begin
78- run_status = Chef :: RunStatus . new ( node )
148+
79149 run_status . start_clock
80150 Chef ::Log . info ( "Starting Chef Run (Version #{ Chef ::VERSION } )" )
151+ run_started
81152
82153 if Chef ::Config [ :solo ]
83154 Chef ::Cookbook ::FileVendor . on_create { |manifest | Chef ::Cookbook ::FileSystemFileVendor . new ( manifest ) }
@@ -86,18 +157,12 @@ def run
86157 assert_cookbook_path_not_empty ( run_context )
87158 converge ( run_context )
88159 else
89- # Keep track of the filenames that we use in both eager cookbook
90- # downloading (during sync_cookbooks) and lazy (during the run
91- # itself, through FileVendor). After the run is over, clean up the
92- # cache.
93- valid_cache_entries = Hash . new
94-
95160 # Sync_cookbooks eagerly loads all files except files and templates.
96161 # It returns the cookbook_hash -- the return result from
97162 # /nodes/#{nodename}/cookbooks -- which we will use for our
98163 # run_context.
99- Chef ::Cookbook ::FileVendor . on_create { |manifest | Chef ::Cookbook ::RemoteFileVendor . new ( manifest , rest , valid_cache_entries ) }
100- cookbook_hash = sync_cookbooks ( valid_cache_entries )
164+ Chef ::Cookbook ::FileVendor . on_create { |manifest | Chef ::Cookbook ::RemoteFileVendor . new ( manifest , rest ) }
165+ cookbook_hash = sync_cookbooks
101166 run_context = Chef ::RunContext . new ( node , Chef ::CookbookCollection . new ( cookbook_hash ) )
102167 run_status . run_context = run_context
103168
@@ -106,41 +171,23 @@ def run
106171 converge ( run_context )
107172 Chef ::Log . debug ( "Saving the current state of node #{ node_name } " )
108173 @node . save
109-
110- cleanup_file_cache ( valid_cache_entries )
111174 end
112175
113176 run_status . stop_clock
114177 Chef ::Log . info ( "Chef Run complete in #{ run_status . elapsed_time } seconds" )
115- run_report_handlers ( run_status )
178+ run_completed_successfully
116179 true
117180 rescue Exception => e
118181 run_status . stop_clock
119182 run_status . exception = e
120- run_exception_handlers ( run_status )
121- Chef ::Log . error ( "Re-raising exception: #{ e . class } - #{ e . message } \n #{ e . backtrace . join ( "\n " ) } " )
183+ run_failed
184+ Chef ::Log . debug ( "Re-raising exception: #{ e . class } - #{ e . message } \n #{ e . backtrace . join ( "\n " ) } " )
122185 raise
123186 ensure
124187 run_status = nil
125188 end
126189 end
127190
128- def run_report_handlers ( run_status )
129- Chef ::Log . info ( "Running report handlers" )
130- Array ( Chef ::Config [ :report_handlers ] ) . each do |handler |
131- handler . run_report_safely ( run_status )
132- end
133- Chef ::Log . info ( "Report handlers complete" )
134- end
135-
136- def run_exception_handlers ( run_status )
137- Chef ::Log . error ( "Running exception handlers" )
138- Array ( Chef ::Config [ :exception_handlers ] ) . each do |handler |
139- handler . run_report_safely ( run_status )
140- end
141- Chef ::Log . error ( "Exception handlers complete" )
142- end
143-
144191 def run_ohai
145192 ohai . all_plugins
146193 end
@@ -171,10 +218,13 @@ def build_node
171218 @node = Chef ::Node . find_or_create ( node_name )
172219 end
173220
221+
174222 @node . consume_external_attrs ( ohai . data , @json_attribs )
175223 @node . save unless Chef ::Config [ :solo ]
176224 @node . reset_defaults_and_overrides
177225
226+ @run_status = Chef ::RunStatus . new ( @node )
227+
178228 @node
179229 end
180230
@@ -196,104 +246,17 @@ def register
196246 #
197247 # === Returns
198248 # true:: Always returns true
199- def sync_cookbooks ( valid_cache_entries )
249+ def sync_cookbooks
200250 Chef ::Log . debug ( "Synchronizing cookbooks" )
201251 cookbook_hash = rest . get_rest ( "nodes/#{ node_name } /cookbooks" )
202- Chef ::Log . debug ( "Cookbooks to load: #{ cookbook_hash . inspect } " )
203-
204- # Remove all cookbooks no longer relevant to this node
205- Chef ::FileCache . find ( File . join ( %w{ cookbooks ** * } ) ) . each do |cache_file |
206- cache_file =~ /^cookbooks\/ ([^\/ ]+)\/ /
207- unless cookbook_hash . has_key? ( $1)
208- Chef ::Log . info ( "Removing #{ cache_file } from the cache; its cookbook is no longer needed on this client." )
209- Chef ::FileCache . delete ( cache_file )
210- end
211- end
212-
213- # Synchronize each of the node's cookbooks, and add to the
214- # valid_cache_entries hash.
215- cookbook_hash . values . each do |cookbook |
216- sync_cookbook_file_cache ( cookbook , valid_cache_entries )
217- end
252+ Chef ::CookbookVersion . sync_cookbooks ( cookbook_hash )
218253
219254 # register the file cache path in the cookbook path so that CookbookLoader actually picks up the synced cookbooks
220255 Chef ::Config [ :cookbook_path ] = File . join ( Chef ::Config [ :file_cache_path ] , "cookbooks" )
221256
222257 cookbook_hash
223258 end
224259
225- # Update the file caches for a given cache segment. Takes a segment name
226- # and a hash that matches one of the cookbooks/_attribute_files style
227- # remote file listings.
228- #
229- # === Parameters
230- # cookbook<Chef::Cookbook>:: The cookbook to update
231- # valid_cache_entries<Hash>:: Out-param; Added to this hash are the files that
232- # were referred to by this cookbook
233- def sync_cookbook_file_cache ( cookbook , valid_cache_entries )
234- Chef ::Log . debug ( "Synchronizing cookbook #{ cookbook . name } " )
235-
236- # files and templates are lazily loaded, and will be done later.
237- eager_segments = Chef ::CookbookVersion ::COOKBOOK_SEGMENTS . dup
238- eager_segments . delete ( :files )
239- eager_segments . delete ( :templates )
240-
241- eager_segments . each do |segment |
242- segment_filenames = Array . new
243- cookbook . manifest [ segment ] . each do |manifest_record |
244- # segment = cookbook segment
245- # remote_list = list of file hashes
246- #
247- # We need the list of known good attribute files, so we can delete any that are
248- # just laying about.
249-
250- cache_filename = File . join ( "cookbooks" , cookbook . name , manifest_record [ 'path' ] )
251- valid_cache_entries [ cache_filename ] = true
252-
253- current_checksum = nil
254- if Chef ::FileCache . has_key? ( cache_filename )
255- current_checksum = Chef ::CookbookVersion . checksum_cookbook_file ( Chef ::FileCache . load ( cache_filename , false ) )
256- end
257-
258- # If the checksums are different between on-disk (current) and on-server
259- # (remote, per manifest), do the update. This will also execute if there
260- # is no current checksum.
261- if current_checksum != manifest_record [ 'checksum' ]
262- raw_file = rest . get_rest ( manifest_record [ :url ] , true )
263-
264- Chef ::Log . info ( "Storing updated #{ cache_filename } in the cache." )
265- Chef ::FileCache . move_to ( raw_file . path , cache_filename )
266- else
267- Chef ::Log . debug ( "Not storing #{ cache_filename } , as the cache is up to date." )
268- end
269-
270- # make the segment filenames a full path.
271- full_path_cache_filename = Chef ::FileCache . load ( cache_filename , false )
272- segment_filenames << full_path_cache_filename
273- end
274-
275- # replace segment filenames with a full-path one.
276- if segment . to_sym == :recipes
277- cookbook . recipe_filenames = segment_filenames
278- elsif segment . to_sym == :attributes
279- cookbook . attribute_filenames = segment_filenames
280- else
281- cookbook . segment_filenames ( segment ) . replace ( segment_filenames )
282- end
283- end
284- end
285-
286- def cleanup_file_cache ( valid_cache_entries )
287- # Delete each file in the cache that we didn't encounter in the
288- # manifest.
289- Chef ::FileCache . find ( File . join ( %w{ cookbooks ** * } ) ) . each do |cache_filename |
290- unless valid_cache_entries [ cache_filename ]
291- Chef ::Log . info ( "Removing #{ cache_filename } from the cache; it is no longer on the server." )
292- Chef ::FileCache . delete ( cache_filename )
293- end
294- end
295- end
296-
297260 # Converges the node.
298261 #
299262 # === Returns
0 commit comments