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,78 @@
|
|
1
|
+
module SwarmClusterCliOpe
|
2
|
+
module Models
|
3
|
+
class Container < Base
|
4
|
+
|
5
|
+
#@return [String]
|
6
|
+
attr_accessor :name
|
7
|
+
#@return [String]
|
8
|
+
attr_accessor :id
|
9
|
+
#@return [String] nome dell'immagine
|
10
|
+
attr_accessor :image
|
11
|
+
#@return [Hash] labels del container
|
12
|
+
attr_accessor :labels
|
13
|
+
|
14
|
+
|
15
|
+
def labels=(labels)
|
16
|
+
@labels = labels.split(",").collect { |a| a.split("=") }.collect { |a, b| [a, b] }.to_h
|
17
|
+
end
|
18
|
+
##
|
19
|
+
# Può essere che riceva dei valori dal config, tipo quando facciamo inspect
|
20
|
+
def config=(config)
|
21
|
+
@labels = config["Labels"]
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [String] id del nodo di appartenenza
|
25
|
+
def node_id
|
26
|
+
labels["com.docker.swarm.node.id"]
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [SwarmClusterCliOpe::Models::Container]
|
30
|
+
def self.find_by_service_name(service_name, stack_name: nil)
|
31
|
+
Service.find(service_name,stack_name:stack_name).containers.first
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.all(service_name: nil)
|
35
|
+
Commands::Container.new.ps(service_name: service_name).result(object_class: Container)
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Copia i file dentro al container
|
40
|
+
# @param [String] src sorgente da cui copiare
|
41
|
+
# @param [String] dest destinazione a cui copiare
|
42
|
+
def copy_in(src, dest)
|
43
|
+
docker_command.cp(src, "#{id}:#{dest}").success?
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Copia i file dal container all'esterno
|
48
|
+
# @param [String] src sorgente da cui copiare
|
49
|
+
# @param [String] dest destinazione a cui copiare
|
50
|
+
def copy_out(src, dest)
|
51
|
+
docker_command.cp("#{id}:#{src}", dest).success?
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# Ritorna il connection_uri del nodo che ospita il container
|
56
|
+
# @return [String]
|
57
|
+
def mapped_uri_connection
|
58
|
+
node.connection_uri
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Elenco dei volumi mappato
|
63
|
+
# @return [Array<MappedVolume>]
|
64
|
+
def mapped_volumes
|
65
|
+
docker_inspect.Mounts.collect { |v| MappedVolume.new(v, container: self) }
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
# Ritorna il nodo dello swarm che contiene questo container
|
70
|
+
# @return [SwarmClusterCliOpe::Node]
|
71
|
+
def node
|
72
|
+
cfgs.get_node_by_id(node_id)
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module SwarmClusterCliOpe
|
2
|
+
module Models
|
3
|
+
class MappedVolume < Base
|
4
|
+
|
5
|
+
#@return [Container]
|
6
|
+
attr_accessor :container
|
7
|
+
|
8
|
+
#@return [String] tipologia di volume mappato [bind,volume]
|
9
|
+
attr_accessor :type
|
10
|
+
|
11
|
+
#@return [String] sorgente del bind
|
12
|
+
attr_accessor :source
|
13
|
+
|
14
|
+
#@return [String] destinazione del bind nel container
|
15
|
+
attr_accessor :destination
|
16
|
+
|
17
|
+
def initialize(obj, container: nil)
|
18
|
+
super(obj)
|
19
|
+
@container = container
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# Controllo se il volume è bindato con l'host
|
24
|
+
def is_binded?
|
25
|
+
type == 'bind'
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Costruisce tutta la path da utilizzare per connettersi via ssh,
|
30
|
+
# se siamo in locale non sarà presente la parte di server e ":"
|
31
|
+
def ssh_connection_path
|
32
|
+
#costruisco la stringa per la parte di connetività del container
|
33
|
+
out = "#{source}"
|
34
|
+
if container.node.is_over_ssh_uri?
|
35
|
+
out = "#{container.node.hostname}:#{out}"
|
36
|
+
end
|
37
|
+
out
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module SwarmClusterCliOpe
|
2
|
+
module Models
|
3
|
+
class Service < Base
|
4
|
+
|
5
|
+
#@return [String]
|
6
|
+
attr_accessor :name
|
7
|
+
#@return [String]
|
8
|
+
attr_accessor :id
|
9
|
+
|
10
|
+
# @return [Array<SwarmClusterCliOpe::Service>]
|
11
|
+
def self.all(stack_name: nil)
|
12
|
+
Commands::Service.new.ls(stack_name: stack_name).result(object_class: Service)
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [SwarmClusterCliOpe::Service]
|
16
|
+
def self.find(service_name, stack_name: nil)
|
17
|
+
Commands::Service.new.find(service_name, stack_name: stack_name).result(object_class: Service).first
|
18
|
+
end
|
19
|
+
|
20
|
+
##
|
21
|
+
# Containers del servizio
|
22
|
+
|
23
|
+
# @return [Array<SwarmClusterCliOpe::Container>]
|
24
|
+
def containers
|
25
|
+
tasks.collect { |t| t.container }
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Elenco dei task del servizio
|
30
|
+
# docker service ps SERVICE_NAME --format="{{json .}}" -f "desired-state=running"
|
31
|
+
# @return [Array<Task>]
|
32
|
+
def tasks
|
33
|
+
docker_command.ps(name).result(object_class: Task)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module SwarmClusterCliOpe
|
2
|
+
module Models
|
3
|
+
class Stack < Base
|
4
|
+
|
5
|
+
#@return [:String]
|
6
|
+
attr_accessor :name
|
7
|
+
#@return [String]
|
8
|
+
attr_accessor :namespace
|
9
|
+
#@return [Integer]
|
10
|
+
attr_accessor :services
|
11
|
+
|
12
|
+
# @return [Array<Stack>]
|
13
|
+
def self.all
|
14
|
+
Commands::Swarm.new.ls.result(object_class: Stack)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module SwarmClusterCliOpe
|
2
|
+
module Models
|
3
|
+
class Task < Base
|
4
|
+
|
5
|
+
#@return [String]
|
6
|
+
attr_accessor :name
|
7
|
+
#@return [String]
|
8
|
+
attr_accessor :id
|
9
|
+
#@return [String] nome dell'immagine
|
10
|
+
attr_accessor :node
|
11
|
+
|
12
|
+
|
13
|
+
##
|
14
|
+
# Estrapola il container dal task
|
15
|
+
def container
|
16
|
+
stack_info = docker_inspect
|
17
|
+
|
18
|
+
cmd = Commands::Container.new(connection_uri: cfgs.get_node_by_id(stack_info.NodeID).connection_uri)
|
19
|
+
container = cmd.docker_inspect(stack_info.Status["ContainerStatus"]["ContainerID"]).result(object_class: Container).first
|
20
|
+
|
21
|
+
container
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module SwarmClusterCliOpe
|
4
|
+
class Node
|
5
|
+
include LoggerConcern
|
6
|
+
|
7
|
+
#@return [String] nome del nodo
|
8
|
+
attr_accessor :name
|
9
|
+
#@return [String] nome da utilizzare nella parte DOCKER_HOST=CONNECTION_URI
|
10
|
+
attr_accessor :connection_uri
|
11
|
+
|
12
|
+
|
13
|
+
# @param [String] name
|
14
|
+
# @param [String] connection_uri
|
15
|
+
def initialize(name: nil, connection_uri: nil)
|
16
|
+
@name = name
|
17
|
+
@connection_uri = connection_uri || name
|
18
|
+
end
|
19
|
+
|
20
|
+
##
|
21
|
+
# Mi definisce se la connessione che stiamo facendo con questo nodo, la facciamo tramite SSH oppure è locale
|
22
|
+
def is_over_ssh_uri?
|
23
|
+
connection_uri.match?(/\Assh\:/)
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# connection_uri senza la parte del protocollo
|
28
|
+
# @return [String]
|
29
|
+
def hostname
|
30
|
+
URI(connection_uri).host
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
##
|
35
|
+
# Controlla se questo nodo è un manager
|
36
|
+
# @return [TrueClass,FalseClass]
|
37
|
+
def manager?
|
38
|
+
info.Swarm["RemoteManagers"].collect { |n| n["NodeID"] }.include?(info.Swarm["NodeID"])
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# ID univoco del nodo
|
43
|
+
# @return [String]
|
44
|
+
def id
|
45
|
+
info.Swarm.NodeID
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Info del nodo da parte di docker
|
50
|
+
# @return [OpenStruct]
|
51
|
+
def info
|
52
|
+
# path al file di cache
|
53
|
+
# TODO sarebbe da aggiornare ogni tanto, metti che uno non spegne mai il pc
|
54
|
+
path = "/tmp/.swarm_cluster_cli_info_cache_#{name}"
|
55
|
+
if File.exist?(path)
|
56
|
+
i = JSON.parse(File.read(path), object_class: OpenStruct)
|
57
|
+
else
|
58
|
+
i = Node.info(connection_uri)
|
59
|
+
#mi salvo in cache le info scaricate
|
60
|
+
File.open(path, "w") do |f|
|
61
|
+
f.write(i.to_h.to_json)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
i
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
# Ritorna le info base di un nodo
|
70
|
+
def self.info(connection_uri)
|
71
|
+
command = Commands::Base.new
|
72
|
+
command.docker_host = "DOCKER_HOST=#{connection_uri}"
|
73
|
+
result = command.command do |cmd|
|
74
|
+
cmd.add("info")
|
75
|
+
end.execute.result.first
|
76
|
+
result
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'open4'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module SwarmClusterCliOpe
|
5
|
+
class ShellCommandExecution
|
6
|
+
include LoggerConcern
|
7
|
+
|
8
|
+
#@return [Array<String>] comando da eseguire
|
9
|
+
attr_accessor :cmd
|
10
|
+
|
11
|
+
# @param [Array<String>,String] cmd
|
12
|
+
def initialize(cmd)
|
13
|
+
cmd = cmd.split(" ") if cmd.is_a? String
|
14
|
+
@cmd = cmd
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [SwarmClusterCliOpe::ShellCommandExecution]
|
18
|
+
# @param [*String] append_command
|
19
|
+
def add(*append_command)
|
20
|
+
@cmd.append(append_command)
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
class Failure < Error
|
25
|
+
def initialize(cmd, error)
|
26
|
+
super("[SYSTEM COMMAND FAILURE] #{cmd} -> #{error}")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Esegue il comando e ritorna STDOUT, altrimenti se va in errore esegue un raise
|
32
|
+
# @return [ShellCommandResponse]
|
33
|
+
# @param [FalseClass] allow_failure -> se impostato a true, ritorniamo risultato anche quando fallisce
|
34
|
+
def execute(allow_failure: false)
|
35
|
+
result = {
|
36
|
+
stdout: nil,
|
37
|
+
stderr: nil,
|
38
|
+
pid: nil,
|
39
|
+
status: nil
|
40
|
+
}
|
41
|
+
logger.debug { "SHELL: #{string_command}" }
|
42
|
+
result[:status] = Open4::popen4(string_command) do |pid, stdin, stdout, stderr|
|
43
|
+
stdin.close
|
44
|
+
|
45
|
+
result[:stdout] = stdout.read.strip
|
46
|
+
result[:stderr] = stderr.read.strip
|
47
|
+
result[:pid] = pid
|
48
|
+
end
|
49
|
+
|
50
|
+
unless allow_failure
|
51
|
+
raise Failure.new(cmd, result[:stderr]) if (result[:status] && result[:status].exitstatus != 0)
|
52
|
+
end
|
53
|
+
|
54
|
+
logger.debug { "SHELL_RESPONSE: #{JSON.pretty_generate(result)}" }
|
55
|
+
|
56
|
+
ShellCommandResponse.new(result)
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# Stampa il comando
|
61
|
+
# @return [String]
|
62
|
+
def string_command
|
63
|
+
@cmd.join(' ')
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module SwarmClusterCliOpe
|
4
|
+
##
|
5
|
+
# Identifica una risposta dalla shell
|
6
|
+
class ShellCommandResponse
|
7
|
+
extend Forwardable
|
8
|
+
include LoggerConcern
|
9
|
+
|
10
|
+
#@return [String]
|
11
|
+
attr_accessor :raw_result
|
12
|
+
|
13
|
+
# @param [Hash] result composto da:
|
14
|
+
# stdout: [String],
|
15
|
+
# stderr: [String],
|
16
|
+
# pid: [Integer],
|
17
|
+
# status: [Process::Status]
|
18
|
+
def initialize(result)
|
19
|
+
@raw_result = result
|
20
|
+
end
|
21
|
+
|
22
|
+
# ##
|
23
|
+
# # Ritorna una versione stampabile del risultato
|
24
|
+
# def to_s
|
25
|
+
# raw_result[:stdout]
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
##
|
29
|
+
# Risultato, essendo sempre composto da una lista di righe in formato json, ritorniamo un array di json
|
30
|
+
# @param [Object] object_class
|
31
|
+
# @return [Array<object_class>]
|
32
|
+
def result(object_class: OpenStruct)
|
33
|
+
raw_result[:stdout].split("\n").collect { |s| object_class.new(JSON.parse(s)) }
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# def to_a
|
38
|
+
# raw_result[:stdout].split("\n")
|
39
|
+
# end
|
40
|
+
|
41
|
+
##
|
42
|
+
# Controlla se il valore di status è diverso da 0
|
43
|
+
def failed?
|
44
|
+
raw_result[:status].exitstatus.to_i != 0
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Inverso di :failed?
|
49
|
+
# @return [TrueClass, FalseClass]
|
50
|
+
def success?
|
51
|
+
!failed?
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# Ritorna l'errore della shell
|
56
|
+
def stderr
|
57
|
+
raw_result[:stderr]
|
58
|
+
end
|
59
|
+
|
60
|
+
# ##
|
61
|
+
# # Quando il risultato è json, ritorniamo l'hash
|
62
|
+
# def from_json
|
63
|
+
# JSON.parse(raw_result[:stdout])
|
64
|
+
# end
|
65
|
+
|
66
|
+
def_delegators :result, :collect, :last, :find
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require_relative 'lib/swarm_cluster_cli_ope/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "swarm_cluster_cli_ope"
|
5
|
+
spec.version = SwarmClusterCliOpe::VERSION
|
6
|
+
spec.authors = ["Marino Bonetti"]
|
7
|
+
spec.email = ["marinobonetti@gmail.com"]
|
8
|
+
|
9
|
+
spec.summary = "WIP Gemma per la gestione del cluster swarm"
|
10
|
+
spec.description = "Gestione di varie operazioni come sincronia con le cartelle bindate dei container (rsync) up o
|
11
|
+
down e possibilità di scaricare/caricare i file direttamente all'interno del cluster, in
|
12
|
+
modo facilitato"
|
13
|
+
spec.homepage = "https://gitlab.archimedianet.it/sistemi/swarm_cluster_cli_ope"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
16
|
+
|
17
|
+
spec.metadata["allowed_push_host"] = 'https://rubygems.org/'
|
18
|
+
|
19
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
20
|
+
# spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
|
21
|
+
# spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
22
|
+
|
23
|
+
# Specify which files should be added to the gem when it is released.
|
24
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
25
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
26
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|test_folder)/}) }
|
27
|
+
end
|
28
|
+
spec.bindir = "exe"
|
29
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
30
|
+
spec.require_paths = ["lib"]
|
31
|
+
|
32
|
+
spec.add_dependency "thor", '~>1.0'
|
33
|
+
spec.add_dependency "zeitwerk", '~>2.3'
|
34
|
+
spec.add_dependency "open4"
|
35
|
+
spec.add_dependency "activesupport"
|
36
|
+
end
|