summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/chef/policy_builder.rb4
-rw-r--r--spec/unit/policy_builder_spec.rb294
2 files changed, 297 insertions, 1 deletions
diff --git a/lib/chef/policy_builder.rb b/lib/chef/policy_builder.rb
index 921271a738..37e7f8f3fa 100644
--- a/lib/chef/policy_builder.rb
+++ b/lib/chef/policy_builder.rb
@@ -51,6 +51,7 @@ class Chef
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
@@ -61,6 +62,7 @@ class Chef
@node = nil
@original_runlist = nil
+ @run_list_expansion = nil
end
def setup_run_context(specific_recipes=nil)
@@ -71,7 +73,7 @@ class Chef
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) }
+ 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)
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