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
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