summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordanielsdeleo <dan@getchef.com>2015-04-01 13:35:24 -0700
committerdanielsdeleo <dan@getchef.com>2015-04-01 13:35:24 -0700
commitb89d75f769ba336449f204f77311a1d43bc22de3 (patch)
tree870211dcd72b19e13fb36fd47f4b19a3c9a6ab1b
parent6813675a20f232afbe440800a56d4385be55e9fe (diff)
parent3c3702b1cf4920844066a5e0c81e04251ae6d120 (diff)
downloadchef-b89d75f769ba336449f204f77311a1d43bc22de3.tar.gz
Merge branch 'socketless-zero'
-rw-r--r--chef.gemspec2
-rw-r--r--lib/chef/application/client.rb5
-rw-r--r--lib/chef/application/knife.rb6
-rw-r--r--lib/chef/config.rb8
-rw-r--r--lib/chef/http.rb16
-rw-r--r--lib/chef/http/socketless_chef_zero_client.rb207
-rw-r--r--lib/chef/knife.rb3
-rw-r--r--lib/chef/local_mode.rb16
-rw-r--r--lib/chef/rest.rb9
-rw-r--r--lib/chef/server_api.rb1
-rw-r--r--spec/integration/client/client_spec.rb11
-rw-r--r--spec/integration/knife/common_options_spec.rb6
-rw-r--r--spec/unit/application/client_spec.rb10
-rw-r--r--spec/unit/http/socketless_chef_zero_client_spec.rb174
-rw-r--r--spec/unit/http_spec.rb14
-rw-r--r--spec/unit/knife_spec.rb5
-rw-r--r--spec/unit/rest_spec.rb9
17 files changed, 486 insertions, 16 deletions
diff --git a/chef.gemspec b/chef.gemspec
index 17861bc4a9..dc0e59ad3e 100644
--- a/chef.gemspec
+++ b/chef.gemspec
@@ -30,7 +30,7 @@ Gem::Specification.new do |s|
s.add_dependency "erubis", "~> 2.7"
s.add_dependency "diff-lcs", "~> 1.2", ">= 1.2.4"
- s.add_dependency "chef-zero", "~> 4.0"
+ s.add_dependency "chef-zero", "~> 4.1"
s.add_dependency "pry", "~> 0.9"
s.add_dependency 'plist', '~> 3.1.0'
diff --git a/lib/chef/application/client.rb b/lib/chef/application/client.rb
index 9984ad5b9d..a5faee9d35 100644
--- a/lib/chef/application/client.rb
+++ b/lib/chef/application/client.rb
@@ -258,6 +258,11 @@ class Chef::Application::Client < Chef::Application
:description => "Only run the bare minimum ohai plugins chef needs to function",
:boolean => true
+ option :listen,
+ :long => "--[no-]listen",
+ :description => "Whether a local mode (-z) server binds to a port",
+ :boolean => true
+
IMMEDIATE_RUN_SIGNAL = "1".freeze
attr_reader :chef_client_json
diff --git a/lib/chef/application/knife.rb b/lib/chef/application/knife.rb
index 1a19a45598..af5216ae00 100644
--- a/lib/chef/application/knife.rb
+++ b/lib/chef/application/knife.rb
@@ -121,6 +121,11 @@ class Chef::Application::Knife < Chef::Application
:long => "--chef-zero-port PORT",
:description => "Port (or port range) to start chef-zero on. Port ranges like 1000,1010 or 8889-9999 will try all given ports until one works."
+ option :listen,
+ :long => "--[no-]listen",
+ :description => "Whether a local mode (-z) server binds to a port",
+ :boolean => true
+
option :version,
:short => "-v",
:long => "--version",
@@ -129,7 +134,6 @@ class Chef::Application::Knife < Chef::Application
:proc => lambda {|v| puts "Chef: #{::Chef::VERSION}"},
:exit => 0
-
# Run knife
def run
Mixlib::Log::Formatter.show_time = false
diff --git a/lib/chef/config.rb b/lib/chef/config.rb
index b897f9fdbd..25557b077f 100644
--- a/lib/chef/config.rb
+++ b/lib/chef/config.rb
@@ -305,6 +305,14 @@ class Chef
default :pid_file, nil
+ # Whether Chef Zero local mode should bind to a port. All internal requests
+ # will go through the socketless code path regardless, so the socket is
+ # only needed if other processes will connect to the local mode server.
+ #
+ # For compatibility this is set to true but it will be changed to false in
+ # the future.
+ default :listen, true
+
config_context :chef_zero do
config_strict_mode true
default(:enabled) { Chef::Config.local_mode }
diff --git a/lib/chef/http.rb b/lib/chef/http.rb
index 5e52337aff..16a826a3db 100644
--- a/lib/chef/http.rb
+++ b/lib/chef/http.rb
@@ -25,6 +25,7 @@ require 'tempfile'
require 'net/https'
require 'uri'
require 'chef/http/basic_client'
+require 'chef/http/socketless_chef_zero_client'
require 'chef/monkey_patches/net_http'
require 'chef/config'
require 'chef/platform/query_helpers'
@@ -196,14 +197,18 @@ class Chef
def http_client(base_url=nil)
base_url ||= url
- BasicClient.new(base_url)
+ if chef_zero_uri?(base_url)
+ SocketlessChefZeroClient.new(base_url)
+ else
+ BasicClient.new(base_url, :ssl_policy => Chef::HTTP::APISSLPolicy)
+ end
end
protected
def create_url(path)
return path if path.is_a?(URI)
- if path =~ /^(http|https):\/\//i
+ if path =~ /^(http|https|chefzero):\/\//i
URI.parse(path)
elsif path.nil? or path.empty?
URI.parse(@url)
@@ -292,7 +297,7 @@ class Chef
http_attempts += 1
response, request, return_value = yield
# handle HTTP 50X Error
- if response.kind_of?(Net::HTTPServerError)
+ if response.kind_of?(Net::HTTPServerError) && !Chef::Config.local_mode
if http_retry_count - http_attempts + 1 > 0
sleep_time = 1 + (2 ** http_attempts) + rand(2 ** http_attempts)
Chef::Log.error("Server returned error #{response.code} for #{url}, retrying #{http_attempts}/#{http_retry_count} in #{sleep_time}s")
@@ -351,6 +356,11 @@ class Chef
private
+ def chef_zero_uri?(uri)
+ uri = URI.parse(uri) unless uri.respond_to?(:scheme)
+ uri.scheme == "chefzero"
+ end
+
def redirected_to(response)
return nil unless response.kind_of?(Net::HTTPRedirection)
# Net::HTTPNotModified is undesired subclass of Net::HTTPRedirection so test for this
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..8f5543a16f
--- /dev/null
+++ b/lib/chef/http/socketless_chef_zero_client.rb
@@ -0,0 +1,207 @@
+#--
+# 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
+
+ attr_reader :url
+
+ # 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/knife.rb b/lib/chef/knife.rb
index e13f80b5a8..2e0694aebc 100644
--- a/lib/chef/knife.rb
+++ b/lib/chef/knife.rb
@@ -373,6 +373,9 @@ class Chef
Chef::Config[:environment] = config[:environment] if config[:environment]
Chef::Config.local_mode = config[:local_mode] if config.has_key?(:local_mode)
+
+ Chef::Config.listen = config[:listen] if config.has_key?(:listen)
+
if Chef::Config.local_mode && !Chef::Config.has_key?(:cookbook_path) && !Chef::Config.has_key?(:chef_repo_path)
Chef::Config.chef_repo_path = Chef::Config.find_chef_repo_path(Dir.pwd)
end
diff --git a/lib/chef/local_mode.rb b/lib/chef/local_mode.rb
index e66acb6b66..79fb750dd8 100644
--- a/lib/chef/local_mode.rb
+++ b/lib/chef/local_mode.rb
@@ -18,6 +18,7 @@ require 'chef/config'
class Chef
module LocalMode
+
# Create a chef local server (if the configuration requires one) for the
# duration of the given block.
#
@@ -59,12 +60,21 @@ 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
+
+ if Chef::Config[:listen]
+ @chef_zero_server.start_background
+ else
+ @chef_zero_server.start_socketless
+ end
+
+ local_mode_url = @chef_zero_server.local_mode_url
+
+ 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
diff --git a/lib/chef/rest.rb b/lib/chef/rest.rb
index f0de443058..2612714a19 100644
--- a/lib/chef/rest.rb
+++ b/lib/chef/rest.rb
@@ -39,6 +39,7 @@ require 'chef/platform/query_helpers'
require 'chef/http/remote_request_id'
class Chef
+
# == Chef::REST
# Chef's custom REST client with built-in JSON support and RSA signed header
# authentication.
@@ -57,6 +58,9 @@ 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={})
+
+ signing_key_filename = nil if chef_zero_uri?(url)
+
options = options.dup
options[:client_name] = client_name
options[:signing_key_filename] = signing_key_filename
@@ -188,11 +192,6 @@ class Chef
public :create_url
- def http_client(base_url=nil)
- base_url ||= url
- BasicClient.new(base_url, :ssl_policy => Chef::HTTP::APISSLPolicy)
- end
-
############################################################################
# DEPRECATED
############################################################################
diff --git a/lib/chef/server_api.rb b/lib/chef/server_api.rb
index 8cdcd7a09d..ec4a864cb3 100644
--- a/lib/chef/server_api.rb
+++ b/lib/chef/server_api.rb
@@ -30,6 +30,7 @@ class Chef
def initialize(url = Chef::Config[:chef_server_url], options = {})
options[:client_name] ||= Chef::Config[:node_name]
options[:signing_key_filename] ||= Chef::Config[:client_key]
+ options[:signing_key_filename] = nil if chef_zero_uri?(url)
super(url, options)
end
diff --git a/spec/integration/client/client_spec.rb b/spec/integration/client/client_spec.rb
index aa9aefe1a7..b5c5e12781 100644
--- a/spec/integration/client/client_spec.rb
+++ b/spec/integration/client/client_spec.rb
@@ -60,6 +60,17 @@ EOM
result.error!
end
+ it "should complete successfully with --no-listen" do
+ file 'config/client.rb', <<EOM
+local_mode true
+cookbook_path "#{path_to('cookbooks')}"
+EOM
+
+ result = shell_out("#{chef_client} --no-listen -c \"#{path_to('config/client.rb')}\" -o 'x::default'", :cwd => chef_dir)
+ result.error!
+ end
+
+
context 'and no config file' do
it 'should complete with success when cwd is just above cookbooks and paths are not specified' do
result = shell_out("#{chef_client} -z -o 'x::default' --disable-config", :cwd => path_to(''))
diff --git a/spec/integration/knife/common_options_spec.rb b/spec/integration/knife/common_options_spec.rb
index ec76738b6f..b2e2e3fc2a 100644
--- a/spec/integration/knife/common_options_spec.rb
+++ b/spec/integration/knife/common_options_spec.rb
@@ -39,7 +39,7 @@ describe 'knife common options', :workstation do
it 'knife raw /nodes/x should retrieve the node' do
knife('raw /nodes/x').should_succeed( /"name": "x"/ )
- expect(Chef::Config.chef_server_url).to eq('http://localhost:9999')
+ expect(Chef::Config.chef_server_url).to eq('chefzero://localhost:9999')
end
end
@@ -101,7 +101,7 @@ EOM
it 'knife raw -z --chef-zero-port=9999 /nodes/x retrieves the node' do
knife('raw -z --chef-zero-port=9999 /nodes/x').should_succeed( /"name": "x"/ )
- expect(Chef::Config.chef_server_url).to eq('http://localhost:9999')
+ expect(Chef::Config.chef_server_url).to eq('chefzero://localhost:9999')
end
context 'when the default port (8889) is already bound' do
@@ -149,7 +149,7 @@ EOM
it 'knife raw -z --chef-zero-port=9999 /nodes/x retrieves the node' do
knife('raw -z --chef-zero-port=9999 /nodes/x').should_succeed( /"name": "x"/ )
- expect(Chef::Config.chef_server_url).to eq('http://localhost:9999')
+ expect(Chef::Config.chef_server_url).to eq('chefzero://localhost:9999')
end
end
end
diff --git a/spec/unit/application/client_spec.rb b/spec/unit/application/client_spec.rb
index 501251b7fe..c753ca0ab8 100644
--- a/spec/unit/application/client_spec.rb
+++ b/spec/unit/application/client_spec.rb
@@ -131,6 +131,16 @@ Enable chef-client interval runs by setting `:client_fork = true` in your config
end
+ describe "when --no-listen is set" do
+
+ it "configures listen = false" do
+ app.config[:listen] = false
+ app.reconfigure
+ expect(Chef::Config[:listen]).to eq(false)
+ end
+
+ end
+
describe "when the json_attribs configuration option is specified" do
let(:json_attribs) { {"a" => "b"} }
diff --git a/spec/unit/http/socketless_chef_zero_client_spec.rb b/spec/unit/http/socketless_chef_zero_client_spec.rb
new file mode 100644
index 0000000000..963cc9e8c4
--- /dev/null
+++ b/spec/unit/http/socketless_chef_zero_client_spec.rb
@@ -0,0 +1,174 @@
+#--
+# 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.
+#
+
+require 'chef/http/socketless_chef_zero_client'
+
+describe Chef::HTTP::SocketlessChefZeroClient do
+
+ let(:relative_url) { "" }
+ let(:uri_str) { "chefzero://localhost:1/#{relative_url}" }
+ let(:uri) { URI(uri_str) }
+
+ subject(:zero_client) { Chef::HTTP::SocketlessChefZeroClient.new(uri) }
+
+ it "has a host" do
+ expect(zero_client.host).to eq("localhost")
+ end
+
+ it "has a port" do
+ expect(zero_client.port).to eq(1)
+ end
+
+ describe "converting requests to rack format" do
+
+ let(:expected_rack_req) do
+ {
+ "SCRIPT_NAME" => "",
+ "SERVER_NAME" => "localhost",
+ "REQUEST_METHOD" => method.to_s.upcase,
+ "PATH_INFO" => uri.path,
+ "QUERY_STRING" => uri.query,
+ "SERVER_PORT" => uri.port,
+ "HTTP_HOST" => "localhost:#{uri.port}",
+ "rack.url_scheme" => "chefzero",
+ }
+ end
+
+ context "when the request has no body" do
+
+ let(:method) { :GET }
+ let(:relative_url) { "clients" }
+ let(:headers) { { "Accept" => "application/json" } }
+ let(:body) { false }
+ let(:expected_body_str) { "" }
+
+ let(:rack_req) { zero_client.req_to_rack(method, uri, body, headers) }
+
+ it "creates a rack request env" do
+ # StringIO doesn't implement == in a way that we can compare, so we
+ # check rack.input individually and then iterate over everything else
+ expect(rack_req["rack.input"].string).to eq(expected_body_str)
+ expected_rack_req.each do |key, value|
+ expect(rack_req[key]).to eq(value)
+ end
+ end
+
+ end
+
+ context "when the request has a body" do
+
+ let(:method) { :PUT }
+ let(:relative_url) { "clients/foo" }
+ let(:headers) { { "Accept" => "application/json" } }
+ let(:body) { "bunch o' JSON" }
+ let(:expected_body_str) { "bunch o' JSON" }
+
+ let(:rack_req) { zero_client.req_to_rack(method, uri, body, headers) }
+
+ it "creates a rack request env" do
+ # StringIO doesn't implement == in a way that we can compare, so we
+ # check rack.input individually and then iterate over everything else
+ expect(rack_req["rack.input"].string).to eq(expected_body_str)
+ expected_rack_req.each do |key, value|
+ expect(rack_req[key]).to eq(value)
+ end
+ end
+
+ end
+
+ end
+
+ describe "converting responses to Net::HTTP objects" do
+
+ let(:net_http_response) { zero_client.to_net_http(code, headers, body) }
+
+ context "when the request was successful (2XX)" do
+
+ let(:code) { 200 }
+ let(:headers) { { "Content-Type" => "Application/JSON" } }
+ let(:body) { [ "bunch o' JSON" ] }
+
+ it "creates a Net::HTTP success response object" do
+ expect(net_http_response).to be_a_kind_of(Net::HTTPOK)
+ expect(net_http_response.read_body).to eq("bunch o' JSON")
+ expect(net_http_response["content-type"]).to eq("Application/JSON")
+ end
+
+ it "does not fail when calling read_body with a block" do
+ expect(net_http_response.read_body {|chunk| chunk }).to eq("bunch o' JSON")
+ end
+
+ end
+
+ context "when the requested object doesn't exist (404)" do
+
+ let(:code) { 404 }
+ let(:headers) { { "Content-Type" => "Application/JSON" } }
+ let(:body) { [ "nope" ] }
+
+ it "creates a Net::HTTPNotFound response object" do
+ expect(net_http_response).to be_a_kind_of(Net::HTTPNotFound)
+ end
+ end
+
+ end
+
+ describe "request-response round trip" do
+
+ let(:method) { :GET }
+ let(:relative_url) { "clients" }
+ let(:headers) { { "Accept" => "application/json" } }
+ let(:body) { false }
+
+ let(:expected_rack_req) do
+ {
+ "SCRIPT_NAME" => "",
+ "SERVER_NAME" => "localhost",
+ "REQUEST_METHOD" => method.to_s.upcase,
+ "PATH_INFO" => uri.path,
+ "QUERY_STRING" => uri.query,
+ "SERVER_PORT" => uri.port,
+ "HTTP_HOST" => "localhost:#{uri.port}",
+ "rack.url_scheme" => "chefzero",
+ "rack.input" => an_instance_of(StringIO),
+ }
+ end
+
+
+ let(:response_code) { 200 }
+ let(:response_headers) { { "Content-Type" => "Application/JSON" } }
+ let(:response_body) { [ "bunch o' JSON" ] }
+
+ let(:rack_response) { [ response_code, response_headers, response_body ] }
+
+ let(:response) { zero_client.request(method, uri, body, headers) }
+
+ before do
+ expect(ChefZero::SocketlessServerMap).to receive(:request).with(1, expected_rack_req).and_return(rack_response)
+ end
+
+ it "makes a rack request to Chef Zero and returns the response as a Net::HTTP object" do
+ _client, net_http_response = response
+ expect(net_http_response).to be_a_kind_of(Net::HTTPOK)
+ expect(net_http_response.code).to eq("200")
+ expect(net_http_response.body).to eq("bunch o' JSON")
+ end
+
+ end
+
+end
diff --git a/spec/unit/http_spec.rb b/spec/unit/http_spec.rb
index ddfc56583d..4d851df951 100644
--- a/spec/unit/http_spec.rb
+++ b/spec/unit/http_spec.rb
@@ -20,6 +20,7 @@ require 'spec_helper'
require 'chef/http'
require 'chef/http/basic_client'
+require 'chef/http/socketless_chef_zero_client'
class Chef::HTTP
public :create_url
@@ -27,6 +28,19 @@ end
describe Chef::HTTP do
+ context "when given a chefzero:// URL" do
+
+ let(:uri) { URI("chefzero://localhost:1") }
+
+ subject(:http) { Chef::HTTP.new(uri) }
+
+ it "uses the SocketlessChefZeroClient to handle requests" do
+ expect(http.http_client).to be_a_kind_of(Chef::HTTP::SocketlessChefZeroClient)
+ expect(http.http_client.url).to eq(uri)
+ end
+
+ end
+
describe "create_url" do
it 'should return a correctly formatted url 1/3 CHEF-5261' do
diff --git a/spec/unit/knife_spec.rb b/spec/unit/knife_spec.rb
index 2ccf8493ad..b748232081 100644
--- a/spec/unit/knife_spec.rb
+++ b/spec/unit/knife_spec.rb
@@ -271,6 +271,11 @@ describe Chef::Knife do
expect(knife_command.config[:opt_with_default]).to eq("from-cli")
end
+ it "merges `listen` config to Chef::Config" do
+ Chef::Knife.run(%w[test yourself --no-listen], Chef::Application::Knife.options)
+ expect(Chef::Config[:listen]).to be(false)
+ end
+
context "verbosity is greater than zero" do
let(:fake_config) { "/does/not/exist/knife.rb" }
diff --git a/spec/unit/rest_spec.rb b/spec/unit/rest_spec.rb
index 1aa7ac84ee..85c9e3df8f 100644
--- a/spec/unit/rest_spec.rb
+++ b/spec/unit/rest_spec.rb
@@ -92,6 +92,15 @@ describe Chef::REST do
Chef::REST.new(base_url, nil, nil, options)
end
+ context 'when created with a chef zero URL' do
+
+ let(:url) { "chefzero://localhost:1" }
+
+ it "does not load the signing key" do
+ expect { Chef::REST.new(url) }.to_not raise_error
+ end
+ end
+
describe "calling an HTTP verb on a path or absolute URL" do
it "adds a relative URL to the base url it was initialized with" do
expect(rest.create_url("foo/bar/baz")).to eq(URI.parse(base_url + "/foo/bar/baz"))