1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
#
# Provides SSH.remote_command for running commands in parallel or in sequence
# on remote servers.
#
# The gem sshkit is used for this.
#
require 'sshkit'
require 'leap_cli/ssh/options'
require 'leap_cli/ssh/backend'
SSHKit.config.backend = LeapCli::SSH::Backend
LeapCli::SSH::Backend.config.ssh_options = LeapCli::SSH::Options.global_options
#
# define remote_command
#
module LeapCli
module SSH
class ExecuteError < StandardError
end
class TimeoutError < ExecuteError
end
# override default runner mode
class CustomCoordinator < SSHKit::Coordinator
private
def default_options
{ in: :groups, limit: 10, wait: 0 }
end
end
#
# Available options:
#
# :port -- ssh port
# :ip -- ssh ip
# :auth_methods -- e.g. ["pubkey", "password"]
# :user -- default 'root'
#
def self.remote_command(nodes, options={}, &block)
CustomCoordinator.new(
host_list(
nodes,
SSH::Options.options_from_args(options)
)
).each do |ssh, host|
LeapCli.log 2, "ssh options for #{host.hostname}: #{host.ssh_options.inspect}"
if host.user != 'root'
# if the ssh user is not root, we want to make the ssh commands
# switch to root before they are run:
ssh.set_user('root')
end
yield ssh, host
end
end
#
# For example:
#
# SSH.remote_sync(nodes) do |sync, host|
# sync.source = '/from'
# sync.dest = '/to'
# sync.flags = ''
# sync.includes = []
# sync.excludes = []
# sync.exec
# end
#
def self.remote_sync(nodes, options={}, &block)
require 'rsync_command'
hosts = host_list(
nodes,
SSH::Options.options_from_args(options)
)
rsync = RsyncCommand.new(:logger => LeapCli::logger)
rsync.asynchronously(hosts) do |sync, host|
sync.logger = LeapCli.new_logger
sync.user = host.user || fetch(:user, ENV['USER'])
sync.host = host.hostname
sync.ssh = SSH::Options.global_options.merge(host.ssh_options)
sync.chdir = Path.provider
yield(sync, host)
end
if rsync.failed?
LeapCli::Util.bail! do
LeapCli.log :failed, "to rsync to #{rsync.failures.map{|f|f[:dest][:host]}.join(' ')}"
end
end
end
private
def self.host_list(nodes, ssh_options_override={})
if nodes.is_a?(Config::ObjectList)
list = nodes.values
elsif nodes.is_a?(Config::Node)
list = [nodes]
else
raise ArgumentError, "I don't understand the type of argument `nodes`"
end
list.collect do |node|
options = SSH::Options.node_options(node, ssh_options_override)
user = options.delete(:user) || 'root'
#
# note: whatever hostname is specified here will be what is used
# when loading options from .ssh/config. However, this value
# has no impact on the actual ip address that is connected to,
# which is determined by the :host_name value in ssh_options.
#
SSHKit::Host.new(
:hostname => node.domain.full,
:user => user,
:ssh_options => options
)
end
end
end
end
|