@return [Hash]
@return [WEBrick::HTTPServer]
# File lib/chef_zero/server.rb, line 73 def initialize(options = {}) @options = DEFAULT_OPTIONS.merge(options) @options.freeze ChefZero::Log.level = @options[:log_level].to_sym end
# File lib/chef_zero/server.rb, line 365 def clear_data data_store.clear if options[:single_org] data_store.create_dir([ 'organizations' ], options[:single_org]) end end
The data store for this server (default is in-memory).
@return [ChefZero::DataStore]
# File lib/chef_zero/server.rb, line 120 def data_store @data_store ||= begin result = @options[:data_store] || DataStore::MemoryStoreV2.new if options[:single_org] if result.respond_to?(:interface_version) && result.interface_version >= 2 && result.interface_version < 3 result.create_dir([ 'organizations' ], options[:single_org]) else result = ChefZero::DataStore::V1ToV2Adapter.new(result, options[:single_org]) end else if !(result.respond_to?(:interface_version) && result.interface_version >= 2 && result.interface_version < 3) raise "Multi-org not supported by data store #{result}!" end end result end end
# File lib/chef_zero/server.rb, line 286 def gen_key_pair if generate_real_keys? private_key = OpenSSL::PKey::RSA.new(2048) public_key = private_key.public_key.to_s public_key.sub!(/^-----BEGIN RSA PUBLIC KEY-----/, '-----BEGIN PUBLIC KEY-----') public_key.sub!(/-----END RSA PUBLIC KEY-----(\s+)$/, '-----END PUBLIC KEY-----\1') [private_key.to_s, public_key] else [PRIVATE_KEY, PUBLIC_KEY] end end
Boolean method to determine if real Public/Private keys should be generated.
@return [Boolean]
true if real keys should be created, false otherwise
# File lib/chef_zero/server.rb, line 146 def generate_real_keys? !!@options[:generate_real_keys] end
# File lib/chef_zero/server.rb, line 380 def inspect "#<#{self.class} @url=#{url.inspect}>" 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"' } }
}
# File lib/chef_zero/server.rb, line 329 def load_data(contents, org_name = 'chef') %w(clients environments nodes roles users).each do |data_type| if contents[data_type] dejsonize_children(contents[data_type]).each_pair do |name, data| data_store.set(['organizations', org_name, data_type, name], data, :create) end end end if contents['data'] contents['data'].each_pair do |key, data_bag| data_store.create_dir(['organizations', org_name, 'data'], key, :recursive) dejsonize_children(data_bag).each do |item_name, item| data_store.set(['organizations', org_name, 'data', key, item_name], item, :create) end end end if contents['cookbooks'] contents['cookbooks'].each_pair do |name_version, cookbook| if name_version =~ /(.+)-(\d+\.\d+\.\d+)$/ cookbook_data = CookbookData.to_hash(cookbook, $1, $2) else cookbook_data = CookbookData.to_hash(cookbook, name_version) end raise "No version specified" if !cookbook_data[:version] data_store.create_dir(['organizations', org_name, 'cookbooks'], cookbook_data[:cookbook_name], :recursive) data_store.set(['organizations', org_name, 'cookbooks', cookbook_data[:cookbook_name], cookbook_data[:version]], JSON.pretty_generate(cookbook_data), :create) cookbook_data.values.each do |files| next unless files.is_a? Array files.each do |file| data_store.set(['organizations', org_name, 'file_store', 'checksums', file[:checksum]], get_file(cookbook, file[:path]), :create) end end end end end
# File lib/chef_zero/server.rb, line 298 def on_request(&block) @on_request_proc = block end
# File lib/chef_zero/server.rb, line 302 def on_response(&block) @on_response_proc = block end
@return [Integer]
# File lib/chef_zero/server.rb, line 84 def port if @port @port elsif !options[:port].respond_to?(:each) options[:port] else raise "port cannot be determined until server is started" end end
# File lib/chef_zero/server.rb, line 372 def request_handler(&block) @request_handler = block end
Boolean method to determine if the server is currently ready to accept requests. This method will attempt to make an HTTP request against the server. If this method returns true, you are safe to make a request.
@return [Boolean]
true if the server is accepting requests, false otherwise
# File lib/chef_zero/server.rb, line 260 def running? !@server.nil? && @running && @server.status == :Running end
Start a Chef Zero server in the current thread. You can stop this server by canceling the current thread.
@param [Boolean|IO] publish
publish the server information to the publish parameter or to STDOUT if it's "true"
@return [nil]
this method will block the main thread until interrupted
# File lib/chef_zero/server.rb, line 160 def start(publish = true) publish = publish[:publish] if publish.is_a?(Hash) # Legacy API if publish output = publish.respond_to?(:puts) ? publish : STDOUT output.puts " >> Starting Chef Zero (v#{ChefZero::VERSION})... ".gsub(/^ {10}/, '') end thread = start_background if publish output = publish.respond_to?(:puts) ? publish : STDOUT output.puts " >> WEBrick (v#{WEBrick::VERSION}) on Rack (v#{Rack.release}) is listening at #{url} >> Press CTRL+C to stop ".gsub(/^ {10}/, '') end %w[INT TERM].each do |signal| Signal.trap(signal) do puts "\n>> Stopping Chef Zero..." @server.shutdown end end # Move the background process to the main thread thread.join end
Start a Chef Zero server in a forked process. This method returns the PID to the forked process.
@param [Fixnum] wait
the number of seconds to wait for the server to start
@return [Thread]
the thread the background process is running in
# File lib/chef_zero/server.rb, line 203 def start_background(wait = 5) @server = WEBrick::HTTPServer.new( :DoNotListen => true, :AccessLog => [], :Logger => WEBrick::Log.new(StringIO.new, 7), :StartCallback => proc { @running = true } ) @server.mount('/', Rack::Handler::WEBrick, app) # Pick a port if options[:port].respond_to?(:each) options[:port].each do |port| begin @server.listen(options[:host], port) @port = port break rescue Errno::EADDRINUSE ChefZero::Log.info("Port #{port} in use: #{$!}") end end if !@port raise Errno::EADDRINUSE, "No port in :port range #{options[:port]} is available" end else @server.listen(options[:host], options[:port]) @port = options[:port] end # Start the server in the background @thread = Thread.new do begin Thread.current.abort_on_exception = true @server.start ensure @port = nil @running = false end end # Do not return until the web server is genuinely started. while !@running && @thread.alive? sleep(0.01) end @thread end
Gracefully stop the Chef Zero server.
@param [Fixnum] wait
the number of seconds to wait before raising force-terminating the server
# File lib/chef_zero/server.rb, line 271 def stop(wait = 5) if @running @server.shutdown @thread.join(wait) end rescue Timeout::Error if @thread ChefZero::Log.error("Chef Zero did not stop within #{wait} seconds! Killing...") @thread.kill end ensure @server = nil @thread = nil end
# File lib/chef_zero/server.rb, line 376 def to_s "#<#{self.class} #{url}>" end
The URL for this Chef Zero server. If the given host is an IPV6 address, it is escaped in brackets according to RFC-2732.
@see www.ietf.org/rfc/rfc2732.txt RFC-2732
@return [String]
# File lib/chef_zero/server.rb, line 107 def url @url ||= if @options[:host].include?(':') URI("http://[#{@options[:host]}]:#{port}").to_s else URI("http://#{@options[:host]}:#{port}").to_s end end
# File lib/chef_zero/server.rb, line 423 def app router = RestRouter.new(open_source_endpoints) router.not_found = NotFoundEndpoint.new if options[:single_org] rest_base_prefix = [ 'organizations', options[:single_org] ] else rest_base_prefix = [] end return proc do |env| begin request = RestRequest.new(env, rest_base_prefix) if @on_request_proc @on_request_proc.call(request) end response = nil if @request_handler response = @request_handler.call(request) end unless response response = router.call(request) end if @on_response_proc @on_response_proc.call(request, response) end # Insert Server header response[1]['Server'] = 'chef-zero' # Puma expects the response to be an array (chunked responses). Since # we are statically generating data, we won't ever have said chunked # response, so fake it. response[-1] = Array(response[-1]) response rescue if options[:log_level] == :debug STDERR.puts "Request Error: #{$!}" STDERR.puts $!.backtrace.join("\n") end end end end
# File lib/chef_zero/server.rb, line 467 def dejsonize_children(hash) result = {} hash.each_pair do |key, value| result[key] = value.is_a?(Hash) ? JSON.pretty_generate(value) : value end result end
# File lib/chef_zero/server.rb, line 475 def get_file(directory, path) value = directory path.split('/').each do |part| value = value[part] end value end
# File lib/chef_zero/server.rb, line 386 def open_source_endpoints [ [ "/organizations/*/authenticate_user", AuthenticateUserEndpoint.new(self) ], [ "/organizations/*/clients", ActorsEndpoint.new(self) ], [ "/organizations/*/clients/*", ActorEndpoint.new(self) ], [ "/organizations/*/cookbooks", CookbooksEndpoint.new(self) ], [ "/organizations/*/cookbooks/*", CookbookEndpoint.new(self) ], [ "/organizations/*/cookbooks/*/*", CookbookVersionEndpoint.new(self) ], [ "/organizations/*/data", DataBagsEndpoint.new(self) ], [ "/organizations/*/data/*", DataBagEndpoint.new(self) ], [ "/organizations/*/data/*/*", DataBagItemEndpoint.new(self) ], [ "/organizations/*/environments", RestListEndpoint.new(self) ], [ "/organizations/*/environments/*", EnvironmentEndpoint.new(self) ], [ "/organizations/*/environments/*/cookbooks", EnvironmentCookbooksEndpoint.new(self) ], [ "/organizations/*/environments/*/cookbooks/*", EnvironmentCookbookEndpoint.new(self) ], [ "/organizations/*/environments/*/cookbook_versions", EnvironmentCookbookVersionsEndpoint.new(self) ], [ "/organizations/*/environments/*/nodes", EnvironmentNodesEndpoint.new(self) ], [ "/organizations/*/environments/*/recipes", EnvironmentRecipesEndpoint.new(self) ], [ "/organizations/*/environments/*/roles/*", EnvironmentRoleEndpoint.new(self) ], [ "/organizations/*/nodes", RestListEndpoint.new(self) ], [ "/organizations/*/nodes/*", NodeEndpoint.new(self) ], [ "/organizations/*/principals/*", PrincipalEndpoint.new(self) ], [ "/organizations/*/roles", RestListEndpoint.new(self) ], [ "/organizations/*/roles/*", RoleEndpoint.new(self) ], [ "/organizations/*/roles/*/environments", RoleEnvironmentsEndpoint.new(self) ], [ "/organizations/*/roles/*/environments/*", EnvironmentRoleEndpoint.new(self) ], [ "/organizations/*/sandboxes", SandboxesEndpoint.new(self) ], [ "/organizations/*/sandboxes/*", SandboxEndpoint.new(self) ], [ "/organizations/*/search", SearchesEndpoint.new(self) ], [ "/organizations/*/search/*", SearchEndpoint.new(self) ], [ "/organizations/*/users", ActorsEndpoint.new(self) ], [ "/organizations/*/users/*", ActorEndpoint.new(self) ], [ "/organizations/*/file_store/**", FileStoreFileEndpoint.new(self) ], ] end