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.
@@ -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
@@ -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
@@ -0,0 +1,9 @@
1
+ module SwarmClusterCliOpe
2
+ module Kubernetes
3
+ module SyncConfigs
4
+ class Sqlite3 < SwarmClusterCliOpe::SyncConfigs::Sqlite3
5
+ include BaseDecorator
6
+ end
7
+ end
8
+ end
9
+ 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
- raw_result[:stdout].split("\n").collect { |s| object_class.new(JSON.parse(s)) }
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