swarm_cluster_cli_ope 0.4 → 0.5.0.pre.3

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.
@@ -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