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,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,42 @@
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
+ end
41
+ end
42
+
@@ -0,0 +1,26 @@
1
+ require 'chef/log'
2
+
3
+ module ChefZero
4
+ class Router
5
+ def initialize(routes)
6
+ @routes = routes.map do |route, endpoint|
7
+ pattern = Regexp.new("^#{route.gsub('*', '[^/]*')}$")
8
+ [ pattern, endpoint ]
9
+ end
10
+ end
11
+
12
+ attr_reader :routes
13
+ attr_accessor :not_found
14
+
15
+ def call(env)
16
+ Chef::Log.debug "#{env['REQUEST_METHOD']} #{env['PATH_INFO']}#{env['QUERY_STRING'] != '' ? "?" + env['QUERY_STRING'] : ''}"
17
+ clean_path = "/" + env['PATH_INFO'].split('/').select { |part| part != "" }.join("/")
18
+ routes.each do |route, endpoint|
19
+ if route.match(clean_path)
20
+ return endpoint.call(env)
21
+ end
22
+ end
23
+ not_found.call(env)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,255 @@
1
+ #
2
+ # Author:: John Keiser (<jkeiser@opscode.com>)
3
+ # Copyright:: Copyright (c) 2012 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'rubygems'
20
+ require 'thin'
21
+ require 'openssl'
22
+ require 'chef_zero'
23
+ require 'chef_zero/router'
24
+ require 'timeout'
25
+ require 'chef_zero/cookbook_data'
26
+
27
+ require 'chef_zero/endpoints/authenticate_user_endpoint'
28
+ require 'chef_zero/endpoints/actors_endpoint'
29
+ require 'chef_zero/endpoints/actor_endpoint'
30
+ require 'chef_zero/endpoints/cookbooks_endpoint'
31
+ require 'chef_zero/endpoints/cookbook_endpoint'
32
+ require 'chef_zero/endpoints/cookbook_version_endpoint'
33
+ require 'chef_zero/endpoints/data_bags_endpoint'
34
+ require 'chef_zero/endpoints/data_bag_endpoint'
35
+ require 'chef_zero/endpoints/data_bag_item_endpoint'
36
+ require 'chef_zero/endpoints/rest_list_endpoint'
37
+ require 'chef_zero/endpoints/environment_endpoint'
38
+ require 'chef_zero/endpoints/environment_cookbooks_endpoint'
39
+ require 'chef_zero/endpoints/environment_cookbook_endpoint'
40
+ require 'chef_zero/endpoints/environment_cookbook_versions_endpoint'
41
+ require 'chef_zero/endpoints/environment_nodes_endpoint'
42
+ require 'chef_zero/endpoints/environment_recipes_endpoint'
43
+ require 'chef_zero/endpoints/environment_role_endpoint'
44
+ require 'chef_zero/endpoints/node_endpoint'
45
+ require 'chef_zero/endpoints/principal_endpoint'
46
+ require 'chef_zero/endpoints/role_endpoint'
47
+ require 'chef_zero/endpoints/role_environments_endpoint'
48
+ require 'chef_zero/endpoints/sandboxes_endpoint'
49
+ require 'chef_zero/endpoints/sandbox_endpoint'
50
+ require 'chef_zero/endpoints/searches_endpoint'
51
+ require 'chef_zero/endpoints/search_endpoint'
52
+ require 'chef_zero/endpoints/file_store_file_endpoint'
53
+ require 'chef_zero/endpoints/not_found_endpoint'
54
+
55
+ module ChefZero
56
+ class Server
57
+ def initialize(options = {})
58
+ @options = options
59
+ options[:host] ||= '127.0.0.1'
60
+ options[:port] ||= 80
61
+ options[:generate_real_keys] = true if !options.has_key?(:generate_real_keys)
62
+ @server = Thin::Server.new(options[:host], options[:port], make_app)
63
+ @generate_real_keys = options[:generate_real_keys]
64
+ @data = {
65
+ 'clients' => {
66
+ 'chef-validator' => '{ "validator": true }',
67
+ 'chef-webui' => '{ "admin": true }'
68
+ },
69
+ 'cookbooks' => {},
70
+ 'data' => {},
71
+ 'environments' => {
72
+ '_default' => '{ "description": "The default Chef environment" }'
73
+ },
74
+ 'file_store' => {},
75
+ 'nodes' => {},
76
+ 'roles' => {},
77
+ 'sandboxes' => {},
78
+ 'users' => {
79
+ 'admin' => '{ "admin": true }'
80
+ }
81
+ }
82
+ end
83
+
84
+ attr_reader :server
85
+ attr_reader :data
86
+ attr_reader :options
87
+ attr_reader :generate_real_keys
88
+
89
+ include ChefZero::Endpoints
90
+
91
+ def url
92
+ "http://#{options[:host]}:#{options[:port]}"
93
+ end
94
+
95
+ def start
96
+ server.start
97
+ end
98
+
99
+ def start_background(timeout = 5)
100
+ @thread = Thread.new do
101
+ begin
102
+ server.start
103
+ rescue
104
+ @server_error = $!
105
+ Chef::Log.error("#{$!.message}\n#{$!.backtrace.join("\n")}")
106
+ end
107
+ end
108
+ Timeout::timeout(timeout) do
109
+ until server.running? || @server_error
110
+ sleep(0.01)
111
+ end
112
+ end
113
+ end
114
+
115
+ def running?
116
+ server.running?
117
+ end
118
+
119
+ def stop(timeout = 5)
120
+ Timeout::timeout(timeout) do
121
+ server.stop
122
+ until !server.running? || !server_error
123
+ sleep(0.01)
124
+ end
125
+ end
126
+ if @thread
127
+ @thread.kill
128
+ @thread = nil
129
+ end
130
+ end
131
+
132
+ def gen_key_pair
133
+ if generate_real_keys
134
+ private_key = OpenSSL::PKey::RSA.new(2048)
135
+ public_key = private_key.public_key.to_s
136
+ public_key.sub!(/^-----BEGIN RSA PUBLIC KEY-----/, '-----BEGIN PUBLIC KEY-----')
137
+ public_key.sub!(/-----END RSA PUBLIC KEY-----(\s+)$/, '-----END PUBLIC KEY-----\1')
138
+ [private_key.to_s, public_key]
139
+ else
140
+ [PRIVATE_KEY, PUBLIC_KEY]
141
+ end
142
+ end
143
+
144
+ # Load data in a nice, friendly form:
145
+ # {
146
+ # 'roles' => {
147
+ # 'desert' => '{ "description": "Hot and dry"' },
148
+ # 'rainforest' => { "description" => 'Wet and humid' }
149
+ # },
150
+ # 'cookbooks' => {
151
+ # 'apache2-1.0.1' => {
152
+ # 'templates' => { 'default' => { 'blah.txt' => 'hi' }}
153
+ # 'recipes' => { 'default.rb' => 'template "blah.txt"' }
154
+ # 'metadata.rb' => 'depends "mysql"'
155
+ # },
156
+ # 'apache2-1.2.0' => {
157
+ # 'templates' => { 'default' => { 'blah.txt' => 'lo' }}
158
+ # 'recipes' => { 'default.rb' => 'template "blah.txt"' }
159
+ # 'metadata.rb' => 'depends "mysql"'
160
+ # },
161
+ # 'mysql' => {
162
+ # 'recipes' => { 'default.rb' => 'file { contents "hi" }' },
163
+ # 'metadata.rb' => 'version "1.0.0"'
164
+ # }
165
+ # }
166
+ # }
167
+ def load_data(contents)
168
+ %w(clients environments nodes roles users).each do |data_type|
169
+ if contents[data_type]
170
+ dejsonize_children!(contents[data_type])
171
+ data[data_type].merge!(contents[data_type])
172
+ end
173
+ end
174
+ if contents['data']
175
+ contents['data'].values.each do |data_bag|
176
+ dejsonize_children!(data_bag)
177
+ end
178
+ data['data'].merge!(contents['data'])
179
+ end
180
+ if contents['cookbooks']
181
+ contents['cookbooks'].each_pair do |name_version, cookbook|
182
+ if name_version =~ /(.+)-(\d+\.\d+\.\d+)$/
183
+ cookbook_data = CookbookData.to_hash(cookbook, $1, $2)
184
+ else
185
+ cookbook_data = CookbookData.to_hash(cookbook, name_version)
186
+ end
187
+ raise "No version specified" if !cookbook_data[:version]
188
+ data['cookbooks'][cookbook_data[:cookbook_name]] = {} if !data['cookbooks'][cookbook_data[:cookbook_name]]
189
+ data['cookbooks'][cookbook_data[:cookbook_name]][cookbook_data[:version]] = JSON.pretty_generate(cookbook_data)
190
+ cookbook_data.values.each do |files|
191
+ next unless files.is_a? Array
192
+ files.each do |file|
193
+ data['file_store'][file[:checksum]] = get_file(cookbook, file[:path])
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end
199
+
200
+ private
201
+
202
+ def make_app
203
+ router = Router.new([
204
+ [ '/authenticate_user', AuthenticateUserEndpoint.new(self) ],
205
+ [ '/clients', ActorsEndpoint.new(self) ],
206
+ [ '/clients/*', ActorEndpoint.new(self) ],
207
+ [ '/cookbooks', CookbooksEndpoint.new(self) ],
208
+ [ '/cookbooks/*', CookbookEndpoint.new(self) ],
209
+ [ '/cookbooks/*/*', CookbookVersionEndpoint.new(self) ],
210
+ [ '/data', DataBagsEndpoint.new(self) ],
211
+ [ '/data/*', DataBagEndpoint.new(self) ],
212
+ [ '/data/*/*', DataBagItemEndpoint.new(self) ],
213
+ [ '/environments', RestListEndpoint.new(self) ],
214
+ [ '/environments/*', EnvironmentEndpoint.new(self) ],
215
+ [ '/environments/*/cookbooks', EnvironmentCookbooksEndpoint.new(self) ],
216
+ [ '/environments/*/cookbooks/*', EnvironmentCookbookEndpoint.new(self) ],
217
+ [ '/environments/*/cookbook_versions', EnvironmentCookbookVersionsEndpoint.new(self) ],
218
+ [ '/environments/*/nodes', EnvironmentNodesEndpoint.new(self) ],
219
+ [ '/environments/*/recipes', EnvironmentRecipesEndpoint.new(self) ],
220
+ [ '/environments/*/roles/*', EnvironmentRoleEndpoint.new(self) ],
221
+ [ '/nodes', RestListEndpoint.new(self) ],
222
+ [ '/nodes/*', NodeEndpoint.new(self) ],
223
+ [ '/principals/*', PrincipalEndpoint.new(self) ],
224
+ [ '/roles', RestListEndpoint.new(self) ],
225
+ [ '/roles/*', RoleEndpoint.new(self) ],
226
+ [ '/roles/*/environments', RoleEnvironmentsEndpoint.new(self) ],
227
+ [ '/roles/*/environments/*', EnvironmentRoleEndpoint.new(self) ],
228
+ [ '/sandboxes', SandboxesEndpoint.new(self) ],
229
+ [ '/sandboxes/*', SandboxEndpoint.new(self) ],
230
+ [ '/search', SearchesEndpoint.new(self) ],
231
+ [ '/search/*', SearchEndpoint.new(self) ],
232
+ [ '/users', ActorsEndpoint.new(self) ],
233
+ [ '/users/*', ActorEndpoint.new(self) ],
234
+
235
+ [ '/file_store/*', FileStoreFileEndpoint.new(self) ],
236
+ ])
237
+ router.not_found = NotFoundEndpoint.new
238
+ router
239
+ end
240
+
241
+ def dejsonize_children!(hash)
242
+ hash.each_pair do |key, value|
243
+ hash[key] = JSON.pretty_generate(value) if value.is_a?(Hash)
244
+ end
245
+ end
246
+
247
+ def get_file(directory, path)
248
+ value = directory
249
+ path.split('/').each do |part|
250
+ value = value[part]
251
+ end
252
+ value
253
+ end
254
+ end
255
+ end
@@ -0,0 +1,53 @@
1
+ module ChefZero
2
+ module Solr
3
+ module Query
4
+ class BinaryOperator
5
+ def initialize(left, operator, right)
6
+ @left = left
7
+ @operator = operator
8
+ @right = right
9
+ end
10
+
11
+ def to_s
12
+ "(#{left} #{operator} #{right})"
13
+ end
14
+
15
+ attr_reader :left
16
+ attr_reader :operator
17
+ attr_reader :right
18
+
19
+ def matches_doc?(doc)
20
+ case @operator
21
+ when 'AND'
22
+ left.matches_doc?(doc) && right.matches_doc?(doc)
23
+ when 'OR'
24
+ left.matches_doc?(doc) || right.matches_doc?(doc)
25
+ when '^'
26
+ left.matches_doc?(doc)
27
+ when ':'
28
+ if left.respond_to?(:literal_string) && left.literal_string
29
+ value = doc[left.literal_string]
30
+ right.matches_values?([value])
31
+ else
32
+ values = doc.matching_values { |key| left.matches_values?([key]) }
33
+ right.matches_values?(values)
34
+ end
35
+ end
36
+ end
37
+
38
+ def matches_values?(values)
39
+ case @operator
40
+ when 'AND'
41
+ left.matches_values?(values) && right.matches_values?(values)
42
+ when 'OR'
43
+ left.matches_values?(values) || right.matches_values?(values)
44
+ when '^'
45
+ left.matches_values?(values)
46
+ when ':'
47
+ raise ": does not work inside a : or term"
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,23 @@
1
+ require 'chef_zero/solr/query/regexpable_query'
2
+
3
+ module ChefZero
4
+ module Solr
5
+ module Query
6
+ class Phrase < RegexpableQuery
7
+ def initialize(terms)
8
+ # Phrase is terms separated by whitespace
9
+ if terms.size == 0 && terms[0].literal_string
10
+ literal_string = terms[0].literal_string
11
+ else
12
+ literal_string = nil
13
+ end
14
+ super(terms.map { |term| term.regexp_string }.join("#{NON_WORD_CHARACTER}+"), literal_string)
15
+ end
16
+
17
+ def to_s
18
+ "Phrase(\"#{@regexp_string}\")"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,34 @@
1
+ module ChefZero
2
+ module Solr
3
+ module Query
4
+ class RangeQuery
5
+ def initialize(from, to, from_inclusive, to_inclusive)
6
+ @from = from
7
+ @to = to
8
+ @from_inclusive = from_inclusive
9
+ @to_inclusive = to_inclusive
10
+ end
11
+
12
+ def to_s
13
+ "#{@from_inclusive ? '[' : '{'}#{@from} TO #{@to}#{@to_inclusive ? '[' : '{'}"
14
+ end
15
+
16
+ def matches?(key, value)
17
+ case @from <=> value
18
+ when -1
19
+ return false
20
+ when 0
21
+ return false if !@from_inclusive
22
+ end
23
+ case @to <=> value
24
+ when 1
25
+ return false
26
+ when 0
27
+ return false if !@to_inclusive
28
+ end
29
+ return true
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end