cheffish 0.7.1 → 0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/lib/chef/provider/chef_acl.rb +434 -0
  3. data/lib/chef/provider/chef_client.rb +5 -1
  4. data/lib/chef/provider/chef_container.rb +50 -0
  5. data/lib/chef/provider/chef_group.rb +78 -0
  6. data/lib/chef/provider/chef_mirror.rb +138 -0
  7. data/lib/chef/provider/chef_organization.rb +150 -0
  8. data/lib/chef/provider/chef_user.rb +6 -1
  9. data/lib/chef/provider/public_key.rb +0 -1
  10. data/lib/chef/resource/chef_acl.rb +38 -44
  11. data/lib/chef/resource/chef_container.rb +18 -0
  12. data/lib/chef/resource/chef_group.rb +49 -0
  13. data/lib/chef/resource/chef_mirror.rb +47 -0
  14. data/lib/chef/resource/chef_organization.rb +64 -0
  15. data/lib/chef/resource/private_key.rb +6 -1
  16. data/lib/chef/resource/public_key.rb +5 -0
  17. data/lib/cheffish/actor_provider_base.rb +14 -9
  18. data/lib/cheffish/basic_chef_client.rb +18 -2
  19. data/lib/cheffish/chef_provider_base.rb +7 -0
  20. data/lib/cheffish/merged_config.rb +10 -2
  21. data/lib/cheffish/recipe_dsl.rb +34 -8
  22. data/lib/cheffish/server_api.rb +12 -2
  23. data/lib/cheffish/version.rb +1 -1
  24. data/lib/cheffish.rb +2 -2
  25. data/spec/functional/merged_config_spec.rb +20 -0
  26. data/spec/integration/chef_acl_spec.rb +914 -0
  27. data/spec/integration/chef_client_spec.rb +78 -44
  28. data/spec/integration/chef_container_spec.rb +34 -0
  29. data/spec/integration/chef_group_spec.rb +324 -0
  30. data/spec/integration/chef_mirror_spec.rb +244 -0
  31. data/spec/integration/chef_node_spec.rb +115 -93
  32. data/spec/integration/chef_organization_spec.rb +244 -0
  33. data/spec/integration/chef_user_spec.rb +51 -9
  34. data/spec/support/repository_support.rb +103 -0
  35. data/spec/support/spec_support.rb +55 -2
  36. metadata +23 -9
  37. data/lib/chef/resource/in_parallel.rb +0 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ede81f8d16044c52e057959e600456f20c3a3b25
4
- data.tar.gz: ec1c78fbef404e83ff691825949f0111e7f962ec
3
+ metadata.gz: 427284963b463df3766e864b509ef9744b4495c8
4
+ data.tar.gz: 86fa11bb31ad0bf98942c410e18868eb8967e903
5
5
  SHA512:
6
- metadata.gz: 9c87bd23e659677f0c3917d6bf75b153edb29abb4f00e0298daf0b15b514853452f4c876a46f55062320a05066f7ff99047514e352901502fabd0787635e106a
7
- data.tar.gz: 46e4d256b1c534875ba01bc86a0fc7f3537fa19bd03b04f522cc022b87be6efac3d5b1568eed249745bf5c024954ccb16d32743dfed7dc6fa26908ccd0ee6619
6
+ metadata.gz: c52954a4961bbb4f880a8dc23fa8a3aa8ee2d931d7139eff910081eb3fb0e7f1469b1eb0f700771d865265521abb3bbde41b29b36913c6a8cb054b6732145a92
7
+ data.tar.gz: 42b1fda6ca8590848118e253ebc82e43e5ae17abdea2be29882e9f5f120af4a36038a66b2c7add5644bcfeb120157c39dddaa36eb4b270207774e3e5568cd11b
@@ -0,0 +1,434 @@
1
+ require 'cheffish/chef_provider_base'
2
+ require 'chef/resource/chef_acl'
3
+ require 'chef/chef_fs/data_handler/acl_data_handler'
4
+ require 'chef/chef_fs/parallelizer'
5
+ require 'uri'
6
+
7
+ class Chef::Provider::ChefAcl < Cheffish::ChefProviderBase
8
+
9
+ def whyrun_supported?
10
+ true
11
+ end
12
+
13
+ action :create do
14
+ if new_resource.remove_rights && new_resource.complete
15
+ Chef::Log.warn("'remove_rights' is redundant when 'complete' is specified: all rights not specified in a 'rights' declaration will be removed.")
16
+ end
17
+ # Verify that we're not destroying all hope of ACL recovery here
18
+ if new_resource.complete && (!new_resource.rights || !new_resource.rights.any? { |r| r[:permissions].include?(:all) || r[:permissions].include?(:grant) })
19
+ # NOTE: if superusers exist, this should turn into a warning.
20
+ raise "'complete' specified on chef_acl resource, but no GRANT permissions were granted. I'm sorry Dave, I can't let you remove all access to an object with no hope of recovery."
21
+ end
22
+
23
+ # Find all matching paths so we can update them (resolve * and **)
24
+ paths = match_paths(new_resource.path)
25
+ if paths.size == 0 && !new_resource.path.split('/').any? { |p| p == '*' }
26
+ raise "Path #{new_resource.path} cannot have an ACL set on it!"
27
+ end
28
+
29
+ # Go through the matches and update the ACLs for them
30
+ paths.each do |path|
31
+ create_acl(path)
32
+ end
33
+ end
34
+
35
+ # Update the ACL if necessary.
36
+ def create_acl(path)
37
+ changed = false
38
+ # There may not be an ACL path for some valid paths (/ and /organizations,
39
+ # for example). We want to recurse into these, but we don't want to try to
40
+ # update nonexistent ACLs for them.
41
+ acl = acl_path(path)
42
+ if acl
43
+ # It's possible to make a custom container
44
+ current_json = current_acl(acl)
45
+ if current_json
46
+
47
+ # Compare the desired and current json for the ACL, and update if different.
48
+ modify = {}
49
+ desired_acl(acl).each do |permission, desired_json|
50
+ differences = json_differences(current_json[permission], desired_json)
51
+
52
+ if differences.size > 0
53
+ # Verify we aren't trying to destroy grant permissions
54
+ if permission == 'grant' && desired_json['actors'] == [] && desired_json['groups'] == []
55
+ # NOTE: if superusers exist, this should turn into a warning.
56
+ raise "chef_acl attempted to remove all actors from GRANT! I'm sorry Dave, I can't let you remove access to an object with no hope of recovery."
57
+ end
58
+ modify[differences] ||= {}
59
+ modify[differences][permission] = desired_json
60
+ end
61
+ end
62
+
63
+ if modify.size > 0
64
+ changed = true
65
+ description = [ "update acl #{path} at #{rest_url(path)}" ] + modify.map do |diffs, permissions|
66
+ diffs.map { |diff| " #{permissions.keys.join(', ')}:#{diff}" }
67
+ end.flatten(1)
68
+ converge_by description do
69
+ modify.values.each do |permissions|
70
+ permissions.each do |permission, desired_json|
71
+ rest.put(rest_url("#{acl}/#{permission}"), { permission => desired_json })
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ # If we have been asked to recurse, do so.
80
+ # If recurse is on_change, then we will recurse if there is no ACL, or if
81
+ # the ACL has changed.
82
+ if new_resource.recursive == true || (new_resource.recursive == :on_change && (!acl || changed))
83
+ children, error = list(path, '*')
84
+ Chef::ChefFS::Parallelizer.parallel_do(children) do |child|
85
+ next if child.split('/')[-1] == 'containers'
86
+ create_acl(child)
87
+ end
88
+ # containers mess up our descent, so we do them last
89
+ Chef::ChefFS::Parallelizer.parallel_do(children) do |child|
90
+ next if child.split('/')[-1] != 'containers'
91
+ create_acl(child)
92
+ end
93
+
94
+ end
95
+ end
96
+
97
+ # Get the current ACL for the given path
98
+ def current_acl(acl_path)
99
+ @current_acls ||= {}
100
+ if !@current_acls.has_key?(acl_path)
101
+ @current_acls[acl_path] = begin
102
+ rest.get(rest_url(acl_path))
103
+ rescue Net::HTTPServerException => e
104
+ unless e.response.code == '404' && new_resource.path.split('/').any? { |p| p == '*' }
105
+ raise
106
+ end
107
+ end
108
+ end
109
+ @current_acls[acl_path]
110
+ end
111
+
112
+ # Get the desired acl for the given acl path
113
+ def desired_acl(acl_path)
114
+ result = new_resource.raw_json ? new_resource.raw_json.dup : {}
115
+
116
+ # Calculate the JSON based on rights
117
+ add_rights(acl_path, result)
118
+
119
+ if new_resource.complete
120
+ result = Chef::ChefFS::DataHandler::AclDataHandler.new.normalize(result, nil)
121
+ else
122
+ # If resource is incomplete, use current json to fill any holes
123
+ current_acl(acl_path).each do |permission, perm_hash|
124
+ if !result[permission]
125
+ result[permission] = perm_hash.dup
126
+ else
127
+ result[permission] = result[permission].dup
128
+ perm_hash.each do |type, actors|
129
+ if !result[permission][type]
130
+ result[permission][type] = actors
131
+ else
132
+ result[permission][type] = result[permission][type].dup
133
+ result[permission][type] |= actors
134
+ end
135
+ end
136
+ end
137
+ end
138
+
139
+ remove_rights(result)
140
+ end
141
+ result
142
+ end
143
+
144
+ def add_rights(acl_path, json)
145
+ if new_resource.rights
146
+ new_resource.rights.each do |rights|
147
+ if rights[:permissions].delete(:all)
148
+ rights[:permissions] |= current_acl(acl_path).keys
149
+ end
150
+
151
+ Array(rights[:permissions]).each do |permission|
152
+ ace = json[permission.to_s] ||= {}
153
+ # WTF, no distinction between users and clients? The Chef API doesn't
154
+ # let us distinguish, so we have no choice :/ This means that:
155
+ # 1. If you specify :users => 'foo', and client 'foo' exists, it will
156
+ # pick that (whether user 'foo' exists or not)
157
+ # 2. If you specify :clients => 'foo', and user 'foo' exists but
158
+ # client 'foo' does not, it will pick user 'foo' and put it in the
159
+ # ACL
160
+ # 3. If an existing item has user 'foo' on it and you specify :clients
161
+ # => 'foo' instead, idempotence will not notice that anything needs
162
+ # to be updated and nothing will happen.
163
+ if rights[:users]
164
+ ace['actors'] ||= []
165
+ ace['actors'] |= Array(rights[:users])
166
+ end
167
+ if rights[:clients]
168
+ ace['actors'] ||= []
169
+ ace['actors'] |= Array(rights[:clients])
170
+ end
171
+ if rights[:groups]
172
+ ace['groups'] ||= []
173
+ ace['groups'] |= Array(rights[:groups])
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
179
+
180
+ def remove_rights(json)
181
+ if new_resource.remove_rights
182
+ new_resource.remove_rights.each do |rights|
183
+ rights[:permissions].each do |permission|
184
+ if permission == :all
185
+ json.each_key do |key|
186
+ ace = json[key] = json[key.dup]
187
+ ace['actors'] = ace['actors'] - Array(rights[:users]) if rights[:users] && ace['actors']
188
+ ace['actors'] = ace['actors'] - Array(rights[:clients]) if rights[:clients] && ace['actors']
189
+ ace['groups'] = ace['groups'] - Array(rights[:groups]) if rights[:groups] && ace['groups']
190
+ end
191
+ else
192
+ ace = json[permission.to_s] = json[permission.to_s].dup
193
+ if ace
194
+ ace['actors'] = ace['actors'] - Array(rights[:users]) if rights[:users] && ace['actors']
195
+ ace['actors'] = ace['actors'] - Array(rights[:clients]) if rights[:clients] && ace['actors']
196
+ ace['groups'] = ace['groups'] - Array(rights[:groups]) if rights[:groups] && ace['groups']
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end
203
+
204
+ def load_current_resource
205
+ end
206
+
207
+ #
208
+ # Matches chef_acl paths like nodes, nodes/*.
209
+ #
210
+ # == Examples
211
+ # match_paths('nodes'): [ 'nodes' ]
212
+ # match_paths('nodes/*'): [ 'nodes/x', 'nodes/y', 'nodes/z' ]
213
+ # match_paths('*'): [ 'clients', 'environments', 'nodes', 'roles', ... ]
214
+ # match_paths('/'): [ '/' ]
215
+ # match_paths(''): [ '' ]
216
+ # match_paths('/*'): [ '/organizations', '/users' ]
217
+ # match_paths('/organizations/*/*'): [ '/organizations/foo/clients', '/organizations/foo/environments', ..., '/organizations/bar/clients', '/organizations/bar/environments', ... ]
218
+ #
219
+ def match_paths(path)
220
+ # Turn multiple slashes into one
221
+ # nodes//x -> nodes/x
222
+ path = path.gsub(/[\/]+/, '/')
223
+ # If it's absolute, start the matching with /. If it's relative, start with '' (relative root).
224
+ if path[0] == '/'
225
+ matches = [ '/' ]
226
+ else
227
+ matches = [ '' ]
228
+ end
229
+
230
+ # Split the path, and get rid of the empty path at the beginning and end
231
+ # (/a/b/c/ -> [ 'a', 'b', 'c' ])
232
+ parts = path.split('/').select { |x| x != '' }.to_a
233
+
234
+ # Descend until we find the matches:
235
+ # path = 'a/b/c'
236
+ # parts = [ 'a', 'b', 'c' ]
237
+ # Starting matches = [ '' ]
238
+ parts.each_with_index do |part, index|
239
+ # For each match, list <match>/<part> and set matches to that.
240
+ #
241
+ # Example: /*/foo
242
+ # 1. To start,
243
+ # matches = [ '/' ], part = '*'.
244
+ # list('/', '*') = [ '/organizations, '/users' ]
245
+ # 2. matches = [ '/organizations', '/users' ], part = 'foo'
246
+ # list('/organizations', 'foo') = [ '/organizations/foo' ]
247
+ # list('/users', 'foo') = [ '/users/foo' ]
248
+ #
249
+ # Result: /*/foo = [ '/organizations/foo', '/users/foo' ]
250
+ #
251
+ matches = Chef::ChefFS::Parallelizer.parallelize(matches) do |path|
252
+ found, error = list(path, part)
253
+ if error
254
+ if parts[0..index-1].all? { |p| p != '*' }
255
+ raise error
256
+ end
257
+ []
258
+ else
259
+ found
260
+ end
261
+ end.flatten(1).to_a
262
+ end
263
+
264
+ matches
265
+ end
266
+
267
+ #
268
+ # Takes a normal path and finds the Chef path to get / set its ACL.
269
+ #
270
+ # nodes/x -> nodes/x/_acl
271
+ # nodes -> containers/nodes/_acl
272
+ # '' -> organizations/_acl (the org acl)
273
+ # /organizations/foo -> /organizations/foo/organizations/_acl
274
+ # /users/foo -> /users/foo/_acl
275
+ # /organizations/foo/nodes/x -> /organizations/foo/nodes/x/_acl
276
+ #
277
+ def acl_path(path)
278
+ parts = path.split('/').select { |x| x != '' }.to_a
279
+ prefix = (path[0] == '/') ? '/' : ''
280
+
281
+ case parts.size
282
+ when 0
283
+ # /, empty (relative root)
284
+ # The root of the server has no publicly visible ACLs. Only nodes/*, etc.
285
+ if prefix == ''
286
+ ::File.join('organizations', '_acl')
287
+ end
288
+
289
+ when 1
290
+ # nodes, roles, etc.
291
+ # The top level organizations and users containers have no publicly
292
+ # visible ACLs. Only nodes/*, etc.
293
+ if prefix == ''
294
+ ::File.join('containers', path, '_acl')
295
+ end
296
+
297
+ when 2
298
+ # /organizations/NAME, /users/NAME, nodes/NAME, roles/NAME, etc.
299
+ if prefix == '/' && parts[0] == 'organizations'
300
+ ::File.join(path, 'organizations', '_acl')
301
+ else
302
+ ::File.join(path, '_acl')
303
+ end
304
+
305
+ when 3
306
+ # /organizations/NAME/nodes, cookbooks/NAME/VERSION, etc.
307
+ if prefix == '/'
308
+ ::File.join('/', parts[0], parts[1], 'containers', parts[2], '_acl')
309
+ else
310
+ ::File.join(parts[0], parts[1], '_acl')
311
+ end
312
+
313
+ when 4
314
+ # /organizations/NAME/nodes/NAME, cookbooks/NAME/VERSION/BLAH
315
+ # /organizations/NAME/nodes/NAME, cookbooks/NAME/VERSION, etc.
316
+ if prefix == '/'
317
+ ::File.join(path, '_acl')
318
+ else
319
+ ::File.join(parts[0], parts[1], '_acl')
320
+ end
321
+
322
+ else
323
+ # /organizations/NAME/cookbooks/NAME/VERSION/..., cookbooks/NAME/VERSION/A/B/...
324
+ if prefix == '/'
325
+ ::File.join('/', parts[0], parts[1], parts[2], parts[3], '_acl')
326
+ else
327
+ ::File.join(parts[0], parts[1], '_acl')
328
+ end
329
+ end
330
+ end
331
+
332
+ #
333
+ # Lists the securable children under a path (the ones that either have ACLs
334
+ # or have children with ACLs).
335
+ #
336
+ # list('nodes', 'x') -> [ 'nodes/x' ]
337
+ # list('nodes', '*') -> [ 'nodes/x', 'nodes/y', 'nodes/z' ]
338
+ # list('', '*') -> [ 'clients', 'environments', 'nodes', 'roles', ... ]
339
+ # list('/', '*') -> [ '/organizations']
340
+ # list('cookbooks', 'x') -> [ 'cookbooks/x' ]
341
+ # list('cookbooks/x', '*') -> [ ] # Individual cookbook versions do not have their own ACLs
342
+ # list('/organizations/foo/nodes', '*') -> [ '/organizations/foo/nodes/x', '/organizations/foo/nodes/y' ]
343
+ #
344
+ # The list of children of an organization is == the list of containers. If new
345
+ # containers are added, the list of children will grow. This allows the system
346
+ # to extend to new types of objects and allow cheffish to work with them.
347
+ #
348
+ def list(path, child)
349
+ # TODO make ChefFS understand top level organizations and stop doing this altogether.
350
+ parts = path.split('/').select { |x| x != '' }.to_a
351
+ absolute = (path[0] == '/')
352
+ if absolute && parts[0] == 'organizations'
353
+ return [ [], "ACLs cannot be set on children of #{path}" ] if parts.size > 3
354
+ else
355
+ return [ [], "ACLs cannot be set on children of #{path}" ] if parts.size > 1
356
+ end
357
+
358
+ error = nil
359
+
360
+ if child == '*'
361
+ case parts.size
362
+ when 0
363
+ # /*, *
364
+ if absolute
365
+ results = [ "/organizations", "/users" ]
366
+ else
367
+ results, error = rest_list("containers")
368
+ end
369
+
370
+ when 1
371
+ # /organizations/*, /users/*, roles/*, nodes/*, etc.
372
+ results, error = rest_list(path)
373
+ if !error
374
+ results = results.map { |result| ::File.join(path, result) }
375
+ end
376
+
377
+ when 2
378
+ # /organizations/NAME/*
379
+ results, error = rest_list(::File.join(path, 'containers'))
380
+ if !error
381
+ results = results.map { |result| ::File.join(path, result) }
382
+ end
383
+
384
+ when 3
385
+ # /organizations/NAME/TYPE/*
386
+ results, error = rest_list(path)
387
+ if !error
388
+ results = results.map { |result| ::File.join(path, result) }
389
+ end
390
+ end
391
+
392
+ else
393
+ if child == 'data_bags' &&
394
+ (parts.size == 0 || (parts.size == 2 && parts[0] == 'organizations'))
395
+ child = 'data'
396
+ end
397
+
398
+ if absolute
399
+ # /<child>, /users/<child>, /organizations/<child>, /organizations/foo/<child>, /organizations/foo/nodes/<child> ...
400
+ results = [ ::File.join('/', parts[0..2], child) ]
401
+ elsif parts.size == 0
402
+ # <child> (nodes, roles, etc.)
403
+ results = [ child ]
404
+ else
405
+ # nodes/<child>, roles/<child>, etc.
406
+ results = [ ::File.join(parts[0], child) ]
407
+ end
408
+ end
409
+
410
+ [ results, error ]
411
+ end
412
+
413
+ def rest_url(path)
414
+ path[0] == '/' ? URI.join(rest.url, path) : path
415
+ end
416
+
417
+ def rest_list(path)
418
+ begin
419
+ # All our rest lists are hashes where the keys are the names
420
+ [ rest.get(rest_url(path)).keys, nil ]
421
+ rescue Net::HTTPServerException => e
422
+ if e.response.code == '405' || e.response.code == '404'
423
+ parts = path.split('/').select { |p| p != '' }.to_a
424
+
425
+ # We KNOW we expect these to exist. Other containers may or may not.
426
+ unless (parts.size == 1 || (parts.size == 3 && parts[0] == 'organizations')) &&
427
+ %w(clients containers cookbooks data environments groups nodes roles).include?(parts[-1])
428
+ return [ [], "Cannot get list of #{path}: HTTP response code #{e.response.code}" ]
429
+ end
430
+ end
431
+ raise
432
+ end
433
+ end
434
+ end
@@ -12,6 +12,10 @@ class Chef::Provider::ChefClient < Cheffish::ActorProviderBase
12
12
  'client'
13
13
  end
14
14
 
15
+ def actor_path
16
+ 'clients'
17
+ end
18
+
15
19
  action :create do
16
20
  create_actor
17
21
  end
@@ -41,4 +45,4 @@ class Chef::Provider::ChefClient < Cheffish::ActorProviderBase
41
45
  }
42
46
  end
43
47
 
44
- end
48
+ end
@@ -0,0 +1,50 @@
1
+ require 'cheffish/chef_provider_base'
2
+ require 'chef/resource/chef_container'
3
+ require 'chef/chef_fs/data_handler/container_data_handler'
4
+
5
+ class Chef::Provider::ChefContainer < Cheffish::ChefProviderBase
6
+
7
+ def whyrun_supported?
8
+ true
9
+ end
10
+
11
+ action :create do
12
+ if !@current_exists
13
+ converge_by "create container #{new_resource.name} at #{rest.url}" do
14
+ rest.post("containers", normalize_for_post(new_json))
15
+ end
16
+ end
17
+ end
18
+
19
+ action :delete do
20
+ if @current_exists
21
+ converge_by "delete container #{new_resource.name} at #{rest.url}" do
22
+ rest.delete("containers/#{new_resource.name}")
23
+ end
24
+ end
25
+ end
26
+
27
+ def load_current_resource
28
+ begin
29
+ @current_exists = rest.get("containers/#{new_resource.name}")
30
+ rescue Net::HTTPServerException => e
31
+ if e.response.code == "404"
32
+ @current_exists = false
33
+ else
34
+ raise
35
+ end
36
+ end
37
+ end
38
+
39
+ def new_json
40
+ {}
41
+ end
42
+
43
+ def data_handler
44
+ Chef::ChefFS::DataHandler::ContainerDataHandler.new
45
+ end
46
+
47
+ def keys
48
+ { 'containername' => :name, 'containerpath' => :name }
49
+ end
50
+ end
@@ -0,0 +1,78 @@
1
+ require 'cheffish/chef_provider_base'
2
+ require 'chef/resource/chef_group'
3
+ require 'chef/chef_fs/data_handler/group_data_handler'
4
+
5
+ class Chef::Provider::ChefGroup < Cheffish::ChefProviderBase
6
+
7
+ def whyrun_supported?
8
+ true
9
+ end
10
+
11
+ action :create do
12
+ differences = json_differences(current_json, new_json)
13
+
14
+ if current_resource_exists?
15
+ if differences.size > 0
16
+ description = [ "update group #{new_resource.name} at #{rest.url}" ] + differences
17
+ converge_by description do
18
+ rest.put("groups/#{new_resource.name}", normalize_for_put(new_json))
19
+ end
20
+ end
21
+ else
22
+ description = [ "create group #{new_resource.name} at #{rest.url}" ] + differences
23
+ converge_by description do
24
+ rest.post("groups", normalize_for_post(new_json))
25
+ end
26
+ end
27
+ end
28
+
29
+ action :delete do
30
+ if current_resource_exists?
31
+ converge_by "delete group #{new_resource.name} at #{rest.url}" do
32
+ rest.delete("groups/#{new_resource.name}")
33
+ end
34
+ end
35
+ end
36
+
37
+ def load_current_resource
38
+ begin
39
+ @current_resource = json_to_resource(rest.get("groups/#{new_resource.name}"))
40
+ rescue Net::HTTPServerException => e
41
+ if e.response.code == "404"
42
+ @current_resource = not_found_resource
43
+ else
44
+ raise
45
+ end
46
+ end
47
+ end
48
+
49
+ def augment_new_json(json)
50
+ # Apply modifiers
51
+ json['users'] |= new_resource.users
52
+ json['clients'] |= new_resource.clients
53
+ json['groups'] |= new_resource.groups
54
+ json['users'] -= new_resource.remove_users
55
+ json['clients'] -= new_resource.remove_clients
56
+ json['groups'] -= new_resource.remove_groups
57
+ json
58
+ end
59
+
60
+ #
61
+ # Helpers
62
+ #
63
+
64
+ def resource_class
65
+ Chef::Resource::ChefGroup
66
+ end
67
+
68
+ def data_handler
69
+ Chef::ChefFS::DataHandler::GroupDataHandler.new
70
+ end
71
+
72
+ def keys
73
+ {
74
+ 'name' => :name,
75
+ 'groupname' => :name
76
+ }
77
+ end
78
+ end