chef-zero 0.9

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 (52) hide show
  1. data/LICENSE +201 -0
  2. data/README.rdoc +79 -0
  3. data/Rakefile +19 -0
  4. data/bin/chef-zero +40 -0
  5. data/lib/chef_zero.rb +5 -0
  6. data/lib/chef_zero/cookbook_data.rb +110 -0
  7. data/lib/chef_zero/data_normalizer.rb +129 -0
  8. data/lib/chef_zero/endpoints/actor_endpoint.rb +68 -0
  9. data/lib/chef_zero/endpoints/actors_endpoint.rb +32 -0
  10. data/lib/chef_zero/endpoints/authenticate_user_endpoint.rb +21 -0
  11. data/lib/chef_zero/endpoints/cookbook_endpoint.rb +39 -0
  12. data/lib/chef_zero/endpoints/cookbook_version_endpoint.rb +106 -0
  13. data/lib/chef_zero/endpoints/cookbooks_base.rb +59 -0
  14. data/lib/chef_zero/endpoints/cookbooks_endpoint.rb +12 -0
  15. data/lib/chef_zero/endpoints/data_bag_endpoint.rb +50 -0
  16. data/lib/chef_zero/endpoints/data_bag_item_endpoint.rb +25 -0
  17. data/lib/chef_zero/endpoints/data_bags_endpoint.rb +21 -0
  18. data/lib/chef_zero/endpoints/environment_cookbook_endpoint.rb +24 -0
  19. data/lib/chef_zero/endpoints/environment_cookbook_versions_endpoint.rb +114 -0
  20. data/lib/chef_zero/endpoints/environment_cookbooks_endpoint.rb +22 -0
  21. data/lib/chef_zero/endpoints/environment_endpoint.rb +33 -0
  22. data/lib/chef_zero/endpoints/environment_nodes_endpoint.rb +23 -0
  23. data/lib/chef_zero/endpoints/environment_recipes_endpoint.rb +22 -0
  24. data/lib/chef_zero/endpoints/environment_role_endpoint.rb +35 -0
  25. data/lib/chef_zero/endpoints/file_store_file_endpoint.rb +22 -0
  26. data/lib/chef_zero/endpoints/node_endpoint.rb +17 -0
  27. data/lib/chef_zero/endpoints/not_found_endpoint.rb +9 -0
  28. data/lib/chef_zero/endpoints/principal_endpoint.rb +30 -0
  29. data/lib/chef_zero/endpoints/rest_list_endpoint.rb +41 -0
  30. data/lib/chef_zero/endpoints/rest_object_endpoint.rb +65 -0
  31. data/lib/chef_zero/endpoints/role_endpoint.rb +16 -0
  32. data/lib/chef_zero/endpoints/role_environments_endpoint.rb +14 -0
  33. data/lib/chef_zero/endpoints/sandbox_endpoint.rb +22 -0
  34. data/lib/chef_zero/endpoints/sandboxes_endpoint.rb +44 -0
  35. data/lib/chef_zero/endpoints/search_endpoint.rb +139 -0
  36. data/lib/chef_zero/endpoints/searches_endpoint.rb +18 -0
  37. data/lib/chef_zero/rest_base.rb +82 -0
  38. data/lib/chef_zero/rest_error_response.rb +11 -0
  39. data/lib/chef_zero/rest_request.rb +42 -0
  40. data/lib/chef_zero/router.rb +26 -0
  41. data/lib/chef_zero/server.rb +255 -0
  42. data/lib/chef_zero/solr/query/binary_operator.rb +53 -0
  43. data/lib/chef_zero/solr/query/phrase.rb +23 -0
  44. data/lib/chef_zero/solr/query/range_query.rb +34 -0
  45. data/lib/chef_zero/solr/query/regexpable_query.rb +29 -0
  46. data/lib/chef_zero/solr/query/subquery.rb +35 -0
  47. data/lib/chef_zero/solr/query/term.rb +45 -0
  48. data/lib/chef_zero/solr/query/unary_operator.rb +43 -0
  49. data/lib/chef_zero/solr/solr_doc.rb +62 -0
  50. data/lib/chef_zero/solr/solr_parser.rb +194 -0
  51. data/lib/chef_zero/version.rb +3 -0
  52. metadata +132 -0
@@ -0,0 +1,12 @@
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
+ json_response(200, format_cookbooks_list(request, data['cookbooks']))
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,50 @@
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
+ container = data['data']
36
+ if !container.has_key?(key)
37
+ raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, request.rest_path)}")
38
+ end
39
+ result = container[key]
40
+ container.delete(key)
41
+ json_response(200, {
42
+ 'chef_type' => 'data_bag',
43
+ 'json_class' => 'Chef::DataBag',
44
+ 'name' => key
45
+ })
46
+ end
47
+ end
48
+ end
49
+ end
50
+
@@ -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,21 @@
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
+ container = get_data(request)
10
+ contents = request.body
11
+ name = JSON.parse(contents, :create_additions => false)[identity_key]
12
+ if container[name]
13
+ error(409, "Object already exists")
14
+ else
15
+ container[name] = {}
16
+ json_response(201, {"uri" => "#{build_uri(request.base_uri, request.rest_path + [name])}"})
17
+ end
18
+ end
19
+ end
20
+ end
21
+ 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 = get_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 }, constraints, num_versions))
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,114 @@
1
+ require 'json'
2
+ require 'chef/exceptions' # Needed so Chef::Version/VersionConstraint load
3
+ require 'chef/version_class'
4
+ require 'chef/version_constraint'
5
+ require 'chef_zero/rest_base'
6
+ require 'chef_zero/rest_error_response'
7
+
8
+ module ChefZero
9
+ module Endpoints
10
+ # /environments/NAME/cookbook_versions
11
+ class EnvironmentCookbookVersionsEndpoint < RestBase
12
+ def cookbooks
13
+ data['cookbooks']
14
+ end
15
+
16
+ def environments
17
+ data['environments']
18
+ end
19
+
20
+ def post(request)
21
+ # Get the list of cookbooks and versions desired by the runlist
22
+ desired_versions = {}
23
+ run_list = JSON.parse(request.body, :create_additions => false)['run_list']
24
+ run_list.each do |run_list_entry|
25
+ if run_list_entry =~ /(.+)\@(.+)/
26
+ raise RestErrorResponse.new(412, "No such cookbook: #{$1}") if !cookbooks[$1]
27
+ raise RestErrorResponse.new(412, "No such cookbook version for cookbook #{$1}: #{$2}") if !cookbooks[$1][$2]
28
+ desired_versions[$1] = [ $2 ]
29
+ else
30
+ raise RestErrorResponse.new(412, "No such cookbook: #{run_list_entry}") if !cookbooks[run_list_entry]
31
+ desired_versions[run_list_entry] = cookbooks[run_list_entry].keys
32
+ end
33
+ end
34
+
35
+ # Filter by environment constraints
36
+ environment = JSON.parse(get_data(request, request.rest_path[0..1]), :create_additions => false)
37
+ environment_constraints = environment['cookbook_versions']
38
+
39
+ desired_versions.each_key do |name|
40
+ desired_versions = filter_by_constraint(desired_versions, name, environment_constraints[name])
41
+ end
42
+
43
+ # Depsolve!
44
+ solved = depsolve(desired_versions.keys, desired_versions, environment_constraints)
45
+ if !solved
46
+ return raise RestErrorResponse.new(412, "Unsolvable versions!")
47
+ end
48
+
49
+ result = {}
50
+ solved.each_pair do |name, versions|
51
+ result[name] = JSON.parse(data['cookbooks'][name][versions[0]], :create_additions => false)
52
+ end
53
+ json_response(200, result)
54
+ end
55
+
56
+ def depsolve(unsolved, desired_versions, environment_constraints)
57
+ return nil if desired_versions.values.any? { |versions| versions.empty? }
58
+
59
+ # If everything is already
60
+ solve_for = unsolved[0]
61
+ return desired_versions if !solve_for
62
+
63
+ # Go through each desired version of this cookbook, starting with the latest,
64
+ # until we find one we can solve successfully with
65
+ sort_versions(desired_versions[solve_for]).each do |desired_version|
66
+ new_desired_versions = desired_versions.clone
67
+ new_desired_versions[solve_for] = [ desired_version ]
68
+ new_unsolved = unsolved[1..-1]
69
+
70
+ # Pick this cookbook, and add dependencies
71
+ cookbook_obj = JSON.parse(cookbooks[solve_for][desired_version], :create_additions => false)
72
+ dep_not_found = false
73
+ cookbook_obj['metadata']['dependencies'].each_pair do |dep_name, dep_constraint|
74
+ # If the dep is not already in the list, add it to the list to solve
75
+ # and bring in all environment-allowed cookbook versions to desired_versions
76
+ if !new_desired_versions.has_key?(dep_name)
77
+ new_unsolved = new_unsolved + [dep_name]
78
+ # If the dep is missing, we will try other versions of the cookbook that might not have the bad dep.
79
+ if !cookbooks[dep_name]
80
+ dep_not_found = true
81
+ break
82
+ end
83
+ new_desired_versions[dep_name] = cookbooks[dep_name].keys
84
+ new_desired_versions = filter_by_constraint(new_desired_versions, dep_name, environment_constraints[dep_name])
85
+ end
86
+ new_desired_versions = filter_by_constraint(new_desired_versions, dep_name, dep_constraint)
87
+ end
88
+
89
+ next if dep_not_found
90
+
91
+ # Depsolve children with this desired version! First solution wins.
92
+ result = depsolve(new_unsolved, new_desired_versions, environment_constraints)
93
+ return result if result
94
+ end
95
+ return nil
96
+ end
97
+
98
+ def sort_versions(versions)
99
+ result = versions.sort_by { |version| Chef::Version.new(version) }
100
+ result.reverse
101
+ end
102
+
103
+ def filter_by_constraint(versions, cookbook_name, constraint)
104
+ return versions if !constraint
105
+ constraint = Chef::VersionConstraint.new(constraint)
106
+ new_versions = versions[cookbook_name]
107
+ new_versions = new_versions.select { |version| constraint.include?(version) }
108
+ result = versions.clone
109
+ result[cookbook_name] = new_versions
110
+ result
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,22 @@
1
+ require 'json'
2
+ require 'chef_zero/endpoints/cookbooks_base'
3
+
4
+ module ChefZero
5
+ module Endpoints
6
+ # /environments/NAME/cookbooks
7
+ class EnvironmentCookbooksEndpoint < CookbooksBase
8
+ def get(request)
9
+ environment = JSON.parse(get_data(request, request.rest_path[0..1]), :create_additions => false)
10
+ constraints = environment['cookbook_versions'] || {}
11
+ if request.query_params['num_versions'] == 'all'
12
+ num_versions = nil
13
+ elsif request.query_params['num_versions']
14
+ num_versions = request.query_params['num_versions'].to_i
15
+ else
16
+ num_versions = 1
17
+ end
18
+ json_response(200, format_cookbooks_list(request, data['cookbooks'], constraints, num_versions))
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,33 @@
1
+ require 'json'
2
+ require 'chef_zero/endpoints/rest_object_endpoint'
3
+ require 'chef_zero/data_normalizer'
4
+
5
+ module ChefZero
6
+ module Endpoints
7
+ # /environments/NAME
8
+ class EnvironmentEndpoint < RestObjectEndpoint
9
+ def delete(request)
10
+ if request.rest_path[1] == "_default"
11
+ # 405, really?
12
+ error(405, "The '_default' environment cannot be modified.")
13
+ else
14
+ super(request)
15
+ end
16
+ end
17
+
18
+ def put(request)
19
+ if request.rest_path[1] == "_default"
20
+ error(405, "The '_default' environment cannot be modified.")
21
+ else
22
+ super(request)
23
+ end
24
+ end
25
+
26
+ def populate_defaults(request, response_json)
27
+ response = JSON.parse(response_json, :create_additions => false)
28
+ response = DataNormalizer.normalize_environment(response, request.rest_path[1])
29
+ JSON.pretty_generate(response)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,23 @@
1
+ require 'json'
2
+ require 'chef_zero/rest_base'
3
+
4
+ module ChefZero
5
+ module Endpoints
6
+ # /environment/NAME/nodes
7
+ class EnvironmentNodesEndpoint < RestBase
8
+ def get(request)
9
+ # 404 if environment does not exist
10
+ get_data(request, request.rest_path[0..1])
11
+
12
+ result = {}
13
+ data['nodes'].each_pair do |name, node|
14
+ node_json = JSON.parse(node, :create_additions => false)
15
+ if node['chef_environment'] == request.rest_path[1]
16
+ result[name] = build_uri(request.base_uri, 'nodes', name)
17
+ end
18
+ end
19
+ json_response(200, result)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,22 @@
1
+ require 'json'
2
+ require 'chef_zero/endpoints/cookbooks_base'
3
+
4
+ module ChefZero
5
+ module Endpoints
6
+ # /environment/NAME/recipes
7
+ class EnvironmentRecipesEndpoint < CookbooksBase
8
+ def get(request)
9
+ environment = JSON.parse(get_data(request, request.rest_path[0..1]), :create_additions => false)
10
+ constraints = environment['cookbook_versions'] || {}
11
+ result = []
12
+ filter_cookbooks(data['cookbooks'], constraints, 1) do |name, versions|
13
+ if versions.size > 0
14
+ cookbook = JSON.parse(data['cookbooks'][name][versions[0]], :create_additions => false)
15
+ result += recipe_names(name, cookbook)
16
+ end
17
+ end
18
+ json_response(200, result.sort)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,35 @@
1
+ require 'json'
2
+ require 'chef_zero/endpoints/cookbooks_base'
3
+
4
+ module ChefZero
5
+ module Endpoints
6
+ # /environments/NAME/roles/NAME
7
+ # /roles/NAME/environments/NAME
8
+ class EnvironmentRoleEndpoint < CookbooksBase
9
+ def get(request)
10
+ # 404 if environment does not exist
11
+ if request.rest_path[0] == 'environments'
12
+ environment_path = request.rest_path[0..1]
13
+ role_path = request.rest_path[2..3]
14
+ else
15
+ environment_path = request.rest_path[2..3]
16
+ role_path = request.rest_path[0..1]
17
+ end
18
+ get_data(request, environment_path)
19
+
20
+ role = JSON.parse(get_data(request, role_path), :create_additions => false)
21
+ environment_name = environment_path[1]
22
+ if environment_name == '_default'
23
+ run_list = role['run_list']
24
+ else
25
+ if role['env_run_lists']
26
+ run_list = role['env_run_lists'][environment_name]
27
+ else
28
+ run_list = nil
29
+ end
30
+ end
31
+ json_response(200, { 'run_list' => run_list })
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,22 @@
1
+ require 'chef_zero/rest_base'
2
+
3
+ module ChefZero
4
+ module Endpoints
5
+ # The minimum amount of S3 necessary to support cookbook upload/download
6
+ # /file_store/FILE
7
+ class FileStoreFileEndpoint < RestBase
8
+ def json_only
9
+ false
10
+ end
11
+
12
+ def get(request)
13
+ [200, {"Content-Type" => 'application/x-binary'}, get_data(request) ]
14
+ end
15
+
16
+ def put(request)
17
+ data['file_store'][request.rest_path[1]] = request.body
18
+ json_response(200, {})
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ require 'json'
2
+ require 'chef_zero/endpoints/rest_object_endpoint'
3
+ require 'chef_zero/data_normalizer'
4
+
5
+ module ChefZero
6
+ module Endpoints
7
+ # /nodes/ID
8
+ class NodeEndpoint < RestObjectEndpoint
9
+ def populate_defaults(request, response_json)
10
+ node = JSON.parse(response_json, :create_additions => false)
11
+ node = DataNormalizer.normalize_node(node, request.rest_path[1])
12
+ JSON.pretty_generate(node)
13
+ end
14
+ end
15
+ end
16
+ end
17
+
@@ -0,0 +1,9 @@
1
+ module ChefZero
2
+ module Endpoints
3
+ class NotFoundEndpoint
4
+ def call(env)
5
+ return [404, {"Content-Type" => "application/json"}, "Object not found: #{env['REQUEST_PATH']}"]
6
+ end
7
+ end
8
+ end
9
+ end