class RHC::Rest::Client
Constants
- CLIENT_API_VERSIONS
Keep the list of supported API versions here The list may not necessarily be sorted; we will select the last matching one supported by the server. See api_version_negotiated
- MAX_RETRIES
Attributes
auth[R]
current_api_version[RW]
Public Class Methods
new(*args)
click to toggle source
# File lib/rhc/rest/client.rb, line 361 def initialize(*args) options = args[0].is_a?(Hash) && args[0] || {} @end_point, @debug, @preferred_api_versions = if options.empty? options[:user] = args.delete_at(1) options[:password] = args.delete_at(1) args else [ options.delete(:url) || (options[:server] && "https://#{options.delete(:server)}/broker/rest/api"), options.delete(:debug), options.delete(:preferred_api_versions) ] end @preferred_api_versions ||= CLIENT_API_VERSIONS @debug ||= false @auth = options.delete(:auth) @api_always_auth = options.delete(:api_always_auth) self.headers.merge!(options.delete(:headers)) if options[:headers] self.options.merge!(options) debug "Connecting to #{@end_point}" end
Public Instance Methods
api()
click to toggle source
# File lib/rhc/rest/client.rb, line 397 def api @api ||= RHC::Rest::Api.new(self, @preferred_api_versions).tap do |api| self.current_api_version = api.api_version_negotiated end end
api_always_auth()
click to toggle source
# File lib/rhc/rest/client.rb, line 393 def api_always_auth @api_always_auth end
api_version_negotiated()
click to toggle source
# File lib/rhc/rest/client.rb, line 403 def api_version_negotiated api current_api_version end
attempt(retries) { |i < (retries-1), i| ... }
click to toggle source
# File lib/rhc/rest/client.rb, line 408 def attempt(retries, &block) (0..retries).each do |i| yield i < (retries-1), i end raise "Too many retries, giving up." end
request(options) { |response| ... }
click to toggle source
# File lib/rhc/rest/client.rb, line 415 def request(options, &block) attempt(MAX_RETRIES) do |more, i| begin client, args = new_request(options.dup) auth = options[:auth] || self.auth response = nil debug "Request #{args[0].to_s.upcase} #{args[1]}#{"?#{args[2].map{|a| a.join('=')}.join(' ')}" if args[2] && args[0] == 'GET'}" time = Benchmark.realtime{ response = client.request(*(args << true)) } debug " code %s %4i ms" % [response.status, (time*1000).to_i] if response next if more && retry_proxy(response, i, args, client) auth.retry_auth?(response, self) and next if more && auth handle_error!(response, args[1], client) unless response.ok? return (if block_given? yield response else parse_response(response.content) unless response.nil? or response.code == 204 end) rescue HTTPClient::BadResponseError => e if e.res debug "Response: #{e.res.status} #{e.res.headers.inspect}\n#{e.res.content}\n-------------" if debug? next if more && retry_proxy(e.res, i, args, client) auth.retry_auth?(e.res, self) and next if more && auth handle_error!(e.res, args[1], client) end raise ConnectionException.new( "An unexpected error occurred when connecting to the server: #{e.message}") rescue HTTPClient::TimeoutError => e raise TimeoutException.new( "Connection to server timed out. "\ "It is possible the operation finished without being able "\ "to report success. Use 'rhc domain show' or 'rhc app show' "\ "to see the status of your applications.", e) rescue EOFError => e raise ConnectionException.new( "Connection to server got interrupted: #{e.message}") rescue OpenSSL::SSL::SSLError => e raise SelfSignedCertificate.new( 'self signed certificate', "The server is using a self-signed certificate, which means that a secure connection can't be established '#{args[1]}'.\n\n"\ "You may disable certificate checks with the -k (or --insecure) option. Using this option means that your data is potentially visible to third parties.") if self_signed? raise case e.message when /self signed certificate/ CertificateVerificationFailed.new( e.message, "The server is using a self-signed certificate, which means that a secure connection can't be established '#{args[1]}'.\n\n"\ "You may disable certificate checks with the -k (or --insecure) option. Using this option means that your data is potentially visible to third parties.") when /certificate verify failed/ CertificateVerificationFailed.new( e.message, "The server's certificate could not be verified, which means that a secure connection can't be established to the server '#{args[1]}'.\n\n"\ "If your server is using a self-signed certificate, you may disable certificate checks with the -k (or --insecure) option. Using this option means that your data is potentially visible to third parties.") when /unable to get local issuer certificate/ SSLConnectionFailed.new( e.message, "The server's certificate could not be verified, which means that a secure connection can't be established to the server '#{args[1]}'.\n\n"\ "You may need to specify your system CA certificate file with --ssl-ca-file=<path_to_file>. If your server is using a self-signed certificate, you may disable certificate checks with the -k (or --insecure) option. Using this option means that your data is potentially visible to third parties.") when /^SSL_connect returned=1 errno=0 state=SSLv2\/v3 read server hello A/ SSLVersionRejected.new( e.message, "The server has rejected your connection attempt with an older SSL protocol. Pass --ssl-version=sslv3 on the command line to connect to this server.") when /^SSL_CTX_set_cipher_list:: no cipher match/ SSLVersionRejected.new( e.message, "The server has rejected your connection attempt because it does not support the requested SSL protocol version.\n\n"\ "Check with the administrator for a valid SSL version to use and pass --ssl-version=<version> on the command line to connect to this server.") else SSLConnectionFailed.new( e.message, "A secure connection could not be established to the server (#{e.message}). You may disable secure connections to your server with the -k (or --insecure) option '#{args[1]}'.\n\n"\ "If your server is using a self-signed certificate, you may disable certificate checks with the -k (or --insecure) option. Using this option means that your data is potentially visible to third parties.") end rescue SocketError, Errno::ECONNREFUSED => e raise ConnectionException.new( "Unable to connect to the server (#{e.message})."\ "#{client.proxy.present? ? " Check that you have correctly specified your proxy server '#{client.proxy}' as well as your OpenShift server '#{args[1]}'." : " Check that you have correctly specified your OpenShift server '#{args[1]}'."}") rescue Errno::ECONNRESET => e raise ConnectionException.new( "The server has closed the connection unexpectedly (#{e.message}). Your last operation may still be running on the server; please check before retrying your last request.") rescue RHC::Rest::Exception raise rescue => e debug_error(e) raise ConnectionException, "An unexpected error occurred: #{e.message}", e.backtrace end end end
url()
click to toggle source
# File lib/rhc/rest/client.rb, line 389 def url @end_point end
Protected Instance Methods
default_verify_callback()
click to toggle source
# File lib/rhc/rest/client.rb, line 554 def default_verify_callback lambda do |is_ok, ctx| @self_signed = false unless is_ok cert = ctx.current_cert if cert && (cert.subject.cmp(cert.issuer) == 0) @self_signed = true debug "SSL Verification failed -- Using self signed cert" else debug "SSL Verification failed -- Preverify: #{is_ok}, Error: #{ctx.error_string} (#{ctx.error})" end return false end true end end
generic_error_message(url, client)
click to toggle source
# File lib/rhc/rest/client.rb, line 733 def generic_error_message(url, client) "The server did not respond correctly. This may be an issue "\ "with the server configuration or with your connection to the "\ "server (such as a Web proxy or firewall)."\ "#{client.proxy.present? ? " Please verify that your proxy server is working correctly (#{client.proxy}) and that you can access the OpenShift server #{url}" : " Please verify that you can access the OpenShift server #{url}"}" end
handle_error!(response, url, client)
click to toggle source
# File lib/rhc/rest/client.rb, line 740 def handle_error!(response, url, client) messages = [] parse_error = nil begin result = RHC::Json.decode(response.content) messages = parse_messages(result, {}) rescue => e debug "Response did not include a message from server: #{e.message}" end case response.status when 400 raise_generic_error(url, client) if messages.empty? message, keys = messages_to_fields(messages) raise ValidationException.new(message || "The operation could not be completed.", keys) when 401 raise UnAuthorizedException, "Not authenticated" when 403 raise RequestDeniedException, messages_to_error(messages) || "You are not authorized to perform this operation." when 404 if messages.length == 1 case messages.first['exit_code'] when 127 raise DomainNotFoundException, messages_to_error(messages) || generic_error_message(url, client) when 101 raise ApplicationNotFoundException, messages_to_error(messages) || generic_error_message(url, client) end end raise ResourceNotFoundException, messages_to_error(messages) || generic_error_message(url, client) when 409 raise_generic_error(url, client) if messages.empty? message, keys = messages_to_fields(messages) raise ValidationException.new(message || "The operation could not be completed.", keys) when 422 raise_generic_error(url, client) if messages.empty? message, keys = messages_to_fields(messages) raise ValidationException.new(message || "The operation was not valid.", keys) when 400 raise ClientErrorException, messages_to_error(messages) || "The server did not accept the requested operation." when 500 raise ServerErrorException, messages_to_error(messages) || generic_error_message(url, client) when 503 raise ServiceUnavailableException, messages_to_error(messages) || generic_error_message(url, client) else raise ServerErrorException, messages_to_error(messages) || "Server returned an unexpected error code: #{response.status}" end raise_generic_error end
headers()
click to toggle source
# File lib/rhc/rest/client.rb, line 511 def headers @headers ||= { :accept => :json } end
httpclient_for(options, auth=nil)
click to toggle source
# File lib/rhc/rest/client.rb, line 526 def httpclient_for(options, auth=nil) user, password, token = options.delete(:user), options.delete(:password), options.delete(:token) if !@httpclient || @last_options != options @httpclient = RHC::Rest::HTTPClient.new(:agent_name => user_agent).tap do |http| debug "Created new httpclient" http.cookie_manager = nil http.debug_dev = $stderr if ENV['HTTP_DEBUG'] options.select{ |sym, value| http.respond_to?("#{sym}=") }.each{ |sym, value| http.send("#{sym}=", value) } ssl = http.ssl_config options.select{ |sym, value| ssl.respond_to?("#{sym}=") }.each{ |sym, value| ssl.send("#{sym}=", value) } ssl.add_trust_ca(options[:ca_file]) if options[:ca_file] ssl.verify_callback = default_verify_callback @last_options = options end end if auth && auth.respond_to?(:to_httpclient) auth.to_httpclient(@httpclient, options) else @httpclient.www_auth.basic_auth.set(@end_point, user, password) if user @httpclient.www_auth.oauth2.set_token(@end_point, token) if token end @httpclient end
new_request(options)
click to toggle source
# File lib/rhc/rest/client.rb, line 574 def new_request(options) options.reverse_merge!(self.options) options[:connect_timeout] ||= options[:timeout] || 120 options[:receive_timeout] ||= options[:timeout] || 0 options[:send_timeout] ||= options[:timeout] || 0 options[:timeout] = nil auth = (options[:auth] || self.auth) unless options[:no_auth] if auth auth.to_request(options, self) end headers = (self.headers.to_a + (options.delete(:headers) || []).to_a).inject({}) do |h,(k,v)| v = "application/#{v}" if k == :accept && v.is_a?(Symbol) h[k.to_s.downcase.gsub(/_/, '-')] = v h end modifiers = [] version = options.delete(:api_version) || current_api_version modifiers << ";version=#{version}" if version query = options.delete(:query) || {} payload = options.delete(:payload) if options[:method].to_s.upcase == 'GET' query = payload payload = nil else headers['content-type'] ||= begin payload = payload.to_json unless payload.nil? || payload.is_a?(String) "application/json#{modifiers.join}" end end query = nil if query.blank? if headers['accept'] && modifiers.present? headers['accept'] << modifiers.join end # remove all unnecessary options options.delete(:lazy_auth) options.delete(:no_auth) options.delete(:accept) args = [options.delete(:method), options.delete(:url), query, payload, headers, true] [httpclient_for(options, auth), args] end
options()
click to toggle source
# File lib/rhc/rest/client.rb, line 521 def options @options ||= { } end
parse_messages(result, data)
click to toggle source
# File lib/rhc/rest/client.rb, line 686 def parse_messages(result, data) raw = (result || {})['messages'] || [] raw.delete_if do |m| m.delete_if{ |k,v| k.nil? || v.blank? } if m.is_a? Hash m.blank? end warnings, messages, raw = Array(raw).inject([[],[],[]]) do |a, m| severity, field, text = m.values_at('severity', 'field', 'text') text = (text || "").gsub(/\A\n+/m, "").rstrip case severity when 'warning' a[0] << text when 'debug' a[2] << m a[1] << text if debug? when 'info' a[2] << m a[1] << text if debug? || field == 'result' else a[2] << m a[1] << text end a end if data.is_a?(Array) data.each do |d| d['messages'] = messages d['warnings'] = warnings end elsif data.is_a?(Hash) data['messages'] = messages data['warnings'] = warnings end warnings.each do |warning| unless (@warning_map ||= Set.new).include?(warning) @warning_map << warning warn warning end end if respond_to? :warn raw end
parse_response(response)
click to toggle source
# File lib/rhc/rest/client.rb, line 633 def parse_response(response) result = RHC::Json.decode(response) type = result['type'] data = result['data'] || {} parse_messages result, data case type when 'domains' data.map{ |json| Domain.new(json, self) } when 'domain' Domain.new(data, self) when 'authorization' Authorization.new(data, self) when 'authorizations' data.map{ |json| Authorization.new(json, self) } when 'applications' data.map{ |json| Application.new(json, self) } when 'application' Application.new(data, self) when 'cartridges' data.map{ |json| Cartridge.new(json, self) } when 'cartridge' Cartridge.new(data, self) when 'user' User.new(data, self) when 'keys' data.map{ |json| Key.new(json, self) } when 'key' Key.new(data, self) when 'gear_groups' data.map{ |json| GearGroup.new(json, self) } when 'aliases' data.map{ |json| Alias.new(json, self) } when 'environment-variables' data.map{ |json| EnvironmentVariable.new(json, self) } when 'deployments' data.map{ |json| Deployment.new(json, self) } when 'team' Team.new(data, self) when 'teams' data.map{ |json| Team.new(json, self) } when 'member' RHC::Rest::Membership::Member.new(data, self) when 'members' data.map{ |json| RHC::Rest::Membership::Member.new(json, self) } when 'regions' data.map{ |json| Region.new(json, self) } else data end end
raise_generic_error(url, client)
click to toggle source
# File lib/rhc/rest/client.rb, line 730 def raise_generic_error(url, client) raise ServerErrorException.new(generic_error_message(url, client), 129) end
retry_proxy(response, i, args, client)
click to toggle source
# File lib/rhc/rest/client.rb, line 623 def retry_proxy(response, i, args, client) if response.status == 502 debug "ERROR: Received bad gateway from server, will retry once if this is a GET" return true if i == 0 && args[0] == :get raise ConnectionException.new( "An error occurred while communicating with the server. This problem may only be temporary."\ "#{client.proxy.present? ? " Check that you have correctly specified your proxy server '#{client.proxy}' as well as your OpenShift server '#{args[1]}'." : " Check that you have correctly specified your OpenShift server '#{args[1]}'."}") end end
self_signed?()
click to toggle source
# File lib/rhc/rest/client.rb, line 570 def self_signed? @self_signed end
user_agent()
click to toggle source
# File lib/rhc/rest/client.rb, line 517 def user_agent RHC::Helpers.user_agent end
Private Instance Methods
messages_to_error(messages)
click to toggle source
# File lib/rhc/rest/client.rb, line 789 def messages_to_error(messages) errors, remaining = messages.partition{ |m| (m['severity'] || "").upcase == 'ERROR' } if errors.present? if errors.length == 1 errors.first['text'] else "The server reported multiple errors:\n* #{errors.map{ |m| m['text'] || "An unknown server error occurred.#{ " (exit code: #{m['exit_code']}" if m['exit_code']}}" }.join("\n* ")}" end elsif remaining.present? "The operation did not complete successfully, but the server returned additional information:\n* #{remaining.map{ |m| m['text'] || 'No message'}.join("\n* ")}" end end
messages_to_fields(messages)
click to toggle source
# File lib/rhc/rest/client.rb, line 802 def messages_to_fields(messages) keys = messages.group_by{ |m| m['field'] }.keys.compact.sort.map(&:to_sym) rescue [] [messages_to_error(messages), keys] end