Class: LMRest::APIClient

Inherits:
Object
  • Object
show all
Includes:
RequestParams
Defined in:
lib/lm_rest/api_client.rb

Constant Summary collapse

ITEMS_SIZE_LIMIT =
1000
ITEMS_SIZE_DEFAULT =
50
BASE_URL_PREFIX =
'https://'
BASE_URL_SUFFIX =
'.logicmonitor.com/santaba/rest'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from RequestParams

parameterize

Constructor Details

#initialize(company = nil, access_id = nil, access_key = nil) ⇒ APIClient

Returns a new instance of APIClient.



35
36
37
38
39
40
41
# File 'lib/lm_rest/api_client.rb', line 35

def initialize(company = nil, access_id =  nil, access_key = nil)
  APIClient.setup
  @company     = company
  @access_id   = access_id
  @access_key  = access_key
  @api_url     = BASE_URL_PREFIX + company + BASE_URL_SUFFIX
end

Instance Attribute Details

#access_idObject (readonly)

Returns the value of attribute access_id.



32
33
34
# File 'lib/lm_rest/api_client.rb', line 32

def access_id
  @access_id
end

#api_urlObject (readonly)

Returns the value of attribute api_url.



32
33
34
# File 'lib/lm_rest/api_client.rb', line 32

def api_url
  @api_url
end

#companyObject (readonly)

Returns the value of attribute company.



32
33
34
# File 'lib/lm_rest/api_client.rb', line 32

def company
  @company
end

#limitObject (readonly)

Returns the value of attribute limit.



33
34
35
# File 'lib/lm_rest/api_client.rb', line 33

def limit
  @limit
end

#remainingObject (readonly)

Returns the value of attribute remaining.



33
34
35
# File 'lib/lm_rest/api_client.rb', line 33

def remaining
  @remaining
end

#windowObject (readonly)

Returns the value of attribute window.



33
34
35
# File 'lib/lm_rest/api_client.rb', line 33

def window
  @window
end

Class Method Details

.define_action_methods(resource_type, attributes) ⇒ Object



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/lm_rest/api_client.rb', line 203

def self.define_action_methods(resource_type, attributes)
  singular = attributes['method_names']['singular']
  plural = attributes['method_names']['plural']
  resource_uri = attributes['url']

  attributes['actions'].each do |action|
    case action
    when 'get'
      define_get_methods(resource_uri, singular, plural)
    when 'add'
      define_add_method(resource_uri, singular)
    when 'update'
      define_update_method(resource_uri, singular)
    when 'delete'
      define_delete_method(resource_uri, singular)
    end
  end
end

.define_add_method(resource_uri, singular) ⇒ Object



250
251
252
253
254
255
# File 'lib/lm_rest/api_client.rb', line 250

def self.define_add_method(resource_uri, singular)
  define_method("add_#{singular}") do |properties|
    properties_hash = properties.is_a?(LMRest::Resource) ? properties.to_h : properties
    Resource.parse request(:post, "#{resource_uri}", properties_hash)
  end
end

.define_alias_methods(aliases) ⇒ Object



192
193
194
195
196
197
198
199
200
201
# File 'lib/lm_rest/api_client.rb', line 192

def self.define_alias_methods(aliases)
  aliases.each do |alias_name, target_name|
    next if alias_name == target_name
    next unless method_defined?(target_name)

    define_method(alias_name) do |*args|
      public_send(target_name, *args)
    end
  end
end

.define_child_methods(resource_type, attributes) ⇒ Object



276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/lm_rest/api_client.rb', line 276

def self.define_child_methods(resource_type, attributes)
  parent_singular = attributes['method_names']['singular']
  parent_resource_uri = attributes['url']
  children = attributes['children']

  children.each do |child_name|
    if @@api_json[child_name]
      child = @@api_json[child_name]
    else
      raise "Child resource " + child_name + " not defined."
    end

    child_singular = child['method_names']['singular']
    child_plural = child['method_names']['plural']
    child_resource_uri = attributes['url'].split("/").last

    child['actions'].each do |action|
        case action
        when 'get'
        define_method("get_#{parent_singular}_#{child_plural}") do |id, params = {}, &block|
          uri = lambda { |params| "#{parent_resource_uri}/#{id}/#{child['method_names']['plural']}#{RequestParams.parameterize(params)}" }
          Resource.parse paginate(uri, params)
        end

        when 'add'
        define_method("add_#{parent_singular}_#{child_singular}") do |parent_id, properties|
          properties_hash = properties.is_a?(LMRest::Resource) ? properties.to_h : properties
          Resource.parse request(:post, "#{parent_resource_uri}/#{parent_id}/#{child_resource_uri}", properties_hash)
        end

        when 'update'
        define_method("update_#{parent_singular}_#{child_singular}") do |parent_id, child_id, properties = {}|
          Resource.parse request(:put, "#{parent_resource_uri}/#{parent_id}/#{child_resource_uri}/#{child_id}", properties)
        end

        when 'delete'
        define_method("delete_#{parent_singular}_#{child_singular}") do |parent_id, child_id|
          Resource.parse request(:delete, "#{parent_resource_uri}/#{parent_id}/#{child_resource_uri}/#{child_id}", nil)
        end
        end
    end
  end
end

.define_delete_method(resource_uri, singular) ⇒ Object



268
269
270
271
272
273
274
# File 'lib/lm_rest/api_client.rb', line 268

def self.define_delete_method(resource_uri, singular)
  define_method("delete_#{singular}") do |id, params = {}|
    id = id.id if id.is_a?(LMRest::Resource)
    uri = "#{resource_uri}/#{id}#{RequestParams.parameterize(params)}"
    Resource.parse request(:delete, uri, nil)
  end
end

.define_get_methods(resource_uri, singular, plural) ⇒ Object



222
223
224
225
226
227
# File 'lib/lm_rest/api_client.rb', line 222

def self.define_get_methods(resource_uri, singular, plural)
  uri = lambda { |params| "#{resource_uri}#{RequestParams.parameterize(params)}" }

  define_plural_get_method(uri, plural) unless plural.nil?
  define_singular_get_method(resource_uri, singular) unless singular.nil?
end

.define_operation_method(method_name, operation) ⇒ Object



186
187
188
189
190
# File 'lib/lm_rest/api_client.rb', line 186

def self.define_operation_method(method_name, operation)
  define_method(method_name) do |*args|
    perform_operation(operation, args)
  end
end

.define_operation_methods(operations) ⇒ Object



180
181
182
183
184
# File 'lib/lm_rest/api_client.rb', line 180

def self.define_operation_methods(operations)
  operations.each do |method_name, operation|
    define_operation_method(method_name, operation)
  end
end

.define_plural_get_method(uri, plural) ⇒ Object



229
230
231
232
233
# File 'lib/lm_rest/api_client.rb', line 229

def self.define_plural_get_method(uri, plural)
  define_method("get_#{plural}") do |params = {}|
    Resource.parse paginate(uri, params)
  end
end

.define_singular_get_method(resource_uri, singular) ⇒ Object



235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/lm_rest/api_client.rb', line 235

def self.define_singular_get_method(resource_uri, singular)
  define_method("get_#{singular}") do |*args|
    case args.size
    when 0
      Resource.parse request(:get, "#{resource_uri}", nil)
    when 1
      Resource.parse request(:get, "#{resource_uri}/#{args[0]}", nil)
    when 2
      Resource.parse request(:get, "#{resource_uri}/#{args[0]}#{RequestParams.parameterize(args[1])}", nil)
    else
      raise ArgumentError.new("wrong number for arguments (#{args.count} for 1..2)")
    end
  end
end

.define_update_method(resource_uri, singular) ⇒ Object



257
258
259
260
261
262
263
264
265
266
# File 'lib/lm_rest/api_client.rb', line 257

def self.define_update_method(resource_uri, singular)
  define_method("update_#{singular}") do |id, properties = {}|
    if id.is_a?(LMRest::Resource)
      properties = id.to_h if properties.empty?
      id = id.id
    end

    Resource.parse request(:put, "#{resource_uri}/#{id}", properties)
  end
end

.setupObject

Define methods based on the JSON structure



321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
# File 'lib/lm_rest/api_client.rb', line 321

def self.setup
  return if @api_methods_defined

  @@api_definition_path = File.expand_path(File.join(File.dirname(__FILE__), "../../api.json"))
  @@api_json = JSON.parse(File.read(@@api_definition_path))

  if @@api_json['operations']
    define_operation_methods(@@api_json['operations'])
    define_alias_methods(@@api_json['aliases'] || {})
  else
    @@api_json.each do |resource_type, attributes|
      define_action_methods(resource_type, attributes) if attributes['actions']
      define_child_methods(resource_type, attributes) if attributes['children']
    end
  end

  @api_methods_defined = true
end

Instance Method Details

#ack_collector_down(id, comment) ⇒ Object

Ack a down collector, pass the ID and a comment



341
342
343
# File 'lib/lm_rest/api_client.rb', line 341

def ack_collector_down(id, comment)
  ack_collector_down_alert_by_id(resource_id(id), comment: comment)
end

#build_headers(method, uri, params, content_type) ⇒ Object



88
89
90
91
92
93
94
95
96
97
# File 'lib/lm_rest/api_client.rb', line 88

def build_headers(method, uri, params, content_type)
  headers = {
    'Authorization' => sign(method, uri, params, content_type),
    'Accept' => 'application/json, text/javascript',
    'X-version' => '3'
  }

  headers['Content-Type'] = content_type unless multipart_form?(content_type)
  headers
end

#determine_user_size(user_size, total) ⇒ Object



175
176
177
178
# File 'lib/lm_rest/api_client.rb', line 175

def determine_user_size(user_size, total)
  user_size ||= total
  user_size > total ? total : user_size
end

#execute_request(method, url, payload, headers) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/lm_rest/api_client.rb', line 99

def execute_request(method, url, payload, headers)
  case method
  when :get
    RestClient.get(url, headers)
  when :post
    RestClient.post(url, payload, headers)
  when :put
    RestClient.put(url, payload, headers)
  when :patch
    RestClient.patch(url, payload, headers)
  when :delete
    if payload.nil?
      RestClient.delete(url, headers)
    else
      RestClient::Request.execute(method: :delete, url: url, payload: payload, headers: headers)
    end
  else
    raise ArgumentError, "unsupported HTTP method: #{method}"
  end
end

#handle_response(response) ⇒ Object

Raises:



120
121
122
123
124
125
126
# File 'lib/lm_rest/api_client.rb', line 120

def handle_response(response)
  raise APIError.new(response) unless response.code.between?(200, 299)

  @limit = response.headers[:x_rate_limit_limit] || response.headers['x_rate_limit_limit']
  @remaining = response.headers[:x_rate_limit_remaining] || response.headers['x_rate_limit_remaining']
  @window = response.headers[:x_rate_limit_window] || response.headers['x_rate_limit_window']
end

#paginate(uri, params, method = :get, payload = nil, content_type = 'application/json') ⇒ Object

Handles making multiple requests to the API if pagination is necessary. Pagination is transparent, and simplifies requests that result in more than ITEMS_SIZE_LIMIT being returned.

If you need to walk through resources page-by-page manullay, use the request() method with the 'offset' and 'size' params



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/lm_rest/api_client.rb', line 147

def paginate(uri, params, method = :get, payload = nil, content_type = 'application/json')
  params = normalize_params_hash(params)
  user_size = params[:size]&.to_i
  params[:size] = user_size && user_size < ITEMS_SIZE_LIMIT ? user_size : ITEMS_SIZE_LIMIT
  params[:offset] = (params[:offset] || 0).to_i

  body = request(method, uri.call(params), payload, content_type: content_type)
  return body unless body.is_a?(Hash) && body.key?('items')

  total = (body['total'] || body['items'].length).to_i
  user_size = determine_user_size(user_size, total)
  item_collector = body['items'].first(user_size)

  while item_collector.length < user_size
    params[:offset] += params[:size].to_i
    remaining = user_size - item_collector.length
    params[:size] = [remaining, ITEMS_SIZE_LIMIT].min
    body = request(method, uri.call(params), payload, content_type: content_type)
    break unless body.is_a?(Hash) && body.key?('items')
    break if body['items'].empty?

    item_collector.concat(body['items'].first(remaining))
  end

  body['items'] = item_collector
  body
end

#parse_response(response) ⇒ Object



128
129
130
131
132
133
134
135
136
137
138
# File 'lib/lm_rest/api_client.rb', line 128

def parse_response(response)
  return nil if response.body.nil? || response.body.empty?

  content_type = response.headers[:content_type] || response.headers['content_type']

  if content_type.to_s.include?('application/json')
    JSON.parse(response.body)
  else
    response.body
  end
end

#request(method, uri, params = nil, content_type: 'application/json') ⇒ Object



77
78
79
80
81
82
83
84
85
86
# File 'lib/lm_rest/api_client.rb', line 77

def request(method, uri, params = nil, content_type: 'application/json')
  headers = build_headers(method, uri, params, content_type)
  url = api_url + uri
  payload = serialize_payload(params, content_type)

  response = execute_request(method.to_sym, url, payload, headers)
  handle_response(response)

  parse_response(response)
end

#run_report(id, type = "generateReport") ⇒ Object

run a report



346
347
348
349
350
351
352
# File 'lib/lm_rest/api_client.rb', line 346

def run_report(id, type = "generateReport")
  if id.class == LMRest::Resource
    Resource.parse request(:post, "/functions", {reportId: id.id, type: type})
  else
    Resource.parse request(:post, "/functions", {reportId: id, type: type})
  end
end

#sign(method, uri, data = nil, content_type = 'application/json') ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/lm_rest/api_client.rb', line 55

def sign(method, uri, data = nil, content_type = 'application/json')
  resource_uri = uri_to_resource_uri(uri)

  time = DateTime.now.strftime('%Q')

  http_method = method.to_s.upcase

  data = data_for_signature(data, content_type)

  message =  "#{http_method}#{time}#{data}#{resource_uri}"

  signature = Base64.strict_encode64(
    OpenSSL::HMAC.hexdigest(
      OpenSSL::Digest.new('sha256'),
      access_key,
      message
    )
  )

  "LMv1 #{access_id}:#{signature}:#{time}"
end

#uri_to_resource_uri(uri) ⇒ Object



43
44
45
46
47
48
49
50
51
52
53
# File 'lib/lm_rest/api_client.rb', line 43

def uri_to_resource_uri(uri)
  # Split the URL down to the resource
  #
  # Here's an example of the process:
  # /setting/datasources/1/graphs?key-value&
  # /setting/datasources/1/graphs
  # /setting/datasources/
  # /setting/datasources
  #
  uri.split("?")[0].split("/").join("/")
end

#website_execution_stats(days = 30) ⇒ Object

Helper to return execution counts for all websites across a time window. Assumes website interval is in minutes and locations/checkpoints is an array or comma-separated list.

Raises:

  • (ArgumentError)


357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
# File 'lib/lm_rest/api_client.rb', line 357

def website_execution_stats(days = 30)
  raise ArgumentError, "days must be positive" if days <= 0

  websites = get_websites

  per_site = websites.map do |site|
    interval = website_interval_minutes(site)
    location_count = website_location_count(site)
    next if interval.nil? || interval <= 0

    executions = (days * 24 * 60.0 / interval * location_count).ceil

    {
      id: site.respond_to?(:id) ? site.id : nil,
      name: site.respond_to?(:name) ? site.name : nil,
      interval_minutes: interval,
      location_count: location_count,
      executions: executions
    }
  end.compact

  {
    days: days,
    website_count: per_site.count,
    total_executions: per_site.sum { |row| row[:executions] },
    websites: per_site
  }
end