elastic_manager 0.1.4 → 0.1.5

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: 94343d3bac2671d6a49463c67d406afb79738edd347b63899ae32bcb58b1ac5e
4
- data.tar.gz: aa36442960604bf810217e82d18c927db87d457e4d4c608202d85870ed190673
3
+ metadata.gz: d7b65a40dfcbcc2632b136bc931cdf0d1118d48281fa31ac134433ca6b2fac96
4
+ data.tar.gz: '0941e34bc0ef2beb51cec9d68d19e3c1c6f91060546f85a6a1838be20c173c72'
5
5
  SHA512:
6
- metadata.gz: 892bf769d7e3392b4ce3dea9d3062b4ee3c2d7bb0335008478a6444d0b51c61d872b0d202ded7e978c854533b2150f08e560dacbf7dc04eb514d4bc9225358ef
7
- data.tar.gz: aadad9042c0bd64848daf39c746639d3e8edcc0ad681686941ecd413324cba29375fdb1c586423abc727fef2e04cbaffe64b171f93c3cb1729640bf2763e0563
6
+ metadata.gz: 362ddbdfd8a8fdfdd440d0dd959ad0ba541d259501b17a0ce5ad7ede07fe0664acb7bcee6440f2c0b53c3aa868901f7f978ed620f56dab7b60abfd0252b7bebc
7
+ data.tar.gz: 0f50e67342101c754c7314aed5e7188fc762e2615a9946c14e36c78aa2442dce8e89634fd97d642e580aaec7d98f2f8926a0db7c58d74f6faecfb98c6370e608
data/README.md CHANGED
@@ -1 +1,14 @@
1
+ # elastic_manager
2
+
3
+ Manager for logstash indices in elasticsearch. Why? Because qurator sucks!
4
+
1
5
  [![Build Status](https://travis-ci.org/onetwotrip/elastic_manager.svg?branch=master)](https://travis-ci.org/onetwotrip/elastic_manager)
6
+
7
+ Progress:
8
+
9
+ - [x] Open closed indices
10
+ - [x] Open indices in snapshot (restore snapshot)
11
+ - [x] Close indices
12
+ - [ ] Chill indices
13
+ - [ ] Snapshot indices
14
+ - [ ] Delete snapshots
data/bin/elastic_manager CHANGED
@@ -1,9 +1,10 @@
1
1
  #! /usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'elastic_manager'
4
5
 
5
6
  STDOUT.sync = true
6
7
 
7
- elastic_manager = ElasticManager.new(ARGV)
8
+ elastic_manager = ElasticManager.new()
8
9
 
9
10
  elastic_manager.run
@@ -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.4'
4
+ s.version = '0.1.5'
5
5
  s.date = '2018-10-15'
6
6
  s.summary = 'Because qurator sucks'
7
7
  s.description = 'Manager for logstash indices in elastic'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'dotenv/load'
2
4
  require 'date'
3
5
  require 'elastic_manager/config'
@@ -7,6 +9,7 @@ require 'elastic_manager/utils'
7
9
  require 'elastic_manager/open'
8
10
  require 'elastic_manager/close'
9
11
 
12
+ # Main
10
13
  class ElasticManager
11
14
  include Config
12
15
  include Logging
@@ -15,17 +18,15 @@ class ElasticManager
15
18
  include Open
16
19
  include Close
17
20
 
18
- def initialize(argv)
19
- if argv.size == 0
20
- @config = load_from_env
21
- else
22
- @config = load_from_argv
23
- end
21
+ def initialize
22
+ @config = load_from_env
24
23
 
25
24
  @elastic = Request::Elastic.new(@config)
26
25
  end
27
26
 
28
27
  def run
29
- action(@config['task'].downcase)
28
+ if @config['task'].casecmp('open').zero?
29
+ open
30
+ end
30
31
  end
31
32
  end
@@ -1,23 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'json'
2
4
  require 'yajl'
3
5
  require 'elastic_manager/logger'
4
6
 
7
+ # Read, validate and merge with default config
5
8
  module Config
6
9
  include Logging
7
10
 
8
- MAIN_PARAMS = %w[TASK INDICES FROM TO]
9
- MAIN_PARAMS.freeze
10
-
11
- ADDITIONAL_PARAMS = %w[ES_URL TIMEOUT_WRITE TIMEOUT_CONNECT TIMEOUT_READ RETRY SLEEP FORCE SETTINGS]
12
- ADDITIONAL_PARAMS.freeze
13
-
14
- BANNER_ENV = "Missing argument: #{MAIN_PARAMS.join(', ')}. "
15
- BANNER_ENV << "Usage: #{MAIN_PARAMS.map{ |p| "#{p}=#{p}"}.join(' ')} "
16
- BANNER_ENV << "#{ADDITIONAL_PARAMS.map{ |p| "#{p}=#{p}"}.join(' ')} elastic_manager"
17
-
18
- BANNER_ARGV = "Missing argument: #{MAIN_PARAMS.join(', ')}. "
19
- BANNER_ARGV << "Usage: elastic_manager #{MAIN_PARAMS.map{ |p| "--#{p.downcase}=#{p.downcase}"}.join(' ')} "
20
- BANNER_ARGV << "#{ADDITIONAL_PARAMS.map{ |p| "--#{p.downcase}=#{p.downcase}"}.join(' ')}"
11
+ PARAMS = %w[
12
+ TASK
13
+ INDICES
14
+ FROM
15
+ TO
16
+ DAYSAGO
17
+ ES_URL
18
+ TIMEOUT_WRITE
19
+ TIMEOUT_CONNECT
20
+ TIMEOUT_READ
21
+ RETRY
22
+ SLEEP
23
+ FORCE
24
+ SETTINGS
25
+ ].freeze
21
26
 
22
27
  def make_default_config
23
28
  default = Hash.new { |hash, key| hash[key] = Hash.new(&hash.default_proc) }
@@ -28,97 +33,61 @@ module Config
28
33
  default['force'] = 'false'
29
34
  default['timeout']['write'] = '2'
30
35
  default['timeout']['connect'] = '3'
31
- default['timeout']['read'] = '60'
36
+ default['timeout']['read'] = '120'
37
+ default['daysago'] = ''
32
38
  default['settings'] = {}
39
+ default['daysago'] = ''
33
40
 
34
41
  log.debug "default config: #{default.inspect}"
35
42
  default
36
43
  end
37
44
 
38
- def option_parser(result)
39
- OptionParser.new do |parser|
40
- MAIN_PARAMS.each do |param|
41
- parser.on("--#{param.downcase}=#{param}") do |pr|
42
- result[param.downcase] = pr
43
- end
44
- end
45
-
46
- ADDITIONAL_PARAMS.each do |param|
47
- parser.on("--#{param.downcase}=#{param}") do |pr|
48
- params = param.split('_')
49
-
50
- if params.length == 2
51
- result[params[0].downcase][params[1].downcase] = pr
52
- elsif params.length == 1
53
- if params[0].downcase == 'settings'
54
- result[params[0].downcase] = json_parse(pr)
55
- else
56
- result[params[0].downcase] = pr
57
- end
58
- end
59
- end
60
- end
61
- end.parse!
62
-
63
- result
45
+ def check_settings(var)
46
+ if var.casecmp('settings').zero?
47
+ json_parse(ENV[var])
48
+ else
49
+ ENV[var]
50
+ end
64
51
  end
65
52
 
66
- def get_env_vars(var, result)
67
- vars = var.split('_')
53
+ def env_parser(config)
54
+ PARAMS.each do |var|
55
+ next if ENV[var] == '' || ENV[var].nil?
68
56
 
69
- if vars.length == 2
70
- result[vars[0].downcase][vars[1].downcase] = ENV[var]
71
- elsif vars.length == 1
72
- if vars[0].downcase == 'settings'
73
- result[vars[0].downcase] = json_parse(ENV[var])
74
- else
75
- result[vars[0].downcase] = ENV[var]
57
+ vars = var.split('_')
58
+
59
+ if vars.length == 2
60
+ config[vars[0].downcase][vars[1].downcase] = ENV[var]
61
+ elsif vars.length == 1
62
+ config[vars[0].downcase] = check_settings(vars[0])
76
63
  end
77
64
  end
78
65
 
79
- result
66
+ config
80
67
  end
81
68
 
82
- def env_parser(result)
83
- MAIN_PARAMS.each do |var|
84
- if ENV[var] == '' || ENV[var].nil?
85
- log.fatal BANNER_ENV
86
- exit 1
87
- end
69
+ # def present?
70
+ # !blank?
71
+ # end
88
72
 
89
- result[var.downcase] = ENV[var]
73
+ def exit_if_invalid(config)
74
+ if config['task'].empty? || config['indices'].empty?
75
+ fail_and_exit('not enough env variables. TASK, INDICES')
90
76
  end
91
77
 
92
- ADDITIONAL_PARAMS.each do |var|
93
- result = get_env_vars(var, result) unless ENV[var] == '' || ENV[var].nil?
78
+ unless (config['from'].empty? && config['to'].empty?) || config['daysago'].empty?
79
+ fail_and_exit('not enough env variables. FROM/TO or DAYSAGO')
94
80
  end
95
-
96
- result
97
81
  end
98
82
 
99
83
  def load_from_env
100
- log.debug "will load config from ENV variables"
84
+ log.debug 'will load config from ENV variables'
101
85
 
102
- result = make_default_config
103
- result = env_parser(result)
104
-
105
- log.debug "env config: #{result.inspect}"
106
- result
107
- end
108
-
109
- def load_from_argv
110
- require 'optparse'
111
-
112
- log.debug "will load config from passed arguments"
113
- result = make_default_config
114
- result = option_parser(result)
115
-
116
- if MAIN_PARAMS.map { |p| p.downcase }.map { |key| result[key].empty? }.any?{ |a| a == true }
117
- log.fatal BANNER_ARGV
118
- exit 1
119
- end
86
+ config = make_default_config
87
+ config = env_parser(config)
88
+ exit_if_invalid(config)
120
89
 
121
- log.debug "argv config: #{result.inspect}"
122
- result
90
+ log.debug "env config: #{config.inspect}"
91
+ config
123
92
  end
124
93
  end
@@ -1,16 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'logger'
2
4
  require 'colorize'
3
5
 
6
+ # Universal global logging
4
7
  module Logging
5
8
 
6
9
  SEVERITY_COLORS = {
7
- 'DEBUG' => 'cyan',
8
- 'INFO' => 'green',
9
- 'WARN' => 'yellow',
10
- 'ERROR' => 'light_red',
11
- 'FATAL' => 'red',
12
- 'UNKNOWN' => 'magenta'
13
- }
10
+ DEBUG: 'cyan',
11
+ INFO: 'green',
12
+ WARN: 'yellow',
13
+ ERROR: 'light_red',
14
+ FATAL: 'red',
15
+ UNKNOWN: 'magenta'
16
+ }.freeze
14
17
 
15
18
  def log
16
19
  @log ||= Logging.logger_for(self.class.name)
@@ -1,9 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'elastic_manager/logger'
4
+
5
+ # Index opening operations
1
6
  module Open
2
- def do_open(indices, date)
3
- indices.each do |index_name|
4
- next if skip?('open', index_name)
7
+ include Logging
8
+
9
+ def open_prechecks(date_from, date_to)
10
+ unless date_from.nil?
11
+ if date_from > date_to
12
+ log.fatal "wrong dates: date to is behind date from. from: #{date_from}, to: #{date_to}"
13
+ exit 1
14
+ end
15
+ end
16
+
17
+ unless true?(@config['force']) && @elastic.green?
18
+ fail_and_exit("elasticsearch on #{@config['es']['url']} is not green")
19
+ end
20
+ end
21
+
22
+ def skip_open?(index)
23
+ index_name = index.split('-')[0..-2].join('-')
24
+
25
+ if @config['settings'][index_name] && @config['settings'][index_name]['skip_open']
26
+ if true?(@config['settings'][index_name]['skip_open'])
27
+ log.warn "#{index_name} index open skiped"
28
+ return true
29
+ end
30
+ end
31
+
32
+ false
33
+ end
34
+
35
+ def index_exist?(response)
36
+ if response.code == 200
37
+ true
38
+ elsif response.code == 404
39
+ false
40
+ else
41
+ log.fatal "wtf in index_exist? response was: #{response.code} - #{response}"
42
+ exit 1
43
+ end
44
+ end
45
+
46
+ def already_open?(response)
47
+ index = json_parse(response).first
48
+ if index['status'] == 'open'
49
+ log.warn "#{index['index']} index status already open"
50
+ return true
51
+ end
52
+
53
+ false
54
+ end
55
+
56
+ def open_prepare_vars
57
+ indices = @config['indices'].split(',')
58
+ daysago = @config['daysago'].to_i
59
+ date_from = @config['from']
60
+ date_to = @config['to']
61
+
62
+ date_from = date_from.empty? ? nil : Date.parse(date_from)
63
+ date_to = date_to.empty? ? nil : Date.parse(date_to)
64
+
65
+ [indices, date_from, date_to, daysago]
66
+ end
67
+
68
+ def action_with_log(action, index)
69
+ if @elastic.send(action, index)
70
+ log.info "#{index} #{action} succes"
71
+ else
72
+ log.error "#{index} #{action} fail"
73
+ end
74
+ end
75
+
76
+ def populate_indices(indices, date_from, date_to, daysago)
77
+ result = []
78
+
79
+ if indices.length == 1 && indices.first == '_all'
80
+ result = @elastic.all_indices(date_from, date_to, daysago, 'close')
81
+ result += @elastic.all_indices_in_snapshots(date_from, date_to, daysago)
82
+ return result
83
+ end
84
+
85
+ if date_from.nil?
86
+ result = @elastic.all_indices(date_from, date_to, daysago, 'close')
87
+ result += @elastic.all_indices_in_snapshots(date_from, date_to, daysago)
88
+ return result.select { |r| r.start_with?(*indices) }
89
+ else
90
+ date_from.upto(date_to) do |date|
91
+ indices.each do |index|
92
+ result << "#{index}-#{date.to_s.tr!('-', '.')}"
93
+ end
94
+ end
95
+ end
96
+
97
+ return result unless result.empty?
98
+
99
+ log.fatal 'no indices for work'
100
+ exit 1
101
+ end
102
+
103
+ def do_open(indices)
104
+ indices.each do |index|
105
+ next if skip_open?(index)
5
106
 
6
- index = "#{index_name}-#{date}"
7
107
  response = @elastic.request(:get, "/_cat/indices/#{index}")
8
108
 
9
109
  if index_exist?(response)
@@ -18,4 +118,14 @@ module Open
18
118
  end
19
119
  end
20
120
  end
121
+
122
+ def open
123
+ indices, date_from, date_to, daysago = open_prepare_vars
124
+ open_prechecks(date_from, date_to)
125
+ indices = populate_indices(indices, date_from, date_to, daysago)
126
+
127
+ log.debug indices.inspect
128
+
129
+ do_open(indices)
130
+ end
21
131
  end
@@ -1,18 +1,23 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'http'
2
4
  require 'yajl'
3
5
  require 'elastic_manager/logger'
4
6
  require 'elastic_manager/utils'
7
+ require 'cgi'
5
8
 
9
+ # All kind of requests
6
10
  module Request
7
11
  class Error < StandardError; end
8
12
  class Throttling < Error; end
9
13
  class ServerError < Error; end
10
14
 
15
+ # Elasticsearch requests wrapper
11
16
  class Elastic
12
17
  include Logging
13
18
  include Utils
14
19
 
15
- RETRY_ERRORS = [StandardError, RuntimeError, Throttling]
20
+ RETRY_ERRORS = [StandardError, RuntimeError, Throttling].freeze
16
21
 
17
22
  def initialize(config)
18
23
  @client = HTTP.timeout(
@@ -62,30 +67,71 @@ module Request
62
67
  def green?
63
68
  response = request(:get, '/_cluster/health')
64
69
  return json_parse(response)['status'] == 'green' if response.code == 200
70
+
65
71
  false
66
72
  end
67
73
 
68
- def all_indices(from=nil, to=nil, state=nil, type=nil)
74
+ def all_indices_in_snapshots(from=nil, to=nil, daysago=nil)
75
+ all_snapshots = get_all_snapshots
76
+ all_snapshots.select! { |snap| snap['status'] == 'SUCCESS' }
77
+
78
+ result = []
79
+ all_snapshots.each do |snap|
80
+ begin
81
+ snap_date = Date.parse(snap['id'].gsub('-', ''))
82
+ rescue ArgumentError => e
83
+ log.error "#{e.message} for #{index}"
84
+ next
85
+ end
86
+
87
+ if from.nil? && snap_date < (Date.today - daysago)
88
+ result << CGI.escape(snap['id'].gsub('snapshot_', ''))
89
+ elsif (from..to).cover? snap_date
90
+ result << CGI.escape(snap['id'].gsub('snapshot_', ''))
91
+ end
92
+ end
93
+
94
+ result
95
+ end
96
+
97
+ def get_all_snapshots
98
+ snapshot_repo = find_snapshot_repo
99
+ response = request(:get, "/_cat/snapshots/#{snapshot_repo}")
100
+
101
+ if response.code == 200
102
+ json_parse(response)
103
+ else
104
+ log.fatal "can't work with all_snapshots response was: #{response.code} - #{response}"
105
+ exit 1
106
+ end
107
+ end
108
+
109
+ def all_indices(from=nil, to=nil, daysago=nil, state=nil, type=nil)
69
110
  indices = get_all_indices
70
111
 
71
- # TODO (anton.ryabov): next line just for debug purpose, need better handling
112
+ # TODO: (anton.ryabov) next line just for debug purpose, need better handling
72
113
  indices.each { |k, v| log.debug "#{k} - #{v.to_json}" unless v['settings'] }
73
114
 
74
- indices.select!{ |_, v| v['state'] == state } if state
75
- indices.select!{ |_, v| v['settings']['index']['routing']['allocation']['require']['box_type'] == type } if type
115
+ indices.select! { |_, v| v['state'] == state } if state
116
+ indices.select! { |_, v| v['settings']['index']['routing']['allocation']['require']['box_type'] == type } if type
76
117
 
77
- indices.select! do |index, _|
118
+ result = []
119
+ indices.each do |index, _|
78
120
  begin
79
121
  index_date = Date.parse(index.gsub('-', ''))
80
122
  rescue ArgumentError => e
81
123
  log.error "#{e.message} for #{index}"
82
124
  next
83
125
  end
84
- (from..to).cover? index_date
126
+
127
+ if from.nil? && index_date < (Date.today - daysago)
128
+ result << CGI.escape(index)
129
+ elsif (from..to).cover? index_date
130
+ result << CGI.escape(index)
131
+ end
85
132
  end
86
133
 
87
- indices = indices.keys.map { |i| i.split('-')[0..-2].join('-') }
88
- return indices
134
+ result
89
135
  end
90
136
 
91
137
  def get_all_indices
@@ -96,7 +142,7 @@ module Request
96
142
  response = request(:get, req_path + req_params)
97
143
 
98
144
  if response.code == 200
99
- return json_parse(response)['metadata']['indices']
145
+ json_parse(response)['metadata']['indices']
100
146
  else
101
147
  log.fatal "can't work with all_indices response was: #{response.code} - #{response}"
102
148
  exit 1
@@ -164,7 +210,7 @@ module Request
164
210
  response = request(:get, "/#{index}/_recovery")
165
211
 
166
212
  if response.code == 200
167
- # TODO anton.ryabov: add logging of percent and time ?
213
+ # TODO: (anton.ryabov) add logging of percent and time ?
168
214
  restore_ok = json_parse(response)[index]['shards'].map { |s| s['stage'] == 'DONE' }.all?{ |a| a == true }
169
215
  else
170
216
  log.error "can't check recovery: #{response.code} - #{response}"
@@ -184,7 +230,7 @@ module Request
184
230
  exit 1
185
231
  end
186
232
 
187
- return response['acknowledged'] == true
233
+ response['acknowledged'].true?
188
234
  end
189
235
  end
190
236
  end
@@ -1,16 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'json'
2
4
  require 'yajl'
3
5
 
6
+ # Sharable methods
4
7
  module Utils
5
8
  include Logging
6
9
 
7
- REVERT_STATE = {
10
+ TASK_TO_STATE = {
8
11
  'open' => 'close',
9
12
  'close' => 'open'
10
13
  }
11
14
 
12
15
  def true?(obj)
13
- obj.to_s.downcase == 'true'
16
+ obj.to_s.casecmp('true').zero?
14
17
  end
15
18
 
16
19
  def json_parse(string)
@@ -20,86 +23,8 @@ module Utils
20
23
  exit 1
21
24
  end
22
25
 
23
- def prechecks(date_from, date_to)
24
- if date_from > date_to
25
- log.fatal "wrong dates: date to is behind date from. from: #{date_from}, to: #{date_to}"
26
- exit 1
27
- end
28
-
29
- unless true?(@config['force'])
30
- unless @elastic.green?
31
- log.fatal "elasticsearch on #{@config['es']['url']} is not green"
32
- exit 1
33
- end
34
- end
35
- end
36
-
37
- def prepare_vars
38
- indices = @config['indices'].split(',')
39
- date_from = Date.parse(@config['from'])
40
- date_to = Date.parse(@config['to'])
41
-
42
- return indices, date_from, date_to
43
- end
44
-
45
- def all_precheck(indices, date_from, date_to, state)
46
- if indices.length == 1 && indices.first == '_all'
47
- indices = @elastic.all_indices(date_from, date_to, state)
48
- end
49
-
50
- indices
51
- end
52
-
53
- def action_with_log(action, index)
54
- if @elastic.index(action, index)
55
- log.info "#{index} #{action} succes"
56
- else
57
- log.error "#{index} #{action} fail"
58
- end
59
- end
60
-
61
- def index_exist?(response)
62
- if response.code == 200
63
- return true
64
- elsif response.code == 404
65
- return false
66
- else
67
- log.fatal "wtf in index_exist? response was: #{response.code} - #{response}"
68
- exit 1
69
- end
70
- end
71
-
72
- def already?(status, response, index)
73
- if json_parse(response).first['status'] == status
74
- log.warn "#{index} index status already #{status}"
75
- return true
76
- end
77
-
78
- false
79
- end
80
-
81
- def skip?(status, index_name)
82
- if @config['settings'][index_name]
83
- if @config['settings'][index_name]['skip'][status]
84
- log.debug @config['settings'][index_name]['skip'][status].inspect
85
-
86
- if true?(@config['settings'][index_name]['skip'][status])
87
- log.warn "#{index_name} index #{status} skiped"
88
- return true
89
- end
90
- end
91
- end
92
-
93
- false
94
- end
95
-
96
- def action(task)
97
- indices, date_from, date_to = prepare_vars
98
- prechecks(date_from, date_to)
99
- indices = all_precheck(indices, date_from, date_to, REVERT_STATE[task])
100
-
101
- date_from.upto(date_to) do |date|
102
- self.send("do_#{task}", indices, date.to_s.tr!('-', '.'))
103
- end
26
+ def fail_and_exit(text)
27
+ log.fatal text
28
+ exit 1
104
29
  end
105
30
  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.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Antony Ryabov