octosh 0.0.6 → 0.0.7

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.
data/Gemfile CHANGED
@@ -5,6 +5,7 @@ gem 'net-scp', '1.0.4'
5
5
  gem 'highline', '1.6.15'
6
6
  gem 'uuid', '2.3.5'
7
7
  gem 'parallel', '0.5.19'
8
+ gem 'colorize', '0.5.8'
8
9
 
9
10
  group :test do
10
11
  gem 'rake', '0.9.2.2'
data/Gemfile.lock CHANGED
@@ -1,6 +1,7 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
+ colorize (0.5.8)
4
5
  diff-lcs (1.1.3)
5
6
  highline (1.6.15)
6
7
  macaddr (1.6.1)
@@ -26,6 +27,7 @@ PLATFORMS
26
27
  ruby
27
28
 
28
29
  DEPENDENCIES
30
+ colorize (= 0.5.8)
29
31
  highline (= 1.6.15)
30
32
  net-scp (= 1.0.4)
31
33
  net-ssh (= 2.6.1)
data/lib/octosh/cli.rb CHANGED
@@ -11,41 +11,54 @@ module Octosh
11
11
  class MODE
12
12
  CONFIG = :config
13
13
  INLINE = :inline
14
+ INTERACTIVE = :interactive
14
15
  end
15
16
 
16
17
  @password = nil
17
18
 
18
19
  def self.start
19
- options = OpenStruct.new
20
+ options = {}
21
+
22
+ # Default configuration
23
+ options[:default_user] = "root"
24
+ options[:password_prompt] = true
25
+ options[:uniform_password] = false
26
+ options[:forward_agent] = false
27
+ options[:interactive] = false
20
28
 
21
29
  optparse = OptionParser.new do|opts|
22
30
  opts.banner = "Usage: octosh [options] [octo config file]"
23
31
 
24
32
  opts.on('-c', '--config FILE', 'Octo config file') do |file|
25
- options.config = file
33
+ options[:config] = file
26
34
  end
27
35
 
36
+ opts.on('-i', '--interactive', 'Interactive shell prompt') do
37
+ options[:interactive] = true
38
+ end
39
+
28
40
  opts.on('-b', '--bash COMMAND', 'Explicitly define a command(s) to run on all hosts (Requires --hosts switch)') do |bash|
29
- options.bash = bash
41
+ options[:bash] = bash
30
42
  end
31
43
 
32
44
  opts.on('-s', '--script SCRIPT', 'Path to script to run on all hosts (Requires --hosts switch)') do |script|
33
- options.script = script
45
+ options[:script] = script
34
46
  end
35
47
 
36
48
  opts.on('-r', '--hosts USER@HOST,USER@HOST', Array, 'Lists of hosts to use when using inline execution (with -b or -s switches)') do |list|
37
- options.hosts = list
49
+ options[:hosts] = list
38
50
  end
39
51
 
40
- options.default_user = "root"
41
52
  opts.on('-u', '--user USER', 'User to use when a user isn\'t defined in the --hosts list (ie. just IP address)') do |user|
42
- options.default_user = user
53
+ options[:default_user] = user
43
54
  end
44
55
 
45
- options.password_prompt = true
46
- options.uniform_password = false
47
56
  opts.on('-p', '--uniform-password', 'Uniform password') do
48
- options.uniform_password = true
57
+ options[:uniform_password] = true
58
+ end
59
+
60
+ opts.on('--forward-agent', 'Forward Agent') do
61
+ options[:forward_agent] = true
49
62
  end
50
63
 
51
64
  opts.on_tail('-h', '--help', 'Display this screen' ) do
@@ -54,29 +67,34 @@ module Octosh
54
67
  end
55
68
  end.parse!
56
69
 
57
- if not ARGV.empty? and not options.config
70
+ if not ARGV.empty? and not options[:config]
58
71
  puts "Using config file"
59
- options.config = ARGV[0]
60
- options.mode = Octosh::CLI::MODE::CONFIG
61
- elsif ARGV.empty? and options.config
72
+ options[:config] = ARGV[0]
73
+ options[:mode] = Octosh::CLI::MODE::CONFIG
74
+ elsif ARGV.empty? and options[:config]
62
75
  puts "Using config file"
63
- options.mode = Octosh::CLI::MODE::CONFIG
64
- elsif not ARGV.empty? and options.config
65
- puts "Two config files specified (#{options.config} and #{ARGV[0]}), using explicit config file (#{options.config})"
66
- options.mode = Octosh::CLI::MODE::CONFIG
67
- elsif (options.bash or options.script) and options.hosts
76
+ options[:mode] = Octosh::CLI::MODE::CONFIG
77
+ elsif not ARGV.empty? and options[:config]
78
+ puts "Two config files specified (#{options[:config]} and #{ARGV[0]}), using explicit config file (#{options[:config]})"
79
+ options[:mode] = Octosh::CLI::MODE::CONFIG
80
+ elsif options[:interactive]
81
+ puts "Interactive mode"
82
+ options[:mode] = Octosh::CLI::MODE::INTERACTIVE
83
+ shell = Octosh::Shell.new(options[:hosts], options)
84
+ shell.start
85
+ elsif (options[:bash] or options[:script]) and options[:hosts]
68
86
  puts "Using inline execution"
69
- options.mode = Octosh::CLI::MODE::INLINE
87
+ options[:mode] = Octosh::CLI::MODE::INLINE
70
88
 
71
- if options.bash and options.script
89
+ if options[:bash] and options[:script]
72
90
  "Error -- Cannot specify both an inline command to run (-b) and a script file (-s)"
73
91
  exit
74
- elsif options.bash
92
+ elsif options[:bash]
75
93
  puts "Inline bash"
76
- self.inline_bash(options.hosts, options.bash, options.default_user, options.password_prompt, options.uniform_password)
77
- elsif options.script
94
+ self.inline_bash(options[:hosts], options[:bash], options[:default_user], options)
95
+ elsif options[:script]
78
96
  puts "Call script on each server"
79
- self.exec_script(options.hosts, options.script, options.default_user, options.password_prompt, options.uniform_password)
97
+ self.exec_script(options[:hosts], options[:script], options[:default_user], options)
80
98
  else
81
99
  "Error -- Something broke"
82
100
  exit
@@ -102,11 +120,11 @@ module Octosh
102
120
  end
103
121
  end
104
122
 
105
- def self.inline_bash(hosts, bash, user, password_prompt=true, uniform_password=false)
123
+ def self.inline_bash(hosts, bash, user, options)
106
124
  workers = []
107
125
 
108
126
  hosts.each do |host|
109
- prompt_for_password(password_prompt, uniform_password)
127
+ prompt_for_password(options[:password_prompt], options[:uniform_password])
110
128
  exec_user,hostname = ""
111
129
  if host.include? '@'
112
130
  # USer defined with host, override provided user
@@ -115,7 +133,8 @@ module Octosh
115
133
  exec_user = user
116
134
  hostname = host
117
135
  end
118
- worker = Octosh::Worker.new(hostname, exec_user, @password)
136
+
137
+ worker = Octosh::Worker.new(hostname, exec_user, @password, options)
119
138
  workers << worker
120
139
  end
121
140
 
@@ -124,11 +143,11 @@ module Octosh
124
143
  end
125
144
  end
126
145
 
127
- def self.exec_script(hosts, script, user, password_prompt=true, uniform_password=false)
146
+ def self.exec_script(hosts, script, user, options)
128
147
  workers = []
129
148
 
130
149
  hosts.each do |host|
131
- prompt_for_password(password_prompt, uniform_password)
150
+ prompt_for_password(options[:password_prompt], options[:uniform_password])
132
151
  exec_user,hostname = ""
133
152
  if host.include? '@'
134
153
  # USer defined with host, override provided user
@@ -137,7 +156,7 @@ module Octosh
137
156
  exec_user = user
138
157
  hostname = host
139
158
  end
140
- worker = Octosh::Worker.new(hostname, exec_user, @password)
159
+ worker = Octosh::Worker.new(hostname, exec_user, @password, options)
141
160
  workers << worker
142
161
  end
143
162
 
data/lib/octosh/helper.rb CHANGED
@@ -1,4 +1,18 @@
1
1
  module Octosh
2
+
3
+ module OUTPUT_COLORS
4
+ RED = 31
5
+ GREEN = 32
6
+ YELLOW = 33
7
+ BLUE = 34
8
+ MAGENTA = 35
9
+ CYAN = 36
10
+ end
11
+
12
+ module COLORS
13
+ COLORS = [Octosh::OUTPUT_COLORS::BLUE, Octosh::OUTPUT_COLORS::YELLOW, Octosh::OUTPUT_COLORS::GREEN, Octosh::OUTPUT_COLORS::MAGENTA, Octosh::OUTPUT_COLORS::CYAN]
14
+ end
15
+
2
16
  class Helper
3
17
 
4
18
  def self.password_prompt(prompt="Password: ")
@@ -0,0 +1,56 @@
1
+ module Octosh
2
+ class Shell
3
+
4
+ @workers = []
5
+ @password = nil
6
+
7
+ def initialize(hosts, options)
8
+ colors = Octosh::COLORS::COLORS.dup
9
+ @workers = []
10
+ hosts.each do |host|
11
+ prompt_for_password(options[:password_prompt], options[:uniform_password])
12
+ exec_user,hostname = ""
13
+ if host.include? '@'
14
+ # USer defined with host, override provided user
15
+ exec_user,hostname = host.split('@')
16
+ else
17
+ exec_user = options[:default_user]
18
+ hostname = host
19
+ end
20
+ worker_options = options.dup
21
+ worker_options[:color] = colors.shift
22
+ colors << worker_options[:color]
23
+ worker = Octosh::Worker.new(hostname, exec_user, @password, worker_options)
24
+ worker.connect
25
+ @workers << worker
26
+ end
27
+ end
28
+
29
+ def colorize(text, color_code)
30
+ "\e[#{color_code}m#{text}\e[0m"
31
+ end
32
+
33
+ def prompt_for_password(password_prompt, uniform_password, host="current host")
34
+ if password_prompt
35
+ # Password authentication
36
+ if uniform_password and @password.nil?
37
+ # One password for all hosts
38
+ @password = Octosh::Helper.password_prompt("Password: ")
39
+ elsif not uniform_password
40
+ # One password for each host
41
+ @password = Octosh::Helper.password_prompt("Password for #{host}: ")
42
+ end
43
+ end
44
+ end
45
+
46
+ def start
47
+ while true
48
+ print ">> "
49
+ command = gets
50
+ Parallel.each(@workers, :in_threads => @workers.length) do |worker|
51
+ print colorize(worker.exec(command), worker.options[:color])
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -1,3 +1,3 @@
1
1
  module Octosh
2
- VERSION = "0.0.6"
2
+ VERSION = "0.0.7"
3
3
  end
@@ -3,34 +3,54 @@ require 'net/scp'
3
3
  require 'uuid'
4
4
  require 'pathname'
5
5
 
6
- module Octosh
6
+ module Octosh
7
7
  class Worker
8
8
 
9
- attr_reader :host, :user, :password, :ssh
9
+ attr_reader :host, :user, :password, :ssh, :options
10
+
11
+ @connected = false
10
12
 
11
- def initialize(host, user, pass)
13
+ def initialize(host, user, pass, options = {})
12
14
  @host = host
13
15
  @user = user
14
16
  @password = pass
15
-
16
- @ssh = Net::SSH.start(@host, @user, :password => @password)
17
+ @options = options
18
+ end
19
+
20
+ def connected?
21
+ return @connected
22
+ end
23
+
24
+ def connect_if_not_connected
25
+ if not connected?
26
+ connect
27
+ end
28
+ end
29
+
30
+ def connect
31
+ if not connected?
32
+ forward_agent = @options[:forward_agent] || false
33
+ @ssh = Net::SSH.start(@host, @user, :password => @password, :forward_agent => forward_agent)
34
+ @connected = true
35
+ end
17
36
  end
18
37
 
19
38
  def exec(command)
39
+ connect_if_not_connected
20
40
  channel = @ssh.open_channel do |ch|
21
41
  ch.exec(command) do |ch, success|
22
42
  raise "Error executing #{command}" unless success
23
43
 
24
44
  ch.on_data do |c, data|
25
- puts "#{@host} -- #{data.to_s}"
45
+ return data.to_s
26
46
  end
27
47
 
28
48
  ch.on_extended_data do |c, type, data|
29
- puts "#{@host} -- #{data}"
49
+ return data.to_s
30
50
  end
31
51
 
32
52
  ch.on_close do
33
- #puts "Octosh execution complete!"
53
+ # For now do nothing
34
54
  end
35
55
  end
36
56
  end
@@ -39,14 +59,17 @@ module Octosh
39
59
  end
40
60
 
41
61
  def put(local_path, remote_path)
62
+ connect_if_not_connected
42
63
  @ssh.scp.upload!(local_path, remote_path)
43
64
  end
44
65
 
45
66
  def get(remote_path, local_path)
67
+ connect_if_not_connected
46
68
  @ssh.scp.download!(remote_path, local_path)
47
69
  end
48
70
 
49
71
  def read(remote_path)
72
+ connect_if_not_connected
50
73
  return @ssh.scp.download!(remote_path)
51
74
  end
52
75
 
data/lib/octosh.rb CHANGED
@@ -1,3 +1,4 @@
1
1
  require 'octosh/helper'
2
2
 
3
+ require 'octosh/shell'
3
4
  require 'octosh/worker'
data/spec/worker_spec.rb CHANGED
@@ -3,13 +3,33 @@ require 'spec_helper'
3
3
  describe Octosh::Worker do
4
4
 
5
5
  it "should instantiate" do
6
- # Removed until we setup mocking for the SSH communication
7
- #worker = Octosh::Worker.new('127.0.0.1', 'bob', 'password')
6
+ worker = Octosh::Worker.new('127.0.0.1', 'bob', 'password')
8
7
 
9
- #worker.should_not be_nil
10
- #worker.host.should == '127.0.0.1'
11
- #worker.user.should == 'bob'
12
- #worker.password.should == 'password'
8
+ worker.should_not be_nil
9
+ worker.host.should == '127.0.0.1'
10
+ worker.user.should == 'bob'
11
+ worker.password.should == 'password'
13
12
  end
14
13
 
14
+ it "should #connect_if_not_connected" do
15
+ worker = Octosh::Worker.new('127.0.0.1', 'bob', 'password')
16
+
17
+ Net::SSH.should_receive(:start).with("127.0.0.1", "bob", :password => "password", :forward_agent=>false)
18
+ worker.connect_if_not_connected
19
+ end
20
+
21
+ it "should #exec a command" do
22
+ worker = Octosh::Worker.new('127.0.0.1', 'bob', 'password')
23
+ worker.should_receive(:exec)
24
+ worker.exec("date")
25
+ end
26
+
27
+ it "should #exec_script" do
28
+ worker = Octosh::Worker.new('127.0.0.1', 'bob', 'password')
29
+ worker.should_receive(:put).with("/tmp/somescript.sh", kind_of(String))
30
+ worker.should_receive(:exec).with(kind_of(String))
31
+ worker.should_receive(:exec).with(kind_of(String))
32
+
33
+ worker.exec_script("/tmp/somescript.sh")
34
+ end
15
35
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: octosh
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.7
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-18 00:00:00.000000000 Z
12
+ date: 2012-11-20 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Octosh
15
15
  email:
@@ -29,6 +29,7 @@ files:
29
29
  - lib/octosh.rb
30
30
  - lib/octosh/cli.rb
31
31
  - lib/octosh/helper.rb
32
+ - lib/octosh/shell.rb
32
33
  - lib/octosh/version.rb
33
34
  - lib/octosh/worker.rb
34
35
  - lib/octosh/worker/worker.rb
@@ -47,6 +48,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
47
48
  - - ! '>='
48
49
  - !ruby/object:Gem::Version
49
50
  version: '0'
51
+ segments:
52
+ - 0
53
+ hash: 806447710594273081
50
54
  required_rubygems_version: !ruby/object:Gem::Requirement
51
55
  none: false
52
56
  requirements: