swarm_cluster_cli_ope 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +9 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +39 -0
- data/LICENSE.txt +21 -0
- data/README.md +121 -0
- data/Rakefile +2 -0
- data/exe/swarm_cli_ope +4 -0
- data/lib/swarm_cluster_cli_ope.rb +8 -0
- data/lib/swarm_cluster_cli_ope/cli.rb +251 -0
- data/lib/swarm_cluster_cli_ope/commands/base.rb +70 -0
- data/lib/swarm_cluster_cli_ope/commands/container.rb +24 -0
- data/lib/swarm_cluster_cli_ope/commands/service.rb +42 -0
- data/lib/swarm_cluster_cli_ope/commands/swarm.rb +14 -0
- data/lib/swarm_cluster_cli_ope/commands/task.rb +11 -0
- data/lib/swarm_cluster_cli_ope/configuration.rb +189 -0
- data/lib/swarm_cluster_cli_ope/configuration_concern.rb +24 -0
- data/lib/swarm_cluster_cli_ope/logger_concern.rb +26 -0
- data/lib/swarm_cluster_cli_ope/manager.rb +9 -0
- data/lib/swarm_cluster_cli_ope/models/base.rb +42 -0
- data/lib/swarm_cluster_cli_ope/models/container.rb +78 -0
- data/lib/swarm_cluster_cli_ope/models/mapped_volume.rb +43 -0
- data/lib/swarm_cluster_cli_ope/models/service.rb +38 -0
- data/lib/swarm_cluster_cli_ope/models/stack.rb +18 -0
- data/lib/swarm_cluster_cli_ope/models/task.rb +27 -0
- data/lib/swarm_cluster_cli_ope/node.rb +80 -0
- data/lib/swarm_cluster_cli_ope/shell_command_execution.rb +68 -0
- data/lib/swarm_cluster_cli_ope/shell_command_response.rb +68 -0
- data/lib/swarm_cluster_cli_ope/version.rb +3 -0
- data/lib/swarm_cluster_cli_ope/worker.rb +7 -0
- data/swarm_cluster_cli_ope.gemspec +36 -0
- 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,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,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
|