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
# File lib/rhc/rest/client.rb, line 210 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 if options[:token] self.headers[:authorization] = "Bearer #{options.delete(:token)}" options.delete(:user) options.delete(:password) end @auth = options.delete(:auth) self.headers.merge!(options.delete(:headers)) if options[:headers] self.options.merge!(options) debug "Connecting to #{@end_point}" end
# File lib/rhc/rest/client.rb, line 251 def api @api ||= RHC::Rest::Api.new(self, @preferred_api_versions).tap do |api| self.current_api_version = api.api_version_negotiated end end
# File lib/rhc/rest/client.rb, line 257 def api_version_negotiated api current_api_version end
# File lib/rhc/rest/client.rb, line 243 def debug? @debug end
# File lib/rhc/rest/client.rb, line 262 def request(options, &block) (0..MAX_RETRIES).each do |i| begin client, args = new_request(options.dup) auth = options[:auth] || self.auth response = nil debug "Request #{args[0].to_s.upcase} #{args[1]}" if debug? time = Benchmark.realtime{ response = client.request(*(args << true)) } debug " code %s %4i ms" % [response.status, (time*1000).to_i] if debug? && response next if retry_proxy(response, i, args, client) auth.retry_auth?(response, self) and next if auth handle_error!(response, args[1], client) unless response.ok? break (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 retry_proxy(e.res, i, args, client) auth.retry_auth?(e.res, self) and next if 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.") 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 %rself 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 %rcertificate 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 %runable 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 %r^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 %r^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 RHC::Rest::Exception raise rescue => e if debug? logger.debug "#{e.message} (#{e.class})" logger.debug e.backtrace.join("\n ") end raise ConnectionException.new("An unexpected error occured: #{e.message}").tap{ |n| n.set_backtrace(e.backtrace) } end end end
# File lib/rhc/rest/client.rb, line 247 def url @end_point end
# File lib/rhc/rest/client.rb, line 391 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" if debug? else debug "SSL Verification failed -- Preverify: #{is_ok}, Error: #{ctx.error_string} (#{ctx.error})" if debug? end return false end true end end
# File lib/rhc/rest/client.rb, line 520 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
# File lib/rhc/rest/client.rb, line 527 def handle_error!(response, url, client) messages = [] parse_error = nil begin result = RHC::Json.decode(response.content) messages = Array(result['messages']) messages.delete_if do |m| m.delete_if{ |k,v| k.nil? || v.blank? } if m.is_a? Hash m.blank? end rescue => e logger.debug "Response did not include a message from server: #{e.message}" if debug? 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
# File lib/rhc/rest/client.rb, line 358 def headers @headers ||= { :accept => :json } end
# File lib/rhc/rest/client.rb, line 373 def httpclient_for(options) return @httpclient if @last_options == options @httpclient = HTTPClient.new(:agent_name => user_agent).tap do |http| http.cookie_manager = nil http.debug_dev = $stderr if ENV['HTTP_DEBUG'] options.select{ |sym, value| http.respond_to?("#{sym}=") }.map{ |sym, value| http.send("#{sym}=", value) } http.set_auth(nil, options[:user], options[:password]) if options[:user] ssl = http.ssl_config options.select{ |sym, value| ssl.respond_to?("#{sym}=") }.map{ |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
# File lib/rhc/rest/client.rb, line 411 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 if auth = options[:auth] || self.auth auth.to_request(options) 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(%r_/, '-')] = v h end user = options.delete(:user) password = options.delete(:password) if user headers['Authorization'] ||= "Basic #{["#{user}:#{password}"].pack('m').tr("\n", '')}" 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) args = [options.delete(:method), options.delete(:url), query, payload, headers, true] [httpclient_for(options), args] end
# File lib/rhc/rest/client.rb, line 368 def options @options ||= { } end
# File lib/rhc/rest/client.rb, line 473 def parse_response(response) result = RHC::Json.decode(response) type = result['type'] data = result['data'] # Copy messages to each object messages = Array(result['messages']).map do |m| m['text'] if m['field'].nil? or m['field'] == 'result' end.compact data.each{ |d| d['messages'] = messages } if data.is_a?(Array) data['messages'] = messages if data.is_a?(Hash) 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) } else data end end
# File lib/rhc/rest/client.rb, line 517 def raise_generic_error(url, client) raise ServerErrorException.new(generic_error_message(url, client), 129) end
# File lib/rhc/rest/client.rb, line 463 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" if debug? 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
# File lib/rhc/rest/client.rb, line 407 def self_signed? @self_signed end
# File lib/rhc/rest/client.rb, line 364 def user_agent RHC::Helpers.user_agent end