diff options
author | danielsdeleo <dan@opscode.com> | 2013-12-27 10:03:39 -0800 |
---|---|---|
committer | danielsdeleo <dan@opscode.com> | 2013-12-27 10:03:39 -0800 |
commit | f97ed8648996b94972cac9e2ba53152d76f5a4e3 (patch) | |
tree | 260a4ce30dcca8b584ec86a4a0e7fa989d9dea14 | |
parent | 7322db4255ebaae7a4d671655202c20119c1d71e (diff) | |
parent | 00fbf880138833d888fa4c6813f83004ae2ef7ea (diff) | |
download | chef-f97ed8648996b94972cac9e2ba53152d76f5a4e3.tar.gz |
Merge branch 'plugin-manifest'
Fixes CHEF-4909
-rw-r--r-- | lib/chef/knife/core/subcommand_loader.rb | 52 | ||||
-rw-r--r-- | spec/unit/knife/core/subcommand_loader_spec.rb | 68 |
2 files changed, 117 insertions, 3 deletions
diff --git a/lib/chef/knife/core/subcommand_loader.rb b/lib/chef/knife/core/subcommand_loader.rb index f18bc6619e..0489c726b3 100644 --- a/lib/chef/knife/core/subcommand_loader.rb +++ b/lib/chef/knife/core/subcommand_loader.rb @@ -60,9 +60,13 @@ class Chef # subcommand loader has been modified to load the plugins by using Kernel.load # with the absolute path. def gem_and_builtin_subcommands - # search all gems for chef/knife/*.rb - require 'rubygems' - find_subcommands_via_rubygems + if have_plugin_manifest? + find_subcommands_via_manifest + else + # search all gems for chef/knife/*.rb + require 'rubygems' + find_subcommands_via_rubygems + end rescue LoadError find_subcommands_via_dirglob end @@ -71,6 +75,36 @@ class Chef @subcommand_files ||= (gem_and_builtin_subcommands.values + site_subcommands).flatten.uniq end + # If the user has created a ~/.chef/plugin_manifest.json file, we'll use + # that instead of inspecting the on-system gems to find the plugins. The + # file format is expected to look like: + # + # { "plugins": { + # "knife-ec2": { + # "paths": [ + # "/home/alice/.rubymanagerthing/gems/knife-ec2-x.y.z/lib/chef/knife/ec2_server_create.rb", + # "/home/alice/.rubymanagerthing/gems/knife-ec2-x.y.z/lib/chef/knife/ec2_server_delete.rb" + # ] + # } + # } + # } + # + # Extraneous content in this file is ignored. This intentional so that we + # can adapt the file format for potential behavior changes to knife in + # the future. + def find_subcommands_via_manifest + # Format of subcommand_files is "relative_path" (something you can + # Kernel.require()) => full_path. The relative path isn't used + # currently, so we just map full_path => full_path. + subcommand_files = {} + plugin_manifest["plugins"].each do |plugin_name, plugin_manifest| + plugin_manifest["paths"].each do |cmd_path| + subcommand_files[cmd_path] = cmd_path + end + end + subcommand_files.merge(find_subcommands_via_dirglob) + end + def find_subcommands_via_dirglob # The "require paths" of the core knife subcommands bundled with chef files = Dir[File.expand_path('../../../knife/*.rb', __FILE__)] @@ -93,6 +127,18 @@ class Chef subcommand_files.merge(find_subcommands_via_dirglob) end + def have_plugin_manifest? + ENV["HOME"] && File.exist?(plugin_manifest_path) + end + + def plugin_manifest + Chef::JSONCompat.from_json(File.read(plugin_manifest_path)) + end + + def plugin_manifest_path + File.join(ENV['HOME'], '.chef', 'plugin_manifest.json') + end + private def find_files_latest_gems(glob, check_load_path=true) diff --git a/spec/unit/knife/core/subcommand_loader_spec.rb b/spec/unit/knife/core/subcommand_loader_spec.rb index b630f4a958..08e6a0bbc1 100644 --- a/spec/unit/knife/core/subcommand_loader_spec.rb +++ b/spec/unit/knife/core/subcommand_loader_spec.rb @@ -20,6 +20,7 @@ require 'spec_helper' describe Chef::Knife::SubcommandLoader do before do + @home = File.join(CHEF_SPEC_DATA, 'knife-home') @env = {'HOME' => @home} @loader = Chef::Knife::SubcommandLoader.new(File.join(CHEF_SPEC_DATA, 'knife-site-subcommands'), @env) @@ -71,4 +72,71 @@ describe Chef::Knife::SubcommandLoader do expected_command = File.join(CHEF_SPEC_DATA, 'knife-site-subcommands', 'plugins', 'knife', 'example_subcommand.rb') @loader.site_subcommands.should include(expected_command) end + + describe "finding 3rd party plugins" do + let(:env_home) { "/home/alice" } + let(:manifest_path) { env_home + "/.chef/plugin_manifest.json" } + + before do + env_dup = ENV.to_hash + ENV.stub(:[]).and_return { |key| env_dup[key] } + ENV.stub(:[]).with("HOME").and_return(env_home) + end + + context "when there is not a ~/.chef/plugin_manifest.json file" do + before do + File.stub(:exist?).with(manifest_path).and_return(false) + end + + it "searches rubygems for plugins" do + Gem::Specification.should_receive(:latest_specs).and_call_original + @loader.subcommand_files.each do |require_path| + require_path.should match(/chef\/knife\/.*|plugins\/knife\/.*/) + end + end + + context "and HOME environment variable is not set" do + before do + ENV.stub(:[]).with("HOME").and_return(nil) + end + + it "searches rubygems for plugins" do + Gem::Specification.should_receive(:latest_specs).and_call_original + @loader.subcommand_files.each do |require_path| + require_path.should match(/chef\/knife\/.*|plugins\/knife\/.*/) + end + end + end + + end + + context "when there is a ~/.chef/plugin_manifest.json file" do + let(:ec2_server_create_plugin) { "/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_server_create.rb" } + + let(:manifest_content) do + { "plugins" => { + "knife-ec2" => { + "paths" => [ + ec2_server_create_plugin + ] + } + } + } + end + + let(:manifest_json) { Chef::JSONCompat.to_json(manifest_content) } + + before do + File.stub(:exist?).with(manifest_path).and_return(true) + File.stub(:read).with(manifest_path).and_return(manifest_json) + end + + it "uses paths from the manifest instead of searching gems" do + Gem::Specification.should_not_receive(:latest_specs).and_call_original + @loader.subcommand_files.should include(ec2_server_create_plugin) + end + + end + end + end |