chef 11.12.0.alpha.1 → 11.12.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. checksums.yaml +4 -4
  2. data/lib/chef/api_client/registration.rb +46 -9
  3. data/lib/chef/application.rb +1 -0
  4. data/lib/chef/application/client.rb +25 -24
  5. data/lib/chef/client.rb +34 -0
  6. data/lib/chef/config.rb +11 -0
  7. data/lib/chef/cookbook/chefignore.rb +10 -2
  8. data/lib/chef/cookbook/metadata.rb +31 -3
  9. data/lib/chef/cookbook/synchronizer.rb +2 -2
  10. data/lib/chef/cookbook/syntax_check.rb +4 -4
  11. data/lib/chef/encrypted_data_bag_item.rb +37 -1
  12. data/lib/chef/exceptions.rb +1 -0
  13. data/lib/chef/guard_interpreter/default_guard_interpreter.rb +42 -0
  14. data/lib/chef/guard_interpreter/resource_guard_interpreter.rb +122 -0
  15. data/lib/chef/http.rb +0 -1
  16. data/lib/chef/http/decompressor.rb +7 -4
  17. data/lib/chef/http/simple.rb +5 -0
  18. data/lib/chef/http/validate_content_length.rb +28 -12
  19. data/lib/chef/knife.rb +1 -0
  20. data/lib/chef/knife/client_bulk_delete.rb +48 -9
  21. data/lib/chef/knife/client_delete.rb +4 -4
  22. data/lib/chef/knife/cookbook_bulk_delete.rb +1 -1
  23. data/lib/chef/knife/cookbook_upload.rb +17 -7
  24. data/lib/chef/knife/core/bootstrap_context.rb +1 -1
  25. data/lib/chef/knife/core/ui.rb +42 -5
  26. data/lib/chef/knife/node_run_list_add.rb +31 -2
  27. data/lib/chef/knife/ssh.rb +44 -31
  28. data/lib/chef/knife/ssl_check.rb +213 -0
  29. data/lib/chef/knife/ssl_fetch.rb +145 -0
  30. data/lib/chef/mixin/deep_merge.rb +13 -5
  31. data/lib/chef/mixin/shell_out.rb +9 -3
  32. data/lib/chef/node.rb +23 -4
  33. data/lib/chef/node/immutable_collections.rb +32 -0
  34. data/lib/chef/platform/provider_mapping.rb +21 -18
  35. data/lib/chef/platform/query_helpers.rb +10 -2
  36. data/lib/chef/policy_builder/expand_node_object.rb +3 -6
  37. data/lib/chef/provider/cron.rb +25 -3
  38. data/lib/chef/provider/mount/mount.rb +1 -1
  39. data/lib/chef/provider/package/dpkg.rb +2 -1
  40. data/lib/chef/provider/package/windows.rb +80 -0
  41. data/lib/chef/provider/package/windows/msi.rb +69 -0
  42. data/lib/chef/provider/powershell_script.rb +19 -6
  43. data/lib/chef/provider/service/solaris.rb +11 -7
  44. data/lib/chef/resource.rb +18 -5
  45. data/lib/chef/resource/conditional.rb +20 -7
  46. data/lib/chef/resource/cron.rb +18 -2
  47. data/lib/chef/resource/execute.rb +0 -2
  48. data/lib/chef/resource/powershell_script.rb +23 -1
  49. data/lib/chef/resource/script.rb +25 -0
  50. data/lib/chef/resource/subversion.rb +4 -0
  51. data/lib/chef/resource/windows_package.rb +79 -0
  52. data/lib/chef/resource/windows_script.rb +0 -5
  53. data/lib/chef/resources.rb +1 -0
  54. data/lib/chef/rest.rb +6 -1
  55. data/lib/chef/run_context.rb +22 -2
  56. data/lib/chef/run_context/cookbook_compiler.rb +12 -0
  57. data/lib/chef/util/editor.rb +92 -0
  58. data/lib/chef/util/file_edit.rb +22 -54
  59. data/lib/chef/version.rb +2 -2
  60. data/lib/chef/win32/api/installer.rb +166 -0
  61. data/lib/chef/win32/version.rb +8 -0
  62. data/spec/data/standalone_cookbook/Gemfile +1 -0
  63. data/spec/data/standalone_cookbook/chefignore +9 -0
  64. data/spec/data/standalone_cookbook/recipes/default.rb +3 -0
  65. data/spec/data/standalone_cookbook/vendor/bundle/ruby/2.0.0/gems/multi_json-1.9.0/lib/multi_json.rb +1 -0
  66. data/spec/functional/resource/powershell_spec.rb +262 -1
  67. data/spec/functional/win32/versions_spec.rb +3 -3
  68. data/spec/integration/knife/chefignore_spec.rb +1 -2
  69. data/spec/integration/knife/raw_spec.rb +8 -13
  70. data/spec/integration/knife/redirection_spec.rb +6 -14
  71. data/spec/integration/solo/solo_spec.rb +19 -0
  72. data/spec/support/shared/functional/windows_script.rb +1 -1
  73. data/spec/support/shared/integration/app_server_support.rb +42 -0
  74. data/spec/support/shared/integration/integration_helper.rb +1 -0
  75. data/spec/support/shared/unit/script_resource.rb +38 -0
  76. data/spec/unit/api_client/registration_spec.rb +109 -38
  77. data/spec/unit/application/client_spec.rb +48 -1
  78. data/spec/unit/cookbook/chefignore_spec.rb +10 -0
  79. data/spec/unit/cookbook/metadata_spec.rb +45 -1
  80. data/spec/unit/cookbook/syntax_check_spec.rb +28 -0
  81. data/spec/unit/cookbook_spec.rb +0 -10
  82. data/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb +56 -0
  83. data/spec/unit/http/simple_spec.rb +32 -0
  84. data/spec/unit/http/validate_content_length_spec.rb +187 -0
  85. data/spec/unit/knife/bootstrap_spec.rb +13 -4
  86. data/spec/unit/knife/client_bulk_delete_spec.rb +123 -38
  87. data/spec/unit/knife/client_delete_spec.rb +4 -4
  88. data/spec/unit/knife/cookbook_upload_spec.rb +181 -88
  89. data/spec/unit/knife/core/bootstrap_context_spec.rb +11 -1
  90. data/spec/unit/knife/core/ui_spec.rb +109 -38
  91. data/spec/unit/knife/node_run_list_add_spec.rb +24 -1
  92. data/spec/unit/knife/ssh_spec.rb +17 -6
  93. data/spec/unit/knife/ssl_check_spec.rb +187 -0
  94. data/spec/unit/knife/ssl_fetch_spec.rb +151 -0
  95. data/spec/unit/mixin/deep_merge_spec.rb +17 -0
  96. data/spec/unit/node/immutable_collections_spec.rb +55 -0
  97. data/spec/unit/node_spec.rb +9 -0
  98. data/spec/unit/platform/query_helpers_spec.rb +32 -0
  99. data/spec/unit/platform_spec.rb +193 -175
  100. data/spec/unit/policy_builder/expand_node_object_spec.rb +1 -1
  101. data/spec/unit/provider/cron_spec.rb +175 -1
  102. data/spec/unit/provider/mount/mount_spec.rb +33 -3
  103. data/spec/unit/provider/package/dpkg_spec.rb +4 -0
  104. data/spec/unit/provider/package/windows/msi_spec.rb +60 -0
  105. data/spec/unit/provider/package/windows_spec.rb +80 -0
  106. data/spec/unit/provider/service/macosx_spec.rb +3 -3
  107. data/spec/unit/provider/service/solaris_smf_service_spec.rb +35 -10
  108. data/spec/unit/pure_application_spec.rb +32 -0
  109. data/spec/unit/recipe_spec.rb +4 -0
  110. data/spec/unit/resource/conditional_spec.rb +13 -12
  111. data/spec/unit/resource/cron_spec.rb +7 -2
  112. data/spec/unit/resource/powershell_spec.rb +85 -2
  113. data/spec/unit/resource/subversion_spec.rb +5 -0
  114. data/spec/unit/resource/windows_package_spec.rb +74 -0
  115. data/spec/unit/resource_spec.rb +23 -1
  116. data/spec/unit/rest_spec.rb +15 -0
  117. data/spec/unit/run_context/cookbook_compiler_spec.rb +12 -0
  118. data/spec/unit/run_context_spec.rb +7 -0
  119. data/spec/unit/util/editor_spec.rb +152 -0
  120. data/spec/unit/util/file_edit_spec.rb +37 -1
  121. metadata +41 -30
@@ -76,6 +76,7 @@ class Chef
76
76
  class CookbookNotFoundInRepo < ArgumentError; end
77
77
  class RecipeNotFound < ArgumentError; end
78
78
  class AttributeNotFound < RuntimeError; end
79
+ class MissingCookbookDependency < StandardError; end # CHEF-5120
79
80
  class InvalidCommandOption < RuntimeError; end
80
81
  class CommandTimeout < RuntimeError; end
81
82
  class RequestedUIDUnavailable < RuntimeError; end
@@ -0,0 +1,42 @@
1
+ #
2
+ # Author:: Adam Edwards (<adamed@getchef.com>)
3
+ # Copyright:: Copyright (c) 2014 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
+ class Chef
20
+ class GuardInterpreter
21
+ class DefaultGuardInterpreter
22
+ include Chef::Mixin::ShellOut
23
+
24
+ protected
25
+
26
+ def initialize(command, opts)
27
+ @command = command
28
+ @command_opts = opts
29
+ end
30
+
31
+ public
32
+
33
+ def evaluate
34
+ shell_out(@command, @command_opts).status.success?
35
+ rescue Chef::Exceptions::CommandTimeout
36
+ Chef::Log.warn "Command '#{@command}' timed out"
37
+ false
38
+ end
39
+ end
40
+ end
41
+ end
42
+
@@ -0,0 +1,122 @@
1
+ #
2
+ # Author:: Adam Edwards (<adamed@getchef.com>)
3
+ # Copyright:: Copyright (c) 2014 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 'chef/guard_interpreter/default_guard_interpreter'
20
+
21
+ class Chef
22
+ class GuardInterpreter
23
+ class ResourceGuardInterpreter < DefaultGuardInterpreter
24
+
25
+ def initialize(parent_resource, command, opts, &block)
26
+ super(command, opts)
27
+ @parent_resource = parent_resource
28
+ @resource = get_interpreter_resource(parent_resource)
29
+ end
30
+
31
+ def evaluate
32
+ # Add attributes inherited from the parent class
33
+ # to the resource
34
+ merge_inherited_attributes
35
+
36
+ # Script resources have a code attribute, which is
37
+ # what is used to execute the command, so include
38
+ # that with attributes specified by caller in opts
39
+ block_attributes = @command_opts.merge({:code => @command})
40
+
41
+ # Handles cases like powershell_script where default
42
+ # attributes are different when used in a guard vs. not. For
43
+ # powershell_script in particular, this will go away when
44
+ # the one attribue that causes this changes its default to be
45
+ # the same after some period to prepare for deprecation
46
+ if @resource.class.respond_to?(:get_default_attributes)
47
+ block_attributes = @resource.class.send(:get_default_attributes, @command_opts).merge(block_attributes)
48
+ end
49
+
50
+ resource_block = block_from_attributes(block_attributes)
51
+ evaluate_action(nil, &resource_block)
52
+ end
53
+
54
+ protected
55
+
56
+ def evaluate_action(action=nil, &block)
57
+ @resource.instance_eval(&block)
58
+
59
+ run_action = action || @resource.action
60
+
61
+ begin
62
+ @resource.run_action(run_action)
63
+ resource_updated = @resource.updated
64
+ rescue Mixlib::ShellOut::ShellCommandFailed
65
+ resource_updated = nil
66
+ end
67
+
68
+ resource_updated
69
+ end
70
+
71
+ def get_interpreter_resource(parent_resource)
72
+ if parent_resource.nil? || parent_resource.node.nil?
73
+ raise ArgumentError, "Node for guard resource parent must not be nil"
74
+ end
75
+
76
+ resource_class = Chef::Resource.resource_for_node(parent_resource.guard_interpreter, parent_resource.node)
77
+
78
+ if resource_class.nil?
79
+ raise ArgumentError, "Specified guard_interpreter resource #{parent_resource.guard_interpreter.to_s} unknown for this platform"
80
+ end
81
+
82
+ if ! resource_class.ancestors.include?(Chef::Resource::Script)
83
+ raise ArgumentError, "Specified guard interpreter class #{resource_class} must be a kind of Chef::Resource::Script resource"
84
+ end
85
+
86
+ empty_events = Chef::EventDispatch::Dispatcher.new
87
+ anonymous_run_context = Chef::RunContext.new(parent_resource.node, {}, empty_events)
88
+ interpreter_resource = resource_class.new('Guard resource', anonymous_run_context)
89
+
90
+ interpreter_resource
91
+ end
92
+
93
+ def block_from_attributes(attributes)
94
+ Proc.new do
95
+ attributes.keys.each do |attribute_name|
96
+ send(attribute_name, attributes[attribute_name]) if respond_to?(attribute_name)
97
+ end
98
+ end
99
+ end
100
+
101
+ def merge_inherited_attributes
102
+ inherited_attributes = []
103
+
104
+ if @parent_resource.class.respond_to?(:guard_inherited_attributes)
105
+ inherited_attributes = @parent_resource.class.send(:guard_inherited_attributes)
106
+ end
107
+
108
+ if inherited_attributes && !inherited_attributes.empty?
109
+ inherited_attributes.each do |attribute|
110
+ if @parent_resource.respond_to?(attribute) && @resource.respond_to?(attribute)
111
+ parent_value = @parent_resource.send(attribute)
112
+ child_value = @resource.send(attribute)
113
+ if parent_value || child_value
114
+ @resource.send(attribute, parent_value)
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -393,4 +393,3 @@ class Chef
393
393
 
394
394
  end
395
395
  end
396
-
@@ -94,16 +94,21 @@ class Chef
94
94
  # object you can use to unzip/inflate a streaming response.
95
95
  def stream_response_handler(response)
96
96
  if gzip_disabled?
97
+ Chef::Log.debug "disable_gzip is set. \
98
+ Not using #{response[CONTENT_ENCODING]} \
99
+ and initializing noop stream deflator."
97
100
  NoopInflater.new
98
101
  else
99
102
  case response[CONTENT_ENCODING]
100
103
  when GZIP
101
- Chef::Log.debug "decompressing gzip stream"
104
+ Chef::Log.debug "Initializing gzip stream deflator"
102
105
  GzipInflater.new
103
106
  when DEFLATE
104
- Chef::Log.debug "decompressing inflate stream"
107
+ Chef::Log.debug "Initializing deflate stream deflator"
105
108
  DeflateInflater.new
106
109
  else
110
+ Chef::Log.debug "content_encoding = '#{response[CONTENT_ENCODING]}' \
111
+ initializing noop stream deflator."
107
112
  NoopInflater.new
108
113
  end
109
114
  end
@@ -137,5 +142,3 @@ class Chef
137
142
  end
138
143
  end
139
144
  end
140
-
141
-
@@ -11,6 +11,11 @@ class Chef
11
11
  use Decompressor
12
12
  use CookieManager
13
13
 
14
+ # ValidateContentLength should come after Decompressor
15
+ # because the order of middlewares is reversed when handling
16
+ # responses.
17
+ use ValidateContentLength
18
+
14
19
  end
15
20
  end
16
21
  end
@@ -49,22 +49,20 @@ class Chef
49
49
  end
50
50
 
51
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)
52
+ validate(http_response, http_response.body.bytesize) if http_response && http_response.body
57
53
  return [http_response, rest_request, return_value]
58
54
  end
59
55
 
60
56
  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?
57
+ if @content_length_counter.nil?
64
58
  Chef::Log.debug("No content-length information collected for the streamed download, cannot identify streamed download.")
65
59
  else
66
- validate(response_content_length(http_response), @content_length_counter.content_length)
60
+ validate(http_response, @content_length_counter.content_length)
67
61
  end
62
+
63
+ # Make sure the counter is reset since this object might get used
64
+ # again. See CHEF-5100
65
+ @content_length_counter = nil
68
66
  return [http_response, rest_request, return_value]
69
67
  end
70
68
 
@@ -73,7 +71,9 @@ class Chef
73
71
  end
74
72
 
75
73
  private
74
+
76
75
  def response_content_length(response)
76
+ return nil if response['content-length'].nil?
77
77
  if response['content-length'].is_a?(Array)
78
78
  response['content-length'].first.to_i
79
79
  else
@@ -81,12 +81,28 @@ class Chef
81
81
  end
82
82
  end
83
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}"
84
+ def validate(http_response, response_length)
85
+ content_length = response_content_length(http_response)
86
+ transfer_encoding = http_response['transfer-encoding']
87
+ content_encoding = http_response['content-encoding']
88
+
89
+ if content_length.nil?
90
+ Chef::Log.debug "HTTP server did not include a Content-Length header in response, cannot identify truncated downloads."
91
+ return true
92
+ end
93
+
94
+ # if Transfer-Encoding is set the RFC states that we must ignore the Content-Length field
95
+ # CHEF-5041: some proxies uncompress gzip content, leave the incorrect content-length, but set the transfer-encoding field
96
+ unless transfer_encoding.nil?
97
+ Chef::Log.debug "Transfer-Encoding header is set, skipping Content-Length check."
98
+ return true
99
+ end
100
+
87
101
  if response_length != content_length
88
102
  raise Chef::Exceptions::ContentLengthMismatch.new(response_length, content_length)
89
103
  end
104
+
105
+ Chef::Log.debug "Content-Length validated correctly."
90
106
  true
91
107
  end
92
108
  end
@@ -421,6 +421,7 @@ class Chef
421
421
 
422
422
  # Don't try to load a knife.rb if it wasn't specified.
423
423
  if config[:config_file]
424
+ Chef::Config.config_file = config[:config_file]
424
425
  fetcher = Chef::ConfigFetcher.new(config[:config_file], Chef::Config.config_file_jail)
425
426
  if fetcher.config_missing?
426
427
  ui.error("Specified config file #{config[:config_file]} does not exist#{Chef::Config.config_file_jail ? " or is not under config file jail #{Chef::Config.config_file_jail}" : ""}!")
@@ -27,6 +27,11 @@ class Chef
27
27
  require 'chef/json_compat'
28
28
  end
29
29
 
30
+ option :delete_validators,
31
+ :short => "-D",
32
+ :long => "--delete-validators",
33
+ :description => "Force deletion of clients if they're validators"
34
+
30
35
  banner "knife client bulk delete REGEX (options)"
31
36
 
32
37
  def run
@@ -38,28 +43,62 @@ class Chef
38
43
 
39
44
  matcher = /#{name_args[0]}/
40
45
  clients_to_delete = {}
46
+ validators_to_delete = {}
41
47
  all_clients.each do |name, client|
42
48
  next unless name =~ matcher
43
- clients_to_delete[client.name] = client
49
+ if client.validator
50
+ validators_to_delete[client.name] = client
51
+ else
52
+ clients_to_delete[client.name] = client
53
+ end
44
54
  end
45
55
 
46
- if clients_to_delete.empty?
56
+ if clients_to_delete.empty? && validators_to_delete.empty?
47
57
  ui.info "No clients match the expression /#{name_args[0]}/"
48
58
  exit 0
49
59
  end
50
60
 
51
- ui.msg("The following clients will be deleted:")
52
- ui.msg("")
53
- ui.msg(ui.list(clients_to_delete.keys.sort, :columns_down))
54
- ui.msg("")
55
- ui.confirm("Are you sure you want to delete these clients")
61
+ check_and_delete_validators(validators_to_delete)
62
+ check_and_delete_clients(clients_to_delete)
63
+ end
56
64
 
57
- clients_to_delete.sort.each do |name, client|
65
+ def check_and_delete_validators(validators)
66
+ unless validators.empty?
67
+ unless config[:delete_validators]
68
+ ui.msg("Following clients are validators and will not be deleted.")
69
+ print_clients(validators)
70
+ ui.msg("You must specify --delete-validators to delete the validator clients")
71
+ else
72
+ ui.msg("The following validators will be deleted:")
73
+ print_clients(validators)
74
+ if ui.confirm_without_exit("Are you sure you want to delete these validators")
75
+ destroy_clients(validators)
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ def check_and_delete_clients(clients)
82
+ unless clients.empty?
83
+ ui.msg("The following clients will be deleted:")
84
+ print_clients(clients)
85
+ ui.confirm("Are you sure you want to delete these clients")
86
+ destroy_clients(clients)
87
+ end
88
+ end
89
+
90
+ def destroy_clients(clients)
91
+ clients.sort.each do |name, client|
58
92
  client.destroy
59
93
  ui.msg("Deleted client #{name}")
60
94
  end
61
95
  end
96
+
97
+ def print_clients(clients)
98
+ ui.msg("")
99
+ ui.msg(ui.list(clients.keys.sort, :columns_down))
100
+ ui.msg("")
101
+ end
62
102
  end
63
103
  end
64
104
  end
65
-
@@ -27,9 +27,9 @@ class Chef
27
27
  require 'chef/json_compat'
28
28
  end
29
29
 
30
- option :force,
31
- :short => "-f",
32
- :long => "--force",
30
+ option :delete_validators,
31
+ :short => "-D",
32
+ :long => "--delete-validators",
33
33
  :description => "Force deletion of client if it's a validator"
34
34
 
35
35
  banner "knife client delete CLIENT (options)"
@@ -46,7 +46,7 @@ class Chef
46
46
  delete_object(Chef::ApiClient, @client_name, 'client') {
47
47
  object = Chef::ApiClient.load(@client_name)
48
48
  if object.validator
49
- unless config[:force]
49
+ unless config[:delete_validators]
50
50
  ui.fatal("You must specify --force to delete the validator client #{@client_name}")
51
51
  exit 2
52
52
  end
@@ -49,7 +49,7 @@ class Chef
49
49
  ui.msg ""
50
50
 
51
51
  unless config[:yes]
52
- ui.confirm("Do you really want to delete these cookbooks? (Y/N) ", false)
52
+ ui.confirm("Do you really want to delete these cookbooks")
53
53
 
54
54
  if config[:purge]
55
55
  ui.msg("Files that are common to multiple cookbooks are shared, so purging the files may break other cookbooks.")
@@ -93,6 +93,7 @@ class Chef
93
93
  end
94
94
 
95
95
  assert_environment_valid!
96
+ warn_about_cookbook_shadowing
96
97
  version_constraints_to_update = {}
97
98
  upload_failures = 0
98
99
  upload_ok = 0
@@ -139,6 +140,7 @@ class Chef
139
140
  end
140
141
  end
141
142
 
143
+
142
144
  upload_failures += @name_args.length - @cookbooks_to_upload.length
143
145
 
144
146
  if upload_failures == 0
@@ -199,6 +201,10 @@ class Chef
199
201
  end
200
202
 
201
203
  def warn_about_cookbook_shadowing
204
+ # because cookbooks are lazy-loaded, we have to force the loader
205
+ # to load the cookbooks the user intends to upload here:
206
+ cookbooks_to_upload
207
+
202
208
  unless cookbook_repo.merged_cookbooks.empty?
203
209
  ui.warn "* " * 40
204
210
  ui.warn(<<-WARNING)
@@ -257,14 +263,18 @@ WARNING
257
263
  end
258
264
 
259
265
  def check_for_dependencies!(cookbook)
260
- # for each dependency, check if the version is on the server, or
266
+ # for all dependencies, check if the version is on the server, or
261
267
  # the version is in the cookbooks being uploaded. If not, exit and warn the user.
262
- cookbook.metadata.dependencies.each do |cookbook_name, version|
263
- unless check_server_side_cookbooks(cookbook_name, version) || check_uploading_cookbooks(cookbook_name, version)
264
- ui.error "Cookbook #{cookbook.name} depends on cookbook '#{cookbook_name}' version '#{version}',"
265
- ui.error "which is not currently being uploaded and cannot be found on the server."
266
- exit 1
267
- end
268
+ missing_dependencies = cookbook.metadata.dependencies.reject do |cookbook_name, version|
269
+ check_server_side_cookbooks(cookbook_name, version) || check_uploading_cookbooks(cookbook_name, version)
270
+ end
271
+
272
+ unless missing_dependencies.empty?
273
+ missing_cookbook_names = missing_dependencies.map { |cookbook_name, version| "'#{cookbook_name}' version '#{version}'"}
274
+ ui.error "Cookbook #{cookbook.name} depends on cookbooks which are not currently"
275
+ ui.error "being uploaded and cannot be found on the server."
276
+ ui.error "The missing cookbook(s) are: #{missing_cookbook_names.join(', ')}"
277
+ exit 1
268
278
  end
269
279
  end
270
280