diff options
author | Lamont Granquist <lamont@scriptkiddie.org> | 2016-03-15 15:09:45 -0700 |
---|---|---|
committer | Lamont Granquist <lamont@scriptkiddie.org> | 2016-03-15 15:09:45 -0700 |
commit | d32484b767e11d4da48fd1c185315fe08b25fdbe (patch) | |
tree | 95f33d862ff59b6704f506279ec72854fad956c7 | |
parent | c1a389c2a8452e9b796aa1d34c4d9e51f4af30c7 (diff) | |
download | chef-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.rb | 9 | ||||
-rw-r--r-- | lib/chef/mixin/lazy_module_include.rb | 77 | ||||
-rw-r--r-- | lib/chef/recipe.rb | 1 | ||||
-rw-r--r-- | spec/unit/dsl/recipe_spec.rb | 13 | ||||
-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.rb | 58 | ||||
-rw-r--r-- | spec/unit/mixin/lazy_module_include.rb | 71 |
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 |