indocker 0.5.4 → 0.6.1
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 +4 -4
- data/Gemfile.lock +1 -1
- data/lib/indocker/artifacts/services/synchronizer.rb +2 -9
- data/lib/indocker/deployment_checker.rb +1 -6
- data/lib/indocker/launchers/configuration_deployer.rb +26 -29
- data/lib/indocker/launchers/dto/remote_operation_dto.rb +18 -1
- data/lib/indocker/rsync.rb +7 -1
- data/lib/indocker/server.rb +6 -2
- data/lib/indocker/server_pools/server_connection.rb +1 -6
- data/lib/indocker/ssh_session.rb +53 -4
- data/lib/indocker/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0eda49208a82e456326a838e76cab5152673a078e30668832c97c7c1b525a4fa
|
|
4
|
+
data.tar.gz: 5904dabfde8e6110821b104be6fd0809799a5deb3b35f991bed33627323bf1c9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '020961f0cae0f634adf95f355d2e537bca061ec0380294f0a12ca75d0e72b54f19eb57d357dbe1f1dbef50cfd5d43896ed7f80107fe7ac822e6d3addd2645586'
|
|
7
|
+
data.tar.gz: f9ff2feb5c3288474635a3e7d31354c8065075068872865f51b6542e1c6933c0342e253260c58492cc7eb99e3ae09e1904692a9b1acd5a13751d467ca0edaf5d
|
data/Gemfile.lock
CHANGED
|
@@ -13,14 +13,9 @@ class Indocker::Artifacts::Services::Synchronizer
|
|
|
13
13
|
remote_operations += servers.map do |server|
|
|
14
14
|
@progress.start_syncing_artifact(server, artifact)
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Indocker::Launchers::DTO::RemoteOperationDTO.spawn(server, :artifact_sync) do
|
|
17
17
|
server.synchronize do
|
|
18
|
-
session = Indocker::SshSession.
|
|
19
|
-
host: server.host,
|
|
20
|
-
user: server.user,
|
|
21
|
-
port: server.port,
|
|
22
|
-
logger: @logger
|
|
23
|
-
)
|
|
18
|
+
session = Indocker::SshSession.for(server, logger: @logger)
|
|
24
19
|
|
|
25
20
|
if artifact.is_git?
|
|
26
21
|
@logger.info("Pulling git artifact #{artifact.name.to_s.green} for #{server.user}@#{server.host}")
|
|
@@ -49,8 +44,6 @@ class Indocker::Artifacts::Services::Synchronizer
|
|
|
49
44
|
@progress.finish_syncing_artifact(server, artifact)
|
|
50
45
|
end
|
|
51
46
|
end
|
|
52
|
-
|
|
53
|
-
Indocker::Launchers::DTO::RemoteOperationDTO.new(thread, server, :artifact_sync)
|
|
54
47
|
end
|
|
55
48
|
end
|
|
56
49
|
|
|
@@ -58,12 +58,7 @@ class Indocker::DeploymentChecker
|
|
|
58
58
|
total_missing_containers = []
|
|
59
59
|
|
|
60
60
|
server_list.each do |server|
|
|
61
|
-
session = Indocker::SshSession.
|
|
62
|
-
host: server.host,
|
|
63
|
-
user: server.user,
|
|
64
|
-
port: server.port,
|
|
65
|
-
logger: @debug_logger
|
|
66
|
-
)
|
|
61
|
+
session = Indocker::SshSession.for(server, logger: @debug_logger)
|
|
67
62
|
|
|
68
63
|
result = session.exec!('docker ps --filter="status=running" --format="{{.Names}}"')
|
|
69
64
|
|
|
@@ -105,8 +105,25 @@ class Indocker::Launchers::ConfigurationDeployer
|
|
|
105
105
|
|
|
106
106
|
def wait_remote_operations(remote_operations)
|
|
107
107
|
remote_operations.each do |remote_operation|
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
server = remote_operation.server
|
|
109
|
+
target = "#{server.name} (#{server.user}@#{server.host})"
|
|
110
|
+
|
|
111
|
+
begin
|
|
112
|
+
if remote_operation.thread.join(Indocker.remote_operation_timeout).nil?
|
|
113
|
+
@global_logger.error("Deployment aborted. Remote operation :#{remote_operation.operation} on server #{target} did not finish during #{Indocker.remote_operation_timeout} seconds.")
|
|
114
|
+
exit 1
|
|
115
|
+
end
|
|
116
|
+
rescue StandardError => e
|
|
117
|
+
# Raised by the operation thread and re-raised here by #join, so the server
|
|
118
|
+
# that failed can be named instead of dumping a bare backtrace.
|
|
119
|
+
reason = e.message.to_s.empty? || e.message == e.class.name ? e.class.to_s : "#{e.class}: #{e.message}"
|
|
120
|
+
|
|
121
|
+
@global_logger.error("Deployment aborted. Remote operation :#{remote_operation.operation} on server #{target} failed: #{reason}")
|
|
122
|
+
|
|
123
|
+
if e.is_a?(Errno::ETIMEDOUT) || (defined?(Net::SSH::ConnectionTimeout) && e.is_a?(Net::SSH::ConnectionTimeout))
|
|
124
|
+
@global_logger.error("Server #{server.host} did not accept a connection within #{Indocker.ssh_connect_timeout} seconds. It is powered off, or its address does not accept TCP.")
|
|
125
|
+
end
|
|
126
|
+
|
|
110
127
|
exit 1
|
|
111
128
|
end
|
|
112
129
|
end
|
|
@@ -380,13 +397,8 @@ class Indocker::Launchers::ConfigurationDeployer
|
|
|
380
397
|
remote_operations += repositories.map do |alias_name, repository|
|
|
381
398
|
@progress.start_syncing_repository(server, alias_name)
|
|
382
399
|
|
|
383
|
-
|
|
384
|
-
session = Indocker::SshSession.
|
|
385
|
-
host: server.host,
|
|
386
|
-
user: server.user,
|
|
387
|
-
port: server.port,
|
|
388
|
-
logger: @logger
|
|
389
|
-
)
|
|
400
|
+
Indocker::Launchers::DTO::RemoteOperationDTO.spawn(server, :repository_pull) do
|
|
401
|
+
session = Indocker::SshSession.for(server, logger: @logger)
|
|
390
402
|
|
|
391
403
|
if repository.is_local?
|
|
392
404
|
@logger.info("Rsyncing repository :#{alias_name} from #{repository.root_path} to #{server.user}@#{server.host}:#{repository.clone_path}")
|
|
@@ -415,8 +427,6 @@ class Indocker::Launchers::ConfigurationDeployer
|
|
|
415
427
|
|
|
416
428
|
@progress.finish_syncing_repository(server, alias_name)
|
|
417
429
|
end
|
|
418
|
-
|
|
419
|
-
Indocker::Launchers::DTO::RemoteOperationDTO.new(thread, server, :repository_pull)
|
|
420
430
|
end
|
|
421
431
|
end
|
|
422
432
|
|
|
@@ -455,6 +465,7 @@ class Indocker::Launchers::ConfigurationDeployer
|
|
|
455
465
|
@logger.info("Updating crontab file #{deploy_user}:#{crontab_filepath}")
|
|
456
466
|
|
|
457
467
|
ssh_options = "-o BatchMode=yes -o ConnectTimeout=#{Indocker.ssh_connect_timeout}"
|
|
468
|
+
ssh_options = "#{ssh_options} -J #{server.jump_host}" if server.jump_host
|
|
458
469
|
|
|
459
470
|
Indocker::Shell.command("scp #{ssh_options} #{tmp_crontab_file.path} #{deploy_user}:#{crontab_filepath}", @logger)
|
|
460
471
|
|
|
@@ -467,13 +478,8 @@ class Indocker::Launchers::ConfigurationDeployer
|
|
|
467
478
|
servers.map do |server|
|
|
468
479
|
@progress.start_syncing_binaries(server)
|
|
469
480
|
|
|
470
|
-
|
|
471
|
-
session = Indocker::SshSession.
|
|
472
|
-
host: server.host,
|
|
473
|
-
user: server.user,
|
|
474
|
-
port: server.port,
|
|
475
|
-
logger: @logger
|
|
476
|
-
)
|
|
481
|
+
Indocker::Launchers::DTO::RemoteOperationDTO.spawn(server, :indocker_sync) do
|
|
482
|
+
session = Indocker::SshSession.for(server, logger: @logger)
|
|
477
483
|
|
|
478
484
|
sync_path = Indocker::IndockerHelper.indocker_dir
|
|
479
485
|
@logger.info("Syncing indocker to #{server.user}@#{server.host}:#{sync_path}")
|
|
@@ -488,8 +494,6 @@ class Indocker::Launchers::ConfigurationDeployer
|
|
|
488
494
|
|
|
489
495
|
@progress.finish_syncing_binaries(server)
|
|
490
496
|
end
|
|
491
|
-
|
|
492
|
-
Indocker::Launchers::DTO::RemoteOperationDTO.new(thread, server, :indocker_sync)
|
|
493
497
|
end
|
|
494
498
|
end
|
|
495
499
|
|
|
@@ -500,17 +504,12 @@ class Indocker::Launchers::ConfigurationDeployer
|
|
|
500
504
|
remote_operations += env_files.map do |alias_name, env_file|
|
|
501
505
|
@progress.start_syncing_env_file(server, alias_name)
|
|
502
506
|
|
|
503
|
-
|
|
507
|
+
Indocker::Launchers::DTO::RemoteOperationDTO.spawn(server, :env_file_sync) do
|
|
504
508
|
if env_file.is_a?(Indocker::EnvFiles::Local)
|
|
505
509
|
sync_path = File.join(Indocker.deploy_dir, 'env_files', File.basename(env_file.path))
|
|
506
510
|
@logger.info("Syncing env file :#{alias_name} from #{env_file.path} to #{server.user}@#{server.host}:#{sync_path}")
|
|
507
511
|
|
|
508
|
-
session = Indocker::SshSession.
|
|
509
|
-
host: server.host,
|
|
510
|
-
user: server.user,
|
|
511
|
-
port: server.port,
|
|
512
|
-
logger: @logger
|
|
513
|
-
)
|
|
512
|
+
session = Indocker::SshSession.for(server, logger: @logger)
|
|
514
513
|
|
|
515
514
|
session.exec!("mkdir -p #{Indocker::EnvFileHelper.folder}")
|
|
516
515
|
|
|
@@ -529,8 +528,6 @@ class Indocker::Launchers::ConfigurationDeployer
|
|
|
529
528
|
|
|
530
529
|
@progress.finish_syncing_env_file(server, alias_name)
|
|
531
530
|
end
|
|
532
|
-
|
|
533
|
-
Indocker::Launchers::DTO::RemoteOperationDTO.new(thread, server, :env_file_sync)
|
|
534
531
|
end
|
|
535
532
|
end
|
|
536
533
|
|
|
@@ -1,10 +1,27 @@
|
|
|
1
1
|
class Indocker::Launchers::DTO::RemoteOperationDTO
|
|
2
2
|
attr_reader :thread, :server, :operation, :message
|
|
3
3
|
|
|
4
|
+
# Runs a remote operation in a thread that keeps its failure to itself, so that
|
|
5
|
+
# wait_remote_operations can re-raise it on join and report which server failed.
|
|
6
|
+
# Left to the global abort_on_exception/report_on_exception defaults, a failed ssh
|
|
7
|
+
# dumps a raw backtrace and tears the deploy down from inside the thread, naming
|
|
8
|
+
# no server at all. The flags are set inside the thread so that they are in place
|
|
9
|
+
# before the operation gets a chance to fail.
|
|
10
|
+
def self.spawn(server, operation, message = nil, &block)
|
|
11
|
+
thread = Thread.new do
|
|
12
|
+
Thread.current.abort_on_exception = false
|
|
13
|
+
Thread.current.report_on_exception = false
|
|
14
|
+
|
|
15
|
+
block.call
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
new(thread, server, operation, message)
|
|
19
|
+
end
|
|
20
|
+
|
|
4
21
|
def initialize(thread, server, operation, message = nil)
|
|
5
22
|
@thread = thread
|
|
6
23
|
@server = server
|
|
7
24
|
@operation = operation
|
|
8
25
|
@message = message
|
|
9
26
|
end
|
|
10
|
-
end
|
|
27
|
+
end
|
data/lib/indocker/rsync.rb
CHANGED
|
@@ -41,7 +41,13 @@ class Indocker::Rsync
|
|
|
41
41
|
"-o ConnectTimeout=#{Indocker.ssh_connect_timeout}",
|
|
42
42
|
"-o ServerAliveInterval=10",
|
|
43
43
|
"-o ServerAliveCountMax=3",
|
|
44
|
-
]
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
# Tunnel through the jump host the server is defined with, so that a server
|
|
47
|
+
# reachable only over its internal address syncs like any other one.
|
|
48
|
+
ssh_command << "-J #{session.jump_host}" if session.jump_host
|
|
49
|
+
|
|
50
|
+
ssh_command = ssh_command.join(' ')
|
|
45
51
|
command = "rsync --delete-after -a -e '#{ssh_command}' #{from} #{session.user}@#{session.host}:#{to}"
|
|
46
52
|
Indocker.logger.debug("sync #{from} #{session.user}@#{session.host}:#{to}")
|
|
47
53
|
Indocker::Shell.command(command, Indocker.logger, raise_on_error: raise_on_error)
|
data/lib/indocker/server.rb
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
class Indocker::Server
|
|
2
2
|
include Indocker::Concerns::Inspectable
|
|
3
3
|
|
|
4
|
-
attr_reader :name, :host, :user, :port
|
|
4
|
+
attr_reader :name, :host, :user, :port, :jump_host
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
# jump_host ("user@host") makes every connection to this server tunnel through
|
|
7
|
+
# another one. Servers whose public address is unreachable can still be deployed
|
|
8
|
+
# to over their internal address that way.
|
|
9
|
+
def initialize(name:, host:, user:, port:, jump_host: nil)
|
|
7
10
|
@name = name
|
|
8
11
|
@host = host
|
|
9
12
|
@user = user
|
|
10
13
|
@port = port
|
|
14
|
+
@jump_host = jump_host
|
|
11
15
|
end
|
|
12
16
|
|
|
13
17
|
def ==(value)
|
|
@@ -10,12 +10,7 @@ class Indocker::ServerPools::ServerConnection
|
|
|
10
10
|
def create_session!
|
|
11
11
|
return unless @server
|
|
12
12
|
|
|
13
|
-
@session = Indocker::SshSession.
|
|
14
|
-
host: @server.host,
|
|
15
|
-
user: @server.user,
|
|
16
|
-
port: @server.port,
|
|
17
|
-
logger: @logger
|
|
18
|
-
)
|
|
13
|
+
@session = Indocker::SshSession.for(@server, logger: @logger)
|
|
19
14
|
end
|
|
20
15
|
|
|
21
16
|
def exec!(command)
|
data/lib/indocker/ssh_session.rb
CHANGED
|
@@ -19,24 +19,47 @@ class Indocker::SshSession
|
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
attr_reader :host, :user, :port, :logger
|
|
22
|
+
attr_reader :host, :user, :port, :logger, :jump_host
|
|
23
|
+
|
|
24
|
+
# Opens a session to a server as it is defined in the configuration, including its
|
|
25
|
+
# jump host, if any. Every remote operation goes through here, so a server reached
|
|
26
|
+
# through a jump host is reached that way by binaries sync, container deploys and
|
|
27
|
+
# the deployment checker alike.
|
|
28
|
+
def self.for(server, logger:)
|
|
29
|
+
new(
|
|
30
|
+
host: server.host,
|
|
31
|
+
user: server.user,
|
|
32
|
+
port: server.port,
|
|
33
|
+
jump_host: server.jump_host,
|
|
34
|
+
logger: logger
|
|
35
|
+
)
|
|
36
|
+
end
|
|
23
37
|
|
|
24
|
-
def initialize(host:, user:, port:, logger:)
|
|
38
|
+
def initialize(host:, user:, port:, logger:, jump_host: nil)
|
|
25
39
|
@host = host
|
|
26
40
|
@user = user
|
|
27
41
|
@port = port
|
|
42
|
+
@jump_host = jump_host
|
|
28
43
|
@logger = logger
|
|
29
44
|
|
|
30
45
|
if host != LOCALHOST
|
|
31
46
|
require 'net/ssh'
|
|
32
|
-
|
|
47
|
+
|
|
48
|
+
options = {
|
|
33
49
|
port: @port,
|
|
34
50
|
non_interactive: true,
|
|
35
51
|
timeout: Indocker.ssh_connect_timeout,
|
|
36
52
|
keepalive: true,
|
|
37
53
|
keepalive_interval: 10,
|
|
38
54
|
keepalive_maxcount: 3,
|
|
39
|
-
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if @jump_host
|
|
58
|
+
require 'net/ssh/proxy/command'
|
|
59
|
+
options[:proxy] = Net::SSH::Proxy::Command.new(jump_proxy_command)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
@ssh = Net::SSH.start(@host, @user, options)
|
|
40
63
|
end
|
|
41
64
|
end
|
|
42
65
|
|
|
@@ -59,6 +82,32 @@ class Indocker::SshSession
|
|
|
59
82
|
end
|
|
60
83
|
|
|
61
84
|
private
|
|
85
|
+
# Net::SSH::Proxy::Jump builds this same `ssh -W` tunnel, but without BatchMode,
|
|
86
|
+
# connect timeout or -q: a jump host that asks for a password hangs the deploy, and
|
|
87
|
+
# every probe of a server whose sshd is still starting prints "channel 0: open failed"
|
|
88
|
+
# from the ssh client.
|
|
89
|
+
def jump_proxy_command
|
|
90
|
+
host = @jump_host
|
|
91
|
+
port = nil
|
|
92
|
+
|
|
93
|
+
if host =~ /\A(.+):(\d+)\z/
|
|
94
|
+
host = $1
|
|
95
|
+
port = $2
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
command = [
|
|
99
|
+
"ssh -q",
|
|
100
|
+
"-o BatchMode=yes",
|
|
101
|
+
"-o ConnectTimeout=#{Indocker.ssh_connect_timeout}",
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
command << "-p #{port}" if port
|
|
105
|
+
command << "-W %h:%p"
|
|
106
|
+
command << host
|
|
107
|
+
|
|
108
|
+
command.join(' ')
|
|
109
|
+
end
|
|
110
|
+
|
|
62
111
|
def exec_locally!(command)
|
|
63
112
|
res = Indocker::Shell.command_with_result(command, @logger, skip_logging: false)
|
|
64
113
|
ExecResult.new(res.stdout, '', res.exit_status, nil)
|
data/lib/indocker/version.rb
CHANGED