chef-zero 4.2.3 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +201 -201
  3. data/README.md +155 -150
  4. data/Rakefile +31 -31
  5. data/bin/chef-zero +100 -100
  6. data/lib/chef_zero.rb +10 -7
  7. data/lib/chef_zero/chef_data/acl_path.rb +139 -139
  8. data/lib/chef_zero/chef_data/cookbook_data.rb +240 -240
  9. data/lib/chef_zero/chef_data/data_normalizer.rb +207 -207
  10. data/lib/chef_zero/chef_data/default_creator.rb +446 -446
  11. data/lib/chef_zero/data_store/data_already_exists_error.rb +29 -29
  12. data/lib/chef_zero/data_store/data_error.rb +31 -31
  13. data/lib/chef_zero/data_store/data_not_found_error.rb +28 -28
  14. data/lib/chef_zero/data_store/default_facade.rb +149 -149
  15. data/lib/chef_zero/data_store/interface_v1.rb +67 -67
  16. data/lib/chef_zero/data_store/interface_v2.rb +18 -18
  17. data/lib/chef_zero/data_store/memory_store.rb +33 -33
  18. data/lib/chef_zero/data_store/memory_store_v2.rb +155 -155
  19. data/lib/chef_zero/data_store/raw_file_store.rb +147 -147
  20. data/lib/chef_zero/data_store/v1_to_v2_adapter.rb +142 -142
  21. data/lib/chef_zero/data_store/v2_to_v1_adapter.rb +107 -107
  22. data/lib/chef_zero/endpoints/acl_endpoint.rb +38 -38
  23. data/lib/chef_zero/endpoints/acls_endpoint.rb +29 -29
  24. data/lib/chef_zero/endpoints/actor_endpoint.rb +94 -88
  25. data/lib/chef_zero/endpoints/actors_endpoint.rb +64 -64
  26. data/lib/chef_zero/endpoints/authenticate_user_endpoint.rb +31 -31
  27. data/lib/chef_zero/endpoints/container_endpoint.rb +22 -22
  28. data/lib/chef_zero/endpoints/containers_endpoint.rb +13 -13
  29. data/lib/chef_zero/endpoints/cookbook_endpoint.rb +39 -39
  30. data/lib/chef_zero/endpoints/cookbook_version_endpoint.rb +119 -119
  31. data/lib/chef_zero/endpoints/cookbooks_base.rb +65 -65
  32. data/lib/chef_zero/endpoints/cookbooks_endpoint.rb +19 -19
  33. data/lib/chef_zero/endpoints/data_bag_endpoint.rb +45 -45
  34. data/lib/chef_zero/endpoints/data_bag_item_endpoint.rb +25 -25
  35. data/lib/chef_zero/endpoints/data_bags_endpoint.rb +23 -23
  36. data/lib/chef_zero/endpoints/environment_cookbook_endpoint.rb +24 -24
  37. data/lib/chef_zero/endpoints/environment_cookbook_versions_endpoint.rb +123 -123
  38. data/lib/chef_zero/endpoints/environment_cookbooks_endpoint.rb +22 -22
  39. data/lib/chef_zero/endpoints/environment_endpoint.rb +33 -33
  40. data/lib/chef_zero/endpoints/environment_nodes_endpoint.rb +23 -23
  41. data/lib/chef_zero/endpoints/environment_recipes_endpoint.rb +22 -22
  42. data/lib/chef_zero/endpoints/environment_role_endpoint.rb +36 -36
  43. data/lib/chef_zero/endpoints/file_store_file_endpoint.rb +22 -22
  44. data/lib/chef_zero/endpoints/group_endpoint.rb +20 -20
  45. data/lib/chef_zero/endpoints/groups_endpoint.rb +13 -13
  46. data/lib/chef_zero/endpoints/license_endpoint.rb +25 -25
  47. data/lib/chef_zero/endpoints/node_endpoint.rb +17 -17
  48. data/lib/chef_zero/endpoints/node_identifiers_endpoint.rb +22 -0
  49. data/lib/chef_zero/endpoints/not_found_endpoint.rb +11 -11
  50. data/lib/chef_zero/endpoints/organization_association_request_endpoint.rb +22 -22
  51. data/lib/chef_zero/endpoints/organization_association_requests_endpoint.rb +30 -29
  52. data/lib/chef_zero/endpoints/organization_authenticate_user_endpoint.rb +26 -26
  53. data/lib/chef_zero/endpoints/organization_endpoint.rb +46 -41
  54. data/lib/chef_zero/endpoints/organization_user_base.rb +15 -0
  55. data/lib/chef_zero/endpoints/organization_user_endpoint.rb +26 -48
  56. data/lib/chef_zero/endpoints/organization_users_endpoint.rb +43 -14
  57. data/lib/chef_zero/endpoints/organization_validator_key_endpoint.rb +20 -20
  58. data/lib/chef_zero/endpoints/organizations_endpoint.rb +62 -55
  59. data/lib/chef_zero/endpoints/policies_endpoint.rb +151 -154
  60. data/lib/chef_zero/endpoints/principal_endpoint.rb +42 -42
  61. data/lib/chef_zero/endpoints/rest_list_endpoint.rb +42 -42
  62. data/lib/chef_zero/endpoints/rest_object_endpoint.rb +63 -63
  63. data/lib/chef_zero/endpoints/role_endpoint.rb +16 -16
  64. data/lib/chef_zero/endpoints/role_environments_endpoint.rb +14 -14
  65. data/lib/chef_zero/endpoints/sandbox_endpoint.rb +27 -27
  66. data/lib/chef_zero/endpoints/sandboxes_endpoint.rb +50 -50
  67. data/lib/chef_zero/endpoints/search_endpoint.rb +194 -192
  68. data/lib/chef_zero/endpoints/searches_endpoint.rb +18 -18
  69. data/lib/chef_zero/endpoints/server_api_version_endpoint.rb +14 -0
  70. data/lib/chef_zero/endpoints/system_recovery_endpoint.rb +30 -30
  71. data/lib/chef_zero/endpoints/user_association_request_endpoint.rb +40 -40
  72. data/lib/chef_zero/endpoints/user_association_requests_count_endpoint.rb +19 -19
  73. data/lib/chef_zero/endpoints/user_association_requests_endpoint.rb +19 -19
  74. data/lib/chef_zero/endpoints/user_organizations_endpoint.rb +22 -22
  75. data/lib/chef_zero/endpoints/version_endpoint.rb +12 -12
  76. data/lib/chef_zero/log.rb +7 -7
  77. data/lib/chef_zero/rest_base.rb +242 -214
  78. data/lib/chef_zero/rest_error_response.rb +11 -11
  79. data/lib/chef_zero/rest_request.rb +69 -65
  80. data/lib/chef_zero/rest_router.rb +45 -45
  81. data/lib/chef_zero/rspec.rb +308 -308
  82. data/lib/chef_zero/server.rb +642 -637
  83. data/lib/chef_zero/socketless_server_map.rb +92 -92
  84. data/lib/chef_zero/solr/query/binary_operator.rb +52 -52
  85. data/lib/chef_zero/solr/query/phrase.rb +23 -23
  86. data/lib/chef_zero/solr/query/range_query.rb +46 -46
  87. data/lib/chef_zero/solr/query/regexpable_query.rb +29 -29
  88. data/lib/chef_zero/solr/query/subquery.rb +37 -37
  89. data/lib/chef_zero/solr/query/term.rb +45 -45
  90. data/lib/chef_zero/solr/query/unary_operator.rb +43 -43
  91. data/lib/chef_zero/solr/solr_doc.rb +53 -53
  92. data/lib/chef_zero/solr/solr_parser.rb +203 -203
  93. data/lib/chef_zero/version.rb +3 -3
  94. data/spec/run_oc_pedant.rb +63 -56
  95. data/spec/search_spec.rb +32 -32
  96. data/spec/server_spec.rb +92 -91
  97. data/spec/socketless_server_map_spec.rb +76 -76
  98. data/spec/support/oc_pedant.rb +132 -134
  99. data/spec/support/stickywicket.pem +27 -27
  100. metadata +10 -15
  101. data/spec/run_pedant.rb +0 -103
  102. data/spec/support/pedant.rb +0 -129
@@ -1,65 +1,65 @@
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
- # Common code for endpoints that return cookbook lists
8
- class CookbooksBase < RestBase
9
- def format_cookbooks_list(request, cookbooks_list, constraints = {}, num_versions = nil)
10
- results = {}
11
- filter_cookbooks(cookbooks_list, constraints, num_versions) do |name, versions|
12
- versions_list = versions.map do |version|
13
- {
14
- 'url' => build_uri(request.base_uri, request.rest_path[0..1] + ['cookbooks', name, version]),
15
- 'version' => version
16
- }
17
- end
18
- results[name] = {
19
- 'url' => build_uri(request.base_uri, request.rest_path[0..1] + ['cookbooks', name]),
20
- 'versions' => versions_list
21
- }
22
- end
23
- results
24
- end
25
-
26
- def all_cookbooks_list(request)
27
- result = {}
28
- # Race conditions exist here (if someone deletes while listing). I don't care.
29
- data_store.list(request.rest_path[0..1] + ['cookbooks']).each do |name|
30
- result[name] = data_store.list(request.rest_path[0..1] + ['cookbooks', name])
31
- end
32
- result
33
- end
34
-
35
- def filter_cookbooks(cookbooks_list, constraints = {}, num_versions = nil)
36
- cookbooks_list.keys.sort.each do |name|
37
- constraint = Gem::Requirement.new(constraints[name])
38
- versions = []
39
- cookbooks_list[name].sort_by { |version| Gem::Version.new(version.dup) }.reverse.each do |version|
40
- break if num_versions && versions.size >= num_versions
41
- if constraint.satisfied_by?(Gem::Version.new(version.dup))
42
- versions << version
43
- end
44
- end
45
- yield [name, versions]
46
- end
47
- end
48
-
49
- def recipe_names(cookbook_name, cookbook)
50
- result = []
51
- if cookbook['recipes']
52
- cookbook['recipes'].each do |recipe|
53
- if recipe['path'] == "recipes/#{recipe['name']}" && recipe['name'][-3..-1] == '.rb'
54
- if recipe['name'] == 'default.rb'
55
- result << cookbook_name
56
- end
57
- result << "#{cookbook_name}::#{recipe['name'][0..-4]}"
58
- end
59
- end
60
- end
61
- result
62
- end
63
- end
64
- end
65
- end
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
+ # Common code for endpoints that return cookbook lists
8
+ class CookbooksBase < RestBase
9
+ def format_cookbooks_list(request, cookbooks_list, constraints = {}, num_versions = nil)
10
+ results = {}
11
+ filter_cookbooks(cookbooks_list, constraints, num_versions) do |name, versions|
12
+ versions_list = versions.map do |version|
13
+ {
14
+ 'url' => build_uri(request.base_uri, request.rest_path[0..1] + ['cookbooks', name, version]),
15
+ 'version' => version
16
+ }
17
+ end
18
+ results[name] = {
19
+ 'url' => build_uri(request.base_uri, request.rest_path[0..1] + ['cookbooks', name]),
20
+ 'versions' => versions_list
21
+ }
22
+ end
23
+ results
24
+ end
25
+
26
+ def all_cookbooks_list(request)
27
+ result = {}
28
+ # Race conditions exist here (if someone deletes while listing). I don't care.
29
+ data_store.list(request.rest_path[0..1] + ['cookbooks']).each do |name|
30
+ result[name] = data_store.list(request.rest_path[0..1] + ['cookbooks', name])
31
+ end
32
+ result
33
+ end
34
+
35
+ def filter_cookbooks(cookbooks_list, constraints = {}, num_versions = nil)
36
+ cookbooks_list.keys.sort.each do |name|
37
+ constraint = Gem::Requirement.new(constraints[name])
38
+ versions = []
39
+ cookbooks_list[name].sort_by { |version| Gem::Version.new(version.dup) }.reverse.each do |version|
40
+ break if num_versions && versions.size >= num_versions
41
+ if constraint.satisfied_by?(Gem::Version.new(version.dup))
42
+ versions << version
43
+ end
44
+ end
45
+ yield [name, versions]
46
+ end
47
+ end
48
+
49
+ def recipe_names(cookbook_name, cookbook)
50
+ result = []
51
+ if cookbook['recipes']
52
+ cookbook['recipes'].each do |recipe|
53
+ if recipe['path'] == "recipes/#{recipe['name']}" && recipe['name'][-3..-1] == '.rb'
54
+ if recipe['name'] == 'default.rb'
55
+ result << cookbook_name
56
+ end
57
+ result << "#{cookbook_name}::#{recipe['name'][0..-4]}"
58
+ end
59
+ end
60
+ end
61
+ result
62
+ end
63
+ end
64
+ end
65
+ end
@@ -1,19 +1,19 @@
1
- require 'chef_zero/endpoints/cookbooks_base'
2
-
3
- module ChefZero
4
- module Endpoints
5
- # /cookbooks
6
- class CookbooksEndpoint < CookbooksBase
7
- def get(request)
8
- if request.query_params['num_versions'] == 'all'
9
- num_versions = nil
10
- elsif request.query_params['num_versions']
11
- num_versions = request.query_params['num_versions'].to_i
12
- else
13
- num_versions = 1
14
- end
15
- json_response(200, format_cookbooks_list(request, all_cookbooks_list(request), {}, num_versions))
16
- end
17
- end
18
- end
19
- end
1
+ require 'chef_zero/endpoints/cookbooks_base'
2
+
3
+ module ChefZero
4
+ module Endpoints
5
+ # /cookbooks
6
+ class CookbooksEndpoint < CookbooksBase
7
+ def get(request)
8
+ if request.query_params['num_versions'] == 'all'
9
+ num_versions = nil
10
+ elsif request.query_params['num_versions']
11
+ num_versions = request.query_params['num_versions'].to_i
12
+ else
13
+ num_versions = 1
14
+ end
15
+ json_response(200, format_cookbooks_list(request, all_cookbooks_list(request), {}, num_versions))
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,45 +1,45 @@
1
- require 'ffi_yajl'
2
- require 'chef_zero/endpoints/rest_list_endpoint'
3
- require 'chef_zero/endpoints/data_bag_item_endpoint'
4
- require 'chef_zero/rest_error_response'
5
-
6
- module ChefZero
7
- module Endpoints
8
- # /data/NAME
9
- class DataBagEndpoint < RestListEndpoint
10
- def initialize(server)
11
- super(server, 'id')
12
- end
13
-
14
- def post(request)
15
- json = FFI_Yajl::Parser.parse(request.body, :create_additions => false)
16
- key = identity_keys.map { |k| json[k] }.select { |v| v }.first
17
- response = super(request)
18
- if response[0] == 201
19
- already_json_response(201, DataBagItemEndpoint::populate_defaults(request, request.body, request.rest_path[3], key))
20
- else
21
- response
22
- end
23
- end
24
-
25
- def get_key(contents)
26
- data_bag_item = FFI_Yajl::Parser.parse(contents, :create_additions => false)
27
- if data_bag_item['json_class'] == 'Chef::DataBagItem' && data_bag_item['raw_data']
28
- data_bag_item['raw_data']['id']
29
- else
30
- data_bag_item['id']
31
- end
32
- end
33
-
34
- def delete(request)
35
- key = request.rest_path[3]
36
- delete_data_dir(request, request.rest_path, :recursive)
37
- json_response(200, {
38
- 'chef_type' => 'data_bag',
39
- 'json_class' => 'Chef::DataBag',
40
- 'name' => key
41
- })
42
- end
43
- end
44
- end
45
- end
1
+ require 'ffi_yajl'
2
+ require 'chef_zero/endpoints/rest_list_endpoint'
3
+ require 'chef_zero/endpoints/data_bag_item_endpoint'
4
+ require 'chef_zero/rest_error_response'
5
+
6
+ module ChefZero
7
+ module Endpoints
8
+ # /data/NAME
9
+ class DataBagEndpoint < RestListEndpoint
10
+ def initialize(server)
11
+ super(server, 'id')
12
+ end
13
+
14
+ def post(request)
15
+ json = FFI_Yajl::Parser.parse(request.body, :create_additions => false)
16
+ key = identity_keys.map { |k| json[k] }.select { |v| v }.first
17
+ response = super(request)
18
+ if response[0] == 201
19
+ already_json_response(201, DataBagItemEndpoint::populate_defaults(request, request.body, request.rest_path[3], key))
20
+ else
21
+ response
22
+ end
23
+ end
24
+
25
+ def get_key(contents)
26
+ data_bag_item = FFI_Yajl::Parser.parse(contents, :create_additions => false)
27
+ if data_bag_item['json_class'] == 'Chef::DataBagItem' && data_bag_item['raw_data']
28
+ data_bag_item['raw_data']['id']
29
+ else
30
+ data_bag_item['id']
31
+ end
32
+ end
33
+
34
+ def delete(request)
35
+ key = request.rest_path[3]
36
+ delete_data_dir(request, request.rest_path, :recursive)
37
+ json_response(200, {
38
+ 'chef_type' => 'data_bag',
39
+ 'json_class' => 'Chef::DataBag',
40
+ 'name' => key
41
+ })
42
+ end
43
+ end
44
+ end
45
+ end
@@ -1,25 +1,25 @@
1
- require 'ffi_yajl'
2
- require 'chef_zero/endpoints/rest_object_endpoint'
3
- require 'chef_zero/endpoints/data_bag_item_endpoint'
4
- require 'chef_zero/chef_data/data_normalizer'
5
-
6
- module ChefZero
7
- module Endpoints
8
- # /data/NAME/NAME
9
- class DataBagItemEndpoint < RestObjectEndpoint
10
- def initialize(server)
11
- super(server, 'id')
12
- end
13
-
14
- def populate_defaults(request, response_json)
15
- DataBagItemEndpoint::populate_defaults(request, response_json, request.rest_path[3], request.rest_path[4])
16
- end
17
-
18
- def self.populate_defaults(request, response_json, data_bag, data_bag_item)
19
- response = FFI_Yajl::Parser.parse(response_json, :create_additions => false)
20
- response = ChefData::DataNormalizer.normalize_data_bag_item(response, data_bag, data_bag_item, request.method)
21
- FFI_Yajl::Encoder.encode(response, :pretty => true)
22
- end
23
- end
24
- end
25
- end
1
+ require 'ffi_yajl'
2
+ require 'chef_zero/endpoints/rest_object_endpoint'
3
+ require 'chef_zero/endpoints/data_bag_item_endpoint'
4
+ require 'chef_zero/chef_data/data_normalizer'
5
+
6
+ module ChefZero
7
+ module Endpoints
8
+ # /data/NAME/NAME
9
+ class DataBagItemEndpoint < RestObjectEndpoint
10
+ def initialize(server)
11
+ super(server, 'id')
12
+ end
13
+
14
+ def populate_defaults(request, response_json)
15
+ DataBagItemEndpoint::populate_defaults(request, response_json, request.rest_path[3], request.rest_path[4])
16
+ end
17
+
18
+ def self.populate_defaults(request, response_json, data_bag, data_bag_item)
19
+ response = FFI_Yajl::Parser.parse(response_json, :create_additions => false)
20
+ response = ChefData::DataNormalizer.normalize_data_bag_item(response, data_bag, data_bag_item, request.method)
21
+ FFI_Yajl::Encoder.encode(response, :pretty => true)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,23 +1,23 @@
1
- require 'ffi_yajl'
2
- require 'chef_zero/endpoints/rest_list_endpoint'
3
-
4
- module ChefZero
5
- module Endpoints
6
- # /data
7
- class DataBagsEndpoint < RestListEndpoint
8
- def post(request)
9
- contents = request.body
10
- json = FFI_Yajl::Parser.parse(contents, :create_additions => false)
11
- name = identity_keys.map { |k| json[k] }.select { |v| v }.first
12
- if name.nil?
13
- error(400, "Must specify #{identity_keys.map { |k| k.inspect }.join(' or ')} in JSON")
14
- elsif exists_data_dir?(request, request.rest_path[0..1] + ['data', name])
15
- error(409, "Object already exists")
16
- else
17
- create_data_dir(request, request.rest_path[0..1] + ['data'], name, :recursive)
18
- json_response(201, {"uri" => "#{build_uri(request.base_uri, request.rest_path + [name])}"})
19
- end
20
- end
21
- end
22
- end
23
- end
1
+ require 'ffi_yajl'
2
+ require 'chef_zero/endpoints/rest_list_endpoint'
3
+
4
+ module ChefZero
5
+ module Endpoints
6
+ # /data
7
+ class DataBagsEndpoint < RestListEndpoint
8
+ def post(request)
9
+ contents = request.body
10
+ json = FFI_Yajl::Parser.parse(contents, :create_additions => false)
11
+ name = identity_keys.map { |k| json[k] }.select { |v| v }.first
12
+ if name.nil?
13
+ error(400, "Must specify #{identity_keys.map { |k| k.inspect }.join(' or ')} in JSON")
14
+ elsif exists_data_dir?(request, request.rest_path[0..1] + ['data', name])
15
+ error(409, "Object already exists")
16
+ else
17
+ create_data_dir(request, request.rest_path[0..1] + ['data'], name, :recursive)
18
+ json_response(201, {"uri" => "#{build_uri(request.base_uri, request.rest_path + [name])}"})
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,24 +1,24 @@
1
- require 'ffi_yajl'
2
- require 'chef_zero/endpoints/cookbooks_base'
3
-
4
- module ChefZero
5
- module Endpoints
6
- # /environments/NAME/cookbooks/NAME
7
- class EnvironmentCookbookEndpoint < CookbooksBase
8
- def get(request)
9
- cookbook_name = request.rest_path[5]
10
- environment = FFI_Yajl::Parser.parse(get_data(request, request.rest_path[0..3]), :create_additions => false)
11
- constraints = environment['cookbook_versions'] || {}
12
- cookbook_versions = list_data(request, request.rest_path[0..1] + request.rest_path[4..5])
13
- if request.query_params['num_versions'] == 'all'
14
- num_versions = nil
15
- elsif request.query_params['num_versions']
16
- num_versions = request.query_params['num_versions'].to_i
17
- else
18
- num_versions = nil
19
- end
20
- json_response(200, format_cookbooks_list(request, { cookbook_name => cookbook_versions }, constraints, num_versions))
21
- end
22
- end
23
- end
24
- end
1
+ require 'ffi_yajl'
2
+ require 'chef_zero/endpoints/cookbooks_base'
3
+
4
+ module ChefZero
5
+ module Endpoints
6
+ # /environments/NAME/cookbooks/NAME
7
+ class EnvironmentCookbookEndpoint < CookbooksBase
8
+ def get(request)
9
+ cookbook_name = request.rest_path[5]
10
+ environment = FFI_Yajl::Parser.parse(get_data(request, request.rest_path[0..3]), :create_additions => false)
11
+ constraints = environment['cookbook_versions'] || {}
12
+ cookbook_versions = list_data(request, request.rest_path[0..1] + request.rest_path[4..5])
13
+ if request.query_params['num_versions'] == 'all'
14
+ num_versions = nil
15
+ elsif request.query_params['num_versions']
16
+ num_versions = request.query_params['num_versions'].to_i
17
+ else
18
+ num_versions = nil
19
+ end
20
+ json_response(200, format_cookbooks_list(request, { cookbook_name => cookbook_versions }, constraints, num_versions))
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,123 +1,123 @@
1
- require 'ffi_yajl'
2
- require 'chef_zero/rest_base'
3
- require 'chef_zero/rest_error_response'
4
-
5
- module ChefZero
6
- module Endpoints
7
- # /environments/NAME/cookbook_versions
8
- class EnvironmentCookbookVersionsEndpoint < RestBase
9
-
10
- def post(request)
11
- cookbook_names = list_data(request, request.rest_path[0..1] + ['cookbooks'])
12
-
13
- # Get the list of cookbooks and versions desired by the runlist
14
- desired_versions = {}
15
- run_list = FFI_Yajl::Parser.parse(request.body, :create_additions => false)['run_list']
16
- run_list.each do |run_list_entry|
17
- if run_list_entry =~ /(.+)::.+\@(.+)/ || run_list_entry =~ /(.+)\@(.+)/
18
- raise RestErrorResponse.new(412, "No such cookbook: #{$1}") if !cookbook_names.include?($1)
19
- raise RestErrorResponse.new(412, "No such cookbook version for cookbook #{$1}: #{$2}") if !list_data(request, request.rest_path[0..1] + ['cookbooks', $1]).include?($2)
20
- desired_versions[$1] = [ $2 ]
21
- else
22
- desired_cookbook = run_list_entry.split('::')[0]
23
- raise RestErrorResponse.new(412, "No such cookbook: #{desired_cookbook}") if !cookbook_names.include?(desired_cookbook)
24
- desired_versions[desired_cookbook] = list_data(request, request.rest_path[0..1] + ['cookbooks', desired_cookbook])
25
- end
26
- end
27
-
28
- # Filter by environment constraints
29
- environment = FFI_Yajl::Parser.parse(get_data(request, request.rest_path[0..3]), :create_additions => false)
30
- environment_constraints = environment['cookbook_versions'] || {}
31
-
32
- desired_versions.each_key do |name|
33
- desired_versions = filter_by_constraint(desired_versions, name, environment_constraints[name])
34
- end
35
-
36
- # Depsolve!
37
- solved = depsolve(request, desired_versions.keys, desired_versions, environment_constraints)
38
- if !solved
39
- if @last_missing_dep && !cookbook_names.include?(@last_missing_dep)
40
- return raise RestErrorResponse.new(412, "No such cookbook: #{@last_missing_dep}")
41
- elsif @last_constraint_failure
42
- return raise RestErrorResponse.new(412, "Could not satisfy version constraints for: #{@last_constraint_failure}")
43
- else
44
-
45
- return raise RestErrorResponse.new(412, "Unsolvable versions!")
46
- end
47
- end
48
-
49
- result = {}
50
- solved.each_pair do |name, versions|
51
- cookbook = FFI_Yajl::Parser.parse(get_data(request, request.rest_path[0..1] + ['cookbooks', name, versions[0]]), :create_additions => false)
52
- result[name] = ChefData::DataNormalizer.normalize_cookbook(self, request.rest_path[0..1], cookbook, name, versions[0], request.base_uri, 'MIN')
53
- end
54
- json_response(200, result)
55
- end
56
-
57
- def depsolve(request, unsolved, desired_versions, environment_constraints)
58
- desired_versions.each do |cb, ver|
59
- if ver.empty?
60
- @last_constraint_failure = cb
61
- return nil
62
- end
63
- end
64
-
65
- # If everything is already
66
- solve_for = unsolved[0]
67
- return desired_versions if !solve_for
68
-
69
- # Go through each desired version of this cookbook, starting with the latest,
70
- # until we find one we can solve successfully with
71
- sort_versions(desired_versions[solve_for]).each do |desired_version|
72
- new_desired_versions = desired_versions.clone
73
- new_desired_versions[solve_for] = [ desired_version ]
74
- new_unsolved = unsolved[1..-1]
75
-
76
- # Pick this cookbook, and add dependencies
77
- cookbook_obj = FFI_Yajl::Parser.parse(get_data(request, request.rest_path[0..1] + ['cookbooks', solve_for, desired_version]), :create_additions => false)
78
- cookbook_metadata = cookbook_obj['metadata'] || {}
79
- cookbook_dependencies = cookbook_metadata['dependencies'] || {}
80
- dep_not_found = false
81
- cookbook_dependencies.each_pair do |dep_name, dep_constraint|
82
- # If the dep is not already in the list, add it to the list to solve
83
- # and bring in all environment-allowed cookbook versions to desired_versions
84
- if !new_desired_versions.has_key?(dep_name)
85
- new_unsolved = new_unsolved + [dep_name]
86
- # If the dep is missing, we will try other versions of the cookbook that might not have the bad dep.
87
- if !exists_data_dir?(request, request.rest_path[0..1] + ['cookbooks', dep_name])
88
- @last_missing_dep = dep_name.to_s
89
- dep_not_found = true
90
- break
91
- end
92
- new_desired_versions[dep_name] = list_data(request, request.rest_path[0..1] + ['cookbooks', dep_name])
93
- new_desired_versions = filter_by_constraint(new_desired_versions, dep_name, environment_constraints[dep_name])
94
- end
95
- new_desired_versions = filter_by_constraint(new_desired_versions, dep_name, dep_constraint)
96
- end
97
-
98
- next if dep_not_found
99
-
100
- # Depsolve children with this desired version! First solution wins.
101
- result = depsolve(request, new_unsolved, new_desired_versions, environment_constraints)
102
- return result if result
103
- end
104
- return nil
105
- end
106
-
107
- def sort_versions(versions)
108
- result = versions.sort_by { |version| Gem::Version.new(version.dup) }
109
- result.reverse
110
- end
111
-
112
- def filter_by_constraint(versions, cookbook_name, constraint)
113
- return versions if !constraint
114
- constraint = Gem::Requirement.new(constraint)
115
- new_versions = versions[cookbook_name]
116
- new_versions = new_versions.select { |version| constraint.satisfied_by?(Gem::Version.new(version.dup)) }
117
- result = versions.clone
118
- result[cookbook_name] = new_versions
119
- result
120
- end
121
- end
122
- end
123
- end
1
+ require 'ffi_yajl'
2
+ require 'chef_zero/rest_base'
3
+ require 'chef_zero/rest_error_response'
4
+
5
+ module ChefZero
6
+ module Endpoints
7
+ # /environments/NAME/cookbook_versions
8
+ class EnvironmentCookbookVersionsEndpoint < RestBase
9
+
10
+ def post(request)
11
+ cookbook_names = list_data(request, request.rest_path[0..1] + ['cookbooks'])
12
+
13
+ # Get the list of cookbooks and versions desired by the runlist
14
+ desired_versions = {}
15
+ run_list = FFI_Yajl::Parser.parse(request.body, :create_additions => false)['run_list']
16
+ run_list.each do |run_list_entry|
17
+ if run_list_entry =~ /(.+)::.+\@(.+)/ || run_list_entry =~ /(.+)\@(.+)/
18
+ raise RestErrorResponse.new(412, "No such cookbook: #{$1}") if !cookbook_names.include?($1)
19
+ raise RestErrorResponse.new(412, "No such cookbook version for cookbook #{$1}: #{$2}") if !list_data(request, request.rest_path[0..1] + ['cookbooks', $1]).include?($2)
20
+ desired_versions[$1] = [ $2 ]
21
+ else
22
+ desired_cookbook = run_list_entry.split('::')[0]
23
+ raise RestErrorResponse.new(412, "No such cookbook: #{desired_cookbook}") if !cookbook_names.include?(desired_cookbook)
24
+ desired_versions[desired_cookbook] = list_data(request, request.rest_path[0..1] + ['cookbooks', desired_cookbook])
25
+ end
26
+ end
27
+
28
+ # Filter by environment constraints
29
+ environment = FFI_Yajl::Parser.parse(get_data(request, request.rest_path[0..3]), :create_additions => false)
30
+ environment_constraints = environment['cookbook_versions'] || {}
31
+
32
+ desired_versions.each_key do |name|
33
+ desired_versions = filter_by_constraint(desired_versions, name, environment_constraints[name])
34
+ end
35
+
36
+ # Depsolve!
37
+ solved = depsolve(request, desired_versions.keys, desired_versions, environment_constraints)
38
+ if !solved
39
+ if @last_missing_dep && !cookbook_names.include?(@last_missing_dep)
40
+ return raise RestErrorResponse.new(412, "No such cookbook: #{@last_missing_dep}")
41
+ elsif @last_constraint_failure
42
+ return raise RestErrorResponse.new(412, "Could not satisfy version constraints for: #{@last_constraint_failure}")
43
+ else
44
+
45
+ return raise RestErrorResponse.new(412, "Unsolvable versions!")
46
+ end
47
+ end
48
+
49
+ result = {}
50
+ solved.each_pair do |name, versions|
51
+ cookbook = FFI_Yajl::Parser.parse(get_data(request, request.rest_path[0..1] + ['cookbooks', name, versions[0]]), :create_additions => false)
52
+ result[name] = ChefData::DataNormalizer.normalize_cookbook(self, request.rest_path[0..1], cookbook, name, versions[0], request.base_uri, 'MIN')
53
+ end
54
+ json_response(200, result)
55
+ end
56
+
57
+ def depsolve(request, unsolved, desired_versions, environment_constraints)
58
+ desired_versions.each do |cb, ver|
59
+ if ver.empty?
60
+ @last_constraint_failure = cb
61
+ return nil
62
+ end
63
+ end
64
+
65
+ # If everything is already
66
+ solve_for = unsolved[0]
67
+ return desired_versions if !solve_for
68
+
69
+ # Go through each desired version of this cookbook, starting with the latest,
70
+ # until we find one we can solve successfully with
71
+ sort_versions(desired_versions[solve_for]).each do |desired_version|
72
+ new_desired_versions = desired_versions.clone
73
+ new_desired_versions[solve_for] = [ desired_version ]
74
+ new_unsolved = unsolved[1..-1]
75
+
76
+ # Pick this cookbook, and add dependencies
77
+ cookbook_obj = FFI_Yajl::Parser.parse(get_data(request, request.rest_path[0..1] + ['cookbooks', solve_for, desired_version]), :create_additions => false)
78
+ cookbook_metadata = cookbook_obj['metadata'] || {}
79
+ cookbook_dependencies = cookbook_metadata['dependencies'] || {}
80
+ dep_not_found = false
81
+ cookbook_dependencies.each_pair do |dep_name, dep_constraint|
82
+ # If the dep is not already in the list, add it to the list to solve
83
+ # and bring in all environment-allowed cookbook versions to desired_versions
84
+ if !new_desired_versions.has_key?(dep_name)
85
+ new_unsolved = new_unsolved + [dep_name]
86
+ # If the dep is missing, we will try other versions of the cookbook that might not have the bad dep.
87
+ if !exists_data_dir?(request, request.rest_path[0..1] + ['cookbooks', dep_name])
88
+ @last_missing_dep = dep_name.to_s
89
+ dep_not_found = true
90
+ break
91
+ end
92
+ new_desired_versions[dep_name] = list_data(request, request.rest_path[0..1] + ['cookbooks', dep_name])
93
+ new_desired_versions = filter_by_constraint(new_desired_versions, dep_name, environment_constraints[dep_name])
94
+ end
95
+ new_desired_versions = filter_by_constraint(new_desired_versions, dep_name, dep_constraint)
96
+ end
97
+
98
+ next if dep_not_found
99
+
100
+ # Depsolve children with this desired version! First solution wins.
101
+ result = depsolve(request, new_unsolved, new_desired_versions, environment_constraints)
102
+ return result if result
103
+ end
104
+ return nil
105
+ end
106
+
107
+ def sort_versions(versions)
108
+ result = versions.sort_by { |version| Gem::Version.new(version.dup) }
109
+ result.reverse
110
+ end
111
+
112
+ def filter_by_constraint(versions, cookbook_name, constraint)
113
+ return versions if !constraint
114
+ constraint = Gem::Requirement.new(constraint)
115
+ new_versions = versions[cookbook_name]
116
+ new_versions = new_versions.select { |version| constraint.satisfied_by?(Gem::Version.new(version.dup)) }
117
+ result = versions.clone
118
+ result[cookbook_name] = new_versions
119
+ result
120
+ end
121
+ end
122
+ end
123
+ end