class Puppet::Configurer
Attributes
Public Class Methods
# 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
# 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
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 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
# 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
# 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
# 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
# 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
# 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
# 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 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
# File lib/puppet/configurer.rb 33 def execute_postrun_command 34 execute_from_setting(:postrun_command) 35 end
# File lib/puppet/configurer.rb 37 def execute_prerun_command 38 execute_from_setting(:prerun_command) 39 end
# 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
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
# 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
# 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
# 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
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
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
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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
@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
# 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
# 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
# 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
# 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