etna 0.1.32 → 0.1.37
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/etna.completion +115 -1
- data/lib/commands.rb +31 -1
- data/lib/etna.rb +1 -0
- data/lib/etna/application.rb +24 -0
- data/lib/etna/auth.rb +25 -0
- data/lib/etna/client.rb +15 -6
- data/lib/etna/clients/base_client.rb +4 -4
- data/lib/etna/clients/janus.rb +1 -0
- data/lib/etna/clients/janus/client.rb +18 -0
- data/lib/etna/clients/janus/models.rb +7 -1
- data/lib/etna/clients/janus/workflows.rb +1 -0
- data/lib/etna/clients/janus/workflows/generate_token_workflow.rb +77 -0
- data/lib/etna/clients/magma/formatting/models_csv.rb +5 -6
- data/lib/etna/clients/magma/formatting/models_odm_xml.rb +1 -1
- data/lib/etna/clients/magma/models.rb +15 -28
- data/lib/etna/clients/magma/workflows/file_linking_workflow.rb +3 -1
- data/lib/etna/clients/magma/workflows/model_synchronization_workflow.rb +1 -4
- data/lib/etna/clients/magma/workflows/walk_model_tree_workflow.rb +59 -11
- data/lib/etna/command.rb +1 -0
- data/lib/etna/directed_graph.rb +67 -8
- data/lib/etna/metrics.rb +26 -0
- data/lib/etna/route.rb +8 -1
- data/lib/etna/server.rb +5 -0
- data/lib/etna/spec/vcr.rb +21 -2
- data/lib/etna/test_auth.rb +0 -1
- data/lib/etna/user.rb +5 -1
- data/lib/helpers.rb +2 -2
- metadata +6 -17
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 484c950fffbafcf5132df923bc0ef88a79faa6c285ccde653805aac526e4a97e
|
|
4
|
+
data.tar.gz: f513b75b048e816acc494c09cb0eccbc2c38cda02c2723266fe7bd98c8595f3a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6ad0e99925dc6110c8f80f4a1e1417d610b28838497f09afe8be1c69a65fec95a7d549af987aaccde2b42480347bf336838be5398ef580d3024476206b4d611d
|
|
7
|
+
data.tar.gz: 44eb05596c635e7ab96df7971fed6b24bfee7bd545238e173e3d1b57bdcfe7b30db5deea355d2711b192d026d8fb72afc32cbcbcf4a83ef92848184433b7a813
|
data/etna.completion
CHANGED
|
@@ -32,7 +32,7 @@ arg_flag_completion_names="$arg_flag_completion_names "
|
|
|
32
32
|
multi_flags="$multi_flags "
|
|
33
33
|
while [[ "$#" != "0" ]]; do
|
|
34
34
|
if [[ "$#" == "1" ]]; then
|
|
35
|
-
all_completion_names="help models project"
|
|
35
|
+
all_completion_names="help models project token"
|
|
36
36
|
all_completion_names="$all_completion_names $all_flag_completion_names"
|
|
37
37
|
if [[ -z "$(echo $all_completion_names | xargs)" ]]; then
|
|
38
38
|
return
|
|
@@ -722,6 +722,120 @@ else
|
|
|
722
722
|
return
|
|
723
723
|
fi
|
|
724
724
|
done
|
|
725
|
+
elif [[ "$1" == "token" ]]; then
|
|
726
|
+
shift
|
|
727
|
+
all_flag_completion_names="$all_flag_completion_names "
|
|
728
|
+
arg_flag_completion_names="$arg_flag_completion_names "
|
|
729
|
+
multi_flags="$multi_flags "
|
|
730
|
+
while [[ "$#" != "0" ]]; do
|
|
731
|
+
if [[ "$#" == "1" ]]; then
|
|
732
|
+
all_completion_names="generate help"
|
|
733
|
+
all_completion_names="$all_completion_names $all_flag_completion_names"
|
|
734
|
+
if [[ -z "$(echo $all_completion_names | xargs)" ]]; then
|
|
735
|
+
return
|
|
736
|
+
fi
|
|
737
|
+
COMPREPLY=($(compgen -W "$all_completion_names" -- "$1"))
|
|
738
|
+
return
|
|
739
|
+
elif [[ "$1" == "generate" ]]; then
|
|
740
|
+
shift
|
|
741
|
+
all_flag_completion_names="$all_flag_completion_names --task --project-name "
|
|
742
|
+
arg_flag_completion_names="$arg_flag_completion_names --project-name "
|
|
743
|
+
multi_flags="$multi_flags "
|
|
744
|
+
declare _completions_for_project_name="__project_name__"
|
|
745
|
+
while [[ "$#" != "0" ]]; do
|
|
746
|
+
if [[ "$#" == "1" ]]; then
|
|
747
|
+
all_completion_names=""
|
|
748
|
+
all_completion_names="$all_completion_names $all_flag_completion_names"
|
|
749
|
+
if [[ -z "$(echo $all_completion_names | xargs)" ]]; then
|
|
750
|
+
return
|
|
751
|
+
fi
|
|
752
|
+
COMPREPLY=($(compgen -W "$all_completion_names" -- "$1"))
|
|
753
|
+
return
|
|
754
|
+
elif [[ -z "$(echo $all_flag_completion_names | xargs)" ]]; then
|
|
755
|
+
return
|
|
756
|
+
elif [[ "$all_flag_completion_names" =~ $1\ ]]; then
|
|
757
|
+
if ! [[ "$multi_flags" =~ $1\ ]]; then
|
|
758
|
+
all_flag_completion_names="${all_flag_completion_names//$1\ /}"
|
|
759
|
+
fi
|
|
760
|
+
a=$1
|
|
761
|
+
shift
|
|
762
|
+
if [[ "$arg_flag_completion_names" =~ $a\ ]]; then
|
|
763
|
+
if [[ "$#" == "1" ]]; then
|
|
764
|
+
a="${a//--/}"
|
|
765
|
+
a="${a//-/_}"
|
|
766
|
+
i="_completions_for_$a"
|
|
767
|
+
all_completion_names="${!i}"
|
|
768
|
+
COMPREPLY=($(compgen -W "$all_completion_names" -- "$1"))
|
|
769
|
+
return
|
|
770
|
+
fi
|
|
771
|
+
shift
|
|
772
|
+
fi
|
|
773
|
+
else
|
|
774
|
+
return
|
|
775
|
+
fi
|
|
776
|
+
done
|
|
777
|
+
return
|
|
778
|
+
elif [[ "$1" == "help" ]]; then
|
|
779
|
+
shift
|
|
780
|
+
all_flag_completion_names="$all_flag_completion_names "
|
|
781
|
+
arg_flag_completion_names="$arg_flag_completion_names "
|
|
782
|
+
multi_flags="$multi_flags "
|
|
783
|
+
while [[ "$#" != "0" ]]; do
|
|
784
|
+
if [[ "$#" == "1" ]]; then
|
|
785
|
+
all_completion_names=""
|
|
786
|
+
all_completion_names="$all_completion_names $all_flag_completion_names"
|
|
787
|
+
if [[ -z "$(echo $all_completion_names | xargs)" ]]; then
|
|
788
|
+
return
|
|
789
|
+
fi
|
|
790
|
+
COMPREPLY=($(compgen -W "$all_completion_names" -- "$1"))
|
|
791
|
+
return
|
|
792
|
+
elif [[ -z "$(echo $all_flag_completion_names | xargs)" ]]; then
|
|
793
|
+
return
|
|
794
|
+
elif [[ "$all_flag_completion_names" =~ $1\ ]]; then
|
|
795
|
+
if ! [[ "$multi_flags" =~ $1\ ]]; then
|
|
796
|
+
all_flag_completion_names="${all_flag_completion_names//$1\ /}"
|
|
797
|
+
fi
|
|
798
|
+
a=$1
|
|
799
|
+
shift
|
|
800
|
+
if [[ "$arg_flag_completion_names" =~ $a\ ]]; then
|
|
801
|
+
if [[ "$#" == "1" ]]; then
|
|
802
|
+
a="${a//--/}"
|
|
803
|
+
a="${a//-/_}"
|
|
804
|
+
i="_completions_for_$a"
|
|
805
|
+
all_completion_names="${!i}"
|
|
806
|
+
COMPREPLY=($(compgen -W "$all_completion_names" -- "$1"))
|
|
807
|
+
return
|
|
808
|
+
fi
|
|
809
|
+
shift
|
|
810
|
+
fi
|
|
811
|
+
else
|
|
812
|
+
return
|
|
813
|
+
fi
|
|
814
|
+
done
|
|
815
|
+
return
|
|
816
|
+
elif [[ -z "$(echo $all_flag_completion_names | xargs)" ]]; then
|
|
817
|
+
return
|
|
818
|
+
elif [[ "$all_flag_completion_names" =~ $1\ ]]; then
|
|
819
|
+
if ! [[ "$multi_flags" =~ $1\ ]]; then
|
|
820
|
+
all_flag_completion_names="${all_flag_completion_names//$1\ /}"
|
|
821
|
+
fi
|
|
822
|
+
a=$1
|
|
823
|
+
shift
|
|
824
|
+
if [[ "$arg_flag_completion_names" =~ $a\ ]]; then
|
|
825
|
+
if [[ "$#" == "1" ]]; then
|
|
826
|
+
a="${a//--/}"
|
|
827
|
+
a="${a//-/_}"
|
|
828
|
+
i="_completions_for_$a"
|
|
829
|
+
all_completion_names="${!i}"
|
|
830
|
+
COMPREPLY=($(compgen -W "$all_completion_names" -- "$1"))
|
|
831
|
+
return
|
|
832
|
+
fi
|
|
833
|
+
shift
|
|
834
|
+
fi
|
|
835
|
+
else
|
|
836
|
+
return
|
|
837
|
+
fi
|
|
838
|
+
done
|
|
725
839
|
elif [[ -z "$(echo $all_flag_completion_names | xargs)" ]]; then
|
|
726
840
|
return
|
|
727
841
|
elif [[ "$all_flag_completion_names" =~ $1\ ]]; then
|
data/lib/commands.rb
CHANGED
|
@@ -91,6 +91,36 @@ class EtnaApp
|
|
|
91
91
|
class Administrate
|
|
92
92
|
include Etna::CommandExecutor
|
|
93
93
|
|
|
94
|
+
class Token
|
|
95
|
+
include Etna::CommandExecutor
|
|
96
|
+
|
|
97
|
+
class Generate < Etna::Command
|
|
98
|
+
include WithLogger
|
|
99
|
+
|
|
100
|
+
boolean_flags << "--task"
|
|
101
|
+
string_flags << "--project-name"
|
|
102
|
+
string_flags << "--email"
|
|
103
|
+
|
|
104
|
+
def execute(email:, task: false, project_name: nil)
|
|
105
|
+
# the token is not required, but can be used if available
|
|
106
|
+
# to generate a task token, so we pass it in here
|
|
107
|
+
janus_client = Etna::Clients::Janus.new(
|
|
108
|
+
token: ENV['TOKEN'],
|
|
109
|
+
ignore_ssl: EtnaApp.instance.config(:ignore_ssl),
|
|
110
|
+
**EtnaApp.instance.config(:janus, EtnaApp.instance.environment))
|
|
111
|
+
|
|
112
|
+
generate_token_workflow = Etna::Clients::Janus::GenerateTokenWorkflow.new(
|
|
113
|
+
janus_client: janus_client,
|
|
114
|
+
token_type: task ? 'task' : 'login',
|
|
115
|
+
email: email,
|
|
116
|
+
project_name: project_name,
|
|
117
|
+
private_key_file: EtnaApp.instance.config(:private_key, EtnaApp.instance.environment)
|
|
118
|
+
)
|
|
119
|
+
generate_token_workflow.generate!
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
94
124
|
class Project
|
|
95
125
|
include Etna::CommandExecutor
|
|
96
126
|
|
|
@@ -309,7 +339,7 @@ class EtnaApp
|
|
|
309
339
|
request = Etna::Clients::Magma::RetrievalRequest.new(project_name: project_name)
|
|
310
340
|
request.model_name = model_name
|
|
311
341
|
request.attribute_names = 'all'
|
|
312
|
-
request.record_names =
|
|
342
|
+
request.record_names = []
|
|
313
343
|
model = magma_client.retrieve(request).models.model(model_name)
|
|
314
344
|
model_parent_name = model.template.attributes.all.select do |attribute|
|
|
315
345
|
attribute.attribute_type == Etna::Clients::Magma::AttributeType::PARENT
|
data/lib/etna.rb
CHANGED
data/lib/etna/application.rb
CHANGED
|
@@ -8,6 +8,7 @@ require_relative './command'
|
|
|
8
8
|
require_relative './generate_autocompletion_script'
|
|
9
9
|
require 'singleton'
|
|
10
10
|
require 'rollbar'
|
|
11
|
+
require 'fileutils'
|
|
11
12
|
|
|
12
13
|
module Etna::Application
|
|
13
14
|
def self.included(other)
|
|
@@ -31,6 +32,12 @@ module Etna::Application
|
|
|
31
32
|
raise "Could not find application instance from #{namespace}, and not subclass of Application found."
|
|
32
33
|
end
|
|
33
34
|
|
|
35
|
+
# Used to find the application in development recorded vcr tests.
|
|
36
|
+
# see spec/vcr.rb
|
|
37
|
+
def dev_route
|
|
38
|
+
"#{self.class.name.split('::').first.downcase}.development.local"
|
|
39
|
+
end
|
|
40
|
+
|
|
34
41
|
def self.register(app)
|
|
35
42
|
@instance = app
|
|
36
43
|
end
|
|
@@ -53,6 +60,23 @@ module Etna::Application
|
|
|
53
60
|
end
|
|
54
61
|
end
|
|
55
62
|
|
|
63
|
+
def setup_yabeda
|
|
64
|
+
Yabeda.configure!
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def write_job_metrics(name)
|
|
68
|
+
node_metrics_dir = config(:node_metrics_dir) || "/tmp/metrics.prom"
|
|
69
|
+
::FileUtils.mkdir_p(node_metrics_dir)
|
|
70
|
+
|
|
71
|
+
tmp_file = ::File.join(node_metrics_dir, "#{name}.prom.$$")
|
|
72
|
+
::File.open(tmp_file, "w") do |f|
|
|
73
|
+
f.write(Prometheus::Client::Formats::Text.marshal(Prometheus::Client.registry))
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
require 'fileutils'
|
|
77
|
+
::FileUtils.mv(tmp_file, ::File.join(node_metrics_dir, "#{name}.prom"))
|
|
78
|
+
end
|
|
79
|
+
|
|
56
80
|
def setup_logger
|
|
57
81
|
@logger = Etna::Logger.new(
|
|
58
82
|
# The name of the log_file, required.
|
data/lib/etna/auth.rb
CHANGED
|
@@ -70,6 +70,29 @@ module Etna
|
|
|
70
70
|
return route && route.noauth?
|
|
71
71
|
end
|
|
72
72
|
|
|
73
|
+
def janus_approved?(payload, token, request)
|
|
74
|
+
route = server.find_route(request)
|
|
75
|
+
|
|
76
|
+
# some routes don't need janus approval
|
|
77
|
+
return true if route && route.ignore_janus?
|
|
78
|
+
|
|
79
|
+
# only process task tokens right now
|
|
80
|
+
return true unless payload['task']
|
|
81
|
+
|
|
82
|
+
return false unless application.config(:janus) && application.config(:janus)[:host]
|
|
83
|
+
|
|
84
|
+
janus_client = Etna::Clients::Janus.new(
|
|
85
|
+
token: token,
|
|
86
|
+
host: application.config(:janus)[:host]
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
response = janus_client.validate_task_token
|
|
90
|
+
|
|
91
|
+
return false unless response.code == '200'
|
|
92
|
+
|
|
93
|
+
return true
|
|
94
|
+
end
|
|
95
|
+
|
|
73
96
|
def approve_user(request)
|
|
74
97
|
token = request.cookies[application.config(:token_name)] || auth(request, :etna)
|
|
75
98
|
|
|
@@ -77,6 +100,8 @@ module Etna
|
|
|
77
100
|
|
|
78
101
|
begin
|
|
79
102
|
payload, header = application.sign.jwt_decode(token)
|
|
103
|
+
|
|
104
|
+
return false unless janus_approved?(payload, token, request)
|
|
80
105
|
return request.env['etna.user'] = Etna::User.new(payload.map{|k,v| [k.to_sym, v]}.to_h, token)
|
|
81
106
|
rescue
|
|
82
107
|
# bail out if anything goes wrong
|
data/lib/etna/client.rb
CHANGED
|
@@ -17,6 +17,13 @@ module Etna
|
|
|
17
17
|
|
|
18
18
|
attr_reader :routes
|
|
19
19
|
|
|
20
|
+
def with_headers(headers, &block)
|
|
21
|
+
@request_headers = headers.compact
|
|
22
|
+
result = instance_eval(&block)
|
|
23
|
+
@request_headers = nil
|
|
24
|
+
return result
|
|
25
|
+
end
|
|
26
|
+
|
|
20
27
|
def signed_route_path(route, params)
|
|
21
28
|
path = route_path(route,params)
|
|
22
29
|
|
|
@@ -111,7 +118,7 @@ module Etna
|
|
|
111
118
|
|
|
112
119
|
def body_request(type, endpoint, params = {}, &block)
|
|
113
120
|
uri = request_uri(endpoint)
|
|
114
|
-
req = type.new(uri.request_uri,
|
|
121
|
+
req = type.new(uri.request_uri, request_headers)
|
|
115
122
|
req.body = params.to_json
|
|
116
123
|
request(uri, req, &block)
|
|
117
124
|
end
|
|
@@ -124,7 +131,7 @@ module Etna
|
|
|
124
131
|
else
|
|
125
132
|
uri.query = URI.encode_www_form(params)
|
|
126
133
|
end
|
|
127
|
-
req = type.new(uri.request_uri,
|
|
134
|
+
req = type.new(uri.request_uri, request_headers)
|
|
128
135
|
request(uri, req, &block)
|
|
129
136
|
end
|
|
130
137
|
|
|
@@ -132,12 +139,14 @@ module Etna
|
|
|
132
139
|
URI("#{@host}#{endpoint}")
|
|
133
140
|
end
|
|
134
141
|
|
|
135
|
-
def
|
|
142
|
+
def request_headers
|
|
136
143
|
{
|
|
137
144
|
'Content-Type' => 'application/json',
|
|
138
145
|
'Accept' => 'application/json, text/*',
|
|
139
146
|
'Authorization' => "Etna #{@token}"
|
|
140
|
-
}
|
|
147
|
+
}.update(
|
|
148
|
+
@request_headers || {}
|
|
149
|
+
)
|
|
141
150
|
end
|
|
142
151
|
|
|
143
152
|
def status_check!(response)
|
|
@@ -164,7 +173,7 @@ module Etna
|
|
|
164
173
|
verify_mode = @ignore_ssl ?
|
|
165
174
|
OpenSSL::SSL::VERIFY_NONE :
|
|
166
175
|
OpenSSL::SSL::VERIFY_PEER
|
|
167
|
-
Net::HTTP.start(uri.host, uri.port, use_ssl: true, verify_mode: verify_mode) do |http|
|
|
176
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: true, verify_mode: verify_mode, read_timeout: 300) do |http|
|
|
168
177
|
http.request(data) do |response|
|
|
169
178
|
status_check!(response)
|
|
170
179
|
yield response
|
|
@@ -174,7 +183,7 @@ module Etna
|
|
|
174
183
|
verify_mode = @ignore_ssl ?
|
|
175
184
|
OpenSSL::SSL::VERIFY_NONE :
|
|
176
185
|
OpenSSL::SSL::VERIFY_PEER
|
|
177
|
-
Net::HTTP.start(uri.host, uri.port, use_ssl: true, verify_mode: verify_mode) do |http|
|
|
186
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: true, verify_mode: verify_mode, read_timeout: 300) do |http|
|
|
178
187
|
response = http.request(data)
|
|
179
188
|
status_check!(response)
|
|
180
189
|
return response
|
|
@@ -8,10 +8,9 @@ module Etna
|
|
|
8
8
|
attr_reader :host, :token, :ignore_ssl
|
|
9
9
|
def initialize(host:, token:, ignore_ssl: false)
|
|
10
10
|
raise "#{self.class.name} client configuration is missing host." unless host
|
|
11
|
-
raise "#{self.class.name} client configuration is missing token." unless token
|
|
12
11
|
|
|
13
12
|
@token = token
|
|
14
|
-
raise "Your token is expired." if token_expired?
|
|
13
|
+
raise "Your token is expired." if token && token_expired?
|
|
15
14
|
|
|
16
15
|
@etna_client = ::Etna::Client.new(
|
|
17
16
|
host,
|
|
@@ -30,10 +29,11 @@ module Etna
|
|
|
30
29
|
def token_will_expire?(offset=3000)
|
|
31
30
|
# offset in seconds
|
|
32
31
|
# Will the user's token expire in the given amount of time?
|
|
33
|
-
|
|
32
|
+
payload = JSON.parse(Base64.urlsafe_decode64(token.split('.')[1]))
|
|
33
|
+
epoch_seconds = payload["exp"]
|
|
34
34
|
expiration = DateTime.strptime(epoch_seconds.to_s, "%s")
|
|
35
35
|
expiration <= DateTime.now.new_offset + offset
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
|
-
end
|
|
39
|
+
end
|
data/lib/etna/clients/janus.rb
CHANGED
|
@@ -57,6 +57,24 @@ module Etna
|
|
|
57
57
|
|
|
58
58
|
TokenResponse.new(token)
|
|
59
59
|
end
|
|
60
|
+
|
|
61
|
+
def validate_task_token
|
|
62
|
+
@etna_client.post('/api/tokens/validate_task')
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def get_nonce
|
|
66
|
+
@etna_client.get('/api/tokens/nonce').body
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def generate_token(token_type, signed_nonce: nil, project_name: nil, read_only: false)
|
|
70
|
+
response = @etna_client.with_headers(
|
|
71
|
+
'Authorization' => signed_nonce ? "Signed-Nonce #{signed_nonce}" : nil
|
|
72
|
+
) do
|
|
73
|
+
post('/api/tokens/generate', token_type: token_type, project_name: project_name, read_only: read_only)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
response.body
|
|
77
|
+
end
|
|
60
78
|
end
|
|
61
79
|
end
|
|
62
80
|
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require_relative './workflows/generate_token_workflow'
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Base workflow for setting up a project by a super user.
|
|
2
|
+
# 1) Creates the project in janus
|
|
3
|
+
# 2) Adds administrator(s) to Janus
|
|
4
|
+
# 3) Refreshes the user's token with the new privileges.
|
|
5
|
+
# 4) Creates the project in .
|
|
6
|
+
|
|
7
|
+
require 'base64'
|
|
8
|
+
require 'json'
|
|
9
|
+
require 'ostruct'
|
|
10
|
+
require_relative '../models'
|
|
11
|
+
|
|
12
|
+
module Etna
|
|
13
|
+
module Clients
|
|
14
|
+
class Janus
|
|
15
|
+
class GenerateTokenWorkflow < Struct.new(:janus_client, :email, :project_name, :token_type, :private_key_file, keyword_init: true)
|
|
16
|
+
def generate!
|
|
17
|
+
nonce = janus_client.get_nonce
|
|
18
|
+
|
|
19
|
+
unless email
|
|
20
|
+
puts "Email address for #{janus_client.host} account?"
|
|
21
|
+
email = STDIN.gets.chomp
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
if use_nonce?
|
|
25
|
+
until private_key_file
|
|
26
|
+
puts "Location of private key file?"
|
|
27
|
+
private_key_file = ::File.expand_path(STDIN.gets.chomp)
|
|
28
|
+
unless File.exists?(private_key_file)
|
|
29
|
+
puts "No such file."
|
|
30
|
+
private_key_file = nil
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
if needs_project_name?
|
|
36
|
+
puts "Project name?"
|
|
37
|
+
project_name = STDIN.gets.chomp
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
token = janus_client.generate_token(token_type, signed_nonce: use_nonce? ? signed_nonce(nonce) : nil, project_name: project_name)
|
|
41
|
+
|
|
42
|
+
puts token
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def signed_nonce(nonce)
|
|
48
|
+
private_key = OpenSSL::PKey::RSA.new(File.read(private_key_file))
|
|
49
|
+
|
|
50
|
+
txt_to_sign = "#{nonce}.#{Base64.strict_encode64(email)}"
|
|
51
|
+
|
|
52
|
+
sig = Base64.strict_encode64(
|
|
53
|
+
private_key.sign(OpenSSL::Digest::SHA256.new,txt_to_sign)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
"#{txt_to_sign}.#{sig}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def use_nonce?
|
|
60
|
+
!task_token? || !janus_client.token
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def needs_project_name?
|
|
64
|
+
task_token? && !project_name
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def task_token?
|
|
68
|
+
token_type == 'task'
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def user
|
|
72
|
+
@user ||= JSON.parse(Base64.urlsafe_decode64(magma_client.token.split('.')[1]))
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -138,7 +138,7 @@ module Etna
|
|
|
138
138
|
attribute_type: attribute.attribute_type,
|
|
139
139
|
link_model_name: attribute.link_model_name,
|
|
140
140
|
reciprocal_link_type: models.find_reciprocal(model: model, attribute: attribute)&.attribute_type,
|
|
141
|
-
description: attribute.
|
|
141
|
+
description: attribute.description,
|
|
142
142
|
display_name: attribute.display_name,
|
|
143
143
|
match: attribute.match,
|
|
144
144
|
format_hint: attribute.format_hint,
|
|
@@ -175,7 +175,7 @@ module Etna
|
|
|
175
175
|
# This should line up with the attribute names _on the model itself_.
|
|
176
176
|
ATTRIBUTE_ROW_ENTRIES = [
|
|
177
177
|
:attribute_type,
|
|
178
|
-
:link_model_name, :
|
|
178
|
+
:link_model_name, :description,
|
|
179
179
|
:display_name, :format_hint,
|
|
180
180
|
:restricted, :read_only,
|
|
181
181
|
:validation, :attribute_group,
|
|
@@ -188,7 +188,6 @@ module Etna
|
|
|
188
188
|
|
|
189
189
|
def format_row(row)
|
|
190
190
|
replace_row_column(row, :attribute_type) { |s| AttributeType.new(s) }
|
|
191
|
-
replace_row_column(row, :desc) { row.delete(:description) }
|
|
192
191
|
replace_row_column(row, :restricted, &COLUMN_AS_BOOLEAN)
|
|
193
192
|
replace_row_column(row, :read_only, &COLUMN_AS_BOOLEAN)
|
|
194
193
|
replace_row_column(row, :options) { |s| {"type" => "Array", "value" => s.split(',').map(&:strip)} }
|
|
@@ -250,7 +249,7 @@ module Etna
|
|
|
250
249
|
parent_att.name = parent_att.attribute_name = parent_model_name
|
|
251
250
|
parent_att.attribute_type = Etna::Clients::Magma::AttributeType::PARENT
|
|
252
251
|
parent_att.link_model_name = parent_model_name
|
|
253
|
-
parent_att.
|
|
252
|
+
parent_att.description = prettify(parent_model_name)
|
|
254
253
|
parent_att.display_name = prettify(parent_model_name)
|
|
255
254
|
end
|
|
256
255
|
end
|
|
@@ -275,7 +274,7 @@ module Etna
|
|
|
275
274
|
attr.attribute_name = attr.name = template.name
|
|
276
275
|
attr.attribute_type = parent_link_type
|
|
277
276
|
attr.link_model_name = template.name
|
|
278
|
-
attr.
|
|
277
|
+
attr.description = prettify(template.name)
|
|
279
278
|
attr.display_name = prettify(template.name)
|
|
280
279
|
end
|
|
281
280
|
end
|
|
@@ -338,7 +337,7 @@ module Etna
|
|
|
338
337
|
models.build_model(att.link_model_name).build_template.build_attributes.build_attribute(template.name).tap do |rec_att|
|
|
339
338
|
rec_att.attribute_name = rec_att.name = template.name
|
|
340
339
|
rec_att.display_name = prettify(template.name)
|
|
341
|
-
rec_att.
|
|
340
|
+
rec_att.description = prettify(template.name)
|
|
342
341
|
rec_att.attribute_type = Etna::Clients::Magma::AttributeType::COLLECTION
|
|
343
342
|
rec_att.link_model_name = template.name
|
|
344
343
|
end
|
|
@@ -246,7 +246,7 @@ module Etna
|
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
params['redcap:TextValidationType'] = redcap_text_validation_map[attribute_type] if redcap_text_validation_map[attribute_type]
|
|
249
|
-
params['redcap:FieldNote'] = attribute.
|
|
249
|
+
params['redcap:FieldNote'] = attribute.description if attribute.description
|
|
250
250
|
xml.ItemDef(params) do
|
|
251
251
|
xml.Question do
|
|
252
252
|
xml.send('TranslatedText', attribute_name.capitalize)
|
|
@@ -10,7 +10,7 @@ require_relative '../base_client'
|
|
|
10
10
|
module Etna
|
|
11
11
|
module Clients
|
|
12
12
|
class Magma < Etna::Clients::BaseClient
|
|
13
|
-
class RetrievalRequest < Struct.new(:model_name, :attribute_names, :record_names, :project_name, :page, :page_size, :order, :filter, keyword_init: true)
|
|
13
|
+
class RetrievalRequest < Struct.new(:model_name, :attribute_names, :record_names, :project_name, :page, :page_size, :order, :filter, :hide_templates, keyword_init: true)
|
|
14
14
|
include JsonSerializableStruct
|
|
15
15
|
|
|
16
16
|
def initialize(**params)
|
|
@@ -18,7 +18,7 @@ module Etna
|
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
class QueryRequest < Struct.new(:query, :project_name, keyword_init: true)
|
|
21
|
+
class QueryRequest < Struct.new(:query, :project_name, :order, :page, :page_size, keyword_init: true)
|
|
22
22
|
include JsonSerializableStruct
|
|
23
23
|
end
|
|
24
24
|
|
|
@@ -117,14 +117,6 @@ module Etna
|
|
|
117
117
|
super({action_name: 'update_attribute'}.update(args))
|
|
118
118
|
end
|
|
119
119
|
|
|
120
|
-
def desc=(val)
|
|
121
|
-
self.description = val
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def desc
|
|
125
|
-
self.description
|
|
126
|
-
end
|
|
127
|
-
|
|
128
120
|
def as_json
|
|
129
121
|
super(keep_nils: true)
|
|
130
122
|
end
|
|
@@ -454,6 +446,16 @@ module Etna
|
|
|
454
446
|
@raw = raw
|
|
455
447
|
end
|
|
456
448
|
|
|
449
|
+
def is_edited?(other)
|
|
450
|
+
# Don't just override == in case need to do a full comparison.
|
|
451
|
+
editable_attribute_names = Attribute::EDITABLE_ATTRIBUTE_ATTRIBUTES.map(&:to_s)
|
|
452
|
+
|
|
453
|
+
self_editable = raw.slice(*editable_attribute_names)
|
|
454
|
+
other_editable = other.raw.slice(*editable_attribute_names)
|
|
455
|
+
|
|
456
|
+
self_editable != other_editable
|
|
457
|
+
end
|
|
458
|
+
|
|
457
459
|
# Sets certain attribute fields which are implicit, even when not set, to match server behavior.
|
|
458
460
|
def set_field_defaults!
|
|
459
461
|
@raw.replace({
|
|
@@ -511,24 +513,12 @@ module Etna
|
|
|
511
513
|
raw['unique'] = val
|
|
512
514
|
end
|
|
513
515
|
|
|
514
|
-
def desc
|
|
515
|
-
raw['desc']
|
|
516
|
-
end
|
|
517
|
-
|
|
518
|
-
def desc=(val)
|
|
519
|
-
@raw['desc'] = val
|
|
520
|
-
end
|
|
521
|
-
|
|
522
|
-
# description and description= are needed
|
|
523
|
-
# to make UpdateAttribute actions
|
|
524
|
-
# work in the model_synchronization_workflow for
|
|
525
|
-
# desc.
|
|
526
516
|
def description
|
|
527
|
-
raw['
|
|
517
|
+
raw['description']
|
|
528
518
|
end
|
|
529
519
|
|
|
530
520
|
def description=(val)
|
|
531
|
-
@raw['
|
|
521
|
+
@raw['description'] = val
|
|
532
522
|
end
|
|
533
523
|
|
|
534
524
|
def display_name
|
|
@@ -595,11 +585,8 @@ module Etna
|
|
|
595
585
|
raw['options']
|
|
596
586
|
end
|
|
597
587
|
|
|
598
|
-
# NOTE! The Attribute class returns description as desc, where as actions take it in as description.
|
|
599
|
-
# There are shortcut methods that try to handle this on the action class side of things. Ideally we would
|
|
600
|
-
# make this more consistent in the near future.
|
|
601
588
|
COPYABLE_ATTRIBUTE_ATTRIBUTES = [
|
|
602
|
-
:attribute_name, :attribute_type, :
|
|
589
|
+
:attribute_name, :attribute_type, :display_name, :format_hint,
|
|
603
590
|
:hidden, :link_model_name, :read_only, :attribute_group, :unique, :validation,
|
|
604
591
|
:restricted, :description
|
|
605
592
|
]
|
|
@@ -95,7 +95,9 @@ module Etna
|
|
|
95
95
|
file_path = ::File.dirname(file_path)
|
|
96
96
|
{attribute_name => "https://metis.ucsf.edu/#{project_name}/browse/#{bucket_name}/#{file_path}"}
|
|
97
97
|
else
|
|
98
|
-
{attribute_name => {
|
|
98
|
+
{attribute_name => {
|
|
99
|
+
path: "metis://#{project_name}/#{bucket_name}/#{file_path}",
|
|
100
|
+
original_filename: File.basename(file_path)}}
|
|
99
101
|
end
|
|
100
102
|
end
|
|
101
103
|
|
|
@@ -190,10 +190,7 @@ module Etna
|
|
|
190
190
|
source_attribute = Attribute.new(source_attribute.raw)
|
|
191
191
|
source_attribute.set_field_defaults!
|
|
192
192
|
|
|
193
|
-
|
|
194
|
-
target_editable = target_attribute.raw.slice(*Attribute::EDITABLE_ATTRIBUTE_ATTRIBUTES.map(&:to_s))
|
|
195
|
-
|
|
196
|
-
if source_editable == target_editable
|
|
193
|
+
if !source_attribute.is_edited?(target_attribute)
|
|
197
194
|
return
|
|
198
195
|
end
|
|
199
196
|
|
|
@@ -12,14 +12,37 @@ module Etna
|
|
|
12
12
|
@template_for = {}
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
def all_attributes(template:)
|
|
16
|
+
template.attributes.attribute_keys
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def safe_group_attributes(template:, attributes_mask:)
|
|
20
|
+
result = []
|
|
21
|
+
|
|
22
|
+
template.attributes.attribute_keys.each do |attribute_name|
|
|
23
|
+
next if template.attributes.attribute(attribute_name).attribute_type == Etna::Clients::Magma::AttributeType::TABLE
|
|
24
|
+
result << attribute_name if attribute_included?(attributes_mask, attribute_name)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
result
|
|
28
|
+
end
|
|
29
|
+
|
|
15
30
|
def masked_attributes(template:, model_attributes_mask:, model_name:)
|
|
16
31
|
attributes_mask = model_attributes_mask[model_name]
|
|
17
|
-
|
|
18
|
-
|
|
32
|
+
|
|
33
|
+
if attributes_mask.nil?
|
|
34
|
+
return [
|
|
35
|
+
safe_group_attributes(template: template, attributes_mask: attributes_mask),
|
|
36
|
+
all_attributes(template: template)
|
|
37
|
+
]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
[(safe_group_attributes(template: template, attributes_mask: attributes_mask) + [template.identifier, 'parent']).uniq,
|
|
41
|
+
attributes_mask]
|
|
19
42
|
end
|
|
20
43
|
|
|
21
44
|
def attribute_included?(mask, attribute_name)
|
|
22
|
-
return true if mask
|
|
45
|
+
return true if mask.nil?
|
|
23
46
|
mask.include?(attribute_name)
|
|
24
47
|
end
|
|
25
48
|
|
|
@@ -48,7 +71,11 @@ module Etna
|
|
|
48
71
|
seen.add([path[:from], model_name])
|
|
49
72
|
|
|
50
73
|
template = template_for(model_name)
|
|
51
|
-
query_attributes, walk_attributes = masked_attributes(
|
|
74
|
+
query_attributes, walk_attributes = masked_attributes(
|
|
75
|
+
template: template,
|
|
76
|
+
model_attributes_mask: model_attributes_mask,
|
|
77
|
+
model_name: model_name
|
|
78
|
+
)
|
|
52
79
|
|
|
53
80
|
request = RetrievalRequest.new(
|
|
54
81
|
project_name: magma_crud.project_name,
|
|
@@ -62,6 +89,7 @@ module Etna
|
|
|
62
89
|
related_models = {}
|
|
63
90
|
|
|
64
91
|
magma_crud.page_records(model_name, request) do |response|
|
|
92
|
+
logger&.info("Fetched page of #{model_name}")
|
|
65
93
|
tables = []
|
|
66
94
|
collections = []
|
|
67
95
|
links = []
|
|
@@ -70,13 +98,15 @@ module Etna
|
|
|
70
98
|
model = response.models.model(model_name)
|
|
71
99
|
|
|
72
100
|
template.attributes.attribute_keys.each do |attr_name|
|
|
101
|
+
attr = template.attributes.attribute(attr_name)
|
|
102
|
+
if attr.attribute_type == AttributeType::TABLE && attribute_included?(walk_attributes, attr_name)
|
|
103
|
+
tables << attr_name
|
|
104
|
+
end
|
|
105
|
+
|
|
73
106
|
next unless attribute_included?(query_attributes, attr_name)
|
|
74
107
|
attributes << attr_name
|
|
75
108
|
|
|
76
|
-
attr
|
|
77
|
-
if attr.attribute_type == AttributeType::TABLE
|
|
78
|
-
tables << attr_name
|
|
79
|
-
elsif attr.attribute_type == AttributeType::COLLECTION
|
|
109
|
+
if attr.attribute_type == AttributeType::COLLECTION
|
|
80
110
|
related_models[attr.link_model_name] ||= Set.new
|
|
81
111
|
collections << attr_name
|
|
82
112
|
elsif attr.attribute_type == AttributeType::LINK
|
|
@@ -91,14 +121,32 @@ module Etna
|
|
|
91
121
|
end
|
|
92
122
|
end
|
|
93
123
|
|
|
124
|
+
table_data = {}
|
|
125
|
+
# Request tables in an inner chunk.
|
|
126
|
+
tables.each do |table_attr|
|
|
127
|
+
request = RetrievalRequest.new(
|
|
128
|
+
project_name: magma_crud.project_name,
|
|
129
|
+
model_name: model_name,
|
|
130
|
+
record_names: model.documents.document_keys,
|
|
131
|
+
attribute_names: [table_attr],
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
logger&.info("Fetching inner table #{table_attr}...")
|
|
135
|
+
|
|
136
|
+
table_response = magma_crud.magma_client.retrieve(request)
|
|
137
|
+
d = table_response.models.model(model_name).documents
|
|
138
|
+
table_d = table_response.models.model(table_attr).documents
|
|
139
|
+
table_data[table_attr] = d.document_keys.map do |id|
|
|
140
|
+
[id, d.document(id)[table_attr].map { |tid| table_d.document(tid) }]
|
|
141
|
+
end.to_h
|
|
142
|
+
end
|
|
143
|
+
|
|
94
144
|
model.documents.document_keys.each do |key|
|
|
95
145
|
record = model.documents.document(key).slice(*attributes)
|
|
96
146
|
|
|
97
147
|
# Inline tables inside the record
|
|
98
148
|
tables.each do |table_attr|
|
|
99
|
-
record[table_attr] =
|
|
100
|
-
response.models.model(template.attributes.attribute(table_attr).link_model_name).documents.document(id)
|
|
101
|
-
end unless record[table_attr].nil?
|
|
149
|
+
record[table_attr] = table_data[table_attr][key] unless table_data[table_attr].nil?
|
|
102
150
|
end
|
|
103
151
|
|
|
104
152
|
collections.each do |collection_attr|
|
data/lib/etna/command.rb
CHANGED
|
@@ -203,6 +203,7 @@ module Etna
|
|
|
203
203
|
@subcommands ||= self.class.constants.sort.reduce({}) do |acc, n|
|
|
204
204
|
acc.tap do
|
|
205
205
|
c = self.class.const_get(n)
|
|
206
|
+
next unless c.respond_to?(:instance_methods)
|
|
206
207
|
next unless c.instance_methods.include?(:find_command)
|
|
207
208
|
v = c.new(self)
|
|
208
209
|
acc[v.command_name] = v
|
data/lib/etna/directed_graph.rb
CHANGED
|
@@ -7,6 +7,64 @@ class DirectedGraph
|
|
|
7
7
|
attr_reader :children
|
|
8
8
|
attr_reader :parents
|
|
9
9
|
|
|
10
|
+
def full_parentage(n)
|
|
11
|
+
[].tap do |result|
|
|
12
|
+
q = @parents[n].keys.dup
|
|
13
|
+
seen = Set.new
|
|
14
|
+
|
|
15
|
+
until q.empty?
|
|
16
|
+
n = q.shift
|
|
17
|
+
next if seen.include?(n)
|
|
18
|
+
seen.add(n)
|
|
19
|
+
|
|
20
|
+
result << n
|
|
21
|
+
q.push(*@parents[n].keys)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
result.uniq!
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def as_normalized_hash(root, include_root = true)
|
|
29
|
+
q = [root]
|
|
30
|
+
{}.tap do |result|
|
|
31
|
+
if include_root
|
|
32
|
+
result[root] = []
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
seen = Set.new
|
|
36
|
+
|
|
37
|
+
until q.empty?
|
|
38
|
+
n = q.shift
|
|
39
|
+
next if seen.include?(n)
|
|
40
|
+
seen.add(n)
|
|
41
|
+
|
|
42
|
+
parentage = full_parentage(n)
|
|
43
|
+
|
|
44
|
+
@children[n].keys.each do |child_node|
|
|
45
|
+
q << child_node
|
|
46
|
+
|
|
47
|
+
if result.include?(n)
|
|
48
|
+
result[n] << child_node
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
parentage.each do |grandparent|
|
|
52
|
+
result[grandparent] << child_node if result.include?(grandparent)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Depending on the graph shape, diamonds could lead to
|
|
56
|
+
# resetting of previously calculated dependencies.
|
|
57
|
+
# Here we avoid resetting existing entries in `result`
|
|
58
|
+
# and instead concatenate them if they already exist.
|
|
59
|
+
result[child_node] = [] unless result.include?(child_node)
|
|
60
|
+
result[n].concat(result[child_node]) if result.include?(child_node) && result.include?(n)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
result.values.each(&:uniq!)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
10
68
|
def add_connection(parent, child)
|
|
11
69
|
children = @children[parent] ||= {}
|
|
12
70
|
child_children = @children[child] ||= {}
|
|
@@ -18,12 +76,13 @@ class DirectedGraph
|
|
|
18
76
|
parents[parent] = parent_parents
|
|
19
77
|
end
|
|
20
78
|
|
|
21
|
-
def serialized_path_from(root)
|
|
79
|
+
def serialized_path_from(root, include_root = true)
|
|
22
80
|
seen = Set.new
|
|
23
81
|
[].tap do |result|
|
|
24
|
-
result << root
|
|
82
|
+
result << root if include_root
|
|
25
83
|
seen.add(root)
|
|
26
|
-
path_q = paths_from(root)
|
|
84
|
+
path_q = paths_from(root, include_root)
|
|
85
|
+
traversables = path_q.flatten
|
|
27
86
|
|
|
28
87
|
until path_q.empty?
|
|
29
88
|
next_path = path_q.shift
|
|
@@ -34,7 +93,7 @@ class DirectedGraph
|
|
|
34
93
|
next if next_n.nil?
|
|
35
94
|
next if seen.include?(next_n)
|
|
36
95
|
|
|
37
|
-
if @parents[next_n].keys.any? { |p| !seen.include?(p) }
|
|
96
|
+
if @parents[next_n].keys.any? { |p| !seen.include?(p) && traversables.include?(p) }
|
|
38
97
|
next_path.unshift(next_n)
|
|
39
98
|
path_q.push(next_path)
|
|
40
99
|
break
|
|
@@ -47,9 +106,9 @@ class DirectedGraph
|
|
|
47
106
|
end
|
|
48
107
|
end
|
|
49
108
|
|
|
50
|
-
def paths_from(root)
|
|
109
|
+
def paths_from(root, include_root = true)
|
|
51
110
|
[].tap do |result|
|
|
52
|
-
parents_of_map = descendants(root)
|
|
111
|
+
parents_of_map = descendants(root, include_root)
|
|
53
112
|
seen = Set.new
|
|
54
113
|
|
|
55
114
|
parents_of_map.to_a.sort_by { |k, parents| [-parents.length, k.inspect] }.each do |k, parents|
|
|
@@ -68,7 +127,7 @@ class DirectedGraph
|
|
|
68
127
|
end
|
|
69
128
|
end
|
|
70
129
|
|
|
71
|
-
def descendants(parent)
|
|
130
|
+
def descendants(parent, include_root = true)
|
|
72
131
|
seen = Set.new
|
|
73
132
|
|
|
74
133
|
seen.add(parent)
|
|
@@ -90,7 +149,7 @@ class DirectedGraph
|
|
|
90
149
|
while child = queue.pop
|
|
91
150
|
next if seen.include? child
|
|
92
151
|
seen.add(child)
|
|
93
|
-
path = (paths[child] ||= [parent])
|
|
152
|
+
path = (paths[child] ||= (include_root ? [parent] : []))
|
|
94
153
|
|
|
95
154
|
@children[child].keys.each do |child_child|
|
|
96
155
|
queue.push child_child
|
data/lib/etna/metrics.rb
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
|
|
2
|
+
module Etna
|
|
3
|
+
class MetricsExporter
|
|
4
|
+
def initialize(app, path: '/metrics')
|
|
5
|
+
@app = app
|
|
6
|
+
@path = path
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def exporter
|
|
10
|
+
@exporter ||= begin
|
|
11
|
+
exporter = Yabeda::Prometheus::Exporter.new(@app, path: @path)
|
|
12
|
+
Rack::Auth::Basic.new(exporter) do |user, pw|
|
|
13
|
+
user == 'prometheus' && pw == ENV['METRICS_PW']
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def call(env)
|
|
19
|
+
if env['PATH_INFO'] == @path
|
|
20
|
+
exporter.call(env)
|
|
21
|
+
else
|
|
22
|
+
@app.call(env)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/etna/route.rb
CHANGED
|
@@ -91,6 +91,10 @@ module Etna
|
|
|
91
91
|
@auth && @auth[:noauth]
|
|
92
92
|
end
|
|
93
93
|
|
|
94
|
+
def ignore_janus?
|
|
95
|
+
@auth && @auth[:ignore_janus]
|
|
96
|
+
end
|
|
97
|
+
|
|
94
98
|
private
|
|
95
99
|
|
|
96
100
|
def application
|
|
@@ -144,7 +148,10 @@ module Etna
|
|
|
144
148
|
params = request.env['rack.request.params']
|
|
145
149
|
|
|
146
150
|
@auth[:user].all? do |constraint, param_name|
|
|
147
|
-
user.respond_to?(constraint) &&
|
|
151
|
+
user.respond_to?(constraint) && (
|
|
152
|
+
param_name.is_a?(Symbol) ?
|
|
153
|
+
user.send(constraint, params[param_name]) :
|
|
154
|
+
user.send(constraint, param_name))
|
|
148
155
|
end
|
|
149
156
|
end
|
|
150
157
|
|
data/lib/etna/server.rb
CHANGED
|
@@ -74,6 +74,11 @@ module Etna
|
|
|
74
74
|
def initialize
|
|
75
75
|
# Setup logging.
|
|
76
76
|
application.setup_logger
|
|
77
|
+
|
|
78
|
+
# This needs to be required before yabeda invocation, but cannot belong at the top of any module since clients
|
|
79
|
+
# do not install yabeda.
|
|
80
|
+
require 'yabeda'
|
|
81
|
+
application.setup_yabeda
|
|
77
82
|
end
|
|
78
83
|
|
|
79
84
|
private
|
data/lib/etna/spec/vcr.rb
CHANGED
|
@@ -4,13 +4,32 @@ require 'openssl'
|
|
|
4
4
|
require 'digest/sha2'
|
|
5
5
|
require 'base64'
|
|
6
6
|
|
|
7
|
-
def setup_base_vcr(spec_helper_dir)
|
|
7
|
+
def setup_base_vcr(spec_helper_dir, server: nil, application: nil)
|
|
8
8
|
VCR.configure do |c|
|
|
9
9
|
c.hook_into :webmock
|
|
10
10
|
c.cassette_serializers
|
|
11
11
|
c.cassette_library_dir = ::File.join(spec_helper_dir, 'fixtures', 'cassettes')
|
|
12
12
|
c.allow_http_connections_when_no_cassette = true
|
|
13
13
|
|
|
14
|
+
c.register_request_matcher :verify_uri_route do |request_1, request_2|
|
|
15
|
+
next true if server.nil? || application.nil?
|
|
16
|
+
|
|
17
|
+
route_match = request_1.uri =~ /https:\/\/#{application.dev_route}(.*)/
|
|
18
|
+
if route_match && route_match[1]
|
|
19
|
+
def request_1.request_method
|
|
20
|
+
method.to_s.upcase
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def request_1.path
|
|
24
|
+
URI(uri).path
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
!!server.find_route(request_1)
|
|
28
|
+
else
|
|
29
|
+
true
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
14
33
|
c.register_request_matcher :try_body do |request_1, request_2|
|
|
15
34
|
if request_1.headers['Content-Type'].first =~ /application\/json/
|
|
16
35
|
if request_2.headers['Content-Type'].first =~ /application\/json/
|
|
@@ -39,7 +58,7 @@ def setup_base_vcr(spec_helper_dir)
|
|
|
39
58
|
else
|
|
40
59
|
ENV['RERECORD'] ? :all : :once
|
|
41
60
|
end,
|
|
42
|
-
match_requests_on: [:method, :uri, :try_body]
|
|
61
|
+
match_requests_on: [:method, :uri, :try_body, :verify_uri_route]
|
|
43
62
|
}
|
|
44
63
|
|
|
45
64
|
# Filter the authorization headers of any request by replacing any occurrence of that request's
|
data/lib/etna/test_auth.rb
CHANGED
|
@@ -42,7 +42,6 @@ module Etna
|
|
|
42
42
|
# We do this to support Metis client tests, we pass in tokens with multiple "."-separated parts, so
|
|
43
43
|
# have to account for that.
|
|
44
44
|
payload = JSON.parse(Base64.decode64(token.split('.')[1]))
|
|
45
|
-
|
|
46
45
|
request.env['etna.user'] = Etna::User.new(payload.map{|k,v| [k.to_sym, v]}.to_h, token)
|
|
47
46
|
end
|
|
48
47
|
|
data/lib/etna/user.rb
CHANGED
|
@@ -7,7 +7,7 @@ module Etna
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
def initialize params, token=nil
|
|
10
|
-
@name, @email, @encoded_permissions, encoded_flags = params.values_at(:name, :email, :perm, :flags)
|
|
10
|
+
@name, @email, @encoded_permissions, encoded_flags, @task = params.values_at(:name, :email, :perm, :flags, :task)
|
|
11
11
|
|
|
12
12
|
@flags = encoded_flags&.split(/;/) || []
|
|
13
13
|
@token = token unless !token
|
|
@@ -16,6 +16,10 @@ module Etna
|
|
|
16
16
|
|
|
17
17
|
attr_reader :name, :email, :token
|
|
18
18
|
|
|
19
|
+
def task?
|
|
20
|
+
!!@task
|
|
21
|
+
end
|
|
22
|
+
|
|
19
23
|
def permissions
|
|
20
24
|
@permissions ||= @encoded_permissions.split(/\;/).map do |roles|
|
|
21
25
|
role, projects = roles.split(/:/)
|
data/lib/helpers.rb
CHANGED
|
@@ -57,9 +57,9 @@ module WithEtnaClients
|
|
|
57
57
|
**EtnaApp.instance.config(:metis, environment) || {})
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
def janus_client
|
|
60
|
+
def janus_client(opts={})
|
|
61
61
|
@janus_client ||= Etna::Clients::Janus.new(
|
|
62
|
-
token: token,
|
|
62
|
+
token: opts.has_key?(:token) ? opts[:token] : token,
|
|
63
63
|
ignore_ssl: EtnaApp.instance.config(:ignore_ssl),
|
|
64
64
|
**EtnaApp.instance.config(:janus, environment) || {})
|
|
65
65
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: etna
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.37
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Saurabh Asthana
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2021-
|
|
11
|
+
date: 2021-05-27 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rack
|
|
@@ -94,20 +94,6 @@ dependencies:
|
|
|
94
94
|
- - ">="
|
|
95
95
|
- !ruby/object:Gem::Version
|
|
96
96
|
version: '0'
|
|
97
|
-
- !ruby/object:Gem::Dependency
|
|
98
|
-
name: concurrent-ruby-ext
|
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
|
100
|
-
requirements:
|
|
101
|
-
- - ">="
|
|
102
|
-
- !ruby/object:Gem::Version
|
|
103
|
-
version: '0'
|
|
104
|
-
type: :runtime
|
|
105
|
-
prerelease: false
|
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
-
requirements:
|
|
108
|
-
- - ">="
|
|
109
|
-
- !ruby/object:Gem::Version
|
|
110
|
-
version: '0'
|
|
111
97
|
description: See summary
|
|
112
98
|
email: Saurabh.Asthana@ucsf.edu
|
|
113
99
|
executables:
|
|
@@ -131,6 +117,8 @@ files:
|
|
|
131
117
|
- lib/etna/clients/janus.rb
|
|
132
118
|
- lib/etna/clients/janus/client.rb
|
|
133
119
|
- lib/etna/clients/janus/models.rb
|
|
120
|
+
- lib/etna/clients/janus/workflows.rb
|
|
121
|
+
- lib/etna/clients/janus/workflows/generate_token_workflow.rb
|
|
134
122
|
- lib/etna/clients/magma.rb
|
|
135
123
|
- lib/etna/clients/magma/client.rb
|
|
136
124
|
- lib/etna/clients/magma/formatting.rb
|
|
@@ -180,6 +168,7 @@ files:
|
|
|
180
168
|
- lib/etna/hmac.rb
|
|
181
169
|
- lib/etna/json_serializable_struct.rb
|
|
182
170
|
- lib/etna/logger.rb
|
|
171
|
+
- lib/etna/metrics.rb
|
|
183
172
|
- lib/etna/multipart_serializable_nested_hash.rb
|
|
184
173
|
- lib/etna/parse_body.rb
|
|
185
174
|
- lib/etna/route.rb
|
|
@@ -213,7 +202,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
213
202
|
version: '0'
|
|
214
203
|
requirements: []
|
|
215
204
|
rubyforge_project:
|
|
216
|
-
rubygems_version: 2.7.6.
|
|
205
|
+
rubygems_version: 2.7.6.3
|
|
217
206
|
signing_key:
|
|
218
207
|
specification_version: 4
|
|
219
208
|
summary: Base classes for Mount Etna applications
|