summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordanielsdeleo <dan@opscode.com>2013-12-27 10:03:39 -0800
committerdanielsdeleo <dan@opscode.com>2013-12-27 10:03:39 -0800
commitf97ed8648996b94972cac9e2ba53152d76f5a4e3 (patch)
tree260a4ce30dcca8b584ec86a4a0e7fa989d9dea14
parent7322db4255ebaae7a4d671655202c20119c1d71e (diff)
parent00fbf880138833d888fa4c6813f83004ae2ef7ea (diff)
downloadchef-f97ed8648996b94972cac9e2ba53152d76f5a4e3.tar.gz
Merge branch 'plugin-manifest'
Fixes CHEF-4909
-rw-r--r--lib/chef/knife/core/subcommand_loader.rb52
-rw-r--r--spec/unit/knife/core/subcommand_loader_spec.rb68
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