etna 0.1.47 → 0.1.48
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/etna.completion +10240 -7315
- data/lib/commands.rb +2 -2
- data/lib/etna/client.rb +235 -35
- data/lib/etna/clients/base_client.rb +3 -2
- data/lib/etna/clients/magma/workflows/walk_model_tree_workflow.rb +0 -1
- data/lib/etna/clients/metis/client.rb +2 -2
- data/lib/etna/spec/auth.rb +9 -1
- data/lib/helpers.rb +4 -2
- metadata +2 -2
data/lib/commands.rb
CHANGED
@@ -261,8 +261,8 @@ class EtnaApp
|
|
261
261
|
model_attributes_mask: model_attribute_pairs(project_name),
|
262
262
|
record_names: 'all',
|
263
263
|
model_filters: {},
|
264
|
-
metis_client: metis_client,
|
265
|
-
magma_client: magma_client,
|
264
|
+
metis_client: metis_client(logger: logger),
|
265
|
+
magma_client: magma_client(logger: logger),
|
266
266
|
logger: logger,
|
267
267
|
project_name: project_name,
|
268
268
|
model_name: 'project', filesystem: filesystem,
|
data/lib/etna/client.rb
CHANGED
@@ -4,10 +4,17 @@ require 'rack/utils'
|
|
4
4
|
|
5
5
|
module Etna
|
6
6
|
class Client
|
7
|
-
|
7
|
+
|
8
|
+
def initialize(host, token, routes_available: true, ignore_ssl: false, max_retries: 10, backoff_time: 15, logger: nil)
|
8
9
|
@host = host.sub(%r!/$!, '')
|
9
10
|
@token = token
|
10
11
|
@ignore_ssl = ignore_ssl
|
12
|
+
@max_retries = max_retries
|
13
|
+
@backoff_time = backoff_time
|
14
|
+
|
15
|
+
default_logger = ::Etna::Logger.new('/dev/stdout', 0, 1048576)
|
16
|
+
default_logger.level = ::Logger::WARN
|
17
|
+
@logger = logger || default_logger
|
11
18
|
|
12
19
|
if routes_available
|
13
20
|
set_routes
|
@@ -55,7 +62,7 @@ module Etna
|
|
55
62
|
uri = request_uri(endpoint)
|
56
63
|
multipart = Net::HTTP::Post::Multipart.new uri.request_uri, content
|
57
64
|
multipart.add_field('Authorization', "Etna #{@token}")
|
58
|
-
|
65
|
+
retrier.retry_request(uri, multipart, &block)
|
59
66
|
end
|
60
67
|
|
61
68
|
def post(endpoint, params = {}, &block)
|
@@ -80,9 +87,12 @@ module Etna
|
|
80
87
|
|
81
88
|
private
|
82
89
|
|
90
|
+
def retrier
|
91
|
+
@retrier ||= Retrier.new(ignore_ssl: @ignore_ssl, max_retries: @max_retries, backoff_time: @backoff_time, logger: @logger)
|
92
|
+
end
|
93
|
+
|
83
94
|
def set_routes
|
84
95
|
response = options('/')
|
85
|
-
status_check!(response)
|
86
96
|
@routes = JSON.parse(response.body, symbolize_names: true)
|
87
97
|
end
|
88
98
|
|
@@ -120,7 +130,7 @@ module Etna
|
|
120
130
|
uri = request_uri(endpoint)
|
121
131
|
req = type.new(uri.request_uri, request_headers)
|
122
132
|
req.body = params.to_json
|
123
|
-
|
133
|
+
retrier.retry_request(uri, req, &block)
|
124
134
|
end
|
125
135
|
|
126
136
|
def query_request(type, endpoint, params = {}, &block)
|
@@ -132,7 +142,7 @@ module Etna
|
|
132
142
|
uri.query = URI.encode_www_form(params)
|
133
143
|
end
|
134
144
|
req = type.new(uri.request_uri, request_headers)
|
135
|
-
|
145
|
+
retrier.retry_request(uri, req, &block)
|
136
146
|
end
|
137
147
|
|
138
148
|
def request_uri(endpoint)
|
@@ -140,6 +150,8 @@ module Etna
|
|
140
150
|
end
|
141
151
|
|
142
152
|
def request_headers
|
153
|
+
refresh_token
|
154
|
+
|
143
155
|
{
|
144
156
|
'Content-Type' => 'application/json',
|
145
157
|
'Accept' => 'application/json, text/*',
|
@@ -149,46 +161,234 @@ module Etna
|
|
149
161
|
)
|
150
162
|
end
|
151
163
|
|
152
|
-
def
|
153
|
-
|
154
|
-
if status >= 400
|
155
|
-
msg = response.content_type == 'application/json' ?
|
156
|
-
json_error(response.body) :
|
157
|
-
response.body
|
158
|
-
raise Etna::Error.new(msg, status)
|
159
|
-
end
|
164
|
+
def refresh_token
|
165
|
+
@token = TokenRefresher.new(@host, @token, @logger).active_token
|
160
166
|
end
|
161
167
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
+
class TokenRefresher
|
169
|
+
def initialize(host, token, logger)
|
170
|
+
@token = token
|
171
|
+
@host = host
|
172
|
+
@logger = logger
|
173
|
+
end
|
174
|
+
|
175
|
+
def active_token
|
176
|
+
token_will_expire? ?
|
177
|
+
refresh_token :
|
178
|
+
@token
|
179
|
+
end
|
180
|
+
|
181
|
+
private
|
182
|
+
|
183
|
+
def token_expired?
|
184
|
+
# Has the token already expired?
|
185
|
+
token_will_expire?(0)
|
186
|
+
end
|
187
|
+
|
188
|
+
def token_will_expire?(offset=3000)
|
189
|
+
return false if @token.nil?
|
190
|
+
|
191
|
+
# Will the user's token expire in the given amount of time?
|
192
|
+
payload = @token.split('.')[1]
|
193
|
+
return false if payload.nil?
|
194
|
+
|
195
|
+
epoch_seconds = JSON.parse(Base64.urlsafe_decode64(payload))["exp"]
|
196
|
+
|
197
|
+
return false if epoch_seconds.nil?
|
198
|
+
|
199
|
+
expiration = DateTime.strptime(epoch_seconds.to_s, "%s").to_time
|
200
|
+
expiration <= DateTime.now.new_offset.to_time + offset
|
201
|
+
end
|
202
|
+
|
203
|
+
def refresh_token
|
204
|
+
@logger.debug("Requesting a refreshed token.")
|
205
|
+
uri = refresh_uri
|
206
|
+
req = Net::HTTP::Post.new(uri.request_uri, request_headers)
|
207
|
+
retrier.retry_request(uri, req).body
|
208
|
+
end
|
209
|
+
|
210
|
+
def refresh_uri
|
211
|
+
URI("#{janus_host}#{refresh_endpoint}")
|
212
|
+
end
|
213
|
+
|
214
|
+
def janus_host
|
215
|
+
@host.gsub(/(metis|magma|timur|polyphemus|janus|gnomon)/, "janus")
|
216
|
+
end
|
217
|
+
|
218
|
+
def refresh_endpoint
|
219
|
+
"/api/tokens/generate"
|
220
|
+
end
|
221
|
+
|
222
|
+
def request_headers
|
223
|
+
{
|
224
|
+
'Content-Type' => 'application/json',
|
225
|
+
'Accept' => 'application/json, text/*',
|
226
|
+
'Authorization' => "Etna #{@token}"
|
227
|
+
}
|
228
|
+
end
|
229
|
+
|
230
|
+
def retrier
|
231
|
+
@retrier ||= Retrier.new(max_retries: 5, backoff_time: 10, logger: @logger)
|
168
232
|
end
|
169
233
|
end
|
170
234
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
235
|
+
class Retrier
|
236
|
+
# Ideally the retry code would be centralized with metis_client ...
|
237
|
+
# unsure what would be the best approach to do that, at this moment.
|
238
|
+
def initialize(ignore_ssl: false, max_retries: 10, backoff_time: 15, logger:)
|
239
|
+
@max_retries = max_retries
|
240
|
+
@ignore_ssl = ignore_ssl
|
241
|
+
@backoff_time = backoff_time
|
242
|
+
@logger = logger
|
243
|
+
end
|
244
|
+
|
245
|
+
def retry_request(uri, req, retries: 0, &block)
|
246
|
+
retries += 1
|
247
|
+
|
248
|
+
begin
|
249
|
+
@logger.debug("\rWaiting for #{uri.host} restart"+"."*retries+"\x1b[0K")
|
250
|
+
|
251
|
+
sleep @backoff_time * retries
|
252
|
+
end if retries > 1
|
253
|
+
|
254
|
+
if retries < @max_retries
|
255
|
+
begin
|
256
|
+
if block_given?
|
257
|
+
request(uri, req) do |block_response|
|
258
|
+
if net_exceptions.include?(block_response.class)
|
259
|
+
@logger.debug("Received #{block_response.class.name}, retrying")
|
260
|
+
retry_request(uri, req, retries: retries, &block)
|
261
|
+
elsif block_response.is_a?(OpenSSL::SSL::SSLError)
|
262
|
+
@logger.debug("SSL error, retrying")
|
263
|
+
retry_request(uri, req, retries: retries, &block)
|
264
|
+
else
|
265
|
+
status_check!(block_response)
|
266
|
+
yield block_response
|
267
|
+
end
|
268
|
+
end
|
269
|
+
else
|
270
|
+
response = request(uri, req)
|
271
|
+
end
|
272
|
+
rescue OpenSSL::SSL::SSLError => e
|
273
|
+
if e.message =~ /write client hello/
|
274
|
+
@logger.debug("SSL error, retrying")
|
275
|
+
return retry_request(uri, req, retries: retries)
|
276
|
+
end
|
277
|
+
raise e
|
278
|
+
rescue *net_exceptions => e
|
279
|
+
@logger.debug("Received #{e.class.name}, retrying")
|
280
|
+
return retry_request(uri, req, retries: retries)
|
281
|
+
end
|
282
|
+
|
283
|
+
begin
|
284
|
+
retry_codes = ['503', '502', '504', '408']
|
285
|
+
if retry_codes.include?(response.code)
|
286
|
+
@logger.debug("Received response with code #{response.code}, retrying")
|
287
|
+
return retry_request(uri, req, retries: retries)
|
288
|
+
elsif response.code == '500' && response.body.start_with?("Puma caught")
|
289
|
+
@logger.debug("Received 500 Puma error #{response.body.split("\n").first}, retrying")
|
290
|
+
return retry_request(uri, req, retries: retries)
|
291
|
+
end
|
292
|
+
|
178
293
|
status_check!(response)
|
179
|
-
|
294
|
+
return response
|
295
|
+
end unless block_given?
|
296
|
+
end
|
297
|
+
|
298
|
+
raise ::Etna::Error, "Could not contact server, giving up" unless block_given?
|
299
|
+
end
|
300
|
+
|
301
|
+
private
|
302
|
+
|
303
|
+
def request(uri, data)
|
304
|
+
if block_given?
|
305
|
+
verify_mode = @ignore_ssl ?
|
306
|
+
OpenSSL::SSL::VERIFY_NONE :
|
307
|
+
OpenSSL::SSL::VERIFY_PEER
|
308
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: true, verify_mode: verify_mode, read_timeout: 300) do |http|
|
309
|
+
http.request(data) do |response|
|
310
|
+
api_error_check!(response)
|
311
|
+
yield response
|
312
|
+
end
|
313
|
+
end
|
314
|
+
else
|
315
|
+
verify_mode = @ignore_ssl ?
|
316
|
+
OpenSSL::SSL::VERIFY_NONE :
|
317
|
+
OpenSSL::SSL::VERIFY_PEER
|
318
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: true, verify_mode: verify_mode, read_timeout: 300) do |http|
|
319
|
+
response = http.request(data)
|
320
|
+
api_error_check!(response)
|
321
|
+
return response
|
180
322
|
end
|
181
323
|
end
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
324
|
+
end
|
325
|
+
|
326
|
+
def api_error_check!(response)
|
327
|
+
status = response.code.to_i
|
328
|
+
raise_status_error(response) if 400 <= status && status < 500
|
329
|
+
end
|
330
|
+
|
331
|
+
def status_check!(response)
|
332
|
+
status = response.code.to_i
|
333
|
+
raise_status_error(response) if status >= 400
|
334
|
+
end
|
335
|
+
|
336
|
+
def raise_status_error(response)
|
337
|
+
msg = response.content_type == 'application/json' ?
|
338
|
+
json_error(response.body) :
|
339
|
+
response.body
|
340
|
+
raise Etna::Error.new(msg, response.code.to_i)
|
341
|
+
end
|
342
|
+
|
343
|
+
def json_error(body)
|
344
|
+
msg = JSON.parse(body, symbolize_names: true)
|
345
|
+
if (msg.has_key?(:errors) && msg[:errors].is_a?(Array))
|
346
|
+
return JSON.generate(msg[:errors])
|
347
|
+
elsif msg.has_key?(:error)
|
348
|
+
return JSON.generate(msg[:error])
|
190
349
|
end
|
191
350
|
end
|
351
|
+
|
352
|
+
def net_exceptions
|
353
|
+
retry_exceptions = [
|
354
|
+
Errno::ECONNREFUSED,
|
355
|
+
Errno::ECONNRESET,
|
356
|
+
Errno::ENETRESET,
|
357
|
+
Errno::EPIPE,
|
358
|
+
Errno::ECONNABORTED,
|
359
|
+
Errno::EHOSTDOWN,
|
360
|
+
Errno::EHOSTUNREACH,
|
361
|
+
Errno::EINVAL,
|
362
|
+
Errno::ETIMEDOUT,
|
363
|
+
Net::ReadTimeout,
|
364
|
+
Net::HTTPFatalError,
|
365
|
+
Net::HTTPBadResponse,
|
366
|
+
Net::HTTPHeaderSyntaxError,
|
367
|
+
Net::ProtocolError,
|
368
|
+
Net::HTTPRequestTimeOut,
|
369
|
+
Net::HTTPGatewayTimeOut,
|
370
|
+
Net::HTTPBadRequest,
|
371
|
+
Net::HTTPBadGateway,
|
372
|
+
Net::HTTPError,
|
373
|
+
Net::HTTPInternalServerError,
|
374
|
+
Net::HTTPRetriableError,
|
375
|
+
Net::HTTPServerError,
|
376
|
+
Net::HTTPServiceUnavailable,
|
377
|
+
Net::HTTPUnprocessableEntity,
|
378
|
+
Net::OpenTimeout,
|
379
|
+
IOError,
|
380
|
+
EOFError,
|
381
|
+
Timeout::Error
|
382
|
+
]
|
383
|
+
|
384
|
+
begin
|
385
|
+
retry_exceptions << Net::HTTPRequestTimeout
|
386
|
+
retry_exceptions << Net::HTTPGatewayTimeout
|
387
|
+
retry_exceptions << Net::WriteTimeout
|
388
|
+
end if RUBY_VERSION > "2.5.8"
|
389
|
+
|
390
|
+
retry_exceptions
|
391
|
+
end
|
192
392
|
end
|
193
393
|
end
|
194
394
|
end
|
@@ -6,7 +6,7 @@ module Etna
|
|
6
6
|
module Clients
|
7
7
|
class BaseClient
|
8
8
|
attr_reader :host, :token, :ignore_ssl
|
9
|
-
def initialize(host:, token:, ignore_ssl: false)
|
9
|
+
def initialize(host:, token:, ignore_ssl: false, logger: nil)
|
10
10
|
raise "#{self.class.name} client configuration is missing host." unless host
|
11
11
|
|
12
12
|
@token = token
|
@@ -16,7 +16,8 @@ module Etna
|
|
16
16
|
host,
|
17
17
|
token,
|
18
18
|
routes_available: false,
|
19
|
-
ignore_ssl: ignore_ssl
|
19
|
+
ignore_ssl: ignore_ssl,
|
20
|
+
logger: logger)
|
20
21
|
@host = host
|
21
22
|
@ignore_ssl = ignore_ssl
|
22
23
|
end
|
@@ -10,10 +10,10 @@ module Etna
|
|
10
10
|
module Clients
|
11
11
|
class Metis < Etna::Clients::BaseClient
|
12
12
|
|
13
|
-
def initialize(host:, token:, ignore_ssl: false)
|
13
|
+
def initialize(host:, token:, ignore_ssl: false, logger: nil)
|
14
14
|
raise 'Metis client configuration is missing host.' unless host
|
15
15
|
raise 'Metis client configuration is missing token.' unless token
|
16
|
-
@etna_client = ::Etna::Client.new(host, token, ignore_ssl: ignore_ssl)
|
16
|
+
@etna_client = ::Etna::Client.new(host, token, ignore_ssl: ignore_ssl, logger: logger)
|
17
17
|
|
18
18
|
@token = token
|
19
19
|
end
|
data/lib/etna/spec/auth.rb
CHANGED
@@ -31,9 +31,17 @@ module Etna::Spec
|
|
31
31
|
def below_admin_roles
|
32
32
|
[:editor, :viewer, :guest]
|
33
33
|
end
|
34
|
-
|
34
|
+
|
35
35
|
def below_editor_roles
|
36
36
|
[:viewer, :guest]
|
37
37
|
end
|
38
|
+
|
39
|
+
def stub_janus_refresh
|
40
|
+
stub_request(:post, /\/api\/tokens\/generate/)
|
41
|
+
.to_return({
|
42
|
+
status: 200,
|
43
|
+
body: ""
|
44
|
+
})
|
45
|
+
end
|
38
46
|
end
|
39
47
|
end
|
data/lib/helpers.rb
CHANGED
@@ -43,17 +43,19 @@ module WithEtnaClients
|
|
43
43
|
env_token
|
44
44
|
end
|
45
45
|
|
46
|
-
def magma_client
|
46
|
+
def magma_client(logger: nil)
|
47
47
|
@magma_client ||= Etna::Clients::Magma.new(
|
48
48
|
token: token,
|
49
49
|
ignore_ssl: EtnaApp.instance.config(:ignore_ssl),
|
50
|
+
logger: logger,
|
50
51
|
**EtnaApp.instance.config(:magma, environment) || {})
|
51
52
|
end
|
52
53
|
|
53
|
-
def metis_client
|
54
|
+
def metis_client(logger: nil)
|
54
55
|
@metis_client ||= Etna::Clients::Metis.new(
|
55
56
|
token: token,
|
56
57
|
ignore_ssl: EtnaApp.instance.config(:ignore_ssl),
|
58
|
+
logger: logger,
|
57
59
|
**EtnaApp.instance.config(:metis, environment) || {})
|
58
60
|
end
|
59
61
|
|
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.48
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Saurabh Asthana
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-02-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|