swarm_cluster_cli_ope 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +9 -0
  6. data/Gemfile +6 -0
  7. data/Gemfile.lock +39 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +121 -0
  10. data/Rakefile +2 -0
  11. data/exe/swarm_cli_ope +4 -0
  12. data/lib/swarm_cluster_cli_ope.rb +8 -0
  13. data/lib/swarm_cluster_cli_ope/cli.rb +251 -0
  14. data/lib/swarm_cluster_cli_ope/commands/base.rb +70 -0
  15. data/lib/swarm_cluster_cli_ope/commands/container.rb +24 -0
  16. data/lib/swarm_cluster_cli_ope/commands/service.rb +42 -0
  17. data/lib/swarm_cluster_cli_ope/commands/swarm.rb +14 -0
  18. data/lib/swarm_cluster_cli_ope/commands/task.rb +11 -0
  19. data/lib/swarm_cluster_cli_ope/configuration.rb +189 -0
  20. data/lib/swarm_cluster_cli_ope/configuration_concern.rb +24 -0
  21. data/lib/swarm_cluster_cli_ope/logger_concern.rb +26 -0
  22. data/lib/swarm_cluster_cli_ope/manager.rb +9 -0
  23. data/lib/swarm_cluster_cli_ope/models/base.rb +42 -0
  24. data/lib/swarm_cluster_cli_ope/models/container.rb +78 -0
  25. data/lib/swarm_cluster_cli_ope/models/mapped_volume.rb +43 -0
  26. data/lib/swarm_cluster_cli_ope/models/service.rb +38 -0
  27. data/lib/swarm_cluster_cli_ope/models/stack.rb +18 -0
  28. data/lib/swarm_cluster_cli_ope/models/task.rb +27 -0
  29. data/lib/swarm_cluster_cli_ope/node.rb +80 -0
  30. data/lib/swarm_cluster_cli_ope/shell_command_execution.rb +68 -0
  31. data/lib/swarm_cluster_cli_ope/shell_command_response.rb +68 -0
  32. data/lib/swarm_cluster_cli_ope/version.rb +3 -0
  33. data/lib/swarm_cluster_cli_ope/worker.rb +7 -0
  34. data/swarm_cluster_cli_ope.gemspec +36 -0
  35. metadata +138 -0
@@ -0,0 +1,70 @@
1
+ module SwarmClusterCliOpe
2
+ module Commands
3
+ class Base
4
+ include LoggerConcern
5
+ include ConfigurationConcern
6
+
7
+ #@return [String] Identifivo per potersi collegare
8
+ attr_accessor :docker_host
9
+
10
+ #@return [Array<String>] elenco di comandi da aggiungere in coda al comando lanciato
11
+ attr_accessor :base_suffix_command
12
+
13
+ def initialize(connection_uri: nil, base_suffix_command: ["--format=\"{{json .}}\""])
14
+ if connection_uri
15
+ if connection_uri.blank?
16
+ @docker_host = "DOCKER_HOST=" # casistica di sviluppo, in cui l'host viene mappato localmente
17
+ else
18
+ @docker_host = "DOCKER_HOST=#{connection_uri}"
19
+ end
20
+ end
21
+ @base_suffix_command = base_suffix_command
22
+ end
23
+
24
+
25
+ def docker_host
26
+ return @docker_host unless @docker_host.nil?
27
+ @docker_host = if Configuration.exist_base?
28
+ "DOCKER_HOST=#{cfgs.managers.first.connection_uri}"
29
+ end
30
+ end
31
+
32
+ ##
33
+ # Aggiunge al blocco passato di comandi, i comandi standard iniziali
34
+ # @return [SwarmClusterCliOpe::ShellCommandExecution]
35
+ def command
36
+ cmd = ShellCommandExecution.new(base_prefix_command)
37
+ yield cmd if block_given?
38
+ cmd.add(*base_suffix_command)
39
+ end
40
+
41
+ ##Esegue l'inspect sul componente
42
+ # @param [String] id
43
+ # @return [SwarmClusterCliOpe::ShellCommandResponse]
44
+ def docker_inspect(id)
45
+ command do |cmd|
46
+ cmd.add(" #{self.class.object_identifier} inspect #{id}")
47
+ end.execute
48
+ end
49
+
50
+ ##
51
+ # Ritorna il nome identificativo dell'elemento all'interno di docker: container,service,stack ecc..
52
+ # @return [String]
53
+ def self.object_identifier
54
+ self.name.demodulize.downcase
55
+ end
56
+
57
+ private
58
+
59
+ def base_prefix_command
60
+ if cfgs.development_mode?
61
+ ["docker"]
62
+ else
63
+ [docker_host, "docker"]
64
+ end
65
+ end
66
+
67
+
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,24 @@
1
+ module SwarmClusterCliOpe
2
+ module Commands
3
+ class Container < Base
4
+
5
+ def cp(src, dest)
6
+ self.base_suffix_command = []
7
+ command do |cmd|
8
+ cmd.add("cp #{src} #{dest}")
9
+ end.execute
10
+ end
11
+
12
+ ##
13
+ # Esegue il ps sui container, possibile filtrare passando nome stack e/o nome servizio
14
+ # @param [String] service_name
15
+ # @return [SwarmClusterCliOpe::ShellCommandResponse]
16
+ def ps(service_name: nil)
17
+ command do |cmd|
18
+ cmd.add("ps")
19
+ cmd.add("--filter=\"label=com.docker.swarm.service.name=#{service_name}\"") if service_name
20
+ end.execute
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,42 @@
1
+ module SwarmClusterCliOpe
2
+ module Commands
3
+ class Service < Base
4
+
5
+ # @return [SwarmClusterCliOpe::ShellCommandResponse]
6
+ # @param [String] stack_name nome dello stack da filtrare
7
+ def ls(stack_name: nil)
8
+ command do |cmd|
9
+ cmd.add("service ls")
10
+ cmd.add("--filter=\"label=com.docker.stack.namespace=#{stack_name}\"") if stack_name
11
+ end.execute
12
+ end
13
+
14
+ ##
15
+ # Ricarca il servizio per nome, nel caso in cui abbiamo anche il nome dello stack, concateniamo il nome
16
+ # del servizio con lo stack (dato che è il sistema con cui è più semplice trovare un servizio di uno stack).
17
+ # sucessivamente troviamo tutti i containers legati a quel servizio ed estrapoliamo l'istanza del primo
18
+ # @param [String] service_name
19
+ # @param [String] stack_name optional
20
+ # @return [SwarmClusterCliOpe::ShellCommandResponse]
21
+ def find(service_name, stack_name: nil)
22
+ command do |cmd|
23
+ cmd.add("service ls --filter=\"name=#{ stack_name ? "#{stack_name}_" : "" }#{service_name}\"")
24
+ end.execute
25
+ end
26
+
27
+ ##
28
+ # Esegue il ps per un determinato servizio
29
+ # @param [String] service_name
30
+ # @param [String] stack_name optional
31
+ # @return [SwarmClusterCliOpe::ShellCommandResponse]
32
+ # @param [TrueClass] only_active se si vuole avere solo quelli attivi
33
+ def ps(service_name, stack_name: nil, only_active: true)
34
+ command do |cmd|
35
+ cmd.add("service ps #{stack_name ? "#{stack_name}_" : "" }#{service_name}")
36
+ cmd.add('-f "desired-state=running"') if only_active
37
+ end.execute
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,14 @@
1
+ module SwarmClusterCliOpe
2
+ module Commands
3
+ class Swarm < Base
4
+
5
+ # @return [SwarmClusterCliOpe::ShellCommandResponse]
6
+ def ls
7
+ command do |cmd|
8
+ cmd.add("stack ls")
9
+ end.execute
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ module SwarmClusterCliOpe
2
+ module Commands
3
+ class Task < Base
4
+
5
+ def self.object_identifier
6
+ ""
7
+ end
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,189 @@
1
+ require "singleton"
2
+ require "fileutils"
3
+ require "json"
4
+ require 'digest'
5
+ require "active_support/core_ext/hash"
6
+ module SwarmClusterCliOpe
7
+ ##
8
+ # Classe per la gestione delle configurazioni, unisce le configurazioni di base alle configurazioni di progetto;
9
+ # le quali sono salvate nel file di configurazione del progetto .swarm_cluster_project sottoforma di json
10
+ # che vengono mergiate sulle configurazioni base
11
+ class Configuration
12
+ include Singleton
13
+ include LoggerConcern
14
+
15
+ #@return [String] nome dello stack con cui lavoriamo
16
+ attr_accessor :stack_name
17
+
18
+ NoBaseConfigurations = Class.new(Error)
19
+
20
+ ##
21
+ # Lista di nodi su cui lavorare
22
+ # @return [Array<SwarmClusterCliOpe::Manager>]
23
+ def managers
24
+ return @_managers if @_managers
25
+ @_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) }
26
+ end
27
+
28
+ ##
29
+ # Esegue un refresh della lista dei manager, ciclando su tutti i nodi, e scrivendo in /tmp un file temporaneo con
30
+ # con la lista dei nomi dei managers
31
+ def refresh_managers_cache_list
32
+ list = self.nodes.select(&:manager?).collect { |c| Manager.new(name: c.name, connection_uri: c.connection_uri) }
33
+ File.open(swarm_manager_cache_path, "w") do |f|
34
+ list.collect(&:name).each do |name|
35
+ f.puts(name)
36
+ end
37
+ end
38
+ end
39
+
40
+ def read_managers_cache_list
41
+ # TODO sarebbe da aggiornare ogni tanto, metti che uno non spegne mai il pc
42
+ refresh_managers_cache_list unless File.exists?(swarm_manager_cache_path)
43
+ File.read(swarm_manager_cache_path).split("\n")
44
+ end
45
+
46
+ ##
47
+ # Lista di tutti i nodi del cluster
48
+ #
49
+ # @return [Array<SwarmClusterCliOpe::Node>]
50
+ def nodes
51
+ return @_nodes if @_nodes
52
+ @_nodes = self.class.merged_configurations[:connections_maps].collect { |m, c| Node.new(name: m.to_s, connection_uri: c) }
53
+ end
54
+
55
+ ##
56
+ # Lista di nodi da assegnare alle configurazioni
57
+ #
58
+ # @param [Array<SwarmClusterCliOpe::Node>]
59
+ # @return [Configuration]
60
+ def nodes=(objs)
61
+ @_nodes = objs
62
+ self
63
+ end
64
+
65
+ # @return [String,NilClass] nome dello stack del progetto se configurato
66
+ def stack_name
67
+ return @stack_name if @stack_name
68
+ return nil unless self.class.exist_base?
69
+ @stack_name = self.class.merged_configurations[:stack_name] if self.class.merged_configurations.key?(:stack_name)
70
+ end
71
+
72
+ ##
73
+ # Livello di logging
74
+ # @return [Integer]
75
+ def logger_level
76
+ self.class.merged_configurations[:log_level].to_s || "0"
77
+ end
78
+
79
+ ##
80
+ # Siamo in sviluppo?
81
+ # @return [TrueClass, FalseClass]
82
+ def development_mode?
83
+ return false unless self.class.exist_base?
84
+ self.class.merged_configurations.key?(:dev_mode)
85
+ end
86
+
87
+ ##
88
+ # Controlla se esiste il file di configurazione base, nella home dell'utente
89
+ def self.exist_base?
90
+ File.exist?(base_cfg_path)
91
+ end
92
+
93
+
94
+ ##
95
+ # Salva le configurazioni base in HOME
96
+ def save_base_cfgs
97
+ FileUtils.mkdir_p(File.dirname(self.class.base_cfg_path))
98
+ File.open(self.class.base_cfg_path, "wb") do |f|
99
+ f.write({
100
+ version: SwarmClusterCliOpe::VERSION,
101
+ connections_maps: nodes.collect { |k| [k.name, k.connection_uri] }.to_h
102
+ }.to_json)
103
+ end
104
+ end
105
+
106
+ ##
107
+ # Si occupa del salvataggio delle configurazioni di progetto, se abbiamo lo stack_name
108
+ def save_project_cfgs
109
+ if @stack_name
110
+ File.open(File.join(FileUtils.pwd, self.class.cfgs_project_file_name), "wb") do |f|
111
+ f.write({
112
+ stack_name: stack_name
113
+ }.to_json)
114
+ end
115
+ end
116
+ end
117
+
118
+ # @return [String] path to base home configurations
119
+ def self.base_cfg_path
120
+ File.join(ENV['HOME'], '.swarm_cluster', 'config.json')
121
+ end
122
+
123
+ # @return [SwarmClusterCliOpe::Node]
124
+ # @param [String] node nome del nodo
125
+ def get_node(node)
126
+ nodes.find { |c| c.name == node }
127
+ end
128
+
129
+ # @return [SwarmClusterCliOpe::Node]
130
+ # @param [String] node_id
131
+ def get_node_by_id(node_id)
132
+ nodes.find { |c| c.id == node_id }
133
+ end
134
+
135
+ private
136
+
137
+ ##
138
+ # nome del file in cui salvare le configurazioni di progetto
139
+ # @return [String]
140
+ def self.cfgs_project_file_name
141
+ '.swarm_cluster_project'
142
+ end
143
+
144
+ ##
145
+ # Path al file dove salviamo la cache dei managers, ha un TTL legato all'orario (anno-mese-giorno-ora)
146
+ # quindi ogni ora si autoripulisce e con un md5 delle configurazioni di base
147
+ # @return [String]
148
+ def swarm_manager_cache_path
149
+ md5 = Digest::MD5.hexdigest(self.class.merged_configurations.to_json)
150
+ file_name = Time.now.strftime(".swarm_cluster_cli_manager_cache-%Y%m%d%H-#{md5}")
151
+ File.join("/tmp", file_name)
152
+ end
153
+
154
+ ##
155
+ # Legge le configurazioni base
156
+ #
157
+ # @return [Hash]
158
+ def self.read_base
159
+ raise NoBaseConfigurations unless exist_base?
160
+ JSON.parse(File.read(self.base_cfg_path)).deep_symbolize_keys
161
+ end
162
+
163
+ ## Cerca le configurazioni di progetto e le mergia se sono presenti
164
+ # @return [Hash]
165
+ def self.merged_configurations
166
+ return @_merged_configurations if @_merged_configurations
167
+ project_file = nil
168
+ folder = FileUtils.pwd
169
+ loop do
170
+
171
+ if File.exist?(File.join(folder, cfgs_project_file_name))
172
+ project_file = File.join(folder, cfgs_project_file_name)
173
+ end
174
+
175
+ break unless project_file.nil?
176
+ break if folder == '/'
177
+ folder = File.expand_path("..", folder)
178
+ end
179
+
180
+ project_cfgs = {}
181
+ unless project_file.nil?
182
+ project_cfgs = JSON.parse(File.read(project_file)).deep_symbolize_keys
183
+ end
184
+
185
+ @_merged_configurations = read_base.merge(project_cfgs)
186
+ end
187
+
188
+ end
189
+ end
@@ -0,0 +1,24 @@
1
+ module SwarmClusterCliOpe
2
+ module ConfigurationConcern
3
+
4
+ def self.included(klass)
5
+ klass.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+ ##
10
+ # Configurazioni standard
11
+ # @return [SwarmClusterCliOpe::Configuration]
12
+ def cfgs
13
+ Configuration.instance
14
+ end
15
+ end
16
+
17
+
18
+ # @return [SwarmClusterCliOpe::Configuration]
19
+ def cfgs
20
+ self.class.cfgs
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ require 'logger'
2
+
3
+ module SwarmClusterCliOpe
4
+ module LoggerConcern
5
+ def logger
6
+ return LoggerConcern.const_get("LOGGER") if LoggerConcern.const_defined?("LOGGER")
7
+ logger = Logger.new(STDOUT)
8
+ LoggerConcern.const_set("LOGGER", logger)
9
+ logger.level = case Configuration.instance.logger_level
10
+ when "0"
11
+ Logger::ERROR
12
+ when "1"
13
+ Logger::WARN
14
+ when "2"
15
+ Logger::INFO
16
+ when "3"
17
+ Logger::DEBUG
18
+ else
19
+ Logger::ERROR
20
+ end
21
+
22
+ logger
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,9 @@
1
+ module SwarmClusterCliOpe
2
+ class Manager < Node
3
+
4
+ def manager?
5
+ true
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,42 @@
1
+ require 'active_support/core_ext/string'
2
+ module SwarmClusterCliOpe
3
+ module Models
4
+ class Base
5
+ include LoggerConcern
6
+ include ConfigurationConcern
7
+
8
+ def initialize(obj)
9
+ logger.debug { obj.inspect }
10
+ obj.each do |k, v|
11
+ name = k.underscore
12
+ self.send("#{name}=", v) if respond_to?("#{name}=".to_sym)
13
+ end
14
+ end
15
+
16
+ IDNotFoundOnObject = Class.new(Error)
17
+
18
+ ##
19
+ # Esegue un inspect del tipo di componente di docker
20
+ def docker_inspect
21
+ raise IDNotFoundOnObject if id.blank?
22
+ docker_command.docker_inspect(id).result.first
23
+ end
24
+
25
+
26
+ ##
27
+ # Ritorna il comando corretto, inizializzato con la connecttion uri corretta
28
+ # @return [Commands::Base]
29
+ def docker_command
30
+ Commands.const_get(self.class.name.demodulize).new(connection_uri: mapped_uri_connection)
31
+ end
32
+
33
+ ##
34
+ # Override della connessione al nodo corretto, i container sono legati allo swarm, conseguentemente dobbiamo
35
+ # collegarci al nodo giusto, di default lasiamo nil, così che prende le cfgs di default
36
+ def mapped_uri_connection
37
+ nil
38
+ end
39
+
40
+ end
41
+ end
42
+ end