summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjkeiser <jkeiser@opscode.com>2012-12-23 16:13:04 -0800
committerjkeiser <jkeiser@opscode.com>2012-12-23 16:13:04 -0800
commit5ef2a78077ef11431ef1625246ff14642ac556d3 (patch)
tree7c8ff7cafb178f053750f546341ef18f98f1f9f9
parent7d1fab82ff572fdde5a53650880bee50ae214894 (diff)
downloadchef-zero-5ef2a78077ef11431ef1625246ff14642ac556d3.tar.gz
Add test start/stop/data APIs, use Thin directly, use Chef::Log instead of puts
-rw-r--r--README.rdoc2
-rwxr-xr-xbin/chef-zero8
-rw-r--r--lib/chef_zero/cookbook_data.rb105
-rw-r--r--lib/chef_zero/rest_base.rb4
-rw-r--r--lib/chef_zero/router.rb4
-rw-r--r--lib/chef_zero/server.rb177
-rw-r--r--test/run-pedant.rb2
7 files changed, 248 insertions, 54 deletions
diff --git a/README.rdoc b/README.rdoc
index 5a1044c..1c00dcb 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -24,7 +24,7 @@ One of chef-zero's primary uses is as a small test server for people writing and
testing clients. To bring it up, simply do:
require 'chef_zero/server'
- server = ChefZero::Server.new(:Port => 8889)
+ server = ChefZero::Server.new(:port => 8889)
server.start
To bring it up in the background, do:
diff --git a/bin/chef-zero b/bin/chef-zero
index 5f41dae..29de409 100755
--- a/bin/chef-zero
+++ b/bin/chef-zero
@@ -8,18 +8,18 @@ require 'chef_zero/server'
require 'optparse'
options = {
- :Host => '127.0.0.1',
- :Port => 8889,
+ :host => '127.0.0.1',
+ :port => 8889,
:generate_real_keys => false
}
OptionParser.new do |opts|
opts.banner = "Usage: chef-zero [ARGS]"
opts.on("-H", "--host HOST", "Host to bind to (default: 127.0.0.1)") do |value|
- options[:Host] = value
+ options[:host] = value
end
opts.on("-p", "--port PORT", Integer, "Port to listen on") do |value|
- options[:Port] = value
+ options[:port] = value
end
opts.on("--[no-]generate-keys", "Whether to generate actual keys or fake it (faster). Default: false.") do |value|
options[:generate_real_keys] = value
diff --git a/lib/chef_zero/cookbook_data.rb b/lib/chef_zero/cookbook_data.rb
new file mode 100644
index 0000000..c83afaa
--- /dev/null
+++ b/lib/chef_zero/cookbook_data.rb
@@ -0,0 +1,105 @@
+require 'digest/md5'
+require 'chef/cookbook/metadata' # for ruby metadata.rb dsl
+
+module ChefZero
+ module CookbookData
+ def self.to_json(cookbook, name, version=nil)
+ result = files_from(cookbook)
+ recipe_names = result[:recipes].map do |recipe|
+ recipe_name = recipe[:name][0..-2]
+ recipe_name == 'default' ? name : "#{name}::#{recipe_name}"
+ end
+ result[:metadata] = metadata_from(cookbook, name, version, recipe_names)
+ result[:name] = "#{name}-#{result[:metadata][:version]}"
+ result[:json_class] = 'Chef::CookbookVersion'
+ result[:cookbook_name] = name
+ result[:version] = result[:metadata][:version]
+ result[:chef_type] = 'cookbook_version'
+ result
+ end
+
+ private
+
+ # Just enough cookbook to make a Metadata object
+ class PretendCookbook
+ def initialize(name, fully_qualified_recipe_names)
+ @name = name
+ @fully_qualified_recipe_names = fully_qualified_recipe_names
+ end
+ attr_reader :name, :fully_qualified_recipe_names
+ end
+
+ def self.metadata_from(directory, name, version, recipe_names)
+ metadata = Chef::Cookbook::Metadata.new(PretendCookbook.new(name, recipe_names))
+ # If both .rb and .json exist, read .rb
+ # TODO if recipes has 3 recipes in it, and the Ruby/JSON has only one, should
+ # the resulting recipe list have 1, or 3-4 recipes in it?
+ if cookbook['metadata.rb']
+ metadata.instance_eval(cookbook['metadata.rb'])
+ elsif cookbook['metadata.json']
+ metadata.from_json(cookbook['metadata.json'])
+ end
+ metadata.to_json
+ end
+
+ def self.files_from(cookbook)
+ # TODO some support .rb only
+ result = {
+ :attributes => load_child_files(cookbook, 'attributes', false),
+ :definitions => load_child_files(cookbook, 'definitions', false),
+ :recipes => load_child_files(cookbook, 'recipes', false),
+ :libraries => load_child_files(cookbook, 'libraries', false),
+ :templates => load_child_files(cookbook, 'templates', true, true),
+ :files => load_child_files(cookbook, 'files', true, true),
+ :resources => load_child_files(cookbook, 'resources', true),
+ :providers => load_child_files(cookbook, 'providers', true),
+ :root_files => load_files(cookbook, false)
+ }
+ set_specificity(result[:templates])
+ set_specificity(result[:files])
+ result
+ end
+
+ def self.load_child_files(parent, key, recursive)
+ result = load_files(parent[key], recursive)
+ result.each do |file|
+ file[:path] = "#{key}/#{file[:path]}"
+ end
+ result
+ end
+
+ def self.load_files(directory, recursive)
+ result = []
+ directory.each_pair do |child_key, child|
+ if child.is_a? Hash
+ if recursive
+ result += load_child_files(directory, child_key, recursive, false)
+ end
+ else
+ result += load_file(child, child_key)
+ end
+ end
+ result.each do |file|
+ file[:path] = "key/#{file[:path]}"
+ end
+ result
+ end
+
+ def self.load_file(value, name)
+ result = {
+ :name => name,
+ :path => name,
+ :checksum => Digest::MD5.hexdigest(value),
+ :specificity => 'default'
+ }
+ end
+
+ def self.set_specificity(files)
+ files.each do |file|
+ parts = file[:path].split('/')
+ raise "Only directories are allowed directly under templates or files: #{file[:path]}" if parts.size == 2
+ file[:specificity] = parts[1]
+ end
+ end
+ end
+end
diff --git a/lib/chef_zero/rest_base.rb b/lib/chef_zero/rest_base.rb
index 068a55d..ca041b5 100644
--- a/lib/chef_zero/rest_base.rb
+++ b/lib/chef_zero/rest_base.rb
@@ -1,5 +1,6 @@
require 'chef_zero/rest_request'
require 'chef_zero/rest_error_response'
+require 'chef/log'
module ChefZero
class RestBase
@@ -32,8 +33,7 @@ module ChefZero
error(e.response_code, e.error)
end
rescue
- puts $!.inspect
- puts $!.backtrace
+ Chef::Log.error("#{$!.inspect}\n#{$!.backtrace}")
raise
end
end
diff --git a/lib/chef_zero/router.rb b/lib/chef_zero/router.rb
index 0389c8a..1b39b79 100644
--- a/lib/chef_zero/router.rb
+++ b/lib/chef_zero/router.rb
@@ -1,3 +1,5 @@
+require 'chef/log'
+
module ChefZero
class Router
def initialize(routes)
@@ -11,7 +13,7 @@ module ChefZero
attr_accessor :not_found
def call(env)
- puts "#{env['REQUEST_METHOD']} #{env['PATH_INFO']}#{env['QUERY_STRING'] != '' ? "?" + env['QUERY_STRING'] : ''}"
+ Chef::Log.debug "#{env['REQUEST_METHOD']} #{env['PATH_INFO']}#{env['QUERY_STRING'] != '' ? "?" + env['QUERY_STRING'] : ''}"
clean_path = "/" + env['PATH_INFO'].split('/').select { |part| part != "" }.join("/")
routes.each do |route, endpoint|
if route.match(clean_path)
diff --git a/lib/chef_zero/server.rb b/lib/chef_zero/server.rb
index 78f8e14..4f6c22d 100644
--- a/lib/chef_zero/server.rb
+++ b/lib/chef_zero/server.rb
@@ -17,11 +17,11 @@
#
require 'rubygems'
-require 'webrick'
-require 'rack'
+require 'thin'
require 'openssl'
require 'chef_zero'
require 'chef_zero/router'
+require 'timeout'
require 'chef_zero/endpoints/authenticate_user_endpoint'
require 'chef_zero/endpoints/actors_endpoint'
@@ -52,12 +52,13 @@ require 'chef_zero/endpoints/file_store_file_endpoint'
require 'chef_zero/endpoints/not_found_endpoint'
module ChefZero
- class Server < Rack::Server
- def initialize(options)
- options[:Host] ||= "localhost" # TODO 0.0.0.0?
- options[:Port] ||= 80
+ class Server
+ def initialize(options = {})
+ @options = options
+ options[:host] ||= '127.0.0.1'
+ options[:port] ||= 80
options[:generate_real_keys] = true if !options.has_key?(:generate_real_keys)
- super(options)
+ @server = Thin::Server.new(options[:host], options[:port], make_app)
@generate_real_keys = options[:generate_real_keys]
@data = {
'clients' => {
@@ -79,49 +80,43 @@ module ChefZero
}
end
+ attr_reader :server
attr_reader :data
+ attr_reader :options
attr_reader :generate_real_keys
include ChefZero::Endpoints
- def app
- @app ||= begin
- router = Router.new([
- [ '/authenticate_user', AuthenticateUserEndpoint.new(self) ],
- [ '/clients', ActorsEndpoint.new(self) ],
- [ '/clients/*', ActorEndpoint.new(self) ],
- [ '/cookbooks', CookbooksEndpoint.new(self) ],
- [ '/cookbooks/*', CookbookEndpoint.new(self) ],
- [ '/cookbooks/*/*', CookbookVersionEndpoint.new(self) ],
- [ '/data', DataBagsEndpoint.new(self) ],
- [ '/data/*', DataBagEndpoint.new(self) ],
- [ '/data/*/*', DataBagItemEndpoint.new(self) ],
- [ '/environments', RestListEndpoint.new(self) ],
- [ '/environments/*', EnvironmentEndpoint.new(self) ],
- [ '/environments/*/cookbooks', EnvironmentCookbooksEndpoint.new(self) ],
- [ '/environments/*/cookbooks/*', EnvironmentCookbookEndpoint.new(self) ],
- [ '/environments/*/cookbook_versions', EnvironmentCookbookVersionsEndpoint.new(self) ],
- [ '/environments/*/nodes', EnvironmentNodesEndpoint.new(self) ],
- [ '/environments/*/recipes', EnvironmentRecipesEndpoint.new(self) ],
- [ '/environments/*/roles/*', EnvironmentRoleEndpoint.new(self) ],
- [ '/nodes', RestListEndpoint.new(self) ],
- [ '/nodes/*', NodeEndpoint.new(self) ],
- [ '/principals/*', PrincipalEndpoint.new(self) ],
- [ '/roles', RestListEndpoint.new(self) ],
- [ '/roles/*', RoleEndpoint.new(self) ],
- [ '/roles/*/environments', RoleEnvironmentsEndpoint.new(self) ],
- [ '/roles/*/environments/*', EnvironmentRoleEndpoint.new(self) ],
- [ '/sandboxes', SandboxesEndpoint.new(self) ],
- [ '/sandboxes/*', SandboxEndpoint.new(self) ],
- [ '/search', SearchesEndpoint.new(self) ],
- [ '/search/*', SearchEndpoint.new(self) ],
- [ '/users', ActorsEndpoint.new(self) ],
- [ '/users/*', ActorEndpoint.new(self) ],
-
- [ '/file_store/*', FileStoreFileEndpoint.new(self) ],
- ])
- router.not_found = NotFoundEndpoint.new
- router
+ def url
+ "http://#{options[:host]}:#{options[:port]}"
+ end
+
+ def start
+ server.start
+ end
+
+ def start_background(timeout = 5)
+ @thread = Thread.new do
+ server.start
+ end
+ Timeout::timeout(timeout) do
+ until server.running?
+ sleep(0.01)
+ end
+ end
+ end
+
+ def running?
+ server.running?
+ end
+
+ def stop(timeout = 5)
+ Timeout::timeout(timeout) do
+ server.stop
+ end
+ if @thread
+ @thread.kill
+ @thread = nil
end
end
@@ -136,5 +131,97 @@ module ChefZero
[PRIVATE_KEY, PUBLIC_KEY]
end
end
+
+ # Load data in a nice, friendly form:
+ # {
+ # 'roles' => {
+ # 'desert' => '{ "description": "Hot and dry"' },
+ # 'rainforest' => { "description" => 'Wet and humid' }
+ # },
+ # 'cookbooks' => {
+ # 'apache2-1.0.1' => {
+ # 'templates' => { 'default' => { 'blah.txt' => 'hi' }}
+ # 'recipes' => { 'default.rb' => 'template "blah.txt"' }
+ # 'metadata.rb' => 'depends "mysql"'
+ # },
+ # 'apache2-1.2.0' => {
+ # 'templates' => { 'default' => { 'blah.txt' => 'lo' }}
+ # 'recipes' => { 'default.rb' => 'template "blah.txt"' }
+ # 'metadata.rb' => 'depends "mysql"'
+ # },
+ # 'mysql' => {
+ # 'recipes' => { 'default.rb' => 'file { contents "hi" }' },
+ # 'metadata.rb' => 'version "1.0.0"'
+ # }
+ # }
+ # }
+ def load_data(contents)
+ %w(clients data environments nodes roles users).each do |data_type|
+ server.data[data_type].merge!(contents[data_type]) if contents[data_type]
+ end
+ if contents['cookbooks']
+ contents['cookbooks'].each_pair do |name_version, cookbook|
+ if name_version =~ /(.+)-(\d+\.\d+\.\d+)$/
+ cookbook_data = CookbookData.to_json(cookbook, $1, $2)
+ else
+ cookbook_data = CookbookData.to_json(cookbook, name_version)
+ end
+ server.data['cookbooks'][cookbook_data['cookbook_name']][cookbook_data['version']] = cookbook_data
+ cookbook_data.each do |files|
+ next unless files.is_a? Array
+ server.data['file_store'][file[:checksum]] = get_file(cookbook, file[:path])
+ end
+ end
+ end
+ end
+
+ private
+
+ def make_app
+ router = Router.new([
+ [ '/authenticate_user', AuthenticateUserEndpoint.new(self) ],
+ [ '/clients', ActorsEndpoint.new(self) ],
+ [ '/clients/*', ActorEndpoint.new(self) ],
+ [ '/cookbooks', CookbooksEndpoint.new(self) ],
+ [ '/cookbooks/*', CookbookEndpoint.new(self) ],
+ [ '/cookbooks/*/*', CookbookVersionEndpoint.new(self) ],
+ [ '/data', DataBagsEndpoint.new(self) ],
+ [ '/data/*', DataBagEndpoint.new(self) ],
+ [ '/data/*/*', DataBagItemEndpoint.new(self) ],
+ [ '/environments', RestListEndpoint.new(self) ],
+ [ '/environments/*', EnvironmentEndpoint.new(self) ],
+ [ '/environments/*/cookbooks', EnvironmentCookbooksEndpoint.new(self) ],
+ [ '/environments/*/cookbooks/*', EnvironmentCookbookEndpoint.new(self) ],
+ [ '/environments/*/cookbook_versions', EnvironmentCookbookVersionsEndpoint.new(self) ],
+ [ '/environments/*/nodes', EnvironmentNodesEndpoint.new(self) ],
+ [ '/environments/*/recipes', EnvironmentRecipesEndpoint.new(self) ],
+ [ '/environments/*/roles/*', EnvironmentRoleEndpoint.new(self) ],
+ [ '/nodes', RestListEndpoint.new(self) ],
+ [ '/nodes/*', NodeEndpoint.new(self) ],
+ [ '/principals/*', PrincipalEndpoint.new(self) ],
+ [ '/roles', RestListEndpoint.new(self) ],
+ [ '/roles/*', RoleEndpoint.new(self) ],
+ [ '/roles/*/environments', RoleEnvironmentsEndpoint.new(self) ],
+ [ '/roles/*/environments/*', EnvironmentRoleEndpoint.new(self) ],
+ [ '/sandboxes', SandboxesEndpoint.new(self) ],
+ [ '/sandboxes/*', SandboxEndpoint.new(self) ],
+ [ '/search', SearchesEndpoint.new(self) ],
+ [ '/search/*', SearchEndpoint.new(self) ],
+ [ '/users', ActorsEndpoint.new(self) ],
+ [ '/users/*', ActorEndpoint.new(self) ],
+
+ [ '/file_store/*', FileStoreFileEndpoint.new(self) ],
+ ])
+ router.not_found = NotFoundEndpoint.new
+ router
+ end
+
+ def get_file(directory, path)
+ value = directory
+ path.split('/').each do |part|
+ value = value[part]
+ end
+ value
+ end
end
end
diff --git a/test/run-pedant.rb b/test/run-pedant.rb
index cc23414..a454508 100644
--- a/test/run-pedant.rb
+++ b/test/run-pedant.rb
@@ -5,7 +5,7 @@ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")))
require 'chef_zero/server'
thread = Thread.new do
- server = ChefZero::Server.new(:Port => 8889)
+ server = ChefZero::Server.new(:port => 8889)
server.start
end