summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordanielsdeleo <dan@getchef.com>2015-03-27 12:07:57 -0700
committerdanielsdeleo <dan@getchef.com>2015-04-01 13:35:01 -0700
commit110f3b77d46d4c58c00e1badeaec92875fb4e1ef (patch)
tree4f7bafa46fb9c7125bcead02faa96e849d72c38c
parent6813675a20f232afbe440800a56d4385be55e9fe (diff)
downloadchef-110f3b77d46d4c58c00e1badeaec92875fb4e1ef.tar.gz
Initial socketless local mode
-rw-r--r--lib/chef/local_mode.rb27
-rw-r--r--lib/chef/rest.rb200
2 files changed, 216 insertions, 11 deletions
diff --git a/lib/chef/local_mode.rb b/lib/chef/local_mode.rb
index e66acb6b66..c814301b4b 100644
--- a/lib/chef/local_mode.rb
+++ b/lib/chef/local_mode.rb
@@ -18,6 +18,9 @@ require 'chef/config'
class Chef
module LocalMode
+
+ LOCAL_MODE_URL = "chefzero://localhost"
+
# Create a chef local server (if the configuration requires one) for the
# duration of the given block.
#
@@ -59,12 +62,15 @@ class Chef
server_options = {}
server_options[:data_store] = data_store
server_options[:log_level] = Chef::Log.level
- server_options[:host] = Chef::Config.chef_zero.host
- server_options[:port] = parse_port(Chef::Config.chef_zero.port)
- @chef_zero_server = ChefZero::Server.new(server_options)
- @chef_zero_server.start_background
- Chef::Log.info("Started chef-zero at #{@chef_zero_server.url} with #{@chef_fs.fs_description}")
- Chef::Config.chef_server_url = @chef_zero_server.url
+ # server_options[:host] = Chef::Config.chef_zero.host
+ # server_options[:port] = parse_port(Chef::Config.chef_zero.port)
+ # @chef_zero_server = ChefZero::Server.new(server_options)
+ # @chef_zero_server.start_background
+
+ ChefZero::Socketless.instance.reset!(server_options)
+
+ Chef::Log.info("Started chef-zero at #{LOCAL_MODE_URL} with #{@chef_fs.fs_description}")
+ Chef::Config.chef_server_url = LOCAL_MODE_URL
end
end
@@ -80,10 +86,11 @@ class Chef
# If chef_zero_server is non-nil, stop it and remove references to it.
def self.destroy_server_connectivity
- if @chef_zero_server
- @chef_zero_server.stop
- @chef_zero_server = nil
- end
+ return nil
+ # if @chef_zero_server
+ # @chef_zero_server.stop
+ # @chef_zero_server = nil
+ # end
end
def self.parse_port(port)
diff --git a/lib/chef/rest.rb b/lib/chef/rest.rb
index f0de443058..8ff01e78da 100644
--- a/lib/chef/rest.rb
+++ b/lib/chef/rest.rb
@@ -38,7 +38,180 @@ require 'chef/exceptions'
require 'chef/platform/query_helpers'
require 'chef/http/remote_request_id'
+module ChefZero
+ # TODO: this needs to wrap all the things in a mutex
+ class Socketless
+
+ include Singleton
+
+ def initialize()
+ reset!
+ end
+
+ def reset!(options={})
+ @server = ChefZero::Server.new(options)
+ # TODO: make this public or whatever we need to do so we don't need #send
+ @app = @server.send(:app)
+ end
+
+ def request(rack_env)
+ @app.call(rack_env)
+ end
+
+ end
+end
+
class Chef
+
+ class SocketlessChefZeroClient
+
+ module Response
+
+ def read_body(dest = nil, &block)
+ if dest
+ raise "responses from socketless chef zero can't be written to specific destination"
+ end
+
+ if block_given?
+ block.call(@body)
+ else
+ super
+ end
+ end
+
+ end
+
+ # copied verbatim from webrick
+ #
+ # HTTP status codes and descriptions
+ STATUS_MESSAGE = { # :nodoc:
+ 100 => 'Continue',
+ 101 => 'Switching Protocols',
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ 207 => 'Multi-Status',
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found',
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ 307 => 'Temporary Redirect',
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Timeout',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Large',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Request Range Not Satisfiable',
+ 417 => 'Expectation Failed',
+ 422 => 'Unprocessable Entity',
+ 423 => 'Locked',
+ 424 => 'Failed Dependency',
+ 426 => 'Upgrade Required',
+ 428 => 'Precondition Required',
+ 429 => 'Too Many Requests',
+ 431 => 'Request Header Fields Too Large',
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Timeout',
+ 505 => 'HTTP Version Not Supported',
+ 507 => 'Insufficient Storage',
+ 511 => 'Network Authentication Required',
+ }
+
+ STATUS_MESSAGE.values.each {|v| v.freeze }
+ STATUS_MESSAGE.freeze
+
+ def initialize(base_url)
+ @url = base_url
+ end
+
+ def host
+ @url.hostname
+ end
+
+ def port
+ "no port"
+ end
+
+ # request, response = client.request(method, url, body, headers) {|r| r.read_body }
+ def request(method, url, body, headers, &handler_block)
+ #pp req: [method, url, body, headers]
+ body_str = body || ""
+ r = {}
+ r["REQUEST_METHOD"] = method.to_s.upcase
+ r["SCRIPT_NAME"] = ""
+ r["PATH_INFO"] = url.path
+ r["QUERY_STRING"] = url.query
+ r["SERVER_NAME"] = "localhost"
+ r["SERVER_PORT"] = ""
+ r["rack.url_scheme"] = "chefzero"
+ r["rack.input"] = StringIO.new(body_str)
+ pp rack_req: r
+
+ res = ChefZero::Socketless.instance.request(r)
+
+ pp raw_rack_response: res
+
+ net_http_response = to_net_http(res[0], res[1], res[2])
+
+ #pp net_http_response: net_http_response
+
+ yield net_http_response if block_given?
+
+ [self, net_http_response]
+ end
+
+ # TODO: this is copied verbatim from the fakeweb project, MIT licensed
+ # Add credits where appropriate
+
+ def to_net_http(code, headers, chunked_body)
+ body = chunked_body.join('')
+ msg = STATUS_MESSAGE[code] or raise "Cannot determine HTTP status message for code #{code}"
+ response = Net::HTTPResponse.send(:response_class, code.to_s).new("1.0", code.to_s, msg)
+ response.instance_variable_set(:@body, body)
+ headers.each do |name, value|
+ if value.respond_to?(:each)
+ value.each { |v| response.add_field(name, v) }
+ else
+ response[name] = value
+ end
+ end
+
+ response.instance_variable_set(:@read, true)
+ response.extend(Response)
+ response
+ end
+
+ private
+
+ def headers_extracted_from_options
+ options.reject {|name, _| KNOWN_OPTIONS.include?(name) }.map { |name, value|
+ [name.to_s.split("_").map { |segment| segment.capitalize }.join("-"), value]
+ }
+ end
+
+
+ end
+
# == Chef::REST
# Chef's custom REST client with built-in JSON support and RSA signed header
# authentication.
@@ -57,6 +230,15 @@ class Chef
# http://localhost:4000, a call to +get_rest+ with 'nodes' will make an
# HTTP GET request to http://localhost:4000/nodes
def initialize(url, client_name=Chef::Config[:node_name], signing_key_filename=Chef::Config[:client_key], options={})
+
+ url_as_uri = url.respond_to?(:scheme) ? url : URI.parse(url)
+
+ # TODO: NEW STUFF ADD TESTS
+ scheme = url_as_uri.scheme
+ @socketless = (scheme == "chefzero")
+ signing_key_filename = nil if @socketless
+
+
options = options.dup
options[:client_name] = client_name
options[:signing_key_filename] = signing_key_filename
@@ -188,9 +370,25 @@ class Chef
public :create_url
+ def create_url(path)
+ return path if path.is_a?(URI)
+ if path =~ /^(chefzero):\/\//i
+ URI.parse(path)
+ else
+ super
+ end
+ end
+
def http_client(base_url=nil)
base_url ||= url
- BasicClient.new(base_url, :ssl_policy => Chef::HTTP::APISSLPolicy)
+ pp url_class: base_url.class, value: base_url
+ base_url = URI.parse(base_url) if base_url.kind_of?(String)
+ if base_url.scheme == "chefzero"
+ pp using_zero_client: base_url
+ SocketlessChefZeroClient.new(base_url)
+ else
+ BasicClient.new(base_url, :ssl_policy => Chef::HTTP::APISSLPolicy)
+ end
end
############################################################################