blower 2.2.0 → 3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/blower/context.rb +109 -32
- data/lib/blower/host.rb +66 -55
- data/lib/blower/logger.rb +28 -11
- data/lib/blower/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 326c39aae83c23c79f60751abeabe1a8eea19870
|
4
|
+
data.tar.gz: e3fa37856f16d53884cc4f79ff7bc762dacc0633
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1705364adfc1bcfa70d7b12cdeccae35d6ba391b2f08192ab1e1380f29bd2495e4945634268237486f8ac250c843752876de2c7574b30ba34634fe4e3b22e3fd
|
7
|
+
data.tar.gz: d091fe3d25f24af89309b01cb716e97961d9165528c9abbe5f3441dc2b6343096eb25ecc9babb5562ad0b28633c5e99d34eecb7ef6c29f2eec49459399d7e2e7
|
data/lib/blower/context.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'erb'
|
1
2
|
require 'forwardable'
|
2
3
|
|
3
4
|
module Blower
|
@@ -12,6 +13,9 @@ module Blower
|
|
12
13
|
# Search path for tasks.
|
13
14
|
attr_accessor :path
|
14
15
|
|
16
|
+
# The currently running task.
|
17
|
+
attr_accessor :task
|
18
|
+
|
15
19
|
# The target hosts.
|
16
20
|
attr_accessor :hosts
|
17
21
|
|
@@ -19,6 +23,28 @@ module Blower
|
|
19
23
|
@path = path
|
20
24
|
@hosts = []
|
21
25
|
@have_seen = {}
|
26
|
+
@data = {}
|
27
|
+
end
|
28
|
+
|
29
|
+
def set (hash, &block)
|
30
|
+
if block
|
31
|
+
begin
|
32
|
+
old_data, @data = @data, @data.dup
|
33
|
+
set(hash)
|
34
|
+
block.()
|
35
|
+
ensure
|
36
|
+
@data = old_data
|
37
|
+
end
|
38
|
+
else
|
39
|
+
@data.merge! hash
|
40
|
+
hash.each do |var, val|
|
41
|
+
define_singleton_method(var) { @data[var] }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def get (var)
|
47
|
+
@data[var]
|
22
48
|
end
|
23
49
|
|
24
50
|
def log
|
@@ -34,50 +60,70 @@ module Blower
|
|
34
60
|
# Execute the block once for each host and return nil.
|
35
61
|
def each (hosts = @hosts, &block)
|
36
62
|
Kernel.fail "No hosts left" if hosts.empty?
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
63
|
+
hosts.each(&block)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Execute the block once for each host and return nil.
|
67
|
+
def with (hosts = @hosts, &block)
|
68
|
+
ctx = clone
|
69
|
+
ctx.hosts = hosts.map do |spec|
|
70
|
+
spec.is_a?(Host) ? spec : Host.new(*spec)
|
71
|
+
end
|
72
|
+
ctx.instance_exec(&block)
|
73
|
+
end
|
74
|
+
|
75
|
+
def with_each (hosts = @hosts, &block)
|
76
|
+
each do |host|
|
77
|
+
with host, &block
|
78
|
+
end
|
42
79
|
end
|
43
80
|
|
44
81
|
# Execute the block once for each host and return a hash of the results.
|
45
82
|
def hashmap (hosts = @hosts, &block)
|
46
83
|
result = {}
|
47
84
|
threads = []
|
48
|
-
|
49
|
-
|
50
|
-
|
85
|
+
log.nest do
|
86
|
+
each hosts do |host|
|
87
|
+
threads << Thread.new do
|
88
|
+
result[host] = block.(host)
|
89
|
+
end
|
51
90
|
end
|
91
|
+
threads.each(&:join)
|
52
92
|
end
|
53
|
-
threads.each(&:join)
|
54
93
|
result
|
55
94
|
end
|
56
95
|
|
57
96
|
# Reboot each host and waits for them to come back up.
|
58
97
|
# @param command The reboot command. A string.
|
59
98
|
def reboot (command = "reboot")
|
60
|
-
|
99
|
+
if f = get(:sh_command)
|
100
|
+
command = f.(command)
|
101
|
+
end
|
102
|
+
each do |host|
|
103
|
+
log.info "rebooting: #{host.name}"
|
61
104
|
begin
|
62
|
-
sh command
|
105
|
+
host.sh command, user: @user
|
63
106
|
rescue IOError
|
64
107
|
end
|
65
|
-
log.debug "
|
66
|
-
sleep 0.1 while ping
|
108
|
+
log.debug "rebooting: #{host.name}: waiting for shutdown..."
|
109
|
+
sleep 0.1 while host.ping
|
67
110
|
end
|
68
|
-
each do
|
69
|
-
log.debug "
|
70
|
-
sleep 1.0 until ping
|
111
|
+
each do |host|
|
112
|
+
log.debug "rebooting: #{host.name}: waiting for return..."
|
113
|
+
sleep 1.0 until host.ping
|
71
114
|
end
|
72
115
|
end
|
73
116
|
|
74
117
|
# Execute a shell command on each host.
|
75
|
-
def sh (command, quiet
|
118
|
+
def sh (command, quiet: false)
|
76
119
|
log.info "sh: #{command}" unless quiet
|
120
|
+
if f = get(:sh_command)
|
121
|
+
command = f.(command)
|
122
|
+
end
|
77
123
|
win = true
|
78
124
|
hashmap do |host|
|
79
125
|
out = ""
|
80
|
-
status = host.sh(command, out)
|
126
|
+
status = host.sh(command, out, user: @user)
|
81
127
|
if status != 0
|
82
128
|
fail host, "#{command}: exit status #{status}"
|
83
129
|
nil
|
@@ -89,20 +135,20 @@ module Blower
|
|
89
135
|
|
90
136
|
# Execute a command on the remote host.
|
91
137
|
# @return false if the command exits with a non-zero status
|
92
|
-
def sh? (command, quiet
|
138
|
+
def sh? (command, quiet: false)
|
93
139
|
log.info "sh?: #{command}" unless quiet
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
140
|
+
if f = get(:sh_command)
|
141
|
+
command = f.(command)
|
142
|
+
end
|
143
|
+
hashmap do |host|
|
144
|
+
host.sh(command, user: @user) == 0
|
98
145
|
end
|
99
|
-
win
|
100
146
|
end
|
101
147
|
|
102
148
|
# Execute a command on the remote host.
|
103
149
|
# @return false if the command exits with a non-zero status
|
104
|
-
def ping (quiet
|
105
|
-
log.
|
150
|
+
def ping (quiet: false)
|
151
|
+
log.debug "ping" unless quiet
|
106
152
|
win = true
|
107
153
|
each do |host|
|
108
154
|
win &&= host.ping
|
@@ -114,22 +160,48 @@ module Blower
|
|
114
160
|
@hosts -= [host]
|
115
161
|
end
|
116
162
|
|
163
|
+
def rebase_path (path)
|
164
|
+
path.gsub(/^(?!\/)/, File.dirname(@file))
|
165
|
+
end
|
166
|
+
|
117
167
|
# Copy a file or readable to the host filesystem.
|
118
168
|
# @param from An object that responds to read, or a string which names a file, or an array of either.
|
119
169
|
# @param to A string.
|
120
170
|
def cp (from, to)
|
121
171
|
log.info "cp: #{from} -> #{to}"
|
172
|
+
from = rebase_path(from)
|
122
173
|
each do |host|
|
123
|
-
host.cp(from, to)
|
174
|
+
host.cp(from, to, rsync_command: get(:rsync_command))
|
124
175
|
end
|
125
176
|
end
|
126
177
|
|
178
|
+
def as (user)
|
179
|
+
old_user, @user = @user, user
|
180
|
+
yield
|
181
|
+
ensure
|
182
|
+
@user = old_user
|
183
|
+
end
|
184
|
+
|
127
185
|
# Writes a string to a file on the host filesystem.
|
128
186
|
# @param string The string to write.
|
129
187
|
# @param to A string.
|
130
188
|
def write (string, to)
|
131
|
-
log "
|
132
|
-
|
189
|
+
log.info "write: -> #{to}"
|
190
|
+
each do |host|
|
191
|
+
host.cp(StringIO.new(string), to, user: @user, sh_command: get(:sh_command))
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def render (from, to)
|
196
|
+
from = rebase_path(from)
|
197
|
+
(Dir["#{from}**/*.erb"] + Dir["#{from}**/.*.erb"]).each do |path|
|
198
|
+
template = ERB.new(File.read(path))
|
199
|
+
to_path = to + path[from.length..-5]
|
200
|
+
log.info "render: #{path} -> #{to_path}"
|
201
|
+
each do |host|
|
202
|
+
host.cp StringIO.new(template.result(binding)), to_path, user: @user, sh_command: get(:sh_command)
|
203
|
+
end
|
204
|
+
end
|
133
205
|
end
|
134
206
|
|
135
207
|
# Run a task.
|
@@ -153,15 +225,20 @@ module Blower
|
|
153
225
|
if optional
|
154
226
|
return
|
155
227
|
else
|
156
|
-
|
228
|
+
raise "can't find #{task}"
|
157
229
|
end
|
158
230
|
else
|
159
|
-
log.
|
231
|
+
log.debug "Running #{task}" do
|
160
232
|
begin
|
161
233
|
old_task, @task = @task, task
|
162
234
|
files.each do |file|
|
163
235
|
@have_seen[file] = true
|
164
|
-
|
236
|
+
begin
|
237
|
+
old_file, @file = @file, file
|
238
|
+
instance_eval(File.read(file), file)
|
239
|
+
ensure
|
240
|
+
@file = old_file
|
241
|
+
end
|
165
242
|
end
|
166
243
|
ensure
|
167
244
|
@task = old_task
|
data/lib/blower/host.rb
CHANGED
@@ -12,6 +12,7 @@ module Blower
|
|
12
12
|
|
13
13
|
attr_accessor :name
|
14
14
|
attr_accessor :user
|
15
|
+
attr_accessor :address
|
15
16
|
|
16
17
|
def_delegators :data, :[], :[]=
|
17
18
|
|
@@ -22,90 +23,99 @@ module Blower
|
|
22
23
|
end
|
23
24
|
end
|
24
25
|
|
25
|
-
def initialize (name, user
|
26
|
-
@name = name
|
26
|
+
def initialize (name, data: {}, user: "root")
|
27
|
+
@address = @name = name
|
27
28
|
@user = user
|
28
|
-
@data =
|
29
|
+
@data = data
|
29
30
|
super()
|
30
31
|
end
|
31
32
|
|
32
33
|
def ping
|
33
34
|
Timeout.timeout(1) do
|
34
|
-
TCPSocket.new(
|
35
|
+
TCPSocket.new(address, 22).close
|
35
36
|
end
|
36
37
|
true
|
37
|
-
rescue Timeout::
|
38
|
+
rescue Timeout::Error
|
38
39
|
false
|
39
40
|
rescue Errno::ECONNREFUSED
|
40
41
|
false
|
41
42
|
end
|
42
43
|
|
43
|
-
def cp (
|
44
|
+
def cp (froms, to, output = "", user: nil, rsync_command: nil, sh_command: nil)
|
44
45
|
synchronize do
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
46
|
+
[froms].flatten.each do |from|
|
47
|
+
if from.is_a?(String)
|
48
|
+
to += "/" if to[-1] != "/" && from.is_a?(Array)
|
49
|
+
command = ["rsync", "-e", "ssh -oStrictHostKeyChecking=no", "-r"]
|
50
|
+
command += ["--rsync-path=#{rsync_command.()}"] if rsync_command
|
51
|
+
command += [*from, "#{user || @user}@#{@address}:#{to}"]
|
52
|
+
log.trace command.shelljoin
|
53
|
+
IO.popen(command, in: :close, err: %i(child out)) do |io|
|
54
|
+
until io.eof?
|
55
|
+
begin
|
56
|
+
output << io.read_nonblock(100)
|
57
|
+
rescue IO::WaitReadable
|
58
|
+
IO.select([io])
|
59
|
+
retry
|
60
|
+
end
|
61
|
+
end
|
62
|
+
io.close
|
63
|
+
if !$?.success?
|
64
|
+
log.fatal "exit status #{$?.exitstatus}: #{command}"
|
65
|
+
log.fatal output
|
66
|
+
fail "failed to copy files"
|
57
67
|
end
|
58
68
|
end
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
69
|
+
elsif from.respond_to?(:read)
|
70
|
+
cmd = "echo #{from.read.shellescape} > #{to.shellescape}"
|
71
|
+
cmd = sh_command.(cmd) if sh_command
|
72
|
+
sh cmd
|
73
|
+
else
|
74
|
+
fail "Don't know how to copy a #{from.class}: #{from}"
|
64
75
|
end
|
65
|
-
elsif from.respond_to?(:read)
|
66
|
-
ssh.scp.upload!(from, to)
|
67
|
-
else
|
68
|
-
fail "Don't know how to copy a #{from.class}: #{from}"
|
69
76
|
end
|
70
77
|
end
|
71
78
|
true
|
72
79
|
end
|
73
80
|
|
74
|
-
def sh (command, output = "")
|
81
|
+
def sh (command, output = "", user: nil)
|
75
82
|
synchronize do
|
76
83
|
log.debug command
|
77
84
|
result = nil
|
78
|
-
ch = ssh.open_channel do |ch|
|
79
|
-
ch.
|
80
|
-
|
81
|
-
ch.
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
85
|
+
ch = ssh(user || @user).open_channel do |ch|
|
86
|
+
ch.request_pty do |ch, success|
|
87
|
+
"failed to acquire pty" unless success unless success
|
88
|
+
ch.exec(command) do |_, success|
|
89
|
+
fail "failed to execute command" unless success
|
90
|
+
ch.on_data do |_, data|
|
91
|
+
log.trace "received #{data.bytesize} bytes stdout"
|
92
|
+
output << data
|
93
|
+
end
|
94
|
+
ch.on_extended_data do |_, _, data|
|
95
|
+
log.trace "received #{data.bytesize} bytes stderr"
|
96
|
+
output << data.colorize(:red)
|
97
|
+
end
|
98
|
+
ch.on_request("exit-status") do |_, data|
|
99
|
+
result = data.read_long
|
100
|
+
log.trace "received exit-status #{result}"
|
101
|
+
end
|
92
102
|
end
|
93
103
|
end
|
94
104
|
end
|
95
105
|
ch.wait
|
96
106
|
if result != 0
|
97
107
|
log.fatal "exit status #{result}: #{command}"
|
98
|
-
log.
|
108
|
+
log.fatal output
|
99
109
|
end
|
100
110
|
result
|
101
111
|
end
|
102
112
|
end
|
103
113
|
|
104
|
-
# Execute the block with self as a parameter.
|
105
|
-
# Exists to conform with the HostGroup interface.
|
106
|
-
def each (&block)
|
107
|
-
|
108
|
-
end
|
114
|
+
# # Execute the block with self as a parameter.
|
115
|
+
# # Exists to conform with the HostGroup interface.
|
116
|
+
# def each (&block)
|
117
|
+
# block.(self)
|
118
|
+
# end
|
109
119
|
|
110
120
|
private
|
111
121
|
|
@@ -115,14 +125,15 @@ module Blower
|
|
115
125
|
Logger.instance.with_prefix("(on #{name})")
|
116
126
|
end
|
117
127
|
|
118
|
-
def ssh
|
119
|
-
|
120
|
-
|
121
|
-
|
128
|
+
def ssh (user)
|
129
|
+
@sessions ||= {}
|
130
|
+
if @sessions[user] && @sessions[user].closed?
|
131
|
+
log.trace "Discovered the connection to ssh:#{user}@#{name} was lost"
|
132
|
+
@sessions[user] = nil
|
122
133
|
end
|
123
|
-
@
|
124
|
-
log.trace "Connecting to ssh:#{
|
125
|
-
Net::SSH.start(
|
134
|
+
@sessions[user] ||= begin
|
135
|
+
log.trace "Connecting to ssh:#{user}@#{name}"
|
136
|
+
Net::SSH.start(address, user)
|
126
137
|
end
|
127
138
|
end
|
128
139
|
|
data/lib/blower/logger.rb
CHANGED
@@ -32,8 +32,9 @@ module Blower
|
|
32
32
|
off: 0,
|
33
33
|
}
|
34
34
|
|
35
|
+
@@indent = 0
|
36
|
+
|
35
37
|
def initialize (prefix = nil)
|
36
|
-
@indent = 0
|
37
38
|
@prefix = prefix
|
38
39
|
super()
|
39
40
|
end
|
@@ -60,9 +61,23 @@ module Blower
|
|
60
61
|
# Log a fatal level event
|
61
62
|
def fatal (a=nil, *b, &c); log(a, :fatal, *b, &c); end
|
62
63
|
|
63
|
-
|
64
|
-
|
65
|
-
|
64
|
+
def nest (&c)
|
65
|
+
log(nil, nil, &c)
|
66
|
+
end
|
67
|
+
|
68
|
+
def task (message, &block)
|
69
|
+
STDOUT.write " " * @@indent + (@prefix ? @prefix + " " : "") + message + "... "
|
70
|
+
begin
|
71
|
+
@@indent += 1
|
72
|
+
block.() if block
|
73
|
+
ensure
|
74
|
+
@@indent -= 1
|
75
|
+
end
|
76
|
+
puts "OK".colorize({color: :green})
|
77
|
+
rescue => e
|
78
|
+
puts "ERROR".colorize({color: :red})
|
79
|
+
raise e
|
80
|
+
end
|
66
81
|
|
67
82
|
private
|
68
83
|
|
@@ -70,15 +85,17 @@ module Blower
|
|
70
85
|
if message && (level.nil? || RANKS[level] <= RANKS[$LOGLEVEL])
|
71
86
|
Logger.instance.synchronize do
|
72
87
|
message = message.colorize(COLORS[level]) if level
|
73
|
-
puts " " *
|
88
|
+
puts " " * @@indent + (@prefix ? @prefix + " " : "") + message
|
89
|
+
end
|
90
|
+
begin
|
91
|
+
@@indent += 1
|
92
|
+
block.() if block
|
93
|
+
ensure
|
94
|
+
@@indent -= 1
|
74
95
|
end
|
96
|
+
else
|
97
|
+
block.() if block
|
75
98
|
end
|
76
|
-
begin
|
77
|
-
@indent += 1
|
78
|
-
block.()
|
79
|
-
ensure
|
80
|
-
@indent -= 1
|
81
|
-
end if block
|
82
99
|
end
|
83
100
|
|
84
101
|
end
|
data/lib/blower/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: blower
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: '3.1'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nathan Baum
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-04-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: net-ssh
|