hawkular/hawkular-client-ruby

View on GitHub
lib/hawkular/base_client.rb

Summary

Maintainability
A
3 hrs
Test Coverage
require 'base64'
require 'addressable/uri'

require 'hawkular/logger'
require 'hawkular/client_utils'
require 'erb'

module Hawkular
  # This is the base functionality for all the clients,
  # that inherit from it. You should not directly use it,
  # but through the more specialized clients.
  class BaseClient
    HawkularException = Hawkular::Exception
    HawkularConnectionException = Hawkular::ConnectionException

    include ClientUtils

    # @!visibility private
    attr_reader :credentials, :entrypoint, :options, :logger
    # @return [Tenants] access tenants API
    attr_reader :tenants

    def initialize(entrypoint = nil,
                   credentials = {},
                   options = {})
      @entrypoint = entrypoint
      @credentials = {
        username: nil,
        password: nil,
        token: nil
      }.merge(credentials)
      @options = {
        verify_ssl: OpenSSL::SSL::VERIFY_PEER,
        headers: {}
      }.merge(options)
      @tenant = @options.delete(:tenant)
      @admin_token = @options.delete(:admin_token)

      @logger = Hawkular::Logger.new

      fail Hawkular::ArgumentError, 'You need to provide an entrypoint' if entrypoint.nil?
    end

    def url(url_format, *params)
      url_format % params.map { |p| ERB::Util.url_encode(p) }
    end

    def http_get(suburl, headers = {})
      res = rest_client(suburl).get(http_headers(headers))

      logger.log(res)

      res.empty? ? {} : JSON.parse(res)
    rescue
      handle_fault $ERROR_INFO
    end

    def http_post(suburl, hash, headers = {})
      body = JSON.generate(hash)
      res = rest_client(suburl).post(body, http_headers(headers))

      logger.log(res)

      res.empty? ? {} : JSON.parse(res)
    rescue
      handle_fault $ERROR_INFO
    end

    def http_put(suburl, hash, headers = {})
      body = JSON.generate(hash)
      res = rest_client(suburl).put(body, http_headers(headers))

      logger.log(res)

      res.empty? ? {} : JSON.parse(res)
    rescue
      handle_fault $ERROR_INFO
    end

    def http_delete(suburl, headers = {})
      res = rest_client(suburl).delete(http_headers(headers))

      logger.log(res)

      res.empty? ? {} : JSON.parse(res)
    rescue
      handle_fault $ERROR_INFO
    end

    # @!visibility private
    def rest_client(suburl)
      opts = @options.dup
      opts[:timeout] ||= ENV['HAWKULARCLIENT_REST_TIMEOUT'] if ENV['HAWKULARCLIENT_REST_TIMEOUT']
      opts[:proxy] ||= opts.delete(:http_proxy_uri)
      opts[:user] = @credentials[:username]
      opts[:password] = @credentials[:password]
      # strip @endpoint in case suburl is absolute
      suburl = suburl[@entrypoint.length, suburl.length] if suburl =~ /^http/
      RestClient::Resource.new(@entrypoint, opts)[suburl]
    end

    # @!visibility private
    def http_headers(headers = {})
      {}.merge(tenant_header)
        .merge(token_header)
        .merge(@options[:headers])
        .merge(content_type: 'application/json',
               accept: 'application/json')
        .merge(headers)
    end

    # timestamp of current time
    # @return [Integer] timestamp
    def now
      Integer(Time.now.to_f * 1000)
    end

    # Encode the passed credentials (username/password) into a base64
    # representation that can be used to generate a Http-Authentication header
    # @param credentials [Hash{:username,:password}]
    # @return [String] Base64 encoded result
    def base_64_credentials(credentials = {})
      creds = credentials.empty? ? @credentials : credentials

      encoded = Base64.encode64(creds[:username] + ':' + creds[:password])
      encoded.rstrip!
    end

    # Generate a query string from the passed hash, starting with '?'
    # Values may be an array, in which case the array values are joined together by `,`.
    # @param params [Hash] key-values pairs
    # @return [String] complete query string to append to a base url, '' if no valid params
    def generate_query_params(params = {})
      params = params.reject { |_k, v| v.nil? || ((v.instance_of? Array) && v.empty?) }
      return '' if params.empty?

      params.inject('?') do |ret, (k, v)|
        ret += '&' unless ret == '?'
        part = v.instance_of?(Array) ? "#{k}=#{v.join(',')}" : "#{k}=#{v}"
        ret + hawk_escape(part)
      end
    end

    # Generate a new url with the passed sufix path if the path is not already added
    # also, this function always remove the slash at the end of the URL, so if your entrypoint is
    # http://localhost/hawkular/inventory/ this function will return http://localhost/hawkular/inventory
    # to the URL
    # @param entrypoint [String] base path (URIs are also accepted)
    # @param suffix_path [String] sufix path to be added if it doesn't exist
    # @return [String] URL with path attached to it at the end
    def normalize_entrypoint_url(entrypoint, suffix_path)
      fail Hawkular::ArgumentError, 'suffix_path must not be empty' if suffix_path.empty?

      strip_path = suffix_path.gsub(%r{/$}, '')
      strip_path.nil? || suffix_path = strip_path
      strip_path = suffix_path.gsub(%r{^/}, '')
      strip_path.nil? || suffix_path = strip_path
      entrypoint = entrypoint.to_s
      strip_entrypoint = entrypoint.gsub(%r{/$}, '')
      strip_path.nil? && strip_entrypoint = entrypoint
      relative_path_rgx = Regexp.new("\/#{Regexp.quote(suffix_path)}(\/)*$")
      if relative_path_rgx.match(entrypoint)
        strip_entrypoint
      else
        "#{strip_entrypoint}/#{suffix_path}"
      end
    end

    # Generate a new url using the websocket scheme. It changes the current scheme to
    # 'ws' for 'http' and 'wss' for 'https' urls.
    # @param url [String|URI] url
    # @return [String] URL with the scheme changed to 'ws' or 'wss' depending on the current scheme.
    def url_with_websocket_scheme(url)
      url.to_s.sub(/^http(s?)/, 'ws\1')
    end

    def admin_header
      headers = {}
      headers[:'Hawkular-Admin-Token'] = @admin_token unless @admin_token.nil?
      headers
    end

    private

    def token_header
      @credentials[:token].nil? ? {} : { 'Authorization' => "Bearer #{@credentials[:token]}" }
    end

    def tenant_header
      headers = {}
      headers[:'Hawkular-Tenant'] = @tenant unless @tenant.nil?
      headers
    end

    # @!visibility private
    def connect_error(fault)
      if fault.is_a?(SocketError)
        Hawkular::ConnectionException.new(fault.to_s)
      elsif [Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EHOSTUNREACH,
             Errno::EADDRNOTAVAIL, Errno::ENETDOWN, Errno::ENETUNREACH,
             Errno::ETIMEDOUT].include?(fault.class)
        Hawkular::ConnectionException.new(fault.to_s, fault.class::Errno)
      end
    end

    def handle_fault(f)
      http_code = (f.respond_to?(:http_code) ? f.http_code : 0)
      fail Hawkular::Exception.new('Unauthorized', http_code) if f.instance_of? RestClient::Unauthorized

      if f.respond_to?(:http_body) && !f.http_body.nil?
        begin
          json_body = JSON.parse(f.http_body)
          fault_message = json_body['errorMsg'] || f.http_body
        rescue JSON::ParserError
          fault_message = f.http_body
        end

        fail Hawkular::Exception.new(fault_message, http_code)
      elsif (connect_error_exception = connect_error(f))
        fail Hawkular::ConnectionException, connect_error_exception
      else
        fail Hawkular::Exception, f
      end
    end
  end
end