flagger 2.0.5 → 3.0.1
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/lib/flagger.rb +63 -68
- data/lib/flagger/entity.rb +49 -0
- data/lib/flagger/flagger-386.dll +0 -0
- data/lib/flagger/flagger-amd64.dll +0 -0
- data/lib/flagger/libflagger-386.so +0 -0
- data/lib/flagger/libflagger-amd64.so +0 -0
- data/lib/flagger/libflagger.dylib +0 -0
- data/lib/flagger/native.rb +31 -0
- data/lib/flagger/response.rb +7 -0
- data/lib/flagger/version.rb +3 -4
- metadata +70 -39
- data/lib/flagger/cloud.rb +0 -440
- data/lib/flagger/microservice.rb +0 -63
- data/lib/flagger/models.rb +0 -655
- data/lib/flagger/stat.rb +0 -56
data/lib/flagger/cloud.rb
DELETED
|
@@ -1,440 +0,0 @@
|
|
|
1
|
-
require 'eventmachine'
|
|
2
|
-
require 'faraday'
|
|
3
|
-
require 'faye/websocket'
|
|
4
|
-
require 'json'
|
|
5
|
-
require 'lru_redux'
|
|
6
|
-
require 'set'
|
|
7
|
-
require 'flagger/models'
|
|
8
|
-
require 'flagger/stat'
|
|
9
|
-
require 'flagger/version'
|
|
10
|
-
|
|
11
|
-
module FlaggerEnvironments
|
|
12
|
-
SERVER_URL = 'https://api.airshiphq.com'
|
|
13
|
-
IDENTIFY_ENDPOINT = "#{SERVER_URL}/v2/identify"
|
|
14
|
-
GATING_INFO_ENDPOINT = "#{SERVER_URL}/v2/gating-info"
|
|
15
|
-
|
|
16
|
-
WEBSOCKET_URL = 'wss://ws.airshiphq.com'
|
|
17
|
-
WEBSOCKET_GATING_INFO_ENDPOINT = "#{WEBSOCKET_URL}/v2/ws-events"
|
|
18
|
-
|
|
19
|
-
CLOUDFRONT_URL = 'https://backup-api.airshiphq.com'
|
|
20
|
-
CLOUDFRONT_GATING_INFO_ENDPOINT = "#{CLOUDFRONT_URL}/v2/gating-info"
|
|
21
|
-
|
|
22
|
-
class CloudDelegate
|
|
23
|
-
attr_accessor :gating_info
|
|
24
|
-
attr_accessor :env_key
|
|
25
|
-
attr_accessor :request_timeout
|
|
26
|
-
|
|
27
|
-
def initialize(env_key, request_timeout)
|
|
28
|
-
@env_key = env_key
|
|
29
|
-
@request_timeout = request_timeout
|
|
30
|
-
|
|
31
|
-
@ingestion_max_items = 500
|
|
32
|
-
@ingestion_interval = 30
|
|
33
|
-
|
|
34
|
-
@entities = []
|
|
35
|
-
@stats = []
|
|
36
|
-
@exposures = []
|
|
37
|
-
@flags = Set[]
|
|
38
|
-
@ingested_flags = Set[]
|
|
39
|
-
|
|
40
|
-
@entity_lru_hashes = LruRedux::Cache.new(500)
|
|
41
|
-
@first_ingestion = true
|
|
42
|
-
|
|
43
|
-
@should_ingest_entities = true
|
|
44
|
-
@should_ingest_stats = true
|
|
45
|
-
@should_ingest_exposures = true
|
|
46
|
-
@should_ingest_flags = true
|
|
47
|
-
|
|
48
|
-
begin
|
|
49
|
-
get_gating_info("#{GATING_INFO_ENDPOINT}/#{@env_key}", 'duration__gating_info')
|
|
50
|
-
rescue Exception => e
|
|
51
|
-
STDERR.puts 'Failed to retrieve initial gating info from API', e.backtrace
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
begin
|
|
55
|
-
get_gating_info("#{CLOUDFRONT_GATING_INFO_ENDPOINT}/#{@env_key}", 'duration__cloudfront_gating_info') unless !@gating_info.nil?
|
|
56
|
-
rescue Exception => e
|
|
57
|
-
STDERR.puts 'Failed to retrieve initial gating info from CloudFront backup', e.backtrace
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
if EM.reactor_running? then
|
|
61
|
-
em_subscribe_updates
|
|
62
|
-
em_ingestion_timer
|
|
63
|
-
else
|
|
64
|
-
Thread.new {
|
|
65
|
-
EM.run {
|
|
66
|
-
em_subscribe_updates
|
|
67
|
-
em_ingestion_timer
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def get_gating_info(url, stat_name)
|
|
74
|
-
stat = FlaggerUtils::Stat.new(stat_name)
|
|
75
|
-
stat.start
|
|
76
|
-
conn = Faraday.new(url: url)
|
|
77
|
-
response = conn.get do |req|
|
|
78
|
-
req.options.timeout = @request_timeout
|
|
79
|
-
end
|
|
80
|
-
if response.status == 200
|
|
81
|
-
@gating_info = FlaggerModels::GatingInfo.new(JSON.parse(response.body))
|
|
82
|
-
update_sdk_vars(@gating_info)
|
|
83
|
-
stat.stop
|
|
84
|
-
save_stat(stat)
|
|
85
|
-
else
|
|
86
|
-
raise "Gating info request failed with HTTP code #{response.code}"
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def em_subscribe_updates()
|
|
91
|
-
@ws_unsubbed = false
|
|
92
|
-
@ws = Faye::WebSocket::Client.new("#{WEBSOCKET_GATING_INFO_ENDPOINT}?envkey=#{@env_key}")
|
|
93
|
-
|
|
94
|
-
@ws.on :message do |event|
|
|
95
|
-
begin
|
|
96
|
-
@gating_info = FlaggerModels::GatingInfo.new(JSON.parse(event.data))
|
|
97
|
-
update_sdk_vars(@gating_info)
|
|
98
|
-
rescue Exception => e
|
|
99
|
-
STDERR.puts 'Failed to update gating information', e
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
@ws.on :close do |event|
|
|
104
|
-
STDERR.puts ['Connection closed to Airship update server', event.code, event.reason] unless @ws_unsubbed
|
|
105
|
-
EventMachine::Timer.new(5) do
|
|
106
|
-
em_subscribe_updates
|
|
107
|
-
end unless @ws_unsubbed
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
def unsubscribe_updates()
|
|
112
|
-
@ws_unsubbed = true
|
|
113
|
-
@ws.close()
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def update_sdk_vars(gating_info)
|
|
117
|
-
if gating_info&.sdk_info&.ingestion_max_items then
|
|
118
|
-
@ingestion_max_items = gating_info.sdk_info.ingestion_max_items
|
|
119
|
-
end
|
|
120
|
-
if gating_info&.sdk_info&.ingestion_interval then
|
|
121
|
-
@ingestion_interval = gating_info.sdk_info.ingestion_interval
|
|
122
|
-
end
|
|
123
|
-
if gating_info&.sdk_info&.should_ingest_entities then
|
|
124
|
-
@should_ingest_entities = gating_info.sdk_info.should_ingest_entities
|
|
125
|
-
end
|
|
126
|
-
if gating_info&.sdk_info&.should_ingest_stats then
|
|
127
|
-
@should_ingest_stats = gating_info&.sdk_info&.should_ingest_stats
|
|
128
|
-
end
|
|
129
|
-
if gating_info&.sdk_info&.should_ingest_exposures then
|
|
130
|
-
@should_ingest_exposures = gating_info&.sdk_info&.should_ingest_exposures
|
|
131
|
-
end
|
|
132
|
-
if gating_info&.sdk_info&.should_ingest_flags then
|
|
133
|
-
@should_ingest_flags = gating_info&.sdk_info&.should_ingest_flags
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
def maybe_ingest(should_ingest = false)
|
|
138
|
-
if !@should_ingest_entities then
|
|
139
|
-
@entities = []
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
if !@should_ingest_stats then
|
|
143
|
-
@stats = []
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
if !@should_ingest_exposures then
|
|
147
|
-
@exposures = []
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
if !@should_ingest_flags then
|
|
151
|
-
@flags = Set[]
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
should_ingest |= (
|
|
155
|
-
@entities.length > @ingestion_max_items ||
|
|
156
|
-
@stats.length > @ingestion_max_items ||
|
|
157
|
-
@exposures.length > @ingestion_max_items ||
|
|
158
|
-
@flags.length > 0
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
if @first_ingestion then
|
|
162
|
-
should_ingest |= @entities.length > 0
|
|
163
|
-
@first_ingestion = !should_ingest
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
should_ingest &= (
|
|
167
|
-
@entities.length > 0 ||
|
|
168
|
-
@stats.length > 0 ||
|
|
169
|
-
@exposures.length > 0 ||
|
|
170
|
-
@flags.length > 0
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
if should_ingest then
|
|
174
|
-
begin
|
|
175
|
-
entities = @entities
|
|
176
|
-
stats = @stats
|
|
177
|
-
exposures = @exposures
|
|
178
|
-
flags = @flags.to_a
|
|
179
|
-
@entities = []
|
|
180
|
-
@stats = []
|
|
181
|
-
@exposures = []
|
|
182
|
-
@ingested_flags.merge(@flags)
|
|
183
|
-
@flags = Set[]
|
|
184
|
-
|
|
185
|
-
conn = Faraday.new(url: "#{IDENTIFY_ENDPOINT}/#{@env_key}")
|
|
186
|
-
response = conn.post do |req|
|
|
187
|
-
req.options.timeout = @request_timeout
|
|
188
|
-
req.headers['Content-Type'] = 'application/json'
|
|
189
|
-
req.body = JSON.generate(
|
|
190
|
-
{
|
|
191
|
-
'sdk_info' => {
|
|
192
|
-
'name' => 'ruby',
|
|
193
|
-
'version' => FlaggerUtils::VERSION
|
|
194
|
-
},
|
|
195
|
-
'objects' => entities,
|
|
196
|
-
'stats' => stats,
|
|
197
|
-
'exposures' => exposures,
|
|
198
|
-
'flags' => flags
|
|
199
|
-
})
|
|
200
|
-
end
|
|
201
|
-
if response.status != 200 then
|
|
202
|
-
STDERR.puts 'Failed to upload entities', response.status
|
|
203
|
-
nil
|
|
204
|
-
end
|
|
205
|
-
rescue Exception => e
|
|
206
|
-
STDERR.puts 'Failed to upload entities', e
|
|
207
|
-
nil
|
|
208
|
-
end
|
|
209
|
-
end
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
def em_ingestion_timer
|
|
213
|
-
seconds = 0
|
|
214
|
-
timer = EventMachine::PeriodicTimer.new(1) do
|
|
215
|
-
timer.cancel unless !@cleaning
|
|
216
|
-
|
|
217
|
-
seconds = (seconds + 1) % @ingestion_interval
|
|
218
|
-
if seconds == 0 then
|
|
219
|
-
EM.defer(proc {
|
|
220
|
-
maybe_ingest(true)
|
|
221
|
-
})
|
|
222
|
-
end
|
|
223
|
-
end
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
def cleanup()
|
|
227
|
-
@cleaning = true
|
|
228
|
-
unsubscribe_updates
|
|
229
|
-
maybe_ingest(true)
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
def save_entity(entity)
|
|
233
|
-
if @entity_lru_hashes[entity.id] != entity.hash then
|
|
234
|
-
@entity_lru_hashes[entity.id] = entity.hash
|
|
235
|
-
@entities.push(entity.attrs)
|
|
236
|
-
maybe_ingest()
|
|
237
|
-
end
|
|
238
|
-
end
|
|
239
|
-
|
|
240
|
-
def save_stat(stat)
|
|
241
|
-
@stats.push(stat)
|
|
242
|
-
@stats = FlaggerUtils::Stat.compact(@stats)
|
|
243
|
-
maybe_ingest()
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
def save_exposure(exposure)
|
|
247
|
-
@exposures.push(exposure)
|
|
248
|
-
maybe_ingest()
|
|
249
|
-
end
|
|
250
|
-
|
|
251
|
-
def save_flag(flag)
|
|
252
|
-
@flags.add(flag)
|
|
253
|
-
maybe_ingest()
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
def stringify_keys(hash)
|
|
257
|
-
if hash.is_a?(Hash) then
|
|
258
|
-
hash.map {|k, v|
|
|
259
|
-
[k.to_s, stringify_keys(v)]
|
|
260
|
-
}.to_h
|
|
261
|
-
else
|
|
262
|
-
hash
|
|
263
|
-
end
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
def identify_entity(entity_json)
|
|
267
|
-
if !entity_json.is_a?(Hash) then
|
|
268
|
-
STDERR.puts 'Entity must be a hash'
|
|
269
|
-
nil
|
|
270
|
-
else
|
|
271
|
-
entity_json = stringify_keys(entity_json)
|
|
272
|
-
|
|
273
|
-
entity = if entity_json['is_group'] then
|
|
274
|
-
FlaggerModels::GroupEntity.new(entity_json)
|
|
275
|
-
else
|
|
276
|
-
FlaggerModels::SingleEntity.new(entity_json)
|
|
277
|
-
end
|
|
278
|
-
|
|
279
|
-
if !entity.valid? then
|
|
280
|
-
STDERR.puts "Entity validation errors: #{entity.validation_errors}"
|
|
281
|
-
nil
|
|
282
|
-
else
|
|
283
|
-
save_entity(entity)
|
|
284
|
-
|
|
285
|
-
entity
|
|
286
|
-
end
|
|
287
|
-
end
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
def treatment(flagger_flag, entity_json)
|
|
291
|
-
stat = FlaggerUtils::Stat.new('duration__get_treatment')
|
|
292
|
-
stat.start()
|
|
293
|
-
|
|
294
|
-
entity = identify_entity(entity_json)
|
|
295
|
-
flag = @gating_info&.flag(flagger_flag.flag_name)
|
|
296
|
-
if flag.nil? && !@ingested_flags.include?(flagger_flag.flag_name) then
|
|
297
|
-
save_flag(flagger_flag.flag_name)
|
|
298
|
-
end
|
|
299
|
-
return_value = flag&.treatment(entity)
|
|
300
|
-
|
|
301
|
-
if !return_value then
|
|
302
|
-
return return_value
|
|
303
|
-
end
|
|
304
|
-
|
|
305
|
-
exposure = {
|
|
306
|
-
flag: flagger_flag.flag_name,
|
|
307
|
-
type: entity.type,
|
|
308
|
-
id: entity.id,
|
|
309
|
-
treatment: return_value[:treatment].codename,
|
|
310
|
-
method_called: 'get_treatment',
|
|
311
|
-
eligible: return_value[:eligible]
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
return_value = return_value[:treatment].codename
|
|
315
|
-
|
|
316
|
-
save_exposure(exposure)
|
|
317
|
-
|
|
318
|
-
stat.stop()
|
|
319
|
-
save_stat(stat)
|
|
320
|
-
|
|
321
|
-
return_value
|
|
322
|
-
end
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
def payload(flagger_flag, entity_json)
|
|
326
|
-
stat = FlaggerUtils::Stat.new('duration__get_payload')
|
|
327
|
-
stat.start()
|
|
328
|
-
|
|
329
|
-
entity = identify_entity(entity_json)
|
|
330
|
-
flag = @gating_info&.flag(flagger_flag.flag_name)
|
|
331
|
-
if flag.nil? && !@ingested_flags.include?(flagger_flag.flag_name) then
|
|
332
|
-
save_flag(flagger_flag.flag_name)
|
|
333
|
-
end
|
|
334
|
-
return_value = flag&.payload(entity)
|
|
335
|
-
|
|
336
|
-
if !return_value then
|
|
337
|
-
return return_value
|
|
338
|
-
end
|
|
339
|
-
|
|
340
|
-
exposure = {
|
|
341
|
-
flag: flagger_flag.flag_name,
|
|
342
|
-
type: entity.type,
|
|
343
|
-
id: entity.id,
|
|
344
|
-
treatment: return_value[:treatment].codename,
|
|
345
|
-
method_called: 'get_payload',
|
|
346
|
-
eligible: return_value[:eligible]
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
return_value = return_value[:treatment].payload
|
|
350
|
-
|
|
351
|
-
save_exposure(exposure)
|
|
352
|
-
|
|
353
|
-
stat.stop()
|
|
354
|
-
save_stat(stat)
|
|
355
|
-
|
|
356
|
-
return_value
|
|
357
|
-
end
|
|
358
|
-
|
|
359
|
-
def eligible?(flagger_flag, entity_json)
|
|
360
|
-
stat = FlaggerUtils::Stat.new('duration__is_eligible')
|
|
361
|
-
stat.start()
|
|
362
|
-
|
|
363
|
-
entity = identify_entity(entity_json)
|
|
364
|
-
flag = @gating_info&.flag(flagger_flag.flag_name)
|
|
365
|
-
if flag.nil? && !@ingested_flags.include?(flagger_flag.flag_name) then
|
|
366
|
-
save_flag(flagger_flag.flag_name)
|
|
367
|
-
end
|
|
368
|
-
return_value = flag&.eligible?(entity)
|
|
369
|
-
|
|
370
|
-
if !return_value then
|
|
371
|
-
return return_value
|
|
372
|
-
end
|
|
373
|
-
|
|
374
|
-
exposure = {
|
|
375
|
-
flag: flagger_flag.flag_name,
|
|
376
|
-
type: entity.type,
|
|
377
|
-
id: entity.id,
|
|
378
|
-
treatment: return_value[:treatment].codename,
|
|
379
|
-
method_called: 'is_eligible',
|
|
380
|
-
eligible: return_value[:eligible]
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
return_value = return_value[:eligible]
|
|
384
|
-
|
|
385
|
-
save_exposure(exposure)
|
|
386
|
-
|
|
387
|
-
stat.stop()
|
|
388
|
-
save_stat(stat)
|
|
389
|
-
|
|
390
|
-
return_value
|
|
391
|
-
end
|
|
392
|
-
|
|
393
|
-
def enabled?(flagger_flag, entity_json)
|
|
394
|
-
stat = FlaggerUtils::Stat.new('duration__is_enabled')
|
|
395
|
-
stat.start()
|
|
396
|
-
|
|
397
|
-
entity = identify_entity(entity_json)
|
|
398
|
-
flag = @gating_info&.flag(flagger_flag.flag_name)
|
|
399
|
-
if flag.nil? && !@ingested_flags.include?(flagger_flag.flag_name) then
|
|
400
|
-
save_flag(flagger_flag.flag_name)
|
|
401
|
-
end
|
|
402
|
-
return_value = flag&.enabled?(entity)
|
|
403
|
-
|
|
404
|
-
if !return_value then
|
|
405
|
-
return return_value
|
|
406
|
-
end
|
|
407
|
-
|
|
408
|
-
exposure = {
|
|
409
|
-
flag: flagger_flag.flag_name,
|
|
410
|
-
type: entity.type,
|
|
411
|
-
id: entity.id,
|
|
412
|
-
treatment: return_value[:treatment].codename,
|
|
413
|
-
method_called: 'is_enabled',
|
|
414
|
-
eligible: return_value[:eligible]
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
return_value = !return_value[:treatment].is_off_treatment
|
|
418
|
-
|
|
419
|
-
save_exposure(exposure)
|
|
420
|
-
|
|
421
|
-
stat.stop()
|
|
422
|
-
save_stat(stat)
|
|
423
|
-
|
|
424
|
-
return_value
|
|
425
|
-
end
|
|
426
|
-
|
|
427
|
-
def publish(entities)
|
|
428
|
-
if !entities.is_a?(Array) then
|
|
429
|
-
STDERR.puts 'The "publish" method takes an array of objects (aka entities).'
|
|
430
|
-
return nil
|
|
431
|
-
end
|
|
432
|
-
|
|
433
|
-
entities.each do |entity|
|
|
434
|
-
identify_entity(entity)
|
|
435
|
-
end
|
|
436
|
-
|
|
437
|
-
maybe_ingest(true)
|
|
438
|
-
end
|
|
439
|
-
end
|
|
440
|
-
end
|
data/lib/flagger/microservice.rb
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
require 'faraday'
|
|
2
|
-
require 'json'
|
|
3
|
-
|
|
4
|
-
module FlaggerEnvironments
|
|
5
|
-
class MicroserviceDelegate
|
|
6
|
-
attr_accessor :env_key
|
|
7
|
-
attr_accessor :edge_url
|
|
8
|
-
attr_accessor :request_timeout
|
|
9
|
-
|
|
10
|
-
def initialize(env_key, edge_url, request_timeout)
|
|
11
|
-
@env_key = env_key
|
|
12
|
-
@edge_url = edge_url
|
|
13
|
-
@request_timeout = request_timeout
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def treatment(flag, entity)
|
|
17
|
-
get_object_values(flag, entity)['treatment'] rescue nil
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def payload(flag, entity)
|
|
21
|
-
get_object_values(flag, entity)['payload'] rescue nil
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def eligible?(flag, entity)
|
|
25
|
-
get_object_values(flag, entity)['isEligible'] rescue nil
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def enabled?(flag, entity)
|
|
29
|
-
get_object_values(flag, entity)['isEnabled'] rescue nil
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def publish(entities)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def cleanup()
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
private
|
|
39
|
-
def get_object_values(flag, entity)
|
|
40
|
-
begin
|
|
41
|
-
conn = Faraday.new(url: "#{@edge_url}/v2/object-values/#{@env_key}")
|
|
42
|
-
response = conn.post do |req|
|
|
43
|
-
req.options.timeout = @request_timeout
|
|
44
|
-
req.headers['Content-Type'] = 'application/json'
|
|
45
|
-
req.body = JSON.generate({
|
|
46
|
-
'flag' => flag.flag_name,
|
|
47
|
-
'entity' => entity,
|
|
48
|
-
})
|
|
49
|
-
end
|
|
50
|
-
if response.status == 200
|
|
51
|
-
object_values = JSON.parse(response.body)
|
|
52
|
-
object_values
|
|
53
|
-
else
|
|
54
|
-
puts 'Failed to connect to Airship edge server'
|
|
55
|
-
nil
|
|
56
|
-
end
|
|
57
|
-
rescue Exception => e
|
|
58
|
-
puts 'Failed to connect to Airship edge server', e
|
|
59
|
-
nil
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
end
|