lib/chef-api/connection.rb in chef-api-0.2.0 vs lib/chef-api/connection.rb in chef-api-0.2.1
- old
+ new
@@ -14,37 +14,58 @@
#
# @private
#
# @macro proxy
# @method $1
- # Get a proxied collection for +$1+. The proxy automatically injects
- # the current connection into the $2, providing a very Rubyesque way
- # for handling multiple connection objects.
+ # Get the list of $1 for this {Connection}. This method is threadsafe.
#
- # @example Get the $1 from the connection object
+ # @example Get the $1 from this {Connection} object
# connection = ChefAPI::Connection.new('...')
- # connection.$1 #=> $2 (with the connection object pre-populated)
+ # connection.$1 #=> $2(attribute1, attribute2, ...)
#
- # @return [ChefAPI::Proxy<$2>]
- # a collection proxy for the $2
+ # @return [Class<$2>]
#
def proxy(name, klass)
class_eval <<-EOH, __FILE__, __LINE__ + 1
def #{name}
- @#{name} ||= ChefAPI::Proxy.new(self, #{klass})
+ Thread.current['chefapi.connection'] = self
+ #{klass}
end
EOH
end
end
+ include Logify
include ChefAPI::Configurable
- include ChefAPI::Logger
+ proxy :clients, 'Resource::Client'
+ proxy :cookbooks, 'Resource::Cookbook'
+ proxy :data_bags, 'Resource::DataBag'
+ proxy :environments, 'Resource::Environment'
+ proxy :nodes, 'Resource::Node'
+ proxy :principals, 'Resource::Principal'
+ proxy :roles, 'Resource::Role'
+ proxy :users, 'Resource::User'
+
#
# Create a new ChefAPI Connection with the given options. Any options
# given take precedence over the default options.
#
+ # @example Create a connection object from a list of options
+ # ChefAPI::Connection.new(
+ # endpoint: 'https://...',
+ # client: 'bacon',
+ # key: '~/.chef/bacon.pem',
+ # )
+ #
+ # @example Create a connection object using a block
+ # ChefAPI::Connection.new do |connection|
+ # connection.endpoint = 'https://...'
+ # connection.client = 'bacon'
+ # connection.key = '~/.chef/bacon.pem'
+ # end
+ #
# @return [ChefAPI::Connection]
#
def initialize(options = {})
# Use any options given, but fall back to the defaults set on the module
ChefAPI::Configurable.keys.each do |key|
@@ -54,10 +75,12 @@
options[key]
end
instance_variable_set(:"@#{key}", value)
end
+
+ yield self if block_given?
end
#
# Determine if the given options are the same as ours.
#
@@ -147,18 +170,17 @@
# @param [String] path
# the absolute or relative path from {Defaults.endpoint} to make the
# request against
# @param [#read, Hash, nil] data
# the data to use (varies based on the +verb+)
- # @param [Hash] headers
- # the list of headers to use
#
# @return [String, Hash]
# the response body
#
def request(verb, path, data = {})
- log.info "===> #{verb.to_s.upcase} #{path}..."
+ log.info "#{verb.to_s.upcase} #{path}..."
+ log.debug "Chef flavor: #{flavor.inspect}"
# Build the URI and request object from the given information
uri = build_uri(verb, path, data)
request = class_for_request(verb).new(uri.request_uri)
@@ -199,11 +221,11 @@
end
# Naughty, naughty, naughty! Don't blame when when someone hops in
# and executes a MITM attack!
unless ssl_verify
- log.warn "===> Disabling SSL verification..."
+ log.warn "Disabling SSL verification..."
log.warn "Neither ChefAPI nor the maintainers are responsible for " \
"damanges incurred as a result of disabling SSL verification. " \
"Please use this with extreme caution, or consider specifying " \
"a custom certificate using `config.ssl_pem_file'."
connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
@@ -213,23 +235,26 @@
# Create a connection using the block form, which will ensure the socket
# is properly closed in the event of an error.
connection.start do |http|
response = http.request(request)
+ log.debug "Raw response:"
+ log.debug response.body
+
case response
when Net::HTTPRedirection
- redirect = URI.parse(response['location'])
- log.debug "===> Performing HTTP redirect to #{redirect}"
- request(verb, redirect, params)
+ redirect = URI.parse(response['location']).to_s
+ log.debug "Performing HTTP redirect to #{redirect}"
+ request(verb, redirect, data)
when Net::HTTPSuccess
success(response)
else
error(response)
end
end
rescue SocketError, Errno::ECONNREFUSED, EOFError
- log.warn " Unable to reach the Chef Server"
+ log.warn "Unable to reach the Chef Server"
raise Error::HTTPServerUnavailable.new
end
#
# Construct a URL from the given verb and path. If the request is a GET or
@@ -248,35 +273,38 @@
# the list of params to build the URI with (for GET and DELETE requests)
#
# @return [URI]
#
def build_uri(verb, path, params = {})
- log.info "===> Building URI..."
+ log.info "Building URI..."
# Add any query string parameters
if [:delete, :get].include?(verb)
- log.debug " Detected verb deserves a querystring"
- log.debug " Building querystring using #{params.inspect}"
- path = [path, to_query_string(params)].compact.join('?')
+ if querystring = to_query_string(params)
+ log.debug "Detected verb deserves a querystring"
+ log.debug "Building querystring using #{params.inspect}"
+ log.debug "Compiled querystring is #{querystring.inspect}"
+ path = [path, querystring].compact.join('?')
+ end
end
# Parse the URI
uri = URI.parse(path)
# Don't merge absolute URLs
unless uri.absolute?
- log.debug " Detected URI is relative"
- log.debug " Appending #{endpoint} to #{path}"
+ log.debug "Detected URI is relative"
+ log.debug "Appending #{path} to #{endpoint}"
uri = URI.parse(File.join(endpoint, path))
end
# Return the URI object
uri
end
#
- # Helper method to get the corresponding {Net::HTTP} class from the given
+ # Helper method to get the corresponding +Net::HTTP+ class from the given
# HTTP verb.
#
# @param [#to_s] verb
# the HTTP verb to create a class from
#
@@ -321,28 +349,28 @@
# the RSA private key as an OpenSSL object
#
def parsed_key
return @parsed_key if @parsed_key
- log.info "===> Parsing private key..."
+ log.info "Parsing private key..."
if key.nil?
- log.warn " No private key given!"
+ log.warn "No private key given!"
raise 'No private key given!'
end
if key.is_a?(OpenSSL::PKey::RSA)
- log.debug " Detected private key is an OpenSSL Ruby object"
+ log.debug "Detected private key is an OpenSSL Ruby object"
@parsed_key = key
end
- if key =~ /(.+)\.pem$/ || File.exists?(key)
- log.debug " Detected private key is the path to a file"
+ if key =~ /(.+)\.pem$/ || File.exists?(File.expand_path(key))
+ log.debug "Detected private key is the path to a file"
contents = File.read(File.expand_path(key))
@parsed_key = OpenSSL::PKey::RSA.new(contents)
else
- log.debug " Detected private key was the literal string key"
+ log.debug "Detected private key was the literal string key"
@parsed_key = OpenSSL::PKey::RSA.new(key)
end
@parsed_key
end
@@ -357,19 +385,19 @@
#
# @return [String, Hash]
# the parsed response, as an object
#
def success(response)
- log.info "===> Parsing response as success..."
+ log.info "Parsing response as success..."
case response['Content-Type']
- when 'application/json'
- log.debug " Detected response as JSON"
- log.debug " Parsing response body as JSON"
+ when /json/
+ log.debug "Detected response as JSON"
+ log.debug "Parsing response body as JSON"
JSON.parse(response.body)
else
- log.debug " Detected response as text/plain"
+ log.debug "Detected response as text/plain"
response.body
end
end
#
@@ -378,19 +406,19 @@
#
# @param [HTTP::Message] response
# the response object from the request
#
def error(response)
- log.info "===> Parsing response as error..."
+ log.info "Parsing response as error..."
case response['Content-Type']
- when 'application/json'
- log.debug " Detected error response as JSON"
- log.debug " Parsing error response as JSON"
- message = JSON.parse(response.body)['error'].first
+ when /json/
+ log.debug "Detected error response as JSON"
+ log.debug "Parsing error response as JSON"
+ message = Array(JSON.parse(response.body)['error']).join(', ')
else
- log.debug " Detected response as text/plain"
+ log.debug "Detected response as text/plain"
message = response.body
end
case response.code.to_i
when 400
@@ -403,10 +431,12 @@
raise Error::HTTPNotFound.new(message: message)
when 405
raise Error::HTTPMethodNotAllowed.new(message: message)
when 406
raise Error::HTTPNotAcceptable.new(message: message)
+ when 504
+ raise Error::HTTPGatewayTimeout.new(message: message)
when 500..600
raise Error::HTTPServerUnavailable.new
else
raise "I got an error #{response.code} that I don't know how to handle!"
end
@@ -416,11 +446,11 @@
# Adds the default headers to the request object.
#
# @param [Net::HTTP::Request] request
#
def add_request_headers(request)
- log.info "===> Adding request headers..."
+ log.info "Adding request headers..."
headers = {
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'Connection' => 'keep-alive',
@@ -428,38 +458,38 @@
'User-Agent' => user_agent,
'X-Chef-Version' => '11.4.0',
}
headers.each do |key, value|
- log.debug " #{key}: #{value}"
+ log.debug "#{key}: #{value}"
request[key] = value
end
end
#
# Use mixlib-auth to create a signed header auth.
#
# @param [Net::HTTP::Request] request
#
def add_signing_headers(verb, uri, request, key)
- log.info "===> Adding signed header authentication..."
+ log.info "Adding signed header authentication..."
- unless defined?(Mixlib::Authentication)
+ unless defined?(Mixlib::Authentication::SignedHeaderAuth)
require 'mixlib/authentication/signedheaderauth'
end
headers = Mixlib::Authentication::SignedHeaderAuth.signing_object(
:http_method => verb,
:body => request.body || '',
:host => "#{uri.host}:#{uri.port}",
- :path => request.path,
+ :path => uri.path,
:timestamp => Time.now.utc.iso8601,
:user_id => client,
:file => '',
).sign(key)
headers.each do |key, value|
- log.debug " #{key}: #{value}"
+ log.debug "#{key}: #{value}"
request[key] = value
end
end
end
end