octosh 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
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: