Project

General

Profile

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

runcible / lib / runcible / base.rb @ 27b106f4

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

    
6
module Runcible
7
  class Base
8
    def initialize(config = {})
9
      @mutex = Mutex.new
10
      @config = config
11
    end
12

    
13
    def lazy_config=(a_block)
14
      @mutex.synchronize { @lazy_config = a_block }
15
    end
16

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

    
25
    def path(*args)
26
      self.class.path(*args)
27
    end
28

    
29
    def call(method, path, options = {})
30
      clone_config = self.config.clone
31
      #on occation path will already have prefix (sync cancel)
32
      path = clone_config[:api_path] + path unless path.start_with?(clone_config[:api_path])
33

    
34
      RestClient.log = []
35
      headers = clone_config[:headers].clone
36

    
37
      get_params = options[:params] if options[:params]
38
      path = combine_get_params(path, get_params) if get_params
39

    
40
      client_options = {}
41
      client_options[:timeout] = clone_config[:timeout] if clone_config[:timeout]
42
      client_options[:open_timeout] = clone_config[:open_timeout] if clone_config[:open_timeout]
43
      client_options[:verify_ssl] = clone_config[:verify_ssl] unless clone_config[:verify_ssl].nil?
44

    
45
      if clone_config[:oauth]
46
        headers = add_oauth_header(method, path, headers)
47
        headers['pulp-user'] = clone_config[:user]
48
      elsif clone_config[:cert_auth]
49
        if !clone_config[:cert_auth][:ssl_client_cert] || !clone_config[:cert_auth][:ssl_client_key]
50
          fail Runcible::ConfigurationUndefinedError, "Missing SSL certificate or key configuration."
51
        end
52
        client_options[:ssl_client_cert] = clone_config[:cert_auth][:ssl_client_cert]
53
        client_options[:ssl_client_key] = clone_config[:cert_auth][:ssl_client_key]
54
      else
55
        client_options[:user] = clone_config[:user]
56
        client_options[:password] = config[:http_auth][:password]
57
      end
58

    
59
      client_options[:ssl_ca_file] = config[:ca_cert_file] unless config[:ca_cert_file].nil?
60
      client = RestClient::Resource.new(clone_config[:url], client_options)
61

    
62
      args = [method]
63
      args << generate_payload(options) if [:post, :put].include?(method)
64
      args << headers
65

    
66
      response = get_response(client, path, *args)
67
      process_response(response)
68

    
69
    rescue RestClient::ResourceNotFound => e
70
      log_info
71
      raise e
72
    rescue => e
73
      log_exception
74
      raise e
75
    end
76

    
77
    def get_response(client, path, *args)
78
      client[path].send(*args) do |response, request, result, &_block|
79
        resp = response.return!(request, result)
80
        log_debug
81
        return resp
82
      end
83
    end
84

    
85
    def combine_get_params(path, params)
86
      query_string = params.map do |k, v|
87
        if v.is_a? Array
88
          v.map { |y| "#{k}=#{y}" }.join('&')
89
        else
90
          "#{k}=#{v}"
91
        end
92
      end
93
      query_string = query_string.flatten.join('&')
94
      path + "?#{query_string}"
95
    end
96

    
97
    def generate_payload(options)
98
      if options[:payload].is_a?(String)
99
        return options[:payload]
100
      elsif options[:payload].is_a?(Hash)
101
        format_payload_json(options[:payload])
102
      end
103
    end
104

    
105
    def format_payload_json(payload_hash)
106
      if payload_hash
107
        if payload_hash[:optional]
108
          payload = if payload_hash[:required]
109
                      payload_hash[:required].merge(payload_hash[:optional])
110
                    else
111
                      payload_hash[:optional]
112
                    end
113
        elsif payload_hash[:delta]
114
          payload = payload_hash
115
        else
116
          payload = payload_hash[:required]
117
        end
118
      else
119
        payload = {}
120
      end
121

    
122
      return payload.to_json
123
    end
124

    
125
    def process_response(response)
126
      begin
127
        body = response.body == "null" ? nil : JSON.parse(response.body)
128
        if body.respond_to? :with_indifferent_access
129
          body = body.with_indifferent_access
130
        elsif body.is_a? Array
131
          body = body.map do |i|
132
            i.respond_to?(:with_indifferent_access) ? i.with_indifferent_access : i
133
          end
134
        end
135
        response = rest_client_response(body, response.net_http_res, response.args)
136
      rescue JSON::ParserError
137
        log_exception
138
      end
139

    
140
      return response
141
    end
142

    
143
    def rest_client_response(body, net_http_res, args)
144
      if Gem.loaded_specs['rest-client'].version < Gem::Version.create('1.8.0')
145
        RestClient::Response.create(body, net_http_res, args)
146
      else
147
        RestClient::Response.create(body, net_http_res, args, nil)
148
      end
149
    end
150

    
151
    def required_params(local_names, binding, keys_to_remove = [])
152
      local_names = local_names.each_with_object({}) do |v, acc|
153
        value = binding.eval(v.to_s) unless v == :_
154
        acc[v] = value unless value.nil?
155
        acc
156
      end
157

    
158
      #The double delete is to support 1.8.7 and 1.9.3
159
      local_names.delete(:payload)
160
      local_names.delete(:optional)
161
      local_names.delete('payload')
162
      local_names.delete('optional')
163
      keys_to_remove.each do |key|
164
        local_names.delete(key)
165
        local_names.delete(key.to_sym)
166
      end
167

    
168
      return local_names
169
    end
170

    
171
    def add_http_auth_header
172
      return {:user => config[:user], :password => config[:http_auth][:password]}
173
    end
174

    
175
    def add_oauth_header(method, path, headers)
176
      default_options = { :site               => config[:url],
177
                          :http_method        => method,
178
                          :request_token_path => '',
179
                          :authorize_path     => '',
180
                          :access_token_path  => '' }
181

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

    
184
      method_to_http_request = { :get    => Net::HTTP::Get,
185
                                 :post   => Net::HTTP::Post,
186
                                 :put    => Net::HTTP::Put,
187
                                 :delete => Net::HTTP::Delete }
188

    
189
      http_request = method_to_http_request[method].new(path)
190
      consumer.sign!(http_request)
191

    
192
      headers['Authorization'] = http_request['Authorization']
193
      return headers
194
    end
195

    
196
    def log_debug
197
      if self.config[:logging][:debug]
198
        log_message = generate_log_message
199
        self.config[:logging][:logger].debug(log_message)
200
      end
201
    end
202

    
203
    def log_exception
204
      if self.config[:logging][:exception]
205
        log_message = generate_log_message
206
        self.config[:logging][:logger].error(log_message)
207
      end
208
    end
209

    
210
    def log_info
211
      if self.config[:logging][:info]
212
        log_message = generate_log_message
213
        self.config[:logging][:logger].info(log_message)
214
      end
215
    end
216

    
217
    def generate_log_message
218
      RestClient.log.join('\n')
219
    end
220

    
221
    def logger
222
      self.config[:logging][:logger]
223
    end
224
  end
225

    
226
  class ConfigurationUndefinedError < StandardError
227
    def self.message
228
      # override me to change the error message
229
      'Configuration not set. Runcible::Base.config= must be called before Runcible::Base.config.'
230
    end
231
  end
232
end