cheffish 1.6.0 → 2.0.0

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