# # Author:: Daniel DeLeo () # Copyright:: Copyright 2015-2019, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "forwardable" unless defined?(Forwardable) require_relative "../log" require_relative "../run_context" require_relative "../config" require_relative "../node" require_relative "../exceptions" class Chef module PolicyBuilder # PolicyBuilder that selects either a Policyfile or non-Policyfile # implementation based on the content of the node object. class Dynamic extend Forwardable attr_reader :node attr_reader :node_name attr_reader :ohai_data attr_reader :json_attribs attr_reader :override_runlist attr_reader :events def initialize(node_name, ohai_data, json_attribs, override_runlist, events) @implementation = nil @node_name = node_name @ohai_data = ohai_data @json_attribs = json_attribs @override_runlist = override_runlist @events = events @node = nil end ## PolicyBuilder API ## # Loads the node state from the server, then picks the correct # implementation class based on the node and json_attribs. # # Calls #finish_load_node on the implementation object to complete the # loading process. All subsequent lifecycle calls are delegated. # # @return [Chef::Node] the loaded node. def load_node events.node_load_start(node_name, config) Chef::Log.trace("Building node object for #{node_name}") @node = if Chef::Config[:solo_legacy_mode] Chef::Node.build(node_name) else Chef::Node.find_or_create(node_name) end select_implementation(node) implementation.finish_load_node(node) node events.node_load_success(node) rescue Exception => e events.node_load_failed(node_name, e, config) raise end ## Delegated Public API Methods ## ### Accessors ### def_delegator :implementation, :original_runlist def_delegator :implementation, :run_context def_delegator :implementation, :run_list_expansion ### Lifecycle Methods ### # @!method build_node # # Applies external attributes (e.g., from JSON file, environment, # policyfile, etc.) and determines the correct expanded run list for the # run. # # @return [Chef::Node] def_delegator :implementation, :build_node # @!method setup_run_context # # Synchronizes cookbooks and initializes the run context object for the # run. # # @return [Chef::RunContext] def_delegator :implementation, :setup_run_context # @!method expanded_run_list # # Resolves the run list to a form containing only recipes and sets the # `roles` and `recipes` automatic attributes on the node. # # @return [#recipes, #roles] A RunListExpansion or duck-type. def_delegator :implementation, :expand_run_list # @!method sync_cookbooks # # Synchronizes cookbooks. In a normal chef-client run, this is handled by # #setup_run_context, but may be called directly in some circumstances. # # @return [Hash{String => Chef::CookbookManifest}] A map of # CookbookManifest objects by cookbook name. def_delegator :implementation, :sync_cookbooks # @!method temporary_policy? # # Indicates whether the policy is temporary, which means an # override_runlist was provided. Chef::Client uses this to decide whether # to do the final node save at the end of the run or not. # # @return [true,false] def_delegator :implementation, :temporary_policy? ## Internal Public API ## # Returns the selected implementation, or raises if not set. The # implementation is set when #load_node is called. # # @return [PolicyBuilder::Policyfile, PolicyBuilder::ExpandNodeObject] def implementation @implementation || raise(Exceptions::InvalidPolicybuilderCall, "#load_node must be called before other policy builder methods") end # @api private # # Sets the implementation based on the content of the node, node JSON # (i.e., the `-j JSON_FILE` data), and config. This is only public for # testing purposes; production code should call #load_node instead. def select_implementation(node) if policyfile_set_in_config? || policyfile_attribs_in_node_json? || node_has_policyfile_attrs?(node) || policyfile_compat_mode_config? @implementation = Policyfile.new(node_name, ohai_data, json_attribs, override_runlist, events) else @implementation = ExpandNodeObject.new(node_name, ohai_data, json_attribs, override_runlist, events) end end def config Chef::Config end private def node_has_policyfile_attrs?(node) node.policy_name || node.policy_group end def policyfile_attribs_in_node_json? json_attribs.key?("policy_name") || json_attribs.key?("policy_group") end def policyfile_set_in_config? config[:policy_name] || config[:policy_group] end def policyfile_compat_mode_config? config[:deployment_group] && !config[:policy_document_native_api] end end end end