Project

General

Profile

Download (7.17 KB) Statistics
| Branch: | Tag: | Revision:

runcible / lib / runcible / base.rb @ b20500a9

1
require 'rest_client'
2
require 'oauth'
3
require 'json'
4
require 'thread'
5

    
6
module Runcible
7
  class Base
8
    attr_accessor :logs
9

    
10
    def initialize(config = {})
11
      @mutex = Mutex.new
12
      @config = config
13
      @logs = []
14
    end
15

    
16
    def lazy_config=(a_block)
17
      @mutex.synchronize { @lazy_config = a_block }
18
    end
19

    
20
    def config
21
      @mutex.synchronize do
22
        @config = @lazy_config.call if defined?(@lazy_config)
23
        fail Runcible::ConfigurationUndefinedError, Runcible::ConfigurationUndefinedError.message unless @config
24
        @config
25
      end
26
    end
27

    
28
    def path(*args)
29
      self.class.path(*args)
30
    end
31

    
32
    # rubocop:disable Metrics/AbcSize:
33
    # rubocop:disable PerceivedComplexity
34
    def call(method, path, options = {})
35
      self.logs = []
36
      clone_config = self.config.clone
37
      #on occation path will already have prefix (sync cancel)
38
      path = clone_config[:api_path] + path unless path.start_with?(clone_config[:api_path])
39

    
40
      headers = clone_config[:headers].clone
41

    
42
      get_params = options[:params] if options[:params]
43
      path = combine_get_params(path, get_params) if get_params
44

    
45
      client_options = {}
46
      client_options[:timeout] = clone_config[:timeout] if clone_config[:timeout]
47
      client_options[:open_timeout] = clone_config[:open_timeout] if clone_config[:open_timeout]
48
      client_options[:verify_ssl] = clone_config[:verify_ssl] unless clone_config[:verify_ssl].nil?
49

    
50
      if clone_config[:oauth]
51
        self.logger.warn('[DEPRECATION] Pulp oauth is deprecated.  Please use cert_auth instead.')
52
        headers = add_oauth_header(method, path, headers)
53
        headers['pulp-user'] = clone_config[:user]
54
      elsif clone_config[:cert_auth]
55
        if !clone_config[:cert_auth][:ssl_client_cert] || !clone_config[:cert_auth][:ssl_client_key]
56
          fail Runcible::ConfigurationUndefinedError, "Missing SSL certificate or key configuration."
57
        end
58
        client_options[:ssl_client_cert] = clone_config[:cert_auth][:ssl_client_cert]
59
        client_options[:ssl_client_key] = clone_config[:cert_auth][:ssl_client_key]
60
      else
61
        client_options[:user] = clone_config[:user]
62
        client_options[:password] = config[:http_auth][:password]
63
      end
64

    
65
      client_options[:ssl_ca_file] = config[:ca_cert_file] unless config[:ca_cert_file].nil?
66
      client = RestClient::Resource.new(clone_config[:url], client_options)
67

    
68
      args = [method]
69
      args << generate_payload(options) if [:post, :put].include?(method)
70
      args << headers
71
      starting_arg = options[:no_log_payload] == true ? 2 : 1
72
      self.logs << ([method.upcase, URI.join(client.url, path)] + args[starting_arg..-1]).join(': ')
73

    
74
      response = get_response(client, path, *args)
75
      processed = process_response(response)
76
      self.logs << "Response: #{response.code}: #{response.body}"
77
      log_info
78
      processed
79
    rescue RestClient::ResourceNotFound => e
80
      self.logs << exception_to_log(e)
81
      log_info
82
      raise e
83
    rescue => e
84
      self.logs << exception_to_log(e)
85
      log_exception
86
      raise e
87
    end
88

    
89
    def exception_to_log(exception, body = exception.try(:response).try(:body))
90
      "#{exception.message}: #{body}"
91
    end
92

    
93
    def get_response(client, path, *args)
94
      client[path].send(*args) do |response, _request, _result, &_block|
95
        resp = response.return!
96
        return resp
97
      end
98
    end
99

    
100
    def combine_get_params(path, params)
101
      query_string = params.map do |k, v|
102
        if v.is_a? Array
103
          v.map { |y| "#{k}=#{y}" }.join('&')
104
        else
105
          "#{k}=#{v}"
106
        end
107
      end
108
      query_string = query_string.flatten.join('&')
109
      path + "?#{query_string}"
110
    end
111

    
112
    def generate_payload(options)
113
      if options[:payload].is_a?(String)
114
        return options[:payload]
115
      elsif options[:payload].is_a?(Hash)
116
        format_payload_json(options[:payload])
117
      end
118
    end
119

    
120
    def format_payload_json(payload_hash)
121
      if payload_hash
122
        if payload_hash[:optional]
123
          payload = if payload_hash[:required]
124
                      payload_hash[:required].merge(payload_hash[:optional])
125
                    else
126
                      payload_hash[:optional]
127
                    end
128
        elsif payload_hash[:delta]
129
          payload = payload_hash
130
        else
131
          payload = payload_hash[:required]
132
        end
133
      else
134
        payload = {}
135
      end
136

    
137
      return payload.to_json
138
    end
139

    
140
    def process_response(response)
141
      begin
142
        body = response.body == "null" ? nil : JSON.parse(response.body)
143
        if body.respond_to? :with_indifferent_access
144
          body = body.with_indifferent_access
145
        elsif body.is_a? Array
146
          body = body.map do |i|
147
            i.respond_to?(:with_indifferent_access) ? i.with_indifferent_access : i
148
          end
149
        end
150
        response = Runcible::Response.new(body, response)
151
      rescue JSON::ParserError => e
152
        self.logs << "Unable to parse JSON: #{e.message}"
153
      end
154

    
155
      return response
156
    end
157

    
158
    def required_params(local_names, binding, keys_to_remove = [])
159
      local_names = local_names.each_with_object({}) do |v, acc|
160
        value = binding.eval(v.to_s) unless v == :_
161
        acc[v] = value unless value.nil?
162
        acc
163
      end
164

    
165
      #The double delete is to support 1.8.7 and 1.9.3
166
      local_names.delete(:payload)
167
      local_names.delete(:optional)
168
      local_names.delete('payload')
169
      local_names.delete('optional')
170
      keys_to_remove.each do |key|
171
        local_names.delete(key)
172
        local_names.delete(key.to_sym)
173
      end
174

    
175
      return local_names
176
    end
177

    
178
    def add_http_auth_header
179
      return {:user => config[:user], :password => config[:http_auth][:password]}
180
    end
181

    
182
    def add_oauth_header(method, path, headers)
183
      default_options = { :site               => config[:url],
184
                          :http_method        => method,
185
                          :request_token_path => '',
186
                          :authorize_path     => '',
187
                          :access_token_path  => '' }
188

    
189
      consumer = OAuth::Consumer.new(config[:oauth][:oauth_key], config[:oauth][:oauth_secret], default_options)
190

    
191
      method_to_http_request = { :get    => Net::HTTP::Get,
192
                                 :post   => Net::HTTP::Post,
193
                                 :put    => Net::HTTP::Put,
194
                                 :delete => Net::HTTP::Delete }
195

    
196
      http_request = method_to_http_request[method].new(path)
197
      consumer.sign!(http_request)
198

    
199
      headers['Authorization'] = http_request['Authorization']
200
      return headers
201
    end
202

    
203
    def log_debug
204
      self.config[:logging][:logger].debug(self.logs.join("\n")) if self.config[:logging][:debug]
205
    end
206

    
207
    def log_exception
208
      self.config[:logging][:logger].error(self.logs.join("\n")) if self.config[:logging][:exception]
209
    end
210

    
211
    def log_info
212
      self.config[:logging][:logger].info(self.logs.join("\n")) if self.config[:logging][:info]
213
    end
214

    
215
    def logger
216
      self.config[:logging][:logger]
217
    end
218
  end
219

    
220
  class ConfigurationUndefinedError < StandardError
221
    def self.message
222
      # override me to change the error message
223
      'Configuration not set. Runcible::Base.config= must be called before Runcible::Base.config.'
224
    end
225
  end
226
end