blower 0.2.3 → 2.0.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 52e337abe103b677ac90233cfd4f38121585fe0b
4
- data.tar.gz: 47e87f53486d3b7ec27e2abc78d24098b5759e0d
3
+ metadata.gz: 43a7308c9eaa1ad35d5a1c3942fad1850b32f3dc
4
+ data.tar.gz: 0d43517ed6d42111fd7aa0eabe3f30275ee1d227
5
5
  SHA512:
6
- metadata.gz: 6f250fa25824a1e5c003cadb0102f90305fd6e536b7a4602021a7222ddf7d3259e2870e4dc5d4efd5d1f617367474f541be6ec1f1d784b164c6f621a00bbec87
7
- data.tar.gz: 518c595f5587dc82b6ef0b2fb831c095e237c407c614807e56e7e126d72d58a66c7a530552fd4ca1f3bf61ad990e271c472445a3c5bcfe944ae152d37f8edbda
6
+ metadata.gz: dd7de8fa74c568387e02af7afde83fa52d719a9ccb8658eae7a26ef9fa483b924654bb7bf9fe5f0b97d08b595a7652f72c4994810424b833058d14feb3dbed89
7
+ data.tar.gz: 200326faec848b91ea49c5422f8fb74843c17de9ac622559fd927e19099628a1a5de6d10573655ae5b4bd67056c9cca459b97758a4d3c160867bec3ba486a422
data/bin/blow CHANGED
@@ -1,24 +1,20 @@
1
1
  #!/usr/bin/env ruby
2
-
3
2
  require 'blower'
3
+ require 'pathname'
4
+ require 'optparse'
4
5
 
5
- huff = Blower::Huff.new
6
-
7
- args = ARGV
8
- args = ["main"] if args.empty? && (File.exist?("main") || !Dir["main.*"].empty?)
9
-
10
- if args.empty?
11
- huff.log.fail "Usage: blow TASK..."
12
- exit 1
13
- end
14
-
15
- args = ["hosts.ini", *args] if File.exist?("hosts.ini")
6
+ OptionParser.new do |opts|
7
+ opts.banner = "Usage: blow [options] task..."
8
+ opts.on "-d DIR", "Change directory" do |v|
9
+ Dir.chdir v
10
+ end
11
+ end.order!
16
12
 
17
13
  begin
18
- args.each do |arg|
19
- huff.run arg
14
+ context = Blower::Context.new([".", Dir.pwd])
15
+ context.target = nil
16
+ context.run "Blowfile"
17
+ ARGV.each do |arg|
18
+ context.run arg
20
19
  end
21
- rescue RuntimeError => e
22
- huff.log.fail e.message
23
- exit 1
24
20
  end
@@ -0,0 +1,107 @@
1
+ require 'forwardable'
2
+
3
+ module Blower
4
+
5
+ class Context
6
+ extend Forwardable
7
+
8
+ attr_accessor :path
9
+ attr_accessor :location
10
+ attr_accessor :target
11
+
12
+ def initialize (path)
13
+ @path = path
14
+ @have_seen = {}
15
+ end
16
+
17
+ def log (message, level, &block)
18
+ message = "(on #{target.name}) " + message if target.respond_to?(:name)
19
+ Logger.instance.log(message, level, &block)
20
+ end
21
+
22
+ def stage (message, &block)
23
+ log message, :info, &block
24
+ end
25
+
26
+ def one_host (name = nil, &block)
27
+ each_host [name || target.hosts.sample], &block
28
+ end
29
+
30
+ def each_host (hosts = target, parallel: true, &block)
31
+ hosts.each do |host|
32
+ ctx = dup
33
+ ctx.target = host
34
+ ctx.instance_exec(&block)
35
+ end
36
+ end
37
+
38
+ def reboot
39
+ begin
40
+ sh "shutdown -r now"
41
+ rescue IOError
42
+ sleep 0.1 while ping
43
+ sleep 1.0 until ping
44
+ end
45
+ end
46
+
47
+ def sh (command)
48
+ log "execute #{command}", :debug
49
+ target.sh(command)
50
+ end
51
+
52
+ def cp (from, to)
53
+ log "upload #{Array(from).join(", ")} -> #{to}", :debug
54
+ target.cp(from, to)
55
+ end
56
+
57
+ def write (string, to)
58
+ log "upload data to #{to}", :debug
59
+ target.cp(StringIO.new(string), to)
60
+ end
61
+
62
+ def sh? (command)
63
+ log "execute #{command}", :debug
64
+ target.sh(command)
65
+ rescue Blower::Host::ExecuteError
66
+ false
67
+ end
68
+
69
+ def capture (command)
70
+ stdout = ""
71
+ target.sh(command, stdout)
72
+ stdout
73
+ end
74
+
75
+ def run (task)
76
+ files = []
77
+ @path.each do |dir|
78
+ name = File.join(dir, task)
79
+ name += ".rb" unless File.exist?(name)
80
+ if File.directory?(name)
81
+ dirtask = File.join(name, File.basename(@task))
82
+ dirtask += ".rb" unless File.exist?(dirtask)
83
+ name = dirtask
84
+ blowfile = File.join(name, "Blowfile")
85
+ files << blowfile if File.exist?(blowfile) && !@have_seen[blowfile]
86
+ end
87
+ files << name if File.exist?(name)
88
+ break unless files.empty?
89
+ end
90
+ if files.empty?
91
+ fail "can't find #{task}"
92
+ else
93
+ begin
94
+ old_task, @task = @task, task
95
+ files.each do |file|
96
+ @have_seen[file] = true
97
+ instance_eval(File.read(file), file)
98
+ end
99
+ ensure
100
+ @task = old_task
101
+ end
102
+ end
103
+ end
104
+
105
+ end
106
+
107
+ end
@@ -0,0 +1,112 @@
1
+ require 'net/ssh'
2
+ require 'net/scp'
3
+ require 'monitor'
4
+ require 'colorize'
5
+
6
+ module Blower
7
+
8
+ class Host
9
+ include MonitorMixin
10
+ extend Forwardable
11
+
12
+ attr_accessor :name
13
+ attr_accessor :user
14
+ attr_accessor :data
15
+
16
+ def_delegators :data, :[], :[]=
17
+
18
+ class ExecuteError < Exception
19
+ attr_accessor :status
20
+ def initialize (status)
21
+ @status = status
22
+ end
23
+ end
24
+
25
+ def initialize (name, user = "root")
26
+ @name = name
27
+ @user = user
28
+ @data = {}
29
+ super()
30
+ end
31
+
32
+ def log
33
+ Logger.instance
34
+ end
35
+
36
+ def ping
37
+ Timeout.timeout(1) do
38
+ TCPSocket.new(name, 22).close
39
+ end
40
+ true
41
+ rescue Timeout::ExitException
42
+ false
43
+ rescue Errno::ECONNREFUSED
44
+ false
45
+ end
46
+
47
+ def cp (from, to, output = "")
48
+ synchronize do
49
+ if from.is_a?(String) || from.is_a?(Array)
50
+ to += "/" if to[-1] != "/" && from.is_a?(Array)
51
+ IO.popen(["rsync", "-z", "-r", "--progress", *from, "#{@user}@#{@name}:#{to}"],
52
+ in: :close, err: %i(child out)) do |io|
53
+ until io.eof?
54
+ begin
55
+ output << io.read_nonblock(100)
56
+ rescue IO::WaitReadable
57
+ IO.select([io])
58
+ retry
59
+ end
60
+ end
61
+ end
62
+ elsif from.is_a?(StringIO) or from.is_a?(IO)
63
+ log.info "string -> #{to}" unless quiet
64
+ ssh.scp.upload!(from, to)
65
+ else
66
+ fail "Don't know how to copy a #{from.class}: #{from}"
67
+ end
68
+ end
69
+ true
70
+ rescue => e
71
+ false
72
+ end
73
+
74
+ def sh (command, output = "")
75
+ synchronize do
76
+ result = nil
77
+ ch = ssh.open_channel do |ch|
78
+ ch.exec(command) do |_, success|
79
+ fail "failed to execute command" unless success
80
+ ch.on_data do |_, data|
81
+ output << data
82
+ end
83
+ ch.on_extended_data do |_, _, data|
84
+ output << data.colorize(:red)
85
+ end
86
+ ch.on_request("exit-status") { |_, data| result = data.read_long }
87
+ end
88
+ end
89
+ ch.wait
90
+ if result != 0
91
+ log.fatal "failed on #{name}"
92
+ log.raw output
93
+ exit 1
94
+ end
95
+ result
96
+ end
97
+ end
98
+
99
+ def each (&block)
100
+ block.(self)
101
+ end
102
+
103
+ private
104
+
105
+ def ssh
106
+ @ssh = nil if @ssh && @ssh.closed?
107
+ @ssh ||= Net::SSH.start(name, user)
108
+ end
109
+
110
+ end
111
+
112
+ end
@@ -0,0 +1,36 @@
1
+ module Blower
2
+
3
+ class HostGroup
4
+
5
+ attr_accessor :hosts
6
+ attr_accessor :root
7
+ attr_accessor :location
8
+
9
+ def initialize (hosts)
10
+ @hosts = hosts
11
+ end
12
+
13
+ def sh (command = nil, *args, &block)
14
+ each do |host|
15
+ command = block.() if block
16
+ host.sh(command)
17
+ end
18
+ end
19
+
20
+ def cp (from, to)
21
+ each do |host|
22
+ host.cp(from, to)
23
+ end
24
+ end
25
+
26
+ def each (&block)
27
+ hosts.map do |host|
28
+ Thread.new do
29
+ block.(host)
30
+ end
31
+ end.map(&:join)
32
+ end
33
+
34
+ end
35
+
36
+ end
data/lib/blower/logger.rb CHANGED
@@ -1,44 +1,52 @@
1
- require "colorize"
1
+ require "singleton"
2
2
 
3
3
  module Blower
4
- class Logger
5
4
 
6
- def initialize ()
7
- @logdent = 0
8
- end
9
-
10
- def info (message, **keys, &block)
11
- log(message, :blue, **keys, &block)
12
- end
13
-
14
- def warn (message, **keys, &block)
15
- log(message, :yellow, STDERR, **keys, &block)
16
- end
5
+ class Logger
6
+ include MonitorMixin
7
+ include Singleton
8
+
9
+ COLORS = {
10
+ trace: :light_black,
11
+ debug: :light_black,
12
+ info: :blue,
13
+ warn: :yellow,
14
+ error: :red,
15
+ fatal: :magenta,
16
+ }
17
+
18
+ def initialize
19
+ @indent = 0
20
+ super()
21
+ end
17
22
 
18
- def fail (message, **keys, &block)
19
- log(message, :red, STDERR, **keys, &block)
20
- end
23
+ def trace (a=nil, *b, &c); log(a, :trace, *b, &c); end
24
+ def debug (a=nil, *b, &c); log(a, :debug, *b, &c); end
25
+ def info (a=nil, *b, &c); log(a, :info, *b, &c); end
26
+ def warn (a=nil, *b, &c); log(a, :warn, *b, &c); end
27
+ def error (a=nil, *b, &c); log(a, :error, *b, &c); end
28
+ def fatal (a=nil, *b, &c); log(a, :fatal, *b, &c); end
29
+ def raw (a=nil, *b, &c); log(a, nil, *b, &c); end
30
+
31
+ def log (message = nil, level = :info, &block)
32
+ if message
33
+ synchronize do
34
+ message = message.colorize(COLORS[level]) if level
35
+ puts " " * @indent + message
36
+ end
37
+ end
38
+ begin
39
+ @indent += 1
40
+ block.()
41
+ ensure
42
+ @indent -= 1
43
+ end if block
44
+ end
21
45
 
22
- def win (message, **keys, &block)
23
- log(message, :green, **keys, &block)
24
46
  end
25
47
 
26
- def log (message, color=nil, to=STDOUT, prefix: "", &block)
27
- message.to_s.scan(/(?:[^\n\r]*[\n\r])|(?:[^\n\r]+$)/) do |line|
28
- case line[-1]
29
- when "\n", "\r"
30
- else
31
- line = line + "\n"
32
- end
33
- STDOUT.write " " * @logdent + prefix + (color ? line.colorize(color) : line)
34
- end
35
- begin
36
- @logdent +=1
37
- block.()
38
- ensure
39
- @logdent -= 1
40
- end if block
48
+ def self.log (*args, &block)
49
+ Logger.instance.log(*args, &block)
41
50
  end
42
51
 
43
52
  end
44
- end
@@ -0,0 +1,42 @@
1
+ module Blower
2
+
3
+ class MockHost
4
+ extend Forwardable
5
+
6
+ attr_accessor :log
7
+ attr_accessor :data
8
+
9
+ def_delegators :data, :[], :[]=
10
+
11
+ def initialize (name)
12
+ @log = Logger.new("mock #{name.ljust(15)} | ")
13
+ @data = {}
14
+ end
15
+
16
+ def sh (command, stdout: nil, stdin: nil)
17
+ log.info command
18
+ sleep rand * 0.1
19
+ end
20
+
21
+ def cp (from, to, quiet: false)
22
+ if from.is_a?(String)
23
+ to += File.basename(from) if to[-1] == "/"
24
+ log.info "#{from} -> #{to}" unless quiet
25
+ elsif from.is_a?(Array)
26
+ to += "/" unless to[-1] == "/"
27
+ log.info "#{from.join(", ")} -> #{to}" unless quiet
28
+ elsif from.is_a?(StringIO) or from.is_a?(IO)
29
+ log.info "string -> #{to}" unless quiet
30
+ else
31
+ fail "Don't know how to copy a #{from.class}: #{from}"
32
+ end
33
+ sleep rand * 0.1
34
+ end
35
+
36
+ def each (&block)
37
+ block.(self)
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -1,3 +1,3 @@
1
1
  module Blower
2
- VERSION = "0.2.3"
2
+ VERSION = "2.0.0"
3
3
  end
data/lib/blower.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  require 'blower/logger'
2
- require 'blower/huff'
3
- require 'blower/puff'
2
+ require 'blower/context'
3
+ require 'blower/host'
4
+ require 'blower/host_group'
5
+ require 'blower/mock_host'
4
6
  require 'blower/version'
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: 0.2.3
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Baum
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-11-01 00:00:00.000000000 Z
11
+ date: 2015-11-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: net-ssh
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: net-scp
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.2'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: colorize
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -47,9 +61,11 @@ extra_rdoc_files: []
47
61
  files:
48
62
  - bin/blow
49
63
  - lib/blower.rb
50
- - lib/blower/huff.rb
64
+ - lib/blower/context.rb
65
+ - lib/blower/host.rb
66
+ - lib/blower/host_group.rb
51
67
  - lib/blower/logger.rb
52
- - lib/blower/puff.rb
68
+ - lib/blower/mock_host.rb
53
69
  - lib/blower/version.rb
54
70
  homepage: http://www.github.org/nbaum/blower
55
71
  licenses:
data/lib/blower/huff.rb DELETED
@@ -1,56 +0,0 @@
1
- module Blower
2
- class Huff
3
-
4
- attr_accessor :hosts, :log, :puffs, :env
5
-
6
- def initialize ()
7
- @hosts = ["127.0.0.1"]
8
- @log = Logger.new
9
- @logdent = 0
10
- @puffs = Hash.new { |h, k| h[k] = Puff.new(self, k) }
11
- @env = {}
12
- end
13
-
14
- def hosts (hosts)
15
- log.info "new hosts: #{hosts.join(", ")}"
16
- @hosts = hosts
17
- end
18
-
19
- def ruby (task)
20
- instance_eval(File.read(task), task)
21
- end
22
-
23
- def shell (task)
24
- @hosts.each do |host|
25
- log.info "running #{task} on #{host}" do
26
- puffs[host].shell(task)
27
- end
28
- end
29
- end
30
-
31
- def inventory (task)
32
- hosts File.read(task).split
33
- end
34
-
35
- def run (name)
36
- name = name.to_s
37
- task = File.exist?(name) ? name : Dir[name + ".*"].first
38
- if !task
39
- fail "Can't find #{name}"
40
- elsif File.directory?(task)
41
- Dir.chdir(task) do
42
- run("main")
43
- end
44
- elsif task =~ /\.rb$/
45
- ruby(task)
46
- elsif task =~ /\.sh$/
47
- shell(task)
48
- elsif task =~ /\.ini$/
49
- inventory(task)
50
- else
51
- fail "Don't know what to do with #{task}"
52
- end
53
- end
54
-
55
- end
56
- end
data/lib/blower/puff.rb DELETED
@@ -1,57 +0,0 @@
1
- require "net/ssh"
2
- require "shellwords"
3
-
4
- module Blower
5
- class Puff
6
-
7
- attr_accessor :huff, :host, :log, :env
8
-
9
- def initialize (huff, host)
10
- @huff = huff
11
- @log = huff.log
12
- @host = host
13
- @env = {}
14
- end
15
-
16
- def env2shell ()
17
- huff.env.merge(@env).map do |name, value|
18
- next unless name =~ /\A[A-Za-z\d_]+\z/
19
- "#{name}=#{value.shellescape}"
20
- end.join("\n") + "\n"
21
- end
22
-
23
- def shell (task)
24
- Net::SSH.start(host, "root") do |ssh|
25
- command = File.read(task)
26
- status, signal = nil, nil
27
- ssh.open_channel do |ch|
28
- stdout, stderr = "", ""
29
- ch.exec(env2shell + command) do |_, success|
30
- fail "failed to execute command" unless success
31
- ch.on_data do |_, data|
32
- stdout << data
33
- if i = stdout.rindex(/[\n\r]/)
34
- data, stdout = stdout[0..i], (stdout[(i + 1)..-1] || "")
35
- log.log data
36
- end
37
- end
38
- ch.on_extended_data do |_, _, data|
39
- stderr << data
40
- if i = stderr.rindex(/[\n\r]/)
41
- data, stderr = stderr[0..i], (stderr[(i + 1)..-1] || "")
42
- log.fail data
43
- end
44
- end
45
- ch.on_request("exit-status") { |_, data| status = data.read_long }
46
- ch.on_request("exit-signal") { |_, data| signal = data.read_long }
47
- end
48
- end
49
- ssh.loop
50
- if status != 0
51
- fail "exit status #{status}"
52
- end
53
- end
54
- end
55
-
56
- end
57
- end