diff options
author | danielsdeleo <dan@getchef.com> | 2015-03-31 12:19:05 -0700 |
---|---|---|
committer | danielsdeleo <dan@getchef.com> | 2015-04-01 13:35:01 -0700 |
commit | aab0ccb5d41913e050707bd6b40e5b820649c566 (patch) | |
tree | 0eb3839f664afb6f5eade15e32f85313cf41defa /lib | |
parent | 8b42ac0374fb075fbcb21df73742e81e69d9bf6f (diff) | |
download | chef-aab0ccb5d41913e050707bd6b40e5b820649c566.tar.gz |
Extract socketless client and add specs
Diffstat (limited to 'lib')
-rw-r--r-- | lib/chef/http/socketless_chef_zero_client.rb | 205 | ||||
-rw-r--r-- | lib/chef/rest.rb | 145 |
2 files changed, 205 insertions, 145 deletions
diff --git a/lib/chef/http/socketless_chef_zero_client.rb b/lib/chef/http/socketless_chef_zero_client.rb new file mode 100644 index 0000000000..a64bfc8d4d --- /dev/null +++ b/lib/chef/http/socketless_chef_zero_client.rb @@ -0,0 +1,205 @@ +#-- +# Author:: Daniel DeLeo (<dan@chef.io>) +# Copyright:: Copyright (c) 2015 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# --- +# Some portions of the code in this file are verbatim copies of code from the +# fakeweb project: https://github.com/chrisk/fakeweb +# +# fakeweb is distributed under the MIT license, which is copied below: +# --- +# +# Copyright 2006-2010 Blaine Cook, Chris Kampmeier, and other contributors +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +require 'chef_zero/server' + +class Chef + class HTTP + + # HTTP Client class that talks directly to Zero via the Rack interface. + class SocketlessChefZeroClient + + # This module is extended into Net::HTTP Response objects created from + # Socketless Chef Zero responses. + module ResponseExts + + # Net::HTTP raises an error if #read_body is called with a block or + # file argument after the body has already been read from the network. + # + # Since we always set the body to the string response from Chef Zero + # and set the `@read` indicator variable, we have to patch this method + # or else streaming-style responses won't work. + 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 (2-clause BSD License) + # + # HTTP status codes and descriptions + STATUS_MESSAGE = { + 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 + @url.port + end + + def request(method, url, body, headers, &handler_block) + request = req_to_rack(method, url, body, headers) + res = ChefZero::SocketlessServerMap.request(port, request) + + net_http_response = to_net_http(res[0], res[1], res[2]) + + yield net_http_response if block_given? + + [self, net_http_response] + end + + def req_to_rack(method, url, body, headers) + body_str = body || "" + { + "SCRIPT_NAME" => "", + "SERVER_NAME" => "localhost", + "REQUEST_METHOD" => method.to_s.upcase, + "PATH_INFO" => url.path, + "QUERY_STRING" => url.query, + "SERVER_PORT" => url.port, + "HTTP_HOST" => "localhost:#{url.port}", + "rack.url_scheme" => "chefzero", + "rack.input" => StringIO.new(body_str), + } + end + + 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(ResponseExts) + 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 + + end +end diff --git a/lib/chef/rest.rb b/lib/chef/rest.rb index 855608385d..2612714a19 100644 --- a/lib/chef/rest.rb +++ b/lib/chef/rest.rb @@ -40,151 +40,6 @@ require 'chef/http/remote_request_id' 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 - @url.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"] = url.port - r["HTTP_HOST"] = "localhost:#{url.port}" - r["rack.url_scheme"] = "chefzero" - r["rack.input"] = StringIO.new(body_str) - - res = ChefZero::SocketlessServerMap.request(port, r) - - net_http_response = to_net_http(res[0], res[1], res[2]) - - 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. |