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.
- checksums.yaml +4 -4
- data/cheffish.gemspec +1 -0
- data/lib/chef/resource/chef_acl.rb +440 -20
- data/lib/chef/resource/chef_client.rb +50 -25
- data/lib/chef/resource/chef_container.rb +44 -11
- data/lib/chef/resource/chef_data_bag.rb +43 -10
- data/lib/chef/resource/chef_data_bag_item.rb +292 -82
- data/lib/chef/resource/chef_environment.rb +79 -27
- data/lib/chef/resource/chef_group.rb +77 -40
- data/lib/chef/resource/chef_mirror.rb +170 -21
- data/lib/chef/resource/chef_node.rb +77 -11
- data/lib/chef/resource/chef_organization.rb +153 -43
- data/lib/chef/resource/chef_resolved_cookbooks.rb +40 -9
- data/lib/chef/resource/chef_role.rb +81 -29
- data/lib/chef/resource/chef_user.rb +64 -33
- data/lib/chef/resource/private_key.rb +230 -17
- data/lib/chef/resource/public_key.rb +88 -9
- data/lib/cheffish/array_property.rb +29 -0
- data/lib/cheffish/base_resource.rb +254 -0
- data/lib/cheffish/chef_actor_base.rb +135 -0
- data/lib/cheffish/node_properties.rb +107 -0
- data/lib/cheffish/recipe_dsl.rb +0 -14
- data/lib/cheffish/version.rb +1 -1
- data/lib/cheffish.rb +4 -108
- data/spec/integration/chef_acl_spec.rb +0 -2
- data/spec/integration/chef_client_spec.rb +0 -1
- data/spec/integration/chef_container_spec.rb +0 -2
- data/spec/integration/chef_group_spec.rb +0 -2
- data/spec/integration/chef_mirror_spec.rb +0 -2
- data/spec/integration/chef_node_spec.rb +0 -2
- data/spec/integration/chef_organization_spec.rb +1 -3
- data/spec/integration/chef_role_spec.rb +0 -2
- data/spec/integration/chef_user_spec.rb +0 -2
- data/spec/integration/private_key_spec.rb +0 -4
- data/spec/integration/recipe_dsl_spec.rb +0 -2
- data/spec/support/spec_support.rb +0 -1
- data/spec/unit/get_private_key_spec.rb +13 -0
- metadata +22 -20
- data/lib/chef/provider/chef_acl.rb +0 -446
- data/lib/chef/provider/chef_client.rb +0 -53
- data/lib/chef/provider/chef_container.rb +0 -55
- data/lib/chef/provider/chef_data_bag.rb +0 -55
- data/lib/chef/provider/chef_data_bag_item.rb +0 -278
- data/lib/chef/provider/chef_environment.rb +0 -83
- data/lib/chef/provider/chef_group.rb +0 -83
- data/lib/chef/provider/chef_mirror.rb +0 -169
- data/lib/chef/provider/chef_node.rb +0 -87
- data/lib/chef/provider/chef_organization.rb +0 -155
- data/lib/chef/provider/chef_resolved_cookbooks.rb +0 -46
- data/lib/chef/provider/chef_role.rb +0 -84
- data/lib/chef/provider/chef_user.rb +0 -59
- data/lib/chef/provider/private_key.rb +0 -225
- data/lib/chef/provider/public_key.rb +0 -88
- data/lib/cheffish/actor_provider_base.rb +0 -131
- data/lib/cheffish/chef_provider_base.rb +0 -246
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 06975d4c508e765ad2bd0a254a91344cd1e61f00
|
4
|
+
data.tar.gz: f101d6fe5aa74c19468480c786d27d4f3ac6e3ee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cfc31b3af5cc86a27a1c8a99db39826a86fff517c0ecb5be97ba872e95a65d533d3b0760b652e725a2fa2e47640d45cc0dab1c0fc0450f20d9f73b366700fcef
|
7
|
+
data.tar.gz: dbf36a51dbef80937d885d7c48b9765aea6c77bd7142137eaa03e991b23032ff660902b5dea0c2d1cb3a055eeea552880984b7526aa106ba987858808cb4fd9d
|
data/cheffish.gemspec
CHANGED
@@ -1,35 +1,23 @@
|
|
1
1
|
require 'cheffish'
|
2
|
-
require '
|
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 <
|
7
|
-
|
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
|
-
|
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
|
-
|
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 '
|
2
|
+
require 'cheffish/chef_actor_base'
|
3
3
|
|
4
4
|
class Chef
|
5
5
|
class Resource
|
6
|
-
class ChefClient <
|
7
|
-
|
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
|
-
|
19
|
-
|
20
|
-
|
10
|
+
property :name, Cheffish::NAME_REGEX, name_property: true
|
11
|
+
property :admin, Boolean
|
12
|
+
property :validator, Boolean
|
21
13
|
|
22
14
|
# Input key
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
29
|
-
|
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 '
|
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 <
|
7
|
-
|
7
|
+
class ChefContainer < Cheffish::BaseResource
|
8
|
+
resource_name :chef_container
|
8
9
|
|
9
|
-
|
10
|
-
default_action :create
|
10
|
+
property :name, Cheffish::NAME_REGEX, name_property: true
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
19
|
-
|
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
|