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