elastic_manager 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bca9616ef02571e4e12f488fb61367e5009788fa919d26dd862b3f5b11fa014f
4
- data.tar.gz: 790823ce0244db16bf668e664a37bce2d644eda86f17283a1ae883242473aa78
3
+ metadata.gz: 59956a9221433200e4da7152617c039ac1dde775d30299876b6bb379a2358fde
4
+ data.tar.gz: c26c03c09b73188ef4e833a580981f538949918538e7df072f74a6eb5d55fd47
5
5
  SHA512:
6
- metadata.gz: 4a25ec8e9155eaa37d6661828853c3708c1eb7bdf81b2f7fcd281eedc337d013e8bcf3d56cc245ce69f0208c7c9d921226260ef7472a765f07024591832a45ba
7
- data.tar.gz: ffe738eae1d02696ec2516331f781326381495ab9d42ee30b83bb9e14d6670285d417e142f529f6b289eb819d62955062761670915cec9893a0d2897da49adbe
6
+ metadata.gz: a4b322f736dcc9fea634113b7f661e49827e8baf26ccdcbd0e8f0c40c2e44c21fc5bf146abf04a751f8321fa157156f5a5fa4f6bfd6165edc89f96a3604103af
7
+ data.tar.gz: 6a06140123e5dc85b40b21ce47d115982c7293d6518ace6332614fcbba7e6a02b6d5b31636505d16a10a88c4bdb1210a140aa0612224e1a130fe164a81d4bb5c
data/README.md CHANGED
@@ -0,0 +1 @@
1
+ [![Build Status](https://travis-ci.org/onetwotrip/elastic_manager.svg?branch=master)](https://travis-ci.org/onetwotrip/elastic_manager)
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'elastic_manager'
3
3
  s.executables = ['elastic_manager']
4
- s.version = '0.1.2'
4
+ s.version = '0.1.3'
5
5
  s.date = '2018-10-15'
6
6
  s.summary = 'Because qurator sucks'
7
7
  s.description = 'Manager for logstash indices in elastic'
@@ -12,7 +12,9 @@ Gem::Specification.new do |s|
12
12
  s.homepage = 'https://github.com/onetwotrip/elastic_manager'
13
13
  s.license = 'MIT'
14
14
  s.required_ruby_version = '>= 2.0.0'
15
- s.add_dependency 'http', '~> 3.3'
16
- s.add_dependency 'colorize', '~> 0.8'
17
- s.add_dependency 'dotenv', '~> 2.4'
15
+ s.add_dependency 'http', '~> 3.3'
16
+ s.add_dependency 'colorize', '~> 0.8'
17
+ s.add_dependency 'dotenv', '~> 2.4'
18
+ s.add_dependency 'rake', '~> 12.3'
19
+ s.add_dependency 'yajl-ruby', '~> 1.4'
18
20
  end
@@ -1,15 +1,17 @@
1
1
  require 'dotenv/load'
2
2
  require 'date'
3
- require 'http'
4
3
  require 'elastic_manager/config'
5
4
  require 'elastic_manager/logger'
5
+ require 'elastic_manager/request'
6
+ require 'elastic_manager/utils'
6
7
 
7
8
  class ElasticManager
8
-
9
9
  include Config
10
10
  include Logging
11
+ include Request
12
+ include Utils
11
13
 
12
- attr_reader :config
14
+ # attr_reader :config
13
15
 
14
16
  def initialize(argv)
15
17
  if argv.size == 0
@@ -17,87 +19,8 @@ class ElasticManager
17
19
  else
18
20
  @config = load_from_argv(argv)
19
21
  end
20
- end
21
22
 
22
- def true?(obj)
23
- obj.to_s.downcase == 'true'
24
- end
25
-
26
- def es_request(verb, uri, json = {})
27
- tries ||= @config['retry'].to_i
28
-
29
- request_uri = "#{@config['es']['url']}#{uri}"
30
-
31
- response = HTTP.timeout(
32
- write: @config['timeout']['write'].to_i,
33
- connect: @config['timeout']['connect'].to_i,
34
- read: @config['timeout']['read'].to_i
35
- ).headers(
36
- 'Accept': 'application/json',
37
- 'Content-type': 'application/json'
38
- ).request(
39
- verb.to_sym,
40
- request_uri,
41
- json: json
42
- )
43
-
44
- if response.code == 200 || response.code == 404
45
- # TODO (anton.raybov): mb we need to return only one value but empty if 404???
46
- return response.code, response.body.to_s
47
- else
48
- log.fatal "error in request - url: #{request_uri}, status: #{response.code}, response: #{response.body}"
49
- exit 1
50
- end
51
-
52
- rescue StandardError => e
53
-
54
- log.warn "try #{tries + 1} '''#{e.message}''' sleeping #{@config['sleep']} sec..."
55
- sleep @config['sleep'].to_i
56
-
57
- retry unless (tries -= 1).zero?
58
- abort "backtrace:\n\t#{e.backtrace.join("\n\t")}".red
59
- end
60
-
61
- def es_green?
62
- status, response = es_request('get', '/_cluster/health')
63
- return JSON.parse(response)['status'] == 'green' if status == 200
64
- false
65
- end
66
-
67
- def es_all_indices(from=nil, to=nil, state=nil, type=nil)
68
- req_path = '/_cluster/state/metadata/'
69
- req_params = '?filter_path=metadata.indices.*.state,'
70
- req_params << 'metadata.indices.*.settings.index.routing.allocation.require.box_type'
71
-
72
- status, response = es_request('get', req_path + req_params)
73
- if status == 200
74
- indices = JSON.parse(response)['metadata']['indices']
75
- else
76
- log.fatal "can't work with all_indices response was: #{status} - #{response}"
77
- exit 1
78
- end
79
-
80
- indices.select!{ |k, v| v['state'] == state } if state
81
-
82
- if type
83
- # TODO (anton.ryabov): next line just for debug purpose, need better handling
84
- indices.each { |k, v| log.warn "#{k} - #{v.to_json}" unless v['settings'] }
85
- indices.select!{ |k, v| v['settings']['index']['routing']['allocation']['require']['box_type'] == type }
86
- end
87
-
88
- res = []
89
- indices.each_key do |index|
90
- begin
91
- index_date = Date.parse(index.gsub('-', ''))
92
- rescue ArgumentError => e
93
- log.error "#{e.message} for #{index}"
94
- next
95
- end
96
-
97
- res << URI.escape(index) if (from..to).cover? index_date
98
- end
99
-
100
- res
23
+ @elastic = Request::Elastic.new(@config)
101
24
  end
102
25
 
103
26
  def work
@@ -107,104 +30,60 @@ class ElasticManager
107
30
  date_to = Date.parse(@config['to'])
108
31
 
109
32
  unless true?(@config['force'])
110
- unless es_green?
33
+ unless @elastic.green?
111
34
  log.fatal "elasticsearch on #{@config['es']['url']} is not green"
112
35
  exit 1
113
36
  end
114
37
  end
115
38
 
116
39
  if indices.length == 1 && indices.first == '_all'
117
- indices = es_all_indices(date_from, date_to, 'close')
40
+ # indices = es_all_indices(date_from, date_to, 'close')
41
+ indices = @elastic.all_indices(date_from, date_to, 'close')
118
42
  end
119
43
 
120
44
  date_from.upto(date_to) do |date|
121
45
  date = date.to_s.tr!('-', '.')
122
46
 
123
47
  indices.each do |index_name|
124
- if @config['skip']['open'].include?(index_name)
125
- log.warn "#{index_name} index open skiped"
126
- next
48
+ if @config['settings'][index_name]
49
+ if @config['settings'][index_name]['skip_open']
50
+ log.debug @config['settings'][index_name]['skip_open'].inspect
51
+
52
+ if true?(@config['settings'][index_name]['skip_open'])
53
+ log.warn "#{index_name} index open skiped"
54
+ next
55
+ end
56
+ end
127
57
  end
128
58
 
129
59
  index = "#{index_name}-#{date}"
130
60
 
131
- status, response = es_request('get', "/_cat/indices/#{index}")
132
- if status == 404
133
- log.warn "#{index} index not found"
134
- log.info "trying snapshot restore for #{index}"
135
-
136
- snapshot_name = "snapshot_#{index}"
137
-
138
- # TODO: we need improve this if several snapshot repos used in elastic
139
- status, response = es_request('get', '/_snapshot/')
140
- if status == 200
141
- snapshot_repo = JSON.parse(response).keys.first
142
-
143
- status, response = es_request('get', "/_snapshot/#{snapshot_repo}/#{snapshot_name}/")
144
- if status == 200
145
- snapshot = JSON.parse(response)['snapshots']
146
- if snapshot.size == 1
147
- body = {
148
- index_settings: {
149
- 'index.number_of_replicas' => 0,
150
- 'index.refresh_interval' => -1,
151
- 'index.routing.allocation.require.box_type' => 'warm'
152
- }
153
- }
154
- status, response = es_request('post', "/_snapshot/#{snapshot_repo}/#{snapshot.first['snapshot']}/_restore", body)
61
+ response = @elastic.request(:get, "/_cat/indices/#{index}")
155
62
 
156
- if status == 200
157
- sleep 5
158
- restore_ok = false
159
- until restore_ok
160
- sleep 30
161
- status, response = es_request('get', "/#{index}/_recovery")
63
+ if response.code == 404
64
+ log.warn "#{index} index not found"
65
+ log.info "#{index} trying snapshot restore"
162
66
 
163
- # TODO: add logging of percent and time ?
164
- restore_ok = JSON.parse(response)[index]['shards'].map { |s| s['stage'] == 'DONE' }.all?{ |a| a == true }
165
- end
166
- log.info "#{index} restored"
167
- else
168
- log.fatal "can't restore snapshot response was: #{status} - #{response}"
169
- exit 1
170
- end
171
- else
172
- log.fatal "wrong snapshot size"
173
- exit 1
174
- end
175
- else
176
- log.fatal "can't work with snapshot response was: #{status} - #{response}"
177
- exit 1
178
- end
67
+ if @elastic.restore_snapshot(index)
68
+ log.info "#{index} restored"
179
69
  else
180
- log.fatal "can't work with snapshot response was: #{status} - #{response}"
181
- exit 1
70
+ log.error "#{index} troubles with restore"
182
71
  end
183
- elsif status == 200
184
- if response =~ /open/
72
+ elsif response.code == 200
73
+ if response.body.to_s =~ /open/
185
74
  log.warn "#{index} index already opened"
186
75
  next
187
76
  end
188
77
 
189
- begin
190
- status, response = es_request('post', "/#{index}/_open?master_timeout=3m")
191
- if status == 200
192
- response = JSON.parse(response)
193
- else
194
- log.fatal "wrong response code for #{index} open"
195
- exit 1
196
- end
197
- rescue JSON::ParserError => e
198
- log.fatal "json parse err: '''#{e.message}'''\n\t#{e.backtrace.join("\n\t")}"
199
- exit 1
200
- end
201
-
202
- if response['acknowledged'] == true
78
+ if @elastic.open_index(index)
203
79
  log.info "#{index} index open success"
204
80
  else
205
81
  log.fatal "#{index} index open failed"
206
82
  exit 1
207
83
  end
84
+ else
85
+ log.fatal "can't work with index #{index} response was: #{response.code} - #{response}"
86
+ exit 1
208
87
  end
209
88
  end
210
89
  end
@@ -1,13 +1,14 @@
1
+ require 'json'
2
+ require 'yajl'
1
3
  require 'elastic_manager/logger'
2
4
 
3
5
  module Config
4
-
5
6
  include Logging
6
7
 
7
8
  MAIN_PARAMS = %w[TASK INDICES FROM TO]
8
9
  MAIN_PARAMS.freeze
9
10
 
10
- ADDITIONAL_PARAMS = %w[ES_URL TIMEOUT_WRITE TIMEOUT_CONNECT TIMEOUT_READ RETRY SLEEP FORCE SKIP_OPEN]
11
+ ADDITIONAL_PARAMS = %w[ES_URL TIMEOUT_WRITE TIMEOUT_CONNECT TIMEOUT_READ RETRY SLEEP FORCE SETTINGS]
11
12
  ADDITIONAL_PARAMS.freeze
12
13
 
13
14
  BANNER_ENV = "Missing argument: #{MAIN_PARAMS.join(', ')}. "
@@ -28,11 +29,49 @@ module Config
28
29
  default['timeout']['write'] = '2'
29
30
  default['timeout']['connect'] = '3'
30
31
  default['timeout']['read'] = '60'
32
+ default['settings'] = {}
31
33
 
32
34
  log.debug "default config: #{default.inspect}"
33
35
  default
34
36
  end
35
37
 
38
+ def parse_settings(json)
39
+ begin
40
+ JSON.parse(json)
41
+ rescue JSON::ParserError => e
42
+ log.fatal "json parse err: '''#{e.message}'''\n\t#{e.backtrace.join("\n\t")}"
43
+ exit 1
44
+ end
45
+ end
46
+
47
+ def option_parser(result)
48
+ OptionParser.new do |parser|
49
+ MAIN_PARAMS.each do |param|
50
+ parser.on("--#{param.downcase}=#{param}") do |pr|
51
+ result[param.downcase] = pr
52
+ end
53
+ end
54
+
55
+ ADDITIONAL_PARAMS.each do |param|
56
+ parser.on("--#{param.downcase}=#{param}") do |pr|
57
+ params = param.split('_')
58
+
59
+ if params.length == 2
60
+ result[params[0].downcase][params[1].downcase] = pr
61
+ elsif params.length == 1
62
+ if params[0].downcase == 'settings'
63
+ result[params[0].downcase] = parse_settings(pr)
64
+ else
65
+ result[params[0].downcase] = pr
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end.parse!
71
+
72
+ result
73
+ end
74
+
36
75
  def load_from_env
37
76
  log.debug "will load config from ENV variables"
38
77
 
@@ -54,7 +93,11 @@ module Config
54
93
  if vars.length == 2
55
94
  result[vars[0].downcase][vars[1].downcase] = ENV[var]
56
95
  elsif vars.length == 1
57
- result[vars[0].downcase] = ENV[var]
96
+ if vars[0].downcase == 'settings'
97
+ result[vars[0].downcase] = parse_settings(ENV[var])
98
+ else
99
+ result[vars[0].downcase] = ENV[var]
100
+ end
58
101
  end
59
102
  end
60
103
  end
@@ -69,36 +112,9 @@ module Config
69
112
  log.debug "will load config from passed arguments"
70
113
  result = make_default_config
71
114
 
72
- optparse = OptionParser.new do |parser|
73
- MAIN_PARAMS.each do |param|
74
- parser.on("--#{param.downcase}=#{param}") do |pr|
75
- result[param.downcase] = pr
76
- end
77
- end
115
+ result = option_parser(result)
78
116
 
79
- ADDITIONAL_PARAMS.each do |param|
80
- parser.on("--#{param.downcase}=#{param}") do |pr|
81
- params = param.split('_')
82
-
83
- if vars.length == 2
84
- result[params[0].downcase][params[1].downcase] = pr
85
- elsif vars.length == 1
86
- result[params[0].downcase] = pr
87
- end
88
- end
89
- end
90
- end
91
-
92
- begin
93
- optparse.parse!
94
-
95
- mandatory = MAIN_PARAMS.map { |p| p.downcase }
96
- if mandatory.map { |key| result[key].empty? }.any?{ |a| a == true }
97
- raise OptionParser::MissingArgument.new(mandatory.join(', '))
98
- end
99
- rescue OptionParser::InvalidOption, OptionParser::MissingArgument
100
- # puts $!.to_s
101
- # puts optparse
117
+ if MAIN_PARAMS.map { |p| p.downcase }.map { |key| result[key].empty? }.any?{ |a| a == true }
102
118
  log.fatal BANNER_ARGV
103
119
  exit 1
104
120
  end
@@ -0,0 +1,188 @@
1
+ require 'http'
2
+ require 'yajl'
3
+ require 'elastic_manager/logger'
4
+ require 'elastic_manager/utils'
5
+
6
+ module Request
7
+ class Error < StandardError; end
8
+ class Throttling < Error; end
9
+ class ServerError < Error; end
10
+
11
+ class Elastic
12
+ include Logging
13
+ include Utils
14
+
15
+ RETRY_ERRORS = [StandardError, RuntimeError, Throttling]
16
+
17
+ def initialize(config)
18
+ @client = HTTP.timeout(
19
+ write: config['timeout']['write'].to_i,
20
+ connect: config['timeout']['connect'].to_i,
21
+ read: config['timeout']['read'].to_i
22
+ ).headers(
23
+ 'Accept': 'application/json',
24
+ 'Content-type': 'application/json'
25
+ )
26
+ @url = config['es']['url']
27
+ @retry = config['retry'].to_i
28
+ @sleep = config['sleep'].to_i
29
+ end
30
+
31
+ def with_retry
32
+ tries ||= @retry
33
+
34
+ yield
35
+
36
+ rescue *RETRY_ERRORS => e
37
+ log.warn "tries left #{tries + 1} '''#{e.message}''' sleeping #{@sleep} sec..."
38
+ sleep @sleep
39
+
40
+ retry unless (tries -= 1).zero?
41
+ log.fatal "backtrace:\n\t#{e.backtrace.join("\n\t")}"
42
+ exit 1
43
+ end
44
+
45
+ def request(method, url, body={})
46
+ uri = @url + url
47
+ log.debug "uri: #{uri}"
48
+
49
+ with_retry do
50
+ response = @client.request(method, uri, json: body)
51
+
52
+ if response.code == 503
53
+ raise Request::Throttling.new(response)
54
+ elsif response.status.server_error?
55
+ raise Request::ServerError.new(response)
56
+ end
57
+
58
+ response
59
+ end
60
+ end
61
+
62
+ def green?
63
+ response = request(:get, '/_cluster/health')
64
+ return json_parse(response)['status'] == 'green' if response.code == 200
65
+ false
66
+ end
67
+
68
+ def all_indices(from=nil, to=nil, state=nil, type=nil)
69
+ indices = get_all_indices
70
+
71
+ # TODO (anton.ryabov): next line just for debug purpose, need better handling
72
+ indices.each { |k, v| log.debug "#{k} - #{v.to_json}" unless v['settings'] }
73
+
74
+ indices.select!{ |_, v| v['state'] == state } if state
75
+ indices.select!{ |_, v| v['settings']['index']['routing']['allocation']['require']['box_type'] == type } if type
76
+
77
+ indices.map do |index, _|
78
+ begin
79
+ index_date = Date.parse(index.gsub('-', ''))
80
+ rescue ArgumentError => e
81
+ log.error "#{e.message} for #{index}"
82
+ next
83
+ end
84
+
85
+ URI.escape(index) if (from..to).cover? index_date
86
+ end
87
+ end
88
+
89
+ def get_all_indices
90
+ req_path = '/_cluster/state/metadata/'
91
+ req_params = '?filter_path=metadata.indices.*.state,'
92
+ req_params << 'metadata.indices.*.settings.index.routing.allocation.require.box_type'
93
+
94
+ response = request(:get, req_path + req_params)
95
+
96
+ if response.code == 200
97
+ return json_parse(response)['metadata']['indices']
98
+ else
99
+ log.fatal "can't work with all_indices response was: #{response.code} - #{response}"
100
+ exit 1
101
+ end
102
+ end
103
+
104
+ def find_snapshot_repo
105
+ # TODO: we need improve this if several snapshot repos used in elastic
106
+ response = request(:get, '/_snapshot/')
107
+
108
+ if response.code == 200
109
+ json_parse(response).keys.first
110
+ else
111
+ log.fatal "dunno what to do with: #{response.code} - #{response}"
112
+ exit 1
113
+ end
114
+ end
115
+
116
+ def find_snapshot(repo, snapshot_name)
117
+ response = request(:get, "/_snapshot/#{repo}/#{snapshot_name}/")
118
+
119
+ if response.code == 200
120
+ snapshot = json_parse(response)['snapshots']
121
+
122
+ if snapshot.size == 1
123
+ snapshot.first['snapshot']
124
+ else
125
+ log.fatal "wrong snapshot size"
126
+ exit 1
127
+ end
128
+ else
129
+ log.fatal "can't find snapshot #{snapshot_name} in #{repo} response was: #{response.code} - #{response}"
130
+ exit 1
131
+ end
132
+ end
133
+
134
+ def restore_snapshot(index)
135
+ snapshot_name = "snapshot_#{index}"
136
+ snapshot_repo = find_snapshot_repo
137
+ snapshot = find_snapshot(snapshot_repo, snapshot_name)
138
+
139
+ body = {
140
+ index_settings: {
141
+ 'index.number_of_replicas' => 0,
142
+ 'index.refresh_interval' => -1,
143
+ 'index.routing.allocation.require.box_type' => 'warm'
144
+ }
145
+ }
146
+ response = request(:post, "/_snapshot/#{snapshot_repo}/#{snapshot}/_restore", body)
147
+
148
+ if response.code == 200
149
+ sleep 5
150
+ wait_snapshot_restore(index)
151
+ else
152
+ log.fatal "can't restore snapshot #{snapshot_name} response was: #{response.code} - #{response}"
153
+ exit 1
154
+ end
155
+ end
156
+
157
+ def wait_snapshot_restore(index)
158
+ restore_ok = false
159
+
160
+ until restore_ok
161
+ sleep @sleep / 2
162
+ response = request(:get, "/#{index}/_recovery")
163
+
164
+ if response.code == 200
165
+ # TODO anton.ryabov: add logging of percent and time ?
166
+ restore_ok = json_parse(response)[index]['shards'].map { |s| s['stage'] == 'DONE' }.all?{ |a| a == true }
167
+ else
168
+ log.error "can't check recovery: #{response.code} - #{response}"
169
+ end
170
+ end
171
+
172
+ true
173
+ end
174
+
175
+ def open_index(index)
176
+ response = request(:post, "/#{index}/_open?master_timeout=1m")
177
+
178
+ if response.code == 200
179
+ response = json_parse(response)
180
+ else
181
+ log.fatal "wrong response code for #{index} open"
182
+ exit 1
183
+ end
184
+
185
+ return response['acknowledged'] == true
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,17 @@
1
+ require 'json'
2
+ require 'yajl'
3
+
4
+ module Utils
5
+ include Logging
6
+
7
+ def true?(obj)
8
+ obj.to_s.downcase == 'true'
9
+ end
10
+
11
+ def json_parse(string)
12
+ JSON.parse(string)
13
+ rescue JSON::ParserError => e
14
+ log.fatal "json parse err: '''#{e.message}'''\n\t#{e.backtrace.join("\n\t")}"
15
+ exit 1
16
+ end
17
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elastic_manager
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Antony Ryabov
@@ -52,6 +52,34 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '2.4'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '12.3'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '12.3'
69
+ - !ruby/object:Gem::Dependency
70
+ name: yajl-ruby
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.4'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.4'
55
83
  description: Manager for logstash indices in elastic
56
84
  email: mail@doam.ru
57
85
  executables:
@@ -69,6 +97,7 @@ files:
69
97
  - lib/elastic_manager/config.rb
70
98
  - lib/elastic_manager/logger.rb
71
99
  - lib/elastic_manager/request.rb
100
+ - lib/elastic_manager/utils.rb
72
101
  homepage: https://github.com/onetwotrip/elastic_manager
73
102
  licenses:
74
103
  - MIT
@@ -89,7 +118,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
89
118
  version: '0'
90
119
  requirements: []
91
120
  rubyforge_project:
92
- rubygems_version: 2.7.6
121
+ rubygems_version: 2.7.7
93
122
  signing_key:
94
123
  specification_version: 4
95
124
  summary: Because qurator sucks