gd_es 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +6 -0
- data/bin/es +375 -0
- data/es.rdoc +5 -0
- data/lib/commands.rb +406 -0
- data/lib/es.rb +1077 -0
- data/lib/es_version.rb +3 -0
- data/lib/templates/deleted_records.jsonify +12 -0
- data/lib/templates/extract_map.jsonify +41 -0
- data/lib/templates/extract_task.jsonify +13 -0
- data/lib/templates/load.jsonify +8 -0
- metadata +271 -0
data/README.rdoc
ADDED
data/bin/es
ADDED
@@ -0,0 +1,375 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# 1.9 adds realpath to resolve symlinks; 1.8 doesn't
|
3
|
+
# have this method, so we add it so we get resolved symlinks
|
4
|
+
# and compatibility
|
5
|
+
unless File.respond_to? :realpath
|
6
|
+
class File #:nodoc:
|
7
|
+
def self.realpath path
|
8
|
+
return realpath(File.readlink(path)) if symlink?(path)
|
9
|
+
path
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
$: << File.expand_path(File.dirname(File.realpath(__FILE__)) + '/../lib')
|
14
|
+
require 'rubygems'
|
15
|
+
require 'gli'
|
16
|
+
require 'es_version'
|
17
|
+
require 'gooddata'
|
18
|
+
require 'pp'
|
19
|
+
require 'logger'
|
20
|
+
require 'es'
|
21
|
+
require 'date'
|
22
|
+
require 'chronic'
|
23
|
+
require 'fastercsv'
|
24
|
+
|
25
|
+
include GLI
|
26
|
+
|
27
|
+
PID = ENV['PID']
|
28
|
+
ES_NAME = ENV['ES_NAME']
|
29
|
+
LOGIN = ENV['LOGIN']
|
30
|
+
PASSWORD = ENV['PASSWORD']
|
31
|
+
|
32
|
+
program_desc 'ES generator - Should help you with working with Event Store'
|
33
|
+
version Es::VERSION
|
34
|
+
|
35
|
+
desc 'Turn on HTTP logger'
|
36
|
+
arg_name 'log'
|
37
|
+
switch [:l,:logger]
|
38
|
+
|
39
|
+
desc 'GD server'
|
40
|
+
arg_name 'server'
|
41
|
+
flag [:s,:server]
|
42
|
+
|
43
|
+
desc 'WEBDAV server'
|
44
|
+
arg_name 'webdav'
|
45
|
+
flag [:w,:webdav]
|
46
|
+
|
47
|
+
|
48
|
+
desc 'Creates ES'
|
49
|
+
command :create do |c|
|
50
|
+
c.action do |global_options,options,args|
|
51
|
+
Es::Commands::create({
|
52
|
+
:pid => PID,
|
53
|
+
:es_name => ES_NAME
|
54
|
+
})
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
desc 'Delete ES'
|
59
|
+
command :delete do |c|
|
60
|
+
c.action do |global_options,options,args|
|
61
|
+
Es::Commands::delete({
|
62
|
+
:pid => PID,
|
63
|
+
:es_name => ES_NAME
|
64
|
+
})
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
desc 'Show all types that are supported.'
|
70
|
+
command :types do |c|
|
71
|
+
c.action do |global_options,options,args|
|
72
|
+
Es::Commands::get_types.each {|t| puts t}
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
desc 'Init ES'
|
77
|
+
command :init do |c|
|
78
|
+
c.desc 'Execute only for one entity.'
|
79
|
+
c.default_value false
|
80
|
+
c.flag [:o, :only]
|
81
|
+
|
82
|
+
c.desc 'Init also IsDeleted and DeletedAt columns.'
|
83
|
+
c.default_value false
|
84
|
+
c.switch [:d, :deleted]
|
85
|
+
|
86
|
+
c.desc 'Verbose mode'
|
87
|
+
c.default_value false
|
88
|
+
c.switch [:v, :verbose]
|
89
|
+
|
90
|
+
c.desc 'Base files directory.'
|
91
|
+
c.default_value nil
|
92
|
+
c.flag [:b, :basedir]
|
93
|
+
|
94
|
+
c.action do |global_options,options,args|
|
95
|
+
options[:pid] = PID
|
96
|
+
options[:es_name] = ES_NAME
|
97
|
+
Es::Commands::init(options)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
desc 'Load data'
|
102
|
+
command :load do |c|
|
103
|
+
c.desc 'Execute only for one entity.'
|
104
|
+
c.default_value false
|
105
|
+
c.flag [:o, :only]
|
106
|
+
|
107
|
+
c.desc 'Print the task in the ugly oneliner mode for use in legacy tools. Does not run the actual extract.'
|
108
|
+
c.default_value false
|
109
|
+
c.switch [:j, :json]
|
110
|
+
|
111
|
+
c.desc 'Verbose mode'
|
112
|
+
c.default_value false
|
113
|
+
c.switch [:v, :verbose]
|
114
|
+
|
115
|
+
c.desc 'Base files directory. If specified it will ignore specific files and it will pick up all files in this directory with pattern load*.json'
|
116
|
+
c.default_value nil
|
117
|
+
c.flag [:b, :basedir]
|
118
|
+
|
119
|
+
c.action do |global_options,options,args|
|
120
|
+
options[:filenames] = args
|
121
|
+
options[:pattern] = "gen_load*.json"
|
122
|
+
options[:pid] = PID
|
123
|
+
options[:es_name] = ES_NAME
|
124
|
+
Es::Commands::load(options)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
desc 'Load Deleted Records'
|
129
|
+
command :load_deleted do |c|
|
130
|
+
|
131
|
+
c.desc 'Base files directory. If specified it will ignore specific files and it will pick up all files in this directory with pattern load*.json'
|
132
|
+
c.default_value nil
|
133
|
+
c.flag [:b, :basedir]
|
134
|
+
|
135
|
+
c.desc 'Compatibility mode. If set to true deleted records will be loaded old style with type isDeleted. Otherwise deleted records will be loaded with type attribute and DeletedAt field will be added.'
|
136
|
+
c.default_value false
|
137
|
+
c.switch [:c, :compatibility]
|
138
|
+
|
139
|
+
c.action do |global_options,options,args|
|
140
|
+
options[:filenames] = args
|
141
|
+
options[:pattern] = "gen_load*.json"
|
142
|
+
options[:pid] = PID
|
143
|
+
options[:es_name] = ES_NAME
|
144
|
+
Es::Commands::load_deleted(options)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
desc 'Extract'
|
149
|
+
command :extract do |c|
|
150
|
+
|
151
|
+
c.desc 'Execute only for one entity.'
|
152
|
+
c.default_value false
|
153
|
+
c.flag [:o, :only]
|
154
|
+
|
155
|
+
c.desc 'Verbose mode'
|
156
|
+
c.default_value false
|
157
|
+
c.switch [:v, :verbose]
|
158
|
+
|
159
|
+
c.desc 'Print the task in the ugly oneliner mode for use in legacy tools. Does not run the actual extract.'
|
160
|
+
c.default_value true
|
161
|
+
c.switch [:j, :json]
|
162
|
+
|
163
|
+
c.desc 'Run as usual but output the task definition in pretty print for debugging.'
|
164
|
+
c.default_value true
|
165
|
+
c.switch [:d, :debug]
|
166
|
+
|
167
|
+
c.desc 'Base files directory. If specified it will ignore specific files and it will pick up all files in this directory with pattern load*.json'
|
168
|
+
c.default_value nil
|
169
|
+
c.flag [:b, :basedir]
|
170
|
+
|
171
|
+
c.desc 'Extract files directory. If specified it will ignore specific files and it will pick up all files in this directory with pattern extract*.json'
|
172
|
+
c.default_value nil
|
173
|
+
c.flag [:e, :extractdir]
|
174
|
+
|
175
|
+
c.desc 'Business date'
|
176
|
+
c.default_value nil
|
177
|
+
c.flag [:n, :business_date]
|
178
|
+
|
179
|
+
c.action do |global_options,options,args|
|
180
|
+
options[:args] = args
|
181
|
+
options[:pid] = PID
|
182
|
+
options[:es_name] = ES_NAME
|
183
|
+
business_date = nil
|
184
|
+
begin
|
185
|
+
business_date = Date.strptime(options[:business_date], "%Y-%m-%d").to_time unless options[:business_date].nil?
|
186
|
+
rescue ArgumentError => e
|
187
|
+
begin
|
188
|
+
business_date = Time.at(Integer(options[:business_date]))
|
189
|
+
rescue ArgumentError => e
|
190
|
+
fail "Business date cannot be parsed from #{options[:business_date]} either as a date in format YYYY-MM-DD or an epoch timestamp"
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
options[:now] = business_date
|
195
|
+
|
196
|
+
Es::Commands::extract(options)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
desc 'Generate extract config template'
|
201
|
+
command :generate_extract do |c|
|
202
|
+
c.desc 'Base files directory. If specified it will ignore specific files and it will pick up all files in this directory with pattern load*.json'
|
203
|
+
c.default_value nil
|
204
|
+
c.flag [:b, :basedir]
|
205
|
+
|
206
|
+
c.action do |global_options,options,args|
|
207
|
+
Es::Commands::generate_extract(options)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
desc 'Generate base conifg template'
|
212
|
+
command :generate_base do |c|
|
213
|
+
|
214
|
+
c.desc 'Name of the entity. If inputdir is also specified all generated base files will have this entity set. If you want to set entity for each file according to the file name do not provide this parameter.'
|
215
|
+
c.default_value nil
|
216
|
+
c.flag [:e, :entity]
|
217
|
+
|
218
|
+
c.desc 'Input file.'
|
219
|
+
c.default_value nil
|
220
|
+
c.flag [:i, :input]
|
221
|
+
|
222
|
+
c.desc 'Output filename. If not provided it print to STDOUT.'
|
223
|
+
c.default_value nil
|
224
|
+
c.flag [:o, :output]
|
225
|
+
|
226
|
+
c.desc 'Input files directory. If specified it will ignore specific file and it will pick up all files in this directory with pattern *.csv'
|
227
|
+
c.default_value nil
|
228
|
+
c.flag [:s, :inputdir]
|
229
|
+
|
230
|
+
c.desc 'Base files directory. If this and inputdir are specified base JSONs will be generated into the directory.'
|
231
|
+
c.default_value nil
|
232
|
+
c.flag [:b, :basedir]
|
233
|
+
|
234
|
+
c.action do |global_options,options,args|
|
235
|
+
Es::Commands::generate_base(options)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
desc 'Truncate entity. Truncation means that you specify a time all events in that entit larger than the time will be thrown away.'
|
240
|
+
command :truncate do |c|
|
241
|
+
|
242
|
+
c.desc 'Name of the entity.'
|
243
|
+
c.default_value nil
|
244
|
+
c.flag [:e, :entity]
|
245
|
+
|
246
|
+
c.desc 'Timestamp in epoch to which the ES will be truncated.'
|
247
|
+
c.default_value nil
|
248
|
+
c.flag [:t, :timestamp]
|
249
|
+
|
250
|
+
c.desc 'Base files directory. If specified it will ignore specific files and it will pick up all files in this directory with pattern load*.json'
|
251
|
+
c.default_value nil
|
252
|
+
c.flag [:b, :basedir]
|
253
|
+
|
254
|
+
c.action do |global_options,options,args|
|
255
|
+
options[:load_filenames] = args
|
256
|
+
options[:pid] = PID
|
257
|
+
options[:es_name] = ES_NAME
|
258
|
+
options[:basedir_pattern] = "gen_load*.json"
|
259
|
+
Es::Commands::truncate(options)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
desc 'Initial load column'
|
264
|
+
command :load_column do |c|
|
265
|
+
|
266
|
+
c.desc 'Verbose mode'
|
267
|
+
c.default_value false
|
268
|
+
c.switch [:v, :verbose]
|
269
|
+
|
270
|
+
c.desc 'Run as usual but output the task definition in pretty print for debugging.'
|
271
|
+
c.default_value true
|
272
|
+
c.switch [:d, :debug]
|
273
|
+
|
274
|
+
c.desc 'Name of the column.'
|
275
|
+
c.default_value nil
|
276
|
+
c.flag [:n, :name]
|
277
|
+
|
278
|
+
c.desc 'Name of the entity.'
|
279
|
+
c.default_value nil
|
280
|
+
c.flag [:e, :entity]
|
281
|
+
|
282
|
+
c.desc 'Type of the column.'
|
283
|
+
c.default_value nil
|
284
|
+
c.flag [:t, :type]
|
285
|
+
|
286
|
+
c.desc 'Base config filename.'
|
287
|
+
c.default_value nil
|
288
|
+
c.flag [:b, :base]
|
289
|
+
|
290
|
+
c.desc 'Input data filename.'
|
291
|
+
c.default_value nil
|
292
|
+
c.flag [:i, :input]
|
293
|
+
|
294
|
+
c.desc 'Name of recordid column'
|
295
|
+
c.default_value nil
|
296
|
+
c.flag :rid
|
297
|
+
|
298
|
+
c.action do |global_options,options,args|
|
299
|
+
options[:pid] = PID
|
300
|
+
options[:es_name] = ES_NAME
|
301
|
+
Es::Commands::load_column(options)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
desc 'Shows more info about'
|
306
|
+
command :inspect do |c|
|
307
|
+
|
308
|
+
c.action do |global_options,options,args|
|
309
|
+
|
310
|
+
what = args.first
|
311
|
+
filename = args[1]
|
312
|
+
|
313
|
+
case what
|
314
|
+
when "load"
|
315
|
+
fail "Specify a file with base config" if filename.nil?
|
316
|
+
base_config_file = Es::Helpers.load_config(filename)
|
317
|
+
base = Es::Load.parse(base_config_file)
|
318
|
+
base.entities.each do |entity|
|
319
|
+
puts entity.to_table
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
|
326
|
+
pre do |global,command,options,args|
|
327
|
+
next true if command.nil?
|
328
|
+
# Pre logic here
|
329
|
+
# Return true to proceed; false to abourt and not call the
|
330
|
+
# chosen command
|
331
|
+
# Use skips_pre before a command to skip this block
|
332
|
+
# on that command only
|
333
|
+
fail "PID env variable should be specified" if PID.nil? || PID.empty?
|
334
|
+
fail "ES_NAME env variable should be specified" if ES_NAME.nil? || ES_NAME.empty?
|
335
|
+
fail "LOGIN env variable should be specified" if LOGIN.nil? || LOGIN.empty?
|
336
|
+
fail "PASSWORD env variable should be specified" if PASSWORD.nil? || PASSWORD.empty?
|
337
|
+
|
338
|
+
GoodData.logger = Logger.new(STDOUT) if global[:logger]
|
339
|
+
GD_SERVER = global[:server]
|
340
|
+
GD_WEBDAV = global[:webdav]
|
341
|
+
begin
|
342
|
+
GoodData.connect LOGIN, PASSWORD, GD_SERVER, {
|
343
|
+
:timeout => 60,
|
344
|
+
:webdav_server => GD_WEBDAV
|
345
|
+
}
|
346
|
+
rescue RestClient::BadRequest => e
|
347
|
+
puts "Login Failed"
|
348
|
+
exit 1
|
349
|
+
end
|
350
|
+
true
|
351
|
+
end
|
352
|
+
|
353
|
+
post do |global,command,options,args|
|
354
|
+
# Post logic here
|
355
|
+
# Use skips_post before a command to skip this
|
356
|
+
# block on that command only
|
357
|
+
end
|
358
|
+
|
359
|
+
on_error do |exception|
|
360
|
+
pp exception.backtrace
|
361
|
+
if exception.is_a?(SystemExit) && exception.status == 0
|
362
|
+
false
|
363
|
+
else
|
364
|
+
# pp exception.inspect
|
365
|
+
puts exception.message.color(:red)
|
366
|
+
false
|
367
|
+
end
|
368
|
+
|
369
|
+
# Error logic here
|
370
|
+
# return false to skip default error handling
|
371
|
+
# false
|
372
|
+
# true
|
373
|
+
end
|
374
|
+
|
375
|
+
exit GLI.run(ARGV)
|
data/es.rdoc
ADDED
data/lib/commands.rb
ADDED
@@ -0,0 +1,406 @@
|
|
1
|
+
module Es
|
2
|
+
module Commands
|
3
|
+
|
4
|
+
def self.create(options)
|
5
|
+
pid = options[:pid]
|
6
|
+
es_name = options[:es_name]
|
7
|
+
|
8
|
+
begin
|
9
|
+
GoodData.post "/gdc/projects/#{pid}/eventStore/stores", {:store => {:storeId => es_name}}
|
10
|
+
rescue RestClient::BadRequest
|
11
|
+
puts "Seems like eventstore with name #{es_name} already exists"
|
12
|
+
exit 1
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.delete(options)
|
17
|
+
pid = options[:pid]
|
18
|
+
es_name = options[:es_name]
|
19
|
+
GoodData.delete "/gdc/projects/#{pid}/eventStore/stores/#{es_name}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.get_types
|
23
|
+
Es::Field::FIELD_TYPES
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.init(options)
|
27
|
+
pid = options[:pid]
|
28
|
+
es_name = options[:es_name]
|
29
|
+
base_dir = options[:basedir]
|
30
|
+
deleted = options[:deleted]
|
31
|
+
only = options[:only]
|
32
|
+
|
33
|
+
max_timestamp = 2147483647
|
34
|
+
|
35
|
+
fail "Provide path to the loading configuration" if base_dir.nil?
|
36
|
+
base_filenames = Dir::glob("#{base_dir}/gen_load*.json")
|
37
|
+
|
38
|
+
base_entities = base_filenames.reduce([]) do |memo, filename|
|
39
|
+
fail "File #{filename} cannot be found" unless File.exist?(filename)
|
40
|
+
load_config = Es::Helpers.load_config(filename)
|
41
|
+
load = Es::Load.parse(load_config)
|
42
|
+
memo.concat(load.entities)
|
43
|
+
end
|
44
|
+
hyper_load = Es::Load.new(base_entities)
|
45
|
+
entity_names = hyper_load.entities.map {|e| e.name}.uniq
|
46
|
+
|
47
|
+
entity_names.each do |entity_name|
|
48
|
+
next if only && entity_name != only
|
49
|
+
entity = hyper_load.get_merged_entity_for(entity_name)
|
50
|
+
|
51
|
+
Tempfile.open(entity.name) do |tmp_file|
|
52
|
+
header_row = []
|
53
|
+
content_row = []
|
54
|
+
entity.fields.each do |field|
|
55
|
+
header_row << field.name
|
56
|
+
content_row << max_timestamp if field.is_timestamp?
|
57
|
+
content_row << 1 if field.is_recordid?
|
58
|
+
content_row << "" if !field.is_recordid? && !field.is_timestamp?
|
59
|
+
end
|
60
|
+
if deleted
|
61
|
+
header_row << "IsDeleted" << "DeletedAt"
|
62
|
+
content_row << "" << ""
|
63
|
+
entity.add_field(Es::Field.new('IsDeleted', 'attribute')) unless entity.has_field?('IsDeleted')
|
64
|
+
entity.add_field(Es::Field.new('DeletedAt', 'time')) unless entity.has_field?('DeletedAt')
|
65
|
+
end
|
66
|
+
tmp_file.puts(header_row.join(","))
|
67
|
+
tmp_file.puts(content_row.join(","))
|
68
|
+
tmp_file.flush
|
69
|
+
|
70
|
+
entity.file = tmp_file.path
|
71
|
+
# create temp file, link it to entity
|
72
|
+
web_dav_file = Es::Helpers.load_destination_dir(pid, entity) + '/' + Es::Helpers.destination_file(entity)
|
73
|
+
|
74
|
+
if options[:verbose]
|
75
|
+
puts "Entity #{entity.name}".bright
|
76
|
+
puts "Will load from #{entity.file} to #{web_dav_file}"
|
77
|
+
puts JSON::pretty_generate(entity.to_load_fragment(pid))
|
78
|
+
end
|
79
|
+
entity.load(pid, es_name)
|
80
|
+
puts "Done" if options[:verbose]
|
81
|
+
end
|
82
|
+
if options[:verbose]
|
83
|
+
puts "Truncating loaded dummy data for #{entity.name}. Using timestamp #{max_timestamp - 1}."
|
84
|
+
end
|
85
|
+
entity.truncate(pid, es_name, (max_timestamp - 1))
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.truncate(options)
|
90
|
+
pid = options[:pid]
|
91
|
+
es_name = options[:es_name]
|
92
|
+
entity_name = options[:entity]
|
93
|
+
timestamp = options[:timestamp]
|
94
|
+
filenames = options[:load_filenames]
|
95
|
+
basedir_pattern = options[:basedir_pattern] || "*.json"
|
96
|
+
logger = options[:logger]
|
97
|
+
|
98
|
+
base_dir = options[:basedir]
|
99
|
+
|
100
|
+
fail "You need to specify timestamp" if timestamp.nil?
|
101
|
+
if base_dir.nil?
|
102
|
+
# fail "You need to specify entity name" if entity_name.nil?
|
103
|
+
fail "You need to specify base filename" if filenames.empty?
|
104
|
+
else
|
105
|
+
# puts "would grab files like this #{"#{base_dir}/gen_load*.json"}"
|
106
|
+
filenames = Dir::glob("#{base_dir}/#{basedir_pattern}")
|
107
|
+
end
|
108
|
+
|
109
|
+
filenames.each do |base_filename|
|
110
|
+
|
111
|
+
base_config_file = Es::Helpers.load_config(base_filename)
|
112
|
+
base = Es::Load.parse(base_config_file)
|
113
|
+
|
114
|
+
base.entities.each do |entity|
|
115
|
+
next if !entity_name.nil? and entity_name != entity.name
|
116
|
+
logger.info "truncating entity \"#{entity.name}\"} at timestamp #{timestamp} that is #{Time.at(timestamp).to_s}" if logger
|
117
|
+
entity.truncate(pid, es_name, timestamp)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.load(options)
|
123
|
+
pid = options[:pid]
|
124
|
+
es_name = options[:es_name]
|
125
|
+
filenames = options[:filenames]
|
126
|
+
base_dir = options[:basedir]
|
127
|
+
pattern = options[:pattern] || "*.json"
|
128
|
+
only = options[:only]
|
129
|
+
logger = options[:logger]
|
130
|
+
|
131
|
+
if base_dir.nil?
|
132
|
+
fail "Provide path to the loading configuration as a first argument" if filenames.empty?
|
133
|
+
else
|
134
|
+
# puts "would grab files like this #{"#{base_dir}/gen_json*.json"}"
|
135
|
+
filenames = Dir::glob("#{base_dir}/#{pattern}")
|
136
|
+
end
|
137
|
+
|
138
|
+
# for each config file
|
139
|
+
filenames.each do |filename|
|
140
|
+
fail "File #{filename} cannot be found" unless File.exist?(filename)
|
141
|
+
load_config_file = Es::Helpers.load_config(filename)
|
142
|
+
load = Es::Load.parse(load_config_file)
|
143
|
+
|
144
|
+
load.entities.each do |entity|
|
145
|
+
next if only && entity.name != only
|
146
|
+
next unless Es::Helpers.has_more_lines?(entity.file)
|
147
|
+
|
148
|
+
logger.info "Loading entity \"#{entity.name}\", fields #{entity.fields.map {|f| f.name}.join(', ')}" if logger
|
149
|
+
logger.info "Using json => #{entity.to_load_fragment(pid).to_json}" if logger
|
150
|
+
|
151
|
+
web_dav_file = Es::Helpers.load_destination_dir(pid, entity) + '/' + Es::Helpers.destination_file(entity)
|
152
|
+
if options[:verbose]
|
153
|
+
puts "Entity #{entity.name}".bright
|
154
|
+
puts "Configuration from #{filename}"
|
155
|
+
puts "Will load from #{entity.file} to #{web_dav_file}"
|
156
|
+
puts JSON::pretty_generate(entity.to_load_fragment(pid))
|
157
|
+
end
|
158
|
+
if options[:j]
|
159
|
+
puts "Entity #{entity.name}".bright unless options[:verbose]
|
160
|
+
puts "load the file #{entity.file} to destination #{web_dav_file} and run the specified as the task"
|
161
|
+
puts "======= Load JSON start"
|
162
|
+
puts entity.to_load_fragment(pid).to_json.color(:blue)
|
163
|
+
puts "======= Load JSON end"
|
164
|
+
puts
|
165
|
+
else
|
166
|
+
entity.load(pid, es_name)
|
167
|
+
puts "Done" if options[:verbose]
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def self.load_deleted(options)
|
174
|
+
filenames = options[:filenames]
|
175
|
+
base_dir = options[:basedir]
|
176
|
+
pattern = options[:pattern] || "*.json"
|
177
|
+
pid = options[:pid]
|
178
|
+
es_name = options[:es_name]
|
179
|
+
logger = options[:logger]
|
180
|
+
|
181
|
+
if base_dir.nil?
|
182
|
+
fail "Provide path to the loading configuration as a first argument" if filenames.empty?
|
183
|
+
else
|
184
|
+
# puts "would grab files like this #{"#{base_dir}/gen_load*.json"}"
|
185
|
+
filenames = Dir::glob("#{base_dir}/#{pattern}")
|
186
|
+
end
|
187
|
+
|
188
|
+
compatibility_mode = options[:compatibility] || false
|
189
|
+
deleted_type = compatibility_mode ? "isDeleted" : "attribute"
|
190
|
+
|
191
|
+
filenames.each do |load_config_file|
|
192
|
+
load_config = Es::Helpers.load_config(load_config_file)
|
193
|
+
load = Es::Load.parse(load_config)
|
194
|
+
|
195
|
+
load.entities.each do |entity|
|
196
|
+
source_dir = File.dirname(entity.file)
|
197
|
+
deleted_filename = Es::Helpers.destination_file(entity, :deleted => true)
|
198
|
+
deleted_source = "#{source_dir}/#{deleted_filename}"
|
199
|
+
next unless File.exist? deleted_source
|
200
|
+
has_more_lines = Es::Helpers.has_more_lines?(deleted_source)
|
201
|
+
logger.info "Deleted records for entity #{entity.name} is not loaded since the file #{deleted_source} is empty." if logger && !has_more_lines
|
202
|
+
next unless has_more_lines
|
203
|
+
|
204
|
+
logger.info "Loading deleted records for entity #{entity.name} in with compatibility mode set to #{compatibility_mode}." if logger
|
205
|
+
|
206
|
+
e = Es::Entity.create_deleted_entity(entity.name, {:compatibility_mode => compatibility_mode, :file => deleted_source})
|
207
|
+
e.load(pid, es_name)
|
208
|
+
|
209
|
+
if !compatibility_mode
|
210
|
+
deleted_with_time = "#{source_dir}/#{deleted_filename}".gsub(/\.csv$/, '_del.csv')
|
211
|
+
FasterCSV.open(deleted_with_time, 'w') do |csv|
|
212
|
+
csv << ['Id', 'Timestamp', 'DeletedAt']
|
213
|
+
FasterCSV.foreach("#{source_dir}/#{deleted_filename}", :headers => true, :return_headers => false) do |row|
|
214
|
+
csv << row.values_at('Id', 'Timestamp', 'Timestamp')
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
e1 = Es::Entity.new(entity.name, {
|
219
|
+
:file => deleted_with_time,
|
220
|
+
:fields => [
|
221
|
+
Es::Field.new('Id', 'recordid'),
|
222
|
+
Es::Field.new('Timestamp', 'timestamp'),
|
223
|
+
Es::Field.new('DeletedAt', 'time')
|
224
|
+
]
|
225
|
+
})
|
226
|
+
e1.load(pid, es_name)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
232
|
+
|
233
|
+
def self.extract(options)
|
234
|
+
base_dir = options[:basedir]
|
235
|
+
extract_dir = options[:extractdir]
|
236
|
+
pid = options[:pid]
|
237
|
+
es_name = options[:es_name]
|
238
|
+
now = options[:now]
|
239
|
+
args = options[:args]
|
240
|
+
logger = options[:logger]
|
241
|
+
|
242
|
+
if base_dir.nil? && extract_dir.nil?
|
243
|
+
fail "Provide path to the loading configuration as a first argument" if args.first.nil?
|
244
|
+
load_config_files = [args.first]
|
245
|
+
fail "Provide path to the extract configuration as a second argument" if args[1].nil?
|
246
|
+
extract_config_files = [args[1]]
|
247
|
+
else
|
248
|
+
load_config_files = Dir::glob("#{base_dir}/gen_load*.json")
|
249
|
+
extract_config_files = Dir::glob("#{extract_dir}/gen_extract*.json")
|
250
|
+
end
|
251
|
+
|
252
|
+
# build one giant load config
|
253
|
+
load_entities = load_config_files.reduce([]) do |memo, filename|
|
254
|
+
fail "File #{filename} cannot be found" unless File.exist?(filename)
|
255
|
+
load_config = Es::Helpers.load_config(filename)
|
256
|
+
load = Es::Load.parse(load_config)
|
257
|
+
memo.concat(load.entities)
|
258
|
+
end
|
259
|
+
hyper_load = Es::Load.new(load_entities)
|
260
|
+
|
261
|
+
extract_config_files.each do |extract_config_file|
|
262
|
+
fail "File #{extract_config_file} cannot be found" unless File.exist?(extract_config_file)
|
263
|
+
extract_config = Es::Helpers.load_config(extract_config_file)
|
264
|
+
extract = Es::Extract.parse(extract_config, hyper_load, :now => now)
|
265
|
+
|
266
|
+
extract.entities.each do |entity|
|
267
|
+
next if options[:only] && entity.name != options[:only]
|
268
|
+
|
269
|
+
logger.info "Extracting entity \"#{entity.name}\", fields #{entity.fields.map {|f| f.name}.join(', ')}" if logger
|
270
|
+
logger.info "Using json => #{entity.to_extract_fragment(pid, :pretty => false).to_json}" if logger
|
271
|
+
|
272
|
+
if options[:verbose] || options[:json] || options[:debug] then
|
273
|
+
puts "Entity #{entity.name.bright}"
|
274
|
+
puts "Config from #{load_config_files.join(', ')} and #{extract_config_file}"
|
275
|
+
end
|
276
|
+
|
277
|
+
puts JSON.pretty_generate(entity.to_extract_fragment(pid)) if options[:debug]
|
278
|
+
|
279
|
+
if options[:json]
|
280
|
+
# puts "load the file #{entity.file} to destination #{web_dav_file} and run the specified as the task"
|
281
|
+
puts "======= Extract JSON start"
|
282
|
+
puts entity.to_extract_fragment(pid, :pretty => false).to_json.color(:blue)
|
283
|
+
puts "======= Extract JSON end"
|
284
|
+
puts
|
285
|
+
else
|
286
|
+
entity.extract(pid, es_name)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
|
293
|
+
def self.generate_base(options)
|
294
|
+
entity = options[:entity]
|
295
|
+
input_filename = options[:input]
|
296
|
+
input_dir = options[:inputdir]
|
297
|
+
output_filename = options[:output]
|
298
|
+
base_dir = options[:basedir]
|
299
|
+
|
300
|
+
fail "You need to specify input file name or input dir" if input_filename.nil? && input_dir.nil?
|
301
|
+
|
302
|
+
if base_dir.nil?
|
303
|
+
input_filenames = [input_filename]
|
304
|
+
else
|
305
|
+
input_filenames = Dir::glob("#{input_dir}/*.csv")
|
306
|
+
end
|
307
|
+
|
308
|
+
input_filenames.each do |input_filename|
|
309
|
+
|
310
|
+
headers = nil
|
311
|
+
FasterCSV.foreach(input_filename, :headers => true, :return_headers => true) do |row|
|
312
|
+
if row.header_row?
|
313
|
+
headers = row.fields
|
314
|
+
break
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
entity_name = entity || File.basename(input_filename, ".csv")
|
319
|
+
load = Es::Load.new([
|
320
|
+
Es::Entity.new(entity_name, {
|
321
|
+
:file => input_filename,
|
322
|
+
:fields => headers.map do |field_name|
|
323
|
+
Es::Field.new(field_name, "none")
|
324
|
+
end
|
325
|
+
})
|
326
|
+
])
|
327
|
+
|
328
|
+
config = JSON.pretty_generate(load.to_config)
|
329
|
+
|
330
|
+
if input_dir && base_dir
|
331
|
+
File.open(base_dir+"/gen_load_"+entity_name+".json", 'w') do |f|
|
332
|
+
f.write config
|
333
|
+
end
|
334
|
+
elsif input_filename && output_filename
|
335
|
+
File.open(output_filename, 'w') do |f|
|
336
|
+
f.write config
|
337
|
+
end
|
338
|
+
else
|
339
|
+
puts config
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
def self.generate_extract(options)
|
345
|
+
base_dir = options[:basedir]
|
346
|
+
fail "You need to specify base dir" if base_dir.nil?
|
347
|
+
|
348
|
+
base_filenames = Dir::glob("#{base_dir}/gen_load_*.json")
|
349
|
+
# build one giant load config
|
350
|
+
base_entities = base_filenames.reduce([]) do |memo, filename|
|
351
|
+
fail "File #{filename} cannot be found" unless File.exist?(filename)
|
352
|
+
load_config = Es::Helpers.load_config(filename)
|
353
|
+
load = Es::Load.parse(load_config)
|
354
|
+
memo.concat(load.entities)
|
355
|
+
end
|
356
|
+
hyper_load = Es::Load.new(base_entities)
|
357
|
+
entity_names = hyper_load.entities.map {|e| e.name}.uniq
|
358
|
+
|
359
|
+
entity_names.each do |entity_name|
|
360
|
+
entity = hyper_load.get_merged_entity_for(entity_name)
|
361
|
+
|
362
|
+
File.open(base_dir+"/gen_extract_"+entity.name+".json", 'w') do |f|
|
363
|
+
f.write JSON.pretty_generate(entity.to_extract_config)
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
def self.load_column(options)
|
369
|
+
file = options[:input]
|
370
|
+
name = options[:name]
|
371
|
+
type = options[:type]
|
372
|
+
entity = options[:entity]
|
373
|
+
base_filename = options[:base]
|
374
|
+
pid = options[:pid]
|
375
|
+
es_name = options[:es_name]
|
376
|
+
rid_name = options[:rid] || 'Id'
|
377
|
+
|
378
|
+
fail "You need to specify column name" if name.nil?
|
379
|
+
fail "You need to specify column type" if type.nil?
|
380
|
+
fail "You need to specify entity name" if entity.nil?
|
381
|
+
fail "You need to specify input file name" if file.nil?
|
382
|
+
|
383
|
+
base_config_file = Es::Helpers.load_config(base_filename)
|
384
|
+
base = Es::Load.parse(base_config_file)
|
385
|
+
|
386
|
+
load = Es::Load.new([
|
387
|
+
Es::Entity.new(entity, {
|
388
|
+
:file => file,
|
389
|
+
:fields => [
|
390
|
+
Es::Field.new('Timestamp', 'timestamp'),
|
391
|
+
Es::Field.new(rid_name, 'recordid'),
|
392
|
+
Es::Field.new(name, type)
|
393
|
+
]
|
394
|
+
})
|
395
|
+
])
|
396
|
+
|
397
|
+
base.get_entity(entity).add_field(Es::Field.new(name, type))
|
398
|
+
puts "Added field #{name}" if options[:verbose]
|
399
|
+
base.to_config_file(base_filename)
|
400
|
+
|
401
|
+
load.entities.first.load(pid, es_name)
|
402
|
+
|
403
|
+
end
|
404
|
+
|
405
|
+
end
|
406
|
+
end
|