From 437ca9a3ba08f3b4288303db1d1ed7c53b452ffb Mon Sep 17 00:00:00 2001 From: Ben Somers Date: Tue, 14 May 2013 11:25:07 -0700 Subject: Enabling storage of roles in subdirectories Storing roles in subdirectories is already effectively enabled for Hosted Chef, as the directory structure gets flattened when they are uploaded. But it is not currently possible with Chef-Solo; one has to flatten the directory manually before running Chef-Solo. This patch changes that behavior, and enables Chef-Solo with roles in subdirectories. --- lib/chef/role.rb | 15 ++++++++------- spec/unit/role_spec.rb | 31 +++++++++++++++++++++++-------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/lib/chef/role.rb b/lib/chef/role.rb index 78bbfadb88..a7e3f657a7 100644 --- a/lib/chef/role.rb +++ b/lib/chef/role.rb @@ -231,18 +231,19 @@ class Chef end # Load a role from disk - prefers to load the JSON, but will happily load - # the raw rb files as well. + # the raw rb files as well. Can search within directories in the role_path. def self.from_disk(name, force=nil) - js_file = File.join(Chef::Config[:role_path], "#{name}.json") - rb_file = File.join(Chef::Config[:role_path], "#{name}.rb") + roles_files = Dir.glob(File.join(Chef::Config[:role_path], "**", "**")) + js_path = roles_files.detect { |file| file.match /#{name}\.json$/ } + rb_path = roles_files.detect { |file| file.match /#{name}\.rb$/ } - if File.exists?(js_file) || force == "json" + if js_path && (File.exists?(js_path) || force == "json") # from_json returns object.class => json_class in the JSON. - Chef::JSONCompat.from_json(IO.read(js_file)) - elsif File.exists?(rb_file) || force == "ruby" + Chef::JSONCompat.from_json(IO.read(js_path)) + elsif rb_path && (File.exists?(rb_path) || force == "ruby") role = Chef::Role.new role.name(name) - role.from_file(rb_file) + role.from_file(rb_path) role else raise Chef::Exceptions::RoleNotFound, "Role '#{name}' could not be loaded from disk" diff --git a/spec/unit/role_spec.rb b/spec/unit/role_spec.rb index 764d586903..77a47ee6fd 100644 --- a/spec/unit/role_spec.rb +++ b/spec/unit/role_spec.rb @@ -246,28 +246,43 @@ describe Chef::Role do describe "when loading from disk" do it "should return a Chef::Role object from JSON" do - File.should_receive(:exists?).with(File.join(Chef::Config[:role_path], 'lolcat.json')).exactly(1).times.and_return(true) - IO.should_receive(:read).with(File.join(Chef::Config[:role_path], 'lolcat.json')).and_return('{"name": "ceiling_cat", "json_class": "Chef::Role" }') + Dir.should_receive(:glob).and_return(["#{Chef::Config[:role_path]}/memes", "#{Chef::Config[:role_path]}/memes/lolcat.json"]) + file_path = File.join(Chef::Config[:role_path], 'memes/lolcat.json') + File.should_receive(:exists?).with(file_path).exactly(1).times.and_return(true) + IO.should_receive(:read).with(file_path).and_return('{"name": "ceiling_cat", "json_class": "Chef::Role" }') @role.should be_a_kind_of(Chef::Role) @role.class.from_disk("lolcat") end it "should return a Chef::Role object from a Ruby DSL" do - File.should_receive(:exists?).with(File.join(Chef::Config[:role_path], 'lolcat.json')).exactly(1).times.and_return(false) - File.should_receive(:exists?).with(File.join(Chef::Config[:role_path], 'lolcat.rb')).exactly(2).times.and_return(true) - File.should_receive(:readable?).with(File.join(Chef::Config[:role_path], 'lolcat.rb')).exactly(1).times.and_return(true) + Dir.should_receive(:glob).and_return(["#{Chef::Config[:role_path]}/memes", "#{Chef::Config[:role_path]}/memes/lolcat.rb"]) + js_path = File.join(Chef::Config[:role_path], 'memes/lolcat.rb') + rb_path = File.join(Chef::Config[:role_path], 'memes/lolcat.rb') + File.should_receive(:exists?).with(rb_path).exactly(2).times.and_return(true) + File.should_receive(:readable?).with(rb_path).exactly(1).times.and_return(true) ROLE_DSL=<<-EOR name "ceiling_cat" description "like Aliens, but furry" EOR - IO.should_receive(:read).with(File.join(Chef::Config[:role_path], 'lolcat.rb')).and_return(ROLE_DSL) + IO.should_receive(:read).with(rb_path).and_return(ROLE_DSL) + @role.should be_a_kind_of(Chef::Role) + @role.class.from_disk("lolcat") + end + + it "should prefer a Chef::Role Object from JSON over one from a Ruby DSL" do + Dir.should_receive(:glob).and_return(["#{Chef::Config[:role_path]}/memes", "#{Chef::Config[:role_path]}/memes/lolcat.json", "#{Chef::Config[:role_path]}/memes/lolcat.rb"]) + js_path = File.join(Chef::Config[:role_path], 'memes/lolcat.json') + rb_path = File.join(Chef::Config[:role_path], 'memes/lolcat.rb') + File.should_receive(:exists?).with(js_path).exactly(1).times.and_return(true) + File.should_not_receive(:exists?).with(rb_path) + IO.should_receive(:read).with(js_path).and_return('{"name": "ceiling_cat", "json_class": "Chef::Role" }') @role.should be_a_kind_of(Chef::Role) @role.class.from_disk("lolcat") end it "should raise an exception if the file does not exist" do - File.should_receive(:exists?).with(File.join(Chef::Config[:role_path], 'lolcat.json')).exactly(1).times.and_return(false) - File.should_receive(:exists?).with(File.join(Chef::Config[:role_path], 'lolcat.rb')).exactly(1).times.and_return(false) + Dir.should_receive(:glob).and_return(["#{Chef::Config[:role_path]}/meme.rb"]) + File.should_not_receive(:exists?) lambda {@role.class.from_disk("lolcat")}.should raise_error(Chef::Exceptions::RoleNotFound) end end -- cgit v1.2.1