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,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