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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
|
#
# these methods are made available in capistrano tasks as 'leap.method_name'
# (see RemoteCommand::new_capistrano)
#
module LeapCli; module Remote; module LeapPlugin
def required_packages
"puppet rsync lsb-release locales"
end
def log(*args, &block)
LeapCli.logger.log(*args, &block)
end
#
# creates directories that are owned by root and 700 permissions
#
def mkdirs(*dirs)
raise ArgumentError.new('illegal dir name') if dirs.grep(/[\' ]/).any?
run dirs.collect{|dir| "mkdir -m 700 -p #{dir}; "}.join
end
#
# echos "ok" if the node has been initialized and the required packages are installed, bails out otherwise.
#
def assert_initialized
begin
test_initialized_file = "test -f #{Leap::Platform.init_path}"
check_required_packages = "! dpkg-query -W --showformat='${Status}\n' #{required_packages} 2>&1 | grep -q -E '(deinstall|no packages)'"
run "#{test_initialized_file} && #{check_required_packages} && echo ok"
rescue Capistrano::CommandError => exc
LeapCli::Util.bail! do
exc.hosts.each do |host|
node = host.to_s.split('.').first
LeapCli::Util.log :error, "running deploy: node not initialized. Run 'leap node init #{node}'", :host => host
end
end
end
end
#
# bails out the deploy if the file /etc/leap/no-deploy exists.
# This kind of sucks, because it would be better to skip over nodes that have no-deploy set instead
# halting the entire deploy. As far as I know, with capistrano, there is no way to close one of the
# ssh connections in the pool and make sure it gets no further commands.
#
def check_for_no_deploy
begin
run "test ! -f /etc/leap/no-deploy"
rescue Capistrano::CommandError => exc
LeapCli::Util.bail! do
exc.hosts.each do |host|
LeapCli::Util.log "Can't continue because file /etc/leap/no-deploy exists", :host => host
end
end
end
end
#
# dumps debugging information
# #
def debug
run "#{Leap::Platform.leap_dir}/bin/debug.sh"
end
#
# dumps the recent deploy history to the console
#
def history(lines)
command = "(test -s /var/log/leap/deploy-summary.log && tail -n #{lines} /var/log/leap/deploy-summary.log) || (test -s /var/log/leap/deploy-summary.log.1 && tail -n #{lines} /var/log/leap/deploy-summary.log.1) || (echo 'no history')"
run command
end
#
# This is a hairy ugly hack, exactly the kind of stuff that makes ruby
# dangerous and too much fun for its own good.
#
# In most places, we run remote ssh without a current 'task'. This works fine,
# except that in a few places, the behavior of capistrano ssh is controlled by
# the options of the current task.
#
# We don't want to create an actual current task, because tasks are no fun
# and can't take arguments or return values. So, when we need to configure
# things that can only be configured in a task, we use this handy hack to
# fake the current task.
#
# This is NOT thread safe, but could be made to be so with some extra work.
#
def with_task(name)
task = @config.tasks[name]
@config.class.send(:alias_method, :original_current_task, :current_task)
@config.class.send(:define_method, :current_task, Proc.new(){ task })
begin
yield
ensure
@config.class.send(:remove_method, :current_task)
@config.class.send(:alias_method, :current_task, :original_current_task)
end
end
#
# similar to run(cmd, &block), but with:
#
# * exit codes
# * stdout and stderr are combined
#
def stream(cmd, &block)
command = '%s 2>&1; echo "exitcode=$?"' % cmd
run(command) do |channel, stream, data|
exitcode = nil
if data =~ /exitcode=(\d+)\n/
exitcode = $1.to_i
data.sub!(/exitcode=(\d+)\n/,'')
end
yield({:host => channel[:host], :data => data, :exitcode => exitcode})
end
end
#
# like stream, but capture all the output before returning
#
def capture(cmd, &block)
command = '%s 2>&1; echo "exitcode=$?" 2>&1;' % cmd
host_data = {}
run(command) do |channel, stream, data|
host_data[channel[:host]] ||= ""
if data =~ /exitcode=(\d+)\n/
exitcode = $1.to_i
data.sub!(/exitcode=(\d+)\n/,'')
host_data[channel[:host]] += data
yield({:host => channel[:host], :data => host_data[channel[:host]], :exitcode => exitcode})
else
host_data[channel[:host]] += data
end
end
end
#
# Run a command, with a nice status report and progress indicator.
# Only successful results are returned, errors are printed.
#
# For each successful run on each host, block is yielded with a hash like so:
#
# {:host => 'bluejay', :exitcode => 0, :data => 'shell output'}
#
def run_with_progress(cmd, &block)
ssh_failures = []
exitcode_failures = []
succeeded = []
task = LeapCli.logger.log_level > 1 ? :standard_task : :skip_errors_task
with_task(task) do
log :querying, 'facts' do
progress " "
call_on_failure do |host|
ssh_failures << host
progress 'F'
end
capture(cmd) do |response|
if response[:exitcode] == 0
progress '.'
yield response
else
exitcode_failures << response
progress 'F'
end
end
end
end
puts "done"
if ssh_failures.any?
log :failed, 'to connect to nodes: ' + ssh_failures.join(' ')
end
if exitcode_failures.any?
log :failed, 'to run successfully:' do
exitcode_failures.each do |response|
log "[%s] exit %s - %s" % [response[:host], response[:exitcode], response[:data].strip]
end
end
end
rescue Capistrano::RemoteError => err
log :error, err.to_s
end
private
def progress(str='.')
print str
STDOUT.flush
end
end; end; end
|