swarm_cluster_cli_ope 0.4 → 0.5.0.pre.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,17 +8,8 @@ module SwarmClusterCliOpe
8
8
  # Classe per la gestione delle configurazioni, unisce le configurazioni di base alle configurazioni di progetto;
9
9
  # le quali sono salvate nel file di configurazione del progetto .swarm_cluster_project sottoforma di json
10
10
  # che vengono mergiate sulle configurazioni base
11
- class Configuration
12
- include Singleton
13
- include LoggerConcern
11
+ class Configuration < BaseConfiguration
14
12
 
15
- #@return [String] nome dello stack con cui lavoriamo
16
- attr_accessor :stack_name
17
-
18
- #@return [String] in che enviroment siamo, altrimenti siamo nel default
19
- attr_accessor :environment
20
-
21
- NoBaseConfigurations = Class.new(Error)
22
13
 
23
14
  ##
24
15
  # Lista di nodi su cui lavorare
@@ -28,19 +19,6 @@ module SwarmClusterCliOpe
28
19
  @_managers = self.nodes.select { |n| read_managers_cache_list.include?(n.name) }.collect { |c| Manager.new(name: c.name.to_s, connection_uri: c.connection_uri) }
29
20
  end
30
21
 
31
- ##
32
- # Serve per entrare nell'env corretto.
33
- # passando l'env, tutto quello eseguito nello yield sarà gestito in quell'env.
34
- # Verrà controllato quindi che esista il relativo file di configurazion
35
- def env(enviroment = nil)
36
- unless enviroment.nil?
37
- @environment = enviroment.to_s.to_sym
38
- end
39
- logger.info { "ENV: #{@environment ? @environment : "BASE"}" }
40
- yield self
41
- @environment = nil
42
- end
43
-
44
22
  ##
45
23
  # Esegue un refresh della lista dei manager, ciclando su tutti i nodi, e scrivendo in /tmp un file temporaneo con
46
24
  # con la lista dei nomi dei managers
@@ -80,77 +58,14 @@ module SwarmClusterCliOpe
80
58
  self
81
59
  end
82
60
 
83
- # @return [String,NilClass] nome dello stack del progetto se configurato
84
- def stack_name
85
- return nil unless self.class.exist_base?
86
- @stack_name ||= Hash.new do |hash, key|
87
- hash[key] = merged_configurations[:stack_name] if merged_configurations.key?(:stack_name)
88
- end
89
- @stack_name[environment]
90
- end
91
-
92
- ##
93
- # Imposta il nome dello stack
94
- def stack_name=(objs)
95
- stack_name #lo richiamo per fargli generare la variabile di classe
96
- @stack_name[environment] = objs
97
- end
98
-
99
- ##
100
- # Livello di logging
101
- # @return [Integer]
102
- def logger_level
103
- merged_configurations[:log_level].to_s || "0"
104
- rescue SwarmClusterCliOpe::Configuration::NoBaseConfigurations
105
- # quando andiamo in errore durante l'installazione per avere le informazioni per il logger.
106
- # Usiamo lo standard
107
- "0"
108
- end
109
-
110
- ##
111
- # Siamo in sviluppo?
112
- # @return [TrueClass, FalseClass]
113
- def development_mode?
114
- return false unless self.class.exist_base?
115
- merged_configurations.key?(:dev_mode)
116
- end
117
-
118
- ##
119
- # Controlla se esiste il file di configurazione base, nella home dell'utente
120
- def self.exist_base?
121
- File.exist?(base_cfg_path)
122
- end
123
-
124
-
125
61
  ##
126
62
  # Salva le configurazioni base in HOME
127
63
  def save_base_cfgs
128
- FileUtils.mkdir_p(File.dirname(self.class.base_cfg_path))
129
- File.open(self.class.base_cfg_path, "wb") do |f|
130
- f.write({
131
- version: SwarmClusterCliOpe::VERSION,
132
- connections_maps: nodes.collect { |k| [k.name, k.connection_uri] }.to_h
133
- }.to_json)
134
- end
135
- end
136
-
137
- ##
138
- # Si occupa del salvataggio delle configurazioni di progetto, se abbiamo lo stack_name
139
- def save_project_cfgs
140
- if stack_name
141
- File.open(File.join(FileUtils.pwd, self.class.cfgs_project_file_name(with_env: @environment)), "wb") do |f|
142
- f.write({
143
- stack_name: stack_name,
144
- version: VERSION
145
- }.to_json)
146
- end
64
+ super do |obj|
65
+ obj.merge({connections_maps: nodes.collect { |k| [k.name, k.connection_uri] }.to_h})
147
66
  end
148
67
  end
149
68
 
150
- # @return [String] path to base home configurations
151
- def self.base_cfg_path
152
- File.join(ENV['HOME'], '.swarm_cluster', 'config.json')
153
- end
154
69
 
155
70
  # @return [SwarmClusterCliOpe::Node]
156
71
  # @param [String] node nome del nodo
@@ -173,41 +88,8 @@ module SwarmClusterCliOpe
173
88
  File.basename(FileUtils.pwd).downcase
174
89
  end
175
90
 
176
- ##
177
- # Elenco di tutte le configurazioni di sincronizzazione
178
- # @return [Array]
179
- def sync_configurations
180
- cfgs = merged_configurations[:sync_configs]
181
- return [] if cfgs.nil? or !cfgs.is_a?(Array)
182
- cfgs.collect do |c|
183
-
184
- case c[:how]
185
- when 'sqlite3'
186
- SyncConfigs::Sqlite3.new(self, c)
187
- when 'rsync'
188
- SyncConfigs::Rsync.new(self, c)
189
- when 'mysql'
190
- SyncConfigs::Mysql.new(self, c)
191
- when 'pg'
192
- SyncConfigs::PostGres.new(self, c)
193
- else
194
- logger.error { "CONFIGURAIONE NON PREVISTA: #{c[:how]}" }
195
- nil
196
- end
197
-
198
- end.compact
199
- end
200
-
201
91
  private
202
92
 
203
- ##
204
- # nome del file in cui salvare le configurazioni di progetto
205
- # @return [String]
206
- # @param [nil|String] with_env nome dell'env da cercare
207
- def self.cfgs_project_file_name(with_env: nil)
208
- ".swarm_cluster_project#{with_env ? ".#{with_env}" : ""}"
209
- end
210
-
211
93
  ##
212
94
  # Path al file dove salviamo la cache dei managers, ha un TTL legato all'orario (anno-mese-giorno-ora)
213
95
  # quindi ogni ora si autoripulisce e con un md5 delle configurazioni di base
@@ -218,67 +100,6 @@ module SwarmClusterCliOpe
218
100
  File.join("/tmp", file_name)
219
101
  end
220
102
 
221
- ##
222
- # Legge le configurazioni base
223
- #
224
- # @return [Hash]
225
- def self.read_base
226
- raise NoBaseConfigurations unless exist_base?
227
- JSON.parse(File.read(self.base_cfg_path)).deep_symbolize_keys
228
- end
229
-
230
- public
231
-
232
- ## Cerca le configurazioni di progetto e le mergia se sono presenti
233
- # @return [Hash]
234
- def merged_configurations
235
- return @_merged_configurations[@environment] if @_merged_configurations
236
-
237
- @_merged_configurations = Hash.new do |hash, key|
238
- folder = FileUtils.pwd
239
- default_file = looped_file(folder, self.class.cfgs_project_file_name)
240
- enviroment_file = looped_file(folder, self.class.cfgs_project_file_name(with_env: key))
241
-
242
- project_cfgs = {}
243
- unless default_file.nil?
244
- project_cfgs = JSON.parse(File.read(default_file)).deep_symbolize_keys
245
- end
246
-
247
- unless enviroment_file.nil?
248
- project_cfgs.merge!(JSON.parse(File.read(enviroment_file)).deep_symbolize_keys)
249
- end
250
-
251
- hash[key] = self.class.read_base.merge(project_cfgs)
252
- end
253
-
254
- configuration_version = @_merged_configurations[@environment][:version]
255
- if Gem::Version.new(configuration_version) > Gem::Version.new(VERSION)
256
- puts "WARNING: Versione del file di configurazione [#{configuration_version}] più aggiornata della gemma [#{VERSION}], eseguire upgrade
257
- gem update swarm_cluster_cli_ope"
258
- exit
259
- end
260
-
261
- @_merged_configurations[@environment]
262
-
263
- end
264
-
265
- private
266
-
267
- def looped_file(start_folder, file)
268
- project_file = nil
269
- loop do
270
-
271
- if File.exist?(File.join(start_folder, file))
272
- project_file = File.join(start_folder, file)
273
- end
274
-
275
- break unless project_file.nil?
276
- break if start_folder == '/'
277
- start_folder = File.expand_path("..", start_folder)
278
- end
279
-
280
- project_file
281
- end
282
103
 
283
104
  end
284
105
  end
@@ -1,8 +1,16 @@
1
+ require 'active_support/concern'
2
+
1
3
  module SwarmClusterCliOpe
2
4
  module ConfigurationConcern
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+
9
+ # @return [SwarmClusterCliOpe::Configuration]
10
+ def cfgs
11
+ self.class.cfgs
12
+ end
3
13
 
4
- def self.included(klass)
5
- klass.extend(ClassMethods)
6
14
  end
7
15
 
8
16
  module ClassMethods
@@ -13,12 +21,5 @@ module SwarmClusterCliOpe
13
21
  Configuration.instance
14
22
  end
15
23
  end
16
-
17
-
18
- # @return [SwarmClusterCliOpe::Configuration]
19
- def cfgs
20
- self.class.cfgs
21
- end
22
-
23
24
  end
24
25
  end
@@ -0,0 +1,90 @@
1
+ module SwarmClusterCliOpe
2
+ class K8s < Thor
3
+ include LoggerConcern
4
+ include ConfigurationConcern
5
+ include ThorConfigurationConcern
6
+ include Thor::Actions
7
+ include StackSyncConcern
8
+
9
+ def self.exit_on_failure?
10
+ true
11
+ end
12
+
13
+ def self.cfgs
14
+ SwarmClusterCliOpe::Kubernetes::Configuration.instance
15
+ end
16
+
17
+ desc "install", "Creazione della configurazione base della gemma"
18
+
19
+ def install
20
+ #contolliamo se presente la configurazione base nella home
21
+ if Configuration.exist_base?
22
+ say "Configurazione già presente"
23
+ else
24
+ #se non presente allora chiediamo le varie configurazioni
25
+ if yes? "Sei nel contesto corretto di kubectl?"
26
+ #scriviamo le varie configurazioni
27
+ cfg = cfgs
28
+ cfg.save_base_cfgs
29
+ else
30
+ say "Cambia prima contesto, sarà quello usato per l'installazione"
31
+ end
32
+ end
33
+
34
+ end
35
+
36
+
37
+ desc "rsync <src> <dst>", "esegue un rsync dalla cartella (viene sincronizzato il contenuto)"
38
+ long_desc "Viene utilizzato rsync standard. La root del pod diviene il context di rsync, quindi
39
+ possiamo fare rsync con qualsiasi path del filesystem del pod.
40
+ Il modo con cui scrivere la path sorgente e quello di destinazione è tale a quello di rsync, quindi:
41
+ - voglio copiare il contenuto della cartella /ciao con il contenuto onlin del pod nella cartella
42
+ /home/pippo, dovrò scrivere /ciao/. podname:/home/pippo "
43
+ option :stack_name, required: false, type: :string, aliases: ["--namespace", "-n"]
44
+
45
+ def rsync(src, dst)
46
+ reg_exp = /(?<pod_name>.*)\:(?<path>.*)/
47
+ if File.exist?(src)
48
+ # il src é la cartella, quindi la destizione è il pod
49
+ direction = :up
50
+ local_path = src
51
+ podname = dst.match(reg_exp)[:pod_name]
52
+ podpath = dst.match(reg_exp)[:path]
53
+ else
54
+ direction = :down
55
+ podname = src.match(reg_exp)[:pod_name]
56
+ podpath = src.match(reg_exp)[:path]
57
+ local_path = dst
58
+ end
59
+
60
+ puts "#{src} #{direction} #{dst}"
61
+
62
+ cfgs.env(options[:environment]) do |cfgs|
63
+
64
+ cfgs.stack_name = options[:stack_name] || cfgs.stack_name
65
+
66
+ sync = Kubernetes::SyncConfigs::Rsync.new(cfgs, {
67
+ service: Kubernetes::Pod.find_by_name(podname),
68
+ how: 'rsync',
69
+ configs: {
70
+ local: local_path,
71
+ remote: podpath
72
+ }
73
+ })
74
+
75
+ if direction == :down
76
+ sync.pull
77
+ end
78
+ if direction == :up
79
+ sync.push
80
+ end
81
+
82
+
83
+
84
+
85
+ end
86
+ end
87
+
88
+
89
+ end
90
+ end
@@ -0,0 +1,49 @@
1
+ module SwarmClusterCliOpe
2
+ module Kubernetes
3
+ class Configuration < BaseConfiguration
4
+
5
+
6
+ ##
7
+ # In kubernetes abbiamo il context, il context può essere ricevuto o dalla configurazione oppure dal current_context
8
+ # di kubelet
9
+ # @return [String]
10
+ def context
11
+ cmd = ShellCommandExecution.new(['kubectl config current-context'])
12
+ cmd.execute.raw_result[:stdout]
13
+ end
14
+
15
+ ##
16
+ # Salva le configurazioni base in HOME
17
+ def save_base_cfgs
18
+ super do |obj|
19
+ obj.merge({connections_maps: {context: context}})
20
+ end
21
+ end
22
+
23
+ ##
24
+ # In k8s utilizziamo namespace come identificativo per avere le idee più chiare a cosi ci riferiamo
25
+ alias_method :namespace, :stack_name
26
+
27
+
28
+ ##
29
+ # Funzione per la restituzione della classe di sincro corretta
30
+ def get_syncro(name)
31
+ case name
32
+ when 'sqlite3'
33
+ SyncConfigs::Sqlite3
34
+ when 'rsync'
35
+ SyncConfigs::Rsync
36
+ # when 'mysql'
37
+ # SyncConfigs::Mysql
38
+ # when 'pg'
39
+ # SyncConfigs::PostGres
40
+ else
41
+ logger.error { "CONFIGURAIONE NON PREVISTA: #{name}" }
42
+ nil
43
+ end
44
+ end
45
+
46
+
47
+ end
48
+ end
49
+ end
@@ -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