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,116 @@
|
|
|
1
|
+
require 'bricolage/datasource'
|
|
2
|
+
require 'bricolage/commandutils'
|
|
3
|
+
require 'stringio'
|
|
4
|
+
require 'date'
|
|
5
|
+
|
|
6
|
+
module Bricolage
|
|
7
|
+
|
|
8
|
+
class TDDataSource < DataSource
|
|
9
|
+
declare_type 'td'
|
|
10
|
+
|
|
11
|
+
include CommandUtils
|
|
12
|
+
|
|
13
|
+
def initialize(database: nil, username: nil, apikey: nil, td: 'td', priority: -2)
|
|
14
|
+
@database = database
|
|
15
|
+
@apikey = apikey
|
|
16
|
+
@td = td
|
|
17
|
+
@priority = priority
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def new_task
|
|
21
|
+
TDTask.new(self)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def query_command_args(*args)
|
|
25
|
+
[@td, "--apikey=#{@apikey}", "query", "--database=#{@database}", "--wait"] + args
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def delete_command_args(*args)
|
|
29
|
+
[@td, "--apikey=#{@apikey}", "table:partial_delete", @database, *args, "--wait"]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def exec(*args)
|
|
33
|
+
JobResult.for_process_status(command(*args))
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class TDTask < DataSourceTask
|
|
38
|
+
def export(stmt, path: nil, **opts)
|
|
39
|
+
add TDTask::Export.new(stmt, path: path, **opts)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class Export < Action
|
|
43
|
+
include CommandUtils
|
|
44
|
+
|
|
45
|
+
def initialize(stmt, path: nil, format: nil, gzip: false, override: false)
|
|
46
|
+
@statement = stmt
|
|
47
|
+
@path = path
|
|
48
|
+
@format = format
|
|
49
|
+
@gzip = gzip
|
|
50
|
+
@override = override
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def_delegator '@statement', :bind
|
|
54
|
+
|
|
55
|
+
def source
|
|
56
|
+
buf = StringIO.new
|
|
57
|
+
buf.puts command_args(new_tmpfile_path).join(' ') + " <<EndTDSQL"
|
|
58
|
+
buf.puts @statement.source
|
|
59
|
+
buf.puts 'EndTDSQL'
|
|
60
|
+
buf.string
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
GZIP_COMMAND = 'gzip' # FIXME: parameterize
|
|
64
|
+
|
|
65
|
+
def run
|
|
66
|
+
if File.exist?(@path) and not @override
|
|
67
|
+
raise JobFailure, "target file exists: #{@path.inspect}"
|
|
68
|
+
end
|
|
69
|
+
puts @statement.source
|
|
70
|
+
td_result = make_tmpfile(@statement.source) {|query_path|
|
|
71
|
+
ds.exec(*command_args(query_path))
|
|
72
|
+
}
|
|
73
|
+
if @gzip
|
|
74
|
+
gzip_result = ds.command(GZIP_COMMAND, export_path)
|
|
75
|
+
raise JobFailure, "gzip failed" unless gzip_result.success?
|
|
76
|
+
end
|
|
77
|
+
td_result
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def command_args(query_path)
|
|
81
|
+
ds.query_command_args("--query=#{query_path}", "--output=#{export_path}", "--format=#{@format}")
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def export_path
|
|
85
|
+
@gzip ? @path.sub(/\.gz\z/, '') : @path
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def delete(table_name, **opts)
|
|
90
|
+
add TDTask::Delete.new(table_name, **opts)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
class Delete < Action
|
|
94
|
+
def initialize(table_name, from:, to:)
|
|
95
|
+
@table = table_name
|
|
96
|
+
@from = from.to_time.to_i
|
|
97
|
+
@to = to.to_time.to_i
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def run
|
|
101
|
+
td_result = ds.exec(source)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def command_args
|
|
105
|
+
ds.delete_command_args(@table, "--from #{@from}", "--to #{@to}")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def source
|
|
109
|
+
buf = StringIO.new
|
|
110
|
+
buf.puts command_args.join(' ')
|
|
111
|
+
buf.string
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
end
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
require 'bricolage/exception'
|
|
2
|
+
|
|
3
|
+
module Bricolage
|
|
4
|
+
|
|
5
|
+
class Variables
|
|
6
|
+
def Variables.union(*vars_list)
|
|
7
|
+
new.tap {|result|
|
|
8
|
+
vars_list.each do |vars|
|
|
9
|
+
result.update vars
|
|
10
|
+
end
|
|
11
|
+
}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def Variables.define
|
|
15
|
+
new.tap {|vars|
|
|
16
|
+
yield vars
|
|
17
|
+
}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def initialize
|
|
21
|
+
@vars = {}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def inspect
|
|
25
|
+
"\#<#{self.class} #{@vars.inspect}>"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def resolved?
|
|
29
|
+
false
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def each_variable(&block)
|
|
33
|
+
@vars.each_value(&block)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def [](name)
|
|
37
|
+
var = @vars[name] or raise ParameterError, "no such variable: #{name}"
|
|
38
|
+
var.value
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def []=(name, value)
|
|
42
|
+
add Variable.new(name, value)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def keys
|
|
46
|
+
@vars.keys
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def add(var)
|
|
50
|
+
@vars[var.name] = var
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def update(vars)
|
|
54
|
+
vars.each_variable do |var|
|
|
55
|
+
add var
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# resolve self recursively
|
|
60
|
+
def resolve
|
|
61
|
+
ResolvedVariables.define {|resolved|
|
|
62
|
+
@vars.each_value do |var|
|
|
63
|
+
resolved.add do_expand_variable(var, resolved, {})
|
|
64
|
+
end
|
|
65
|
+
}
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def do_expand_variable(var, resolved, seen)
|
|
69
|
+
if seen[var.name]
|
|
70
|
+
cycle = (seen.keys + [var.name]).join(', ')
|
|
71
|
+
raise ParameterError, "recursive variable reference: #{var.name} (#{cycle})"
|
|
72
|
+
end
|
|
73
|
+
seen[var.name] = true
|
|
74
|
+
if var.resolved?
|
|
75
|
+
var
|
|
76
|
+
else
|
|
77
|
+
value = Variable.expand_string(var.value.to_s) {|name|
|
|
78
|
+
if resolved.bound?(name)
|
|
79
|
+
resolved[name]
|
|
80
|
+
elsif var2 = @vars[name]
|
|
81
|
+
res_var2 = do_expand_variable(var2, resolved, seen)
|
|
82
|
+
resolved.add res_var2
|
|
83
|
+
res_var2.value
|
|
84
|
+
else
|
|
85
|
+
raise ParameterError, "undefined variable in parameter #{var.name}: $#{name}"
|
|
86
|
+
end
|
|
87
|
+
}
|
|
88
|
+
ResolvedVariable.new(var.name, value)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
private :do_expand_variable
|
|
92
|
+
|
|
93
|
+
def resolve_with(resolved)
|
|
94
|
+
raise "[BUG] unresolved variables given" unless resolved.resolved?
|
|
95
|
+
ResolvedVariables.define {|result|
|
|
96
|
+
each_variable do |var|
|
|
97
|
+
if var.resolved?
|
|
98
|
+
result.add var
|
|
99
|
+
else
|
|
100
|
+
val = resolved.expand(var.value.to_s)
|
|
101
|
+
result.add ResolvedVariable.new(var.name, val)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
}
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
class ResolvedVariables
|
|
109
|
+
def ResolvedVariables.define
|
|
110
|
+
new.tap {|vars|
|
|
111
|
+
yield vars
|
|
112
|
+
vars.freeze
|
|
113
|
+
}
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def initialize
|
|
117
|
+
@vars = {}
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def inspect
|
|
121
|
+
"\#<#{self.class} #{@vars.inspect}>"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def resolved?
|
|
125
|
+
true
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def freeze
|
|
129
|
+
@vars.freeze
|
|
130
|
+
super
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def add(var)
|
|
134
|
+
raise "[BUG] unresolved variable: #{var.name}" unless var.resolved?
|
|
135
|
+
@vars[var.name] = var
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def resolve_update(unresolved)
|
|
139
|
+
raise "[BUG] already resolved variables given" if unresolved.resolved?
|
|
140
|
+
unresolved.resolve_with(self).each_variable do |var|
|
|
141
|
+
add var
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def bound?(name)
|
|
146
|
+
@vars.key?(name.to_s)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def [](name)
|
|
150
|
+
var = @vars[name.to_s] or raise ParameterError, "undefined parameter: #{name}"
|
|
151
|
+
var.value
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def keys
|
|
155
|
+
@vars.keys
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def each_variable(&block)
|
|
159
|
+
@vars.each_value(&block)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def expand(str)
|
|
163
|
+
Variable.expand_string(str) {|name| self[name] }
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def bind_declarations(decls)
|
|
167
|
+
decls.each do |decl|
|
|
168
|
+
unless bound?(decl.name)
|
|
169
|
+
raise ParameterError, "script parameter not given: #{decl.name}"
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
class Variable
|
|
176
|
+
# generic variable extractor
|
|
177
|
+
def Variable.expand_string(str)
|
|
178
|
+
str.gsub(/\$(\w+)|\$\{(\w+)\}/) { yield ($1 || $2) }
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def Variable.list(str)
|
|
182
|
+
str.scan(/\$(\w+)|\$\{(\w+)\}/).flatten.compact.uniq
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def initialize(name, value)
|
|
186
|
+
@name = name
|
|
187
|
+
@value = value
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
attr_reader :name
|
|
191
|
+
attr_reader :value
|
|
192
|
+
|
|
193
|
+
def resolved?
|
|
194
|
+
false
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def inspect
|
|
198
|
+
"\#<#{self.class} #{@name}=#{@value.inspect}>"
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
class ResolvedVariable < Variable
|
|
203
|
+
def resolved?
|
|
204
|
+
true
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
end
|
data/lib/bricolage.rb
ADDED
data/libexec/sqldump
ADDED
|
Binary file
|
|
Binary file
|
data/test/all.rb
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
sql: &redshift_parameters
|
|
2
|
+
type: psql
|
|
3
|
+
host: <%= ENV['RSPROXYHOST'] ? 'localhost' : (ENV['RSHOST'] || 'cookpad-dwh-201.cbzwjkf60uat.ap-northeast-1.redshift.amazonaws.com') %>
|
|
4
|
+
port: <%= ENV['RSPROXYPORT'] || ENV['RSPORT'] || 5439 %>
|
|
5
|
+
database: production
|
|
6
|
+
username: tabemirudev
|
|
7
|
+
pgpass: <%= user_home_relative_path '.pgpass' %>
|
|
8
|
+
encoding: utf8
|
|
9
|
+
|
|
10
|
+
sql_dev:
|
|
11
|
+
<<: *redshift_parameters
|
|
12
|
+
|
|
13
|
+
sql_prod:
|
|
14
|
+
<<: *redshift_parameters
|
|
15
|
+
username: tabemiru_batch
|
|
16
|
+
|
|
17
|
+
sql_app:
|
|
18
|
+
type: psql
|
|
19
|
+
host: localhost #cookpad-dwh-101.cbzwjkf60uat.ap-northeast-1.redshift.amazonaws.com
|
|
20
|
+
port: 5445
|
|
21
|
+
database: production
|
|
22
|
+
username: tabemiru_batch
|
|
23
|
+
pgpass: <%= user_home_relative_path '.pgpass' %>
|
|
24
|
+
encoding: utf8
|
|
25
|
+
|
|
26
|
+
sql_app2:
|
|
27
|
+
type: psql
|
|
28
|
+
host: localhost
|
|
29
|
+
port: 5446
|
|
30
|
+
database: production
|
|
31
|
+
username: tabemiru_batch
|
|
32
|
+
pgpass: <%= user_home_relative_path '.pgpass' %>
|
|
33
|
+
encoding: utf8
|
|
34
|
+
|
|
35
|
+
td:
|
|
36
|
+
type: td
|
|
37
|
+
database: logs
|
|
38
|
+
apikey: <%= password 'td_tabemiru' %>
|
|
39
|
+
|
|
40
|
+
td_search_log:
|
|
41
|
+
type: td
|
|
42
|
+
database: search_log
|
|
43
|
+
apikey: <%= password 'td_tabemiru' %>
|
|
44
|
+
|
|
45
|
+
s3:
|
|
46
|
+
type: s3
|
|
47
|
+
bucket: tabemiru-data.ap-northeast-1
|
|
48
|
+
prefix: "/dev"
|
|
49
|
+
s3cfg: <%= user_home_relative_path '.s3cfg' %>
|
|
50
|
+
|
|
51
|
+
mysql:
|
|
52
|
+
type: mysql
|
|
53
|
+
host: db-main-slave-free-001
|
|
54
|
+
database: main
|
|
55
|
+
username: work_readonly
|
|
56
|
+
password: <%= password 'mysql_local_work_readonly' %>
|
|
57
|
+
encoding: utf8
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class: sql
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
select 1;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
unified
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require 'test/unit'
|
|
2
|
+
require 'bricolage/filesystem'
|
|
3
|
+
require 'pathname'
|
|
4
|
+
require 'pp'
|
|
5
|
+
|
|
6
|
+
module Bricolage
|
|
7
|
+
class TestFileSystem < Test::Unit::TestCase
|
|
8
|
+
fs = FileSystem.new("#{__dir__}/home", "test")
|
|
9
|
+
subfs = fs.subsystem('subsys')
|
|
10
|
+
|
|
11
|
+
test "FileSystem.job_file" do
|
|
12
|
+
path = subfs.job_file('unified')
|
|
13
|
+
assert_instance_of Pathname, path
|
|
14
|
+
assert_equal subfs.relative('unified.sql.job'), path
|
|
15
|
+
|
|
16
|
+
assert_equal subfs.relative('separated.job'), subfs.job_file('separated')
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|