flex-admin 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012-2013 by Domizio Demichelis
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,24 @@
1
+ # Flex-admin
2
+
3
+ Simple tool to dump/load one or more elasticsearch indices and types.
4
+
5
+ ## Links
6
+
7
+ * [Flex Repository](https://github.com/ddnexus/flex)
8
+ * [Flex Project (Global Documentation)](http://ddnexus.github.io/flex/doc/)
9
+ * [flex-admin Gem (Specific Documentation)](http://ddnexus.github.io/flex/doc/6-flex-admin)
10
+ * [Issues](https://github.com/ddnexus/flex-admin/issues)
11
+ * [Pull Requests](https://github.com/ddnexus/flex-admin/pulls)
12
+
13
+ ## Branches
14
+
15
+ The master branch reflects the last published gem. Then you may find a next-version branch (named after the version string), with the commits that will be merged in master just before publishing the next gem version. The next-version branch may get rebased or force pushed.
16
+
17
+ ## Credits
18
+
19
+ Special thanks for their sponsorship to [Escalate Media](http://www.escalatemedia.com) and [Barquin International](http://www.barquin.com).
20
+
21
+ ## Copyright
22
+
23
+ Copyright (c) 2012-2013 by [Domizio Demichelis](mailto://dd.nexus@gmail.com)<br>
24
+ See [LICENSE](https://github.com/ddnexus/flex-admin/blob/master/LICENSE) for details.
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.1
@@ -0,0 +1,112 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'flex-admin'
5
+
6
+ options = Flex::Admin::Tasks.new.default_options
7
+
8
+ version = File.read(File.expand_path('../../VERSION', __FILE__)).strip
9
+ copy = "flex-admin #{version} (c) 2012-2013 by Domizio Demichelis"
10
+ optparse = OptionParser.new do |opts|
11
+
12
+ opts.banner = <<-banner
13
+
14
+ flex-admin:
15
+ Generic binary tool to dump/load data from/to any elasticsearch index (no app needed).
16
+ If you need to migrate data, use the flex live-reindexing.
17
+
18
+ Usage:
19
+ flex-admin <command> [options]
20
+ <command>:
21
+ dump dumps the data from one or more elasticsearch indices
22
+ load loads a dumpfile
23
+ stats prints the full elasticsearch stats
24
+
25
+ Notice: The load command will load the dump-file into elasticsearch without removing any pre-existent data.
26
+ If you need fresh indices, use the flex:index:delete and flex:index:create rake tasks from your
27
+ application, which will also recreate the mapping.
28
+ banner
29
+
30
+
31
+ opts.separator ''
32
+ opts.separator 'Common options:'
33
+
34
+ opts.on( '-f', '--file [FILE]', "The path of the dumpfile (default: '#{options[:file]}')" ) do |f|
35
+ options[:file] = f
36
+ end
37
+
38
+ opts.on( '-r', '--[no-]verbose', "Run verbosely (default: '#{options[:verbose]}')" ) do |q|
39
+ options[:verbose] = q
40
+ end
41
+
42
+ opts.separator ''
43
+ opts.separator 'Dump options:'
44
+
45
+ opts.on( '-i', '--index [INDEX_OR_INDICES]', Array, 'The index or comma separated indices to dump (default: all indices)' ) do |i|
46
+ options[:index] = i
47
+ end
48
+
49
+ opts.on( '-t', '--type [TYPE_OR_TYPES]', Array, 'The type or comma separated types to dump (default: all types)' ) do |t|
50
+ options[:type] = t
51
+ end
52
+
53
+ opts.on( '-s', '--scroll [TIME]', Integer, "The ElasticSearch scroll time (default: #{options[:scroll]})" ) do |s|
54
+ options[:scroll] = s
55
+ end
56
+
57
+ opts.on( '-z', '--size [SIZE]', Integer, "The chunk size to dump per shard (default: #{options[:size]} * number of shards)" ) do |z|
58
+ options[:size] = z
59
+ end
60
+
61
+ opts.separator ''
62
+ opts.separator 'Load options:'
63
+
64
+ opts.on( '-m', '--index-map [INDEX_MAP]', 'The index rename map (example: -m=dumped_index_name:loaded_index_name,a:b)') do |m|
65
+ options[:index_map] = Hash[m.split(',').map{|i|i.split(':')}]
66
+ end
67
+
68
+ opts.on( '-o', '--timeout [SECONDS]', Integer, "The http_client timeout for bulk loading (default: #{options[:timeout]} seconds)" ) do |o|
69
+ options[:timeout] = o
70
+ end
71
+
72
+ opts.on( '-b', '--batch-size [BATCH_SIZE]', Integer, "The batch size to load (default: #{options[:batch_size]})" ) do |z|
73
+ options[:batch_size] = z
74
+ end
75
+
76
+ opts.separator ''
77
+ opts.separator 'Other options:'
78
+
79
+ opts.on( '-v', '--version', 'Shows the version and exits' ) do
80
+ puts version
81
+ exit
82
+ end
83
+
84
+ opts.on_tail( '-h', '--help', 'Displays this screen' ) do
85
+ puts copy
86
+ puts opts
87
+ exit
88
+ end
89
+
90
+ end
91
+
92
+ optparse.parse!
93
+ command = ARGV.first
94
+ exec "#{$0} -h" if command.nil?
95
+ puts copy
96
+
97
+ case command
98
+
99
+ when 'dump'
100
+ Flex::Admin::Tasks.new(options).dump_to_file(true)
101
+
102
+ when 'load'
103
+ Flex::Admin::Tasks.new(options).load_from_file
104
+
105
+ when 'stats'
106
+ puts '>> puts Flex.index_stats.to_yaml'
107
+ puts Flex.index_stats.to_yaml
108
+
109
+ else
110
+ puts "unknown command: #{command.inspect}"
111
+
112
+ end
@@ -0,0 +1,20 @@
1
+ require 'date'
2
+ version = File.read(File.expand_path('../VERSION', __FILE__)).strip
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'flex-admin'
6
+ s.summary = 'Dump/load/rename/live-redindex one or more elasticsearch indices and types.'
7
+ s.description = 'Provides binary and rake tasks to dump, load and optionally rename indices. Implements live-reindex with hot-swap of old code/index with new code/index.'
8
+ s.homepage = 'http://github.com/ddnexus/flex-admin'
9
+ s.authors = ["Domizio Demichelis"]
10
+ s.email = 'dd.nexus@gmail.com'
11
+ s.executables = %w[flex-admin]
12
+ s.extra_rdoc_files = %w[README.md]
13
+ s.files = `git ls-files -z`.split("\0")
14
+ s.version = version
15
+ s.date = Date.today.to_s
16
+ s.required_rubygems_version = ">= 1.3.6"
17
+ s.rdoc_options = %w[--charset=UTF-8]
18
+
19
+ s.add_runtime_dependency 'flex', version
20
+ end
@@ -0,0 +1,7 @@
1
+ require 'flex'
2
+ require 'flex/admin'
3
+ require 'flex/live_reindex'
4
+
5
+ Flex::LIB_PATHS << File.dirname(__FILE__)
6
+
7
+ Flex::Conf.redis = $redis || defined?(::Redis) && ::Redis.current
@@ -0,0 +1,114 @@
1
+ module Flex
2
+ module Admin
3
+
4
+ class Tasks
5
+
6
+ attr_reader :options
7
+
8
+ def initialize(overrides={})
9
+ options = Flex::Utils.env2options *default_options.keys
10
+
11
+ options[:size] = options[:size].to_i if options[:size]
12
+ options[:timeout] = options[:timeout].to_i if options[:timeout]
13
+ options[:batch_size] = options[:batch_size].to_i if options[:batch_size]
14
+ options[:index_map] = Hash[options[:index_map].split(',').map{|i|i.split(':')}] if options[:index_map] && options[:index_map].is_a?(String)
15
+
16
+ @options = default_options.merge(options).merge(overrides)
17
+ end
18
+
19
+ def default_options
20
+ @default_options ||= { :file => './flex.dump',
21
+ :index => Conf.variables[:index],
22
+ :type => Conf.variables[:type],
23
+ :scroll => '5m',
24
+ :size => 50,
25
+ :timeout => 20,
26
+ :batch_size => 1000,
27
+ :verbose => true,
28
+ :index_map => nil }
29
+ end
30
+
31
+ def dump_to_file(cli=false)
32
+ vars = { :index => cli ? options[:index] : (options[:index] || Flex::Tasks.new.config_hash.keys),
33
+ :type => options[:type] }
34
+ if options[:verbose]
35
+ total_hits = Flex.count(vars)['count'].to_i
36
+ total_count = 0
37
+ pbar = ProgBar.new(total_hits)
38
+ dump_stats = Hash.new { |hash, key| hash[key] = Hash.new { |h, k| h[k] = 0 } }
39
+ file_size = 0
40
+ end
41
+ vars.merge! :params => { :scroll => options[:scroll],
42
+ :size => options[:size],
43
+ :fields => '_source,*' }
44
+
45
+ file = options[:file].is_a?(String) ? File.open(options[:file], 'wb') : options[:file]
46
+ path = file.path
47
+
48
+ Flex.dump_all(vars) do |batch|
49
+ bulk_string = ''
50
+ batch.each do |document|
51
+ dump_stats[document['_index']][document['_type']] += 1 if options[:verbose]
52
+ bulk_string << Flex.build_bulk_string(document)
53
+ end
54
+ file.puts bulk_string
55
+ if options[:verbose]
56
+ total_count += batch.size
57
+ pbar.pbar.inc(batch.size)
58
+ end
59
+ end
60
+ file_size = file.size if options[:verbose]
61
+ file.close
62
+
63
+ if options[:verbose]
64
+ formatted_file_size = file_size.to_s.reverse.gsub(/...(?=.)/, '\&,').reverse
65
+ pbar.pbar.finish
66
+ puts "\n***** WARNING: Expected document to dump: #{total_hits}, dumped: #{total_count}. *****" \
67
+ unless total_hits == total_count
68
+ puts "\nDumped #{total_count} documents to #{path} (size: #{formatted_file_size} bytes)"
69
+ puts dump_stats.to_yaml
70
+ end
71
+ end
72
+
73
+ def load_from_file
74
+ Configuration.http_client.options[:timeout] = options[:timeout]
75
+ chunk_size = options[:batch_size] * 2 # 2 lines per doc
76
+ bulk_string = ''
77
+ file = options[:file].is_a?(String) ? File.open(options[:file]) : options[:file]
78
+ path = file.path
79
+ if options[:verbose]
80
+ line_count = 0
81
+ file.lines { line_count += 1 }
82
+ file.rewind
83
+ puts "\nLoading from #{path}...\n"
84
+ pbar = ProgBar.new(line_count / 2, options[:batch_size])
85
+ end
86
+ file.lines do |line|
87
+ bulk_string << (options[:index_map] ? map_index(line) : line)
88
+ if (file.lineno % chunk_size) == 0
89
+ result = Flex.post_bulk_string :bulk_string => bulk_string
90
+ bulk_string = ''
91
+ pbar.process_result(result, options[:batch_size]) if options[:verbose]
92
+ end
93
+ end
94
+ # last chunk
95
+ unless bulk_string == ''
96
+ result = Flex.post_bulk_string :bulk_string => bulk_string
97
+ pbar.process_result(result, (file.lineno % chunk_size) / 2) if options[:verbose]
98
+ end
99
+ file.close
100
+ pbar.finish if options[:verbose]
101
+ end
102
+
103
+ private
104
+
105
+ def map_index(line)
106
+ joined_keys = options[:index_map].keys.join('|')
107
+ line.sub(/"_index":"(#{joined_keys})"/) do |match_string|
108
+ options[:index_map].has_key?($1) ? %("_index":"#{options[:index_map][$1]}") : match_string
109
+ end
110
+ end
111
+
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,248 @@
1
+ module Flex
2
+ # private module
3
+ module LiveReindex
4
+
5
+ class MissingRedisError < StandardError; end
6
+ class LiveReindexInProgressError < StandardError; end
7
+ class MissingAppIdError < StandardError; end
8
+ class MissingStopIndexingProcError < StandardError; end
9
+ class MissingEnsureIndicesError < StandardError; end
10
+ class MissingOnReindexBlockError < StandardError; end
11
+ class ExtraIndexError < StandardError; end
12
+ class MultipleReindexError < StandardError; end
13
+
14
+ # private module
15
+ module Redis
16
+
17
+ KEYS = { :pid => 'flex-reindexing-pid',
18
+ :changes => 'flex-reindexing-changes' }
19
+
20
+ extend self
21
+
22
+ def method_missing(command, key, *args)
23
+ return unless Conf.redis
24
+ Conf.redis.send(command, "#{KEYS[key]}-#{Conf.app_id}", *args)
25
+ end
26
+
27
+ def reset_keys
28
+ KEYS.keys.each { |k| del k }
29
+ end
30
+
31
+ def init
32
+ begin
33
+ require 'redis'
34
+ rescue LoadError
35
+ raise MissingRedisError, 'The live-reindex feature rely on redis. Please, install redis and the "redis" gem.'
36
+ end
37
+ raise MissingAppIdError, 'You must set the Flex::Configuration.app_id, and be sure you deploy it before live-reindexing.' \
38
+ if Conf.app_id.nil? || Conf.app_id.empty?
39
+ raise LiveReindexInProgressError, %(It looks like a live-reindex is in progress (PID #{get(:pid)}). If you are sure that there is no live-reindex in progress, please run the "flex:reset_redis_keys" rake task and retry.) \
40
+ if get(:pid)
41
+ reset_keys # just in case
42
+ set(:pid, $$)
43
+ end
44
+
45
+ end
46
+
47
+ extend self
48
+
49
+ def on_reindex(&block)
50
+ @reindex = block
51
+ end
52
+
53
+ def on_each_change(&block)
54
+ @each_change = block
55
+ end
56
+ attr_reader :each_change
57
+
58
+ def on_stop_indexing(&block)
59
+ @stop_indexing = block
60
+ end
61
+
62
+ def reindex(opts={})
63
+ yield self
64
+ perform(opts)
65
+ end
66
+
67
+ def reindex_indices(opts={})
68
+ yield self if block_given?
69
+
70
+ opts[:verbose] = true unless opts.has_key?(:verbose)
71
+ opts[:index] ||= opts.delete(:indices) || config_hash.keys
72
+
73
+ # we override the on_reindex eventually set
74
+ on_reindex do
75
+ migrate_indices(opts)
76
+ end
77
+
78
+ perform(opts)
79
+ end
80
+
81
+ def should_prefix_index?
82
+ Redis.get(:pid) == $$.to_s
83
+ end
84
+
85
+ def should_track_change?
86
+ pid = Redis.get(:pid)
87
+ !!pid && !(pid == $$.to_s)
88
+ end
89
+
90
+ def track_change(action, document)
91
+ Redis.rpush(:changes, MultiJson.encode([action, document]))
92
+ end
93
+
94
+ # use this method when you are tracking the change of another app
95
+ # you must pass the app_id of the app being affected by the change
96
+ def track_external_change(app_id, action, document)
97
+ return unless Conf.redis
98
+ Conf.redis.rpush("#{KEYS[:changes]}-#{app_id}", MultiJson.encode([action, document]))
99
+ end
100
+
101
+ def prefix_index(index)
102
+ base = unprefix_index(index)
103
+ # raise if base is not included in @ensure_indices
104
+ raise ExtraIndexError, "The index #{base} is missing from the :ensure_indices option. Reindexing aborted." \
105
+ if @ensure_indices && !@ensure_indices.include?(base)
106
+ prefixed = @timestamp + base
107
+ unless @indices.include?(base)
108
+ unless Flex.exist?(:index => prefixed)
109
+ config_hash[base] = {} unless config_hash.has_key?(base)
110
+ Flex.POST "/#{prefixed}", config_hash[base]
111
+ end
112
+ @indices |= [base]
113
+ end
114
+ prefixed
115
+ end
116
+
117
+ # remove the (eventual) prefix
118
+ def unprefix_index(index)
119
+ index.sub(/^\d{14}_/, '')
120
+ end
121
+
122
+ private
123
+
124
+ def config_hash
125
+ @config_hash ||= ModelTasks.new.config_hash
126
+ end
127
+
128
+ def perform(opts={})
129
+ Conf.logger.warn 'Safe reindex is disabled!' if opts[:safe_reindex] == false
130
+ Redis.init
131
+ @indices = []
132
+ @timestamp = Time.now.strftime('%Y%m%d%H%M%S_')
133
+ @ensure_indices = nil
134
+
135
+ unless opts[:on_stop_indexing] == false || Conf.on_stop_indexing == false
136
+ @stop_indexing ||= Conf.on_stop_indexing || raise(MissingStopIndexingProcError, 'The on_stop_indexing block is not set.')
137
+ end
138
+
139
+ raise MissingOnReindexBlockError, 'You must configure an on_reindex block.' \
140
+ unless @reindex
141
+
142
+ raise MissingEnsureIndicesError, 'You must pass the :ensure_indices option when you pass the :models option.' \
143
+ if opts.has_key?(:models) && !opts.has_key?(:ensure_indices)
144
+ if opts[:ensure_indices]
145
+ @ensure_indices = opts.delete(:ensure_indices)
146
+ @ensure_indices = @ensure_indices.split(',') unless @ensure_indices.is_a?(Array)
147
+ each_change = @each_change
148
+ @each_change = nil
149
+ migrate_indices(:index => @ensure_indices)
150
+ @each_change = each_change
151
+ end
152
+
153
+ @reindex.call
154
+
155
+ # when the reindexing is finished we try to empty the changes list a few times
156
+ tries = 0
157
+ bulk_string = ''
158
+ until (count = Redis.llen(:changes)) == 0 || tries > 9
159
+ count.times { bulk_string << build_bulk_string_from_change(Redis.lpop(:changes))}
160
+ Flex.post_bulk_string(:bulk_string => bulk_string)
161
+ bulk_string = ''
162
+ tries += 1
163
+ end
164
+ # at this point the changes list should be empty or contain the minimum number of changes we could achieve live
165
+ # the @stop_indexing should ensure to stop/suspend all the actions that would produce changes in the indices being reindexed
166
+ @stop_indexing.call if @stop_indexing
167
+ # if we have still changes, we can index them (until the list will be empty)
168
+ bulk_string = ''
169
+ while (change = Redis.lpop(:changes))
170
+ bulk_string << build_bulk_string_from_change(change)
171
+ end
172
+ Flex.post_bulk_string(:bulk_string => bulk_string)
173
+
174
+ # deletes the old indices and create the aliases to the new
175
+ @indices.each do |index|
176
+ Flex.delete_index :index => index
177
+ Flex.put_index_alias :alias => index,
178
+ :index => @timestamp + index
179
+ end
180
+ # after the execution of this method the user should deploy the new code and then resume the regular app processing
181
+
182
+ # we redefine this method so it will raise an error if any new live-reindex is attempted during this session.
183
+ unless opts[:safe_reindex] == false
184
+ class_eval <<-ruby, __FILE__, __LINE__
185
+ def perform(*)
186
+ raise MultipleReindexError, "Multiple live-reindex attempted! You cannot use any reindexing method multiple times in the same session or you may corrupt your index/indices! The previous reindexing in this session successfully reindexed and swapped the new index/indices: #{@indices.map{|i| @timestamp + i}.join(', ')}. You must deploy now, and run the other reindexing in single successive deploys ASAP. Notice that if the code-changes that you are about to deploy rely on the successive reindexings that have been aborted, your app may fail. If you are working in development mode you must restart the session now. The next time you can silence this error by passing :safe_reindex => false"
187
+ end
188
+ ruby
189
+ end
190
+
191
+ rescue Exception
192
+ # delete all the created indices
193
+ @indices ||=[]
194
+ @indices.each do |index|
195
+ Flex.delete_index :index => @timestamp + index
196
+ end
197
+ raise
198
+
199
+ ensure
200
+ Redis.reset_keys
201
+ end
202
+
203
+
204
+ def migrate_indices(opts)
205
+ opts[:verbose] = true unless opts.has_key?(:verbose)
206
+ pbar = ProgBar.new(Flex.count(opts)['count'], nil, "index #{opts[:index].inspect}: ") if opts[:verbose]
207
+
208
+ Flex.dump_all(opts) do |batch|
209
+ result = process_and_post_batch(batch)
210
+ pbar.process_result(result, batch.size) if opts[:verbose]
211
+ end
212
+
213
+ pbar.finish if opts[:verbose]
214
+ end
215
+
216
+ def process_and_post_batch(batch)
217
+ bulk_string = ''
218
+ batch.each do |document|
219
+ bulk_string << build_bulk_string('index', document)
220
+ end
221
+ Flex.post_bulk_string(:bulk_string => bulk_string)
222
+ end
223
+
224
+ def build_bulk_string_from_change(change)
225
+ action, document = MultiJson.decode(change)
226
+ return '' unless @indices.include?(unprefix_index(document['_index']))
227
+ build_bulk_string(action, document)
228
+ end
229
+
230
+ def build_bulk_string(action, document)
231
+ result = if @each_change
232
+ document.extend(Result::Document)
233
+ document.extend(Result::DocumentLoader)
234
+ @each_change.call(action, document)
235
+ else
236
+ [{ action => document }]
237
+ end
238
+ result = [result] unless result.is_a?(Array)
239
+ bulk_string = ''
240
+ result.compact.each do |hash|
241
+ act, doc = hash.to_a.flatten
242
+ bulk_string << Flex.build_bulk_string(doc, :action => act)
243
+ end
244
+ bulk_string
245
+ end
246
+
247
+ end
248
+ end
@@ -0,0 +1,17 @@
1
+ require 'flex'
2
+ require 'flex-admin'
3
+
4
+ env = defined?(Rails) ? :environment : []
5
+
6
+ namespace :flex do
7
+ namespace :admin do
8
+
9
+ desc 'Dumps the data from one or more ElasticSearch indices to a file'
10
+ task(:dump => env) { Flex::Admin::Tasks.new.dump_to_file }
11
+
12
+ desc 'Loads a dumpfile into ElasticSearch'
13
+ task(:load => env) { Flex::Admin::Tasks.new.load_from_file }
14
+
15
+ end
16
+
17
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: flex-admin
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Domizio Demichelis
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-08-12 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: flex
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - '='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.0.1
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - '='
28
+ - !ruby/object:Gem::Version
29
+ version: 1.0.1
30
+ description: Provides binary and rake tasks to dump, load and optionally rename indices.
31
+ Implements live-reindex with hot-swap of old code/index with new code/index.
32
+ email: dd.nexus@gmail.com
33
+ executables:
34
+ - flex-admin
35
+ extensions: []
36
+ extra_rdoc_files:
37
+ - README.md
38
+ files:
39
+ - LICENSE
40
+ - README.md
41
+ - VERSION
42
+ - bin/flex-admin
43
+ - flex-admin.gemspec
44
+ - lib/flex-admin.rb
45
+ - lib/flex/admin.rb
46
+ - lib/flex/live_reindex.rb
47
+ - lib/tasks.rake
48
+ homepage: http://github.com/ddnexus/flex-admin
49
+ licenses: []
50
+ post_install_message:
51
+ rdoc_options:
52
+ - --charset=UTF-8
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ! '>='
65
+ - !ruby/object:Gem::Version
66
+ version: 1.3.6
67
+ requirements: []
68
+ rubyforge_project:
69
+ rubygems_version: 1.8.25
70
+ signing_key:
71
+ specification_version: 3
72
+ summary: Dump/load/rename/live-redindex one or more elasticsearch indices and types.
73
+ test_files: []