swarm_cluster_cli_ope 0.5.0.pre.2 → 0.5.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.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -45
  3. data/README.md +11 -2
  4. data/lib/swarm_cluster_cli_ope.rb +9 -1
  5. data/lib/swarm_cluster_cli_ope/base_configuration.rb +226 -0
  6. data/lib/swarm_cluster_cli_ope/cli.rb +2 -129
  7. data/lib/swarm_cluster_cli_ope/configuration.rb +10 -186
  8. data/lib/swarm_cluster_cli_ope/configuration_concern.rb +10 -9
  9. data/lib/swarm_cluster_cli_ope/k8s.rb +59 -83
  10. data/lib/swarm_cluster_cli_ope/kubernetes/configuration.rb +75 -0
  11. data/lib/swarm_cluster_cli_ope/kubernetes/pod.rb +144 -0
  12. data/lib/swarm_cluster_cli_ope/{k8s_rsync → kubernetes/rsync_cfgs}/password +0 -0
  13. data/lib/swarm_cluster_cli_ope/{k8s_rsync → kubernetes/rsync_cfgs}/rsyncd.conf +0 -0
  14. data/lib/swarm_cluster_cli_ope/{k8s_rsync → kubernetes/rsync_cfgs}/rsyncd.secrets +0 -0
  15. data/lib/swarm_cluster_cli_ope/kubernetes/sync_configs/base_decorator.rb +28 -0
  16. data/lib/swarm_cluster_cli_ope/kubernetes/sync_configs/mysql.rb +10 -0
  17. data/lib/swarm_cluster_cli_ope/kubernetes/sync_configs/post_gres.rb +11 -0
  18. data/lib/swarm_cluster_cli_ope/kubernetes/sync_configs/rsync.rb +125 -0
  19. data/lib/swarm_cluster_cli_ope/kubernetes/sync_configs/sqlite3.rb +9 -0
  20. data/lib/swarm_cluster_cli_ope/logger_concern.rb +1 -1
  21. data/lib/swarm_cluster_cli_ope/shell_command_response.rb +18 -5
  22. data/lib/swarm_cluster_cli_ope/stack_sync_concern.rb +135 -0
  23. data/lib/swarm_cluster_cli_ope/sync_configs/base.rb +1 -1
  24. data/lib/swarm_cluster_cli_ope/sync_configs/mysql.rb +17 -2
  25. data/lib/swarm_cluster_cli_ope/sync_configs/post_gres.rb +1 -1
  26. data/lib/swarm_cluster_cli_ope/thor_configuration_concern.rb +29 -0
  27. data/lib/swarm_cluster_cli_ope/version.rb +1 -1
  28. data/swarm_cluster_cli_ope.gemspec +0 -1
  29. metadata +17 -21
@@ -0,0 +1,144 @@
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::ShellCommandResponse]
34
+ def exec(cmd)
35
+ base_cmd(["exec", name, "--", cmd].flatten).execute
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.single_obj[:items].empty?
102
+ puts "non abbiamo trovato il pod"
103
+ exit
104
+ else
105
+ self.new(ris.single_obj[:items].first, context: context)
106
+ end
107
+ end
108
+ end
109
+
110
+ def self.find_by_name(name, namespace: nil, context: nil)
111
+ base_cmd = ["kubectl"]
112
+ base_cmd << "--namespace=#{namespace}" unless namespace.blank?
113
+ base_cmd << "--context=#{context}" unless context.blank?
114
+ base_cmd << "get pod #{name}"
115
+ base_cmd << "--output=json"
116
+
117
+ cmd = ShellCommandExecution.new(base_cmd)
118
+ ris = cmd.execute(allow_failure: true)
119
+ if ris.failed?
120
+ puts "Problemi nella ricerca del pod"
121
+ exit
122
+ else
123
+ self.new(ris.single_obj, context: context)
124
+ end
125
+ end
126
+
127
+
128
+ private
129
+
130
+ ##
131
+ # Array con i comandi base di kubectl
132
+ # @return [Array<String>]
133
+ def base_kubectl_cmd_env(json: false)
134
+ base_cmd = ["kubectl"]
135
+ base_cmd << "--namespace=#{namespace}" unless namespace.blank?
136
+ base_cmd << "--context=#{context}" unless context.blank?
137
+ base_cmd << "--output=json" if json
138
+ base_cmd
139
+ end
140
+
141
+
142
+ end
143
+ end
144
+ 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,10 @@
1
+ module SwarmClusterCliOpe
2
+ module Kubernetes
3
+ module SyncConfigs
4
+ class Mysql < SwarmClusterCliOpe::SyncConfigs::Mysql
5
+
6
+ include BaseDecorator
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ module SwarmClusterCliOpe
2
+ module Kubernetes
3
+ module SyncConfigs
4
+ class PostGres < SwarmClusterCliOpe::SyncConfigs::PostGres
5
+
6
+ include BaseDecorator
7
+
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,125 @@
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.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.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.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
+ container.exec('bash -c "killall rsync"')
106
+ end
107
+ end
108
+
109
+ ensure
110
+ say "Eseguo pulizia configurazioni caricate"
111
+ container.exec('bash -c "rm -fr /tmp/rsyncd*"')
112
+ end
113
+
114
+ end
115
+
116
+ end
117
+
118
+ end
119
+
120
+ end
121
+
122
+ end
123
+ end
124
+ end
125
+ 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
@@ -6,7 +6,7 @@ module SwarmClusterCliOpe
6
6
  return LoggerConcern.const_get("LOGGER") if LoggerConcern.const_defined?("LOGGER")
7
7
  logger = Logger.new(STDOUT)
8
8
  LoggerConcern.const_set("LOGGER", logger)
9
- logger.level = case Configuration.instance.logger_level
9
+ logger.level = case BaseConfiguration.instance.logger_level
10
10
  when "0"
11
11
  Logger::ERROR
12
12
  when "1"
@@ -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
- raw_result[:stdout].split("\n").collect { |s| object_class.new(JSON.parse(s)) }
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,135 @@
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
+
106
+ if direction == :push
107
+ unless yes? "ATTENZIONE STAI FACENDO PUSH, proseguire????[y,yes]"
108
+ exit "OK, CIAO"
109
+ end
110
+ end
111
+
112
+ cfgs.env(options[:environment]) do |cfgs|
113
+ sync_cfgs = cfgs.sync_configurations
114
+ if sync_cfgs.empty?
115
+ say "Attenzione, configurazioni di sincronizzazione vuoto. Leggere la documentazione"
116
+ else
117
+ sync_cfgs.each do |sync|
118
+ say "----------->>>>>>"
119
+ say "[ #{sync.class.name} ]"
120
+ sync.send(direction)
121
+ say "COMPLETE"
122
+ say "<<<<<<-----------"
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+
129
+ end
130
+
131
+ # module ClassMethods
132
+
133
+ # end
134
+ end
135
+ end