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

Provide more helpful strings to the logging that the Agent does

   # File lib/puppet/configurer.rb
21 def self.to_s
22   _("Puppet configuration client")
23 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
281 def apply_catalog(catalog, options)
282   report = options[:report]
283   report.configuration_version = catalog.version
284 
285   benchmark(:notice, _("Applied catalog in %{seconds} seconds")) do
286     apply_catalog_time = thinmark do
287       catalog.apply(options)
288     end
289     options[:report].add_times(:catalog_application, apply_catalog_time)
290   end
291 
292   report
293 end
check_fact_name_length(name, number_of_dots) click to toggle source
    # File lib/puppet/configurer.rb
153 def check_fact_name_length(name, number_of_dots)
154   max_length = Puppet[:fact_name_length_soft_limit]
155   return if max_length.zero?
156 
157   # rough byte size estimations of fact path as a postgresql btree index
158   size_as_btree_index = 8 + (number_of_dots * 2) + name.to_s.bytesize
159   warn_fact_name_length(name, max_length) if size_as_btree_index > max_length
160 end
check_fact_values_length(values) click to toggle source
    # File lib/puppet/configurer.rb
162 def check_fact_values_length(values)
163   max_length = Puppet[:fact_value_length_soft_limit]
164   return if max_length.zero?
165 
166   warn_fact_value_length(values, max_length) if values.to_s.bytesize > max_length
167 end
check_facts_limits(facts) click to toggle source
    # File lib/puppet/configurer.rb
212 def check_facts_limits(facts)
213   @number_of_facts = 0
214   check_top_level_number_limit(facts.size)
215 
216   parse_fact_name_and_value_limits(facts)
217   check_total_number_limit(@number_of_facts)
218   Puppet.debug _("The total number of facts registered is %{number_of_facts}") % {number_of_facts: @number_of_facts}
219 end
check_payload_size(payload) click to toggle source
    # File lib/puppet/configurer.rb
183 def check_payload_size(payload)
184   max_size = Puppet[:payload_soft_limit]
185   return if max_size.zero?
186 
187   warn_fact_payload_size(payload, max_size) if payload > max_size
188   Puppet.debug _("The size of the payload is %{payload}") % {payload: payload}
189 end
check_top_level_number_limit(size) click to toggle source
    # File lib/puppet/configurer.rb
169 def check_top_level_number_limit(size)
170   max_size = Puppet[:top_level_facts_soft_limit]
171   return if max_size.zero?
172 
173   warn_number_of_top_level_facts(size, max_size) if size > max_size
174 end
check_total_number_limit(size) click to toggle source
    # File lib/puppet/configurer.rb
176 def check_total_number_limit(size)
177   max_size = Puppet[:number_of_facts_soft_limit]
178   return if max_size.zero?
179 
180   warn_number_of_facts(size, max_size) if size > max_size
181 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
110 def convert_catalog(result, duration, facts, options = {})
111   catalog = nil
112 
113   catalog_conversion_time = thinmark do
114     # Will mutate the result and replace all Deferred values with resolved values
115     if facts
116       Puppet::Pops::Evaluator::DeferredResolver.resolve_and_replace(facts, result, Puppet.lookup(:current_environment), Puppet[:preprocess_deferred])
117     end
118 
119     catalog = result.to_ral
120     catalog.finalize
121     catalog.retrieval_duration = duration
122 
123     if Puppet[:write_catalog_summary]
124       catalog.write_class_file
125       catalog.write_resource_file
126     end
127   end
128   options[:report].add_times(:convert_catalog, catalog_conversion_time) if options[:report]
129 
130   catalog
131 end
execute_postrun_command() click to toggle source
   # File lib/puppet/configurer.rb
33 def execute_postrun_command
34   execute_from_setting(:postrun_command)
35 end
execute_prerun_command() click to toggle source
   # File lib/puppet/configurer.rb
37 def execute_prerun_command
38   execute_from_setting(:prerun_command)
39 end
get_facts(options) click to toggle source
    # File lib/puppet/configurer.rb
221 def get_facts(options)
222   if options[:pluginsync]
223     plugin_sync_time = thinmark do
224       remote_environment_for_plugins = Puppet::Node::Environment.remote(@environment)
225       download_plugins(remote_environment_for_plugins)
226 
227       Puppet::GettextConfig.reset_text_domain('agent')
228       Puppet::ModuleTranslations.load_from_vardir(Puppet[:vardir])
229     end
230     options[:report].add_times(:plugin_sync, plugin_sync_time) if options[:report]
231   end
232 
233   facts_hash = {}
234   facts = nil
235   if Puppet::Resource::Catalog.indirection.terminus_class == :rest
236     # This is a bit complicated.  We need the serialized and escaped facts,
237     # and we need to know which format they're encoded in.  Thus, we
238     # get a hash with both of these pieces of information.
239     #
240     # facts_for_uploading may set Puppet[:node_name_value] as a side effect
241     facter_time = thinmark do
242       facts = find_facts
243       check_facts_limits(facts.to_data_hash['values'])
244       facts_hash = encode_facts(facts) # encode for uploading # was: facts_for_uploading
245       check_payload_size(facts_hash[:facts].bytesize)
246     end
247     options[:report].add_times(:fact_generation, facter_time) if options[:report]
248   end
249   [facts_hash, facts]
250 end
init_storage() click to toggle source

Initialize and load storage

   # File lib/puppet/configurer.rb
42 def init_storage
43     Puppet::Util::Storage.load
44 rescue => detail
45   Puppet.log_exception(detail, _("Removing corrupt state file %{file}: %{detail}") % { file: Puppet[:statefile], detail: detail })
46   begin
47     Puppet::FileSystem.unlink(Puppet[:statefile])
48     retry
49   rescue => detail
50     raise Puppet::Error.new(_("Cannot remove %{file}: %{detail}") % { file: Puppet[:statefile], detail: detail }, detail)
51   end
52 end
parse_fact_name_and_value_limits(object, path = []) click to toggle source
    # File lib/puppet/configurer.rb
191 def parse_fact_name_and_value_limits(object, path = [])
192   case object
193   when Hash
194     object.each do |key, value|
195       path.push(key)
196       parse_fact_name_and_value_limits(value, path)
197       path.pop
198       @number_of_facts += 1
199     end
200   when Array
201     object.each_with_index do |e, idx|
202       path.push(idx)
203       parse_fact_name_and_value_limits(e, path)
204       path.pop
205     end
206   else
207     check_fact_name_length(path.join(), path.size)
208     check_fact_values_length(object)
209   end
210 end
prepare_and_retrieve_catalog(cached_catalog, facts, options, query_options) click to toggle source
    # File lib/puppet/configurer.rb
252 def prepare_and_retrieve_catalog(cached_catalog, facts, options, query_options)
253   # set report host name now that we have the fact
254   options[:report].host = Puppet[:node_name_value]
255 
256   query_options[:transaction_uuid] = @transaction_uuid
257   query_options[:job_id] = @job_id
258   query_options[:static_catalog] = @static_catalog
259 
260   # Query params don't enforce ordered evaluation, so munge this list into a
261   # dot-separated string.
262   query_options[:checksum_type] = @checksum_type.join('.')
263 
264   # apply passes in ral catalog
265   catalog = cached_catalog || options[:catalog]
266   unless catalog
267     # retrieve_catalog returns resource catalog
268     catalog = retrieve_catalog(facts, query_options)
269     Puppet.err _("Could not retrieve catalog; skipping run") unless catalog
270   end
271   catalog
272 end
prepare_and_retrieve_catalog_from_cache(options = {}) click to toggle source
    # File lib/puppet/configurer.rb
274 def prepare_and_retrieve_catalog_from_cache(options = {})
275   result = retrieve_catalog_from_cache({:transaction_uuid => @transaction_uuid, :static_catalog => @static_catalog})
276   Puppet.info _("Using cached catalog from environment '%{catalog_env}'") % { catalog_env: result.environment } if result
277   result
278 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
659 def resubmit_facts
660   Puppet.runtime[:facter].clear
661   facts = find_facts
662 
663   client = Puppet.runtime[:http]
664   session = client.create_session
665   puppet = session.route_to(:puppet)
666 
667   Puppet.info(_("Uploading facts for %{node} to %{server}") % {
668                 node: facts.name,
669                 server: puppet.url.hostname})
670 
671   puppet.put_facts(facts.name, facts: facts, environment: Puppet.lookup(:current_environment).name.to_s)
672 
673   return true
674 rescue => detail
675   Puppet.log_exception(detail, _("Failed to submit facts: %{detail}") %
676                                { detail: detail })
677 
678   return false
679 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
 68 def retrieve_catalog(facts, query_options)
 69   query_options ||= {}
 70   if Puppet[:use_cached_catalog] || @running_failure
 71     result = retrieve_catalog_from_cache(query_options)
 72   end
 73 
 74   if result
 75     if Puppet[:use_cached_catalog]
 76       @cached_catalog_status = 'explicitly_requested'
 77     elsif @running_failure
 78       @cached_catalog_status = 'on_failure'
 79     end
 80 
 81     Puppet.info _("Using cached catalog from environment '%{environment}'") % { environment: result.environment }
 82   else
 83     result = retrieve_new_catalog(facts, query_options)
 84 
 85     if !result
 86       if !Puppet[:usecacheonfailure]
 87         Puppet.warning _("Not using cache on failed catalog")
 88         return nil
 89       end
 90 
 91       result = retrieve_catalog_from_cache(query_options)
 92 
 93       if result
 94         # don't use use cached catalog if it doesn't match server specified environment
 95         if result.environment != @environment
 96           Puppet.err _("Not using cached catalog because its environment '%{catalog_env}' does not match '%{local_env}'") % { catalog_env: result.environment, local_env: @environment }
 97           return nil
 98         end
 99 
100         @cached_catalog_status = 'on_failure'
101         Puppet.info _("Using cached catalog from environment '%{catalog_env}'") % { catalog_env: result.environment }
102       end
103     end
104   end
105 
106   result
107 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
298 def run(options = {})
299   # We create the report pre-populated with default settings for
300   # environment and transaction_uuid very early, this is to ensure
301   # they are sent regardless of any catalog compilation failures or
302   # exceptions.
303   options[:report] ||= Puppet::Transaction::Report.new(nil, @environment, @transaction_uuid, @job_id, options[:start_time] || Time.now)
304   report = options[:report]
305   init_storage
306 
307   Puppet::Util::Log.newdestination(report)
308 
309   completed = nil
310   begin
311     # Skip failover logic if the server_list setting is empty
312     do_failover = Puppet.settings[:server_list] && !Puppet.settings[:server_list].empty?
313 
314     # When we are passed a catalog, that means we're in apply
315     # mode. We shouldn't try to do any failover in that case.
316     if options[:catalog].nil? && do_failover
317       server, port = find_functional_server
318       if server.nil?
319         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) }
320         if Puppet[:usecacheonfailure]
321           options[:pluginsync] = false
322           @running_failure = true
323 
324           server = Puppet[:server_list].first[0]
325           port = Puppet[:server_list].first[1] || Puppet[:serverport]
326 
327           Puppet.err(detail)
328         else
329           raise Puppet::Error, detail
330         end
331       else
332         #TRANSLATORS 'server_list' is the name of a setting and should not be translated
333         Puppet.debug _("Selected puppet server from the `server_list` setting: %{server}:%{port}") % { server: server, port: port }
334         report.server_used = "#{server}:#{port}"
335       end
336       Puppet.override(server: server, serverport: port) do
337         completed = run_internal(options)
338       end
339     else
340       completed = run_internal(options)
341     end
342   ensure
343     # we may sleep for awhile, close connections now
344     Puppet.runtime[:http].close
345   end
346 
347   completed ? report.exit_status : nil
348 end
save_last_run_summary(report) click to toggle source
    # File lib/puppet/configurer.rb
642 def save_last_run_summary(report)
643   mode = Puppet.settings.setting(:lastrunfile).mode
644   Puppet::Util.replace_file(Puppet[:lastrunfile], mode) do |fh|
645     fh.print YAML.dump(report.raw_summary)
646   end
647 rescue => detail
648   Puppet.log_exception(detail, _("Could not save last run local report: %{detail}") % { detail: detail })
649 end
send_report(report) click to toggle source
    # File lib/puppet/configurer.rb
627 def send_report(report)
628   puts report.summary if Puppet[:summarize]
629   save_last_run_summary(report)
630   if Puppet[:report]
631     remote = Puppet::Node::Environment.remote(@environment)
632     begin
633       Puppet::Transaction::Report.indirection.save(report, nil, ignore_cache: true, environment: remote)
634     ensure
635       Puppet::Transaction::Report.indirection.save(report, nil, ignore_terminus: true, environment: remote)
636     end
637   end
638 rescue => detail
639   Puppet.log_exception(detail, _("Could not send report: %{detail}") % { detail: detail })
640 end
valid_server_environment?() click to toggle source
    # File lib/puppet/configurer.rb
527 def valid_server_environment?
528   session = Puppet.lookup(:http_session)
529   begin
530     fs = session.route_to(:fileserver)
531     fs.get_file_metadatas(path: URI(Puppet[:pluginsource]).path, recurse: :false, environment: @environment)
532     true
533   rescue Puppet::HTTP::ResponseError => detail
534     if detail.response.code == 404
535       if Puppet[:strict_environment_mode]
536         raise Puppet::Error.new(_("Environment '%{environment}' not found on server, aborting run.") % { environment: @environment })
537       else
538         Puppet.notice(_("Environment '%{environment}' not found on server, skipping initial pluginsync.") % { environment: @environment })
539       end
540     else
541       Puppet.log_exception(detail, detail.message)
542     end
543     false
544   rescue => detail
545     Puppet.log_exception(detail, detail.message)
546     false
547   end
548 end
warn_fact_name_length(name, max_length) click to toggle source
    # File lib/puppet/configurer.rb
137 def warn_fact_name_length(name, max_length)
138   Puppet.warning _("Fact %{name} with length: '%{length}' exceeds the length limit: %{limit}") % { name: name, length: name.to_s.bytesize, limit: max_length }
139 end
warn_fact_payload_size(payload, max_size) click to toggle source
    # File lib/puppet/configurer.rb
149 def warn_fact_payload_size(payload, max_size)
150   Puppet.warning _("Payload with the current size of: '%{payload}' exceeds the payload size limit: %{max_size}") % { payload: payload, max_size: max_size }
151 end
warn_fact_value_length(value, max_length) click to toggle source
    # File lib/puppet/configurer.rb
145 def warn_fact_value_length(value, max_length)
146   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 }
147 end
warn_number_of_facts(size, max_number) click to toggle source
    # File lib/puppet/configurer.rb
133 def warn_number_of_facts(size, max_number)
134   Puppet.warning _("The current total number of facts: %{size} exceeds the number of facts limit: %{max_size}") % { size: size, max_size: max_number }
135 end
warn_number_of_top_level_facts(size, max_number) click to toggle source
    # File lib/puppet/configurer.rb
141 def warn_number_of_top_level_facts(size, max_number)
142   Puppet.warning _("The current number of top level facts: %{size} exceeds the top facts limit: %{max_size}") % { size: size, max_size: max_number }
143 end

Private Instance Methods

current_server_specified_environment(current_environment, configured_environment, options) click to toggle source
    # File lib/puppet/configurer.rb
597 def current_server_specified_environment(current_environment, configured_environment, options)
598   return @server_specified_environment if @server_specified_environment
599 
600   begin
601     node_retr_time = thinmark do
602       node = Puppet::Node.indirection.find(Puppet[:node_name_value],
603                                            :environment => Puppet::Node::Environment.remote(current_environment),
604                                            :configured_environment => configured_environment,
605                                            :ignore_cache => true,
606                                            :transaction_uuid => @transaction_uuid,
607                                            :fail_on_404 => true)
608 
609       @server_specified_environment = node.environment_name.to_s
610 
611       if @server_specified_environment != @environment
612         Puppet.notice _("Local environment: '%{local_env}' doesn't match server specified node environment '%{node_env}', switching agent to '%{node_env}'.") % { local_env: @environment, node_env: @server_specified_environment }
613       end
614     end
615 
616     options[:report].add_times(:node_retrieval, node_retr_time)
617 
618     @server_specified_environment
619   rescue => detail
620     Puppet.warning(_("Unable to fetch my node definition, but the agent run will continue:"))
621     Puppet.warning(detail)
622     nil
623   end
624 end
download_plugins(remote_environment_for_plugins) click to toggle source
    # File lib/puppet/configurer.rb
745 def download_plugins(remote_environment_for_plugins)
746   begin
747     @handler.download_plugins(remote_environment_for_plugins)
748   rescue Puppet::Error => detail
749     if !Puppet[:ignore_plugin_errors] && Puppet[:usecacheonfailure]
750       @running_failure = true
751     else
752       raise detail
753     end
754   end
755 end
execute_from_setting(setting) click to toggle source
    # File lib/puppet/configurer.rb
683 def execute_from_setting(setting)
684   return true if (command = Puppet[setting]) == ""
685 
686   begin
687     Puppet::Util::Execution.execute([command])
688     true
689   rescue => detail
690     Puppet.log_exception(detail, _("Could not run command from %{setting}: %{detail}") % { setting: setting, detail: detail })
691     false
692   end
693 end
find_functional_server() click to toggle source
    # File lib/puppet/configurer.rb
550 def find_functional_server
551   begin
552     session = Puppet.lookup(:http_session)
553     service = session.route_to(:puppet)
554     return [service.url.host, service.url.port]
555   rescue Puppet::HTTP::ResponseError => e
556     Puppet.debug(_("Puppet server %{host}:%{port} is unavailable: %{code} %{reason}") %
557                  { host: e.response.url.host, port: e.response.url.port, code: e.response.code, reason: e.response.reason })
558   rescue => detail
559     #TRANSLATORS 'server_list' is the name of a setting and should not be translated
560     Puppet.debug _("Unable to connect to server from server_list setting: %{detail}") % {detail: detail}
561   end
562   [nil, nil]
563 end
last_server_specified_environment() click to toggle source

@api private

Read the last server-specified environment from the lastrunfile. The environment is considered to be server-specified if the values of `initial_environment` and `converged_environment` are different.

@return [String, Boolean] An array containing a string with the environment

read from the lastrunfile in case the server is authoritative, and a
boolean marking whether the last environment was correctly loaded.
    # File lib/puppet/configurer.rb
576 def last_server_specified_environment
577   return @last_server_specified_environment, @loaded_last_environment if @last_server_specified_environment
578 
579   if Puppet::FileSystem.exist?(Puppet[:lastrunfile])
580     summary = Puppet::Util::Yaml.safe_load_file(Puppet[:lastrunfile])
581     return [nil, nil] unless summary['application']['run_mode'] == 'agent'
582     initial_environment = summary['application']['initial_environment']
583     converged_environment = summary['application']['converged_environment']
584     @last_server_specified_environment = converged_environment if initial_environment != converged_environment
585     Puppet.debug(_("Successfully loaded last environment from the lastrunfile"))
586     @loaded_last_environment = true
587   end
588 
589   Puppet.debug(_("Found last server-specified environment: %{environment}") % { environment: @last_server_specified_environment }) if @last_server_specified_environment
590   [@last_server_specified_environment, @loaded_last_environment]
591 rescue => detail
592   Puppet.debug(_("Could not find last server-specified environment: %{detail}") % { detail: detail })
593   [nil, nil]
594 end
push_current_environment_and_loaders() click to toggle source
    # File lib/puppet/configurer.rb
695 def push_current_environment_and_loaders
696   new_env = Puppet::Node::Environment.remote(@environment)
697   Puppet.push_context(
698     {
699       :current_environment => new_env,
700       :loaders => Puppet::Pops::Loaders.new(new_env, true)
701     },
702     "Local node environment #{@environment} for configurer transaction"
703   )
704 end
retrieve_catalog_from_cache(query_options) click to toggle source
    # File lib/puppet/configurer.rb
706 def retrieve_catalog_from_cache(query_options)
707   result = nil
708   @duration = thinmark do
709     result = Puppet::Resource::Catalog.indirection.find(
710       Puppet[:node_name_value],
711       query_options.merge(
712         :ignore_terminus => true,
713         :environment     => Puppet::Node::Environment.remote(@environment)
714       )
715     )
716   end
717   result
718 rescue => detail
719   Puppet.log_exception(detail, _("Could not retrieve catalog from cache: %{detail}") % { detail: detail })
720   return nil
721 end
retrieve_new_catalog(facts, query_options) click to toggle source
    # File lib/puppet/configurer.rb
723 def retrieve_new_catalog(facts, query_options)
724   result = nil
725   @duration = thinmark do
726     result = Puppet::Resource::Catalog.indirection.find(
727       Puppet[:node_name_value],
728       query_options.merge(
729         :ignore_cache      => true,
730         # don't update cache until after environment converges
731         :ignore_cache_save => true,
732         :environment       => Puppet::Node::Environment.remote(@environment),
733         :check_environment => true,
734         :fail_on_404       => true,
735         :facts_for_catalog => facts
736       )
737     )
738   end
739   result
740 rescue StandardError => detail
741   Puppet.log_exception(detail, _("Could not retrieve catalog from remote server: %{detail}") % { detail: detail })
742   return nil
743 end
run_internal(options) click to toggle source
    # File lib/puppet/configurer.rb
350 def run_internal(options)
351   report = options[:report]
352   report.initial_environment = Puppet[:environment]
353 
354   if options[:start_time]
355     startup_time = Time.now - options[:start_time]
356     report.add_times(:startup_time, startup_time)
357   end
358 
359   # If a cached catalog is explicitly requested, attempt to retrieve it. Skip the node request,
360   # don't pluginsync and switch to the catalog's environment if we successfully retrieve it.
361   if Puppet[:use_cached_catalog]
362     Puppet::GettextConfig.reset_text_domain('agent')
363     Puppet::ModuleTranslations.load_from_vardir(Puppet[:vardir])
364 
365     cached_catalog = prepare_and_retrieve_catalog_from_cache(options)
366     if cached_catalog
367       @cached_catalog_status = 'explicitly_requested'
368 
369       if @environment != cached_catalog.environment && !Puppet[:strict_environment_mode]
370         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 }
371         @environment = cached_catalog.environment
372       end
373 
374       report.environment = @environment
375     else
376       # Don't try to retrieve a catalog from the cache again after we've already
377       # failed to do so the first time.
378       Puppet[:use_cached_catalog] = false
379       Puppet[:usecacheonfailure] = false
380       options[:pluginsync] = Puppet::Configurer.should_pluginsync?
381     end
382   end
383 
384   begin
385     unless Puppet[:node_name_fact].empty?
386       query_options, facts = get_facts(options)
387     end
388 
389     configured_environment = Puppet[:environment] if Puppet.settings.set_by_config?(:environment)
390 
391     # We only need to find out the environment to run in if we don't already have a catalog
392     unless (cached_catalog || options[:catalog] || Puppet.settings.set_by_cli?(:environment) || Puppet[:strict_environment_mode])
393       Puppet.debug(_("Environment not passed via CLI and no catalog was given, attempting to find out the last server-specified environment"))
394       initial_environment, loaded_last_environment = last_server_specified_environment
395 
396       unless Puppet[:use_last_environment] && loaded_last_environment
397         Puppet.debug(_("Requesting environment from the server"))
398         initial_environment = current_server_specified_environment(@environment, configured_environment, options)
399       end
400 
401       if initial_environment
402         @environment = initial_environment
403         report.environment = initial_environment
404 
405         push_current_environment_and_loaders
406       else
407         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."))
408       end
409     end
410 
411     Puppet.info _("Using environment '%{env}'") % { env: @environment }
412 
413     # This is to maintain compatibility with anyone using this class
414     # aside from agent, apply, device.
415     unless Puppet.lookup(:loaders) { nil }
416       push_current_environment_and_loaders
417     end
418 
419     temp_value = options[:pluginsync]
420 
421     # only validate server environment if pluginsync is requested
422     options[:pluginsync] = valid_server_environment? if options[:pluginsync]
423 
424     query_options, facts = get_facts(options) unless query_options
425     options[:pluginsync] = temp_value
426 
427     query_options[:configured_environment] = configured_environment
428 
429     catalog = prepare_and_retrieve_catalog(cached_catalog, facts, options, query_options)
430     unless catalog
431       return nil
432     end
433 
434     if Puppet[:strict_environment_mode] && catalog.environment != @environment
435       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 }
436       return nil
437     end
438 
439     # Here we set the local environment based on what we get from the
440     # catalog. Since a change in environment means a change in facts, and
441     # facts may be used to determine which catalog we get, we need to
442     # rerun the process if the environment is changed.
443     tries = 0
444     while catalog.environment and not catalog.environment.empty? and catalog.environment != @environment
445       if tries > 3
446         raise Puppet::Error, _("Catalog environment didn't stabilize after %{tries} fetches, aborting run") % { tries: tries }
447       end
448       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 }
449       @environment = catalog.environment
450       report.environment = @environment
451 
452       push_current_environment_and_loaders
453 
454       query_options, facts = get_facts(options)
455       query_options[:configured_environment] = configured_environment
456 
457       # if we get here, ignore the cached catalog
458       catalog = prepare_and_retrieve_catalog(nil, facts, options, query_options)
459       return nil unless catalog
460       tries += 1
461     end
462 
463     # now that environment has converged, convert resource catalog into ral catalog
464     # unless we were given a RAL catalog
465     if !cached_catalog && options[:catalog]
466       ral_catalog = options[:catalog]
467     else
468       # Ordering here matters. We have to resolve deferred resources in the
469       # resource catalog, convert the resource catalog to a RAL catalog (which
470       # triggers type/provider validation), and only if that is successful,
471       # should we cache the *original* resource catalog. However, deferred
472       # evaluation mutates the resource catalog, so we need to make a copy of
473       # it here. If PUP-9323 is ever implemented so that we resolve deferred
474       # resources in the RAL catalog as they are needed, then we could eliminate
475       # this step.
476       catalog_to_cache = Puppet.override(:rich_data => Puppet[:rich_data]) do
477         Puppet::Resource::Catalog.from_data_hash(catalog.to_data_hash)
478       end
479 
480       # REMIND @duration is the time spent loading the last catalog, and doesn't
481       # account for things like we failed to download and fell back to the cache
482       ral_catalog = convert_catalog(catalog, @duration, facts, options)
483 
484       # Validation succeeded, so commit the `catalog_to_cache` for non-noop runs. Don't
485       # commit `catalog` since it contains the result of deferred evaluation. Ideally
486       # we'd just copy the downloaded response body, instead of serializing the
487       # in-memory catalog, but that's hard due to the indirector.
488       indirection = Puppet::Resource::Catalog.indirection
489       if !Puppet[:noop] && indirection.cache?
490         request = indirection.request(:save, nil, catalog_to_cache, environment: Puppet::Node::Environment.remote(catalog_to_cache.environment))
491         Puppet.info("Caching catalog for #{request.key}")
492         indirection.cache.save(request)
493       end
494     end
495 
496     execute_prerun_command or return nil
497 
498     options[:report].code_id = ral_catalog.code_id
499     options[:report].catalog_uuid = ral_catalog.catalog_uuid
500     options[:report].cached_catalog_status = @cached_catalog_status
501     apply_catalog(ral_catalog, options)
502     true
503   rescue => detail
504     Puppet.log_exception(detail, _("Failed to apply catalog: %{detail}") % { detail: detail })
505     return nil
506   ensure
507     execute_postrun_command or return nil
508   end
509 ensure
510   if Puppet[:resubmit_facts]
511     # TODO: Should mark the report as "failed" if an error occurs and
512     #       resubmit_facts returns false. There is currently no API for this.
513     resubmit_facts_time = thinmark { resubmit_facts }
514 
515     report.add_times(:resubmit_facts, resubmit_facts_time)
516   end
517 
518   report.cached_catalog_status ||= @cached_catalog_status
519   report.add_times(:total, Time.now - report.time)
520   report.finalize_report
521   Puppet::Util::Log.close(report)
522   send_report(report)
523   Puppet.pop_context
524 end