blower 4.8 → 5.0a1
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/bin/blow +1 -0
- data/lib/blower.rb +2 -0
- data/lib/blower/context.rb +77 -24
- data/lib/blower/host.rb +1 -2
- data/lib/blower/local.rb +41 -0
- data/lib/blower/logger.rb +10 -5
- data/lib/blower/target.rb +106 -0
- data/lib/blower/version.rb +1 -1
- metadata +7 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7e86119a63baca69801f4556a37de41ace91bcb5a877d3c82e6fe15cb569f512
|
|
4
|
+
data.tar.gz: ef8affaeb03b9eaf5cba800f18bf43a80bcb2a86d59de734744926eb9441e7c8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 27bba3e972d839c4f1b0a0469e0abff4a6ad007cfc6d87e03596604e2a4025db007ccd2b3e679901440134ad598d09312af448b1f880a7ae50e282ce498b45ee
|
|
7
|
+
data.tar.gz: b3e698f5285034a22b44e66dd1bfff3b987dbae6af6bc5f840a43fe1bef1cafc3cc1644b19fd69a8c1b211ec979f308e6fa1135905830ed796970d9cd3ba5909
|
data/bin/blow
CHANGED
data/lib/blower.rb
CHANGED
data/lib/blower/context.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require 'colorize'
|
|
1
2
|
require 'find'
|
|
2
3
|
require 'erb'
|
|
3
4
|
require 'json'
|
|
@@ -32,6 +33,9 @@ module Blower
|
|
|
32
33
|
# The target hosts.
|
|
33
34
|
attr_accessor :hosts
|
|
34
35
|
|
|
36
|
+
# The failed hosts.
|
|
37
|
+
attr_accessor :failures
|
|
38
|
+
|
|
35
39
|
# Username override. If not-nil, this user is used for all remote accesses.
|
|
36
40
|
attr_accessor :user
|
|
37
41
|
|
|
@@ -45,6 +49,7 @@ module Blower
|
|
|
45
49
|
@path = path
|
|
46
50
|
@data = {}
|
|
47
51
|
@hosts = []
|
|
52
|
+
@failures = []
|
|
48
53
|
end
|
|
49
54
|
|
|
50
55
|
# Return a context variable.
|
|
@@ -115,12 +120,20 @@ module Blower
|
|
|
115
120
|
# @raise Whatever the task itself raises.
|
|
116
121
|
# @return The result of evaluating the task file.
|
|
117
122
|
def run (task, optional: false, quiet: false, once: nil)
|
|
123
|
+
@run_cache ||= {}
|
|
118
124
|
once once, quiet: quiet do
|
|
119
125
|
log.info "run #{task}", quiet: quiet do
|
|
120
126
|
file = find_task(task)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
127
|
+
if @run_cache.has_key? file
|
|
128
|
+
log.info "*cached*"
|
|
129
|
+
@run_cache[file]
|
|
130
|
+
else
|
|
131
|
+
@run_cache[file] = begin
|
|
132
|
+
code = File.read(file)
|
|
133
|
+
let :@file => file do
|
|
134
|
+
instance_eval(code, file)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
124
137
|
end
|
|
125
138
|
end
|
|
126
139
|
end
|
|
@@ -136,7 +149,7 @@ module Blower
|
|
|
136
149
|
# @macro onceable
|
|
137
150
|
def sh (command, as: user, on: hosts, quiet: false, once: nil)
|
|
138
151
|
self.once once, quiet: quiet do
|
|
139
|
-
log.info "sh #{command}", quiet: quiet do
|
|
152
|
+
log.info "sh: #{command}", quiet: quiet do
|
|
140
153
|
hash_map(hosts) do |host|
|
|
141
154
|
host.sh command, as: as, quiet: quiet
|
|
142
155
|
end
|
|
@@ -282,7 +295,7 @@ module Blower
|
|
|
282
295
|
|
|
283
296
|
def to_s
|
|
284
297
|
map do |host, data|
|
|
285
|
-
"#{host.name.blue}
|
|
298
|
+
"#{host.name.blue}\n" + data.strip.to_s.gsub(/^/, " ")
|
|
286
299
|
end.join("\n")
|
|
287
300
|
end
|
|
288
301
|
|
|
@@ -302,40 +315,80 @@ module Blower
|
|
|
302
315
|
end
|
|
303
316
|
|
|
304
317
|
def hash_map (hosts = self.hosts)
|
|
305
|
-
HostHash.new.tap do |result|
|
|
306
|
-
each(hosts) do |host|
|
|
307
|
-
result[host] = yield(host)
|
|
318
|
+
hh = HostHash.new.tap do |result|
|
|
319
|
+
each(hosts) do |host, i|
|
|
320
|
+
result[host] = yield(host, i)
|
|
308
321
|
end
|
|
309
322
|
end
|
|
323
|
+
if @singular
|
|
324
|
+
hh.values.first
|
|
325
|
+
else
|
|
326
|
+
hh
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def singularly (flag = true)
|
|
331
|
+
was, @singular = @singular, flag
|
|
332
|
+
yield
|
|
333
|
+
ensure
|
|
334
|
+
@singular = was
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def on_one (host = self.hosts.sample, serial: true)
|
|
338
|
+
ret = nil
|
|
339
|
+
each([host], serial: serial) do |host, i|
|
|
340
|
+
on host do
|
|
341
|
+
singularly do
|
|
342
|
+
ret = yield host, i
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
ret
|
|
310
347
|
end
|
|
311
348
|
|
|
312
349
|
def on_each (hosts = self.hosts, serial: true)
|
|
313
|
-
each(hosts, serial: serial) do |host|
|
|
350
|
+
each(hosts, serial: serial) do |host, i|
|
|
314
351
|
on host do
|
|
315
|
-
|
|
352
|
+
singularly do
|
|
353
|
+
yield host, i
|
|
354
|
+
end
|
|
316
355
|
end
|
|
317
356
|
end
|
|
318
357
|
end
|
|
319
358
|
|
|
359
|
+
def locally (&block)
|
|
360
|
+
on Local.new("<local>") do
|
|
361
|
+
singularly &block
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
|
|
320
365
|
def each (hosts = self.hosts, serial: false)
|
|
321
366
|
fail "No hosts" if hosts.empty?
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
367
|
+
q = (@threads || serial) && Queue.new
|
|
368
|
+
if q && serial
|
|
369
|
+
q.push nil
|
|
370
|
+
elsif q
|
|
371
|
+
@threads.times { q.push nil }
|
|
372
|
+
end
|
|
373
|
+
indent = Thread.current[:indent]
|
|
374
|
+
i = -1
|
|
375
|
+
threads = [hosts].flatten.map.with_index do |host|
|
|
376
|
+
Thread.new do
|
|
377
|
+
Thread.current[:indent] = indent
|
|
378
|
+
begin
|
|
379
|
+
q.pop if q
|
|
380
|
+
yield host, i += 1
|
|
381
|
+
rescue => e
|
|
382
|
+
host.log.error e.message
|
|
383
|
+
hosts.delete host
|
|
384
|
+
@failures |= [host]
|
|
385
|
+
ensure
|
|
386
|
+
q.push nil if q
|
|
387
|
+
sleep @delay if @delay
|
|
335
388
|
end
|
|
336
389
|
end
|
|
337
|
-
threads.each(&:join)
|
|
338
390
|
end
|
|
391
|
+
threads.each(&:join)
|
|
339
392
|
fail "No hosts remaining" if hosts.empty?
|
|
340
393
|
end
|
|
341
394
|
|
data/lib/blower/host.rb
CHANGED
|
@@ -2,7 +2,6 @@ require 'net/ssh'
|
|
|
2
2
|
require 'net/ssh/gateway'
|
|
3
3
|
require 'net/scp'
|
|
4
4
|
require 'monitor'
|
|
5
|
-
require 'colorize'
|
|
6
5
|
require 'base64'
|
|
7
6
|
require 'timeout'
|
|
8
7
|
|
|
@@ -127,7 +126,7 @@ module Blower
|
|
|
127
126
|
end
|
|
128
127
|
ch.on_extended_data do |_, _, data|
|
|
129
128
|
log.trace "received #{data.bytesize} bytes stderr", quiet: quiet
|
|
130
|
-
output << data
|
|
129
|
+
output << data
|
|
131
130
|
end
|
|
132
131
|
ch.on_request("exit-status") do |_, data|
|
|
133
132
|
result = data.read_long
|
data/lib/blower/local.rb
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require 'net/ssh'
|
|
2
|
+
require 'net/ssh/gateway'
|
|
3
|
+
require 'net/scp'
|
|
4
|
+
require 'monitor'
|
|
5
|
+
require 'base64'
|
|
6
|
+
require 'timeout'
|
|
7
|
+
|
|
8
|
+
module Blower
|
|
9
|
+
|
|
10
|
+
class Local
|
|
11
|
+
include MonitorMixin
|
|
12
|
+
extend Forwardable
|
|
13
|
+
|
|
14
|
+
attr_reader :name, :data
|
|
15
|
+
|
|
16
|
+
def_delegators :data, :[], :[]=
|
|
17
|
+
|
|
18
|
+
def initialize (name, proxy: nil)
|
|
19
|
+
@name, @proxy = name, proxy
|
|
20
|
+
@data = {}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Represent the host as a string.
|
|
24
|
+
def to_s
|
|
25
|
+
@name
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def sh (command, as: nil, quiet: false)
|
|
29
|
+
command = "#{@proxy} #{command.shellescape}" if @proxy
|
|
30
|
+
IO.popen(command).read
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Produce a Logger prefixed with the host name.
|
|
34
|
+
# @api private
|
|
35
|
+
def log
|
|
36
|
+
@log ||= Logger.instance.with_prefix("on #{name}: ")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
end
|
data/lib/blower/logger.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require 'colorize'
|
|
1
2
|
require "singleton"
|
|
2
3
|
|
|
3
4
|
module Blower
|
|
@@ -33,12 +34,11 @@ module Blower
|
|
|
33
34
|
attr_accessor :indent
|
|
34
35
|
end
|
|
35
36
|
|
|
36
|
-
self.indent = 0
|
|
37
|
-
|
|
38
37
|
self.level = :info
|
|
39
38
|
|
|
40
39
|
def initialize (prefix = "")
|
|
41
40
|
@prefix = prefix
|
|
41
|
+
thread[:indent] = 0
|
|
42
42
|
super()
|
|
43
43
|
end
|
|
44
44
|
|
|
@@ -49,10 +49,10 @@ module Blower
|
|
|
49
49
|
|
|
50
50
|
# Yield with a temporarily incremented indent counter
|
|
51
51
|
def with_indent ()
|
|
52
|
-
|
|
52
|
+
thread[:indent] += 1
|
|
53
53
|
yield
|
|
54
54
|
ensure
|
|
55
|
-
|
|
55
|
+
thread[:indent] -= 1
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
# Display a log message. The block, if specified, is executed in an indented region after the log message is shown.
|
|
@@ -64,9 +64,10 @@ module Blower
|
|
|
64
64
|
def log (level, message, quiet: false, &block)
|
|
65
65
|
if !quiet && (LEVELS.index(level) >= LEVELS.index(Logger.level))
|
|
66
66
|
synchronize do
|
|
67
|
+
message = message.to_s.colorize(COLORS[level]) if level
|
|
67
68
|
message = message.to_s.colorize(COLORS[level]) if level
|
|
68
69
|
message.split("\n").each do |line|
|
|
69
|
-
STDERR.puts " " *
|
|
70
|
+
STDERR.puts " " * thread[:indent] + @prefix + line
|
|
70
71
|
end
|
|
71
72
|
end
|
|
72
73
|
with_indent(&block) if block
|
|
@@ -95,6 +96,10 @@ module Blower
|
|
|
95
96
|
define_helper :error
|
|
96
97
|
define_helper :fatal
|
|
97
98
|
|
|
99
|
+
def thread
|
|
100
|
+
Thread.current
|
|
101
|
+
end
|
|
102
|
+
|
|
98
103
|
end
|
|
99
104
|
|
|
100
105
|
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
require 'monitor'
|
|
2
|
+
require 'base64'
|
|
3
|
+
require 'timeout'
|
|
4
|
+
require 'open3'
|
|
5
|
+
|
|
6
|
+
module Blower
|
|
7
|
+
|
|
8
|
+
class Target
|
|
9
|
+
include MonitorMixin
|
|
10
|
+
extend Forwardable
|
|
11
|
+
|
|
12
|
+
attr_reader :name, :data
|
|
13
|
+
|
|
14
|
+
def_delegators :data, :[], :[]=
|
|
15
|
+
|
|
16
|
+
def initialize (name, ssh: "ssh", scp: "scp")
|
|
17
|
+
@name, @ssh, @scp = name, ssh, scp
|
|
18
|
+
@data = {}
|
|
19
|
+
super()
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Represent the host as a string.
|
|
23
|
+
def to_s
|
|
24
|
+
@name
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Copy files or directories to the host.
|
|
28
|
+
# @api private
|
|
29
|
+
def cp (froms, to, as: nil, quiet: false, delete: false)
|
|
30
|
+
as ||= @user
|
|
31
|
+
output = ""
|
|
32
|
+
synchronize do
|
|
33
|
+
[froms].flatten.each do |from|
|
|
34
|
+
if from.is_a?(String)
|
|
35
|
+
to += "/" if to[-1] != "/" && from.is_a?(Array)
|
|
36
|
+
command = ["rsync", "-e", @ssh, "-r"]
|
|
37
|
+
if File.exist?(".blowignore")
|
|
38
|
+
command += ["--exclude-from", ".blowignore"]
|
|
39
|
+
end
|
|
40
|
+
command += ["--delete"] if delete
|
|
41
|
+
command += [*from, ":#{to}"]
|
|
42
|
+
log.trace command.shelljoin, quiet: quiet
|
|
43
|
+
IO.popen(command, in: :close, err: %i(child out)) do |io|
|
|
44
|
+
until io.eof?
|
|
45
|
+
begin
|
|
46
|
+
output << io.read_nonblock(100)
|
|
47
|
+
rescue IO::WaitReadable
|
|
48
|
+
IO.select([io])
|
|
49
|
+
retry
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
io.close
|
|
53
|
+
if !$?.success?
|
|
54
|
+
log.fatal "exit status #{$?.exitstatus}: #{command}", quiet: quiet
|
|
55
|
+
log.fatal output, quiet: quiet
|
|
56
|
+
fail "failed to copy files"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
elsif from.respond_to?(:read)
|
|
60
|
+
cmd = "echo #{Base64.strict_encode64(from.read).shellescape} | base64 -d > #{to.shellescape}"
|
|
61
|
+
sh cmd, quiet: quiet
|
|
62
|
+
else
|
|
63
|
+
fail "Don't know how to copy a #{from.class}: #{from}"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
true
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def sh (command, as: nil, quiet: false)
|
|
71
|
+
marker, output = SecureRandom.hex(32), nil
|
|
72
|
+
ssh do |i, o, _|
|
|
73
|
+
i.puts "echo #{marker}"
|
|
74
|
+
i.puts "sh -c #{command.shellescape} 2>&1"
|
|
75
|
+
i.puts "STATUS_#{marker}=$?"
|
|
76
|
+
i.puts "echo #{marker}"
|
|
77
|
+
i.flush
|
|
78
|
+
o.readline("#{marker}\n")
|
|
79
|
+
output = o.readline("#{marker}\n")[0..-(marker.length + 2)]
|
|
80
|
+
i.puts "echo $STATUS_#{marker}"
|
|
81
|
+
status = o.readline.to_i
|
|
82
|
+
if status != 0
|
|
83
|
+
fail FailedCommand, output
|
|
84
|
+
end
|
|
85
|
+
output
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Produce a Logger prefixed with the host name.
|
|
90
|
+
# @api private
|
|
91
|
+
def log
|
|
92
|
+
@log ||= Logger.instance.with_prefix("on #{name}: ")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
def ssh
|
|
98
|
+
unless @wait
|
|
99
|
+
@stdin, @stdout, @stderr, @wait = Open3.popen3(@ssh)
|
|
100
|
+
end
|
|
101
|
+
yield @stdin, @stdout, @stderr
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
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: 5.0a1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nathan Baum
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2019-02-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: net-ssh
|
|
@@ -120,8 +120,10 @@ files:
|
|
|
120
120
|
- lib/blower/context.rb
|
|
121
121
|
- lib/blower/errors.rb
|
|
122
122
|
- lib/blower/host.rb
|
|
123
|
+
- lib/blower/local.rb
|
|
123
124
|
- lib/blower/logger.rb
|
|
124
125
|
- lib/blower/plugin.rb
|
|
126
|
+
- lib/blower/target.rb
|
|
125
127
|
- lib/blower/util.rb
|
|
126
128
|
- lib/blower/version.rb
|
|
127
129
|
homepage: http://www.github.org/nbaum/blower
|
|
@@ -139,12 +141,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
139
141
|
version: '0'
|
|
140
142
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
141
143
|
requirements:
|
|
142
|
-
- - "
|
|
144
|
+
- - ">"
|
|
143
145
|
- !ruby/object:Gem::Version
|
|
144
|
-
version:
|
|
146
|
+
version: 1.3.1
|
|
145
147
|
requirements: []
|
|
146
|
-
|
|
147
|
-
rubygems_version: 2.7.7
|
|
148
|
+
rubygems_version: 3.0.2
|
|
148
149
|
signing_key:
|
|
149
150
|
specification_version: 4
|
|
150
151
|
summary: Really simple server orchestration
|