chef-zero 2.2.1 → 3.0.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -0
  3. data/Rakefile +5 -1
  4. data/bin/chef-zero +17 -0
  5. data/lib/chef_zero/chef_data/acl_path.rb +139 -0
  6. data/lib/chef_zero/chef_data/cookbook_data.rb +240 -0
  7. data/lib/chef_zero/chef_data/data_normalizer.rb +207 -0
  8. data/lib/chef_zero/chef_data/default_creator.rb +446 -0
  9. data/lib/chef_zero/data_store/default_facade.rb +149 -0
  10. data/lib/chef_zero/data_store/interface_v1.rb +18 -0
  11. data/lib/chef_zero/data_store/memory_store.rb +2 -1
  12. data/lib/chef_zero/data_store/memory_store_v2.rb +3 -36
  13. data/lib/chef_zero/data_store/raw_file_store.rb +147 -0
  14. data/lib/chef_zero/data_store/v1_to_v2_adapter.rb +39 -103
  15. data/lib/chef_zero/data_store/v2_to_v1_adapter.rb +1 -1
  16. data/lib/chef_zero/endpoints/acl_endpoint.rb +38 -0
  17. data/lib/chef_zero/endpoints/acls_endpoint.rb +29 -0
  18. data/lib/chef_zero/endpoints/actor_endpoint.rb +36 -10
  19. data/lib/chef_zero/endpoints/actors_endpoint.rb +38 -6
  20. data/lib/chef_zero/endpoints/authenticate_user_endpoint.rb +15 -9
  21. data/lib/chef_zero/endpoints/container_endpoint.rb +22 -0
  22. data/lib/chef_zero/endpoints/containers_endpoint.rb +13 -0
  23. data/lib/chef_zero/endpoints/cookbook_endpoint.rb +1 -1
  24. data/lib/chef_zero/endpoints/cookbook_version_endpoint.rb +15 -14
  25. data/lib/chef_zero/endpoints/cookbooks_base.rb +2 -2
  26. data/lib/chef_zero/endpoints/data_bag_endpoint.rb +4 -4
  27. data/lib/chef_zero/endpoints/data_bag_item_endpoint.rb +5 -5
  28. data/lib/chef_zero/endpoints/data_bags_endpoint.rb +5 -4
  29. data/lib/chef_zero/endpoints/environment_cookbook_endpoint.rb +2 -2
  30. data/lib/chef_zero/endpoints/environment_cookbook_versions_endpoint.rb +6 -6
  31. data/lib/chef_zero/endpoints/environment_cookbooks_endpoint.rb +2 -2
  32. data/lib/chef_zero/endpoints/environment_endpoint.rb +5 -5
  33. data/lib/chef_zero/endpoints/environment_nodes_endpoint.rb +2 -2
  34. data/lib/chef_zero/endpoints/environment_recipes_endpoint.rb +3 -3
  35. data/lib/chef_zero/endpoints/environment_role_endpoint.rb +2 -2
  36. data/lib/chef_zero/endpoints/file_store_file_endpoint.rb +1 -1
  37. data/lib/chef_zero/endpoints/group_endpoint.rb +20 -0
  38. data/lib/chef_zero/endpoints/groups_endpoint.rb +13 -0
  39. data/lib/chef_zero/endpoints/license_endpoint.rb +25 -0
  40. data/lib/chef_zero/endpoints/node_endpoint.rb +5 -5
  41. data/lib/chef_zero/endpoints/not_found_endpoint.rb +2 -2
  42. data/lib/chef_zero/endpoints/organization_association_request_endpoint.rb +22 -0
  43. data/lib/chef_zero/endpoints/organization_association_requests_endpoint.rb +29 -0
  44. data/lib/chef_zero/endpoints/organization_authenticate_user_endpoint.rb +26 -0
  45. data/lib/chef_zero/endpoints/organization_endpoint.rb +41 -0
  46. data/lib/chef_zero/endpoints/organization_user_endpoint.rb +48 -0
  47. data/lib/chef_zero/endpoints/organization_users_endpoint.rb +14 -0
  48. data/lib/chef_zero/endpoints/organization_validator_key_endpoint.rb +20 -0
  49. data/lib/chef_zero/endpoints/organizations_endpoint.rb +55 -0
  50. data/lib/chef_zero/endpoints/principal_endpoint.rb +15 -3
  51. data/lib/chef_zero/endpoints/rest_list_endpoint.rb +8 -6
  52. data/lib/chef_zero/endpoints/rest_object_endpoint.rb +12 -10
  53. data/lib/chef_zero/endpoints/role_endpoint.rb +5 -5
  54. data/lib/chef_zero/endpoints/role_environments_endpoint.rb +2 -2
  55. data/lib/chef_zero/endpoints/sandbox_endpoint.rb +2 -2
  56. data/lib/chef_zero/endpoints/sandboxes_endpoint.rb +4 -4
  57. data/lib/chef_zero/endpoints/search_endpoint.rb +10 -10
  58. data/lib/chef_zero/endpoints/system_recovery_endpoint.rb +30 -0
  59. data/lib/chef_zero/endpoints/user_association_request_endpoint.rb +40 -0
  60. data/lib/chef_zero/endpoints/user_association_requests_count_endpoint.rb +19 -0
  61. data/lib/chef_zero/endpoints/user_association_requests_endpoint.rb +19 -0
  62. data/lib/chef_zero/endpoints/user_organizations_endpoint.rb +22 -0
  63. data/lib/chef_zero/rest_base.rb +79 -13
  64. data/lib/chef_zero/rest_error_response.rb +1 -1
  65. data/lib/chef_zero/rest_request.rb +4 -0
  66. data/lib/chef_zero/rest_router.rb +1 -0
  67. data/lib/chef_zero/rspec.rb +55 -8
  68. data/lib/chef_zero/server.rb +87 -21
  69. data/lib/chef_zero/version.rb +1 -1
  70. data/spec/run_oc_pedant.rb +53 -0
  71. data/spec/{run.rb → run_pedant.rb} +13 -4
  72. data/spec/server_spec.rb +54 -0
  73. data/spec/support/oc_pedant.rb +134 -0
  74. data/spec/support/pedant.rb +1 -1
  75. metadata +54 -13
  76. data/lib/chef_zero/cookbook_data.rb +0 -236
  77. data/lib/chef_zero/data_normalizer.rb +0 -146
@@ -0,0 +1,207 @@
1
+ require 'chef_zero'
2
+ require 'chef_zero/rest_base'
3
+ require 'chef_zero/chef_data/default_creator'
4
+
5
+ module ChefZero
6
+ module ChefData
7
+ class DataNormalizer
8
+ def self.normalize_acls(acls)
9
+ ChefData::DefaultCreator::PERMISSIONS.each do |perm|
10
+ acls[perm] ||= {}
11
+ acls[perm]['actors'] ||= []
12
+ acls[perm]['groups'] ||= []
13
+ end
14
+ acls
15
+ end
16
+
17
+ def self.normalize_client(client, name)
18
+ client['name'] ||= name
19
+ client['admin'] ||= false
20
+ client['admin'] = !!client['admin']
21
+ client['public_key'] ||= PUBLIC_KEY
22
+ client['validator'] ||= false
23
+ client['validator'] = !!client['validator']
24
+ client['json_class'] ||= "Chef::ApiClient"
25
+ client['chef_type'] ||= "client"
26
+ client
27
+ end
28
+
29
+ def self.normalize_container(container, name)
30
+ container.delete('id')
31
+ container['containername'] = name
32
+ container['containerpath'] = name
33
+ container
34
+ end
35
+
36
+ def self.normalize_user(user, name, identity_keys, osc_compat, method=nil)
37
+ user[identity_keys.first] ||= name
38
+ user['public_key'] ||= PUBLIC_KEY
39
+ user['admin'] ||= false
40
+ user['admin'] = !!user['admin']
41
+ user['openid'] ||= nil
42
+ if !osc_compat
43
+ if method == 'GET'
44
+ user.delete('admin')
45
+ user.delete('password')
46
+ user.delete('openid')
47
+ end
48
+ user['email'] ||= nil
49
+ user['first_name'] ||= nil
50
+ user['last_name'] ||= nil
51
+ end
52
+ user
53
+ end
54
+
55
+ def self.normalize_data_bag_item(data_bag_item, data_bag_name, id, method)
56
+ if method == 'DELETE'
57
+ # TODO SERIOUSLY, WHO DOES THIS MANY EXCEPTIONS IN THEIR INTERFACE
58
+ if !(data_bag_item['json_class'] == 'Chef::DataBagItem' && data_bag_item['raw_data'])
59
+ data_bag_item['id'] ||= id
60
+ data_bag_item = { 'raw_data' => data_bag_item }
61
+ data_bag_item['chef_type'] ||= 'data_bag_item'
62
+ data_bag_item['json_class'] ||= 'Chef::DataBagItem'
63
+ data_bag_item['data_bag'] ||= data_bag_name
64
+ data_bag_item['name'] ||= "data_bag_item_#{data_bag_name}_#{id}"
65
+ end
66
+ else
67
+ # If it's not already wrapped with raw_data, wrap it.
68
+ if data_bag_item['json_class'] == 'Chef::DataBagItem' && data_bag_item['raw_data']
69
+ data_bag_item = data_bag_item['raw_data']
70
+ end
71
+ # Argh. We don't do this on GET, but we do on PUT and POST????
72
+ if %w(PUT POST).include?(method)
73
+ data_bag_item['chef_type'] ||= 'data_bag_item'
74
+ data_bag_item['data_bag'] ||= data_bag_name
75
+ end
76
+ data_bag_item['id'] ||= id
77
+ end
78
+ data_bag_item
79
+ end
80
+
81
+ def self.normalize_cookbook(endpoint, org_prefix, cookbook, name, version, base_uri, method)
82
+ # TODO I feel dirty
83
+ if method != 'PUT'
84
+ cookbook.each_pair do |key, value|
85
+ if value.is_a?(Array)
86
+ value.each do |file|
87
+ if file.is_a?(Hash) && file.has_key?('checksum')
88
+ file['url'] ||= endpoint.build_uri(base_uri, org_prefix + ['file_store', 'checksums', file['checksum']])
89
+ end
90
+ end
91
+ end
92
+ end
93
+ cookbook['name'] ||= "#{name}-#{version}"
94
+ # TODO this feels wrong, but the real chef server doesn't expand this default
95
+ # cookbook['version'] ||= version
96
+ cookbook['cookbook_name'] ||= name
97
+ cookbook['frozen?'] ||= false
98
+ cookbook['metadata'] ||= {}
99
+ cookbook['metadata']['version'] ||= version
100
+ # Sad to not be expanding defaults just because Chef doesn't :(
101
+ # cookbook['metadata']['name'] ||= name
102
+ # cookbook['metadata']['description'] ||= "A fabulous new cookbook"
103
+ cookbook['metadata']['long_description'] ||= ""
104
+ # cookbook['metadata']['maintainer'] ||= "YOUR_COMPANY_NAME"
105
+ # cookbook['metadata']['maintainer_email'] ||= "YOUR_EMAIL"
106
+ # cookbook['metadata']['license'] ||= "none"
107
+ cookbook['metadata']['dependencies'] ||= {}
108
+ cookbook['metadata']['attributes'] ||= {}
109
+ cookbook['metadata']['recipes'] ||= {}
110
+ end
111
+ cookbook['json_class'] ||= 'Chef::CookbookVersion'
112
+ cookbook['chef_type'] ||= 'cookbook_version'
113
+ if method == 'MIN'
114
+ cookbook['metadata'].delete('attributes')
115
+ cookbook['metadata'].delete('long_description')
116
+ end
117
+ cookbook
118
+ end
119
+
120
+ def self.normalize_environment(environment, name)
121
+ environment['name'] ||= name
122
+ environment['description'] ||= ''
123
+ environment['cookbook_versions'] ||= {}
124
+ environment['json_class'] ||= "Chef::Environment"
125
+ environment['chef_type'] ||= "environment"
126
+ environment['default_attributes'] ||= {}
127
+ environment['override_attributes'] ||= {}
128
+ environment
129
+ end
130
+
131
+ def self.normalize_group(group, name, orgname)
132
+ group.delete('id')
133
+ if group['actors'].is_a?(Hash)
134
+ group['users'] ||= group['actors']['users']
135
+ group['clients'] ||= group['actors']['clients']
136
+ group['groups'] ||= group['actors']['groups']
137
+ group['actors'] = nil
138
+ end
139
+ group['users'] ||= []
140
+ group['clients'] ||= []
141
+ group['actors'] ||= (group['clients'] + group['users'])
142
+ group['groups'] ||= []
143
+ group['orgname'] ||= orgname if orgname
144
+ group['name'] ||= name
145
+ group['groupname'] ||= name
146
+
147
+ group['users'].uniq!
148
+ group['clients'].uniq!
149
+ group['actors'].uniq!
150
+ group['groups'].uniq!
151
+ group
152
+ end
153
+
154
+ def self.normalize_node(node, name)
155
+ node['name'] ||= name
156
+ node['json_class'] ||= 'Chef::Node'
157
+ node['chef_type'] ||= 'node'
158
+ node['chef_environment'] ||= '_default'
159
+ node['override'] ||= {}
160
+ node['normal'] ||= {}
161
+ node['default'] ||= {}
162
+ node['automatic'] ||= {}
163
+ node['run_list'] ||= []
164
+ node['run_list'] = normalize_run_list(node['run_list'])
165
+ node
166
+ end
167
+
168
+ def self.normalize_organization(org, name)
169
+ org['name'] ||= name
170
+ org['full_name'] ||= name
171
+ org['org_type'] ||= 'Business'
172
+ org['clientname'] ||= "#{name}-validator"
173
+ org['billing_plan'] ||= 'platform-free'
174
+ org
175
+ end
176
+
177
+ def self.normalize_role(role, name)
178
+ role['name'] ||= name
179
+ role['description'] ||= ''
180
+ role['json_class'] ||= 'Chef::Role'
181
+ role['chef_type'] ||= 'role'
182
+ role['default_attributes'] ||= {}
183
+ role['override_attributes'] ||= {}
184
+ role['run_list'] ||= []
185
+ role['run_list'] = normalize_run_list(role['run_list'])
186
+ role['env_run_lists'] ||= {}
187
+ role['env_run_lists'].each_pair do |env, run_list|
188
+ role['env_run_lists'][env] = normalize_run_list(run_list)
189
+ end
190
+ role
191
+ end
192
+
193
+ def self.normalize_run_list(run_list)
194
+ run_list.map{|item|
195
+ case item
196
+ when /^recipe\[.*\]$/
197
+ item # explicit recipe
198
+ when /^role\[.*\]$/
199
+ item # explicit role
200
+ else
201
+ "recipe[#{item}]"
202
+ end
203
+ }.uniq
204
+ end
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,446 @@
1
+ require 'chef_zero/chef_data/acl_path'
2
+
3
+ module ChefZero
4
+ module ChefData
5
+ #
6
+ # The DefaultCreator creates default values when you ask for them.
7
+ # - It relies on created and deleted being called when things get
8
+ # created and deleted, so that it knows the owners of said objects
9
+ # and knows to eliminate default values on delete.
10
+ # - get, list and exists? get data.
11
+ #
12
+ class DefaultCreator
13
+ def initialize(data, single_org, osc_compat, superusers = nil)
14
+ @data = data
15
+ @single_org = single_org
16
+ @osc_compat = osc_compat
17
+ @superusers = superusers || DEFAULT_SUPERUSERS
18
+ clear
19
+ end
20
+
21
+ attr_reader :data
22
+ attr_reader :single_org
23
+ attr_reader :osc_compat
24
+ attr_reader :creators
25
+ attr_reader :deleted
26
+
27
+ PERMISSIONS = %w(create read update delete grant)
28
+ DEFAULT_SUPERUSERS = %w(pivotal)
29
+
30
+ def clear
31
+ @creators = { [] => @superusers }
32
+ @deleted = {}
33
+ end
34
+
35
+ def deleted(path)
36
+ # acl deletes mean nothing, they are entirely subservient to their
37
+ # parent object
38
+ if path[0] == 'acls' || (path[0] == 'organizations' && path[2] == 'acls')
39
+ return false
40
+ end
41
+
42
+ result = exists?(path)
43
+ @deleted[path] = true
44
+ result
45
+ end
46
+
47
+ def deleted?(path)
48
+ 1.upto(path.size) do |index|
49
+ return true if @deleted[path[0..-index]]
50
+ end
51
+ false
52
+ end
53
+
54
+ def created(path, creator, create_parents)
55
+ # If a parent has been deleted, we will need to clear that.
56
+ deleted_index = nil
57
+ 0.upto(path.size-1) do |index|
58
+ deleted_index = index if @deleted[path[0..index]]
59
+ end
60
+
61
+ # Walk up the tree, setting the creator on anything that doesn't exist
62
+ # (anything that is either deleted or was never created)
63
+ while (deleted_index && path.size > deleted_index) || !@creators[path]
64
+ @creators[path] = creator ? [ creator ] : []
65
+ @deleted.delete(path)
66
+ # Only do this once if create_parents is false
67
+ break if !create_parents || path.size == 0
68
+
69
+ path = path[0..-2]
70
+ end
71
+ end
72
+
73
+ def superusers
74
+ @creators[[]]
75
+ end
76
+
77
+ def get(path)
78
+ return nil if deleted?(path)
79
+
80
+ result = case path[0]
81
+ when 'acls'
82
+ # /acls/*
83
+ object_path = AclPath.get_object_path(path)
84
+ if data_exists?(object_path)
85
+ default_acl(path)
86
+ end
87
+
88
+ when 'containers'
89
+ if path.size == 2 && exists?(path)
90
+ {}
91
+ end
92
+
93
+ when 'users'
94
+ if path.size == 2 && data.exists?(path)
95
+ # User is empty user
96
+ {}
97
+ end
98
+
99
+ when 'organizations'
100
+ if path.size >= 2
101
+ # /organizations/*/**
102
+ if data.exists_dir?(path[0..1])
103
+ get_org_default(path)
104
+ end
105
+ end
106
+ end
107
+
108
+ result
109
+ end
110
+
111
+ def list(path)
112
+ return nil if deleted?(path)
113
+
114
+ if path.size == 0
115
+ return %w(containers users organizations acls)
116
+ end
117
+
118
+ case path[0]
119
+ when 'acls'
120
+ if path.size == 1
121
+ [ 'root' ] + (data.list(path + [ 'containers' ]) - [ 'organizations' ])
122
+ else
123
+ data.list(AclPath.get_object_path(path))
124
+ end
125
+
126
+ when 'containers'
127
+ [ 'containers', 'users', 'organizations' ]
128
+
129
+ when 'users'
130
+ superusers
131
+
132
+ when 'organizations'
133
+ if path.size == 1
134
+ single_org ? [ single_org ] : []
135
+ elsif path.size >= 2 && data.exists_dir?(path[0..1])
136
+ list_org_default(path)
137
+ end
138
+ end
139
+ end
140
+
141
+ def exists?(path)
142
+ return true if path.size == 0
143
+ parent_list = list(path[0..-2])
144
+ parent_list && parent_list.include?(path[-1])
145
+ end
146
+
147
+ protected
148
+
149
+ DEFAULT_ORG_SPINE = {
150
+ 'clients' => {},
151
+ 'cookbooks' => {},
152
+ 'data' => {},
153
+ 'environments' => %w(_default),
154
+ 'file_store' => {
155
+ 'checksums' => {}
156
+ },
157
+ 'nodes' => {},
158
+ 'roles' => {},
159
+ 'sandboxes' => {},
160
+ 'users' => {},
161
+
162
+ 'org' => {},
163
+ 'containers' => %w(clients containers cookbooks data environments groups nodes roles sandboxes),
164
+ 'groups' => %w(admins billing-admins clients users),
165
+ 'association_requests' => {}
166
+ }
167
+
168
+ def list_org_default(path)
169
+ if path.size >= 3 && path[2] == 'acls'
170
+ if path.size == 3
171
+ # /organizations/ORG/acls
172
+ return [ 'root' ] + data.list(path[0..1] + [ 'containers' ])
173
+ elsif path.size == 4
174
+ # /organizations/ORG/acls/TYPE
175
+ return data.list(path[0..1] + [ path[3] ])
176
+ else
177
+ return nil
178
+ end
179
+ end
180
+
181
+ value = DEFAULT_ORG_SPINE
182
+ 2.upto(path.size-1) do |index|
183
+ value = nil if @deleted[path[0..index]]
184
+ break if !value
185
+ value = value[path[index]]
186
+ end
187
+
188
+ result = if value.is_a?(Hash)
189
+ value.keys
190
+ elsif value
191
+ value
192
+ end
193
+
194
+ if path.size == 3
195
+ if path[2] == 'clients'
196
+ result << "#{path[1]}-validator"
197
+ if osc_compat
198
+ result << "#{path[1]}-webui"
199
+ end
200
+ elsif path[2] == 'users'
201
+ if osc_compat
202
+ result << 'admin'
203
+ end
204
+ end
205
+ end
206
+
207
+ result
208
+ end
209
+
210
+ def get_org_default(path)
211
+ if path[2] == 'acls'
212
+ get_org_acl_default(path)
213
+
214
+ elsif path.size >= 4
215
+ if path[2] == 'containers' && path.size == 4
216
+ if exists?(path)
217
+ return {}
218
+ else
219
+ return nil
220
+ end
221
+ end
222
+
223
+ # /organizations/(*)/clients/\1-validator
224
+ # /organizations/*/environments/_default
225
+ # /organizations/*/groups/{admins,billing-admins,clients,users}
226
+ case path[2..-1].join('/')
227
+ when "clients/#{path[1]}-validator"
228
+ { 'validator' => 'true' }
229
+
230
+ when "clients/#{path[1]}-webui", "users/admin"
231
+ if osc_compat
232
+ { 'admin' => 'true' }
233
+ end
234
+
235
+ when "environments/_default"
236
+ { "description" => "The default Chef environment" }
237
+
238
+ when "groups/admins"
239
+ admins = data.list(path[0..1] + [ 'users' ]).select do |name|
240
+ user = JSON.parse(data.get(path[0..1] + [ 'users', name ]), :create_additions => false)
241
+ user['admin']
242
+ end
243
+ admins += data.list(path[0..1] + [ 'clients' ]).select do |name|
244
+ client = JSON.parse(data.get(path[0..1] + [ 'clients', name ]), :create_additions => false)
245
+ client['admin']
246
+ end
247
+ admins += @creators[path[0..1]] if @creators[path[0..1]]
248
+ { 'actors' => admins.uniq }
249
+
250
+ when "groups/billing-admins"
251
+ {}
252
+
253
+ when "groups/clients"
254
+ { 'clients' => data.list(path[0..1] + [ 'clients' ]) }
255
+
256
+ when "groups/users"
257
+ users = data.list(path[0..1] + [ 'users' ])
258
+ users |= @creators[path[0..1]] if @creators[path[0..1]]
259
+ { 'users' => users }
260
+
261
+ when "org"
262
+ {}
263
+
264
+ end
265
+ end
266
+ end
267
+
268
+ def get_org_acl_default(path)
269
+ object_path = AclPath.get_object_path(path)
270
+ # The actual things containers correspond to don't have to exist, as long as the container does
271
+ return nil if object_path[2] != 'containers' && !data_exists?(object_path)
272
+ basic_acl =
273
+ case path[3..-1].join('/')
274
+ when 'root', 'containers/containers', 'containers/groups'
275
+ {
276
+ 'create' => { 'groups' => %w(admins) },
277
+ 'read' => { 'groups' => %w(admins users) },
278
+ 'update' => { 'groups' => %w(admins) },
279
+ 'delete' => { 'groups' => %w(admins) },
280
+ 'grant' => { 'groups' => %w(admins) },
281
+ }
282
+ when 'containers/cookbooks', 'containers/environments', 'containers/roles'
283
+ {
284
+ 'create' => { 'groups' => %w(admins users) },
285
+ 'read' => { 'groups' => %w(admins users clients) },
286
+ 'update' => { 'groups' => %w(admins users) },
287
+ 'delete' => { 'groups' => %w(admins users) },
288
+ 'grant' => { 'groups' => %w(admins) },
289
+ }
290
+ when 'containers/cookbooks', 'containers/data'
291
+ {
292
+ 'create' => { 'groups' => %w(admins users clients) },
293
+ 'read' => { 'groups' => %w(admins users clients) },
294
+ 'update' => { 'groups' => %w(admins users clients) },
295
+ 'delete' => { 'groups' => %w(admins users clients) },
296
+ 'grant' => { 'groups' => %w(admins) },
297
+ }
298
+ when 'containers/nodes'
299
+ {
300
+ 'create' => { 'groups' => %w(admins users clients) },
301
+ 'read' => { 'groups' => %w(admins users clients) },
302
+ 'update' => { 'groups' => %w(admins users) },
303
+ 'delete' => { 'groups' => %w(admins users) },
304
+ 'grant' => { 'groups' => %w(admins) },
305
+ }
306
+ when 'containers/clients'
307
+ {
308
+ 'create' => { 'groups' => %w(admins) },
309
+ 'read' => { 'groups' => %w(admins users) },
310
+ 'update' => { 'groups' => %w(admins) },
311
+ 'delete' => { 'groups' => %w(admins users) },
312
+ 'grant' => { 'groups' => %w(admins) },
313
+ }
314
+ when 'containers/sandboxes'
315
+ {
316
+ 'create' => { 'groups' => %w(admins users) },
317
+ 'read' => { 'groups' => %w(admins) },
318
+ 'update' => { 'groups' => %w(admins) },
319
+ 'delete' => { 'groups' => %w(admins) },
320
+ 'grant' => { 'groups' => %w(admins) },
321
+ }
322
+ when 'groups/admins', 'groups/clients', 'groups/users'
323
+ {
324
+ 'create' => { 'groups' => %w(admins) },
325
+ 'read' => { 'groups' => %w(admins) },
326
+ 'update' => { 'groups' => %w(admins) },
327
+ 'delete' => { 'groups' => %w(admins) },
328
+ 'grant' => { 'groups' => %w(admins) },
329
+ }
330
+ when 'groups/billing-admins'
331
+ {
332
+ 'create' => { 'groups' => %w() },
333
+ 'read' => { 'groups' => %w(billing-admins) },
334
+ 'update' => { 'groups' => %w(billing-admins) },
335
+ 'delete' => { 'groups' => %w() },
336
+ 'grant' => { 'groups' => %w() },
337
+ }
338
+ else
339
+ {}
340
+ end
341
+
342
+ default_acl(path, basic_acl)
343
+ end
344
+
345
+ def get_owners(acl_path)
346
+ owners = []
347
+
348
+ path = AclPath.get_object_path(acl_path)
349
+ if path
350
+
351
+ # Non-validator clients own themselves.
352
+ if path.size == 4 && path[0] == 'organizations' && path[2] == 'clients'
353
+ begin
354
+ client = JSON.parse(data.get(path), :create_additions => false)
355
+ if !client['validator']
356
+ owners |= [ path[3] ]
357
+ end
358
+ rescue
359
+ owners |= [ path[3] ]
360
+ end
361
+
362
+ # Add creators as owners (except any validator clients).
363
+ if @creators[path]
364
+ @creators[path].each do |creator|
365
+ begin
366
+ client = JSON.parse(data.get(path[0..2] + [ creator ]), :create_additions => false)
367
+ next if client['validator']
368
+ rescue
369
+ end
370
+ owners |= [ creator ]
371
+ end
372
+ end
373
+ else
374
+ owners |= @creators[path] if @creators[path]
375
+ end
376
+
377
+ #ANGRY
378
+ # Non-default containers do not get superusers added to them,
379
+ # because reasons.
380
+ unless path.size == 4 && path[0] == 'organizations' && path[2] == 'containers' && !exists?(path)
381
+ owners |= superusers
382
+ end
383
+ end
384
+
385
+ owners.uniq
386
+ end
387
+
388
+ def default_acl(acl_path, acl={})
389
+ owners = nil
390
+ container_acl = nil
391
+ PERMISSIONS.each do |perm|
392
+ acl[perm] ||= {}
393
+ acl[perm]['actors'] ||= begin
394
+ owners ||= get_owners(acl_path)
395
+ end
396
+ acl[perm]['groups'] ||= begin
397
+ # When we create containers, we don't merge groups (not sure why).
398
+ if acl_path[0] == 'organizations' && acl_path[3] == 'containers'
399
+ []
400
+ else
401
+ container_acl ||= get_container_acl(acl_path) || {}
402
+ (container_acl[perm] ? container_acl[perm]['groups'] : []) || []
403
+ end
404
+ end
405
+ end
406
+ acl
407
+ end
408
+
409
+ def get_container_acl(acl_path)
410
+ parent_path = AclPath.parent_acl_data_path(acl_path)
411
+ if parent_path
412
+ JSON.parse(data.get(parent_path), :create_additions => false)
413
+ else
414
+ nil
415
+ end
416
+ end
417
+
418
+ def data_exists?(path)
419
+ if is_dir?(path)
420
+ data.exists_dir?(path)
421
+ else
422
+ data.exists?(path)
423
+ end
424
+ end
425
+
426
+ def is_dir?(path)
427
+ case path.size
428
+ when 0, 1
429
+ return true
430
+ when 2
431
+ return path[0] == 'organizations' || (path[0] == 'acls' && path[1] != 'root')
432
+ when 3
433
+ # If it has a container, it is a directory.
434
+ return path[0] == 'organizations' &&
435
+ (path[2] == 'acls' || data.exists?(path[0..1] + [ 'containers', path[2] ]))
436
+ when 4
437
+ return path[0] == 'organizations' && (
438
+ (path[2] == 'acls' && path[1] != 'root') ||
439
+ %w(cookbooks data).include?(path[2]))
440
+ else
441
+ return false
442
+ end
443
+ end
444
+ end
445
+ end
446
+ end