chef 11.10.0.alpha.1 → 11.10.0.rc.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (131) hide show
  1. data/README.md +57 -36
  2. data/distro/common/html/chef-client.8.html +4 -4
  3. data/distro/common/html/chef-expander.8.html +4 -4
  4. data/distro/common/html/chef-expanderctl.8.html +4 -4
  5. data/distro/common/html/chef-server-webui.8.html +4 -4
  6. data/distro/common/html/chef-server.8.html +4 -4
  7. data/distro/common/html/chef-shell.1.html +4 -4
  8. data/distro/common/html/chef-solo.8.html +4 -4
  9. data/distro/common/html/chef-solr.8.html +5 -5
  10. data/distro/common/html/knife-bootstrap.1.html +4 -4
  11. data/distro/common/html/knife-client.1.html +4 -4
  12. data/distro/common/html/knife-configure.1.html +4 -4
  13. data/distro/common/html/knife-cookbook-site.1.html +4 -4
  14. data/distro/common/html/knife-cookbook.1.html +4 -4
  15. data/distro/common/html/knife-data-bag.1.html +4 -4
  16. data/distro/common/html/knife-environment.1.html +4 -4
  17. data/distro/common/html/knife-exec.1.html +4 -4
  18. data/distro/common/html/knife-index.1.html +4 -4
  19. data/distro/common/html/knife-node.1.html +4 -4
  20. data/distro/common/html/knife-role.1.html +4 -4
  21. data/distro/common/html/knife-search.1.html +4 -4
  22. data/distro/common/html/knife-ssh.1.html +4 -4
  23. data/distro/common/html/knife-status.1.html +4 -4
  24. data/distro/common/html/knife-tag.1.html +4 -4
  25. data/distro/common/html/knife.1.html +4 -4
  26. data/distro/common/man/man1/knife-bootstrap.1 +58 -64
  27. data/distro/common/man/man1/knife-client.1 +19 -22
  28. data/distro/common/man/man1/knife-configure.1 +37 -46
  29. data/distro/common/man/man1/knife-cookbook-site.1 +14 -17
  30. data/distro/common/man/man1/knife-cookbook.1 +15 -18
  31. data/distro/common/man/man1/knife-data-bag.1 +14 -17
  32. data/distro/common/man/man1/knife-delete.1 +38 -47
  33. data/distro/common/man/man1/knife-deps.1 +39 -48
  34. data/distro/common/man/man1/knife-diff.1 +43 -52
  35. data/distro/common/man/man1/knife-download.1 +47 -53
  36. data/distro/common/man/man1/knife-edit.1 +32 -41
  37. data/distro/common/man/man1/knife-environment.1 +14 -17
  38. data/distro/common/man/man1/knife-exec.1 +52 -61
  39. data/distro/common/man/man1/knife-index-rebuild.1 +1 -61
  40. data/distro/common/man/man1/knife-list.1 +47 -59
  41. data/distro/common/man/man1/knife-node.1 +15 -18
  42. data/distro/common/man/man1/knife-raw.1 +28 -46
  43. data/distro/common/man/man1/knife-recipe-list.1 +1 -61
  44. data/distro/common/man/man1/knife-role.1 +19 -25
  45. data/distro/common/man/man1/knife-search.1 +53 -62
  46. data/distro/common/man/man1/knife-show.1 +36 -28
  47. data/distro/common/man/man1/knife-ssh.1 +55 -61
  48. data/distro/common/man/man1/knife-status.1 +34 -43
  49. data/distro/common/man/man1/knife-tag.1 +14 -17
  50. data/distro/common/man/man1/knife-upload.1 +47 -56
  51. data/distro/common/man/man1/knife-user.1 +17 -20
  52. data/distro/common/man/man1/knife-xargs.1 +60 -69
  53. data/lib/chef/application.rb +3 -1
  54. data/lib/chef/application/windows_service.rb +0 -1
  55. data/lib/chef/client.rb +41 -152
  56. data/lib/chef/config.rb +19 -23
  57. data/lib/chef/data_bag.rb +1 -1
  58. data/lib/chef/data_bag_item.rb +1 -1
  59. data/lib/chef/exceptions.rb +8 -0
  60. data/lib/chef/formatters/doc.rb +15 -0
  61. data/lib/chef/formatters/error_inspectors/api_error_formatting.rb +2 -1
  62. data/lib/chef/http.rb +18 -8
  63. data/lib/chef/http/authenticator.rb +4 -0
  64. data/lib/chef/http/cookie_manager.rb +3 -0
  65. data/lib/chef/http/decompressor.rb +4 -0
  66. data/lib/chef/http/json_input.rb +4 -0
  67. data/lib/chef/http/json_output.rb +4 -0
  68. data/lib/chef/http/validate_content_length.rb +94 -0
  69. data/lib/chef/knife.rb +0 -1
  70. data/lib/chef/knife/configure.rb +6 -6
  71. data/lib/chef/knife/cookbook_create.rb +2 -2
  72. data/lib/chef/knife/core/subcommand_loader.rb +49 -3
  73. data/lib/chef/knife/ssh.rb +34 -4
  74. data/lib/chef/mixin/path_sanity.rb +1 -0
  75. data/lib/chef/monologger.rb +1 -2
  76. data/lib/chef/node.rb +7 -0
  77. data/lib/chef/policy_builder.rb +49 -0
  78. data/lib/chef/policy_builder/expand_node_object.rb +230 -0
  79. data/lib/chef/policy_builder/policyfile.rb +338 -0
  80. data/lib/chef/provider/file.rb +15 -5
  81. data/lib/chef/provider/group.rb +6 -2
  82. data/lib/chef/provider/group/windows.rb +12 -2
  83. data/lib/chef/provider/http_request.rb +3 -2
  84. data/lib/chef/provider/package.rb +1 -0
  85. data/lib/chef/provider/package/aix.rb +1 -1
  86. data/lib/chef/provider/service/debian.rb +7 -2
  87. data/lib/chef/resource/file.rb +8 -1
  88. data/lib/chef/resource/package.rb +9 -0
  89. data/lib/chef/resource/service.rb +0 -1
  90. data/lib/chef/rest.rb +2 -0
  91. data/lib/chef/run_context.rb +1 -1
  92. data/lib/chef/util/file_edit.rb +1 -1
  93. data/lib/chef/util/windows/net_group.rb +7 -6
  94. data/lib/chef/version.rb +1 -1
  95. data/lib/chef/win32/version.rb +31 -18
  96. data/spec/data/cookbooks/preseed/templates/default/preseed-template-variables.seed +1 -0
  97. data/spec/functional/resource/file_spec.rb +0 -1
  98. data/spec/functional/resource/group_spec.rb +96 -16
  99. data/spec/functional/resource/package_spec.rb +17 -0
  100. data/spec/functional/resource/user_spec.rb +2 -2
  101. data/spec/functional/win32/versions_spec.rb +39 -0
  102. data/spec/integration/client/client_spec.rb +27 -28
  103. data/spec/spec_helper.rb +2 -0
  104. data/spec/support/platform_helpers.rb +7 -1
  105. data/spec/support/shared/functional/file_resource.rb +83 -43
  106. data/spec/unit/application_spec.rb +7 -5
  107. data/spec/unit/client_spec.rb +10 -3
  108. data/spec/unit/config_spec.rb +0 -30
  109. data/spec/unit/cookbook_spec.rb +1 -0
  110. data/spec/unit/data_bag_item_spec.rb +8 -0
  111. data/spec/unit/data_bag_spec.rb +6 -0
  112. data/spec/unit/http_spec.rb +48 -0
  113. data/spec/unit/knife/core/subcommand_loader_spec.rb +77 -1
  114. data/spec/unit/knife/ssh_spec.rb +107 -0
  115. data/spec/unit/mixin/path_sanity_spec.rb +6 -0
  116. data/spec/unit/mixin/securable_spec.rb +77 -3
  117. data/spec/unit/monologger_spec.rb +45 -0
  118. data/spec/unit/node_spec.rb +16 -0
  119. data/spec/unit/policy_builder/expand_node_object_spec.rb +320 -0
  120. data/spec/unit/policy_builder/policyfile_spec.rb +399 -0
  121. data/spec/unit/policy_builder_spec.rb +26 -0
  122. data/spec/unit/provider/deploy_spec.rb +3 -0
  123. data/spec/unit/provider/group/windows_spec.rb +1 -0
  124. data/spec/unit/provider/http_request_spec.rb +23 -1
  125. data/spec/unit/provider/service/debian_service_spec.rb +50 -19
  126. data/spec/unit/recipe_spec.rb +4 -0
  127. data/spec/unit/resource/package_spec.rb +5 -0
  128. data/spec/unit/rest_spec.rb +375 -278
  129. data/spec/unit/run_context_spec.rb +4 -0
  130. metadata +96 -59
  131. checksums.yaml +0 -7
@@ -127,26 +127,6 @@ class Chef
127
127
  # properly.
128
128
  configurable(:daemonize).writes_value { |v| v }
129
129
 
130
- # Override the config dispatch to set the value of log_location configuration option
131
- #
132
- # === Parameters
133
- # location<IO||String>:: Logging location as either an IO stream or string representing log file path
134
- #
135
- config_attr_writer :log_location do |location|
136
- if location.respond_to? :sync=
137
- location.sync = true
138
- location
139
- elsif location.respond_to? :to_str
140
- begin
141
- f = File.new(location.to_str, "a")
142
- f.sync = true
143
- rescue Errno::ENOENT
144
- raise Chef::Exceptions::ConfigurationError, "Failed to open or create log file at #{location.to_str}"
145
- end
146
- f
147
- end
148
- end
149
-
150
130
  # The root where all local chef object data is stored. cookbooks, data bags,
151
131
  # environments are all assumed to be in separate directories under this.
152
132
  # chef-solo uses these directories for input data. knife commands
@@ -299,6 +279,9 @@ class Chef
299
279
  # logger is the primary mode of output, and the log level is set to :info
300
280
  default :log_level, :auto
301
281
 
282
+ # Logging location as either an IO stream or string representing log file path
283
+ default :log_location, STDOUT
284
+
302
285
  # Using `force_formatter` causes chef to default to formatter output when STDOUT is not a tty
303
286
  default :force_formatter, false
304
287
 
@@ -310,7 +293,6 @@ class Chef
310
293
  default :interval, nil
311
294
  default :once, nil
312
295
  default :json_attribs, nil
313
- default :log_location, STDOUT
314
296
  # toggle info level log items that can create a lot of output
315
297
  default :verbose_logging, true
316
298
  default :node_name, nil
@@ -338,6 +320,16 @@ class Chef
338
320
  default :enable_reporting, true
339
321
  default :enable_reporting_url_fatals, false
340
322
 
323
+ # Policyfile is an experimental feature where a node gets its run list and
324
+ # cookbook version set from a single document on the server instead of
325
+ # expanding the run list and having the server compute the cookbook version
326
+ # set based on environment constraints.
327
+ #
328
+ # Because this feature is experimental, it is not recommended for
329
+ # production use. Developent/release of this feature may not adhere to
330
+ # semver guidelines.
331
+ default :use_policyfile, false
332
+
341
333
  # Set these to enable SSL authentication / mutual-authentication
342
334
  # with the server
343
335
 
@@ -497,8 +489,12 @@ class Chef
497
489
 
498
490
  default :fatal_windows_admin_check, false
499
491
  else
500
- default :user_valid_regex, [ /^([-a-zA-Z0-9_.]+[\\@]?[-a-zA-Z0-9_.]*)$/, /^\d+$/ ]
501
- default :group_valid_regex, [ /^([-a-zA-Z0-9_.\\@^ ]+)$/, /^\d+$/ ]
492
+ # user/group cannot start with '-', '+' or '~'
493
+ # user/group cannot contain ':', ',' or non-space-whitespace or null byte
494
+ # everything else is allowed (UTF-8, spaces, etc) and we delegate to your O/S useradd program to barf or not
495
+ # copies: http://anonscm.debian.org/viewvc/pkg-shadow/debian/trunk/debian/patches/506_relaxed_usernames?view=markup
496
+ default :user_valid_regex, [ /^[^-+~:,\t\r\n\f\0]+[^:,\t\r\n\f\0]*$/ ]
497
+ default :group_valid_regex, [ /^[^-+~:,\t\r\n\f\0]+[^:,\t\r\n\f\0]*$/ ]
502
498
  end
503
499
 
504
500
  # returns a platform specific path to the user home dir
@@ -31,7 +31,7 @@ class Chef
31
31
  include Chef::Mixin::FromFile
32
32
  include Chef::Mixin::ParamsValidate
33
33
 
34
- VALID_NAME = /^[\-[:alnum:]_]+$/
34
+ VALID_NAME = /^[\.\-[:alnum:]_]+$/
35
35
 
36
36
  def self.validate_name!(name)
37
37
  unless name =~ VALID_NAME
@@ -35,7 +35,7 @@ class Chef
35
35
  include Chef::Mixin::FromFile
36
36
  include Chef::Mixin::ParamsValidate
37
37
 
38
- VALID_ID = /^[\-[:alnum:]_]+$/
38
+ VALID_ID = /^[\.\-[:alnum:]_]+$/
39
39
 
40
40
  def self.validate_id!(id_str)
41
41
  if id_str.nil? || ( id_str !~ VALID_ID )
@@ -297,5 +297,13 @@ class Chef
297
297
  # non-GET and non-HEAD request will thus raise an InvalidRedirect.
298
298
  class InvalidRedirect < StandardError; end
299
299
 
300
+ # Raised when the content length of a download does not match the content
301
+ # length declared in the http response.
302
+ class ContentLengthMismatch < RuntimeError
303
+ def initialize(response_length, content_length)
304
+ super "Response body length #{response_length} does not match HTTP Content-Length header #{content_length}."
305
+ end
306
+ end
307
+
300
308
  end
301
309
  end
@@ -230,6 +230,21 @@ class Chef
230
230
  @output.color("\n * Whyrun not supported for #{resource}, bypassing load.", :yellow)
231
231
  end
232
232
 
233
+ # Called before handlers run
234
+ def handlers_start(handler_count)
235
+ puts "\nRunning handlers:"
236
+ end
237
+
238
+ # Called after an individual handler has run
239
+ def handler_executed(handler)
240
+ puts " - #{handler.class.name}"
241
+ end
242
+
243
+ # Called after all handlers have executed
244
+ def handlers_completed
245
+ puts "Running handlers complete\n"
246
+ end
247
+
233
248
  # Called when a provider makes an assumption after a failed assertion
234
249
  # in whyrun mode, in order to allow execution to continue
235
250
  def whyrun_assumption(action, resource, message)
@@ -52,7 +52,8 @@ chef_server_url "#{server_url}"
52
52
  node_name "#{username}"
53
53
  client_key "#{api_key}"
54
54
 
55
- If these settings are correct, your client_key may be invalid.
55
+ If these settings are correct, your client_key may be invalid, or
56
+ you may have a chef user with the same client name as this node.
56
57
  E
57
58
  end
58
59
  end
@@ -165,18 +165,22 @@ class Chef
165
165
  response, rest_request, return_value = send_http_request(method, url, headers, data) do |http_response|
166
166
  if http_response.kind_of?(Net::HTTPSuccess)
167
167
  tempfile = stream_to_tempfile(url, http_response)
168
- if block_given?
169
- begin
170
- yield tempfile
171
- ensure
172
- tempfile && tempfile.close!
173
- end
174
- end
175
168
  end
169
+ apply_stream_complete_middleware(http_response, rest_request, return_value)
176
170
  end
177
- unless response.kind_of?(Net::HTTPSuccess) or response.kind_of?(Net::HTTPRedirection)
171
+
172
+ return nil if response.kind_of?(Net::HTTPRedirection)
173
+ unless response.kind_of?(Net::HTTPSuccess)
178
174
  response.error!
179
175
  end
176
+
177
+ if block_given?
178
+ begin
179
+ yield tempfile
180
+ ensure
181
+ tempfile && tempfile.close!
182
+ end
183
+ end
180
184
  tempfile
181
185
  rescue Exception => e
182
186
  log_failed_request(response, return_value) unless response.nil?
@@ -216,6 +220,12 @@ class Chef
216
220
  end
217
221
  end
218
222
 
223
+ def apply_stream_complete_middleware(response, rest_request, return_value)
224
+ middlewares.reverse.inject([response, rest_request, return_value]) do |res_data, middleware|
225
+ middleware.handle_stream_complete(*res_data)
226
+ end
227
+ end
228
+
219
229
  def log_failed_request(response, return_value)
220
230
  return_value ||= {}
221
231
  error_message = "HTTP Request Returned #{response.code} #{response.message}: "
@@ -52,6 +52,10 @@ class Chef
52
52
  nil
53
53
  end
54
54
 
55
+ def handle_stream_complete(http_response, rest_request, return_value)
56
+ [http_response, rest_request, return_value]
57
+ end
58
+
55
59
  def sign_requests?
56
60
  auth_credentials.sign_requests? && @sign_request
57
61
  end
@@ -50,6 +50,9 @@ class Chef
50
50
  nil
51
51
  end
52
52
 
53
+ def handle_stream_complete(http_response, rest_request, return_value)
54
+ [http_response, rest_request, return_value]
55
+ end
53
56
 
54
57
  end
55
58
  end
@@ -69,6 +69,10 @@ class Chef
69
69
  [http_response, rest_request, return_value]
70
70
  end
71
71
 
72
+ def handle_stream_complete(http_response, rest_request, return_value)
73
+ [http_response, rest_request, return_value]
74
+ end
75
+
72
76
  def decompress_body(response)
73
77
  if gzip_disabled? || response.body.nil?
74
78
  response.body
@@ -48,6 +48,10 @@ class Chef
48
48
  nil
49
49
  end
50
50
 
51
+ def handle_stream_complete(http_response, rest_request, return_value)
52
+ [http_response, rest_request, return_value]
53
+ end
54
+
51
55
  end
52
56
  end
53
57
  end
@@ -60,6 +60,10 @@ class Chef
60
60
  end
61
61
  end
62
62
 
63
+ def handle_stream_complete(http_response, rest_request, return_value)
64
+ [http_response, rest_request, return_value]
65
+ end
66
+
63
67
  def stream_response_handler(response)
64
68
  nil
65
69
  end
@@ -0,0 +1,94 @@
1
+ #--
2
+ # Author:: Lamont Granquist (<lamont@getchef.com>)
3
+ # Copyright:: Copyright (c) 2013 Chef Software, 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 'pp'
20
+ require 'chef/log'
21
+
22
+ class Chef
23
+ class HTTP
24
+
25
+ # Middleware that validates the Content-Length header against the downloaded number of bytes.
26
+ #
27
+ # This must run before the decompressor middleware, since otherwise we will count the uncompressed
28
+ # streamed bytes, rather than the on-the-wire compressed bytes.
29
+ class ValidateContentLength
30
+
31
+ class ContentLengthCounter
32
+ attr_accessor :content_length
33
+
34
+ def initialize
35
+ @content_length = 0
36
+ end
37
+
38
+ def handle_chunk(chunk)
39
+ @content_length += chunk.bytesize
40
+ chunk
41
+ end
42
+ end
43
+
44
+ def initialize(opts={})
45
+ end
46
+
47
+ def handle_request(method, url, headers={}, data=false)
48
+ [method, url, headers, data]
49
+ end
50
+
51
+ def handle_response(http_response, rest_request, return_value)
52
+ unless http_response['content-length']
53
+ Chef::Log.debug("HTTP server did not include a Content-Length header in response, cannot identify truncated downloads.")
54
+ return [http_response, rest_request, return_value]
55
+ end
56
+ validate(response_content_length(http_response), http_response.body.bytesize)
57
+ return [http_response, rest_request, return_value]
58
+ end
59
+
60
+ def handle_stream_complete(http_response, rest_request, return_value)
61
+ if http_response['content-length'].nil?
62
+ Chef::Log.debug("HTTP server did not include a Content-Length header in response, cannot idenfity streamed download.")
63
+ elsif @content_length_counter.nil?
64
+ Chef::Log.debug("No content-length information collected for the streamed download, cannot identify streamed download.")
65
+ else
66
+ validate(response_content_length(http_response), @content_length_counter.content_length)
67
+ end
68
+ return [http_response, rest_request, return_value]
69
+ end
70
+
71
+ def stream_response_handler(response)
72
+ @content_length_counter = ContentLengthCounter.new
73
+ end
74
+
75
+ private
76
+ def response_content_length(response)
77
+ if response['content-length'].is_a?(Array)
78
+ response['content-length'].first.to_i
79
+ else
80
+ response['content-length'].to_i
81
+ end
82
+ end
83
+
84
+ def validate(content_length, response_length)
85
+ Chef::Log.debug "Content-Length header = #{content_length}"
86
+ Chef::Log.debug "Response body length = #{response_length}"
87
+ if response_length != content_length
88
+ raise Chef::Exceptions::ContentLengthMismatch.new(response_length, content_length)
89
+ end
90
+ true
91
+ end
92
+ end
93
+ end
94
+ end
@@ -245,7 +245,6 @@ class Chef
245
245
  ENV['PWD']
246
246
  end || Dir.pwd
247
247
 
248
- puts "Working directory: #{a}"
249
248
  a
250
249
  end
251
250
 
@@ -35,29 +35,29 @@ class Chef
35
35
  option :repository,
36
36
  :short => "-r REPO",
37
37
  :long => "--repository REPO",
38
- :description => "The path to your chef-repo"
38
+ :description => "The path to the chef-repo"
39
39
 
40
40
  option :initial,
41
41
  :short => "-i",
42
42
  :long => "--initial",
43
43
  :boolean => true,
44
- :description => "Create an initial API User"
44
+ :description => "Use to create a API client, typically an administrator client on a freshly-installed server"
45
45
 
46
46
  option :admin_client_name,
47
47
  :long => "--admin-client-name NAME",
48
- :description => "The existing admin clientname (usually admin)"
48
+ :description => "The name of the client, typically the name of the admin client"
49
49
 
50
50
  option :admin_client_key,
51
51
  :long => "--admin-client-key PATH",
52
- :description => "The path to the admin client's private key (usually a file named admin.pem)"
52
+ :description => "The path to the private key used by the client, typically a file named admin.pem"
53
53
 
54
54
  option :validation_client_name,
55
55
  :long => "--validation-client-name NAME",
56
- :description => "The validation clientname (usually chef-validator)"
56
+ :description => "The name of the validation client, typically a client named chef-validator"
57
57
 
58
58
  option :validation_key,
59
59
  :long => "--validation-key PATH",
60
- :description => "The location of the validation key (usually a file named validation.pem)"
60
+ :description => "The path to the validation key used by the client, typically a file named validation.pem"
61
61
 
62
62
  def configure_chef
63
63
  # We are just faking out the system so that you can do this without a key specified
@@ -290,7 +290,7 @@ e.g.
290
290
 
291
291
  Attributes
292
292
  ----------
293
- TODO: List you cookbook attributes here.
293
+ TODO: List your cookbook attributes here.
294
294
 
295
295
  e.g.
296
296
  #### #{cookbook_name}::default
@@ -358,7 +358,7 @@ Requirements
358
358
  toaster #{cookbook_name} needs toaster to brown your bagel.
359
359
 
360
360
  Attributes
361
- TODO: List you cookbook attributes here.
361
+ TODO: List your cookbook attributes here.
362
362
 
363
363
  #{cookbook_name}
364
364
  Key Type Description Default
@@ -60,9 +60,13 @@ class Chef
60
60
  # subcommand loader has been modified to load the plugins by using Kernel.load
61
61
  # with the absolute path.
62
62
  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
66
70
  rescue LoadError
67
71
  find_subcommands_via_dirglob
68
72
  end
@@ -71,6 +75,36 @@ class Chef
71
75
  @subcommand_files ||= (gem_and_builtin_subcommands.values + site_subcommands).flatten.uniq
72
76
  end
73
77
 
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
+
74
108
  def find_subcommands_via_dirglob
75
109
  # The "require paths" of the core knife subcommands bundled with chef
76
110
  files = Dir[File.expand_path('../../../knife/*.rb', __FILE__)]
@@ -93,6 +127,18 @@ class Chef
93
127
  subcommand_files.merge(find_subcommands_via_dirglob)
94
128
  end
95
129
 
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
+
96
142
  private
97
143
 
98
144
  def find_files_latest_gems(glob, check_load_path=true)