Chef's custom REST client with built-in JSON support and RSA signed header authentication.
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 localhost:4000, a call to get_rest with 'nodes' will make an HTTP GET request to localhost:4000/nodes
# File lib/chef/rest.rb, line 61 def initialize(url, client_name=Chef::Config[:node_name], signing_key_filename=Chef::Config[:client_key], options={}) @url = url @cookies = CookieJar.instance @default_headers = options[:headers] || {} @signing_key_filename = signing_key_filename @key = load_signing_key(@signing_key_filename, options[:raw_key]) @auth_credentials = AuthCredentials.new(client_name, @key) @sign_on_redirect, @sign_request = true, true @redirects_followed = 0 @redirect_limit = 10 @disable_gzip = false handle_options(options) end
Runs an HTTP request to a JSON API with JSON body. File Download not supported.
# File lib/chef/rest.rb, line 152 def api_request(method, url, headers={}, data=false) json_body = data ? Chef::JSONCompat.to_json(data) : nil # Force encoding to binary to fix SSL related EOFErrors # cf. http://tickets.opscode.com/browse/CHEF-2363 # http://redmine.ruby-lang.org/issues/5233 json_body.force_encoding(Encoding::BINARY) if json_body.respond_to?(:force_encoding) raw_http_request(method, url, headers, json_body) end
# File lib/chef/rest.rb, line 310 def authentication_headers(method, url, json_body=nil) request_params = {:http_method => method, :path => url.path, :body => json_body, :host => "#{url.host}:#{url.port}"} request_params[:body] ||= "" auth_credentials.signature_headers(request_params) end
# File lib/chef/rest.rb, line 79 def client_name @auth_credentials.client_name end
# File lib/chef/rest.rb, line 139 def create_url(path) if path =~ /^(http|https):\/\// URI.parse(path) else URI.parse("#{@url}/#{path}") end end
# File lib/chef/rest.rb, line 210 def decompress_body(response) if gzip_disabled? || response.body.nil? response.body else case response[CONTENT_ENCODING] when GZIP Chef::Log.debug "decompressing gzip response" Zlib::Inflate.new(Zlib::MAX_WBITS + 16).inflate(response.body) when DEFLATE Chef::Log.debug "decompressing deflate response" Zlib::Inflate.inflate(response.body) else response.body end end end
Send an HTTP DELETE request to the path
# File lib/chef/rest.rb, line 110 def delete(path, headers={}) api_request(:DELETE, create_url(path), headers) end
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.
# File lib/chef/rest.rb, line 135 def fetch(path, headers={}) streaming_request(create_url(path), headers) {|tmp_file| yield tmp_file } end
# File lib/chef/rest.rb, line 328 def follow_redirect raise Chef::Exceptions::RedirectLimitExceeded if @redirects_followed >= redirect_limit @redirects_followed += 1 Chef::Log.debug("Following redirect #{@redirects_followed}/#{redirect_limit}") if @sign_on_redirect yield else @sign_request = false yield end ensure @redirects_followed = 0 @sign_request = true end
Send an HTTP GET request to the path
Using this method to fetch a file is considered deprecated.
path |
The path to GET |
raw |
Whether you want the raw body returned, or JSON inflated. Defaults |
to JSON inflated.
# File lib/chef/rest.rb, line 95 def get(path, raw=false, headers={}) if raw streaming_request(create_url(path), headers) else api_request(:GET, create_url(path), headers) end end
# File lib/chef/rest.rb, line 103 def head(path, headers={}) api_request(:HEAD, create_url(path), headers) end
# File lib/chef/rest.rb, line 320 def http_retry_count config[:http_retry_count] end
# File lib/chef/rest.rb, line 316 def http_retry_delay config[:http_retry_delay] end
Send an HTTP POST request to the path
# File lib/chef/rest.rb, line 117 def post(path, json, headers={}) api_request(:POST, create_url(path), headers, json) end
Send an HTTP PUT request to the path
# File lib/chef/rest.rb, line 124 def put(path, json, headers={}) api_request(:PUT, create_url(path), headers, json) end
Runs an HTTP request to a JSON API with raw body. File Download not supported.
# File lib/chef/rest.rb, line 162 def raw_http_request(method, url, headers, body) headers = build_headers(method, url, headers, body) retriable_rest_request(method, url, body, headers) do |rest_request| begin response = rest_request.call {|r| r.read_body} Chef::Log.debug("---- HTTP Status and Header Data: ----") Chef::Log.debug("HTTP #{response.http_version} #{response.code} #{response.msg}") response.each do |header, value| Chef::Log.debug("#{header}: #{value}") end Chef::Log.debug("---- End HTTP Status/Header Data ----") response_body = decompress_body(response) if response.kind_of?(Net::HTTPSuccess) if response['content-type'] =~ /json/ Chef::JSONCompat.from_json(response_body.chomp) else Chef::Log.warn("Expected JSON response, but got content-type '#{response['content-type']}'") response_body.to_s end elsif response.kind_of?(Net::HTTPNotModified) # Must be tested before Net::HTTPRedirection because it's subclass. false elsif redirect_location = redirected_to(response) follow_redirect {api_request(:GET, create_url(redirect_location))} else # have to decompress the body before making an exception for it. But the body could be nil. response.body.replace(response_body) if response.body.respond_to?(:replace) if response['content-type'] =~ /json/ exception = Chef::JSONCompat.from_json(response_body) msg = "HTTP Request Returned #{response.code} #{response.message}: " msg << (exception["error"].respond_to?(:join) ? exception["error"].join(", ") : exception["error"].to_s) Chef::Log.info(msg) end response.error! end rescue Exception => e if e.respond_to?(:chef_rest_request=) e.chef_rest_request = rest_request end raise end end end
# File lib/chef/rest.rb, line 270 def retriable_rest_request(method, url, req_body, headers) rest_request = Chef::REST::RESTRequest.new(method, url, req_body, headers) Chef::Log.debug("Sending HTTP Request via #{method} to #{url.host}:#{url.port}#{rest_request.path}") http_attempts = 0 begin http_attempts += 1 yield rest_request rescue SocketError, Errno::ETIMEDOUT => e e.message.replace "Error connecting to #{url} - #{e.message}" raise e rescue Errno::ECONNREFUSED if http_retry_count - http_attempts + 1 > 0 Chef::Log.error("Connection refused connecting to #{url.host}:#{url.port} for #{rest_request.path}, retry #{http_attempts}/#{http_retry_count}") sleep(http_retry_delay) retry end raise Errno::ECONNREFUSED, "Connection refused connecting to #{url.host}:#{url.port} for #{rest_request.path}, giving up" rescue Timeout::Error if http_retry_count - http_attempts + 1 > 0 Chef::Log.error("Timeout connecting to #{url.host}:#{url.port} for #{rest_request.path}, retry #{http_attempts}/#{http_retry_count}") sleep(http_retry_delay) retry end raise Timeout::Error, "Timeout connecting to #{url.host}:#{url.port} for #{rest_request.path}, giving up" rescue Net::HTTPFatalError => e if http_retry_count - http_attempts + 1 > 0 sleep_time = 1 + (2 ** http_attempts) + rand(2 ** http_attempts) Chef::Log.error("Server returned error for #{url}, retrying #{http_attempts}/#{http_retry_count} in #{sleep_time}s") sleep(sleep_time) retry end raise end end
# File lib/chef/rest.rb, line 147 def sign_requests? auth_credentials.sign_requests? && @sign_request end
# File lib/chef/rest.rb, line 75 def signing_key_filename @signing_key_filename end
Makes a streaming download request. Doesn't speak JSON. Streams the response body to a tempfile. If a block is given, it's passed to Tempfile.open(), which means that the tempfile will automatically be unlinked after the block is executed.
If no block is given, the tempfile is returned, which means it's up to you to unlink the tempfile when you're done with it.
# File lib/chef/rest.rb, line 234 def streaming_request(url, headers, &block) headers = build_headers(:GET, url, headers, nil, true) retriable_rest_request(:GET, url, nil, headers) do |rest_request| begin tempfile = nil response = rest_request.call do |r| if block_given? && r.kind_of?(Net::HTTPSuccess) begin tempfile = stream_to_tempfile(url, r, &block) yield tempfile ensure tempfile.close! end else tempfile = stream_to_tempfile(url, r) end end if response.kind_of?(Net::HTTPSuccess) tempfile elsif redirect_location = redirected_to(response) # TODO: test tempfile unlinked when following redirects. tempfile && tempfile.close! follow_redirect {streaming_request(create_url(redirect_location), {}, &block)} else tempfile && tempfile.close! response.error! end rescue Exception => e if e.respond_to?(:chef_rest_request=) e.chef_rest_request = rest_request end raise end end end
Generated with the Darkfish Rdoc Generator 2.