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,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,3 @@
1
+ module SwarmClusterCliOpe
2
+ VERSION = "0.1.2"
3
+ end
@@ -0,0 +1,7 @@
1
+ module SwarmClusterCliOpe
2
+ class Worker < Node
3
+ def manager?
4
+ false
5
+ end
6
+ end
7
+ 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