class Puppet::Configurer

Attributes

environment[R]

Public Class Methods

new(transaction_uuid = nil, job_id = nil) click to toggle source
   # File lib/puppet/configurer.rb
53 def initialize(transaction_uuid = nil, job_id = nil)
54   @running = false
55   @splayed = false
56   @running_failure = false
57   @cached_catalog_status = 'not_used'
58   @environment = Puppet[:environment]
59   @transaction_uuid = transaction_uuid || SecureRandom.uuid
60   @job_id = job_id
61   @static_catalog = true
62   @checksum_type = Puppet[:supported_checksum_types]
63   @handler = Puppet::Configurer::PluginHandler.new()
64 end
should_pluginsync?() click to toggle source
   # File lib/puppet/configurer.rb
24 def self.should_pluginsync?
25   if Puppet[:use_cached_catalog]
26     false
27   else
28     true
29   end
30 end
to_s() click to toggle source

Provide more helpful strings to the logging that the Agent does

   # File lib/puppet/configurer.rb
20 def self.to_s
21   _("Puppet configuration client")
22 end

Public Instance Methods

apply_catalog(catalog, options) click to toggle source

Apply supplied catalog and return associated application report

    # File lib/puppet/configurer.rb
277 def apply_catalog(catalog, options)
278   report = options[:report]
279   report.configuration_version = catalog.version
280 
281   benchmark(:notice, _("Applied catalog in %{seconds} seconds")) do
282     apply_catalog_time = thinmark do
283       catalog.apply(options)
284     end
285     options[:report].add_times(:catalog_application, apply_catalog_time)
286   end
287 
288   report
289 end
check_fact_name_length(name, number_of_dots) click to toggle source
    # File lib/puppet/configurer.rb
149 def check_fact_name_length(name, number_of_dots)
150   max_length = Puppet[:fact_name_length_soft_limit]
151   return if max_length.zero?
152 
153   # rough byte size estimations of fact path as a postgresql btree index
154   size_as_btree_index = 8 + (number_of_dots * 2) + name.to_s.bytesize
155   warn_fact_name_length(name, max_length) if size_as_btree_index > max_length
156 end
check_fact_values_length(values) click to toggle source
    # File lib/puppet/configurer.rb
158 def check_fact_values_length(values)
159   max_length = Puppet[:fact_value_length_soft_limit]
160   return if max_length.zero?
161 
162   warn_fact_value_length(values, max_length) if values.to_s.bytesize > max_length
163 end
check_facts_limits(facts) click to toggle source
    # File lib/puppet/configurer.rb
208 def check_facts_limits(facts)
209   @number_of_facts = 0
210   check_top_level_number_limit(facts.size)
211 
212   parse_fact_name_and_value_limits(facts)
213   check_total_number_limit(@number_of_facts)
214   Puppet.debug _("The total number of facts registered is %{number_of_facts}") % {number_of_facts: @number_of_facts}
215 end
check_payload_size(payload) click to toggle source
    # File lib/puppet/configurer.rb
179 def check_payload_size(payload)
180   max_size = Puppet[:payload_soft_limit]
181   return if max_size.zero?
182 
183   warn_fact_payload_size(payload, max_size) if payload > max_size
184   Puppet.debug _("The size of the payload is %{payload}") % {payload: payload}
185 end
check_top_level_number_limit(size) click to toggle source
    # File lib/puppet/configurer.rb
165 def check_top_level_number_limit(size)
166   max_size = Puppet[:top_level_facts_soft_limit]
167   return if max_size.zero?
168 
169   warn_number_of_top_level_facts(size, max_size) if size > max_size
170 end
check_total_number_limit(size) click to toggle source
    # File lib/puppet/configurer.rb
172 def check_total_number_limit(size)
173   max_size = Puppet[:number_of_facts_soft_limit]
174   return if max_size.zero?
175 
176   warn_number_of_facts(size, max_size) if size > max_size
177 end
convert_catalog(result, duration, facts, options = {}) click to toggle source

Convert a plain resource catalog into our full host catalog.

    # File lib/puppet/configurer.rb
109 def convert_catalog(result, duration, facts, options = {})
110   catalog = nil
111 
112   catalog_conversion_time = thinmark do
113     # Will mutate the result and replace all Deferred values with resolved values
114     if facts
115       Puppet::Pops::Evaluator::DeferredResolver.resolve_and_replace(facts, result, Puppet.lookup(:current_environment))
116     end
117 
118     catalog = result.to_ral
119     catalog.finalize
120     catalog.retrieval_duration = duration
121     catalog.write_class_file
122     catalog.write_resource_file
123   end
124   options[:report].add_times(:convert_catalog, catalog_conversion_time) if options[:report]
125 
126   catalog
127 end
execute_postrun_command() click to toggle source
   # File lib/puppet/configurer.rb
32 def execute_postrun_command
33   execute_from_setting(:postrun_command)
34 end
execute_prerun_command() click to toggle source
   # File lib/puppet/configurer.rb
36 def execute_prerun_command
37   execute_from_setting(:prerun_command)
38 end
get_facts(options) click to toggle source
    # File lib/puppet/configurer.rb
217 def get_facts(options)
218   if options[:pluginsync]
219     plugin_sync_time = thinmark do
220       remote_environment_for_plugins = Puppet::Node::Environment.remote(@environment)
221       download_plugins(remote_environment_for_plugins)
222 
223       Puppet::GettextConfig.reset_text_domain('agent')
224       Puppet::ModuleTranslations.load_from_vardir(Puppet[:vardir])
225     end
226     options[:report].add_times(:plugin_sync, plugin_sync_time) if options[:report]
227   end
228 
229   facts_hash = {}
230   facts = nil
231   if Puppet::Resource::Catalog.indirection.terminus_class == :rest
232     # This is a bit complicated.  We need the serialized and escaped facts,
233     # and we need to know which format they're encoded in.  Thus, we
234     # get a hash with both of these pieces of information.
235     #
236     # facts_for_uploading may set Puppet[:node_name_value] as a side effect
237     facter_time = thinmark do
238       facts = find_facts
239       check_facts_limits(facts.to_data_hash['values'])
240       facts_hash = encode_facts(facts) # encode for uploading # was: facts_for_uploading
241       check_payload_size(facts_hash[:facts].bytesize)
242     end
243     options[:report].add_times(:fact_generation, facter_time) if options[:report]
244   end
245   [facts_hash, facts]
246 end
init_storage() click to toggle source

Initialize and load storage

   # File lib/puppet/configurer.rb
41 def init_storage
42     Puppet::Util::Storage.load
43 rescue => detail
44   Puppet.log_exception(detail, _("Removing corrupt state file %{file}: %{detail}") % { file: Puppet[:statefile], detail: detail })
45   begin
46     Puppet::FileSystem.unlink(Puppet[:statefile])
47     retry
48   rescue => detail
49     raise Puppet::Error.new(_("Cannot remove %{file}: %{detail}") % { file: Puppet[:statefile], detail: detail }, detail)
50   end
51 end
parse_fact_name_and_value_limits(object, path = []) click to toggle source
    # File lib/puppet/configurer.rb
187 def parse_fact_name_and_value_limits(object, path = [])
188   case object
189   when Hash
190     object.each do |key, value|
191       path.push(key)
192       parse_fact_name_and_value_limits(value, path)
193       path.pop
194       @number_of_facts += 1
195     end
196   when Array
197     object.each_with_index do |e, idx|
198       path.push(idx)
199       parse_fact_name_and_value_limits(e, path)
200       path.pop
201     end
202   else
203     check_fact_name_length(path.join(), path.size)
204     check_fact_values_length(object)
205   end
206 end
prepare_and_retrieve_catalog(cached_catalog, facts, options, query_options) click to toggle source
    # File lib/puppet/configurer.rb
248 def prepare_and_retrieve_catalog(cached_catalog, facts, options, query_options)
249   # set report host name now that we have the fact
250   options[:report].host = Puppet[:node_name_value]
251 
252   query_options[:transaction_uuid] = @transaction_uuid
253   query_options[:job_id] = @job_id
254   query_options[:static_catalog] = @static_catalog
255 
256   # Query params don't enforce ordered evaluation, so munge this list into a
257   # dot-separated string.
258   query_options[:checksum_type] = @checksum_type.join('.')
259 
260   # apply passes in ral catalog
261   catalog = cached_catalog || options[:catalog]
262   unless catalog
263     # retrieve_catalog returns resource catalog
264     catalog = retrieve_catalog(facts, query_options)
265     Puppet.err _("Could not retrieve catalog; skipping run") unless catalog
266   end
267   catalog
268 end
prepare_and_retrieve_catalog_from_cache(options = {}) click to toggle source
    # File lib/puppet/configurer.rb
270 def prepare_and_retrieve_catalog_from_cache(options = {})
271   result = retrieve_catalog_from_cache({:transaction_uuid => @transaction_uuid, :static_catalog => @static_catalog})
272   Puppet.info _("Using cached catalog from environment '%{catalog_env}'") % { catalog_env: result.environment } if result
273   result
274 end
resubmit_facts() click to toggle source

Submit updated facts to the Puppet Server

This method will clear all current fact values, load a fresh set of fact data, and then submit it to the Puppet Server.

@return [true] If fact submission succeeds. @return [false] If an exception is raised during fact generation or

submission.
    # File lib/puppet/configurer.rb
606 def resubmit_facts
607   ::Facter.clear
608   facts = find_facts
609 
610   client = Puppet.runtime[:http]
611   session = client.create_session
612   puppet = session.route_to(:puppet)
613 
614   Puppet.info(_("Uploading facts for %{node} to %{server}") % {
615                 node: facts.name,
616                 server: puppet.url.hostname})
617 
618   puppet.put_facts(facts.name, facts: facts, environment: Puppet.lookup(:current_environment).name.to_s)
619 
620   return true
621 rescue => detail
622   Puppet.log_exception(detail, _("Failed to submit facts: %{detail}") %
623                                { detail: detail })
624 
625   return false
626 end
retrieve_catalog(facts, query_options) click to toggle source

Get the remote catalog, yo. Returns nil if no catalog can be found.

    # File lib/puppet/configurer.rb
 67 def retrieve_catalog(facts, query_options)
 68   query_options ||= {}
 69   if Puppet[:use_cached_catalog] || @running_failure
 70     result = retrieve_catalog_from_cache(query_options)
 71   end
 72 
 73   if result
 74     if Puppet[:use_cached_catalog]
 75       @cached_catalog_status = 'explicitly_requested'
 76     elsif @running_failure
 77       @cached_catalog_status = 'on_failure'
 78     end
 79 
 80     Puppet.info _("Using cached catalog from environment '%{environment}'") % { environment: result.environment }
 81   else
 82     result = retrieve_new_catalog(facts, query_options)
 83 
 84     if !result
 85       if !Puppet[:usecacheonfailure]
 86         Puppet.warning _("Not using cache on failed catalog")
 87         return nil
 88       end
 89 
 90       result = retrieve_catalog_from_cache(query_options)
 91 
 92       if result
 93         # don't use use cached catalog if it doesn't match server specified environment
 94         if result.environment != @environment
 95           Puppet.err _("Not using cached catalog because its environment '%{catalog_env}' does not match '%{local_env}'") % { catalog_env: result.environment, local_env: @environment }
 96           return nil
 97         end
 98 
 99         @cached_catalog_status = 'on_failure'
100         Puppet.info _("Using cached catalog from environment '%{catalog_env}'") % { catalog_env: result.environment }
101       end
102     end
103   end
104 
105   result
106 end
run(options = {}) click to toggle source

The code that actually runs the catalog. This just passes any options on to the catalog, which accepts :tags and :ignoreschedules.

    # File lib/puppet/configurer.rb
294 def run(options = {})
295   # We create the report pre-populated with default settings for
296   # environment and transaction_uuid very early, this is to ensure
297   # they are sent regardless of any catalog compilation failures or
298   # exceptions.
299   options[:report] ||= Puppet::Transaction::Report.new(nil, @environment, @transaction_uuid, @job_id, options[:start_time] || Time.now)
300   report = options[:report]
301   init_storage
302 
303   Puppet::Util::Log.newdestination(report)
304 
305   completed = nil
306   begin
307     # Skip failover logic if the server_list setting is empty
308     do_failover = Puppet.settings[:server_list] && !Puppet.settings[:server_list].empty?
309 
310     # When we are passed a catalog, that means we're in apply
311     # mode. We shouldn't try to do any failover in that case.
312     if options[:catalog].nil? && do_failover
313       server, port = find_functional_server
314       if server.nil?
315         detail = _("Could not select a functional puppet server from server_list: '%{server_list}'") % { server_list: Puppet.settings.value(:server_list, Puppet[:environment].to_sym, true) }
316         if Puppet[:usecacheonfailure]
317           options[:pluginsync] = false
318           @running_failure = true
319 
320           server = Puppet[:server_list].first[0]
321           port = Puppet[:server_list].first[1] || Puppet[:serverport]
322 
323           Puppet.err(detail)
324         else
325           raise Puppet::Error, detail
326         end
327       else
328         #TRANSLATORS 'server_list' is the name of a setting and should not be translated
329         Puppet.debug _("Selected puppet server from the `server_list` setting: %{server}:%{port}") % { server: server, port: port }
330         report.server_used = "#{server}:#{port}"
331       end
332       Puppet.override(server: server, serverport: port) do
333         completed = run_internal(options)
334       end
335     else
336       completed = run_internal(options)
337     end
338   ensure
339     # we may sleep for awhile, close connections now
340     Puppet.runtime[:http].close
341   end
342 
343   completed ? report.exit_status : nil
344 end
save_last_run_summary(report) click to toggle source
    # File lib/puppet/configurer.rb
589 def save_last_run_summary(report)
590   mode = Puppet.settings.setting(:lastrunfile).mode
591   Puppet::Util.replace_file(Puppet[:lastrunfile], mode) do |fh|
592     fh.print YAML.dump(report.raw_summary)
593   end
594 rescue => detail
595   Puppet.log_exception(detail, _("Could not save last run local report: %{detail}") % { detail: detail })
596 end
send_report(report) click to toggle source
    # File lib/puppet/configurer.rb
581 def send_report(report)
582   puts report.summary if Puppet[:summarize]
583   save_last_run_summary(report)
584   Puppet::Transaction::Report.indirection.save(report, nil, :environment => Puppet::Node::Environment.remote(@environment)) if Puppet[:report]
585 rescue => detail
586   Puppet.log_exception(detail, _("Could not send report: %{detail}") % { detail: detail })
587 end
valid_server_environment?() click to toggle source
    # File lib/puppet/configurer.rb
528 def valid_server_environment?
529   session = Puppet.lookup(:http_session)
530   begin
531     fs = session.route_to(:fileserver)
532     fs.get_file_metadatas(path: URI(Puppet[:pluginsource]).path, recurse: :false, environment: @environment)
533     true
534   rescue Puppet::HTTP::ResponseError => detail
535     if detail.response.code == 404
536       Puppet.notice(_("Environment '%{environment}' not found on server, skipping initial pluginsync.") % { environment: @environment })
537     else
538       Puppet.log_exception(detail, detail.message)
539     end
540     false
541   rescue => detail
542     Puppet.log_exception(detail, detail.message)
543     false
544   end
545 end
warn_fact_name_length(name, max_length) click to toggle source
    # File lib/puppet/configurer.rb
133 def warn_fact_name_length(name, max_length)
134   Puppet.warning _("Fact %{name} with length: '%{length}' exceeds the length limit: %{limit}") % { name: name, length: name.to_s.bytesize, limit: max_length }
135 end
warn_fact_payload_size(payload, max_size) click to toggle source
    # File lib/puppet/configurer.rb
145 def warn_fact_payload_size(payload, max_size)
146   Puppet.warning _("Payload with the current size of: '%{payload}' exceeds the payload size limit: %{max_size}") % { payload: payload, max_size: max_size }
147 end
warn_fact_value_length(value, max_length) click to toggle source
    # File lib/puppet/configurer.rb
141 def warn_fact_value_length(value, max_length)
142   Puppet.warning _("Fact value '%{value}' with the value length: '%{length}' exceeds the value length limit: %{max_length}") % { value: value, length:value.to_s.bytesize, max_length: max_length }
143 end
warn_number_of_facts(size, max_number) click to toggle source
    # File lib/puppet/configurer.rb
129 def warn_number_of_facts(size, max_number)
130   Puppet.warning _("The current total number of facts: %{size} exceeds the number of facts limit: %{max_size}") % { size: size, max_size: max_number }
131 end
warn_number_of_top_level_facts(size, max_number) click to toggle source
    # File lib/puppet/configurer.rb
137 def warn_number_of_top_level_facts(size, max_number)
138   Puppet.warning _("The current number of top level facts: %{size} exceeds the top facts limit: %{max_size}") % { size: size, max_size: max_number }
139 end

Private Instance Methods

download_plugins(remote_environment_for_plugins) click to toggle source
    # File lib/puppet/configurer.rb
681 def download_plugins(remote_environment_for_plugins)
682   begin
683     @handler.download_plugins(remote_environment_for_plugins)
684   rescue Puppet::Error => detail
685     if !Puppet[:ignore_plugin_errors] && Puppet[:usecacheonfailure]
686       @running_failure = true
687     else
688       raise detail
689     end
690   end
691 end
execute_from_setting(setting) click to toggle source
    # File lib/puppet/configurer.rb
630 def execute_from_setting(setting)
631   return true if (command = Puppet[setting]) == ""
632 
633   begin
634     Puppet::Util::Execution.execute([command])
635     true
636   rescue => detail
637     Puppet.log_exception(detail, _("Could not run command from %{setting}: %{detail}") % { setting: setting, detail: detail })
638     false
639   end
640 end
find_functional_server() click to toggle source
    # File lib/puppet/configurer.rb
547 def find_functional_server
548   begin
549     session = Puppet.lookup(:http_session)
550     service = session.route_to(:puppet)
551     return [service.url.host, service.url.port]
552   rescue Puppet::HTTP::ResponseError => e
553     Puppet.debug(_("Puppet server %{host}:%{port} is unavailable: %{code} %{reason}") %
554                  { host: e.response.url.host, port: e.response.url.port, code: e.response.code, reason: e.response.reason })
555   rescue => detail
556     #TRANSLATORS 'server_list' is the name of a setting and should not be translated
557     Puppet.debug _("Unable to connect to server from server_list setting: %{detail}") % {detail: detail}
558   end
559   [nil, nil]
560 end
last_server_specified_environment() click to toggle source
    # File lib/puppet/configurer.rb
563 def last_server_specified_environment
564   return @last_server_specified_environment if @last_server_specified_environment
565   if Puppet::FileSystem.exist?(Puppet[:lastrunfile])
566     summary = Puppet::Util::Yaml.safe_load_file(Puppet[:lastrunfile])
567     return unless summary.dig('application', 'run_mode') == 'agent'
568     initial_environment = summary.dig('application', 'initial_environment')
569     converged_environment = summary.dig('application', 'converged_environment')
570     @last_server_specified_environment = converged_environment if initial_environment != converged_environment
571   end
572 
573   Puppet.debug(_("Found last server-specified environment: %{environment}") % { environment: @last_server_specified_environment }) if @last_server_specified_environment
574   @last_server_specified_environment
575 rescue => detail
576   Puppet.debug(_("Could not find last server-specified environment: %{detail}") % { detail: detail })
577   nil
578 end
retrieve_catalog_from_cache(query_options) click to toggle source
    # File lib/puppet/configurer.rb
642 def retrieve_catalog_from_cache(query_options)
643   result = nil
644   @duration = thinmark do
645     result = Puppet::Resource::Catalog.indirection.find(
646       Puppet[:node_name_value],
647       query_options.merge(
648         :ignore_terminus => true,
649         :environment     => Puppet::Node::Environment.remote(@environment)
650       )
651     )
652   end
653   result
654 rescue => detail
655   Puppet.log_exception(detail, _("Could not retrieve catalog from cache: %{detail}") % { detail: detail })
656   return nil
657 end
retrieve_new_catalog(facts, query_options) click to toggle source
    # File lib/puppet/configurer.rb
659 def retrieve_new_catalog(facts, query_options)
660   result = nil
661   @duration = thinmark do
662     result = Puppet::Resource::Catalog.indirection.find(
663       Puppet[:node_name_value],
664       query_options.merge(
665         :ignore_cache      => true,
666         # don't update cache until after environment converges
667         :ignore_cache_save => true,
668         :environment       => Puppet::Node::Environment.remote(@environment),
669         :check_environment => true,
670         :fail_on_404       => true,
671         :facts_for_catalog => facts
672       )
673     )
674   end
675   result
676 rescue StandardError => detail
677   Puppet.log_exception(detail, _("Could not retrieve catalog from remote server: %{detail}") % { detail: detail })
678   return nil
679 end
run_internal(options) click to toggle source
    # File lib/puppet/configurer.rb
346 def run_internal(options)
347   report = options[:report]
348   report.initial_environment = Puppet[:environment]
349 
350   if options[:start_time]
351     startup_time = Time.now - options[:start_time]
352     report.add_times(:startup_time, startup_time)
353   end
354 
355   # If a cached catalog is explicitly requested, attempt to retrieve it. Skip the node request,
356   # don't pluginsync and switch to the catalog's environment if we successfully retrieve it.
357   if Puppet[:use_cached_catalog]
358     Puppet::GettextConfig.reset_text_domain('agent')
359     Puppet::ModuleTranslations.load_from_vardir(Puppet[:vardir])
360 
361     cached_catalog = prepare_and_retrieve_catalog_from_cache(options)
362     if cached_catalog
363       @cached_catalog_status = 'explicitly_requested'
364 
365       if @environment != cached_catalog.environment && !Puppet[:strict_environment_mode]
366         Puppet.notice _("Local environment: '%{local_env}' doesn't match the environment of the cached catalog '%{catalog_env}', switching agent to '%{catalog_env}'.") % { local_env: @environment, catalog_env: cached_catalog.environment }
367         @environment = cached_catalog.environment
368       end
369 
370       report.environment = @environment
371     else
372       # Don't try to retrieve a catalog from the cache again after we've already
373       # failed to do so the first time.
374       Puppet[:use_cached_catalog] = false
375       Puppet[:usecacheonfailure] = false
376       options[:pluginsync] = Puppet::Configurer.should_pluginsync?
377     end
378   end
379 
380   begin
381     unless Puppet[:node_name_fact].empty?
382       query_options, facts = get_facts(options)
383     end
384 
385     configured_environment = Puppet[:environment] if Puppet.settings.set_by_config?(:environment)
386 
387     # We only need to find out the environment to run in if we don't already have a catalog
388     unless (cached_catalog || options[:catalog] || Puppet.settings.set_by_cli?(:environment) || Puppet[:strict_environment_mode])
389       Puppet.debug(_("Environment not passed via CLI and no catalog was given, attempting to find out the last server-specified environment"))
390       if last_server_specified_environment
391         @environment = last_server_specified_environment
392         report.environment = last_server_specified_environment
393       else
394         Puppet.debug(_("Could not find a usable environment in the lastrunfile. Either the file does not exist, does not have the required keys, or the values of 'initial_environment' and 'converged_environment' are identical."))
395       end
396     end
397 
398     Puppet.info _("Using environment '%{env}'") % { env: @environment }
399 
400     # This is to maintain compatibility with anyone using this class
401     # aside from agent, apply, device.
402     unless Puppet.lookup(:loaders) { nil }
403       new_env = Puppet::Node::Environment.remote(@environment)
404       Puppet.push_context(
405         {
406           current_environment: new_env,
407           loaders: Puppet::Pops::Loaders.new(new_env, true)
408         },
409         "Local node environment #{@environment} for configurer transaction"
410       )
411     end
412 
413     temp_value = options[:pluginsync]
414 
415     # only validate server environment if pluginsync is requested
416     options[:pluginsync] = valid_server_environment? if options[:pluginsync] == true
417 
418     query_options, facts = get_facts(options) unless query_options
419     options[:pluginsync] = temp_value
420 
421     query_options[:configured_environment] = configured_environment
422 
423     catalog = prepare_and_retrieve_catalog(cached_catalog, facts, options, query_options)
424     unless catalog
425       return nil
426     end
427 
428     if Puppet[:strict_environment_mode] && catalog.environment != @environment
429       Puppet.err _("Not using catalog because its environment '%{catalog_env}' does not match agent specified environment '%{local_env}' and strict_environment_mode is set") % { catalog_env: catalog.environment, local_env: @environment }
430       return nil
431     end
432 
433     # Here we set the local environment based on what we get from the
434     # catalog. Since a change in environment means a change in facts, and
435     # facts may be used to determine which catalog we get, we need to
436     # rerun the process if the environment is changed.
437     tries = 0
438     while catalog.environment and not catalog.environment.empty? and catalog.environment != @environment
439       if tries > 3
440         raise Puppet::Error, _("Catalog environment didn't stabilize after %{tries} fetches, aborting run") % { tries: tries }
441       end
442       Puppet.notice _("Local environment: '%{local_env}' doesn't match server specified environment '%{catalog_env}', restarting agent run with environment '%{catalog_env}'") % { local_env: @environment, catalog_env: catalog.environment }
443       @environment = catalog.environment
444       report.environment = @environment
445 
446       new_env = Puppet::Node::Environment.remote(@environment)
447       Puppet.push_context(
448         {
449           :current_environment => new_env,
450           :loaders => Puppet::Pops::Loaders.new(new_env, true)
451         },
452         "Local node environment #{@environment} for configurer transaction"
453       )
454 
455       query_options, facts = get_facts(options)
456       query_options[:configured_environment] = configured_environment
457 
458       # if we get here, ignore the cached catalog
459       catalog = prepare_and_retrieve_catalog(nil, facts, options, query_options)
460       return nil unless catalog
461       tries += 1
462     end
463 
464     # now that environment has converged, convert resource catalog into ral catalog
465     # unless we were given a RAL catalog
466     if !cached_catalog && options[:catalog]
467       ral_catalog = options[:catalog]
468     else
469       # Ordering here matters. We have to resolve deferred resources in the
470       # resource catalog, convert the resource catalog to a RAL catalog (which
471       # triggers type/provider validation), and only if that is successful,
472       # should we cache the *original* resource catalog. However, deferred
473       # evaluation mutates the resource catalog, so we need to make a copy of
474       # it here. If PUP-9323 is ever implemented so that we resolve deferred
475       # resources in the RAL catalog as they are needed, then we could eliminate
476       # this step.
477       catalog_to_cache = Puppet.override(:rich_data => Puppet[:rich_data]) do
478         Puppet::Resource::Catalog.from_data_hash(catalog.to_data_hash)
479       end
480 
481       # REMIND @duration is the time spent loading the last catalog, and doesn't
482       # account for things like we failed to download and fell back to the cache
483       ral_catalog = convert_catalog(catalog, @duration, facts, options)
484 
485       # Validation succeeded, so commit the `catalog_to_cache` for non-noop runs. Don't
486       # commit `catalog` since it contains the result of deferred evaluation. Ideally
487       # we'd just copy the downloaded response body, instead of serializing the
488       # in-memory catalog, but that's hard due to the indirector.
489       indirection = Puppet::Resource::Catalog.indirection
490       if !Puppet[:noop] && indirection.cache?
491         request = indirection.request(:save, nil, catalog_to_cache, environment: Puppet::Node::Environment.remote(catalog_to_cache.environment))
492         Puppet.info("Caching catalog for #{request.key}")
493         indirection.cache.save(request)
494       end
495     end
496 
497     execute_prerun_command or return nil
498 
499     options[:report].code_id = ral_catalog.code_id
500     options[:report].catalog_uuid = ral_catalog.catalog_uuid
501     options[:report].cached_catalog_status = @cached_catalog_status
502     apply_catalog(ral_catalog, options)
503     true
504   rescue => detail
505     Puppet.log_exception(detail, _("Failed to apply catalog: %{detail}") % { detail: detail })
506     return nil
507   ensure
508     execute_postrun_command or return nil
509   end
510 ensure
511   if Puppet[:resubmit_facts]
512     # TODO: Should mark the report as "failed" if an error occurs and
513     #       resubmit_facts returns false. There is currently no API for this.
514     resubmit_facts_time = thinmark { resubmit_facts }
515 
516     report.add_times(:resubmit_facts, resubmit_facts_time)
517   end
518 
519   report.cached_catalog_status ||= @cached_catalog_status
520   report.add_times(:total, Time.now - report.time)
521   report.finalize_report
522   Puppet::Util::Log.close(report)
523   send_report(report)
524   Puppet.pop_context
525 end