Skip to content

Commit f97ed86

Browse files
committed
Merge branch 'plugin-manifest'
Fixes CHEF-4909
2 parents 7322db4 + 00fbf88 commit f97ed86

File tree

2 files changed

+117
-3
lines changed

2 files changed

+117
-3
lines changed

lib/chef/knife/core/subcommand_loader.rb

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,13 @@ def site_subcommands
6060
# subcommand loader has been modified to load the plugins by using Kernel.load
6161
# with the absolute path.
6262
def gem_and_builtin_subcommands
63-
# search all gems for chef/knife/*.rb
64-
require 'rubygems'
65-
find_subcommands_via_rubygems
63+
if have_plugin_manifest?
64+
find_subcommands_via_manifest
65+
else
66+
# search all gems for chef/knife/*.rb
67+
require 'rubygems'
68+
find_subcommands_via_rubygems
69+
end
6670
rescue LoadError
6771
find_subcommands_via_dirglob
6872
end
@@ -71,6 +75,36 @@ def subcommand_files
7175
@subcommand_files ||= (gem_and_builtin_subcommands.values + site_subcommands).flatten.uniq
7276
end
7377

78+
# If the user has created a ~/.chef/plugin_manifest.json file, we'll use
79+
# that instead of inspecting the on-system gems to find the plugins. The
80+
# file format is expected to look like:
81+
#
82+
# { "plugins": {
83+
# "knife-ec2": {
84+
# "paths": [
85+
# "/home/alice/.rubymanagerthing/gems/knife-ec2-x.y.z/lib/chef/knife/ec2_server_create.rb",
86+
# "/home/alice/.rubymanagerthing/gems/knife-ec2-x.y.z/lib/chef/knife/ec2_server_delete.rb"
87+
# ]
88+
# }
89+
# }
90+
# }
91+
#
92+
# Extraneous content in this file is ignored. This intentional so that we
93+
# can adapt the file format for potential behavior changes to knife in
94+
# the future.
95+
def find_subcommands_via_manifest
96+
# Format of subcommand_files is "relative_path" (something you can
97+
# Kernel.require()) => full_path. The relative path isn't used
98+
# currently, so we just map full_path => full_path.
99+
subcommand_files = {}
100+
plugin_manifest["plugins"].each do |plugin_name, plugin_manifest|
101+
plugin_manifest["paths"].each do |cmd_path|
102+
subcommand_files[cmd_path] = cmd_path
103+
end
104+
end
105+
subcommand_files.merge(find_subcommands_via_dirglob)
106+
end
107+
74108
def find_subcommands_via_dirglob
75109
# The "require paths" of the core knife subcommands bundled with chef
76110
files = Dir[File.expand_path('../../../knife/*.rb', __FILE__)]
@@ -93,6 +127,18 @@ def find_subcommands_via_rubygems
93127
subcommand_files.merge(find_subcommands_via_dirglob)
94128
end
95129

130+
def have_plugin_manifest?
131+
ENV["HOME"] && File.exist?(plugin_manifest_path)
132+
end
133+
134+
def plugin_manifest
135+
Chef::JSONCompat.from_json(File.read(plugin_manifest_path))
136+
end
137+
138+
def plugin_manifest_path
139+
File.join(ENV['HOME'], '.chef', 'plugin_manifest.json')
140+
end
141+
96142
private
97143

98144
def find_files_latest_gems(glob, check_load_path=true)

spec/unit/knife/core/subcommand_loader_spec.rb

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
describe Chef::Knife::SubcommandLoader do
2222
before do
23+
2324
@home = File.join(CHEF_SPEC_DATA, 'knife-home')
2425
@env = {'HOME' => @home}
2526
@loader = Chef::Knife::SubcommandLoader.new(File.join(CHEF_SPEC_DATA, 'knife-site-subcommands'), @env)
@@ -71,4 +72,71 @@
7172
expected_command = File.join(CHEF_SPEC_DATA, 'knife-site-subcommands', 'plugins', 'knife', 'example_subcommand.rb')
7273
@loader.site_subcommands.should include(expected_command)
7374
end
75+
76+
describe "finding 3rd party plugins" do
77+
let(:env_home) { "/home/alice" }
78+
let(:manifest_path) { env_home + "/.chef/plugin_manifest.json" }
79+
80+
before do
81+
env_dup = ENV.to_hash
82+
ENV.stub(:[]).and_return { |key| env_dup[key] }
83+
ENV.stub(:[]).with("HOME").and_return(env_home)
84+
end
85+
86+
context "when there is not a ~/.chef/plugin_manifest.json file" do
87+
before do
88+
File.stub(:exist?).with(manifest_path).and_return(false)
89+
end
90+
91+
it "searches rubygems for plugins" do
92+
Gem::Specification.should_receive(:latest_specs).and_call_original
93+
@loader.subcommand_files.each do |require_path|
94+
require_path.should match(/chef\/knife\/.*|plugins\/knife\/.*/)
95+
end
96+
end
97+
98+
context "and HOME environment variable is not set" do
99+
before do
100+
ENV.stub(:[]).with("HOME").and_return(nil)
101+
end
102+
103+
it "searches rubygems for plugins" do
104+
Gem::Specification.should_receive(:latest_specs).and_call_original
105+
@loader.subcommand_files.each do |require_path|
106+
require_path.should match(/chef\/knife\/.*|plugins\/knife\/.*/)
107+
end
108+
end
109+
end
110+
111+
end
112+
113+
context "when there is a ~/.chef/plugin_manifest.json file" do
114+
let(:ec2_server_create_plugin) { "/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_server_create.rb" }
115+
116+
let(:manifest_content) do
117+
{ "plugins" => {
118+
"knife-ec2" => {
119+
"paths" => [
120+
ec2_server_create_plugin
121+
]
122+
}
123+
}
124+
}
125+
end
126+
127+
let(:manifest_json) { Chef::JSONCompat.to_json(manifest_content) }
128+
129+
before do
130+
File.stub(:exist?).with(manifest_path).and_return(true)
131+
File.stub(:read).with(manifest_path).and_return(manifest_json)
132+
end
133+
134+
it "uses paths from the manifest instead of searching gems" do
135+
Gem::Specification.should_not_receive(:latest_specs).and_call_original
136+
@loader.subcommand_files.should include(ec2_server_create_plugin)
137+
end
138+
139+
end
140+
end
141+
74142
end

0 commit comments

Comments
 (0)