etna 0.1.51 → 0.2.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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/bin/etna +2 -2
  3. data/etna.completion +75 -50
  4. data/lib/commands.rb +10 -8
  5. data/lib/etna/application.rb +33 -0
  6. data/lib/etna/auth.rb +5 -2
  7. data/lib/etna/client.rb +7 -12
  8. data/lib/etna/clients/base_client.rb +10 -2
  9. data/lib/etna/clients/gnomon/client.rb +20 -0
  10. data/lib/etna/clients/gnomon/models.rb +33 -0
  11. data/lib/etna/clients/gnomon.rb +2 -0
  12. data/lib/etna/clients/janus/client.rb +26 -0
  13. data/lib/etna/clients/janus/models.rb +16 -0
  14. data/lib/etna/clients/janus/workflows/generate_token_workflow.rb +1 -1
  15. data/lib/etna/clients/magma/client.rb +126 -6
  16. data/lib/etna/clients/magma/models.rb +64 -22
  17. data/lib/etna/clients/magma/workflows/model_synchronization_workflow.rb +2 -4
  18. data/lib/etna/clients/metis/client.rb +66 -2
  19. data/lib/etna/clients/metis/models.rb +138 -85
  20. data/lib/etna/clients/metis/workflows/ingest_metis_data_workflow.rb +15 -5
  21. data/lib/etna/clients/metis/workflows/metis_upload_workflow.rb +1 -0
  22. data/lib/etna/clients/models.rb +19 -0
  23. data/lib/etna/clients/polyphemus/client.rb +96 -0
  24. data/lib/etna/clients.rb +2 -0
  25. data/lib/etna/controller.rb +31 -6
  26. data/lib/etna/cross_origin.rb +1 -1
  27. data/lib/etna/cwl.rb +1 -1
  28. data/lib/etna/errors.rb +8 -0
  29. data/lib/etna/filesystem.rb +5 -170
  30. data/lib/etna/hmac.rb +4 -4
  31. data/lib/etna/instrumentation.rb +6 -6
  32. data/lib/etna/redirect.rb +1 -1
  33. data/lib/etna/remote.rb +18 -2
  34. data/lib/etna/route.rb +3 -0
  35. data/lib/etna/spec/event_log.rb +16 -0
  36. data/lib/etna/spec/vcr.rb +3 -3
  37. data/lib/etna/test_auth.rb +1 -1
  38. data/lib/etna/user.rb +1 -5
  39. data/lib/helpers.rb +8 -0
  40. metadata +22 -5
@@ -158,6 +158,21 @@ module Etna
158
158
  end
159
159
  end
160
160
 
161
+ class TailBucketRequest < Struct.new(:project_name, :bucket_name, :type, :folder_id, :batch_start, :batch_end, keyword_init: true)
162
+ include JsonSerializableStruct
163
+
164
+ def initialize(**params)
165
+ super({}.update(params))
166
+ end
167
+
168
+ def to_h
169
+ # The :project_name comes in from Polyphemus as a symbol value,
170
+ # we need to make sure it's a string because it's going
171
+ # in the URL.
172
+ super().compact.transform_values(&:to_s)
173
+ end
174
+ end
175
+
161
176
  class FindRequest < Struct.new(:project_name, :bucket_name, :limit, :offset, :params, :hide_paths, keyword_init: true)
162
177
  include JsonSerializableStruct
163
178
 
@@ -219,25 +234,99 @@ module Etna
219
234
  end
220
235
  end
221
236
 
222
- class FoldersResponse
223
- attr_reader :raw
237
+ class TailNode < Etna::Clients::Response
238
+ def initialize(tail, raw = {})
239
+ super(raw)
240
+ @tail = tail
241
+ end
224
242
 
225
- def initialize(raw = {})
226
- @raw = raw
243
+ def parent?
244
+ @raw[:type] == 'parent'
227
245
  end
228
246
 
229
- def folders
230
- Folders.new(raw[:folders])
247
+ def folder?
248
+ @raw[:type] == 'folder'
249
+ end
250
+
251
+ def file?
252
+ @raw[:type] == 'file'
253
+ end
254
+
255
+ params :id, :node_name, :parent_id, :file_hash, :updated_at
256
+
257
+ def as_file
258
+ {
259
+ file_name: node_name,
260
+ file_hash: file_hash,
261
+ updated_at: updated_at,
262
+ file_path: file_path,
263
+ folder_id: parent_id,
264
+ project_name: @tail.project_name,
265
+ bucket_name: @tail.bucket_name,
266
+ }
267
+ end
268
+
269
+ def parent_path
270
+ return nil if !parent_id
271
+
272
+ if !@tail.paths[parent_id]
273
+ parent = @tail.parents[parent_id]
274
+ @tail.paths[parent_id] = parent.file_path
275
+ end
276
+
277
+ return @tail.paths[parent_id]
278
+ end
279
+
280
+ def file_path
281
+ [ parent_path, node_name ].compact.join('/')
231
282
  end
232
283
  end
233
284
 
234
- class FilesResponse
235
- attr_reader :raw
285
+ class TailResponse < Etna::Clients::Response
286
+ attr_reader :project_name, :bucket_name, :paths, :parents
287
+
288
+
289
+ def initialize(project_name, bucket_name, raw = {})
290
+ super(raw)
291
+
292
+ @paths = {}
293
+
294
+ @parents = {}
295
+
296
+ @project_name = project_name
297
+ @bucket_name = bucket_name
298
+
299
+ @nodes = []
300
+ raw.each do |raw_node|
301
+ node = TailNode.new(self, raw_node)
302
+ if node.parent?
303
+ @parents[node.id] = node
304
+ else
305
+ @nodes.push(node)
306
+ end
307
+ end
308
+ end
309
+
310
+ def folders
311
+ @nodes.filter_map do |node|
312
+ Folder.new(node.as_folder) if node.folder?
313
+ end
314
+ end
315
+
316
+ def files
317
+ @nodes.filter_map do |node|
318
+ File.new(node.as_file) if node.file?
319
+ end
320
+ end
321
+ end
236
322
 
237
- def initialize(raw = {})
238
- @raw = raw
323
+ class FoldersResponse < Etna::Clients::Response
324
+ def folders
325
+ Folders.new(raw[:folders])
239
326
  end
327
+ end
240
328
 
329
+ class FilesResponse < Etna::Clients::Response
241
330
  def files
242
331
  Files.new(raw[:files])
243
332
  end
@@ -253,37 +342,19 @@ module Etna
253
342
  end
254
343
  end
255
344
 
256
- class Files
257
- attr_reader :raw
258
-
259
- def initialize(raw = {})
260
- @raw = raw
261
- end
262
-
345
+ class Files < Etna::Clients::Response
263
346
  def all
264
347
  raw.map { |file| File.new(file) }
265
348
  end
266
349
  end
267
350
 
268
- class Folders
269
- attr_reader :raw
270
-
271
- def initialize(raw = {})
272
- @raw = raw
273
- end
274
-
351
+ class Folders < Etna::Clients::Response
275
352
  def all
276
353
  raw.map { |folder| Folder.new(folder) }
277
354
  end
278
355
  end
279
356
 
280
- class File
281
- attr_reader :raw
282
-
283
- def initialize(raw = {})
284
- @raw = raw
285
- end
286
-
357
+ class File < Etna::Clients::Response
287
358
  def with_containing_folder(folder)
288
359
  folder_path = folder.is_a?(Folder) ? folder.folder_path : folder
289
360
  File.new({}.update(self.raw).update({
@@ -291,17 +362,7 @@ module Etna
291
362
  }))
292
363
  end
293
364
 
294
- def file_path
295
- raw[:file_path]
296
- end
297
-
298
- def project_name
299
- raw[:project_name]
300
- end
301
-
302
- def bucket_name
303
- raw[:bucket_name]
304
- end
365
+ params :file_path, :project_name, :bucket_name, :file_name, :size, :file_hash, :folder_id
305
366
 
306
367
  def download_path
307
368
  raw[:download_url].nil? ?
@@ -313,65 +374,40 @@ module Etna
313
374
  raw[:download_url] || ''
314
375
  end
315
376
 
316
- def file_name
317
- raw[:file_name]
318
- end
319
-
320
377
  def updated_at
321
378
  time = raw[:updated_at]
322
379
  time.nil? ? nil : Time.parse(time)
323
380
  end
324
381
 
325
- def size
326
- raw[:size]
382
+ def as_magma_file_attribute
383
+ {
384
+ path: as_metis_url,
385
+ original_filename: file_name || ::File.basename(file_path)
386
+ }
327
387
  end
328
388
 
329
- def file_hash
330
- raw[:file_hash]
331
- end
332
-
333
- def folder_id
334
- raw[:folder_id]
389
+ def as_metis_url
390
+ "metis://#{project_name}/#{bucket_name}/#{file_path}"
335
391
  end
336
392
  end
337
393
 
338
- class Folder
339
- attr_reader :raw
340
-
341
- def initialize(raw = {})
342
- @raw = raw
343
- end
344
-
345
- def folder_path
346
- raw[:folder_path]
347
- end
348
-
349
- def folder_name
350
- raw[:folder_name]
351
- end
352
-
353
- def bucket_name
354
- raw[:bucket_name]
355
- end
356
-
357
- def project_name
358
- raw[:project_name]
359
- end
394
+ class Folder < Etna::Clients::Response
395
+ params :folder_path, :folder_name, :bucket_name, :project_name, :id
360
396
 
361
397
  def updated_at
362
398
  time = raw[:updated_at]
363
399
  time.nil? ? nil : Time.parse(time)
364
400
  end
365
-
366
- def id
367
- raw[:id]
368
- end
369
401
  end
370
402
 
371
403
  class AuthorizeUploadRequest < Struct.new(:project_name, :bucket_name, :file_path, keyword_init: true)
372
404
  include JsonSerializableStruct
373
405
  end
374
406
 
407
+ class AuthorizeDownloadRequest < Struct.new(:project_name, :bucket_name, :file_path, keyword_init: true)
408
+ include JsonSerializableStruct
409
+ end
410
+
375
411
  class UploadStartRequest < Struct.new(:file_size, :action, :metis_uid, :next_blob_size, :upload_path, :next_blob_hash, :reset, keyword_init: true)
376
412
  include JsonSerializableStruct
377
413
 
@@ -392,12 +428,13 @@ module Etna
392
428
  end
393
429
  end
394
430
 
395
- class UploadResponse
396
- attr_reader :raw
397
- def initialize(raw = {})
398
- @raw = raw
431
+ class DownloadResponse < Etna::Clients::Response
432
+ def download_url
433
+ raw['download_url']
399
434
  end
435
+ end
400
436
 
437
+ class UploadResponse < Etna::Clients::Response
401
438
  def current_byte_position
402
439
  raw['current_byte_position'].to_i
403
440
  end
@@ -419,6 +456,22 @@ module Etna
419
456
  START = UploadAction.new("start")
420
457
  BLOB = UploadAction.new("blob")
421
458
  end
459
+
460
+ class GetFileCountByProjectRequest < Struct.new(:project_names, keyword_init: true)
461
+ include JsonSerializableStruct
462
+
463
+ def initialize(**params)
464
+ super({}.update(params))
465
+ end
466
+ end
467
+
468
+ class GetByteCountByProjectRequest < Struct.new(:project_names, keyword_init: true)
469
+ include JsonSerializableStruct
470
+
471
+ def initialize(**params)
472
+ super({}.update(params))
473
+ end
474
+ end
422
475
  end
423
476
  end
424
- end
477
+ end
@@ -9,24 +9,34 @@ module Etna
9
9
  # Since we are doing manual triage of files,
10
10
  # do not automatically copy directory trees.
11
11
  # srcs must be a list of full paths to files.
12
- def copy_files(srcs, &block)
13
- srcs.each do |src|
12
+ def copy_files(files, &block)
13
+ files.each do |file|
14
+ if file.is_a?(Array)
15
+ src, dest = file
16
+ else
17
+ src = dest = file
18
+ end
19
+
14
20
  if !ingest_filesystem.exist?(src)
15
21
  logger&.warn("#{src} does not exist on source filesystem. Skipping.")
22
+ yield src, false if block_given?
16
23
  next
17
24
  end
18
25
 
19
26
  logger&.info("Copying file #{src} (#{Etna::Formatting.as_size(ingest_filesystem.stat(src).size)})")
20
27
 
21
- # For ingestion triage, just copy over the exact path + filename.
22
- copy_file(dest: src, src: src, &block)
28
+ begin
29
+ copy_file(dest: dest, src: src, &block)
30
+ rescue Exception => e
31
+ yield src, false if block_given?
32
+ end
23
33
  end
24
34
  end
25
35
 
26
36
  def copy_file(dest:, src:, &block)
27
37
  ingest_filesystem.with_readable(src, "r") do |io|
28
38
  metis_filesystem.do_streaming_upload(io, dest, ingest_filesystem.stat(src).size)
29
- yield src if block_given?
39
+ yield src, true if block_given?
30
40
  end
31
41
  end
32
42
  end
@@ -197,6 +197,7 @@ module Etna
197
197
  end
198
198
  end
199
199
  end
200
+ @last_bytes
200
201
  end
201
202
  end
202
203
  end
@@ -0,0 +1,19 @@
1
+ module Etna
2
+ module Clients
3
+ class Response
4
+ attr_reader :raw
5
+
6
+ def self.params(*params)
7
+ params.each do |param|
8
+ self.define_method param do
9
+ @raw[param]
10
+ end
11
+ end
12
+ end
13
+
14
+ def initialize(raw = {})
15
+ @raw = raw || {}
16
+ end
17
+ end
18
+ end
19
+ end
@@ -33,6 +33,102 @@ module Etna
33
33
  yield res
34
34
  end
35
35
  end
36
+
37
+ def get_config(project_name, config_id, version_number)
38
+ json = nil
39
+ @etna_client.post("/api/workflows/#{project_name}/configs/#{config_id}", version_number: version_number) do |res|
40
+ json = JSON.parse(res.body)
41
+ end
42
+ json
43
+ end
44
+
45
+ def get_runtime_config(project_name, config_id)
46
+ json = nil
47
+ @etna_client.get("/api/workflows/#{project_name}/runtime_configs/#{config_id}") do |res|
48
+ json = JSON.parse(res.body)
49
+ end
50
+ json
51
+ end
52
+
53
+ def update_run(project_name, run_id, updates)
54
+ payload = {
55
+ run_id: run_id,
56
+ workflow_name: updates[:workflow_name],
57
+ name: updates[:name],
58
+ config_id: updates[:config_id],
59
+ version_number: updates[:version_number],
60
+ state: updates[:state],
61
+ orchestrator_metadata: updates[:orchestrator_metadata],
62
+ output: updates[:output],
63
+ append_output: updates[:append_output]
64
+ }.compact
65
+
66
+ json = nil
67
+ @etna_client.post("/api/workflows/#{project_name}/run/update/#{run_id}", payload) do |res|
68
+ json = JSON.parse(res.body)
69
+ end
70
+ json
71
+ end
72
+
73
+ def get_run(project_name, run_id)
74
+ json = nil
75
+ @etna_client.get("/api/workflows/#{project_name}/run/#{run_id}") do |res|
76
+ json = JSON.parse(res.body)
77
+ end
78
+ json
79
+ end
80
+
81
+ def get_previous_state(project_name, config_id, state: [], collect: false)
82
+ json = nil
83
+ @etna_client.post("/api/workflows/#{project_name}/run/previous/#{config_id}",
84
+ state: state,
85
+ collect: collect
86
+ ) do |res|
87
+ json = JSON.parse(res.body)
88
+ end
89
+ json
90
+ end
91
+
92
+ def update_runtime_config(project_name, config_id, updates)
93
+ payload = {
94
+ config_id: updates[:config_id],
95
+ runtime_config: updates[:runtime_config],
96
+ run_interval: updates[:run_interval],
97
+ disabled: updates[:disabled]
98
+ }.compact
99
+
100
+ json = nil
101
+ @etna_client.post("/api/workflows/#{project_name}/runtime_config/#{config_id}", payload) do |res|
102
+ json = JSON.parse(res.body)
103
+ end
104
+ json
105
+ end
106
+
107
+ def get_run_metadata(project_name, config_id)
108
+ json = nil
109
+ @etna_client.get("/api/workflows/#{project_name}/runtime_config/#{config_id}") do |res|
110
+ json = JSON.parse(res.body)
111
+ end
112
+ json
113
+ end
114
+
115
+ def log(project_name:, user:, event:, message:, payload:, signatory:, consolidate:, application:nil)
116
+ params = {
117
+ project_name: project_name,
118
+ user: user,
119
+ event: event,
120
+ message: message,
121
+ payload: payload,
122
+ consolidate: consolidate,
123
+ application: application,
124
+ signatory: signatory
125
+ }
126
+ json = nil
127
+ @etna_client.log_write(params) do |res|
128
+ json = JSON.parse(res.body)
129
+ end
130
+ json
131
+ end
36
132
  end
37
133
  end
38
134
  end
data/lib/etna/clients.rb CHANGED
@@ -1,4 +1,6 @@
1
+ require_relative 'clients/models'
1
2
  require_relative 'clients/magma'
3
+ require_relative 'clients/gnomon'
2
4
  require_relative 'clients/metis'
3
5
  require_relative 'clients/janus'
4
6
  require_relative 'clients/polyphemus'
@@ -16,10 +16,25 @@ module Etna
16
16
  @hmac = @request.env['etna.hmac']
17
17
  end
18
18
 
19
+ def application
20
+ Etna::Application.instance
21
+ end
22
+
19
23
  def log(line)
20
24
  @logger.warn(request_msg(line))
21
25
  end
22
26
 
27
+ def event_log(params)
28
+ begin
29
+ Etna::Application.instance.event_log(**{
30
+ project_name: @params[:project_name],
31
+ user: @user
32
+ }.compact.merge(params))
33
+ rescue Exception => e
34
+ log("event_log failed with #{e.backtrace} #{e.message}")
35
+ end
36
+ end
37
+
23
38
  def handle_error(e)
24
39
  case e
25
40
  when Etna::Error
@@ -72,7 +87,16 @@ module Etna
72
87
  @response.close
73
88
  else
74
89
  @response['Content-Type'] = content_type
75
- block.call(@response)
90
+ response = @response
91
+ # Rack::Response is not a full IO object in Rack 3. JSON.dump calls
92
+ # flush after writing, so adapt the non-hijack/test response path to
93
+ # the same small stream interface provided by a hijacked socket.
94
+ stream = Object.new
95
+ stream.define_singleton_method(:write) { |data| response.write(data) }
96
+ stream.define_singleton_method(:<<) { |data| write(data) }
97
+ stream.define_singleton_method(:flush) {}
98
+
99
+ block.call(stream)
76
100
  @response.finish
77
101
  end
78
102
  end
@@ -86,7 +110,7 @@ Subject: #{subject}
86
110
  #{content}
87
111
  MESSAGE_END
88
112
 
89
- unless @server.send(:application).test?
113
+ unless application.test?
90
114
  Net::SMTP.start('smtp.ucsf.edu') do |smtp|
91
115
  smtp.send_message message, 'noreply@janus', to_email
92
116
  end
@@ -108,7 +132,7 @@ MESSAGE_END
108
132
  def route_url(name, params={})
109
133
  path = route_path(name,params)
110
134
  return nil unless path
111
- @request.scheme + '://' + @request.host + path
135
+ @request.scheme + '://' + application.host + path
112
136
  end
113
137
 
114
138
  # methods for returning a view
@@ -133,8 +157,8 @@ MESSAGE_END
133
157
  end
134
158
 
135
159
  def config_hosts
136
- [:janus, :magma, :timur, :metis, :vulcan, :polyphemus, :gnomon].map do |host|
137
- [ :"#{host}_host", @server.send(:application).config(host)&.dig(:host) ]
160
+ [:janus, :magma, :timur, :metis, :vulcan, :polyphemus, :gnomon, :vesta].map do |host|
161
+ [ :"#{host}_host", application.config(host)&.dig(:host) ]
138
162
  end.to_h.compact
139
163
  end
140
164
 
@@ -162,8 +186,9 @@ MESSAGE_END
162
186
  self.class.name.sub("Kernel::", "").sub("Controller", "").downcase
163
187
  end
164
188
 
165
- def success(msg, content_type='text/plain')
189
+ def success(msg, content_type='text/plain', disposition='not given')
166
190
  @response['Content-Type'] = content_type
191
+ @response['Content-Disposition'] = disposition unless disposition=='not given'
167
192
  @response.write(msg)
168
193
  @response.finish
169
194
  end
@@ -27,7 +27,7 @@ module Etna
27
27
  end
28
28
 
29
29
  def origin_allowed?(request)
30
- subdomain(URI.parse(header(request, :origin)).host) == subdomain(request.host)
30
+ subdomain(URI.parse(header(request, :origin)).host) == subdomain(Etna::Application.instance.host)
31
31
  end
32
32
 
33
33
  def actual_response(request)
data/lib/etna/cwl.rb CHANGED
@@ -240,7 +240,7 @@ module Etna
240
240
  end
241
241
 
242
242
  PRIMITIVE_TYPE = EnumLoader.new("null", "boolean", "int", "long", "float", "double", "string")
243
- NOMINAL_TYPE = EnumLoader.new("File")
243
+ NOMINAL_TYPE = EnumLoader.new("File", "MetisFile", "MetisFolder", "MetisPath", "MetisCSVorTSV")
244
244
  end
245
245
 
246
246
  class OptionalLoader < Loader
data/lib/etna/errors.rb CHANGED
@@ -40,4 +40,12 @@ module Etna
40
40
  @level = Logger::ERROR
41
41
  end
42
42
  end
43
+
44
+ class TooManyRequests < Etna::Error
45
+ def initialize(msg = 'Too many requests', status = 429)
46
+ super
47
+ @level = Logger::ERROR
48
+ end
49
+ end
50
+
43
51
  end