chef 12.5.1 → 12.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (221) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +6 -1
  3. data/README.md +6 -4
  4. data/Rakefile +1 -4
  5. data/chef-windows.gemspec +21 -0
  6. data/chef.gemspec +58 -0
  7. data/lib/chef/api_client/registration.rb +9 -4
  8. data/lib/chef/application.rb +3 -84
  9. data/lib/chef/application/apply.rb +9 -2
  10. data/lib/chef/application/client.rb +8 -3
  11. data/lib/chef/application/solo.rb +7 -1
  12. data/lib/chef/application/windows_service.rb +21 -6
  13. data/lib/chef/application/windows_service_manager.rb +2 -3
  14. data/lib/chef/audit/runner.rb +1 -0
  15. data/lib/chef/chef_class.rb +1 -11
  16. data/lib/chef/chef_fs/chef_fs_data_store.rb +181 -2
  17. data/lib/chef/chef_fs/file_system/cookbook_subdir.rb +5 -0
  18. data/lib/chef/chef_fs/file_system/file_system_entry.rb +11 -7
  19. data/lib/chef/client.rb +28 -1
  20. data/lib/chef/cookbook/cookbook_collection.rb +14 -1
  21. data/lib/chef/cookbook/cookbook_version_loader.rb +1 -1
  22. data/lib/chef/cookbook/metadata.rb +115 -9
  23. data/lib/chef/cookbook/remote_file_vendor.rb +1 -1
  24. data/lib/chef/cookbook_version.rb +6 -2
  25. data/lib/chef/data_bag.rb +1 -1
  26. data/lib/chef/data_bag_item.rb +1 -1
  27. data/lib/chef/digester.rb +5 -1
  28. data/lib/chef/dsl/chef_provisioning.rb +57 -0
  29. data/lib/chef/dsl/cheffish.rb +64 -0
  30. data/lib/chef/dsl/declare_resource.rb +108 -0
  31. data/lib/chef/dsl/platform_introspection.rb +3 -3
  32. data/lib/chef/dsl/recipe.rb +3 -73
  33. data/lib/chef/dsl/resources.rb +27 -1
  34. data/lib/chef/event_dispatch/base.rb +3 -0
  35. data/lib/chef/event_dispatch/dispatcher.rb +5 -0
  36. data/lib/chef/event_dispatch/events_output_stream.rb +8 -0
  37. data/lib/chef/exceptions.rb +21 -1
  38. data/lib/chef/file_access_control/unix.rb +12 -12
  39. data/lib/chef/file_content_management/deploy/cp.rb +2 -2
  40. data/lib/chef/file_content_management/deploy/mv_unix.rb +4 -4
  41. data/lib/chef/file_content_management/deploy/mv_windows.rb +1 -1
  42. data/lib/chef/formatters/base.rb +7 -0
  43. data/lib/chef/formatters/error_inspectors/compile_error_inspector.rb +2 -2
  44. data/lib/chef/formatters/indentable_output_stream.rb +5 -0
  45. data/lib/chef/http.rb +19 -3
  46. data/lib/chef/http/decompressor.rb +2 -2
  47. data/lib/chef/json_compat.rb +1 -0
  48. data/lib/chef/knife.rb +16 -2
  49. data/lib/chef/knife/bootstrap.rb +55 -10
  50. data/lib/chef/knife/cookbook_site_install.rb +5 -1
  51. data/lib/chef/knife/core/bootstrap_context.rb +2 -1
  52. data/lib/chef/knife/core/node_presenter.rb +1 -1
  53. data/lib/chef/knife/ssh.rb +30 -16
  54. data/lib/chef/knife/ssl_check.rb +4 -2
  55. data/lib/chef/knife/ssl_fetch.rb +3 -2
  56. data/lib/chef/knife/status.rb +14 -1
  57. data/lib/chef/log.rb +14 -0
  58. data/lib/chef/mixin/get_source_from_package.rb +7 -2
  59. data/lib/chef/mixin/properties.rb +302 -0
  60. data/lib/chef/mixin/proxified_socket.rb +38 -0
  61. data/lib/chef/mixin/subclass_directive.rb +37 -0
  62. data/lib/chef/node.rb +13 -5
  63. data/lib/chef/platform/query_helpers.rb +14 -3
  64. data/lib/chef/platform/service_helpers.rb +20 -38
  65. data/lib/chef/policy_builder/expand_node_object.rb +3 -0
  66. data/lib/chef/policy_builder/policyfile.rb +1 -0
  67. data/lib/chef/property.rb +51 -12
  68. data/lib/chef/provider.rb +40 -35
  69. data/lib/chef/provider/deploy.rb +1 -1
  70. data/lib/chef/provider/dsc_resource.rb +54 -20
  71. data/lib/chef/provider/execute.rb +25 -4
  72. data/lib/chef/provider/group.rb +1 -1
  73. data/lib/chef/provider/lwrp_base.rb +1 -0
  74. data/lib/chef/provider/package.rb +76 -30
  75. data/lib/chef/provider/package/dpkg.rb +152 -69
  76. data/lib/chef/provider/package/openbsd.rb +6 -8
  77. data/lib/chef/provider/package/solaris.rb +2 -0
  78. data/lib/chef/provider/package/windows.rb +95 -14
  79. data/lib/chef/provider/package/windows/exe.rb +129 -0
  80. data/lib/chef/provider/package/windows/msi.rb +37 -13
  81. data/lib/chef/provider/package/windows/registry_uninstall_entry.rb +89 -0
  82. data/lib/chef/provider/package/yum.rb +13 -3
  83. data/lib/chef/provider/powershell_script.rb +3 -0
  84. data/lib/chef/provider/remote_file/cache_control_data.rb +37 -4
  85. data/lib/chef/provider/remote_file/http.rb +1 -1
  86. data/lib/chef/provider/script.rb +1 -0
  87. data/lib/chef/provider/service.rb +13 -10
  88. data/lib/chef/provider/service/solaris.rb +43 -17
  89. data/lib/chef/provider/service/upstart.rb +3 -3
  90. data/lib/chef/provider/user.rb +1 -1
  91. data/lib/chef/provider/user/dscl.rb +111 -100
  92. data/lib/chef/provider/user/windows.rb +5 -3
  93. data/lib/chef/recipe.rb +3 -5
  94. data/lib/chef/resource.rb +77 -320
  95. data/lib/chef/resource/action_class.rb +4 -0
  96. data/lib/chef/resource/dpkg_package.rb +4 -3
  97. data/lib/chef/resource/dsc_resource.rb +40 -2
  98. data/lib/chef/resource/execute.rb +9 -1
  99. data/lib/chef/resource/ksh.rb +32 -0
  100. data/lib/chef/resource/lwrp_base.rb +6 -10
  101. data/lib/chef/resource/package.rb +8 -9
  102. data/lib/chef/resource/registry_key.rb +1 -1
  103. data/lib/chef/resource/resource_notification.rb +14 -1
  104. data/lib/chef/resource/script.rb +1 -1
  105. data/lib/chef/resource/windows_package.rb +1 -1
  106. data/lib/chef/resource_builder.rb +14 -7
  107. data/lib/chef/resource_reporter.rb +6 -0
  108. data/lib/chef/resources.rb +1 -7
  109. data/lib/chef/rest.rb +1 -1
  110. data/lib/chef/run_context.rb +45 -2
  111. data/lib/chef/run_list/run_list_expansion.rb +47 -0
  112. data/lib/chef/runner.rb +25 -0
  113. data/lib/chef/search/query.rb +16 -2
  114. data/lib/chef/util/diff.rb +2 -2
  115. data/lib/chef/util/powershell/ps_credential.rb +2 -3
  116. data/lib/chef/version.rb +1 -1
  117. data/lib/chef/win32/api/file.rb +51 -1
  118. data/lib/chef/win32/file.rb +5 -0
  119. data/lib/chef/win32/file/version_info.rb +93 -0
  120. data/lib/chef/win32/mutex.rb +1 -1
  121. data/spec/data/apt/chef-integration-test2-1.0/debian/changelog +5 -0
  122. data/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2.debhelper.log +45 -0
  123. data/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2.substvars +1 -0
  124. data/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/conffiles +1 -0
  125. data/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/control +10 -0
  126. data/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/md5sums +1 -0
  127. data/spec/data/apt/chef-integration-test2-1.0/debian/compat +1 -0
  128. data/spec/data/apt/chef-integration-test2-1.0/debian/conffiles +1 -0
  129. data/spec/data/apt/chef-integration-test2-1.0/debian/control +13 -0
  130. data/spec/data/apt/chef-integration-test2-1.0/debian/copyright +34 -0
  131. data/spec/data/apt/chef-integration-test2-1.0/debian/files +1 -0
  132. data/spec/data/apt/chef-integration-test2-1.0/debian/rules +13 -0
  133. data/spec/data/apt/chef-integration-test2-1.0/debian/source/format +1 -0
  134. data/spec/data/apt/chef-integration-test2_1.0-1.debian.tar.gz +0 -0
  135. data/spec/data/apt/chef-integration-test2_1.0-1.dsc +18 -0
  136. data/spec/data/apt/chef-integration-test2_1.0-1_amd64.build +91 -0
  137. data/spec/data/apt/chef-integration-test2_1.0-1_amd64.changes +31 -0
  138. data/spec/data/apt/chef-integration-test2_1.0-1_amd64.deb +0 -0
  139. data/spec/data/apt/chef-integration-test2_1.0.orig.tar.gz +0 -0
  140. data/spec/functional/application_spec.rb +1 -1
  141. data/spec/functional/audit/runner_spec.rb +4 -0
  142. data/spec/functional/knife/ssh_spec.rb +5 -5
  143. data/spec/functional/notifications_spec.rb +74 -4
  144. data/spec/functional/resource/aix_service_spec.rb +2 -2
  145. data/spec/functional/resource/dpkg_package_spec.rb +339 -0
  146. data/spec/functional/resource/ifconfig_spec.rb +3 -1
  147. data/spec/functional/resource/mount_spec.rb +5 -2
  148. data/spec/functional/resource/package_spec.rb +1 -1
  149. data/spec/functional/resource/user/windows_spec.rb +8 -0
  150. data/spec/functional/resource/windows_package_spec.rb +177 -0
  151. data/spec/functional/win32/version_info_spec.rb +50 -0
  152. data/spec/integration/client/client_spec.rb +80 -0
  153. data/spec/integration/knife/download_spec.rb +9 -0
  154. data/spec/integration/knife/upload_spec.rb +28 -1
  155. data/spec/integration/recipes/lwrp_inline_resources_spec.rb +93 -23
  156. data/spec/integration/recipes/resource_action_spec.rb +211 -116
  157. data/spec/integration/recipes/resource_converge_if_changed_spec.rb +72 -0
  158. data/spec/integration/solo/solo_spec.rb +34 -0
  159. data/spec/spec_helper.rb +11 -1
  160. data/spec/support/platform_helpers.rb +8 -0
  161. data/spec/support/shared/integration/integration_helper.rb +6 -0
  162. data/spec/support/shared/unit/execute_resource.rb +5 -0
  163. data/spec/support/shared/unit/platform_introspector.rb +7 -0
  164. data/spec/tiny_server.rb +6 -2
  165. data/spec/unit/api_client/registration_spec.rb +5 -4
  166. data/spec/unit/application_spec.rb +1 -181
  167. data/spec/unit/chef_fs/file_system/cookbook_subdir_spec.rb +34 -0
  168. data/spec/unit/cookbook/metadata_spec.rb +122 -2
  169. data/spec/unit/http_spec.rb +102 -0
  170. data/spec/unit/knife/bootstrap_spec.rb +55 -13
  171. data/spec/unit/knife/core/bootstrap_context_spec.rb +10 -3
  172. data/spec/unit/knife/ssl_check_spec.rb +7 -3
  173. data/spec/unit/knife/ssl_fetch_spec.rb +2 -2
  174. data/spec/unit/knife/status_spec.rb +13 -13
  175. data/spec/unit/knife_spec.rb +26 -2
  176. data/spec/unit/lwrp_spec.rb +1 -1
  177. data/spec/unit/mixin/properties_spec.rb +97 -0
  178. data/spec/unit/mixin/proxified_socket_spec.rb +94 -0
  179. data/spec/unit/mixin/subclass_directive_spec.rb +45 -0
  180. data/spec/unit/node_spec.rb +9 -1
  181. data/spec/unit/policy_builder/policyfile_spec.rb +2 -0
  182. data/spec/unit/property/validation_spec.rb +14 -12
  183. data/spec/unit/property_spec.rb +56 -0
  184. data/spec/unit/provider/deploy_spec.rb +1 -1
  185. data/spec/unit/provider/dsc_resource_spec.rb +63 -24
  186. data/spec/unit/provider/execute_spec.rb +95 -28
  187. data/spec/unit/provider/package/dpkg_spec.rb +185 -96
  188. data/spec/unit/provider/package/windows/exe_spec.rb +251 -0
  189. data/spec/unit/provider/package/windows/msi_spec.rb +94 -10
  190. data/spec/unit/provider/package/windows_spec.rb +227 -26
  191. data/spec/unit/provider/package/yum_spec.rb +6 -0
  192. data/spec/unit/provider/package_spec.rb +495 -366
  193. data/spec/unit/provider/remote_file/cache_control_data_spec.rb +62 -36
  194. data/spec/unit/provider/script_spec.rb +2 -2
  195. data/spec/unit/provider/service/solaris_smf_service_spec.rb +110 -39
  196. data/spec/unit/provider/service/upstart_service_spec.rb +19 -0
  197. data/spec/unit/provider/user/dscl_spec.rb +14 -0
  198. data/spec/unit/provider/user/windows_spec.rb +2 -2
  199. data/spec/unit/provider/user_spec.rb +9 -0
  200. data/spec/unit/provider_resolver_spec.rb +6 -30
  201. data/spec/unit/recipe_spec.rb +46 -20
  202. data/spec/unit/resource/chef_gem_spec.rb +1 -1
  203. data/spec/unit/resource/dsc_resource_spec.rb +14 -3
  204. data/spec/unit/resource/ksh_spec.rb +40 -0
  205. data/spec/unit/resource/registry_key_spec.rb +2 -2
  206. data/spec/unit/resource/resource_notification_spec.rb +44 -45
  207. data/spec/unit/resource_reporter_spec.rb +7 -0
  208. data/spec/unit/resource_spec.rb +268 -253
  209. data/spec/unit/rest_spec.rb +2 -2
  210. data/spec/unit/run_list/run_list_expansion_spec.rb +18 -3
  211. data/spec/unit/search/query_spec.rb +19 -1
  212. data/spec/unit/util/powershell/ps_credential_spec.rb +8 -1
  213. data/spec/unit/windows_service_spec.rb +83 -38
  214. data/tasks/external_tests.rb +19 -9
  215. data/tasks/rspec.rb +1 -1
  216. metadata +64 -15
  217. data/spec/support/pedant/Gemfile +0 -3
  218. data/spec/support/pedant/pedant_config.rb +0 -129
  219. data/spec/support/pedant/run_pedant.rb +0 -63
  220. data/spec/support/pedant/stickywicket.pem +0 -27
  221. data/spec/unit/provider/package_spec.rbe +0 -0
@@ -1,6 +1,6 @@
1
1
  #
2
2
  # Author:: Seth Chisamore (<schisamo@opscode.com>)
3
- # Copyright:: Copyright (c) 2011 Opscode, Inc.
3
+ # Copyright:: Copyright (c) 2011-2015 Chef Software, Inc.
4
4
  # License:: Apache License, Version 2.0
5
5
  #
6
6
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -51,8 +51,7 @@ class Chef
51
51
  option :log_location,
52
52
  :short => "-L LOGLOCATION",
53
53
  :long => "--logfile LOGLOCATION",
54
- :description => "Set the log file location for chef-service",
55
- :default => "#{ENV['SYSTEMDRIVE']}/chef/client.log"
54
+ :description => "Set the log file location for chef-service"
56
55
 
57
56
  option :help,
58
57
  :short => "-h",
@@ -106,6 +106,7 @@ class Chef
106
106
  RSpec.configure do |c|
107
107
  c.color = Chef::Config[:color]
108
108
  c.expose_dsl_globally = false
109
+ c.project_source_dirs = Array(Chef::Config[:cookbook_path])
109
110
  c.backtrace_exclusion_patterns << exclusion_pattern
110
111
  end
111
112
  end
@@ -205,17 +205,7 @@ class Chef
205
205
  # @api private this will likely be removed in favor of an as-yet unwritten
206
206
  # `Chef.log`
207
207
  def log_deprecation(message, location=nil)
208
- if !location
209
- # Pick the first caller that is *not* part of the Chef gem, that's the
210
- # thing the user wrote.
211
- chef_gem_path = File.expand_path("../..", __FILE__)
212
- caller(0..10).each do |c|
213
- if !c.start_with?(chef_gem_path)
214
- location = c
215
- break
216
- end
217
- end
218
- end
208
+ location ||= Chef::Log.caller_location
219
209
  # `run_context.events` is the primary deprecation target if we're in a
220
210
  # run. If we are not yet in a run, print to `Chef::Log`.
221
211
  if run_context && run_context.events
@@ -66,12 +66,65 @@ class Chef
66
66
  # - ChefFSDataStore lets cookbooks be uploaded into a temporary memory
67
67
  # storage, and when the cookbook is committed, copies the files onto the
68
68
  # disk in the correct place (/cookbooks/apache2/recipes/default.rb).
69
+ #
69
70
  # 3. Data bags:
70
71
  # - The Chef server expects data bags in /data/BAG/ITEM
71
72
  # - The repository stores data bags in /data_bags/BAG/ITEM
72
73
  #
73
74
  # 4. JSON filenames are generally NAME.json in the repository (e.g. /nodes/foo.json).
74
75
  #
76
+ # 5. Org membership:
77
+ # chef-zero stores user membership in an org as a series of empty files.
78
+ # If an org has jkeiser and cdoherty as members, chef-zero expects these
79
+ # files to exist:
80
+ #
81
+ # - `users/jkeiser` (content: '{}')
82
+ # - `users/cdoherty` (content: '{}')
83
+ #
84
+ # ChefFS, on the other hand, stores user membership in an org as a single
85
+ # file, `members.json`, with content:
86
+ #
87
+ # ```json
88
+ # [
89
+ # { "user": { "username": "jkeiser" } },
90
+ # { "user": { "username": "cdoherty" } }
91
+ # ]
92
+ # ```
93
+ #
94
+ # To translate between the two, we need to intercept requests to `users`
95
+ # like so:
96
+ #
97
+ # - `list(users)` -> `get(/members.json)`
98
+ # - `get(users/NAME)` -> `get(/members.json)`, see if it's in there
99
+ # - `create(users/NAME)` -> `get(/members.json)`, add name, `set(/members.json)`
100
+ # - `delete(users/NAME)` -> `get(/members.json)`, remove name, `set(/members.json)`
101
+ #
102
+ # 6. Org invitations:
103
+ # chef-zero stores org membership invitations as a series of empty files.
104
+ # If an org has invited jkeiser and cdoherty (and they have not yet accepted
105
+ # the invite), chef-zero expects these files to exist:
106
+ #
107
+ # - `association_requests/jkeiser` (content: '{}')
108
+ # - `association_requests/cdoherty` (content: '{}')
109
+ #
110
+ # ChefFS, on the other hand, stores invitations as a single file,
111
+ # `invitations.json`, with content:
112
+ #
113
+ # ```json
114
+ # [
115
+ # { "id" => "jkeiser-chef", 'username' => 'jkeiser' },
116
+ # { "id" => "cdoherty-chef", 'username' => 'cdoherty' }
117
+ # ]
118
+ # ```
119
+ #
120
+ # To translate between the two, we need to intercept requests to `users`
121
+ # like so:
122
+ #
123
+ # - `list(association_requests)` -> `get(/invitations.json)`
124
+ # - `get(association_requests/NAME)` -> `get(/invitations.json)`, see if it's in there
125
+ # - `create(association_requests/NAME)` -> `get(/invitations.json)`, add name, `set(/invitations.json)`
126
+ # - `delete(association_requests/NAME)` -> `get(/invitations.json)`, remove name, `set(/invitations.json)`
127
+ #
75
128
  class ChefFSDataStore
76
129
  #
77
130
  # Create a new ChefFSDataStore
@@ -83,9 +136,10 @@ class Chef
83
136
  # Generally will be a +ChefFS::FileSystem::ChefRepositoryFileSystemRoot+
84
137
  # object, created from +ChefFS::Config.local_fs+.
85
138
  #
86
- def initialize(chef_fs)
139
+ def initialize(chef_fs, chef_config=Chef::Config)
87
140
  @chef_fs = chef_fs
88
141
  @memory_store = ChefZero::DataStore::MemoryStore.new
142
+ @repo_mode = chef_config[:repo_mode]
89
143
  end
90
144
 
91
145
  def publish_description
@@ -93,6 +147,7 @@ class Chef
93
147
  end
94
148
 
95
149
  attr_reader :chef_fs
150
+ attr_reader :repo_mode
96
151
 
97
152
  def create_dir(path, name, *options)
98
153
  if use_memory_store?(path)
@@ -108,6 +163,24 @@ class Chef
108
163
  end
109
164
  end
110
165
 
166
+ #
167
+ # If you want to get the contents of /data/x/y from the server,
168
+ # you say chef_fs.child('data').child('x').child('y').read.
169
+ # It will make exactly one network request: GET /data/x/y
170
+ # And that will return 404 if it doesn't exist.
171
+ #
172
+ # ChefFS objects do not go to the network until you ask them for data.
173
+ # This means you can construct a /data/x/y ChefFS entry early.
174
+ #
175
+ # Alternative:
176
+ # chef_fs.child('data') could have done a GET /data preemptively,
177
+ # allowing it to know whether child('x') was valid (GET /data gives you
178
+ # a list of data bags). Then child('x') could have done a GET /data/x,
179
+ # allowing it to know whether child('y') (the item) existed. Finally,
180
+ # we would do the GET /data/x/y to read the contents. Three network
181
+ # requests instead of 1.
182
+ #
183
+
111
184
  def create(path, name, data, *options)
112
185
  if use_memory_store?(path)
113
186
  @memory_store.create(path, name, data, *options)
@@ -115,6 +188,32 @@ class Chef
115
188
  elsif path[0] == 'cookbooks' && path.length == 2
116
189
  # Do nothing. The entry gets created when the cookbook is created.
117
190
 
191
+ # create [/organizations/ORG]/users/NAME (with content '{}')
192
+ # Manipulate the `members.json` file that contains a list of all users
193
+ elsif is_org? && path == [ 'users' ]
194
+ update_json('members.json', []) do |members|
195
+ # Format of each entry: { "user": { "username": "jkeiser" } }
196
+ if members.any? { |member| member['user']['username'] == name }
197
+ raise ChefZero::DataStore::DataAlreadyExistsError.new(path, entry)
198
+ end
199
+
200
+ # Actually add the user
201
+ members << { "user" => { "username" => name } }
202
+ end
203
+
204
+ # create [/organizations/ORG]/association_requests/NAME (with content '{}')
205
+ # Manipulate the `invitations.json` file that contains a list of all users
206
+ elsif is_org? && path == [ 'association_requests' ]
207
+ update_json('invitations.json', []) do |invitations|
208
+ # Format of each entry: { "id" => "jkeiser-chef", 'username' => 'jkeiser' }
209
+ if invitations.any? { |member| member['username'] == name }
210
+ raise ChefZero::DataStore::DataAlreadyExistsError.new(path)
211
+ end
212
+
213
+ # Actually add the user (TODO insert org name??)
214
+ invitations << { "username" => name }
215
+ end
216
+
118
217
  else
119
218
  if !data.is_a?(String)
120
219
  raise "set only works with strings"
@@ -142,6 +241,24 @@ class Chef
142
241
  raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e)
143
242
  end
144
243
 
244
+ # GET [/organizations/ORG]/users/NAME -> /users/NAME
245
+ # Manipulates members.json
246
+ elsif is_org? && path[0] == 'users' && path.length == 2
247
+ if get_json('members.json', []).any? { |member| member['user']['username'] == path[1] }
248
+ '{}'
249
+ else
250
+ raise ChefZero::DataStore::DataNotFoundError.new(path)
251
+ end
252
+
253
+ # GET [/organizations/ORG]/association_requests/NAME -> /users/NAME
254
+ # Manipulates invites.json
255
+ elsif is_org? && path[0] == 'association_requests' && path.length == 2
256
+ if get_json('invites.json', []).any? { |member| member['user']['username'] == path[1] }
257
+ '{}'
258
+ else
259
+ raise ChefZero::DataStore::DataNotFoundError.new(path)
260
+ end
261
+
145
262
  else
146
263
  with_entry(path) do |entry|
147
264
  if path[0] == 'cookbooks' && path.length == 3
@@ -209,6 +326,29 @@ class Chef
209
326
  def delete(path)
210
327
  if use_memory_store?(path)
211
328
  @memory_store.delete(path)
329
+
330
+ # DELETE [/organizations/ORG]/users/NAME
331
+ # Manipulates members.json
332
+ elsif is_org? && path[0] == 'users' && path.length == 2
333
+ update_json('members.json', []) do |members|
334
+ result = members.reject { |member| member['user']['username'] == path[1] }
335
+ if result.size == members.size
336
+ raise ChefZero::DataStore::DataNotFoundError.new(path)
337
+ end
338
+ result
339
+ end
340
+
341
+ # DELETE [/organizations/ORG]/users/NAME
342
+ # Manipulates members.json
343
+ elsif is_org? && path[0] == 'association_requests' && path.length == 2
344
+ update_json('invitations.json', []) do |invitations|
345
+ result = invitations.reject { |invitation| invitation['username'] == path[1] }
346
+ if result.size == invitations.size
347
+ raise ChefZero::DataStore::DataNotFoundError.new(path)
348
+ end
349
+ result
350
+ end
351
+
212
352
  else
213
353
  with_entry(path) do |entry|
214
354
  begin
@@ -394,9 +534,22 @@ class Chef
394
534
  end
395
535
  end
396
536
  end
537
+
538
+ elsif path[0] == 'acls'
539
+ # /acls/containers|nodes|.../x.json
540
+ # /acls/organization.json
541
+ if path.length == 3 || path == [ 'acls', 'organization' ]
542
+ path = path.dup
543
+ path[-1] = "#{path[-1]}.json"
544
+ end
545
+
546
+ # /acls/containers|nodes|... do NOT drop into the next elsif, and do
547
+ # not get .json appended
548
+
549
+ # /nodes|clients|.../x.json
397
550
  elsif path.length == 2
398
551
  path = path.dup
399
- path[1] = "#{path[1]}.json"
552
+ path[-1] = "#{path[-1]}.json"
400
553
  end
401
554
  path
402
555
  end
@@ -477,6 +630,32 @@ class Chef
477
630
  metadata = ChefZero::CookbookData.metadata_from(dir, path[1], nil, [])
478
631
  metadata[:version] || '0.0.0'
479
632
  end
633
+
634
+ def update_json(path, default_value)
635
+ entry = Chef::ChefFS::FileSystem.resolve_path(chef_fs, path)
636
+ begin
637
+ input = Chef::JSONCompat.parse(entry.read)
638
+ output = yield input.dup
639
+ entry.write(Chef::JSONCompat.to_json_pretty(output)) if output != input
640
+ rescue Chef::ChefFS::FileSystem::NotFoundError
641
+ # Send the default value to the caller, and create the entry if the caller updates it
642
+ output = yield default_value
643
+ entry.parent.create_child(entry.name, Chef::JSONCompat.to_json_pretty(output)) if output != []
644
+ end
645
+ end
646
+
647
+ def get_json(path, default_value)
648
+ entry = Chef::ChefFS::FileSystem.resolve_path(chef_fs, path)
649
+ begin
650
+ Chef::JSONCompat.parse(entry.read)
651
+ rescue Chef::ChefFS::FileSystem::NotFoundError
652
+ default_value
653
+ end
654
+ end
655
+
656
+ def is_org?
657
+ repo_mode == 'hosted_everything'
658
+ end
480
659
  end
481
660
  end
482
661
  end
@@ -45,6 +45,11 @@ class Chef
45
45
  true
46
46
  end
47
47
 
48
+ def make_child_entry(name)
49
+ result = @children.select { |child| child.name == name }.first if @children
50
+ result || NonexistentFSObject.new(name, self)
51
+ end
52
+
48
53
  def rest
49
54
  parent.rest
50
55
  end
@@ -72,18 +72,22 @@ class Chef
72
72
  end
73
73
 
74
74
  def delete(recurse)
75
- if dir?
76
- if !recurse
77
- raise MustDeleteRecursivelyError.new(self, $!)
75
+ begin
76
+ if dir?
77
+ if !recurse
78
+ raise MustDeleteRecursivelyError.new(self, $!)
79
+ end
80
+ FileUtils.rm_r(file_path)
81
+ else
82
+ File.delete(file_path)
78
83
  end
79
- FileUtils.rm_rf(file_path)
80
- else
81
- File.delete(file_path)
84
+ rescue Errno::ENOENT
85
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
82
86
  end
83
87
  end
84
88
 
85
89
  def exists?
86
- File.exists?(file_path) && parent.can_have_child?(name, dir?)
90
+ File.exists?(file_path) && (parent.nil? || parent.can_have_child?(name, dir?))
87
91
  end
88
92
 
89
93
  def read
@@ -232,6 +232,8 @@ class Chef
232
232
  # @return Always returns true.
233
233
  #
234
234
  def run
235
+ start_profiling
236
+
235
237
  run_error = nil
236
238
 
237
239
  runlock = RunLock.new(Chef::Config.lockfile)
@@ -271,7 +273,7 @@ class Chef
271
273
 
272
274
  if Chef::Config[:why_run] == true
273
275
  # why_run should probably be renamed to why_converge
274
- Chef::Log.debug("Not running controls in 'why_run' mode - this mode is used to see potential converge changes")
276
+ Chef::Log.debug("Not running controls in 'why-run' mode - this mode is used to see potential converge changes")
275
277
  elsif Chef::Config[:audit_mode] != :disabled
276
278
  audit_error = run_audits(run_context)
277
279
  end
@@ -284,6 +286,9 @@ class Chef
284
286
  run_completed_successfully
285
287
  events.run_completed(node)
286
288
 
289
+ # keep this inside the main loop to get exception backtraces
290
+ end_profiling
291
+
287
292
  # rebooting has to be the last thing we do, no exceptions.
288
293
  Chef::Platform::Rebooter.reboot_if_needed!(node)
289
294
  rescue Exception => run_error
@@ -891,6 +896,28 @@ class Chef
891
896
  attr_reader :override_runlist
892
897
  attr_reader :specific_recipes
893
898
 
899
+ def profiling_prereqs!
900
+ require 'ruby-prof'
901
+ rescue LoadError
902
+ raise "You must have the ruby-prof gem installed in order to use --profile-ruby"
903
+ end
904
+
905
+ def start_profiling
906
+ return unless Chef::Config[:profile_ruby]
907
+ profiling_prereqs!
908
+ RubyProf.start
909
+ end
910
+
911
+ def end_profiling
912
+ return unless Chef::Config[:profile_ruby]
913
+ profiling_prereqs!
914
+ path = Chef::FileCache.create_cache_path("graph_profile.out", false)
915
+ File.open(path, "w+") do |file|
916
+ RubyProf::GraphPrinter.new(RubyProf.stop).print(file, {})
917
+ end
918
+ Chef::Log.warn("Ruby execution profile dumped to #{path}")
919
+ end
920
+
894
921
  def empty_directory?(path)
895
922
  !File.exists?(path) || (Dir.entries(path).size <= 2)
896
923
  end
@@ -1,7 +1,7 @@
1
1
  #--
2
2
  # Author:: Tim Hinderliter (<tim@opscode.com>)
3
3
  # Author:: Christopher Walters (<cw@opscode.com>)
4
- # Copyright:: Copyright (c) 2010 Opscode, Inc.
4
+ # Copyright:: Copyright (c) 2010-2015 Chef Software, Inc.
5
5
  # License:: Apache License, Version 2.0
6
6
  #
7
7
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -41,5 +41,18 @@ class Chef
41
41
  cookbook_versions.each{ |cookbook_name, cookbook_version| self[cookbook_name] = cookbook_version }
42
42
  end
43
43
 
44
+ # Validates that the cookbook metadata allows it to run on this instance.
45
+ #
46
+ # Currently checks chef_version and ohai_version in the cookbook metadata
47
+ # against the running Chef::VERSION and Ohai::VERSION.
48
+ #
49
+ # @raises [Chef::Exceptions::CookbookChefVersionMismatch] if the Chef::VERSION fails validation
50
+ # @raises [Chef::Exceptions::CookbookOhaiVersionMismatch] if the Ohai::VERSION fails validation
51
+ def validate!
52
+ each do |cookbook_name, cookbook_version|
53
+ cookbook_version.metadata.validate_chef_version!
54
+ cookbook_version.metadata.validate_ohai_version!
55
+ end
56
+ end
44
57
  end
45
58
  end
@@ -91,7 +91,7 @@ class Chef
91
91
  remove_ignored_files
92
92
 
93
93
  if empty?
94
- Chef::Log.warn "found a directory #{cookbook_name} in the cookbook path, but it contains no cookbook files. skipping."
94
+ Chef::Log.warn "Found a directory #{cookbook_name} in the cookbook path, but it contains no cookbook files. skipping."
95
95
  end
96
96
  @cookbook_settings
97
97
  end
@@ -2,7 +2,7 @@
2
2
  # Author:: Adam Jacob (<adam@opscode.com>)
3
3
  # Author:: AJ Christensen (<aj@opscode.com>)
4
4
  # Author:: Seth Falcon (<seth@opscode.com>)
5
- # Copyright:: Copyright 2008-2010 Opscode, Inc.
5
+ # Copyright:: Copyright 2008-2015 Chef Software, Inc.
6
6
  # License:: Apache License, Version 2.0
7
7
  #
8
8
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -55,19 +55,23 @@ class Chef
55
55
  SOURCE_URL = 'source_url'.freeze
56
56
  ISSUES_URL = 'issues_url'.freeze
57
57
  PRIVACY = 'privacy'.freeze
58
+ CHEF_VERSIONS = 'chef_versions'.freeze
59
+ OHAI_VERSIONS = 'ohai_versions'.freeze
58
60
 
59
61
  COMPARISON_FIELDS = [ :name, :description, :long_description, :maintainer,
60
62
  :maintainer_email, :license, :platforms, :dependencies,
61
63
  :recommendations, :suggestions, :conflicting, :providing,
62
64
  :replacing, :attributes, :groupings, :recipes, :version,
63
- :source_url, :issues_url, :privacy ]
65
+ :source_url, :issues_url, :privacy, :chef_versions, :ohai_versions ]
64
66
 
65
- VERSION_CONSTRAINTS = {:depends => DEPENDENCIES,
66
- :recommends => RECOMMENDATIONS,
67
- :suggests => SUGGESTIONS,
68
- :conflicts => CONFLICTING,
69
- :provides => PROVIDING,
70
- :replaces => REPLACING }
67
+ VERSION_CONSTRAINTS = {:depends => DEPENDENCIES,
68
+ :recommends => RECOMMENDATIONS,
69
+ :suggests => SUGGESTIONS,
70
+ :conflicts => CONFLICTING,
71
+ :provides => PROVIDING,
72
+ :replaces => REPLACING,
73
+ :chef_version => CHEF_VERSIONS,
74
+ :ohai_version => OHAI_VERSIONS }
71
75
 
72
76
  include Chef::Mixin::ParamsValidate
73
77
  include Chef::Mixin::FromFile
@@ -84,6 +88,11 @@ class Chef
84
88
  attr_reader :recipes
85
89
  attr_reader :version
86
90
 
91
+ # @return [Array<Gem::Dependency>] Array of supported Chef versions
92
+ attr_reader :chef_versions
93
+ # @return [Array<Gem::Dependency>] Array of supported Ohai versions
94
+ attr_reader :ohai_versions
95
+
87
96
  # Builds a new Chef::Cookbook::Metadata object.
88
97
  #
89
98
  # === Parameters
@@ -118,6 +127,8 @@ class Chef
118
127
  @source_url = ''
119
128
  @issues_url = ''
120
129
  @privacy = false
130
+ @chef_versions = []
131
+ @ohai_versions = []
121
132
 
122
133
  @errors = []
123
134
  end
@@ -386,6 +397,28 @@ class Chef
386
397
  @replacing[cookbook]
387
398
  end
388
399
 
400
+ # Metadata DSL to set a valid chef_version. May be declared multiple times
401
+ # with the result being 'OR'd such that if any statements match, the version
402
+ # is considered supported. Uses Gem::Requirement for its implementation.
403
+ #
404
+ # @param version_args [Array<String>] Version constraint in String form
405
+ # @return [Array<Gem::Dependency>] Current chef_versions array
406
+ def chef_version(*version_args)
407
+ @chef_versions << Gem::Dependency.new('chef', *version_args) unless version_args.empty?
408
+ @chef_versions
409
+ end
410
+
411
+ # Metadata DSL to set a valid ohai_version. May be declared multiple times
412
+ # with the result being 'OR'd such that if any statements match, the version
413
+ # is considered supported. Uses Gem::Requirement for its implementation.
414
+ #
415
+ # @param version_args [Array<String>] Version constraint in String form
416
+ # @return [Array<Gem::Dependency>] Current ohai_versions array
417
+ def ohai_version(*version_args)
418
+ @ohai_versions << Gem::Dependency.new('ohai', *version_args) unless version_args.empty?
419
+ @ohai_versions
420
+ end
421
+
389
422
  # Adds a description for a recipe.
390
423
  #
391
424
  # === Parameters
@@ -481,6 +514,40 @@ class Chef
481
514
  @groupings[name]
482
515
  end
483
516
 
517
+ # Convert an Array of Gem::Dependency objects (chef_version/ohai_version) to an Array.
518
+ #
519
+ # Gem::Dependencey#to_s is not useful, and there is no #to_json defined on it or its component
520
+ # objets, so we have to write our own rendering method.
521
+ #
522
+ # [ Gem::Dependency.new(">= 12.5"), Gem::Dependency.new(">= 11.18.0", "< 12.0") ]
523
+ #
524
+ # results in:
525
+ #
526
+ # [ [ ">= 12.5" ], [ ">= 11.18.0", "< 12.0" ] ]
527
+ #
528
+ # @param deps [Array<Gem::Dependency>] Multiple Gem-style version constraints
529
+ # @return [Array<Array<String>]] Simple object representation of version constraints (for json)
530
+ def gem_requirements_to_array(*deps)
531
+ deps.map do |dep|
532
+ dep.requirement.requirements.map do |op, version|
533
+ "#{op} #{version}"
534
+ end.sort
535
+ end
536
+ end
537
+
538
+ # Convert an Array of Gem::Dependency objects (chef_version/ohai_version) to a hash.
539
+ #
540
+ # This is the inverse of #gem_requirements_to_array
541
+ #
542
+ # @param what [String] What version constraint we are constructing ('chef' or 'ohai' presently)
543
+ # @param array [Array<Array<String>]] Simple object representation of version constraints (from json)
544
+ # @return [Array<Gem::Dependency>] Multiple Gem-style version constraints
545
+ def gem_requirements_from_array(what, array)
546
+ array.map do |dep|
547
+ Gem::Dependency.new(what, *dep)
548
+ end
549
+ end
550
+
484
551
  def to_hash
485
552
  {
486
553
  NAME => self.name,
@@ -502,7 +569,9 @@ class Chef
502
569
  VERSION => self.version,
503
570
  SOURCE_URL => self.source_url,
504
571
  ISSUES_URL => self.issues_url,
505
- PRIVACY => self.privacy
572
+ PRIVACY => self.privacy,
573
+ CHEF_VERSIONS => gem_requirements_to_array(*self.chef_versions),
574
+ OHAI_VERSIONS => gem_requirements_to_array(*self.ohai_versions)
506
575
  }
507
576
  end
508
577
 
@@ -537,6 +606,8 @@ class Chef
537
606
  @source_url = o[SOURCE_URL] if o.has_key?(SOURCE_URL)
538
607
  @issues_url = o[ISSUES_URL] if o.has_key?(ISSUES_URL)
539
608
  @privacy = o[PRIVACY] if o.has_key?(PRIVACY)
609
+ @chef_versions = gem_requirements_from_array("chef", o[CHEF_VERSIONS]) if o.has_key?(CHEF_VERSIONS)
610
+ @ohai_versions = gem_requirements_from_array("ohai", o[OHAI_VERSIONS]) if o.has_key?(OHAI_VERSIONS)
540
611
  self
541
612
  end
542
613
 
@@ -612,8 +683,43 @@ class Chef
612
683
  )
613
684
  end
614
685
 
686
+ # Validates that the Ohai::VERSION of the running chef-client matches one of the
687
+ # configured ohai_version statements in this cookbooks metadata.
688
+ #
689
+ # @raises [Chef::Exceptions::CookbookOhaiVersionMismatch] if the cookbook fails validation
690
+ def validate_ohai_version!
691
+ unless gem_dep_matches?("ohai", Gem::Version.new(Ohai::VERSION), *ohai_versions)
692
+ raise Exceptions::CookbookOhaiVersionMismatch.new(Ohai::VERSION, name, version, *ohai_versions)
693
+ end
694
+ end
695
+
696
+ # Validates that the Chef::VERSION of the running chef-client matches one of the
697
+ # configured chef_version statements in this cookbooks metadata.
698
+ #
699
+ # @raises [Chef::Exceptions::CookbookChefVersionMismatch] if the cookbook fails validation
700
+ def validate_chef_version!
701
+ unless gem_dep_matches?("chef", Gem::Version.new(Chef::VERSION), *chef_versions)
702
+ raise Exceptions::CookbookChefVersionMismatch.new(Chef::VERSION, name, version, *chef_versions)
703
+ end
704
+ end
705
+
615
706
  private
616
707
 
708
+ # Helper to match a gem style version (ohai_version/chef_version) against a set of
709
+ # Gem::Dependency version constraints. If none are present, it always matches. if
710
+ # multiple are present, one must match. Returns false if none matches.
711
+ #
712
+ # @param what [String] the name of the constraint (e.g. 'chef' or 'ohai')
713
+ # @param version [String] the version to compare against the constraints
714
+ # @param deps [Array<Gem::Dependency>] Multiple Gem-style version constraints
715
+ # @return [Boolean] true if no constraints or a match, false if no match
716
+ def gem_dep_matches?(what, version, *deps)
717
+ # always match if we have no chef_version at all
718
+ return true unless deps.length > 0
719
+ # match if we match any of the chef_version lines
720
+ deps.any? { |dep| dep.match?(what, version) }
721
+ end
722
+
617
723
  def run_validation
618
724
  if name.nil?
619
725
  @errors = ["The `name' attribute is required in cookbook metadata"]