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) 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 392 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_version_negotiated()
click to toggle source
# File lib/rhc/rest/client.rb, line 398 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 403 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 410 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 occured 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 occured: #{e.message}", e.backtrace end end end
url()
click to toggle source
# File lib/rhc/rest/client.rb, line 388 def url @end_point end
Protected Instance Methods
default_verify_callback()
click to toggle source
# File lib/rhc/rest/client.rb, line 549 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 728 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 735 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 506 def headers @headers ||= { :accept => :json } end
httpclient_for(options, auth=nil)
click to toggle source
# File lib/rhc/rest/client.rb, line 521 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 569 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 516 def options @options ||= { } end
parse_messages(result, data)
click to toggle source
# File lib/rhc/rest/client.rb, line 681 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 628 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 725 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 618 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 565 def self_signed? @self_signed end
user_agent()
click to toggle source
# File lib/rhc/rest/client.rb, line 512 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 784 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 797 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