chef-zero 1.4.0.alpha-x86-mingw32

Sign up to get free protection for your applications and to get access to all the features.
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,188 @@
1
+ require 'json'
2
+ require 'chef_zero/endpoints/rest_object_endpoint'
3
+ require 'chef_zero/data_normalizer'
4
+ require 'chef_zero/rest_error_response'
5
+ require 'chef_zero/solr/solr_parser'
6
+ require 'chef_zero/solr/solr_doc'
7
+
8
+ module ChefZero
9
+ module Endpoints
10
+ # /search/INDEX
11
+ class SearchEndpoint < RestBase
12
+ def get(request)
13
+ results = search(request)
14
+ results['rows'] = results['rows'].map { |name,uri,value,search_value| value }
15
+ json_response(200, results)
16
+ end
17
+
18
+ def post(request)
19
+ full_results = search(request)
20
+ keys = JSON.parse(request.body, :create_additions => false)
21
+ partial_results = full_results['rows'].map do |name, uri, doc, search_value|
22
+ data = {}
23
+ keys.each_pair do |key, path|
24
+ if path.size > 0
25
+ value = search_value
26
+ path.each do |path_part|
27
+ value = value[path_part] if !value.nil?
28
+ end
29
+ data[key] = value
30
+ else
31
+ data[key] = nil
32
+ end
33
+ end
34
+ {
35
+ 'url' => uri,
36
+ 'data' => data
37
+ }
38
+ end
39
+ json_response(200, {
40
+ 'rows' => partial_results,
41
+ 'start' => full_results['start'],
42
+ 'total' => full_results['total']
43
+ })
44
+ end
45
+
46
+ private
47
+
48
+ def search_container(request, index)
49
+ case index
50
+ when 'client'
51
+ [ ['clients'], Proc.new { |client, name| DataNormalizer.normalize_client(client, name) }, build_uri(request.base_uri, [ 'clients' ]) ]
52
+ when 'node'
53
+ [ ['nodes'], Proc.new { |node, name| DataNormalizer.normalize_node(node, name) }, build_uri(request.base_uri, [ 'nodes' ]) ]
54
+ when 'environment'
55
+ [ ['environments'], Proc.new { |environment, name| DataNormalizer.normalize_environment(environment, name) }, build_uri(request.base_uri, [ 'environments' ]) ]
56
+ when 'role'
57
+ [ ['roles'], Proc.new { |role, name| DataNormalizer.normalize_role(role, name) }, build_uri(request.base_uri, [ 'roles' ]) ]
58
+ else
59
+ [ ['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 ]) ]
60
+ end
61
+ end
62
+
63
+ def expand_for_indexing(value, index, id)
64
+ if index == 'node'
65
+ result = {}
66
+ deep_merge!(value['default'] || {}, result)
67
+ deep_merge!(value['normal'] || {}, result)
68
+ deep_merge!(value['override'] || {}, result)
69
+ deep_merge!(value['automatic'] || {}, result)
70
+ result['recipe'] = []
71
+ result['role'] = []
72
+ if value['run_list']
73
+ value['run_list'].each do |run_list_entry|
74
+ if run_list_entry =~ /^(recipe|role)\[(.*)\]/
75
+ result[$1] << $2
76
+ end
77
+ end
78
+ end
79
+ value.each_pair do |key, value|
80
+ result[key] = value unless %w(default normal override automatic).include?(key)
81
+ end
82
+ result
83
+
84
+ elsif !%w(client environment role).include?(index)
85
+ DataNormalizer.normalize_data_bag_item(value, index, id, 'GET')
86
+ else
87
+ value
88
+ end
89
+ end
90
+
91
+ def search(request)
92
+ # Extract parameters
93
+ index = request.rest_path[1]
94
+ query_string = request.query_params['q'] || '*:*'
95
+ solr_query = ChefZero::Solr::SolrParser.new(query_string).parse
96
+ sort_string = request.query_params['sort']
97
+ start = request.query_params['start']
98
+ start = start.to_i if start
99
+ rows = request.query_params['rows']
100
+ rows = rows.to_i if rows
101
+
102
+ # Get the search container
103
+ container, expander, base_uri = search_container(request, index)
104
+
105
+ # Search!
106
+ result = []
107
+ list_data(request, container).each do |name|
108
+ value = get_data(request, container + [name])
109
+ expanded = expander.call(JSON.parse(value, :create_additions => false), name)
110
+ result << [ name, build_uri(base_uri, [name]), expanded, expand_for_indexing(expanded, index, name) ]
111
+ end
112
+ result = result.select do |name, uri, value, search_value|
113
+ solr_query.matches_doc?(ChefZero::Solr::SolrDoc.new(search_value, name))
114
+ end
115
+ total = result.size
116
+
117
+ # Sort
118
+ if sort_string
119
+ sort_key, sort_order = sort_string.split(/\s+/, 2)
120
+ result = result.sort_by { |name,uri,value,search_value| ChefZero::Solr::SolrDoc.new(search_value, name)[sort_key] }
121
+ result = result.reverse if sort_order == "DESC"
122
+ end
123
+
124
+ # Paginate
125
+ if start
126
+ result = result[start..start+(rows||-1)]
127
+ end
128
+ {
129
+ 'rows' => result,
130
+ 'start' => start || 0,
131
+ 'total' => total
132
+ }
133
+ end
134
+
135
+ private
136
+
137
+ # Deep Merge core documentation.
138
+ # deep_merge! method permits merging of arbitrary child elements. The two top level
139
+ # elements must be hashes. These hashes can contain unlimited (to stack limit) levels
140
+ # of child elements. These child elements to not have to be of the same types.
141
+ # Where child elements are of the same type, deep_merge will attempt to merge them together.
142
+ # Where child elements are not of the same type, deep_merge will skip or optionally overwrite
143
+ # the destination element with the contents of the source element at that level.
144
+ # So if you have two hashes like this:
145
+ # source = {:x => [1,2,3], :y => 2}
146
+ # dest = {:x => [4,5,'6'], :y => [7,8,9]}
147
+ # dest.deep_merge!(source)
148
+ # Results: {:x => [1,2,3,4,5,'6'], :y => 2}
149
+ # By default, "deep_merge!" will overwrite any unmergeables and merge everything else.
150
+ # To avoid this, use "deep_merge" (no bang/exclamation mark)
151
+ def deep_merge!(source, dest)
152
+ # if dest doesn't exist, then simply copy source to it
153
+ if dest.nil?
154
+ dest = source; return dest
155
+ end
156
+
157
+ case source
158
+ when nil
159
+ dest
160
+ when Hash
161
+ source.each do |src_key, src_value|
162
+ if dest.kind_of?(Hash)
163
+ if dest[src_key]
164
+ dest[src_key] = deep_merge!(src_value, dest[src_key])
165
+ else # dest[src_key] doesn't exist so we take whatever source has
166
+ dest[src_key] = src_value
167
+ end
168
+ else # dest isn't a hash, so we overwrite it completely
169
+ dest = source
170
+ end
171
+ end
172
+ when Array
173
+ if dest.kind_of?(Array)
174
+ dest = dest | source
175
+ else
176
+ dest = source
177
+ end
178
+ when String
179
+ dest = source
180
+ else # src_hash is not an array or hash, so we'll have to overwrite dest
181
+ dest = source
182
+ end
183
+ dest
184
+ end # deep_merge!
185
+
186
+ end
187
+ end
188
+ 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_store.list(['data'])).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,7 @@
1
+ require 'mixlib/log'
2
+
3
+ module ChefZero
4
+ class Log
5
+ extend Mixlib::Log
6
+ end
7
+ end
@@ -0,0 +1,133 @@
1
+ require 'chef_zero/rest_request'
2
+ require 'chef_zero/rest_error_response'
3
+ require 'chef_zero/data_store/data_not_found_error'
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_store
14
+ server.data_store
15
+ end
16
+
17
+ def call(request)
18
+ method = request.method.downcase.to_sym
19
+ if !self.respond_to?(method)
20
+ accept_methods = [:get, :put, :post, :delete].select { |m| self.respond_to?(m) }
21
+ accept_methods_str = accept_methods.map { |m| m.to_s.upcase }.join(', ')
22
+ return [405, {"Content-Type" => "text/plain", "Allow" => accept_methods_str}, "Bad request method for '#{request.env['REQUEST_PATH']}': #{request.env['REQUEST_METHOD']}"]
23
+ end
24
+ if json_only && request.env['HTTP_ACCEPT'] && !request.env['HTTP_ACCEPT'].split(';').include?('application/json')
25
+ return [406, {"Content-Type" => "text/plain"}, "Must accept application/json"]
26
+ end
27
+ # Dispatch to get()/post()/put()/delete()
28
+ begin
29
+ self.send(method, request)
30
+ rescue RestErrorResponse => e
31
+ error(e.response_code, e.error)
32
+ end
33
+ end
34
+
35
+ def json_only
36
+ true
37
+ end
38
+
39
+ def get_data(request, rest_path=nil, *options)
40
+ rest_path ||= request.rest_path
41
+ begin
42
+ data_store.get(rest_path, request)
43
+ rescue DataStore::DataNotFoundError
44
+ if options.include?(:nil)
45
+ nil
46
+ else
47
+ raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, rest_path)}")
48
+ end
49
+ end
50
+ end
51
+
52
+ def list_data(request, rest_path=nil)
53
+ rest_path ||= request.rest_path
54
+ begin
55
+ data_store.list(rest_path)
56
+ rescue DataStore::DataNotFoundError
57
+ raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, rest_path)}")
58
+ end
59
+ end
60
+
61
+ def delete_data(request, rest_path=nil)
62
+ rest_path ||= request.rest_path
63
+ begin
64
+ data_store.delete(rest_path)
65
+ rescue DataStore::DataNotFoundError
66
+ raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, request.rest_path)}")
67
+ end
68
+ end
69
+
70
+ def delete_data_dir(request, rest_path, *options)
71
+ rest_path ||= request.rest_path
72
+ begin
73
+ data_store.delete_dir(rest_path)
74
+ rescue DataStore::DataNotFoundError
75
+ raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, request.rest_path)}")
76
+ end
77
+ end
78
+
79
+ def set_data(request, rest_path, data, *options)
80
+ rest_path ||= request.rest_path
81
+ begin
82
+ data_store.set(rest_path, request.body, *options)
83
+ rescue DataStore::DataNotFoundError
84
+ raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, request.rest_path)}")
85
+ end
86
+ end
87
+
88
+ def create_data(request, rest_path, name, data, *options)
89
+ rest_path ||= request.rest_path
90
+ begin
91
+ data_store.create(rest_path, name, data, *options)
92
+ rescue DataStore::DataNotFoundError
93
+ raise RestErrorResponse.new(404, "Parent not found: #{build_uri(request.base_uri, request.rest_path)}")
94
+ rescue DataStore::DataAlreadyExistsError
95
+ raise RestErrorResponse.new(409, "Object already exists: #{build_uri(request.base_uri, request.rest_path + [name])}")
96
+ end
97
+ end
98
+
99
+ def exists_data?(request, rest_path=nil)
100
+ rest_path ||= request.rest_path
101
+ data_store.exists?(rest_path)
102
+ end
103
+
104
+ def exists_data_dir?(request, rest_path=nil)
105
+ rest_path ||= request.rest_path
106
+ data_store.exists_dir?(rest_path)
107
+ end
108
+
109
+ def error(response_code, error)
110
+ json_response(response_code, {"error" => [error]})
111
+ end
112
+
113
+ def json_response(response_code, json)
114
+ already_json_response(response_code, JSON.pretty_generate(json))
115
+ end
116
+
117
+ def already_json_response(response_code, json_text)
118
+ [response_code, {"Content-Type" => "application/json"}, json_text]
119
+ end
120
+
121
+ def build_uri(base_uri, rest_path)
122
+ RestBase::build_uri(base_uri, rest_path)
123
+ end
124
+
125
+ def self.build_uri(base_uri, rest_path)
126
+ "#{base_uri}/#{rest_path.join('/')}"
127
+ end
128
+
129
+ def populate_defaults(request, response)
130
+ response
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,11 @@
1
+ module ChefZero
2
+ class RestErrorResponse < Exception
3
+ def initialize(response_code, error)
4
+ @response_code = response_code
5
+ @error = error
6
+ end
7
+
8
+ attr_reader :response_code
9
+ attr_reader :error
10
+ end
11
+ end
@@ -0,0 +1,56 @@
1
+ require 'rack/request'
2
+
3
+ module ChefZero
4
+ class RestRequest
5
+ def initialize(env)
6
+ @env = env
7
+ end
8
+
9
+ attr_reader :env
10
+
11
+ def base_uri
12
+ @base_uri ||= "#{env['rack.url_scheme']}://#{env['HTTP_HOST']}#{env['SCRIPT_NAME']}"
13
+ end
14
+
15
+ def method
16
+ @env['REQUEST_METHOD']
17
+ end
18
+
19
+ def rest_path
20
+ @rest_path ||= env['PATH_INFO'].split('/').select { |part| part != "" }
21
+ end
22
+
23
+ def body=(body)
24
+ @body = body
25
+ end
26
+
27
+ def body
28
+ @body ||= env['rack.input'].read
29
+ end
30
+
31
+ def query_params
32
+ @query_params ||= begin
33
+ params = Rack::Request.new(env).GET
34
+ params.keys.each do |key|
35
+ params[key] = URI.unescape(params[key])
36
+ end
37
+ params
38
+ end
39
+ end
40
+
41
+ def to_s
42
+ result = "#{method} #{rest_path.join('/')}"
43
+ if query_params.size > 0
44
+ result << "?#{query_params.map { |k,v| "#{k}=#{v}" }.join('&') }"
45
+ end
46
+ if body.chomp != ''
47
+ result << "\n--- #{method} BODY ---\n"
48
+ result << body
49
+ result << "\n" if !body.end_with?("\n")
50
+ result << "--- END #{method} BODY ---"
51
+ end
52
+ result
53
+ end
54
+ end
55
+ end
56
+
@@ -0,0 +1,44 @@
1
+ module ChefZero
2
+ class RestRouter
3
+ def initialize(routes)
4
+ @routes = routes.map do |route, endpoint|
5
+ if route =~ /\*\*$/
6
+ pattern = Regexp.new("^#{route[0..-3].gsub('*', '[^/]*')}")
7
+ else
8
+ pattern = Regexp.new("^#{route.gsub('*', '[^/]*')}$")
9
+ end
10
+ [ pattern, endpoint ]
11
+ end
12
+ end
13
+
14
+ attr_reader :routes
15
+ attr_accessor :not_found
16
+
17
+ def call(request)
18
+ begin
19
+ ChefZero::Log.debug(request)
20
+
21
+ clean_path = "/" + request.rest_path.join("/")
22
+
23
+ response = find_endpoint(clean_path).call(request)
24
+ ChefZero::Log.debug([
25
+ "",
26
+ "--- RESPONSE (#{response[0]}) ---",
27
+ response[2],
28
+ "--- END RESPONSE ---",
29
+ ].join("\n"))
30
+ return response
31
+ rescue
32
+ ChefZero::Log.error("#{$!.inspect}\n#{$!.backtrace.join("\n")}")
33
+ [500, {"Content-Type" => "text/plain"}, "Exception raised! #{$!.inspect}\n#{$!.backtrace.join("\n")}"]
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def find_endpoint(clean_path)
40
+ _, endpoint = routes.find { |route, endpoint| route.match(clean_path) }
41
+ endpoint || not_found
42
+ end
43
+ end
44
+ end