pampa 2.0.12 → 2.0.15
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/pampa.rb +210 -34
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a8627e0b3e3f9d923c9dc750fde056078bfd202a74e3abd36eecfa27566c93f3
|
|
4
|
+
data.tar.gz: 27990d2b64539c229974280e5d80b630b7e6dfb05b4b6533ef8976031e791a7d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 71fb9a3ab3965c5aa591412c358ff6074b12d9e7ff6b8409d941ac28e42ba2ccb85288f1987a20758fd81c795d22323c597c2f15be040d5e1b628fed008aaecb
|
|
7
|
+
data.tar.gz: e33cfc7bca76c0a3031c4bfdc96417cfba6fadbcf7593b499a9eb2b5cefd996371b05548f159149ff2d204ae8035c524ac77c2dadbb081bde93693f0e64da1d9
|
data/lib/pampa.rb
CHANGED
|
@@ -9,6 +9,11 @@ module BlackStack
|
|
|
9
9
|
module Pampa
|
|
10
10
|
# activate this flag if you want to add pampa nodes to blackstack-deployer.
|
|
11
11
|
@@integrate_with_blackstack_deployer = false
|
|
12
|
+
# setup custom locations for config and worker files.
|
|
13
|
+
@@config_filename = "config.rb"
|
|
14
|
+
@@worker_filename = "worker.rb"
|
|
15
|
+
# setu the directory where the worker.rb file will be lauched, and the log files will be stored.
|
|
16
|
+
@@working_directory = "$HOME/pampa"
|
|
12
17
|
# arrays of workers, nodes, and jobs.
|
|
13
18
|
@@nodes = []
|
|
14
19
|
@@jobs = []
|
|
@@ -17,7 +22,43 @@ module BlackStack
|
|
|
17
22
|
@@logger = BlackStack::DummyLogger.new(nil)
|
|
18
23
|
# Connection string to the database. Example: mysql2://user:password@localhost:3306/database
|
|
19
24
|
@@connection_string = nil
|
|
20
|
-
|
|
25
|
+
|
|
26
|
+
# @@integrate_with_blackstack_deployer
|
|
27
|
+
def self.integrate_with_blackstack_deployer()
|
|
28
|
+
@@integrate_with_blackstack_deployer
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.set_integrate_with_blackstack_deployer(b)
|
|
32
|
+
@@integrate_with_blackstack_deployer = b
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @@config_filename
|
|
36
|
+
def self.config_filename()
|
|
37
|
+
@@config_filename
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.set_config_filename(s)
|
|
41
|
+
@@config_filename = s
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @@worker_filename
|
|
45
|
+
def self.worker_filename()
|
|
46
|
+
@@worker_filename
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.set_worker_filename(s)
|
|
50
|
+
@@worker_filename = s
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
## @@working_directory
|
|
54
|
+
def self.working_directory()
|
|
55
|
+
@@working_directory
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.set_working_directory(s)
|
|
59
|
+
@@working_directory = s
|
|
60
|
+
end
|
|
61
|
+
|
|
21
62
|
# define a filename for the log file.
|
|
22
63
|
def self.set_log_filename(s)
|
|
23
64
|
@@log_filename = s
|
|
@@ -33,15 +74,6 @@ module BlackStack
|
|
|
33
74
|
@@logger = l
|
|
34
75
|
end
|
|
35
76
|
|
|
36
|
-
# @@integrate_with_blackstack_deployer
|
|
37
|
-
def self.integrate_with_blackstack_deployer()
|
|
38
|
-
@@integrate_with_blackstack_deployer
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def self.set_integrate_with_blackstack_deployer(b)
|
|
42
|
-
@@integrate_with_blackstack_deployer = b
|
|
43
|
-
end
|
|
44
|
-
|
|
45
77
|
# return the log filename.
|
|
46
78
|
def self.log_filename()
|
|
47
79
|
@@log_filename
|
|
@@ -297,10 +329,10 @@ module BlackStack
|
|
|
297
329
|
|
|
298
330
|
# connect the nodes via ssh.
|
|
299
331
|
# kill all Ruby processes except this one.
|
|
300
|
-
# rename any existing folder
|
|
301
|
-
# create a new folder
|
|
302
|
-
# build the file
|
|
303
|
-
# copy the file
|
|
332
|
+
# rename any existing folder $HOME/pampa to $HOME/pampa.<current timestamp>.
|
|
333
|
+
# create a new folder $HOME/pampa.
|
|
334
|
+
# build the file $HOME/pampa/config.rb in the remote node.
|
|
335
|
+
# copy the file $HOME/pampa/worker.rb to the remote node.
|
|
304
336
|
# run the number of workers specified in the configuration of the Pampa module.
|
|
305
337
|
# return an array with the IDs of the workers.
|
|
306
338
|
#
|
|
@@ -308,7 +340,7 @@ module BlackStack
|
|
|
308
340
|
# - config: relative path of the configuration file. Example: '../config.rb'
|
|
309
341
|
# - worker: relative path of the worker.rb file. Example: '../worker.rb'
|
|
310
342
|
#
|
|
311
|
-
def self.deploy(
|
|
343
|
+
def self.deploy()
|
|
312
344
|
# validate: the connection string is not nil
|
|
313
345
|
raise "The connection string is nil" if @@connection_string.nil?
|
|
314
346
|
# validate: the connection string is not empty
|
|
@@ -326,33 +358,48 @@ module BlackStack
|
|
|
326
358
|
l.done
|
|
327
359
|
# kill all ruby processes except this one
|
|
328
360
|
l.logs("Killing all Ruby processes except this one... ")
|
|
329
|
-
|
|
361
|
+
node.kill_workers()
|
|
330
362
|
l.done
|
|
331
363
|
# rename any existing folder ~/code/pampa to ~/code/pampa.<current timestamp>.
|
|
332
364
|
l.logs("Renaming old folder... ")
|
|
333
|
-
|
|
365
|
+
node.exec("mv #{BlackStack::Pampa.working_directory} #{BlackStack::Pampa.working_directory}.#{Time.now().to_i.to_s}", false);
|
|
334
366
|
l.done
|
|
335
367
|
# create a new folder ~/code. - ignore if it already exists.
|
|
336
368
|
l.logs("Creating new folder... ")
|
|
337
|
-
|
|
369
|
+
node.exec("mkdir #{BlackStack::Pampa.working_directory}", false);
|
|
338
370
|
l.done
|
|
339
|
-
# build the file
|
|
371
|
+
# build the file $HOME/pampa/config.rb in the remote node. - Be sure the BlackStack::Pampa.to_hash.to_s don't have single-quotes (') in the string.
|
|
340
372
|
l.logs("Building config file... ")
|
|
341
|
-
s = "echo \"#{File.read(config_filename)}\" >
|
|
342
|
-
|
|
373
|
+
s = "echo \"#{File.read(config_filename)}\" > #{BlackStack::Pampa.working_directory}/#{BlackStack::Pampa.config_filename}"
|
|
374
|
+
node.exec("#{s}", false);
|
|
343
375
|
l.done
|
|
344
|
-
# copy the file
|
|
376
|
+
# copy the file $HOME/pampa/worker.rb to the remote node. - Be sure the script don't have single-quotes (') in the string.
|
|
345
377
|
l.logs("Copying worker file... ")
|
|
346
|
-
s = "echo \"#{File.read(worker_filename)}\" >
|
|
347
|
-
|
|
378
|
+
s = "echo \"#{File.read(worker_filename)}\" > #{BlackStack::Pampa.working_directory}/#{BlackStack::Pampa.worker_filename}"
|
|
379
|
+
node.exec("#{s}", false);
|
|
348
380
|
l.done
|
|
349
381
|
# run the number of workers specified in the configuration of the Pampa module.
|
|
350
382
|
node.workers.each { |worker|
|
|
351
383
|
# run the worker
|
|
352
384
|
# add these parameters for debug: debug=yes pampa=~/code/pampa/lib/pampa.rb
|
|
353
385
|
l.logs "Running worker #{worker.id}... "
|
|
354
|
-
|
|
355
|
-
|
|
386
|
+
|
|
387
|
+
# write bash command to initialize bash file
|
|
388
|
+
s = "echo \"
|
|
389
|
+
export RUBYLIB=$HOME/code/mysaas;
|
|
390
|
+
source $HOME/.profile;
|
|
391
|
+
source /usr/local/rvm/scripts/rvm;
|
|
392
|
+
cd ~/code/mysaas; rvm install 3.1.2;
|
|
393
|
+
rvm --default use 3.1.2;
|
|
394
|
+
cd #{BlackStack::Pampa.working_directory};
|
|
395
|
+
nohup ruby #{worker_filename} id=#{worker.id} config=#{self.config_filename} >/dev/null 2>&1 &
|
|
396
|
+
\" > #{BlackStack::Pampa.working_directory}/#{worker.id}.sh"
|
|
397
|
+
node.exec(s, false);
|
|
398
|
+
|
|
399
|
+
#s = "nohup bash #{BlackStack::Pampa.working_directory}/worker.sh >/dev/null 2>&1 &"
|
|
400
|
+
s = "bash #{BlackStack::Pampa.working_directory}/#{worker.id}.sh"
|
|
401
|
+
node.exec(s, false);
|
|
402
|
+
|
|
356
403
|
l.done
|
|
357
404
|
}
|
|
358
405
|
# disconnect the node
|
|
@@ -368,7 +415,7 @@ module BlackStack
|
|
|
368
415
|
# run the number of workers specified in the configuration of the Pampa module.
|
|
369
416
|
# return an array with the IDs of the workers.
|
|
370
417
|
#
|
|
371
|
-
def self.start(
|
|
418
|
+
def self.start()
|
|
372
419
|
# validate: the connection string is not nil
|
|
373
420
|
raise "The connection string is nil" if @@connection_string.nil?
|
|
374
421
|
# validate: the connection string is not empty
|
|
@@ -386,15 +433,31 @@ module BlackStack
|
|
|
386
433
|
l.done
|
|
387
434
|
# kill all ruby processes except this one
|
|
388
435
|
l.logs("Killing all Ruby processes except this one... ")
|
|
389
|
-
|
|
436
|
+
node.kill_workers()
|
|
390
437
|
l.done
|
|
391
438
|
# run the number of workers specified in the configuration of the Pampa module.
|
|
392
439
|
node.workers.each { |worker|
|
|
393
440
|
# run the worker
|
|
394
441
|
# add these parameters for debug: debug=yes pampa=~/code/pampa/lib/pampa.rb
|
|
442
|
+
# run a bash command that sources the .profile file and runs the ruby script in the background, returning immediatelly.
|
|
443
|
+
|
|
395
444
|
l.logs "Running worker #{worker.id}... "
|
|
396
|
-
|
|
397
|
-
|
|
445
|
+
|
|
446
|
+
# write bash command to initialize bash file
|
|
447
|
+
s = "echo \"
|
|
448
|
+
export RUBYLIB=$HOME/code/mysaas;
|
|
449
|
+
source $HOME/.profile;
|
|
450
|
+
source /usr/local/rvm/scripts/rvm;
|
|
451
|
+
cd ~/code/mysaas; rvm install 3.1.2;
|
|
452
|
+
rvm --default use 3.1.2;
|
|
453
|
+
cd #{BlackStack::Pampa.working_directory};
|
|
454
|
+
nohup ruby #{worker_filename} id=#{worker.id} config=#{self.config_filename} >/dev/null 2>&1 &
|
|
455
|
+
\" > #{BlackStack::Pampa.working_directory}/#{worker.id}.sh"
|
|
456
|
+
#binding.pry
|
|
457
|
+
node.exec(s, false);
|
|
458
|
+
s = "nohup bash #{BlackStack::Pampa.working_directory}/#{worker.id}.sh >/dev/null 2>&1 &"
|
|
459
|
+
node.exec(s, false);
|
|
460
|
+
|
|
398
461
|
l.done
|
|
399
462
|
}
|
|
400
463
|
# disconnect the node
|
|
@@ -411,7 +474,7 @@ module BlackStack
|
|
|
411
474
|
# Parameters:
|
|
412
475
|
# - config: relative path of the configuration file. Example: '../config.rb'
|
|
413
476
|
#
|
|
414
|
-
def self.stop(
|
|
477
|
+
def self.stop()
|
|
415
478
|
# validate: the connection string is not nil
|
|
416
479
|
raise "The connection string is nil" if @@connection_string.nil?
|
|
417
480
|
# validate: the connection string is not empty
|
|
@@ -429,7 +492,7 @@ module BlackStack
|
|
|
429
492
|
l.done
|
|
430
493
|
# kill all ruby processes except this one
|
|
431
494
|
l.logs("Killing all Ruby processes except this one... ")
|
|
432
|
-
|
|
495
|
+
node.kill_workers()
|
|
433
496
|
l.done
|
|
434
497
|
# disconnect the node
|
|
435
498
|
l.logs("Disconnecting... ")
|
|
@@ -439,10 +502,44 @@ module BlackStack
|
|
|
439
502
|
} # @@nodes.each do |node|
|
|
440
503
|
end
|
|
441
504
|
|
|
505
|
+
# get the node by `node_name`
|
|
506
|
+
# connect the nodes via ssh.
|
|
507
|
+
# get how many minutes the worker wrote the log file
|
|
508
|
+
# close the connection
|
|
509
|
+
def self.log_minutes_ago(node_name, worker_id)
|
|
510
|
+
# get the node
|
|
511
|
+
n = self.nodes.select { |n| n.name == node_name }.first
|
|
512
|
+
return nil if !n
|
|
513
|
+
# connect the node
|
|
514
|
+
n.connect()
|
|
515
|
+
# get the time of the last time the worker wrote the log file
|
|
516
|
+
code = "cat #{BlackStack::Pampa.working_directory}/worker.#{worker_id}.log | tail -n 1 | cut -b1-19"
|
|
517
|
+
s = n.exec(code, false).to_s.strip
|
|
518
|
+
# run bash command to get the difference in minutes beteen now and the last time the worker wrote the log file
|
|
519
|
+
s = n.exec("echo \"$(($(date +%s) - $(date -d '#{s}' +%s))) / 60\" | bc", false).to_s.strip
|
|
520
|
+
# disconnect the node
|
|
521
|
+
n.disconnect
|
|
522
|
+
# return the number of minutes
|
|
523
|
+
s
|
|
524
|
+
end # log_minutes_ago
|
|
525
|
+
|
|
526
|
+
# get the node usage of CPU, RAM, DISK, and NETWORK
|
|
527
|
+
# return a hash with the usage of CPU, RAM, DISK, and NETWORK
|
|
528
|
+
#
|
|
529
|
+
# sudo apt install sysstat
|
|
530
|
+
#
|
|
531
|
+
def self.node_usage(node_name)
|
|
532
|
+
ret = {}
|
|
533
|
+
# get the node
|
|
534
|
+
n = self.nodes.select { |n| n.name == node_name }.first
|
|
535
|
+
return nil if !n
|
|
536
|
+
n.usage
|
|
537
|
+
end # node_usage
|
|
538
|
+
|
|
442
539
|
# stub worker class
|
|
443
540
|
class Worker
|
|
444
541
|
# name to identify uniquely the worker
|
|
445
|
-
attr_accessor :id, :assigned_job, :attached
|
|
542
|
+
attr_accessor :id, :assigned_job, :attached, :node
|
|
446
543
|
# return an array with the errors found in the description of the job
|
|
447
544
|
def self.descriptor_errors(h)
|
|
448
545
|
errors = []
|
|
@@ -471,6 +568,10 @@ module BlackStack
|
|
|
471
568
|
def detach()
|
|
472
569
|
self.attached = false
|
|
473
570
|
end
|
|
571
|
+
# get the latest n lines of the log of this worker
|
|
572
|
+
def tail(n=10)
|
|
573
|
+
self.node.tail("#{BlackStack::Pampa.working_directory}/worker.#{self.id}.log", n)
|
|
574
|
+
end
|
|
474
575
|
end
|
|
475
576
|
|
|
476
577
|
# stub node class
|
|
@@ -500,7 +601,9 @@ module BlackStack
|
|
|
500
601
|
self.max_workers = h[:max_workers]
|
|
501
602
|
self.workers = []
|
|
502
603
|
self.max_workers.times do |i|
|
|
503
|
-
|
|
604
|
+
new_worker = BlackStack::Pampa::Worker.new({:id => "#{self.name}.#{(i+1).to_s}", :node => self.to_hash})
|
|
605
|
+
new_worker.node = self
|
|
606
|
+
self.workers << new_worker
|
|
504
607
|
end
|
|
505
608
|
end # def self.create(h)
|
|
506
609
|
# returh a hash descriptor of the node
|
|
@@ -513,6 +616,15 @@ module BlackStack
|
|
|
513
616
|
end
|
|
514
617
|
ret
|
|
515
618
|
end
|
|
619
|
+
# kill all workers
|
|
620
|
+
def kill_workers()
|
|
621
|
+
self.workers.each do |worker|
|
|
622
|
+
self.kill_worker(worker.id)
|
|
623
|
+
end
|
|
624
|
+
end
|
|
625
|
+
def kill_worker(worker_id)
|
|
626
|
+
self.exec("kill -9 $(ps -ef | grep \"ruby worker.rb id=#{worker_id}\" | grep -v grep | awk '{print $2}')", false)
|
|
627
|
+
end
|
|
516
628
|
end # class Node
|
|
517
629
|
|
|
518
630
|
# stub job class
|
|
@@ -569,6 +681,8 @@ module BlackStack
|
|
|
569
681
|
# stretch assignation/unassignation of workers
|
|
570
682
|
attr_accessor :max_pending_tasks
|
|
571
683
|
attr_accessor :max_assigned_workers
|
|
684
|
+
# tags in order to choose on which nodes the job will be executed
|
|
685
|
+
attr_accessor :tags
|
|
572
686
|
|
|
573
687
|
# return a hash descriptor of the job
|
|
574
688
|
def to_hash()
|
|
@@ -596,6 +710,7 @@ module BlackStack
|
|
|
596
710
|
:processing_function => self.processing_function.to_s,
|
|
597
711
|
:max_pending_tasks => self.max_pending_tasks,
|
|
598
712
|
:max_assigned_workers => self.max_assigned_workers,
|
|
713
|
+
:tags => self.tags
|
|
599
714
|
}
|
|
600
715
|
end
|
|
601
716
|
|
|
@@ -631,6 +746,7 @@ module BlackStack
|
|
|
631
746
|
self.processing_function = h[:processing_function]
|
|
632
747
|
self.max_pending_tasks = h[:max_pending_tasks]
|
|
633
748
|
self.max_assigned_workers = h[:max_assigned_workers]
|
|
749
|
+
self.tags = h[:tags].nil? ? [] : ( h[:tags].is_a?(Array) ? h[:tags] : [h[:tags].to_s] )
|
|
634
750
|
end
|
|
635
751
|
|
|
636
752
|
# returns an array of tasks pending to be processed by the worker.
|
|
@@ -785,6 +901,66 @@ module BlackStack
|
|
|
785
901
|
#
|
|
786
902
|
return i
|
|
787
903
|
end
|
|
904
|
+
|
|
905
|
+
# reporting method: idle
|
|
906
|
+
# reutrn the number of idle tasks.
|
|
907
|
+
# if the numbr if idle tasks is higher than `max_tasks_to_show` then it returns `max_tasks_to_show`+.
|
|
908
|
+
def idle(max_tasks_to_show=25)
|
|
909
|
+
j = self
|
|
910
|
+
ret = j.selecting(max_tasks_to_show).size
|
|
911
|
+
ret = ret >= max_tasks_to_show ? "#{ret.to_label}+" : ret.to_label
|
|
912
|
+
ret
|
|
913
|
+
end # def idle
|
|
914
|
+
|
|
915
|
+
# reporting method: running
|
|
916
|
+
# return the number of running tasks.
|
|
917
|
+
# if the numbr if running tasks is higher than `max_tasks_to_show` then it returns `max_tasks_to_show`+.
|
|
918
|
+
def running(max_tasks_to_show=25)
|
|
919
|
+
j = self
|
|
920
|
+
ret = 0
|
|
921
|
+
BlackStack::Pampa::nodes.each { |n|
|
|
922
|
+
n.workers.each { |w|
|
|
923
|
+
ret += j.occupied_slots(w).size
|
|
924
|
+
break if ret>=max_tasks_to_show
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
ret = ret >= max_tasks_to_show ? "#{max_tasks_to_show}+" : ret.to_label
|
|
928
|
+
ret
|
|
929
|
+
end # def idle
|
|
930
|
+
|
|
931
|
+
# reporting method: running
|
|
932
|
+
# return the number of running tasks.
|
|
933
|
+
# if the numbr if running tasks is higher than `max_tasks_to_show` then it returns `max_tasks_to_show`+.
|
|
934
|
+
def failed(max_tasks_to_show=25)
|
|
935
|
+
j = self
|
|
936
|
+
q = "
|
|
937
|
+
SELECT *
|
|
938
|
+
FROM #{j.table.to_s}
|
|
939
|
+
WHERE COALESCE(#{j.field_success.to_s},true)=false
|
|
940
|
+
--AND #{j.field_end_time.to_s} IS NULL
|
|
941
|
+
--AND COALESCE(#{j.field_times.to_s},0) >= #{j.max_try_times.to_i}
|
|
942
|
+
LIMIT #{max_tasks_to_show}
|
|
943
|
+
"
|
|
944
|
+
ret = DB[q].all.size
|
|
945
|
+
ret = ret >= max_tasks_to_show ? "#{ret.to_label}+" : ret.to_label
|
|
946
|
+
ret
|
|
947
|
+
end # def idle
|
|
948
|
+
|
|
949
|
+
# reporting method: error_descriptions
|
|
950
|
+
# return an array of hashes { :id, :error_description } with the tasks that have an the success flag in false, error description.
|
|
951
|
+
# if the numbr if running tasks is higher than `max_tasks_to_show` then it returns `max_tasks_to_show` errors.
|
|
952
|
+
def error_descriptions(max_tasks_to_show=25)
|
|
953
|
+
j = self
|
|
954
|
+
q = "
|
|
955
|
+
SELECT #{j.field_primary_key.to_s} as id, #{j.field_error_description.to_s} as description
|
|
956
|
+
FROM #{j.table.to_s}
|
|
957
|
+
WHERE COALESCE(#{j.field_success.to_s},true)=false
|
|
958
|
+
--AND #{j.field_end_time.to_s} IS NULL
|
|
959
|
+
--AND COALESCE(#{j.field_times.to_s},0) >= #{j.max_try_times.to_i}
|
|
960
|
+
LIMIT #{max_tasks_to_show}
|
|
961
|
+
"
|
|
962
|
+
DB[q].all
|
|
963
|
+
end
|
|
788
964
|
end # class Job
|
|
789
965
|
end # module Pampa
|
|
790
966
|
end # module BlackStack
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pampa
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0.
|
|
4
|
+
version: 2.0.15
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Leandro Daniel Sardi
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2022-12-
|
|
11
|
+
date: 2022-12-12 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: sequel
|