swarm_cluster_cli_ope 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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