summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThom May <thom@chef.io>2017-03-16 17:43:45 +0000
committerThom May <thom@chef.io>2017-03-16 20:23:40 +0000
commit6a16e21869e430352a6d4955843b79e3b6a63ff0 (patch)
tree15752ad125f2b6c92d57998896ffc9b3cf6c168a
parent1f23881a95d1dfa63158c7c35d3f8723df97a758 (diff)
downloadchef-tm/remove_chef_rest.tar.gz
Remove Chef::RESTtm/remove_chef_rest
Signed-off-by: Thom May <thom@chef.io>
-rw-r--r--RELEASE_NOTES.md3
-rw-r--r--lib/chef/application/solo.rb1
-rw-r--r--lib/chef/rest.rb210
-rw-r--r--spec/functional/rest_spec.rb95
-rw-r--r--spec/unit/rest/auth_credentials_spec.rb292
-rw-r--r--spec/unit/rest_spec.rb753
6 files changed, 3 insertions, 1351 deletions
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 0219369b7b..28468a1667 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -89,3 +89,6 @@ process of merging the node attributes strings and other simple objects are dup'
now correctly use the `node.dup` or `node.to_hash` methods, or you should mutate the object correctly through its precedence level like
`node.default["some_string"] << "appending_this"`.
+### The Chef::REST API has been removed
+
+It has been fully replaced with `Chef::ServerAPI` in chef-client code.
diff --git a/lib/chef/application/solo.rb b/lib/chef/application/solo.rb
index 1481338a9c..2705a930ae 100644
--- a/lib/chef/application/solo.rb
+++ b/lib/chef/application/solo.rb
@@ -23,7 +23,6 @@ require "chef/client"
require "chef/config"
require "chef/daemon"
require "chef/log"
-require "chef/rest"
require "chef/config_fetcher"
require "fileutils"
require "chef/mixin/shell_out"
diff --git a/lib/chef/rest.rb b/lib/chef/rest.rb
deleted file mode 100644
index 0705ca9f5a..0000000000
--- a/lib/chef/rest.rb
+++ /dev/null
@@ -1,210 +0,0 @@
-#--
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Thom May (<thom@clearairturbulence.org>)
-# Author:: Nuo Yan (<nuo@chef.io>)
-# Author:: Christopher Brown (<cb@chef.io>)
-# Author:: Christopher Walters (<cw@chef.io>)
-# Copyright:: Copyright 2009-2016, 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 "tempfile"
-require "chef/http"
-class Chef
- class HTTP; end
- class REST < HTTP; end
-end
-
-require "chef/http/authenticator"
-require "chef/http/decompressor"
-require "chef/http/json_input"
-require "chef/http/json_to_model_output"
-require "chef/http/cookie_manager"
-require "chef/http/validate_content_length"
-require "chef/config"
-require "chef/exceptions"
-require "chef/platform/query_helpers"
-require "chef/http/remote_request_id"
-require "chef/chef_class"
-
-class Chef
-
- # == Chef::REST
- # Chef's custom REST client with built-in JSON support and RSA signed header
- # authentication.
- class REST < HTTP
-
- # Backwards compatibility for things that use
- # Chef::REST::RESTRequest or its constants
- RESTRequest = HTTP::HTTPRequest
-
- attr_accessor :url, :cookies, :sign_on_redirect, :redirect_limit
-
- attr_reader :authenticator
-
- # Create a REST client object. The supplied +url+ is used as the base for
- # all subsequent requests. For example, when initialized with a base url
- # 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 = {})
- Chef.deprecated(:chef_rest, "Chef::REST is deprecated. Please use Chef::ServerAPI, or investigate Ridley or ChefAPI.")
-
- signing_key_filename = nil if chef_zero_uri?(url)
-
- options = options.dup
- options[:client_name] = client_name
- options[:signing_key_filename] = signing_key_filename
-
- super(url, options)
-
- @decompressor = Decompressor.new(options)
- @authenticator = Authenticator.new(options)
- @request_id = RemoteRequestID.new(options)
-
- @middlewares << JSONInput.new(options)
- @middlewares << JSONToModelOutput.new(options)
- @middlewares << CookieManager.new(options)
- @middlewares << @decompressor
- @middlewares << @authenticator
- @middlewares << @request_id
-
- # ValidateContentLength should come after Decompressor
- # because the order of middlewares is reversed when handling
- # responses.
- @middlewares << ValidateContentLength.new(options)
- end
-
- def signing_key_filename
- authenticator.signing_key_filename
- end
-
- def auth_credentials
- authenticator.auth_credentials
- end
-
- def client_name
- authenticator.client_name
- end
-
- def signing_key
- authenticator.raw_key
- end
-
- def sign_requests?
- authenticator.sign_requests?
- end
-
- # Send an HTTP GET request to the path
- #
- # Using this method to +fetch+ a file is considered deprecated.
- #
- # === Parameters
- # path:: The path to GET
- # raw:: Whether you want the raw body returned, or JSON inflated. Defaults
- # to JSON inflated.
- def get(path, raw = false, headers = {})
- if raw
- streaming_request(path, headers)
- else
- request(:GET, path, headers)
- end
- end
-
- alias :get_rest :get
-
- alias :delete_rest :delete
-
- alias :post_rest :post
-
- alias :put_rest :put
-
- # Streams a download to a tempfile, then yields the tempfile to a block.
- # After the download, the tempfile will be closed and unlinked.
- # If you rename the tempfile, it will not be deleted.
- # Beware that if the server streams infinite content, this method will
- # stream it until you run out of disk space.
- def fetch(path, headers = {})
- streaming_request(create_url(path), headers) { |tmp_file| yield tmp_file }
- end
-
- alias :api_request :request
-
- # Do a HTTP request where no middleware is loaded (e.g. JSON input/output
- # conversion) but the standard Chef Authentication headers are added to the
- # request.
- def raw_http_request(method, path, headers, data)
- url = create_url(path)
- method, url, headers, data = @authenticator.handle_request(method, url, headers, data)
- method, url, headers, data = @request_id.handle_request(method, url, headers, data)
- response, rest_request, return_value = send_http_request(method, url, headers, data)
- response.error! unless success_response?(response)
- return_value
- rescue Exception => exception
- log_failed_request(response, return_value) unless response.nil?
-
- if exception.respond_to?(:chef_rest_request=)
- exception.chef_rest_request = rest_request
- end
- raise
- end
-
- # Deprecated:
- # Responsibilities of this method have been split up. The #http_client is
- # now responsible for making individual requests, while
- # #retrying_http_errors handles error/retry logic.
- def retriable_http_request(method, url, req_body, headers)
- rest_request = Chef::HTTP::HTTPRequest.new(method, url, req_body, headers)
-
- Chef::Log.debug("Sending HTTP request via #{method} to #{url.host}:#{url.port}#{rest_request.path}")
-
- retrying_http_errors(url) do
- yield rest_request
- end
- end
-
- # Customized streaming behavior; sets the accepted content type to "*/*"
- # if not otherwise specified for compatibility purposes
- def streaming_request(url, headers, &block)
- headers["Accept"] ||= "*/*"
- super
- end
-
- alias :retriable_rest_request :retriable_http_request
-
- def follow_redirect
- unless @sign_on_redirect
- @authenticator.sign_request = false
- end
- super
- ensure
- @authenticator.sign_request = true
- end
-
- public :create_url
-
- ############################################################################
- # DEPRECATED
- ############################################################################
-
- def decompress_body(body)
- @decompressor.decompress_body(body)
- end
-
- def authentication_headers(method, url, json_body = nil)
- authenticator.authentication_headers(method, url, json_body)
- end
-
- end
-end
diff --git a/spec/functional/rest_spec.rb b/spec/functional/rest_spec.rb
deleted file mode 100644
index 14e76087c4..0000000000
--- a/spec/functional/rest_spec.rb
+++ /dev/null
@@ -1,95 +0,0 @@
-#
-# Author:: Lamont Granquist (<lamont@chef.io>)
-# Copyright:: Copyright 2014-2016, 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 "spec_helper"
-require "tiny_server"
-require "support/shared/functional/http"
-
-describe Chef::REST do
- include ChefHTTPShared
-
- let(:http_client) { described_class.new(source) }
- let(:http_client_disable_gzip) { described_class.new(source, Chef::Config[:node_name], Chef::Config[:client_key], { :disable_gzip => true } ) }
-
- shared_examples_for "downloads requests correctly" do
- it "successfully downloads a streaming request" do
- tempfile = http_client.streaming_request(source, {})
- tempfile.close
- expect(Digest::MD5.hexdigest(binread(tempfile.path))).to eq(Digest::MD5.hexdigest(expected_content))
- end
-
- it "successfully downloads a GET request" do
- tempfile = http_client.get(source, {})
- tempfile.close
- expect(Digest::MD5.hexdigest(binread(tempfile.path))).to eq(Digest::MD5.hexdigest(expected_content))
- end
- end
-
- shared_examples_for "validates content length and throws an exception" do
- it "fails validation on a streaming download" do
- expect { http_client.streaming_request(source, {}) }.to raise_error(Chef::Exceptions::ContentLengthMismatch)
- end
-
- it "fails validation on a GET request" do
- expect { http_client.get(source, {}) }.to raise_error(Chef::Exceptions::ContentLengthMismatch)
- end
- end
-
- shared_examples_for "an endpoint that 403s" do
- it "fails with a Net::HTTPServerException on a streaming download" do
- expect { http_client.streaming_request(source, {}) }.to raise_error(Net::HTTPServerException)
- end
-
- it "fails with a Net::HTTPServerException on a GET request" do
- expect { http_client.get(source, {}) }.to raise_error(Net::HTTPServerException)
- end
- end
-
- # see CHEF-5100
- shared_examples_for "a 403 after a successful request when reusing the request object" do
- it "fails with a Net::HTTPServerException on a streaming download" do
- tempfile = http_client.streaming_request(source, {})
- tempfile.close
- expect(Digest::MD5.hexdigest(binread(tempfile.path))).to eq(Digest::MD5.hexdigest(expected_content))
- expect { http_client.streaming_request(source2, {}) }.to raise_error(Net::HTTPServerException)
- end
-
- it "fails with a Net::HTTPServerException on a GET request" do
- tempfile = http_client.get(source, {})
- tempfile.close
- expect(Digest::MD5.hexdigest(binread(tempfile.path))).to eq(Digest::MD5.hexdigest(expected_content))
- expect { http_client.get(source2, {}) }.to raise_error(Net::HTTPServerException)
- end
- end
-
- before do
- Chef::Config[:node_name] = "webmonkey.example.com"
- Chef::Config[:client_key] = CHEF_SPEC_DATA + "/ssl/private_key.pem"
- Chef::Config[:treat_deprecation_warnings_as_errors] = false
- end
-
- before(:each) do
- start_tiny_server
- end
-
- after(:each) do
- stop_tiny_server
- end
-
- it_behaves_like "downloading all the things"
-end
diff --git a/spec/unit/rest/auth_credentials_spec.rb b/spec/unit/rest/auth_credentials_spec.rb
deleted file mode 100644
index 2728463c81..0000000000
--- a/spec/unit/rest/auth_credentials_spec.rb
+++ /dev/null
@@ -1,292 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Christopher Brown (<cb@chef.io>)
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
-# Copyright:: Copyright 2010-2016, 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 "spec_helper"
-require "uri"
-require "net/https"
-
-describe Chef::REST::AuthCredentials do
- before do
- @key_file_fixture = CHEF_SPEC_DATA + "/ssl/private_key.pem"
- @key = OpenSSL::PKey::RSA.new(IO.read(@key_file_fixture).strip)
- @auth_credentials = Chef::REST::AuthCredentials.new("client-name", @key)
- end
-
- it "has a client name" do
- expect(@auth_credentials.client_name).to eq("client-name")
- end
-
- it "loads the private key when initialized with the path to the key" do
- expect(@auth_credentials.key).to respond_to(:private_encrypt)
- expect(@auth_credentials.key).to eq(@key)
- end
-
- describe "when loading the private key" do
- it "strips extra whitespace before checking the key" do
- key_file_fixture = CHEF_SPEC_DATA + "/ssl/private_key_with_whitespace.pem"
- expect { Chef::REST::AuthCredentials.new("client-name", @key_file_fixture) }.not_to raise_error
- end
- end
-
- describe "generating signature headers for a request" do
- before do
- @request_time = Time.at(1270920860)
- @request_params = { :http_method => :POST, :path => "/clients", :body => '{"some":"json"}', :host => "localhost" }
- allow(Chef::Config).to(
- receive(:[]).with(:authentication_protocol_version).and_return(protocol_version))
- end
-
- context "when configured for version 1.0 of the authn protocol" do
- let(:protocol_version) { "1.0" }
-
- it "generates signature headers for the request" do
- allow(Time).to receive(:now).and_return(@request_time)
- actual = @auth_credentials.signature_headers(@request_params)
- expect(actual["HOST"]).to eq("localhost")
- expect(actual["X-OPS-AUTHORIZATION-1"]).to eq("kBssX1ENEwKtNYFrHElN9vYGWS7OeowepN9EsYc9csWfh8oUovryPKDxytQ/")
- expect(actual["X-OPS-AUTHORIZATION-2"]).to eq("Wc2/nSSyxdWJjjfHzrE+YrqNQTaArOA7JkAf5p75eTUonCWcvNPjFrZVgKGS")
- expect(actual["X-OPS-AUTHORIZATION-3"]).to eq("yhzHJQh+lcVA9wwARg5Hu9q+ddS8xBOdm3Vp5atl5NGHiP0loiigMYvAvzPO")
- expect(actual["X-OPS-AUTHORIZATION-4"]).to eq("r9853eIxwYMhn5hLGhAGFQznJbE8+7F/lLU5Zmk2t2MlPY8q3o1Q61YD8QiJ")
- expect(actual["X-OPS-AUTHORIZATION-5"]).to eq("M8lIt53ckMyUmSU0DDURoiXLVkE9mag/6Yq2tPNzWq2AdFvBqku9h2w+DY5k")
- expect(actual["X-OPS-AUTHORIZATION-6"]).to eq("qA5Rnzw5rPpp3nrWA9jKkPw4Wq3+4ufO2Xs6w7GCjA==")
- expect(actual["X-OPS-CONTENT-HASH"]).to eq("1tuzs5XKztM1ANrkGNPah6rW9GY=")
- expect(actual["X-OPS-SIGN"]).to match(%r{(version=1\.0)|(algorithm=sha1;version=1.0;)})
- expect(actual["X-OPS-TIMESTAMP"]).to eq("2010-04-10T17:34:20Z")
- expect(actual["X-OPS-USERID"]).to eq("client-name")
- end
- end
-
- context "when configured for version 1.1 of the authn protocol" do
- let(:protocol_version) { "1.1" }
-
- it "generates the correct signature for version 1.1" do
- allow(Time).to receive(:now).and_return(@request_time)
- actual = @auth_credentials.signature_headers(@request_params)
- expect(actual["HOST"]).to eq("localhost")
- expect(actual["X-OPS-CONTENT-HASH"]).to eq("1tuzs5XKztM1ANrkGNPah6rW9GY=")
- expect(actual["X-OPS-SIGN"]).to eq("algorithm=sha1;version=1.1;")
- expect(actual["X-OPS-TIMESTAMP"]).to eq("2010-04-10T17:34:20Z")
- expect(actual["X-OPS-USERID"]).to eq("client-name")
-
- # mixlib-authN will test the actual signature stuff for each version of
- # the protocol so we won't test it again here.
- end
- end
- end
-end
-
-describe Chef::REST::RESTRequest do
- let(:url) { URI.parse("http://chef.example.com:4000/?q=chef_is_awesome") }
-
- def new_request(method = nil)
- method ||= :POST
- Chef::REST::RESTRequest.new(method, url, @req_body, @headers)
- end
-
- before do
- @auth_credentials = Chef::REST::AuthCredentials.new("client-name", CHEF_SPEC_DATA + "/ssl/private_key.pem")
- @req_body = '{"json_data":"as_a_string"}'
- @headers = { "Content-type" => "application/json",
- "Accept" => "application/json",
- "Accept-Encoding" => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE,
- "Host" => "chef.example.com:4000" }
- @request = Chef::REST::RESTRequest.new(:POST, url, @req_body, @headers)
- end
-
- it "stores the url it was created with" do
- expect(@request.url).to eq(url)
- end
-
- it "stores the HTTP method" do
- expect(@request.method).to eq(:POST)
- end
-
- it "adds the chef version header" do
- expect(@request.headers).to eq(@headers.merge("X-Chef-Version" => ::Chef::VERSION))
- end
-
- describe "configuring the HTTP request" do
- let(:url) do
- URI.parse("http://homie:theclown@chef.example.com:4000/?q=chef_is_awesome")
- end
-
- it "configures GET requests" do
- @req_body = nil
- rest_req = new_request(:GET)
- expect(rest_req.http_request).to be_a_kind_of(Net::HTTP::Get)
- expect(rest_req.http_request.path).to eq("/?q=chef_is_awesome")
- expect(rest_req.http_request.body).to be_nil
- end
-
- it "configures POST requests, including the body" do
- expect(@request.http_request).to be_a_kind_of(Net::HTTP::Post)
- expect(@request.http_request.path).to eq("/?q=chef_is_awesome")
- expect(@request.http_request.body).to eq(@req_body)
- end
-
- it "configures PUT requests, including the body" do
- rest_req = new_request(:PUT)
- expect(rest_req.http_request).to be_a_kind_of(Net::HTTP::Put)
- expect(rest_req.http_request.path).to eq("/?q=chef_is_awesome")
- expect(rest_req.http_request.body).to eq(@req_body)
- end
-
- it "configures DELETE requests" do
- rest_req = new_request(:DELETE)
- expect(rest_req.http_request).to be_a_kind_of(Net::HTTP::Delete)
- expect(rest_req.http_request.path).to eq("/?q=chef_is_awesome")
- expect(rest_req.http_request.body).to be_nil
- end
-
- it "configures HTTP basic auth" do
- rest_req = new_request(:GET)
- expect(rest_req.http_request.to_hash["authorization"]).to eq(["Basic aG9taWU6dGhlY2xvd24="])
- end
- end
-
- describe "configuring the HTTP client" do
- it "configures the HTTP client for the host and port" do
- http_client = new_request.http_client
- expect(http_client.address).to eq("chef.example.com")
- expect(http_client.port).to eq(4000)
- end
-
- it "configures the HTTP client with the read timeout set in the config file" do
- Chef::Config[:rest_timeout] = 9001
- expect(new_request.http_client.read_timeout).to eq(9001)
- end
-
- describe "for proxy" do
- before do
- stub_const("ENV", "http_proxy" => "http://proxy.example.com:3128",
- "https_proxy" => "http://sproxy.example.com:3129",
- "http_proxy_user" => nil,
- "http_proxy_pass" => nil,
- "https_proxy_user" => nil,
- "https_proxy_pass" => nil,
- "no_proxy" => nil
- )
- end
-
- describe "with :no_proxy nil" do
- it "configures the proxy address and port when using http scheme" do
- http_client = new_request.http_client
- expect(http_client.proxy?).to eq(true)
- expect(http_client.proxy_address).to eq("proxy.example.com")
- expect(http_client.proxy_port).to eq(3128)
- expect(http_client.proxy_user).to be_nil
- expect(http_client.proxy_pass).to be_nil
- end
-
- context "when the url has an https scheme" do
- let(:url) { URI.parse("https://chef.example.com:4000/?q=chef_is_awesome") }
-
- it "configures the proxy address and port when using https scheme" do
- http_client = new_request.http_client
- expect(http_client.proxy?).to eq(true)
- expect(http_client.proxy_address).to eq("sproxy.example.com")
- expect(http_client.proxy_port).to eq(3129)
- expect(http_client.proxy_user).to be_nil
- expect(http_client.proxy_pass).to be_nil
- end
- end
- end
-
- describe "with :no_proxy set" do
- before do
- stub_const("ENV", "no_proxy" => "10.*,*.example.com")
- end
-
- it "does not configure the proxy address and port when using http scheme" do
- http_client = new_request.http_client
- expect(http_client.proxy?).to eq(false)
- expect(http_client.proxy_address).to be_nil
- expect(http_client.proxy_port).to be_nil
- expect(http_client.proxy_user).to be_nil
- expect(http_client.proxy_pass).to be_nil
- end
-
- context "when the url has an https scheme" do
- let(:url) { URI.parse("https://chef.example.com:4000/?q=chef_is_awesome") }
-
- it "does not configure the proxy address and port when using https scheme" do
- http_client = new_request.http_client
- expect(http_client.proxy?).to eq(false)
- expect(http_client.proxy_address).to be_nil
- expect(http_client.proxy_port).to be_nil
- expect(http_client.proxy_user).to be_nil
- expect(http_client.proxy_pass).to be_nil
- end
- end
- end
-
- describe "with :http_proxy_user and :http_proxy_pass set" do
- before do
- stub_const("ENV", "http_proxy" => "http://homie:theclown@proxy.example.com:3128")
- end
-
- it "configures the proxy user and pass when using http scheme" do
- http_client = new_request.http_client
- expect(http_client.proxy?).to eq(true)
- expect(http_client.proxy_user).to eq("homie")
- expect(http_client.proxy_pass).to eq("theclown")
- end
-
- context "when the url has an https scheme" do
- let(:url) { URI.parse("https://chef.example.com:4000/?q=chef_is_awesome") }
-
- it "does not configure the proxy user and pass when using https scheme" do
- http_client = new_request.http_client
- expect(http_client.proxy?).to eq(false)
- expect(http_client.proxy_user).to be_nil
- expect(http_client.proxy_pass).to be_nil
- end
- end
- end
-
- describe "with :https_proxy_user and :https_proxy_pass set" do
- before do
- stub_const("ENV", "http_proxy" => "http://proxy.example.com:3128",
- "https_proxy" => "https://homie:theclown@sproxy.example.com:3129"
- )
- end
-
- it "does not configure the proxy user and pass when using http scheme" do
- http_client = new_request.http_client
- expect(http_client.proxy?).to eq(true)
- expect(http_client.proxy_user).to be_nil
- expect(http_client.proxy_pass).to be_nil
- end
-
- context "when the url has an https scheme" do
- let(:url) { URI.parse("https://chef.example.com:4000/?q=chef_is_awesome") }
-
- it "configures the proxy user and pass when using https scheme" do
- http_client = new_request.http_client
- expect(http_client.proxy?).to eq(true)
- expect(http_client.proxy_user).to eq("homie")
- expect(http_client.proxy_pass).to eq("theclown")
- end
- end
- end
- end
- end
-end
diff --git a/spec/unit/rest_spec.rb b/spec/unit/rest_spec.rb
deleted file mode 100644
index ea3bd88023..0000000000
--- a/spec/unit/rest_spec.rb
+++ /dev/null
@@ -1,753 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Christopher Brown (<cb@chef.io>)
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright 2008-2016, Chef Software Inc.
-# Copyright:: Copyright 2010-2016, 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 "spec_helper"
-require "uri"
-require "net/https"
-require "stringio"
-
-SIGNING_KEY_DOT_PEM = "-----BEGIN RSA PRIVATE KEY-----
-MIIEpAIBAAKCAQEA49TA0y81ps0zxkOpmf5V4/c4IeR5yVyQFpX3JpxO4TquwnRh
-8VSUhrw8kkTLmB3cS39Db+3HadvhoqCEbqPE6915kXSuk/cWIcNozujLK7tkuPEy
-YVsyTioQAddSdfe+8EhQVf3oHxaKmUd6waXrWqYCnhxgOjxocenREYNhZ/OETIei
-PbOku47vB4nJK/0GhKBytL2XnsRgfKgDxf42BqAi1jglIdeq8lAWZNF9TbNBU21A
-O1iuT7Pm6LyQujhggPznR5FJhXKRUARXBJZawxpGV4dGtdcahwXNE4601aXPra+x
-PcRd2puCNoEDBzgVuTSsLYeKBDMSfs173W1QYwIDAQABAoIBAGF05q7vqOGbMaSD
-2Q7YbuE/JTHKTBZIlBI1QC2x+0P5GDxyEFttNMOVzcs7xmNhkpRw8eX1LrInrpMk
-WsIBKAFFEfWYlf0RWtRChJjNl+szE9jQxB5FJnWtJH/FHa78tR6PsF24aQyzVcJP
-g0FGujBihwgfV0JSCNOBkz8MliQihjQA2i8PGGmo4R4RVzGfxYKTIq9vvRq/+QEa
-Q4lpVLoBqnENpnY/9PTl6JMMjW2b0spbLjOPVwDaIzXJ0dChjNXo15K5SHI5mALJ
-I5gN7ODGb8PKUf4619ez194FXq+eob5YJdilTFKensIUvt3YhP1ilGMM+Chi5Vi/
-/RCTw3ECgYEA9jTw4wv9pCswZ9wbzTaBj9yZS3YXspGg26y6Ohq3ZmvHz4jlT6uR
-xK+DDcUiK4072gci8S4Np0fIVS7q6ivqcOdzXPrTF5/j+MufS32UrBbUTPiM1yoO
-ECcy+1szl/KoLEV09bghPbvC58PFSXV71evkaTETYnA/F6RK12lEepcCgYEA7OSy
-bsMrGDVU/MKJtwqyGP9ubA53BorM4Pp9VVVSCrGGVhb9G/XNsjO5wJC8J30QAo4A
-s59ZzCpyNRy046AB8jwRQuSwEQbejSdeNgQGXhZ7aIVUtuDeFFdaIz/zjVgxsfj4
-DPOuzieMmJ2MLR4F71ocboxNoDI7xruPSE8dDhUCgYA3vx732cQxgtHwAkeNPJUz
-dLiE/JU7CnxIoSB9fYUfPLI+THnXgzp7NV5QJN2qzMzLfigsQcg3oyo6F2h7Yzwv
-GkjlualIRRzCPaCw4Btkp7qkPvbs1QngIHALt8fD1N69P3DPHkTwjG4COjKWgnJq
-qoHKS6Fe/ZlbigikI6KsuwKBgQCTlSLoyGRHr6oj0hqz01EDK9ciMJzMkZp0Kvn8
-OKxlBxYW+jlzut4MQBdgNYtS2qInxUoAnaz2+hauqhSzntK3k955GznpUatCqx0R
-b857vWviwPX2/P6+E3GPdl8IVsKXCvGWOBZWTuNTjQtwbDzsUepWoMgXnlQJSn5I
-YSlLxQKBgQD16Gw9kajpKlzsPa6XoQeGmZALT6aKWJQlrKtUQIrsIWM0Z6eFtX12
-2jjHZ0awuCQ4ldqwl8IfRogWMBkHOXjTPVK0YKWWlxMpD/5+bGPARa5fir8O1Zpo
-Y6S6MeZ69Rp89ma4ttMZ+kwi1+XyHqC/dlcVRW42Zl5Dc7BALRlJjQ==
------END RSA PRIVATE KEY-----"
-
-describe Chef::REST do
- let(:base_url) { "http://chef.example.com:4000" }
-
- let(:monkey_uri) { URI.parse("http://chef.example.com:4000/monkey") }
-
- let(:log_stringio) { StringIO.new }
-
- let(:request_id) { "1234" }
-
- let(:rest) do
- allow(Chef::REST::CookieJar).to receive(:instance).and_return({})
- allow(Chef::RequestID.instance).to receive(:request_id).and_return(request_id)
- rest = Chef::REST.new(base_url, nil, nil)
- Chef::REST::CookieJar.instance.clear
- rest
- end
-
- let(:standard_read_headers) { { "Accept" => "application/json", "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-REMOTE-REQUEST-ID" => request_id, "X-Ops-Server-API-Version" => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION } }
- let(:standard_write_headers) { { "Accept" => "application/json", "Content-Type" => "application/json", "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-REMOTE-REQUEST-ID" => request_id, "X-Ops-Server-API-Version" => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION } }
-
- before(:each) do
- Chef::Log.init(log_stringio)
- Chef::Config[:treat_deprecation_warnings_as_errors] = false
- end
-
- it "should have content length validation middleware after compressor middleware" do
- middlewares = rest.instance_variable_get(:@middlewares)
- content_length = middlewares.find_index { |e| e.is_a? Chef::HTTP::ValidateContentLength }
- decompressor = middlewares.find_index { |e| e.is_a? Chef::HTTP::Decompressor }
-
- expect(content_length).not_to be_nil
- expect(decompressor).not_to be_nil
- expect(decompressor < content_length).to be_truthy
- end
-
- it "should allow the options hash to be frozen" do
- options = {}.freeze
- # should not raise any exception
- Chef::REST.new(base_url, nil, nil, options)
- end
-
- it "emits a deprecation warning" do
- Chef::Config[:treat_deprecation_warnings_as_errors] = true
- expect { Chef::REST.new(base_url) }.to raise_error Chef::Exceptions::DeprecatedFeatureError,
- /Chef::REST is deprecated. Please use Chef::ServerAPI, or investigate Ridley or ChefAPI./
- 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"))
- end
-
- it "replaces the base URL when given an absolute URL" do
- expect(rest.create_url("http://chef-rulez.example.com:9000")).to eq(URI.parse("http://chef-rulez.example.com:9000"))
- end
-
- it "makes a :GET request with the composed url object" do
- expect(rest).to receive(:send_http_request).
- with(:GET, monkey_uri, standard_read_headers, false).
- and_return([1, 2, 3])
- expect(rest).to receive(:apply_response_middleware).with(1, 2, 3).and_return([1, 2, 3])
- expect(rest).to receive("success_response?".to_sym).with(1).and_return(true)
- rest.get_rest("monkey")
- end
-
- it "makes a :GET reqest for a streaming download with the composed url" do
- expect(rest).to receive(:streaming_request).with("monkey", {})
- rest.get_rest("monkey", true)
- end
-
- it "makes a :DELETE request with the composed url object" do
- expect(rest).to receive(:send_http_request).
- with(:DELETE, monkey_uri, standard_read_headers, false).
- and_return([1, 2, 3])
- expect(rest).to receive(:apply_response_middleware).with(1, 2, 3).and_return([1, 2, 3])
- expect(rest).to receive("success_response?".to_sym).with(1).and_return(true)
- rest.delete_rest("monkey")
- end
-
- it "makes a :POST request with the composed url object and data" do
- expect(rest).to receive(:send_http_request).
- with(:POST, monkey_uri, standard_write_headers, "\"data\"").
- and_return([1, 2, 3])
- expect(rest).to receive(:apply_response_middleware).with(1, 2, 3).and_return([1, 2, 3])
- expect(rest).to receive("success_response?".to_sym).with(1).and_return(true)
- rest.post_rest("monkey", "data")
- end
-
- it "makes a :PUT request with the composed url object and data" do
- expect(rest).to receive(:send_http_request).
- with(:PUT, monkey_uri, standard_write_headers, "\"data\"").
- and_return([1, 2, 3])
- expect(rest).to receive(:apply_response_middleware).with(1, 2, 3).and_return([1, 2, 3])
- expect(rest).to receive("success_response?".to_sym).with(1).and_return(true)
- rest.put_rest("monkey", "data")
- end
- end
-
- describe "legacy API" do
- let(:rest) do
- Chef::REST.new(base_url)
- end
-
- before(:each) do
- Chef::Config[:node_name] = "webmonkey.example.com"
- Chef::Config[:client_key] = CHEF_SPEC_DATA + "/ssl/private_key.pem"
- end
-
- it "responds to raw_http_request as a public method" do
- expect(rest.public_methods.map(&:to_s)).to include("raw_http_request")
- end
-
- it "calls the authn middleware" do
- data = "\"secure data\""
-
- auth_headers = standard_write_headers.merge({ "auth_done" => "yep" })
-
- expect(rest.authenticator).to receive(:handle_request).
- with(:POST, monkey_uri, standard_write_headers, data).
- and_return([:POST, monkey_uri, auth_headers, data])
- expect(rest).to receive(:send_http_request).
- with(:POST, monkey_uri, auth_headers, data).
- and_return([1, 2, 3])
- expect(rest).to receive("success_response?".to_sym).with(1).and_return(true)
- rest.raw_http_request(:POST, monkey_uri, standard_write_headers, data)
- end
-
- it "sets correct authn headers" do
- data = "\"secure data\""
- method, uri, auth_headers, d = rest.authenticator.handle_request(:POST, monkey_uri, standard_write_headers, data)
-
- expect(rest).to receive(:send_http_request).
- with(:POST, monkey_uri, auth_headers, data).
- and_return([1, 2, 3])
- expect(rest).to receive("success_response?".to_sym).with(1).and_return(true)
- rest.raw_http_request(:POST, monkey_uri, standard_write_headers, data)
- end
- end
-
- describe "when configured to authenticate to the Chef server" do
- let(:base_url) { URI.parse("http://chef.example.com:4000") }
-
- let(:rest) do
- Chef::REST.new(base_url)
- end
-
- before do
- Chef::Config[:node_name] = "webmonkey.example.com"
- Chef::Config[:client_key] = CHEF_SPEC_DATA + "/ssl/private_key.pem"
- end
-
- it "configures itself to use the node_name and client_key in the config by default" do
- expect(rest.client_name).to eq("webmonkey.example.com")
- expect(rest.signing_key_filename).to eq(CHEF_SPEC_DATA + "/ssl/private_key.pem")
- end
-
- it "provides access to the raw key data" do
- expect(rest.signing_key).to eq(SIGNING_KEY_DOT_PEM)
- end
-
- it "does not error out when initialized without credentials" do
- rest = Chef::REST.new(base_url, nil, nil) #should_not raise_error hides the bt from you, so screw it.
- expect(rest.client_name).to be_nil
- expect(rest.signing_key).to be_nil
- end
-
- it "indicates that requests should not be signed when it has no credentials" do
- rest = Chef::REST.new(base_url, nil, nil)
- expect(rest.sign_requests?).to be_falsey
- end
-
- it "raises PrivateKeyMissing when the key file doesn't exist" do
- expect { Chef::REST.new(base_url, "client-name", "/dev/null/nothing_here") }.to raise_error(Chef::Exceptions::PrivateKeyMissing)
- end
-
- it "raises InvalidPrivateKey when the key file doesnt' look like a key" do
- invalid_key_file = CHEF_SPEC_DATA + "/bad-config.rb"
- expect { Chef::REST.new(base_url, "client-name", invalid_key_file) }.to raise_error(Chef::Exceptions::InvalidPrivateKey)
- end
-
- it "can take private key as a sting :raw_key in options during initializaton" do
- expect(Chef::REST.new(base_url, "client-name", nil, :raw_key => SIGNING_KEY_DOT_PEM).signing_key).to eq(SIGNING_KEY_DOT_PEM)
- end
-
- it "raises InvalidPrivateKey when the key passed as string :raw_key in options doesnt' look like a key" do
- expect { Chef::REST.new(base_url, "client-name", nil, :raw_key => "bad key string") }.to raise_error(Chef::Exceptions::InvalidPrivateKey)
- end
-
- end
-
- context "when making REST requests" do
- let(:body) { "ninja" }
-
- let(:http_response) do
- http_response = Net::HTTPSuccess.new("1.1", "200", "successful rest req")
- allow(http_response).to receive(:read_body)
- allow(http_response).to receive(:body).and_return(body)
- http_response["Content-Length"] = body.bytesize.to_s
- http_response
- end
-
- let(:host_header) { "one" }
-
- let(:url) { URI.parse("http://one:80/?foo=bar") }
-
- let(:base_url) { "http://chef.example.com:4000" }
-
- let!(:http_client) do
- http_client = Net::HTTP.new(url.host, url.port)
- allow(http_client).to receive(:request).and_yield(http_response).and_return(http_response)
- http_client
- end
-
- let(:rest) do
- allow(Net::HTTP).to receive(:new).and_return(http_client)
- allow(Chef::REST::CookieJar).to receive(:instance).and_return({})
- allow(Chef::RequestID.instance).to receive(:request_id).and_return(request_id)
- rest = Chef::REST.new(base_url, nil, nil)
- Chef::REST::CookieJar.instance.clear
- rest
- end
-
- before(:each) do
- Chef::Config[:ssl_client_cert] = nil
- Chef::Config[:ssl_client_key] = nil
- end
-
- describe "as JSON API requests" do
- let(:request_mock) { {} }
-
- let(:base_headers) do #FIXME: huh?
- {
- "Accept" => "application/json",
- "X-Chef-Version" => Chef::VERSION,
- "Accept-Encoding" => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE,
- "Host" => host_header,
- "X-REMOTE-REQUEST-ID" => request_id,
- "X-Ops-Server-API-Version" => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION,
- }
- end
-
- before do
- allow(Net::HTTP::Get).to receive(:new).and_return(request_mock)
- end
-
- it "should always include the X-Chef-Version header" do
- expect(Net::HTTP::Get).to receive(:new).with("/?foo=bar", base_headers).and_return(request_mock)
- rest.request(:GET, url, {})
- end
-
- it "should always include the X-Remote-Request-Id header" do
- expect(Net::HTTP::Get).to receive(:new).with("/?foo=bar", base_headers).and_return(request_mock)
- rest.request(:GET, url, {})
- end
-
- it "sets the user agent to chef-client" do
- # XXX: must reset to default b/c knife changes the UA
- Chef::REST::RESTRequest.user_agent = Chef::REST::RESTRequest::DEFAULT_UA
- rest.request(:GET, url, {})
- expect(request_mock["User-Agent"]).to match(/^Chef Client\/#{Chef::VERSION}/)
- end
-
- # CHEF-3140
- context "when configured to disable compression" do
- let(:rest) do
- allow(Net::HTTP).to receive(:new).and_return(http_client)
- Chef::REST.new(base_url, nil, nil, :disable_gzip => true)
- end
-
- it "does not accept encoding gzip" do
- expect(rest.send(:build_headers, :GET, url, {})).not_to have_key("Accept-Encoding")
- end
-
- it "does not decompress a response encoded as gzip" do
- http_response.add_field("content-encoding", "gzip")
- request = Net::HTTP::Get.new(url.path)
- expect(Net::HTTP::Get).to receive(:new).and_return(request)
- # will raise a Zlib error if incorrect
- expect(rest.request(:GET, url, {})).to eq("ninja")
- end
- end
-
- context "when configured with custom http headers" do
- let(:custom_headers) do
- {
- "X-Custom-ChefSecret" => "sharpknives",
- "X-Custom-RequestPriority" => "extremely low",
- }
- end
-
- before(:each) do
- Chef::Config[:custom_http_headers] = custom_headers
- end
-
- after(:each) do
- Chef::Config[:custom_http_headers] = nil
- end
-
- it "should set them on the http request" do
- url_string = an_instance_of(String)
- header_hash = hash_including(custom_headers)
- expect(Net::HTTP::Get).to receive(:new).with(url_string, header_hash)
- rest.request(:GET, url, {})
- end
- end
-
- context "when setting cookies" do
- let(:rest) do
- allow(Net::HTTP).to receive(:new).and_return(http_client)
- Chef::REST::CookieJar.instance["#{url.host}:#{url.port}"] = "cookie monster"
- allow(Chef::RequestID.instance).to receive(:request_id).and_return(request_id)
- rest = Chef::REST.new(base_url, nil, nil)
- rest
- end
-
- it "should set the cookie for this request if one exists for the given host:port" do
- expect(Net::HTTP::Get).to receive(:new).with("/?foo=bar", base_headers.merge("Cookie" => "cookie monster")).and_return(request_mock)
- rest.request(:GET, url, {})
- end
- end
-
- it "should build a new HTTP GET request" do
- expect(Net::HTTP::Get).to receive(:new).with("/?foo=bar", base_headers).and_return(request_mock)
- rest.request(:GET, url, {})
- end
-
- it "should build a new HTTP POST request" do
- request = Net::HTTP::Post.new(url.path)
- expected_headers = base_headers.merge("Content-Type" => "application/json", "Content-Length" => "13")
-
- expect(Net::HTTP::Post).to receive(:new).with("/?foo=bar", expected_headers).and_return(request)
- rest.request(:POST, url, {}, { :one => :two })
- expect(request.body).to eq('{"one":"two"}')
- end
-
- it "should build a new HTTP PUT request" do
- request = Net::HTTP::Put.new(url.path)
- expected_headers = base_headers.merge("Content-Type" => "application/json", "Content-Length" => "13")
- expect(Net::HTTP::Put).to receive(:new).with("/?foo=bar", expected_headers).and_return(request)
- rest.request(:PUT, url, {}, { :one => :two })
- expect(request.body).to eq('{"one":"two"}')
- end
-
- it "should build a new HTTP DELETE request" do
- expect(Net::HTTP::Delete).to receive(:new).with("/?foo=bar", base_headers).and_return(request_mock)
- rest.request(:DELETE, url)
- end
-
- it "should raise an error if the method is not GET/PUT/POST/DELETE" do
- expect { rest.request(:MONKEY, url) }.to raise_error(ArgumentError)
- end
-
- it "returns nil when the response is successful but content-type is not JSON" do
- expect(rest.request(:GET, url)).to eq("ninja")
- end
-
- it "should fail if the response is truncated" do
- http_response["Content-Length"] = (body.bytesize + 99).to_s
- expect { rest.request(:GET, url) }.to raise_error(Chef::Exceptions::ContentLengthMismatch)
- end
-
- context "when JSON is returned" do
- let(:body) { '{"ohai2u":"json_api"}' }
- it "should inflate the body as to an object" do
- http_response.add_field("content-type", "application/json")
- expect(rest.request(:GET, url, {})).to eq({ "ohai2u" => "json_api" })
- end
-
- it "should fail if the response is truncated" do
- http_response.add_field("content-type", "application/json")
- http_response["Content-Length"] = (body.bytesize + 99).to_s
- expect { rest.request(:GET, url, {}) }.to raise_error(Chef::Exceptions::ContentLengthMismatch)
- end
- end
-
- %w{ HTTPFound HTTPMovedPermanently HTTPSeeOther HTTPUseProxy HTTPTemporaryRedirect HTTPMultipleChoice }.each do |resp_name|
- describe "when encountering a #{resp_name} redirect" do
- let(:http_response) do
- resp_cls = Net.const_get(resp_name)
- resp_code = Net::HTTPResponse::CODE_TO_OBJ.keys.detect { |k| Net::HTTPResponse::CODE_TO_OBJ[k] == resp_cls }
- http_response = Net::HTTPFound.new("1.1", resp_code, "bob is somewhere else again")
- http_response.add_field("location", url.path)
- allow(http_response).to receive(:read_body)
- http_response
- end
- it "should call request again" do
-
- expect { rest.request(:GET, url) }.to raise_error(Chef::Exceptions::RedirectLimitExceeded)
-
- [:PUT, :POST, :DELETE].each do |method|
- expect { rest.request(method, url) }.to raise_error(Chef::Exceptions::InvalidRedirect)
- end
- end
- end
- end
-
- context "when the response is 304 NotModified" do
- let (:http_response) do
- http_response = Net::HTTPNotModified.new("1.1", "304", "it's the same as when you asked 5 minutes ago")
- allow(http_response).to receive(:read_body)
- http_response
- end
-
- it "should return `false`" do
- expect(rest.request(:GET, url)).to be_falsey
- end
- end
-
- describe "when the request fails" do
- before do
- @original_log_level = Chef::Log.level
- Chef::Log.level = :info
- end
-
- after do
- Chef::Log.level = @original_log_level
- end
-
- context "on an unsuccessful response with a JSON error" do
- let(:http_response) do
- http_response = Net::HTTPServerError.new("1.1", "500", "drooling from inside of mouth")
- http_response.add_field("content-type", "application/json")
- allow(http_response).to receive(:body).and_return('{ "error":[ "Ears get sore!", "Not even four" ] }')
- allow(http_response).to receive(:read_body)
- http_response
- end
-
- it "should show the JSON error message" do
- allow(rest).to receive(:sleep)
-
- expect { rest.request(:GET, url) }.to raise_error(Net::HTTPFatalError)
- expect(log_stringio.string).to match(Regexp.escape("INFO: HTTP Request Returned 500 drooling from inside of mouth: Ears get sore!, Not even four"))
- end
- end
-
- context "on an unsuccessful response with a JSON error that is compressed" do
- let(:http_response) do
- http_response = Net::HTTPServerError.new("1.1", "500", "drooling from inside of mouth")
- http_response.add_field("content-type", "application/json")
- http_response.add_field("content-encoding", "deflate")
- unzipped_body = '{ "error":[ "Ears get sore!", "Not even four" ] }'
- gzipped_body = Zlib::Deflate.deflate(unzipped_body)
- gzipped_body.force_encoding(Encoding::BINARY) if "strings".respond_to?(:force_encoding)
-
- allow(http_response).to receive(:body).and_return gzipped_body
- allow(http_response).to receive(:read_body)
- http_response
- end
-
- before do
- allow(rest).to receive(:sleep)
- allow(rest).to receive(:http_retry_count).and_return(0)
- end
-
- it "decompresses the JSON error message" do
- expect { rest.request(:GET, url) }.to raise_error(Net::HTTPFatalError)
- expect(log_stringio.string).to match(Regexp.escape("INFO: HTTP Request Returned 500 drooling from inside of mouth: Ears get sore!, Not even four"))
- end
-
- it "fails when the compressed body is truncated" do
- http_response["Content-Length"] = (body.bytesize + 99).to_s
- expect { rest.request(:GET, url) }.to raise_error(Chef::Exceptions::ContentLengthMismatch)
- end
- end
-
- context "on a generic unsuccessful request" do
- let(:http_response) do
- http_response = Net::HTTPServerError.new("1.1", "500", "drooling from inside of mouth")
- allow(http_response).to receive(:body)
- allow(http_response).to receive(:read_body)
- http_response
- end
-
- it "retries then throws an exception" do
- allow(rest).to receive(:sleep)
- expect { rest.request(:GET, url) }.to raise_error(Net::HTTPFatalError)
- count = Chef::Config[:http_retry_count]
- expect(log_stringio.string).to match(Regexp.escape("ERROR: Server returned error 500 for #{url}, retrying #{count}/#{count}"))
- end
- end
- end
- end # as JSON API requests
-
- context "when streaming downloads to a tempfile" do
- let!(:tempfile) { Tempfile.open("chef-rspec-rest_spec-line-@{__LINE__}--") }
-
- let(:request_mock) { {} }
-
- let(:http_response) do
- http_response = Net::HTTPSuccess.new("1.1", "200", "it-works")
-
- allow(http_response).to receive(:read_body)
- expect(http_response).not_to receive(:body)
- http_response["Content-Length"] = "0" # call set_content_length (in test), if otherwise
- http_response
- end
-
- def set_content_length
- content_length = 0
- http_response.read_body do |chunk|
- content_length += chunk.bytesize
- end
- http_response["Content-Length"] = content_length.to_s
- end
-
- before do
- allow(Tempfile).to receive(:new).with("chef-rest").and_return(tempfile)
- allow(Net::HTTP::Get).to receive(:new).and_return(request_mock)
- end
-
- after do
- tempfile.close!
- end
-
- it " build a new HTTP GET request without the application/json accept header" do
- expected_headers = { "Accept" => "*/*",
- "X-Chef-Version" => Chef::VERSION,
- "Accept-Encoding" => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE,
- "Host" => host_header,
- "X-REMOTE-REQUEST-ID" => request_id,
- "X-Ops-Server-API-Version" => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION,
- }
- expect(Net::HTTP::Get).to receive(:new).with("/?foo=bar", expected_headers).and_return(request_mock)
- rest.streaming_request(url, {})
- end
-
- it "build a new HTTP GET request with the X-Remote-Request-Id header" do
- expected_headers = { "Accept" => "*/*",
- "X-Chef-Version" => Chef::VERSION,
- "Accept-Encoding" => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE,
- "Host" => host_header,
- "X-REMOTE-REQUEST-ID" => request_id,
- "X-Ops-Server-API-Version" => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION,
- }
- expect(Net::HTTP::Get).to receive(:new).with("/?foo=bar", expected_headers).and_return(request_mock)
- rest.streaming_request(url, {})
- end
-
- it "returns a tempfile containing the streamed response body" do
- expect(rest.streaming_request(url, {})).to equal(tempfile)
- end
-
- it "writes the response body to a tempfile" do
- allow(http_response).to receive(:read_body).and_yield("real").and_yield("ultimate").and_yield("power")
- set_content_length
- rest.streaming_request(url, {})
- expect(IO.read(tempfile.path).chomp).to eq("realultimatepower")
- end
-
- it "closes the tempfile" do
- rest.streaming_request(url, {})
- expect(tempfile).to be_closed
- end
-
- it "yields the tempfile containing the streamed response body and then unlinks it when given a block" do
- allow(http_response).to receive(:read_body).and_yield("real").and_yield("ultimate").and_yield("power")
- set_content_length
- tempfile_path = nil
- rest.streaming_request(url, {}) do |tempfile|
- tempfile_path = tempfile.path
- expect(File.exist?(tempfile.path)).to be_truthy
- expect(IO.read(tempfile.path).chomp).to eq("realultimatepower")
- end
- expect(File.exist?(tempfile_path)).to be_falsey
- end
-
- it "does not raise a divide by zero exception if the content's actual size is 0" do
- http_response["Content-Length"] = "5"
- allow(http_response).to receive(:read_body).and_yield("")
- expect { rest.streaming_request(url, {}) }.to raise_error(Chef::Exceptions::ContentLengthMismatch)
- end
-
- it "does not raise a divide by zero exception when the Content-Length is 0" do
- http_response["Content-Length"] = "0"
- allow(http_response).to receive(:read_body).and_yield("ninja")
- expect { rest.streaming_request(url, {}) }.to raise_error(Chef::Exceptions::ContentLengthMismatch)
- end
-
- it "it raises an exception when the download is truncated" do
- http_response["Content-Length"] = (body.bytesize + 99).to_s
- allow(http_response).to receive(:read_body).and_yield("ninja")
- expect { rest.streaming_request(url, {}) }.to raise_error(Chef::Exceptions::ContentLengthMismatch)
- end
-
- it "fetches a file and yields the tempfile it is streamed to" do
- allow(http_response).to receive(:read_body).and_yield("real").and_yield("ultimate").and_yield("power")
- set_content_length
- tempfile_path = nil
- rest.fetch("cookbooks/a_cookbook") do |tempfile|
- tempfile_path = tempfile.path
- expect(IO.read(tempfile.path).chomp).to eq("realultimatepower")
- end
- expect(File.exist?(tempfile_path)).to be_falsey
- end
-
- it "closes and unlinks the tempfile if there is an error while streaming the content to the tempfile" do
- path = tempfile.path
- expect(path).not_to be_nil
- allow(tempfile).to receive(:write).and_raise(IOError)
- rest.fetch("cookbooks/a_cookbook") { |tmpfile| "shouldn't get here" }
- expect(File.exists?(path)).to be_falsey
- end
-
- it "closes and unlinks the tempfile when the response is a redirect" do
- tempfile = double("A tempfile", :path => "/tmp/ragefist", :close => true, :binmode => true)
- expect(tempfile).to receive(:close!).at_least(1).times
- allow(Tempfile).to receive(:new).with("chef-rest").and_return(tempfile)
-
- redirect = Net::HTTPFound.new("1.1", "302", "bob is taking care of that one for me today")
- redirect.add_field("location", url.path)
- allow(redirect).to receive(:read_body)
-
- expect(http_client).to receive(:request).and_yield(redirect).and_return(redirect)
- expect(http_client).to receive(:request).and_yield(http_response).and_return(http_response)
- rest.fetch("cookbooks/a_cookbook") { |tmpfile| "shouldn't get here" }
- end
-
- it "passes the original block to the redirected request" do
- http_redirect = Net::HTTPFound.new("1.1", "302", "bob is taking care of that one for me today")
- http_redirect.add_field("location", "/that-thing-is-here-now")
- allow(http_redirect).to receive(:read_body)
-
- block_called = false
- allow(http_client).to receive(:request).and_yield(http_response).and_return(http_redirect, http_response)
- rest.fetch("cookbooks/a_cookbook") do |tmpfile|
- block_called = true
- end
- expect(block_called).to be_truthy
- end
- end
- end # when making REST requests
-
- context "when following redirects" do
- let(:rest) do
- Chef::REST.new(base_url)
- end
-
- before do
- Chef::Config[:node_name] = "webmonkey.example.com"
- Chef::Config[:client_key] = CHEF_SPEC_DATA + "/ssl/private_key.pem"
- end
-
- it "raises a RedirectLimitExceeded when redirected more than 10 times" do
- redirected = lambda { rest.follow_redirect { redirected.call } }
- expect { redirected.call }.to raise_error(Chef::Exceptions::RedirectLimitExceeded)
- end
-
- it "does not count redirects from previous calls against the redirect limit" do
- total_redirects = 0
- redirected = lambda do
- rest.follow_redirect do
- total_redirects += 1
- redirected.call unless total_redirects >= 9
- end
- end
- expect { redirected.call }.not_to raise_error
- total_redirects = 0
- expect { redirected.call }.not_to raise_error
- end
-
- it "does not sign the redirected request when sign_on_redirect is false" do
- rest.sign_on_redirect = false
- rest.follow_redirect { expect(rest.sign_requests?).to be_falsey }
- end
-
- it "resets sign_requests to the original value after following an unsigned redirect" do
- rest.sign_on_redirect = false
- expect(rest.sign_requests?).to be_truthy
-
- rest.follow_redirect { expect(rest.sign_requests?).to be_falsey }
- expect(rest.sign_requests?).to be_truthy
- end
-
- it "configures the redirect limit" do
- total_redirects = 0
- redirected = lambda do
- rest.follow_redirect do
- total_redirects += 1
- redirected.call unless total_redirects >= 9
- end
- end
- expect { redirected.call }.not_to raise_error
-
- total_redirects = 0
- rest.redirect_limit = 3
- expect { redirected.call }.to raise_error(Chef::Exceptions::RedirectLimitExceeded)
- end
-
- end
-end