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 +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:
|