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.
- data/LICENSE +201 -0
- data/README.md +145 -0
- data/Rakefile +11 -0
- data/bin/chef-zero +43 -0
- data/lib/chef_zero.rb +7 -0
- data/lib/chef_zero/cookbook_data.rb +223 -0
- data/lib/chef_zero/data_normalizer.rb +142 -0
- data/lib/chef_zero/data_store/data_already_exists_error.rb +29 -0
- data/lib/chef_zero/data_store/data_error.rb +31 -0
- data/lib/chef_zero/data_store/data_not_found_error.rb +29 -0
- data/lib/chef_zero/data_store/memory_store.rb +167 -0
- data/lib/chef_zero/endpoints/actor_endpoint.rb +68 -0
- data/lib/chef_zero/endpoints/actors_endpoint.rb +32 -0
- data/lib/chef_zero/endpoints/authenticate_user_endpoint.rb +25 -0
- data/lib/chef_zero/endpoints/cookbook_endpoint.rb +39 -0
- data/lib/chef_zero/endpoints/cookbook_version_endpoint.rb +110 -0
- data/lib/chef_zero/endpoints/cookbooks_base.rb +65 -0
- data/lib/chef_zero/endpoints/cookbooks_endpoint.rb +19 -0
- data/lib/chef_zero/endpoints/data_bag_endpoint.rb +45 -0
- data/lib/chef_zero/endpoints/data_bag_item_endpoint.rb +25 -0
- data/lib/chef_zero/endpoints/data_bags_endpoint.rb +22 -0
- data/lib/chef_zero/endpoints/environment_cookbook_endpoint.rb +24 -0
- data/lib/chef_zero/endpoints/environment_cookbook_versions_endpoint.rb +109 -0
- data/lib/chef_zero/endpoints/environment_cookbooks_endpoint.rb +22 -0
- data/lib/chef_zero/endpoints/environment_endpoint.rb +33 -0
- data/lib/chef_zero/endpoints/environment_nodes_endpoint.rb +23 -0
- data/lib/chef_zero/endpoints/environment_recipes_endpoint.rb +22 -0
- data/lib/chef_zero/endpoints/environment_role_endpoint.rb +36 -0
- data/lib/chef_zero/endpoints/file_store_file_endpoint.rb +22 -0
- data/lib/chef_zero/endpoints/node_endpoint.rb +17 -0
- data/lib/chef_zero/endpoints/not_found_endpoint.rb +11 -0
- data/lib/chef_zero/endpoints/principal_endpoint.rb +30 -0
- data/lib/chef_zero/endpoints/rest_list_endpoint.rb +40 -0
- data/lib/chef_zero/endpoints/rest_object_endpoint.rb +61 -0
- data/lib/chef_zero/endpoints/role_endpoint.rb +16 -0
- data/lib/chef_zero/endpoints/role_environments_endpoint.rb +14 -0
- data/lib/chef_zero/endpoints/sandbox_endpoint.rb +27 -0
- data/lib/chef_zero/endpoints/sandboxes_endpoint.rb +51 -0
- data/lib/chef_zero/endpoints/search_endpoint.rb +188 -0
- data/lib/chef_zero/endpoints/searches_endpoint.rb +18 -0
- data/lib/chef_zero/log.rb +7 -0
- data/lib/chef_zero/rest_base.rb +133 -0
- data/lib/chef_zero/rest_error_response.rb +11 -0
- data/lib/chef_zero/rest_request.rb +56 -0
- data/lib/chef_zero/rest_router.rb +44 -0
- data/lib/chef_zero/rspec.rb +107 -0
- data/lib/chef_zero/server.rb +309 -0
- data/lib/chef_zero/solr/query/binary_operator.rb +53 -0
- data/lib/chef_zero/solr/query/phrase.rb +23 -0
- data/lib/chef_zero/solr/query/range_query.rb +34 -0
- data/lib/chef_zero/solr/query/regexpable_query.rb +29 -0
- data/lib/chef_zero/solr/query/subquery.rb +35 -0
- data/lib/chef_zero/solr/query/term.rb +45 -0
- data/lib/chef_zero/solr/query/unary_operator.rb +43 -0
- data/lib/chef_zero/solr/solr_doc.rb +62 -0
- data/lib/chef_zero/solr/solr_parser.rb +193 -0
- data/lib/chef_zero/version.rb +3 -0
- data/spec/run.rb +25 -0
- data/spec/support/pedant.rb +117 -0
- data/spec/support/stickywicket.pem +27 -0
- 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
|