summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThom May <thom@may.lt>2017-04-06 20:05:16 +0100
committerdanielsdeleo <dan@chef.io>2017-04-10 14:36:44 -0700
commit4b87ea73692e31b9922e449b2a6e5ab3a3820ab6 (patch)
tree9052d9d8fe245bddf1783f54be6b95aa2aa5f129
parent2dc827958371ccba4b83e8afa0e2205c12e57bc9 (diff)
downloadchef-4b87ea73692e31b9922e449b2a6e5ab3a3820ab6.tar.gz
Merge pull request #6032 from chef/sd/required-recipe
server enforced required recipe Signed-off-by: Daniel DeLeo <dan@chef.io>
-rw-r--r--lib/chef/client.rb44
-rw-r--r--spec/support/shared/context/client.rb7
-rw-r--r--spec/unit/client_spec.rb49
3 files changed, 100 insertions, 0 deletions
diff --git a/lib/chef/client.rb b/lib/chef/client.rb
index c064d33209..5bd79beac4 100644
--- a/lib/chef/client.rb
+++ b/lib/chef/client.rb
@@ -280,6 +280,8 @@ class Chef
run_context = setup_run_context
+ load_required_recipe(@rest, run_context) unless Chef::Config[:solo_legacy_mode]
+
if Chef::Config[:audit_mode] != :audit_only
converge_error = converge_and_save(run_context)
end
@@ -515,6 +517,48 @@ class Chef
end
#
+ # Adds a required recipe as specified by the Chef Server
+ #
+ # @return The modified run context
+ #
+ # @api private
+ #
+ # TODO: @rest doesn't appear to be used anywhere outside
+ # of client.register except for here. If it's common practice
+ # to create your own rest client, perhaps we should do that
+ # here but it seems more appropriate to reuse one that we
+ # know is already created. for ease of testing, we'll pass
+ # the existing rest client in as a parameter
+ #
+ def load_required_recipe(rest, run_context)
+ required_recipe_contents = rest.get("required_recipe")
+ Chef::FileCache.store("required_recipe", required_recipe_contents)
+ required_recipe_file = Chef::FileCache.load("required_recipe", false)
+
+ # TODO: add integration tests with resource reporting turned on
+ # (presumably requires changes to chef-zero)
+ #
+ # Chef::Recipe.new takes a cookbook name and a recipe name along
+ # with the run context. These names are eventually used in the
+ # resource reporter, and if the cookbook name cannot be found in the
+ # cookbook collection then we will fail with an exception. Cases where
+ # we currently also fail:
+ # - specific recipes
+ # - chef-apply would fail if resource reporting was enabled
+ #
+ recipe = Chef::Recipe.new(nil, nil, run_context)
+ recipe.from_file(required_recipe_file)
+ run_context
+ rescue Net::HTTPServerException => e
+ case e.response
+ when Net::HTTPNotFound
+ Chef::Log.info("Required Recipe not found")
+ else
+ raise
+ end
+ end
+
+ #
# The PolicyBuilder strategy for figuring out run list and cookbooks.
#
# @return [Chef::PolicyBuilder::Policyfile, Chef::PolicyBuilder::ExpandNodeObject]
diff --git a/spec/support/shared/context/client.rb b/spec/support/shared/context/client.rb
index c65650e6b1..a241e59533 100644
--- a/spec/support/shared/context/client.rb
+++ b/spec/support/shared/context/client.rb
@@ -135,6 +135,12 @@ shared_context "a client run" do
and_return({})
end
+ def stub_for_required_recipe
+ response = Net::HTTPNotFound.new("1.1", "404", "Not Found")
+ exception = Net::HTTPServerException.new('404 "Not Found"', response)
+ expect(http_node_load).to receive(:get).with("required_recipe").and_raise(exception)
+ end
+
def stub_for_converge
# define me
end
@@ -165,6 +171,7 @@ shared_context "a client run" do
stub_for_data_collector_init
stub_for_node_load
stub_for_sync_cookbooks
+ stub_for_required_recipe
stub_for_converge
stub_for_audit
stub_for_node_save
diff --git a/spec/unit/client_spec.rb b/spec/unit/client_spec.rb
index ec3f70b9b0..275f5cbbfc 100644
--- a/spec/unit/client_spec.rb
+++ b/spec/unit/client_spec.rb
@@ -402,6 +402,55 @@ describe Chef::Client do
end
end
+ describe "load_required_recipe" do
+ let(:rest) { double("Chef::ServerAPI (required recipe)") }
+ let(:run_context) { double("Chef::RunContext") }
+ let(:recipe) { double("Chef::Recipe (required recipe)") }
+ let(:required_recipe) do
+ <<EOM
+fake_recipe_variable = "for reals"
+EOM
+ end
+
+ context "when required_recipe is configured" do
+
+ before(:each) do
+ expect(rest).to receive(:get).with("required_recipe").and_return(required_recipe)
+ expect(Chef::Recipe).to receive(:new).with(nil, nil, run_context).and_return(recipe)
+ expect(recipe).to receive(:from_file)
+ end
+
+ it "fetches the recipe and adds it to the run context" do
+ client.load_required_recipe(rest, run_context)
+ end
+
+ context "when the required_recipe has bad contents" do
+ let(:required_recipe) do
+ <<EOM
+this is not a recipe
+EOM
+ end
+ it "should not raise an error" do
+ expect { client.load_required_recipe(rest, run_context) }.not_to raise_error()
+ end
+ end
+ end
+
+ context "when required_recipe returns 404" do
+ let(:http_response) { Net::HTTPNotFound.new("1.1", "404", "Not Found") }
+ let(:http_exception) { Net::HTTPServerException.new('404 "Not Found"', http_response) }
+
+ before(:each) do
+ expect(rest).to receive(:get).with("required_recipe").and_raise(http_exception)
+ end
+
+ it "should log and continue on" do
+ expect(Chef::Log).to receive(:info)
+ client.load_required_recipe(rest, run_context)
+ end
+ end
+ end
+
describe "windows_admin_check" do
context "platform is not windows" do
before do