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 +1 -0
- data/Gemfile.lock +2 -0
- data/lib/octosh/cli.rb +50 -31
- data/lib/octosh/helper.rb +14 -0
- data/lib/octosh/shell.rb +56 -0
- data/lib/octosh/version.rb +1 -1
- data/lib/octosh/worker/worker.rb +31 -8
- data/lib/octosh.rb +1 -0
- data/spec/worker_spec.rb +26 -6
- metadata +6 -2
data/Gemfile
CHANGED
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 =
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
70
|
+
if not ARGV.empty? and not options[:config]
|
58
71
|
puts "Using config file"
|
59
|
-
options
|
60
|
-
options
|
61
|
-
elsif ARGV.empty? and options
|
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
|
64
|
-
elsif not ARGV.empty? and options
|
65
|
-
puts "Two config files specified (#{options
|
66
|
-
options
|
67
|
-
elsif
|
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
|
87
|
+
options[:mode] = Octosh::CLI::MODE::INLINE
|
70
88
|
|
71
|
-
if options
|
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
|
92
|
+
elsif options[:bash]
|
75
93
|
puts "Inline bash"
|
76
|
-
self.inline_bash(options
|
77
|
-
elsif options
|
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
|
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,
|
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
|
-
|
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,
|
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: ")
|
data/lib/octosh/shell.rb
ADDED
@@ -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
|
data/lib/octosh/version.rb
CHANGED
data/lib/octosh/worker/worker.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
45
|
+
return data.to_s
|
26
46
|
end
|
27
47
|
|
28
48
|
ch.on_extended_data do |c, type, data|
|
29
|
-
|
49
|
+
return data.to_s
|
30
50
|
end
|
31
51
|
|
32
52
|
ch.on_close do
|
33
|
-
#
|
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
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
|
-
|
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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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.
|
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-
|
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:
|