cheffish 1.6.0 → 2.0.0

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 (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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4289cc92396d079a533cb15440e7179a55f18ba4
4
- data.tar.gz: 2dc7c782e3906f0f01306e7bb88c2707eaf94d2e
3
+ metadata.gz: 06975d4c508e765ad2bd0a254a91344cd1e61f00
4
+ data.tar.gz: f101d6fe5aa74c19468480c786d27d4f3ac6e3ee
5
5
  SHA512:
6
- metadata.gz: 9500d8446ad6de7ef42f99907894bf66327363f6c816b9a2a392f0681b45c3f6c04c2a56bea8c4d8aecc1603ab0719704a0dac09a9f9d34a628a3628f101f03f
7
- data.tar.gz: d90933e816465955d008d384dc59ba6c0ef6215e0cfe658d8d3bbe19ebf0aac8da141e8fc9bec5b43744aedcab63be580dc99293b170400330536e4b9d60a3c1
6
+ metadata.gz: cfc31b3af5cc86a27a1c8a99db39826a86fff517c0ecb5be97ba872e95a65d533d3b0760b652e725a2fa2e47640d45cc0dab1c0fc0450f20d9f73b366700fcef
7
+ data.tar.gz: dbf36a51dbef80937d885d7c48b9765aea6c77bd7142137eaa03e991b23032ff660902b5dea0c2d1cb3a055eeea552880984b7526aa106ba987858808cb4fd9d
data/cheffish.gemspec CHANGED
@@ -13,6 +13,7 @@ Gem::Specification.new do |s|
13
13
  s.homepage = 'http://github.com/chef/cheffish'
14
14
 
15
15
  s.add_dependency 'chef-zero', '~> 4.3'
16
+ s.add_dependency 'compat_resource'
16
17
 
17
18
  s.add_development_dependency 'chef', '~> 12.2'
18
19
  s.add_development_dependency 'rake'
@@ -1,35 +1,23 @@
1
1
  require 'cheffish'
2
- require 'chef/resource/lwrp_base'
2
+ require 'cheffish/base_resource'
3
+ require 'chef/chef_fs/data_handler/acl_data_handler'
4
+ require 'chef/chef_fs/parallelizer'
5
+ require 'uri'
3
6
 
4
7
  class Chef
5
8
  class Resource
6
- class ChefAcl < Chef::Resource::LWRPBase
7
- self.resource_name = 'chef_acl'
8
-
9
- actions :create, :nothing
10
- default_action :create
11
-
12
- def initialize(*args)
13
- super
14
- chef_server run_context.cheffish.current_chef_server
15
- end
9
+ class ChefAcl < Cheffish::BaseResource
10
+ resource_name :chef_acl
16
11
 
17
12
  # Path of the thing being secured, e.g. nodes, nodes/*, nodes/mynode,
18
13
  # */*, **, roles/base, data/secrets, cookbooks/apache2, /users/*,
19
14
  # /organizations/foo/nodes/x
20
- attribute :path, :kind_of => String, :name_attribute => true
15
+ property :path, String, name_property: true
21
16
 
22
17
  # Whether to change things recursively. true means it will descend all children
23
18
  # and make the same modifications to them. :on_change will only descend if
24
19
  # the parent has changed. :on_change is the default.
25
- attribute :recursive, :equal_to => [ true, false, :on_change ], :default => :on_change
26
-
27
- # Specifies that this is a complete specification for the acl (i.e. rights
28
- # you don't specify will be reset to their defaults)
29
- attribute :complete, :kind_of => [TrueClass, FalseClass]
30
-
31
- attribute :raw_json, :kind_of => Hash
32
- attribute :chef_server, :kind_of => Hash
20
+ property :recursive, [ true, false, :on_change ], default: :on_change
33
21
 
34
22
  # rights :read, :users => 'jkeiser', :groups => [ 'admins', 'users' ]
35
23
  # rights [ :create, :read ], :users => [ 'jkeiser', 'adam' ]
@@ -64,6 +52,438 @@ class Chef
64
52
  @remove_rights << args
65
53
  end
66
54
  end
55
+
56
+ action :create do
57
+ if new_resource.remove_rights && new_resource.complete
58
+ Chef::Log.warn("'remove_rights' is redundant when 'complete' is specified: all rights not specified in a 'rights' declaration will be removed.")
59
+ end
60
+ # Verify that we're not destroying all hope of ACL recovery here
61
+ if new_resource.complete && (!new_resource.rights || !new_resource.rights.any? { |r| r[:permissions].include?(:all) || r[:permissions].include?(:grant) })
62
+ # NOTE: if superusers exist, this should turn into a warning.
63
+ 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."
64
+ end
65
+
66
+ # Find all matching paths so we can update them (resolve * and **)
67
+ paths = match_paths(new_resource.path)
68
+ if paths.size == 0 && !new_resource.path.split('/').any? { |p| p == '*' }
69
+ raise "Path #{new_resource.path} cannot have an ACL set on it!"
70
+ end
71
+
72
+ # Go through the matches and update the ACLs for them
73
+ paths.each do |path|
74
+ create_acl(path)
75
+ end
76
+ end
77
+
78
+ action_class.class_eval do
79
+ # Update the ACL if necessary.
80
+ def create_acl(path)
81
+ changed = false
82
+ # There may not be an ACL path for some valid paths (/ and /organizations,
83
+ # for example). We want to recurse into these, but we don't want to try to
84
+ # update nonexistent ACLs for them.
85
+ acl = acl_path(path)
86
+ if acl
87
+ # It's possible to make a custom container
88
+ current_json = current_acl(acl)
89
+ if current_json
90
+
91
+ # Compare the desired and current json for the ACL, and update if different.
92
+ modify = {}
93
+ desired_acl(acl).each do |permission, desired_json|
94
+ differences = json_differences(sort_values(current_json[permission]), sort_values(desired_json))
95
+
96
+ if differences.size > 0
97
+ # Verify we aren't trying to destroy grant permissions
98
+ if permission == 'grant' && desired_json['actors'] == [] && desired_json['groups'] == []
99
+ # NOTE: if superusers exist, this should turn into a warning.
100
+ 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."
101
+ end
102
+ modify[differences] ||= {}
103
+ modify[differences][permission] = desired_json
104
+ end
105
+ end
106
+
107
+ if modify.size > 0
108
+ changed = true
109
+ description = [ "update acl #{path} at #{rest_url(path)}" ] + modify.map do |diffs, permissions|
110
+ diffs.map { |diff| " #{permissions.keys.join(', ')}:#{diff}" }
111
+ end.flatten(1)
112
+ converge_by description do
113
+ modify.values.each do |permissions|
114
+ permissions.each do |permission, desired_json|
115
+ rest.put(rest_url("#{acl}/#{permission}"), { permission => desired_json })
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ # If we have been asked to recurse, do so.
124
+ # If recurse is on_change, then we will recurse if there is no ACL, or if
125
+ # the ACL has changed.
126
+ if new_resource.recursive == true || (new_resource.recursive == :on_change && (!acl || changed))
127
+ children, error = list(path, '*')
128
+ Chef::ChefFS::Parallelizer.parallel_do(children) do |child|
129
+ next if child.split('/')[-1] == 'containers'
130
+ create_acl(child)
131
+ end
132
+ # containers mess up our descent, so we do them last
133
+ Chef::ChefFS::Parallelizer.parallel_do(children) do |child|
134
+ next if child.split('/')[-1] != 'containers'
135
+ create_acl(child)
136
+ end
137
+
138
+ end
139
+ end
140
+
141
+ # Get the current ACL for the given path
142
+ def current_acl(acl_path)
143
+ @current_acls ||= {}
144
+ if !@current_acls.has_key?(acl_path)
145
+ @current_acls[acl_path] = begin
146
+ rest.get(rest_url(acl_path))
147
+ rescue Net::HTTPServerException => e
148
+ unless e.response.code == '404' && new_resource.path.split('/').any? { |p| p == '*' }
149
+ raise
150
+ end
151
+ end
152
+ end
153
+ @current_acls[acl_path]
154
+ end
155
+
156
+ # Get the desired acl for the given acl path
157
+ def desired_acl(acl_path)
158
+ result = new_resource.raw_json ? new_resource.raw_json.dup : {}
159
+
160
+ # Calculate the JSON based on rights
161
+ add_rights(acl_path, result)
162
+
163
+ if new_resource.complete
164
+ result = Chef::ChefFS::DataHandler::AclDataHandler.new.normalize(result, nil)
165
+ else
166
+ # If resource is incomplete, use current json to fill any holes
167
+ current_acl(acl_path).each do |permission, perm_hash|
168
+ if !result[permission]
169
+ result[permission] = perm_hash.dup
170
+ else
171
+ result[permission] = result[permission].dup
172
+ perm_hash.each do |type, actors|
173
+ if !result[permission][type]
174
+ result[permission][type] = actors
175
+ else
176
+ result[permission][type] = result[permission][type].dup
177
+ result[permission][type] |= actors
178
+ end
179
+ end
180
+ end
181
+ end
182
+
183
+ remove_rights(result)
184
+ end
185
+ result
186
+ end
187
+
188
+ def sort_values(json)
189
+ json.each do |key, value|
190
+ json[key] = value.sort if value.is_a?(Array)
191
+ end
192
+ json
193
+ end
194
+
195
+ def add_rights(acl_path, json)
196
+ if new_resource.rights
197
+ new_resource.rights.each do |rights|
198
+ if rights[:permissions].delete(:all)
199
+ rights[:permissions] |= current_acl(acl_path).keys
200
+ end
201
+
202
+ Array(rights[:permissions]).each do |permission|
203
+ ace = json[permission.to_s] ||= {}
204
+ # WTF, no distinction between users and clients? The Chef API doesn't
205
+ # let us distinguish, so we have no choice :/ This means that:
206
+ # 1. If you specify :users => 'foo', and client 'foo' exists, it will
207
+ # pick that (whether user 'foo' exists or not)
208
+ # 2. If you specify :clients => 'foo', and user 'foo' exists but
209
+ # client 'foo' does not, it will pick user 'foo' and put it in the
210
+ # ACL
211
+ # 3. If an existing item has user 'foo' on it and you specify :clients
212
+ # => 'foo' instead, idempotence will not notice that anything needs
213
+ # to be updated and nothing will happen.
214
+ if rights[:users]
215
+ ace['actors'] ||= []
216
+ ace['actors'] |= Array(rights[:users])
217
+ end
218
+ if rights[:clients]
219
+ ace['actors'] ||= []
220
+ ace['actors'] |= Array(rights[:clients])
221
+ end
222
+ if rights[:groups]
223
+ ace['groups'] ||= []
224
+ ace['groups'] |= Array(rights[:groups])
225
+ end
226
+ end
227
+ end
228
+ end
229
+ end
230
+
231
+ def remove_rights(json)
232
+ if new_resource.remove_rights
233
+ new_resource.remove_rights.each do |rights|
234
+ rights[:permissions].each do |permission|
235
+ if permission == :all
236
+ json.each_key do |key|
237
+ ace = json[key] = json[key.dup]
238
+ ace['actors'] = ace['actors'] - Array(rights[:users]) if rights[:users] && ace['actors']
239
+ ace['actors'] = ace['actors'] - Array(rights[:clients]) if rights[:clients] && ace['actors']
240
+ ace['groups'] = ace['groups'] - Array(rights[:groups]) if rights[:groups] && ace['groups']
241
+ end
242
+ else
243
+ ace = json[permission.to_s] = json[permission.to_s].dup
244
+ if ace
245
+ ace['actors'] = ace['actors'] - Array(rights[:users]) if rights[:users] && ace['actors']
246
+ ace['actors'] = ace['actors'] - Array(rights[:clients]) if rights[:clients] && ace['actors']
247
+ ace['groups'] = ace['groups'] - Array(rights[:groups]) if rights[:groups] && ace['groups']
248
+ end
249
+ end
250
+ end
251
+ end
252
+ end
253
+ end
254
+
255
+ def load_current_resource
256
+ end
257
+
258
+ #
259
+ # Matches chef_acl paths like nodes, nodes/*.
260
+ #
261
+ # == Examples
262
+ # match_paths('nodes'): [ 'nodes' ]
263
+ # match_paths('nodes/*'): [ 'nodes/x', 'nodes/y', 'nodes/z' ]
264
+ # match_paths('*'): [ 'clients', 'environments', 'nodes', 'roles', ... ]
265
+ # match_paths('/'): [ '/' ]
266
+ # match_paths(''): [ '' ]
267
+ # match_paths('/*'): [ '/organizations', '/users' ]
268
+ # match_paths('/organizations/*/*'): [ '/organizations/foo/clients', '/organizations/foo/environments', ..., '/organizations/bar/clients', '/organizations/bar/environments', ... ]
269
+ #
270
+ def match_paths(path)
271
+ # Turn multiple slashes into one
272
+ # nodes//x -> nodes/x
273
+ path = path.gsub(/[\/]+/, '/')
274
+ # If it's absolute, start the matching with /. If it's relative, start with '' (relative root).
275
+ if path[0] == '/'
276
+ matches = [ '/' ]
277
+ else
278
+ matches = [ '' ]
279
+ end
280
+
281
+ # Split the path, and get rid of the empty path at the beginning and end
282
+ # (/a/b/c/ -> [ 'a', 'b', 'c' ])
283
+ parts = path.split('/').select { |x| x != '' }.to_a
284
+
285
+ # Descend until we find the matches:
286
+ # path = 'a/b/c'
287
+ # parts = [ 'a', 'b', 'c' ]
288
+ # Starting matches = [ '' ]
289
+ parts.each_with_index do |part, index|
290
+ # For each match, list <match>/<part> and set matches to that.
291
+ #
292
+ # Example: /*/foo
293
+ # 1. To start,
294
+ # matches = [ '/' ], part = '*'.
295
+ # list('/', '*') = [ '/organizations, '/users' ]
296
+ # 2. matches = [ '/organizations', '/users' ], part = 'foo'
297
+ # list('/organizations', 'foo') = [ '/organizations/foo' ]
298
+ # list('/users', 'foo') = [ '/users/foo' ]
299
+ #
300
+ # Result: /*/foo = [ '/organizations/foo', '/users/foo' ]
301
+ #
302
+ matches = Chef::ChefFS::Parallelizer.parallelize(matches) do |path|
303
+ found, error = list(path, part)
304
+ if error
305
+ if parts[0..index-1].all? { |p| p != '*' }
306
+ raise error
307
+ end
308
+ []
309
+ else
310
+ found
311
+ end
312
+ end.flatten(1).to_a
313
+ end
314
+
315
+ matches
316
+ end
317
+
318
+ #
319
+ # Takes a normal path and finds the Chef path to get / set its ACL.
320
+ #
321
+ # nodes/x -> nodes/x/_acl
322
+ # nodes -> containers/nodes/_acl
323
+ # '' -> organizations/_acl (the org acl)
324
+ # /organizations/foo -> /organizations/foo/organizations/_acl
325
+ # /users/foo -> /users/foo/_acl
326
+ # /organizations/foo/nodes/x -> /organizations/foo/nodes/x/_acl
327
+ #
328
+ def acl_path(path)
329
+ parts = path.split('/').select { |x| x != '' }.to_a
330
+ prefix = (path[0] == '/') ? '/' : ''
331
+
332
+ case parts.size
333
+ when 0
334
+ # /, empty (relative root)
335
+ # The root of the server has no publicly visible ACLs. Only nodes/*, etc.
336
+ if prefix == ''
337
+ ::File.join('organizations', '_acl')
338
+ end
339
+
340
+ when 1
341
+ # nodes, roles, etc.
342
+ # The top level organizations and users containers have no publicly
343
+ # visible ACLs. Only nodes/*, etc.
344
+ if prefix == ''
345
+ ::File.join('containers', path, '_acl')
346
+ end
347
+
348
+ when 2
349
+ # /organizations/NAME, /users/NAME, nodes/NAME, roles/NAME, etc.
350
+ if prefix == '/' && parts[0] == 'organizations'
351
+ ::File.join(path, 'organizations', '_acl')
352
+ else
353
+ ::File.join(path, '_acl')
354
+ end
355
+
356
+ when 3
357
+ # /organizations/NAME/nodes, cookbooks/NAME/VERSION, etc.
358
+ if prefix == '/'
359
+ ::File.join('/', parts[0], parts[1], 'containers', parts[2], '_acl')
360
+ else
361
+ ::File.join(parts[0], parts[1], '_acl')
362
+ end
363
+
364
+ when 4
365
+ # /organizations/NAME/nodes/NAME, cookbooks/NAME/VERSION/BLAH
366
+ # /organizations/NAME/nodes/NAME, cookbooks/NAME/VERSION, etc.
367
+ if prefix == '/'
368
+ ::File.join(path, '_acl')
369
+ else
370
+ ::File.join(parts[0], parts[1], '_acl')
371
+ end
372
+
373
+ else
374
+ # /organizations/NAME/cookbooks/NAME/VERSION/..., cookbooks/NAME/VERSION/A/B/...
375
+ if prefix == '/'
376
+ ::File.join('/', parts[0], parts[1], parts[2], parts[3], '_acl')
377
+ else
378
+ ::File.join(parts[0], parts[1], '_acl')
379
+ end
380
+ end
381
+ end
382
+
383
+ #
384
+ # Lists the securable children under a path (the ones that either have ACLs
385
+ # or have children with ACLs).
386
+ #
387
+ # list('nodes', 'x') -> [ 'nodes/x' ]
388
+ # list('nodes', '*') -> [ 'nodes/x', 'nodes/y', 'nodes/z' ]
389
+ # list('', '*') -> [ 'clients', 'environments', 'nodes', 'roles', ... ]
390
+ # list('/', '*') -> [ '/organizations']
391
+ # list('cookbooks', 'x') -> [ 'cookbooks/x' ]
392
+ # list('cookbooks/x', '*') -> [ ] # Individual cookbook versions do not have their own ACLs
393
+ # list('/organizations/foo/nodes', '*') -> [ '/organizations/foo/nodes/x', '/organizations/foo/nodes/y' ]
394
+ #
395
+ # The list of children of an organization is == the list of containers. If new
396
+ # containers are added, the list of children will grow. This allows the system
397
+ # to extend to new types of objects and allow cheffish to work with them.
398
+ #
399
+ def list(path, child)
400
+ # TODO make ChefFS understand top level organizations and stop doing this altogether.
401
+ parts = path.split('/').select { |x| x != '' }.to_a
402
+ absolute = (path[0] == '/')
403
+ if absolute && parts[0] == 'organizations'
404
+ return [ [], "ACLs cannot be set on children of #{path}" ] if parts.size > 3
405
+ else
406
+ return [ [], "ACLs cannot be set on children of #{path}" ] if parts.size > 1
407
+ end
408
+
409
+ error = nil
410
+
411
+ if child == '*'
412
+ case parts.size
413
+ when 0
414
+ # /*, *
415
+ if absolute
416
+ results = [ "/organizations", "/users" ]
417
+ else
418
+ results, error = rest_list("containers")
419
+ end
420
+
421
+ when 1
422
+ # /organizations/*, /users/*, roles/*, nodes/*, etc.
423
+ results, error = rest_list(path)
424
+ if !error
425
+ results = results.map { |result| ::File.join(path, result) }
426
+ end
427
+
428
+ when 2
429
+ # /organizations/NAME/*
430
+ results, error = rest_list(::File.join(path, 'containers'))
431
+ if !error
432
+ results = results.map { |result| ::File.join(path, result) }
433
+ end
434
+
435
+ when 3
436
+ # /organizations/NAME/TYPE/*
437
+ results, error = rest_list(path)
438
+ if !error
439
+ results = results.map { |result| ::File.join(path, result) }
440
+ end
441
+ end
442
+
443
+ else
444
+ if child == 'data_bags' &&
445
+ (parts.size == 0 || (parts.size == 2 && parts[0] == 'organizations'))
446
+ child = 'data'
447
+ end
448
+
449
+ if absolute
450
+ # /<child>, /users/<child>, /organizations/<child>, /organizations/foo/<child>, /organizations/foo/nodes/<child> ...
451
+ results = [ ::File.join('/', parts[0..2], child) ]
452
+ elsif parts.size == 0
453
+ # <child> (nodes, roles, etc.)
454
+ results = [ child ]
455
+ else
456
+ # nodes/<child>, roles/<child>, etc.
457
+ results = [ ::File.join(parts[0], child) ]
458
+ end
459
+ end
460
+
461
+ [ results, error ]
462
+ end
463
+
464
+ def rest_url(path)
465
+ path[0] == '/' ? URI.join(rest.url, path) : path
466
+ end
467
+
468
+ def rest_list(path)
469
+ begin
470
+ # All our rest lists are hashes where the keys are the names
471
+ [ rest.get(rest_url(path)).keys, nil ]
472
+ rescue Net::HTTPServerException => e
473
+ if e.response.code == '405' || e.response.code == '404'
474
+ parts = path.split('/').select { |p| p != '' }.to_a
475
+
476
+ # We KNOW we expect these to exist. Other containers may or may not.
477
+ unless (parts.size == 1 || (parts.size == 3 && parts[0] == 'organizations')) &&
478
+ %w(clients containers cookbooks data environments groups nodes roles).include?(parts[-1])
479
+ return [ [], "Cannot get list of #{path}: HTTP response code #{e.response.code}" ]
480
+ end
481
+ end
482
+ raise
483
+ end
484
+ end
485
+ end
486
+
67
487
  end
68
488
  end
69
489
  end
@@ -1,38 +1,24 @@
1
1
  require 'cheffish'
2
- require 'chef/resource/lwrp_base'
2
+ require 'cheffish/chef_actor_base'
3
3
 
4
4
  class Chef
5
5
  class Resource
6
- class ChefClient < Chef::Resource::LWRPBase
7
- self.resource_name = 'chef_client'
8
-
9
- actions :create, :delete, :regenerate_keys, :nothing
10
- default_action :create
11
-
12
- def initialize(*args)
13
- super
14
- chef_server run_context.cheffish.current_chef_server
15
- end
6
+ class ChefClient < Cheffish::ChefActorBase
7
+ resource_name :chef_client
16
8
 
17
9
  # Client attributes
18
- attribute :name, :kind_of => String, :regex => Cheffish::NAME_REGEX, :name_attribute => true
19
- attribute :admin, :kind_of => [TrueClass, FalseClass]
20
- attribute :validator, :kind_of => [TrueClass, FalseClass]
10
+ property :name, Cheffish::NAME_REGEX, name_property: true
11
+ property :admin, Boolean
12
+ property :validator, Boolean
21
13
 
22
14
  # Input key
23
- attribute :source_key # String or OpenSSL::PKey::*
24
- attribute :source_key_path, :kind_of => String
25
- attribute :source_key_pass_phrase
15
+ property :source_key # String or OpenSSL::PKey::*
16
+ property :source_key_path, String
17
+ property :source_key_pass_phrase
26
18
 
27
19
  # Output public key (if so desired)
28
- attribute :output_key_path, :kind_of => String
29
- attribute :output_key_format, :kind_of => Symbol, :default => :openssh, :equal_to => [ :pem, :der, :openssh ]
30
-
31
- # If this is set, client is not patchy
32
- attribute :complete, :kind_of => [TrueClass, FalseClass]
33
-
34
- attribute :raw_json, :kind_of => Hash
35
- attribute :chef_server, :kind_of => Hash
20
+ property :output_key_path, String
21
+ property :output_key_format, Symbol, default: :openssh, equal_to: [ :pem, :der, :openssh ]
36
22
 
37
23
  # Proc that runs just before the resource executes. Called with (resource)
38
24
  def before(&block)
@@ -43,6 +29,45 @@ class Chef
43
29
  def after(&block)
44
30
  block ? @after = block : @after
45
31
  end
32
+
33
+ action :create do
34
+ create_actor
35
+ end
36
+
37
+ action :delete do
38
+ delete_actor
39
+ end
40
+
41
+ action_class.class_eval do
42
+ def actor_type
43
+ 'client'
44
+ end
45
+
46
+ def actor_path
47
+ 'clients'
48
+ end
49
+
50
+ #
51
+ # Helpers
52
+ #
53
+
54
+ def resource_class
55
+ Chef::Resource::ChefClient
56
+ end
57
+
58
+ def data_handler
59
+ Chef::ChefFS::DataHandler::ClientDataHandler.new
60
+ end
61
+
62
+ def keys
63
+ {
64
+ 'name' => :name,
65
+ 'admin' => :admin,
66
+ 'validator' => :validator,
67
+ 'public_key' => :source_key
68
+ }
69
+ end
70
+ end
46
71
  end
47
72
  end
48
73
  end
@@ -1,22 +1,55 @@
1
1
  require 'cheffish'
2
- require 'chef/resource/lwrp_base'
2
+ require 'cheffish/base_resource'
3
+ require 'chef/chef_fs/data_handler/container_data_handler'
3
4
 
4
5
  class Chef
5
6
  class Resource
6
- class ChefContainer < Chef::Resource::LWRPBase
7
- self.resource_name = 'chef_container'
7
+ class ChefContainer < Cheffish::BaseResource
8
+ resource_name :chef_container
8
9
 
9
- actions :create, :delete, :nothing
10
- default_action :create
10
+ property :name, Cheffish::NAME_REGEX, name_property: true
11
11
 
12
- # Grab environment from with_environment
13
- def initialize(*args)
14
- super
15
- chef_server run_context.cheffish.current_chef_server
12
+ action :create do
13
+ if !@current_exists
14
+ converge_by "create container #{new_resource.name} at #{rest.url}" do
15
+ rest.post("containers", normalize_for_post(new_json))
16
+ end
17
+ end
16
18
  end
17
19
 
18
- attribute :name, :kind_of => String, :regex => Cheffish::NAME_REGEX, :name_attribute => true
19
- attribute :chef_server, :kind_of => Hash
20
+ action :delete do
21
+ if @current_exists
22
+ converge_by "delete container #{new_resource.name} at #{rest.url}" do
23
+ rest.delete("containers/#{new_resource.name}")
24
+ end
25
+ end
26
+ end
27
+
28
+ action_class.class_eval do
29
+ def load_current_resource
30
+ begin
31
+ @current_exists = rest.get("containers/#{new_resource.name}")
32
+ rescue Net::HTTPServerException => e
33
+ if e.response.code == "404"
34
+ @current_exists = false
35
+ else
36
+ raise
37
+ end
38
+ end
39
+ end
40
+
41
+ def new_json
42
+ {}
43
+ end
44
+
45
+ def data_handler
46
+ Chef::ChefFS::DataHandler::ContainerDataHandler.new
47
+ end
48
+
49
+ def keys
50
+ { 'containername' => :name, 'containerpath' => :name }
51
+ end
52
+ end
20
53
  end
21
54
  end
22
55
  end