bricolage 5.8.7
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 +7 -0
- data/README.md +4 -0
- data/bin/bricolage +6 -0
- data/bin/bricolage-jobnet +6 -0
- data/jobclass/create.rb +21 -0
- data/jobclass/exec.rb +17 -0
- data/jobclass/insert-delta.rb +31 -0
- data/jobclass/insert.rb +33 -0
- data/jobclass/load.rb +39 -0
- data/jobclass/my-export.rb +40 -0
- data/jobclass/my-migrate.rb +103 -0
- data/jobclass/noop.rb +13 -0
- data/jobclass/rebuild-drop.rb +37 -0
- data/jobclass/rebuild-rename.rb +49 -0
- data/jobclass/s3-put.rb +19 -0
- data/jobclass/sql.rb +29 -0
- data/jobclass/td-delete.rb +20 -0
- data/jobclass/td-export.rb +30 -0
- data/jobclass/unload.rb +30 -0
- data/jobclass/wait-file.rb +48 -0
- data/lib/bricolage/application.rb +260 -0
- data/lib/bricolage/commandutils.rb +52 -0
- data/lib/bricolage/configloader.rb +126 -0
- data/lib/bricolage/context.rb +108 -0
- data/lib/bricolage/datasource.rb +144 -0
- data/lib/bricolage/eventhandlers.rb +47 -0
- data/lib/bricolage/exception.rb +47 -0
- data/lib/bricolage/filedatasource.rb +42 -0
- data/lib/bricolage/filesystem.rb +165 -0
- data/lib/bricolage/genericdatasource.rb +37 -0
- data/lib/bricolage/job.rb +212 -0
- data/lib/bricolage/jobclass.rb +98 -0
- data/lib/bricolage/jobfile.rb +100 -0
- data/lib/bricolage/jobflow.rb +389 -0
- data/lib/bricolage/jobnetrunner.rb +264 -0
- data/lib/bricolage/jobresult.rb +74 -0
- data/lib/bricolage/logger.rb +52 -0
- data/lib/bricolage/mysqldatasource.rb +223 -0
- data/lib/bricolage/parameters.rb +653 -0
- data/lib/bricolage/postgresconnection.rb +78 -0
- data/lib/bricolage/psqldatasource.rb +449 -0
- data/lib/bricolage/resource.rb +68 -0
- data/lib/bricolage/rubyjobclass.rb +42 -0
- data/lib/bricolage/s3datasource.rb +144 -0
- data/lib/bricolage/script.rb +120 -0
- data/lib/bricolage/sqlstatement.rb +351 -0
- data/lib/bricolage/taskqueue.rb +156 -0
- data/lib/bricolage/tddatasource.rb +116 -0
- data/lib/bricolage/variables.rb +208 -0
- data/lib/bricolage/version.rb +4 -0
- data/lib/bricolage.rb +8 -0
- data/libexec/sqldump +9 -0
- data/libexec/sqldump.Darwin +0 -0
- data/libexec/sqldump.Linux +0 -0
- data/test/all.rb +3 -0
- data/test/home/config/development/database.yml +57 -0
- data/test/home/config/development/password.yml +2 -0
- data/test/home/subsys/separated.job +1 -0
- data/test/home/subsys/separated.sql +1 -0
- data/test/home/subsys/unified.jobnet +1 -0
- data/test/home/subsys/unified.sql.job +5 -0
- data/test/test_filesystem.rb +19 -0
- data/test/test_parameters.rb +401 -0
- data/test/test_variables.rb +114 -0
- metadata +192 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
require 'bricolage/context'
|
|
2
|
+
require 'bricolage/job'
|
|
3
|
+
require 'bricolage/jobclass'
|
|
4
|
+
require 'bricolage/jobresult'
|
|
5
|
+
require 'bricolage/variables'
|
|
6
|
+
require 'bricolage/datasource'
|
|
7
|
+
require 'bricolage/eventhandlers'
|
|
8
|
+
require 'bricolage/logger'
|
|
9
|
+
require 'bricolage/exception'
|
|
10
|
+
require 'bricolage/version'
|
|
11
|
+
require 'pathname'
|
|
12
|
+
require 'optparse'
|
|
13
|
+
|
|
14
|
+
module Bricolage
|
|
15
|
+
|
|
16
|
+
class Application
|
|
17
|
+
def Application.main
|
|
18
|
+
new.main
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize
|
|
22
|
+
Signal.trap('PIPE', 'IGNORE')
|
|
23
|
+
@hooks = Bricolage
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def main
|
|
27
|
+
opts = GlobalOptions.new(self)
|
|
28
|
+
@hooks.run_before_option_parsing_hooks(opts)
|
|
29
|
+
opts.parse ARGV
|
|
30
|
+
@ctx = Context.for_application(opts.home, opts.job_file, environment: opts.environment, global_variables: opts.global_variables)
|
|
31
|
+
if opts.list_global_variables?
|
|
32
|
+
list_variables @ctx.global_variables.resolve
|
|
33
|
+
exit 0
|
|
34
|
+
end
|
|
35
|
+
job = load_job(@ctx, opts)
|
|
36
|
+
process_job_options job, opts
|
|
37
|
+
job.compile
|
|
38
|
+
if opts.list_declarations?
|
|
39
|
+
list_declarations job.declarations
|
|
40
|
+
exit 0
|
|
41
|
+
end
|
|
42
|
+
if opts.list_variables?
|
|
43
|
+
list_variables job.variables
|
|
44
|
+
exit 0
|
|
45
|
+
end
|
|
46
|
+
if opts.dry_run?
|
|
47
|
+
puts job.script_source
|
|
48
|
+
exit 0
|
|
49
|
+
end
|
|
50
|
+
if opts.explain?
|
|
51
|
+
job.explain
|
|
52
|
+
exit 0
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
@hooks.run_before_all_jobs_hooks(BeforeAllJobsEvent.new(job.id, [job]))
|
|
56
|
+
@hooks.run_before_job_hooks(BeforeJobEvent.new(job))
|
|
57
|
+
result = job.execute
|
|
58
|
+
@hooks.run_after_job_hooks(AfterJobEvent.new(result))
|
|
59
|
+
@hooks.run_after_all_jobs_hooks(AfterAllJobsEvent.new(result.success?, [job]))
|
|
60
|
+
exit result.status
|
|
61
|
+
rescue OptionError => ex
|
|
62
|
+
raise if $DEBUG
|
|
63
|
+
usage_exit ex.message, opts.help
|
|
64
|
+
rescue ApplicationError => ex
|
|
65
|
+
raise if $DEBUG
|
|
66
|
+
error_exit ex.message
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def load_job(ctx, opts)
|
|
70
|
+
if opts.file_mode?
|
|
71
|
+
Job.load_file(opts.job_file, ctx)
|
|
72
|
+
else
|
|
73
|
+
usage_exit "no job class given", opts.help if ARGV.empty?
|
|
74
|
+
job_class_id = ARGV.shift
|
|
75
|
+
Job.instantiate(nil, job_class_id, ctx)
|
|
76
|
+
end
|
|
77
|
+
rescue ParameterError => ex
|
|
78
|
+
raise if $DEBUG
|
|
79
|
+
usage_exit ex.message, opts.help
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def process_job_options(job, opts)
|
|
83
|
+
parser = OptionParser.new
|
|
84
|
+
parser.banner = "Usage: #{program_name} #{job.class_id} [job_class_options]"
|
|
85
|
+
job.parsing_options {|job_opt_defs|
|
|
86
|
+
job_opt_defs.define_options parser
|
|
87
|
+
parser.on_tail('--help', 'Shows this message and quit.') {
|
|
88
|
+
puts parser.help
|
|
89
|
+
exit 0
|
|
90
|
+
}
|
|
91
|
+
parser.on_tail('--version', 'Shows program version and quit.') {
|
|
92
|
+
puts "#{APPLICATION_NAME} version #{VERSION}"
|
|
93
|
+
exit 0
|
|
94
|
+
}
|
|
95
|
+
parser.parse!
|
|
96
|
+
}
|
|
97
|
+
unless ARGV.empty?
|
|
98
|
+
msg = opts.file_mode? ? "--job-file and job class argument is exclusive" : "bad argument: #{ARGV.first}"
|
|
99
|
+
usage_exit msg, parser.help
|
|
100
|
+
end
|
|
101
|
+
rescue OptionError => ex
|
|
102
|
+
raise if $DEBUG
|
|
103
|
+
usage_exit ex.message, parser.help
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def list_variables(vars)
|
|
107
|
+
vars.each_variable do |var|
|
|
108
|
+
puts "#{var.name}=#{var.value.inspect}"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def list_declarations(decls)
|
|
113
|
+
decls.each do |decl|
|
|
114
|
+
if decl.have_default_value?
|
|
115
|
+
puts "#{decl.name}\t= #{decl.default_value.inspect}"
|
|
116
|
+
else
|
|
117
|
+
puts decl.name
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def usage_exit(msg, usage)
|
|
123
|
+
print_error msg
|
|
124
|
+
$stderr.puts usage
|
|
125
|
+
exit 1
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def error_exit(msg)
|
|
129
|
+
print_error msg
|
|
130
|
+
exit 1
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def print_error(msg)
|
|
134
|
+
$stderr.puts "#{program_name}: error: #{msg}"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def program_name
|
|
138
|
+
File.basename($PROGRAM_NAME, '.*')
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
class GlobalOptions
|
|
143
|
+
def initialize(app)
|
|
144
|
+
@app = app
|
|
145
|
+
@job_file = nil
|
|
146
|
+
@environment = nil
|
|
147
|
+
@home = nil
|
|
148
|
+
@global_variables = Variables.new
|
|
149
|
+
@dry_run = false
|
|
150
|
+
@explain = false
|
|
151
|
+
@list_global_variables = false
|
|
152
|
+
@list_variables = false
|
|
153
|
+
@list_declarations = false
|
|
154
|
+
@parser = OptionParser.new
|
|
155
|
+
define_options @parser
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
attr_reader :parser
|
|
159
|
+
|
|
160
|
+
def help
|
|
161
|
+
@parser.help
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def define_options(parser)
|
|
165
|
+
parser.banner = <<-EndBanner
|
|
166
|
+
Synopsis:
|
|
167
|
+
#{@app.program_name} [global_options] JOB_CLASS [job_options]
|
|
168
|
+
#{@app.program_name} [global_options] --job=JOB_FILE -- [job_options]
|
|
169
|
+
Global Options:
|
|
170
|
+
EndBanner
|
|
171
|
+
parser.on('-f', '--job=JOB_FILE', 'Give job parameters via job file (YAML).') {|path|
|
|
172
|
+
@job_file = path
|
|
173
|
+
}
|
|
174
|
+
parser.on('-e', '--environment=NAME', "Sets execution environment [default: #{Context::DEFAULT_ENV}]") {|env|
|
|
175
|
+
@environment = env
|
|
176
|
+
}
|
|
177
|
+
parser.on('-C', '--home=PATH', 'Sets application home directory.') {|path|
|
|
178
|
+
@home = Pathname(path)
|
|
179
|
+
}
|
|
180
|
+
parser.on('-n', '--dry-run', 'Shows job script without executing it.') {
|
|
181
|
+
@dry_run = true
|
|
182
|
+
}
|
|
183
|
+
parser.on('-E', '--explain', 'Applies EXPLAIN to the SQL.') {
|
|
184
|
+
@explain = true
|
|
185
|
+
}
|
|
186
|
+
parser.on('--list-job-class', 'Lists job class name and (internal) class path.') {
|
|
187
|
+
JobClass.list.each do |name|
|
|
188
|
+
puts name
|
|
189
|
+
end
|
|
190
|
+
exit 0
|
|
191
|
+
}
|
|
192
|
+
parser.on('--list-global-variables', 'Lists global variables.') {
|
|
193
|
+
@list_global_variables = true
|
|
194
|
+
}
|
|
195
|
+
parser.on('--list-variables', 'Lists all variables.') {
|
|
196
|
+
@list_variables = true
|
|
197
|
+
}
|
|
198
|
+
parser.on('--list-declarations', 'Lists script variable declarations.') {
|
|
199
|
+
@list_declarations = true
|
|
200
|
+
}
|
|
201
|
+
parser.on('-r', '--require=FEATURE', 'Requires ruby library.') {|feature|
|
|
202
|
+
require feature
|
|
203
|
+
}
|
|
204
|
+
parser.on('-v', '--variable=NAME=VALUE', 'Set global variable (is different from job-level -v !!).') {|name_value|
|
|
205
|
+
name, value = name_value.split('=', 2)
|
|
206
|
+
@global_variables[name] = value
|
|
207
|
+
}
|
|
208
|
+
parser.on('--help', 'Shows this message and quit.') {
|
|
209
|
+
puts parser.help
|
|
210
|
+
exit 0
|
|
211
|
+
}
|
|
212
|
+
parser.on('--version', 'Shows program version and quit.') {
|
|
213
|
+
puts "#{APPLICATION_NAME} version #{VERSION}"
|
|
214
|
+
exit 0
|
|
215
|
+
}
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def on(*args, &block)
|
|
219
|
+
@parser.on(*args, &block)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def parse(argv)
|
|
223
|
+
@parser.order! argv
|
|
224
|
+
@rest_args = argv.dup
|
|
225
|
+
rescue OptionParser::ParseError => ex
|
|
226
|
+
raise OptionError, ex.message
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
attr_reader :environment
|
|
230
|
+
attr_reader :home
|
|
231
|
+
attr_reader :global_variables
|
|
232
|
+
|
|
233
|
+
attr_reader :job_file
|
|
234
|
+
|
|
235
|
+
def file_mode?
|
|
236
|
+
!!@job_file
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def dry_run?
|
|
240
|
+
@dry_run
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def explain?
|
|
244
|
+
@explain
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def list_global_variables?
|
|
248
|
+
@list_global_variables
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def list_variables?
|
|
252
|
+
@list_variables
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def list_declarations?
|
|
256
|
+
@list_declarations
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
require 'tmpdir'
|
|
3
|
+
|
|
4
|
+
module Bricolage
|
|
5
|
+
|
|
6
|
+
module CommandUtils
|
|
7
|
+
def command(*args, env: nil)
|
|
8
|
+
logger.info "command: #{args.join(' ')}"
|
|
9
|
+
sargs = args.map {|a| a.to_s }
|
|
10
|
+
sargs.unshift env if env
|
|
11
|
+
system(*sargs)
|
|
12
|
+
st = $?
|
|
13
|
+
logger.info "status: #{st.exitstatus} (#{st})"
|
|
14
|
+
st
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def make_tmpfile(content, tmpdir: Dir.tmpdir)
|
|
18
|
+
path = new_tmpfile_path(tmpdir)
|
|
19
|
+
File.open(path, 'w') {|f|
|
|
20
|
+
f.write content
|
|
21
|
+
}
|
|
22
|
+
yield path
|
|
23
|
+
ensure
|
|
24
|
+
FileUtils.rm_f path
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def new_tmpfile_path(tmpdir = Dir.tmpdir)
|
|
28
|
+
"#{tmpdir}/#{Time.now.to_i}_#{$$}_#{'%x' % Thread.current.object_id}_#{rand(2**16)}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# CLUDGE: FIXME: bricolage-jobnet command writes stderr to the file, we can find error messages from there.
|
|
32
|
+
# Using a temporary file or Ruby SQL driver is **MUCH** better.
|
|
33
|
+
def retrieve_last_match_from_stderr(re, nth = 0)
|
|
34
|
+
return unless $stderr.stat.file?
|
|
35
|
+
$stderr.flush
|
|
36
|
+
f = $stderr.dup
|
|
37
|
+
matched = nil
|
|
38
|
+
begin
|
|
39
|
+
f.seek(0)
|
|
40
|
+
f.each do |line|
|
|
41
|
+
m = line.slice(re, nth)
|
|
42
|
+
matched = m if m
|
|
43
|
+
end
|
|
44
|
+
ensure
|
|
45
|
+
f.close
|
|
46
|
+
end
|
|
47
|
+
matched = matched.to_s.strip
|
|
48
|
+
matched.empty? ? nil : matched
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
require 'bricolage/sqlstatement'
|
|
2
|
+
require 'bricolage/resource'
|
|
3
|
+
require 'bricolage/exception'
|
|
4
|
+
require 'pathname'
|
|
5
|
+
require 'yaml'
|
|
6
|
+
require 'erb'
|
|
7
|
+
require 'date'
|
|
8
|
+
|
|
9
|
+
module Bricolage
|
|
10
|
+
|
|
11
|
+
class ConfigLoader
|
|
12
|
+
def ConfigLoader.load_eruby_yaml(path)
|
|
13
|
+
new(nil).load_eruby_yaml(path)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(app_home)
|
|
17
|
+
@app_home = app_home
|
|
18
|
+
@base_dir = Pathname('.')
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def load_eruby(path)
|
|
22
|
+
eruby(read_file(path), path)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def load_eruby_yaml(path)
|
|
26
|
+
parse_yaml(load_eruby(path), path)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def load_yaml(path)
|
|
30
|
+
parse_yaml(read_file(path), path)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def parse_yaml(text, path)
|
|
34
|
+
YAML.load(text)
|
|
35
|
+
rescue => err
|
|
36
|
+
raise ParameterError, "#{path}: config file syntax error: #{err.message}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def eruby(text, path)
|
|
40
|
+
erb = ERB.new(text, nil, '%-')
|
|
41
|
+
erb.filename = path.to_s
|
|
42
|
+
push_base_dir(path) {
|
|
43
|
+
erb.result(binding())
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def eval_file(path)
|
|
48
|
+
push_base_dir(path) {
|
|
49
|
+
instance_eval(File.read(path), path.to_s, 1)
|
|
50
|
+
}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def read_file(path)
|
|
54
|
+
File.read(path)
|
|
55
|
+
rescue SystemCallError => err
|
|
56
|
+
raise ParameterError, "could not read file: #{err.message}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def app_home
|
|
62
|
+
@app_home or raise ParameterError, "app_home is not given in this file"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def base_dir
|
|
66
|
+
@base_dir
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def push_base_dir(path)
|
|
70
|
+
saved, @base_dir = @base_dir, Pathname(path).parent
|
|
71
|
+
begin
|
|
72
|
+
yield
|
|
73
|
+
ensure
|
|
74
|
+
@base_dir = saved
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
module EmbeddedCodeAPI
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def user_home
|
|
83
|
+
Pathname(ENV['HOME'])
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def user_home_relative_path(rel)
|
|
87
|
+
user_home + rel
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def app_home_relative_path(rel)
|
|
91
|
+
app_home + rel
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def relative_path(rel)
|
|
95
|
+
base_dir + rel
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def read_file_if_exist(path)
|
|
99
|
+
return nil unless File.exist?(path)
|
|
100
|
+
File.read(path)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def date(str)
|
|
104
|
+
Date.parse(str)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def ymd(date)
|
|
108
|
+
date.strftime('%Y-%m-%d')
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def attribute_tables(attr)
|
|
112
|
+
all_tables.select {|table| table.attributes.include?(attr) }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def all_tables
|
|
116
|
+
Dir.glob("#{app_home}/*/*.ct").map {|path|
|
|
117
|
+
SQLStatement.new(FileResource.new(path))
|
|
118
|
+
}
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
class ConfigLoader
|
|
123
|
+
include EmbeddedCodeAPI
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
require 'bricolage/filesystem'
|
|
2
|
+
require 'bricolage/datasource'
|
|
3
|
+
require 'bricolage/configloader'
|
|
4
|
+
require 'bricolage/logger'
|
|
5
|
+
require 'forwardable'
|
|
6
|
+
|
|
7
|
+
module Bricolage
|
|
8
|
+
|
|
9
|
+
class Context
|
|
10
|
+
DEFAULT_ENV = 'development'
|
|
11
|
+
|
|
12
|
+
def Context.environment(opt_env)
|
|
13
|
+
opt_env || ENV['BRICOLAGE_ENV'] || DEFAULT_ENV
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def Context.for_application(home_path, job_path = nil, environment: nil, global_variables: nil, logger: nil)
|
|
17
|
+
env = environment(environment)
|
|
18
|
+
fs = FileSystem.for_option_pathes(home_path, job_path, env)
|
|
19
|
+
load(fs, env, global_variables: global_variables, logger: logger)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def Context.load(fs, env, global_variables: nil, data_sources: nil, logger: nil)
|
|
23
|
+
new(fs, env, global_variables: global_variables, logger: logger).tap {|ctx|
|
|
24
|
+
ctx.load_configurations
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
private_class_method :load
|
|
28
|
+
|
|
29
|
+
def initialize(fs, env, global_variables: nil, data_sources: nil, logger: nil)
|
|
30
|
+
@logger = logger || Logger.default
|
|
31
|
+
@filesystem = fs
|
|
32
|
+
@environment = env
|
|
33
|
+
@opt_global_variables = global_variables || Variables.new
|
|
34
|
+
@data_sources = data_sources
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def load_configurations
|
|
38
|
+
@filesystem.config_pathes('prelude.rb').each do |path|
|
|
39
|
+
EmbeddedCodeAPI.module_eval(File.read(path)) if path.exist?
|
|
40
|
+
end
|
|
41
|
+
@data_sources = DataSourceFactory.load(self, @logger)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
attr_reader :environment
|
|
45
|
+
attr_reader :logger
|
|
46
|
+
|
|
47
|
+
def get_data_source(type, name)
|
|
48
|
+
@data_sources.get(type, name)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def subsystem(id)
|
|
52
|
+
self.class.new(@filesystem.subsystem(id), @environment,
|
|
53
|
+
global_variables: @opt_global_variables,
|
|
54
|
+
data_sources: @data_sources,
|
|
55
|
+
logger: @logger)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
extend Forwardable
|
|
59
|
+
def_delegators '@filesystem',
|
|
60
|
+
:scoped?,
|
|
61
|
+
:home_path,
|
|
62
|
+
:root_relative_path,
|
|
63
|
+
:config_path,
|
|
64
|
+
:config_pathes,
|
|
65
|
+
:job_dir,
|
|
66
|
+
:job_file,
|
|
67
|
+
:parameter_file,
|
|
68
|
+
:parameter_file_loader
|
|
69
|
+
|
|
70
|
+
#
|
|
71
|
+
# Variables
|
|
72
|
+
#
|
|
73
|
+
|
|
74
|
+
def global_variables
|
|
75
|
+
Variables.union(
|
|
76
|
+
builtin_variables,
|
|
77
|
+
load_global_variables,
|
|
78
|
+
@opt_global_variables
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def builtin_variables
|
|
83
|
+
Variables.define {|vars|
|
|
84
|
+
vars['bricolage_env'] = @environment
|
|
85
|
+
vars['bricolage_home'] = home_path.to_s
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def load_global_variables
|
|
90
|
+
vars_list = config_pathes(GLOBAL_VARIABLE_FILE).map {|path|
|
|
91
|
+
path.exist? ? load_variables(path) : nil
|
|
92
|
+
}
|
|
93
|
+
Variables.union(*vars_list.compact)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
GLOBAL_VARIABLE_FILE = 'variable.yml'
|
|
97
|
+
|
|
98
|
+
def load_variables(path)
|
|
99
|
+
Variables.define {|vars|
|
|
100
|
+
parameter_file_loader.load_eruby_yaml(path).each do |name, value|
|
|
101
|
+
vars[name] = value
|
|
102
|
+
end
|
|
103
|
+
}
|
|
104
|
+
end
|
|
105
|
+
private :load_variables
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
end
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
require 'bricolage/script'
|
|
2
|
+
require 'bricolage/configloader'
|
|
3
|
+
require 'bricolage/exception'
|
|
4
|
+
|
|
5
|
+
module Bricolage
|
|
6
|
+
|
|
7
|
+
class DataSourceFactory
|
|
8
|
+
def DataSourceFactory.load(context, logger)
|
|
9
|
+
loader = Loader.new(context, logger)
|
|
10
|
+
loader.load_passwords
|
|
11
|
+
loader.load
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
DEFAULT_CONFIG_FILE_NAME = 'database.yml'
|
|
15
|
+
DEFAULT_PASSWORD_FILE_NAME = 'password.yml'
|
|
16
|
+
|
|
17
|
+
class Loader < ConfigLoader
|
|
18
|
+
def initialize(context, logger)
|
|
19
|
+
super context.home_path
|
|
20
|
+
@context = context
|
|
21
|
+
@logger = logger
|
|
22
|
+
@passwords = nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def load_passwords(basename = DEFAULT_PASSWORD_FILE_NAME)
|
|
26
|
+
@context.config_pathes(basename).each do |path|
|
|
27
|
+
if path.exist?
|
|
28
|
+
@passwords = load_yaml(path)
|
|
29
|
+
break
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def load(basename = DEFAULT_CONFIG_FILE_NAME)
|
|
35
|
+
database_yml = @context.config_pathes(basename).detect {|path| path.exist? }
|
|
36
|
+
raise ParameterError, "database.yml does not exist" unless database_yml
|
|
37
|
+
@config_dir = database_yml.parent
|
|
38
|
+
DataSourceFactory.new(load_eruby_yaml(database_yml), @context, @logger)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def password(name)
|
|
42
|
+
(@passwords || {})[name] or raise ParameterError, "no such password entry: #{name}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def initialize(configs, context, logger)
|
|
47
|
+
@configs = configs
|
|
48
|
+
@context = context
|
|
49
|
+
@logger = logger
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
BUILTIN_TYPES = %w(generic file)
|
|
53
|
+
|
|
54
|
+
# For job classes
|
|
55
|
+
def get(kind, name)
|
|
56
|
+
if BUILTIN_TYPES.include?(kind)
|
|
57
|
+
return DataSource.new_for_type(kind, kind, {}, @context, @logger)
|
|
58
|
+
end
|
|
59
|
+
entry_name = name || kind
|
|
60
|
+
conf = config(entry_name)
|
|
61
|
+
type = conf.delete(:type)
|
|
62
|
+
DataSource.new_for_type(type, entry_name, conf, @context, @logger)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Ruby API
|
|
66
|
+
def [](name)
|
|
67
|
+
if BUILTIN_TYPES.include?(name)
|
|
68
|
+
return DataSource.new_for_type(name, name, {}, @context, @logger)
|
|
69
|
+
end
|
|
70
|
+
conf = config(name)
|
|
71
|
+
type = conf.delete(:type)
|
|
72
|
+
DataSource.new_for_type(type, name, conf, @context, @logger)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def config(key)
|
|
78
|
+
ent = @configs[key] or raise ParameterError, "no such data source entry: #{key}"
|
|
79
|
+
canonicalize(ent)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def canonicalize(config)
|
|
83
|
+
h = {}
|
|
84
|
+
config.each do |k, v|
|
|
85
|
+
h[k.intern] = v
|
|
86
|
+
end
|
|
87
|
+
h
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
class DataSource
|
|
92
|
+
def DataSource.new_for_type(type, name, config, context, logger)
|
|
93
|
+
ds = get_class(type).new(**config)
|
|
94
|
+
ds.__send__ :initialize_base, name, context, logger
|
|
95
|
+
ds
|
|
96
|
+
rescue ArgumentError => err
|
|
97
|
+
# FIXME: do not rely on error message
|
|
98
|
+
ent = err.message.slice(/unknown keyword: (\S+)/, 1) or raise
|
|
99
|
+
raise ParameterError, "unknown config entry in database.yml: #{name}.#{ent}"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
CLASSES = {}
|
|
103
|
+
|
|
104
|
+
class << self
|
|
105
|
+
private
|
|
106
|
+
|
|
107
|
+
def declare_type(type)
|
|
108
|
+
CLASSES[type.to_s] = self
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def DataSource.get_class(type)
|
|
113
|
+
unless CLASSES[type.to_s]
|
|
114
|
+
begin
|
|
115
|
+
require "bricolage/#{type}datasource"
|
|
116
|
+
rescue LoadError
|
|
117
|
+
raise ParameterError, "no such SQL client type: #{type}"
|
|
118
|
+
end
|
|
119
|
+
raise FatalError, "DataSource class does not exist: #{type}" unless CLASSES[type.to_s]
|
|
120
|
+
end
|
|
121
|
+
CLASSES[type.to_s]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def initialize_base(name, context, logger)
|
|
125
|
+
@name = name
|
|
126
|
+
@context = context
|
|
127
|
+
@logger = logger
|
|
128
|
+
end
|
|
129
|
+
private :initialize_base
|
|
130
|
+
|
|
131
|
+
attr_reader :name
|
|
132
|
+
attr_reader :context
|
|
133
|
+
attr_reader :logger
|
|
134
|
+
|
|
135
|
+
def open
|
|
136
|
+
yield nil
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def open_for_batch(&block)
|
|
140
|
+
open(&block)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
end
|