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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9cfbaaa18cbeed95710091e851f7869055d0adb3
4
- data.tar.gz: 5dba28a766c20b2c622a814687fe6defdc825982
3
+ metadata.gz: 326c39aae83c23c79f60751abeabe1a8eea19870
4
+ data.tar.gz: e3fa37856f16d53884cc4f79ff7bc762dacc0633
5
5
  SHA512:
6
- metadata.gz: 720985c8137a5283d7b9909c767b39342d06b87ed8d83866f595ac44f18ccd86b6ce7286aca31bc4a54ba3246f148d5ddf63fc43b6351510d3d5100df84aa668
7
- data.tar.gz: f6b2a4d82dc3ffc7ff803a082a8f1374e52b20f7f95c843aa8265197613f63dbec6d89d20962354da9441535bf5cd5458acab48d68408bbd5646fc426f621bec
6
+ metadata.gz: 1705364adfc1bcfa70d7b12cdeccae35d6ba391b2f08192ab1e1380f29bd2495e4945634268237486f8ac250c843752876de2c7574b30ba34634fe4e3b22e3fd
7
+ data.tar.gz: d091fe3d25f24af89309b01cb716e97961d9165528c9abbe5f3441dc2b6343096eb25ecc9babb5562ad0b28633c5e99d34eecb7ef6c29f2eec49459399d7e2e7
@@ -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
- Enumerator.new do |y|
38
- hosts.each do |host|
39
- y << host
40
- end
41
- end.each(&block)
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
- each hosts do |host|
49
- threads << Thread.new do
50
- result[host] = block.(host)
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
- each do
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 "Waiting for server to go away..."
66
- sleep 0.1 while ping(true)
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 "Waiting for server to come back..."
70
- sleep 1.0 until ping(true)
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 = false)
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 = false)
138
+ def sh? (command, quiet: false)
93
139
  log.info "sh?: #{command}" unless quiet
94
- win = true
95
- each do |host|
96
- status = host.sh(command)
97
- win = false if status != 0
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 = false)
105
- log.info "ping" unless quiet
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 "upload data to #{to}", :debug
132
- target.cp(StringIO.new(string), to)
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
- fail "can't find #{task}"
228
+ raise "can't find #{task}"
157
229
  end
158
230
  else
159
- log.info "Running #{task}" do
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
- instance_eval(File.read(file), file)
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 = "root")
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(name, 22).close
35
+ TCPSocket.new(address, 22).close
35
36
  end
36
37
  true
37
- rescue Timeout::ExitException
38
+ rescue Timeout::Error
38
39
  false
39
40
  rescue Errno::ECONNREFUSED
40
41
  false
41
42
  end
42
43
 
43
- def cp (from, to, output = "")
44
+ def cp (froms, to, output = "", user: nil, rsync_command: nil, sh_command: nil)
44
45
  synchronize do
45
- if from.is_a?(String) || from.is_a?(Array)
46
- to += "/" if to[-1] != "/" && from.is_a?(Array)
47
- command = ["rsync", "-e", "ssh -oStrictHostKeyChecking=no", "-r", "--progress", *from,
48
- "#{@user}@#{@name}:#{to}"]
49
- log.trace command.shelljoin
50
- IO.popen(command, in: :close, err: %i(child out)) do |io|
51
- until io.eof?
52
- begin
53
- output << io.read_nonblock(100)
54
- rescue IO::WaitReadable
55
- IO.select([io])
56
- retry
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
- io.close
60
- if !$?.success?
61
- log.fatal "exit status #{$?.exitstatus}: #{command}"
62
- log.raw output
63
- end
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.exec(command) do |_, success|
80
- fail "failed to execute command" unless success
81
- ch.on_data do |_, data|
82
- log.trace "received #{data.bytesize} bytes stdout"
83
- output << data
84
- end
85
- ch.on_extended_data do |_, _, data|
86
- log.trace "received #{data.bytesize} bytes stderr"
87
- output << data.colorize(:red)
88
- end
89
- ch.on_request("exit-status") do |_, data|
90
- result = data.read_long
91
- log.trace "received exit-status #{result}"
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.raw output
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
- block.(self)
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
- if @ssh && @ssh.closed?
120
- log.trace "Discovered the connection to ssh:#{name}@#{user} was lost"
121
- @ssh = nil
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
- @ssh ||= begin
124
- log.trace "Connecting to ssh:#{name}@#{user}"
125
- Net::SSH.start(name, user)
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
- # Log a level-less event
64
- # @deprecated
65
- def raw (a=nil, *b, &c); log(a, nil, *b, &c); end
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 " " * @indent + (@prefix ? @prefix + " " : "") + message
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
@@ -1,3 +1,3 @@
1
1
  module Blower
2
- VERSION = "2.2.0"
2
+ VERSION = "3.1"
3
3
  end
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: 2.2.0
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-02-07 00:00:00.000000000 Z
11
+ date: 2016-04-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: net-ssh