diff options
-rw-r--r-- | lib/chef/policy_builder.rb | 4 | ||||
-rw-r--r-- | spec/unit/policy_builder_spec.rb | 294 |
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 |