summaryrefslogtreecommitdiff
path: root/lib/chef/rest/rest_request.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/chef/rest/rest_request.rb')
-rw-r--r--lib/chef/rest/rest_request.rb229
1 files changed, 229 insertions, 0 deletions
diff --git a/lib/chef/rest/rest_request.rb b/lib/chef/rest/rest_request.rb
new file mode 100644
index 0000000000..4ff0016205
--- /dev/null
+++ b/lib/chef/rest/rest_request.rb
@@ -0,0 +1,229 @@
+#--
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Thom May (<thom@clearairturbulence.org>)
+# Author:: Nuo Yan (<nuo@opscode.com>)
+# Author:: Christopher Brown (<cb@opscode.com>)
+# Author:: Christopher Walters (<cw@opscode.com>)
+# Author:: Daniel DeLeo (<dan@opscode.com>)
+# Copyright:: Copyright (c) 2009, 2010 Opscode, 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 'uri'
+require 'net/http'
+require 'chef/rest/cookie_jar'
+
+# To load faster, we only want ohai's version string.
+# However, in ohai before 0.6.0, the version is defined
+# in ohai, not ohai/version
+begin
+ require 'ohai/version' #used in user agent string.
+rescue LoadError
+ require 'ohai'
+end
+
+require 'chef/version'
+
+class Chef
+ class REST
+ class RESTRequest
+
+ engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : "ruby"
+
+ UA_COMMON = "/#{::Chef::VERSION} (#{engine}-#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}; ohai-#{Ohai::VERSION}; #{RUBY_PLATFORM}; +http://opscode.com)"
+ DEFAULT_UA = "Chef Client" << UA_COMMON
+
+ USER_AGENT = "User-Agent".freeze
+
+ ACCEPT_ENCODING = "Accept-Encoding".freeze
+ ENCODING_GZIP_DEFLATE = "gzip;q=1.0,deflate;q=0.6,identity;q=0.3".freeze
+
+ GET = "get".freeze
+ PUT = "put".freeze
+ POST = "post".freeze
+ DELETE = "delete".freeze
+ HEAD = "head".freeze
+
+ HTTPS = "https".freeze
+
+ SLASH = "/".freeze
+
+ def self.user_agent=(ua)
+ @user_agent = ua
+ end
+
+ def self.user_agent
+ @user_agent ||= DEFAULT_UA
+ end
+
+ attr_reader :method, :url, :headers, :http_client, :http_request
+
+ def initialize(method, url, req_body, base_headers={})
+ @method, @url = method, url
+ @request_body = nil
+ @cookies = CookieJar.instance
+ configure_http_client
+ build_headers(base_headers)
+ configure_http_request(req_body)
+ end
+
+ def host
+ @url.host
+ end
+
+ def port
+ @url.port
+ end
+
+ def query
+ @url.query
+ end
+
+ def path
+ @url.path.empty? ? SLASH : @url.path
+ end
+
+ def call
+ hide_net_http_bug do
+ http_client.request(http_request) do |response|
+ store_cookie(response)
+ yield response if block_given?
+ response
+ end
+ end
+ end
+
+ def config
+ Chef::Config
+ end
+
+ private
+
+ def hide_net_http_bug
+ yield
+ rescue NoMethodError => e
+ # http://redmine.ruby-lang.org/issues/show/2708
+ # http://redmine.ruby-lang.org/issues/show/2758
+ if e.to_s =~ /#{Regexp.escape(%q|undefined method `closed?' for nil:NilClass|)}/
+ Chef::Log.debug("Rescued error in http connect, re-raising as Errno::ECONNREFUSED to hide bug in net/http")
+ Chef::Log.debug("#{e.class.name}: #{e.to_s}")
+ Chef::Log.debug(e.backtrace.join("\n"))
+ raise Errno::ECONNREFUSED, "Connection refused attempting to contact #{url.scheme}://#{host}:#{port}"
+ else
+ raise
+ end
+ end
+
+ def store_cookie(response)
+ if response['set-cookie']
+ @cookies["#{host}:#{port}"] = response['set-cookie']
+ end
+ end
+
+ def build_headers(headers)
+ @headers = headers.dup
+ # TODO: need to set accept somewhere else
+ # headers.merge!('Accept' => "application/json") unless raw
+ @headers['X-Chef-Version'] = ::Chef::VERSION
+ @headers[ACCEPT_ENCODING] = ENCODING_GZIP_DEFLATE
+
+ if @cookies.has_key?("#{host}:#{port}")
+ @headers['Cookie'] = @cookies["#{host}:#{port}"]
+ end
+ end
+
+ #adapted from buildr/lib/buildr/core/transports.rb
+ def proxy_uri
+ proxy = Chef::Config["#{url.scheme}_proxy"]
+ proxy = URI.parse(proxy) if String === proxy
+ excludes = Chef::Config[:no_proxy].to_s.split(/\s*,\s*/).compact
+ excludes = excludes.map { |exclude| exclude =~ /:\d+$/ ? exclude : "#{exclude}:*" }
+ return proxy unless excludes.any? { |exclude| File.fnmatch(exclude, "#{host}:#{port}") }
+ end
+
+ def configure_http_client
+ http_proxy = proxy_uri
+ if http_proxy.nil?
+ @http_client = Net::HTTP.new(host, port)
+ else
+ Chef::Log.debug("Using #{http_proxy.host}:#{http_proxy.port} for proxy")
+ user = Chef::Config["#{url.scheme}_proxy_user"]
+ pass = Chef::Config["#{url.scheme}_proxy_pass"]
+ @http_client = Net::HTTP.Proxy(http_proxy.host, http_proxy.port, user, pass).new(host, port)
+ end
+ if url.scheme == HTTPS
+ @http_client.use_ssl = true
+ if config[:ssl_verify_mode] == :verify_none
+ @http_client.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ elsif config[:ssl_verify_mode] == :verify_peer
+ @http_client.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ end
+ if config[:ssl_ca_path]
+ unless ::File.exist?(config[:ssl_ca_path])
+ raise Chef::Exceptions::ConfigurationError, "The configured ssl_ca_path #{config[:ssl_ca_path]} does not exist"
+ end
+ @http_client.ca_path = config[:ssl_ca_path]
+ elsif config[:ssl_ca_file]
+ unless ::File.exist?(config[:ssl_ca_file])
+ raise Chef::Exceptions::ConfigurationError, "The configured ssl_ca_file #{config[:ssl_ca_file]} does not exist"
+ end
+ @http_client.ca_file = config[:ssl_ca_file]
+ end
+ if (config[:ssl_client_cert] || config[:ssl_client_key])
+ unless (config[:ssl_client_cert] && config[:ssl_client_key])
+ raise Chef::Exceptions::ConfigurationError, "You must configure ssl_client_cert and ssl_client_key together"
+ end
+ unless ::File.exists?(config[:ssl_client_cert])
+ raise Chef::Exceptions::ConfigurationError, "The configured ssl_client_cert #{config[:ssl_client_cert]} does not exist"
+ end
+ unless ::File.exists?(config[:ssl_client_key])
+ raise Chef::Exceptions::ConfigurationError, "The configured ssl_client_key #{config[:ssl_client_key]} does not exist"
+ end
+ @http_client.cert = OpenSSL::X509::Certificate.new(::File.read(config[:ssl_client_cert]))
+ @http_client.key = OpenSSL::PKey::RSA.new(::File.read(config[:ssl_client_key]))
+ end
+ end
+
+ @http_client.read_timeout = config[:rest_timeout]
+ end
+
+
+ def configure_http_request(request_body=nil)
+ req_path = "#{path}"
+ req_path << "?#{query}" if query
+
+ @http_request = case method.to_s.downcase
+ when GET
+ Net::HTTP::Get.new(req_path, headers)
+ when POST
+ Net::HTTP::Post.new(req_path, headers)
+ when PUT
+ Net::HTTP::Put.new(req_path, headers)
+ when DELETE
+ Net::HTTP::Delete.new(req_path, headers)
+ when HEAD
+ Net::HTTP::Head.new(req_path, headers)
+ else
+ raise ArgumentError, "You must provide :GET, :PUT, :POST, :DELETE or :HEAD as the method"
+ end
+
+ @http_request.body = request_body if (request_body && @http_request.request_body_permitted?)
+ # Optionally handle HTTP Basic Authentication
+ @http_request.basic_auth(url.user, url.password) if url.user
+ @http_request[USER_AGENT] = self.class.user_agent
+ end
+
+ end
+ end
+end