etna 0.1.52 → 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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/bin/etna +2 -2
  3. data/lib/commands.rb +10 -8
  4. data/lib/etna/application.rb +33 -0
  5. data/lib/etna/auth.rb +5 -2
  6. data/lib/etna/client.rb +7 -12
  7. data/lib/etna/clients/base_client.rb +10 -2
  8. data/lib/etna/clients/gnomon/client.rb +20 -0
  9. data/lib/etna/clients/gnomon/models.rb +33 -0
  10. data/lib/etna/clients/gnomon.rb +2 -0
  11. data/lib/etna/clients/janus/client.rb +26 -0
  12. data/lib/etna/clients/janus/models.rb +16 -0
  13. data/lib/etna/clients/janus/workflows/generate_token_workflow.rb +1 -1
  14. data/lib/etna/clients/magma/client.rb +126 -6
  15. data/lib/etna/clients/magma/models.rb +64 -22
  16. data/lib/etna/clients/magma/workflows/model_synchronization_workflow.rb +2 -4
  17. data/lib/etna/clients/metis/client.rb +66 -2
  18. data/lib/etna/clients/metis/models.rb +138 -85
  19. data/lib/etna/clients/metis/workflows/ingest_metis_data_workflow.rb +15 -5
  20. data/lib/etna/clients/metis/workflows/metis_upload_workflow.rb +1 -0
  21. data/lib/etna/clients/models.rb +19 -0
  22. data/lib/etna/clients/polyphemus/client.rb +96 -0
  23. data/lib/etna/clients.rb +2 -0
  24. data/lib/etna/controller.rb +31 -6
  25. data/lib/etna/cross_origin.rb +1 -1
  26. data/lib/etna/cwl.rb +1 -1
  27. data/lib/etna/errors.rb +8 -0
  28. data/lib/etna/filesystem.rb +5 -170
  29. data/lib/etna/hmac.rb +4 -4
  30. data/lib/etna/instrumentation.rb +6 -6
  31. data/lib/etna/redirect.rb +1 -1
  32. data/lib/etna/remote.rb +18 -2
  33. data/lib/etna/route.rb +3 -0
  34. data/lib/etna/spec/event_log.rb +16 -0
  35. data/lib/etna/spec/vcr.rb +3 -3
  36. data/lib/etna/test_auth.rb +1 -1
  37. data/lib/etna/user.rb +0 -4
  38. data/lib/helpers.rb +8 -0
  39. metadata +8 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7d6b6988d247bdac2da6cc841a5016a1aa5148fa4c34ad7f241b0257ea840fb8
4
- data.tar.gz: 2958a486129aa27765188b608ba71498a8f01ab6f7955c5c0ff671dbb78c99ed
3
+ metadata.gz: ffd856f01fb2f4a29f162498157bf551dd3d1bb0ae2b65de37f4a7cd3ee094e1
4
+ data.tar.gz: 618661ad891fb65a7ff75e405f4b16b376bcbf76f2e4af35cecd861ba388738c
5
5
  SHA512:
6
- metadata.gz: ad99e8e9d09c3c51d88b92ea3c92cac9b7584476e4f1631b8123673be6d272e78e6640bfed1a9a0217868c6dda27bd6276415c5b974854dcba60f7df30f642a6
7
- data.tar.gz: 4b07429942fb8edb9cb131e64a3781c302dfcfcc5f74e151888fbb9e3bcddecd3f89b7b033e9ec41268e8bc5c5147e4b6884d063a391fbcdf4e0c4db91ab3976
6
+ metadata.gz: d257a17c567769bf16fee63592559b7ddde5ad26bd75e81ba0a1b287acae7e0295e80aa91c5eb27e3a3383829c66587434aa1bd7029a9dd7e29b0209801d038a
7
+ data.tar.gz: 4ffd6f992711147a6c6ed5b4abdf99833a82772a4e80436e3bf7e912b35186c162ef02720c2ea5f2393b0ba633058996343a11a08467f0e0cebd965df6f652bf
data/bin/etna CHANGED
@@ -9,8 +9,8 @@ require 'yaml'
9
9
  require_relative '../lib/etna'
10
10
  require_relative '../lib/commands'
11
11
 
12
- config = if File.exists?(EtnaApp.config_file_path)
13
- YAML.load(File.read(EtnaApp.config_file_path))
12
+ config = if ::File.exist?(EtnaApp.config_file_path)
13
+ YAML.load(::File.read(EtnaApp.config_file_path))
14
14
  else
15
15
  {
16
16
  production: {
data/lib/commands.rb CHANGED
@@ -328,15 +328,16 @@ class EtnaApp
328
328
  string_flags << '--target-model'
329
329
 
330
330
  def execute(project_name, target_model: 'project', file: "#{project_name}_models_#{target_model}_tree.csv")
331
- unless File.exists?(file)
332
- puts "File #{file} is being prepared from the #{project_name} project."
333
- puts "Copying models descending from #{target_model}..."
334
- prepare_template(file, project_name, target_model)
335
- puts
336
- puts "Done! You can start editing the file #{file} now"
337
- else
331
+ if ::File.exist?(file)
338
332
  puts "File #{file} already exists! Please remove or specify a different file name before running again."
333
+ return
339
334
  end
335
+
336
+ puts "File #{file} is being prepared from the #{project_name} project."
337
+ puts "Copying models descending from #{target_model}..."
338
+ prepare_template(file, project_name, target_model)
339
+ puts
340
+ puts "Done! You can start editing the file #{file} now"
340
341
  end
341
342
 
342
343
  def workflow
@@ -366,7 +367,7 @@ class EtnaApp
366
367
  def execute(project_name, target_model: 'project', file: "#{project_name}_models_#{target_model}_tree.csv")
367
368
  reset
368
369
 
369
- unless File.exists?(file)
370
+ unless ::File.exist?(file)
370
371
  puts "Could not find file #{file}"
371
372
  return
372
373
  end
@@ -696,4 +697,5 @@ class EtnaApp
696
697
  super
697
698
  end
698
699
  end
700
+
699
701
  end
@@ -144,6 +144,31 @@ module Etna::Application
144
144
  # the application logger is available globally
145
145
  attr_reader :logger
146
146
 
147
+ def event_log(project_name:, user:, event:, message:, payload: nil, application: nil, consolidate: nil)
148
+ begin
149
+ @polyphemus_client ||= Etna::Clients::Polyphemus.new(
150
+ token: nil,
151
+ host: config(:polyphemus)[:host],
152
+ routes_available: true,
153
+ logger: logger
154
+ )
155
+
156
+ @polyphemus_client.log(
157
+ project_name: project_name,
158
+ user: user.email,
159
+ event: event,
160
+ message: message,
161
+ payload: JSON.generate(payload),
162
+ consolidate: consolidate,
163
+ application: application,
164
+ signatory: self
165
+ )
166
+ rescue => e
167
+ logger.info("Could not log event #{event}, '#{message}'")
168
+ logger.log_error(e)
169
+ end
170
+ end
171
+
147
172
  def config(type, env = environment)
148
173
  return nil if @config.nil?
149
174
  return nil if @config[env].nil?
@@ -174,6 +199,14 @@ module Etna::Application
174
199
  ENV["APP_NAME"] || self.class.name.snake_case.split(/::/).last
175
200
  end
176
201
 
202
+ def host
203
+ URI(url).host
204
+ end
205
+
206
+ def url
207
+ config(id.to_sym)[:host]
208
+ end
209
+
177
210
  def find_descendents(klass)
178
211
  ObjectSpace.each_object(Class).select do |k|
179
212
  k < klass
data/lib/etna/auth.rb CHANGED
@@ -64,7 +64,10 @@ module Etna
64
64
  uri = URI(
65
65
  application.config(:auth_redirect).chomp('/') + '/login'
66
66
  )
67
- uri.query = URI.encode_www_form(refer: request.url)
67
+ refer = URI(
68
+ application.url + request.fullpath
69
+ )
70
+ uri.query = URI.encode_www_form(refer: refer)
68
71
  Etna::Redirect(request).to(uri.to_s)
69
72
  end
70
73
 
@@ -147,7 +150,7 @@ module Etna
147
150
  # Now expect the standard headers
148
151
  hmac_params = {
149
152
  method: request.request_method,
150
- host: request.host,
153
+ host: application.host,
151
154
  path: request.path,
152
155
 
153
156
  expiration: etna_param(request, :expiration),
data/lib/etna/client.rb CHANGED
@@ -4,7 +4,6 @@ require 'rack/utils'
4
4
 
5
5
  module Etna
6
6
  class Client
7
-
8
7
  def initialize(host, token, routes_available: true, ignore_ssl: false, max_retries: 10, backoff_time: 15, logger: nil)
9
8
  @host = host.sub(%r!/$!, '')
10
9
  @token = token
@@ -300,22 +299,18 @@ module Etna
300
299
 
301
300
  private
302
301
 
302
+ def verify_mode
303
+ @ignore_ssl ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
304
+ end
305
+
303
306
  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|
307
+ Net::HTTP.start(uri.host, uri.port, use_ssl: true, verify_mode: verify_mode, read_timeout: 300) do |http|
308
+ if block_given?
309
309
  http.request(data) do |response|
310
310
  api_error_check!(response)
311
311
  yield response
312
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|
313
+ else
319
314
  response = http.request(data)
320
315
  api_error_check!(response)
321
316
  return response
@@ -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, logger: nil)
9
+ def initialize(host:, token:, ignore_ssl: false, logger: nil, routes_available: false)
10
10
  raise "#{self.class.name} client configuration is missing host." unless host
11
11
 
12
12
  @token = token
@@ -15,7 +15,7 @@ module Etna
15
15
  @etna_client = ::Etna::Client.new(
16
16
  host,
17
17
  token,
18
- routes_available: false,
18
+ routes_available: routes_available,
19
19
  ignore_ssl: ignore_ssl,
20
20
  logger: logger)
21
21
  @host = host
@@ -27,6 +27,14 @@ module Etna
27
27
  token_will_expire?(0)
28
28
  end
29
29
 
30
+ def safe_parse(response)
31
+ if response['Content-Type'] == 'application/json'
32
+ return JSON.parse(response.body)
33
+ end
34
+
35
+ raise "Could not parse non-JSON response #{response.code} #{response.body} #{response.each_header.to_h}"
36
+ end
37
+
30
38
  def token_will_expire?(offset=3000)
31
39
  # offset in seconds
32
40
  # Will the user's token expire in the given amount of time?
@@ -0,0 +1,20 @@
1
+ require 'net/http/post/multipart'
2
+ require 'singleton'
3
+ require_relative '../base_client'
4
+ require_relative '../../client'
5
+ require_relative './models'
6
+
7
+ module Etna
8
+ module Clients
9
+ class Gnomon < Etna::Clients::BaseClient
10
+ def project_rules(project_name)
11
+ json = nil
12
+ @etna_client.get("/gnomon/#{project_name}/rules") do |res|
13
+ json = JSON.parse(res.body)
14
+ end
15
+
16
+ ProjectRulesResponse.new(json)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,33 @@
1
+ require_relative '../base_client'
2
+
3
+ # TODO: In the near future, I'd like to transition to specifying apis via SWAGGER and generating model stubs from the
4
+ # common definitions. For nowe I've written them out by hand here.
5
+ module Etna
6
+ module Clients
7
+ class Gnomon < Etna::Clients::BaseClient
8
+ class ProjectRulesResponse
9
+ attr_reader :raw
10
+
11
+ def initialize(raw = {})
12
+ @raw = raw
13
+ end
14
+
15
+ def rules
16
+ Rules.new(raw['rules'])
17
+ end
18
+ end
19
+
20
+ class Rules
21
+ attr_reader :raw
22
+
23
+ def initialize(raw = {})
24
+ @raw = raw
25
+ end
26
+
27
+ def [](model_name)
28
+ raw[model_name.to_s]
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,2 @@
1
+ require_relative './gnomon/client'
2
+ require_relative './gnomon/models'
@@ -40,6 +40,14 @@ module Etna
40
40
  end
41
41
  end
42
42
 
43
+ def update_project(update_project_request = UpdateProjectRequest.new)
44
+ @etna_client.post(
45
+ "/api/admin/#{update_project_request.project_name}/update",
46
+ update_project_request) do |res|
47
+ # Redirect, no response data
48
+ end
49
+ end
50
+
43
51
  def update_permission(update_permission_request = UpdatePermissionRequest.new)
44
52
  @etna_client.post(
45
53
  "/api/admin/#{update_permission_request.project_name}/update_permission",
@@ -74,6 +82,24 @@ module Etna
74
82
 
75
83
  response.body
76
84
  end
85
+
86
+ def get_project_stats(get_stats_request = GetStatsRequest.new)
87
+ query = ""
88
+ unless get_stats_request.project_names.nil?
89
+ query = "?"
90
+ query += get_stats_request.project_names.map do |name|
91
+ "projects[]=#{name}"
92
+ end.join('&')
93
+ end
94
+
95
+ json = nil
96
+
97
+ @etna_client.get("/api/stats/projects#{query}") do |res|
98
+ json = JSON.parse(res.body, symbolize_names: true)
99
+ end
100
+
101
+ json
102
+ end
77
103
  end
78
104
  end
79
105
  end
@@ -31,6 +31,14 @@ module Etna
31
31
  end
32
32
  end
33
33
 
34
+ class UpdateProjectRequest < Struct.new(:project_name, :resource, :requires_agreement, :cc_text, :contact_email, :project_name_full, keyword_init: true)
35
+ include JsonSerializableStruct
36
+
37
+ def initialize(**params)
38
+ super({}.update(params))
39
+ end
40
+ end
41
+
34
42
  class UpdatePermissionRequest < Struct.new(:project_name, :email, :role, :privileged, :affiliation, keyword_init: true)
35
43
  include JsonSerializableStruct
36
44
 
@@ -132,6 +140,14 @@ module Etna
132
140
  @raw
133
141
  end
134
142
  end
143
+
144
+ class GetStatsRequest < Struct.new(:project_names, keyword_init: true)
145
+ include JsonSerializableStruct
146
+
147
+ def initialize(**params)
148
+ super({}.update(params))
149
+ end
150
+ end
135
151
  end
136
152
  end
137
153
  end
@@ -25,7 +25,7 @@ module Etna
25
25
  until private_key_file
26
26
  puts "Location of private key file?"
27
27
  private_key_file = ::File.expand_path(STDIN.gets.chomp)
28
- unless File.exists?(private_key_file)
28
+ unless ::File.exist?(private_key_file)
29
29
  puts "No such file."
30
30
  private_key_file = nil
31
31
  end
@@ -56,9 +56,13 @@ module Etna
56
56
  # attribute_names: "all"
57
57
  # }
58
58
  def retrieve(retrieval_request = RetrievalRequest.new)
59
+ if retrieval_request.is_a?(Hash)
60
+ retrieval_request = RetrievalRequest.new(retrieval_request)
61
+ end
62
+
59
63
  json = nil
60
64
  @etna_client.post('/retrieve', retrieval_request) do |res|
61
- json = JSON.parse(res.body)
65
+ json = safe_parse(res)
62
66
  end
63
67
 
64
68
  RetrievalResponse.new(json)
@@ -69,7 +73,7 @@ module Etna
69
73
  def query(query_request = QueryRequest.new)
70
74
  json = nil
71
75
  @etna_client.post('/query', query_request) do |res|
72
- json = JSON.parse(res.body)
76
+ json = safe_parse(res)
73
77
  end
74
78
 
75
79
  QueryResponse.new(json)
@@ -77,17 +81,124 @@ module Etna
77
81
 
78
82
  def update(update_request = UpdateRequest.new)
79
83
  json = nil
84
+
80
85
  @etna_client.multipart_post('/update', update_request.encode_multipart_content) do |res|
81
- json = JSON.parse(res.body)
86
+ json = safe_parse(res)
82
87
  end
83
88
 
84
89
  UpdateResponse.new(json)
85
90
  end
86
91
 
87
- def update_json(update_request = UpdateRequest.new)
92
+ class Pager
93
+ def initialize(client, update_request, page_size)
94
+ @client = client
95
+
96
+ @update_request = update_request
97
+
98
+ @page_size = [ page_size || 2, 2 ].max
99
+ end
100
+
101
+ def response
102
+ page_count = 0
103
+ page = {}
104
+
105
+ results = UpdateResponse.new({"models" => {}})
106
+
107
+ flat_records.each do |flat_record|
108
+ page_count += 1 + flat_record[:tables].length
109
+
110
+ add_to_page( page, flat_record )
111
+
112
+ flat_record[:tables].each do |table_record|
113
+ add_to_page(page, table_record)
114
+ end
115
+
116
+ if page_count >= @page_size
117
+ results.update(update_part(page))
118
+ page = {}
119
+ page_count = 0
120
+ end
121
+ end
122
+
123
+ results.update(update_part(page)) if page_count > 0
124
+
125
+ return results
126
+ end
127
+
128
+ def revisions
129
+ @update_request.revisions
130
+ end
131
+
132
+ def flat_records
133
+ project = @client.retrieve(
134
+ project_name: @update_request.project_name,
135
+ hide_templates: false
136
+ )
137
+
138
+ flat_records = {}
139
+ revisions.each do |model_name, records|
140
+ records.each do |record_name, record|
141
+ flat = {
142
+ model_name: model_name,
143
+ record_name: record_name,
144
+ record: record
145
+ }
146
+ flat_name = "#{model_name}.#{record_name}"
147
+
148
+ # if the update sets a table attribute, attach table records to their parent so they are submitted together
149
+ flat_tables = []
150
+ record.each do |attribute_name, value|
151
+ att = project.models.model(model_name).template.attributes.attribute(attribute_name)
152
+ if att.attribute_type == 'table'
153
+ value.each do |table_entry_name|
154
+ table_record = revisions[ att.link_model_name ][table_entry_name]
155
+ flat_tables.push(
156
+ {
157
+ model_name: att.link_model_name,
158
+ record_name: table_entry_name,
159
+ record: table_record
160
+ }
161
+ )
162
+
163
+ # exclude the attached table record from the flattened list of records
164
+ flat_records[ "#{ att.link_model_name }.#{table_entry_name}" ] = nil
165
+ end
166
+ end
167
+ end
168
+ flat[:tables] = flat_tables
169
+ flat_records[ flat_name ] = flat unless flat_records.has_key?(flat_name)
170
+ end
171
+ end
172
+
173
+ return flat_records.values.compact.shuffle
174
+ end
175
+
176
+ def update_part(page)
177
+ @client.update_json(
178
+ UpdateRequest.new(
179
+ project_name: @update_request.project_name,
180
+ revisions: page,
181
+ dry_run: @update_request.dry_run,
182
+ autolink: @update_request.autolink
183
+ )
184
+ )
185
+ end
186
+
187
+ def add_to_page(page, entry)
188
+ page[ entry[:model_name] ] ||= {}
189
+ page[ entry[:model_name] ][ entry[:record_name] ] = entry[:record]
190
+ end
191
+ end
192
+
193
+ def update_json(update_request = UpdateRequest.new, page_size=nil)
88
194
  json = nil
195
+
196
+ if page_size
197
+ return Etna::Clients::Magma::Pager.new(self, update_request, page_size).response
198
+ end
199
+
89
200
  @etna_client.post('/update', update_request) do |res|
90
- json = JSON.parse(res.body)
201
+ json = safe_parse(res)
91
202
  end
92
203
 
93
204
  UpdateResponse.new(json)
@@ -96,11 +207,20 @@ module Etna
96
207
  def update_model(update_model_request = UpdateModelRequest.new)
97
208
  json = nil
98
209
  @etna_client.post('/update_model', update_model_request) do |res|
99
- json = JSON.parse(res.body)
210
+ json = safe_parse(res)
100
211
  end
101
212
 
102
213
  UpdateModelResponse.new(json)
103
214
  end
215
+
216
+ def set_flags(project_name:, flags:)
217
+ json = nil
218
+ @etna_client.post("/flags/#{project_name}", { flags: flags }) do |res|
219
+ json = safe_parse(res)
220
+ end
221
+
222
+ json
223
+ end
104
224
  end
105
225
  end
106
226
  end