etna 0.1.46 → 0.1.48
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/etna.completion +10240 -7315
- data/lib/commands.rb +54 -1
- data/lib/etna/application.rb +19 -28
- data/lib/etna/auth.rb +2 -2
- data/lib/etna/client.rb +235 -35
- data/lib/etna/clients/base_client.rb +3 -2
- data/lib/etna/clients/magma/workflows/materialize_magma_record_files_workflow.rb +4 -0
- data/lib/etna/clients/magma/workflows/walk_model_tree_workflow.rb +0 -1
- data/lib/etna/clients/metis/client.rb +2 -2
- data/lib/etna/clients/metis/workflows/sync_metis_data_workflow.rb +41 -22
- data/lib/etna/command.rb +1 -1
- data/lib/etna/controller.rb +1 -1
- data/lib/etna/filesystem.rb +31 -11
- data/lib/etna/instrumentation.rb +56 -0
- data/lib/etna/route.rb +25 -40
- data/lib/etna/spec/auth.rb +9 -1
- data/lib/etna/user.rb +4 -0
- data/lib/etna.rb +1 -0
- data/lib/helpers.rb +4 -2
- metadata +4 -4
data/lib/commands.rb
CHANGED
@@ -206,7 +206,7 @@ class EtnaApp
|
|
206
206
|
end
|
207
207
|
end
|
208
208
|
|
209
|
-
class
|
209
|
+
class Janus
|
210
210
|
include Etna::CommandExecutor
|
211
211
|
|
212
212
|
class Token
|
@@ -238,6 +238,59 @@ class EtnaApp
|
|
238
238
|
end
|
239
239
|
end
|
240
240
|
end
|
241
|
+
end
|
242
|
+
|
243
|
+
class Magma
|
244
|
+
include Etna::CommandExecutor
|
245
|
+
|
246
|
+
class Materialize < Etna::Command
|
247
|
+
include WithEtnaClients
|
248
|
+
|
249
|
+
string_flags << "--project-name"
|
250
|
+
string_flags << "--log-file"
|
251
|
+
string_flags << "--log-level"
|
252
|
+
string_flags << "--concurrency"
|
253
|
+
|
254
|
+
|
255
|
+
def execute(project_name:, log_file:'/dev/stdout', log_level: ::Logger::INFO, concurrency: 1)
|
256
|
+
logger = Etna::Logger.new(log_file, 0, 1048576)
|
257
|
+
|
258
|
+
logger.level = log_level
|
259
|
+
|
260
|
+
workflow = Etna::Clients::Magma::MaterializeDataWorkflow.new(
|
261
|
+
model_attributes_mask: model_attribute_pairs(project_name),
|
262
|
+
record_names: 'all',
|
263
|
+
model_filters: {},
|
264
|
+
metis_client: metis_client(logger: logger),
|
265
|
+
magma_client: magma_client(logger: logger),
|
266
|
+
logger: logger,
|
267
|
+
project_name: project_name,
|
268
|
+
model_name: 'project', filesystem: filesystem,
|
269
|
+
concurrency: concurrency.to_i)
|
270
|
+
|
271
|
+
workflow.materialize_all(project_name)
|
272
|
+
logger.info("Done")
|
273
|
+
end
|
274
|
+
|
275
|
+
def model_attribute_pairs(project_name)
|
276
|
+
models = magma_client.retrieve(Etna::Clients::Magma::RetrievalRequest.new(
|
277
|
+
project_name: project_name,
|
278
|
+
model_name: 'all',
|
279
|
+
attribute_names: 'all',
|
280
|
+
record_names: []
|
281
|
+
)).models
|
282
|
+
|
283
|
+
result = models.model_keys.map do |model_name|
|
284
|
+
[ model_name, models.model(model_name).template.attributes.attribute_keys ]
|
285
|
+
end.to_h
|
286
|
+
|
287
|
+
result
|
288
|
+
end
|
289
|
+
|
290
|
+
def filesystem
|
291
|
+
@filesystem ||= Etna::Filesystem.new
|
292
|
+
end
|
293
|
+
end
|
241
294
|
|
242
295
|
class Project
|
243
296
|
include Etna::CommandExecutor
|
data/lib/etna/application.rb
CHANGED
@@ -68,38 +68,43 @@ module Etna::Application
|
|
68
68
|
[path.map(&:to_sym), YAML.load(File.read(value))]
|
69
69
|
end
|
70
70
|
|
71
|
+
def controller_tags
|
72
|
+
[:controller, :action, :project_name, :user_hash]
|
73
|
+
end
|
74
|
+
|
71
75
|
def setup_yabeda
|
72
76
|
application = self.id
|
77
|
+
ctags = self.controller_tags
|
73
78
|
Yabeda.configure do
|
74
79
|
default_tag :application, application
|
80
|
+
default_tag :project_name, 'unknown'
|
81
|
+
default_tag :controller, 'none'
|
82
|
+
default_tag :action, 'none'
|
83
|
+
default_tag :user_hash, 'unknown'
|
75
84
|
|
76
85
|
group :etna do
|
77
86
|
histogram :response_time do
|
78
87
|
comment "Time spent by a controller returning a response"
|
79
88
|
unit :seconds
|
80
|
-
tags
|
89
|
+
tags ctags
|
90
|
+
buckets [0.01, 0.1, 0.3, 0.5, 1, 5]
|
91
|
+
end
|
92
|
+
|
93
|
+
histogram :perf do
|
94
|
+
comment "Time spent inside code path"
|
95
|
+
unit :seconds
|
96
|
+
tags ctags + [:class_name, :method_name]
|
81
97
|
buckets [0.01, 0.1, 0.3, 0.5, 1, 5]
|
82
98
|
end
|
83
99
|
|
84
100
|
counter :visits do
|
85
101
|
comment "Counts visits to the controller"
|
86
|
-
tags
|
102
|
+
tags ctags
|
87
103
|
end
|
88
104
|
|
89
105
|
counter :rollbar_errors do
|
90
106
|
comment "Counts errors detected by and sent to rollbar"
|
91
|
-
|
92
|
-
|
93
|
-
gauge :last_command_completion do
|
94
|
-
comment "Unix time of last time command was completed"
|
95
|
-
tags [:command, :status, :application]
|
96
|
-
end
|
97
|
-
|
98
|
-
histogram :command_runtime do
|
99
|
-
comment "Time spent processing a given command"
|
100
|
-
tags [:command, :status, :application]
|
101
|
-
unit :seconds
|
102
|
-
buckets [0.1, 1, 5, 60, 300, 1500]
|
107
|
+
tags ctags
|
103
108
|
end
|
104
109
|
end
|
105
110
|
end
|
@@ -176,9 +181,6 @@ module Etna::Application
|
|
176
181
|
end
|
177
182
|
|
178
183
|
def run_command(config, *args, &block)
|
179
|
-
application = self.id
|
180
|
-
status = 'success'
|
181
|
-
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
182
184
|
cmd, cmd_args, cmd_kwds = find_command(*args)
|
183
185
|
|
184
186
|
begin
|
@@ -190,18 +192,7 @@ module Etna::Application
|
|
190
192
|
cmd.execute(*cmd.fill_in_missing_params(cmd_args), **cmd_kwds)
|
191
193
|
rescue => e
|
192
194
|
Rollbar.error(e)
|
193
|
-
status = 'failed'
|
194
195
|
raise
|
195
|
-
ensure
|
196
|
-
if defined?(Yabeda) && Yabeda.configured?
|
197
|
-
tags = { command: cmd.class.name, status: status, application: application }
|
198
|
-
dur = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
199
|
-
|
200
|
-
Yabeda.etna.last_command_completion.set(tags, Time.now.to_i)
|
201
|
-
Yabeda.etna.command_runtime.measure(tags, dur)
|
202
|
-
|
203
|
-
write_job_metrics("run_command.#{cmd.class.name}")
|
204
|
-
end
|
205
196
|
end
|
206
197
|
end
|
207
198
|
end
|
data/lib/etna/auth.rb
CHANGED
@@ -18,7 +18,7 @@ module Etna
|
|
18
18
|
if [ approve_noauth(request), approve_hmac(request), approve_user(request) ].all?{|approved| !approved}
|
19
19
|
return fail_or_redirect(request)
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
@app.call(request.env)
|
23
23
|
end
|
24
24
|
|
@@ -97,7 +97,7 @@ module Etna
|
|
97
97
|
|
98
98
|
return payload unless route
|
99
99
|
|
100
|
-
begin
|
100
|
+
begin
|
101
101
|
permissions = permissions(payload)
|
102
102
|
|
103
103
|
janus.resource_projects(token).each do |resource_project|
|
data/lib/etna/client.rb
CHANGED
@@ -4,10 +4,17 @@ require 'rack/utils'
|
|
4
4
|
|
5
5
|
module Etna
|
6
6
|
class Client
|
7
|
-
|
7
|
+
|
8
|
+
def initialize(host, token, routes_available: true, ignore_ssl: false, max_retries: 10, backoff_time: 15, logger: nil)
|
8
9
|
@host = host.sub(%r!/$!, '')
|
9
10
|
@token = token
|
10
11
|
@ignore_ssl = ignore_ssl
|
12
|
+
@max_retries = max_retries
|
13
|
+
@backoff_time = backoff_time
|
14
|
+
|
15
|
+
default_logger = ::Etna::Logger.new('/dev/stdout', 0, 1048576)
|
16
|
+
default_logger.level = ::Logger::WARN
|
17
|
+
@logger = logger || default_logger
|
11
18
|
|
12
19
|
if routes_available
|
13
20
|
set_routes
|
@@ -55,7 +62,7 @@ module Etna
|
|
55
62
|
uri = request_uri(endpoint)
|
56
63
|
multipart = Net::HTTP::Post::Multipart.new uri.request_uri, content
|
57
64
|
multipart.add_field('Authorization', "Etna #{@token}")
|
58
|
-
|
65
|
+
retrier.retry_request(uri, multipart, &block)
|
59
66
|
end
|
60
67
|
|
61
68
|
def post(endpoint, params = {}, &block)
|
@@ -80,9 +87,12 @@ module Etna
|
|
80
87
|
|
81
88
|
private
|
82
89
|
|
90
|
+
def retrier
|
91
|
+
@retrier ||= Retrier.new(ignore_ssl: @ignore_ssl, max_retries: @max_retries, backoff_time: @backoff_time, logger: @logger)
|
92
|
+
end
|
93
|
+
|
83
94
|
def set_routes
|
84
95
|
response = options('/')
|
85
|
-
status_check!(response)
|
86
96
|
@routes = JSON.parse(response.body, symbolize_names: true)
|
87
97
|
end
|
88
98
|
|
@@ -120,7 +130,7 @@ module Etna
|
|
120
130
|
uri = request_uri(endpoint)
|
121
131
|
req = type.new(uri.request_uri, request_headers)
|
122
132
|
req.body = params.to_json
|
123
|
-
|
133
|
+
retrier.retry_request(uri, req, &block)
|
124
134
|
end
|
125
135
|
|
126
136
|
def query_request(type, endpoint, params = {}, &block)
|
@@ -132,7 +142,7 @@ module Etna
|
|
132
142
|
uri.query = URI.encode_www_form(params)
|
133
143
|
end
|
134
144
|
req = type.new(uri.request_uri, request_headers)
|
135
|
-
|
145
|
+
retrier.retry_request(uri, req, &block)
|
136
146
|
end
|
137
147
|
|
138
148
|
def request_uri(endpoint)
|
@@ -140,6 +150,8 @@ module Etna
|
|
140
150
|
end
|
141
151
|
|
142
152
|
def request_headers
|
153
|
+
refresh_token
|
154
|
+
|
143
155
|
{
|
144
156
|
'Content-Type' => 'application/json',
|
145
157
|
'Accept' => 'application/json, text/*',
|
@@ -149,46 +161,234 @@ module Etna
|
|
149
161
|
)
|
150
162
|
end
|
151
163
|
|
152
|
-
def
|
153
|
-
|
154
|
-
if status >= 400
|
155
|
-
msg = response.content_type == 'application/json' ?
|
156
|
-
json_error(response.body) :
|
157
|
-
response.body
|
158
|
-
raise Etna::Error.new(msg, status)
|
159
|
-
end
|
164
|
+
def refresh_token
|
165
|
+
@token = TokenRefresher.new(@host, @token, @logger).active_token
|
160
166
|
end
|
161
167
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
+
class TokenRefresher
|
169
|
+
def initialize(host, token, logger)
|
170
|
+
@token = token
|
171
|
+
@host = host
|
172
|
+
@logger = logger
|
173
|
+
end
|
174
|
+
|
175
|
+
def active_token
|
176
|
+
token_will_expire? ?
|
177
|
+
refresh_token :
|
178
|
+
@token
|
179
|
+
end
|
180
|
+
|
181
|
+
private
|
182
|
+
|
183
|
+
def token_expired?
|
184
|
+
# Has the token already expired?
|
185
|
+
token_will_expire?(0)
|
186
|
+
end
|
187
|
+
|
188
|
+
def token_will_expire?(offset=3000)
|
189
|
+
return false if @token.nil?
|
190
|
+
|
191
|
+
# Will the user's token expire in the given amount of time?
|
192
|
+
payload = @token.split('.')[1]
|
193
|
+
return false if payload.nil?
|
194
|
+
|
195
|
+
epoch_seconds = JSON.parse(Base64.urlsafe_decode64(payload))["exp"]
|
196
|
+
|
197
|
+
return false if epoch_seconds.nil?
|
198
|
+
|
199
|
+
expiration = DateTime.strptime(epoch_seconds.to_s, "%s").to_time
|
200
|
+
expiration <= DateTime.now.new_offset.to_time + offset
|
201
|
+
end
|
202
|
+
|
203
|
+
def refresh_token
|
204
|
+
@logger.debug("Requesting a refreshed token.")
|
205
|
+
uri = refresh_uri
|
206
|
+
req = Net::HTTP::Post.new(uri.request_uri, request_headers)
|
207
|
+
retrier.retry_request(uri, req).body
|
208
|
+
end
|
209
|
+
|
210
|
+
def refresh_uri
|
211
|
+
URI("#{janus_host}#{refresh_endpoint}")
|
212
|
+
end
|
213
|
+
|
214
|
+
def janus_host
|
215
|
+
@host.gsub(/(metis|magma|timur|polyphemus|janus|gnomon)/, "janus")
|
216
|
+
end
|
217
|
+
|
218
|
+
def refresh_endpoint
|
219
|
+
"/api/tokens/generate"
|
220
|
+
end
|
221
|
+
|
222
|
+
def request_headers
|
223
|
+
{
|
224
|
+
'Content-Type' => 'application/json',
|
225
|
+
'Accept' => 'application/json, text/*',
|
226
|
+
'Authorization' => "Etna #{@token}"
|
227
|
+
}
|
228
|
+
end
|
229
|
+
|
230
|
+
def retrier
|
231
|
+
@retrier ||= Retrier.new(max_retries: 5, backoff_time: 10, logger: @logger)
|
168
232
|
end
|
169
233
|
end
|
170
234
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
235
|
+
class Retrier
|
236
|
+
# Ideally the retry code would be centralized with metis_client ...
|
237
|
+
# unsure what would be the best approach to do that, at this moment.
|
238
|
+
def initialize(ignore_ssl: false, max_retries: 10, backoff_time: 15, logger:)
|
239
|
+
@max_retries = max_retries
|
240
|
+
@ignore_ssl = ignore_ssl
|
241
|
+
@backoff_time = backoff_time
|
242
|
+
@logger = logger
|
243
|
+
end
|
244
|
+
|
245
|
+
def retry_request(uri, req, retries: 0, &block)
|
246
|
+
retries += 1
|
247
|
+
|
248
|
+
begin
|
249
|
+
@logger.debug("\rWaiting for #{uri.host} restart"+"."*retries+"\x1b[0K")
|
250
|
+
|
251
|
+
sleep @backoff_time * retries
|
252
|
+
end if retries > 1
|
253
|
+
|
254
|
+
if retries < @max_retries
|
255
|
+
begin
|
256
|
+
if block_given?
|
257
|
+
request(uri, req) do |block_response|
|
258
|
+
if net_exceptions.include?(block_response.class)
|
259
|
+
@logger.debug("Received #{block_response.class.name}, retrying")
|
260
|
+
retry_request(uri, req, retries: retries, &block)
|
261
|
+
elsif block_response.is_a?(OpenSSL::SSL::SSLError)
|
262
|
+
@logger.debug("SSL error, retrying")
|
263
|
+
retry_request(uri, req, retries: retries, &block)
|
264
|
+
else
|
265
|
+
status_check!(block_response)
|
266
|
+
yield block_response
|
267
|
+
end
|
268
|
+
end
|
269
|
+
else
|
270
|
+
response = request(uri, req)
|
271
|
+
end
|
272
|
+
rescue OpenSSL::SSL::SSLError => e
|
273
|
+
if e.message =~ /write client hello/
|
274
|
+
@logger.debug("SSL error, retrying")
|
275
|
+
return retry_request(uri, req, retries: retries)
|
276
|
+
end
|
277
|
+
raise e
|
278
|
+
rescue *net_exceptions => e
|
279
|
+
@logger.debug("Received #{e.class.name}, retrying")
|
280
|
+
return retry_request(uri, req, retries: retries)
|
281
|
+
end
|
282
|
+
|
283
|
+
begin
|
284
|
+
retry_codes = ['503', '502', '504', '408']
|
285
|
+
if retry_codes.include?(response.code)
|
286
|
+
@logger.debug("Received response with code #{response.code}, retrying")
|
287
|
+
return retry_request(uri, req, retries: retries)
|
288
|
+
elsif response.code == '500' && response.body.start_with?("Puma caught")
|
289
|
+
@logger.debug("Received 500 Puma error #{response.body.split("\n").first}, retrying")
|
290
|
+
return retry_request(uri, req, retries: retries)
|
291
|
+
end
|
292
|
+
|
178
293
|
status_check!(response)
|
179
|
-
|
294
|
+
return response
|
295
|
+
end unless block_given?
|
296
|
+
end
|
297
|
+
|
298
|
+
raise ::Etna::Error, "Could not contact server, giving up" unless block_given?
|
299
|
+
end
|
300
|
+
|
301
|
+
private
|
302
|
+
|
303
|
+
def request(uri, data)
|
304
|
+
if block_given?
|
305
|
+
verify_mode = @ignore_ssl ?
|
306
|
+
OpenSSL::SSL::VERIFY_NONE :
|
307
|
+
OpenSSL::SSL::VERIFY_PEER
|
308
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: true, verify_mode: verify_mode, read_timeout: 300) do |http|
|
309
|
+
http.request(data) do |response|
|
310
|
+
api_error_check!(response)
|
311
|
+
yield response
|
312
|
+
end
|
313
|
+
end
|
314
|
+
else
|
315
|
+
verify_mode = @ignore_ssl ?
|
316
|
+
OpenSSL::SSL::VERIFY_NONE :
|
317
|
+
OpenSSL::SSL::VERIFY_PEER
|
318
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: true, verify_mode: verify_mode, read_timeout: 300) do |http|
|
319
|
+
response = http.request(data)
|
320
|
+
api_error_check!(response)
|
321
|
+
return response
|
180
322
|
end
|
181
323
|
end
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
324
|
+
end
|
325
|
+
|
326
|
+
def api_error_check!(response)
|
327
|
+
status = response.code.to_i
|
328
|
+
raise_status_error(response) if 400 <= status && status < 500
|
329
|
+
end
|
330
|
+
|
331
|
+
def status_check!(response)
|
332
|
+
status = response.code.to_i
|
333
|
+
raise_status_error(response) if status >= 400
|
334
|
+
end
|
335
|
+
|
336
|
+
def raise_status_error(response)
|
337
|
+
msg = response.content_type == 'application/json' ?
|
338
|
+
json_error(response.body) :
|
339
|
+
response.body
|
340
|
+
raise Etna::Error.new(msg, response.code.to_i)
|
341
|
+
end
|
342
|
+
|
343
|
+
def json_error(body)
|
344
|
+
msg = JSON.parse(body, symbolize_names: true)
|
345
|
+
if (msg.has_key?(:errors) && msg[:errors].is_a?(Array))
|
346
|
+
return JSON.generate(msg[:errors])
|
347
|
+
elsif msg.has_key?(:error)
|
348
|
+
return JSON.generate(msg[:error])
|
190
349
|
end
|
191
350
|
end
|
351
|
+
|
352
|
+
def net_exceptions
|
353
|
+
retry_exceptions = [
|
354
|
+
Errno::ECONNREFUSED,
|
355
|
+
Errno::ECONNRESET,
|
356
|
+
Errno::ENETRESET,
|
357
|
+
Errno::EPIPE,
|
358
|
+
Errno::ECONNABORTED,
|
359
|
+
Errno::EHOSTDOWN,
|
360
|
+
Errno::EHOSTUNREACH,
|
361
|
+
Errno::EINVAL,
|
362
|
+
Errno::ETIMEDOUT,
|
363
|
+
Net::ReadTimeout,
|
364
|
+
Net::HTTPFatalError,
|
365
|
+
Net::HTTPBadResponse,
|
366
|
+
Net::HTTPHeaderSyntaxError,
|
367
|
+
Net::ProtocolError,
|
368
|
+
Net::HTTPRequestTimeOut,
|
369
|
+
Net::HTTPGatewayTimeOut,
|
370
|
+
Net::HTTPBadRequest,
|
371
|
+
Net::HTTPBadGateway,
|
372
|
+
Net::HTTPError,
|
373
|
+
Net::HTTPInternalServerError,
|
374
|
+
Net::HTTPRetriableError,
|
375
|
+
Net::HTTPServerError,
|
376
|
+
Net::HTTPServiceUnavailable,
|
377
|
+
Net::HTTPUnprocessableEntity,
|
378
|
+
Net::OpenTimeout,
|
379
|
+
IOError,
|
380
|
+
EOFError,
|
381
|
+
Timeout::Error
|
382
|
+
]
|
383
|
+
|
384
|
+
begin
|
385
|
+
retry_exceptions << Net::HTTPRequestTimeout
|
386
|
+
retry_exceptions << Net::HTTPGatewayTimeout
|
387
|
+
retry_exceptions << Net::WriteTimeout
|
388
|
+
end if RUBY_VERSION > "2.5.8"
|
389
|
+
|
390
|
+
retry_exceptions
|
391
|
+
end
|
192
392
|
end
|
193
393
|
end
|
194
394
|
end
|
@@ -6,7 +6,7 @@ module Etna
|
|
6
6
|
module Clients
|
7
7
|
class BaseClient
|
8
8
|
attr_reader :host, :token, :ignore_ssl
|
9
|
-
def initialize(host:, token:, ignore_ssl: false)
|
9
|
+
def initialize(host:, token:, ignore_ssl: false, logger: nil)
|
10
10
|
raise "#{self.class.name} client configuration is missing host." unless host
|
11
11
|
|
12
12
|
@token = token
|
@@ -16,7 +16,8 @@ module Etna
|
|
16
16
|
host,
|
17
17
|
token,
|
18
18
|
routes_available: false,
|
19
|
-
ignore_ssl: ignore_ssl
|
19
|
+
ignore_ssl: ignore_ssl,
|
20
|
+
logger: logger)
|
20
21
|
@host = host
|
21
22
|
@ignore_ssl = ignore_ssl
|
22
23
|
end
|
@@ -41,6 +41,7 @@ module Etna
|
|
41
41
|
|
42
42
|
begin
|
43
43
|
if (error = errors.pop(true))
|
44
|
+
logger&.error(error)
|
44
45
|
raise error
|
45
46
|
end
|
46
47
|
rescue ThreadError
|
@@ -51,6 +52,7 @@ module Etna
|
|
51
52
|
begin
|
52
53
|
materialize_record(dest, template, document)
|
53
54
|
rescue => e
|
55
|
+
logger&.error(e)
|
54
56
|
errors << e
|
55
57
|
ensure
|
56
58
|
semaphore.release
|
@@ -62,6 +64,7 @@ module Etna
|
|
62
64
|
|
63
65
|
begin
|
64
66
|
if (error = errors.pop(true))
|
67
|
+
logger&.error(error)
|
65
68
|
raise error
|
66
69
|
end
|
67
70
|
rescue ThreadError
|
@@ -105,6 +108,7 @@ module Etna
|
|
105
108
|
end
|
106
109
|
|
107
110
|
dest_file = File.join(dest_dir, metadata_file_name(record_name: record[template.identifier], record_model_name: template.name, ext: "_#{attr_name}_#{idx}#{File.extname(filename)}"))
|
111
|
+
filesystem.mkdir_p(File.dirname(dest_file))
|
108
112
|
sync_metis_data_workflow.copy_file(dest: dest_file, url: url, stub: stub_files)
|
109
113
|
record_to_serialize[attr_name] << {file: dest_file, original_filename: filename}
|
110
114
|
end
|
@@ -10,10 +10,10 @@ module Etna
|
|
10
10
|
module Clients
|
11
11
|
class Metis < Etna::Clients::BaseClient
|
12
12
|
|
13
|
-
def initialize(host:, token:, ignore_ssl: false)
|
13
|
+
def initialize(host:, token:, ignore_ssl: false, logger: nil)
|
14
14
|
raise 'Metis client configuration is missing host.' unless host
|
15
15
|
raise 'Metis client configuration is missing token.' unless token
|
16
|
-
@etna_client = ::Etna::Client.new(host, token, ignore_ssl: ignore_ssl)
|
16
|
+
@etna_client = ::Etna::Client.new(host, token, ignore_ssl: ignore_ssl, logger: logger)
|
17
17
|
|
18
18
|
@token = token
|
19
19
|
end
|
@@ -22,36 +22,56 @@ module Etna
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def copy_file(dest:, url:, stub: false)
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
25
|
+
url_match = DOWNLOAD_REGEX.match(url)
|
26
|
+
|
27
|
+
if filesystem.instance_of?(Etna::Filesystem::Metis) && !url_match.nil?
|
28
|
+
bucket_name = url_match[:bucket_name]
|
29
|
+
project_name = url_match[:project_name]
|
30
|
+
file_path = url_match[:file_path]
|
31
|
+
|
32
|
+
# ensure target parent directory exists
|
33
|
+
metis_client.ensure_parent_folder_exists(
|
34
|
+
project_name: filesystem.project_name,
|
35
|
+
bucket_name: filesystem.bucket_name,
|
36
|
+
path: dest
|
37
|
+
)
|
38
|
+
|
39
|
+
metis_client.copy_files(
|
40
|
+
Etna::Clients::Metis::CopyFilesRequest.new(
|
41
|
+
project_name: project_name,
|
42
|
+
revisions: [
|
43
|
+
Etna::Clients::Metis::CopyRevision.new(
|
44
|
+
source: "metis://#{project_name}/#{bucket_name}/#{file_path}",
|
45
|
+
dest: "metis://#{filesystem.project_name}/#{filesystem.bucket_name}/#{dest}",
|
46
|
+
)
|
47
|
+
]
|
48
|
+
)
|
49
|
+
)
|
50
|
+
|
51
|
+
return
|
52
|
+
end
|
45
53
|
|
46
54
|
metadata = metis_client.file_metadata(url)
|
47
55
|
size = metadata[:size]
|
48
56
|
|
57
|
+
begin
|
58
|
+
if filesystem.exist?(dest) && filesystem.stat(dest).size == size
|
59
|
+
logger&.info "Already downloaded #{dest}"
|
60
|
+
return
|
61
|
+
end
|
62
|
+
rescue Etna::Filesystem::Error => e
|
63
|
+
unless e.message =~ /stat not supported/
|
64
|
+
raise e
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
49
68
|
tmp_file = dest
|
50
69
|
upload_timings = []
|
51
70
|
upload_amount = 0
|
52
71
|
last_rate = 0.00001
|
53
72
|
remaining = size
|
54
73
|
|
74
|
+
logger&.info "Downloading #{dest} - #{Etna::Formatting.as_size(size)}"
|
55
75
|
filesystem.with_writeable(tmp_file, "w", size_hint: size) do |io|
|
56
76
|
if stub
|
57
77
|
io.write("(stub) #{size} bytes")
|
@@ -78,7 +98,7 @@ module Etna
|
|
78
98
|
rate = upload_amount / (end_time - start_time)
|
79
99
|
|
80
100
|
if rate / last_rate > 1.3 || rate / last_rate < 0.7
|
81
|
-
logger&.
|
101
|
+
logger&.debug("Uploading #{Etna::Formatting.as_size(rate)} per second, #{Etna::Formatting.as_size(remaining)} remaining")
|
82
102
|
|
83
103
|
if rate == 0
|
84
104
|
last_rate = 0.0001
|
@@ -87,7 +107,6 @@ module Etna
|
|
87
107
|
end
|
88
108
|
end
|
89
109
|
end
|
90
|
-
|
91
110
|
end
|
92
111
|
end
|
93
112
|
end
|