Skip to content

Commit 9239ff8

Browse files
committed
[CHEF-2137] move subcommand loading into its own class
1 parent 588035f commit 9239ff8

6 files changed

Lines changed: 164 additions & 59 deletions

File tree

‎chef/lib/chef/knife.rb‎

Lines changed: 9 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
require 'chef/version'
2121
require 'mixlib/cli'
2222
require 'chef/mixin/convert_to_class_name'
23+
require 'chef/knife/subcommand_loader'
2324

2425
require 'pp'
2526

@@ -28,9 +29,6 @@ class Knife
2829
include Mixlib::CLI
2930
extend Chef::Mixin::ConvertToClassName
3031

31-
CHEF_FILE_IN_GEM = /chef-[\d]+\.[\d]+\.[\d]+/
32-
CURRENT_CHEF_GEM = /chef-#{Regexp.escape(Chef::VERSION)}/
33-
3432
attr_accessor :name_args
3533

3634
def self.msg(msg="")
@@ -74,6 +72,14 @@ def self.unnamed?
7472
name.nil? || name.empty?
7573
end
7674

75+
def self.subcommand_loader
76+
@subcommand_loader ||= Knife::SubcommandLoader.new(chef_config_dir)
77+
end
78+
79+
def self.load_commands
80+
subcommand_loader.load_commands
81+
end
82+
7783
def self.subcommands
7884
@@subcommands ||= {}
7985
end
@@ -88,12 +94,6 @@ def self.subcommands_by_category
8894
@subcommands_by_category
8995
end
9096

91-
# Load all the sub-commands
92-
def self.load_commands
93-
find_subcommand_files.each { |subcommand| require subcommand }
94-
subcommands
95-
end
96-
9797
# Print the list of subcommands knife knows about. If +preferred_category+
9898
# is given, only subcommands in that category are shown
9999
def self.list_commands(preferred_category=nil)
@@ -205,49 +205,6 @@ def self.chef_config_dir
205205
@@chef_config_dir
206206
end
207207

208-
# Returns an Array of paths to knife commands located in chef_config_dir/plugins/knife/
209-
# and ~/.chef/plugins/knife/
210-
def self.site_subcommands
211-
user_specific_files = []
212-
213-
if chef_config_dir
214-
user_specific_files.concat Dir.glob(File.expand_path("plugins/knife/*.rb", chef_config_dir))
215-
end
216-
217-
# finally search ~/.chef/plugins/knife/*.rb
218-
user_specific_files.concat Dir.glob(File.join(ENV['HOME'], '.chef', 'plugins', 'knife', '*.rb'))
219-
220-
user_specific_files.map! { |path| path[/(.+).rb/, 1] }
221-
user_specific_files
222-
end
223-
224-
# Returns an Array of paths to knife commands built-in to chef, or installed via gem.
225-
# If rubygems is not installed, falls back to globbing the knife directory.
226-
def self.gem_and_builtin_subcommands
227-
begin
228-
# search all gems for chef/knife/*.rb
229-
require 'rubygems'
230-
files = Gem.find_files 'chef/knife/*.rb'
231-
# wow, this is a sad hack :(
232-
# Gem.find_files finds files in all versions of a gem, which
233-
# means that if chef 0.10 and 0.9.x are installed, we'll try to
234-
# require, e.g., chef/knife/ec2_server_create, which will cause
235-
# a gem activation error. So remove files from older chef gems.
236-
files.reject! {|f| f =~ CHEF_FILE_IN_GEM && f !~ CURRENT_CHEF_GEM }
237-
files.map! do |file|
238-
file[/(#{Regexp.escape File.join('chef', 'knife', '')}.*\.rb)/, 1]
239-
end.uniq!
240-
rescue LoadError
241-
# The "require paths" of the core knife subcommands bundled with chef
242-
files = Dir[File.expand_path(File.join(File.dirname(__FILE__), 'knife', '*.rb'))]
243-
files.map! { |knife_file| knife_file[/#{CHEF_ROOT}#{Regexp.escape(File::SEPARATOR)}(.*)\.rb/,1] }
244-
end
245-
files
246-
end
247-
248-
def self.find_subcommand_files
249-
@@subcommand_files ||= (gem_and_builtin_subcommands + site_subcommands).flatten.uniq
250-
end
251208

252209
public
253210

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#
2+
# Author:: Adam Jacob (<adam@opscode.com>)
3+
# Author:: Christopher Brown (<cb@opscode.com>)
4+
# Author:: Daniel DeLeo (<dan@opscode.com>)
5+
# Copyright:: Copyright (c) 2009, 2011 Opscode, Inc.
6+
# License:: Apache License, Version 2.0
7+
#
8+
# Licensed under the Apache License, Version 2.0 (the "License");
9+
# you may not use this file except in compliance with the License.
10+
# You may obtain a copy of the License at
11+
#
12+
# http://www.apache.org/licenses/LICENSE-2.0
13+
#
14+
# Unless required by applicable law or agreed to in writing, software
15+
# distributed under the License is distributed on an "AS IS" BASIS,
16+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
# See the License for the specific language governing permissions and
18+
# limitations under the License.
19+
#
20+
21+
require 'chef/version'
22+
23+
class Chef
24+
class Knife
25+
class SubcommandLoader
26+
27+
CHEF_FILE_IN_GEM = /chef-[\d]+\.[\d]+\.[\d]+/
28+
CURRENT_CHEF_GEM = /chef-#{Regexp.escape(Chef::VERSION)}/
29+
30+
attr_reader :chef_config_dir
31+
attr_reader :env
32+
33+
def initialize(chef_config_dir, env=ENV)
34+
@chef_config_dir, @env = chef_config_dir, env
35+
end
36+
37+
# Load all the sub-commands
38+
def load_commands
39+
subcommand_files.each { |subcommand| require subcommand }
40+
true
41+
end
42+
43+
# Returns an Array of paths to knife commands located in chef_config_dir/plugins/knife/
44+
# and ~/.chef/plugins/knife/
45+
def site_subcommands
46+
user_specific_files = []
47+
48+
if chef_config_dir
49+
user_specific_files.concat Dir.glob(File.expand_path("plugins/knife/*.rb", chef_config_dir))
50+
end
51+
52+
# finally search ~/.chef/plugins/knife/*.rb
53+
user_specific_files.concat Dir.glob(File.join(env['HOME'], '.chef', 'plugins', 'knife', '*.rb'))
54+
55+
user_specific_files.map! { |path| path[/(.+).rb/, 1] }
56+
user_specific_files
57+
end
58+
59+
# Returns an Array of paths to knife commands built-in to chef, or installed via gem.
60+
# If rubygems is not installed, falls back to globbing the knife directory.
61+
def gem_and_builtin_subcommands
62+
# search all gems for chef/knife/*.rb
63+
require 'rubygems'
64+
find_subcommands_via_rubygems
65+
rescue LoadError
66+
find_subcommands_via_dirglob
67+
end
68+
69+
def subcommand_files
70+
@subcommand_files ||= (gem_and_builtin_subcommands + site_subcommands).flatten.uniq
71+
end
72+
73+
def find_subcommands_via_dirglob
74+
# The "require paths" of the core knife subcommands bundled with chef
75+
files = Dir[File.expand_path('../../knife/*.rb', __FILE__)]
76+
files.map! { |knife_file| knife_file[/#{CHEF_ROOT}#{Regexp.escape(File::SEPARATOR)}(.*)\.rb/,1] }
77+
files
78+
end
79+
80+
def find_subcommands_via_rubygems
81+
files = Gem.find_files 'chef/knife/*.rb'
82+
files.reject! {|f| from_old_gem?(f) }
83+
files.map! do |file|
84+
file[/(#{Regexp.escape File.join('chef', 'knife', '')}.*)\.rb/, 1]
85+
end.uniq!
86+
files
87+
end
88+
89+
private
90+
91+
# wow, this is a sad hack :(
92+
# Gem.find_files finds files in all versions of a gem, which
93+
# means that if chef 0.10 and 0.9.x are installed, we'll try to
94+
# require, e.g., chef/knife/ec2_server_create, which will cause
95+
# a gem activation error. So remove files from older chef gems.
96+
def from_old_gem?(path)
97+
path =~ CHEF_FILE_IN_GEM && path !~ CURRENT_CHEF_GEM
98+
end
99+
end
100+
end
101+
end

‎chef/spec/data/knife-home/.chef/plugins/knife/example_home_subcommand.rb‎

Whitespace-only changes.

‎chef/spec/data/knife-site-subcommands/plugins/knife/example_subcommand.rb‎

Whitespace-only changes.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#
2+
# Author:: Daniel DeLeo (<dan@opscode.com>)
3+
# Copyright:: Copyright (c) 2011 Opscode, Inc.
4+
# License:: Apache License, Version 2.0
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
19+
require File.expand_path('../../../spec_helper.rb', __FILE__)
20+
21+
describe Chef::Knife::SubcommandLoader do
22+
before do
23+
@home = File.join(CHEF_SPEC_DATA, 'knife-home')
24+
@env = {'HOME' => @home}
25+
@loader = Chef::Knife::SubcommandLoader.new(File.join(CHEF_SPEC_DATA, 'knife-site-subcommands'), @env)
26+
end
27+
28+
it "builds a list of the core subcommand file require paths" do
29+
@loader.subcommand_files.should_not be_empty
30+
@loader.subcommand_files.each do |require_path|
31+
require_path.should match(/chef\/knife\/.*|plugins\/knife\/.*/)
32+
end
33+
end
34+
35+
it "finds files installed via rubygems" do
36+
@loader.find_subcommands_via_rubygems.should include('chef/knife/node_create')
37+
@loader.find_subcommands_via_rubygems.each {|path| path.should match(%r[chef/knife/.+])}
38+
end
39+
40+
it "finds files using a dirglob when rubygems is not available" do
41+
@loader.find_subcommands_via_dirglob.should include('chef/knife/node_create')
42+
@loader.find_subcommands_via_dirglob.each {|path| path.should match(%r[chef/knife/.+])}
43+
end
44+
45+
it "finds user-specific subcommands in the user's ~/.chef directory" do
46+
expected_command = File.join(@home, '.chef', 'plugins', 'knife', 'example_home_subcommand')
47+
@loader.site_subcommands.should include(expected_command)
48+
end
49+
50+
it "finds repo specific subcommands by searching for a .chef directory" do
51+
expected_command = File.join(CHEF_SPEC_DATA, 'knife-site-subcommands', 'plugins', 'knife', 'example_subcommand')
52+
@loader.site_subcommands.should include(expected_command)
53+
end
54+
end

‎chef/spec/unit/knife_spec.rb‎

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,6 @@ module KnifeSpecs
3939
Chef::Knife.stub!(:puts)
4040
end
4141

42-
it "builds a list of the core subcommand file require paths" do
43-
Chef::Knife.find_subcommand_files.should_not be_empty
44-
Chef::Knife.find_subcommand_files.each do |require_path|
45-
require_path.should match(/chef\/knife\/.*|plugins\/knife\/.*/)
46-
end
47-
end
48-
4942
describe "after loading a subcommand" do
5043
before do
5144
Chef::Knife.reset_subcommands!

0 commit comments

Comments
 (0)