chef-zero 4.3.2 → 4.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,24 @@
1
+ require 'chef_zero/chef_data/data_normalizer'
2
+
3
+ module ChefZero
4
+ module Endpoints
5
+ # /organizations/ORG/policies/NAME
6
+ class PolicyEndpoint < RestBase
7
+ # GET /organizations/ORG/policies/NAME
8
+ def get(request)
9
+ revisions = list_data(request, request.rest_path + ["revisions"])
10
+ data = { revisions: hashify_list(revisions) }
11
+ return json_response(200, data)
12
+ end
13
+
14
+ # DELETE /organizations/ORG/policies/NAME
15
+ def delete(request)
16
+ revisions = list_data(request, request.rest_path + ["revisions"])
17
+ data = { revisions: hashify_list(revisions) }
18
+
19
+ delete_data_dir(request, nil, :recursive)
20
+ return json_response(200, data)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,46 @@
1
+ require 'ffi_yajl'
2
+ require 'chef_zero/rest_base'
3
+ require 'chef_zero/chef_data/data_normalizer'
4
+
5
+ module ChefZero
6
+ module Endpoints
7
+ # /organizations/ORG/policy_groups/NAME
8
+ class PolicyGroupEndpoint < RestBase
9
+
10
+ # GET /organizations/ORG/policy_groups/NAME
11
+ def get(request)
12
+ data = {
13
+ uri: build_uri(request.base_uri, request.rest_path),
14
+ policies: get_policy_group_policies(request)
15
+ }
16
+ json_response(200, data)
17
+ end
18
+
19
+ # build a hash of {"some_policy_name"=>{"revision_id"=>"909c26701e291510eacdc6c06d626b9fa5350d25"}}
20
+ def get_policy_group_policies(request)
21
+ policies_revisions = {}
22
+
23
+ policies_path = request.rest_path + ["policies"]
24
+ policy_names = list_data(request, policies_path)
25
+ policy_names.each do |policy_name|
26
+ revision = parse_json(get_data(request, policies_path + [policy_name]))
27
+ policies_revisions[policy_name] = { revision_id: revision}
28
+ end
29
+
30
+ policies_revisions
31
+ end
32
+
33
+ # DELETE /organizations/ORG/policy_groups/NAME
34
+ def delete(request)
35
+ policy_group_policies = get_policy_group_policies(request)
36
+ delete_data_dir(request, nil, :recursive)
37
+
38
+ data = {
39
+ uri: build_uri(request.base_uri, request.rest_path),
40
+ policies: policy_group_policies
41
+ }
42
+ json_response(200, data)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,84 @@
1
+ require 'ffi_yajl'
2
+ require 'chef_zero/rest_base'
3
+ require 'chef_zero/chef_data/data_normalizer'
4
+
5
+ module ChefZero
6
+ module Endpoints
7
+ # /organizations/ORG/policy_groups/GROUP/policies/NAME
8
+ #
9
+ # in the data store, this REST path actually stores the revision ID of ${policy_name} that's currently
10
+ # associated with ${policy_group}.
11
+ class PolicyGroupPolicyEndpoint < RestBase
12
+
13
+ # GET /organizations/ORG/policy_groups/GROUP/policies/NAME
14
+ def get(request)
15
+ policy_name = request.rest_path[5]
16
+
17
+ # fetch /organizations/{organization}/policies/{policy_name}/revisions/{revision_id}
18
+ revision_id = parse_json(get_data(request))
19
+ result = get_data(request, request.rest_path[0..1] +
20
+ ["policies", policy_name, "revisions", revision_id])
21
+ result = ChefData::DataNormalizer.normalize_policy(parse_json(result), policy_name, revision_id)
22
+ json_response(200, result)
23
+ end
24
+
25
+ # Create or update the policy document for the given policy group and policy name. If no policy group
26
+ # with the given name exists, it will be created. If no policy with the given revision_id exists, it
27
+ # will be created from the document in the request body. If a policy with that revision_id exists, the
28
+ # Chef Server simply associates that revision id with the given policy group. When successful, the
29
+ # document that was created or updated is returned.
30
+
31
+ ## MANDATORY FIELDS AND FORMATS
32
+ # * `revision_id`: String; Must be < 255 chars, matches /^[\-[:alnum:]_\.\:]+$/
33
+ # * `name`: String; Must match name in URI; Must be <= 255 chars, matches /^[\-[:alnum:]_\.\:]+$/
34
+ # * `run_list`: Array
35
+ # * `run_list[i]`: Fully Qualified Recipe Run List Item
36
+ # * `cookbook_locks`: JSON Object
37
+ # * `cookbook_locks(key)`: CookbookName
38
+ # * `cookbook_locks[item]`: JSON Object, mandatory keys: "identifier", "dotted_decimal_identifier"
39
+ # * `cookbook_locks[item]["identifier"]`: varchar(255) ?
40
+ # * `cookbook_locks[item]["dotted_decimal_identifier"]` ChefCompatibleVersionNumber
41
+
42
+ # PUT /organizations/ORG/policy_groups/GROUP/policies/NAME
43
+ def put(request)
44
+ policyfile_data = parse_json(request.body)
45
+ policy_name = request.rest_path[5]
46
+ revision_id = policyfile_data["revision_id"]
47
+
48
+ # If the policy revision being submitted does not exist, create it.
49
+ # Storage: /organizations/ORG/policies/POLICY/revisions/REVISION
50
+ policyfile_path = request.rest_path[0..1] + ["policies", policy_name, "revisions", revision_id]
51
+ if !exists_data?(request, policyfile_path)
52
+ create_data(request, policyfile_path[0..-2], revision_id, request.body, :create_dir)
53
+ end
54
+
55
+ # if named policy exists and the given revision ID exists, associate the revision ID with the policy
56
+ # group.
57
+ # Storage: /organizations/ORG/policies/POLICY/revisions/REVISION
58
+ response_code = exists_data?(request) ? 200 : 201
59
+ set_data(request, nil, to_json(revision_id), :create_dir)
60
+
61
+ already_json_response(response_code, request.body)
62
+ end
63
+
64
+ # DELETE /organizations/ORG/policy_groups/GROUP/policies/NAME
65
+ def delete(request)
66
+ # Save the existing association.
67
+ current_revision_id = parse_json(get_data(request))
68
+
69
+ # delete the association.
70
+ delete_data(request)
71
+
72
+ # return the full policy document at the no-longer-associated revision.
73
+ policy_name = request.rest_path[5]
74
+ policy_path = request.rest_path[0..1] + ["policies", policy_name,
75
+ "revisions", current_revision_id]
76
+
77
+
78
+ full_policy_doc = parse_json(get_data(request, policy_path))
79
+ full_policy_doc = ChefData::DataNormalizer.normalize_policy(full_policy_doc, policy_name, current_revision_id)
80
+ return json_response(200, full_policy_doc)
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,38 @@
1
+ require 'ffi_yajl'
2
+ require 'chef_zero/rest_base'
3
+ require 'chef_zero/chef_data/data_normalizer'
4
+
5
+ module ChefZero
6
+ module Endpoints
7
+ # /organizations/ORG/policy_groups
8
+ #
9
+ # in the data store, this REST path actually stores the revision ID of ${policy_name} that's currently
10
+ # associated with ${policy_group}.
11
+ class PolicyGroupsEndpoint < RestBase
12
+ # GET /organizations/ORG/policy_groups
13
+ def get(request)
14
+ # each policy group has policies and associated revisions under
15
+ # /policy_groups/{group name}/policies/{policy name}.
16
+ response_data = {}
17
+ list_data(request).each do |group_name|
18
+ group_path = request.rest_path + [group_name]
19
+ policy_list = list_data(request, group_path + ["policies"])
20
+
21
+ # build the list of policies with their revision ID associated with this policy group.
22
+ policies = {}
23
+ policy_list.each do |policy_name|
24
+ revision_id = parse_json(get_data(request, group_path + ["policies", policy_name]))
25
+ policies[policy_name] = { revision_id: revision_id }
26
+ end
27
+
28
+ response_data[group_name] = {
29
+ uri: build_uri(request.base_uri, group_path)
30
+ }
31
+ response_data[group_name][:policies] = policies unless policies.empty?
32
+ end
33
+
34
+ json_response(200, response_data)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,23 @@
1
+ require 'chef_zero/chef_data/data_normalizer'
2
+
3
+ module ChefZero
4
+ module Endpoints
5
+ # /organizations/ORG/policies/NAME/revisions/REVISION
6
+ class PolicyRevisionEndpoint < RestBase
7
+ # GET /organizations/ORG/policies/NAME/revisions/REVISION
8
+ def get(request)
9
+ data = parse_json(get_data(request))
10
+ data = ChefData::DataNormalizer.normalize_policy(data, request.rest_path[3], request.rest_path[5])
11
+ return json_response(200, data)
12
+ end
13
+
14
+ # DELETE /organizations/ORG/policies/NAME/revisions/REVISION
15
+ def delete(request)
16
+ policyfile_data = parse_json(get_data(request))
17
+ policyfile_data = ChefData::DataNormalizer.normalize_policy(policyfile_data, request.rest_path[3], request.rest_path[5])
18
+ delete_data(request)
19
+ return json_response(200, policyfile_data)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ require 'chef_zero/chef_data/data_normalizer'
2
+
3
+ module ChefZero
4
+ module Endpoints
5
+ # /organizations/ORG/policies/NAME/revisions
6
+ class PolicyRevisionsEndpoint < RestBase
7
+ # POST /organizations/ORG/policies/NAME/revisions
8
+ def post(request)
9
+ policyfile_data = parse_json(request.body)
10
+ create_data(request, request.rest_path, policyfile_data["revision_id"], request.body, :create_dir)
11
+ return already_json_response(201, request.body)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -8,16 +8,20 @@ module ChefZero
8
8
  class PrincipalEndpoint < RestBase
9
9
  def get(request)
10
10
  name = request.rest_path[-1]
11
+ # If /organizations/ORG/users/NAME exists, use this user (only org members have precedence over clients). hey are an org member.
11
12
  json = get_data(request, request.rest_path[0..1] + [ 'users', name ], :nil)
12
13
  if json
13
14
  type = 'user'
14
15
  org_member = true
15
16
  else
17
+ # If /organizations/ORG/clients/NAME exists, use the client.
16
18
  json = get_data(request, request.rest_path[0..1] + [ 'clients', name ], :nil)
17
19
  if json
18
20
  type = 'client'
19
21
  org_member = true
20
22
  else
23
+ # If there is no client with that name, check for a user (/users/NAME) and return that with
24
+ # org_member = false.
21
25
  json = get_data(request, [ 'users', name ], :nil)
22
26
  if json
23
27
  type = 'user'
@@ -195,12 +195,13 @@ module ChefZero
195
195
  data_store.exists_dir?(rest_path)
196
196
  end
197
197
 
198
- def error(response_code, error)
199
- json_response(response_code, {"error" => [error]})
198
+ def error(response_code, error, opts={})
199
+ json_response(response_code, {"error" => [error]}, 0, 0, opts)
200
200
  end
201
201
 
202
- def json_response(response_code, json, request_version=0, response_version=0)
203
- already_json_response(response_code, FFI_Yajl::Encoder.encode(json, :pretty => true), request_version, response_version)
202
+ def json_response(response_code, json, request_version=0, response_version=0, opts={pretty: true})
203
+ do_pretty_json = opts[:pretty] && true
204
+ already_json_response(response_code, FFI_Yajl::Encoder.encode(json, :pretty => do_pretty_json), request_version, response_version)
204
205
  end
205
206
 
206
207
  def text_response(response_code, text)
@@ -238,5 +239,39 @@ module ChefZero
238
239
  def populate_defaults(request, response)
239
240
  response
240
241
  end
242
+
243
+ def parse_json(json)
244
+ FFI_Yajl::Parser.parse(json, create_additions: false)
245
+ end
246
+
247
+ def to_json(data)
248
+ FFI_Yajl::Encoder.encode(data, :pretty => true)
249
+ end
250
+
251
+ def get_data_or_else(request, path, or_else_value)
252
+ if exists_data?(request, path)
253
+ parse_json(get_data(request, path))
254
+ else
255
+ or_else_value
256
+ end
257
+ end
258
+
259
+ def list_data_or_else(request, path, or_else_value)
260
+ if exists_data_dir?(request, path)
261
+ list_data(request, path)
262
+ else
263
+ or_else_value
264
+ end
265
+ end
266
+
267
+ def hashify_list(list)
268
+ list.reduce({}) { |acc, obj| acc.merge( obj => {} ) }
269
+ end
270
+
271
+ def policy_name_invalid?(name)
272
+ !name.is_a?(String) ||
273
+ name.size > 255 ||
274
+ name =~ /[+ !]/
275
+ end
241
276
  end
242
277
  end
@@ -125,6 +125,10 @@ module ChefZero
125
125
  end
126
126
  end
127
127
 
128
+ def cookbook_artifact(name, data, &block)
129
+ before(chef_server_options[:server_scope]) { cookbook_artifact(name, data, &block) }
130
+ end
131
+
128
132
  def data_bag(name, data, &block)
129
133
  before(chef_server_options[:server_scope]) { data_bag(name, data, &block) }
130
134
  end
@@ -149,6 +153,14 @@ module ChefZero
149
153
  before(chef_server_options[:server_scope]) { org_member(*usernames) }
150
154
  end
151
155
 
156
+ def policy(name, data, &block)
157
+ before(chef_server_options[:server_scope]) { policy(name, data, &block) }
158
+ end
159
+
160
+ def policy_group(name, data, &block)
161
+ before(chef_server_options[:server_scope]) { policy_group(name, data, &block) }
162
+ end
163
+
152
164
  def role(name, data, &block)
153
165
  before(chef_server_options[:server_scope]) { role(name, data, &block) }
154
166
  end
@@ -215,6 +227,13 @@ module ChefZero
215
227
  end
216
228
  end
217
229
 
230
+ def cookbook_artifact(name, identifier, data, &block)
231
+ with_object_path("cookbook_artifacts/#{name}") do
232
+ ChefZero::RSpec.server.load_data({ 'cookbook_artifact' => { name => { identifier => data } } }, current_org)
233
+ instance_eval(&block) if block_given?
234
+ end
235
+ end
236
+
218
237
  def data_bag(name, data, &block)
219
238
  with_object_path("data/#{name}") do
220
239
  ChefZero::RSpec.server.load_data({ 'data' => { name => data }}, current_org)
@@ -251,6 +270,20 @@ module ChefZero
251
270
  ChefZero::RSpec.server.load_data({ 'members' => usernames }, current_org)
252
271
  end
253
272
 
273
+ def policy(name, version, data, &block)
274
+ with_object_path("policies/#{name}") do
275
+ ChefZero::RSpec.server.load_data({ 'policies' => { name => { version => data } } }, current_org)
276
+ instance_eval(&block) if block_given?
277
+ end
278
+ end
279
+
280
+ def policy_group(name, data, &block)
281
+ with_object_path("policy_groups/#{name}") do
282
+ ChefZero::RSpec.server.load_data({ 'policy_groups' => { name => data } }, current_org)
283
+ instance_eval(&block) if block_given?
284
+ end
285
+ end
286
+
254
287
  def role(name, data, &block)
255
288
  with_object_path("roles/#{name}") do
256
289
  ChefZero::RSpec.server.load_data({ 'roles' => { name => data } }, current_org)
@@ -45,8 +45,12 @@ require 'chef_zero/endpoints/actor_endpoint'
45
45
  require 'chef_zero/endpoints/cookbooks_endpoint'
46
46
  require 'chef_zero/endpoints/cookbook_endpoint'
47
47
  require 'chef_zero/endpoints/cookbook_version_endpoint'
48
+ require 'chef_zero/endpoints/cookbook_artifacts_cookbook_endpoint'
49
+ require 'chef_zero/endpoints/cookbook_artifacts_cookbook_identifier'
50
+ require 'chef_zero/endpoints/cookbook_artifacts_endpoint'
48
51
  require 'chef_zero/endpoints/containers_endpoint'
49
52
  require 'chef_zero/endpoints/container_endpoint'
53
+ require 'chef_zero/endpoints/dummy_endpoint'
50
54
  require 'chef_zero/endpoints/data_bags_endpoint'
51
55
  require 'chef_zero/endpoints/data_bag_endpoint'
52
56
  require 'chef_zero/endpoints/data_bag_item_endpoint'
@@ -61,6 +65,7 @@ require 'chef_zero/endpoints/environment_recipes_endpoint'
61
65
  require 'chef_zero/endpoints/environment_role_endpoint'
62
66
  require 'chef_zero/endpoints/license_endpoint'
63
67
  require 'chef_zero/endpoints/node_endpoint'
68
+ require 'chef_zero/endpoints/nodes_endpoint'
64
69
  require 'chef_zero/endpoints/node_identifiers_endpoint'
65
70
  require 'chef_zero/endpoints/organizations_endpoint'
66
71
  require 'chef_zero/endpoints/organization_endpoint'
@@ -70,8 +75,14 @@ require 'chef_zero/endpoints/organization_authenticate_user_endpoint'
70
75
  require 'chef_zero/endpoints/organization_users_endpoint'
71
76
  require 'chef_zero/endpoints/organization_user_endpoint'
72
77
  require 'chef_zero/endpoints/organization_validator_key_endpoint'
73
- require 'chef_zero/endpoints/principal_endpoint'
74
78
  require 'chef_zero/endpoints/policies_endpoint'
79
+ require 'chef_zero/endpoints/policy_endpoint'
80
+ require 'chef_zero/endpoints/policy_revisions_endpoint'
81
+ require 'chef_zero/endpoints/policy_revision_endpoint'
82
+ require 'chef_zero/endpoints/policy_groups_endpoint'
83
+ require 'chef_zero/endpoints/policy_group_endpoint'
84
+ require 'chef_zero/endpoints/policy_group_policy_endpoint'
85
+ require 'chef_zero/endpoints/principal_endpoint'
75
86
  require 'chef_zero/endpoints/role_endpoint'
76
87
  require 'chef_zero/endpoints/role_environments_endpoint'
77
88
  require 'chef_zero/endpoints/sandboxes_endpoint'
@@ -89,6 +100,7 @@ require 'chef_zero/endpoints/version_endpoint'
89
100
  require 'chef_zero/endpoints/server_api_version_endpoint'
90
101
 
91
102
  module ChefZero
103
+
92
104
  class Server
93
105
 
94
106
  DEFAULT_OPTIONS = {
@@ -443,6 +455,24 @@ module ChefZero
443
455
  end
444
456
  end
445
457
 
458
+ if contents['policies']
459
+ contents['policies'].each_pair do |policy_name, policy_struct|
460
+ # data_store.create_dir(['organizations', org_name, 'policies', policy_name], "revisions", :recursive)
461
+ dejsonize_children(policy_struct).each do |revision, policy_data|
462
+ data_store.set(['organizations', org_name, 'policies', policy_name,
463
+ "revisions", revision], policy_data, :create, :create_dir)
464
+ end
465
+ end
466
+ end
467
+
468
+ if contents['policy_groups']
469
+ contents['policy_groups'].each_pair do |group_name, group|
470
+ group['policies'].each do |policy_name, policy_revision|
471
+ data_store.set(['organizations', org_name, 'policy_groups', group_name, 'policies', policy_name], FFI_Yajl::Encoder.encode(policy_revision['revision_id'], :pretty => true), :create, :create_dir)
472
+ end
473
+ end
474
+ end
475
+
446
476
  if contents['cookbooks']
447
477
  contents['cookbooks'].each_pair do |name_version, cookbook|
448
478
  if name_version =~ /(.+)-(\d+\.\d+\.\d+)$/
@@ -481,7 +511,7 @@ module ChefZero
481
511
 
482
512
  private
483
513
 
484
- def open_source_endpoints
514
+ def endpoints
485
515
  result = if options[:osc_compat]
486
516
  # OSC-only
487
517
  [
@@ -525,11 +555,15 @@ module ChefZero
525
555
  end
526
556
  result + [
527
557
  # Both
558
+ [ "/dummy", DummyEndpoint.new(self) ],
528
559
  [ "/organizations/*/clients", ActorsEndpoint.new(self) ],
529
560
  [ "/organizations/*/clients/*", ActorEndpoint.new(self) ],
530
561
  [ "/organizations/*/cookbooks", CookbooksEndpoint.new(self) ],
531
562
  [ "/organizations/*/cookbooks/*", CookbookEndpoint.new(self) ],
532
563
  [ "/organizations/*/cookbooks/*/*", CookbookVersionEndpoint.new(self) ],
564
+ [ "/organizations/*/cookbook_artifacts", CookbookArtifactsEndpoint.new(self) ],
565
+ [ "/organizations/*/cookbook_artifacts/*", CookbookArtifactsCookbookEndpoint.new(self) ],
566
+ [ "/organizations/*/cookbook_artifacts/*/*", CookbookArtifactsCookbookIdentifierEndpoint.new(self) ],
533
567
  [ "/organizations/*/data", DataBagsEndpoint.new(self) ],
534
568
  [ "/organizations/*/data/*", DataBagEndpoint.new(self) ],
535
569
  [ "/organizations/*/data/*/*", DataBagItemEndpoint.new(self) ],
@@ -541,10 +575,16 @@ module ChefZero
541
575
  [ "/organizations/*/environments/*/nodes", EnvironmentNodesEndpoint.new(self) ],
542
576
  [ "/organizations/*/environments/*/recipes", EnvironmentRecipesEndpoint.new(self) ],
543
577
  [ "/organizations/*/environments/*/roles/*", EnvironmentRoleEndpoint.new(self) ],
544
- [ "/organizations/*/nodes", RestListEndpoint.new(self) ],
578
+ [ "/organizations/*/nodes", NodesEndpoint.new(self) ],
545
579
  [ "/organizations/*/nodes/*", NodeEndpoint.new(self) ],
546
580
  [ "/organizations/*/nodes/*/_identifiers", NodeIdentifiersEndpoint.new(self) ],
547
- [ "/organizations/*/policies/*/*", PoliciesEndpoint.new(self) ],
581
+ [ "/organizations/*/policies", PoliciesEndpoint.new(self) ],
582
+ [ "/organizations/*/policies/*", PolicyEndpoint.new(self) ],
583
+ [ "/organizations/*/policies/*/revisions", PolicyRevisionsEndpoint.new(self) ],
584
+ [ "/organizations/*/policies/*/revisions/*", PolicyRevisionEndpoint.new(self) ],
585
+ [ "/organizations/*/policy_groups", PolicyGroupsEndpoint.new(self) ],
586
+ [ "/organizations/*/policy_groups/*", PolicyGroupEndpoint.new(self) ],
587
+ [ "/organizations/*/policy_groups/*/policies/*", PolicyGroupPolicyEndpoint.new(self) ],
548
588
  [ "/organizations/*/principals/*", PrincipalEndpoint.new(self) ],
549
589
  [ "/organizations/*/roles", RestListEndpoint.new(self) ],
550
590
  [ "/organizations/*/roles/*", RoleEndpoint.new(self) ],
@@ -570,7 +610,7 @@ module ChefZero
570
610
 
571
611
  def app
572
612
  return @app if @app
573
- router = RestRouter.new(open_source_endpoints)
613
+ router = RestRouter.new(endpoints)
574
614
  router.not_found = NotFoundEndpoint.new
575
615
 
576
616
  if options[:single_org]