summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLamont Granquist <lamont@scriptkiddie.org>2016-03-15 15:09:45 -0700
committerLamont Granquist <lamont@scriptkiddie.org>2016-03-15 15:09:45 -0700
commitd32484b767e11d4da48fd1c185315fe08b25fdbe (patch)
tree95f33d862ff59b6704f506279ec72854fad956c7
parentc1a389c2a8452e9b796aa1d34c4d9e51f4af30c7 (diff)
downloadchef-lcg/use-and-lazy-module-include.tar.gz
lazy module inclusion into DSL moduleslcg/use-and-lazy-module-include
Chef::DSL::Recipe::FullDSL.send(:include, MyModule) will now patch all its descendants that it has been included into (works the way actual inheritance works now).
-rw-r--r--lib/chef/dsl/recipe.rb9
-rw-r--r--lib/chef/mixin/lazy_module_include.rb77
-rw-r--r--lib/chef/recipe.rb1
-rw-r--r--spec/unit/dsl/recipe_spec.rb13
-rw-r--r--spec/unit/dsl/registry_helper_spec.rb (renamed from spec/unit/dsl/regsitry_helper_spec.rb)0
-rw-r--r--spec/unit/lwrp_spec.rb58
-rw-r--r--spec/unit/mixin/lazy_module_include.rb71
7 files changed, 226 insertions, 3 deletions
diff --git a/lib/chef/dsl/recipe.rb b/lib/chef/dsl/recipe.rb
index 6d254df48d..22be303c5b 100644
--- a/lib/chef/dsl/recipe.rb
+++ b/lib/chef/dsl/recipe.rb
@@ -23,21 +23,21 @@ require "chef/mixin/powershell_out"
require "chef/dsl/resources"
require "chef/dsl/definitions"
require "chef/dsl/declare_resource"
+require "chef/mixin/lazy_module_include"
class Chef
module DSL
-
# == Chef::DSL::Recipe
# Provides the primary recipe DSL functionality for defining Chef resource
# objects via method calls.
module Recipe
-
include Chef::Mixin::ShellOut
include Chef::Mixin::PowershellOut
include Chef::DSL::Resources
include Chef::DSL::Definitions
include Chef::DSL::DeclareResource
+ extend Chef::Mixin::LazyModuleInclude
def resource_class_for(snake_case_name)
Chef::Resource.resource_for_node(snake_case_name, run_context.node)
@@ -113,6 +113,8 @@ class Chef
require "chef/dsl/reboot_pending"
require "chef/dsl/audit"
require "chef/dsl/powershell"
+ require "chef/mixin/lazy_module_include"
+
include Chef::DSL::DataQuery
include Chef::DSL::PlatformIntrospection
include Chef::DSL::IncludeRecipe
@@ -121,6 +123,9 @@ class Chef
include Chef::DSL::RebootPending
include Chef::DSL::Audit
include Chef::DSL::Powershell
+
+ extend Chef::Mixin::LazyModuleInclude
+
end
end
end
diff --git a/lib/chef/mixin/lazy_module_include.rb b/lib/chef/mixin/lazy_module_include.rb
new file mode 100644
index 0000000000..34e1fce4f1
--- /dev/null
+++ b/lib/chef/mixin/lazy_module_include.rb
@@ -0,0 +1,77 @@
+#
+# Copyright:: Copyright 2011-2016, 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.
+#
+
+class Chef
+ module Mixin
+ # If you have:
+ #
+ # module A
+ # extend LazyModuleInclude
+ # end
+ #
+ # module B
+ # include A
+ # end
+ #
+ # module C
+ # include B
+ # end
+ #
+ # module Monkeypatches
+ # def monkey
+ # puts "monkey!"
+ # end
+ # end
+ #
+ # A.send(:include, Monkeypatches)
+ #
+ # Then B and C and any classes that they're included in will also get the #monkey method patched into them.
+ #
+ module LazyModuleInclude
+
+ # Most of the magick is in this hook which creates a closure over the parent class and then builds an
+ # "infector" module which infects all descendants and which is responsible for updating the list of
+ # descendants in the parent class.
+ def included(klass)
+ super
+ parent_klass = self
+ infector = Module.new do
+ define_method(:included) do |subklass|
+ super(subklass)
+ subklass.extend(infector)
+ parent_klass.descendants.push(subklass)
+ end
+ end
+ klass.extend(infector)
+ parent_klass.descendants.push(klass)
+ end
+
+ def descendants
+ @descendants ||= []
+ end
+
+ def include(*classes)
+ super
+ classes.each do |klass|
+ descendants.each do |descendant|
+ descendant.send(:include, klass)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/recipe.rb b/lib/chef/recipe.rb
index 3a91781b2e..55b6fd7d52 100644
--- a/lib/chef/recipe.rb
+++ b/lib/chef/recipe.rb
@@ -114,6 +114,5 @@ class Chef
run_context.node.tags.delete(tag)
end
end
-
end
end
diff --git a/spec/unit/dsl/recipe_spec.rb b/spec/unit/dsl/recipe_spec.rb
index cd01079c15..bc97ecc029 100644
--- a/spec/unit/dsl/recipe_spec.rb
+++ b/spec/unit/dsl/recipe_spec.rb
@@ -24,6 +24,11 @@ class RecipeDSLExampleClass
include Chef::DSL::Recipe
end
+FullRecipeDSLExampleClass = Struct.new(:cookbook_name, :recipe_name)
+class FullRecipeDSLExampleClass
+ include Chef::DSL::Recipe::FullDSL
+end
+
RecipeDSLBaseAPI = Struct.new(:cookbook_name, :recipe_name)
class RecipeDSLExampleSubclass < RecipeDSLBaseAPI
include Chef::DSL::Recipe
@@ -36,6 +41,14 @@ describe Chef::DSL::Recipe do
let(:cookbook_name) { "example_cb" }
let(:recipe_name) { "example_recipe" }
+ it "tracks when it is included via FullDSL" do
+ expect(Chef::DSL::Recipe::FullDSL.descendants).to include(FullRecipeDSLExampleClass)
+ end
+
+ it "doesn't track what is included via only the recipe DSL" do
+ expect(Chef::DSL::Recipe::FullDSL.descendants).not_to include(RecipeDSLExampleClass)
+ end
+
shared_examples_for "A Recipe DSL Implementation" do
it "responds to cookbook_name" do
diff --git a/spec/unit/dsl/regsitry_helper_spec.rb b/spec/unit/dsl/registry_helper_spec.rb
index 45c7e73979..45c7e73979 100644
--- a/spec/unit/dsl/regsitry_helper_spec.rb
+++ b/spec/unit/dsl/registry_helper_spec.rb
diff --git a/spec/unit/lwrp_spec.rb b/spec/unit/lwrp_spec.rb
index 5afd838551..937915055e 100644
--- a/spec/unit/lwrp_spec.rb
+++ b/spec/unit/lwrp_spec.rb
@@ -717,4 +717,62 @@ describe "LWRP" do
end
end
end
+
+ describe "extending the DSL mixin" do
+ module MyAwesomeDSLExensionClass
+ def my_awesome_dsl_extension(argument)
+ argument
+ end
+ end
+
+ class MyAwesomeResource < Chef::Resource::LWRPBase
+ provides :my_awesome_resource
+ resource_name :my_awesome_resource
+ default_action :create
+ end
+
+ class MyAwesomeProvider < Chef::Provider::LWRPBase
+ use_inline_resources
+
+ provides :my_awesome_resource
+
+ action :create do
+ my_awesome_dsl_extension("foo")
+ end
+ end
+
+ let(:recipe) {
+ cookbook_repo = File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "cookbooks"))
+ cookbook_loader = Chef::CookbookLoader.new(cookbook_repo)
+ cookbook_loader.load_cookbooks
+ cookbook_collection = Chef::CookbookCollection.new(cookbook_loader)
+ node = Chef::Node.new
+ events = Chef::EventDispatch::Dispatcher.new
+ run_context = Chef::RunContext.new(node, cookbook_collection, events)
+ Chef::Recipe.new("hjk", "test", run_context)
+ }
+
+ it "lets you extend the recipe DSL" do
+ expect(Chef::Recipe).to receive(:include).with(MyAwesomeDSLExensionClass)
+ expect(Chef::Provider::InlineResources).to receive(:include).with(MyAwesomeDSLExensionClass)
+ Chef::DSL::Recipe::FullDSL.send(:include, MyAwesomeDSLExensionClass)
+ end
+
+ it "lets you call your DSL from a recipe" do
+ Chef::DSL::Recipe::FullDSL.send(:include, MyAwesomeDSLExensionClass)
+ expect(recipe.my_awesome_dsl_extension("foo")).to eql("foo")
+ end
+
+ it "lets you call your DSL from a provider" do
+ Chef::DSL::Recipe::FullDSL.send(:include, MyAwesomeDSLExensionClass)
+
+ resource = MyAwesomeResource.new("name", run_context)
+ run_context.resource_collection << resource
+
+ runner = Chef::Runner.new(run_context)
+ expect_any_instance_of(MyAwesomeProvider).to receive(:my_awesome_dsl_extension).and_call_original
+ runner.converge
+ end
+ end
+
end
diff --git a/spec/unit/mixin/lazy_module_include.rb b/spec/unit/mixin/lazy_module_include.rb
new file mode 100644
index 0000000000..542ae853ae
--- /dev/null
+++ b/spec/unit/mixin/lazy_module_include.rb
@@ -0,0 +1,71 @@
+#
+# Copyright:: Copyright 2015-2016, 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"
+
+module TestA
+ extend Chef::Mixin::LazyModuleInclude
+end
+
+module TestB
+ include TestA
+ extend Chef::Mixin::LazyModuleInclude
+end
+
+class TestC
+ include TestB
+end
+
+module Monkey
+ def monkey
+ "monkey"
+ end
+end
+
+module Klowns
+ def klowns
+ "klowns"
+ end
+end
+
+TestA.send(:include, Monkey)
+
+TestB.send(:include, Klowns)
+
+describe Chef::Mixin::LazyModuleInclude do
+
+ it "tracks descendant classes of TestA" do
+ expect(TestA.descendants).to include(TestB)
+ expect(TestA.descendants).to include(TestC)
+ end
+
+ it "tracks descendent classes of TestB" do
+ expect(TestB.descendants).to eql([TestC])
+ end
+
+ it "including into A mixins in methods into B and C" do
+ expect(TestA.instance_methods).to include(:monkey)
+ expect(TestB.instance_methods).to include(:monkey)
+ expect(TestC.instance_methods).to include(:monkey)
+ end
+
+ it "including into B only mixins in methods into C" do
+ expect(TestA.instance_methods).not_to include(:klowns)
+ expect(TestB.instance_methods).to include(:klowns)
+ expect(TestC.instance_methods).to include(:klowns)
+ end
+end