chef-zero 1.4.0.alpha-x86-mingw32

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 (61) hide show
  1. data/LICENSE +201 -0
  2. data/README.md +145 -0
  3. data/Rakefile +11 -0
  4. data/bin/chef-zero +43 -0
  5. data/lib/chef_zero.rb +7 -0
  6. data/lib/chef_zero/cookbook_data.rb +223 -0
  7. data/lib/chef_zero/data_normalizer.rb +142 -0
  8. data/lib/chef_zero/data_store/data_already_exists_error.rb +29 -0
  9. data/lib/chef_zero/data_store/data_error.rb +31 -0
  10. data/lib/chef_zero/data_store/data_not_found_error.rb +29 -0
  11. data/lib/chef_zero/data_store/memory_store.rb +167 -0
  12. data/lib/chef_zero/endpoints/actor_endpoint.rb +68 -0
  13. data/lib/chef_zero/endpoints/actors_endpoint.rb +32 -0
  14. data/lib/chef_zero/endpoints/authenticate_user_endpoint.rb +25 -0
  15. data/lib/chef_zero/endpoints/cookbook_endpoint.rb +39 -0
  16. data/lib/chef_zero/endpoints/cookbook_version_endpoint.rb +110 -0
  17. data/lib/chef_zero/endpoints/cookbooks_base.rb +65 -0
  18. data/lib/chef_zero/endpoints/cookbooks_endpoint.rb +19 -0
  19. data/lib/chef_zero/endpoints/data_bag_endpoint.rb +45 -0
  20. data/lib/chef_zero/endpoints/data_bag_item_endpoint.rb +25 -0
  21. data/lib/chef_zero/endpoints/data_bags_endpoint.rb +22 -0
  22. data/lib/chef_zero/endpoints/environment_cookbook_endpoint.rb +24 -0
  23. data/lib/chef_zero/endpoints/environment_cookbook_versions_endpoint.rb +109 -0
  24. data/lib/chef_zero/endpoints/environment_cookbooks_endpoint.rb +22 -0
  25. data/lib/chef_zero/endpoints/environment_endpoint.rb +33 -0
  26. data/lib/chef_zero/endpoints/environment_nodes_endpoint.rb +23 -0
  27. data/lib/chef_zero/endpoints/environment_recipes_endpoint.rb +22 -0
  28. data/lib/chef_zero/endpoints/environment_role_endpoint.rb +36 -0
  29. data/lib/chef_zero/endpoints/file_store_file_endpoint.rb +22 -0
  30. data/lib/chef_zero/endpoints/node_endpoint.rb +17 -0
  31. data/lib/chef_zero/endpoints/not_found_endpoint.rb +11 -0
  32. data/lib/chef_zero/endpoints/principal_endpoint.rb +30 -0
  33. data/lib/chef_zero/endpoints/rest_list_endpoint.rb +40 -0
  34. data/lib/chef_zero/endpoints/rest_object_endpoint.rb +61 -0
  35. data/lib/chef_zero/endpoints/role_endpoint.rb +16 -0
  36. data/lib/chef_zero/endpoints/role_environments_endpoint.rb +14 -0
  37. data/lib/chef_zero/endpoints/sandbox_endpoint.rb +27 -0
  38. data/lib/chef_zero/endpoints/sandboxes_endpoint.rb +51 -0
  39. data/lib/chef_zero/endpoints/search_endpoint.rb +188 -0
  40. data/lib/chef_zero/endpoints/searches_endpoint.rb +18 -0
  41. data/lib/chef_zero/log.rb +7 -0
  42. data/lib/chef_zero/rest_base.rb +133 -0
  43. data/lib/chef_zero/rest_error_response.rb +11 -0
  44. data/lib/chef_zero/rest_request.rb +56 -0
  45. data/lib/chef_zero/rest_router.rb +44 -0
  46. data/lib/chef_zero/rspec.rb +107 -0
  47. data/lib/chef_zero/server.rb +309 -0
  48. data/lib/chef_zero/solr/query/binary_operator.rb +53 -0
  49. data/lib/chef_zero/solr/query/phrase.rb +23 -0
  50. data/lib/chef_zero/solr/query/range_query.rb +34 -0
  51. data/lib/chef_zero/solr/query/regexpable_query.rb +29 -0
  52. data/lib/chef_zero/solr/query/subquery.rb +35 -0
  53. data/lib/chef_zero/solr/query/term.rb +45 -0
  54. data/lib/chef_zero/solr/query/unary_operator.rb +43 -0
  55. data/lib/chef_zero/solr/solr_doc.rb +62 -0
  56. data/lib/chef_zero/solr/solr_parser.rb +193 -0
  57. data/lib/chef_zero/version.rb +3 -0
  58. data/spec/run.rb +25 -0
  59. data/spec/support/pedant.rb +117 -0
  60. data/spec/support/stickywicket.pem +27 -0
  61. metadata +204 -0
@@ -0,0 +1,32 @@
1
+ require 'json'
2
+ require 'chef_zero/endpoints/rest_list_endpoint'
3
+
4
+ module ChefZero
5
+ module Endpoints
6
+ # /clients or /users
7
+ class ActorsEndpoint < RestListEndpoint
8
+ def post(request)
9
+ # First, find out if the user actually posted a public key. If not, make
10
+ # one.
11
+ request_body = JSON.parse(request.body, :create_additions => false)
12
+ public_key = request_body['public_key']
13
+ if !public_key
14
+ private_key, public_key = server.gen_key_pair
15
+ request_body['public_key'] = public_key
16
+ request.body = JSON.pretty_generate(request_body)
17
+ end
18
+
19
+ result = super(request)
20
+ if result[0] == 201
21
+ # If we generated a key, stuff it in the response.
22
+ response = JSON.parse(result[2], :create_additions => false)
23
+ response['private_key'] = private_key if private_key
24
+ response['public_key'] = public_key
25
+ json_response(201, response)
26
+ else
27
+ result
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,25 @@
1
+ require 'json'
2
+ require 'chef_zero/rest_base'
3
+
4
+ module ChefZero
5
+ module Endpoints
6
+ # /authenticate_user
7
+ class AuthenticateUserEndpoint < RestBase
8
+ def post(request)
9
+ request_json = JSON.parse(request.body, :create_additions => false)
10
+ name = request_json['name']
11
+ password = request_json['password']
12
+ begin
13
+ user = data_store.get(['users', name])
14
+ verified = JSON.parse(user, :create_additions => false)['password'] == password
15
+ rescue DataStore::DataNotFoundError
16
+ verified = false
17
+ end
18
+ json_response(200, {
19
+ 'name' => name,
20
+ 'verified' => !!verified
21
+ })
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,39 @@
1
+ require 'chef_zero/endpoints/cookbooks_base'
2
+
3
+ module ChefZero
4
+ module Endpoints
5
+ # /cookbooks/NAME
6
+ class CookbookEndpoint < CookbooksBase
7
+ def get(request)
8
+ filter = request.rest_path[1]
9
+ case filter
10
+ when '_latest'
11
+ result = {}
12
+ filter_cookbooks(all_cookbooks_list, {}, 1) do |name, versions|
13
+ if versions.size > 0
14
+ result[name] = build_uri(request.base_uri, ['cookbooks', name, versions[0]])
15
+ end
16
+ end
17
+ json_response(200, result)
18
+ when '_recipes'
19
+ result = []
20
+ filter_cookbooks(all_cookbooks_list, {}, 1) do |name, versions|
21
+ if versions.size > 0
22
+ cookbook = JSON.parse(get_data(request, ['cookbooks', name, versions[0]]), :create_additions => false)
23
+ result += recipe_names(name, cookbook)
24
+ end
25
+ end
26
+ json_response(200, result.sort)
27
+ else
28
+ cookbook_list = { filter => list_data(request, request.rest_path) }
29
+ json_response(200, format_cookbooks_list(request, cookbook_list))
30
+ end
31
+ end
32
+
33
+ def latest_version(versions)
34
+ sorted = versions.sort_by { |version| Gem::Version.new(version.dup) }
35
+ sorted[-1]
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,110 @@
1
+ require 'json'
2
+ require 'chef_zero/endpoints/rest_object_endpoint'
3
+ require 'chef_zero/rest_error_response'
4
+ require 'chef_zero/data_normalizer'
5
+
6
+ module ChefZero
7
+ module Endpoints
8
+ # /cookbooks/NAME/VERSION
9
+ class CookbookVersionEndpoint < RestObjectEndpoint
10
+ def get(request)
11
+ if request.rest_path[2] == "_latest" || request.rest_path[2] == "latest"
12
+ request.rest_path[2] = latest_version(list_data(request, request.rest_path[0..1]))
13
+ end
14
+ super(request)
15
+ end
16
+
17
+ def put(request)
18
+ name = request.rest_path[1]
19
+ version = request.rest_path[2]
20
+ existing_cookbook = get_data(request, request.rest_path, :nil)
21
+
22
+ # Honor frozen
23
+ if existing_cookbook
24
+ existing_cookbook_json = JSON.parse(existing_cookbook, :create_additions => false)
25
+ if existing_cookbook_json['frozen?']
26
+ if request.query_params['force'] != "true"
27
+ raise RestErrorResponse.new(409, "The cookbook #{name} at version #{version} is frozen. Use the 'force' option to override.")
28
+ end
29
+ # For some reason, you are forever unable to modify "frozen?" on a frozen cookbook.
30
+ request_body = JSON.parse(request.body, :create_additions => false)
31
+ if !request_body['frozen?']
32
+ request_body['frozen?'] = true
33
+ request.body = JSON.pretty_generate(request_body)
34
+ end
35
+ end
36
+ end
37
+
38
+ # Set the cookbook
39
+ set_data(request, ['cookbooks', name, version], request.body, :create_dir, :create)
40
+
41
+ # If the cookbook was updated, check for deleted files and clean them up
42
+ if existing_cookbook
43
+ missing_checksums = get_checksums(existing_cookbook) - get_checksums(request.body)
44
+ if missing_checksums.size > 0
45
+ hoover_unused_checksums(missing_checksums)
46
+ end
47
+ end
48
+
49
+ already_json_response(existing_cookbook ? 200 : 201, populate_defaults(request, request.body))
50
+ end
51
+
52
+ def delete(request)
53
+ if request.rest_path[2] == "_latest" || request.rest_path[2] == "latest"
54
+ request.rest_path[2] = latest_version(list_data(request, request.rest_path[0..1]))
55
+ end
56
+
57
+ deleted_cookbook = get_data(request)
58
+
59
+ response = super(request)
60
+ cookbook_name = request.rest_path[1]
61
+ delete_data_dir(request, ['cookbooks', cookbook_name]) if list_data(request, ['cookbooks', cookbook_name]).size == 0
62
+
63
+ # Hoover deleted files, if they exist
64
+ hoover_unused_checksums(get_checksums(deleted_cookbook))
65
+ response
66
+ end
67
+
68
+ def get_checksums(cookbook)
69
+ result = []
70
+ JSON.parse(cookbook, :create_additions => false).each_pair do |key, value|
71
+ if value.is_a?(Array)
72
+ value.each do |file|
73
+ if file.is_a?(Hash) && file.has_key?('checksum')
74
+ result << file['checksum']
75
+ end
76
+ end
77
+ end
78
+ end
79
+ result.uniq
80
+ end
81
+
82
+ private
83
+
84
+ def hoover_unused_checksums(deleted_checksums)
85
+ data_store.list(['cookbooks']).each do |cookbook_name|
86
+ data_store.list(['cookbooks', cookbook_name]).each do |version|
87
+ cookbook = data_store.get(['cookbooks', cookbook_name, version])
88
+ deleted_checksums = deleted_checksums - get_checksums(cookbook)
89
+ end
90
+ end
91
+ deleted_checksums.each do |checksum|
92
+ # There can be a race here if multiple cookbooks are uploading.
93
+ data_store.delete(['file_store', 'checksums', checksum])
94
+ end
95
+ end
96
+
97
+ def populate_defaults(request, response_json)
98
+ # Inject URIs into each cookbook file
99
+ cookbook = JSON.parse(response_json, :create_additions => false)
100
+ cookbook = DataNormalizer.normalize_cookbook(cookbook, request.rest_path[1], request.rest_path[2], request.base_uri, request.method)
101
+ JSON.pretty_generate(cookbook)
102
+ end
103
+
104
+ def latest_version(versions)
105
+ sorted = versions.sort_by { |version| Gem::Version.new(version.dup) }
106
+ sorted[-1]
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,65 @@
1
+ require 'json'
2
+ require 'chef_zero/rest_base'
3
+ require 'chef_zero/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, ['cookbooks', name, version]),
15
+ 'version' => version
16
+ }
17
+ end
18
+ results[name] = {
19
+ 'url' => build_uri(request.base_uri, ['cookbooks', name]),
20
+ 'versions' => versions_list
21
+ }
22
+ end
23
+ results
24
+ end
25
+
26
+ def all_cookbooks_list
27
+ result = {}
28
+ # Race conditions exist here (if someone deletes while listing). I don't care.
29
+ data_store.list(['cookbooks']).each do |name|
30
+ result[name] = data_store.list(['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
@@ -0,0 +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, {}, num_versions))
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,45 @@
1
+ require 'json'
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
+ key = JSON.parse(request.body, :create_additions => false)[identity_key]
16
+ response = super(request)
17
+ if response[0] == 201
18
+ already_json_response(201, DataBagItemEndpoint::populate_defaults(request, request.body, request.rest_path[1], key))
19
+ else
20
+ response
21
+ end
22
+ end
23
+
24
+ def get_key(contents)
25
+ data_bag_item = JSON.parse(contents, :create_additions => false)
26
+ if data_bag_item['json_class'] == 'Chef::DataBagItem' && data_bag_item['raw_data']
27
+ data_bag_item['raw_data']['id']
28
+ else
29
+ data_bag_item['id']
30
+ end
31
+ end
32
+
33
+ def delete(request)
34
+ key = request.rest_path[1]
35
+ delete_data_dir(request, request.rest_path, :recursive)
36
+ json_response(200, {
37
+ 'chef_type' => 'data_bag',
38
+ 'json_class' => 'Chef::DataBag',
39
+ 'name' => key
40
+ })
41
+ end
42
+ end
43
+ end
44
+ end
45
+
@@ -0,0 +1,25 @@
1
+ require 'json'
2
+ require 'chef_zero/endpoints/rest_object_endpoint'
3
+ require 'chef_zero/endpoints/data_bag_item_endpoint'
4
+ require 'chef_zero/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[1], request.rest_path[2])
16
+ end
17
+
18
+ def self.populate_defaults(request, response_json, data_bag, data_bag_item)
19
+ response = JSON.parse(response_json, :create_additions => false)
20
+ response = DataNormalizer.normalize_data_bag_item(response, data_bag, data_bag_item, request.method)
21
+ JSON.pretty_generate(response)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,22 @@
1
+ require 'json'
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
+ name = JSON.parse(contents, :create_additions => false)[identity_key]
11
+ if name.nil?
12
+ error(400, "Must specify '#{identity_key}' in JSON")
13
+ elsif exists_data_dir?(request, ['data', name])
14
+ error(409, "Object already exists")
15
+ else
16
+ data_store.create_dir(['data'], name, :recursive)
17
+ json_response(201, {"uri" => "#{build_uri(request.base_uri, request.rest_path + [name])}"})
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,24 @@
1
+ require 'json'
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[3]
10
+ environment = JSON.parse(get_data(request, request.rest_path[0..1]), :create_additions => false)
11
+ constraints = environment['cookbook_versions'] || {}
12
+ cookbook_versions = list_data(request, request.rest_path[2..3])
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
@@ -0,0 +1,109 @@
1
+ require 'json'
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
+ def post(request)
10
+ cookbook_names = list_data(request, ['cookbooks'])
11
+
12
+ # Get the list of cookbooks and versions desired by the runlist
13
+ desired_versions = {}
14
+ run_list = JSON.parse(request.body, :create_additions => false)['run_list']
15
+ run_list.each do |run_list_entry|
16
+ if run_list_entry =~ /(.+)(::.+)?\@(.+)/
17
+ raise RestErrorResponse.new(412, "No such cookbook: #{$1}") if !cookbook_names.include?($1)
18
+ raise RestErrorResponse.new(412, "No such cookbook version for cookbook #{$1}: #{$3}") if !list_data(request, ['cookbooks', $1]).include?($3)
19
+ desired_versions[$1] = [ $3 ]
20
+ else
21
+ desired_cookbook = run_list_entry.split('::')[0]
22
+ raise RestErrorResponse.new(412, "No such cookbook: #{desired_cookbook}") if !cookbook_names.include?(desired_cookbook)
23
+ desired_versions[desired_cookbook] = list_data(request, ['cookbooks', desired_cookbook])
24
+ end
25
+ end
26
+
27
+ # Filter by environment constraints
28
+ environment = JSON.parse(get_data(request, request.rest_path[0..1]), :create_additions => false)
29
+ environment_constraints = environment['cookbook_versions'] || {}
30
+
31
+ desired_versions.each_key do |name|
32
+ desired_versions = filter_by_constraint(desired_versions, name, environment_constraints[name])
33
+ end
34
+
35
+ # Depsolve!
36
+ solved = depsolve(request, desired_versions.keys, desired_versions, environment_constraints)
37
+ if !solved
38
+ return raise RestErrorResponse.new(412, "Unsolvable versions!")
39
+ end
40
+
41
+ result = {}
42
+ solved.each_pair do |name, versions|
43
+ cookbook = JSON.parse(get_data(request, ['cookbooks', name, versions[0]]), :create_additions => false)
44
+ result[name] = DataNormalizer.normalize_cookbook(cookbook, name, versions[0], request.base_uri, 'MIN')
45
+ end
46
+ json_response(200, result)
47
+ end
48
+
49
+ def depsolve(request, unsolved, desired_versions, environment_constraints)
50
+ return nil if desired_versions.values.any? { |versions| versions.empty? }
51
+
52
+ # If everything is already
53
+ solve_for = unsolved[0]
54
+ return desired_versions if !solve_for
55
+
56
+ # Go through each desired version of this cookbook, starting with the latest,
57
+ # until we find one we can solve successfully with
58
+ sort_versions(desired_versions[solve_for]).each do |desired_version|
59
+ new_desired_versions = desired_versions.clone
60
+ new_desired_versions[solve_for] = [ desired_version ]
61
+ new_unsolved = unsolved[1..-1]
62
+
63
+ # Pick this cookbook, and add dependencies
64
+ cookbook_obj = JSON.parse(get_data(request, ['cookbooks', solve_for, desired_version]), :create_additions => false)
65
+ cookbook_metadata = cookbook_obj['metadata'] || {}
66
+ cookbook_dependencies = cookbook_metadata['dependencies'] || {}
67
+ dep_not_found = false
68
+ cookbook_dependencies.each_pair do |dep_name, dep_constraint|
69
+ # If the dep is not already in the list, add it to the list to solve
70
+ # and bring in all environment-allowed cookbook versions to desired_versions
71
+ if !new_desired_versions.has_key?(dep_name)
72
+ new_unsolved = new_unsolved + [dep_name]
73
+ # If the dep is missing, we will try other versions of the cookbook that might not have the bad dep.
74
+ if !exists_data_dir?(request, ['cookbooks', dep_name])
75
+ dep_not_found = true
76
+ break
77
+ end
78
+ new_desired_versions[dep_name] = list_data(request, ['cookbooks', dep_name])
79
+ new_desired_versions = filter_by_constraint(new_desired_versions, dep_name, environment_constraints[dep_name])
80
+ end
81
+ new_desired_versions = filter_by_constraint(new_desired_versions, dep_name, dep_constraint)
82
+ end
83
+
84
+ next if dep_not_found
85
+
86
+ # Depsolve children with this desired version! First solution wins.
87
+ result = depsolve(request, new_unsolved, new_desired_versions, environment_constraints)
88
+ return result if result
89
+ end
90
+ return nil
91
+ end
92
+
93
+ def sort_versions(versions)
94
+ result = versions.sort_by { |version| Gem::Version.new(version.dup) }
95
+ result.reverse
96
+ end
97
+
98
+ def filter_by_constraint(versions, cookbook_name, constraint)
99
+ return versions if !constraint
100
+ constraint = Gem::Requirement.new(constraint)
101
+ new_versions = versions[cookbook_name]
102
+ new_versions = new_versions.select { |version| constraint.satisfied_by?(Gem::Version.new(version.dup)) }
103
+ result = versions.clone
104
+ result[cookbook_name] = new_versions
105
+ result
106
+ end
107
+ end
108
+ end
109
+ end