td 0.14.1 → 0.15.0
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 +4 -4
- data/.travis.yml +3 -0
- data/ChangeLog +5 -0
- data/appveyor.yml +1 -0
- data/contrib/completion/_td +4 -0
- data/lib/td/command/account.rb +1 -1
- data/lib/td/command/common.rb +3 -0
- data/lib/td/command/connector.rb +44 -5
- data/lib/td/command/list.rb +7 -1
- data/lib/td/command/runner.rb +5 -3
- data/lib/td/command/workflow.rb +446 -0
- data/lib/td/helpers.rb +14 -0
- data/lib/td/updater.rb +2 -1
- data/lib/td/version.rb +1 -1
- data/spec/td/command/connector_spec.rb +122 -0
- data/spec/td/command/workflow_spec.rb +314 -0
- data/spec/td/helpers_spec.rb +55 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 61447ffe419ff8f382b433975388f420a4b7d013
|
4
|
+
data.tar.gz: fedca79cdca59a6a91eb4968ca3d5508e3bf65b8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0d85fb2e56cb54143505b472a18ff26a9b532db3ddf0a7033d7f8610626f564c9ec90a9dfece2cff4dee5447945d82cf481a061fc6eb88a43826205d7fc0c01e
|
7
|
+
data.tar.gz: ef7d72a9d251c546098ac39b2a3aa455dc933dfe224286ed4569b49f443f054c3acdb8ed1672738902b51731092354de11a1d8300f1690c3081cb89823cd50fc
|
data/.travis.yml
CHANGED
data/ChangeLog
CHANGED
data/appveyor.yml
CHANGED
data/contrib/completion/_td
CHANGED
@@ -81,6 +81,10 @@ subcommands=(
|
|
81
81
|
"table\:tail":"Get recently imported logs"
|
82
82
|
"table\:partial_delete":"Delete logs from the table within the specified time range"
|
83
83
|
|
84
|
+
"wf":"Run a workflow command"
|
85
|
+
"workflow":"Run a workflow command"
|
86
|
+
"workflow:reset":"Reset the workflow module"
|
87
|
+
|
84
88
|
# TODO: Add ACL related commands
|
85
89
|
)
|
86
90
|
|
data/lib/td/command/account.rb
CHANGED
@@ -41,7 +41,7 @@ module Command
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
$stdout.puts "Enter your Treasure Data credentials."
|
44
|
+
$stdout.puts "Enter your Treasure Data credentials. For Google SSO user, please see https://docs.treasuredata.com/articles/command-line#google-sso-users"
|
45
45
|
unless user_name
|
46
46
|
begin
|
47
47
|
$stdout.print "Email: "
|
data/lib/td/command/common.rb
CHANGED
data/lib/td/command/connector.rb
CHANGED
@@ -212,12 +212,47 @@ module Command
|
|
212
212
|
end
|
213
213
|
|
214
214
|
def connector_update(op)
|
215
|
-
|
215
|
+
settings = {}
|
216
216
|
|
217
|
-
|
217
|
+
op.on('-n', '--newname NAME', 'change the schedule\'s name', String) {|n|
|
218
|
+
settings['name'] = n
|
219
|
+
}
|
220
|
+
op.on('-d', '--database DB_NAME', 'change the database', String) {|s|
|
221
|
+
settings['database'] = s
|
222
|
+
}
|
223
|
+
op.on('-t', '--table TABLE_NAME', 'change the table', String) {|s|
|
224
|
+
settings['table'] = s
|
225
|
+
}
|
226
|
+
op.on('-s', '--schedule [CRON]', 'change the schedule or leave blank to remove the schedule', String) {|s|
|
227
|
+
settings['cron'] = s || ''
|
228
|
+
}
|
229
|
+
op.on('-z', '--timezone TZ', "name of the timezone.",
|
230
|
+
" Only extended timezones like 'Asia/Tokyo', 'America/Los_Angeles' are supported,",
|
231
|
+
" (no 'PST', 'PDT', etc...).",
|
232
|
+
" When a timezone is specified, the cron schedule is referred to that timezone.",
|
233
|
+
" Otherwise, the cron schedule is referred to the UTC timezone.",
|
234
|
+
" E.g. cron schedule '0 12 * * *' will execute daily at 5 AM without timezone option",
|
235
|
+
" and at 12PM with the -t / --timezone 'America/Los_Angeles' timezone option", String) {|s|
|
236
|
+
settings['timezone'] = s
|
237
|
+
}
|
238
|
+
op.on('-D', '--delay SECONDS', 'change the delay time of the schedule', Integer) {|i|
|
239
|
+
settings['delay'] = i
|
240
|
+
}
|
241
|
+
op.on('-T', '--time-column COLUMN_NAME', 'change the name of the time column', String) {|s|
|
242
|
+
settings['time_column'] = s
|
243
|
+
}
|
244
|
+
op.on('-c', '--config CONFIG_FILE', 'update the connector configuration', String) {|s|
|
245
|
+
settings['config'] = s
|
246
|
+
}
|
247
|
+
op.on('--config-diff CONFIG_DIFF_FILE', "update the connector config_diff", String) { |s| settings['config_diff'] = s }
|
218
248
|
|
249
|
+
name, config_file = op.cmd_parse
|
250
|
+
settings['config'] = config_file if config_file
|
251
|
+
op.cmd_usage 'nothing to update' if settings.empty?
|
252
|
+
settings['config'] = prepare_bulkload_job_config(settings['config']) if settings.key?('config')
|
253
|
+
settings['config_diff'] = prepare_bulkload_job_config(settings['config_diff']) if settings.key?('config_diff')
|
219
254
|
client = get_client()
|
220
|
-
session = client.bulk_load_update(name,
|
255
|
+
session = client.bulk_load_update(name, settings)
|
221
256
|
dump_connector_session(session)
|
222
257
|
end
|
223
258
|
|
@@ -312,6 +347,11 @@ private
|
|
312
347
|
|
313
348
|
|
314
349
|
def prepare_bulkload_job_config(config_file)
|
350
|
+
config = prepare_bulkload_job_config_diff(config_file)
|
351
|
+
TreasureData::ConnectorConfigNormalizer.new(config).normalized_config
|
352
|
+
end
|
353
|
+
|
354
|
+
def prepare_bulkload_job_config_diff(config_file)
|
315
355
|
unless File.exist?(config_file)
|
316
356
|
raise ParameterConfigurationError, "configuration file: #{config_file} not found"
|
317
357
|
end
|
@@ -326,8 +366,7 @@ private
|
|
326
366
|
rescue => e
|
327
367
|
raise ParameterConfigurationError, "configuration file: #{config_file} #{e.message}"
|
328
368
|
end
|
329
|
-
|
330
|
-
TreasureData::ConnectorConfigNormalizer.new(config).normalized_config
|
369
|
+
config
|
331
370
|
end
|
332
371
|
|
333
372
|
def dump_connector_session(session)
|
data/lib/td/command/list.rb
CHANGED
@@ -352,11 +352,15 @@ module List
|
|
352
352
|
add_list 'connector:list', %w[], 'Show list of connector sessions', ['connector:list']
|
353
353
|
add_list 'connector:create', %w[name cron database table config], 'Create new connector session', ['connector:create connector1 "0 * * * *" connector_database connector_table td-bulkload.yml']
|
354
354
|
add_list 'connector:show', %w[name], 'Show connector session', ['connector:show connector1']
|
355
|
-
add_list 'connector:update', %w[name config], 'Modify connector session', ['connector:update connector1 td-bulkload.yml']
|
355
|
+
add_list 'connector:update', %w[name config?], 'Modify connector session', ['connector:update connector1 -c td-bulkload.yml -s \'@daily\' ...']
|
356
356
|
add_list 'connector:delete', %w[name], 'Delete connector session', ['connector:delete connector1']
|
357
357
|
add_list 'connector:history', %w[name], 'Show job history of connector session', ['connector:history connector1']
|
358
358
|
add_list 'connector:run', %w[name time?], 'Run connector with session for the specified time option', ['connector:run connector1 "2016-01-01 00:00:00"']
|
359
359
|
|
360
|
+
add_list 'workflow', %w[], 'Run a workflow command'
|
361
|
+
add_list 'workflow:reset', %w[], 'Reset the workflow module'
|
362
|
+
add_list 'workflow:version', %w[], 'Show workflow module version'
|
363
|
+
|
360
364
|
# aliases
|
361
365
|
add_alias 'db', 'db:show'
|
362
366
|
add_alias 'dbs', 'db:list'
|
@@ -417,6 +421,8 @@ module List
|
|
417
421
|
|
418
422
|
add_alias 'connector', 'connector:guess'
|
419
423
|
|
424
|
+
add_alias 'wf', 'workflow'
|
425
|
+
|
420
426
|
# backward compatibility
|
421
427
|
add_alias 'show-databases', 'db:list'
|
422
428
|
add_alias 'show-dbs', 'db:list'
|
data/lib/td/command/runner.rb
CHANGED
@@ -47,6 +47,7 @@ Basic commands:
|
|
47
47
|
sched # create/delete/list schedules that run a query periodically
|
48
48
|
schema # create/delete/modify schemas of tables
|
49
49
|
connector # manage connectors
|
50
|
+
workflow # manage workflows
|
50
51
|
|
51
52
|
Additional commands:
|
52
53
|
|
@@ -162,12 +163,13 @@ EOF
|
|
162
163
|
return 1
|
163
164
|
end
|
164
165
|
|
166
|
+
status = nil
|
165
167
|
begin
|
166
168
|
# test the connectivity with the API endpoint
|
167
169
|
if cmd_req_connectivity && Config.cl_endpoint
|
168
170
|
Command.test_api_endpoint(Config.endpoint)
|
169
171
|
end
|
170
|
-
method.call(argv)
|
172
|
+
status = method.call(argv)
|
171
173
|
rescue ConfigError
|
172
174
|
$stderr.puts "TreasureData account is not configured yet."
|
173
175
|
$stderr.puts "Run '#{$prog} account' first."
|
@@ -185,7 +187,7 @@ EOF
|
|
185
187
|
# => NotFoundError
|
186
188
|
# => AuthError
|
187
189
|
if ![ParameterConfigurationError, BulkImportExecutionError, UpdateError, ImportError,
|
188
|
-
APIError, ForbiddenError, NotFoundError, AuthError, AlreadyExistsError].include?(e.class) ||
|
190
|
+
APIError, ForbiddenError, NotFoundError, AuthError, AlreadyExistsError, WorkflowError].include?(e.class) ||
|
189
191
|
!ENV['TD_TOOLBELT_DEBUG'].nil? || $verbose
|
190
192
|
show_backtrace "Error #{$!.class}: backtrace:", $!.backtrace
|
191
193
|
end
|
@@ -211,7 +213,7 @@ EOS
|
|
211
213
|
end
|
212
214
|
return 1
|
213
215
|
end
|
214
|
-
return 0
|
216
|
+
return (status.is_a? Integer) ? status : 0
|
215
217
|
end
|
216
218
|
|
217
219
|
private
|
@@ -0,0 +1,446 @@
|
|
1
|
+
require 'td/helpers'
|
2
|
+
require 'td/updater'
|
3
|
+
require 'open3'
|
4
|
+
require 'pathname'
|
5
|
+
require 'time'
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
module TreasureData
|
9
|
+
module Command
|
10
|
+
include TreasureData::Helpers
|
11
|
+
|
12
|
+
# The workflow entrypoint command. Invokes the digdag cli, passing on any command line arguments.
|
13
|
+
def workflow(op, capture_output=false, check_prereqs=true)
|
14
|
+
if Config.apikey.nil?
|
15
|
+
raise ConfigError
|
16
|
+
end
|
17
|
+
check_digdag_cli if check_prereqs
|
18
|
+
cmd = [
|
19
|
+
java_cmd,
|
20
|
+
'-Dio.digdag.cli.programName=td workflow',
|
21
|
+
'-XX:+TieredCompilation', '-XX:TieredStopAtLevel=1', '-Xverify:none'
|
22
|
+
]
|
23
|
+
|
24
|
+
FileUtils.mkdir_p digdag_tmp_dir
|
25
|
+
Dir.mktmpdir(nil, digdag_tmp_dir) { |wd|
|
26
|
+
env = {}
|
27
|
+
digdag_config_path = File.join(wd, 'config')
|
28
|
+
FileUtils.touch(digdag_config_path)
|
29
|
+
if Config.cl_apikey
|
30
|
+
# If the user passes the apikey on the command line we cannot use the digdag td.conf plugin.
|
31
|
+
# Instead, create a digdag configuration file with the endpoint and the specified apikey.
|
32
|
+
env['TD_CONFIG_PATH'] = nil
|
33
|
+
apikey = TreasureData::Config.apikey
|
34
|
+
File.write(digdag_config_path, [
|
35
|
+
'client.http.endpoint = https://api-workflow.treasuredata.com',
|
36
|
+
"client.http.headers.authorization = TD1 #{apikey}",
|
37
|
+
"secrets.td.apikey = #{apikey}"
|
38
|
+
].join($/) + $/)
|
39
|
+
cmd << '-Dio.digdag.standards.td.secrets.enabled=false'
|
40
|
+
else
|
41
|
+
# Use the digdag td.conf plugin to configure wf api and apikey.
|
42
|
+
env['TREASURE_DATA_CONFIG_PATH'] = Config.path
|
43
|
+
cmd << '-Dio.digdag.standards.td.client-configurator.enabled=true'
|
44
|
+
end
|
45
|
+
|
46
|
+
cmd << '-jar' << digdag_cli_path
|
47
|
+
unless op.argv.empty?
|
48
|
+
cmd << '--config' << digdag_config_path
|
49
|
+
end
|
50
|
+
cmd.concat(op.argv)
|
51
|
+
|
52
|
+
unless ENV['TD_TOOLBELT_DEBUG'].nil?
|
53
|
+
$stderr.puts cmd.to_s
|
54
|
+
end
|
55
|
+
|
56
|
+
if capture_output
|
57
|
+
# TODO: use popen3 instead?
|
58
|
+
stdout_str, stderr_str, status = Open3.capture3(env, *cmd)
|
59
|
+
$stdout.write(stdout_str)
|
60
|
+
$stderr.write(stderr_str)
|
61
|
+
return status.exitstatus
|
62
|
+
else
|
63
|
+
Kernel::system(env, *cmd)
|
64
|
+
return $?.exitstatus
|
65
|
+
end
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
# "Factory reset"
|
70
|
+
def workflow_reset(op)
|
71
|
+
$stdout << 'Removing workflow module...'
|
72
|
+
FileUtils.rm_rf digdag_dir
|
73
|
+
$stdout.puts ' Done.'
|
74
|
+
return 0
|
75
|
+
end
|
76
|
+
|
77
|
+
def workflow_version(op)
|
78
|
+
unless File.exists?(digdag_cli_path)
|
79
|
+
$stderr.puts('Workflow module not yet installed.')
|
80
|
+
return 1
|
81
|
+
end
|
82
|
+
|
83
|
+
$stdout.puts("Bundled Java: #{bundled_java?}")
|
84
|
+
|
85
|
+
begin
|
86
|
+
out, status = Open3.capture2e(java_cmd, '-version')
|
87
|
+
raise unless status.success?
|
88
|
+
rescue
|
89
|
+
$stderr.puts('Failed to run java')
|
90
|
+
return 1
|
91
|
+
end
|
92
|
+
$stdout.puts(out)
|
93
|
+
|
94
|
+
version_op = List::CommandParser.new("workflow", [], [], nil, ['--version'], true)
|
95
|
+
$stdout.write('Digdag version: ')
|
96
|
+
workflow(version_op, capture_output=true, check_prereqs=false)
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
def system_java_cmd
|
101
|
+
if td_wf_java.nil? or td_wf_java.empty?
|
102
|
+
'java'
|
103
|
+
else
|
104
|
+
td_wf_java
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
def bundled_java?
|
110
|
+
if not td_wf_java.empty?
|
111
|
+
return false
|
112
|
+
end
|
113
|
+
return Helpers.on_64bit_os?
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
def td_wf_java
|
118
|
+
ENV.fetch('TD_WF_JAVA', '').strip
|
119
|
+
end
|
120
|
+
|
121
|
+
def digdag_base_url
|
122
|
+
'http://toolbelt.treasure-data.com/digdag'
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
def digdag_url
|
127
|
+
url = ENV.fetch('TD_DIGDAG_URL', '').strip
|
128
|
+
return url unless url.empty?
|
129
|
+
user = Config.read['account.user']
|
130
|
+
if user.nil? or user.strip.empty?
|
131
|
+
raise ConfigError
|
132
|
+
end
|
133
|
+
query = URI.encode_www_form('user' => user)
|
134
|
+
"#{digdag_base_url}?#{query}"
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
def digdag_dir
|
139
|
+
File.join(home_directory, '.td', 'digdag')
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
def digdag_tmp_dir
|
144
|
+
File.join(home_directory, '.td', 'digdag', 'tmp')
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
def digdag_cli_path
|
149
|
+
File.join(digdag_dir, 'digdag')
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
def digdag_jre_dir
|
154
|
+
File.join(digdag_dir, 'jre')
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
158
|
+
def digdag_jre_tmp_dir
|
159
|
+
File.join(digdag_dir, 'jre.tmp')
|
160
|
+
end
|
161
|
+
|
162
|
+
private
|
163
|
+
def java_cmd
|
164
|
+
if bundled_java?
|
165
|
+
digdag_java_path
|
166
|
+
else
|
167
|
+
system_java_cmd
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
private
|
172
|
+
def digdag_java_path
|
173
|
+
File.join(digdag_jre_dir, 'bin', 'java')
|
174
|
+
end
|
175
|
+
|
176
|
+
private
|
177
|
+
def digdag_cli_tmp_path
|
178
|
+
File.join(digdag_dir, 'digdag.tmp')
|
179
|
+
end
|
180
|
+
|
181
|
+
private
|
182
|
+
def jre_archive
|
183
|
+
# XXX (dano): platform detection could be more robust
|
184
|
+
if Helpers.on_64bit_os?
|
185
|
+
if Helpers.on_windows?
|
186
|
+
return 'win_x64'
|
187
|
+
elsif Helpers.on_mac?
|
188
|
+
return 'mac_x64'
|
189
|
+
else # Assume linux
|
190
|
+
return 'lin_x64'
|
191
|
+
end
|
192
|
+
end
|
193
|
+
raise 'OS architecture not supported'
|
194
|
+
end
|
195
|
+
|
196
|
+
private
|
197
|
+
def jre_url
|
198
|
+
base_url = ENV.fetch('TD_WF_JRE_BASE_URL', 'http://toolbelt.treasuredata.com/digdag/jdk/')
|
199
|
+
"#{base_url}#{jre_archive}"
|
200
|
+
end
|
201
|
+
|
202
|
+
private
|
203
|
+
def fail_system_java
|
204
|
+
raise WorkflowError, <<EOF
|
205
|
+
A suitable installed version of Java could not be found and and Java cannot be
|
206
|
+
automatically installed for this OS.
|
207
|
+
|
208
|
+
Please install at least Java 8u71.
|
209
|
+
EOF
|
210
|
+
end
|
211
|
+
|
212
|
+
private
|
213
|
+
def detect_system_java
|
214
|
+
begin
|
215
|
+
output, status = Open3.capture2e(system_java_cmd, '-version')
|
216
|
+
rescue => e
|
217
|
+
return false
|
218
|
+
end
|
219
|
+
unless status.success?
|
220
|
+
return false
|
221
|
+
end
|
222
|
+
if output =~ /openjdk version/ or output =~ /java version/
|
223
|
+
m = output.match(/version "(\d+)\.(\d+)\.(\d+)(?:_(\d+))"/)
|
224
|
+
if not m or m.size < 4
|
225
|
+
return false
|
226
|
+
end
|
227
|
+
# Check for at least Java 8. Let digdag itself verify revision.
|
228
|
+
major = m[1].to_i
|
229
|
+
minor = m[2].to_i
|
230
|
+
if major < 1 or minor < 8
|
231
|
+
return false
|
232
|
+
end
|
233
|
+
end
|
234
|
+
return true
|
235
|
+
end
|
236
|
+
|
237
|
+
private
|
238
|
+
def check_system_java
|
239
|
+
# Trust the user if they've specified a jre to use
|
240
|
+
if td_wf_java.empty?
|
241
|
+
unless detect_system_java
|
242
|
+
fail_system_java
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
# Follow all redirects and return the resulting url
|
248
|
+
def resolve_url(url)
|
249
|
+
require 'net/http'
|
250
|
+
require 'openssl'
|
251
|
+
|
252
|
+
uri = URI(url)
|
253
|
+
http_class = Command.get_http_class
|
254
|
+
http = http_class.new(uri.host, uri.port)
|
255
|
+
|
256
|
+
if uri.scheme == 'https'
|
257
|
+
http.use_ssl = true
|
258
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
259
|
+
end
|
260
|
+
|
261
|
+
http.request_get(uri.path + (uri.query ? '?' + uri.query : '')) {|response|
|
262
|
+
if response.class == Net::HTTPOK
|
263
|
+
return url
|
264
|
+
elsif response.class == Net::HTTPFound || \
|
265
|
+
response.class == Net::HTTPRedirection
|
266
|
+
unless ENV['TD_TOOLBELT_DEBUG'].nil?
|
267
|
+
$stdout.puts "redirect '#{url}' to '#{response['Location']}'... "
|
268
|
+
end
|
269
|
+
return resolve_url(response['Location'])
|
270
|
+
else
|
271
|
+
raise_error "An error occurred when fetching from '#{uri}' " +
|
272
|
+
"(#{response.class.to_s}: #{response.message})."
|
273
|
+
return false
|
274
|
+
end
|
275
|
+
}
|
276
|
+
end
|
277
|
+
|
278
|
+
private
|
279
|
+
def download_java
|
280
|
+
if File.exists?(digdag_jre_dir)
|
281
|
+
return
|
282
|
+
end
|
283
|
+
|
284
|
+
require 'net/http'
|
285
|
+
require 'openssl'
|
286
|
+
|
287
|
+
Dir.mktmpdir do |download_dir|
|
288
|
+
indicator = Command::TimeBasedDownloadProgressIndicator.new(
|
289
|
+
'Downloading Java...', Time.new.to_i, 2)
|
290
|
+
status = nil
|
291
|
+
real_jre_uri = URI(resolve_url(jre_url))
|
292
|
+
jre_filename = Pathname.new(real_jre_uri.path).basename.to_s
|
293
|
+
download_path = File.join(download_dir, jre_filename)
|
294
|
+
File.open(download_path, 'wb') do |file|
|
295
|
+
status = Updater.stream_fetch(jre_url, file) {
|
296
|
+
indicator.update
|
297
|
+
}
|
298
|
+
end
|
299
|
+
indicator.finish
|
300
|
+
|
301
|
+
$stdout.puts
|
302
|
+
|
303
|
+
unless status
|
304
|
+
raise WorkflowError, 'Failed to download Java.'
|
305
|
+
end
|
306
|
+
|
307
|
+
$stdout.print 'Installing Java... '
|
308
|
+
FileUtils.rm_rf digdag_jre_tmp_dir
|
309
|
+
FileUtils.mkdir_p digdag_jre_tmp_dir
|
310
|
+
extract_archive(download_path, digdag_jre_tmp_dir, 1)
|
311
|
+
FileUtils.mv digdag_jre_tmp_dir, digdag_jre_dir
|
312
|
+
$stdout.puts 'done'
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
private
|
317
|
+
def extract_archive(archive, destination, strip)
|
318
|
+
if archive.end_with? '.tar.gz'
|
319
|
+
extract_tarball(archive, destination, strip)
|
320
|
+
elsif archive.end_with? '.zip'
|
321
|
+
extract_zip(archive, destination, strip)
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
private
|
326
|
+
def extract_zip(zip_archive, destination, strip)
|
327
|
+
require 'fileutils'
|
328
|
+
require 'zip/zip'
|
329
|
+
Zip::ZipFile.open(zip_archive) { |zip_file|
|
330
|
+
zip_file.each { |f|
|
331
|
+
stripped = strip_components(f.name, strip)
|
332
|
+
if stripped.empty?
|
333
|
+
next
|
334
|
+
end
|
335
|
+
dest = File.join destination, stripped
|
336
|
+
FileUtils.rm_rf dest if File.exist? dest
|
337
|
+
FileUtils.mkdir_p(File.dirname(dest))
|
338
|
+
zip_file.extract(f, dest)
|
339
|
+
}
|
340
|
+
}
|
341
|
+
end
|
342
|
+
|
343
|
+
# http://stackoverflow.com/a/31310593
|
344
|
+
TAR_LONGLINK = '././@LongLink'
|
345
|
+
private
|
346
|
+
def extract_tarball(tar_gz_archive, destination, strip)
|
347
|
+
require 'fileutils'
|
348
|
+
require 'rubygems/package'
|
349
|
+
require 'zlib'
|
350
|
+
|
351
|
+
Zlib::GzipReader.open tar_gz_archive do |gzip_reader|
|
352
|
+
Gem::Package::TarReader.new(gzip_reader) do |tar|
|
353
|
+
filename = nil
|
354
|
+
tar.each do |entry|
|
355
|
+
# Handle LongLink
|
356
|
+
if entry.full_name == TAR_LONGLINK
|
357
|
+
filename = entry.read.strip
|
358
|
+
next
|
359
|
+
end
|
360
|
+
filename ||= entry.full_name
|
361
|
+
|
362
|
+
# Strip path components
|
363
|
+
stripped = strip_components(filename, strip)
|
364
|
+
filename = nil
|
365
|
+
if stripped.empty?
|
366
|
+
next
|
367
|
+
end
|
368
|
+
dest = File.join destination, stripped
|
369
|
+
|
370
|
+
if entry.directory? || (entry.header.typeflag == '' && entry.full_name.end_with?('/'))
|
371
|
+
File.rm_rf dest if File.file? dest
|
372
|
+
FileUtils.mkdir_p dest, :mode => entry.header.mode, :verbose => false
|
373
|
+
elsif entry.file? || (entry.header.typeflag == '' && !entry.full_name.end_with?('/'))
|
374
|
+
FileUtils.rm_rf dest if File.exist? dest
|
375
|
+
FileUtils.mkdir_p File.dirname dest
|
376
|
+
File.open dest, "wb" do |f|
|
377
|
+
f.print entry.read
|
378
|
+
end
|
379
|
+
FileUtils.chmod entry.header.mode, dest, :verbose => false
|
380
|
+
elsif entry.header.typeflag == '2' #Symlink!
|
381
|
+
File.symlink entry.header.linkname, dest
|
382
|
+
else
|
383
|
+
raise "Unkown tar entry: #{entry.full_name} type: #{entry.header.typeflag}."
|
384
|
+
end
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
def strip_components(filename, strip)
|
391
|
+
File.join(Pathname.new(filename).each_filename.drop(strip))
|
392
|
+
end
|
393
|
+
|
394
|
+
private
|
395
|
+
def check_digdag_cli
|
396
|
+
check_system_java unless bundled_java?
|
397
|
+
|
398
|
+
unless File.exists?(digdag_cli_path)
|
399
|
+
$stderr.puts 'Workflow module not yet installed, download now? [Y/n]'
|
400
|
+
line = $stdin.gets
|
401
|
+
line.strip!
|
402
|
+
if (not line.empty?) and (line !~ /^y(?:es)?$/i)
|
403
|
+
raise WorkflowError, 'Aborted'
|
404
|
+
end
|
405
|
+
download_digdag
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
def download_digdag
|
410
|
+
require 'net/http'
|
411
|
+
require 'openssl'
|
412
|
+
|
413
|
+
FileUtils.mkdir_p digdag_dir
|
414
|
+
|
415
|
+
if bundled_java?
|
416
|
+
download_java
|
417
|
+
end
|
418
|
+
|
419
|
+
Dir.mktmpdir do |download_dir|
|
420
|
+
indicator = Command::TimeBasedDownloadProgressIndicator.new(
|
421
|
+
'Downloading workflow module...', Time.new.to_i, 2)
|
422
|
+
status = nil
|
423
|
+
download_path = File.join(download_dir, 'digdag')
|
424
|
+
File.open(download_path, 'wb') do |file|
|
425
|
+
status = Updater.stream_fetch(digdag_url, file) {
|
426
|
+
indicator.update
|
427
|
+
}
|
428
|
+
end
|
429
|
+
indicator.finish
|
430
|
+
|
431
|
+
$stdout.puts
|
432
|
+
|
433
|
+
unless status
|
434
|
+
raise WorkflowError, 'Failed to download workflow module.'
|
435
|
+
end
|
436
|
+
|
437
|
+
$stdout.print 'Installing workflow module... '
|
438
|
+
FileUtils.rm_rf(digdag_cli_tmp_path)
|
439
|
+
FileUtils.cp(download_path, digdag_cli_tmp_path)
|
440
|
+
FileUtils.chmod('a=xr', digdag_cli_tmp_path)
|
441
|
+
FileUtils.mv(digdag_cli_tmp_path, digdag_cli_path)
|
442
|
+
$stdout.puts 'done'
|
443
|
+
end
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end
|
data/lib/td/helpers.rb
CHANGED
@@ -17,5 +17,19 @@ module TreasureData
|
|
17
17
|
def on_mac?
|
18
18
|
RUBY_PLATFORM =~ /-darwin\d/
|
19
19
|
end
|
20
|
+
|
21
|
+
def on_64bit_os?
|
22
|
+
if on_windows?
|
23
|
+
if ENV.fetch('PROCESSOR_ARCHITECTURE', '').downcase.include? 'amd64'
|
24
|
+
return true
|
25
|
+
end
|
26
|
+
return ENV.has_key?('PROCESSOR_ARCHITEW6432')
|
27
|
+
else
|
28
|
+
require 'open3'
|
29
|
+
out, status = Open3.capture2('uname', '-m')
|
30
|
+
raise 'Failed to detect OS bitness' unless status.success?
|
31
|
+
return out.downcase.include? 'x86_64'
|
32
|
+
end
|
33
|
+
end
|
20
34
|
end
|
21
35
|
end
|
data/lib/td/updater.rb
CHANGED
@@ -258,6 +258,7 @@ module ModuleDefinition
|
|
258
258
|
|
259
259
|
def stream_fetch(url, binfile, &progress)
|
260
260
|
require 'net/http'
|
261
|
+
require 'openssl'
|
261
262
|
|
262
263
|
uri = URI(url)
|
263
264
|
http_class = Command.get_http_class
|
@@ -268,7 +269,7 @@ module ModuleDefinition
|
|
268
269
|
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
269
270
|
end
|
270
271
|
|
271
|
-
http.request_get(uri.path) {|response|
|
272
|
+
http.request_get(uri.path + (uri.query ? '?' + uri.query : '')) {|response|
|
272
273
|
if response.class == Net::HTTPOK
|
273
274
|
# $stdout.print a . every tick_period seconds
|
274
275
|
response.read_body do |chunk|
|
data/lib/td/version.rb
CHANGED
@@ -353,5 +353,127 @@ module TreasureData::Command
|
|
353
353
|
expect(stdout_io.string).to include table
|
354
354
|
end
|
355
355
|
end
|
356
|
+
|
357
|
+
describe '#connector_update' do
|
358
|
+
let(:name) { 'daily_mysql_import' }
|
359
|
+
let(:name2) { 'daily_mysql_import2' }
|
360
|
+
let(:cron) { '10 0 * * *' }
|
361
|
+
let(:cron2) { '20 0 * * *' }
|
362
|
+
let(:database) { 'td_sample_db' }
|
363
|
+
let(:table) { 'td_sample_table' }
|
364
|
+
let(:config_file) {
|
365
|
+
Tempfile.new('config.yml').tap {|tf|
|
366
|
+
tf.puts({"foo" => "bar"}.to_yaml)
|
367
|
+
tf.close
|
368
|
+
}
|
369
|
+
}
|
370
|
+
let(:config) {
|
371
|
+
h = YAML.load_file(config_file.path)
|
372
|
+
TreasureData::ConnectorConfigNormalizer.new(h).normalized_config
|
373
|
+
}
|
374
|
+
let(:config_diff_file) {
|
375
|
+
Tempfile.new('config_diff.yml').tap {|tf|
|
376
|
+
tf.puts({"hoge" => "fuga"}.to_yaml)
|
377
|
+
tf.close
|
378
|
+
}
|
379
|
+
}
|
380
|
+
let(:config_diff) {
|
381
|
+
h = YAML.load_file(config_diff_file.path)
|
382
|
+
TreasureData::ConnectorConfigNormalizer.new(h).normalized_config
|
383
|
+
}
|
384
|
+
let(:option) {
|
385
|
+
List::CommandParser.new("connector:update", %w(name), %w(config_file), nil, argv, true)
|
386
|
+
}
|
387
|
+
let(:response) {
|
388
|
+
{'name' => name, 'cron' => cron, 'timezone' => 'UTC', 'delay' => 0, 'database' => database, 'table' => table,
|
389
|
+
'config' => config, 'config_diff' => config_diff}
|
390
|
+
}
|
391
|
+
let(:client) { double(:client) }
|
392
|
+
|
393
|
+
before do
|
394
|
+
allow(command).to receive(:get_client).and_return(client)
|
395
|
+
allow(client).to receive(:bulk_load_update) do |name, settings|
|
396
|
+
r = response.merge('name' => name)
|
397
|
+
settings.each do |key, value|
|
398
|
+
value = nil if key == 'cron' && value.empty?
|
399
|
+
r[key.to_s] = value
|
400
|
+
end
|
401
|
+
r
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
context 'with new_name' do
|
406
|
+
let (:argv){ ['--newname', name2, name] }
|
407
|
+
it 'show update result' do
|
408
|
+
expect{command.connector_update(option)}.not_to raise_error(SystemExit)
|
409
|
+
expect(stdout_io.string).to include name2
|
410
|
+
expect(stdout_io.string).to include cron
|
411
|
+
expect(stdout_io.string).to include database
|
412
|
+
expect(stdout_io.string).to include table
|
413
|
+
expect(YAML.load(stdout_io.string[/^Config\n---\n(.*?\n)\n/m, 1])).to eq(config)
|
414
|
+
expect(YAML.load(stdout_io.string[/^Config Diff\n---\n(.*?\n)\Z/m, 1])).to eq(config_diff)
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
context 'with config' do
|
419
|
+
let (:argv){ [name, config_file.path] }
|
420
|
+
it 'show update result' do
|
421
|
+
expect{command.connector_update(option)}.not_to raise_error(SystemExit)
|
422
|
+
expect(stdout_io.string).to include name
|
423
|
+
expect(stdout_io.string).to include cron
|
424
|
+
expect(stdout_io.string).to include database
|
425
|
+
expect(stdout_io.string).to include table
|
426
|
+
expect(YAML.load(stdout_io.string[/^Config\n---\n(.*?\n)\n/m, 1])).to eq(config)
|
427
|
+
expect(YAML.load(stdout_io.string[/^Config Diff\n---\n(.*?\n)\Z/m, 1])).to eq(config_diff)
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
context 'with config_diff' do
|
432
|
+
let(:argv){ ['--config-diff', config_diff_file.path, name] }
|
433
|
+
it 'show update result' do
|
434
|
+
expect{command.connector_update(option)}.not_to raise_error(SystemExit)
|
435
|
+
expect(stdout_io.string).to include name
|
436
|
+
expect(stdout_io.string).to include cron
|
437
|
+
expect(stdout_io.string).to include database
|
438
|
+
expect(stdout_io.string).to include table
|
439
|
+
expect(YAML.load(stdout_io.string[/^Config\n---\n(.*?\n)\n/m, 1])).to eq(config)
|
440
|
+
expect(YAML.load(stdout_io.string[/^Config Diff\n---\n(.*?\n)\Z/m, 1])).to eq(config_diff)
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
context 'with schedule' do
|
445
|
+
let(:argv) { ['--schedule', cron2, name] }
|
446
|
+
it 'can update cron' do
|
447
|
+
expect{command.connector_update(option)}.not_to raise_error(SystemExit)
|
448
|
+
expect(stdout_io.string).to include name
|
449
|
+
expect(stdout_io.string).to include cron2
|
450
|
+
expect(stdout_io.string).to include database
|
451
|
+
expect(stdout_io.string).to include table
|
452
|
+
expect(YAML.load(stdout_io.string[/^Config\n---\n(.*?\n)\n/m, 1])).to eq(config)
|
453
|
+
expect(YAML.load(stdout_io.string[/^Config Diff\n---\n(.*?\n)\Z/m, 1])).to eq(config_diff)
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
context 'with empty schedule' do
|
458
|
+
let(:argv) { [name, '--schedule'] }
|
459
|
+
it 'can update cron' do
|
460
|
+
expect{command.connector_update(option)}.not_to raise_error(SystemExit)
|
461
|
+
expect(stdout_io.string).to include name
|
462
|
+
expect(stdout_io.string).to include "Cron : \n"
|
463
|
+
expect(stdout_io.string).to include database
|
464
|
+
expect(stdout_io.string).to include table
|
465
|
+
expect(YAML.load(stdout_io.string[/^Config\n---\n(.*?\n)\n/m, 1])).to eq(config)
|
466
|
+
expect(YAML.load(stdout_io.string[/^Config Diff\n---\n(.*?\n)\Z/m, 1])).to eq(config_diff)
|
467
|
+
end
|
468
|
+
end
|
469
|
+
|
470
|
+
context 'nothing to update' do
|
471
|
+
let (:argv) { [name] }
|
472
|
+
it 'show update result' do
|
473
|
+
expect{command.connector_update(option)}.to raise_error(SystemExit)
|
474
|
+
expect(stdout_io.string).to include 'Error: nothing to update'
|
475
|
+
end
|
476
|
+
end
|
477
|
+
end
|
356
478
|
end
|
357
479
|
end
|
@@ -0,0 +1,314 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'td/command/common'
|
3
|
+
require 'td/command/list'
|
4
|
+
require 'td/command/workflow'
|
5
|
+
|
6
|
+
def java_available?
|
7
|
+
begin
|
8
|
+
output, status = Open3.capture2e('java', '-version')
|
9
|
+
rescue
|
10
|
+
return false
|
11
|
+
end
|
12
|
+
if not status.success?
|
13
|
+
return false
|
14
|
+
end
|
15
|
+
if output !~ /(openjdk|java) version "1/
|
16
|
+
return false
|
17
|
+
end
|
18
|
+
return true
|
19
|
+
end
|
20
|
+
|
21
|
+
STDERR.puts
|
22
|
+
STDERR.puts("RUBY_PLATFORM: #{RUBY_PLATFORM}")
|
23
|
+
STDERR.puts("on_64bit_os?: #{TreasureData::Helpers.on_64bit_os?}")
|
24
|
+
STDERR.puts("java_available?: #{java_available?}")
|
25
|
+
STDERR.puts
|
26
|
+
|
27
|
+
module TreasureData::Command
|
28
|
+
|
29
|
+
describe 'workflow command' do
|
30
|
+
|
31
|
+
let(:command) {
|
32
|
+
Class.new { include TreasureData::Command }.new
|
33
|
+
}
|
34
|
+
let(:stdout_io) { StringIO.new }
|
35
|
+
let(:stderr_io) { StringIO.new }
|
36
|
+
let(:home_env) { TreasureData::Helpers.on_windows? ? 'USERPROFILE' : 'HOME' }
|
37
|
+
let(:java_exe) { TreasureData::Helpers.on_windows? ? 'java.exe' : 'java' }
|
38
|
+
|
39
|
+
around do |example|
|
40
|
+
|
41
|
+
stdout = $stdout.dup
|
42
|
+
stderr = $stderr.dup
|
43
|
+
|
44
|
+
begin
|
45
|
+
$stdout = stdout_io
|
46
|
+
$stderr = stderr_io
|
47
|
+
|
48
|
+
Dir.mktmpdir { |home|
|
49
|
+
with_env(home_env, home) {
|
50
|
+
example.run
|
51
|
+
}
|
52
|
+
}
|
53
|
+
ensure
|
54
|
+
$stdout = stdout
|
55
|
+
$stderr = stderr
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def with_env(name, var)
|
60
|
+
backup, ENV[name] = ENV[name], var
|
61
|
+
begin
|
62
|
+
yield
|
63
|
+
ensure
|
64
|
+
ENV[name] = backup
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
let(:tmpdir) {
|
69
|
+
Dir.mktmpdir
|
70
|
+
}
|
71
|
+
|
72
|
+
let(:project_name) {
|
73
|
+
'foobar'
|
74
|
+
}
|
75
|
+
|
76
|
+
let(:project_dir) {
|
77
|
+
File.join(tmpdir, project_name)
|
78
|
+
}
|
79
|
+
|
80
|
+
let(:workflow_name) {
|
81
|
+
project_name
|
82
|
+
}
|
83
|
+
|
84
|
+
let(:workflow_file) {
|
85
|
+
File.join(project_dir, workflow_name + '.dig')
|
86
|
+
}
|
87
|
+
|
88
|
+
let(:td_conf) {
|
89
|
+
File.join(tmpdir, 'td.conf')
|
90
|
+
}
|
91
|
+
|
92
|
+
after(:each) {
|
93
|
+
FileUtils.rm_rf tmpdir
|
94
|
+
}
|
95
|
+
|
96
|
+
describe '#workflow' do
|
97
|
+
let(:empty_option) {
|
98
|
+
List::CommandParser.new("workflow", [], [], nil, [], true)
|
99
|
+
}
|
100
|
+
|
101
|
+
let(:version_option) {
|
102
|
+
List::CommandParser.new("workflow", [], [], nil, ['version'], true)
|
103
|
+
}
|
104
|
+
|
105
|
+
let(:init_option) {
|
106
|
+
List::CommandParser.new("workflow", [], [], nil, ['init', project_dir], true)
|
107
|
+
}
|
108
|
+
|
109
|
+
let(:run_option) {
|
110
|
+
List::CommandParser.new("workflow", [], [], nil, ['run', workflow_name], true)
|
111
|
+
}
|
112
|
+
|
113
|
+
let(:reset_option) {
|
114
|
+
List::CommandParser.new("workflow:reset", [], [], nil, [], true)
|
115
|
+
}
|
116
|
+
|
117
|
+
let(:version_option) {
|
118
|
+
List::CommandParser.new("workflow:version", [], [], nil, [], true)
|
119
|
+
}
|
120
|
+
|
121
|
+
let (:apikey) {
|
122
|
+
'4711/badf00d'
|
123
|
+
}
|
124
|
+
|
125
|
+
before(:each) {
|
126
|
+
allow(TreasureData::Config).to receive(:apikey) { apikey }
|
127
|
+
allow(TreasureData::Config).to receive(:path) { td_conf }
|
128
|
+
File.write(td_conf, [
|
129
|
+
'[account]',
|
130
|
+
' user = test@example.com',
|
131
|
+
" apikey = #{apikey}",
|
132
|
+
].join($/) + $/)
|
133
|
+
}
|
134
|
+
|
135
|
+
it 'complains about 32 bit platform if no usable java on path' do
|
136
|
+
allow(TreasureData::Helpers).to receive(:on_64bit_os?) { false }
|
137
|
+
with_env('PATH', '') do
|
138
|
+
expect { command.workflow(empty_option, capture_output=true) }.to raise_error(WorkflowError) { |error|
|
139
|
+
expect(error.message).to include(<<EOF
|
140
|
+
A suitable installed version of Java could not be found and and Java cannot be
|
141
|
+
automatically installed for this OS.
|
142
|
+
|
143
|
+
Please install at least Java 8u71.
|
144
|
+
EOF
|
145
|
+
)
|
146
|
+
}
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'uses system java by default on 32 bit platforms' do
|
151
|
+
allow(TreasureData::Helpers).to receive(:on_64bit_os?) { false }
|
152
|
+
expect(java_available?).to be(true)
|
153
|
+
|
154
|
+
allow(TreasureData::Updater).to receive(:stream_fetch).and_call_original
|
155
|
+
allow($stdin).to receive(:gets) { 'Y' }
|
156
|
+
status = command.workflow(empty_option, capture_output=true)
|
157
|
+
expect(status).to be 0
|
158
|
+
expect(stdout_io.string).to_not include 'Downloading Java'
|
159
|
+
expect(stdout_io.string).to include 'Downloading workflow module'
|
160
|
+
expect(File).to exist(File.join(ENV[home_env], '.td', 'digdag', 'digdag'))
|
161
|
+
expect(TreasureData::Updater).to_not have_received(:stream_fetch).with(
|
162
|
+
%r{/java/}, instance_of(File))
|
163
|
+
expect(TreasureData::Updater).to have_received(:stream_fetch).with(
|
164
|
+
'http://toolbelt.treasure-data.com/digdag?user=test%40example.com', instance_of(File))
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'installs java + digdag and can run a workflow' do
|
168
|
+
skip 'Requires 64 bit OS or java' unless (TreasureData::Helpers::on_64bit_os? or java_available?)
|
169
|
+
|
170
|
+
allow(TreasureData::Updater).to receive(:stream_fetch).and_call_original
|
171
|
+
allow($stdin).to receive(:gets) { 'Y' }
|
172
|
+
status = command.workflow(empty_option, capture_output=true)
|
173
|
+
expect(status).to be 0
|
174
|
+
if TreasureData::Helpers::on_64bit_os?
|
175
|
+
expect(stdout_io.string).to include 'Downloading Java'
|
176
|
+
expect(File).to exist(File.join(ENV[home_env], '.td', 'digdag', 'jre', 'bin', java_exe))
|
177
|
+
expect(TreasureData::Updater).to have_received(:stream_fetch).with(
|
178
|
+
%r{/java/}, instance_of(File))
|
179
|
+
end
|
180
|
+
expect(stdout_io.string).to include 'Downloading workflow module'
|
181
|
+
expect(File).to exist(File.join(ENV[home_env], '.td', 'digdag', 'digdag'))
|
182
|
+
expect(TreasureData::Updater).to have_received(:stream_fetch).with(
|
183
|
+
'http://toolbelt.treasure-data.com/digdag?user=test%40example.com', instance_of(File))
|
184
|
+
|
185
|
+
# Check that java and digdag is not re-installed
|
186
|
+
stdout_io.truncate(0)
|
187
|
+
stderr_io.truncate(0)
|
188
|
+
status = command.workflow(empty_option, capture_output=true)
|
189
|
+
expect(status).to be 0
|
190
|
+
expect(stdout_io.string).to_not include 'Downloading Java'
|
191
|
+
expect(stdout_io.string).to_not include 'Downloading workflow module'
|
192
|
+
|
193
|
+
# Generate a new project
|
194
|
+
expect(Dir.exist? project_dir).to be(false)
|
195
|
+
stdout_io.truncate(0)
|
196
|
+
stderr_io.truncate(0)
|
197
|
+
status = command.workflow(init_option, capture_output=true)
|
198
|
+
expect(status).to be 0
|
199
|
+
expect(stdout_io.string).to include('Creating')
|
200
|
+
expect(Dir.exist? project_dir).to be(true)
|
201
|
+
expect(File.exist? workflow_file).to be(true)
|
202
|
+
|
203
|
+
# Run a workflow
|
204
|
+
File.write(workflow_file, <<EOF
|
205
|
+
+main:
|
206
|
+
echo>: hello world
|
207
|
+
EOF
|
208
|
+
)
|
209
|
+
Dir.chdir(project_dir) {
|
210
|
+
stdout_io.truncate(0)
|
211
|
+
stderr_io.truncate(0)
|
212
|
+
status = command.workflow(run_option, capture_output=true)
|
213
|
+
expect(status).to be 0
|
214
|
+
expect(stderr_io.string).to include('Success')
|
215
|
+
expect(stdout_io.string).to include('hello world')
|
216
|
+
}
|
217
|
+
end
|
218
|
+
|
219
|
+
it 'uses specified java and installs digdag' do
|
220
|
+
with_env('TD_WF_JAVA', 'java') {
|
221
|
+
allow(TreasureData::Updater).to receive(:stream_fetch).and_call_original
|
222
|
+
allow($stdin).to receive(:gets) { 'Y' }
|
223
|
+
status = command.workflow(empty_option, capture_output=true)
|
224
|
+
expect(status).to be 0
|
225
|
+
expect(stdout_io.string).to_not include 'Downloading Java'
|
226
|
+
expect(stdout_io.string).to include 'Downloading workflow module'
|
227
|
+
expect(File).to exist(File.join(ENV[home_env], '.td', 'digdag', 'digdag'))
|
228
|
+
expect(TreasureData::Updater).to_not have_received(:stream_fetch).with(
|
229
|
+
%r{/java/}, instance_of(File))
|
230
|
+
expect(TreasureData::Updater).to have_received(:stream_fetch).with(
|
231
|
+
'http://toolbelt.treasure-data.com/digdag?user=test%40example.com', instance_of(File))
|
232
|
+
|
233
|
+
# Check that digdag is not re-installed
|
234
|
+
stdout_io.truncate(0)
|
235
|
+
stderr_io.truncate(0)
|
236
|
+
status = command.workflow(empty_option, capture_output=true)
|
237
|
+
expect(status).to be 0
|
238
|
+
expect(stdout_io.string).to_not include 'Downloading Java'
|
239
|
+
expect(stdout_io.string).to_not include 'Downloading workflow module'
|
240
|
+
}
|
241
|
+
end
|
242
|
+
|
243
|
+
it 'reinstalls cleanly after reset' do
|
244
|
+
skip 'Requires 64 bit OS or system java' unless (TreasureData::Helpers::on_64bit_os? or java_available?)
|
245
|
+
|
246
|
+
# First install
|
247
|
+
allow($stdin).to receive(:gets) { 'Y' }
|
248
|
+
status = command.workflow(empty_option, capture_output=true)
|
249
|
+
expect(status).to be 0
|
250
|
+
expect(stderr_io.string).to include 'Digdag v'
|
251
|
+
expect(File).to exist(File.join(ENV[home_env], '.td', 'digdag'))
|
252
|
+
|
253
|
+
# Reset
|
254
|
+
stdout_io.truncate(0)
|
255
|
+
stderr_io.truncate(0)
|
256
|
+
status = command.workflow_reset(reset_option)
|
257
|
+
expect(status).to be 0
|
258
|
+
expect(File).to_not exist(File.join(ENV[home_env], '.td', 'digdag'))
|
259
|
+
expect(File).to exist(File.join(ENV[home_env], '.td'))
|
260
|
+
expect(stdout_io.string).to include 'Removing workflow module...'
|
261
|
+
expect(stdout_io.string).to include 'Done'
|
262
|
+
|
263
|
+
# Reinstall
|
264
|
+
allow($stdin).to receive(:gets) { 'Y' }
|
265
|
+
stdout_io.truncate(0)
|
266
|
+
stderr_io.truncate(0)
|
267
|
+
status = command.workflow(empty_option, capture_output=true)
|
268
|
+
expect(status).to be 0
|
269
|
+
expect(stderr_io.string).to include 'Digdag v'
|
270
|
+
expect(File).to exist(File.join(ENV[home_env], '.td', 'digdag'))
|
271
|
+
end
|
272
|
+
|
273
|
+
it 'uses -k apikey' do
|
274
|
+
with_env('TD_WF_JAVA', 'echo') {
|
275
|
+
allow(TreasureData::Config).to receive(:cl_apikey) { true }
|
276
|
+
stdout_io.truncate(0)
|
277
|
+
stderr_io.truncate(0)
|
278
|
+
status = command.workflow(init_option, capture_output=true, check_prereqs=false)
|
279
|
+
expect(status).to be 0
|
280
|
+
expect(stdout_io.string).to include('--config')
|
281
|
+
expect(stdout_io.string).to_not include('io.digdag.standards.td.client-configurator.enabled=true')
|
282
|
+
}
|
283
|
+
end
|
284
|
+
|
285
|
+
it 'complains if there is no apikey' do
|
286
|
+
allow(TreasureData::Config).to receive(:apikey) { nil}
|
287
|
+
expect{command.workflow(version_option)}.to raise_error(TreasureData::ConfigError)
|
288
|
+
end
|
289
|
+
|
290
|
+
it 'prints the java and digdag versions' do
|
291
|
+
skip 'Requires 64 bit OS' unless TreasureData::Helpers::on_64bit_os?
|
292
|
+
|
293
|
+
# Not yet installed
|
294
|
+
status = command.workflow_version(version_option)
|
295
|
+
expect(status).to be 1
|
296
|
+
expect(stderr_io.string).to include 'Workflow module not yet installed.'
|
297
|
+
|
298
|
+
# Install
|
299
|
+
allow($stdin).to receive(:gets) { 'Y' }
|
300
|
+
status = command.workflow(empty_option, capture_output=true)
|
301
|
+
expect(status).to be 0
|
302
|
+
|
303
|
+
# Check that version is shown
|
304
|
+
stdout_io.truncate(0)
|
305
|
+
stderr_io.truncate(0)
|
306
|
+
status = command.workflow_version(version_option)
|
307
|
+
expect(status).to be 0
|
308
|
+
expect(stdout_io.string).to include 'Bundled Java: true'
|
309
|
+
expect(stdout_io.string).to include 'openjdk version'
|
310
|
+
expect(stdout_io.string).to include 'Digdag version:'
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
data/spec/td/helpers_spec.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'td/helpers'
|
3
|
+
require 'open3'
|
3
4
|
|
4
5
|
module TreasureData
|
6
|
+
|
5
7
|
describe 'format_with_delimiter' do
|
6
8
|
it "delimits the number with ',' by default" do
|
7
9
|
expect(Helpers.format_with_delimiter(0)).to eq("0")
|
@@ -13,4 +15,57 @@ module TreasureData
|
|
13
15
|
expect(Helpers.format_with_delimiter(1000000)).to eq("1,000,000")
|
14
16
|
end
|
15
17
|
end
|
18
|
+
|
19
|
+
describe 'on_64bit_os?' do
|
20
|
+
|
21
|
+
def with_env(name, var)
|
22
|
+
backup, ENV[name] = ENV[name], var
|
23
|
+
begin
|
24
|
+
yield
|
25
|
+
ensure
|
26
|
+
ENV[name] = backup
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'returns true for windows when PROCESSOR_ARCHITECTURE=amd64' do
|
31
|
+
allow(Helpers).to receive(:on_windows?) {true}
|
32
|
+
with_env('PROCESSOR_ARCHITECTURE', 'amd64') {
|
33
|
+
expect(Helpers.on_64bit_os?).to be(true)
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'returns true for windows when PROCESSOR_ARCHITECTURE=x86 and PROCESSOR_ARCHITEW6432 is set' do
|
38
|
+
allow(Helpers).to receive(:on_windows?) {true}
|
39
|
+
with_env('PROCESSOR_ARCHITECTURE', 'x86') {
|
40
|
+
with_env('PROCESSOR_ARCHITEW6432', '') {
|
41
|
+
expect(Helpers.on_64bit_os?).to be(true)
|
42
|
+
}
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'returns false for windows when PROCESSOR_ARCHITECTURE=x86 and PROCESSOR_ARCHITEW6432 is not set' do
|
47
|
+
allow(Helpers).to receive(:on_windows?) {true}
|
48
|
+
with_env('PROCESSOR_ARCHITECTURE', 'x86') {
|
49
|
+
with_env('PROCESSOR_ARCHITEW6432', nil) {
|
50
|
+
expect(Helpers.on_64bit_os?).to be(false)
|
51
|
+
}
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'returns true for non-windows when uname -m prints x86_64' do
|
56
|
+
allow(Helpers).to receive(:on_windows?) {false}
|
57
|
+
allow(Open3).to receive(:capture2).with('uname', '-m') {['x86_64', double(:success? => true)]}
|
58
|
+
expect(Helpers.on_64bit_os?).to be(true)
|
59
|
+
expect(Open3).to have_received(:capture2).with('uname', '-m')
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'returns false for non-windows when uname -m prints i686' do
|
63
|
+
allow(Helpers).to receive(:on_windows?) {false}
|
64
|
+
allow(Open3).to receive(:capture2).with('uname', '-m') {['i686', double(:success? => true)]}
|
65
|
+
expect(Helpers.on_64bit_os?).to be(false)
|
66
|
+
expect(Open3).to have_received(:capture2).with('uname', '-m')
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
16
71
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: td
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.15.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Treasure Data, Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-07
|
11
|
+
date: 2016-09-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: msgpack
|
@@ -269,6 +269,7 @@ files:
|
|
269
269
|
- lib/td/command/table.rb
|
270
270
|
- lib/td/command/update.rb
|
271
271
|
- lib/td/command/user.rb
|
272
|
+
- lib/td/command/workflow.rb
|
272
273
|
- lib/td/compact_format_yamler.rb
|
273
274
|
- lib/td/compat_core.rb
|
274
275
|
- lib/td/compat_gzip_reader.rb
|
@@ -293,6 +294,7 @@ files:
|
|
293
294
|
- spec/td/command/query_spec.rb
|
294
295
|
- spec/td/command/sched_spec.rb
|
295
296
|
- spec/td/command/table_spec.rb
|
297
|
+
- spec/td/command/workflow_spec.rb
|
296
298
|
- spec/td/common_spec.rb
|
297
299
|
- spec/td/compact_format_yamler_spec.rb
|
298
300
|
- spec/td/connector_config_normalizer_spec.rb
|
@@ -343,6 +345,7 @@ test_files:
|
|
343
345
|
- spec/td/command/query_spec.rb
|
344
346
|
- spec/td/command/sched_spec.rb
|
345
347
|
- spec/td/command/table_spec.rb
|
348
|
+
- spec/td/command/workflow_spec.rb
|
346
349
|
- spec/td/common_spec.rb
|
347
350
|
- spec/td/compact_format_yamler_spec.rb
|
348
351
|
- spec/td/connector_config_normalizer_spec.rb
|