etna 0.1.41 → 0.1.44

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 44b6c9fde1d01fab95038052180531fafd4a120e77ffbd12a8ac14e28122cd05
4
- data.tar.gz: e1cb315eaeb4d35efc9e7f181e0cad767833b98f7ce3f26386e040c758c8483c
3
+ metadata.gz: bea4cbc1d94fca11f52c02b75ef487d11e7b0a6f2aaa7d64c03a09803891d627
4
+ data.tar.gz: cb764eb044b4b711ddcc4ea46efa598763a34fedf2dc1984a9a2e88234094e2c
5
5
  SHA512:
6
- metadata.gz: 3129a01c4181a772ab6752ed319396485e06f774b8091bff1ed9cbf6543a34820e0cc0706fd313ce5b3fb848a25f347de17bc376dc66b36458975d6298f48608
7
- data.tar.gz: 82c57cff18c826a0a2664a63e0a531df57c75af30ceb3408d1d8ec1b9cb942fd25bd11253e6d880db6973df4cd60ade0764fac798a543af1e7601bfcc21abc45
6
+ metadata.gz: 30100186f9af0e44654cc0a72d23eff04872f9479e119d308ac749bac4c7125358ea6e62f0a5cafd751512068715ac7831cbb5770ec554394c35c8c3781c30ce
7
+ data.tar.gz: dcee2b46965993e00d0dcae0a516b501a2afbbe968f1c9df35e45fcff4cd05535af7de52ddfec7e345943f3a1f4536aca196dd8150632d1a628559af482c6d7a
data/etna.completion CHANGED
@@ -84,7 +84,7 @@ arg_flag_completion_names="$arg_flag_completion_names "
84
84
  multi_flags="$multi_flags "
85
85
  while [[ "$#" != "0" ]]; do
86
86
  if [[ "$#" == "1" ]]; then
87
- all_completion_names="apply_template attributes copy_template help load_from_redcap"
87
+ all_completion_names="apply_template attributes copy_template help load_from_redcap set_date_shift_root"
88
88
  all_completion_names="$all_completion_names $all_flag_completion_names"
89
89
  if [[ -z "$(echo $all_completion_names | xargs)" ]]; then
90
90
  return
@@ -568,6 +568,54 @@ return
568
568
  fi
569
569
  done
570
570
  return
571
+ elif [[ "$1" == "set_date_shift_root" ]]; then
572
+ shift
573
+ if [[ "$#" == "1" ]]; then
574
+ all_completion_names="__project_name__"
575
+ if [[ -z "$(echo $all_completion_names | xargs)" ]]; then
576
+ return
577
+ fi
578
+ COMPREPLY=($(compgen -W "$all_completion_names" -- "$1"))
579
+ return
580
+ fi
581
+ shift
582
+ all_flag_completion_names="$all_flag_completion_names --date-shift-root --target-model "
583
+ arg_flag_completion_names="$arg_flag_completion_names --target-model "
584
+ multi_flags="$multi_flags "
585
+ declare _completions_for_target_model="__target_model__"
586
+ while [[ "$#" != "0" ]]; do
587
+ if [[ "$#" == "1" ]]; then
588
+ all_completion_names=""
589
+ all_completion_names="$all_completion_names $all_flag_completion_names"
590
+ if [[ -z "$(echo $all_completion_names | xargs)" ]]; then
591
+ return
592
+ fi
593
+ COMPREPLY=($(compgen -W "$all_completion_names" -- "$1"))
594
+ return
595
+ elif [[ -z "$(echo $all_flag_completion_names | xargs)" ]]; then
596
+ return
597
+ elif [[ "$all_flag_completion_names" =~ $1\ ]]; then
598
+ if ! [[ "$multi_flags" =~ $1\ ]]; then
599
+ all_flag_completion_names="${all_flag_completion_names//$1\ /}"
600
+ fi
601
+ a=$1
602
+ shift
603
+ if [[ "$arg_flag_completion_names" =~ $a\ ]]; then
604
+ if [[ "$#" == "1" ]]; then
605
+ a="${a//--/}"
606
+ a="${a//-/_}"
607
+ i="_completions_for_$a"
608
+ all_completion_names="${!i}"
609
+ COMPREPLY=($(compgen -W "$all_completion_names" -- "$1"))
610
+ return
611
+ fi
612
+ shift
613
+ fi
614
+ else
615
+ return
616
+ fi
617
+ done
618
+ return
571
619
  elif [[ -z "$(echo $all_flag_completion_names | xargs)" ]]; then
572
620
  return
573
621
  elif [[ "$all_flag_completion_names" =~ $1\ ]]; then
@@ -738,10 +786,11 @@ COMPREPLY=($(compgen -W "$all_completion_names" -- "$1"))
738
786
  return
739
787
  elif [[ "$1" == "generate" ]]; then
740
788
  shift
741
- all_flag_completion_names="$all_flag_completion_names --task --project-name "
742
- arg_flag_completion_names="$arg_flag_completion_names --project-name "
789
+ all_flag_completion_names="$all_flag_completion_names --task --project-name --email "
790
+ arg_flag_completion_names="$arg_flag_completion_names --project-name --email "
743
791
  multi_flags="$multi_flags "
744
792
  declare _completions_for_project_name="__project_name__"
793
+ declare _completions_for_email="__email__"
745
794
  while [[ "$#" != "0" ]]; do
746
795
  if [[ "$#" == "1" ]]; then
747
796
  all_completion_names=""
data/lib/commands.rb CHANGED
@@ -411,6 +411,25 @@ class EtnaApp
411
411
  end
412
412
  end
413
413
  end
414
+
415
+ class SetDateShiftRoot < Etna::Command
416
+ include WithEtnaClients
417
+ include WithLogger
418
+ include StrongConfirmation
419
+
420
+ boolean_flags << '--date-shift-root'
421
+ string_flags << '--target-model'
422
+
423
+ def execute(project_name, target_model: 'subject', date_shift_root: false)
424
+ magma_client.update_model(Etna::Clients::Magma::UpdateModelRequest.new(
425
+ actions:[Etna::Clients::Magma::SetDateShiftRootAction.new(
426
+ model_name: target_model,
427
+ date_shift_root: date_shift_root
428
+ )],
429
+ project_name: project_name,
430
+ ))
431
+ end
432
+ end
414
433
  end
415
434
  end
416
435
 
@@ -50,9 +50,42 @@ module Etna::Application
50
50
  Etna::Application.register(self)
51
51
  end
52
52
 
53
+ # a <- b
54
+ def deep_merge(a, b)
55
+ if a.is_a?(Hash)
56
+ if b.is_a?(Hash)
57
+ b.keys.each do |b_key|
58
+ a[b_key] = deep_merge(a[b_key], b[b_key])
59
+ end
60
+
61
+ return a
62
+ end
63
+ end
64
+
65
+ a.nil? ? b : a
66
+ end
67
+
53
68
  def configure(opts)
54
69
  @config = opts
55
70
 
71
+ # Apply environmental variables of the form "ETNA__x__y__z_FILE"
72
+ # by reading the given file.
73
+ prefix = "ETNA__"
74
+ ENV.keys.select { |k| k.start_with?(prefix) && k.end_with?("_FILE") }.each do |key|
75
+ path = key.sub(/_FILE/, '').split("__", -1)
76
+ path.shift # drop the first, just app name
77
+
78
+ target = @config
79
+ while path.length > 1
80
+ n = path.shift
81
+ target = (target[n.downcase.to_sym] ||= {})
82
+ end
83
+
84
+ v = YAML.load(File.read(ENV[key]))
85
+ target[path.last.downcase.to_sym] =
86
+ deep_merge(target[path.last.downcase.to_sym], v)
87
+ end
88
+
56
89
  if (rollbar_config = config(:rollbar)) && rollbar_config[:access_token]
57
90
  Rollbar.configure do |config|
58
91
  config.access_token = rollbar_config[:access_token]
data/lib/etna/auth.rb CHANGED
@@ -76,23 +76,63 @@ module Etna
76
76
  # some routes don't need janus approval
77
77
  return true if route && route.ignore_janus?
78
78
 
79
- # only process task tokens right now
80
- return true unless payload['task']
79
+ # process task tokens
80
+ payload['task'] ? valid_task_token?(token) : true
81
+ end
82
+
83
+ def resource_projects(token)
84
+ return [] unless has_janus_config?
81
85
 
82
- return false unless application.config(:janus) && application.config(:janus)[:host]
86
+ janus_client(token).get_projects.projects.select do |project|
87
+ project.resource
88
+ end
89
+ rescue
90
+ # If encounter any issue with Janus, we'll return no resource projects
91
+ []
92
+ end
83
93
 
84
- janus_client = Etna::Clients::Janus.new(
94
+ def janus_client(token)
95
+ Etna::Clients::Janus.new(
85
96
  token: token,
86
97
  host: application.config(:janus)[:host]
87
98
  )
99
+ end
100
+
101
+ def valid_task_token?(token)
102
+ return false unless has_janus_config?
88
103
 
89
- response = janus_client.validate_task_token
104
+ response = janus_client(token).validate_task_token
90
105
 
91
106
  return false unless response.code == '200'
92
107
 
93
108
  return true
94
109
  end
95
110
 
111
+ def has_janus_config?
112
+ application.config(:janus) && application.config(:janus)[:host]
113
+ end
114
+
115
+ def update_payload(payload, token, request)
116
+ route = server.find_route(request)
117
+
118
+ payload = payload.map{|k,v| [k.to_sym, v]}.to_h
119
+
120
+ return payload unless route
121
+
122
+ begin
123
+ permissions = Etna::Permissions.from_encoded_permissions(payload[:perm])
124
+
125
+ resource_projects(token).each do |resource_project|
126
+ permissions.add_permission(
127
+ Etna::Permission.new('v', resource_project.project_name)
128
+ )
129
+ end
130
+ payload[:perm] = permissions.to_string
131
+ end if (!route.ignore_janus? && route.has_user_constraint?(:can_view?))
132
+
133
+ payload
134
+ end
135
+
96
136
  def approve_user(request)
97
137
  token = request.cookies[application.config(:token_name)] || auth(request, :etna)
98
138
 
@@ -102,8 +142,11 @@ module Etna
102
142
  payload, header = application.sign.jwt_decode(token)
103
143
 
104
144
  return false unless janus_approved?(payload, token, request)
105
- return request.env['etna.user'] = Etna::User.new(payload.map{|k,v| [k.to_sym, v]}.to_h, token)
106
- rescue
145
+ return request.env['etna.user'] = Etna::User.new(
146
+ update_payload(payload, token, request),
147
+ token)
148
+ rescue => e
149
+ application.logger.log_error(e)
107
150
  # bail out if anything goes wrong
108
151
  return false
109
152
  end
@@ -0,0 +1,36 @@
1
+ module Etna
2
+ class Censor
3
+ def initialize(log_redact_keys)
4
+ @log_redact_keys = log_redact_keys
5
+ end
6
+
7
+ def redact_keys
8
+ @log_redact_keys
9
+ end
10
+
11
+ def redact(key, value)
12
+ # Redact any values for the supplied key values, so they
13
+ # don't appear in the logs.
14
+ return compact(value) unless redact_keys
15
+
16
+ if redact_keys.include?(key)
17
+ return "*"
18
+ elsif value.is_a?(Hash)
19
+ redacted_value = value.map do |value_key, value_value|
20
+ [value_key, redact(value_key, value_value)]
21
+ end.to_h
22
+ return redacted_value
23
+ end
24
+
25
+ return compact(value)
26
+ end
27
+
28
+ private
29
+
30
+ def compact(value)
31
+ value = value.to_s
32
+ value = value[0..500] + "..." + value[-100..-1] if value.length > 600
33
+ value
34
+ end
35
+ end
36
+ end
@@ -8,25 +8,33 @@ module Etna
8
8
  module Clients
9
9
  class Janus < Etna::Clients::BaseClient
10
10
  def get_project(get_project_request = GetProjectRequest.new)
11
- html = nil
11
+ json = nil
12
12
  @etna_client.get(
13
- "/project/#{get_project_request.project_name}",
14
- get_project_request) do |res|
15
- html = res.body
13
+ "/api/admin/#{get_project_request.project_name}/info") do |res|
14
+ json = JSON.parse(res.body, symbolize_names: true)
16
15
  end
17
16
 
18
- HtmlResponse.new(html)
17
+ GetProjectResponse.new(json)
18
+ end
19
+
20
+ def get_projects()
21
+ json = nil
22
+ @etna_client.get('/api/user/projects') do |res|
23
+ json = JSON.parse(res.body, symbolize_names: true)
24
+ end
25
+
26
+ GetProjectsResponse.new(json)
19
27
  end
20
28
 
21
29
  def add_project(add_project_request = AddProjectRequest.new)
22
- @etna_client.post('/add_project', add_project_request) do |res|
30
+ @etna_client.post('/api/admin/add_project', add_project_request) do |res|
23
31
  # Redirect, no response data
24
32
  end
25
33
  end
26
34
 
27
35
  def add_user(add_user_request = AddUserRequest.new)
28
36
  @etna_client.post(
29
- "/add_user/#{add_user_request.project_name}",
37
+ "/api/admin/#{add_user_request.project_name}/add_user",
30
38
  add_user_request) do |res|
31
39
  # Redirect, no response data
32
40
  end
@@ -34,7 +42,7 @@ module Etna
34
42
 
35
43
  def update_permission(update_permission_request = UpdatePermissionRequest.new)
36
44
  @etna_client.post(
37
- "/update_permission/#{update_permission_request.project_name}",
45
+ "/api/admin/#{update_permission_request.project_name}/update_permission",
38
46
  update_permission_request) do |res|
39
47
  # Redirect, no response data
40
48
  end
@@ -42,16 +50,7 @@ module Etna
42
50
 
43
51
  def refresh_token(refresh_token_request = RefreshTokenRequest.new)
44
52
  token = nil
45
- @etna_client.get('/refresh_token', refresh_token_request) do |res|
46
- token = res.body
47
- end
48
-
49
- TokenResponse.new(token)
50
- end
51
-
52
- def viewer_token(viewer_token_request = ViewerTokenRequest.new)
53
- token = nil
54
- @etna_client.get('/viewer_token', viewer_token_request) do |res|
53
+ @etna_client.post('/api/tokens/generate') do |res|
55
54
  token = res.body
56
55
  end
57
56
 
@@ -69,6 +69,54 @@ module Etna
69
69
  end
70
70
  end
71
71
 
72
+ class GetProjectResponse
73
+ attr_reader :raw
74
+
75
+ def initialize(raw = '')
76
+ @raw = raw
77
+ end
78
+
79
+ def project
80
+ Project.new(@raw[:project])
81
+ end
82
+ end
83
+
84
+ class GetProjectsResponse
85
+ attr_reader :raw
86
+
87
+ def initialize(raw = '')
88
+ @raw = raw
89
+ end
90
+
91
+ def projects
92
+ @raw[:projects].map do |project|
93
+ Project.new(project)
94
+ end
95
+ end
96
+ end
97
+
98
+ class Project
99
+ def initialize(raw = '')
100
+ @raw = raw
101
+ end
102
+
103
+ def project_name
104
+ @raw[:project_name]
105
+ end
106
+
107
+ def project_name_full
108
+ @raw[:project_name_full]
109
+ end
110
+
111
+ def permissions
112
+ @raw[:permissions] || []
113
+ end
114
+
115
+ def resource
116
+ !!@raw[:resource]
117
+ end
118
+ end
119
+
72
120
  class TokenResponse
73
121
  attr_reader :raw
74
122
 
@@ -6,6 +6,47 @@ require_relative './models'
6
6
 
7
7
  module Etna
8
8
  module Clients
9
+ class LocalMagmaClient # May only be used from within magma app.
10
+ def logger
11
+ ::Magma.instance.logger
12
+ end
13
+
14
+ def user
15
+ permissions = Etna::Permissions.new([])
16
+ permissions.add_permission(Etna::Permission.new('A', 'administration'))
17
+ @user ||= Etna::User.new({name: 'Admin', email: 'etnaagent@ucsf.edu', perm: permissions.to_string})
18
+ end
19
+
20
+ def fake_request(request)
21
+ Rack::Request.new({
22
+ 'rack.request.params' => request.as_json,
23
+ 'etna.logger' => self.logger,
24
+ 'etna.user' => user,
25
+ 'etna.request_id' => 'local',
26
+ })
27
+ end
28
+
29
+ def parse_json_response(response)
30
+ status, headers, body = response
31
+ body = body.join('')
32
+ if status < 200 || status >= 300
33
+ raise Etna::Error.new(body, status)
34
+ end
35
+
36
+ JSON.parse(body)
37
+ end
38
+
39
+ def retrieve(retrieval_request = RetrievalRequest.new)
40
+ controller = ::RetrieveController.new(fake_request(retrieval_request), nil)
41
+ Magma::RetrievalResponse.new(parse_json_response(controller.action))
42
+ end
43
+
44
+ def update_model(update_model_request = UpdateModelRequest.new)
45
+ controller = ::UpdateModelController.new(fake_request(update_model_request), nil)
46
+ Magma::UpdateModelResponse.new(parse_json_response(controller.action))
47
+ end
48
+ end
49
+
9
50
  class Magma < Etna::Clients::BaseClient
10
51
  # This endpoint returns models and records by name:
11
52
  # e.g. params:
@@ -22,12 +22,12 @@ module Etna
22
22
  include JsonSerializableStruct
23
23
  end
24
24
 
25
- class UpdateRequest < Struct.new(:revisions, :project_name, keyword_init: true)
25
+ class UpdateRequest < Struct.new(:revisions, :project_name, :dry_run, keyword_init: true)
26
26
  include JsonSerializableStruct
27
27
  include MultipartSerializableNestedHash
28
28
 
29
29
  def initialize(**params)
30
- super({revisions: {}}.update(params))
30
+ super({revisions: {}, dry_run: false}.update(params))
31
31
  end
32
32
 
33
33
  def update_revision(model_name, record_name, attrs)
@@ -58,11 +58,19 @@ module Etna
58
58
  end
59
59
  end
60
60
 
61
- class AddModelAction < Struct.new(:action_name, :model_name, :parent_model_name, :parent_link_type, :identifier, keyword_init: true)
61
+ class AddModelAction < Struct.new(:action_name, :model_name, :parent_model_name, :parent_link_type, :identifier, :date_shift_root, keyword_init: true)
62
62
  include JsonSerializableStruct
63
63
 
64
64
  def initialize(**args)
65
- super({action_name: 'add_model'}.update(args))
65
+ super({action_name: 'add_model', date_shift_root: false}.update(args))
66
+ end
67
+ end
68
+
69
+ class SetDateShiftRootAction < Struct.new(:action_name, :model_name, :date_shift_root, keyword_init: true)
70
+ include JsonSerializableStruct
71
+
72
+ def initialize(**args)
73
+ super({action_name: 'set_date_shift_root'}.update(args))
66
74
  end
67
75
  end
68
76
 
@@ -102,7 +110,7 @@ module Etna
102
110
  include JsonSerializableStruct
103
111
  end
104
112
 
105
- class AddProjectAction < Struct.new(:action_name, keyword_init: true)
113
+ class AddProjectAction < Struct.new(:action_name, :no_metis_bucket, keyword_init: true)
106
114
  include JsonSerializableStruct
107
115
 
108
116
  def initialize(**args)
@@ -207,8 +215,8 @@ module Etna
207
215
  end
208
216
 
209
217
  def model(model_key)
210
- return nil unless raw.include?(model_key)
211
- Model.new(raw[model_key])
218
+ return nil unless raw.include?(model_key.to_s)
219
+ Model.new(raw[model_key.to_s])
212
220
  end
213
221
 
214
222
  def all
@@ -426,8 +434,8 @@ module Etna
426
434
  end
427
435
 
428
436
  def attribute(attribute_key)
429
- return nil unless raw.include?(attribute_key)
430
- Attribute.new(raw[attribute_key])
437
+ return nil unless raw.include?(attribute_key.to_s)
438
+ Attribute.new(raw[attribute_key.to_s])
431
439
  end
432
440
 
433
441
  def build_attribute(key)
@@ -620,6 +628,7 @@ module Etna
620
628
  MATRIX = AttributeType.new("matrix")
621
629
  PARENT = AttributeType.new("parent")
622
630
  TABLE = AttributeType.new("table")
631
+ SHIFTED_DATE_TIME = AttributeType.new("shifted_date_time")
623
632
  end
624
633
 
625
634
  class ParentLinkType < String
@@ -172,10 +172,6 @@ module Etna
172
172
  link_attributes.each do |attribute|
173
173
  check_key("attribute #{attribute.attribute_name}", attribute.raw, 'link_model_name')
174
174
 
175
- if attribute.attribute_name != attribute.link_model_name
176
- @errors << "Linked model, \"#{attribute.link_model_name}\", does not match attribute #{attribute.attribute_name}, link attribute names must match the model name."
177
- end
178
-
179
175
  unless @models.model_keys.include?(attribute.link_model_name)
180
176
  @errors << "Linked model, \"#{attribute.link_model_name}\", on attribute #{attribute.attribute_name} does not exist!"
181
177
  end
@@ -29,7 +29,7 @@ module Etna
29
29
  end
30
30
 
31
31
  def execute_planned!
32
- @planned_actions.each do |action|
32
+ planned_actions.each do |action|
33
33
  execute_update(action)
34
34
  end
35
35
  end
@@ -161,7 +161,7 @@ module Etna
161
161
  return if target_attributes.attribute_keys.include?(target_link_model_name)
162
162
 
163
163
  add_link = AddLinkAction.new
164
- add_link.links << AddLinkDefinition.new(model_name: target_model_name, attribute_name: target_link_model_name, type: source_attribute.attribute_type)
164
+ add_link.links << AddLinkDefinition.new(model_name: target_model_name, attribute_name: attribute_name, type: source_attribute.attribute_type)
165
165
  add_link.links << AddLinkDefinition.new(model_name: target_link_model_name, attribute_name: reciprocal.attribute_name, type: reciprocal.attribute_type)
166
166
 
167
167
  queue_update(add_link)
@@ -24,8 +24,28 @@ module Etna
24
24
  end
25
25
 
26
26
  def list_folder(list_folder_request = ListFolderRequest.new)
27
+ if list_folder_request.folder_path != ''
28
+ FoldersAndFilesResponse.new(
29
+ @etna_client.folder_list(list_folder_request.to_h))
30
+ else
31
+ FoldersAndFilesResponse.new(
32
+ @etna_client.bucket_list(list_folder_request.to_h))
33
+ end
34
+ end
35
+
36
+ def list_folder_by_id(list_folder_by_id_request = ListFolderByIdRequest.new)
27
37
  FoldersAndFilesResponse.new(
28
- @etna_client.folder_list(list_folder_request.to_h))
38
+ @etna_client.folder_list_by_id(list_folder_by_id_request.to_h))
39
+ end
40
+
41
+ def touch_folder(touch_folder_request = TouchFolderRequest.new)
42
+ FoldersResponse.new(
43
+ @etna_client.folder_touch(touch_folder_request.to_h))
44
+ end
45
+
46
+ def touch_file(touch_file_request = TouchFileRequest.new)
47
+ FilesResponse.new(
48
+ @etna_client.file_touch(touch_file_request.to_h))
29
49
  end
30
50
 
31
51
  def ensure_parent_folder_exists(project_name:, bucket_name:, path:)
@@ -181,7 +201,16 @@ module Etna
181
201
 
182
202
  return if found_folders.length == 0
183
203
 
184
- found_folders.each { |folder|
204
+ rename_folders(
205
+ project_name: project_name,
206
+ source_bucket: source_bucket,
207
+ source_folders: found_folders,
208
+ dest_bucket: dest_bucket
209
+ )
210
+ end
211
+
212
+ def rename_folders(project_name:, source_bucket:, source_folders:, dest_bucket:)
213
+ source_folders.each { |folder|
185
214
  # If the destination folder already exists, we need to copy the files
186
215
  # over to it and delete the source folder.
187
216
  create_folder_request = CreateFolderRequest.new(