swarm_cluster_cli_ope 0.1.2

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 (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