swarm_cluster_cli_ope 0.4.2 → 0.5.0.pre.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +4 -4
- data/README.md +7 -2
- data/lib/swarm_cluster_cli_ope/base_configuration.rb +211 -0
- data/lib/swarm_cluster_cli_ope/cli.rb +6 -129
- data/lib/swarm_cluster_cli_ope/configuration.rb +3 -182
- data/lib/swarm_cluster_cli_ope/configuration_concern.rb +10 -9
- data/lib/swarm_cluster_cli_ope/k8s.rb +90 -0
- data/lib/swarm_cluster_cli_ope/kubernetes/configuration.rb +64 -0
- data/lib/swarm_cluster_cli_ope/kubernetes/pod.rb +144 -0
- data/lib/swarm_cluster_cli_ope/kubernetes/rsync_cfgs/password +1 -0
- data/lib/swarm_cluster_cli_ope/kubernetes/rsync_cfgs/rsyncd.conf +14 -0
- data/lib/swarm_cluster_cli_ope/kubernetes/rsync_cfgs/rsyncd.secrets +1 -0
- data/lib/swarm_cluster_cli_ope/kubernetes/sync_configs/base_decorator.rb +28 -0
- data/lib/swarm_cluster_cli_ope/kubernetes/sync_configs/rsync.rb +127 -0
- data/lib/swarm_cluster_cli_ope/kubernetes/sync_configs/sqlite3.rb +9 -0
- data/lib/swarm_cluster_cli_ope/shell_command_execution.rb +1 -1
- data/lib/swarm_cluster_cli_ope/shell_command_response.rb +18 -5
- data/lib/swarm_cluster_cli_ope/stack_sync_concern.rb +128 -0
- data/lib/swarm_cluster_cli_ope/sync_configs/base.rb +1 -1
- data/lib/swarm_cluster_cli_ope/thor_configuration_concern.rb +29 -0
- data/lib/swarm_cluster_cli_ope/version.rb +1 -1
- metadata +16 -4
@@ -0,0 +1 @@
|
|
1
|
+
root
|
@@ -0,0 +1 @@
|
|
1
|
+
root:root
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
module SwarmClusterCliOpe
|
3
|
+
module Kubernetes
|
4
|
+
module SyncConfigs
|
5
|
+
module BaseDecorator
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
|
10
|
+
delegate :namespace, :context, to: :@stack_cfgs
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
# @return [SwarmClusterCliOpe::Kubernetes::Pod]
|
15
|
+
def container
|
16
|
+
return @service if @service.is_a? SwarmClusterCliOpe::Kubernetes::Pod
|
17
|
+
@_container ||= Pod.find_by_selector(service, namespace: namespace, context: context)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
# module ClassMethods
|
23
|
+
|
24
|
+
# end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module SwarmClusterCliOpe
|
2
|
+
module Kubernetes
|
3
|
+
module SyncConfigs
|
4
|
+
class Rsync < SwarmClusterCliOpe::SyncConfigs::Base
|
5
|
+
|
6
|
+
include BaseDecorator
|
7
|
+
|
8
|
+
# @return [String]
|
9
|
+
def local_folder
|
10
|
+
@configs[:configs][:local]
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [String]
|
14
|
+
def remote_folder
|
15
|
+
@configs[:configs][:remote]
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
# @return [SwarmClusterCliOpe::ShellCommandResponse]
|
20
|
+
def push
|
21
|
+
execute(direction: :up)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [SwarmClusterCliOpe::ShellCommandResponse]
|
25
|
+
def pull
|
26
|
+
execute(direction: :down)
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def execute(direction: :down)
|
33
|
+
|
34
|
+
if container.nil?
|
35
|
+
say "Container non trovato"
|
36
|
+
exit
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
if yes? "Attenzione, i dati locali o remoti verranno sovrascritti/cancellati?[y,yes]"
|
41
|
+
|
42
|
+
podname = container.name
|
43
|
+
|
44
|
+
if namespace.nil?
|
45
|
+
say "Mancata configurazione del namespace tramite argomento o .swarm_cluster_project"
|
46
|
+
exit
|
47
|
+
end
|
48
|
+
|
49
|
+
cmd = container.exec(['bash -c "apt update && apt install -yq rsync psmisc"'])
|
50
|
+
if cmd.execute.failed?
|
51
|
+
puts "Problemi nell'installazione di rsync nel pod"
|
52
|
+
else
|
53
|
+
cmd = container.cp_in(File.expand_path("../../rsync_cfgs/rsyncd.conf", __FILE__), "/tmp/.")
|
54
|
+
copy_1 = cmd.execute.failed?
|
55
|
+
cmd = container.cp_in(File.expand_path("../../rsync_cfgs/rsyncd.secrets", __FILE__), "/tmp/.")
|
56
|
+
copy_2 = cmd.execute.failed?
|
57
|
+
cmd = container.exec(['bash -c "chmod 600 /tmp/rsyncd.secrets && chown root /tmp/*"'])
|
58
|
+
chmod = cmd.execute.failed?
|
59
|
+
if copy_1 or copy_2 or chmod
|
60
|
+
puts "problema nella copia dei file di configurazione nel pod"
|
61
|
+
else
|
62
|
+
|
63
|
+
begin
|
64
|
+
cmd = container.exec('bash -c "rsync --daemon --config=/tmp/rsyncd.conf --verbose --log-file=/tmp/rsync.log"')
|
65
|
+
if cmd.execute.failed?
|
66
|
+
say "Rsync non Inizializzato"
|
67
|
+
else
|
68
|
+
begin
|
69
|
+
local_port = rand(30000..40000)
|
70
|
+
|
71
|
+
p = IO.popen(container.base_cmd("port-forward #{podname} #{local_port}:873").string_command)
|
72
|
+
pid = p.pid
|
73
|
+
say "PID in execuzione port forward:#{pid}"
|
74
|
+
|
75
|
+
begin
|
76
|
+
|
77
|
+
sleep 1
|
78
|
+
|
79
|
+
# lanciamo il comando quindi per far rsync
|
80
|
+
rsync_command = [
|
81
|
+
"rsync -az --no-o --no-g",
|
82
|
+
"--delete",
|
83
|
+
"--password-file=#{ File.expand_path("../../rsync_cfgs/password", __FILE__)}"
|
84
|
+
]
|
85
|
+
|
86
|
+
if direction == :up
|
87
|
+
rsync_command << "#{local_folder}/."
|
88
|
+
rsync_command << "rsync://root@0.0.0.0:#{local_port}/archives#{remote_folder}"
|
89
|
+
else
|
90
|
+
rsync_command << "rsync://root@0.0.0.0:#{local_port}/archives#{remote_folder}/."
|
91
|
+
rsync_command << local_folder
|
92
|
+
end
|
93
|
+
say "Eseguo rsync #{rsync_command.join(" ")}"
|
94
|
+
|
95
|
+
cmd = ShellCommandExecution.new(rsync_command)
|
96
|
+
cmd.execute
|
97
|
+
|
98
|
+
ensure
|
99
|
+
sleep 1
|
100
|
+
say "Stoppo porta forwarded"
|
101
|
+
Process.kill("INT", pid)
|
102
|
+
end
|
103
|
+
ensure
|
104
|
+
say "Tolgo il servizio di rsyn"
|
105
|
+
cmd = container.exec('bash -c "killall rsync"')
|
106
|
+
cmd.execute
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
ensure
|
111
|
+
say "Eseguo pulizia configurazioni caricate"
|
112
|
+
cmd = container.exec('bash -c "rm -fr /tmp/rsyncd*"')
|
113
|
+
cmd.execute
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
require 'forwardable'
|
2
2
|
|
3
3
|
module SwarmClusterCliOpe
|
4
|
-
##
|
5
|
-
# Identifica una risposta dalla shell
|
4
|
+
##
|
5
|
+
# Identifica una risposta dalla shell
|
6
6
|
class ShellCommandResponse
|
7
7
|
extend Forwardable
|
8
8
|
include LoggerConcern
|
@@ -28,9 +28,22 @@ module SwarmClusterCliOpe
|
|
28
28
|
##
|
29
29
|
# Risultato, essendo sempre composto da una lista di righe in formato json, ritorniamo un array di json
|
30
30
|
# @param [Object] object_class
|
31
|
-
# @return [Array<object_class
|
32
|
-
def result(object_class: OpenStruct)
|
33
|
-
|
31
|
+
# @return [Array<object_class>,Object]
|
32
|
+
def result(object_class: OpenStruct, single: false)
|
33
|
+
#tento prima di estrapolare direttamente da json e sucessivamente come array
|
34
|
+
if single
|
35
|
+
# questo per k8s, dato che abbiamo come risposta un json vero
|
36
|
+
object_class.new(JSON.parse(raw_result[:stdout]))
|
37
|
+
else
|
38
|
+
# questo nel caso siamo in swarm che ci ritorna un array di json
|
39
|
+
raw_result[:stdout].split("\n").collect { |s| object_class.new(JSON.parse(s)) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param [Class<OpenStruct>] object_class
|
44
|
+
# @return [Object]
|
45
|
+
def single_obj(object_class: OpenStruct)
|
46
|
+
result(object_class: object_class, single: true)
|
34
47
|
end
|
35
48
|
|
36
49
|
#
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module SwarmClusterCliOpe
|
4
|
+
module StackSyncConcern
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
|
9
|
+
desc "stacksync [DIRECTION:pull|push]", "Si occupa di scaricare|caricare,utilizzando le configurazioni presenti, i dati dallo stack remoto"
|
10
|
+
long_desc <<-LONGDESC.gsub("\n", "\x5")
|
11
|
+
le configurazioni sono contenute nell'array: sync_configs.
|
12
|
+
ogni configurazione è composta da:
|
13
|
+
{
|
14
|
+
service:""
|
15
|
+
how:""
|
16
|
+
configs:{ }
|
17
|
+
}
|
18
|
+
- service è il nome del servizio (o nel caso di k8s una stringa da utilizzare come selettore labels)
|
19
|
+
- how è il come sincronizzare, definendo la tipologia:
|
20
|
+
---- pg -> DB TODO
|
21
|
+
---- mysql -> DB dump con mysql
|
22
|
+
---- sqlite3 -> DB: viene eseguita una copia del file
|
23
|
+
---- rsync -> RSYNC
|
24
|
+
- configs: è un hash con le configurazioni per ogni tipo di sincronizzazione
|
25
|
+
|
26
|
+
Possibili CFGS per tipologia:
|
27
|
+
rsync:
|
28
|
+
--local: -> path cartella locale
|
29
|
+
--remote: -> path cartella remota (contesto del container)
|
30
|
+
|
31
|
+
sqlite3:
|
32
|
+
--local: -> path al file
|
33
|
+
--remote: -> path al file remoto (contesto del container)
|
34
|
+
|
35
|
+
mysql:
|
36
|
+
--local: -> hash di configurazioni per il DB locale
|
37
|
+
- service: "db" -> nome del servizio nel compose locale, DEFAULT: quello definito sopra
|
38
|
+
- mysql_password_env: "MYSQL_PASSWORD" -> variabile ambiente interna al servizio contenente PASSWORD, DEFAULT: MYSQL_PASSWORD
|
39
|
+
- mysql_password: "root" -> valore in chiaro, in sostituzione della variabile ambiente, DEFAULT: root
|
40
|
+
- mysql_user_env: "MYSQL_USER" -> variabile ambiente interna al servizio contenente USERNAME, DEFAULT: MYSQL_USER
|
41
|
+
- mysql_user: "root" -> valore in chiaro, in sostituzione della variabile ambiente, DEFAULT: root
|
42
|
+
- database_name_env: "MYSQL_DATABASE" -> variabile ambiente interna al servizio contenente NOME DB, DEFAULT: MYSQL_DATABASE
|
43
|
+
- database_name: "nome_db" -> valore in chiaro, in sostituzione della variabile ambiente
|
44
|
+
--remote: -> hash di configurazioni per il DB remoto
|
45
|
+
- service: "db" -> nome del servizio nel compose locale, DEFAULT: quello definito sopra
|
46
|
+
- mysql_password_env: "MYSQL_PASSWORD" -> variabile ambiente interna al servizio contenente PASSWORD, DEFAULT: MYSQL_PASSWORD
|
47
|
+
- mysql_password: "root" -> valore in chiaro, in sostituzione della variabile ambiente, DEFAULT: root
|
48
|
+
- mysql_user_env: "MYSQL_USER" -> variabile ambiente interna al servizio contenente USERNAME, DEFAULT: MYSQL_USER
|
49
|
+
- mysql_user: "root" -> valore in chiaro, in sostituzione della variabile ambiente, DEFAULT: root
|
50
|
+
- database_name_env: "MYSQL_DATABASE" -> variabile ambiente interna al servizio contenente NOME DB, DEFAULT: MYSQL_DATABASE
|
51
|
+
- database_name: "MYSQL_DATABASE" -> valore in chiaro, in sostituzione della variabile ambiente
|
52
|
+
pg:
|
53
|
+
--local: -> hash di configurazioni per il DB locale
|
54
|
+
- service: "db" -> nome del servizio nel compose locale, DEFAULT: quello definito sopra
|
55
|
+
- pg_password_env: "POSTGRES_USER" -> variabile ambiente interna al servizio contenente PASSWORD, DEFAULT: POSTGRES_PASSWORD
|
56
|
+
- pg_password: "" -> valore in chiaro, in sostituzione della variabile ambiente
|
57
|
+
- pg_user_env: "POSTGRES_USER" -> variabile ambiente interna al servizio contenente USERNAME, DEFAULT: POSTGRES_USER
|
58
|
+
- pg_user: "postgres" -> valore in chiaro, in sostituzione della variabile ambiente, DEFAULT: postgres
|
59
|
+
- database_name_env: "POSTGRES_DB" -> variabile ambiente interna al servizio contenente NOME DB, DEFAULT: POSTGRES_DB
|
60
|
+
- database_name: "nome_db" -> valore in chiaro, in sostituzione della variabile ambiente
|
61
|
+
--remote: -> hash di configurazioni per il DB remoto
|
62
|
+
- service: "db" -> nome del servizio nel compose locale, DEFAULT: quello definito sopra
|
63
|
+
- pg_password_env: "POSTGRES_USER" -> variabile ambiente interna al servizio contenente PASSWORD, DEFAULT: POSTGRES_PASSWORD
|
64
|
+
- pg_password: "" -> valore in chiaro, in sostituzione della variabile ambiente
|
65
|
+
- pg_user_env: "POSTGRES_USER" -> variabile ambiente interna al servizio contenente USERNAME, DEFAULT: POSTGRES_USER
|
66
|
+
- pg_user: "postgres" -> valore in chiaro, in sostituzione della variabile ambiente, DEFAULT: postgres
|
67
|
+
- database_name_env: "POSTGRES_DB" -> variabile ambiente interna al servizio contenente NOME DB, DEFAULT: POSTGRES_DB
|
68
|
+
- database_name: "nome_db" -> valore in chiaro, in sostituzione della variabile ambiente
|
69
|
+
|
70
|
+
|
71
|
+
EXAMPLE:
|
72
|
+
Esempio di sincronizzazione di un file sqlite3 e una cartella
|
73
|
+
{
|
74
|
+
"stack_name": "test1",
|
75
|
+
"sync_configs": [
|
76
|
+
{
|
77
|
+
"service": "second",
|
78
|
+
"how": "rsync",
|
79
|
+
"configs": {
|
80
|
+
"remote": "/test_bind",
|
81
|
+
"local": "./uploads"
|
82
|
+
}
|
83
|
+
},
|
84
|
+
{
|
85
|
+
"service": "test_sqlite3",
|
86
|
+
"how": "sqlite3",
|
87
|
+
"configs": {
|
88
|
+
"remote": "/cartella_sqlite3/esempio.sqlite3",
|
89
|
+
"local": "./development.sqlite3"
|
90
|
+
}
|
91
|
+
}
|
92
|
+
]
|
93
|
+
}
|
94
|
+
LONGDESC
|
95
|
+
|
96
|
+
def stacksync(direction)
|
97
|
+
direction = case direction
|
98
|
+
when 'push'
|
99
|
+
:push
|
100
|
+
when 'pull'
|
101
|
+
:pull
|
102
|
+
else
|
103
|
+
raise "ONLY [push|pull] action accepted"
|
104
|
+
end
|
105
|
+
cfgs.env(options[:environment]) do |cfgs|
|
106
|
+
sync_cfgs = cfgs.sync_configurations
|
107
|
+
if sync_cfgs.empty?
|
108
|
+
say "Attenzione, configurazioni di sincronizzazione vuoto. Leggere la documentazione"
|
109
|
+
else
|
110
|
+
sync_cfgs.each do |sync|
|
111
|
+
say "----------->>>>>>"
|
112
|
+
say "[ #{sync.class.name} ]"
|
113
|
+
sync.send(direction)
|
114
|
+
say "COMPLETE"
|
115
|
+
say "<<<<<<-----------"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
# module ClassMethods
|
125
|
+
|
126
|
+
# end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module SwarmClusterCliOpe
|
4
|
+
module ThorConfigurationConcern
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
|
9
|
+
desc "config", "Visualizza le configurazioni mergiate (HOME + Project configuration[#{Configuration.cfgs_project_file_name}])"
|
10
|
+
|
11
|
+
def config
|
12
|
+
cfgs.env(options[:environment]) do
|
13
|
+
puts JSON.pretty_generate(cfgs.merged_configurations)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "configure_project STACK_NAME", "Genera il file di configurazione del progetto contenente il nome dello stack"
|
18
|
+
|
19
|
+
def configure_project(stack_name)
|
20
|
+
cfgs.env(options[:environment]) do |c|
|
21
|
+
c.stack_name = stack_name
|
22
|
+
c.save_project_cfgs
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: swarm_cluster_cli_ope
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0.pre.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marino Bonetti
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-11-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -88,6 +88,7 @@ files:
|
|
88
88
|
- Rakefile
|
89
89
|
- exe/swarm_cli_ope
|
90
90
|
- lib/swarm_cluster_cli_ope.rb
|
91
|
+
- lib/swarm_cluster_cli_ope/base_configuration.rb
|
91
92
|
- lib/swarm_cluster_cli_ope/cli.rb
|
92
93
|
- lib/swarm_cluster_cli_ope/commands/base.rb
|
93
94
|
- lib/swarm_cluster_cli_ope/commands/compose_container.rb
|
@@ -97,6 +98,15 @@ files:
|
|
97
98
|
- lib/swarm_cluster_cli_ope/commands/task.rb
|
98
99
|
- lib/swarm_cluster_cli_ope/configuration.rb
|
99
100
|
- lib/swarm_cluster_cli_ope/configuration_concern.rb
|
101
|
+
- lib/swarm_cluster_cli_ope/k8s.rb
|
102
|
+
- lib/swarm_cluster_cli_ope/kubernetes/configuration.rb
|
103
|
+
- lib/swarm_cluster_cli_ope/kubernetes/pod.rb
|
104
|
+
- lib/swarm_cluster_cli_ope/kubernetes/rsync_cfgs/password
|
105
|
+
- lib/swarm_cluster_cli_ope/kubernetes/rsync_cfgs/rsyncd.conf
|
106
|
+
- lib/swarm_cluster_cli_ope/kubernetes/rsync_cfgs/rsyncd.secrets
|
107
|
+
- lib/swarm_cluster_cli_ope/kubernetes/sync_configs/base_decorator.rb
|
108
|
+
- lib/swarm_cluster_cli_ope/kubernetes/sync_configs/rsync.rb
|
109
|
+
- lib/swarm_cluster_cli_ope/kubernetes/sync_configs/sqlite3.rb
|
100
110
|
- lib/swarm_cluster_cli_ope/logger_concern.rb
|
101
111
|
- lib/swarm_cluster_cli_ope/manager.rb
|
102
112
|
- lib/swarm_cluster_cli_ope/models/base.rb
|
@@ -109,6 +119,7 @@ files:
|
|
109
119
|
- lib/swarm_cluster_cli_ope/node.rb
|
110
120
|
- lib/swarm_cluster_cli_ope/shell_command_execution.rb
|
111
121
|
- lib/swarm_cluster_cli_ope/shell_command_response.rb
|
122
|
+
- lib/swarm_cluster_cli_ope/stack_sync_concern.rb
|
112
123
|
- lib/swarm_cluster_cli_ope/sync_configs/base.rb
|
113
124
|
- lib/swarm_cluster_cli_ope/sync_configs/base_database.rb
|
114
125
|
- lib/swarm_cluster_cli_ope/sync_configs/copy.rb
|
@@ -117,6 +128,7 @@ files:
|
|
117
128
|
- lib/swarm_cluster_cli_ope/sync_configs/post_gres.rb
|
118
129
|
- lib/swarm_cluster_cli_ope/sync_configs/rsync.rb
|
119
130
|
- lib/swarm_cluster_cli_ope/sync_configs/sqlite3.rb
|
131
|
+
- lib/swarm_cluster_cli_ope/thor_configuration_concern.rb
|
120
132
|
- lib/swarm_cluster_cli_ope/version.rb
|
121
133
|
- lib/swarm_cluster_cli_ope/worker.rb
|
122
134
|
- swarm_cluster_cli_ope.gemspec
|
@@ -139,9 +151,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
139
151
|
version: 2.3.0
|
140
152
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
141
153
|
requirements:
|
142
|
-
- - "
|
154
|
+
- - ">"
|
143
155
|
- !ruby/object:Gem::Version
|
144
|
-
version:
|
156
|
+
version: 1.3.1
|
145
157
|
requirements: []
|
146
158
|
rubygems_version: 3.0.8
|
147
159
|
signing_key:
|