elastic_manager 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
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