diff options
author | danielsdeleo <dan@opscode.com> | 2014-01-14 17:55:49 -0800 |
---|---|---|
committer | danielsdeleo <dan@opscode.com> | 2014-01-14 17:55:49 -0800 |
commit | c4620aae4c08e33a1ee593799a80eee473c90a01 (patch) | |
tree | fcfaa6e0e2b12e409559d6eccd4f5b085904c165 | |
parent | f7763fa5ae9ee07ba83e5dadf606a2d105684dc7 (diff) | |
parent | 1a9a9bc334e464261be99a8452f5cefb6e25bae1 (diff) | |
download | chef-c4620aae4c08e33a1ee593799a80eee473c90a01.tar.gz |
Merge branch 'policy-setup'
https://tickets.opscode.com/browse/CHEF-4958
-rw-r--r-- | lib/chef/client.rb | 168 | ||||
-rw-r--r-- | lib/chef/policy_builder.rb | 237 | ||||
-rw-r--r-- | spec/unit/client_spec.rb | 11 | ||||
-rw-r--r-- | spec/unit/policy_builder_spec.rb | 294 |
4 files changed, 555 insertions, 155 deletions
diff --git a/lib/chef/client.rb b/lib/chef/client.rb index 31def49a3d..390dc247ab 100644 --- a/lib/chef/client.rb +++ b/lib/chef/client.rb @@ -43,6 +43,7 @@ require 'chef/formatters/minimal' require 'chef/version' require 'chef/resource_reporter' require 'chef/run_lock' +require 'chef/policy_builder' require 'ohai' require 'rbconfig' @@ -147,7 +148,6 @@ class Chef @events = EventDispatch::Dispatcher.new(*event_handlers) @override_runlist = args.delete(:override_runlist) @specific_recipes = args.delete(:specific_recipes) - runlist_override_sanity_check! end def configure_formatters @@ -226,38 +226,28 @@ class Chef raise Exceptions::ChildConvergeError, message end + def load_node + policy_builder.load_node + @node = policy_builder.node + end + def build_node + policy_builder.build_node + @run_status = Chef::RunStatus.new(node, events) + end - # Configures the Chef::Cookbook::FileVendor class to fetch file from the - # server or disk as appropriate, creates the run context for this run, and - # sanity checks the cookbook collection. - #===Returns - # Chef::RunContext:: the run context for this run. def setup_run_context - if Chef::Config[:solo] - Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::FileSystemFileVendor.new(manifest, Chef::Config[:cookbook_path]) } - cl = Chef::CookbookLoader.new(Chef::Config[:cookbook_path]) - cl.load_cookbooks - cookbook_collection = Chef::CookbookCollection.new(cl) - run_context = Chef::RunContext.new(node, cookbook_collection, @events) - else - Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::RemoteFileVendor.new(manifest, rest) } - cookbook_hash = sync_cookbooks - cookbook_collection = Chef::CookbookCollection.new(cookbook_hash) - run_context = Chef::RunContext.new(node, cookbook_collection, @events) - end - run_status.run_context = run_context - - run_context.load(@run_list_expansion) - if @specific_recipes - @specific_recipes.each do |recipe_file| - run_context.load_recipe_file(recipe_file) - end - end + run_context = policy_builder.setup_run_context(@specific_recipes) assert_cookbook_path_not_empty(run_context) + run_status.run_context = run_context run_context end + def policy_builder + @policy_builder ||= Chef::PolicyBuilder.new(node_name, ohai.data, json_attribs, @override_runlist, events) + end + + def save_updated_node unless Chef::Config[:solo] Chef::Log.debug("Saving the current state of node #{node_name}") @@ -291,85 +281,6 @@ class Chef name end - # Applies environment, external JSON attributes, and override run list to - # the node, Then expands the run_list. - # - # === Returns - # node<Chef::Node>:: The modified @node object. @node is modified in place. - def build_node - # Allow user to override the environment of a node by specifying - # a config parameter. - if Chef::Config[:environment] && !Chef::Config[:environment].chop.empty? - @node.chef_environment(Chef::Config[:environment]) - end - - # consume_external_attrs may add items to the run_list. Save the - # expanded run_list, which we will pass to the server later to - # determine which versions of cookbooks to use. - @node.reset_defaults_and_overrides - @node.consume_external_attrs(ohai.data, @json_attribs) - - unless(@override_runlist.empty?) - @original_runlist = @node.run_list.run_list_items.dup - runlist_override_sanity_check! - @node.run_list(*@override_runlist) - Chef::Log.warn "Run List override has been provided." - Chef::Log.warn "Original Run List: [#{@original_runlist.join(', ')}]" - Chef::Log.warn "Overridden Run List: [#{@node.run_list}]" - end - - @run_list_expansion = expand_run_list - - # @run_list_expansion is a RunListExpansion. - # - # Convert @expanded_run_list, which is an - # Array of Hashes of the form - # {:name => NAME, :version_constraint => Chef::VersionConstraint }, - # into @expanded_run_list_with_versions, an - # Array of Strings of the form - # "#{NAME}@#{VERSION}" - @expanded_run_list_with_versions = @run_list_expansion.recipes.with_version_constraints_strings - - Chef::Log.info("Run List is [#{@node.run_list}]") - Chef::Log.info("Run List expands to [#{@expanded_run_list_with_versions.join(', ')}]") - - @run_status = Chef::RunStatus.new(@node, @events) - - @events.node_load_completed(node, @expanded_run_list_with_versions, Chef::Config) - - @node - end - - # In client-server operation, loads the node state from the server. In - # chef-solo operation, builds a new node object. - def load_node - @events.node_load_start(node_name, Chef::Config) - Chef::Log.debug("Building node object for #{node_name}") - - if Chef::Config[:solo] - @node = Chef::Node.build(node_name) - else - @node = Chef::Node.find_or_create(node_name) - end - rescue Exception => e - # TODO: wrap this exception so useful error info can be given to the - # user. - @events.node_load_failed(node_name, e, Chef::Config) - raise - end - - def expand_run_list - if Chef::Config[:solo] - @node.expand!('disk') - else - @node.expand!('server') - end - rescue Exception => e - # TODO: wrap/munge exception with useful error output. - @events.run_list_expand_failed(node, e) - raise - end - # # === Returns # rest<Chef::REST>:: returns Chef::REST connection object @@ -397,37 +308,6 @@ class Chef raise end - # Sync_cookbooks eagerly loads all files except files and - # templates. It returns the cookbook_hash -- the return result - # from /environments/#{node.chef_environment}/cookbook_versions, - # which we will use for our run_context. - # - # === Returns - # Hash:: The hash of cookbooks with download URLs as given by the server - def sync_cookbooks - Chef::Log.debug("Synchronizing cookbooks") - - begin - @events.cookbook_resolution_start(@expanded_run_list_with_versions) - cookbook_hash = rest.post_rest("environments/#{@node.chef_environment}/cookbook_versions", - {:run_list => @expanded_run_list_with_versions}) - rescue Exception => e - # TODO: wrap/munge exception to provide helpful error output - @events.cookbook_resolution_failed(@expanded_run_list_with_versions, e) - raise - else - @events.cookbook_resolution_complete(cookbook_hash) - end - - synchronizer = Chef::CookbookSynchronizer.new(cookbook_hash, @events) - synchronizer.sync_cookbooks - - # register the file cache path in the cookbook path so that CookbookLoader actually picks up the synced cookbooks - Chef::Config[:cookbook_path] = File.join(Chef::Config[:file_cache_path], "cookbooks") - - cookbook_hash - end - # Converges the node. # # === Returns @@ -533,22 +413,6 @@ class Chef true end - # Ensures runlist override contains RunListItem instances - def runlist_override_sanity_check! - # Convert to array and remove whitespace - if @override_runlist.is_a?(String) - @override_runlist = @override_runlist.split(',').map { |e| e.strip } - end - @override_runlist = [@override_runlist].flatten.compact - @override_runlist.map! do |item| - if(item.is_a?(Chef::RunList::RunListItem)) - item - else - Chef::RunList::RunListItem.new(item) - end - end - end - def empty_directory?(path) !File.exists?(path) || (Dir.entries(path).size <= 2) end diff --git a/lib/chef/policy_builder.rb b/lib/chef/policy_builder.rb new file mode 100644 index 0000000000..37e7f8f3fa --- /dev/null +++ b/lib/chef/policy_builder.rb @@ -0,0 +1,237 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Author:: Tim Hinderliter (<tim@opscode.com>) +# Author:: Christopher Walters (<cw@opscode.com>) +# Author:: Daniel DeLeo (<dan@getchef.com>) +# Copyright:: Copyright 2008-2014 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 'chef/log' +require 'chef/rest' +require 'chef/run_context' +require 'chef/config' +require 'chef/node' + +class Chef + + # Class that handles fetching policy from server or disk and resolving any + # indirection (e.g. expanding run_list). + # + # INPUTS + # * event stream object + # * node object/run_list + # * json_attribs + # * override_runlist + # + # OUTPUTS + # * mutated node object (implicit) + # * a new RunStatus (probably doesn't need to be here) + # * cookbooks sync'd to disk + # * cookbook_hash is stored in run_context + class PolicyBuilder + + attr_reader :events + attr_reader :node + attr_reader :node_name + attr_reader :ohai_data + attr_reader :json_attribs + attr_reader :override_runlist + attr_reader :original_runlist + attr_reader :run_context + attr_reader :run_list_expansion + + def initialize(node_name, ohai_data, json_attribs, override_runlist, events) + @node_name = node_name + @ohai_data = ohai_data + @json_attribs = json_attribs + @override_runlist = override_runlist + @events = events + + @node = nil + @original_runlist = nil + @run_list_expansion = nil + end + + def setup_run_context(specific_recipes=nil) + if Chef::Config[:solo] + Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::FileSystemFileVendor.new(manifest, Chef::Config[:cookbook_path]) } + cl = Chef::CookbookLoader.new(Chef::Config[:cookbook_path]) + cl.load_cookbooks + cookbook_collection = Chef::CookbookCollection.new(cl) + run_context = Chef::RunContext.new(node, cookbook_collection, @events) + else + Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::RemoteFileVendor.new(manifest, api_service) } + cookbook_hash = sync_cookbooks + cookbook_collection = Chef::CookbookCollection.new(cookbook_hash) + run_context = Chef::RunContext.new(node, cookbook_collection, @events) + end + + # TODO: this is not the place for this. It should be in Runner or + # CookbookCompiler or something. + run_context.load(@run_list_expansion) + if specific_recipes + specific_recipes.each do |recipe_file| + run_context.load_recipe_file(recipe_file) + end + end + run_context + end + + + # In client-server operation, loads the node state from the server. In + # chef-solo operation, builds a new node object. + def load_node + events.node_load_start(node_name, Chef::Config) + Chef::Log.debug("Building node object for #{node_name}") + + if Chef::Config[:solo] + @node = Chef::Node.build(node_name) + else + @node = Chef::Node.find_or_create(node_name) + end + rescue Exception => e + # TODO: wrap this exception so useful error info can be given to the + # user. + events.node_load_failed(node_name, e, Chef::Config) + raise + end + + + # Applies environment, external JSON attributes, and override run list to + # the node, Then expands the run_list. + # + # === Returns + # node<Chef::Node>:: The modified node object. node is modified in place. + def build_node + # Allow user to override the environment of a node by specifying + # a config parameter. + if Chef::Config[:environment] && !Chef::Config[:environment].chop.empty? + node.chef_environment(Chef::Config[:environment]) + end + + # consume_external_attrs may add items to the run_list. Save the + # expanded run_list, which we will pass to the server later to + # determine which versions of cookbooks to use. + node.reset_defaults_and_overrides + node.consume_external_attrs(ohai_data, @json_attribs) + + setup_run_list_override + + @run_list_expansion = expand_run_list + + # @run_list_expansion is a RunListExpansion. + # + # Convert @expanded_run_list, which is an + # Array of Hashes of the form + # {:name => NAME, :version_constraint => Chef::VersionConstraint }, + # into @expanded_run_list_with_versions, an + # Array of Strings of the form + # "#{NAME}@#{VERSION}" + @expanded_run_list_with_versions = @run_list_expansion.recipes.with_version_constraints_strings + + Chef::Log.info("Run List is [#{node.run_list}]") + Chef::Log.info("Run List expands to [#{@expanded_run_list_with_versions.join(', ')}]") + + + events.node_load_completed(node, @expanded_run_list_with_versions, Chef::Config) + + node + end + + ######################################## + # Internal public API + ######################################## + + def expand_run_list + if Chef::Config[:solo] + node.expand!('disk') + else + node.expand!('server') + end + rescue Exception => e + # TODO: wrap/munge exception with useful error output. + events.run_list_expand_failed(node, e) + raise + end + + # Sync_cookbooks eagerly loads all files except files and + # templates. It returns the cookbook_hash -- the return result + # from /environments/#{node.chef_environment}/cookbook_versions, + # which we will use for our run_context. + # + # === Returns + # Hash:: The hash of cookbooks with download URLs as given by the server + def sync_cookbooks + Chef::Log.debug("Synchronizing cookbooks") + + begin + events.cookbook_resolution_start(@expanded_run_list_with_versions) + cookbook_hash = api_service.post("environments/#{node.chef_environment}/cookbook_versions", + {:run_list => @expanded_run_list_with_versions}) + rescue Exception => e + # TODO: wrap/munge exception to provide helpful error output + events.cookbook_resolution_failed(@expanded_run_list_with_versions, e) + raise + else + events.cookbook_resolution_complete(cookbook_hash) + end + + synchronizer = Chef::CookbookSynchronizer.new(cookbook_hash, events) + synchronizer.sync_cookbooks + + # register the file cache path in the cookbook path so that CookbookLoader actually picks up the synced cookbooks + Chef::Config[:cookbook_path] = File.join(Chef::Config[:file_cache_path], "cookbooks") + + cookbook_hash + end + + def setup_run_list_override + runlist_override_sanity_check! + unless(override_runlist.empty?) + @original_runlist = node.run_list.run_list_items.dup + node.run_list(*override_runlist) + Chef::Log.warn "Run List override has been provided." + Chef::Log.warn "Original Run List: [#{original_runlist.join(', ')}]" + Chef::Log.warn "Overridden Run List: [#{node.run_list}]" + end + end + + # Ensures runlist override contains RunListItem instances + def runlist_override_sanity_check! + # Convert to array and remove whitespace + if override_runlist.is_a?(String) + @override_runlist = override_runlist.split(',').map { |e| e.strip } + end + @override_runlist = [override_runlist].flatten.compact + override_runlist.map! do |item| + if(item.is_a?(Chef::RunList::RunListItem)) + item + else + Chef::RunList::RunListItem.new(item) + end + end + end + + def api_service + @api_service ||= Chef::REST.new(config[:chef_server_url]) + end + + def config + Chef::Config + end + + end +end diff --git a/spec/unit/client_spec.rb b/spec/unit/client_spec.rb index eb705e0386..6b7dd36930 100644 --- a/spec/unit/client_spec.rb +++ b/spec/unit/client_spec.rb @@ -169,6 +169,7 @@ shared_examples_for Chef::Client do it "should identify the node and run ohai, then register the client" do mock_chef_rest_for_node = mock("Chef::REST (node)") + mock_chef_rest_for_cookbook_sync = mock("Chef::REST (cookbook sync)") mock_chef_rest_for_node_save = mock("Chef::REST (node save)") mock_chef_runner = mock("Chef::Runner") @@ -201,7 +202,8 @@ shared_examples_for Chef::Client do # ---Client#sync_cookbooks -- downloads the list of cookbooks to sync # Chef::CookbookSynchronizer.any_instance.should_receive(:sync_cookbooks) - mock_chef_rest_for_node.should_receive(:post_rest).with("environments/_default/cookbook_versions", {:run_list => []}).and_return({}) + Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url]).and_return(mock_chef_rest_for_cookbook_sync) + mock_chef_rest_for_cookbook_sync.should_receive(:post).with("environments/_default/cookbook_versions", {:run_list => []}).and_return({}) # --Client#converge Chef::Runner.should_receive(:new).and_return(mock_chef_runner) @@ -274,6 +276,7 @@ shared_examples_for Chef::Client do Chef::Client.clear_notifications Chef::Node.stub!(:find_or_create).and_return(@node) @node.stub!(:save) + @client.load_node @client.build_node end @@ -329,7 +332,8 @@ shared_examples_for Chef::Client do @node[:roles].should be_nil @node[:recipes].should be_nil - @client.build_node + @client.policy_builder.stub!(:node).and_return(@node) + @client.policy_builder.build_node # check post-conditions. @node[:roles].should_not be_nil @@ -434,7 +438,8 @@ shared_examples_for Chef::Client do @node.should_receive(:save).and_return(nil) - @client.build_node + @client.policy_builder.stub(:node).and_return(@node) + @client.policy_builder.build_node @node[:roles].should_not be_nil @node[:roles].should eql(['test_role']) diff --git a/spec/unit/policy_builder_spec.rb b/spec/unit/policy_builder_spec.rb new file mode 100644 index 0000000000..4b95c079b7 --- /dev/null +++ b/spec/unit/policy_builder_spec.rb @@ -0,0 +1,294 @@ +# +# Author:: Daniel DeLeo (<dan@getchef.com>) +# Copyright:: Copyright 2014 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 'spec_helper' +require 'chef/policy_builder' + +describe Chef::PolicyBuilder do + + let(:node_name) { "joe_node" } + let(:ohai_data) { {"platform" => "ubuntu", "platform_version" => "13.04", "fqdn" => "joenode.example.com"} } + let(:json_attribs) { {"run_list" => []} } + let(:override_runlist) { "recipe[foo::default]" } + let(:events) { Chef::EventDispatch::Dispatcher.new } + let(:policy_builder) { Chef::PolicyBuilder.new(node_name, ohai_data, json_attribs, override_runlist, events) } + + # All methods that Chef::Client calls on this class. + describe "Public API" do + it "implements a node method" do + expect(policy_builder).to respond_to(:node) + end + + it "implements a load_node method" do + expect(policy_builder).to respond_to(:load_node) + end + + it "implements a build_node method" do + expect(policy_builder).to respond_to(:build_node) + end + + it "implements a setup_run_context method that accepts a list of recipe files to run" do + expect(policy_builder).to respond_to(:setup_run_context) + expect(policy_builder.method(:setup_run_context).arity).to eq(-1) #optional argument + end + + it "implements a run_context method" do + expect(policy_builder).to respond_to(:run_context) + end + + describe "loading the node" do + + context "on chef-solo" do + + before do + Chef::Config[:solo] = true + end + + it "creates a new in-memory node object with the given name" do + policy_builder.load_node + policy_builder.node.name.should == node_name + end + + end + + context "on chef-client" do + + let(:node) { Chef::Node.new.tap { |n| n.name(node_name) } } + + it "loads or creates a node on the server" do + Chef::Node.should_receive(:find_or_create).with(node_name).and_return(node) + policy_builder.load_node + policy_builder.node.should == node + end + + end + end + + describe "building the node" do + + # XXX: Chef::Client just needs to be able to call this, it doesn't depend on the return value. + it "builds the node and returns the updated node object" do + pending + end + + end + + end + + # Implementation specific tests + + describe "when first created" do + + it "has a node_name" do + expect(policy_builder.node_name).to eq(node_name) + end + + it "has ohai data" do + expect(policy_builder.ohai_data).to eq(ohai_data) + end + + it "has a set of attributes from command line option" do + expect(policy_builder.json_attribs).to eq(json_attribs) + end + + it "has an override_runlist" do + expect(policy_builder.override_runlist).to eq(override_runlist) + end + + end + + describe "building the node" do + + let(:configured_environment) { nil } + let(:json_attribs) { nil } + + let(:override_runlist) { nil } + let(:primary_runlist) { ["recipe[primary::default]"] } + + let(:original_default_attrs) { {"default_key" => "default_value"} } + let(:original_override_attrs) { {"override_key" => "override_value"} } + + let(:node) do + node = Chef::Node.new + node.name(node_name) + node.default_attrs = original_default_attrs + node.override_attrs = original_override_attrs + node.run_list(primary_runlist) + node + end + + before do + Chef::Config[:environment] = configured_environment + Chef::Node.should_receive(:find_or_create).with(node_name).and_return(node) + policy_builder.load_node + policy_builder.build_node + end + + it "sanity checks test setup" do + expect(node.run_list).to eq(primary_runlist) + end + + it "clears existing default and override attributes from the node" do + expect(node["default_key"]).to be_nil + expect(node["override_key"]).to be_nil + end + + it "applies ohai data to the node" do + expect(node["fqdn"]).to eq(ohai_data["fqdn"]) + end + + describe "when the given run list is not in expanded form" do + + # NOTE: for chef-client, the behavior is always to expand the run list, + # but this operation is a no-op when none of the run list items are + # roles. Because of the amount of mocking required to make this work in + # tests, this test is isolated from the others. + + let(:primary_runlist) { ["role[some_role]"] } + let(:expansion) do + recipe_list = Chef::RunList::VersionedRecipeList.new + recipe_list.add_recipe("recipe[from_role::default", "1.0.2") + double("RunListExpansion", :recipes => recipe_list) + end + + let(:node) do + node = Chef::Node.new + node.name(node_name) + node.default_attrs = original_default_attrs + node.override_attrs = original_override_attrs + node.run_list(primary_runlist) + + node.should_receive(:expand!).with("server") do + node.run_list("recipe[from_role::default]") + expansion + end + + node + end + + it "expands run list items via the server API" do + expect(node.run_list).to eq(["recipe[from_role::default]"]) + end + + end + + context "when JSON attributes are given on the command line" do + + let(:json_attribs) { {"run_list" => ["recipe[json_attribs::default]"], "json_attribs_key" => "json_attribs_value" } } + + it "sets the run list according to the given JSON" do + expect(node.run_list).to eq(["recipe[json_attribs::default]"]) + end + + it "sets node attributes according to the given JSON" do + expect(node["json_attribs_key"]).to eq("json_attribs_value") + end + + end + + context "when an override_runlist is given" do + + let(:override_runlist) { "recipe[foo::default]" } + + it "sets the override run_list on the node" do + expect(node.run_list).to eq([override_runlist]) + expect(policy_builder.original_runlist).to eq(primary_runlist) + end + + end + + context "when no environment is specified" do + + it "does not set the environment" do + expect(node.chef_environment).to eq("_default") + end + + end + + context "when a custom environment is configured" do + + let(:configured_environment) { environment.name } + + let(:environment) do + environment = Chef::Environment.new.tap {|e| e.name("prod") } + Chef::Environment.should_receive(:load).with("prod").and_return(environment) + environment + end + + it "sets the environment as configured" do + expect(node.chef_environment).to eq(environment.name) + end + end + + end + + describe "configuring the run_context" do + let(:json_attribs) { nil } + let(:override_runlist) { nil } + + let(:node) do + node = Chef::Node.new + node.name(node_name) + node.run_list("recipe[first::default]", "recipe[second::default]") + node + end + + let(:chef_http) { double("Chef::REST") } + + let(:cookbook_resolve_url) { "environments/#{node.chef_environment}/cookbook_versions" } + let(:cookbook_resolve_post_data) { {:run_list=>["first::default", "second::default"]} } + + # cookbook_hash is just a hash, but since we're passing it between mock + # objects, we get a little better test strictness by using a double (which + # will have object equality rather than semantic equality #== semantics). + let(:cookbook_hash) { double("cookbook hash", :each => nil) } + + let(:cookbook_synchronizer) { double("CookbookSynchronizer") } + + before do + Chef::Node.should_receive(:find_or_create).with(node_name).and_return(node) + + policy_builder.stub(:api_service).and_return(chef_http) + + policy_builder.load_node + policy_builder.build_node + + run_list_expansion = policy_builder.run_list_expansion + + chef_http.should_receive(:post).with(cookbook_resolve_url, cookbook_resolve_post_data).and_return(cookbook_hash) + Chef::CookbookSynchronizer.should_receive(:new).with(cookbook_hash, events).and_return(cookbook_synchronizer) + cookbook_synchronizer.should_receive(:sync_cookbooks) + + Chef::RunContext.any_instance.should_receive(:load).with(run_list_expansion) + + policy_builder.setup_run_context + end + + it "configures FileVendor to fetch files remotely" do + manifest = double("cookbook manifest") + Chef::Cookbook::RemoteFileVendor.should_receive(:new).with(manifest, chef_http) + Chef::Cookbook::FileVendor.create_from_manifest(manifest) + end + + it "triggers cookbook compilation in the run_context" do + # Test condition already covered by `Chef::RunContext.any_instance.should_receive(:load).with(run_list_expansion)` + end + + end + +end |