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

Attributes

auth[R]
current_api_version[RW]

Public Class Methods

new(*args) click to toggle source
# 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

Public Instance Methods

api() click to toggle source
# 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
api_version_negotiated() click to toggle source
# File lib/rhc/rest/client.rb, line 257
def api_version_negotiated
  api
  current_api_version
end
debug?() click to toggle source
# File lib/rhc/rest/client.rb, line 243
def debug?
  @debug
end
request(options) { |response| ... } click to toggle source
# 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
url() click to toggle source
# File lib/rhc/rest/client.rb, line 247
def url
  @end_point
end

Protected Instance Methods

default_verify_callback() click to toggle source
# 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
generic_error_message(url, client) click to toggle source
# 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
handle_error!(response, url, client) click to toggle source
# 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
headers() click to toggle source
# File lib/rhc/rest/client.rb, line 358
def headers
  @headers ||= {
    :accept => :json
  }
end
httpclient_for(options) click to toggle source
# 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
new_request(options) click to toggle source
# 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
options() click to toggle source
# File lib/rhc/rest/client.rb, line 368
def options
  @options ||= {
  }
end
parse_response(response) click to toggle source
# 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
raise_generic_error(url, client) click to toggle source
# File lib/rhc/rest/client.rb, line 517
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 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
self_signed?() click to toggle source
# File lib/rhc/rest/client.rb, line 407
def self_signed?
  @self_signed
end
user_agent() click to toggle source
# File lib/rhc/rest/client.rb, line 364
def user_agent
  RHC::Helpers.user_agent
end