chef-zero 0.9

Sign up to get free protection for your applications and to get access to all the features.
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,30 @@
1
+ require 'json'
2
+ require 'chef_zero'
3
+ require 'chef_zero/rest_base'
4
+
5
+ module ChefZero
6
+ module Endpoints
7
+ # /principals/NAME
8
+ class PrincipalEndpoint < RestBase
9
+ def get(request)
10
+ name = request.rest_path[-1]
11
+ json = data['users'][name]
12
+ if json
13
+ type = 'user'
14
+ else
15
+ json = data['clients'][name]
16
+ type = 'client'
17
+ end
18
+ if json
19
+ json_response(200, {
20
+ 'name' => name,
21
+ 'type' => type,
22
+ 'public_key' => JSON.parse(json)['public_key'] || PUBLIC_KEY
23
+ })
24
+ else
25
+ error(404, 'Principal not found')
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,41 @@
1
+ require 'json'
2
+ require 'chef_zero/rest_base'
3
+
4
+ module ChefZero
5
+ module Endpoints
6
+ # Typical REST list endpoint (/roles or /data/BAG)
7
+ class RestListEndpoint < RestBase
8
+ def initialize(server, identity_key = 'name')
9
+ super(server)
10
+ @identity_key = identity_key
11
+ end
12
+
13
+ attr_reader :identity_key
14
+
15
+ def get(request)
16
+ # Get the result
17
+ result_hash = {}
18
+ get_data(request).keys.sort.each do |name|
19
+ result_hash[name] = "#{build_uri(request.base_uri, request.rest_path + [name])}"
20
+ end
21
+ json_response(200, result_hash)
22
+ end
23
+
24
+ def post(request)
25
+ container = get_data(request)
26
+ contents = request.body
27
+ key = get_key(contents)
28
+ if container[key]
29
+ error(409, 'Object already exists')
30
+ else
31
+ container[key] = contents
32
+ json_response(201, {'uri' => "#{build_uri(request.base_uri, request.rest_path + [key])}"})
33
+ end
34
+ end
35
+
36
+ def get_key(contents)
37
+ JSON.parse(contents, :create_additions => false)[identity_key]
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,65 @@
1
+ require 'json'
2
+ require 'chef_zero/rest_base'
3
+ require 'chef_zero/rest_error_response'
4
+
5
+ module ChefZero
6
+ module Endpoints
7
+ # Typical REST leaf endpoint (/roles/NAME or /data/BAG/NAME)
8
+ class RestObjectEndpoint < RestBase
9
+ def initialize(server, identity_key = 'name')
10
+ super(server)
11
+ @identity_key = identity_key
12
+ end
13
+
14
+ attr_reader :identity_key
15
+
16
+ def get(request)
17
+ already_json_response(200, populate_defaults(request, get_data(request)))
18
+ end
19
+
20
+ def put(request)
21
+ # We grab the old body to trigger a 404 if it doesn't exist
22
+ old_body = get_data(request)
23
+ request_json = JSON.parse(request.body, :create_additions => false)
24
+ key = request_json[identity_key] || request.rest_path[-1]
25
+ container = get_data(request, request.rest_path[0..-2])
26
+ # If it's a rename, check for conflict and delete the old value
27
+ rename = key != request.rest_path[-1]
28
+ if rename
29
+ if container.has_key?(key)
30
+ return error(409, "Cannot rename '#{request.rest_path[-1]}' to '#{key}': '#{key}' already exists")
31
+ end
32
+ container.delete(request.rest_path[-1])
33
+ end
34
+ container[key] = request.body
35
+ already_json_response(200, populate_defaults(request, request.body))
36
+ end
37
+
38
+ def delete(request)
39
+ key = request.rest_path[-1]
40
+ container = get_data(request, request.rest_path[0..-2])
41
+ if !container.has_key?(key)
42
+ raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, request.rest_path)}")
43
+ end
44
+ result = container[key]
45
+ container.delete(key)
46
+ already_json_response(200, populate_defaults(request, result))
47
+ end
48
+
49
+ def patch_request_body(request)
50
+ container = get_data(request, request.rest_path[0..-2])
51
+ existing_value = container[request.rest_path[-1]]
52
+ if existing_value
53
+ request_json = JSON.parse(request.body, :create_additions => false)
54
+ existing_json = JSON.parse(existing_value, :create_additions => false)
55
+ merged_json = existing_json.merge(request_json)
56
+ if merged_json.size > request_json.size
57
+ return JSON.pretty_generate(merged_json)
58
+ end
59
+ end
60
+ request.body
61
+ end
62
+ end
63
+ end
64
+ end
65
+
@@ -0,0 +1,16 @@
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
+ # /roles/NAME
8
+ class RoleEndpoint < RestObjectEndpoint
9
+ def populate_defaults(request, response_json)
10
+ role = JSON.parse(response_json, :create_additions => false)
11
+ role = DataNormalizer.normalize_role(role, request.rest_path[1])
12
+ JSON.pretty_generate(role)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,14 @@
1
+ require 'json'
2
+ require 'chef_zero/rest_base'
3
+
4
+ module ChefZero
5
+ module Endpoints
6
+ # /roles/NAME/environments
7
+ class RoleEnvironmentsEndpoint < RestBase
8
+ def get(request)
9
+ role = JSON.parse(get_data(request, request.rest_path[0..1]), :create_additions => false)
10
+ json_response(200, [ '_default' ] + (role['env_run_lists'].keys || []))
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,22 @@
1
+ require 'chef_zero/rest_base'
2
+
3
+ module ChefZero
4
+ module Endpoints
5
+ # /sandboxes/ID
6
+ class SandboxEndpoint < RestBase
7
+ def put(request)
8
+ existing_sandbox = get_data(request, request.rest_path)
9
+ data['sandboxes'].delete(request.rest_path[1])
10
+ time_str = existing_sandbox[:create_time].strftime('%Y-%m-%dT%H:%M:%S%z')
11
+ time_str = "#{time_str[0..21]}:#{time_str[22..23]}"
12
+ json_response(200, {
13
+ :guid => request.rest_path[1],
14
+ :name => request.rest_path[1],
15
+ :checksums => existing_sandbox[:checksums],
16
+ :create_time => time_str,
17
+ :is_completed => true
18
+ })
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,44 @@
1
+ require 'json'
2
+ require 'chef_zero/rest_base'
3
+
4
+ module ChefZero
5
+ module Endpoints
6
+ # /sandboxes
7
+ class SandboxesEndpoint < RestBase
8
+ def initialize(server)
9
+ super(server)
10
+ @next_id = 1
11
+ end
12
+
13
+ def post(request)
14
+ sandbox_checksums = []
15
+
16
+ needed_checksums = JSON.parse(request.body, :create_additions => false)['checksums']
17
+ result_checksums = {}
18
+ needed_checksums.keys.each do |needed_checksum|
19
+ if data['file_store'].has_key?(needed_checksum)
20
+ result_checksums[needed_checksum] = { :needs_upload => false }
21
+ else
22
+ result_checksums[needed_checksum] = {
23
+ :needs_upload => true,
24
+ :url => build_uri(request.base_uri, ['file_store', needed_checksum])
25
+ }
26
+ sandbox_checksums << needed_checksum
27
+ end
28
+ end
29
+
30
+ id = @next_id.to_s
31
+ @next_id+=1
32
+
33
+ data['sandboxes'][id] = { :create_time => Time.now.utc, :checksums => sandbox_checksums }
34
+
35
+ json_response(201, {
36
+ :uri => build_uri(request.base_uri, request.rest_path + [id.to_s]),
37
+ :checksums => result_checksums,
38
+ :sandbox_id => id
39
+ })
40
+ end
41
+ end
42
+ end
43
+ end
44
+
@@ -0,0 +1,139 @@
1
+ require 'json'
2
+ require 'chef/mixin/deep_merge'
3
+ require 'chef_zero/endpoints/rest_object_endpoint'
4
+ require 'chef_zero/data_normalizer'
5
+ require 'chef_zero/rest_error_response'
6
+ require 'chef_zero/solr/solr_parser'
7
+ require 'chef_zero/solr/solr_doc'
8
+
9
+ module ChefZero
10
+ module Endpoints
11
+ # /search/INDEX
12
+ class SearchEndpoint < RestBase
13
+ def get(request)
14
+ results = search(request)
15
+ results['rows'] = results['rows'].map { |name,uri,value,search_value| value }
16
+ json_response(200, results)
17
+ end
18
+
19
+ def post(request)
20
+ full_results = search(request)
21
+ keys = JSON.parse(request.body, :create_additions => false)
22
+ partial_results = full_results['rows'].map do |name, uri, doc, search_value|
23
+ data = {}
24
+ keys.each_pair do |key, path|
25
+ if path.size > 0
26
+ value = search_value
27
+ path.each do |path_part|
28
+ value = value[path_part] if !value.nil?
29
+ end
30
+ data[key] = value
31
+ else
32
+ data[key] = nil
33
+ end
34
+ end
35
+ {
36
+ 'url' => uri,
37
+ 'data' => data
38
+ }
39
+ end
40
+ json_response(200, {
41
+ 'rows' => partial_results,
42
+ 'start' => full_results['start'],
43
+ 'total' => full_results['total']
44
+ })
45
+ end
46
+
47
+ private
48
+
49
+ def search_container(request, index)
50
+ case index
51
+ when 'client'
52
+ [ data['clients'], Proc.new { |client, name| DataNormalizer.normalize_client(client, name) }, build_uri(request.base_uri, [ 'clients' ]) ]
53
+ when 'node'
54
+ [ data['nodes'], Proc.new { |node, name| DataNormalizer.normalize_node(node, name) }, build_uri(request.base_uri, [ 'nodes' ]) ]
55
+ when 'environment'
56
+ [ data['environments'], Proc.new { |environment, name| DataNormalizer.normalize_environment(environment, name) }, build_uri(request.base_uri, [ 'environments' ]) ]
57
+ when 'role'
58
+ [ data['roles'], Proc.new { |role, name| DataNormalizer.normalize_role(role, name) }, build_uri(request.base_uri, [ 'roles' ]) ]
59
+ else
60
+ [ data['data'][index], Proc.new { |data_bag_item, id| DataNormalizer.normalize_data_bag_item(data_bag_item, index, id, 'DELETE') }, build_uri(request.base_uri, [ 'data', index ]) ]
61
+ end
62
+ end
63
+
64
+ def expand_for_indexing(value, index, id)
65
+ if index == 'node'
66
+ result = {}
67
+ Chef::Mixin::DeepMerge.deep_merge!(value['default'] || {}, result)
68
+ Chef::Mixin::DeepMerge.deep_merge!(value['normal'] || {}, result)
69
+ Chef::Mixin::DeepMerge.deep_merge!(value['override'] || {}, result)
70
+ Chef::Mixin::DeepMerge.deep_merge!(value['automatic'] || {}, result)
71
+ result['recipe'] = []
72
+ result['role'] = []
73
+ if value['run_list']
74
+ value['run_list'].each do |run_list_entry|
75
+ if run_list_entry =~ /^(recipe|role)\[(.*)\]/
76
+ result[$1] << $2
77
+ end
78
+ end
79
+ end
80
+ value.each_pair do |key, value|
81
+ result[key] = value unless %w(default normal override automatic).include?(key)
82
+ end
83
+ result
84
+
85
+ elsif !%w(client environment role).include?(index)
86
+ DataNormalizer.normalize_data_bag_item(value, index, id, 'GET')
87
+ else
88
+ value
89
+ end
90
+ end
91
+
92
+ def search(request)
93
+ # Extract parameters
94
+ index = request.rest_path[1]
95
+ query_string = request.query_params['q'] || '*:*'
96
+ solr_query = ChefZero::Solr::SolrParser.new(query_string).parse
97
+ sort_string = request.query_params['sort']
98
+ start = request.query_params['start']
99
+ start = start.to_i if start
100
+ rows = request.query_params['rows']
101
+ rows = rows.to_i if rows
102
+
103
+ # Get the search container
104
+ container, expander, base_uri = search_container(request, index)
105
+ if container.nil?
106
+ raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, request.rest_path)}")
107
+ end
108
+
109
+ # Search!
110
+ result = []
111
+ container.each_pair do |name,value|
112
+ expanded = expander.call(JSON.parse(value, :create_additions => false), name)
113
+ result << [ name, build_uri(base_uri, [name]), expanded, expand_for_indexing(expanded, index, name) ]
114
+ end
115
+ result = result.select do |name, uri, value, search_value|
116
+ solr_query.matches_doc?(ChefZero::Solr::SolrDoc.new(search_value, name))
117
+ end
118
+ total = result.size
119
+
120
+ # Sort
121
+ if sort_string
122
+ sort_key, sort_order = sort_string.split(/\s+/, 2)
123
+ result = result.sort_by { |name,uri,value,search_value| ChefZero::Solr::SolrDoc.new(search_value, name)[sort_key] }
124
+ result = result.reverse if sort_order == "DESC"
125
+ end
126
+
127
+ # Paginate
128
+ if start
129
+ result = result[start..start+(rows||-1)]
130
+ end
131
+ {
132
+ 'rows' => result,
133
+ 'start' => start || 0,
134
+ 'total' => total
135
+ }
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,18 @@
1
+ require 'chef_zero/rest_base'
2
+
3
+ module ChefZero
4
+ module Endpoints
5
+ # /search
6
+ class SearchesEndpoint < RestBase
7
+ def get(request)
8
+ # Get the result
9
+ result_hash = {}
10
+ indices = (%w(client environment node role) + data['data'].keys).sort
11
+ indices.each do |index|
12
+ result_hash[index] = build_uri(request.base_uri, request.rest_path + [index])
13
+ end
14
+ json_response(200, result_hash)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,82 @@
1
+ require 'chef_zero/rest_request'
2
+ require 'chef_zero/rest_error_response'
3
+ require 'chef/log'
4
+
5
+ module ChefZero
6
+ class RestBase
7
+ def initialize(server)
8
+ @server = server
9
+ end
10
+
11
+ attr_reader :server
12
+
13
+ def data
14
+ server.data
15
+ end
16
+
17
+ def call(env)
18
+ begin
19
+ rest_path = env['PATH_INFO'].split('/').select { |part| part != "" }
20
+ method = env['REQUEST_METHOD'].downcase.to_sym
21
+ if !self.respond_to?(method)
22
+ accept_methods = [:get, :put, :post, :delete].select { |m| self.respond_to?(m) }
23
+ accept_methods_str = accept_methods.map { |m| m.to_s.upcase }.join(', ')
24
+ return [405, {"Content-Type" => "text/plain", "Allow" => accept_methods_str}, "Bad request method for '#{env['REQUEST_PATH']}': #{env['REQUEST_METHOD']}"]
25
+ end
26
+ if json_only && !env['HTTP_ACCEPT'].split(';').include?('application/json')
27
+ return [406, {"Content-Type" => "text/plain"}, "Must accept application/json"]
28
+ end
29
+ # Dispatch to get()/post()/put()/delete()
30
+ begin
31
+ self.send(method, RestRequest.new(env))
32
+ rescue RestErrorResponse => e
33
+ error(e.response_code, e.error)
34
+ end
35
+ rescue
36
+ Chef::Log.error("#{$!.inspect}\n#{$!.backtrace.join("\n")}")
37
+ raise
38
+ end
39
+ end
40
+
41
+ def json_only
42
+ true
43
+ end
44
+
45
+ def get_data(request, rest_path=nil)
46
+ rest_path ||= request.rest_path
47
+ # Grab the value we're looking for
48
+ value = data
49
+ rest_path.each do |path_part|
50
+ if !value.has_key?(path_part)
51
+ raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, rest_path)}")
52
+ end
53
+ value = value[path_part]
54
+ end
55
+ value
56
+ end
57
+
58
+ def error(response_code, error)
59
+ json_response(response_code, {"error" => [error]})
60
+ end
61
+
62
+ def json_response(response_code, json)
63
+ already_json_response(response_code, JSON.pretty_generate(json))
64
+ end
65
+
66
+ def already_json_response(response_code, json_text)
67
+ [response_code, {"Content-Type" => "application/json"}, json_text]
68
+ end
69
+
70
+ def build_uri(base_uri, rest_path)
71
+ RestBase::build_uri(base_uri, rest_path)
72
+ end
73
+
74
+ def self.build_uri(base_uri, rest_path)
75
+ "#{base_uri}/#{rest_path.join('/')}"
76
+ end
77
+
78
+ def populate_defaults(request, response)
79
+ response
80
+ end
81
+ end
82
+ end