swarm_cluster_cli_ope 0.5.0.pre.2 → 0.5.0.pre.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -45
- data/README.md +7 -2
- data/lib/swarm_cluster_cli_ope/base_configuration.rb +211 -0
- data/lib/swarm_cluster_cli_ope/cli.rb +2 -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 +59 -83
- data/lib/swarm_cluster_cli_ope/kubernetes/configuration.rb +49 -0
- data/lib/swarm_cluster_cli_ope/kubernetes/pod.rb +143 -0
- data/lib/swarm_cluster_cli_ope/{k8s_rsync → kubernetes/rsync_cfgs}/password +0 -0
- data/lib/swarm_cluster_cli_ope/{k8s_rsync → kubernetes/rsync_cfgs}/rsyncd.conf +0 -0
- data/lib/swarm_cluster_cli_ope/{k8s_rsync → kubernetes/rsync_cfgs}/rsyncd.secrets +0 -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 +119 -0
- data/lib/swarm_cluster_cli_ope/kubernetes/sync_configs/sqlite3.rb +9 -0
- data/lib/swarm_cluster_cli_ope/shell_command_response.rb +8 -1
- 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
- data/swarm_cluster_cli_ope.gemspec +0 -1
- metadata +13 -19
@@ -0,0 +1,143 @@
|
|
1
|
+
module SwarmClusterCliOpe
|
2
|
+
module Kubernetes
|
3
|
+
##
|
4
|
+
# Interfaccia per la comunicazione con il POD
|
5
|
+
class Pod
|
6
|
+
include LoggerConcern
|
7
|
+
|
8
|
+
|
9
|
+
#@return [Hash]
|
10
|
+
attr_accessor :pod_description
|
11
|
+
|
12
|
+
#@return [String]
|
13
|
+
attr_accessor :context
|
14
|
+
|
15
|
+
# @param [Hash] pod_description -> hash con le configurazioni ritornate da kubectl
|
16
|
+
# @param [String] context -> se non presente utiliziamo l'attuale
|
17
|
+
def initialize(pod_description, context:)
|
18
|
+
@pod_description = pod_description.to_h.deep_symbolize_keys
|
19
|
+
@context = context
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
# @return [String]
|
24
|
+
def name
|
25
|
+
@pod_description[:metadata][:name]
|
26
|
+
end
|
27
|
+
|
28
|
+
def namespace
|
29
|
+
@pod_description[:metadata][:namespace]
|
30
|
+
end
|
31
|
+
|
32
|
+
# @param [String,Array<String>] cmd -> comando da passare a kubectl exec -- CMD
|
33
|
+
# @return [SwarmClusterCliOpe::ShellCommandExecution]
|
34
|
+
def exec(cmd)
|
35
|
+
base_cmd(["exec", name, "--", cmd].flatten)
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Appende solamente la parte base dei comandi
|
40
|
+
# @return [SwarmClusterCliOpe::ShellCommandExecution]
|
41
|
+
# @param [String,Array<String>] cmd
|
42
|
+
def base_cmd(cmd)
|
43
|
+
ShellCommandExecution.new([base_kubectl_cmd_env, cmd].flatten)
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
##
|
48
|
+
# Comando per la copia del file
|
49
|
+
# @param [String] src
|
50
|
+
# @param [String] dst
|
51
|
+
# @return [SwarmClusterCliOpe::ShellCommandExecution]
|
52
|
+
def cp_in(src, dst)
|
53
|
+
base_cmd(["cp", src, "#{name}:#{dst}"])
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Comando per la copia del file dal container
|
58
|
+
# @param [String] src
|
59
|
+
# @param [String] dst
|
60
|
+
# @return [SwarmClusterCliOpe::ShellCommandExecution]
|
61
|
+
def cp_out(src, dst)
|
62
|
+
base_cmd(["cp", "#{name}:#{src}", dst])
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
# Proxy class per essere simili al container per swarm
|
67
|
+
# @return [TrueClass, FalseClass]
|
68
|
+
# @param [String] src
|
69
|
+
# @param [String] dst
|
70
|
+
def copy_in(src, dst)
|
71
|
+
cp_in(src, dst).execute.success?
|
72
|
+
end
|
73
|
+
|
74
|
+
# @param [String] src
|
75
|
+
# @param [String] dst
|
76
|
+
# @return [TrueClass, FalseClass]
|
77
|
+
def copy_out(src, dst)
|
78
|
+
cp_out(src, dst).execute.success?
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
# @param [String] selector
|
83
|
+
# @return [Pod]
|
84
|
+
# @param [nil,String] namespace -> se la sciato vuoto utiliziamo il namespace corrente
|
85
|
+
# @param [String, nil] context -> contesto di kubectl, nel caso utilizziamo il corrente
|
86
|
+
def self.find_by_selector(selector, namespace: nil, context: nil)
|
87
|
+
|
88
|
+
base_cmd = ["kubectl"]
|
89
|
+
base_cmd << "--namespace=#{namespace}" unless namespace.blank?
|
90
|
+
base_cmd << "--context=#{context}" unless context.blank?
|
91
|
+
base_cmd << "get pod"
|
92
|
+
base_cmd << "--selector=#{selector}"
|
93
|
+
base_cmd << "--output=json"
|
94
|
+
|
95
|
+
cmd = ShellCommandExecution.new(base_cmd)
|
96
|
+
ris = cmd.execute(allow_failure: true)
|
97
|
+
if ris.failed?
|
98
|
+
puts "Problemi nella ricerca del pod"
|
99
|
+
exit
|
100
|
+
else
|
101
|
+
if ris.result[:items].empty?
|
102
|
+
logger.warn { "non abbiamo trovato il pod" }
|
103
|
+
else
|
104
|
+
self.new(ris.result[:items].first, context: context)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.find_by_name(name, namespace: nil, context: nil)
|
110
|
+
base_cmd = ["kubectl"]
|
111
|
+
base_cmd << "--namespace=#{namespace}" unless namespace.blank?
|
112
|
+
base_cmd << "--context=#{context}" unless context.blank?
|
113
|
+
base_cmd << "get pod #{name}"
|
114
|
+
base_cmd << "--output=json"
|
115
|
+
|
116
|
+
cmd = ShellCommandExecution.new(base_cmd)
|
117
|
+
ris = cmd.execute(allow_failure: true)
|
118
|
+
if ris.failed?
|
119
|
+
puts "Problemi nella ricerca del pod"
|
120
|
+
exit
|
121
|
+
else
|
122
|
+
self.new(ris.result, context: context)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
##
|
130
|
+
# Array con i comandi base di kubectl
|
131
|
+
# @return [Array<String>]
|
132
|
+
def base_kubectl_cmd_env(json: false)
|
133
|
+
base_cmd = ["kubectl"]
|
134
|
+
base_cmd << "--namespace=#{namespace}" unless namespace.blank?
|
135
|
+
base_cmd << "--context=#{context}" unless context.blank?
|
136
|
+
base_cmd << "--output=json" if json
|
137
|
+
base_cmd
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
File without changes
|
File without changes
|
File without changes
|
@@ -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,119 @@
|
|
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
|
+
|
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
|
+
local_port = rand(30000..40000)
|
69
|
+
|
70
|
+
p = IO.popen(container.base_cmd("port-forward #{podname} #{local_port}:873").string_command)
|
71
|
+
pid = p.pid
|
72
|
+
say "PID in execuzione port forward:#{pid}"
|
73
|
+
|
74
|
+
sleep 1
|
75
|
+
|
76
|
+
# lanciamo il comando quindi per far rsync
|
77
|
+
rsync_command = [
|
78
|
+
"rsync -az --no-o --no-g",
|
79
|
+
"--delete",
|
80
|
+
"--password-file=#{ File.expand_path("../../rsync_cfgs/password", __FILE__)}"
|
81
|
+
]
|
82
|
+
|
83
|
+
if direction == :up
|
84
|
+
rsync_command << "#{local_folder}/."
|
85
|
+
rsync_command << "rsync://root@0.0.0.0:#{local_port}/archives#{remote_folder}"
|
86
|
+
else
|
87
|
+
rsync_command << "rsync://root@0.0.0.0:#{local_port}/archives#{remote_folder}/."
|
88
|
+
rsync_command << local_folder
|
89
|
+
end
|
90
|
+
say "Eseguo rsync #{rsync_command.join(" ")}"
|
91
|
+
|
92
|
+
|
93
|
+
cmd = ShellCommandExecution.new(rsync_command)
|
94
|
+
cmd.execute
|
95
|
+
|
96
|
+
sleep 1
|
97
|
+
Process.kill("INT", pid)
|
98
|
+
|
99
|
+
|
100
|
+
say "Eseguo pulizia"
|
101
|
+
cmd = container.exec('bash -c "killall rsync"')
|
102
|
+
cmd.execute
|
103
|
+
cmd = container.exec('bash -c "rm -fr /tmp/rsyncd*"')
|
104
|
+
cmd.execute
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -30,7 +30,14 @@ module SwarmClusterCliOpe
|
|
30
30
|
# @param [Object] object_class
|
31
31
|
# @return [Array<object_class>]
|
32
32
|
def result(object_class: OpenStruct)
|
33
|
-
|
33
|
+
#tento prima di estrapolare direttamente da json e sucessivamente come array
|
34
|
+
begin
|
35
|
+
# questo per k8s, dato che abbiamo come risposta un json vero
|
36
|
+
object_class.new(JSON.parse( raw_result[:stdout]))
|
37
|
+
rescue
|
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
|
34
41
|
end
|
35
42
|
|
36
43
|
#
|
@@ -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
|