blower 4.7 → 6
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 +4 -0
- data/lib/blower.rb +2 -0
- data/lib/blower/context.rb +84 -25
- data/lib/blower/host.rb +3 -2
- data/lib/blower/local.rb +48 -0
- data/lib/blower/logger.rb +10 -5
- data/lib/blower/target.rb +106 -0
- data/lib/blower/util.rb +6 -0
- data/lib/blower/version.rb +1 -1
- metadata +36 -7
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: b43c163f31a76c68c8327d1c696bda453f2c6fefcfc673fdcf338f8c840e3a6c
         | 
| 4 | 
            +
              data.tar.gz: e33fde4f43223527937dd57d11da5609bb2f124a66dc21dfa71d2f79a119a765
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 11ee37db482dad0ea13f9cdf42cb29007d6018a995f404bc06fb27f971ba3c6c2073f0a44165d0790c75f8cd931d37cd197ffda7d59e2f43368d9ebad14957a1
         | 
| 7 | 
            +
              data.tar.gz: '0860054ed01d01203c7a616ae03fbc0653e0e3519e3516786530a88163207aef206f3da6e4be1ddcf78642314ddbde62d4cd427c1aecbfa64e0b73dc9f7465a9'
         | 
    
        data/bin/blow
    CHANGED
    
    | @@ -2,6 +2,9 @@ | |
| 2 2 | 
             
            require 'blower'
         | 
| 3 3 | 
             
            require 'pathname'
         | 
| 4 4 | 
             
            require 'optparse'
         | 
| 5 | 
            +
            require 'dotenv'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            Dotenv.load
         | 
| 5 8 |  | 
| 6 9 | 
             
            path = []
         | 
| 7 10 |  | 
| @@ -30,6 +33,7 @@ if File.directory?(File.join(Dir.pwd, "lib")) | |
| 30 33 | 
             
            end
         | 
| 31 34 |  | 
| 32 35 | 
             
            context.run "Blowfile", optional: true
         | 
| 36 | 
            +
             | 
| 33 37 | 
             
            begin
         | 
| 34 38 | 
             
              until ARGV.empty?
         | 
| 35 39 | 
             
                context.run ARGV.shift
         | 
    
        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.
         | 
| @@ -90,7 +95,7 @@ module Blower | |
| 90 95 | 
             
                def on (*hosts, quiet: false)
         | 
| 91 96 | 
             
                  let :@hosts => hosts.flatten do
         | 
| 92 97 | 
             
                    log.info "on #{@hosts.map(&:name).join(", ")}", quiet: quiet do
         | 
| 93 | 
            -
                      yield
         | 
| 98 | 
            +
                      yield *hosts
         | 
| 94 99 | 
             
                    end
         | 
| 95 100 | 
             
                  end
         | 
| 96 101 | 
             
                end
         | 
| @@ -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
         | 
| @@ -129,6 +142,12 @@ module Blower | |
| 129 142 | 
             
                  raise e
         | 
| 130 143 | 
             
                end
         | 
| 131 144 |  | 
| 145 | 
            +
                def sh? (command, as: user, on: hosts, quiet: false, once: nil)
         | 
| 146 | 
            +
                  sh command, as: as, on: on, quiet: quiet, once: once
         | 
| 147 | 
            +
                rescue
         | 
| 148 | 
            +
                  nil
         | 
| 149 | 
            +
                end
         | 
| 150 | 
            +
             | 
| 132 151 | 
             
                # Execute a shell command on each host
         | 
| 133 152 | 
             
                # @macro onable
         | 
| 134 153 | 
             
                # @macro asable
         | 
| @@ -136,7 +155,7 @@ module Blower | |
| 136 155 | 
             
                # @macro onceable
         | 
| 137 156 | 
             
                def sh (command, as: user, on: hosts, quiet: false, once: nil)
         | 
| 138 157 | 
             
                  self.once once, quiet: quiet do
         | 
| 139 | 
            -
                    log.info "sh #{command}", quiet: quiet do
         | 
| 158 | 
            +
                    log.info "sh: #{command}", quiet: quiet do
         | 
| 140 159 | 
             
                      hash_map(hosts) do |host|
         | 
| 141 160 | 
             
                        host.sh command, as: as, quiet: quiet
         | 
| 142 161 | 
             
                      end
         | 
| @@ -282,7 +301,7 @@ module Blower | |
| 282 301 |  | 
| 283 302 | 
             
                  def to_s
         | 
| 284 303 | 
             
                    map do |host, data|
         | 
| 285 | 
            -
                      "#{host.name.blue} | 
| 304 | 
            +
                      "#{host.name.blue}\n" + data.strip.to_s.gsub(/^/, "  ")
         | 
| 286 305 | 
             
                    end.join("\n")
         | 
| 287 306 | 
             
                  end
         | 
| 288 307 |  | 
| @@ -302,40 +321,80 @@ module Blower | |
| 302 321 | 
             
                end
         | 
| 303 322 |  | 
| 304 323 | 
             
                def hash_map (hosts = self.hosts)
         | 
| 305 | 
            -
                  HostHash.new.tap do |result|
         | 
| 306 | 
            -
                    each(hosts) do |host|
         | 
| 307 | 
            -
                      result[host] = yield(host)
         | 
| 324 | 
            +
                  hh = HostHash.new.tap do |result|
         | 
| 325 | 
            +
                    each(hosts) do |host, i|
         | 
| 326 | 
            +
                      result[host] = yield(host, i)
         | 
| 327 | 
            +
                    end
         | 
| 328 | 
            +
                  end
         | 
| 329 | 
            +
                  if @singular
         | 
| 330 | 
            +
                    hh.values.first
         | 
| 331 | 
            +
                  else
         | 
| 332 | 
            +
                    hh
         | 
| 333 | 
            +
                  end
         | 
| 334 | 
            +
                end
         | 
| 335 | 
            +
             | 
| 336 | 
            +
                def singularly (flag = true)
         | 
| 337 | 
            +
                  was, @singular = @singular, flag
         | 
| 338 | 
            +
                  yield
         | 
| 339 | 
            +
                ensure
         | 
| 340 | 
            +
                  @singular = was
         | 
| 341 | 
            +
                end
         | 
| 342 | 
            +
             | 
| 343 | 
            +
                def on_one (host = self.hosts.sample, serial: true)
         | 
| 344 | 
            +
                  ret = nil
         | 
| 345 | 
            +
                  each([host], serial: serial) do |host, i|
         | 
| 346 | 
            +
                    on host do
         | 
| 347 | 
            +
                      singularly do
         | 
| 348 | 
            +
                        ret = yield host, i
         | 
| 349 | 
            +
                      end
         | 
| 308 350 | 
             
                    end
         | 
| 309 351 | 
             
                  end
         | 
| 352 | 
            +
                  ret
         | 
| 310 353 | 
             
                end
         | 
| 311 354 |  | 
| 312 355 | 
             
                def on_each (hosts = self.hosts, serial: true)
         | 
| 313 | 
            -
                  each(hosts, serial: serial) do |host|
         | 
| 356 | 
            +
                  each(hosts.dup, serial: serial) do |host, i|
         | 
| 314 357 | 
             
                    on host do
         | 
| 315 | 
            -
                       | 
| 358 | 
            +
                      singularly do
         | 
| 359 | 
            +
                        yield host, i
         | 
| 360 | 
            +
                      end
         | 
| 316 361 | 
             
                    end
         | 
| 317 362 | 
             
                  end
         | 
| 318 363 | 
             
                end
         | 
| 319 364 |  | 
| 365 | 
            +
                def locally (&block)
         | 
| 366 | 
            +
                  on Local.new("<local>") do
         | 
| 367 | 
            +
                    singularly &block
         | 
| 368 | 
            +
                  end
         | 
| 369 | 
            +
                end
         | 
| 370 | 
            +
             | 
| 320 371 | 
             
                def each (hosts = self.hosts, serial: false)
         | 
| 321 372 | 
             
                  fail "No hosts" if hosts.empty?
         | 
| 322 | 
            -
                   | 
| 323 | 
            -
             | 
| 324 | 
            -
             | 
| 325 | 
            -
             | 
| 326 | 
            -
             | 
| 327 | 
            -
             | 
| 328 | 
            -
             | 
| 329 | 
            -
             | 
| 330 | 
            -
             | 
| 331 | 
            -
             | 
| 332 | 
            -
             | 
| 333 | 
            -
             | 
| 334 | 
            -
                         | 
| 373 | 
            +
                  q = (@threads || serial) && Queue.new
         | 
| 374 | 
            +
                  if q && serial
         | 
| 375 | 
            +
                    q.push nil
         | 
| 376 | 
            +
                  elsif q
         | 
| 377 | 
            +
                    @threads.times { q.push nil }
         | 
| 378 | 
            +
                  end
         | 
| 379 | 
            +
                  indent = Thread.current[:indent]
         | 
| 380 | 
            +
                  i = -1
         | 
| 381 | 
            +
                  threads = [hosts].flatten.map.with_index do |host|
         | 
| 382 | 
            +
                    Thread.new do
         | 
| 383 | 
            +
                      Thread.current[:indent] = indent
         | 
| 384 | 
            +
                      begin
         | 
| 385 | 
            +
                        q.pop if q
         | 
| 386 | 
            +
                        yield host, i += 1
         | 
| 387 | 
            +
                      rescue => e
         | 
| 388 | 
            +
                        host.log.error e.message
         | 
| 389 | 
            +
                        hosts.delete host
         | 
| 390 | 
            +
                        @failures |= [host]
         | 
| 391 | 
            +
                      ensure
         | 
| 392 | 
            +
                        q.push nil if q
         | 
| 393 | 
            +
                        sleep @delay if @delay
         | 
| 335 394 | 
             
                      end
         | 
| 336 395 | 
             
                    end
         | 
| 337 | 
            -
                    threads.each(&:join)
         | 
| 338 396 | 
             
                  end
         | 
| 397 | 
            +
                  threads.each(&:join)
         | 
| 339 398 | 
             
                  fail "No hosts remaining" if hosts.empty?
         | 
| 340 399 | 
             
                end
         | 
| 341 400 |  | 
    
        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 |  | 
| @@ -56,6 +55,7 @@ module Blower | |
| 56 55 | 
             
                # Copy files or directories to the host.
         | 
| 57 56 | 
             
                # @api private
         | 
| 58 57 | 
             
                def cp (froms, to, as: nil, quiet: false, delete: false)
         | 
| 58 | 
            +
                  sleep DELAY if defined?(DELAY)
         | 
| 59 59 | 
             
                  as ||= @user
         | 
| 60 60 | 
             
                  output = ""
         | 
| 61 61 | 
             
                  synchronize do
         | 
| @@ -111,6 +111,7 @@ module Blower | |
| 111 111 | 
             
                # Execute a command on the host and return its output.
         | 
| 112 112 | 
             
                # @api private
         | 
| 113 113 | 
             
                def sh (command, as: nil, quiet: false)
         | 
| 114 | 
            +
                  sleep DELAY if defined?(DELAY)
         | 
| 114 115 | 
             
                  as ||= @user
         | 
| 115 116 | 
             
                  output = ""
         | 
| 116 117 | 
             
                  synchronize do
         | 
| @@ -127,7 +128,7 @@ module Blower | |
| 127 128 | 
             
                          end
         | 
| 128 129 | 
             
                          ch.on_extended_data do |_, _, data|
         | 
| 129 130 | 
             
                            log.trace "received #{data.bytesize} bytes stderr", quiet: quiet
         | 
| 130 | 
            -
                            output << data | 
| 131 | 
            +
                            output << data
         | 
| 131 132 | 
             
                          end
         | 
| 132 133 | 
             
                          ch.on_request("exit-status") do |_, data|
         | 
| 133 134 | 
             
                            result = data.read_long
         | 
    
        data/lib/blower/local.rb
    ADDED
    
    | @@ -0,0 +1,48 @@ | |
| 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 | 
            +
                  result = IO.popen(command) do |io|
         | 
| 31 | 
            +
                    io.read
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
                  if $?.success?
         | 
| 34 | 
            +
                    result
         | 
| 35 | 
            +
                  else
         | 
| 36 | 
            +
                    raise "Command failed"
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                # Produce a Logger prefixed with the host name.
         | 
| 41 | 
            +
                # @api private
         | 
| 42 | 
            +
                def log
         | 
| 43 | 
            +
                  @log ||= Logger.instance.with_prefix("on #{name}: ")
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            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/util.rb
    CHANGED
    
    
    
        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: '6'
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Nathan Baum
         | 
| 8 | 
            -
            autorequire: | 
| 8 | 
            +
            autorequire:
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date:  | 
| 11 | 
            +
            date: 2021-06-15 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: net-ssh
         | 
| @@ -94,6 +94,34 @@ dependencies: | |
| 94 94 | 
             
                - - ">="
         | 
| 95 95 | 
             
                  - !ruby/object:Gem::Version
         | 
| 96 96 | 
             
                    version: '0'
         | 
| 97 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 98 | 
            +
              name: ed25519
         | 
| 99 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 100 | 
            +
                requirements:
         | 
| 101 | 
            +
                - - ">="
         | 
| 102 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 103 | 
            +
                    version: '0'
         | 
| 104 | 
            +
              type: :runtime
         | 
| 105 | 
            +
              prerelease: false
         | 
| 106 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 107 | 
            +
                requirements:
         | 
| 108 | 
            +
                - - ">="
         | 
| 109 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 110 | 
            +
                    version: '0'
         | 
| 111 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 112 | 
            +
              name: dotenv
         | 
| 113 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 114 | 
            +
                requirements:
         | 
| 115 | 
            +
                - - ">="
         | 
| 116 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 117 | 
            +
                    version: '0'
         | 
| 118 | 
            +
              type: :runtime
         | 
| 119 | 
            +
              prerelease: false
         | 
| 120 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 121 | 
            +
                requirements:
         | 
| 122 | 
            +
                - - ">="
         | 
| 123 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 124 | 
            +
                    version: '0'
         | 
| 97 125 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 98 126 | 
             
              name: yard
         | 
| 99 127 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -120,15 +148,17 @@ files: | |
| 120 148 | 
             
            - lib/blower/context.rb
         | 
| 121 149 | 
             
            - lib/blower/errors.rb
         | 
| 122 150 | 
             
            - lib/blower/host.rb
         | 
| 151 | 
            +
            - lib/blower/local.rb
         | 
| 123 152 | 
             
            - lib/blower/logger.rb
         | 
| 124 153 | 
             
            - lib/blower/plugin.rb
         | 
| 154 | 
            +
            - lib/blower/target.rb
         | 
| 125 155 | 
             
            - lib/blower/util.rb
         | 
| 126 156 | 
             
            - lib/blower/version.rb
         | 
| 127 157 | 
             
            homepage: http://www.github.org/nbaum/blower
         | 
| 128 158 | 
             
            licenses:
         | 
| 129 159 | 
             
            - MIT
         | 
| 130 160 | 
             
            metadata: {}
         | 
| 131 | 
            -
            post_install_message: | 
| 161 | 
            +
            post_install_message:
         | 
| 132 162 | 
             
            rdoc_options: []
         | 
| 133 163 | 
             
            require_paths:
         | 
| 134 164 | 
             
            - lib
         | 
| @@ -143,9 +173,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 143 173 | 
             
                - !ruby/object:Gem::Version
         | 
| 144 174 | 
             
                  version: '0'
         | 
| 145 175 | 
             
            requirements: []
         | 
| 146 | 
            -
             | 
| 147 | 
            -
             | 
| 148 | 
            -
            signing_key: 
         | 
| 176 | 
            +
            rubygems_version: 3.2.3
         | 
| 177 | 
            +
            signing_key:
         | 
| 149 178 | 
             
            specification_version: 4
         | 
| 150 179 | 
             
            summary: Really simple server orchestration
         | 
| 151 180 | 
             
            test_files: []
         |